diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 5428d7a33b..2c3f93b55e 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -135,7 +135,7 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void { pub const StackTraceContext = blk: { if (native_os == .windows) { - break :blk @typeInfo(@TypeOf(os.windows.CONTEXT.getRegs)).Fn.return_type.?; + break :blk std.os.windows.CONTEXT; } else if (@hasDecl(os.system, "ucontext_t")) { break :blk os.ucontext_t; } else { @@ -166,7 +166,14 @@ pub fn dumpStackTraceFromBase(context: *const StackTraceContext) void { }; const tty_config = io.tty.detectConfig(io.getStdErr()); if (native_os == .windows) { - writeCurrentStackTraceWindows(stderr, debug_info, tty_config, context.ip) catch return; + // On x86_64 and aarch64, the stack will be unwound using RtlVirtualUnwind using the context + // provided by the exception handler. On x86, RtlVirtualUnwind doesn't exist. Instead, a new backtrace + // will be captured and frames prior to the exception will be filtered. + // The caveat is that RtlCaptureStackBackTrace does not include the KiUserExceptionDispatcher frame, + // which is where the IP in `context` points to, so it can't be used as start_addr. + // Instead, start_addr is recovered from the stack. + const start_addr = if (builtin.cpu.arch == .x86) @as(*const usize, @ptrFromInt(context.getRegs().bp + 4)).* else null; + writeStackTraceWindows(stderr, debug_info, tty_config, context, start_addr) catch return; return; } @@ -196,12 +203,12 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT if (native_os == .windows) { const addrs = stack_trace.instruction_addresses; const first_addr = first_address orelse { - stack_trace.index = walkStackWindows(addrs[0..]); + stack_trace.index = walkStackWindows(addrs[0..], null); return; }; var addr_buf_stack: [32]usize = undefined; const addr_buf = if (addr_buf_stack.len > addrs.len) addr_buf_stack[0..] else addrs; - const n = walkStackWindows(addr_buf[0..]); + const n = walkStackWindows(addr_buf[0..], null); const first_index = for (addr_buf[0..n], 0..) |addr, i| { if (addr == first_addr) { break i; @@ -218,7 +225,7 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT } stack_trace.index = slice.len; } else { - // TODO: This should use the dwarf unwinder if it's available + // TODO: This should use the DWARF unwinder if .eh_frame_hdr is available (so that full debug info parsing isn't required) var it = StackIterator.init(first_address, null); defer it.deinit(); for (stack_trace.instruction_addresses, 0..) |*addr, i| { @@ -415,10 +422,18 @@ pub fn writeStackTrace( inline fn getContext(context: *StackTraceContext) bool { if (native_os == .windows) { - @compileError("Syscall please!"); + context.* = std.mem.zeroes(windows.CONTEXT); + windows.ntdll.RtlCaptureContext(context); + return true; } - return @hasDecl(os.system, "getcontext") and os.system.getcontext(context) == 0; + const supports_getcontext = @hasDecl(os.system, "getcontext") and + (builtin.os.tag != .linux or switch (builtin.cpu.arch) { + .x86, .x86_64 => true, + else => false, + }); + + return supports_getcontext and os.system.getcontext(context) == 0; } pub const StackIterator = struct { @@ -431,6 +446,7 @@ pub const StackIterator = struct { // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer). debug_info: ?*DebugInfo, dwarf_context: if (supports_context) DW.UnwindContext else void = undefined, + pub const supports_context = @hasDecl(os.system, "ucontext_t") and (builtin.os.tag != .linux or switch (builtin.cpu.arch) { .mips, .mipsel, .mips64, .mips64el, .riscv64 => false, @@ -548,19 +564,20 @@ pub const StackIterator = struct { } } - fn next_dwarf(self: *StackIterator) !void { + fn next_dwarf(self: *StackIterator) !usize { const module = try self.debug_info.?.getModuleForAddress(self.dwarf_context.pc); if (try module.getDwarfInfoForAddress(self.debug_info.?.allocator, self.dwarf_context.pc)) |di| { self.dwarf_context.reg_ctx.eh_frame = true; self.dwarf_context.reg_ctx.is_macho = di.is_macho; - try di.unwindFrame(self.debug_info.?.allocator, &self.dwarf_context, module.base_address); + return di.unwindFrame(self.debug_info.?.allocator, &self.dwarf_context, module.base_address); } else return error.MissingDebugInfo; } fn next_internal(self: *StackIterator) ?usize { if (supports_context and self.debug_info != null) { - if (self.next_dwarf()) |_| { - return self.dwarf_context.pc; + if (self.dwarf_context.pc == 0) return null; + if (self.next_dwarf()) |return_address| { + return return_address; } else |err| { if (err != error.MissingFDE) print("DWARF unwind error: {}\n", .{err}); @@ -611,12 +628,13 @@ pub fn writeCurrentStackTrace( tty_config: io.tty.Config, start_addr: ?usize, ) !void { + var context: StackTraceContext = undefined; + const has_context = getContext(&context); if (native_os == .windows) { - return writeCurrentStackTraceWindows(out_stream, debug_info, tty_config, start_addr); + return writeStackTraceWindows(out_stream, debug_info, tty_config, &context, start_addr); } - var context: StackTraceContext = undefined; - var it = (if (getContext(&context)) blk: { + var it = (if (has_context) blk: { break :blk StackIterator.initWithContext(start_addr, debug_info, &context) catch null; } else null) orelse StackIterator.init(start_addr, null); defer it.deinit(); @@ -632,7 +650,7 @@ pub fn writeCurrentStackTrace( } } -pub noinline fn walkStackWindows(addresses: []usize) usize { +pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const windows.CONTEXT) usize { if (builtin.cpu.arch == .x86) { // RtlVirtualUnwind doesn't exist on x86 return windows.ntdll.RtlCaptureStackBackTrace(0, addresses.len, @as(**anyopaque, @ptrCast(addresses.ptr)), null); @@ -640,8 +658,13 @@ pub noinline fn walkStackWindows(addresses: []usize) usize { const tib = @as(*const windows.NT_TIB, @ptrCast(&windows.teb().Reserved1)); - var context: windows.CONTEXT = std.mem.zeroes(windows.CONTEXT); - windows.ntdll.RtlCaptureContext(&context); + var context: windows.CONTEXT = undefined; + if (existing_context) |context_ptr| { + context = context_ptr.*; + } else { + context = std.mem.zeroes(windows.CONTEXT); + windows.ntdll.RtlCaptureContext(&context); + } var i: usize = 0; var image_base: usize = undefined; @@ -683,14 +706,15 @@ pub noinline fn walkStackWindows(addresses: []usize) usize { return i; } -pub fn writeCurrentStackTraceWindows( +pub fn writeStackTraceWindows( out_stream: anytype, debug_info: *DebugInfo, tty_config: io.tty.Config, + context: *const windows.CONTEXT, start_addr: ?usize, ) !void { var addr_buf: [1024]usize = undefined; - const n = walkStackWindows(addr_buf[0..]); + const n = walkStackWindows(addr_buf[0..], context); const addrs = addr_buf[0..n]; var start_i: usize = if (start_addr) |saddr| blk: { for (addrs, 0..) |addr, i| { @@ -2164,16 +2188,15 @@ fn handleSegfaultWindowsExtra( } fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) void { - const regs = info.ContextRecord.getRegs(); const stderr = io.getStdErr().writer(); _ = switch (msg) { 0 => stderr.print("{s}\n", .{label.?}), 1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}), - 2 => stderr.print("Illegal instruction at address 0x{x}\n", .{regs.ip}), + 2 => stderr.print("Illegal instruction at address 0x{x}\n", .{info.ContextRecord.getRegs().ip}), else => unreachable, } catch os.abort(); - dumpStackTraceFromBase(®s); + dumpStackTraceFromBase(info.ContextRecord); } pub fn dumpStackPointerAddr(prefix: []const u8) void { diff --git a/lib/std/dwarf.zig b/lib/std/dwarf.zig index f152876191..06714aad2f 100644 --- a/lib/std/dwarf.zig +++ b/lib/std/dwarf.zig @@ -1577,9 +1577,9 @@ pub const DwarfInfo = struct { } } - pub fn unwindFrame(di: *const DwarfInfo, allocator: mem.Allocator, context: *UnwindContext, module_base_address: usize) !void { + pub fn unwindFrame(di: *const DwarfInfo, allocator: mem.Allocator, context: *UnwindContext, module_base_address: usize) !usize { if (!comptime abi.isSupportedArch(builtin.target.cpu.arch)) return error.UnsupportedCpuArchitecture; - if (context.pc == 0) return; + if (context.pc == 0) return 0; // TODO: Handle unwinding from a signal frame (ie. use_prev_instr in libunwind) @@ -1626,8 +1626,11 @@ pub const DwarfInfo = struct { } context.vm.reset(); + context.reg_ctx.eh_frame = cie.version != 4; + + _ = try context.vm.runToNative(allocator, mapped_pc, cie, fde); + const row = &context.vm.current_row; - const row = try context.vm.runToNative(allocator, mapped_pc, cie, fde); context.cfa = switch (row.cfa.rule) { .val_offset => |offset| blk: { const register = row.cfa.register orelse return error.InvalidCFARule; @@ -1650,7 +1653,7 @@ pub const DwarfInfo = struct { var next_ucontext = context.ucontext; var has_next_ip = false; - for (context.vm.rowColumns(row)) |column| { + for (context.vm.rowColumns(row.*)) |column| { if (column.register) |register| { const dest = try abi.regBytes(&next_ucontext, register, context.reg_ctx); if (register == cie.return_address_register) { @@ -1670,6 +1673,14 @@ pub const DwarfInfo = struct { } mem.writeIntSliceNative(usize, try abi.regBytes(&context.ucontext, abi.spRegNum(context.reg_ctx), context.reg_ctx), context.cfa.?); + + // The call instruction will have pushed the address of the instruction that follows the call as the return address + // However, this return address may be past the end of the function if the caller was `noreturn`. + // TODO: Check this on non-x86_64 + const return_address = context.pc; + if (context.pc > 0) context.pc -= 1; + + return return_address; } }; diff --git a/lib/std/dwarf/call_frame.zig b/lib/std/dwarf/call_frame.zig index 460d157bf9..f512d7a909 100644 --- a/lib/std/dwarf/call_frame.zig +++ b/lib/std/dwarf/call_frame.zig @@ -386,7 +386,9 @@ pub const VirtualMachine = struct { } /// Runs the CIE instructions, then the FDE instructions. Execution halts - /// once the row that corresponds to `pc` is known, and it is returned. + /// once the row that corresponds to `pc` is known (and set as `current_row`). + /// + /// The state of the row prior to the last execution step is returned. pub fn runTo( self: *VirtualMachine, allocator: std.mem.Allocator, diff --git a/lib/std/os/linux/x86.zig b/lib/std/os/linux/x86.zig index 05c012c77c..81a3bac92b 100644 --- a/lib/std/os/linux/x86.zig +++ b/lib/std/os/linux/x86.zig @@ -389,3 +389,57 @@ pub const SC = struct { pub const recvmmsg = 19; pub const sendmmsg = 20; }; + +fn gpRegisterOffset(comptime reg_index: comptime_int) usize { + return @offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "gregs") + @sizeOf(usize) * reg_index; +} + +pub inline fn getcontext(context: *ucontext_t) usize { + asm volatile ( + \\ movl %%edi, (%[edi_offset])(%[context]) + \\ movl %%esi, (%[esi_offset])(%[context]) + \\ movl %%ebp, (%[ebp_offset])(%[context]) + \\ movl %%esp, (%[esp_offset])(%[context]) + \\ movl %%ebx, (%[ebx_offset])(%[context]) + \\ movl %%edx, (%[edx_offset])(%[context]) + \\ movl %%ecx, (%[ecx_offset])(%[context]) + \\ movl %%eax, (%[eax_offset])(%[context]) + \\ xorl %%ecx, %%ecx + \\ movw %%fs, %%cx + \\ movl %%ecx, (%[fs_offset])(%[context]) + \\ leal (%[regspace_offset])(%[context]), %%ecx + \\ movl %%ecx, (%[fpregs_offset])(%[context]) + \\ fnstenv (%%ecx) + \\ fldenv (%%ecx) + \\ call getcontext_read_eip + \\ getcontext_read_eip: pop %%ecx + \\ movl %%ecx, (%[eip_offset])(%[context]) + : + : [context] "{edi}" (context), + [edi_offset] "p" (comptime gpRegisterOffset(REG.EDI)), + [esi_offset] "p" (comptime gpRegisterOffset(REG.ESI)), + [ebp_offset] "p" (comptime gpRegisterOffset(REG.EBP)), + [esp_offset] "p" (comptime gpRegisterOffset(REG.ESP)), + [ebx_offset] "p" (comptime gpRegisterOffset(REG.EBX)), + [edx_offset] "p" (comptime gpRegisterOffset(REG.EDX)), + [ecx_offset] "p" (comptime gpRegisterOffset(REG.ECX)), + [eax_offset] "p" (comptime gpRegisterOffset(REG.EAX)), + [eip_offset] "p" (comptime gpRegisterOffset(REG.EIP)), + [fs_offset] "p" (comptime gpRegisterOffset(REG.FS)), + [fpregs_offset] "p" (@offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "fpregs")), + [regspace_offset] "p" (@offsetOf(ucontext_t, "regspace")), + : "memory", "ecx" + ); + + // TODO: Read CS/SS registers? + // TODO: Store mxcsr state, need an actual definition of fpstate for that + + // TODO: `flags` isn't present in the getcontext man page, figure out what to write here + context.flags = 0; + context.link = null; + + const altstack_result = linux.sigaltstack(null, &context.stack); + if (altstack_result != 0) return altstack_result; + + return linux.sigprocmask(0, null, &context.sigmask); +} diff --git a/lib/std/os/linux/x86_64.zig b/lib/std/os/linux/x86_64.zig index 0beb70e691..786a95c7a5 100644 --- a/lib/std/os/linux/x86_64.zig +++ b/lib/std/os/linux/x86_64.zig @@ -425,6 +425,7 @@ pub inline fn getcontext(context: *ucontext_t) usize { \\ leaq (%[fpmem_offset])(%[context]), %%rcx \\ movq %%rcx, (%[fpstate_offset])(%[context]) \\ fnstenv (%%rcx) + \\ fldenv (%%rcx) \\ stmxcsr (%[mxcsr_offset])(%[context]) : : [context] "{rdi}" (context), diff --git a/src/crash_report.zig b/src/crash_report.zig index 83c5af7ba0..f09fce14f9 100644 --- a/src/crash_report.zig +++ b/src/crash_report.zig @@ -233,10 +233,9 @@ fn handleSegfaultWindows(info: *os.windows.EXCEPTION_POINTERS) callconv(os.windo fn handleSegfaultWindowsExtra(info: *os.windows.EXCEPTION_POINTERS, comptime msg: WindowsSegfaultMessage) noreturn { PanicSwitch.preDispatch(); - const stack_ctx = if (@hasDecl(os.windows, "CONTEXT")) ctx: { - const regs = info.ContextRecord.getRegs(); - break :ctx StackContext{ .exception = regs }; - } else ctx: { + const stack_ctx = if (@hasDecl(os.windows, "CONTEXT")) + StackContext{ .exception = info.ContextRecord } + else ctx: { const addr = @intFromPtr(info.ExceptionRecord.ExceptionAddress); break :ctx StackContext{ .current = .{ .ret_addr = addr } }; }; @@ -251,7 +250,7 @@ fn handleSegfaultWindowsExtra(info: *os.windows.EXCEPTION_POINTERS, comptime msg }, .illegal_instruction => { const ip: ?usize = switch (stack_ctx) { - .exception => |ex| ex.ip, + .exception => |ex| ex.getRegs().ip, .current => |cur| cur.ret_addr, .not_supported => null, }; @@ -272,7 +271,7 @@ const StackContext = union(enum) { current: struct { ret_addr: ?usize, }, - exception: debug.StackTraceContext, + exception: *const debug.StackTraceContext, not_supported: void, pub fn dumpStackTrace(ctx: @This()) void {