authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-04-08 08:36:31-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-04-09 14:09:52-04:00
log95ac94b7ac02dfd3c19dc1918b3df009067f7a19
treec60522e17f305aebf42160537f5fe2553eae27c2
parent2ee328995a70c5c446f24c5593e0fad760e6d839

std.debug: fix segfault/panic race condition

closes #7859 closes #12207

1 files changed, 88 insertions(+), 41 deletions(-)

lib/std/debug.zig+88-41
......@@ -334,6 +334,7 @@ pub fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize
334334 resetSegfaultHandler();
335335 }
336336
337 // Note there is similar logic in handleSegfaultPosix and handleSegfaultWindowsExtra.
337338 nosuspend switch (panic_stage) {
338339 0 => {
339340 panic_stage = 1;
......@@ -359,16 +360,7 @@ pub fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize
359360 dumpCurrentStackTrace(first_trace_addr);
360361 }
361362
362 if (panicking.fetchSub(1, .SeqCst) != 1) {
363 // Another thread is panicking, wait for the last one to finish
364 // and call abort()
365 if (builtin.single_threaded) unreachable;
366
367 // Sleep forever without hammering the CPU
368 var futex = std.atomic.Atomic(u32).init(0);
369 while (true) std.Thread.Futex.wait(&futex, 0);
370 unreachable;
371 }
363 waitForOtherThreadToFinishPanicking();
372364 },
373365 1 => {
374366 panic_stage = 2;
......@@ -387,6 +379,20 @@ pub fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize
387379 os.abort();
388380}
389381
382/// Must be called only after adding 1 to `panicking`. There are three callsites.
383fn waitForOtherThreadToFinishPanicking() void {
384 if (panicking.fetchSub(1, .SeqCst) != 1) {
385 // Another thread is panicking, wait for the last one to finish
386 // and call abort()
387 if (builtin.single_threaded) unreachable;
388
389 // Sleep forever without hammering the CPU
390 var futex = std.atomic.Atomic(u32).init(0);
391 while (true) std.Thread.Futex.wait(&futex, 0);
392 unreachable;
393 }
394}
395
390396pub fn writeStackTrace(
391397 stack_trace: std.builtin.StackTrace,
392398 out_stream: anytype,
......@@ -1971,17 +1977,41 @@ fn handleSegfaultPosix(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const any
19711977 else => unreachable,
19721978 };
19731979
1974 // Don't use std.debug.print() as stderr_mutex may still be locked.
1975 nosuspend {
1976 const stderr = io.getStdErr().writer();
1977 _ = switch (sig) {
1978 os.SIG.SEGV => stderr.print("Segmentation fault at address 0x{x}\n", .{addr}),
1979 os.SIG.ILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}),
1980 os.SIG.BUS => stderr.print("Bus error at address 0x{x}\n", .{addr}),
1981 os.SIG.FPE => stderr.print("Arithmetic exception at address 0x{x}\n", .{addr}),
1982 else => unreachable,
1983 } catch os.abort();
1984 }
1980 nosuspend switch (panic_stage) {
1981 0 => {
1982 panic_stage = 1;
1983 _ = panicking.fetchAdd(1, .SeqCst);
1984
1985 {
1986 panic_mutex.lock();
1987 defer panic_mutex.unlock();
1988
1989 dumpSegfaultInfoPosix(sig, addr, ctx_ptr);
1990 }
1991
1992 waitForOtherThreadToFinishPanicking();
1993 },
1994 else => {
1995 // panic mutex already locked
1996 dumpSegfaultInfoPosix(sig, addr, ctx_ptr);
1997 },
1998 };
1999
2000 // We cannot allow the signal handler to return because when it runs the original instruction
2001 // again, the memory may be mapped and undefined behavior would occur rather than repeating
2002 // the segfault. So we simply abort here.
2003 os.abort();
2004}
2005
2006fn dumpSegfaultInfoPosix(sig: i32, addr: usize, ctx_ptr: ?*const anyopaque) void {
2007 const stderr = io.getStdErr().writer();
2008 _ = switch (sig) {
2009 os.SIG.SEGV => stderr.print("Segmentation fault at address 0x{x}\n", .{addr}),
2010 os.SIG.ILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}),
2011 os.SIG.BUS => stderr.print("Bus error at address 0x{x}\n", .{addr}),
2012 os.SIG.FPE => stderr.print("Arithmetic exception at address 0x{x}\n", .{addr}),
2013 else => unreachable,
2014 } catch os.abort();
19852015
19862016 switch (native_arch) {
19872017 .x86 => {
......@@ -2033,11 +2063,6 @@ fn handleSegfaultPosix(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const any
20332063 },
20342064 else => {},
20352065 }
2036
2037 // We cannot allow the signal handler to return because when it runs the original instruction
2038 // again, the memory may be mapped and undefined behavior would occur rather than repeating
2039 // the segfault. So we simply abort here.
2040 os.abort();
20412066}
20422067
20432068fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(windows.WINAPI) c_long {
......@@ -2050,27 +2075,36 @@ fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(windows.WIN
20502075 }
20512076}
20522077
2053// zig won't let me use an anon enum here https://github.com/ziglang/zig/issues/3707
2054fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, comptime msg: u8, comptime format: ?[]const u8) noreturn {
2078fn handleSegfaultWindowsExtra(
2079 info: *windows.EXCEPTION_POINTERS,
2080 msg: u8,
2081 label: ?[]const u8,
2082) noreturn {
20552083 const exception_address = @ptrToInt(info.ExceptionRecord.ExceptionAddress);
20562084 if (@hasDecl(windows, "CONTEXT")) {
2057 const regs = info.ContextRecord.getRegs();
2058 // Don't use std.debug.print() as stderr_mutex may still be locked.
2059 nosuspend {
2060 const stderr = io.getStdErr().writer();
2061 _ = switch (msg) {
2062 0 => stderr.print("{s}\n", .{format.?}),
2063 1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}),
2064 2 => stderr.print("Illegal instruction at address 0x{x}\n", .{regs.ip}),
2065 else => unreachable,
2066 } catch os.abort();
2067 }
2085 nosuspend switch (panic_stage) {
2086 0 => {
2087 panic_stage = 1;
2088 _ = panicking.fetchAdd(1, .SeqCst);
20682089
2069 dumpStackTraceFromBase(regs.bp, regs.ip);
2090 {
2091 panic_mutex.lock();
2092 defer panic_mutex.unlock();
2093
2094 dumpSegfaultInfoWindows(info, msg, label);
2095 }
2096
2097 waitForOtherThreadToFinishPanicking();
2098 },
2099 else => {
2100 // panic mutex already locked
2101 dumpSegfaultInfoWindows(info, msg, label);
2102 },
2103 };
20702104 os.abort();
20712105 } else {
20722106 switch (msg) {
2073 0 => panicImpl(null, exception_address, format.?),
2107 0 => panicImpl(null, exception_address, "{s}", label.?),
20742108 1 => {
20752109 const format_item = "Segmentation fault at address 0x{x}";
20762110 var buf: [format_item.len + 64]u8 = undefined; // 64 is arbitrary, but sufficiently large
......@@ -2083,6 +2117,19 @@ fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, comptime msg: u
20832117 }
20842118}
20852119
2120fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) void {
2121 const regs = info.ContextRecord.getRegs();
2122 const stderr = io.getStdErr().writer();
2123 _ = switch (msg) {
2124 0 => stderr.print("{s}\n", .{label.?}),
2125 1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}),
2126 2 => stderr.print("Illegal instruction at address 0x{x}\n", .{regs.ip}),
2127 else => unreachable,
2128 } catch os.abort();
2129
2130 dumpStackTraceFromBase(regs.bp, regs.ip);
2131}
2132
20862133pub fn dumpStackPointerAddr(prefix: []const u8) void {
20872134 const sp = asm (""
20882135 : [argc] "={rsp}" (-> usize),