| ... | ... | @@ -182,6 +182,9 @@ pub fn dumpStackTraceFromBase(context: *const StackTraceContext) void { |
| 182 | 182 | printSourceAtAddress(debug_info, stderr, it.dwarf_context.pc, tty_config) catch return; |
| 183 | 183 | |
| 184 | 184 | while (it.next()) |return_address| { |
| 185 | if (it.getLastError()) |unwind_error| |
| 186 | printUnwindError(debug_info, stderr, unwind_error.address, unwind_error.err, tty_config) catch {}; |
| 187 | |
| 185 | 188 | // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS, |
| 186 | 189 | // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid |
| 187 | 190 | // an overflow. We do not need to signal `StackIterator` as it will correctly detect this |
| ... | ... | @@ -225,7 +228,9 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT |
| 225 | 228 | } |
| 226 | 229 | stack_trace.index = slice.len; |
| 227 | 230 | } else { |
| 228 | | // TODO: This should use the DWARF unwinder if .eh_frame_hdr is available (so that full debug info parsing isn't required) |
| 231 | // TODO: This should use the DWARF unwinder if .eh_frame_hdr is available (so that full debug info parsing isn't required). |
| 232 | // A new path for loading DebugInfo needs to be created which will only attempt to parse in-memory sections, because |
| 233 | // stopping to load other debug info (ie. source line info) from disk here is not required for unwinding. |
| 229 | 234 | var it = StackIterator.init(first_address, null); |
| 230 | 235 | defer it.deinit(); |
| 231 | 236 | for (stack_trace.instruction_addresses, 0..) |*addr, i| { |
| ... | ... | @@ -442,6 +447,11 @@ pub inline fn getContext(context: *StackTraceContext) bool { |
| 442 | 447 | return have_getcontext and os.system.getcontext(context) == 0; |
| 443 | 448 | } |
| 444 | 449 | |
| 450 | pub const UnwindError = if (have_ucontext) |
| 451 | @typeInfo(@typeInfo(@TypeOf(StackIterator.next_dwarf)).Fn.return_type.?).ErrorUnion.error_set |
| 452 | else |
| 453 | void; |
| 454 | |
| 445 | 455 | pub const StackIterator = struct { |
| 446 | 456 | // Skip every frame before this address is found. |
| 447 | 457 | first_address: ?usize, |
| ... | ... | @@ -452,6 +462,8 @@ pub const StackIterator = struct { |
| 452 | 462 | // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer). |
| 453 | 463 | debug_info: ?*DebugInfo, |
| 454 | 464 | dwarf_context: if (have_ucontext) DW.UnwindContext else void = undefined, |
| 465 | last_error: if (have_ucontext) ?UnwindError else void = undefined, |
| 466 | last_error_address: if (have_ucontext) usize else void = undefined, |
| 455 | 467 | |
| 456 | 468 | pub fn init(first_address: ?usize, fp: ?usize) StackIterator { |
| 457 | 469 | if (native_arch == .sparc64) { |
| ... | ... | @@ -472,6 +484,7 @@ pub const StackIterator = struct { |
| 472 | 484 | var iterator = init(first_address, null); |
| 473 | 485 | iterator.debug_info = debug_info; |
| 474 | 486 | iterator.dwarf_context = try DW.UnwindContext.init(context, &isValidMemory); |
| 487 | iterator.last_error = null; |
| 475 | 488 | return iterator; |
| 476 | 489 | } |
| 477 | 490 | |
| ... | ... | @@ -483,6 +496,23 @@ pub const StackIterator = struct { |
| 483 | 496 | } |
| 484 | 497 | } |
| 485 | 498 | |
| 499 | pub fn getLastError(self: *StackIterator) ?struct { |
| 500 | address: usize, |
| 501 | err: UnwindError, |
| 502 | } { |
| 503 | if (have_ucontext) { |
| 504 | if (self.last_error) |err| { |
| 505 | self.last_error = null; |
| 506 | return .{ |
| 507 | .address = self.last_error_address, |
| 508 | .err = err, |
| 509 | }; |
| 510 | } |
| 511 | } |
| 512 | |
| 513 | return null; |
| 514 | } |
| 515 | |
| 486 | 516 | // Offset of the saved BP wrt the frame pointer. |
| 487 | 517 | const fp_offset = if (native_arch.isRISCV()) |
| 488 | 518 | // On RISC-V the frame pointer points to the top of the saved register |
| ... | ... | @@ -579,14 +609,10 @@ pub const StackIterator = struct { |
| 579 | 609 | if (self.next_dwarf()) |return_address| { |
| 580 | 610 | return return_address; |
| 581 | 611 | } else |err| { |
| 582 | | if (err != error.MissingFDE) print("DWARF unwind error: {}\n", .{err}); |
| 583 | | |
| 584 | | // Fall back to fp unwinding on the first failure, |
| 585 | | // as the register context won't be updated |
| 586 | | |
| 587 | | // TODO: Could still attempt dwarf unwinding after this, maybe marking non-updated registers as |
| 588 | | // invalid, so the unwind only fails if it requires out of date registers? |
| 612 | self.last_error = err; |
| 613 | self.last_error_address = self.dwarf_context.pc; |
| 589 | 614 | |
| 615 | // Fall back to fp unwinding on the first failure, as the register context won't have been updated |
| 590 | 616 | self.fp = self.dwarf_context.getFp() catch 0; |
| 591 | 617 | self.debug_info = null; |
| 592 | 618 | } |
| ... | ... | @@ -640,6 +666,9 @@ pub fn writeCurrentStackTrace( |
| 640 | 666 | defer it.deinit(); |
| 641 | 667 | |
| 642 | 668 | while (it.next()) |return_address| { |
| 669 | if (it.getLastError()) |unwind_error| |
| 670 | try printUnwindError(debug_info, out_stream, unwind_error.address, unwind_error.err, tty_config); |
| 671 | |
| 643 | 672 | // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS, |
| 644 | 673 | // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid |
| 645 | 674 | // an overflow. We do not need to signal `StackIterator` as it will correctly detect this |
| ... | ... | @@ -785,6 +814,17 @@ fn printUnknownSource(debug_info: *DebugInfo, out_stream: anytype, address: usiz |
| 785 | 814 | ); |
| 786 | 815 | } |
| 787 | 816 | |
| 817 | pub fn printUnwindError(debug_info: *DebugInfo, out_stream: anytype, address: usize, err: UnwindError, tty_config: io.tty.Config) !void { |
| 818 | const module_name = debug_info.getModuleNameForAddress(address) orelse "???"; |
| 819 | try tty_config.setColor(out_stream, .dim); |
| 820 | if (err != error.MissingDebugInfo) { |
| 821 | try out_stream.print("Unwind information for {s} was not available ({}), trace may be incomplete\n\n", .{ module_name, err }); |
| 822 | } else { |
| 823 | try out_stream.print("Unwind information for {s} was not available, trace may be incomplete\n\n", .{module_name}); |
| 824 | } |
| 825 | try tty_config.setColor(out_stream, .reset); |
| 826 | } |
| 827 | |
| 788 | 828 | pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { |
| 789 | 829 | const module = debug_info.getModuleForAddress(address) catch |err| switch (err) { |
| 790 | 830 | error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config), |
| ... | ... | @@ -1099,16 +1139,14 @@ pub fn readElfDebugInfo( |
| 1099 | 1139 | ) catch break :blk; |
| 1100 | 1140 | |
| 1101 | 1141 | for (global_debug_directories) |global_directory| { |
| 1102 | | // TODO: joinBuf would be ideal (with a fs.MAX_PATH_BYTES buffer) |
| 1103 | 1142 | const path = try fs.path.join(allocator, &.{ global_directory, ".build-id", &id_prefix_buf, filename }); |
| 1104 | 1143 | defer allocator.free(path); |
| 1105 | | // TODO: Remove |
| 1106 | | std.debug.print(" Loading external debug info from {s}\n", .{path}); |
| 1144 | |
| 1107 | 1145 | return readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem) catch continue; |
| 1108 | 1146 | } |
| 1109 | 1147 | } |
| 1110 | 1148 | |
| 1111 | | // use the path from .gnu_debuglink, in the search order as gdb |
| 1149 | // use the path from .gnu_debuglink, in the same search order as gdb |
| 1112 | 1150 | if (separate_debug_filename) |separate_filename| blk: { |
| 1113 | 1151 | if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) return error.MissingDebugInfo; |
| 1114 | 1152 | |
| ... | ... | @@ -1456,6 +1494,9 @@ pub const DebugInfo = struct { |
| 1456 | 1494 | } |
| 1457 | 1495 | } |
| 1458 | 1496 | |
| 1497 | // Returns the module name for a given address. |
| 1498 | // This can be called when getModuleForAddress fails, so implementations should provide |
| 1499 | // a path that doesn't rely on any side-effects of successful module lookup. |
| 1459 | 1500 | pub fn getModuleNameForAddress(self: *DebugInfo, address: usize) ?[]const u8 { |
| 1460 | 1501 | if (comptime builtin.target.isDarwin()) { |
| 1461 | 1502 | return null; |
| ... | ... | @@ -1466,7 +1507,7 @@ pub const DebugInfo = struct { |
| 1466 | 1507 | } else if (comptime builtin.target.isWasm()) { |
| 1467 | 1508 | return null; |
| 1468 | 1509 | } else { |
| 1469 | | return null; |
| 1510 | return self.lookupModuleNameDl(address); |
| 1470 | 1511 | } |
| 1471 | 1512 | } |
| 1472 | 1513 | |
| ... | ... | @@ -1624,6 +1665,44 @@ pub const DebugInfo = struct { |
| 1624 | 1665 | return null; |
| 1625 | 1666 | } |
| 1626 | 1667 | |
| 1668 | fn lookupModuleNameDl(self: *DebugInfo, address: usize) ?[]const u8 { |
| 1669 | _ = self; |
| 1670 | |
| 1671 | var ctx: struct { |
| 1672 | // Input |
| 1673 | address: usize, |
| 1674 | // Output |
| 1675 | name: []const u8 = "", |
| 1676 | } = .{ .address = address }; |
| 1677 | const CtxTy = @TypeOf(ctx); |
| 1678 | |
| 1679 | if (os.dl_iterate_phdr(&ctx, error{Found}, struct { |
| 1680 | fn callback(info: *os.dl_phdr_info, size: usize, context: *CtxTy) !void { |
| 1681 | _ = size; |
| 1682 | if (context.address < info.dlpi_addr) return; |
| 1683 | const phdrs = info.dlpi_phdr[0..info.dlpi_phnum]; |
| 1684 | for (phdrs) |*phdr| { |
| 1685 | if (phdr.p_type != elf.PT_LOAD) continue; |
| 1686 | |
| 1687 | const seg_start = info.dlpi_addr +% phdr.p_vaddr; |
| 1688 | const seg_end = seg_start + phdr.p_memsz; |
| 1689 | if (context.address >= seg_start and context.address < seg_end) { |
| 1690 | context.name = mem.sliceTo(info.dlpi_name, 0) orelse ""; |
| 1691 | break; |
| 1692 | } |
| 1693 | } else return; |
| 1694 | |
| 1695 | return error.Found; |
| 1696 | } |
| 1697 | }.callback)) { |
| 1698 | return null; |
| 1699 | } else |err| switch (err) { |
| 1700 | error.Found => return fs.path.basename(ctx.name), |
| 1701 | } |
| 1702 | |
| 1703 | return null; |
| 1704 | } |
| 1705 | |
| 1627 | 1706 | fn lookupModuleDl(self: *DebugInfo, address: usize) !*ModuleDebugInfo { |
| 1628 | 1707 | var ctx: struct { |
| 1629 | 1708 | // Input |