From 1a1598c58cf506217355c6b8eec84c0e9a3a6d2e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 2 Jul 2019 13:27:40 -0400 Subject: [PATCH] stack traces on segfault by default for linux-x86_64 closes #2355 --- std/debug.zig | 67 +++++++++++++++ std/os.zig | 10 +++ std/os/bits/linux.zig | 15 ++-- std/os/bits/linux/x86_64.zig | 155 +++++++++++++++++++++++++++++++++++ std/os/linux.zig | 4 +- std/special/start.zig | 9 ++ 6 files changed, 252 insertions(+), 8 deletions(-) diff --git a/std/debug.zig b/std/debug.zig index 223f93d1ad..0a007418f5 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -99,6 +99,32 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void { }; } +/// Tries to print the stack trace starting from the supplied base pointer to stderr, +/// unbuffered, and ignores any error returned. +/// TODO multithreaded awareness +pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void { + const stderr = getStderrStream() catch return; + if (builtin.strip_debug_info) { + stderr.print("Unable to dump stack trace: debug info stripped\n") catch return; + return; + } + const debug_info = getSelfDebugInfo() catch |err| { + stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", @errorName(err)) catch return; + return; + }; + const tty_color = wantTtyColor(); + printSourceAtAddress(debug_info, stderr, ip, tty_color) catch return; + const first_return_address = @intToPtr(*const usize, bp + @sizeOf(usize)).*; + printSourceAtAddress(debug_info, stderr, first_return_address - 1, tty_color) catch return; + var it = StackIterator{ + .first_addr = null, + .fp = bp, + }; + while (it.next()) |return_address| { + printSourceAtAddress(debug_info, stderr, return_address - 1, tty_color) catch return; + } +} + /// Returns a slice with the same pointer as addresses, with a potentially smaller len. /// On Windows, when first_address is not null, we ask for at least 32 stack frames, /// and then try to find the first address. If addresses.len is more than 32, we @@ -2291,3 +2317,44 @@ fn getDebugInfoAllocator() *mem.Allocator { debug_info_allocator = &debug_info_arena_allocator.allocator; return &debug_info_arena_allocator.allocator; } + +/// Whether or not the current target can print useful debug information when a segfault occurs. +pub const have_segfault_handling_support = builtin.arch == .x86_64 and builtin.os == .linux; + +/// Attaches a global SIGSEGV handler which calls @panic("segmentation fault"); +pub fn attachSegfaultHandler() void { + if (!have_segfault_handling_support) { + @compileError("segfault handler not supported for this target"); + } + var act = os.Sigaction{ + .sigaction = handleSegfault, + .mask = os.empty_sigset, + .flags = (os.SA_SIGINFO | os.SA_RESTART | os.SA_RESETHAND), + }; + + os.sigaction(os.SIGSEGV, &act, null); +} + +extern fn handleSegfault(sig: i32, info: *const os.siginfo_t, ctx_ptr: *const c_void) noreturn { + // Reset to the default handler so that if a segfault happens in this handler it will crash + // the process. Also when this handler returns, the original instruction will be repeated + // and the resulting segfault will crash the process rather than continually dump stack traces. + var act = os.Sigaction{ + .sigaction = os.SIG_DFL, + .mask = os.empty_sigset, + .flags = 0, + }; + os.sigaction(os.SIGSEGV, &act, null); + + const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr)); + const ip = @intCast(usize, ctx.mcontext.gregs[os.REG_RIP]); + const bp = @intCast(usize, ctx.mcontext.gregs[os.REG_RBP]); + const addr = @ptrToInt(info.fields.sigfault.addr); + std.debug.warn("Segmentation fault at address 0x{x}\n", addr); + dumpStackTraceFromBase(bp, ip); + + // We cannot allow the signal handler to return because when it runs the original instruction + // again, the memory may be mapped and undefined behavior would occur rather than repeating + // the segfault. So we simply abort here. + os.abort(); +} diff --git a/std/os.zig b/std/os.zig index a0f8d1f12b..a4e8ca3ad8 100644 --- a/std/os.zig +++ b/std/os.zig @@ -2529,6 +2529,16 @@ pub fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) SigaltstackError!void { } } +/// Examine and change a signal action. +pub fn sigaction(sig: u6, act: *const Sigaction, oact: ?*Sigaction) void { + switch (errno(system.sigaction(sig, act, oact))) { + 0 => return, + EFAULT => unreachable, + EINVAL => unreachable, + else => unreachable, + } +} + test "" { _ = @import("os/darwin.zig"); _ = @import("os/freebsd.zig"); diff --git a/std/os/bits/linux.zig b/std/os/bits/linux.zig index 0b355cd819..a11d843f88 100644 --- a/std/os/bits/linux.zig +++ b/std/os/bits/linux.zig @@ -12,6 +12,8 @@ pub usingnamespace switch (builtin.arch) { pub const pid_t = i32; pub const fd_t = i32; +pub const uid_t = i32; +pub const clock_t = isize; pub const PATH_MAX = 4096; pub const IOV_MAX = 1024; @@ -712,22 +714,23 @@ pub const all_mask = [_]u32{ 0xffffffff, 0xffffffff }; pub const app_mask = [_]u32{ 0xfffffffc, 0x7fffffff }; pub const k_sigaction = extern struct { - handler: extern fn (i32) void, + sigaction: ?extern fn (i32, *siginfo_t, *c_void) void, flags: usize, restorer: extern fn () void, mask: [2]u32, }; /// Renamed from `sigaction` to `Sigaction` to avoid conflict with the syscall. -pub const Sigaction = struct { - handler: extern fn (i32) void, +pub const Sigaction = extern struct { + sigaction: ?extern fn (i32, *siginfo_t, *c_void) void, mask: sigset_t, flags: u32, + restorer: ?extern fn () void = null, }; -pub const SIG_ERR = @intToPtr(extern fn (i32) void, maxInt(usize)); -pub const SIG_DFL = @intToPtr(extern fn (i32) void, 0); -pub const SIG_IGN = @intToPtr(extern fn (i32) void, 1); +pub const SIG_ERR = @intToPtr(extern fn (i32, *siginfo_t, *c_void) void, maxInt(usize)); +pub const SIG_DFL = @intToPtr(?extern fn (i32, *siginfo_t, *c_void) void, 0); +pub const SIG_IGN = @intToPtr(extern fn (i32, *siginfo_t, *c_void) void, 1); pub const empty_sigset = [_]usize{0} ** sigset_t.len; pub const in_port_t = u16; diff --git a/std/os/bits/linux/x86_64.zig b/std/os/bits/linux/x86_64.zig index 12ac6b8d7a..6012d877fd 100644 --- a/std/os/bits/linux/x86_64.zig +++ b/std/os/bits/linux/x86_64.zig @@ -1,5 +1,10 @@ // x86-64-specific declarations that are intended to be imported into the POSIX namespace. const std = @import("../../../std.zig"); +const pid_t = linux.pid_t; +const uid_t = linux.uid_t; +const clock_t = linux.clock_t; +const stack_t = linux.stack_t; +const sigset_t = linux.sigset_t; const linux = std.os.linux; const sockaddr = linux.sockaddr; @@ -407,6 +412,30 @@ pub const ARCH_SET_FS = 0x1002; pub const ARCH_GET_FS = 0x1003; pub const ARCH_GET_GS = 0x1004; +pub const REG_R8 = 0; +pub const REG_R9 = 1; +pub const REG_R10 = 2; +pub const REG_R11 = 3; +pub const REG_R12 = 4; +pub const REG_R13 = 5; +pub const REG_R14 = 6; +pub const REG_R15 = 7; +pub const REG_RDI = 8; +pub const REG_RSI = 9; +pub const REG_RBP = 10; +pub const REG_RBX = 11; +pub const REG_RDX = 12; +pub const REG_RAX = 13; +pub const REG_RCX = 14; +pub const REG_RSP = 15; +pub const REG_RIP = 16; +pub const REG_EFL = 17; +pub const REG_CSGSFS = 18; +pub const REG_ERR = 19; +pub const REG_TRAPNO = 20; +pub const REG_OLDMASK = 21; +pub const REG_CR2 = 22; + pub const msghdr = extern struct { msg_name: ?*sockaddr, msg_namelen: socklen_t, @@ -468,3 +497,129 @@ pub const timezone = extern struct { }; pub const Elf_Symndx = u32; + +pub const sigval = extern union { + int: i32, + ptr: *c_void, +}; + +pub const siginfo_t = extern struct { + signo: i32, + errno: i32, + code: i32, + fields: extern union { + pad: [128 - 2 * @sizeOf(c_int) - @sizeOf(c_long)]u8, + common: extern struct { + first: extern union { + piduid: extern struct { + pid: pid_t, + uid: uid_t, + }, + timer: extern struct { + timerid: i32, + overrun: i32, + }, + }, + second: extern union { + value: sigval, + sigchld: extern struct { + status: i32, + utime: clock_t, + stime: clock_t, + }, + }, + }, + sigfault: extern struct { + addr: *c_void, + addr_lsb: i16, + first: extern union { + addr_bnd: extern struct { + lower: *c_void, + upper: *c_void, + }, + pkey: u32, + }, + }, + sigpoll: extern struct { + band: isize, + fd: i32, + }, + sigsys: extern struct { + call_addr: *c_void, + syscall: i32, + arch: u32, + }, + }, +}; + +pub const greg_t = usize; +pub const gregset_t = [23]greg_t; +pub const fpstate = extern struct { + cwd: u16, + swd: u16, + ftw: u16, + fop: u16, + rip: usize, + rdp: usize, + mxcsr: u32, + mxcr_mask: u32, + st: [8]extern struct { + significand: [4]u16, + exponent: u16, + padding: [3]u16 = undefined, + }, + xmm: [16]extern struct { + element: [4]u32, + }, + padding: [24]u32 = undefined, +}; +pub const fpregset_t = *fpstate; +pub const sigcontext = extern struct { + r8: usize, + r9: usize, + r10: usize, + r11: usize, + r12: usize, + r13: usize, + r14: usize, + r15: usize, + + rdi: usize, + rsi: usize, + rbp: usize, + rbx: usize, + rdx: usize, + rax: usize, + rcx: usize, + rsp: usize, + rip: usize, + eflags: usize, + + cs: u16, + gs: u16, + fs: u16, + pad0: u16 = undefined, + + err: usize, + trapno: usize, + oldmask: usize, + cr2: usize, + + fpstate: *fpstate, + reserved1: [8]usize = undefined, +}; + +pub const mcontext_t = extern struct { + gregs: gregset_t, + fpregs: fpregset_t, + reserved1: [8]usize = undefined, +}; + +pub const ucontext_t = extern struct { + flags: usize, + link: *ucontext_t, + stack: stack_t, + mcontext: mcontext_t, + sigmask: sigset_t, + fpregs_mem: [64]usize, +}; diff --git a/std/os/linux.zig b/std/os/linux.zig index 74e1c48444..dd65fb9d83 100644 --- a/std/os/linux.zig +++ b/std/os/linux.zig @@ -542,7 +542,7 @@ pub fn sigaction(sig: u6, noalias act: *const Sigaction, noalias oact: ?*Sigacti assert(sig != SIGKILL); assert(sig != SIGSTOP); var ksa = k_sigaction{ - .handler = act.handler, + .sigaction = act.sigaction, .flags = act.flags | SA_RESTORER, .mask = undefined, .restorer = @ptrCast(extern fn () void, restore_rt), @@ -555,7 +555,7 @@ pub fn sigaction(sig: u6, noalias act: *const Sigaction, noalias oact: ?*Sigacti return result; } if (oact) |old| { - old.handler = ksa_old.handler; + old.sigaction = ksa_old.sigaction; old.flags = @truncate(u32, ksa_old.flags); @memcpy(@ptrCast([*]u8, &old.mask), @ptrCast([*]const u8, &ksa_old.mask), @sizeOf(@typeOf(ksa_old.mask))); } diff --git a/std/special/start.zig b/std/special/start.zig index 45348feab0..f2572def05 100644 --- a/std/special/start.zig +++ b/std/special/start.zig @@ -99,6 +99,15 @@ fn posixCallMainAndExit() noreturn { inline fn callMainWithArgs(argc: usize, argv: [*][*]u8, envp: [][*]u8) u8 { std.os.argv = argv[0..argc]; std.os.environ = envp; + + const enable_segfault_handler: bool = if (@hasDecl(root, "enable_segfault_handler")) + root.enable_segfault_handler + else + std.debug.runtime_safety and std.debug.have_segfault_handling_support; + if (enable_segfault_handler) { + std.debug.attachSegfaultHandler(); + } + return callMain(); }