| ... | @@ -182,6 +182,9 @@ pub fn dumpStackTraceFromBase(context: *const StackTraceContext) void { | ... | @@ -182,6 +182,9 @@ pub fn dumpStackTraceFromBase(context: *const StackTraceContext) void { |
| 182 | printSourceAtAddress(debug_info, stderr, it.dwarf_context.pc, tty_config) catch return; | 182 | printSourceAtAddress(debug_info, stderr, it.dwarf_context.pc, tty_config) catch return; |
| 183 | | 183 | |
| 184 | while (it.next()) |return_address| { | 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 | // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS, | 188 | // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS, |
| 186 | // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid | 189 | // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid |
| 187 | // an overflow. We do not need to signal `StackIterator` as it will correctly detect this | 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,7 +228,9 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT |
| 225 | } | 228 | } |
| 226 | stack_trace.index = slice.len; | 229 | stack_trace.index = slice.len; |
| 227 | } else { | 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 | var it = StackIterator.init(first_address, null); | 234 | var it = StackIterator.init(first_address, null); |
| 230 | defer it.deinit(); | 235 | defer it.deinit(); |
| 231 | for (stack_trace.instruction_addresses, 0..) |*addr, i| { | 236 | for (stack_trace.instruction_addresses, 0..) |*addr, i| { |
| ... | @@ -442,6 +447,11 @@ pub inline fn getContext(context: *StackTraceContext) bool { | ... | @@ -442,6 +447,11 @@ pub inline fn getContext(context: *StackTraceContext) bool { |
| 442 | return have_getcontext and os.system.getcontext(context) == 0; | 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 | pub const StackIterator = struct { | 455 | pub const StackIterator = struct { |
| 446 | // Skip every frame before this address is found. | 456 | // Skip every frame before this address is found. |
| 447 | first_address: ?usize, | 457 | first_address: ?usize, |
| ... | @@ -452,6 +462,8 @@ pub const StackIterator = struct { | ... | @@ -452,6 +462,8 @@ pub const StackIterator = struct { |
| 452 | // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer). | 462 | // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer). |
| 453 | debug_info: ?*DebugInfo, | 463 | debug_info: ?*DebugInfo, |
| 454 | dwarf_context: if (have_ucontext) DW.UnwindContext else void = undefined, | 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 | pub fn init(first_address: ?usize, fp: ?usize) StackIterator { | 468 | pub fn init(first_address: ?usize, fp: ?usize) StackIterator { |
| 457 | if (native_arch == .sparc64) { | 469 | if (native_arch == .sparc64) { |
| ... | @@ -472,6 +484,7 @@ pub const StackIterator = struct { | ... | @@ -472,6 +484,7 @@ pub const StackIterator = struct { |
| 472 | var iterator = init(first_address, null); | 484 | var iterator = init(first_address, null); |
| 473 | iterator.debug_info = debug_info; | 485 | iterator.debug_info = debug_info; |
| 474 | iterator.dwarf_context = try DW.UnwindContext.init(context, &isValidMemory); | 486 | iterator.dwarf_context = try DW.UnwindContext.init(context, &isValidMemory); |
| | 487 | iterator.last_error = null; |
| 475 | return iterator; | 488 | return iterator; |
| 476 | } | 489 | } |
| 477 | | 490 | |
| ... | @@ -483,6 +496,23 @@ pub const StackIterator = struct { | ... | @@ -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 | // Offset of the saved BP wrt the frame pointer. | 516 | // Offset of the saved BP wrt the frame pointer. |
| 487 | const fp_offset = if (native_arch.isRISCV()) | 517 | const fp_offset = if (native_arch.isRISCV()) |
| 488 | // On RISC-V the frame pointer points to the top of the saved register | 518 | // On RISC-V the frame pointer points to the top of the saved register |
| ... | @@ -579,14 +609,10 @@ pub const StackIterator = struct { | ... | @@ -579,14 +609,10 @@ pub const StackIterator = struct { |
| 579 | if (self.next_dwarf()) |return_address| { | 609 | if (self.next_dwarf()) |return_address| { |
| 580 | return return_address; | 610 | return return_address; |
| 581 | } else |err| { | 611 | } else |err| { |
| 582 | if (err != error.MissingFDE) print("DWARF unwind error: {}\n", .{err}); | 612 | self.last_error = err; |
| 583 | | 613 | self.last_error_address = self.dwarf_context.pc; |
| 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? | | |
| 589 | | 614 | |
| | 615 | // Fall back to fp unwinding on the first failure, as the register context won't have been updated |
| 590 | self.fp = self.dwarf_context.getFp() catch 0; | 616 | self.fp = self.dwarf_context.getFp() catch 0; |
| 591 | self.debug_info = null; | 617 | self.debug_info = null; |
| 592 | } | 618 | } |
| ... | @@ -640,6 +666,9 @@ pub fn writeCurrentStackTrace( | ... | @@ -640,6 +666,9 @@ pub fn writeCurrentStackTrace( |
| 640 | defer it.deinit(); | 666 | defer it.deinit(); |
| 641 | | 667 | |
| 642 | while (it.next()) |return_address| { | 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 | // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS, | 672 | // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS, |
| 644 | // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid | 673 | // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid |
| 645 | // an overflow. We do not need to signal `StackIterator` as it will correctly detect this | 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,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 | pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { | 828 | pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { |
| 789 | const module = debug_info.getModuleForAddress(address) catch |err| switch (err) { | 829 | const module = debug_info.getModuleForAddress(address) catch |err| switch (err) { |
| 790 | error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config), | 830 | error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config), |
| ... | @@ -1099,16 +1139,14 @@ pub fn readElfDebugInfo( | ... | @@ -1099,16 +1139,14 @@ pub fn readElfDebugInfo( |
| 1099 | ) catch break :blk; | 1139 | ) catch break :blk; |
| 1100 | | 1140 | |
| 1101 | for (global_debug_directories) |global_directory| { | 1141 | for (global_debug_directories) |global_directory| { |
| 1102 | // TODO: joinBuf would be ideal (with a fs.MAX_PATH_BYTES buffer) | | |
| 1103 | const path = try fs.path.join(allocator, &.{ global_directory, ".build-id", &id_prefix_buf, filename }); | 1142 | const path = try fs.path.join(allocator, &.{ global_directory, ".build-id", &id_prefix_buf, filename }); |
| 1104 | defer allocator.free(path); | 1143 | defer allocator.free(path); |
| 1105 | // TODO: Remove | 1144 | |
| 1106 | std.debug.print(" Loading external debug info from {s}\n", .{path}); | | |
| 1107 | return readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem) catch continue; | 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 | if (separate_debug_filename) |separate_filename| blk: { | 1150 | if (separate_debug_filename) |separate_filename| blk: { |
| 1113 | if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) return error.MissingDebugInfo; | 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,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 | pub fn getModuleNameForAddress(self: *DebugInfo, address: usize) ?[]const u8 { | 1500 | pub fn getModuleNameForAddress(self: *DebugInfo, address: usize) ?[]const u8 { |
| 1460 | if (comptime builtin.target.isDarwin()) { | 1501 | if (comptime builtin.target.isDarwin()) { |
| 1461 | return null; | 1502 | return null; |
| ... | @@ -1466,7 +1507,7 @@ pub const DebugInfo = struct { | ... | @@ -1466,7 +1507,7 @@ pub const DebugInfo = struct { |
| 1466 | } else if (comptime builtin.target.isWasm()) { | 1507 | } else if (comptime builtin.target.isWasm()) { |
| 1467 | return null; | 1508 | return null; |
| 1468 | } else { | 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,6 +1665,44 @@ pub const DebugInfo = struct { |
| 1624 | return null; | 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 | fn lookupModuleDl(self: *DebugInfo, address: usize) !*ModuleDebugInfo { | 1706 | fn lookupModuleDl(self: *DebugInfo, address: usize) !*ModuleDebugInfo { |
| 1628 | var ctx: struct { | 1707 | var ctx: struct { |
| 1629 | // Input | 1708 | // Input |