Skip to content

User Guide Debugging and Inspection

Zakaria Madaoui edited this page Aug 17, 2026 · 2 revisions

Debugging and Inspection

RTICX applications are written with a set of attributes (#[<distro>::app], #[task], #[sw_task], …) that are expanded into a large amount of generated Rust code. The rticx-expand cargo tool expands an application into a plain, compilable Rust file so you can:

  • Step-debug with GDB through the generated code (entry point, dispatchers, interrupt handlers, resource proxies) instead of the attribute syntax.
  • Inspect and security-vet exactly what the framework generates: no macro magic left between you and the code that actually runs on the device.
  • Debug your own compilation passes by dumping the intermediate syntax after each pipeline stage (distribution developers, see Writing Compilation Passes).

Install

cargo install rticx-expand
# or from local source
cargo install --path path/to/rticx-expand

This installs the cargo-rticx-expand binary, invocable as cargo rticx-expand ….

Expand an application

Run from your application crate (the one whose Cargo.toml depends on the distribution):

cargo rticx-expand --example hello_rtic --features swtasks

Like cargo expand, this prints the expanded application to stdout. The output is the complete, compilable file: the expanded module spliced into your original source, so everything around it (#![no_std], #![no_main], imports, statics, and any code after the module) is preserved. Pipe it into a file and it stays executable:

cargo rticx-expand --example hello_rtic --features swtasks > expanded.rs

Merge the expansion into your source (GDB step-debugging)

To debug the generated code with GDB, replace the #[…::app] module in your source file with the expansion:

cargo rticx-expand --example hello_rtic --features swtasks --merge

The tool finds the file containing the #[<distro>::app] module automatically (or use --file if your app module lives in a separate file), keeps the original as <file>.old, and replaces the attribute and its module with the expanded code: all other code in the file (#![no_std], imports, statics, trailing items) stays untouched. The resulting file is a plain Rust file: set your GDB breakpoints anywhere in it and build/flash as usual.

The tool runs cargo check and reports its result, but the merge happens even when checking fails (for example because of an error in your task bodies), the expansion is still useful for inspection. If the macro itself panics or the crate fails to compile before reaching it, no expansion can be produced; the tool prints the cargo output and leaves your sources untouched.

Passes through to the build

Any build flag you would pass to cargo check works:

cargo rticx-expand --bin hello_rtic --target thumbv7m-none-eabi \
    --features swtasks --no-default-features

When multiple targets exist

If the package has several binaries/examples, specify the one to expand with --bin <name> or --example <name>.

Restore your original source

When you are done debugging after a --merge, use:

cargo rticx-expand restore

This replaces the merged file with its .old backup (auto-detected; use --file to disambiguate) and, with --remove-expansions, deletes the expansion directory. You can then rebuild the original RTICX sources.

Per-stage snapshots (pass/distribution developers)

Snapshot the module after every pipeline stage (each compilation pass and the core pass) into a directory of your choice:

cargo rticx-expand --example hello_rtic --features swtasks --expand-passes target/passes

The directory receives one file per stage, named so lexical order equals pipeline order:

target/passes/00_original.rs        # the module exactly as you wrote it
target/passes/01_SoftwareTasks.rs   # after the software tasks pass
target/passes/02_core.rs            # after the core pass (the final expansion)

Now point your diff tool at consecutive files to see exactly what each stage changed: no need to re-run with different pass selections:

diff -u target/passes/00_original.rs target/passes/01_SoftwareTasks.rs   # what the pass did
diff -u target/passes/01_SoftwareTasks.rs target/passes/02_core.rs       # what the core pass did
# or compare the whole chain side by side:
meld target/passes
File Contents
00_original.rs The module exactly as received, before any pass
NN_<Pass>.rs Module state after the pass at pipeline position N
NN_<Pass>_input.rs State the failing pass received (only when a pass errors)
NN_core.rs The full, final expansion after the core pass
NN_post_passes.rs State after all passes (only when core parsing fails)

Clone this wiki locally