mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
std.debug: unwinding on Windows
...using `RtlVirtualUnwind` on x86_64 and aarch64, and `RtaCaptureStackBackTrace` on x86.
This commit is contained in:
parent
ac4d633ed6
commit
1392a7af17
6 changed files with 162 additions and 28 deletions
|
|
@ -378,6 +378,8 @@ pub inline fn getContext(context: *ThreadContext) bool {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invokes detectable illegal behavior when `ok` is `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.
|
/// See `writeCurrentStackTrace` to immediately print the trace instead of capturing it.
|
||||||
pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) std.builtin.StackTrace {
|
pub fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: []usize) std.builtin.StackTrace {
|
||||||
var context_buf: ThreadContext = undefined;
|
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();
|
defer it.deinit();
|
||||||
if (!it.stratOk(options.allow_unsafe_unwind)) {
|
if (!it.stratOk(options.allow_unsafe_unwind)) {
|
||||||
return .{ .index = 0, .instruction_addresses = &.{} };
|
return .{ .index = 0, .instruction_addresses = &.{} };
|
||||||
|
|
@ -657,7 +661,14 @@ pub fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, tty_
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
var context_buf: ThreadContext = undefined;
|
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();
|
defer it.deinit();
|
||||||
if (!it.stratOk(options.allow_unsafe_unwind)) {
|
if (!it.stratOk(options.allow_unsafe_unwind)) {
|
||||||
tty_config.setColor(writer, .dim) catch {};
|
tty_config.setColor(writer, .dim) catch {};
|
||||||
|
|
@ -751,14 +762,14 @@ pub fn dumpStackTrace(st: *const std.builtin.StackTrace) void {
|
||||||
|
|
||||||
const StackIterator = union(enum) {
|
const StackIterator = union(enum) {
|
||||||
/// Unwinding using debug info (e.g. DWARF CFI).
|
/// 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.
|
/// Naive frame-pointer-based unwinding. Very simple, but typically unreliable.
|
||||||
fp: usize,
|
fp: usize,
|
||||||
|
|
||||||
/// It is important that this function is marked `inline` so that it can safely use
|
/// 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
|
/// `@frameAddress` and `getContext` as the caller's stack frame and our own are one
|
||||||
/// and the same.
|
/// 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()) {
|
if (builtin.cpu.arch.isSPARC()) {
|
||||||
// Flush all the register windows on stack.
|
// Flush all the register windows on stack.
|
||||||
if (builtin.cpu.has(.sparc, .v9)) {
|
if (builtin.cpu.has(.sparc, .v9)) {
|
||||||
|
|
@ -770,10 +781,10 @@ const StackIterator = union(enum) {
|
||||||
if (context_opt) |context| {
|
if (context_opt) |context| {
|
||||||
context_buf.* = context.*;
|
context_buf.* = context.*;
|
||||||
relocateContext(context_buf);
|
relocateContext(context_buf);
|
||||||
return .{ .di = .init(context_buf) };
|
return .{ .di = try .init(context_buf, getDebugInfoAllocator()) };
|
||||||
}
|
}
|
||||||
if (getContext(context_buf)) {
|
if (getContext(context_buf)) {
|
||||||
return .{ .di = .init(context_buf) };
|
return .{ .di = try .init(context_buf, getDebugInfoAllocator()) };
|
||||||
}
|
}
|
||||||
return .{ .fp = @frameAddress() };
|
return .{ .fp = @frameAddress() };
|
||||||
}
|
}
|
||||||
|
|
@ -816,10 +827,10 @@ const StackIterator = union(enum) {
|
||||||
if (ra == 0) return .end;
|
if (ra == 0) return .end;
|
||||||
return .{ .frame = ra };
|
return .{ .frame = ra };
|
||||||
} else |err| {
|
} else |err| {
|
||||||
const bad_pc = unwind_context.pc;
|
const pc = unwind_context.pc;
|
||||||
it.* = .{ .fp = unwind_context.getFp() catch 0 };
|
it.* = .{ .fp = unwind_context.getFp() };
|
||||||
return .{ .switch_to_fp = .{
|
return .{ .switch_to_fp = .{
|
||||||
.address = bad_pc,
|
.address = pc,
|
||||||
.err = err,
|
.err = err,
|
||||||
} };
|
} };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -282,13 +282,13 @@ pub const Die = struct {
|
||||||
.@"32" => {
|
.@"32" => {
|
||||||
const byte_offset = compile_unit.str_offsets_base + 4 * index;
|
const byte_offset = compile_unit.str_offsets_base + 4 * index;
|
||||||
if (byte_offset + 4 > debug_str_offsets.len) return bad();
|
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);
|
return getStringGeneric(opt_str, offset);
|
||||||
},
|
},
|
||||||
.@"64" => {
|
.@"64" => {
|
||||||
const byte_offset = compile_unit.str_offsets_base + 8 * index;
|
const byte_offset = compile_unit.str_offsets_base + 8 * index;
|
||||||
if (byte_offset + 8 > debug_str_offsets.len) return bad();
|
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);
|
return getStringGeneric(opt_str, offset);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,8 @@ pub const target_supported: bool = Module != void;
|
||||||
/// For whether DWARF unwinding is *theoretically* possible, see `Dwarf.abi.supportsUnwinding`.
|
/// For whether DWARF unwinding is *theoretically* possible, see `Dwarf.abi.supportsUnwinding`.
|
||||||
pub const supports_unwinding: bool = Module.supports_unwinding;
|
pub const supports_unwinding: bool = Module.supports_unwinding;
|
||||||
|
|
||||||
|
pub const UnwindContext = if (supports_unwinding) Module.UnwindContext;
|
||||||
|
|
||||||
pub const init: SelfInfo = .{
|
pub const init: SelfInfo = .{
|
||||||
.modules = .empty,
|
.modules = .empty,
|
||||||
.lookup_cache = if (Module.LookupCache != void) .init,
|
.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);
|
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);
|
comptime assert(supports_unwinding);
|
||||||
const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc);
|
const module: Module = try .lookup(&self.lookup_cache, gpa, context.pc);
|
||||||
const gop = try self.modules.getOrPut(gpa, module.key());
|
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;
|
/// ) SelfInfo.Error!std.debug.Symbol;
|
||||||
/// /// Whether a reliable stack unwinding strategy, such as DWARF unwinding, is available.
|
/// /// Whether a reliable stack unwinding strategy, such as DWARF unwinding, is available.
|
||||||
/// pub const supports_unwinding: bool;
|
/// 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
|
/// /// 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
|
/// /// the next return address (which may be 0 indicating end of stack).
|
||||||
/// /// specialized to DWARF unwinding.
|
|
||||||
/// pub fn unwindFrame(
|
/// pub fn unwindFrame(
|
||||||
/// mod: *const Module,
|
/// mod: *const Module,
|
||||||
/// gpa: Allocator,
|
/// gpa: Allocator,
|
||||||
/// di: *DebugInfo,
|
/// di: *DebugInfo,
|
||||||
/// ctx: *SelfInfo.DwarfUnwindContext,
|
/// ctx: *UnwindContext,
|
||||||
/// ) SelfInfo.Error!usize;
|
/// ) SelfInfo.Error!usize;
|
||||||
/// ```
|
/// ```
|
||||||
const Module: type = Module: {
|
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 {
|
pub const DwarfUnwindContext = struct {
|
||||||
cfa: ?usize,
|
cfa: ?usize,
|
||||||
pc: usize,
|
pc: usize,
|
||||||
|
|
@ -144,8 +157,9 @@ pub const DwarfUnwindContext = struct {
|
||||||
vm: Dwarf.Unwind.VirtualMachine,
|
vm: Dwarf.Unwind.VirtualMachine,
|
||||||
stack_machine: Dwarf.expression.StackMachine(.{ .call_frame_context = true }),
|
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);
|
comptime assert(supports_unwinding);
|
||||||
|
_ = gpa;
|
||||||
|
|
||||||
const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?;
|
const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?;
|
||||||
const raw_pc_ptr = regValueNative(thread_context, ip_reg_num, null) catch {
|
const raw_pc_ptr = regValueNative(thread_context, ip_reg_num, null) catch {
|
||||||
|
|
@ -169,8 +183,8 @@ pub const DwarfUnwindContext = struct {
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getFp(self: *const DwarfUnwindContext) !usize {
|
pub fn getFp(self: *const DwarfUnwindContext) usize {
|
||||||
return (try regValueNative(self.thread_context, Dwarf.abi.fpRegNum(native_arch, self.reg_context), self.reg_context)).*;
|
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)
|
/// Resolves the register rule and places the result into `out` (see regBytes)
|
||||||
|
|
|
||||||
|
|
@ -252,10 +252,11 @@ pub fn getSymbolAtAddress(module: *const DarwinModule, gpa: Allocator, di: *Debu
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub const supports_unwinding: bool = true;
|
pub const supports_unwinding: bool = true;
|
||||||
|
pub const UnwindContext = std.debug.SelfInfo.DwarfUnwindContext;
|
||||||
/// Unwind a frame using MachO compact unwind info (from __unwind_info).
|
/// 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
|
/// 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.
|
/// 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) {
|
return unwindFrameInner(module, gpa, di, context) catch |err| switch (err) {
|
||||||
error.InvalidDebugInfo,
|
error.InvalidDebugInfo,
|
||||||
error.MissingDebugInfo,
|
error.MissingDebugInfo,
|
||||||
|
|
@ -274,7 +275,7 @@ pub fn unwindFrame(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
|
||||||
=> return error.InvalidDebugInfo,
|
=> 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();
|
if (di.unwind == null) di.unwind = module.loadUnwindInfo();
|
||||||
const unwind = &di.unwind.?;
|
const unwind = &di.unwind.?;
|
||||||
|
|
||||||
|
|
@ -575,7 +576,7 @@ fn unwindFrameInner(module: *const DarwinModule, gpa: Allocator, di: *DebugInfo,
|
||||||
else => comptime unreachable, // unimplemented
|
else => comptime unreachable, // unimplemented
|
||||||
};
|
};
|
||||||
|
|
||||||
context.pc = DwarfUnwindContext.stripInstructionPtrAuthCode(new_ip);
|
context.pc = UnwindContext.stripInstructionPtrAuthCode(new_ip);
|
||||||
if (context.pc > 0) context.pc -= 1;
|
if (context.pc > 0) context.pc -= 1;
|
||||||
return new_ip;
|
return new_ip;
|
||||||
}
|
}
|
||||||
|
|
@ -819,7 +820,6 @@ const macho = std.macho;
|
||||||
const mem = std.mem;
|
const mem = std.mem;
|
||||||
const posix = std.posix;
|
const posix = std.posix;
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const DwarfUnwindContext = std.debug.SelfInfo.DwarfUnwindContext;
|
|
||||||
const Error = std.debug.SelfInfo.Error;
|
const Error = std.debug.SelfInfo.Error;
|
||||||
const regBytes = Dwarf.abi.regBytes;
|
const regBytes = Dwarf.abi.regBytes;
|
||||||
const regValueNative = Dwarf.abi.regValueNative;
|
const regValueNative = Dwarf.abi.regValueNative;
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,7 @@ fn loadUnwindInfo(module: *const ElfModule, gpa: Allocator, di: *DebugInfo) Erro
|
||||||
else => unreachable,
|
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);
|
if (di.unwind[0] == null) try module.loadUnwindInfo(gpa, di);
|
||||||
std.debug.assert(di.unwind[0] != null);
|
std.debug.assert(di.unwind[0] != null);
|
||||||
for (&di.unwind) |*opt_unwind| {
|
for (&di.unwind) |*opt_unwind| {
|
||||||
|
|
@ -205,6 +205,7 @@ pub fn unwindFrame(module: *const ElfModule, gpa: Allocator, di: *DebugInfo, con
|
||||||
}
|
}
|
||||||
return error.MissingDebugInfo;
|
return error.MissingDebugInfo;
|
||||||
}
|
}
|
||||||
|
pub const UnwindContext = std.debug.SelfInfo.DwarfUnwindContext;
|
||||||
pub const supports_unwinding: bool = s: {
|
pub const supports_unwinding: bool = s: {
|
||||||
const archs: []const std.Target.Cpu.Arch = switch (builtin.target.os.tag) {
|
const archs: []const std.Target.Cpu.Arch = switch (builtin.target.os.tag) {
|
||||||
.linux => &.{ .x86, .x86_64, .arm, .armeb, .thumb, .thumbeb, .aarch64, .aarch64_be },
|
.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 Dwarf = std.debug.Dwarf;
|
||||||
const elf = std.elf;
|
const elf = std.elf;
|
||||||
const mem = std.mem;
|
const mem = std.mem;
|
||||||
const DwarfUnwindContext = std.debug.SelfInfo.DwarfUnwindContext;
|
|
||||||
const Error = std.debug.SelfInfo.Error;
|
const Error = std.debug.SelfInfo.Error;
|
||||||
|
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ fn loadDebugInfo(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo) !
|
||||||
if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
|
if (create_section_rc != .SUCCESS) return error.MissingDebugInfo;
|
||||||
errdefer windows.CloseHandle(section_handle);
|
errdefer windows.CloseHandle(section_handle);
|
||||||
var coff_len: usize = 0;
|
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(
|
const map_section_rc = windows.ntdll.NtMapViewOfSection(
|
||||||
section_handle,
|
section_handle,
|
||||||
process_handle,
|
process_handle,
|
||||||
|
|
@ -116,8 +116,8 @@ fn loadDebugInfo(module: *const WindowsModule, gpa: Allocator, di: *DebugInfo) !
|
||||||
windows.PAGE_READONLY,
|
windows.PAGE_READONLY,
|
||||||
);
|
);
|
||||||
if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
|
if (map_section_rc != .SUCCESS) return error.MissingDebugInfo;
|
||||||
errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr)) == .SUCCESS);
|
errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr.?)) == .SUCCESS);
|
||||||
const section_view = section_view_ptr[0..coff_len];
|
const section_view = section_view_ptr.?[0..coff_len];
|
||||||
coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
|
coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo;
|
||||||
di.mapped_file = .{
|
di.mapped_file = .{
|
||||||
.file = coff_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();
|
const WindowsModule = @This();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue