macho: handle TLS imported from dylib

This is a missing feature which requires `__thread_ptrs` section
to be synthesised for any extern reference to a global TLS variable.
This commit is contained in:
Jakub Konka 2021-12-13 23:18:27 +01:00
parent 2e7a48d6bf
commit 3ff05b79b9
2 changed files with 135 additions and 9 deletions

View File

@ -130,6 +130,7 @@ objc_imageinfo_section_index: ?u16 = null,
tlv_section_index: ?u16 = null,
tlv_data_section_index: ?u16 = null,
tlv_bss_section_index: ?u16 = null,
tlv_ptrs_section_index: ?u16 = null,
la_symbol_ptr_section_index: ?u16 = null,
data_section_index: ?u16 = null,
bss_section_index: ?u16 = null,
@ -164,6 +165,9 @@ stub_helper_preamble_atom: ?*Atom = null,
strtab: std.ArrayListUnmanaged(u8) = .{},
strtab_dir: std.HashMapUnmanaged(u32, void, StringIndexContext, std.hash_map.default_max_load_percentage) = .{},
tlv_ptr_entries_map: std.AutoArrayHashMapUnmanaged(Atom.Relocation.Target, *Atom) = .{},
tlv_ptr_entries_map_free_list: std.ArrayListUnmanaged(u32) = .{},
got_entries_map: std.AutoArrayHashMapUnmanaged(Atom.Relocation.Target, *Atom) = .{},
got_entries_map_free_list: std.ArrayListUnmanaged(u32) = .{},
@ -1525,6 +1529,24 @@ pub fn getMatchingSection(self: *MachO, sect: macho.section_64) !?MatchingSectio
.sect = self.tlv_section_index.?,
};
},
macho.S_THREAD_LOCAL_VARIABLE_POINTERS => {
if (self.tlv_ptrs_section_index == null) {
self.tlv_ptrs_section_index = try self.initSection(
self.data_segment_cmd_index.?,
"__thread_ptrs",
sect.size,
sect.@"align",
.{
.flags = macho.S_THREAD_LOCAL_VARIABLE_POINTERS,
},
);
}
break :blk .{
.seg = self.data_segment_cmd_index.?,
.sect = self.tlv_ptrs_section_index.?,
};
},
macho.S_THREAD_LOCAL_REGULAR => {
if (self.tlv_data_section_index == null) {
self.tlv_data_section_index = try self.initSection(
@ -2142,6 +2164,24 @@ pub fn createGotAtom(self: *MachO, target: Atom.Relocation.Target) !*Atom {
return atom;
}
pub fn createTlvPtrAtom(self: *MachO, target: Atom.Relocation.Target) !*Atom {
const local_sym_index = @intCast(u32, self.locals.items.len);
try self.locals.append(self.base.allocator, .{
.n_strx = 0,
.n_type = macho.N_SECT,
.n_sect = 0,
.n_desc = 0,
.n_value = 0,
});
const atom = try self.createEmptyAtom(local_sym_index, @sizeOf(u64), 3);
assert(target == .global);
try atom.bindings.append(self.base.allocator, .{
.n_strx = target.global,
.offset = 0,
});
return atom;
}
fn createDyldPrivateAtom(self: *MachO) !void {
if (self.dyld_private_atom != null) return;
const local_sym_index = @intCast(u32, self.locals.items.len);
@ -3214,6 +3254,8 @@ pub fn deinit(self: *MachO) void {
}
self.section_ordinals.deinit(self.base.allocator);
self.tlv_ptr_entries_map.deinit(self.base.allocator);
self.tlv_ptr_entries_map_free_list.deinit(self.base.allocator);
self.got_entries_map.deinit(self.base.allocator);
self.got_entries_map_free_list.deinit(self.base.allocator);
self.stubs_map.deinit(self.base.allocator);
@ -4989,6 +5031,7 @@ fn sortSections(self: *MachO) !void {
&self.objc_data_section_index,
&self.data_section_index,
&self.tlv_section_index,
&self.tlv_ptrs_section_index,
&self.tlv_data_section_index,
&self.tlv_bss_section_index,
&self.bss_section_index,
@ -6216,6 +6259,14 @@ fn logSymtab(self: MachO) void {
}
}
log.debug("__thread_ptrs entries:", .{});
for (self.tlv_ptr_entries_map.keys()) |key| {
switch (key) {
.local => unreachable,
.global => |n_strx| log.debug(" {} => {s}", .{ key, self.getString(n_strx) }),
}
}
log.debug("stubs:", .{});
for (self.stubs_map.keys()) |key| {
log.debug(" {} => {s}", .{ key, self.getString(key) });

View File

@ -403,6 +403,13 @@ pub fn parseRelocs(self: *Atom, relocs: []macho.relocation_info, context: RelocC
}
try self.addPtrBindingOrRebase(rel, target, context);
},
.ARM64_RELOC_TLVP_LOAD_PAGE21,
.ARM64_RELOC_TLVP_LOAD_PAGEOFF12,
=> {
if (target == .global) {
try addTlvPtrEntry(target, context);
}
},
else => {},
}
},
@ -452,6 +459,11 @@ pub fn parseRelocs(self: *Atom, relocs: []macho.relocation_info, context: RelocC
@intCast(i64, target_sect_base_addr);
}
},
.X86_64_RELOC_TLV => {
if (target == .global) {
try addTlvPtrEntry(target, context);
}
},
else => {},
}
},
@ -531,6 +543,47 @@ fn addPtrBindingOrRebase(
}
}
fn addTlvPtrEntry(target: Relocation.Target, context: RelocContext) !void {
if (context.macho_file.tlv_ptr_entries_map.contains(target)) return;
const value_ptr = blk: {
if (context.macho_file.tlv_ptr_entries_map_free_list.popOrNull()) |i| {
log.debug("reusing __thread_ptrs entry index {d} for {}", .{ i, target });
context.macho_file.tlv_ptr_entries_map.keys()[i] = target;
const value_ptr = context.macho_file.tlv_ptr_entries_map.getPtr(target).?;
break :blk value_ptr;
} else {
const res = try context.macho_file.tlv_ptr_entries_map.getOrPut(
context.macho_file.base.allocator,
target,
);
log.debug("creating new __thread_ptrs entry at index {d} for {}", .{
context.macho_file.tlv_ptr_entries_map.getIndex(target).?,
target,
});
break :blk res.value_ptr;
}
};
const atom = try context.macho_file.createTlvPtrAtom(target);
value_ptr.* = atom;
const match = (try context.macho_file.getMatchingSection(.{
.segname = MachO.makeStaticString("__DATA"),
.sectname = MachO.makeStaticString("__thread_ptrs"),
.flags = macho.S_THREAD_LOCAL_VARIABLE_POINTERS,
})).?;
if (!context.object.start_atoms.contains(match)) {
try context.object.start_atoms.putNoClobber(context.allocator, match, atom);
}
if (context.object.end_atoms.getPtr(match)) |last| {
last.*.next = atom;
atom.prev = last.*;
last.* = atom;
} else {
try context.object.end_atoms.putNoClobber(context.allocator, match, atom);
}
}
fn addGotEntry(target: Relocation.Target, context: RelocContext) !void {
if (context.macho_file.got_entries_map.contains(target)) return;
@ -667,6 +720,7 @@ pub fn resolveRelocs(self: *Atom, macho_file: *MachO) !void {
const sym = macho_file.locals.items[self.local_sym_index];
break :blk sym.n_value + rel.offset;
};
var is_via_thread_ptrs: bool = false;
const target_addr = blk: {
const is_via_got = got: {
switch (arch) {
@ -742,8 +796,13 @@ pub fn resolveRelocs(self: *Atom, macho_file: *MachO) !void {
.undef => {
break :blk if (macho_file.stubs_map.get(n_strx)) |atom|
macho_file.locals.items[atom.local_sym_index].n_value
else
0;
else inner: {
if (macho_file.tlv_ptr_entries_map.get(rel.target)) |atom| {
is_via_thread_ptrs = true;
break :inner macho_file.locals.items[atom.local_sym_index].n_value;
}
break :inner 0;
};
},
}
},
@ -854,10 +913,12 @@ pub fn resolveRelocs(self: *Atom, macho_file: *MachO) !void {
},
.ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => {
const code = self.code.items[rel.offset..][0..4];
const actual_target_addr = @intCast(i64, target_addr) + rel.addend;
const RegInfo = struct {
rd: u5,
rn: u5,
size: u1,
size: u2,
};
const reg_info: RegInfo = blk: {
if (isArithmeticOp(code)) {
@ -878,13 +939,25 @@ pub fn resolveRelocs(self: *Atom, macho_file: *MachO) !void {
break :blk .{
.rd = inst.rt,
.rn = inst.rn,
.size = @truncate(u1, inst.size),
.size = inst.size,
};
}
};
const actual_target_addr = @intCast(i64, target_addr) + rel.addend;
const narrowed = @truncate(u12, @intCast(u64, actual_target_addr));
var inst = aarch64.Instruction{
var inst = if (is_via_thread_ptrs) blk: {
const offset = try math.divExact(u12, narrowed, 8);
break :blk aarch64.Instruction{
.load_store_register = .{
.rt = reg_info.rd,
.rn = reg_info.rn,
.offset = offset,
.opc = 0b01,
.op1 = 0b01,
.v = 0,
.size = reg_info.size,
},
};
} else aarch64.Instruction{
.add_subtract_immediate = .{
.rd = reg_info.rd,
.rn = reg_info.rn,
@ -892,7 +965,7 @@ pub fn resolveRelocs(self: *Atom, macho_file: *MachO) !void {
.sh = 0,
.s = 0,
.op = 0,
.sf = reg_info.size,
.sf = @truncate(u1, reg_info.size),
},
};
mem.writeIntLittle(u32, code, inst.toU32());
@ -942,8 +1015,10 @@ pub fn resolveRelocs(self: *Atom, macho_file: *MachO) !void {
mem.writeIntLittle(u32, self.code.items[rel.offset..][0..4], @bitCast(u32, displacement));
},
.X86_64_RELOC_TLV => {
// We need to rewrite the opcode from movq to leaq.
self.code.items[rel.offset - 2] = 0x8d;
if (!is_via_thread_ptrs) {
// We need to rewrite the opcode from movq to leaq.
self.code.items[rel.offset - 2] = 0x8d;
}
const displacement = try math.cast(
i32,
@intCast(i64, target_addr) - @intCast(i64, source_addr) - 4 + rel.addend,