| author | |
| committer | |
| log | 85cac9e5b6c3039537e87247f8640f7182df8540 |
| tree | 378eaba88621711544e645f6c5287c788f3ce222 |
| parent | be84d7cb9bdbf8a1a4212f10c1dfdc437af0532c |
This allows stack overflows to print stack traces. The size of the
sigaltstack (and whether it is actually set) can be configured by
setting `std.Options.signal_stack_size`.
The default value for the signal stack size was chosen experimentally by
doubling the value required to get stack traces on stack overflow with
the self-hosted x86_64 backend. While some targets may typically use
more stack space than x86_64-linux, the self-hosted x86_64 backend is
quite wasteful with stack at the moment, making it a fair benchmark.
Executables produced by the LLVM backend should have lower stack usage.7 files changed, 51 insertions(+), 7 deletions(-)
lib/std/Thread.zig+28-1| ... | ... | @@ -540,13 +540,16 @@ const Completion = std.atomic.Value(enum(if (builtin.zig_backend == .stage2_risc |
| 540 | 540 | completed, |
| 541 | 541 | }); |
| 542 | 542 | |
| 543 | /// Used by the Thread implementations to call the spawned function with the arguments. | |
| 543 | /// Performs implementation-agnostic thread setup (`maybeAttachSignalStack`), then calls the given | |
| 544 | /// thread entry point `f` with `args` and handles the result. | |
| 544 | 545 | fn callFn(comptime f: anytype, args: anytype) switch (Impl) { |
| 545 | 546 | WindowsThreadImpl => windows.DWORD, |
| 546 | 547 | LinuxThreadImpl => u8, |
| 547 | 548 | PosixThreadImpl => ?*anyopaque, |
| 548 | 549 | else => unreachable, |
| 549 | 550 | } { |
| 551 | maybeAttachSignalStack(); | |
| 552 | ||
| 550 | 553 | const default_value = if (Impl == PosixThreadImpl) null else 0; |
| 551 | 554 | const bad_fn_ret = "expected return type of startFn to be 'u8', 'noreturn', '!noreturn', 'void', or '!void'"; |
| 552 | 555 | |
| ... | ... | @@ -1917,3 +1920,27 @@ test "ResetEvent broadcast" { |
| 1917 | 1920 | |
| 1918 | 1921 | ctx.run(); |
| 1919 | 1922 | } |
| 1923 | ||
| 1924 | /// Configures the per-thread alternative signal stack requested by `std.options.signal_stack_size`. | |
| 1925 | pub fn maybeAttachSignalStack() void { | |
| 1926 | const size = std.options.signal_stack_size orelse return; | |
| 1927 | switch (builtin.target.os.tag) { | |
| 1928 | // TODO: Windows vectored exception handlers always run on the main stack, but we could use | |
| 1929 | // some target-specific inline assembly to swap the stack pointer. | |
| 1930 | .windows => return, | |
| 1931 | .wasi => return, | |
| 1932 | else => {}, | |
| 1933 | } | |
| 1934 | const global = struct { | |
| 1935 | threadlocal var signal_stack: [size]u8 = undefined; | |
| 1936 | }; | |
| 1937 | std.posix.sigaltstack(&.{ | |
| 1938 | .sp = &global.signal_stack, | |
| 1939 | .flags = 0, | |
| 1940 | .size = size, | |
| 1941 | }, null) catch |err| switch (err) { | |
| 1942 | error.SizeTooSmall => unreachable, // `std.options.signal_stack_size` must be sufficient for the target | |
| 1943 | error.PermissionDenied => unreachable, // called `maybeAttachSignalStack` from a signal handler | |
| 1944 | error.Unexpected => @panic("unexpected error attaching signal stack"), | |
| 1945 | }; | |
| 1946 | } |
lib/std/c.zig+2-2| ... | ... | @@ -11490,7 +11490,7 @@ const private = struct { |
| 11490 | 11490 | extern "c" fn sigprocmask(how: c_int, noalias set: ?*const sigset_t, noalias oset: ?*sigset_t) c_int; |
| 11491 | 11491 | extern "c" fn socket(domain: c_uint, sock_type: c_uint, protocol: c_uint) c_int; |
| 11492 | 11492 | extern "c" fn socketpair(domain: c_uint, sock_type: c_uint, protocol: c_uint, sv: *[2]fd_t) c_int; |
| 11493 | extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int; | |
| 11493 | extern "c" fn sigaltstack(ss: ?*const stack_t, old_ss: ?*stack_t) c_int; | |
| 11494 | 11494 | extern "c" fn sysconf(sc: c_int) c_long; |
| 11495 | 11495 | extern "c" fn shm_open(name: [*:0]const u8, flag: c_int, mode: mode_t) c_int; |
| 11496 | 11496 | extern "c" fn wait4(pid: pid_t, status: ?*c_int, options: c_int, ru: ?*rusage) pid_t; |
| ... | ... | @@ -11545,7 +11545,7 @@ const private = struct { |
| 11545 | 11545 | extern "c" fn __socket30(domain: c_uint, sock_type: c_uint, protocol: c_uint) c_int; |
| 11546 | 11546 | extern "c" fn __stat50(path: [*:0]const u8, buf: *Stat) c_int; |
| 11547 | 11547 | extern "c" fn __getdents30(fd: c_int, buf_ptr: [*]u8, nbytes: usize) c_int; |
| 11548 | extern "c" fn __sigaltstack14(ss: ?*stack_t, old_ss: ?*stack_t) c_int; | |
| 11548 | extern "c" fn __sigaltstack14(ss: ?*const stack_t, old_ss: ?*stack_t) c_int; | |
| 11549 | 11549 | extern "c" fn __wait450(pid: pid_t, status: ?*c_int, options: c_int, ru: ?*rusage) pid_t; |
| 11550 | 11550 | |
| 11551 | 11551 | extern "c" fn __libc_current_sigrtmin() c_int; |
lib/std/debug.zig+5-2| ... | ... | @@ -1411,6 +1411,9 @@ pub fn updateSegfaultHandler(act: ?*const posix.Sigaction) void { |
| 1411 | 1411 | /// trace if possible. This implementation does not just call the panic handler, because unwinding |
| 1412 | 1412 | /// the stack (for a stack trace) when a signal is received requires special target-specific logic. |
| 1413 | 1413 | /// |
| 1414 | /// On POSIX targets, the signal handler is configured to use the alternative signal stack. Such a | |
| 1415 | /// stack is configured by the Zig Standard Library if `std.options.signal_stack_size` is set. | |
| 1416 | /// | |
| 1414 | 1417 | /// The signals for which a handler is installed are: |
| 1415 | 1418 | /// * SIGSEGV (segmentation fault) |
| 1416 | 1419 | /// * SIGILL (illegal instruction) |
| ... | ... | @@ -1424,10 +1427,10 @@ pub fn attachSegfaultHandler() void { |
| 1424 | 1427 | windows_segfault_handle = windows.ntdll.RtlAddVectoredExceptionHandler(0, handleSegfaultWindows); |
| 1425 | 1428 | return; |
| 1426 | 1429 | } |
| 1427 | const act = posix.Sigaction{ | |
| 1430 | const act: posix.Sigaction = .{ | |
| 1428 | 1431 | .handler = .{ .sigaction = handleSegfaultPosix }, |
| 1429 | 1432 | .mask = posix.sigemptyset(), |
| 1430 | .flags = (posix.SA.SIGINFO | posix.SA.RESTART | posix.SA.RESETHAND), | |
| 1433 | .flags = (posix.SA.SIGINFO | posix.SA.RESTART | posix.SA.RESETHAND | posix.SA.ONSTACK), | |
| 1431 | 1434 | }; |
| 1432 | 1435 | updateSegfaultHandler(&act); |
| 1433 | 1436 | } |
lib/std/os/linux.zig+1-1| ... | ... | @@ -2488,7 +2488,7 @@ pub fn capset(hdrp: *cap_user_header_t, datap: *const cap_user_data_t) usize { |
| 2488 | 2488 | return syscall2(.capset, @intFromPtr(hdrp), @intFromPtr(datap)); |
| 2489 | 2489 | } |
| 2490 | 2490 | |
| 2491 | pub fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) usize { | |
| 2491 | pub fn sigaltstack(ss: ?*const stack_t, old_ss: ?*stack_t) usize { | |
| 2492 | 2492 | return syscall2(.sigaltstack, @intFromPtr(ss), @intFromPtr(old_ss)); |
| 2493 | 2493 | } |
| 2494 | 2494 |
lib/std/posix.zig+1-1| ... | ... | @@ -1310,7 +1310,7 @@ pub const SigaltstackError = error{ |
| 1310 | 1310 | PermissionDenied, |
| 1311 | 1311 | } || UnexpectedError; |
| 1312 | 1312 | |
| 1313 | pub fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) SigaltstackError!void { | |
| 1313 | pub fn sigaltstack(ss: ?*const stack_t, old_ss: ?*stack_t) SigaltstackError!void { | |
| 1314 | 1314 | switch (errno(system.sigaltstack(ss, old_ss))) { |
| 1315 | 1315 | .SUCCESS => return, |
| 1316 | 1316 | .FAULT => unreachable, |
lib/std/start.zig+4| ... | ... | @@ -470,6 +470,7 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn { |
| 470 | 470 | _ = @import("os/windows/tls.zig"); |
| 471 | 471 | } |
| 472 | 472 | |
| 473 | std.Thread.maybeAttachSignalStack(); | |
| 473 | 474 | std.debug.maybeEnableSegfaultHandler(); |
| 474 | 475 | |
| 475 | 476 | const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine; |
| ... | ... | @@ -486,6 +487,7 @@ fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn { |
| 486 | 487 | _ = @import("os/windows/tls.zig"); |
| 487 | 488 | } |
| 488 | 489 | |
| 490 | std.Thread.maybeAttachSignalStack(); | |
| 489 | 491 | std.debug.maybeEnableSegfaultHandler(); |
| 490 | 492 | |
| 491 | 493 | const result: std.os.windows.INT = call_wWinMain(); |
| ... | ... | @@ -622,6 +624,7 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [:null]?[*:0]u8) |
| 622 | 624 | if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0]; |
| 623 | 625 | t.environ = .{ .process_environ = .{ .block = envp } }; |
| 624 | 626 | } |
| 627 | std.Thread.maybeAttachSignalStack(); | |
| 625 | 628 | std.debug.maybeEnableSegfaultHandler(); |
| 626 | 629 | return callMain(argv[0..argc], envp); |
| 627 | 630 | } |
| ... | ... | @@ -641,6 +644,7 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal |
| 641 | 644 | .windows => { |
| 642 | 645 | // On Windows, we ignore libc environment and argv and get those |
| 643 | 646 | // values in their intended encoding from the PEB instead. |
| 647 | std.Thread.maybeAttachSignalStack(); | |
| 644 | 648 | std.debug.maybeEnableSegfaultHandler(); |
| 645 | 649 | const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine; |
| 646 | 650 | const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)]; |
lib/std/std.zig+10| ... | ... | @@ -114,6 +114,16 @@ pub const options: Options = if (@hasDecl(root, "std_options")) root.std_options |
| 114 | 114 | pub const Options = struct { |
| 115 | 115 | enable_segfault_handler: bool = debug.default_enable_segfault_handler, |
| 116 | 116 | |
| 117 | /// If set, `std.start` and `std.Thread` will configure an per-thread alternative signal stack | |
| 118 | /// of this size. Importantly, if `enable_segfault_handler` is set, the segfault handler will | |
| 119 | /// use this alternative stack, meaning it can still print stack traces even if a segmentation | |
| 120 | /// fault is caused by a stack overflow. | |
| 121 | /// | |
| 122 | /// On POSIX targets, the signal stack is configured using 'sigaltstack(2)'. | |
| 123 | /// | |
| 124 | /// On Windows, this value is currently ignored. | |
| 125 | signal_stack_size: ?u64 = 1 << 18, // 1<<17 observed to be sufficient for stack tracing with self-hosted x86_64 backend | |
| 126 | ||
| 117 | 127 | /// The current log level. |
| 118 | 128 | log_level: log.Level = log.default_level, |
| 119 | 129 |