Skip to main content
This guide covers debugging techniques for the Rust compiler itself, from internal compiler errors (ICEs) to miscompilations and performance issues.

Types of Compiler Issues

ICE (Internal Compiler Error)

The compiler panicked - this is always a bug

Miscompilation

Incorrect code generation - produces wrong results

Compile-time Error

False positive or incorrect error message

Performance Regression

Compilation takes too long or uses too much memory

Setting Up for Debugging

1

Build with debug symbols

Configure config.toml for debugging:
[rust]
# Enable debug assertions
debug = true
debug-assertions = true
debug-assertions-std = true

# Debug info for the compiler
debuginfo-level = 1
debuginfo-level-rustc = 1
debuginfo-level-std = 1

# Backtrace on ICE
backtrace-on-ice = true

# Enable debug logging
debug-logging = true
2

Build the debug compiler

# Build stage 1 with debug info
./x.py build --stage 1

# Or use the stage 0 compiler for faster iteration
./x.py build --stage 0 library/std
3

Set up debugging tools

# Install GDB
sudo apt-get install gdb

# Set in config.toml
[build]
gdb = "gdb"

Debugging Internal Compiler Errors (ICEs)

Reproducing an ICE

1

Create a minimal test case

Create a file that triggers the ICE:
// ice.rs
fn main() {
    // Minimal code that triggers the ICE
}
2

Compile with backtrace

# Set environment variable
export RUST_BACKTRACE=1

# Compile the test case
rustc ice.rs

# Or with your stage 1 compiler
./build/x86_64-unknown-linux-gnu/stage1/bin/rustc ice.rs
3

Get detailed backtrace

# Full backtrace with RUST_BACKTRACE=full
RUST_BACKTRACE=full rustc ice.rs

# With additional logging
RUSTC_LOG=debug rustc ice.rs 2>&1 | tee debug.log

Using GDB with the Compiler

# Start rustc under GDB
gdb --args ./build/stage1/bin/rustc ice.rs

# In GDB:
(gdb) run
# Wait for crash
(gdb) backtrace
(gdb) info locals
(gdb) frame 3
(gdb) print variable_name

Debugging with LLDB

# Start rustc under LLDB
lldb ./build/stage1/bin/rustc -- ice.rs

# In LLDB:
(lldb) run
# Wait for crash
(lldb) bt
(lldb) frame variable
(lldb) frame select 3

Debug Logging

Using RUSTC_LOG

The compiler has extensive debug logging:
# Enable all debug logs
RUSTC_LOG=debug rustc file.rs

# Specific module
RUSTC_LOG=rustc_middle::ty=debug rustc file.rs

# Multiple modules
RUSTC_LOG=rustc_borrowck=debug,rustc_mir=trace rustc file.rs

Compiler Flags for Debugging

# Dump HIR
rustc -Z unpretty=hir-tree file.rs

# Dump MIR
rustc -Z dump-mir=all file.rs

# Dump MIR for specific function
rustc -Z dump-mir=main file.rs

# Dump LLVM IR
rustc --emit=llvm-ir file.rs

Debugging Specific Components

Borrow Checker

# Dump NLL facts
rustc -Z dump-mir=all -Z dump-mir-dir=mir_dump file.rs

# Polonius facts
rustc -Z polonius -Z dump-mir=all file.rs

# Borrow check logging
RUSTC_LOG=rustc_borrowck=debug rustc file.rs

Type Checker

# Type checking logs
RUSTC_LOG=rustc_hir_analysis=debug rustc file.rs

# Trait resolution
RUSTC_LOG=rustc_trait_selection=debug rustc file.rs

# Print trait solver tree
rustc -Z dump-solver-proof-tree file.rs

Code Generation

# LLVM module dump
rustc --emit=llvm-ir -C llvm-args=-print-after-all file.rs

# LLVM optimization remarks
rustc -C llvm-args="-pass-remarks=inline" file.rs

# Disable optimizations
rustc -C opt-level=0 file.rs

Debugging Tests

Running Individual Tests

1

Find the failing test

# Run UI tests
./x.py test tests/ui

# Find specific test
./x.py test tests/ui --test-args my-test
2

Run with verbose output

# Verbose test output
./x.py test tests/ui/my-test.rs --test-args --verbose

# Show compiler output
./x.py test tests/ui/my-test.rs --test-args --nocapture
3

Debug the test

# Get the rustc command from test output
# Then run it manually:
RUST_BACKTRACE=1 ./build/stage1/bin/rustc tests/ui/my-test.rs

# Or under GDB:
gdb --args ./build/stage1/bin/rustc tests/ui/my-test.rs

Updating Test Expectations

# Update stderr expectations
./x.py test tests/ui --bless

# Update specific test
./x.py test tests/ui/my-test.rs --bless

Debugging Miscompilations

Isolating the Problem

1

Create minimal reproducer

Reduce the code to the smallest example that shows the bug:
# Use cargo-minimize or manual reduction
cargo install cargo-minimize
cargo minimize --bin my-bin
2

Identify optimization level

# Test different optimization levels
rustc -C opt-level=0 file.rs  # No optimization
rustc -C opt-level=1 file.rs  # Basic
rustc -C opt-level=2 file.rs  # Default
rustc -C opt-level=3 file.rs  # Aggressive
3

Check MIR vs LLVM

# Dump MIR
rustc -Z dump-mir=all file.rs

# Dump LLVM IR
rustc --emit=llvm-ir file.rs

# Compare optimized vs unoptimized
rustc -C opt-level=0 --emit=llvm-ir -o unopt.ll file.rs
rustc -C opt-level=2 --emit=llvm-ir -o opt.ll file.rs
diff unopt.ll opt.ll
4

Bisect to find the culprit

# Use cargo-bisect-rustc to find regression
cargo install cargo-bisect-rustc
cargo bisect-rustc --start 2024-01-01 --end 2024-06-01

Tools for Miscompilations

Miri

Detect undefined behavior:
cargo +nightly miri test
cargo +nightly miri run

Sanitizers

Detect memory errors:
RUSTFLAGS="-Z sanitizer=address" cargo build
RUSTFLAGS="-Z sanitizer=memory" cargo build

Valgrind

Memory debugging:
valgrind --leak-check=full ./target/debug/binary

cargo-bisect-rustc

Find regressions:
cargo bisect-rustc

Advanced Debugging Techniques

Using LLVM Tools

# Disassemble bitcode
llvm-dis output.bc -o output.ll

Query System Debugging

# Track query execution
RUSTC_LOG=rustc_query_system::query::plumbing=debug rustc file.rs

# Query dependencies
rustc -Z dump-dep-graph file.rs

Memory Debugging

# Profile heap usage
valgrind --tool=massif ./build/stage1/bin/rustc file.rs

# Visualize with massif-visualizer
massif-visualizer massif.out.12345

Common Debugging Scenarios

# Set backtrace
export RUST_BACKTRACE=1

# Enable panic logging
export RUSTC_LOG=debug

# Run under debugger
gdb --args ./build/stage1/bin/rustc file.rs
(gdb) break rust_panic
(gdb) run
# Time each pass
rustc -Z time-passes file.rs

# Self-profile
rustc -Z self-profile file.rs

# Analyze with measureme
cargo install measureme
crox file-pid.mm_profdata
# Enable type logging
RUSTC_LOG=rustc_hir_analysis::check=debug rustc file.rs

# Dump type information
rustc -Z print-type-sizes file.rs
# Enable borrow check logging
RUSTC_LOG=rustc_borrowck=debug rustc file.rs

# Dump NLL facts
rustc -Z dump-mir=all file.rs

Debugging Checklist

1

Reproduce the issue

  • Create minimal test case
  • Verify it reproduces consistently
  • Note exact compiler version
2

Gather information

  • Get full backtrace
  • Enable debug logging
  • Check for similar issues
3

Isolate the problem

  • Identify which compiler pass fails
  • Check if it’s a recent regression
  • Test with different flags
4

Debug

  • Use appropriate debugging tool
  • Set breakpoints at key locations
  • Inspect relevant data structures
5

Fix and test

  • Implement fix
  • Add regression test
  • Verify fix doesn’t break other tests

Resources

Debugging Chapter

Rustc dev guide debugging section

GDB Tutorial

GDB documentation

LLDB Tutorial

LLDB tutorial

Zulip #t-compiler

Ask for help on Zulip

Next Steps

Profiling

Learn about profiling the compiler

Compiler Development

Return to compiler development guide

Build docs developers (and LLMs) love