471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
pub fn prepare(
unwind: *Unwind,
gpa: Allocator,
addr_size_bytes: u8,
endian: Endian,
need_lookup: bool,
/// The `__eh_frame` section in Mach-O binaries deviates from the standard `.eh_frame` section
/// in one way which this function needs to be aware of.
is_macho: bool,
) !void {
if (unwind.cie_list.len > 0 and (!need_lookup or unwind.lookup != null)) return;
unwind.cie_list.clearRetainingCapacity();
if (is_macho) assert(unwind.lookup == null or unwind.lookup.? != .eh_frame_hdr);
const section = unwind.frame_section;
var r: Reader = .fixed(section.bytes);
var fde_list: std.ArrayList(SortedFdeEntry) = .empty;
defer fde_list.deinit(gpa);
const saw_terminator = while (r.seek < r.buffer.len) {
const entry_offset = r.seek;
switch (try EntryHeader.read(&r, entry_offset, section.id, endian)) {
.cie => |cie_info| {
// We will pre-populate a list of CIEs for efficiency: this avoids work re-parsing
// them every time we look up an FDE. It also lets us cache the result of evaluating
// the CIE's initial CFI instructions, which is useful because in the vast majority
// of cases those instructions will be needed to reach the PC we are unwinding to.
const bytes_len = cast(usize, cie_info.bytes_len) orelse return error.EndOfStream;
const idx = unwind.cie_list.len;
try unwind.cie_list.append(gpa, .{
.offset = entry_offset,
.cie = CommonInformationEntry.parse(cie_info.format, try r.take(bytes_len), section.id, addr_size_bytes) catch |err| switch (err) {
error.UnsupportedDwarfVersion,
error.UnsupportedAugmentation,
=> {
// These are recoverable by just skipping the CIE.
continue;
},
else => |e| return e,
},
});
errdefer _ = unwind.cie_list.pop().?;
try VirtualMachine.populateCieLastRow(gpa, &unwind.cie_list.items(.cie)[idx], addr_size_bytes, endian);
continue;
},
.fde => |fde_info| {
const bytes_len = cast(usize, fde_info.bytes_len) orelse return error.EndOfStream;
if (!need_lookup) {
try r.discardAll(bytes_len);
continue;
}
const fde_vaddr = section.vaddr + r.seek;
const fde_bytes = try r.take(bytes_len);
const cie = unwind.findCie(fde_info.cie_offset) orelse continue;
const fde: FrameDescriptionEntry = try .parse(fde_vaddr, fde_bytes, cie, endian);
try fde_list.append(gpa, .{
.pc_begin = fde.pc_begin,
.fde_offset = entry_offset,
});
},
.terminator => break true,
}
} else false;
const expect_terminator = switch (section.id) {
.eh_frame => !is_macho, // `.eh_frame` indicates the end of the CIE/FDE list with a sentinel entry, though macOS omits this
.debug_frame => false, // `.debug_frame` uses the section bounds and does not specify a sentinel entry
};
if (saw_terminator != expect_terminator) return bad();
if (need_lookup) {
std.mem.sortUnstable(SortedFdeEntry, fde_list.items, {}, struct {
fn lessThan(ctx: void, a: SortedFdeEntry, b: SortedFdeEntry) bool {
ctx;
return a.pc_begin < b.pc_begin;
}
}.lessThan);
// This temporary is necessary to avoid an RLS footgun where `lookup` ends up non-null `undefined` on OOM.
const final_fdes = try fde_list.toOwnedSlice(gpa);
unwind.lookup = .{ .sorted_fdes = final_fdes };
}
}
Builds the CIE list and FDE lookup table if they are not already built. It is required to call this function at least once before calling
lookupPcorgetFde. If onlygetFdeis needed, thenneed_lookupcan be set tofalseto make this function more efficient.