Skip to content

Commit 1a1598c

Browse files
committed
stack traces on segfault by default for linux-x86_64
closes #2355
1 parent 0dd2e93 commit 1a1598c

File tree

6 files changed

+252
-8
lines changed

6 files changed

+252
-8
lines changed

std/debug.zig

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,32 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
9999
};
100100
}
101101

102+
/// Tries to print the stack trace starting from the supplied base pointer to stderr,
103+
/// unbuffered, and ignores any error returned.
104+
/// TODO multithreaded awareness
105+
pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void {
106+
const stderr = getStderrStream() catch return;
107+
if (builtin.strip_debug_info) {
108+
stderr.print("Unable to dump stack trace: debug info stripped\n") catch return;
109+
return;
110+
}
111+
const debug_info = getSelfDebugInfo() catch |err| {
112+
stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", @errorName(err)) catch return;
113+
return;
114+
};
115+
const tty_color = wantTtyColor();
116+
printSourceAtAddress(debug_info, stderr, ip, tty_color) catch return;
117+
const first_return_address = @intToPtr(*const usize, bp + @sizeOf(usize)).*;
118+
printSourceAtAddress(debug_info, stderr, first_return_address - 1, tty_color) catch return;
119+
var it = StackIterator{
120+
.first_addr = null,
121+
.fp = bp,
122+
};
123+
while (it.next()) |return_address| {
124+
printSourceAtAddress(debug_info, stderr, return_address - 1, tty_color) catch return;
125+
}
126+
}
127+
102128
/// Returns a slice with the same pointer as addresses, with a potentially smaller len.
103129
/// On Windows, when first_address is not null, we ask for at least 32 stack frames,
104130
/// and then try to find the first address. If addresses.len is more than 32, we
@@ -2291,3 +2317,44 @@ fn getDebugInfoAllocator() *mem.Allocator {
22912317
debug_info_allocator = &debug_info_arena_allocator.allocator;
22922318
return &debug_info_arena_allocator.allocator;
22932319
}
2320+
2321+
/// Whether or not the current target can print useful debug information when a segfault occurs.
2322+
pub const have_segfault_handling_support = builtin.arch == .x86_64 and builtin.os == .linux;
2323+
2324+
/// Attaches a global SIGSEGV handler which calls @panic("segmentation fault");
2325+
pub fn attachSegfaultHandler() void {
2326+
if (!have_segfault_handling_support) {
2327+
@compileError("segfault handler not supported for this target");
2328+
}
2329+
var act = os.Sigaction{
2330+
.sigaction = handleSegfault,
2331+
.mask = os.empty_sigset,
2332+
.flags = (os.SA_SIGINFO | os.SA_RESTART | os.SA_RESETHAND),
2333+
};
2334+
2335+
os.sigaction(os.SIGSEGV, &act, null);
2336+
}
2337+
2338+
extern fn handleSegfault(sig: i32, info: *const os.siginfo_t, ctx_ptr: *const c_void) noreturn {
2339+
// Reset to the default handler so that if a segfault happens in this handler it will crash
2340+
// the process. Also when this handler returns, the original instruction will be repeated
2341+
// and the resulting segfault will crash the process rather than continually dump stack traces.
2342+
var act = os.Sigaction{
2343+
.sigaction = os.SIG_DFL,
2344+
.mask = os.empty_sigset,
2345+
.flags = 0,
2346+
};
2347+
os.sigaction(os.SIGSEGV, &act, null);
2348+
2349+
const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr));
2350+
const ip = @intCast(usize, ctx.mcontext.gregs[os.REG_RIP]);
2351+
const bp = @intCast(usize, ctx.mcontext.gregs[os.REG_RBP]);
2352+
const addr = @ptrToInt(info.fields.sigfault.addr);
2353+
std.debug.warn("Segmentation fault at address 0x{x}\n", addr);
2354+
dumpStackTraceFromBase(bp, ip);
2355+
2356+
// We cannot allow the signal handler to return because when it runs the original instruction
2357+
// again, the memory may be mapped and undefined behavior would occur rather than repeating
2358+
// the segfault. So we simply abort here.
2359+
os.abort();
2360+
}

std/os.zig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2529,6 +2529,16 @@ pub fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) SigaltstackError!void {
25292529
}
25302530
}
25312531

2532+
/// Examine and change a signal action.
2533+
pub fn sigaction(sig: u6, act: *const Sigaction, oact: ?*Sigaction) void {
2534+
switch (errno(system.sigaction(sig, act, oact))) {
2535+
0 => return,
2536+
EFAULT => unreachable,
2537+
EINVAL => unreachable,
2538+
else => unreachable,
2539+
}
2540+
}
2541+
25322542
test "" {
25332543
_ = @import("os/darwin.zig");
25342544
_ = @import("os/freebsd.zig");

std/os/bits/linux.zig

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ pub usingnamespace switch (builtin.arch) {
1212

1313
pub const pid_t = i32;
1414
pub const fd_t = i32;
15+
pub const uid_t = i32;
16+
pub const clock_t = isize;
1517

1618
pub const PATH_MAX = 4096;
1719
pub const IOV_MAX = 1024;
@@ -712,22 +714,23 @@ pub const all_mask = [_]u32{ 0xffffffff, 0xffffffff };
712714
pub const app_mask = [_]u32{ 0xfffffffc, 0x7fffffff };
713715

714716
pub const k_sigaction = extern struct {
715-
handler: extern fn (i32) void,
717+
sigaction: ?extern fn (i32, *siginfo_t, *c_void) void,
716718
flags: usize,
717719
restorer: extern fn () void,
718720
mask: [2]u32,
719721
};
720722

721723
/// Renamed from `sigaction` to `Sigaction` to avoid conflict with the syscall.
722-
pub const Sigaction = struct {
723-
handler: extern fn (i32) void,
724+
pub const Sigaction = extern struct {
725+
sigaction: ?extern fn (i32, *siginfo_t, *c_void) void,
724726
mask: sigset_t,
725727
flags: u32,
728+
restorer: ?extern fn () void = null,
726729
};
727730

728-
pub const SIG_ERR = @intToPtr(extern fn (i32) void, maxInt(usize));
729-
pub const SIG_DFL = @intToPtr(extern fn (i32) void, 0);
730-
pub const SIG_IGN = @intToPtr(extern fn (i32) void, 1);
731+
pub const SIG_ERR = @intToPtr(extern fn (i32, *siginfo_t, *c_void) void, maxInt(usize));
732+
pub const SIG_DFL = @intToPtr(?extern fn (i32, *siginfo_t, *c_void) void, 0);
733+
pub const SIG_IGN = @intToPtr(extern fn (i32, *siginfo_t, *c_void) void, 1);
731734
pub const empty_sigset = [_]usize{0} ** sigset_t.len;
732735

733736
pub const in_port_t = u16;

std/os/bits/linux/x86_64.zig

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
// x86-64-specific declarations that are intended to be imported into the POSIX namespace.
22
const std = @import("../../../std.zig");
3+
const pid_t = linux.pid_t;
4+
const uid_t = linux.uid_t;
5+
const clock_t = linux.clock_t;
6+
const stack_t = linux.stack_t;
7+
const sigset_t = linux.sigset_t;
38

49
const linux = std.os.linux;
510
const sockaddr = linux.sockaddr;
@@ -407,6 +412,30 @@ pub const ARCH_SET_FS = 0x1002;
407412
pub const ARCH_GET_FS = 0x1003;
408413
pub const ARCH_GET_GS = 0x1004;
409414

415+
pub const REG_R8 = 0;
416+
pub const REG_R9 = 1;
417+
pub const REG_R10 = 2;
418+
pub const REG_R11 = 3;
419+
pub const REG_R12 = 4;
420+
pub const REG_R13 = 5;
421+
pub const REG_R14 = 6;
422+
pub const REG_R15 = 7;
423+
pub const REG_RDI = 8;
424+
pub const REG_RSI = 9;
425+
pub const REG_RBP = 10;
426+
pub const REG_RBX = 11;
427+
pub const REG_RDX = 12;
428+
pub const REG_RAX = 13;
429+
pub const REG_RCX = 14;
430+
pub const REG_RSP = 15;
431+
pub const REG_RIP = 16;
432+
pub const REG_EFL = 17;
433+
pub const REG_CSGSFS = 18;
434+
pub const REG_ERR = 19;
435+
pub const REG_TRAPNO = 20;
436+
pub const REG_OLDMASK = 21;
437+
pub const REG_CR2 = 22;
438+
410439
pub const msghdr = extern struct {
411440
msg_name: ?*sockaddr,
412441
msg_namelen: socklen_t,
@@ -468,3 +497,129 @@ pub const timezone = extern struct {
468497
};
469498

470499
pub const Elf_Symndx = u32;
500+
501+
pub const sigval = extern union {
502+
int: i32,
503+
ptr: *c_void,
504+
};
505+
506+
pub const siginfo_t = extern struct {
507+
signo: i32,
508+
errno: i32,
509+
code: i32,
510+
fields: extern union {
511+
pad: [128 - 2 * @sizeOf(c_int) - @sizeOf(c_long)]u8,
512+
common: extern struct {
513+
first: extern union {
514+
piduid: extern struct {
515+
pid: pid_t,
516+
uid: uid_t,
517+
},
518+
timer: extern struct {
519+
timerid: i32,
520+
overrun: i32,
521+
},
522+
},
523+
second: extern union {
524+
value: sigval,
525+
sigchld: extern struct {
526+
status: i32,
527+
utime: clock_t,
528+
stime: clock_t,
529+
},
530+
},
531+
},
532+
sigfault: extern struct {
533+
addr: *c_void,
534+
addr_lsb: i16,
535+
first: extern union {
536+
addr_bnd: extern struct {
537+
lower: *c_void,
538+
upper: *c_void,
539+
},
540+
pkey: u32,
541+
},
542+
},
543+
sigpoll: extern struct {
544+
band: isize,
545+
fd: i32,
546+
},
547+
sigsys: extern struct {
548+
call_addr: *c_void,
549+
syscall: i32,
550+
arch: u32,
551+
},
552+
},
553+
};
554+
555+
pub const greg_t = usize;
556+
pub const gregset_t = [23]greg_t;
557+
pub const fpstate = extern struct {
558+
cwd: u16,
559+
swd: u16,
560+
ftw: u16,
561+
fop: u16,
562+
rip: usize,
563+
rdp: usize,
564+
mxcsr: u32,
565+
mxcr_mask: u32,
566+
st: [8]extern struct {
567+
significand: [4]u16,
568+
exponent: u16,
569+
padding: [3]u16 = undefined,
570+
},
571+
xmm: [16]extern struct {
572+
element: [4]u32,
573+
},
574+
padding: [24]u32 = undefined,
575+
};
576+
pub const fpregset_t = *fpstate;
577+
pub const sigcontext = extern struct {
578+
r8: usize,
579+
r9: usize,
580+
r10: usize,
581+
r11: usize,
582+
r12: usize,
583+
r13: usize,
584+
r14: usize,
585+
r15: usize,
586+
587+
rdi: usize,
588+
rsi: usize,
589+
rbp: usize,
590+
rbx: usize,
591+
rdx: usize,
592+
rax: usize,
593+
rcx: usize,
594+
rsp: usize,
595+
rip: usize,
596+
eflags: usize,
597+
598+
cs: u16,
599+
gs: u16,
600+
fs: u16,
601+
pad0: u16 = undefined,
602+
603+
err: usize,
604+
trapno: usize,
605+
oldmask: usize,
606+
cr2: usize,
607+
608+
fpstate: *fpstate,
609+
reserved1: [8]usize = undefined,
610+
};
611+
612+
pub const mcontext_t = extern struct {
613+
gregs: gregset_t,
614+
fpregs: fpregset_t,
615+
reserved1: [8]usize = undefined,
616+
};
617+
618+
pub const ucontext_t = extern struct {
619+
flags: usize,
620+
link: *ucontext_t,
621+
stack: stack_t,
622+
mcontext: mcontext_t,
623+
sigmask: sigset_t,
624+
fpregs_mem: [64]usize,
625+
};

std/os/linux.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ pub fn sigaction(sig: u6, noalias act: *const Sigaction, noalias oact: ?*Sigacti
542542
assert(sig != SIGKILL);
543543
assert(sig != SIGSTOP);
544544
var ksa = k_sigaction{
545-
.handler = act.handler,
545+
.sigaction = act.sigaction,
546546
.flags = act.flags | SA_RESTORER,
547547
.mask = undefined,
548548
.restorer = @ptrCast(extern fn () void, restore_rt),
@@ -555,7 +555,7 @@ pub fn sigaction(sig: u6, noalias act: *const Sigaction, noalias oact: ?*Sigacti
555555
return result;
556556
}
557557
if (oact) |old| {
558-
old.handler = ksa_old.handler;
558+
old.sigaction = ksa_old.sigaction;
559559
old.flags = @truncate(u32, ksa_old.flags);
560560
@memcpy(@ptrCast([*]u8, &old.mask), @ptrCast([*]const u8, &ksa_old.mask), @sizeOf(@typeOf(ksa_old.mask)));
561561
}

std/special/start.zig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@ fn posixCallMainAndExit() noreturn {
9999
inline fn callMainWithArgs(argc: usize, argv: [*][*]u8, envp: [][*]u8) u8 {
100100
std.os.argv = argv[0..argc];
101101
std.os.environ = envp;
102+
103+
const enable_segfault_handler: bool = if (@hasDecl(root, "enable_segfault_handler"))
104+
root.enable_segfault_handler
105+
else
106+
std.debug.runtime_safety and std.debug.have_segfault_handling_support;
107+
if (enable_segfault_handler) {
108+
std.debug.attachSegfaultHandler();
109+
}
110+
102111
return callMain();
103112
}
104113

0 commit comments

Comments
 (0)