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)...@@ -572,9 +572,12 @@ pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize)
572 defer it.deinit();572 defer it.deinit();
573 if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace;573 if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace;
574 var total_frames: usize = 0;574 var total_frames: usize = 0;
575 var frame_idx: usize = 0;575 var index: usize = 0;
576 var wait_for = options.first_address;576 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()) {
578 .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break,581 .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break,
579 .end => break,582 .end => break,
580 .frame => |ret_addr| {583 .frame => |ret_addr| {
...@@ -588,13 +591,13 @@ pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize)...@@ -588,13 +591,13 @@ pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize)
588 if (ret_addr != target) continue;591 if (ret_addr != target) continue;
589 wait_for = null;592 wait_for = null;
590 }593 }
591 if (frame_idx < addr_buf.len) addr_buf[frame_idx] = ret_addr;594 addr_buf[index] = ret_addr;
592 frame_idx += 1;595 index += 1;
593 },596 },
594 };597 };
595 return .{598 return .{
596 .index = frame_idx,599 .index = index,
597 .instruction_addresses = addr_buf[0..@min(frame_idx, addr_buf.len)],600 .instruction_addresses = addr_buf[0..index],
598 };601 };
599}602}
600/// Write the current stack trace to `writer`, annotated with source locations.603/// 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;...@@ -27,7 +27,6 @@ const Reader = std.Io.Reader;
27const Dwarf = @This();27const Dwarf = @This();
2828
29pub const expression = @import("Dwarf/expression.zig");29pub const expression = @import("Dwarf/expression.zig");
30pub const call_frame = @import("Dwarf/call_frame.zig");
31pub const Unwind = @import("Dwarf/Unwind.zig");30pub const Unwind = @import("Dwarf/Unwind.zig");
3231
33/// Useful to temporarily enable while working on this file.32/// Useful to temporarily enable while working on this file.
lib/std/debug/Dwarf/Unwind.zig+95-50
...@@ -10,7 +10,7 @@...@@ -10,7 +10,7 @@
10//! The typical usage of `Unwind` is as follows:10//! The typical usage of `Unwind` is as follows:
11//!11//!
12//! * Initialize with `initEhFrameHdr` or `initSection`, depending on the available data12//! * Initialize with `initEhFrameHdr` or `initSection`, depending on the available data
13//! * Call `prepareLookup` to construct a search table if necessary13//! * 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 PC14//! * Call `lookupPc` to find the section offset of the FDE corresponding to a PC
15//! * Call `getFde` to load the corresponding FDE and CIE15//! * 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)16//! * Check that the PC does indeed fall in that range (`lookupPc` may return a false positive)
...@@ -18,7 +18,7 @@...@@ -18,7 +18,7 @@
18//!18//!
19//! In some cases, such as when using the "compact unwind" data in Mach-O binaries, the FDE offsets19//! 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 to20//! 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
23pub const VirtualMachine = @import("Unwind/VirtualMachine.zig");23pub const VirtualMachine = @import("Unwind/VirtualMachine.zig");
2424
...@@ -45,7 +45,7 @@ frame_section: struct {...@@ -45,7 +45,7 @@ frame_section: struct {
4545
46/// A structure allowing fast lookups of the FDE corresponding to a particular PC. We use a binary46/// 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` means47/// 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`.
49lookup: ?union(enum) {49lookup: ?union(enum) {
50 /// The `.eh_frame_hdr` section contains a pre-computed search table which we can use.50 /// The `.eh_frame_hdr` section contains a pre-computed search table which we can use.
51 eh_frame_hdr: struct {51 eh_frame_hdr: struct {
...@@ -58,6 +58,12 @@ lookup: ?union(enum) {...@@ -58,6 +58,12 @@ lookup: ?union(enum) {
58 sorted_fdes: []SortedFdeEntry,58 sorted_fdes: []SortedFdeEntry,
59},59},
6060
61/// Initially empty; populated by `prepare`.
62cie_list: std.MultiArrayList(struct {
63 offset: u64,
64 cie: CommonInformationEntry,
65}),
66
61const SortedFdeEntry = struct {67const SortedFdeEntry = struct {
62 /// This FDE's value of `pc_begin`.68 /// This FDE's value of `pc_begin`.
63 pc_begin: u64,69 pc_begin: u64,
...@@ -83,6 +89,7 @@ pub fn initEhFrameHdr(header: EhFrameHeader, section_vaddr: u64, section_bytes_p...@@ -83,6 +89,7 @@ pub fn initEhFrameHdr(header: EhFrameHeader, section_vaddr: u64, section_bytes_p
83 .vaddr = section_vaddr,89 .vaddr = section_vaddr,
84 .table = table,90 .table = table,
85 } } else null,91 } } else null,
92 .cie_list = .empty,
86 };93 };
87}94}
8895
...@@ -98,16 +105,21 @@ pub fn initSection(section: Section, section_vaddr: u64, section_bytes: []const...@@ -98,16 +105,21 @@ pub fn initSection(section: Section, section_vaddr: u64, section_bytes: []const
98 .vaddr = section_vaddr,105 .vaddr = section_vaddr,
99 },106 },
100 .lookup = null,107 .lookup = null,
108 .cie_list = .empty,
101 };109 };
102}110}
103111
104/// Technically, it is only necessary to call this if `prepareLookup` has previously been called,
105/// since no other function here allocates resources.
106pub fn deinit(unwind: *Unwind, gpa: Allocator) void {112pub fn deinit(unwind: *Unwind, gpa: Allocator) void {
107 if (unwind.lookup) |lookup| switch (lookup) {113 if (unwind.lookup) |lookup| switch (lookup) {
108 .eh_frame_hdr => {},114 .eh_frame_hdr => {},
109 .sorted_fdes => |fdes| gpa.free(fdes),115 .sorted_fdes => |fdes| gpa.free(fdes),
110 };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);
111}123}
112124
113/// Decoded version of the `.eh_frame_hdr` section.125/// Decoded version of the `.eh_frame_hdr` section.
...@@ -236,7 +248,6 @@ const EntryHeader = union(enum) {...@@ -236,7 +248,6 @@ const EntryHeader = union(enum) {
236 bytes_len: u64,248 bytes_len: u64,
237 },249 },
238 fde: struct {250 fde: struct {
239 format: Format,
240 /// Offset into the section of the corresponding CIE, *including* its entry header.251 /// Offset into the section of the corresponding CIE, *including* its entry header.
241 cie_offset: u64,252 cie_offset: u64,
242 /// Remaining bytes in the FDE. These are parseable by `FrameDescriptionEntry.parse`.253 /// Remaining bytes in the FDE. These are parseable by `FrameDescriptionEntry.parse`.
...@@ -290,7 +301,6 @@ const EntryHeader = union(enum) {...@@ -290,7 +301,6 @@ const EntryHeader = union(enum) {
290 .debug_frame => cie_ptr_or_id,301 .debug_frame => cie_ptr_or_id,
291 };302 };
292 return .{ .fde = .{303 return .{ .fde = .{
293 .format = unit_header.format,
294 .cie_offset = cie_offset,304 .cie_offset = cie_offset,
295 .bytes_len = remaining_bytes,305 .bytes_len = remaining_bytes,
296 } };306 } };
...@@ -299,6 +309,7 @@ const EntryHeader = union(enum) {...@@ -299,6 +309,7 @@ const EntryHeader = union(enum) {
299309
300pub const CommonInformationEntry = struct {310pub const CommonInformationEntry = struct {
301 version: u8,311 version: u8,
312 format: Format,
302313
303 /// In version 4, CIEs can specify the address size used in the CIE and associated FDEs.314 /// In version 4, CIEs can specify the address size used in the CIE and associated FDEs.
304 /// This value must be used *only* to parse associated FDEs in `FrameDescriptionEntry.parse`.315 /// This value must be used *only* to parse associated FDEs in `FrameDescriptionEntry.parse`.
...@@ -318,6 +329,12 @@ pub const CommonInformationEntry = struct {...@@ -318,6 +329,12 @@ pub const CommonInformationEntry = struct {
318329
319 initial_instructions: []const u8,330 initial_instructions: []const u8,
320331
332 last_row: ?struct {
333 offset: u64,
334 cfa: VirtualMachine.CfaRule,
335 cols: []VirtualMachine.Column,
336 },
337
321 pub const AugmentationKind = enum { none, gcc_eh, lsb_z };338 pub const AugmentationKind = enum { none, gcc_eh, lsb_z };
322339
323 /// This function expects to read the CIE starting with the version field.340 /// This function expects to read the CIE starting with the version field.
...@@ -326,6 +343,7 @@ pub const CommonInformationEntry = struct {...@@ -326,6 +343,7 @@ pub const CommonInformationEntry = struct {
326 /// `length_offset` specifies the offset of this CIE's length field in the343 /// `length_offset` specifies the offset of this CIE's length field in the
327 /// .eh_frame / .debug_frame section.344 /// .eh_frame / .debug_frame section.
328 fn parse(345 fn parse(
346 format: Format,
329 cie_bytes: []const u8,347 cie_bytes: []const u8,
330 section: Section,348 section: Section,
331 default_addr_size_bytes: u8,349 default_addr_size_bytes: u8,
...@@ -384,6 +402,7 @@ pub const CommonInformationEntry = struct {...@@ -384,6 +402,7 @@ pub const CommonInformationEntry = struct {
384 };402 };
385403
386 return .{404 return .{
405 .format = format,
387 .version = version,406 .version = version,
388 .addr_size_bytes = addr_size_bytes,407 .addr_size_bytes = addr_size_bytes,
389 .segment_selector_size = segment_selector_size,408 .segment_selector_size = segment_selector_size,
...@@ -394,6 +413,7 @@ pub const CommonInformationEntry = struct {...@@ -394,6 +413,7 @@ pub const CommonInformationEntry = struct {
394 .is_signal_frame = is_signal_frame,413 .is_signal_frame = is_signal_frame,
395 .augmentation_kind = aug_kind,414 .augmentation_kind = aug_kind,
396 .initial_instructions = r.buffered(),415 .initial_instructions = r.buffered(),
416 .last_row = null,
397 };417 };
398 }418 }
399};419};
...@@ -411,7 +431,7 @@ pub const FrameDescriptionEntry = struct {...@@ -411,7 +431,7 @@ pub const FrameDescriptionEntry = struct {
411 /// module's `.eh_frame` section, this will equal `fde_bytes.ptr`.431 /// module's `.eh_frame` section, this will equal `fde_bytes.ptr`.
412 fde_vaddr: u64,432 fde_vaddr: u64,
413 fde_bytes: []const u8,433 fde_bytes: []const u8,
414 cie: CommonInformationEntry,434 cie: *const CommonInformationEntry,
415 endian: Endian,435 endian: Endian,
416 ) !FrameDescriptionEntry {436 ) !FrameDescriptionEntry {
417 if (cie.segment_selector_size != 0) return error.UnsupportedAddrSize;437 if (cie.segment_selector_size != 0) return error.UnsupportedAddrSize;
...@@ -446,11 +466,18 @@ pub const FrameDescriptionEntry = struct {...@@ -446,11 +466,18 @@ pub const FrameDescriptionEntry = struct {
446 }466 }
447};467};
448468
449/// Builds the PC FDE lookup table if it is not already built. It is required to call this function469/// Builds the CIE list and FDE lookup table if they are not already built. It is required to call
450/// at least once before calling `lookupPc`. Once this function is called, memory has been allocated470/// this function at least once before calling `lookupPc` or `getFde`. If only `getFde` is needed,
451/// and so `deinit` (matching this `gpa`) is required to free it.471/// then `need_lookup` can be set to `false` to make this function more efficient.
452pub fn prepareLookup(unwind: *Unwind, gpa: Allocator, addr_size_bytes: u8, endian: Endian) !void {472pub fn prepare(
453 if (unwind.lookup != null) return;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
455 const section = unwind.frame_section;482 const section = unwind.frame_section;
456483
...@@ -462,21 +489,28 @@ pub fn prepareLookup(unwind: *Unwind, gpa: Allocator, addr_size_bytes: u8, endia...@@ -462,21 +489,28 @@ pub fn prepareLookup(unwind: *Unwind, gpa: Allocator, addr_size_bytes: u8, endia
462 const entry_offset = r.seek;489 const entry_offset = r.seek;
463 switch (try EntryHeader.read(&r, entry_offset, section.id, endian)) {490 switch (try EntryHeader.read(&r, entry_offset, section.id, endian)) {
464 .cie => |cie_info| {491 .cie => |cie_info| {
465 // Ignore CIEs for now; we'll parse them when we read a corresponding FDE492 // We will pre-populate a list of CIEs for efficiency: this avoids work re-parsing
466 try r.discardAll(cast(usize, cie_info.bytes_len) orelse return error.EndOfStream);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);
467 continue;504 continue;
468 },505 },
469 .fde => |fde_info| {506 .fde => |fde_info| {
470 if (fde_info.cie_offset > section.bytes.len) return error.EndOfStream;507 const bytes_len = cast(usize, fde_info.bytes_len) orelse return error.EndOfStream;
471 var cie_r: Reader = .fixed(section.bytes[@intCast(fde_info.cie_offset)..]);508 if (!need_lookup) {
472 const cie_info = switch (try EntryHeader.read(&cie_r, fde_info.cie_offset, section.id, endian)) {509 try r.discardAll(bytes_len);
473 .cie => |cie_info| cie_info,510 continue;
474 .fde, .terminator => return bad(), // this is meant to be a CIE511 }
475 };512 const cie = unwind.findCie(fde_info.cie_offset) orelse return error.InvalidDebugInfo;
476 const cie_bytes_len = cast(usize, cie_info.bytes_len) orelse return error.EndOfStream;513 const fde: FrameDescriptionEntry = try .parse(section.vaddr + r.seek, try r.take(bytes_len), cie, endian);
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);
480 try fde_list.append(gpa, .{514 try fde_list.append(gpa, .{
481 .pc_begin = fde.pc_begin,515 .pc_begin = fde.pc_begin,
482 .fde_offset = entry_offset,516 .fde_offset = entry_offset,
...@@ -502,12 +536,30 @@ pub fn prepareLookup(unwind: *Unwind, gpa: Allocator, addr_size_bytes: u8, endia...@@ -502,12 +536,30 @@ pub fn prepareLookup(unwind: *Unwind, gpa: Allocator, addr_size_bytes: u8, endia
502 unwind.lookup = .{ .sorted_fdes = final_fdes };536 unwind.lookup = .{ .sorted_fdes = final_fdes };
503}537}
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
505/// Given a program counter value, returns the offset of the corresponding FDE, or `null` if no557/// Given a program counter value, returns the offset of the corresponding FDE, or `null` if no
506/// matching FDE was found. The returned offset can be passed to `getFde` to load the data558/// matching FDE was found. The returned offset can be passed to `getFde` to load the data
507/// associated with the FDE.559/// associated with the FDE.
508///560///
509/// Before calling this function, `prepareLookup` must return successfully at least once, to ensure561/// Before calling this function, `prepare` must return successfully at least once, to ensure that
510/// that `unwind.lookup` is populated.562/// `unwind.lookup` is populated.
511///563///
512/// The return value may be a false positive. After loading the FDE with `loadFde`, the caller must564/// The return value may be a false positive. After loading the FDE with `loadFde`, the caller must
513/// validate that `pc` is indeed in its range -- if it is not, then no FDE matches `pc`.565/// 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...@@ -524,20 +576,25 @@ pub fn lookupPc(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian: End
524 },576 },
525 .sorted_fdes => |sorted_fdes| sorted_fdes,577 .sorted_fdes => |sorted_fdes| sorted_fdes,
526 };578 };
527 const first_bad_idx = std.sort.partitionPoint(SortedFdeEntry, sorted_fdes, pc, struct {579 if (sorted_fdes.len == 0) return null;
528 fn canIncludePc(target_pc: u64, entry: SortedFdeEntry) bool {580 var start: usize = 0;
529 return target_pc >= entry.pc_begin; // i.e. does 'entry_pc..<last pc>' include 'target_pc'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;
530 }589 }
531 }.canIncludePc);590 }
532 // `first_bad_idx` is the index of the first FDE whose `pc_begin` is too high to include `pc`.591 // If any FDE matches, it'll be the one at `start` (maybe false positive).
533 // So if any FDE matches, it'll be the one at `first_bad_idx - 1` (maybe false positive).592 return sorted_fdes[start].fde_offset;
534 if (first_bad_idx == 0) return null;
535 return sorted_fdes[first_bad_idx - 1].fde_offset;
536}593}
537594
538/// Get the FDE at a given offset, as well as its associated CIE. This offset typically comes from595/// Get the FDE at a given offset, as well as its associated CIE. This offset typically comes from
539/// `lookupPc`. The CFI instructions within can be evaluated with `VirtualMachine`.596/// `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 } {
541 const section = unwind.frame_section;598 const section = unwind.frame_section;
542599
543 if (fde_offset > section.bytes.len) return error.EndOfStream;600 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...@@ -547,19 +604,7 @@ pub fn getFde(unwind: *const Unwind, fde_offset: u64, addr_size_bytes: u8, endia
547 .cie, .terminator => return bad(), // This is meant to be an FDE604 .cie, .terminator => return bad(), // This is meant to be an FDE
548 };605 };
549606
550 const cie_offset = fde_info.cie_offset;607 const cie = unwind.findCie(fde_info.cie_offset) orelse return error.InvalidDebugInfo;
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 );
563 const fde: FrameDescriptionEntry = try .parse(608 const fde: FrameDescriptionEntry = try .parse(
564 section.vaddr + fde_offset + fde_reader.seek,609 section.vaddr + fde_offset + fde_reader.seek,
565 try fde_reader.take(cast(usize, fde_info.bytes_len) orelse return error.EndOfStream),610 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...@@ -567,7 +612,7 @@ pub fn getFde(unwind: *const Unwind, fde_offset: u64, addr_size_bytes: u8, endia
567 endian,612 endian,
568 );613 );
569614
570 return .{ cie_info.format, cie, fde };615 return .{ cie, fde };
571}616}
572617
573const EhPointerContext = struct {618const EhPointerContext = struct {
lib/std/debug/Dwarf/Unwind/VirtualMachine.zig+354-200
...@@ -5,9 +5,9 @@ pub const RegisterRule = union(enum) {...@@ -5,9 +5,9 @@ pub const RegisterRule = union(enum) {
5 /// The spec says that the default rule for each column is the undefined rule.5 /// The spec says that the default rule for each column is the undefined rule.
6 /// However, it also allows ABI / compiler authors to specify alternate defaults, so6 /// However, it also allows ABI / compiler authors to specify alternate defaults, so
7 /// there is a distinction made here.7 /// there is a distinction made here.
8 default: void,8 default,
9 undefined: void,9 undefined,
10 same_value: void,10 same_value,
11 /// offset(N)11 /// offset(N)
12 offset: i64,12 offset: i64,
13 /// val_offset(N)13 /// val_offset(N)
...@@ -18,38 +18,39 @@ pub const RegisterRule = union(enum) {...@@ -18,38 +18,39 @@ pub const RegisterRule = union(enum) {
18 expression: []const u8,18 expression: []const u8,
19 /// val_expression(E)19 /// val_expression(E)
20 val_expression: []const u8,20 val_expression: []const u8,
21 /// Augmenter-defined rule21};
22 architectural: void,22
23pub const CfaRule = union(enum) {
24 none,
25 reg_off: struct {
26 register: u8,
27 offset: i64,
28 },
29 expression: []const u8,
23};30};
2431
25/// Each row contains unwinding rules for a set of registers.32/// Each row contains unwinding rules for a set of registers.
26pub const Row = struct {33pub const Row = struct {
27 /// Offset from `FrameDescriptionEntry.pc_begin`34 /// Offset from `FrameDescriptionEntry.pc_begin`
28 offset: u64 = 0,35 offset: u64 = 0,
29 /// Special-case column that defines the CFA (Canonical Frame Address) rule.36 cfa: CfaRule = .none,
30 /// The register field of this column defines the register that CFA is derived from.
31 cfa: Column = .{},
32 /// The register fields in these columns define the register the rule applies to.37 /// The register fields in these columns define the register the rule applies to.
33 columns: ColumnRange = .{},38 columns: ColumnRange = .{ .start = undefined, .len = 0 },
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,
37};39};
3840
39pub const Column = struct {41pub const Column = struct {
40 register: ?u8 = null,42 register: u8,
41 rule: RegisterRule = .{ .default = {} },43 rule: RegisterRule,
42};44};
4345
44const ColumnRange = struct {46const ColumnRange = struct {
45 /// Index into `columns` of the first column in this row.47 start: usize,
46 start: usize = undefined,48 len: u8,
47 len: u8 = 0,
48};49};
4950
50columns: std.ArrayList(Column) = .empty,51columns: std.ArrayList(Column) = .empty,
51stack: std.ArrayList(struct {52stack: std.ArrayList(struct {
52 cfa: Column,53 cfa: CfaRule,
53 columns: ColumnRange,54 columns: ColumnRange,
54}) = .empty,55}) = .empty,
55current_row: Row = .{},56current_row: Row = .{},
...@@ -71,235 +72,388 @@ pub fn reset(self: *VirtualMachine) void {...@@ -71,235 +72,388 @@ pub fn reset(self: *VirtualMachine) void {
71}72}
7273
73/// Return a slice backed by the row's non-CFA columns74/// 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 {
75 if (row.columns.len == 0) return &.{};76 if (row.columns.len == 0) return &.{};
76 return self.columns.items[row.columns.start..][0..row.columns.len];77 return self.columns.items[row.columns.start..][0..row.columns.len];
77}78}
7879
79/// Either retrieves or adds a column for `register` (non-CFA) in the current row.80/// Either retrieves or adds a column for `register` (non-CFA) in the current row.
80fn getOrAddColumn(self: *VirtualMachine, gpa: Allocator, register: u8) !*Column {81fn getOrAddColumn(self: *VirtualMachine, gpa: Allocator, register: u8) !*Column {
81 for (self.rowColumns(self.current_row)) |*c| {82 for (self.rowColumns(&self.current_row)) |*c| {
82 if (c.register == register) return c;83 if (c.register == register) return c;
83 }84 }
8485
85 if (self.current_row.columns.len == 0) {86 if (self.current_row.columns.len == 0) {
86 self.current_row.columns.start = self.columns.items.len;87 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);
87 }90 }
88 self.current_row.columns.len += 1;91 self.current_row.columns.len += 1;
8992
90 const column = try self.columns.addOne(gpa);93 const column = try self.columns.addOne(gpa);
91 column.* = .{94 column.* = .{
92 .register = register,95 .register = register,
96 .rule = .default,
93 };97 };
9498
95 return column;99 return column;
96}100}
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
98/// Runs the CIE instructions, then the FDE instructions. Execution halts129/// Runs the CIE instructions, then the FDE instructions. Execution halts
99/// once the row that corresponds to `pc` is known, and the row is returned.130/// once the row that corresponds to `pc` is known, and the row is returned.
100pub fn runTo(131pub fn runTo(
101 self: *VirtualMachine,132 vm: *VirtualMachine,
102 gpa: Allocator,133 gpa: Allocator,
103 pc: u64,134 pc: u64,
104 cie: Dwarf.Unwind.CommonInformationEntry,135 cie: *const Unwind.CommonInformationEntry,
105 fde: Dwarf.Unwind.FrameDescriptionEntry,136 fde: *const Unwind.FrameDescriptionEntry,
106 addr_size_bytes: u8,137 addr_size_bytes: u8,
107 endian: std.builtin.Endian,138 endian: std.builtin.Endian,
108) !Row {139) !Row {
109 assert(self.cie_row == null);140 assert(vm.cie_row == null);
110 assert(pc >= fde.pc_begin);
111 assert(pc < fde.pc_begin + fde.pc_range);
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 = .{145 const instruction_bytes: []const u8 = insts: {
116 cie.initial_instructions,146 if (target_offset < cie.last_row.?.offset) {
117 fde.instructions,147 break :insts cie.initial_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;
125 }148 }
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;
129}173}
130174
131fn resolveCopyOnWrite(self: *VirtualMachine, gpa: Allocator) !void {175/// Evaluates instructions from `instruction_bytes` until `target_addr` is reached or all
132 if (!self.current_row.copy_on_write) return;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;215 .advance_loc => |delta| {
135 if (self.current_row.columns.len > 0) {216 const new_addr = vm.current_row.offset + delta * cie.code_alignment_factor;
136 try self.columns.ensureUnusedCapacity(gpa, self.current_row.columns.len);217 if (new_addr > target_addr) return;
137 self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row));218 vm.current_row.offset = new_addr;
138 self.current_row.columns.start = new_start;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 }
139 }274 }
140}275}
141276
142/// Executes a single instruction.277fn takeExprBlock(r: *std.Io.Reader, len: usize) error{ ReadFailed, InvalidOperand }![]const u8 {
143/// If this instruction is from the CIE, `is_initial` should be set.278 return r.take(len) catch |err| switch (err) {
144/// Returns the value of `current_row` before executing this instruction.279 error.ReadFailed => |e| return e,
145pub fn step(280 error.EndOfStream => return error.InvalidOperand,
146 self: *VirtualMachine,281 };
147 gpa: Allocator,282}
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 }
158283
159 const prev_row = self.current_row;284const OpcodeByte = packed struct(u8) {
160 switch (instruction) {285 low: packed union {
161 .set_loc => |i| {286 operand: u6,
162 if (i.address <= self.current_row.offset) return error.InvalidOperation;287 extended: enum(u6) {
163 if (cie.segment_selector_size != 0) return error.InvalidOperation; // unsupported288 nop = 0,
164 // TODO: Check cie.segment_selector_size != 0 for DWARFV4289 set_loc = 1,
165 self.current_row.offset = i.address;290 advance_loc1 = 2,
166 },291 advance_loc2 = 3,
167 inline .advance_loc,292 advance_loc4 = 4,
168 .advance_loc1,293 offset_extended = 5,
169 .advance_loc2,294 restore_extended = 6,
170 .advance_loc4,295 undefined = 7,
171 => |i| {296 same_value = 8,
172 self.current_row.offset += i.delta * cie.code_alignment_factor;297 register = 9,
173 self.current_row.copy_on_write = true;298 remember_state = 10,
174 },299 restore_state = 11,
175 inline .offset,300 def_cfa = 12,
176 .offset_extended,301 def_cfa_register = 13,
177 .offset_extended_sf,302 def_cfa_offset = 14,
178 => |i| {303 def_cfa_expression = 15,
179 try self.resolveCopyOnWrite(gpa);304 expression = 16,
180 const column = try self.getOrAddColumn(gpa, i.register);305 offset_extended_sf = 17,
181 column.rule = .{ .offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor };306 def_cfa_sf = 18,
182 },307 def_cfa_offset_sf = 19,
183 inline .restore,308 val_offset = 20,
184 .restore_extended,309 val_offset_sf = 21,
185 => |i| {310 val_expression = 22,
186 try self.resolveCopyOnWrite(gpa);311 _,
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 };
266 },312 },
267 .expression => |i| {313 },
268 try self.resolveCopyOnWrite(gpa);314 opcode: enum(u2) {
269 const column = try self.getOrAddColumn(gpa, i.register);315 extended = 0,
270 column.rule = .{316 advance_loc = 1,
271 .expression = i.block,317 offset = 2,
272 };318 restore = 3,
273 },319 },
274 .val_offset => |i| {320};
275 try self.resolveCopyOnWrite(gpa);321
276 const column = try self.getOrAddColumn(gpa, i.register);322pub const Instruction = union(enum) {
277 column.rule = .{323 nop,
278 .val_offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor,324 remember_state,
279 };325 restore_state,
280 },326 advance_loc: u32,
281 .val_offset_sf => |i| {327 set_loc: u64,
282 try self.resolveCopyOnWrite(gpa);328
283 const column = try self.getOrAddColumn(gpa, i.register);329 register: struct {
284 column.rule = .{330 index: u8,
285 .val_offset = i.offset * cie.data_alignment_factor,331 rule: union(enum) {
286 };332 restore, // restore from cie
287 },333 undefined,
288 .val_expression => |i| {334 same_value,
289 try self.resolveCopyOnWrite(gpa);335 offset_uf: u64,
290 const column = try self.getOrAddColumn(gpa, i.register);336 offset_sf: i64,
291 column.rule = .{337 val_offset_uf: u64,
292 .val_expression = i.block,338 val_offset_sf: i64,
293 };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,
294 },344 },
295 }345 },
296346
297 return prev_row;347 def_cfa: struct {
298}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
300const std = @import("../../../std.zig");454const std = @import("../../../std.zig");
301const assert = std.debug.assert;455const assert = std.debug.assert;
302const Allocator = std.mem.Allocator;456const Allocator = std.mem.Allocator;
303const Dwarf = std.debug.Dwarf;457const Unwind = std.debug.Dwarf.Unwind;
304458
305const VirtualMachine = @This();459const 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 {...@@ -207,6 +207,36 @@ pub const DwarfUnwindContext = struct {
207 vm: Dwarf.Unwind.VirtualMachine,207 vm: Dwarf.Unwind.VirtualMachine,
208 stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }),208 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
210 pub fn init(cpu_context: *const CpuContext) DwarfUnwindContext {240 pub fn init(cpu_context: *const CpuContext) DwarfUnwindContext {
211 comptime assert(supports_unwinding);241 comptime assert(supports_unwinding);
212242
...@@ -243,126 +273,30 @@ pub const DwarfUnwindContext = struct {...@@ -243,126 +273,30 @@ pub const DwarfUnwindContext = struct {
243 return ptr.*;273 return ptr.*;
244 }274 }
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
346 /// Unwind a stack frame using DWARF unwinding info, updating the register context.276 /// Unwind a stack frame using DWARF unwinding info, updating the register context.
347 ///277 ///
348 /// If `.eh_frame_hdr` is available and complete, it will be used to binary search for the FDE.278 /// If `.eh_frame_hdr` is available and complete, it will be used to binary search for the FDE.
349 /// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. The latter279 /// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. The latter
350 /// may require lazily loading the data in those sections.280 /// may require lazily loading the data in those sections.
351 ///281 ///
352 /// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info282 /// `explicit_fde_offset` is for cases where the FDE offset is known, such as when using macOS'
283 /// `__unwind_info` section.
353 pub fn unwindFrame(284 pub fn unwindFrame(
354 context: *DwarfUnwindContext,285 context: *DwarfUnwindContext,
286 cache: *Cache,
355 gpa: Allocator,287 gpa: Allocator,
356 unwind: *const Dwarf.Unwind,288 unwind: *const Dwarf.Unwind,
357 load_offset: usize,289 load_offset: usize,
358 explicit_fde_offset: ?usize,290 explicit_fde_offset: ?usize,
359 ) Error!usize {291 ) Error!usize {
360 return unwindFrameInner(context, gpa, unwind, load_offset, explicit_fde_offset) catch |err| switch (err) {292 return unwindFrameInner(context, cache, gpa, unwind, load_offset, explicit_fde_offset) catch |err| switch (err) {
361 error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory => |e| return e,293 error.InvalidDebugInfo,
294 error.MissingDebugInfo,
295 error.UnsupportedDebugInfo,
296 error.OutOfMemory,
297 => |e| return e,
362298
363 error.UnimplementedRegisterRule,
364 error.UnsupportedAddrSize,299 error.UnsupportedAddrSize,
365 error.UnsupportedDwarfVersion,
366 error.UnimplementedUserOpcode,300 error.UnimplementedUserOpcode,
367 error.UnimplementedExpressionCall,301 error.UnimplementedExpressionCall,
368 error.UnimplementedOpcode,302 error.UnimplementedOpcode,
...@@ -394,12 +328,12 @@ pub const DwarfUnwindContext = struct {...@@ -394,12 +328,12 @@ pub const DwarfUnwindContext = struct {
394 error.InvalidExpressionValue,328 error.InvalidExpressionValue,
395 error.NoExpressionValue,329 error.NoExpressionValue,
396 error.RegisterSizeMismatch,330 error.RegisterSizeMismatch,
397 error.InvalidCFA,
398 => return error.InvalidDebugInfo,331 => return error.InvalidDebugInfo,
399 };332 };
400 }333 }
401 fn unwindFrameInner(334 fn unwindFrameInner(
402 context: *DwarfUnwindContext,335 context: *DwarfUnwindContext,
336 cache: *Cache,
403 gpa: Allocator,337 gpa: Allocator,
404 unwind: *const Dwarf.Unwind,338 unwind: *const Dwarf.Unwind,
405 load_offset: usize,339 load_offset: usize,
...@@ -411,57 +345,85 @@ pub const DwarfUnwindContext = struct {...@@ -411,57 +345,85 @@ pub const DwarfUnwindContext = struct {
411345
412 const pc_vaddr = context.pc - load_offset;346 const pc_vaddr = context.pc - load_offset;
413347
414 const fde_offset = explicit_fde_offset orelse try unwind.lookupPc(348 const cache_slot: Cache.Slot = slot: {
415 pc_vaddr,349 const slot_idx = std.hash.int(pc_vaddr) % Cache.num_slots;
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);
420350
421 // Check if the FDE *actually* includes the pc (`lookupPc` can return false positives).351 {
422 if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) {352 cache.mutex.lock();
423 return error.MissingDebugInfo;353 defer cache.mutex.unlock();
424 }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 CFIs364 // Check if the FDE *actually* includes the pc (`lookupPc` can return false positives).
427 // may not reference other debug sections anyway.365 if (pc_vaddr < fde.pc_begin or pc_vaddr >= fde.pc_begin + fde.pc_range) {
428 var expression_context: Dwarf.expression.Context = .{366 return error.MissingDebugInfo;
429 .format = format,367 }
430 .cpu_context = &context.cpu_context,368
431 .cfa = context.cfa,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;
432 };397 };
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);402 context.cfa = switch (cache_slot.cfa_rule) {
437 context.cfa = switch (row.cfa.rule) {403 .none => return error.InvalidCFARule,
438 .val_offset => |offset| blk: {404 .reg_off => |ro| cfa: {
439 const register = row.cfa.register orelse return error.InvalidCFARule;405 const ptr = try regNative(&context.cpu_context, ro.register);
440 const value = (try regNative(&context.cpu_context, register)).*;406 break :cfa try applyOffset(ptr.*, ro.offset);
441 break :blk try applyOffset(value, offset);
442 },407 },
443 .expression => |expr| blk: {408 .expression => |expr| cfa: {
444 context.stack_machine.reset();409 context.stack_machine.reset();
445 const value = try context.stack_machine.run(410 const value = try context.stack_machine.run(expr, gpa, .{
446 expr,411 .format = format,
447 gpa,412 .cpu_context = &context.cpu_context,
448 expression_context,413 }, context.cfa) orelse return error.NoExpressionValue;
449 context.cfa,414 switch (value) {
450 );415 .generic => |g| break :cfa g,
451416 else => return error.InvalidExpressionValue,
452 if (value) |v| {417 }
453 if (v != .generic) return error.InvalidExpressionValue;
454 break :blk v.generic;
455 } else return error.NoExpressionValue;
456 },418 },
457 else => return error.InvalidCFARule,
458 };419 };
459420
460 expression_context.cfa = context.cfa;421 // If unspecified, we'll use the default rule for the return address register, which is
461422 // typically equivalent to `.undefined` (meaning there is no return address), but may be
462 // If the rule for the return address register is 'undefined', that indicates there is no423 // overriden by ABIs.
463 // return address, i.e. this is the end of the stack.424 var has_return_address: bool = builtin.cpu.arch.isAARCH64() and
464 var explicit_has_return_address: ?bool = null;425 return_address_register >= 19 and
426 return_address_register <= 28;
465427
466 // Create a copy of the CPU context, to which we will apply the new rules.428 // Create a copy of the CPU context, to which we will apply the new rules.
467 var new_cpu_context = context.cpu_context;429 var new_cpu_context = context.cpu_context;
...@@ -469,25 +431,78 @@ pub const DwarfUnwindContext = struct {...@@ -469,25 +431,78 @@ pub const DwarfUnwindContext = struct {
469 // On all implemented architectures, the CFA is defined as being the previous frame's SP431 // On all implemented architectures, the CFA is defined as being the previous frame's SP
470 (try regNative(&new_cpu_context, sp_reg_num)).* = context.cfa.?;432 (try regNative(&new_cpu_context, sp_reg_num)).* = context.cfa.?;
471433
472 for (context.vm.rowColumns(row)) |column| {434 const rules_len = cache_slot.num_rules;
473 if (column.register) |register| {435 for (cache_slot.rules_regs[0..rules_len], cache_slot.rules[0..rules_len]) |register, rule| {
474 const dest = try new_cpu_context.dwarfRegisterBytes(register);436 const new_val: union(enum) {
475 const rule_undef = try context.resolveRegisterRule(gpa, column, expression_context, dest);437 same,
476 if (register == cie.return_address_register) {438 undefined,
477 explicit_has_return_address = !rule_undef;439 val: usize,
478 }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;
479 }501 }
480 }502 }
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
489 const return_address: usize = if (has_return_address) pc: {504 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);
491 break :pc stripInstructionPtrAuthCode(raw_ptr.*);506 break :pc stripInstructionPtrAuthCode(raw_ptr.*);
492 } else 0;507 } else 0;
493508
...@@ -501,7 +516,7 @@ pub const DwarfUnwindContext = struct {...@@ -501,7 +516,7 @@ pub const DwarfUnwindContext = struct {
501 // "return address" we have is the instruction which triggered the signal (if the signal516 // "return address" we have is the instruction which triggered the signal (if the signal
502 // handler returned, the instruction would be re-run). Compensate for this by incrementing517 // handler returned, the instruction would be re-run). Compensate for this by incrementing
503 // the address in that case.518 // 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
506 // We also want to do that same subtraction here to get the PC for the next frame's FDE.521 // We also want to do that same subtraction here to get the PC for the next frame's FDE.
507 // This is because if the callee was noreturn, then the function call might be the caller's522 // 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...@@ -20,7 +20,7 @@ pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) Error!DarwinM
20 },20 },
21 }21 }
22}22}
23fn loadUnwindInfo(module: *const DarwinModule) DebugInfo.Unwind {23fn loadUnwindInfo(module: *const DarwinModule, gpa: Allocator, out: *DebugInfo) !void {
24 const header: *std.macho.mach_header = @ptrFromInt(module.text_base);24 const header: *std.macho.mach_header = @ptrFromInt(module.text_base);
2525
26 var it: macho.LoadCommandIterator = .{26 var it: macho.LoadCommandIterator = .{
...@@ -36,21 +36,57 @@ fn loadUnwindInfo(module: *const DarwinModule) DebugInfo.Unwind {...@@ -36,21 +36,57 @@ fn loadUnwindInfo(module: *const DarwinModule) DebugInfo.Unwind {
3636
37 const vmaddr_slide = module.text_base - text_vmaddr;37 const vmaddr_slide = module.text_base - text_vmaddr;
3838
39 var unwind_info: ?[]const u8 = null;39 var opt_unwind_info: ?[]const u8 = null;
40 var eh_frame: ?[]const u8 = null;40 var opt_eh_frame: ?[]const u8 = null;
41 for (sections) |sect| {41 for (sections) |sect| {
42 if (mem.eql(u8, sect.sectName(), "__unwind_info")) {42 if (mem.eql(u8, sect.sectName(), "__unwind_info")) {
43 const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr)));43 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)];
45 } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) {45 } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) {
46 const sect_ptr: [*]u8 = @ptrFromInt(@as(usize, @intCast(vmaddr_slide + sect.addr)));46 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)];
48 }48 }
49 }49 }
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 = .{
51 .vmaddr_slide = vmaddr_slide,86 .vmaddr_slide = vmaddr_slide,
52 .unwind_info = unwind_info,87 .unwind_info = opt_unwind_info,
53 .eh_frame = eh_frame,88 .dwarf = dwarf,
89 .dwarf_cache = dwarf_cache,
54 };90 };
55}91}
56fn loadMachO(module: *const DarwinModule, gpa: Allocator) !DebugInfo.LoadedMachO {92fn loadMachO(module: *const DarwinModule, gpa: Allocator) !DebugInfo.LoadedMachO {
...@@ -350,10 +386,10 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,...@@ -350,10 +386,10 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
350 };386 };
351}387}
352fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {388fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
353 const unwind: *const DebugInfo.Unwind = u: {389 const unwind: *DebugInfo.Unwind = u: {
354 di.mutex.lock();390 di.mutex.lock();
355 defer di.mutex.unlock();391 defer di.mutex.unlock();
356 if (di.unwind == null) di.unwind = module.loadUnwindInfo();392 if (di.unwind == null) try module.loadUnwindInfo(gpa, di);
357 break :u &di.unwind.?;393 break :u &di.unwind.?;
358 };394 };
359395
...@@ -580,14 +616,8 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,...@@ -580,14 +616,8 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
580 break :ip new_ip;616 break :ip new_ip;
581 },617 },
582 .DWARF => {618 .DWARF => {
583 const eh_frame = unwind.eh_frame orelse return error.MissingDebugInfo;619 const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo);
584 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - unwind.vmaddr_slide;620 return context.unwindFrame(unwind.dwarf_cache, gpa, dwarf, unwind.vmaddr_slide, encoding.value.x86_64.dwarf);
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 );
591 },621 },
592 },622 },
593 .aarch64, .aarch64_be => switch (encoding.mode.arm64) {623 .aarch64, .aarch64_be => switch (encoding.mode.arm64) {
...@@ -600,14 +630,8 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,...@@ -600,14 +630,8 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
600 break :ip new_ip;630 break :ip new_ip;
601 },631 },
602 .DWARF => {632 .DWARF => {
603 const eh_frame = unwind.eh_frame orelse return error.MissingDebugInfo;633 const dwarf = &(unwind.dwarf orelse return error.MissingDebugInfo);
604 const eh_frame_vaddr = @intFromPtr(eh_frame.ptr) - unwind.vmaddr_slide;634 return context.unwindFrame(unwind.dwarf_cache, gpa, dwarf, unwind.vmaddr_slide, encoding.value.arm64.dwarf);
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 );
611 },635 },
612 .FRAME => ip: {636 .FRAME => ip: {
613 const frame = encoding.value.arm64.frame;637 const frame = encoding.value.arm64.frame;
...@@ -691,12 +715,15 @@ pub const DebugInfo = struct {...@@ -691,12 +715,15 @@ pub const DebugInfo = struct {
691 }715 }
692716
693 const Unwind = struct {717 const Unwind = struct {
694 /// The slide applied to the following sections. So, `unwind_info.ptr` is this many bytes718 /// The slide applied to the `__unwind_info` and `__eh_frame` sections.
695 /// higher than the vmaddr of `__unwind_info`, and likewise for `__eh_frame`.719 /// So, `unwind_info.ptr` is this many bytes higher than the section's vmaddr.
696 vmaddr_slide: u64,720 vmaddr_slide: u64,
697 // Backed by the in-memory sections mapped by the loader721 /// Backed by the in-memory section mapped by the loader.
698 unwind_info: ?[]const u8,722 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,
700 };727 };
701728
702 const LoadedMachO = struct {729 const LoadedMachO = struct {
lib/std/debug/SelfInfo/ElfModule.zig+118-71
...@@ -3,8 +3,22 @@ name: []const u8,...@@ -3,8 +3,22 @@ name: []const u8,
3build_id: ?[]const u8,3build_id: ?[]const u8,
4gnu_eh_frame: ?[]const u8,4gnu_eh_frame: ?[]const u8,
55
6/// No cache needed, because `dl_iterate_phdr` is already fast.6pub const LookupCache = struct {
7pub const LookupCache = void;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
9pub const DebugInfo = struct {23pub const DebugInfo = struct {
10 /// Held while checking and/or populating `loaded_elf`/`scanned_dwarf`/`unwind`.24 /// Held while checking and/or populating `loaded_elf`/`scanned_dwarf`/`unwind`.
...@@ -14,18 +28,24 @@ pub const DebugInfo = struct {...@@ -14,18 +28,24 @@ pub const DebugInfo = struct {
1428
15 loaded_elf: ?ElfFile,29 loaded_elf: ?ElfFile,
16 scanned_dwarf: bool,30 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
18 pub const init: DebugInfo = .{34 pub const init: DebugInfo = .{
19 .mutex = .{},35 .mutex = .{},
20 .loaded_elf = null,36 .loaded_elf = null,
21 .scanned_dwarf = false,37 .scanned_dwarf = false,
22 .unwind = @splat(null),38 .unwind = if (supports_unwinding) @splat(null),
39 .unwind_cache = undefined,
23 };40 };
24 pub fn deinit(di: *DebugInfo, gpa: Allocator) void {41 pub fn deinit(di: *DebugInfo, gpa: Allocator) void {
25 if (di.loaded_elf) |*loaded_elf| loaded_elf.deinit(gpa);42 if (di.loaded_elf) |*loaded_elf| loaded_elf.deinit(gpa);
26 for (&di.unwind) |*opt_unwind| {43 if (supports_unwinding) {
27 const unwind = &(opt_unwind.* orelse continue);44 if (di.unwind[0] != null) gpa.destroy(di.unwind_cache);
28 unwind.deinit(gpa);45 for (&di.unwind) |*opt_unwind| {
46 const unwind = &(opt_unwind.* orelse continue);
47 unwind.deinit(gpa);
48 }
29 }49 }
30 }50 }
31};51};
...@@ -34,75 +54,84 @@ pub fn key(m: ElfModule) usize {...@@ -34,75 +54,84 @@ pub fn key(m: ElfModule) usize {
34 return m.load_offset;54 return m.load_offset;
35}55}
36pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) Error!ElfModule {56pub fn lookup(cache: *LookupCache, gpa: Allocator, address: usize) Error!ElfModule {
37 _ = cache;57 if (lookupInCache(cache, address)) |m| return m;
38 _ = gpa;
39 const DlIterContext = struct {
40 /// input
41 address: usize,
42 /// output
43 module: ElfModule,
4458
45 fn callback(info: *std.posix.dl_phdr_info, size: usize, context: *@This()) !void {59 {
46 _ = size;60 // Check a new module hasn't been loaded
47 // The base address is too high61 cache.rwlock.lock();
48 if (context.address < info.addr)62 defer cache.rwlock.unlock();
49 return;63 const DlIterContext = struct {
64 ranges: *std.ArrayList(LookupCache.Range),
65 gpa: Allocator,
5066
51 const phdrs = info.phdr[0..info.phnum];67 fn callback(info: *std.posix.dl_phdr_info, size: usize, context: *@This()) !void {
52 for (phdrs) |*phdr| {68 _ = size;
53 if (phdr.p_type != elf.PT_LOAD) continue;
5469
55 // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff70000070 var mod: ElfModule = .{
56 const seg_start = info.addr +% phdr.p_vaddr;71 .load_offset = info.addr,
57 const seg_end = seg_start + phdr.p_memsz;72 // Android libc uses NULL instead of "" to mark the main program
58 if (context.address >= seg_start and context.address < seg_end) {73 .name = mem.sliceTo(info.name, 0) orelse "",
59 context.module = .{74 .build_id = null,
60 .load_offset = info.addr,75 .gnu_eh_frame = null,
61 // Android libc uses NULL instead of "" to mark the main program76 };
62 .name = mem.sliceTo(info.name, 0) orelse "",77
63 .build_id = null,78 // Populate `build_id` and `gnu_eh_frame`
64 .gnu_eh_frame = null,79 for (info.phdr[0..info.phnum]) |phdr| {
65 };80 switch (phdr.p_type) {
66 break;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 }
67 }100 }
68 } else return;
69101
70 for (info.phdr[0..info.phnum]) |phdr| {102 // Now that `mod` is populated, create the ranges
71 switch (phdr.p_type) {103 for (info.phdr[0..info.phnum]) |phdr| {
72 elf.PT_NOTE => {104 if (phdr.p_type != elf.PT_LOAD) continue;
73 // Look for .note.gnu.build-id105 try context.ranges.append(context.gpa, .{
74 const segment_ptr: [*]const u8 = @ptrFromInt(info.addr + phdr.p_vaddr);106 // Overflowing addition handles VSDOs having p_vaddr = 0xffffffffff700000
75 var r: std.Io.Reader = .fixed(segment_ptr[0..phdr.p_memsz]);107 .start = info.addr +% phdr.p_vaddr,
76 const name_size = r.takeInt(u32, native_endian) catch continue;108 .len = phdr.p_memsz,
77 const desc_size = r.takeInt(u32, native_endian) catch continue;109 .mod = mod,
78 const note_type = r.takeInt(u32, native_endian) catch continue;110 });
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 => {},
90 }111 }
91 }112 }
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 iteration122 if (lookupInCache(cache, address)) |m| return m;
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 };
104 return error.MissingDebugInfo;123 return error.MissingDebugInfo;
105}124}
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}
106fn loadElf(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void {135fn loadElf(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void {
107 std.debug.assert(di.loaded_elf == null);136 std.debug.assert(di.loaded_elf == null);
108 std.debug.assert(!di.scanned_dwarf);137 std.debug.assert(!di.scanned_dwarf);
...@@ -199,11 +228,23 @@ pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugIn...@@ -199,11 +228,23 @@ pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugIn
199 };228 };
200}229}
201fn prepareUnwindLookup(unwind: *Dwarf.Unwind, gpa: Allocator) Error!void {230fn 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) {
203 error.ReadFailed => unreachable, // it's all fixed buffers232 error.ReadFailed => unreachable, // it's all fixed buffers
204 error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory => |e| return e,233 error.InvalidDebugInfo,
205 error.EndOfStream, error.Overflow, error.StreamTooLong => return error.InvalidDebugInfo,234 error.MissingDebugInfo,
206 error.UnsupportedAddrSize, error.UnsupportedDwarfVersion => return error.UnsupportedDebugInfo,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,
207 };248 };
208}249}
209fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void {250fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void {
...@@ -240,12 +281,18 @@ fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Erro...@@ -240,12 +281,18 @@ fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Erro
240 };281 };
241 errdefer for (unwinds) |*u| u.deinit(gpa);282 errdefer for (unwinds) |*u| u.deinit(gpa);
242 for (unwinds) |*u| try prepareUnwindLookup(u, gpa);283 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
243 switch (unwinds.len) {289 switch (unwinds.len) {
244 0 => unreachable,290 0 => unreachable,
245 1 => di.unwind = .{ unwinds[0], null },291 1 => di.unwind = .{ unwinds[0], null },
246 2 => di.unwind = .{ unwinds[0], unwinds[1] },292 2 => di.unwind = .{ unwinds[0], unwinds[1] },
247 else => unreachable,293 else => unreachable,
248 }294 }
295 di.unwind_cache = unwind_cache;
249}296}
250pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize {297pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize {
251 const unwinds: *const [2]?Dwarf.Unwind = u: {298 const unwinds: *const [2]?Dwarf.Unwind = u: {
...@@ -257,7 +304,7 @@ pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, con...@@ -257,7 +304,7 @@ pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, con
257 };304 };
258 for (unwinds) |*opt_unwind| {305 for (unwinds) |*opt_unwind| {
259 const unwind = &(opt_unwind.* orelse break);306 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) {
261 error.MissingDebugInfo => continue, // try the next one308 error.MissingDebugInfo => continue, // try the next one
262 else => |e| return e,309 else => |e| return e,
263 };310 };