Transform backtrace to string during catch_unwind
Asked Answered
D

1

6

I am writing a cross-platform (Linux/iOS/Android) library in Rust. In case my library panics I want the application to continue working as normal and not crash. In order to do that, I am using catch_unwind; its result contains panic information and doesn't contain enough information about the source of the problem, here is a piece of code explaining what I am doing:

fn calculate(json: &str) -> String {
    unimplemented!()
}

#[no_mangle]
pub fn rust_calculation(json: &str) -> String {
    let r = std::panic::catch_unwind(||{
        // rust calculation
        let calc: String = calculate(json).into();
        calc
    });
    let r_str = match r {
        Ok(v) => v,
        Err(e) => {
            let panic_information = match e.downcast::<String>() {
                Ok(v) => *v,
                _ => "Unknown Source of Error".to_owned()
            };
            panic_information
        }
    };
    return r_str;
}

fn main() {
    println!("{}", rust_calculation("test"));
}

Playground

In case an error occurs, the message returned is not sufficient, and sometimes, it only contains the message

Unknown Source of Error

I want to send the backtrace to the calling source so that it can log it and then we can debug the Rust library afterwards. How can I do that?

Dekow answered 28/7, 2021 at 8:20 Comment(0)
B
4

some times it countains the message:

Unknown Source of Error

This is probably because e may not only be a String, but also a &str (well, it can basically be any type, but those two are the most common, you can only get other types when using panic_any()). When calling panic!() inside catch_unwind(), you only get a String if you are panic!'ing with arguments, otherwise, the panic message is a &str (as there is no need for allocating a String).

I want to send the backtrace to the calling source so that he can log it and then we can debug the rust library afterwards, how can I do that ?

Solution on nightly Rust

On nightly Rust, you can generate a backtrace inside a panic handler via std::backtrace::Backtrace:

#![feature(backtrace)]
use lazy_static::lazy_static;
use std::sync::{Arc, Mutex};

// on panic, save the backtrace here (as string)
lazy_static! {
    static ref BACKTRACE: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
}

#[no_mangle]
pub fn rust_calculation(json: &str) -> String {
    let r = std::panic::catch_unwind(||{
        panic!("Panic");
    });
    let r_str = match r {
        Ok(v) => v,
        Err(e) => {
            let panic_information = match e.downcast::<String>() {
                Ok(v) => *v,
                Err(e) => match e.downcast::<&str>() {
                    Ok(v) => v.to_string(),
                    _ => "Unknown Source of Error".to_owned()
                }
            };
            format!("{}\nBacktrace:\n{}", panic_information, BACKTRACE.lock().unwrap().take().unwrap_or("<Backtrace not found>".to_string()))
        }
    };
    return r_str;
}

fn panic_hook(_: &std::panic::PanicInfo) {
    *BACKTRACE.lock().unwrap() = Some(std::backtrace::Backtrace::force_capture().to_string());
}

fn main() {
    std::panic::set_hook(Box::new(panic_hook));
    println!("{}", rust_calculation(""));
}

Playground link

Output:

Panic
Backtrace:
   0: playground::panic_hook
             at ./src/main.rs:32:39
   1: core::ops::function::Fn::call
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/core/src/ops/function.rs:70:5
   2: std::panicking::rust_panic_with_hook
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panicking.rs:626:17
   3: std::panicking::begin_panic::{{closure}}
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panicking.rs:542:9
   4: std::sys_common::backtrace::__rust_end_short_backtrace
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/sys_common/backtrace.rs:141:18
   5: std::panicking::begin_panic
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panicking.rs:541:12
   6: playground::rust_calculation::{{closure}}
             at ./src/main.rs:13:9
   7: std::panicking::try::do_call
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panicking.rs:401:40
   8: __rust_try
   9: std::panicking::try
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panicking.rs:365:19
  10: std::panic::catch_unwind
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panic.rs:434:14
  11: rust_calculation
             at ./src/main.rs:12:13
  12: playground::main
             at ./src/main.rs:37:20
  13: core::ops::function::FnOnce::call_once
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/core/src/ops/function.rs:227:5
  14: std::sys_common::backtrace::__rust_begin_short_backtrace
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/sys_common/backtrace.rs:125:18
  15: std::rt::lang_start::{{closure}}
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/rt.rs:63:18
  16: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/core/src/ops/function.rs:259:13
  17: std::panicking::try::do_call
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panicking.rs:401:40
  18: std::panicking::try
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panicking.rs:365:19
  19: std::panic::catch_unwind
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panic.rs:434:14
  20: std::rt::lang_start_internal::{{closure}}
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/rt.rs:45:48
  21: std::panicking::try::do_call
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panicking.rs:401:40
  22: std::panicking::try
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panicking.rs:365:19
  23: std::panic::catch_unwind
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panic.rs:434:14
  24: std::rt::lang_start_internal
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/rt.rs:45:20
  25: std::rt::lang_start
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/rt.rs:62:5
  26: main
  27: __libc_start_main
  28: _start

Please note that this backtrace contains a few additional frames after the call to panic! that are due to Rust's internal panic handling code.

Solution with dependencies

If you are not on nightly Rust, but are willing to use additional dependencies, you can do the same with the backtrace crate (thanks to @BashirAbdelwahed for calling my attention to this):

use lazy_static::lazy_static;
use std::sync::{Arc, Mutex};

// on panic, save the backtrace here (as string)
lazy_static! {
    static ref BACKTRACE: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
}

#[no_mangle]
pub fn rust_calculation(json: &str) -> String {
    let r = std::panic::catch_unwind(||{
        panic!("Panic");
    });
    let r_str = match r {
        Ok(v) => v,
        Err(e) => {
            let panic_information = match e.downcast::<String>() {
                Ok(v) => *v,
                Err(e) => match e.downcast::<&str>() {
                    Ok(v) => v.to_string(),
                    _ => "Unknown Source of Error".to_owned()
                }
            };
            format!("{}\nBacktrace:\n{}", panic_information, BACKTRACE.lock().unwrap().take().unwrap_or("<Backtrace not found>".to_string()))
        }
    };
    return r_str;
}

fn panic_hook(_: &std::panic::PanicInfo) {
    *BACKTRACE.lock().unwrap() = Some(format!("{:?}", backtrace::Backtrace::new()));
}

fn main() {
    std::panic::set_hook(Box::new(panic_hook));
    println!("{}", rust_calculation(""));
}

Playground link

Output:

Panic
Backtrace:
   0: playground::panic_hook
             at src/main.rs:31:55
   1: core::ops::function::Fn::call
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/core/src/ops/function.rs:70:5
   2: std::panicking::rust_panic_with_hook
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/panicking.rs:595:17
   3: std::panicking::begin_panic::{{closure}}
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/panicking.rs:520:9
   4: std::sys_common::backtrace::__rust_end_short_backtrace
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/sys_common/backtrace.rs:141:18
   5: std::panicking::begin_panic
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/panicking.rs:519:12
   6: playground::rust_calculation::{{closure}}
             at src/main.rs:12:9
   7: std::panicking::try::do_call
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/panicking.rs:379:40
   8: __rust_try
   9: std::panicking::try
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/panicking.rs:343:19
  10: std::panic::catch_unwind
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/panic.rs:431:14
  11: rust_calculation
             at src/main.rs:11:13
  12: playground::main
             at src/main.rs:36:20
  13: core::ops::function::FnOnce::call_once
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/core/src/ops/function.rs:227:5
  14: std::sys_common::backtrace::__rust_begin_short_backtrace
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/sys_common/backtrace.rs:125:18
  15: std::rt::lang_start::{{closure}}
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/rt.rs:49:18
  16: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/core/src/ops/function.rs:259:13
      std::panicking::try::do_call
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/panicking.rs:379:40
      std::panicking::try
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/panicking.rs:343:19
      std::panic::catch_unwind
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/panic.rs:431:14
      std::rt::lang_start_internal
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/rt.rs:34:21
  17: std::rt::lang_start
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/rt.rs:48:5
  18: main
  19: __libc_start_main
  20: _start
Bayly answered 28/7, 2021 at 10:3 Comment(3)
But is it a good idea to include in the production binary ? how about in release mode ?Dekow
Good to know: Backtrace is not nightly anymore!Polyzoan
Specifically, stabilized in Rust 1.65.Engle

© 2022 - 2024 — McMap. All rights reserved.