Skip to content
This repository has been archived by the owner on Jan 30, 2024. It is now read-only.

[RFC] pass command line arguments to the embedded program #148

Open
japaric opened this issue Feb 15, 2021 · 1 comment
Open

[RFC] pass command line arguments to the embedded program #148

japaric opened this issue Feb 15, 2021 · 1 comment
Labels
difficulty: hard Fairly difficult to solve priority: low Low priority for the Knurling team status: needs PR Issue just needs a Pull Request implementing the changes type: enhancement Enhancement or feature request

Comments

@japaric
Copy link
Member

japaric commented Feb 15, 2021

this is a non-hacky solution to knurling-rs/defmt#260

Summary

Arguments passed to probe-run after the ELF file path are passed to the embedded program. The embedded program can retrieve those command line arguments using the probe_args::args API

$ probe-run --chip CHIP my-elf hello world
let args: &'static [&'static str] = probe_args::args();
assert_eq!(args, &["hello", "world"]);

With the ability to read command line arguments we can make defmt-test behave closer to the standard test harness and support e.g. command-line test filters.

Implementation

TL; DR probe-run will write the command line arguments in RAM below the first stack frame.
It will do this before the user code runs so no host-target communication (e.g. RTT) is required to pass the command-line flags.

(well, technically the flags will be written above (higher address) the first stack frame because the stack grows downwards in ARM Cortex-M programs)

probe-run side

Before flashing the program, probe-run will compute how much stack space is required to store the command line arguments. Using this information it will compute a "modified initial Stack Pointer" (MISP)

Example:
initial SP value in vector table = 0x2001_0000
stack space required by command line arguments = 0x100
modified initial SP (MISP) = 0x2000_FF000

probe-run will flash the firmware and then do a "reset halt". The target is now at the start of the Reset handler. At this point the target has loaded 'initial SP' from the vector table into its SP register. probe-run will write the MISP into the target's SP register.

probe-run puts a breakpoint on main (we already do this) and lets the program execute until that point. At this point the target has initialized its static variables. probe-run will write the command line arguments, along with a slice of pointers to them, into RAM and then write to the special PROBE_ARGS_POINTER and PROBE_ARGS_LENGTH static variables (see probe-args section for more details about these 2 variables)

probe-run "resumes" the target and does what it already does today (defmt logs, backtrace, etc.)

probe-args side

This is a target library (no_std code).

#[no_mangle]
static PROBE_ARGS_POINTER: AtomicU32 = AtomicU32::new(0);
#[no_mangle]
static PROBE_ARGS_LENGTH: AtomicU32 = AtomicU32::new(0);

pub fn args() -> &'static [&'static str] {
    let ptr = PROBE_ARGS_POINTER.load(Ordering::Relaxed);
    if ptr == 0 {
        &[]
    } else {
        let len = PROBE_ARGS_LENGTH.load(Ordering::Relaxed);
        unsafe { slice::from_raw_parts(ptr as *const _, len) }
    }
}

Runtime semantics:

  • when the program runs "outside" probe-run, probe_args::args returns an empty slice and the target uses the normal initial stack pointer (the one stored in the vector table)

  • when the program runs "within" probe-run, probe_args::args returns probe-run's command line arguments and the target uses the modified initial stack pointer value.

Prior art

From what I remember the Linux ELF loader works a bit like this. It writes command line arguments below the first stack frame. Above that data and just below the first stack frame it writes the pointer to those arguments (argv) and the number of arguments (argc). The real entry point (the one in libc) of the ELF program, written in assembly, retrieves argv and argc from the stack (they are at $SP - 16) and passes them to the user entry point ("main").

Known limitations

  • probe_args::args will always return an empty slice when invoked after a software reset. This is because cortex-m-rt will zero PROBE_ARGS_POINTER (before user main runs) but probe-run will not set PROBE_ARGS_POINTER on software reset.

Edits

  • 2021-02-15 I originally wanted to put the embedded program arguments after a -- (e.g. probe-run --chip CHIP my-elf -- command line arguments) but this is not compatible with how Rust Analyzer cargo test passes arguments to the Cargo runner; it passes e.g. --exact, capture right after the ELF file path.
@japaric
Copy link
Member Author

japaric commented Feb 15, 2021

After writing all this I realized we can easily fix knurling-rs/defmt#260 w/o doing any of this: we can make probe-run drop all arguments after the ELF path. That quick fix would still be compatible with this RFC.

@jonas-schievink jonas-schievink added difficulty: hard Fairly difficult to solve priority: low Low priority for the Knurling team status: needs PR Issue just needs a Pull Request implementing the changes type: enhancement Enhancement or feature request labels Feb 23, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
difficulty: hard Fairly difficult to solve priority: low Low priority for the Knurling team status: needs PR Issue just needs a Pull Request implementing the changes type: enhancement Enhancement or feature request
Projects
None yet
Development

No branches or pull requests

3 participants