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...@@ -334,6 +334,7 @@ pub fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize
334 resetSegfaultHandler();334 resetSegfaultHandler();
335 }335 }
336336
337 // Note there is similar logic in handleSegfaultPosix and handleSegfaultWindowsExtra.
337 nosuspend switch (panic_stage) {338 nosuspend switch (panic_stage) {
338 0 => {339 0 => {
339 panic_stage = 1;340 panic_stage = 1;
...@@ -359,16 +360,7 @@ pub fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize...@@ -359,16 +360,7 @@ pub fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize
359 dumpCurrentStackTrace(first_trace_addr);360 dumpCurrentStackTrace(first_trace_addr);
360 }361 }
361362
362 if (panicking.fetchSub(1, .SeqCst) != 1) {363 waitForOtherThreadToFinishPanicking();
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 }
372 },364 },
373 1 => {365 1 => {
374 panic_stage = 2;366 panic_stage = 2;
...@@ -387,6 +379,20 @@ pub fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize...@@ -387,6 +379,20 @@ pub fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize
387 os.abort();379 os.abort();
388}380}
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
390pub fn writeStackTrace(396pub fn writeStackTrace(
391 stack_trace: std.builtin.StackTrace,397 stack_trace: std.builtin.StackTrace,
392 out_stream: anytype,398 out_stream: anytype,
...@@ -1971,17 +1977,41 @@ fn handleSegfaultPosix(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const any...@@ -1971,17 +1977,41 @@ fn handleSegfaultPosix(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const any
1971 else => unreachable,1977 else => unreachable,
1972 };1978 };
19731979
1974 // Don't use std.debug.print() as stderr_mutex may still be locked.1980 nosuspend switch (panic_stage) {
1975 nosuspend {1981 0 => {
1976 const stderr = io.getStdErr().writer();1982 panic_stage = 1;
1977 _ = switch (sig) {1983 _ = panicking.fetchAdd(1, .SeqCst);
1978 os.SIG.SEGV => stderr.print("Segmentation fault at address 0x{x}\n", .{addr}),1984
1979 os.SIG.ILL => stderr.print("Illegal instruction at address 0x{x}\n", .{addr}),1985 {
1980 os.SIG.BUS => stderr.print("Bus error at address 0x{x}\n", .{addr}),1986 panic_mutex.lock();
1981 os.SIG.FPE => stderr.print("Arithmetic exception at address 0x{x}\n", .{addr}),1987 defer panic_mutex.unlock();
1982 else => unreachable,1988
1983 } catch os.abort();1989 dumpSegfaultInfoPosix(sig, addr, ctx_ptr);
1984 }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
1986 switch (native_arch) {2016 switch (native_arch) {
1987 .x86 => {2017 .x86 => {
...@@ -2033,11 +2063,6 @@ fn handleSegfaultPosix(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const any...@@ -2033,11 +2063,6 @@ fn handleSegfaultPosix(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const any
2033 },2063 },
2034 else => {},2064 else => {},
2035 }2065 }
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();
2041}2066}
20422067
2043fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(windows.WINAPI) c_long {2068fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(windows.WINAPI) c_long {
...@@ -2050,27 +2075,36 @@ fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(windows.WIN...@@ -2050,27 +2075,36 @@ fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(windows.WIN
2050 }2075 }
2051}2076}
20522077
2053// zig won't let me use an anon enum here https://github.com/ziglang/zig/issues/37072078fn handleSegfaultWindowsExtra(
2054fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, comptime msg: u8, comptime format: ?[]const u8) noreturn {2079 info: *windows.EXCEPTION_POINTERS,
2080 msg: u8,
2081 label: ?[]const u8,
2082) noreturn {
2055 const exception_address = @ptrToInt(info.ExceptionRecord.ExceptionAddress);2083 const exception_address = @ptrToInt(info.ExceptionRecord.ExceptionAddress);
2056 if (@hasDecl(windows, "CONTEXT")) {2084 if (@hasDecl(windows, "CONTEXT")) {
2057 const regs = info.ContextRecord.getRegs();2085 nosuspend switch (panic_stage) {
2058 // Don't use std.debug.print() as stderr_mutex may still be locked.2086 0 => {
2059 nosuspend {2087 panic_stage = 1;
2060 const stderr = io.getStdErr().writer();2088 _ = panicking.fetchAdd(1, .SeqCst);
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 }
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 };
2070 os.abort();2104 os.abort();
2071 } else {2105 } else {
2072 switch (msg) {2106 switch (msg) {
2073 0 => panicImpl(null, exception_address, format.?),2107 0 => panicImpl(null, exception_address, "{s}", label.?),
2074 1 => {2108 1 => {
2075 const format_item = "Segmentation fault at address 0x{x}";2109 const format_item = "Segmentation fault at address 0x{x}";
2076 var buf: [format_item.len + 64]u8 = undefined; // 64 is arbitrary, but sufficiently large2110 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...@@ -2083,6 +2117,19 @@ fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, comptime msg: u
2083 }2117 }
2084}2118}
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
2086pub fn dumpStackPointerAddr(prefix: []const u8) void {2133pub fn dumpStackPointerAddr(prefix: []const u8) void {
2087 const sp = asm (""2134 const sp = asm (""
2088 : [argc] "={rsp}" (-> usize),2135 : [argc] "={rsp}" (-> usize),