iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 12
0
Software Development

從 Rust 往程式底層前進系列 第 19

unwind 與 backtrace

是說在之前介紹到的 libunwind ,因為它有讀取 frame 資訊的功能,實際上還有個很有用的用途,顯示 backtrace ,另外在 Rust 發生 panic 時不是也有 backtrace 可以看嗎?只要呼叫時加上 RUST_BACKTRACE=1 就可以看到了,像這樣:

$ RUST_BACKTRACE=1 ./demo
thread 'main' panicked at 'panic', demo.rs:2:5
stack backtrace:
   0: backtrace::backtrace::libunwind::trace
             at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.37/src/backtrace/libunwind.rs:88
   1: backtrace::backtrace::trace_unsynchronized
             at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.37/src/backtrace/mod.rs:66
   2: std::sys_common::backtrace::_print_fmt
             at src/libstd/sys_common/backtrace.rs:76
   3: <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt
             at src/libstd/sys_common/backtrace.rs:60
   4: core::fmt::write
             at src/libcore/fmt/mod.rs:1028
   5: std::io::Write::write_fmt
             at src/libstd/io/mod.rs:1412
   6: std::sys_common::backtrace::_print
             at src/libstd/sys_common/backtrace.rs:64
   7: std::sys_common::backtrace::print
             at src/libstd/sys_common/backtrace.rs:49
   8: std::panicking::default_hook::{{closure}}
             at src/libstd/panicking.rs:196
   9: std::panicking::default_hook
             at src/libstd/panicking.rs:210
  10: std::panicking::rust_panic_with_hook
             at src/libstd/panicking.rs:473
  11: std::panicking::begin_panic
  12: demo::main
  13: std::rt::lang_start::{{closure}}
  14: std::rt::lang_start_internal::{{closure}}
             at src/libstd/rt.rs:49
  15: std::panicking::try::do_call
             at src/libstd/panicking.rs:292
  16: __rust_maybe_catch_panic
             at src/libpanic_unwind/lib.rs:80
  17: std::panicking::try
             at src/libstd/panicking.rs:271
  18: std::panic::catch_unwind
             at src/libstd/panic.rs:394
  19: std::rt::lang_start_internal
             at src/libstd/rt.rs:48
  20: std::rt::lang_start
  21: main
  22: __libc_start_main
  23: _start

其實在上面的 backtrace 中就已經出現 libunwind 了,不過我們還是要來實際試試看,自己用 libunwind 顯示 backtrace 吧,正好 Rust 有個 crate 叫 unwind 有提供 libunwind 的包裝,文件中也有附範例,就來試試吧:

use unwind::{Cursor, RegNum};

fn main() {
    Cursor::local(|mut cursor| {
        loop {
            let ip = cursor.register(RegNum::IP)?;

            match (cursor.procedure_info(), cursor.procedure_name()) {
                (Ok(ref info), Ok(ref name)) if ip == info.start_ip() + name.offset() => {
                    println!(
                        "{:#016x} - {} ({:#016x}) + {:#x}",
                        ip,
                        name.name(),
                        info.start_ip(),
                        name.offset()
                    );
                }
                _ => println!("{:#016x} - ????", ip),
            }

            if !cursor.step()? {
                break;
            }
        }
        Ok(())
    })
    .unwrap();
}

但它輸出的是:

0x0055a105c131b6 - _ZN6unwind6Cursor5local17ha597244bc85e9decE (0x0055a105c13170) + 0x46
0x0055a105c13d59 - _ZN14backtrace_test4main17hdb114b59026e36b3E (0x0055a105c13d50) + 0x9
0x0055a105c13f40 - _ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hceb03d45505ee7d3E (0x0055a105c13f30) + 0x10
0x0055a105c230c3 - _ZN3std9panicking3try7do_call17hec516f7d20f460a8E (0x0055a105c230b0) + 0x13
0x0055a105c2488a - __rust_maybe_catch_panic (0x0055a105c24870) + 0x1a
0x0055a105c23b3d - _ZN3std2rt19lang_start_internal17h57288edab9fc6490E (0x0055a105c237e0) + 0x35d
0x0055a105c13f19 - _ZN3std2rt10lang_start17h1cb42ec6dca06f3dE (0x0055a105c13ed0) + 0x49
0x0055a105c13daa - main (0x0055a105c13d80) + 0x2a
0x007fda73677b97 - __libc_start_main (0x007fda73677ab0) + 0xe7
0x0055a105c1307a - _start (0x0055a105c13050) + 0x2a

天啊,這什麼東西啊,跟 Rust panic 時顯示的差好多啊,首先那個像亂碼一樣的名字是怎麼回事呢?還有,有沒有辦法拿到位在哪個檔案與它的行數?

首先那個名字是因為 mangle 的原故, Rust 或是像 C++ 這邊的語言,因為支援 mod 或是 C++ 的 namespace 而要避免在不同 mod 下的函式名稱重覆,所以會把函式名稱以一定的規則加工,變成像上面這樣的名稱,這時候可以用在第 8 篇提過的 c++filt 將名稱還原,還原後會變成像下面這樣

0x005584f38291b6 - unwind::Cursor::local (0x005584f3829170) + 0x46
0x005584f3829d59 - backtrace_test::main (0x005584f3829d50) + 0x9
0x005584f3829f40 - std::rt::lang_start::{{closure}} (0x005584f3829f30) + 0x10
0x005584f38390c3 - std::panicking::try::do_call (0x005584f38390b0) + 0x13
0x005584f383a88a - __rust_maybe_catch_panic (0x005584f383a870) + 0x1a
0x005584f3839b3d - std::rt::lang_start_internal (0x005584f38397e0) + 0x35d
0x005584f3829f19 - std::rt::lang_start (0x005584f3829ed0) + 0x49
0x005584f3829daa - main (0x005584f3829d80) + 0x2a
0x007f000b64eb97 - __libc_start_main (0x007f000b64eab0) + 0xe7
0x005584f382907a - _start (0x005584f3829050) + 0x2a

另外這其實也可以在程式中解決,有個叫 rustc-demangle 的 crate 可以用,不過這晚點再說,先來解決沒有檔名與行號的問題吧,說來 Linux 下有個程式叫 addr2line 能讀取 debug 的資訊,從位置轉換回檔名與行號,但它要的是跟 .text 段相對的偏移位置,為了要拿到這個位置,我是先透過 objdump -tC 取得函式的位置,再加上上面取得的偏移位置來取得的,比如上面那個 backtrace_test::main 用 objdump 取得的位置是 0x5d50 再加上 0x9 得到 0x5d59 拿去給 addr2line 就能得到以下輸出

$ addr2line -e target/debug/backtrace-test 5d59
src/main.rs:4

如果有更好的方法歡迎留言跟我說,然後這樣做實在太麻煩了,於是有另一個函式庫把這些功能也包裝了起來叫 libbacktrace 而 Rust 也有個包裝的 crate 把這些工作都做好了:

use backtrace::Backtrace;

fn main() {
    let backtrace = Backtrace::new();
    println!("{:?}", backtrace);
}

執行後會得到

stack backtrace:
   0: backtrace_test::main
             at src/main.rs:4
   1: std::rt::lang_start::{{closure}}
             at /rustc/084beb83e0e87d673d5fabc844d28e8e8ae2ab4c/src/libstd/rt.rs:64
   2: std::rt::lang_start_internal::{{closure}}
             at src/libstd/rt.rs:49
      std::panicking::try::do_call
             at src/libstd/panicking.rs:292
   3: __rust_maybe_catch_panic
             at src/libpanic_unwind/lib.rs:80
   4: std::panicking::try
             at src/libstd/panicking.rs:271
      std::panic::catch_unwind
             at src/libstd/panic.rs:394
      std::rt::lang_start_internal
             at src/libstd/rt.rs:48
   5: std::rt::lang_start
             at /rustc/084beb83e0e87d673d5fabc844d28e8e8ae2ab4c/src/libstd/rt.rs:64
   6: main
   7: __libc_start_main
   8: _start

看來平常還是直接用 backtrace 就好了,會需要直接利用到 libunwind 的機會我想應該不多吧


上一篇
panic - 編譯器篇
下一篇
手動載入執行檔
系列文
從 Rust 往程式底層前進26
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言