Skip to main content
Jolt provides several debugging tools to help you troubleshoot issues in your zkVM guest programs.

Backtrace Support

Tracer, Jolt’s emulator, supports backtraces for panics that occur in guest programs. This is essential for debugging runtime errors.
By default, symbols are stripped from release guest ELFs, so backtraces won’t contain detailed information. Debug/dev builds preserve symbols automatically.

JOLT_BACKTRACE Environment Variable

The JOLT_BACKTRACE environment variable enables ad-hoc debugging by preserving symbols and enabling backtrace support.

Basic Backtrace

Set JOLT_BACKTRACE=1 to enable symbol resolution (function names, file:line):
JOLT_BACKTRACE=1 cargo run --release -p example
The guest program auto-rebuilds with symbols preserved. The call stack is always captured; this flag just enables symbol resolution.
Use JOLT_BACKTRACE=1 for most debugging scenarios. It provides function names and source locations without rebuilding your entire project.

Full Backtrace

For more detailed debugging information including register snapshots and cycle counts per frame, use JOLT_BACKTRACE=full:
JOLT_BACKTRACE=full cargo run --release -p example

Backtrace Attribute in #[jolt::provable]

For guest programs where you always want full debug support, you can bake symbol preservation into the build using the backtrace attribute:
#[jolt::provable(backtrace = "dwarf")]
fn my_function(input: u64) -> u64 {
    // Your code here
}
This configuration:
  • Preserves symbols in the guest ELF
  • Adds -Cforce-frame-pointers=yes to the build
  • Is useful for test programs, dedicated debug builds, or when you need frame pointers for ZeroOS-level unwinding
The backtrace attribute is not needed for normal debugging — JOLT_BACKTRACE=1 is sufficient for most cases.

Valid Backtrace Values

The backtrace attribute accepts the following values:
  • "off" — Disable backtrace support
  • "dwarf" — Full DWARF debug info with frame pointers
  • "frame-pointers" — Frame pointers only

Manual Symbol Control

You can also control symbol preservation directly via the CLI:
jolt build --backtrace enable

Printing and Tracing

Jolt supports standard print! and println! macros in guest programs for debugging output.

Using println! in Guest Programs

no_std Guests

For no_std guests, import the macros via:
use jolt::println;

#[jolt::provable]
fn my_function(input: u64) -> u64 {
    println!("Input value: {}", input);
    // Your code here
}

std Guests

When std is enabled, the standard println! works automatically:
#[jolt::provable]
fn my_function(input: u64) -> u64 {
    println!("Input value: {}", input);
    // Your code here
}

Fast Iteration with trace_analyze

When debugging issues with guest programs, use the corresponding trace_analyze function for your #[jolt::provable] functions. This approach:
  • Skips instantiating the prover
  • Allows for faster iteration during debugging
  • Executes the guest program in the tracer without generating a proof
// Instead of running the full prover
let (output, proof, io_device) = prove_fib(50);

// Use trace_analyze for faster debugging
let (output, io_device) = trace_analyze_fib(50);
Use trace_analyze during development to quickly test your guest program logic without the overhead of proof generation.

Debugging Workflow

  1. Add print statements to understand program flow
  2. Use JOLT_BACKTRACE=1 to get detailed panic information
  3. Use trace_analyze for fast iteration without proving
  4. Switch to full proving once the logic is correct
The tracer doesn’t currently support attaching a debugger. Use print statements and backtraces for debugging instead.

Common Debugging Scenarios

Debugging Panics

# Run with backtrace enabled
JOLT_BACKTRACE=1 cargo run --release -p my-guest

# For detailed information
JOLT_BACKTRACE=full cargo run --release -p my-guest

Debugging Logic Issues

// Add print statements in your guest code
#[jolt::provable]
fn my_function(input: u64) -> u64 {
    println!("Starting computation with input: {}", input);
    let result = complex_calculation(input);
    println!("Result: {}", result);
    result
}

// Use trace_analyze for fast testing
let (output, io_device) = trace_analyze_my_function(42);

Debugging Performance Issues

Combine debugging with profiling tools:
# Profile with debug symbols
JOLT_BACKTRACE=1 cargo run --release --features monitor -p jolt-core profile --name myprogram --format chrome

Build docs developers (and LLMs) love