From edb428fae42ea82c49347fce6d48d80f1fed6ef1 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 13 Apr 2022 19:05:19 +0200 Subject: [PATCH] macho,x64: resolve debug info relocs for RIP-based addressing Sometimes we will want to generate debug info for a constant that has been lowered to memory and not copied anywhere else. For this we will need to defer resolution on PIE platforms until all locals (including GOT entries) have been allocated. --- src/arch/x86_64/CodeGen.zig | 11 ++++++- src/link/Dwarf.zig | 43 +++++++++++++++++++++++++++ src/link/MachO.zig | 8 +++++ src/link/MachO/DebugSymbols.zig | 52 ++++++++++++++++++++++++++++++++- 4 files changed, 112 insertions(+), 2 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 32065fa1bb..53a6bfc4d9 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -3950,7 +3950,7 @@ fn genVarDbgInfo( leb128.writeILEB128(dbg_info.writer(), -off) catch unreachable; dbg_info.items[fixup] += @intCast(u8, dbg_info.items.len - fixup - 2); }, - .memory => |addr| { + .memory, .got_load, .direct_load => { const endian = self.target.cpu.arch.endian(); const ptr_width = @intCast(u8, @divExact(self.target.cpu.arch.ptrBitWidth(), 8)); const is_ptr = switch (tag) { @@ -3963,6 +3963,11 @@ fn genVarDbgInfo( 1 + ptr_width + @boolToInt(is_ptr), DW.OP.addr, // literal address }); + const offset = @intCast(u32, dbg_info.items.len); + const addr = switch (mcv) { + .memory => |addr| addr, + else => 0, + }; switch (ptr_width) { 0...4 => { try dbg_info.writer().writeInt(u32, @intCast(u32, addr), endian); @@ -3976,6 +3981,10 @@ fn genVarDbgInfo( // We need deref the address as we point to the value via GOT entry. try dbg_info.append(DW.OP.deref); } + switch (mcv) { + .got_load, .direct_load => |index| try dw.addExprlocReloc(index, offset, is_ptr), + else => {}, + } }, else => { log.debug("TODO generate debug info for {}", .{mcv}); diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index bd7f429177..248521c544 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -79,6 +79,7 @@ pub const DeclState = struct { std.hash_map.default_max_load_percentage, ) = .{}, abbrev_relocs: std.ArrayListUnmanaged(AbbrevRelocation) = .{}, + exprloc_relocs: std.ArrayListUnmanaged(ExprlocRelocation) = .{}, fn init(gpa: Allocator, target: std.Target) DeclState { return .{ @@ -97,6 +98,16 @@ pub const DeclState = struct { self.abbrev_table.deinit(self.gpa); self.abbrev_resolver.deinit(self.gpa); self.abbrev_relocs.deinit(self.gpa); + self.exprloc_relocs.deinit(self.gpa); + } + + pub fn addExprlocReloc(self: *DeclState, target: u32, offset: u32, is_ptr: bool) !void { + log.debug("{x}: target sym @{d}, via GOT {}", .{ offset, target, is_ptr }); + try self.exprloc_relocs.append(self.gpa, .{ + .@"type" = if (is_ptr) .got_load else .direct_load, + .target = target, + .offset = offset, + }); } pub fn addTypeReloc( @@ -549,6 +560,18 @@ pub const AbbrevRelocation = struct { addend: u32, }; +pub const ExprlocRelocation = struct { + /// Type of the relocation: direct load ref, or GOT load ref (via GOT table) + @"type": enum { + direct_load, + got_load, + }, + /// Index of the target in the linker's locals symbol table. + target: u32, + /// Offset within the debug info buffer where to patch up the address value. + offset: u32, +}; + pub const SrcFn = struct { /// Offset from the beginning of the Debug Line Program header that contains this function. off: u32, @@ -1009,6 +1032,26 @@ pub fn commitDeclState( } } + while (decl_state.exprloc_relocs.popOrNull()) |reloc| { + switch (self.tag) { + .macho => { + const macho_file = file.cast(File.MachO).?; + const d_sym = &macho_file.d_sym.?; + try d_sym.relocs.append(d_sym.base.base.allocator, .{ + .@"type" = switch (reloc.@"type") { + .direct_load => .direct_load, + .got_load => .got_load, + }, + .target = reloc.target, + .offset = reloc.offset + atom.off, + .addend = 0, + .prev_vaddr = 0, + }); + }, + else => unreachable, + } + } + try self.writeDeclDebugInfo(file, atom, dbg_info_buffer.items); } diff --git a/src/link/MachO.zig b/src/link/MachO.zig index b193068361..d359a3fd5d 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -3472,6 +3472,9 @@ pub fn closeFiles(self: MachO) void { for (self.dylibs.items) |dylib| { dylib.file.close(); } + if (self.d_sym) |ds| { + ds.file.close(); + } } fn freeAtom(self: *MachO, atom: *Atom, match: MatchingSection, owns_atom: bool) void { @@ -4274,6 +4277,11 @@ pub fn freeDecl(self: *MachO, decl: *Module.Decl) void { self.got_entries_free_list.append(self.base.allocator, @intCast(u32, got_index)) catch {}; self.got_entries.items[got_index] = .{ .target = .{ .local = 0 }, .atom = undefined }; _ = self.got_entries_table.swapRemove(.{ .local = decl.link.macho.local_sym_index }); + + if (self.d_sym) |*d_sym| { + d_sym.swapRemoveRelocs(decl.link.macho.local_sym_index); + } + log.debug(" adding GOT index {d} to free list (target local@{d})", .{ got_index, decl.link.macho.local_sym_index, diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index 885f0ca6a8..aa7a29fcd1 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -59,6 +59,19 @@ debug_aranges_section_dirty: bool = false, debug_info_header_dirty: bool = false, debug_line_header_dirty: bool = false, +relocs: std.ArrayListUnmanaged(Reloc) = .{}, + +pub const Reloc = struct { + @"type": enum { + direct_load, + got_load, + }, + target: u32, + offset: u64, + addend: u32, + prev_vaddr: u64, +}; + /// You must call this function *after* `MachO.populateMissingMetadata()` /// has been called to get a viable debug symbols output. pub fn populateMissingMetadata(self: *DebugSymbols, allocator: Allocator) !void { @@ -254,6 +267,30 @@ pub fn flushModule(self: *DebugSymbols, allocator: Allocator, options: link.Opti // Zig source code. const module = options.module orelse return error.LinkingWithoutZigSourceUnimplemented; + for (self.relocs.items) |*reloc| { + const sym = switch (reloc.@"type") { + .direct_load => self.base.locals.items[reloc.target], + .got_load => blk: { + const got_index = self.base.got_entries_table.get(.{ .local = reloc.target }).?; + const got_entry = self.base.got_entries.items[got_index]; + break :blk self.base.locals.items[got_entry.atom.local_sym_index]; + }, + }; + if (sym.n_value == reloc.prev_vaddr) continue; + + const seg = &self.load_commands.items[self.dwarf_segment_cmd_index.?].segment; + const sect = &seg.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, + self.base.getString(sym.n_strx), + file_offset, + }); + try self.file.pwriteAll(mem.asBytes(&sym.n_value), file_offset); + reloc.prev_vaddr = sym.n_value; + } + if (self.debug_abbrev_section_dirty) { try self.dwarf.writeDbgAbbrev(&self.base.base); self.load_commands_dirty = true; @@ -330,7 +367,20 @@ pub fn deinit(self: *DebugSymbols, allocator: Allocator) void { } self.load_commands.deinit(allocator); self.dwarf.deinit(); - self.file.close(); + self.relocs.deinit(allocator); +} + +pub fn swapRemoveRelocs(self: *DebugSymbols, target: u32) void { + // TODO re-implement using a hashmap with free lists + var last_index: usize = 0; + while (last_index < self.relocs.items.len) { + const reloc = self.relocs.items[last_index]; + if (reloc.target == target) { + _ = self.relocs.swapRemove(last_index); + } else { + last_index += 1; + } + } } fn copySegmentCommand(