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