Why rust program segfaults when panic=abort? EDIT: its not SIGSEGV (segfault), its SIGABRT (abort signal)

hello

i have a simple program

main.rs

fn main() {
    panic!("hello world from panic!")
}

with panic=abort in all profiles

Cargo.toml

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"

when running in debug mode:

❱  cargo run -q
thread 'main' panicked at 'hello world from panic!', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
[1]    589143 IOT instruction (core dumped)  cargo run -q

when running in release mode:

❱  cargo run -q --release
thread 'main' panicked at 'hello world from panic!', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
[1]    589533 IOT instruction (core dumped)  cargo run -q --release

my question is why the program segfaults?

shouldnt it "abort on panic"?

this what i understood initially in my mind about the term "abort": on panic the program stops executing code, does cleanup and exits with code != 0

isnt that expected behaviour? (to not segfault)

what am i missing? any ideas?

thanks in advance.

1 Like

It doesn't look like to me it segfaulted. It aborted somehow, which is exactly what you asked for.

Aborts don't do any cleanup, except of course the cleanup that the OS will do for you anyways when the process ends (which, off the top of my head, might include closing files... in a way... IIRC, and of course the RAM that was used becomes available again).

More involved cleanup is one of the main reasons for non-aborting panic, aka unwinding panic, which "unwinds", i. e. walks up the stack, and drops all the local variables along the way. But it is the kind-of up to the program logic to make sure that panics are propagated (or handled) across thread boundaries, and unwinding panics can even be caught within a thread, so there's definitely no guarantee that it will actually terminate the program, unless you make sure to avoid catching and handling (without re-throwing) panics, and to not ignore panics from spawned threads.

4 Likes

For historical reasons (thank you, PDP-11), some shells will describe a program that aborted as exiting due to the "IOT instruction" (which was how aborts were implemented on the PDP-11).

You're seeing exactly what you'd expect from something that aborted, but with a shell that's using the ancient PDP-11 convention for describing it, instead of the more modern:

$  cargo run -q --release
thread 'main' panicked at 'hello world from panic!', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
[1]    589533 Aborted (core dumped)  cargo run -q --release
12 Likes

A core dump isn't a segmentation fault. When a process terminates due to certain signals, e.g. SIGSEGV (segmentation fault) or SIGABRT (abort program), then a core dump may be created by default. For other signals, no core dump is created by default. This depends on the signal. See this man page for an example which signals create core dumps by default on FreeBSD (the same applies more or less to Linux).

So your Rust program doesn't segfault, but it does create a core dump (as expected):

Num   Name       Default Action       Description
2     SIGINT     terminate process    interrupt program
6     SIGABRT    create core image    abort program (formerly SIGIOT)
9     SIGKILL    terminate process    kill program
11    SIGSEGV    create core image    segmentation violation
15    SIGTERM    terminate process    software termination signal

(excerpt from the FreeBSD manpage)

As you can see, both SIGABRT and SIGSEGV create a core image by default. Calling abort() in C isn't used to cleanly terminate a program but to indicate abnormal program behavior.

Note that while sometimes signals get mapped to an exit code by some layers, a process which terminates due to a signal does normally not have an exit code!

This is why in C, you have the macros WIFEXITED and WIFSIGNALED. See this manpage. So if a Rust program aborts, it will not have an exit code either. At least not under FreeBSD or Linux, and assuming there is no other layer which maps the signal to an exit code (e.g. a shell).

6 Likes

Related question: When I have panic=unwind, my Rust programs terminate with signal 33 (SIGLIBRT) on FreeBSD. I have never heard of that signal. What is it? And how to Rust programs generally terminate in case of an unwind? Is this the same on all platforms? Edit: this isn't true, I made a mistake when checking for the status of the child process.

And related: Is it true that aborting a program also doesn't create an exit code on Windows, or does Windows behave differently?

wow. thanks everyone for the good explanations!

now, everything is clear.

so aborts dont do cleanup, but the memory leaks and file descriptors which are not closed at the time of aborting ... will be closed by the operating system after the process ends, right?

on manjaro linux (arch linux) running the program with panic=unwind doesnt do anything special.

just:

  • prints the panic message
  • exits with code 101

Cargo.toml

[profile.dev]
panic = "unwind"

❱ cargo run -q

thread 'main' panicked at 'hello world from panic!', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Are you sure it exits with code 101? I thought so at first too, but then noticed it was a mapping for signal 33 (SIGLIBRT), so the shell's mapping led me in the wrong direction.

I just noticed I had a bug in my C code. I also get exit code 101.

Yes. In case of sockets (e.g. TCP connections), what happens may depend on how the socket options have been set. It may result in a TCP reset, for example, or a regular close (which is the default currently, I believe). See also TcpStream always terminates connections successfully (even on panic), for example.

Memory generally is cleaned up. The only exception could be shared memory that has been allocated (which is more of a special case and not normally used by Rust, I believe).

Windows 7 + Debug build + Visual Studio installed results in a debug dialog box being displayed...

image

I don't don't think I have a machine without Visual Studio installed so I won't be able to test that.

I do have some Windows 10 machines but I suspect the behaviour is identical.

The output to the console when run using Cargo...

thread 'main' panicked at 'hello world from panic!', src\main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\windows-abort.exe` (exit code: 255)

Release build behaves exactly the same.

Because ExitProcess and TerminateProcess both require an exit code I don't think there's a way to set the exit code to None.

panic_abort uses the fastfail mechanism introduced in windows 8 to abort without running any exception handlers. This results in the STATUS_FAIL_FAST_EXCEPTION status code. On windows 7 and lower it is treated as an access violation and runs exception handlers. rust/lib.rs at 659e169d37990b9c730a59a96081f2ef7afbe8f1 · rust-lang/rust · GitHub

1 Like

Yup. Different...

thread 'main' panicked at 'hello world from panic!', src\main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\release\windows-abort.exe` (exit code: 0xc0000409, STATUS_STACK_BUFFER_OVERRUN)

And no debug dialog box.

In any case, there is still an exit code. As I said, as far as I know, it's impossible to terminate a Windows process without including an exit code.