Search code examples
rusterror-handlingpanic

How to get panic information (i.e. stack trace) with `catch_unwind`?


If using set_hook, we can get lots of information, especially the stack trace - which is very helpful. However, with catch_unwind, I only get a Result which contains almost no useful information at all. Therefore, I wonder how to get panic information (especially stack trace) with Rust's catch_unwind?

I am in a multi-threading environment, where there are many threads concurrently running and any can panic. I guess I should use set_hook together with catch_unwind, and also uses some thread-local variables, but I am not sure about whether it is feasible and the details.


Solution

  • You can get a backtrace in the panic hook by using the backtrace crate, however that doesn't help in catch_unwind since the stack has already been unwound from where the panic occurred.

    What you can do is smuggle the backtrace from the panic hook into the catcher by storing it in a thread local variable. This should be reliable since a panic while panicking is an automatic process abort (so you can't overwrite one that hasn't been caught yet), and panics do not propagate across thread boundaries (joining a panicked thread returns a Result<_, Box<dyn Any>> with the panic as the error).

    Here's a full example:

    use std::cell::RefCell;
    use backtrace::Backtrace;
    
    thread_local! {
        static Backtrace: RefCell<Option<Backtrace>> = RefCell::new(None);
    }
    
    fn f() {
        panic!("xyz");
    }
    
    fn g() {
        if let Err(err) = std::panic::catch_unwind(f) {
            let b = Backtrace.with(|b| b.borrow_mut().take()).unwrap();
            println!("at panic:\n{:?}", b);
        }
    }
    
    fn main() {
        std::panic::set_hook(Box::new(|_| {
            let trace = Backtrace::new();
            Backtrace.with(move |b| b.borrow_mut().replace(trace));
        }));
        
        g();
    }