-
Notifications
You must be signed in to change notification settings - Fork 547
[WIP] Add panic implementation docs #521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9fa1429
dd87ad8
880b7b1
e87f54b
a6c577b
9f25a02
05b70f7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
### Panicking in rust ### | ||
|
||
#### Step 1: Invocation of the `panic!` macro. | ||
|
||
There are actually two panic macros - one defined in `libcore`, and one defined in `libstd`. | ||
This is due to the fact that code in `libcore` can panic. `libcore` is built before `libstd`, | ||
but we want panics to use the same machinery at runtime, whether they originate in `libcore` | ||
or `libstd`. | ||
|
||
##### libcore definition of panic! | ||
|
||
The `libcore` `panic!` macro eventually makes the following call (in `src/libcore/panicking.rs`): | ||
|
||
```rust | ||
// NOTE This function never crosses the FFI boundary; it's a Rust-to-Rust call | ||
extern "Rust" { | ||
#[lang = "panic_impl"] | ||
fn panic_impl(pi: &PanicInfo<'_>) -> !; | ||
} | ||
|
||
let pi = PanicInfo::internal_constructor(Some(&fmt), location); | ||
unsafe { panic_impl(&pi) } | ||
``` | ||
|
||
Actually resolving this goes through several layers of indirection: | ||
|
||
1. In `src/librustc/middle/weak_lang_items.rs`, `panic_impl` is declared as 'weak lang item', | ||
with the symbol `rust_begin_unwind`. This is used in `librustc_typeck/collect.rs` | ||
to set the actual symbol name to `rust_begin_unwind`. | ||
|
||
Note that `panic_impl` is declared in an `extern "Rust"` block, | ||
which means that libcore will attempt to call a foreign symbol called `rust_begin_unwind` | ||
(to be resolved at link time) | ||
|
||
2. In `src/libstd/panicking.rs`, we have this definition: | ||
|
||
```rust | ||
/// Entry point of panic from the libcore crate. | ||
[cfg(not(test))] | ||
[panic_handler] | ||
[unwind(allowed)] | ||
Comment on lines
+39
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like you are missing # before these lines? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't seem to figure out how to stop the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think md has comments? Can you elaborate on what the concern is? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When I added There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm... Ok, we can figure this out in a followup PR. I think we can go ahead and merge without this. |
||
pub fn begin_panic_handler(info: &PanicInfo<'_>) -> ! { | ||
... | ||
} | ||
``` | ||
|
||
The special `panic_handler` attribute is resolved via `src/librustc/middle/lang_items`. | ||
The `extract` function converts the `panic_handler` attribute to a `panic_impl` lang item. | ||
|
||
Now, we have a matching `panic_handler` lang item in the `libstd`. This function goes | ||
through the same process as the `extern { fn panic_impl }` definition in `libcore`, ending | ||
up with a symbol name of `rust_begin_unwind`. At link time, the symbol reference in `libcore` | ||
will be resolved to the definition of `libstd` (the function called `begin_panic_handler` in the | ||
Rust source). | ||
|
||
Thus, control flow will pass from libcore to std at runtime. This allows panics from `libcore` | ||
to go through the same infrastructure that other panics use (panic hooks, unwinding, etc) | ||
|
||
##### libstd implementation of panic! | ||
|
||
This is where the actual panic-related logic begins. In `src/libstd/panicking.rs`, | ||
control passes to `rust_panic_with_hook`. This method is responsible | ||
for invoking the global panic hook, and checking for double panics. Finally, | ||
we call `__rust_start_panic`, which is provided by the panic runtime. | ||
|
||
The call to `__rust_start_panic` is very weird - it is passed a `*mut &mut dyn BoxMeUp`, | ||
converted to an `usize`. Let's break this type down: | ||
|
||
1. `BoxMeUp` is an internal trait. It is implemented for `PanicPayload` | ||
(a wrapper around the user-supplied payload type), and has a method | ||
`fn box_me_up(&mut self) -> *mut (dyn Any + Send)`. | ||
This method takes the user-provided payload (`T: Any + Send`), | ||
boxes it, and converts the box to a raw pointer. | ||
|
||
2. When we call `__rust_start_panic`, we have an `&mut dyn BoxMeUp`. | ||
However, this is a fat pointer (twice the size of a `usize`). | ||
To pass this to the panic runtime across an FFI boundary, we take a mutable | ||
reference *to this mutable reference* (`&mut &mut dyn BoxMeUp`), and convert it to a raw pointer | ||
(`*mut &mut dyn BoxMeUp`). The outer raw pointer is a thin pointer, since it points to a `Sized` | ||
type (a mutable reference). Therefore, we can convert this thin pointer into a `usize`, which | ||
is suitable for passing across an FFI boundary. | ||
|
||
Finally, we call `__rust_start_panic` with this `usize`. We have now entered the panic runtime. | ||
|
||
#### Step 2: The panic runtime | ||
|
||
Rust provides two panic runtimes: `libpanic_abort` and `libpanic_unwind`. The user chooses | ||
between them at build time via their `Cargo.toml` | ||
|
||
`libpanic_abort` is extremely simple: its implementation of `__rust_start_panic` just aborts, | ||
as you would expect. | ||
|
||
`libpanic_unwind` is the more interesting case. | ||
|
||
In its implementation of `__rust_start_panic`, we take the `usize`, convert | ||
it back to a `*mut &mut dyn BoxMeUp`, dereference it, and call `box_me_up` | ||
on the `&mut dyn BoxMeUp`. At this point, we have a raw pointer to the payload | ||
itself (a `*mut (dyn Send + Any)`): that is, a raw pointer to the actual value | ||
provided by the user who called `panic!`. | ||
|
||
At this point, the platform-independent code ends. We now call into | ||
platform-specific unwinding logic (e.g `libunwind`). This code is | ||
responsible for unwinding the stack, running any 'landing pads' associated | ||
with each frame (currently, running destructors), and transferring control | ||
to the `catch_unwind` frame. | ||
|
||
Note that all panics either abort the process or get caught by some call to `catch_unwind`: | ||
in `src/libstd/rt.rs`, the call to the user-provided `main` function is wrapped in `catch_unwind`. |
Uh oh!
There was an error while loading. Please reload this page.