diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 98c1eb2339..2b4005c1fe 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -290,10 +290,16 @@ pub fn deinit(self: *MachO) void { self.dylibs.deinit(gpa); self.segments.deinit(gpa); - for (self.sections.items(.atoms), self.sections.items(.out), self.sections.items(.thunks)) |*atoms, *out, *thnks| { + for ( + self.sections.items(.atoms), + self.sections.items(.out), + self.sections.items(.thunks), + self.sections.items(.relocs), + ) |*atoms, *out, *thnks, *relocs| { atoms.deinit(gpa); out.deinit(gpa); thnks.deinit(gpa); + relocs.deinit(gpa); } self.sections.deinit(gpa); @@ -1457,10 +1463,10 @@ pub fn dedupLiterals(self: *MachO) !void { fn claimUnresolved(self: *MachO) void { if (self.getZigObject()) |zo| { - zo.claimUnresolved(self); + zo.asFile().claimUnresolved(self); } for (self.objects.items) |index| { - self.getFile(index).?.object.claimUnresolved(self); + self.getFile(index).?.claimUnresolved(self); } } @@ -3987,6 +3993,7 @@ const Section = struct { last_atom_index: Atom.Index = 0, thunks: std.ArrayListUnmanaged(Thunk.Index) = .{}, out: std.ArrayListUnmanaged(u8) = .{}, + relocs: std.ArrayListUnmanaged(macho.relocation_info) = .{}, }; pub const LiteralPool = struct { diff --git a/src/link/MachO/Atom.zig b/src/link/MachO/Atom.zig index d03727aa6a..b928542600 100644 --- a/src/link/MachO/Atom.zig +++ b/src/link/MachO/Atom.zig @@ -1008,6 +1008,7 @@ pub fn writeRelocs(self: Atom, macho_file: *MachO, code: []u8, buffer: []macho.r .r_extern = 0, .r_type = @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_ADDEND), }; + i += 1; } const r_type: macho.reloc_type_arm64 = switch (rel.type) { diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index d283b35a88..7ab128aaeb 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -1636,56 +1636,6 @@ 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.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) !u8 { const n_sect = @as(u8, @intCast(try self.sections.addOne(allocator))); self.sections.set(n_sect, .{ @@ -1936,7 +1886,7 @@ pub fn writeAtomsRelocatable(self: *Object, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const gpa = macho_file.base.allocator; + const gpa = macho_file.base.comp.gpa; const headers = self.sections.items(.header); const sections_data = try gpa.alloc([]const u8, headers.len); defer { @@ -1995,15 +1945,17 @@ pub fn writeCompactUnwindRelocatable(self: *Object, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); + const cpu_arch = macho_file.getTarget().cpu.arch; + const addReloc = struct { - fn addReloc(offset: u32, cpu_arch: std.Target.Cpu.Arch) !macho.relocation_info { + fn addReloc(offset: u32, 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) { + .r_type = switch (arch) { .aarch64 => @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_UNSIGNED), .x86_64 => @intFromEnum(macho.reloc_type_x86_64.X86_64_RELOC_UNSIGNED), else => unreachable, @@ -2039,7 +1991,7 @@ pub fn writeCompactUnwindRelocatable(self: *Object, macho_file: *MachO) !void { 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.?); + var reloc = try addReloc(offset, cpu_arch); reloc.r_symbolnum = atom.out_n_sect + 1; relocs[reloc_index] = reloc; reloc_index += 1; @@ -2048,7 +2000,7 @@ pub fn writeCompactUnwindRelocatable(self: *Object, macho_file: *MachO) !void { // 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.?); + var reloc = try addReloc(offset + 16, cpu_arch); reloc.r_symbolnum = r_symbolnum; reloc.r_extern = 1; relocs[reloc_index] = reloc; @@ -2059,7 +2011,7 @@ pub fn writeCompactUnwindRelocatable(self: *Object, macho_file: *MachO) !void { 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.?); + var reloc = try addReloc(offset + 24, cpu_arch); reloc.r_symbolnum = atom.out_n_sect + 1; relocs[reloc_index] = reloc; reloc_index += 1; diff --git a/src/link/MachO/file.zig b/src/link/MachO/file.zig index 5f52d6eb49..4957ed71c2 100644 --- a/src/link/MachO/file.zig +++ b/src/link/MachO/file.zig @@ -118,7 +118,24 @@ pub const File = union(enum) { }; } + pub fn getNlists(file: File) []macho.nlist_64 { + return switch (file) { + .dylib => unreachable, + .internal => |x| x.symtab.items, + inline else => |x| x.symtab.items(.nlist), + }; + } + + pub fn getGlobals(file: File) []MachO.SymbolResolver.Index { + return switch (file) { + inline else => |x| x.globals.items, + }; + } + pub fn markImportsExports(file: File, macho_file: *MachO) void { + const tracy = trace(@src()); + defer tracy.end(); + const nsyms = switch (file) { .dylib => unreachable, inline else => |x| x.symbols.items.len, @@ -138,7 +155,25 @@ pub const File = union(enum) { } } + pub fn markExportsRelocatable(file: File, macho_file: *MachO) void { + const tracy = trace(@src()); + defer tracy.end(); + + assert(file == .object or file == .zig_object); + + for (file.getSymbols(), 0..) |*sym, i| { + const ref = file.getSymbolRef(@intCast(i), macho_file); + const other_file = ref.getFile(macho_file) orelse continue; + if (other_file.getIndex() != file.getIndex()) continue; + if (sym.visibility != .global) continue; + sym.flags.@"export" = true; + } + } + pub fn createSymbolIndirection(file: File, macho_file: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); + const nsyms = switch (file) { inline else => |x| x.symbols.items.len, }; @@ -166,6 +201,59 @@ pub const File = union(enum) { } } + pub fn claimUnresolved(file: File, macho_file: *MachO) void { + const tracy = trace(@src()); + defer tracy.end(); + + assert(file == .object or file == .zig_object); + + for (file.getSymbols(), file.getNlists(), 0..) |*sym, nlist, i| { + if (!nlist.ext()) continue; + if (!nlist.undf()) continue; + + if (file.getSymbolRef(@intCast(i), macho_file).getFile(macho_file) != null) continue; + + const is_import = switch (macho_file.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 = file.getGlobals()[i]; + macho_file.resolver.values.items[idx - 1] = .{ .index = @intCast(i), .file = file.getIndex() }; + } + } + } + + pub fn claimUnresolvedRelocatable(file: File, macho_file: *MachO) void { + const tracy = trace(@src()); + defer tracy.end(); + + assert(file == .object or file == .zig_object); + + for (file.getSymbols(), file.getNlists(), 0..) |*sym, nlist, i| { + if (!nlist.ext()) continue; + if (!nlist.undf()) continue; + if (file.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 = file.getGlobals()[i]; + macho_file.resolver.values.items[idx - 1] = .{ .index = @intCast(i), .file = file.getIndex() }; + } + } + pub fn initOutputSections(file: File, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); diff --git a/src/link/MachO/relocatable.zig b/src/link/MachO/relocatable.zig index 9dcd7a64ff..0095118c86 100644 --- a/src/link/MachO/relocatable.zig +++ b/src/link/MachO/relocatable.zig @@ -26,70 +26,51 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]c return; } - @panic("TODO -r mode"); + for (positionals.items) |obj| { + macho_file.parsePositional(obj.path, obj.must_link) catch |err| switch (err) { + error.MalformedObject, + error.MalformedArchive, + error.InvalidCpuArch, + error.InvalidTarget, + => continue, // already reported + error.UnknownFileType => try macho_file.reportParseError(obj.path, "unknown file type for an object file", .{}), + else => |e| try macho_file.reportParseError( + obj.path, + "unexpected error: parsing input file failed with error {s}", + .{@errorName(e)}, + ), + }; + } - // for (positionals.items) |obj| { - // macho_file.parsePositional(obj.path, obj.must_link) catch |err| switch (err) { - // error.MalformedObject, - // error.MalformedArchive, - // error.InvalidCpuArch, - // error.InvalidTarget, - // => continue, // already reported - // error.UnknownFileType => try macho_file.reportParseError(obj.path, "unknown file type for an object file", .{}), - // else => |e| try macho_file.reportParseError( - // obj.path, - // "unexpected error: parsing input file failed with error {s}", - // .{@errorName(e)}, - // ), - // }; - // } + if (comp.link_errors.items.len > 0) return error.FlushFailure; - // if (comp.link_errors.items.len > 0) return error.FlushFailure; + try macho_file.resolveSymbols(); + try macho_file.dedupLiterals(); + markExports(macho_file); + claimUnresolved(macho_file); + try initOutputSections(macho_file); + try macho_file.sortSections(); + try macho_file.addAtomsToSections(); + try calcSectionSizes(macho_file); - // try macho_file.addUndefinedGlobals(); - // try macho_file.resolveSymbols(); - // try macho_file.parseDebugInfo(); - // try macho_file.dedupLiterals(); - // markExports(macho_file); - // claimUnresolved(macho_file); - // try initOutputSections(macho_file); - // try macho_file.sortSections(); - // try macho_file.addAtomsToSections(); - // try calcSectionSizes(macho_file); + try createSegment(macho_file); + try allocateSections(macho_file); + allocateSegment(macho_file); - // try createSegment(macho_file); - // try allocateSections(macho_file); - // allocateSegment(macho_file); + if (build_options.enable_logging) { + state_log.debug("{}", .{macho_file.dumpState()}); + } - // var off = off: { - // const seg = macho_file.segments.items[0]; - // const off = math.cast(u32, seg.fileoff + seg.filesize) orelse return error.Overflow; - // break :off mem.alignForward(u32, off, @alignOf(macho.relocation_info)); - // }; - // off = allocateSectionsRelocs(macho_file, off); + try writeSections(macho_file); + sortRelocs(macho_file); + try writeSectionsToFile(macho_file); - // if (build_options.enable_logging) { - // state_log.debug("{}", .{macho_file.dumpState()}); - // } + // In order to please Apple ld (and possibly other MachO linkers in the wild), + // we will now sanitize segment names of Zig-specific segments. + sanitizeZigSections(macho_file); - // try macho_file.calcSymtabSize(); - // try writeAtoms(macho_file); - // try writeCompactUnwind(macho_file); - // try writeEhFrame(macho_file); - - // off = mem.alignForward(u32, off, @alignOf(u64)); - // off = try macho_file.writeDataInCode(0, off); - // off = mem.alignForward(u32, off, @alignOf(u64)); - // off = try macho_file.writeSymtab(off); - // off = mem.alignForward(u32, off, @alignOf(u64)); - // off = try macho_file.writeStrtab(off); - - // // In order to please Apple ld (and possibly other MachO linkers in the wild), - // // we will now sanitize segment names of Zig-specific segments. - // sanitizeZigSections(macho_file); - - // const ncmds, const sizeofcmds = try writeLoadCommands(macho_file); - // try writeHeader(macho_file, ncmds, sizeofcmds); + const ncmds, const sizeofcmds = try writeLoadCommands(macho_file); + try writeHeader(macho_file, ncmds, sizeofcmds); } pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]const u8) link.File.FlushError!void { @@ -353,9 +334,9 @@ pub fn claimUnresolved(macho_file: *MachO) void { fn initOutputSections(macho_file: *MachO) !void { for (macho_file.objects.items) |index| { - const object = macho_file.getFile(index).?.object; - for (object.atoms.items) |atom_index| { - const atom = macho_file.getAtom(atom_index) orelse continue; + const file = macho_file.getFile(index).?; + for (file.getAtoms()) |atom_index| { + const atom = file.getAtom(atom_index) orelse continue; if (!atom.flags.alive) continue; atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(macho_file), macho_file); } @@ -383,69 +364,141 @@ fn calcSectionSizes(macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - const slice = macho_file.sections.slice(); - for (slice.items(.header), slice.items(.atoms)) |*header, atoms| { + for (macho_file.sections.items(.atoms), 0..) |atoms, i| { if (atoms.items.len == 0) continue; - for (atoms.items) |atom_index| { - const atom = macho_file.getAtom(atom_index).?; - const atom_alignment = atom.alignment.toByteUnits() orelse 1; - const offset = mem.alignForward(u64, header.size, atom_alignment); - const padding = offset - header.size; - atom.value = offset; - header.size += padding + atom.size; - header.@"align" = @max(header.@"align", atom.alignment.toLog2Units()); - header.nreloc += atom.calcNumRelocs(macho_file); + calcSectionSize(macho_file, @intCast(i)); + } + + if (macho_file.eh_frame_sect_index) |_| { + try calcEhFrameSize(macho_file); + } + + for (macho_file.objects.items) |index| { + if (macho_file.unwind_info_sect_index) |_| { + macho_file.getFile(index).?.object.calcCompactUnwindSizeRelocatable(macho_file); } + macho_file.getFile(index).?.calcSymtabSize(macho_file); } - if (macho_file.unwind_info_sect_index) |index| { - calcCompactUnwindSize(macho_file, index); - } + try macho_file.data_in_code.updateSize(macho_file); - if (macho_file.eh_frame_sect_index) |index| { - const sect = &macho_file.sections.items(.header)[index]; - sect.size = try eh_frame.calcSize(macho_file); - sect.@"align" = 3; - sect.nreloc = eh_frame.calcNumRelocs(macho_file); - } + calcCompactUnwindSize(macho_file); + calcSymtabSize(macho_file); - if (macho_file.getZigObject()) |zo| { - for (zo.atoms.items) |atom_index| { - const atom = macho_file.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; - const header = &macho_file.sections.items(.header)[atom.out_n_sect]; - if (!macho_file.isZigSection(atom.out_n_sect) and !macho_file.isDebugSection(atom.out_n_sect)) continue; - header.nreloc += atom.calcNumRelocs(macho_file); - } + // TODO + // if (macho_file.getZigObject()) |zo| { + // for (zo.atoms.items) |atom_index| { + // const atom = macho_file.getAtom(atom_index) orelse continue; + // if (!atom.flags.alive) continue; + // const header = &macho_file.sections.items(.header)[atom.out_n_sect]; + // if (!macho_file.isZigSection(atom.out_n_sect) and !macho_file.isDebugSection(atom.out_n_sect)) continue; + // header.nreloc += atom.calcNumRelocs(macho_file); + // } + // } +} + +fn calcSectionSize(macho_file: *MachO, sect_id: u8) void { + const tracy = trace(@src()); + defer tracy.end(); + + const slice = macho_file.sections.slice(); + const header = &slice.items(.header)[sect_id]; + const atoms = slice.items(.atoms)[sect_id].items; + for (atoms) |ref| { + const atom = ref.getAtom(macho_file).?; + const atom_alignment = atom.alignment.toByteUnits() orelse 1; + const offset = mem.alignForward(u64, header.size, atom_alignment); + const padding = offset - header.size; + atom.value = offset; + header.size += padding + atom.size; + header.@"align" = @max(header.@"align", atom.alignment.toLog2Units()); + const nreloc = atom.calcNumRelocs(macho_file); + atom.addExtra(.{ .rel_out_index = header.nreloc, .rel_out_count = nreloc }, macho_file); + header.nreloc += nreloc; } } -fn calcCompactUnwindSize(macho_file: *MachO, sect_index: u8) void { - var size: u32 = 0; +fn calcEhFrameSize(macho_file: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const header = &macho_file.sections.items(.header)[macho_file.eh_frame_sect_index.?]; + header.size = try eh_frame.calcSize(macho_file); + header.@"align" = 3; + header.nreloc = eh_frame.calcNumRelocs(macho_file); +} + +fn calcCompactUnwindSize(macho_file: *MachO) void { + const tracy = trace(@src()); + defer tracy.end(); + + var nrec: u32 = 0; var nreloc: u32 = 0; for (macho_file.objects.items) |index| { - const object = macho_file.getFile(index).?.object; - for (object.unwind_records_indexes.items) |irec| { - const rec = object.getUnwindRecord(irec); - if (!rec.alive) continue; - size += @sizeOf(macho.compact_unwind_entry); - nreloc += 1; - if (rec.getPersonality(macho_file)) |_| { - nreloc += 1; - } - if (rec.getLsdaAtom(macho_file)) |_| { - nreloc += 1; - } - } + const ctx = &macho_file.getFile(index).?.object.compact_unwind_ctx; + ctx.rec_index = nrec; + ctx.reloc_index = nreloc; + nrec += ctx.rec_count; + nreloc += ctx.reloc_count; } - const sect = &macho_file.sections.items(.header)[sect_index]; - sect.size = size; + const sect = &macho_file.sections.items(.header)[macho_file.unwind_info_sect_index.?]; + sect.size = nrec * @sizeOf(macho.compact_unwind_entry); sect.nreloc = nreloc; sect.@"align" = 3; } +fn calcSymtabSize(macho_file: *MachO) void { + const tracy = trace(@src()); + defer tracy.end(); + + var nlocals: u32 = 0; + var nstabs: u32 = 0; + var nexports: u32 = 0; + var nimports: u32 = 0; + var strsize: u32 = 1; + + for (macho_file.objects.items) |index| { + const object = macho_file.getFile(index).?.object; + const ctx = &object.output_symtab_ctx; + ctx.ilocal = nlocals; + ctx.istab = nstabs; + ctx.iexport = nexports; + ctx.iimport = nimports; + ctx.stroff = strsize; + nlocals += ctx.nlocals; + nstabs += ctx.nstabs; + nexports += ctx.nexports; + nimports += ctx.nimports; + strsize += ctx.strsize; + } + + for (macho_file.objects.items) |index| { + const object = macho_file.getFile(index).?.object; + const ctx = &object.output_symtab_ctx; + ctx.istab += nlocals; + ctx.iexport += nlocals + nstabs; + ctx.iimport += nlocals + nstabs + nexports; + } + + { + const cmd = &macho_file.symtab_cmd; + cmd.nsyms = nlocals + nstabs + nexports + nimports; + cmd.strsize = strsize; + } + + { + const cmd = &macho_file.dysymtab_cmd; + cmd.ilocalsym = 0; + cmd.nlocalsym = nlocals + nstabs; + cmd.iextdefsym = nlocals + nstabs; + cmd.nextdefsym = nexports; + cmd.iundefsym = nlocals + nstabs + nexports; + cmd.nundefsym = nimports; + } +} + fn allocateSections(macho_file: *MachO) !void { const slice = macho_file.sections.slice(); for (slice.items(.header)) |*header| { @@ -463,6 +516,37 @@ fn allocateSections(macho_file: *MachO) !void { } header.size = needed_size; } + + var fileoff: u32 = 0; + for (slice.items(.header)) |header| { + fileoff = @max(fileoff, header.offset + @as(u32, @intCast(header.size))); + } + + for (slice.items(.header)) |*header| { + if (header.nreloc == 0) continue; + header.reloff = mem.alignForward(u32, fileoff, @alignOf(macho.relocation_info)); + fileoff = header.reloff + header.nreloc * @sizeOf(macho.relocation_info); + } + + // In -r mode, there is no LINKEDIT segment and so we allocate required LINKEDIT commands + // as if they were detached or part of the single segment. + + // DATA_IN_CODE + { + const cmd = &macho_file.data_in_code_cmd; + cmd.dataoff = fileoff; + fileoff += cmd.datasize; + fileoff = mem.alignForward(u32, fileoff, @alignOf(u64)); + } + + // SYMTAB + { + const cmd = &macho_file.symtab_cmd; + cmd.symoff = fileoff; + fileoff += cmd.nsyms * @sizeOf(macho.nlist_64); + fileoff = mem.alignForward(u32, fileoff, @alignOf(u32)); + cmd.stroff = fileoff; + } } /// Renames segment names in Zig sections to standard MachO segment names such as @@ -525,232 +609,77 @@ fn allocateSegment(macho_file: *MachO) void { seg.filesize = fileoff - seg.fileoff; } -fn allocateSectionsRelocs(macho_file: *MachO, off: u32) u32 { - var fileoff = off; - const slice = macho_file.sections.slice(); - for (slice.items(.header)) |*header| { - if (header.nreloc == 0) continue; - header.reloff = mem.alignForward(u32, fileoff, @alignOf(macho.relocation_info)); - fileoff = header.reloff + header.nreloc * @sizeOf(macho.relocation_info); - } - return fileoff; -} - // We need to sort relocations in descending order to be compatible with Apple's linker. fn sortReloc(ctx: void, lhs: macho.relocation_info, rhs: macho.relocation_info) bool { _ = ctx; return lhs.r_address > rhs.r_address; } -fn writeAtoms(macho_file: *MachO) !void { +fn sortRelocs(macho_file: *MachO) void { + const tracy = trace(@src()); + defer tracy.end(); + + for (macho_file.sections.items(.relocs)) |*relocs| { + mem.sort(macho.relocation_info, relocs.items, {}, sortReloc); + } +} + +fn writeSections(macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); const gpa = macho_file.base.comp.gpa; const cpu_arch = macho_file.getTarget().cpu.arch; const slice = macho_file.sections.slice(); - - var relocs = std.ArrayList(macho.relocation_info).init(gpa); - defer relocs.deinit(); - - for (slice.items(.header), slice.items(.atoms), 0..) |header, atoms, i| { - if (atoms.items.len == 0) continue; + for (slice.items(.header), slice.items(.out), slice.items(.relocs)) |header, *out, *relocs| { if (header.isZerofill()) continue; - if (macho_file.isZigSection(@intCast(i)) or macho_file.isDebugSection(@intCast(i))) continue; - - const size = math.cast(usize, header.size) orelse return error.Overflow; - const code = try gpa.alloc(u8, size); - defer gpa.free(code); + try out.resize(gpa, header.size); const padding_byte: u8 = if (header.isCode() and cpu_arch == .x86_64) 0xcc else 0; - @memset(code, padding_byte); - - try relocs.ensureTotalCapacity(header.nreloc); - - for (atoms.items) |atom_index| { - const atom = macho_file.getAtom(atom_index).?; - assert(atom.flags.alive); - const off = math.cast(usize, atom.value) orelse return error.Overflow; - const atom_size = math.cast(usize, atom.size) orelse return error.Overflow; - try atom.getData(macho_file, code[off..][0..atom_size]); - try atom.writeRelocs(macho_file, code[off..][0..atom_size], &relocs); - } - - assert(relocs.items.len == header.nreloc); - - mem.sort(macho.relocation_info, relocs.items, {}, sortReloc); - - // TODO scattered writes? - try macho_file.base.file.?.pwriteAll(code, header.offset); - try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff); - - relocs.clearRetainingCapacity(); + @memset(out.items, padding_byte); + try relocs.resize(gpa, header.nreloc); } - if (macho_file.getZigObject()) |zo| { - // TODO: this is ugly; perhaps we should aggregrate before? - var zo_relocs = std.AutoArrayHashMap(u8, std.ArrayList(macho.relocation_info)).init(gpa); - defer { - for (zo_relocs.values()) |*list| { - list.deinit(); - } - zo_relocs.deinit(); - } + const cmd = macho_file.symtab_cmd; + try macho_file.symtab.resize(gpa, cmd.nsyms); + try macho_file.strtab.resize(gpa, cmd.strsize); + macho_file.strtab.items[0] = 0; - for (macho_file.sections.items(.header), 0..) |header, n_sect| { - if (header.isZerofill()) continue; - if (!macho_file.isZigSection(@intCast(n_sect)) and !macho_file.isDebugSection(@intCast(n_sect))) continue; - const gop = try zo_relocs.getOrPut(@intCast(n_sect)); - if (gop.found_existing) continue; - gop.value_ptr.* = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc); - } - - for (zo.atoms.items) |atom_index| { - const atom = macho_file.getAtom(atom_index) orelse continue; - if (!atom.flags.alive) continue; - const header = macho_file.sections.items(.header)[atom.out_n_sect]; - if (header.isZerofill()) continue; - if (!macho_file.isZigSection(atom.out_n_sect) and !macho_file.isDebugSection(atom.out_n_sect)) continue; - if (atom.getRelocs(macho_file).len == 0) continue; - const atom_size = math.cast(usize, atom.size) orelse return error.Overflow; - const code = try gpa.alloc(u8, atom_size); - defer gpa.free(code); - atom.getData(macho_file, code) catch |err| switch (err) { - error.InputOutput => { - try macho_file.reportUnexpectedError("fetching code for '{s}' failed", .{ - atom.getName(macho_file), - }); - return error.FlushFailure; - }, - else => |e| { - try macho_file.reportUnexpectedError("unexpected error while fetching code for '{s}': {s}", .{ - atom.getName(macho_file), - @errorName(e), - }); - return error.FlushFailure; - }, - }; - const file_offset = header.offset + atom.value; - const rels = zo_relocs.getPtr(atom.out_n_sect).?; - try atom.writeRelocs(macho_file, code, rels); - try macho_file.base.file.?.pwriteAll(code, file_offset); - } - - for (zo_relocs.keys(), zo_relocs.values()) |sect_id, rels| { - const header = macho_file.sections.items(.header)[sect_id]; - assert(rels.items.len == header.nreloc); - mem.sort(macho.relocation_info, rels.items, {}, sortReloc); - try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(rels.items), header.reloff); - } - } -} - -fn writeCompactUnwind(macho_file: *MachO) !void { - const sect_index = macho_file.unwind_info_sect_index orelse return; - const gpa = macho_file.base.comp.gpa; - const header = macho_file.sections.items(.header)[sect_index]; - - const nrecs = math.cast(usize, @divExact(header.size, @sizeOf(macho.compact_unwind_entry))) orelse return error.Overflow; - var entries = try std.ArrayList(macho.compact_unwind_entry).initCapacity(gpa, nrecs); - defer entries.deinit(); - - var relocs = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc); - defer relocs.deinit(); - - const addReloc = struct { - fn addReloc(offset: i32, cpu_arch: std.Target.Cpu.Arch) macho.relocation_info { - return .{ - .r_address = offset, - .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; - - var offset: i32 = 0; for (macho_file.objects.items) |index| { - const object = macho_file.getFile(index).?.object; - for (object.unwind_records_indexes.items) |irec| { - const rec = object.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, - }; - - { - // Function address - const atom = rec.getAtom(macho_file); - const addr = rec.getAtomAddress(macho_file); - out.rangeStart = addr; - var reloc = addReloc(offset, macho_file.getTarget().cpu.arch); - reloc.r_symbolnum = atom.out_n_sect + 1; - relocs.appendAssumeCapacity(reloc); - } - - // Personality function - if (rec.getPersonality(macho_file)) |sym| { - const r_symbolnum = math.cast(u24, sym.getOutputSymtabIndex(macho_file).?) orelse return error.Overflow; - var reloc = addReloc(offset + 16, macho_file.getTarget().cpu.arch); - reloc.r_symbolnum = r_symbolnum; - reloc.r_extern = 1; - relocs.appendAssumeCapacity(reloc); - } - - // LSDA address - if (rec.getLsdaAtom(macho_file)) |atom| { - const addr = rec.getLsdaAddress(macho_file); - out.lsda = addr; - var reloc = addReloc(offset + 24, macho_file.getTarget().cpu.arch); - reloc.r_symbolnum = atom.out_n_sect + 1; - relocs.appendAssumeCapacity(reloc); - } - - entries.appendAssumeCapacity(out); - offset += @sizeOf(macho.compact_unwind_entry); - } + try macho_file.getFile(index).?.object.writeAtomsRelocatable(macho_file); + macho_file.getFile(index).?.writeSymtab(macho_file, macho_file); } - assert(entries.items.len == nrecs); - assert(relocs.items.len == header.nreloc); + if (macho_file.eh_frame_sect_index) |_| { + try writeEhFrame(macho_file); + } - mem.sort(macho.relocation_info, relocs.items, {}, sortReloc); - - // TODO scattered writes? - try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(entries.items), header.offset); - try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff); + if (macho_file.unwind_info_sect_index) |_| { + for (macho_file.objects.items) |index| { + try macho_file.getFile(index).?.object.writeCompactUnwindRelocatable(macho_file); + } + } } fn writeEhFrame(macho_file: *MachO) !void { - const sect_index = macho_file.eh_frame_sect_index orelse return; - const gpa = macho_file.base.comp.gpa; - const header = macho_file.sections.items(.header)[sect_index]; - const size = math.cast(usize, header.size) orelse return error.Overflow; + const sect_index = macho_file.eh_frame_sect_index.?; + const buffer = macho_file.sections.items(.out)[sect_index]; + const relocs = macho_file.sections.items(.relocs)[sect_index]; + try eh_frame.writeRelocs(macho_file, buffer.items, relocs.items); +} - const code = try gpa.alloc(u8, size); - defer gpa.free(code); +fn writeSectionsToFile(macho_file: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); - var relocs = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc); - defer relocs.deinit(); + const slice = macho_file.sections.slice(); + for (slice.items(.header), slice.items(.out), slice.items(.relocs)) |header, out, relocs| { + try macho_file.base.file.?.pwriteAll(out.items, header.offset); + try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff); + } - try eh_frame.writeRelocs(macho_file, code, &relocs); - assert(relocs.items.len == header.nreloc); - - mem.sort(macho.relocation_info, relocs.items, {}, sortReloc); - - // TODO scattered writes? - try macho_file.base.file.?.pwriteAll(code, header.offset); - try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff); + try macho_file.writeDataInCode(); + try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(macho_file.symtab.items), macho_file.symtab_cmd.symoff); + try macho_file.base.file.?.pwriteAll(macho_file.strtab.items, macho_file.symtab_cmd.stroff); } fn writeLoadCommands(macho_file: *MachO) !struct { usize, usize } {