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