authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-10-10 11:20:19+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-10-10 20:33:30+01:00
log45143c6f04c84e8a519aa24e4d79e9385b5d5e2f
treeb36a125db7a5861a14f61154b6200535548eee2a
parentd629a146f5d3a487fa666c0a51f9e0683dde76b8

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

4 files changed, 58 insertions(+), 65 deletions(-)

src/link/MachO.zig+11-5
...@@ -919,7 +919,16 @@ fn addObject(self: *MachO, path: Path, handle: File.HandleIndex, offset: u64) !v...@@ -919,7 +919,16 @@ fn addObject(self: *MachO, path: Path, handle: File.HandleIndex, offset: u64) !v
919 const tracy = trace(@src());919 const tracy = trace(@src());
920 defer tracy.end();920 defer tracy.end();
921921
922 const gpa = self.base.comp.gpa;922 const comp = self.base.comp;
923 const gpa = comp.gpa;
924
925 const abs_path = try std.fs.path.resolvePosix(gpa, &.{
926 comp.dirs.cwd,
927 path.root_dir.path orelse ".",
928 path.sub_path,
929 });
930 errdefer gpa.free(abs_path);
931
923 const mtime: u64 = mtime: {932 const mtime: u64 = mtime: {
924 const file = self.getFileHandle(handle);933 const file = self.getFileHandle(handle);
925 const stat = file.stat() catch break :mtime 0;934 const stat = file.stat() catch break :mtime 0;
...@@ -928,10 +937,7 @@ fn addObject(self: *MachO, path: Path, handle: File.HandleIndex, offset: u64) !v...@@ -928,10 +937,7 @@ fn addObject(self: *MachO, path: Path, handle: File.HandleIndex, offset: u64) !v
928 const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));937 const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
929 self.files.set(index, .{ .object = .{938 self.files.set(index, .{ .object = .{
930 .offset = offset,939 .offset = offset,
931 .path = .{940 .path = abs_path,
932 .root_dir = path.root_dir,
933 .sub_path = try gpa.dupe(u8, path.sub_path),
934 },
935 .file_handle = handle,941 .file_handle = handle,
936 .mtime = mtime,942 .mtime = mtime,
937 .index = index,943 .index = index,
src/link/MachO/Archive.zig+16-8
...@@ -5,8 +5,9 @@ pub fn deinit(self: *Archive, allocator: Allocator) void {...@@ -5,8 +5,9 @@ pub fn deinit(self: *Archive, allocator: Allocator) void {
5}5}
66
7pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File.HandleIndex, fat_arch: ?fat.Arch) !void {7pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File.HandleIndex, fat_arch: ?fat.Arch) !void {
8 const gpa = macho_file.base.comp.gpa;8 const comp = macho_file.base.comp;
9 const diags = &macho_file.base.comp.link_diags;9 const gpa = comp.gpa;
10 const diags = &comp.link_diags;
1011
11 var arena = std.heap.ArenaAllocator.init(gpa);12 var arena = std.heap.ArenaAllocator.init(gpa);
12 defer arena.deinit();13 defer arena.deinit();
...@@ -55,23 +56,30 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File...@@ -55,23 +56,30 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File
55 mem.eql(u8, name, SYMDEF_SORTED) or56 mem.eql(u8, name, SYMDEF_SORTED) or
56 mem.eql(u8, name, SYMDEF64_SORTED)) continue;57 mem.eql(u8, name, SYMDEF64_SORTED)) continue;
5758
59 const abs_path = try std.fs.path.resolvePosix(gpa, &.{
60 comp.dirs.cwd,
61 path.root_dir.path orelse ".",
62 path.sub_path,
63 });
64 errdefer gpa.free(abs_path);
65
66 const o_basename = try gpa.dupe(u8, name);
67 errdefer gpa.free(o_basename);
68
58 const object: Object = .{69 const object: Object = .{
59 .offset = pos,70 .offset = pos,
60 .in_archive = .{71 .in_archive = .{
61 .path = .{72 .path = abs_path,
62 .root_dir = path.root_dir,
63 .sub_path = try gpa.dupe(u8, path.sub_path),
64 },
65 .size = hdr_size,73 .size = hdr_size,
66 },74 },
67 .path = Path.initCwd(try gpa.dupe(u8, name)),75 .path = o_basename,
68 .file_handle = handle_index,76 .file_handle = handle_index,
69 .index = undefined,77 .index = undefined,
70 .alive = false,78 .alive = false,
71 .mtime = hdr.date() catch 0,79 .mtime = hdr.date() catch 0,
72 };80 };
7381
74 log.debug("extracting object '{f}' from archive '{f}'", .{ object.path, path });82 log.debug("extracting object '{s}' from archive '{f}'", .{ o_basename, path });
7583
76 try self.objects.append(gpa, object);84 try self.objects.append(gpa, object);
77 }85 }
src/link/MachO/Object.zig+30-51
...@@ -1,8 +1,9 @@...@@ -1,8 +1,9 @@
1/// Non-zero for fat object files or archives1/// Non-zero for fat object files or archives
2offset: u64,2offset: u64,
3/// Archive files cannot contain subdirectories, so only the basename is needed3/// If `in_archive` is not `null`, this is the basename of the object in the archive. Otherwise,
4/// for output. However, the full path is kept for error reporting.4/// this is a fully-resolved absolute path, because that is the path we need to embed in stabs to
5path: Path,5/// ensure the output does not depend on its cwd.
6path: []u8,
6file_handle: File.HandleIndex,7file_handle: File.HandleIndex,
7mtime: u64,8mtime: u64,
8index: File.Index,9index: File.Index,
...@@ -41,8 +42,8 @@ output_symtab_ctx: MachO.SymtabCtx = .{},...@@ -41,8 +42,8 @@ output_symtab_ctx: MachO.SymtabCtx = .{},
41output_ar_state: Archive.ArState = .{},42output_ar_state: Archive.ArState = .{},
4243
43pub fn deinit(self: *Object, allocator: Allocator) void {44pub fn deinit(self: *Object, allocator: Allocator) void {
44 if (self.in_archive) |*ar| allocator.free(ar.path.sub_path);45 if (self.in_archive) |*ar| allocator.free(ar.path);
45 allocator.free(self.path.sub_path);46 allocator.free(self.path);
46 for (self.sections.items(.relocs), self.sections.items(.subsections)) |*relocs, *sub| {47 for (self.sections.items(.relocs), self.sections.items(.subsections)) |*relocs, *sub| {
47 relocs.deinit(allocator);48 relocs.deinit(allocator);
48 sub.deinit(allocator);49 sub.deinit(allocator);
...@@ -1703,7 +1704,7 @@ pub fn updateArSize(self: *Object, macho_file: *MachO) !void {...@@ -1703,7 +1704,7 @@ pub fn updateArSize(self: *Object, macho_file: *MachO) !void {
1703pub fn writeAr(self: Object, ar_format: Archive.Format, macho_file: *MachO, writer: anytype) !void {1704pub fn writeAr(self: Object, ar_format: Archive.Format, macho_file: *MachO, writer: anytype) !void {
1704 // Header1705 // Header
1705 const size = try macho_file.cast(usize, self.output_ar_state.size);1706 const size = try macho_file.cast(usize, self.output_ar_state.size);
1706 const basename = std.fs.path.basename(self.path.sub_path);1707 const basename = std.fs.path.basename(self.path);
1707 try Archive.writeHeader(basename, size, ar_format, writer);1708 try Archive.writeHeader(basename, size, ar_format, writer);
1708 // Data1709 // Data
1709 const file = macho_file.getFileHandle(self.file_handle);1710 const file = macho_file.getFileHandle(self.file_handle);
...@@ -1756,12 +1757,7 @@ pub fn calcSymtabSize(self: *Object, macho_file: *MachO) void {...@@ -1756,12 +1757,7 @@ pub fn calcSymtabSize(self: *Object, macho_file: *MachO) void {
1756 self.calcStabsSize(macho_file);1757 self.calcStabsSize(macho_file);
1757}1758}
17581759
1759fn pathLen(path: Path) usize {1760fn calcStabsSize(self: *Object, macho_file: *MachO) void {
1760 // +1 for the path separator
1761 return (if (path.root_dir.path) |p| p.len + @intFromBool(path.sub_path.len != 0) else 0) + path.sub_path.len;
1762}
1763
1764pub fn calcStabsSize(self: *Object, macho_file: *MachO) void {
1765 if (self.compile_unit) |cu| {1761 if (self.compile_unit) |cu| {
1766 const comp_dir = cu.getCompDir(self.*);1762 const comp_dir = cu.getCompDir(self.*);
1767 const tu_name = cu.getTuName(self.*);1763 const tu_name = cu.getTuName(self.*);
...@@ -1771,9 +1767,11 @@ pub fn calcStabsSize(self: *Object, macho_file: *MachO) void {...@@ -1771,9 +1767,11 @@ pub fn calcStabsSize(self: *Object, macho_file: *MachO) void {
1771 self.output_symtab_ctx.strsize += @as(u32, @intCast(tu_name.len + 1)); // tu_name1767 self.output_symtab_ctx.strsize += @as(u32, @intCast(tu_name.len + 1)); // tu_name
17721768
1773 if (self.in_archive) |ar| {1769 if (self.in_archive) |ar| {
1774 self.output_symtab_ctx.strsize += @intCast(pathLen(ar.path) + 1 + self.path.basename().len + 1 + 1);1770 // "/path/to/archive.a(object.o)\x00"
1771 self.output_symtab_ctx.strsize += @intCast(ar.path.len + self.path.len + 3);
1775 } else {1772 } else {
1776 self.output_symtab_ctx.strsize += @intCast(pathLen(self.path) + 1);1773 // "/path/to/object.o\x00"
1774 self.output_symtab_ctx.strsize += @intCast(self.path.len + 1);
1777 }1775 }
17781776
1779 for (self.symbols.items, 0..) |sym, i| {1777 for (self.symbols.items, 0..) |sym, i| {
...@@ -2018,7 +2016,7 @@ pub fn writeSymtab(self: Object, macho_file: *MachO, ctx: anytype) void {...@@ -2018,7 +2016,7 @@ pub fn writeSymtab(self: Object, macho_file: *MachO, ctx: anytype) void {
2018 self.writeStabs(n_strx, macho_file, ctx);2016 self.writeStabs(n_strx, macho_file, ctx);
2019}2017}
20202018
2021pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) void {2019fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) void {
2022 const writeFuncStab = struct {2020 const writeFuncStab = struct {
2023 inline fn writeFuncStab(2021 inline fn writeFuncStab(
2024 n_strx: u32,2022 n_strx: u32,
...@@ -2103,38 +2101,20 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v...@@ -2103,38 +2101,20 @@ pub fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) v
2103 };2101 };
2104 index += 1;2102 index += 1;
2105 if (self.in_archive) |ar| {2103 if (self.in_archive) |ar| {
2106 if (ar.path.root_dir.path) |p| {2104 // "/path/to/archive.a(object.o)\x00"
2107 @memcpy(ctx.strtab.items[n_strx..][0..p.len], p);2105 @memcpy(ctx.strtab.items[n_strx..][0..ar.path.len], ar.path);
2108 n_strx += @intCast(p.len);2106 n_strx += @intCast(ar.path.len);
2109 if (ar.path.sub_path.len != 0) {2107 ctx.strtab.items[n_strx..][0] = '(';
2110 ctx.strtab.items[n_strx] = '/';
2111 n_strx += 1;
2112 }
2113 }
2114 @memcpy(ctx.strtab.items[n_strx..][0..ar.path.sub_path.len], ar.path.sub_path);
2115 n_strx += @intCast(ar.path.sub_path.len);
2116 ctx.strtab.items[n_strx] = '(';
2117 n_strx += 1;
2118 const basename = self.path.basename();
2119 @memcpy(ctx.strtab.items[n_strx..][0..basename.len], basename);
2120 n_strx += @intCast(basename.len);
2121 ctx.strtab.items[n_strx] = ')';
2122 n_strx += 1;
2123 ctx.strtab.items[n_strx] = 0;
2124 n_strx += 1;2108 n_strx += 1;
2109 @memcpy(ctx.strtab.items[n_strx..][0..self.path.len], self.path);
2110 n_strx += @intCast(self.path.len);
2111 ctx.strtab.items[n_strx..][0..2].* = ")\x00".*;
2112 n_strx += 2;
2125 } else {2113 } else {
2126 if (self.path.root_dir.path) |p| {2114 // "/path/to/object.o\x00"
2127 @memcpy(ctx.strtab.items[n_strx..][0..p.len], p);2115 @memcpy(ctx.strtab.items[n_strx..][0..self.path.len], self.path);
2128 n_strx += @intCast(p.len);2116 ctx.strtab.items[n_strx..][self.path.len] = 0;
2129 if (self.path.sub_path.len != 0) {2117 n_strx += @intCast(self.path.len + 1);
2130 ctx.strtab.items[n_strx] = '/';
2131 n_strx += 1;
2132 }
2133 }
2134 @memcpy(ctx.strtab.items[n_strx..][0..self.path.sub_path.len], self.path.sub_path);
2135 n_strx += @intCast(self.path.sub_path.len);
2136 ctx.strtab.items[n_strx] = 0;
2137 n_strx += 1;
2138 }2118 }
21392119
2140 for (self.symbols.items, 0..) |sym, i| {2120 for (self.symbols.items, 0..) |sym, i| {
...@@ -2621,11 +2601,9 @@ pub fn fmtPath(self: Object) std.fmt.Alt(Object, formatPath) {...@@ -2621,11 +2601,9 @@ pub fn fmtPath(self: Object) std.fmt.Alt(Object, formatPath) {
26212601
2622fn formatPath(object: Object, w: *Writer) Writer.Error!void {2602fn formatPath(object: Object, w: *Writer) Writer.Error!void {
2623 if (object.in_archive) |ar| {2603 if (object.in_archive) |ar| {
2624 try w.print("{f}({s})", .{2604 try w.print("{s}({s})", .{ ar.path, object.path });
2625 ar.path, object.path.basename(),
2626 });
2627 } else {2605 } else {
2628 try w.print("{f}", .{object.path});2606 try w.writeAll(object.path);
2629 }2607 }
2630}2608}
26312609
...@@ -2716,7 +2694,9 @@ const CompileUnit = struct {...@@ -2716,7 +2694,9 @@ const CompileUnit = struct {
2716};2694};
27172695
2718const InArchive = struct {2696const InArchive = struct {
2719 path: Path,2697 /// This is a fully-resolved absolute path, because that is the path we need to embed in stabs
2698 /// to ensure the output does not depend on its cwd.
2699 path: []u8,
2720 size: u32,2700 size: u32,
2721};2701};
27222702
...@@ -3094,7 +3074,6 @@ const log = std.log.scoped(.link);...@@ -3094,7 +3074,6 @@ const log = std.log.scoped(.link);
3094const macho = std.macho;3074const macho = std.macho;
3095const math = std.math;3075const math = std.math;
3096const mem = std.mem;3076const mem = std.mem;
3097const Path = std.Build.Cache.Path;
3098const Allocator = std.mem.Allocator;3077const Allocator = std.mem.Allocator;
3099const Writer = std.Io.Writer;3078const Writer = std.Io.Writer;
31003079
src/link/MachO/relocatable.zig+1-1
...@@ -191,7 +191,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?...@@ -191,7 +191,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?
191 pos = mem.alignForward(usize, pos, 2);191 pos = mem.alignForward(usize, pos, 2);
192 state.file_off = pos;192 state.file_off = pos;
193 pos += @sizeOf(Archive.ar_hdr);193 pos += @sizeOf(Archive.ar_hdr);
194 pos += mem.alignForward(usize, o.path.basename().len + 1, ptr_width);194 pos += mem.alignForward(usize, std.fs.path.basename(o.path).len + 1, ptr_width);
195 pos += try macho_file.cast(usize, state.size);195 pos += try macho_file.cast(usize, state.size);
196 },196 },
197 else => unreachable,197 else => unreachable,