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 lookupPc or getFde. If only getFde is needed,
then need_lookup can be set to false to make this function more efficient.
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
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 = try .parse(cie_info.format, try r.take(bytes_len), section.id, addr_size_bytes),
});
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 cie = unwind.findCie(fde_info.cie_offset) orelse return error.InvalidDebugInfo;
const fde: FrameDescriptionEntry = try .parse(section.vaddr + r.seek, try r.take(bytes_len), 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 };
}
}