Commit 5ae82956aa

Jakub Konka <kubkon@jakubkonka.com>
2021-01-06 17:24:48
macho: write out rebase info
1 parent f44732c
Changed files (2)
src
src/link/MachO/imports.zig
@@ -6,6 +6,49 @@ const mem = std.mem;
 const assert = std.debug.assert;
 const Allocator = mem.Allocator;
 
+pub const RebaseInfoTable = struct {
+    rebase_type: u8 = macho.REBASE_TYPE_POINTER,
+    symbols: std.ArrayListUnmanaged(Symbol) = .{},
+
+    pub const Symbol = struct {
+        segment: u8,
+        offset: i64,
+    };
+
+    pub fn deinit(self: *RebaseInfoTable, allocator: *Allocator) void {
+        self.symbols.deinit(allocator);
+    }
+
+    /// Write the rebase info table to byte stream.
+    pub fn write(self: RebaseInfoTable, writer: anytype) !void {
+        try writer.writeByte(macho.REBASE_OPCODE_SET_TYPE_IMM | @truncate(u4, self.rebase_type));
+
+        for (self.symbols.items) |symbol| {
+            try writer.writeByte(macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment));
+            try leb.writeILEB128(writer, symbol.offset);
+            try writer.writeByte(macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | @truncate(u4, 1));
+        }
+
+        try writer.writeByte(macho.REBASE_OPCODE_DONE);
+    }
+
+    /// Calculate size in bytes of this rebase info table.
+    pub fn calcSize(self: *RebaseInfoTable) !u64 {
+        var stream = std.io.countingWriter(std.io.null_writer);
+        var writer = stream.writer();
+        var size: u64 = 1;
+
+        for (self.symbols.items) |symbol| {
+            size += 1;
+            try leb.writeILEB128(writer, symbol.offset);
+            size += 1;
+        }
+
+        size += 1 + stream.bytes_written;
+        return size;
+    }
+};
+
 /// Table of binding info entries used to tell the dyld which
 /// symbols to bind at loading time.
 pub const BindingInfoTable = struct {
src/link/MachO.zig
@@ -123,6 +123,8 @@ string_table: std.ArrayListUnmanaged(u8) = .{},
 /// Table of trampolines to the actual symbols in __text section.
 offset_table: std.ArrayListUnmanaged(u64) = .{},
 
+/// Table of rebase info entries.
+rebase_info_table: RebaseInfoTable = .{},
 /// Table of binding info entries.
 binding_info_table: BindingInfoTable = .{},
 /// Table of lazy binding info entries.
@@ -133,6 +135,7 @@ error_flags: File.ErrorFlags = File.ErrorFlags{},
 offset_table_count_dirty: bool = false,
 header_dirty: bool = false,
 load_commands_dirty: bool = false,
+rebase_info_dirty: bool = false,
 binding_info_dirty: bool = false,
 lazy_binding_info_dirty: bool = false,
 export_info_dirty: bool = false,
@@ -400,6 +403,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
                 main_cmd.entryoff = addr - text_segment.inner.vmaddr;
                 self.load_commands_dirty = true;
             }
+            try self.writeRebaseInfoTable();
             try self.writeBindingInfoTable();
             try self.writeLazyBindingInfoTable();
             try self.writeExportTrie();
@@ -425,6 +429,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
         .Lib => return error.TODOImplementWritingLibFiles,
     }
 
+    {
+        const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab;
+        dysymtab.nindirectsyms = 0;
+    }
+
     try self.writeLoadCommands();
     try self.writeHeader();
 
@@ -439,6 +448,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
     assert(!self.offset_table_count_dirty);
     assert(!self.header_dirty);
     assert(!self.load_commands_dirty);
+    assert(!self.rebase_info_dirty);
     assert(!self.binding_info_dirty);
     assert(!self.lazy_binding_info_dirty);
     assert(!self.export_info_dirty);
@@ -1289,6 +1299,12 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
         mem.writeIntLittle(u32, cccode[8..12], 0);
         try self.base.file.?.pwriteAll(&cccode, self.next_stub_helper_off.?);
         self.next_stub_helper_off = self.next_stub_helper_off.? + 3 * @sizeOf(u32);
+
+        try self.rebase_info_table.symbols.append(self.base.allocator, .{
+            .segment = 3,
+            .offset = 0,
+        });
+        self.rebase_info_dirty = true;
     }
 
     const text_section = text_segment.sections.items[self.text_section_index.?];
@@ -1821,12 +1837,6 @@ pub fn populateMissingMetadata(self: *MachO) !void {
     if (self.dyld_info_cmd_index == null) {
         self.dyld_info_cmd_index = @intCast(u16, self.load_commands.items.len);
 
-        // TODO Preallocate rebase, binding, and lazy binding info.
-        const export_size = 2;
-        const export_off = self.findFreeSpaceLinkedit(export_size, 1);
-
-        log.debug("found export info free space 0x{x} to 0x{x}", .{ export_off, export_off + export_size });
-
         try self.load_commands.append(self.base.allocator, .{
             .DyldInfoOnly = .{
                 .cmd = macho.LC_DYLD_INFO_ONLY,
@@ -1839,37 +1849,67 @@ pub fn populateMissingMetadata(self: *MachO) !void {
                 .weak_bind_size = 0,
                 .lazy_bind_off = 0,
                 .lazy_bind_size = 0,
-                .export_off = @intCast(u32, export_off),
-                .export_size = export_size,
+                .export_off = 0,
+                .export_size = 0,
             },
         });
+
+        const dyld = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
+
+        // Preallocate rebase, binding, lazy binding info, and export info.
+        const expected_size = 48; // TODO This is totally random.
+        const rebase_off = self.findFreeSpaceLinkedit(expected_size, 1);
+        log.debug("found rebase info free space 0x{x} to 0x{x}", .{ rebase_off, rebase_off + expected_size });
+        dyld.rebase_off = @intCast(u32, rebase_off);
+        dyld.rebase_size = expected_size;
+
+        const bind_off = self.findFreeSpaceLinkedit(expected_size, 1);
+        log.debug("found binding info free space 0x{x} to 0x{x}", .{ bind_off, bind_off + expected_size });
+        dyld.bind_off = @intCast(u32, bind_off);
+        dyld.bind_size = expected_size;
+
+        const lazy_bind_off = self.findFreeSpaceLinkedit(expected_size, 1);
+        log.debug("found lazy binding info free space 0x{x} to 0x{x}", .{ lazy_bind_off, lazy_bind_off + expected_size });
+        dyld.lazy_bind_off = @intCast(u32, lazy_bind_off);
+        dyld.lazy_bind_size = expected_size;
+
+        const export_off = self.findFreeSpaceLinkedit(expected_size, 1);
+        log.debug("found export info free space 0x{x} to 0x{x}", .{ export_off, export_off + expected_size });
+        dyld.export_off = @intCast(u32, export_off);
+        dyld.export_size = expected_size;
+
         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);
 
+        try self.load_commands.append(self.base.allocator, .{
+            .Symtab = .{
+                .cmd = macho.LC_SYMTAB,
+                .cmdsize = @sizeOf(macho.symtab_command),
+                .symoff = 0,
+                .nsyms = 0,
+                .stroff = 0,
+                .strsize = 0,
+            },
+        });
+
+        const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab;
+
         const symtab_size = self.base.options.symbol_count_hint * @sizeOf(macho.nlist_64);
         const symtab_off = self.findFreeSpaceLinkedit(symtab_size, @sizeOf(macho.nlist_64));
-
         log.debug("found symbol table free space 0x{x} to 0x{x}", .{ symtab_off, symtab_off + symtab_size });
+        symtab.symoff = @intCast(u32, symtab_off);
+        symtab.nsyms = @intCast(u32, self.base.options.symbol_count_hint);
 
         try self.string_table.append(self.base.allocator, 0); // Need a null at position 0.
         const strtab_size = self.string_table.items.len;
         const strtab_off = self.findFreeSpaceLinkedit(strtab_size, 1);
-
         log.debug("found string table free space 0x{x} to 0x{x}", .{ strtab_off, strtab_off + strtab_size });
+        symtab.stroff = @intCast(u32, strtab_off);
+        symtab.strsize = @intCast(u32, strtab_size);
 
-        try self.load_commands.append(self.base.allocator, .{
-            .Symtab = .{
-                .cmd = macho.LC_SYMTAB,
-                .cmdsize = @sizeOf(macho.symtab_command),
-                .symoff = @intCast(u32, symtab_off),
-                .nsyms = @intCast(u32, self.base.options.symbol_count_hint),
-                .stroff = @intCast(u32, strtab_off),
-                .strsize = @intCast(u32, strtab_size),
-            },
-        });
         self.header_dirty = true;
         self.load_commands_dirty = true;
         self.string_table_dirty = true;
@@ -1877,7 +1917,11 @@ pub fn populateMissingMetadata(self: *MachO) !void {
     if (self.dysymtab_cmd_index == null) {
         self.dysymtab_cmd_index = @intCast(u16, self.load_commands.items.len);
 
-        // TODO Preallocate space for indirect symbol table.
+        // Preallocate space for indirect symbol table.
+        const indsymtab_size = self.base.options.symbol_count_hint * @sizeOf(u64); // Each entry is just a u64.
+        const indsymtab_off = self.findFreeSpaceLinkedit(indsymtab_size, @sizeOf(u64));
+
+        log.debug("found indirect symbol table free space 0x{x} to 0x{x}", .{ indsymtab_off, indsymtab_off + indsymtab_size });
 
         try self.load_commands.append(self.base.allocator, .{
             .Dysymtab = .{
@@ -1895,8 +1939,8 @@ pub fn populateMissingMetadata(self: *MachO) !void {
                 .nmodtab = 0,
                 .extrefsymoff = 0,
                 .nextrefsyms = 0,
-                .indirectsymoff = 0,
-                .nindirectsyms = 0,
+                .indirectsymoff = @intCast(u32, indsymtab_off),
+                .nindirectsyms = @intCast(u32, self.base.options.symbol_count_hint),
                 .extreloff = 0,
                 .nextrel = 0,
                 .locreloff = 0,
@@ -2565,6 +2609,37 @@ fn writeExportTrie(self: *MachO) !void {
     self.export_info_dirty = false;
 }
 
+fn writeRebaseInfoTable(self: *MachO) !void {
+    if (!self.rebase_info_dirty) return;
+
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    const size = try self.rebase_info_table.calcSize();
+    var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size));
+    defer self.base.allocator.free(buffer);
+
+    var stream = std.io.fixedBufferStream(buffer);
+    try self.rebase_info_table.write(stream.writer());
+
+    const linkedit_segment = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
+    const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly;
+    const allocated_size = self.allocatedSizeLinkedit(dyld_info.rebase_off);
+    const needed_size = mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64));
+
+    if (needed_size > allocated_size) {
+        dyld_info.rebase_off = 0;
+        dyld_info.rebase_off = @intCast(u32, self.findFreeSpaceLinkedit(needed_size, 1));
+    }
+
+    dyld_info.rebase_size = @intCast(u32, needed_size);
+    log.debug("writing rebase info from 0x{x} to 0x{x}", .{ dyld_info.rebase_off, dyld_info.rebase_off + dyld_info.rebase_size });
+
+    try self.base.file.?.pwriteAll(buffer, dyld_info.rebase_off);
+    self.load_commands_dirty = true;
+    self.rebase_info_dirty = false;
+}
+
 fn writeBindingInfoTable(self: *MachO) !void {
     if (!self.binding_info_dirty) return;