authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-08 20:08:28+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-09-30 13:44:52+01:00
log1392a7af171c00679b0754775a8f6f54e967eafd
treeb9b340770b2ecbd274316ccb77ef8e47783902c8
parentac4d633ed691159ea61130182a1b51635a95e228
signaturelock-open Commit is signed but in an unrecognized format.

std.debug: unwinding on Windows

...using `RtlVirtualUnwind` on x86_64 and aarch64, and `RtaCaptureStackBackTrace` on x86.

6 files changed, 162 insertions(+), 28 deletions(-)

lib/std/debug.zig+20-9
...@@ -378,6 +378,8 @@ pub inline fn getContext(context: *ThreadContext) bool {...@@ -378,6 +378,8 @@ pub inline fn getContext(context: *ThreadContext) bool {
378 }378 }
379 return true;379 return true;
380 }380 }
381
382 return false;
381}383}
382384
383/// Invokes detectable illegal behavior when `ok` is `false`.385/// Invokes detectable illegal behavior when `ok` is `false`.
...@@ -619,7 +621,9 @@ pub const StackUnwindOptions = struct {...@@ -619,7 +621,9 @@ pub const StackUnwindOptions = struct {
619/// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it.621/// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it.
620pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) std.builtin.StackTrace {622pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) std.builtin.StackTrace {
621 var context_buf: ThreadContext = undefined;623 var context_buf: ThreadContext = undefined;
622 var it: StackIterator = .init(options.context, &context_buf);624 var it = StackIterator.init(options.context, &context_buf) catch {
625 return .{ .index = 0, .instruction_addresses = &.{} };
626 };
623 defer it.deinit();627 defer it.deinit();
624 if (!it.stratOk(options.allow_unsafe_unwind)) {628 if (!it.stratOk(options.allow_unsafe_unwind)) {
625 return .{ .index = 0, .instruction_addresses = &.{} };629 return .{ .index = 0, .instruction_addresses = &.{} };
...@@ -657,7 +661,14 @@ pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_...@@ -657,7 +661,14 @@ pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_
657 },661 },
658 };662 };
659 var context_buf: ThreadContext = undefined;663 var context_buf: ThreadContext = undefined;
660 var it: StackIterator = .init(options.context, &context_buf);664 var it = StackIterator.init(options.context, &context_buf) catch |err| switch (err) {
665 error.OutOfMemory => {
666 tty_config.setColor(writer, .dim) catch {};
667 try writer.print("Cannot print stack trace: out of memory\n", .{});
668 tty_config.setColor(writer, .reset) catch {};
669 return;
670 },
671 };
661 defer it.deinit();672 defer it.deinit();
662 if (!it.stratOk(options.allow_unsafe_unwind)) {673 if (!it.stratOk(options.allow_unsafe_unwind)) {
663 tty_config.setColor(writer, .dim) catch {};674 tty_config.setColor(writer, .dim) catch {};
...@@ -751,14 +762,14 @@ pub fn dumpStackTrace(st: *const std.builtin.StackTrace) void {...@@ -751,14 +762,14 @@ pub fn dumpStackTrace(st: *const std.builtin.StackTrace) void {
751762
752const StackIterator = union(enum) {763const StackIterator = union(enum) {
753 /// Unwinding using debug info (e.g. DWARF CFI).764 /// Unwinding using debug info (e.g. DWARF CFI).
754 di: if (SelfInfo.supports_unwinding) SelfInfo.DwarfUnwindContext else noreturn,765 di: if (SelfInfo.supports_unwinding) SelfInfo.UnwindContext else noreturn,
755 /// Naive frame-pointer-based unwinding. Very simple, but typically unreliable.766 /// Naive frame-pointer-based unwinding. Very simple, but typically unreliable.
756 fp: usize,767 fp: usize,
757768
758 /// It is important that this function is marked `inline` so that it can safely use769 /// It is important that this function is marked `inline` so that it can safely use
759 /// `@frameAddress` and `getContext` as the caller's stack frame and our own are one770 /// `@frameAddress` and `getContext` as the caller's stack frame and our own are one
760 /// and the same.771 /// and the same.
761 inline fn init(context_opt: ?*const ThreadContext, context_buf: *ThreadContext) StackIterator {772 inline fn init(context_opt: ?*const ThreadContext, context_buf: *ThreadContext) error{OutOfMemory}!StackIterator {
762 if (builtin.cpu.arch.isSPARC()) {773 if (builtin.cpu.arch.isSPARC()) {
763 // Flush all the register windows on stack.774 // Flush all the register windows on stack.
764 if (builtin.cpu.has(.sparc, .v9)) {775 if (builtin.cpu.has(.sparc, .v9)) {
...@@ -770,10 +781,10 @@ const StackIterator = union(enum) {...@@ -770,10 +781,10 @@ const StackIterator = union(enum) {
770 if (context_opt) |context| {781 if (context_opt) |context| {
771 context_buf.* = context.*;782 context_buf.* = context.*;
772 relocateContext(context_buf);783 relocateContext(context_buf);
773 return .{ .di = .init(context_buf) };784 return .{ .di = try .init(context_buf, getDebugInfoAllocator()) };
774 }785 }
775 if (getContext(context_buf)) {786 if (getContext(context_buf)) {
776 return .{ .di = .init(context_buf) };787 return .{ .di = try .init(context_buf, getDebugInfoAllocator()) };
777 }788 }
778 return .{ .fp = @frameAddress() };789 return .{ .fp = @frameAddress() };
779 }790 }
...@@ -816,10 +827,10 @@ const StackIterator = union(enum) {...@@ -816,10 +827,10 @@ const StackIterator = union(enum) {
816 if (ra == 0) return .end;827 if (ra == 0) return .end;
817 return .{ .frame = ra };828 return .{ .frame = ra };
818 } else |err| {829 } else |err| {
819 const bad_pc = unwind_context.pc;830 const pc = unwind_context.pc;
820 it.* = .{ .fp = unwind_context.getFp() catch 0 };831 it.* = .{ .fp = unwind_context.getFp() };
821 return .{ .switch_to_fp = .{832 return .{ .switch_to_fp = .{
822 .address = bad_pc,833 .address = pc,
823 .err = err,834 .err = err,
824 } };835 } };
825 }836 }
lib/std/debug/Dwarf.zig+2-2
...@@ -282,13 +282,13 @@ pub const Die = struct {...@@ -282,13 +282,13 @@ pub const Die = struct {
282 .@"32" => {282 .@"32" => {
283 const byte_offset = compile_unit.str_offsets_base + 4 * index;283 const byte_offset = compile_unit.str_offsets_base + 4 * index;
284 if (byte_offset + 4 > debug_str_offsets.len) return bad();284 if (byte_offset + 4 > debug_str_offsets.len) return bad();
285 const offset = mem.readInt(u32, debug_str_offsets[byte_offset..][0..4], endian);285 const offset = mem.readInt(u32, debug_str_offsets[@intCast(byte_offset)..][0..4], endian);
286 return getStringGeneric(opt_str, offset);286 return getStringGeneric(opt_str, offset);
287 },287 },
288 .@"64" => {288 .@"64" => {
289 const byte_offset = compile_unit.str_offsets_base + 8 * index;289 const byte_offset = compile_unit.str_offsets_base + 8 * index;
290 if (byte_offset + 8 > debug_str_offsets.len) return bad();290 if (byte_offset + 8 > debug_str_offsets.len) return bad();
291 const offset = mem.readInt(u64, debug_str_offsets[byte_offset..][0..8], endian);291 const offset = mem.readInt(u64, debug_str_offsets[@intCast(byte_offset)..][0..8], endian);
292 return getStringGeneric(opt_str, offset);292 return getStringGeneric(opt_str, offset);
293 },293 },
294 }294 }
lib/std/debug/SelfInfo.zig+21-7
...@@ -42,6 +42,8 @@ pub const target_supported: bool = Module != void;...@@ -42,6 +42,8 @@ pub const target_supported: bool = Module != void;
42/// For whether DWARF unwinding is *theoretically* possible, see `Dwarf.abi.supportsUnwinding`.42/// For whether DWARF unwinding is *theoretically* possible, see `Dwarf.abi.supportsUnwinding`.
43pub const supports_unwinding: bool = Module.supports_unwinding;43pub const supports_unwinding: bool = Module.supports_unwinding;
4444
45pub const UnwindContext = if (supports_unwinding) Module.UnwindContext;
46
45pub const init: SelfInfo = .{47pub const init: SelfInfo = .{
46 .modules = .empty,48 .modules = .empty,
47 .lookup_cache = if (Module.LookupCache != void) .init,49 .lookup_cache = if (Module.LookupCache != void) .init,
...@@ -53,7 +55,7 @@ pub fn deinit(self: *SelfInfo, gpa: Allocator) void {...@@ -53,7 +55,7 @@ pub fn deinit(self: *SelfInfo, gpa: Allocator) void {
53 if (Module.LookupCache != void) self.lookup_cache.deinit(gpa);55 if (Module.LookupCache != void) self.lookup_cache.deinit(gpa);
54}56}
5557
56pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *DwarfUnwindContext) Error!usize {58pub fn unwindFrame(self: *SelfInfo, gpa: Allocator, context: *UnwindContext) Error!usize {
57 comptime assert(supports_unwinding);59 comptime assert(supports_unwinding);
58 const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc);60 const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc);
59 const gop = try self.modules.getOrPut(gpa, module.key());61 const gop = try self.modules.getOrPut(gpa, module.key());
...@@ -113,14 +115,23 @@ pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize)...@@ -113,14 +115,23 @@ pub fn getModuleNameForAddress(self: *SelfInfo, gpa: Allocator, address: usize)
113/// ) SelfInfo.Error!std.debug.Symbol;115/// ) SelfInfo.Error!std.debug.Symbol;
114/// /// Whether a reliable stack unwinding strategy, such as DWARF unwinding, is available.116/// /// Whether a reliable stack unwinding strategy, such as DWARF unwinding, is available.
115/// pub const supports_unwinding: bool;117/// pub const supports_unwinding: bool;
118/// /// Only required if `supports_unwinding == true`.
119/// pub const UnwindContext = struct {
120/// /// A PC value inside the function of the last unwound frame.
121/// pc: usize,
122/// pub fn init(tc: *std.debug.ThreadContext, gpa: Allocator) Allocator.Error!UnwindContext;
123/// pub fn deinit(uc: *UnwindContext, gpa: Allocator) void;
124/// /// Returns the frame pointer associated with the last unwound stack frame. If the frame
125/// /// pointer is unknown, 0 may be returned instead.
126/// pub fn getFp(uc: *UnwindContext) usize;
127/// };
116/// /// Only required if `supports_unwinding == true`. Unwinds a single stack frame and returns128/// /// Only required if `supports_unwinding == true`. Unwinds a single stack frame and returns
117/// /// the next return address (which may be 0 indicating end of stack). This is currently129/// /// the next return address (which may be 0 indicating end of stack).
118/// /// specialized to DWARF unwinding.
119/// pub fn unwindFrame(130/// pub fn unwindFrame(
120/// mod: *const Module,131/// mod: *const Module,
121/// gpa: Allocator,132/// gpa: Allocator,
122/// di: *DebugInfo,133/// di: *DebugInfo,
123/// ctx: *SelfInfo.DwarfUnwindContext,134/// ctx: *UnwindContext,
124/// ) SelfInfo.Error!usize;135/// ) SelfInfo.Error!usize;
125/// ```136/// ```
126const Module: type = Module: {137const Module: type = Module: {
...@@ -136,6 +147,8 @@ const Module: type = Module: {...@@ -136,6 +147,8 @@ const Module: type = Module: {
136 };147 };
137};148};
138149
150/// An implementation of `UnwindContext` useful for DWARF-based unwinders. The `Module.unwindFrame`
151/// implementation should wrap `DwarfUnwindContext.unwindFrame`.
139pub const DwarfUnwindContext = struct {152pub const DwarfUnwindContext = struct {
140 cfa: ?usize,153 cfa: ?usize,
141 pc: usize,154 pc: usize,
...@@ -144,8 +157,9 @@ pub const DwarfUnwindContext = struct {...@@ -144,8 +157,9 @@ pub const DwarfUnwindContext = struct {
144 vm: Dwarf.Unwind.VirtualMachine,157 vm: Dwarf.Unwind.VirtualMachine,
145 stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }),158 stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }),
146159
147 pub fn init(thread_context: *std.debug.ThreadContext) DwarfUnwindContext {160 pub fn init(thread_context: *std.debug.ThreadContext, gpa: Allocator) error{}!DwarfUnwindContext {
148 comptime assert(supports_unwinding);161 comptime assert(supports_unwinding);
162 _ = gpa;
149163
150 const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?;164 const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?;
151 const raw_pc_ptr = regValueNative(thread_context, ip_reg_num, null) catch {165 const raw_pc_ptr = regValueNative(thread_context, ip_reg_num, null) catch {
...@@ -169,8 +183,8 @@ pub const DwarfUnwindContext = struct {...@@ -169,8 +183,8 @@ pub const DwarfUnwindContext = struct {
169 self.* = undefined;183 self.* = undefined;
170 }184 }
171185
172 pub fn getFp(self: *const DwarfUnwindContext) !usize {186 pub fn getFp(self: *const DwarfUnwindContext) usize {
173 return (try regValueNative(self.thread_context, Dwarf.abi.fpRegNum(native_arch, self.reg_context), self.reg_context)).*;187 return (regValueNative(self.thread_context, Dwarf.abi.fpRegNum(native_arch, self.reg_context), self.reg_context) catch return 0).*;
174 }188 }
175189
176 /// Resolves the register rule and places the result into `out` (see regBytes)190 /// Resolves the register rule and places the result into `out` (see regBytes)
lib/std/debug/SelfInfo/DarwinModule.zig+4-4
...@@ -252,10 +252,11 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu...@@ -252,10 +252,11 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu
252 };252 };
253}253}
254pub const supports_unwinding: bool = true;254pub const supports_unwinding: bool = true;
255pub const UnwindContext = std.debug.SelfInfo.DwarfUnwindContext;
255/// Unwind a frame using MachO compact unwind info (from __unwind_info).256/// Unwind a frame using MachO compact unwind info (from __unwind_info).
256/// If the compact encoding can't encode a way to unwind a frame, it will257/// If the compact encoding can't encode a way to unwind a frame, it will
257/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.258/// defer unwinding to DWARF, in which case `.eh_frame` will be used if available.
258pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *DwarfUnwindContext) Error!usize {259pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize {
259 return unwindFrameInner(module, gpa, di, context) catch |err| switch (err) {260 return unwindFrameInner(module, gpa, di, context) catch |err| switch (err) {
260 error.InvalidDebugInfo,261 error.InvalidDebugInfo,
261 error.MissingDebugInfo,262 error.MissingDebugInfo,
...@@ -274,7 +275,7 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,...@@ -274,7 +275,7 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
274 => return error.InvalidDebugInfo,275 => return error.InvalidDebugInfo,
275 };276 };
276}277}
277fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *DwarfUnwindContext) !usize {278fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
278 if (di.unwind == null) di.unwind = module.loadUnwindInfo();279 if (di.unwind == null) di.unwind = module.loadUnwindInfo();
279 const unwind = &di.unwind.?;280 const unwind = &di.unwind.?;
280281
...@@ -575,7 +576,7 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,...@@ -575,7 +576,7 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
575 else => comptime unreachable, // unimplemented576 else => comptime unreachable, // unimplemented
576 };577 };
577578
578 context.pc = DwarfUnwindContext.stripInstructionPtrAuthCode(new_ip);579 context.pc = UnwindContext.stripInstructionPtrAuthCode(new_ip);
579 if (context.pc > 0) context.pc -= 1;580 if (context.pc > 0) context.pc -= 1;
580 return new_ip;581 return new_ip;
581}582}
...@@ -819,7 +820,6 @@ const macho = std.macho;...@@ -819,7 +820,6 @@ const macho = std.macho;
819const mem = std.mem;820const mem = std.mem;
820const posix = std.posix;821const posix = std.posix;
821const testing = std.testing;822const testing = std.testing;
822const DwarfUnwindContext = std.debug.SelfInfo.DwarfUnwindContext;
823const Error = std.debug.SelfInfo.Error;823const Error = std.debug.SelfInfo.Error;
824const regBytes = Dwarf.abi.regBytes;824const regBytes = Dwarf.abi.regBytes;
825const regValueNative = Dwarf.abi.regValueNative;825const regValueNative = Dwarf.abi.regValueNative;
lib/std/debug/SelfInfo/ElfModule.zig+2-2
...@@ -193,7 +193,7 @@ fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Erro...@@ -193,7 +193,7 @@ fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Erro
193 else => unreachable,193 else => unreachable,
194 }194 }
195}195}
196pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *DwarfUnwindContext) Error!usize {196pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) Error!usize {
197 if (di.unwind[0] == null) try module.loadUnwindInfo(gpa, di);197 if (di.unwind[0] == null) try module.loadUnwindInfo(gpa, di);
198 std.debug.assert(di.unwind[0] != null);198 std.debug.assert(di.unwind[0] != null);
199 for (&di.unwind) |*opt_unwind| {199 for (&di.unwind) |*opt_unwind| {
...@@ -205,6 +205,7 @@ pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, con...@@ -205,6 +205,7 @@ pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, con
205 }205 }
206 return error.MissingDebugInfo;206 return error.MissingDebugInfo;
207}207}
208pub const UnwindContext = std.debug.SelfInfo.DwarfUnwindContext;
208pub const supports_unwinding: bool = s: {209pub const supports_unwinding: bool = s: {
209 const archs: []const std.Target.Cpu.Arch = switch (builtin.target.os.tag) {210 const archs: []const std.Target.Cpu.Arch = switch (builtin.target.os.tag) {
210 .linux => &.{ .x86, .x86_64, .arm, .armeb, .thumb, .thumbeb, .aarch64, .aarch64_be },211 .linux => &.{ .x86, .x86_64, .arm, .armeb, .thumb, .thumbeb, .aarch64, .aarch64_be },
...@@ -233,7 +234,6 @@ const Allocator = std.mem.Allocator;...@@ -233,7 +234,6 @@ const Allocator = std.mem.Allocator;
233const Dwarf = std.debug.Dwarf;234const Dwarf = std.debug.Dwarf;
234const elf = std.elf;235const elf = std.elf;
235const mem = std.mem;236const mem = std.mem;
236const DwarfUnwindContext = std.debug.SelfInfo.DwarfUnwindContext;
237const Error = std.debug.SelfInfo.Error;237const Error = std.debug.SelfInfo.Error;
238238
239const builtin = @import("builtin");239const builtin = @import("builtin");
lib/std/debug/SelfInfo/WindowsModule.zig+113-4
...@@ -102,7 +102,7 @@ fn loadDebugInfo(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo) !...@@ -102,7 +102,7 @@ fn loadDebugInfo(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo) !
102 if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;102 if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
103 errdefer windows.CloseHandle(section_handle);103 errdefer windows.CloseHandle(section_handle);
104 var coff_len: usize = 0;104 var coff_len: usize = 0;
105 var section_view_ptr: [*]const u8 = undefined;105 var section_view_ptr: ?[*]const u8 = null;
106 const map_section_rc = windows.ntdll.NtMapViewOfSection(106 const map_section_rc = windows.ntdll.NtMapViewOfSection(
107 section_handle,107 section_handle,
108 process_handle,108 process_handle,
...@@ -116,8 +116,8 @@ fn loadDebugInfo(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo) !...@@ -116,8 +116,8 @@ fn loadDebugInfo(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo) !
116 windows.PAGE_READONLY,116 windows.PAGE_READONLY,
117 );117 );
118 if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;118 if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
119 errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr)) == .SUCCESS);119 errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr.?)) == .SUCCESS);
120 const section_view = section_view_ptr[0..coff_len];120 const section_view = section_view_ptr.?[0..coff_len];
121 coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;121 coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
122 di.mapped_file = .{122 di.mapped_file = .{
123 .file = coff_file,123 .file = coff_file,
...@@ -246,7 +246,116 @@ pub const DebugInfo = struct {...@@ -246,7 +246,116 @@ pub const DebugInfo = struct {
246 };246 };
247 }247 }
248};248};
249pub const supports_unwinding: bool = false;249
250pub const supports_unwinding: bool = true;
251pub const UnwindContext = switch (builtin.cpu.arch) {
252 .x86 => struct {
253 pc: usize,
254 frames: []usize,
255 frames_capacity: usize,
256 next_index: usize,
257 /// Marked `noinline` to ensure that `RtlCaptureStackBackTrace` includes our caller.
258 pub noinline fn init(ctx: *windows.CONTEXT, gpa: Allocator) Allocator.Error!UnwindContext {
259 const frames_buf = try gpa.alloc(usize, 1024);
260 errdefer comptime unreachable;
261 const frames_len = windows.ntdll.RtlCaptureStackBackTrace(0, frames_buf.len, @ptrCast(frames_buf.ptr), null);
262 const regs = ctx.getRegs();
263 const first_index = for (frames_buf[0..frames_len], 0..) |ret_addr, idx| {
264 if (ret_addr == regs.ip) break idx;
265 } else i: {
266 // If we were called by an exception handler, `regs.ip` wasn't in the trace because
267 // RtlCaptureStackBackTrace omits the KiUserExceptionDispatcher frame, which is the
268 // one in `regs.ip`. In that case, we have to start one frame shallower instead, and
269 // we can figure out that frame's ip from the context's bp.
270 const start_addr_ptr: *const usize = @ptrFromInt(regs.bp + 4);
271 const start_addr = start_addr_ptr.*;
272 for (frames_buf[0..frames_len], 0..) |ret_addr, idx| {
273 if (ret_addr == start_addr) break :i idx;
274 }
275 // The IP in the context can't be found; return an empty trace.
276 gpa.free(frames_buf);
277 return .{ .pc = 0, .frames = &.{}, .frames_capacity = 0, .next_index = 0 };
278 };
279 return .{
280 .pc = @returnAddress(),
281 .frames = frames_buf[0..frames_len],
282 .frames_capacity = 0,
283 .next_index = first_index,
284 };
285 }
286 pub fn deinit(ctx: *UnwindContext, gpa: Allocator) void {
287 gpa.free(ctx.frames.ptr[0..ctx.frames_capacity]);
288 ctx.* = undefined;
289 }
290 pub fn getFp(ctx: *UnwindContext) usize {
291 _ = ctx;
292 return 0;
293 }
294 },
295 else => struct {
296 pc: usize,
297 cur: windows.CONTEXT,
298 history_table: windows.UNWIND_HISTORY_TABLE,
299 pub fn init(ctx: *const windows.CONTEXT, gpa: Allocator) Allocator.Error!UnwindContext {
300 _ = gpa;
301 return .{
302 .pc = @returnAddress(),
303 .cur = ctx.*,
304 .history_table = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE),
305 };
306 }
307 pub fn deinit(ctx: *UnwindContext, gpa: Allocator) void {
308 _ = ctx;
309 _ = gpa;
310 }
311 pub fn getFp(ctx: *UnwindContext) usize {
312 return ctx.cur.getRegs().bp;
313 }
314 },
315};
316pub fn unwindFrame(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize {
317 _ = module;
318 _ = gpa;
319 _ = di;
320
321 if (builtin.cpu.arch == .x86) {
322 const i = context.next_index;
323 if (i == context.frames.len) return 0;
324 context.next_index += 1;
325 const ip = context.frames[i];
326 context.pc = ip -| 1;
327 return ip;
328 }
329
330 const current_regs = context.cur.getRegs();
331 var image_base: windows.DWORD64 = undefined;
332 if (windows.ntdll.RtlLookupFunctionEntry(current_regs.ip, &image_base, &context.history_table)) |runtime_function| {
333 var handler_data: ?*anyopaque = null;
334 var establisher_frame: u64 = undefined;
335 _ = windows.ntdll.RtlVirtualUnwind(
336 windows.UNW_FLAG_NHANDLER,
337 image_base,
338 current_regs.ip,
339 runtime_function,
340 &context.cur,
341 &handler_data,
342 &establisher_frame,
343 null,
344 );
345 } else {
346 // leaf function
347 context.cur.setIp(@as(*const usize, @ptrFromInt(current_regs.sp)).*);
348 context.cur.setSp(current_regs.sp + @sizeOf(usize));
349 }
350
351 const next_regs = context.cur.getRegs();
352 const tib = &windows.teb().NtTib;
353 if (next_regs.sp < @intFromPtr(tib.StackLimit) or next_regs.sp > @intFromPtr(tib.StackBase)) {
354 return 0;
355 }
356 context.pc = next_regs.ip -| 1;
357 return next_regs.ip;
358}
250359
251const WindowsModule = @This();360const WindowsModule = @This();
252361