stack traces on segfault by default for linux-x86_64

closes #2355
This commit is contained in:
Andrew Kelley 2019-07-02 13:27:40 -04:00
parent 0dd2e93e4c
commit 1a1598c58c
No known key found for this signature in database
GPG Key ID: 7C5F548F728501A9
6 changed files with 252 additions and 8 deletions

View File

@ -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();
}

View File

@ -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");

View File

@ -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;

View File

@ -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,
};

View File

@ -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)));
}

View File

@ -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();
}