diff options
Diffstat (limited to 'src/link/MachO.zig')
| -rw-r--r-- | src/link/MachO.zig | 1391 |
1 files changed, 1173 insertions, 218 deletions
diff --git a/src/link/MachO.zig b/src/link/MachO.zig index de723639f1..ac7b4af988 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -405,9 +405,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No const in_file = try std.fs.cwd().openFile(path, .{}); defer in_file.close(); - parseLibrary( - self, - self.base.allocator, + self.parseLibrary( in_file, path, lib, @@ -421,24 +419,15 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No }; } - parseDependentLibs(self, self.base.allocator, &dependent_libs, &self.base.options) catch |err| { + self.parseDependentLibs(&dependent_libs, &self.base.options) catch |err| { // TODO convert to error log.err("parsing dependent libraries failed with err {s}", .{@errorName(err)}); }; } - if (self.dyld_stub_binder_index == null) { - self.dyld_stub_binder_index = try self.addUndefined("dyld_stub_binder", .add_got); - } - if (!self.base.options.single_threaded) { - _ = try self.addUndefined("__tlv_bootstrap", .none); - } - - try self.createMhExecuteHeaderSymbol(); - var actions = std.ArrayList(ResolveAction).init(self.base.allocator); defer actions.deinit(); - try self.resolveSymbolsInDylibs(&actions); + try self.resolveSymbols(&actions); if (self.getEntryPoint() == null) { self.error_flags.no_entry_point_found = true; @@ -527,14 +516,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No try self.writeLinkeditSegmentData(); - const target = self.base.options.target; - const requires_codesig = blk: { - if (self.base.options.entitlements) |_| break :blk true; - if (target.cpu.arch == .aarch64 and (target.os.tag == .macos or target.abi == .simulator)) - break :blk true; - break :blk false; - }; - var codesig: ?CodeSignature = if (requires_codesig) blk: { + var codesig: ?CodeSignature = if (self.requiresCodeSignature()) blk: { // Preallocate space for the code signature. // We need to do this at this stage so that we have the load commands with proper values // written out to the file. @@ -596,14 +578,14 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No try load_commands.writeLoadDylibLCs(self.dylibs.items, self.referenced_dylibs.keys(), lc_writer); - if (requires_codesig) { + if (codesig != null) { try lc_writer.writeStruct(self.codesig_cmd); } const ncmds = load_commands.calcNumOfLCs(lc_buffer.items); try self.base.file.?.pwriteAll(lc_buffer.items, @sizeOf(macho.mach_header_64)); try self.writeHeader(ncmds, @as(u32, @intCast(lc_buffer.items.len))); - try self.writeUuid(comp, uuid_cmd_offset, requires_codesig); + try self.writeUuid(comp, uuid_cmd_offset, codesig != null); if (codesig) |*csig| { try self.writeCodeSignature(comp, csig); // code signing always comes last @@ -729,8 +711,7 @@ fn resolveLib( } pub fn parsePositional( - ctx: anytype, - gpa: Allocator, + self: *MachO, file: std.fs.File, path: []const u8, must_link: bool, @@ -741,9 +722,9 @@ pub fn parsePositional( defer tracy.end(); if (Object.isObject(file)) { - try parseObject(ctx, gpa, file, path, link_options); + try self.parseObject(file, path, link_options); } else { - try parseLibrary(ctx, gpa, file, path, .{ + try self.parseLibrary(file, path, .{ .path = null, .needed = false, .weak = false, @@ -752,8 +733,7 @@ pub fn parsePositional( } fn parseObject( - ctx: anytype, - gpa: Allocator, + self: *MachO, file: std.fs.File, path: []const u8, link_options: *const link.Options, @@ -761,6 +741,7 @@ fn parseObject( const tracy = trace(@src()); defer tracy.end(); + const gpa = self.base.allocator; const mtime: u64 = mtime: { const stat = file.stat() catch break :mtime 0; break :mtime @as(u64, @intCast(@divFloor(stat.mtime, 1_000_000_000))); @@ -776,7 +757,7 @@ fn parseObject( }; errdefer object.deinit(gpa); try object.parse(gpa); - try ctx.objects.append(gpa, object); + try self.objects.append(gpa, object); const cpu_arch: std.Target.Cpu.Arch = switch (object.header.cputype) { macho.CPU_TYPE_ARM64 => .aarch64, @@ -796,8 +777,7 @@ fn parseObject( } pub fn parseLibrary( - ctx: anytype, - gpa: Allocator, + self: *MachO, file: std.fs.File, path: []const u8, lib: link.SystemLib, @@ -811,16 +791,16 @@ pub fn parseLibrary( const cpu_arch = link_options.target.cpu.arch; if (fat.isFatLibrary(file)) { - const offset = parseFatLibrary(ctx, file, path, cpu_arch) catch |err| switch (err) { + const offset = self.parseFatLibrary(file, path, cpu_arch) catch |err| switch (err) { error.MissingArch => return, else => |e| return e, }; try file.seekTo(offset); if (Archive.isArchive(file, offset)) { - try parseArchive(ctx, gpa, path, offset, must_link, cpu_arch); + try self.parseArchive(path, offset, must_link, cpu_arch); } else if (Dylib.isDylib(file, offset)) { - try parseDylib(ctx, gpa, file, path, offset, dependent_libs, link_options, .{ + try self.parseDylib(file, path, offset, dependent_libs, link_options, .{ .needed = lib.needed, .weak = lib.weak, }); @@ -830,14 +810,14 @@ pub fn parseLibrary( return; } } else if (Archive.isArchive(file, 0)) { - try parseArchive(ctx, gpa, path, 0, must_link, cpu_arch); + try self.parseArchive(path, 0, must_link, cpu_arch); } else if (Dylib.isDylib(file, 0)) { - try parseDylib(ctx, gpa, file, path, 0, dependent_libs, link_options, .{ + try self.parseDylib(file, path, 0, dependent_libs, link_options, .{ .needed = lib.needed, .weak = lib.weak, }); } else { - parseLibStub(ctx, gpa, file, path, dependent_libs, link_options, .{ + self.parseLibStub(file, path, dependent_libs, link_options, .{ .needed = lib.needed, .weak = lib.weak, }) catch |err| switch (err) { @@ -852,12 +832,12 @@ pub fn parseLibrary( } pub fn parseFatLibrary( - ctx: anytype, + self: *MachO, file: std.fs.File, path: []const u8, cpu_arch: std.Target.Cpu.Arch, ) !u64 { - _ = ctx; + _ = self; var buffer: [2]fat.Arch = undefined; const fat_archs = try fat.parseArchs(file, &buffer); const offset = for (fat_archs) |arch| { @@ -871,13 +851,13 @@ pub fn parseFatLibrary( } fn parseArchive( - ctx: anytype, - gpa: Allocator, + self: *MachO, path: []const u8, fat_offset: u64, must_link: bool, cpu_arch: std.Target.Cpu.Arch, ) !void { + const gpa = self.base.allocator; // We take ownership of the file so that we can store it for the duration of symbol resolution. // TODO we shouldn't need to do that and could pre-parse the archive like we do for zld/ELF? @@ -929,10 +909,10 @@ fn parseArchive( } for (offsets.keys()) |off| { const object = try archive.parseObject(gpa, off); - try ctx.objects.append(gpa, object); + try self.objects.append(gpa, object); } } else { - try ctx.archives.append(gpa, archive); + try self.archives.append(gpa, archive); } } @@ -944,8 +924,7 @@ const DylibOpts = struct { }; fn parseDylib( - ctx: anytype, - gpa: Allocator, + self: *MachO, file: std.fs.File, path: []const u8, offset: u64, @@ -953,6 +932,7 @@ fn parseDylib( link_options: *const link.Options, dylib_options: DylibOpts, ) !void { + const gpa = self.base.allocator; const self_cpu_arch = link_options.target.cpu.arch; const file_stat = try file.stat(); @@ -968,7 +948,7 @@ fn parseDylib( try dylib.parseFromBinary( gpa, - @intCast(ctx.dylibs.items.len), // TODO defer it till later + @intCast(self.dylibs.items.len), // TODO defer it till later dependent_libs, path, contents, @@ -991,7 +971,7 @@ fn parseDylib( // TODO verify platform - addDylib(ctx, gpa, dylib, link_options, .{ + self.addDylib(dylib, link_options, .{ .needed = dylib_options.needed, .weak = dylib_options.weak, }) catch |err| switch (err) { @@ -1001,14 +981,14 @@ fn parseDylib( } fn parseLibStub( - ctx: anytype, - gpa: Allocator, + self: *MachO, file: std.fs.File, path: []const u8, dependent_libs: anytype, link_options: *const link.Options, dylib_options: DylibOpts, ) !void { + const gpa = self.base.allocator; var lib_stub = try LibStub.loadFromFile(gpa, file); defer lib_stub.deinit(); @@ -1023,12 +1003,12 @@ fn parseLibStub( gpa, link_options.target, lib_stub, - @intCast(ctx.dylibs.items.len), // TODO defer it till later + @intCast(self.dylibs.items.len), // TODO defer it till later dependent_libs, path, ); - addDylib(ctx, gpa, dylib, link_options, .{ + self.addDylib(dylib, link_options, .{ .needed = dylib_options.needed, .weak = dylib_options.weak, }) catch |err| switch (err) { @@ -1038,8 +1018,7 @@ fn parseLibStub( } fn addDylib( - ctx: anytype, - gpa: Allocator, + self: *MachO, dylib: Dylib, link_options: *const link.Options, dylib_options: DylibOpts, @@ -1055,28 +1034,24 @@ fn addDylib( } } - const gop = try ctx.dylibs_map.getOrPut(gpa, dylib.id.?.name); + const gpa = self.base.allocator; + const gop = try self.dylibs_map.getOrPut(gpa, dylib.id.?.name); if (gop.found_existing) return error.DylibAlreadyExists; - gop.value_ptr.* = @as(u16, @intCast(ctx.dylibs.items.len)); - try ctx.dylibs.append(gpa, dylib); + gop.value_ptr.* = @as(u16, @intCast(self.dylibs.items.len)); + try self.dylibs.append(gpa, dylib); const should_link_dylib_even_if_unreachable = blk: { if (link_options.dead_strip_dylibs and !dylib_options.needed) break :blk false; - break :blk !(dylib_options.dependent or ctx.referenced_dylibs.contains(gop.value_ptr.*)); + break :blk !(dylib_options.dependent or self.referenced_dylibs.contains(gop.value_ptr.*)); }; if (should_link_dylib_even_if_unreachable) { - try ctx.referenced_dylibs.putNoClobber(gpa, gop.value_ptr.*, {}); + try self.referenced_dylibs.putNoClobber(gpa, gop.value_ptr.*, {}); } } -pub fn parseDependentLibs( - ctx: anytype, - gpa: Allocator, - dependent_libs: anytype, - link_options: *const link.Options, -) !void { +pub fn parseDependentLibs(self: *MachO, dependent_libs: anytype, link_options: *const link.Options) !void { const tracy = trace(@src()); defer tracy.end(); @@ -1085,6 +1060,7 @@ pub fn parseDependentLibs( // 2) afterwards, we parse dependents of the included dylibs // TODO this should not be performed if the user specifies `-flat_namespace` flag. // See ld64 manpages. + const gpa = self.base.allocator; var arena_alloc = std.heap.ArenaAllocator.init(gpa); const arena = arena_alloc.allocator(); defer arena_alloc.deinit(); @@ -1092,9 +1068,9 @@ pub fn parseDependentLibs( outer: while (dependent_libs.readItem()) |dep_id| { defer dep_id.id.deinit(gpa); - if (ctx.dylibs_map.contains(dep_id.id.name)) continue; + if (self.dylibs_map.contains(dep_id.id.name)) continue; - const weak = ctx.dylibs.items[dep_id.parent].weak; + const weak = self.dylibs.items[dep_id.parent].weak; const has_ext = blk: { const basename = fs.path.basename(dep_id.id.name); break :blk mem.lastIndexOfScalar(u8, basename, '.') != null; @@ -1121,7 +1097,7 @@ pub fn parseDependentLibs( log.debug("trying dependency at fully resolved path {s}", .{full_path}); const offset: u64 = if (fat.isFatLibrary(file)) blk: { - const offset = parseFatLibrary(ctx, file, full_path, link_options.target.cpu.arch) catch |err| switch (err) { + const offset = self.parseFatLibrary(file, full_path, link_options.target.cpu.arch) catch |err| switch (err) { error.MissingArch => break, else => |e| return e, }; @@ -1130,12 +1106,12 @@ pub fn parseDependentLibs( } else 0; if (Dylib.isDylib(file, offset)) { - try parseDylib(ctx, gpa, file, full_path, offset, dependent_libs, link_options, .{ + try self.parseDylib(file, full_path, offset, dependent_libs, link_options, .{ .dependent = true, .weak = weak, }); } else { - parseLibStub(ctx, gpa, file, full_path, dependent_libs, link_options, .{ + self.parseLibStub(file, full_path, dependent_libs, link_options, .{ .dependent = true, .weak = weak, }) catch |err| switch (err) { @@ -1394,7 +1370,7 @@ fn markRelocsDirtyByAddress(self: *MachO, addr: u64) void { } } -pub fn allocateSpecialSymbols(self: anytype) !void { +pub fn allocateSpecialSymbols(self: *MachO) !void { for (&[_][]const u8{ "___dso_handle", "__mh_execute_header", @@ -1432,24 +1408,82 @@ pub fn createAtom(self: *MachO, sym_index: u32, opts: CreateAtomOpts) !Atom.Inde return index; } +pub fn createTentativeDefAtoms(self: *MachO) !void { + const gpa = self.base.allocator; + + for (self.globals.items) |global| { + const sym = self.getSymbolPtr(global); + if (!sym.tentative()) continue; + if (sym.n_desc == N_DEAD) continue; + + log.debug("creating tentative definition for ATOM(%{d}, '{s}') in object({?})", .{ + global.sym_index, self.getSymbolName(global), global.file, + }); + + // Convert any tentative definition into a regular symbol and allocate + // text blocks for each tentative definition. + const size = sym.n_value; + const alignment = (sym.n_desc >> 8) & 0x0f; + + if (self.bss_section_index == null) { + self.bss_section_index = try self.initSection("__DATA", "__bss", .{ + .flags = macho.S_ZEROFILL, + }); + } + + sym.* = .{ + .n_strx = sym.n_strx, + .n_type = macho.N_SECT | macho.N_EXT, + .n_sect = self.bss_section_index.? + 1, + .n_desc = 0, + .n_value = 0, + }; + + const atom_index = try self.createAtom(global.sym_index, .{ + .size = size, + .alignment = alignment, + }); + const atom = self.getAtomPtr(atom_index); + atom.file = global.file; + + self.addAtomToSection(atom_index); + + assert(global.getFile() != null); + const object = &self.objects.items[global.getFile().?]; + try object.atoms.append(gpa, atom_index); + object.atom_by_index_table[global.sym_index] = atom_index; + } +} + fn createDyldPrivateAtom(self: *MachO) !void { if (self.dyld_private_atom_index != null) return; const sym_index = try self.allocateSymbol(); - const atom_index = try self.createAtom(sym_index, .{}); + const atom_index = try self.createAtom(sym_index, .{ + .size = @sizeOf(u64), + .alignment = 3, + }); try self.atom_by_index_table.putNoClobber(self.base.allocator, sym_index, atom_index); - const atom = self.getAtomPtr(atom_index); - atom.size = @sizeOf(u64); + if (self.data_section_index == null) { + self.data_section_index = try self.initSection("__DATA", "__data", .{}); + } + + const atom = self.getAtom(atom_index); const sym = atom.getSymbolPtr(self); sym.n_type = macho.N_SECT; sym.n_sect = self.data_section_index.? + 1; self.dyld_private_atom_index = atom_index; - sym.n_value = try self.allocateAtom(atom_index, atom.size, @alignOf(u64)); - log.debug("allocated dyld_private atom at 0x{x}", .{sym.n_value}); - var buffer: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64); - try self.writeAtom(atom_index, &buffer); + switch (self.mode) { + .zld => self.addAtomToSection(atom_index), + .incremental => { + sym.n_value = try self.allocateAtom(atom_index, atom.size, @alignOf(u64)); + log.debug("allocated dyld_private atom at 0x{x}", .{sym.n_value}); + var buffer: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64); + try self.writeAtom(atom_index, &buffer); + }, + } } fn createThreadLocalDescriptorAtom(self: *MachO, sym_name: []const u8, target: SymbolWithLoc) !Atom.Index { @@ -1485,7 +1519,7 @@ fn createThreadLocalDescriptorAtom(self: *MachO, sym_name: []const u8, target: S return atom_index; } -fn createMhExecuteHeaderSymbol(self: *MachO) !void { +pub fn createMhExecuteHeaderSymbol(self: *MachO) !void { if (self.base.options.output_mode != .Exe) return; const gpa = self.base.allocator; @@ -1501,10 +1535,17 @@ fn createMhExecuteHeaderSymbol(self: *MachO) !void { }; const gop = try self.getOrPutGlobalPtr("__mh_execute_header"); + if (gop.found_existing) { + const global = gop.value_ptr.*; + if (global.getFile()) |file| { + const global_object = &self.objects.items[file]; + global_object.globals_lookup[global.sym_index] = self.getGlobalIndex("__mh_execute_header").?; + } + } gop.value_ptr.* = sym_loc; } -fn createDsoHandleSymbol(self: *MachO) !void { +pub fn createDsoHandleSymbol(self: *MachO) !void { const global = self.getGlobalPtr("___dso_handle") orelse return; if (!self.getSymbol(global.*).undf()) return; @@ -1519,10 +1560,51 @@ fn createDsoHandleSymbol(self: *MachO) !void { .n_desc = macho.N_WEAK_DEF, .n_value = 0, }; + const global_index = self.getGlobalIndex("___dso_handle").?; + if (global.getFile()) |file| { + const global_object = &self.objects.items[file]; + global_object.globals_lookup[global.sym_index] = global_index; + } global.* = sym_loc; _ = self.unresolved.swapRemove(self.getGlobalIndex("___dso_handle").?); } +pub fn resolveSymbols(self: *MachO, actions: *std.ArrayList(ResolveAction)) !void { + // We add the specified entrypoint as the first unresolved symbols so that + // we search for it in libraries should there be no object files specified + // on the linker line. + if (self.base.options.output_mode == .Exe) { + const entry_name = self.base.options.entry orelse load_commands.default_entry_point; + _ = try self.addUndefined(entry_name, .none); + } + + // Force resolution of any symbols requested by the user. + for (self.base.options.force_undefined_symbols.keys()) |sym_name| { + _ = try self.addUndefined(sym_name, .none); + } + + for (self.objects.items, 0..) |_, object_id| { + try self.resolveSymbolsInObject(@as(u32, @intCast(object_id))); + } + + try self.resolveSymbolsInArchives(); + + // Finally, force resolution of dyld_stub_binder if there are imports + // requested. + if (self.unresolved.count() > 0 and self.dyld_stub_binder_index == null) { + self.dyld_stub_binder_index = try self.addUndefined("dyld_stub_binder", .add_got); + } + if (!self.base.options.single_threaded and self.mode == .incremental) { + _ = try self.addUndefined("__tlv_bootstrap", .none); + } + + try self.resolveSymbolsInDylibs(actions); + + try self.createMhExecuteHeaderSymbol(); + try self.createDsoHandleSymbol(); + try self.resolveSymbolsAtLoading(); +} + fn resolveGlobalSymbol(self: *MachO, current: SymbolWithLoc) !void { const gpa = self.base.allocator; const sym = self.getSymbol(current); @@ -1536,6 +1618,7 @@ fn resolveGlobalSymbol(self: *MachO, current: SymbolWithLoc) !void { } return; } + const global_index = self.getGlobalIndex(sym_name).?; const global = gop.value_ptr.*; const global_sym = self.getSymbol(global); @@ -1566,7 +1649,22 @@ fn resolveGlobalSymbol(self: *MachO, current: SymbolWithLoc) !void { const sym_is_weak = sym.sect() and (sym.weakDef() or sym.pext()); const global_is_weak = global_sym.sect() and (global_sym.weakDef() or global_sym.pext()); - if (sym_is_strong and global_is_strong) return error.MultipleSymbolDefinitions; + if (sym_is_strong and global_is_strong) { + log.err("symbol '{s}' defined multiple times", .{sym_name}); + if (global.getFile()) |file| { + log.err(" first definition in '{s}'", .{self.objects.items[file].name}); + } + if (current.getFile()) |file| { + log.err(" next definition in '{s}'", .{self.objects.items[file].name}); + } + return error.MultipleSymbolDefinitions; + } + + if (current.getFile()) |file| { + const object = &self.objects.items[file]; + object.globals_lookup[current.sym_index] = global_index; + } + if (global_is_strong) return; if (sym_is_weak and global_is_weak) return; if (sym.tentative() and global_sym.tentative()) { @@ -1574,11 +1672,88 @@ fn resolveGlobalSymbol(self: *MachO, current: SymbolWithLoc) !void { } if (sym.undf() and !sym.tentative()) return; - _ = self.unresolved.swapRemove(self.getGlobalIndex(sym_name).?); + if (global.getFile()) |file| { + const global_object = &self.objects.items[file]; + global_object.globals_lookup[global.sym_index] = global_index; + } + _ = self.unresolved.swapRemove(global_index); gop.value_ptr.* = current; } +fn resolveSymbolsInObject(self: *MachO, object_id: u32) !void { + const object = &self.objects.items[object_id]; + const in_symtab = object.in_symtab orelse return; + + log.debug("resolving symbols in '{s}'", .{object.name}); + + var sym_index: u32 = 0; + while (sym_index < in_symtab.len) : (sym_index += 1) { + const sym = &object.symtab[sym_index]; + const sym_name = object.getSymbolName(sym_index); + + if (sym.stab()) { + log.err("unhandled symbol type: stab", .{}); + log.err(" symbol '{s}'", .{sym_name}); + log.err(" first definition in '{s}'", .{object.name}); + return error.UnhandledSymbolType; + } + + if (sym.indr()) { + log.err("unhandled symbol type: indirect", .{}); + log.err(" symbol '{s}'", .{sym_name}); + log.err(" first definition in '{s}'", .{object.name}); + return error.UnhandledSymbolType; + } + + if (sym.abs()) { + log.err("unhandled symbol type: absolute", .{}); + log.err(" symbol '{s}'", .{sym_name}); + log.err(" first definition in '{s}'", .{object.name}); + return error.UnhandledSymbolType; + } + + if (sym.sect() and !sym.ext()) { + log.debug("symbol '{s}' local to object {s}; skipping...", .{ + sym_name, + object.name, + }); + continue; + } + + try self.resolveGlobalSymbol(.{ .sym_index = sym_index, .file = object_id + 1 }); + } +} + +fn resolveSymbolsInArchives(self: *MachO) !void { + if (self.archives.items.len == 0) return; + + const gpa = self.base.allocator; + var next_sym: usize = 0; + loop: while (next_sym < self.unresolved.count()) { + const global = self.globals.items[self.unresolved.keys()[next_sym]]; + const sym_name = self.getSymbolName(global); + + for (self.archives.items) |archive| { + // Check if the entry exists in a static archive. + const offsets = archive.toc.get(sym_name) orelse { + // No hit. + continue; + }; + assert(offsets.items.len > 0); + + const object_id = @as(u16, @intCast(self.objects.items.len)); + const object = try archive.parseObject(gpa, offsets.items[0]); + try self.objects.append(gpa, object); + try self.resolveSymbolsInObject(object_id); + + continue :loop; + } + + next_sym += 1; + } +} + fn resolveSymbolsInDylibs(self: *MachO, actions: *std.ArrayList(ResolveAction)) !void { if (self.dylibs.items.len == 0) return; @@ -1608,6 +1783,7 @@ fn resolveSymbolsInDylibs(self: *MachO, actions: *std.ArrayList(ResolveAction)) if (self.unresolved.fetchSwapRemove(global_index)) |entry| blk: { if (!sym.undf()) break :blk; + if (self.mode == .zld) break :blk; try actions.append(.{ .kind = entry.value, .target = global }); } @@ -1618,6 +1794,42 @@ fn resolveSymbolsInDylibs(self: *MachO, actions: *std.ArrayList(ResolveAction)) } } +fn resolveSymbolsAtLoading(self: *MachO) !void { + const is_lib = self.base.options.output_mode == .Lib; + const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib; + const allow_undef = is_dyn_lib and (self.base.options.allow_shlib_undefined orelse false); + + var next_sym: usize = 0; + while (next_sym < self.unresolved.count()) { + const global_index = self.unresolved.keys()[next_sym]; + const global = self.globals.items[global_index]; + const sym = self.getSymbolPtr(global); + + if (sym.discarded()) { + sym.* = .{ + .n_strx = 0, + .n_type = macho.N_UNDF, + .n_sect = 0, + .n_desc = 0, + .n_value = 0, + }; + _ = self.unresolved.swapRemove(global_index); + continue; + } else if (allow_undef) { + const n_desc = @as( + u16, + @bitCast(macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP * @as(i16, @intCast(macho.N_SYMBOL_RESOLVER))), + ); + sym.n_type = macho.N_EXT; + sym.n_desc = n_desc; + _ = self.unresolved.swapRemove(global_index); + continue; + } + + next_sym += 1; + } +} + pub fn deinit(self: *MachO) void { const gpa = self.base.allocator; @@ -1638,7 +1850,6 @@ pub fn deinit(self: *MachO) void { self.thunks.deinit(gpa); self.strtab.deinit(gpa); - self.locals.deinit(gpa); self.globals.deinit(gpa); self.locals_free_list.deinit(gpa); @@ -1653,6 +1864,14 @@ pub fn deinit(self: *MachO) void { self.resolver.deinit(gpa); } + for (self.objects.items) |*object| { + object.deinit(gpa); + } + self.objects.deinit(gpa); + for (self.archives.items) |*archive| { + archive.deinit(gpa); + } + self.archives.deinit(gpa); for (self.dylibs.items) |*dylib| { dylib.deinit(gpa); } @@ -1842,20 +2061,55 @@ fn allocateGlobal(self: *MachO) !u32 { return index; } -fn addGotEntry(self: *MachO, target: SymbolWithLoc) !void { +pub fn addGotEntry(self: *MachO, target: SymbolWithLoc) !void { if (self.got_table.lookup.contains(target)) return; const got_index = try self.got_table.allocateEntry(self.base.allocator, target); - try self.writeOffsetTableEntry(got_index); - self.got_table_count_dirty = true; - self.markRelocsDirtyByTarget(target); + if (self.got_section_index == null) { + self.got_section_index = try self.initSection("__DATA_CONST", "__got", .{ + .flags = macho.S_NON_LAZY_SYMBOL_POINTERS, + }); + } + if (self.mode == .incremental) { + try self.writeOffsetTableEntry(got_index); + self.got_table_count_dirty = true; + self.markRelocsDirtyByTarget(target); + } } -fn addStubEntry(self: *MachO, target: SymbolWithLoc) !void { +pub fn addStubEntry(self: *MachO, target: SymbolWithLoc) !void { if (self.stub_table.lookup.contains(target)) return; const stub_index = try self.stub_table.allocateEntry(self.base.allocator, target); - try self.writeStubTableEntry(stub_index); - self.stub_table_count_dirty = true; - self.markRelocsDirtyByTarget(target); + if (self.stubs_section_index == null) { + self.stubs_section_index = try self.initSection("__TEXT", "__stubs", .{ + .flags = macho.S_SYMBOL_STUBS | + macho.S_ATTR_PURE_INSTRUCTIONS | + macho.S_ATTR_SOME_INSTRUCTIONS, + .reserved2 = stubs.stubSize(self.base.options.target.cpu.arch), + }); + self.stub_helper_section_index = try self.initSection("__TEXT", "__stub_helper", .{ + .flags = macho.S_REGULAR | + macho.S_ATTR_PURE_INSTRUCTIONS | + macho.S_ATTR_SOME_INSTRUCTIONS, + }); + self.la_symbol_ptr_section_index = try self.initSection("__DATA", "__la_symbol_ptr", .{ + .flags = macho.S_LAZY_SYMBOL_POINTERS, + }); + } + if (self.mode == .incremental) { + try self.writeStubTableEntry(stub_index); + self.stub_table_count_dirty = true; + self.markRelocsDirtyByTarget(target); + } +} + +pub fn addTlvPtrEntry(self: *MachO, target: SymbolWithLoc) !void { + if (self.tlv_ptr_table.lookup.contains(target)) return; + _ = try self.tlv_ptr_table.allocateEntry(self.gpa, target); + if (self.tlv_ptr_section_index == null) { + self.tlv_ptr_section_index = try self.initSection("__DATA", "__thread_ptrs", .{ + .flags = macho.S_THREAD_LOCAL_VARIABLE_POINTERS, + }); + } } pub fn updateFunc(self: *MachO, mod: *Module, func_index: InternPool.Index, air: Air, liveness: Liveness) !void { @@ -2758,16 +3012,10 @@ const InitSectionOpts = struct { reserved2: u32 = 0, }; -pub fn initSection( - gpa: Allocator, - ctx: anytype, - segname: []const u8, - sectname: []const u8, - opts: InitSectionOpts, -) !u8 { +pub fn initSection(self: *MachO, segname: []const u8, sectname: []const u8, opts: InitSectionOpts) !u8 { log.debug("creating section '{s},{s}'", .{ segname, sectname }); - const index = @as(u8, @intCast(ctx.sections.slice().len)); - try ctx.sections.append(gpa, .{ + const index = @as(u8, @intCast(self.sections.slice().len)); + try self.sections.append(self.base.allocator, .{ .segment_index = undefined, // Segments will be created automatically later down the pipeline .header = .{ .sectname = makeStaticString(sectname), @@ -2822,7 +3070,7 @@ fn allocateSection(self: *MachO, segname: []const u8, sectname: []const u8, opts .cmdsize = @sizeOf(macho.segment_command_64) + @sizeOf(macho.section_64), }; - const sect_id = try initSection(gpa, self, sectname, segname, .{ + const sect_id = try self.initSection(sectname, segname, .{ .flags = opts.flags, .reserved2 = opts.reserved2, }); @@ -2918,10 +3166,29 @@ fn growSectionVirtualMemory(self: *MachO, sect_id: u8, needed_size: u64) !void { } } +pub fn addAtomToSection(self: *MachO, atom_index: Atom.Index) void { + assert(self.mode == .zld); + const atom = self.getAtomPtr(atom_index); + const sym = self.getSymbol(atom.getSymbolWithLoc()); + var section = self.sections.get(sym.n_sect - 1); + if (section.header.size > 0) { + const last_atom = self.getAtomPtr(section.last_atom_index.?); + last_atom.next_index = atom_index; + atom.prev_index = section.last_atom_index; + } else { + section.first_atom_index = atom_index; + } + section.last_atom_index = atom_index; + section.header.size += atom.size; + self.sections.set(sym.n_sect - 1, section); +} + fn allocateAtom(self: *MachO, atom_index: Atom.Index, new_atom_size: u64, alignment: u64) !u64 { const tracy = trace(@src()); defer tracy.end(); + assert(self.mode == .incremental); + const atom = self.getAtom(atom_index); const sect_id = atom.getSymbol(self).n_sect - 1; const segment = self.getSegmentPtr(sect_id); @@ -3048,7 +3315,7 @@ pub fn getGlobalSymbol(self: *MachO, name: []const u8, lib_name: ?[]const u8) !u return self.addUndefined(sym_name, .add_stub); } -pub fn writeSegmentHeaders(self: anytype, writer: anytype) !void { +pub fn writeSegmentHeaders(self: *MachO, writer: anytype) !void { for (self.segments.items, 0..) |seg, i| { const indexes = self.getSectionIndexes(@as(u8, @intCast(i))); var out_seg = seg; @@ -3075,7 +3342,7 @@ pub fn writeSegmentHeaders(self: anytype, writer: anytype) !void { } } -fn writeLinkeditSegmentData(self: *MachO) !void { +pub fn writeLinkeditSegmentData(self: *MachO) !void { const page_size = getPageSize(self.base.options.target.cpu.arch); const seg = self.getLinkeditSegmentPtr(); seg.filesize = 0; @@ -3092,29 +3359,29 @@ fn writeLinkeditSegmentData(self: *MachO) !void { } try self.writeDyldInfoData(); + // TODO handle this better + if (self.mode == .zld) { + try self.writeFunctionStarts(); + try self.writeDataInCode(); + } try self.writeSymtabs(); seg.vmsize = mem.alignForward(u64, seg.filesize, page_size); } -pub fn collectRebaseDataFromTableSection( - gpa: Allocator, - ctx: anytype, - sect_id: u8, - rebase: *Rebase, - table: anytype, -) !void { - const header = ctx.sections.items(.header)[sect_id]; - const segment_index = ctx.sections.items(.segment_index)[sect_id]; - const segment = ctx.segments.items[segment_index]; +fn collectRebaseDataFromTableSection(self: *MachO, sect_id: u8, rebase: *Rebase, table: anytype) !void { + const gpa = self.base.allocator; + const header = self.sections.items(.header)[sect_id]; + const segment_index = self.sections.items(.segment_index)[sect_id]; + const segment = self.segments.items[segment_index]; const base_offset = header.addr - segment.vmaddr; - const is_got = if (ctx.got_section_index) |index| index == sect_id else false; + const is_got = if (self.got_section_index) |index| index == sect_id else false; try rebase.entries.ensureUnusedCapacity(gpa, table.entries.items.len); for (table.entries.items, 0..) |entry, i| { if (!table.lookup.contains(entry)) continue; - const sym = ctx.getSymbol(entry); + const sym = self.getSymbol(entry); if (is_got and sym.undf()) continue; const offset = i * @sizeOf(u64); log.debug(" | rebase at {x}", .{base_offset + offset}); @@ -3152,34 +3419,105 @@ fn collectRebaseData(self: *MachO, rebase: *Rebase) !void { } } - try collectRebaseDataFromTableSection(gpa, self, self.got_section_index.?, rebase, self.got_table); - try collectRebaseDataFromTableSection(gpa, self, self.la_symbol_ptr_section_index.?, rebase, self.stub_table); + // Unpack GOT entries + if (self.got_section_index) |sect_id| { + try self.collectRebaseDataFromTableSection(sect_id, rebase, self.got_table); + } + + // Next, unpack __la_symbol_ptr entries + if (self.la_symbol_ptr_section_index) |sect_id| { + try self.collectRebaseDataFromTableSection(sect_id, rebase, self.stub_table); + } + + // Finally, unpack the rest. + const cpu_arch = self.base.options.target.cpu.arch; + for (self.objects.items) |*object| { + for (object.atoms.items) |atom_index| { + const atom = self.getAtom(atom_index); + const sym = self.getSymbol(atom.getSymbolWithLoc()); + if (sym.n_desc == N_DEAD) continue; + + const sect_id = sym.n_sect - 1; + const section = self.sections.items(.header)[sect_id]; + const segment_id = self.sections.items(.segment_index)[sect_id]; + const segment = self.segments.items[segment_id]; + if (segment.maxprot & macho.PROT.WRITE == 0) continue; + switch (section.type()) { + macho.S_LITERAL_POINTERS, + macho.S_REGULAR, + macho.S_MOD_INIT_FUNC_POINTERS, + macho.S_MOD_TERM_FUNC_POINTERS, + => {}, + else => continue, + } + + log.debug(" ATOM({d}, %{d}, '{s}')", .{ + atom_index, + atom.sym_index, + self.getSymbolName(atom.getSymbolWithLoc()), + }); + + const code = Atom.getAtomCode(self, atom_index); + const relocs = Atom.getAtomRelocs(self, atom_index); + const ctx = Atom.getRelocContext(self, atom_index); + + for (relocs) |rel| { + switch (cpu_arch) { + .aarch64 => { + const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)); + if (rel_type != .ARM64_RELOC_UNSIGNED) continue; + if (rel.r_length != 3) continue; + }, + .x86_64 => { + const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type)); + if (rel_type != .X86_64_RELOC_UNSIGNED) continue; + if (rel.r_length != 3) continue; + }, + else => unreachable, + } + const target = Atom.parseRelocTarget(self, .{ + .object_id = atom.getFile().?, + .rel = rel, + .code = code, + .base_offset = ctx.base_offset, + .base_addr = ctx.base_addr, + }); + const target_sym = self.getSymbol(target); + if (target_sym.undf()) continue; + + const base_offset = @as(i32, @intCast(sym.n_value - segment.vmaddr)); + const rel_offset = rel.r_address - ctx.base_offset; + const offset = @as(u64, @intCast(base_offset + rel_offset)); + log.debug(" | rebase at {x}", .{offset}); + + try rebase.entries.append(self.gpa, .{ + .offset = offset, + .segment_id = segment_id, + }); + } + } + } try rebase.finalize(gpa); } -pub fn collectBindDataFromTableSection( - gpa: Allocator, - ctx: anytype, - sect_id: u8, - bind: anytype, - table: anytype, -) !void { - const header = ctx.sections.items(.header)[sect_id]; - const segment_index = ctx.sections.items(.segment_index)[sect_id]; - const segment = ctx.segments.items[segment_index]; +fn collectBindDataFromTableSection(self: *MachO, sect_id: u8, bind: anytype, table: anytype) !void { + const gpa = self.base.allocator; + const header = self.sections.items(.header)[sect_id]; + const segment_index = self.sections.items(.segment_index)[sect_id]; + const segment = self.segments.items[segment_index]; const base_offset = header.addr - segment.vmaddr; try bind.entries.ensureUnusedCapacity(gpa, table.entries.items.len); for (table.entries.items, 0..) |entry, i| { if (!table.lookup.contains(entry)) continue; - const bind_sym = ctx.getSymbol(entry); + const bind_sym = self.getSymbol(entry); if (!bind_sym.undf()) continue; const offset = i * @sizeOf(u64); log.debug(" | bind at {x}, import('{s}') in dylib({d})", .{ base_offset + offset, - ctx.getSymbolName(entry), + self.getSymbolName(entry), @divTrunc(@as(i16, @bitCast(bind_sym.n_desc)), macho.N_SYMBOL_RESOLVER), }); if (bind_sym.weakRef()) { @@ -3235,13 +3573,105 @@ fn collectBindData(self: *MachO, bind: anytype, raw_bindings: anytype) !void { } } - // Gather GOT pointers - try collectBindDataFromTableSection(gpa, self, self.got_section_index.?, bind, self.got_table); + // Unpack GOT pointers + if (self.got_section_index) |sect_id| { + try self.collectBindDataFromTableSection(sect_id, bind, self.got_table); + } + + // Next, unpack TLV pointers section + if (self.tlv_ptr_section_index) |sect_id| { + try self.collectBindDataFromTableSection(sect_id, bind, self.tlv_ptr_table); + } + + // Finally, unpack the rest. + const cpu_arch = self.base.options.target.cpu.arch; + for (self.objects.items) |*object| { + for (object.atoms.items) |atom_index| { + const atom = self.getAtom(atom_index); + const sym = self.getSymbol(atom.getSymbolWithLoc()); + if (sym.n_desc == N_DEAD) continue; + + const sect_id = sym.n_sect - 1; + const section = self.sections.items(.header)[sect_id]; + const segment_id = self.sections.items(.segment_index)[sect_id]; + const segment = self.segments.items[segment_id]; + if (segment.maxprot & macho.PROT.WRITE == 0) continue; + switch (section.type()) { + macho.S_LITERAL_POINTERS, + macho.S_REGULAR, + macho.S_MOD_INIT_FUNC_POINTERS, + macho.S_MOD_TERM_FUNC_POINTERS, + => {}, + else => continue, + } + + log.debug(" ATOM({d}, %{d}, '{s}')", .{ + atom_index, + atom.sym_index, + self.getSymbolName(atom.getSymbolWithLoc()), + }); + + const code = Atom.getAtomCode(self, atom_index); + const relocs = Atom.getAtomRelocs(self, atom_index); + const ctx = Atom.getRelocContext(self, atom_index); + + for (relocs) |rel| { + switch (cpu_arch) { + .aarch64 => { + const rel_type = @as(macho.reloc_type_arm64, @enumFromInt(rel.r_type)); + if (rel_type != .ARM64_RELOC_UNSIGNED) continue; + if (rel.r_length != 3) continue; + }, + .x86_64 => { + const rel_type = @as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type)); + if (rel_type != .X86_64_RELOC_UNSIGNED) continue; + if (rel.r_length != 3) continue; + }, + else => unreachable, + } + + const global = Atom.parseRelocTarget(self, .{ + .object_id = atom.getFile().?, + .rel = rel, + .code = code, + .base_offset = ctx.base_offset, + .base_addr = ctx.base_addr, + }); + const bind_sym_name = self.getSymbolName(global); + const bind_sym = self.getSymbol(global); + if (!bind_sym.undf()) continue; + + const base_offset = sym.n_value - segment.vmaddr; + const rel_offset = @as(u32, @intCast(rel.r_address - ctx.base_offset)); + const offset = @as(u64, @intCast(base_offset + rel_offset)); + const addend = mem.readIntLittle(i64, code[rel_offset..][0..8]); + + const dylib_ordinal = @divTrunc(@as(i16, @bitCast(bind_sym.n_desc)), macho.N_SYMBOL_RESOLVER); + log.debug(" | bind at {x}, import('{s}') in dylib({d})", .{ + base_offset, + bind_sym_name, + dylib_ordinal, + }); + log.debug(" | with addend {x}", .{addend}); + if (bind_sym.weakRef()) { + log.debug(" | marking as weak ref ", .{}); + } + try bind.entries.append(self.gpa, .{ + .target = global, + .offset = offset, + .segment_id = segment_id, + .addend = addend, + }); + } + } + } + try bind.finalize(gpa, self); } fn collectLazyBindData(self: *MachO, bind: anytype) !void { - try collectBindDataFromTableSection(self.base.allocator, self, self.la_symbol_ptr_section_index.?, bind, self.stub_table); + const sect_id = self.la_symbol_ptr_section_index orelse return; + try self.collectBindDataFromTableSection(sect_id, bind, self.stub_table); try bind.finalize(self.base.allocator, self); } @@ -3259,6 +3689,7 @@ fn collectExportData(self: *MachO, trie: *Trie) !void { if (sym.undf()) continue; assert(sym.ext()); + if (sym.n_desc == N_DEAD) continue; const sym_name = self.getSymbolName(global); log.debug(" (putting '{s}' defined at 0x{x})", .{ sym_name, sym.n_value }); @@ -3349,12 +3780,7 @@ fn writeDyldInfoData(self: *MachO) !void { }); try self.base.file.?.pwriteAll(buffer, rebase_off); - try populateLazyBindOffsetsInStubHelper( - self, - self.base.options.target.cpu.arch, - self.base.file.?, - lazy_bind, - ); + try self.populateLazyBindOffsetsInStubHelper(lazy_bind); self.dyld_info_cmd.rebase_off = @as(u32, @intCast(rebase_off)); self.dyld_info_cmd.rebase_size = @as(u32, @intCast(rebase_size_aligned)); @@ -3366,19 +3792,15 @@ fn writeDyldInfoData(self: *MachO) !void { self.dyld_info_cmd.export_size = @as(u32, @intCast(export_size_aligned)); } -pub fn populateLazyBindOffsetsInStubHelper( - ctx: anytype, - cpu_arch: std.Target.Cpu.Arch, - file: fs.File, - lazy_bind: anytype, -) !void { +fn populateLazyBindOffsetsInStubHelper(self: *MachO, lazy_bind: anytype) !void { if (lazy_bind.size() == 0) return; - const stub_helper_section_index = ctx.stub_helper_section_index.?; + const stub_helper_section_index = self.stub_helper_section_index.?; // assert(ctx.stub_helper_preamble_allocated); - const header = ctx.sections.items(.header)[stub_helper_section_index]; + const header = self.sections.items(.header)[stub_helper_section_index]; + const cpu_arch = self.base.options.target.cpu.arch; const preamble_size = stubs.stubHelperPreambleSize(cpu_arch); const stub_size = stubs.stubHelperSize(cpu_arch); const stub_offset = stubs.stubOffsetInStubHelper(cpu_arch); @@ -3389,14 +3811,175 @@ pub fn populateLazyBindOffsetsInStubHelper( log.debug("writing lazy bind offset 0x{x} ({s}) in stub helper at 0x{x}", .{ bind_offset, - ctx.getSymbolName(lazy_bind.entries.items[index].target), + self.getSymbolName(lazy_bind.entries.items[index].target), file_offset, }); - try file.pwriteAll(mem.asBytes(&bind_offset), file_offset); + try self.base.file.?.pwriteAll(mem.asBytes(&bind_offset), file_offset); } } +const asc_u64 = std.sort.asc(u64); + +fn addSymbolToFunctionStarts(self: *MachO, sym_loc: SymbolWithLoc, addresses: *std.ArrayList(u64)) !void { + const sym = self.getSymbol(sym_loc); + if (sym.n_strx == 0) return; + if (sym.n_desc == MachO.N_DEAD) return; + if (self.symbolIsTemp(sym_loc)) return; + try addresses.append(sym.n_value); +} + +fn writeFunctionStarts(self: *MachO) !void { + const gpa = self.base.allocator; + const seg = self.segments.items[self.header_segment_cmd_index.?]; + + // We need to sort by address first + var addresses = std.ArrayList(u64).init(gpa); + defer addresses.deinit(); + + for (self.objects.items) |object| { + for (object.exec_atoms.items) |atom_index| { + const atom = self.getAtom(atom_index); + const sym_loc = atom.getSymbolWithLoc(); + try self.addSymbolToFunctionStarts(sym_loc, &addresses); + + var it = Atom.getInnerSymbolsIterator(self, atom_index); + while (it.next()) |inner_sym_loc| { + try self.addSymbolToFunctionStarts(inner_sym_loc, &addresses); + } + } + } + + mem.sort(u64, addresses.items, {}, asc_u64); + + var offsets = std.ArrayList(u32).init(gpa); + defer offsets.deinit(); + try offsets.ensureTotalCapacityPrecise(addresses.items.len); + + var last_off: u32 = 0; + for (addresses.items) |addr| { + const offset = @as(u32, @intCast(addr - seg.vmaddr)); + const diff = offset - last_off; + + if (diff == 0) continue; + + offsets.appendAssumeCapacity(diff); + last_off = offset; + } + + var buffer = std.ArrayList(u8).init(gpa); + defer buffer.deinit(); + + const max_size = @as(usize, @intCast(offsets.items.len * @sizeOf(u64))); + try buffer.ensureTotalCapacity(max_size); + + for (offsets.items) |offset| { + try std.leb.writeULEB128(buffer.writer(), offset); + } + + const link_seg = self.getLinkeditSegmentPtr(); + const offset = link_seg.fileoff + link_seg.filesize; + assert(mem.isAlignedGeneric(u64, offset, @alignOf(u64))); + const needed_size = buffer.items.len; + const needed_size_aligned = mem.alignForward(u64, needed_size, @alignOf(u64)); + const padding = math.cast(usize, needed_size_aligned - needed_size) orelse return error.Overflow; + if (padding > 0) { + try buffer.ensureUnusedCapacity(padding); + buffer.appendNTimesAssumeCapacity(0, padding); + } + link_seg.filesize = offset + needed_size_aligned - link_seg.fileoff; + + log.debug("writing function starts info from 0x{x} to 0x{x}", .{ offset, offset + needed_size_aligned }); + + try self.base.file.?.pwriteAll(buffer.items, offset); + + self.function_starts_cmd.dataoff = @as(u32, @intCast(offset)); + self.function_starts_cmd.datasize = @as(u32, @intCast(needed_size_aligned)); +} + +fn filterDataInCode( + dices: []const macho.data_in_code_entry, + start_addr: u64, + end_addr: u64, +) []const macho.data_in_code_entry { + const Predicate = struct { + addr: u64, + + pub fn predicate(self: @This(), dice: macho.data_in_code_entry) bool { + return dice.offset >= self.addr; + } + }; + + const start = MachO.lsearch(macho.data_in_code_entry, dices, Predicate{ .addr = start_addr }); + const end = MachO.lsearch(macho.data_in_code_entry, dices[start..], Predicate{ .addr = end_addr }) + start; + + return dices[start..end]; +} + +pub fn writeDataInCode(self: *MachO) !void { + const gpa = self.base.allocator; + var out_dice = std.ArrayList(macho.data_in_code_entry).init(gpa); + defer out_dice.deinit(); + + const text_sect_id = self.text_section_index orelse return; + const text_sect_header = self.sections.items(.header)[text_sect_id]; + + for (self.objects.items) |object| { + if (!object.hasDataInCode()) continue; + const dice = object.data_in_code.items; + try out_dice.ensureUnusedCapacity(dice.len); + + for (object.exec_atoms.items) |atom_index| { + const atom = self.getAtom(atom_index); + const sym = self.getSymbol(atom.getSymbolWithLoc()); + if (sym.n_desc == MachO.N_DEAD) continue; + + const source_addr = if (object.getSourceSymbol(atom.sym_index)) |source_sym| + source_sym.n_value + else blk: { + const nbase = @as(u32, @intCast(object.in_symtab.?.len)); + const source_sect_id = @as(u8, @intCast(atom.sym_index - nbase)); + break :blk object.getSourceSection(source_sect_id).addr; + }; + const filtered_dice = filterDataInCode(dice, source_addr, source_addr + atom.size); + const base = math.cast(u32, sym.n_value - text_sect_header.addr + text_sect_header.offset) orelse + return error.Overflow; + + for (filtered_dice) |single| { + const offset = math.cast(u32, single.offset - source_addr + base) orelse + return error.Overflow; + out_dice.appendAssumeCapacity(.{ + .offset = offset, + .length = single.length, + .kind = single.kind, + }); + } + } + } + + const seg = self.getLinkeditSegmentPtr(); + const offset = seg.fileoff + seg.filesize; + assert(mem.isAlignedGeneric(u64, offset, @alignOf(u64))); + const needed_size = out_dice.items.len * @sizeOf(macho.data_in_code_entry); + const needed_size_aligned = mem.alignForward(u64, needed_size, @alignOf(u64)); + seg.filesize = offset + needed_size_aligned - seg.fileoff; + + const buffer = try gpa.alloc(u8, math.cast(usize, needed_size_aligned) orelse return error.Overflow); + defer gpa.free(buffer); + { + const src = mem.sliceAsBytes(out_dice.items); + @memcpy(buffer[0..src.len], src); + @memset(buffer[src.len..], 0); + } + + log.debug("writing data-in-code from 0x{x} to 0x{x}", .{ offset, offset + needed_size_aligned }); + + try self.base.file.?.pwriteAll(buffer, offset); + + self.data_in_code_cmd.dataoff = @as(u32, @intCast(offset)); + self.data_in_code_cmd.datasize = @as(u32, @intCast(needed_size_aligned)); +} + fn writeSymtabs(self: *MachO) !void { var ctx = try self.writeSymtab(); defer ctx.imports_table.deinit(); @@ -3404,18 +3987,38 @@ fn writeSymtabs(self: *MachO) !void { try self.writeStrtab(); } +fn addLocalToSymtab(self: *MachO, sym_loc: SymbolWithLoc, locals: *std.ArrayList(macho.nlist_64)) !void { + const sym = self.getSymbol(sym_loc); + if (sym.n_strx == 0) return; // no name, skip + if (sym.n_desc == MachO.N_DEAD) return; // garbage-collected, skip + if (sym.ext()) return; // an export lands in its own symtab section, skip + if (self.symbolIsTemp(sym_loc)) return; // local temp symbol, skip + var out_sym = sym; + out_sym.n_strx = try self.strtab.insert(self.base.allocator, self.getSymbolName(sym_loc)); + try locals.append(out_sym); +} + fn writeSymtab(self: *MachO) !SymtabCtx { const gpa = self.base.allocator; var locals = std.ArrayList(macho.nlist_64).init(gpa); defer locals.deinit(); - for (self.locals.items, 0..) |sym, sym_id| { - if (sym.n_strx == 0) continue; // no name, skip - const sym_loc = SymbolWithLoc{ .sym_index = @as(u32, @intCast(sym_id)) }; - if (self.symbolIsTemp(sym_loc)) continue; // local temp symbol, skip - if (self.getGlobal(self.getSymbolName(sym_loc)) != null) continue; // global symbol is either an export or import, skip - try locals.append(sym); + for (0..self.locals.items) |sym_id| { + try self.addLocalToSymtab(.{ .sym_index = @intCast(sym_id) }); + } + + for (self.objects.items) |object| { + for (object.atoms.items) |atom_index| { + const atom = self.getAtom(atom_index); + const sym_loc = atom.getSymbolWithLoc(); + try self.addLocalToSymtab(sym_loc, &locals); + + var it = Atom.getInnerSymbolsIterator(self, atom_index); + while (it.next()) |inner_sym_loc| { + try self.addLocalToSymtab(inner_sym_loc, &locals); + } + } } var exports = std.ArrayList(macho.nlist_64).init(gpa); @@ -3424,6 +4027,7 @@ fn writeSymtab(self: *MachO) !SymtabCtx { for (self.globals.items) |global| { const sym = self.getSymbol(global); if (sym.undf()) continue; // import, skip + if (sym.n_desc == N_DEAD) continue; var out_sym = sym; out_sym.n_strx = try self.strtab.insert(gpa, self.getSymbolName(global)); try exports.append(out_sym); @@ -3438,6 +4042,7 @@ fn writeSymtab(self: *MachO) !SymtabCtx { const sym = self.getSymbol(global); if (sym.n_strx == 0) continue; // no name, skip if (!sym.undf()) continue; // not an import, skip + if (sym.n_desc == N_DEAD) continue; const new_index = @as(u32, @intCast(imports.items.len)); var out_sym = sym; out_sym.n_strx = try self.strtab.insert(gpa, self.getSymbolName(global)); @@ -3445,6 +4050,15 @@ fn writeSymtab(self: *MachO) !SymtabCtx { try imports_table.putNoClobber(global, new_index); } + // We generate stabs last in order to ensure that the strtab always has debug info + // strings trailing + if (!self.base.options.strip) { + assert(self.d_sym == null); // TODO + for (self.objects.items) |object| { + try self.generateSymbolStabs(object, &locals); + } + } + const nlocals = @as(u32, @intCast(locals.items.len)); const nexports = @as(u32, @intCast(exports.items.len)); const nimports = @as(u32, @intCast(imports.items.len)); @@ -3478,7 +4092,218 @@ fn writeSymtab(self: *MachO) !SymtabCtx { }; } -fn writeStrtab(self: *MachO) !void { +fn generateSymbolStabs( + self: *MachO, + object: Object, + locals: *std.ArrayList(macho.nlist_64), +) !void { + log.debug("generating stabs for '{s}'", .{object.name}); + + const gpa = self.base.allocator; + var debug_info = object.parseDwarfInfo(); + + var lookup = DwarfInfo.AbbrevLookupTable.init(gpa); + defer lookup.deinit(); + try lookup.ensureUnusedCapacity(std.math.maxInt(u8)); + + // We assume there is only one CU. + var cu_it = debug_info.getCompileUnitIterator(); + const compile_unit = while (try cu_it.next()) |cu| { + const offset = math.cast(usize, cu.cuh.debug_abbrev_offset) orelse return error.Overflow; + try debug_info.genAbbrevLookupByKind(offset, &lookup); + break cu; + } else { + log.debug("no compile unit found in debug info in {s}; skipping", .{object.name}); + return; + }; + + var abbrev_it = compile_unit.getAbbrevEntryIterator(debug_info); + const cu_entry: DwarfInfo.AbbrevEntry = while (try abbrev_it.next(lookup)) |entry| switch (entry.tag) { + dwarf.TAG.compile_unit => break entry, + else => continue, + } else { + log.debug("missing DWARF_TAG_compile_unit tag in {s}; skipping", .{object.name}); + return; + }; + + var maybe_tu_name: ?[]const u8 = null; + var maybe_tu_comp_dir: ?[]const u8 = null; + var attr_it = cu_entry.getAttributeIterator(debug_info, compile_unit.cuh); + + while (try attr_it.next()) |attr| switch (attr.name) { + dwarf.AT.comp_dir => maybe_tu_comp_dir = attr.getString(debug_info, compile_unit.cuh) orelse continue, + dwarf.AT.name => maybe_tu_name = attr.getString(debug_info, compile_unit.cuh) orelse continue, + else => continue, + }; + + if (maybe_tu_name == null or maybe_tu_comp_dir == null) { + log.debug("missing DWARF_AT_comp_dir and DWARF_AT_name attributes {s}; skipping", .{object.name}); + return; + } + + const tu_name = maybe_tu_name.?; + const tu_comp_dir = maybe_tu_comp_dir.?; + + // Open scope + try locals.ensureUnusedCapacity(3); + locals.appendAssumeCapacity(.{ + .n_strx = try self.strtab.insert(gpa, tu_comp_dir), + .n_type = macho.N_SO, + .n_sect = 0, + .n_desc = 0, + .n_value = 0, + }); + locals.appendAssumeCapacity(.{ + .n_strx = try self.strtab.insert(gpa, tu_name), + .n_type = macho.N_SO, + .n_sect = 0, + .n_desc = 0, + .n_value = 0, + }); + locals.appendAssumeCapacity(.{ + .n_strx = try self.strtab.insert(gpa, object.name), + .n_type = macho.N_OSO, + .n_sect = 0, + .n_desc = 1, + .n_value = object.mtime, + }); + + var stabs_buf: [4]macho.nlist_64 = undefined; + + var name_lookup: ?DwarfInfo.SubprogramLookupByName = if (object.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS == 0) blk: { + var name_lookup = DwarfInfo.SubprogramLookupByName.init(gpa); + errdefer name_lookup.deinit(); + try name_lookup.ensureUnusedCapacity(@as(u32, @intCast(object.atoms.items.len))); + try debug_info.genSubprogramLookupByName(compile_unit, lookup, &name_lookup); + break :blk name_lookup; + } else null; + defer if (name_lookup) |*nl| nl.deinit(); + + for (object.atoms.items) |atom_index| { + const atom = self.getAtom(atom_index); + const stabs = try self.generateSymbolStabsForSymbol( + atom_index, + atom.getSymbolWithLoc(), + name_lookup, + &stabs_buf, + ); + try locals.appendSlice(stabs); + + var it = Atom.getInnerSymbolsIterator(self, atom_index); + while (it.next()) |sym_loc| { + const contained_stabs = try self.generateSymbolStabsForSymbol( + atom_index, + sym_loc, + name_lookup, + &stabs_buf, + ); + try locals.appendSlice(contained_stabs); + } + } + + // Close scope + try locals.append(.{ + .n_strx = 0, + .n_type = macho.N_SO, + .n_sect = 0, + .n_desc = 0, + .n_value = 0, + }); +} + +fn generateSymbolStabsForSymbol( + self: *MachO, + atom_index: Atom.Index, + sym_loc: SymbolWithLoc, + lookup: ?DwarfInfo.SubprogramLookupByName, + buf: *[4]macho.nlist_64, +) ![]const macho.nlist_64 { + const gpa = self.base.allocator; + const object = self.objects.items[sym_loc.getFile().?]; + const sym = self.getSymbol(sym_loc); + const sym_name = self.getSymbolName(sym_loc); + const header = self.sections.items(.header)[sym.n_sect - 1]; + + if (sym.n_strx == 0) return buf[0..0]; + if (self.symbolIsTemp(sym_loc)) return buf[0..0]; + + if (!header.isCode()) { + // Since we are not dealing with machine code, it's either a global or a static depending + // on the linkage scope. + if (sym.sect() and sym.ext()) { + // Global gets an N_GSYM stab type. + buf[0] = .{ + .n_strx = try self.strtab.insert(gpa, sym_name), + .n_type = macho.N_GSYM, + .n_sect = sym.n_sect, + .n_desc = 0, + .n_value = 0, + }; + } else { + // Local static gets an N_STSYM stab type. + buf[0] = .{ + .n_strx = try self.strtab.insert(gpa, sym_name), + .n_type = macho.N_STSYM, + .n_sect = sym.n_sect, + .n_desc = 0, + .n_value = sym.n_value, + }; + } + return buf[0..1]; + } + + const size: u64 = size: { + if (object.header.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0) { + break :size self.getAtom(atom_index).size; + } + + // Since we don't have subsections to work with, we need to infer the size of each function + // the slow way by scanning the debug info for matching symbol names and extracting + // the symbol's DWARF_AT_low_pc and DWARF_AT_high_pc values. + const source_sym = object.getSourceSymbol(sym_loc.sym_index) orelse return buf[0..0]; + const subprogram = lookup.?.get(sym_name[1..]) orelse return buf[0..0]; + + if (subprogram.addr <= source_sym.n_value and source_sym.n_value < subprogram.addr + subprogram.size) { + break :size subprogram.size; + } else { + log.debug("no stab found for {s}", .{sym_name}); + return buf[0..0]; + } + }; + + buf[0] = .{ + .n_strx = 0, + .n_type = macho.N_BNSYM, + .n_sect = sym.n_sect, + .n_desc = 0, + .n_value = sym.n_value, + }; + buf[1] = .{ + .n_strx = try self.strtab.insert(gpa, sym_name), + .n_type = macho.N_FUN, + .n_sect = sym.n_sect, + .n_desc = 0, + .n_value = sym.n_value, + }; + buf[2] = .{ + .n_strx = 0, + .n_type = macho.N_FUN, + .n_sect = 0, + .n_desc = 0, + .n_value = size, + }; + buf[3] = .{ + .n_strx = 0, + .n_type = macho.N_ENSYM, + .n_sect = sym.n_sect, + .n_desc = 0, + .n_value = size, + }; + + return buf; +} + +pub fn writeStrtab(self: *MachO) !void { const gpa = self.base.allocator; const seg = self.getLinkeditSegmentPtr(); const offset = seg.fileoff + seg.filesize; @@ -3507,7 +4332,7 @@ const SymtabCtx = struct { imports_table: std.AutoHashMap(SymbolWithLoc, u32), }; -fn writeDysymtab(self: *MachO, ctx: SymtabCtx) !void { +pub fn writeDysymtab(self: *MachO, ctx: SymtabCtx) !void { const gpa = self.base.allocator; const nstubs = @as(u32, @intCast(self.stub_table.lookup.count())); const ngot_entries = @as(u32, @intCast(self.got_table.lookup.count())); @@ -3582,7 +4407,7 @@ fn writeDysymtab(self: *MachO, ctx: SymtabCtx) !void { self.dysymtab_cmd.nindirectsyms = nindirectsyms; } -fn writeUuid(self: *MachO, comp: *const Compilation, uuid_cmd_offset: u32, has_codesig: bool) !void { +pub fn writeUuid(self: *MachO, comp: *const Compilation, uuid_cmd_offset: u32, has_codesig: bool) !void { const file_size = if (!has_codesig) blk: { const seg = self.getLinkeditSegmentPtr(); break :blk seg.fileoff + seg.filesize; @@ -3592,7 +4417,7 @@ fn writeUuid(self: *MachO, comp: *const Compilation, uuid_cmd_offset: u32, has_c try self.base.file.?.pwriteAll(&self.uuid_cmd.uuid, offset); } -fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void { +pub fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void { const seg = self.getLinkeditSegmentPtr(); // Code signature data has to be 16-bytes aligned for Apple tools to recognize the file // https://github.com/opensource-apple/cctools/blob/fdb4825f303fd5c0751be524babd32958181b3ed/libstuff/checkout.c#L271 @@ -3609,8 +4434,9 @@ fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void { self.codesig_cmd.datasize = @as(u32, @intCast(needed_size)); } -fn writeCodeSignature(self: *MachO, comp: *const Compilation, code_sig: *CodeSignature) !void { - const seg = self.getSegment(self.text_section_index.?); +pub fn writeCodeSignature(self: *MachO, comp: *const Compilation, code_sig: *CodeSignature) !void { + const seg_id = self.header_segment_cmd_index.?; + const seg = self.segments.items[seg_id]; const offset = self.codesig_cmd.dataoff; var buffer = std.ArrayList(u8).init(self.base.allocator); @@ -3634,14 +4460,10 @@ fn writeCodeSignature(self: *MachO, comp: *const Compilation, code_sig: *CodeSig } /// Writes Mach-O file header. -fn writeHeader(self: *MachO, ncmds: u32, sizeofcmds: u32) !void { +pub fn writeHeader(self: *MachO, ncmds: u32, sizeofcmds: u32) !void { var header: macho.mach_header_64 = .{}; header.flags = macho.MH_NOUNDEFS | macho.MH_DYLDLINK | macho.MH_PIE | macho.MH_TWOLEVEL; - if (!self.base.options.single_threaded) { - header.flags |= macho.MH_HAS_TLV_DESCRIPTORS; - } - switch (self.base.options.target.cpu.arch) { .aarch64 => { header.cputype = macho.CPU_TYPE_ARM64; @@ -3666,6 +4488,13 @@ fn writeHeader(self: *MachO, ncmds: u32, sizeofcmds: u32) !void { else => unreachable, } + if (self.thread_vars_section_index) |sect_id| { + header.flags |= macho.MH_HAS_TLV_DESCRIPTORS; + if (self.sections.items(.header)[sect_id].size > 0) { + header.flags |= macho.MH_HAS_TLV_DESCRIPTORS; + } + } + header.ncmds = ncmds; header.sizeofcmds = sizeofcmds; @@ -3830,20 +4659,33 @@ pub fn symbolIsTemp(self: *MachO, sym_with_loc: SymbolWithLoc) bool { /// Returns pointer-to-symbol described by `sym_with_loc` descriptor. pub fn getSymbolPtr(self: *MachO, sym_with_loc: SymbolWithLoc) *macho.nlist_64 { - assert(sym_with_loc.getFile() == null); - return &self.locals.items[sym_with_loc.sym_index]; + if (sym_with_loc.getFile()) |file| { + const object = &self.objects.items[file]; + return &object.symtab[sym_with_loc.sym_index]; + } else { + return &self.locals.items[sym_with_loc.sym_index]; + } } /// Returns symbol described by `sym_with_loc` descriptor. pub fn getSymbol(self: *const MachO, sym_with_loc: SymbolWithLoc) macho.nlist_64 { - assert(sym_with_loc.getFile() == null); - return self.locals.items[sym_with_loc.sym_index]; + if (sym_with_loc.getFile()) |file| { + const object = &self.objects.items[file]; + return object.symtab[sym_with_loc.sym_index]; + } else { + return self.locals.items[sym_with_loc.sym_index]; + } } /// Returns name of the symbol described by `sym_with_loc` descriptor. pub fn getSymbolName(self: *const MachO, sym_with_loc: SymbolWithLoc) []const u8 { - const sym = self.getSymbol(sym_with_loc); - return self.strtab.get(sym.n_strx).?; + if (sym_with_loc.getFile()) |file| { + const object = self.objects.items[file]; + return object.getSymbolName(sym_with_loc.sym_index); + } else { + const sym = self.locals.items[sym_with_loc.sym_index]; + return self.strtab.get(sym.n_strx).?; + } } /// Returns pointer to the global entry for `name` if one exists. @@ -3945,6 +4787,19 @@ pub inline fn getPageSize(cpu_arch: std.Target.Cpu.Arch) u16 { }; } +pub inline fn requiresThunks(self: MachO) bool { + return self.base.options.target.cpu.arch == .aarch64; +} + +pub fn requiresCodeSignature(self: MachO) bool { + if (self.base.options.entitlements) |_| return true; + const cpu_arch = self.base.options.target.cpu.arch; + const os_tag = self.base.options.target.os.tag; + const abi = self.base.options.target.abi; + if (cpu_arch == .aarch64 and (os_tag == .macos or abi == .simulator)) return true; + return false; +} + pub fn getSegmentPrecedence(segname: []const u8) u4 { if (mem.eql(u8, segname, "__PAGEZERO")) return 0x0; if (mem.eql(u8, segname, "__TEXT")) return 0x1; @@ -3988,24 +4843,26 @@ pub fn getSectionPrecedence(header: macho.section_64) u8 { return (@as(u8, @intCast(segment_precedence)) << 4) + section_precedence; } -pub fn reportUndefined(self: *MachO, ctx: anytype) !void { - const count = ctx.unresolved.count(); +pub fn reportUndefined(self: *MachO) !void { + const count = self.unresolved.count(); if (count == 0) return; const gpa = self.base.allocator; try self.misc_errors.ensureUnusedCapacity(gpa, count); - for (ctx.unresolved.keys()) |global_index| { - const global = ctx.globals.items[global_index]; - const sym_name = ctx.getSymbolName(global); + for (self.unresolved.keys()) |global_index| { + const global = self.globals.items[global_index]; + const sym_name = self.getSymbolName(global); const nnotes: usize = if (global.getFile() == null) @as(usize, 0) else 1; var notes = try std.ArrayList(File.ErrorMsg).initCapacity(gpa, nnotes); defer notes.deinit(); if (global.getFile()) |file| { - const note = try std.fmt.allocPrint(gpa, "referenced in {s}", .{ctx.objects.items[file].name}); + const note = try std.fmt.allocPrint(gpa, "referenced in {s}", .{ + self.objects.items[file].name, + }); notes.appendAssumeCapacity(.{ .msg = note }); } @@ -4051,6 +4908,19 @@ pub fn lsearch(comptime T: type, haystack: []align(1) const T, predicate: anytyp return i; } +pub fn logSegments(self: *MachO) void { + log.debug("segments:", .{}); + for (self.segments.items, 0..) |segment, i| { + log.debug(" segment({d}): {s} @{x} ({x}), sizeof({x})", .{ + i, + segment.segName(), + segment.fileoff, + segment.vmaddr, + segment.vmsize, + }); + } +} + pub fn logSections(self: *MachO) void { log.debug("sections:", .{}); for (self.sections.items(.header), 0..) |header, i| { @@ -4065,9 +4935,7 @@ pub fn logSections(self: *MachO) void { } } -fn logSymAttributes(sym: macho.nlist_64, buf: *[4]u8) []const u8 { - @memset(buf[0..4], '_'); - @memset(buf[4..], ' '); +fn logSymAttributes(sym: macho.nlist_64, buf: []u8) []const u8 { if (sym.sect()) { buf[0] = 's'; } @@ -4090,56 +4958,110 @@ fn logSymAttributes(sym: macho.nlist_64, buf: *[4]u8) []const u8 { pub fn logSymtab(self: *MachO) void { var buf: [4]u8 = undefined; - log.debug("symtab:", .{}); + const scoped_log = std.log.scoped(.symtab); + + scoped_log.debug("locals:", .{}); + for (self.objects.items, 0..) |object, id| { + scoped_log.debug(" object({d}): {s}", .{ id, object.name }); + if (object.in_symtab == null) continue; + for (object.symtab, 0..) |sym, sym_id| { + @memset(&buf, '_'); + scoped_log.debug(" %{d}: {s} @{x} in sect({d}), {s}", .{ + sym_id, + object.getSymbolName(@as(u32, @intCast(sym_id))), + sym.n_value, + sym.n_sect, + logSymAttributes(sym, &buf), + }); + } + } + scoped_log.debug(" object(-1)", .{}); for (self.locals.items, 0..) |sym, sym_id| { - const where = if (sym.undf() and !sym.tentative()) "ord" else "sect"; - const def_index = if (sym.undf() and !sym.tentative()) - @divTrunc(sym.n_desc, macho.N_SYMBOL_RESOLVER) - else - sym.n_sect + 1; - log.debug(" %{d}: {?s} @{x} in {s}({d}), {s}", .{ + if (sym.undf()) continue; + scoped_log.debug(" %{d}: {s} @{x} in sect({d}), {s}", .{ sym_id, - self.strtab.get(sym.n_strx), + self.strtab.get(sym.n_strx).?, sym.n_value, - where, - def_index, + sym.n_sect, logSymAttributes(sym, &buf), }); } - log.debug("globals table:", .{}); - for (self.globals.items) |global| { - const name = self.getSymbolName(global); - log.debug(" {s} => %{d} in object({?d})", .{ name, global.sym_index, global.file }); + scoped_log.debug("exports:", .{}); + for (self.globals.items, 0..) |global, i| { + const sym = self.getSymbol(global); + if (sym.undf()) continue; + if (sym.n_desc == MachO.N_DEAD) continue; + scoped_log.debug(" %{d}: {s} @{x} in sect({d}), {s} (def in object({?}))", .{ + i, + self.getSymbolName(global), + sym.n_value, + sym.n_sect, + logSymAttributes(sym, &buf), + global.file, + }); + } + + scoped_log.debug("imports:", .{}); + for (self.globals.items, 0..) |global, i| { + const sym = self.getSymbol(global); + if (!sym.undf()) continue; + if (sym.n_desc == MachO.N_DEAD) continue; + const ord = @divTrunc(sym.n_desc, macho.N_SYMBOL_RESOLVER); + scoped_log.debug(" %{d}: {s} @{x} in ord({d}), {s}", .{ + i, + self.getSymbolName(global), + sym.n_value, + ord, + logSymAttributes(sym, &buf), + }); } - log.debug("GOT entries:", .{}); - log.debug("{}", .{self.got_table}); + scoped_log.debug("GOT entries:", .{}); + scoped_log.debug("{}", .{self.got_table}); + + scoped_log.debug("TLV pointers:", .{}); + scoped_log.debug("{}", .{self.tlv_ptr_table}); - log.debug("stubs entries:", .{}); - log.debug("{}", .{self.stub_table}); + scoped_log.debug("stubs entries:", .{}); + scoped_log.debug("{}", .{self.stubs_table}); + + scoped_log.debug("thunks:", .{}); + for (self.thunks.items, 0..) |thunk, i| { + scoped_log.debug(" thunk({d})", .{i}); + const slice = thunk.targets.slice(); + for (slice.items(.tag), slice.items(.target), 0..) |tag, target, j| { + const atom_index = @as(u32, @intCast(thunk.getStartAtomIndex() + j)); + const atom = self.getAtom(atom_index); + const atom_sym = self.getSymbol(atom.getSymbolWithLoc()); + const target_addr = switch (tag) { + .stub => self.getStubsEntryAddress(target).?, + .atom => self.getSymbol(target).n_value, + }; + scoped_log.debug(" {d}@{x} => {s}({s}@{x})", .{ + j, + atom_sym.n_value, + @tagName(tag), + self.getSymbolName(target), + target_addr, + }); + } + } } pub fn logAtoms(self: *MachO) void { log.debug("atoms:", .{}); - const slice = self.sections.slice(); - for (slice.items(.last_atom_index), 0..) |last_atom_index, i| { - var atom_index = last_atom_index orelse continue; - const header = slice.items(.header)[i]; - - while (true) { - const atom = self.getAtom(atom_index); - if (atom.prev_index) |prev_index| { - atom_index = prev_index; - } else break; - } + for (slice.items(.first_atom_index), 0..) |first_atom_index, sect_id| { + var atom_index = first_atom_index orelse continue; + const header = slice.items(.header)[sect_id]; log.debug("{s},{s}", .{ header.segName(), header.sectName() }); while (true) { - self.logAtom(atom_index); const atom = self.getAtom(atom_index); + self.logAtom(atom_index, log); + if (atom.next_index) |next_index| { atom_index = next_index; } else break; @@ -4147,18 +5069,50 @@ pub fn logAtoms(self: *MachO) void { } } -pub fn logAtom(self: *MachO, atom_index: Atom.Index) void { +pub fn logAtom(self: *MachO, atom_index: Atom.Index, logger: anytype) void { + if (!build_options.enable_logging) return; + const atom = self.getAtom(atom_index); - const sym = atom.getSymbol(self); - const sym_name = atom.getName(self); - log.debug(" ATOM(%{?d}, '{s}') @ {x} sizeof({x}) in object({?d}) in sect({d})", .{ - atom.getSymbolIndex(), + const sym = self.getSymbol(atom.getSymbolWithLoc()); + const sym_name = self.getSymbolName(atom.getSymbolWithLoc()); + logger.debug(" ATOM({d}, %{d}, '{s}') @ {x} (sizeof({x}), alignof({x})) in object({?}) in sect({d})", .{ + atom_index, + atom.sym_index, sym_name, sym.n_value, atom.size, - atom.file, - sym.n_sect + 1, + atom.alignment, + atom.getFile(), + sym.n_sect, }); + + if (atom.getFile() != null) { + var it = Atom.getInnerSymbolsIterator(self, atom_index); + while (it.next()) |sym_loc| { + const inner = self.getSymbol(sym_loc); + const inner_name = self.getSymbolName(sym_loc); + const offset = Atom.calcInnerSymbolOffset(self, atom_index, sym_loc.sym_index); + + logger.debug(" (%{d}, '{s}') @ {x} ({x})", .{ + sym_loc.sym_index, + inner_name, + inner.n_value, + offset, + }); + } + + if (Atom.getSectionAlias(self, atom_index)) |sym_loc| { + const alias = self.getSymbol(sym_loc); + const alias_name = self.getSymbolName(sym_loc); + + logger.debug(" (%{d}, '{s}') @ {x} ({x})", .{ + sym_loc.sym_index, + alias_name, + alias.n_value, + 0, + }); + } + } } const MachO = @This(); @@ -4197,6 +5151,7 @@ const Cache = std.Build.Cache; const CodeSignature = @import("MachO/CodeSignature.zig"); const Compilation = @import("../Compilation.zig"); const Dwarf = File.Dwarf; +const DwarfInfo = @import("DwarfInfo.zig"); const Dylib = @import("MachO/Dylib.zig"); const File = link.File; const Object = @import("MachO/Object.zig"); |
