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