2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

私は std::backtrace::Backtrace がなんとなく1嫌で、その「なんとなく」だけで8カ月ほど時間をかけて hooq という属性マクロを作ってしまいました...

このままでは良くないので、ちゃんと触っておきたいと思います。というわけで、 std::backtrace::Backtrace に関する諸々をまとめました!

Backtrace の使い方はシンプル!

std::backtraceを読んだ感じ、Backtrace 自体はすごくシンプルで特にエラー等とは関係なく、シンプルにスタックバックトレースを得る機能のようです。

モジュールトップに説明が書かれていますが特に知っておくべき情報は以下ぐらいでした2:

  • OSスレッドに依存
    • プラットフォームによってはキャプチャできない
    • 利用可能かは Backtrace::status メソッドで確認できる
  • 基本的には、環境変数でキャプチャされるかを管理
    • Backtrace::force_capture は無関係にキャプチャ
    • Backtrace::capture は以下に従ってキャプチャを決定
      • RUST_LIB_BACKTRACE0 ならキャプチャしない。そのほかの値 ( 1 とか full とか任意) ならキャプチャ
      • RUST_LIB_BACKTRACE が設定されていない時、 RUST_BACKTRACE 変数で上記と同様の判定
      • 両方とも設定されていない時、キャプチャは行わない

使用例は記載されていませんでしたが、シンプルな例は次のような感じでしょう。stableで使える主要APIについては以下のソースコードで大体網羅しています。

src/main.rs
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.06s
     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
   ... 以降省略 ...
出力例省略なし
出力例
$ RUST_BACKTRACE=1 cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s
     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

パニック時に得られるバックトレースと似た出力が得られましたね。有益な情報はトレース序盤の最初の行の方に固まっていそうです。

おまけ: nightlyで使えるBacktrace::framesも試してみた

#![feature(backtrace_frames)] を入れてnightly付きで実行すると Backtrace::frames というメソッドにアクセスできます。

名前通りバックトレースのフレーム(0: backtrace_pg::func1 at ./src/main.rs:4:5 みたいな単位)をスライスで得られるようです。

src/main.rs
#![feature(backtrace_frames)]

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);

    for (i, frame) in bt.frames().iter().enumerate() {
        println!("Frame {}: {:?}", i, frame);
    }
}
出力例
出力例
$ RUST_BACTRACE=1 cargo +nightly run
   Compiling backtrace_frames_pg v0.1.0 (/home/namn/workspace/qiita_adv_articles_2025/programs/adv18/backtrace_frames_pg)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.54s
     Running `target/debug/backtrace_frames_pg`
Status: Disabled
---
disabled backtrace

programs/adv18/backtrace_frames_pg on  main [!?] is 📦 v0.1.0 via 🦀 v1.91.1 
❯ RUST_BACKTRACE=1 cargo +nightly run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/backtrace_frames_pg`
Status: Captured
---
   0: backtrace_frames_pg::func1
             at ./src/main.rs:6:5
   1: backtrace_frames_pg::func2
             at ./src/main.rs:10:5
   2: backtrace_frames_pg::func3
             at ./src/main.rs:14:5
   3: backtrace_frames_pg::main
             at ./src/main.rs:18:14
   4: <fn() as core::ops::function::FnOnce<()>>::call_once
             at /home/namn/.rustup/toolchains/nightly-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::<fn(), ()>
             at /home/namn/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/backtrace.rs:158:18
   6: std::rt::lang_start::<()>::{closure#0}
             at /home/namn/.rustup/toolchains/nightly-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/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/core/src/ops/function.rs:287:21
   8: std::panicking::catch_unwind::do_call
             at /rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panicking.rs:582:40
   9: std::panicking::catch_unwind
             at /rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panicking.rs:545:19
  10: std::panic::catch_unwind
             at /rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panic.rs:359:14
  11: std::rt::lang_start_internal::{{closure}}
             at /rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/rt.rs:175:24
  12: std::panicking::catch_unwind::do_call
             at /rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panicking.rs:582:40
  13: std::panicking::catch_unwind
             at /rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panicking.rs:545:19
  14: std::panic::catch_unwind
             at /rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panic.rs:359:14
  15: std::rt::lang_start_internal
             at /rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/rt.rs:171:5
  16: std::rt::lang_start::<()>
             at /home/namn/.rustup/toolchains/nightly-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

Frame 0: [{ fn: "std::backtrace_rs::backtrace::libunwind::trace", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/../../backtrace/src/backtrace/libunwind.rs", line: 117 }, { fn: "std::backtrace_rs::backtrace::trace_unsynchronized", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/../../backtrace/src/backtrace/mod.rs", line: 66 }, { fn: "std::backtrace::Backtrace::create", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/backtrace.rs", line: 331 }]
Frame 1: [{ fn: "std::backtrace::Backtrace::capture", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/backtrace.rs", line: 296 }]
Frame 2: [{ fn: "backtrace_frames_pg::func1", file: "./src/main.rs", line: 6 }]
Frame 3: [{ fn: "backtrace_frames_pg::func2", file: "./src/main.rs", line: 10 }]
Frame 4: [{ fn: "backtrace_frames_pg::func3", file: "./src/main.rs", line: 14 }]
Frame 5: [{ fn: "backtrace_frames_pg::main", file: "./src/main.rs", line: 18 }]
Frame 6: [{ fn: "<fn() as core::ops::function::FnOnce<()>>::call_once", file: "/home/namn/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs", line: 250 }]
Frame 7: [{ fn: "std::sys::backtrace::__rust_begin_short_backtrace::<fn(), ()>", file: "/home/namn/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/backtrace.rs", line: 158 }]
Frame 8: [{ fn: "std::rt::lang_start::<()>::{closure#0}", file: "/home/namn/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs", line: 206 }]
Frame 9: [{ fn: "core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/core/src/ops/function.rs", line: 287 }, { fn: "std::panicking::catch_unwind::do_call", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panicking.rs", line: 582 }, { fn: "std::panicking::catch_unwind", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panicking.rs", line: 545 }, { fn: "std::panic::catch_unwind", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panic.rs", line: 359 }, { fn: "std::rt::lang_start_internal::{{closure}}", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/rt.rs", line: 175 }, { fn: "std::panicking::catch_unwind::do_call", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panicking.rs", line: 582 }, { fn: "std::panicking::catch_unwind", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panicking.rs", line: 545 }, { fn: "std::panic::catch_unwind", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/panic.rs", line: 359 }, { fn: "std::rt::lang_start_internal", file: "/rustc/27b076af7e3e7a363975443d81dfa9ecee5a74ec/library/std/src/rt.rs", line: 171 }]
Frame 10: [{ fn: "std::rt::lang_start::<()>", file: "/home/namn/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs", line: 205 }]
Frame 11: [{ fn: "main" }]
Frame 12: []
Frame 13: [{ fn: "__libc_start_main" }]
Frame 14: [{ fn: "_start" }]
Frame 15: []

ただBacktraceFrameデバッグ出力以外の能力がないようなので需要は不明です。

せめてファイル名や行番号のAPIは晒してほしい...

なんでバックトレースを管理する環境変数が2つあるの?

ドキュメントには書いておらず筆者もChatGPTに言われて理解できたのですが、以下のモチベがあるようです。

  • モチベ1: 「パニック時のバックトレース出力」と「 std::backtrace::Backtrace 由来のバックトレース出力」の制御を別々にしたい
  • モチベ2: とはいえ両方出力したい時に両方指定するのは面倒

パニック時のバックトレース出力は RUST_BACKTRACE のみに依存し後付けされた RUST_LIB_BACKTRACE には影響されないことを利用し、以下のように出力を制御できます。

  • パニック時も Backtrace でも出力したい: RUST_BACKTRACE=1
  • Backtrace 分だけ出力しパニック時は出力しない: RUST_LIB_BACKTRACE=1
  • パニック時だけ出力し Backtrace 分は出力しない: RUST_LIB_BACKTRACE=0 & RUST_BACKTRACE=1

なるほどこれなら細かいニーズに対応できそうです!

まとめ・所感

というわけで、 hooqアドベントカレンダー 18日目の記事でした!

色々調べたのですが、記事に起こすのは間に合わなかったので明日も引き続きBacktraceについてまとめたいと思います(まとめました)。多分hooqアドベントカレンダー中で一番内容が薄い記事だったかも :sweat_smile:

ともかく、ここまで読んでいただきありがとうございました!

  1. 具体的には、表示が見辛い(color-eyreで改善)、比較的重い処理らしくそのせいでいちいち環境変数を用意するのが煩雑、非同期だとうまく機能しなさそう、といった理由ですが、ちゃんと検証したわけではないので、「なんとなく」です。

  2. 他の「正確性」や「プラットフォームサポート」はFYIではありますが本筋とは関係なさそうなので本記事ではまとめません。興味がある方は読んでみるとよさそうです。

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?