| author | |
| committer | |
| log | 156cd8f678ebdcccc48382d093a3ef7e45c85a45 |
| tree | ca3f4c37bda9cf1d039ac25ba37b2c45ab5a345f |
| parent | 3f84b6c80ed3306f040dd98b8ccba561a052167a |
| signature |
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 | 572 | defer it.deinit(); |
| 573 | 573 | if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace; |
| 574 | 574 | var total_frames: usize = 0; |
| 575 | var frame_idx: usize = 0; | |
| 575 | var index: usize = 0; | |
| 576 | 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 | 581 | .switch_to_fp => if (!it.stratOk(options.allow_unsafe_unwind)) break, |
| 579 | 582 | .end => break, |
| 580 | 583 | .frame => |ret_addr| { |
| ... | ... | @@ -588,13 +591,13 @@ pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) |
| 588 | 591 | if (ret_addr != target) continue; |
| 589 | 592 | wait_for = null; |
| 590 | 593 | } |
| 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; | |
| 593 | 596 | }, |
| 594 | 597 | }; |
| 595 | 598 | 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], | |
| 598 | 601 | }; |
| 599 | 602 | } |
| 600 | 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 | 27 | const Dwarf = @This(); |
| 28 | 28 | |
| 29 | 29 | pub const expression = @import("Dwarf/expression.zig"); |
| 30 | pub const call_frame = @import("Dwarf/call_frame.zig"); | |
| 31 | 30 | pub const Unwind = @import("Dwarf/Unwind.zig"); |
| 32 | 31 | |
| 33 | 32 | /// Useful to temporarily enable while working on this file. |
lib/std/debug/Dwarf/Unwind.zig+95-50| ... | ... | @@ -10,7 +10,7 @@ |
| 10 | 10 | //! The typical usage of `Unwind` is as follows: |
| 11 | 11 | //! |
| 12 | 12 | //! * 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 | |
| 14 | 14 | //! * Call `lookupPc` to find the section offset of the FDE corresponding to a PC |
| 15 | 15 | //! * Call `getFde` to load the corresponding FDE and CIE |
| 16 | 16 | //! * Check that the PC does indeed fall in that range (`lookupPc` may return a false positive) |
| ... | ... | @@ -18,7 +18,7 @@ |
| 18 | 18 | //! |
| 19 | 19 | //! In some cases, such as when using the "compact unwind" data in Mach-O binaries, the FDE offsets |
| 20 | 20 | //! 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. | |
| 22 | 22 | |
| 23 | 23 | pub const VirtualMachine = @import("Unwind/VirtualMachine.zig"); |
| 24 | 24 | |
| ... | ... | @@ -45,7 +45,7 @@ frame_section: struct { |
| 45 | 45 | |
| 46 | 46 | /// A structure allowing fast lookups of the FDE corresponding to a particular PC. We use a binary |
| 47 | 47 | /// search table for the lookup; essentially, a list of all FDEs ordered by PC range. `null` means |
| 48 | /// the lookup data is not yet populated, so `prepareLookup` must be called before `lookupPc`. | |
| 48 | /// the lookup data is not yet populated, so `prepare` must be called before `lookupPc`. | |
| 49 | 49 | lookup: ?union(enum) { |
| 50 | 50 | /// The `.eh_frame_hdr` section contains a pre-computed search table which we can use. |
| 51 | 51 | eh_frame_hdr: struct { |
| ... | ... | @@ -58,6 +58,12 @@ lookup: ?union(enum) { |
| 58 | 58 | sorted_fdes: []SortedFdeEntry, |
| 59 | 59 | }, |
| 60 | 60 | |
| 61 | /// Initially empty; populated by `prepare`. | |
| 62 | cie_list: std.MultiArrayList(struct { | |
| 63 | offset: u64, | |
| 64 | cie: CommonInformationEntry, | |
| 65 | }), | |
| 66 | ||
| 61 | 67 | const SortedFdeEntry = struct { |
| 62 | 68 | /// This FDE's value of `pc_begin`. |
| 63 | 69 | pc_begin: u64, |
| ... | ... | @@ -83,6 +89,7 @@ pub fn initEhFrameHdr(header: EhFrameHeader, section_vaddr: u64, section_bytes_p |
| 83 | 89 | .vaddr = section_vaddr, |
| 84 | 90 | .table = table, |
| 85 | 91 | } } else null, |
| 92 | .cie_list = .empty, | |
| 86 | 93 | }; |
| 87 | 94 | } |
| 88 | 95 | |
| ... | ... | @@ -98,16 +105,21 @@ pub fn initSection(section: Section, section_vaddr: u64, section_bytes: []const |
| 98 | 105 | .vaddr = section_vaddr, |
| 99 | 106 | }, |
| 100 | 107 | .lookup = null, |
| 108 | .cie_list = .empty, | |
| 101 | 109 | }; |
| 102 | 110 | } |
| 103 | 111 | |
| 104 | /// Technically, it is only necessary to call this if `prepareLookup` has previously been called, | |
| 105 | /// since no other function here allocates resources. | |
| 106 | 112 | pub fn deinit(unwind: *Unwind, gpa: Allocator) void { |
| 107 | 113 | if (unwind.lookup) |lookup| switch (lookup) { |
| 108 | 114 | .eh_frame_hdr => {}, |
| 109 | 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 | } |
| 112 | 124 | |
| 113 | 125 | /// Decoded version of the `.eh_frame_hdr` section. |
| ... | ... | @@ -236,7 +248,6 @@ const EntryHeader = union(enum) { |
| 236 | 248 | bytes_len: u64, |
| 237 | 249 | }, |
| 238 | 250 | fde: struct { |
| 239 | format: Format, | |
| 240 | 251 | /// Offset into the section of the corresponding CIE, *including* its entry header. |
| 241 | 252 | cie_offset: u64, |
| 242 | 253 | /// Remaining bytes in the FDE. These are parseable by `FrameDescriptionEntry.parse`. |
| ... | ... | @@ -290,7 +301,6 @@ const EntryHeader = union(enum) { |
| 290 | 301 | .debug_frame => cie_ptr_or_id, |
| 291 | 302 | }; |
| 292 | 303 | return .{ .fde = .{ |
| 293 | .format = unit_header.format, | |
| 294 | 304 | .cie_offset = cie_offset, |
| 295 | 305 | .bytes_len = remaining_bytes, |
| 296 | 306 | } }; |
| ... | ... | @@ -299,6 +309,7 @@ const EntryHeader = union(enum) { |
| 299 | 309 | |
| 300 | 310 | pub const CommonInformationEntry = struct { |
| 301 | 311 | version: u8, |
| 312 | format: Format, | |
| 302 | 313 | |
| 303 | 314 | /// In version 4, CIEs can specify the address size used in the CIE and associated FDEs. |
| 304 | 315 | /// This value must be used *only* to parse associated FDEs in `FrameDescriptionEntry.parse`. |
| ... | ... | @@ -318,6 +329,12 @@ pub const CommonInformationEntry = struct { |
| 318 | 329 | |
| 319 | 330 | initial_instructions: []const u8, |
| 320 | 331 | |
| 332 | last_row: ?struct { | |
| 333 | offset: u64, | |
| 334 | cfa: VirtualMachine.CfaRule, | |
| 335 | cols: []VirtualMachine.Column, | |
| 336 | }, | |
| 337 | ||
| 321 | 338 | pub const AugmentationKind = enum { none, gcc_eh, lsb_z }; |
| 322 | 339 | |
| 323 | 340 | /// This function expects to read the CIE starting with the version field. |
| ... | ... | @@ -326,6 +343,7 @@ pub const CommonInformationEntry = struct { |
| 326 | 343 | /// `length_offset` specifies the offset of this CIE's length field in the |
| 327 | 344 | /// .eh_frame / .debug_frame section. |
| 328 | 345 | fn parse( |
| 346 | format: Format, | |
| 329 | 347 | cie_bytes: []const u8, |
| 330 | 348 | section: Section, |
| 331 | 349 | default_addr_size_bytes: u8, |
| ... | ... | @@ -384,6 +402,7 @@ pub const CommonInformationEntry = struct { |
| 384 | 402 | }; |
| 385 | 403 | |
| 386 | 404 | return .{ |
| 405 | .format = format, | |
| 387 | 406 | .version = version, |
| 388 | 407 | .addr_size_bytes = addr_size_bytes, |
| 389 | 408 | .segment_selector_size = segment_selector_size, |
| ... | ... | @@ -394,6 +413,7 @@ pub const CommonInformationEntry = struct { |
| 394 | 413 | .is_signal_frame = is_signal_frame, |
| 395 | 414 | .augmentation_kind = aug_kind, |
| 396 | 415 | .initial_instructions = r.buffered(), |
| 416 | .last_row = null, | |
| 397 | 417 | }; |
| 398 | 418 | } |
| 399 | 419 | }; |
| ... | ... | @@ -411,7 +431,7 @@ pub const FrameDescriptionEntry = struct { |
| 411 | 431 | /// module's `.eh_frame` section, this will equal `fde_bytes.ptr`. |
| 412 | 432 | fde_vaddr: u64, |
| 413 | 433 | fde_bytes: []const u8, |
| 414 | cie: CommonInformationEntry, | |
| 434 | cie: *const CommonInformationEntry, | |
| 415 | 435 | endian: Endian, |
| 416 | 436 | ) !FrameDescriptionEntry { |
| 417 | 437 | if (cie.segment_selector_size != 0) return error.UnsupportedAddrSize; |
| ... | ... | @@ -446,11 +466,18 @@ pub const FrameDescriptionEntry = struct { |
| 446 | 466 | } |
| 447 | 467 | }; |
| 448 | 468 | |
| 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. | |
| 452 | pub 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. | |
| 472 | pub 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(); | |
| 454 | 481 | |
| 455 | 482 | const section = unwind.frame_section; |
| 456 | 483 | |
| ... | ... | @@ -462,21 +489,28 @@ pub fn prepareLookup(unwind: *Unwind, gpa: Allocator, addr_size_bytes: u8, endia |
| 462 | 489 | const entry_offset = r.seek; |
| 463 | 490 | switch (try EntryHeader.read(&r, entry_offset, section.id, endian)) { |
| 464 | 491 | .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); | |
| 467 | 504 | continue; |
| 468 | 505 | }, |
| 469 | 506 | .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); | |
| 480 | 514 | try fde_list.append(gpa, .{ |
| 481 | 515 | .pc_begin = fde.pc_begin, |
| 482 | 516 | .fde_offset = entry_offset, |
| ... | ... | @@ -502,12 +536,30 @@ pub fn prepareLookup(unwind: *Unwind, gpa: Allocator, addr_size_bytes: u8, endia |
| 502 | 536 | unwind.lookup = .{ .sorted_fdes = final_fdes }; |
| 503 | 537 | } |
| 504 | 538 | |
| 539 | fn 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 | 557 | /// Given a program counter value, returns the offset of the corresponding FDE, or `null` if no |
| 506 | 558 | /// matching FDE was found. The returned offset can be passed to `getFde` to load the data |
| 507 | 559 | /// associated with the FDE. |
| 508 | 560 | /// |
| 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. | |
| 511 | 563 | /// |
| 512 | 564 | /// The return value may be a false positive. After loading the FDE with `loadFde`, the caller must |
| 513 | 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 | 576 | }, |
| 525 | 577 | .sorted_fdes => |sorted_fdes| sorted_fdes, |
| 526 | 578 | }; |
| 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; | |
| 530 | 589 | } |
| 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; | |
| 536 | 593 | } |
| 537 | 594 | |
| 538 | 595 | /// Get the FDE at a given offset, as well as its associated CIE. This offset typically comes from |
| 539 | 596 | /// `lookupPc`. The CFI instructions within can be evaluated with `VirtualMachine`. |
| 540 | pub fn getFde(unwind: *const Unwind, fde_offset: u64, addr_size_bytes: u8, endian: Endian) !struct { Format, CommonInformationEntry, FrameDescriptionEntry } { | |
| 597 | pub fn getFde(unwind: *const Unwind, fde_offset: u64, endian: Endian) !struct { *const CommonInformationEntry, FrameDescriptionEntry } { | |
| 541 | 598 | const section = unwind.frame_section; |
| 542 | 599 | |
| 543 | 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 | 604 | .cie, .terminator => return bad(), // This is meant to be an FDE |
| 548 | 605 | }; |
| 549 | 606 | |
| 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; | |
| 563 | 608 | const fde: FrameDescriptionEntry = try .parse( |
| 564 | 609 | section.vaddr + fde_offset + fde_reader.seek, |
| 565 | 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 | 612 | endian, |
| 568 | 613 | ); |
| 569 | 614 | |
| 570 | return .{ cie_info.format, cie, fde }; | |
| 615 | return .{ cie, fde }; | |
| 571 | 616 | } |
| 572 | 617 | |
| 573 | 618 | const EhPointerContext = struct { |
lib/std/debug/Dwarf/Unwind/VirtualMachine.zig+354-200| ... | ... | @@ -5,9 +5,9 @@ pub const RegisterRule = union(enum) { |
| 5 | 5 | /// The spec says that the default rule for each column is the undefined rule. |
| 6 | 6 | /// However, it also allows ABI / compiler authors to specify alternate defaults, so |
| 7 | 7 | /// there is a distinction made here. |
| 8 | default: void, | |
| 9 | undefined: void, | |
| 10 | same_value: void, | |
| 8 | default, | |
| 9 | undefined, | |
| 10 | same_value, | |
| 11 | 11 | /// offset(N) |
| 12 | 12 | offset: i64, |
| 13 | 13 | /// val_offset(N) |
| ... | ... | @@ -18,38 +18,39 @@ pub const RegisterRule = union(enum) { |
| 18 | 18 | expression: []const u8, |
| 19 | 19 | /// val_expression(E) |
| 20 | 20 | val_expression: []const u8, |
| 21 | /// Augmenter-defined rule | |
| 22 | architectural: void, | |
| 21 | }; | |
| 22 | ||
| 23 | pub const CfaRule = union(enum) { | |
| 24 | none, | |
| 25 | reg_off: struct { | |
| 26 | register: u8, | |
| 27 | offset: i64, | |
| 28 | }, | |
| 29 | expression: []const u8, | |
| 23 | 30 | }; |
| 24 | 31 | |
| 25 | 32 | /// Each row contains unwinding rules for a set of registers. |
| 26 | 33 | pub const Row = struct { |
| 27 | 34 | /// Offset from `FrameDescriptionEntry.pc_begin` |
| 28 | 35 | 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, | |
| 32 | 37 | /// 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 }, | |
| 37 | 39 | }; |
| 38 | 40 | |
| 39 | 41 | pub const Column = struct { |
| 40 | register: ?u8 = null, | |
| 41 | rule: RegisterRule = .{ .default = {} }, | |
| 42 | register: u8, | |
| 43 | rule: RegisterRule, | |
| 42 | 44 | }; |
| 43 | 45 | |
| 44 | 46 | const 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, | |
| 48 | 49 | }; |
| 49 | 50 | |
| 50 | 51 | columns: std.ArrayList(Column) = .empty, |
| 51 | 52 | stack: std.ArrayList(struct { |
| 52 | cfa: Column, | |
| 53 | cfa: CfaRule, | |
| 53 | 54 | columns: ColumnRange, |
| 54 | 55 | }) = .empty, |
| 55 | 56 | current_row: Row = .{}, |
| ... | ... | @@ -71,235 +72,388 @@ pub fn reset(self: *VirtualMachine) void { |
| 71 | 72 | } |
| 72 | 73 | |
| 73 | 74 | /// Return a slice backed by the row's non-CFA columns |
| 74 | pub fn rowColumns(self: VirtualMachine, row: Row) []Column { | |
| 75 | pub fn rowColumns(self: *const VirtualMachine, row: *const Row) []Column { | |
| 75 | 76 | if (row.columns.len == 0) return &.{}; |
| 76 | 77 | return self.columns.items[row.columns.start..][0..row.columns.len]; |
| 77 | 78 | } |
| 78 | 79 | |
| 79 | 80 | /// Either retrieves or adds a column for `register` (non-CFA) in the current row. |
| 80 | 81 | fn 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 | 83 | if (c.register == register) return c; |
| 83 | 84 | } |
| 84 | 85 | |
| 85 | 86 | if (self.current_row.columns.len == 0) { |
| 86 | 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 | 91 | self.current_row.columns.len += 1; |
| 89 | 92 | |
| 90 | 93 | const column = try self.columns.addOne(gpa); |
| 91 | 94 | column.* = .{ |
| 92 | 95 | .register = register, |
| 96 | .rule = .default, | |
| 93 | 97 | }; |
| 94 | 98 | |
| 95 | 99 | return column; |
| 96 | 100 | } |
| 97 | 101 | |
| 102 | pub 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 | 129 | /// Runs the CIE instructions, then the FDE instructions. Execution halts |
| 99 | 130 | /// once the row that corresponds to `pc` is known, and the row is returned. |
| 100 | 131 | pub fn runTo( |
| 101 | self: *VirtualMachine, | |
| 132 | vm: *VirtualMachine, | |
| 102 | 133 | gpa: Allocator, |
| 103 | 134 | pc: u64, |
| 104 | cie: Dwarf.Unwind.CommonInformationEntry, | |
| 105 | fde: Dwarf.Unwind.FrameDescriptionEntry, | |
| 135 | cie: *const Unwind.CommonInformationEntry, | |
| 136 | fde: *const Unwind.FrameDescriptionEntry, | |
| 106 | 137 | addr_size_bytes: u8, |
| 107 | 138 | endian: std.builtin.Endian, |
| 108 | 139 | ) !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); | |
| 112 | 141 | |
| 113 | var prev_row: Row = self.current_row; | |
| 142 | const target_offset = pc - fde.pc_begin; | |
| 143 | assert(target_offset < fde.pc_range); | |
| 114 | 144 | |
| 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; | |
| 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 | }; | |
| 127 | 163 | |
| 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 | } |
| 130 | 174 | |
| 131 | fn 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. | |
| 177 | fn 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 | }, | |
| 133 | 214 | |
| 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 | } | |
| 139 | 274 | } |
| 140 | 275 | } |
| 141 | 276 | |
| 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. | |
| 145 | pub 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 | } | |
| 277 | fn 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 | } | |
| 158 | 283 | |
| 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 | }; | |
| 284 | const 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 | _, | |
| 266 | 312 | }, |
| 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 | ||
| 322 | pub 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, | |
| 294 | 344 | }, |
| 295 | } | |
| 345 | }, | |
| 296 | 346 | |
| 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 | }; | |
| 299 | 453 | |
| 300 | 454 | const std = @import("../../../std.zig"); |
| 301 | 455 | const assert = std.debug.assert; |
| 302 | 456 | const Allocator = std.mem.Allocator; |
| 303 | const Dwarf = std.debug.Dwarf; | |
| 457 | const Unwind = std.debug.Dwarf.Unwind; | |
| 304 | 458 | |
| 305 | 459 | const VirtualMachine = @This(); |
lib/std/debug/Dwarf/call_frame.zig deleted-288| ... | ... | @@ -1,288 +0,0 @@ |
| 1 | const std = @import("../../std.zig"); | |
| 2 | const Reader = std.Io.Reader; | |
| 3 | ||
| 4 | /// TODO merge with std.dwarf.CFA | |
| 5 | const 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`. | |
| 48 | fn 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 | ||
| 56 | pub 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 | 207 | vm: Dwarf.Unwind.VirtualMachine, |
| 208 | 208 | stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }), |
| 209 | 209 | |
| 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 | 240 | pub fn init(cpu_context: *const CpuContext) DwarfUnwindContext { |
| 211 | 241 | comptime assert(supports_unwinding); |
| 212 | 242 | |
| ... | ... | @@ -243,126 +273,30 @@ pub const DwarfUnwindContext = struct { |
| 243 | 273 | return ptr.*; |
| 244 | 274 | } |
| 245 | 275 | |
| 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 | 276 | /// Unwind a stack frame using DWARF unwinding info, updating the register context. |
| 347 | 277 | /// |
| 348 | 278 | /// If `.eh_frame_hdr` is available and complete, it will be used to binary search for the FDE. |
| 349 | 279 | /// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. The latter |
| 350 | 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_info | |
| 282 | /// `explicit_fde_offset` is for cases where the FDE offset is known, such as when using macOS' | |
| 283 | /// `__unwind_info` section. | |
| 353 | 284 | pub fn unwindFrame( |
| 354 | 285 | context: *DwarfUnwindContext, |
| 286 | cache: *Cache, | |
| 355 | 287 | gpa: Allocator, |
| 356 | 288 | unwind: *const Dwarf.Unwind, |
| 357 | 289 | load_offset: usize, |
| 358 | 290 | explicit_fde_offset: ?usize, |
| 359 | 291 | ) 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, | |
| 362 | 298 | |
| 363 | error.UnimplementedRegisterRule, | |
| 364 | 299 | error.UnsupportedAddrSize, |
| 365 | error.UnsupportedDwarfVersion, | |
| 366 | 300 | error.UnimplementedUserOpcode, |
| 367 | 301 | error.UnimplementedExpressionCall, |
| 368 | 302 | error.UnimplementedOpcode, |
| ... | ... | @@ -394,12 +328,12 @@ pub const DwarfUnwindContext = struct { |
| 394 | 328 | error.InvalidExpressionValue, |
| 395 | 329 | error.NoExpressionValue, |
| 396 | 330 | error.RegisterSizeMismatch, |
| 397 | error.InvalidCFA, | |
| 398 | 331 | => return error.InvalidDebugInfo, |
| 399 | 332 | }; |
| 400 | 333 | } |
| 401 | 334 | fn unwindFrameInner( |
| 402 | 335 | context: *DwarfUnwindContext, |
| 336 | cache: *Cache, | |
| 403 | 337 | gpa: Allocator, |
| 404 | 338 | unwind: *const Dwarf.Unwind, |
| 405 | 339 | load_offset: usize, |
| ... | ... | @@ -411,57 +345,85 @@ pub const DwarfUnwindContext = struct { |
| 411 | 345 | |
| 412 | 346 | const pc_vaddr = context.pc - load_offset; |
| 413 | 347 | |
| 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; | |
| 420 | 350 | |
| 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); | |
| 425 | 363 | |
| 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; | |
| 432 | 397 | }; |
| 433 | 398 | |
| 434 | context.vm.reset(); | |
| 399 | const format = cache_slot.cie.format; | |
| 400 | const return_address_register = cache_slot.cie.return_address_register; | |
| 435 | 401 | |
| 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); | |
| 442 | 407 | }, |
| 443 | .expression => |expr| blk: { | |
| 408 | .expression => |expr| cfa: { | |
| 444 | 409 | 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 | } | |
| 456 | 418 | }, |
| 457 | else => return error.InvalidCFARule, | |
| 458 | 419 | }; |
| 459 | 420 | |
| 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; | |
| 465 | 427 | |
| 466 | 428 | // Create a copy of the CPU context, to which we will apply the new rules. |
| 467 | 429 | var new_cpu_context = context.cpu_context; |
| ... | ... | @@ -469,25 +431,78 @@ pub const DwarfUnwindContext = struct { |
| 469 | 431 | // On all implemented architectures, the CFA is defined as being the previous frame's SP |
| 470 | 432 | (try regNative(&new_cpu_context, sp_reg_num)).* = context.cfa.?; |
| 471 | 433 | |
| 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; | |
| 479 | 501 | } |
| 480 | 502 | } |
| 481 | 503 | |
| 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 | 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 | 506 | break :pc stripInstructionPtrAuthCode(raw_ptr.*); |
| 492 | 507 | } else 0; |
| 493 | 508 | |
| ... | ... | @@ -501,7 +516,7 @@ pub const DwarfUnwindContext = struct { |
| 501 | 516 | // "return address" we have is the instruction which triggered the signal (if the signal |
| 502 | 517 | // handler returned, the instruction would be re-run). Compensate for this by incrementing |
| 503 | 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; | |
| 505 | 520 | |
| 506 | 521 | // We also want to do that same subtraction here to get the PC for the next frame's FDE. |
| 507 | 522 | // 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 | 20 | }, |
| 21 | 21 | } |
| 22 | 22 | } |
| 23 | fn loadUnwindInfo(module: *const DarwinModule) DebugInfo.Unwind { | |
| 23 | fn loadUnwindInfo(module: *const DarwinModule, gpa: Allocator, out: *DebugInfo) !void { | |
| 24 | 24 | const header: *std.macho.mach_header = @ptrFromInt(module.text_base); |
| 25 | 25 | |
| 26 | 26 | var it: macho.LoadCommandIterator = .{ |
| ... | ... | @@ -36,21 +36,57 @@ fn loadUnwindInfo(module: *const DarwinModule) DebugInfo.Unwind { |
| 36 | 36 | |
| 37 | 37 | const vmaddr_slide = module.text_base - text_vmaddr; |
| 38 | 38 | |
| 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; | |
| 41 | 41 | for (sections) |sect| { |
| 42 | 42 | if (mem.eql(u8, sect.sectName(), "__unwind_info")) { |
| 43 | 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 | 45 | } else if (mem.eql(u8, sect.sectName(), "__eh_frame")) { |
| 46 | 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 | 86 | .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, | |
| 54 | 90 | }; |
| 55 | 91 | } |
| 56 | 92 | fn loadMachO(module: *const DarwinModule, gpa: Allocator) !DebugInfo.LoadedMachO { |
| ... | ... | @@ -350,10 +386,10 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, |
| 350 | 386 | }; |
| 351 | 387 | } |
| 352 | 388 | fn 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 | 390 | di.mutex.lock(); |
| 355 | 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 | 393 | break :u &di.unwind.?; |
| 358 | 394 | }; |
| 359 | 395 | |
| ... | ... | @@ -580,14 +616,8 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, |
| 580 | 616 | break :ip new_ip; |
| 581 | 617 | }, |
| 582 | 618 | .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); | |
| 591 | 621 | }, |
| 592 | 622 | }, |
| 593 | 623 | .aarch64, .aarch64_be => switch (encoding.mode.arm64) { |
| ... | ... | @@ -600,14 +630,8 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, |
| 600 | 630 | break :ip new_ip; |
| 601 | 631 | }, |
| 602 | 632 | .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); | |
| 611 | 635 | }, |
| 612 | 636 | .FRAME => ip: { |
| 613 | 637 | const frame = encoding.value.arm64.frame; |
| ... | ... | @@ -691,12 +715,15 @@ pub const DebugInfo = struct { |
| 691 | 715 | } |
| 692 | 716 | |
| 693 | 717 | 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. | |
| 696 | 720 | 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. | |
| 698 | 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 | }; |
| 701 | 728 | |
| 702 | 729 | const LoadedMachO = struct { |
lib/std/debug/SelfInfo/ElfModule.zig+118-71| ... | ... | @@ -3,8 +3,22 @@ name: []const u8, |
| 3 | 3 | build_id: ?[]const u8, |
| 4 | 4 | gnu_eh_frame: ?[]const u8, |
| 5 | 5 | |
| 6 | /// No cache needed, because `dl_iterate_phdr` is already fast. | |
| 7 | pub const LookupCache = void; | |
| 6 | pub 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 | }; | |
| 8 | 22 | |
| 9 | 23 | pub const DebugInfo = struct { |
| 10 | 24 | /// Held while checking and/or populating `loaded_elf`/`scanned_dwarf`/`unwind`. |
| ... | ... | @@ -14,18 +28,24 @@ pub const DebugInfo = struct { |
| 14 | 28 | |
| 15 | 29 | loaded_elf: ?ElfFile, |
| 16 | 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 | 34 | pub const init: DebugInfo = .{ |
| 19 | 35 | .mutex = .{}, |
| 20 | 36 | .loaded_elf = null, |
| 21 | 37 | .scanned_dwarf = false, |
| 22 | .unwind = @splat(null), | |
| 38 | .unwind = if (supports_unwinding) @splat(null), | |
| 39 | .unwind_cache = undefined, | |
| 23 | 40 | }; |
| 24 | 41 | pub fn deinit(di: *DebugInfo, gpa: Allocator) void { |
| 25 | 42 | 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 | } | |
| 29 | 49 | } |
| 30 | 50 | } |
| 31 | 51 | }; |
| ... | ... | @@ -34,75 +54,84 @@ pub fn key(m: ElfModule) usize { |
| 34 | 54 | return m.load_offset; |
| 35 | 55 | } |
| 36 | 56 | pub 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; | |
| 44 | 58 | |
| 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, | |
| 50 | 66 | |
| 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; | |
| 54 | 69 | |
| 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 | } | |
| 67 | 100 | } |
| 68 | } else return; | |
| 69 | 101 | |
| 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 | }); | |
| 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 | } | |
| 92 | 121 | |
| 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; | |
| 104 | 123 | return error.MissingDebugInfo; |
| 105 | 124 | } |
| 125 | fn 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 | } | |
| 106 | 135 | fn loadElf(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Error!void { |
| 107 | 136 | std.debug.assert(di.loaded_elf == null); |
| 108 | 137 | std.debug.assert(!di.scanned_dwarf); |
| ... | ... | @@ -199,11 +228,23 @@ pub fn getSymbolAtAddress(module: *const ElfModule, gpa: Allocator, di: *DebugIn |
| 199 | 228 | }; |
| 200 | 229 | } |
| 201 | 230 | fn 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 | 232 | 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, | |
| 207 | 248 | }; |
| 208 | 249 | } |
| 209 | 250 | fn 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 | 281 | }; |
| 241 | 282 | errdefer for (unwinds) |*u| u.deinit(gpa); |
| 242 | 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 | 289 | switch (unwinds.len) { |
| 244 | 290 | 0 => unreachable, |
| 245 | 291 | 1 => di.unwind = .{ unwinds[0], null }, |
| 246 | 292 | 2 => di.unwind = .{ unwinds[0], unwinds[1] }, |
| 247 | 293 | else => unreachable, |
| 248 | 294 | } |
| 295 | di.unwind_cache = unwind_cache; | |
| 249 | 296 | } |
| 250 | 297 | pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize { |
| 251 | 298 | const unwinds: *const [2]?Dwarf.Unwind = u: { |
| ... | ... | @@ -257,7 +304,7 @@ pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, con |
| 257 | 304 | }; |
| 258 | 305 | for (unwinds) |*opt_unwind| { |
| 259 | 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 | 308 | error.MissingDebugInfo => continue, // try the next one |
| 262 | 309 | else => |e| return e, |
| 263 | 310 | }; |