1/// List of all unwind records gathered from all objects and sorted
2/// by allocated relative function address within the section.
3records: std.ArrayList(Record.Ref) = .empty,
4
5/// List of all personalities referenced by either unwind info entries
6/// or __eh_frame entries.
7personalities: [max_personalities]MachO.Ref = undefined,
8personalities_count: u2 = 0,
9
10/// List of common encodings sorted in descending order with the most common first.
11common_encodings: [max_common_encodings]Encoding = undefined,
12common_encodings_count: u7 = 0,
13
14/// List of record indexes containing an LSDA pointer.
15lsdas: std.ArrayList(u32) = .empty,
16lsdas_lookup: std.ArrayList(u32) = .empty,
17
18/// List of second level pages.
19pages: std.ArrayList(Page) = .empty,
20
21pub fn deinit(info: *UnwindInfo, allocator: Allocator) void {
22 info.records.deinit(allocator);
23 info.pages.deinit(allocator);
24 info.lsdas.deinit(allocator);
25 info.lsdas_lookup.deinit(allocator);
26}
27
28fn canFold(macho_file: *MachO, lhs_ref: Record.Ref, rhs_ref: Record.Ref) bool {
29 const cpu_arch = macho_file.getTarget().cpu.arch;
30 const lhs = lhs_ref.getUnwindRecord(macho_file);
31 const rhs = rhs_ref.getUnwindRecord(macho_file);
32 if (cpu_arch == .x86_64) {
33 if (lhs.enc.getMode() == @backingInt(macho.UNWIND_X86_64_MODE.STACK_IND) or
34 rhs.enc.getMode() == @backingInt(macho.UNWIND_X86_64_MODE.STACK_IND)) return false;
35 }
36 const lhs_per = lhs.personality orelse 0;
37 const rhs_per = rhs.personality orelse 0;
38 return lhs.enc.eql(rhs.enc) and
39 lhs_per == rhs_per and
40 lhs.fde == rhs.fde and
41 lhs.getLsdaAtom(macho_file) == null and rhs.getLsdaAtom(macho_file) == null;
42}
43
44pub fn generate(info: *UnwindInfo, macho_file: *MachO) !void {
45 const tracy = trace(@src());
46 defer tracy.end();
47
48 const gpa = macho_file.base.comp.gpa;
49
50 log.debug("generating unwind info", .{});
51
52 // Collect all unwind records
53 for (macho_file.sections.items(.atoms)) |atoms| {
54 for (atoms.items) |ref| {
55 const atom = ref.getAtom(macho_file) orelse continue;
56 if (!atom.isAlive()) continue;
57 const recs = atom.getUnwindRecords(macho_file);
58 const file = atom.getFile(macho_file);
59 try info.records.ensureUnusedCapacity(gpa, recs.len);
60 for (recs) |rec| {
61 if (!file.object.getUnwindRecord(rec).alive) continue;
62 info.records.appendAssumeCapacity(.{ .record = rec, .file = file.getIndex() });
63 }
64 }
65 }
66
67 // Encode records
68 for (info.records.items) |ref| {
69 const rec = ref.getUnwindRecord(macho_file);
70 if (rec.getFde(macho_file)) |fde| {
71 // The unwinder will look for the DWARF entry starting at the hint,
72 // assuming the hint points to a valid CFI record start. If it
73 // fails to find the record, it proceeds in a linear search through
74 // the contiguous CFI records from the hint until the end of the
75 // section. Ideally, in the case where the offset is too large to
76 // be encoded, we would instead encode the largest possible offset
77 // to a valid CFI record, but since we don't keep track of that,
78 // just encode zero -- the start of the section is always the start
79 // of a CFI record.
80 const hint = std.math.cast(u24, fde.out_offset) orelse 0;
81 rec.enc.setDwarfSectionOffset(hint);
82
83 if (fde.getLsdaAtom(macho_file)) |lsda| {
84 rec.lsda = lsda.atom_index;
85 rec.lsda_offset = fde.lsda_offset;
86 rec.enc.setHasLsda(true);
87 }
88 const cie = fde.getCie(macho_file);
89 if (cie.getPersonality(macho_file)) |_| {
90 const object = cie.getObject(macho_file);
91 const sym_ref = object.getSymbolRef(cie.personality.?.index, macho_file);
92 const personality_index = try info.getOrPutPersonalityFunction(sym_ref); // TODO handle error
93 rec.enc.setPersonalityIndex(personality_index + 1);
94 }
95 } else if (rec.getPersonality(macho_file)) |_| {
96 const object = rec.getObject(macho_file);
97 const sym_ref = object.getSymbolRef(rec.personality.?, macho_file);
98 const personality_index = try info.getOrPutPersonalityFunction(sym_ref); // TODO handle error
99 rec.enc.setPersonalityIndex(personality_index + 1);
100 }
101 }
102
103 // Sort by assigned relative address within each output section
104 const sortFn = struct {
105 fn sortFn(ctx: *MachO, lhs_ref: Record.Ref, rhs_ref: Record.Ref) bool {
106 const lhs = lhs_ref.getUnwindRecord(ctx);
107 const rhs = rhs_ref.getUnwindRecord(ctx);
108 const lhsa = lhs.getAtom(ctx);
109 const rhsa = rhs.getAtom(ctx);
110 if (lhsa.out_n_sect == rhsa.out_n_sect) return lhs.getAtomAddress(ctx) < rhs.getAtomAddress(ctx);
111 return lhsa.out_n_sect < rhsa.out_n_sect;
112 }
113 }.sortFn;
114 mem.sort(Record.Ref, info.records.items, macho_file, sortFn);
115
116 // Fold the records
117 // Any adjacent two records that share encoding can be folded into one.
118 {
119 var i: usize = 0;
120 var j: usize = 1;
121 while (j < info.records.items.len) : (j += 1) {
122 if (canFold(macho_file, info.records.items[i], info.records.items[j])) {
123 const rec = info.records.items[i].getUnwindRecord(macho_file);
124 rec.length += info.records.items[j].getUnwindRecord(macho_file).length + 1;
125 } else {
126 i += 1;
127 info.records.items[i] = info.records.items[j];
128 }
129 }
130 info.records.shrinkAndFree(gpa, i + 1);
131 }
132
133 for (info.records.items) |ref| {
134 const rec = ref.getUnwindRecord(macho_file);
135 const atom = rec.getAtom(macho_file);
136 log.debug("@{x}-{x} : {s} : rec({d}) : object({d}) : {f}", .{
137 rec.getAtomAddress(macho_file),
138 rec.getAtomAddress(macho_file) + rec.length,
139 atom.getName(macho_file),
140 ref.record,
141 ref.file,
142 rec.enc,
143 });
144 }
145
146 // Calculate common encodings
147 {
148 const CommonEncWithCount = struct {
149 enc: Encoding,
150 count: u32,
151
152 fn greaterThan(ctx: void, lhs: @This(), rhs: @This()) bool {
153 _ = ctx;
154 return lhs.count > rhs.count;
155 }
156 };
157
158 const Context = struct {
159 pub fn hash(ctx: @This(), key: Encoding) u32 {
160 _ = ctx;
161 return key.enc;
162 }
163
164 pub fn eql(
165 ctx: @This(),
166 key1: Encoding,
167 key2: Encoding,
168 b_index: usize,
169 ) bool {
170 _ = ctx;
171 _ = b_index;
172 return key1.eql(key2);
173 }
174 };
175
176 var common_encodings_counts: std.array_hash_map.Custom(
177 Encoding,
178 CommonEncWithCount,
179 Context,
180 false,
181 ) = .empty;
182 defer common_encodings_counts.deinit(gpa);
183
184 for (info.records.items) |ref| {
185 const rec = ref.getUnwindRecord(macho_file);
186 if (rec.enc.isDwarf(macho_file)) continue;
187 const gop = try common_encodings_counts.getOrPut(gpa, rec.enc);
188 if (!gop.found_existing) {
189 gop.value_ptr.* = .{
190 .enc = rec.enc,
191 .count = 0,
192 };
193 }
194 gop.value_ptr.count += 1;
195 }
196
197 const slice = common_encodings_counts.values();
198 mem.sort(CommonEncWithCount, slice, {}, CommonEncWithCount.greaterThan);
199
200 var i: u7 = 0;
201 while (i < slice.len) : (i += 1) {
202 if (i >= max_common_encodings) break;
203 if (slice[i].count < 2) continue;
204 info.appendCommonEncoding(slice[i].enc);
205 log.debug("adding common encoding: {d} => {f}", .{ i, slice[i].enc });
206 }
207 }
208
209 // Compute page allocations
210 {
211 var i: u32 = 0;
212 while (i < info.records.items.len) {
213 const rec = info.records.items[i].getUnwindRecord(macho_file);
214 const range_start_max: u64 = rec.getAtomAddress(macho_file) + compressed_entry_func_offset_mask;
215 var encoding_count: u9 = info.common_encodings_count;
216 var space_left: u32 = second_level_page_words -
217 @sizeOf(macho.unwind_info_compressed_second_level_page_header) / @sizeOf(u32);
218 var page = Page{
219 .kind = undefined,
220 .start = i,
221 .count = 0,
222 };
223
224 while (space_left >= 1 and i < info.records.items.len) {
225 const next = info.records.items[i].getUnwindRecord(macho_file);
226 const is_dwarf = next.enc.isDwarf(macho_file);
227
228 if (next.getAtomAddress(macho_file) >= range_start_max) {
229 break;
230 } else if (info.getCommonEncoding(next.enc) != null or
231 page.getPageEncoding(next.enc) != null and !is_dwarf)
232 {
233 i += 1;
234 space_left -= 1;
235 } else if (space_left >= 2 and encoding_count < max_compact_encodings) {
236 page.appendPageEncoding(next.enc);
237 i += 1;
238 space_left -= 2;
239 encoding_count += 1;
240 } else {
241 break;
242 }
243 }
244
245 page.count = @as(u16, @intCast(i - page.start));
246
247 if (i < info.records.items.len and page.count < max_regular_second_level_entries) {
248 page.kind = .regular;
249 page.count = @as(u16, @intCast(@min(
250 max_regular_second_level_entries,
251 info.records.items.len - page.start,
252 )));
253 i = page.start + page.count;
254 } else {
255 page.kind = .compressed;
256 }
257
258 page.size = page.calcSize();
259
260 log.debug("{f}", .{page.fmt(info.*)});
261
262 try info.pages.append(gpa, page);
263 }
264 }
265
266 // Save records having an LSDA pointer
267 log.debug("LSDA pointers:", .{});
268 try info.lsdas_lookup.ensureTotalCapacityPrecise(gpa, info.records.items.len);
269 for (info.records.items, 0..) |ref, i| {
270 const rec = ref.getUnwindRecord(macho_file);
271 info.lsdas_lookup.appendAssumeCapacity(@intCast(info.lsdas.items.len));
272 if (rec.getLsdaAtom(macho_file)) |lsda| {
273 log.debug(" @{x} => lsda({d})", .{ rec.getAtomAddress(macho_file), lsda.atom_index });
274 try info.lsdas.append(gpa, @intCast(i));
275 }
276 }
277}
278
279pub fn calcSize(info: UnwindInfo) usize {
280 const tracy = trace(@src());
281 defer tracy.end();
282
283 var total_size: usize = 0;
284 total_size += @sizeOf(macho.unwind_info_section_header);
285 total_size +=
286 @as(usize, @intCast(info.common_encodings_count)) * @sizeOf(macho.compact_unwind_encoding_t);
287 total_size += @as(usize, @intCast(info.personalities_count)) * @sizeOf(u32);
288 total_size += (info.pages.items.len + 1) * @sizeOf(macho.unwind_info_section_header_index_entry);
289 total_size += info.lsdas.items.len * @sizeOf(macho.unwind_info_section_header_lsda_index_entry);
290 for (info.pages.items) |page| {
291 total_size += page.size;
292 }
293 return total_size;
294}
295
296pub fn write(info: UnwindInfo, macho_file: *MachO, buffer: []u8) !void {
297 const seg = macho_file.getTextSegment();
298 const header = macho_file.sections.items(.header)[macho_file.unwind_info_sect_index.?];
299
300 var writer: Writer = .fixed(buffer);
301
302 const common_encodings_offset: u32 = @sizeOf(macho.unwind_info_section_header);
303 const common_encodings_count: u32 = info.common_encodings_count;
304 const personalities_offset: u32 = common_encodings_offset + common_encodings_count * @sizeOf(u32);
305 const personalities_count: u32 = info.personalities_count;
306 const indexes_offset: u32 = personalities_offset + personalities_count * @sizeOf(u32);
307 const indexes_count: u32 = @as(u32, @intCast(info.pages.items.len + 1));
308
309 try writer.writeStruct(@as(macho.unwind_info_section_header, .{
310 .commonEncodingsArraySectionOffset = common_encodings_offset,
311 .commonEncodingsArrayCount = common_encodings_count,
312 .personalityArraySectionOffset = personalities_offset,
313 .personalityArrayCount = personalities_count,
314 .indexSectionOffset = indexes_offset,
315 .indexCount = indexes_count,
316 }), .little);
317
318 try writer.writeSliceEndian(Encoding, info.common_encodings[0..info.common_encodings_count], .little);
319
320 for (info.personalities[0..info.personalities_count]) |ref| {
321 const sym = ref.getSymbol(macho_file).?;
322 try writer.writeInt(u32, @intCast(sym.getGotAddress(macho_file) - seg.vmaddr), .little);
323 }
324
325 // Calculate total size of all pages
326 var total_pages_size: u32 = 0;
327 for (info.pages.items) |page| {
328 total_pages_size += page.size;
329 }
330
331 const pages_base_offset = @as(u32, @intCast(header.size - total_pages_size));
332 const lsda_base_offset = @as(u32, @intCast(pages_base_offset -
333 (info.lsdas.items.len * @sizeOf(macho.unwind_info_section_header_lsda_index_entry))));
334
335 var page_offset: u32 = pages_base_offset;
336 for (info.pages.items) |page| {
337 assert(page.count > 0);
338 const rec = info.records.items[page.start].getUnwindRecord(macho_file);
339 try writer.writeStruct(@as(macho.unwind_info_section_header_index_entry, .{
340 .functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)),
341 .secondLevelPagesSectionOffset = page_offset,
342 .lsdaIndexArraySectionOffset = lsda_base_offset +
343 info.lsdas_lookup.items[page.start] * @sizeOf(macho.unwind_info_section_header_lsda_index_entry),
344 }), .little);
345 page_offset += page.size;
346 }
347
348 const last_rec = info.records.items[info.records.items.len - 1].getUnwindRecord(macho_file);
349 const sentinel_address = @as(u32, @intCast(last_rec.getAtomAddress(macho_file) + last_rec.length - seg.vmaddr));
350 try writer.writeStruct(@as(macho.unwind_info_section_header_index_entry, .{
351 .functionOffset = sentinel_address,
352 .secondLevelPagesSectionOffset = 0,
353 .lsdaIndexArraySectionOffset = lsda_base_offset +
354 @as(u32, @intCast(info.lsdas.items.len)) * @sizeOf(macho.unwind_info_section_header_lsda_index_entry),
355 }), .little);
356
357 for (info.lsdas.items) |index| {
358 const rec = info.records.items[index].getUnwindRecord(macho_file);
359 try writer.writeStruct(@as(macho.unwind_info_section_header_lsda_index_entry, .{
360 .functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)),
361 .lsdaOffset = @as(u32, @intCast(rec.getLsdaAddress(macho_file) - seg.vmaddr)),
362 }), .little);
363 }
364
365 for (info.pages.items) |page| {
366 try page.write(info, macho_file, &writer);
367 }
368
369 assert(writer.end == header.size);
370}
371
372fn getOrPutPersonalityFunction(info: *UnwindInfo, ref: MachO.Ref) error{TooManyPersonalities}!u2 {
373 comptime var index: u2 = 0;
374 inline while (index < max_personalities) : (index += 1) {
375 if (info.personalities[index].eql(ref)) {
376 return index;
377 } else if (index == info.personalities_count) {
378 info.personalities[index] = ref;
379 info.personalities_count += 1;
380 return index;
381 }
382 }
383 return error.TooManyPersonalities;
384}
385
386fn appendCommonEncoding(info: *UnwindInfo, enc: Encoding) void {
387 assert(info.common_encodings_count <= max_common_encodings);
388 info.common_encodings[info.common_encodings_count] = enc;
389 info.common_encodings_count += 1;
390}
391
392fn getCommonEncoding(info: UnwindInfo, enc: Encoding) ?u7 {
393 comptime var index: u7 = 0;
394 inline while (index < max_common_encodings) : (index += 1) {
395 if (index >= info.common_encodings_count) return null;
396 if (info.common_encodings[index].eql(enc)) {
397 return index;
398 }
399 }
400 return null;
401}
402
403pub const Encoding = extern struct {
404 enc: macho.compact_unwind_encoding_t,
405
406 pub fn getMode(enc: Encoding) u4 {
407 comptime assert(macho.UNWIND_ARM64_MODE_MASK == macho.UNWIND_X86_64_MODE_MASK);
408 const shift = comptime @ctz(macho.UNWIND_ARM64_MODE_MASK);
409 return @as(u4, @truncate((enc.enc & macho.UNWIND_ARM64_MODE_MASK) >> shift));
410 }
411
412 pub fn isDwarf(enc: Encoding, macho_file: *MachO) bool {
413 const mode = enc.getMode();
414 return switch (macho_file.getTarget().cpu.arch) {
415 .aarch64 => @as(macho.UNWIND_ARM64_MODE, @fromBackingInt(@intCast(mode))) == .DWARF,
416 .x86_64 => @as(macho.UNWIND_X86_64_MODE, @fromBackingInt(@intCast(mode))) == .DWARF,
417 else => unreachable,
418 };
419 }
420
421 pub fn setMode(enc: *Encoding, mode: anytype) void {
422 comptime assert(macho.UNWIND_ARM64_MODE_MASK == macho.UNWIND_X86_64_MODE_MASK);
423 const shift = comptime @ctz(macho.UNWIND_ARM64_MODE_MASK);
424 enc.enc |= @as(u32, @intCast(@backingInt(mode))) << shift;
425 }
426
427 pub fn hasLsda(enc: Encoding) bool {
428 const shift = comptime @ctz(macho.UNWIND_HAS_LSDA);
429 const has_lsda = @as(u1, @truncate((enc.enc & macho.UNWIND_HAS_LSDA) >> shift));
430 return has_lsda == 1;
431 }
432
433 pub fn setHasLsda(enc: *Encoding, has_lsda: bool) void {
434 const shift = comptime @ctz(macho.UNWIND_HAS_LSDA);
435 const mask = @as(u32, @intCast(@intFromBool(has_lsda))) << shift;
436 enc.enc |= mask;
437 }
438
439 pub fn getPersonalityIndex(enc: Encoding) u2 {
440 const shift = comptime @ctz(macho.UNWIND_PERSONALITY_MASK);
441 const index = @as(u2, @truncate((enc.enc & macho.UNWIND_PERSONALITY_MASK) >> shift));
442 return index;
443 }
444
445 pub fn setPersonalityIndex(enc: *Encoding, index: u2) void {
446 const shift = comptime @ctz(macho.UNWIND_PERSONALITY_MASK);
447 const mask = @as(u32, @intCast(index)) << shift;
448 enc.enc |= mask;
449 }
450
451 pub fn getDwarfSectionOffset(enc: Encoding) u24 {
452 const offset = @as(u24, @truncate(enc.enc));
453 return offset;
454 }
455
456 pub fn setDwarfSectionOffset(enc: *Encoding, offset: u24) void {
457 enc.enc |= offset;
458 }
459
460 pub fn eql(enc: Encoding, other: Encoding) bool {
461 return enc.enc == other.enc;
462 }
463
464 pub fn format(enc: Encoding, w: *Writer) Writer.Error!void {
465 try w.print("0x{x:0>8}", .{enc.enc});
466 }
467};
468
469pub const Record = struct {
470 length: u32 = 0,
471 enc: Encoding = .{ .enc = 0 },
472 atom: Atom.Index = 0,
473 atom_offset: u32 = 0,
474 lsda: Atom.Index = 0,
475 lsda_offset: u32 = 0,
476 personality: ?Symbol.Index = null, // TODO make this zero-is-null
477 fde: Fde.Index = 0, // TODO actually make FDE at 0 an invalid FDE
478 file: File.Index = 0,
479 alive: bool = true,
480
481 pub fn getObject(rec: Record, macho_file: *MachO) *Object {
482 return macho_file.getFile(rec.file).?.object;
483 }
484
485 pub fn getAtom(rec: Record, macho_file: *MachO) *Atom {
486 return rec.getObject(macho_file).getAtom(rec.atom).?;
487 }
488
489 pub fn getLsdaAtom(rec: Record, macho_file: *MachO) ?*Atom {
490 return rec.getObject(macho_file).getAtom(rec.lsda);
491 }
492
493 pub fn getPersonality(rec: Record, macho_file: *MachO) ?*Symbol {
494 const personality = rec.personality orelse return null;
495 const object = rec.getObject(macho_file);
496 return object.getSymbolRef(personality, macho_file).getSymbol(macho_file);
497 }
498
499 pub fn getFde(rec: Record, macho_file: *MachO) ?Fde {
500 if (!rec.enc.isDwarf(macho_file)) return null;
501 return rec.getObject(macho_file).fdes.items[rec.fde];
502 }
503
504 pub fn getFdePtr(rec: Record, macho_file: *MachO) ?*Fde {
505 if (!rec.enc.isDwarf(macho_file)) return null;
506 return &rec.getObject(macho_file).fdes.items[rec.fde];
507 }
508
509 pub fn getAtomAddress(rec: Record, macho_file: *MachO) u64 {
510 const atom = rec.getAtom(macho_file);
511 return atom.getAddress(macho_file) + rec.atom_offset;
512 }
513
514 pub fn getLsdaAddress(rec: Record, macho_file: *MachO) u64 {
515 const lsda = rec.getLsdaAtom(macho_file) orelse return 0;
516 return lsda.getAddress(macho_file) + rec.lsda_offset;
517 }
518
519 pub fn fmt(rec: Record, macho_file: *MachO) std.fmt.Alt(Format, Format.default) {
520 return .{ .data = .{
521 .rec = rec,
522 .macho_file = macho_file,
523 } };
524 }
525
526 const Format = struct {
527 rec: Record,
528 macho_file: *MachO,
529
530 fn default(f: Format, w: *Writer) Writer.Error!void {
531 const rec = f.rec;
532 const macho_file = f.macho_file;
533 try w.print("{x} : len({x})", .{
534 rec.enc.enc, rec.length,
535 });
536 if (rec.enc.isDwarf(macho_file)) try w.print(" : fde({d})", .{rec.fde});
537 try w.print(" : {s}", .{rec.getAtom(macho_file).getName(macho_file)});
538 if (!rec.alive) try w.writeAll(" : [*]");
539 }
540 };
541
542 pub const Index = u32;
543
544 const Ref = struct {
545 record: Index,
546 file: File.Index,
547
548 pub fn getUnwindRecord(ref: Ref, macho_file: *MachO) *Record {
549 return macho_file.getFile(ref.file).?.object.getUnwindRecord(ref.record);
550 }
551 };
552};
553
554const max_personalities = 3;
555const max_common_encodings = 127;
556const max_compact_encodings = 256;
557
558const second_level_page_bytes = 0x1000;
559const second_level_page_words = second_level_page_bytes / @sizeOf(u32);
560
561const max_regular_second_level_entries =
562 (second_level_page_bytes - @sizeOf(macho.unwind_info_regular_second_level_page_header)) /
563 @sizeOf(macho.unwind_info_regular_second_level_entry);
564
565const max_compressed_second_level_entries =
566 (second_level_page_bytes - @sizeOf(macho.unwind_info_compressed_second_level_page_header)) /
567 @sizeOf(u32);
568
569const compressed_entry_func_offset_mask = ~@as(u24, 0);
570
571const Page = struct {
572 kind: enum { regular, compressed },
573 start: u32,
574 count: u16,
575 page_encodings: [max_compact_encodings]Encoding = undefined,
576 page_encodings_count: u9 = 0,
577 size: u32 = 0,
578
579 fn appendPageEncoding(page: *Page, enc: Encoding) void {
580 assert(page.page_encodings_count <= max_compact_encodings);
581 page.page_encodings[page.page_encodings_count] = enc;
582 page.page_encodings_count += 1;
583 }
584
585 fn getPageEncoding(page: Page, enc: Encoding) ?u8 {
586 comptime var index: u9 = 0;
587 inline while (index < max_compact_encodings) : (index += 1) {
588 if (index >= page.page_encodings_count) return null;
589 if (page.page_encodings[index].eql(enc)) {
590 return @as(u8, @intCast(index));
591 }
592 }
593 return null;
594 }
595
596 fn calcSize(page: Page) u32 {
597 return switch (page.kind) {
598 .regular => @sizeOf(macho.unwind_info_regular_second_level_page_header) +
599 @as(u32, page.count) * @sizeOf(macho.unwind_info_regular_second_level_entry),
600 .compressed => @sizeOf(macho.unwind_info_compressed_second_level_page_header) +
601 @as(u32, page.page_encodings_count) * @sizeOf(u32) +
602 @as(u32, page.count) * @sizeOf(u32),
603 };
604 }
605
606 const Format = struct {
607 page: Page,
608 info: UnwindInfo,
609
610 fn default(f: Format, w: *Writer) Writer.Error!void {
611 try w.writeAll("Page:\n");
612 try w.print(" kind: {s}\n", .{@tagName(f.page.kind)});
613 try w.print(" entries: {d} - {d}\n", .{
614 f.page.start,
615 f.page.start + f.page.count,
616 });
617 try w.print(" encodings (count = {d})\n", .{f.page.page_encodings_count});
618 for (f.page.page_encodings[0..f.page.page_encodings_count], 0..) |enc, i| {
619 try w.print(" {d}: {f}\n", .{ f.info.common_encodings_count + i, enc });
620 }
621 }
622 };
623
624 fn fmt(page: Page, info: UnwindInfo) std.fmt.Alt(Format, Format.default) {
625 return .{ .data = .{
626 .page = page,
627 .info = info,
628 } };
629 }
630
631 fn write(page: Page, info: UnwindInfo, macho_file: *MachO, writer: *Writer) !void {
632 const seg = macho_file.getTextSegment();
633
634 switch (page.kind) {
635 .regular => {
636 try writer.writeStruct(@as(macho.unwind_info_regular_second_level_page_header, .{
637 .entryPageOffset = @sizeOf(macho.unwind_info_regular_second_level_page_header),
638 .entryCount = page.count,
639 }), .little);
640
641 for (info.records.items[page.start..][0..page.count]) |ref| {
642 const rec = ref.getUnwindRecord(macho_file);
643 try writer.writeStruct(@as(macho.unwind_info_regular_second_level_entry, .{
644 .functionOffset = @as(u32, @intCast(rec.getAtomAddress(macho_file) - seg.vmaddr)),
645 .encoding = rec.enc.enc,
646 }), .little);
647 }
648 },
649 .compressed => {
650 const entry_offset = @sizeOf(macho.unwind_info_compressed_second_level_page_header) +
651 @as(u16, @intCast(page.page_encodings_count)) * @sizeOf(u32);
652 try writer.writeStruct(@as(macho.unwind_info_compressed_second_level_page_header, .{
653 .entryPageOffset = entry_offset,
654 .entryCount = page.count,
655 .encodingsPageOffset = @sizeOf(macho.unwind_info_compressed_second_level_page_header),
656 .encodingsCount = page.page_encodings_count,
657 }), .little);
658
659 for (page.page_encodings[0..page.page_encodings_count]) |enc| {
660 try writer.writeInt(u32, enc.enc, .little);
661 }
662
663 assert(page.count > 0);
664 const first_rec = info.records.items[page.start].getUnwindRecord(macho_file);
665 for (info.records.items[page.start..][0..page.count]) |ref| {
666 const rec = ref.getUnwindRecord(macho_file);
667 const enc_index = blk: {
668 if (info.getCommonEncoding(rec.enc)) |id| break :blk id;
669 const ncommon = info.common_encodings_count;
670 break :blk ncommon + page.getPageEncoding(rec.enc).?;
671 };
672 const compressed = macho.UnwindInfoCompressedEntry{
673 .funcOffset = @as(u24, @intCast(rec.getAtomAddress(macho_file) - first_rec.getAtomAddress(macho_file))),
674 .encodingIndex = @as(u8, @intCast(enc_index)),
675 };
676 try writer.writeStruct(compressed, .little);
677 }
678 },
679 }
680 }
681};
682
683const std = @import("std");
684const assert = std.debug.assert;
685const eh_frame = @import("eh_frame.zig");
686const fs = std.fs;
687const leb = std.leb;
688const log = std.log.scoped(.link);
689const macho = std.macho;
690const math = std.math;
691const mem = std.mem;
692const trace = @import("../../tracy.zig").trace;
693const Writer = std.Io.Writer;
694
695const Allocator = mem.Allocator;
696const Atom = @import("Atom.zig");
697const Fde = eh_frame.Fde;
698const File = @import("file.zig").File;
699const MachO = @import("../MachO.zig");
700const Object = @import("Object.zig");
701const Symbol = @import("Symbol.zig");
702const UnwindInfo = @This();