diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 90c4757979..c2a6e96a39 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -301,6 +301,9 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio try self.populateMissingMetadata(); try self.d_sym.?.populateMissingMetadata(allocator); + try self.writeLocalSymbol(0); + try self.d_sym.?.writeLocalSymbol(0); + return self; } @@ -1123,6 +1126,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { symbol.n_desc = 0; try self.writeLocalSymbol(decl.link.macho.local_sym_index); + try self.d_sym.?.writeLocalSymbol(decl.link.macho.local_sym_index); } else { const decl_name = mem.spanZ(decl.name); const name_str_index = try self.makeString(decl_name); @@ -1140,6 +1144,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { self.offset_table.items[decl.link.macho.offset_table_index] = addr; try self.writeLocalSymbol(decl.link.macho.local_sym_index); + try self.d_sym.?.writeLocalSymbol(decl.link.macho.local_sym_index); try self.writeOffsetTableEntry(decl.link.macho.offset_table_index); } @@ -1517,7 +1522,6 @@ pub fn populateMissingMetadata(self: *MachO) !void { .strsize = @intCast(u32, strtab_size), }, }); - try self.writeLocalSymbol(0); self.header_dirty = true; self.load_commands_dirty = true; self.string_table_dirty = true; @@ -1795,6 +1799,7 @@ fn makeString(self: *MachO, bytes: []const u8) !u32 { self.string_table.appendSliceAssumeCapacity(bytes); self.string_table.appendAssumeCapacity(0); self.string_table_dirty = true; + self.d_sym.?.string_table_dirty = true; return @intCast(u32, result); } @@ -2247,7 +2252,6 @@ fn writeStringTable(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const linkedit_segment = 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.string_table.items.len, @alignOf(u64)); diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index 22d7e04fef..b1fc3fc4c5 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -10,7 +10,11 @@ const DW = std.dwarf; const leb = std.leb; const Allocator = mem.Allocator; +const trace = @import("../../tracy.zig").trace; const MachO = @import("../MachO.zig"); +const satMul = MachO.satMul; +const alloc_num = MachO.alloc_num; +const alloc_den = MachO.alloc_den; usingnamespace @import("commands.zig"); @@ -40,8 +44,12 @@ uuid_cmd_index: ?u16 = null, /// Index into __TEXT,__text section. text_section_index: ?u16 = null, +linkedit_off: u16 = 0x1000, +linkedit_size: u16 = 0x1000, + header_dirty: bool = false, load_commands_dirty: bool = false, +string_table_dirty: bool = false, /// You must call this function *after* `MachO.populateMissingMetadata()` /// has been called to get a viable debug symbols output. @@ -61,22 +69,6 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void self.header = header; self.header_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; - try self.copySegmentCommand(allocator, base_cmd); - } - 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; - try self.copySegmentCommand(allocator, base_cmd); - } - 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); - const base_cmd = self.base.load_commands.items[self.base.data_segment_cmd_index.?].Segment; - try self.copySegmentCommand(allocator, base_cmd); - } if (self.uuid_cmd_index == null) { const base_cmd = self.base.load_commands.items[self.base.uuid_cmd_index.?]; self.uuid_cmd_index = @intCast(u16, self.load_commands.items.len); @@ -84,13 +76,79 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: *Allocator) !void self.header_dirty = true; 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 dSym 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 dSym string table free space 0x{x} to 0x{x}", .{ strtab_off, strtab_off + base_cmd.strsize }); + + try self.load_commands.append(allocator, .{ + .Symtab = .{ + .cmd = macho.LC_SYMTAB, + .cmdsize = @sizeOf(macho.symtab_command), + .symoff = @intCast(u32, symtab_off), + .nsyms = base_cmd.nsyms, + .stroff = @intCast(u32, strtab_off), + .strsize = base_cmd.strsize, + }, + }); + try self.writeLocalSymbol(0); + self.header_dirty = true; + self.load_commands_dirty = true; + self.string_table_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; + const cmd = try self.copySegmentCommand(allocator, base_cmd); + try self.load_commands.append(allocator, .{ .Segment = cmd }); + self.header_dirty = true; + 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; + const cmd = try self.copySegmentCommand(allocator, base_cmd); + try self.load_commands.append(allocator, .{ .Segment = cmd }); + self.header_dirty = true; + 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); + const base_cmd = self.base.load_commands.items[self.base.data_segment_cmd_index.?].Segment; + const cmd = try self.copySegmentCommand(allocator, base_cmd); + try self.load_commands.append(allocator, .{ .Segment = cmd }); + self.header_dirty = true; + 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; + try self.load_commands.append(allocator, .{ .Segment = cmd }); + self.header_dirty = true; + self.load_commands_dirty = true; + } } pub fn flush(self: *DebugSymbols, allocator: *Allocator) !void { + try self.writeStringTable(); try self.writeLoadCommands(allocator); try self.writeHeader(); + assert(!self.header_dirty); assert(!self.load_commands_dirty); + assert(!self.string_table_dirty); } pub fn deinit(self: *DebugSymbols, allocator: *Allocator) void { @@ -100,7 +158,7 @@ pub fn deinit(self: *DebugSymbols, allocator: *Allocator) void { self.file.close(); } -fn copySegmentCommand(self: *DebugSymbols, allocator: *Allocator, base_cmd: SegmentCommand) !void { +fn copySegmentCommand(self: *DebugSymbols, allocator: *Allocator, base_cmd: SegmentCommand) !SegmentCommand { var cmd = SegmentCommand.empty(.{ .cmd = macho.LC_SEGMENT_64, .cmdsize = base_cmd.inner.cmdsize, @@ -142,9 +200,7 @@ fn copySegmentCommand(self: *DebugSymbols, allocator: *Allocator, base_cmd: Segm cmd.sections.appendAssumeCapacity(sect); } - try self.load_commands.append(allocator, .{ .Segment = cmd }); - self.header_dirty = true; - self.load_commands_dirty = true; + return cmd; } /// Writes all load commands and section headers. @@ -182,3 +238,115 @@ fn writeHeader(self: *DebugSymbols) !void { try self.file.pwriteAll(mem.asBytes(&self.header.?), 0); self.header_dirty = false; } + +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 detectAllocCollisionLinkedit(self: *DebugSymbols, start: u64, size: u64) ?u64 { + const end = start + satMul(size, alloc_num) / alloc_den; + + 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 = satMul(symsize, alloc_num) / alloc_den; + const test_end = symtab.symoff + increased_size; + if (end > symtab.symoff and start < test_end) { + return test_end; + } + } + { + // String table + const increased_size = satMul(symtab.strsize, alloc_num) / alloc_den; + const test_end = symtab.stroff + increased_size; + if (end > symtab.stroff and start < test_end) { + return test_end; + } + } + } + + return null; +} + +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 relocateSymbolTable(self: *DebugSymbols) !void { + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + const nlocals = self.base.local_symbols.items.len; + const nglobals = self.base.global_symbols.items.len; + const nsyms = nlocals + nglobals; + + if (symtab.nsyms < nsyms) { + const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + 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); + log.debug("relocating dSym symbol table from 0x{x}-0x{x} to 0x{x}-0x{x}", .{ + symtab.symoff, + symtab.symoff + existing_size, + new_symoff, + new_symoff + existing_size, + }); + + 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; + } +} + +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 dSym local symbol {} at 0x{x}", .{ index, off }); + try self.file.pwriteAll(mem.asBytes(&self.base.local_symbols.items[index]), off); +} + +pub fn writeStringTable(self: *DebugSymbols) !void { + if (!self.string_table_dirty) return; + + const tracy = trace(@src()); + defer tracy.end(); + + 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.string_table.items.len, @alignOf(u64)); + + 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 dSym string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize }); + + try self.file.pwriteAll(self.base.string_table.items, symtab.stroff); + self.load_commands_dirty = true; + self.string_table_dirty = false; +}