3

Consider the following code that purposely causes a double panic:

use scopeguard::defer; // 1.1.0

fn main() {
    defer!{ panic!() };
    defer!{ panic!() };
}

I know this typically happens when a Drop implementation panics while unwinding from a previous panic, but why does it cause the program to issue an illegal instruction? That sounds like the code is corrupted or jumped somewhere unintended. I figure this might be system or code generation dependent but I tested on various platforms and they all issue similar errors with the same reason:

  • Linux:

    thread panicked while panicking. aborting.
    Illegal instruction (core dumped)
    
  • Windows (with cargo run):

    thread panicked while panicking. aborting.
    error: process didn't exit successfully: `target\debug\tests.exe` (exit code: 0xc000001d, STATUS_ILLEGAL_INSTRUCTION)
    
  • The Rust Playground:

    thread panicked while panicking. aborting.
    timeout: the monitored command dumped core
    /playground/tools/entrypoint.sh: line 11:     8 Illegal instruction     timeout --signal=KILL ${timeout} "$@"
    

What's going on? What causes this?

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • it's because you panic but catch it or something and panic twice, since the first panic didn't work, I guess rust do a ["OK I WILL DO IT MYSELF"](https://youtu.be/a8HqxpZ_rvk?t=158) – Stargateur Sep 26 '21 at 01:39

1 Answers1

7

This behavior is intended.

From a comment by Jonas Schievink in Why does panicking in a Drop impl cause SIGILL?:

It calls intrinsics::abort(), which LLVM turns into a ub2 instruction, which is illegal, thus SIGILL

I couldn't find any documentation for how double panics are handled, but a paragraph for std::intrinsics::abort() lines up with this behavior:

The current implementation of intrinsics::abort is to invoke an invalid instruction, on most platforms. On Unix, the process will probably terminate with a signal like SIGABRT, SIGILL, SIGTRAP, SIGSEGV or SIGBUS. The precise behaviour is not guaranteed and not stable.

Curiously, this behavior is different from calling std::process::abort(), which always terminates with SIGABRT.

The illegal instruction of choice on x86 is UD2 (I think a typo in the comment above) a.k.a. an undefined instruction which is paradoxically reserved and documented to not be an instruction. So there is no corruption or invalid jump, just a quick and loud way to tell the OS that something has gone very wrong.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • 5
    My assumption would be that `core::intrinsics::abort` (which `std::intrinsics::abort` is a re-export of) is used during double-panics because it must work even with `#![no_std]`, and that `core::instrinsics::abort` uses the illegal instruction because the `core` library cannot use functionality from the operating system. – Frxstrem Sep 26 '21 at 01:26