| author | |
| committer | |
| log | a931bfada5e358ace980b2f8fbc50ce424ced526 |
| tree | 5aabd9fb3833765926ee5409c1ce14e04d2d9fd0 |
| parent | 9e2668cd2ecc587390335e1c9f6e1592a7bd6eb6 |
| parent | 6d606cc38b4df2b20af9d77367f8ab22bbbea092 |
| signature |
std.debug: reorg and clarify API goals11 files changed, 3512 insertions(+), 3428 deletions(-)
lib/std/debug.zig+154-1515| ... | @@ -1,16 +1,11 @@ | ... | @@ -1,16 +1,11 @@ |
| 1 | const std = @import("std.zig"); | ||
| 2 | const builtin = @import("builtin"); | 1 | const builtin = @import("builtin"); |
| 2 | const std = @import("std.zig"); | ||
| 3 | const math = std.math; | 3 | const math = std.math; |
| 4 | const mem = std.mem; | 4 | const mem = std.mem; |
| 5 | const io = std.io; | 5 | const io = std.io; |
| 6 | const posix = std.posix; | 6 | const posix = std.posix; |
| 7 | const fs = std.fs; | 7 | const fs = std.fs; |
| 8 | const testing = std.testing; | 8 | const testing = std.testing; |
| 9 | const elf = std.elf; | ||
| 10 | const DW = std.dwarf; | ||
| 11 | const macho = std.macho; | ||
| 12 | const coff = std.coff; | ||
| 13 | const pdb = std.pdb; | ||
| 14 | const root = @import("root"); | 9 | const root = @import("root"); |
| 15 | const File = std.fs.File; | 10 | const File = std.fs.File; |
| 16 | const windows = std.os.windows; | 11 | const windows = std.os.windows; |
| ... | @@ -18,8 +13,24 @@ const native_arch = builtin.cpu.arch; | ... | @@ -18,8 +13,24 @@ const native_arch = builtin.cpu.arch; |
| 18 | const native_os = builtin.os.tag; | 13 | const native_os = builtin.os.tag; |
| 19 | const native_endian = native_arch.endian(); | 14 | const native_endian = native_arch.endian(); |
| 20 | 15 | ||
| 16 | pub const MemoryAccessor = @import("debug/MemoryAccessor.zig"); | ||
| 21 | pub const Dwarf = @import("debug/Dwarf.zig"); | 17 | pub const Dwarf = @import("debug/Dwarf.zig"); |
| 18 | pub const Pdb = @import("debug/Pdb.zig"); | ||
| 19 | pub const SelfInfo = @import("debug/SelfInfo.zig"); | ||
| 20 | |||
| 21 | /// Unresolved source locations can be represented with a single `usize` that | ||
| 22 | /// corresponds to a virtual memory address of the program counter. Combined | ||
| 23 | /// with debug information, those values can be converted into a resolved | ||
| 24 | /// source location, including file, line, and column. | ||
| 25 | pub const SourceLocation = struct { | ||
| 26 | line: u64, | ||
| 27 | column: u64, | ||
| 28 | file_name: []const u8, | ||
| 29 | }; | ||
| 22 | 30 | ||
| 31 | /// Deprecated because it returns the optimization mode of the standard | ||
| 32 | /// library, when the caller probably wants to use the optimization mode of | ||
| 33 | /// their own module. | ||
| 23 | pub const runtime_safety = switch (builtin.mode) { | 34 | pub const runtime_safety = switch (builtin.mode) { |
| 24 | .Debug, .ReleaseSafe => true, | 35 | .Debug, .ReleaseSafe => true, |
| 25 | .ReleaseFast, .ReleaseSmall => false, | 36 | .ReleaseFast, .ReleaseSmall => false, |
| ... | @@ -46,39 +57,6 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) { | ... | @@ -46,39 +57,6 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) { |
| 46 | else => true, | 57 | else => true, |
| 47 | }; | 58 | }; |
| 48 | 59 | ||
| 49 | pub const LineInfo = struct { | ||
| 50 | line: u64, | ||
| 51 | column: u64, | ||
| 52 | file_name: []const u8, | ||
| 53 | |||
| 54 | pub fn deinit(self: LineInfo, allocator: mem.Allocator) void { | ||
| 55 | allocator.free(self.file_name); | ||
| 56 | } | ||
| 57 | }; | ||
| 58 | |||
| 59 | pub const SymbolInfo = struct { | ||
| 60 | symbol_name: []const u8 = "???", | ||
| 61 | compile_unit_name: []const u8 = "???", | ||
| 62 | line_info: ?LineInfo = null, | ||
| 63 | |||
| 64 | pub fn deinit(self: SymbolInfo, allocator: mem.Allocator) void { | ||
| 65 | if (self.line_info) |li| { | ||
| 66 | li.deinit(allocator); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | }; | ||
| 70 | const PdbOrDwarf = union(enum) { | ||
| 71 | pdb: pdb.Pdb, | ||
| 72 | dwarf: Dwarf, | ||
| 73 | |||
| 74 | fn deinit(self: *PdbOrDwarf, allocator: mem.Allocator) void { | ||
| 75 | switch (self.*) { | ||
| 76 | .pdb => |*inner| inner.deinit(), | ||
| 77 | .dwarf => |*inner| inner.deinit(allocator), | ||
| 78 | } | ||
| 79 | } | ||
| 80 | }; | ||
| 81 | |||
| 82 | /// Allows the caller to freely write to stderr until `unlockStdErr` is called. | 60 | /// Allows the caller to freely write to stderr until `unlockStdErr` is called. |
| 83 | /// | 61 | /// |
| 84 | /// During the lock, any `std.Progress` information is cleared from the terminal. | 62 | /// During the lock, any `std.Progress` information is cleared from the terminal. |
| ... | @@ -104,13 +82,13 @@ pub fn getStderrMutex() *std.Thread.Mutex { | ... | @@ -104,13 +82,13 @@ pub fn getStderrMutex() *std.Thread.Mutex { |
| 104 | } | 82 | } |
| 105 | 83 | ||
| 106 | /// TODO multithreaded awareness | 84 | /// TODO multithreaded awareness |
| 107 | var self_debug_info: ?Info = null; | 85 | var self_debug_info: ?SelfInfo = null; |
| 108 | 86 | ||
| 109 | pub fn getSelfDebugInfo() !*Info { | 87 | pub fn getSelfDebugInfo() !*SelfInfo { |
| 110 | if (self_debug_info) |*info| { | 88 | if (self_debug_info) |*info| { |
| 111 | return info; | 89 | return info; |
| 112 | } else { | 90 | } else { |
| 113 | self_debug_info = try openSelfDebugInfo(getDebugInfoAllocator()); | 91 | self_debug_info = try SelfInfo.open(getDebugInfoAllocator()); |
| 114 | return &self_debug_info.?; | 92 | return &self_debug_info.?; |
| 115 | } | 93 | } |
| 116 | } | 94 | } |
| ... | @@ -266,7 +244,7 @@ pub inline fn getContext(context: *ThreadContext) bool { | ... | @@ -266,7 +244,7 @@ pub inline fn getContext(context: *ThreadContext) bool { |
| 266 | /// Tries to print the stack trace starting from the supplied base pointer to stderr, | 244 | /// Tries to print the stack trace starting from the supplied base pointer to stderr, |
| 267 | /// unbuffered, and ignores any error returned. | 245 | /// unbuffered, and ignores any error returned. |
| 268 | /// TODO multithreaded awareness | 246 | /// TODO multithreaded awareness |
| 269 | pub fn dumpStackTraceFromBase(context: *const ThreadContext) void { | 247 | pub fn dumpStackTraceFromBase(context: *ThreadContext) void { |
| 270 | nosuspend { | 248 | nosuspend { |
| 271 | if (comptime builtin.target.isWasm()) { | 249 | if (comptime builtin.target.isWasm()) { |
| 272 | if (native_os == .wasi) { | 250 | if (native_os == .wasi) { |
| ... | @@ -348,7 +326,7 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT | ... | @@ -348,7 +326,7 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT |
| 348 | stack_trace.index = slice.len; | 326 | stack_trace.index = slice.len; |
| 349 | } else { | 327 | } else { |
| 350 | // TODO: This should use the DWARF unwinder if .eh_frame_hdr is available (so that full debug info parsing isn't required). | 328 | // TODO: This should use the DWARF unwinder if .eh_frame_hdr is available (so that full debug info parsing isn't required). |
| 351 | // A new path for loading Info needs to be created which will only attempt to parse in-memory sections, because | 329 | // A new path for loading SelfInfo needs to be created which will only attempt to parse in-memory sections, because |
| 352 | // stopping to load other debug info (ie. source line info) from disk here is not required for unwinding. | 330 | // stopping to load other debug info (ie. source line info) from disk here is not required for unwinding. |
| 353 | var it = StackIterator.init(first_address, null); | 331 | var it = StackIterator.init(first_address, null); |
| 354 | defer it.deinit(); | 332 | defer it.deinit(); |
| ... | @@ -526,7 +504,7 @@ pub fn writeStackTrace( | ... | @@ -526,7 +504,7 @@ pub fn writeStackTrace( |
| 526 | stack_trace: std.builtin.StackTrace, | 504 | stack_trace: std.builtin.StackTrace, |
| 527 | out_stream: anytype, | 505 | out_stream: anytype, |
| 528 | allocator: mem.Allocator, | 506 | allocator: mem.Allocator, |
| 529 | debug_info: *Info, | 507 | debug_info: *SelfInfo, |
| 530 | tty_config: io.tty.Config, | 508 | tty_config: io.tty.Config, |
| 531 | ) !void { | 509 | ) !void { |
| 532 | _ = allocator; | 510 | _ = allocator; |
| ... | @@ -563,12 +541,12 @@ pub const StackIterator = struct { | ... | @@ -563,12 +541,12 @@ pub const StackIterator = struct { |
| 563 | fp: usize, | 541 | fp: usize, |
| 564 | ma: MemoryAccessor = MemoryAccessor.init, | 542 | ma: MemoryAccessor = MemoryAccessor.init, |
| 565 | 543 | ||
| 566 | // When Info and a register context is available, this iterator can unwind | 544 | // When SelfInfo and a register context is available, this iterator can unwind |
| 567 | // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer), | 545 | // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer), |
| 568 | // using DWARF and MachO unwind info. | 546 | // using DWARF and MachO unwind info. |
| 569 | unwind_state: if (have_ucontext) ?struct { | 547 | unwind_state: if (have_ucontext) ?struct { |
| 570 | debug_info: *Info, | 548 | debug_info: *SelfInfo, |
| 571 | dwarf_context: Dwarf.UnwindContext, | 549 | dwarf_context: SelfInfo.UnwindContext, |
| 572 | last_error: ?UnwindError = null, | 550 | last_error: ?UnwindError = null, |
| 573 | failed: bool = false, | 551 | failed: bool = false, |
| 574 | } else void = if (have_ucontext) null else {}, | 552 | } else void = if (have_ucontext) null else {}, |
| ... | @@ -592,20 +570,22 @@ pub const StackIterator = struct { | ... | @@ -592,20 +570,22 @@ pub const StackIterator = struct { |
| 592 | }; | 570 | }; |
| 593 | } | 571 | } |
| 594 | 572 | ||
| 595 | pub fn initWithContext(first_address: ?usize, debug_info: *Info, context: *const posix.ucontext_t) !StackIterator { | 573 | pub fn initWithContext(first_address: ?usize, debug_info: *SelfInfo, context: *posix.ucontext_t) !StackIterator { |
| 596 | // The implementation of DWARF unwinding on aarch64-macos is not complete. However, Apple mandates that | 574 | // The implementation of DWARF unwinding on aarch64-macos is not complete. However, Apple mandates that |
| 597 | // the frame pointer register is always used, so on this platform we can safely use the FP-based unwinder. | 575 | // the frame pointer register is always used, so on this platform we can safely use the FP-based unwinder. |
| 598 | if (comptime builtin.target.isDarwin() and native_arch == .aarch64) { | 576 | if (builtin.target.isDarwin() and native_arch == .aarch64) |
| 599 | return init(first_address, context.mcontext.ss.fp); | 577 | return init(first_address, context.mcontext.ss.fp); |
| 600 | } else { | 578 | |
| 579 | if (SelfInfo.supports_unwinding) { | ||
| 601 | var iterator = init(first_address, null); | 580 | var iterator = init(first_address, null); |
| 602 | iterator.unwind_state = .{ | 581 | iterator.unwind_state = .{ |
| 603 | .debug_info = debug_info, | 582 | .debug_info = debug_info, |
| 604 | .dwarf_context = try Dwarf.UnwindContext.init(debug_info.allocator, context), | 583 | .dwarf_context = try SelfInfo.UnwindContext.init(debug_info.allocator, context), |
| 605 | }; | 584 | }; |
| 606 | |||
| 607 | return iterator; | 585 | return iterator; |
| 608 | } | 586 | } |
| 587 | |||
| 588 | return init(first_address, null); | ||
| 609 | } | 589 | } |
| 610 | 590 | ||
| 611 | pub fn deinit(it: *StackIterator) void { | 591 | pub fn deinit(it: *StackIterator) void { |
| ... | @@ -667,116 +647,6 @@ pub const StackIterator = struct { | ... | @@ -667,116 +647,6 @@ pub const StackIterator = struct { |
| 667 | return address; | 647 | return address; |
| 668 | } | 648 | } |
| 669 | 649 | ||
| 670 | fn isValidMemory(address: usize) bool { | ||
| 671 | // We are unable to determine validity of memory for freestanding targets | ||
| 672 | if (native_os == .freestanding or native_os == .uefi) return true; | ||
| 673 | |||
| 674 | const aligned_address = address & ~@as(usize, @intCast((mem.page_size - 1))); | ||
| 675 | if (aligned_address == 0) return false; | ||
| 676 | const aligned_memory = @as([*]align(mem.page_size) u8, @ptrFromInt(aligned_address))[0..mem.page_size]; | ||
| 677 | |||
| 678 | if (native_os == .windows) { | ||
| 679 | var memory_info: windows.MEMORY_BASIC_INFORMATION = undefined; | ||
| 680 | |||
| 681 | // The only error this function can throw is ERROR_INVALID_PARAMETER. | ||
| 682 | // supply an address that invalid i'll be thrown. | ||
| 683 | const rc = windows.VirtualQuery(aligned_memory, &memory_info, aligned_memory.len) catch { | ||
| 684 | return false; | ||
| 685 | }; | ||
| 686 | |||
| 687 | // Result code has to be bigger than zero (number of bytes written) | ||
| 688 | if (rc == 0) { | ||
| 689 | return false; | ||
| 690 | } | ||
| 691 | |||
| 692 | // Free pages cannot be read, they are unmapped | ||
| 693 | if (memory_info.State == windows.MEM_FREE) { | ||
| 694 | return false; | ||
| 695 | } | ||
| 696 | |||
| 697 | return true; | ||
| 698 | } else if (have_msync) { | ||
| 699 | posix.msync(aligned_memory, posix.MSF.ASYNC) catch |err| { | ||
| 700 | switch (err) { | ||
| 701 | error.UnmappedMemory => return false, | ||
| 702 | else => unreachable, | ||
| 703 | } | ||
| 704 | }; | ||
| 705 | |||
| 706 | return true; | ||
| 707 | } else { | ||
| 708 | // We are unable to determine validity of memory on this target. | ||
| 709 | return true; | ||
| 710 | } | ||
| 711 | } | ||
| 712 | |||
| 713 | pub const MemoryAccessor = struct { | ||
| 714 | var cached_pid: posix.pid_t = -1; | ||
| 715 | |||
| 716 | mem: switch (native_os) { | ||
| 717 | .linux => File, | ||
| 718 | else => void, | ||
| 719 | }, | ||
| 720 | |||
| 721 | pub const init: MemoryAccessor = .{ | ||
| 722 | .mem = switch (native_os) { | ||
| 723 | .linux => .{ .handle = -1 }, | ||
| 724 | else => {}, | ||
| 725 | }, | ||
| 726 | }; | ||
| 727 | |||
| 728 | fn read(ma: *MemoryAccessor, address: usize, buf: []u8) bool { | ||
| 729 | switch (native_os) { | ||
| 730 | .linux => while (true) switch (ma.mem.handle) { | ||
| 731 | -2 => break, | ||
| 732 | -1 => { | ||
| 733 | const linux = std.os.linux; | ||
| 734 | const pid = switch (@atomicLoad(posix.pid_t, &cached_pid, .monotonic)) { | ||
| 735 | -1 => pid: { | ||
| 736 | const pid = linux.getpid(); | ||
| 737 | @atomicStore(posix.pid_t, &cached_pid, pid, .monotonic); | ||
| 738 | break :pid pid; | ||
| 739 | }, | ||
| 740 | else => |pid| pid, | ||
| 741 | }; | ||
| 742 | const bytes_read = linux.process_vm_readv( | ||
| 743 | pid, | ||
| 744 | &.{.{ .base = buf.ptr, .len = buf.len }}, | ||
| 745 | &.{.{ .base = @ptrFromInt(address), .len = buf.len }}, | ||
| 746 | 0, | ||
| 747 | ); | ||
| 748 | switch (linux.E.init(bytes_read)) { | ||
| 749 | .SUCCESS => return bytes_read == buf.len, | ||
| 750 | .FAULT => return false, | ||
| 751 | .INVAL, .PERM, .SRCH => unreachable, // own pid is always valid | ||
| 752 | .NOMEM => {}, | ||
| 753 | .NOSYS => {}, // QEMU is known not to implement this syscall. | ||
| 754 | else => unreachable, // unexpected | ||
| 755 | } | ||
| 756 | var path_buf: [ | ||
| 757 | std.fmt.count("/proc/{d}/mem", .{math.minInt(posix.pid_t)}) | ||
| 758 | ]u8 = undefined; | ||
| 759 | const path = std.fmt.bufPrint(&path_buf, "/proc/{d}/mem", .{pid}) catch | ||
| 760 | unreachable; | ||
| 761 | ma.mem = std.fs.openFileAbsolute(path, .{}) catch { | ||
| 762 | ma.mem.handle = -2; | ||
| 763 | break; | ||
| 764 | }; | ||
| 765 | }, | ||
| 766 | else => return (ma.mem.pread(buf, address) catch return false) == buf.len, | ||
| 767 | }, | ||
| 768 | else => {}, | ||
| 769 | } | ||
| 770 | if (!isValidMemory(address)) return false; | ||
| 771 | @memcpy(buf, @as([*]const u8, @ptrFromInt(address))); | ||
| 772 | return true; | ||
| 773 | } | ||
| 774 | pub fn load(ma: *MemoryAccessor, comptime Type: type, address: usize) ?Type { | ||
| 775 | var result: Type = undefined; | ||
| 776 | return if (ma.read(address, std.mem.asBytes(&result))) result else null; | ||
| 777 | } | ||
| 778 | }; | ||
| 779 | |||
| 780 | fn next_unwind(it: *StackIterator) !usize { | 650 | fn next_unwind(it: *StackIterator) !usize { |
| 781 | const unwind_state = &it.unwind_state.?; | 651 | const unwind_state = &it.unwind_state.?; |
| 782 | const module = try unwind_state.debug_info.getModuleForAddress(unwind_state.dwarf_context.pc); | 652 | const module = try unwind_state.debug_info.getModuleForAddress(unwind_state.dwarf_context.pc); |
| ... | @@ -785,7 +655,13 @@ pub const StackIterator = struct { | ... | @@ -785,7 +655,13 @@ pub const StackIterator = struct { |
| 785 | // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding | 655 | // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding |
| 786 | // via DWARF before attempting to use the compact unwind info will produce incorrect results. | 656 | // via DWARF before attempting to use the compact unwind info will produce incorrect results. |
| 787 | if (module.unwind_info) |unwind_info| { | 657 | if (module.unwind_info) |unwind_info| { |
| 788 | if (Dwarf.unwindFrameMachO(&unwind_state.dwarf_context, &it.ma, unwind_info, module.eh_frame, module.base_address)) |return_address| { | 658 | if (SelfInfo.unwindFrameMachO( |
| 659 | &unwind_state.dwarf_context, | ||
| 660 | &it.ma, | ||
| 661 | unwind_info, | ||
| 662 | module.eh_frame, | ||
| 663 | module.base_address, | ||
| 664 | )) |return_address| { | ||
| 789 | return return_address; | 665 | return return_address; |
| 790 | } else |err| { | 666 | } else |err| { |
| 791 | if (err != error.RequiresDWARFUnwind) return err; | 667 | if (err != error.RequiresDWARFUnwind) return err; |
| ... | @@ -796,7 +672,7 @@ pub const StackIterator = struct { | ... | @@ -796,7 +672,7 @@ pub const StackIterator = struct { |
| 796 | } | 672 | } |
| 797 | 673 | ||
| 798 | if (try module.getDwarfInfoForAddress(unwind_state.debug_info.allocator, unwind_state.dwarf_context.pc)) |di| { | 674 | if (try module.getDwarfInfoForAddress(unwind_state.debug_info.allocator, unwind_state.dwarf_context.pc)) |di| { |
| 799 | return di.unwindFrame(&unwind_state.dwarf_context, &it.ma, null); | 675 | return SelfInfo.unwindFrameDwarf(di, &unwind_state.dwarf_context, &it.ma, null); |
| 800 | } else return error.MissingDebugInfo; | 676 | } else return error.MissingDebugInfo; |
| 801 | } | 677 | } |
| 802 | 678 | ||
| ... | @@ -845,22 +721,19 @@ pub const StackIterator = struct { | ... | @@ -845,22 +721,19 @@ pub const StackIterator = struct { |
| 845 | } | 721 | } |
| 846 | }; | 722 | }; |
| 847 | 723 | ||
| 848 | const have_msync = switch (native_os) { | ||
| 849 | .wasi, .emscripten, .windows => false, | ||
| 850 | else => true, | ||
| 851 | }; | ||
| 852 | |||
| 853 | pub fn writeCurrentStackTrace( | 724 | pub fn writeCurrentStackTrace( |
| 854 | out_stream: anytype, | 725 | out_stream: anytype, |
| 855 | debug_info: *Info, | 726 | debug_info: *SelfInfo, |
| 856 | tty_config: io.tty.Config, | 727 | tty_config: io.tty.Config, |
| 857 | start_addr: ?usize, | 728 | start_addr: ?usize, |
| 858 | ) !void { | 729 | ) !void { |
| 859 | var context: ThreadContext = undefined; | ||
| 860 | const has_context = getContext(&context); | ||
| 861 | if (native_os == .windows) { | 730 | if (native_os == .windows) { |
| 731 | var context: ThreadContext = undefined; | ||
| 732 | assert(getContext(&context)); | ||
| 862 | return writeStackTraceWindows(out_stream, debug_info, tty_config, &context, start_addr); | 733 | return writeStackTraceWindows(out_stream, debug_info, tty_config, &context, start_addr); |
| 863 | } | 734 | } |
| 735 | var context: ThreadContext = undefined; | ||
| 736 | const has_context = getContext(&context); | ||
| 864 | 737 | ||
| 865 | var it = (if (has_context) blk: { | 738 | var it = (if (has_context) blk: { |
| 866 | break :blk StackIterator.initWithContext(start_addr, debug_info, &context) catch null; | 739 | break :blk StackIterator.initWithContext(start_addr, debug_info, &context) catch null; |
| ... | @@ -938,7 +811,7 @@ pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const w | ... | @@ -938,7 +811,7 @@ pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const w |
| 938 | 811 | ||
| 939 | pub fn writeStackTraceWindows( | 812 | pub fn writeStackTraceWindows( |
| 940 | out_stream: anytype, | 813 | out_stream: anytype, |
| 941 | debug_info: *Info, | 814 | debug_info: *SelfInfo, |
| 942 | tty_config: io.tty.Config, | 815 | tty_config: io.tty.Config, |
| 943 | context: *const windows.CONTEXT, | 816 | context: *const windows.CONTEXT, |
| 944 | start_addr: ?usize, | 817 | start_addr: ?usize, |
| ... | @@ -957,52 +830,7 @@ pub fn writeStackTraceWindows( | ... | @@ -957,52 +830,7 @@ pub fn writeStackTraceWindows( |
| 957 | } | 830 | } |
| 958 | } | 831 | } |
| 959 | 832 | ||
| 960 | fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol { | 833 | fn printUnknownSource(debug_info: *SelfInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { |
| 961 | var min: usize = 0; | ||
| 962 | var max: usize = symbols.len - 1; | ||
| 963 | while (min < max) { | ||
| 964 | const mid = min + (max - min) / 2; | ||
| 965 | const curr = &symbols[mid]; | ||
| 966 | const next = &symbols[mid + 1]; | ||
| 967 | if (address >= next.address()) { | ||
| 968 | min = mid + 1; | ||
| 969 | } else if (address < curr.address()) { | ||
| 970 | max = mid; | ||
| 971 | } else { | ||
| 972 | return curr; | ||
| 973 | } | ||
| 974 | } | ||
| 975 | |||
| 976 | const max_sym = &symbols[symbols.len - 1]; | ||
| 977 | if (address >= max_sym.address()) | ||
| 978 | return max_sym; | ||
| 979 | |||
| 980 | return null; | ||
| 981 | } | ||
| 982 | |||
| 983 | test machoSearchSymbols { | ||
| 984 | const symbols = [_]MachoSymbol{ | ||
| 985 | .{ .addr = 100, .strx = undefined, .size = undefined, .ofile = undefined }, | ||
| 986 | .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined }, | ||
| 987 | .{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined }, | ||
| 988 | }; | ||
| 989 | |||
| 990 | try testing.expectEqual(null, machoSearchSymbols(&symbols, 0)); | ||
| 991 | try testing.expectEqual(null, machoSearchSymbols(&symbols, 99)); | ||
| 992 | try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 100).?); | ||
| 993 | try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 150).?); | ||
| 994 | try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 199).?); | ||
| 995 | |||
| 996 | try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 200).?); | ||
| 997 | try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 250).?); | ||
| 998 | try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 299).?); | ||
| 999 | |||
| 1000 | try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 300).?); | ||
| 1001 | try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 301).?); | ||
| 1002 | try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?); | ||
| 1003 | } | ||
| 1004 | |||
| 1005 | fn printUnknownSource(debug_info: *Info, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { | ||
| 1006 | const module_name = debug_info.getModuleNameForAddress(address); | 834 | const module_name = debug_info.getModuleNameForAddress(address); |
| 1007 | return printLineInfo( | 835 | return printLineInfo( |
| 1008 | out_stream, | 836 | out_stream, |
| ... | @@ -1015,14 +843,14 @@ fn printUnknownSource(debug_info: *Info, out_stream: anytype, address: usize, tt | ... | @@ -1015,14 +843,14 @@ fn printUnknownSource(debug_info: *Info, out_stream: anytype, address: usize, tt |
| 1015 | ); | 843 | ); |
| 1016 | } | 844 | } |
| 1017 | 845 | ||
| 1018 | fn printLastUnwindError(it: *StackIterator, debug_info: *Info, out_stream: anytype, tty_config: io.tty.Config) void { | 846 | fn printLastUnwindError(it: *StackIterator, debug_info: *SelfInfo, out_stream: anytype, tty_config: io.tty.Config) void { |
| 1019 | if (!have_ucontext) return; | 847 | if (!have_ucontext) return; |
| 1020 | if (it.getLastError()) |unwind_error| { | 848 | if (it.getLastError()) |unwind_error| { |
| 1021 | printUnwindError(debug_info, out_stream, unwind_error.address, unwind_error.err, tty_config) catch {}; | 849 | printUnwindError(debug_info, out_stream, unwind_error.address, unwind_error.err, tty_config) catch {}; |
| 1022 | } | 850 | } |
| 1023 | } | 851 | } |
| 1024 | 852 | ||
| 1025 | fn printUnwindError(debug_info: *Info, out_stream: anytype, address: usize, err: UnwindError, tty_config: io.tty.Config) !void { | 853 | fn printUnwindError(debug_info: *SelfInfo, out_stream: anytype, address: usize, err: UnwindError, tty_config: io.tty.Config) !void { |
| 1026 | const module_name = debug_info.getModuleNameForAddress(address) orelse "???"; | 854 | const module_name = debug_info.getModuleNameForAddress(address) orelse "???"; |
| 1027 | try tty_config.setColor(out_stream, .dim); | 855 | try tty_config.setColor(out_stream, .dim); |
| 1028 | if (err == error.MissingDebugInfo) { | 856 | if (err == error.MissingDebugInfo) { |
| ... | @@ -1033,7 +861,7 @@ fn printUnwindError(debug_info: *Info, out_stream: anytype, address: usize, err: | ... | @@ -1033,7 +861,7 @@ fn printUnwindError(debug_info: *Info, out_stream: anytype, address: usize, err: |
| 1033 | try tty_config.setColor(out_stream, .reset); | 861 | try tty_config.setColor(out_stream, .reset); |
| 1034 | } | 862 | } |
| 1035 | 863 | ||
| 1036 | pub fn printSourceAtAddress(debug_info: *Info, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { | 864 | pub fn printSourceAtAddress(debug_info: *SelfInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { |
| 1037 | const module = debug_info.getModuleForAddress(address) catch |err| switch (err) { | 865 | const module = debug_info.getModuleForAddress(address) catch |err| switch (err) { |
| 1038 | error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config), | 866 | error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config), |
| 1039 | else => return err, | 867 | else => return err, |
| ... | @@ -1058,7 +886,7 @@ pub fn printSourceAtAddress(debug_info: *Info, out_stream: anytype, address: usi | ... | @@ -1058,7 +886,7 @@ pub fn printSourceAtAddress(debug_info: *Info, out_stream: anytype, address: usi |
| 1058 | 886 | ||
| 1059 | fn printLineInfo( | 887 | fn printLineInfo( |
| 1060 | out_stream: anytype, | 888 | out_stream: anytype, |
| 1061 | line_info: ?LineInfo, | 889 | line_info: ?SourceLocation, |
| 1062 | address: usize, | 890 | address: usize, |
| 1063 | symbol_name: []const u8, | 891 | symbol_name: []const u8, |
| 1064 | compile_unit_name: []const u8, | 892 | compile_unit_name: []const u8, |
| ... | @@ -1104,428 +932,7 @@ fn printLineInfo( | ... | @@ -1104,428 +932,7 @@ fn printLineInfo( |
| 1104 | } | 932 | } |
| 1105 | } | 933 | } |
| 1106 | 934 | ||
| 1107 | pub const OpenSelfDebugInfoError = error{ | 935 | fn printLineFromFileAnyOs(out_stream: anytype, line_info: SourceLocation) !void { |
| 1108 | MissingDebugInfo, | ||
| 1109 | UnsupportedOperatingSystem, | ||
| 1110 | } || @typeInfo(@typeInfo(@TypeOf(Info.init)).Fn.return_type.?).ErrorUnion.error_set; | ||
| 1111 | |||
| 1112 | pub fn openSelfDebugInfo(allocator: mem.Allocator) OpenSelfDebugInfoError!Info { | ||
| 1113 | nosuspend { | ||
| 1114 | if (builtin.strip_debug_info) | ||
| 1115 | return error.MissingDebugInfo; | ||
| 1116 | if (@hasDecl(root, "os") and @hasDecl(root.os, "debug") and @hasDecl(root.os.debug, "openSelfDebugInfo")) { | ||
| 1117 | return root.os.debug.openSelfDebugInfo(allocator); | ||
| 1118 | } | ||
| 1119 | switch (native_os) { | ||
| 1120 | .linux, | ||
| 1121 | .freebsd, | ||
| 1122 | .netbsd, | ||
| 1123 | .dragonfly, | ||
| 1124 | .openbsd, | ||
| 1125 | .macos, | ||
| 1126 | .solaris, | ||
| 1127 | .illumos, | ||
| 1128 | .windows, | ||
| 1129 | => return try Info.init(allocator), | ||
| 1130 | else => return error.UnsupportedOperatingSystem, | ||
| 1131 | } | ||
| 1132 | } | ||
| 1133 | } | ||
| 1134 | |||
| 1135 | fn readCoffDebugInfo(allocator: mem.Allocator, coff_obj: *coff.Coff) !ModuleDebugInfo { | ||
| 1136 | nosuspend { | ||
| 1137 | var di = ModuleDebugInfo{ | ||
| 1138 | .base_address = undefined, | ||
| 1139 | .coff_image_base = coff_obj.getImageBase(), | ||
| 1140 | .coff_section_headers = undefined, | ||
| 1141 | }; | ||
| 1142 | |||
| 1143 | if (coff_obj.getSectionByName(".debug_info")) |_| { | ||
| 1144 | // This coff file has embedded DWARF debug info | ||
| 1145 | var sections: Dwarf.SectionArray = Dwarf.null_section_array; | ||
| 1146 | errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data); | ||
| 1147 | |||
| 1148 | inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| { | ||
| 1149 | sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: { | ||
| 1150 | break :blk .{ | ||
| 1151 | .data = try coff_obj.getSectionDataAlloc(section_header, allocator), | ||
| 1152 | .virtual_address = section_header.virtual_address, | ||
| 1153 | .owned = true, | ||
| 1154 | }; | ||
| 1155 | } else null; | ||
| 1156 | } | ||
| 1157 | |||
| 1158 | var dwarf = Dwarf{ | ||
| 1159 | .endian = native_endian, | ||
| 1160 | .sections = sections, | ||
| 1161 | .is_macho = false, | ||
| 1162 | }; | ||
| 1163 | |||
| 1164 | try Dwarf.open(&dwarf, allocator); | ||
| 1165 | di.dwarf = dwarf; | ||
| 1166 | } | ||
| 1167 | |||
| 1168 | const raw_path = try coff_obj.getPdbPath() orelse return di; | ||
| 1169 | const path = blk: { | ||
| 1170 | if (fs.path.isAbsolute(raw_path)) { | ||
| 1171 | break :blk raw_path; | ||
| 1172 | } else { | ||
| 1173 | const self_dir = try fs.selfExeDirPathAlloc(allocator); | ||
| 1174 | defer allocator.free(self_dir); | ||
| 1175 | break :blk try fs.path.join(allocator, &.{ self_dir, raw_path }); | ||
| 1176 | } | ||
| 1177 | }; | ||
| 1178 | defer if (path.ptr != raw_path.ptr) allocator.free(path); | ||
| 1179 | |||
| 1180 | di.pdb = pdb.Pdb.init(allocator, path) catch |err| switch (err) { | ||
| 1181 | error.FileNotFound, error.IsDir => { | ||
| 1182 | if (di.dwarf == null) return error.MissingDebugInfo; | ||
| 1183 | return di; | ||
| 1184 | }, | ||
| 1185 | else => return err, | ||
| 1186 | }; | ||
| 1187 | try di.pdb.?.parseInfoStream(); | ||
| 1188 | try di.pdb.?.parseDbiStream(); | ||
| 1189 | |||
| 1190 | if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age) | ||
| 1191 | return error.InvalidDebugInfo; | ||
| 1192 | |||
| 1193 | // Only used by the pdb path | ||
| 1194 | di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator); | ||
| 1195 | errdefer allocator.free(di.coff_section_headers); | ||
| 1196 | |||
| 1197 | return di; | ||
| 1198 | } | ||
| 1199 | } | ||
| 1200 | |||
| 1201 | fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 { | ||
| 1202 | const start = math.cast(usize, offset) orelse return error.Overflow; | ||
| 1203 | const end = start + (math.cast(usize, size) orelse return error.Overflow); | ||
| 1204 | return ptr[start..end]; | ||
| 1205 | } | ||
| 1206 | |||
| 1207 | /// Reads debug info from an ELF file, or the current binary if none in specified. | ||
| 1208 | /// If the required sections aren't present but a reference to external debug info is, | ||
| 1209 | /// then this this function will recurse to attempt to load the debug sections from | ||
| 1210 | /// an external file. | ||
| 1211 | pub fn readElfDebugInfo( | ||
| 1212 | allocator: mem.Allocator, | ||
| 1213 | elf_filename: ?[]const u8, | ||
| 1214 | build_id: ?[]const u8, | ||
| 1215 | expected_crc: ?u32, | ||
| 1216 | parent_sections: *Dwarf.SectionArray, | ||
| 1217 | parent_mapped_mem: ?[]align(mem.page_size) const u8, | ||
| 1218 | ) !ModuleDebugInfo { | ||
| 1219 | nosuspend { | ||
| 1220 | const elf_file = (if (elf_filename) |filename| blk: { | ||
| 1221 | break :blk fs.cwd().openFile(filename, .{}); | ||
| 1222 | } else fs.openSelfExe(.{})) catch |err| switch (err) { | ||
| 1223 | error.FileNotFound => return error.MissingDebugInfo, | ||
| 1224 | else => return err, | ||
| 1225 | }; | ||
| 1226 | |||
| 1227 | const mapped_mem = try mapWholeFile(elf_file); | ||
| 1228 | if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo; | ||
| 1229 | |||
| 1230 | const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]); | ||
| 1231 | if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; | ||
| 1232 | if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; | ||
| 1233 | |||
| 1234 | const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) { | ||
| 1235 | elf.ELFDATA2LSB => .little, | ||
| 1236 | elf.ELFDATA2MSB => .big, | ||
| 1237 | else => return error.InvalidElfEndian, | ||
| 1238 | }; | ||
| 1239 | assert(endian == native_endian); // this is our own debug info | ||
| 1240 | |||
| 1241 | const shoff = hdr.e_shoff; | ||
| 1242 | const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx); | ||
| 1243 | const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[math.cast(usize, str_section_off) orelse return error.Overflow])); | ||
| 1244 | const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size]; | ||
| 1245 | const shdrs = @as( | ||
| 1246 | [*]const elf.Shdr, | ||
| 1247 | @ptrCast(@alignCast(&mapped_mem[shoff])), | ||
| 1248 | )[0..hdr.e_shnum]; | ||
| 1249 | |||
| 1250 | var sections: Dwarf.SectionArray = Dwarf.null_section_array; | ||
| 1251 | |||
| 1252 | // Combine section list. This takes ownership over any owned sections from the parent scope. | ||
| 1253 | for (parent_sections, &sections) |*parent, *section| { | ||
| 1254 | if (parent.*) |*p| { | ||
| 1255 | section.* = p.*; | ||
| 1256 | p.owned = false; | ||
| 1257 | } | ||
| 1258 | } | ||
| 1259 | errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data); | ||
| 1260 | |||
| 1261 | var separate_debug_filename: ?[]const u8 = null; | ||
| 1262 | var separate_debug_crc: ?u32 = null; | ||
| 1263 | |||
| 1264 | for (shdrs) |*shdr| { | ||
| 1265 | if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue; | ||
| 1266 | const name = mem.sliceTo(header_strings[shdr.sh_name..], 0); | ||
| 1267 | |||
| 1268 | if (mem.eql(u8, name, ".gnu_debuglink")) { | ||
| 1269 | const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | ||
| 1270 | const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0); | ||
| 1271 | const crc_offset = mem.alignForward(usize, @intFromPtr(&debug_filename[debug_filename.len]) + 1, 4) - @intFromPtr(gnu_debuglink.ptr); | ||
| 1272 | const crc_bytes = gnu_debuglink[crc_offset..][0..4]; | ||
| 1273 | separate_debug_crc = mem.readInt(u32, crc_bytes, native_endian); | ||
| 1274 | separate_debug_filename = debug_filename; | ||
| 1275 | continue; | ||
| 1276 | } | ||
| 1277 | |||
| 1278 | var section_index: ?usize = null; | ||
| 1279 | inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| { | ||
| 1280 | if (mem.eql(u8, "." ++ section.name, name)) section_index = i; | ||
| 1281 | } | ||
| 1282 | if (section_index == null) continue; | ||
| 1283 | if (sections[section_index.?] != null) continue; | ||
| 1284 | |||
| 1285 | const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | ||
| 1286 | sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: { | ||
| 1287 | var section_stream = io.fixedBufferStream(section_bytes); | ||
| 1288 | var section_reader = section_stream.reader(); | ||
| 1289 | const chdr = section_reader.readStruct(elf.Chdr) catch continue; | ||
| 1290 | if (chdr.ch_type != .ZLIB) continue; | ||
| 1291 | |||
| 1292 | var zlib_stream = std.compress.zlib.decompressor(section_stream.reader()); | ||
| 1293 | |||
| 1294 | const decompressed_section = try allocator.alloc(u8, chdr.ch_size); | ||
| 1295 | errdefer allocator.free(decompressed_section); | ||
| 1296 | |||
| 1297 | const read = zlib_stream.reader().readAll(decompressed_section) catch continue; | ||
| 1298 | assert(read == decompressed_section.len); | ||
| 1299 | |||
| 1300 | break :blk .{ | ||
| 1301 | .data = decompressed_section, | ||
| 1302 | .virtual_address = shdr.sh_addr, | ||
| 1303 | .owned = true, | ||
| 1304 | }; | ||
| 1305 | } else .{ | ||
| 1306 | .data = section_bytes, | ||
| 1307 | .virtual_address = shdr.sh_addr, | ||
| 1308 | .owned = false, | ||
| 1309 | }; | ||
| 1310 | } | ||
| 1311 | |||
| 1312 | const missing_debug_info = | ||
| 1313 | sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or | ||
| 1314 | sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or | ||
| 1315 | sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or | ||
| 1316 | sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; | ||
| 1317 | |||
| 1318 | // Attempt to load debug info from an external file | ||
| 1319 | // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html | ||
| 1320 | if (missing_debug_info) { | ||
| 1321 | |||
| 1322 | // Only allow one level of debug info nesting | ||
| 1323 | if (parent_mapped_mem) |_| { | ||
| 1324 | return error.MissingDebugInfo; | ||
| 1325 | } | ||
| 1326 | |||
| 1327 | const global_debug_directories = [_][]const u8{ | ||
| 1328 | "/usr/lib/debug", | ||
| 1329 | }; | ||
| 1330 | |||
| 1331 | // <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.debug | ||
| 1332 | if (build_id) |id| blk: { | ||
| 1333 | if (id.len < 3) break :blk; | ||
| 1334 | |||
| 1335 | // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice | ||
| 1336 | const extension = ".debug"; | ||
| 1337 | var id_prefix_buf: [2]u8 = undefined; | ||
| 1338 | var filename_buf: [38 + extension.len]u8 = undefined; | ||
| 1339 | |||
| 1340 | _ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable; | ||
| 1341 | const filename = std.fmt.bufPrint( | ||
| 1342 | &filename_buf, | ||
| 1343 | "{s}" ++ extension, | ||
| 1344 | .{std.fmt.fmtSliceHexLower(id[1..])}, | ||
| 1345 | ) catch break :blk; | ||
| 1346 | |||
| 1347 | for (global_debug_directories) |global_directory| { | ||
| 1348 | const path = try fs.path.join(allocator, &.{ global_directory, ".build-id", &id_prefix_buf, filename }); | ||
| 1349 | defer allocator.free(path); | ||
| 1350 | |||
| 1351 | return readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem) catch continue; | ||
| 1352 | } | ||
| 1353 | } | ||
| 1354 | |||
| 1355 | // use the path from .gnu_debuglink, in the same search order as gdb | ||
| 1356 | if (separate_debug_filename) |separate_filename| blk: { | ||
| 1357 | if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) return error.MissingDebugInfo; | ||
| 1358 | |||
| 1359 | // <cwd>/<gnu_debuglink> | ||
| 1360 | if (readElfDebugInfo(allocator, separate_filename, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {} | ||
| 1361 | |||
| 1362 | // <cwd>/.debug/<gnu_debuglink> | ||
| 1363 | { | ||
| 1364 | const path = try fs.path.join(allocator, &.{ ".debug", separate_filename }); | ||
| 1365 | defer allocator.free(path); | ||
| 1366 | |||
| 1367 | if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {} | ||
| 1368 | } | ||
| 1369 | |||
| 1370 | var cwd_buf: [fs.max_path_bytes]u8 = undefined; | ||
| 1371 | const cwd_path = posix.realpath(".", &cwd_buf) catch break :blk; | ||
| 1372 | |||
| 1373 | // <global debug directory>/<absolute folder of current binary>/<gnu_debuglink> | ||
| 1374 | for (global_debug_directories) |global_directory| { | ||
| 1375 | const path = try fs.path.join(allocator, &.{ global_directory, cwd_path, separate_filename }); | ||
| 1376 | defer allocator.free(path); | ||
| 1377 | if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {} | ||
| 1378 | } | ||
| 1379 | } | ||
| 1380 | |||
| 1381 | return error.MissingDebugInfo; | ||
| 1382 | } | ||
| 1383 | |||
| 1384 | var di = Dwarf{ | ||
| 1385 | .endian = endian, | ||
| 1386 | .sections = sections, | ||
| 1387 | .is_macho = false, | ||
| 1388 | }; | ||
| 1389 | |||
| 1390 | try Dwarf.open(&di, allocator); | ||
| 1391 | |||
| 1392 | return ModuleDebugInfo{ | ||
| 1393 | .base_address = undefined, | ||
| 1394 | .dwarf = di, | ||
| 1395 | .mapped_memory = parent_mapped_mem orelse mapped_mem, | ||
| 1396 | .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null, | ||
| 1397 | }; | ||
| 1398 | } | ||
| 1399 | } | ||
| 1400 | |||
| 1401 | /// This takes ownership of macho_file: users of this function should not close | ||
| 1402 | /// it themselves, even on error. | ||
| 1403 | /// TODO it's weird to take ownership even on error, rework this code. | ||
| 1404 | fn readMachODebugInfo(allocator: mem.Allocator, macho_file: File) !ModuleDebugInfo { | ||
| 1405 | const mapped_mem = try mapWholeFile(macho_file); | ||
| 1406 | |||
| 1407 | const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr)); | ||
| 1408 | if (hdr.magic != macho.MH_MAGIC_64) | ||
| 1409 | return error.InvalidDebugInfo; | ||
| 1410 | |||
| 1411 | var it = macho.LoadCommandIterator{ | ||
| 1412 | .ncmds = hdr.ncmds, | ||
| 1413 | .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], | ||
| 1414 | }; | ||
| 1415 | const symtab = while (it.next()) |cmd| switch (cmd.cmd()) { | ||
| 1416 | .SYMTAB => break cmd.cast(macho.symtab_command).?, | ||
| 1417 | else => {}, | ||
| 1418 | } else return error.MissingDebugInfo; | ||
| 1419 | |||
| 1420 | const syms = @as( | ||
| 1421 | [*]const macho.nlist_64, | ||
| 1422 | @ptrCast(@alignCast(&mapped_mem[symtab.symoff])), | ||
| 1423 | )[0..symtab.nsyms]; | ||
| 1424 | const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0]; | ||
| 1425 | |||
| 1426 | const symbols_buf = try allocator.alloc(MachoSymbol, syms.len); | ||
| 1427 | |||
| 1428 | var ofile: u32 = undefined; | ||
| 1429 | var last_sym: MachoSymbol = undefined; | ||
| 1430 | var symbol_index: usize = 0; | ||
| 1431 | var state: enum { | ||
| 1432 | init, | ||
| 1433 | oso_open, | ||
| 1434 | oso_close, | ||
| 1435 | bnsym, | ||
| 1436 | fun_strx, | ||
| 1437 | fun_size, | ||
| 1438 | ensym, | ||
| 1439 | } = .init; | ||
| 1440 | |||
| 1441 | for (syms) |*sym| { | ||
| 1442 | if (!sym.stab()) continue; | ||
| 1443 | |||
| 1444 | // TODO handle globals N_GSYM, and statics N_STSYM | ||
| 1445 | switch (sym.n_type) { | ||
| 1446 | macho.N_OSO => { | ||
| 1447 | switch (state) { | ||
| 1448 | .init, .oso_close => { | ||
| 1449 | state = .oso_open; | ||
| 1450 | ofile = sym.n_strx; | ||
| 1451 | }, | ||
| 1452 | else => return error.InvalidDebugInfo, | ||
| 1453 | } | ||
| 1454 | }, | ||
| 1455 | macho.N_BNSYM => { | ||
| 1456 | switch (state) { | ||
| 1457 | .oso_open, .ensym => { | ||
| 1458 | state = .bnsym; | ||
| 1459 | last_sym = .{ | ||
| 1460 | .strx = 0, | ||
| 1461 | .addr = sym.n_value, | ||
| 1462 | .size = 0, | ||
| 1463 | .ofile = ofile, | ||
| 1464 | }; | ||
| 1465 | }, | ||
| 1466 | else => return error.InvalidDebugInfo, | ||
| 1467 | } | ||
| 1468 | }, | ||
| 1469 | macho.N_FUN => { | ||
| 1470 | switch (state) { | ||
| 1471 | .bnsym => { | ||
| 1472 | state = .fun_strx; | ||
| 1473 | last_sym.strx = sym.n_strx; | ||
| 1474 | }, | ||
| 1475 | .fun_strx => { | ||
| 1476 | state = .fun_size; | ||
| 1477 | last_sym.size = @as(u32, @intCast(sym.n_value)); | ||
| 1478 | }, | ||
| 1479 | else => return error.InvalidDebugInfo, | ||
| 1480 | } | ||
| 1481 | }, | ||
| 1482 | macho.N_ENSYM => { | ||
| 1483 | switch (state) { | ||
| 1484 | .fun_size => { | ||
| 1485 | state = .ensym; | ||
| 1486 | symbols_buf[symbol_index] = last_sym; | ||
| 1487 | symbol_index += 1; | ||
| 1488 | }, | ||
| 1489 | else => return error.InvalidDebugInfo, | ||
| 1490 | } | ||
| 1491 | }, | ||
| 1492 | macho.N_SO => { | ||
| 1493 | switch (state) { | ||
| 1494 | .init, .oso_close => {}, | ||
| 1495 | .oso_open, .ensym => { | ||
| 1496 | state = .oso_close; | ||
| 1497 | }, | ||
| 1498 | else => return error.InvalidDebugInfo, | ||
| 1499 | } | ||
| 1500 | }, | ||
| 1501 | else => {}, | ||
| 1502 | } | ||
| 1503 | } | ||
| 1504 | |||
| 1505 | switch (state) { | ||
| 1506 | .init => return error.MissingDebugInfo, | ||
| 1507 | .oso_close => {}, | ||
| 1508 | else => return error.InvalidDebugInfo, | ||
| 1509 | } | ||
| 1510 | |||
| 1511 | const symbols = try allocator.realloc(symbols_buf, symbol_index); | ||
| 1512 | |||
| 1513 | // Even though lld emits symbols in ascending order, this debug code | ||
| 1514 | // should work for programs linked in any valid way. | ||
| 1515 | // This sort is so that we can binary search later. | ||
| 1516 | mem.sort(MachoSymbol, symbols, {}, MachoSymbol.addressLessThan); | ||
| 1517 | |||
| 1518 | return ModuleDebugInfo{ | ||
| 1519 | .base_address = undefined, | ||
| 1520 | .vmaddr_slide = undefined, | ||
| 1521 | .mapped_memory = mapped_mem, | ||
| 1522 | .ofiles = ModuleDebugInfo.OFileTable.init(allocator), | ||
| 1523 | .symbols = symbols, | ||
| 1524 | .strings = strings, | ||
| 1525 | }; | ||
| 1526 | } | ||
| 1527 | |||
| 1528 | fn printLineFromFileAnyOs(out_stream: anytype, line_info: LineInfo) !void { | ||
| 1529 | // Need this to always block even in async I/O mode, because this could potentially | 936 | // Need this to always block even in async I/O mode, because this could potentially |
| 1530 | // be called from e.g. the event loop code crashing. | 937 | // be called from e.g. the event loop code crashing. |
| 1531 | var f = try fs.cwd().openFile(line_info.file_name, .{}); | 938 | var f = try fs.cwd().openFile(line_info.file_name, .{}); |
| ... | @@ -1591,7 +998,7 @@ test printLineFromFileAnyOs { | ... | @@ -1591,7 +998,7 @@ test printLineFromFileAnyOs { |
| 1591 | 998 | ||
| 1592 | var test_dir = std.testing.tmpDir(.{}); | 999 | var test_dir = std.testing.tmpDir(.{}); |
| 1593 | defer test_dir.cleanup(); | 1000 | defer test_dir.cleanup(); |
| 1594 | // Relies on testing.tmpDir internals which is not ideal, but LineInfo requires paths. | 1001 | // Relies on testing.tmpDir internals which is not ideal, but SourceLocation requires paths. |
| 1595 | const test_dir_path = try join(allocator, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] }); | 1002 | const test_dir_path = try join(allocator, &.{ ".zig-cache", "tmp", test_dir.sub_path[0..] }); |
| 1596 | defer allocator.free(test_dir_path); | 1003 | defer allocator.free(test_dir_path); |
| 1597 | 1004 | ||
| ... | @@ -1702,871 +1109,6 @@ test printLineFromFileAnyOs { | ... | @@ -1702,871 +1109,6 @@ test printLineFromFileAnyOs { |
| 1702 | } | 1109 | } |
| 1703 | } | 1110 | } |
| 1704 | 1111 | ||
| 1705 | const MachoSymbol = struct { | ||
| 1706 | strx: u32, | ||
| 1707 | addr: u64, | ||
| 1708 | size: u32, | ||
| 1709 | ofile: u32, | ||
| 1710 | |||
| 1711 | /// Returns the address from the macho file | ||
| 1712 | fn address(self: MachoSymbol) u64 { | ||
| 1713 | return self.addr; | ||
| 1714 | } | ||
| 1715 | |||
| 1716 | fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool { | ||
| 1717 | _ = context; | ||
| 1718 | return lhs.addr < rhs.addr; | ||
| 1719 | } | ||
| 1720 | }; | ||
| 1721 | |||
| 1722 | /// Takes ownership of file, even on error. | ||
| 1723 | /// TODO it's weird to take ownership even on error, rework this code. | ||
| 1724 | fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 { | ||
| 1725 | nosuspend { | ||
| 1726 | defer file.close(); | ||
| 1727 | |||
| 1728 | const file_len = math.cast(usize, try file.getEndPos()) orelse math.maxInt(usize); | ||
| 1729 | const mapped_mem = try posix.mmap( | ||
| 1730 | null, | ||
| 1731 | file_len, | ||
| 1732 | posix.PROT.READ, | ||
| 1733 | .{ .TYPE = .SHARED }, | ||
| 1734 | file.handle, | ||
| 1735 | 0, | ||
| 1736 | ); | ||
| 1737 | errdefer posix.munmap(mapped_mem); | ||
| 1738 | |||
| 1739 | return mapped_mem; | ||
| 1740 | } | ||
| 1741 | } | ||
| 1742 | |||
| 1743 | pub const WindowsModuleInfo = struct { | ||
| 1744 | base_address: usize, | ||
| 1745 | size: u32, | ||
| 1746 | name: []const u8, | ||
| 1747 | handle: windows.HMODULE, | ||
| 1748 | |||
| 1749 | // Set when the image file needed to be mapped from disk | ||
| 1750 | mapped_file: ?struct { | ||
| 1751 | file: File, | ||
| 1752 | section_handle: windows.HANDLE, | ||
| 1753 | section_view: []const u8, | ||
| 1754 | |||
| 1755 | pub fn deinit(self: @This()) void { | ||
| 1756 | const process_handle = windows.GetCurrentProcess(); | ||
| 1757 | assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(@ptrCast(self.section_view.ptr))) == .SUCCESS); | ||
| 1758 | windows.CloseHandle(self.section_handle); | ||
| 1759 | self.file.close(); | ||
| 1760 | } | ||
| 1761 | } = null, | ||
| 1762 | }; | ||
| 1763 | |||
| 1764 | pub const Info = struct { | ||
| 1765 | allocator: mem.Allocator, | ||
| 1766 | address_map: std.AutoHashMap(usize, *ModuleDebugInfo), | ||
| 1767 | modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModuleInfo) else void, | ||
| 1768 | |||
| 1769 | pub fn init(allocator: mem.Allocator) !Info { | ||
| 1770 | var debug_info = Info{ | ||
| 1771 | .allocator = allocator, | ||
| 1772 | .address_map = std.AutoHashMap(usize, *ModuleDebugInfo).init(allocator), | ||
| 1773 | .modules = if (native_os == .windows) .{} else {}, | ||
| 1774 | }; | ||
| 1775 | |||
| 1776 | if (native_os == .windows) { | ||
| 1777 | errdefer debug_info.modules.deinit(allocator); | ||
| 1778 | |||
| 1779 | const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0); | ||
| 1780 | if (handle == windows.INVALID_HANDLE_VALUE) { | ||
| 1781 | switch (windows.GetLastError()) { | ||
| 1782 | else => |err| return windows.unexpectedError(err), | ||
| 1783 | } | ||
| 1784 | } | ||
| 1785 | defer windows.CloseHandle(handle); | ||
| 1786 | |||
| 1787 | var module_entry: windows.MODULEENTRY32 = undefined; | ||
| 1788 | module_entry.dwSize = @sizeOf(windows.MODULEENTRY32); | ||
| 1789 | if (windows.kernel32.Module32First(handle, &module_entry) == 0) { | ||
| 1790 | return error.MissingDebugInfo; | ||
| 1791 | } | ||
| 1792 | |||
| 1793 | var module_valid = true; | ||
| 1794 | while (module_valid) { | ||
| 1795 | const module_info = try debug_info.modules.addOne(allocator); | ||
| 1796 | const name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{}; | ||
| 1797 | errdefer allocator.free(name); | ||
| 1798 | |||
| 1799 | module_info.* = .{ | ||
| 1800 | .base_address = @intFromPtr(module_entry.modBaseAddr), | ||
| 1801 | .size = module_entry.modBaseSize, | ||
| 1802 | .name = name, | ||
| 1803 | .handle = module_entry.hModule, | ||
| 1804 | }; | ||
| 1805 | |||
| 1806 | module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1; | ||
| 1807 | } | ||
| 1808 | } | ||
| 1809 | |||
| 1810 | return debug_info; | ||
| 1811 | } | ||
| 1812 | |||
| 1813 | pub fn deinit(self: *Info) void { | ||
| 1814 | var it = self.address_map.iterator(); | ||
| 1815 | while (it.next()) |entry| { | ||
| 1816 | const mdi = entry.value_ptr.*; | ||
| 1817 | mdi.deinit(self.allocator); | ||
| 1818 | self.allocator.destroy(mdi); | ||
| 1819 | } | ||
| 1820 | self.address_map.deinit(); | ||
| 1821 | if (native_os == .windows) { | ||
| 1822 | for (self.modules.items) |module| { | ||
| 1823 | self.allocator.free(module.name); | ||
| 1824 | if (module.mapped_file) |mapped_file| mapped_file.deinit(); | ||
| 1825 | } | ||
| 1826 | self.modules.deinit(self.allocator); | ||
| 1827 | } | ||
| 1828 | } | ||
| 1829 | |||
| 1830 | pub fn getModuleForAddress(self: *Info, address: usize) !*ModuleDebugInfo { | ||
| 1831 | if (comptime builtin.target.isDarwin()) { | ||
| 1832 | return self.lookupModuleDyld(address); | ||
| 1833 | } else if (native_os == .windows) { | ||
| 1834 | return self.lookupModuleWin32(address); | ||
| 1835 | } else if (native_os == .haiku) { | ||
| 1836 | return self.lookupModuleHaiku(address); | ||
| 1837 | } else if (comptime builtin.target.isWasm()) { | ||
| 1838 | return self.lookupModuleWasm(address); | ||
| 1839 | } else { | ||
| 1840 | return self.lookupModuleDl(address); | ||
| 1841 | } | ||
| 1842 | } | ||
| 1843 | |||
| 1844 | // Returns the module name for a given address. | ||
| 1845 | // This can be called when getModuleForAddress fails, so implementations should provide | ||
| 1846 | // a path that doesn't rely on any side-effects of a prior successful module lookup. | ||
| 1847 | pub fn getModuleNameForAddress(self: *Info, address: usize) ?[]const u8 { | ||
| 1848 | if (comptime builtin.target.isDarwin()) { | ||
| 1849 | return self.lookupModuleNameDyld(address); | ||
| 1850 | } else if (native_os == .windows) { | ||
| 1851 | return self.lookupModuleNameWin32(address); | ||
| 1852 | } else if (native_os == .haiku) { | ||
| 1853 | return null; | ||
| 1854 | } else if (comptime builtin.target.isWasm()) { | ||
| 1855 | return null; | ||
| 1856 | } else { | ||
| 1857 | return self.lookupModuleNameDl(address); | ||
| 1858 | } | ||
| 1859 | } | ||
| 1860 | |||
| 1861 | fn lookupModuleDyld(self: *Info, address: usize) !*ModuleDebugInfo { | ||
| 1862 | const image_count = std.c._dyld_image_count(); | ||
| 1863 | |||
| 1864 | var i: u32 = 0; | ||
| 1865 | while (i < image_count) : (i += 1) { | ||
| 1866 | const header = std.c._dyld_get_image_header(i) orelse continue; | ||
| 1867 | const base_address = @intFromPtr(header); | ||
| 1868 | if (address < base_address) continue; | ||
| 1869 | const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i); | ||
| 1870 | |||
| 1871 | var it = macho.LoadCommandIterator{ | ||
| 1872 | .ncmds = header.ncmds, | ||
| 1873 | .buffer = @alignCast(@as( | ||
| 1874 | [*]u8, | ||
| 1875 | @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)), | ||
| 1876 | )[0..header.sizeofcmds]), | ||
| 1877 | }; | ||
| 1878 | |||
| 1879 | var unwind_info: ?[]const u8 = null; | ||
| 1880 | var eh_frame: ?[]const u8 = null; | ||
| 1881 | while (it.next()) |cmd| switch (cmd.cmd()) { | ||
| 1882 | .SEGMENT_64 => { | ||
| 1883 | const segment_cmd = cmd.cast(macho.segment_command_64).?; | ||
| 1884 | if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue; | ||
| 1885 | |||
| 1886 | const seg_start = segment_cmd.vmaddr + vmaddr_slide; | ||
| 1887 | const seg_end = seg_start + segment_cmd.vmsize; | ||
| 1888 | if (address >= seg_start and address < seg_end) { | ||
| 1889 | if (self.address_map.get(base_address)) |obj_di| { | ||
| 1890 | return obj_di; | ||
| 1891 | } | ||
| 1892 | |||
| 1893 | for (cmd.getSections()) |sect| { | ||
| 1894 | if (mem.eql(u8, "__unwind_info", sect.sectName())) { | ||
| 1895 | unwind_info = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size]; | ||
| 1896 | } else if (mem.eql(u8, "__eh_frame", sect.sectName())) { | ||
| 1897 | eh_frame = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size]; | ||
| 1898 | } | ||
| 1899 | } | ||
| 1900 | |||
| 1901 | const obj_di = try self.allocator.create(ModuleDebugInfo); | ||
| 1902 | errdefer self.allocator.destroy(obj_di); | ||
| 1903 | |||
| 1904 | const macho_path = mem.sliceTo(std.c._dyld_get_image_name(i), 0); | ||
| 1905 | const macho_file = fs.cwd().openFile(macho_path, .{}) catch |err| switch (err) { | ||
| 1906 | error.FileNotFound => return error.MissingDebugInfo, | ||
| 1907 | else => return err, | ||
| 1908 | }; | ||
| 1909 | obj_di.* = try readMachODebugInfo(self.allocator, macho_file); | ||
| 1910 | obj_di.base_address = base_address; | ||
| 1911 | obj_di.vmaddr_slide = vmaddr_slide; | ||
| 1912 | obj_di.unwind_info = unwind_info; | ||
| 1913 | obj_di.eh_frame = eh_frame; | ||
| 1914 | |||
| 1915 | try self.address_map.putNoClobber(base_address, obj_di); | ||
| 1916 | |||
| 1917 | return obj_di; | ||
| 1918 | } | ||
| 1919 | }, | ||
| 1920 | else => {}, | ||
| 1921 | }; | ||
| 1922 | } | ||
| 1923 | |||
| 1924 | return error.MissingDebugInfo; | ||
| 1925 | } | ||
| 1926 | |||
| 1927 | fn lookupModuleNameDyld(self: *Info, address: usize) ?[]const u8 { | ||
| 1928 | _ = self; | ||
| 1929 | const image_count = std.c._dyld_image_count(); | ||
| 1930 | |||
| 1931 | var i: u32 = 0; | ||
| 1932 | while (i < image_count) : (i += 1) { | ||
| 1933 | const header = std.c._dyld_get_image_header(i) orelse continue; | ||
| 1934 | const base_address = @intFromPtr(header); | ||
| 1935 | if (address < base_address) continue; | ||
| 1936 | const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i); | ||
| 1937 | |||
| 1938 | var it = macho.LoadCommandIterator{ | ||
| 1939 | .ncmds = header.ncmds, | ||
| 1940 | .buffer = @alignCast(@as( | ||
| 1941 | [*]u8, | ||
| 1942 | @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)), | ||
| 1943 | )[0..header.sizeofcmds]), | ||
| 1944 | }; | ||
| 1945 | |||
| 1946 | while (it.next()) |cmd| switch (cmd.cmd()) { | ||
| 1947 | .SEGMENT_64 => { | ||
| 1948 | const segment_cmd = cmd.cast(macho.segment_command_64).?; | ||
| 1949 | if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue; | ||
| 1950 | |||
| 1951 | const original_address = address - vmaddr_slide; | ||
| 1952 | const seg_start = segment_cmd.vmaddr; | ||
| 1953 | const seg_end = seg_start + segment_cmd.vmsize; | ||
| 1954 | if (original_address >= seg_start and original_address < seg_end) { | ||
| 1955 | return fs.path.basename(mem.sliceTo(std.c._dyld_get_image_name(i), 0)); | ||
| 1956 | } | ||
| 1957 | }, | ||
| 1958 | else => {}, | ||
| 1959 | }; | ||
| 1960 | } | ||
| 1961 | |||
| 1962 | return null; | ||
| 1963 | } | ||
| 1964 | |||
| 1965 | fn lookupModuleWin32(self: *Info, address: usize) !*ModuleDebugInfo { | ||
| 1966 | for (self.modules.items) |*module| { | ||
| 1967 | if (address >= module.base_address and address < module.base_address + module.size) { | ||
| 1968 | if (self.address_map.get(module.base_address)) |obj_di| { | ||
| 1969 | return obj_di; | ||
| 1970 | } | ||
| 1971 | |||
| 1972 | const obj_di = try self.allocator.create(ModuleDebugInfo); | ||
| 1973 | errdefer self.allocator.destroy(obj_di); | ||
| 1974 | |||
| 1975 | const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size]; | ||
| 1976 | var coff_obj = try coff.Coff.init(mapped_module, true); | ||
| 1977 | |||
| 1978 | // The string table is not mapped into memory by the loader, so if a section name is in the | ||
| 1979 | // string table then we have to map the full image file from disk. This can happen when | ||
| 1980 | // a binary is produced with -gdwarf, since the section names are longer than 8 bytes. | ||
| 1981 | if (coff_obj.strtabRequired()) { | ||
| 1982 | var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined; | ||
| 1983 | // openFileAbsoluteW requires the prefix to be present | ||
| 1984 | @memcpy(name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' }); | ||
| 1985 | |||
| 1986 | const process_handle = windows.GetCurrentProcess(); | ||
| 1987 | const len = windows.kernel32.GetModuleFileNameExW( | ||
| 1988 | process_handle, | ||
| 1989 | module.handle, | ||
| 1990 | @ptrCast(&name_buffer[4]), | ||
| 1991 | windows.PATH_MAX_WIDE, | ||
| 1992 | ); | ||
| 1993 | |||
| 1994 | if (len == 0) return error.MissingDebugInfo; | ||
| 1995 | const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) { | ||
| 1996 | error.FileNotFound => return error.MissingDebugInfo, | ||
| 1997 | else => return err, | ||
| 1998 | }; | ||
| 1999 | errdefer coff_file.close(); | ||
| 2000 | |||
| 2001 | var section_handle: windows.HANDLE = undefined; | ||
| 2002 | const create_section_rc = windows.ntdll.NtCreateSection( | ||
| 2003 | &section_handle, | ||
| 2004 | windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ, | ||
| 2005 | null, | ||
| 2006 | null, | ||
| 2007 | windows.PAGE_READONLY, | ||
| 2008 | // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default. | ||
| 2009 | // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6. | ||
| 2010 | windows.SEC_COMMIT, | ||
| 2011 | coff_file.handle, | ||
| 2012 | ); | ||
| 2013 | if (create_section_rc != .SUCCESS) return error.MissingDebugInfo; | ||
| 2014 | errdefer windows.CloseHandle(section_handle); | ||
| 2015 | |||
| 2016 | var coff_len: usize = 0; | ||
| 2017 | var base_ptr: usize = 0; | ||
| 2018 | const map_section_rc = windows.ntdll.NtMapViewOfSection( | ||
| 2019 | section_handle, | ||
| 2020 | process_handle, | ||
| 2021 | @ptrCast(&base_ptr), | ||
| 2022 | null, | ||
| 2023 | 0, | ||
| 2024 | null, | ||
| 2025 | &coff_len, | ||
| 2026 | .ViewUnmap, | ||
| 2027 | 0, | ||
| 2028 | windows.PAGE_READONLY, | ||
| 2029 | ); | ||
| 2030 | if (map_section_rc != .SUCCESS) return error.MissingDebugInfo; | ||
| 2031 | errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrFromInt(base_ptr)) == .SUCCESS); | ||
| 2032 | |||
| 2033 | const section_view = @as([*]const u8, @ptrFromInt(base_ptr))[0..coff_len]; | ||
| 2034 | coff_obj = try coff.Coff.init(section_view, false); | ||
| 2035 | |||
| 2036 | module.mapped_file = .{ | ||
| 2037 | .file = coff_file, | ||
| 2038 | .section_handle = section_handle, | ||
| 2039 | .section_view = section_view, | ||
| 2040 | }; | ||
| 2041 | } | ||
| 2042 | errdefer if (module.mapped_file) |mapped_file| mapped_file.deinit(); | ||
| 2043 | |||
| 2044 | obj_di.* = try readCoffDebugInfo(self.allocator, &coff_obj); | ||
| 2045 | obj_di.base_address = module.base_address; | ||
| 2046 | |||
| 2047 | try self.address_map.putNoClobber(module.base_address, obj_di); | ||
| 2048 | return obj_di; | ||
| 2049 | } | ||
| 2050 | } | ||
| 2051 | |||
| 2052 | return error.MissingDebugInfo; | ||
| 2053 | } | ||
| 2054 | |||
| 2055 | fn lookupModuleNameWin32(self: *Info, address: usize) ?[]const u8 { | ||
| 2056 | for (self.modules.items) |module| { | ||
| 2057 | if (address >= module.base_address and address < module.base_address + module.size) { | ||
| 2058 | return module.name; | ||
| 2059 | } | ||
| 2060 | } | ||
| 2061 | return null; | ||
| 2062 | } | ||
| 2063 | |||
| 2064 | fn lookupModuleNameDl(self: *Info, address: usize) ?[]const u8 { | ||
| 2065 | _ = self; | ||
| 2066 | |||
| 2067 | var ctx: struct { | ||
| 2068 | // Input | ||
| 2069 | address: usize, | ||
| 2070 | // Output | ||
| 2071 | name: []const u8 = "", | ||
| 2072 | } = .{ .address = address }; | ||
| 2073 | const CtxTy = @TypeOf(ctx); | ||
| 2074 | |||
| 2075 | if (posix.dl_iterate_phdr(&ctx, error{Found}, struct { | ||
| 2076 | fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void { | ||
| 2077 | _ = size; | ||
| 2078 | if (context.address < info.addr) return; | ||
| 2079 | const phdrs = info.phdr[0..info.phnum]; | ||
| 2080 | for (phdrs) |*phdr| { | ||
| 2081 | if (phdr.p_type != elf.PT_LOAD) continue; | ||
| 2082 | |||
| 2083 | const seg_start = info.addr +% phdr.p_vaddr; | ||
| 2084 | const seg_end = seg_start + phdr.p_memsz; | ||
| 2085 | if (context.address >= seg_start and context.address < seg_end) { | ||
| 2086 | context.name = mem.sliceTo(info.name, 0) orelse ""; | ||
| 2087 | break; | ||
| 2088 | } | ||
| 2089 | } else return; | ||
| 2090 | |||
| 2091 | return error.Found; | ||
| 2092 | } | ||
| 2093 | }.callback)) { | ||
| 2094 | return null; | ||
| 2095 | } else |err| switch (err) { | ||
| 2096 | error.Found => return fs.path.basename(ctx.name), | ||
| 2097 | } | ||
| 2098 | |||
| 2099 | return null; | ||
| 2100 | } | ||
| 2101 | |||
| 2102 | fn lookupModuleDl(self: *Info, address: usize) !*ModuleDebugInfo { | ||
| 2103 | var ctx: struct { | ||
| 2104 | // Input | ||
| 2105 | address: usize, | ||
| 2106 | // Output | ||
| 2107 | base_address: usize = undefined, | ||
| 2108 | name: []const u8 = undefined, | ||
| 2109 | build_id: ?[]const u8 = null, | ||
| 2110 | gnu_eh_frame: ?[]const u8 = null, | ||
| 2111 | } = .{ .address = address }; | ||
| 2112 | const CtxTy = @TypeOf(ctx); | ||
| 2113 | |||
| 2114 | if (posix.dl_iterate_phdr(&ctx, error{Found}, struct { | ||
| 2115 | fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void { | ||
| 2116 | _ = size; | ||
| 2117 | // The base address is too high | ||
| 2118 | if (context.address < info.addr) | ||
| 2119 | return; | ||
| 2120 | |||
| 2121 | const phdrs = info.phdr[0..info.phnum]; | ||
| 2122 | for (phdrs) |*phdr| { | ||
| 2123 | if (phdr.p_type != elf.PT_LOAD) continue; | ||
| 2124 | |||
| 2125 | // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000 | ||
| 2126 | const seg_start = info.addr +% phdr.p_vaddr; | ||
| 2127 | const seg_end = seg_start + phdr.p_memsz; | ||
| 2128 | if (context.address >= seg_start and context.address < seg_end) { | ||
| 2129 | // Android libc uses NULL instead of an empty string to mark the | ||
| 2130 | // main program | ||
| 2131 | context.name = mem.sliceTo(info.name, 0) orelse ""; | ||
| 2132 | context.base_address = info.addr; | ||
| 2133 | break; | ||
| 2134 | } | ||
| 2135 | } else return; | ||
| 2136 | |||
| 2137 | for (info.phdr[0..info.phnum]) |phdr| { | ||
| 2138 | switch (phdr.p_type) { | ||
| 2139 | elf.PT_NOTE => { | ||
| 2140 | // Look for .note.gnu.build-id | ||
| 2141 | const note_bytes = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz]; | ||
| 2142 | const name_size = mem.readInt(u32, note_bytes[0..4], native_endian); | ||
| 2143 | if (name_size != 4) continue; | ||
| 2144 | const desc_size = mem.readInt(u32, note_bytes[4..8], native_endian); | ||
| 2145 | const note_type = mem.readInt(u32, note_bytes[8..12], native_endian); | ||
| 2146 | if (note_type != elf.NT_GNU_BUILD_ID) continue; | ||
| 2147 | if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue; | ||
| 2148 | context.build_id = note_bytes[16..][0..desc_size]; | ||
| 2149 | }, | ||
| 2150 | elf.PT_GNU_EH_FRAME => { | ||
| 2151 | context.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz]; | ||
| 2152 | }, | ||
| 2153 | else => {}, | ||
| 2154 | } | ||
| 2155 | } | ||
| 2156 | |||
| 2157 | // Stop the iteration | ||
| 2158 | return error.Found; | ||
| 2159 | } | ||
| 2160 | }.callback)) { | ||
| 2161 | return error.MissingDebugInfo; | ||
| 2162 | } else |err| switch (err) { | ||
| 2163 | error.Found => {}, | ||
| 2164 | } | ||
| 2165 | |||
| 2166 | if (self.address_map.get(ctx.base_address)) |obj_di| { | ||
| 2167 | return obj_di; | ||
| 2168 | } | ||
| 2169 | |||
| 2170 | const obj_di = try self.allocator.create(ModuleDebugInfo); | ||
| 2171 | errdefer self.allocator.destroy(obj_di); | ||
| 2172 | |||
| 2173 | var sections: Dwarf.SectionArray = Dwarf.null_section_array; | ||
| 2174 | if (ctx.gnu_eh_frame) |eh_frame_hdr| { | ||
| 2175 | // This is a special case - pointer offsets inside .eh_frame_hdr | ||
| 2176 | // are encoded relative to its base address, so we must use the | ||
| 2177 | // version that is already memory mapped, and not the one that | ||
| 2178 | // will be mapped separately from the ELF file. | ||
| 2179 | sections[@intFromEnum(Dwarf.Section.Id.eh_frame_hdr)] = .{ | ||
| 2180 | .data = eh_frame_hdr, | ||
| 2181 | .owned = false, | ||
| 2182 | }; | ||
| 2183 | } | ||
| 2184 | |||
| 2185 | obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, &sections, null); | ||
| 2186 | obj_di.base_address = ctx.base_address; | ||
| 2187 | |||
| 2188 | // Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding | ||
| 2189 | obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.base_address) catch {}; | ||
| 2190 | |||
| 2191 | try self.address_map.putNoClobber(ctx.base_address, obj_di); | ||
| 2192 | |||
| 2193 | return obj_di; | ||
| 2194 | } | ||
| 2195 | |||
| 2196 | fn lookupModuleHaiku(self: *Info, address: usize) !*ModuleDebugInfo { | ||
| 2197 | _ = self; | ||
| 2198 | _ = address; | ||
| 2199 | @panic("TODO implement lookup module for Haiku"); | ||
| 2200 | } | ||
| 2201 | |||
| 2202 | fn lookupModuleWasm(self: *Info, address: usize) !*ModuleDebugInfo { | ||
| 2203 | _ = self; | ||
| 2204 | _ = address; | ||
| 2205 | @panic("TODO implement lookup module for Wasm"); | ||
| 2206 | } | ||
| 2207 | }; | ||
| 2208 | |||
| 2209 | pub const ModuleDebugInfo = switch (native_os) { | ||
| 2210 | .macos, .ios, .watchos, .tvos, .visionos => struct { | ||
| 2211 | base_address: usize, | ||
| 2212 | vmaddr_slide: usize, | ||
| 2213 | mapped_memory: []align(mem.page_size) const u8, | ||
| 2214 | symbols: []const MachoSymbol, | ||
| 2215 | strings: [:0]const u8, | ||
| 2216 | ofiles: OFileTable, | ||
| 2217 | |||
| 2218 | // Backed by the in-memory sections mapped by the loader | ||
| 2219 | unwind_info: ?[]const u8 = null, | ||
| 2220 | eh_frame: ?[]const u8 = null, | ||
| 2221 | |||
| 2222 | const OFileTable = std.StringHashMap(OFileInfo); | ||
| 2223 | const OFileInfo = struct { | ||
| 2224 | di: Dwarf, | ||
| 2225 | addr_table: std.StringHashMap(u64), | ||
| 2226 | }; | ||
| 2227 | |||
| 2228 | pub fn deinit(self: *@This(), allocator: mem.Allocator) void { | ||
| 2229 | var it = self.ofiles.iterator(); | ||
| 2230 | while (it.next()) |entry| { | ||
| 2231 | const ofile = entry.value_ptr; | ||
| 2232 | ofile.di.deinit(allocator); | ||
| 2233 | ofile.addr_table.deinit(); | ||
| 2234 | } | ||
| 2235 | self.ofiles.deinit(); | ||
| 2236 | allocator.free(self.symbols); | ||
| 2237 | posix.munmap(self.mapped_memory); | ||
| 2238 | } | ||
| 2239 | |||
| 2240 | fn loadOFile(self: *@This(), allocator: mem.Allocator, o_file_path: []const u8) !*OFileInfo { | ||
| 2241 | const o_file = try fs.cwd().openFile(o_file_path, .{}); | ||
| 2242 | const mapped_mem = try mapWholeFile(o_file); | ||
| 2243 | |||
| 2244 | const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr)); | ||
| 2245 | if (hdr.magic != std.macho.MH_MAGIC_64) | ||
| 2246 | return error.InvalidDebugInfo; | ||
| 2247 | |||
| 2248 | var segcmd: ?macho.LoadCommandIterator.LoadCommand = null; | ||
| 2249 | var symtabcmd: ?macho.symtab_command = null; | ||
| 2250 | var it = macho.LoadCommandIterator{ | ||
| 2251 | .ncmds = hdr.ncmds, | ||
| 2252 | .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], | ||
| 2253 | }; | ||
| 2254 | while (it.next()) |cmd| switch (cmd.cmd()) { | ||
| 2255 | .SEGMENT_64 => segcmd = cmd, | ||
| 2256 | .SYMTAB => symtabcmd = cmd.cast(macho.symtab_command).?, | ||
| 2257 | else => {}, | ||
| 2258 | }; | ||
| 2259 | |||
| 2260 | if (segcmd == null or symtabcmd == null) return error.MissingDebugInfo; | ||
| 2261 | |||
| 2262 | // Parse symbols | ||
| 2263 | const strtab = @as( | ||
| 2264 | [*]const u8, | ||
| 2265 | @ptrCast(&mapped_mem[symtabcmd.?.stroff]), | ||
| 2266 | )[0 .. symtabcmd.?.strsize - 1 :0]; | ||
| 2267 | const symtab = @as( | ||
| 2268 | [*]const macho.nlist_64, | ||
| 2269 | @ptrCast(@alignCast(&mapped_mem[symtabcmd.?.symoff])), | ||
| 2270 | )[0..symtabcmd.?.nsyms]; | ||
| 2271 | |||
| 2272 | // TODO handle tentative (common) symbols | ||
| 2273 | var addr_table = std.StringHashMap(u64).init(allocator); | ||
| 2274 | try addr_table.ensureTotalCapacity(@as(u32, @intCast(symtab.len))); | ||
| 2275 | for (symtab) |sym| { | ||
| 2276 | if (sym.n_strx == 0) continue; | ||
| 2277 | if (sym.undf() or sym.tentative() or sym.abs()) continue; | ||
| 2278 | const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0); | ||
| 2279 | // TODO is it possible to have a symbol collision? | ||
| 2280 | addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value); | ||
| 2281 | } | ||
| 2282 | |||
| 2283 | var sections: Dwarf.SectionArray = Dwarf.null_section_array; | ||
| 2284 | if (self.eh_frame) |eh_frame| sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{ | ||
| 2285 | .data = eh_frame, | ||
| 2286 | .owned = false, | ||
| 2287 | }; | ||
| 2288 | |||
| 2289 | for (segcmd.?.getSections()) |sect| { | ||
| 2290 | if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue; | ||
| 2291 | |||
| 2292 | var section_index: ?usize = null; | ||
| 2293 | inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| { | ||
| 2294 | if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i; | ||
| 2295 | } | ||
| 2296 | if (section_index == null) continue; | ||
| 2297 | |||
| 2298 | const section_bytes = try chopSlice(mapped_mem, sect.offset, sect.size); | ||
| 2299 | sections[section_index.?] = .{ | ||
| 2300 | .data = section_bytes, | ||
| 2301 | .virtual_address = sect.addr, | ||
| 2302 | .owned = false, | ||
| 2303 | }; | ||
| 2304 | } | ||
| 2305 | |||
| 2306 | const missing_debug_info = | ||
| 2307 | sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or | ||
| 2308 | sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or | ||
| 2309 | sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or | ||
| 2310 | sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; | ||
| 2311 | if (missing_debug_info) return error.MissingDebugInfo; | ||
| 2312 | |||
| 2313 | var di = Dwarf{ | ||
| 2314 | .endian = .little, | ||
| 2315 | .sections = sections, | ||
| 2316 | .is_macho = true, | ||
| 2317 | }; | ||
| 2318 | |||
| 2319 | try Dwarf.open(&di, allocator); | ||
| 2320 | const info = OFileInfo{ | ||
| 2321 | .di = di, | ||
| 2322 | .addr_table = addr_table, | ||
| 2323 | }; | ||
| 2324 | |||
| 2325 | // Add the debug info to the cache | ||
| 2326 | const result = try self.ofiles.getOrPut(o_file_path); | ||
| 2327 | assert(!result.found_existing); | ||
| 2328 | result.value_ptr.* = info; | ||
| 2329 | |||
| 2330 | return result.value_ptr; | ||
| 2331 | } | ||
| 2332 | |||
| 2333 | pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo { | ||
| 2334 | nosuspend { | ||
| 2335 | const result = try self.getOFileInfoForAddress(allocator, address); | ||
| 2336 | if (result.symbol == null) return .{}; | ||
| 2337 | |||
| 2338 | // Take the symbol name from the N_FUN STAB entry, we're going to | ||
| 2339 | // use it if we fail to find the DWARF infos | ||
| 2340 | const stab_symbol = mem.sliceTo(self.strings[result.symbol.?.strx..], 0); | ||
| 2341 | if (result.o_file_info == null) return .{ .symbol_name = stab_symbol }; | ||
| 2342 | |||
| 2343 | // Translate again the address, this time into an address inside the | ||
| 2344 | // .o file | ||
| 2345 | const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{ | ||
| 2346 | .symbol_name = "???", | ||
| 2347 | }; | ||
| 2348 | |||
| 2349 | const addr_off = result.relocated_address - result.symbol.?.addr; | ||
| 2350 | const o_file_di = &result.o_file_info.?.di; | ||
| 2351 | if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| { | ||
| 2352 | return SymbolInfo{ | ||
| 2353 | .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???", | ||
| 2354 | .compile_unit_name = compile_unit.die.getAttrString( | ||
| 2355 | o_file_di, | ||
| 2356 | DW.AT.name, | ||
| 2357 | o_file_di.section(.debug_str), | ||
| 2358 | compile_unit.*, | ||
| 2359 | ) catch |err| switch (err) { | ||
| 2360 | error.MissingDebugInfo, error.InvalidDebugInfo => "???", | ||
| 2361 | }, | ||
| 2362 | .line_info = o_file_di.getLineNumberInfo( | ||
| 2363 | allocator, | ||
| 2364 | compile_unit.*, | ||
| 2365 | relocated_address_o + addr_off, | ||
| 2366 | ) catch |err| switch (err) { | ||
| 2367 | error.MissingDebugInfo, error.InvalidDebugInfo => null, | ||
| 2368 | else => return err, | ||
| 2369 | }, | ||
| 2370 | }; | ||
| 2371 | } else |err| switch (err) { | ||
| 2372 | error.MissingDebugInfo, error.InvalidDebugInfo => { | ||
| 2373 | return SymbolInfo{ .symbol_name = stab_symbol }; | ||
| 2374 | }, | ||
| 2375 | else => return err, | ||
| 2376 | } | ||
| 2377 | } | ||
| 2378 | } | ||
| 2379 | |||
| 2380 | pub fn getOFileInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !struct { | ||
| 2381 | relocated_address: usize, | ||
| 2382 | symbol: ?*const MachoSymbol = null, | ||
| 2383 | o_file_info: ?*OFileInfo = null, | ||
| 2384 | } { | ||
| 2385 | nosuspend { | ||
| 2386 | // Translate the VA into an address into this object | ||
| 2387 | const relocated_address = address - self.vmaddr_slide; | ||
| 2388 | |||
| 2389 | // Find the .o file where this symbol is defined | ||
| 2390 | const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse return .{ | ||
| 2391 | .relocated_address = relocated_address, | ||
| 2392 | }; | ||
| 2393 | |||
| 2394 | // Check if its debug infos are already in the cache | ||
| 2395 | const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0); | ||
| 2396 | const o_file_info = self.ofiles.getPtr(o_file_path) orelse | ||
| 2397 | (self.loadOFile(allocator, o_file_path) catch |err| switch (err) { | ||
| 2398 | error.FileNotFound, | ||
| 2399 | error.MissingDebugInfo, | ||
| 2400 | error.InvalidDebugInfo, | ||
| 2401 | => return .{ | ||
| 2402 | .relocated_address = relocated_address, | ||
| 2403 | .symbol = symbol, | ||
| 2404 | }, | ||
| 2405 | else => return err, | ||
| 2406 | }); | ||
| 2407 | |||
| 2408 | return .{ | ||
| 2409 | .relocated_address = relocated_address, | ||
| 2410 | .symbol = symbol, | ||
| 2411 | .o_file_info = o_file_info, | ||
| 2412 | }; | ||
| 2413 | } | ||
| 2414 | } | ||
| 2415 | |||
| 2416 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf { | ||
| 2417 | return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null; | ||
| 2418 | } | ||
| 2419 | }, | ||
| 2420 | .uefi, .windows => struct { | ||
| 2421 | base_address: usize, | ||
| 2422 | pdb: ?pdb.Pdb = null, | ||
| 2423 | dwarf: ?Dwarf = null, | ||
| 2424 | coff_image_base: u64, | ||
| 2425 | |||
| 2426 | /// Only used if pdb is non-null | ||
| 2427 | coff_section_headers: []coff.SectionHeader, | ||
| 2428 | |||
| 2429 | pub fn deinit(self: *@This(), allocator: mem.Allocator) void { | ||
| 2430 | if (self.dwarf) |*dwarf| { | ||
| 2431 | dwarf.deinit(allocator); | ||
| 2432 | } | ||
| 2433 | |||
| 2434 | if (self.pdb) |*p| { | ||
| 2435 | p.deinit(); | ||
| 2436 | allocator.free(self.coff_section_headers); | ||
| 2437 | } | ||
| 2438 | } | ||
| 2439 | |||
| 2440 | fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?SymbolInfo { | ||
| 2441 | var coff_section: *align(1) const coff.SectionHeader = undefined; | ||
| 2442 | const mod_index = for (self.pdb.?.sect_contribs) |sect_contrib| { | ||
| 2443 | if (sect_contrib.Section > self.coff_section_headers.len) continue; | ||
| 2444 | // Remember that SectionContribEntry.Section is 1-based. | ||
| 2445 | coff_section = &self.coff_section_headers[sect_contrib.Section - 1]; | ||
| 2446 | |||
| 2447 | const vaddr_start = coff_section.virtual_address + sect_contrib.Offset; | ||
| 2448 | const vaddr_end = vaddr_start + sect_contrib.Size; | ||
| 2449 | if (relocated_address >= vaddr_start and relocated_address < vaddr_end) { | ||
| 2450 | break sect_contrib.ModuleIndex; | ||
| 2451 | } | ||
| 2452 | } else { | ||
| 2453 | // we have no information to add to the address | ||
| 2454 | return null; | ||
| 2455 | }; | ||
| 2456 | |||
| 2457 | const module = (try self.pdb.?.getModule(mod_index)) orelse | ||
| 2458 | return error.InvalidDebugInfo; | ||
| 2459 | const obj_basename = fs.path.basename(module.obj_file_name); | ||
| 2460 | |||
| 2461 | const symbol_name = self.pdb.?.getSymbolName( | ||
| 2462 | module, | ||
| 2463 | relocated_address - coff_section.virtual_address, | ||
| 2464 | ) orelse "???"; | ||
| 2465 | const opt_line_info = try self.pdb.?.getLineNumberInfo( | ||
| 2466 | module, | ||
| 2467 | relocated_address - coff_section.virtual_address, | ||
| 2468 | ); | ||
| 2469 | |||
| 2470 | return SymbolInfo{ | ||
| 2471 | .symbol_name = symbol_name, | ||
| 2472 | .compile_unit_name = obj_basename, | ||
| 2473 | .line_info = opt_line_info, | ||
| 2474 | }; | ||
| 2475 | } | ||
| 2476 | |||
| 2477 | pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo { | ||
| 2478 | // Translate the VA into an address into this object | ||
| 2479 | const relocated_address = address - self.base_address; | ||
| 2480 | |||
| 2481 | if (self.pdb != null) { | ||
| 2482 | if (try self.getSymbolFromPdb(relocated_address)) |symbol| return symbol; | ||
| 2483 | } | ||
| 2484 | |||
| 2485 | if (self.dwarf) |*dwarf| { | ||
| 2486 | const dwarf_address = relocated_address + self.coff_image_base; | ||
| 2487 | return getSymbolFromDwarf(allocator, dwarf_address, dwarf); | ||
| 2488 | } | ||
| 2489 | |||
| 2490 | return SymbolInfo{}; | ||
| 2491 | } | ||
| 2492 | |||
| 2493 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf { | ||
| 2494 | _ = allocator; | ||
| 2495 | _ = address; | ||
| 2496 | |||
| 2497 | return switch (self.debug_data) { | ||
| 2498 | .dwarf => |*dwarf| dwarf, | ||
| 2499 | else => null, | ||
| 2500 | }; | ||
| 2501 | } | ||
| 2502 | }, | ||
| 2503 | .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct { | ||
| 2504 | base_address: usize, | ||
| 2505 | dwarf: Dwarf, | ||
| 2506 | mapped_memory: []align(mem.page_size) const u8, | ||
| 2507 | external_mapped_memory: ?[]align(mem.page_size) const u8, | ||
| 2508 | |||
| 2509 | pub fn deinit(self: *@This(), allocator: mem.Allocator) void { | ||
| 2510 | self.dwarf.deinit(allocator); | ||
| 2511 | posix.munmap(self.mapped_memory); | ||
| 2512 | if (self.external_mapped_memory) |m| posix.munmap(m); | ||
| 2513 | } | ||
| 2514 | |||
| 2515 | pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo { | ||
| 2516 | // Translate the VA into an address into this object | ||
| 2517 | const relocated_address = address - self.base_address; | ||
| 2518 | return getSymbolFromDwarf(allocator, relocated_address, &self.dwarf); | ||
| 2519 | } | ||
| 2520 | |||
| 2521 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf { | ||
| 2522 | _ = allocator; | ||
| 2523 | _ = address; | ||
| 2524 | return &self.dwarf; | ||
| 2525 | } | ||
| 2526 | }, | ||
| 2527 | .wasi, .emscripten => struct { | ||
| 2528 | pub fn deinit(self: *@This(), allocator: mem.Allocator) void { | ||
| 2529 | _ = self; | ||
| 2530 | _ = allocator; | ||
| 2531 | } | ||
| 2532 | |||
| 2533 | pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo { | ||
| 2534 | _ = self; | ||
| 2535 | _ = allocator; | ||
| 2536 | _ = address; | ||
| 2537 | return SymbolInfo{}; | ||
| 2538 | } | ||
| 2539 | |||
| 2540 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf { | ||
| 2541 | _ = self; | ||
| 2542 | _ = allocator; | ||
| 2543 | _ = address; | ||
| 2544 | return null; | ||
| 2545 | } | ||
| 2546 | }, | ||
| 2547 | else => Dwarf, | ||
| 2548 | }; | ||
| 2549 | |||
| 2550 | fn getSymbolFromDwarf(allocator: mem.Allocator, address: u64, di: *Dwarf) !SymbolInfo { | ||
| 2551 | if (nosuspend di.findCompileUnit(address)) |compile_unit| { | ||
| 2552 | return SymbolInfo{ | ||
| 2553 | .symbol_name = nosuspend di.getSymbolName(address) orelse "???", | ||
| 2554 | .compile_unit_name = compile_unit.die.getAttrString(di, DW.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) { | ||
| 2555 | error.MissingDebugInfo, error.InvalidDebugInfo => "???", | ||
| 2556 | }, | ||
| 2557 | .line_info = nosuspend di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) { | ||
| 2558 | error.MissingDebugInfo, error.InvalidDebugInfo => null, | ||
| 2559 | else => return err, | ||
| 2560 | }, | ||
| 2561 | }; | ||
| 2562 | } else |err| switch (err) { | ||
| 2563 | error.MissingDebugInfo, error.InvalidDebugInfo => { | ||
| 2564 | return SymbolInfo{}; | ||
| 2565 | }, | ||
| 2566 | else => return err, | ||
| 2567 | } | ||
| 2568 | } | ||
| 2569 | |||
| 2570 | /// TODO multithreaded awareness | 1112 | /// TODO multithreaded awareness |
| 2571 | var debug_info_allocator: ?mem.Allocator = null; | 1113 | var debug_info_allocator: ?mem.Allocator = null; |
| 2572 | var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined; | 1114 | var debug_info_arena_allocator: std.heap.ArenaAllocator = undefined; |
| ... | @@ -2687,7 +1229,7 @@ fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopa | ... | @@ -2687,7 +1229,7 @@ fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*anyopa |
| 2687 | posix.abort(); | 1229 | posix.abort(); |
| 2688 | } | 1230 | } |
| 2689 | 1231 | ||
| 2690 | fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*const anyopaque) void { | 1232 | fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) void { |
| 2691 | const stderr = io.getStdErr().writer(); | 1233 | const stderr = io.getStdErr().writer(); |
| 2692 | _ = switch (sig) { | 1234 | _ = switch (sig) { |
| 2693 | posix.SIG.SEGV => if (native_arch == .x86_64 and native_os == .linux and code == 128) // SI_KERNEL | 1235 | posix.SIG.SEGV => if (native_arch == .x86_64 and native_os == .linux and code == 128) // SI_KERNEL |
| ... | @@ -2713,7 +1255,7 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*const anyo | ... | @@ -2713,7 +1255,7 @@ fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*const anyo |
| 2713 | .arm, | 1255 | .arm, |
| 2714 | .aarch64, | 1256 | .aarch64, |
| 2715 | => { | 1257 | => { |
| 2716 | const ctx: *const posix.ucontext_t = @ptrCast(@alignCast(ctx_ptr)); | 1258 | const ctx: *posix.ucontext_t = @ptrCast(@alignCast(ctx_ptr)); |
| 2717 | dumpStackTraceFromBase(ctx); | 1259 | dumpStackTraceFromBase(ctx); |
| 2718 | }, | 1260 | }, |
| 2719 | else => {}, | 1261 | else => {}, |
| ... | @@ -2802,7 +1344,7 @@ test "manage resources correctly" { | ... | @@ -2802,7 +1344,7 @@ test "manage resources correctly" { |
| 2802 | } | 1344 | } |
| 2803 | 1345 | ||
| 2804 | const writer = std.io.null_writer; | 1346 | const writer = std.io.null_writer; |
| 2805 | var di = try openSelfDebugInfo(testing.allocator); | 1347 | var di = try SelfInfo.open(testing.allocator); |
| 2806 | defer di.deinit(); | 1348 | defer di.deinit(); |
| 2807 | try printSourceAtAddress(&di, writer, showMyTrace(), io.tty.detectConfig(std.io.getStdErr())); | 1349 | try printSourceAtAddress(&di, writer, showMyTrace(), io.tty.detectConfig(std.io.getStdErr())); |
| 2808 | } | 1350 | } |
| ... | @@ -2939,6 +1481,99 @@ pub const SafetyLock = struct { | ... | @@ -2939,6 +1481,99 @@ pub const SafetyLock = struct { |
| 2939 | } | 1481 | } |
| 2940 | }; | 1482 | }; |
| 2941 | 1483 | ||
| 1484 | /// Deprecated. Don't use this, just read from your memory directly. | ||
| 1485 | /// | ||
| 1486 | /// This only exists because someone was too lazy to rework logic that used to | ||
| 1487 | /// operate on an open file to operate on a memory buffer instead. | ||
| 1488 | pub const DeprecatedFixedBufferReader = struct { | ||
| 1489 | buf: []const u8, | ||
| 1490 | pos: usize = 0, | ||
| 1491 | endian: std.builtin.Endian, | ||
| 1492 | |||
| 1493 | pub const Error = error{ EndOfBuffer, Overflow, InvalidBuffer }; | ||
| 1494 | |||
| 1495 | pub fn seekTo(fbr: *DeprecatedFixedBufferReader, pos: u64) Error!void { | ||
| 1496 | if (pos > fbr.buf.len) return error.EndOfBuffer; | ||
| 1497 | fbr.pos = @intCast(pos); | ||
| 1498 | } | ||
| 1499 | |||
| 1500 | pub fn seekForward(fbr: *DeprecatedFixedBufferReader, amount: u64) Error!void { | ||
| 1501 | if (fbr.buf.len - fbr.pos < amount) return error.EndOfBuffer; | ||
| 1502 | fbr.pos += @intCast(amount); | ||
| 1503 | } | ||
| 1504 | |||
| 1505 | pub inline fn readByte(fbr: *DeprecatedFixedBufferReader) Error!u8 { | ||
| 1506 | if (fbr.pos >= fbr.buf.len) return error.EndOfBuffer; | ||
| 1507 | defer fbr.pos += 1; | ||
| 1508 | return fbr.buf[fbr.pos]; | ||
| 1509 | } | ||
| 1510 | |||
| 1511 | pub fn readByteSigned(fbr: *DeprecatedFixedBufferReader) Error!i8 { | ||
| 1512 | return @bitCast(try fbr.readByte()); | ||
| 1513 | } | ||
| 1514 | |||
| 1515 | pub fn readInt(fbr: *DeprecatedFixedBufferReader, comptime T: type) Error!T { | ||
| 1516 | const size = @divExact(@typeInfo(T).Int.bits, 8); | ||
| 1517 | if (fbr.buf.len - fbr.pos < size) return error.EndOfBuffer; | ||
| 1518 | defer fbr.pos += size; | ||
| 1519 | return std.mem.readInt(T, fbr.buf[fbr.pos..][0..size], fbr.endian); | ||
| 1520 | } | ||
| 1521 | |||
| 1522 | pub fn readIntChecked( | ||
| 1523 | fbr: *DeprecatedFixedBufferReader, | ||
| 1524 | comptime T: type, | ||
| 1525 | ma: *MemoryAccessor, | ||
| 1526 | ) Error!T { | ||
| 1527 | if (ma.load(T, @intFromPtr(fbr.buf[fbr.pos..].ptr)) == null) | ||
| 1528 | return error.InvalidBuffer; | ||
| 1529 | |||
| 1530 | return fbr.readInt(T); | ||
| 1531 | } | ||
| 1532 | |||
| 1533 | pub fn readUleb128(fbr: *DeprecatedFixedBufferReader, comptime T: type) Error!T { | ||
| 1534 | return std.leb.readUleb128(T, fbr); | ||
| 1535 | } | ||
| 1536 | |||
| 1537 | pub fn readIleb128(fbr: *DeprecatedFixedBufferReader, comptime T: type) Error!T { | ||
| 1538 | return std.leb.readIleb128(T, fbr); | ||
| 1539 | } | ||
| 1540 | |||
| 1541 | pub fn readAddress(fbr: *DeprecatedFixedBufferReader, format: std.dwarf.Format) Error!u64 { | ||
| 1542 | return switch (format) { | ||
| 1543 | .@"32" => try fbr.readInt(u32), | ||
| 1544 | .@"64" => try fbr.readInt(u64), | ||
| 1545 | }; | ||
| 1546 | } | ||
| 1547 | |||
| 1548 | pub fn readAddressChecked( | ||
| 1549 | fbr: *DeprecatedFixedBufferReader, | ||
| 1550 | format: std.dwarf.Format, | ||
| 1551 | ma: *MemoryAccessor, | ||
| 1552 | ) Error!u64 { | ||
| 1553 | return switch (format) { | ||
| 1554 | .@"32" => try fbr.readIntChecked(u32, ma), | ||
| 1555 | .@"64" => try fbr.readIntChecked(u64, ma), | ||
| 1556 | }; | ||
| 1557 | } | ||
| 1558 | |||
| 1559 | pub fn readBytes(fbr: *DeprecatedFixedBufferReader, len: usize) Error![]const u8 { | ||
| 1560 | if (fbr.buf.len - fbr.pos < len) return error.EndOfBuffer; | ||
| 1561 | defer fbr.pos += len; | ||
| 1562 | return fbr.buf[fbr.pos..][0..len]; | ||
| 1563 | } | ||
| 1564 | |||
| 1565 | pub fn readBytesTo(fbr: *DeprecatedFixedBufferReader, comptime sentinel: u8) Error![:sentinel]const u8 { | ||
| 1566 | const end = @call(.always_inline, std.mem.indexOfScalarPos, .{ | ||
| 1567 | u8, | ||
| 1568 | fbr.buf, | ||
| 1569 | fbr.pos, | ||
| 1570 | sentinel, | ||
| 1571 | }) orelse return error.EndOfBuffer; | ||
| 1572 | defer fbr.pos = end + 1; | ||
| 1573 | return fbr.buf[fbr.pos..end :sentinel]; | ||
| 1574 | } | ||
| 1575 | }; | ||
| 1576 | |||
| 2942 | /// Detect whether the program is being executed in the Valgrind virtual machine. | 1577 | /// Detect whether the program is being executed in the Valgrind virtual machine. |
| 2943 | /// | 1578 | /// |
| 2944 | /// When Valgrind integrations are disabled, this returns comptime-known false. | 1579 | /// When Valgrind integrations are disabled, this returns comptime-known false. |
| ... | @@ -2950,5 +1585,9 @@ pub inline fn inValgrind() bool { | ... | @@ -2950,5 +1585,9 @@ pub inline fn inValgrind() bool { |
| 2950 | } | 1585 | } |
| 2951 | 1586 | ||
| 2952 | test { | 1587 | test { |
| 1588 | _ = &Dwarf; | ||
| 1589 | _ = &MemoryAccessor; | ||
| 1590 | _ = &Pdb; | ||
| 1591 | _ = &SelfInfo; | ||
| 2953 | _ = &dumpHex; | 1592 | _ = &dumpHex; |
| 2954 | } | 1593 | } |
lib/std/debug/Dwarf.zig+119-803| ... | @@ -1,23 +1,32 @@ | ... | @@ -1,23 +1,32 @@ |
| 1 | //! Implements parsing, decoding, and caching of DWARF information. | 1 | //! Implements parsing, decoding, and caching of DWARF information. |
| 2 | //! | 2 | //! |
| 3 | //! This API does not assume the current executable is itself the thing being | ||
| 4 | //! debugged, however, it does assume the debug info has the same CPU | ||
| 5 | //! architecture and OS as the current executable. It is planned to remove this | ||
| 6 | //! limitation. | ||
| 7 | //! | ||
| 3 | //! For unopinionated types and bits, see `std.dwarf`. | 8 | //! For unopinionated types and bits, see `std.dwarf`. |
| 4 | 9 | ||
| 5 | const builtin = @import("builtin"); | 10 | const builtin = @import("builtin"); |
| 11 | const native_endian = builtin.cpu.arch.endian(); | ||
| 12 | |||
| 6 | const std = @import("../std.zig"); | 13 | const std = @import("../std.zig"); |
| 7 | const AT = DW.AT; | ||
| 8 | const Allocator = std.mem.Allocator; | 14 | const Allocator = std.mem.Allocator; |
| 9 | const DW = std.dwarf; | 15 | const DW = std.dwarf; |
| 16 | const AT = DW.AT; | ||
| 10 | const EH = DW.EH; | 17 | const EH = DW.EH; |
| 11 | const FORM = DW.FORM; | 18 | const FORM = DW.FORM; |
| 12 | const Format = DW.Format; | 19 | const Format = DW.Format; |
| 13 | const RLE = DW.RLE; | 20 | const RLE = DW.RLE; |
| 14 | const StackIterator = std.debug.StackIterator; | ||
| 15 | const UT = DW.UT; | 21 | const UT = DW.UT; |
| 16 | const assert = std.debug.assert; | 22 | const assert = std.debug.assert; |
| 17 | const cast = std.math.cast; | 23 | const cast = std.math.cast; |
| 18 | const maxInt = std.math.maxInt; | 24 | const maxInt = std.math.maxInt; |
| 19 | const native_endian = builtin.cpu.arch.endian(); | ||
| 20 | const readInt = std.mem.readInt; | 25 | const readInt = std.mem.readInt; |
| 26 | const MemoryAccessor = std.debug.MemoryAccessor; | ||
| 27 | |||
| 28 | /// Did I mention this is deprecated? | ||
| 29 | const DeprecatedFixedBufferReader = std.debug.DeprecatedFixedBufferReader; | ||
| 21 | 30 | ||
| 22 | const Dwarf = @This(); | 31 | const Dwarf = @This(); |
| 23 | 32 | ||
| ... | @@ -153,7 +162,7 @@ pub const FormValue = union(enum) { | ... | @@ -153,7 +162,7 @@ pub const FormValue = union(enum) { |
| 153 | .string => |s| return s, | 162 | .string => |s| return s, |
| 154 | .strp => |off| return di.getString(off), | 163 | .strp => |off| return di.getString(off), |
| 155 | .line_strp => |off| return di.getLineString(off), | 164 | .line_strp => |off| return di.getLineString(off), |
| 156 | else => return badDwarf(), | 165 | else => return bad(), |
| 157 | } | 166 | } |
| 158 | } | 167 | } |
| 159 | 168 | ||
| ... | @@ -162,8 +171,8 @@ pub const FormValue = union(enum) { | ... | @@ -162,8 +171,8 @@ pub const FormValue = union(enum) { |
| 162 | inline .udata, | 171 | inline .udata, |
| 163 | .sdata, | 172 | .sdata, |
| 164 | .sec_offset, | 173 | .sec_offset, |
| 165 | => |c| cast(U, c) orelse badDwarf(), | 174 | => |c| cast(U, c) orelse bad(), |
| 166 | else => badDwarf(), | 175 | else => bad(), |
| 167 | }; | 176 | }; |
| 168 | } | 177 | } |
| 169 | }; | 178 | }; |
| ... | @@ -237,25 +246,25 @@ pub const Die = struct { | ... | @@ -237,25 +246,25 @@ pub const Die = struct { |
| 237 | .string => |value| return value, | 246 | .string => |value| return value, |
| 238 | .strp => |offset| return di.getString(offset), | 247 | .strp => |offset| return di.getString(offset), |
| 239 | .strx => |index| { | 248 | .strx => |index| { |
| 240 | const debug_str_offsets = di.section(.debug_str_offsets) orelse return badDwarf(); | 249 | const debug_str_offsets = di.section(.debug_str_offsets) orelse return bad(); |
| 241 | if (compile_unit.str_offsets_base == 0) return badDwarf(); | 250 | if (compile_unit.str_offsets_base == 0) return bad(); |
| 242 | switch (compile_unit.format) { | 251 | switch (compile_unit.format) { |
| 243 | .@"32" => { | 252 | .@"32" => { |
| 244 | const byte_offset = compile_unit.str_offsets_base + 4 * index; | 253 | const byte_offset = compile_unit.str_offsets_base + 4 * index; |
| 245 | if (byte_offset + 4 > debug_str_offsets.len) return badDwarf(); | 254 | if (byte_offset + 4 > debug_str_offsets.len) return bad(); |
| 246 | const offset = readInt(u32, debug_str_offsets[byte_offset..][0..4], di.endian); | 255 | const offset = readInt(u32, debug_str_offsets[byte_offset..][0..4], di.endian); |
| 247 | return getStringGeneric(opt_str, offset); | 256 | return getStringGeneric(opt_str, offset); |
| 248 | }, | 257 | }, |
| 249 | .@"64" => { | 258 | .@"64" => { |
| 250 | const byte_offset = compile_unit.str_offsets_base + 8 * index; | 259 | const byte_offset = compile_unit.str_offsets_base + 8 * index; |
| 251 | if (byte_offset + 8 > debug_str_offsets.len) return badDwarf(); | 260 | if (byte_offset + 8 > debug_str_offsets.len) return bad(); |
| 252 | const offset = readInt(u64, debug_str_offsets[byte_offset..][0..8], di.endian); | 261 | const offset = readInt(u64, debug_str_offsets[byte_offset..][0..8], di.endian); |
| 253 | return getStringGeneric(opt_str, offset); | 262 | return getStringGeneric(opt_str, offset); |
| 254 | }, | 263 | }, |
| 255 | } | 264 | } |
| 256 | }, | 265 | }, |
| 257 | .line_strp => |offset| return di.getLineString(offset), | 266 | .line_strp => |offset| return di.getLineString(offset), |
| 258 | else => return badDwarf(), | 267 | else => return bad(), |
| 259 | } | 268 | } |
| 260 | } | 269 | } |
| 261 | }; | 270 | }; |
| ... | @@ -279,7 +288,7 @@ pub const ExceptionFrameHeader = struct { | ... | @@ -279,7 +288,7 @@ pub const ExceptionFrameHeader = struct { |
| 279 | EH.PE.sdata8, | 288 | EH.PE.sdata8, |
| 280 | => 16, | 289 | => 16, |
| 281 | // This is a binary search table, so all entries must be the same length | 290 | // This is a binary search table, so all entries must be the same length |
| 282 | else => return badDwarf(), | 291 | else => return bad(), |
| 283 | }; | 292 | }; |
| 284 | } | 293 | } |
| 285 | 294 | ||
| ... | @@ -287,7 +296,7 @@ pub const ExceptionFrameHeader = struct { | ... | @@ -287,7 +296,7 @@ pub const ExceptionFrameHeader = struct { |
| 287 | self: ExceptionFrameHeader, | 296 | self: ExceptionFrameHeader, |
| 288 | comptime T: type, | 297 | comptime T: type, |
| 289 | ptr: usize, | 298 | ptr: usize, |
| 290 | ma: *StackIterator.MemoryAccessor, | 299 | ma: *MemoryAccessor, |
| 291 | eh_frame_len: ?usize, | 300 | eh_frame_len: ?usize, |
| 292 | ) bool { | 301 | ) bool { |
| 293 | if (eh_frame_len) |len| { | 302 | if (eh_frame_len) |len| { |
| ... | @@ -304,7 +313,7 @@ pub const ExceptionFrameHeader = struct { | ... | @@ -304,7 +313,7 @@ pub const ExceptionFrameHeader = struct { |
| 304 | /// If `eh_frame_len` is provided, then these checks can be skipped. | 313 | /// If `eh_frame_len` is provided, then these checks can be skipped. |
| 305 | pub fn findEntry( | 314 | pub fn findEntry( |
| 306 | self: ExceptionFrameHeader, | 315 | self: ExceptionFrameHeader, |
| 307 | ma: *StackIterator.MemoryAccessor, | 316 | ma: *MemoryAccessor, |
| 308 | eh_frame_len: ?usize, | 317 | eh_frame_len: ?usize, |
| 309 | eh_frame_hdr_ptr: usize, | 318 | eh_frame_hdr_ptr: usize, |
| 310 | pc: usize, | 319 | pc: usize, |
| ... | @@ -316,7 +325,7 @@ pub const ExceptionFrameHeader = struct { | ... | @@ -316,7 +325,7 @@ pub const ExceptionFrameHeader = struct { |
| 316 | var left: usize = 0; | 325 | var left: usize = 0; |
| 317 | var len: usize = self.fde_count; | 326 | var len: usize = self.fde_count; |
| 318 | 327 | ||
| 319 | var fbr: FixedBufferReader = .{ .buf = self.entries, .endian = native_endian }; | 328 | var fbr: DeprecatedFixedBufferReader = .{ .buf = self.entries, .endian = native_endian }; |
| 320 | 329 | ||
| 321 | while (len > 1) { | 330 | while (len > 1) { |
| 322 | const mid = left + len / 2; | 331 | const mid = left + len / 2; |
| ... | @@ -326,7 +335,7 @@ pub const ExceptionFrameHeader = struct { | ... | @@ -326,7 +335,7 @@ pub const ExceptionFrameHeader = struct { |
| 326 | .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]), | 335 | .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]), |
| 327 | .follow_indirect = true, | 336 | .follow_indirect = true, |
| 328 | .data_rel_base = eh_frame_hdr_ptr, | 337 | .data_rel_base = eh_frame_hdr_ptr, |
| 329 | }) orelse return badDwarf(); | 338 | }) orelse return bad(); |
| 330 | 339 | ||
| 331 | if (pc < pc_begin) { | 340 | if (pc < pc_begin) { |
| 332 | len /= 2; | 341 | len /= 2; |
| ... | @@ -337,7 +346,7 @@ pub const ExceptionFrameHeader = struct { | ... | @@ -337,7 +346,7 @@ pub const ExceptionFrameHeader = struct { |
| 337 | } | 346 | } |
| 338 | } | 347 | } |
| 339 | 348 | ||
| 340 | if (len == 0) return badDwarf(); | 349 | if (len == 0) return bad(); |
| 341 | fbr.pos = left * entry_size; | 350 | fbr.pos = left * entry_size; |
| 342 | 351 | ||
| 343 | // Read past the pc_begin field of the entry | 352 | // Read past the pc_begin field of the entry |
| ... | @@ -345,36 +354,36 @@ pub const ExceptionFrameHeader = struct { | ... | @@ -345,36 +354,36 @@ pub const ExceptionFrameHeader = struct { |
| 345 | .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]), | 354 | .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]), |
| 346 | .follow_indirect = true, | 355 | .follow_indirect = true, |
| 347 | .data_rel_base = eh_frame_hdr_ptr, | 356 | .data_rel_base = eh_frame_hdr_ptr, |
| 348 | }) orelse return badDwarf(); | 357 | }) orelse return bad(); |
| 349 | 358 | ||
| 350 | const fde_ptr = cast(usize, try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{ | 359 | const fde_ptr = cast(usize, try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{ |
| 351 | .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]), | 360 | .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]), |
| 352 | .follow_indirect = true, | 361 | .follow_indirect = true, |
| 353 | .data_rel_base = eh_frame_hdr_ptr, | 362 | .data_rel_base = eh_frame_hdr_ptr, |
| 354 | }) orelse return badDwarf()) orelse return badDwarf(); | 363 | }) orelse return bad()) orelse return bad(); |
| 355 | 364 | ||
| 356 | if (fde_ptr < self.eh_frame_ptr) return badDwarf(); | 365 | if (fde_ptr < self.eh_frame_ptr) return bad(); |
| 357 | 366 | ||
| 358 | // Even if eh_frame_len is not specified, all ranges accssed are checked via MemoryAccessor | 367 | // Even if eh_frame_len is not specified, all ranges accssed are checked via MemoryAccessor |
| 359 | const eh_frame = @as([*]const u8, @ptrFromInt(self.eh_frame_ptr))[0 .. eh_frame_len orelse maxInt(u32)]; | 368 | const eh_frame = @as([*]const u8, @ptrFromInt(self.eh_frame_ptr))[0 .. eh_frame_len orelse maxInt(u32)]; |
| 360 | 369 | ||
| 361 | const fde_offset = fde_ptr - self.eh_frame_ptr; | 370 | const fde_offset = fde_ptr - self.eh_frame_ptr; |
| 362 | var eh_frame_fbr: FixedBufferReader = .{ | 371 | var eh_frame_fbr: DeprecatedFixedBufferReader = .{ |
| 363 | .buf = eh_frame, | 372 | .buf = eh_frame, |
| 364 | .pos = fde_offset, | 373 | .pos = fde_offset, |
| 365 | .endian = native_endian, | 374 | .endian = native_endian, |
| 366 | }; | 375 | }; |
| 367 | 376 | ||
| 368 | const fde_entry_header = try EntryHeader.read(&eh_frame_fbr, if (eh_frame_len == null) ma else null, .eh_frame); | 377 | const fde_entry_header = try EntryHeader.read(&eh_frame_fbr, if (eh_frame_len == null) ma else null, .eh_frame); |
| 369 | if (!self.isValidPtr(u8, @intFromPtr(&fde_entry_header.entry_bytes[fde_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return badDwarf(); | 378 | if (!self.isValidPtr(u8, @intFromPtr(&fde_entry_header.entry_bytes[fde_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return bad(); |
| 370 | if (fde_entry_header.type != .fde) return badDwarf(); | 379 | if (fde_entry_header.type != .fde) return bad(); |
| 371 | 380 | ||
| 372 | // CIEs always come before FDEs (the offset is a subtraction), so we can assume this memory is readable | 381 | // CIEs always come before FDEs (the offset is a subtraction), so we can assume this memory is readable |
| 373 | const cie_offset = fde_entry_header.type.fde; | 382 | const cie_offset = fde_entry_header.type.fde; |
| 374 | try eh_frame_fbr.seekTo(cie_offset); | 383 | try eh_frame_fbr.seekTo(cie_offset); |
| 375 | const cie_entry_header = try EntryHeader.read(&eh_frame_fbr, if (eh_frame_len == null) ma else null, .eh_frame); | 384 | const cie_entry_header = try EntryHeader.read(&eh_frame_fbr, if (eh_frame_len == null) ma else null, .eh_frame); |
| 376 | if (!self.isValidPtr(u8, @intFromPtr(&cie_entry_header.entry_bytes[cie_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return badDwarf(); | 385 | if (!self.isValidPtr(u8, @intFromPtr(&cie_entry_header.entry_bytes[cie_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return bad(); |
| 377 | if (cie_entry_header.type != .cie) return badDwarf(); | 386 | if (cie_entry_header.type != .cie) return bad(); |
| 378 | 387 | ||
| 379 | cie.* = try CommonInformationEntry.parse( | 388 | cie.* = try CommonInformationEntry.parse( |
| 380 | cie_entry_header.entry_bytes, | 389 | cie_entry_header.entry_bytes, |
| ... | @@ -417,17 +426,17 @@ pub const EntryHeader = struct { | ... | @@ -417,17 +426,17 @@ pub const EntryHeader = struct { |
| 417 | } | 426 | } |
| 418 | 427 | ||
| 419 | /// Reads a header for either an FDE or a CIE, then advances the fbr to the position after the trailing structure. | 428 | /// Reads a header for either an FDE or a CIE, then advances the fbr to the position after the trailing structure. |
| 420 | /// `fbr` must be a FixedBufferReader backed by either the .eh_frame or .debug_frame sections. | 429 | /// `fbr` must be a DeprecatedFixedBufferReader backed by either the .eh_frame or .debug_frame sections. |
| 421 | pub fn read( | 430 | pub fn read( |
| 422 | fbr: *FixedBufferReader, | 431 | fbr: *DeprecatedFixedBufferReader, |
| 423 | opt_ma: ?*StackIterator.MemoryAccessor, | 432 | opt_ma: ?*MemoryAccessor, |
| 424 | dwarf_section: Section.Id, | 433 | dwarf_section: Section.Id, |
| 425 | ) !EntryHeader { | 434 | ) !EntryHeader { |
| 426 | assert(dwarf_section == .eh_frame or dwarf_section == .debug_frame); | 435 | assert(dwarf_section == .eh_frame or dwarf_section == .debug_frame); |
| 427 | 436 | ||
| 428 | const length_offset = fbr.pos; | 437 | const length_offset = fbr.pos; |
| 429 | const unit_header = try readUnitHeader(fbr, opt_ma); | 438 | const unit_header = try readUnitHeader(fbr, opt_ma); |
| 430 | const unit_length = cast(usize, unit_header.unit_length) orelse return badDwarf(); | 439 | const unit_length = cast(usize, unit_header.unit_length) orelse return bad(); |
| 431 | if (unit_length == 0) return .{ | 440 | if (unit_length == 0) return .{ |
| 432 | .length_offset = length_offset, | 441 | .length_offset = length_offset, |
| 433 | .format = unit_header.format, | 442 | .format = unit_header.format, |
| ... | @@ -532,7 +541,7 @@ pub const CommonInformationEntry = struct { | ... | @@ -532,7 +541,7 @@ pub const CommonInformationEntry = struct { |
| 532 | ) !CommonInformationEntry { | 541 | ) !CommonInformationEntry { |
| 533 | if (addr_size_bytes > 8) return error.UnsupportedAddrSize; | 542 | if (addr_size_bytes > 8) return error.UnsupportedAddrSize; |
| 534 | 543 | ||
| 535 | var fbr: FixedBufferReader = .{ .buf = cie_bytes, .endian = endian }; | 544 | var fbr: DeprecatedFixedBufferReader = .{ .buf = cie_bytes, .endian = endian }; |
| 536 | 545 | ||
| 537 | const version = try fbr.readByte(); | 546 | const version = try fbr.readByte(); |
| 538 | switch (dwarf_section) { | 547 | switch (dwarf_section) { |
| ... | @@ -550,15 +559,15 @@ pub const CommonInformationEntry = struct { | ... | @@ -550,15 +559,15 @@ pub const CommonInformationEntry = struct { |
| 550 | while (aug_byte != 0) : (aug_byte = try fbr.readByte()) { | 559 | while (aug_byte != 0) : (aug_byte = try fbr.readByte()) { |
| 551 | switch (aug_byte) { | 560 | switch (aug_byte) { |
| 552 | 'z' => { | 561 | 'z' => { |
| 553 | if (aug_str_len != 0) return badDwarf(); | 562 | if (aug_str_len != 0) return bad(); |
| 554 | has_aug_data = true; | 563 | has_aug_data = true; |
| 555 | }, | 564 | }, |
| 556 | 'e' => { | 565 | 'e' => { |
| 557 | if (has_aug_data or aug_str_len != 0) return badDwarf(); | 566 | if (has_aug_data or aug_str_len != 0) return bad(); |
| 558 | if (try fbr.readByte() != 'h') return badDwarf(); | 567 | if (try fbr.readByte() != 'h') return bad(); |
| 559 | has_eh_data = true; | 568 | has_eh_data = true; |
| 560 | }, | 569 | }, |
| 561 | else => if (has_eh_data) return badDwarf(), | 570 | else => if (has_eh_data) return bad(), |
| 562 | } | 571 | } |
| 563 | 572 | ||
| 564 | aug_str_len += 1; | 573 | aug_str_len += 1; |
| ... | @@ -604,7 +613,7 @@ pub const CommonInformationEntry = struct { | ... | @@ -604,7 +613,7 @@ pub const CommonInformationEntry = struct { |
| 604 | fde_pointer_enc = try fbr.readByte(); | 613 | fde_pointer_enc = try fbr.readByte(); |
| 605 | }, | 614 | }, |
| 606 | 'S', 'B', 'G' => {}, | 615 | 'S', 'B', 'G' => {}, |
| 607 | else => return badDwarf(), | 616 | else => return bad(), |
| 608 | } | 617 | } |
| 609 | } | 618 | } |
| 610 | 619 | ||
| ... | @@ -666,17 +675,17 @@ pub const FrameDescriptionEntry = struct { | ... | @@ -666,17 +675,17 @@ pub const FrameDescriptionEntry = struct { |
| 666 | ) !FrameDescriptionEntry { | 675 | ) !FrameDescriptionEntry { |
| 667 | if (addr_size_bytes > 8) return error.InvalidAddrSize; | 676 | if (addr_size_bytes > 8) return error.InvalidAddrSize; |
| 668 | 677 | ||
| 669 | var fbr: FixedBufferReader = .{ .buf = fde_bytes, .endian = endian }; | 678 | var fbr: DeprecatedFixedBufferReader = .{ .buf = fde_bytes, .endian = endian }; |
| 670 | 679 | ||
| 671 | const pc_begin = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{ | 680 | const pc_begin = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{ |
| 672 | .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.pos]), pc_rel_offset), | 681 | .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.pos]), pc_rel_offset), |
| 673 | .follow_indirect = is_runtime, | 682 | .follow_indirect = is_runtime, |
| 674 | }) orelse return badDwarf(); | 683 | }) orelse return bad(); |
| 675 | 684 | ||
| 676 | const pc_range = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{ | 685 | const pc_range = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{ |
| 677 | .pc_rel_base = 0, | 686 | .pc_rel_base = 0, |
| 678 | .follow_indirect = false, | 687 | .follow_indirect = false, |
| 679 | }) orelse return badDwarf(); | 688 | }) orelse return bad(); |
| 680 | 689 | ||
| 681 | var aug_data: []const u8 = &[_]u8{}; | 690 | var aug_data: []const u8 = &[_]u8{}; |
| 682 | const lsda_pointer = if (cie.aug_str.len > 0) blk: { | 691 | const lsda_pointer = if (cie.aug_str.len > 0) blk: { |
| ... | @@ -708,54 +717,6 @@ pub const FrameDescriptionEntry = struct { | ... | @@ -708,54 +717,6 @@ pub const FrameDescriptionEntry = struct { |
| 708 | } | 717 | } |
| 709 | }; | 718 | }; |
| 710 | 719 | ||
| 711 | pub const UnwindContext = struct { | ||
| 712 | allocator: Allocator, | ||
| 713 | cfa: ?usize, | ||
| 714 | pc: usize, | ||
| 715 | thread_context: *std.debug.ThreadContext, | ||
| 716 | reg_context: abi.RegisterContext, | ||
| 717 | vm: call_frame.VirtualMachine, | ||
| 718 | stack_machine: expression.StackMachine(.{ .call_frame_context = true }), | ||
| 719 | |||
| 720 | pub fn init( | ||
| 721 | allocator: Allocator, | ||
| 722 | thread_context: *const std.debug.ThreadContext, | ||
| 723 | ) !UnwindContext { | ||
| 724 | const pc = abi.stripInstructionPtrAuthCode( | ||
| 725 | (try abi.regValueNative( | ||
| 726 | usize, | ||
| 727 | thread_context, | ||
| 728 | abi.ipRegNum(), | ||
| 729 | null, | ||
| 730 | )).*, | ||
| 731 | ); | ||
| 732 | |||
| 733 | const context_copy = try allocator.create(std.debug.ThreadContext); | ||
| 734 | std.debug.copyContext(thread_context, context_copy); | ||
| 735 | |||
| 736 | return .{ | ||
| 737 | .allocator = allocator, | ||
| 738 | .cfa = null, | ||
| 739 | .pc = pc, | ||
| 740 | .thread_context = context_copy, | ||
| 741 | .reg_context = undefined, | ||
| 742 | .vm = .{}, | ||
| 743 | .stack_machine = .{}, | ||
| 744 | }; | ||
| 745 | } | ||
| 746 | |||
| 747 | pub fn deinit(self: *UnwindContext) void { | ||
| 748 | self.vm.deinit(self.allocator); | ||
| 749 | self.stack_machine.deinit(self.allocator); | ||
| 750 | self.allocator.destroy(self.thread_context); | ||
| 751 | self.* = undefined; | ||
| 752 | } | ||
| 753 | |||
| 754 | pub fn getFp(self: *const UnwindContext) !usize { | ||
| 755 | return (try abi.regValueNative(usize, self.thread_context, abi.fpRegNum(self.reg_context), self.reg_context)).*; | ||
| 756 | } | ||
| 757 | }; | ||
| 758 | |||
| 759 | const num_sections = std.enums.directEnumArrayLen(Section.Id, 0); | 720 | const num_sections = std.enums.directEnumArrayLen(Section.Id, 0); |
| 760 | pub const SectionArray = [num_sections]?Section; | 721 | pub const SectionArray = [num_sections]?Section; |
| 761 | pub const null_section_array = [_]?Section{null} ** num_sections; | 722 | pub const null_section_array = [_]?Section{null} ** num_sections; |
| ... | @@ -817,7 +778,7 @@ pub fn getSymbolName(di: *Dwarf, address: u64) ?[]const u8 { | ... | @@ -817,7 +778,7 @@ pub fn getSymbolName(di: *Dwarf, address: u64) ?[]const u8 { |
| 817 | } | 778 | } |
| 818 | 779 | ||
| 819 | fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { | 780 | fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { |
| 820 | var fbr: FixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; | 781 | var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; |
| 821 | var this_unit_offset: u64 = 0; | 782 | var this_unit_offset: u64 = 0; |
| 822 | 783 | ||
| 823 | while (this_unit_offset < fbr.buf.len) { | 784 | while (this_unit_offset < fbr.buf.len) { |
| ... | @@ -828,20 +789,20 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { | ... | @@ -828,20 +789,20 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { |
| 828 | const next_offset = unit_header.header_length + unit_header.unit_length; | 789 | const next_offset = unit_header.header_length + unit_header.unit_length; |
| 829 | 790 | ||
| 830 | const version = try fbr.readInt(u16); | 791 | const version = try fbr.readInt(u16); |
| 831 | if (version < 2 or version > 5) return badDwarf(); | 792 | if (version < 2 or version > 5) return bad(); |
| 832 | 793 | ||
| 833 | var address_size: u8 = undefined; | 794 | var address_size: u8 = undefined; |
| 834 | var debug_abbrev_offset: u64 = undefined; | 795 | var debug_abbrev_offset: u64 = undefined; |
| 835 | if (version >= 5) { | 796 | if (version >= 5) { |
| 836 | const unit_type = try fbr.readInt(u8); | 797 | const unit_type = try fbr.readInt(u8); |
| 837 | if (unit_type != DW.UT.compile) return badDwarf(); | 798 | if (unit_type != DW.UT.compile) return bad(); |
| 838 | address_size = try fbr.readByte(); | 799 | address_size = try fbr.readByte(); |
| 839 | debug_abbrev_offset = try fbr.readAddress(unit_header.format); | 800 | debug_abbrev_offset = try fbr.readAddress(unit_header.format); |
| 840 | } else { | 801 | } else { |
| 841 | debug_abbrev_offset = try fbr.readAddress(unit_header.format); | 802 | debug_abbrev_offset = try fbr.readAddress(unit_header.format); |
| 842 | address_size = try fbr.readByte(); | 803 | address_size = try fbr.readByte(); |
| 843 | } | 804 | } |
| 844 | if (address_size != @sizeOf(usize)) return badDwarf(); | 805 | if (address_size != @sizeOf(usize)) return bad(); |
| 845 | 806 | ||
| 846 | const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset); | 807 | const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset); |
| 847 | 808 | ||
| ... | @@ -915,28 +876,28 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { | ... | @@ -915,28 +876,28 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { |
| 915 | 876 | ||
| 916 | // Follow the DIE it points to and repeat | 877 | // Follow the DIE it points to and repeat |
| 917 | const ref_offset = try this_die_obj.getAttrRef(AT.abstract_origin); | 878 | const ref_offset = try this_die_obj.getAttrRef(AT.abstract_origin); |
| 918 | if (ref_offset > next_offset) return badDwarf(); | 879 | if (ref_offset > next_offset) return bad(); |
| 919 | try fbr.seekTo(this_unit_offset + ref_offset); | 880 | try fbr.seekTo(this_unit_offset + ref_offset); |
| 920 | this_die_obj = (try parseDie( | 881 | this_die_obj = (try parseDie( |
| 921 | &fbr, | 882 | &fbr, |
| 922 | attrs_bufs[2], | 883 | attrs_bufs[2], |
| 923 | abbrev_table, | 884 | abbrev_table, |
| 924 | unit_header.format, | 885 | unit_header.format, |
| 925 | )) orelse return badDwarf(); | 886 | )) orelse return bad(); |
| 926 | } else if (this_die_obj.getAttr(AT.specification)) |_| { | 887 | } else if (this_die_obj.getAttr(AT.specification)) |_| { |
| 927 | const after_die_offset = fbr.pos; | 888 | const after_die_offset = fbr.pos; |
| 928 | defer fbr.pos = after_die_offset; | 889 | defer fbr.pos = after_die_offset; |
| 929 | 890 | ||
| 930 | // Follow the DIE it points to and repeat | 891 | // Follow the DIE it points to and repeat |
| 931 | const ref_offset = try this_die_obj.getAttrRef(AT.specification); | 892 | const ref_offset = try this_die_obj.getAttrRef(AT.specification); |
| 932 | if (ref_offset > next_offset) return badDwarf(); | 893 | if (ref_offset > next_offset) return bad(); |
| 933 | try fbr.seekTo(this_unit_offset + ref_offset); | 894 | try fbr.seekTo(this_unit_offset + ref_offset); |
| 934 | this_die_obj = (try parseDie( | 895 | this_die_obj = (try parseDie( |
| 935 | &fbr, | 896 | &fbr, |
| 936 | attrs_bufs[2], | 897 | attrs_bufs[2], |
| 937 | abbrev_table, | 898 | abbrev_table, |
| 938 | unit_header.format, | 899 | unit_header.format, |
| 939 | )) orelse return badDwarf(); | 900 | )) orelse return bad(); |
| 940 | } else { | 901 | } else { |
| 941 | break :x null; | 902 | break :x null; |
| 942 | } | 903 | } |
| ... | @@ -950,7 +911,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { | ... | @@ -950,7 +911,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { |
| 950 | const pc_end = switch (high_pc_value.*) { | 911 | const pc_end = switch (high_pc_value.*) { |
| 951 | .addr => |value| value, | 912 | .addr => |value| value, |
| 952 | .udata => |offset| low_pc + offset, | 913 | .udata => |offset| low_pc + offset, |
| 953 | else => return badDwarf(), | 914 | else => return bad(), |
| 954 | }; | 915 | }; |
| 955 | 916 | ||
| 956 | try di.func_list.append(allocator, .{ | 917 | try di.func_list.append(allocator, .{ |
| ... | @@ -1004,7 +965,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { | ... | @@ -1004,7 +965,7 @@ fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { |
| 1004 | } | 965 | } |
| 1005 | 966 | ||
| 1006 | fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void { | 967 | fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void { |
| 1007 | var fbr: FixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; | 968 | var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; |
| 1008 | var this_unit_offset: u64 = 0; | 969 | var this_unit_offset: u64 = 0; |
| 1009 | 970 | ||
| 1010 | var attrs_buf = std.ArrayList(Die.Attr).init(allocator); | 971 | var attrs_buf = std.ArrayList(Die.Attr).init(allocator); |
| ... | @@ -1018,20 +979,20 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void { | ... | @@ -1018,20 +979,20 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void { |
| 1018 | const next_offset = unit_header.header_length + unit_header.unit_length; | 979 | const next_offset = unit_header.header_length + unit_header.unit_length; |
| 1019 | 980 | ||
| 1020 | const version = try fbr.readInt(u16); | 981 | const version = try fbr.readInt(u16); |
| 1021 | if (version < 2 or version > 5) return badDwarf(); | 982 | if (version < 2 or version > 5) return bad(); |
| 1022 | 983 | ||
| 1023 | var address_size: u8 = undefined; | 984 | var address_size: u8 = undefined; |
| 1024 | var debug_abbrev_offset: u64 = undefined; | 985 | var debug_abbrev_offset: u64 = undefined; |
| 1025 | if (version >= 5) { | 986 | if (version >= 5) { |
| 1026 | const unit_type = try fbr.readInt(u8); | 987 | const unit_type = try fbr.readInt(u8); |
| 1027 | if (unit_type != UT.compile) return badDwarf(); | 988 | if (unit_type != UT.compile) return bad(); |
| 1028 | address_size = try fbr.readByte(); | 989 | address_size = try fbr.readByte(); |
| 1029 | debug_abbrev_offset = try fbr.readAddress(unit_header.format); | 990 | debug_abbrev_offset = try fbr.readAddress(unit_header.format); |
| 1030 | } else { | 991 | } else { |
| 1031 | debug_abbrev_offset = try fbr.readAddress(unit_header.format); | 992 | debug_abbrev_offset = try fbr.readAddress(unit_header.format); |
| 1032 | address_size = try fbr.readByte(); | 993 | address_size = try fbr.readByte(); |
| 1033 | } | 994 | } |
| 1034 | if (address_size != @sizeOf(usize)) return badDwarf(); | 995 | if (address_size != @sizeOf(usize)) return bad(); |
| 1035 | 996 | ||
| 1036 | const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset); | 997 | const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset); |
| 1037 | 998 | ||
| ... | @@ -1046,9 +1007,9 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void { | ... | @@ -1046,9 +1007,9 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void { |
| 1046 | attrs_buf.items, | 1007 | attrs_buf.items, |
| 1047 | abbrev_table, | 1008 | abbrev_table, |
| 1048 | unit_header.format, | 1009 | unit_header.format, |
| 1049 | )) orelse return badDwarf(); | 1010 | )) orelse return bad(); |
| 1050 | 1011 | ||
| 1051 | if (compile_unit_die.tag_id != DW.TAG.compile_unit) return badDwarf(); | 1012 | if (compile_unit_die.tag_id != DW.TAG.compile_unit) return bad(); |
| 1052 | 1013 | ||
| 1053 | compile_unit_die.attrs = try allocator.dupe(Die.Attr, compile_unit_die.attrs); | 1014 | compile_unit_die.attrs = try allocator.dupe(Die.Attr, compile_unit_die.attrs); |
| 1054 | 1015 | ||
| ... | @@ -1070,7 +1031,7 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void { | ... | @@ -1070,7 +1031,7 @@ fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void { |
| 1070 | const pc_end = switch (high_pc_value.*) { | 1031 | const pc_end = switch (high_pc_value.*) { |
| 1071 | .addr => |value| value, | 1032 | .addr => |value| value, |
| 1072 | .udata => |offset| low_pc + offset, | 1033 | .udata => |offset| low_pc + offset, |
| 1073 | else => return badDwarf(), | 1034 | else => return bad(), |
| 1074 | }; | 1035 | }; |
| 1075 | break :x PcRange{ | 1036 | break :x PcRange{ |
| 1076 | .start = low_pc, | 1037 | .start = low_pc, |
| ... | @@ -1096,7 +1057,7 @@ const DebugRangeIterator = struct { | ... | @@ -1096,7 +1057,7 @@ const DebugRangeIterator = struct { |
| 1096 | section_type: Section.Id, | 1057 | section_type: Section.Id, |
| 1097 | di: *const Dwarf, | 1058 | di: *const Dwarf, |
| 1098 | compile_unit: *const CompileUnit, | 1059 | compile_unit: *const CompileUnit, |
| 1099 | fbr: FixedBufferReader, | 1060 | fbr: DeprecatedFixedBufferReader, |
| 1100 | 1061 | ||
| 1101 | pub fn init(ranges_value: *const FormValue, di: *const Dwarf, compile_unit: *const CompileUnit) !@This() { | 1062 | pub fn init(ranges_value: *const FormValue, di: *const Dwarf, compile_unit: *const CompileUnit) !@This() { |
| 1102 | const section_type = if (compile_unit.version >= 5) Section.Id.debug_rnglists else Section.Id.debug_ranges; | 1063 | const section_type = if (compile_unit.version >= 5) Section.Id.debug_rnglists else Section.Id.debug_ranges; |
| ... | @@ -1108,19 +1069,19 @@ const DebugRangeIterator = struct { | ... | @@ -1108,19 +1069,19 @@ const DebugRangeIterator = struct { |
| 1108 | switch (compile_unit.format) { | 1069 | switch (compile_unit.format) { |
| 1109 | .@"32" => { | 1070 | .@"32" => { |
| 1110 | const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 4 * idx)); | 1071 | const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 4 * idx)); |
| 1111 | if (offset_loc + 4 > debug_ranges.len) return badDwarf(); | 1072 | if (offset_loc + 4 > debug_ranges.len) return bad(); |
| 1112 | const offset = readInt(u32, debug_ranges[offset_loc..][0..4], di.endian); | 1073 | const offset = readInt(u32, debug_ranges[offset_loc..][0..4], di.endian); |
| 1113 | break :off compile_unit.rnglists_base + offset; | 1074 | break :off compile_unit.rnglists_base + offset; |
| 1114 | }, | 1075 | }, |
| 1115 | .@"64" => { | 1076 | .@"64" => { |
| 1116 | const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 8 * idx)); | 1077 | const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 8 * idx)); |
| 1117 | if (offset_loc + 8 > debug_ranges.len) return badDwarf(); | 1078 | if (offset_loc + 8 > debug_ranges.len) return bad(); |
| 1118 | const offset = readInt(u64, debug_ranges[offset_loc..][0..8], di.endian); | 1079 | const offset = readInt(u64, debug_ranges[offset_loc..][0..8], di.endian); |
| 1119 | break :off compile_unit.rnglists_base + offset; | 1080 | break :off compile_unit.rnglists_base + offset; |
| 1120 | }, | 1081 | }, |
| 1121 | } | 1082 | } |
| 1122 | }, | 1083 | }, |
| 1123 | else => return badDwarf(), | 1084 | else => return bad(), |
| 1124 | }; | 1085 | }; |
| 1125 | 1086 | ||
| 1126 | // All the addresses in the list are relative to the value | 1087 | // All the addresses in the list are relative to the value |
| ... | @@ -1139,7 +1100,7 @@ const DebugRangeIterator = struct { | ... | @@ -1139,7 +1100,7 @@ const DebugRangeIterator = struct { |
| 1139 | .compile_unit = compile_unit, | 1100 | .compile_unit = compile_unit, |
| 1140 | .fbr = .{ | 1101 | .fbr = .{ |
| 1141 | .buf = debug_ranges, | 1102 | .buf = debug_ranges, |
| 1142 | .pos = cast(usize, ranges_offset) orelse return badDwarf(), | 1103 | .pos = cast(usize, ranges_offset) orelse return bad(), |
| 1143 | .endian = di.endian, | 1104 | .endian = di.endian, |
| 1144 | }, | 1105 | }, |
| 1145 | }; | 1106 | }; |
| ... | @@ -1214,7 +1175,7 @@ const DebugRangeIterator = struct { | ... | @@ -1214,7 +1175,7 @@ const DebugRangeIterator = struct { |
| 1214 | .end_addr = end_addr, | 1175 | .end_addr = end_addr, |
| 1215 | }; | 1176 | }; |
| 1216 | }, | 1177 | }, |
| 1217 | else => return badDwarf(), | 1178 | else => return bad(), |
| 1218 | } | 1179 | } |
| 1219 | }, | 1180 | }, |
| 1220 | .debug_ranges => { | 1181 | .debug_ranges => { |
| ... | @@ -1251,7 +1212,7 @@ pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*const CompileUni | ... | @@ -1251,7 +1212,7 @@ pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*const CompileUni |
| 1251 | } | 1212 | } |
| 1252 | } | 1213 | } |
| 1253 | 1214 | ||
| 1254 | return missingDwarf(); | 1215 | return missing(); |
| 1255 | } | 1216 | } |
| 1256 | 1217 | ||
| 1257 | /// Gets an already existing AbbrevTable given the abbrev_offset, or if not found, | 1218 | /// Gets an already existing AbbrevTable given the abbrev_offset, or if not found, |
| ... | @@ -1270,9 +1231,9 @@ fn getAbbrevTable(di: *Dwarf, allocator: Allocator, abbrev_offset: u64) !*const | ... | @@ -1270,9 +1231,9 @@ fn getAbbrevTable(di: *Dwarf, allocator: Allocator, abbrev_offset: u64) !*const |
| 1270 | } | 1231 | } |
| 1271 | 1232 | ||
| 1272 | fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table { | 1233 | fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table { |
| 1273 | var fbr: FixedBufferReader = .{ | 1234 | var fbr: DeprecatedFixedBufferReader = .{ |
| 1274 | .buf = di.section(.debug_abbrev).?, | 1235 | .buf = di.section(.debug_abbrev).?, |
| 1275 | .pos = cast(usize, offset) orelse return badDwarf(), | 1236 | .pos = cast(usize, offset) orelse return bad(), |
| 1276 | .endian = di.endian, | 1237 | .endian = di.endian, |
| 1277 | }; | 1238 | }; |
| 1278 | 1239 | ||
| ... | @@ -1322,14 +1283,14 @@ fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table | ... | @@ -1322,14 +1283,14 @@ fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table |
| 1322 | } | 1283 | } |
| 1323 | 1284 | ||
| 1324 | fn parseDie( | 1285 | fn parseDie( |
| 1325 | fbr: *FixedBufferReader, | 1286 | fbr: *DeprecatedFixedBufferReader, |
| 1326 | attrs_buf: []Die.Attr, | 1287 | attrs_buf: []Die.Attr, |
| 1327 | abbrev_table: *const Abbrev.Table, | 1288 | abbrev_table: *const Abbrev.Table, |
| 1328 | format: Format, | 1289 | format: Format, |
| 1329 | ) !?Die { | 1290 | ) !?Die { |
| 1330 | const abbrev_code = try fbr.readUleb128(u64); | 1291 | const abbrev_code = try fbr.readUleb128(u64); |
| 1331 | if (abbrev_code == 0) return null; | 1292 | if (abbrev_code == 0) return null; |
| 1332 | const table_entry = abbrev_table.get(abbrev_code) orelse return badDwarf(); | 1293 | const table_entry = abbrev_table.get(abbrev_code) orelse return bad(); |
| 1333 | 1294 | ||
| 1334 | const attrs = attrs_buf[0..table_entry.attrs.len]; | 1295 | const attrs = attrs_buf[0..table_entry.attrs.len]; |
| 1335 | for (attrs, table_entry.attrs) |*result_attr, attr| result_attr.* = Die.Attr{ | 1296 | for (attrs, table_entry.attrs) |*result_attr, attr| result_attr.* = Die.Attr{ |
| ... | @@ -1353,19 +1314,19 @@ pub fn getLineNumberInfo( | ... | @@ -1353,19 +1314,19 @@ pub fn getLineNumberInfo( |
| 1353 | allocator: Allocator, | 1314 | allocator: Allocator, |
| 1354 | compile_unit: CompileUnit, | 1315 | compile_unit: CompileUnit, |
| 1355 | target_address: u64, | 1316 | target_address: u64, |
| 1356 | ) !std.debug.LineInfo { | 1317 | ) !std.debug.SourceLocation { |
| 1357 | const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.section(.debug_line_str), compile_unit); | 1318 | const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.section(.debug_line_str), compile_unit); |
| 1358 | const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list); | 1319 | const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list); |
| 1359 | 1320 | ||
| 1360 | var fbr: FixedBufferReader = .{ .buf = di.section(.debug_line).?, .endian = di.endian }; | 1321 | var fbr: DeprecatedFixedBufferReader = .{ .buf = di.section(.debug_line).?, .endian = di.endian }; |
| 1361 | try fbr.seekTo(line_info_offset); | 1322 | try fbr.seekTo(line_info_offset); |
| 1362 | 1323 | ||
| 1363 | const unit_header = try readUnitHeader(&fbr, null); | 1324 | const unit_header = try readUnitHeader(&fbr, null); |
| 1364 | if (unit_header.unit_length == 0) return missingDwarf(); | 1325 | if (unit_header.unit_length == 0) return missing(); |
| 1365 | const next_offset = unit_header.header_length + unit_header.unit_length; | 1326 | const next_offset = unit_header.header_length + unit_header.unit_length; |
| 1366 | 1327 | ||
| 1367 | const version = try fbr.readInt(u16); | 1328 | const version = try fbr.readInt(u16); |
| 1368 | if (version < 2) return badDwarf(); | 1329 | if (version < 2) return bad(); |
| 1369 | 1330 | ||
| 1370 | var addr_size: u8 = switch (unit_header.format) { | 1331 | var addr_size: u8 = switch (unit_header.format) { |
| 1371 | .@"32" => 4, | 1332 | .@"32" => 4, |
| ... | @@ -1381,7 +1342,7 @@ pub fn getLineNumberInfo( | ... | @@ -1381,7 +1342,7 @@ pub fn getLineNumberInfo( |
| 1381 | const prog_start_offset = fbr.pos + prologue_length; | 1342 | const prog_start_offset = fbr.pos + prologue_length; |
| 1382 | 1343 | ||
| 1383 | const minimum_instruction_length = try fbr.readByte(); | 1344 | const minimum_instruction_length = try fbr.readByte(); |
| 1384 | if (minimum_instruction_length == 0) return badDwarf(); | 1345 | if (minimum_instruction_length == 0) return bad(); |
| 1385 | 1346 | ||
| 1386 | if (version >= 4) { | 1347 | if (version >= 4) { |
| 1387 | // maximum_operations_per_instruction | 1348 | // maximum_operations_per_instruction |
| ... | @@ -1392,7 +1353,7 @@ pub fn getLineNumberInfo( | ... | @@ -1392,7 +1353,7 @@ pub fn getLineNumberInfo( |
| 1392 | const line_base = try fbr.readByteSigned(); | 1353 | const line_base = try fbr.readByteSigned(); |
| 1393 | 1354 | ||
| 1394 | const line_range = try fbr.readByte(); | 1355 | const line_range = try fbr.readByte(); |
| 1395 | if (line_range == 0) return badDwarf(); | 1356 | if (line_range == 0) return bad(); |
| 1396 | 1357 | ||
| 1397 | const opcode_base = try fbr.readByte(); | 1358 | const opcode_base = try fbr.readByte(); |
| 1398 | 1359 | ||
| ... | @@ -1433,7 +1394,7 @@ pub fn getLineNumberInfo( | ... | @@ -1433,7 +1394,7 @@ pub fn getLineNumberInfo( |
| 1433 | { | 1394 | { |
| 1434 | var dir_ent_fmt_buf: [10]FileEntFmt = undefined; | 1395 | var dir_ent_fmt_buf: [10]FileEntFmt = undefined; |
| 1435 | const directory_entry_format_count = try fbr.readByte(); | 1396 | const directory_entry_format_count = try fbr.readByte(); |
| 1436 | if (directory_entry_format_count > dir_ent_fmt_buf.len) return badDwarf(); | 1397 | if (directory_entry_format_count > dir_ent_fmt_buf.len) return bad(); |
| 1437 | for (dir_ent_fmt_buf[0..directory_entry_format_count]) |*ent_fmt| { | 1398 | for (dir_ent_fmt_buf[0..directory_entry_format_count]) |*ent_fmt| { |
| 1438 | ent_fmt.* = .{ | 1399 | ent_fmt.* = .{ |
| 1439 | .content_type_code = try fbr.readUleb128(u8), | 1400 | .content_type_code = try fbr.readUleb128(u8), |
| ... | @@ -1461,7 +1422,7 @@ pub fn getLineNumberInfo( | ... | @@ -1461,7 +1422,7 @@ pub fn getLineNumberInfo( |
| 1461 | DW.LNCT.size => e.size = try form_value.getUInt(u64), | 1422 | DW.LNCT.size => e.size = try form_value.getUInt(u64), |
| 1462 | DW.LNCT.MD5 => e.md5 = switch (form_value) { | 1423 | DW.LNCT.MD5 => e.md5 = switch (form_value) { |
| 1463 | .data16 => |data16| data16.*, | 1424 | .data16 => |data16| data16.*, |
| 1464 | else => return badDwarf(), | 1425 | else => return bad(), |
| 1465 | }, | 1426 | }, |
| 1466 | else => continue, | 1427 | else => continue, |
| 1467 | } | 1428 | } |
| ... | @@ -1473,7 +1434,7 @@ pub fn getLineNumberInfo( | ... | @@ -1473,7 +1434,7 @@ pub fn getLineNumberInfo( |
| 1473 | 1434 | ||
| 1474 | var file_ent_fmt_buf: [10]FileEntFmt = undefined; | 1435 | var file_ent_fmt_buf: [10]FileEntFmt = undefined; |
| 1475 | const file_name_entry_format_count = try fbr.readByte(); | 1436 | const file_name_entry_format_count = try fbr.readByte(); |
| 1476 | if (file_name_entry_format_count > file_ent_fmt_buf.len) return badDwarf(); | 1437 | if (file_name_entry_format_count > file_ent_fmt_buf.len) return bad(); |
| 1477 | for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| { | 1438 | for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| { |
| 1478 | ent_fmt.* = .{ | 1439 | ent_fmt.* = .{ |
| 1479 | .content_type_code = try fbr.readUleb128(u8), | 1440 | .content_type_code = try fbr.readUleb128(u8), |
| ... | @@ -1501,7 +1462,7 @@ pub fn getLineNumberInfo( | ... | @@ -1501,7 +1462,7 @@ pub fn getLineNumberInfo( |
| 1501 | DW.LNCT.size => e.size = try form_value.getUInt(u64), | 1462 | DW.LNCT.size => e.size = try form_value.getUInt(u64), |
| 1502 | DW.LNCT.MD5 => e.md5 = switch (form_value) { | 1463 | DW.LNCT.MD5 => e.md5 = switch (form_value) { |
| 1503 | .data16 => |data16| data16.*, | 1464 | .data16 => |data16| data16.*, |
| 1504 | else => return badDwarf(), | 1465 | else => return bad(), |
| 1505 | }, | 1466 | }, |
| 1506 | else => continue, | 1467 | else => continue, |
| 1507 | } | 1468 | } |
| ... | @@ -1527,7 +1488,7 @@ pub fn getLineNumberInfo( | ... | @@ -1527,7 +1488,7 @@ pub fn getLineNumberInfo( |
| 1527 | 1488 | ||
| 1528 | if (opcode == DW.LNS.extended_op) { | 1489 | if (opcode == DW.LNS.extended_op) { |
| 1529 | const op_size = try fbr.readUleb128(u64); | 1490 | const op_size = try fbr.readUleb128(u64); |
| 1530 | if (op_size < 1) return badDwarf(); | 1491 | if (op_size < 1) return bad(); |
| 1531 | const sub_op = try fbr.readByte(); | 1492 | const sub_op = try fbr.readByte(); |
| 1532 | switch (sub_op) { | 1493 | switch (sub_op) { |
| 1533 | DW.LNE.end_sequence => { | 1494 | DW.LNE.end_sequence => { |
| ... | @@ -1600,14 +1561,14 @@ pub fn getLineNumberInfo( | ... | @@ -1600,14 +1561,14 @@ pub fn getLineNumberInfo( |
| 1600 | }, | 1561 | }, |
| 1601 | DW.LNS.set_prologue_end => {}, | 1562 | DW.LNS.set_prologue_end => {}, |
| 1602 | else => { | 1563 | else => { |
| 1603 | if (opcode - 1 >= standard_opcode_lengths.len) return badDwarf(); | 1564 | if (opcode - 1 >= standard_opcode_lengths.len) return bad(); |
| 1604 | try fbr.seekForward(standard_opcode_lengths[opcode - 1]); | 1565 | try fbr.seekForward(standard_opcode_lengths[opcode - 1]); |
| 1605 | }, | 1566 | }, |
| 1606 | } | 1567 | } |
| 1607 | } | 1568 | } |
| 1608 | } | 1569 | } |
| 1609 | 1570 | ||
| 1610 | return missingDwarf(); | 1571 | return missing(); |
| 1611 | } | 1572 | } |
| 1612 | 1573 | ||
| 1613 | fn getString(di: Dwarf, offset: u64) ![:0]const u8 { | 1574 | fn getString(di: Dwarf, offset: u64) ![:0]const u8 { |
| ... | @@ -1619,28 +1580,28 @@ fn getLineString(di: Dwarf, offset: u64) ![:0]const u8 { | ... | @@ -1619,28 +1580,28 @@ fn getLineString(di: Dwarf, offset: u64) ![:0]const u8 { |
| 1619 | } | 1580 | } |
| 1620 | 1581 | ||
| 1621 | fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 { | 1582 | fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 { |
| 1622 | const debug_addr = di.section(.debug_addr) orelse return badDwarf(); | 1583 | const debug_addr = di.section(.debug_addr) orelse return bad(); |
| 1623 | 1584 | ||
| 1624 | // addr_base points to the first item after the header, however we | 1585 | // addr_base points to the first item after the header, however we |
| 1625 | // need to read the header to know the size of each item. Empirically, | 1586 | // need to read the header to know the size of each item. Empirically, |
| 1626 | // it may disagree with is_64 on the compile unit. | 1587 | // it may disagree with is_64 on the compile unit. |
| 1627 | // The header is 8 or 12 bytes depending on is_64. | 1588 | // The header is 8 or 12 bytes depending on is_64. |
| 1628 | if (compile_unit.addr_base < 8) return badDwarf(); | 1589 | if (compile_unit.addr_base < 8) return bad(); |
| 1629 | 1590 | ||
| 1630 | const version = readInt(u16, debug_addr[compile_unit.addr_base - 4 ..][0..2], di.endian); | 1591 | const version = readInt(u16, debug_addr[compile_unit.addr_base - 4 ..][0..2], di.endian); |
| 1631 | if (version != 5) return badDwarf(); | 1592 | if (version != 5) return bad(); |
| 1632 | 1593 | ||
| 1633 | const addr_size = debug_addr[compile_unit.addr_base - 2]; | 1594 | const addr_size = debug_addr[compile_unit.addr_base - 2]; |
| 1634 | const seg_size = debug_addr[compile_unit.addr_base - 1]; | 1595 | const seg_size = debug_addr[compile_unit.addr_base - 1]; |
| 1635 | 1596 | ||
| 1636 | const byte_offset = @as(usize, @intCast(compile_unit.addr_base + (addr_size + seg_size) * index)); | 1597 | const byte_offset = @as(usize, @intCast(compile_unit.addr_base + (addr_size + seg_size) * index)); |
| 1637 | if (byte_offset + addr_size > debug_addr.len) return badDwarf(); | 1598 | if (byte_offset + addr_size > debug_addr.len) return bad(); |
| 1638 | return switch (addr_size) { | 1599 | return switch (addr_size) { |
| 1639 | 1 => debug_addr[byte_offset], | 1600 | 1 => debug_addr[byte_offset], |
| 1640 | 2 => readInt(u16, debug_addr[byte_offset..][0..2], di.endian), | 1601 | 2 => readInt(u16, debug_addr[byte_offset..][0..2], di.endian), |
| 1641 | 4 => readInt(u32, debug_addr[byte_offset..][0..4], di.endian), | 1602 | 4 => readInt(u32, debug_addr[byte_offset..][0..4], di.endian), |
| 1642 | 8 => readInt(u64, debug_addr[byte_offset..][0..8], di.endian), | 1603 | 8 => readInt(u64, debug_addr[byte_offset..][0..8], di.endian), |
| 1643 | else => badDwarf(), | 1604 | else => bad(), |
| 1644 | }; | 1605 | }; |
| 1645 | } | 1606 | } |
| 1646 | 1607 | ||
| ... | @@ -1650,7 +1611,7 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 { | ... | @@ -1650,7 +1611,7 @@ fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 { |
| 1650 | /// of FDEs is built for binary searching during unwinding. | 1611 | /// of FDEs is built for binary searching during unwinding. |
| 1651 | pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void { | 1612 | pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void { |
| 1652 | if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: { | 1613 | if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: { |
| 1653 | var fbr: FixedBufferReader = .{ .buf = eh_frame_hdr, .endian = native_endian }; | 1614 | var fbr: DeprecatedFixedBufferReader = .{ .buf = eh_frame_hdr, .endian = native_endian }; |
| 1654 | 1615 | ||
| 1655 | const version = try fbr.readByte(); | 1616 | const version = try fbr.readByte(); |
| 1656 | if (version != 1) break :blk; | 1617 | if (version != 1) break :blk; |
| ... | @@ -1665,16 +1626,16 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) | ... | @@ -1665,16 +1626,16 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) |
| 1665 | const eh_frame_ptr = cast(usize, try readEhPointer(&fbr, eh_frame_ptr_enc, @sizeOf(usize), .{ | 1626 | const eh_frame_ptr = cast(usize, try readEhPointer(&fbr, eh_frame_ptr_enc, @sizeOf(usize), .{ |
| 1666 | .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.pos]), | 1627 | .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.pos]), |
| 1667 | .follow_indirect = true, | 1628 | .follow_indirect = true, |
| 1668 | }) orelse return badDwarf()) orelse return badDwarf(); | 1629 | }) orelse return bad()) orelse return bad(); |
| 1669 | 1630 | ||
| 1670 | const fde_count = cast(usize, try readEhPointer(&fbr, fde_count_enc, @sizeOf(usize), .{ | 1631 | const fde_count = cast(usize, try readEhPointer(&fbr, fde_count_enc, @sizeOf(usize), .{ |
| 1671 | .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.pos]), | 1632 | .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.pos]), |
| 1672 | .follow_indirect = true, | 1633 | .follow_indirect = true, |
| 1673 | }) orelse return badDwarf()) orelse return badDwarf(); | 1634 | }) orelse return bad()) orelse return bad(); |
| 1674 | 1635 | ||
| 1675 | const entry_size = try ExceptionFrameHeader.entrySize(table_enc); | 1636 | const entry_size = try ExceptionFrameHeader.entrySize(table_enc); |
| 1676 | const entries_len = fde_count * entry_size; | 1637 | const entries_len = fde_count * entry_size; |
| 1677 | if (entries_len > eh_frame_hdr.len - fbr.pos) return badDwarf(); | 1638 | if (entries_len > eh_frame_hdr.len - fbr.pos) return bad(); |
| 1678 | 1639 | ||
| 1679 | di.eh_frame_hdr = .{ | 1640 | di.eh_frame_hdr = .{ |
| 1680 | .eh_frame_ptr = eh_frame_ptr, | 1641 | .eh_frame_ptr = eh_frame_ptr, |
| ... | @@ -1690,7 +1651,7 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) | ... | @@ -1690,7 +1651,7 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) |
| 1690 | const frame_sections = [2]Section.Id{ .eh_frame, .debug_frame }; | 1651 | const frame_sections = [2]Section.Id{ .eh_frame, .debug_frame }; |
| 1691 | for (frame_sections) |frame_section| { | 1652 | for (frame_sections) |frame_section| { |
| 1692 | if (di.section(frame_section)) |section_data| { | 1653 | if (di.section(frame_section)) |section_data| { |
| 1693 | var fbr: FixedBufferReader = .{ .buf = section_data, .endian = di.endian }; | 1654 | var fbr: DeprecatedFixedBufferReader = .{ .buf = section_data, .endian = di.endian }; |
| 1694 | while (fbr.pos < fbr.buf.len) { | 1655 | while (fbr.pos < fbr.buf.len) { |
| 1695 | const entry_header = try EntryHeader.read(&fbr, null, frame_section); | 1656 | const entry_header = try EntryHeader.read(&fbr, null, frame_section); |
| 1696 | switch (entry_header.type) { | 1657 | switch (entry_header.type) { |
| ... | @@ -1708,7 +1669,7 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) | ... | @@ -1708,7 +1669,7 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) |
| 1708 | try di.cie_map.put(allocator, entry_header.length_offset, cie); | 1669 | try di.cie_map.put(allocator, entry_header.length_offset, cie); |
| 1709 | }, | 1670 | }, |
| 1710 | .fde => |cie_offset| { | 1671 | .fde => |cie_offset| { |
| 1711 | const cie = di.cie_map.get(cie_offset) orelse return badDwarf(); | 1672 | const cie = di.cie_map.get(cie_offset) orelse return bad(); |
| 1712 | const fde = try FrameDescriptionEntry.parse( | 1673 | const fde = try FrameDescriptionEntry.parse( |
| 1713 | entry_header.entry_bytes, | 1674 | entry_header.entry_bytes, |
| 1714 | di.sectionVirtualOffset(frame_section, base_address).?, | 1675 | di.sectionVirtualOffset(frame_section, base_address).?, |
| ... | @@ -1733,205 +1694,8 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) | ... | @@ -1733,205 +1694,8 @@ pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) |
| 1733 | } | 1694 | } |
| 1734 | } | 1695 | } |
| 1735 | 1696 | ||
| 1736 | /// Unwind a stack frame using DWARF unwinding info, updating the register context. | ||
| 1737 | /// | ||
| 1738 | /// If `.eh_frame_hdr` is available, it will be used to binary search for the FDE. | ||
| 1739 | /// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. | ||
| 1740 | /// | ||
| 1741 | /// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info | ||
| 1742 | /// defers unwinding to DWARF. This is an offset into the `.eh_frame` section. | ||
| 1743 | pub fn unwindFrame(di: *const Dwarf, context: *UnwindContext, ma: *StackIterator.MemoryAccessor, explicit_fde_offset: ?usize) !usize { | ||
| 1744 | if (!comptime abi.supportsUnwinding(builtin.target)) return error.UnsupportedCpuArchitecture; | ||
| 1745 | if (context.pc == 0) return 0; | ||
| 1746 | |||
| 1747 | // Find the FDE and CIE | ||
| 1748 | var cie: CommonInformationEntry = undefined; | ||
| 1749 | var fde: FrameDescriptionEntry = undefined; | ||
| 1750 | |||
| 1751 | if (explicit_fde_offset) |fde_offset| { | ||
| 1752 | const dwarf_section: Section.Id = .eh_frame; | ||
| 1753 | const frame_section = di.section(dwarf_section) orelse return error.MissingFDE; | ||
| 1754 | if (fde_offset >= frame_section.len) return error.MissingFDE; | ||
| 1755 | |||
| 1756 | var fbr: FixedBufferReader = .{ | ||
| 1757 | .buf = frame_section, | ||
| 1758 | .pos = fde_offset, | ||
| 1759 | .endian = di.endian, | ||
| 1760 | }; | ||
| 1761 | |||
| 1762 | const fde_entry_header = try EntryHeader.read(&fbr, null, dwarf_section); | ||
| 1763 | if (fde_entry_header.type != .fde) return error.MissingFDE; | ||
| 1764 | |||
| 1765 | const cie_offset = fde_entry_header.type.fde; | ||
| 1766 | try fbr.seekTo(cie_offset); | ||
| 1767 | |||
| 1768 | fbr.endian = native_endian; | ||
| 1769 | const cie_entry_header = try EntryHeader.read(&fbr, null, dwarf_section); | ||
| 1770 | if (cie_entry_header.type != .cie) return badDwarf(); | ||
| 1771 | |||
| 1772 | cie = try CommonInformationEntry.parse( | ||
| 1773 | cie_entry_header.entry_bytes, | ||
| 1774 | 0, | ||
| 1775 | true, | ||
| 1776 | cie_entry_header.format, | ||
| 1777 | dwarf_section, | ||
| 1778 | cie_entry_header.length_offset, | ||
| 1779 | @sizeOf(usize), | ||
| 1780 | native_endian, | ||
| 1781 | ); | ||
| 1782 | |||
| 1783 | fde = try FrameDescriptionEntry.parse( | ||
| 1784 | fde_entry_header.entry_bytes, | ||
| 1785 | 0, | ||
| 1786 | true, | ||
| 1787 | cie, | ||
| 1788 | @sizeOf(usize), | ||
| 1789 | native_endian, | ||
| 1790 | ); | ||
| 1791 | } else if (di.eh_frame_hdr) |header| { | ||
| 1792 | const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else null; | ||
| 1793 | try header.findEntry( | ||
| 1794 | ma, | ||
| 1795 | eh_frame_len, | ||
| 1796 | @intFromPtr(di.section(.eh_frame_hdr).?.ptr), | ||
| 1797 | context.pc, | ||
| 1798 | &cie, | ||
| 1799 | &fde, | ||
| 1800 | ); | ||
| 1801 | } else { | ||
| 1802 | const index = std.sort.binarySearch(FrameDescriptionEntry, context.pc, di.fde_list.items, {}, struct { | ||
| 1803 | pub fn compareFn(_: void, pc: usize, mid_item: FrameDescriptionEntry) std.math.Order { | ||
| 1804 | if (pc < mid_item.pc_begin) return .lt; | ||
| 1805 | |||
| 1806 | const range_end = mid_item.pc_begin + mid_item.pc_range; | ||
| 1807 | if (pc < range_end) return .eq; | ||
| 1808 | |||
| 1809 | return .gt; | ||
| 1810 | } | ||
| 1811 | }.compareFn); | ||
| 1812 | |||
| 1813 | fde = if (index) |i| di.fde_list.items[i] else return error.MissingFDE; | ||
| 1814 | cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE; | ||
| 1815 | } | ||
| 1816 | |||
| 1817 | var expression_context: expression.Context = .{ | ||
| 1818 | .format = cie.format, | ||
| 1819 | .memory_accessor = ma, | ||
| 1820 | .compile_unit = di.findCompileUnit(fde.pc_begin) catch null, | ||
| 1821 | .thread_context = context.thread_context, | ||
| 1822 | .reg_context = context.reg_context, | ||
| 1823 | .cfa = context.cfa, | ||
| 1824 | }; | ||
| 1825 | |||
| 1826 | context.vm.reset(); | ||
| 1827 | context.reg_context.eh_frame = cie.version != 4; | ||
| 1828 | context.reg_context.is_macho = di.is_macho; | ||
| 1829 | |||
| 1830 | const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde); | ||
| 1831 | context.cfa = switch (row.cfa.rule) { | ||
| 1832 | .val_offset => |offset| blk: { | ||
| 1833 | const register = row.cfa.register orelse return error.InvalidCFARule; | ||
| 1834 | const value = readInt(usize, (try abi.regBytes(context.thread_context, register, context.reg_context))[0..@sizeOf(usize)], native_endian); | ||
| 1835 | break :blk try call_frame.applyOffset(value, offset); | ||
| 1836 | }, | ||
| 1837 | .expression => |expr| blk: { | ||
| 1838 | context.stack_machine.reset(); | ||
| 1839 | const value = try context.stack_machine.run( | ||
| 1840 | expr, | ||
| 1841 | context.allocator, | ||
| 1842 | expression_context, | ||
| 1843 | context.cfa, | ||
| 1844 | ); | ||
| 1845 | |||
| 1846 | if (value) |v| { | ||
| 1847 | if (v != .generic) return error.InvalidExpressionValue; | ||
| 1848 | break :blk v.generic; | ||
| 1849 | } else return error.NoExpressionValue; | ||
| 1850 | }, | ||
| 1851 | else => return error.InvalidCFARule, | ||
| 1852 | }; | ||
| 1853 | |||
| 1854 | if (ma.load(usize, context.cfa.?) == null) return error.InvalidCFA; | ||
| 1855 | expression_context.cfa = context.cfa; | ||
| 1856 | |||
| 1857 | // Buffering the modifications is done because copying the thread context is not portable, | ||
| 1858 | // some implementations (ie. darwin) use internal pointers to the mcontext. | ||
| 1859 | var arena = std.heap.ArenaAllocator.init(context.allocator); | ||
| 1860 | defer arena.deinit(); | ||
| 1861 | const update_allocator = arena.allocator(); | ||
| 1862 | |||
| 1863 | const RegisterUpdate = struct { | ||
| 1864 | // Backed by thread_context | ||
| 1865 | dest: []u8, | ||
| 1866 | // Backed by arena | ||
| 1867 | src: []const u8, | ||
| 1868 | prev: ?*@This(), | ||
| 1869 | }; | ||
| 1870 | |||
| 1871 | var update_tail: ?*RegisterUpdate = null; | ||
| 1872 | var has_return_address = true; | ||
| 1873 | for (context.vm.rowColumns(row)) |column| { | ||
| 1874 | if (column.register) |register| { | ||
| 1875 | if (register == cie.return_address_register) { | ||
| 1876 | has_return_address = column.rule != .undefined; | ||
| 1877 | } | ||
| 1878 | |||
| 1879 | const dest = try abi.regBytes(context.thread_context, register, context.reg_context); | ||
| 1880 | const src = try update_allocator.alloc(u8, dest.len); | ||
| 1881 | |||
| 1882 | const prev = update_tail; | ||
| 1883 | update_tail = try update_allocator.create(RegisterUpdate); | ||
| 1884 | update_tail.?.* = .{ | ||
| 1885 | .dest = dest, | ||
| 1886 | .src = src, | ||
| 1887 | .prev = prev, | ||
| 1888 | }; | ||
| 1889 | |||
| 1890 | try column.resolveValue( | ||
| 1891 | context, | ||
| 1892 | expression_context, | ||
| 1893 | ma, | ||
| 1894 | src, | ||
| 1895 | ); | ||
| 1896 | } | ||
| 1897 | } | ||
| 1898 | |||
| 1899 | // On all implemented architectures, the CFA is defined as being the previous frame's SP | ||
| 1900 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(context.reg_context), context.reg_context)).* = context.cfa.?; | ||
| 1901 | |||
| 1902 | while (update_tail) |tail| { | ||
| 1903 | @memcpy(tail.dest, tail.src); | ||
| 1904 | update_tail = tail.prev; | ||
| 1905 | } | ||
| 1906 | |||
| 1907 | if (has_return_address) { | ||
| 1908 | context.pc = abi.stripInstructionPtrAuthCode(readInt(usize, (try abi.regBytes( | ||
| 1909 | context.thread_context, | ||
| 1910 | cie.return_address_register, | ||
| 1911 | context.reg_context, | ||
| 1912 | ))[0..@sizeOf(usize)], native_endian)); | ||
| 1913 | } else { | ||
| 1914 | context.pc = 0; | ||
| 1915 | } | ||
| 1916 | |||
| 1917 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), context.reg_context)).* = context.pc; | ||
| 1918 | |||
| 1919 | // The call instruction will have pushed the address of the instruction that follows the call as the return address. | ||
| 1920 | // This next instruction may be past the end of the function if the caller was `noreturn` (ie. the last instruction in | ||
| 1921 | // the function was the call). If we were to look up an FDE entry using the return address directly, it could end up | ||
| 1922 | // either not finding an FDE at all, or using the next FDE in the program, producing incorrect results. To prevent this, | ||
| 1923 | // we subtract one so that the next lookup is guaranteed to land inside the | ||
| 1924 | // | ||
| 1925 | // The exception to this rule is signal frames, where we return execution would be returned to the instruction | ||
| 1926 | // that triggered the handler. | ||
| 1927 | const return_address = context.pc; | ||
| 1928 | if (context.pc > 0 and !cie.isSignalFrame()) context.pc -= 1; | ||
| 1929 | |||
| 1930 | return return_address; | ||
| 1931 | } | ||
| 1932 | |||
| 1933 | fn parseFormValue( | 1697 | fn parseFormValue( |
| 1934 | fbr: *FixedBufferReader, | 1698 | fbr: *DeprecatedFixedBufferReader, |
| 1935 | form_id: u64, | 1699 | form_id: u64, |
| 1936 | format: Format, | 1700 | format: Format, |
| 1937 | implicit_const: ?i64, | 1701 | implicit_const: ?i64, |
| ... | @@ -1990,12 +1754,12 @@ fn parseFormValue( | ... | @@ -1990,12 +1754,12 @@ fn parseFormValue( |
| 1990 | FORM.strx => .{ .strx = try fbr.readUleb128(usize) }, | 1754 | FORM.strx => .{ .strx = try fbr.readUleb128(usize) }, |
| 1991 | FORM.line_strp => .{ .line_strp = try fbr.readAddress(format) }, | 1755 | FORM.line_strp => .{ .line_strp = try fbr.readAddress(format) }, |
| 1992 | FORM.indirect => parseFormValue(fbr, try fbr.readUleb128(u64), format, implicit_const), | 1756 | FORM.indirect => parseFormValue(fbr, try fbr.readUleb128(u64), format, implicit_const), |
| 1993 | FORM.implicit_const => .{ .sdata = implicit_const orelse return badDwarf() }, | 1757 | FORM.implicit_const => .{ .sdata = implicit_const orelse return bad() }, |
| 1994 | FORM.loclistx => .{ .loclistx = try fbr.readUleb128(u64) }, | 1758 | FORM.loclistx => .{ .loclistx = try fbr.readUleb128(u64) }, |
| 1995 | FORM.rnglistx => .{ .rnglistx = try fbr.readUleb128(u64) }, | 1759 | FORM.rnglistx => .{ .rnglistx = try fbr.readUleb128(u64) }, |
| 1996 | else => { | 1760 | else => { |
| 1997 | //debug.print("unrecognized form id: {x}\n", .{form_id}); | 1761 | //debug.print("unrecognized form id: {x}\n", .{form_id}); |
| 1998 | return badDwarf(); | 1762 | return bad(); |
| 1999 | }, | 1763 | }, |
| 2000 | }; | 1764 | }; |
| 2001 | } | 1765 | } |
| ... | @@ -2084,27 +1848,27 @@ const LineNumberProgram = struct { | ... | @@ -2084,27 +1848,27 @@ const LineNumberProgram = struct { |
| 2084 | self: *LineNumberProgram, | 1848 | self: *LineNumberProgram, |
| 2085 | allocator: Allocator, | 1849 | allocator: Allocator, |
| 2086 | file_entries: []const FileEntry, | 1850 | file_entries: []const FileEntry, |
| 2087 | ) !?std.debug.LineInfo { | 1851 | ) !?std.debug.SourceLocation { |
| 2088 | if (self.prev_valid and | 1852 | if (self.prev_valid and |
| 2089 | self.target_address >= self.prev_address and | 1853 | self.target_address >= self.prev_address and |
| 2090 | self.target_address < self.address) | 1854 | self.target_address < self.address) |
| 2091 | { | 1855 | { |
| 2092 | const file_index = if (self.version >= 5) self.prev_file else i: { | 1856 | const file_index = if (self.version >= 5) self.prev_file else i: { |
| 2093 | if (self.prev_file == 0) return missingDwarf(); | 1857 | if (self.prev_file == 0) return missing(); |
| 2094 | break :i self.prev_file - 1; | 1858 | break :i self.prev_file - 1; |
| 2095 | }; | 1859 | }; |
| 2096 | 1860 | ||
| 2097 | if (file_index >= file_entries.len) return badDwarf(); | 1861 | if (file_index >= file_entries.len) return bad(); |
| 2098 | const file_entry = &file_entries[file_index]; | 1862 | const file_entry = &file_entries[file_index]; |
| 2099 | 1863 | ||
| 2100 | if (file_entry.dir_index >= self.include_dirs.len) return badDwarf(); | 1864 | if (file_entry.dir_index >= self.include_dirs.len) return bad(); |
| 2101 | const dir_name = self.include_dirs[file_entry.dir_index].path; | 1865 | const dir_name = self.include_dirs[file_entry.dir_index].path; |
| 2102 | 1866 | ||
| 2103 | const file_name = try std.fs.path.join(allocator, &[_][]const u8{ | 1867 | const file_name = try std.fs.path.join(allocator, &[_][]const u8{ |
| 2104 | dir_name, file_entry.path, | 1868 | dir_name, file_entry.path, |
| 2105 | }); | 1869 | }); |
| 2106 | 1870 | ||
| 2107 | return std.debug.LineInfo{ | 1871 | return std.debug.SourceLocation{ |
| 2108 | .line = if (self.prev_line >= 0) @as(u64, @intCast(self.prev_line)) else 0, | 1872 | .line = if (self.prev_line >= 0) @as(u64, @intCast(self.prev_line)) else 0, |
| 2109 | .column = self.prev_column, | 1873 | .column = self.prev_column, |
| 2110 | .file_name = file_name, | 1874 | .file_name = file_name, |
| ... | @@ -2128,14 +1892,14 @@ const UnitHeader = struct { | ... | @@ -2128,14 +1892,14 @@ const UnitHeader = struct { |
| 2128 | header_length: u4, | 1892 | header_length: u4, |
| 2129 | unit_length: u64, | 1893 | unit_length: u64, |
| 2130 | }; | 1894 | }; |
| 2131 | fn readUnitHeader(fbr: *FixedBufferReader, opt_ma: ?*StackIterator.MemoryAccessor) !UnitHeader { | 1895 | fn readUnitHeader(fbr: *DeprecatedFixedBufferReader, opt_ma: ?*MemoryAccessor) !UnitHeader { |
| 2132 | return switch (try if (opt_ma) |ma| fbr.readIntChecked(u32, ma) else fbr.readInt(u32)) { | 1896 | return switch (try if (opt_ma) |ma| fbr.readIntChecked(u32, ma) else fbr.readInt(u32)) { |
| 2133 | 0...0xfffffff0 - 1 => |unit_length| .{ | 1897 | 0...0xfffffff0 - 1 => |unit_length| .{ |
| 2134 | .format = .@"32", | 1898 | .format = .@"32", |
| 2135 | .header_length = 4, | 1899 | .header_length = 4, |
| 2136 | .unit_length = unit_length, | 1900 | .unit_length = unit_length, |
| 2137 | }, | 1901 | }, |
| 2138 | 0xfffffff0...0xffffffff - 1 => badDwarf(), | 1902 | 0xfffffff0...0xffffffff - 1 => bad(), |
| 2139 | 0xffffffff => .{ | 1903 | 0xffffffff => .{ |
| 2140 | .format = .@"64", | 1904 | .format = .@"64", |
| 2141 | .header_length = 12, | 1905 | .header_length = 12, |
| ... | @@ -2145,7 +1909,7 @@ fn readUnitHeader(fbr: *FixedBufferReader, opt_ma: ?*StackIterator.MemoryAccesso | ... | @@ -2145,7 +1909,7 @@ fn readUnitHeader(fbr: *FixedBufferReader, opt_ma: ?*StackIterator.MemoryAccesso |
| 2145 | } | 1909 | } |
| 2146 | 1910 | ||
| 2147 | /// Returns the DWARF register number for an x86_64 register number found in compact unwind info | 1911 | /// Returns the DWARF register number for an x86_64 register number found in compact unwind info |
| 2148 | fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 { | 1912 | pub fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 { |
| 2149 | return switch (unwind_reg_number) { | 1913 | return switch (unwind_reg_number) { |
| 2150 | 1 => 3, // RBX | 1914 | 1 => 3, // RBX |
| 2151 | 2 => 12, // R12 | 1915 | 2 => 12, // R12 |
| ... | @@ -2159,473 +1923,25 @@ fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 { | ... | @@ -2159,473 +1923,25 @@ fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 { |
| 2159 | 1923 | ||
| 2160 | /// This function is to make it handy to comment out the return and make it | 1924 | /// This function is to make it handy to comment out the return and make it |
| 2161 | /// into a crash when working on this file. | 1925 | /// into a crash when working on this file. |
| 2162 | fn badDwarf() error{InvalidDebugInfo} { | 1926 | pub fn bad() error{InvalidDebugInfo} { |
| 2163 | //if (true) @panic("badDwarf"); // can be handy to uncomment when working on this file | 1927 | //if (true) @panic("bad dwarf"); // can be handy to uncomment when working on this file |
| 2164 | return error.InvalidDebugInfo; | 1928 | return error.InvalidDebugInfo; |
| 2165 | } | 1929 | } |
| 2166 | 1930 | ||
| 2167 | fn missingDwarf() error{MissingDebugInfo} { | 1931 | fn missing() error{MissingDebugInfo} { |
| 2168 | //if (true) @panic("missingDwarf"); // can be handy to uncomment when working on this file | 1932 | //if (true) @panic("missing dwarf"); // can be handy to uncomment when working on this file |
| 2169 | return error.MissingDebugInfo; | 1933 | return error.MissingDebugInfo; |
| 2170 | } | 1934 | } |
| 2171 | 1935 | ||
| 2172 | fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 { | 1936 | fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 { |
| 2173 | const str = opt_str orelse return badDwarf(); | 1937 | const str = opt_str orelse return bad(); |
| 2174 | if (offset > str.len) return badDwarf(); | 1938 | if (offset > str.len) return bad(); |
| 2175 | const casted_offset = cast(usize, offset) orelse return badDwarf(); | 1939 | const casted_offset = cast(usize, offset) orelse return bad(); |
| 2176 | // Valid strings always have a terminating zero byte | 1940 | // Valid strings always have a terminating zero byte |
| 2177 | const last = std.mem.indexOfScalarPos(u8, str, casted_offset, 0) orelse return badDwarf(); | 1941 | const last = std.mem.indexOfScalarPos(u8, str, casted_offset, 0) orelse return bad(); |
| 2178 | return str[casted_offset..last :0]; | 1942 | return str[casted_offset..last :0]; |
| 2179 | } | 1943 | } |
| 2180 | 1944 | ||
| 2181 | // Reading debug info needs to be fast, even when compiled in debug mode, | ||
| 2182 | // so avoid using a `std.io.FixedBufferStream` which is too slow. | ||
| 2183 | pub const FixedBufferReader = struct { | ||
| 2184 | buf: []const u8, | ||
| 2185 | pos: usize = 0, | ||
| 2186 | endian: std.builtin.Endian, | ||
| 2187 | |||
| 2188 | pub const Error = error{ EndOfBuffer, Overflow, InvalidBuffer }; | ||
| 2189 | |||
| 2190 | fn seekTo(fbr: *FixedBufferReader, pos: u64) Error!void { | ||
| 2191 | if (pos > fbr.buf.len) return error.EndOfBuffer; | ||
| 2192 | fbr.pos = @intCast(pos); | ||
| 2193 | } | ||
| 2194 | |||
| 2195 | fn seekForward(fbr: *FixedBufferReader, amount: u64) Error!void { | ||
| 2196 | if (fbr.buf.len - fbr.pos < amount) return error.EndOfBuffer; | ||
| 2197 | fbr.pos += @intCast(amount); | ||
| 2198 | } | ||
| 2199 | |||
| 2200 | pub inline fn readByte(fbr: *FixedBufferReader) Error!u8 { | ||
| 2201 | if (fbr.pos >= fbr.buf.len) return error.EndOfBuffer; | ||
| 2202 | defer fbr.pos += 1; | ||
| 2203 | return fbr.buf[fbr.pos]; | ||
| 2204 | } | ||
| 2205 | |||
| 2206 | fn readByteSigned(fbr: *FixedBufferReader) Error!i8 { | ||
| 2207 | return @bitCast(try fbr.readByte()); | ||
| 2208 | } | ||
| 2209 | |||
| 2210 | fn readInt(fbr: *FixedBufferReader, comptime T: type) Error!T { | ||
| 2211 | const size = @divExact(@typeInfo(T).Int.bits, 8); | ||
| 2212 | if (fbr.buf.len - fbr.pos < size) return error.EndOfBuffer; | ||
| 2213 | defer fbr.pos += size; | ||
| 2214 | return std.mem.readInt(T, fbr.buf[fbr.pos..][0..size], fbr.endian); | ||
| 2215 | } | ||
| 2216 | |||
| 2217 | fn readIntChecked( | ||
| 2218 | fbr: *FixedBufferReader, | ||
| 2219 | comptime T: type, | ||
| 2220 | ma: *std.debug.StackIterator.MemoryAccessor, | ||
| 2221 | ) Error!T { | ||
| 2222 | if (ma.load(T, @intFromPtr(fbr.buf[fbr.pos..].ptr)) == null) | ||
| 2223 | return error.InvalidBuffer; | ||
| 2224 | |||
| 2225 | return fbr.readInt(T); | ||
| 2226 | } | ||
| 2227 | |||
| 2228 | fn readUleb128(fbr: *FixedBufferReader, comptime T: type) Error!T { | ||
| 2229 | return std.leb.readUleb128(T, fbr); | ||
| 2230 | } | ||
| 2231 | |||
| 2232 | fn readIleb128(fbr: *FixedBufferReader, comptime T: type) Error!T { | ||
| 2233 | return std.leb.readIleb128(T, fbr); | ||
| 2234 | } | ||
| 2235 | |||
| 2236 | fn readAddress(fbr: *FixedBufferReader, format: Format) Error!u64 { | ||
| 2237 | return switch (format) { | ||
| 2238 | .@"32" => try fbr.readInt(u32), | ||
| 2239 | .@"64" => try fbr.readInt(u64), | ||
| 2240 | }; | ||
| 2241 | } | ||
| 2242 | |||
| 2243 | fn readAddressChecked( | ||
| 2244 | fbr: *FixedBufferReader, | ||
| 2245 | format: Format, | ||
| 2246 | ma: *std.debug.StackIterator.MemoryAccessor, | ||
| 2247 | ) Error!u64 { | ||
| 2248 | return switch (format) { | ||
| 2249 | .@"32" => try fbr.readIntChecked(u32, ma), | ||
| 2250 | .@"64" => try fbr.readIntChecked(u64, ma), | ||
| 2251 | }; | ||
| 2252 | } | ||
| 2253 | |||
| 2254 | fn readBytes(fbr: *FixedBufferReader, len: usize) Error![]const u8 { | ||
| 2255 | if (fbr.buf.len - fbr.pos < len) return error.EndOfBuffer; | ||
| 2256 | defer fbr.pos += len; | ||
| 2257 | return fbr.buf[fbr.pos..][0..len]; | ||
| 2258 | } | ||
| 2259 | |||
| 2260 | fn readBytesTo(fbr: *FixedBufferReader, comptime sentinel: u8) Error![:sentinel]const u8 { | ||
| 2261 | const end = @call(.always_inline, std.mem.indexOfScalarPos, .{ | ||
| 2262 | u8, | ||
| 2263 | fbr.buf, | ||
| 2264 | fbr.pos, | ||
| 2265 | sentinel, | ||
| 2266 | }) orelse return error.EndOfBuffer; | ||
| 2267 | defer fbr.pos = end + 1; | ||
| 2268 | return fbr.buf[fbr.pos..end :sentinel]; | ||
| 2269 | } | ||
| 2270 | }; | ||
| 2271 | |||
| 2272 | /// Unwind a frame using MachO compact unwind info (from __unwind_info). | ||
| 2273 | /// If the compact encoding can't encode a way to unwind a frame, it will | ||
| 2274 | /// defer unwinding to DWARF, in which case `.eh_frame` will be used if available. | ||
| 2275 | pub fn unwindFrameMachO( | ||
| 2276 | context: *UnwindContext, | ||
| 2277 | ma: *StackIterator.MemoryAccessor, | ||
| 2278 | unwind_info: []const u8, | ||
| 2279 | eh_frame: ?[]const u8, | ||
| 2280 | module_base_address: usize, | ||
| 2281 | ) !usize { | ||
| 2282 | const macho = std.macho; | ||
| 2283 | |||
| 2284 | const header = std.mem.bytesAsValue( | ||
| 2285 | macho.unwind_info_section_header, | ||
| 2286 | unwind_info[0..@sizeOf(macho.unwind_info_section_header)], | ||
| 2287 | ); | ||
| 2288 | const indices = std.mem.bytesAsSlice( | ||
| 2289 | macho.unwind_info_section_header_index_entry, | ||
| 2290 | unwind_info[header.indexSectionOffset..][0 .. header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry)], | ||
| 2291 | ); | ||
| 2292 | if (indices.len == 0) return error.MissingUnwindInfo; | ||
| 2293 | |||
| 2294 | const mapped_pc = context.pc - module_base_address; | ||
| 2295 | const second_level_index = blk: { | ||
| 2296 | var left: usize = 0; | ||
| 2297 | var len: usize = indices.len; | ||
| 2298 | |||
| 2299 | while (len > 1) { | ||
| 2300 | const mid = left + len / 2; | ||
| 2301 | const offset = indices[mid].functionOffset; | ||
| 2302 | if (mapped_pc < offset) { | ||
| 2303 | len /= 2; | ||
| 2304 | } else { | ||
| 2305 | left = mid; | ||
| 2306 | if (mapped_pc == offset) break; | ||
| 2307 | len -= len / 2; | ||
| 2308 | } | ||
| 2309 | } | ||
| 2310 | |||
| 2311 | // Last index is a sentinel containing the highest address as its functionOffset | ||
| 2312 | if (indices[left].secondLevelPagesSectionOffset == 0) return error.MissingUnwindInfo; | ||
| 2313 | break :blk &indices[left]; | ||
| 2314 | }; | ||
| 2315 | |||
| 2316 | const common_encodings = std.mem.bytesAsSlice( | ||
| 2317 | macho.compact_unwind_encoding_t, | ||
| 2318 | unwind_info[header.commonEncodingsArraySectionOffset..][0 .. header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t)], | ||
| 2319 | ); | ||
| 2320 | |||
| 2321 | const start_offset = second_level_index.secondLevelPagesSectionOffset; | ||
| 2322 | const kind = std.mem.bytesAsValue( | ||
| 2323 | macho.UNWIND_SECOND_LEVEL, | ||
| 2324 | unwind_info[start_offset..][0..@sizeOf(macho.UNWIND_SECOND_LEVEL)], | ||
| 2325 | ); | ||
| 2326 | |||
| 2327 | const entry: struct { | ||
| 2328 | function_offset: usize, | ||
| 2329 | raw_encoding: u32, | ||
| 2330 | } = switch (kind.*) { | ||
| 2331 | .REGULAR => blk: { | ||
| 2332 | const page_header = std.mem.bytesAsValue( | ||
| 2333 | macho.unwind_info_regular_second_level_page_header, | ||
| 2334 | unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_regular_second_level_page_header)], | ||
| 2335 | ); | ||
| 2336 | |||
| 2337 | const entries = std.mem.bytesAsSlice( | ||
| 2338 | macho.unwind_info_regular_second_level_entry, | ||
| 2339 | unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry)], | ||
| 2340 | ); | ||
| 2341 | if (entries.len == 0) return error.InvalidUnwindInfo; | ||
| 2342 | |||
| 2343 | var left: usize = 0; | ||
| 2344 | var len: usize = entries.len; | ||
| 2345 | while (len > 1) { | ||
| 2346 | const mid = left + len / 2; | ||
| 2347 | const offset = entries[mid].functionOffset; | ||
| 2348 | if (mapped_pc < offset) { | ||
| 2349 | len /= 2; | ||
| 2350 | } else { | ||
| 2351 | left = mid; | ||
| 2352 | if (mapped_pc == offset) break; | ||
| 2353 | len -= len / 2; | ||
| 2354 | } | ||
| 2355 | } | ||
| 2356 | |||
| 2357 | break :blk .{ | ||
| 2358 | .function_offset = entries[left].functionOffset, | ||
| 2359 | .raw_encoding = entries[left].encoding, | ||
| 2360 | }; | ||
| 2361 | }, | ||
| 2362 | .COMPRESSED => blk: { | ||
| 2363 | const page_header = std.mem.bytesAsValue( | ||
| 2364 | macho.unwind_info_compressed_second_level_page_header, | ||
| 2365 | unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_compressed_second_level_page_header)], | ||
| 2366 | ); | ||
| 2367 | |||
| 2368 | const entries = std.mem.bytesAsSlice( | ||
| 2369 | macho.UnwindInfoCompressedEntry, | ||
| 2370 | unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry)], | ||
| 2371 | ); | ||
| 2372 | if (entries.len == 0) return error.InvalidUnwindInfo; | ||
| 2373 | |||
| 2374 | var left: usize = 0; | ||
| 2375 | var len: usize = entries.len; | ||
| 2376 | while (len > 1) { | ||
| 2377 | const mid = left + len / 2; | ||
| 2378 | const offset = second_level_index.functionOffset + entries[mid].funcOffset; | ||
| 2379 | if (mapped_pc < offset) { | ||
| 2380 | len /= 2; | ||
| 2381 | } else { | ||
| 2382 | left = mid; | ||
| 2383 | if (mapped_pc == offset) break; | ||
| 2384 | len -= len / 2; | ||
| 2385 | } | ||
| 2386 | } | ||
| 2387 | |||
| 2388 | const entry = entries[left]; | ||
| 2389 | const function_offset = second_level_index.functionOffset + entry.funcOffset; | ||
| 2390 | if (entry.encodingIndex < header.commonEncodingsArrayCount) { | ||
| 2391 | if (entry.encodingIndex >= common_encodings.len) return error.InvalidUnwindInfo; | ||
| 2392 | break :blk .{ | ||
| 2393 | .function_offset = function_offset, | ||
| 2394 | .raw_encoding = common_encodings[entry.encodingIndex], | ||
| 2395 | }; | ||
| 2396 | } else { | ||
| 2397 | const local_index = try std.math.sub( | ||
| 2398 | u8, | ||
| 2399 | entry.encodingIndex, | ||
| 2400 | cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo, | ||
| 2401 | ); | ||
| 2402 | const local_encodings = std.mem.bytesAsSlice( | ||
| 2403 | macho.compact_unwind_encoding_t, | ||
| 2404 | unwind_info[start_offset + page_header.encodingsPageOffset ..][0 .. page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t)], | ||
| 2405 | ); | ||
| 2406 | if (local_index >= local_encodings.len) return error.InvalidUnwindInfo; | ||
| 2407 | break :blk .{ | ||
| 2408 | .function_offset = function_offset, | ||
| 2409 | .raw_encoding = local_encodings[local_index], | ||
| 2410 | }; | ||
| 2411 | } | ||
| 2412 | }, | ||
| 2413 | else => return error.InvalidUnwindInfo, | ||
| 2414 | }; | ||
| 2415 | |||
| 2416 | if (entry.raw_encoding == 0) return error.NoUnwindInfo; | ||
| 2417 | const reg_context = abi.RegisterContext{ | ||
| 2418 | .eh_frame = false, | ||
| 2419 | .is_macho = true, | ||
| 2420 | }; | ||
| 2421 | |||
| 2422 | const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding); | ||
| 2423 | const new_ip = switch (builtin.cpu.arch) { | ||
| 2424 | .x86_64 => switch (encoding.mode.x86_64) { | ||
| 2425 | .OLD => return error.UnimplementedUnwindEncoding, | ||
| 2426 | .RBP_FRAME => blk: { | ||
| 2427 | const regs: [5]u3 = .{ | ||
| 2428 | encoding.value.x86_64.frame.reg0, | ||
| 2429 | encoding.value.x86_64.frame.reg1, | ||
| 2430 | encoding.value.x86_64.frame.reg2, | ||
| 2431 | encoding.value.x86_64.frame.reg3, | ||
| 2432 | encoding.value.x86_64.frame.reg4, | ||
| 2433 | }; | ||
| 2434 | |||
| 2435 | const frame_offset = encoding.value.x86_64.frame.frame_offset * @sizeOf(usize); | ||
| 2436 | var max_reg: usize = 0; | ||
| 2437 | inline for (regs, 0..) |reg, i| { | ||
| 2438 | if (reg > 0) max_reg = i; | ||
| 2439 | } | ||
| 2440 | |||
| 2441 | const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*; | ||
| 2442 | const new_sp = fp + 2 * @sizeOf(usize); | ||
| 2443 | |||
| 2444 | // Verify the stack range we're about to read register values from | ||
| 2445 | if (ma.load(usize, new_sp) == null or ma.load(usize, fp - frame_offset + max_reg * @sizeOf(usize)) == null) return error.InvalidUnwindInfo; | ||
| 2446 | |||
| 2447 | const ip_ptr = fp + @sizeOf(usize); | ||
| 2448 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | ||
| 2449 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | ||
| 2450 | |||
| 2451 | (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp; | ||
| 2452 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; | ||
| 2453 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; | ||
| 2454 | |||
| 2455 | for (regs, 0..) |reg, i| { | ||
| 2456 | if (reg == 0) continue; | ||
| 2457 | const addr = fp - frame_offset + i * @sizeOf(usize); | ||
| 2458 | const reg_number = try compactUnwindToDwarfRegNumber(reg); | ||
| 2459 | (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*; | ||
| 2460 | } | ||
| 2461 | |||
| 2462 | break :blk new_ip; | ||
| 2463 | }, | ||
| 2464 | .STACK_IMMD, | ||
| 2465 | .STACK_IND, | ||
| 2466 | => blk: { | ||
| 2467 | const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*; | ||
| 2468 | const stack_size = if (encoding.mode.x86_64 == .STACK_IMMD) | ||
| 2469 | @as(usize, encoding.value.x86_64.frameless.stack.direct.stack_size) * @sizeOf(usize) | ||
| 2470 | else stack_size: { | ||
| 2471 | // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function. | ||
| 2472 | const sub_offset_addr = | ||
| 2473 | module_base_address + | ||
| 2474 | entry.function_offset + | ||
| 2475 | encoding.value.x86_64.frameless.stack.indirect.sub_offset; | ||
| 2476 | if (ma.load(usize, sub_offset_addr) == null) return error.InvalidUnwindInfo; | ||
| 2477 | |||
| 2478 | // `sub_offset_addr` points to the offset of the literal within the instruction | ||
| 2479 | const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*; | ||
| 2480 | break :stack_size sub_operand + @sizeOf(usize) * @as(usize, encoding.value.x86_64.frameless.stack.indirect.stack_adjust); | ||
| 2481 | }; | ||
| 2482 | |||
| 2483 | // Decode the Lehmer-coded sequence of registers. | ||
| 2484 | // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h | ||
| 2485 | |||
| 2486 | // Decode the variable-based permutation number into its digits. Each digit represents | ||
| 2487 | // an index into the list of register numbers that weren't yet used in the sequence at | ||
| 2488 | // the time the digit was added. | ||
| 2489 | const reg_count = encoding.value.x86_64.frameless.stack_reg_count; | ||
| 2490 | const ip_ptr = if (reg_count > 0) reg_blk: { | ||
| 2491 | var digits: [6]u3 = undefined; | ||
| 2492 | var accumulator: usize = encoding.value.x86_64.frameless.stack_reg_permutation; | ||
| 2493 | var base: usize = 2; | ||
| 2494 | for (0..reg_count) |i| { | ||
| 2495 | const div = accumulator / base; | ||
| 2496 | digits[digits.len - 1 - i] = @intCast(accumulator - base * div); | ||
| 2497 | accumulator = div; | ||
| 2498 | base += 1; | ||
| 2499 | } | ||
| 2500 | |||
| 2501 | const reg_numbers = [_]u3{ 1, 2, 3, 4, 5, 6 }; | ||
| 2502 | var registers: [reg_numbers.len]u3 = undefined; | ||
| 2503 | var used_indices = [_]bool{false} ** reg_numbers.len; | ||
| 2504 | for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| { | ||
| 2505 | var unused_count: u8 = 0; | ||
| 2506 | const unused_index = for (used_indices, 0..) |used, index| { | ||
| 2507 | if (!used) { | ||
| 2508 | if (target_unused_index == unused_count) break index; | ||
| 2509 | unused_count += 1; | ||
| 2510 | } | ||
| 2511 | } else unreachable; | ||
| 2512 | |||
| 2513 | registers[i] = reg_numbers[unused_index]; | ||
| 2514 | used_indices[unused_index] = true; | ||
| 2515 | } | ||
| 2516 | |||
| 2517 | var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1); | ||
| 2518 | if (ma.load(usize, reg_addr) == null) return error.InvalidUnwindInfo; | ||
| 2519 | for (0..reg_count) |i| { | ||
| 2520 | const reg_number = try compactUnwindToDwarfRegNumber(registers[i]); | ||
| 2521 | (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | ||
| 2522 | reg_addr += @sizeOf(usize); | ||
| 2523 | } | ||
| 2524 | |||
| 2525 | break :reg_blk reg_addr; | ||
| 2526 | } else sp + stack_size - @sizeOf(usize); | ||
| 2527 | |||
| 2528 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | ||
| 2529 | const new_sp = ip_ptr + @sizeOf(usize); | ||
| 2530 | if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo; | ||
| 2531 | |||
| 2532 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; | ||
| 2533 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; | ||
| 2534 | |||
| 2535 | break :blk new_ip; | ||
| 2536 | }, | ||
| 2537 | .DWARF => { | ||
| 2538 | return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf)); | ||
| 2539 | }, | ||
| 2540 | }, | ||
| 2541 | .aarch64 => switch (encoding.mode.arm64) { | ||
| 2542 | .OLD => return error.UnimplementedUnwindEncoding, | ||
| 2543 | .FRAMELESS => blk: { | ||
| 2544 | const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*; | ||
| 2545 | const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16; | ||
| 2546 | const new_ip = (try abi.regValueNative(usize, context.thread_context, 30, reg_context)).*; | ||
| 2547 | if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo; | ||
| 2548 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; | ||
| 2549 | break :blk new_ip; | ||
| 2550 | }, | ||
| 2551 | .DWARF => { | ||
| 2552 | return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf)); | ||
| 2553 | }, | ||
| 2554 | .FRAME => blk: { | ||
| 2555 | const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*; | ||
| 2556 | const new_sp = fp + 16; | ||
| 2557 | const ip_ptr = fp + @sizeOf(usize); | ||
| 2558 | |||
| 2559 | const num_restored_pairs: usize = | ||
| 2560 | @popCount(@as(u5, @bitCast(encoding.value.arm64.frame.x_reg_pairs))) + | ||
| 2561 | @popCount(@as(u4, @bitCast(encoding.value.arm64.frame.d_reg_pairs))); | ||
| 2562 | const min_reg_addr = fp - num_restored_pairs * 2 * @sizeOf(usize); | ||
| 2563 | |||
| 2564 | if (ma.load(usize, new_sp) == null or ma.load(usize, min_reg_addr) == null) return error.InvalidUnwindInfo; | ||
| 2565 | |||
| 2566 | var reg_addr = fp - @sizeOf(usize); | ||
| 2567 | inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.x_reg_pairs)).Struct.fields, 0..) |field, i| { | ||
| 2568 | if (@field(encoding.value.arm64.frame.x_reg_pairs, field.name) != 0) { | ||
| 2569 | (try abi.regValueNative(usize, context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | ||
| 2570 | reg_addr += @sizeOf(usize); | ||
| 2571 | (try abi.regValueNative(usize, context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | ||
| 2572 | reg_addr += @sizeOf(usize); | ||
| 2573 | } | ||
| 2574 | } | ||
| 2575 | |||
| 2576 | inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.d_reg_pairs)).Struct.fields, 0..) |field, i| { | ||
| 2577 | if (@field(encoding.value.arm64.frame.d_reg_pairs, field.name) != 0) { | ||
| 2578 | // Only the lower half of the 128-bit V registers are restored during unwinding | ||
| 2579 | @memcpy( | ||
| 2580 | try abi.regBytes(context.thread_context, 64 + 8 + i, context.reg_context), | ||
| 2581 | std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), | ||
| 2582 | ); | ||
| 2583 | reg_addr += @sizeOf(usize); | ||
| 2584 | @memcpy( | ||
| 2585 | try abi.regBytes(context.thread_context, 64 + 9 + i, context.reg_context), | ||
| 2586 | std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), | ||
| 2587 | ); | ||
| 2588 | reg_addr += @sizeOf(usize); | ||
| 2589 | } | ||
| 2590 | } | ||
| 2591 | |||
| 2592 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | ||
| 2593 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | ||
| 2594 | |||
| 2595 | (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp; | ||
| 2596 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; | ||
| 2597 | |||
| 2598 | break :blk new_ip; | ||
| 2599 | }, | ||
| 2600 | }, | ||
| 2601 | else => return error.UnimplementedArch, | ||
| 2602 | }; | ||
| 2603 | |||
| 2604 | context.pc = abi.stripInstructionPtrAuthCode(new_ip); | ||
| 2605 | if (context.pc > 0) context.pc -= 1; | ||
| 2606 | return new_ip; | ||
| 2607 | } | ||
| 2608 | |||
| 2609 | fn unwindFrameMachODwarf( | ||
| 2610 | context: *UnwindContext, | ||
| 2611 | ma: *std.debug.StackIterator.MemoryAccessor, | ||
| 2612 | eh_frame: []const u8, | ||
| 2613 | fde_offset: usize, | ||
| 2614 | ) !usize { | ||
| 2615 | var di = Dwarf{ | ||
| 2616 | .endian = native_endian, | ||
| 2617 | .is_macho = true, | ||
| 2618 | }; | ||
| 2619 | defer di.deinit(context.allocator); | ||
| 2620 | |||
| 2621 | di.sections[@intFromEnum(Section.Id.eh_frame)] = .{ | ||
| 2622 | .data = eh_frame, | ||
| 2623 | .owned = false, | ||
| 2624 | }; | ||
| 2625 | |||
| 2626 | return di.unwindFrame(context, ma, fde_offset); | ||
| 2627 | } | ||
| 2628 | |||
| 2629 | const EhPointerContext = struct { | 1945 | const EhPointerContext = struct { |
| 2630 | // The address of the pointer field itself | 1946 | // The address of the pointer field itself |
| 2631 | pc_rel_base: u64, | 1947 | pc_rel_base: u64, |
| ... | @@ -2641,7 +1957,7 @@ const EhPointerContext = struct { | ... | @@ -2641,7 +1957,7 @@ const EhPointerContext = struct { |
| 2641 | text_rel_base: ?u64 = null, | 1957 | text_rel_base: ?u64 = null, |
| 2642 | function_rel_base: ?u64 = null, | 1958 | function_rel_base: ?u64 = null, |
| 2643 | }; | 1959 | }; |
| 2644 | fn readEhPointer(fbr: *FixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext) !?u64 { | 1960 | fn readEhPointer(fbr: *DeprecatedFixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext) !?u64 { |
| 2645 | if (enc == EH.PE.omit) return null; | 1961 | if (enc == EH.PE.omit) return null; |
| 2646 | 1962 | ||
| 2647 | const value: union(enum) { | 1963 | const value: union(enum) { |
| ... | @@ -2664,7 +1980,7 @@ fn readEhPointer(fbr: *FixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhP | ... | @@ -2664,7 +1980,7 @@ fn readEhPointer(fbr: *FixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhP |
| 2664 | EH.PE.sdata2 => .{ .signed = try fbr.readInt(i16) }, | 1980 | EH.PE.sdata2 => .{ .signed = try fbr.readInt(i16) }, |
| 2665 | EH.PE.sdata4 => .{ .signed = try fbr.readInt(i32) }, | 1981 | EH.PE.sdata4 => .{ .signed = try fbr.readInt(i32) }, |
| 2666 | EH.PE.sdata8 => .{ .signed = try fbr.readInt(i64) }, | 1982 | EH.PE.sdata8 => .{ .signed = try fbr.readInt(i64) }, |
| 2667 | else => return badDwarf(), | 1983 | else => return bad(), |
| 2668 | }; | 1984 | }; |
| 2669 | 1985 | ||
| 2670 | const base = switch (enc & EH.PE.rel_mask) { | 1986 | const base = switch (enc & EH.PE.rel_mask) { |
lib/std/debug/Dwarf/abi.zig+55-112| ... | @@ -1,44 +1,50 @@ | ... | @@ -1,44 +1,50 @@ |
| 1 | const builtin = @import("builtin"); | 1 | const builtin = @import("builtin"); |
| 2 | |||
| 2 | const std = @import("../../std.zig"); | 3 | const std = @import("../../std.zig"); |
| 3 | const mem = std.mem; | 4 | const mem = std.mem; |
| 4 | const native_os = builtin.os.tag; | ||
| 5 | const posix = std.posix; | 5 | const posix = std.posix; |
| 6 | const Arch = std.Target.Cpu.Arch; | ||
| 6 | 7 | ||
| 8 | /// Tells whether unwinding for this target is supported by the Dwarf standard. | ||
| 9 | /// | ||
| 10 | /// See also `std.debug.SelfInfo.supportsUnwinding` which tells whether the Zig | ||
| 11 | /// standard library has a working implementation of unwinding for this target. | ||
| 7 | pub fn supportsUnwinding(target: std.Target) bool { | 12 | pub fn supportsUnwinding(target: std.Target) bool { |
| 8 | return switch (target.cpu.arch) { | 13 | return switch (target.cpu.arch) { |
| 9 | .x86 => switch (target.os.tag) { | 14 | .amdgcn, |
| 10 | .linux, .netbsd, .solaris, .illumos => true, | 15 | .nvptx, |
| 11 | else => false, | 16 | .nvptx64, |
| 12 | }, | 17 | .spirv, |
| 13 | .x86_64 => switch (target.os.tag) { | 18 | .spirv32, |
| 14 | .linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true, | 19 | .spirv64, |
| 15 | else => false, | 20 | .spu_2, |
| 16 | }, | 21 | => false, |
| 17 | .arm => switch (target.os.tag) { | 22 | |
| 18 | .linux => true, | 23 | // Enabling this causes relocation errors such as: |
| 19 | else => false, | 24 | // error: invalid relocation type R_RISCV_SUB32 at offset 0x20 |
| 20 | }, | 25 | .riscv64, .riscv32 => false, |
| 21 | .aarch64 => switch (target.os.tag) { | 26 | |
| 22 | .linux, .netbsd, .freebsd, .macos, .ios => true, | 27 | // Conservative guess. Feel free to update this logic with any targets |
| 23 | else => false, | 28 | // that are known to not support Dwarf unwinding. |
| 24 | }, | 29 | else => true, |
| 25 | else => false, | ||
| 26 | }; | 30 | }; |
| 27 | } | 31 | } |
| 28 | 32 | ||
| 29 | pub fn ipRegNum() u8 { | 33 | /// Returns `null` for CPU architectures without an instruction pointer register. |
| 30 | return switch (builtin.cpu.arch) { | 34 | pub fn ipRegNum(arch: Arch) ?u8 { |
| 35 | return switch (arch) { | ||
| 31 | .x86 => 8, | 36 | .x86 => 8, |
| 32 | .x86_64 => 16, | 37 | .x86_64 => 16, |
| 33 | .arm => 15, | 38 | .arm => 15, |
| 34 | .aarch64 => 32, | 39 | .aarch64 => 32, |
| 35 | else => unreachable, | 40 | else => null, |
| 36 | }; | 41 | }; |
| 37 | } | 42 | } |
| 38 | 43 | ||
| 39 | pub fn fpRegNum(reg_context: RegisterContext) u8 { | 44 | pub fn fpRegNum(arch: Arch, reg_context: RegisterContext) u8 { |
| 40 | return switch (builtin.cpu.arch) { | 45 | return switch (arch) { |
| 41 | // GCC on OS X historically did the opposite of ELF for these registers (only in .eh_frame), and that is now the convention for MachO | 46 | // GCC on OS X historically did the opposite of ELF for these registers |
| 47 | // (only in .eh_frame), and that is now the convention for MachO | ||
| 42 | .x86 => if (reg_context.eh_frame and reg_context.is_macho) 4 else 5, | 48 | .x86 => if (reg_context.eh_frame and reg_context.is_macho) 4 else 5, |
| 43 | .x86_64 => 6, | 49 | .x86_64 => 6, |
| 44 | .arm => 11, | 50 | .arm => 11, |
| ... | @@ -47,8 +53,8 @@ pub fn fpRegNum(reg_context: RegisterContext) u8 { | ... | @@ -47,8 +53,8 @@ pub fn fpRegNum(reg_context: RegisterContext) u8 { |
| 47 | }; | 53 | }; |
| 48 | } | 54 | } |
| 49 | 55 | ||
| 50 | pub fn spRegNum(reg_context: RegisterContext) u8 { | 56 | pub fn spRegNum(arch: Arch, reg_context: RegisterContext) u8 { |
| 51 | return switch (builtin.cpu.arch) { | 57 | return switch (arch) { |
| 52 | .x86 => if (reg_context.eh_frame and reg_context.is_macho) 5 else 4, | 58 | .x86 => if (reg_context.eh_frame and reg_context.is_macho) 5 else 4, |
| 53 | .x86_64 => 7, | 59 | .x86_64 => 7, |
| 54 | .arm => 13, | 60 | .arm => 13, |
| ... | @@ -57,33 +63,12 @@ pub fn spRegNum(reg_context: RegisterContext) u8 { | ... | @@ -57,33 +63,12 @@ pub fn spRegNum(reg_context: RegisterContext) u8 { |
| 57 | }; | 63 | }; |
| 58 | } | 64 | } |
| 59 | 65 | ||
| 60 | /// Some platforms use pointer authentication - the upper bits of instruction pointers contain a signature. | ||
| 61 | /// This function clears these signature bits to make the pointer usable. | ||
| 62 | pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize { | ||
| 63 | if (builtin.cpu.arch == .aarch64) { | ||
| 64 | // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it) | ||
| 65 | // The save / restore is because `xpaclri` operates on x30 (LR) | ||
| 66 | return asm ( | ||
| 67 | \\mov x16, x30 | ||
| 68 | \\mov x30, x15 | ||
| 69 | \\hint 0x07 | ||
| 70 | \\mov x15, x30 | ||
| 71 | \\mov x30, x16 | ||
| 72 | : [ret] "={x15}" (-> usize), | ||
| 73 | : [ptr] "{x15}" (ptr), | ||
| 74 | : "x16" | ||
| 75 | ); | ||
| 76 | } | ||
| 77 | |||
| 78 | return ptr; | ||
| 79 | } | ||
| 80 | |||
| 81 | pub const RegisterContext = struct { | 66 | pub const RegisterContext = struct { |
| 82 | eh_frame: bool, | 67 | eh_frame: bool, |
| 83 | is_macho: bool, | 68 | is_macho: bool, |
| 84 | }; | 69 | }; |
| 85 | 70 | ||
| 86 | pub const AbiError = error{ | 71 | pub const RegBytesError = error{ |
| 87 | InvalidRegister, | 72 | InvalidRegister, |
| 88 | UnimplementedArch, | 73 | UnimplementedArch, |
| 89 | UnimplementedOs, | 74 | UnimplementedOs, |
| ... | @@ -91,55 +76,21 @@ pub const AbiError = error{ | ... | @@ -91,55 +76,21 @@ pub const AbiError = error{ |
| 91 | ThreadContextNotSupported, | 76 | ThreadContextNotSupported, |
| 92 | }; | 77 | }; |
| 93 | 78 | ||
| 94 | fn RegValueReturnType(comptime ContextPtrType: type, comptime T: type) type { | ||
| 95 | const reg_bytes_type = comptime RegBytesReturnType(ContextPtrType); | ||
| 96 | const info = @typeInfo(reg_bytes_type).Pointer; | ||
| 97 | return @Type(.{ | ||
| 98 | .Pointer = .{ | ||
| 99 | .size = .One, | ||
| 100 | .is_const = info.is_const, | ||
| 101 | .is_volatile = info.is_volatile, | ||
| 102 | .is_allowzero = info.is_allowzero, | ||
| 103 | .alignment = info.alignment, | ||
| 104 | .address_space = info.address_space, | ||
| 105 | .child = T, | ||
| 106 | .sentinel = null, | ||
| 107 | }, | ||
| 108 | }); | ||
| 109 | } | ||
| 110 | |||
| 111 | /// Returns a pointer to a register stored in a ThreadContext, preserving the pointer attributes of the context. | ||
| 112 | pub fn regValueNative( | ||
| 113 | comptime T: type, | ||
| 114 | thread_context_ptr: anytype, | ||
| 115 | reg_number: u8, | ||
| 116 | reg_context: ?RegisterContext, | ||
| 117 | ) !RegValueReturnType(@TypeOf(thread_context_ptr), T) { | ||
| 118 | const reg_bytes = try regBytes(thread_context_ptr, reg_number, reg_context); | ||
| 119 | if (@sizeOf(T) != reg_bytes.len) return error.IncompatibleRegisterSize; | ||
| 120 | return mem.bytesAsValue(T, reg_bytes[0..@sizeOf(T)]); | ||
| 121 | } | ||
| 122 | |||
| 123 | fn RegBytesReturnType(comptime ContextPtrType: type) type { | ||
| 124 | const info = @typeInfo(ContextPtrType); | ||
| 125 | if (info != .Pointer or info.Pointer.child != std.debug.ThreadContext) { | ||
| 126 | @compileError("Expected a pointer to std.debug.ThreadContext, got " ++ @typeName(@TypeOf(ContextPtrType))); | ||
| 127 | } | ||
| 128 | |||
| 129 | return if (info.Pointer.is_const) return []const u8 else []u8; | ||
| 130 | } | ||
| 131 | |||
| 132 | /// Returns a slice containing the backing storage for `reg_number`. | 79 | /// Returns a slice containing the backing storage for `reg_number`. |
| 133 | /// | 80 | /// |
| 81 | /// This function assumes the Dwarf information corresponds not necessarily to | ||
| 82 | /// the current executable, but at least with a matching CPU architecture and | ||
| 83 | /// OS. It is planned to lift this limitation with a future enhancement. | ||
| 84 | /// | ||
| 134 | /// `reg_context` describes in what context the register number is used, as it can have different | 85 | /// `reg_context` describes in what context the register number is used, as it can have different |
| 135 | /// meanings depending on the DWARF container. It is only required when getting the stack or | 86 | /// meanings depending on the DWARF container. It is only required when getting the stack or |
| 136 | /// frame pointer register on some architectures. | 87 | /// frame pointer register on some architectures. |
| 137 | pub fn regBytes( | 88 | pub fn regBytes( |
| 138 | thread_context_ptr: anytype, | 89 | thread_context_ptr: *std.debug.ThreadContext, |
| 139 | reg_number: u8, | 90 | reg_number: u8, |
| 140 | reg_context: ?RegisterContext, | 91 | reg_context: ?RegisterContext, |
| 141 | ) AbiError!RegBytesReturnType(@TypeOf(thread_context_ptr)) { | 92 | ) RegBytesError![]u8 { |
| 142 | if (native_os == .windows) { | 93 | if (builtin.os.tag == .windows) { |
| 143 | return switch (builtin.cpu.arch) { | 94 | return switch (builtin.cpu.arch) { |
| 144 | .x86 => switch (reg_number) { | 95 | .x86 => switch (reg_number) { |
| 145 | 0 => mem.asBytes(&thread_context_ptr.Eax), | 96 | 0 => mem.asBytes(&thread_context_ptr.Eax), |
| ... | @@ -194,7 +145,7 @@ pub fn regBytes( | ... | @@ -194,7 +145,7 @@ pub fn regBytes( |
| 194 | 145 | ||
| 195 | const ucontext_ptr = thread_context_ptr; | 146 | const ucontext_ptr = thread_context_ptr; |
| 196 | return switch (builtin.cpu.arch) { | 147 | return switch (builtin.cpu.arch) { |
| 197 | .x86 => switch (native_os) { | 148 | .x86 => switch (builtin.os.tag) { |
| 198 | .linux, .netbsd, .solaris, .illumos => switch (reg_number) { | 149 | .linux, .netbsd, .solaris, .illumos => switch (reg_number) { |
| 199 | 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EAX]), | 150 | 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EAX]), |
| 200 | 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ECX]), | 151 | 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ECX]), |
| ... | @@ -229,7 +180,7 @@ pub fn regBytes( | ... | @@ -229,7 +180,7 @@ pub fn regBytes( |
| 229 | }, | 180 | }, |
| 230 | else => error.UnimplementedOs, | 181 | else => error.UnimplementedOs, |
| 231 | }, | 182 | }, |
| 232 | .x86_64 => switch (native_os) { | 183 | .x86_64 => switch (builtin.os.tag) { |
| 233 | .linux, .solaris, .illumos => switch (reg_number) { | 184 | .linux, .solaris, .illumos => switch (reg_number) { |
| 234 | 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RAX]), | 185 | 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RAX]), |
| 235 | 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RDX]), | 186 | 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RDX]), |
| ... | @@ -248,7 +199,7 @@ pub fn regBytes( | ... | @@ -248,7 +199,7 @@ pub fn regBytes( |
| 248 | 14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R14]), | 199 | 14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R14]), |
| 249 | 15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R15]), | 200 | 15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R15]), |
| 250 | 16 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RIP]), | 201 | 16 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RIP]), |
| 251 | 17...32 => |i| if (native_os.isSolarish()) | 202 | 17...32 => |i| if (builtin.os.tag.isSolarish()) |
| 252 | mem.asBytes(&ucontext_ptr.mcontext.fpregs.chip_state.xmm[i - 17]) | 203 | mem.asBytes(&ucontext_ptr.mcontext.fpregs.chip_state.xmm[i - 17]) |
| 253 | else | 204 | else |
| 254 | mem.asBytes(&ucontext_ptr.mcontext.fpregs.xmm[i - 17]), | 205 | mem.asBytes(&ucontext_ptr.mcontext.fpregs.xmm[i - 17]), |
| ... | @@ -318,7 +269,7 @@ pub fn regBytes( | ... | @@ -318,7 +269,7 @@ pub fn regBytes( |
| 318 | }, | 269 | }, |
| 319 | else => error.UnimplementedOs, | 270 | else => error.UnimplementedOs, |
| 320 | }, | 271 | }, |
| 321 | .arm => switch (native_os) { | 272 | .arm => switch (builtin.os.tag) { |
| 322 | .linux => switch (reg_number) { | 273 | .linux => switch (reg_number) { |
| 323 | 0 => mem.asBytes(&ucontext_ptr.mcontext.arm_r0), | 274 | 0 => mem.asBytes(&ucontext_ptr.mcontext.arm_r0), |
| 324 | 1 => mem.asBytes(&ucontext_ptr.mcontext.arm_r1), | 275 | 1 => mem.asBytes(&ucontext_ptr.mcontext.arm_r1), |
| ... | @@ -341,7 +292,7 @@ pub fn regBytes( | ... | @@ -341,7 +292,7 @@ pub fn regBytes( |
| 341 | }, | 292 | }, |
| 342 | else => error.UnimplementedOs, | 293 | else => error.UnimplementedOs, |
| 343 | }, | 294 | }, |
| 344 | .aarch64 => switch (native_os) { | 295 | .aarch64 => switch (builtin.os.tag) { |
| 345 | .macos, .ios => switch (reg_number) { | 296 | .macos, .ios => switch (reg_number) { |
| 346 | 0...28 => mem.asBytes(&ucontext_ptr.mcontext.ss.regs[reg_number]), | 297 | 0...28 => mem.asBytes(&ucontext_ptr.mcontext.ss.regs[reg_number]), |
| 347 | 29 => mem.asBytes(&ucontext_ptr.mcontext.ss.fp), | 298 | 29 => mem.asBytes(&ucontext_ptr.mcontext.ss.fp), |
| ... | @@ -389,22 +340,14 @@ pub fn regBytes( | ... | @@ -389,22 +340,14 @@ pub fn regBytes( |
| 389 | }; | 340 | }; |
| 390 | } | 341 | } |
| 391 | 342 | ||
| 392 | /// Returns the ABI-defined default value this register has in the unwinding table | 343 | /// Returns a pointer to a register stored in a ThreadContext, preserving the |
| 393 | /// before running any of the CIE instructions. The DWARF spec defines these as having | 344 | /// pointer attributes of the context. |
| 394 | /// the .undefined rule by default, but allows ABI authors to override that. | 345 | pub fn regValueNative( |
| 395 | pub fn getRegDefaultValue(reg_number: u8, context: *std.debug.Dwarf.UnwindContext, out: []u8) !void { | 346 | thread_context_ptr: *std.debug.ThreadContext, |
| 396 | switch (builtin.cpu.arch) { | 347 | reg_number: u8, |
| 397 | .aarch64 => { | 348 | reg_context: ?RegisterContext, |
| 398 | // Callee-saved registers are initialized as if they had the .same_value rule | 349 | ) !*align(1) usize { |
| 399 | if (reg_number >= 19 and reg_number <= 28) { | 350 | const reg_bytes = try regBytes(thread_context_ptr, reg_number, reg_context); |
| 400 | const src = try regBytes(context.thread_context, reg_number, context.reg_context); | 351 | if (@sizeOf(usize) != reg_bytes.len) return error.IncompatibleRegisterSize; |
| 401 | if (src.len != out.len) return error.RegisterSizeMismatch; | 352 | return mem.bytesAsValue(usize, reg_bytes[0..@sizeOf(usize)]); |
| 402 | @memcpy(out, src); | ||
| 403 | return; | ||
| 404 | } | ||
| 405 | }, | ||
| 406 | else => {}, | ||
| 407 | } | ||
| 408 | |||
| 409 | @memset(out, undefined); | ||
| 410 | } | 353 | } |
lib/std/debug/Dwarf/call_frame.zig-388| ... | @@ -297,391 +297,3 @@ pub const Instruction = union(Opcode) { | ... | @@ -297,391 +297,3 @@ pub const Instruction = union(Opcode) { |
| 297 | } | 297 | } |
| 298 | } | 298 | } |
| 299 | }; | 299 | }; |
| 300 | |||
| 301 | /// Since register rules are applied (usually) during a panic, | ||
| 302 | /// checked addition / subtraction is used so that we can return | ||
| 303 | /// an error and fall back to FP-based unwinding. | ||
| 304 | pub fn applyOffset(base: usize, offset: i64) !usize { | ||
| 305 | return if (offset >= 0) | ||
| 306 | try std.math.add(usize, base, @as(usize, @intCast(offset))) | ||
| 307 | else | ||
| 308 | try std.math.sub(usize, base, @as(usize, @intCast(-offset))); | ||
| 309 | } | ||
| 310 | |||
| 311 | /// This is a virtual machine that runs DWARF call frame instructions. | ||
| 312 | pub const VirtualMachine = struct { | ||
| 313 | /// See section 6.4.1 of the DWARF5 specification for details on each | ||
| 314 | const RegisterRule = union(enum) { | ||
| 315 | // The spec says that the default rule for each column is the undefined rule. | ||
| 316 | // However, it also allows ABI / compiler authors to specify alternate defaults, so | ||
| 317 | // there is a distinction made here. | ||
| 318 | default: void, | ||
| 319 | |||
| 320 | undefined: void, | ||
| 321 | same_value: void, | ||
| 322 | |||
| 323 | // offset(N) | ||
| 324 | offset: i64, | ||
| 325 | |||
| 326 | // val_offset(N) | ||
| 327 | val_offset: i64, | ||
| 328 | |||
| 329 | // register(R) | ||
| 330 | register: u8, | ||
| 331 | |||
| 332 | // expression(E) | ||
| 333 | expression: []const u8, | ||
| 334 | |||
| 335 | // val_expression(E) | ||
| 336 | val_expression: []const u8, | ||
| 337 | |||
| 338 | // Augmenter-defined rule | ||
| 339 | architectural: void, | ||
| 340 | }; | ||
| 341 | |||
| 342 | /// Each row contains unwinding rules for a set of registers. | ||
| 343 | pub const Row = struct { | ||
| 344 | /// Offset from `FrameDescriptionEntry.pc_begin` | ||
| 345 | offset: u64 = 0, | ||
| 346 | |||
| 347 | /// Special-case column that defines the CFA (Canonical Frame Address) rule. | ||
| 348 | /// The register field of this column defines the register that CFA is derived from. | ||
| 349 | cfa: Column = .{}, | ||
| 350 | |||
| 351 | /// The register fields in these columns define the register the rule applies to. | ||
| 352 | columns: ColumnRange = .{}, | ||
| 353 | |||
| 354 | /// Indicates that the next write to any column in this row needs to copy | ||
| 355 | /// the backing column storage first, as it may be referenced by previous rows. | ||
| 356 | copy_on_write: bool = false, | ||
| 357 | }; | ||
| 358 | |||
| 359 | pub const Column = struct { | ||
| 360 | register: ?u8 = null, | ||
| 361 | rule: RegisterRule = .{ .default = {} }, | ||
| 362 | |||
| 363 | /// Resolves the register rule and places the result into `out` (see dwarf.abi.regBytes) | ||
| 364 | pub fn resolveValue( | ||
| 365 | self: Column, | ||
| 366 | context: *std.debug.Dwarf.UnwindContext, | ||
| 367 | expression_context: std.debug.Dwarf.expression.Context, | ||
| 368 | ma: *debug.StackIterator.MemoryAccessor, | ||
| 369 | out: []u8, | ||
| 370 | ) !void { | ||
| 371 | switch (self.rule) { | ||
| 372 | .default => { | ||
| 373 | const register = self.register orelse return error.InvalidRegister; | ||
| 374 | try abi.getRegDefaultValue(register, context, out); | ||
| 375 | }, | ||
| 376 | .undefined => { | ||
| 377 | @memset(out, undefined); | ||
| 378 | }, | ||
| 379 | .same_value => { | ||
| 380 | // TODO: This copy could be eliminated if callers always copy the state then call this function to update it | ||
| 381 | const register = self.register orelse return error.InvalidRegister; | ||
| 382 | const src = try abi.regBytes(context.thread_context, register, context.reg_context); | ||
| 383 | if (src.len != out.len) return error.RegisterSizeMismatch; | ||
| 384 | @memcpy(out, src); | ||
| 385 | }, | ||
| 386 | .offset => |offset| { | ||
| 387 | if (context.cfa) |cfa| { | ||
| 388 | const addr = try applyOffset(cfa, offset); | ||
| 389 | if (ma.load(usize, addr) == null) return error.InvalidAddress; | ||
| 390 | const ptr: *const usize = @ptrFromInt(addr); | ||
| 391 | mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian); | ||
| 392 | } else return error.InvalidCFA; | ||
| 393 | }, | ||
| 394 | .val_offset => |offset| { | ||
| 395 | if (context.cfa) |cfa| { | ||
| 396 | mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian); | ||
| 397 | } else return error.InvalidCFA; | ||
| 398 | }, | ||
| 399 | .register => |register| { | ||
| 400 | const src = try abi.regBytes(context.thread_context, register, context.reg_context); | ||
| 401 | if (src.len != out.len) return error.RegisterSizeMismatch; | ||
| 402 | @memcpy(out, try abi.regBytes(context.thread_context, register, context.reg_context)); | ||
| 403 | }, | ||
| 404 | .expression => |expression| { | ||
| 405 | context.stack_machine.reset(); | ||
| 406 | const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?); | ||
| 407 | const addr = if (value) |v| blk: { | ||
| 408 | if (v != .generic) return error.InvalidExpressionValue; | ||
| 409 | break :blk v.generic; | ||
| 410 | } else return error.NoExpressionValue; | ||
| 411 | |||
| 412 | if (ma.load(usize, addr) == null) return error.InvalidExpressionAddress; | ||
| 413 | const ptr: *usize = @ptrFromInt(addr); | ||
| 414 | mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian); | ||
| 415 | }, | ||
| 416 | .val_expression => |expression| { | ||
| 417 | context.stack_machine.reset(); | ||
| 418 | const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?); | ||
| 419 | if (value) |v| { | ||
| 420 | if (v != .generic) return error.InvalidExpressionValue; | ||
| 421 | mem.writeInt(usize, out[0..@sizeOf(usize)], v.generic, native_endian); | ||
| 422 | } else return error.NoExpressionValue; | ||
| 423 | }, | ||
| 424 | .architectural => return error.UnimplementedRegisterRule, | ||
| 425 | } | ||
| 426 | } | ||
| 427 | }; | ||
| 428 | |||
| 429 | const ColumnRange = struct { | ||
| 430 | /// Index into `columns` of the first column in this row. | ||
| 431 | start: usize = undefined, | ||
| 432 | len: u8 = 0, | ||
| 433 | }; | ||
| 434 | |||
| 435 | columns: std.ArrayListUnmanaged(Column) = .{}, | ||
| 436 | stack: std.ArrayListUnmanaged(ColumnRange) = .{}, | ||
| 437 | current_row: Row = .{}, | ||
| 438 | |||
| 439 | /// The result of executing the CIE's initial_instructions | ||
| 440 | cie_row: ?Row = null, | ||
| 441 | |||
| 442 | pub fn deinit(self: *VirtualMachine, allocator: std.mem.Allocator) void { | ||
| 443 | self.stack.deinit(allocator); | ||
| 444 | self.columns.deinit(allocator); | ||
| 445 | self.* = undefined; | ||
| 446 | } | ||
| 447 | |||
| 448 | pub fn reset(self: *VirtualMachine) void { | ||
| 449 | self.stack.clearRetainingCapacity(); | ||
| 450 | self.columns.clearRetainingCapacity(); | ||
| 451 | self.current_row = .{}; | ||
| 452 | self.cie_row = null; | ||
| 453 | } | ||
| 454 | |||
| 455 | /// Return a slice backed by the row's non-CFA columns | ||
| 456 | pub fn rowColumns(self: VirtualMachine, row: Row) []Column { | ||
| 457 | if (row.columns.len == 0) return &.{}; | ||
| 458 | return self.columns.items[row.columns.start..][0..row.columns.len]; | ||
| 459 | } | ||
| 460 | |||
| 461 | /// Either retrieves or adds a column for `register` (non-CFA) in the current row. | ||
| 462 | fn getOrAddColumn(self: *VirtualMachine, allocator: std.mem.Allocator, register: u8) !*Column { | ||
| 463 | for (self.rowColumns(self.current_row)) |*c| { | ||
| 464 | if (c.register == register) return c; | ||
| 465 | } | ||
| 466 | |||
| 467 | if (self.current_row.columns.len == 0) { | ||
| 468 | self.current_row.columns.start = self.columns.items.len; | ||
| 469 | } | ||
| 470 | self.current_row.columns.len += 1; | ||
| 471 | |||
| 472 | const column = try self.columns.addOne(allocator); | ||
| 473 | column.* = .{ | ||
| 474 | .register = register, | ||
| 475 | }; | ||
| 476 | |||
| 477 | return column; | ||
| 478 | } | ||
| 479 | |||
| 480 | /// Runs the CIE instructions, then the FDE instructions. Execution halts | ||
| 481 | /// once the row that corresponds to `pc` is known, and the row is returned. | ||
| 482 | pub fn runTo( | ||
| 483 | self: *VirtualMachine, | ||
| 484 | allocator: std.mem.Allocator, | ||
| 485 | pc: u64, | ||
| 486 | cie: std.debug.Dwarf.CommonInformationEntry, | ||
| 487 | fde: std.debug.Dwarf.FrameDescriptionEntry, | ||
| 488 | addr_size_bytes: u8, | ||
| 489 | endian: std.builtin.Endian, | ||
| 490 | ) !Row { | ||
| 491 | assert(self.cie_row == null); | ||
| 492 | if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return error.AddressOutOfRange; | ||
| 493 | |||
| 494 | var prev_row: Row = self.current_row; | ||
| 495 | |||
| 496 | var cie_stream = std.io.fixedBufferStream(cie.initial_instructions); | ||
| 497 | var fde_stream = std.io.fixedBufferStream(fde.instructions); | ||
| 498 | var streams = [_]*std.io.FixedBufferStream([]const u8){ | ||
| 499 | &cie_stream, | ||
| 500 | &fde_stream, | ||
| 501 | }; | ||
| 502 | |||
| 503 | for (&streams, 0..) |stream, i| { | ||
| 504 | while (stream.pos < stream.buffer.len) { | ||
| 505 | const instruction = try std.debug.Dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian); | ||
| 506 | prev_row = try self.step(allocator, cie, i == 0, instruction); | ||
| 507 | if (pc < fde.pc_begin + self.current_row.offset) return prev_row; | ||
| 508 | } | ||
| 509 | } | ||
| 510 | |||
| 511 | return self.current_row; | ||
| 512 | } | ||
| 513 | |||
| 514 | pub fn runToNative( | ||
| 515 | self: *VirtualMachine, | ||
| 516 | allocator: std.mem.Allocator, | ||
| 517 | pc: u64, | ||
| 518 | cie: std.debug.Dwarf.CommonInformationEntry, | ||
| 519 | fde: std.debug.Dwarf.FrameDescriptionEntry, | ||
| 520 | ) !Row { | ||
| 521 | return self.runTo(allocator, pc, cie, fde, @sizeOf(usize), builtin.target.cpu.arch.endian()); | ||
| 522 | } | ||
| 523 | |||
| 524 | fn resolveCopyOnWrite(self: *VirtualMachine, allocator: std.mem.Allocator) !void { | ||
| 525 | if (!self.current_row.copy_on_write) return; | ||
| 526 | |||
| 527 | const new_start = self.columns.items.len; | ||
| 528 | if (self.current_row.columns.len > 0) { | ||
| 529 | try self.columns.ensureUnusedCapacity(allocator, self.current_row.columns.len); | ||
| 530 | self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row)); | ||
| 531 | self.current_row.columns.start = new_start; | ||
| 532 | } | ||
| 533 | } | ||
| 534 | |||
| 535 | /// Executes a single instruction. | ||
| 536 | /// If this instruction is from the CIE, `is_initial` should be set. | ||
| 537 | /// Returns the value of `current_row` before executing this instruction. | ||
| 538 | pub fn step( | ||
| 539 | self: *VirtualMachine, | ||
| 540 | allocator: std.mem.Allocator, | ||
| 541 | cie: std.debug.Dwarf.CommonInformationEntry, | ||
| 542 | is_initial: bool, | ||
| 543 | instruction: Instruction, | ||
| 544 | ) !Row { | ||
| 545 | // CIE instructions must be run before FDE instructions | ||
| 546 | assert(!is_initial or self.cie_row == null); | ||
| 547 | if (!is_initial and self.cie_row == null) { | ||
| 548 | self.cie_row = self.current_row; | ||
| 549 | self.current_row.copy_on_write = true; | ||
| 550 | } | ||
| 551 | |||
| 552 | const prev_row = self.current_row; | ||
| 553 | switch (instruction) { | ||
| 554 | .set_loc => |i| { | ||
| 555 | if (i.address <= self.current_row.offset) return error.InvalidOperation; | ||
| 556 | // TODO: Check cie.segment_selector_size != 0 for DWARFV4 | ||
| 557 | self.current_row.offset = i.address; | ||
| 558 | }, | ||
| 559 | inline .advance_loc, | ||
| 560 | .advance_loc1, | ||
| 561 | .advance_loc2, | ||
| 562 | .advance_loc4, | ||
| 563 | => |i| { | ||
| 564 | self.current_row.offset += i.delta * cie.code_alignment_factor; | ||
| 565 | self.current_row.copy_on_write = true; | ||
| 566 | }, | ||
| 567 | inline .offset, | ||
| 568 | .offset_extended, | ||
| 569 | .offset_extended_sf, | ||
| 570 | => |i| { | ||
| 571 | try self.resolveCopyOnWrite(allocator); | ||
| 572 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 573 | column.rule = .{ .offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor }; | ||
| 574 | }, | ||
| 575 | inline .restore, | ||
| 576 | .restore_extended, | ||
| 577 | => |i| { | ||
| 578 | try self.resolveCopyOnWrite(allocator); | ||
| 579 | if (self.cie_row) |cie_row| { | ||
| 580 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 581 | column.rule = for (self.rowColumns(cie_row)) |cie_column| { | ||
| 582 | if (cie_column.register == i.register) break cie_column.rule; | ||
| 583 | } else .{ .default = {} }; | ||
| 584 | } else return error.InvalidOperation; | ||
| 585 | }, | ||
| 586 | .nop => {}, | ||
| 587 | .undefined => |i| { | ||
| 588 | try self.resolveCopyOnWrite(allocator); | ||
| 589 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 590 | column.rule = .{ .undefined = {} }; | ||
| 591 | }, | ||
| 592 | .same_value => |i| { | ||
| 593 | try self.resolveCopyOnWrite(allocator); | ||
| 594 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 595 | column.rule = .{ .same_value = {} }; | ||
| 596 | }, | ||
| 597 | .register => |i| { | ||
| 598 | try self.resolveCopyOnWrite(allocator); | ||
| 599 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 600 | column.rule = .{ .register = i.target_register }; | ||
| 601 | }, | ||
| 602 | .remember_state => { | ||
| 603 | try self.stack.append(allocator, self.current_row.columns); | ||
| 604 | self.current_row.copy_on_write = true; | ||
| 605 | }, | ||
| 606 | .restore_state => { | ||
| 607 | const restored_columns = self.stack.popOrNull() orelse return error.InvalidOperation; | ||
| 608 | self.columns.shrinkRetainingCapacity(self.columns.items.len - self.current_row.columns.len); | ||
| 609 | try self.columns.ensureUnusedCapacity(allocator, restored_columns.len); | ||
| 610 | |||
| 611 | self.current_row.columns.start = self.columns.items.len; | ||
| 612 | self.current_row.columns.len = restored_columns.len; | ||
| 613 | self.columns.appendSliceAssumeCapacity(self.columns.items[restored_columns.start..][0..restored_columns.len]); | ||
| 614 | }, | ||
| 615 | .def_cfa => |i| { | ||
| 616 | try self.resolveCopyOnWrite(allocator); | ||
| 617 | self.current_row.cfa = .{ | ||
| 618 | .register = i.register, | ||
| 619 | .rule = .{ .val_offset = @intCast(i.offset) }, | ||
| 620 | }; | ||
| 621 | }, | ||
| 622 | .def_cfa_sf => |i| { | ||
| 623 | try self.resolveCopyOnWrite(allocator); | ||
| 624 | self.current_row.cfa = .{ | ||
| 625 | .register = i.register, | ||
| 626 | .rule = .{ .val_offset = i.offset * cie.data_alignment_factor }, | ||
| 627 | }; | ||
| 628 | }, | ||
| 629 | .def_cfa_register => |i| { | ||
| 630 | try self.resolveCopyOnWrite(allocator); | ||
| 631 | if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; | ||
| 632 | self.current_row.cfa.register = i.register; | ||
| 633 | }, | ||
| 634 | .def_cfa_offset => |i| { | ||
| 635 | try self.resolveCopyOnWrite(allocator); | ||
| 636 | if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; | ||
| 637 | self.current_row.cfa.rule = .{ | ||
| 638 | .val_offset = @intCast(i.offset), | ||
| 639 | }; | ||
| 640 | }, | ||
| 641 | .def_cfa_offset_sf => |i| { | ||
| 642 | try self.resolveCopyOnWrite(allocator); | ||
| 643 | if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; | ||
| 644 | self.current_row.cfa.rule = .{ | ||
| 645 | .val_offset = i.offset * cie.data_alignment_factor, | ||
| 646 | }; | ||
| 647 | }, | ||
| 648 | .def_cfa_expression => |i| { | ||
| 649 | try self.resolveCopyOnWrite(allocator); | ||
| 650 | self.current_row.cfa.register = undefined; | ||
| 651 | self.current_row.cfa.rule = .{ | ||
| 652 | .expression = i.block, | ||
| 653 | }; | ||
| 654 | }, | ||
| 655 | .expression => |i| { | ||
| 656 | try self.resolveCopyOnWrite(allocator); | ||
| 657 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 658 | column.rule = .{ | ||
| 659 | .expression = i.block, | ||
| 660 | }; | ||
| 661 | }, | ||
| 662 | .val_offset => |i| { | ||
| 663 | try self.resolveCopyOnWrite(allocator); | ||
| 664 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 665 | column.rule = .{ | ||
| 666 | .val_offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor, | ||
| 667 | }; | ||
| 668 | }, | ||
| 669 | .val_offset_sf => |i| { | ||
| 670 | try self.resolveCopyOnWrite(allocator); | ||
| 671 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 672 | column.rule = .{ | ||
| 673 | .val_offset = i.offset * cie.data_alignment_factor, | ||
| 674 | }; | ||
| 675 | }, | ||
| 676 | .val_expression => |i| { | ||
| 677 | try self.resolveCopyOnWrite(allocator); | ||
| 678 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 679 | column.rule = .{ | ||
| 680 | .val_expression = i.block, | ||
| 681 | }; | ||
| 682 | }, | ||
| 683 | } | ||
| 684 | |||
| 685 | return prev_row; | ||
| 686 | } | ||
| 687 | }; |
lib/std/debug/Dwarf/expression.zig+14-12| ... | @@ -1,11 +1,13 @@ | ... | @@ -1,11 +1,13 @@ |
| 1 | const std = @import("std"); | ||
| 2 | const builtin = @import("builtin"); | 1 | const builtin = @import("builtin"); |
| 2 | const native_arch = builtin.cpu.arch; | ||
| 3 | const native_endian = native_arch.endian(); | ||
| 4 | |||
| 5 | const std = @import("std"); | ||
| 3 | const leb = std.leb; | 6 | const leb = std.leb; |
| 4 | const OP = std.dwarf.OP; | 7 | const OP = std.dwarf.OP; |
| 5 | const abi = std.debug.Dwarf.abi; | 8 | const abi = std.debug.Dwarf.abi; |
| 6 | const mem = std.mem; | 9 | const mem = std.mem; |
| 7 | const assert = std.debug.assert; | 10 | const assert = std.debug.assert; |
| 8 | const native_endian = builtin.cpu.arch.endian(); | ||
| 9 | 11 | ||
| 10 | /// Expressions can be evaluated in different contexts, each requiring its own set of inputs. | 12 | /// Expressions can be evaluated in different contexts, each requiring its own set of inputs. |
| 11 | /// Callers should specify all the fields relevant to their context. If a field is required | 13 | /// Callers should specify all the fields relevant to their context. If a field is required |
| ... | @@ -14,7 +16,7 @@ pub const Context = struct { | ... | @@ -14,7 +16,7 @@ pub const Context = struct { |
| 14 | /// The dwarf format of the section this expression is in | 16 | /// The dwarf format of the section this expression is in |
| 15 | format: std.dwarf.Format = .@"32", | 17 | format: std.dwarf.Format = .@"32", |
| 16 | /// If specified, any addresses will pass through before being accessed | 18 | /// If specified, any addresses will pass through before being accessed |
| 17 | memory_accessor: ?*std.debug.StackIterator.MemoryAccessor = null, | 19 | memory_accessor: ?*std.debug.MemoryAccessor = null, |
| 18 | /// The compilation unit this expression relates to, if any | 20 | /// The compilation unit this expression relates to, if any |
| 19 | compile_unit: ?*const std.debug.Dwarf.CompileUnit = null, | 21 | compile_unit: ?*const std.debug.Dwarf.CompileUnit = null, |
| 20 | /// When evaluating a user-presented expression, this is the address of the object being evaluated | 22 | /// When evaluating a user-presented expression, this is the address of the object being evaluated |
| ... | @@ -34,7 +36,7 @@ pub const Options = struct { | ... | @@ -34,7 +36,7 @@ pub const Options = struct { |
| 34 | /// The address size of the target architecture | 36 | /// The address size of the target architecture |
| 35 | addr_size: u8 = @sizeOf(usize), | 37 | addr_size: u8 = @sizeOf(usize), |
| 36 | /// Endianness of the target architecture | 38 | /// Endianness of the target architecture |
| 37 | endian: std.builtin.Endian = builtin.target.cpu.arch.endian(), | 39 | endian: std.builtin.Endian = native_endian, |
| 38 | /// Restrict the stack machine to a subset of opcodes used in call frame instructions | 40 | /// Restrict the stack machine to a subset of opcodes used in call frame instructions |
| 39 | call_frame_context: bool = false, | 41 | call_frame_context: bool = false, |
| 40 | }; | 42 | }; |
| ... | @@ -60,7 +62,7 @@ pub const Error = error{ | ... | @@ -60,7 +62,7 @@ pub const Error = error{ |
| 60 | InvalidTypeLength, | 62 | InvalidTypeLength, |
| 61 | 63 | ||
| 62 | TruncatedIntegralType, | 64 | TruncatedIntegralType, |
| 63 | } || abi.AbiError || error{ EndOfStream, Overflow, OutOfMemory, DivisionByZero }; | 65 | } || abi.RegBytesError || error{ EndOfStream, Overflow, OutOfMemory, DivisionByZero }; |
| 64 | 66 | ||
| 65 | /// A stack machine that can decode and run DWARF expressions. | 67 | /// A stack machine that can decode and run DWARF expressions. |
| 66 | /// Expressions can be decoded for non-native address size and endianness, | 68 | /// Expressions can be decoded for non-native address size and endianness, |
| ... | @@ -304,7 +306,7 @@ pub fn StackMachine(comptime options: Options) type { | ... | @@ -304,7 +306,7 @@ pub fn StackMachine(comptime options: Options) type { |
| 304 | allocator: std.mem.Allocator, | 306 | allocator: std.mem.Allocator, |
| 305 | context: Context, | 307 | context: Context, |
| 306 | ) Error!bool { | 308 | ) Error!bool { |
| 307 | if (@sizeOf(usize) != @sizeOf(addr_type) or options.endian != comptime builtin.target.cpu.arch.endian()) | 309 | if (@sizeOf(usize) != @sizeOf(addr_type) or options.endian != native_endian) |
| 308 | @compileError("Execution of non-native address sizes / endianness is not supported"); | 310 | @compileError("Execution of non-native address sizes / endianness is not supported"); |
| 309 | 311 | ||
| 310 | const opcode = try stream.reader().readByte(); | 312 | const opcode = try stream.reader().readByte(); |
| ... | @@ -1186,13 +1188,13 @@ test "DWARF expressions" { | ... | @@ -1186,13 +1188,13 @@ test "DWARF expressions" { |
| 1186 | // TODO: Test fbreg (once implemented): mock a DIE and point compile_unit.frame_base at it | 1188 | // TODO: Test fbreg (once implemented): mock a DIE and point compile_unit.frame_base at it |
| 1187 | 1189 | ||
| 1188 | mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian); | 1190 | mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian); |
| 1189 | (try abi.regValueNative(usize, &thread_context, abi.fpRegNum(reg_context), reg_context)).* = 1; | 1191 | (try abi.regValueNative(&thread_context, abi.fpRegNum(native_arch, reg_context), reg_context)).* = 1; |
| 1190 | (try abi.regValueNative(usize, &thread_context, abi.spRegNum(reg_context), reg_context)).* = 2; | 1192 | (try abi.regValueNative(&thread_context, abi.spRegNum(native_arch, reg_context), reg_context)).* = 2; |
| 1191 | (try abi.regValueNative(usize, &thread_context, abi.ipRegNum(), reg_context)).* = 3; | 1193 | (try abi.regValueNative(&thread_context, abi.ipRegNum(native_arch).?, reg_context)).* = 3; |
| 1192 | 1194 | ||
| 1193 | try b.writeBreg(writer, abi.fpRegNum(reg_context), @as(usize, 100)); | 1195 | try b.writeBreg(writer, abi.fpRegNum(native_arch, reg_context), @as(usize, 100)); |
| 1194 | try b.writeBreg(writer, abi.spRegNum(reg_context), @as(usize, 200)); | 1196 | try b.writeBreg(writer, abi.spRegNum(native_arch, reg_context), @as(usize, 200)); |
| 1195 | try b.writeBregx(writer, abi.ipRegNum(), @as(usize, 300)); | 1197 | try b.writeBregx(writer, abi.ipRegNum(native_arch).?, @as(usize, 300)); |
| 1196 | try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 400)); | 1198 | try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 400)); |
| 1197 | 1199 | ||
| 1198 | _ = try stack_machine.run(program.items, allocator, context, 0); | 1200 | _ = try stack_machine.run(program.items, allocator, context, 0); |
lib/std/debug/MemoryAccessor.zig created+128| ... | @@ -0,0 +1,128 @@ | ||
| 1 | //! Reads memory from any address of the current location using OS-specific | ||
| 2 | //! syscalls, bypassing memory page protection. Useful for stack unwinding. | ||
| 3 | |||
| 4 | const builtin = @import("builtin"); | ||
| 5 | const native_os = builtin.os.tag; | ||
| 6 | |||
| 7 | const std = @import("../std.zig"); | ||
| 8 | const posix = std.posix; | ||
| 9 | const File = std.fs.File; | ||
| 10 | const page_size = std.mem.page_size; | ||
| 11 | |||
| 12 | const MemoryAccessor = @This(); | ||
| 13 | |||
| 14 | var cached_pid: posix.pid_t = -1; | ||
| 15 | |||
| 16 | mem: switch (native_os) { | ||
| 17 | .linux => File, | ||
| 18 | else => void, | ||
| 19 | }, | ||
| 20 | |||
| 21 | pub const init: MemoryAccessor = .{ | ||
| 22 | .mem = switch (native_os) { | ||
| 23 | .linux => .{ .handle = -1 }, | ||
| 24 | else => {}, | ||
| 25 | }, | ||
| 26 | }; | ||
| 27 | |||
| 28 | fn read(ma: *MemoryAccessor, address: usize, buf: []u8) bool { | ||
| 29 | switch (native_os) { | ||
| 30 | .linux => while (true) switch (ma.mem.handle) { | ||
| 31 | -2 => break, | ||
| 32 | -1 => { | ||
| 33 | const linux = std.os.linux; | ||
| 34 | const pid = switch (@atomicLoad(posix.pid_t, &cached_pid, .monotonic)) { | ||
| 35 | -1 => pid: { | ||
| 36 | const pid = linux.getpid(); | ||
| 37 | @atomicStore(posix.pid_t, &cached_pid, pid, .monotonic); | ||
| 38 | break :pid pid; | ||
| 39 | }, | ||
| 40 | else => |pid| pid, | ||
| 41 | }; | ||
| 42 | const bytes_read = linux.process_vm_readv( | ||
| 43 | pid, | ||
| 44 | &.{.{ .base = buf.ptr, .len = buf.len }}, | ||
| 45 | &.{.{ .base = @ptrFromInt(address), .len = buf.len }}, | ||
| 46 | 0, | ||
| 47 | ); | ||
| 48 | switch (linux.E.init(bytes_read)) { | ||
| 49 | .SUCCESS => return bytes_read == buf.len, | ||
| 50 | .FAULT => return false, | ||
| 51 | .INVAL, .PERM, .SRCH => unreachable, // own pid is always valid | ||
| 52 | .NOMEM => {}, | ||
| 53 | .NOSYS => {}, // QEMU is known not to implement this syscall. | ||
| 54 | else => unreachable, // unexpected | ||
| 55 | } | ||
| 56 | var path_buf: [ | ||
| 57 | std.fmt.count("/proc/{d}/mem", .{std.math.minInt(posix.pid_t)}) | ||
| 58 | ]u8 = undefined; | ||
| 59 | const path = std.fmt.bufPrint(&path_buf, "/proc/{d}/mem", .{pid}) catch | ||
| 60 | unreachable; | ||
| 61 | ma.mem = std.fs.openFileAbsolute(path, .{}) catch { | ||
| 62 | ma.mem.handle = -2; | ||
| 63 | break; | ||
| 64 | }; | ||
| 65 | }, | ||
| 66 | else => return (ma.mem.pread(buf, address) catch return false) == buf.len, | ||
| 67 | }, | ||
| 68 | else => {}, | ||
| 69 | } | ||
| 70 | if (!isValidMemory(address)) return false; | ||
| 71 | @memcpy(buf, @as([*]const u8, @ptrFromInt(address))); | ||
| 72 | return true; | ||
| 73 | } | ||
| 74 | |||
| 75 | pub fn load(ma: *MemoryAccessor, comptime Type: type, address: usize) ?Type { | ||
| 76 | var result: Type = undefined; | ||
| 77 | return if (ma.read(address, std.mem.asBytes(&result))) result else null; | ||
| 78 | } | ||
| 79 | |||
| 80 | pub fn isValidMemory(address: usize) bool { | ||
| 81 | // We are unable to determine validity of memory for freestanding targets | ||
| 82 | if (native_os == .freestanding or native_os == .uefi) return true; | ||
| 83 | |||
| 84 | const aligned_address = address & ~@as(usize, @intCast((page_size - 1))); | ||
| 85 | if (aligned_address == 0) return false; | ||
| 86 | const aligned_memory = @as([*]align(page_size) u8, @ptrFromInt(aligned_address))[0..page_size]; | ||
| 87 | |||
| 88 | if (native_os == .windows) { | ||
| 89 | const windows = std.os.windows; | ||
| 90 | |||
| 91 | var memory_info: windows.MEMORY_BASIC_INFORMATION = undefined; | ||
| 92 | |||
| 93 | // The only error this function can throw is ERROR_INVALID_PARAMETER. | ||
| 94 | // supply an address that invalid i'll be thrown. | ||
| 95 | const rc = windows.VirtualQuery(aligned_memory, &memory_info, aligned_memory.len) catch { | ||
| 96 | return false; | ||
| 97 | }; | ||
| 98 | |||
| 99 | // Result code has to be bigger than zero (number of bytes written) | ||
| 100 | if (rc == 0) { | ||
| 101 | return false; | ||
| 102 | } | ||
| 103 | |||
| 104 | // Free pages cannot be read, they are unmapped | ||
| 105 | if (memory_info.State == windows.MEM_FREE) { | ||
| 106 | return false; | ||
| 107 | } | ||
| 108 | |||
| 109 | return true; | ||
| 110 | } else if (have_msync) { | ||
| 111 | posix.msync(aligned_memory, posix.MSF.ASYNC) catch |err| { | ||
| 112 | switch (err) { | ||
| 113 | error.UnmappedMemory => return false, | ||
| 114 | else => unreachable, | ||
| 115 | } | ||
| 116 | }; | ||
| 117 | |||
| 118 | return true; | ||
| 119 | } else { | ||
| 120 | // We are unable to determine validity of memory on this target. | ||
| 121 | return true; | ||
| 122 | } | ||
| 123 | } | ||
| 124 | |||
| 125 | const have_msync = switch (native_os) { | ||
| 126 | .wasi, .emscripten, .windows => false, | ||
| 127 | else => true, | ||
| 128 | }; | ||
lib/std/debug/Pdb.zig created+591| ... | @@ -0,0 +1,591 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const File = std.fs.File; | ||
| 3 | const Allocator = std.mem.Allocator; | ||
| 4 | const pdb = std.pdb; | ||
| 5 | |||
| 6 | const Pdb = @This(); | ||
| 7 | |||
| 8 | in_file: File, | ||
| 9 | msf: Msf, | ||
| 10 | allocator: Allocator, | ||
| 11 | string_table: ?*MsfStream, | ||
| 12 | dbi: ?*MsfStream, | ||
| 13 | modules: []Module, | ||
| 14 | sect_contribs: []pdb.SectionContribEntry, | ||
| 15 | guid: [16]u8, | ||
| 16 | age: u32, | ||
| 17 | |||
| 18 | pub const Module = struct { | ||
| 19 | mod_info: pdb.ModInfo, | ||
| 20 | module_name: []u8, | ||
| 21 | obj_file_name: []u8, | ||
| 22 | // The fields below are filled on demand. | ||
| 23 | populated: bool, | ||
| 24 | symbols: []u8, | ||
| 25 | subsect_info: []u8, | ||
| 26 | checksum_offset: ?usize, | ||
| 27 | |||
| 28 | pub fn deinit(self: *Module, allocator: Allocator) void { | ||
| 29 | allocator.free(self.module_name); | ||
| 30 | allocator.free(self.obj_file_name); | ||
| 31 | if (self.populated) { | ||
| 32 | allocator.free(self.symbols); | ||
| 33 | allocator.free(self.subsect_info); | ||
| 34 | } | ||
| 35 | } | ||
| 36 | }; | ||
| 37 | |||
| 38 | pub fn init(allocator: Allocator, path: []const u8) !Pdb { | ||
| 39 | const file = try std.fs.cwd().openFile(path, .{}); | ||
| 40 | errdefer file.close(); | ||
| 41 | |||
| 42 | return .{ | ||
| 43 | .in_file = file, | ||
| 44 | .allocator = allocator, | ||
| 45 | .string_table = null, | ||
| 46 | .dbi = null, | ||
| 47 | .msf = try Msf.init(allocator, file), | ||
| 48 | .modules = &[_]Module{}, | ||
| 49 | .sect_contribs = &[_]pdb.SectionContribEntry{}, | ||
| 50 | .guid = undefined, | ||
| 51 | .age = undefined, | ||
| 52 | }; | ||
| 53 | } | ||
| 54 | |||
| 55 | pub fn deinit(self: *Pdb) void { | ||
| 56 | self.in_file.close(); | ||
| 57 | self.msf.deinit(self.allocator); | ||
| 58 | for (self.modules) |*module| { | ||
| 59 | module.deinit(self.allocator); | ||
| 60 | } | ||
| 61 | self.allocator.free(self.modules); | ||
| 62 | self.allocator.free(self.sect_contribs); | ||
| 63 | } | ||
| 64 | |||
| 65 | pub fn parseDbiStream(self: *Pdb) !void { | ||
| 66 | var stream = self.getStream(pdb.StreamType.Dbi) orelse | ||
| 67 | return error.InvalidDebugInfo; | ||
| 68 | const reader = stream.reader(); | ||
| 69 | |||
| 70 | const header = try reader.readStruct(std.pdb.DbiStreamHeader); | ||
| 71 | if (header.VersionHeader != 19990903) // V70, only value observed by LLVM team | ||
| 72 | return error.UnknownPDBVersion; | ||
| 73 | // if (header.Age != age) | ||
| 74 | // return error.UnmatchingPDB; | ||
| 75 | |||
| 76 | const mod_info_size = header.ModInfoSize; | ||
| 77 | const section_contrib_size = header.SectionContributionSize; | ||
| 78 | |||
| 79 | var modules = std.ArrayList(Module).init(self.allocator); | ||
| 80 | errdefer modules.deinit(); | ||
| 81 | |||
| 82 | // Module Info Substream | ||
| 83 | var mod_info_offset: usize = 0; | ||
| 84 | while (mod_info_offset != mod_info_size) { | ||
| 85 | const mod_info = try reader.readStruct(pdb.ModInfo); | ||
| 86 | var this_record_len: usize = @sizeOf(pdb.ModInfo); | ||
| 87 | |||
| 88 | const module_name = try reader.readUntilDelimiterAlloc(self.allocator, 0, 1024); | ||
| 89 | errdefer self.allocator.free(module_name); | ||
| 90 | this_record_len += module_name.len + 1; | ||
| 91 | |||
| 92 | const obj_file_name = try reader.readUntilDelimiterAlloc(self.allocator, 0, 1024); | ||
| 93 | errdefer self.allocator.free(obj_file_name); | ||
| 94 | this_record_len += obj_file_name.len + 1; | ||
| 95 | |||
| 96 | if (this_record_len % 4 != 0) { | ||
| 97 | const round_to_next_4 = (this_record_len | 0x3) + 1; | ||
| 98 | const march_forward_bytes = round_to_next_4 - this_record_len; | ||
| 99 | try stream.seekBy(@as(isize, @intCast(march_forward_bytes))); | ||
| 100 | this_record_len += march_forward_bytes; | ||
| 101 | } | ||
| 102 | |||
| 103 | try modules.append(Module{ | ||
| 104 | .mod_info = mod_info, | ||
| 105 | .module_name = module_name, | ||
| 106 | .obj_file_name = obj_file_name, | ||
| 107 | |||
| 108 | .populated = false, | ||
| 109 | .symbols = undefined, | ||
| 110 | .subsect_info = undefined, | ||
| 111 | .checksum_offset = null, | ||
| 112 | }); | ||
| 113 | |||
| 114 | mod_info_offset += this_record_len; | ||
| 115 | if (mod_info_offset > mod_info_size) | ||
| 116 | return error.InvalidDebugInfo; | ||
| 117 | } | ||
| 118 | |||
| 119 | // Section Contribution Substream | ||
| 120 | var sect_contribs = std.ArrayList(pdb.SectionContribEntry).init(self.allocator); | ||
| 121 | errdefer sect_contribs.deinit(); | ||
| 122 | |||
| 123 | var sect_cont_offset: usize = 0; | ||
| 124 | if (section_contrib_size != 0) { | ||
| 125 | const version = reader.readEnum(std.pdb.SectionContrSubstreamVersion, .little) catch |err| switch (err) { | ||
| 126 | error.InvalidValue => return error.InvalidDebugInfo, | ||
| 127 | else => |e| return e, | ||
| 128 | }; | ||
| 129 | _ = version; | ||
| 130 | sect_cont_offset += @sizeOf(u32); | ||
| 131 | } | ||
| 132 | while (sect_cont_offset != section_contrib_size) { | ||
| 133 | const entry = try sect_contribs.addOne(); | ||
| 134 | entry.* = try reader.readStruct(pdb.SectionContribEntry); | ||
| 135 | sect_cont_offset += @sizeOf(pdb.SectionContribEntry); | ||
| 136 | |||
| 137 | if (sect_cont_offset > section_contrib_size) | ||
| 138 | return error.InvalidDebugInfo; | ||
| 139 | } | ||
| 140 | |||
| 141 | self.modules = try modules.toOwnedSlice(); | ||
| 142 | self.sect_contribs = try sect_contribs.toOwnedSlice(); | ||
| 143 | } | ||
| 144 | |||
| 145 | pub fn parseInfoStream(self: *Pdb) !void { | ||
| 146 | var stream = self.getStream(pdb.StreamType.Pdb) orelse | ||
| 147 | return error.InvalidDebugInfo; | ||
| 148 | const reader = stream.reader(); | ||
| 149 | |||
| 150 | // Parse the InfoStreamHeader. | ||
| 151 | const version = try reader.readInt(u32, .little); | ||
| 152 | const signature = try reader.readInt(u32, .little); | ||
| 153 | _ = signature; | ||
| 154 | const age = try reader.readInt(u32, .little); | ||
| 155 | const guid = try reader.readBytesNoEof(16); | ||
| 156 | |||
| 157 | if (version != 20000404) // VC70, only value observed by LLVM team | ||
| 158 | return error.UnknownPDBVersion; | ||
| 159 | |||
| 160 | self.guid = guid; | ||
| 161 | self.age = age; | ||
| 162 | |||
| 163 | // Find the string table. | ||
| 164 | const string_table_index = str_tab_index: { | ||
| 165 | const name_bytes_len = try reader.readInt(u32, .little); | ||
| 166 | const name_bytes = try self.allocator.alloc(u8, name_bytes_len); | ||
| 167 | defer self.allocator.free(name_bytes); | ||
| 168 | try reader.readNoEof(name_bytes); | ||
| 169 | |||
| 170 | const HashTableHeader = extern struct { | ||
| 171 | Size: u32, | ||
| 172 | Capacity: u32, | ||
| 173 | |||
| 174 | fn maxLoad(cap: u32) u32 { | ||
| 175 | return cap * 2 / 3 + 1; | ||
| 176 | } | ||
| 177 | }; | ||
| 178 | const hash_tbl_hdr = try reader.readStruct(HashTableHeader); | ||
| 179 | if (hash_tbl_hdr.Capacity == 0) | ||
| 180 | return error.InvalidDebugInfo; | ||
| 181 | |||
| 182 | if (hash_tbl_hdr.Size > HashTableHeader.maxLoad(hash_tbl_hdr.Capacity)) | ||
| 183 | return error.InvalidDebugInfo; | ||
| 184 | |||
| 185 | const present = try readSparseBitVector(&reader, self.allocator); | ||
| 186 | defer self.allocator.free(present); | ||
| 187 | if (present.len != hash_tbl_hdr.Size) | ||
| 188 | return error.InvalidDebugInfo; | ||
| 189 | const deleted = try readSparseBitVector(&reader, self.allocator); | ||
| 190 | defer self.allocator.free(deleted); | ||
| 191 | |||
| 192 | for (present) |_| { | ||
| 193 | const name_offset = try reader.readInt(u32, .little); | ||
| 194 | const name_index = try reader.readInt(u32, .little); | ||
| 195 | if (name_offset > name_bytes.len) | ||
| 196 | return error.InvalidDebugInfo; | ||
| 197 | const name = std.mem.sliceTo(name_bytes[name_offset..], 0); | ||
| 198 | if (std.mem.eql(u8, name, "/names")) { | ||
| 199 | break :str_tab_index name_index; | ||
| 200 | } | ||
| 201 | } | ||
| 202 | return error.MissingDebugInfo; | ||
| 203 | }; | ||
| 204 | |||
| 205 | self.string_table = self.getStreamById(string_table_index) orelse | ||
| 206 | return error.MissingDebugInfo; | ||
| 207 | } | ||
| 208 | |||
| 209 | pub fn getSymbolName(self: *Pdb, module: *Module, address: u64) ?[]const u8 { | ||
| 210 | _ = self; | ||
| 211 | std.debug.assert(module.populated); | ||
| 212 | |||
| 213 | var symbol_i: usize = 0; | ||
| 214 | while (symbol_i != module.symbols.len) { | ||
| 215 | const prefix = @as(*align(1) pdb.RecordPrefix, @ptrCast(&module.symbols[symbol_i])); | ||
| 216 | if (prefix.RecordLen < 2) | ||
| 217 | return null; | ||
| 218 | switch (prefix.RecordKind) { | ||
| 219 | .S_LPROC32, .S_GPROC32 => { | ||
| 220 | const proc_sym = @as(*align(1) pdb.ProcSym, @ptrCast(&module.symbols[symbol_i + @sizeOf(pdb.RecordPrefix)])); | ||
| 221 | if (address >= proc_sym.CodeOffset and address < proc_sym.CodeOffset + proc_sym.CodeSize) { | ||
| 222 | return std.mem.sliceTo(@as([*:0]u8, @ptrCast(&proc_sym.Name[0])), 0); | ||
| 223 | } | ||
| 224 | }, | ||
| 225 | else => {}, | ||
| 226 | } | ||
| 227 | symbol_i += prefix.RecordLen + @sizeOf(u16); | ||
| 228 | } | ||
| 229 | |||
| 230 | return null; | ||
| 231 | } | ||
| 232 | |||
| 233 | pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !std.debug.SourceLocation { | ||
| 234 | std.debug.assert(module.populated); | ||
| 235 | const subsect_info = module.subsect_info; | ||
| 236 | |||
| 237 | var sect_offset: usize = 0; | ||
| 238 | var skip_len: usize = undefined; | ||
| 239 | const checksum_offset = module.checksum_offset orelse return error.MissingDebugInfo; | ||
| 240 | while (sect_offset != subsect_info.len) : (sect_offset += skip_len) { | ||
| 241 | const subsect_hdr = @as(*align(1) pdb.DebugSubsectionHeader, @ptrCast(&subsect_info[sect_offset])); | ||
| 242 | skip_len = subsect_hdr.Length; | ||
| 243 | sect_offset += @sizeOf(pdb.DebugSubsectionHeader); | ||
| 244 | |||
| 245 | switch (subsect_hdr.Kind) { | ||
| 246 | .Lines => { | ||
| 247 | var line_index = sect_offset; | ||
| 248 | |||
| 249 | const line_hdr = @as(*align(1) pdb.LineFragmentHeader, @ptrCast(&subsect_info[line_index])); | ||
| 250 | if (line_hdr.RelocSegment == 0) | ||
| 251 | return error.MissingDebugInfo; | ||
| 252 | line_index += @sizeOf(pdb.LineFragmentHeader); | ||
| 253 | const frag_vaddr_start = line_hdr.RelocOffset; | ||
| 254 | const frag_vaddr_end = frag_vaddr_start + line_hdr.CodeSize; | ||
| 255 | |||
| 256 | if (address >= frag_vaddr_start and address < frag_vaddr_end) { | ||
| 257 | // There is an unknown number of LineBlockFragmentHeaders (and their accompanying line and column records) | ||
| 258 | // from now on. We will iterate through them, and eventually find a SourceLocation that we're interested in, | ||
| 259 | // breaking out to :subsections. If not, we will make sure to not read anything outside of this subsection. | ||
| 260 | const subsection_end_index = sect_offset + subsect_hdr.Length; | ||
| 261 | |||
| 262 | while (line_index < subsection_end_index) { | ||
| 263 | const block_hdr = @as(*align(1) pdb.LineBlockFragmentHeader, @ptrCast(&subsect_info[line_index])); | ||
| 264 | line_index += @sizeOf(pdb.LineBlockFragmentHeader); | ||
| 265 | const start_line_index = line_index; | ||
| 266 | |||
| 267 | const has_column = line_hdr.Flags.LF_HaveColumns; | ||
| 268 | |||
| 269 | // All line entries are stored inside their line block by ascending start address. | ||
| 270 | // Heuristic: we want to find the last line entry | ||
| 271 | // that has a vaddr_start <= address. | ||
| 272 | // This is done with a simple linear search. | ||
| 273 | var line_i: u32 = 0; | ||
| 274 | while (line_i < block_hdr.NumLines) : (line_i += 1) { | ||
| 275 | const line_num_entry = @as(*align(1) pdb.LineNumberEntry, @ptrCast(&subsect_info[line_index])); | ||
| 276 | line_index += @sizeOf(pdb.LineNumberEntry); | ||
| 277 | |||
| 278 | const vaddr_start = frag_vaddr_start + line_num_entry.Offset; | ||
| 279 | if (address < vaddr_start) { | ||
| 280 | break; | ||
| 281 | } | ||
| 282 | } | ||
| 283 | |||
| 284 | // line_i == 0 would mean that no matching pdb.LineNumberEntry was found. | ||
| 285 | if (line_i > 0) { | ||
| 286 | const subsect_index = checksum_offset + block_hdr.NameIndex; | ||
| 287 | const chksum_hdr = @as(*align(1) pdb.FileChecksumEntryHeader, @ptrCast(&module.subsect_info[subsect_index])); | ||
| 288 | const strtab_offset = @sizeOf(pdb.StringTableHeader) + chksum_hdr.FileNameOffset; | ||
| 289 | try self.string_table.?.seekTo(strtab_offset); | ||
| 290 | const source_file_name = try self.string_table.?.reader().readUntilDelimiterAlloc(self.allocator, 0, 1024); | ||
| 291 | |||
| 292 | const line_entry_idx = line_i - 1; | ||
| 293 | |||
| 294 | const column = if (has_column) blk: { | ||
| 295 | const start_col_index = start_line_index + @sizeOf(pdb.LineNumberEntry) * block_hdr.NumLines; | ||
| 296 | const col_index = start_col_index + @sizeOf(pdb.ColumnNumberEntry) * line_entry_idx; | ||
| 297 | const col_num_entry = @as(*align(1) pdb.ColumnNumberEntry, @ptrCast(&subsect_info[col_index])); | ||
| 298 | break :blk col_num_entry.StartColumn; | ||
| 299 | } else 0; | ||
| 300 | |||
| 301 | const found_line_index = start_line_index + line_entry_idx * @sizeOf(pdb.LineNumberEntry); | ||
| 302 | const line_num_entry: *align(1) pdb.LineNumberEntry = @ptrCast(&subsect_info[found_line_index]); | ||
| 303 | const flags: *align(1) pdb.LineNumberEntry.Flags = @ptrCast(&line_num_entry.Flags); | ||
| 304 | |||
| 305 | return .{ | ||
| 306 | .file_name = source_file_name, | ||
| 307 | .line = flags.Start, | ||
| 308 | .column = column, | ||
| 309 | }; | ||
| 310 | } | ||
| 311 | } | ||
| 312 | |||
| 313 | // Checking that we are not reading garbage after the (possibly) multiple block fragments. | ||
| 314 | if (line_index != subsection_end_index) { | ||
| 315 | return error.InvalidDebugInfo; | ||
| 316 | } | ||
| 317 | } | ||
| 318 | }, | ||
| 319 | else => {}, | ||
| 320 | } | ||
| 321 | |||
| 322 | if (sect_offset > subsect_info.len) | ||
| 323 | return error.InvalidDebugInfo; | ||
| 324 | } | ||
| 325 | |||
| 326 | return error.MissingDebugInfo; | ||
| 327 | } | ||
| 328 | |||
| 329 | pub fn getModule(self: *Pdb, index: usize) !?*Module { | ||
| 330 | if (index >= self.modules.len) | ||
| 331 | return null; | ||
| 332 | |||
| 333 | const mod = &self.modules[index]; | ||
| 334 | if (mod.populated) | ||
| 335 | return mod; | ||
| 336 | |||
| 337 | // At most one can be non-zero. | ||
| 338 | if (mod.mod_info.C11ByteSize != 0 and mod.mod_info.C13ByteSize != 0) | ||
| 339 | return error.InvalidDebugInfo; | ||
| 340 | if (mod.mod_info.C13ByteSize == 0) | ||
| 341 | return error.InvalidDebugInfo; | ||
| 342 | |||
| 343 | const stream = self.getStreamById(mod.mod_info.ModuleSymStream) orelse | ||
| 344 | return error.MissingDebugInfo; | ||
| 345 | const reader = stream.reader(); | ||
| 346 | |||
| 347 | const signature = try reader.readInt(u32, .little); | ||
| 348 | if (signature != 4) | ||
| 349 | return error.InvalidDebugInfo; | ||
| 350 | |||
| 351 | mod.symbols = try self.allocator.alloc(u8, mod.mod_info.SymByteSize - 4); | ||
| 352 | errdefer self.allocator.free(mod.symbols); | ||
| 353 | try reader.readNoEof(mod.symbols); | ||
| 354 | |||
| 355 | mod.subsect_info = try self.allocator.alloc(u8, mod.mod_info.C13ByteSize); | ||
| 356 | errdefer self.allocator.free(mod.subsect_info); | ||
| 357 | try reader.readNoEof(mod.subsect_info); | ||
| 358 | |||
| 359 | var sect_offset: usize = 0; | ||
| 360 | var skip_len: usize = undefined; | ||
| 361 | while (sect_offset != mod.subsect_info.len) : (sect_offset += skip_len) { | ||
| 362 | const subsect_hdr = @as(*align(1) pdb.DebugSubsectionHeader, @ptrCast(&mod.subsect_info[sect_offset])); | ||
| 363 | skip_len = subsect_hdr.Length; | ||
| 364 | sect_offset += @sizeOf(pdb.DebugSubsectionHeader); | ||
| 365 | |||
| 366 | switch (subsect_hdr.Kind) { | ||
| 367 | .FileChecksums => { | ||
| 368 | mod.checksum_offset = sect_offset; | ||
| 369 | break; | ||
| 370 | }, | ||
| 371 | else => {}, | ||
| 372 | } | ||
| 373 | |||
| 374 | if (sect_offset > mod.subsect_info.len) | ||
| 375 | return error.InvalidDebugInfo; | ||
| 376 | } | ||
| 377 | |||
| 378 | mod.populated = true; | ||
| 379 | return mod; | ||
| 380 | } | ||
| 381 | |||
| 382 | pub fn getStreamById(self: *Pdb, id: u32) ?*MsfStream { | ||
| 383 | if (id >= self.msf.streams.len) | ||
| 384 | return null; | ||
| 385 | return &self.msf.streams[id]; | ||
| 386 | } | ||
| 387 | |||
| 388 | pub fn getStream(self: *Pdb, stream: pdb.StreamType) ?*MsfStream { | ||
| 389 | const id = @intFromEnum(stream); | ||
| 390 | return self.getStreamById(id); | ||
| 391 | } | ||
| 392 | |||
| 393 | /// https://llvm.org/docs/PDB/MsfFile.html | ||
| 394 | const Msf = struct { | ||
| 395 | directory: MsfStream, | ||
| 396 | streams: []MsfStream, | ||
| 397 | |||
| 398 | fn init(allocator: Allocator, file: File) !Msf { | ||
| 399 | const in = file.reader(); | ||
| 400 | |||
| 401 | const superblock = try in.readStruct(pdb.SuperBlock); | ||
| 402 | |||
| 403 | // Sanity checks | ||
| 404 | if (!std.mem.eql(u8, &superblock.FileMagic, pdb.SuperBlock.file_magic)) | ||
| 405 | return error.InvalidDebugInfo; | ||
| 406 | if (superblock.FreeBlockMapBlock != 1 and superblock.FreeBlockMapBlock != 2) | ||
| 407 | return error.InvalidDebugInfo; | ||
| 408 | const file_len = try file.getEndPos(); | ||
| 409 | if (superblock.NumBlocks * superblock.BlockSize != file_len) | ||
| 410 | return error.InvalidDebugInfo; | ||
| 411 | switch (superblock.BlockSize) { | ||
| 412 | // llvm only supports 4096 but we can handle any of these values | ||
| 413 | 512, 1024, 2048, 4096 => {}, | ||
| 414 | else => return error.InvalidDebugInfo, | ||
| 415 | } | ||
| 416 | |||
| 417 | const dir_block_count = blockCountFromSize(superblock.NumDirectoryBytes, superblock.BlockSize); | ||
| 418 | if (dir_block_count > superblock.BlockSize / @sizeOf(u32)) | ||
| 419 | return error.UnhandledBigDirectoryStream; // cf. BlockMapAddr comment. | ||
| 420 | |||
| 421 | try file.seekTo(superblock.BlockSize * superblock.BlockMapAddr); | ||
| 422 | const dir_blocks = try allocator.alloc(u32, dir_block_count); | ||
| 423 | for (dir_blocks) |*b| { | ||
| 424 | b.* = try in.readInt(u32, .little); | ||
| 425 | } | ||
| 426 | var directory = MsfStream.init( | ||
| 427 | superblock.BlockSize, | ||
| 428 | file, | ||
| 429 | dir_blocks, | ||
| 430 | ); | ||
| 431 | |||
| 432 | const begin = directory.pos; | ||
| 433 | const stream_count = try directory.reader().readInt(u32, .little); | ||
| 434 | const stream_sizes = try allocator.alloc(u32, stream_count); | ||
| 435 | defer allocator.free(stream_sizes); | ||
| 436 | |||
| 437 | // Microsoft's implementation uses @as(u32, -1) for inexistent streams. | ||
| 438 | // These streams are not used, but still participate in the file | ||
| 439 | // and must be taken into account when resolving stream indices. | ||
| 440 | const Nil = 0xFFFFFFFF; | ||
| 441 | for (stream_sizes) |*s| { | ||
| 442 | const size = try directory.reader().readInt(u32, .little); | ||
| 443 | s.* = if (size == Nil) 0 else blockCountFromSize(size, superblock.BlockSize); | ||
| 444 | } | ||
| 445 | |||
| 446 | const streams = try allocator.alloc(MsfStream, stream_count); | ||
| 447 | for (streams, 0..) |*stream, i| { | ||
| 448 | const size = stream_sizes[i]; | ||
| 449 | if (size == 0) { | ||
| 450 | stream.* = MsfStream{ | ||
| 451 | .blocks = &[_]u32{}, | ||
| 452 | }; | ||
| 453 | } else { | ||
| 454 | var blocks = try allocator.alloc(u32, size); | ||
| 455 | var j: u32 = 0; | ||
| 456 | while (j < size) : (j += 1) { | ||
| 457 | const block_id = try directory.reader().readInt(u32, .little); | ||
| 458 | const n = (block_id % superblock.BlockSize); | ||
| 459 | // 0 is for pdb.SuperBlock, 1 and 2 for FPMs. | ||
| 460 | if (block_id == 0 or n == 1 or n == 2 or block_id * superblock.BlockSize > file_len) | ||
| 461 | return error.InvalidBlockIndex; | ||
| 462 | blocks[j] = block_id; | ||
| 463 | } | ||
| 464 | |||
| 465 | stream.* = MsfStream.init( | ||
| 466 | superblock.BlockSize, | ||
| 467 | file, | ||
| 468 | blocks, | ||
| 469 | ); | ||
| 470 | } | ||
| 471 | } | ||
| 472 | |||
| 473 | const end = directory.pos; | ||
| 474 | if (end - begin != superblock.NumDirectoryBytes) | ||
| 475 | return error.InvalidStreamDirectory; | ||
| 476 | |||
| 477 | return Msf{ | ||
| 478 | .directory = directory, | ||
| 479 | .streams = streams, | ||
| 480 | }; | ||
| 481 | } | ||
| 482 | |||
| 483 | fn deinit(self: *Msf, allocator: Allocator) void { | ||
| 484 | allocator.free(self.directory.blocks); | ||
| 485 | for (self.streams) |*stream| { | ||
| 486 | allocator.free(stream.blocks); | ||
| 487 | } | ||
| 488 | allocator.free(self.streams); | ||
| 489 | } | ||
| 490 | }; | ||
| 491 | |||
| 492 | const MsfStream = struct { | ||
| 493 | in_file: File = undefined, | ||
| 494 | pos: u64 = undefined, | ||
| 495 | blocks: []u32 = undefined, | ||
| 496 | block_size: u32 = undefined, | ||
| 497 | |||
| 498 | pub const Error = @typeInfo(@typeInfo(@TypeOf(read)).Fn.return_type.?).ErrorUnion.error_set; | ||
| 499 | |||
| 500 | fn init(block_size: u32, file: File, blocks: []u32) MsfStream { | ||
| 501 | const stream = MsfStream{ | ||
| 502 | .in_file = file, | ||
| 503 | .pos = 0, | ||
| 504 | .blocks = blocks, | ||
| 505 | .block_size = block_size, | ||
| 506 | }; | ||
| 507 | |||
| 508 | return stream; | ||
| 509 | } | ||
| 510 | |||
| 511 | fn read(self: *MsfStream, buffer: []u8) !usize { | ||
| 512 | var block_id = @as(usize, @intCast(self.pos / self.block_size)); | ||
| 513 | if (block_id >= self.blocks.len) return 0; // End of Stream | ||
| 514 | var block = self.blocks[block_id]; | ||
| 515 | var offset = self.pos % self.block_size; | ||
| 516 | |||
| 517 | try self.in_file.seekTo(block * self.block_size + offset); | ||
| 518 | const in = self.in_file.reader(); | ||
| 519 | |||
| 520 | var size: usize = 0; | ||
| 521 | var rem_buffer = buffer; | ||
| 522 | while (size < buffer.len) { | ||
| 523 | const size_to_read = @min(self.block_size - offset, rem_buffer.len); | ||
| 524 | size += try in.read(rem_buffer[0..size_to_read]); | ||
| 525 | rem_buffer = buffer[size..]; | ||
| 526 | offset += size_to_read; | ||
| 527 | |||
| 528 | // If we're at the end of a block, go to the next one. | ||
| 529 | if (offset == self.block_size) { | ||
| 530 | offset = 0; | ||
| 531 | block_id += 1; | ||
| 532 | if (block_id >= self.blocks.len) break; // End of Stream | ||
| 533 | block = self.blocks[block_id]; | ||
| 534 | try self.in_file.seekTo(block * self.block_size); | ||
| 535 | } | ||
| 536 | } | ||
| 537 | |||
| 538 | self.pos += buffer.len; | ||
| 539 | return buffer.len; | ||
| 540 | } | ||
| 541 | |||
| 542 | pub fn seekBy(self: *MsfStream, len: i64) !void { | ||
| 543 | self.pos = @as(u64, @intCast(@as(i64, @intCast(self.pos)) + len)); | ||
| 544 | if (self.pos >= self.blocks.len * self.block_size) | ||
| 545 | return error.EOF; | ||
| 546 | } | ||
| 547 | |||
| 548 | pub fn seekTo(self: *MsfStream, len: u64) !void { | ||
| 549 | self.pos = len; | ||
| 550 | if (self.pos >= self.blocks.len * self.block_size) | ||
| 551 | return error.EOF; | ||
| 552 | } | ||
| 553 | |||
| 554 | fn getSize(self: *const MsfStream) u64 { | ||
| 555 | return self.blocks.len * self.block_size; | ||
| 556 | } | ||
| 557 | |||
| 558 | fn getFilePos(self: MsfStream) u64 { | ||
| 559 | const block_id = self.pos / self.block_size; | ||
| 560 | const block = self.blocks[block_id]; | ||
| 561 | const offset = self.pos % self.block_size; | ||
| 562 | |||
| 563 | return block * self.block_size + offset; | ||
| 564 | } | ||
| 565 | |||
| 566 | pub fn reader(self: *MsfStream) std.io.Reader(*MsfStream, Error, read) { | ||
| 567 | return .{ .context = self }; | ||
| 568 | } | ||
| 569 | }; | ||
| 570 | |||
| 571 | fn readSparseBitVector(stream: anytype, allocator: Allocator) ![]u32 { | ||
| 572 | const num_words = try stream.readInt(u32, .little); | ||
| 573 | var list = std.ArrayList(u32).init(allocator); | ||
| 574 | errdefer list.deinit(); | ||
| 575 | var word_i: u32 = 0; | ||
| 576 | while (word_i != num_words) : (word_i += 1) { | ||
| 577 | const word = try stream.readInt(u32, .little); | ||
| 578 | var bit_i: u5 = 0; | ||
| 579 | while (true) : (bit_i += 1) { | ||
| 580 | if (word & (@as(u32, 1) << bit_i) != 0) { | ||
| 581 | try list.append(word_i * 32 + bit_i); | ||
| 582 | } | ||
| 583 | if (bit_i == std.math.maxInt(u5)) break; | ||
| 584 | } | ||
| 585 | } | ||
| 586 | return try list.toOwnedSlice(); | ||
| 587 | } | ||
| 588 | |||
| 589 | fn blockCountFromSize(size: u32, block_size: u32) u32 { | ||
| 590 | return (size + block_size - 1) / block_size; | ||
| 591 | } | ||
lib/std/debug/SelfInfo.zig created+2438| ... | @@ -0,0 +1,2438 @@ | ||
| 1 | //! Cross-platform abstraction for this binary's own debug information, with a | ||
| 2 | //! goal of minimal code bloat and compilation speed penalty. | ||
| 3 | |||
| 4 | const builtin = @import("builtin"); | ||
| 5 | const native_os = builtin.os.tag; | ||
| 6 | const native_endian = native_arch.endian(); | ||
| 7 | const native_arch = builtin.cpu.arch; | ||
| 8 | |||
| 9 | const std = @import("../std.zig"); | ||
| 10 | const mem = std.mem; | ||
| 11 | const Allocator = std.mem.Allocator; | ||
| 12 | const windows = std.os.windows; | ||
| 13 | const macho = std.macho; | ||
| 14 | const fs = std.fs; | ||
| 15 | const coff = std.coff; | ||
| 16 | const pdb = std.pdb; | ||
| 17 | const assert = std.debug.assert; | ||
| 18 | const posix = std.posix; | ||
| 19 | const elf = std.elf; | ||
| 20 | const Dwarf = std.debug.Dwarf; | ||
| 21 | const Pdb = std.debug.Pdb; | ||
| 22 | const File = std.fs.File; | ||
| 23 | const math = std.math; | ||
| 24 | const testing = std.testing; | ||
| 25 | const StackIterator = std.debug.StackIterator; | ||
| 26 | const regBytes = Dwarf.abi.regBytes; | ||
| 27 | const regValueNative = Dwarf.abi.regValueNative; | ||
| 28 | |||
| 29 | const SelfInfo = @This(); | ||
| 30 | |||
| 31 | const root = @import("root"); | ||
| 32 | |||
| 33 | allocator: Allocator, | ||
| 34 | address_map: std.AutoHashMap(usize, *Module), | ||
| 35 | modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModule) else void, | ||
| 36 | |||
| 37 | pub const OpenError = error{ | ||
| 38 | MissingDebugInfo, | ||
| 39 | UnsupportedOperatingSystem, | ||
| 40 | } || @typeInfo(@typeInfo(@TypeOf(SelfInfo.init)).Fn.return_type.?).ErrorUnion.error_set; | ||
| 41 | |||
| 42 | pub fn open(allocator: Allocator) OpenError!SelfInfo { | ||
| 43 | nosuspend { | ||
| 44 | if (builtin.strip_debug_info) | ||
| 45 | return error.MissingDebugInfo; | ||
| 46 | switch (native_os) { | ||
| 47 | .linux, | ||
| 48 | .freebsd, | ||
| 49 | .netbsd, | ||
| 50 | .dragonfly, | ||
| 51 | .openbsd, | ||
| 52 | .macos, | ||
| 53 | .solaris, | ||
| 54 | .illumos, | ||
| 55 | .windows, | ||
| 56 | => return try SelfInfo.init(allocator), | ||
| 57 | else => return error.UnsupportedOperatingSystem, | ||
| 58 | } | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | pub fn init(allocator: Allocator) !SelfInfo { | ||
| 63 | var debug_info: SelfInfo = .{ | ||
| 64 | .allocator = allocator, | ||
| 65 | .address_map = std.AutoHashMap(usize, *Module).init(allocator), | ||
| 66 | .modules = if (native_os == .windows) .{} else {}, | ||
| 67 | }; | ||
| 68 | |||
| 69 | if (native_os == .windows) { | ||
| 70 | errdefer debug_info.modules.deinit(allocator); | ||
| 71 | |||
| 72 | const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0); | ||
| 73 | if (handle == windows.INVALID_HANDLE_VALUE) { | ||
| 74 | switch (windows.GetLastError()) { | ||
| 75 | else => |err| return windows.unexpectedError(err), | ||
| 76 | } | ||
| 77 | } | ||
| 78 | defer windows.CloseHandle(handle); | ||
| 79 | |||
| 80 | var module_entry: windows.MODULEENTRY32 = undefined; | ||
| 81 | module_entry.dwSize = @sizeOf(windows.MODULEENTRY32); | ||
| 82 | if (windows.kernel32.Module32First(handle, &module_entry) == 0) { | ||
| 83 | return error.MissingDebugInfo; | ||
| 84 | } | ||
| 85 | |||
| 86 | var module_valid = true; | ||
| 87 | while (module_valid) { | ||
| 88 | const module_info = try debug_info.modules.addOne(allocator); | ||
| 89 | const name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{}; | ||
| 90 | errdefer allocator.free(name); | ||
| 91 | |||
| 92 | module_info.* = .{ | ||
| 93 | .base_address = @intFromPtr(module_entry.modBaseAddr), | ||
| 94 | .size = module_entry.modBaseSize, | ||
| 95 | .name = name, | ||
| 96 | .handle = module_entry.hModule, | ||
| 97 | }; | ||
| 98 | |||
| 99 | module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1; | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | return debug_info; | ||
| 104 | } | ||
| 105 | |||
| 106 | pub fn deinit(self: *SelfInfo) void { | ||
| 107 | var it = self.address_map.iterator(); | ||
| 108 | while (it.next()) |entry| { | ||
| 109 | const mdi = entry.value_ptr.*; | ||
| 110 | mdi.deinit(self.allocator); | ||
| 111 | self.allocator.destroy(mdi); | ||
| 112 | } | ||
| 113 | self.address_map.deinit(); | ||
| 114 | if (native_os == .windows) { | ||
| 115 | for (self.modules.items) |module| { | ||
| 116 | self.allocator.free(module.name); | ||
| 117 | if (module.mapped_file) |mapped_file| mapped_file.deinit(); | ||
| 118 | } | ||
| 119 | self.modules.deinit(self.allocator); | ||
| 120 | } | ||
| 121 | } | ||
| 122 | |||
| 123 | pub fn getModuleForAddress(self: *SelfInfo, address: usize) !*Module { | ||
| 124 | if (comptime builtin.target.isDarwin()) { | ||
| 125 | return self.lookupModuleDyld(address); | ||
| 126 | } else if (native_os == .windows) { | ||
| 127 | return self.lookupModuleWin32(address); | ||
| 128 | } else if (native_os == .haiku) { | ||
| 129 | return self.lookupModuleHaiku(address); | ||
| 130 | } else if (comptime builtin.target.isWasm()) { | ||
| 131 | return self.lookupModuleWasm(address); | ||
| 132 | } else { | ||
| 133 | return self.lookupModuleDl(address); | ||
| 134 | } | ||
| 135 | } | ||
| 136 | |||
| 137 | // Returns the module name for a given address. | ||
| 138 | // This can be called when getModuleForAddress fails, so implementations should provide | ||
| 139 | // a path that doesn't rely on any side-effects of a prior successful module lookup. | ||
| 140 | pub fn getModuleNameForAddress(self: *SelfInfo, address: usize) ?[]const u8 { | ||
| 141 | if (comptime builtin.target.isDarwin()) { | ||
| 142 | return self.lookupModuleNameDyld(address); | ||
| 143 | } else if (native_os == .windows) { | ||
| 144 | return self.lookupModuleNameWin32(address); | ||
| 145 | } else if (native_os == .haiku) { | ||
| 146 | return null; | ||
| 147 | } else if (comptime builtin.target.isWasm()) { | ||
| 148 | return null; | ||
| 149 | } else { | ||
| 150 | return self.lookupModuleNameDl(address); | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | fn lookupModuleDyld(self: *SelfInfo, address: usize) !*Module { | ||
| 155 | const image_count = std.c._dyld_image_count(); | ||
| 156 | |||
| 157 | var i: u32 = 0; | ||
| 158 | while (i < image_count) : (i += 1) { | ||
| 159 | const header = std.c._dyld_get_image_header(i) orelse continue; | ||
| 160 | const base_address = @intFromPtr(header); | ||
| 161 | if (address < base_address) continue; | ||
| 162 | const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i); | ||
| 163 | |||
| 164 | var it = macho.LoadCommandIterator{ | ||
| 165 | .ncmds = header.ncmds, | ||
| 166 | .buffer = @alignCast(@as( | ||
| 167 | [*]u8, | ||
| 168 | @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)), | ||
| 169 | )[0..header.sizeofcmds]), | ||
| 170 | }; | ||
| 171 | |||
| 172 | var unwind_info: ?[]const u8 = null; | ||
| 173 | var eh_frame: ?[]const u8 = null; | ||
| 174 | while (it.next()) |cmd| switch (cmd.cmd()) { | ||
| 175 | .SEGMENT_64 => { | ||
| 176 | const segment_cmd = cmd.cast(macho.segment_command_64).?; | ||
| 177 | if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue; | ||
| 178 | |||
| 179 | const seg_start = segment_cmd.vmaddr + vmaddr_slide; | ||
| 180 | const seg_end = seg_start + segment_cmd.vmsize; | ||
| 181 | if (address >= seg_start and address < seg_end) { | ||
| 182 | if (self.address_map.get(base_address)) |obj_di| { | ||
| 183 | return obj_di; | ||
| 184 | } | ||
| 185 | |||
| 186 | for (cmd.getSections()) |sect| { | ||
| 187 | if (mem.eql(u8, "__unwind_info", sect.sectName())) { | ||
| 188 | unwind_info = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size]; | ||
| 189 | } else if (mem.eql(u8, "__eh_frame", sect.sectName())) { | ||
| 190 | eh_frame = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size]; | ||
| 191 | } | ||
| 192 | } | ||
| 193 | |||
| 194 | const obj_di = try self.allocator.create(Module); | ||
| 195 | errdefer self.allocator.destroy(obj_di); | ||
| 196 | |||
| 197 | const macho_path = mem.sliceTo(std.c._dyld_get_image_name(i), 0); | ||
| 198 | const macho_file = fs.cwd().openFile(macho_path, .{}) catch |err| switch (err) { | ||
| 199 | error.FileNotFound => return error.MissingDebugInfo, | ||
| 200 | else => return err, | ||
| 201 | }; | ||
| 202 | obj_di.* = try readMachODebugInfo(self.allocator, macho_file); | ||
| 203 | obj_di.base_address = base_address; | ||
| 204 | obj_di.vmaddr_slide = vmaddr_slide; | ||
| 205 | obj_di.unwind_info = unwind_info; | ||
| 206 | obj_di.eh_frame = eh_frame; | ||
| 207 | |||
| 208 | try self.address_map.putNoClobber(base_address, obj_di); | ||
| 209 | |||
| 210 | return obj_di; | ||
| 211 | } | ||
| 212 | }, | ||
| 213 | else => {}, | ||
| 214 | }; | ||
| 215 | } | ||
| 216 | |||
| 217 | return error.MissingDebugInfo; | ||
| 218 | } | ||
| 219 | |||
| 220 | fn lookupModuleNameDyld(self: *SelfInfo, address: usize) ?[]const u8 { | ||
| 221 | _ = self; | ||
| 222 | const image_count = std.c._dyld_image_count(); | ||
| 223 | |||
| 224 | var i: u32 = 0; | ||
| 225 | while (i < image_count) : (i += 1) { | ||
| 226 | const header = std.c._dyld_get_image_header(i) orelse continue; | ||
| 227 | const base_address = @intFromPtr(header); | ||
| 228 | if (address < base_address) continue; | ||
| 229 | const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i); | ||
| 230 | |||
| 231 | var it = macho.LoadCommandIterator{ | ||
| 232 | .ncmds = header.ncmds, | ||
| 233 | .buffer = @alignCast(@as( | ||
| 234 | [*]u8, | ||
| 235 | @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)), | ||
| 236 | )[0..header.sizeofcmds]), | ||
| 237 | }; | ||
| 238 | |||
| 239 | while (it.next()) |cmd| switch (cmd.cmd()) { | ||
| 240 | .SEGMENT_64 => { | ||
| 241 | const segment_cmd = cmd.cast(macho.segment_command_64).?; | ||
| 242 | if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue; | ||
| 243 | |||
| 244 | const original_address = address - vmaddr_slide; | ||
| 245 | const seg_start = segment_cmd.vmaddr; | ||
| 246 | const seg_end = seg_start + segment_cmd.vmsize; | ||
| 247 | if (original_address >= seg_start and original_address < seg_end) { | ||
| 248 | return fs.path.basename(mem.sliceTo(std.c._dyld_get_image_name(i), 0)); | ||
| 249 | } | ||
| 250 | }, | ||
| 251 | else => {}, | ||
| 252 | }; | ||
| 253 | } | ||
| 254 | |||
| 255 | return null; | ||
| 256 | } | ||
| 257 | |||
| 258 | fn lookupModuleWin32(self: *SelfInfo, address: usize) !*Module { | ||
| 259 | for (self.modules.items) |*module| { | ||
| 260 | if (address >= module.base_address and address < module.base_address + module.size) { | ||
| 261 | if (self.address_map.get(module.base_address)) |obj_di| { | ||
| 262 | return obj_di; | ||
| 263 | } | ||
| 264 | |||
| 265 | const obj_di = try self.allocator.create(Module); | ||
| 266 | errdefer self.allocator.destroy(obj_di); | ||
| 267 | |||
| 268 | const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size]; | ||
| 269 | var coff_obj = try coff.Coff.init(mapped_module, true); | ||
| 270 | |||
| 271 | // The string table is not mapped into memory by the loader, so if a section name is in the | ||
| 272 | // string table then we have to map the full image file from disk. This can happen when | ||
| 273 | // a binary is produced with -gdwarf, since the section names are longer than 8 bytes. | ||
| 274 | if (coff_obj.strtabRequired()) { | ||
| 275 | var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined; | ||
| 276 | // openFileAbsoluteW requires the prefix to be present | ||
| 277 | @memcpy(name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' }); | ||
| 278 | |||
| 279 | const process_handle = windows.GetCurrentProcess(); | ||
| 280 | const len = windows.kernel32.GetModuleFileNameExW( | ||
| 281 | process_handle, | ||
| 282 | module.handle, | ||
| 283 | @ptrCast(&name_buffer[4]), | ||
| 284 | windows.PATH_MAX_WIDE, | ||
| 285 | ); | ||
| 286 | |||
| 287 | if (len == 0) return error.MissingDebugInfo; | ||
| 288 | const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) { | ||
| 289 | error.FileNotFound => return error.MissingDebugInfo, | ||
| 290 | else => return err, | ||
| 291 | }; | ||
| 292 | errdefer coff_file.close(); | ||
| 293 | |||
| 294 | var section_handle: windows.HANDLE = undefined; | ||
| 295 | const create_section_rc = windows.ntdll.NtCreateSection( | ||
| 296 | &section_handle, | ||
| 297 | windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ, | ||
| 298 | null, | ||
| 299 | null, | ||
| 300 | windows.PAGE_READONLY, | ||
| 301 | // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default. | ||
| 302 | // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6. | ||
| 303 | windows.SEC_COMMIT, | ||
| 304 | coff_file.handle, | ||
| 305 | ); | ||
| 306 | if (create_section_rc != .SUCCESS) return error.MissingDebugInfo; | ||
| 307 | errdefer windows.CloseHandle(section_handle); | ||
| 308 | |||
| 309 | var coff_len: usize = 0; | ||
| 310 | var base_ptr: usize = 0; | ||
| 311 | const map_section_rc = windows.ntdll.NtMapViewOfSection( | ||
| 312 | section_handle, | ||
| 313 | process_handle, | ||
| 314 | @ptrCast(&base_ptr), | ||
| 315 | null, | ||
| 316 | 0, | ||
| 317 | null, | ||
| 318 | &coff_len, | ||
| 319 | .ViewUnmap, | ||
| 320 | 0, | ||
| 321 | windows.PAGE_READONLY, | ||
| 322 | ); | ||
| 323 | if (map_section_rc != .SUCCESS) return error.MissingDebugInfo; | ||
| 324 | errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrFromInt(base_ptr)) == .SUCCESS); | ||
| 325 | |||
| 326 | const section_view = @as([*]const u8, @ptrFromInt(base_ptr))[0..coff_len]; | ||
| 327 | coff_obj = try coff.Coff.init(section_view, false); | ||
| 328 | |||
| 329 | module.mapped_file = .{ | ||
| 330 | .file = coff_file, | ||
| 331 | .section_handle = section_handle, | ||
| 332 | .section_view = section_view, | ||
| 333 | }; | ||
| 334 | } | ||
| 335 | errdefer if (module.mapped_file) |mapped_file| mapped_file.deinit(); | ||
| 336 | |||
| 337 | obj_di.* = try readCoffDebugInfo(self.allocator, &coff_obj); | ||
| 338 | obj_di.base_address = module.base_address; | ||
| 339 | |||
| 340 | try self.address_map.putNoClobber(module.base_address, obj_di); | ||
| 341 | return obj_di; | ||
| 342 | } | ||
| 343 | } | ||
| 344 | |||
| 345 | return error.MissingDebugInfo; | ||
| 346 | } | ||
| 347 | |||
| 348 | fn lookupModuleNameWin32(self: *SelfInfo, address: usize) ?[]const u8 { | ||
| 349 | for (self.modules.items) |module| { | ||
| 350 | if (address >= module.base_address and address < module.base_address + module.size) { | ||
| 351 | return module.name; | ||
| 352 | } | ||
| 353 | } | ||
| 354 | return null; | ||
| 355 | } | ||
| 356 | |||
| 357 | fn lookupModuleNameDl(self: *SelfInfo, address: usize) ?[]const u8 { | ||
| 358 | _ = self; | ||
| 359 | |||
| 360 | var ctx: struct { | ||
| 361 | // Input | ||
| 362 | address: usize, | ||
| 363 | // Output | ||
| 364 | name: []const u8 = "", | ||
| 365 | } = .{ .address = address }; | ||
| 366 | const CtxTy = @TypeOf(ctx); | ||
| 367 | |||
| 368 | if (posix.dl_iterate_phdr(&ctx, error{Found}, struct { | ||
| 369 | fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void { | ||
| 370 | _ = size; | ||
| 371 | if (context.address < info.addr) return; | ||
| 372 | const phdrs = info.phdr[0..info.phnum]; | ||
| 373 | for (phdrs) |*phdr| { | ||
| 374 | if (phdr.p_type != elf.PT_LOAD) continue; | ||
| 375 | |||
| 376 | const seg_start = info.addr +% phdr.p_vaddr; | ||
| 377 | const seg_end = seg_start + phdr.p_memsz; | ||
| 378 | if (context.address >= seg_start and context.address < seg_end) { | ||
| 379 | context.name = mem.sliceTo(info.name, 0) orelse ""; | ||
| 380 | break; | ||
| 381 | } | ||
| 382 | } else return; | ||
| 383 | |||
| 384 | return error.Found; | ||
| 385 | } | ||
| 386 | }.callback)) { | ||
| 387 | return null; | ||
| 388 | } else |err| switch (err) { | ||
| 389 | error.Found => return fs.path.basename(ctx.name), | ||
| 390 | } | ||
| 391 | |||
| 392 | return null; | ||
| 393 | } | ||
| 394 | |||
| 395 | fn lookupModuleDl(self: *SelfInfo, address: usize) !*Module { | ||
| 396 | var ctx: struct { | ||
| 397 | // Input | ||
| 398 | address: usize, | ||
| 399 | // Output | ||
| 400 | base_address: usize = undefined, | ||
| 401 | name: []const u8 = undefined, | ||
| 402 | build_id: ?[]const u8 = null, | ||
| 403 | gnu_eh_frame: ?[]const u8 = null, | ||
| 404 | } = .{ .address = address }; | ||
| 405 | const CtxTy = @TypeOf(ctx); | ||
| 406 | |||
| 407 | if (posix.dl_iterate_phdr(&ctx, error{Found}, struct { | ||
| 408 | fn callback(info: *posix.dl_phdr_info, size: usize, context: *CtxTy) !void { | ||
| 409 | _ = size; | ||
| 410 | // The base address is too high | ||
| 411 | if (context.address < info.addr) | ||
| 412 | return; | ||
| 413 | |||
| 414 | const phdrs = info.phdr[0..info.phnum]; | ||
| 415 | for (phdrs) |*phdr| { | ||
| 416 | if (phdr.p_type != elf.PT_LOAD) continue; | ||
| 417 | |||
| 418 | // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000 | ||
| 419 | const seg_start = info.addr +% phdr.p_vaddr; | ||
| 420 | const seg_end = seg_start + phdr.p_memsz; | ||
| 421 | if (context.address >= seg_start and context.address < seg_end) { | ||
| 422 | // Android libc uses NULL instead of an empty string to mark the | ||
| 423 | // main program | ||
| 424 | context.name = mem.sliceTo(info.name, 0) orelse ""; | ||
| 425 | context.base_address = info.addr; | ||
| 426 | break; | ||
| 427 | } | ||
| 428 | } else return; | ||
| 429 | |||
| 430 | for (info.phdr[0..info.phnum]) |phdr| { | ||
| 431 | switch (phdr.p_type) { | ||
| 432 | elf.PT_NOTE => { | ||
| 433 | // Look for .note.gnu.build-id | ||
| 434 | const note_bytes = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz]; | ||
| 435 | const name_size = mem.readInt(u32, note_bytes[0..4], native_endian); | ||
| 436 | if (name_size != 4) continue; | ||
| 437 | const desc_size = mem.readInt(u32, note_bytes[4..8], native_endian); | ||
| 438 | const note_type = mem.readInt(u32, note_bytes[8..12], native_endian); | ||
| 439 | if (note_type != elf.NT_GNU_BUILD_ID) continue; | ||
| 440 | if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue; | ||
| 441 | context.build_id = note_bytes[16..][0..desc_size]; | ||
| 442 | }, | ||
| 443 | elf.PT_GNU_EH_FRAME => { | ||
| 444 | context.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.addr + phdr.p_vaddr))[0..phdr.p_memsz]; | ||
| 445 | }, | ||
| 446 | else => {}, | ||
| 447 | } | ||
| 448 | } | ||
| 449 | |||
| 450 | // Stop the iteration | ||
| 451 | return error.Found; | ||
| 452 | } | ||
| 453 | }.callback)) { | ||
| 454 | return error.MissingDebugInfo; | ||
| 455 | } else |err| switch (err) { | ||
| 456 | error.Found => {}, | ||
| 457 | } | ||
| 458 | |||
| 459 | if (self.address_map.get(ctx.base_address)) |obj_di| { | ||
| 460 | return obj_di; | ||
| 461 | } | ||
| 462 | |||
| 463 | const obj_di = try self.allocator.create(Module); | ||
| 464 | errdefer self.allocator.destroy(obj_di); | ||
| 465 | |||
| 466 | var sections: Dwarf.SectionArray = Dwarf.null_section_array; | ||
| 467 | if (ctx.gnu_eh_frame) |eh_frame_hdr| { | ||
| 468 | // This is a special case - pointer offsets inside .eh_frame_hdr | ||
| 469 | // are encoded relative to its base address, so we must use the | ||
| 470 | // version that is already memory mapped, and not the one that | ||
| 471 | // will be mapped separately from the ELF file. | ||
| 472 | sections[@intFromEnum(Dwarf.Section.Id.eh_frame_hdr)] = .{ | ||
| 473 | .data = eh_frame_hdr, | ||
| 474 | .owned = false, | ||
| 475 | }; | ||
| 476 | } | ||
| 477 | |||
| 478 | obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, &sections, null); | ||
| 479 | obj_di.base_address = ctx.base_address; | ||
| 480 | |||
| 481 | // Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding | ||
| 482 | obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.base_address) catch {}; | ||
| 483 | |||
| 484 | try self.address_map.putNoClobber(ctx.base_address, obj_di); | ||
| 485 | |||
| 486 | return obj_di; | ||
| 487 | } | ||
| 488 | |||
| 489 | fn lookupModuleHaiku(self: *SelfInfo, address: usize) !*Module { | ||
| 490 | _ = self; | ||
| 491 | _ = address; | ||
| 492 | @panic("TODO implement lookup module for Haiku"); | ||
| 493 | } | ||
| 494 | |||
| 495 | fn lookupModuleWasm(self: *SelfInfo, address: usize) !*Module { | ||
| 496 | _ = self; | ||
| 497 | _ = address; | ||
| 498 | @panic("TODO implement lookup module for Wasm"); | ||
| 499 | } | ||
| 500 | |||
| 501 | pub const Module = switch (native_os) { | ||
| 502 | .macos, .ios, .watchos, .tvos, .visionos => struct { | ||
| 503 | base_address: usize, | ||
| 504 | vmaddr_slide: usize, | ||
| 505 | mapped_memory: []align(mem.page_size) const u8, | ||
| 506 | symbols: []const MachoSymbol, | ||
| 507 | strings: [:0]const u8, | ||
| 508 | ofiles: OFileTable, | ||
| 509 | |||
| 510 | // Backed by the in-memory sections mapped by the loader | ||
| 511 | unwind_info: ?[]const u8 = null, | ||
| 512 | eh_frame: ?[]const u8 = null, | ||
| 513 | |||
| 514 | const OFileTable = std.StringHashMap(OFileInfo); | ||
| 515 | const OFileInfo = struct { | ||
| 516 | di: Dwarf, | ||
| 517 | addr_table: std.StringHashMap(u64), | ||
| 518 | }; | ||
| 519 | |||
| 520 | pub fn deinit(self: *@This(), allocator: Allocator) void { | ||
| 521 | var it = self.ofiles.iterator(); | ||
| 522 | while (it.next()) |entry| { | ||
| 523 | const ofile = entry.value_ptr; | ||
| 524 | ofile.di.deinit(allocator); | ||
| 525 | ofile.addr_table.deinit(); | ||
| 526 | } | ||
| 527 | self.ofiles.deinit(); | ||
| 528 | allocator.free(self.symbols); | ||
| 529 | posix.munmap(self.mapped_memory); | ||
| 530 | } | ||
| 531 | |||
| 532 | fn loadOFile(self: *@This(), allocator: Allocator, o_file_path: []const u8) !*OFileInfo { | ||
| 533 | const o_file = try fs.cwd().openFile(o_file_path, .{}); | ||
| 534 | const mapped_mem = try mapWholeFile(o_file); | ||
| 535 | |||
| 536 | const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr)); | ||
| 537 | if (hdr.magic != std.macho.MH_MAGIC_64) | ||
| 538 | return error.InvalidDebugInfo; | ||
| 539 | |||
| 540 | var segcmd: ?macho.LoadCommandIterator.LoadCommand = null; | ||
| 541 | var symtabcmd: ?macho.symtab_command = null; | ||
| 542 | var it = macho.LoadCommandIterator{ | ||
| 543 | .ncmds = hdr.ncmds, | ||
| 544 | .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], | ||
| 545 | }; | ||
| 546 | while (it.next()) |cmd| switch (cmd.cmd()) { | ||
| 547 | .SEGMENT_64 => segcmd = cmd, | ||
| 548 | .SYMTAB => symtabcmd = cmd.cast(macho.symtab_command).?, | ||
| 549 | else => {}, | ||
| 550 | }; | ||
| 551 | |||
| 552 | if (segcmd == null or symtabcmd == null) return error.MissingDebugInfo; | ||
| 553 | |||
| 554 | // Parse symbols | ||
| 555 | const strtab = @as( | ||
| 556 | [*]const u8, | ||
| 557 | @ptrCast(&mapped_mem[symtabcmd.?.stroff]), | ||
| 558 | )[0 .. symtabcmd.?.strsize - 1 :0]; | ||
| 559 | const symtab = @as( | ||
| 560 | [*]const macho.nlist_64, | ||
| 561 | @ptrCast(@alignCast(&mapped_mem[symtabcmd.?.symoff])), | ||
| 562 | )[0..symtabcmd.?.nsyms]; | ||
| 563 | |||
| 564 | // TODO handle tentative (common) symbols | ||
| 565 | var addr_table = std.StringHashMap(u64).init(allocator); | ||
| 566 | try addr_table.ensureTotalCapacity(@as(u32, @intCast(symtab.len))); | ||
| 567 | for (symtab) |sym| { | ||
| 568 | if (sym.n_strx == 0) continue; | ||
| 569 | if (sym.undf() or sym.tentative() or sym.abs()) continue; | ||
| 570 | const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0); | ||
| 571 | // TODO is it possible to have a symbol collision? | ||
| 572 | addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value); | ||
| 573 | } | ||
| 574 | |||
| 575 | var sections: Dwarf.SectionArray = Dwarf.null_section_array; | ||
| 576 | if (self.eh_frame) |eh_frame| sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{ | ||
| 577 | .data = eh_frame, | ||
| 578 | .owned = false, | ||
| 579 | }; | ||
| 580 | |||
| 581 | for (segcmd.?.getSections()) |sect| { | ||
| 582 | if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue; | ||
| 583 | |||
| 584 | var section_index: ?usize = null; | ||
| 585 | inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| { | ||
| 586 | if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i; | ||
| 587 | } | ||
| 588 | if (section_index == null) continue; | ||
| 589 | |||
| 590 | const section_bytes = try chopSlice(mapped_mem, sect.offset, sect.size); | ||
| 591 | sections[section_index.?] = .{ | ||
| 592 | .data = section_bytes, | ||
| 593 | .virtual_address = sect.addr, | ||
| 594 | .owned = false, | ||
| 595 | }; | ||
| 596 | } | ||
| 597 | |||
| 598 | const missing_debug_info = | ||
| 599 | sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or | ||
| 600 | sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or | ||
| 601 | sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or | ||
| 602 | sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; | ||
| 603 | if (missing_debug_info) return error.MissingDebugInfo; | ||
| 604 | |||
| 605 | var di = Dwarf{ | ||
| 606 | .endian = .little, | ||
| 607 | .sections = sections, | ||
| 608 | .is_macho = true, | ||
| 609 | }; | ||
| 610 | |||
| 611 | try Dwarf.open(&di, allocator); | ||
| 612 | const info = OFileInfo{ | ||
| 613 | .di = di, | ||
| 614 | .addr_table = addr_table, | ||
| 615 | }; | ||
| 616 | |||
| 617 | // Add the debug info to the cache | ||
| 618 | const result = try self.ofiles.getOrPut(o_file_path); | ||
| 619 | assert(!result.found_existing); | ||
| 620 | result.value_ptr.* = info; | ||
| 621 | |||
| 622 | return result.value_ptr; | ||
| 623 | } | ||
| 624 | |||
| 625 | pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo { | ||
| 626 | nosuspend { | ||
| 627 | const result = try self.getOFileInfoForAddress(allocator, address); | ||
| 628 | if (result.symbol == null) return .{}; | ||
| 629 | |||
| 630 | // Take the symbol name from the N_FUN STAB entry, we're going to | ||
| 631 | // use it if we fail to find the DWARF infos | ||
| 632 | const stab_symbol = mem.sliceTo(self.strings[result.symbol.?.strx..], 0); | ||
| 633 | if (result.o_file_info == null) return .{ .symbol_name = stab_symbol }; | ||
| 634 | |||
| 635 | // Translate again the address, this time into an address inside the | ||
| 636 | // .o file | ||
| 637 | const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{ | ||
| 638 | .symbol_name = "???", | ||
| 639 | }; | ||
| 640 | |||
| 641 | const addr_off = result.relocated_address - result.symbol.?.addr; | ||
| 642 | const o_file_di = &result.o_file_info.?.di; | ||
| 643 | if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| { | ||
| 644 | return SymbolInfo{ | ||
| 645 | .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???", | ||
| 646 | .compile_unit_name = compile_unit.die.getAttrString( | ||
| 647 | o_file_di, | ||
| 648 | std.dwarf.AT.name, | ||
| 649 | o_file_di.section(.debug_str), | ||
| 650 | compile_unit.*, | ||
| 651 | ) catch |err| switch (err) { | ||
| 652 | error.MissingDebugInfo, error.InvalidDebugInfo => "???", | ||
| 653 | }, | ||
| 654 | .line_info = o_file_di.getLineNumberInfo( | ||
| 655 | allocator, | ||
| 656 | compile_unit.*, | ||
| 657 | relocated_address_o + addr_off, | ||
| 658 | ) catch |err| switch (err) { | ||
| 659 | error.MissingDebugInfo, error.InvalidDebugInfo => null, | ||
| 660 | else => return err, | ||
| 661 | }, | ||
| 662 | }; | ||
| 663 | } else |err| switch (err) { | ||
| 664 | error.MissingDebugInfo, error.InvalidDebugInfo => { | ||
| 665 | return SymbolInfo{ .symbol_name = stab_symbol }; | ||
| 666 | }, | ||
| 667 | else => return err, | ||
| 668 | } | ||
| 669 | } | ||
| 670 | } | ||
| 671 | |||
| 672 | pub fn getOFileInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !struct { | ||
| 673 | relocated_address: usize, | ||
| 674 | symbol: ?*const MachoSymbol = null, | ||
| 675 | o_file_info: ?*OFileInfo = null, | ||
| 676 | } { | ||
| 677 | nosuspend { | ||
| 678 | // Translate the VA into an address into this object | ||
| 679 | const relocated_address = address - self.vmaddr_slide; | ||
| 680 | |||
| 681 | // Find the .o file where this symbol is defined | ||
| 682 | const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse return .{ | ||
| 683 | .relocated_address = relocated_address, | ||
| 684 | }; | ||
| 685 | |||
| 686 | // Check if its debug infos are already in the cache | ||
| 687 | const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0); | ||
| 688 | const o_file_info = self.ofiles.getPtr(o_file_path) orelse | ||
| 689 | (self.loadOFile(allocator, o_file_path) catch |err| switch (err) { | ||
| 690 | error.FileNotFound, | ||
| 691 | error.MissingDebugInfo, | ||
| 692 | error.InvalidDebugInfo, | ||
| 693 | => return .{ | ||
| 694 | .relocated_address = relocated_address, | ||
| 695 | .symbol = symbol, | ||
| 696 | }, | ||
| 697 | else => return err, | ||
| 698 | }); | ||
| 699 | |||
| 700 | return .{ | ||
| 701 | .relocated_address = relocated_address, | ||
| 702 | .symbol = symbol, | ||
| 703 | .o_file_info = o_file_info, | ||
| 704 | }; | ||
| 705 | } | ||
| 706 | } | ||
| 707 | |||
| 708 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf { | ||
| 709 | return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null; | ||
| 710 | } | ||
| 711 | }, | ||
| 712 | .uefi, .windows => struct { | ||
| 713 | base_address: usize, | ||
| 714 | pdb: ?Pdb = null, | ||
| 715 | dwarf: ?Dwarf = null, | ||
| 716 | coff_image_base: u64, | ||
| 717 | |||
| 718 | /// Only used if pdb is non-null | ||
| 719 | coff_section_headers: []coff.SectionHeader, | ||
| 720 | |||
| 721 | pub fn deinit(self: *@This(), allocator: Allocator) void { | ||
| 722 | if (self.dwarf) |*dwarf| { | ||
| 723 | dwarf.deinit(allocator); | ||
| 724 | } | ||
| 725 | |||
| 726 | if (self.pdb) |*p| { | ||
| 727 | p.deinit(); | ||
| 728 | allocator.free(self.coff_section_headers); | ||
| 729 | } | ||
| 730 | } | ||
| 731 | |||
| 732 | fn getSymbolFromPdb(self: *@This(), relocated_address: usize) !?SymbolInfo { | ||
| 733 | var coff_section: *align(1) const coff.SectionHeader = undefined; | ||
| 734 | const mod_index = for (self.pdb.?.sect_contribs) |sect_contrib| { | ||
| 735 | if (sect_contrib.Section > self.coff_section_headers.len) continue; | ||
| 736 | // Remember that SectionContribEntry.Section is 1-based. | ||
| 737 | coff_section = &self.coff_section_headers[sect_contrib.Section - 1]; | ||
| 738 | |||
| 739 | const vaddr_start = coff_section.virtual_address + sect_contrib.Offset; | ||
| 740 | const vaddr_end = vaddr_start + sect_contrib.Size; | ||
| 741 | if (relocated_address >= vaddr_start and relocated_address < vaddr_end) { | ||
| 742 | break sect_contrib.ModuleIndex; | ||
| 743 | } | ||
| 744 | } else { | ||
| 745 | // we have no information to add to the address | ||
| 746 | return null; | ||
| 747 | }; | ||
| 748 | |||
| 749 | const module = (try self.pdb.?.getModule(mod_index)) orelse | ||
| 750 | return error.InvalidDebugInfo; | ||
| 751 | const obj_basename = fs.path.basename(module.obj_file_name); | ||
| 752 | |||
| 753 | const symbol_name = self.pdb.?.getSymbolName( | ||
| 754 | module, | ||
| 755 | relocated_address - coff_section.virtual_address, | ||
| 756 | ) orelse "???"; | ||
| 757 | const opt_line_info = try self.pdb.?.getLineNumberInfo( | ||
| 758 | module, | ||
| 759 | relocated_address - coff_section.virtual_address, | ||
| 760 | ); | ||
| 761 | |||
| 762 | return SymbolInfo{ | ||
| 763 | .symbol_name = symbol_name, | ||
| 764 | .compile_unit_name = obj_basename, | ||
| 765 | .line_info = opt_line_info, | ||
| 766 | }; | ||
| 767 | } | ||
| 768 | |||
| 769 | pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo { | ||
| 770 | // Translate the VA into an address into this object | ||
| 771 | const relocated_address = address - self.base_address; | ||
| 772 | |||
| 773 | if (self.pdb != null) { | ||
| 774 | if (try self.getSymbolFromPdb(relocated_address)) |symbol| return symbol; | ||
| 775 | } | ||
| 776 | |||
| 777 | if (self.dwarf) |*dwarf| { | ||
| 778 | const dwarf_address = relocated_address + self.coff_image_base; | ||
| 779 | return getSymbolFromDwarf(allocator, dwarf_address, dwarf); | ||
| 780 | } | ||
| 781 | |||
| 782 | return SymbolInfo{}; | ||
| 783 | } | ||
| 784 | |||
| 785 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf { | ||
| 786 | _ = allocator; | ||
| 787 | _ = address; | ||
| 788 | |||
| 789 | return switch (self.debug_data) { | ||
| 790 | .dwarf => |*dwarf| dwarf, | ||
| 791 | else => null, | ||
| 792 | }; | ||
| 793 | } | ||
| 794 | }, | ||
| 795 | .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct { | ||
| 796 | base_address: usize, | ||
| 797 | dwarf: Dwarf, | ||
| 798 | mapped_memory: []align(mem.page_size) const u8, | ||
| 799 | external_mapped_memory: ?[]align(mem.page_size) const u8, | ||
| 800 | |||
| 801 | pub fn deinit(self: *@This(), allocator: Allocator) void { | ||
| 802 | self.dwarf.deinit(allocator); | ||
| 803 | posix.munmap(self.mapped_memory); | ||
| 804 | if (self.external_mapped_memory) |m| posix.munmap(m); | ||
| 805 | } | ||
| 806 | |||
| 807 | pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo { | ||
| 808 | // Translate the VA into an address into this object | ||
| 809 | const relocated_address = address - self.base_address; | ||
| 810 | return getSymbolFromDwarf(allocator, relocated_address, &self.dwarf); | ||
| 811 | } | ||
| 812 | |||
| 813 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf { | ||
| 814 | _ = allocator; | ||
| 815 | _ = address; | ||
| 816 | return &self.dwarf; | ||
| 817 | } | ||
| 818 | }, | ||
| 819 | .wasi, .emscripten => struct { | ||
| 820 | pub fn deinit(self: *@This(), allocator: Allocator) void { | ||
| 821 | _ = self; | ||
| 822 | _ = allocator; | ||
| 823 | } | ||
| 824 | |||
| 825 | pub fn getSymbolAtAddress(self: *@This(), allocator: Allocator, address: usize) !SymbolInfo { | ||
| 826 | _ = self; | ||
| 827 | _ = allocator; | ||
| 828 | _ = address; | ||
| 829 | return SymbolInfo{}; | ||
| 830 | } | ||
| 831 | |||
| 832 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: Allocator, address: usize) !?*const Dwarf { | ||
| 833 | _ = self; | ||
| 834 | _ = allocator; | ||
| 835 | _ = address; | ||
| 836 | return null; | ||
| 837 | } | ||
| 838 | }, | ||
| 839 | else => Dwarf, | ||
| 840 | }; | ||
| 841 | |||
| 842 | /// How is this different than `Module` when the host is Windows? | ||
| 843 | /// Why are both stored in the `SelfInfo` struct? | ||
| 844 | /// Boy, it sure would be nice if someone added documentation comments for this | ||
| 845 | /// struct explaining it. | ||
| 846 | pub const WindowsModule = struct { | ||
| 847 | base_address: usize, | ||
| 848 | size: u32, | ||
| 849 | name: []const u8, | ||
| 850 | handle: windows.HMODULE, | ||
| 851 | |||
| 852 | // Set when the image file needed to be mapped from disk | ||
| 853 | mapped_file: ?struct { | ||
| 854 | file: File, | ||
| 855 | section_handle: windows.HANDLE, | ||
| 856 | section_view: []const u8, | ||
| 857 | |||
| 858 | pub fn deinit(self: @This()) void { | ||
| 859 | const process_handle = windows.GetCurrentProcess(); | ||
| 860 | assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(@ptrCast(self.section_view.ptr))) == .SUCCESS); | ||
| 861 | windows.CloseHandle(self.section_handle); | ||
| 862 | self.file.close(); | ||
| 863 | } | ||
| 864 | } = null, | ||
| 865 | }; | ||
| 866 | |||
| 867 | /// This takes ownership of macho_file: users of this function should not close | ||
| 868 | /// it themselves, even on error. | ||
| 869 | /// TODO it's weird to take ownership even on error, rework this code. | ||
| 870 | fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module { | ||
| 871 | const mapped_mem = try mapWholeFile(macho_file); | ||
| 872 | |||
| 873 | const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr)); | ||
| 874 | if (hdr.magic != macho.MH_MAGIC_64) | ||
| 875 | return error.InvalidDebugInfo; | ||
| 876 | |||
| 877 | var it = macho.LoadCommandIterator{ | ||
| 878 | .ncmds = hdr.ncmds, | ||
| 879 | .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], | ||
| 880 | }; | ||
| 881 | const symtab = while (it.next()) |cmd| switch (cmd.cmd()) { | ||
| 882 | .SYMTAB => break cmd.cast(macho.symtab_command).?, | ||
| 883 | else => {}, | ||
| 884 | } else return error.MissingDebugInfo; | ||
| 885 | |||
| 886 | const syms = @as( | ||
| 887 | [*]const macho.nlist_64, | ||
| 888 | @ptrCast(@alignCast(&mapped_mem[symtab.symoff])), | ||
| 889 | )[0..symtab.nsyms]; | ||
| 890 | const strings = mapped_mem[symtab.stroff..][0 .. symtab.strsize - 1 :0]; | ||
| 891 | |||
| 892 | const symbols_buf = try allocator.alloc(MachoSymbol, syms.len); | ||
| 893 | |||
| 894 | var ofile: u32 = undefined; | ||
| 895 | var last_sym: MachoSymbol = undefined; | ||
| 896 | var symbol_index: usize = 0; | ||
| 897 | var state: enum { | ||
| 898 | init, | ||
| 899 | oso_open, | ||
| 900 | oso_close, | ||
| 901 | bnsym, | ||
| 902 | fun_strx, | ||
| 903 | fun_size, | ||
| 904 | ensym, | ||
| 905 | } = .init; | ||
| 906 | |||
| 907 | for (syms) |*sym| { | ||
| 908 | if (!sym.stab()) continue; | ||
| 909 | |||
| 910 | // TODO handle globals N_GSYM, and statics N_STSYM | ||
| 911 | switch (sym.n_type) { | ||
| 912 | macho.N_OSO => { | ||
| 913 | switch (state) { | ||
| 914 | .init, .oso_close => { | ||
| 915 | state = .oso_open; | ||
| 916 | ofile = sym.n_strx; | ||
| 917 | }, | ||
| 918 | else => return error.InvalidDebugInfo, | ||
| 919 | } | ||
| 920 | }, | ||
| 921 | macho.N_BNSYM => { | ||
| 922 | switch (state) { | ||
| 923 | .oso_open, .ensym => { | ||
| 924 | state = .bnsym; | ||
| 925 | last_sym = .{ | ||
| 926 | .strx = 0, | ||
| 927 | .addr = sym.n_value, | ||
| 928 | .size = 0, | ||
| 929 | .ofile = ofile, | ||
| 930 | }; | ||
| 931 | }, | ||
| 932 | else => return error.InvalidDebugInfo, | ||
| 933 | } | ||
| 934 | }, | ||
| 935 | macho.N_FUN => { | ||
| 936 | switch (state) { | ||
| 937 | .bnsym => { | ||
| 938 | state = .fun_strx; | ||
| 939 | last_sym.strx = sym.n_strx; | ||
| 940 | }, | ||
| 941 | .fun_strx => { | ||
| 942 | state = .fun_size; | ||
| 943 | last_sym.size = @as(u32, @intCast(sym.n_value)); | ||
| 944 | }, | ||
| 945 | else => return error.InvalidDebugInfo, | ||
| 946 | } | ||
| 947 | }, | ||
| 948 | macho.N_ENSYM => { | ||
| 949 | switch (state) { | ||
| 950 | .fun_size => { | ||
| 951 | state = .ensym; | ||
| 952 | symbols_buf[symbol_index] = last_sym; | ||
| 953 | symbol_index += 1; | ||
| 954 | }, | ||
| 955 | else => return error.InvalidDebugInfo, | ||
| 956 | } | ||
| 957 | }, | ||
| 958 | macho.N_SO => { | ||
| 959 | switch (state) { | ||
| 960 | .init, .oso_close => {}, | ||
| 961 | .oso_open, .ensym => { | ||
| 962 | state = .oso_close; | ||
| 963 | }, | ||
| 964 | else => return error.InvalidDebugInfo, | ||
| 965 | } | ||
| 966 | }, | ||
| 967 | else => {}, | ||
| 968 | } | ||
| 969 | } | ||
| 970 | |||
| 971 | switch (state) { | ||
| 972 | .init => return error.MissingDebugInfo, | ||
| 973 | .oso_close => {}, | ||
| 974 | else => return error.InvalidDebugInfo, | ||
| 975 | } | ||
| 976 | |||
| 977 | const symbols = try allocator.realloc(symbols_buf, symbol_index); | ||
| 978 | |||
| 979 | // Even though lld emits symbols in ascending order, this debug code | ||
| 980 | // should work for programs linked in any valid way. | ||
| 981 | // This sort is so that we can binary search later. | ||
| 982 | mem.sort(MachoSymbol, symbols, {}, MachoSymbol.addressLessThan); | ||
| 983 | |||
| 984 | return .{ | ||
| 985 | .base_address = undefined, | ||
| 986 | .vmaddr_slide = undefined, | ||
| 987 | .mapped_memory = mapped_mem, | ||
| 988 | .ofiles = Module.OFileTable.init(allocator), | ||
| 989 | .symbols = symbols, | ||
| 990 | .strings = strings, | ||
| 991 | }; | ||
| 992 | } | ||
| 993 | |||
| 994 | fn readCoffDebugInfo(allocator: Allocator, coff_obj: *coff.Coff) !Module { | ||
| 995 | nosuspend { | ||
| 996 | var di: Module = .{ | ||
| 997 | .base_address = undefined, | ||
| 998 | .coff_image_base = coff_obj.getImageBase(), | ||
| 999 | .coff_section_headers = undefined, | ||
| 1000 | }; | ||
| 1001 | |||
| 1002 | if (coff_obj.getSectionByName(".debug_info")) |_| { | ||
| 1003 | // This coff file has embedded DWARF debug info | ||
| 1004 | var sections: Dwarf.SectionArray = Dwarf.null_section_array; | ||
| 1005 | errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data); | ||
| 1006 | |||
| 1007 | inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| { | ||
| 1008 | sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: { | ||
| 1009 | break :blk .{ | ||
| 1010 | .data = try coff_obj.getSectionDataAlloc(section_header, allocator), | ||
| 1011 | .virtual_address = section_header.virtual_address, | ||
| 1012 | .owned = true, | ||
| 1013 | }; | ||
| 1014 | } else null; | ||
| 1015 | } | ||
| 1016 | |||
| 1017 | var dwarf = Dwarf{ | ||
| 1018 | .endian = native_endian, | ||
| 1019 | .sections = sections, | ||
| 1020 | .is_macho = false, | ||
| 1021 | }; | ||
| 1022 | |||
| 1023 | try Dwarf.open(&dwarf, allocator); | ||
| 1024 | di.dwarf = dwarf; | ||
| 1025 | } | ||
| 1026 | |||
| 1027 | const raw_path = try coff_obj.getPdbPath() orelse return di; | ||
| 1028 | const path = blk: { | ||
| 1029 | if (fs.path.isAbsolute(raw_path)) { | ||
| 1030 | break :blk raw_path; | ||
| 1031 | } else { | ||
| 1032 | const self_dir = try fs.selfExeDirPathAlloc(allocator); | ||
| 1033 | defer allocator.free(self_dir); | ||
| 1034 | break :blk try fs.path.join(allocator, &.{ self_dir, raw_path }); | ||
| 1035 | } | ||
| 1036 | }; | ||
| 1037 | defer if (path.ptr != raw_path.ptr) allocator.free(path); | ||
| 1038 | |||
| 1039 | di.pdb = Pdb.init(allocator, path) catch |err| switch (err) { | ||
| 1040 | error.FileNotFound, error.IsDir => { | ||
| 1041 | if (di.dwarf == null) return error.MissingDebugInfo; | ||
| 1042 | return di; | ||
| 1043 | }, | ||
| 1044 | else => return err, | ||
| 1045 | }; | ||
| 1046 | try di.pdb.?.parseInfoStream(); | ||
| 1047 | try di.pdb.?.parseDbiStream(); | ||
| 1048 | |||
| 1049 | if (!mem.eql(u8, &coff_obj.guid, &di.pdb.?.guid) or coff_obj.age != di.pdb.?.age) | ||
| 1050 | return error.InvalidDebugInfo; | ||
| 1051 | |||
| 1052 | // Only used by the pdb path | ||
| 1053 | di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator); | ||
| 1054 | errdefer allocator.free(di.coff_section_headers); | ||
| 1055 | |||
| 1056 | return di; | ||
| 1057 | } | ||
| 1058 | } | ||
| 1059 | |||
| 1060 | /// Reads debug info from an ELF file, or the current binary if none in specified. | ||
| 1061 | /// If the required sections aren't present but a reference to external debug info is, | ||
| 1062 | /// then this this function will recurse to attempt to load the debug sections from | ||
| 1063 | /// an external file. | ||
| 1064 | pub fn readElfDebugInfo( | ||
| 1065 | allocator: Allocator, | ||
| 1066 | elf_filename: ?[]const u8, | ||
| 1067 | build_id: ?[]const u8, | ||
| 1068 | expected_crc: ?u32, | ||
| 1069 | parent_sections: *Dwarf.SectionArray, | ||
| 1070 | parent_mapped_mem: ?[]align(mem.page_size) const u8, | ||
| 1071 | ) !Module { | ||
| 1072 | nosuspend { | ||
| 1073 | const elf_file = (if (elf_filename) |filename| blk: { | ||
| 1074 | break :blk fs.cwd().openFile(filename, .{}); | ||
| 1075 | } else fs.openSelfExe(.{})) catch |err| switch (err) { | ||
| 1076 | error.FileNotFound => return error.MissingDebugInfo, | ||
| 1077 | else => return err, | ||
| 1078 | }; | ||
| 1079 | |||
| 1080 | const mapped_mem = try mapWholeFile(elf_file); | ||
| 1081 | if (expected_crc) |crc| if (crc != std.hash.crc.Crc32.hash(mapped_mem)) return error.InvalidDebugInfo; | ||
| 1082 | |||
| 1083 | const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]); | ||
| 1084 | if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; | ||
| 1085 | if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; | ||
| 1086 | |||
| 1087 | const endian: std.builtin.Endian = switch (hdr.e_ident[elf.EI_DATA]) { | ||
| 1088 | elf.ELFDATA2LSB => .little, | ||
| 1089 | elf.ELFDATA2MSB => .big, | ||
| 1090 | else => return error.InvalidElfEndian, | ||
| 1091 | }; | ||
| 1092 | assert(endian == native_endian); // this is our own debug info | ||
| 1093 | |||
| 1094 | const shoff = hdr.e_shoff; | ||
| 1095 | const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx); | ||
| 1096 | const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[math.cast(usize, str_section_off) orelse return error.Overflow])); | ||
| 1097 | const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size]; | ||
| 1098 | const shdrs = @as( | ||
| 1099 | [*]const elf.Shdr, | ||
| 1100 | @ptrCast(@alignCast(&mapped_mem[shoff])), | ||
| 1101 | )[0..hdr.e_shnum]; | ||
| 1102 | |||
| 1103 | var sections: Dwarf.SectionArray = Dwarf.null_section_array; | ||
| 1104 | |||
| 1105 | // Combine section list. This takes ownership over any owned sections from the parent scope. | ||
| 1106 | for (parent_sections, &sections) |*parent, *section| { | ||
| 1107 | if (parent.*) |*p| { | ||
| 1108 | section.* = p.*; | ||
| 1109 | p.owned = false; | ||
| 1110 | } | ||
| 1111 | } | ||
| 1112 | errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data); | ||
| 1113 | |||
| 1114 | var separate_debug_filename: ?[]const u8 = null; | ||
| 1115 | var separate_debug_crc: ?u32 = null; | ||
| 1116 | |||
| 1117 | for (shdrs) |*shdr| { | ||
| 1118 | if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue; | ||
| 1119 | const name = mem.sliceTo(header_strings[shdr.sh_name..], 0); | ||
| 1120 | |||
| 1121 | if (mem.eql(u8, name, ".gnu_debuglink")) { | ||
| 1122 | const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | ||
| 1123 | const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0); | ||
| 1124 | const crc_offset = mem.alignForward(usize, @intFromPtr(&debug_filename[debug_filename.len]) + 1, 4) - @intFromPtr(gnu_debuglink.ptr); | ||
| 1125 | const crc_bytes = gnu_debuglink[crc_offset..][0..4]; | ||
| 1126 | separate_debug_crc = mem.readInt(u32, crc_bytes, native_endian); | ||
| 1127 | separate_debug_filename = debug_filename; | ||
| 1128 | continue; | ||
| 1129 | } | ||
| 1130 | |||
| 1131 | var section_index: ?usize = null; | ||
| 1132 | inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| { | ||
| 1133 | if (mem.eql(u8, "." ++ section.name, name)) section_index = i; | ||
| 1134 | } | ||
| 1135 | if (section_index == null) continue; | ||
| 1136 | if (sections[section_index.?] != null) continue; | ||
| 1137 | |||
| 1138 | const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | ||
| 1139 | sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: { | ||
| 1140 | var section_stream = std.io.fixedBufferStream(section_bytes); | ||
| 1141 | var section_reader = section_stream.reader(); | ||
| 1142 | const chdr = section_reader.readStruct(elf.Chdr) catch continue; | ||
| 1143 | if (chdr.ch_type != .ZLIB) continue; | ||
| 1144 | |||
| 1145 | var zlib_stream = std.compress.zlib.decompressor(section_stream.reader()); | ||
| 1146 | |||
| 1147 | const decompressed_section = try allocator.alloc(u8, chdr.ch_size); | ||
| 1148 | errdefer allocator.free(decompressed_section); | ||
| 1149 | |||
| 1150 | const read = zlib_stream.reader().readAll(decompressed_section) catch continue; | ||
| 1151 | assert(read == decompressed_section.len); | ||
| 1152 | |||
| 1153 | break :blk .{ | ||
| 1154 | .data = decompressed_section, | ||
| 1155 | .virtual_address = shdr.sh_addr, | ||
| 1156 | .owned = true, | ||
| 1157 | }; | ||
| 1158 | } else .{ | ||
| 1159 | .data = section_bytes, | ||
| 1160 | .virtual_address = shdr.sh_addr, | ||
| 1161 | .owned = false, | ||
| 1162 | }; | ||
| 1163 | } | ||
| 1164 | |||
| 1165 | const missing_debug_info = | ||
| 1166 | sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or | ||
| 1167 | sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or | ||
| 1168 | sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or | ||
| 1169 | sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; | ||
| 1170 | |||
| 1171 | // Attempt to load debug info from an external file | ||
| 1172 | // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html | ||
| 1173 | if (missing_debug_info) { | ||
| 1174 | |||
| 1175 | // Only allow one level of debug info nesting | ||
| 1176 | if (parent_mapped_mem) |_| { | ||
| 1177 | return error.MissingDebugInfo; | ||
| 1178 | } | ||
| 1179 | |||
| 1180 | const global_debug_directories = [_][]const u8{ | ||
| 1181 | "/usr/lib/debug", | ||
| 1182 | }; | ||
| 1183 | |||
| 1184 | // <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.debug | ||
| 1185 | if (build_id) |id| blk: { | ||
| 1186 | if (id.len < 3) break :blk; | ||
| 1187 | |||
| 1188 | // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice | ||
| 1189 | const extension = ".debug"; | ||
| 1190 | var id_prefix_buf: [2]u8 = undefined; | ||
| 1191 | var filename_buf: [38 + extension.len]u8 = undefined; | ||
| 1192 | |||
| 1193 | _ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable; | ||
| 1194 | const filename = std.fmt.bufPrint( | ||
| 1195 | &filename_buf, | ||
| 1196 | "{s}" ++ extension, | ||
| 1197 | .{std.fmt.fmtSliceHexLower(id[1..])}, | ||
| 1198 | ) catch break :blk; | ||
| 1199 | |||
| 1200 | for (global_debug_directories) |global_directory| { | ||
| 1201 | const path = try fs.path.join(allocator, &.{ global_directory, ".build-id", &id_prefix_buf, filename }); | ||
| 1202 | defer allocator.free(path); | ||
| 1203 | |||
| 1204 | return readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem) catch continue; | ||
| 1205 | } | ||
| 1206 | } | ||
| 1207 | |||
| 1208 | // use the path from .gnu_debuglink, in the same search order as gdb | ||
| 1209 | if (separate_debug_filename) |separate_filename| blk: { | ||
| 1210 | if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) return error.MissingDebugInfo; | ||
| 1211 | |||
| 1212 | // <cwd>/<gnu_debuglink> | ||
| 1213 | if (readElfDebugInfo(allocator, separate_filename, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {} | ||
| 1214 | |||
| 1215 | // <cwd>/.debug/<gnu_debuglink> | ||
| 1216 | { | ||
| 1217 | const path = try fs.path.join(allocator, &.{ ".debug", separate_filename }); | ||
| 1218 | defer allocator.free(path); | ||
| 1219 | |||
| 1220 | if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {} | ||
| 1221 | } | ||
| 1222 | |||
| 1223 | var cwd_buf: [fs.max_path_bytes]u8 = undefined; | ||
| 1224 | const cwd_path = posix.realpath(".", &cwd_buf) catch break :blk; | ||
| 1225 | |||
| 1226 | // <global debug directory>/<absolute folder of current binary>/<gnu_debuglink> | ||
| 1227 | for (global_debug_directories) |global_directory| { | ||
| 1228 | const path = try fs.path.join(allocator, &.{ global_directory, cwd_path, separate_filename }); | ||
| 1229 | defer allocator.free(path); | ||
| 1230 | if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {} | ||
| 1231 | } | ||
| 1232 | } | ||
| 1233 | |||
| 1234 | return error.MissingDebugInfo; | ||
| 1235 | } | ||
| 1236 | |||
| 1237 | var di = Dwarf{ | ||
| 1238 | .endian = endian, | ||
| 1239 | .sections = sections, | ||
| 1240 | .is_macho = false, | ||
| 1241 | }; | ||
| 1242 | |||
| 1243 | try Dwarf.open(&di, allocator); | ||
| 1244 | |||
| 1245 | return .{ | ||
| 1246 | .base_address = undefined, | ||
| 1247 | .dwarf = di, | ||
| 1248 | .mapped_memory = parent_mapped_mem orelse mapped_mem, | ||
| 1249 | .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null, | ||
| 1250 | }; | ||
| 1251 | } | ||
| 1252 | } | ||
| 1253 | |||
| 1254 | const MachoSymbol = struct { | ||
| 1255 | strx: u32, | ||
| 1256 | addr: u64, | ||
| 1257 | size: u32, | ||
| 1258 | ofile: u32, | ||
| 1259 | |||
| 1260 | /// Returns the address from the macho file | ||
| 1261 | fn address(self: MachoSymbol) u64 { | ||
| 1262 | return self.addr; | ||
| 1263 | } | ||
| 1264 | |||
| 1265 | fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool { | ||
| 1266 | _ = context; | ||
| 1267 | return lhs.addr < rhs.addr; | ||
| 1268 | } | ||
| 1269 | }; | ||
| 1270 | |||
| 1271 | /// Takes ownership of file, even on error. | ||
| 1272 | /// TODO it's weird to take ownership even on error, rework this code. | ||
| 1273 | fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 { | ||
| 1274 | nosuspend { | ||
| 1275 | defer file.close(); | ||
| 1276 | |||
| 1277 | const file_len = math.cast(usize, try file.getEndPos()) orelse math.maxInt(usize); | ||
| 1278 | const mapped_mem = try posix.mmap( | ||
| 1279 | null, | ||
| 1280 | file_len, | ||
| 1281 | posix.PROT.READ, | ||
| 1282 | .{ .TYPE = .SHARED }, | ||
| 1283 | file.handle, | ||
| 1284 | 0, | ||
| 1285 | ); | ||
| 1286 | errdefer posix.munmap(mapped_mem); | ||
| 1287 | |||
| 1288 | return mapped_mem; | ||
| 1289 | } | ||
| 1290 | } | ||
| 1291 | |||
| 1292 | fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 { | ||
| 1293 | const start = math.cast(usize, offset) orelse return error.Overflow; | ||
| 1294 | const end = start + (math.cast(usize, size) orelse return error.Overflow); | ||
| 1295 | return ptr[start..end]; | ||
| 1296 | } | ||
| 1297 | |||
| 1298 | pub const SymbolInfo = struct { | ||
| 1299 | symbol_name: []const u8 = "???", | ||
| 1300 | compile_unit_name: []const u8 = "???", | ||
| 1301 | line_info: ?std.debug.SourceLocation = null, | ||
| 1302 | |||
| 1303 | pub fn deinit(self: SymbolInfo, allocator: Allocator) void { | ||
| 1304 | if (self.line_info) |li| allocator.free(li.file_name); | ||
| 1305 | } | ||
| 1306 | }; | ||
| 1307 | |||
| 1308 | fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol { | ||
| 1309 | var min: usize = 0; | ||
| 1310 | var max: usize = symbols.len - 1; | ||
| 1311 | while (min < max) { | ||
| 1312 | const mid = min + (max - min) / 2; | ||
| 1313 | const curr = &symbols[mid]; | ||
| 1314 | const next = &symbols[mid + 1]; | ||
| 1315 | if (address >= next.address()) { | ||
| 1316 | min = mid + 1; | ||
| 1317 | } else if (address < curr.address()) { | ||
| 1318 | max = mid; | ||
| 1319 | } else { | ||
| 1320 | return curr; | ||
| 1321 | } | ||
| 1322 | } | ||
| 1323 | |||
| 1324 | const max_sym = &symbols[symbols.len - 1]; | ||
| 1325 | if (address >= max_sym.address()) | ||
| 1326 | return max_sym; | ||
| 1327 | |||
| 1328 | return null; | ||
| 1329 | } | ||
| 1330 | |||
| 1331 | test machoSearchSymbols { | ||
| 1332 | const symbols = [_]MachoSymbol{ | ||
| 1333 | .{ .addr = 100, .strx = undefined, .size = undefined, .ofile = undefined }, | ||
| 1334 | .{ .addr = 200, .strx = undefined, .size = undefined, .ofile = undefined }, | ||
| 1335 | .{ .addr = 300, .strx = undefined, .size = undefined, .ofile = undefined }, | ||
| 1336 | }; | ||
| 1337 | |||
| 1338 | try testing.expectEqual(null, machoSearchSymbols(&symbols, 0)); | ||
| 1339 | try testing.expectEqual(null, machoSearchSymbols(&symbols, 99)); | ||
| 1340 | try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 100).?); | ||
| 1341 | try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 150).?); | ||
| 1342 | try testing.expectEqual(&symbols[0], machoSearchSymbols(&symbols, 199).?); | ||
| 1343 | |||
| 1344 | try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 200).?); | ||
| 1345 | try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 250).?); | ||
| 1346 | try testing.expectEqual(&symbols[1], machoSearchSymbols(&symbols, 299).?); | ||
| 1347 | |||
| 1348 | try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 300).?); | ||
| 1349 | try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 301).?); | ||
| 1350 | try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?); | ||
| 1351 | } | ||
| 1352 | |||
| 1353 | fn getSymbolFromDwarf(allocator: Allocator, address: u64, di: *Dwarf) !SymbolInfo { | ||
| 1354 | if (nosuspend di.findCompileUnit(address)) |compile_unit| { | ||
| 1355 | return SymbolInfo{ | ||
| 1356 | .symbol_name = nosuspend di.getSymbolName(address) orelse "???", | ||
| 1357 | .compile_unit_name = compile_unit.die.getAttrString(di, std.dwarf.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) { | ||
| 1358 | error.MissingDebugInfo, error.InvalidDebugInfo => "???", | ||
| 1359 | }, | ||
| 1360 | .line_info = nosuspend di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) { | ||
| 1361 | error.MissingDebugInfo, error.InvalidDebugInfo => null, | ||
| 1362 | else => return err, | ||
| 1363 | }, | ||
| 1364 | }; | ||
| 1365 | } else |err| switch (err) { | ||
| 1366 | error.MissingDebugInfo, error.InvalidDebugInfo => { | ||
| 1367 | return SymbolInfo{}; | ||
| 1368 | }, | ||
| 1369 | else => return err, | ||
| 1370 | } | ||
| 1371 | } | ||
| 1372 | |||
| 1373 | /// Unwind a frame using MachO compact unwind info (from __unwind_info). | ||
| 1374 | /// If the compact encoding can't encode a way to unwind a frame, it will | ||
| 1375 | /// defer unwinding to DWARF, in which case `.eh_frame` will be used if available. | ||
| 1376 | pub fn unwindFrameMachO( | ||
| 1377 | context: *UnwindContext, | ||
| 1378 | ma: *std.debug.MemoryAccessor, | ||
| 1379 | unwind_info: []const u8, | ||
| 1380 | eh_frame: ?[]const u8, | ||
| 1381 | module_base_address: usize, | ||
| 1382 | ) !usize { | ||
| 1383 | const header = std.mem.bytesAsValue( | ||
| 1384 | macho.unwind_info_section_header, | ||
| 1385 | unwind_info[0..@sizeOf(macho.unwind_info_section_header)], | ||
| 1386 | ); | ||
| 1387 | const indices = std.mem.bytesAsSlice( | ||
| 1388 | macho.unwind_info_section_header_index_entry, | ||
| 1389 | unwind_info[header.indexSectionOffset..][0 .. header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry)], | ||
| 1390 | ); | ||
| 1391 | if (indices.len == 0) return error.MissingUnwindInfo; | ||
| 1392 | |||
| 1393 | const mapped_pc = context.pc - module_base_address; | ||
| 1394 | const second_level_index = blk: { | ||
| 1395 | var left: usize = 0; | ||
| 1396 | var len: usize = indices.len; | ||
| 1397 | |||
| 1398 | while (len > 1) { | ||
| 1399 | const mid = left + len / 2; | ||
| 1400 | const offset = indices[mid].functionOffset; | ||
| 1401 | if (mapped_pc < offset) { | ||
| 1402 | len /= 2; | ||
| 1403 | } else { | ||
| 1404 | left = mid; | ||
| 1405 | if (mapped_pc == offset) break; | ||
| 1406 | len -= len / 2; | ||
| 1407 | } | ||
| 1408 | } | ||
| 1409 | |||
| 1410 | // Last index is a sentinel containing the highest address as its functionOffset | ||
| 1411 | if (indices[left].secondLevelPagesSectionOffset == 0) return error.MissingUnwindInfo; | ||
| 1412 | break :blk &indices[left]; | ||
| 1413 | }; | ||
| 1414 | |||
| 1415 | const common_encodings = std.mem.bytesAsSlice( | ||
| 1416 | macho.compact_unwind_encoding_t, | ||
| 1417 | unwind_info[header.commonEncodingsArraySectionOffset..][0 .. header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t)], | ||
| 1418 | ); | ||
| 1419 | |||
| 1420 | const start_offset = second_level_index.secondLevelPagesSectionOffset; | ||
| 1421 | const kind = std.mem.bytesAsValue( | ||
| 1422 | macho.UNWIND_SECOND_LEVEL, | ||
| 1423 | unwind_info[start_offset..][0..@sizeOf(macho.UNWIND_SECOND_LEVEL)], | ||
| 1424 | ); | ||
| 1425 | |||
| 1426 | const entry: struct { | ||
| 1427 | function_offset: usize, | ||
| 1428 | raw_encoding: u32, | ||
| 1429 | } = switch (kind.*) { | ||
| 1430 | .REGULAR => blk: { | ||
| 1431 | const page_header = std.mem.bytesAsValue( | ||
| 1432 | macho.unwind_info_regular_second_level_page_header, | ||
| 1433 | unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_regular_second_level_page_header)], | ||
| 1434 | ); | ||
| 1435 | |||
| 1436 | const entries = std.mem.bytesAsSlice( | ||
| 1437 | macho.unwind_info_regular_second_level_entry, | ||
| 1438 | unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry)], | ||
| 1439 | ); | ||
| 1440 | if (entries.len == 0) return error.InvalidUnwindInfo; | ||
| 1441 | |||
| 1442 | var left: usize = 0; | ||
| 1443 | var len: usize = entries.len; | ||
| 1444 | while (len > 1) { | ||
| 1445 | const mid = left + len / 2; | ||
| 1446 | const offset = entries[mid].functionOffset; | ||
| 1447 | if (mapped_pc < offset) { | ||
| 1448 | len /= 2; | ||
| 1449 | } else { | ||
| 1450 | left = mid; | ||
| 1451 | if (mapped_pc == offset) break; | ||
| 1452 | len -= len / 2; | ||
| 1453 | } | ||
| 1454 | } | ||
| 1455 | |||
| 1456 | break :blk .{ | ||
| 1457 | .function_offset = entries[left].functionOffset, | ||
| 1458 | .raw_encoding = entries[left].encoding, | ||
| 1459 | }; | ||
| 1460 | }, | ||
| 1461 | .COMPRESSED => blk: { | ||
| 1462 | const page_header = std.mem.bytesAsValue( | ||
| 1463 | macho.unwind_info_compressed_second_level_page_header, | ||
| 1464 | unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_compressed_second_level_page_header)], | ||
| 1465 | ); | ||
| 1466 | |||
| 1467 | const entries = std.mem.bytesAsSlice( | ||
| 1468 | macho.UnwindInfoCompressedEntry, | ||
| 1469 | unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry)], | ||
| 1470 | ); | ||
| 1471 | if (entries.len == 0) return error.InvalidUnwindInfo; | ||
| 1472 | |||
| 1473 | var left: usize = 0; | ||
| 1474 | var len: usize = entries.len; | ||
| 1475 | while (len > 1) { | ||
| 1476 | const mid = left + len / 2; | ||
| 1477 | const offset = second_level_index.functionOffset + entries[mid].funcOffset; | ||
| 1478 | if (mapped_pc < offset) { | ||
| 1479 | len /= 2; | ||
| 1480 | } else { | ||
| 1481 | left = mid; | ||
| 1482 | if (mapped_pc == offset) break; | ||
| 1483 | len -= len / 2; | ||
| 1484 | } | ||
| 1485 | } | ||
| 1486 | |||
| 1487 | const entry = entries[left]; | ||
| 1488 | const function_offset = second_level_index.functionOffset + entry.funcOffset; | ||
| 1489 | if (entry.encodingIndex < header.commonEncodingsArrayCount) { | ||
| 1490 | if (entry.encodingIndex >= common_encodings.len) return error.InvalidUnwindInfo; | ||
| 1491 | break :blk .{ | ||
| 1492 | .function_offset = function_offset, | ||
| 1493 | .raw_encoding = common_encodings[entry.encodingIndex], | ||
| 1494 | }; | ||
| 1495 | } else { | ||
| 1496 | const local_index = try math.sub( | ||
| 1497 | u8, | ||
| 1498 | entry.encodingIndex, | ||
| 1499 | math.cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo, | ||
| 1500 | ); | ||
| 1501 | const local_encodings = std.mem.bytesAsSlice( | ||
| 1502 | macho.compact_unwind_encoding_t, | ||
| 1503 | unwind_info[start_offset + page_header.encodingsPageOffset ..][0 .. page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t)], | ||
| 1504 | ); | ||
| 1505 | if (local_index >= local_encodings.len) return error.InvalidUnwindInfo; | ||
| 1506 | break :blk .{ | ||
| 1507 | .function_offset = function_offset, | ||
| 1508 | .raw_encoding = local_encodings[local_index], | ||
| 1509 | }; | ||
| 1510 | } | ||
| 1511 | }, | ||
| 1512 | else => return error.InvalidUnwindInfo, | ||
| 1513 | }; | ||
| 1514 | |||
| 1515 | if (entry.raw_encoding == 0) return error.NoUnwindInfo; | ||
| 1516 | const reg_context = Dwarf.abi.RegisterContext{ | ||
| 1517 | .eh_frame = false, | ||
| 1518 | .is_macho = true, | ||
| 1519 | }; | ||
| 1520 | |||
| 1521 | const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding); | ||
| 1522 | const new_ip = switch (builtin.cpu.arch) { | ||
| 1523 | .x86_64 => switch (encoding.mode.x86_64) { | ||
| 1524 | .OLD => return error.UnimplementedUnwindEncoding, | ||
| 1525 | .RBP_FRAME => blk: { | ||
| 1526 | const regs: [5]u3 = .{ | ||
| 1527 | encoding.value.x86_64.frame.reg0, | ||
| 1528 | encoding.value.x86_64.frame.reg1, | ||
| 1529 | encoding.value.x86_64.frame.reg2, | ||
| 1530 | encoding.value.x86_64.frame.reg3, | ||
| 1531 | encoding.value.x86_64.frame.reg4, | ||
| 1532 | }; | ||
| 1533 | |||
| 1534 | const frame_offset = encoding.value.x86_64.frame.frame_offset * @sizeOf(usize); | ||
| 1535 | var max_reg: usize = 0; | ||
| 1536 | inline for (regs, 0..) |reg, i| { | ||
| 1537 | if (reg > 0) max_reg = i; | ||
| 1538 | } | ||
| 1539 | |||
| 1540 | const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*; | ||
| 1541 | const new_sp = fp + 2 * @sizeOf(usize); | ||
| 1542 | |||
| 1543 | // Verify the stack range we're about to read register values from | ||
| 1544 | if (ma.load(usize, new_sp) == null or ma.load(usize, fp - frame_offset + max_reg * @sizeOf(usize)) == null) return error.InvalidUnwindInfo; | ||
| 1545 | |||
| 1546 | const ip_ptr = fp + @sizeOf(usize); | ||
| 1547 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | ||
| 1548 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | ||
| 1549 | |||
| 1550 | (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp; | ||
| 1551 | (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp; | ||
| 1552 | (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip; | ||
| 1553 | |||
| 1554 | for (regs, 0..) |reg, i| { | ||
| 1555 | if (reg == 0) continue; | ||
| 1556 | const addr = fp - frame_offset + i * @sizeOf(usize); | ||
| 1557 | const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(reg); | ||
| 1558 | (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*; | ||
| 1559 | } | ||
| 1560 | |||
| 1561 | break :blk new_ip; | ||
| 1562 | }, | ||
| 1563 | .STACK_IMMD, | ||
| 1564 | .STACK_IND, | ||
| 1565 | => blk: { | ||
| 1566 | const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*; | ||
| 1567 | const stack_size = if (encoding.mode.x86_64 == .STACK_IMMD) | ||
| 1568 | @as(usize, encoding.value.x86_64.frameless.stack.direct.stack_size) * @sizeOf(usize) | ||
| 1569 | else stack_size: { | ||
| 1570 | // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function. | ||
| 1571 | const sub_offset_addr = | ||
| 1572 | module_base_address + | ||
| 1573 | entry.function_offset + | ||
| 1574 | encoding.value.x86_64.frameless.stack.indirect.sub_offset; | ||
| 1575 | if (ma.load(usize, sub_offset_addr) == null) return error.InvalidUnwindInfo; | ||
| 1576 | |||
| 1577 | // `sub_offset_addr` points to the offset of the literal within the instruction | ||
| 1578 | const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*; | ||
| 1579 | break :stack_size sub_operand + @sizeOf(usize) * @as(usize, encoding.value.x86_64.frameless.stack.indirect.stack_adjust); | ||
| 1580 | }; | ||
| 1581 | |||
| 1582 | // Decode the Lehmer-coded sequence of registers. | ||
| 1583 | // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h | ||
| 1584 | |||
| 1585 | // Decode the variable-based permutation number into its digits. Each digit represents | ||
| 1586 | // an index into the list of register numbers that weren't yet used in the sequence at | ||
| 1587 | // the time the digit was added. | ||
| 1588 | const reg_count = encoding.value.x86_64.frameless.stack_reg_count; | ||
| 1589 | const ip_ptr = if (reg_count > 0) reg_blk: { | ||
| 1590 | var digits: [6]u3 = undefined; | ||
| 1591 | var accumulator: usize = encoding.value.x86_64.frameless.stack_reg_permutation; | ||
| 1592 | var base: usize = 2; | ||
| 1593 | for (0..reg_count) |i| { | ||
| 1594 | const div = accumulator / base; | ||
| 1595 | digits[digits.len - 1 - i] = @intCast(accumulator - base * div); | ||
| 1596 | accumulator = div; | ||
| 1597 | base += 1; | ||
| 1598 | } | ||
| 1599 | |||
| 1600 | const reg_numbers = [_]u3{ 1, 2, 3, 4, 5, 6 }; | ||
| 1601 | var registers: [reg_numbers.len]u3 = undefined; | ||
| 1602 | var used_indices = [_]bool{false} ** reg_numbers.len; | ||
| 1603 | for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| { | ||
| 1604 | var unused_count: u8 = 0; | ||
| 1605 | const unused_index = for (used_indices, 0..) |used, index| { | ||
| 1606 | if (!used) { | ||
| 1607 | if (target_unused_index == unused_count) break index; | ||
| 1608 | unused_count += 1; | ||
| 1609 | } | ||
| 1610 | } else unreachable; | ||
| 1611 | |||
| 1612 | registers[i] = reg_numbers[unused_index]; | ||
| 1613 | used_indices[unused_index] = true; | ||
| 1614 | } | ||
| 1615 | |||
| 1616 | var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1); | ||
| 1617 | if (ma.load(usize, reg_addr) == null) return error.InvalidUnwindInfo; | ||
| 1618 | for (0..reg_count) |i| { | ||
| 1619 | const reg_number = try Dwarf.compactUnwindToDwarfRegNumber(registers[i]); | ||
| 1620 | (try regValueNative(context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | ||
| 1621 | reg_addr += @sizeOf(usize); | ||
| 1622 | } | ||
| 1623 | |||
| 1624 | break :reg_blk reg_addr; | ||
| 1625 | } else sp + stack_size - @sizeOf(usize); | ||
| 1626 | |||
| 1627 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | ||
| 1628 | const new_sp = ip_ptr + @sizeOf(usize); | ||
| 1629 | if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo; | ||
| 1630 | |||
| 1631 | (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp; | ||
| 1632 | (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip; | ||
| 1633 | |||
| 1634 | break :blk new_ip; | ||
| 1635 | }, | ||
| 1636 | .DWARF => { | ||
| 1637 | return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf)); | ||
| 1638 | }, | ||
| 1639 | }, | ||
| 1640 | .aarch64 => switch (encoding.mode.arm64) { | ||
| 1641 | .OLD => return error.UnimplementedUnwindEncoding, | ||
| 1642 | .FRAMELESS => blk: { | ||
| 1643 | const sp = (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).*; | ||
| 1644 | const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16; | ||
| 1645 | const new_ip = (try regValueNative(context.thread_context, 30, reg_context)).*; | ||
| 1646 | if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo; | ||
| 1647 | (try regValueNative(context.thread_context, spRegNum(reg_context), reg_context)).* = new_sp; | ||
| 1648 | break :blk new_ip; | ||
| 1649 | }, | ||
| 1650 | .DWARF => { | ||
| 1651 | return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf)); | ||
| 1652 | }, | ||
| 1653 | .FRAME => blk: { | ||
| 1654 | const fp = (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).*; | ||
| 1655 | const new_sp = fp + 16; | ||
| 1656 | const ip_ptr = fp + @sizeOf(usize); | ||
| 1657 | |||
| 1658 | const num_restored_pairs: usize = | ||
| 1659 | @popCount(@as(u5, @bitCast(encoding.value.arm64.frame.x_reg_pairs))) + | ||
| 1660 | @popCount(@as(u4, @bitCast(encoding.value.arm64.frame.d_reg_pairs))); | ||
| 1661 | const min_reg_addr = fp - num_restored_pairs * 2 * @sizeOf(usize); | ||
| 1662 | |||
| 1663 | if (ma.load(usize, new_sp) == null or ma.load(usize, min_reg_addr) == null) return error.InvalidUnwindInfo; | ||
| 1664 | |||
| 1665 | var reg_addr = fp - @sizeOf(usize); | ||
| 1666 | inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.x_reg_pairs)).Struct.fields, 0..) |field, i| { | ||
| 1667 | if (@field(encoding.value.arm64.frame.x_reg_pairs, field.name) != 0) { | ||
| 1668 | (try regValueNative(context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | ||
| 1669 | reg_addr += @sizeOf(usize); | ||
| 1670 | (try regValueNative(context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | ||
| 1671 | reg_addr += @sizeOf(usize); | ||
| 1672 | } | ||
| 1673 | } | ||
| 1674 | |||
| 1675 | inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.d_reg_pairs)).Struct.fields, 0..) |field, i| { | ||
| 1676 | if (@field(encoding.value.arm64.frame.d_reg_pairs, field.name) != 0) { | ||
| 1677 | // Only the lower half of the 128-bit V registers are restored during unwinding | ||
| 1678 | @memcpy( | ||
| 1679 | try regBytes(context.thread_context, 64 + 8 + i, context.reg_context), | ||
| 1680 | std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), | ||
| 1681 | ); | ||
| 1682 | reg_addr += @sizeOf(usize); | ||
| 1683 | @memcpy( | ||
| 1684 | try regBytes(context.thread_context, 64 + 9 + i, context.reg_context), | ||
| 1685 | std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), | ||
| 1686 | ); | ||
| 1687 | reg_addr += @sizeOf(usize); | ||
| 1688 | } | ||
| 1689 | } | ||
| 1690 | |||
| 1691 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | ||
| 1692 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | ||
| 1693 | |||
| 1694 | (try regValueNative(context.thread_context, fpRegNum(reg_context), reg_context)).* = new_fp; | ||
| 1695 | (try regValueNative(context.thread_context, ip_reg_num, reg_context)).* = new_ip; | ||
| 1696 | |||
| 1697 | break :blk new_ip; | ||
| 1698 | }, | ||
| 1699 | }, | ||
| 1700 | else => return error.UnimplementedArch, | ||
| 1701 | }; | ||
| 1702 | |||
| 1703 | context.pc = stripInstructionPtrAuthCode(new_ip); | ||
| 1704 | if (context.pc > 0) context.pc -= 1; | ||
| 1705 | return new_ip; | ||
| 1706 | } | ||
| 1707 | |||
| 1708 | pub const UnwindContext = struct { | ||
| 1709 | allocator: Allocator, | ||
| 1710 | cfa: ?usize, | ||
| 1711 | pc: usize, | ||
| 1712 | thread_context: *std.debug.ThreadContext, | ||
| 1713 | reg_context: Dwarf.abi.RegisterContext, | ||
| 1714 | vm: VirtualMachine, | ||
| 1715 | stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }), | ||
| 1716 | |||
| 1717 | pub fn init( | ||
| 1718 | allocator: Allocator, | ||
| 1719 | thread_context: *std.debug.ThreadContext, | ||
| 1720 | ) !UnwindContext { | ||
| 1721 | comptime assert(supports_unwinding); | ||
| 1722 | |||
| 1723 | const pc = stripInstructionPtrAuthCode( | ||
| 1724 | (try regValueNative(thread_context, ip_reg_num, null)).*, | ||
| 1725 | ); | ||
| 1726 | |||
| 1727 | const context_copy = try allocator.create(std.debug.ThreadContext); | ||
| 1728 | std.debug.copyContext(thread_context, context_copy); | ||
| 1729 | |||
| 1730 | return .{ | ||
| 1731 | .allocator = allocator, | ||
| 1732 | .cfa = null, | ||
| 1733 | .pc = pc, | ||
| 1734 | .thread_context = context_copy, | ||
| 1735 | .reg_context = undefined, | ||
| 1736 | .vm = .{}, | ||
| 1737 | .stack_machine = .{}, | ||
| 1738 | }; | ||
| 1739 | } | ||
| 1740 | |||
| 1741 | pub fn deinit(self: *UnwindContext) void { | ||
| 1742 | self.vm.deinit(self.allocator); | ||
| 1743 | self.stack_machine.deinit(self.allocator); | ||
| 1744 | self.allocator.destroy(self.thread_context); | ||
| 1745 | self.* = undefined; | ||
| 1746 | } | ||
| 1747 | |||
| 1748 | pub fn getFp(self: *const UnwindContext) !usize { | ||
| 1749 | return (try regValueNative(self.thread_context, fpRegNum(self.reg_context), self.reg_context)).*; | ||
| 1750 | } | ||
| 1751 | }; | ||
| 1752 | |||
| 1753 | /// Some platforms use pointer authentication - the upper bits of instruction pointers contain a signature. | ||
| 1754 | /// This function clears these signature bits to make the pointer usable. | ||
| 1755 | pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize { | ||
| 1756 | if (native_arch == .aarch64) { | ||
| 1757 | // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it) | ||
| 1758 | // The save / restore is because `xpaclri` operates on x30 (LR) | ||
| 1759 | return asm ( | ||
| 1760 | \\mov x16, x30 | ||
| 1761 | \\mov x30, x15 | ||
| 1762 | \\hint 0x07 | ||
| 1763 | \\mov x15, x30 | ||
| 1764 | \\mov x30, x16 | ||
| 1765 | : [ret] "={x15}" (-> usize), | ||
| 1766 | : [ptr] "{x15}" (ptr), | ||
| 1767 | : "x16" | ||
| 1768 | ); | ||
| 1769 | } | ||
| 1770 | |||
| 1771 | return ptr; | ||
| 1772 | } | ||
| 1773 | |||
| 1774 | /// Unwind a stack frame using DWARF unwinding info, updating the register context. | ||
| 1775 | /// | ||
| 1776 | /// If `.eh_frame_hdr` is available, it will be used to binary search for the FDE. | ||
| 1777 | /// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. | ||
| 1778 | /// | ||
| 1779 | /// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info | ||
| 1780 | /// defers unwinding to DWARF. This is an offset into the `.eh_frame` section. | ||
| 1781 | pub fn unwindFrameDwarf( | ||
| 1782 | di: *const Dwarf, | ||
| 1783 | context: *UnwindContext, | ||
| 1784 | ma: *std.debug.MemoryAccessor, | ||
| 1785 | explicit_fde_offset: ?usize, | ||
| 1786 | ) !usize { | ||
| 1787 | if (!supports_unwinding) return error.UnsupportedCpuArchitecture; | ||
| 1788 | if (context.pc == 0) return 0; | ||
| 1789 | |||
| 1790 | // Find the FDE and CIE | ||
| 1791 | var cie: Dwarf.CommonInformationEntry = undefined; | ||
| 1792 | var fde: Dwarf.FrameDescriptionEntry = undefined; | ||
| 1793 | |||
| 1794 | if (explicit_fde_offset) |fde_offset| { | ||
| 1795 | const dwarf_section: Dwarf.Section.Id = .eh_frame; | ||
| 1796 | const frame_section = di.section(dwarf_section) orelse return error.MissingFDE; | ||
| 1797 | if (fde_offset >= frame_section.len) return error.MissingFDE; | ||
| 1798 | |||
| 1799 | var fbr: std.debug.DeprecatedFixedBufferReader = .{ | ||
| 1800 | .buf = frame_section, | ||
| 1801 | .pos = fde_offset, | ||
| 1802 | .endian = di.endian, | ||
| 1803 | }; | ||
| 1804 | |||
| 1805 | const fde_entry_header = try Dwarf.EntryHeader.read(&fbr, null, dwarf_section); | ||
| 1806 | if (fde_entry_header.type != .fde) return error.MissingFDE; | ||
| 1807 | |||
| 1808 | const cie_offset = fde_entry_header.type.fde; | ||
| 1809 | try fbr.seekTo(cie_offset); | ||
| 1810 | |||
| 1811 | fbr.endian = native_endian; | ||
| 1812 | const cie_entry_header = try Dwarf.EntryHeader.read(&fbr, null, dwarf_section); | ||
| 1813 | if (cie_entry_header.type != .cie) return Dwarf.bad(); | ||
| 1814 | |||
| 1815 | cie = try Dwarf.CommonInformationEntry.parse( | ||
| 1816 | cie_entry_header.entry_bytes, | ||
| 1817 | 0, | ||
| 1818 | true, | ||
| 1819 | cie_entry_header.format, | ||
| 1820 | dwarf_section, | ||
| 1821 | cie_entry_header.length_offset, | ||
| 1822 | @sizeOf(usize), | ||
| 1823 | native_endian, | ||
| 1824 | ); | ||
| 1825 | |||
| 1826 | fde = try Dwarf.FrameDescriptionEntry.parse( | ||
| 1827 | fde_entry_header.entry_bytes, | ||
| 1828 | 0, | ||
| 1829 | true, | ||
| 1830 | cie, | ||
| 1831 | @sizeOf(usize), | ||
| 1832 | native_endian, | ||
| 1833 | ); | ||
| 1834 | } else if (di.eh_frame_hdr) |header| { | ||
| 1835 | const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else null; | ||
| 1836 | try header.findEntry( | ||
| 1837 | ma, | ||
| 1838 | eh_frame_len, | ||
| 1839 | @intFromPtr(di.section(.eh_frame_hdr).?.ptr), | ||
| 1840 | context.pc, | ||
| 1841 | &cie, | ||
| 1842 | &fde, | ||
| 1843 | ); | ||
| 1844 | } else { | ||
| 1845 | const index = std.sort.binarySearch(Dwarf.FrameDescriptionEntry, context.pc, di.fde_list.items, {}, struct { | ||
| 1846 | pub fn compareFn(_: void, pc: usize, mid_item: Dwarf.FrameDescriptionEntry) std.math.Order { | ||
| 1847 | if (pc < mid_item.pc_begin) return .lt; | ||
| 1848 | |||
| 1849 | const range_end = mid_item.pc_begin + mid_item.pc_range; | ||
| 1850 | if (pc < range_end) return .eq; | ||
| 1851 | |||
| 1852 | return .gt; | ||
| 1853 | } | ||
| 1854 | }.compareFn); | ||
| 1855 | |||
| 1856 | fde = if (index) |i| di.fde_list.items[i] else return error.MissingFDE; | ||
| 1857 | cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE; | ||
| 1858 | } | ||
| 1859 | |||
| 1860 | var expression_context: Dwarf.expression.Context = .{ | ||
| 1861 | .format = cie.format, | ||
| 1862 | .memory_accessor = ma, | ||
| 1863 | .compile_unit = di.findCompileUnit(fde.pc_begin) catch null, | ||
| 1864 | .thread_context = context.thread_context, | ||
| 1865 | .reg_context = context.reg_context, | ||
| 1866 | .cfa = context.cfa, | ||
| 1867 | }; | ||
| 1868 | |||
| 1869 | context.vm.reset(); | ||
| 1870 | context.reg_context.eh_frame = cie.version != 4; | ||
| 1871 | context.reg_context.is_macho = di.is_macho; | ||
| 1872 | |||
| 1873 | const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde); | ||
| 1874 | context.cfa = switch (row.cfa.rule) { | ||
| 1875 | .val_offset => |offset| blk: { | ||
| 1876 | const register = row.cfa.register orelse return error.InvalidCFARule; | ||
| 1877 | const value = mem.readInt(usize, (try regBytes(context.thread_context, register, context.reg_context))[0..@sizeOf(usize)], native_endian); | ||
| 1878 | break :blk try applyOffset(value, offset); | ||
| 1879 | }, | ||
| 1880 | .expression => |expr| blk: { | ||
| 1881 | context.stack_machine.reset(); | ||
| 1882 | const value = try context.stack_machine.run( | ||
| 1883 | expr, | ||
| 1884 | context.allocator, | ||
| 1885 | expression_context, | ||
| 1886 | context.cfa, | ||
| 1887 | ); | ||
| 1888 | |||
| 1889 | if (value) |v| { | ||
| 1890 | if (v != .generic) return error.InvalidExpressionValue; | ||
| 1891 | break :blk v.generic; | ||
| 1892 | } else return error.NoExpressionValue; | ||
| 1893 | }, | ||
| 1894 | else => return error.InvalidCFARule, | ||
| 1895 | }; | ||
| 1896 | |||
| 1897 | if (ma.load(usize, context.cfa.?) == null) return error.InvalidCFA; | ||
| 1898 | expression_context.cfa = context.cfa; | ||
| 1899 | |||
| 1900 | // Buffering the modifications is done because copying the thread context is not portable, | ||
| 1901 | // some implementations (ie. darwin) use internal pointers to the mcontext. | ||
| 1902 | var arena = std.heap.ArenaAllocator.init(context.allocator); | ||
| 1903 | defer arena.deinit(); | ||
| 1904 | const update_allocator = arena.allocator(); | ||
| 1905 | |||
| 1906 | const RegisterUpdate = struct { | ||
| 1907 | // Backed by thread_context | ||
| 1908 | dest: []u8, | ||
| 1909 | // Backed by arena | ||
| 1910 | src: []const u8, | ||
| 1911 | prev: ?*@This(), | ||
| 1912 | }; | ||
| 1913 | |||
| 1914 | var update_tail: ?*RegisterUpdate = null; | ||
| 1915 | var has_return_address = true; | ||
| 1916 | for (context.vm.rowColumns(row)) |column| { | ||
| 1917 | if (column.register) |register| { | ||
| 1918 | if (register == cie.return_address_register) { | ||
| 1919 | has_return_address = column.rule != .undefined; | ||
| 1920 | } | ||
| 1921 | |||
| 1922 | const dest = try regBytes(context.thread_context, register, context.reg_context); | ||
| 1923 | const src = try update_allocator.alloc(u8, dest.len); | ||
| 1924 | |||
| 1925 | const prev = update_tail; | ||
| 1926 | update_tail = try update_allocator.create(RegisterUpdate); | ||
| 1927 | update_tail.?.* = .{ | ||
| 1928 | .dest = dest, | ||
| 1929 | .src = src, | ||
| 1930 | .prev = prev, | ||
| 1931 | }; | ||
| 1932 | |||
| 1933 | try column.resolveValue( | ||
| 1934 | context, | ||
| 1935 | expression_context, | ||
| 1936 | ma, | ||
| 1937 | src, | ||
| 1938 | ); | ||
| 1939 | } | ||
| 1940 | } | ||
| 1941 | |||
| 1942 | // On all implemented architectures, the CFA is defined as being the previous frame's SP | ||
| 1943 | (try regValueNative(context.thread_context, spRegNum(context.reg_context), context.reg_context)).* = context.cfa.?; | ||
| 1944 | |||
| 1945 | while (update_tail) |tail| { | ||
| 1946 | @memcpy(tail.dest, tail.src); | ||
| 1947 | update_tail = tail.prev; | ||
| 1948 | } | ||
| 1949 | |||
| 1950 | if (has_return_address) { | ||
| 1951 | context.pc = stripInstructionPtrAuthCode(mem.readInt(usize, (try regBytes( | ||
| 1952 | context.thread_context, | ||
| 1953 | cie.return_address_register, | ||
| 1954 | context.reg_context, | ||
| 1955 | ))[0..@sizeOf(usize)], native_endian)); | ||
| 1956 | } else { | ||
| 1957 | context.pc = 0; | ||
| 1958 | } | ||
| 1959 | |||
| 1960 | (try regValueNative(context.thread_context, ip_reg_num, context.reg_context)).* = context.pc; | ||
| 1961 | |||
| 1962 | // The call instruction will have pushed the address of the instruction that follows the call as the return address. | ||
| 1963 | // This next instruction may be past the end of the function if the caller was `noreturn` (ie. the last instruction in | ||
| 1964 | // the function was the call). If we were to look up an FDE entry using the return address directly, it could end up | ||
| 1965 | // either not finding an FDE at all, or using the next FDE in the program, producing incorrect results. To prevent this, | ||
| 1966 | // we subtract one so that the next lookup is guaranteed to land inside the | ||
| 1967 | // | ||
| 1968 | // The exception to this rule is signal frames, where we return execution would be returned to the instruction | ||
| 1969 | // that triggered the handler. | ||
| 1970 | const return_address = context.pc; | ||
| 1971 | if (context.pc > 0 and !cie.isSignalFrame()) context.pc -= 1; | ||
| 1972 | |||
| 1973 | return return_address; | ||
| 1974 | } | ||
| 1975 | |||
| 1976 | fn fpRegNum(reg_context: Dwarf.abi.RegisterContext) u8 { | ||
| 1977 | return Dwarf.abi.fpRegNum(native_arch, reg_context); | ||
| 1978 | } | ||
| 1979 | |||
| 1980 | fn spRegNum(reg_context: Dwarf.abi.RegisterContext) u8 { | ||
| 1981 | return Dwarf.abi.spRegNum(native_arch, reg_context); | ||
| 1982 | } | ||
| 1983 | |||
| 1984 | const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?; | ||
| 1985 | |||
| 1986 | /// Tells whether unwinding for the host is implemented. | ||
| 1987 | pub const supports_unwinding = supportsUnwinding(builtin.target); | ||
| 1988 | |||
| 1989 | comptime { | ||
| 1990 | if (supports_unwinding) assert(Dwarf.abi.supportsUnwinding(builtin.target)); | ||
| 1991 | } | ||
| 1992 | |||
| 1993 | /// Tells whether unwinding for this target is *implemented* here in the Zig | ||
| 1994 | /// standard library. | ||
| 1995 | /// | ||
| 1996 | /// See also `Dwarf.abi.supportsUnwinding` which tells whether Dwarf supports | ||
| 1997 | /// unwinding on that target *in theory*. | ||
| 1998 | pub fn supportsUnwinding(target: std.Target) bool { | ||
| 1999 | return switch (target.cpu.arch) { | ||
| 2000 | .x86 => switch (target.os.tag) { | ||
| 2001 | .linux, .netbsd, .solaris, .illumos => true, | ||
| 2002 | else => false, | ||
| 2003 | }, | ||
| 2004 | .x86_64 => switch (target.os.tag) { | ||
| 2005 | .linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true, | ||
| 2006 | else => false, | ||
| 2007 | }, | ||
| 2008 | .arm => switch (target.os.tag) { | ||
| 2009 | .linux => true, | ||
| 2010 | else => false, | ||
| 2011 | }, | ||
| 2012 | .aarch64 => switch (target.os.tag) { | ||
| 2013 | .linux, .netbsd, .freebsd, .macos, .ios => true, | ||
| 2014 | else => false, | ||
| 2015 | }, | ||
| 2016 | // Unwinding is possible on other targets but this implementation does | ||
| 2017 | // not support them...yet! | ||
| 2018 | else => false, | ||
| 2019 | }; | ||
| 2020 | } | ||
| 2021 | |||
| 2022 | fn unwindFrameMachODwarf( | ||
| 2023 | context: *UnwindContext, | ||
| 2024 | ma: *std.debug.MemoryAccessor, | ||
| 2025 | eh_frame: []const u8, | ||
| 2026 | fde_offset: usize, | ||
| 2027 | ) !usize { | ||
| 2028 | var di: Dwarf = .{ | ||
| 2029 | .endian = native_endian, | ||
| 2030 | .is_macho = true, | ||
| 2031 | }; | ||
| 2032 | defer di.deinit(context.allocator); | ||
| 2033 | |||
| 2034 | di.sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{ | ||
| 2035 | .data = eh_frame, | ||
| 2036 | .owned = false, | ||
| 2037 | }; | ||
| 2038 | |||
| 2039 | return unwindFrameDwarf(&di, context, ma, fde_offset); | ||
| 2040 | } | ||
| 2041 | |||
| 2042 | /// This is a virtual machine that runs DWARF call frame instructions. | ||
| 2043 | pub const VirtualMachine = struct { | ||
| 2044 | /// See section 6.4.1 of the DWARF5 specification for details on each | ||
| 2045 | const RegisterRule = union(enum) { | ||
| 2046 | // The spec says that the default rule for each column is the undefined rule. | ||
| 2047 | // However, it also allows ABI / compiler authors to specify alternate defaults, so | ||
| 2048 | // there is a distinction made here. | ||
| 2049 | default: void, | ||
| 2050 | undefined: void, | ||
| 2051 | same_value: void, | ||
| 2052 | // offset(N) | ||
| 2053 | offset: i64, | ||
| 2054 | // val_offset(N) | ||
| 2055 | val_offset: i64, | ||
| 2056 | // register(R) | ||
| 2057 | register: u8, | ||
| 2058 | // expression(E) | ||
| 2059 | expression: []const u8, | ||
| 2060 | // val_expression(E) | ||
| 2061 | val_expression: []const u8, | ||
| 2062 | // Augmenter-defined rule | ||
| 2063 | architectural: void, | ||
| 2064 | }; | ||
| 2065 | |||
| 2066 | /// Each row contains unwinding rules for a set of registers. | ||
| 2067 | pub const Row = struct { | ||
| 2068 | /// Offset from `FrameDescriptionEntry.pc_begin` | ||
| 2069 | offset: u64 = 0, | ||
| 2070 | /// Special-case column that defines the CFA (Canonical Frame Address) rule. | ||
| 2071 | /// The register field of this column defines the register that CFA is derived from. | ||
| 2072 | cfa: Column = .{}, | ||
| 2073 | /// The register fields in these columns define the register the rule applies to. | ||
| 2074 | columns: ColumnRange = .{}, | ||
| 2075 | /// Indicates that the next write to any column in this row needs to copy | ||
| 2076 | /// the backing column storage first, as it may be referenced by previous rows. | ||
| 2077 | copy_on_write: bool = false, | ||
| 2078 | }; | ||
| 2079 | |||
| 2080 | pub const Column = struct { | ||
| 2081 | register: ?u8 = null, | ||
| 2082 | rule: RegisterRule = .{ .default = {} }, | ||
| 2083 | |||
| 2084 | /// Resolves the register rule and places the result into `out` (see regBytes) | ||
| 2085 | pub fn resolveValue( | ||
| 2086 | self: Column, | ||
| 2087 | context: *SelfInfo.UnwindContext, | ||
| 2088 | expression_context: std.debug.Dwarf.expression.Context, | ||
| 2089 | ma: *std.debug.MemoryAccessor, | ||
| 2090 | out: []u8, | ||
| 2091 | ) !void { | ||
| 2092 | switch (self.rule) { | ||
| 2093 | .default => { | ||
| 2094 | const register = self.register orelse return error.InvalidRegister; | ||
| 2095 | try getRegDefaultValue(register, context, out); | ||
| 2096 | }, | ||
| 2097 | .undefined => { | ||
| 2098 | @memset(out, undefined); | ||
| 2099 | }, | ||
| 2100 | .same_value => { | ||
| 2101 | // TODO: This copy could be eliminated if callers always copy the state then call this function to update it | ||
| 2102 | const register = self.register orelse return error.InvalidRegister; | ||
| 2103 | const src = try regBytes(context.thread_context, register, context.reg_context); | ||
| 2104 | if (src.len != out.len) return error.RegisterSizeMismatch; | ||
| 2105 | @memcpy(out, src); | ||
| 2106 | }, | ||
| 2107 | .offset => |offset| { | ||
| 2108 | if (context.cfa) |cfa| { | ||
| 2109 | const addr = try applyOffset(cfa, offset); | ||
| 2110 | if (ma.load(usize, addr) == null) return error.InvalidAddress; | ||
| 2111 | const ptr: *const usize = @ptrFromInt(addr); | ||
| 2112 | mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian); | ||
| 2113 | } else return error.InvalidCFA; | ||
| 2114 | }, | ||
| 2115 | .val_offset => |offset| { | ||
| 2116 | if (context.cfa) |cfa| { | ||
| 2117 | mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian); | ||
| 2118 | } else return error.InvalidCFA; | ||
| 2119 | }, | ||
| 2120 | .register => |register| { | ||
| 2121 | const src = try regBytes(context.thread_context, register, context.reg_context); | ||
| 2122 | if (src.len != out.len) return error.RegisterSizeMismatch; | ||
| 2123 | @memcpy(out, try regBytes(context.thread_context, register, context.reg_context)); | ||
| 2124 | }, | ||
| 2125 | .expression => |expression| { | ||
| 2126 | context.stack_machine.reset(); | ||
| 2127 | const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?); | ||
| 2128 | const addr = if (value) |v| blk: { | ||
| 2129 | if (v != .generic) return error.InvalidExpressionValue; | ||
| 2130 | break :blk v.generic; | ||
| 2131 | } else return error.NoExpressionValue; | ||
| 2132 | |||
| 2133 | if (ma.load(usize, addr) == null) return error.InvalidExpressionAddress; | ||
| 2134 | const ptr: *usize = @ptrFromInt(addr); | ||
| 2135 | mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian); | ||
| 2136 | }, | ||
| 2137 | .val_expression => |expression| { | ||
| 2138 | context.stack_machine.reset(); | ||
| 2139 | const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?); | ||
| 2140 | if (value) |v| { | ||
| 2141 | if (v != .generic) return error.InvalidExpressionValue; | ||
| 2142 | mem.writeInt(usize, out[0..@sizeOf(usize)], v.generic, native_endian); | ||
| 2143 | } else return error.NoExpressionValue; | ||
| 2144 | }, | ||
| 2145 | .architectural => return error.UnimplementedRegisterRule, | ||
| 2146 | } | ||
| 2147 | } | ||
| 2148 | }; | ||
| 2149 | |||
| 2150 | const ColumnRange = struct { | ||
| 2151 | /// Index into `columns` of the first column in this row. | ||
| 2152 | start: usize = undefined, | ||
| 2153 | len: u8 = 0, | ||
| 2154 | }; | ||
| 2155 | |||
| 2156 | columns: std.ArrayListUnmanaged(Column) = .{}, | ||
| 2157 | stack: std.ArrayListUnmanaged(ColumnRange) = .{}, | ||
| 2158 | current_row: Row = .{}, | ||
| 2159 | |||
| 2160 | /// The result of executing the CIE's initial_instructions | ||
| 2161 | cie_row: ?Row = null, | ||
| 2162 | |||
| 2163 | pub fn deinit(self: *VirtualMachine, allocator: std.mem.Allocator) void { | ||
| 2164 | self.stack.deinit(allocator); | ||
| 2165 | self.columns.deinit(allocator); | ||
| 2166 | self.* = undefined; | ||
| 2167 | } | ||
| 2168 | |||
| 2169 | pub fn reset(self: *VirtualMachine) void { | ||
| 2170 | self.stack.clearRetainingCapacity(); | ||
| 2171 | self.columns.clearRetainingCapacity(); | ||
| 2172 | self.current_row = .{}; | ||
| 2173 | self.cie_row = null; | ||
| 2174 | } | ||
| 2175 | |||
| 2176 | /// Return a slice backed by the row's non-CFA columns | ||
| 2177 | pub fn rowColumns(self: VirtualMachine, row: Row) []Column { | ||
| 2178 | if (row.columns.len == 0) return &.{}; | ||
| 2179 | return self.columns.items[row.columns.start..][0..row.columns.len]; | ||
| 2180 | } | ||
| 2181 | |||
| 2182 | /// Either retrieves or adds a column for `register` (non-CFA) in the current row. | ||
| 2183 | fn getOrAddColumn(self: *VirtualMachine, allocator: std.mem.Allocator, register: u8) !*Column { | ||
| 2184 | for (self.rowColumns(self.current_row)) |*c| { | ||
| 2185 | if (c.register == register) return c; | ||
| 2186 | } | ||
| 2187 | |||
| 2188 | if (self.current_row.columns.len == 0) { | ||
| 2189 | self.current_row.columns.start = self.columns.items.len; | ||
| 2190 | } | ||
| 2191 | self.current_row.columns.len += 1; | ||
| 2192 | |||
| 2193 | const column = try self.columns.addOne(allocator); | ||
| 2194 | column.* = .{ | ||
| 2195 | .register = register, | ||
| 2196 | }; | ||
| 2197 | |||
| 2198 | return column; | ||
| 2199 | } | ||
| 2200 | |||
| 2201 | /// Runs the CIE instructions, then the FDE instructions. Execution halts | ||
| 2202 | /// once the row that corresponds to `pc` is known, and the row is returned. | ||
| 2203 | pub fn runTo( | ||
| 2204 | self: *VirtualMachine, | ||
| 2205 | allocator: std.mem.Allocator, | ||
| 2206 | pc: u64, | ||
| 2207 | cie: std.debug.Dwarf.CommonInformationEntry, | ||
| 2208 | fde: std.debug.Dwarf.FrameDescriptionEntry, | ||
| 2209 | addr_size_bytes: u8, | ||
| 2210 | endian: std.builtin.Endian, | ||
| 2211 | ) !Row { | ||
| 2212 | assert(self.cie_row == null); | ||
| 2213 | if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return error.AddressOutOfRange; | ||
| 2214 | |||
| 2215 | var prev_row: Row = self.current_row; | ||
| 2216 | |||
| 2217 | var cie_stream = std.io.fixedBufferStream(cie.initial_instructions); | ||
| 2218 | var fde_stream = std.io.fixedBufferStream(fde.instructions); | ||
| 2219 | var streams = [_]*std.io.FixedBufferStream([]const u8){ | ||
| 2220 | &cie_stream, | ||
| 2221 | &fde_stream, | ||
| 2222 | }; | ||
| 2223 | |||
| 2224 | for (&streams, 0..) |stream, i| { | ||
| 2225 | while (stream.pos < stream.buffer.len) { | ||
| 2226 | const instruction = try std.debug.Dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian); | ||
| 2227 | prev_row = try self.step(allocator, cie, i == 0, instruction); | ||
| 2228 | if (pc < fde.pc_begin + self.current_row.offset) return prev_row; | ||
| 2229 | } | ||
| 2230 | } | ||
| 2231 | |||
| 2232 | return self.current_row; | ||
| 2233 | } | ||
| 2234 | |||
| 2235 | pub fn runToNative( | ||
| 2236 | self: *VirtualMachine, | ||
| 2237 | allocator: std.mem.Allocator, | ||
| 2238 | pc: u64, | ||
| 2239 | cie: std.debug.Dwarf.CommonInformationEntry, | ||
| 2240 | fde: std.debug.Dwarf.FrameDescriptionEntry, | ||
| 2241 | ) !Row { | ||
| 2242 | return self.runTo(allocator, pc, cie, fde, @sizeOf(usize), native_endian); | ||
| 2243 | } | ||
| 2244 | |||
| 2245 | fn resolveCopyOnWrite(self: *VirtualMachine, allocator: std.mem.Allocator) !void { | ||
| 2246 | if (!self.current_row.copy_on_write) return; | ||
| 2247 | |||
| 2248 | const new_start = self.columns.items.len; | ||
| 2249 | if (self.current_row.columns.len > 0) { | ||
| 2250 | try self.columns.ensureUnusedCapacity(allocator, self.current_row.columns.len); | ||
| 2251 | self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row)); | ||
| 2252 | self.current_row.columns.start = new_start; | ||
| 2253 | } | ||
| 2254 | } | ||
| 2255 | |||
| 2256 | /// Executes a single instruction. | ||
| 2257 | /// If this instruction is from the CIE, `is_initial` should be set. | ||
| 2258 | /// Returns the value of `current_row` before executing this instruction. | ||
| 2259 | pub fn step( | ||
| 2260 | self: *VirtualMachine, | ||
| 2261 | allocator: std.mem.Allocator, | ||
| 2262 | cie: std.debug.Dwarf.CommonInformationEntry, | ||
| 2263 | is_initial: bool, | ||
| 2264 | instruction: Dwarf.call_frame.Instruction, | ||
| 2265 | ) !Row { | ||
| 2266 | // CIE instructions must be run before FDE instructions | ||
| 2267 | assert(!is_initial or self.cie_row == null); | ||
| 2268 | if (!is_initial and self.cie_row == null) { | ||
| 2269 | self.cie_row = self.current_row; | ||
| 2270 | self.current_row.copy_on_write = true; | ||
| 2271 | } | ||
| 2272 | |||
| 2273 | const prev_row = self.current_row; | ||
| 2274 | switch (instruction) { | ||
| 2275 | .set_loc => |i| { | ||
| 2276 | if (i.address <= self.current_row.offset) return error.InvalidOperation; | ||
| 2277 | // TODO: Check cie.segment_selector_size != 0 for DWARFV4 | ||
| 2278 | self.current_row.offset = i.address; | ||
| 2279 | }, | ||
| 2280 | inline .advance_loc, | ||
| 2281 | .advance_loc1, | ||
| 2282 | .advance_loc2, | ||
| 2283 | .advance_loc4, | ||
| 2284 | => |i| { | ||
| 2285 | self.current_row.offset += i.delta * cie.code_alignment_factor; | ||
| 2286 | self.current_row.copy_on_write = true; | ||
| 2287 | }, | ||
| 2288 | inline .offset, | ||
| 2289 | .offset_extended, | ||
| 2290 | .offset_extended_sf, | ||
| 2291 | => |i| { | ||
| 2292 | try self.resolveCopyOnWrite(allocator); | ||
| 2293 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 2294 | column.rule = .{ .offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor }; | ||
| 2295 | }, | ||
| 2296 | inline .restore, | ||
| 2297 | .restore_extended, | ||
| 2298 | => |i| { | ||
| 2299 | try self.resolveCopyOnWrite(allocator); | ||
| 2300 | if (self.cie_row) |cie_row| { | ||
| 2301 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 2302 | column.rule = for (self.rowColumns(cie_row)) |cie_column| { | ||
| 2303 | if (cie_column.register == i.register) break cie_column.rule; | ||
| 2304 | } else .{ .default = {} }; | ||
| 2305 | } else return error.InvalidOperation; | ||
| 2306 | }, | ||
| 2307 | .nop => {}, | ||
| 2308 | .undefined => |i| { | ||
| 2309 | try self.resolveCopyOnWrite(allocator); | ||
| 2310 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 2311 | column.rule = .{ .undefined = {} }; | ||
| 2312 | }, | ||
| 2313 | .same_value => |i| { | ||
| 2314 | try self.resolveCopyOnWrite(allocator); | ||
| 2315 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 2316 | column.rule = .{ .same_value = {} }; | ||
| 2317 | }, | ||
| 2318 | .register => |i| { | ||
| 2319 | try self.resolveCopyOnWrite(allocator); | ||
| 2320 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 2321 | column.rule = .{ .register = i.target_register }; | ||
| 2322 | }, | ||
| 2323 | .remember_state => { | ||
| 2324 | try self.stack.append(allocator, self.current_row.columns); | ||
| 2325 | self.current_row.copy_on_write = true; | ||
| 2326 | }, | ||
| 2327 | .restore_state => { | ||
| 2328 | const restored_columns = self.stack.popOrNull() orelse return error.InvalidOperation; | ||
| 2329 | self.columns.shrinkRetainingCapacity(self.columns.items.len - self.current_row.columns.len); | ||
| 2330 | try self.columns.ensureUnusedCapacity(allocator, restored_columns.len); | ||
| 2331 | |||
| 2332 | self.current_row.columns.start = self.columns.items.len; | ||
| 2333 | self.current_row.columns.len = restored_columns.len; | ||
| 2334 | self.columns.appendSliceAssumeCapacity(self.columns.items[restored_columns.start..][0..restored_columns.len]); | ||
| 2335 | }, | ||
| 2336 | .def_cfa => |i| { | ||
| 2337 | try self.resolveCopyOnWrite(allocator); | ||
| 2338 | self.current_row.cfa = .{ | ||
| 2339 | .register = i.register, | ||
| 2340 | .rule = .{ .val_offset = @intCast(i.offset) }, | ||
| 2341 | }; | ||
| 2342 | }, | ||
| 2343 | .def_cfa_sf => |i| { | ||
| 2344 | try self.resolveCopyOnWrite(allocator); | ||
| 2345 | self.current_row.cfa = .{ | ||
| 2346 | .register = i.register, | ||
| 2347 | .rule = .{ .val_offset = i.offset * cie.data_alignment_factor }, | ||
| 2348 | }; | ||
| 2349 | }, | ||
| 2350 | .def_cfa_register => |i| { | ||
| 2351 | try self.resolveCopyOnWrite(allocator); | ||
| 2352 | if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; | ||
| 2353 | self.current_row.cfa.register = i.register; | ||
| 2354 | }, | ||
| 2355 | .def_cfa_offset => |i| { | ||
| 2356 | try self.resolveCopyOnWrite(allocator); | ||
| 2357 | if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; | ||
| 2358 | self.current_row.cfa.rule = .{ | ||
| 2359 | .val_offset = @intCast(i.offset), | ||
| 2360 | }; | ||
| 2361 | }, | ||
| 2362 | .def_cfa_offset_sf => |i| { | ||
| 2363 | try self.resolveCopyOnWrite(allocator); | ||
| 2364 | if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; | ||
| 2365 | self.current_row.cfa.rule = .{ | ||
| 2366 | .val_offset = i.offset * cie.data_alignment_factor, | ||
| 2367 | }; | ||
| 2368 | }, | ||
| 2369 | .def_cfa_expression => |i| { | ||
| 2370 | try self.resolveCopyOnWrite(allocator); | ||
| 2371 | self.current_row.cfa.register = undefined; | ||
| 2372 | self.current_row.cfa.rule = .{ | ||
| 2373 | .expression = i.block, | ||
| 2374 | }; | ||
| 2375 | }, | ||
| 2376 | .expression => |i| { | ||
| 2377 | try self.resolveCopyOnWrite(allocator); | ||
| 2378 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 2379 | column.rule = .{ | ||
| 2380 | .expression = i.block, | ||
| 2381 | }; | ||
| 2382 | }, | ||
| 2383 | .val_offset => |i| { | ||
| 2384 | try self.resolveCopyOnWrite(allocator); | ||
| 2385 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 2386 | column.rule = .{ | ||
| 2387 | .val_offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor, | ||
| 2388 | }; | ||
| 2389 | }, | ||
| 2390 | .val_offset_sf => |i| { | ||
| 2391 | try self.resolveCopyOnWrite(allocator); | ||
| 2392 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 2393 | column.rule = .{ | ||
| 2394 | .val_offset = i.offset * cie.data_alignment_factor, | ||
| 2395 | }; | ||
| 2396 | }, | ||
| 2397 | .val_expression => |i| { | ||
| 2398 | try self.resolveCopyOnWrite(allocator); | ||
| 2399 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 2400 | column.rule = .{ | ||
| 2401 | .val_expression = i.block, | ||
| 2402 | }; | ||
| 2403 | }, | ||
| 2404 | } | ||
| 2405 | |||
| 2406 | return prev_row; | ||
| 2407 | } | ||
| 2408 | }; | ||
| 2409 | |||
| 2410 | /// Returns the ABI-defined default value this register has in the unwinding table | ||
| 2411 | /// before running any of the CIE instructions. The DWARF spec defines these as having | ||
| 2412 | /// the .undefined rule by default, but allows ABI authors to override that. | ||
| 2413 | fn getRegDefaultValue(reg_number: u8, context: *UnwindContext, out: []u8) !void { | ||
| 2414 | switch (builtin.cpu.arch) { | ||
| 2415 | .aarch64 => { | ||
| 2416 | // Callee-saved registers are initialized as if they had the .same_value rule | ||
| 2417 | if (reg_number >= 19 and reg_number <= 28) { | ||
| 2418 | const src = try regBytes(context.thread_context, reg_number, context.reg_context); | ||
| 2419 | if (src.len != out.len) return error.RegisterSizeMismatch; | ||
| 2420 | @memcpy(out, src); | ||
| 2421 | return; | ||
| 2422 | } | ||
| 2423 | }, | ||
| 2424 | else => {}, | ||
| 2425 | } | ||
| 2426 | |||
| 2427 | @memset(out, undefined); | ||
| 2428 | } | ||
| 2429 | |||
| 2430 | /// Since register rules are applied (usually) during a panic, | ||
| 2431 | /// checked addition / subtraction is used so that we can return | ||
| 2432 | /// an error and fall back to FP-based unwinding. | ||
| 2433 | fn applyOffset(base: usize, offset: i64) !usize { | ||
| 2434 | return if (offset >= 0) | ||
| 2435 | try std.math.add(usize, base, @as(usize, @intCast(offset))) | ||
| 2436 | else | ||
| 2437 | try std.math.sub(usize, base, @as(usize, @intCast(-offset))); | ||
| 2438 | } | ||
lib/std/pdb.zig+11-596| ... | @@ -1,3 +1,12 @@ | ... | @@ -1,3 +1,12 @@ |
| 1 | //! Program Data Base debugging information format. | ||
| 2 | //! | ||
| 3 | //! This namespace contains unopinionated types and data definitions only. For | ||
| 4 | //! an implementation of parsing and caching PDB information, see | ||
| 5 | //! `std.debug.Pdb`. | ||
| 6 | //! | ||
| 7 | //! Most of this is based on information gathered from LLVM source code, | ||
| 8 | //! documentation and/or contributors. | ||
| 9 | |||
| 1 | const std = @import("std.zig"); | 10 | const std = @import("std.zig"); |
| 2 | const io = std.io; | 11 | const io = std.io; |
| 3 | const math = std.math; | 12 | const math = std.math; |
| ... | @@ -9,10 +18,7 @@ const debug = std.debug; | ... | @@ -9,10 +18,7 @@ const debug = std.debug; |
| 9 | 18 | ||
| 10 | const ArrayList = std.ArrayList; | 19 | const ArrayList = std.ArrayList; |
| 11 | 20 | ||
| 12 | // Note: most of this is based on information gathered from LLVM source code, | 21 | /// https://llvm.org/docs/PDB/DbiStream.html#stream-header |
| 13 | // documentation and/or contributors. | ||
| 14 | |||
| 15 | // https://llvm.org/docs/PDB/DbiStream.html#stream-header | ||
| 16 | pub const DbiStreamHeader = extern struct { | 22 | pub const DbiStreamHeader = extern struct { |
| 17 | VersionSignature: i32, | 23 | VersionSignature: i32, |
| 18 | VersionHeader: u32, | 24 | VersionHeader: u32, |
| ... | @@ -415,10 +421,8 @@ pub const ColumnNumberEntry = extern struct { | ... | @@ -415,10 +421,8 @@ pub const ColumnNumberEntry = extern struct { |
| 415 | pub const FileChecksumEntryHeader = extern struct { | 421 | pub const FileChecksumEntryHeader = extern struct { |
| 416 | /// Byte offset of filename in global string table. | 422 | /// Byte offset of filename in global string table. |
| 417 | FileNameOffset: u32, | 423 | FileNameOffset: u32, |
| 418 | |||
| 419 | /// Number of bytes of checksum. | 424 | /// Number of bytes of checksum. |
| 420 | ChecksumSize: u8, | 425 | ChecksumSize: u8, |
| 421 | |||
| 422 | /// FileChecksumKind | 426 | /// FileChecksumKind |
| 423 | ChecksumKind: u8, | 427 | ChecksumKind: u8, |
| 424 | }; | 428 | }; |
| ... | @@ -451,525 +455,15 @@ pub const DebugSubsectionHeader = extern struct { | ... | @@ -451,525 +455,15 @@ pub const DebugSubsectionHeader = extern struct { |
| 451 | Length: u32, | 455 | Length: u32, |
| 452 | }; | 456 | }; |
| 453 | 457 | ||
| 454 | pub const PDBStringTableHeader = extern struct { | 458 | pub const StringTableHeader = extern struct { |
| 455 | /// PDBStringTableSignature | 459 | /// PDBStringTableSignature |
| 456 | Signature: u32, | 460 | Signature: u32, |
| 457 | |||
| 458 | /// 1 or 2 | 461 | /// 1 or 2 |
| 459 | HashVersion: u32, | 462 | HashVersion: u32, |
| 460 | |||
| 461 | /// Number of bytes of names buffer. | 463 | /// Number of bytes of names buffer. |
| 462 | ByteSize: u32, | 464 | ByteSize: u32, |
| 463 | }; | 465 | }; |
| 464 | 466 | ||
| 465 | fn readSparseBitVector(stream: anytype, allocator: mem.Allocator) ![]u32 { | ||
| 466 | const num_words = try stream.readInt(u32, .little); | ||
| 467 | var list = ArrayList(u32).init(allocator); | ||
| 468 | errdefer list.deinit(); | ||
| 469 | var word_i: u32 = 0; | ||
| 470 | while (word_i != num_words) : (word_i += 1) { | ||
| 471 | const word = try stream.readInt(u32, .little); | ||
| 472 | var bit_i: u5 = 0; | ||
| 473 | while (true) : (bit_i += 1) { | ||
| 474 | if (word & (@as(u32, 1) << bit_i) != 0) { | ||
| 475 | try list.append(word_i * 32 + bit_i); | ||
| 476 | } | ||
| 477 | if (bit_i == std.math.maxInt(u5)) break; | ||
| 478 | } | ||
| 479 | } | ||
| 480 | return try list.toOwnedSlice(); | ||
| 481 | } | ||
| 482 | |||
| 483 | pub const Pdb = struct { | ||
| 484 | in_file: File, | ||
| 485 | msf: Msf, | ||
| 486 | allocator: mem.Allocator, | ||
| 487 | string_table: ?*MsfStream, | ||
| 488 | dbi: ?*MsfStream, | ||
| 489 | modules: []Module, | ||
| 490 | sect_contribs: []SectionContribEntry, | ||
| 491 | guid: [16]u8, | ||
| 492 | age: u32, | ||
| 493 | |||
| 494 | pub const Module = struct { | ||
| 495 | mod_info: ModInfo, | ||
| 496 | module_name: []u8, | ||
| 497 | obj_file_name: []u8, | ||
| 498 | // The fields below are filled on demand. | ||
| 499 | populated: bool, | ||
| 500 | symbols: []u8, | ||
| 501 | subsect_info: []u8, | ||
| 502 | checksum_offset: ?usize, | ||
| 503 | |||
| 504 | pub fn deinit(self: *Module, allocator: mem.Allocator) void { | ||
| 505 | allocator.free(self.module_name); | ||
| 506 | allocator.free(self.obj_file_name); | ||
| 507 | if (self.populated) { | ||
| 508 | allocator.free(self.symbols); | ||
| 509 | allocator.free(self.subsect_info); | ||
| 510 | } | ||
| 511 | } | ||
| 512 | }; | ||
| 513 | |||
| 514 | pub fn init(allocator: mem.Allocator, path: []const u8) !Pdb { | ||
| 515 | const file = try fs.cwd().openFile(path, .{}); | ||
| 516 | errdefer file.close(); | ||
| 517 | |||
| 518 | return Pdb{ | ||
| 519 | .in_file = file, | ||
| 520 | .allocator = allocator, | ||
| 521 | .string_table = null, | ||
| 522 | .dbi = null, | ||
| 523 | .msf = try Msf.init(allocator, file), | ||
| 524 | .modules = &[_]Module{}, | ||
| 525 | .sect_contribs = &[_]SectionContribEntry{}, | ||
| 526 | .guid = undefined, | ||
| 527 | .age = undefined, | ||
| 528 | }; | ||
| 529 | } | ||
| 530 | |||
| 531 | pub fn deinit(self: *Pdb) void { | ||
| 532 | self.in_file.close(); | ||
| 533 | self.msf.deinit(self.allocator); | ||
| 534 | for (self.modules) |*module| { | ||
| 535 | module.deinit(self.allocator); | ||
| 536 | } | ||
| 537 | self.allocator.free(self.modules); | ||
| 538 | self.allocator.free(self.sect_contribs); | ||
| 539 | } | ||
| 540 | |||
| 541 | pub fn parseDbiStream(self: *Pdb) !void { | ||
| 542 | var stream = self.getStream(StreamType.Dbi) orelse | ||
| 543 | return error.InvalidDebugInfo; | ||
| 544 | const reader = stream.reader(); | ||
| 545 | |||
| 546 | const header = try reader.readStruct(DbiStreamHeader); | ||
| 547 | if (header.VersionHeader != 19990903) // V70, only value observed by LLVM team | ||
| 548 | return error.UnknownPDBVersion; | ||
| 549 | // if (header.Age != age) | ||
| 550 | // return error.UnmatchingPDB; | ||
| 551 | |||
| 552 | const mod_info_size = header.ModInfoSize; | ||
| 553 | const section_contrib_size = header.SectionContributionSize; | ||
| 554 | |||
| 555 | var modules = ArrayList(Module).init(self.allocator); | ||
| 556 | errdefer modules.deinit(); | ||
| 557 | |||
| 558 | // Module Info Substream | ||
| 559 | var mod_info_offset: usize = 0; | ||
| 560 | while (mod_info_offset != mod_info_size) { | ||
| 561 | const mod_info = try reader.readStruct(ModInfo); | ||
| 562 | var this_record_len: usize = @sizeOf(ModInfo); | ||
| 563 | |||
| 564 | const module_name = try reader.readUntilDelimiterAlloc(self.allocator, 0, 1024); | ||
| 565 | errdefer self.allocator.free(module_name); | ||
| 566 | this_record_len += module_name.len + 1; | ||
| 567 | |||
| 568 | const obj_file_name = try reader.readUntilDelimiterAlloc(self.allocator, 0, 1024); | ||
| 569 | errdefer self.allocator.free(obj_file_name); | ||
| 570 | this_record_len += obj_file_name.len + 1; | ||
| 571 | |||
| 572 | if (this_record_len % 4 != 0) { | ||
| 573 | const round_to_next_4 = (this_record_len | 0x3) + 1; | ||
| 574 | const march_forward_bytes = round_to_next_4 - this_record_len; | ||
| 575 | try stream.seekBy(@as(isize, @intCast(march_forward_bytes))); | ||
| 576 | this_record_len += march_forward_bytes; | ||
| 577 | } | ||
| 578 | |||
| 579 | try modules.append(Module{ | ||
| 580 | .mod_info = mod_info, | ||
| 581 | .module_name = module_name, | ||
| 582 | .obj_file_name = obj_file_name, | ||
| 583 | |||
| 584 | .populated = false, | ||
| 585 | .symbols = undefined, | ||
| 586 | .subsect_info = undefined, | ||
| 587 | .checksum_offset = null, | ||
| 588 | }); | ||
| 589 | |||
| 590 | mod_info_offset += this_record_len; | ||
| 591 | if (mod_info_offset > mod_info_size) | ||
| 592 | return error.InvalidDebugInfo; | ||
| 593 | } | ||
| 594 | |||
| 595 | // Section Contribution Substream | ||
| 596 | var sect_contribs = ArrayList(SectionContribEntry).init(self.allocator); | ||
| 597 | errdefer sect_contribs.deinit(); | ||
| 598 | |||
| 599 | var sect_cont_offset: usize = 0; | ||
| 600 | if (section_contrib_size != 0) { | ||
| 601 | const version = reader.readEnum(SectionContrSubstreamVersion, .little) catch |err| switch (err) { | ||
| 602 | error.InvalidValue => return error.InvalidDebugInfo, | ||
| 603 | else => |e| return e, | ||
| 604 | }; | ||
| 605 | _ = version; | ||
| 606 | sect_cont_offset += @sizeOf(u32); | ||
| 607 | } | ||
| 608 | while (sect_cont_offset != section_contrib_size) { | ||
| 609 | const entry = try sect_contribs.addOne(); | ||
| 610 | entry.* = try reader.readStruct(SectionContribEntry); | ||
| 611 | sect_cont_offset += @sizeOf(SectionContribEntry); | ||
| 612 | |||
| 613 | if (sect_cont_offset > section_contrib_size) | ||
| 614 | return error.InvalidDebugInfo; | ||
| 615 | } | ||
| 616 | |||
| 617 | self.modules = try modules.toOwnedSlice(); | ||
| 618 | self.sect_contribs = try sect_contribs.toOwnedSlice(); | ||
| 619 | } | ||
| 620 | |||
| 621 | pub fn parseInfoStream(self: *Pdb) !void { | ||
| 622 | var stream = self.getStream(StreamType.Pdb) orelse | ||
| 623 | return error.InvalidDebugInfo; | ||
| 624 | const reader = stream.reader(); | ||
| 625 | |||
| 626 | // Parse the InfoStreamHeader. | ||
| 627 | const version = try reader.readInt(u32, .little); | ||
| 628 | const signature = try reader.readInt(u32, .little); | ||
| 629 | _ = signature; | ||
| 630 | const age = try reader.readInt(u32, .little); | ||
| 631 | const guid = try reader.readBytesNoEof(16); | ||
| 632 | |||
| 633 | if (version != 20000404) // VC70, only value observed by LLVM team | ||
| 634 | return error.UnknownPDBVersion; | ||
| 635 | |||
| 636 | self.guid = guid; | ||
| 637 | self.age = age; | ||
| 638 | |||
| 639 | // Find the string table. | ||
| 640 | const string_table_index = str_tab_index: { | ||
| 641 | const name_bytes_len = try reader.readInt(u32, .little); | ||
| 642 | const name_bytes = try self.allocator.alloc(u8, name_bytes_len); | ||
| 643 | defer self.allocator.free(name_bytes); | ||
| 644 | try reader.readNoEof(name_bytes); | ||
| 645 | |||
| 646 | const HashTableHeader = extern struct { | ||
| 647 | Size: u32, | ||
| 648 | Capacity: u32, | ||
| 649 | |||
| 650 | fn maxLoad(cap: u32) u32 { | ||
| 651 | return cap * 2 / 3 + 1; | ||
| 652 | } | ||
| 653 | }; | ||
| 654 | const hash_tbl_hdr = try reader.readStruct(HashTableHeader); | ||
| 655 | if (hash_tbl_hdr.Capacity == 0) | ||
| 656 | return error.InvalidDebugInfo; | ||
| 657 | |||
| 658 | if (hash_tbl_hdr.Size > HashTableHeader.maxLoad(hash_tbl_hdr.Capacity)) | ||
| 659 | return error.InvalidDebugInfo; | ||
| 660 | |||
| 661 | const present = try readSparseBitVector(&reader, self.allocator); | ||
| 662 | defer self.allocator.free(present); | ||
| 663 | if (present.len != hash_tbl_hdr.Size) | ||
| 664 | return error.InvalidDebugInfo; | ||
| 665 | const deleted = try readSparseBitVector(&reader, self.allocator); | ||
| 666 | defer self.allocator.free(deleted); | ||
| 667 | |||
| 668 | for (present) |_| { | ||
| 669 | const name_offset = try reader.readInt(u32, .little); | ||
| 670 | const name_index = try reader.readInt(u32, .little); | ||
| 671 | if (name_offset > name_bytes.len) | ||
| 672 | return error.InvalidDebugInfo; | ||
| 673 | const name = mem.sliceTo(name_bytes[name_offset..], 0); | ||
| 674 | if (mem.eql(u8, name, "/names")) { | ||
| 675 | break :str_tab_index name_index; | ||
| 676 | } | ||
| 677 | } | ||
| 678 | return error.MissingDebugInfo; | ||
| 679 | }; | ||
| 680 | |||
| 681 | self.string_table = self.getStreamById(string_table_index) orelse | ||
| 682 | return error.MissingDebugInfo; | ||
| 683 | } | ||
| 684 | |||
| 685 | pub fn getSymbolName(self: *Pdb, module: *Module, address: u64) ?[]const u8 { | ||
| 686 | _ = self; | ||
| 687 | std.debug.assert(module.populated); | ||
| 688 | |||
| 689 | var symbol_i: usize = 0; | ||
| 690 | while (symbol_i != module.symbols.len) { | ||
| 691 | const prefix = @as(*align(1) RecordPrefix, @ptrCast(&module.symbols[symbol_i])); | ||
| 692 | if (prefix.RecordLen < 2) | ||
| 693 | return null; | ||
| 694 | switch (prefix.RecordKind) { | ||
| 695 | .S_LPROC32, .S_GPROC32 => { | ||
| 696 | const proc_sym = @as(*align(1) ProcSym, @ptrCast(&module.symbols[symbol_i + @sizeOf(RecordPrefix)])); | ||
| 697 | if (address >= proc_sym.CodeOffset and address < proc_sym.CodeOffset + proc_sym.CodeSize) { | ||
| 698 | return mem.sliceTo(@as([*:0]u8, @ptrCast(&proc_sym.Name[0])), 0); | ||
| 699 | } | ||
| 700 | }, | ||
| 701 | else => {}, | ||
| 702 | } | ||
| 703 | symbol_i += prefix.RecordLen + @sizeOf(u16); | ||
| 704 | } | ||
| 705 | |||
| 706 | return null; | ||
| 707 | } | ||
| 708 | |||
| 709 | pub fn getLineNumberInfo(self: *Pdb, module: *Module, address: u64) !debug.LineInfo { | ||
| 710 | std.debug.assert(module.populated); | ||
| 711 | const subsect_info = module.subsect_info; | ||
| 712 | |||
| 713 | var sect_offset: usize = 0; | ||
| 714 | var skip_len: usize = undefined; | ||
| 715 | const checksum_offset = module.checksum_offset orelse return error.MissingDebugInfo; | ||
| 716 | while (sect_offset != subsect_info.len) : (sect_offset += skip_len) { | ||
| 717 | const subsect_hdr = @as(*align(1) DebugSubsectionHeader, @ptrCast(&subsect_info[sect_offset])); | ||
| 718 | skip_len = subsect_hdr.Length; | ||
| 719 | sect_offset += @sizeOf(DebugSubsectionHeader); | ||
| 720 | |||
| 721 | switch (subsect_hdr.Kind) { | ||
| 722 | .Lines => { | ||
| 723 | var line_index = sect_offset; | ||
| 724 | |||
| 725 | const line_hdr = @as(*align(1) LineFragmentHeader, @ptrCast(&subsect_info[line_index])); | ||
| 726 | if (line_hdr.RelocSegment == 0) | ||
| 727 | return error.MissingDebugInfo; | ||
| 728 | line_index += @sizeOf(LineFragmentHeader); | ||
| 729 | const frag_vaddr_start = line_hdr.RelocOffset; | ||
| 730 | const frag_vaddr_end = frag_vaddr_start + line_hdr.CodeSize; | ||
| 731 | |||
| 732 | if (address >= frag_vaddr_start and address < frag_vaddr_end) { | ||
| 733 | // There is an unknown number of LineBlockFragmentHeaders (and their accompanying line and column records) | ||
| 734 | // from now on. We will iterate through them, and eventually find a LineInfo that we're interested in, | ||
| 735 | // breaking out to :subsections. If not, we will make sure to not read anything outside of this subsection. | ||
| 736 | const subsection_end_index = sect_offset + subsect_hdr.Length; | ||
| 737 | |||
| 738 | while (line_index < subsection_end_index) { | ||
| 739 | const block_hdr = @as(*align(1) LineBlockFragmentHeader, @ptrCast(&subsect_info[line_index])); | ||
| 740 | line_index += @sizeOf(LineBlockFragmentHeader); | ||
| 741 | const start_line_index = line_index; | ||
| 742 | |||
| 743 | const has_column = line_hdr.Flags.LF_HaveColumns; | ||
| 744 | |||
| 745 | // All line entries are stored inside their line block by ascending start address. | ||
| 746 | // Heuristic: we want to find the last line entry | ||
| 747 | // that has a vaddr_start <= address. | ||
| 748 | // This is done with a simple linear search. | ||
| 749 | var line_i: u32 = 0; | ||
| 750 | while (line_i < block_hdr.NumLines) : (line_i += 1) { | ||
| 751 | const line_num_entry = @as(*align(1) LineNumberEntry, @ptrCast(&subsect_info[line_index])); | ||
| 752 | line_index += @sizeOf(LineNumberEntry); | ||
| 753 | |||
| 754 | const vaddr_start = frag_vaddr_start + line_num_entry.Offset; | ||
| 755 | if (address < vaddr_start) { | ||
| 756 | break; | ||
| 757 | } | ||
| 758 | } | ||
| 759 | |||
| 760 | // line_i == 0 would mean that no matching LineNumberEntry was found. | ||
| 761 | if (line_i > 0) { | ||
| 762 | const subsect_index = checksum_offset + block_hdr.NameIndex; | ||
| 763 | const chksum_hdr = @as(*align(1) FileChecksumEntryHeader, @ptrCast(&module.subsect_info[subsect_index])); | ||
| 764 | const strtab_offset = @sizeOf(PDBStringTableHeader) + chksum_hdr.FileNameOffset; | ||
| 765 | try self.string_table.?.seekTo(strtab_offset); | ||
| 766 | const source_file_name = try self.string_table.?.reader().readUntilDelimiterAlloc(self.allocator, 0, 1024); | ||
| 767 | |||
| 768 | const line_entry_idx = line_i - 1; | ||
| 769 | |||
| 770 | const column = if (has_column) blk: { | ||
| 771 | const start_col_index = start_line_index + @sizeOf(LineNumberEntry) * block_hdr.NumLines; | ||
| 772 | const col_index = start_col_index + @sizeOf(ColumnNumberEntry) * line_entry_idx; | ||
| 773 | const col_num_entry = @as(*align(1) ColumnNumberEntry, @ptrCast(&subsect_info[col_index])); | ||
| 774 | break :blk col_num_entry.StartColumn; | ||
| 775 | } else 0; | ||
| 776 | |||
| 777 | const found_line_index = start_line_index + line_entry_idx * @sizeOf(LineNumberEntry); | ||
| 778 | const line_num_entry: *align(1) LineNumberEntry = @ptrCast(&subsect_info[found_line_index]); | ||
| 779 | const flags: *align(1) LineNumberEntry.Flags = @ptrCast(&line_num_entry.Flags); | ||
| 780 | |||
| 781 | return debug.LineInfo{ | ||
| 782 | .file_name = source_file_name, | ||
| 783 | .line = flags.Start, | ||
| 784 | .column = column, | ||
| 785 | }; | ||
| 786 | } | ||
| 787 | } | ||
| 788 | |||
| 789 | // Checking that we are not reading garbage after the (possibly) multiple block fragments. | ||
| 790 | if (line_index != subsection_end_index) { | ||
| 791 | return error.InvalidDebugInfo; | ||
| 792 | } | ||
| 793 | } | ||
| 794 | }, | ||
| 795 | else => {}, | ||
| 796 | } | ||
| 797 | |||
| 798 | if (sect_offset > subsect_info.len) | ||
| 799 | return error.InvalidDebugInfo; | ||
| 800 | } | ||
| 801 | |||
| 802 | return error.MissingDebugInfo; | ||
| 803 | } | ||
| 804 | |||
| 805 | pub fn getModule(self: *Pdb, index: usize) !?*Module { | ||
| 806 | if (index >= self.modules.len) | ||
| 807 | return null; | ||
| 808 | |||
| 809 | const mod = &self.modules[index]; | ||
| 810 | if (mod.populated) | ||
| 811 | return mod; | ||
| 812 | |||
| 813 | // At most one can be non-zero. | ||
| 814 | if (mod.mod_info.C11ByteSize != 0 and mod.mod_info.C13ByteSize != 0) | ||
| 815 | return error.InvalidDebugInfo; | ||
| 816 | if (mod.mod_info.C13ByteSize == 0) | ||
| 817 | return error.InvalidDebugInfo; | ||
| 818 | |||
| 819 | const stream = self.getStreamById(mod.mod_info.ModuleSymStream) orelse | ||
| 820 | return error.MissingDebugInfo; | ||
| 821 | const reader = stream.reader(); | ||
| 822 | |||
| 823 | const signature = try reader.readInt(u32, .little); | ||
| 824 | if (signature != 4) | ||
| 825 | return error.InvalidDebugInfo; | ||
| 826 | |||
| 827 | mod.symbols = try self.allocator.alloc(u8, mod.mod_info.SymByteSize - 4); | ||
| 828 | errdefer self.allocator.free(mod.symbols); | ||
| 829 | try reader.readNoEof(mod.symbols); | ||
| 830 | |||
| 831 | mod.subsect_info = try self.allocator.alloc(u8, mod.mod_info.C13ByteSize); | ||
| 832 | errdefer self.allocator.free(mod.subsect_info); | ||
| 833 | try reader.readNoEof(mod.subsect_info); | ||
| 834 | |||
| 835 | var sect_offset: usize = 0; | ||
| 836 | var skip_len: usize = undefined; | ||
| 837 | while (sect_offset != mod.subsect_info.len) : (sect_offset += skip_len) { | ||
| 838 | const subsect_hdr = @as(*align(1) DebugSubsectionHeader, @ptrCast(&mod.subsect_info[sect_offset])); | ||
| 839 | skip_len = subsect_hdr.Length; | ||
| 840 | sect_offset += @sizeOf(DebugSubsectionHeader); | ||
| 841 | |||
| 842 | switch (subsect_hdr.Kind) { | ||
| 843 | .FileChecksums => { | ||
| 844 | mod.checksum_offset = sect_offset; | ||
| 845 | break; | ||
| 846 | }, | ||
| 847 | else => {}, | ||
| 848 | } | ||
| 849 | |||
| 850 | if (sect_offset > mod.subsect_info.len) | ||
| 851 | return error.InvalidDebugInfo; | ||
| 852 | } | ||
| 853 | |||
| 854 | mod.populated = true; | ||
| 855 | return mod; | ||
| 856 | } | ||
| 857 | |||
| 858 | pub fn getStreamById(self: *Pdb, id: u32) ?*MsfStream { | ||
| 859 | if (id >= self.msf.streams.len) | ||
| 860 | return null; | ||
| 861 | return &self.msf.streams[id]; | ||
| 862 | } | ||
| 863 | |||
| 864 | pub fn getStream(self: *Pdb, stream: StreamType) ?*MsfStream { | ||
| 865 | const id = @intFromEnum(stream); | ||
| 866 | return self.getStreamById(id); | ||
| 867 | } | ||
| 868 | }; | ||
| 869 | |||
| 870 | // see https://llvm.org/docs/PDB/MsfFile.html | ||
| 871 | const Msf = struct { | ||
| 872 | directory: MsfStream, | ||
| 873 | streams: []MsfStream, | ||
| 874 | |||
| 875 | fn init(allocator: mem.Allocator, file: File) !Msf { | ||
| 876 | const in = file.reader(); | ||
| 877 | |||
| 878 | const superblock = try in.readStruct(SuperBlock); | ||
| 879 | |||
| 880 | // Sanity checks | ||
| 881 | if (!mem.eql(u8, &superblock.FileMagic, SuperBlock.file_magic)) | ||
| 882 | return error.InvalidDebugInfo; | ||
| 883 | if (superblock.FreeBlockMapBlock != 1 and superblock.FreeBlockMapBlock != 2) | ||
| 884 | return error.InvalidDebugInfo; | ||
| 885 | const file_len = try file.getEndPos(); | ||
| 886 | if (superblock.NumBlocks * superblock.BlockSize != file_len) | ||
| 887 | return error.InvalidDebugInfo; | ||
| 888 | switch (superblock.BlockSize) { | ||
| 889 | // llvm only supports 4096 but we can handle any of these values | ||
| 890 | 512, 1024, 2048, 4096 => {}, | ||
| 891 | else => return error.InvalidDebugInfo, | ||
| 892 | } | ||
| 893 | |||
| 894 | const dir_block_count = blockCountFromSize(superblock.NumDirectoryBytes, superblock.BlockSize); | ||
| 895 | if (dir_block_count > superblock.BlockSize / @sizeOf(u32)) | ||
| 896 | return error.UnhandledBigDirectoryStream; // cf. BlockMapAddr comment. | ||
| 897 | |||
| 898 | try file.seekTo(superblock.BlockSize * superblock.BlockMapAddr); | ||
| 899 | const dir_blocks = try allocator.alloc(u32, dir_block_count); | ||
| 900 | for (dir_blocks) |*b| { | ||
| 901 | b.* = try in.readInt(u32, .little); | ||
| 902 | } | ||
| 903 | var directory = MsfStream.init( | ||
| 904 | superblock.BlockSize, | ||
| 905 | file, | ||
| 906 | dir_blocks, | ||
| 907 | ); | ||
| 908 | |||
| 909 | const begin = directory.pos; | ||
| 910 | const stream_count = try directory.reader().readInt(u32, .little); | ||
| 911 | const stream_sizes = try allocator.alloc(u32, stream_count); | ||
| 912 | defer allocator.free(stream_sizes); | ||
| 913 | |||
| 914 | // Microsoft's implementation uses @as(u32, -1) for inexistent streams. | ||
| 915 | // These streams are not used, but still participate in the file | ||
| 916 | // and must be taken into account when resolving stream indices. | ||
| 917 | const Nil = 0xFFFFFFFF; | ||
| 918 | for (stream_sizes) |*s| { | ||
| 919 | const size = try directory.reader().readInt(u32, .little); | ||
| 920 | s.* = if (size == Nil) 0 else blockCountFromSize(size, superblock.BlockSize); | ||
| 921 | } | ||
| 922 | |||
| 923 | const streams = try allocator.alloc(MsfStream, stream_count); | ||
| 924 | for (streams, 0..) |*stream, i| { | ||
| 925 | const size = stream_sizes[i]; | ||
| 926 | if (size == 0) { | ||
| 927 | stream.* = MsfStream{ | ||
| 928 | .blocks = &[_]u32{}, | ||
| 929 | }; | ||
| 930 | } else { | ||
| 931 | var blocks = try allocator.alloc(u32, size); | ||
| 932 | var j: u32 = 0; | ||
| 933 | while (j < size) : (j += 1) { | ||
| 934 | const block_id = try directory.reader().readInt(u32, .little); | ||
| 935 | const n = (block_id % superblock.BlockSize); | ||
| 936 | // 0 is for SuperBlock, 1 and 2 for FPMs. | ||
| 937 | if (block_id == 0 or n == 1 or n == 2 or block_id * superblock.BlockSize > file_len) | ||
| 938 | return error.InvalidBlockIndex; | ||
| 939 | blocks[j] = block_id; | ||
| 940 | } | ||
| 941 | |||
| 942 | stream.* = MsfStream.init( | ||
| 943 | superblock.BlockSize, | ||
| 944 | file, | ||
| 945 | blocks, | ||
| 946 | ); | ||
| 947 | } | ||
| 948 | } | ||
| 949 | |||
| 950 | const end = directory.pos; | ||
| 951 | if (end - begin != superblock.NumDirectoryBytes) | ||
| 952 | return error.InvalidStreamDirectory; | ||
| 953 | |||
| 954 | return Msf{ | ||
| 955 | .directory = directory, | ||
| 956 | .streams = streams, | ||
| 957 | }; | ||
| 958 | } | ||
| 959 | |||
| 960 | fn deinit(self: *Msf, allocator: mem.Allocator) void { | ||
| 961 | allocator.free(self.directory.blocks); | ||
| 962 | for (self.streams) |*stream| { | ||
| 963 | allocator.free(stream.blocks); | ||
| 964 | } | ||
| 965 | allocator.free(self.streams); | ||
| 966 | } | ||
| 967 | }; | ||
| 968 | |||
| 969 | fn blockCountFromSize(size: u32, block_size: u32) u32 { | ||
| 970 | return (size + block_size - 1) / block_size; | ||
| 971 | } | ||
| 972 | |||
| 973 | // https://llvm.org/docs/PDB/MsfFile.html#the-superblock | 467 | // https://llvm.org/docs/PDB/MsfFile.html#the-superblock |
| 974 | pub const SuperBlock = extern struct { | 468 | pub const SuperBlock = extern struct { |
| 975 | /// The LLVM docs list a space between C / C++ but empirically this is not the case. | 469 | /// The LLVM docs list a space between C / C++ but empirically this is not the case. |
| ... | @@ -1016,82 +510,3 @@ pub const SuperBlock = extern struct { | ... | @@ -1016,82 +510,3 @@ pub const SuperBlock = extern struct { |
| 1016 | // implement it so we're kind of safe making this assumption for now. | 510 | // implement it so we're kind of safe making this assumption for now. |
| 1017 | BlockMapAddr: u32, | 511 | BlockMapAddr: u32, |
| 1018 | }; | 512 | }; |
| 1019 | |||
| 1020 | const MsfStream = struct { | ||
| 1021 | in_file: File = undefined, | ||
| 1022 | pos: u64 = undefined, | ||
| 1023 | blocks: []u32 = undefined, | ||
| 1024 | block_size: u32 = undefined, | ||
| 1025 | |||
| 1026 | pub const Error = @typeInfo(@typeInfo(@TypeOf(read)).Fn.return_type.?).ErrorUnion.error_set; | ||
| 1027 | |||
| 1028 | fn init(block_size: u32, file: File, blocks: []u32) MsfStream { | ||
| 1029 | const stream = MsfStream{ | ||
| 1030 | .in_file = file, | ||
| 1031 | .pos = 0, | ||
| 1032 | .blocks = blocks, | ||
| 1033 | .block_size = block_size, | ||
| 1034 | }; | ||
| 1035 | |||
| 1036 | return stream; | ||
| 1037 | } | ||
| 1038 | |||
| 1039 | fn read(self: *MsfStream, buffer: []u8) !usize { | ||
| 1040 | var block_id = @as(usize, @intCast(self.pos / self.block_size)); | ||
| 1041 | if (block_id >= self.blocks.len) return 0; // End of Stream | ||
| 1042 | var block = self.blocks[block_id]; | ||
| 1043 | var offset = self.pos % self.block_size; | ||
| 1044 | |||
| 1045 | try self.in_file.seekTo(block * self.block_size + offset); | ||
| 1046 | const in = self.in_file.reader(); | ||
| 1047 | |||
| 1048 | var size: usize = 0; | ||
| 1049 | var rem_buffer = buffer; | ||
| 1050 | while (size < buffer.len) { | ||
| 1051 | const size_to_read = @min(self.block_size - offset, rem_buffer.len); | ||
| 1052 | size += try in.read(rem_buffer[0..size_to_read]); | ||
| 1053 | rem_buffer = buffer[size..]; | ||
| 1054 | offset += size_to_read; | ||
| 1055 | |||
| 1056 | // If we're at the end of a block, go to the next one. | ||
| 1057 | if (offset == self.block_size) { | ||
| 1058 | offset = 0; | ||
| 1059 | block_id += 1; | ||
| 1060 | if (block_id >= self.blocks.len) break; // End of Stream | ||
| 1061 | block = self.blocks[block_id]; | ||
| 1062 | try self.in_file.seekTo(block * self.block_size); | ||
| 1063 | } | ||
| 1064 | } | ||
| 1065 | |||
| 1066 | self.pos += buffer.len; | ||
| 1067 | return buffer.len; | ||
| 1068 | } | ||
| 1069 | |||
| 1070 | pub fn seekBy(self: *MsfStream, len: i64) !void { | ||
| 1071 | self.pos = @as(u64, @intCast(@as(i64, @intCast(self.pos)) + len)); | ||
| 1072 | if (self.pos >= self.blocks.len * self.block_size) | ||
| 1073 | return error.EOF; | ||
| 1074 | } | ||
| 1075 | |||
| 1076 | pub fn seekTo(self: *MsfStream, len: u64) !void { | ||
| 1077 | self.pos = len; | ||
| 1078 | if (self.pos >= self.blocks.len * self.block_size) | ||
| 1079 | return error.EOF; | ||
| 1080 | } | ||
| 1081 | |||
| 1082 | fn getSize(self: *const MsfStream) u64 { | ||
| 1083 | return self.blocks.len * self.block_size; | ||
| 1084 | } | ||
| 1085 | |||
| 1086 | fn getFilePos(self: MsfStream) u64 { | ||
| 1087 | const block_id = self.pos / self.block_size; | ||
| 1088 | const block = self.blocks[block_id]; | ||
| 1089 | const offset = self.pos % self.block_size; | ||
| 1090 | |||
| 1091 | return block * self.block_size + offset; | ||
| 1092 | } | ||
| 1093 | |||
| 1094 | pub fn reader(self: *MsfStream) std.io.Reader(*MsfStream, Error, read) { | ||
| 1095 | return .{ .context = self }; | ||
| 1096 | } | ||
| 1097 | }; |
src/crash_report.zig+1-1| ... | @@ -256,7 +256,7 @@ const StackContext = union(enum) { | ... | @@ -256,7 +256,7 @@ const StackContext = union(enum) { |
| 256 | current: struct { | 256 | current: struct { |
| 257 | ret_addr: ?usize, | 257 | ret_addr: ?usize, |
| 258 | }, | 258 | }, |
| 259 | exception: *const debug.ThreadContext, | 259 | exception: *debug.ThreadContext, |
| 260 | not_supported: void, | 260 | not_supported: void, |
| 261 | 261 | ||
| 262 | pub fn dumpStackTrace(ctx: @This()) void { | 262 | pub fn dumpStackTrace(ctx: @This()) void { |
test/standalone/coff_dwarf/main.zig+1-1| ... | @@ -9,7 +9,7 @@ pub fn main() !void { | ... | @@ -9,7 +9,7 @@ pub fn main() !void { |
| 9 | defer assert(gpa.deinit() == .ok); | 9 | defer assert(gpa.deinit() == .ok); |
| 10 | const allocator = gpa.allocator(); | 10 | const allocator = gpa.allocator(); |
| 11 | 11 | ||
| 12 | var debug_info = try std.debug.openSelfDebugInfo(allocator); | 12 | var debug_info = try std.debug.SelfInfo.open(allocator); |
| 13 | defer debug_info.deinit(); | 13 | defer debug_info.deinit(); |
| 14 | 14 | ||
| 15 | var add_addr: usize = undefined; | 15 | var add_addr: usize = undefined; |