ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
34.85k stars 2.55k forks source link

Panic on stack overflow/segfault in debug mode. #7371

Open lolbinarycat opened 3 years ago

lolbinarycat commented 3 years ago

I'm not sure whether this is a bug report or a proposal because this seems like a fairly basic feature. Currently it seems like we are doing nothing about segfaults/stack overflows. This is alright if you are running the compiled program from a terminal, as all terminals I've used will tell you if a program segfaults, but zig run gives no indication other than a nonzero exit code. This makes it very difficult to figure out what's going on if you get infinite recursion and zig run exits with no output.

I don't know if it's feasible to distinguish a stack overflow from other segfaults or to provide a stack trace, but I do know that it's possible to at least install a signal handler, or to make zig run print "Segmentation fault".

LemonBoy commented 3 years ago

Currently it seems like we are doing nothing about segfaults/stack overflows.

A segfault handler is installed by default and we emit stack probes on i386/x86_64, I wouldn't say we're not not doing anything. What code are you running that doesn't give you any stack trace?

lolbinarycat commented 3 years ago
fn stackOverflow() void {
    var x: [200]usize = undefined;
    @call(.{ .modifier = .never_tail }, stackOverflow, .{});
}

pub fn main() void {
    stackOverflow();
}

zig has no output and returns 1. building an executable and running it results in a shell-specific message about a segmentation fault/SIGSEGV

zig version is 0.6.0+006b780d4, I'll try it with a newer version after this.

LemonBoy commented 3 years ago

I see. The overflow protection kicks in but once it tries to print out a nice message, stating that something went wrong, it dies because there's no more stack space.

The solution is simple yet convoluted, using sigaltstack during the startup phase (and for each thread we spawn). The obvious downside is the need for more memory, a quick test showed we need something like 32kb of alternate stack for each N+1 active threads.

nektro commented 2 years ago

proof of concept for posix https://gist.github.com/nektro/c7a3a3232e6dcb409664ed784be73529

VisenDev commented 1 year ago

This stack segmentation fault is also happening when attempting to parse large json strings using std.json.parseFromSlice

Example code causing a segfault. level.Level is a large struct.

    const string = try std.fs.cwd().readFileAlloc(a, path, 100000);
    if (!try std.json.validate(a, string)) {
        return error.invalid_json;
    }
    const parsed = try std.json.parseFromSlice(level.Level, a, string, .{});

Process 13483 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x7ff7bf6ff500)
    frame #0: 0x0000000100164c54 dev`__zig_probe_stack at stack_probe.zig:53:13 [opt]
   50       switch (arch) {
   51           .x86_64 => {
   52               // %rax = probe length, %rsp = stack pointer
-> 53               asm volatile (
   54                   \\        push   %%rcx
   55                   \\        mov    %%rax, %%rcx
   56                   \\        cmp    $0x1000,%%rcx
warning: dev was compiled with optimization - stepping may behave oddly; variables may not be available.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x7ff7bf6ff500)
  * frame #0: 0x0000000100164c54 dev`__zig_probe_stack at stack_probe.zig:53:13 [opt]
    frame #1: 0x00000001000a0f89 dev`json.static.innerParse__anon_16225(allocator=mem.Allocator @ 0x00007ff7bfc75588, source=0x00007ff7bfd02868, options=json.static.ParseOptions @ 0x00007ff7bfbe8260) at static.zig:361:63
    frame #2: 0x0000000100088e4e dev`json.static.innerParse__anon_15817(allocator=mem.Allocator @ 0x00007ff7bfc75588, source=0x00007ff7bfd02868, options=json.static.ParseOptions @ 0x00007ff7bfbe8260) at static.zig:361:63
    frame #3: 0x000000010006fbf0 dev`json.static.parseFromTokenSourceLeaky__anon_14537(allocator=mem.Allocator @ 0x00007ff7bfc75588, scanner_or_reader=0x00007ff7bfd02868, options=json.static.ParseOptions @ 0x00000001001d3980) at static.zig:140:33
    frame #4: 0x00000001000409c4 dev`json.static.parseFromTokenSource__anon_11798(allocator=mem.Allocator @ 0x00007ff7bfeff338, scanner_or_reader=0x00007ff7bfd02868, options=json.static.ParseOptions @ 0x00000001001d3980) at static.zig:107:49
    frame #5: 0x000000010002c19f dev`json.static.parseFromSlice__anon_10958(allocator=mem.Allocator @ 0x00007ff7bfeff338,```