|
| 1 | +### Panicking in rust ### |
| 2 | + |
| 3 | +#### Step 1: Invocation of the `panic!` macro. |
| 4 | + |
| 5 | +There are actually two panic macros - one defined in `libcore`, and one defined in `libstd`. |
| 6 | +This is due to the fact that code in `libcore` can panic. `libcore` is built before `libsd`, |
| 7 | +but we want panics to use the same machinery at runtime, whether they originate in `libcore` or `libstd`. |
| 8 | + |
| 9 | +##### libcore definition of panic! |
| 10 | + |
| 11 | +The `libcore` `panic!` macro eventually makes the following call (in `src/libcore/panicking.rs`): |
| 12 | + |
| 13 | +```rust |
| 14 | +// NOTE This function never crosses the FFI boundary; it's a Rust-to-Rust call |
| 15 | +extern "Rust" { |
| 16 | + #[lang = "panic_impl"] |
| 17 | + fn panic_impl(pi: &PanicInfo<'_>) -> !; |
| 18 | +} |
| 19 | + |
| 20 | +let pi = PanicInfo::internal_constructor(Some(&fmt), location); |
| 21 | +unsafe { panic_impl(&pi) } |
| 22 | +``` |
| 23 | + |
| 24 | +Actually resolving this goes through several layers of indirection: |
| 25 | + |
| 26 | +1. In `src/librustc/middle/weak_lang_items.rs`, `panic_impl` is declared as 'weak lang item', |
| 27 | + with the symbol `rust_begin_unwind`. This is used in `librustc_typeck/collect.rs` |
| 28 | + to set the actual symbol name to `rust_begin_unwind`. |
| 29 | + |
| 30 | + Note that `panic_impl` is declared in an `extern "Rust"` block, |
| 31 | + which means that libcore will attempt to call a foreign symbol called `rust_begin_unwind` |
| 32 | + (to be resolved at link time) |
| 33 | + |
| 34 | +2. In `src/libstd/panicking.rs`, we have this definition: |
| 35 | + |
| 36 | +```rust |
| 37 | +/// Entry point of panic from the libcore crate. |
| 38 | +[cfg(not(test))] |
| 39 | +[panic_handler] |
| 40 | +[unwind(allowed)] |
| 41 | +pub fn rust_begin_panic(info: &PanicInfo<'_>) -> ! { |
| 42 | + continue_panic_fmt(&info) |
| 43 | +} |
| 44 | +``` |
| 45 | + |
| 46 | +The special `panic_handler` attribute is resolved via `src/librustc/middle/lang_items`. |
| 47 | +The `extract` functions convers the `panic_handler` attribute to a `panic_impl` lang item. |
| 48 | + |
| 49 | +Now, we have a matching `panic_impl` lang item in the `libstd`. This function goes |
| 50 | +through the same process as the `extern { fn panic_impl }` definition in `libcore`, ending |
| 51 | +up with a symbol name of `rust_begin_unwind`. At link time, the symbol refernce in `libcore` |
| 52 | +will be resolved to the definition of `libstd` (the function called `rust_begin_panic` in the |
| 53 | +Rust source). |
| 54 | + |
| 55 | +Thus, control flow will pass from libcore to std at runtime. This allows panics from `libcore` |
| 56 | +to go through the same infratructure that other panics use (panic hooks, unwinding, etc) |
| 57 | + |
| 58 | +##### libstd implementation of panic! |
| 59 | + |
| 60 | +This is where the actual panic-related logic begins. In `src/libstd/pancking.rs`, |
| 61 | +control passes to `rust_panic_with_hook`. This method is responsible |
| 62 | +for checking for invoking the global panic hook, and checking for double panics. Finally, |
| 63 | +we call ```__rust_start_panic```, which is provided by the panic runtime. |
| 64 | + |
| 65 | +The call to ```__rust_start_panic``` is very weird - it is passed a ```*mut &mut dyn BoxMeUp```, |
| 66 | +converted to an `usize`. Let's break this type down: |
| 67 | + |
| 68 | +1. `BoxMeUp` is an internal trait. It is implemented for `PanicPayload` (a wrapper around the user-supplied |
| 69 | +payload type), and has a method ```fn box_me_up(&mut self) -> *mut (dyn Any + Send)```. |
| 70 | +This method takes the user-provided payload (`T: Any + Send`), boxes it, and convertes the box to a raw pointer. |
| 71 | + |
| 72 | +2. When we call ```__rust_start_panic```, we have an `&mut dyn BoxMeUp`. However, this is a fat pointer |
| 73 | +(twice the size of a `usize`). To pass this to the panic runtime across an FFI boundary, we take a mutable |
| 74 | +reference *to this mutable reference* (`&mut &mut dyn BoxMeUp`), and convert it to a raw pointer |
| 75 | +(`*mut &mut dyn BoxMeUp`). The outer raw pointer is a thin pointer, since it points to a `Sized` |
| 76 | +type (a mutable reference). Therefore, we can convert this thin pointer into a `usize`, which |
| 77 | +is suitable for passing across an FFI boundary. |
| 78 | + |
| 79 | +Finally, we call ```__rust_start_panic``` with this `usize`. We have now entered the panic runtime. |
| 80 | + |
| 81 | +#### Step 2: The panic runtime |
| 82 | + |
| 83 | +Rust provides two panic runtimes: `libpanic_abort` and `libpanic_unwind`. The user chooses |
| 84 | +between them at build time via their `Cargo.toml` |
| 85 | + |
| 86 | +`libpanic_abort` is extremely simple: its implementation of ```__rust_start_panic``` just aborts, |
| 87 | +as you would expect. |
| 88 | + |
| 89 | +`libpanic_unwind` is the more interesting case. |
| 90 | + |
| 91 | +In its implementation of ```__rust_start_panic```, we take the `usize`, convert |
| 92 | +it back to a `*mut &mut dyn BoxMeUp`, dereference it, and call `box_me_up` |
| 93 | +on the `&mut dyn BoxMeUp`. At this point, we have a raw pointer to the payload |
| 94 | +itself (a `*mut (dyn Send + Any)`): that is, a raw pointer to the actual value |
| 95 | +provided by the user who called `panic!`. |
| 96 | + |
| 97 | +At this point, the platform-independent code ends. We now call into |
| 98 | +platform-specific unwinding logic (e.g `libunwind`). This code is |
| 99 | +responsible for unwinding the stack, running any 'landing pads' associated |
| 100 | +with each frame (currently, running destructors), and transferring control |
| 101 | +to the `catch_unwind` frame. |
| 102 | + |
| 103 | +Note that all panics either abort the process or get caught by some call to `catch_unwind`: |
| 104 | +in `src/libstd/rt.rs`, the call to the user-provided `main` function is wrapped in `catch_unwind |
| 105 | + |
0 commit comments