컴파일러 디버깅
이 장에서는 컴파일러를 디버깅하는 몇 가지 팁을 소개합니다. 이 팁들은 어떤 작업을 하든 유용하도록 작성되었습니다. 다른 몇몇 장에서는 컴파일러의 특정 부분에 관한 조언을 다룹니다(예: 쿼리 디버깅 및 테스트 장이나 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
NOTE: 이 작업은 디스크 공간을 많이( 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)를 만나면 현재 작업 디렉터리에 rustc-ice-<timestamp>-<pid>.txt라는 이름의 ICE 파일을 만들고 ICE 내용을 덤프합니다. 이를 원하지 않는다면 RUSTC_ICE=0으로 ICE 파일이 생성되지 않도록 할 수 있습니다.
백트레이스 얻기
ICE(컴파일러 내부의 패닉)가 발생하면 일반 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를 사용하십시오.
유용한 플래그 중 하나는 -Z verbose-internals로, 대체로 디버깅에 유용할 수 있는 정보를 더 많이 출력하도록 합니다.
바로 아래에서 그중 몇 가지를 골라 자세히 설명합니다.
오류의 백트레이스 얻기
컴파일러가 오류 메시지를 출력하는 지점까지의 백트레이스를 얻으려면 -Z treat-err-as-bug=n을 전달하면 됩니다. 이렇게 하면 컴파일러가 nth 오류에서 패닉합니다. =n을 생략하면 컴파일러는 n을 1로 간주하므로, 처음 만나는 오류에서 패닉합니다.
예를 들면 다음과 같습니다.
$ 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
좋습니다. 이제 오류의 백트레이스를 얻었습니다!
지연된 버그 디버깅하기
-Z eagerly-emit-delayed-bugs 옵션을 사용하면 지연된 버그를 쉽게 디버깅할 수 있습니다. 이 옵션은 지연된 버그를 일반 오류로 바꿉니다. 즉, 눈에 보이게 만듭니다. 이 옵션을 -Z treat-err-as-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 도구를 사용하면 어떤 PR 때문에 rustc의 동작이 바뀌었는지 빠르고 쉽게 정확히 찾아낼 수 있습니다. 이 도구는 rustc PR 산출물을 자동으로 내려받아 회귀를 찾을 때까지 사용자가 제공한 프로젝트에 대해 테스트합니다. 그런 다음 해당 PR을 살펴보면 변경된 이유에 관한 맥락을 더 얻을 수 있습니다. 사용 방법은 이 튜토리얼을 참고하십시오.
Rust CI에서 산출물 내려받기
kennytm이 만든 rustup-toolchain-install-master 도구를 사용하면 Rust CI가 특정 SHA1에 대해 생성한 산출물을 다운로드한 뒤 로컬에서 사용할 수 있는 툴체인으로 설정할 수 있습니다. 이 SHA1은 기본적으로 어떤 PR이 성공적으로 병합된 시점에 해당합니다. 이 방법은 @bors try로 생성된 산출물에도 적용됩니다. 이 도구는 직접 빌드하지 않고 PR의 빌드 결과물을 살펴보고 싶을 때 유용합니다.
#[rustc_*] 테스트 속성
컴파일러는 수많은 내부(영구 불안정) 속성을 정의합니다. 그중 일부는 컴파일러 내부 정보를 추가로 덤프해 주므로 디버깅에 유용합니다. 이 속성들에는 rustc_ 접두사가 붙으며, 내부 기능 rustc_attrs로 게이트되어 있습니다(예: #![feature(rustc_attrs)]로 활성화합니다).
완전한 최신 목록은 builtin_attrs를 참고하십시오. 더 구체적으로는 TEST로 표시된 항목들입니다. 주목할 만한 몇 가지는 다음과 같습니다.
| 속성 | 설명 |
|---|---|
rustc_dump_def_parents | 특정 정의들의 DefId 부모 체인을 덤프합니다. |
rustc_dump_def_path | 항목의 def_path_str을 덤프합니다. |
rustc_dump_generics | 항목의 제네릭을 덤프합니다. |
rustc_dump_hidden_type_of_opaques | 크레이트에 있는 각 불투명 타입의 숨겨진 타입을 덤프합니다. |
rustc_dump_inferred_outlives | 항목의 암시적 바운드를 덤프합니다. 더 정확히 말하면, 항목의 inferred_outlives_of를 덤프합니다. |
rustc_dump_item_bounds | 항목의 item_bounds를 덤프합니다. |
rustc_dump_layout | 이 절을 참고하십시오. |
rustc_dump_object_lifetime_defaults | 항목의 객체 라이프타임 기본값을 덤프합니다. |
rustc_dump_clauses | 항목의 clauses_of를 덤프합니다. |
rustc_dump_symbol_name | 항목의 맹글링된 & 디맹글링된 symbol_name을 덤프합니다. |
rustc_dump_variances | 항목의 변성을 덤프합니다. |
rustc_dump_vtable | impl 또는 dyn 타입의 타입 별칭에 대한 vtable 레이아웃을 덤프합니다. |
rustc_regions | NLL 클로저 영역 요구 사항을 덤프합니다. |
바로 아래에서 그중 몇 가지에 대한 자세한 설명을 확인할 수 있습니다.
Graphviz 출력(.dot 파일) 서식 지정
특정 기능을 디버깅하기 위한 일부 컴파일러 옵션은 graphviz 그래프를 생성합니다. 예를 들어 함수에 #[rustc_mir(borrowck_graphviz_postflow="suffix.dot")] 속성을 붙이고 -Zdump-mir-dataflow와 함께 사용하면 다양한 빌림 검사기 데이터 흐름 그래프를 덤프합니다.
이 옵션들은 모두 .dot 파일을 생성합니다. 이 파일을 보려면 graphviz를 설치한 다음(예: apt-get install graphviz) 다음 명령을 실행하십시오.
$ dot -T pdf maybe_init_suffix.dot > maybe_init_suffix.pdf
$ firefox maybe_init_suffix.pdf # Or your favorite pdf viewer
Graphviz에는 전처리기 프로그램인 unflatten도 함께 제공되며, 이 프로그램은 출력이 이상하게 흩어져 보이는 현상을 줄이는 데 도움이 되기도 합니다. 이 프로그램은 dot 파일을 읽어 다른 dot 파일을 출력하므로, 다음과 같이 파이프에서 사용할 수 있습니다.
$ unflatten mir_dump/*.foo.-------.nll.0.regioncx.all.dot | dot -Tpdf -o foo-outlives.pdf
이는 빌림 검사기에서 나오는 복잡한 영역 outlives 그래프에 특히 유용합니다.
온라인 Graphviz 편집기 및 시각화 도구도 이용할 수 있습니다.
회귀 범위 좁히기(이분 탐색)
내부 속성 #[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": [], // array of string command-line arguments to pass to compiler
"program": "${workspaceFolder}/build/host/stage1/bin/rustc",
"windows": { // applicable if using windows
"program": "${workspaceFolder}/build/host/stage1/bin/rustc.exe"
},
"cwd": "${workspaceFolder}", // current working directory at program start
"stopOnEntry": false,
"sourceLanguages": ["rust"]
}
]
}