Commit 3ff05b79b9

Jakub Konka <kubkon@jakubkonka.com>
2021-12-13 23:18:27
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.
1 parent 2e7a48d
Changed files (2)
src
src/link/MachO/Atom.zig
@@ -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,
src/link/MachO.zig
@@ -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) });