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.
This commit is contained in:
Jakub Konka 2022-04-13 19:05:19 +02:00
parent 3f912430bd
commit edb428fae4
4 changed files with 112 additions and 2 deletions

View File

@ -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});

View File

@ -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);
}

View File

@ -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,

View File

@ -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(