Commit 45143c6f04

mlugg <mlugg@mlugg.co.uk>
2025-10-10 12:20:19
MachO: emit absolute path in N_OSO stabs
This path being relative is unconventional and causes issues for us if the output artifact is ever used from a different cwd than the one it was built from. The behavior implemented by this commit of always emitting these paths as absolute was actually the behavior in 0.14.x, but it regressed in 0.15.1 due to internal reworks to path handling which led to relative paths being more common in the compiler internals. Resolves: #25433
1 parent d629a14
Changed files (4)
src/link/MachO/Archive.zig
@@ -5,8 +5,9 @@ pub fn deinit(self: *Archive, allocator: Allocator) void {
 }
 
 pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File.HandleIndex, fat_arch: ?fat.Arch) !void {
-    const gpa = macho_file.base.comp.gpa;
-    const diags = &macho_file.base.comp.link_diags;
+    const comp = macho_file.base.comp;
+    const gpa = comp.gpa;
+    const diags = &comp.link_diags;
 
     var arena = std.heap.ArenaAllocator.init(gpa);
     defer arena.deinit();
@@ -55,23 +56,30 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File
             mem.eql(u8, name, SYMDEF_SORTED) or
             mem.eql(u8, name, SYMDEF64_SORTED)) continue;
 
+        const abs_path = try std.fs.path.resolvePosix(gpa, &.{
+            comp.dirs.cwd,
+            path.root_dir.path orelse ".",
+            path.sub_path,
+        });
+        errdefer gpa.free(abs_path);
+
+        const o_basename = try gpa.dupe(u8, name);
+        errdefer gpa.free(o_basename);
+
         const object: Object = .{
             .offset = pos,
             .in_archive = .{
-                .path = .{
-                    .root_dir = path.root_dir,
-                    .sub_path = try gpa.dupe(u8, path.sub_path),
-                },
+                .path = abs_path,
                 .size = hdr_size,
             },
-            .path = Path.initCwd(try gpa.dupe(u8, name)),
+            .path = o_basename,
             .file_handle = handle_index,
             .index = undefined,
             .alive = false,
             .mtime = hdr.date() catch 0,
         };
 
-        log.debug("extracting object '{f}' from archive '{f}'", .{ object.path, path });
+        log.debug("extracting object '{s}' from archive '{f}'", .{ o_basename, path });
 
         try self.objects.append(gpa, object);
     }
src/link/MachO/Object.zig
@@ -1,8 +1,9 @@
 /// Non-zero for fat object files or archives
 offset: u64,
-/// Archive files cannot contain subdirectories, so only the basename is needed
-/// for output. However, the full path is kept for error reporting.
-path: Path,
+/// If `in_archive` is not `null`, this is the basename of the object in the archive. Otherwise,
+/// this is a fully-resolved absolute path, because that is the path we need to embed in stabs to
+/// ensure the output does not depend on its cwd.
+path: []u8,
 file_handle: File.HandleIndex,
 mtime: u64,
 index: File.Index,
@@ -41,8 +42,8 @@ output_symtab_ctx: MachO.SymtabCtx = .{},
 output_ar_state: Archive.ArState = .{},
 
 pub fn deinit(self: *Object, allocator: Allocator) void {
-    if (self.in_archive) |*ar| allocator.free(ar.path.sub_path);
-    allocator.free(self.path.sub_path);
+    if (self.in_archive) |*ar| allocator.free(ar.path);
+    allocator.free(self.path);
     for (self.sections.items(.relocs), self.sections.items(.subsections)) |*relocs, *sub| {
         relocs.deinit(allocator);
         sub.deinit(allocator);
@@ -1703,7 +1704,7 @@ pub fn updateArSize(self: *Object, macho_file: *MachO) !void {
 pub fn writeAr(self: Object, ar_format: Archive.Format, macho_file: *MachO, writer: anytype) !void {
     // Header
     const size = try macho_file.cast(usize, self.output_ar_state.size);
-    const basename = std.fs.path.basename(self.path.sub_path);
+    const basename = std.fs.path.basename(self.path);
     try Archive.writeHeader(basename, size, ar_format, writer);
     // Data
     const file = macho_file.getFileHandle(self.file_handle);
@@ -1756,12 +1757,7 @@ pub fn calcSymtabSize(self: *Object, macho_file: *MachO) void {
         self.calcStabsSize(macho_file);
 }
 
-fn pathLen(path: Path) usize {
-    // +1 for the path separator
-    return (if (path.root_dir.path) |p| p.len + @intFromBool(path.sub_path.len != 0) else 0) + path.sub_path.len;
-}
-
-pub fn calcStabsSize(self: *Object, macho_file: *MachO) void {
+fn calcStabsSize(self: *Object, macho_file: *MachO) void {
     if (self.compile_unit) |cu| {
         const comp_dir = cu.getCompDir(self.*);
         const tu_name = cu.getTuName(self.*);
@@ -1771,9 +1767,11 @@ pub fn calcStabsSize(self: *Object, macho_file: *MachO) void {
         self.output_symtab_ctx.strsize += @as(u32, @intCast(tu_name.len + 1)); // tu_name
 
         if (self.in_archive) |ar| {
-            self.output_symtab_ctx.strsize += @intCast(pathLen(ar.path) + 1 + self.path.basename().len + 1 + 1);
+            // "/path/to/archive.a(object.o)\x00"
+            self.output_symtab_ctx.strsize += @intCast(ar.path.len + self.path.len + 3);
         } else {
-            self.output_symtab_ctx.strsize += @intCast(pathLen(self.path) + 1);
+            // "/path/to/object.o\x00"
+            self.output_symtab_ctx.strsize += @intCast(self.path.len + 1);
         }
 
         for (self.symbols.items, 0..) |sym, i| {
@@ -2018,7 +2016,7 @@ pub fn writeSymtab(self: Object, macho_file: *MachO, ctx: anytype) void {
         self.writeStabs(n_strx, macho_file, ctx);
 }
 
-pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) void {
+fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) void {
     const writeFuncStab = struct {
         inline fn writeFuncStab(
             n_strx: u32,
@@ -2103,38 +2101,20 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
         };
         index += 1;
         if (self.in_archive) |ar| {
-            if (ar.path.root_dir.path) |p| {
-                @memcpy(ctx.strtab.items[n_strx..][0..p.len], p);
-                n_strx += @intCast(p.len);
-                if (ar.path.sub_path.len != 0) {
-                    ctx.strtab.items[n_strx] = '/';
-                    n_strx += 1;
-                }
-            }
-            @memcpy(ctx.strtab.items[n_strx..][0..ar.path.sub_path.len], ar.path.sub_path);
-            n_strx += @intCast(ar.path.sub_path.len);
-            ctx.strtab.items[n_strx] = '(';
-            n_strx += 1;
-            const basename = self.path.basename();
-            @memcpy(ctx.strtab.items[n_strx..][0..basename.len], basename);
-            n_strx += @intCast(basename.len);
-            ctx.strtab.items[n_strx] = ')';
-            n_strx += 1;
-            ctx.strtab.items[n_strx] = 0;
+            // "/path/to/archive.a(object.o)\x00"
+            @memcpy(ctx.strtab.items[n_strx..][0..ar.path.len], ar.path);
+            n_strx += @intCast(ar.path.len);
+            ctx.strtab.items[n_strx..][0] = '(';
             n_strx += 1;
+            @memcpy(ctx.strtab.items[n_strx..][0..self.path.len], self.path);
+            n_strx += @intCast(self.path.len);
+            ctx.strtab.items[n_strx..][0..2].* = ")\x00".*;
+            n_strx += 2;
         } else {
-            if (self.path.root_dir.path) |p| {
-                @memcpy(ctx.strtab.items[n_strx..][0..p.len], p);
-                n_strx += @intCast(p.len);
-                if (self.path.sub_path.len != 0) {
-                    ctx.strtab.items[n_strx] = '/';
-                    n_strx += 1;
-                }
-            }
-            @memcpy(ctx.strtab.items[n_strx..][0..self.path.sub_path.len], self.path.sub_path);
-            n_strx += @intCast(self.path.sub_path.len);
-            ctx.strtab.items[n_strx] = 0;
-            n_strx += 1;
+            // "/path/to/object.o\x00"
+            @memcpy(ctx.strtab.items[n_strx..][0..self.path.len], self.path);
+            ctx.strtab.items[n_strx..][self.path.len] = 0;
+            n_strx += @intCast(self.path.len + 1);
         }
 
         for (self.symbols.items, 0..) |sym, i| {
@@ -2621,11 +2601,9 @@ pub fn fmtPath(self: Object) std.fmt.Alt(Object, formatPath) {
 
 fn formatPath(object: Object, w: *Writer) Writer.Error!void {
     if (object.in_archive) |ar| {
-        try w.print("{f}({s})", .{
-            ar.path, object.path.basename(),
-        });
+        try w.print("{s}({s})", .{ ar.path, object.path });
     } else {
-        try w.print("{f}", .{object.path});
+        try w.writeAll(object.path);
     }
 }
 
@@ -2716,7 +2694,9 @@ const CompileUnit = struct {
 };
 
 const InArchive = struct {
-    path: Path,
+    /// This is a fully-resolved absolute path, because that is the path we need to embed in stabs
+    /// to ensure the output does not depend on its cwd.
+    path: []u8,
     size: u32,
 };
 
@@ -3094,7 +3074,6 @@ const log = std.log.scoped(.link);
 const macho = std.macho;
 const math = std.math;
 const mem = std.mem;
-const Path = std.Build.Cache.Path;
 const Allocator = std.mem.Allocator;
 const Writer = std.Io.Writer;
 
src/link/MachO/relocatable.zig
@@ -191,7 +191,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?
                     pos = mem.alignForward(usize, pos, 2);
                     state.file_off = pos;
                     pos += @sizeOf(Archive.ar_hdr);
-                    pos += mem.alignForward(usize, o.path.basename().len + 1, ptr_width);
+                    pos += mem.alignForward(usize, std.fs.path.basename(o.path).len + 1, ptr_width);
                     pos += try macho_file.cast(usize, state.size);
                 },
                 else => unreachable,
src/link/MachO.zig
@@ -919,7 +919,16 @@ fn addObject(self: *MachO, path: Path, handle: File.HandleIndex, offset: u64) !v
     const tracy = trace(@src());
     defer tracy.end();
 
-    const gpa = self.base.comp.gpa;
+    const comp = self.base.comp;
+    const gpa = comp.gpa;
+
+    const abs_path = try std.fs.path.resolvePosix(gpa, &.{
+        comp.dirs.cwd,
+        path.root_dir.path orelse ".",
+        path.sub_path,
+    });
+    errdefer gpa.free(abs_path);
+
     const mtime: u64 = mtime: {
         const file = self.getFileHandle(handle);
         const stat = file.stat() catch break :mtime 0;
@@ -928,10 +937,7 @@ fn addObject(self: *MachO, path: Path, handle: File.HandleIndex, offset: u64) !v
     const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
     self.files.set(index, .{ .object = .{
         .offset = offset,
-        .path = .{
-            .root_dir = path.root_dir,
-            .sub_path = try gpa.dupe(u8, path.sub_path),
-        },
+        .path = abs_path,
         .file_handle = handle,
         .mtime = mtime,
         .index = index,