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