diff --git a/src/link/MachO.zig b/src/link/MachO.zig index b557eb350e..99424b6f40 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -63,9 +63,6 @@ dso_handle_index: ?Symbol.Index = null, objc_msg_send_index: ?Symbol.Index = null, entry_index: ?Symbol.Index = null, -/// List of atoms that are either synthetic or map directly to the Zig source program. -atoms: std.ArrayListUnmanaged(Atom) = .{}, -atoms_extra: std.ArrayListUnmanaged(u32) = .{}, thunks: std.ArrayListUnmanaged(Thunk) = .{}, /// String interning table @@ -549,13 +546,14 @@ pub fn flushModule(self: *MachO, arena: Allocator, tid: Zcu.PerThread.Id, prog_n try dead_strip.gcAtoms(self); } - self.checkDuplicates() catch |err| switch (err) { - error.HasDuplicates => return error.FlushFailure, - else => |e| { - try self.reportUnexpectedError("unexpected error while checking for duplicate symbol definitions", .{}); - return e; - }, - }; + // TODO + // self.checkDuplicates() catch |err| switch (err) { + // error.HasDuplicates => return error.FlushFailure, + // else => |e| { + // try self.reportUnexpectedError("unexpected error while checking for duplicate symbol definitions", .{}); + // return e; + // }, + // }; self.markImportsAndExports(); self.deadStripDylibs(); diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index 30bdce7d9e..fcbb31f4ed 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -12,7 +12,11 @@ symtab: std.MultiArrayList(Nlist) = .{}, strtab: std.ArrayListUnmanaged(u8) = .{}, symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, +symbols_extra: std.ArrayListUnmanaged(u32) = .{}, +globals: std.ArrayListUnmanaged(MachO.SymbolResolver.Index) = .{}, atoms: std.ArrayListUnmanaged(Atom.Index) = .{}, +atoms_indexes: std.ArrayListUnmanaged(Atom.Index) = .{}, +atoms_extra: std.ArrayListUnmanaged(u32) = .{}, platform: ?MachO.Platform = null, compile_unit: ?CompileUnit = null, @@ -30,6 +34,7 @@ data_in_code: std.ArrayListUnmanaged(macho.data_in_code_entry) = .{}, alive: bool = true, hidden: bool = false, +compact_unwind_ctx: CompactUnwindCtx = .{}, output_symtab_ctx: MachO.SymtabCtx = .{}, output_ar_state: Archive.ArState = .{}, @@ -51,7 +56,11 @@ pub fn deinit(self: *Object, allocator: Allocator) void { self.symtab.deinit(allocator); self.strtab.deinit(allocator); self.symbols.deinit(allocator); + self.symbols_extra.deinit(allocator); + self.globals.deinit(allocator); self.atoms.deinit(allocator); + self.atoms_indexes.deinit(allocator); + self.atoms_extra.deinit(allocator); self.cies.deinit(allocator); self.fdes.deinit(allocator); self.eh_frame_data.deinit(allocator); @@ -70,6 +79,10 @@ pub fn parse(self: *Object, macho_file: *MachO) !void { const gpa = macho_file.base.comp.gpa; const handle = macho_file.getFileHandle(self.file_handle); + const cpu_arch = macho_file.getTarget().cpu.arch; + + // Atom at index 0 is reserved as null atom + try self.atoms.append(gpa, .{ .extra = try self.addAtomExtra(gpa, .{}) }); var header_buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined; { @@ -86,7 +99,7 @@ pub fn parse(self: *Object, macho_file: *MachO) !void { return error.InvalidCpuArch; }, }; - if (macho_file.getTarget().cpu.arch != this_cpu_arch) { + if (cpu_arch != this_cpu_arch) { try macho_file.reportParseError2(self.index, "invalid cpu architecture: {s}", .{@tagName(this_cpu_arch)}); return error.InvalidCpuArch; } @@ -198,20 +211,20 @@ pub fn parse(self: *Object, macho_file: *MachO) !void { mem.sort(NlistIdx, nlists.items, self, NlistIdx.lessThan); if (self.hasSubsections()) { - try self.initSubsections(nlists.items, macho_file); + try self.initSubsections(gpa, nlists.items); } else { - try self.initSections(nlists.items, macho_file); + try self.initSections(gpa, nlists.items); } - try self.initCstringLiterals(macho_file); - try self.initFixedSizeLiterals(macho_file); - try self.initPointerLiterals(macho_file); + try self.initCstringLiterals(gpa, handle, macho_file); + try self.initFixedSizeLiterals(gpa, macho_file); + try self.initPointerLiterals(gpa, macho_file); try self.linkNlistToAtom(macho_file); try self.sortAtoms(macho_file); - try self.initSymbols(macho_file); - try self.initSymbolStabs(nlists.items, macho_file); - try self.initRelocs(macho_file); + try self.initSymbols(gpa, macho_file); + try self.initSymbolStabs(gpa, nlists.items, macho_file); + try self.initRelocs(handle, cpu_arch, macho_file); // Parse DWARF __TEXT,__eh_frame section if (self.eh_frame_sect_index) |index| { @@ -224,13 +237,13 @@ pub fn parse(self: *Object, macho_file: *MachO) !void { } if (self.hasUnwindRecords() or self.hasEhFrameRecords()) { - try self.parseUnwindRecords(gpa, macho_file.getTarget().cpu.arch, macho_file); + try self.parseUnwindRecords(gpa, cpu_arch, macho_file); } if (self.platform) |platform| { if (!macho_file.platform.eqlTarget(platform)) { try macho_file.reportParseError2(self.index, "invalid platform: {}", .{ - platform.fmtTarget(macho_file.getTarget().cpu.arch), + platform.fmtTarget(cpu_arch), }); return error.InvalidTarget; } @@ -247,7 +260,7 @@ pub fn parse(self: *Object, macho_file: *MachO) !void { } for (self.atoms.items) |atom_index| { - const atom = macho_file.getAtom(atom_index).?; + const atom = self.getAtom(atom_index) orelse continue; const isec = atom.getInputSection(macho_file); if (mem.eql(u8, isec.sectName(), "__eh_frame") or mem.eql(u8, isec.sectName(), "__compact_unwind") or @@ -276,10 +289,9 @@ pub fn isPtrLiteral(sect: macho.section_64) bool { return sect.type() == macho.S_LITERAL_POINTERS; } -fn initSubsections(self: *Object, nlists: anytype, macho_file: *MachO) !void { +fn initSubsections(self: *Object, allocator: Allocator, nlists: anytype) !void { const tracy = trace(@src()); defer tracy.end(); - const gpa = macho_file.base.comp.gpa; const slice = self.sections.slice(); for (slice.items(.header), slice.items(.subsections), 0..) |sect, *subsections, n_sect| { if (isCstringLiteral(sect)) continue; @@ -294,17 +306,18 @@ fn initSubsections(self: *Object, nlists: anytype, macho_file: *MachO) !void { } else nlists.len; if (nlist_start == nlist_end or nlists[nlist_start].nlist.n_value > sect.addr) { - const name = try std.fmt.allocPrintZ(gpa, "{s}${s}", .{ sect.segName(), sect.sectName() }); - defer gpa.free(name); + const name = try std.fmt.allocPrintZ(allocator, "{s}${s}", .{ sect.segName(), sect.sectName() }); + defer allocator.free(name); const size = if (nlist_start == nlist_end) sect.size else nlists[nlist_start].nlist.n_value - sect.addr; - const atom_index = try self.addAtom(.{ - .name = try self.addString(gpa, name), + const atom_index = try self.addAtom(allocator, .{ + .name = try self.addString(allocator, name), .n_sect = @intCast(n_sect), .off = 0, .size = size, .alignment = sect.@"align", - }, macho_file); - try subsections.append(gpa, .{ + }); + try self.atoms_indexes.append(allocator, atom_index); + try subsections.append(allocator, .{ .atom = atom_index, .off = 0, }); @@ -327,14 +340,15 @@ fn initSubsections(self: *Object, nlists: anytype, macho_file: *MachO) !void { @min(@ctz(nlist.nlist.n_value), sect.@"align") else sect.@"align"; - const atom_index = try self.addAtom(.{ + const atom_index = try self.addAtom(allocator, .{ .name = nlist.nlist.n_strx, .n_sect = @intCast(n_sect), .off = nlist.nlist.n_value - sect.addr, .size = size, .alignment = alignment, - }, macho_file); - try subsections.append(gpa, .{ + }); + try self.atoms_indexes.append(allocator, atom_index); + try subsections.append(allocator, .{ .atom = atom_index, .off = nlist.nlist.n_value - sect.addr, }); @@ -346,30 +360,31 @@ fn initSubsections(self: *Object, nlists: anytype, macho_file: *MachO) !void { } } -fn initSections(self: *Object, nlists: anytype, macho_file: *MachO) !void { +fn initSections(self: *Object, allocator: Allocator, nlists: anytype) !void { const tracy = trace(@src()); defer tracy.end(); - const gpa = macho_file.base.comp.gpa; const slice = self.sections.slice(); - try self.atoms.ensureUnusedCapacity(gpa, self.sections.items(.header).len); + try self.atoms.ensureUnusedCapacity(allocator, self.sections.items(.header).len); + try self.atoms_indexes.ensureUnusedCapacity(allocator, self.sections.items(.header).len); for (slice.items(.header), 0..) |sect, n_sect| { if (isCstringLiteral(sect)) continue; if (isFixedSizeLiteral(sect)) continue; if (isPtrLiteral(sect)) continue; - const name = try std.fmt.allocPrintZ(gpa, "{s}${s}", .{ sect.segName(), sect.sectName() }); - defer gpa.free(name); + const name = try std.fmt.allocPrintZ(allocator, "{s}${s}", .{ sect.segName(), sect.sectName() }); + defer allocator.free(name); - const atom_index = try self.addAtom(.{ - .name = try self.addString(gpa, name), + const atom_index = try self.addAtom(allocator, .{ + .name = try self.addString(allocator, name), .n_sect = @intCast(n_sect), .off = 0, .size = sect.size, .alignment = sect.@"align", - }, macho_file); - try slice.items(.subsections)[n_sect].append(gpa, .{ .atom = atom_index, .off = 0 }); + }); + try self.atoms_indexes.append(allocator, atom_index); + try slice.items(.subsections)[n_sect].append(allocator, .{ .atom = atom_index, .off = 0 }); const nlist_start = for (nlists, 0..) |nlist, i| { if (nlist.nlist.n_sect - 1 == n_sect) break i; @@ -398,21 +413,25 @@ fn initSections(self: *Object, nlists: anytype, macho_file: *MachO) !void { } } -fn initCstringLiterals(self: *Object, macho_file: *MachO) !void { +fn initCstringLiterals(self: *Object, allocator: Allocator, file: File.Handle, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const gpa = macho_file.base.comp.gpa; const slice = self.sections.slice(); for (slice.items(.header), 0..) |sect, n_sect| { if (!isCstringLiteral(sect)) continue; - const data = try self.getSectionData(@intCast(n_sect), macho_file); - defer gpa.free(data); + const sect_size = math.cast(usize, sect.size) orelse return error.Overflow; + const data = try allocator.alloc(u8, sect_size); + defer allocator.free(data); + const amt = try file.preadAll(data, sect.offset + self.offset); + if (amt != data.len) return error.InputOutput; + var count: u32 = 0; var start: u32 = 0; while (start < data.len) { + defer count += 1; var end = start; while (end < data.len - 1 and data[end] != 0) : (end += 1) {} if (data[end] != 0) { @@ -425,32 +444,52 @@ fn initCstringLiterals(self: *Object, macho_file: *MachO) !void { } end += 1; - const atom_index = try self.addAtom(.{ - .name = 0, + const name = try std.fmt.allocPrintZ(allocator, "l._str{d}", .{count}); + defer allocator.free(name); + const name_str = try self.addString(allocator, name); + + const atom_index = try self.addAtom(allocator, .{ + .name = name_str, .n_sect = @intCast(n_sect), .off = start, .size = end - start, .alignment = sect.@"align", - }, macho_file); - try slice.items(.subsections)[n_sect].append(gpa, .{ + }); + try self.atoms_indexes.append(allocator, atom_index); + try slice.items(.subsections)[n_sect].append(allocator, .{ .atom = atom_index, .off = start, }); + const atom = self.getAtom(atom_index).?; + const nlist_index: u32 = @intCast(try self.symtab.addOne(allocator)); + self.symtab.set(nlist_index, .{ + .nlist = .{ + .n_strx = name_str, + .n_type = macho.N_SECT, + .n_sect = @intCast(atom.n_sect + 1), + .n_desc = 0, + .n_value = atom.getInputAddress(macho_file), + }, + .size = atom.size, + .atom = atom_index, + }); + atom.addExtra(.{ .literal_symbol_index = nlist_index }, macho_file); + start = end; } } } -fn initFixedSizeLiterals(self: *Object, macho_file: *MachO) !void { +fn initFixedSizeLiterals(self: *Object, allocator: Allocator, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const gpa = macho_file.base.comp.gpa; const slice = self.sections.slice(); for (slice.items(.header), 0..) |sect, n_sect| { if (!isFixedSizeLiteral(sect)) continue; + const rec_size: u8 = switch (sect.type()) { macho.S_4BYTE_LITERALS => 4, macho.S_8BYTE_LITERALS => 8, @@ -465,28 +504,52 @@ fn initFixedSizeLiterals(self: *Object, macho_file: *MachO) !void { ); return error.MalformedObject; } + var pos: u32 = 0; - while (pos < sect.size) : (pos += rec_size) { - const atom_index = try self.addAtom(.{ - .name = 0, + var count: u32 = 0; + while (pos < sect.size) : ({ + pos += rec_size; + count += 1; + }) { + const name = try std.fmt.allocPrintZ(allocator, "l._literal{d}", .{count}); + defer allocator.free(name); + const name_str = try self.addString(allocator, name); + + const atom_index = try self.addAtom(allocator, .{ + .name = name_str, .n_sect = @intCast(n_sect), .off = pos, .size = rec_size, .alignment = sect.@"align", - }, macho_file); - try slice.items(.subsections)[n_sect].append(gpa, .{ + }); + try self.atoms_indexes.append(allocator, atom_index); + try slice.items(.subsections)[n_sect].append(allocator, .{ .atom = atom_index, .off = pos, }); + + const atom = self.getAtom(atom_index).?; + const nlist_index: u32 = @intCast(try self.symtab.addOne(allocator)); + self.symtab.set(nlist_index, .{ + .nlist = .{ + .n_strx = name_str, + .n_type = macho.N_SECT, + .n_sect = @intCast(atom.n_sect + 1), + .n_desc = 0, + .n_value = atom.getInputAddress(macho_file), + }, + .size = atom.size, + .atom = atom_index, + }); + atom.addExtra(.{ .literal_symbol_index = nlist_index }, macho_file); } } } -fn initPointerLiterals(self: *Object, macho_file: *MachO) !void { +fn initPointerLiterals(self: *Object, allocator: Allocator, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const gpa = macho_file.base.comp.gpa; const slice = self.sections.slice(); for (slice.items(.header), 0..) |sect, n_sect| { @@ -505,134 +568,171 @@ fn initPointerLiterals(self: *Object, macho_file: *MachO) !void { for (0..num_ptrs) |i| { const pos: u32 = @as(u32, @intCast(i)) * rec_size; - const atom_index = try self.addAtom(.{ - .name = 0, + + const name = try std.fmt.allocPrintZ(allocator, "l._ptr{d}", .{i}); + defer allocator.free(name); + const name_str = try self.addString(allocator, name); + + const atom_index = try self.addAtom(allocator, .{ + .name = name_str, .n_sect = @intCast(n_sect), .off = pos, .size = rec_size, .alignment = sect.@"align", - }, macho_file); - try slice.items(.subsections)[n_sect].append(gpa, .{ + }); + try self.atoms_indexes.append(allocator, atom_index); + try slice.items(.subsections)[n_sect].append(allocator, .{ .atom = atom_index, .off = pos, }); + + const atom = self.getAtom(atom_index).?; + const nlist_index: u32 = @intCast(try self.symtab.addOne(allocator)); + self.symtab.set(nlist_index, .{ + .nlist = .{ + .n_strx = name_str, + .n_type = macho.N_SECT, + .n_sect = @intCast(atom.n_sect + 1), + .n_desc = 0, + .n_value = atom.getInputAddress(macho_file), + }, + .size = atom.size, + .atom = atom_index, + }); + atom.addExtra(.{ .literal_symbol_index = nlist_index }, macho_file); } } } pub fn resolveLiterals(self: Object, lp: *MachO.LiteralPool, macho_file: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); + const gpa = macho_file.base.comp.gpa; + const file = macho_file.getFileHandle(self.file_handle); var buffer = std.ArrayList(u8).init(gpa); defer buffer.deinit(); + var sections_data = std.AutoHashMap(u32, []const u8).init(gpa); + try sections_data.ensureTotalCapacity(@intCast(self.sections.items(.header).len)); + defer { + var it = sections_data.iterator(); + while (it.next()) |entry| { + gpa.free(entry.value_ptr.*); + } + sections_data.deinit(); + } + const slice = self.sections.slice(); - for (slice.items(.header), slice.items(.subsections), 0..) |header, subs, n_sect| { + for (slice.items(.header), slice.items(.subsections)) |header, subs| { if (isCstringLiteral(header) or isFixedSizeLiteral(header)) { - const data = try self.getSectionData(@intCast(n_sect), macho_file); + const sect_size = math.cast(usize, header.size) orelse return error.Overflow; + const data = try gpa.alloc(u8, sect_size); defer gpa.free(data); + const amt = try file.preadAll(data, header.offset + self.offset); + if (amt != data.len) return error.InputOutput; for (subs.items) |sub| { - const atom = macho_file.getAtom(sub.atom).?; + const atom = self.getAtom(sub.atom).?; const atom_off = math.cast(usize, atom.off) orelse return error.Overflow; const atom_size = math.cast(usize, atom.size) orelse return error.Overflow; const atom_data = data[atom_off..][0..atom_size]; const res = try lp.insert(gpa, header.type(), atom_data); if (!res.found_existing) { - res.atom.* = sub.atom; + res.ref.* = .{ .index = atom.getExtra(macho_file).literal_symbol_index, .file = self.index }; + } else { + const lp_sym = lp.getSymbol(res.index, macho_file); + const lp_atom = lp_sym.getAtom(macho_file).?; + lp_atom.alignment = @max(lp_atom.alignment, atom.alignment); + atom.flags.alive = false; } - atom.flags.literal_pool = true; - try atom.addExtra(.{ .literal_index = res.index }, macho_file); + atom.addExtra(.{ .literal_pool_index = res.index }, macho_file); } } else if (isPtrLiteral(header)) { for (subs.items) |sub| { - const atom = macho_file.getAtom(sub.atom).?; + const atom = self.getAtom(sub.atom).?; const relocs = atom.getRelocs(macho_file); assert(relocs.len == 1); const rel = relocs[0]; const target = switch (rel.tag) { - .local => rel.target, - .@"extern" => rel.getTargetSymbol(macho_file).atom, + .local => rel.getTargetAtom(atom.*, macho_file), + .@"extern" => rel.getTargetSymbol(atom.*, macho_file).getAtom(macho_file).?, }; const addend = math.cast(u32, rel.addend) orelse return error.Overflow; - const target_atom = macho_file.getAtom(target).?; - const target_atom_size = math.cast(usize, target_atom.size) orelse return error.Overflow; - try buffer.ensureUnusedCapacity(target_atom_size); - buffer.resize(target_atom_size) catch unreachable; - try target_atom.getData(macho_file, buffer.items); + const target_size = math.cast(usize, target.size) orelse return error.Overflow; + try buffer.ensureUnusedCapacity(target_size); + buffer.resize(target_size) catch unreachable; + const gop = try sections_data.getOrPut(target.n_sect); + if (!gop.found_existing) { + const target_sect = slice.items(.header)[target.n_sect]; + const target_sect_size = math.cast(usize, target_sect.size) orelse return error.Overflow; + const data = try gpa.alloc(u8, target_sect_size); + const amt = try file.preadAll(data, target_sect.offset + self.offset); + if (amt != data.len) return error.InputOutput; + gop.value_ptr.* = data; + } + const data = gop.value_ptr.*; + const target_off = math.cast(usize, target.off) orelse return error.Overflow; + @memcpy(buffer.items, data[target_off..][0..target_size]); const res = try lp.insert(gpa, header.type(), buffer.items[addend..]); buffer.clearRetainingCapacity(); if (!res.found_existing) { - res.atom.* = sub.atom; + res.ref.* = .{ .index = atom.getExtra(macho_file).literal_symbol_index, .file = self.index }; + } else { + const lp_sym = lp.getSymbol(res.index, macho_file); + const lp_atom = lp_sym.getAtom(macho_file).?; + lp_atom.alignment = @max(lp_atom.alignment, atom.alignment); + atom.flags.alive = false; } - atom.flags.literal_pool = true; - try atom.addExtra(.{ .literal_index = res.index }, macho_file); + atom.addExtra(.{ .literal_pool_index = res.index }, macho_file); } } } } -pub fn dedupLiterals(self: Object, lp: MachO.LiteralPool, macho_file: *MachO) void { - for (self.atoms.items) |atom_index| { - const atom = macho_file.getAtom(atom_index) orelse continue; +pub fn dedupLiterals(self: *Object, lp: MachO.LiteralPool, macho_file: *MachO) void { + const tracy = trace(@src()); + defer tracy.end(); + + for (self.getAtoms()) |atom_index| { + const atom = self.getAtom(atom_index) orelse continue; if (!atom.flags.alive) continue; - if (!atom.flags.relocs) continue; const relocs = blk: { - const extra = atom.getExtra(macho_file).?; + const extra = atom.getExtra(macho_file); const relocs = self.sections.items(.relocs)[atom.n_sect].items; break :blk relocs[extra.rel_index..][0..extra.rel_count]; }; - for (relocs) |*rel| switch (rel.tag) { - .local => { - const target = macho_file.getAtom(rel.target).?; - if (target.getLiteralPoolIndex(macho_file)) |lp_index| { - const lp_atom = lp.getAtom(lp_index, macho_file); - if (target.atom_index != lp_atom.atom_index) { - lp_atom.alignment = lp_atom.alignment.max(target.alignment); - target.flags.alive = false; - rel.target = lp_atom.atom_index; - } - } - }, - .@"extern" => { - const target_sym = rel.getTargetSymbol(macho_file); - if (target_sym.getAtom(macho_file)) |target_atom| { - if (target_atom.getLiteralPoolIndex(macho_file)) |lp_index| { - const lp_atom = lp.getAtom(lp_index, macho_file); - if (target_atom.atom_index != lp_atom.atom_index) { - lp_atom.alignment = lp_atom.alignment.max(target_atom.alignment); - target_atom.flags.alive = false; - target_sym.atom = lp_atom.atom_index; - } - } - } - }, - }; + for (relocs) |*rel| { + if (rel.tag != .@"extern") continue; + const target_sym_ref = rel.getTargetSymbolRef(atom.*, macho_file); + const file = target_sym_ref.getFile(macho_file) orelse continue; + if (file.getIndex() != self.index) continue; + const target_sym = target_sym_ref.getSymbol(macho_file).?; + const target_atom = target_sym.getAtom(macho_file) orelse continue; + const isec = target_atom.getInputSection(macho_file); + if (!Object.isCstringLiteral(isec) and !Object.isFixedSizeLiteral(isec) and !Object.isPtrLiteral(isec)) continue; + const lp_index = target_atom.getExtra(macho_file).literal_pool_index; + const lp_sym = lp.getSymbol(lp_index, macho_file); + const lp_atom_ref = lp_sym.atom_ref; + if (target_atom.atom_index != lp_atom_ref.index or target_atom.file != lp_atom_ref.file) { + target_sym.atom_ref = lp_atom_ref; + } + } } -} -const AddAtomArgs = struct { - name: u32, - n_sect: u8, - off: u64, - size: u64, - alignment: u32, -}; - -fn addAtom(self: *Object, args: AddAtomArgs, macho_file: *MachO) !Atom.Index { - const gpa = macho_file.base.comp.gpa; - const atom_index = try macho_file.addAtom(); - const atom = macho_file.getAtom(atom_index).?; - atom.file = self.index; - atom.atom_index = atom_index; - atom.name = args.name; - atom.n_sect = args.n_sect; - atom.size = args.size; - atom.alignment = Atom.Alignment.fromLog2Units(args.alignment); - atom.off = args.off; - try self.atoms.append(gpa, atom_index); - return atom_index; + for (self.symbols.items) |*sym| { + const atom = sym.getAtom(macho_file) orelse continue; + const isec = atom.getInputSection(macho_file); + if (!Object.isCstringLiteral(isec) and !Object.isFixedSizeLiteral(isec) and !Object.isPtrLiteral(isec)) continue; + const lp_index = atom.getExtra(macho_file).literal_pool_index; + const lp_sym = lp.getSymbol(lp_index, macho_file); + const lp_atom_ref = lp_sym.atom_ref; + if (atom.atom_index != lp_atom_ref.index or self.index != lp_atom_ref.file) { + sym.atom_ref = lp_atom_ref; + } + } } pub fn findAtom(self: Object, addr: u64) ?Atom.Index { @@ -704,55 +804,59 @@ fn linkNlistToAtom(self: *Object, macho_file: *MachO) !void { } } -fn initSymbols(self: *Object, macho_file: *MachO) !void { +fn initSymbols(self: *Object, allocator: Allocator, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const gpa = macho_file.base.comp.gpa; - const slice = self.symtab.slice(); - try self.symbols.ensureUnusedCapacity(gpa, slice.items(.nlist).len); + const slice = self.symtab.slice(); + const nsyms = slice.items(.nlist).len; + + try self.symbols.ensureTotalCapacityPrecise(allocator, nsyms); + try self.symbols_extra.ensureTotalCapacityPrecise(allocator, nsyms * @sizeOf(Symbol.Extra)); + try self.globals.ensureTotalCapacityPrecise(allocator, nsyms); + self.globals.resize(allocator, nsyms) catch unreachable; + @memset(self.globals.items, 0); for (slice.items(.nlist), slice.items(.atom), 0..) |nlist, atom_index, i| { - if (nlist.ext()) { - const name = self.getString(nlist.n_strx); - const off = try macho_file.strings.insert(gpa, name); - const gop = try macho_file.getOrCreateGlobal(off); - self.symbols.addOneAssumeCapacity().* = gop.index; - if (nlist.undf() and nlist.weakRef()) { - macho_file.getSymbol(gop.index).flags.weak_ref = true; - } - continue; - } + const index = self.addSymbolAssumeCapacity(); + const symbol = &self.symbols.items[index]; + symbol.value = nlist.n_value; + symbol.name = .{ .pos = nlist.n_strx, .len = @intCast(self.getNStrx(nlist.n_strx).len + 1) }; + symbol.nlist_idx = @intCast(i); + symbol.extra = self.addSymbolExtraAssumeCapacity(.{}); - const index = try macho_file.addSymbol(); - self.symbols.appendAssumeCapacity(index); - const symbol = macho_file.getSymbol(index); - symbol.* = .{ - .value = nlist.n_value, - .name = nlist.n_strx, - .nlist_idx = @intCast(i), - .atom = 0, - .file = self.index, - }; - - if (macho_file.getAtom(atom_index)) |atom| { + if (self.getAtom(atom_index)) |atom| { assert(!nlist.abs()); symbol.value -= atom.getInputAddress(macho_file); - symbol.atom = atom_index; + symbol.atom_ref = .{ .index = atom_index, .file = self.index }; } + symbol.flags.weak = nlist.weakDef(); symbol.flags.abs = nlist.abs(); + symbol.flags.tentative = nlist.tentative(); symbol.flags.no_dead_strip = symbol.flags.no_dead_strip or nlist.noDeadStrip(); + symbol.flags.dyn_ref = nlist.n_desc & macho.REFERENCED_DYNAMICALLY != 0; + symbol.flags.interposable = nlist.ext() and (nlist.sect() or nlist.abs()) and macho_file.options.dylib and macho_file.options.namespace == .flat and !nlist.pext(); if (nlist.sect() and self.sections.items(.header)[nlist.n_sect - 1].type() == macho.S_THREAD_LOCAL_VARIABLES) { symbol.flags.tlv = true; } + + if (nlist.ext()) { + if (nlist.undf()) { + symbol.flags.weak_ref = nlist.weakRef(); + } else if (nlist.pext() or (nlist.weakDef() and nlist.weakRef()) or self.hidden) { + symbol.visibility = .hidden; + } else { + symbol.visibility = .global; + } + } } } -fn initSymbolStabs(self: *Object, nlists: anytype, macho_file: *MachO) !void { +fn initSymbolStabs(self: *Object, allocator: Allocator, nlists: anytype, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); @@ -778,14 +882,13 @@ fn initSymbolStabs(self: *Object, nlists: anytype, macho_file: *MachO) !void { if (start == end) return; - const gpa = macho_file.base.comp.gpa; const syms = self.symtab.items(.nlist); const sym_lookup = SymbolLookup{ .ctx = self, .entries = nlists }; // We need to cache nlists by name so that we can properly resolve local N_GSYM stabs. // What happens is `ld -r` will emit an N_GSYM stab for a symbol that may be either an // external or private external. - var addr_lookup = std.StringHashMap(u64).init(gpa); + var addr_lookup = std.StringHashMap(u64).init(allocator); defer addr_lookup.deinit(); for (syms) |sym| { if (sym.sect() and (sym.ext() or sym.pext())) { @@ -834,29 +937,35 @@ fn initSymbolStabs(self: *Object, nlists: anytype, macho_file: *MachO) !void { return error.MalformedObject; }, } - try sf.stabs.append(gpa, stab); + try sf.stabs.append(allocator, stab); } - try self.stab_files.append(gpa, sf); + try self.stab_files.append(allocator, sf); } } fn sortAtoms(self: *Object, macho_file: *MachO) !void { - const lessThanAtom = struct { - fn lessThanAtom(ctx: *MachO, lhs: Atom.Index, rhs: Atom.Index) bool { - return ctx.getAtom(lhs).?.getInputAddress(ctx) < ctx.getAtom(rhs).?.getInputAddress(ctx); + const Ctx = struct { + object: *Object, + mfile: *MachO, + + fn lessThanAtom(ctx: @This(), lhs: Atom.Index, rhs: Atom.Index) bool { + return ctx.object.getAtom(lhs).?.getInputAddress(ctx.mfile) < + ctx.object.getAtom(rhs).?.getInputAddress(ctx.mfile); } - }.lessThanAtom; - mem.sort(Atom.Index, self.atoms.items, macho_file, lessThanAtom); + }; + mem.sort(Atom.Index, self.atoms_indexes.items, Ctx{ + .object = self, + .mfile = macho_file, + }, Ctx.lessThanAtom); } -fn initRelocs(self: *Object, macho_file: *MachO) !void { +fn initRelocs(self: *Object, file: File.Handle, cpu_arch: std.Target.Cpu.Arch, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const cpu_arch = macho_file.getTarget().cpu.arch; const slice = self.sections.slice(); - for (slice.items(.header), slice.items(.relocs), 0..) |sect, *out, n_sect| { + for (slice.items(.header), slice.items(.relocs)) |sect, *out| { if (sect.nreloc == 0) continue; // We skip relocs for __DWARF since even in -r mode, the linker is expected to emit // debug symbol stabs in the relocatable. This made me curious why that is. For now, @@ -865,8 +974,8 @@ fn initRelocs(self: *Object, macho_file: *MachO) !void { !mem.eql(u8, sect.sectName(), "__compact_unwind")) continue; switch (cpu_arch) { - .x86_64 => try x86_64.parseRelocs(self, @intCast(n_sect), sect, out, macho_file), - .aarch64 => try aarch64.parseRelocs(self, @intCast(n_sect), sect, out, macho_file), + .x86_64 => try x86_64.parseRelocs(self, sect, out, file, macho_file), + .aarch64 => try aarch64.parseRelocs(self, sect, out, file, macho_file), else => unreachable, } @@ -878,7 +987,7 @@ fn initRelocs(self: *Object, macho_file: *MachO) !void { var next_reloc: u32 = 0; for (subsections.items) |subsection| { - const atom = macho_file.getAtom(subsection.atom).?; + const atom = self.getAtom(subsection.atom).?; if (!atom.flags.alive) continue; if (next_reloc >= relocs.items.len) break; const end_addr = atom.off + atom.size; @@ -887,27 +996,22 @@ fn initRelocs(self: *Object, macho_file: *MachO) !void { while (next_reloc < relocs.items.len and relocs.items[next_reloc].offset < end_addr) : (next_reloc += 1) {} const rel_count = next_reloc - rel_index; - try atom.addExtra(.{ .rel_index = rel_index, .rel_count = rel_count }, macho_file); - atom.flags.relocs = true; + atom.addExtra(.{ .rel_index = @intCast(rel_index), .rel_count = @intCast(rel_count) }, macho_file); } } } -fn initEhFrameRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void { +fn initEhFrameRecords(self: *Object, allocator: Allocator, sect_id: u8, file: File.Handle, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const gpa = macho_file.base.comp.gpa; const nlists = self.symtab.items(.nlist); const slice = self.sections.slice(); const sect = slice.items(.header)[sect_id]; const relocs = slice.items(.relocs)[sect_id]; - // TODO: read into buffer directly - const data = try self.getSectionData(sect_id, macho_file); - defer gpa.free(data); - - try self.eh_frame_data.ensureTotalCapacityPrecise(gpa, data.len); - self.eh_frame_data.appendSliceAssumeCapacity(data); + try self.eh_frame_data.resize(allocator, sect.size); + const amt = try file.preadAll(self.eh_frame_data.items, sect.offset + self.offset); + if (amt != self.eh_frame_data.items.len) return error.InputOutput; // Check for non-personality relocs in FDEs and apply them for (relocs.items, 0..) |rel, i| { @@ -939,12 +1043,12 @@ fn initEhFrameRecords(self: *Object, sect_id: u8, macho_file: *MachO) !void { var it = eh_frame.Iterator{ .data = self.eh_frame_data.items }; while (try it.next()) |rec| { switch (rec.tag) { - .cie => try self.cies.append(gpa, .{ + .cie => try self.cies.append(allocator, .{ .offset = rec.offset, .size = rec.size, .file = self.index, }), - .fde => try self.fdes.append(gpa, .{ + .fde => try self.fdes.append(allocator, .{ .offset = rec.offset, .size = rec.size, .cie = undefined, @@ -1205,8 +1309,7 @@ fn parseUnwindRecords(self: *Object, allocator: Allocator, cpu_arch: std.Target. {} const atom = rec.getAtom(macho_file); - try atom.addExtra(.{ .unwind_index = start, .unwind_count = next_cu - start }, macho_file); - atom.flags.unwind = true; + atom.addExtra(.{ .unwind_index = start, .unwind_count = next_cu - start }, macho_file); } } @@ -1233,11 +1336,31 @@ pub fn parseDebugInfo(self: *Object, macho_file: *MachO) !void { if (debug_info_index == null or debug_abbrev_index == null) return; - const debug_info = try self.getSectionData(@intCast(debug_info_index.?), macho_file); + const slice = self.sections.slice(); + const file = macho_file.getFileHandle(self.file_handle); + const debug_info = blk: { + const sect = slice.items(.header)[debug_info_index.?]; + const data = try gpa.alloc(u8, sect.size); + const amt = try file.preadAll(data, sect.offset + self.offset); + if (amt != data.len) return error.InputOutput; + break :blk data; + }; defer gpa.free(debug_info); - const debug_abbrev = try self.getSectionData(@intCast(debug_abbrev_index.?), macho_file); + const debug_abbrev = blk: { + const sect = slice.items(.header)[debug_abbrev_index.?]; + const data = try gpa.alloc(u8, sect.size); + const amt = try file.preadAll(data, sect.offset + self.offset); + if (amt != data.len) return error.InputOutput; + break :blk data; + }; defer gpa.free(debug_abbrev); - const debug_str = if (debug_str_index) |index| try self.getSectionData(@intCast(index), macho_file) else &[0]u8{}; + const debug_str = if (debug_str_index) |sid| blk: { + const sect = slice.items(.header)[sid]; + const data = try gpa.alloc(u8, sect.size); + const amt = try file.preadAll(data, sect.offset + self.offset); + if (amt != data.len) return error.InputOutput; + break :blk data; + } else &[0]u8{}; defer gpa.free(debug_str); self.compile_unit = self.findCompileUnit(.{ @@ -1347,70 +1470,37 @@ pub fn resolveSymbols(self: *Object, macho_file: *MachO) void { const tracy = trace(@src()); defer tracy.end(); - for (self.symbols.items, 0..) |index, i| { - const nlist_idx = @as(Symbol.Index, @intCast(i)); - const nlist = self.symtab.items(.nlist)[nlist_idx]; - const atom_index = self.symtab.items(.atom)[nlist_idx]; + const gpa = macho_file.base.allocator; + for (self.symtab.items(.nlist), self.symtab.items(.atom), self.globals.items, 0..) |nlist, atom_index, *global, i| { if (!nlist.ext()) continue; - if (nlist.undf() and !nlist.tentative()) continue; if (nlist.sect()) { - const atom = macho_file.getAtom(atom_index).?; - if (!atom.flags.alive) continue; + const atom = self.getAtom(atom_index).?; + if (!atom.alive.load(.seq_cst)) continue; + } + + const gop = try macho_file.resolver.getOrPut(gpa, .{ + .index = @intCast(i), + .file = self.index, + }, macho_file); + if (!gop.found_existing) { + gop.ref.* = .{ .index = 0, .file = 0 }; + } + global.* = gop.index; + + if (nlist.undf() and !nlist.tentative()) continue; + if (gop.ref.getFile(macho_file) == null) { + gop.ref.* = .{ .index = @intCast(i), .file = self.index }; + continue; } - const symbol = macho_file.getSymbol(index); if (self.asFile().getSymbolRank(.{ .archive = !self.alive, .weak = nlist.weakDef(), .tentative = nlist.tentative(), - }) < symbol.getSymbolRank(macho_file)) { - const value = if (nlist.sect()) blk: { - const atom = macho_file.getAtom(atom_index).?; - break :blk nlist.n_value - atom.getInputAddress(macho_file); - } else nlist.n_value; - symbol.value = value; - symbol.atom = atom_index; - symbol.nlist_idx = nlist_idx; - symbol.file = self.index; - symbol.flags.weak = nlist.weakDef(); - symbol.flags.abs = nlist.abs(); - symbol.flags.tentative = nlist.tentative(); - symbol.flags.weak_ref = false; - symbol.flags.dyn_ref = nlist.n_desc & macho.REFERENCED_DYNAMICALLY != 0; - symbol.flags.no_dead_strip = symbol.flags.no_dead_strip or nlist.noDeadStrip(); - // TODO: symbol.flags.interposable = macho_file.base.isDynLib() and macho_file.options.namespace == .flat and !nlist.pext(); - symbol.flags.interposable = false; - - if (nlist.sect() and - self.sections.items(.header)[nlist.n_sect - 1].type() == macho.S_THREAD_LOCAL_VARIABLES) - { - symbol.flags.tlv = true; - } + }) < gop.ref.getSymbol(macho_file).?.getSymbolRank(macho_file)) { + gop.ref.* = .{ .index = @intCast(i), .file = self.index }; } - - // Regardless of who the winner is, we still merge symbol visibility here. - if (nlist.pext() or (nlist.weakDef() and nlist.weakRef()) or self.hidden) { - if (symbol.visibility != .global) { - symbol.visibility = .hidden; - } - } else { - symbol.visibility = .global; - } - } -} - -pub fn resetGlobals(self: *Object, macho_file: *MachO) void { - for (self.symbols.items, 0..) |sym_index, nlist_idx| { - if (!self.symtab.items(.nlist)[nlist_idx].ext()) continue; - const sym = macho_file.getSymbol(sym_index); - const name = sym.name; - const global = sym.flags.global; - const weak_ref = sym.flags.weak_ref; - sym.* = .{}; - sym.name = name; - sym.flags.global = global; - sym.flags.weak_ref = weak_ref; } } @@ -1418,12 +1508,13 @@ pub fn markLive(self: *Object, macho_file: *MachO) void { const tracy = trace(@src()); defer tracy.end(); - for (self.symbols.items, 0..) |index, nlist_idx| { - const nlist = self.symtab.items(.nlist)[nlist_idx]; + for (0..self.symbols.items.len) |i| { + const nlist = self.symtab.items(.nlist)[i]; if (!nlist.ext()) continue; - const sym = macho_file.getSymbol(index); - const file = sym.getFile(macho_file) orelse continue; + const ref = self.getSymbolRef(@intCast(i), macho_file); + const file = ref.getFile(macho_file) orelse continue; + const sym = ref.getSymbol(macho_file).?; const should_keep = nlist.undf() or (nlist.tentative() and !sym.flags.tentative); if (should_keep and file == .object and !file.object.alive) { file.object.alive = true; @@ -1432,30 +1523,47 @@ pub fn markLive(self: *Object, macho_file: *MachO) void { } } -pub fn checkDuplicates(self: *Object, dupes: anytype, macho_file: *MachO) error{OutOfMemory}!void { - for (self.symbols.items, 0..) |index, nlist_idx| { - const sym = macho_file.getSymbol(index); - if (sym.visibility != .global) continue; - const file = sym.getFile(macho_file) orelse continue; - if (file.getIndex() == self.index) continue; +pub fn mergeSymbolVisibility(self: *Object, macho_file: *MachO) void { + const tracy = trace(@src()); + defer tracy.end(); - const nlist = self.symtab.items(.nlist)[nlist_idx]; - if (!nlist.undf() and !nlist.tentative() and !(nlist.weakDef() or nlist.pext())) { - const gop = try dupes.getOrPut(index); - if (!gop.found_existing) { - gop.value_ptr.* = .{}; - } - try gop.value_ptr.append(macho_file.base.comp.gpa, self.index); + for (self.symbols.items, 0..) |sym, i| { + const ref = self.getSymbolRef(@intCast(i), macho_file); + const global = ref.getSymbol(macho_file) orelse continue; + if (global.visibility != .global) { + global.visibility = sym.visibility; + } + if (sym.flags.weak_ref) { + global.flags.weak_ref = true; } } } +// TODO +// pub fn checkDuplicates(self: *Object, dupes: anytype, macho_file: *MachO) error{OutOfMemory}!void { +// for (self.symbols.items, 0..) |index, nlist_idx| { +// const sym = macho_file.getSymbol(index); +// if (sym.visibility != .global) continue; +// const file = sym.getFile(macho_file) orelse continue; +// if (file.getIndex() == self.index) continue; + +// const nlist = self.symtab.items(.nlist)[nlist_idx]; +// if (!nlist.undf() and !nlist.tentative() and !(nlist.weakDef() or nlist.pext())) { +// const gop = try dupes.getOrPut(index); +// if (!gop.found_existing) { +// gop.value_ptr.* = .{}; +// } +// try gop.value_ptr.append(macho_file.base.comp.gpa, self.index); +// } +// } +// } + pub fn scanRelocs(self: *Object, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - for (self.atoms.items) |atom_index| { - const atom = macho_file.getAtom(atom_index).?; + for (self.getAtoms()) |atom_index| { + const atom = self.getAtom(atom_index) orelse continue; if (!atom.flags.alive) continue; const sect = atom.getInputSection(macho_file); if (sect.isZerofill()) continue; @@ -1480,38 +1588,35 @@ pub fn convertTentativeDefinitions(self: *Object, macho_file: *MachO) !void { defer tracy.end(); const gpa = macho_file.base.comp.gpa; - for (self.symbols.items, 0..) |index, i| { - const sym = macho_file.getSymbol(index); + for (self.symbols.items, self.globals.items, 0..) |*sym, off, i| { if (!sym.flags.tentative) continue; - const sym_file = sym.getFile(macho_file).?; - if (sym_file.getIndex() != self.index) continue; + if (macho_file.resolver.get(off).?.file != self.index) continue; const nlist_idx = @as(Symbol.Index, @intCast(i)); const nlist = &self.symtab.items(.nlist)[nlist_idx]; const nlist_atom = &self.symtab.items(.atom)[nlist_idx]; - const atom_index = try macho_file.addAtom(); - try self.atoms.append(gpa, atom_index); - const name = try std.fmt.allocPrintZ(gpa, "__DATA$__common${s}", .{sym.getName(macho_file)}); defer gpa.free(name); - const atom = macho_file.getAtom(atom_index).?; - atom.atom_index = atom_index; - atom.name = try self.addString(gpa, name); - atom.file = self.index; - atom.size = nlist.n_value; - atom.alignment = Atom.Alignment.fromLog2Units((nlist.n_desc >> 8) & 0x0f); + const alignment = (nlist.n_desc >> 8) & 0x0f; const n_sect = try self.addSection(gpa, "__DATA", "__common"); + const atom_index = try self.addAtom(gpa, .{ + .name = try self.addString(gpa, name), + .n_sect = n_sect, + .off = 0, + .size = nlist.n_value, + .alignment = alignment, + }); + try self.atoms_indexes.append(gpa, atom_index); + const sect = &self.sections.items(.header)[n_sect]; sect.flags = macho.S_ZEROFILL; - sect.size = atom.size; - sect.@"align" = atom.alignment.toLog2Units(); - atom.n_sect = n_sect; + sect.size = nlist.n_value; + sect.@"align" = alignment; sym.value = 0; - sym.atom = atom_index; - sym.flags.global = true; + sym.atom_ref = .{ .index = atom_index, .file = self.index }; sym.flags.weak = false; sym.flags.weak_ref = false; sym.flags.tentative = false; @@ -1525,6 +1630,56 @@ pub fn convertTentativeDefinitions(self: *Object, macho_file: *MachO) !void { } } +pub fn claimUnresolved(self: *Object, macho_file: *MachO) void { + const tracy = trace(@src()); + defer tracy.end(); + + for (self.symbols.items, 0..) |*sym, i| { + const nlist = self.symtab.items(.nlist)[i]; + if (!nlist.ext()) continue; + if (!nlist.undf()) continue; + + if (self.getSymbolRef(@intCast(i), macho_file).getFile(macho_file) != null) continue; + + const is_import = switch (macho_file.options.undefined_treatment) { + .@"error" => false, + .warn, .suppress => nlist.weakRef(), + .dynamic_lookup => true, + }; + if (is_import) { + sym.value = 0; + sym.atom_ref = .{ .index = 0, .file = 0 }; + sym.flags.weak = false; + sym.flags.weak_ref = nlist.weakRef(); + sym.flags.import = is_import; + sym.visibility = .global; + + const idx = self.globals.items[i]; + macho_file.resolver.values.items[idx - 1] = .{ .index = @intCast(i), .file = self.index }; + } + } +} + +pub fn claimUnresolvedRelocatable(self: *Object, macho_file: *MachO) void { + const tracy = trace(@src()); + defer tracy.end(); + + for (self.symbols.items, self.symtab.items(.nlist), 0..) |*sym, nlist, i| { + if (!nlist.ext()) continue; + if (!nlist.undf()) continue; + if (self.getSymbolRef(@intCast(i), macho_file).getFile(macho_file) != null) continue; + + sym.value = 0; + sym.atom_ref = .{ .index = 0, .file = 0 }; + sym.flags.weak_ref = nlist.weakRef(); + sym.flags.import = true; + sym.visibility = .global; + + const idx = self.globals.items[i]; + macho_file.resolver.values.items[idx - 1] = .{ .index = @intCast(i), .file = self.index }; + } +} + fn addSection(self: *Object, allocator: Allocator, segname: []const u8, sectname: []const u8) !u32 { const n_sect = @as(u32, @intCast(try self.sections.addOne(allocator))); self.sections.set(n_sect, .{ @@ -1646,19 +1801,22 @@ pub fn calcSymtabSize(self: *Object, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - for (self.symbols.items) |sym_index| { - const sym = macho_file.getSymbol(sym_index); - const file = sym.getFile(macho_file) orelse continue; + const is_obj = macho_file.base.isObject(); + + for (self.symbols.items, 0..) |*sym, i| { + const ref = self.getSymbolRef(@intCast(i), macho_file); + const file = ref.getFile(macho_file) orelse continue; if (file.getIndex() != self.index) continue; if (sym.getAtom(macho_file)) |atom| if (!atom.flags.alive) continue; if (sym.isSymbolStab(macho_file)) continue; const name = sym.getName(macho_file); + if (name.len == 0) continue; // TODO in -r mode, we actually want to merge symbol names and emit only one // work it out when emitting relocs - if (name.len > 0 and - (name[0] == 'L' or name[0] == 'l' or + if ((name[0] == 'L' or name[0] == 'l' or mem.startsWith(u8, name, "_OBJC_SELECTOR_REFERENCES_")) and - !macho_file.base.isObject()) continue; + !is_obj) + continue; sym.flags.output_symtab = true; if (sym.isLocal()) { try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file); @@ -1693,9 +1851,9 @@ pub fn calcStabsSize(self: *Object, macho_file: *MachO) error{Overflow}!void { self.output_symtab_ctx.strsize += @as(u32, @intCast(self.path.len + 1)); } - for (self.symbols.items) |sym_index| { - const sym = macho_file.getSymbol(sym_index); - const file = sym.getFile(macho_file) orelse continue; + for (self.symbols.items, 0..) |sym, i| { + const ref = self.getSymbolRef(@intCast(i), macho_file); + const file = ref.getFile(macho_file) orelse continue; if (file.getIndex() != self.index) continue; if (!sym.flags.output_symtab) continue; if (macho_file.base.isObject()) { @@ -1732,28 +1890,204 @@ pub fn calcStabsSize(self: *Object, macho_file: *MachO) error{Overflow}!void { } } -pub fn writeSymtab(self: Object, macho_file: *MachO, ctx: anytype) error{Overflow}!void { +pub fn writeAtoms(self: *Object, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - for (self.symbols.items) |sym_index| { - const sym = macho_file.getSymbol(sym_index); - const file = sym.getFile(macho_file) orelse continue; + const gpa = macho_file.base.comp.gpa; + const headers = self.sections.items(.header); + const sections_data = try gpa.alloc([]const u8, headers.len); + defer { + for (sections_data) |data| { + gpa.free(data); + } + gpa.free(sections_data); + } + @memset(sections_data, &[0]u8{}); + const file = macho_file.getFileHandle(self.file_handle); + + for (headers, 0..) |header, n_sect| { + if (header.isZerofill()) continue; + const data = try gpa.alloc(u8, header.size); + const amt = try file.preadAll(data, header.offset + self.offset); + if (amt != data.len) return error.InputOutput; + sections_data[n_sect] = data; + } + for (self.getAtoms()) |atom_index| { + const atom = self.getAtom(atom_index) orelse continue; + if (!atom.flags.alive) continue; + const sect = atom.getInputSection(macho_file); + if (sect.isZerofill()) continue; + const off = atom.value; + const buffer = macho_file.sections.items(.out)[atom.out_n_sect].items; + const data = sections_data[atom.n_sect]; + @memcpy(buffer[off..][0..atom.size], data[atom.off..][0..atom.size]); + try atom.resolveRelocs(macho_file, buffer[off..][0..atom.size]); + } +} + +pub fn writeAtomsRelocatable(self: *Object, macho_file: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const gpa = macho_file.base.allocator; + const headers = self.sections.items(.header); + const sections_data = try gpa.alloc([]const u8, headers.len); + defer { + for (sections_data) |data| { + gpa.free(data); + } + gpa.free(sections_data); + } + @memset(sections_data, &[0]u8{}); + const file = macho_file.getFileHandle(self.file_handle); + + for (headers, 0..) |header, n_sect| { + if (header.isZerofill()) continue; + const data = try gpa.alloc(u8, header.size); + const amt = try file.preadAll(data, header.offset + self.offset); + if (amt != data.len) return error.InputOutput; + sections_data[n_sect] = data; + } + for (self.getAtoms()) |atom_index| { + const atom = self.getAtom(atom_index) orelse continue; + if (!atom.flags.alive) continue; + const sect = atom.getInputSection(macho_file); + if (sect.isZerofill()) continue; + const off = atom.value; + const buffer = macho_file.sections.items(.out)[atom.out_n_sect].items; + const data = sections_data[atom.n_sect]; + @memcpy(buffer[off..][0..atom.size], data[atom.off..][0..atom.size]); + const relocs = macho_file.sections.items(.relocs)[atom.out_n_sect].items; + const extra = atom.getExtra(macho_file); + try atom.writeRelocs(macho_file, buffer[off..][0..atom.size], relocs[extra.rel_out_index..][0..extra.rel_out_count]); + } +} + +pub fn calcCompactUnwindSizeRelocatable(self: *Object, macho_file: *MachO) void { + const tracy = trace(@src()); + defer tracy.end(); + + const ctx = &self.compact_unwind_ctx; + + for (self.unwind_records_indexes.items) |irec| { + const rec = self.getUnwindRecord(irec); + if (!rec.alive) continue; + + ctx.rec_count += 1; + ctx.reloc_count += 1; + if (rec.getPersonality(macho_file)) |_| { + ctx.reloc_count += 1; + } + if (rec.getLsdaAtom(macho_file)) |_| { + ctx.reloc_count += 1; + } + } +} + +pub fn writeCompactUnwindRelocatable(self: *Object, macho_file: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const addReloc = struct { + fn addReloc(offset: u32, cpu_arch: std.Target.Cpu.Arch) !macho.relocation_info { + return .{ + .r_address = math.cast(i32, offset) orelse return error.Overflow, + .r_symbolnum = 0, + .r_pcrel = 0, + .r_length = 3, + .r_extern = 0, + .r_type = switch (cpu_arch) { + .aarch64 => @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_UNSIGNED), + .x86_64 => @intFromEnum(macho.reloc_type_x86_64.X86_64_RELOC_UNSIGNED), + else => unreachable, + }, + }; + } + }.addReloc; + + const nsect = macho_file.unwind_info_sect_index.?; + const buffer = macho_file.sections.items(.out)[nsect].items; + const relocs = macho_file.sections.items(.relocs)[nsect].items; + + var rec_index: u32 = self.compact_unwind_ctx.rec_index; + var reloc_index: u32 = self.compact_unwind_ctx.reloc_index; + + for (self.unwind_records_indexes.items) |irec| { + const rec = self.getUnwindRecord(irec); + if (!rec.alive) continue; + + var out: macho.compact_unwind_entry = .{ + .rangeStart = 0, + .rangeLength = rec.length, + .compactUnwindEncoding = rec.enc.enc, + .personalityFunction = 0, + .lsda = 0, + }; + defer rec_index += 1; + + const offset = rec_index * @sizeOf(macho.compact_unwind_entry); + + { + // Function address + const atom = rec.getAtom(macho_file); + const addr = rec.getAtomAddress(macho_file); + out.rangeStart = addr; + var reloc = try addReloc(offset, macho_file.options.cpu_arch.?); + reloc.r_symbolnum = atom.out_n_sect + 1; + relocs[reloc_index] = reloc; + reloc_index += 1; + } + + // Personality function + if (rec.getPersonality(macho_file)) |sym| { + const r_symbolnum = math.cast(u24, sym.getOutputSymtabIndex(macho_file).?) orelse return error.Overflow; + var reloc = try addReloc(offset + 16, macho_file.options.cpu_arch.?); + reloc.r_symbolnum = r_symbolnum; + reloc.r_extern = 1; + relocs[reloc_index] = reloc; + reloc_index += 1; + } + + // LSDA address + if (rec.getLsdaAtom(macho_file)) |atom| { + const addr = rec.getLsdaAddress(macho_file); + out.lsda = addr; + var reloc = try addReloc(offset + 24, macho_file.options.cpu_arch.?); + reloc.r_symbolnum = atom.out_n_sect + 1; + relocs[reloc_index] = reloc; + reloc_index += 1; + } + + @memcpy(buffer[offset..][0..@sizeOf(macho.compact_unwind_entry)], mem.asBytes(&out)); + } +} + +pub fn writeSymtab(self: Object, macho_file: *MachO) void { + const tracy = trace(@src()); + defer tracy.end(); + + var n_strx = self.output_symtab_ctx.stroff; + for (self.symbols.items, 0..) |sym, i| { + const ref = self.getSymbolRef(@intCast(i), macho_file); + const file = ref.getFile(macho_file) orelse continue; if (file.getIndex() != self.index) continue; const idx = sym.getOutputSymtabIndex(macho_file) orelse continue; - const n_strx = @as(u32, @intCast(ctx.strtab.items.len)); - ctx.strtab.appendSliceAssumeCapacity(sym.getName(macho_file)); - ctx.strtab.appendAssumeCapacity(0); - const out_sym = &ctx.symtab.items[idx]; + const out_sym = &macho_file.symtab.items[idx]; out_sym.n_strx = n_strx; sym.setOutputSym(macho_file, out_sym); + const name = sym.getName(macho_file); + @memcpy(macho_file.strtab.items[n_strx..][0..name.len], name); + n_strx += @intCast(name.len); + macho_file.strtab.items[n_strx] = 0; + n_strx += 1; } if (macho_file.base.comp.config.debug_format != .strip and self.hasDebugInfo()) - try self.writeStabs(macho_file, ctx); + try self.writeStabs(n_strx, macho_file); } -pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{Overflow}!void { +pub fn writeStabs(self: *const Object, stroff: u32, macho_file: *MachO) void { const writeFuncStab = struct { inline fn writeFuncStab( n_strx: u32, @@ -1761,7 +2095,7 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O n_value: u64, size: u64, index: u32, - context: anytype, + context: *MachO, ) void { context.symtab.items[index] = .{ .n_strx = 0, @@ -1795,6 +2129,7 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O }.writeFuncStab; var index = self.output_symtab_ctx.istab; + var n_strx = stroff; if (self.compile_unit) |cu| { const comp_dir = cu.getCompDir(self); @@ -1802,10 +2137,7 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O // Open scope // N_SO comp_dir - var n_strx = @as(u32, @intCast(ctx.strtab.items.len)); - ctx.strtab.appendSliceAssumeCapacity(comp_dir); - ctx.strtab.appendAssumeCapacity(0); - ctx.symtab.items[index] = .{ + macho_file.symtab.items[index] = .{ .n_strx = n_strx, .n_type = macho.N_SO, .n_sect = 0, @@ -1813,11 +2145,12 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O .n_value = 0, }; index += 1; + @memcpy(macho_file.strtab.items[n_strx..][0..comp_dir.len], comp_dir); + n_strx += @intCast(comp_dir.len); + macho_file.strtab.items[n_strx] = 0; + n_strx += 1; // N_SO tu_name - n_strx = @as(u32, @intCast(ctx.strtab.items.len)); - ctx.strtab.appendSliceAssumeCapacity(tu_name); - ctx.strtab.appendAssumeCapacity(0); - ctx.symtab.items[index] = .{ + macho_file.symtab.items[index] = .{ .n_strx = n_strx, .n_type = macho.N_SO, .n_sect = 0, @@ -1825,19 +2158,12 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O .n_value = 0, }; index += 1; + @memcpy(macho_file.strtab.items[n_strx..][0..tu_name.len], tu_name); + n_strx += @intCast(tu_name.len); + macho_file.strtab.items[n_strx] = 0; + n_strx += 1; // N_OSO path - n_strx = @as(u32, @intCast(ctx.strtab.items.len)); - if (self.in_archive) |ar| { - ctx.strtab.appendSliceAssumeCapacity(ar.path); - ctx.strtab.appendAssumeCapacity('('); - ctx.strtab.appendSliceAssumeCapacity(self.path); - ctx.strtab.appendAssumeCapacity(')'); - ctx.strtab.appendAssumeCapacity(0); - } else { - ctx.strtab.appendSliceAssumeCapacity(self.path); - ctx.strtab.appendAssumeCapacity(0); - } - ctx.symtab.items[index] = .{ + macho_file.symtab.items[index] = .{ .n_strx = n_strx, .n_type = macho.N_OSO, .n_sect = 0, @@ -1845,10 +2171,27 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O .n_value = self.mtime, }; index += 1; + if (self.in_archive) |ar| { + @memcpy(macho_file.strtab.items[n_strx..][0..ar.path.len], ar.path); + n_strx += @intCast(ar.path.len); + macho_file.strtab.items[n_strx] = '('; + n_strx += 1; + @memcpy(macho_file.strtab.items[n_strx..][0..self.path.len], self.path); + n_strx += @intCast(self.path.len); + macho_file.strtab.items[n_strx] = ')'; + n_strx += 1; + macho_file.strtab.items[n_strx] = 0; + n_strx += 1; + } else { + @memcpy(macho_file.strtab.items[n_strx..][0..self.path.len], self.path); + n_strx += @intCast(self.path.len); + macho_file.strtab.items[n_strx] = 0; + n_strx += 1; + } - for (self.symbols.items) |sym_index| { - const sym = macho_file.getSymbol(sym_index); - const file = sym.getFile(macho_file) orelse continue; + for (self.symbols.items, 0..) |sym, i| { + const ref = self.getSymbolRef(@intCast(i), macho_file); + const file = ref.getFile(macho_file) orelse continue; if (file.getIndex() != self.index) continue; if (!sym.flags.output_symtab) continue; if (macho_file.base.isObject()) { @@ -1858,17 +2201,17 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O const sect = macho_file.sections.items(.header)[sym.out_n_sect]; const sym_n_strx = n_strx: { const symtab_index = sym.getOutputSymtabIndex(macho_file).?; - const osym = ctx.symtab.items[symtab_index]; + const osym = macho_file.symtab.items[symtab_index]; break :n_strx osym.n_strx; }; const sym_n_sect: u8 = if (!sym.flags.abs) @intCast(sym.out_n_sect + 1) else 0; const sym_n_value = sym.getAddress(.{}, macho_file); const sym_size = sym.getSize(macho_file); if (sect.isCode()) { - writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, ctx); + writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, macho_file); index += 4; } else if (sym.visibility == .global) { - ctx.symtab.items[index] = .{ + macho_file.symtab.items[index] = .{ .n_strx = sym_n_strx, .n_type = macho.N_GSYM, .n_sect = sym_n_sect, @@ -1877,7 +2220,7 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O }; index += 1; } else { - ctx.symtab.items[index] = .{ + macho_file.symtab.items[index] = .{ .n_strx = sym_n_strx, .n_type = macho.N_STSYM, .n_sect = sym_n_sect, @@ -1890,7 +2233,7 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O // Close scope // N_SO - ctx.symtab.items[index] = .{ + macho_file.symtab.items[index] = .{ .n_strx = 0, .n_type = macho.N_SO, .n_sect = 0, @@ -1901,12 +2244,13 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O assert(self.hasSymbolStabs()); for (self.stab_files.items) |sf| { + const comp_dir = sf.getCombDir(self); + const tu_name = sf.getTuName(self); + const oso_path = sf.getOsoPath(self); + // Open scope // N_SO comp_dir - var n_strx = @as(u32, @intCast(ctx.strtab.items.len)); - ctx.strtab.appendSliceAssumeCapacity(sf.getCompDir(self)); - ctx.strtab.appendAssumeCapacity(0); - ctx.symtab.items[index] = .{ + macho_file.symtab.items[index] = .{ .n_strx = n_strx, .n_type = macho.N_SO, .n_sect = 0, @@ -1914,11 +2258,12 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O .n_value = 0, }; index += 1; + @memcpy(macho_file.strtab.items[n_strx..][0..comp_dir.len], comp_dir); + n_strx += @intCast(comp_dir.len); + macho_file.strtab.items[n_strx] = 0; + n_strx += 1; // N_SO tu_name - n_strx = @as(u32, @intCast(ctx.strtab.items.len)); - ctx.strtab.appendSliceAssumeCapacity(sf.getTuName(self)); - ctx.strtab.appendAssumeCapacity(0); - ctx.symtab.items[index] = .{ + macho_file.symtab.items[index] = .{ .n_strx = n_strx, .n_type = macho.N_SO, .n_sect = 0, @@ -1926,11 +2271,12 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O .n_value = 0, }; index += 1; + @memcpy(macho_file.strtab.items[n_strx..][0..tu_name.len], tu_name); + n_strx += @intCast(tu_name.len); + macho_file.strtab.items[n_strx] = 0; + n_strx += 1; // N_OSO path - n_strx = @as(u32, @intCast(ctx.strtab.items.len)); - ctx.strtab.appendSliceAssumeCapacity(sf.getOsoPath(self)); - ctx.strtab.appendAssumeCapacity(0); - ctx.symtab.items[index] = .{ + macho_file.symtab.items[index] = .{ .n_strx = n_strx, .n_type = macho.N_OSO, .n_sect = 0, @@ -1938,6 +2284,10 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O .n_value = sf.getOsoModTime(self), }; index += 1; + @memcpy(macho_file.strtab.items[n_strx..][0..oso_path.len], oso_path); + n_strx += @intCast(oso_path.len); + macho_file.strtab.items[n_strx] = 0; + n_strx += 1; for (sf.stabs.items) |stab| { const sym = stab.getSymbol(macho_file) orelse continue; @@ -1946,17 +2296,17 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O if (!sym.flags.output_symtab) continue; const sym_n_strx = n_strx: { const symtab_index = sym.getOutputSymtabIndex(macho_file).?; - const osym = ctx.symtab.items[symtab_index]; + const osym = macho_file.symtab.items[symtab_index]; break :n_strx osym.n_strx; }; const sym_n_sect: u8 = if (!sym.flags.abs) @intCast(sym.out_n_sect + 1) else 0; const sym_n_value = sym.getAddress(.{}, macho_file); const sym_size = sym.getSize(macho_file); if (stab.is_func) { - writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, ctx); + writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, macho_file); index += 4; } else if (sym.visibility == .global) { - ctx.symtab.items[index] = .{ + macho_file.symtab.items[index] = .{ .n_strx = sym_n_strx, .n_type = macho.N_GSYM, .n_sect = sym_n_sect, @@ -1965,7 +2315,7 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O }; index += 1; } else { - ctx.symtab.items[index] = .{ + macho_file.symtab.items[index] = .{ .n_strx = sym_n_strx, .n_type = macho.N_STSYM, .n_sect = sym_n_sect, @@ -1978,7 +2328,7 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O // Close scope // N_SO - ctx.symtab.items[index] = .{ + macho_file.symtab.items[index] = .{ .n_strx = 0, .n_type = macho.N_SO, .n_sect = 0, @@ -1990,36 +2340,6 @@ pub fn writeStabs(self: *const Object, macho_file: *MachO, ctx: anytype) error{O } } -fn getSectionData(self: *const Object, index: u32, macho_file: *MachO) ![]u8 { - const gpa = macho_file.base.comp.gpa; - const slice = self.sections.slice(); - assert(index < slice.items(.header).len); - const sect = slice.items(.header)[index]; - const handle = macho_file.getFileHandle(self.file_handle); - const size = math.cast(usize, sect.size) orelse return error.Overflow; - const buffer = try gpa.alloc(u8, size); - errdefer gpa.free(buffer); - const amt = try handle.preadAll(buffer, sect.offset + self.offset); - if (amt != buffer.len) return error.InputOutput; - return buffer; -} - -pub fn getAtomData(self: *const Object, macho_file: *MachO, atom: Atom, buffer: []u8) !void { - assert(buffer.len == atom.size); - const slice = self.sections.slice(); - const handle = macho_file.getFileHandle(self.file_handle); - const sect = slice.items(.header)[atom.n_sect]; - const amt = try handle.preadAll(buffer, sect.offset + self.offset + atom.off); - if (amt != buffer.len) return error.InputOutput; -} - -pub fn getAtomRelocs(self: *const Object, atom: Atom, macho_file: *MachO) []const Relocation { - if (!atom.flags.relocs) return &[0]Relocation{}; - const extra = atom.getExtra(macho_file).?; - const relocs = self.sections.items(.relocs)[atom.n_sect]; - return relocs.items[extra.rel_index..][0..extra.rel_count]; -} - fn addString(self: *Object, allocator: Allocator, name: [:0]const u8) error{OutOfMemory}!u32 { const off: u32 = @intCast(self.strtab.items.len); try self.strtab.ensureUnusedCapacity(allocator, name.len + 1); @@ -2073,6 +2393,143 @@ pub fn asFile(self: *Object) File { return .{ .object = self }; } +const AddAtomArgs = struct { + name: MachO.String, + n_sect: u8, + off: u64, + size: u64, + alignment: u32, +}; + +fn addAtom(self: *Object, allocator: Allocator, args: AddAtomArgs) !Atom.Index { + const atom_index: Atom.Index = @intCast(self.atoms.items.len); + const atom = try self.atoms.addOne(allocator); + atom.* = .{ + .file = self.index, + .atom_index = atom_index, + .name = args.name, + .n_sect = args.n_sect, + .size = args.size, + .off = args.off, + .extra = try self.addAtomExtra(allocator, .{}), + .alignment = args.alignment, + }; + return atom_index; +} + +pub fn getAtom(self: *Object, atom_index: Atom.Index) ?*Atom { + if (atom_index == 0) return null; + assert(atom_index < self.atoms.items.len); + return &self.atoms.items[atom_index]; +} + +pub fn getAtoms(self: *Object) []const Atom.Index { + return self.atoms_indexes.items; +} + +fn addAtomExtra(self: *Object, allocator: Allocator, extra: Atom.Extra) !u32 { + const fields = @typeInfo(Atom.Extra).Struct.fields; + try self.atoms_extra.ensureUnusedCapacity(allocator, fields.len); + return self.addAtomExtraAssumeCapacity(extra); +} + +fn addAtomExtraAssumeCapacity(self: *Object, extra: Atom.Extra) u32 { + const index = @as(u32, @intCast(self.atoms_extra.items.len)); + const fields = @typeInfo(Atom.Extra).Struct.fields; + inline for (fields) |field| { + self.atoms_extra.appendAssumeCapacity(switch (field.type) { + u32 => @field(extra, field.name), + else => @compileError("bad field type"), + }); + } + return index; +} + +pub fn getAtomExtra(self: Object, index: u32) Atom.Extra { + const fields = @typeInfo(Atom.Extra).Struct.fields; + var i: usize = index; + var result: Atom.Extra = undefined; + inline for (fields) |field| { + @field(result, field.name) = switch (field.type) { + u32 => self.atoms_extra.items[i], + else => @compileError("bad field type"), + }; + i += 1; + } + return result; +} + +pub fn setAtomExtra(self: *Object, index: u32, extra: Atom.Extra) void { + assert(index > 0); + const fields = @typeInfo(Atom.Extra).Struct.fields; + inline for (fields, 0..) |field, i| { + self.atoms_extra.items[index + i] = switch (field.type) { + u32 => @field(extra, field.name), + else => @compileError("bad field type"), + }; + } +} + +fn addSymbol(self: *Object, allocator: Allocator) !Symbol.Index { + try self.symbols.ensureUnusedCapacity(allocator, 1); + return self.addSymbolAssumeCapacity(); +} + +fn addSymbolAssumeCapacity(self: *Object) Symbol.Index { + const index: Symbol.Index = @intCast(self.symbols.items.len); + const symbol = self.symbols.addOneAssumeCapacity(); + symbol.* = .{ .file = self.index }; + return index; +} + +pub fn getSymbolRef(self: Object, index: Symbol.Index, macho_file: *MachO) MachO.Ref { + const global_index = self.globals.items[index]; + if (macho_file.resolver.get(global_index)) |ref| return ref; + return .{ .index = index, .file = self.index }; +} + +pub fn addSymbolExtra(self: *Object, allocator: Allocator, extra: Symbol.Extra) !u32 { + const fields = @typeInfo(Symbol.Extra).Struct.fields; + try self.symbols_extra.ensureUnusedCapacity(allocator, fields.len); + return self.addSymbolExtraAssumeCapacity(extra); +} + +fn addSymbolExtraAssumeCapacity(self: *Object, extra: Symbol.Extra) u32 { + const index = @as(u32, @intCast(self.symbols_extra.items.len)); + const fields = @typeInfo(Symbol.Extra).Struct.fields; + inline for (fields) |field| { + self.symbols_extra.appendAssumeCapacity(switch (field.type) { + u32 => @field(extra, field.name), + else => @compileError("bad field type"), + }); + } + return index; +} + +pub fn getSymbolExtra(self: Object, index: u32) Symbol.Extra { + const fields = @typeInfo(Symbol.Extra).Struct.fields; + var i: usize = index; + var result: Symbol.Extra = undefined; + inline for (fields) |field| { + @field(result, field.name) = switch (field.type) { + u32 => self.symbols_extra.items[i], + else => @compileError("bad field type"), + }; + i += 1; + } + return result; +} + +pub fn setSymbolExtra(self: *Object, index: u32, extra: Symbol.Extra) void { + const fields = @typeInfo(Symbol.Extra).Struct.fields; + inline for (fields, 0..) |field, i| { + self.symbols_extra.items[index + i] = switch (field.type) { + u32 => @field(extra, field.name), + else => @compileError("bad field type"), + }; + } +} + fn addUnwindRecord(self: *Object, allocator: Allocator) !UnwindInfo.Record.Index { try self.unwind_records.ensureUnusedCapacity(allocator, 1); return self.addUnwindRecordAssumeCapacity(); @@ -2124,10 +2581,11 @@ fn formatAtoms( _ = unused_fmt_string; _ = options; const object = ctx.object; + const macho_file = ctx.macho_file; try writer.writeAll(" atoms\n"); - for (object.atoms.items) |atom_index| { - const atom = ctx.macho_file.getAtom(atom_index).?; - try writer.print(" {}\n", .{atom.fmt(ctx.macho_file)}); + for (object.getAtoms()) |atom_index| { + const atom = object.getAtom(atom_index) orelse continue; + try writer.print(" {}\n", .{atom.fmt(macho_file)}); } } @@ -2214,10 +2672,26 @@ fn formatSymtab( _ = unused_fmt_string; _ = options; const object = ctx.object; + const macho_file = ctx.macho_file; try writer.writeAll(" symbols\n"); - for (object.symbols.items) |index| { - const sym = ctx.macho_file.getSymbol(index); - try writer.print(" {}\n", .{sym.fmt(ctx.macho_file)}); + for (object.symbols.items, 0..) |sym, i| { + const ref = object.getSymbolRef(@intCast(i), macho_file); + if (ref.getFile(macho_file) == null) { + // TODO any better way of handling this? + try writer.print(" {s} : unclaimed\n", .{sym.getName(macho_file)}); + } else { + try writer.print(" {}\n", .{ref.getSymbol(macho_file).?.fmt(macho_file)}); + } + } + for (object.stab_files.items) |sf| { + try writer.print(" stabs({s},{s},{s})\n", .{ + sf.getCompDir(object), + sf.getTuName(object), + sf.getOsoPath(object), + }); + for (sf.stabs.items) |stab| { + try writer.print(" {}", .{stab.fmt(object)}); + } } } @@ -2286,8 +2760,47 @@ const StabFile = struct { is_func: bool = true, symbol: ?Symbol.Index = null, - fn getSymbol(stab: Stab, macho_file: *MachO) ?*Symbol { - return if (stab.symbol) |s| macho_file.getSymbol(s) else null; + fn getSymbol(stab: Stab, object: *const Object) ?*Symbol { + const index = stab.index orelse return null; + return &object.symbols.items[index]; + } + + pub fn format( + stab: Stab, + comptime unused_fmt_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = stab; + _ = unused_fmt_string; + _ = options; + _ = writer; + @compileError("do not format stabs directly"); + } + + const StabFormatContext = struct { Stab, *const Object }; + + pub fn fmt(stab: Stab, object: *const Object) std.fmt.Formatter(format2) { + return .{ .data = .{ stab, object } }; + } + + fn format2( + ctx: StabFormatContext, + comptime unused_fmt_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = unused_fmt_string; + _ = options; + const stab, const object = ctx; + const sym = stab.getSymbol(object).?; + if (stab.is_func) { + try writer.print("func({d})", .{stab.index.?}); + } else if (sym.visibility == .global) { + try writer.print("gsym({d})", .{stab.index.?}); + } else { + try writer.print("stsym({d})", .{stab.index.?}); + } } }; }; @@ -2310,6 +2823,13 @@ const InArchive = struct { size: u32, }; +const CompactUnwindCtx = struct { + rec_index: u32 = 0, + rec_count: u32 = 0, + reloc_index: u32 = 0, + reloc_count: u32 = 0, +}; + const x86_64 = struct { fn parseRelocs( self: *const Object,