master
  1allocator: Allocator,
  2file: ?fs.File,
  3
  4symtab_cmd: macho.symtab_command = .{},
  5uuid_cmd: macho.uuid_command = .{ .uuid = [_]u8{0} ** 16 },
  6
  7segments: std.ArrayList(macho.segment_command_64) = .empty,
  8sections: std.ArrayList(macho.section_64) = .empty,
  9
 10dwarf_segment_cmd_index: ?u8 = null,
 11linkedit_segment_cmd_index: ?u8 = null,
 12
 13debug_info_section_index: ?u8 = null,
 14debug_abbrev_section_index: ?u8 = null,
 15debug_str_section_index: ?u8 = null,
 16debug_aranges_section_index: ?u8 = null,
 17debug_line_section_index: ?u8 = null,
 18debug_line_str_section_index: ?u8 = null,
 19debug_loclists_section_index: ?u8 = null,
 20debug_rnglists_section_index: ?u8 = null,
 21
 22relocs: std.ArrayList(Reloc) = .empty,
 23
 24/// Output synthetic sections
 25symtab: std.ArrayList(macho.nlist_64) = .empty,
 26strtab: std.ArrayList(u8) = .empty,
 27
 28pub const Reloc = struct {
 29    type: enum {
 30        direct_load,
 31        got_load,
 32    },
 33    target: u32,
 34    offset: u64,
 35    addend: u32,
 36};
 37
 38/// You must call this function *after* `ZigObject.initMetadata()`
 39/// has been called to get a viable debug symbols output.
 40pub fn initMetadata(self: *DebugSymbols, macho_file: *MachO) !void {
 41    try self.strtab.append(self.allocator, 0);
 42
 43    {
 44        self.dwarf_segment_cmd_index = @as(u8, @intCast(self.segments.items.len));
 45
 46        const page_size = macho_file.getPageSize();
 47        const off = @as(u64, @intCast(page_size));
 48        const ideal_size: u16 = 200 + 128 + 160 + 250;
 49        const needed_size = mem.alignForward(u64, padToIdeal(ideal_size), page_size);
 50
 51        log.debug("found __DWARF segment free space 0x{x} to 0x{x}", .{ off, off + needed_size });
 52
 53        try self.segments.append(self.allocator, .{
 54            .segname = makeStaticString("__DWARF"),
 55            .vmsize = needed_size,
 56            .fileoff = off,
 57            .filesize = needed_size,
 58            .cmdsize = @sizeOf(macho.segment_command_64),
 59        });
 60    }
 61
 62    self.debug_str_section_index = try self.createSection("__debug_str", 0);
 63    self.debug_info_section_index = try self.createSection("__debug_info", 0);
 64    self.debug_abbrev_section_index = try self.createSection("__debug_abbrev", 0);
 65    self.debug_aranges_section_index = try self.createSection("__debug_aranges", 4);
 66    self.debug_line_section_index = try self.createSection("__debug_line", 0);
 67    self.debug_line_str_section_index = try self.createSection("__debug_line_str", 0);
 68    self.debug_loclists_section_index = try self.createSection("__debug_loclists", 0);
 69    self.debug_rnglists_section_index = try self.createSection("__debug_rnglists", 0);
 70
 71    self.linkedit_segment_cmd_index = @intCast(self.segments.items.len);
 72    try self.segments.append(self.allocator, .{
 73        .segname = makeStaticString("__LINKEDIT"),
 74        .maxprot = macho.PROT.READ,
 75        .initprot = macho.PROT.READ,
 76        .cmdsize = @sizeOf(macho.segment_command_64),
 77    });
 78}
 79
 80fn createSection(self: *DebugSymbols, sectname: []const u8, alignment: u16) !u8 {
 81    const segment = self.getDwarfSegmentPtr();
 82    var sect = macho.section_64{
 83        .sectname = makeStaticString(sectname),
 84        .segname = segment.segname,
 85        .@"align" = alignment,
 86    };
 87
 88    log.debug("create {s},{s} section", .{ sect.segName(), sect.sectName() });
 89
 90    const index: u8 = @intCast(self.sections.items.len);
 91    try self.sections.append(self.allocator, sect);
 92    segment.cmdsize += @sizeOf(macho.section_64);
 93    segment.nsects += 1;
 94
 95    return index;
 96}
 97
 98pub fn growSection(
 99    self: *DebugSymbols,
100    sect_index: u8,
101    needed_size: u64,
102    requires_file_copy: bool,
103    macho_file: *MachO,
104) !void {
105    const sect = self.getSectionPtr(sect_index);
106
107    const allocated_size = self.allocatedSize(sect.offset);
108    if (needed_size > allocated_size) {
109        const existing_size = sect.size;
110        sect.size = 0; // free the space
111        const new_offset = try self.findFreeSpace(needed_size, 1);
112
113        log.debug("moving {s} section: {} bytes from 0x{x} to 0x{x}", .{
114            sect.sectName(),
115            existing_size,
116            sect.offset,
117            new_offset,
118        });
119
120        if (requires_file_copy) {
121            const amt = try self.file.?.copyRangeAll(
122                sect.offset,
123                self.file.?,
124                new_offset,
125                existing_size,
126            );
127            if (amt != existing_size) return error.InputOutput;
128        }
129
130        sect.offset = @intCast(new_offset);
131    } else if (sect.offset + allocated_size == std.math.maxInt(u64)) {
132        try self.file.?.setEndPos(sect.offset + needed_size);
133    }
134
135    sect.size = needed_size;
136    self.markDirty(sect_index, macho_file);
137}
138
139pub fn markDirty(self: *DebugSymbols, sect_index: u8, macho_file: *MachO) void {
140    if (macho_file.getZigObject()) |zo| {
141        if (self.debug_info_section_index.? == sect_index) {
142            zo.debug_info_header_dirty = true;
143        } else if (self.debug_line_section_index.? == sect_index) {
144            zo.debug_line_header_dirty = true;
145        } else if (self.debug_abbrev_section_index.? == sect_index) {
146            zo.debug_abbrev_dirty = true;
147        } else if (self.debug_str_section_index.? == sect_index) {
148            zo.debug_strtab_dirty = true;
149        } else if (self.debug_aranges_section_index.? == sect_index) {
150            zo.debug_aranges_dirty = true;
151        }
152    }
153}
154
155fn detectAllocCollision(self: *DebugSymbols, start: u64, size: u64) !?u64 {
156    var at_end = true;
157    const end = start + padToIdeal(size);
158
159    for (self.sections.items) |section| {
160        const increased_size = padToIdeal(section.size);
161        const test_end = section.offset + increased_size;
162        if (start < test_end) {
163            if (end > section.offset) return test_end;
164            if (test_end < std.math.maxInt(u64)) at_end = false;
165        }
166    }
167
168    if (at_end) try self.file.?.setEndPos(end);
169    return null;
170}
171
172fn findFreeSpace(self: *DebugSymbols, object_size: u64, min_alignment: u64) !u64 {
173    const segment = self.getDwarfSegmentPtr();
174    var offset: u64 = segment.fileoff;
175    while (try self.detectAllocCollision(offset, object_size)) |item_end| {
176        offset = mem.alignForward(u64, item_end, min_alignment);
177    }
178    return offset;
179}
180
181pub fn flush(self: *DebugSymbols, macho_file: *MachO) !void {
182    const zo = macho_file.getZigObject().?;
183    for (self.relocs.items) |*reloc| {
184        const sym = zo.symbols.items[reloc.target];
185        const sym_name = sym.getName(macho_file);
186        const addr = switch (reloc.type) {
187            .direct_load => sym.getAddress(.{}, macho_file),
188            .got_load => sym.getGotAddress(macho_file),
189        };
190        const sect = &self.sections.items[self.debug_info_section_index.?];
191        const file_offset = sect.offset + reloc.offset;
192        log.debug("resolving relocation: {d}@{x} ('{s}') at offset {x}", .{
193            reloc.target,
194            addr,
195            sym_name,
196            file_offset,
197        });
198        try self.file.?.pwriteAll(mem.asBytes(&addr), file_offset);
199    }
200
201    self.finalizeDwarfSegment(macho_file);
202    try self.writeLinkeditSegmentData(macho_file);
203
204    // Write load commands
205    const ncmds, const sizeofcmds = try self.writeLoadCommands(macho_file);
206    try self.writeHeader(macho_file, ncmds, sizeofcmds);
207}
208
209pub fn deinit(self: *DebugSymbols) void {
210    const gpa = self.allocator;
211    if (self.file) |file| file.close();
212    self.segments.deinit(gpa);
213    self.sections.deinit(gpa);
214    self.relocs.deinit(gpa);
215    self.symtab.deinit(gpa);
216    self.strtab.deinit(gpa);
217}
218
219pub fn swapRemoveRelocs(self: *DebugSymbols, target: u32) void {
220    // TODO re-implement using a hashmap with free lists
221    var last_index: usize = 0;
222    while (last_index < self.relocs.items.len) {
223        const reloc = self.relocs.items[last_index];
224        if (reloc.target == target) {
225            _ = self.relocs.swapRemove(last_index);
226        } else {
227            last_index += 1;
228        }
229    }
230}
231
232fn finalizeDwarfSegment(self: *DebugSymbols, macho_file: *MachO) void {
233    const base_vmaddr = blk: {
234        // Note that we purposely take the last VM address of the MachO binary including
235        // the binary's LINKEDIT segment. This is in contrast to how dsymutil does it
236        // which overwrites the the address space taken by the original MachO binary,
237        // however at the cost of having LINKEDIT preceed DWARF in dSYM binary which we
238        // do not want as we want to be able to incrementally move DWARF sections in the
239        // file as we please.
240        const last_seg = macho_file.getLinkeditSegment();
241        break :blk last_seg.vmaddr + last_seg.vmsize;
242    };
243    const dwarf_segment = self.getDwarfSegmentPtr();
244
245    var file_size: u64 = 0;
246    for (self.sections.items) |header| {
247        file_size = @max(file_size, header.offset + header.size);
248    }
249
250    const page_size = macho_file.getPageSize();
251    const aligned_size = mem.alignForward(u64, file_size, page_size);
252    dwarf_segment.vmaddr = base_vmaddr;
253    dwarf_segment.filesize = aligned_size;
254    dwarf_segment.vmsize = aligned_size;
255
256    const linkedit = self.getLinkeditSegmentPtr();
257    linkedit.vmaddr = mem.alignForward(
258        u64,
259        dwarf_segment.vmaddr + aligned_size,
260        page_size,
261    );
262    linkedit.fileoff = mem.alignForward(
263        u64,
264        dwarf_segment.fileoff + aligned_size,
265        page_size,
266    );
267    log.debug("found __LINKEDIT segment free space at 0x{x}", .{linkedit.fileoff});
268}
269
270fn writeLoadCommands(self: *DebugSymbols, macho_file: *MachO) !struct { usize, usize } {
271    const gpa = self.allocator;
272    const needed_size = load_commands.calcLoadCommandsSizeDsym(macho_file, self);
273    const buffer = try gpa.alloc(u8, needed_size);
274    defer gpa.free(buffer);
275
276    var writer: Writer = .fixed(buffer);
277
278    var ncmds: usize = 0;
279
280    // UUID comes first presumably to speed up lookup by the consumer like lldb.
281    @memcpy(&self.uuid_cmd.uuid, &macho_file.uuid_cmd.uuid);
282    try writer.writeStruct(self.uuid_cmd, .little);
283    ncmds += 1;
284
285    // Segment and section load commands
286    {
287        // Write segment/section headers from the binary file first.
288        const slice = macho_file.sections.slice();
289        var sect_id: usize = 0;
290        for (macho_file.segments.items, 0..) |seg, seg_id| {
291            if (seg_id == macho_file.linkedit_seg_index.?) break;
292            var out_seg = seg;
293            out_seg.fileoff = 0;
294            out_seg.filesize = 0;
295            try writer.writeStruct(out_seg, .little);
296            for (slice.items(.header)[sect_id..][0..seg.nsects]) |header| {
297                var out_header = header;
298                out_header.offset = 0;
299                try writer.writeStruct(out_header, .little);
300            }
301            sect_id += seg.nsects;
302        }
303        ncmds += macho_file.segments.items.len - 1;
304
305        // Next, commit DSYM's __LINKEDIT and __DWARF segments headers.
306        sect_id = 0;
307        for (self.segments.items) |seg| {
308            try writer.writeStruct(seg, .little);
309            for (self.sections.items[sect_id..][0..seg.nsects]) |header| {
310                try writer.writeStruct(header, .little);
311            }
312            sect_id += seg.nsects;
313        }
314        ncmds += self.segments.items.len;
315    }
316
317    try writer.writeStruct(self.symtab_cmd, .little);
318    ncmds += 1;
319
320    assert(writer.end == needed_size);
321
322    try self.file.?.pwriteAll(buffer, @sizeOf(macho.mach_header_64));
323
324    return .{ ncmds, buffer.len };
325}
326
327fn writeHeader(self: *DebugSymbols, macho_file: *MachO, ncmds: usize, sizeofcmds: usize) !void {
328    var header: macho.mach_header_64 = .{};
329    header.filetype = macho.MH_DSYM;
330
331    switch (macho_file.getTarget().cpu.arch) {
332        .aarch64 => {
333            header.cputype = macho.CPU_TYPE_ARM64;
334            header.cpusubtype = macho.CPU_SUBTYPE_ARM_ALL;
335        },
336        .x86_64 => {
337            header.cputype = macho.CPU_TYPE_X86_64;
338            header.cpusubtype = macho.CPU_SUBTYPE_X86_64_ALL;
339        },
340        else => return error.UnsupportedCpuArchitecture,
341    }
342
343    header.ncmds = @intCast(ncmds);
344    header.sizeofcmds = @intCast(sizeofcmds);
345
346    log.debug("writing Mach-O header {}", .{header});
347
348    try self.file.?.pwriteAll(mem.asBytes(&header), 0);
349}
350
351fn allocatedSize(self: *DebugSymbols, start: u64) u64 {
352    if (start == 0) return 0;
353    const seg = self.getDwarfSegmentPtr();
354    assert(start >= seg.fileoff);
355    var min_pos: u64 = std.math.maxInt(u64);
356    for (self.sections.items) |section| {
357        if (section.offset <= start) continue;
358        if (section.offset < min_pos) min_pos = section.offset;
359    }
360    return min_pos - start;
361}
362
363fn writeLinkeditSegmentData(self: *DebugSymbols, macho_file: *MachO) !void {
364    const tracy = trace(@src());
365    defer tracy.end();
366
367    const page_size = macho_file.getPageSize();
368    const seg = &self.segments.items[self.linkedit_segment_cmd_index.?];
369
370    var off = math.cast(u32, seg.fileoff) orelse return error.Overflow;
371    off = try self.writeSymtab(off, macho_file);
372    off = mem.alignForward(u32, off, @alignOf(u64));
373    off = try self.writeStrtab(off);
374    seg.filesize = off - seg.fileoff;
375
376    const aligned_size = mem.alignForward(u64, seg.filesize, page_size);
377    seg.vmsize = aligned_size;
378}
379
380pub fn writeSymtab(self: *DebugSymbols, off: u32, macho_file: *MachO) !u32 {
381    const tracy = trace(@src());
382    defer tracy.end();
383    const gpa = self.allocator;
384    const cmd = &self.symtab_cmd;
385    cmd.nsyms = macho_file.symtab_cmd.nsyms;
386    cmd.strsize = macho_file.symtab_cmd.strsize;
387    cmd.symoff = off;
388
389    try self.symtab.resize(gpa, cmd.nsyms);
390    try self.strtab.resize(gpa, cmd.strsize);
391    self.strtab.items[0] = 0;
392
393    if (macho_file.getZigObject()) |zo| {
394        zo.writeSymtab(macho_file, self);
395    }
396    for (macho_file.objects.items) |index| {
397        macho_file.getFile(index).?.writeSymtab(macho_file, self);
398    }
399    for (macho_file.dylibs.items) |index| {
400        macho_file.getFile(index).?.writeSymtab(macho_file, self);
401    }
402    if (macho_file.getInternalObject()) |internal| {
403        internal.writeSymtab(macho_file, self);
404    }
405
406    try self.file.?.pwriteAll(@ptrCast(self.symtab.items), cmd.symoff);
407
408    return off + cmd.nsyms * @sizeOf(macho.nlist_64);
409}
410
411pub fn writeStrtab(self: *DebugSymbols, off: u32) !u32 {
412    const cmd = &self.symtab_cmd;
413    cmd.stroff = off;
414    try self.file.?.pwriteAll(self.strtab.items, cmd.stroff);
415    return off + cmd.strsize;
416}
417
418pub fn getSectionIndexes(self: *DebugSymbols, segment_index: u8) struct { start: u8, end: u8 } {
419    var start: u8 = 0;
420    const nsects: u8 = for (self.segments.items, 0..) |seg, i| {
421        if (i == segment_index) break @intCast(seg.nsects);
422        start += @intCast(seg.nsects);
423    } else 0;
424    return .{ .start = start, .end = start + nsects };
425}
426
427fn getDwarfSegmentPtr(self: *DebugSymbols) *macho.segment_command_64 {
428    const index = self.dwarf_segment_cmd_index.?;
429    return &self.segments.items[index];
430}
431
432fn getLinkeditSegmentPtr(self: *DebugSymbols) *macho.segment_command_64 {
433    const index = self.linkedit_segment_cmd_index.?;
434    return &self.segments.items[index];
435}
436
437pub fn getSectionPtr(self: *DebugSymbols, sect: u8) *macho.section_64 {
438    assert(sect < self.sections.items.len);
439    return &self.sections.items[sect];
440}
441
442pub fn getSection(self: DebugSymbols, sect: u8) macho.section_64 {
443    assert(sect < self.sections.items.len);
444    return self.sections.items[sect];
445}
446
447const DebugSymbols = @This();
448
449const std = @import("std");
450const build_options = @import("build_options");
451const assert = std.debug.assert;
452const fs = std.fs;
453const link = @import("../../link.zig");
454const load_commands = @import("load_commands.zig");
455const log = std.log.scoped(.link_dsym);
456const macho = std.macho;
457const makeStaticString = MachO.makeStaticString;
458const math = std.math;
459const mem = std.mem;
460const padToIdeal = MachO.padToIdeal;
461const trace = @import("../../tracy.zig").trace;
462const Writer = std.Io.Writer;
463
464const Allocator = mem.Allocator;
465const MachO = @import("../MachO.zig");
466const StringTable = @import("../StringTable.zig");
467const Type = @import("../../Type.zig");