Commit f2214e4371

Jakub Konka <kubkon@jakubkonka.com>
2024-01-28 13:45:13
macho: re-enable dSYM but don't write anything to it yet
1 parent d53cb28
src/link/MachO/DebugSymbols.zig
@@ -3,6 +3,7 @@ dwarf: Dwarf,
 file: fs.File,
 
 symtab_cmd: macho.symtab_command = .{},
+uuid_cmd: macho.uuid_command = .{ .uuid = [_]u8{0} ** 16 },
 
 segments: std.ArrayListUnmanaged(macho.segment_command_64) = .{},
 sections: std.ArrayListUnmanaged(macho.section_64) = .{},
@@ -22,9 +23,12 @@ debug_aranges_section_dirty: bool = false,
 debug_info_header_dirty: bool = false,
 debug_line_header_dirty: bool = false,
 
-strtab: StringTable = .{},
 relocs: std.ArrayListUnmanaged(Reloc) = .{},
 
+/// Output synthetic sections
+symtab: std.ArrayListUnmanaged(macho.nlist_64) = .{},
+strtab: std.ArrayListUnmanaged(u8) = .{},
+
 pub const Reloc = struct {
     type: enum {
         direct_load,
@@ -33,12 +37,13 @@ pub const Reloc = struct {
     target: u32,
     offset: u64,
     addend: u32,
-    prev_vaddr: u64,
 };
 
 /// You must call this function *after* `ZigObject.initMetadata()`
 /// has been called to get a viable debug symbols output.
 pub fn initMetadata(self: *DebugSymbols, macho_file: *MachO) !void {
+    try self.strtab.append(self.allocator, 0);
+
     if (self.dwarf_segment_cmd_index == null) {
         self.dwarf_segment_cmd_index = @as(u8, @intCast(self.segments.items.len));
 
@@ -202,34 +207,21 @@ pub fn flushModule(self: *DebugSymbols, macho_file: *MachO) !void {
     const zcu = comp.module orelse return error.LinkingWithoutZigSourceUnimplemented;
 
     for (self.relocs.items) |*reloc| {
-        const sym = switch (reloc.type) {
-            .direct_load => macho_file.getSymbol(.{ .sym_index = reloc.target }),
-            .got_load => blk: {
-                const got_index = macho_file.got_table.lookup.get(.{ .sym_index = reloc.target }).?;
-                const got_entry = macho_file.got_table.entries.items[got_index];
-                break :blk macho_file.getSymbol(got_entry);
-            },
-        };
-        if (sym.n_value == reloc.prev_vaddr) continue;
-
-        const sym_name = switch (reloc.type) {
-            .direct_load => macho_file.getSymbolName(.{ .sym_index = reloc.target }),
-            .got_load => blk: {
-                const got_index = macho_file.got_table.lookup.get(.{ .sym_index = reloc.target }).?;
-                const got_entry = macho_file.got_table.entries.items[got_index];
-                break :blk macho_file.getSymbolName(got_entry);
-            },
+        const sym = macho_file.getSymbol(reloc.target);
+        const sym_name = sym.getName(macho_file);
+        const addr = switch (reloc.type) {
+            .direct_load => sym.getAddress(.{}, macho_file),
+            .got_load => sym.getGotAddress(macho_file),
         };
         const sect = &self.sections.items[self.debug_info_section_index.?];
         const file_offset = sect.offset + reloc.offset;
         log.debug("resolving relocation: {d}@{x} ('{s}') at offset {x}", .{
             reloc.target,
-            sym.n_value,
+            addr,
             sym_name,
             file_offset,
         });
-        try self.file.pwriteAll(mem.asBytes(&sym.n_value), file_offset);
-        reloc.prev_vaddr = sym.n_value;
+        try self.file.pwriteAll(mem.asBytes(&addr), file_offset);
     }
 
     if (self.debug_abbrev_section_dirty) {
@@ -240,7 +232,7 @@ pub fn flushModule(self: *DebugSymbols, macho_file: *MachO) !void {
     if (self.debug_info_header_dirty) {
         // Currently only one compilation unit is supported, so the address range is simply
         // identical to the main program header virtual address and memory size.
-        const text_section = macho_file.sections.items(.header)[macho_file.text_section_index.?];
+        const text_section = macho_file.sections.items(.header)[macho_file.zig_text_sect_index.?];
         const low_pc = text_section.addr;
         const high_pc = text_section.addr + text_section.size;
         try self.dwarf.writeDbgInfoHeader(zcu, low_pc, high_pc);
@@ -250,7 +242,7 @@ pub fn flushModule(self: *DebugSymbols, macho_file: *MachO) !void {
     if (self.debug_aranges_section_dirty) {
         // Currently only one compilation unit is supported, so the address range is simply
         // identical to the main program header virtual address and memory size.
-        const text_section = macho_file.sections.items(.header)[macho_file.text_section_index.?];
+        const text_section = macho_file.sections.items(.header)[macho_file.zig_text_sect_index.?];
         try self.dwarf.writeDbgAranges(text_section.addr, text_section.size);
         self.debug_aranges_section_dirty = false;
     }
@@ -274,17 +266,8 @@ pub fn flushModule(self: *DebugSymbols, macho_file: *MachO) !void {
     try self.writeLinkeditSegmentData(macho_file);
 
     // Write load commands
-    var lc_buffer = std.ArrayList(u8).init(self.allocator);
-    defer lc_buffer.deinit();
-    const lc_writer = lc_buffer.writer();
-
-    try self.writeSegmentHeaders(macho_file, lc_writer);
-    try lc_writer.writeStruct(self.symtab_cmd);
-    try lc_writer.writeStruct(macho_file.uuid_cmd);
-
-    const ncmds = load_commands.calcNumOfLCs(lc_buffer.items);
-    try self.file.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64));
-    try self.writeHeader(macho_file, ncmds, @as(u32, @intCast(lc_buffer.items.len)));
+    const ncmds, const sizeofcmds = try self.writeLoadCommands(macho_file);
+    try self.writeHeader(macho_file, ncmds, sizeofcmds);
 
     assert(!self.debug_abbrev_section_dirty);
     assert(!self.debug_aranges_section_dirty);
@@ -297,8 +280,9 @@ pub fn deinit(self: *DebugSymbols) void {
     self.segments.deinit(gpa);
     self.sections.deinit(gpa);
     self.dwarf.deinit();
-    self.strtab.deinit(gpa);
     self.relocs.deinit(gpa);
+    self.symtab.deinit(gpa);
+    self.strtab.deinit(gpa);
 }
 
 pub fn swapRemoveRelocs(self: *DebugSymbols, target: u32) void {
@@ -322,7 +306,7 @@ fn finalizeDwarfSegment(self: *DebugSymbols, macho_file: *MachO) void {
         // however at the cost of having LINKEDIT preceed DWARF in dSYM binary which we
         // do not want as we want to be able to incrementally move DWARF sections in the
         // file as we please.
-        const last_seg = macho_file.getLinkeditSegmentPtr();
+        const last_seg = macho_file.getLinkeditSegment();
         break :blk last_seg.vmaddr + last_seg.vmsize;
     };
     const dwarf_segment = self.getDwarfSegmentPtr();
@@ -332,8 +316,7 @@ fn finalizeDwarfSegment(self: *DebugSymbols, macho_file: *MachO) void {
         file_size = @max(file_size, header.offset + header.size);
     }
 
-    const target = macho_file.base.comp.root_mod.resolved_target.result;
-    const page_size = MachO.getPageSize(target.cpu.arch);
+    const page_size = macho_file.getPageSize();
     const aligned_size = mem.alignForward(u64, file_size, page_size);
     dwarf_segment.vmaddr = base_vmaddr;
     dwarf_segment.filesize = aligned_size;
@@ -353,54 +336,70 @@ fn finalizeDwarfSegment(self: *DebugSymbols, macho_file: *MachO) void {
     log.debug("found __LINKEDIT segment free space at 0x{x}", .{linkedit.fileoff});
 }
 
-fn writeSegmentHeaders(self: *DebugSymbols, macho_file: *MachO, writer: anytype) !void {
-    // Write segment/section headers from the binary file first.
-    const end = macho_file.linkedit_segment_cmd_index.?;
-    for (macho_file.segments.items[0..end], 0..) |seg, i| {
-        const indexes = macho_file.getSectionIndexes(@as(u8, @intCast(i)));
-        var out_seg = seg;
-        out_seg.fileoff = 0;
-        out_seg.filesize = 0;
-        out_seg.cmdsize = @sizeOf(macho.segment_command_64);
-        out_seg.nsects = 0;
-
-        // Update section headers count; any section with size of 0 is excluded
-        // since it doesn't have any data in the final binary file.
-        for (macho_file.sections.items(.header)[indexes.start..indexes.end]) |header| {
-            if (header.size == 0) continue;
-            out_seg.cmdsize += @sizeOf(macho.section_64);
-            out_seg.nsects += 1;
-        }
+fn writeLoadCommands(self: *DebugSymbols, macho_file: *MachO) !struct { usize, usize } {
+    const gpa = self.allocator;
+    const needed_size = load_commands.calcLoadCommandsSizeDsym(macho_file, self);
+    const buffer = try gpa.alloc(u8, needed_size);
+    defer gpa.free(buffer);
+
+    var stream = std.io.fixedBufferStream(buffer);
+    var cwriter = std.io.countingWriter(stream.writer());
+    const writer = cwriter.writer();
 
-        if (out_seg.nsects == 0 and
-            (mem.eql(u8, out_seg.segName(), "__DATA_CONST") or
-            mem.eql(u8, out_seg.segName(), "__DATA"))) continue;
+    var ncmds: usize = 0;
 
-        try writer.writeStruct(out_seg);
-        for (macho_file.sections.items(.header)[indexes.start..indexes.end]) |header| {
-            if (header.size == 0) continue;
-            var out_header = header;
-            out_header.offset = 0;
-            try writer.writeStruct(out_header);
+    // UUID comes first presumably to speed up lookup by the consumer like lldb.
+    @memcpy(&self.uuid_cmd.uuid, &macho_file.uuid_cmd.uuid);
+    try writer.writeStruct(self.uuid_cmd);
+    ncmds += 1;
+
+    // Segment and section load commands
+    {
+        // Write segment/section headers from the binary file first.
+        const slice = macho_file.sections.slice();
+        var sect_id: usize = 0;
+        for (macho_file.segments.items, 0..) |seg, seg_id| {
+            if (seg_id == macho_file.linkedit_seg_index.?) break;
+            var out_seg = seg;
+            out_seg.fileoff = 0;
+            out_seg.filesize = 0;
+            try writer.writeStruct(out_seg);
+            for (slice.items(.header)[sect_id..][0..seg.nsects]) |header| {
+                var out_header = header;
+                out_header.offset = 0;
+                try writer.writeStruct(out_header);
+            }
+            sect_id += seg.nsects;
         }
-    }
-    // Next, commit DSYM's __LINKEDIT and __DWARF segments headers.
-    for (self.segments.items, 0..) |seg, i| {
-        const indexes = self.getSectionIndexes(@as(u8, @intCast(i)));
-        try writer.writeStruct(seg);
-        for (self.sections.items[indexes.start..indexes.end]) |header| {
-            try writer.writeStruct(header);
+        ncmds += macho_file.segments.items.len - 1;
+
+        // Next, commit DSYM's __LINKEDIT and __DWARF segments headers.
+        sect_id = 0;
+        for (self.segments.items) |seg| {
+            try writer.writeStruct(seg);
+            for (self.sections.items[sect_id..][0..seg.nsects]) |header| {
+                try writer.writeStruct(header);
+            }
+            sect_id += seg.nsects;
         }
+        ncmds += self.segments.items.len;
     }
-}
 
-fn writeHeader(self: *DebugSymbols, macho_file: *MachO, ncmds: u32, sizeofcmds: u32) !void {
-    const target = macho_file.base.comp.root_mod.resolved_target.result;
+    try writer.writeStruct(self.symtab_cmd);
+    ncmds += 1;
+
+    assert(cwriter.bytes_written == needed_size);
 
+    try self.file.pwriteAll(buffer, @sizeOf(macho.mach_header_64));
+
+    return .{ ncmds, buffer.len };
+}
+
+fn writeHeader(self: *DebugSymbols, macho_file: *MachO, ncmds: usize, sizeofcmds: usize) !void {
     var header: macho.mach_header_64 = .{};
     header.filetype = macho.MH_DSYM;
 
-    switch (target.cpu.arch) {
+    switch (macho_file.getTarget().cpu.arch) {
         .aarch64 => {
             header.cputype = macho.CPU_TYPE_ARM64;
             header.cpusubtype = macho.CPU_SUBTYPE_ARM_ALL;
@@ -412,8 +411,8 @@ fn writeHeader(self: *DebugSymbols, macho_file: *MachO, ncmds: u32, sizeofcmds:
         else => return error.UnsupportedCpuArchitecture,
     }
 
-    header.ncmds = ncmds;
-    header.sizeofcmds = sizeofcmds;
+    header.ncmds = @intCast(ncmds);
+    header.sizeofcmds = @intCast(sizeofcmds);
 
     log.debug("writing Mach-O header {}", .{header});
 
@@ -435,91 +434,56 @@ fn writeLinkeditSegmentData(self: *DebugSymbols, macho_file: *MachO) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    try self.writeSymtab(macho_file);
-    try self.writeStrtab();
-
-    const target = macho_file.base.comp.root_mod.resolved_target.result;
-    const page_size = MachO.getPageSize(target.cpu.arch);
+    const page_size = macho_file.getPageSize();
     const seg = &self.segments.items[self.linkedit_segment_cmd_index.?];
+
+    var off = math.cast(u32, seg.fileoff) orelse return error.Overflow;
+    off = try self.writeSymtab(off, macho_file);
+    off = mem.alignForward(u32, off, @alignOf(u64));
+    off = try self.writeStrtab(off);
+    seg.filesize = off - seg.fileoff;
+
     const aligned_size = mem.alignForward(u64, seg.filesize, page_size);
     seg.vmsize = aligned_size;
 }
 
-fn writeSymtab(self: *DebugSymbols, macho_file: *MachO) !void {
+pub fn writeSymtab(self: *DebugSymbols, off: u32, macho_file: *MachO) !u32 {
     const tracy = trace(@src());
     defer tracy.end();
-
     const gpa = self.allocator;
+    const cmd = &self.symtab_cmd;
+    cmd.nsyms = macho_file.symtab_cmd.nsyms;
+    cmd.strsize = macho_file.symtab_cmd.strsize;
+    cmd.symoff = off;
 
-    var locals = std.ArrayList(macho.nlist_64).init(gpa);
-    defer locals.deinit();
-
-    for (macho_file.locals.items, 0..) |sym, sym_id| {
-        if (sym.n_strx == 0) continue; // no name, skip
-        const sym_loc = MachO.SymbolWithLoc{ .sym_index = @as(u32, @intCast(sym_id)) };
-        if (macho_file.symbolIsTemp(sym_loc)) continue; // local temp symbol, skip
-        if (macho_file.getGlobal(macho_file.getSymbolName(sym_loc)) != null) continue; // global symbol is either an export or import, skip
-        var out_sym = sym;
-        out_sym.n_strx = try self.strtab.insert(gpa, macho_file.getSymbolName(sym_loc));
-        try locals.append(out_sym);
-    }
+    try self.symtab.resize(gpa, cmd.nsyms);
+    try self.strtab.ensureUnusedCapacity(gpa, cmd.strsize - 1);
 
-    var exports = std.ArrayList(macho.nlist_64).init(gpa);
-    defer exports.deinit();
-
-    for (macho_file.globals.items) |global| {
-        const sym = macho_file.getSymbol(global);
-        if (sym.undf()) continue; // import, skip
-        var out_sym = sym;
-        out_sym.n_strx = try self.strtab.insert(gpa, macho_file.getSymbolName(global));
-        try exports.append(out_sym);
+    if (macho_file.getZigObject()) |zo| {
+        zo.writeSymtab(macho_file, self);
+    }
+    for (macho_file.objects.items) |index| {
+        try macho_file.getFile(index).?.writeSymtab(macho_file, self);
+    }
+    for (macho_file.dylibs.items) |index| {
+        try macho_file.getFile(index).?.writeSymtab(macho_file, self);
+    }
+    if (macho_file.getInternalObject()) |internal| {
+        internal.writeSymtab(macho_file, self);
     }
 
-    const nlocals = locals.items.len;
-    const nexports = exports.items.len;
-    const nsyms = nlocals + nexports;
-
-    const seg = &self.segments.items[self.linkedit_segment_cmd_index.?];
-    const offset = mem.alignForward(u64, seg.fileoff, @alignOf(macho.nlist_64));
-    const needed_size = nsyms * @sizeOf(macho.nlist_64);
-    seg.filesize = offset + needed_size - seg.fileoff;
-
-    self.symtab_cmd.symoff = @as(u32, @intCast(offset));
-    self.symtab_cmd.nsyms = @as(u32, @intCast(nsyms));
-
-    const locals_off = @as(u32, @intCast(offset));
-    const locals_size = nlocals * @sizeOf(macho.nlist_64);
-    const exports_off = locals_off + locals_size;
-    const exports_size = nexports * @sizeOf(macho.nlist_64);
+    assert(self.strtab.items.len == cmd.strsize);
 
-    log.debug("writing local symbols from 0x{x} to 0x{x}", .{ locals_off, locals_size + locals_off });
-    try self.file.pwriteAll(mem.sliceAsBytes(locals.items), locals_off);
+    try self.file.pwriteAll(mem.sliceAsBytes(self.symtab.items), cmd.symoff);
 
-    log.debug("writing exported symbols from 0x{x} to 0x{x}", .{ exports_off, exports_size + exports_off });
-    try self.file.pwriteAll(mem.sliceAsBytes(exports.items), exports_off);
+    return off + cmd.nsyms * @sizeOf(macho.nlist_64);
 }
 
-fn writeStrtab(self: *DebugSymbols) !void {
-    const tracy = trace(@src());
-    defer tracy.end();
-
-    const seg = &self.segments.items[self.linkedit_segment_cmd_index.?];
-    const symtab_size = @as(u32, @intCast(self.symtab_cmd.nsyms * @sizeOf(macho.nlist_64)));
-    const offset = mem.alignForward(u64, self.symtab_cmd.symoff + symtab_size, @alignOf(u64));
-    const needed_size = mem.alignForward(u64, self.strtab.buffer.items.len, @alignOf(u64));
-
-    seg.filesize = offset + needed_size - seg.fileoff;
-    self.symtab_cmd.stroff = @as(u32, @intCast(offset));
-    self.symtab_cmd.strsize = @as(u32, @intCast(needed_size));
-
-    log.debug("writing string table from 0x{x} to 0x{x}", .{ offset, offset + needed_size });
-
-    try self.file.pwriteAll(self.strtab.buffer.items, offset);
-
-    if (self.strtab.buffer.items.len < needed_size) {
-        // Ensure we are always padded to the actual length of the file.
-        try self.file.pwriteAll(&[_]u8{0}, offset + needed_size);
-    }
+pub fn writeStrtab(self: *DebugSymbols, off: u32) !u32 {
+    const cmd = &self.symtab_cmd;
+    cmd.stroff = off;
+    try self.file.pwriteAll(self.strtab.items, cmd.stroff);
+    return off + cmd.strsize;
 }
 
 pub fn getSectionIndexes(self: *DebugSymbols, segment_index: u8) struct { start: u8, end: u8 } {
@@ -559,7 +523,7 @@ const assert = std.debug.assert;
 const fs = std.fs;
 const link = @import("../../link.zig");
 const load_commands = @import("load_commands.zig");
-const log = std.log.scoped(.dsym);
+const log = std.log.scoped(.link_dsym);
 const macho = std.macho;
 const makeStaticString = MachO.makeStaticString;
 const math = std.math;
src/link/MachO/Dylib.zig
@@ -576,7 +576,7 @@ pub fn calcSymtabSize(self: *Dylib, macho_file: *MachO) !void {
     }
 }
 
-pub fn writeSymtab(self: Dylib, macho_file: *MachO) void {
+pub fn writeSymtab(self: Dylib, macho_file: *MachO, ctx: anytype) void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -585,10 +585,10 @@ pub fn writeSymtab(self: Dylib, macho_file: *MachO) void {
         const file = global.getFile(macho_file) orelse continue;
         if (file.getIndex() != self.index) continue;
         const idx = global.getOutputSymtabIndex(macho_file) orelse continue;
-        const n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
-        macho_file.strtab.appendSliceAssumeCapacity(global.getName(macho_file));
-        macho_file.strtab.appendAssumeCapacity(0);
-        const out_sym = &macho_file.symtab.items[idx];
+        const n_strx = @as(u32, @intCast(ctx.strtab.items.len));
+        ctx.strtab.appendSliceAssumeCapacity(global.getName(macho_file));
+        ctx.strtab.appendAssumeCapacity(0);
+        const out_sym = &ctx.symtab.items[idx];
         out_sym.n_strx = n_strx;
         global.setOutputSym(macho_file, out_sym);
     }
src/link/MachO/file.zig
@@ -90,9 +90,9 @@ pub const File = union(enum) {
         };
     }
 
-    pub fn writeSymtab(file: File, macho_file: *MachO) !void {
+    pub fn writeSymtab(file: File, macho_file: *MachO, ctx: anytype) !void {
         return switch (file) {
-            inline else => |x| x.writeSymtab(macho_file),
+            inline else => |x| x.writeSymtab(macho_file, ctx),
         };
     }
 
src/link/MachO/InternalObject.zig
@@ -139,15 +139,15 @@ pub fn calcSymtabSize(self: *InternalObject, macho_file: *MachO) !void {
     }
 }
 
-pub fn writeSymtab(self: InternalObject, macho_file: *MachO) void {
+pub fn writeSymtab(self: InternalObject, macho_file: *MachO, ctx: anytype) void {
     for (self.symbols.items) |sym_index| {
         const sym = macho_file.getSymbol(sym_index);
         if (sym.getFile(macho_file)) |file| if (file.getIndex() != self.index) continue;
         const idx = sym.getOutputSymtabIndex(macho_file) orelse continue;
-        const n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
-        macho_file.strtab.appendSliceAssumeCapacity(sym.getName(macho_file));
-        macho_file.strtab.appendAssumeCapacity(0);
-        const out_sym = &macho_file.symtab.items[idx];
+        const n_strx = @as(u32, @intCast(ctx.strtab.items.len));
+        ctx.strtab.appendSliceAssumeCapacity(sym.getName(macho_file));
+        ctx.strtab.appendAssumeCapacity(0);
+        const out_sym = &ctx.symtab.items[idx];
         out_sym.n_strx = n_strx;
         sym.setOutputSym(macho_file, out_sym);
     }
src/link/MachO/load_commands.zig
@@ -5,6 +5,7 @@ const macho = std.macho;
 const mem = std.mem;
 
 const Allocator = mem.Allocator;
+const DebugSymbols = @import("DebugSymbols.zig");
 const Dylib = @import("Dylib.zig");
 const MachO = @import("../MachO.zig");
 
@@ -97,6 +98,27 @@ pub fn calcLoadCommandsSize(macho_file: *MachO, assume_max_path_len: bool) u32 {
     return @as(u32, @intCast(sizeofcmds));
 }
 
+pub fn calcLoadCommandsSizeDsym(macho_file: *MachO, dsym: *const DebugSymbols) u32 {
+    var sizeofcmds: u64 = 0;
+
+    // LC_SEGMENT_64
+    sizeofcmds += @sizeOf(macho.segment_command_64) * (macho_file.segments.items.len - 1);
+    for (macho_file.segments.items) |seg| {
+        sizeofcmds += seg.nsects * @sizeOf(macho.section_64);
+    }
+    sizeofcmds += @sizeOf(macho.segment_command_64) * dsym.segments.items.len;
+    for (dsym.segments.items) |seg| {
+        sizeofcmds += seg.nsects * @sizeOf(macho.section_64);
+    }
+
+    // LC_SYMTAB
+    sizeofcmds += @sizeOf(macho.symtab_command);
+    // LC_UUID
+    sizeofcmds += @sizeOf(macho.uuid_command);
+
+    return @as(u32, @intCast(sizeofcmds));
+}
+
 pub fn calcLoadCommandsSizeObject(macho_file: *MachO) u32 {
     var sizeofcmds: u64 = 0;
 
src/link/MachO/Object.zig
@@ -1320,7 +1320,7 @@ pub fn calcStabsSize(self: *Object, macho_file: *MachO) error{Overflow}!void {
     }
 }
 
-pub fn writeSymtab(self: Object, macho_file: *MachO) error{Overflow}!void {
+pub fn writeSymtab(self: Object, macho_file: *MachO, ctx: anytype) error{Overflow}!void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -1329,19 +1329,19 @@ pub fn writeSymtab(self: Object, macho_file: *MachO) error{Overflow}!void {
         const file = sym.getFile(macho_file) orelse continue;
         if (file.getIndex() != self.index) continue;
         const idx = sym.getOutputSymtabIndex(macho_file) orelse continue;
-        const n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
-        macho_file.strtab.appendSliceAssumeCapacity(sym.getName(macho_file));
-        macho_file.strtab.appendAssumeCapacity(0);
-        const out_sym = &macho_file.symtab.items[idx];
+        const n_strx = @as(u32, @intCast(ctx.strtab.items.len));
+        ctx.strtab.appendSliceAssumeCapacity(sym.getName(macho_file));
+        ctx.strtab.appendAssumeCapacity(0);
+        const out_sym = &ctx.symtab.items[idx];
         out_sym.n_strx = n_strx;
         sym.setOutputSym(macho_file, out_sym);
     }
 
     if (macho_file.base.comp.config.debug_format != .strip and self.hasDebugInfo())
-        try self.writeStabs(macho_file);
+        try self.writeStabs(macho_file, ctx);
 }
 
-pub fn writeStabs(self: *const Object, macho_file: *MachO) error{Overflow}!void {
+pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{Overflow}!void {
     const writeFuncStab = struct {
         inline fn writeFuncStab(
             n_strx: u32,
@@ -1349,30 +1349,30 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO) error{Overflow}!void
             n_value: u64,
             size: u64,
             index: u32,
-            ctx: *MachO,
+            context: anytype,
         ) void {
-            ctx.symtab.items[index] = .{
+            context.symtab.items[index] = .{
                 .n_strx = 0,
                 .n_type = macho.N_BNSYM,
                 .n_sect = n_sect,
                 .n_desc = 0,
                 .n_value = n_value,
             };
-            ctx.symtab.items[index + 1] = .{
+            context.symtab.items[index + 1] = .{
                 .n_strx = n_strx,
                 .n_type = macho.N_FUN,
                 .n_sect = n_sect,
                 .n_desc = 0,
                 .n_value = n_value,
             };
-            ctx.symtab.items[index + 2] = .{
+            context.symtab.items[index + 2] = .{
                 .n_strx = 0,
                 .n_type = macho.N_FUN,
                 .n_sect = 0,
                 .n_desc = 0,
                 .n_value = size,
             };
-            ctx.symtab.items[index + 3] = .{
+            context.symtab.items[index + 3] = .{
                 .n_strx = 0,
                 .n_type = macho.N_ENSYM,
                 .n_sect = n_sect,
@@ -1392,10 +1392,10 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO) error{Overflow}!void
 
         // Open scope
         // N_SO comp_dir
-        var n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
-        macho_file.strtab.appendSliceAssumeCapacity(comp_dir);
-        macho_file.strtab.appendAssumeCapacity(0);
-        macho_file.symtab.items[index] = .{
+        var n_strx = @as(u32, @intCast(ctx.strtab.items.len));
+        ctx.strtab.appendSliceAssumeCapacity(comp_dir);
+        ctx.strtab.appendAssumeCapacity(0);
+        ctx.symtab.items[index] = .{
             .n_strx = n_strx,
             .n_type = macho.N_SO,
             .n_sect = 0,
@@ -1404,10 +1404,10 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO) error{Overflow}!void
         };
         index += 1;
         // N_SO tu_name
-        n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
-        macho_file.strtab.appendSliceAssumeCapacity(tu_name);
-        macho_file.strtab.appendAssumeCapacity(0);
-        macho_file.symtab.items[index] = .{
+        n_strx = @as(u32, @intCast(ctx.strtab.items.len));
+        ctx.strtab.appendSliceAssumeCapacity(tu_name);
+        ctx.strtab.appendAssumeCapacity(0);
+        ctx.symtab.items[index] = .{
             .n_strx = n_strx,
             .n_type = macho.N_SO,
             .n_sect = 0,
@@ -1416,18 +1416,18 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO) error{Overflow}!void
         };
         index += 1;
         // N_OSO path
-        n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
+        n_strx = @as(u32, @intCast(ctx.strtab.items.len));
         if (self.archive) |ar| {
-            macho_file.strtab.appendSliceAssumeCapacity(ar.path);
-            macho_file.strtab.appendAssumeCapacity('(');
-            macho_file.strtab.appendSliceAssumeCapacity(self.path);
-            macho_file.strtab.appendAssumeCapacity(')');
-            macho_file.strtab.appendAssumeCapacity(0);
+            ctx.strtab.appendSliceAssumeCapacity(ar.path);
+            ctx.strtab.appendAssumeCapacity('(');
+            ctx.strtab.appendSliceAssumeCapacity(self.path);
+            ctx.strtab.appendAssumeCapacity(')');
+            ctx.strtab.appendAssumeCapacity(0);
         } else {
-            macho_file.strtab.appendSliceAssumeCapacity(self.path);
-            macho_file.strtab.appendAssumeCapacity(0);
+            ctx.strtab.appendSliceAssumeCapacity(self.path);
+            ctx.strtab.appendAssumeCapacity(0);
         }
-        macho_file.symtab.items[index] = .{
+        ctx.symtab.items[index] = .{
             .n_strx = n_strx,
             .n_type = macho.N_OSO,
             .n_sect = 0,
@@ -1448,17 +1448,17 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO) error{Overflow}!void
             const sect = macho_file.sections.items(.header)[sym.out_n_sect];
             const sym_n_strx = n_strx: {
                 const symtab_index = sym.getOutputSymtabIndex(macho_file).?;
-                const osym = macho_file.symtab.items[symtab_index];
+                const osym = ctx.symtab.items[symtab_index];
                 break :n_strx osym.n_strx;
             };
             const sym_n_sect: u8 = if (!sym.flags.abs) @intCast(sym.out_n_sect + 1) else 0;
             const sym_n_value = sym.getAddress(.{}, macho_file);
             const sym_size = sym.getSize(macho_file);
             if (sect.isCode()) {
-                writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, macho_file);
+                writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, ctx);
                 index += 4;
             } else if (sym.visibility == .global) {
-                macho_file.symtab.items[index] = .{
+                ctx.symtab.items[index] = .{
                     .n_strx = sym_n_strx,
                     .n_type = macho.N_GSYM,
                     .n_sect = sym_n_sect,
@@ -1467,7 +1467,7 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO) error{Overflow}!void
                 };
                 index += 1;
             } else {
-                macho_file.symtab.items[index] = .{
+                ctx.symtab.items[index] = .{
                     .n_strx = sym_n_strx,
                     .n_type = macho.N_STSYM,
                     .n_sect = sym_n_sect,
@@ -1480,7 +1480,7 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO) error{Overflow}!void
 
         // Close scope
         // N_SO
-        macho_file.symtab.items[index] = .{
+        ctx.symtab.items[index] = .{
             .n_strx = 0,
             .n_type = macho.N_SO,
             .n_sect = 0,
@@ -1493,10 +1493,10 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO) error{Overflow}!void
         for (self.stab_files.items) |sf| {
             // Open scope
             // N_SO comp_dir
-            var n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
-            macho_file.strtab.appendSliceAssumeCapacity(sf.getCompDir(self));
-            macho_file.strtab.appendAssumeCapacity(0);
-            macho_file.symtab.items[index] = .{
+            var n_strx = @as(u32, @intCast(ctx.strtab.items.len));
+            ctx.strtab.appendSliceAssumeCapacity(sf.getCompDir(self));
+            ctx.strtab.appendAssumeCapacity(0);
+            ctx.symtab.items[index] = .{
                 .n_strx = n_strx,
                 .n_type = macho.N_SO,
                 .n_sect = 0,
@@ -1505,10 +1505,10 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO) error{Overflow}!void
             };
             index += 1;
             // N_SO tu_name
-            n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
-            macho_file.strtab.appendSliceAssumeCapacity(sf.getTuName(self));
-            macho_file.strtab.appendAssumeCapacity(0);
-            macho_file.symtab.items[index] = .{
+            n_strx = @as(u32, @intCast(ctx.strtab.items.len));
+            ctx.strtab.appendSliceAssumeCapacity(sf.getTuName(self));
+            ctx.strtab.appendAssumeCapacity(0);
+            ctx.symtab.items[index] = .{
                 .n_strx = n_strx,
                 .n_type = macho.N_SO,
                 .n_sect = 0,
@@ -1517,10 +1517,10 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO) error{Overflow}!void
             };
             index += 1;
             // N_OSO path
-            n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
-            macho_file.strtab.appendSliceAssumeCapacity(sf.getOsoPath(self));
-            macho_file.strtab.appendAssumeCapacity(0);
-            macho_file.symtab.items[index] = .{
+            n_strx = @as(u32, @intCast(ctx.strtab.items.len));
+            ctx.strtab.appendSliceAssumeCapacity(sf.getOsoPath(self));
+            ctx.strtab.appendAssumeCapacity(0);
+            ctx.symtab.items[index] = .{
                 .n_strx = n_strx,
                 .n_type = macho.N_OSO,
                 .n_sect = 0,
@@ -1536,7 +1536,7 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO) error{Overflow}!void
                 if (!sym.flags.output_symtab) continue;
                 const sym_n_strx = n_strx: {
                     const symtab_index = sym.getOutputSymtabIndex(macho_file).?;
-                    const osym = macho_file.symtab.items[symtab_index];
+                    const osym = ctx.symtab.items[symtab_index];
                     break :n_strx osym.n_strx;
                 };
                 const sym_n_sect: u8 = if (!sym.flags.abs) @intCast(sym.out_n_sect + 1) else 0;
@@ -1544,11 +1544,11 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO) error{Overflow}!void
                 const sym_size = sym.getSize(macho_file);
                 switch (stab.tag) {
                     .func => {
-                        writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, macho_file);
+                        writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, ctx);
                         index += 4;
                     },
                     .global => {
-                        macho_file.symtab.items[index] = .{
+                        ctx.symtab.items[index] = .{
                             .n_strx = sym_n_strx,
                             .n_type = macho.N_GSYM,
                             .n_sect = sym_n_sect,
@@ -1558,7 +1558,7 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO) error{Overflow}!void
                         index += 1;
                     },
                     .static => {
-                        macho_file.symtab.items[index] = .{
+                        ctx.symtab.items[index] = .{
                             .n_strx = sym_n_strx,
                             .n_type = macho.N_STSYM,
                             .n_sect = sym_n_sect,
@@ -1572,7 +1572,7 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO) error{Overflow}!void
 
             // Close scope
             // N_SO
-            macho_file.symtab.items[index] = .{
+            ctx.symtab.items[index] = .{
                 .n_strx = 0,
                 .n_type = macho.N_SO,
                 .n_sect = 0,
src/link/MachO/ZigObject.zig
@@ -310,7 +310,7 @@ pub fn calcSymtabSize(self: *ZigObject, macho_file: *MachO) !void {
     }
 }
 
-pub fn writeSymtab(self: ZigObject, macho_file: *MachO) void {
+pub fn writeSymtab(self: ZigObject, macho_file: *MachO, ctx: anytype) void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -319,10 +319,10 @@ pub fn writeSymtab(self: ZigObject, macho_file: *MachO) void {
         const file = sym.getFile(macho_file) orelse continue;
         if (file.getIndex() != self.index) continue;
         const idx = sym.getOutputSymtabIndex(macho_file) orelse continue;
-        const n_strx = @as(u32, @intCast(macho_file.strtab.items.len));
-        macho_file.strtab.appendSliceAssumeCapacity(sym.getName(macho_file));
-        macho_file.strtab.appendAssumeCapacity(0);
-        const out_sym = &macho_file.symtab.items[idx];
+        const n_strx = @as(u32, @intCast(ctx.strtab.items.len));
+        ctx.strtab.appendSliceAssumeCapacity(sym.getName(macho_file));
+        ctx.strtab.appendAssumeCapacity(0);
+        const out_sym = &ctx.symtab.items[idx];
         out_sym.n_strx = n_strx;
         sym.setOutputSym(macho_file, out_sym);
     }
src/link/Dwarf.zig
@@ -1468,7 +1468,6 @@ pub fn commitDeclState(
                     .target = reloc.target,
                     .offset = reloc.offset + self.getAtom(.di_atom, di_atom_index).off,
                     .addend = 0,
-                    .prev_vaddr = 0,
                 });
             },
             .elf => {}, // TODO
src/link/MachO.zig
@@ -258,7 +258,7 @@ pub fn createEmpty(
 
             switch (comp.config.debug_format) {
                 .strip => {},
-                .dwarf => {
+                .dwarf => if (!self.base.isRelocatable()) {
                     // Create dSYM bundle.
                     log.debug("creating {s}.dSYM bundle", .{emit.sub_path});
 
@@ -283,6 +283,9 @@ pub fn createEmpty(
                         .file = d_sym_file,
                     };
                     try self.d_sym.?.initMetadata(self);
+                } else {
+                    try self.reportUnexpectedError("TODO: implement generating and emitting __DWARF in .o file", .{});
+                    return error.Unexpected;
                 },
                 .code_view => unreachable,
             }
@@ -696,6 +699,7 @@ pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node
     const ncmds, const sizeofcmds, const uuid_cmd_offset = try self.writeLoadCommands();
     try self.writeHeader(ncmds, sizeofcmds);
     try self.writeUuid(uuid_cmd_offset, self.requiresCodeSig());
+    if (self.getDebugSymbols()) |dsym| try dsym.flushModule(self);
 
     if (codesig) |*csig| {
         try self.writeCodeSignature(csig); // code signing always comes last
@@ -2902,16 +2906,16 @@ pub fn writeSymtab(self: *MachO, off: u32) !u32 {
     try self.strtab.ensureUnusedCapacity(gpa, cmd.strsize - 1);
 
     if (self.getZigObject()) |zo| {
-        zo.writeSymtab(self);
+        zo.writeSymtab(self, self);
     }
     for (self.objects.items) |index| {
-        try self.getFile(index).?.writeSymtab(self);
+        try self.getFile(index).?.writeSymtab(self, self);
     }
     for (self.dylibs.items) |index| {
-        try self.getFile(index).?.writeSymtab(self);
+        try self.getFile(index).?.writeSymtab(self, self);
     }
     if (self.getInternalObject()) |internal| {
-        internal.writeSymtab(self);
+        internal.writeSymtab(self, self);
     }
 
     assert(self.strtab.items.len == cmd.strsize);