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