11
5

More than 3 years have passed since last update.

Rustのcatch_unwindでスタックトレースを取得する

Posted at

Rustにはcatch_unwindという関数がありpanicをキャッチすることが出来ます。

Rustのパニック機構 - 簡潔なQ

しかしここから得られるエラー情報がかなり少ないです。キャッチして得られるResultのError型はBox<dyn Any>みたいな型ですが、これがほとんどStringまたは&strとなっており(今の所他の型を見ていませんが詳しい方教えて下さい)、確認した所スタックトレース情報やパニックが発生したソースの位置情報は入っていませんでした。例えば以下のコードはpanic testと出力します。

if let Err(e) = panic::catch_unwind(move || {
    panic!("panic test");
}) {
    println!("{}", any_to_string(&*e));
}

fn any_to_string(any: &dyn std::any::Any) -> String {
    if let Some(s) = any.downcast_ref::<String>() {
        s.clone()
    } else if let Some(s) = any.downcast_ref::<&str>() {
        s.to_string()
    } else {
        "Any".to_string()
    }
}

これではスタックトレース情報が取れないのでlazy_staticなどと組み合わせて以下のようにしたら上手くいきました。

lazy_static! {
    pub static ref last_panic_info: RwLock<Option<(String, Backtrace)>> = { RwLock::new(None) };
}
  let default_hook = panic::take_hook();
  panic::set_hook(Box::new(move |panic_info| {
      *self::last_panic_info.write().unwrap() = Some((
          format!("{}", panic_info),
          Backtrace::capture(),
      ));
      default_hook(panic_info);
  }));

  if let Err(_) = panic::catch_unwind(move || {
      panic!("panic test");
  }) {
      let last = self::last_panic_info.read().unwrap();
      let last = last.as_ref().unwrap();
      println!("[catch panic]\ninfo: {}\nbacktrace: {}", last.0, last.1);
  }

lazy_staticで最後のパニック情報を保存しておくグローバル変数を宣言して、set_hookでそこにパニック情報を保存しています。

set_hook内でBacktracecaptureすることでバックトレースも取得することが出来ます。 (#![feature(backtrace)]が必要です)。デフォルトのhookを一度避難させて再度呼び出したりもしています(Rustでデフォルトのパニック表示を損なわずにpanic時に行われる処理を増やす - ncaq)

これを環境変数RUST_BACKTRACE=1を設定して実行すると以下のように出力できます。

[catch panic]
info: panicked at 'panic test', src/main.rs:24:9
backtrace: stack backtrace:
   0: backtrace::backtrace::libunwind::trace
             at /Users/vsts/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.37/src/backtrace/libunwind.rs:88
      backtrace::backtrace::trace_unsynchronized
             at /Users/vsts/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.37/src/backtrace/mod.rs:66
      std::backtrace::Backtrace::create
             at src/libstd/backtrace.rs:232
   1: std::backtrace::Backtrace::capture
             at src/libstd/backtrace.rs:207
   2: panic_test_rs::main::{{closure}}
             at src/main.rs:18
   3: std::panicking::rust_panic_with_hook
             at src/libstd/panicking.rs:473
   4: std::panicking::begin_panic
   5: panic_test_rs::main::{{closure}}
             at src/main.rs:24
   6: std::panicking::try::do_call
             at /rustc/fa0f7d0080d8e7e9eb20aa9cbf8013f96c81287f/src/libstd/panicking.rs:288
   7: __rust_maybe_catch_panic
             at src/libpanic_unwind/lib.rs:80
   8: std::panicking::try
             at /rustc/fa0f7d0080d8e7e9eb20aa9cbf8013f96c81287f/src/libstd/panicking.rs:267
   9: std::panic::catch_unwind
             at /rustc/fa0f7d0080d8e7e9eb20aa9cbf8013f96c81287f/src/libstd/panic.rs:396
  10: panic_test_rs::main
             at src/main.rs:23
  11: std::rt::lang_start::{{closure}}
             at /rustc/fa0f7d0080d8e7e9eb20aa9cbf8013f96c81287f/src/libstd/rt.rs:61
  12: std::rt::lang_start_internal::{{closure}}
             at src/libstd/rt.rs:48
      std::panicking::try::do_call
             at src/libstd/panicking.rs:288
  13: __rust_maybe_catch_panic
             at src/libpanic_unwind/lib.rs:80
  14: std::panicking::try
             at src/libstd/panicking.rs:267
      std::panic::catch_unwind
             at src/libstd/panic.rs:396
      std::rt::lang_start_internal
             at src/libstd/rt.rs:47
  15: std::rt::lang_start
             at /rustc/fa0f7d0080d8e7e9eb20aa9cbf8013f96c81287f/src/libstd/rt.rs:61
  16: <panic_test_rs::last_panic_info as core::ops::deref::Deref>::deref

ちなみにRUST_BACKTRACEを設定しないと以下のような出力になります。

[catch panic]
info: panicked at 'panic test', src/main.rs:24:9
backtrace: disabled backtrace
11
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
5