18日目の記事の続きということで、 std::backtrace::Backtrace についてもう少し掘り下げたいと思います!
バックトレースの元になる情報の場所 (ELFの場合)
バイナリコード中のどこにデバッグ情報があるか・どうやってバックトレースを生成しているのか知りたく、色々調べたのですが結局よくわかってないです!![]()
![]()
ChatGPTから聞いたことをまとめました。
Linuxの実行ファイル形式の一つであるELFの場合、 .symtab というセクションに関数名が入っているそうです。
そしてファイル名や行番号は別なところに入っていて、デバッグ情報が必要らしいですね。デバッグビルドを行うとDWARFという規格1により記述されたデバッグ情報が埋め込まれるそうで、ここの情報を元にして得られるようです。
デバッグ情報が埋め込まれているかは file コマンドで確かめることが可能です。
$ file target/debug/backtrace_pg
target/debug/backtrace_pg: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=d74c675df3fc991f62d74bc4b143ed5d1452ed28, with debug_info, not stripped
後ろの方に(Qiita上だとスクロールが必要かもしれません) with debug_info と書かれていればバイナリにデバッグ情報が含まれています。
デバッグ情報については、 readelf というコマンドを叩き、 .debug_* みたいな形式の名前になっているセクションがあればその中に入っているらしいです。
$ readelf -WS target/debug/backtrace_pg | grep -E '\.(debug_|symtab|dynsym|eh_frame)'
[ 4] .dynsym DYNSYM 0000000000000340 000340 0006a8 18 A 8 1 8
[13] .debug_gdb_scripts PROGBITS 000000000000e238 00e238 000022 01 AMS 0 0 1
[14] .eh_frame_hdr PROGBITS 000000000000e25c 00e25c 00125c 00 A 0 0 4
[15] .eh_frame PROGBITS 000000000000f4b8 00f4b8 005a4c 00 A 0 0 8
[33] .debug_abbrev PROGBITS 0000000000000000 05ae18 001899 00 0 0 1
[34] .debug_info PROGBITS 0000000000000000 05c6b1 106f48 00 0 0 1
[35] .debug_aranges PROGBITS 0000000000000000 1635f9 007b80 00 0 0 1
[36] .debug_ranges PROGBITS 0000000000000000 16b179 06f580 00 0 0 1
[37] .debug_str PROGBITS 0000000000000000 1da6f9 1641bc 01 MS 0 0 1
[38] .debug_line PROGBITS 0000000000000000 33e8b5 06ed63 00 0 0 1
[39] .symtab SYMTAB 0000000000000000 3ad618 006a20 18 41 801 8
行番号は .debug_line セクションになるんですかね?本当はもっと詳細に踏み込みたかったのですが今の自分の知識ではこれ以上踏み込めなかったので、一旦保留にします 🫠
release時もバックトレースを得たい場合: カスタムプロファイル
デバッグビルドでは詳細なバックトレースを出力できますが、リリースビルドではバックトレースの情報がかなり少なくなってしまいます。
use std::backtrace::Backtrace;
fn func1() -> Backtrace {
Backtrace::capture()
}
fn func2() -> Backtrace {
func1()
}
fn func3() -> Backtrace {
func2()
}
fn main() {
let bt = func3();
println!("Status: {:?}\n---\n{}", bt.status(), bt);
}
$ RUST_BACKTRACE=1 cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/backtrace_pg`
Status: Captured
---
0: backtrace_pg::func1
at ./src/main.rs:4:5
1: backtrace_pg::func2
at ./src/main.rs:8:5
2: backtrace_pg::func3
at ./src/main.rs:12:5
3: backtrace_pg::main
at ./src/main.rs:16:14
4: core::ops::function::FnOnce::call_once
at /home/namn/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
5: std::sys::backtrace::__rust_begin_short_backtrace
at /home/namn/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/backtrace.rs:158:18
6: std::rt::lang_start::{{closure}}
at /home/namn/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:206:18
7: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/core/src/ops/function.rs:287:21
8: std::panicking::catch_unwind::do_call
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/panicking.rs:590:40
9: std::panicking::catch_unwind
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/panicking.rs:553:19
10: std::panic::catch_unwind
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/panic.rs:359:14
11: std::rt::lang_start_internal::{{closure}}
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/rt.rs:175:24
12: std::panicking::catch_unwind::do_call
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/panicking.rs:590:40
13: std::panicking::catch_unwind
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/panicking.rs:553:19
14: std::panic::catch_unwind
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/panic.rs:359:14
15: std::rt::lang_start_internal
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/rt.rs:171:5
16: std::rt::lang_start
at /home/namn/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:205:5
17: main
18: <unknown>
19: __libc_start_main
20: _start
$ RUST_BACKTRACE=1 cargo run --release
Finished `release` profile [optimized] target(s) in 0.00s
Running `target/release/backtrace_pg`
Status: Captured
---
0: backtrace_pg::main
1: std::sys::backtrace::__rust_begin_short_backtrace
2: std::rt::lang_start::{{closure}}
3: std::rt::lang_start_internal
4: main
5: <unknown>
6: __libc_start_main
7: _start
関数名も行番号もわからなくなりました...
ですが安心してください。 Cargo.toml にて debug = true を指定したプロファイルを作成すれば、引き続きリリースビルドでもデバッグ情報を出せます。そこまでして出したい...?
[profile.release-with-debug]
inherits = "release"
debug = true
$ RUST_BACKTRACE=1 cargo run --profile=release-with-debug
Compiling backtrace_pg v0.1.0 (/home/namn/workspace/qiita_adv_articles_2025/programs/adv18/backtrace_pg)
Finished `release-with-debug` profile [optimized + debuginfo] target(s) in 0.50s
Running `target/release-with-debug/backtrace_pg`
Status: Captured
---
0: backtrace_pg::func1
at ./src/main.rs:4:5
1: backtrace_pg::func2
at ./src/main.rs:8:5
2: backtrace_pg::func3
at ./src/main.rs:12:5
3: backtrace_pg::main
at ./src/main.rs:16:14
4: core::ops::function::FnOnce::call_once
at /home/namn/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
5: std::sys::backtrace::__rust_begin_short_backtrace
at /home/namn/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/backtrace.rs:158:18
6: std::rt::lang_start::{{closure}}
at /home/namn/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:206:18
7: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/core/src/ops/function.rs:287:21
8: std::panicking::catch_unwind::do_call
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/panicking.rs:590:40
9: std::panicking::catch_unwind
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/panicking.rs:553:19
10: std::panic::catch_unwind
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/panic.rs:359:14
11: std::rt::lang_start_internal::{{closure}}
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/rt.rs:175:24
12: std::panicking::catch_unwind::do_call
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/panicking.rs:590:40
13: std::panicking::catch_unwind
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/panicking.rs:553:19
14: std::panic::catch_unwind
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/panic.rs:359:14
15: std::rt::lang_start_internal
at /rustc/ed61e7d7e242494fb7057f2657300d9e77bb4fcb/library/std/src/rt.rs:171:5
16: main
17: <unknown>
18: __libc_start_main
19: _start
ちなみにダイマなんですけどhooqマクロならプロファイルに関係なくエラートレースもどきを付けられますし、プロファイル次第で細かくトレース内容を決められます!
詳しくは今日までのアドカレ記事を見ていただけると幸いです! ![]()
WebAssemblyでBacktrace::statusを見てみる
「プラットフォームによってはサポートされない」とのことですが、サポートされないプラットフォームが実際にあるのか気になりますね...?
というわけで...あ、 Wasm君ちょうど良いところに 。君ちょっとバックトレース見せなさい!
use wasm_bindgen::prelude::*;
use std::backtrace::Backtrace;
fn func1() -> Backtrace {
Backtrace::force_capture()
}
fn func2() -> Backtrace {
func1()
}
fn func3() -> Backtrace {
func2()
}
#[wasm_bindgen]
pub fn show_trace() -> String {
let bt = func3();
format!("Status: {:?}\n---\n{}", bt.status(), bt)
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="output"></div>
<script type="module">
import init, { show_trace } from "./pkg/backtrace_wasm_pg.js";
init().then(() => {
const output = document.getElementById("output");
output.textContent = show_trace();
});
</script>
</body>
</html>
Cargo.toml
[package]
name = "backtrace_wasm_pg"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2.106"
$ wasm-pack build --target web --dev
[INFO]: 🎯 Checking for the Wasm target...
[INFO]: 🌀 Compiling to Wasm...
Compiling backtrace_wasm_pg v0.1.0 (/home/namn/workspace/qiita_adv_articles_2025/programs/adv18/backtrace_wasm_pg)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s
[INFO]: ⬇️ Installing wasm-bindgen...
[INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended
[INFO]: ✨ Done in 0.81s
[INFO]: 📦 Your wasm pkg is ready to publish at /home/namn/workspace/qiita_adv_articles_2025/programs/adv18/backtrace_wasm_pg/pkg.
$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
_人人人人人人人_
> Unsupported <
 ̄Y^Y^Y^Y^Y^Y^Y^ ̄
デバッグビルドでしたがデバッグ情報を取得できなかったみたいです...この辺りが環境依存ということみたいですね。
Wasmでバックトレースを見る方法 (パニック時限定)
console_error_panic_hookを使うことでパニック時にバックトレース(にあたるもの)をデバッグコンソールで見れます!
以下を初期化の最初に置いておく必要があります。
console_error_panic_hook::set_once();
ただ、 std::backtrace::Backtrace が有効になるというわけではなく、 anyhow::Error などとの相性は悪そうです...(せっかくanyhowはWasmでも使えるのに...)
多分他のランタイムを使ったりすればWebAssemblyでも std::backtrace::Backtrace の詳細を見られるかもしれませんが、筆者があまり詳しくないのと今回の目的はサポートされていないプラットフォームの確認なのでこれ以上の検証は省略します ![]()
それとまたまたダイマなのですがhooqマクロならWebAssemblyでもエラートレースもどきを取得できます!
- 【Rust】anyhowで実行時エラー発生行やスタックトレース的なものを取得する方法2選 #hooq - Qiita
- 【Rust】hooq属性マクロで?演算子(旧tryマクロ)の前にメソッドをフック(挿入)する #macro - Qiita
記事としてはまとめていませんが、動くことは実証済みなのでぜひ使ってみてほしいです!
まとめ・所感
というわけで、 hooqアドベントカレンダー 19日目の記事でした!
hooqはメソッドを ? 演算子にフックする属性マクロです。本アドカレではhooqの使い方を始め、hooqマクロを作成するにあたり得た知識、Rustのエラーハンドリング・エラーロギング周りの話をまとめています。
昨日と本日の記事を通して示した通り std::backtrace::Backtrace はある意味hooqのライバルであるところのバックトレース系列技術の根幹の一つであるため、この機会に色々知ることができてよかったです!
ここまで読んでいただきありがとうございました!
-
ELF(エルフ)だからDWARF(ドワーフ)という洒落のようです。 ↩
