Commit 27cfbf949a

Jakub Konka <kubkon@jakubkonka.com>
2022-02-12 18:41:16
macho: re-enable creating dSYM bundle
* update number of type abbrevs to match Elf linker * update `DebugSymbols` to write symbol and string tables at the end to match the `MachO` linker * TODO: update segment vm addresses when growing segments in the binary * TODO: store DWARF relocations in linker's interned arena
1 parent 04f3d93
Changed files (2)
src/link/MachO/DebugSymbols.zig
@@ -3,7 +3,8 @@ const DebugSymbols = @This();
 const std = @import("std");
 const assert = std.debug.assert;
 const fs = std.fs;
-const log = std.log.scoped(.dsym);
+const log = std.log.scoped(.link);
+const leb128 = std.leb;
 const macho = std.macho;
 const math = std.math;
 const mem = std.mem;
@@ -22,8 +23,6 @@ const SrcFn = MachO.SrcFn;
 const makeStaticString = MachO.makeStaticString;
 const padToIdeal = MachO.padToIdeal;
 
-const page_size: u16 = 0x1000;
-
 base: *MachO,
 file: fs.File,
 
@@ -49,9 +48,6 @@ uuid_cmd_index: ?u16 = null,
 /// Index into __TEXT,__text section.
 text_section_index: ?u16 = null,
 
-linkedit_off: u16 = page_size,
-linkedit_size: u16 = page_size,
-
 debug_info_section_index: ?u16 = null,
 debug_abbrev_section_index: ?u16 = null,
 debug_str_section_index: ?u16 = null,
@@ -76,7 +72,6 @@ dbg_info_decl_last: ?*TextBlock = null,
 debug_string_table: std.ArrayListUnmanaged(u8) = .{},
 
 load_commands_dirty: bool = false,
-strtab_dirty: bool = false,
 debug_string_table_dirty: bool = false,
 debug_abbrev_section_dirty: bool = false,
 debug_aranges_section_dirty: bool = false,
@@ -87,8 +82,12 @@ const abbrev_compile_unit = 1;
 const abbrev_subprogram = 2;
 const abbrev_subprogram_retvoid = 3;
 const abbrev_base_type = 4;
-const abbrev_pad1 = 5;
-const abbrev_parameter = 6;
+const abbrev_ptr_type = 5;
+const abbrev_struct_type = 6;
+const abbrev_anon_struct_type = 7;
+const abbrev_struct_member = 8;
+const abbrev_pad1 = 9;
+const abbrev_parameter = 10;
 
 /// The reloc offset for the virtual address of a function in its Line Number Program.
 /// Size is a virtual address integer.
@@ -108,30 +107,21 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: Allocator) !void
         try self.load_commands.append(allocator, base_cmd);
         self.load_commands_dirty = true;
     }
+
     if (self.symtab_cmd_index == null) {
         self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len);
-        const base_cmd = self.base.load_commands.items[self.base.symtab_cmd_index.?].symtab;
-        const symtab_size = base_cmd.nsyms * @sizeOf(macho.nlist_64);
-        const symtab_off = self.findFreeSpaceLinkedit(symtab_size, @sizeOf(macho.nlist_64));
-
-        log.debug("found symbol table free space 0x{x} to 0x{x}", .{ symtab_off, symtab_off + symtab_size });
-
-        const strtab_off = self.findFreeSpaceLinkedit(base_cmd.strsize, 1);
-
-        log.debug("found string table free space 0x{x} to 0x{x}", .{ strtab_off, strtab_off + base_cmd.strsize });
-
-        try self.load_commands.append(allocator, .{
+        try self.load_commands.append(self.base.base.allocator, .{
             .symtab = .{
                 .cmdsize = @sizeOf(macho.symtab_command),
-                .symoff = @intCast(u32, symtab_off),
-                .nsyms = base_cmd.nsyms,
-                .stroff = @intCast(u32, strtab_off),
-                .strsize = base_cmd.strsize,
+                .symoff = 0,
+                .nsyms = 0,
+                .stroff = 0,
+                .strsize = 0,
             },
         });
         self.load_commands_dirty = true;
-        self.strtab_dirty = true;
     }
+
     if (self.pagezero_segment_cmd_index == null) {
         self.pagezero_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
         const base_cmd = self.base.load_commands.items[self.base.pagezero_segment_cmd_index.?].segment;
@@ -139,6 +129,7 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: Allocator) !void
         try self.load_commands.append(allocator, .{ .segment = cmd });
         self.load_commands_dirty = true;
     }
+
     if (self.text_segment_cmd_index == null) {
         self.text_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
         const base_cmd = self.base.load_commands.items[self.base.text_segment_cmd_index.?].segment;
@@ -146,6 +137,7 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: Allocator) !void
         try self.load_commands.append(allocator, .{ .segment = cmd });
         self.load_commands_dirty = true;
     }
+
     if (self.data_const_segment_cmd_index == null) outer: {
         if (self.base.data_const_segment_cmd_index == null) break :outer; // __DATA_CONST is optional
         self.data_const_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
@@ -154,6 +146,7 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: Allocator) !void
         try self.load_commands.append(allocator, .{ .segment = cmd });
         self.load_commands_dirty = true;
     }
+
     if (self.data_segment_cmd_index == null) outer: {
         if (self.base.data_segment_cmd_index == null) break :outer; // __DATA is optional
         self.data_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
@@ -162,26 +155,29 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: Allocator) !void
         try self.load_commands.append(allocator, .{ .segment = cmd });
         self.load_commands_dirty = true;
     }
+
     if (self.linkedit_segment_cmd_index == null) {
         self.linkedit_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
         const base_cmd = self.base.load_commands.items[self.base.linkedit_segment_cmd_index.?].segment;
         var cmd = try self.copySegmentCommand(allocator, base_cmd);
-        cmd.inner.vmsize = self.linkedit_size;
-        cmd.inner.fileoff = self.linkedit_off;
-        cmd.inner.filesize = self.linkedit_size;
+        // TODO this needs reworking
+        cmd.inner.vmsize = self.base.page_size;
+        cmd.inner.fileoff = self.base.page_size;
+        cmd.inner.filesize = self.base.page_size;
         try self.load_commands.append(allocator, .{ .segment = cmd });
         self.load_commands_dirty = true;
     }
+
     if (self.dwarf_segment_cmd_index == null) {
         self.dwarf_segment_cmd_index = @intCast(u16, self.load_commands.items.len);
 
         const linkedit = self.load_commands.items[self.linkedit_segment_cmd_index.?].segment;
         const ideal_size: u16 = 200 + 128 + 160 + 250;
-        const needed_size = mem.alignForwardGeneric(u64, padToIdeal(ideal_size), page_size);
-        const off = linkedit.inner.fileoff + linkedit.inner.filesize;
+        const needed_size = mem.alignForwardGeneric(u64, padToIdeal(ideal_size), self.base.page_size);
+        const fileoff = linkedit.inner.fileoff + linkedit.inner.filesize;
         const vmaddr = linkedit.inner.vmaddr + linkedit.inner.vmsize;
 
-        log.debug("found __DWARF segment free space 0x{x} to 0x{x}", .{ off, off + needed_size });
+        log.debug("found __DWARF segment free space 0x{x} to 0x{x}", .{ fileoff, fileoff + needed_size });
 
         try self.load_commands.append(allocator, .{
             .segment = .{
@@ -189,13 +185,14 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: Allocator) !void
                     .segname = makeStaticString("__DWARF"),
                     .vmaddr = vmaddr,
                     .vmsize = needed_size,
-                    .fileoff = off,
+                    .fileoff = fileoff,
                     .filesize = needed_size,
                 },
             },
         });
         self.load_commands_dirty = true;
     }
+
     if (self.debug_str_section_index == null) {
         assert(self.debug_string_table.items.len == 0);
         self.debug_str_section_index = try self.allocateSection(
@@ -205,18 +202,22 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: Allocator) !void
         );
         self.debug_string_table_dirty = true;
     }
+
     if (self.debug_info_section_index == null) {
         self.debug_info_section_index = try self.allocateSection("__debug_info", 200, 0);
         self.debug_info_header_dirty = true;
     }
+
     if (self.debug_abbrev_section_index == null) {
         self.debug_abbrev_section_index = try self.allocateSection("__debug_abbrev", 128, 0);
         self.debug_abbrev_section_dirty = true;
     }
+
     if (self.debug_aranges_section_index == null) {
         self.debug_aranges_section_index = try self.allocateSection("__debug_aranges", 160, 4);
         self.debug_aranges_section_dirty = true;
     }
+
     if (self.debug_line_section_index == null) {
         self.debug_line_section_index = try self.allocateSection("__debug_line", 250, 0);
         self.debug_line_header_dirty = true;
@@ -300,41 +301,91 @@ pub fn flushModule(self: *DebugSymbols, allocator: Allocator, options: link.Opti
         // we can simply append these bytes.
         const abbrev_buf = [_]u8{
             abbrev_compile_unit, DW.TAG.compile_unit, DW.CHILDREN.yes, // header
-            DW.AT.stmt_list, DW.FORM.sec_offset, // offset
-            DW.AT.low_pc,    DW.FORM.addr,
-            DW.AT.high_pc,   DW.FORM.addr,
-            DW.AT.name,      DW.FORM.strp,
-            DW.AT.comp_dir,  DW.FORM.strp,
-            DW.AT.producer,  DW.FORM.strp,
-            DW.AT.language,  DW.FORM.data2,
-            0, 0, // table sentinel
-            abbrev_subprogram, DW.TAG.subprogram, DW.CHILDREN.yes, // header
-            DW.AT.low_pc,    DW.FORM.addr, // start VM address
-            DW.AT.high_pc,   DW.FORM.data4,
-            DW.AT.type,      DW.FORM.ref4,
-            DW.AT.name,      DW.FORM.string,
-            DW.AT.decl_line, DW.FORM.data4,
-            DW.AT.decl_file, DW.FORM.data1,
+            DW.AT.stmt_list,     DW.FORM.sec_offset,  DW.AT.low_pc,
+            DW.FORM.addr,        DW.AT.high_pc,       DW.FORM.addr,
+            DW.AT.name,          DW.FORM.strp,        DW.AT.comp_dir,
+            DW.FORM.strp,        DW.AT.producer,      DW.FORM.strp,
+            DW.AT.language,      DW.FORM.data2,       0,
+            0, // table sentinel
+            abbrev_subprogram,
+            DW.TAG.subprogram,
+            DW.CHILDREN.yes, // header
+            DW.AT.low_pc,
+            DW.FORM.addr,
+            DW.AT.high_pc,
+            DW.FORM.data4,
+            DW.AT.type,
+            DW.FORM.ref4,
+            DW.AT.name,
+            DW.FORM.string,
             0,                         0, // table sentinel
             abbrev_subprogram_retvoid,
             DW.TAG.subprogram, DW.CHILDREN.yes, // header
             DW.AT.low_pc,      DW.FORM.addr,
             DW.AT.high_pc,     DW.FORM.data4,
             DW.AT.name,        DW.FORM.string,
-            DW.AT.decl_line,   DW.FORM.data4,
-            DW.AT.decl_file,   DW.FORM.data1,
-            0, 0, // table sentinel
-            abbrev_base_type, DW.TAG.base_type, DW.CHILDREN.no, // header
-            DW.AT.encoding,   DW.FORM.data1,    DW.AT.byte_size,
-            DW.FORM.data1,    DW.AT.name,       DW.FORM.string,
-            0, 0, // table sentinel
-            abbrev_pad1, DW.TAG.unspecified_type, DW.CHILDREN.no, // header
-            0, 0, // table sentinel
-            abbrev_parameter, DW.TAG.formal_parameter, DW.CHILDREN.no, // header
-            DW.AT.location,   DW.FORM.exprloc,         DW.AT.type,
-            DW.FORM.ref4,     DW.AT.name,              DW.FORM.string,
-            0, 0, // table sentinel
-            0, 0, 0, // section sentinel
+            0,
+            0, // table sentinel
+            abbrev_base_type,
+            DW.TAG.base_type,
+            DW.CHILDREN.no, // header
+            DW.AT.encoding,
+            DW.FORM.data1,
+            DW.AT.byte_size,
+            DW.FORM.data1,
+            DW.AT.name,
+            DW.FORM.string,
+            0,
+            0, // table sentinel
+            abbrev_ptr_type,
+            DW.TAG.pointer_type,
+            DW.CHILDREN.no, // header
+            DW.AT.type,
+            DW.FORM.ref4,
+            0,
+            0, // table sentinel
+            abbrev_struct_type,
+            DW.TAG.structure_type,
+            DW.CHILDREN.yes, // header
+            DW.AT.byte_size,
+            DW.FORM.sdata,
+            DW.AT.name,
+            DW.FORM.string,
+            0,
+            0, // table sentinel
+            abbrev_anon_struct_type,
+            DW.TAG.structure_type,
+            DW.CHILDREN.yes, // header
+            DW.AT.byte_size,
+            DW.FORM.sdata,
+            0,
+            0, // table sentinel
+            abbrev_struct_member,
+            DW.TAG.member,
+            DW.CHILDREN.no, // header
+            DW.AT.name,
+            DW.FORM.string,
+            DW.AT.type,
+            DW.FORM.ref4,
+            DW.AT.data_member_location,
+            DW.FORM.sdata,
+            0,
+            0, // table sentinel
+            abbrev_pad1,
+            DW.TAG.unspecified_type,
+            DW.CHILDREN.no, // header
+            0,
+            0, // table sentinel
+            abbrev_parameter,
+            DW.TAG.formal_parameter, DW.CHILDREN.no, // header
+            DW.AT.location,          DW.FORM.exprloc,
+            DW.AT.type,              DW.FORM.ref4,
+            DW.AT.name,              DW.FORM.string,
+            0,
+            0, // table sentinel
+            0,
+            0,
+            0, // section sentinel
         };
 
         const needed_size = abbrev_buf.len;
@@ -583,13 +634,12 @@ pub fn flushModule(self: *DebugSymbols, allocator: Allocator, options: link.Opti
         }
     }
 
-    try self.writeStringTable();
+    try self.writeLinkeditSegment();
     self.updateDwarfSegment();
     try self.writeLoadCommands(allocator);
     try self.writeHeader();
 
     assert(!self.load_commands_dirty);
-    assert(!self.strtab_dirty);
     assert(!self.debug_abbrev_section_dirty);
     assert(!self.debug_aranges_section_dirty);
     assert(!self.debug_string_table_dirty);
@@ -663,7 +713,7 @@ fn updateDwarfSegment(self: *DebugSymbols) void {
     if (file_size != dwarf_segment.inner.filesize) {
         dwarf_segment.inner.filesize = file_size;
         if (dwarf_segment.inner.vmsize < dwarf_segment.inner.filesize) {
-            dwarf_segment.inner.vmsize = mem.alignForwardGeneric(u64, dwarf_segment.inner.filesize, page_size);
+            dwarf_segment.inner.vmsize = mem.alignForwardGeneric(u64, dwarf_segment.inner.filesize, self.base.page_size);
         }
         self.load_commands_dirty = true;
     }
@@ -719,23 +769,10 @@ fn writeHeader(self: *DebugSymbols) !void {
     try self.file.pwriteAll(mem.asBytes(&header), 0);
 }
 
-fn allocatedSizeLinkedit(self: *DebugSymbols, start: u64) u64 {
-    assert(start > 0);
-    var min_pos: u64 = std.math.maxInt(u64);
-
-    if (self.symtab_cmd_index) |idx| {
-        const symtab = self.load_commands.items[idx].symtab;
-        if (symtab.symoff >= start and symtab.symoff < min_pos) min_pos = symtab.symoff;
-        if (symtab.stroff >= start and symtab.stroff < min_pos) min_pos = symtab.stroff;
-    }
-
-    return min_pos - start;
-}
-
 fn allocatedSize(self: *DebugSymbols, start: u64) u64 {
     const seg = self.load_commands.items[self.dwarf_segment_cmd_index.?].segment;
     assert(start >= seg.inner.fileoff);
-    var min_pos: u64 = seg.inner.fileoff + seg.inner.filesize;
+    var min_pos: u64 = std.math.maxInt(u64);
     for (seg.sections.items) |section| {
         if (section.offset <= start) continue;
         if (section.offset < min_pos) min_pos = section.offset;
@@ -743,102 +780,72 @@ fn allocatedSize(self: *DebugSymbols, start: u64) u64 {
     return min_pos - start;
 }
 
-fn detectAllocCollisionLinkedit(self: *DebugSymbols, start: u64, size: u64) ?u64 {
-    const end = start + padToIdeal(size);
+fn writeLinkeditSegment(self: *DebugSymbols) !void {
+    const tracy = trace(@src());
+    defer tracy.end();
 
-    if (self.symtab_cmd_index) |idx| outer: {
-        if (self.load_commands.items.len == idx) break :outer;
-        const symtab = self.load_commands.items[idx].symtab;
-        {
-            // Symbol table
-            const symsize = symtab.nsyms * @sizeOf(macho.nlist_64);
-            const increased_size = padToIdeal(symsize);
-            const test_end = symtab.symoff + increased_size;
-            if (end > symtab.symoff and start < test_end) {
-                return test_end;
-            }
-        }
-        {
-            // String table
-            const increased_size = padToIdeal(symtab.strsize);
-            const test_end = symtab.stroff + increased_size;
-            if (end > symtab.stroff and start < test_end) {
-                return test_end;
-            }
-        }
-    }
+    const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment;
+    seg.inner.filesize = 0;
 
-    return null;
+    try self.writeSymbolTable();
+    try self.writeStringTable();
 }
 
-fn findFreeSpaceLinkedit(self: *DebugSymbols, object_size: u64, min_alignment: u16) u64 {
-    var start: u64 = self.linkedit_off;
-    while (self.detectAllocCollisionLinkedit(start, object_size)) |item_end| {
-        start = mem.alignForwardGeneric(u64, item_end, min_alignment);
-    }
-    return start;
-}
+fn writeSymbolTable(self: *DebugSymbols) !void {
+    const tracy = trace(@src());
+    defer tracy.end();
 
-fn relocateSymbolTable(self: *DebugSymbols) !void {
+    const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment;
     const symtab = &self.load_commands.items[self.symtab_cmd_index.?].symtab;
-    const nlocals = self.base.locals.items.len;
-    const nglobals = self.base.globals.items.len;
-    const nsyms = nlocals + nglobals;
-
-    if (symtab.nsyms < nsyms) {
-        const needed_size = nsyms * @sizeOf(macho.nlist_64);
-        if (needed_size > self.allocatedSizeLinkedit(symtab.symoff)) {
-            // Move the entire symbol table to a new location
-            const new_symoff = self.findFreeSpaceLinkedit(needed_size, @alignOf(macho.nlist_64));
-            const existing_size = symtab.nsyms * @sizeOf(macho.nlist_64);
-
-            assert(new_symoff + existing_size <= self.linkedit_off + self.linkedit_size); // TODO expand LINKEDIT segment.
-            log.debug("relocating symbol table from 0x{x}-0x{x} to 0x{x}-0x{x}", .{
-                symtab.symoff,
-                symtab.symoff + existing_size,
-                new_symoff,
-                new_symoff + existing_size,
-            });
+    symtab.symoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
 
-            const amt = try self.file.copyRangeAll(symtab.symoff, self.file, new_symoff, existing_size);
-            if (amt != existing_size) return error.InputOutput;
-            symtab.symoff = @intCast(u32, new_symoff);
-        }
-        symtab.nsyms = @intCast(u32, nsyms);
-        self.load_commands_dirty = true;
+    var locals = std.ArrayList(macho.nlist_64).init(self.base.base.allocator);
+    defer locals.deinit();
+
+    for (self.base.locals.items) |sym| {
+        if (sym.n_strx == 0) continue;
+        if (self.base.symbol_resolver.get(sym.n_strx)) |_| continue;
+        try locals.append(sym);
     }
-}
 
-pub fn writeLocalSymbol(self: *DebugSymbols, index: usize) !void {
-    const tracy = trace(@src());
-    defer tracy.end();
-    try self.relocateSymbolTable();
-    const symtab = &self.load_commands.items[self.symtab_cmd_index.?].symtab;
-    const off = symtab.symoff + @sizeOf(macho.nlist_64) * index;
-    log.debug("writing local symbol {} at 0x{x}", .{ index, off });
-    try self.file.pwriteAll(mem.asBytes(&self.base.locals.items[index]), off);
+    const nlocals = locals.items.len;
+    const nexports = self.base.globals.items.len;
+
+    const locals_off = symtab.symoff;
+    const locals_size = nlocals * @sizeOf(macho.nlist_64);
+    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);
+
+    const exports_off = locals_off + locals_size;
+    const exports_size = nexports * @sizeOf(macho.nlist_64);
+    log.debug("writing exported symbols from 0x{x} to 0x{x}", .{ exports_off, exports_size + exports_off });
+    try self.file.pwriteAll(mem.sliceAsBytes(self.base.globals.items), exports_off);
+
+    symtab.nsyms = @intCast(u32, nlocals + nexports);
+    seg.inner.filesize += locals_size + exports_size;
+
+    self.load_commands_dirty = true;
 }
 
 fn writeStringTable(self: *DebugSymbols) !void {
-    if (!self.strtab_dirty) return;
-
     const tracy = trace(@src());
     defer tracy.end();
 
+    const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment;
     const symtab = &self.load_commands.items[self.symtab_cmd_index.?].symtab;
-    const allocated_size = self.allocatedSizeLinkedit(symtab.stroff);
-    const needed_size = mem.alignForwardGeneric(u64, self.base.strtab.items.len, @alignOf(u64));
+    symtab.stroff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
+    symtab.strsize = @intCast(u32, mem.alignForwardGeneric(u64, self.base.strtab.items.len, @alignOf(u64)));
+    seg.inner.filesize += symtab.strsize;
 
-    if (needed_size > allocated_size) {
-        symtab.strsize = 0;
-        symtab.stroff = @intCast(u32, self.findFreeSpaceLinkedit(needed_size, 1));
-    }
-    symtab.strsize = @intCast(u32, needed_size);
     log.debug("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize });
 
     try self.file.pwriteAll(self.base.strtab.items, symtab.stroff);
+
+    if (symtab.strsize > self.base.strtab.items.len) {
+        // This is potentially the last section, so we need to pad it out.
+        try self.file.pwriteAll(&[_]u8{0}, seg.inner.fileoff + seg.inner.filesize - 1);
+    }
     self.load_commands_dirty = true;
-    self.strtab_dirty = false;
 }
 
 pub fn updateDeclLineNumber(self: *DebugSymbols, module: *Module, decl: *const Module.Decl) !void {
@@ -846,14 +853,21 @@ pub fn updateDeclLineNumber(self: *DebugSymbols, module: *Module, decl: *const M
     const tracy = trace(@src());
     defer tracy.end();
 
+    log.debug("updateDeclLineNumber {s}{*}", .{ decl.name, decl });
+
     const func = decl.val.castTag(.function).?.data;
-    const line_off = @intCast(u28, decl.src_line + func.lbrace_line);
+    log.debug("  (decl.src_line={d}, func.lbrace_line={d}, func.rbrace_line={d})", .{
+        decl.src_line,
+        func.lbrace_line,
+        func.rbrace_line,
+    });
+    const line = @intCast(u28, decl.src_line + func.lbrace_line);
 
     const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].segment;
     const shdr = &dwarf_segment.sections.items[self.debug_line_section_index.?];
     const file_pos = shdr.offset + decl.fn_link.macho.off + getRelocDbgLineOff();
     var data: [4]u8 = undefined;
-    leb.writeUnsignedFixed(4, &data, line_off);
+    leb.writeUnsignedFixed(4, &data, line);
     try self.file.pwriteAll(&data, file_pos);
 }
 
@@ -886,7 +900,13 @@ pub fn initDeclDebugBuffers(
             try dbg_line_buffer.ensureTotalCapacity(26);
 
             const func = decl.val.castTag(.function).?.data;
-            const line_off = @intCast(u28, decl.src_line + func.lbrace_line);
+            log.debug("updateFunc {s}{*}", .{ decl.name, func.owner_decl });
+            log.debug("  (decl.src_line={d}, func.lbrace_line={d}, func.rbrace_line={d})", .{
+                decl.src_line,
+                func.lbrace_line,
+                func.rbrace_line,
+            });
+            const line = @intCast(u28, decl.src_line + func.lbrace_line);
 
             dbg_line_buffer.appendSliceAssumeCapacity(&[_]u8{
                 DW.LNS.extended_op,
@@ -902,7 +922,7 @@ pub fn initDeclDebugBuffers(
             // to this function's begin curly.
             assert(getRelocDbgLineOff() == dbg_line_buffer.items.len);
             // Here we use a ULEB128-fixed-4 to make sure this field can be overwritten later.
-            leb.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), line_off);
+            leb.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), line);
 
             dbg_line_buffer.appendAssumeCapacity(DW.LNS.set_file);
             assert(getRelocDbgFileIndex() == dbg_line_buffer.items.len);
@@ -917,7 +937,7 @@ pub fn initDeclDebugBuffers(
 
             // .debug_info subprogram
             const decl_name_with_null = decl.name[0 .. mem.sliceTo(decl.name, 0).len + 1];
-            try dbg_info_buffer.ensureUnusedCapacity(27 + decl_name_with_null.len);
+            try dbg_info_buffer.ensureUnusedCapacity(25 + decl_name_with_null.len);
 
             const fn_ret_type = decl.ty.fnReturnType();
             const fn_ret_has_bits = fn_ret_type.hasRuntimeBits();
@@ -945,8 +965,6 @@ pub fn initDeclDebugBuffers(
                 dbg_info_buffer.items.len += 4; // DW.AT.type,  DW.FORM.ref4
             }
             dbg_info_buffer.appendSliceAssumeCapacity(decl_name_with_null); // DW.AT.name, DW.FORM.string
-            mem.writeIntLittle(u32, dbg_info_buffer.addManyAsArrayAssumeCapacity(4), line_off + 1); // DW.AT.decl_line, DW.FORM.data4
-            dbg_info_buffer.appendAssumeCapacity(file_index); // DW.AT.decl_file, DW.FORM.data1
         },
         else => {
             // TODO implement .debug_info for global variables
@@ -966,7 +984,6 @@ pub fn commitDeclDebugInfo(
     module: *Module,
     decl: *Module.Decl,
     debug_buffers: *DeclDebugBuffers,
-    target: std.Target,
 ) !void {
     const tracy = trace(@src());
     defer tracy.end();
@@ -1097,14 +1114,26 @@ pub fn commitDeclDebugInfo(
     if (dbg_info_buffer.items.len == 0)
         return;
 
+    // We need this for the duration of this function only so that for composite
+    // types such as []const u32, if the type *u32 is non-existent, we create
+    // it synthetically and store the backing bytes in this arena. After we are
+    // done with the relocations, we can safely deinit the entire memory slab.
+    // TODO currently, we do not store the relocations for future use, however,
+    // if that is the case, we should move memory management to a higher scope,
+    // such as linker scope, or whatnot.
+    var dbg_type_arena = std.heap.ArenaAllocator.init(allocator);
+    defer dbg_type_arena.deinit();
+
     {
         // Now we emit the .debug_info types of the Decl. These will count towards the size of
         // the buffer, so we have to do it before computing the offset, and we can't perform the actual
         // relocations yet.
-        var it = dbg_info_type_relocs.iterator();
-        while (it.next()) |entry| {
-            entry.value_ptr.off = @intCast(u32, dbg_info_buffer.items.len);
-            try self.addDbgInfoType(entry.key_ptr.*, dbg_info_buffer, target);
+        var it: usize = 0;
+        while (it < dbg_info_type_relocs.count()) : (it += 1) {
+            const ty = dbg_info_type_relocs.keys()[it];
+            const value_ptr = dbg_info_type_relocs.getPtr(ty).?;
+            value_ptr.off = @intCast(u32, dbg_info_buffer.items.len);
+            try self.addDbgInfoType(dbg_type_arena.allocator(), ty, dbg_info_buffer, dbg_info_type_relocs);
         }
     }
 
@@ -1129,24 +1158,25 @@ pub fn commitDeclDebugInfo(
 /// Asserts the type has codegen bits.
 fn addDbgInfoType(
     self: *DebugSymbols,
+    arena: Allocator,
     ty: Type,
     dbg_info_buffer: *std.ArrayList(u8),
-    target: std.Target,
+    dbg_info_type_relocs: *link.File.DbgInfoTypeRelocsTable,
 ) !void {
-    _ = self;
+    const target = self.base.base.options.target;
+    var relocs = std.ArrayList(struct { ty: Type, reloc: u32 }).init(arena);
+
     switch (ty.zigTypeTag()) {
-        .Void => unreachable,
         .NoReturn => unreachable,
+        .Void => {
+            try dbg_info_buffer.append(abbrev_pad1);
+        },
         .Bool => {
             try dbg_info_buffer.appendSlice(&[_]u8{
                 abbrev_base_type,
                 DW.ATE.boolean, // DW.AT.encoding ,  DW.FORM.data1
                 1, // DW.AT.byte_size,  DW.FORM.data1
-                'b',
-                'o',
-                'o',
-                'l',
-                0, // DW.AT.name,  DW.FORM.string
+                'b', 'o', 'o', 'l', 0, // DW.AT.name,  DW.FORM.string
             });
         },
         .Int => {
@@ -1163,11 +1193,120 @@ fn addDbgInfoType(
             // DW.AT.name,  DW.FORM.string
             try dbg_info_buffer.writer().print("{}\x00", .{ty});
         },
+        .Optional => {
+            if (ty.isPtrLikeOptional()) {
+                try dbg_info_buffer.ensureUnusedCapacity(12);
+                dbg_info_buffer.appendAssumeCapacity(abbrev_base_type);
+                // DW.AT.encoding, DW.FORM.data1
+                dbg_info_buffer.appendAssumeCapacity(DW.ATE.address);
+                // DW.AT.byte_size,  DW.FORM.data1
+                dbg_info_buffer.appendAssumeCapacity(@intCast(u8, ty.abiSize(target)));
+                // DW.AT.name,  DW.FORM.string
+                try dbg_info_buffer.writer().print("{}\x00", .{ty});
+            } else {
+                log.debug("TODO implement .debug_info for type '{}'", .{ty});
+                try dbg_info_buffer.append(abbrev_pad1);
+            }
+        },
+        .Pointer => {
+            if (ty.isSlice()) {
+                // Slices are anonymous structs: struct { .ptr = *, .len = N }
+                try dbg_info_buffer.ensureUnusedCapacity(23);
+                // DW.AT.structure_type
+                dbg_info_buffer.appendAssumeCapacity(abbrev_anon_struct_type);
+                // DW.AT.byte_size, DW.FORM.sdata
+                dbg_info_buffer.appendAssumeCapacity(16);
+                // DW.AT.member
+                dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
+                // DW.AT.name, DW.FORM.string
+                dbg_info_buffer.appendSliceAssumeCapacity("ptr");
+                dbg_info_buffer.appendAssumeCapacity(0);
+                // DW.AT.type, DW.FORM.ref4
+                var index = dbg_info_buffer.items.len;
+                try dbg_info_buffer.resize(index + 4);
+                var buf = try arena.create(Type.SlicePtrFieldTypeBuffer);
+                const ptr_ty = ty.slicePtrFieldType(buf);
+                try relocs.append(.{ .ty = ptr_ty, .reloc = @intCast(u32, index) });
+                // DW.AT.data_member_location, DW.FORM.sdata
+                dbg_info_buffer.appendAssumeCapacity(0);
+                // DW.AT.member
+                dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
+                // DW.AT.name, DW.FORM.string
+                dbg_info_buffer.appendSliceAssumeCapacity("len");
+                dbg_info_buffer.appendAssumeCapacity(0);
+                // DW.AT.type, DW.FORM.ref4
+                index = dbg_info_buffer.items.len;
+                try dbg_info_buffer.resize(index + 4);
+                try relocs.append(.{ .ty = Type.initTag(.usize), .reloc = @intCast(u32, index) });
+                // DW.AT.data_member_location, DW.FORM.sdata
+                dbg_info_buffer.appendAssumeCapacity(8);
+                // DW.AT.structure_type delimit children
+                dbg_info_buffer.appendAssumeCapacity(0);
+            } else {
+                try dbg_info_buffer.ensureUnusedCapacity(5);
+                dbg_info_buffer.appendAssumeCapacity(abbrev_ptr_type);
+                // DW.AT.type, DW.FORM.ref4
+                const index = dbg_info_buffer.items.len;
+                try dbg_info_buffer.resize(index + 4);
+                try relocs.append(.{ .ty = ty.childType(), .reloc = @intCast(u32, index) });
+            }
+        },
+        .Struct => blk: {
+            // try dbg_info_buffer.ensureUnusedCapacity(23);
+            // DW.AT.structure_type
+            try dbg_info_buffer.append(abbrev_struct_type);
+            // DW.AT.byte_size, DW.FORM.sdata
+            const abi_size = ty.abiSize(target);
+            try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size);
+            // DW.AT.name, DW.FORM.string
+            const struct_name = try ty.nameAlloc(arena);
+            try dbg_info_buffer.ensureUnusedCapacity(struct_name.len + 1);
+            dbg_info_buffer.appendSliceAssumeCapacity(struct_name);
+            dbg_info_buffer.appendAssumeCapacity(0);
+
+            const struct_obj = ty.castTag(.@"struct").?.data;
+            if (struct_obj.layout == .Packed) {
+                log.debug("TODO implement .debug_info for packed structs", .{});
+                break :blk;
+            }
+
+            const fields = ty.structFields();
+            for (fields.keys()) |field_name, field_index| {
+                const field = fields.get(field_name).?;
+                // DW.AT.member
+                try dbg_info_buffer.ensureUnusedCapacity(field_name.len + 2);
+                dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member);
+                // DW.AT.name, DW.FORM.string
+                dbg_info_buffer.appendSliceAssumeCapacity(field_name);
+                dbg_info_buffer.appendAssumeCapacity(0);
+                // DW.AT.type, DW.FORM.ref4
+                var index = dbg_info_buffer.items.len;
+                try dbg_info_buffer.resize(index + 4);
+                try relocs.append(.{ .ty = field.ty, .reloc = @intCast(u32, index) });
+                // DW.AT.data_member_location, DW.FORM.sdata
+                const field_off = ty.structFieldOffset(field_index, target);
+                try leb128.writeULEB128(dbg_info_buffer.writer(), field_off);
+            }
+
+            // DW.AT.structure_type delimit children
+            try dbg_info_buffer.append(0);
+        },
         else => {
-            std.log.scoped(.compiler).err("TODO implement .debug_info for type '{}'", .{ty});
+            log.debug("TODO implement .debug_info for type '{}'", .{ty});
             try dbg_info_buffer.append(abbrev_pad1);
         },
     }
+
+    for (relocs.items) |rel| {
+        const gop = try dbg_info_type_relocs.getOrPut(self.base.base.allocator, rel.ty);
+        if (!gop.found_existing) {
+            gop.value_ptr.* = .{
+                .off = undefined,
+                .relocs = .{},
+            };
+        }
+        try gop.value_ptr.relocs.append(self.base.base.allocator, rel.reloc);
+    }
 }
 
 fn updateDeclDebugInfoAllocation(
src/link/MachO.zig
@@ -354,32 +354,31 @@ pub fn openPath(allocator: Allocator, options: link.Options) !*MachO {
         return self;
     }
 
-    // TODO Migrate DebugSymbols to the merged linker codepaths
-    // if (!options.strip and options.module != null) {
-    //     // Create dSYM bundle.
-    //     const dir = options.module.?.zig_cache_artifact_directory;
-    //     log.debug("creating {s}.dSYM bundle in {s}", .{ sub_path, dir.path });
-
-    //     const d_sym_path = try fmt.allocPrint(
-    //         allocator,
-    //         "{s}.dSYM" ++ fs.path.sep_str ++ "Contents" ++ fs.path.sep_str ++ "Resources" ++ fs.path.sep_str ++ "DWARF",
-    //         .{sub_path},
-    //     );
-    //     defer allocator.free(d_sym_path);
-
-    //     var d_sym_bundle = try dir.handle.makeOpenPath(d_sym_path, .{});
-    //     defer d_sym_bundle.close();
-
-    //     const d_sym_file = try d_sym_bundle.createFile(sub_path, .{
-    //         .truncate = false,
-    //         .read = true,
-    //     });
-
-    //     self.d_sym = .{
-    //         .base = self,
-    //         .file = d_sym_file,
-    //     };
-    // }
+    if (!options.strip and options.module != null) {
+        // Create dSYM bundle.
+        const dir = options.module.?.zig_cache_artifact_directory;
+        log.debug("creating {s}.dSYM bundle in {s}", .{ emit.sub_path, dir.path });
+
+        const d_sym_path = try fmt.allocPrint(
+            allocator,
+            "{s}.dSYM" ++ fs.path.sep_str ++ "Contents" ++ fs.path.sep_str ++ "Resources" ++ fs.path.sep_str ++ "DWARF",
+            .{emit.sub_path},
+        );
+        defer allocator.free(d_sym_path);
+
+        var d_sym_bundle = try dir.handle.makeOpenPath(d_sym_path, .{});
+        defer d_sym_bundle.close();
+
+        const d_sym_file = try d_sym_bundle.createFile(emit.sub_path, .{
+            .truncate = false,
+            .read = true,
+        });
+
+        self.d_sym = .{
+            .base = self,
+            .file = d_sym_file,
+        };
+    }
 
     // Index 0 is always a null symbol.
     try self.locals.append(allocator, .{
@@ -393,8 +392,8 @@ pub fn openPath(allocator: Allocator, options: link.Options) !*MachO {
 
     try self.populateMissingMetadata();
 
-    if (self.d_sym) |*ds| {
-        try ds.populateMissingMetadata(allocator);
+    if (self.d_sym) |*d_sym| {
+        try d_sym.populateMissingMetadata(allocator);
     }
 
     return self;
@@ -1048,9 +1047,9 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
         try self.updateSectionOrdinals();
         try self.writeLinkeditSegment();
 
-        if (self.d_sym) |*ds| {
+        if (self.d_sym) |*d_sym| {
             // Flush debug symbols bundle.
-            try ds.flushModule(self.base.allocator, self.base.options);
+            try d_sym.flushModule(self.base.allocator, self.base.options);
         }
 
         if (self.requires_adhoc_codesig) {
@@ -3374,8 +3373,8 @@ pub fn deinit(self: *MachO) void {
         if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator);
     }
 
-    if (self.d_sym) |*ds| {
-        ds.deinit(self.base.allocator);
+    if (self.d_sym) |*d_sym| {
+        d_sym.deinit(self.base.allocator);
     }
 
     self.section_ordinals.deinit(self.base.allocator);
@@ -3497,13 +3496,13 @@ fn freeAtom(self: *MachO, atom: *Atom, match: MatchingSection, owns_atom: bool)
         }
     }
 
-    if (self.d_sym) |*ds| {
-        if (ds.dbg_info_decl_first == atom) {
-            ds.dbg_info_decl_first = atom.dbg_info_next;
+    if (self.d_sym) |*d_sym| {
+        if (d_sym.dbg_info_decl_first == atom) {
+            d_sym.dbg_info_decl_first = atom.dbg_info_next;
         }
-        if (ds.dbg_info_decl_last == atom) {
+        if (d_sym.dbg_info_decl_last == atom) {
             // TODO shrink the .debug_info section size here
-            ds.dbg_info_decl_last = atom.dbg_info_prev;
+            d_sym.dbg_info_decl_last = atom.dbg_info_prev;
         }
     }
 
@@ -3675,6 +3674,7 @@ pub fn updateFunc(self: *MachO, module: *Module, func: *Module.Fn, air: Air, liv
 
     const decl = func.owner_decl;
     self.freeUnnamedConsts(decl);
+
     // TODO clearing the code and relocs buffer should probably be orchestrated
     // in a different, smarter, more automatic way somewhere else, in a more centralised
     // way than this.
@@ -3686,8 +3686,8 @@ pub fn updateFunc(self: *MachO, module: *Module, func: *Module.Fn, air: Air, liv
     defer code_buffer.deinit();
 
     var debug_buffers_buf: DebugSymbols.DeclDebugBuffers = undefined;
-    const debug_buffers = if (self.d_sym) |*ds| blk: {
-        debug_buffers_buf = try ds.initDeclDebugBuffers(self.base.allocator, module, decl);
+    const debug_buffers = if (self.d_sym) |*d_sym| blk: {
+        debug_buffers_buf = try d_sym.initDeclDebugBuffers(self.base.allocator, module, decl);
         break :blk &debug_buffers_buf;
     } else null;
     defer {
@@ -3725,13 +3725,9 @@ pub fn updateFunc(self: *MachO, module: *Module, func: *Module.Fn, air: Air, liv
     _ = try self.placeDecl(decl, decl.link.macho.code.items.len);
 
     if (debug_buffers) |db| {
-        try self.d_sym.?.commitDeclDebugInfo(
-            self.base.allocator,
-            module,
-            decl,
-            db,
-            self.base.options.target,
-        );
+        if (self.d_sym) |*d_sym| {
+            try d_sym.commitDeclDebugInfo(self.base.allocator, module, decl, db);
+        }
     }
 
     // Since we updated the vaddr and the size, each corresponding export symbol also
@@ -3827,8 +3823,8 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
     defer code_buffer.deinit();
 
     var debug_buffers_buf: DebugSymbols.DeclDebugBuffers = undefined;
-    const debug_buffers = if (self.d_sym) |*ds| blk: {
-        debug_buffers_buf = try ds.initDeclDebugBuffers(self.base.allocator, module, decl);
+    const debug_buffers = if (self.d_sym) |*d_sym| blk: {
+        debug_buffers_buf = try d_sym.initDeclDebugBuffers(self.base.allocator, module, decl);
         break :blk &debug_buffers_buf;
     } else null;
     defer {
@@ -4125,8 +4121,8 @@ fn placeDecl(self: *MachO, decl: *Module.Decl, code_len: usize) !*macho.nlist_64
 }
 
 pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl: *const Module.Decl) !void {
-    if (self.d_sym) |*ds| {
-        try ds.updateDeclLineNumber(module, decl);
+    if (self.d_sym) |*d_sym| {
+        try d_sym.updateDeclLineNumber(module, decl);
     }
 }
 
@@ -4322,27 +4318,27 @@ pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {
         _ = self.atom_by_index_table.remove(decl.link.macho.local_sym_index);
         decl.link.macho.local_sym_index = 0;
     }
-    if (self.d_sym) |*ds| {
+    if (self.d_sym) |*d_sym| {
         // TODO make this logic match freeAtom. Maybe abstract the logic
         // out since the same thing is desired for both.
-        _ = ds.dbg_line_fn_free_list.remove(&decl.fn_link.macho);
+        _ = d_sym.dbg_line_fn_free_list.remove(&decl.fn_link.macho);
         if (decl.fn_link.macho.prev) |prev| {
-            ds.dbg_line_fn_free_list.put(self.base.allocator, prev, {}) catch {};
+            d_sym.dbg_line_fn_free_list.put(self.base.allocator, prev, {}) catch {};
             prev.next = decl.fn_link.macho.next;
             if (decl.fn_link.macho.next) |next| {
                 next.prev = prev;
             } else {
-                ds.dbg_line_fn_last = prev;
+                d_sym.dbg_line_fn_last = prev;
             }
         } else if (decl.fn_link.macho.next) |next| {
-            ds.dbg_line_fn_first = next;
+            d_sym.dbg_line_fn_first = next;
             next.prev = null;
         }
-        if (ds.dbg_line_fn_first == &decl.fn_link.macho) {
-            ds.dbg_line_fn_first = decl.fn_link.macho.next;
+        if (d_sym.dbg_line_fn_first == &decl.fn_link.macho) {
+            d_sym.dbg_line_fn_first = decl.fn_link.macho.next;
         }
-        if (ds.dbg_line_fn_last == &decl.fn_link.macho) {
-            ds.dbg_line_fn_last = decl.fn_link.macho.prev;
+        if (d_sym.dbg_line_fn_last == &decl.fn_link.macho) {
+            d_sym.dbg_line_fn_last = decl.fn_link.macho.prev;
         }
     }
 }