Commit 1a1598c58c

Andrew Kelley <andrew@ziglang.org>
2019-07-02 19:27:40
stack traces on segfault by default for linux-x86_64
closes #2355
1 parent 0dd2e93
Changed files (6)
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,
+};
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;
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)));
     }
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();
 }
 
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();
+}
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");