diff --git a/lib/std/debug.zig b/lib/std/debug.zig index b8d0cbaab4..eaa8ae3b8b 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -378,6 +378,8 @@ pub inline fn getContext(context: *ThreadContext) bool { } return true; } + + return false; } /// Invokes detectable illegal behavior when `ok` is `false`. @@ -619,7 +621,9 @@ pub const StackUnwindOptions = struct { /// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it. pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) std.builtin.StackTrace { var context_buf: ThreadContext = undefined; - var it: StackIterator = .init(options.context, &context_buf); + var it = StackIterator.init(options.context, &context_buf) catch { + return .{ .index = 0, .instruction_addresses = &.{} }; + }; defer it.deinit(); if (!it.stratOk(options.allow_unsafe_unwind)) { return .{ .index = 0, .instruction_addresses = &.{} }; @@ -657,7 +661,14 @@ pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_ }, }; var context_buf: ThreadContext = undefined; - var it: StackIterator = .init(options.context, &context_buf); + var it = StackIterator.init(options.context, &context_buf) catch |err| switch (err) { + error.OutOfMemory => { + tty_config.setColor(writer, .dim) catch {}; + try writer.print("Cannot print stack trace: out of memory\n", .{}); + tty_config.setColor(writer, .reset) catch {}; + return; + }, + }; defer it.deinit(); if (!it.stratOk(options.allow_unsafe_unwind)) { tty_config.setColor(writer, .dim) catch {}; @@ -751,14 +762,14 @@ pub fn dumpStackTrace(st: *const std.builtin.StackTrace) void { const StackIterator = union(enum) { /// Unwinding using debug info (e.g. DWARF CFI). - di: if (SelfInfo.supports_unwinding) SelfInfo.DwarfUnwindContext else noreturn, + di: if (SelfInfo.supports_unwinding) SelfInfo.UnwindContext else noreturn, /// Naive frame-pointer-based unwinding. Very simple, but typically unreliable. fp: usize, /// It is important that this function is marked `inline` so that it can safely use /// `@frameAddress` and `getContext` as the caller's stack frame and our own are one /// and the same. - inline fn init(context_opt: ?*const ThreadContext, context_buf: *ThreadContext) StackIterator { + inline fn init(context_opt: ?*const ThreadContext, context_buf: *ThreadContext) error{OutOfMemory}!StackIterator { if (builtin.cpu.arch.isSPARC()) { // Flush all the register windows on stack. if (builtin.cpu.has(.sparc, .v9)) { @@ -770,10 +781,10 @@ const StackIterator = union(enum) { if (context_opt) |context| { context_buf.* = context.*; relocateContext(context_buf); - return .{ .di = .init(context_buf) }; + return .{ .di = try .init(context_buf, getDebugInfoAllocator()) }; } if (getContext(context_buf)) { - return .{ .di = .init(context_buf) }; + return .{ .di = try .init(context_buf, getDebugInfoAllocator()) }; } return .{ .fp = @frameAddress() }; } @@ -816,10 +827,10 @@ const StackIterator = union(enum) { if (ra == 0) return .end; return .{ .frame = ra }; } else |err| { - const bad_pc = unwind_context.pc; - it.* = .{ .fp = unwind_context.getFp() catch 0 }; + const pc = unwind_context.pc; + it.* = .{ .fp = unwind_context.getFp() }; return .{ .switch_to_fp = .{ - .address = bad_pc, + .address = pc, .err = err, } }; } diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig index 5bc751e1aa..8b8dc7d732 100644 --- a/lib/std/debug/Dwarf.zig +++ b/lib/std/debug/Dwarf.zig @@ -282,13 +282,13 @@ pub const Die = struct { .@"32" => { const byte_offset = compile_unit.str_offsets_base + 4 * index; if (byte_offset + 4 > debug_str_offsets.len) return bad(); - const offset = mem.readInt(u32, debug_str_offsets[byte_offset..][0..4], endian); + const offset = mem.readInt(u32, debug_str_offsets[@intCast(byte_offset)..][0..4], endian); return getStringGeneric(opt_str, offset); }, .@"64" => { const byte_offset = compile_unit.str_offsets_base + 8 * index; if (byte_offset + 8 > debug_str_offsets.len) return bad(); - const offset = mem.readInt(u64, debug_str_offsets[byte_offset..][0..8], endian); + const offset = mem.readInt(u64, debug_str_offsets[@intCast(byte_offset)..][0..8], endian); return getStringGeneric(opt_str, offset); }, } diff --git a/lib/std/debug/SelfInfo.zig b/lib/std/debug/SelfInfo.zig index df5a9e7ad8..ef222cc7f4 100644 --- a/lib/std/debug/SelfInfo.zig +++ b/lib/std/debug/SelfInfo.zig @@ -42,6 +42,8 @@ pub const target_supported: bool = Module != void; /// For whether DWARF unwinding is *theoretically* possible, see `Dwarf.abi.supportsUnwinding`. pub const supports_unwinding: bool = Module.supports_unwinding; +pub const UnwindContext = if (supports_unwinding) Module.UnwindContext; + pub const init: SelfInfo = .{ .modules = .empty, .lookup_cache = if (Module.LookupCache != void) .init, @@ -53,7 +55,7 @@ pub fn deinit(self: *SelfInfo, gpa: Allocator) void { if (Module.LookupCache != void) self.lookup_cache.deinit(gpa); } -pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *DwarfUnwindContext) Error!usize { +pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize { comptime assert(supports_unwinding); const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc); const gop = try self.modules.getOrPut(gpa, module.key()); @@ -113,14 +115,23 @@ pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize) /// ) SelfInfo.Error!std.debug.Symbol; /// /// Whether a reliable stack unwinding strategy, such as DWARF unwinding, is available. /// pub const supports_unwinding: bool; +/// /// Only required if `supports_unwinding == true`. +/// pub const UnwindContext = struct { +/// /// A PC value inside the function of the last unwound frame. +/// pc: usize, +/// pub fn init(tc: *std.debug.ThreadContext, gpa: Allocator) Allocator.Error!UnwindContext; +/// pub fn deinit(uc: *UnwindContext, gpa: Allocator) void; +/// /// Returns the frame pointer associated with the last unwound stack frame. If the frame +/// /// pointer is unknown, 0 may be returned instead. +/// pub fn getFp(uc: *UnwindContext) usize; +/// }; /// /// Only required if `supports_unwinding == true`. Unwinds a single stack frame and returns -/// /// the next return address (which may be 0 indicating end of stack). This is currently -/// /// specialized to DWARF unwinding. +/// /// the next return address (which may be 0 indicating end of stack). /// pub fn unwindFrame( /// mod: *const Module, /// gpa: Allocator, /// di: *DebugInfo, -/// ctx: *SelfInfo.DwarfUnwindContext, +/// ctx: *UnwindContext, /// ) SelfInfo.Error!usize; /// ``` const Module: type = Module: { @@ -136,6 +147,8 @@ const Module: type = Module: { }; }; +/// An implementation of `UnwindContext` useful for DWARF-based unwinders. The `Module.unwindFrame` +/// implementation should wrap `DwarfUnwindContext.unwindFrame`. pub const DwarfUnwindContext = struct { cfa: ?usize, pc: usize, @@ -144,8 +157,9 @@ pub const DwarfUnwindContext = struct { vm: Dwarf.Unwind.VirtualMachine, stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }), - pub fn init(thread_context: *std.debug.ThreadContext) DwarfUnwindContext { + pub fn init(thread_context: *std.debug.ThreadContext, gpa: Allocator) error{}!DwarfUnwindContext { comptime assert(supports_unwinding); + _ = gpa; const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?; const raw_pc_ptr = regValueNative(thread_context, ip_reg_num, null) catch { @@ -169,8 +183,8 @@ pub const DwarfUnwindContext = struct { self.* = undefined; } - pub fn getFp(self: *const DwarfUnwindContext) !usize { - return (try regValueNative(self.thread_context, Dwarf.abi.fpRegNum(native_arch, self.reg_context), self.reg_context)).*; + pub fn getFp(self: *const DwarfUnwindContext) usize { + return (regValueNative(self.thread_context, Dwarf.abi.fpRegNum(native_arch, self.reg_context), self.reg_context) catch return 0).*; } /// Resolves the register rule and places the result into `out` (see regBytes) diff --git a/lib/std/debug/SelfInfo/DarwinModule.zig b/lib/std/debug/SelfInfo/DarwinModule.zig index 0434b4eaaa..5bce65b89f 100644 --- a/lib/std/debug/SelfInfo/DarwinModule.zig +++ b/lib/std/debug/SelfInfo/DarwinModule.zig @@ -252,10 +252,11 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu }; } pub const supports_unwinding: bool = true; +pub const UnwindContext = std.debug.SelfInfo.DwarfUnwindContext; /// Unwind a frame using MachO compact unwind info (from __unwind_info). /// If the compact encoding can't encode a way to unwind a frame, it will /// defer unwinding to DWARF, in which case `.eh_frame` will be used if available. -pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *DwarfUnwindContext) Error!usize { +pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize { return unwindFrameInner(module, gpa, di, context) catch |err| switch (err) { error.InvalidDebugInfo, error.MissingDebugInfo, @@ -274,7 +275,7 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, => return error.InvalidDebugInfo, }; } -fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *DwarfUnwindContext) !usize { +fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize { if (di.unwind == null) di.unwind = module.loadUnwindInfo(); const unwind = &di.unwind.?; @@ -575,7 +576,7 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, else => comptime unreachable, // unimplemented }; - context.pc = DwarfUnwindContext.stripInstructionPtrAuthCode(new_ip); + context.pc = UnwindContext.stripInstructionPtrAuthCode(new_ip); if (context.pc > 0) context.pc -= 1; return new_ip; } @@ -819,7 +820,6 @@ const macho = std.macho; const mem = std.mem; const posix = std.posix; const testing = std.testing; -const DwarfUnwindContext = std.debug.SelfInfo.DwarfUnwindContext; const Error = std.debug.SelfInfo.Error; const regBytes = Dwarf.abi.regBytes; const regValueNative = Dwarf.abi.regValueNative; diff --git a/lib/std/debug/SelfInfo/ElfModule.zig b/lib/std/debug/SelfInfo/ElfModule.zig index ff37e283b9..7a280c0d6e 100644 --- a/lib/std/debug/SelfInfo/ElfModule.zig +++ b/lib/std/debug/SelfInfo/ElfModule.zig @@ -193,7 +193,7 @@ fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Erro else => unreachable, } } -pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *DwarfUnwindContext) Error!usize { +pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize { if (di.unwind[0] == null) try module.loadUnwindInfo(gpa, di); std.debug.assert(di.unwind[0] != null); for (&di.unwind) |*opt_unwind| { @@ -205,6 +205,7 @@ pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, con } return error.MissingDebugInfo; } +pub const UnwindContext = std.debug.SelfInfo.DwarfUnwindContext; pub const supports_unwinding: bool = s: { const archs: []const std.Target.Cpu.Arch = switch (builtin.target.os.tag) { .linux => &.{ .x86, .x86_64, .arm, .armeb, .thumb, .thumbeb, .aarch64, .aarch64_be }, @@ -233,7 +234,6 @@ const Allocator = std.mem.Allocator; const Dwarf = std.debug.Dwarf; const elf = std.elf; const mem = std.mem; -const DwarfUnwindContext = std.debug.SelfInfo.DwarfUnwindContext; const Error = std.debug.SelfInfo.Error; const builtin = @import("builtin"); diff --git a/lib/std/debug/SelfInfo/WindowsModule.zig b/lib/std/debug/SelfInfo/WindowsModule.zig index ccede7efb2..a0f5deafc5 100644 --- a/lib/std/debug/SelfInfo/WindowsModule.zig +++ b/lib/std/debug/SelfInfo/WindowsModule.zig @@ -102,7 +102,7 @@ fn loadDebugInfo(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo) ! if (create_section_rc != .SUCCESS) return error.MissingDebugInfo; errdefer windows.CloseHandle(section_handle); var coff_len: usize = 0; - var section_view_ptr: [*]const u8 = undefined; + var section_view_ptr: ?[*]const u8 = null; const map_section_rc = windows.ntdll.NtMapViewOfSection( section_handle, process_handle, @@ -116,8 +116,8 @@ fn loadDebugInfo(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo) ! windows.PAGE_READONLY, ); if (map_section_rc != .SUCCESS) return error.MissingDebugInfo; - errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr)) == .SUCCESS); - const section_view = section_view_ptr[0..coff_len]; + errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr.?)) == .SUCCESS); + const section_view = section_view_ptr.?[0..coff_len]; coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo; di.mapped_file = .{ .file = coff_file, @@ -246,7 +246,116 @@ pub const DebugInfo = struct { }; } }; -pub const supports_unwinding: bool = false; + +pub const supports_unwinding: bool = true; +pub const UnwindContext = switch (builtin.cpu.arch) { + .x86 => struct { + pc: usize, + frames: []usize, + frames_capacity: usize, + next_index: usize, + /// Marked `noinline` to ensure that `RtlCaptureStackBackTrace` includes our caller. + pub noinline fn init(ctx: *windows.CONTEXT, gpa: Allocator) Allocator.Error!UnwindContext { + const frames_buf = try gpa.alloc(usize, 1024); + errdefer comptime unreachable; + const frames_len = windows.ntdll.RtlCaptureStackBackTrace(0, frames_buf.len, @ptrCast(frames_buf.ptr), null); + const regs = ctx.getRegs(); + const first_index = for (frames_buf[0..frames_len], 0..) |ret_addr, idx| { + if (ret_addr == regs.ip) break idx; + } else i: { + // If we were called by an exception handler, `regs.ip` wasn't in the trace because + // RtlCaptureStackBackTrace omits the KiUserExceptionDispatcher frame, which is the + // one in `regs.ip`. In that case, we have to start one frame shallower instead, and + // we can figure out that frame's ip from the context's bp. + const start_addr_ptr: *const usize = @ptrFromInt(regs.bp + 4); + const start_addr = start_addr_ptr.*; + for (frames_buf[0..frames_len], 0..) |ret_addr, idx| { + if (ret_addr == start_addr) break :i idx; + } + // The IP in the context can't be found; return an empty trace. + gpa.free(frames_buf); + return .{ .pc = 0, .frames = &.{}, .frames_capacity = 0, .next_index = 0 }; + }; + return .{ + .pc = @returnAddress(), + .frames = frames_buf[0..frames_len], + .frames_capacity = 0, + .next_index = first_index, + }; + } + pub fn deinit(ctx: *UnwindContext, gpa: Allocator) void { + gpa.free(ctx.frames.ptr[0..ctx.frames_capacity]); + ctx.* = undefined; + } + pub fn getFp(ctx: *UnwindContext) usize { + _ = ctx; + return 0; + } + }, + else => struct { + pc: usize, + cur: windows.CONTEXT, + history_table: windows.UNWIND_HISTORY_TABLE, + pub fn init(ctx: *const windows.CONTEXT, gpa: Allocator) Allocator.Error!UnwindContext { + _ = gpa; + return .{ + .pc = @returnAddress(), + .cur = ctx.*, + .history_table = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE), + }; + } + pub fn deinit(ctx: *UnwindContext, gpa: Allocator) void { + _ = ctx; + _ = gpa; + } + pub fn getFp(ctx: *UnwindContext) usize { + return ctx.cur.getRegs().bp; + } + }, +}; +pub fn unwindFrame(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize { + _ = module; + _ = gpa; + _ = di; + + if (builtin.cpu.arch == .x86) { + const i = context.next_index; + if (i == context.frames.len) return 0; + context.next_index += 1; + const ip = context.frames[i]; + context.pc = ip -| 1; + return ip; + } + + const current_regs = context.cur.getRegs(); + var image_base: windows.DWORD64 = undefined; + if (windows.ntdll.RtlLookupFunctionEntry(current_regs.ip, &image_base, &context.history_table)) |runtime_function| { + var handler_data: ?*anyopaque = null; + var establisher_frame: u64 = undefined; + _ = windows.ntdll.RtlVirtualUnwind( + windows.UNW_FLAG_NHANDLER, + image_base, + current_regs.ip, + runtime_function, + &context.cur, + &handler_data, + &establisher_frame, + null, + ); + } else { + // leaf function + context.cur.setIp(@as(*const usize, @ptrFromInt(current_regs.sp)).*); + context.cur.setSp(current_regs.sp + @sizeOf(usize)); + } + + const next_regs = context.cur.getRegs(); + const tib = &windows.teb().NtTib; + if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) { + return 0; + } + context.pc = next_regs.ip -| 1; + return next_regs.ip; +} const WindowsModule = @This();