master
  1//! Contains state relevant to stack unwinding through the DWARF `.debug_frame` section, or the
  2//! `.eh_frame` section which is an extension of the former specified by Linux Standard Base Core.
  3//! Like `Dwarf`, no assumptions are made about the host's relationship to the target of the unwind
  4//! information -- unwind data for any target can be read by any host.
  5//!
  6//! `Unwind` specifically deals with loading the data from CIEs and FDEs in the section, and with
  7//! performing fast lookups of a program counter's corresponding FDE. The CFI instructions in the
  8//! CIEs and FDEs can be interpreted by `VirtualMachine`.
  9//!
 10//! The typical usage of `Unwind` is as follows:
 11//!
 12//! * Initialize with `initEhFrameHdr` or `initSection`, depending on the available data
 13//! * Call `prepare` to scan CIEs and, if necessary, construct a search table
 14//! * Call `lookupPc` to find the section offset of the FDE corresponding to a PC
 15//! * Call `getFde` to load the corresponding FDE and CIE
 16//! * Check that the PC does indeed fall in that range (`lookupPc` may return a false positive)
 17//! * Interpret the embedded CFI instructions using `VirtualMachine`
 18//!
 19//! In some cases, such as when using the "compact unwind" data in Mach-O binaries, the FDE offsets
 20//! may already be known. In that case, no call to `lookupPc` is necessary, which means the call to
 21//! `prepare` can be optimized to only scan CIEs.
 22
 23pub const VirtualMachine = @import("Unwind/VirtualMachine.zig");
 24
 25frame_section: struct {
 26    id: Section,
 27    /// The virtual address of the start of the section. "Virtual address" refers to the address in
 28    /// the binary (e.g. `sh_addr` in an ELF file); the equivalent runtime address may be relocated
 29    /// in position-independent binaries.
 30    vaddr: u64,
 31    /// The full contents of the section. May have imprecise bounds depending on `section`. This
 32    /// memory is externally managed.
 33    ///
 34    /// For `.debug_frame`, the slice length is exactly equal to the section length. This is needed
 35    /// to know the number of CIEs and FDEs.
 36    ///
 37    /// For `.eh_frame`, the slice length may exceed the section length, i.e. the slice may refer to
 38    /// more bytes than are in the second. This restriction exists because `.eh_frame_hdr` only
 39    /// includes the address of the loaded `.eh_frame` data, not its length. It is not a problem
 40    /// because unlike `.debug_frame`, the end of the CIE/FDE list is signaled through a sentinel
 41    /// value. If this slice does have bounds, they will still be checked, preventing crashes when
 42    /// reading potentially-invalid `.eh_frame` data from files.
 43    bytes: []const u8,
 44},
 45
 46/// A structure allowing fast lookups of the FDE corresponding to a particular PC. We use a binary
 47/// search table for the lookup; essentially, a list of all FDEs ordered by PC range. `null` means
 48/// the lookup data is not yet populated, so `prepare` must be called before `lookupPc`.
 49lookup: ?union(enum) {
 50    /// The `.eh_frame_hdr` section contains a pre-computed search table which we can use.
 51    eh_frame_hdr: struct {
 52        /// Virtual address of the `.eh_frame_hdr` section.
 53        vaddr: u64,
 54        table: EhFrameHeader.SearchTable,
 55    },
 56    /// There is no pre-computed search table, so we have built one ourselves.
 57    /// Allocated into `gpa` and freed by `deinit`.
 58    sorted_fdes: []SortedFdeEntry,
 59},
 60
 61/// Initially empty; populated by `prepare`.
 62cie_list: std.MultiArrayList(struct {
 63    offset: u64,
 64    cie: CommonInformationEntry,
 65}),
 66
 67const SortedFdeEntry = struct {
 68    /// This FDE's value of `pc_begin`.
 69    pc_begin: u64,
 70    /// Offset into the section of the corresponding FDE, including the entry header.
 71    fde_offset: u64,
 72};
 73
 74pub const Section = enum { debug_frame, eh_frame };
 75
 76/// Initialize with unwind information from a header loaded from an `.eh_frame_hdr` section, and a
 77/// pointer to the contents of the `.eh_frame` section.
 78///
 79/// `.eh_frame_hdr` may embed a binary search table of FDEs. If it does, we will use that table for
 80/// PC lookups rather than spending time constructing our own search table.
 81pub fn initEhFrameHdr(header: EhFrameHeader, section_vaddr: u64, section_bytes_ptr: [*]const u8) Unwind {
 82    return .{
 83        .frame_section = .{
 84            .id = .eh_frame,
 85            .bytes = maxSlice(section_bytes_ptr),
 86            .vaddr = header.eh_frame_vaddr,
 87        },
 88        .lookup = if (header.search_table) |table| .{ .eh_frame_hdr = .{
 89            .vaddr = section_vaddr,
 90            .table = table,
 91        } } else null,
 92        .cie_list = .empty,
 93    };
 94}
 95
 96/// Initialize with unwind information from the contents of a `.debug_frame` or `.eh_frame` section.
 97///
 98/// If the `.eh_frame_hdr` section is available, consider instead using `initEhFrameHdr`, which
 99/// allows the implementation to use a search table embedded in that section if it is available.
100pub fn initSection(section: Section, section_vaddr: u64, section_bytes: []const u8) Unwind {
101    return .{
102        .frame_section = .{
103            .id = section,
104            .bytes = section_bytes,
105            .vaddr = section_vaddr,
106        },
107        .lookup = null,
108        .cie_list = .empty,
109    };
110}
111
112pub fn deinit(unwind: *Unwind, gpa: Allocator) void {
113    if (unwind.lookup) |lookup| switch (lookup) {
114        .eh_frame_hdr => {},
115        .sorted_fdes => |fdes| gpa.free(fdes),
116    };
117    for (unwind.cie_list.items(.cie)) |*cie| {
118        if (cie.last_row) |*lr| {
119            gpa.free(lr.cols);
120        }
121    }
122    unwind.cie_list.deinit(gpa);
123}
124
125/// Decoded version of the `.eh_frame_hdr` section.
126pub const EhFrameHeader = struct {
127    /// The virtual address (i.e. as given in the binary, before relocations) of the `.eh_frame`
128    /// section. This value is important when using `.eh_frame_hdr` to find debug information for
129    /// the current binary, because it allows locating where the `.eh_frame` section is loaded in
130    /// memory (by adding it to the ELF module's base address).
131    eh_frame_vaddr: u64,
132    search_table: ?SearchTable,
133
134    pub const SearchTable = struct {
135        /// The byte offset of the search table into the `.eh_frame_hdr` section.
136        offset: u8,
137        encoding: EH.PE,
138        fde_count: usize,
139        /// The actual table entries are viewed as a plain byte slice because `encoding` causes the
140        /// size of entries in the table to vary.
141        entries: []const u8,
142
143        /// Returns the vaddr of the FDE for `pc`, or `null` if no matching FDE was found.
144        fn findEntry(
145            table: *const SearchTable,
146            eh_frame_hdr_vaddr: u64,
147            pc: u64,
148            addr_size_bytes: u8,
149            endian: Endian,
150        ) !?u64 {
151            const table_vaddr = eh_frame_hdr_vaddr + table.offset;
152            const entry_size = try entrySize(table.encoding, addr_size_bytes);
153            var left: usize = 0;
154            var len: usize = table.fde_count;
155            while (len > 1) {
156                const mid = left + len / 2;
157                var entry_reader: Reader = .fixed(table.entries[mid * entry_size ..][0..entry_size]);
158                const pc_begin = try readEhPointer(&entry_reader, table.encoding, addr_size_bytes, .{
159                    .pc_rel_base = table_vaddr + left * entry_size,
160                    .data_rel_base = eh_frame_hdr_vaddr,
161                }, endian);
162                if (pc < pc_begin) {
163                    len /= 2;
164                } else {
165                    left = mid;
166                    len -= len / 2;
167                }
168            }
169            if (len == 0) return null;
170            var entry_reader: Reader = .fixed(table.entries[left * entry_size ..][0..entry_size]);
171            // Skip past `pc_begin`; we're now interested in the fde offset
172            _ = try readEhPointerAbs(&entry_reader, table.encoding.type, addr_size_bytes, endian);
173            const fde_ptr = try readEhPointer(&entry_reader, table.encoding, addr_size_bytes, .{
174                .pc_rel_base = table_vaddr + left * entry_size,
175                .data_rel_base = eh_frame_hdr_vaddr,
176            }, endian);
177            return fde_ptr;
178        }
179
180        fn entrySize(table_enc: EH.PE, addr_size_bytes: u8) !u8 {
181            return switch (table_enc.type) {
182                .absptr => 2 * addr_size_bytes,
183                .udata2, .sdata2 => 4,
184                .udata4, .sdata4 => 8,
185                .udata8, .sdata8 => 16,
186                .uleb128, .sleb128 => return bad(), // this is a binary search table; all entries must be the same size
187                _ => return bad(),
188            };
189        }
190    };
191
192    pub fn parse(
193        eh_frame_hdr_vaddr: u64,
194        eh_frame_hdr_bytes: []const u8,
195        addr_size_bytes: u8,
196        endian: Endian,
197    ) !EhFrameHeader {
198        var r: Reader = .fixed(eh_frame_hdr_bytes);
199
200        const version = try r.takeByte();
201        if (version != 1) return bad();
202
203        const eh_frame_ptr_enc: EH.PE = @bitCast(try r.takeByte());
204        const fde_count_enc: EH.PE = @bitCast(try r.takeByte());
205        const table_enc: EH.PE = @bitCast(try r.takeByte());
206
207        const eh_frame_ptr = try readEhPointer(&r, eh_frame_ptr_enc, addr_size_bytes, .{
208            .pc_rel_base = eh_frame_hdr_vaddr + r.seek,
209        }, endian);
210
211        const table: ?SearchTable = table: {
212            if (fde_count_enc == EH.PE.omit) break :table null;
213            if (table_enc == EH.PE.omit) break :table null;
214            const fde_count = try readEhPointer(&r, fde_count_enc, addr_size_bytes, .{
215                .pc_rel_base = eh_frame_hdr_vaddr + r.seek,
216            }, endian);
217            const entry_size = try SearchTable.entrySize(table_enc, addr_size_bytes);
218            const bytes_offset = r.seek;
219            const bytes_len = cast(usize, fde_count * entry_size) orelse return error.EndOfStream;
220            const bytes = try r.take(bytes_len);
221            break :table .{
222                .encoding = table_enc,
223                .fde_count = @intCast(fde_count),
224                .entries = bytes,
225                .offset = @intCast(bytes_offset),
226            };
227        };
228
229        return .{
230            .eh_frame_vaddr = eh_frame_ptr,
231            .search_table = table,
232        };
233    }
234};
235
236/// The shared header of an FDE/CIE, containing a length in bytes (DWARF's "initial length field")
237/// and a value which differentiates CIEs from FDEs and maps FDEs to their corresponding CIEs. The
238/// `.eh_frame` format also includes a third variation, here called `.terminator`, which acts as a
239/// sentinel for the whole section.
240///
241/// `CommonInformationEntry.parse` and `FrameDescriptionEntry.parse` expect the `EntryHeader` to
242/// have been parsed first: they accept data stored in the `EntryHeader`, and only read the bytes
243/// following this header.
244const EntryHeader = union(enum) {
245    cie: struct {
246        format: Format,
247        /// Remaining bytes in the CIE. These are parseable by `CommonInformationEntry.parse`.
248        bytes_len: u64,
249    },
250    fde: struct {
251        /// Offset into the section of the corresponding CIE, *including* its entry header.
252        cie_offset: u64,
253        /// Remaining bytes in the FDE. These are parseable by `FrameDescriptionEntry.parse`.
254        bytes_len: u64,
255    },
256    /// The `.eh_frame` format includes terminators which indicate that the last CIE/FDE has been
257    /// reached. However, `.debug_frame` does not include such a terminator, so the caller must
258    /// keep track of how many section bytes remain when parsing all entries in `.debug_frame`.
259    terminator,
260
261    fn read(r: *Reader, header_section_offset: u64, section: Section, endian: Endian) !EntryHeader {
262        const unit_header = try Dwarf.readUnitHeader(r, endian);
263        if (unit_header.unit_length == 0) return .terminator;
264
265        // Next is a value which will disambiguate CIEs and FDEs. Annoyingly, LSB Core makes this
266        // value always 4-byte, whereas DWARF makes it depend on the `dwarf.Format`.
267        const cie_ptr_or_id_size: u8 = switch (section) {
268            .eh_frame => 4,
269            .debug_frame => switch (unit_header.format) {
270                .@"32" => 4,
271                .@"64" => 8,
272            },
273        };
274        const cie_ptr_or_id = switch (cie_ptr_or_id_size) {
275            4 => try r.takeInt(u32, endian),
276            8 => try r.takeInt(u64, endian),
277            else => unreachable,
278        };
279        const remaining_bytes = unit_header.unit_length - cie_ptr_or_id_size;
280
281        // If this entry is a CIE, then `cie_ptr_or_id` will have this value, which is different
282        // between the DWARF `.debug_frame` section and the LSB Core `.eh_frame` section.
283        const cie_id: u64 = switch (section) {
284            .eh_frame => 0,
285            .debug_frame => switch (unit_header.format) {
286                .@"32" => maxInt(u32),
287                .@"64" => maxInt(u64),
288            },
289        };
290        if (cie_ptr_or_id == cie_id) {
291            return .{ .cie = .{
292                .format = unit_header.format,
293                .bytes_len = remaining_bytes,
294            } };
295        }
296
297        // This is an FDE -- `cie_ptr_or_id` points to the associated CIE. Unfortunately, the format
298        // of that pointer again differs between `.debug_frame` and `.eh_frame`.
299        const cie_offset = switch (section) {
300            .eh_frame => try std.math.sub(u64, header_section_offset + unit_header.header_length, cie_ptr_or_id),
301            .debug_frame => cie_ptr_or_id,
302        };
303        return .{ .fde = .{
304            .cie_offset = cie_offset,
305            .bytes_len = remaining_bytes,
306        } };
307    }
308};
309
310pub const CommonInformationEntry = struct {
311    version: u8,
312    format: Format,
313
314    /// In version 4, CIEs can specify the address size used in the CIE and associated FDEs.
315    /// This value must be used *only* to parse associated FDEs in `FrameDescriptionEntry.parse`.
316    addr_size_bytes: u8,
317
318    /// Always 0 for versions which do not specify this (currently all versions other than 4).
319    segment_selector_size: u8,
320
321    code_alignment_factor: u32,
322    data_alignment_factor: i32,
323    return_address_register: u8,
324
325    fde_pointer_enc: EH.PE,
326    is_signal_frame: bool,
327
328    augmentation_kind: AugmentationKind,
329
330    initial_instructions: []const u8,
331
332    last_row: ?struct {
333        offset: u64,
334        cfa: VirtualMachine.CfaRule,
335        cols: []VirtualMachine.Column,
336    },
337
338    pub const AugmentationKind = enum { none, gcc_eh, lsb_z };
339
340    /// This function expects to read the CIE starting with the version field.
341    /// The returned struct references memory backed by `cie_bytes`.
342    ///
343    /// `length_offset` specifies the offset of this CIE's length field in the
344    /// .eh_frame / .debug_frame section.
345    fn parse(
346        format: Format,
347        cie_bytes: []const u8,
348        section: Section,
349        default_addr_size_bytes: u8,
350    ) !CommonInformationEntry {
351        // We only read the data through this reader.
352        var r: Reader = .fixed(cie_bytes);
353
354        const version = try r.takeByte();
355        switch (section) {
356            .eh_frame => if (version != 1 and version != 3) return error.UnsupportedDwarfVersion,
357            .debug_frame => if (version != 4) return error.UnsupportedDwarfVersion,
358        }
359
360        const aug_str = try r.takeSentinel(0);
361        const aug_kind: AugmentationKind = aug: {
362            if (aug_str.len == 0) break :aug .none;
363            if (aug_str[0] == 'z') break :aug .lsb_z;
364            if (std.mem.eql(u8, aug_str, "eh")) break :aug .gcc_eh;
365            // We can't finish parsing the CIE if we don't know what its augmentation means.
366            return bad();
367        };
368
369        switch (aug_kind) {
370            .none => {}, // no extra data
371            .lsb_z => {}, // no extra data yet, but there is a bit later
372            .gcc_eh => try r.discardAll(default_addr_size_bytes), // unsupported data
373        }
374
375        const addr_size_bytes = if (version == 4) try r.takeByte() else default_addr_size_bytes;
376        const segment_selector_size: u8 = if (version == 4) try r.takeByte() else 0;
377        const code_alignment_factor = try r.takeLeb128(u32);
378        const data_alignment_factor = try r.takeLeb128(i32);
379        const return_address_register = if (version == 1) try r.takeByte() else try r.takeLeb128(u8);
380
381        // This is where LSB's augmentation might add some data.
382        const fde_pointer_enc: EH.PE, const is_signal_frame: bool = aug: {
383            const default_fde_pointer_enc: EH.PE = .{ .type = .absptr, .rel = .abs };
384            if (aug_kind != .lsb_z) break :aug .{ default_fde_pointer_enc, false };
385            const aug_data_len = try r.takeLeb128(u32);
386            var aug_data: Reader = .fixed(try r.take(aug_data_len));
387            var fde_pointer_enc: EH.PE = default_fde_pointer_enc;
388            var is_signal_frame = false;
389            for (aug_str[1..]) |byte| switch (byte) {
390                'L' => _ = try aug_data.takeByte(), // we ignore the LSDA pointer
391                'P' => {
392                    const enc: EH.PE = @bitCast(try aug_data.takeByte());
393                    const endian: Endian = .little; // irrelevant because we're discarding the value anyway
394                    _ = try readEhPointerAbs(&aug_data, enc.type, addr_size_bytes, endian); // we ignore the personality routine; endianness is irrelevant since we're discarding
395                },
396                'R' => fde_pointer_enc = @bitCast(try aug_data.takeByte()),
397                'S' => is_signal_frame = true,
398                'B', 'G' => {},
399                else => return bad(),
400            };
401            break :aug .{ fde_pointer_enc, is_signal_frame };
402        };
403
404        return .{
405            .format = format,
406            .version = version,
407            .addr_size_bytes = addr_size_bytes,
408            .segment_selector_size = segment_selector_size,
409            .code_alignment_factor = code_alignment_factor,
410            .data_alignment_factor = data_alignment_factor,
411            .return_address_register = return_address_register,
412            .fde_pointer_enc = fde_pointer_enc,
413            .is_signal_frame = is_signal_frame,
414            .augmentation_kind = aug_kind,
415            .initial_instructions = r.buffered(),
416            .last_row = null,
417        };
418    }
419};
420
421pub const FrameDescriptionEntry = struct {
422    pc_begin: u64,
423    pc_range: u64,
424    instructions: []const u8,
425
426    /// This function expects to read the FDE starting at the PC Begin field.
427    /// The returned struct references memory backed by `fde_bytes`.
428    fn parse(
429        /// The virtual address of the FDE we're parsing, *excluding* its entry header (i.e. the
430        /// address is after the header). If `fde_bytes` is backed by the memory of a loaded
431        /// module's `.eh_frame` section, this will equal `fde_bytes.ptr`.
432        fde_vaddr: u64,
433        fde_bytes: []const u8,
434        cie: *const CommonInformationEntry,
435        endian: Endian,
436    ) !FrameDescriptionEntry {
437        if (cie.segment_selector_size != 0) return error.UnsupportedAddrSize;
438
439        var r: Reader = .fixed(fde_bytes);
440
441        const pc_begin = try readEhPointer(&r, cie.fde_pointer_enc, cie.addr_size_bytes, .{
442            .pc_rel_base = fde_vaddr,
443        }, endian);
444
445        // I swear I'm not kidding when I say that PC Range is encoded with `cie.fde_pointer_enc`, but ignoring `rel`.
446        const pc_range = switch (try readEhPointerAbs(&r, cie.fde_pointer_enc.type, cie.addr_size_bytes, endian)) {
447            .unsigned => |x| x,
448            .signed => |x| cast(u64, x) orelse return bad(),
449        };
450
451        switch (cie.augmentation_kind) {
452            .none, .gcc_eh => {},
453            .lsb_z => {
454                // There is augmentation data, but it's irrelevant to us -- it
455                // only contains the LSDA pointer, which we don't care about.
456                const aug_data_len = try r.takeLeb128(usize);
457                _ = try r.discardAll(aug_data_len);
458            },
459        }
460
461        return .{
462            .pc_begin = pc_begin,
463            .pc_range = pc_range,
464            .instructions = r.buffered(),
465        };
466    }
467};
468
469/// Builds the CIE list and FDE lookup table if they are not already built. It is required to call
470/// this function at least once before calling `lookupPc` or `getFde`. If only `getFde` is needed,
471/// then `need_lookup` can be set to `false` to make this function more efficient.
472pub fn prepare(
473    unwind: *Unwind,
474    gpa: Allocator,
475    addr_size_bytes: u8,
476    endian: Endian,
477    need_lookup: bool,
478    /// The `__eh_frame` section in Mach-O binaries deviates from the standard `.eh_frame` section
479    /// in one way which this function needs to be aware of.
480    is_macho: bool,
481) !void {
482    if (unwind.cie_list.len > 0 and (!need_lookup or unwind.lookup != null)) return;
483    unwind.cie_list.clearRetainingCapacity();
484
485    if (is_macho) assert(unwind.lookup == null or unwind.lookup.? != .eh_frame_hdr);
486
487    const section = unwind.frame_section;
488
489    var r: Reader = .fixed(section.bytes);
490    var fde_list: std.ArrayList(SortedFdeEntry) = .empty;
491    defer fde_list.deinit(gpa);
492
493    const saw_terminator = while (r.seek < r.buffer.len) {
494        const entry_offset = r.seek;
495        switch (try EntryHeader.read(&r, entry_offset, section.id, endian)) {
496            .cie => |cie_info| {
497                // We will pre-populate a list of CIEs for efficiency: this avoids work re-parsing
498                // them every time we look up an FDE. It also lets us cache the result of evaluating
499                // the CIE's initial CFI instructions, which is useful because in the vast majority
500                // of cases those instructions will be needed to reach the PC we are unwinding to.
501                const bytes_len = cast(usize, cie_info.bytes_len) orelse return error.EndOfStream;
502                const idx = unwind.cie_list.len;
503                try unwind.cie_list.append(gpa, .{
504                    .offset = entry_offset,
505                    .cie = try .parse(cie_info.format, try r.take(bytes_len), section.id, addr_size_bytes),
506                });
507                errdefer _ = unwind.cie_list.pop().?;
508                try VirtualMachine.populateCieLastRow(gpa, &unwind.cie_list.items(.cie)[idx], addr_size_bytes, endian);
509                continue;
510            },
511            .fde => |fde_info| {
512                const bytes_len = cast(usize, fde_info.bytes_len) orelse return error.EndOfStream;
513                if (!need_lookup) {
514                    try r.discardAll(bytes_len);
515                    continue;
516                }
517                const cie = unwind.findCie(fde_info.cie_offset) orelse return error.InvalidDebugInfo;
518                const fde: FrameDescriptionEntry = try .parse(section.vaddr + r.seek, try r.take(bytes_len), cie, endian);
519                try fde_list.append(gpa, .{
520                    .pc_begin = fde.pc_begin,
521                    .fde_offset = entry_offset,
522                });
523            },
524            .terminator => break true,
525        }
526    } else false;
527    const expect_terminator = switch (section.id) {
528        .eh_frame => !is_macho, // `.eh_frame` indicates the end of the CIE/FDE list with a sentinel entry, though macOS omits this
529        .debug_frame => false, // `.debug_frame` uses the section bounds and does not specify a sentinel entry
530    };
531    if (saw_terminator != expect_terminator) return bad();
532
533    if (need_lookup) {
534        std.mem.sortUnstable(SortedFdeEntry, fde_list.items, {}, struct {
535            fn lessThan(ctx: void, a: SortedFdeEntry, b: SortedFdeEntry) bool {
536                ctx;
537                return a.pc_begin < b.pc_begin;
538            }
539        }.lessThan);
540
541        // This temporary is necessary to avoid an RLS footgun where `lookup` ends up non-null `undefined` on OOM.
542        const final_fdes = try fde_list.toOwnedSlice(gpa);
543        unwind.lookup = .{ .sorted_fdes = final_fdes };
544    }
545}
546
547fn findCie(unwind: *const Unwind, offset: u64) ?*const CommonInformationEntry {
548    const offsets = unwind.cie_list.items(.offset);
549    if (offsets.len == 0) return null;
550    var start: usize = 0;
551    var len: usize = offsets.len;
552    while (len > 1) {
553        const mid = len / 2;
554        if (offset < offsets[start + mid]) {
555            len = mid;
556        } else {
557            start += mid;
558            len -= mid;
559        }
560    }
561    if (offsets[start] != offset) return null;
562    return &unwind.cie_list.items(.cie)[start];
563}
564
565/// Given a program counter value, returns the offset of the corresponding FDE, or `null` if no
566/// matching FDE was found. The returned offset can be passed to `getFde` to load the data
567/// associated with the FDE.
568///
569/// Before calling this function, `prepare` must return successfully at least once, to ensure that
570/// `unwind.lookup` is populated.
571///
572/// The return value may be a false positive. After loading the FDE with `loadFde`, the caller must
573/// validate that `pc` is indeed in its range -- if it is not, then no FDE matches `pc`.
574pub fn lookupPc(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian: Endian) !?u64 {
575    const sorted_fdes: []const SortedFdeEntry = switch (unwind.lookup.?) {
576        .eh_frame_hdr => |eh_frame_hdr| {
577            const fde_vaddr = try eh_frame_hdr.table.findEntry(
578                eh_frame_hdr.vaddr,
579                pc,
580                addr_size_bytes,
581                endian,
582            ) orelse return null;
583            return std.math.sub(u64, fde_vaddr, unwind.frame_section.vaddr) catch bad(); // convert vaddr to offset
584        },
585        .sorted_fdes => |sorted_fdes| sorted_fdes,
586    };
587    if (sorted_fdes.len == 0) return null;
588    var start: usize = 0;
589    var len: usize = sorted_fdes.len;
590    while (len > 1) {
591        const half = len / 2;
592        if (pc < sorted_fdes[start + half].pc_begin) {
593            len = half;
594        } else {
595            start += half;
596            len -= half;
597        }
598    }
599    // If any FDE matches, it'll be the one at `start` (maybe false positive).
600    return sorted_fdes[start].fde_offset;
601}
602
603/// Get the FDE at a given offset, as well as its associated CIE. This offset typically comes from
604/// `lookupPc`. The CFI instructions within can be evaluated with `VirtualMachine`.
605pub fn getFde(unwind: *const Unwind, fde_offset: u64, endian: Endian) !struct { *const CommonInformationEntry, FrameDescriptionEntry } {
606    const section = unwind.frame_section;
607
608    if (fde_offset > section.bytes.len) return error.EndOfStream;
609    var fde_reader: Reader = .fixed(section.bytes[@intCast(fde_offset)..]);
610    const fde_info = switch (try EntryHeader.read(&fde_reader, fde_offset, section.id, endian)) {
611        .fde => |info| info,
612        .cie, .terminator => return bad(), // This is meant to be an FDE
613    };
614
615    const cie = unwind.findCie(fde_info.cie_offset) orelse return error.InvalidDebugInfo;
616    const fde: FrameDescriptionEntry = try .parse(
617        section.vaddr + fde_offset + fde_reader.seek,
618        try fde_reader.take(cast(usize, fde_info.bytes_len) orelse return error.EndOfStream),
619        cie,
620        endian,
621    );
622
623    return .{ cie, fde };
624}
625
626const EhPointerContext = struct {
627    /// The address of the pointer field itself
628    pc_rel_base: u64,
629    // These relative addressing modes are only used in specific cases, and
630    // might not be available / required in all parsing contexts
631    data_rel_base: ?u64 = null,
632    text_rel_base: ?u64 = null,
633    function_rel_base: ?u64 = null,
634};
635/// Returns `error.InvalidDebugInfo` if the encoding is `EH.PE.omit`.
636fn readEhPointerAbs(r: *Reader, enc_ty: EH.PE.Type, addr_size_bytes: u8, endian: Endian) !union(enum) {
637    signed: i64,
638    unsigned: u64,
639} {
640    return switch (enc_ty) {
641        .absptr => .{
642            .unsigned = switch (addr_size_bytes) {
643                2 => try r.takeInt(u16, endian),
644                4 => try r.takeInt(u32, endian),
645                8 => try r.takeInt(u64, endian),
646                else => return error.UnsupportedAddrSize,
647            },
648        },
649        .uleb128 => .{ .unsigned = try r.takeLeb128(u64) },
650        .udata2 => .{ .unsigned = try r.takeInt(u16, endian) },
651        .udata4 => .{ .unsigned = try r.takeInt(u32, endian) },
652        .udata8 => .{ .unsigned = try r.takeInt(u64, endian) },
653        .sleb128 => .{ .signed = try r.takeLeb128(i64) },
654        .sdata2 => .{ .signed = try r.takeInt(i16, endian) },
655        .sdata4 => .{ .signed = try r.takeInt(i32, endian) },
656        .sdata8 => .{ .signed = try r.takeInt(i64, endian) },
657        else => return bad(),
658    };
659}
660/// Returns `error.InvalidDebugInfo` if the encoding is `EH.PE.omit`.
661fn readEhPointer(r: *Reader, enc: EH.PE, addr_size_bytes: u8, ctx: EhPointerContext, endian: Endian) !u64 {
662    const offset = try readEhPointerAbs(r, enc.type, addr_size_bytes, endian);
663    if (enc.indirect) return bad(); // GCC extension; not supported
664    const base: u64 = switch (enc.rel) {
665        .abs, .aligned => 0,
666        .pcrel => ctx.pc_rel_base,
667        .textrel => ctx.text_rel_base orelse return bad(),
668        .datarel => ctx.data_rel_base orelse return bad(),
669        .funcrel => ctx.function_rel_base orelse return bad(),
670        _ => return bad(),
671    };
672    return switch (offset) {
673        .signed => |s| if (s >= 0)
674            try std.math.add(u64, base, @intCast(s))
675        else
676            try std.math.sub(u64, base, @intCast(-s)),
677        // absptr can actually contain signed values in some cases (aarch64 MachO)
678        .unsigned => |u| u +% base,
679    };
680}
681
682/// Like `Reader.fixed`, but when the length of the data is unknown and we just want to allow
683/// reading indefinitely.
684fn maxSlice(ptr: [*]const u8) []const u8 {
685    const len = std.math.maxInt(usize) - @intFromPtr(ptr);
686    return ptr[0..len];
687}
688
689const Allocator = std.mem.Allocator;
690const assert = std.debug.assert;
691const bad = Dwarf.bad;
692const cast = std.math.cast;
693const DW = std.dwarf;
694const Dwarf = std.debug.Dwarf;
695const EH = DW.EH;
696const Endian = std.builtin.Endian;
697const Format = DW.Format;
698const maxInt = std.math.maxInt;
699const missing = Dwarf.missing;
700const Reader = std.Io.Reader;
701const std = @import("std");
702const Unwind = @This();