| ... | ... | @@ -264,86 +264,58 @@ pub const DebugInfo = struct { |
| 264 | 264 | } |
| 265 | 265 | }; |
| 266 | 266 | |
| 267 | | pub const supports_unwinding: bool = true; |
| 268 | | pub const UnwindContext = switch (builtin.cpu.arch) { |
| 269 | | .x86 => struct { |
| 270 | | pc: usize, |
| 271 | | frames: []usize, |
| 272 | | frames_capacity: usize, |
| 273 | | next_index: usize, |
| 274 | | /// Marked `noinline` to ensure that `RtlCaptureStackBackTrace` includes our caller. |
| 275 | | pub noinline fn init(ctx: *windows.CONTEXT, gpa: Allocator) Allocator.Error!UnwindContext { |
| 276 | | const frames_buf = try gpa.alloc(usize, 1024); |
| 277 | | errdefer comptime unreachable; |
| 278 | | const frames_len = windows.ntdll.RtlCaptureStackBackTrace(0, frames_buf.len, @ptrCast(frames_buf.ptr), null); |
| 279 | | const regs = ctx.getRegs(); |
| 280 | | const first_index = for (frames_buf[0..frames_len], 0..) |ret_addr, idx| { |
| 281 | | if (ret_addr == regs.ip) break idx; |
| 282 | | } else i: { |
| 283 | | // If we were called by an exception handler, `regs.ip` wasn't in the trace because |
| 284 | | // RtlCaptureStackBackTrace omits the KiUserExceptionDispatcher frame, which is the |
| 285 | | // one in `regs.ip`. In that case, we have to start one frame shallower instead, and |
| 286 | | // we can figure out that frame's ip from the context's bp. |
| 287 | | const start_addr_ptr: *const usize = @ptrFromInt(regs.bp + 4); |
| 288 | | const start_addr = start_addr_ptr.*; |
| 289 | | for (frames_buf[0..frames_len], 0..) |ret_addr, idx| { |
| 290 | | if (ret_addr == start_addr) break :i idx; |
| 291 | | } |
| 292 | | // The IP in the context can't be found; return an empty trace. |
| 293 | | gpa.free(frames_buf); |
| 294 | | return .{ .pc = 0, .frames = &.{}, .frames_capacity = 0, .next_index = 0 }; |
| 295 | | }; |
| 296 | | return .{ |
| 297 | | .pc = @returnAddress(), |
| 298 | | .frames = frames_buf[0..frames_len], |
| 299 | | .frames_capacity = 0, |
| 300 | | .next_index = first_index, |
| 301 | | }; |
| 302 | | } |
| 303 | | pub fn deinit(ctx: *UnwindContext, gpa: Allocator) void { |
| 304 | | gpa.free(ctx.frames.ptr[0..ctx.frames_capacity]); |
| 305 | | ctx.* = undefined; |
| 306 | | } |
| 307 | | pub fn getFp(ctx: *UnwindContext) usize { |
| 308 | | _ = ctx; |
| 309 | | return 0; |
| 310 | | } |
| 311 | | }, |
| 312 | | else => struct { |
| 313 | | pc: usize, |
| 314 | | cur: windows.CONTEXT, |
| 315 | | history_table: windows.UNWIND_HISTORY_TABLE, |
| 316 | | pub fn init(ctx: *const windows.CONTEXT, gpa: Allocator) Allocator.Error!UnwindContext { |
| 317 | | _ = gpa; |
| 318 | | return .{ |
| 319 | | .pc = @returnAddress(), |
| 320 | | .cur = ctx.*, |
| 321 | | .history_table = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE), |
| 322 | | }; |
| 323 | | } |
| 324 | | pub fn deinit(ctx: *UnwindContext, gpa: Allocator) void { |
| 325 | | _ = ctx; |
| 326 | | _ = gpa; |
| 327 | | } |
| 328 | | pub fn getFp(ctx: *UnwindContext) usize { |
| 329 | | return ctx.cur.getRegs().bp; |
| 330 | | } |
| 331 | | }, |
| 267 | pub const supports_unwinding: bool = switch (builtin.cpu.arch) { |
| 268 | else => true, |
| 269 | // On x86, `RtlVirtualUnwind` does not exist. We could in theory use `RtlCaptureStackBackTrace` |
| 270 | // instead, but on x86, it turns out that function is just... doing FP unwinding with esp! It's |
| 271 | // hard to find implementation details to confirm that, but the most authoritative source I have |
| 272 | // is an entry in the LLVM mailing list from 2020/08/16 which contains this quote: |
| 273 | // |
| 274 | // > x86 doesn't have what most architectures would consider an "unwinder" in the sense of |
| 275 | // > restoring registers; there is simply a linked list of frames that participate in SEH and |
| 276 | // > that desire to be called for a dynamic unwind operation, so RtlCaptureStackBackTrace |
| 277 | // > assumes that EBP-based frames are in use and walks an EBP-based frame chain on x86 - not |
| 278 | // > all x86 code is written with EBP-based frames so while even though we generally build the |
| 279 | // > OS that way, you might always run the risk of encountering external code that uses EBP as a |
| 280 | // > general purpose register for which such an unwind attempt for a stack trace would fail. |
| 281 | // |
| 282 | // Regardless, it's easy to effectively confirm this hypothesis just by compiling some code with |
| 283 | // `-fomit-frame-pointer -OReleaseFast` and observing that `RtlCaptureStackBackTrace` returns an |
| 284 | // empty trace when it's called in such an application. Note that without `-OReleaseFast` or |
| 285 | // similar, LLVM seems reluctant to ever clobber ebp, so you'll get a trace returned which just |
| 286 | // contains all of the kernel32/ntdll frames but none of your own. Don't be deceived---this is |
| 287 | // just coincidental! |
| 288 | // |
| 289 | // Anyway, the point is, the only stack walking primitive on x86-windows is FP unwinding. We |
| 290 | // *could* ask Microsoft to do that for us with `RtlCaptureStackBackTrace`... but better to just |
| 291 | // use our existing FP unwinder in `std.debug`! |
| 292 | .x86 => false, |
| 293 | }; |
| 294 | pub const UnwindContext = struct { |
| 295 | pc: usize, |
| 296 | cur: windows.CONTEXT, |
| 297 | history_table: windows.UNWIND_HISTORY_TABLE, |
| 298 | pub fn init(ctx: *const windows.CONTEXT, gpa: Allocator) Allocator.Error!UnwindContext { |
| 299 | _ = gpa; |
| 300 | return .{ |
| 301 | .pc = @returnAddress(), |
| 302 | .cur = ctx.*, |
| 303 | .history_table = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE), |
| 304 | }; |
| 305 | } |
| 306 | pub fn deinit(ctx: *UnwindContext, gpa: Allocator) void { |
| 307 | _ = ctx; |
| 308 | _ = gpa; |
| 309 | } |
| 310 | pub fn getFp(ctx: *UnwindContext) usize { |
| 311 | return ctx.cur.getRegs().bp; |
| 312 | } |
| 332 | 313 | }; |
| 333 | 314 | pub fn unwindFrame(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo, context: *UnwindContext) !usize { |
| 334 | 315 | _ = module; |
| 335 | 316 | _ = gpa; |
| 336 | 317 | _ = di; |
| 337 | 318 | |
| 338 | | if (builtin.cpu.arch == .x86) { |
| 339 | | const i = context.next_index; |
| 340 | | if (i == context.frames.len) return 0; |
| 341 | | context.next_index += 1; |
| 342 | | const ip = context.frames[i]; |
| 343 | | context.pc = ip -| 1; |
| 344 | | return ip; |
| 345 | | } |
| 346 | | |
| 347 | 319 | const current_regs = context.cur.getRegs(); |
| 348 | 320 | var image_base: windows.DWORD64 = undefined; |
| 349 | 321 | if (windows.ntdll.RtlLookupFunctionEntry(current_regs.ip, &image_base, &context.history_table)) |runtime_function| { |