Skip to content

User Guide Debugging and Inspection

Zakaria Madaoui edited this page Aug 15, 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