とあるライブラリを使っていたらクラッシュはしないが情報の少ないエラーに遭遇して、コードを読んでも手がかりが掴めず困って、Rustのデバッグの仕方を調べました。
env_loggerでログを見る
コードを読んでいたら以下のようなマクロが使われていました。
trace!(...);
debug!(...);
info!(...);
warn!(...);
error!(...);
どこかに出力されていそうです。rustdoc によると、これはFacadeになっているRustのロギングの仕組みで、ロギングの実装を渡さないとすべてのログを無視するようにフォールバックされるそうです。
なので、実行ファイル側にロギングの実装を渡します。ロガーは自分で定義することも可能ですが、今回は出力を見たいだけなので rust-lang/log のenv_loggerを使います。
[dependencies]
env_logger = "*"
extern crate env_logger;
fn main() {
env_logger::init().unwrap();
実行ファイルのルートでロガーを初期化します。環境変数にログレベルを渡すことでフィルタすることができます。
$ cargo build && RUST_LOG=trace ./target/debug/xxx
これでどこまで実行できているかを知ることができました。
rust-lldbでデバッグする
だいたいの箇所が掴めたのでrust-gdbあるいはrust-lldbを使います。Macであればrust-lldbはrustcをインストールしていればそのまま使うことができます。rust-lldbの本体はシェルスクリプト (rust/rust-lldb at master · rust-lang/rust) です。
実行ファイルを指定してrust-lldbを起動します。
$ rust-lldb ./target/debug/xxx
lldbを起動して help
でコマンド一覧を見ることができます。あとはこのサイト (Practical Debugging with LLDB) が詳しかったです。
基本的にはブレークポイントを貼って、プロセスを起動して、ステップ実行して、変数の中身を見るということをします。
// 関数名を指定してブレークポイントを設定する
(lldb) breakpoint set --name access
// モジュールと関数を指定してブレークポイントを設定する
(lldb) breakpoint set --name xxx::http::access
// ファイルと行数を指定してブレークポイントを設定する
(lldb) breakpoint set --file main.rs --line 1
// 設定したブレークポイントのリストを出力する
(lldb) breakpoint list
// プロセスを起動する
(lldb) run
// ステップイン
(lldb) step
// ステップオーバー
(lldb) next
// 現在のフレームの引数やローカル変数を出力する
(lldb) frame variable x
breakpoint -> br
や frame variable -> fr v
などエイリアスがあります。また変数は型情報まで出力してくれます。
(lldb) br set -f main.rs -l 30
(lldb) r
Process 95934 stopped
26 extern crate env_logger;
27
28 fn main() {
29 let vec = vec![0, 1, 2, 3];
-> 30 ...
(lldb) fr v vec
(collections::vec::Vec<i32>) vec = vec![0, 1, 2, 3]
これでロガーとデバッガーを見ながらデバッグすることができます。