コンパイラのデバッグ
この章では、コンパイラをデバッグするためのヒントをいくつか紹介します。 これらのヒントは、何に取り組んでいるかに関係なく役立つことを目指しています。 他の章の中には、 コンパイラの特定の部分に関するアドバイスがあります(例: クエリのデバッグと テストの章や LLVM デバッグの 章)。
コンパイラの設定
デフォルトでは、rustc はほとんどのデバッグ情報なしでビルドされます。
デバッグ情報を有効にするには、
bootstrap.toml で rust.debug = true を設定してください。
rust.debug = true を設定すると、多くの異なるデバッグオプション(例: debug-assertions、
debug-logging など)が有効になります。必要であれば個別に調整できますが、多くの人は
単に rust.debug = true を設定します。
GDB を使用して rustc をデバッグしたい場合は、bootstrap.toml に次のオプションを設定してください。
rust.debug = true
rust.debuginfo-level = 2
注: これは大量のディスク容量を使用します ( 35GB 以上)し、 コンパイル時間も大幅に長くなります。
debuginfo-level = 1(debug = trueのときのデフォルト)では、 実行パスを追跡できますが、 デバッグ用のシンボル情報は失われます。
デフォルト設定では、symbol-mangling-version v0 が有効になります。
これには少なくとも GDB v10.2 が必要です。
そうでない場合は、bootstrap.toml で新しい symbol-mangling-version を無効にする必要があります。
rust.new-symbol-mangling = false
詳細については、
bootstrap.example.tomlのコメントを参照してください。
設定オプションを変更した後は、コンパイラをリビルドする必要があります。
ICE ファイルの抑制
デフォルトでは、rustc が Internal Compiler Error(ICE)に遭遇すると、ICE の内容を
rustc-ice-<timestamp>-<pid>.txt という名前の ICE ファイルとして現在の作業ディレクトリにダンプします。
これが望ましくない場合は、RUSTC_ICE=0 を使用して ICE ファイルが作成されないようにできます。
バックトレースの取得
ICE(コンパイラ内の panic)が発生した場合、通常の Rust プログラムと同様に
RUST_BACKTRACE=1 を設定して panic! のスタックトレースを取得できます。
記憶が正しければ、MinGW ではバックトレースは 機能しません。申し訳ありません。
問題がある場合やバックトレースが unknown だらけの場合は、
Linux、Mac、または Windows 上の MSVC を使用する方法を探した方がよいかもしれません。
デフォルト設定(debug が true に設定されていない場合)では、行番号が
有効になっていないため、バックトレースは次のようになります。
stack backtrace:
0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace
1: std::sys_common::backtrace::_print
2: std::panicking::default_hook::{{closure}}
3: std::panicking::default_hook
4: std::panicking::rust_panic_with_hook
5: std::panicking::begin_panic
(~~~~ LINES REMOVED BY ME FOR BREVITY ~~~~)
32: rustc_typeck::check_crate
33: <std::thread::local::LocalKey<T>>::with
34: <std::thread::local::LocalKey<T>>::with
35: rustc::ty::context::TyCtxt::create_and_enter
36: rustc_driver::driver::compile_input
37: rustc_driver::run_compiler
debug = true を設定すると、スタックトレースに行番号が表示されます。
その場合、バックトレースは次のようになります。
stack backtrace:
(~~~~ LINES REMOVED BY ME FOR BREVITY ~~~~)
at /home/user/rust/compiler/rustc_typeck/src/check/cast.rs:110
7: rustc_typeck::check::cast::CastCheck::check
at /home/user/rust/compiler/rustc_typeck/src/check/cast.rs:572
at /home/user/rust/compiler/rustc_typeck/src/check/cast.rs:460
at /home/user/rust/compiler/rustc_typeck/src/check/cast.rs:370
(~~~~ LINES REMOVED BY ME FOR BREVITY ~~~~)
33: rustc_driver::driver::compile_input
at /home/user/rust/compiler/rustc_driver/src/driver.rs:1010
at /home/user/rust/compiler/rustc_driver/src/driver.rs:212
34: rustc_driver::run_compiler
at /home/user/rust/compiler/rustc_driver/src/lib.rs:253
-Z フラグ
コンパイラには多数の -Z * フラグがあります。
これらは nightly でのみ有効になる不安定なフラグです。
その多くはデバッグに役立ちます。
-Z フラグの完全な一覧を取得するには、-Z help を使用してください。
便利なフラグの 1 つに -Z verbose-internals があります。これは一般に、デバッグに役立つ可能性がある
情報をより多く出力できるようにします。
すぐ下に、選択したいくつかのフラグについて詳しい解説があります。
エラーのバックトレースの取得
コンパイラがエラーメッセージを出力する箇所までのバックトレースを取得したい場合は、
-Z treat-err-as-bug=n を渡すことができます。これにより、
コンパイラは n 番目のエラーで panic します。
=n を省略した場合、コンパイラは
n として 1 を想定するため、最初に遭遇したエラーで panic します。
例:
cat error.rs
fn main() {
1 + ();
}
$ rustc +stage1 error.rs
error[E0277]: cannot add `()` to `{integer}`
--> error.rs:2:7
|
2 | 1 + ();
| ^ no implementation for `{integer} + ()`
|
= help: the trait `Add<()>` is not implemented for `{integer}`
error: aborting due to previous error
では、上記のエラーはどこから発生しているのでしょうか?
$ RUST_BACKTRACE=1 rustc +stage1 error.rs -Z treat-err-as-bug
error[E0277]: the trait bound `{integer}: std::ops::Add<()>` is not satisfied
--> error.rs:2:7
|
2 | 1 + ();
| ^ no implementation for `{integer} + ()`
|
= help: the trait `std::ops::Add<()>` is not implemented for `{integer}`
error: internal compiler error: unexpected panic
note: the compiler unexpectedly panicked. this is a bug.
note: we would appreciate a bug report: https://github.com/rust-lang/rust/blob/HEAD/CONTRIBUTING.md#bug-reports
note: rustc 1.24.0-dev running on x86_64-unknown-linux-gnu
note: run with `RUST_BACKTRACE=1` for a backtrace
thread 'rustc' panicked at 'encountered error with `-Z treat_err_as_bug',
/home/user/rust/compiler/rustc_errors/src/lib.rs:411:12
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose
backtrace.
stack backtrace:
(~~~ IRRELEVANT PART OF BACKTRACE REMOVED BY ME ~~~)
7: rustc::traits::error_reporting::<impl rustc::infer::InferCtxt<'a, 'tcx>>
::report_selection_error
at /home/user/rust/compiler/rustc_middle/src/traits/error_reporting.rs:823
8: rustc::traits::error_reporting::<impl rustc::infer::InferCtxt<'a, 'tcx>>
::report_fulfillment_errors
at /home/user/rust/compiler/rustc_middle/src/traits/error_reporting.rs:160
at /home/user/rust/compiler/rustc_middle/src/traits/error_reporting.rs:112
9: rustc_typeck::check::FnCtxt::select_obligations_where_possible
at /home/user/rust/compiler/rustc_typeck/src/check/mod.rs:2192
(~~~ IRRELEVANT PART OF BACKTRACE REMOVED BY ME ~~~)
36: rustc_driver::run_compiler
at /home/user/rust/compiler/rustc_driver/src/lib.rs:253
よし、これでエラーのバックトレースを取得できました!
delayed bug のデバッグ
-Z eagerly-emit-delayed-bugs オプションを使うと、delayed bug を簡単にデバッグできます。
これは delayed bug を通常のエラーに変換します。つまり、可視化します。これは
-Z treat-err-as-bug と組み合わせて使用することで、特定の delayed bug で停止し、バックトレースを取得できます。
エラー作成場所を取得する
-Z track-diagnostics は、エラーがどこで出力されたかを特定するのに役立ちます。
これはそのために #[track_caller] を使用し、エラーとともにその場所を出力します。
$ RUST_BACKTRACE=1 rustc +stage1 error.rs -Z track-diagnostics
error[E0277]: cannot add `()` to `{integer}`
--> src\error.rs:2:7
|
2 | 1 + ();
| ^ no implementation for `{integer} + ()`
-Ztrack-diagnostics: created at compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs:638:39
|
= help: the trait `Add<()>` is not implemented for `{integer}`
= help: the following other types implement trait `Add<Rhs>`:
<&'a f32 as Add<f32>>
<&'a f64 as Add<f64>>
<&'a i128 as Add<i128>>
<&'a i16 as Add<i16>>
<&'a i32 as Add<i32>>
<&'a i64 as Add<i64>>
<&'a i8 as Add<i8>>
<&'a isize as Add<isize>>
and 48 others
For more information about this error, try `rustc --explain E0277`.
これは -Z treat-err-as-bug と似ていますが、異なります。
- 出力されたすべてのエラーについて場所を出力します
- デバッグシンボル付きでビルドされたコンパイラを必要としません
- 大きなスタックトレースを読み解く必要がありません。
ロギング出力を取得する
コンパイラはロギングに tracing クレートを使用します。
詳細については、tracing に関するガイドのセクションを参照してください
リグレッションの絞り込み(二分探索)
cargo-bisect-rustc ツールは、rustc の挙動の変化を引き起こした PR を正確に見つけるための、手早く簡単な方法として使用できます。
これは rustc の PR アーティファクトを自動的にダウンロードし、リグレッションが見つかるまで、あなたが提供したプロジェクトに対してそれらをテストします。
その後、その PR を確認して、なぜ 変更されたのかについてより多くの文脈を得ることができます。
使用方法については、このチュートリアルを参照してください。
Rust の CI からアーティファクトをダウンロードする
kennytm による rustup-toolchain-install-master ツールは、特定の SHA1 に対して Rust の CI によって生成されたアーティファクトをダウンロードするために使用できます。これは基本的には、ある PR が正常にマージされたことに対応します。そして、それらをローカルで使用できるようにセットアップします。
これは @bors try によって生成されたアーティファクトに対しても機能します。
これは、自分でビルドせずに PR の結果として生成されたビルドを調べたい場合に便利です。
#[rustc_*] TEST 属性
コンパイラは、大量の内部(永続的に unstable な)属性を定義しており、その一部はコンパイラ内部の追加情報をダンプすることでデバッグに役立ちます。
これらには rustc_ というプレフィックスが付けられており、内部 feature である rustc_attrs によってゲートされています(例: #![feature(rustc_attrs)] で有効化)。
完全かつ最新の一覧については、builtin_attrs を参照してください。
より具体的には、TEST とマークされているものです。
注目すべきものをいくつか示します。
| 属性 | 説明 |
|---|---|
rustc_dump_def_parents | 特定の定義の DefId 親の連鎖をダンプします。 |
rustc_dump_def_path | アイテムの def_path_str をダンプします。 |
rustc_dump_hidden_type_of_opaques | クレート内の各 opaque type の隠された型をダンプします。 |
rustc_dump_inferred_outlives | アイテムの暗黙の境界をダンプします。より正確には、アイテムの inferred_outlives_of です。 |
rustc_dump_item_bounds | アイテムの item_bounds をダンプします。 |
rustc_dump_layout | このセクションを参照してください。 |
rustc_dump_object_lifetime_defaults | アイテムの object lifetime defaults をダンプします。 |
rustc_dump_predicates | アイテムの predicates_of をダンプします。 |
rustc_dump_symbol_name | アイテムのマングル済みおよびデマングル済みの symbol_name をダンプします。 |
rustc_dump_variances | アイテムの variances をダンプします。 |
rustc_dump_vtable | impl、または dyn 型の型エイリアスの vtable レイアウトをダンプします。 |
rustc_regions | NLL クロージャのリージョン要件をダンプします。 |
すぐ下に、選択したいくつかについての詳しい解説があります。
Graphviz 出力(.dot ファイル)の整形
特定の機能をデバッグするためのコンパイラオプションの中には、graphviz グラフを生成するものがあります。たとえば、関数に付与された #[rustc_mir(borrowck_graphviz_postflow="suffix.dot")] 属性は、-Zdump-mir-dataflow と組み合わせることで、さまざまな borrow-checker のデータフローグラフをダンプします。
これらはすべて .dot ファイルを生成します。これらのファイルを表示するには、graphviz をインストールし(例: apt-get install graphviz)、次のコマンドを実行します。
dot -T pdf maybe_init_suffix.dot > maybe_init_suffix.pdf
firefox maybe_init_suffix.pdf # またはお好みの pdf ビューアー
型レイアウトのデバッグ
内部属性 #[rustc_dump_layout(...)] は、それが付与された型の Layout をダンプするために使用できます。
例:
#![allow(unused)]
#![feature(rustc_attrs)]
fn main() {
#[rustc_dump_layout(debug)]
type T<'a> = &'a u32;
}
次の内容が出力されます。
error: layout_of(&u32) = Layout {
size: Size(8 bytes),
align: AbiAlign {
abi: Align(8 bytes),
},
backend_repr: Scalar(
Initialized {
value: Pointer(
AddressSpace(
0,
),
),
valid_range: 1..=18446744073709551615,
},
),
fields: Primitive,
largest_niche: Some(
Niche {
offset: Size(0 bytes),
value: Pointer(
AddressSpace(
0,
),
),
valid_range: 1..=18446744073709551615,
},
),
uninhabited: false,
variants: Single {
index: 0,
},
max_repr_align: None,
unadjusted_abi_align: Align(8 bytes),
randomization_seed: 281492156579847,
}
--> src/lib.rs:4:1
|
4 | type T<'a> = &'a u32;
| ^^^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error
rustc をデバッグするための CodeLLDB の設定
VSCode を使用しており、関心のあるコード部分についてデバッグレベル 1 または 2 を要求するように bootstrap.toml を編集している場合は、VSCode の CodeLLDB 拡張機能を使ってデバッグできるはずです。
以下は、stage 1 コンパイラをビルドされたディレクトリから直接実行するために使用している launch.json ファイルのサンプルです(“インストール“されている必要はありません)。
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Launch",
"args": [], // コンパイラに渡す文字列のコマンドライン引数の配列
"program": "${workspaceFolder}/build/host/stage1/bin/rustc",
"windows": { // windows を使用している場合に適用可能
"program": "${workspaceFolder}/build/host/stage1/bin/rustc.exe"
},
"cwd": "${workspaceFolder}", // プログラム開始時の現在の作業ディレクトリ
"stopOnEntry": false,
"sourceLanguages": ["rust"]
}
]
}