master
   1/// Non-zero for fat object files or archives
   2offset: u64,
   3/// If `in_archive` is not `null`, this is the basename of the object in the archive. Otherwise,
   4/// this is a fully-resolved absolute path, because that is the path we need to embed in stabs to
   5/// ensure the output does not depend on its cwd.
   6path: []u8,
   7file_handle: File.HandleIndex,
   8mtime: u64,
   9index: File.Index,
  10in_archive: ?InArchive = null,
  11
  12header: ?macho.mach_header_64 = null,
  13sections: std.MultiArrayList(Section) = .{},
  14symtab: std.MultiArrayList(Nlist) = .{},
  15strtab: std.ArrayList(u8) = .empty,
  16
  17symbols: std.ArrayList(Symbol) = .empty,
  18symbols_extra: std.ArrayList(u32) = .empty,
  19globals: std.ArrayList(MachO.SymbolResolver.Index) = .empty,
  20atoms: std.ArrayList(Atom) = .empty,
  21atoms_indexes: std.ArrayList(Atom.Index) = .empty,
  22atoms_extra: std.ArrayList(u32) = .empty,
  23
  24platform: ?MachO.Platform = null,
  25compile_unit: ?CompileUnit = null,
  26stab_files: std.ArrayList(StabFile) = .empty,
  27
  28eh_frame_sect_index: ?u8 = null,
  29compact_unwind_sect_index: ?u8 = null,
  30cies: std.ArrayList(Cie) = .empty,
  31fdes: std.ArrayList(Fde) = .empty,
  32eh_frame_data: std.ArrayList(u8) = .empty,
  33unwind_records: std.ArrayList(UnwindInfo.Record) = .empty,
  34unwind_records_indexes: std.ArrayList(UnwindInfo.Record.Index) = .empty,
  35data_in_code: std.ArrayList(macho.data_in_code_entry) = .empty,
  36
  37alive: bool = true,
  38hidden: bool = false,
  39
  40compact_unwind_ctx: CompactUnwindCtx = .{},
  41output_symtab_ctx: MachO.SymtabCtx = .{},
  42output_ar_state: Archive.ArState = .{},
  43
  44pub fn deinit(self: *Object, allocator: Allocator) void {
  45    if (self.in_archive) |*ar| allocator.free(ar.path);
  46    allocator.free(self.path);
  47    for (self.sections.items(.relocs), self.sections.items(.subsections)) |*relocs, *sub| {
  48        relocs.deinit(allocator);
  49        sub.deinit(allocator);
  50    }
  51    self.sections.deinit(allocator);
  52    self.symtab.deinit(allocator);
  53    self.strtab.deinit(allocator);
  54    self.symbols.deinit(allocator);
  55    self.symbols_extra.deinit(allocator);
  56    self.globals.deinit(allocator);
  57    self.atoms.deinit(allocator);
  58    self.atoms_indexes.deinit(allocator);
  59    self.atoms_extra.deinit(allocator);
  60    self.cies.deinit(allocator);
  61    self.fdes.deinit(allocator);
  62    self.eh_frame_data.deinit(allocator);
  63    self.unwind_records.deinit(allocator);
  64    self.unwind_records_indexes.deinit(allocator);
  65    for (self.stab_files.items) |*sf| {
  66        sf.stabs.deinit(allocator);
  67    }
  68    self.stab_files.deinit(allocator);
  69    self.data_in_code.deinit(allocator);
  70}
  71
  72pub fn parse(self: *Object, macho_file: *MachO) !void {
  73    const tracy = trace(@src());
  74    defer tracy.end();
  75
  76    log.debug("parsing {f}", .{self.fmtPath()});
  77
  78    const gpa = macho_file.base.comp.gpa;
  79    const handle = macho_file.getFileHandle(self.file_handle);
  80    const cpu_arch = macho_file.getTarget().cpu.arch;
  81
  82    // Atom at index 0 is reserved as null atom
  83    try self.atoms.append(gpa, .{ .extra = try self.addAtomExtra(gpa, .{}) });
  84
  85    var header_buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined;
  86    {
  87        const amt = try handle.preadAll(&header_buffer, self.offset);
  88        if (amt != @sizeOf(macho.mach_header_64)) return error.InputOutput;
  89    }
  90    self.header = @as(*align(1) const macho.mach_header_64, @ptrCast(&header_buffer)).*;
  91
  92    const this_cpu_arch: std.Target.Cpu.Arch = switch (self.header.?.cputype) {
  93        macho.CPU_TYPE_ARM64 => .aarch64,
  94        macho.CPU_TYPE_X86_64 => .x86_64,
  95        else => |x| {
  96            try macho_file.reportParseError2(self.index, "unknown cpu architecture: {d}", .{x});
  97            return error.InvalidMachineType;
  98        },
  99    };
 100    if (cpu_arch != this_cpu_arch) {
 101        try macho_file.reportParseError2(self.index, "invalid cpu architecture: {s}", .{@tagName(this_cpu_arch)});
 102        return error.InvalidMachineType;
 103    }
 104
 105    const lc_buffer = try gpa.alloc(u8, self.header.?.sizeofcmds);
 106    defer gpa.free(lc_buffer);
 107    {
 108        const amt = try handle.preadAll(lc_buffer, self.offset + @sizeOf(macho.mach_header_64));
 109        if (amt != self.header.?.sizeofcmds) return error.InputOutput;
 110    }
 111
 112    var it = LoadCommandIterator.init(&self.header.?, lc_buffer) catch |err| std.debug.panic("bad object: {t}", .{err});
 113    while (it.next() catch |err| std.debug.panic("bad object: {t}", .{err})) |lc| switch (lc.hdr.cmd) {
 114        .SEGMENT_64 => {
 115            const sections = lc.getSections();
 116            try self.sections.ensureUnusedCapacity(gpa, sections.len);
 117            for (sections) |sect| {
 118                const index = try self.sections.addOne(gpa);
 119                self.sections.set(index, .{ .header = sect });
 120
 121                if (mem.eql(u8, sect.sectName(), "__eh_frame")) {
 122                    self.eh_frame_sect_index = @intCast(index);
 123                } else if (mem.eql(u8, sect.sectName(), "__compact_unwind")) {
 124                    self.compact_unwind_sect_index = @intCast(index);
 125                }
 126            }
 127        },
 128        .SYMTAB => {
 129            const cmd = lc.cast(macho.symtab_command).?;
 130            try self.strtab.resize(gpa, cmd.strsize);
 131            {
 132                const amt = try handle.preadAll(self.strtab.items, cmd.stroff + self.offset);
 133                if (amt != self.strtab.items.len) return error.InputOutput;
 134            }
 135
 136            const symtab_buffer = try gpa.alloc(u8, cmd.nsyms * @sizeOf(macho.nlist_64));
 137            defer gpa.free(symtab_buffer);
 138            {
 139                const amt = try handle.preadAll(symtab_buffer, cmd.symoff + self.offset);
 140                if (amt != symtab_buffer.len) return error.InputOutput;
 141            }
 142            const symtab = @as([*]align(1) const macho.nlist_64, @ptrCast(symtab_buffer.ptr))[0..cmd.nsyms];
 143            try self.symtab.ensureUnusedCapacity(gpa, symtab.len);
 144            for (symtab) |nlist| {
 145                self.symtab.appendAssumeCapacity(.{
 146                    .nlist = nlist,
 147                    .atom = 0,
 148                    .size = 0,
 149                });
 150            }
 151        },
 152        .DATA_IN_CODE => {
 153            const cmd = lc.cast(macho.linkedit_data_command).?;
 154            const buffer = try gpa.alloc(u8, cmd.datasize);
 155            defer gpa.free(buffer);
 156            {
 157                const amt = try handle.preadAll(buffer, self.offset + cmd.dataoff);
 158                if (amt != buffer.len) return error.InputOutput;
 159            }
 160            const ndice = @divExact(cmd.datasize, @sizeOf(macho.data_in_code_entry));
 161            const dice = @as([*]align(1) const macho.data_in_code_entry, @ptrCast(buffer.ptr))[0..ndice];
 162            try self.data_in_code.appendUnalignedSlice(gpa, dice);
 163        },
 164        .BUILD_VERSION,
 165        .VERSION_MIN_MACOSX,
 166        .VERSION_MIN_IPHONEOS,
 167        .VERSION_MIN_TVOS,
 168        .VERSION_MIN_WATCHOS,
 169        => if (self.platform == null) {
 170            self.platform = MachO.Platform.fromLoadCommand(lc);
 171        },
 172        else => {},
 173    };
 174
 175    const NlistIdx = struct {
 176        nlist: macho.nlist_64,
 177        idx: usize,
 178
 179        fn rank(ctx: *const Object, nl: macho.nlist_64) u8 {
 180            if (!nl.n_type.bits.ext) {
 181                const name = ctx.getNStrx(nl.n_strx);
 182                if (name.len == 0) return 5;
 183                if (name[0] == 'l' or name[0] == 'L') return 4;
 184                return 3;
 185            }
 186            return if (nl.n_desc.weak_def_or_ref_to_weak) 2 else 1;
 187        }
 188
 189        fn lessThan(ctx: *const Object, lhs: @This(), rhs: @This()) bool {
 190            if (lhs.nlist.n_sect == rhs.nlist.n_sect) {
 191                if (lhs.nlist.n_value == rhs.nlist.n_value) {
 192                    return rank(ctx, lhs.nlist) < rank(ctx, rhs.nlist);
 193                }
 194                return lhs.nlist.n_value < rhs.nlist.n_value;
 195            }
 196            return lhs.nlist.n_sect < rhs.nlist.n_sect;
 197        }
 198    };
 199
 200    var nlists = try std.array_list.Managed(NlistIdx).initCapacity(gpa, self.symtab.items(.nlist).len);
 201    defer nlists.deinit();
 202    for (self.symtab.items(.nlist), 0..) |nlist, i| {
 203        if (nlist.n_type.bits.is_stab != 0 or nlist.n_type.bits.type != .sect) continue;
 204        nlists.appendAssumeCapacity(.{ .nlist = nlist, .idx = i });
 205    }
 206    mem.sort(NlistIdx, nlists.items, self, NlistIdx.lessThan);
 207
 208    if (self.hasSubsections()) {
 209        try self.initSubsections(gpa, nlists.items);
 210    } else {
 211        try self.initSections(gpa, nlists.items);
 212    }
 213
 214    try self.initCstringLiterals(gpa, handle, macho_file);
 215    try self.initFixedSizeLiterals(gpa, macho_file);
 216    try self.initPointerLiterals(gpa, macho_file);
 217    try self.linkNlistToAtom(macho_file);
 218
 219    try self.sortAtoms(macho_file);
 220    try self.initSymbols(gpa, macho_file);
 221    try self.initSymbolStabs(gpa, nlists.items, macho_file);
 222    try self.initRelocs(handle, cpu_arch, macho_file);
 223
 224    // Parse DWARF __TEXT,__eh_frame section
 225    if (self.eh_frame_sect_index) |index| {
 226        try self.initEhFrameRecords(gpa, index, handle, macho_file);
 227    }
 228
 229    // Parse Apple's __LD,__compact_unwind section
 230    if (self.compact_unwind_sect_index) |index| {
 231        try self.initUnwindRecords(gpa, index, handle, macho_file);
 232    }
 233
 234    if (self.hasUnwindRecords() or self.hasEhFrameRecords()) {
 235        try self.parseUnwindRecords(gpa, cpu_arch, macho_file);
 236    }
 237
 238    if (self.platform) |platform| {
 239        if (!macho_file.platform.eqlTarget(platform)) {
 240            try macho_file.reportParseError2(self.index, "invalid platform: {f}", .{
 241                platform.fmtTarget(cpu_arch),
 242            });
 243            return error.InvalidTarget;
 244        }
 245        // TODO: this causes the CI to fail so I'm commenting this check out so that
 246        // I can work out the rest of the changes first
 247        // if (macho_file.platform.version.order(platform.version) == .lt) {
 248        //     try macho_file.reportParseError2(self.index, "object file built for newer platform: {f}: {f} < {f}", .{
 249        //         macho_file.platform.fmtTarget(macho_file.getTarget().cpu.arch),
 250        //         macho_file.platform.version,
 251        //         platform.version,
 252        //     });
 253        //     return error.InvalidTarget;
 254        // }
 255    }
 256
 257    try self.parseDebugInfo(macho_file);
 258
 259    for (self.getAtoms()) |atom_index| {
 260        const atom = self.getAtom(atom_index) orelse continue;
 261        const isec = atom.getInputSection(macho_file);
 262        if (mem.eql(u8, isec.sectName(), "__eh_frame") or
 263            mem.eql(u8, isec.sectName(), "__compact_unwind") or
 264            isec.attrs() & macho.S_ATTR_DEBUG != 0)
 265        {
 266            atom.setAlive(false);
 267        }
 268    }
 269
 270    // Finally, we do a post-parse check for -ObjC to see if we need to force load this member anyhow.
 271    self.alive = self.alive or (macho_file.force_load_objc and self.hasObjC());
 272}
 273
 274pub fn isCstringLiteral(sect: macho.section_64) bool {
 275    return sect.type() == macho.S_CSTRING_LITERALS;
 276}
 277
 278pub fn isFixedSizeLiteral(sect: macho.section_64) bool {
 279    return switch (sect.type()) {
 280        macho.S_4BYTE_LITERALS,
 281        macho.S_8BYTE_LITERALS,
 282        macho.S_16BYTE_LITERALS,
 283        => true,
 284        else => false,
 285    };
 286}
 287
 288pub fn isPtrLiteral(sect: macho.section_64) bool {
 289    return sect.type() == macho.S_LITERAL_POINTERS;
 290}
 291
 292fn initSubsections(self: *Object, allocator: Allocator, nlists: anytype) !void {
 293    const tracy = trace(@src());
 294    defer tracy.end();
 295    const slice = self.sections.slice();
 296    for (slice.items(.header), slice.items(.subsections), 0..) |sect, *subsections, n_sect| {
 297        if (isCstringLiteral(sect)) continue;
 298        if (isFixedSizeLiteral(sect)) continue;
 299        if (isPtrLiteral(sect)) continue;
 300
 301        const nlist_start = for (nlists, 0..) |nlist, i| {
 302            if (nlist.nlist.n_sect - 1 == n_sect) break i;
 303        } else nlists.len;
 304        const nlist_end = for (nlists[nlist_start..], nlist_start..) |nlist, i| {
 305            if (nlist.nlist.n_sect - 1 != n_sect) break i;
 306        } else nlists.len;
 307
 308        if (nlist_start == nlist_end or nlists[nlist_start].nlist.n_value > sect.addr) {
 309            const name = try std.fmt.allocPrintSentinel(allocator, "{s}${s}$begin", .{
 310                sect.segName(), sect.sectName(),
 311            }, 0);
 312            defer allocator.free(name);
 313            const size = if (nlist_start == nlist_end) sect.size else nlists[nlist_start].nlist.n_value - sect.addr;
 314            const atom_index = try self.addAtom(allocator, .{
 315                .name = try self.addString(allocator, name),
 316                .n_sect = @intCast(n_sect),
 317                .off = 0,
 318                .size = size,
 319                .alignment = sect.@"align",
 320            });
 321            try self.atoms_indexes.append(allocator, atom_index);
 322            try subsections.append(allocator, .{
 323                .atom = atom_index,
 324                .off = 0,
 325            });
 326        }
 327
 328        var idx: usize = nlist_start;
 329        while (idx < nlist_end) {
 330            const alias_start = idx;
 331            const nlist = nlists[alias_start];
 332
 333            while (idx < nlist_end and
 334                nlists[idx].nlist.n_value == nlist.nlist.n_value) : (idx += 1)
 335            {}
 336
 337            const size = if (idx < nlist_end)
 338                nlists[idx].nlist.n_value - nlist.nlist.n_value
 339            else
 340                sect.addr + sect.size - nlist.nlist.n_value;
 341            const alignment = if (nlist.nlist.n_value > 0)
 342                @min(@ctz(nlist.nlist.n_value), sect.@"align")
 343            else
 344                sect.@"align";
 345            const atom_index = try self.addAtom(allocator, .{
 346                .name = .{ .pos = nlist.nlist.n_strx, .len = @intCast(self.getNStrx(nlist.nlist.n_strx).len + 1) },
 347                .n_sect = @intCast(n_sect),
 348                .off = nlist.nlist.n_value - sect.addr,
 349                .size = size,
 350                .alignment = alignment,
 351            });
 352            try self.atoms_indexes.append(allocator, atom_index);
 353            try subsections.append(allocator, .{
 354                .atom = atom_index,
 355                .off = nlist.nlist.n_value - sect.addr,
 356            });
 357
 358            for (alias_start..idx) |i| {
 359                self.symtab.items(.size)[nlists[i].idx] = size;
 360            }
 361        }
 362
 363        // Some compilers such as Go reference the end of a section (addr + size)
 364        // which cannot be contained in any non-zero atom (since then this atom
 365        // would exceed section boundaries). In order to facilitate this behaviour,
 366        // we create a dummy zero-sized atom at section end (addr + size).
 367        const name = try std.fmt.allocPrintSentinel(allocator, "{s}${s}$end", .{
 368            sect.segName(), sect.sectName(),
 369        }, 0);
 370        defer allocator.free(name);
 371        const atom_index = try self.addAtom(allocator, .{
 372            .name = try self.addString(allocator, name),
 373            .n_sect = @intCast(n_sect),
 374            .off = sect.size,
 375            .size = 0,
 376            .alignment = sect.@"align",
 377        });
 378        try self.atoms_indexes.append(allocator, atom_index);
 379        try subsections.append(allocator, .{
 380            .atom = atom_index,
 381            .off = sect.size,
 382        });
 383    }
 384}
 385
 386fn initSections(self: *Object, allocator: Allocator, nlists: anytype) !void {
 387    const tracy = trace(@src());
 388    defer tracy.end();
 389    const slice = self.sections.slice();
 390
 391    try self.atoms.ensureUnusedCapacity(allocator, self.sections.items(.header).len);
 392    try self.atoms_indexes.ensureUnusedCapacity(allocator, self.sections.items(.header).len);
 393
 394    for (slice.items(.header), 0..) |sect, n_sect| {
 395        if (isCstringLiteral(sect)) continue;
 396        if (isFixedSizeLiteral(sect)) continue;
 397        if (isPtrLiteral(sect)) continue;
 398
 399        const name = try std.fmt.allocPrintSentinel(allocator, "{s}${s}", .{ sect.segName(), sect.sectName() }, 0);
 400        defer allocator.free(name);
 401
 402        const atom_index = try self.addAtom(allocator, .{
 403            .name = try self.addString(allocator, name),
 404            .n_sect = @intCast(n_sect),
 405            .off = 0,
 406            .size = sect.size,
 407            .alignment = sect.@"align",
 408        });
 409        try self.atoms_indexes.append(allocator, atom_index);
 410        try slice.items(.subsections)[n_sect].append(allocator, .{ .atom = atom_index, .off = 0 });
 411
 412        const nlist_start = for (nlists, 0..) |nlist, i| {
 413            if (nlist.nlist.n_sect - 1 == n_sect) break i;
 414        } else nlists.len;
 415        const nlist_end = for (nlists[nlist_start..], nlist_start..) |nlist, i| {
 416            if (nlist.nlist.n_sect - 1 != n_sect) break i;
 417        } else nlists.len;
 418
 419        var idx: usize = nlist_start;
 420        while (idx < nlist_end) {
 421            const nlist = nlists[idx];
 422
 423            while (idx < nlist_end and
 424                nlists[idx].nlist.n_value == nlist.nlist.n_value) : (idx += 1)
 425            {}
 426
 427            const size = if (idx < nlist_end)
 428                nlists[idx].nlist.n_value - nlist.nlist.n_value
 429            else
 430                sect.addr + sect.size - nlist.nlist.n_value;
 431
 432            for (nlist_start..idx) |i| {
 433                self.symtab.items(.size)[nlists[i].idx] = size;
 434            }
 435        }
 436    }
 437}
 438
 439fn initCstringLiterals(self: *Object, allocator: Allocator, file: File.Handle, macho_file: *MachO) !void {
 440    const tracy = trace(@src());
 441    defer tracy.end();
 442
 443    const slice = self.sections.slice();
 444
 445    for (slice.items(.header), 0..) |sect, n_sect| {
 446        if (!isCstringLiteral(sect)) continue;
 447
 448        const data = try self.readSectionData(allocator, file, @intCast(n_sect));
 449        defer allocator.free(data);
 450
 451        var count: u32 = 0;
 452        var start: u32 = 0;
 453        while (start < data.len) {
 454            defer count += 1;
 455            var end = start;
 456            while (end < data.len - 1 and data[end] != 0) : (end += 1) {}
 457            if (data[end] != 0) {
 458                try macho_file.reportParseError2(
 459                    self.index,
 460                    "string not null terminated in '{s},{s}'",
 461                    .{ sect.segName(), sect.sectName() },
 462                );
 463                return error.MalformedObject;
 464            }
 465            end += 1;
 466
 467            const name = try std.fmt.allocPrintSentinel(allocator, "l._str{d}", .{count}, 0);
 468            defer allocator.free(name);
 469            const name_str = try self.addString(allocator, name);
 470
 471            const atom_index = try self.addAtom(allocator, .{
 472                .name = name_str,
 473                .n_sect = @intCast(n_sect),
 474                .off = start,
 475                .size = end - start,
 476                .alignment = sect.@"align",
 477            });
 478            try self.atoms_indexes.append(allocator, atom_index);
 479            try slice.items(.subsections)[n_sect].append(allocator, .{
 480                .atom = atom_index,
 481                .off = start,
 482            });
 483
 484            const atom = self.getAtom(atom_index).?;
 485            const nlist_index: u32 = @intCast(try self.symtab.addOne(allocator));
 486            self.symtab.set(nlist_index, .{
 487                .nlist = .{
 488                    .n_strx = name_str.pos,
 489                    .n_type = .{ .bits = .{ .ext = false, .type = .sect, .pext = false, .is_stab = 0 } },
 490                    .n_sect = @intCast(atom.n_sect + 1),
 491                    .n_desc = @bitCast(@as(u16, 0)),
 492                    .n_value = atom.getInputAddress(macho_file),
 493                },
 494                .size = atom.size,
 495                .atom = atom_index,
 496            });
 497            atom.addExtra(.{ .literal_symbol_index = nlist_index }, macho_file);
 498
 499            start = end;
 500        }
 501    }
 502}
 503
 504fn initFixedSizeLiterals(self: *Object, allocator: Allocator, macho_file: *MachO) !void {
 505    const tracy = trace(@src());
 506    defer tracy.end();
 507
 508    const slice = self.sections.slice();
 509
 510    for (slice.items(.header), 0..) |sect, n_sect| {
 511        if (!isFixedSizeLiteral(sect)) continue;
 512
 513        const rec_size: u8 = switch (sect.type()) {
 514            macho.S_4BYTE_LITERALS => 4,
 515            macho.S_8BYTE_LITERALS => 8,
 516            macho.S_16BYTE_LITERALS => 16,
 517            else => unreachable,
 518        };
 519        if (sect.size % rec_size != 0) {
 520            try macho_file.reportParseError2(
 521                self.index,
 522                "size not multiple of record size in '{s},{s}'",
 523                .{ sect.segName(), sect.sectName() },
 524            );
 525            return error.MalformedObject;
 526        }
 527
 528        var pos: u32 = 0;
 529        var count: u32 = 0;
 530        while (pos < sect.size) : ({
 531            pos += rec_size;
 532            count += 1;
 533        }) {
 534            const name = try std.fmt.allocPrintSentinel(allocator, "l._literal{d}", .{count}, 0);
 535            defer allocator.free(name);
 536            const name_str = try self.addString(allocator, name);
 537
 538            const atom_index = try self.addAtom(allocator, .{
 539                .name = name_str,
 540                .n_sect = @intCast(n_sect),
 541                .off = pos,
 542                .size = rec_size,
 543                .alignment = sect.@"align",
 544            });
 545            try self.atoms_indexes.append(allocator, atom_index);
 546            try slice.items(.subsections)[n_sect].append(allocator, .{
 547                .atom = atom_index,
 548                .off = pos,
 549            });
 550
 551            const atom = self.getAtom(atom_index).?;
 552            const nlist_index: u32 = @intCast(try self.symtab.addOne(allocator));
 553            self.symtab.set(nlist_index, .{
 554                .nlist = .{
 555                    .n_strx = name_str.pos,
 556                    .n_type = .{ .bits = .{ .ext = false, .type = .sect, .pext = false, .is_stab = 0 } },
 557                    .n_sect = @intCast(atom.n_sect + 1),
 558                    .n_desc = @bitCast(@as(u16, 0)),
 559                    .n_value = atom.getInputAddress(macho_file),
 560                },
 561                .size = atom.size,
 562                .atom = atom_index,
 563            });
 564            atom.addExtra(.{ .literal_symbol_index = nlist_index }, macho_file);
 565        }
 566    }
 567}
 568
 569fn initPointerLiterals(self: *Object, allocator: Allocator, macho_file: *MachO) !void {
 570    const tracy = trace(@src());
 571    defer tracy.end();
 572
 573    const slice = self.sections.slice();
 574
 575    for (slice.items(.header), 0..) |sect, n_sect| {
 576        if (!isPtrLiteral(sect)) continue;
 577
 578        const rec_size: u8 = 8;
 579        if (sect.size % rec_size != 0) {
 580            try macho_file.reportParseError2(
 581                self.index,
 582                "size not multiple of record size in '{s},{s}'",
 583                .{ sect.segName(), sect.sectName() },
 584            );
 585            return error.MalformedObject;
 586        }
 587        const num_ptrs = try macho_file.cast(usize, @divExact(sect.size, rec_size));
 588
 589        for (0..num_ptrs) |i| {
 590            const pos: u32 = @as(u32, @intCast(i)) * rec_size;
 591
 592            const name = try std.fmt.allocPrintSentinel(allocator, "l._ptr{d}", .{i}, 0);
 593            defer allocator.free(name);
 594            const name_str = try self.addString(allocator, name);
 595
 596            const atom_index = try self.addAtom(allocator, .{
 597                .name = name_str,
 598                .n_sect = @intCast(n_sect),
 599                .off = pos,
 600                .size = rec_size,
 601                .alignment = sect.@"align",
 602            });
 603            try self.atoms_indexes.append(allocator, atom_index);
 604            try slice.items(.subsections)[n_sect].append(allocator, .{
 605                .atom = atom_index,
 606                .off = pos,
 607            });
 608
 609            const atom = self.getAtom(atom_index).?;
 610            const nlist_index: u32 = @intCast(try self.symtab.addOne(allocator));
 611            self.symtab.set(nlist_index, .{
 612                .nlist = .{
 613                    .n_strx = name_str.pos,
 614                    .n_type = .{ .bits = .{ .ext = false, .type = .sect, .pext = false, .is_stab = 0 } },
 615                    .n_sect = @intCast(atom.n_sect + 1),
 616                    .n_desc = @bitCast(@as(u16, 0)),
 617                    .n_value = atom.getInputAddress(macho_file),
 618                },
 619                .size = atom.size,
 620                .atom = atom_index,
 621            });
 622            atom.addExtra(.{ .literal_symbol_index = nlist_index }, macho_file);
 623        }
 624    }
 625}
 626
 627pub fn resolveLiterals(self: *Object, lp: *MachO.LiteralPool, macho_file: *MachO) !void {
 628    const tracy = trace(@src());
 629    defer tracy.end();
 630
 631    const gpa = macho_file.base.comp.gpa;
 632    const file = macho_file.getFileHandle(self.file_handle);
 633
 634    var buffer = std.array_list.Managed(u8).init(gpa);
 635    defer buffer.deinit();
 636
 637    var sections_data = std.AutoHashMap(u32, []const u8).init(gpa);
 638    try sections_data.ensureTotalCapacity(@intCast(self.sections.items(.header).len));
 639    defer {
 640        var it = sections_data.iterator();
 641        while (it.next()) |entry| {
 642            gpa.free(entry.value_ptr.*);
 643        }
 644        sections_data.deinit();
 645    }
 646
 647    const slice = self.sections.slice();
 648    for (slice.items(.header), slice.items(.subsections), 0..) |header, subs, n_sect| {
 649        if (isCstringLiteral(header) or isFixedSizeLiteral(header)) {
 650            const data = try self.readSectionData(gpa, file, @intCast(n_sect));
 651            defer gpa.free(data);
 652
 653            for (subs.items) |sub| {
 654                const atom = self.getAtom(sub.atom).?;
 655                const atom_off = try macho_file.cast(usize, atom.off);
 656                const atom_size = try macho_file.cast(usize, atom.size);
 657                const atom_data = data[atom_off..][0..atom_size];
 658                const res = try lp.insert(gpa, header.type(), atom_data);
 659                if (!res.found_existing) {
 660                    res.ref.* = .{ .index = atom.getExtra(macho_file).literal_symbol_index, .file = self.index };
 661                } else {
 662                    const lp_sym = lp.getSymbol(res.index, macho_file);
 663                    const lp_atom = lp_sym.getAtom(macho_file).?;
 664                    lp_atom.alignment = lp_atom.alignment.max(atom.alignment);
 665                    atom.setAlive(false);
 666                }
 667                atom.addExtra(.{ .literal_pool_index = res.index }, macho_file);
 668            }
 669        } else if (isPtrLiteral(header)) {
 670            for (subs.items) |sub| {
 671                const atom = self.getAtom(sub.atom).?;
 672                const relocs = atom.getRelocs(macho_file);
 673                assert(relocs.len == 1);
 674                const rel = relocs[0];
 675                const target = switch (rel.tag) {
 676                    .local => rel.getTargetAtom(atom.*, macho_file),
 677                    .@"extern" => rel.getTargetSymbol(atom.*, macho_file).getAtom(macho_file).?,
 678                };
 679                const addend = try macho_file.cast(u32, rel.addend);
 680                const target_size = try macho_file.cast(usize, target.size);
 681                try buffer.ensureUnusedCapacity(target_size);
 682                buffer.resize(target_size) catch unreachable;
 683                const gop = try sections_data.getOrPut(target.n_sect);
 684                if (!gop.found_existing) {
 685                    gop.value_ptr.* = try self.readSectionData(gpa, file, @intCast(target.n_sect));
 686                }
 687                const data = gop.value_ptr.*;
 688                const target_off = try macho_file.cast(usize, target.off);
 689                @memcpy(buffer.items, data[target_off..][0..target_size]);
 690                const res = try lp.insert(gpa, header.type(), buffer.items[addend..]);
 691                buffer.clearRetainingCapacity();
 692                if (!res.found_existing) {
 693                    res.ref.* = .{ .index = atom.getExtra(macho_file).literal_symbol_index, .file = self.index };
 694                } else {
 695                    const lp_sym = lp.getSymbol(res.index, macho_file);
 696                    const lp_atom = lp_sym.getAtom(macho_file).?;
 697                    lp_atom.alignment = lp_atom.alignment.max(atom.alignment);
 698                    atom.setAlive(false);
 699                }
 700                atom.addExtra(.{ .literal_pool_index = res.index }, macho_file);
 701            }
 702        }
 703    }
 704}
 705
 706pub fn dedupLiterals(self: *Object, lp: MachO.LiteralPool, macho_file: *MachO) void {
 707    const tracy = trace(@src());
 708    defer tracy.end();
 709
 710    for (self.getAtoms()) |atom_index| {
 711        const atom = self.getAtom(atom_index) orelse continue;
 712        if (!atom.isAlive()) continue;
 713
 714        const relocs = blk: {
 715            const extra = atom.getExtra(macho_file);
 716            const relocs = self.sections.items(.relocs)[atom.n_sect].items;
 717            break :blk relocs[extra.rel_index..][0..extra.rel_count];
 718        };
 719        for (relocs) |*rel| {
 720            if (rel.tag != .@"extern") continue;
 721            const target_sym_ref = rel.getTargetSymbolRef(atom.*, macho_file);
 722            const file = target_sym_ref.getFile(macho_file) orelse continue;
 723            if (file.getIndex() != self.index) continue;
 724            const target_sym = target_sym_ref.getSymbol(macho_file).?;
 725            const target_atom = target_sym.getAtom(macho_file) orelse continue;
 726            const isec = target_atom.getInputSection(macho_file);
 727            if (!Object.isCstringLiteral(isec) and !Object.isFixedSizeLiteral(isec) and !Object.isPtrLiteral(isec)) continue;
 728            const lp_index = target_atom.getExtra(macho_file).literal_pool_index;
 729            const lp_sym = lp.getSymbol(lp_index, macho_file);
 730            const lp_atom_ref = lp_sym.atom_ref;
 731            if (target_atom.atom_index != lp_atom_ref.index or target_atom.file != lp_atom_ref.file) {
 732                target_sym.atom_ref = lp_atom_ref;
 733            }
 734        }
 735    }
 736
 737    for (self.symbols.items) |*sym| {
 738        const atom = sym.getAtom(macho_file) orelse continue;
 739        const isec = atom.getInputSection(macho_file);
 740        if (!Object.isCstringLiteral(isec) and !Object.isFixedSizeLiteral(isec) and !Object.isPtrLiteral(isec)) continue;
 741        const lp_index = atom.getExtra(macho_file).literal_pool_index;
 742        const lp_sym = lp.getSymbol(lp_index, macho_file);
 743        const lp_atom_ref = lp_sym.atom_ref;
 744        if (atom.atom_index != lp_atom_ref.index or self.index != lp_atom_ref.file) {
 745            sym.atom_ref = lp_atom_ref;
 746        }
 747    }
 748}
 749
 750pub fn findAtom(self: Object, addr: u64) ?Atom.Index {
 751    const tracy = trace(@src());
 752    defer tracy.end();
 753    const slice = self.sections.slice();
 754    for (slice.items(.header), slice.items(.subsections), 0..) |sect, subs, n_sect| {
 755        if (subs.items.len == 0) continue;
 756        if (addr == sect.addr) return subs.items[0].atom;
 757        if (sect.addr < addr and addr < sect.addr + sect.size) {
 758            return self.findAtomInSection(addr, @intCast(n_sect));
 759        }
 760    }
 761    return null;
 762}
 763
 764fn findAtomInSection(self: Object, addr: u64, n_sect: u8) ?Atom.Index {
 765    const tracy = trace(@src());
 766    defer tracy.end();
 767    const slice = self.sections.slice();
 768    const sect = slice.items(.header)[n_sect];
 769    const subsections = slice.items(.subsections)[n_sect];
 770
 771    var min: usize = 0;
 772    var max: usize = subsections.items.len;
 773    while (min < max) {
 774        const idx = (min + max) / 2;
 775        const sub = subsections.items[idx];
 776        const sub_addr = sect.addr + sub.off;
 777        const sub_size = if (idx + 1 < subsections.items.len)
 778            subsections.items[idx + 1].off - sub.off
 779        else
 780            sect.size - sub.off;
 781        if (sub_addr == addr or (sub_addr < addr and addr < sub_addr + sub_size)) return sub.atom;
 782        if (sub_addr < addr) {
 783            min = idx + 1;
 784        } else {
 785            max = idx;
 786        }
 787    }
 788
 789    if (min < subsections.items.len) {
 790        const sub = subsections.items[min];
 791        const sub_addr = sect.addr + sub.off;
 792        const sub_size = if (min + 1 < subsections.items.len)
 793            subsections.items[min + 1].off - sub.off
 794        else
 795            sect.size - sub.off;
 796        if (sub_addr == addr or (sub_addr < addr and addr < sub_addr + sub_size)) return sub.atom;
 797    }
 798
 799    return null;
 800}
 801
 802fn linkNlistToAtom(self: *Object, macho_file: *MachO) !void {
 803    const tracy = trace(@src());
 804    defer tracy.end();
 805    for (self.symtab.items(.nlist), self.symtab.items(.atom)) |nlist, *atom| {
 806        if (nlist.n_type.bits.is_stab == 0 and nlist.n_type.bits.type == .sect) {
 807            const sect = self.sections.items(.header)[nlist.n_sect - 1];
 808            const subs = self.sections.items(.subsections)[nlist.n_sect - 1].items;
 809            if (nlist.n_value == sect.addr) {
 810                // If the nlist address is the start of the section, return the first atom
 811                // since it is guaranteed to always start at section's start address.
 812                atom.* = subs[0].atom;
 813            } else if (nlist.n_value == sect.addr + sect.size) {
 814                // If the nlist address matches section's boundary (address + size),
 815                // return the last atom since it is guaranteed to always point
 816                // at the section's end boundary.
 817                atom.* = subs[subs.len - 1].atom;
 818            } else if (self.findAtomInSection(nlist.n_value, nlist.n_sect - 1)) |atom_index| {
 819                // In all other cases, do a binary search to find a matching atom for the symbol.
 820                atom.* = atom_index;
 821            } else {
 822                try macho_file.reportParseError2(self.index, "symbol {s} not attached to any (sub)section", .{
 823                    self.getNStrx(nlist.n_strx),
 824                });
 825                return error.MalformedObject;
 826            }
 827        }
 828    }
 829}
 830
 831fn initSymbols(self: *Object, allocator: Allocator, macho_file: *MachO) !void {
 832    const tracy = trace(@src());
 833    defer tracy.end();
 834
 835    const slice = self.symtab.slice();
 836    const nsyms = slice.items(.nlist).len;
 837
 838    try self.symbols.ensureTotalCapacityPrecise(allocator, nsyms);
 839    try self.symbols_extra.ensureTotalCapacityPrecise(allocator, nsyms * @sizeOf(Symbol.Extra));
 840    try self.globals.ensureTotalCapacityPrecise(allocator, nsyms);
 841    self.globals.resize(allocator, nsyms) catch unreachable;
 842    @memset(self.globals.items, 0);
 843
 844    for (slice.items(.nlist), slice.items(.atom), 0..) |nlist, atom_index, i| {
 845        const index = self.addSymbolAssumeCapacity();
 846        const symbol = &self.symbols.items[index];
 847        symbol.value = nlist.n_value;
 848        symbol.name = .{ .pos = nlist.n_strx, .len = @intCast(self.getNStrx(nlist.n_strx).len + 1) };
 849        symbol.nlist_idx = @intCast(i);
 850        symbol.extra = self.addSymbolExtraAssumeCapacity(.{});
 851
 852        if (self.getAtom(atom_index)) |atom| {
 853            assert(nlist.n_type.bits.type != .abs);
 854            symbol.value -= atom.getInputAddress(macho_file);
 855            symbol.atom_ref = .{ .index = atom_index, .file = self.index };
 856        }
 857
 858        symbol.flags.weak = nlist.n_desc.weak_def_or_ref_to_weak;
 859        symbol.flags.abs = nlist.n_type.bits.type == .abs;
 860        symbol.flags.tentative = nlist.tentative();
 861        symbol.flags.no_dead_strip = symbol.flags.no_dead_strip or nlist.n_desc.discarded_or_no_dead_strip;
 862        symbol.flags.dyn_ref = nlist.n_desc.referenced_dynamically;
 863        symbol.flags.interposable = false;
 864        // TODO
 865        // symbol.flags.interposable = nlist.ext() and (nlist.n_type.bits.type == .sect or nlist.n_type.bits.type == .abs) and macho_file.base.isDynLib() and macho_file.options.namespace == .flat and !nlist.pext();
 866
 867        if (nlist.n_type.bits.type == .sect and
 868            self.sections.items(.header)[nlist.n_sect - 1].type() == macho.S_THREAD_LOCAL_VARIABLES)
 869        {
 870            symbol.flags.tlv = true;
 871        }
 872
 873        if (nlist.n_type.bits.ext) {
 874            if (nlist.n_type.bits.type == .undf) {
 875                symbol.flags.weak_ref = nlist.n_desc.weak_ref;
 876            } else if (nlist.n_type.bits.pext or (nlist.n_desc.weak_def_or_ref_to_weak and nlist.n_desc.weak_ref) or self.hidden) {
 877                symbol.visibility = .hidden;
 878            } else {
 879                symbol.visibility = .global;
 880            }
 881        }
 882    }
 883}
 884
 885fn initSymbolStabs(self: *Object, allocator: Allocator, nlists: anytype, macho_file: *MachO) !void {
 886    const tracy = trace(@src());
 887    defer tracy.end();
 888
 889    const SymbolLookup = struct {
 890        ctx: *const Object,
 891        entries: @TypeOf(nlists),
 892
 893        fn find(fs: @This(), addr: u64) ?Symbol.Index {
 894            // TODO binary search since we have the list sorted
 895            for (fs.entries) |nlist| {
 896                if (nlist.nlist.n_value == addr) return @intCast(nlist.idx);
 897            }
 898            return null;
 899        }
 900    };
 901
 902    const start: u32 = for (self.symtab.items(.nlist), 0..) |nlist, i| {
 903        if (nlist.n_type.bits.is_stab != 0) break @intCast(i);
 904    } else @intCast(self.symtab.items(.nlist).len);
 905    const end: u32 = for (self.symtab.items(.nlist)[start..], start..) |nlist, i| {
 906        if (nlist.n_type.bits.is_stab == 0) break @intCast(i);
 907    } else @intCast(self.symtab.items(.nlist).len);
 908
 909    if (start == end) return;
 910
 911    const syms = self.symtab.items(.nlist);
 912    const sym_lookup = SymbolLookup{ .ctx = self, .entries = nlists };
 913
 914    // We need to cache nlists by name so that we can properly resolve local N_GSYM stabs.
 915    // What happens is `ld -r` will emit an N_GSYM stab for a symbol that may be either an
 916    // external or private external.
 917    var addr_lookup = std.StringHashMap(u64).init(allocator);
 918    defer addr_lookup.deinit();
 919    for (syms) |sym| {
 920        if (sym.n_type.bits.type == .sect and (sym.n_type.bits.ext or sym.n_type.bits.pext)) {
 921            try addr_lookup.putNoClobber(self.getNStrx(sym.n_strx), sym.n_value);
 922        }
 923    }
 924
 925    var i: u32 = start;
 926    while (i < end) : (i += 1) {
 927        const open = syms[i];
 928        if (open.n_type.stab != .so) {
 929            try macho_file.reportParseError2(self.index, "unexpected symbol stab type 0x{x} as the first entry", .{
 930                @intFromEnum(open.n_type.stab),
 931            });
 932            return error.MalformedObject;
 933        }
 934
 935        while (i < end and syms[i].n_type.stab == .so and syms[i].n_sect != 0) : (i += 1) {}
 936
 937        var sf: StabFile = .{ .comp_dir = i };
 938        // TODO validate
 939        i += 3;
 940
 941        while (i < end and syms[i].n_type.stab != .so) : (i += 1) {
 942            const nlist = syms[i];
 943            var stab: StabFile.Stab = .{};
 944            switch (nlist.n_type.stab) {
 945                .bnsym => {
 946                    stab.is_func = true;
 947                    stab.index = sym_lookup.find(nlist.n_value);
 948                    // TODO validate
 949                    i += 3;
 950                },
 951                .gsym => {
 952                    stab.is_func = false;
 953                    stab.index = sym_lookup.find(addr_lookup.get(self.getNStrx(nlist.n_strx)).?);
 954                },
 955                .stsym => {
 956                    stab.is_func = false;
 957                    stab.index = sym_lookup.find(nlist.n_value);
 958                },
 959                _ => {
 960                    try macho_file.reportParseError2(self.index, "unhandled symbol stab type 0x{x}", .{@intFromEnum(nlist.n_type.stab)});
 961                    return error.MalformedObject;
 962                },
 963                else => {
 964                    try macho_file.reportParseError2(self.index, "unhandled symbol stab type '{t}'", .{nlist.n_type.stab});
 965                    return error.MalformedObject;
 966                },
 967            }
 968            try sf.stabs.append(allocator, stab);
 969        }
 970
 971        try self.stab_files.append(allocator, sf);
 972    }
 973}
 974
 975fn sortAtoms(self: *Object, macho_file: *MachO) !void {
 976    const Ctx = struct {
 977        object: *Object,
 978        mfile: *MachO,
 979
 980        fn lessThanAtom(ctx: @This(), lhs: Atom.Index, rhs: Atom.Index) bool {
 981            return ctx.object.getAtom(lhs).?.getInputAddress(ctx.mfile) <
 982                ctx.object.getAtom(rhs).?.getInputAddress(ctx.mfile);
 983        }
 984    };
 985    mem.sort(Atom.Index, self.atoms_indexes.items, Ctx{
 986        .object = self,
 987        .mfile = macho_file,
 988    }, Ctx.lessThanAtom);
 989}
 990
 991fn initRelocs(self: *Object, file: File.Handle, cpu_arch: std.Target.Cpu.Arch, macho_file: *MachO) !void {
 992    const tracy = trace(@src());
 993    defer tracy.end();
 994    const slice = self.sections.slice();
 995
 996    for (slice.items(.header), slice.items(.relocs), 0..) |sect, *out, n_sect| {
 997        if (sect.nreloc == 0) continue;
 998        // We skip relocs for __DWARF since even in -r mode, the linker is expected to emit
 999        // debug symbol stabs in the relocatable. This made me curious why that is. For now,
1000        // I shall comply, but I wanna compare with dsymutil.
1001        if (sect.attrs() & macho.S_ATTR_DEBUG != 0 and
1002            !mem.eql(u8, sect.sectName(), "__compact_unwind")) continue;
1003
1004        switch (cpu_arch) {
1005            .x86_64 => try x86_64.parseRelocs(self, @intCast(n_sect), sect, out, file, macho_file),
1006            .aarch64 => try aarch64.parseRelocs(self, @intCast(n_sect), sect, out, file, macho_file),
1007            else => unreachable,
1008        }
1009
1010        mem.sort(Relocation, out.items, {}, Relocation.lessThan);
1011    }
1012
1013    for (slice.items(.header), slice.items(.relocs), slice.items(.subsections)) |sect, relocs, subsections| {
1014        if (sect.isZerofill()) continue;
1015
1016        var next_reloc: u32 = 0;
1017        for (subsections.items) |subsection| {
1018            const atom = self.getAtom(subsection.atom).?;
1019            if (!atom.isAlive()) continue;
1020            if (next_reloc >= relocs.items.len) break;
1021            const end_addr = atom.off + atom.size;
1022            const rel_index = next_reloc;
1023
1024            while (next_reloc < relocs.items.len and relocs.items[next_reloc].offset < end_addr) : (next_reloc += 1) {}
1025
1026            const rel_count = next_reloc - rel_index;
1027            atom.addExtra(.{ .rel_index = @intCast(rel_index), .rel_count = @intCast(rel_count) }, macho_file);
1028        }
1029    }
1030}
1031
1032fn initEhFrameRecords(self: *Object, allocator: Allocator, sect_id: u8, file: File.Handle, macho_file: *MachO) !void {
1033    const tracy = trace(@src());
1034    defer tracy.end();
1035    const nlists = self.symtab.items(.nlist);
1036    const slice = self.sections.slice();
1037    const sect = slice.items(.header)[sect_id];
1038    const relocs = slice.items(.relocs)[sect_id];
1039
1040    const size = try macho_file.cast(usize, sect.size);
1041    try self.eh_frame_data.resize(allocator, size);
1042    const amt = try file.preadAll(self.eh_frame_data.items, sect.offset + self.offset);
1043    if (amt != self.eh_frame_data.items.len) return error.InputOutput;
1044
1045    // Check for non-personality relocs in FDEs and apply them
1046    for (relocs.items, 0..) |rel, i| {
1047        switch (rel.type) {
1048            .unsigned => {
1049                assert((rel.meta.length == 2 or rel.meta.length == 3) and rel.meta.has_subtractor); // TODO error
1050                const S: i64 = switch (rel.tag) {
1051                    .local => rel.meta.symbolnum,
1052                    .@"extern" => @intCast(nlists[rel.meta.symbolnum].n_value),
1053                };
1054                const A = rel.addend;
1055                const SUB: i64 = blk: {
1056                    const sub_rel = relocs.items[i - 1];
1057                    break :blk switch (sub_rel.tag) {
1058                        .local => sub_rel.meta.symbolnum,
1059                        .@"extern" => @intCast(nlists[sub_rel.meta.symbolnum].n_value),
1060                    };
1061                };
1062                switch (rel.meta.length) {
1063                    0, 1 => unreachable,
1064                    2 => mem.writeInt(u32, self.eh_frame_data.items[rel.offset..][0..4], @bitCast(@as(i32, @truncate(S + A - SUB))), .little),
1065                    3 => mem.writeInt(u64, self.eh_frame_data.items[rel.offset..][0..8], @bitCast(S + A - SUB), .little),
1066                }
1067            },
1068            else => {},
1069        }
1070    }
1071
1072    var it = eh_frame.Iterator{ .data = self.eh_frame_data.items };
1073    while (try it.next()) |rec| {
1074        switch (rec.tag) {
1075            .cie => try self.cies.append(allocator, .{
1076                .offset = rec.offset,
1077                .size = rec.size,
1078                .file = self.index,
1079            }),
1080            .fde => try self.fdes.append(allocator, .{
1081                .offset = rec.offset,
1082                .size = rec.size,
1083                .cie = undefined,
1084                .file = self.index,
1085            }),
1086        }
1087    }
1088
1089    for (self.cies.items) |*cie| {
1090        try cie.parse(macho_file);
1091    }
1092
1093    for (self.fdes.items) |*fde| {
1094        try fde.parse(macho_file);
1095    }
1096
1097    const sortFn = struct {
1098        fn sortFn(ctx: *MachO, lhs: Fde, rhs: Fde) bool {
1099            return lhs.getAtom(ctx).getInputAddress(ctx) < rhs.getAtom(ctx).getInputAddress(ctx);
1100        }
1101    }.sortFn;
1102
1103    mem.sort(Fde, self.fdes.items, macho_file, sortFn);
1104
1105    // Parse and attach personality pointers to CIEs if any
1106    for (relocs.items) |rel| {
1107        switch (rel.type) {
1108            .got => {
1109                assert(rel.meta.length == 2 and rel.tag == .@"extern");
1110                const cie = for (self.cies.items) |*cie| {
1111                    if (cie.offset <= rel.offset and rel.offset < cie.offset + cie.getSize()) break cie;
1112                } else {
1113                    try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: bad relocation", .{
1114                        sect.segName(), sect.sectName(), rel.offset,
1115                    });
1116                    return error.MalformedObject;
1117                };
1118                cie.personality = .{ .index = @intCast(rel.target), .offset = rel.offset - cie.offset };
1119            },
1120            else => {},
1121        }
1122    }
1123}
1124
1125fn initUnwindRecords(self: *Object, allocator: Allocator, sect_id: u8, file: File.Handle, macho_file: *MachO) !void {
1126    const tracy = trace(@src());
1127    defer tracy.end();
1128
1129    const SymbolLookup = struct {
1130        ctx: *const Object,
1131
1132        fn find(fs: @This(), addr: u64) ?Symbol.Index {
1133            for (0..fs.ctx.symbols.items.len) |i| {
1134                const nlist = fs.ctx.symtab.items(.nlist)[i];
1135                if (nlist.n_type.bits.ext and nlist.n_value == addr) return @intCast(i);
1136            }
1137            return null;
1138        }
1139    };
1140
1141    const header = self.sections.items(.header)[sect_id];
1142    const data = try self.readSectionData(allocator, file, sect_id);
1143    defer allocator.free(data);
1144
1145    const nrecs = @divExact(data.len, @sizeOf(macho.compact_unwind_entry));
1146    const recs = @as([*]align(1) const macho.compact_unwind_entry, @ptrCast(data.ptr))[0..nrecs];
1147    const sym_lookup = SymbolLookup{ .ctx = self };
1148
1149    try self.unwind_records.ensureTotalCapacityPrecise(allocator, nrecs);
1150    try self.unwind_records_indexes.ensureTotalCapacityPrecise(allocator, nrecs);
1151
1152    const relocs = self.sections.items(.relocs)[sect_id].items;
1153    var reloc_idx: usize = 0;
1154    for (recs, 0..) |rec, rec_idx| {
1155        const rec_start = rec_idx * @sizeOf(macho.compact_unwind_entry);
1156        const rec_end = rec_start + @sizeOf(macho.compact_unwind_entry);
1157        const reloc_start = reloc_idx;
1158        while (reloc_idx < relocs.len and
1159            relocs[reloc_idx].offset < rec_end) : (reloc_idx += 1)
1160        {}
1161
1162        const out_index = self.addUnwindRecordAssumeCapacity();
1163        self.unwind_records_indexes.appendAssumeCapacity(out_index);
1164        const out = self.getUnwindRecord(out_index);
1165        out.length = rec.rangeLength;
1166        out.enc = .{ .enc = rec.compactUnwindEncoding };
1167
1168        for (relocs[reloc_start..reloc_idx]) |rel| {
1169            if (rel.type != .unsigned or rel.meta.length != 3) {
1170                try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: bad relocation", .{
1171                    header.segName(), header.sectName(), rel.offset,
1172                });
1173                return error.MalformedObject;
1174            }
1175            assert(rel.type == .unsigned and rel.meta.length == 3); // TODO error
1176            const offset = rel.offset - rec_start;
1177            switch (offset) {
1178                0 => switch (rel.tag) { // target symbol
1179                    .@"extern" => {
1180                        out.atom = self.symtab.items(.atom)[rel.meta.symbolnum];
1181                        out.atom_offset = @intCast(rec.rangeStart);
1182                    },
1183                    .local => if (self.findAtom(rec.rangeStart)) |atom_index| {
1184                        out.atom = atom_index;
1185                        const atom = out.getAtom(macho_file);
1186                        out.atom_offset = @intCast(rec.rangeStart - atom.getInputAddress(macho_file));
1187                    } else {
1188                        try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: bad relocation", .{
1189                            header.segName(), header.sectName(), rel.offset,
1190                        });
1191                        return error.MalformedObject;
1192                    },
1193                },
1194                16 => switch (rel.tag) { // personality function
1195                    .@"extern" => {
1196                        out.personality = rel.target;
1197                    },
1198                    .local => if (sym_lookup.find(rec.personalityFunction)) |sym_index| {
1199                        out.personality = sym_index;
1200                    } else {
1201                        try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: bad relocation", .{
1202                            header.segName(), header.sectName(), rel.offset,
1203                        });
1204                        return error.MalformedObject;
1205                    },
1206                },
1207                24 => switch (rel.tag) { // lsda
1208                    .@"extern" => {
1209                        out.lsda = self.symtab.items(.atom)[rel.meta.symbolnum];
1210                        out.lsda_offset = @intCast(rec.lsda);
1211                    },
1212                    .local => if (self.findAtom(rec.lsda)) |atom_index| {
1213                        out.lsda = atom_index;
1214                        const atom = out.getLsdaAtom(macho_file).?;
1215                        out.lsda_offset = @intCast(rec.lsda - atom.getInputAddress(macho_file));
1216                    } else {
1217                        try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: bad relocation", .{
1218                            header.segName(), header.sectName(), rel.offset,
1219                        });
1220                        return error.MalformedObject;
1221                    },
1222                },
1223                else => {},
1224            }
1225        }
1226    }
1227}
1228
1229fn parseUnwindRecords(self: *Object, allocator: Allocator, cpu_arch: std.Target.Cpu.Arch, macho_file: *MachO) !void {
1230    // Synthesise missing unwind records.
1231    // The logic here is as follows:
1232    // 1. if an atom has unwind info record that is not DWARF, FDE is marked dead
1233    // 2. if an atom has unwind info record that is DWARF, FDE is tied to this unwind record
1234    // 3. if an atom doesn't have unwind info record but FDE is available, synthesise and tie
1235    // 4. if an atom doesn't have either, synthesise a null unwind info record
1236
1237    const Superposition = struct { atom: Atom.Index, size: u64, cu: ?UnwindInfo.Record.Index = null, fde: ?Fde.Index = null };
1238
1239    var superposition = std.AutoArrayHashMap(u64, Superposition).init(allocator);
1240    defer superposition.deinit();
1241
1242    const slice = self.symtab.slice();
1243    for (slice.items(.nlist), slice.items(.atom), slice.items(.size)) |nlist, atom, size| {
1244        if (nlist.n_type.bits.is_stab != 0) continue;
1245        if (nlist.n_type.bits.type != .sect) continue;
1246        const sect = self.sections.items(.header)[nlist.n_sect - 1];
1247        if (sect.isCode() and sect.size > 0) {
1248            try superposition.ensureUnusedCapacity(1);
1249            const gop = superposition.getOrPutAssumeCapacity(nlist.n_value);
1250            if (gop.found_existing) {
1251                assert(gop.value_ptr.atom == atom and gop.value_ptr.size == size);
1252            }
1253            gop.value_ptr.* = .{ .atom = atom, .size = size };
1254        }
1255    }
1256
1257    for (self.unwind_records_indexes.items) |rec_index| {
1258        const rec = self.getUnwindRecord(rec_index);
1259        const atom = rec.getAtom(macho_file);
1260        const addr = atom.getInputAddress(macho_file) + rec.atom_offset;
1261        superposition.getPtr(addr).?.cu = rec_index;
1262    }
1263
1264    for (self.fdes.items, 0..) |fde, fde_index| {
1265        const atom = fde.getAtom(macho_file);
1266        const addr = atom.getInputAddress(macho_file) + fde.atom_offset;
1267        superposition.getPtr(addr).?.fde = @intCast(fde_index);
1268    }
1269
1270    for (superposition.keys(), superposition.values()) |addr, meta| {
1271        if (meta.fde) |fde_index| {
1272            const fde = &self.fdes.items[fde_index];
1273
1274            if (meta.cu) |rec_index| {
1275                const rec = self.getUnwindRecord(rec_index);
1276                if (!rec.enc.isDwarf(macho_file)) {
1277                    // Mark FDE dead
1278                    fde.alive = false;
1279                } else {
1280                    // Tie FDE to unwind record
1281                    rec.fde = fde_index;
1282                }
1283            } else {
1284                // Synthesise new unwind info record
1285                const rec_index = try self.addUnwindRecord(allocator);
1286                const rec = self.getUnwindRecord(rec_index);
1287                try self.unwind_records_indexes.append(allocator, rec_index);
1288                rec.length = @intCast(meta.size);
1289                rec.atom = fde.atom;
1290                rec.atom_offset = fde.atom_offset;
1291                rec.fde = fde_index;
1292                switch (cpu_arch) {
1293                    .x86_64 => rec.enc.setMode(macho.UNWIND_X86_64_MODE.DWARF),
1294                    .aarch64 => rec.enc.setMode(macho.UNWIND_ARM64_MODE.DWARF),
1295                    else => unreachable,
1296                }
1297            }
1298        } else if (meta.cu == null and meta.fde == null) {
1299            // Create a null record
1300            const rec_index = try self.addUnwindRecord(allocator);
1301            const rec = self.getUnwindRecord(rec_index);
1302            const atom = self.getAtom(meta.atom).?;
1303            try self.unwind_records_indexes.append(allocator, rec_index);
1304            rec.length = @intCast(meta.size);
1305            rec.atom = meta.atom;
1306            rec.atom_offset = @intCast(addr - atom.getInputAddress(macho_file));
1307            rec.file = self.index;
1308        }
1309    }
1310
1311    const SortCtx = struct {
1312        object: *Object,
1313        mfile: *MachO,
1314
1315        fn sort(ctx: @This(), lhs_index: UnwindInfo.Record.Index, rhs_index: UnwindInfo.Record.Index) bool {
1316            const lhs = ctx.object.getUnwindRecord(lhs_index);
1317            const rhs = ctx.object.getUnwindRecord(rhs_index);
1318            const lhsa = lhs.getAtom(ctx.mfile);
1319            const rhsa = rhs.getAtom(ctx.mfile);
1320            return lhsa.getInputAddress(ctx.mfile) + lhs.atom_offset < rhsa.getInputAddress(ctx.mfile) + rhs.atom_offset;
1321        }
1322    };
1323    mem.sort(UnwindInfo.Record.Index, self.unwind_records_indexes.items, SortCtx{
1324        .object = self,
1325        .mfile = macho_file,
1326    }, SortCtx.sort);
1327
1328    // Associate unwind records to atoms
1329    var next_cu: u32 = 0;
1330    while (next_cu < self.unwind_records_indexes.items.len) {
1331        const start = next_cu;
1332        const rec_index = self.unwind_records_indexes.items[start];
1333        const rec = self.getUnwindRecord(rec_index);
1334        while (next_cu < self.unwind_records_indexes.items.len and
1335            self.getUnwindRecord(self.unwind_records_indexes.items[next_cu]).atom == rec.atom) : (next_cu += 1)
1336        {}
1337
1338        const atom = rec.getAtom(macho_file);
1339        atom.addExtra(.{ .unwind_index = start, .unwind_count = next_cu - start }, macho_file);
1340    }
1341}
1342
1343/// Currently, we only check if a compile unit for this input object file exists
1344/// and record that so that we can emit symbol stabs.
1345/// TODO in the future, we want parse debug info and debug line sections so that
1346/// we can provide nice error locations to the user.
1347fn parseDebugInfo(self: *Object, macho_file: *MachO) !void {
1348    const tracy = trace(@src());
1349    defer tracy.end();
1350
1351    const gpa = macho_file.base.comp.gpa;
1352    const file = macho_file.getFileHandle(self.file_handle);
1353
1354    var dwarf: Dwarf = .{};
1355    defer dwarf.deinit(gpa);
1356
1357    for (self.sections.items(.header), 0..) |sect, index| {
1358        const n_sect: u8 = @intCast(index);
1359        if (sect.attrs() & macho.S_ATTR_DEBUG == 0) continue;
1360        if (mem.eql(u8, sect.sectName(), "__debug_info")) {
1361            dwarf.debug_info = try self.readSectionData(gpa, file, n_sect);
1362        }
1363        if (mem.eql(u8, sect.sectName(), "__debug_abbrev")) {
1364            dwarf.debug_abbrev = try self.readSectionData(gpa, file, n_sect);
1365        }
1366        if (mem.eql(u8, sect.sectName(), "__debug_str")) {
1367            dwarf.debug_str = try self.readSectionData(gpa, file, n_sect);
1368        }
1369        // __debug_str_offs[ets] section is a new addition in DWARFv5 and is generally
1370        // required in order to correctly parse strings.
1371        if (mem.eql(u8, sect.sectName(), "__debug_str_offs")) {
1372            dwarf.debug_str_offsets = try self.readSectionData(gpa, file, n_sect);
1373        }
1374    }
1375
1376    if (dwarf.debug_info.len == 0) return;
1377
1378    // TODO return error once we fix emitting DWARF in self-hosted backend.
1379    // https://github.com/ziglang/zig/issues/21719
1380    self.compile_unit = self.findCompileUnit(gpa, dwarf) catch null;
1381}
1382
1383fn findCompileUnit(self: *Object, gpa: Allocator, ctx: Dwarf) !CompileUnit {
1384    var info_reader = Dwarf.InfoReader{ .ctx = ctx };
1385    var abbrev_reader = Dwarf.AbbrevReader{ .ctx = ctx };
1386
1387    const cuh = try info_reader.readCompileUnitHeader();
1388    try abbrev_reader.seekTo(cuh.debug_abbrev_offset);
1389
1390    const cu_decl = (try abbrev_reader.readDecl()) orelse return error.UnexpectedEndOfFile;
1391    if (cu_decl.tag != Dwarf.TAG.compile_unit) return error.UnexpectedTag;
1392
1393    try info_reader.seekToDie(cu_decl.code, cuh, &abbrev_reader);
1394
1395    const Pos = struct {
1396        pos: usize,
1397        form: Dwarf.Form,
1398    };
1399    var saved: struct {
1400        tu_name: ?Pos,
1401        comp_dir: ?Pos,
1402        str_offsets_base: ?Pos,
1403    } = .{
1404        .tu_name = null,
1405        .comp_dir = null,
1406        .str_offsets_base = null,
1407    };
1408    while (try abbrev_reader.readAttr()) |attr| {
1409        const pos: Pos = .{ .pos = info_reader.pos, .form = attr.form };
1410        switch (attr.at) {
1411            Dwarf.AT.name => saved.tu_name = pos,
1412            Dwarf.AT.comp_dir => saved.comp_dir = pos,
1413            Dwarf.AT.str_offsets_base => saved.str_offsets_base = pos,
1414            else => {},
1415        }
1416        try info_reader.skip(attr.form, cuh);
1417    }
1418
1419    if (saved.comp_dir == null) return error.MissingCompileDir;
1420    if (saved.tu_name == null) return error.MissingTuName;
1421
1422    const str_offsets_base: ?u64 = if (saved.str_offsets_base) |str_offsets_base| str_offsets_base: {
1423        try info_reader.seekTo(str_offsets_base.pos);
1424        break :str_offsets_base try info_reader.readOffset(cuh.format);
1425    } else null;
1426
1427    var cu: CompileUnit = .{ .comp_dir = .{}, .tu_name = .{} };
1428    for (&[_]struct { Pos, *MachO.String }{
1429        .{ saved.comp_dir.?, &cu.comp_dir },
1430        .{ saved.tu_name.?, &cu.tu_name },
1431    }) |tuple| {
1432        const pos, const str_offset_ptr = tuple;
1433        try info_reader.seekTo(pos.pos);
1434        str_offset_ptr.* = switch (pos.form) {
1435            Dwarf.FORM.strp,
1436            Dwarf.FORM.string,
1437            => try self.addString(gpa, try info_reader.readString(pos.form, cuh)),
1438            Dwarf.FORM.strx,
1439            Dwarf.FORM.strx1,
1440            Dwarf.FORM.strx2,
1441            Dwarf.FORM.strx3,
1442            Dwarf.FORM.strx4,
1443            => blk: {
1444                const base = str_offsets_base orelse return error.MissingStrOffsetsBase;
1445                break :blk try self.addString(gpa, try info_reader.readStringIndexed(pos.form, cuh, base));
1446            },
1447            else => return error.InvalidForm,
1448        };
1449    }
1450
1451    return cu;
1452}
1453
1454pub fn resolveSymbols(self: *Object, macho_file: *MachO) !void {
1455    const tracy = trace(@src());
1456    defer tracy.end();
1457
1458    const gpa = macho_file.base.comp.gpa;
1459
1460    for (self.symtab.items(.nlist), self.symtab.items(.atom), self.globals.items, 0..) |nlist, atom_index, *global, i| {
1461        if (!nlist.n_type.bits.ext) continue;
1462        if (nlist.n_type.bits.type == .sect) {
1463            const atom = self.getAtom(atom_index).?;
1464            if (!atom.isAlive()) continue;
1465        }
1466
1467        const gop = try macho_file.resolver.getOrPut(gpa, .{
1468            .index = @intCast(i),
1469            .file = self.index,
1470        }, macho_file);
1471        if (!gop.found_existing) {
1472            gop.ref.* = .{ .index = 0, .file = 0 };
1473        }
1474        global.* = gop.index;
1475
1476        if (nlist.n_type.bits.type == .undf and !nlist.tentative()) continue;
1477        if (gop.ref.getFile(macho_file) == null) {
1478            gop.ref.* = .{ .index = @intCast(i), .file = self.index };
1479            continue;
1480        }
1481
1482        if (self.asFile().getSymbolRank(.{
1483            .archive = !self.alive,
1484            .weak = nlist.n_desc.weak_def_or_ref_to_weak,
1485            .tentative = nlist.tentative(),
1486        }) < gop.ref.getSymbol(macho_file).?.getSymbolRank(macho_file)) {
1487            gop.ref.* = .{ .index = @intCast(i), .file = self.index };
1488        }
1489    }
1490}
1491
1492pub fn markLive(self: *Object, macho_file: *MachO) void {
1493    const tracy = trace(@src());
1494    defer tracy.end();
1495
1496    for (0..self.symbols.items.len) |i| {
1497        const nlist = self.symtab.items(.nlist)[i];
1498        if (!nlist.n_type.bits.ext) continue;
1499
1500        const ref = self.getSymbolRef(@intCast(i), macho_file);
1501        const file = ref.getFile(macho_file) orelse continue;
1502        const sym = ref.getSymbol(macho_file).?;
1503        const should_keep = nlist.n_type.bits.type == .undf or (nlist.tentative() and !sym.flags.tentative);
1504        if (should_keep and file == .object and !file.object.alive) {
1505            file.object.alive = true;
1506            file.object.markLive(macho_file);
1507        }
1508    }
1509}
1510
1511pub fn mergeSymbolVisibility(self: *Object, macho_file: *MachO) void {
1512    const tracy = trace(@src());
1513    defer tracy.end();
1514
1515    for (self.symbols.items, 0..) |sym, i| {
1516        const ref = self.getSymbolRef(@intCast(i), macho_file);
1517        const global = ref.getSymbol(macho_file) orelse continue;
1518        if (sym.visibility.rank() < global.visibility.rank()) {
1519            global.visibility = sym.visibility;
1520        }
1521        if (sym.flags.weak_ref) {
1522            global.flags.weak_ref = true;
1523        }
1524    }
1525}
1526
1527pub fn scanRelocs(self: *Object, macho_file: *MachO) !void {
1528    const tracy = trace(@src());
1529    defer tracy.end();
1530
1531    for (self.getAtoms()) |atom_index| {
1532        const atom = self.getAtom(atom_index) orelse continue;
1533        if (!atom.isAlive()) continue;
1534        const sect = atom.getInputSection(macho_file);
1535        if (sect.isZerofill()) continue;
1536        try atom.scanRelocs(macho_file);
1537    }
1538
1539    for (self.unwind_records_indexes.items) |rec_index| {
1540        const rec = self.getUnwindRecord(rec_index);
1541        if (!rec.alive) continue;
1542        if (rec.getFde(macho_file)) |fde| {
1543            if (fde.getCie(macho_file).getPersonality(macho_file)) |sym| {
1544                sym.setSectionFlags(.{ .needs_got = true });
1545            }
1546        } else if (rec.getPersonality(macho_file)) |sym| {
1547            sym.setSectionFlags(.{ .needs_got = true });
1548        }
1549    }
1550}
1551
1552pub fn convertTentativeDefinitions(self: *Object, macho_file: *MachO) !void {
1553    const tracy = trace(@src());
1554    defer tracy.end();
1555    const gpa = macho_file.base.comp.gpa;
1556
1557    for (self.symbols.items, self.globals.items, 0..) |*sym, off, i| {
1558        if (!sym.flags.tentative) continue;
1559        if (macho_file.resolver.get(off).?.file != self.index) continue;
1560
1561        const nlist_idx = @as(Symbol.Index, @intCast(i));
1562        const nlist = &self.symtab.items(.nlist)[nlist_idx];
1563        const nlist_atom = &self.symtab.items(.atom)[nlist_idx];
1564
1565        const name = try std.fmt.allocPrintSentinel(gpa, "__DATA$__common${s}", .{sym.getName(macho_file)}, 0);
1566        defer gpa.free(name);
1567
1568        const alignment = (@as(u16, @bitCast(nlist.n_desc)) >> 8) & 0x0f;
1569        const n_sect = try self.addSection(gpa, "__DATA", "__common");
1570        const atom_index = try self.addAtom(gpa, .{
1571            .name = try self.addString(gpa, name),
1572            .n_sect = n_sect,
1573            .off = 0,
1574            .size = nlist.n_value,
1575            .alignment = alignment,
1576        });
1577        try self.atoms_indexes.append(gpa, atom_index);
1578
1579        const sect = &self.sections.items(.header)[n_sect];
1580        sect.flags = macho.S_ZEROFILL;
1581        sect.size = nlist.n_value;
1582        sect.@"align" = alignment;
1583
1584        sym.value = 0;
1585        sym.atom_ref = .{ .index = atom_index, .file = self.index };
1586        sym.flags.weak = false;
1587        sym.flags.weak_ref = false;
1588        sym.flags.tentative = false;
1589        sym.visibility = .global;
1590
1591        nlist.n_value = 0;
1592        nlist.n_type = .{ .bits = .{ .ext = true, .type = .sect, .pext = false, .is_stab = 0 } };
1593        nlist.n_sect = 0;
1594        nlist.n_desc = @bitCast(@as(u16, 0));
1595        nlist_atom.* = atom_index;
1596    }
1597}
1598
1599fn addSection(self: *Object, allocator: Allocator, segname: []const u8, sectname: []const u8) !u8 {
1600    const n_sect = @as(u8, @intCast(try self.sections.addOne(allocator)));
1601    self.sections.set(n_sect, .{
1602        .header = .{
1603            .sectname = MachO.makeStaticString(sectname),
1604            .segname = MachO.makeStaticString(segname),
1605        },
1606    });
1607    return n_sect;
1608}
1609
1610pub fn parseAr(self: *Object, macho_file: *MachO) !void {
1611    const tracy = trace(@src());
1612    defer tracy.end();
1613
1614    const gpa = macho_file.base.comp.gpa;
1615    const handle = macho_file.getFileHandle(self.file_handle);
1616
1617    var header_buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined;
1618    {
1619        const amt = try handle.preadAll(&header_buffer, self.offset);
1620        if (amt != @sizeOf(macho.mach_header_64)) return error.InputOutput;
1621    }
1622    self.header = @as(*align(1) const macho.mach_header_64, @ptrCast(&header_buffer)).*;
1623
1624    const this_cpu_arch: std.Target.Cpu.Arch = switch (self.header.?.cputype) {
1625        macho.CPU_TYPE_ARM64 => .aarch64,
1626        macho.CPU_TYPE_X86_64 => .x86_64,
1627        else => |x| {
1628            try macho_file.reportParseError2(self.index, "unknown cpu architecture: {d}", .{x});
1629            return error.InvalidMachineType;
1630        },
1631    };
1632    if (macho_file.getTarget().cpu.arch != this_cpu_arch) {
1633        try macho_file.reportParseError2(self.index, "invalid cpu architecture: {s}", .{@tagName(this_cpu_arch)});
1634        return error.InvalidMachineType;
1635    }
1636
1637    const lc_buffer = try gpa.alloc(u8, self.header.?.sizeofcmds);
1638    defer gpa.free(lc_buffer);
1639    {
1640        const amt = try handle.preadAll(lc_buffer, self.offset + @sizeOf(macho.mach_header_64));
1641        if (amt != self.header.?.sizeofcmds) return error.InputOutput;
1642    }
1643
1644    var it = LoadCommandIterator.init(&self.header.?, lc_buffer) catch |err| std.debug.panic("bad object: {t}", .{err});
1645    while (it.next() catch |err| std.debug.panic("bad object: {t}", .{err})) |lc| switch (lc.hdr.cmd) {
1646        .SYMTAB => {
1647            const cmd = lc.cast(macho.symtab_command).?;
1648            try self.strtab.resize(gpa, cmd.strsize);
1649            {
1650                const amt = try handle.preadAll(self.strtab.items, cmd.stroff + self.offset);
1651                if (amt != self.strtab.items.len) return error.InputOutput;
1652            }
1653
1654            const symtab_buffer = try gpa.alloc(u8, cmd.nsyms * @sizeOf(macho.nlist_64));
1655            defer gpa.free(symtab_buffer);
1656            {
1657                const amt = try handle.preadAll(symtab_buffer, cmd.symoff + self.offset);
1658                if (amt != symtab_buffer.len) return error.InputOutput;
1659            }
1660            const symtab = @as([*]align(1) const macho.nlist_64, @ptrCast(symtab_buffer.ptr))[0..cmd.nsyms];
1661            try self.symtab.ensureUnusedCapacity(gpa, symtab.len);
1662            for (symtab) |nlist| {
1663                self.symtab.appendAssumeCapacity(.{
1664                    .nlist = nlist,
1665                    .atom = 0,
1666                    .size = 0,
1667                });
1668            }
1669        },
1670        .BUILD_VERSION,
1671        .VERSION_MIN_MACOSX,
1672        .VERSION_MIN_IPHONEOS,
1673        .VERSION_MIN_TVOS,
1674        .VERSION_MIN_WATCHOS,
1675        => if (self.platform == null) {
1676            self.platform = MachO.Platform.fromLoadCommand(lc);
1677        },
1678        else => {},
1679    };
1680}
1681
1682pub fn updateArSymtab(self: Object, ar_symtab: *Archive.ArSymtab, macho_file: *MachO) error{OutOfMemory}!void {
1683    const gpa = macho_file.base.comp.gpa;
1684    for (self.symtab.items(.nlist)) |nlist| {
1685        if (!nlist.n_type.bits.ext or (nlist.n_type.bits.type == .undf and !nlist.tentative())) continue;
1686        const off = try ar_symtab.strtab.insert(gpa, self.getNStrx(nlist.n_strx));
1687        try ar_symtab.entries.append(gpa, .{ .off = off, .file = self.index });
1688    }
1689}
1690
1691pub fn updateArSize(self: *Object, macho_file: *MachO) !void {
1692    self.output_ar_state.size = if (self.in_archive) |ar| ar.size else size: {
1693        const file = macho_file.getFileHandle(self.file_handle);
1694        break :size (try file.stat()).size;
1695    };
1696}
1697
1698pub fn writeAr(self: Object, ar_format: Archive.Format, macho_file: *MachO, writer: anytype) !void {
1699    // Header
1700    const size = try macho_file.cast(usize, self.output_ar_state.size);
1701    const basename = std.fs.path.basename(self.path);
1702    try Archive.writeHeader(basename, size, ar_format, writer);
1703    // Data
1704    const file = macho_file.getFileHandle(self.file_handle);
1705    // TODO try using copyRangeAll
1706    const gpa = macho_file.base.comp.gpa;
1707    const data = try gpa.alloc(u8, size);
1708    defer gpa.free(data);
1709    const amt = try file.preadAll(data, self.offset);
1710    if (amt != size) return error.InputOutput;
1711    try writer.writeAll(data);
1712}
1713
1714pub fn calcSymtabSize(self: *Object, macho_file: *MachO) void {
1715    const tracy = trace(@src());
1716    defer tracy.end();
1717
1718    const is_obj = macho_file.base.isObject();
1719
1720    for (self.symbols.items, 0..) |*sym, i| {
1721        const ref = self.getSymbolRef(@intCast(i), macho_file);
1722        const file = ref.getFile(macho_file) orelse continue;
1723        if (file.getIndex() != self.index) continue;
1724        if (sym.getAtom(macho_file)) |atom| if (!atom.isAlive()) continue;
1725        if (sym.isSymbolStab(macho_file)) continue;
1726        if (macho_file.discard_local_symbols and sym.isLocal()) continue;
1727        const name = sym.getName(macho_file);
1728        if (name.len == 0) continue;
1729        // TODO in -r mode, we actually want to merge symbol names and emit only one
1730        // work it out when emitting relocs
1731        if ((name[0] == 'L' or name[0] == 'l' or
1732            mem.startsWith(u8, name, "_OBJC_SELECTOR_REFERENCES_")) and
1733            !is_obj)
1734            continue;
1735        sym.flags.output_symtab = true;
1736        if (sym.isLocal()) {
1737            sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file);
1738            self.output_symtab_ctx.nlocals += 1;
1739        } else if (sym.flags.@"export") {
1740            sym.addExtra(.{ .symtab = self.output_symtab_ctx.nexports }, macho_file);
1741            self.output_symtab_ctx.nexports += 1;
1742        } else {
1743            assert(sym.flags.import);
1744            sym.addExtra(.{ .symtab = self.output_symtab_ctx.nimports }, macho_file);
1745            self.output_symtab_ctx.nimports += 1;
1746        }
1747        self.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + 1));
1748    }
1749
1750    if (macho_file.base.comp.config.debug_format != .strip and self.hasDebugInfo())
1751        self.calcStabsSize(macho_file);
1752}
1753
1754fn calcStabsSize(self: *Object, macho_file: *MachO) void {
1755    if (self.compile_unit) |cu| {
1756        const comp_dir = cu.getCompDir(self.*);
1757        const tu_name = cu.getTuName(self.*);
1758
1759        self.output_symtab_ctx.nstabs += 4; // N_SO, N_SO, N_OSO, N_SO
1760        self.output_symtab_ctx.strsize += @as(u32, @intCast(comp_dir.len + 1)); // comp_dir
1761        self.output_symtab_ctx.strsize += @as(u32, @intCast(tu_name.len + 1)); // tu_name
1762
1763        if (self.in_archive) |ar| {
1764            // "/path/to/archive.a(object.o)\x00"
1765            self.output_symtab_ctx.strsize += @intCast(ar.path.len + self.path.len + 3);
1766        } else {
1767            // "/path/to/object.o\x00"
1768            self.output_symtab_ctx.strsize += @intCast(self.path.len + 1);
1769        }
1770
1771        for (self.symbols.items, 0..) |sym, i| {
1772            const ref = self.getSymbolRef(@intCast(i), macho_file);
1773            const file = ref.getFile(macho_file) orelse continue;
1774            if (file.getIndex() != self.index) continue;
1775            if (!sym.flags.output_symtab) continue;
1776            if (macho_file.base.isObject()) {
1777                const name = sym.getName(macho_file);
1778                if (name.len > 0 and (name[0] == 'L' or name[0] == 'l')) continue;
1779            }
1780            const sect = macho_file.sections.items(.header)[sym.getOutputSectionIndex(macho_file)];
1781            if (sect.isCode()) {
1782                self.output_symtab_ctx.nstabs += 4; // N_BNSYM, N_FUN, N_FUN, N_ENSYM
1783            } else if (sym.visibility == .global) {
1784                self.output_symtab_ctx.nstabs += 1; // N_GSYM
1785            } else {
1786                self.output_symtab_ctx.nstabs += 1; // N_STSYM
1787            }
1788        }
1789    } else {
1790        assert(self.hasSymbolStabs());
1791
1792        for (self.stab_files.items) |sf| {
1793            self.output_symtab_ctx.nstabs += 4; // N_SO, N_SO, N_OSO, N_SO
1794            self.output_symtab_ctx.strsize += @as(u32, @intCast(sf.getCompDir(self.*).len + 1)); // comp_dir
1795            self.output_symtab_ctx.strsize += @as(u32, @intCast(sf.getTuName(self.*).len + 1)); // tu_name
1796            self.output_symtab_ctx.strsize += @as(u32, @intCast(sf.getOsoPath(self.*).len + 1)); // path
1797
1798            for (sf.stabs.items) |stab| {
1799                const sym = stab.getSymbol(self.*) orelse continue;
1800                const file = sym.getFile(macho_file).?;
1801                if (file.getIndex() != self.index) continue;
1802                if (!sym.flags.output_symtab) continue;
1803                const nstabs: u32 = if (stab.is_func) 4 else 1;
1804                self.output_symtab_ctx.nstabs += nstabs;
1805            }
1806        }
1807    }
1808}
1809
1810pub fn writeAtoms(self: *Object, macho_file: *MachO) !void {
1811    const tracy = trace(@src());
1812    defer tracy.end();
1813
1814    const gpa = macho_file.base.comp.gpa;
1815    const headers = self.sections.items(.header);
1816    const sections_data = try gpa.alloc([]const u8, headers.len);
1817    defer {
1818        for (sections_data) |data| {
1819            gpa.free(data);
1820        }
1821        gpa.free(sections_data);
1822    }
1823    @memset(sections_data, &[0]u8{});
1824    const file = macho_file.getFileHandle(self.file_handle);
1825
1826    for (headers, 0..) |header, n_sect| {
1827        if (header.isZerofill()) continue;
1828        const size = try macho_file.cast(usize, header.size);
1829        const data = try gpa.alloc(u8, size);
1830        const amt = try file.preadAll(data, header.offset + self.offset);
1831        if (amt != data.len) return error.InputOutput;
1832        sections_data[n_sect] = data;
1833    }
1834    for (self.getAtoms()) |atom_index| {
1835        const atom = self.getAtom(atom_index) orelse continue;
1836        if (!atom.isAlive()) continue;
1837        const sect = atom.getInputSection(macho_file);
1838        if (sect.isZerofill()) continue;
1839        const value = try macho_file.cast(usize, atom.value);
1840        const off = try macho_file.cast(usize, atom.off);
1841        const size = try macho_file.cast(usize, atom.size);
1842        const buffer = macho_file.sections.items(.out)[atom.out_n_sect].items;
1843        const data = sections_data[atom.n_sect];
1844        @memcpy(buffer[value..][0..size], data[off..][0..size]);
1845        try atom.resolveRelocs(macho_file, buffer[value..][0..size]);
1846    }
1847}
1848
1849pub fn writeAtomsRelocatable(self: *Object, macho_file: *MachO) !void {
1850    const tracy = trace(@src());
1851    defer tracy.end();
1852
1853    const gpa = macho_file.base.comp.gpa;
1854    const headers = self.sections.items(.header);
1855    const sections_data = try gpa.alloc([]const u8, headers.len);
1856    defer {
1857        for (sections_data) |data| {
1858            gpa.free(data);
1859        }
1860        gpa.free(sections_data);
1861    }
1862    @memset(sections_data, &[0]u8{});
1863    const file = macho_file.getFileHandle(self.file_handle);
1864
1865    for (headers, 0..) |header, n_sect| {
1866        if (header.isZerofill()) continue;
1867        const size = try macho_file.cast(usize, header.size);
1868        const data = try gpa.alloc(u8, size);
1869        const amt = try file.preadAll(data, header.offset + self.offset);
1870        if (amt != data.len) return error.InputOutput;
1871        sections_data[n_sect] = data;
1872    }
1873    for (self.getAtoms()) |atom_index| {
1874        const atom = self.getAtom(atom_index) orelse continue;
1875        if (!atom.isAlive()) continue;
1876        const sect = atom.getInputSection(macho_file);
1877        if (sect.isZerofill()) continue;
1878        const value = try macho_file.cast(usize, atom.value);
1879        const off = try macho_file.cast(usize, atom.off);
1880        const size = try macho_file.cast(usize, atom.size);
1881        const buffer = macho_file.sections.items(.out)[atom.out_n_sect].items;
1882        const data = sections_data[atom.n_sect];
1883        @memcpy(buffer[value..][0..size], data[off..][0..size]);
1884        const relocs = macho_file.sections.items(.relocs)[atom.out_n_sect].items;
1885        const extra = atom.getExtra(macho_file);
1886        try atom.writeRelocs(macho_file, buffer[value..][0..size], relocs[extra.rel_out_index..][0..extra.rel_out_count]);
1887    }
1888}
1889
1890pub fn calcCompactUnwindSizeRelocatable(self: *Object, macho_file: *MachO) void {
1891    const tracy = trace(@src());
1892    defer tracy.end();
1893
1894    const ctx = &self.compact_unwind_ctx;
1895
1896    for (self.unwind_records_indexes.items) |irec| {
1897        const rec = self.getUnwindRecord(irec);
1898        if (!rec.alive) continue;
1899
1900        ctx.rec_count += 1;
1901        ctx.reloc_count += 1;
1902        if (rec.getPersonality(macho_file)) |_| {
1903            ctx.reloc_count += 1;
1904        }
1905        if (rec.getLsdaAtom(macho_file)) |_| {
1906            ctx.reloc_count += 1;
1907        }
1908    }
1909}
1910
1911fn addReloc(offset: u32, arch: std.Target.Cpu.Arch) !macho.relocation_info {
1912    return .{
1913        .r_address = std.math.cast(i32, offset) orelse return error.Overflow,
1914        .r_symbolnum = 0,
1915        .r_pcrel = 0,
1916        .r_length = 3,
1917        .r_extern = 0,
1918        .r_type = switch (arch) {
1919            .aarch64 => @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_UNSIGNED),
1920            .x86_64 => @intFromEnum(macho.reloc_type_x86_64.X86_64_RELOC_UNSIGNED),
1921            else => unreachable,
1922        },
1923    };
1924}
1925
1926pub fn writeCompactUnwindRelocatable(self: *Object, macho_file: *MachO) !void {
1927    const tracy = trace(@src());
1928    defer tracy.end();
1929
1930    const cpu_arch = macho_file.getTarget().cpu.arch;
1931
1932    const nsect = macho_file.unwind_info_sect_index.?;
1933    const buffer = macho_file.sections.items(.out)[nsect].items;
1934    const relocs = macho_file.sections.items(.relocs)[nsect].items;
1935
1936    var rec_index: u32 = self.compact_unwind_ctx.rec_index;
1937    var reloc_index: u32 = self.compact_unwind_ctx.reloc_index;
1938
1939    for (self.unwind_records_indexes.items) |irec| {
1940        const rec = self.getUnwindRecord(irec);
1941        if (!rec.alive) continue;
1942
1943        var out: macho.compact_unwind_entry = .{
1944            .rangeStart = 0,
1945            .rangeLength = rec.length,
1946            .compactUnwindEncoding = rec.enc.enc,
1947            .personalityFunction = 0,
1948            .lsda = 0,
1949        };
1950        defer rec_index += 1;
1951
1952        const offset = rec_index * @sizeOf(macho.compact_unwind_entry);
1953
1954        {
1955            // Function address
1956            const atom = rec.getAtom(macho_file);
1957            const addr = rec.getAtomAddress(macho_file);
1958            out.rangeStart = addr;
1959            var reloc = try addReloc(offset, cpu_arch);
1960            reloc.r_symbolnum = atom.out_n_sect + 1;
1961            relocs[reloc_index] = reloc;
1962            reloc_index += 1;
1963        }
1964
1965        // Personality function
1966        if (rec.getPersonality(macho_file)) |sym| {
1967            const r_symbolnum = try macho_file.cast(u24, sym.getOutputSymtabIndex(macho_file).?);
1968            var reloc = try addReloc(offset + 16, cpu_arch);
1969            reloc.r_symbolnum = r_symbolnum;
1970            reloc.r_extern = 1;
1971            relocs[reloc_index] = reloc;
1972            reloc_index += 1;
1973        }
1974
1975        // LSDA address
1976        if (rec.getLsdaAtom(macho_file)) |atom| {
1977            const addr = rec.getLsdaAddress(macho_file);
1978            out.lsda = addr;
1979            var reloc = try addReloc(offset + 24, cpu_arch);
1980            reloc.r_symbolnum = atom.out_n_sect + 1;
1981            relocs[reloc_index] = reloc;
1982            reloc_index += 1;
1983        }
1984
1985        @memcpy(buffer[offset..][0..@sizeOf(macho.compact_unwind_entry)], mem.asBytes(&out));
1986    }
1987}
1988
1989pub fn writeSymtab(self: Object, macho_file: *MachO, ctx: anytype) void {
1990    const tracy = trace(@src());
1991    defer tracy.end();
1992
1993    var n_strx = self.output_symtab_ctx.stroff;
1994    for (self.symbols.items, 0..) |sym, i| {
1995        const ref = self.getSymbolRef(@intCast(i), macho_file);
1996        const file = ref.getFile(macho_file) orelse continue;
1997        if (file.getIndex() != self.index) continue;
1998        const idx = sym.getOutputSymtabIndex(macho_file) orelse continue;
1999        const out_sym = &ctx.symtab.items[idx];
2000        out_sym.n_strx = n_strx;
2001        sym.setOutputSym(macho_file, out_sym);
2002        const name = sym.getName(macho_file);
2003        @memcpy(ctx.strtab.items[n_strx..][0..name.len], name);
2004        n_strx += @intCast(name.len);
2005        ctx.strtab.items[n_strx] = 0;
2006        n_strx += 1;
2007    }
2008
2009    if (macho_file.base.comp.config.debug_format != .strip and self.hasDebugInfo())
2010        self.writeStabs(n_strx, macho_file, ctx);
2011}
2012
2013fn writeStabs(self: Object, stroff: u32, macho_file: *MachO, ctx: anytype) void {
2014    const writeFuncStab = struct {
2015        inline fn writeFuncStab(
2016            n_strx: u32,
2017            n_sect: u8,
2018            n_value: u64,
2019            size: u64,
2020            index: u32,
2021            context: anytype,
2022        ) void {
2023            context.symtab.items[index] = .{
2024                .n_strx = 0,
2025                .n_type = .{ .stab = .bnsym },
2026                .n_sect = n_sect,
2027                .n_desc = @bitCast(@as(u16, 0)),
2028                .n_value = n_value,
2029            };
2030            context.symtab.items[index + 1] = .{
2031                .n_strx = n_strx,
2032                .n_type = .{ .stab = .fun },
2033                .n_sect = n_sect,
2034                .n_desc = @bitCast(@as(u16, 0)),
2035                .n_value = n_value,
2036            };
2037            context.symtab.items[index + 2] = .{
2038                .n_strx = 0,
2039                .n_type = .{ .stab = .fun },
2040                .n_sect = 0,
2041                .n_desc = @bitCast(@as(u16, 0)),
2042                .n_value = size,
2043            };
2044            context.symtab.items[index + 3] = .{
2045                .n_strx = 0,
2046                .n_type = .{ .stab = .ensym },
2047                .n_sect = n_sect,
2048                .n_desc = @bitCast(@as(u16, 0)),
2049                .n_value = size,
2050            };
2051        }
2052    }.writeFuncStab;
2053
2054    var index = self.output_symtab_ctx.istab;
2055    var n_strx = stroff;
2056
2057    if (self.compile_unit) |cu| {
2058        const comp_dir = cu.getCompDir(self);
2059        const tu_name = cu.getTuName(self);
2060
2061        // Open scope
2062        // N_SO comp_dir
2063        ctx.symtab.items[index] = .{
2064            .n_strx = n_strx,
2065            .n_type = .{ .stab = .so },
2066            .n_sect = 0,
2067            .n_desc = @bitCast(@as(u16, 0)),
2068            .n_value = 0,
2069        };
2070        index += 1;
2071        @memcpy(ctx.strtab.items[n_strx..][0..comp_dir.len], comp_dir);
2072        n_strx += @intCast(comp_dir.len);
2073        ctx.strtab.items[n_strx] = 0;
2074        n_strx += 1;
2075        // N_SO tu_name
2076        macho_file.symtab.items[index] = .{
2077            .n_strx = n_strx,
2078            .n_type = .{ .stab = .so },
2079            .n_sect = 0,
2080            .n_desc = @bitCast(@as(u16, 0)),
2081            .n_value = 0,
2082        };
2083        index += 1;
2084        @memcpy(ctx.strtab.items[n_strx..][0..tu_name.len], tu_name);
2085        n_strx += @intCast(tu_name.len);
2086        ctx.strtab.items[n_strx] = 0;
2087        n_strx += 1;
2088        // N_OSO path
2089        ctx.symtab.items[index] = .{
2090            .n_strx = n_strx,
2091            .n_type = .{ .stab = .oso },
2092            .n_sect = 0,
2093            .n_desc = @bitCast(@as(u16, 1)),
2094            .n_value = self.mtime,
2095        };
2096        index += 1;
2097        if (self.in_archive) |ar| {
2098            // "/path/to/archive.a(object.o)\x00"
2099            @memcpy(ctx.strtab.items[n_strx..][0..ar.path.len], ar.path);
2100            n_strx += @intCast(ar.path.len);
2101            ctx.strtab.items[n_strx..][0] = '(';
2102            n_strx += 1;
2103            @memcpy(ctx.strtab.items[n_strx..][0..self.path.len], self.path);
2104            n_strx += @intCast(self.path.len);
2105            ctx.strtab.items[n_strx..][0..2].* = ")\x00".*;
2106            n_strx += 2;
2107        } else {
2108            // "/path/to/object.o\x00"
2109            @memcpy(ctx.strtab.items[n_strx..][0..self.path.len], self.path);
2110            ctx.strtab.items[n_strx..][self.path.len] = 0;
2111            n_strx += @intCast(self.path.len + 1);
2112        }
2113
2114        for (self.symbols.items, 0..) |sym, i| {
2115            const ref = self.getSymbolRef(@intCast(i), macho_file);
2116            const file = ref.getFile(macho_file) orelse continue;
2117            if (file.getIndex() != self.index) continue;
2118            if (!sym.flags.output_symtab) continue;
2119            if (macho_file.base.isObject()) {
2120                const name = sym.getName(macho_file);
2121                if (name.len > 0 and (name[0] == 'L' or name[0] == 'l')) continue;
2122            }
2123            const sect = macho_file.sections.items(.header)[sym.getOutputSectionIndex(macho_file)];
2124            const sym_n_strx = n_strx: {
2125                const symtab_index = sym.getOutputSymtabIndex(macho_file).?;
2126                const osym = ctx.symtab.items[symtab_index];
2127                break :n_strx osym.n_strx;
2128            };
2129            const sym_n_sect: u8 = if (!sym.flags.abs) @intCast(sym.getOutputSectionIndex(macho_file) + 1) else 0;
2130            const sym_n_value = sym.getAddress(.{}, macho_file);
2131            const sym_size = sym.getSize(macho_file);
2132            if (sect.isCode()) {
2133                writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, ctx);
2134                index += 4;
2135            } else if (sym.visibility == .global) {
2136                ctx.symtab.items[index] = .{
2137                    .n_strx = sym_n_strx,
2138                    .n_type = .{ .stab = .gsym },
2139                    .n_sect = sym_n_sect,
2140                    .n_desc = @bitCast(@as(u16, 0)),
2141                    .n_value = 0,
2142                };
2143                index += 1;
2144            } else {
2145                ctx.symtab.items[index] = .{
2146                    .n_strx = sym_n_strx,
2147                    .n_type = .{ .stab = .stsym },
2148                    .n_sect = sym_n_sect,
2149                    .n_desc = @bitCast(@as(u16, 0)),
2150                    .n_value = sym_n_value,
2151                };
2152                index += 1;
2153            }
2154        }
2155
2156        // Close scope
2157        // N_SO
2158        ctx.symtab.items[index] = .{
2159            .n_strx = 0,
2160            .n_type = .{ .stab = .so },
2161            .n_sect = 0,
2162            .n_desc = @bitCast(@as(u16, 0)),
2163            .n_value = 0,
2164        };
2165    } else {
2166        assert(self.hasSymbolStabs());
2167
2168        for (self.stab_files.items) |sf| {
2169            const comp_dir = sf.getCompDir(self);
2170            const tu_name = sf.getTuName(self);
2171            const oso_path = sf.getOsoPath(self);
2172
2173            // Open scope
2174            // N_SO comp_dir
2175            ctx.symtab.items[index] = .{
2176                .n_strx = n_strx,
2177                .n_type = .{ .stab = .so },
2178                .n_sect = 0,
2179                .n_desc = @bitCast(@as(u16, 0)),
2180                .n_value = 0,
2181            };
2182            index += 1;
2183            @memcpy(ctx.strtab.items[n_strx..][0..comp_dir.len], comp_dir);
2184            n_strx += @intCast(comp_dir.len);
2185            ctx.strtab.items[n_strx] = 0;
2186            n_strx += 1;
2187            // N_SO tu_name
2188            ctx.symtab.items[index] = .{
2189                .n_strx = n_strx,
2190                .n_type = .{ .stab = .so },
2191                .n_sect = 0,
2192                .n_desc = @bitCast(@as(u16, 0)),
2193                .n_value = 0,
2194            };
2195            index += 1;
2196            @memcpy(ctx.strtab.items[n_strx..][0..tu_name.len], tu_name);
2197            n_strx += @intCast(tu_name.len);
2198            ctx.strtab.items[n_strx] = 0;
2199            n_strx += 1;
2200            // N_OSO path
2201            ctx.symtab.items[index] = .{
2202                .n_strx = n_strx,
2203                .n_type = .{ .stab = .so },
2204                .n_sect = 0,
2205                .n_desc = @bitCast(@as(u16, 1)),
2206                .n_value = sf.getOsoModTime(self),
2207            };
2208            index += 1;
2209            @memcpy(ctx.strtab.items[n_strx..][0..oso_path.len], oso_path);
2210            n_strx += @intCast(oso_path.len);
2211            ctx.strtab.items[n_strx] = 0;
2212            n_strx += 1;
2213
2214            for (sf.stabs.items) |stab| {
2215                const sym = stab.getSymbol(self) orelse continue;
2216                const file = sym.getFile(macho_file).?;
2217                if (file.getIndex() != self.index) continue;
2218                if (!sym.flags.output_symtab) continue;
2219                const sym_n_strx = n_strx: {
2220                    const symtab_index = sym.getOutputSymtabIndex(macho_file).?;
2221                    const osym = ctx.symtab.items[symtab_index];
2222                    break :n_strx osym.n_strx;
2223                };
2224                const sym_n_sect: u8 = if (!sym.flags.abs) @intCast(sym.getOutputSectionIndex(macho_file) + 1) else 0;
2225                const sym_n_value = sym.getAddress(.{}, macho_file);
2226                const sym_size = sym.getSize(macho_file);
2227                if (stab.is_func) {
2228                    writeFuncStab(sym_n_strx, sym_n_sect, sym_n_value, sym_size, index, ctx);
2229                    index += 4;
2230                } else if (sym.visibility == .global) {
2231                    ctx.symtab.items[index] = .{
2232                        .n_strx = sym_n_strx,
2233                        .n_type = .{ .stab = .gsym },
2234                        .n_sect = sym_n_sect,
2235                        .n_desc = @bitCast(@as(u16, 0)),
2236                        .n_value = 0,
2237                    };
2238                    index += 1;
2239                } else {
2240                    ctx.symtab.items[index] = .{
2241                        .n_strx = sym_n_strx,
2242                        .n_type = .{ .stab = .stsym },
2243                        .n_sect = sym_n_sect,
2244                        .n_desc = @bitCast(@as(u16, 0)),
2245                        .n_value = sym_n_value,
2246                    };
2247                    index += 1;
2248                }
2249            }
2250
2251            // Close scope
2252            // N_SO
2253            ctx.symtab.items[index] = .{
2254                .n_strx = 0,
2255                .n_type = .{ .stab = .so },
2256                .n_sect = 0,
2257                .n_desc = @bitCast(@as(u16, 0)),
2258                .n_value = 0,
2259            };
2260            index += 1;
2261        }
2262    }
2263}
2264
2265pub fn getAtomRelocs(self: *const Object, atom: Atom, macho_file: *MachO) []const Relocation {
2266    const extra = atom.getExtra(macho_file);
2267    const relocs = self.sections.items(.relocs)[atom.n_sect];
2268    return relocs.items[extra.rel_index..][0..extra.rel_count];
2269}
2270
2271fn addString(self: *Object, allocator: Allocator, string: [:0]const u8) error{OutOfMemory}!MachO.String {
2272    const off: u32 = @intCast(self.strtab.items.len);
2273    try self.strtab.ensureUnusedCapacity(allocator, string.len + 1);
2274    self.strtab.appendSliceAssumeCapacity(string);
2275    self.strtab.appendAssumeCapacity(0);
2276    return .{ .pos = off, .len = @intCast(string.len + 1) };
2277}
2278
2279pub fn getString(self: Object, string: MachO.String) [:0]const u8 {
2280    assert(string.pos < self.strtab.items.len and string.pos + string.len <= self.strtab.items.len);
2281    if (string.len == 0) return "";
2282    return self.strtab.items[string.pos..][0 .. string.len - 1 :0];
2283}
2284
2285fn getNStrx(self: Object, n_strx: u32) [:0]const u8 {
2286    assert(n_strx < self.strtab.items.len);
2287    return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.items.ptr + n_strx)), 0);
2288}
2289
2290pub fn hasUnwindRecords(self: Object) bool {
2291    return self.unwind_records.items.len > 0;
2292}
2293
2294pub fn hasEhFrameRecords(self: Object) bool {
2295    return self.cies.items.len > 0;
2296}
2297
2298pub fn hasDebugInfo(self: Object) bool {
2299    return self.compile_unit != null or self.hasSymbolStabs();
2300}
2301
2302fn hasSymbolStabs(self: Object) bool {
2303    return self.stab_files.items.len > 0;
2304}
2305
2306fn hasObjC(self: Object) bool {
2307    for (self.symtab.items(.nlist)) |nlist| {
2308        const name = self.getNStrx(nlist.n_strx);
2309        if (mem.startsWith(u8, name, "_OBJC_CLASS_$_")) return true;
2310    }
2311    for (self.sections.items(.header)) |sect| {
2312        if (mem.eql(u8, sect.segName(), "__DATA") and mem.eql(u8, sect.sectName(), "__objc_catlist")) return true;
2313        if (mem.eql(u8, sect.segName(), "__TEXT") and mem.eql(u8, sect.sectName(), "__swift")) return true;
2314    }
2315    return false;
2316}
2317
2318pub fn getDataInCode(self: Object) []const macho.data_in_code_entry {
2319    return self.data_in_code.items;
2320}
2321
2322pub inline fn hasSubsections(self: Object) bool {
2323    return self.header.?.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0;
2324}
2325
2326pub fn asFile(self: *Object) File {
2327    return .{ .object = self };
2328}
2329
2330const AddAtomArgs = struct {
2331    name: MachO.String,
2332    n_sect: u8,
2333    off: u64,
2334    size: u64,
2335    alignment: u32,
2336};
2337
2338fn addAtom(self: *Object, allocator: Allocator, args: AddAtomArgs) !Atom.Index {
2339    const atom_index: Atom.Index = @intCast(self.atoms.items.len);
2340    const atom = try self.atoms.addOne(allocator);
2341    atom.* = .{
2342        .file = self.index,
2343        .atom_index = atom_index,
2344        .name = args.name,
2345        .n_sect = args.n_sect,
2346        .size = args.size,
2347        .off = args.off,
2348        .extra = try self.addAtomExtra(allocator, .{}),
2349        .alignment = Atom.Alignment.fromLog2Units(args.alignment),
2350    };
2351    return atom_index;
2352}
2353
2354pub fn getAtom(self: *Object, atom_index: Atom.Index) ?*Atom {
2355    if (atom_index == 0) return null;
2356    assert(atom_index < self.atoms.items.len);
2357    return &self.atoms.items[atom_index];
2358}
2359
2360pub fn getAtoms(self: *Object) []const Atom.Index {
2361    return self.atoms_indexes.items;
2362}
2363
2364fn addAtomExtra(self: *Object, allocator: Allocator, extra: Atom.Extra) !u32 {
2365    const fields = @typeInfo(Atom.Extra).@"struct".fields;
2366    try self.atoms_extra.ensureUnusedCapacity(allocator, fields.len);
2367    return self.addAtomExtraAssumeCapacity(extra);
2368}
2369
2370fn addAtomExtraAssumeCapacity(self: *Object, extra: Atom.Extra) u32 {
2371    const index = @as(u32, @intCast(self.atoms_extra.items.len));
2372    const fields = @typeInfo(Atom.Extra).@"struct".fields;
2373    inline for (fields) |field| {
2374        self.atoms_extra.appendAssumeCapacity(switch (field.type) {
2375            u32 => @field(extra, field.name),
2376            else => @compileError("bad field type"),
2377        });
2378    }
2379    return index;
2380}
2381
2382pub fn getAtomExtra(self: Object, index: u32) Atom.Extra {
2383    const fields = @typeInfo(Atom.Extra).@"struct".fields;
2384    var i: usize = index;
2385    var result: Atom.Extra = undefined;
2386    inline for (fields) |field| {
2387        @field(result, field.name) = switch (field.type) {
2388            u32 => self.atoms_extra.items[i],
2389            else => @compileError("bad field type"),
2390        };
2391        i += 1;
2392    }
2393    return result;
2394}
2395
2396pub fn setAtomExtra(self: *Object, index: u32, extra: Atom.Extra) void {
2397    assert(index > 0);
2398    const fields = @typeInfo(Atom.Extra).@"struct".fields;
2399    inline for (fields, 0..) |field, i| {
2400        self.atoms_extra.items[index + i] = switch (field.type) {
2401            u32 => @field(extra, field.name),
2402            else => @compileError("bad field type"),
2403        };
2404    }
2405}
2406
2407fn addSymbol(self: *Object, allocator: Allocator) !Symbol.Index {
2408    try self.symbols.ensureUnusedCapacity(allocator, 1);
2409    return self.addSymbolAssumeCapacity();
2410}
2411
2412fn addSymbolAssumeCapacity(self: *Object) Symbol.Index {
2413    const index: Symbol.Index = @intCast(self.symbols.items.len);
2414    const symbol = self.symbols.addOneAssumeCapacity();
2415    symbol.* = .{ .file = self.index };
2416    return index;
2417}
2418
2419pub fn getSymbolRef(self: Object, index: Symbol.Index, macho_file: *MachO) MachO.Ref {
2420    const global_index = self.globals.items[index];
2421    if (macho_file.resolver.get(global_index)) |ref| return ref;
2422    return .{ .index = index, .file = self.index };
2423}
2424
2425pub fn addSymbolExtra(self: *Object, allocator: Allocator, extra: Symbol.Extra) !u32 {
2426    const fields = @typeInfo(Symbol.Extra).@"struct".fields;
2427    try self.symbols_extra.ensureUnusedCapacity(allocator, fields.len);
2428    return self.addSymbolExtraAssumeCapacity(extra);
2429}
2430
2431fn addSymbolExtraAssumeCapacity(self: *Object, extra: Symbol.Extra) u32 {
2432    const index = @as(u32, @intCast(self.symbols_extra.items.len));
2433    const fields = @typeInfo(Symbol.Extra).@"struct".fields;
2434    inline for (fields) |field| {
2435        self.symbols_extra.appendAssumeCapacity(switch (field.type) {
2436            u32 => @field(extra, field.name),
2437            else => @compileError("bad field type"),
2438        });
2439    }
2440    return index;
2441}
2442
2443pub fn getSymbolExtra(self: Object, index: u32) Symbol.Extra {
2444    const fields = @typeInfo(Symbol.Extra).@"struct".fields;
2445    var i: usize = index;
2446    var result: Symbol.Extra = undefined;
2447    inline for (fields) |field| {
2448        @field(result, field.name) = switch (field.type) {
2449            u32 => self.symbols_extra.items[i],
2450            else => @compileError("bad field type"),
2451        };
2452        i += 1;
2453    }
2454    return result;
2455}
2456
2457pub fn setSymbolExtra(self: *Object, index: u32, extra: Symbol.Extra) void {
2458    const fields = @typeInfo(Symbol.Extra).@"struct".fields;
2459    inline for (fields, 0..) |field, i| {
2460        self.symbols_extra.items[index + i] = switch (field.type) {
2461            u32 => @field(extra, field.name),
2462            else => @compileError("bad field type"),
2463        };
2464    }
2465}
2466
2467fn addUnwindRecord(self: *Object, allocator: Allocator) !UnwindInfo.Record.Index {
2468    try self.unwind_records.ensureUnusedCapacity(allocator, 1);
2469    return self.addUnwindRecordAssumeCapacity();
2470}
2471
2472fn addUnwindRecordAssumeCapacity(self: *Object) UnwindInfo.Record.Index {
2473    const index = @as(UnwindInfo.Record.Index, @intCast(self.unwind_records.items.len));
2474    const rec = self.unwind_records.addOneAssumeCapacity();
2475    rec.* = .{ .file = self.index };
2476    return index;
2477}
2478
2479pub fn getUnwindRecord(self: *Object, index: UnwindInfo.Record.Index) *UnwindInfo.Record {
2480    assert(index < self.unwind_records.items.len);
2481    return &self.unwind_records.items[index];
2482}
2483
2484/// Caller owns the memory.
2485pub fn readSectionData(self: Object, allocator: Allocator, file: File.Handle, n_sect: u8) ![]u8 {
2486    const header = self.sections.items(.header)[n_sect];
2487    const size = math.cast(usize, header.size) orelse return error.Overflow;
2488    const data = try allocator.alloc(u8, size);
2489    const amt = try file.preadAll(data, header.offset + self.offset);
2490    errdefer allocator.free(data);
2491    if (amt != data.len) return error.InputOutput;
2492    return data;
2493}
2494
2495const Format = struct {
2496    object: *Object,
2497    macho_file: *MachO,
2498
2499    fn atoms(f: Format, w: *Writer) Writer.Error!void {
2500        const object = f.object;
2501        const macho_file = f.macho_file;
2502        try w.writeAll("  atoms\n");
2503        for (object.getAtoms()) |atom_index| {
2504            const atom = object.getAtom(atom_index) orelse continue;
2505            try w.print("    {f}\n", .{atom.fmt(macho_file)});
2506        }
2507    }
2508    fn cies(f: Format, w: *Writer) Writer.Error!void {
2509        const object = f.object;
2510        try w.writeAll("  cies\n");
2511        for (object.cies.items, 0..) |cie, i| {
2512            try w.print("    cie({d}) : {f}\n", .{ i, cie.fmt(f.macho_file) });
2513        }
2514    }
2515    fn fdes(f: Format, w: *Writer) Writer.Error!void {
2516        const object = f.object;
2517        try w.writeAll("  fdes\n");
2518        for (object.fdes.items, 0..) |fde, i| {
2519            try w.print("    fde({d}) : {f}\n", .{ i, fde.fmt(f.macho_file) });
2520        }
2521    }
2522    fn unwindRecords(f: Format, w: *Writer) Writer.Error!void {
2523        const object = f.object;
2524        const macho_file = f.macho_file;
2525        try w.writeAll("  unwind records\n");
2526        for (object.unwind_records_indexes.items) |rec| {
2527            try w.print("    rec({d}) : {f}\n", .{ rec, object.getUnwindRecord(rec).fmt(macho_file) });
2528        }
2529    }
2530
2531    fn symtab(f: Format, w: *Writer) Writer.Error!void {
2532        const object = f.object;
2533        const macho_file = f.macho_file;
2534        try w.writeAll("  symbols\n");
2535        for (object.symbols.items, 0..) |sym, i| {
2536            const ref = object.getSymbolRef(@intCast(i), macho_file);
2537            if (ref.getFile(macho_file) == null) {
2538                // TODO any better way of handling this?
2539                try w.print("    {s} : unclaimed\n", .{sym.getName(macho_file)});
2540            } else {
2541                try w.print("    {f}\n", .{ref.getSymbol(macho_file).?.fmt(macho_file)});
2542            }
2543        }
2544        for (object.stab_files.items) |sf| {
2545            try w.print("  stabs({s},{s},{s})\n", .{
2546                sf.getCompDir(object.*),
2547                sf.getTuName(object.*),
2548                sf.getOsoPath(object.*),
2549            });
2550            for (sf.stabs.items) |stab| {
2551                try w.print("    {f}", .{stab.fmt(object.*)});
2552            }
2553        }
2554    }
2555};
2556
2557pub fn fmtAtoms(self: *Object, macho_file: *MachO) std.fmt.Alt(Format, Format.atoms) {
2558    return .{ .data = .{
2559        .object = self,
2560        .macho_file = macho_file,
2561    } };
2562}
2563
2564pub fn fmtCies(self: *Object, macho_file: *MachO) std.fmt.Alt(Format, Format.cies) {
2565    return .{ .data = .{
2566        .object = self,
2567        .macho_file = macho_file,
2568    } };
2569}
2570
2571pub fn fmtFdes(self: *Object, macho_file: *MachO) std.fmt.Alt(Format, Format.fdes) {
2572    return .{ .data = .{
2573        .object = self,
2574        .macho_file = macho_file,
2575    } };
2576}
2577
2578pub fn fmtUnwindRecords(self: *Object, macho_file: *MachO) std.fmt.Alt(Format, Format.unwindRecords) {
2579    return .{ .data = .{
2580        .object = self,
2581        .macho_file = macho_file,
2582    } };
2583}
2584
2585pub fn fmtSymtab(self: *Object, macho_file: *MachO) std.fmt.Alt(Format, Format.symtab) {
2586    return .{ .data = .{
2587        .object = self,
2588        .macho_file = macho_file,
2589    } };
2590}
2591
2592pub fn fmtPath(self: Object) std.fmt.Alt(Object, formatPath) {
2593    return .{ .data = self };
2594}
2595
2596fn formatPath(object: Object, w: *Writer) Writer.Error!void {
2597    if (object.in_archive) |ar| {
2598        try w.print("{s}({s})", .{ ar.path, object.path });
2599    } else {
2600        try w.writeAll(object.path);
2601    }
2602}
2603
2604const Section = struct {
2605    header: macho.section_64,
2606    subsections: std.ArrayList(Subsection) = .empty,
2607    relocs: std.ArrayList(Relocation) = .empty,
2608};
2609
2610const Subsection = struct {
2611    atom: Atom.Index,
2612    off: u64,
2613};
2614
2615pub const Nlist = struct {
2616    nlist: macho.nlist_64,
2617    size: u64,
2618    atom: Atom.Index,
2619};
2620
2621const StabFile = struct {
2622    comp_dir: u32,
2623    stabs: std.ArrayList(Stab) = .empty,
2624
2625    fn getCompDir(sf: StabFile, object: Object) [:0]const u8 {
2626        const nlist = object.symtab.items(.nlist)[sf.comp_dir];
2627        return object.getNStrx(nlist.n_strx);
2628    }
2629
2630    fn getTuName(sf: StabFile, object: Object) [:0]const u8 {
2631        const nlist = object.symtab.items(.nlist)[sf.comp_dir + 1];
2632        return object.getNStrx(nlist.n_strx);
2633    }
2634
2635    fn getOsoPath(sf: StabFile, object: Object) [:0]const u8 {
2636        const nlist = object.symtab.items(.nlist)[sf.comp_dir + 2];
2637        return object.getNStrx(nlist.n_strx);
2638    }
2639
2640    fn getOsoModTime(sf: StabFile, object: Object) u64 {
2641        const nlist = object.symtab.items(.nlist)[sf.comp_dir + 2];
2642        return nlist.n_value;
2643    }
2644
2645    const Stab = struct {
2646        is_func: bool = true,
2647        index: ?Symbol.Index = null,
2648
2649        fn getSymbol(stab: Stab, object: Object) ?Symbol {
2650            const index = stab.index orelse return null;
2651            return object.symbols.items[index];
2652        }
2653
2654        const Format = struct {
2655            stab: Stab,
2656            object: Object,
2657
2658            fn default(f: Stab.Format, w: *Writer) Writer.Error!void {
2659                const stab = f.stab;
2660                const sym = stab.getSymbol(f.object).?;
2661                if (stab.is_func) {
2662                    try w.print("func({d})", .{stab.index.?});
2663                } else if (sym.visibility == .global) {
2664                    try w.print("gsym({d})", .{stab.index.?});
2665                } else {
2666                    try w.print("stsym({d})", .{stab.index.?});
2667                }
2668            }
2669        };
2670
2671        pub fn fmt(stab: Stab, object: Object) std.fmt.Alt(Stab.Format, Stab.Format.default) {
2672            return .{ .data = .{ .stab = stab, .object = object } };
2673        }
2674    };
2675};
2676
2677const CompileUnit = struct {
2678    comp_dir: MachO.String,
2679    tu_name: MachO.String,
2680
2681    fn getCompDir(cu: CompileUnit, object: Object) [:0]const u8 {
2682        return object.getString(cu.comp_dir);
2683    }
2684
2685    fn getTuName(cu: CompileUnit, object: Object) [:0]const u8 {
2686        return object.getString(cu.tu_name);
2687    }
2688};
2689
2690const InArchive = struct {
2691    /// This is a fully-resolved absolute path, because that is the path we need to embed in stabs
2692    /// to ensure the output does not depend on its cwd.
2693    path: []u8,
2694    size: u32,
2695};
2696
2697const CompactUnwindCtx = struct {
2698    rec_index: u32 = 0,
2699    rec_count: u32 = 0,
2700    reloc_index: u32 = 0,
2701    reloc_count: u32 = 0,
2702};
2703
2704const x86_64 = struct {
2705    fn parseRelocs(
2706        self: *Object,
2707        n_sect: u8,
2708        sect: macho.section_64,
2709        out: *std.ArrayList(Relocation),
2710        handle: File.Handle,
2711        macho_file: *MachO,
2712    ) !void {
2713        const gpa = macho_file.base.comp.gpa;
2714
2715        const relocs_buffer = try gpa.alloc(u8, sect.nreloc * @sizeOf(macho.relocation_info));
2716        defer gpa.free(relocs_buffer);
2717        const amt = try handle.preadAll(relocs_buffer, sect.reloff + self.offset);
2718        if (amt != relocs_buffer.len) return error.InputOutput;
2719        const relocs = @as([*]align(1) const macho.relocation_info, @ptrCast(relocs_buffer.ptr))[0..sect.nreloc];
2720
2721        const code = try self.readSectionData(gpa, handle, n_sect);
2722        defer gpa.free(code);
2723
2724        try out.ensureTotalCapacityPrecise(gpa, relocs.len);
2725
2726        var i: usize = 0;
2727        while (i < relocs.len) : (i += 1) {
2728            const rel = relocs[i];
2729            const rel_type: macho.reloc_type_x86_64 = @enumFromInt(rel.r_type);
2730            const rel_offset = @as(u32, @intCast(rel.r_address));
2731
2732            var addend = switch (rel.r_length) {
2733                0 => code[rel_offset],
2734                1 => mem.readInt(i16, code[rel_offset..][0..2], .little),
2735                2 => mem.readInt(i32, code[rel_offset..][0..4], .little),
2736                3 => mem.readInt(i64, code[rel_offset..][0..8], .little),
2737            };
2738            addend += switch (@as(macho.reloc_type_x86_64, @enumFromInt(rel.r_type))) {
2739                .X86_64_RELOC_SIGNED_1 => 1,
2740                .X86_64_RELOC_SIGNED_2 => 2,
2741                .X86_64_RELOC_SIGNED_4 => 4,
2742                else => 0,
2743            };
2744            var is_extern = rel.r_extern == 1;
2745
2746            const target = if (!is_extern) blk: {
2747                const nsect = rel.r_symbolnum - 1;
2748                const taddr: i64 = if (rel.r_pcrel == 1)
2749                    @as(i64, @intCast(sect.addr)) + rel.r_address + addend + 4
2750                else
2751                    addend;
2752                const target = self.findAtomInSection(@intCast(taddr), @intCast(nsect)) orelse {
2753                    try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: bad relocation", .{
2754                        sect.segName(), sect.sectName(), rel.r_address,
2755                    });
2756                    return error.MalformedObject;
2757                };
2758                const target_atom = self.getAtom(target).?;
2759                addend = taddr - @as(i64, @intCast(target_atom.getInputAddress(macho_file)));
2760                const isec = target_atom.getInputSection(macho_file);
2761                if (isCstringLiteral(isec) or isFixedSizeLiteral(isec) or isPtrLiteral(isec)) {
2762                    is_extern = true;
2763                    break :blk target_atom.getExtra(macho_file).literal_symbol_index;
2764                }
2765                break :blk target;
2766            } else rel.r_symbolnum;
2767
2768            const has_subtractor = if (i > 0 and
2769                @as(macho.reloc_type_x86_64, @enumFromInt(relocs[i - 1].r_type)) == .X86_64_RELOC_SUBTRACTOR)
2770            blk: {
2771                if (rel_type != .X86_64_RELOC_UNSIGNED) {
2772                    try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: X86_64_RELOC_SUBTRACTOR followed by {s}", .{
2773                        sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type),
2774                    });
2775                    return error.MalformedObject;
2776                }
2777                break :blk true;
2778            } else false;
2779
2780            const @"type": Relocation.Type = validateRelocType(rel, rel_type, is_extern) catch |err| {
2781                switch (err) {
2782                    error.Pcrel => try macho_file.reportParseError2(
2783                        self.index,
2784                        "{s},{s}: 0x{x}: PC-relative {s} relocation",
2785                        .{ sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
2786                    ),
2787                    error.NonPcrel => try macho_file.reportParseError2(
2788                        self.index,
2789                        "{s},{s}: 0x{x}: non-PC-relative {s} relocation",
2790                        .{ sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
2791                    ),
2792                    error.InvalidLength => try macho_file.reportParseError2(
2793                        self.index,
2794                        "{s},{s}: 0x{x}: invalid length of {d} in {s} relocation",
2795                        .{ sect.segName(), sect.sectName(), rel_offset, @as(u8, 1) << rel.r_length, @tagName(rel_type) },
2796                    ),
2797                    error.NonExtern => try macho_file.reportParseError2(
2798                        self.index,
2799                        "{s},{s}: 0x{x}: non-extern target in {s} relocation",
2800                        .{ sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
2801                    ),
2802                }
2803                return error.MalformedObject;
2804            };
2805
2806            out.appendAssumeCapacity(.{
2807                .tag = if (is_extern) .@"extern" else .local,
2808                .offset = @as(u32, @intCast(rel.r_address)),
2809                .target = target,
2810                .addend = addend,
2811                .type = @"type",
2812                .meta = .{
2813                    .pcrel = rel.r_pcrel == 1,
2814                    .has_subtractor = has_subtractor,
2815                    .length = rel.r_length,
2816                    .symbolnum = rel.r_symbolnum,
2817                },
2818            });
2819        }
2820    }
2821
2822    fn validateRelocType(rel: macho.relocation_info, rel_type: macho.reloc_type_x86_64, is_extern: bool) !Relocation.Type {
2823        switch (rel_type) {
2824            .X86_64_RELOC_UNSIGNED => {
2825                if (rel.r_pcrel == 1) return error.Pcrel;
2826                if (rel.r_length != 2 and rel.r_length != 3) return error.InvalidLength;
2827                return .unsigned;
2828            },
2829
2830            .X86_64_RELOC_SUBTRACTOR => {
2831                if (rel.r_pcrel == 1) return error.Pcrel;
2832                return .subtractor;
2833            },
2834
2835            .X86_64_RELOC_BRANCH,
2836            .X86_64_RELOC_GOT_LOAD,
2837            .X86_64_RELOC_GOT,
2838            .X86_64_RELOC_TLV,
2839            => {
2840                if (rel.r_pcrel == 0) return error.NonPcrel;
2841                if (rel.r_length != 2) return error.InvalidLength;
2842                if (!is_extern) return error.NonExtern;
2843                return switch (rel_type) {
2844                    .X86_64_RELOC_BRANCH => .branch,
2845                    .X86_64_RELOC_GOT_LOAD => .got_load,
2846                    .X86_64_RELOC_GOT => .got,
2847                    .X86_64_RELOC_TLV => .tlv,
2848                    else => unreachable,
2849                };
2850            },
2851
2852            .X86_64_RELOC_SIGNED,
2853            .X86_64_RELOC_SIGNED_1,
2854            .X86_64_RELOC_SIGNED_2,
2855            .X86_64_RELOC_SIGNED_4,
2856            => {
2857                if (rel.r_pcrel == 0) return error.NonPcrel;
2858                if (rel.r_length != 2) return error.InvalidLength;
2859                return switch (rel_type) {
2860                    .X86_64_RELOC_SIGNED => .signed,
2861                    .X86_64_RELOC_SIGNED_1 => .signed1,
2862                    .X86_64_RELOC_SIGNED_2 => .signed2,
2863                    .X86_64_RELOC_SIGNED_4 => .signed4,
2864                    else => unreachable,
2865                };
2866            },
2867        }
2868    }
2869};
2870
2871const aarch64 = struct {
2872    fn parseRelocs(
2873        self: *Object,
2874        n_sect: u8,
2875        sect: macho.section_64,
2876        out: *std.ArrayList(Relocation),
2877        handle: File.Handle,
2878        macho_file: *MachO,
2879    ) !void {
2880        const gpa = macho_file.base.comp.gpa;
2881
2882        const relocs_buffer = try gpa.alloc(u8, sect.nreloc * @sizeOf(macho.relocation_info));
2883        defer gpa.free(relocs_buffer);
2884        const amt = try handle.preadAll(relocs_buffer, sect.reloff + self.offset);
2885        if (amt != relocs_buffer.len) return error.InputOutput;
2886        const relocs = @as([*]align(1) const macho.relocation_info, @ptrCast(relocs_buffer.ptr))[0..sect.nreloc];
2887
2888        const code = try self.readSectionData(gpa, handle, n_sect);
2889        defer gpa.free(code);
2890
2891        try out.ensureTotalCapacityPrecise(gpa, relocs.len);
2892
2893        var i: usize = 0;
2894        while (i < relocs.len) : (i += 1) {
2895            var rel = relocs[i];
2896            const rel_offset = @as(u32, @intCast(rel.r_address));
2897
2898            var addend: i64 = 0;
2899
2900            switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
2901                .ARM64_RELOC_ADDEND => {
2902                    addend = rel.r_symbolnum;
2903                    i += 1;
2904                    if (i >= relocs.len) {
2905                        try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: unterminated ARM64_RELOC_ADDEND", .{
2906                            sect.segName(), sect.sectName(), rel_offset,
2907                        });
2908                        return error.MalformedObject;
2909                    }
2910                    rel = relocs[i];
2911                    switch (@as(macho.reloc_type_arm64, @enumFromInt(rel.r_type))) {
2912                        .ARM64_RELOC_PAGE21, .ARM64_RELOC_PAGEOFF12 => {},
2913                        else => |x| {
2914                            try macho_file.reportParseError2(
2915                                self.index,
2916                                "{s},{s}: 0x{x}: ARM64_RELOC_ADDEND followed by {s}",
2917                                .{ sect.segName(), sect.sectName(), rel_offset, @tagName(x) },
2918                            );
2919                            return error.MalformedObject;
2920                        },
2921                    }
2922                },
2923                .ARM64_RELOC_UNSIGNED => {
2924                    addend = switch (rel.r_length) {
2925                        0 => code[rel_offset],
2926                        1 => mem.readInt(i16, code[rel_offset..][0..2], .little),
2927                        2 => mem.readInt(i32, code[rel_offset..][0..4], .little),
2928                        3 => mem.readInt(i64, code[rel_offset..][0..8], .little),
2929                    };
2930                },
2931                else => {},
2932            }
2933
2934            const rel_type: macho.reloc_type_arm64 = @enumFromInt(rel.r_type);
2935            var is_extern = rel.r_extern == 1;
2936
2937            const target = if (!is_extern) blk: {
2938                const nsect = rel.r_symbolnum - 1;
2939                const taddr: i64 = if (rel.r_pcrel == 1)
2940                    @as(i64, @intCast(sect.addr)) + rel.r_address + addend
2941                else
2942                    addend;
2943                const target = self.findAtomInSection(@intCast(taddr), @intCast(nsect)) orelse {
2944                    try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: bad relocation", .{
2945                        sect.segName(), sect.sectName(), rel.r_address,
2946                    });
2947                    return error.MalformedObject;
2948                };
2949                const target_atom = self.getAtom(target).?;
2950                addend = taddr - @as(i64, @intCast(target_atom.getInputAddress(macho_file)));
2951                const isec = target_atom.getInputSection(macho_file);
2952                if (isCstringLiteral(isec) or isFixedSizeLiteral(isec) or isPtrLiteral(isec)) {
2953                    is_extern = true;
2954                    break :blk target_atom.getExtra(macho_file).literal_symbol_index;
2955                }
2956                break :blk target;
2957            } else rel.r_symbolnum;
2958
2959            const has_subtractor = if (i > 0 and
2960                @as(macho.reloc_type_arm64, @enumFromInt(relocs[i - 1].r_type)) == .ARM64_RELOC_SUBTRACTOR)
2961            blk: {
2962                if (rel_type != .ARM64_RELOC_UNSIGNED) {
2963                    try macho_file.reportParseError2(self.index, "{s},{s}: 0x{x}: ARM64_RELOC_SUBTRACTOR followed by {s}", .{
2964                        sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type),
2965                    });
2966                    return error.MalformedObject;
2967                }
2968                break :blk true;
2969            } else false;
2970
2971            const @"type": Relocation.Type = validateRelocType(rel, rel_type, is_extern) catch |err| {
2972                switch (err) {
2973                    error.Pcrel => try macho_file.reportParseError2(
2974                        self.index,
2975                        "{s},{s}: 0x{x}: PC-relative {s} relocation",
2976                        .{ sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
2977                    ),
2978                    error.NonPcrel => try macho_file.reportParseError2(
2979                        self.index,
2980                        "{s},{s}: 0x{x}: non-PC-relative {s} relocation",
2981                        .{ sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
2982                    ),
2983                    error.InvalidLength => try macho_file.reportParseError2(
2984                        self.index,
2985                        "{s},{s}: 0x{x}: invalid length of {d} in {s} relocation",
2986                        .{ sect.segName(), sect.sectName(), rel_offset, @as(u8, 1) << rel.r_length, @tagName(rel_type) },
2987                    ),
2988                    error.NonExtern => try macho_file.reportParseError2(
2989                        self.index,
2990                        "{s},{s}: 0x{x}: non-extern target in {s} relocation",
2991                        .{ sect.segName(), sect.sectName(), rel_offset, @tagName(rel_type) },
2992                    ),
2993                }
2994                return error.MalformedObject;
2995            };
2996
2997            out.appendAssumeCapacity(.{
2998                .tag = if (is_extern) .@"extern" else .local,
2999                .offset = @as(u32, @intCast(rel.r_address)),
3000                .target = target,
3001                .addend = addend,
3002                .type = @"type",
3003                .meta = .{
3004                    .pcrel = rel.r_pcrel == 1,
3005                    .has_subtractor = has_subtractor,
3006                    .length = rel.r_length,
3007                    .symbolnum = rel.r_symbolnum,
3008                },
3009            });
3010        }
3011    }
3012
3013    fn validateRelocType(rel: macho.relocation_info, rel_type: macho.reloc_type_arm64, is_extern: bool) !Relocation.Type {
3014        switch (rel_type) {
3015            .ARM64_RELOC_UNSIGNED => {
3016                if (rel.r_pcrel == 1) return error.Pcrel;
3017                if (rel.r_length != 2 and rel.r_length != 3) return error.InvalidLength;
3018                return .unsigned;
3019            },
3020
3021            .ARM64_RELOC_SUBTRACTOR => {
3022                if (rel.r_pcrel == 1) return error.Pcrel;
3023                return .subtractor;
3024            },
3025
3026            .ARM64_RELOC_BRANCH26,
3027            .ARM64_RELOC_PAGE21,
3028            .ARM64_RELOC_GOT_LOAD_PAGE21,
3029            .ARM64_RELOC_TLVP_LOAD_PAGE21,
3030            .ARM64_RELOC_POINTER_TO_GOT,
3031            => {
3032                if (rel.r_pcrel == 0) return error.NonPcrel;
3033                if (rel.r_length != 2) return error.InvalidLength;
3034                if (!is_extern) return error.NonExtern;
3035                return switch (rel_type) {
3036                    .ARM64_RELOC_BRANCH26 => .branch,
3037                    .ARM64_RELOC_PAGE21 => .page,
3038                    .ARM64_RELOC_GOT_LOAD_PAGE21 => .got_load_page,
3039                    .ARM64_RELOC_TLVP_LOAD_PAGE21 => .tlvp_page,
3040                    .ARM64_RELOC_POINTER_TO_GOT => .got,
3041                    else => unreachable,
3042                };
3043            },
3044
3045            .ARM64_RELOC_PAGEOFF12,
3046            .ARM64_RELOC_GOT_LOAD_PAGEOFF12,
3047            .ARM64_RELOC_TLVP_LOAD_PAGEOFF12,
3048            => {
3049                if (rel.r_pcrel == 1) return error.Pcrel;
3050                if (rel.r_length != 2) return error.InvalidLength;
3051                if (!is_extern) return error.NonExtern;
3052                return switch (rel_type) {
3053                    .ARM64_RELOC_PAGEOFF12 => .pageoff,
3054                    .ARM64_RELOC_GOT_LOAD_PAGEOFF12 => .got_load_pageoff,
3055                    .ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => .tlvp_pageoff,
3056                    else => unreachable,
3057                };
3058            },
3059
3060            .ARM64_RELOC_ADDEND => unreachable, // We make it part of the addend field
3061        }
3062    }
3063};
3064
3065const std = @import("std");
3066const assert = std.debug.assert;
3067const log = std.log.scoped(.link);
3068const macho = std.macho;
3069const math = std.math;
3070const mem = std.mem;
3071const Allocator = std.mem.Allocator;
3072const Writer = std.Io.Writer;
3073
3074const eh_frame = @import("eh_frame.zig");
3075const trace = @import("../../tracy.zig").trace;
3076const Archive = @import("Archive.zig");
3077const Atom = @import("Atom.zig");
3078const Cie = eh_frame.Cie;
3079const Dwarf = @import("Dwarf.zig");
3080const Fde = eh_frame.Fde;
3081const File = @import("file.zig").File;
3082const LoadCommandIterator = macho.LoadCommandIterator;
3083const MachO = @import("../MachO.zig");
3084const Object = @This();
3085const Relocation = @import("Relocation.zig");
3086const Symbol = @import("Symbol.zig");
3087const UnwindInfo = @import("UnwindInfo.zig");