diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2021-03-28 21:42:56 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2021-03-28 21:42:56 -0700 |
| commit | b85ef2300fa72f5f4c73b8eb9e14f0218ada592d (patch) | |
| tree | daee8ab81eaefb5433f6ba3750656ba769a311a4 /src/link | |
| parent | 75080e351af8be45722bca50c1d5fcd503304d77 (diff) | |
| parent | 175adc0bd738c2e3a55bb71c6a53dcc920c203ba (diff) | |
| download | zig-b85ef2300fa72f5f4c73b8eb9e14f0218ada592d.tar.gz zig-b85ef2300fa72f5f4c73b8eb9e14f0218ada592d.zip | |
Merge remote-tracking branch 'origin/master' into llvm12
Diffstat (limited to 'src/link')
| -rw-r--r-- | src/link/MachO.zig | 1091 | ||||
| -rw-r--r-- | src/link/MachO/Archive.zig | 278 | ||||
| -rw-r--r-- | src/link/MachO/DebugSymbols.zig | 8 | ||||
| -rw-r--r-- | src/link/MachO/Object.zig | 229 | ||||
| -rw-r--r-- | src/link/MachO/Zld.zig | 3294 | ||||
| -rw-r--r-- | src/link/MachO/bind.zig | 145 | ||||
| -rw-r--r-- | src/link/MachO/imports.zig | 152 |
7 files changed, 4501 insertions, 696 deletions
diff --git a/src/link/MachO.zig b/src/link/MachO.zig index fe8fcd6281..517fabaf3e 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -11,7 +11,9 @@ const codegen = @import("../codegen.zig"); const aarch64 = @import("../codegen/aarch64.zig"); const math = std.math; const mem = std.mem; +const meta = std.meta; +const bind = @import("MachO/bind.zig"); const trace = @import("../tracy.zig").trace; const build_options = @import("build_options"); const Module = @import("../Module.zig"); @@ -24,9 +26,9 @@ const target_util = @import("../target.zig"); const DebugSymbols = @import("MachO/DebugSymbols.zig"); const Trie = @import("MachO/Trie.zig"); const CodeSignature = @import("MachO/CodeSignature.zig"); +const Zld = @import("MachO/Zld.zig"); usingnamespace @import("MachO/commands.zig"); -usingnamespace @import("MachO/imports.zig"); pub const base_tag: File.Tag = File.Tag.macho; @@ -87,14 +89,12 @@ code_signature_cmd_index: ?u16 = null, /// Index into __TEXT,__text section. text_section_index: ?u16 = null, -/// Index into __TEXT,__ziggot section. -got_section_index: ?u16 = null, /// Index into __TEXT,__stubs section. stubs_section_index: ?u16 = null, /// Index into __TEXT,__stub_helper section. stub_helper_section_index: ?u16 = null, /// Index into __DATA_CONST,__got section. -data_got_section_index: ?u16 = null, +got_section_index: ?u16 = null, /// Index into __DATA,__la_symbol_ptr section. la_symbol_ptr_section_index: ?u16 = null, /// Index into __DATA,__data section. @@ -104,16 +104,16 @@ entry_addr: ?u64 = null, /// Table of all local symbols /// Internally references string table for names (which are optional). -local_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{}, +locals: std.ArrayListUnmanaged(macho.nlist_64) = .{}, /// Table of all global symbols -global_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{}, +globals: std.ArrayListUnmanaged(macho.nlist_64) = .{}, /// Table of all extern nonlazy symbols, indexed by name. -extern_nonlazy_symbols: std.StringArrayHashMapUnmanaged(ExternSymbol) = .{}, +nonlazy_imports: std.StringArrayHashMapUnmanaged(Import) = .{}, /// Table of all extern lazy symbols, indexed by name. -extern_lazy_symbols: std.StringArrayHashMapUnmanaged(ExternSymbol) = .{}, +lazy_imports: std.StringArrayHashMapUnmanaged(Import) = .{}, -local_symbol_free_list: std.ArrayListUnmanaged(u32) = .{}, -global_symbol_free_list: std.ArrayListUnmanaged(u32) = .{}, +locals_free_list: std.ArrayListUnmanaged(u32) = .{}, +globals_free_list: std.ArrayListUnmanaged(u32) = .{}, offset_table_free_list: std.ArrayListUnmanaged(u32) = .{}, stub_helper_stubs_start_off: ?u64 = null, @@ -122,8 +122,8 @@ stub_helper_stubs_start_off: ?u64 = null, string_table: std.ArrayListUnmanaged(u8) = .{}, string_table_directory: std.StringHashMapUnmanaged(u32) = .{}, -/// Table of trampolines to the actual symbols in __text section. -offset_table: std.ArrayListUnmanaged(u64) = .{}, +/// Table of GOT entries. +offset_table: std.ArrayListUnmanaged(GOTEntry) = .{}, error_flags: File.ErrorFlags = File.ErrorFlags{}, @@ -154,14 +154,19 @@ string_table_needs_relocation: bool = false, /// allocate a fresh text block, which will have ideal capacity, and then grow it /// by 1 byte. It will then have -1 overcapacity. text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = .{}, + /// Pointer to the last allocated text block last_text_block: ?*TextBlock = null, + /// A list of all PIE fixups required for this run of the linker. /// Warning, this is currently NOT thread-safe. See the TODO below. /// TODO Move this list inside `updateDecl` where it should be allocated /// prior to calling `generateSymbol`, and then immediately deallocated /// rather than sitting in the global scope. -pie_fixups: std.ArrayListUnmanaged(PieFixup) = .{}, +/// TODO We should also rewrite this using generic relocations common to all +/// backends. +pie_fixups: std.ArrayListUnmanaged(PIEFixup) = .{}, + /// A list of all stub (extern decls) fixups required for this run of the linker. /// Warning, this is currently NOT thread-safe. See the TODO below. /// TODO Move this list inside `updateDecl` where it should be allocated @@ -169,14 +174,42 @@ pie_fixups: std.ArrayListUnmanaged(PieFixup) = .{}, /// rather than sitting in the global scope. stub_fixups: std.ArrayListUnmanaged(StubFixup) = .{}, -pub const PieFixup = struct { - /// Target address we wanted to address in absolute terms. - address: u64, - /// Where in the byte stream we should perform the fixup. - start: usize, - /// The length of the byte stream. For x86_64, this will be - /// variable. For aarch64, it will be fixed at 4 bytes. - len: usize, +pub const GOTEntry = struct { + /// GOT entry can either be a local pointer or an extern (nonlazy) import. + kind: enum { + Local, + Extern, + }, + + /// Id to the macho.nlist_64 from the respective table: either locals or nonlazy imports. + /// TODO I'm more and more inclined to just manage a single, max two symbol tables + /// rather than 4 as we currently do, but I'll follow up in the future PR. + symbol: u32, + + /// Index of this entry in the GOT. + index: u32, +}; + +pub const Import = struct { + /// MachO symbol table entry. + symbol: macho.nlist_64, + + /// Id of the dynamic library where the specified entries can be found. + dylib_ordinal: i64, + + /// Index of this import within the import list. + index: u32, +}; + +pub const PIEFixup = struct { + /// Target VM address of this relocation. + target_addr: u64, + + /// Offset within the byte stream. + offset: usize, + + /// Size of the relocation. + size: usize, }; pub const StubFixup = struct { @@ -260,9 +293,9 @@ pub const TextBlock = struct { /// File offset relocation happens transparently, so it is not included in /// this calculation. fn capacity(self: TextBlock, macho_file: MachO) u64 { - const self_sym = macho_file.local_symbols.items[self.local_sym_index]; + const self_sym = macho_file.locals.items[self.local_sym_index]; if (self.next) |next| { - const next_sym = macho_file.local_symbols.items[next.local_sym_index]; + const next_sym = macho_file.locals.items[next.local_sym_index]; return next_sym.n_value - self_sym.n_value; } else { // We are the last block. @@ -274,8 +307,8 @@ pub const TextBlock = struct { fn freeListEligible(self: TextBlock, macho_file: MachO) bool { // No need to keep a free list node for the last block. const next = self.next orelse return false; - const self_sym = macho_file.local_symbols.items[self.local_sym_index]; - const next_sym = macho_file.local_symbols.items[next.local_sym_index]; + const self_sym = macho_file.locals.items[self.local_sym_index]; + const next_sym = macho_file.locals.items[next.local_sym_index]; const cap = next_sym.n_value - self_sym.n_value; const ideal_cap = padToIdeal(self.size); if (cap <= ideal_cap) return false; @@ -344,7 +377,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio }; // Index 0 is always a null symbol. - try self.local_symbols.append(allocator, .{ + try self.locals.append(allocator, .{ .n_strx = 0, .n_type = 0, .n_sect = 0, @@ -600,7 +633,74 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { if (!mem.eql(u8, the_object_path, full_out_path)) { try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{}); } - } else { + } else outer: { + const use_zld = blk: { + if (self.base.options.is_native_os and self.base.options.system_linker_hack) { + // If the user forces the use of ld64, make sure we are running native! + break :blk false; + } + + if (self.base.options.target.cpu.arch == .aarch64) { + // On aarch64, always use zld. + break :blk true; + } + + if (self.base.options.link_libcpp or + self.base.options.output_mode == .Lib or + self.base.options.linker_script != null) + { + // Fallback to LLD in this handful of cases on x86_64 only. + break :blk false; + } + + break :blk true; + }; + + if (use_zld) { + var zld = Zld.init(self.base.allocator); + defer zld.deinit(); + zld.arch = target.cpu.arch; + + var input_files = std.ArrayList([]const u8).init(self.base.allocator); + defer input_files.deinit(); + // Positional arguments to the linker such as object files. + try input_files.appendSlice(self.base.options.objects); + for (comp.c_object_table.items()) |entry| { + try input_files.append(entry.key.status.success.object_path); + } + if (module_obj_path) |p| { + try input_files.append(p); + } + try input_files.append(comp.compiler_rt_static_lib.?.full_object_path); + // libc++ dep + if (self.base.options.link_libcpp) { + try input_files.append(comp.libcxxabi_static_lib.?.full_object_path); + try input_files.append(comp.libcxx_static_lib.?.full_object_path); + } + + if (self.base.options.verbose_link) { + var argv = std.ArrayList([]const u8).init(self.base.allocator); + defer argv.deinit(); + + try argv.append("zig"); + try argv.append("ld"); + + try argv.ensureCapacity(input_files.items.len); + for (input_files.items) |f| { + argv.appendAssumeCapacity(f); + } + + try argv.append("-o"); + try argv.append(full_out_path); + + Compilation.dump_argv(argv.items); + } + + try zld.link(input_files.items, full_out_path); + + break :outer; + } + // Create an LLD command line and invoke it. var argv = std.ArrayList([]const u8).init(self.base.allocator); defer argv.deinit(); @@ -644,9 +744,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { try argv.append("defs"); } - if (is_dyn_lib) { - try argv.append("-static"); - } else { + if (is_exe_or_dyn_lib) { try argv.append("-dynamic"); } @@ -836,7 +934,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { } }, else => { - log.err("{s} terminated", .{ argv.items[0] }); + log.err("{s} terminated", .{argv.items[0]}); return error.LLDCrashed; }, } @@ -873,119 +971,6 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { log.warn("unexpected LLD stderr:\n{s}", .{stderr}); } } - - // At this stage, LLD has done its job. It is time to patch the resultant - // binaries up! - const out_file = try directory.handle.openFile(self.base.options.emit.?.sub_path, .{ .write = true }); - try self.parseFromFile(out_file); - - if (self.libsystem_cmd_index == null and self.header.?.filetype == macho.MH_EXECUTE) { - const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; - const text_section = text_segment.sections.items[self.text_section_index.?]; - const after_last_cmd_offset = self.header.?.sizeofcmds + @sizeOf(macho.mach_header_64); - const needed_size = padToIdeal(@sizeOf(macho.linkedit_data_command)); - - if (needed_size + after_last_cmd_offset > text_section.offset) { - log.err("Unable to extend padding between the end of load commands and start of __text section.", .{}); - log.err("Re-run the linker with '-headerpad 0x{x}' option if available, or", .{needed_size}); - log.err("fall back to the system linker by exporting 'ZIG_SYSTEM_LINKER_HACK=1'.", .{}); - return error.NotEnoughPadding; - } - - // Calculate next available dylib ordinal. - const next_ordinal = blk: { - var ordinal: u32 = 1; - for (self.load_commands.items) |cmd| { - switch (cmd) { - .Dylib => ordinal += 1, - else => {}, - } - } - break :blk ordinal; - }; - - // Add load dylib load command - self.libsystem_cmd_index = @intCast(u16, self.load_commands.items.len); - const cmdsize = @intCast(u32, mem.alignForwardGeneric( - u64, - @sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH), - @sizeOf(u64), - )); - // TODO Find a way to work out runtime version from the OS version triple stored in std.Target. - // In the meantime, we're gonna hardcode to the minimum compatibility version of 0.0.0. - const min_version = 0x0; - var dylib_cmd = emptyGenericCommandWithData(macho.dylib_command{ - .cmd = macho.LC_LOAD_DYLIB, - .cmdsize = cmdsize, - .dylib = .{ - .name = @sizeOf(macho.dylib_command), - .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files - .current_version = min_version, - .compatibility_version = min_version, - }, - }); - dylib_cmd.data = try self.base.allocator.alloc(u8, cmdsize - dylib_cmd.inner.dylib.name); - mem.set(u8, dylib_cmd.data, 0); - mem.copy(u8, dylib_cmd.data, mem.spanZ(LIB_SYSTEM_PATH)); - try self.load_commands.append(self.base.allocator, .{ .Dylib = dylib_cmd }); - self.header_dirty = true; - self.load_commands_dirty = true; - - if (self.symtab_cmd_index == null or self.dysymtab_cmd_index == null) { - log.err("Incomplete Mach-O binary: no LC_SYMTAB or LC_DYSYMTAB load command found!", .{}); - log.err("Without the symbol table, it is not possible to patch up the binary for cross-compilation.", .{}); - return error.NoSymbolTableFound; - } - - // Patch dyld info - try self.fixupBindInfo(next_ordinal); - try self.fixupLazyBindInfo(next_ordinal); - - // Write updated load commands and the header - try self.writeLoadCommands(); - try self.writeHeader(); - - assert(!self.header_dirty); - assert(!self.load_commands_dirty); - } - if (self.code_signature_cmd_index == null) outer: { - if (target.cpu.arch != .aarch64) break :outer; // This is currently needed only for aarch64 targets. - const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; - const text_section = text_segment.sections.items[self.text_section_index.?]; - const after_last_cmd_offset = self.header.?.sizeofcmds + @sizeOf(macho.mach_header_64); - const needed_size = padToIdeal(@sizeOf(macho.linkedit_data_command)); - - if (needed_size + after_last_cmd_offset > text_section.offset) { - log.err("Unable to extend padding between the end of load commands and start of __text section.", .{}); - log.err("Re-run the linker with '-headerpad 0x{x}' option if available, or", .{needed_size}); - log.err("fall back to the system linker by exporting 'ZIG_SYSTEM_LINKER_HACK=1'.", .{}); - return error.NotEnoughPadding; - } - - // Add code signature load command - self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len); - try self.load_commands.append(self.base.allocator, .{ - .LinkeditData = .{ - .cmd = macho.LC_CODE_SIGNATURE, - .cmdsize = @sizeOf(macho.linkedit_data_command), - .dataoff = 0, - .datasize = 0, - }, - }); - self.header_dirty = true; - self.load_commands_dirty = true; - - // Pad out space for code signature - try self.writeCodeSignaturePadding(); - // Write updated load commands and the header - try self.writeLoadCommands(); - try self.writeHeader(); - // Generate adhoc code signature - try self.writeCodeSignature(); - - assert(!self.header_dirty); - assert(!self.load_commands_dirty); - } } } @@ -1021,14 +1006,14 @@ pub fn deinit(self: *MachO) void { if (self.d_sym) |*ds| { ds.deinit(self.base.allocator); } - for (self.extern_lazy_symbols.items()) |*entry| { + for (self.lazy_imports.items()) |*entry| { self.base.allocator.free(entry.key); } - self.extern_lazy_symbols.deinit(self.base.allocator); - for (self.extern_nonlazy_symbols.items()) |*entry| { + self.lazy_imports.deinit(self.base.allocator); + for (self.nonlazy_imports.items()) |*entry| { self.base.allocator.free(entry.key); } - self.extern_nonlazy_symbols.deinit(self.base.allocator); + self.nonlazy_imports.deinit(self.base.allocator); self.pie_fixups.deinit(self.base.allocator); self.stub_fixups.deinit(self.base.allocator); self.text_block_free_list.deinit(self.base.allocator); @@ -1042,10 +1027,10 @@ pub fn deinit(self: *MachO) void { } self.string_table_directory.deinit(self.base.allocator); self.string_table.deinit(self.base.allocator); - self.global_symbols.deinit(self.base.allocator); - self.global_symbol_free_list.deinit(self.base.allocator); - self.local_symbols.deinit(self.base.allocator); - self.local_symbol_free_list.deinit(self.base.allocator); + self.globals.deinit(self.base.allocator); + self.globals_free_list.deinit(self.base.allocator); + self.locals.deinit(self.base.allocator); + self.locals_free_list.deinit(self.base.allocator); for (self.load_commands.items) |*lc| { lc.deinit(self.base.allocator); } @@ -1100,7 +1085,7 @@ fn shrinkTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64) vo } fn growTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { - const sym = self.local_symbols.items[text_block.local_sym_index]; + const sym = self.locals.items[text_block.local_sym_index]; const align_ok = mem.alignBackwardGeneric(u64, sym.n_value, alignment) == sym.n_value; const need_realloc = !align_ok or new_block_size > text_block.capacity(self.*); if (!need_realloc) return sym.n_value; @@ -1110,34 +1095,41 @@ fn growTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, alig pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void { if (decl.link.macho.local_sym_index != 0) return; - try self.local_symbols.ensureCapacity(self.base.allocator, self.local_symbols.items.len + 1); + try self.locals.ensureCapacity(self.base.allocator, self.locals.items.len + 1); try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1); - if (self.local_symbol_free_list.popOrNull()) |i| { + if (self.locals_free_list.popOrNull()) |i| { log.debug("reusing symbol index {d} for {s}", .{ i, decl.name }); decl.link.macho.local_sym_index = i; } else { - log.debug("allocating symbol index {d} for {s}", .{ self.local_symbols.items.len, decl.name }); - decl.link.macho.local_sym_index = @intCast(u32, self.local_symbols.items.len); - _ = self.local_symbols.addOneAssumeCapacity(); + log.debug("allocating symbol index {d} for {s}", .{ self.locals.items.len, decl.name }); + decl.link.macho.local_sym_index = @intCast(u32, self.locals.items.len); + _ = self.locals.addOneAssumeCapacity(); } if (self.offset_table_free_list.popOrNull()) |i| { + log.debug("reusing offset table entry index {d} for {s}", .{ i, decl.name }); decl.link.macho.offset_table_index = i; } else { + log.debug("allocating offset table entry index {d} for {s}", .{ self.offset_table.items.len, decl.name }); decl.link.macho.offset_table_index = @intCast(u32, self.offset_table.items.len); _ = self.offset_table.addOneAssumeCapacity(); self.offset_table_count_dirty = true; + self.rebase_info_dirty = true; } - self.local_symbols.items[decl.link.macho.local_sym_index] = .{ + self.locals.items[decl.link.macho.local_sym_index] = .{ .n_strx = 0, .n_type = 0, .n_sect = 0, .n_desc = 0, .n_value = 0, }; - self.offset_table.items[decl.link.macho.offset_table_index] = 0; + self.offset_table.items[decl.link.macho.offset_table_index] = .{ + .kind = .Local, + .symbol = decl.link.macho.local_sym_index, + .index = decl.link.macho.offset_table_index, + }; } pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { @@ -1180,8 +1172,9 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { .externally_managed => |x| x, .appended => code_buffer.items, .fail => |em| { - // Clear any PIE fixups and stub fixups for this decl. + // Clear any PIE fixups for this decl. self.pie_fixups.shrinkRetainingCapacity(0); + // Clear any stub fixups for this decl. self.stub_fixups.shrinkRetainingCapacity(0); decl.analysis = .codegen_failure; try module.failed_decls.put(module.gpa, decl, em); @@ -1191,7 +1184,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { const required_alignment = typed_value.ty.abiAlignment(self.base.options.target); assert(decl.link.macho.local_sym_index != 0); // Caller forgot to call allocateDeclIndexes() - const symbol = &self.local_symbols.items[decl.link.macho.local_sym_index]; + const symbol = &self.locals.items[decl.link.macho.local_sym_index]; if (decl.link.macho.size != 0) { const capacity = decl.link.macho.capacity(self.*); @@ -1200,9 +1193,12 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { const vaddr = try self.growTextBlock(&decl.link.macho, code.len, required_alignment); log.debug("growing {s} from 0x{x} to 0x{x}", .{ decl.name, symbol.n_value, vaddr }); if (vaddr != symbol.n_value) { - symbol.n_value = vaddr; log.debug(" (writing new offset table entry)", .{}); - self.offset_table.items[decl.link.macho.offset_table_index] = vaddr; + self.offset_table.items[decl.link.macho.offset_table_index] = .{ + .kind = .Local, + .symbol = decl.link.macho.local_sym_index, + .index = decl.link.macho.offset_table_index, + }; try self.writeOffsetTableEntry(decl.link.macho.offset_table_index); } } else if (code.len < decl.link.macho.size) { @@ -1231,7 +1227,11 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { .n_desc = 0, .n_value = addr, }; - self.offset_table.items[decl.link.macho.offset_table_index] = addr; + self.offset_table.items[decl.link.macho.offset_table_index] = .{ + .kind = .Local, + .symbol = decl.link.macho.local_sym_index, + .index = decl.link.macho.offset_table_index, + }; try self.writeLocalSymbol(decl.link.macho.local_sym_index); if (self.d_sym) |*ds| @@ -1239,30 +1239,48 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { try self.writeOffsetTableEntry(decl.link.macho.offset_table_index); } - // Perform PIE fixups (if any) - const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; - const got_section = text_segment.sections.items[self.got_section_index.?]; + // Calculate displacements to target addr (if any). while (self.pie_fixups.popOrNull()) |fixup| { - const target_addr = fixup.address; - const this_addr = symbol.n_value + fixup.start; + assert(fixup.size == 4); + const this_addr = symbol.n_value + fixup.offset; + const target_addr = fixup.target_addr; + switch (self.base.options.target.cpu.arch) { .x86_64 => { - assert(target_addr >= this_addr + fixup.len); - const displacement = try math.cast(u32, target_addr - this_addr - fixup.len); - var placeholder = code_buffer.items[fixup.start + fixup.len - @sizeOf(u32) ..][0..@sizeOf(u32)]; - mem.writeIntSliceLittle(u32, placeholder, displacement); + const displacement = try math.cast(u32, target_addr - this_addr - 4); + mem.writeIntLittle(u32, code_buffer.items[fixup.offset..][0..4], displacement); }, .aarch64 => { - assert(target_addr >= this_addr); - const displacement = try math.cast(u27, target_addr - this_addr); - var placeholder = code_buffer.items[fixup.start..][0..fixup.len]; - mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.b(@as(i28, displacement)).toU32()); + // TODO optimize instruction based on jump length (use ldr(literal) + nop if possible). + { + const inst = code_buffer.items[fixup.offset..][0..4]; + var parsed = mem.bytesAsValue(meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.PCRelativeAddress, + ), inst); + const this_page = @intCast(i32, this_addr >> 12); + const target_page = @intCast(i32, target_addr >> 12); + const pages = @bitCast(u21, @intCast(i21, target_page - this_page)); + parsed.immhi = @truncate(u19, pages >> 2); + parsed.immlo = @truncate(u2, pages); + } + { + const inst = code_buffer.items[fixup.offset + 4 ..][0..4]; + var parsed = mem.bytesAsValue(meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.LoadStoreRegister, + ), inst); + const narrowed = @truncate(u12, target_addr); + const offset = try math.divExact(u12, narrowed, 8); + parsed.offset = offset; + } }, else => unreachable, // unsupported target architecture } } // Resolve stubs (if any) + const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; const stubs = text_segment.sections.items[self.stubs_section_index.?]; for (self.stub_fixups.items) |fixup| { const stub_addr = stubs.addr + fixup.symbol * stubs.reserved2; @@ -1287,9 +1305,6 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { try self.writeStubInStubHelper(fixup.symbol); try self.writeLazySymbolPointer(fixup.symbol); - const extern_sym = &self.extern_lazy_symbols.items()[fixup.symbol].value; - extern_sym.segment = self.data_segment_cmd_index.?; - extern_sym.offset = fixup.symbol * @sizeOf(u64); self.rebase_info_dirty = true; self.lazy_binding_info_dirty = true; } @@ -1331,9 +1346,9 @@ pub fn updateDeclExports( const tracy = trace(@src()); defer tracy.end(); - try self.global_symbols.ensureCapacity(self.base.allocator, self.global_symbols.items.len + exports.len); + try self.globals.ensureCapacity(self.base.allocator, self.globals.items.len + exports.len); if (decl.link.macho.local_sym_index == 0) return; - const decl_sym = &self.local_symbols.items[decl.link.macho.local_sym_index]; + const decl_sym = &self.locals.items[decl.link.macho.local_sym_index]; for (exports) |exp| { if (exp.options.section) |section_name| { @@ -1366,7 +1381,7 @@ pub fn updateDeclExports( }; const n_type = decl_sym.n_type | macho.N_EXT; if (exp.link.macho.sym_index) |i| { - const sym = &self.global_symbols.items[i]; + const sym = &self.globals.items[i]; sym.* = .{ .n_strx = try self.updateString(sym.n_strx, exp.options.name), .n_type = n_type, @@ -1376,12 +1391,12 @@ pub fn updateDeclExports( }; } else { const name_str_index = try self.makeString(exp.options.name); - const i = if (self.global_symbol_free_list.popOrNull()) |i| i else blk: { - _ = self.global_symbols.addOneAssumeCapacity(); + const i = if (self.globals_free_list.popOrNull()) |i| i else blk: { + _ = self.globals.addOneAssumeCapacity(); self.export_info_dirty = true; - break :blk self.global_symbols.items.len - 1; + break :blk self.globals.items.len - 1; }; - self.global_symbols.items[i] = .{ + self.globals.items[i] = .{ .n_strx = name_str_index, .n_type = n_type, .n_sect = @intCast(u8, self.text_section_index.?) + 1, @@ -1396,18 +1411,18 @@ pub fn updateDeclExports( pub fn deleteExport(self: *MachO, exp: Export) void { const sym_index = exp.sym_index orelse return; - self.global_symbol_free_list.append(self.base.allocator, sym_index) catch {}; - self.global_symbols.items[sym_index].n_type = 0; + self.globals_free_list.append(self.base.allocator, sym_index) catch {}; + self.globals.items[sym_index].n_type = 0; } pub fn freeDecl(self: *MachO, decl: *Module.Decl) void { // Appending to free lists is allowed to fail because the free lists are heuristics based anyway. self.freeTextBlock(&decl.link.macho); if (decl.link.macho.local_sym_index != 0) { - self.local_symbol_free_list.append(self.base.allocator, decl.link.macho.local_sym_index) catch {}; + self.locals_free_list.append(self.base.allocator, decl.link.macho.local_sym_index) catch {}; self.offset_table_free_list.append(self.base.allocator, decl.link.macho.offset_table_index) catch {}; - self.local_symbols.items[decl.link.macho.local_sym_index].n_type = 0; + self.locals.items[decl.link.macho.local_sym_index].n_type = 0; decl.link.macho.local_sym_index = 0; } @@ -1415,7 +1430,7 @@ pub fn freeDecl(self: *MachO, decl: *Module.Decl) void { pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 { assert(decl.link.macho.local_sym_index != 0); - return self.local_symbols.items[decl.link.macho.local_sym_index].n_value; + return self.locals.items[decl.link.macho.local_sym_index].n_value; } pub fn populateMissingMetadata(self: *MachO) !void { @@ -1555,39 +1570,6 @@ pub fn populateMissingMetadata(self: *MachO) !void { self.header_dirty = true; self.load_commands_dirty = true; } - if (self.got_section_index == null) { - const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; - self.got_section_index = @intCast(u16, text_segment.sections.items.len); - - const alignment: u2 = switch (self.base.options.target.cpu.arch) { - .x86_64 => 0, - .aarch64 => 2, - else => unreachable, // unhandled architecture type - }; - const flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; - const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint; - const off = text_segment.findFreeSpace(needed_size, @alignOf(u64), self.header_pad); - assert(off + needed_size <= text_segment.inner.fileoff + text_segment.inner.filesize); // TODO Must expand __TEXT segment. - - log.debug("found __ziggot section free space 0x{x} to 0x{x}", .{ off, off + needed_size }); - - try text_segment.addSection(self.base.allocator, .{ - .sectname = makeStaticString("__ziggot"), - .segname = makeStaticString("__TEXT"), - .addr = text_segment.inner.vmaddr + off, - .size = needed_size, - .offset = @intCast(u32, off), - .@"align" = alignment, - .reloff = 0, - .nreloc = 0, - .flags = flags, - .reserved1 = 0, - .reserved2 = 0, - .reserved3 = 0, - }); - self.header_dirty = true; - self.load_commands_dirty = true; - } if (self.stubs_section_index == null) { const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; self.stubs_section_index = @intCast(u16, text_segment.sections.items.len); @@ -1599,7 +1581,7 @@ pub fn populateMissingMetadata(self: *MachO) !void { }; const stub_size: u4 = switch (self.base.options.target.cpu.arch) { .x86_64 => 6, - .aarch64 => 2 * @sizeOf(u32), + .aarch64 => 3 * @sizeOf(u32), else => unreachable, // unhandled architecture type }; const flags = macho.S_SYMBOL_STUBS | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS; @@ -1688,9 +1670,9 @@ pub fn populateMissingMetadata(self: *MachO) !void { self.header_dirty = true; self.load_commands_dirty = true; } - if (self.data_got_section_index == null) { + if (self.got_section_index == null) { const dc_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; - self.data_got_section_index = @intCast(u16, dc_segment.sections.items.len); + self.got_section_index = @intCast(u16, dc_segment.sections.items.len); const flags = macho.S_NON_LAZY_SYMBOL_POINTERS; const needed_size = @sizeOf(u64) * self.base.options.symbol_count_hint; @@ -2062,12 +2044,12 @@ pub fn populateMissingMetadata(self: *MachO) !void { self.header_dirty = true; self.load_commands_dirty = true; } - if (!self.extern_nonlazy_symbols.contains("dyld_stub_binder")) { - const index = @intCast(u32, self.extern_nonlazy_symbols.items().len); + if (!self.nonlazy_imports.contains("dyld_stub_binder")) { + const index = @intCast(u32, self.nonlazy_imports.items().len); const name = try self.base.allocator.dupe(u8, "dyld_stub_binder"); const offset = try self.makeString("dyld_stub_binder"); - try self.extern_nonlazy_symbols.putNoClobber(self.base.allocator, name, .{ - .inner = .{ + try self.nonlazy_imports.putNoClobber(self.base.allocator, name, .{ + .symbol = .{ .n_strx = offset, .n_type = std.macho.N_UNDF | std.macho.N_EXT, .n_sect = 0, @@ -2075,68 +2057,19 @@ pub fn populateMissingMetadata(self: *MachO) !void { .n_value = 0, }, .dylib_ordinal = 1, // TODO this is currently hardcoded. - .segment = self.data_const_segment_cmd_index.?, - .offset = index * @sizeOf(u64), + .index = index, + }); + const off_index = @intCast(u32, self.offset_table.items.len); + try self.offset_table.append(self.base.allocator, .{ + .kind = .Extern, + .symbol = index, + .index = off_index, }); + try self.writeOffsetTableEntry(off_index); self.binding_info_dirty = true; } if (self.stub_helper_stubs_start_off == null) { - const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; - const stub_helper = &text_segment.sections.items[self.stub_helper_section_index.?]; - const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; - const data = &data_segment.sections.items[self.data_section_index.?]; - const data_const_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; - const got = &data_const_segment.sections.items[self.data_got_section_index.?]; - switch (self.base.options.target.cpu.arch) { - .x86_64 => { - const code_size = 15; - var code: [code_size]u8 = undefined; - // lea %r11, [rip + disp] - code[0] = 0x4c; - code[1] = 0x8d; - code[2] = 0x1d; - { - const displacement = try math.cast(u32, data.addr - stub_helper.addr - 7); - mem.writeIntLittle(u32, code[3..7], displacement); - } - // push %r11 - code[7] = 0x41; - code[8] = 0x53; - // jmp [rip + disp] - code[9] = 0xff; - code[10] = 0x25; - { - const displacement = try math.cast(u32, got.addr - stub_helper.addr - code_size); - mem.writeIntLittle(u32, code[11..], displacement); - } - self.stub_helper_stubs_start_off = stub_helper.offset + code_size; - try self.base.file.?.pwriteAll(&code, stub_helper.offset); - }, - .aarch64 => { - var code: [4 * @sizeOf(u32)]u8 = undefined; - { - const displacement = try math.cast(i21, data.addr - stub_helper.addr); - mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x17, displacement).toU32()); - } - mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.stp( - .x16, - .x17, - aarch64.Register.sp, - aarch64.Instruction.LoadStorePairOffset.pre_index(-16), - ).toU32()); - { - const displacement = try math.divExact(u64, got.addr - stub_helper.addr - 2 * @sizeOf(u32), 4); - const literal = try math.cast(u19, displacement); - mem.writeIntLittle(u32, code[8..12], aarch64.Instruction.ldr(.x16, .{ - .literal = literal, - }).toU32()); - } - mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.br(.x16).toU32()); - self.stub_helper_stubs_start_off = stub_helper.offset + 4 * @sizeOf(u32); - try self.base.file.?.pwriteAll(&code, stub_helper.offset); - }, - else => unreachable, - } + try self.writeStubHelperPreamble(); } } @@ -2161,7 +2094,7 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, const big_block = self.text_block_free_list.items[i]; // We now have a pointer to a live text block that has too much capacity. // Is it enough that we could fit this new text block? - const sym = self.local_symbols.items[big_block.local_sym_index]; + const sym = self.locals.items[big_block.local_sym_index]; const capacity = big_block.capacity(self.*); const ideal_capacity = padToIdeal(capacity); const ideal_capacity_end_vaddr = sym.n_value + ideal_capacity; @@ -2192,7 +2125,7 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, } break :blk new_start_vaddr; } else if (self.last_text_block) |last| { - const last_symbol = self.local_symbols.items[last.local_sym_index]; + const last_symbol = self.locals.items[last.local_sym_index]; // TODO We should pad out the excess capacity with NOPs. For executables, // no padding seems to be OK, but it will probably not be for objects. const ideal_capacity = padToIdeal(last.size); @@ -2290,12 +2223,12 @@ fn updateString(self: *MachO, old_str_off: u32, new_name: []const u8) !u32 { } pub fn addExternSymbol(self: *MachO, name: []const u8) !u32 { - const index = @intCast(u32, self.extern_lazy_symbols.items().len); + const index = @intCast(u32, self.lazy_imports.items().len); const offset = try self.makeString(name); const sym_name = try self.base.allocator.dupe(u8, name); const dylib_ordinal = 1; // TODO this is now hardcoded, since we only support libSystem. - try self.extern_lazy_symbols.putNoClobber(self.base.allocator, sym_name, .{ - .inner = .{ + try self.lazy_imports.putNoClobber(self.base.allocator, sym_name, .{ + .symbol = .{ .n_strx = offset, .n_type = macho.N_UNDF | macho.N_EXT, .n_sect = 0, @@ -2303,6 +2236,7 @@ pub fn addExternSymbol(self: *MachO, name: []const u8) !u32 { .n_value = 0, }, .dylib_ordinal = dylib_ordinal, + .index = index, }); log.debug("adding new extern symbol '{s}' with dylib ordinal '{}'", .{ name, dylib_ordinal }); return index; @@ -2461,41 +2395,29 @@ fn findFreeSpaceLinkedit(self: *MachO, object_size: u64, min_alignment: u16, sta } fn writeOffsetTableEntry(self: *MachO, index: usize) !void { - const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; - const sect = &text_segment.sections.items[self.got_section_index.?]; + const seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const sect = &seg.sections.items[self.got_section_index.?]; const off = sect.offset + @sizeOf(u64) * index; - const vmaddr = sect.addr + @sizeOf(u64) * index; if (self.offset_table_count_dirty) { // TODO relocate. self.offset_table_count_dirty = false; } - var code: [8]u8 = undefined; - switch (self.base.options.target.cpu.arch) { - .x86_64 => { - const pos_symbol_off = try math.cast(u31, vmaddr - self.offset_table.items[index] + 7); - const symbol_off = @bitCast(u32, @as(i32, pos_symbol_off) * -1); - // lea %rax, [rip - disp] - code[0] = 0x48; - code[1] = 0x8D; - code[2] = 0x5; - mem.writeIntLittle(u32, code[3..7], symbol_off); - // ret - code[7] = 0xC3; - }, - .aarch64 => { - const pos_symbol_off = try math.cast(u20, vmaddr - self.offset_table.items[index]); - const symbol_off = @as(i21, pos_symbol_off) * -1; - // adr x0, #-disp - mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x0, symbol_off).toU32()); - // ret x28 - mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ret(.x28).toU32()); - }, - else => unreachable, // unsupported target architecture - } - log.debug("writing offset table entry 0x{x} at 0x{x}", .{ self.offset_table.items[index], off }); - try self.base.file.?.pwriteAll(&code, off); + const got_entry = self.offset_table.items[index]; + const sym = blk: { + switch (got_entry.kind) { + .Local => { + break :blk self.locals.items[got_entry.symbol]; + }, + .Extern => { + break :blk self.nonlazy_imports.items()[got_entry.symbol].value.symbol; + }, + } + }; + const sym_name = self.getString(sym.n_strx); + log.debug("writing offset table entry [ 0x{x} => 0x{x} ({s}) ]", .{ off, sym.n_value, sym_name }); + try self.base.file.?.pwriteAll(mem.asBytes(&sym.n_value), off); } fn writeLazySymbolPointer(self: *MachO, index: u32) !void { @@ -2518,6 +2440,133 @@ fn writeLazySymbolPointer(self: *MachO, index: u32) !void { try self.base.file.?.pwriteAll(&buf, off); } +fn writeStubHelperPreamble(self: *MachO) !void { + const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const stub_helper = &text_segment.sections.items[self.stub_helper_section_index.?]; + const data_const_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const got = &data_const_segment.sections.items[self.got_section_index.?]; + const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const data = &data_segment.sections.items[self.data_section_index.?]; + + switch (self.base.options.target.cpu.arch) { + .x86_64 => { + const code_size = 15; + var code: [code_size]u8 = undefined; + // lea %r11, [rip + disp] + code[0] = 0x4c; + code[1] = 0x8d; + code[2] = 0x1d; + { + const target_addr = data.addr; + const displacement = try math.cast(u32, target_addr - stub_helper.addr - 7); + mem.writeIntLittle(u32, code[3..7], displacement); + } + // push %r11 + code[7] = 0x41; + code[8] = 0x53; + // jmp [rip + disp] + code[9] = 0xff; + code[10] = 0x25; + { + const displacement = try math.cast(u32, got.addr - stub_helper.addr - code_size); + mem.writeIntLittle(u32, code[11..], displacement); + } + try self.base.file.?.pwriteAll(&code, stub_helper.offset); + self.stub_helper_stubs_start_off = stub_helper.offset + code_size; + }, + .aarch64 => { + var code: [6 * @sizeOf(u32)]u8 = undefined; + + data_blk_outer: { + const this_addr = stub_helper.addr; + const target_addr = data.addr; + data_blk: { + const displacement = math.cast(i21, target_addr - this_addr) catch |_| break :data_blk; + // adr x17, disp + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x17, displacement).toU32()); + // nop + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.nop().toU32()); + break :data_blk_outer; + } + data_blk: { + const new_this_addr = this_addr + @sizeOf(u32); + const displacement = math.cast(i21, target_addr - new_this_addr) catch |_| break :data_blk; + // nop + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.nop().toU32()); + // adr x17, disp + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.adr(.x17, displacement).toU32()); + break :data_blk_outer; + } + // Jump is too big, replace adr with adrp and add. + const this_page = @intCast(i32, this_addr >> 12); + const target_page = @intCast(i32, target_addr >> 12); + const pages = @intCast(i21, target_page - this_page); + // adrp x17, pages + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adrp(.x17, pages).toU32()); + const narrowed = @truncate(u12, target_addr); + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.add(.x17, .x17, narrowed, false).toU32()); + } + + // stp x16, x17, [sp, #-16]! + mem.writeIntLittle(u32, code[8..12], aarch64.Instruction.stp( + .x16, + .x17, + aarch64.Register.sp, + aarch64.Instruction.LoadStorePairOffset.pre_index(-16), + ).toU32()); + + binder_blk_outer: { + const this_addr = stub_helper.addr + 3 * @sizeOf(u32); + const target_addr = got.addr; + binder_blk: { + const displacement = math.divExact(u64, target_addr - this_addr, 4) catch |_| break :binder_blk; + const literal = math.cast(u18, displacement) catch |_| break :binder_blk; + // ldr x16, label + mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.ldr(.x16, .{ + .literal = literal, + }).toU32()); + // nop + mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.nop().toU32()); + break :binder_blk_outer; + } + binder_blk: { + const new_this_addr = this_addr + @sizeOf(u32); + const displacement = math.divExact(u64, target_addr - new_this_addr, 4) catch |_| break :binder_blk; + const literal = math.cast(u18, displacement) catch |_| break :binder_blk; + // nop + mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.nop().toU32()); + // ldr x16, label + mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.ldr(.x16, .{ + .literal = literal, + }).toU32()); + break :binder_blk_outer; + } + // Jump is too big, replace ldr with adrp and ldr(register). + const this_page = @intCast(i32, this_addr >> 12); + const target_page = @intCast(i32, target_addr >> 12); + const pages = @intCast(i21, target_page - this_page); + // adrp x16, pages + mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.adrp(.x16, pages).toU32()); + const narrowed = @truncate(u12, target_addr); + const offset = try math.divExact(u12, narrowed, 8); + // ldr x16, x16, offset + mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.ldr(.x16, .{ + .register = .{ + .rn = .x16, + .offset = aarch64.Instruction.LoadStoreOffset.imm(offset), + }, + }).toU32()); + } + + // br x16 + mem.writeIntLittle(u32, code[20..24], aarch64.Instruction.br(.x16).toU32()); + try self.base.file.?.pwriteAll(&code, stub_helper.offset); + self.stub_helper_stubs_start_off = stub_helper.offset + code.len; + }, + else => unreachable, + } +} + fn writeStub(self: *MachO, index: u32) !void { const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; const stubs = text_segment.sections.items[self.stubs_section_index.?]; @@ -2527,9 +2576,12 @@ fn writeStub(self: *MachO, index: u32) !void { const stub_off = stubs.offset + index * stubs.reserved2; const stub_addr = stubs.addr + index * stubs.reserved2; const la_ptr_addr = la_symbol_ptr.addr + index * @sizeOf(u64); + log.debug("writing stub at 0x{x}", .{stub_off}); + var code = try self.base.allocator.alloc(u8, stubs.reserved2); defer self.base.allocator.free(code); + switch (self.base.options.target.cpu.arch) { .x86_64 => { assert(la_ptr_addr >= stub_addr + stubs.reserved2); @@ -2541,12 +2593,50 @@ fn writeStub(self: *MachO, index: u32) !void { }, .aarch64 => { assert(la_ptr_addr >= stub_addr); - const displacement = try math.divExact(u64, la_ptr_addr - stub_addr, 4); - const literal = try math.cast(u19, displacement); - mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.x16, .{ - .literal = literal, - }).toU32()); - mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.br(.x16).toU32()); + outer: { + const this_addr = stub_addr; + const target_addr = la_ptr_addr; + inner: { + const displacement = math.divExact(u64, target_addr - this_addr, 4) catch |_| break :inner; + const literal = math.cast(u18, displacement) catch |_| break :inner; + // ldr x16, literal + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.x16, .{ + .literal = literal, + }).toU32()); + // nop + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.nop().toU32()); + break :outer; + } + inner: { + const new_this_addr = this_addr + @sizeOf(u32); + const displacement = math.divExact(u64, target_addr - new_this_addr, 4) catch |_| break :inner; + const literal = math.cast(u18, displacement) catch |_| break :inner; + // nop + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.nop().toU32()); + // ldr x16, literal + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ldr(.x16, .{ + .literal = literal, + }).toU32()); + break :outer; + } + // Use adrp followed by ldr(register). + const this_page = @intCast(i32, this_addr >> 12); + const target_page = @intCast(i32, target_addr >> 12); + const pages = @intCast(i21, target_page - this_page); + // adrp x16, pages + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adrp(.x16, pages).toU32()); + const narrowed = @truncate(u12, target_addr); + const offset = try math.divExact(u12, narrowed, 8); + // ldr x16, x16, offset + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ldr(.x16, .{ + .register = .{ + .rn = .x16, + .offset = aarch64.Instruction.LoadStoreOffset.imm(offset), + }, + }).toU32()); + } + // br x16 + mem.writeIntLittle(u32, code[8..12], aarch64.Instruction.br(.x16).toU32()); }, else => unreachable, } @@ -2563,8 +2653,10 @@ fn writeStubInStubHelper(self: *MachO, index: u32) !void { else => unreachable, }; const stub_off = self.stub_helper_stubs_start_off.? + index * stub_size; + var code = try self.base.allocator.alloc(u8, stub_size); defer self.base.allocator.free(code); + switch (self.base.options.target.cpu.arch) { .x86_64 => { const displacement = try math.cast( @@ -2579,12 +2671,19 @@ fn writeStubInStubHelper(self: *MachO, index: u32) !void { mem.writeIntLittle(u32, code[6..][0..4], @bitCast(u32, displacement)); }, .aarch64 => { - const displacement = try math.cast(i28, @intCast(i64, stub_helper.offset) - @intCast(i64, stub_off) - 4); + const literal = blk: { + const div_res = try math.divExact(u64, stub_size - @sizeOf(u32), 4); + break :blk try math.cast(u18, div_res); + }; + // ldr w16, literal mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.w16, .{ - .literal = @divExact(stub_size - @sizeOf(u32), 4), + .literal = literal, }).toU32()); + const displacement = try math.cast(i28, @intCast(i64, stub_helper.offset) - @intCast(i64, stub_off) - 4); + // b disp mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.b(displacement).toU32()); - mem.writeIntLittle(u32, code[8..12], 0x0); // Just a placeholder populated in `populateLazyBindOffsetsInStubHelper`. + // Just a placeholder populated in `populateLazyBindOffsetsInStubHelper`. + mem.writeIntLittle(u32, code[8..12], 0x0); }, else => unreachable, } @@ -2593,9 +2692,9 @@ fn writeStubInStubHelper(self: *MachO, index: u32) !void { fn relocateSymbolTable(self: *MachO) !void { const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - const nlocals = self.local_symbols.items.len; - const nglobals = self.global_symbols.items.len; - const nundefs = self.extern_lazy_symbols.items().len + self.extern_nonlazy_symbols.items().len; + const nlocals = self.locals.items.len; + const nglobals = self.globals.items.len; + const nundefs = self.lazy_imports.items().len + self.nonlazy_imports.items().len; const nsyms = nlocals + nglobals + nundefs; if (symtab.nsyms < nsyms) { @@ -2630,7 +2729,7 @@ fn writeLocalSymbol(self: *MachO, index: usize) !void { const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; const off = symtab.symoff + @sizeOf(macho.nlist_64) * index; log.debug("writing local symbol {} at 0x{x}", .{ index, off }); - try self.base.file.?.pwriteAll(mem.asBytes(&self.local_symbols.items[index]), off); + try self.base.file.?.pwriteAll(mem.asBytes(&self.locals.items[index]), off); } fn writeAllGlobalAndUndefSymbols(self: *MachO) !void { @@ -2639,18 +2738,18 @@ fn writeAllGlobalAndUndefSymbols(self: *MachO) !void { try self.relocateSymbolTable(); const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - const nlocals = self.local_symbols.items.len; - const nglobals = self.global_symbols.items.len; + const nlocals = self.locals.items.len; + const nglobals = self.globals.items.len; - const nundefs = self.extern_lazy_symbols.items().len + self.extern_nonlazy_symbols.items().len; + const nundefs = self.lazy_imports.items().len + self.nonlazy_imports.items().len; var undefs = std.ArrayList(macho.nlist_64).init(self.base.allocator); defer undefs.deinit(); try undefs.ensureCapacity(nundefs); - for (self.extern_lazy_symbols.items()) |entry| { - undefs.appendAssumeCapacity(entry.value.inner); + for (self.lazy_imports.items()) |entry| { + undefs.appendAssumeCapacity(entry.value.symbol); } - for (self.extern_nonlazy_symbols.items()) |entry| { - undefs.appendAssumeCapacity(entry.value.inner); + for (self.nonlazy_imports.items()) |entry| { + undefs.appendAssumeCapacity(entry.value.symbol); } const locals_off = symtab.symoff; @@ -2659,7 +2758,7 @@ fn writeAllGlobalAndUndefSymbols(self: *MachO) !void { const globals_off = locals_off + locals_size; const globals_size = nglobals * @sizeOf(macho.nlist_64); log.debug("writing global symbols from 0x{x} to 0x{x}", .{ globals_off, globals_size + globals_off }); - try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.global_symbols.items), globals_off); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.globals.items), globals_off); const undefs_off = globals_off + globals_size; const undefs_size = nundefs * @sizeOf(macho.nlist_64); @@ -2685,15 +2784,15 @@ fn writeIndirectSymbolTable(self: *MachO) !void { const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; const stubs = &text_segment.sections.items[self.stubs_section_index.?]; const data_const_seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; - const got = &data_const_seg.sections.items[self.data_got_section_index.?]; + const got = &data_const_seg.sections.items[self.got_section_index.?]; const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; const la_symbol_ptr = &data_segment.sections.items[self.la_symbol_ptr_section_index.?]; const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; - const lazy = self.extern_lazy_symbols.items(); - const nonlazy = self.extern_nonlazy_symbols.items(); + const lazy = self.lazy_imports.items(); + const got_entries = self.offset_table.items; const allocated_size = self.allocatedSizeLinkedit(dysymtab.indirectsymoff); - const nindirectsyms = @intCast(u32, lazy.len * 2 + nonlazy.len); + const nindirectsyms = @intCast(u32, lazy.len * 2 + got_entries.len); const needed_size = @intCast(u32, nindirectsyms * @sizeOf(u32)); if (needed_size > allocated_size) { @@ -2712,20 +2811,27 @@ fn writeIndirectSymbolTable(self: *MachO) !void { var writer = stream.writer(); stubs.reserved1 = 0; - for (self.extern_lazy_symbols.items()) |_, i| { + for (lazy) |_, i| { const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); try writer.writeIntLittle(u32, symtab_idx); } const base_id = @intCast(u32, lazy.len); got.reserved1 = base_id; - for (self.extern_nonlazy_symbols.items()) |_, i| { - const symtab_idx = @intCast(u32, dysymtab.iundefsym + i + base_id); - try writer.writeIntLittle(u32, symtab_idx); + for (got_entries) |entry| { + switch (entry.kind) { + .Local => { + try writer.writeIntLittle(u32, macho.INDIRECT_SYMBOL_LOCAL); + }, + .Extern => { + const symtab_idx = @intCast(u32, dysymtab.iundefsym + entry.index + base_id); + try writer.writeIntLittle(u32, symtab_idx); + }, + } } - la_symbol_ptr.reserved1 = got.reserved1 + @intCast(u32, nonlazy.len); - for (self.extern_lazy_symbols.items()) |_, i| { + la_symbol_ptr.reserved1 = got.reserved1 + @intCast(u32, got_entries.len); + for (lazy) |_, i| { const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); try writer.writeIntLittle(u32, symtab_idx); } @@ -2791,7 +2897,7 @@ fn writeCodeSignature(self: *MachO) !void { fn writeExportTrie(self: *MachO) !void { if (!self.export_info_dirty) return; - if (self.global_symbols.items.len == 0) return; + if (self.globals.items.len == 0) return; const tracy = trace(@src()); defer tracy.end(); @@ -2800,7 +2906,7 @@ fn writeExportTrie(self: *MachO) !void { defer trie.deinit(); const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; - for (self.global_symbols.items) |symbol| { + for (self.globals.items) |symbol| { // TODO figure out if we should put all global symbols into the export trie const name = self.getString(symbol.n_strx); assert(symbol.n_value >= text_segment.inner.vmaddr); @@ -2842,14 +2948,48 @@ fn writeRebaseInfoTable(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const size = try rebaseInfoSize(self.extern_lazy_symbols.items()); + var pointers = std.ArrayList(bind.Pointer).init(self.base.allocator); + defer pointers.deinit(); + + if (self.got_section_index) |idx| { + const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const sect = seg.sections.items[idx]; + const base_offset = sect.addr - seg.inner.vmaddr; + const segment_id = self.data_const_segment_cmd_index.?; + + for (self.offset_table.items) |entry| { + if (entry.kind == .Extern) continue; + try pointers.append(.{ + .offset = base_offset + entry.index * @sizeOf(u64), + .segment_id = segment_id, + }); + } + } + + if (self.la_symbol_ptr_section_index) |idx| { + try pointers.ensureCapacity(pointers.items.len + self.lazy_imports.items().len); + const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const sect = seg.sections.items[idx]; + const base_offset = sect.addr - seg.inner.vmaddr; + const segment_id = self.data_segment_cmd_index.?; + + for (self.lazy_imports.items()) |entry| { + pointers.appendAssumeCapacity(.{ + .offset = base_offset + entry.value.index * @sizeOf(u64), + .segment_id = segment_id, + }); + } + } + + std.sort.sort(bind.Pointer, pointers.items, {}, bind.pointerCmp); + + const size = try bind.rebaseInfoSize(pointers.items); var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); defer self.base.allocator.free(buffer); var stream = std.io.fixedBufferStream(buffer); - try writeRebaseInfo(self.extern_lazy_symbols.items(), stream.writer()); + try bind.writeRebaseInfo(pointers.items, 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)); @@ -2874,14 +3014,34 @@ fn writeBindingInfoTable(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const size = try bindInfoSize(self.extern_nonlazy_symbols.items()); + var pointers = std.ArrayList(bind.Pointer).init(self.base.allocator); + defer pointers.deinit(); + + if (self.got_section_index) |idx| { + const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const sect = seg.sections.items[idx]; + const base_offset = sect.addr - seg.inner.vmaddr; + const segment_id = @intCast(u16, self.data_const_segment_cmd_index.?); + + for (self.offset_table.items) |entry| { + if (entry.kind == .Local) continue; + const import = self.nonlazy_imports.items()[entry.symbol]; + try pointers.append(.{ + .offset = base_offset + entry.index * @sizeOf(u64), + .segment_id = segment_id, + .dylib_ordinal = import.value.dylib_ordinal, + .name = import.key, + }); + } + } + + const size = try bind.bindInfoSize(pointers.items); var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); defer self.base.allocator.free(buffer); var stream = std.io.fixedBufferStream(buffer); - try writeBindInfo(self.extern_nonlazy_symbols.items(), stream.writer()); + try bind.writeBindInfo(pointers.items, 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.bind_off); const needed_size = mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64)); @@ -2903,14 +3063,36 @@ fn writeBindingInfoTable(self: *MachO) !void { fn writeLazyBindingInfoTable(self: *MachO) !void { if (!self.lazy_binding_info_dirty) return; - const size = try lazyBindInfoSize(self.extern_lazy_symbols.items()); + const tracy = trace(@src()); + defer tracy.end(); + + var pointers = std.ArrayList(bind.Pointer).init(self.base.allocator); + defer pointers.deinit(); + + if (self.la_symbol_ptr_section_index) |idx| { + try pointers.ensureCapacity(self.lazy_imports.items().len); + const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const sect = seg.sections.items[idx]; + const base_offset = sect.addr - seg.inner.vmaddr; + const segment_id = @intCast(u16, self.data_segment_cmd_index.?); + + for (self.lazy_imports.items()) |entry| { + pointers.appendAssumeCapacity(.{ + .offset = base_offset + entry.value.index * @sizeOf(u64), + .segment_id = segment_id, + .dylib_ordinal = entry.value.dylib_ordinal, + .name = entry.key, + }); + } + } + + const size = try bind.lazyBindInfoSize(pointers.items); var buffer = try self.base.allocator.alloc(u8, @intCast(usize, size)); defer self.base.allocator.free(buffer); var stream = std.io.fixedBufferStream(buffer); - try writeLazyBindInfo(self.extern_lazy_symbols.items(), stream.writer()); + try bind.writeLazyBindInfo(pointers.items, 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.lazy_bind_off); const needed_size = mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64)); @@ -2931,7 +3113,7 @@ fn writeLazyBindingInfoTable(self: *MachO) !void { } fn populateLazyBindOffsetsInStubHelper(self: *MachO, buffer: []const u8) !void { - if (self.extern_lazy_symbols.items().len == 0) return; + if (self.lazy_imports.items().len == 0) return; var stream = std.io.fixedBufferStream(buffer); var reader = stream.reader(); @@ -2977,7 +3159,7 @@ fn populateLazyBindOffsetsInStubHelper(self: *MachO, buffer: []const u8) !void { else => {}, } } - assert(self.extern_lazy_symbols.items().len <= offsets.items.len); + assert(self.lazy_imports.items().len <= offsets.items.len); const stub_size: u4 = switch (self.base.options.target.cpu.arch) { .x86_64 => 10, @@ -2990,7 +3172,7 @@ fn populateLazyBindOffsetsInStubHelper(self: *MachO, buffer: []const u8) !void { else => unreachable, }; var buf: [@sizeOf(u32)]u8 = undefined; - for (self.extern_lazy_symbols.items()) |_, i| { + for (self.lazy_imports.items()) |_, i| { const placeholder_off = self.stub_helper_stubs_start_off.? + i * stub_size + off; mem.writeIntLittle(u32, &buf, offsets.items[i]); try self.base.file.?.pwriteAll(&buf, placeholder_off); @@ -3104,177 +3286,6 @@ fn writeHeader(self: *MachO) !void { self.header_dirty = false; } -/// Parse MachO contents from existing binary file. -fn parseFromFile(self: *MachO, file: fs.File) !void { - self.base.file = file; - var reader = file.reader(); - const header = try reader.readStruct(macho.mach_header_64); - try self.load_commands.ensureCapacity(self.base.allocator, header.ncmds); - var i: u16 = 0; - while (i < header.ncmds) : (i += 1) { - const cmd = try LoadCommand.read(self.base.allocator, reader); - switch (cmd.cmd()) { - macho.LC_SEGMENT_64 => { - const x = cmd.Segment; - if (parseAndCmpName(&x.inner.segname, "__PAGEZERO")) { - self.pagezero_segment_cmd_index = i; - } else if (parseAndCmpName(&x.inner.segname, "__LINKEDIT")) { - self.linkedit_segment_cmd_index = i; - } else if (parseAndCmpName(&x.inner.segname, "__TEXT")) { - self.text_segment_cmd_index = i; - for (x.sections.items) |sect, j| { - if (parseAndCmpName(§.sectname, "__text")) { - self.text_section_index = @intCast(u16, j); - } - } - } else if (parseAndCmpName(&x.inner.segname, "__DATA")) { - self.data_segment_cmd_index = i; - } else if (parseAndCmpName(&x.inner.segname, "__DATA_CONST")) { - self.data_const_segment_cmd_index = i; - } - }, - macho.LC_DYLD_INFO_ONLY => { - self.dyld_info_cmd_index = i; - }, - macho.LC_SYMTAB => { - self.symtab_cmd_index = i; - }, - macho.LC_DYSYMTAB => { - self.dysymtab_cmd_index = i; - }, - macho.LC_LOAD_DYLINKER => { - self.dylinker_cmd_index = i; - }, - macho.LC_VERSION_MIN_MACOSX, macho.LC_VERSION_MIN_IPHONEOS, macho.LC_VERSION_MIN_WATCHOS, macho.LC_VERSION_MIN_TVOS => { - self.version_min_cmd_index = i; - }, - macho.LC_SOURCE_VERSION => { - self.source_version_cmd_index = i; - }, - macho.LC_UUID => { - self.uuid_cmd_index = i; - }, - macho.LC_MAIN => { - self.main_cmd_index = i; - }, - macho.LC_LOAD_DYLIB => { - const x = cmd.Dylib; - if (parseAndCmpName(x.data, mem.spanZ(LIB_SYSTEM_PATH))) { - self.libsystem_cmd_index = i; - } - }, - macho.LC_FUNCTION_STARTS => { - self.function_starts_cmd_index = i; - }, - macho.LC_DATA_IN_CODE => { - self.data_in_code_cmd_index = i; - }, - macho.LC_CODE_SIGNATURE => { - self.code_signature_cmd_index = i; - }, - else => { - log.warn("Unknown load command detected: 0x{x}.", .{cmd.cmd()}); - }, - } - self.load_commands.appendAssumeCapacity(cmd); - } - self.header = header; -} - -fn parseAndCmpName(name: []const u8, needle: []const u8) bool { - const len = mem.indexOfScalar(u8, name, @as(u8, 0)) orelse name.len; - return mem.eql(u8, name[0..len], needle); -} - -fn parseSymbolTable(self: *MachO) !void { - const symtab = self.load_commands.items[self.symtab_cmd_index.?].Symtab; - const dysymtab = self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; - - var buffer = try self.base.allocator.alloc(macho.nlist_64, symtab.nsyms); - defer self.base.allocator.free(buffer); - const nread = try self.base.file.?.preadAll(@ptrCast([*]u8, buffer)[0 .. symtab.nsyms * @sizeOf(macho.nlist_64)], symtab.symoff); - assert(@divExact(nread, @sizeOf(macho.nlist_64)) == buffer.len); - - try self.local_symbols.ensureCapacity(self.base.allocator, dysymtab.nlocalsym); - try self.global_symbols.ensureCapacity(self.base.allocator, dysymtab.nextdefsym); - try self.undef_symbols.ensureCapacity(self.base.allocator, dysymtab.nundefsym); - - self.local_symbols.appendSliceAssumeCapacity(buffer[dysymtab.ilocalsym .. dysymtab.ilocalsym + dysymtab.nlocalsym]); - self.global_symbols.appendSliceAssumeCapacity(buffer[dysymtab.iextdefsym .. dysymtab.iextdefsym + dysymtab.nextdefsym]); - self.undef_symbols.appendSliceAssumeCapacity(buffer[dysymtab.iundefsym .. dysymtab.iundefsym + dysymtab.nundefsym]); -} - -fn parseStringTable(self: *MachO) !void { - const symtab = self.load_commands.items[self.symtab_cmd_index.?].Symtab; - - var buffer = try self.base.allocator.alloc(u8, symtab.strsize); - defer self.base.allocator.free(buffer); - const nread = try self.base.file.?.preadAll(buffer, symtab.stroff); - assert(nread == buffer.len); - - try self.string_table.ensureCapacity(self.base.allocator, symtab.strsize); - self.string_table.appendSliceAssumeCapacity(buffer); -} - -fn fixupBindInfo(self: *MachO, dylib_ordinal: u32) !void { - const dyld_info = self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; - var buffer = try self.base.allocator.alloc(u8, dyld_info.bind_size); - defer self.base.allocator.free(buffer); - const nread = try self.base.file.?.preadAll(buffer, dyld_info.bind_off); - assert(nread == buffer.len); - try self.fixupInfoCommon(buffer, dylib_ordinal); - try self.base.file.?.pwriteAll(buffer, dyld_info.bind_off); -} - -fn fixupLazyBindInfo(self: *MachO, dylib_ordinal: u32) !void { - const dyld_info = self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; - var buffer = try self.base.allocator.alloc(u8, dyld_info.lazy_bind_size); - defer self.base.allocator.free(buffer); - const nread = try self.base.file.?.preadAll(buffer, dyld_info.lazy_bind_off); - assert(nread == buffer.len); - try self.fixupInfoCommon(buffer, dylib_ordinal); - try self.base.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off); -} - -fn fixupInfoCommon(self: *MachO, buffer: []u8, dylib_ordinal: u32) !void { - var stream = std.io.fixedBufferStream(buffer); - var reader = stream.reader(); - - while (true) { - const inst = reader.readByte() catch |err| switch (err) { - error.EndOfStream => break, - else => return err, - }; - const imm: u8 = inst & macho.BIND_IMMEDIATE_MASK; - const opcode: u8 = inst & macho.BIND_OPCODE_MASK; - - switch (opcode) { - macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => { - var next = try reader.readByte(); - while (next != @as(u8, 0)) { - next = try reader.readByte(); - } - }, - macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => { - _ = try std.leb.readULEB128(u64, reader); - }, - macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM, macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM => { - // Perform the fixup. - try stream.seekBy(-1); - var writer = stream.writer(); - try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, dylib_ordinal)); - }, - macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB => { - _ = try std.leb.readULEB128(u64, reader); - }, - macho.BIND_OPCODE_SET_ADDEND_SLEB => { - _ = try std.leb.readILEB128(i64, reader); - }, - else => {}, - } - } -} - pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { // TODO https://github.com/ziglang/zig/issues/1284 return std.math.add(@TypeOf(actual_size), actual_size, actual_size / ideal_factor) catch diff --git a/src/link/MachO/Archive.zig b/src/link/MachO/Archive.zig new file mode 100644 index 0000000000..bb6fb68231 --- /dev/null +++ b/src/link/MachO/Archive.zig @@ -0,0 +1,278 @@ +const Archive = @This(); + +const std = @import("std"); +const assert = std.debug.assert; +const fs = std.fs; +const log = std.log.scoped(.archive); +const macho = std.macho; +const mem = std.mem; + +const Allocator = mem.Allocator; +const Object = @import("Object.zig"); +const parseName = @import("Zld.zig").parseName; + +usingnamespace @import("commands.zig"); + +allocator: *Allocator, +file: fs.File, +header: ar_hdr, +name: []u8, + +objects: std.ArrayListUnmanaged(Object) = .{}, + +/// Parsed table of contents. +/// Each symbol name points to a list of all definition +/// sites within the current static archive. +toc: std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(u32)) = .{}, + +// Archive files start with the ARMAG identifying string. Then follows a +// `struct ar_hdr', and as many bytes of member file data as its `ar_size' +// member indicates, for each member file. +/// String that begins an archive file. +const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n"; +/// Size of that string. +const SARMAG: u4 = 8; + +/// String in ar_fmag at the end of each header. +const ARFMAG: *const [2:0]u8 = "`\n"; + +const ar_hdr = extern struct { + /// Member file name, sometimes / terminated. + ar_name: [16]u8, + + /// File date, decimal seconds since Epoch. + ar_date: [12]u8, + + /// User ID, in ASCII format. + ar_uid: [6]u8, + + /// Group ID, in ASCII format. + ar_gid: [6]u8, + + /// File mode, in ASCII octal. + ar_mode: [8]u8, + + /// File size, in ASCII decimal. + ar_size: [10]u8, + + /// Always contains ARFMAG. + ar_fmag: [2]u8, + + const NameOrLength = union(enum) { + Name: []const u8, + Length: u64, + }; + pub fn nameOrLength(self: ar_hdr) !NameOrLength { + const value = getValue(&self.ar_name); + const slash_index = mem.indexOf(u8, value, "/") orelse return error.MalformedArchive; + const len = value.len; + if (slash_index == len - 1) { + // Name stored directly + return NameOrLength{ .Name = value }; + } else { + // Name follows the header directly and its length is encoded in + // the name field. + const length = try std.fmt.parseInt(u64, value[slash_index + 1 ..], 10); + return NameOrLength{ .Length = length }; + } + } + + pub fn size(self: ar_hdr) !u64 { + const value = getValue(&self.ar_size); + return std.fmt.parseInt(u64, value, 10); + } + + fn getValue(raw: []const u8) []const u8 { + return mem.trimRight(u8, raw, &[_]u8{@as(u8, 0x20)}); + } +}; + +pub fn deinit(self: *Archive) void { + self.allocator.free(self.name); + for (self.objects.items) |*object| { + object.deinit(); + } + self.objects.deinit(self.allocator); + for (self.toc.items()) |*entry| { + self.allocator.free(entry.key); + entry.value.deinit(self.allocator); + } + self.toc.deinit(self.allocator); + self.file.close(); +} + +/// Caller owns the returned Archive instance and is responsible for calling +/// `deinit` to free allocated memory. +pub fn initFromFile(allocator: *Allocator, arch: std.Target.Cpu.Arch, ar_name: []const u8, file: fs.File) !Archive { + var reader = file.reader(); + var magic = try readMagic(allocator, reader); + defer allocator.free(magic); + + if (!mem.eql(u8, magic, ARMAG)) { + // Reset file cursor. + try file.seekTo(0); + return error.NotArchive; + } + + const header = try reader.readStruct(ar_hdr); + + if (!mem.eql(u8, &header.ar_fmag, ARFMAG)) + return error.MalformedArchive; + + var embedded_name = try getName(allocator, header, reader); + log.debug("parsing archive '{s}' at '{s}'", .{ embedded_name, ar_name }); + defer allocator.free(embedded_name); + + var name = try allocator.dupe(u8, ar_name); + var self = Archive{ + .allocator = allocator, + .file = file, + .header = header, + .name = name, + }; + + var object_offsets = try self.readTableOfContents(reader); + defer self.allocator.free(object_offsets); + + var i: usize = 1; + while (i < object_offsets.len) : (i += 1) { + const offset = object_offsets[i]; + try reader.context.seekTo(offset); + try self.readObject(arch, ar_name, reader); + } + + return self; +} + +fn readTableOfContents(self: *Archive, reader: anytype) ![]u32 { + const symtab_size = try reader.readIntLittle(u32); + var symtab = try self.allocator.alloc(u8, symtab_size); + defer self.allocator.free(symtab); + try reader.readNoEof(symtab); + + const strtab_size = try reader.readIntLittle(u32); + var strtab = try self.allocator.alloc(u8, strtab_size); + defer self.allocator.free(strtab); + try reader.readNoEof(strtab); + + var symtab_stream = std.io.fixedBufferStream(symtab); + var symtab_reader = symtab_stream.reader(); + + var object_offsets = std.ArrayList(u32).init(self.allocator); + try object_offsets.append(0); + var last: usize = 0; + + while (true) { + const n_strx = symtab_reader.readIntLittle(u32) catch |err| switch (err) { + error.EndOfStream => break, + else => |e| return e, + }; + const object_offset = try symtab_reader.readIntLittle(u32); + + const sym_name = mem.spanZ(@ptrCast([*:0]const u8, strtab.ptr + n_strx)); + const owned_name = try self.allocator.dupe(u8, sym_name); + const res = try self.toc.getOrPut(self.allocator, owned_name); + defer if (res.found_existing) self.allocator.free(owned_name); + + if (!res.found_existing) { + res.entry.value = .{}; + } + + try res.entry.value.append(self.allocator, object_offset); + + // TODO This will go once we properly use archive's TOC to pick + // an object which defines a missing symbol rather than pasting in + // all of the objects always. + // Here, we assume that symbols are NOT sorted in any way, and + // they point to objects in sequence. + if (object_offsets.items[last] != object_offset) { + try object_offsets.append(object_offset); + last += 1; + } + } + + return object_offsets.toOwnedSlice(); +} + +fn readObject(self: *Archive, arch: std.Target.Cpu.Arch, ar_name: []const u8, reader: anytype) !void { + const object_header = try reader.readStruct(ar_hdr); + + if (!mem.eql(u8, &object_header.ar_fmag, ARFMAG)) + return error.MalformedArchive; + + var object_name = try getName(self.allocator, object_header, reader); + log.debug("extracting object '{s}' from archive '{s}'", .{ object_name, self.name }); + + const offset = @intCast(u32, try reader.context.getPos()); + const header = try reader.readStruct(macho.mach_header_64); + + const this_arch: std.Target.Cpu.Arch = switch (header.cputype) { + macho.CPU_TYPE_ARM64 => .aarch64, + macho.CPU_TYPE_X86_64 => .x86_64, + else => |value| { + log.err("unsupported cpu architecture 0x{x}", .{value}); + return error.UnsupportedCpuArchitecture; + }, + }; + if (this_arch != arch) { + log.err("mismatched cpu architecture: found {s}, expected {s}", .{ this_arch, arch }); + return error.MismatchedCpuArchitecture; + } + + // TODO Implement std.fs.File.clone() or similar. + var new_file = try fs.cwd().openFile(ar_name, .{}); + var object = Object{ + .allocator = self.allocator, + .name = object_name, + .ar_name = try mem.dupe(self.allocator, u8, ar_name), + .file = new_file, + .header = header, + }; + + try object.readLoadCommands(reader, .{ .offset = offset }); + + if (object.symtab_cmd_index != null) { + try object.readSymtab(); + try object.readStrtab(); + } + + if (object.data_in_code_cmd_index != null) try object.readDataInCode(); + + log.debug("\n\n", .{}); + log.debug("{s} defines symbols", .{object.name}); + for (object.symtab.items) |sym| { + const symname = object.getString(sym.n_strx); + log.debug("'{s}': {}", .{ symname, sym }); + } + + try self.objects.append(self.allocator, object); +} + +fn readMagic(allocator: *Allocator, reader: anytype) ![]u8 { + var magic = std.ArrayList(u8).init(allocator); + try magic.ensureCapacity(SARMAG); + var i: usize = 0; + while (i < SARMAG) : (i += 1) { + const next = try reader.readByte(); + magic.appendAssumeCapacity(next); + } + return magic.toOwnedSlice(); +} + +fn getName(allocator: *Allocator, header: ar_hdr, reader: anytype) ![]u8 { + const name_or_length = try header.nameOrLength(); + var name: []u8 = undefined; + switch (name_or_length) { + .Name => |n| { + name = try allocator.dupe(u8, n); + }, + .Length => |len| { + var n = try allocator.alloc(u8, len); + defer allocator.free(n); + try reader.readNoEof(n); + const actual_len = mem.indexOfScalar(u8, n, @as(u8, 0)) orelse n.len; + name = try allocator.dupe(u8, n[0..actual_len]); + }, + } + return name; +} diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index 042c1a12cf..a81fd00c0a 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -839,8 +839,8 @@ fn findFreeSpaceLinkedit(self: *DebugSymbols, object_size: u64, min_alignment: u fn relocateSymbolTable(self: *DebugSymbols) !void { const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; - const nlocals = self.base.local_symbols.items.len; - const nglobals = self.base.global_symbols.items.len; + const nlocals = self.base.locals.items.len; + const nglobals = self.base.globals.items.len; const nsyms = nlocals + nglobals; if (symtab.nsyms < nsyms) { @@ -875,7 +875,7 @@ pub fn writeLocalSymbol(self: *DebugSymbols, index: usize) !void { const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; const off = symtab.symoff + @sizeOf(macho.nlist_64) * index; log.debug("writing dSym local symbol {} at 0x{x}", .{ index, off }); - try self.file.pwriteAll(mem.asBytes(&self.base.local_symbols.items[index]), off); + try self.file.pwriteAll(mem.asBytes(&self.base.locals.items[index]), off); } fn writeStringTable(self: *DebugSymbols) !void { @@ -1057,7 +1057,7 @@ pub fn commitDeclDebugInfo( var dbg_info_buffer = &debug_buffers.dbg_info_buffer; var dbg_info_type_relocs = &debug_buffers.dbg_info_type_relocs; - const symbol = self.base.local_symbols.items[decl.link.macho.local_sym_index]; + const symbol = self.base.locals.items[decl.link.macho.local_sym_index]; const text_block = &decl.link.macho; // If the Decl is a function, we need to update the __debug_line program. const typed_value = decl.typed_value.most_recent.typed_value; diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig new file mode 100644 index 0000000000..a8293d6410 --- /dev/null +++ b/src/link/MachO/Object.zig @@ -0,0 +1,229 @@ +const Object = @This(); + +const std = @import("std"); +const assert = std.debug.assert; +const fs = std.fs; +const io = std.io; +const log = std.log.scoped(.object); +const macho = std.macho; +const mem = std.mem; + +const Allocator = mem.Allocator; +const parseName = @import("Zld.zig").parseName; + +usingnamespace @import("commands.zig"); + +allocator: *Allocator, +file: fs.File, +name: []u8, +ar_name: ?[]u8 = null, + +header: macho.mach_header_64, + +load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, + +segment_cmd_index: ?u16 = null, +symtab_cmd_index: ?u16 = null, +dysymtab_cmd_index: ?u16 = null, +build_version_cmd_index: ?u16 = null, +data_in_code_cmd_index: ?u16 = null, +text_section_index: ?u16 = null, + +// __DWARF segment sections +dwarf_debug_info_index: ?u16 = null, +dwarf_debug_abbrev_index: ?u16 = null, +dwarf_debug_str_index: ?u16 = null, +dwarf_debug_line_index: ?u16 = null, +dwarf_debug_ranges_index: ?u16 = null, + +symtab: std.ArrayListUnmanaged(macho.nlist_64) = .{}, +strtab: std.ArrayListUnmanaged(u8) = .{}, + +data_in_code_entries: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{}, + +pub fn deinit(self: *Object) void { + for (self.load_commands.items) |*lc| { + lc.deinit(self.allocator); + } + self.load_commands.deinit(self.allocator); + self.symtab.deinit(self.allocator); + self.strtab.deinit(self.allocator); + self.data_in_code_entries.deinit(self.allocator); + self.allocator.free(self.name); + if (self.ar_name) |v| { + self.allocator.free(v); + } + self.file.close(); +} + +/// Caller owns the returned Object instance and is responsible for calling +/// `deinit` to free allocated memory. +pub fn initFromFile(allocator: *Allocator, arch: std.Target.Cpu.Arch, name: []const u8, file: fs.File) !Object { + var reader = file.reader(); + const header = try reader.readStruct(macho.mach_header_64); + + if (header.filetype != macho.MH_OBJECT) { + // Reset file cursor. + try file.seekTo(0); + return error.NotObject; + } + + const this_arch: std.Target.Cpu.Arch = switch (header.cputype) { + macho.CPU_TYPE_ARM64 => .aarch64, + macho.CPU_TYPE_X86_64 => .x86_64, + else => |value| { + log.err("unsupported cpu architecture 0x{x}", .{value}); + return error.UnsupportedCpuArchitecture; + }, + }; + if (this_arch != arch) { + log.err("mismatched cpu architecture: found {s}, expected {s}", .{ this_arch, arch }); + return error.MismatchedCpuArchitecture; + } + + var self = Object{ + .allocator = allocator, + .name = try allocator.dupe(u8, name), + .file = file, + .header = header, + }; + + try self.readLoadCommands(reader, .{}); + + if (self.symtab_cmd_index != null) { + try self.readSymtab(); + try self.readStrtab(); + } + + if (self.data_in_code_cmd_index != null) try self.readDataInCode(); + + log.debug("\n\n", .{}); + log.debug("{s} defines symbols", .{self.name}); + for (self.symtab.items) |sym| { + const symname = self.getString(sym.n_strx); + log.debug("'{s}': {}", .{ symname, sym }); + } + + return self; +} + +pub const ReadOffset = struct { + offset: ?u32 = null, +}; + +pub fn readLoadCommands(self: *Object, reader: anytype, offset: ReadOffset) !void { + const offset_mod = offset.offset orelse 0; + try self.load_commands.ensureCapacity(self.allocator, self.header.ncmds); + + var i: u16 = 0; + while (i < self.header.ncmds) : (i += 1) { + var cmd = try LoadCommand.read(self.allocator, reader); + switch (cmd.cmd()) { + macho.LC_SEGMENT_64 => { + self.segment_cmd_index = i; + var seg = cmd.Segment; + for (seg.sections.items) |*sect, j| { + const index = @intCast(u16, j); + const segname = parseName(§.segname); + const sectname = parseName(§.sectname); + if (mem.eql(u8, segname, "__DWARF")) { + if (mem.eql(u8, sectname, "__debug_info")) { + self.dwarf_debug_info_index = index; + } else if (mem.eql(u8, sectname, "__debug_abbrev")) { + self.dwarf_debug_abbrev_index = index; + } else if (mem.eql(u8, sectname, "__debug_str")) { + self.dwarf_debug_str_index = index; + } else if (mem.eql(u8, sectname, "__debug_line")) { + self.dwarf_debug_line_index = index; + } else if (mem.eql(u8, sectname, "__debug_ranges")) { + self.dwarf_debug_ranges_index = index; + } + } else if (mem.eql(u8, segname, "__TEXT")) { + if (mem.eql(u8, sectname, "__text")) { + self.text_section_index = index; + } + } + + sect.offset += offset_mod; + if (sect.reloff > 0) + sect.reloff += offset_mod; + } + + seg.inner.fileoff += offset_mod; + }, + macho.LC_SYMTAB => { + self.symtab_cmd_index = i; + cmd.Symtab.symoff += offset_mod; + cmd.Symtab.stroff += offset_mod; + }, + macho.LC_DYSYMTAB => { + self.dysymtab_cmd_index = i; + }, + macho.LC_BUILD_VERSION => { + self.build_version_cmd_index = i; + }, + macho.LC_DATA_IN_CODE => { + self.data_in_code_cmd_index = i; + cmd.LinkeditData.dataoff += offset_mod; + }, + else => { + log.debug("Unknown load command detected: 0x{x}.", .{cmd.cmd()}); + }, + } + self.load_commands.appendAssumeCapacity(cmd); + } +} + +pub fn readSymtab(self: *Object) !void { + const symtab_cmd = self.load_commands.items[self.symtab_cmd_index.?].Symtab; + var buffer = try self.allocator.alloc(u8, @sizeOf(macho.nlist_64) * symtab_cmd.nsyms); + defer self.allocator.free(buffer); + _ = try self.file.preadAll(buffer, symtab_cmd.symoff); + try self.symtab.ensureCapacity(self.allocator, symtab_cmd.nsyms); + // TODO this align case should not be needed. + // Probably a bug in stage1. + const slice = @alignCast(@alignOf(macho.nlist_64), mem.bytesAsSlice(macho.nlist_64, buffer)); + self.symtab.appendSliceAssumeCapacity(slice); +} + +pub fn readStrtab(self: *Object) !void { + const symtab_cmd = self.load_commands.items[self.symtab_cmd_index.?].Symtab; + var buffer = try self.allocator.alloc(u8, symtab_cmd.strsize); + defer self.allocator.free(buffer); + _ = try self.file.preadAll(buffer, symtab_cmd.stroff); + try self.strtab.ensureCapacity(self.allocator, symtab_cmd.strsize); + self.strtab.appendSliceAssumeCapacity(buffer); +} + +pub fn getString(self: *const Object, str_off: u32) []const u8 { + assert(str_off < self.strtab.items.len); + return mem.spanZ(@ptrCast([*:0]const u8, self.strtab.items.ptr + str_off)); +} + +pub fn readSection(self: Object, allocator: *Allocator, index: u16) ![]u8 { + const seg = self.load_commands.items[self.segment_cmd_index.?].Segment; + const sect = seg.sections.items[index]; + var buffer = try allocator.alloc(u8, sect.size); + _ = try self.file.preadAll(buffer, sect.offset); + return buffer; +} + +pub fn readDataInCode(self: *Object) !void { + const index = self.data_in_code_cmd_index orelse return; + const data_in_code = self.load_commands.items[index].LinkeditData; + + var buffer = try self.allocator.alloc(u8, data_in_code.datasize); + defer self.allocator.free(buffer); + + _ = try self.file.preadAll(buffer, data_in_code.dataoff); + + var stream = io.fixedBufferStream(buffer); + var reader = stream.reader(); + while (true) { + const dice = reader.readStruct(macho.data_in_code_entry) catch |err| switch (err) { + error.EndOfStream => break, + else => |e| return e, + }; + try self.data_in_code_entries.append(self.allocator, dice); + } +} diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig new file mode 100644 index 0000000000..9340474d19 --- /dev/null +++ b/src/link/MachO/Zld.zig @@ -0,0 +1,3294 @@ +const Zld = @This(); + +const std = @import("std"); +const assert = std.debug.assert; +const dwarf = std.dwarf; +const leb = std.leb; +const mem = std.mem; +const meta = std.meta; +const fs = std.fs; +const macho = std.macho; +const math = std.math; +const log = std.log.scoped(.zld); +const aarch64 = @import("../../codegen/aarch64.zig"); + +const Allocator = mem.Allocator; +const CodeSignature = @import("CodeSignature.zig"); +const Archive = @import("Archive.zig"); +const Object = @import("Object.zig"); +const Trie = @import("Trie.zig"); + +usingnamespace @import("commands.zig"); +usingnamespace @import("bind.zig"); + +allocator: *Allocator, + +arch: ?std.Target.Cpu.Arch = null, +page_size: ?u16 = null, +file: ?fs.File = null, +out_path: ?[]const u8 = null, + +// TODO Eventually, we will want to keep track of the archives themselves to be able to exclude objects +// contained within from landing in the final artifact. For now however, since we don't optimise the binary +// at all, we just move all objects from the archives into the final artifact. +objects: std.ArrayListUnmanaged(Object) = .{}, + +load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, + +pagezero_segment_cmd_index: ?u16 = null, +text_segment_cmd_index: ?u16 = null, +data_const_segment_cmd_index: ?u16 = null, +data_segment_cmd_index: ?u16 = null, +linkedit_segment_cmd_index: ?u16 = null, +dyld_info_cmd_index: ?u16 = null, +symtab_cmd_index: ?u16 = null, +dysymtab_cmd_index: ?u16 = null, +dylinker_cmd_index: ?u16 = null, +libsystem_cmd_index: ?u16 = null, +data_in_code_cmd_index: ?u16 = null, +function_starts_cmd_index: ?u16 = null, +main_cmd_index: ?u16 = null, +version_min_cmd_index: ?u16 = null, +source_version_cmd_index: ?u16 = null, +uuid_cmd_index: ?u16 = null, +code_signature_cmd_index: ?u16 = null, + +// __TEXT segment sections +text_section_index: ?u16 = null, +stubs_section_index: ?u16 = null, +stub_helper_section_index: ?u16 = null, +text_const_section_index: ?u16 = null, +cstring_section_index: ?u16 = null, + +// __DATA_CONST segment sections +got_section_index: ?u16 = null, +mod_init_func_section_index: ?u16 = null, +mod_term_func_section_index: ?u16 = null, +data_const_section_index: ?u16 = null, + +// __DATA segment sections +tlv_section_index: ?u16 = null, +tlv_data_section_index: ?u16 = null, +tlv_bss_section_index: ?u16 = null, +la_symbol_ptr_section_index: ?u16 = null, +data_section_index: ?u16 = null, +bss_section_index: ?u16 = null, + +locals: std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(Symbol)) = .{}, +exports: std.StringArrayHashMapUnmanaged(macho.nlist_64) = .{}, +nonlazy_imports: std.StringArrayHashMapUnmanaged(Import) = .{}, +lazy_imports: std.StringArrayHashMapUnmanaged(Import) = .{}, +tlv_bootstrap: ?Import = null, +threadlocal_offsets: std.ArrayListUnmanaged(u64) = .{}, +local_rebases: std.ArrayListUnmanaged(Pointer) = .{}, +nonlazy_pointers: std.StringArrayHashMapUnmanaged(GotEntry) = .{}, + +strtab: std.ArrayListUnmanaged(u8) = .{}, + +stub_helper_stubs_start_off: ?u64 = null, + +mappings: std.AutoHashMapUnmanaged(MappingKey, SectionMapping) = .{}, +unhandled_sections: std.AutoHashMapUnmanaged(MappingKey, u0) = .{}, + +// TODO this will require scanning the relocations at least one to work out +// the exact amount of local GOT indirections. For the time being, set some +// default value. +const max_local_got_indirections: u16 = 1000; + +const GotEntry = struct { + index: u32, + target_addr: u64, +}; + +const MappingKey = struct { + object_id: u16, + source_sect_id: u16, +}; + +const SectionMapping = struct { + source_sect_id: u16, + target_seg_id: u16, + target_sect_id: u16, + offset: u32, +}; + +const Symbol = struct { + inner: macho.nlist_64, + tt: Type, + object_id: u16, + + const Type = enum { + Local, + WeakGlobal, + Global, + }; +}; + +const DebugInfo = struct { + inner: dwarf.DwarfInfo, + debug_info: []u8, + debug_abbrev: []u8, + debug_str: []u8, + debug_line: []u8, + debug_ranges: []u8, + + pub fn parseFromObject(allocator: *Allocator, object: Object) !?DebugInfo { + var debug_info = blk: { + const index = object.dwarf_debug_info_index orelse return null; + break :blk try object.readSection(allocator, index); + }; + var debug_abbrev = blk: { + const index = object.dwarf_debug_abbrev_index orelse return null; + break :blk try object.readSection(allocator, index); + }; + var debug_str = blk: { + const index = object.dwarf_debug_str_index orelse return null; + break :blk try object.readSection(allocator, index); + }; + var debug_line = blk: { + const index = object.dwarf_debug_line_index orelse return null; + break :blk try object.readSection(allocator, index); + }; + var debug_ranges = blk: { + if (object.dwarf_debug_ranges_index) |ind| { + break :blk try object.readSection(allocator, ind); + } + break :blk try allocator.alloc(u8, 0); + }; + + var inner: dwarf.DwarfInfo = .{ + .endian = .Little, + .debug_info = debug_info, + .debug_abbrev = debug_abbrev, + .debug_str = debug_str, + .debug_line = debug_line, + .debug_ranges = debug_ranges, + }; + try dwarf.openDwarfDebugInfo(&inner, allocator); + + return DebugInfo{ + .inner = inner, + .debug_info = debug_info, + .debug_abbrev = debug_abbrev, + .debug_str = debug_str, + .debug_line = debug_line, + .debug_ranges = debug_ranges, + }; + } + + pub fn deinit(self: *DebugInfo, allocator: *Allocator) void { + allocator.free(self.debug_info); + allocator.free(self.debug_abbrev); + allocator.free(self.debug_str); + allocator.free(self.debug_line); + allocator.free(self.debug_ranges); + self.inner.abbrev_table_list.deinit(); + self.inner.compile_unit_list.deinit(); + self.inner.func_list.deinit(); + } +}; + +pub const Import = struct { + /// MachO symbol table entry. + symbol: macho.nlist_64, + + /// Id of the dynamic library where the specified entries can be found. + dylib_ordinal: i64, + + /// Index of this import within the import list. + index: u32, +}; + +/// Default path to dyld +/// TODO instead of hardcoding it, we should probably look through some env vars and search paths +/// instead but this will do for now. +const DEFAULT_DYLD_PATH: [*:0]const u8 = "/usr/lib/dyld"; + +/// Default lib search path +/// TODO instead of hardcoding it, we should probably look through some env vars and search paths +/// instead but this will do for now. +const DEFAULT_LIB_SEARCH_PATH: []const u8 = "/usr/lib"; + +const LIB_SYSTEM_NAME: [*:0]const u8 = "System"; +/// TODO we should search for libSystem and fail if it doesn't exist, instead of hardcoding it +const LIB_SYSTEM_PATH: [*:0]const u8 = DEFAULT_LIB_SEARCH_PATH ++ "/libSystem.B.dylib"; + +pub fn init(allocator: *Allocator) Zld { + return .{ .allocator = allocator }; +} + +pub fn deinit(self: *Zld) void { + self.threadlocal_offsets.deinit(self.allocator); + self.strtab.deinit(self.allocator); + self.local_rebases.deinit(self.allocator); + for (self.lazy_imports.items()) |*entry| { + self.allocator.free(entry.key); + } + self.lazy_imports.deinit(self.allocator); + for (self.nonlazy_imports.items()) |*entry| { + self.allocator.free(entry.key); + } + self.nonlazy_imports.deinit(self.allocator); + for (self.nonlazy_pointers.items()) |*entry| { + self.allocator.free(entry.key); + } + self.nonlazy_pointers.deinit(self.allocator); + for (self.exports.items()) |*entry| { + self.allocator.free(entry.key); + } + self.exports.deinit(self.allocator); + for (self.locals.items()) |*entry| { + self.allocator.free(entry.key); + entry.value.deinit(self.allocator); + } + self.locals.deinit(self.allocator); + for (self.objects.items) |*object| { + object.deinit(); + } + self.objects.deinit(self.allocator); + for (self.load_commands.items) |*lc| { + lc.deinit(self.allocator); + } + self.load_commands.deinit(self.allocator); + self.mappings.deinit(self.allocator); + self.unhandled_sections.deinit(self.allocator); + if (self.file) |*f| f.close(); +} + +pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8) !void { + if (files.len == 0) return error.NoInputFiles; + if (out_path.len == 0) return error.EmptyOutputPath; + + if (self.arch == null) { + // Try inferring the arch from the object files. + self.arch = blk: { + const file = try fs.cwd().openFile(files[0], .{}); + defer file.close(); + var reader = file.reader(); + const header = try reader.readStruct(macho.mach_header_64); + const arch: std.Target.Cpu.Arch = switch (header.cputype) { + macho.CPU_TYPE_X86_64 => .x86_64, + macho.CPU_TYPE_ARM64 => .aarch64, + else => |value| { + log.err("unsupported cpu architecture 0x{x}", .{value}); + return error.UnsupportedCpuArchitecture; + }, + }; + break :blk arch; + }; + } + + self.page_size = switch (self.arch.?) { + .aarch64 => 0x4000, + .x86_64 => 0x1000, + else => unreachable, + }; + self.out_path = out_path; + self.file = try fs.cwd().createFile(out_path, .{ + .truncate = true, + .read = true, + .mode = if (std.Target.current.os.tag == .windows) 0 else 0o777, + }); + + try self.populateMetadata(); + try self.parseInputFiles(files); + try self.sortSections(); + try self.resolveImports(); + try self.allocateTextSegment(); + try self.allocateDataConstSegment(); + try self.allocateDataSegment(); + self.allocateLinkeditSegment(); + try self.writeStubHelperCommon(); + try self.resolveSymbols(); + try self.doRelocs(); + try self.flush(); +} + +fn parseInputFiles(self: *Zld, files: []const []const u8) !void { + for (files) |file_name| { + const file = try fs.cwd().openFile(file_name, .{}); + + try_object: { + var object = Object.initFromFile(self.allocator, self.arch.?, file_name, file) catch |err| switch (err) { + error.NotObject => break :try_object, + else => |e| return e, + }; + const index = @intCast(u16, self.objects.items.len); + try self.objects.append(self.allocator, object); + try self.updateMetadata(index); + continue; + } + + try_archive: { + var archive = Archive.initFromFile(self.allocator, self.arch.?, file_name, file) catch |err| switch (err) { + error.NotArchive => break :try_archive, + else => |e| return e, + }; + defer archive.deinit(); + while (archive.objects.popOrNull()) |object| { + const index = @intCast(u16, self.objects.items.len); + try self.objects.append(self.allocator, object); + try self.updateMetadata(index); + } + continue; + } + + log.err("unexpected file type: expected object '.o' or archive '.a': {s}", .{file_name}); + return error.UnexpectedInputFileType; + } +} + +fn mapAndUpdateSections( + self: *Zld, + object_id: u16, + source_sect_id: u16, + target_seg_id: u16, + target_sect_id: u16, +) !void { + const object = self.objects.items[object_id]; + const source_seg = object.load_commands.items[object.segment_cmd_index.?].Segment; + const source_sect = source_seg.sections.items[source_sect_id]; + const target_seg = &self.load_commands.items[target_seg_id].Segment; + const target_sect = &target_seg.sections.items[target_sect_id]; + + const alignment = try math.powi(u32, 2, target_sect.@"align"); + const offset = mem.alignForwardGeneric(u64, target_sect.size, alignment); + const size = mem.alignForwardGeneric(u64, source_sect.size, alignment); + const key = MappingKey{ + .object_id = object_id, + .source_sect_id = source_sect_id, + }; + try self.mappings.putNoClobber(self.allocator, key, .{ + .source_sect_id = source_sect_id, + .target_seg_id = target_seg_id, + .target_sect_id = target_sect_id, + .offset = @intCast(u32, offset), + }); + log.debug("{s}: {s},{s} mapped to {s},{s} from 0x{x} to 0x{x}", .{ + object.name, + parseName(&source_sect.segname), + parseName(&source_sect.sectname), + parseName(&target_sect.segname), + parseName(&target_sect.sectname), + offset, + offset + size, + }); + + target_sect.size = offset + size; +} + +fn updateMetadata(self: *Zld, object_id: u16) !void { + const object = self.objects.items[object_id]; + const object_seg = object.load_commands.items[object.segment_cmd_index.?].Segment; + const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const data_const_seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + + // Create missing metadata + for (object_seg.sections.items) |source_sect, id| { + if (id == object.text_section_index.?) continue; + const segname = parseName(&source_sect.segname); + const sectname = parseName(&source_sect.sectname); + const flags = source_sect.flags; + + switch (flags) { + macho.S_REGULAR, macho.S_4BYTE_LITERALS, macho.S_8BYTE_LITERALS, macho.S_16BYTE_LITERALS => { + if (mem.eql(u8, segname, "__TEXT")) { + if (self.text_const_section_index != null) continue; + + self.text_const_section_index = @intCast(u16, text_seg.sections.items.len); + try text_seg.addSection(self.allocator, .{ + .sectname = makeStaticString("__const"), + .segname = makeStaticString("__TEXT"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 0, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_REGULAR, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + } else if (mem.eql(u8, segname, "__DATA")) { + if (!mem.eql(u8, sectname, "__const")) continue; + if (self.data_const_section_index != null) continue; + + self.data_const_section_index = @intCast(u16, data_const_seg.sections.items.len); + try data_const_seg.addSection(self.allocator, .{ + .sectname = makeStaticString("__const"), + .segname = makeStaticString("__DATA_CONST"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 0, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_REGULAR, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + } + }, + macho.S_CSTRING_LITERALS => { + if (!mem.eql(u8, segname, "__TEXT")) continue; + if (self.cstring_section_index != null) continue; + + self.cstring_section_index = @intCast(u16, text_seg.sections.items.len); + try text_seg.addSection(self.allocator, .{ + .sectname = makeStaticString("__cstring"), + .segname = makeStaticString("__TEXT"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 0, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_CSTRING_LITERALS, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + }, + macho.S_MOD_INIT_FUNC_POINTERS => { + if (!mem.eql(u8, segname, "__DATA")) continue; + if (self.mod_init_func_section_index != null) continue; + + self.mod_init_func_section_index = @intCast(u16, data_const_seg.sections.items.len); + try data_const_seg.addSection(self.allocator, .{ + .sectname = makeStaticString("__mod_init_func"), + .segname = makeStaticString("__DATA_CONST"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 0, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_MOD_INIT_FUNC_POINTERS, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + }, + macho.S_MOD_TERM_FUNC_POINTERS => { + if (!mem.eql(u8, segname, "__DATA")) continue; + if (self.mod_term_func_section_index != null) continue; + + self.mod_term_func_section_index = @intCast(u16, data_const_seg.sections.items.len); + try data_const_seg.addSection(self.allocator, .{ + .sectname = makeStaticString("__mod_term_func"), + .segname = makeStaticString("__DATA_CONST"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 0, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_MOD_TERM_FUNC_POINTERS, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + }, + macho.S_ZEROFILL => { + if (!mem.eql(u8, segname, "__DATA")) continue; + if (self.bss_section_index != null) continue; + + self.bss_section_index = @intCast(u16, data_seg.sections.items.len); + try data_seg.addSection(self.allocator, .{ + .sectname = makeStaticString("__bss"), + .segname = makeStaticString("__DATA"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 0, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_ZEROFILL, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + }, + macho.S_THREAD_LOCAL_VARIABLES => { + if (!mem.eql(u8, segname, "__DATA")) continue; + if (self.tlv_section_index != null) continue; + + self.tlv_section_index = @intCast(u16, data_seg.sections.items.len); + try data_seg.addSection(self.allocator, .{ + .sectname = makeStaticString("__thread_vars"), + .segname = makeStaticString("__DATA"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 0, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_THREAD_LOCAL_VARIABLES, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + }, + macho.S_THREAD_LOCAL_REGULAR => { + if (!mem.eql(u8, segname, "__DATA")) continue; + if (self.tlv_data_section_index != null) continue; + + self.tlv_data_section_index = @intCast(u16, data_seg.sections.items.len); + try data_seg.addSection(self.allocator, .{ + .sectname = makeStaticString("__thread_data"), + .segname = makeStaticString("__DATA"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 0, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_THREAD_LOCAL_REGULAR, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + }, + macho.S_THREAD_LOCAL_ZEROFILL => { + if (!mem.eql(u8, segname, "__DATA")) continue; + if (self.tlv_bss_section_index != null) continue; + + self.tlv_bss_section_index = @intCast(u16, data_seg.sections.items.len); + try data_seg.addSection(self.allocator, .{ + .sectname = makeStaticString("__thread_bss"), + .segname = makeStaticString("__DATA"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 0, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_THREAD_LOCAL_ZEROFILL, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + }, + else => { + log.debug("unhandled section type 0x{x} for '{s}/{s}'", .{ flags, segname, sectname }); + }, + } + } + + // Find ideal section alignment. + for (object_seg.sections.items) |source_sect, id| { + if (self.getMatchingSection(source_sect)) |res| { + const target_seg = &self.load_commands.items[res.seg].Segment; + const target_sect = &target_seg.sections.items[res.sect]; + target_sect.@"align" = math.max(target_sect.@"align", source_sect.@"align"); + } + } + + // Update section mappings + for (object_seg.sections.items) |source_sect, id| { + const source_sect_id = @intCast(u16, id); + if (self.getMatchingSection(source_sect)) |res| { + try self.mapAndUpdateSections(object_id, source_sect_id, res.seg, res.sect); + continue; + } + + const segname = parseName(&source_sect.segname); + const sectname = parseName(&source_sect.sectname); + log.debug("section '{s}/{s}' will be unmapped", .{ segname, sectname }); + try self.unhandled_sections.putNoClobber(self.allocator, .{ + .object_id = object_id, + .source_sect_id = source_sect_id, + }, 0); + } +} + +const MatchingSection = struct { + seg: u16, + sect: u16, +}; + +fn getMatchingSection(self: *Zld, section: macho.section_64) ?MatchingSection { + const segname = parseName(§ion.segname); + const sectname = parseName(§ion.sectname); + const res: ?MatchingSection = blk: { + switch (section.flags) { + macho.S_4BYTE_LITERALS, macho.S_8BYTE_LITERALS, macho.S_16BYTE_LITERALS => { + break :blk .{ + .seg = self.text_segment_cmd_index.?, + .sect = self.text_const_section_index.?, + }; + }, + macho.S_CSTRING_LITERALS => { + break :blk .{ + .seg = self.text_segment_cmd_index.?, + .sect = self.cstring_section_index.?, + }; + }, + macho.S_MOD_INIT_FUNC_POINTERS => { + break :blk .{ + .seg = self.data_const_segment_cmd_index.?, + .sect = self.mod_init_func_section_index.?, + }; + }, + macho.S_MOD_TERM_FUNC_POINTERS => { + break :blk .{ + .seg = self.data_const_segment_cmd_index.?, + .sect = self.mod_term_func_section_index.?, + }; + }, + macho.S_ZEROFILL => { + break :blk .{ + .seg = self.data_segment_cmd_index.?, + .sect = self.bss_section_index.?, + }; + }, + macho.S_THREAD_LOCAL_VARIABLES => { + break :blk .{ + .seg = self.data_segment_cmd_index.?, + .sect = self.tlv_section_index.?, + }; + }, + macho.S_THREAD_LOCAL_REGULAR => { + break :blk .{ + .seg = self.data_segment_cmd_index.?, + .sect = self.tlv_data_section_index.?, + }; + }, + macho.S_THREAD_LOCAL_ZEROFILL => { + break :blk .{ + .seg = self.data_segment_cmd_index.?, + .sect = self.tlv_bss_section_index.?, + }; + }, + macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS => { + break :blk .{ + .seg = self.text_segment_cmd_index.?, + .sect = self.text_section_index.?, + }; + }, + macho.S_REGULAR => { + if (mem.eql(u8, segname, "__TEXT")) { + break :blk .{ + .seg = self.text_segment_cmd_index.?, + .sect = self.text_const_section_index.?, + }; + } else if (mem.eql(u8, segname, "__DATA")) { + if (mem.eql(u8, sectname, "__data")) { + break :blk .{ + .seg = self.data_segment_cmd_index.?, + .sect = self.data_section_index.?, + }; + } else if (mem.eql(u8, sectname, "__const")) { + break :blk .{ + .seg = self.data_const_segment_cmd_index.?, + .sect = self.data_const_section_index.?, + }; + } + } + break :blk null; + }, + else => { + break :blk null; + }, + } + }; + return res; +} + +fn sortSections(self: *Zld) !void { + var text_index_mapping = std.AutoHashMap(u16, u16).init(self.allocator); + defer text_index_mapping.deinit(); + var data_const_index_mapping = std.AutoHashMap(u16, u16).init(self.allocator); + defer data_const_index_mapping.deinit(); + var data_index_mapping = std.AutoHashMap(u16, u16).init(self.allocator); + defer data_index_mapping.deinit(); + + { + // __TEXT segment + const seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + var sections = seg.sections.toOwnedSlice(self.allocator); + defer self.allocator.free(sections); + try seg.sections.ensureCapacity(self.allocator, sections.len); + + const indices = &[_]*?u16{ + &self.text_section_index, + &self.stubs_section_index, + &self.stub_helper_section_index, + &self.text_const_section_index, + &self.cstring_section_index, + }; + for (indices) |maybe_index| { + const new_index: u16 = if (maybe_index.*) |index| blk: { + const idx = @intCast(u16, seg.sections.items.len); + seg.sections.appendAssumeCapacity(sections[index]); + try text_index_mapping.putNoClobber(index, idx); + break :blk idx; + } else continue; + maybe_index.* = new_index; + } + } + + { + // __DATA_CONST segment + const seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + var sections = seg.sections.toOwnedSlice(self.allocator); + defer self.allocator.free(sections); + try seg.sections.ensureCapacity(self.allocator, sections.len); + + const indices = &[_]*?u16{ + &self.got_section_index, + &self.mod_init_func_section_index, + &self.mod_term_func_section_index, + &self.data_const_section_index, + }; + for (indices) |maybe_index| { + const new_index: u16 = if (maybe_index.*) |index| blk: { + const idx = @intCast(u16, seg.sections.items.len); + seg.sections.appendAssumeCapacity(sections[index]); + try data_const_index_mapping.putNoClobber(index, idx); + break :blk idx; + } else continue; + maybe_index.* = new_index; + } + } + + { + // __DATA segment + const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + var sections = seg.sections.toOwnedSlice(self.allocator); + defer self.allocator.free(sections); + try seg.sections.ensureCapacity(self.allocator, sections.len); + + // __DATA segment + const indices = &[_]*?u16{ + &self.la_symbol_ptr_section_index, + &self.tlv_section_index, + &self.data_section_index, + &self.tlv_data_section_index, + &self.tlv_bss_section_index, + &self.bss_section_index, + }; + for (indices) |maybe_index| { + const new_index: u16 = if (maybe_index.*) |index| blk: { + const idx = @intCast(u16, seg.sections.items.len); + seg.sections.appendAssumeCapacity(sections[index]); + try data_index_mapping.putNoClobber(index, idx); + break :blk idx; + } else continue; + maybe_index.* = new_index; + } + } + + var it = self.mappings.iterator(); + while (it.next()) |entry| { + const mapping = &entry.value; + if (self.text_segment_cmd_index.? == mapping.target_seg_id) { + const new_index = text_index_mapping.get(mapping.target_sect_id) orelse unreachable; + mapping.target_sect_id = new_index; + } else if (self.data_const_segment_cmd_index.? == mapping.target_seg_id) { + const new_index = data_const_index_mapping.get(mapping.target_sect_id) orelse unreachable; + mapping.target_sect_id = new_index; + } else if (self.data_segment_cmd_index.? == mapping.target_seg_id) { + const new_index = data_index_mapping.get(mapping.target_sect_id) orelse unreachable; + mapping.target_sect_id = new_index; + } else unreachable; + } +} + +fn resolveImports(self: *Zld) !void { + var imports = std.StringArrayHashMap(bool).init(self.allocator); + defer imports.deinit(); + + for (self.objects.items) |object| { + for (object.symtab.items) |sym| { + if (isLocal(&sym)) continue; + + const name = object.getString(sym.n_strx); + const res = try imports.getOrPut(name); + if (isExport(&sym)) { + res.entry.value = false; + continue; + } + if (res.found_existing and !res.entry.value) + continue; + res.entry.value = true; + } + } + + for (imports.items()) |entry| { + if (!entry.value) continue; + + const sym_name = entry.key; + const n_strx = try self.makeString(sym_name); + var new_sym: macho.nlist_64 = .{ + .n_strx = n_strx, + .n_type = macho.N_UNDF | macho.N_EXT, + .n_value = 0, + .n_desc = macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY | macho.N_SYMBOL_RESOLVER, + .n_sect = 0, + }; + var key = try self.allocator.dupe(u8, sym_name); + // TODO handle symbol resolution from non-libc dylibs. + const dylib_ordinal = 1; + + // TODO need to rework this. Perhaps should create a set of all possible libc + // symbols which are expected to be nonlazy? + if (mem.eql(u8, sym_name, "___stdoutp") or + mem.eql(u8, sym_name, "___stderrp") or + mem.eql(u8, sym_name, "___stdinp") or + mem.eql(u8, sym_name, "___stack_chk_guard") or + mem.eql(u8, sym_name, "_environ") or + mem.eql(u8, sym_name, "__DefaultRuneLocale") or + mem.eql(u8, sym_name, "_mach_task_self_")) + { + log.debug("writing nonlazy symbol '{s}'", .{sym_name}); + const index = @intCast(u32, self.nonlazy_imports.items().len); + try self.nonlazy_imports.putNoClobber(self.allocator, key, .{ + .symbol = new_sym, + .dylib_ordinal = dylib_ordinal, + .index = index, + }); + } else if (mem.eql(u8, sym_name, "__tlv_bootstrap")) { + log.debug("writing threadlocal symbol '{s}'", .{sym_name}); + self.tlv_bootstrap = .{ + .symbol = new_sym, + .dylib_ordinal = dylib_ordinal, + .index = 0, + }; + } else { + log.debug("writing lazy symbol '{s}'", .{sym_name}); + const index = @intCast(u32, self.lazy_imports.items().len); + try self.lazy_imports.putNoClobber(self.allocator, key, .{ + .symbol = new_sym, + .dylib_ordinal = dylib_ordinal, + .index = index, + }); + } + } + + const n_strx = try self.makeString("dyld_stub_binder"); + const name = try self.allocator.dupe(u8, "dyld_stub_binder"); + log.debug("writing nonlazy symbol 'dyld_stub_binder'", .{}); + const index = @intCast(u32, self.nonlazy_imports.items().len); + try self.nonlazy_imports.putNoClobber(self.allocator, name, .{ + .symbol = .{ + .n_strx = n_strx, + .n_type = std.macho.N_UNDF | std.macho.N_EXT, + .n_sect = 0, + .n_desc = std.macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY | std.macho.N_SYMBOL_RESOLVER, + .n_value = 0, + }, + .dylib_ordinal = 1, + .index = index, + }); +} + +fn allocateTextSegment(self: *Zld) !void { + const seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const nexterns = @intCast(u32, self.lazy_imports.items().len); + + const base_vmaddr = self.load_commands.items[self.pagezero_segment_cmd_index.?].Segment.inner.vmsize; + seg.inner.fileoff = 0; + seg.inner.vmaddr = base_vmaddr; + + // Set stubs and stub_helper sizes + const stubs = &seg.sections.items[self.stubs_section_index.?]; + const stub_helper = &seg.sections.items[self.stub_helper_section_index.?]; + stubs.size += nexterns * stubs.reserved2; + + const stub_size: u4 = switch (self.arch.?) { + .x86_64 => 10, + .aarch64 => 3 * @sizeOf(u32), + else => unreachable, + }; + stub_helper.size += nexterns * stub_size; + + var sizeofcmds: u64 = 0; + for (self.load_commands.items) |lc| { + sizeofcmds += lc.cmdsize(); + } + + try self.allocateSegment(self.text_segment_cmd_index.?, @sizeOf(macho.mach_header_64) + sizeofcmds); + + // Shift all sections to the back to minimize jump size between __TEXT and __DATA segments. + var min_alignment: u32 = 0; + for (seg.sections.items) |sect| { + const alignment = try math.powi(u32, 2, sect.@"align"); + min_alignment = math.max(min_alignment, alignment); + } + + assert(min_alignment > 0); + const last_sect_idx = seg.sections.items.len - 1; + const last_sect = seg.sections.items[last_sect_idx]; + const shift: u32 = blk: { + const diff = seg.inner.filesize - last_sect.offset - last_sect.size; + const factor = @divTrunc(diff, min_alignment); + break :blk @intCast(u32, factor * min_alignment); + }; + + if (shift > 0) { + for (seg.sections.items) |*sect| { + sect.offset += shift; + sect.addr += shift; + } + } +} + +fn allocateDataConstSegment(self: *Zld) !void { + const seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const nonlazy = @intCast(u32, self.nonlazy_imports.items().len); + + const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + seg.inner.fileoff = text_seg.inner.fileoff + text_seg.inner.filesize; + seg.inner.vmaddr = text_seg.inner.vmaddr + text_seg.inner.vmsize; + + // Set got size + const got = &seg.sections.items[self.got_section_index.?]; + // TODO this will require scanning the relocations at least one to work out + // the exact amount of local GOT indirections. For the time being, set some + // default value. + got.size += (max_local_got_indirections + nonlazy) * @sizeOf(u64); + + try self.allocateSegment(self.data_const_segment_cmd_index.?, 0); +} + +fn allocateDataSegment(self: *Zld) !void { + const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const lazy = @intCast(u32, self.lazy_imports.items().len); + + const data_const_seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + seg.inner.fileoff = data_const_seg.inner.fileoff + data_const_seg.inner.filesize; + seg.inner.vmaddr = data_const_seg.inner.vmaddr + data_const_seg.inner.vmsize; + + // Set la_symbol_ptr and data size + const la_symbol_ptr = &seg.sections.items[self.la_symbol_ptr_section_index.?]; + const data = &seg.sections.items[self.data_section_index.?]; + la_symbol_ptr.size += lazy * @sizeOf(u64); + data.size += @sizeOf(u64); // TODO when do we need more? + + try self.allocateSegment(self.data_segment_cmd_index.?, 0); +} + +fn allocateLinkeditSegment(self: *Zld) void { + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const data_seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + seg.inner.fileoff = data_seg.inner.fileoff + data_seg.inner.filesize; + seg.inner.vmaddr = data_seg.inner.vmaddr + data_seg.inner.vmsize; +} + +fn allocateSegment(self: *Zld, index: u16, offset: u64) !void { + const base_vmaddr = self.load_commands.items[self.pagezero_segment_cmd_index.?].Segment.inner.vmsize; + const seg = &self.load_commands.items[index].Segment; + + // Allocate the sections according to their alignment at the beginning of the segment. + var start: u64 = offset; + for (seg.sections.items) |*sect| { + const alignment = try math.powi(u32, 2, sect.@"align"); + const start_aligned = mem.alignForwardGeneric(u64, start, alignment); + const end_aligned = mem.alignForwardGeneric(u64, start_aligned + sect.size, alignment); + sect.offset = @intCast(u32, seg.inner.fileoff + start_aligned); + sect.addr = seg.inner.vmaddr + start_aligned; + start = end_aligned; + } + + const seg_size_aligned = mem.alignForwardGeneric(u64, start, self.page_size.?); + seg.inner.filesize = seg_size_aligned; + seg.inner.vmsize = seg_size_aligned; +} + +fn writeStubHelperCommon(self: *Zld) !void { + const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const stub_helper = &text_segment.sections.items[self.stub_helper_section_index.?]; + const data_const_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const got = &data_const_segment.sections.items[self.got_section_index.?]; + const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const data = &data_segment.sections.items[self.data_section_index.?]; + const la_symbol_ptr = data_segment.sections.items[self.la_symbol_ptr_section_index.?]; + + self.stub_helper_stubs_start_off = blk: { + switch (self.arch.?) { + .x86_64 => { + const code_size = 15; + var code: [code_size]u8 = undefined; + // lea %r11, [rip + disp] + code[0] = 0x4c; + code[1] = 0x8d; + code[2] = 0x1d; + { + const target_addr = data.addr + data.size - @sizeOf(u64); + const displacement = try math.cast(u32, target_addr - stub_helper.addr - 7); + mem.writeIntLittle(u32, code[3..7], displacement); + } + // push %r11 + code[7] = 0x41; + code[8] = 0x53; + // jmp [rip + disp] + code[9] = 0xff; + code[10] = 0x25; + { + const dyld_stub_binder = self.nonlazy_imports.get("dyld_stub_binder").?; + const addr = (got.addr + dyld_stub_binder.index * @sizeOf(u64)); + const displacement = try math.cast(u32, addr - stub_helper.addr - code_size); + mem.writeIntLittle(u32, code[11..], displacement); + } + try self.file.?.pwriteAll(&code, stub_helper.offset); + break :blk stub_helper.offset + code_size; + }, + .aarch64 => { + var code: [6 * @sizeOf(u32)]u8 = undefined; + data_blk_outer: { + const this_addr = stub_helper.addr; + const target_addr = data.addr + data.size - @sizeOf(u64); + data_blk: { + const displacement = math.cast(i21, target_addr - this_addr) catch |_| break :data_blk; + // adr x17, disp + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adr(.x17, displacement).toU32()); + // nop + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.nop().toU32()); + break :data_blk_outer; + } + data_blk: { + const new_this_addr = this_addr + @sizeOf(u32); + const displacement = math.cast(i21, target_addr - new_this_addr) catch |_| break :data_blk; + // nop + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.nop().toU32()); + // adr x17, disp + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.adr(.x17, displacement).toU32()); + break :data_blk_outer; + } + // Jump is too big, replace adr with adrp and add. + const this_page = @intCast(i32, this_addr >> 12); + const target_page = @intCast(i32, target_addr >> 12); + const pages = @intCast(i21, target_page - this_page); + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adrp(.x17, pages).toU32()); + const narrowed = @truncate(u12, target_addr); + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.add(.x17, .x17, narrowed, false).toU32()); + } + // stp x16, x17, [sp, #-16]! + code[8] = 0xf0; + code[9] = 0x47; + code[10] = 0xbf; + code[11] = 0xa9; + binder_blk_outer: { + const dyld_stub_binder = self.nonlazy_imports.get("dyld_stub_binder").?; + const this_addr = stub_helper.addr + 3 * @sizeOf(u32); + const target_addr = (got.addr + dyld_stub_binder.index * @sizeOf(u64)); + binder_blk: { + const displacement = math.divExact(u64, target_addr - this_addr, 4) catch |_| break :binder_blk; + const literal = math.cast(u18, displacement) catch |_| break :binder_blk; + // ldr x16, label + mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.ldr(.x16, .{ + .literal = literal, + }).toU32()); + // nop + mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.nop().toU32()); + break :binder_blk_outer; + } + binder_blk: { + const new_this_addr = this_addr + @sizeOf(u32); + const displacement = math.divExact(u64, target_addr - new_this_addr, 4) catch |_| break :binder_blk; + const literal = math.cast(u18, displacement) catch |_| break :binder_blk; + log.debug("2: disp=0x{x}, literal=0x{x}", .{ displacement, literal }); + // Pad with nop to please division. + // nop + mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.nop().toU32()); + // ldr x16, label + mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.ldr(.x16, .{ + .literal = literal, + }).toU32()); + break :binder_blk_outer; + } + // Use adrp followed by ldr(immediate). + const this_page = @intCast(i32, this_addr >> 12); + const target_page = @intCast(i32, target_addr >> 12); + const pages = @intCast(i21, target_page - this_page); + mem.writeIntLittle(u32, code[12..16], aarch64.Instruction.adrp(.x16, pages).toU32()); + const narrowed = @truncate(u12, target_addr); + const offset = try math.divExact(u12, narrowed, 8); + mem.writeIntLittle(u32, code[16..20], aarch64.Instruction.ldr(.x16, .{ + .register = .{ + .rn = .x16, + .offset = aarch64.Instruction.LoadStoreOffset.imm(offset), + }, + }).toU32()); + } + // br x16 + code[20] = 0x00; + code[21] = 0x02; + code[22] = 0x1f; + code[23] = 0xd6; + try self.file.?.pwriteAll(&code, stub_helper.offset); + break :blk stub_helper.offset + 6 * @sizeOf(u32); + }, + else => unreachable, + } + }; + + for (self.lazy_imports.items()) |_, i| { + const index = @intCast(u32, i); + try self.writeLazySymbolPointer(index); + try self.writeStub(index); + try self.writeStubInStubHelper(index); + } +} + +fn writeLazySymbolPointer(self: *Zld, index: u32) !void { + const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const stub_helper = text_segment.sections.items[self.stub_helper_section_index.?]; + const data_segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const la_symbol_ptr = data_segment.sections.items[self.la_symbol_ptr_section_index.?]; + + const stub_size: u4 = switch (self.arch.?) { + .x86_64 => 10, + .aarch64 => 3 * @sizeOf(u32), + else => unreachable, + }; + const stub_off = self.stub_helper_stubs_start_off.? + index * stub_size; + const end = stub_helper.addr + stub_off - stub_helper.offset; + var buf: [@sizeOf(u64)]u8 = undefined; + mem.writeIntLittle(u64, &buf, end); + const off = la_symbol_ptr.offset + index * @sizeOf(u64); + log.debug("writing lazy symbol pointer entry 0x{x} at 0x{x}", .{ end, off }); + try self.file.?.pwriteAll(&buf, off); +} + +fn writeStub(self: *Zld, index: u32) !void { + const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const stubs = text_segment.sections.items[self.stubs_section_index.?]; + const data_segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const la_symbol_ptr = data_segment.sections.items[self.la_symbol_ptr_section_index.?]; + + const stub_off = stubs.offset + index * stubs.reserved2; + const stub_addr = stubs.addr + index * stubs.reserved2; + const la_ptr_addr = la_symbol_ptr.addr + index * @sizeOf(u64); + log.debug("writing stub at 0x{x}", .{stub_off}); + var code = try self.allocator.alloc(u8, stubs.reserved2); + defer self.allocator.free(code); + switch (self.arch.?) { + .x86_64 => { + assert(la_ptr_addr >= stub_addr + stubs.reserved2); + const displacement = try math.cast(u32, la_ptr_addr - stub_addr - stubs.reserved2); + // jmp + code[0] = 0xff; + code[1] = 0x25; + mem.writeIntLittle(u32, code[2..][0..4], displacement); + }, + .aarch64 => { + assert(la_ptr_addr >= stub_addr); + outer: { + const this_addr = stub_addr; + const target_addr = la_ptr_addr; + inner: { + const displacement = math.divExact(u64, target_addr - this_addr, 4) catch |_| break :inner; + const literal = math.cast(u18, displacement) catch |_| break :inner; + // ldr x16, literal + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.x16, .{ + .literal = literal, + }).toU32()); + // nop + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.nop().toU32()); + break :outer; + } + inner: { + const new_this_addr = this_addr + @sizeOf(u32); + const displacement = math.divExact(u64, target_addr - new_this_addr, 4) catch |_| break :inner; + const literal = math.cast(u18, displacement) catch |_| break :inner; + // nop + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.nop().toU32()); + // ldr x16, literal + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ldr(.x16, .{ + .literal = literal, + }).toU32()); + break :outer; + } + // Use adrp followed by ldr(immediate). + const this_page = @intCast(i32, this_addr >> 12); + const target_page = @intCast(i32, target_addr >> 12); + const pages = @intCast(i21, target_page - this_page); + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.adrp(.x16, pages).toU32()); + const narrowed = @truncate(u12, target_addr); + const offset = try math.divExact(u12, narrowed, 8); + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.ldr(.x16, .{ + .register = .{ + .rn = .x16, + .offset = aarch64.Instruction.LoadStoreOffset.imm(offset), + }, + }).toU32()); + } + // br x16 + mem.writeIntLittle(u32, code[8..12], aarch64.Instruction.br(.x16).toU32()); + }, + else => unreachable, + } + try self.file.?.pwriteAll(code, stub_off); +} + +fn writeStubInStubHelper(self: *Zld, index: u32) !void { + const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const stub_helper = text_segment.sections.items[self.stub_helper_section_index.?]; + + const stub_size: u4 = switch (self.arch.?) { + .x86_64 => 10, + .aarch64 => 3 * @sizeOf(u32), + else => unreachable, + }; + const stub_off = self.stub_helper_stubs_start_off.? + index * stub_size; + var code = try self.allocator.alloc(u8, stub_size); + defer self.allocator.free(code); + switch (self.arch.?) { + .x86_64 => { + const displacement = try math.cast( + i32, + @intCast(i64, stub_helper.offset) - @intCast(i64, stub_off) - stub_size, + ); + // pushq + code[0] = 0x68; + mem.writeIntLittle(u32, code[1..][0..4], 0x0); // Just a placeholder populated in `populateLazyBindOffsetsInStubHelper`. + // jmpq + code[5] = 0xe9; + mem.writeIntLittle(u32, code[6..][0..4], @bitCast(u32, displacement)); + }, + .aarch64 => { + const displacement = try math.cast(i28, @intCast(i64, stub_helper.offset) - @intCast(i64, stub_off) - 4); + const literal = @divExact(stub_size - @sizeOf(u32), 4); + // ldr w16, literal + mem.writeIntLittle(u32, code[0..4], aarch64.Instruction.ldr(.w16, .{ + .literal = literal, + }).toU32()); + // b disp + mem.writeIntLittle(u32, code[4..8], aarch64.Instruction.b(displacement).toU32()); + mem.writeIntLittle(u32, code[8..12], 0x0); // Just a placeholder populated in `populateLazyBindOffsetsInStubHelper`. + }, + else => unreachable, + } + try self.file.?.pwriteAll(code, stub_off); +} + +fn resolveSymbols(self: *Zld) !void { + for (self.objects.items) |object, object_id| { + const seg = object.load_commands.items[object.segment_cmd_index.?].Segment; + log.debug("\n\n", .{}); + log.debug("resolving symbols in {s}", .{object.name}); + + for (object.symtab.items) |sym| { + if (isImport(&sym)) continue; + + const sym_name = object.getString(sym.n_strx); + const out_name = try self.allocator.dupe(u8, sym_name); + const locs = try self.locals.getOrPut(self.allocator, out_name); + defer { + if (locs.found_existing) self.allocator.free(out_name); + } + + if (!locs.found_existing) { + locs.entry.value = .{}; + } + + const tt: Symbol.Type = blk: { + if (isLocal(&sym)) { + break :blk .Local; + } else if (isWeakDef(&sym)) { + break :blk .WeakGlobal; + } else { + break :blk .Global; + } + }; + if (tt == .Global) { + for (locs.entry.value.items) |ss| { + if (ss.tt == .Global) { + log.debug("symbol already defined '{s}'", .{sym_name}); + continue; + // log.err("symbol '{s}' defined multiple times: {}", .{ sym_name, sym }); + // return error.MultipleSymbolDefinitions; + } + } + } + + const source_sect_id = sym.n_sect - 1; + const target_mapping = self.mappings.get(.{ + .object_id = @intCast(u16, object_id), + .source_sect_id = source_sect_id, + }) orelse { + if (self.unhandled_sections.get(.{ + .object_id = @intCast(u16, object_id), + .source_sect_id = source_sect_id, + }) != null) continue; + + log.err("section not mapped for symbol '{s}': {}", .{ sym_name, sym }); + return error.SectionNotMappedForSymbol; + }; + const source_sect = seg.sections.items[source_sect_id]; + const target_seg = self.load_commands.items[target_mapping.target_seg_id].Segment; + const target_sect = target_seg.sections.items[target_mapping.target_sect_id]; + const target_addr = target_sect.addr + target_mapping.offset; + const n_value = sym.n_value - source_sect.addr + target_addr; + + log.debug("resolving '{s}':{} as {s} symbol at 0x{x}", .{ sym_name, sym, tt, n_value }); + + // TODO there might be a more generic way of doing this. + var n_sect: u16 = 0; + for (self.load_commands.items) |cmd, cmd_id| { + if (cmd != .Segment) break; + if (cmd_id == target_mapping.target_seg_id) { + n_sect += target_mapping.target_sect_id + 1; + break; + } + n_sect += @intCast(u16, cmd.Segment.sections.items.len); + } + + const n_strx = try self.makeString(sym_name); + try locs.entry.value.append(self.allocator, .{ + .inner = .{ + .n_strx = n_strx, + .n_value = n_value, + .n_type = macho.N_SECT, + .n_desc = sym.n_desc, + .n_sect = @intCast(u8, n_sect), + }, + .tt = tt, + .object_id = @intCast(u16, object_id), + }); + } + } +} + +fn doRelocs(self: *Zld) !void { + for (self.objects.items) |object, object_id| { + log.debug("\n\n", .{}); + log.debug("relocating object {s}", .{object.name}); + + const seg = object.load_commands.items[object.segment_cmd_index.?].Segment; + + for (seg.sections.items) |sect, source_sect_id| { + const segname = parseName(§.segname); + const sectname = parseName(§.sectname); + + var code = try self.allocator.alloc(u8, sect.size); + _ = try object.file.preadAll(code, sect.offset); + defer self.allocator.free(code); + + // Parse relocs (if any) + var raw_relocs = try self.allocator.alloc(u8, @sizeOf(macho.relocation_info) * sect.nreloc); + defer self.allocator.free(raw_relocs); + _ = try object.file.preadAll(raw_relocs, sect.reloff); + const relocs = mem.bytesAsSlice(macho.relocation_info, raw_relocs); + + // Get mapping + const target_mapping = self.mappings.get(.{ + .object_id = @intCast(u16, object_id), + .source_sect_id = @intCast(u16, source_sect_id), + }) orelse { + log.debug("no mapping for {s},{s}; skipping", .{ segname, sectname }); + continue; + }; + const target_seg = self.load_commands.items[target_mapping.target_seg_id].Segment; + const target_sect = target_seg.sections.items[target_mapping.target_sect_id]; + const target_sect_addr = target_sect.addr + target_mapping.offset; + const target_sect_off = target_sect.offset + target_mapping.offset; + + var addend: ?u64 = null; + var sub: ?i64 = null; + + for (relocs) |rel| { + const off = @intCast(u32, rel.r_address); + const this_addr = target_sect_addr + off; + + switch (self.arch.?) { + .aarch64 => { + const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type); + log.debug("{s}", .{rel_type}); + log.debug(" | source address 0x{x}", .{this_addr}); + log.debug(" | offset 0x{x}", .{off}); + + if (rel_type == .ARM64_RELOC_ADDEND) { + addend = rel.r_symbolnum; + log.debug(" | calculated addend = 0x{x}", .{addend}); + // TODO followed by either PAGE21 or PAGEOFF12 only. + continue; + } + }, + .x86_64 => { + const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type); + log.debug("{s}", .{rel_type}); + log.debug(" | source address 0x{x}", .{this_addr}); + log.debug(" | offset 0x{x}", .{off}); + }, + else => {}, + } + + const target_addr = try self.relocTargetAddr(@intCast(u16, object_id), rel); + log.debug(" | target address 0x{x}", .{target_addr}); + if (rel.r_extern == 1) { + const target_symname = object.getString(object.symtab.items[rel.r_symbolnum].n_strx); + log.debug(" | target symbol '{s}'", .{target_symname}); + } else { + const target_sectname = seg.sections.items[rel.r_symbolnum - 1].sectname; + log.debug(" | target section '{s}'", .{parseName(&target_sectname)}); + } + + switch (self.arch.?) { + .x86_64 => { + const rel_type = @intToEnum(macho.reloc_type_x86_64, rel.r_type); + + switch (rel_type) { + .X86_64_RELOC_BRANCH => { + assert(rel.r_length == 2); + const inst = code[off..][0..4]; + const displacement = @bitCast(u32, @intCast(i32, @intCast(i64, target_addr) - @intCast(i64, this_addr) - 4)); + mem.writeIntLittle(u32, inst, displacement); + }, + .X86_64_RELOC_GOT_LOAD => { + assert(rel.r_length == 2); + const inst = code[off..][0..4]; + const displacement = @bitCast(u32, @intCast(i32, @intCast(i64, target_addr) - @intCast(i64, this_addr) - 4)); + + blk: { + const data_const_seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const got = data_const_seg.sections.items[self.got_section_index.?]; + if (got.addr <= target_addr and target_addr < got.addr + got.size) break :blk; + log.debug(" | rewriting to leaq", .{}); + code[off - 2] = 0x8d; + } + + mem.writeIntLittle(u32, inst, displacement); + }, + .X86_64_RELOC_GOT => { + assert(rel.r_length == 2); + // TODO Instead of referring to the target symbol directly, we refer to it + // indirectly via GOT. Getting actual target address should be done in the + // helper relocTargetAddr function rather than here. + const sym = object.symtab.items[rel.r_symbolnum]; + const sym_name = try self.allocator.dupe(u8, object.getString(sym.n_strx)); + const res = try self.nonlazy_pointers.getOrPut(self.allocator, sym_name); + defer if (res.found_existing) self.allocator.free(sym_name); + + const data_const_seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const got = data_const_seg.sections.items[self.got_section_index.?]; + + if (!res.found_existing) { + const index = @intCast(u32, self.nonlazy_pointers.items().len) - 1; + assert(index < max_local_got_indirections); // TODO This is just a temp solution. + res.entry.value = .{ + .index = index, + .target_addr = target_addr, + }; + var buf: [@sizeOf(u64)]u8 = undefined; + mem.writeIntLittle(u64, &buf, target_addr); + const got_offset = got.offset + (index + self.nonlazy_imports.items().len) * @sizeOf(u64); + + log.debug(" | GOT off 0x{x}", .{got.offset}); + log.debug(" | writing GOT entry 0x{x} at 0x{x}", .{ target_addr, got_offset }); + + try self.file.?.pwriteAll(&buf, got_offset); + } + + const index = res.entry.value.index + self.nonlazy_imports.items().len; + const actual_target_addr = got.addr + index * @sizeOf(u64); + + log.debug(" | GOT addr 0x{x}", .{got.addr}); + log.debug(" | actual target address in GOT 0x{x}", .{actual_target_addr}); + + const inst = code[off..][0..4]; + const displacement = @bitCast(u32, @intCast(i32, @intCast(i64, actual_target_addr) - @intCast(i64, this_addr) - 4)); + mem.writeIntLittle(u32, inst, displacement); + }, + .X86_64_RELOC_TLV => { + assert(rel.r_length == 2); + // We need to rewrite the opcode from movq to leaq. + code[off - 2] = 0x8d; + // Add displacement. + const inst = code[off..][0..4]; + const displacement = @bitCast(u32, @intCast(i32, @intCast(i64, target_addr) - @intCast(i64, this_addr) - 4)); + mem.writeIntLittle(u32, inst, displacement); + }, + .X86_64_RELOC_SIGNED, + .X86_64_RELOC_SIGNED_1, + .X86_64_RELOC_SIGNED_2, + .X86_64_RELOC_SIGNED_4, + => { + assert(rel.r_length == 2); + const inst = code[off..][0..4]; + const offset = @intCast(i64, mem.readIntLittle(i32, inst)); + log.debug(" | calculated addend 0x{x}", .{offset}); + const actual_target_addr = blk: { + if (rel.r_extern == 1) { + break :blk @intCast(i64, target_addr) + offset; + } else { + const correction: i4 = switch (rel_type) { + .X86_64_RELOC_SIGNED => 0, + .X86_64_RELOC_SIGNED_1 => 1, + .X86_64_RELOC_SIGNED_2 => 2, + .X86_64_RELOC_SIGNED_4 => 4, + else => unreachable, + }; + log.debug(" | calculated correction 0x{x}", .{correction}); + + // The value encoded in the instruction is a displacement - 4 - correction. + // To obtain the adjusted target address in the final binary, we need + // calculate the original target address within the object file, establish + // what the offset from the original target section was, and apply this + // offset to the resultant target section with this relocated binary. + const orig_sect_id = @intCast(u16, rel.r_symbolnum - 1); + const target_map = self.mappings.get(.{ + .object_id = @intCast(u16, object_id), + .source_sect_id = orig_sect_id, + }) orelse unreachable; + const orig_seg = object.load_commands.items[object.segment_cmd_index.?].Segment; + const orig_sect = orig_seg.sections.items[orig_sect_id]; + const orig_offset = off + offset + 4 + correction - @intCast(i64, orig_sect.addr); + log.debug(" | original offset 0x{x}", .{orig_offset}); + const adjusted = @intCast(i64, target_addr) + orig_offset; + log.debug(" | adjusted target address 0x{x}", .{adjusted}); + break :blk adjusted - correction; + } + }; + const result = actual_target_addr - @intCast(i64, this_addr) - 4; + const displacement = @bitCast(u32, @intCast(i32, result)); + mem.writeIntLittle(u32, inst, displacement); + }, + .X86_64_RELOC_SUBTRACTOR => { + sub = @intCast(i64, target_addr); + }, + .X86_64_RELOC_UNSIGNED => { + switch (rel.r_length) { + 3 => { + const inst = code[off..][0..8]; + const offset = mem.readIntLittle(i64, inst); + + const result = outer: { + if (rel.r_extern == 1) { + log.debug(" | calculated addend 0x{x}", .{offset}); + if (sub) |s| { + break :outer @intCast(i64, target_addr) - s + offset; + } else { + break :outer @intCast(i64, target_addr) + offset; + } + } else { + // The value encoded in the instruction is an absolute offset + // from the start of MachO header to the target address in the + // object file. To extract the address, we calculate the offset from + // the beginning of the source section to the address, and apply it to + // the target address value. + const orig_sect_id = @intCast(u16, rel.r_symbolnum - 1); + const target_map = self.mappings.get(.{ + .object_id = @intCast(u16, object_id), + .source_sect_id = orig_sect_id, + }) orelse unreachable; + const orig_seg = object.load_commands.items[object.segment_cmd_index.?].Segment; + const orig_sect = orig_seg.sections.items[orig_sect_id]; + const orig_offset = offset - @intCast(i64, orig_sect.addr); + const actual_target_addr = inner: { + if (sub) |s| { + break :inner @intCast(i64, target_addr) - s + orig_offset; + } else { + break :inner @intCast(i64, target_addr) + orig_offset; + } + }; + log.debug(" | adjusted target address 0x{x}", .{actual_target_addr}); + break :outer actual_target_addr; + } + }; + mem.writeIntLittle(u64, inst, @bitCast(u64, result)); + sub = null; + + rebases: { + var hit: bool = false; + if (target_mapping.target_seg_id == self.data_segment_cmd_index.?) { + if (self.data_section_index) |index| { + if (index == target_mapping.target_sect_id) hit = true; + } + } + if (target_mapping.target_seg_id == self.data_const_segment_cmd_index.?) { + if (self.data_const_section_index) |index| { + if (index == target_mapping.target_sect_id) hit = true; + } + } + + if (!hit) break :rebases; + + try self.local_rebases.append(self.allocator, .{ + .offset = this_addr - target_seg.inner.vmaddr, + .segment_id = target_mapping.target_seg_id, + }); + } + // TLV is handled via a separate offset mechanism. + // Calculate the offset to the initializer. + if (target_sect.flags == macho.S_THREAD_LOCAL_VARIABLES) tlv: { + assert(rel.r_extern == 1); + const sym = object.symtab.items[rel.r_symbolnum]; + if (isImport(&sym)) break :tlv; + + const base_addr = blk: { + if (self.tlv_data_section_index) |index| { + const tlv_data = target_seg.sections.items[index]; + break :blk tlv_data.addr; + } else { + const tlv_bss = target_seg.sections.items[self.tlv_bss_section_index.?]; + break :blk tlv_bss.addr; + } + }; + // Since we require TLV data to always preceed TLV bss section, we calculate + // offsets wrt to the former if it is defined; otherwise, wrt to the latter. + try self.threadlocal_offsets.append(self.allocator, target_addr - base_addr); + } + }, + 2 => { + const inst = code[off..][0..4]; + const offset = mem.readIntLittle(i32, inst); + log.debug(" | calculated addend 0x{x}", .{offset}); + const result = if (sub) |s| + @intCast(i64, target_addr) - s + offset + else + @intCast(i64, target_addr) + offset; + mem.writeIntLittle(u32, inst, @truncate(u32, @bitCast(u64, result))); + sub = null; + }, + else => |len| { + log.err("unexpected relocation length 0x{x}", .{len}); + return error.UnexpectedRelocationLength; + }, + } + }, + } + }, + .aarch64 => { + const rel_type = @intToEnum(macho.reloc_type_arm64, rel.r_type); + + switch (rel_type) { + .ARM64_RELOC_BRANCH26 => { + assert(rel.r_length == 2); + const inst = code[off..][0..4]; + const displacement = @intCast( + i28, + @intCast(i64, target_addr) - @intCast(i64, this_addr), + ); + var parsed = mem.bytesAsValue( + meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.UnconditionalBranchImmediate, + ), + inst, + ); + parsed.imm26 = @truncate(u26, @bitCast(u28, displacement) >> 2); + }, + .ARM64_RELOC_PAGE21, + .ARM64_RELOC_GOT_LOAD_PAGE21, + .ARM64_RELOC_TLVP_LOAD_PAGE21, + => { + assert(rel.r_length == 2); + const inst = code[off..][0..4]; + const ta = if (addend) |a| target_addr + a else target_addr; + const this_page = @intCast(i32, this_addr >> 12); + const target_page = @intCast(i32, ta >> 12); + const pages = @bitCast(u21, @intCast(i21, target_page - this_page)); + log.debug(" | moving by {} pages", .{pages}); + var parsed = mem.bytesAsValue( + meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.PCRelativeAddress, + ), + inst, + ); + parsed.immhi = @truncate(u19, pages >> 2); + parsed.immlo = @truncate(u2, pages); + addend = null; + }, + .ARM64_RELOC_PAGEOFF12, + .ARM64_RELOC_GOT_LOAD_PAGEOFF12, + => { + const inst = code[off..][0..4]; + if (aarch64IsArithmetic(inst)) { + log.debug(" | detected ADD opcode", .{}); + // add + var parsed = mem.bytesAsValue( + meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.AddSubtractImmediate, + ), + inst, + ); + const ta = if (addend) |a| target_addr + a else target_addr; + const narrowed = @truncate(u12, ta); + parsed.imm12 = narrowed; + } else { + log.debug(" | detected LDR/STR opcode", .{}); + // ldr/str + var parsed = mem.bytesAsValue( + meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.LoadStoreRegister, + ), + inst, + ); + + const ta = if (addend) |a| target_addr + a else target_addr; + const narrowed = @truncate(u12, ta); + log.debug(" | narrowed 0x{x}", .{narrowed}); + log.debug(" | parsed.size 0x{x}", .{parsed.size}); + + if (rel_type == .ARM64_RELOC_GOT_LOAD_PAGEOFF12) blk: { + const data_const_seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const got = data_const_seg.sections.items[self.got_section_index.?]; + if (got.addr <= target_addr and target_addr < got.addr + got.size) break :blk; + + log.debug(" | rewriting to add", .{}); + mem.writeIntLittle(u32, inst, aarch64.Instruction.add( + @intToEnum(aarch64.Register, parsed.rt), + @intToEnum(aarch64.Register, parsed.rn), + narrowed, + false, + ).toU32()); + addend = null; + continue; + } + + const offset: u12 = blk: { + if (parsed.size == 0) { + if (parsed.v == 1) { + // 128-bit SIMD is scaled by 16. + break :blk try math.divExact(u12, narrowed, 16); + } + // Otherwise, 8-bit SIMD or ldrb. + break :blk narrowed; + } else { + const denom: u4 = try math.powi(u4, 2, parsed.size); + break :blk try math.divExact(u12, narrowed, denom); + } + }; + parsed.offset = offset; + } + addend = null; + }, + .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => { + const RegInfo = struct { + rd: u5, + rn: u5, + size: u1, + }; + const inst = code[off..][0..4]; + const parsed: RegInfo = blk: { + if (aarch64IsArithmetic(inst)) { + const curr = mem.bytesAsValue( + meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.AddSubtractImmediate, + ), + inst, + ); + break :blk .{ .rd = curr.rd, .rn = curr.rn, .size = curr.sf }; + } else { + const curr = mem.bytesAsValue( + meta.TagPayload( + aarch64.Instruction, + aarch64.Instruction.LoadStoreRegister, + ), + inst, + ); + break :blk .{ .rd = curr.rt, .rn = curr.rn, .size = @truncate(u1, curr.size) }; + } + }; + const ta = if (addend) |a| target_addr + a else target_addr; + const narrowed = @truncate(u12, ta); + log.debug(" | rewriting TLV access to ADD opcode", .{}); + // For TLV, we always generate an add instruction. + mem.writeIntLittle(u32, inst, aarch64.Instruction.add( + @intToEnum(aarch64.Register, parsed.rd), + @intToEnum(aarch64.Register, parsed.rn), + narrowed, + false, + ).toU32()); + }, + .ARM64_RELOC_SUBTRACTOR => { + sub = @intCast(i64, target_addr); + }, + .ARM64_RELOC_UNSIGNED => { + switch (rel.r_length) { + 3 => { + const inst = code[off..][0..8]; + const offset = mem.readIntLittle(i64, inst); + log.debug(" | calculated addend 0x{x}", .{offset}); + const result = if (sub) |s| + @intCast(i64, target_addr) - s + offset + else + @intCast(i64, target_addr) + offset; + mem.writeIntLittle(u64, inst, @bitCast(u64, result)); + sub = null; + + rebases: { + var hit: bool = false; + if (target_mapping.target_seg_id == self.data_segment_cmd_index.?) { + if (self.data_section_index) |index| { + if (index == target_mapping.target_sect_id) hit = true; + } + } + if (target_mapping.target_seg_id == self.data_const_segment_cmd_index.?) { + if (self.data_const_section_index) |index| { + if (index == target_mapping.target_sect_id) hit = true; + } + } + + if (!hit) break :rebases; + + try self.local_rebases.append(self.allocator, .{ + .offset = this_addr - target_seg.inner.vmaddr, + .segment_id = target_mapping.target_seg_id, + }); + } + // TLV is handled via a separate offset mechanism. + // Calculate the offset to the initializer. + if (target_sect.flags == macho.S_THREAD_LOCAL_VARIABLES) tlv: { + assert(rel.r_extern == 1); + const sym = object.symtab.items[rel.r_symbolnum]; + if (isImport(&sym)) break :tlv; + + const base_addr = blk: { + if (self.tlv_data_section_index) |index| { + const tlv_data = target_seg.sections.items[index]; + break :blk tlv_data.addr; + } else { + const tlv_bss = target_seg.sections.items[self.tlv_bss_section_index.?]; + break :blk tlv_bss.addr; + } + }; + // Since we require TLV data to always preceed TLV bss section, we calculate + // offsets wrt to the former if it is defined; otherwise, wrt to the latter. + try self.threadlocal_offsets.append(self.allocator, target_addr - base_addr); + } + }, + 2 => { + const inst = code[off..][0..4]; + const offset = mem.readIntLittle(i32, inst); + log.debug(" | calculated addend 0x{x}", .{offset}); + const result = if (sub) |s| + @intCast(i64, target_addr) - s + offset + else + @intCast(i64, target_addr) + offset; + mem.writeIntLittle(u32, inst, @truncate(u32, @bitCast(u64, result))); + sub = null; + }, + else => |len| { + log.err("unexpected relocation length 0x{x}", .{len}); + return error.UnexpectedRelocationLength; + }, + } + }, + .ARM64_RELOC_POINTER_TO_GOT => return error.TODOArm64RelocPointerToGot, + else => unreachable, + } + }, + else => unreachable, + } + } + + log.debug("writing contents of '{s},{s}' section from '{s}' from 0x{x} to 0x{x}", .{ + segname, + sectname, + object.name, + target_sect_off, + target_sect_off + code.len, + }); + + if (target_sect.flags == macho.S_ZEROFILL or + target_sect.flags == macho.S_THREAD_LOCAL_ZEROFILL or + target_sect.flags == macho.S_THREAD_LOCAL_VARIABLES) + { + log.debug("zeroing out '{s},{s}' from 0x{x} to 0x{x}", .{ + parseName(&target_sect.segname), + parseName(&target_sect.sectname), + target_sect_off, + target_sect_off + code.len, + }); + // Zero-out the space + var zeroes = try self.allocator.alloc(u8, code.len); + defer self.allocator.free(zeroes); + mem.set(u8, zeroes, 0); + try self.file.?.pwriteAll(zeroes, target_sect_off); + } else { + try self.file.?.pwriteAll(code, target_sect_off); + } + } + } +} + +fn relocTargetAddr(self: *Zld, object_id: u16, rel: macho.relocation_info) !u64 { + const object = self.objects.items[object_id]; + const seg = object.load_commands.items[object.segment_cmd_index.?].Segment; + const target_addr = blk: { + if (rel.r_extern == 1) { + const sym = object.symtab.items[rel.r_symbolnum]; + if (isLocal(&sym) or isExport(&sym)) { + // Relocate using section offsets only. + const target_mapping = self.mappings.get(.{ + .object_id = object_id, + .source_sect_id = sym.n_sect - 1, + }) orelse unreachable; + const source_sect = seg.sections.items[target_mapping.source_sect_id]; + const target_seg = self.load_commands.items[target_mapping.target_seg_id].Segment; + const target_sect = target_seg.sections.items[target_mapping.target_sect_id]; + const target_sect_addr = target_sect.addr + target_mapping.offset; + log.debug(" | symbol local to object", .{}); + break :blk target_sect_addr + sym.n_value - source_sect.addr; + } else if (isImport(&sym)) { + // Relocate to either the artifact's local symbol, or an import from + // shared library. + const sym_name = object.getString(sym.n_strx); + if (self.locals.get(sym_name)) |locs| { + var n_value: ?u64 = null; + for (locs.items) |loc| { + switch (loc.tt) { + .Global => { + n_value = loc.inner.n_value; + break; + }, + .WeakGlobal => { + n_value = loc.inner.n_value; + }, + .Local => {}, + } + } + if (n_value) |v| { + break :blk v; + } + log.err("local symbol export '{s}' not found", .{sym_name}); + return error.LocalSymbolExportNotFound; + } else if (self.lazy_imports.get(sym_name)) |ext| { + const segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const stubs = segment.sections.items[self.stubs_section_index.?]; + break :blk stubs.addr + ext.index * stubs.reserved2; + } else if (self.nonlazy_imports.get(sym_name)) |ext| { + const segment = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const got = segment.sections.items[self.got_section_index.?]; + break :blk got.addr + ext.index * @sizeOf(u64); + } else if (mem.eql(u8, sym_name, "__tlv_bootstrap")) { + const segment = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const tlv = segment.sections.items[self.tlv_section_index.?]; + break :blk tlv.addr + self.tlv_bootstrap.?.index * @sizeOf(u64); + } else { + log.err("failed to resolve symbol '{s}' as a relocation target", .{sym_name}); + return error.FailedToResolveRelocationTarget; + } + } else { + log.err("unexpected symbol {}, {s}", .{ sym, object.getString(sym.n_strx) }); + return error.UnexpectedSymbolWhenRelocating; + } + } else { + // TODO I think we need to reparse the relocation_info as scattered_relocation_info + // here to get the actual section plus offset into that section of the relocated + // symbol. Unless the fine-grained location is encoded within the cell in the code + // buffer? + const target_mapping = self.mappings.get(.{ + .object_id = object_id, + .source_sect_id = @intCast(u16, rel.r_symbolnum - 1), + }) orelse unreachable; + const target_seg = self.load_commands.items[target_mapping.target_seg_id].Segment; + const target_sect = target_seg.sections.items[target_mapping.target_sect_id]; + break :blk target_sect.addr + target_mapping.offset; + } + }; + return target_addr; +} + +fn populateMetadata(self: *Zld) !void { + if (self.pagezero_segment_cmd_index == null) { + self.pagezero_segment_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .Segment = SegmentCommand.empty(.{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = @sizeOf(macho.segment_command_64), + .segname = makeStaticString("__PAGEZERO"), + .vmaddr = 0, + .vmsize = 0x100000000, // size always set to 4GB + .fileoff = 0, + .filesize = 0, + .maxprot = 0, + .initprot = 0, + .nsects = 0, + .flags = 0, + }), + }); + } + + if (self.text_segment_cmd_index == null) { + self.text_segment_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .Segment = SegmentCommand.empty(.{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = @sizeOf(macho.segment_command_64), + .segname = makeStaticString("__TEXT"), + .vmaddr = 0x100000000, // always starts at 4GB + .vmsize = 0, + .fileoff = 0, + .filesize = 0, + .maxprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE, + .initprot = macho.VM_PROT_READ | macho.VM_PROT_EXECUTE, + .nsects = 0, + .flags = 0, + }), + }); + } + + if (self.text_section_index == null) { + const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + self.text_section_index = @intCast(u16, text_seg.sections.items.len); + const alignment: u2 = switch (self.arch.?) { + .x86_64 => 0, + .aarch64 => 2, + else => unreachable, // unhandled architecture type + }; + try text_seg.addSection(self.allocator, .{ + .sectname = makeStaticString("__text"), + .segname = makeStaticString("__TEXT"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = alignment, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + } + + if (self.stubs_section_index == null) { + const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + self.stubs_section_index = @intCast(u16, text_seg.sections.items.len); + const alignment: u2 = switch (self.arch.?) { + .x86_64 => 0, + .aarch64 => 2, + else => unreachable, // unhandled architecture type + }; + const stub_size: u4 = switch (self.arch.?) { + .x86_64 => 6, + .aarch64 => 3 * @sizeOf(u32), + else => unreachable, // unhandled architecture type + }; + try text_seg.addSection(self.allocator, .{ + .sectname = makeStaticString("__stubs"), + .segname = makeStaticString("__TEXT"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = alignment, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_SYMBOL_STUBS | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS, + .reserved1 = 0, + .reserved2 = stub_size, + .reserved3 = 0, + }); + } + + if (self.stub_helper_section_index == null) { + const text_seg = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + self.stub_helper_section_index = @intCast(u16, text_seg.sections.items.len); + const alignment: u2 = switch (self.arch.?) { + .x86_64 => 0, + .aarch64 => 2, + else => unreachable, // unhandled architecture type + }; + const stub_helper_size: u6 = switch (self.arch.?) { + .x86_64 => 15, + .aarch64 => 6 * @sizeOf(u32), + else => unreachable, + }; + try text_seg.addSection(self.allocator, .{ + .sectname = makeStaticString("__stub_helper"), + .segname = makeStaticString("__TEXT"), + .addr = 0, + .size = stub_helper_size, + .offset = 0, + .@"align" = alignment, + .reloff = 0, + .nreloc = 0, + .flags = macho.S_REGULAR | macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + } + + if (self.data_const_segment_cmd_index == null) { + self.data_const_segment_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .Segment = SegmentCommand.empty(.{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = @sizeOf(macho.segment_command_64), + .segname = makeStaticString("__DATA_CONST"), + .vmaddr = 0, + .vmsize = 0, + .fileoff = 0, + .filesize = 0, + .maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE, + .initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE, + .nsects = 0, + .flags = 0, + }), + }); + } + + if (self.got_section_index == null) { + const data_const_seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + self.got_section_index = @intCast(u16, data_const_seg.sections.items.len); + try data_const_seg.addSection(self.allocator, .{ + .sectname = makeStaticString("__got"), + .segname = makeStaticString("__DATA_CONST"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 3, // 2^3 = @sizeOf(u64) + .reloff = 0, + .nreloc = 0, + .flags = macho.S_NON_LAZY_SYMBOL_POINTERS, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + } + + if (self.data_segment_cmd_index == null) { + self.data_segment_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .Segment = SegmentCommand.empty(.{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = @sizeOf(macho.segment_command_64), + .segname = makeStaticString("__DATA"), + .vmaddr = 0, + .vmsize = 0, + .fileoff = 0, + .filesize = 0, + .maxprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE, + .initprot = macho.VM_PROT_READ | macho.VM_PROT_WRITE, + .nsects = 0, + .flags = 0, + }), + }); + } + + if (self.la_symbol_ptr_section_index == null) { + const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + self.la_symbol_ptr_section_index = @intCast(u16, data_seg.sections.items.len); + try data_seg.addSection(self.allocator, .{ + .sectname = makeStaticString("__la_symbol_ptr"), + .segname = makeStaticString("__DATA"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 3, // 2^3 = @sizeOf(u64) + .reloff = 0, + .nreloc = 0, + .flags = macho.S_LAZY_SYMBOL_POINTERS, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + } + + if (self.data_section_index == null) { + const data_seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + self.data_section_index = @intCast(u16, data_seg.sections.items.len); + try data_seg.addSection(self.allocator, .{ + .sectname = makeStaticString("__data"), + .segname = makeStaticString("__DATA"), + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = 3, // 2^3 = @sizeOf(u64) + .reloff = 0, + .nreloc = 0, + .flags = macho.S_REGULAR, + .reserved1 = 0, + .reserved2 = 0, + .reserved3 = 0, + }); + } + + if (self.linkedit_segment_cmd_index == null) { + self.linkedit_segment_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .Segment = SegmentCommand.empty(.{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = @sizeOf(macho.segment_command_64), + .segname = makeStaticString("__LINKEDIT"), + .vmaddr = 0, + .vmsize = 0, + .fileoff = 0, + .filesize = 0, + .maxprot = macho.VM_PROT_READ, + .initprot = macho.VM_PROT_READ, + .nsects = 0, + .flags = 0, + }), + }); + } + + if (self.dyld_info_cmd_index == null) { + self.dyld_info_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .DyldInfoOnly = .{ + .cmd = macho.LC_DYLD_INFO_ONLY, + .cmdsize = @sizeOf(macho.dyld_info_command), + .rebase_off = 0, + .rebase_size = 0, + .bind_off = 0, + .bind_size = 0, + .weak_bind_off = 0, + .weak_bind_size = 0, + .lazy_bind_off = 0, + .lazy_bind_size = 0, + .export_off = 0, + .export_size = 0, + }, + }); + } + + if (self.symtab_cmd_index == null) { + self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .Symtab = .{ + .cmd = macho.LC_SYMTAB, + .cmdsize = @sizeOf(macho.symtab_command), + .symoff = 0, + .nsyms = 0, + .stroff = 0, + .strsize = 0, + }, + }); + try self.strtab.append(self.allocator, 0); + } + + if (self.dysymtab_cmd_index == null) { + self.dysymtab_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .Dysymtab = .{ + .cmd = macho.LC_DYSYMTAB, + .cmdsize = @sizeOf(macho.dysymtab_command), + .ilocalsym = 0, + .nlocalsym = 0, + .iextdefsym = 0, + .nextdefsym = 0, + .iundefsym = 0, + .nundefsym = 0, + .tocoff = 0, + .ntoc = 0, + .modtaboff = 0, + .nmodtab = 0, + .extrefsymoff = 0, + .nextrefsyms = 0, + .indirectsymoff = 0, + .nindirectsyms = 0, + .extreloff = 0, + .nextrel = 0, + .locreloff = 0, + .nlocrel = 0, + }, + }); + } + + if (self.dylinker_cmd_index == null) { + self.dylinker_cmd_index = @intCast(u16, self.load_commands.items.len); + const cmdsize = @intCast(u32, mem.alignForwardGeneric( + u64, + @sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH), + @sizeOf(u64), + )); + var dylinker_cmd = emptyGenericCommandWithData(macho.dylinker_command{ + .cmd = macho.LC_LOAD_DYLINKER, + .cmdsize = cmdsize, + .name = @sizeOf(macho.dylinker_command), + }); + dylinker_cmd.data = try self.allocator.alloc(u8, cmdsize - dylinker_cmd.inner.name); + mem.set(u8, dylinker_cmd.data, 0); + mem.copy(u8, dylinker_cmd.data, mem.spanZ(DEFAULT_DYLD_PATH)); + try self.load_commands.append(self.allocator, .{ .Dylinker = dylinker_cmd }); + } + + if (self.libsystem_cmd_index == null) { + self.libsystem_cmd_index = @intCast(u16, self.load_commands.items.len); + const cmdsize = @intCast(u32, mem.alignForwardGeneric( + u64, + @sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH), + @sizeOf(u64), + )); + // TODO Find a way to work out runtime version from the OS version triple stored in std.Target. + // In the meantime, we're gonna hardcode to the minimum compatibility version of 0.0.0. + const min_version = 0x0; + var dylib_cmd = emptyGenericCommandWithData(macho.dylib_command{ + .cmd = macho.LC_LOAD_DYLIB, + .cmdsize = cmdsize, + .dylib = .{ + .name = @sizeOf(macho.dylib_command), + .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files + .current_version = min_version, + .compatibility_version = min_version, + }, + }); + dylib_cmd.data = try self.allocator.alloc(u8, cmdsize - dylib_cmd.inner.dylib.name); + mem.set(u8, dylib_cmd.data, 0); + mem.copy(u8, dylib_cmd.data, mem.spanZ(LIB_SYSTEM_PATH)); + try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd }); + } + + if (self.main_cmd_index == null) { + self.main_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .Main = .{ + .cmd = macho.LC_MAIN, + .cmdsize = @sizeOf(macho.entry_point_command), + .entryoff = 0x0, + .stacksize = 0, + }, + }); + } + + if (self.source_version_cmd_index == null) { + self.source_version_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .SourceVersion = .{ + .cmd = macho.LC_SOURCE_VERSION, + .cmdsize = @sizeOf(macho.source_version_command), + .version = 0x0, + }, + }); + } + + if (self.uuid_cmd_index == null) { + self.uuid_cmd_index = @intCast(u16, self.load_commands.items.len); + var uuid_cmd: macho.uuid_command = .{ + .cmd = macho.LC_UUID, + .cmdsize = @sizeOf(macho.uuid_command), + .uuid = undefined, + }; + std.crypto.random.bytes(&uuid_cmd.uuid); + try self.load_commands.append(self.allocator, .{ .Uuid = uuid_cmd }); + } + + if (self.code_signature_cmd_index == null and self.arch.? == .aarch64) { + self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .LinkeditData = .{ + .cmd = macho.LC_CODE_SIGNATURE, + .cmdsize = @sizeOf(macho.linkedit_data_command), + .dataoff = 0, + .datasize = 0, + }, + }); + } + + if (self.data_in_code_cmd_index == null and self.arch.? == .x86_64) { + self.data_in_code_cmd_index = @intCast(u16, self.load_commands.items.len); + try self.load_commands.append(self.allocator, .{ + .LinkeditData = .{ + .cmd = macho.LC_DATA_IN_CODE, + .cmdsize = @sizeOf(macho.linkedit_data_command), + .dataoff = 0, + .datasize = 0, + }, + }); + } +} + +fn flush(self: *Zld) !void { + if (self.bss_section_index) |index| { + const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const sect = &seg.sections.items[index]; + sect.offset = 0; + } + + if (self.tlv_bss_section_index) |index| { + const seg = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const sect = &seg.sections.items[index]; + sect.offset = 0; + } + + if (self.tlv_section_index) |index| { + const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const sect = &seg.sections.items[index]; + + var buffer = try self.allocator.alloc(u8, sect.size); + defer self.allocator.free(buffer); + _ = try self.file.?.preadAll(buffer, sect.offset); + + var stream = std.io.fixedBufferStream(buffer); + var writer = stream.writer(); + + const seek_amt = 2 * @sizeOf(u64); + while (self.threadlocal_offsets.popOrNull()) |offset| { + try writer.context.seekBy(seek_amt); + try writer.writeIntLittle(u64, offset); + } + + try self.file.?.pwriteAll(buffer, sect.offset); + } + + try self.setEntryPoint(); + try self.writeRebaseInfoTable(); + try self.writeBindInfoTable(); + try self.writeLazyBindInfoTable(); + try self.writeExportInfo(); + if (self.arch.? == .x86_64) { + try self.writeDataInCode(); + } + + { + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + symtab.symoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); + } + + try self.writeDebugInfo(); + try self.writeSymbolTable(); + try self.writeDynamicSymbolTable(); + try self.writeStringTable(); + + { + // Seal __LINKEDIT size + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size.?); + } + + if (self.arch.? == .aarch64) { + try self.writeCodeSignaturePadding(); + } + + try self.writeLoadCommands(); + try self.writeHeader(); + + if (self.arch.? == .aarch64) { + try self.writeCodeSignature(); + } + + if (comptime std.Target.current.isDarwin() and std.Target.current.cpu.arch == .aarch64) { + try fs.cwd().copyFile(self.out_path.?, fs.cwd(), self.out_path.?, .{}); + } +} + +fn setEntryPoint(self: *Zld) !void { + // TODO we should respect the -entry flag passed in by the user to set a custom + // entrypoint. For now, assume default of `_main`. + const seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const text = seg.sections.items[self.text_section_index.?]; + const entry_syms = self.locals.get("_main") orelse return error.MissingMainEntrypoint; + + var entry_sym: ?macho.nlist_64 = null; + for (entry_syms.items) |es| { + switch (es.tt) { + .Global => { + entry_sym = es.inner; + break; + }, + .WeakGlobal => { + entry_sym = es.inner; + }, + .Local => {}, + } + } + if (entry_sym == null) { + log.err("no (weak) global definition of _main found", .{}); + return error.MissingMainEntrypoint; + } + + const name = try self.allocator.dupe(u8, "_main"); + try self.exports.putNoClobber(self.allocator, name, .{ + .n_strx = entry_sym.?.n_strx, + .n_value = entry_sym.?.n_value, + .n_type = macho.N_SECT | macho.N_EXT, + .n_desc = entry_sym.?.n_desc, + .n_sect = entry_sym.?.n_sect, + }); + + const ec = &self.load_commands.items[self.main_cmd_index.?].Main; + ec.entryoff = @intCast(u32, entry_sym.?.n_value - seg.inner.vmaddr); +} + +fn writeRebaseInfoTable(self: *Zld) !void { + var pointers = std.ArrayList(Pointer).init(self.allocator); + defer pointers.deinit(); + + try pointers.ensureCapacity(pointers.items.len + self.local_rebases.items.len); + pointers.appendSliceAssumeCapacity(self.local_rebases.items); + + if (self.got_section_index) |idx| { + // TODO this should be cleaned up! + try pointers.ensureCapacity(pointers.items.len + self.nonlazy_pointers.items().len); + const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const sect = seg.sections.items[idx]; + const base_offset = sect.addr - seg.inner.vmaddr; + const segment_id = @intCast(u16, self.data_const_segment_cmd_index.?); + const index_offset = @intCast(u32, self.nonlazy_imports.items().len); + for (self.nonlazy_pointers.items()) |entry| { + const index = index_offset + entry.value.index; + pointers.appendAssumeCapacity(.{ + .offset = base_offset + index * @sizeOf(u64), + .segment_id = segment_id, + }); + } + } + + if (self.mod_init_func_section_index) |idx| { + // TODO audit and investigate this. + const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const sect = seg.sections.items[idx]; + const npointers = sect.size * @sizeOf(u64); + const base_offset = sect.addr - seg.inner.vmaddr; + const segment_id = @intCast(u16, self.data_const_segment_cmd_index.?); + + try pointers.ensureCapacity(pointers.items.len + npointers); + var i: usize = 0; + while (i < npointers) : (i += 1) { + pointers.appendAssumeCapacity(.{ + .offset = base_offset + i * @sizeOf(u64), + .segment_id = segment_id, + }); + } + } + + if (self.mod_term_func_section_index) |idx| { + // TODO audit and investigate this. + const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const sect = seg.sections.items[idx]; + const npointers = sect.size * @sizeOf(u64); + const base_offset = sect.addr - seg.inner.vmaddr; + const segment_id = @intCast(u16, self.data_const_segment_cmd_index.?); + + try pointers.ensureCapacity(pointers.items.len + npointers); + var i: usize = 0; + while (i < npointers) : (i += 1) { + pointers.appendAssumeCapacity(.{ + .offset = base_offset + i * @sizeOf(u64), + .segment_id = segment_id, + }); + } + } + + if (self.la_symbol_ptr_section_index) |idx| { + try pointers.ensureCapacity(pointers.items.len + self.lazy_imports.items().len); + const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const sect = seg.sections.items[idx]; + const base_offset = sect.addr - seg.inner.vmaddr; + const segment_id = @intCast(u16, self.data_segment_cmd_index.?); + for (self.lazy_imports.items()) |entry| { + pointers.appendAssumeCapacity(.{ + .offset = base_offset + entry.value.index * @sizeOf(u64), + .segment_id = segment_id, + }); + } + } + + std.sort.sort(Pointer, pointers.items, {}, pointerCmp); + + const size = try rebaseInfoSize(pointers.items); + var buffer = try self.allocator.alloc(u8, @intCast(usize, size)); + defer self.allocator.free(buffer); + + var stream = std.io.fixedBufferStream(buffer); + try writeRebaseInfo(pointers.items, stream.writer()); + + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; + dyld_info.rebase_off = @intCast(u32, seg.inner.fileoff); + dyld_info.rebase_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @sizeOf(u64))); + seg.inner.filesize += dyld_info.rebase_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.file.?.pwriteAll(buffer, dyld_info.rebase_off); +} + +fn writeBindInfoTable(self: *Zld) !void { + var pointers = std.ArrayList(Pointer).init(self.allocator); + defer pointers.deinit(); + + if (self.got_section_index) |idx| { + try pointers.ensureCapacity(pointers.items.len + self.nonlazy_imports.items().len); + const seg = self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const sect = seg.sections.items[idx]; + const base_offset = sect.addr - seg.inner.vmaddr; + const segment_id = @intCast(u16, self.data_const_segment_cmd_index.?); + for (self.nonlazy_imports.items()) |entry| { + pointers.appendAssumeCapacity(.{ + .offset = base_offset + entry.value.index * @sizeOf(u64), + .segment_id = segment_id, + .dylib_ordinal = entry.value.dylib_ordinal, + .name = entry.key, + }); + } + } + + if (self.tlv_section_index) |idx| { + const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const sect = seg.sections.items[idx]; + const base_offset = sect.addr - seg.inner.vmaddr; + const segment_id = @intCast(u16, self.data_segment_cmd_index.?); + try pointers.append(.{ + .offset = base_offset + self.tlv_bootstrap.?.index * @sizeOf(u64), + .segment_id = segment_id, + .dylib_ordinal = self.tlv_bootstrap.?.dylib_ordinal, + .name = "__tlv_bootstrap", + }); + } + + const size = try bindInfoSize(pointers.items); + var buffer = try self.allocator.alloc(u8, @intCast(usize, size)); + defer self.allocator.free(buffer); + + var stream = std.io.fixedBufferStream(buffer); + try writeBindInfo(pointers.items, stream.writer()); + + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; + dyld_info.bind_off = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); + dyld_info.bind_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); + seg.inner.filesize += dyld_info.bind_size; + + log.debug("writing binding info from 0x{x} to 0x{x}", .{ dyld_info.bind_off, dyld_info.bind_off + dyld_info.bind_size }); + + try self.file.?.pwriteAll(buffer, dyld_info.bind_off); +} + +fn writeLazyBindInfoTable(self: *Zld) !void { + var pointers = std.ArrayList(Pointer).init(self.allocator); + defer pointers.deinit(); + try pointers.ensureCapacity(self.lazy_imports.items().len); + + if (self.la_symbol_ptr_section_index) |idx| { + const seg = self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const sect = seg.sections.items[idx]; + const base_offset = sect.addr - seg.inner.vmaddr; + const segment_id = @intCast(u16, self.data_segment_cmd_index.?); + for (self.lazy_imports.items()) |entry| { + pointers.appendAssumeCapacity(.{ + .offset = base_offset + entry.value.index * @sizeOf(u64), + .segment_id = segment_id, + .dylib_ordinal = entry.value.dylib_ordinal, + .name = entry.key, + }); + } + } + + const size = try lazyBindInfoSize(pointers.items); + var buffer = try self.allocator.alloc(u8, @intCast(usize, size)); + defer self.allocator.free(buffer); + + var stream = std.io.fixedBufferStream(buffer); + try writeLazyBindInfo(pointers.items, stream.writer()); + + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; + dyld_info.lazy_bind_off = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); + dyld_info.lazy_bind_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); + seg.inner.filesize += dyld_info.lazy_bind_size; + + log.debug("writing lazy binding info from 0x{x} to 0x{x}", .{ dyld_info.lazy_bind_off, dyld_info.lazy_bind_off + dyld_info.lazy_bind_size }); + + try self.file.?.pwriteAll(buffer, dyld_info.lazy_bind_off); + try self.populateLazyBindOffsetsInStubHelper(buffer); +} + +fn populateLazyBindOffsetsInStubHelper(self: *Zld, buffer: []const u8) !void { + var stream = std.io.fixedBufferStream(buffer); + var reader = stream.reader(); + var offsets = std.ArrayList(u32).init(self.allocator); + try offsets.append(0); + defer offsets.deinit(); + var valid_block = false; + + while (true) { + const inst = reader.readByte() catch |err| switch (err) { + error.EndOfStream => break, + else => return err, + }; + const imm: u8 = inst & macho.BIND_IMMEDIATE_MASK; + const opcode: u8 = inst & macho.BIND_OPCODE_MASK; + + switch (opcode) { + macho.BIND_OPCODE_DO_BIND => { + valid_block = true; + }, + macho.BIND_OPCODE_DONE => { + if (valid_block) { + const offset = try stream.getPos(); + try offsets.append(@intCast(u32, offset)); + } + valid_block = false; + }, + macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => { + var next = try reader.readByte(); + while (next != @as(u8, 0)) { + next = try reader.readByte(); + } + }, + macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => { + _ = try leb.readULEB128(u64, reader); + }, + macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB => { + _ = try leb.readULEB128(u64, reader); + }, + macho.BIND_OPCODE_SET_ADDEND_SLEB => { + _ = try leb.readILEB128(i64, reader); + }, + else => {}, + } + } + assert(self.lazy_imports.items().len <= offsets.items.len); + + const stub_size: u4 = switch (self.arch.?) { + .x86_64 => 10, + .aarch64 => 3 * @sizeOf(u32), + else => unreachable, + }; + const off: u4 = switch (self.arch.?) { + .x86_64 => 1, + .aarch64 => 2 * @sizeOf(u32), + else => unreachable, + }; + var buf: [@sizeOf(u32)]u8 = undefined; + for (self.lazy_imports.items()) |entry| { + const symbol = entry.value; + const placeholder_off = self.stub_helper_stubs_start_off.? + symbol.index * stub_size + off; + mem.writeIntLittle(u32, &buf, offsets.items[symbol.index]); + try self.file.?.pwriteAll(&buf, placeholder_off); + } +} + +fn writeExportInfo(self: *Zld) !void { + var trie = Trie.init(self.allocator); + defer trie.deinit(); + + const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + for (self.exports.items()) |entry| { + const name = entry.key; + const symbol = entry.value; + // TODO figure out if we should put all exports into the export trie + assert(symbol.n_value >= text_segment.inner.vmaddr); + try trie.put(.{ + .name = name, + .vmaddr_offset = symbol.n_value - text_segment.inner.vmaddr, + .export_flags = macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR, + }); + } + + try trie.finalize(); + var buffer = try self.allocator.alloc(u8, @intCast(usize, trie.size)); + defer self.allocator.free(buffer); + var stream = std.io.fixedBufferStream(buffer); + const nwritten = try trie.write(stream.writer()); + assert(nwritten == trie.size); + + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfoOnly; + dyld_info.export_off = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); + dyld_info.export_size = @intCast(u32, mem.alignForwardGeneric(u64, buffer.len, @alignOf(u64))); + seg.inner.filesize += dyld_info.export_size; + + log.debug("writing export info from 0x{x} to 0x{x}", .{ dyld_info.export_off, dyld_info.export_off + dyld_info.export_size }); + + try self.file.?.pwriteAll(buffer, dyld_info.export_off); +} + +fn writeDebugInfo(self: *Zld) !void { + var stabs = std.ArrayList(macho.nlist_64).init(self.allocator); + defer stabs.deinit(); + + for (self.objects.items) |object, object_id| { + var debug_info = blk: { + var di = try DebugInfo.parseFromObject(self.allocator, object); + break :blk di orelse continue; + }; + defer debug_info.deinit(self.allocator); + + // We assume there is only one CU. + const compile_unit = debug_info.inner.findCompileUnit(0x0) catch |err| switch (err) { + error.MissingDebugInfo => { + // TODO audit cases with missing debug info and audit our dwarf.zig module. + log.debug("invalid or missing debug info in {s}; skipping", .{object.name}); + continue; + }, + else => |e| return e, + }; + const name = try compile_unit.die.getAttrString(&debug_info.inner, dwarf.AT_name); + const comp_dir = try compile_unit.die.getAttrString(&debug_info.inner, dwarf.AT_comp_dir); + + { + const tu_path = try std.fs.path.join(self.allocator, &[_][]const u8{ comp_dir, name }); + defer self.allocator.free(tu_path); + const dirname = std.fs.path.dirname(tu_path) orelse "./"; + // Current dir + try stabs.append(.{ + .n_strx = try self.makeString(tu_path[0 .. dirname.len + 1]), + .n_type = macho.N_SO, + .n_sect = 0, + .n_desc = 0, + .n_value = 0, + }); + // Artifact name + try stabs.append(.{ + .n_strx = try self.makeString(tu_path[dirname.len + 1 ..]), + .n_type = macho.N_SO, + .n_sect = 0, + .n_desc = 0, + .n_value = 0, + }); + // Path to object file with debug info + var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; + const full_path = blk: { + if (object.ar_name) |prefix| { + const path = try std.os.realpath(prefix, &buffer); + break :blk try std.fmt.allocPrint(self.allocator, "{s}({s})", .{ path, object.name }); + } else { + const path = try std.os.realpath(object.name, &buffer); + break :blk try mem.dupe(self.allocator, u8, path); + } + }; + defer self.allocator.free(full_path); + const stat = try object.file.stat(); + const mtime = @intCast(u64, @divFloor(stat.mtime, 1_000_000_000)); + try stabs.append(.{ + .n_strx = try self.makeString(full_path), + .n_type = macho.N_OSO, + .n_sect = 0, + .n_desc = 1, + .n_value = mtime, + }); + } + log.debug("analyzing debug info in '{s}'", .{object.name}); + + for (object.symtab.items) |source_sym| { + const symname = object.getString(source_sym.n_strx); + const source_addr = source_sym.n_value; + const target_syms = self.locals.get(symname) orelse continue; + const target_sym: Symbol = blk: { + for (target_syms.items) |ts| { + if (ts.object_id == @intCast(u16, object_id)) break :blk ts; + } else continue; + }; + + const maybe_size = blk: for (debug_info.inner.func_list.items) |func| { + if (func.pc_range) |range| { + if (source_addr >= range.start and source_addr < range.end) { + break :blk range.end - range.start; + } + } + } else null; + + if (maybe_size) |size| { + try stabs.append(.{ + .n_strx = 0, + .n_type = macho.N_BNSYM, + .n_sect = target_sym.inner.n_sect, + .n_desc = 0, + .n_value = target_sym.inner.n_value, + }); + try stabs.append(.{ + .n_strx = target_sym.inner.n_strx, + .n_type = macho.N_FUN, + .n_sect = target_sym.inner.n_sect, + .n_desc = 0, + .n_value = target_sym.inner.n_value, + }); + try stabs.append(.{ + .n_strx = 0, + .n_type = macho.N_FUN, + .n_sect = 0, + .n_desc = 0, + .n_value = size, + }); + try stabs.append(.{ + .n_strx = 0, + .n_type = macho.N_ENSYM, + .n_sect = target_sym.inner.n_sect, + .n_desc = 0, + .n_value = size, + }); + } else { + // TODO need a way to differentiate symbols: global, static, local, etc. + try stabs.append(.{ + .n_strx = target_sym.inner.n_strx, + .n_type = macho.N_STSYM, + .n_sect = target_sym.inner.n_sect, + .n_desc = 0, + .n_value = target_sym.inner.n_value, + }); + } + } + + // Close the source file! + try stabs.append(.{ + .n_strx = 0, + .n_type = macho.N_SO, + .n_sect = 0, + .n_desc = 0, + .n_value = 0, + }); + } + + if (stabs.items.len == 0) return; + + // Write stabs into the symbol table + const linkedit = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + + symtab.nsyms = @intCast(u32, stabs.items.len); + + const stabs_off = symtab.symoff; + const stabs_size = symtab.nsyms * @sizeOf(macho.nlist_64); + log.debug("writing symbol stabs from 0x{x} to 0x{x}", .{ stabs_off, stabs_size + stabs_off }); + try self.file.?.pwriteAll(mem.sliceAsBytes(stabs.items), stabs_off); + + linkedit.inner.filesize += stabs_size; + + // Update dynamic symbol table. + const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; + dysymtab.nlocalsym = symtab.nsyms; +} + +fn writeSymbolTable(self: *Zld) !void { + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + + var locals = std.ArrayList(macho.nlist_64).init(self.allocator); + defer locals.deinit(); + + for (self.locals.items()) |entries| { + log.debug("'{s}': {} entries", .{ entries.key, entries.value.items.len }); + // var symbol: ?macho.nlist_64 = null; + for (entries.value.items) |entry| { + log.debug(" | {}", .{entry.inner}); + log.debug(" | {}", .{entry.tt}); + log.debug(" | {s}", .{self.objects.items[entry.object_id].name}); + try locals.append(entry.inner); + } + } + const nlocals = locals.items.len; + + const nexports = self.exports.items().len; + var exports = std.ArrayList(macho.nlist_64).init(self.allocator); + defer exports.deinit(); + + try exports.ensureCapacity(nexports); + for (self.exports.items()) |entry| { + exports.appendAssumeCapacity(entry.value); + } + + const has_tlv: bool = self.tlv_bootstrap != null; + + var nundefs = self.lazy_imports.items().len + self.nonlazy_imports.items().len; + if (has_tlv) nundefs += 1; + + var undefs = std.ArrayList(macho.nlist_64).init(self.allocator); + defer undefs.deinit(); + + try undefs.ensureCapacity(nundefs); + for (self.lazy_imports.items()) |entry| { + undefs.appendAssumeCapacity(entry.value.symbol); + } + for (self.nonlazy_imports.items()) |entry| { + undefs.appendAssumeCapacity(entry.value.symbol); + } + if (has_tlv) { + undefs.appendAssumeCapacity(self.tlv_bootstrap.?.symbol); + } + + const locals_off = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64); + const locals_size = nlocals * @sizeOf(macho.nlist_64); + log.debug("writing local symbols from 0x{x} to 0x{x}", .{ locals_off, locals_size + locals_off }); + try self.file.?.pwriteAll(mem.sliceAsBytes(locals.items), locals_off); + + const exports_off = locals_off + locals_size; + const exports_size = nexports * @sizeOf(macho.nlist_64); + log.debug("writing exported symbols from 0x{x} to 0x{x}", .{ exports_off, exports_size + exports_off }); + try self.file.?.pwriteAll(mem.sliceAsBytes(exports.items), exports_off); + + const undefs_off = exports_off + exports_size; + const undefs_size = nundefs * @sizeOf(macho.nlist_64); + log.debug("writing undefined symbols from 0x{x} to 0x{x}", .{ undefs_off, undefs_size + undefs_off }); + try self.file.?.pwriteAll(mem.sliceAsBytes(undefs.items), undefs_off); + + symtab.nsyms += @intCast(u32, nlocals + nexports + nundefs); + seg.inner.filesize += locals_size + exports_size + undefs_size; + + // Update dynamic symbol table. + const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; + dysymtab.nlocalsym += @intCast(u32, nlocals); + dysymtab.iextdefsym = dysymtab.nlocalsym; + dysymtab.nextdefsym = @intCast(u32, nexports); + dysymtab.iundefsym = dysymtab.nlocalsym + dysymtab.nextdefsym; + dysymtab.nundefsym = @intCast(u32, nundefs); +} + +fn writeDynamicSymbolTable(self: *Zld) !void { + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const stubs = &text_segment.sections.items[self.stubs_section_index.?]; + const data_const_segment = &self.load_commands.items[self.data_const_segment_cmd_index.?].Segment; + const got = &data_const_segment.sections.items[self.got_section_index.?]; + const data_segment = &self.load_commands.items[self.data_segment_cmd_index.?].Segment; + const la_symbol_ptr = &data_segment.sections.items[self.la_symbol_ptr_section_index.?]; + const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].Dysymtab; + + const lazy = self.lazy_imports.items(); + const nonlazy = self.nonlazy_imports.items(); + const got_locals = self.nonlazy_pointers.items(); + dysymtab.indirectsymoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); + dysymtab.nindirectsyms = @intCast(u32, lazy.len * 2 + nonlazy.len + got_locals.len); + const needed_size = dysymtab.nindirectsyms * @sizeOf(u32); + seg.inner.filesize += needed_size; + + log.debug("writing indirect symbol table from 0x{x} to 0x{x}", .{ + dysymtab.indirectsymoff, + dysymtab.indirectsymoff + needed_size, + }); + + var buf = try self.allocator.alloc(u8, needed_size); + defer self.allocator.free(buf); + var stream = std.io.fixedBufferStream(buf); + var writer = stream.writer(); + + stubs.reserved1 = 0; + for (lazy) |_, i| { + const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); + try writer.writeIntLittle(u32, symtab_idx); + } + + const base_id = @intCast(u32, lazy.len); + got.reserved1 = base_id; + for (nonlazy) |_, i| { + const symtab_idx = @intCast(u32, dysymtab.iundefsym + i + base_id); + try writer.writeIntLittle(u32, symtab_idx); + } + // TODO there should be one common set of GOT entries. + for (got_locals) |_| { + try writer.writeIntLittle(u32, macho.INDIRECT_SYMBOL_LOCAL); + } + + la_symbol_ptr.reserved1 = got.reserved1 + @intCast(u32, nonlazy.len) + @intCast(u32, got_locals.len); + for (lazy) |_, i| { + const symtab_idx = @intCast(u32, dysymtab.iundefsym + i); + try writer.writeIntLittle(u32, symtab_idx); + } + + try self.file.?.pwriteAll(buf, dysymtab.indirectsymoff); +} + +fn writeStringTable(self: *Zld) !void { + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; + symtab.stroff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); + symtab.strsize = @intCast(u32, mem.alignForwardGeneric(u64, self.strtab.items.len, @alignOf(u64))); + seg.inner.filesize += symtab.strsize; + + log.debug("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize }); + + try self.file.?.pwriteAll(self.strtab.items, symtab.stroff); + + if (symtab.strsize > self.strtab.items.len and self.arch.? == .x86_64) { + // This is the last section, so we need to pad it out. + try self.file.?.pwriteAll(&[_]u8{0}, seg.inner.fileoff + seg.inner.filesize - 1); + } +} + +fn writeDataInCode(self: *Zld) !void { + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const dice_cmd = &self.load_commands.items[self.data_in_code_cmd_index.?].LinkeditData; + const fileoff = seg.inner.fileoff + seg.inner.filesize; + + var buf = std.ArrayList(u8).init(self.allocator); + defer buf.deinit(); + + const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const text_sect = text_seg.sections.items[self.text_section_index.?]; + for (self.objects.items) |object, object_id| { + const source_seg = object.load_commands.items[object.segment_cmd_index.?].Segment; + const source_sect = source_seg.sections.items[object.text_section_index.?]; + const target_mapping = self.mappings.get(.{ + .object_id = @intCast(u16, object_id), + .source_sect_id = object.text_section_index.?, + }) orelse continue; + + try buf.ensureCapacity( + buf.items.len + object.data_in_code_entries.items.len * @sizeOf(macho.data_in_code_entry), + ); + for (object.data_in_code_entries.items) |dice| { + const new_dice: macho.data_in_code_entry = .{ + .offset = text_sect.offset + target_mapping.offset + dice.offset, + .length = dice.length, + .kind = dice.kind, + }; + buf.appendSliceAssumeCapacity(mem.asBytes(&new_dice)); + } + } + const datasize = @intCast(u32, buf.items.len); + + dice_cmd.dataoff = @intCast(u32, fileoff); + dice_cmd.datasize = datasize; + seg.inner.filesize += datasize; + + log.debug("writing data-in-code from 0x{x} to 0x{x}", .{ fileoff, fileoff + datasize }); + + try self.file.?.pwriteAll(buf.items, fileoff); +} + +fn writeCodeSignaturePadding(self: *Zld) !void { + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; + const fileoff = seg.inner.fileoff + seg.inner.filesize; + const needed_size = CodeSignature.calcCodeSignaturePaddingSize( + self.out_path.?, + fileoff, + self.page_size.?, + ); + code_sig_cmd.dataoff = @intCast(u32, fileoff); + code_sig_cmd.datasize = needed_size; + + // Advance size of __LINKEDIT segment + seg.inner.filesize += needed_size; + seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size.?); + + log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ fileoff, fileoff + needed_size }); + + // Pad out the space. We need to do this to calculate valid hashes for everything in the file + // except for code signature data. + try self.file.?.pwriteAll(&[_]u8{0}, fileoff + needed_size - 1); +} + +fn writeCodeSignature(self: *Zld) !void { + const text_seg = self.load_commands.items[self.text_segment_cmd_index.?].Segment; + const code_sig_cmd = self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; + + var code_sig = CodeSignature.init(self.allocator, self.page_size.?); + defer code_sig.deinit(); + try code_sig.calcAdhocSignature( + self.file.?, + self.out_path.?, + text_seg.inner, + code_sig_cmd, + .Exe, + ); + + var buffer = try self.allocator.alloc(u8, code_sig.size()); + defer self.allocator.free(buffer); + var stream = std.io.fixedBufferStream(buffer); + try code_sig.write(stream.writer()); + + log.debug("writing code signature from 0x{x} to 0x{x}", .{ code_sig_cmd.dataoff, code_sig_cmd.dataoff + buffer.len }); + + try self.file.?.pwriteAll(buffer, code_sig_cmd.dataoff); +} + +fn writeLoadCommands(self: *Zld) !void { + var sizeofcmds: u32 = 0; + for (self.load_commands.items) |lc| { + sizeofcmds += lc.cmdsize(); + } + + var buffer = try self.allocator.alloc(u8, sizeofcmds); + defer self.allocator.free(buffer); + var writer = std.io.fixedBufferStream(buffer).writer(); + for (self.load_commands.items) |lc| { + try lc.write(writer); + } + + const off = @sizeOf(macho.mach_header_64); + log.debug("writing {} load commands from 0x{x} to 0x{x}", .{ self.load_commands.items.len, off, off + sizeofcmds }); + try self.file.?.pwriteAll(buffer, off); +} + +fn writeHeader(self: *Zld) !void { + var header: macho.mach_header_64 = undefined; + header.magic = macho.MH_MAGIC_64; + + const CpuInfo = struct { + cpu_type: macho.cpu_type_t, + cpu_subtype: macho.cpu_subtype_t, + }; + + const cpu_info: CpuInfo = switch (self.arch.?) { + .aarch64 => .{ + .cpu_type = macho.CPU_TYPE_ARM64, + .cpu_subtype = macho.CPU_SUBTYPE_ARM_ALL, + }, + .x86_64 => .{ + .cpu_type = macho.CPU_TYPE_X86_64, + .cpu_subtype = macho.CPU_SUBTYPE_X86_64_ALL, + }, + else => return error.UnsupportedCpuArchitecture, + }; + header.cputype = cpu_info.cpu_type; + header.cpusubtype = cpu_info.cpu_subtype; + header.filetype = macho.MH_EXECUTE; + header.flags = macho.MH_NOUNDEFS | macho.MH_DYLDLINK | macho.MH_PIE | macho.MH_TWOLEVEL; + header.reserved = 0; + + if (self.tlv_section_index) |_| + header.flags |= macho.MH_HAS_TLV_DESCRIPTORS; + + header.ncmds = @intCast(u32, self.load_commands.items.len); + header.sizeofcmds = 0; + for (self.load_commands.items) |cmd| { + header.sizeofcmds += cmd.cmdsize(); + } + log.debug("writing Mach-O header {}", .{header}); + try self.file.?.pwriteAll(mem.asBytes(&header), 0); +} + +pub fn makeStaticString(bytes: []const u8) [16]u8 { + var buf = [_]u8{0} ** 16; + assert(bytes.len <= buf.len); + mem.copy(u8, &buf, bytes); + return buf; +} + +fn makeString(self: *Zld, bytes: []const u8) !u32 { + try self.strtab.ensureCapacity(self.allocator, self.strtab.items.len + bytes.len + 1); + const offset = @intCast(u32, self.strtab.items.len); + log.debug("writing new string '{s}' into string table at offset 0x{x}", .{ bytes, offset }); + self.strtab.appendSliceAssumeCapacity(bytes); + self.strtab.appendAssumeCapacity(0); + return offset; +} + +fn getString(self: *const Zld, str_off: u32) []const u8 { + assert(str_off < self.strtab.items.len); + return mem.spanZ(@ptrCast([*:0]const u8, self.strtab.items.ptr + str_off)); +} + +pub fn parseName(name: *const [16]u8) []const u8 { + const len = mem.indexOfScalar(u8, name, @as(u8, 0)) orelse name.len; + return name[0..len]; +} + +fn isLocal(sym: *const macho.nlist_64) callconv(.Inline) bool { + if (isExtern(sym)) return false; + const tt = macho.N_TYPE & sym.n_type; + return tt == macho.N_SECT; +} + +fn isExport(sym: *const macho.nlist_64) callconv(.Inline) bool { + if (!isExtern(sym)) return false; + const tt = macho.N_TYPE & sym.n_type; + return tt == macho.N_SECT; +} + +fn isImport(sym: *const macho.nlist_64) callconv(.Inline) bool { + if (!isExtern(sym)) return false; + const tt = macho.N_TYPE & sym.n_type; + return tt == macho.N_UNDF; +} + +fn isExtern(sym: *const macho.nlist_64) callconv(.Inline) bool { + if ((sym.n_type & macho.N_EXT) == 0) return false; + return (sym.n_type & macho.N_PEXT) == 0; +} + +fn isWeakDef(sym: *const macho.nlist_64) callconv(.Inline) bool { + return (sym.n_desc & macho.N_WEAK_DEF) != 0; +} + +fn aarch64IsArithmetic(inst: *const [4]u8) callconv(.Inline) bool { + const group_decode = @truncate(u5, inst[3]); + return ((group_decode >> 2) == 4); +} diff --git a/src/link/MachO/bind.zig b/src/link/MachO/bind.zig new file mode 100644 index 0000000000..d234fa8242 --- /dev/null +++ b/src/link/MachO/bind.zig @@ -0,0 +1,145 @@ +const std = @import("std"); +const leb = std.leb; +const macho = std.macho; + +pub const Pointer = struct { + offset: u64, + segment_id: u16, + dylib_ordinal: ?i64 = null, + name: ?[]const u8 = null, +}; + +pub fn pointerCmp(context: void, a: Pointer, b: Pointer) bool { + if (a.segment_id < b.segment_id) return true; + if (a.segment_id == b.segment_id) { + return a.offset < b.offset; + } + return false; +} + +pub fn rebaseInfoSize(pointers: []const Pointer) !u64 { + var stream = std.io.countingWriter(std.io.null_writer); + var writer = stream.writer(); + var size: u64 = 0; + + for (pointers) |pointer| { + size += 2; + try leb.writeILEB128(writer, pointer.offset); + size += 1; + } + + size += 1 + stream.bytes_written; + return size; +} + +pub fn writeRebaseInfo(pointers: []const Pointer, writer: anytype) !void { + for (pointers) |pointer| { + try writer.writeByte(macho.REBASE_OPCODE_SET_TYPE_IMM | @truncate(u4, macho.REBASE_TYPE_POINTER)); + try writer.writeByte(macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, pointer.segment_id)); + + try leb.writeILEB128(writer, pointer.offset); + try writer.writeByte(macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES | @truncate(u4, 1)); + } + try writer.writeByte(macho.REBASE_OPCODE_DONE); +} + +pub fn bindInfoSize(pointers: []const Pointer) !u64 { + var stream = std.io.countingWriter(std.io.null_writer); + var writer = stream.writer(); + var size: u64 = 0; + + for (pointers) |pointer| { + size += 1; + if (pointer.dylib_ordinal.? > 15) { + try leb.writeULEB128(writer, @bitCast(u64, pointer.dylib_ordinal.?)); + } + size += 1; + + size += 1; + size += pointer.name.?.len; + size += 1; + + size += 1; + + try leb.writeILEB128(writer, pointer.offset); + size += 1; + } + + size += stream.bytes_written + 1; + return size; +} + +pub fn writeBindInfo(pointers: []const Pointer, writer: anytype) !void { + for (pointers) |pointer| { + if (pointer.dylib_ordinal.? > 15) { + try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB); + try leb.writeULEB128(writer, @bitCast(u64, pointer.dylib_ordinal.?)); + } else if (pointer.dylib_ordinal.? > 0) { + try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, pointer.dylib_ordinal.?))); + } else { + try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, pointer.dylib_ordinal.?))); + } + try writer.writeByte(macho.BIND_OPCODE_SET_TYPE_IMM | @truncate(u4, macho.BIND_TYPE_POINTER)); + + try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags. + try writer.writeAll(pointer.name.?); + try writer.writeByte(0); + + try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, pointer.segment_id)); + + try leb.writeILEB128(writer, pointer.offset); + try writer.writeByte(macho.BIND_OPCODE_DO_BIND); + } + + try writer.writeByte(macho.BIND_OPCODE_DONE); +} + +pub fn lazyBindInfoSize(pointers: []const Pointer) !u64 { + var stream = std.io.countingWriter(std.io.null_writer); + var writer = stream.writer(); + var size: u64 = 0; + + for (pointers) |pointer| { + size += 1; + + try leb.writeILEB128(writer, pointer.offset); + + size += 1; + if (pointer.dylib_ordinal.? > 15) { + try leb.writeULEB128(writer, @bitCast(u64, pointer.dylib_ordinal.?)); + } + + size += 1; + size += pointer.name.?.len; + size += 1; + + size += 2; + } + + size += stream.bytes_written; + return size; +} + +pub fn writeLazyBindInfo(pointers: []const Pointer, writer: anytype) !void { + for (pointers) |pointer| { + try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, pointer.segment_id)); + + try leb.writeILEB128(writer, pointer.offset); + + if (pointer.dylib_ordinal.? > 15) { + try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB); + try leb.writeULEB128(writer, @bitCast(u64, pointer.dylib_ordinal.?)); + } else if (pointer.dylib_ordinal.? > 0) { + try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, pointer.dylib_ordinal.?))); + } else { + try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, pointer.dylib_ordinal.?))); + } + + try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags. + try writer.writeAll(pointer.name.?); + try writer.writeByte(0); + + try writer.writeByte(macho.BIND_OPCODE_DO_BIND); + try writer.writeByte(macho.BIND_OPCODE_DONE); + } +} diff --git a/src/link/MachO/imports.zig b/src/link/MachO/imports.zig deleted file mode 100644 index 2bc34d14c5..0000000000 --- a/src/link/MachO/imports.zig +++ /dev/null @@ -1,152 +0,0 @@ -const std = @import("std"); -const leb = std.leb; -const macho = std.macho; -const mem = std.mem; - -const assert = std.debug.assert; -const Allocator = mem.Allocator; - -pub const ExternSymbol = struct { - /// MachO symbol table entry. - inner: macho.nlist_64, - - /// Id of the dynamic library where the specified entries can be found. - /// Id of 0 means self. - /// TODO this should really be an id into the table of all defined - /// dylibs. - dylib_ordinal: i64 = 0, - - /// Id of the segment where this symbol is defined (will have its address - /// resolved). - segment: u16 = 0, - - /// Offset relative to the start address of the `segment`. - offset: u32 = 0, -}; - -pub fn rebaseInfoSize(symbols: anytype) !u64 { - var stream = std.io.countingWriter(std.io.null_writer); - var writer = stream.writer(); - var size: u64 = 0; - - for (symbols) |entry| { - size += 2; - try leb.writeILEB128(writer, entry.value.offset); - size += 1; - } - - size += 1 + stream.bytes_written; - return size; -} - -pub fn writeRebaseInfo(symbols: anytype, writer: anytype) !void { - for (symbols) |entry| { - const symbol = entry.value; - try writer.writeByte(macho.REBASE_OPCODE_SET_TYPE_IMM | @truncate(u4, macho.REBASE_TYPE_POINTER)); - 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); -} - -pub fn bindInfoSize(symbols: anytype) !u64 { - var stream = std.io.countingWriter(std.io.null_writer); - var writer = stream.writer(); - var size: u64 = 0; - - for (symbols) |entry| { - const symbol = entry.value; - - size += 1; - if (symbol.dylib_ordinal > 15) { - try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal)); - } - size += 1; - - size += 1; - size += entry.key.len; - size += 1; - - size += 1; - try leb.writeILEB128(writer, symbol.offset); - size += 2; - } - - size += stream.bytes_written; - return size; -} - -pub fn writeBindInfo(symbols: anytype, writer: anytype) !void { - for (symbols) |entry| { - const symbol = entry.value; - - if (symbol.dylib_ordinal > 15) { - try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB); - try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal)); - } else if (symbol.dylib_ordinal > 0) { - try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal))); - } else { - try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal))); - } - try writer.writeByte(macho.BIND_OPCODE_SET_TYPE_IMM | @truncate(u4, macho.BIND_TYPE_POINTER)); - - try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags. - try writer.writeAll(entry.key); - try writer.writeByte(0); - - try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment)); - try leb.writeILEB128(writer, symbol.offset); - try writer.writeByte(macho.BIND_OPCODE_DO_BIND); - try writer.writeByte(macho.BIND_OPCODE_DONE); - } -} - -pub fn lazyBindInfoSize(symbols: anytype) !u64 { - var stream = std.io.countingWriter(std.io.null_writer); - var writer = stream.writer(); - var size: u64 = 0; - - for (symbols) |entry| { - const symbol = entry.value; - size += 1; - try leb.writeILEB128(writer, symbol.offset); - size += 1; - if (symbol.dylib_ordinal > 15) { - try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal)); - } - - size += 1; - size += entry.key.len; - size += 1; - - size += 2; - } - - size += stream.bytes_written; - return size; -} - -pub fn writeLazyBindInfo(symbols: anytype, writer: anytype) !void { - for (symbols) |entry| { - const symbol = entry.value; - try writer.writeByte(macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | @truncate(u4, symbol.segment)); - try leb.writeILEB128(writer, symbol.offset); - - if (symbol.dylib_ordinal > 15) { - try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB); - try leb.writeULEB128(writer, @bitCast(u64, symbol.dylib_ordinal)); - } else if (symbol.dylib_ordinal > 0) { - try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal))); - } else { - try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, symbol.dylib_ordinal))); - } - - try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags. - try writer.writeAll(entry.key); - try writer.writeByte(0); - - try writer.writeByte(macho.BIND_OPCODE_DO_BIND); - try writer.writeByte(macho.BIND_OPCODE_DONE); - } -} |
