authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-26 10:52:09+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:56+01:00
log156cd8f678ebdcccc48382d093a3ef7e45c85a45
treeca3f4c37bda9cf1d039ac25ba37b2c45ab5a345f
parent3f84b6c80ed3306f040dd98b8ccba561a052167a
signaturelock-open Commit is signed but in an unrecognized format.

std.debug: significantly speed up capturing stack traces

By my estimation, these changes speed up DWARF unwinding when using the self-hosted x86_64 backend by around 7x. There are two very significant enhancements: we no longer iterate frames which don't fit in the stack trace buffer, and we cache register rules (in a fixed buffer) to avoid re-parsing and evaluating CFI instructions in most cases. Alongside this are a bunch of smaller enhancements, such as pre-caching the result of evaluating the CIE's initial instructions, avoiding re-parsing of CIEs, and big simplifications to the `Dwarf.Unwind.VirtualMachine` logic.

8 files changed, 811 insertions(+), 809 deletions(-)

lib/std/debug.zig+9-6
......@@ -572,9 +572,12 @@ pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize)
572572 defer it.deinit();
573573 if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace;
574574 var total_frames: usize = 0;
575 var frame_idx: usize = 0;
575 var index: usize = 0;
576576 var wait_for = options.first_address;
577 while (true) switch (it.next()) {
577 // Ideally, we would iterate the whole stack so that the `index` in the returned trace was
578 // indicative of how many frames were skipped. However, this has a significant runtime cost
579 // in some cases, so at least for now, we don't do that.
580 while (index < addr_buf.len) switch (it.next()) {
578581 .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break,
579582 .end => break,
580583 .frame => |ret_addr| {
......@@ -588,13 +591,13 @@ pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize)
588591 if (ret_addr != target) continue;
589592 wait_for = null;
590593 }
591 if (frame_idx < addr_buf.len) addr_buf[frame_idx] = ret_addr;
592 frame_idx += 1;
594 addr_buf[index] = ret_addr;
595 index += 1;
593596 },
594597 };
595598 return .{
596 .index = frame_idx,
597 .instruction_addresses = addr_buf[0..@min(frame_idx, addr_buf.len)],
599 .index = index,
600 .instruction_addresses = addr_buf[0..index],
598601 };
599602}
600603/// Write the current stack trace to `writer`, annotated with source locations.
lib/std/debug/Dwarf.zig-1
......@@ -27,7 +27,6 @@ const Reader = std.Io.Reader;
2727const Dwarf = @This();
2828
2929pub const expression = @import("Dwarf/expression.zig");
30pub const call_frame = @import("Dwarf/call_frame.zig");
3130pub const Unwind = @import("Dwarf/Unwind.zig");
3231
3332/// Useful to temporarily enable while working on this file.
lib/std/debug/Dwarf/Unwind.zig+95-50
......@@ -10,7 +10,7 @@
1010//! The typical usage of `Unwind` is as follows:
1111//!
1212//! * Initialize with `initEhFrameHdr` or `initSection`, depending on the available data
13//! * Call `prepareLookup` to construct a search table if necessary
13//! * Call `prepare` to scan CIEs and, if necessary, construct a search table
1414//! * Call `lookupPc` to find the section offset of the FDE corresponding to a PC
1515//! * Call `getFde` to load the corresponding FDE and CIE
1616//! * Check that the PC does indeed fall in that range (`lookupPc` may return a false positive)
......@@ -18,7 +18,7 @@
1818//!
1919//! In some cases, such as when using the "compact unwind" data in Mach-O binaries, the FDE offsets
2020//! may already be known. In that case, no call to `lookupPc` is necessary, which means the call to
21//! `prepareLookup` can also be omitted.
21//! `prepare` can be optimized to only scan CIEs.
2222
2323pub const VirtualMachine = @import("Unwind/VirtualMachine.zig");
2424
......@@ -45,7 +45,7 @@ frame_section: struct {
4545
4646/// A structure allowing fast lookups of the FDE corresponding to a particular PC. We use a binary
4747/// 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 `prepareLookup` must be called before `lookupPc`.
48/// the lookup data is not yet populated, so `prepare` must be called before `lookupPc`.
4949lookup: ?union(enum) {
5050 /// The `.eh_frame_hdr` section contains a pre-computed search table which we can use.
5151 eh_frame_hdr: struct {
......@@ -58,6 +58,12 @@ lookup: ?union(enum) {
5858 sorted_fdes: []SortedFdeEntry,
5959},
6060
61/// Initially empty; populated by `prepare`.
62cie_list: std.MultiArrayList(struct {
63 offset: u64,
64 cie: CommonInformationEntry,
65}),
66
6167const SortedFdeEntry = struct {
6268 /// This FDE's value of `pc_begin`.
6369 pc_begin: u64,
......@@ -83,6 +89,7 @@ pub fn initEhFrameHdr(header: EhFrameHeader, section_vaddr: u64, section_bytes_p
8389 .vaddr = section_vaddr,
8490 .table = table,
8591 } } else null,
92 .cie_list = .empty,
8693 };
8794}
8895
......@@ -98,16 +105,21 @@ pub fn initSection(section: Section, section_vaddr: u64, section_bytes: []const
98105 .vaddr = section_vaddr,
99106 },
100107 .lookup = null,
108 .cie_list = .empty,
101109 };
102110}
103111
104/// Technically, it is only necessary to call this if `prepareLookup` has previously been called,
105/// since no other function here allocates resources.
106112pub fn deinit(unwind: *Unwind, gpa: Allocator) void {
107113 if (unwind.lookup) |lookup| switch (lookup) {
108114 .eh_frame_hdr => {},
109115 .sorted_fdes => |fdes| gpa.free(fdes),
110116 };
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);
111123}
112124
113125/// Decoded version of the `.eh_frame_hdr` section.
......@@ -236,7 +248,6 @@ const EntryHeader = union(enum) {
236248 bytes_len: u64,
237249 },
238250 fde: struct {
239 format: Format,
240251 /// Offset into the section of the corresponding CIE, *including* its entry header.
241252 cie_offset: u64,
242253 /// Remaining bytes in the FDE. These are parseable by `FrameDescriptionEntry.parse`.
......@@ -290,7 +301,6 @@ const EntryHeader = union(enum) {
290301 .debug_frame => cie_ptr_or_id,
291302 };
292303 return .{ .fde = .{
293 .format = unit_header.format,
294304 .cie_offset = cie_offset,
295305 .bytes_len = remaining_bytes,
296306 } };
......@@ -299,6 +309,7 @@ const EntryHeader = union(enum) {
299309
300310pub const CommonInformationEntry = struct {
301311 version: u8,
312 format: Format,
302313
303314 /// In version 4, CIEs can specify the address size used in the CIE and associated FDEs.
304315 /// This value must be used *only* to parse associated FDEs in `FrameDescriptionEntry.parse`.
......@@ -318,6 +329,12 @@ pub const CommonInformationEntry = struct {
318329
319330 initial_instructions: []const u8,
320331
332 last_row: ?struct {
333 offset: u64,
334 cfa: VirtualMachine.CfaRule,
335 cols: []VirtualMachine.Column,
336 },
337
321338 pub const AugmentationKind = enum { none, gcc_eh, lsb_z };
322339
323340 /// This function expects to read the CIE starting with the version field.
......@@ -326,6 +343,7 @@ pub const CommonInformationEntry = struct {
326343 /// `length_offset` specifies the offset of this CIE's length field in the
327344 /// .eh_frame / .debug_frame section.
328345 fn parse(
346 format: Format,
329347 cie_bytes: []const u8,
330348 section: Section,
331349 default_addr_size_bytes: u8,
......@@ -384,6 +402,7 @@ pub const CommonInformationEntry = struct {
384402 };
385403
386404 return .{
405 .format = format,
387406 .version = version,
388407 .addr_size_bytes = addr_size_bytes,
389408 .segment_selector_size = segment_selector_size,
......@@ -394,6 +413,7 @@ pub const CommonInformationEntry = struct {
394413 .is_signal_frame = is_signal_frame,
395414 .augmentation_kind = aug_kind,
396415 .initial_instructions = r.buffered(),
416 .last_row = null,
397417 };
398418 }
399419};
......@@ -411,7 +431,7 @@ pub const FrameDescriptionEntry = struct {
411431 /// module's `.eh_frame` section, this will equal `fde_bytes.ptr`.
412432 fde_vaddr: u64,
413433 fde_bytes: []const u8,
414 cie: CommonInformationEntry,
434 cie: *const CommonInformationEntry,
415435 endian: Endian,
416436 ) !FrameDescriptionEntry {
417437 if (cie.segment_selector_size != 0) return error.UnsupportedAddrSize;
......@@ -446,11 +466,18 @@ pub const FrameDescriptionEntry = struct {
446466 }
447467};
448468
449/// Builds the PC FDE lookup table if it is not already built. It is required to call this function
450/// at least once before calling `lookupPc`. Once this function is called, memory has been allocated
451/// and so `deinit` (matching this `gpa`) is required to free it.
452pub fn prepareLookup(unwind: *Unwind, gpa: Allocator, addr_size_bytes: u8, endian: Endian) !void {
453 if (unwind.lookup != null) return;
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) !void {
479 if (unwind.cie_list.len > 0 and (!need_lookup or unwind.lookup != null)) return;
480 unwind.cie_list.clearRetainingCapacity();
454481
455482 const section = unwind.frame_section;
456483
......@@ -462,21 +489,28 @@ pub fn prepareLookup(unwind: *Unwind, gpa: Allocator, addr_size_bytes: u8, endia
462489 const entry_offset = r.seek;
463490 switch (try EntryHeader.read(&r, entry_offset, section.id, endian)) {
464491 .cie => |cie_info| {
465 // Ignore CIEs for now; we'll parse them when we read a corresponding FDE
466 try r.discardAll(cast(usize, cie_info.bytes_len) orelse return error.EndOfStream);
492 // We will pre-populate a list of CIEs for efficiency: this avoids work re-parsing
493 // them every time we look up an FDE. It also lets us cache the result of evaluating
494 // the CIE's initial CFI instructions, which is useful because in the vast majority
495 // of cases those instructions will be needed to reach the PC we are unwinding to.
496 const bytes_len = cast(usize, cie_info.bytes_len) orelse return error.EndOfStream;
497 const idx = unwind.cie_list.len;
498 try unwind.cie_list.append(gpa, .{
499 .offset = entry_offset,
500 .cie = try .parse(cie_info.format, try r.take(bytes_len), section.id, addr_size_bytes),
501 });
502 errdefer _ = unwind.cie_list.pop().?;
503 try VirtualMachine.populateCieLastRow(gpa, &unwind.cie_list.items(.cie)[idx], addr_size_bytes, endian);
467504 continue;
468505 },
469506 .fde => |fde_info| {
470 if (fde_info.cie_offset > section.bytes.len) return error.EndOfStream;
471 var cie_r: Reader = .fixed(section.bytes[@intCast(fde_info.cie_offset)..]);
472 const cie_info = switch (try EntryHeader.read(&cie_r, fde_info.cie_offset, section.id, endian)) {
473 .cie => |cie_info| cie_info,
474 .fde, .terminator => return bad(), // this is meant to be a CIE
475 };
476 const cie_bytes_len = cast(usize, cie_info.bytes_len) orelse return error.EndOfStream;
477 const fde_bytes_len = cast(usize, fde_info.bytes_len) orelse return error.EndOfStream;
478 const cie: CommonInformationEntry = try .parse(try cie_r.take(cie_bytes_len), section.id, addr_size_bytes);
479 const fde: FrameDescriptionEntry = try .parse(section.vaddr + r.seek, try r.take(fde_bytes_len), cie, endian);
507 const bytes_len = cast(usize, fde_info.bytes_len) orelse return error.EndOfStream;
508 if (!need_lookup) {
509 try r.discardAll(bytes_len);
510 continue;
511 }
512 const cie = unwind.findCie(fde_info.cie_offset) orelse return error.InvalidDebugInfo;
513 const fde: FrameDescriptionEntry = try .parse(section.vaddr + r.seek, try r.take(bytes_len), cie, endian);
480514 try fde_list.append(gpa, .{
481515 .pc_begin = fde.pc_begin,
482516 .fde_offset = entry_offset,
......@@ -502,12 +536,30 @@ pub fn prepareLookup(unwind: *Unwind, gpa: Allocator, addr_size_bytes: u8, endia
502536 unwind.lookup = .{ .sorted_fdes = final_fdes };
503537}
504538
539fn findCie(unwind: *const Unwind, offset: u64) ?*const CommonInformationEntry {
540 const offsets = unwind.cie_list.items(.offset);
541 if (offsets.len == 0) return null;
542 var start: usize = 0;
543 var len: usize = offsets.len;
544 while (len > 1) {
545 const mid = len / 2;
546 if (offset < offsets[start + mid]) {
547 len = mid;
548 } else {
549 start += mid;
550 len -= mid;
551 }
552 }
553 if (offsets[start] != offset) return null;
554 return &unwind.cie_list.items(.cie)[start];
555}
556
505557/// Given a program counter value, returns the offset of the corresponding FDE, or `null` if no
506558/// matching FDE was found. The returned offset can be passed to `getFde` to load the data
507559/// associated with the FDE.
508560///
509/// Before calling this function, `prepareLookup` must return successfully at least once, to ensure
510/// that `unwind.lookup` is populated.
561/// Before calling this function, `prepare` must return successfully at least once, to ensure that
562/// `unwind.lookup` is populated.
511563///
512564/// The return value may be a false positive. After loading the FDE with `loadFde`, the caller must
513565/// validate that `pc` is indeed in its range -- if it is not, then no FDE matches `pc`.
......@@ -524,20 +576,25 @@ pub fn lookupPc(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian: End
524576 },
525577 .sorted_fdes => |sorted_fdes| sorted_fdes,
526578 };
527 const first_bad_idx = std.sort.partitionPoint(SortedFdeEntry, sorted_fdes, pc, struct {
528 fn canIncludePc(target_pc: u64, entry: SortedFdeEntry) bool {
529 return target_pc >= entry.pc_begin; // i.e. does 'entry_pc..<last pc>' include 'target_pc'
579 if (sorted_fdes.len == 0) return null;
580 var start: usize = 0;
581 var len: usize = sorted_fdes.len;
582 while (len > 1) {
583 const half = len / 2;
584 if (pc < sorted_fdes[start + half].pc_begin) {
585 len = half;
586 } else {
587 start += half;
588 len -= half;
530589 }
531 }.canIncludePc);
532 // `first_bad_idx` is the index of the first FDE whose `pc_begin` is too high to include `pc`.
533 // So if any FDE matches, it'll be the one at `first_bad_idx - 1` (maybe false positive).
534 if (first_bad_idx == 0) return null;
535 return sorted_fdes[first_bad_idx - 1].fde_offset;
590 }
591 // If any FDE matches, it'll be the one at `start` (maybe false positive).
592 return sorted_fdes[start].fde_offset;
536593}
537594
538595/// Get the FDE at a given offset, as well as its associated CIE. This offset typically comes from
539596/// `lookupPc`. The CFI instructions within can be evaluated with `VirtualMachine`.
540pub fn getFde(unwind: *const Unwind, fde_offset: u64, addr_size_bytes: u8, endian: Endian) !struct { Format, CommonInformationEntry, FrameDescriptionEntry } {
597pub fn getFde(unwind: *const Unwind, fde_offset: u64, endian: Endian) !struct { *const CommonInformationEntry, FrameDescriptionEntry } {
541598 const section = unwind.frame_section;
542599
543600 if (fde_offset > section.bytes.len) return error.EndOfStream;
......@@ -547,19 +604,7 @@ pub fn getFde(unwind: *const Unwind, fde_offset: u64, addr_size_bytes: u8, endia
547604 .cie, .terminator => return bad(), // This is meant to be an FDE
548605 };
549606
550 const cie_offset = fde_info.cie_offset;
551 if (cie_offset > section.bytes.len) return error.EndOfStream;
552 var cie_reader: Reader = .fixed(section.bytes[@intCast(cie_offset)..]);
553 const cie_info = switch (try EntryHeader.read(&cie_reader, cie_offset, section.id, endian)) {
554 .cie => |info| info,
555 .fde, .terminator => return bad(), // This is meant to be a CIE
556 };
557
558 const cie: CommonInformationEntry = try .parse(
559 try cie_reader.take(cast(usize, cie_info.bytes_len) orelse return error.EndOfStream),
560 section.id,
561 addr_size_bytes,
562 );
607 const cie = unwind.findCie(fde_info.cie_offset) orelse return error.InvalidDebugInfo;
563608 const fde: FrameDescriptionEntry = try .parse(
564609 section.vaddr + fde_offset + fde_reader.seek,
565610 try fde_reader.take(cast(usize, fde_info.bytes_len) orelse return error.EndOfStream),
......@@ -567,7 +612,7 @@ pub fn getFde(unwind: *const Unwind, fde_offset: u64, addr_size_bytes: u8, endia
567612 endian,
568613 );
569614
570 return .{ cie_info.format, cie, fde };
615 return .{ cie, fde };
571616}
572617
573618const EhPointerContext = struct {
lib/std/debug/Dwarf/Unwind/VirtualMachine.zig+354-200
......@@ -5,9 +5,9 @@ pub const RegisterRule = union(enum) {
55 /// The spec says that the default rule for each column is the undefined rule.
66 /// However, it also allows ABI / compiler authors to specify alternate defaults, so
77 /// there is a distinction made here.
8 default: void,
9 undefined: void,
10 same_value: void,
8 default,
9 undefined,
10 same_value,
1111 /// offset(N)
1212 offset: i64,
1313 /// val_offset(N)
......@@ -18,38 +18,39 @@ pub const RegisterRule = union(enum) {
1818 expression: []const u8,
1919 /// val_expression(E)
2020 val_expression: []const u8,
21 /// Augmenter-defined rule
22 architectural: void,
21};
22
23pub const CfaRule = union(enum) {
24 none,
25 reg_off: struct {
26 register: u8,
27 offset: i64,
28 },
29 expression: []const u8,
2330};
2431
2532/// Each row contains unwinding rules for a set of registers.
2633pub const Row = struct {
2734 /// Offset from `FrameDescriptionEntry.pc_begin`
2835 offset: u64 = 0,
29 /// Special-case column that defines the CFA (Canonical Frame Address) rule.
30 /// The register field of this column defines the register that CFA is derived from.
31 cfa: Column = .{},
36 cfa: CfaRule = .none,
3237 /// The register fields in these columns define the register the rule applies to.
33 columns: ColumnRange = .{},
34 /// Indicates that the next write to any column in this row needs to copy
35 /// the backing column storage first, as it may be referenced by previous rows.
36 copy_on_write: bool = false,
38 columns: ColumnRange = .{ .start = undefined, .len = 0 },
3739};
3840
3941pub const Column = struct {
40 register: ?u8 = null,
41 rule: RegisterRule = .{ .default = {} },
42 register: u8,
43 rule: RegisterRule,
4244};
4345
4446const ColumnRange = struct {
45 /// Index into `columns` of the first column in this row.
46 start: usize = undefined,
47 len: u8 = 0,
47 start: usize,
48 len: u8,
4849};
4950
5051columns: std.ArrayList(Column) = .empty,
5152stack: std.ArrayList(struct {
52 cfa: Column,
53 cfa: CfaRule,
5354 columns: ColumnRange,
5455}) = .empty,
5556current_row: Row = .{},
......@@ -71,235 +72,388 @@ pub fn reset(self: *VirtualMachine) void {
7172}
7273
7374/// Return a slice backed by the row's non-CFA columns
74pub fn rowColumns(self: VirtualMachine, row: Row) []Column {
75pub fn rowColumns(self: *const VirtualMachine, row: *const Row) []Column {
7576 if (row.columns.len == 0) return &.{};
7677 return self.columns.items[row.columns.start..][0..row.columns.len];
7778}
7879
7980/// Either retrieves or adds a column for `register` (non-CFA) in the current row.
8081fn getOrAddColumn(self: *VirtualMachine, gpa: Allocator, register: u8) !*Column {
81 for (self.rowColumns(self.current_row)) |*c| {
82 for (self.rowColumns(&self.current_row)) |*c| {
8283 if (c.register == register) return c;
8384 }
8485
8586 if (self.current_row.columns.len == 0) {
8687 self.current_row.columns.start = self.columns.items.len;
88 } else {
89 assert(self.current_row.columns.start + self.current_row.columns.len == self.columns.items.len);
8790 }
8891 self.current_row.columns.len += 1;
8992
9093 const column = try self.columns.addOne(gpa);
9194 column.* = .{
9295 .register = register,
96 .rule = .default,
9397 };
9498
9599 return column;
96100}
97101
102pub fn populateCieLastRow(
103 gpa: Allocator,
104 cie: *Unwind.CommonInformationEntry,
105 addr_size_bytes: u8,
106 endian: std.builtin.Endian,
107) !void {
108 assert(cie.last_row == null);
109
110 var vm: VirtualMachine = .{};
111 defer vm.deinit(gpa);
112
113 try vm.evalInstructions(
114 gpa,
115 cie,
116 std.math.maxInt(u64),
117 cie.initial_instructions,
118 addr_size_bytes,
119 endian,
120 );
121
122 cie.last_row = .{
123 .offset = vm.current_row.offset,
124 .cfa = vm.current_row.cfa,
125 .cols = try gpa.dupe(Column, vm.rowColumns(&vm.current_row)),
126 };
127}
128
98129/// Runs the CIE instructions, then the FDE instructions. Execution halts
99130/// once the row that corresponds to `pc` is known, and the row is returned.
100131pub fn runTo(
101 self: *VirtualMachine,
132 vm: *VirtualMachine,
102133 gpa: Allocator,
103134 pc: u64,
104 cie: Dwarf.Unwind.CommonInformationEntry,
105 fde: Dwarf.Unwind.FrameDescriptionEntry,
135 cie: *const Unwind.CommonInformationEntry,
136 fde: *const Unwind.FrameDescriptionEntry,
106137 addr_size_bytes: u8,
107138 endian: std.builtin.Endian,
108139) !Row {
109 assert(self.cie_row == null);
110 assert(pc >= fde.pc_begin);
111 assert(pc < fde.pc_begin + fde.pc_range);
140 assert(vm.cie_row == null);
112141
113 var prev_row: Row = self.current_row;
142 const target_offset = pc - fde.pc_begin;
143 assert(target_offset < fde.pc_range);
114144
115 const instruction_slices: [2][]const u8 = .{
116 cie.initial_instructions,
117 fde.instructions,
118 };
119 for (instruction_slices, [2]bool{ true, false }) |slice, is_cie_stream| {
120 var stream: std.Io.Reader = .fixed(slice);
121 while (stream.seek < slice.len) {
122 const instruction: Dwarf.call_frame.Instruction = try .read(&stream, addr_size_bytes, endian);
123 prev_row = try self.step(gpa, cie, is_cie_stream, instruction);
124 if (pc < fde.pc_begin + self.current_row.offset) return prev_row;
145 const instruction_bytes: []const u8 = insts: {
146 if (target_offset < cie.last_row.?.offset) {
147 break :insts cie.initial_instructions;
125148 }
126 }
149 // This is the more common case: start from the CIE's last row.
150 assert(vm.columns.items.len == 0);
151 vm.current_row = .{
152 .offset = cie.last_row.?.offset,
153 .cfa = cie.last_row.?.cfa,
154 .columns = .{
155 .start = 0,
156 .len = @intCast(cie.last_row.?.cols.len),
157 },
158 };
159 try vm.columns.appendSlice(gpa, cie.last_row.?.cols);
160 vm.cie_row = vm.current_row;
161 break :insts fde.instructions;
162 };
127163
128 return self.current_row;
164 try vm.evalInstructions(
165 gpa,
166 cie,
167 target_offset,
168 instruction_bytes,
169 addr_size_bytes,
170 endian,
171 );
172 return vm.current_row;
129173}
130174
131fn resolveCopyOnWrite(self: *VirtualMachine, gpa: Allocator) !void {
132 if (!self.current_row.copy_on_write) return;
175/// Evaluates instructions from `instruction_bytes` until `target_addr` is reached or all
176/// instructions have been evaluated.
177fn evalInstructions(
178 vm: *VirtualMachine,
179 gpa: Allocator,
180 cie: *const Unwind.CommonInformationEntry,
181 target_addr: u64,
182 instruction_bytes: []const u8,
183 addr_size_bytes: u8,
184 endian: std.builtin.Endian,
185) !void {
186 var fr: std.Io.Reader = .fixed(instruction_bytes);
187 while (fr.seek < fr.buffer.len) {
188 switch (try Instruction.read(&fr, addr_size_bytes, endian)) {
189 .nop => {
190 // If there was one nop, there's a good chance we've reached the padding and so
191 // everything left is a nop, which is represented by a 0 byte.
192 if (std.mem.allEqual(u8, fr.buffered(), 0)) return;
193 },
194
195 .remember_state => {
196 try vm.stack.append(gpa, .{
197 .cfa = vm.current_row.cfa,
198 .columns = vm.current_row.columns,
199 });
200 const cols_len = vm.current_row.columns.len;
201 const copy_start = vm.columns.items.len;
202 assert(vm.current_row.columns.start == copy_start - cols_len);
203 try vm.columns.ensureUnusedCapacity(gpa, cols_len); // to prevent aliasing issues
204 vm.columns.appendSliceAssumeCapacity(vm.columns.items[copy_start - cols_len ..]);
205 vm.current_row.columns.start = copy_start;
206 },
207 .restore_state => {
208 const restored = vm.stack.pop() orelse return error.InvalidOperation;
209 vm.columns.shrinkRetainingCapacity(restored.columns.start + restored.columns.len);
210
211 vm.current_row.cfa = restored.cfa;
212 vm.current_row.columns = restored.columns;
213 },
133214
134 const new_start = self.columns.items.len;
135 if (self.current_row.columns.len > 0) {
136 try self.columns.ensureUnusedCapacity(gpa, self.current_row.columns.len);
137 self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row));
138 self.current_row.columns.start = new_start;
215 .advance_loc => |delta| {
216 const new_addr = vm.current_row.offset + delta * cie.code_alignment_factor;
217 if (new_addr > target_addr) return;
218 vm.current_row.offset = new_addr;
219 },
220 .set_loc => |new_addr| {
221 if (new_addr <= vm.current_row.offset) return error.InvalidOperation;
222 if (cie.segment_selector_size != 0) return error.InvalidOperation; // unsupported
223 // TODO: Check cie.segment_selector_size != 0 for DWARFV4
224
225 if (new_addr > target_addr) return;
226 vm.current_row.offset = new_addr;
227 },
228
229 .register => |reg| {
230 const column = try vm.getOrAddColumn(gpa, reg.index);
231 column.rule = switch (reg.rule) {
232 .restore => rule: {
233 const cie_row = &(vm.cie_row orelse return error.InvalidOperation);
234 for (vm.rowColumns(cie_row)) |cie_col| {
235 if (cie_col.register == reg.index) break :rule cie_col.rule;
236 }
237 break :rule .default;
238 },
239 .undefined => .undefined,
240 .same_value => .same_value,
241 .offset_uf => |off| .{ .offset = @as(i64, @intCast(off)) * cie.data_alignment_factor },
242 .offset_sf => |off| .{ .offset = off * cie.data_alignment_factor },
243 .val_offset_uf => |off| .{ .val_offset = @as(i64, @intCast(off)) * cie.data_alignment_factor },
244 .val_offset_sf => |off| .{ .val_offset = off * cie.data_alignment_factor },
245 .register => |callee_reg| .{ .register = callee_reg },
246 .expr => |len| .{ .expression = try takeExprBlock(&fr, len) },
247 .val_expr => |len| .{ .val_expression = try takeExprBlock(&fr, len) },
248 };
249 },
250 .def_cfa => |cfa| vm.current_row.cfa = .{ .reg_off = .{
251 .register = cfa.register,
252 .offset = @intCast(cfa.offset),
253 } },
254 .def_cfa_sf => |cfa| vm.current_row.cfa = .{ .reg_off = .{
255 .register = cfa.register,
256 .offset = cfa.offset_sf * cie.data_alignment_factor,
257 } },
258 .def_cfa_reg => |register| switch (vm.current_row.cfa) {
259 .none, .expression => return error.InvalidOperation,
260 .reg_off => |*ro| ro.register = register,
261 },
262 .def_cfa_offset => |offset| switch (vm.current_row.cfa) {
263 .none, .expression => return error.InvalidOperation,
264 .reg_off => |*ro| ro.offset = @intCast(offset),
265 },
266 .def_cfa_offset_sf => |offset_sf| switch (vm.current_row.cfa) {
267 .none, .expression => return error.InvalidOperation,
268 .reg_off => |*ro| ro.offset = offset_sf * cie.data_alignment_factor,
269 },
270 .def_cfa_expr => |len| {
271 vm.current_row.cfa = .{ .expression = try takeExprBlock(&fr, len) };
272 },
273 }
139274 }
140275}
141276
142/// Executes a single instruction.
143/// If this instruction is from the CIE, `is_initial` should be set.
144/// Returns the value of `current_row` before executing this instruction.
145pub fn step(
146 self: *VirtualMachine,
147 gpa: Allocator,
148 cie: Dwarf.Unwind.CommonInformationEntry,
149 is_initial: bool,
150 instruction: Dwarf.call_frame.Instruction,
151) !Row {
152 // CIE instructions must be run before FDE instructions
153 assert(!is_initial or self.cie_row == null);
154 if (!is_initial and self.cie_row == null) {
155 self.cie_row = self.current_row;
156 self.current_row.copy_on_write = true;
157 }
277fn takeExprBlock(r: *std.Io.Reader, len: usize) error{ ReadFailed, InvalidOperand }![]const u8 {
278 return r.take(len) catch |err| switch (err) {
279 error.ReadFailed => |e| return e,
280 error.EndOfStream => return error.InvalidOperand,
281 };
282}
158283
159 const prev_row = self.current_row;
160 switch (instruction) {
161 .set_loc => |i| {
162 if (i.address <= self.current_row.offset) return error.InvalidOperation;
163 if (cie.segment_selector_size != 0) return error.InvalidOperation; // unsupported
164 // TODO: Check cie.segment_selector_size != 0 for DWARFV4
165 self.current_row.offset = i.address;
166 },
167 inline .advance_loc,
168 .advance_loc1,
169 .advance_loc2,
170 .advance_loc4,
171 => |i| {
172 self.current_row.offset += i.delta * cie.code_alignment_factor;
173 self.current_row.copy_on_write = true;
174 },
175 inline .offset,
176 .offset_extended,
177 .offset_extended_sf,
178 => |i| {
179 try self.resolveCopyOnWrite(gpa);
180 const column = try self.getOrAddColumn(gpa, i.register);
181 column.rule = .{ .offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor };
182 },
183 inline .restore,
184 .restore_extended,
185 => |i| {
186 try self.resolveCopyOnWrite(gpa);
187 if (self.cie_row) |cie_row| {
188 const column = try self.getOrAddColumn(gpa, i.register);
189 column.rule = for (self.rowColumns(cie_row)) |cie_column| {
190 if (cie_column.register == i.register) break cie_column.rule;
191 } else .{ .default = {} };
192 } else return error.InvalidOperation;
193 },
194 .nop => {},
195 .undefined => |i| {
196 try self.resolveCopyOnWrite(gpa);
197 const column = try self.getOrAddColumn(gpa, i.register);
198 column.rule = .{ .undefined = {} };
199 },
200 .same_value => |i| {
201 try self.resolveCopyOnWrite(gpa);
202 const column = try self.getOrAddColumn(gpa, i.register);
203 column.rule = .{ .same_value = {} };
204 },
205 .register => |i| {
206 try self.resolveCopyOnWrite(gpa);
207 const column = try self.getOrAddColumn(gpa, i.register);
208 column.rule = .{ .register = i.target_register };
209 },
210 .remember_state => {
211 try self.stack.append(gpa, .{
212 .cfa = self.current_row.cfa,
213 .columns = self.current_row.columns,
214 });
215 self.current_row.copy_on_write = true;
216 },
217 .restore_state => {
218 const restored = self.stack.pop() orelse return error.InvalidOperation;
219 self.columns.shrinkRetainingCapacity(self.columns.items.len - self.current_row.columns.len);
220 try self.columns.ensureUnusedCapacity(gpa, restored.columns.len);
221
222 self.current_row.cfa = restored.cfa;
223 self.current_row.columns.start = self.columns.items.len;
224 self.current_row.columns.len = restored.columns.len;
225 self.columns.appendSliceAssumeCapacity(self.columns.items[restored.columns.start..][0..restored.columns.len]);
226 },
227 .def_cfa => |i| {
228 try self.resolveCopyOnWrite(gpa);
229 self.current_row.cfa = .{
230 .register = i.register,
231 .rule = .{ .val_offset = @intCast(i.offset) },
232 };
233 },
234 .def_cfa_sf => |i| {
235 try self.resolveCopyOnWrite(gpa);
236 self.current_row.cfa = .{
237 .register = i.register,
238 .rule = .{ .val_offset = i.offset * cie.data_alignment_factor },
239 };
240 },
241 .def_cfa_register => |i| {
242 try self.resolveCopyOnWrite(gpa);
243 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
244 self.current_row.cfa.register = i.register;
245 },
246 .def_cfa_offset => |i| {
247 try self.resolveCopyOnWrite(gpa);
248 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
249 self.current_row.cfa.rule = .{
250 .val_offset = @intCast(i.offset),
251 };
252 },
253 .def_cfa_offset_sf => |i| {
254 try self.resolveCopyOnWrite(gpa);
255 if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation;
256 self.current_row.cfa.rule = .{
257 .val_offset = i.offset * cie.data_alignment_factor,
258 };
259 },
260 .def_cfa_expression => |i| {
261 try self.resolveCopyOnWrite(gpa);
262 self.current_row.cfa.register = undefined;
263 self.current_row.cfa.rule = .{
264 .expression = i.block,
265 };
284const OpcodeByte = packed struct(u8) {
285 low: packed union {
286 operand: u6,
287 extended: enum(u6) {
288 nop = 0,
289 set_loc = 1,
290 advance_loc1 = 2,
291 advance_loc2 = 3,
292 advance_loc4 = 4,
293 offset_extended = 5,
294 restore_extended = 6,
295 undefined = 7,
296 same_value = 8,
297 register = 9,
298 remember_state = 10,
299 restore_state = 11,
300 def_cfa = 12,
301 def_cfa_register = 13,
302 def_cfa_offset = 14,
303 def_cfa_expression = 15,
304 expression = 16,
305 offset_extended_sf = 17,
306 def_cfa_sf = 18,
307 def_cfa_offset_sf = 19,
308 val_offset = 20,
309 val_offset_sf = 21,
310 val_expression = 22,
311 _,
266312 },
267 .expression => |i| {
268 try self.resolveCopyOnWrite(gpa);
269 const column = try self.getOrAddColumn(gpa, i.register);
270 column.rule = .{
271 .expression = i.block,
272 };
273 },
274 .val_offset => |i| {
275 try self.resolveCopyOnWrite(gpa);
276 const column = try self.getOrAddColumn(gpa, i.register);
277 column.rule = .{
278 .val_offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor,
279 };
280 },
281 .val_offset_sf => |i| {
282 try self.resolveCopyOnWrite(gpa);
283 const column = try self.getOrAddColumn(gpa, i.register);
284 column.rule = .{
285 .val_offset = i.offset * cie.data_alignment_factor,
286 };
287 },
288 .val_expression => |i| {
289 try self.resolveCopyOnWrite(gpa);
290 const column = try self.getOrAddColumn(gpa, i.register);
291 column.rule = .{
292 .val_expression = i.block,
293 };
313 },
314 opcode: enum(u2) {
315 extended = 0,
316 advance_loc = 1,
317 offset = 2,
318 restore = 3,
319 },
320};
321
322pub const Instruction = union(enum) {
323 nop,
324 remember_state,
325 restore_state,
326 advance_loc: u32,
327 set_loc: u64,
328
329 register: struct {
330 index: u8,
331 rule: union(enum) {
332 restore, // restore from cie
333 undefined,
334 same_value,
335 offset_uf: u64,
336 offset_sf: i64,
337 val_offset_uf: u64,
338 val_offset_sf: i64,
339 register: u8,
340 /// Value is the number of bytes in the DWARF expression, which the caller must read.
341 expr: usize,
342 /// Value is the number of bytes in the DWARF expression, which the caller must read.
343 val_expr: usize,
294344 },
295 }
345 },
296346
297 return prev_row;
298}
347 def_cfa: struct {
348 register: u8,
349 offset: u64,
350 },
351 def_cfa_sf: struct {
352 register: u8,
353 offset_sf: i64,
354 },
355 def_cfa_reg: u8,
356 def_cfa_offset: u64,
357 def_cfa_offset_sf: i64,
358 /// Value is the number of bytes in the DWARF expression, which the caller must read.
359 def_cfa_expr: usize,
360
361 pub fn read(
362 reader: *std.Io.Reader,
363 addr_size_bytes: u8,
364 endian: std.builtin.Endian,
365 ) !Instruction {
366 const inst: OpcodeByte = @bitCast(try reader.takeByte());
367 return switch (inst.opcode) {
368 .advance_loc => .{ .advance_loc = inst.low.operand },
369 .offset => .{ .register = .{
370 .index = inst.low.operand,
371 .rule = .{ .offset_uf = try reader.takeLeb128(u64) },
372 } },
373 .restore => .{ .register = .{
374 .index = inst.low.operand,
375 .rule = .restore,
376 } },
377 .extended => switch (inst.low.extended) {
378 .nop => .nop,
379 .remember_state => .remember_state,
380 .restore_state => .restore_state,
381 .advance_loc1 => .{ .advance_loc = try reader.takeByte() },
382 .advance_loc2 => .{ .advance_loc = try reader.takeInt(u16, endian) },
383 .advance_loc4 => .{ .advance_loc = try reader.takeInt(u32, endian) },
384 .set_loc => .{ .set_loc = switch (addr_size_bytes) {
385 2 => try reader.takeInt(u16, endian),
386 4 => try reader.takeInt(u32, endian),
387 8 => try reader.takeInt(u64, endian),
388 else => return error.UnsupportedAddrSize,
389 } },
390
391 .offset_extended => .{ .register = .{
392 .index = try reader.takeLeb128(u8),
393 .rule = .{ .offset_uf = try reader.takeLeb128(u64) },
394 } },
395 .offset_extended_sf => .{ .register = .{
396 .index = try reader.takeLeb128(u8),
397 .rule = .{ .offset_sf = try reader.takeLeb128(i64) },
398 } },
399 .restore_extended => .{ .register = .{
400 .index = try reader.takeLeb128(u8),
401 .rule = .restore,
402 } },
403 .undefined => .{ .register = .{
404 .index = try reader.takeLeb128(u8),
405 .rule = .undefined,
406 } },
407 .same_value => .{ .register = .{
408 .index = try reader.takeLeb128(u8),
409 .rule = .same_value,
410 } },
411 .register => .{ .register = .{
412 .index = try reader.takeLeb128(u8),
413 .rule = .{ .register = try reader.takeLeb128(u8) },
414 } },
415 .val_offset => .{ .register = .{
416 .index = try reader.takeLeb128(u8),
417 .rule = .{ .val_offset_uf = try reader.takeLeb128(u64) },
418 } },
419 .val_offset_sf => .{ .register = .{
420 .index = try reader.takeLeb128(u8),
421 .rule = .{ .val_offset_sf = try reader.takeLeb128(i64) },
422 } },
423 .expression => .{ .register = .{
424 .index = try reader.takeLeb128(u8),
425 .rule = .{ .expr = try reader.takeLeb128(usize) },
426 } },
427 .val_expression => .{ .register = .{
428 .index = try reader.takeLeb128(u8),
429 .rule = .{ .val_expr = try reader.takeLeb128(usize) },
430 } },
431
432 .def_cfa => .{ .def_cfa = .{
433 .register = try reader.takeLeb128(u8),
434 .offset = try reader.takeLeb128(u64),
435 } },
436 .def_cfa_sf => .{ .def_cfa_sf = .{
437 .register = try reader.takeLeb128(u8),
438 .offset_sf = try reader.takeLeb128(i64),
439 } },
440 .def_cfa_register => .{ .def_cfa_reg = try reader.takeLeb128(u8) },
441 .def_cfa_offset => .{ .def_cfa_offset = try reader.takeLeb128(u64) },
442 .def_cfa_offset_sf => .{ .def_cfa_offset_sf = try reader.takeLeb128(i64) },
443 .def_cfa_expression => .{ .def_cfa_expr = try reader.takeLeb128(usize) },
444
445 _ => switch (@intFromEnum(inst.low.extended)) {
446 0x1C...0x3F => return error.UnimplementedUserOpcode,
447 else => return error.InvalidOpcode,
448 },
449 },
450 };
451 }
452};
299453
300454const std = @import("../../../std.zig");
301455const assert = std.debug.assert;
302456const Allocator = std.mem.Allocator;
303const Dwarf = std.debug.Dwarf;
457const Unwind = std.debug.Dwarf.Unwind;
304458
305459const VirtualMachine = @This();
lib/std/debug/Dwarf/call_frame.zig deleted-288
......@@ -1,288 +0,0 @@
1const std = @import("../../std.zig");
2const Reader = std.Io.Reader;
3
4/// TODO merge with std.dwarf.CFA
5const Opcode = enum(u8) {
6 advance_loc = 0x1 << 6,
7 offset = 0x2 << 6,
8 restore = 0x3 << 6,
9
10 nop = 0x00,
11 set_loc = 0x01,
12 advance_loc1 = 0x02,
13 advance_loc2 = 0x03,
14 advance_loc4 = 0x04,
15 offset_extended = 0x05,
16 restore_extended = 0x06,
17 undefined = 0x07,
18 same_value = 0x08,
19 register = 0x09,
20 remember_state = 0x0a,
21 restore_state = 0x0b,
22 def_cfa = 0x0c,
23 def_cfa_register = 0x0d,
24 def_cfa_offset = 0x0e,
25 def_cfa_expression = 0x0f,
26 expression = 0x10,
27 offset_extended_sf = 0x11,
28 def_cfa_sf = 0x12,
29 def_cfa_offset_sf = 0x13,
30 val_offset = 0x14,
31 val_offset_sf = 0x15,
32 val_expression = 0x16,
33
34 // These opcodes encode an operand in the lower 6 bits of the opcode itself
35 pub const lo_inline = @intFromEnum(Opcode.advance_loc);
36 pub const hi_inline = @intFromEnum(Opcode.restore) | 0b111111;
37
38 // These opcodes are trailed by zero or more operands
39 pub const lo_reserved = @intFromEnum(Opcode.nop);
40 pub const hi_reserved = @intFromEnum(Opcode.val_expression);
41
42 // Vendor-specific opcodes
43 pub const lo_user = 0x1c;
44 pub const hi_user = 0x3f;
45};
46
47/// The returned slice points into `reader.buffer`.
48fn readBlock(reader: *Reader) ![]const u8 {
49 const block_len = try reader.takeLeb128(usize);
50 return reader.take(block_len) catch |err| switch (err) {
51 error.EndOfStream => return error.InvalidOperand,
52 error.ReadFailed => |e| return e,
53 };
54}
55
56pub const Instruction = union(Opcode) {
57 advance_loc: struct {
58 delta: u8,
59 },
60 offset: struct {
61 register: u8,
62 offset: u64,
63 },
64 restore: struct {
65 register: u8,
66 },
67 nop: void,
68 set_loc: struct {
69 address: u64,
70 },
71 advance_loc1: struct {
72 delta: u8,
73 },
74 advance_loc2: struct {
75 delta: u16,
76 },
77 advance_loc4: struct {
78 delta: u32,
79 },
80 offset_extended: struct {
81 register: u8,
82 offset: u64,
83 },
84 restore_extended: struct {
85 register: u8,
86 },
87 undefined: struct {
88 register: u8,
89 },
90 same_value: struct {
91 register: u8,
92 },
93 register: struct {
94 register: u8,
95 target_register: u8,
96 },
97 remember_state: void,
98 restore_state: void,
99 def_cfa: struct {
100 register: u8,
101 offset: u64,
102 },
103 def_cfa_register: struct {
104 register: u8,
105 },
106 def_cfa_offset: struct {
107 offset: u64,
108 },
109 def_cfa_expression: struct {
110 block: []const u8,
111 },
112 expression: struct {
113 register: u8,
114 block: []const u8,
115 },
116 offset_extended_sf: struct {
117 register: u8,
118 offset: i64,
119 },
120 def_cfa_sf: struct {
121 register: u8,
122 offset: i64,
123 },
124 def_cfa_offset_sf: struct {
125 offset: i64,
126 },
127 val_offset: struct {
128 register: u8,
129 offset: u64,
130 },
131 val_offset_sf: struct {
132 register: u8,
133 offset: i64,
134 },
135 val_expression: struct {
136 register: u8,
137 block: []const u8,
138 },
139
140 /// `reader` must be a `Reader.fixed` so that regions of its buffer are never invalidated.
141 pub fn read(
142 reader: *Reader,
143 addr_size_bytes: u8,
144 endian: std.builtin.Endian,
145 ) !Instruction {
146 switch (try reader.takeByte()) {
147 Opcode.lo_inline...Opcode.hi_inline => |opcode| {
148 const e: Opcode = @enumFromInt(opcode & 0b11000000);
149 const value: u6 = @intCast(opcode & 0b111111);
150 return switch (e) {
151 .advance_loc => .{
152 .advance_loc = .{ .delta = value },
153 },
154 .offset => .{
155 .offset = .{
156 .register = value,
157 .offset = try reader.takeLeb128(u64),
158 },
159 },
160 .restore => .{
161 .restore = .{ .register = value },
162 },
163 else => unreachable,
164 };
165 },
166 Opcode.lo_reserved...Opcode.hi_reserved => |opcode| {
167 const e: Opcode = @enumFromInt(opcode);
168 return switch (e) {
169 .advance_loc,
170 .offset,
171 .restore,
172 => unreachable,
173 .nop => .{ .nop = {} },
174 .set_loc => .{ .set_loc = .{
175 .address = switch (addr_size_bytes) {
176 2 => try reader.takeInt(u16, endian),
177 4 => try reader.takeInt(u32, endian),
178 8 => try reader.takeInt(u64, endian),
179 else => return error.UnsupportedAddrSize,
180 },
181 } },
182 .advance_loc1 => .{
183 .advance_loc1 = .{ .delta = try reader.takeByte() },
184 },
185 .advance_loc2 => .{
186 .advance_loc2 = .{ .delta = try reader.takeInt(u16, endian) },
187 },
188 .advance_loc4 => .{
189 .advance_loc4 = .{ .delta = try reader.takeInt(u32, endian) },
190 },
191 .offset_extended => .{
192 .offset_extended = .{
193 .register = try reader.takeLeb128(u8),
194 .offset = try reader.takeLeb128(u64),
195 },
196 },
197 .restore_extended => .{
198 .restore_extended = .{
199 .register = try reader.takeLeb128(u8),
200 },
201 },
202 .undefined => .{
203 .undefined = .{
204 .register = try reader.takeLeb128(u8),
205 },
206 },
207 .same_value => .{
208 .same_value = .{
209 .register = try reader.takeLeb128(u8),
210 },
211 },
212 .register => .{
213 .register = .{
214 .register = try reader.takeLeb128(u8),
215 .target_register = try reader.takeLeb128(u8),
216 },
217 },
218 .remember_state => .{ .remember_state = {} },
219 .restore_state => .{ .restore_state = {} },
220 .def_cfa => .{
221 .def_cfa = .{
222 .register = try reader.takeLeb128(u8),
223 .offset = try reader.takeLeb128(u64),
224 },
225 },
226 .def_cfa_register => .{
227 .def_cfa_register = .{
228 .register = try reader.takeLeb128(u8),
229 },
230 },
231 .def_cfa_offset => .{
232 .def_cfa_offset = .{
233 .offset = try reader.takeLeb128(u64),
234 },
235 },
236 .def_cfa_expression => .{
237 .def_cfa_expression = .{
238 .block = try readBlock(reader),
239 },
240 },
241 .expression => .{
242 .expression = .{
243 .register = try reader.takeLeb128(u8),
244 .block = try readBlock(reader),
245 },
246 },
247 .offset_extended_sf => .{
248 .offset_extended_sf = .{
249 .register = try reader.takeLeb128(u8),
250 .offset = try reader.takeLeb128(i64),
251 },
252 },
253 .def_cfa_sf => .{
254 .def_cfa_sf = .{
255 .register = try reader.takeLeb128(u8),
256 .offset = try reader.takeLeb128(i64),
257 },
258 },
259 .def_cfa_offset_sf => .{
260 .def_cfa_offset_sf = .{
261 .offset = try reader.takeLeb128(i64),
262 },
263 },
264 .val_offset => .{
265 .val_offset = .{
266 .register = try reader.takeLeb128(u8),
267 .offset = try reader.takeLeb128(u64),
268 },
269 },
270 .val_offset_sf => .{
271 .val_offset_sf = .{
272 .register = try reader.takeLeb128(u8),
273 .offset = try reader.takeLeb128(i64),
274 },
275 },
276 .val_expression => .{
277 .val_expression = .{
278 .register = try reader.takeLeb128(u8),
279 .block = try readBlock(reader),
280 },
281 },
282 };
283 },
284 Opcode.lo_user...Opcode.hi_user => return error.UnimplementedUserOpcode,
285 else => return error.InvalidOpcode,
286 }
287 }
288};
lib/std/debug/SelfInfo.zig+178-163
......@@ -207,6 +207,36 @@ pub const DwarfUnwindContext = struct {
207207 vm: Dwarf.Unwind.VirtualMachine,
208208 stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }),
209209
210 pub const Cache = struct {
211 /// TODO: to allow `DwarfUnwindContext` to work on freestanding, we currently just don't use
212 /// this mutex there. That's a bad solution, but a better one depends on the standard
213 /// library's general support for "bring your own OS" being improved.
214 mutex: switch (builtin.os.tag) {
215 else => std.Thread.Mutex,
216 .freestanding, .other => struct {
217 fn lock(_: @This()) void {}
218 fn unlock(_: @This()) void {}
219 },
220 },
221 buf: [num_slots]Slot,
222 const num_slots = 2048;
223 const Slot = struct {
224 const max_regs = 32;
225 pc: usize,
226 cie: *const Dwarf.Unwind.CommonInformationEntry,
227 cfa_rule: Dwarf.Unwind.VirtualMachine.CfaRule,
228 rules_regs: [max_regs]u16,
229 rules: [max_regs]Dwarf.Unwind.VirtualMachine.RegisterRule,
230 num_rules: u8,
231 };
232 /// This is a function rather than a declaration to avoid lowering a very large struct value
233 /// into the binary when most of it is `undefined`.
234 pub fn init(c: *Cache) void {
235 c.mutex = .{};
236 for (&c.buf) |*slot| slot.pc = 0;
237 }
238 };
239
210240 pub fn init(cpu_context: *const CpuContext) DwarfUnwindContext {
211241 comptime assert(supports_unwinding);
212242
......@@ -243,126 +273,30 @@ pub const DwarfUnwindContext = struct {
243273 return ptr.*;
244274 }
245275
246 /// The default rule is typically equivalent to `.undefined`, but ABIs may define it differently.
247 fn defaultRuleBehavior(register: u8) enum { undefined, same_value } {
248 if (builtin.cpu.arch.isAARCH64() and register >= 19 and register <= 28) {
249 // The default rule for callee-saved registers on AArch64 acts like the `.same_value` rule
250 return .same_value;
251 }
252 return .undefined;
253 }
254
255 /// Resolves the register rule and places the result into `out` (see regBytes). Returns `true`
256 /// iff the rule was undefined. This is *not* the same as `col.rule == .undefined`, because the
257 /// default rule may be undefined.
258 pub fn resolveRegisterRule(
259 context: *DwarfUnwindContext,
260 gpa: Allocator,
261 col: Dwarf.Unwind.VirtualMachine.Column,
262 expression_context: std.debug.Dwarf.expression.Context,
263 out: []u8,
264 ) !bool {
265 switch (col.rule) {
266 .default => {
267 const register = col.register orelse return error.InvalidRegister;
268 switch (defaultRuleBehavior(register)) {
269 .undefined => {
270 @memset(out, undefined);
271 return true;
272 },
273 .same_value => {
274 const src = try context.cpu_context.dwarfRegisterBytes(register);
275 if (src.len != out.len) return error.RegisterSizeMismatch;
276 @memcpy(out, src);
277 return false;
278 },
279 }
280 },
281 .undefined => {
282 @memset(out, undefined);
283 return true;
284 },
285 .same_value => {
286 // TODO: This copy could be eliminated if callers always copy the state then call this function to update it
287 const register = col.register orelse return error.InvalidRegister;
288 const src = try context.cpu_context.dwarfRegisterBytes(register);
289 if (src.len != out.len) return error.RegisterSizeMismatch;
290 @memcpy(out, src);
291 return false;
292 },
293 .offset => |offset| {
294 const cfa = context.cfa orelse return error.InvalidCFA;
295 const addr = try applyOffset(cfa, offset);
296 const ptr: *const usize = @ptrFromInt(addr);
297 mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
298 return false;
299 },
300 .val_offset => |offset| {
301 const cfa = context.cfa orelse return error.InvalidCFA;
302 mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian);
303 return false;
304 },
305 .register => |register| {
306 const src = try context.cpu_context.dwarfRegisterBytes(register);
307 if (src.len != out.len) return error.RegisterSizeMismatch;
308 @memcpy(out, src);
309 return false;
310 },
311 .expression => |expression| {
312 context.stack_machine.reset();
313 const value = try context.stack_machine.run(
314 expression,
315 gpa,
316 expression_context,
317 context.cfa.?,
318 ) orelse return error.NoExpressionValue;
319 const addr = switch (value) {
320 .generic => |addr| addr,
321 else => return error.InvalidExpressionValue,
322 };
323 const ptr: *usize = @ptrFromInt(addr);
324 mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian);
325 return false;
326 },
327 .val_expression => |expression| {
328 context.stack_machine.reset();
329 const value = try context.stack_machine.run(
330 expression,
331 gpa,
332 expression_context,
333 context.cfa.?,
334 ) orelse return error.NoExpressionValue;
335 const val_raw = switch (value) {
336 .generic => |raw| raw,
337 else => return error.InvalidExpressionValue,
338 };
339 mem.writeInt(usize, out[0..@sizeOf(usize)], val_raw, native_endian);
340 return false;
341 },
342 .architectural => return error.UnimplementedRegisterRule,
343 }
344 }
345
346276 /// Unwind a stack frame using DWARF unwinding info, updating the register context.
347277 ///
348278 /// If `.eh_frame_hdr` is available and complete, it will be used to binary search for the FDE.
349279 /// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. The latter
350280 /// may require lazily loading the data in those sections.
351281 ///
352 /// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info
282 /// `explicit_fde_offset` is for cases where the FDE offset is known, such as when using macOS'
283 /// `__unwind_info` section.
353284 pub fn unwindFrame(
354285 context: *DwarfUnwindContext,
286 cache: *Cache,
355287 gpa: Allocator,
356288 unwind: *const Dwarf.Unwind,
357289 load_offset: usize,
358290 explicit_fde_offset: ?usize,
359291 ) Error!usize {
360 return unwindFrameInner(context, gpa, unwind, load_offset, explicit_fde_offset) catch |err| switch (err) {
361 error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory => |e| return e,
292 return unwindFrameInner(context, cache, gpa, unwind, load_offset, explicit_fde_offset) catch |err| switch (err) {
293 error.InvalidDebugInfo,
294 error.MissingDebugInfo,
295 error.UnsupportedDebugInfo,
296 error.OutOfMemory,
297 => |e| return e,
362298
363 error.UnimplementedRegisterRule,
364299 error.UnsupportedAddrSize,
365 error.UnsupportedDwarfVersion,
366300 error.UnimplementedUserOpcode,
367301 error.UnimplementedExpressionCall,
368302 error.UnimplementedOpcode,
......@@ -394,12 +328,12 @@ pub const DwarfUnwindContext = struct {
394328 error.InvalidExpressionValue,
395329 error.NoExpressionValue,
396330 error.RegisterSizeMismatch,
397 error.InvalidCFA,
398331 => return error.InvalidDebugInfo,
399332 };
400333 }
401334 fn unwindFrameInner(
402335 context: *DwarfUnwindContext,
336 cache: *Cache,
403337 gpa: Allocator,
404338 unwind: *const Dwarf.Unwind,
405339 load_offset: usize,
......@@ -411,57 +345,85 @@ pub const DwarfUnwindContext = struct {
411345
412346 const pc_vaddr = context.pc - load_offset;
413347
414 const fde_offset = explicit_fde_offset orelse try unwind.lookupPc(
415 pc_vaddr,
416 @sizeOf(usize),
417 native_endian,
418 ) orelse return error.MissingDebugInfo;
419 const format, const cie, const fde = try unwind.getFde(fde_offset, @sizeOf(usize), native_endian);
348 const cache_slot: Cache.Slot = slot: {
349 const slot_idx = std.hash.int(pc_vaddr) % Cache.num_slots;
420350
421 // Check if the FDE *actually* includes the pc (`lookupPc` can return false positives).
422 if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) {
423 return error.MissingDebugInfo;
424 }
351 {
352 cache.mutex.lock();
353 defer cache.mutex.unlock();
354 if (cache.buf[slot_idx].pc == pc_vaddr) break :slot cache.buf[slot_idx];
355 }
356
357 const fde_offset = explicit_fde_offset orelse try unwind.lookupPc(
358 pc_vaddr,
359 @sizeOf(usize),
360 native_endian,
361 ) orelse return error.MissingDebugInfo;
362 const cie, const fde = try unwind.getFde(fde_offset, native_endian);
425363
426 // Do not set `compile_unit` because the spec states that CFIs
427 // may not reference other debug sections anyway.
428 var expression_context: Dwarf.expression.Context = .{
429 .format = format,
430 .cpu_context = &context.cpu_context,
431 .cfa = context.cfa,
364 // Check if the FDE *actually* includes the pc (`lookupPc` can return false positives).
365 if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) {
366 return error.MissingDebugInfo;
367 }
368
369 context.vm.reset();
370
371 const row = try context.vm.runTo(gpa, pc_vaddr, cie, &fde, @sizeOf(usize), native_endian);
372
373 if (row.columns.len > Cache.Slot.max_regs) return error.UnsupportedDebugInfo;
374
375 var slot: Cache.Slot = .{
376 .pc = pc_vaddr,
377 .cie = cie,
378 .cfa_rule = row.cfa,
379 .rules_regs = undefined,
380 .rules = undefined,
381 .num_rules = 0,
382 };
383 for (context.vm.rowColumns(&row)) |col| {
384 const i = slot.num_rules;
385 slot.rules_regs[i] = col.register;
386 slot.rules[i] = col.rule;
387 slot.num_rules += 1;
388 }
389
390 {
391 cache.mutex.lock();
392 defer cache.mutex.unlock();
393 cache.buf[slot_idx] = slot;
394 }
395
396 break :slot slot;
432397 };
433398
434 context.vm.reset();
399 const format = cache_slot.cie.format;
400 const return_address_register = cache_slot.cie.return_address_register;
435401
436 const row = try context.vm.runTo(gpa, pc_vaddr, cie, fde, @sizeOf(usize), native_endian);
437 context.cfa = switch (row.cfa.rule) {
438 .val_offset => |offset| blk: {
439 const register = row.cfa.register orelse return error.InvalidCFARule;
440 const value = (try regNative(&context.cpu_context, register)).*;
441 break :blk try applyOffset(value, offset);
402 context.cfa = switch (cache_slot.cfa_rule) {
403 .none => return error.InvalidCFARule,
404 .reg_off => |ro| cfa: {
405 const ptr = try regNative(&context.cpu_context, ro.register);
406 break :cfa try applyOffset(ptr.*, ro.offset);
442407 },
443 .expression => |expr| blk: {
408 .expression => |expr| cfa: {
444409 context.stack_machine.reset();
445 const value = try context.stack_machine.run(
446 expr,
447 gpa,
448 expression_context,
449 context.cfa,
450 );
451
452 if (value) |v| {
453 if (v != .generic) return error.InvalidExpressionValue;
454 break :blk v.generic;
455 } else return error.NoExpressionValue;
410 const value = try context.stack_machine.run(expr, gpa, .{
411 .format = format,
412 .cpu_context = &context.cpu_context,
413 }, context.cfa) orelse return error.NoExpressionValue;
414 switch (value) {
415 .generic => |g| break :cfa g,
416 else => return error.InvalidExpressionValue,
417 }
456418 },
457 else => return error.InvalidCFARule,
458419 };
459420
460 expression_context.cfa = context.cfa;
461
462 // If the rule for the return address register is 'undefined', that indicates there is no
463 // return address, i.e. this is the end of the stack.
464 var explicit_has_return_address: ?bool = null;
421 // If unspecified, we'll use the default rule for the return address register, which is
422 // typically equivalent to `.undefined` (meaning there is no return address), but may be
423 // overriden by ABIs.
424 var has_return_address: bool = builtin.cpu.arch.isAARCH64() and
425 return_address_register >= 19 and
426 return_address_register <= 28;
465427
466428 // Create a copy of the CPU context, to which we will apply the new rules.
467429 var new_cpu_context = context.cpu_context;
......@@ -469,25 +431,78 @@ pub const DwarfUnwindContext = struct {
469431 // On all implemented architectures, the CFA is defined as being the previous frame's SP
470432 (try regNative(&new_cpu_context, sp_reg_num)).* = context.cfa.?;
471433
472 for (context.vm.rowColumns(row)) |column| {
473 if (column.register) |register| {
474 const dest = try new_cpu_context.dwarfRegisterBytes(register);
475 const rule_undef = try context.resolveRegisterRule(gpa, column, expression_context, dest);
476 if (register == cie.return_address_register) {
477 explicit_has_return_address = !rule_undef;
478 }
434 const rules_len = cache_slot.num_rules;
435 for (cache_slot.rules_regs[0..rules_len], cache_slot.rules[0..rules_len]) |register, rule| {
436 const new_val: union(enum) {
437 same,
438 undefined,
439 val: usize,
440 bytes: []const u8,
441 } = switch (rule) {
442 .default => val: {
443 // The default rule is typically equivalent to `.undefined`, but ABIs may override it.
444 if (builtin.cpu.arch.isAARCH64() and register >= 19 and register <= 28) {
445 break :val .same;
446 }
447 break :val .undefined;
448 },
449 .undefined => .undefined,
450 .same_value => .same,
451 .offset => |offset| val: {
452 const ptr: *const usize = @ptrFromInt(try applyOffset(context.cfa.?, offset));
453 break :val .{ .val = ptr.* };
454 },
455 .val_offset => |offset| .{ .val = try applyOffset(context.cfa.?, offset) },
456 .register => |r| .{ .bytes = try context.cpu_context.dwarfRegisterBytes(r) },
457 .expression => |expr| val: {
458 context.stack_machine.reset();
459 const value = try context.stack_machine.run(expr, gpa, .{
460 .format = format,
461 .cpu_context = &context.cpu_context,
462 }, context.cfa.?) orelse return error.NoExpressionValue;
463 const ptr: *const usize = switch (value) {
464 .generic => |addr| @ptrFromInt(addr),
465 else => return error.InvalidExpressionValue,
466 };
467 break :val .{ .val = ptr.* };
468 },
469 .val_expression => |expr| val: {
470 context.stack_machine.reset();
471 const value = try context.stack_machine.run(expr, gpa, .{
472 .format = format,
473 .cpu_context = &context.cpu_context,
474 }, context.cfa.?) orelse return error.NoExpressionValue;
475 switch (value) {
476 .generic => |val| break :val .{ .val = val },
477 else => return error.InvalidExpressionValue,
478 }
479 },
480 };
481 switch (new_val) {
482 .same => {},
483 .undefined => {
484 const dest = try new_cpu_context.dwarfRegisterBytes(@intCast(register));
485 @memset(dest, undefined);
486 },
487 .val => |val| {
488 const dest = try new_cpu_context.dwarfRegisterBytes(@intCast(register));
489 if (dest.len != @sizeOf(usize)) return error.RegisterSizeMismatch;
490 const dest_ptr: *align(1) usize = @ptrCast(dest);
491 dest_ptr.* = val;
492 },
493 .bytes => |src| {
494 const dest = try new_cpu_context.dwarfRegisterBytes(@intCast(register));
495 if (dest.len != src.len) return error.RegisterSizeMismatch;
496 @memcpy(dest, src);
497 },
498 }
499 if (register == return_address_register) {
500 has_return_address = new_val != .undefined;
479501 }
480502 }
481503
482 // If the return address register did not have an explicitly specified rules then it uses
483 // the default rule, which is usually equivalent to '.undefined', i.e. end-of-stack.
484 const has_return_address = explicit_has_return_address orelse switch (defaultRuleBehavior(cie.return_address_register)) {
485 .undefined => false,
486 .same_value => return error.InvalidDebugInfo, // this doesn't make sense, we would get stuck in an infinite loop
487 };
488
489504 const return_address: usize = if (has_return_address) pc: {
490 const raw_ptr = try regNative(&new_cpu_context, cie.return_address_register);
505 const raw_ptr = try regNative(&new_cpu_context, return_address_register);
491506 break :pc stripInstructionPtrAuthCode(raw_ptr.*);
492507 } else 0;
493508
......@@ -501,7 +516,7 @@ pub const DwarfUnwindContext = struct {
501516 // "return address" we have is the instruction which triggered the signal (if the signal
502517 // handler returned, the instruction would be re-run). Compensate for this by incrementing
503518 // the address in that case.
504 const adjusted_ret_addr = if (cie.is_signal_frame) return_address +| 1 else return_address;
519 const adjusted_ret_addr = if (cache_slot.cie.is_signal_frame) return_address +| 1 else return_address;
505520
506521 // We also want to do that same subtraction here to get the PC for the next frame's FDE.
507522 // This is because if the callee was noreturn, then the function call might be the caller's
lib/std/debug/SelfInfo/DarwinModule.zig+57-30
......@@ -20,7 +20,7 @@ pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) Error!DarwinM
2020 },
2121 }
2222}
23fn loadUnwindInfo(module: *const DarwinModule) DebugInfo.Unwind {
23fn loadUnwindInfo(module: *const DarwinModule, gpa: Allocator, out: *DebugInfo) !void {
2424 const header: *std.macho.mach_header = @ptrFromInt(module.text_base);
2525
2626 var it: macho.LoadCommandIterator = .{
......@@ -36,21 +36,57 @@ fn loadUnwindInfo(module: *const DarwinModule) DebugInfo.Unwind {
3636
3737 const vmaddr_slide = module.text_base - text_vmaddr;
3838
39 var unwind_info: ?[]const u8 = null;
40 var eh_frame: ?[]const u8 = null;
39 var opt_unwind_info: ?[]const u8 = null;
40 var opt_eh_frame: ?[]const u8 = null;
4141 for (sections) |sect| {
4242 if (mem.eql(u8, sect.sectName(), "__unwind_info")) {
4343 const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr)));
44 unwind_info = sect_ptr[0..@intCast(sect.size)];
44 opt_unwind_info = sect_ptr[0..@intCast(sect.size)];
4545 } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) {
4646 const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr)));
47 eh_frame = sect_ptr[0..@intCast(sect.size)];
47 opt_eh_frame = sect_ptr[0..@intCast(sect.size)];
4848 }
4949 }
50 return .{
50 const eh_frame = opt_eh_frame orelse {
51 out.unwind = .{
52 .vmaddr_slide = vmaddr_slide,
53 .unwind_info = opt_unwind_info,
54 .dwarf = null,
55 .dwarf_cache = undefined,
56 };
57 return;
58 };
59 var dwarf: Dwarf.Unwind = .initSection(.eh_frame, @intFromPtr(eh_frame.ptr) - vmaddr_slide, eh_frame);
60 errdefer dwarf.deinit(gpa);
61 // We don't need lookups, so this call is just for scanning CIEs.
62 dwarf.prepare(gpa, @sizeOf(usize), native_endian, false) catch |err| switch (err) {
63 error.ReadFailed => unreachable, // it's all fixed buffers
64 error.InvalidDebugInfo,
65 error.MissingDebugInfo,
66 error.OutOfMemory,
67 => |e| return e,
68 error.EndOfStream,
69 error.Overflow,
70 error.StreamTooLong,
71 error.InvalidOperand,
72 error.InvalidOpcode,
73 error.InvalidOperation,
74 => return error.InvalidDebugInfo,
75 error.UnsupportedAddrSize,
76 error.UnsupportedDwarfVersion,
77 error.UnimplementedUserOpcode,
78 => return error.UnsupportedDebugInfo,
79 };
80
81 const dwarf_cache = try gpa.create(UnwindContext.Cache);
82 errdefer gpa.destroy(dwarf_cache);
83 dwarf_cache.init();
84
85 out.unwind = .{
5186 .vmaddr_slide = vmaddr_slide,
52 .unwind_info = unwind_info,
53 .eh_frame = eh_frame,
87 .unwind_info = opt_unwind_info,
88 .dwarf = dwarf,
89 .dwarf_cache = dwarf_cache,
5490 };
5591}
5692fn loadMachO(module: *const DarwinModule, gpa: Allocator) !DebugInfo.LoadedMachO {
......@@ -350,10 +386,10 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
350386 };
351387}
352388fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
353 const unwind: *const DebugInfo.Unwind = u: {
389 const unwind: *DebugInfo.Unwind = u: {
354390 di.mutex.lock();
355391 defer di.mutex.unlock();
356 if (di.unwind == null) di.unwind = module.loadUnwindInfo();
392 if (di.unwind == null) try module.loadUnwindInfo(gpa, di);
357393 break :u &di.unwind.?;
358394 };
359395
......@@ -580,14 +616,8 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
580616 break :ip new_ip;
581617 },
582618 .DWARF => {
583 const eh_frame = unwind.eh_frame orelse return error.MissingDebugInfo;
584 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - unwind.vmaddr_slide;
585 return context.unwindFrame(
586 gpa,
587 &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),
588 unwind.vmaddr_slide,
589 @intCast(encoding.value.x86_64.dwarf),
590 );
619 const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo);
620 return context.unwindFrame(unwind.dwarf_cache, gpa, dwarf, unwind.vmaddr_slide, encoding.value.x86_64.dwarf);
591621 },
592622 },
593623 .aarch64, .aarch64_be => switch (encoding.mode.arm64) {
......@@ -600,14 +630,8 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
600630 break :ip new_ip;
601631 },
602632 .DWARF => {
603 const eh_frame = unwind.eh_frame orelse return error.MissingDebugInfo;
604 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - unwind.vmaddr_slide;
605 return context.unwindFrame(
606 gpa,
607 &.initSection(.eh_frame, eh_frame_vaddr, eh_frame),
608 unwind.vmaddr_slide,
609 @intCast(encoding.value.x86_64.dwarf),
610 );
633 const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo);
634 return context.unwindFrame(unwind.dwarf_cache, gpa, dwarf, unwind.vmaddr_slide, encoding.value.arm64.dwarf);
611635 },
612636 .FRAME => ip: {
613637 const frame = encoding.value.arm64.frame;
......@@ -691,12 +715,15 @@ pub const DebugInfo = struct {
691715 }
692716
693717 const Unwind = struct {
694 /// The slide applied to the following sections. So, `unwind_info.ptr` is this many bytes
695 /// higher than the vmaddr of `__unwind_info`, and likewise for `__eh_frame`.
718 /// The slide applied to the `__unwind_info` and `__eh_frame` sections.
719 /// So, `unwind_info.ptr` is this many bytes higher than the section's vmaddr.
696720 vmaddr_slide: u64,
697 // Backed by the in-memory sections mapped by the loader
721 /// Backed by the in-memory section mapped by the loader.
698722 unwind_info: ?[]const u8,
699 eh_frame: ?[]const u8,
723 /// Backed by the in-memory `__eh_frame` section mapped by the loader.
724 dwarf: ?Dwarf.Unwind,
725 /// This is `undefined` if `dwarf == null`.
726 dwarf_cache: *UnwindContext.Cache,
700727 };
701728
702729 const LoadedMachO = struct {
lib/std/debug/SelfInfo/ElfModule.zig+118-71
......@@ -3,8 +3,22 @@ name: []const u8,
33build_id: ?[]const u8,
44gnu_eh_frame: ?[]const u8,
55
6/// No cache needed, because `dl_iterate_phdr` is already fast.
7pub const LookupCache = void;
6pub const LookupCache = struct {
7 rwlock: std.Thread.RwLock,
8 ranges: std.ArrayList(Range),
9 const Range = struct {
10 start: usize,
11 len: usize,
12 mod: ElfModule,
13 };
14 pub const init: LookupCache = .{
15 .rwlock = .{},
16 .ranges = .empty,
17 };
18 pub fn deinit(lc: *LookupCache, gpa: Allocator) void {
19 lc.ranges.deinit(gpa);
20 }
21};
822
923pub const DebugInfo = struct {
1024 /// Held while checking and/or populating `loaded_elf`/`scanned_dwarf`/`unwind`.
......@@ -14,18 +28,24 @@ pub const DebugInfo = struct {
1428
1529 loaded_elf: ?ElfFile,
1630 scanned_dwarf: bool,
17 unwind: [2]?Dwarf.Unwind,
31 unwind: if (supports_unwinding) [2]?Dwarf.Unwind else void,
32 unwind_cache: if (supports_unwinding) *UnwindContext.Cache else void,
33
1834 pub const init: DebugInfo = .{
1935 .mutex = .{},
2036 .loaded_elf = null,
2137 .scanned_dwarf = false,
22 .unwind = @splat(null),
38 .unwind = if (supports_unwinding) @splat(null),
39 .unwind_cache = undefined,
2340 };
2441 pub fn deinit(di: *DebugInfo, gpa: Allocator) void {
2542 if (di.loaded_elf) |*loaded_elf| loaded_elf.deinit(gpa);
26 for (&di.unwind) |*opt_unwind| {
27 const unwind = &(opt_unwind.* orelse continue);
28 unwind.deinit(gpa);
43 if (supports_unwinding) {
44 if (di.unwind[0] != null) gpa.destroy(di.unwind_cache);
45 for (&di.unwind) |*opt_unwind| {
46 const unwind = &(opt_unwind.* orelse continue);
47 unwind.deinit(gpa);
48 }
2949 }
3050 }
3151};
......@@ -34,75 +54,84 @@ pub fn key(m: ElfModule) usize {
3454 return m.load_offset;
3555}
3656pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) Error!ElfModule {
37 _ = cache;
38 _ = gpa;
39 const DlIterContext = struct {
40 /// input
41 address: usize,
42 /// output
43 module: ElfModule,
57 if (lookupInCache(cache, address)) |m| return m;
4458
45 fn callback(info: *std.posix.dl_phdr_info, size: usize, context: *@This()) !void {
46 _ = size;
47 // The base address is too high
48 if (context.address < info.addr)
49 return;
59 {
60 // Check a new module hasn't been loaded
61 cache.rwlock.lock();
62 defer cache.rwlock.unlock();
63 const DlIterContext = struct {
64 ranges: *std.ArrayList(LookupCache.Range),
65 gpa: Allocator,
5066
51 const phdrs = info.phdr[0..info.phnum];
52 for (phdrs) |*phdr| {
53 if (phdr.p_type != elf.PT_LOAD) continue;
67 fn callback(info: *std.posix.dl_phdr_info, size: usize, context: *@This()) !void {
68 _ = size;
5469
55 // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000
56 const seg_start = info.addr +% phdr.p_vaddr;
57 const seg_end = seg_start + phdr.p_memsz;
58 if (context.address >= seg_start and context.address < seg_end) {
59 context.module = .{
60 .load_offset = info.addr,
61 // Android libc uses NULL instead of "" to mark the main program
62 .name = mem.sliceTo(info.name, 0) orelse "",
63 .build_id = null,
64 .gnu_eh_frame = null,
65 };
66 break;
70 var mod: ElfModule = .{
71 .load_offset = info.addr,
72 // Android libc uses NULL instead of "" to mark the main program
73 .name = mem.sliceTo(info.name, 0) orelse "",
74 .build_id = null,
75 .gnu_eh_frame = null,
76 };
77
78 // Populate `build_id` and `gnu_eh_frame`
79 for (info.phdr[0..info.phnum]) |phdr| {
80 switch (phdr.p_type) {
81 elf.PT_NOTE => {
82 // Look for .note.gnu.build-id
83 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
84 var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]);
85 const name_size = r.takeInt(u32, native_endian) catch continue;
86 const desc_size = r.takeInt(u32, native_endian) catch continue;
87 const note_type = r.takeInt(u32, native_endian) catch continue;
88 const name = r.take(name_size) catch continue;
89 if (note_type != elf.NT_GNU_BUILD_ID) continue;
90 if (!mem.eql(u8, name, "GNU\x00")) continue;
91 const desc = r.take(desc_size) catch continue;
92 mod.build_id = desc;
93 },
94 elf.PT_GNU_EH_FRAME => {
95 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
96 mod.gnu_eh_frame = segment_ptr[0..phdr.p_memsz];
97 },
98 else => {},
99 }
67100 }
68 } else return;
69101
70 for (info.phdr[0..info.phnum]) |phdr| {
71 switch (phdr.p_type) {
72 elf.PT_NOTE => {
73 // Look for .note.gnu.build-id
74 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
75 var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]);
76 const name_size = r.takeInt(u32, native_endian) catch continue;
77 const desc_size = r.takeInt(u32, native_endian) catch continue;
78 const note_type = r.takeInt(u32, native_endian) catch continue;
79 const name = r.take(name_size) catch continue;
80 if (note_type != elf.NT_GNU_BUILD_ID) continue;
81 if (!mem.eql(u8, name, "GNU\x00")) continue;
82 const desc = r.take(desc_size) catch continue;
83 context.module.build_id = desc;
84 },
85 elf.PT_GNU_EH_FRAME => {
86 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);
87 context.module.gnu_eh_frame = segment_ptr[0..phdr.p_memsz];
88 },
89 else => {},
102 // Now that `mod` is populated, create the ranges
103 for (info.phdr[0..info.phnum]) |phdr| {
104 if (phdr.p_type != elf.PT_LOAD) continue;
105 try context.ranges.append(context.gpa, .{
106 // Overflowing addition handles VSDOs having p_vaddr = 0xffffffffff700000
107 .start = info.addr +% phdr.p_vaddr,
108 .len = phdr.p_memsz,
109 .mod = mod,
110 });
90111 }
91112 }
113 };
114 cache.ranges.clearRetainingCapacity();
115 var ctx: DlIterContext = .{
116 .ranges = &cache.ranges,
117 .gpa = gpa,
118 };
119 try std.posix.dl_iterate_phdr(&ctx, error{OutOfMemory}, DlIterContext.callback);
120 }
92121
93 // Stop the iteration
94 return error.Found;
95 }
96 };
97 var ctx: DlIterContext = .{
98 .address = address,
99 .module = undefined,
100 };
101 std.posix.dl_iterate_phdr(&ctx, error{Found}, DlIterContext.callback) catch |err| switch (err) {
102 error.Found => return ctx.module,
103 };
122 if (lookupInCache(cache, address)) |m| return m;
104123 return error.MissingDebugInfo;
105124}
125fn lookupInCache(cache: *LookupCache, address: usize) ?ElfModule {
126 cache.rwlock.lockShared();
127 defer cache.rwlock.unlockShared();
128 for (cache.ranges.items) |*range| {
129 if (address >= range.start and address < range.start + range.len) {
130 return range.mod;
131 }
132 }
133 return null;
134}
106135fn loadElf(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void {
107136 std.debug.assert(di.loaded_elf == null);
108137 std.debug.assert(!di.scanned_dwarf);
......@@ -199,11 +228,23 @@ pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugIn
199228 };
200229}
201230fn prepareUnwindLookup(unwind: *Dwarf.Unwind, gpa: Allocator) Error!void {
202 unwind.prepareLookup(gpa, @sizeOf(usize), native_endian) catch |err| switch (err) {
231 unwind.prepare(gpa, @sizeOf(usize), native_endian, true) catch |err| switch (err) {
203232 error.ReadFailed => unreachable, // it's all fixed buffers
204 error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory => |e| return e,
205 error.EndOfStream, error.Overflow, error.StreamTooLong => return error.InvalidDebugInfo,
206 error.UnsupportedAddrSize, error.UnsupportedDwarfVersion => return error.UnsupportedDebugInfo,
233 error.InvalidDebugInfo,
234 error.MissingDebugInfo,
235 error.OutOfMemory,
236 => |e| return e,
237 error.EndOfStream,
238 error.Overflow,
239 error.StreamTooLong,
240 error.InvalidOperand,
241 error.InvalidOpcode,
242 error.InvalidOperation,
243 => return error.InvalidDebugInfo,
244 error.UnsupportedAddrSize,
245 error.UnsupportedDwarfVersion,
246 error.UnimplementedUserOpcode,
247 => return error.UnsupportedDebugInfo,
207248 };
208249}
209250fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void {
......@@ -240,12 +281,18 @@ fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Erro
240281 };
241282 errdefer for (unwinds) |*u| u.deinit(gpa);
242283 for (unwinds) |*u| try prepareUnwindLookup(u, gpa);
284
285 const unwind_cache = try gpa.create(UnwindContext.Cache);
286 errdefer gpa.destroy(unwind_cache);
287 unwind_cache.init();
288
243289 switch (unwinds.len) {
244290 0 => unreachable,
245291 1 => di.unwind = .{ unwinds[0], null },
246292 2 => di.unwind = .{ unwinds[0], unwinds[1] },
247293 else => unreachable,
248294 }
295 di.unwind_cache = unwind_cache;
249296}
250297pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize {
251298 const unwinds: *const [2]?Dwarf.Unwind = u: {
......@@ -257,7 +304,7 @@ pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, con
257304 };
258305 for (unwinds) |*opt_unwind| {
259306 const unwind = &(opt_unwind.* orelse break);
260 return context.unwindFrame(gpa, unwind, module.load_offset, null) catch |err| switch (err) {
307 return context.unwindFrame(di.unwind_cache, gpa, unwind, module.load_offset, null) catch |err| switch (err) {
261308 error.MissingDebugInfo => continue, // try the next one
262309 else => |e| return e,
263310 };