the --measure-stack feature reports the maximum stack usage of an entire program. it would be useful to have a more fine-grained report of stack usage per function. there are static analysis tools like cargo-call-stack that provide that kind of information but they rely on a nightly Rust toolchain and break often. as a complement to that tool, probe-run could report the stack usage of each function -- or more precisely the "stack depth" -- when it computes / prints a backtrace with relatively low implementation effort.
more details on the idea from a conversation:
when probe-run does (host-side) unwinding of the target's call stack it's also indirectly reading the value the SP register had at the start of each call frame. this is because it had to read the value of the Link Register (LR) that was pushed onto the stack in the prologue of each called function (+) to figure out the call site (PC value) of the function is currently unwinding.
in theory, it would be possible to include this information in the backtrace so each backtrace frame includes the "stack depth".
you can glimpse at the SP information right now by looking at probe-run logs but it's going to non-obvious to figure out which frame the SP value refers to.
for example, given this program
#[cortex_m_rt::entry]
fn main() -> ! {
a();
loop {}
}
#[inline(never)]
fn a() {
let mut bytes = [MaybeUninit::<u8>::uninit(); 4];
// "use" the stack
black_box(&mut bytes.as_mut_ptr());
b();
}
#[inline(never)]
fn b() {
let mut bytes = [MaybeUninit::<u8>::uninit(); 12];
black_box(&mut bytes.as_mut_ptr());
c();
}
#[inline(never)]
fn c() {
let mut bytes = [MaybeUninit::<u8>::uninit(); 20];
black_box(&mut bytes.as_mut_ptr());
unsafe { asm!("BKPT") } // exit
}
fn black_box<T>(x: &mut T) {
unsafe { asm!("// {0}", in(reg) x as *mut T as usize) }
}
register # 14 is the Linker Register (LR) I mentioned. abs= in the logs is the stack address from which LR was read; that's actually the SP value of each frame in the backtrace (++)
so roughly:
frame 0, function cg::c. SP = ? (currently not printed in the logs +++)
this (the per-frame "changes") matches the stack usage values reported by cargo-call-stack:
not sure how much this SP in backtrace would help you but it doesn't require instrumenting the program so it's both runtime cost free and requires minimal changes to the program (introduce an artificial panic to print the backtrace at the desired spot)
which in pseudo-rust is roughly *sp = lr where sp: *mut u32 and lr: u32
(++) well, not always; exceptions (interrupts) need special handling but it's still possible to determine some SP value in that case, I think. also, the backtrace sometime includes inlined functions; there's no actual function call there so the SP stays the same in those frames.
(+++) this missing value can be obtained by reading the value of the SP when unwinding starts. I think the unwinding algorithm already does this but I don't think the value is printed in the logs.
TBD: the format in which to print the stack depth. one option is to include the SP value in each backtrace frame, like this:
stack backtrace:
0: 0x00000494 @ cg::c (SP=0x2003fb60)
at src/bin/cg.rs:33:14
1: 0x00000484 @ cg::b (SP=0x2003fb80)
at src/bin/cg.rs:27:2
2: 0x00000470 @ cg::a (SP=0x2003fb98)
at src/bin/cg.rs:20:2
3: 0x000004ac @ cg::__cortex_m_rt_main (SP=0x2003fba8)
at src/bin/cg.rs:11:5
4: 0x000004a2 @ main (SP=0x2003fbb0)
at src/bin/cg.rs:8:1
5: 0x00000444 @ Reset (SP=0x2003fbb8)
the
--measure-stack
feature reports the maximum stack usage of an entire program. it would be useful to have a more fine-grained report of stack usage per function. there are static analysis tools likecargo-call-stack
that provide that kind of information but they rely on a nightly Rust toolchain and break often. as a complement to that tool,probe-run
could report the stack usage of each function -- or more precisely the "stack depth" -- when it computes / prints a backtrace with relatively low implementation effort.more details on the idea from a conversation:
when
probe-run
does (host-side) unwinding of the target's call stack it's also indirectly reading the value the SP register had at the start of each call frame. this is because it had to read the value of the Link Register (LR) that was pushed onto the stack in the prologue of each called function (+) to figure out the call site (PC value) of the function is currently unwinding.in theory, it would be possible to include this information in the backtrace so each backtrace frame includes the "stack depth".
you can glimpse at the SP information right now by looking at
probe-run
logs but it's going to non-obvious to figure out which frame the SP value refers to.for example, given this program
the
probe-run
logs and backtrace look like this:register # 14 is the Linker Register (LR) I mentioned.
abs=
in the logs is the stack address from which LR was read; that's actually the SP value of each frame in the backtrace (++)so roughly:
cg::c
. SP = ? (currently not printed in the logs +++)cg::b
. SP = 0x2003fb80cg::a
. SP = 0x2003fb98 (change 0x18 = 24 bytes)cg::main
. SP = 0x2003fba8 (change 0x10 = 16 bytes)main
. SP = 0x2003fbb0 (change 0x08 = 8 bytes)this (the per-frame "changes") matches the stack usage values reported by
cargo-call-stack
:not sure how much this SP in backtrace would help you but it doesn't require instrumenting the program so it's both runtime cost free and requires minimal changes to the program (introduce an artificial panic to print the backtrace at the desired spot)
(+) most functions start like this:
which in pseudo-rust is roughly
*sp = lr
wheresp: *mut u32
andlr: u32
(++) well, not always; exceptions (interrupts) need special handling but it's still possible to determine some SP value in that case, I think. also, the backtrace sometime includes inlined functions; there's no actual function call there so the SP stays the same in those frames.
(+++) this missing value can be obtained by reading the value of the SP when unwinding starts. I think the unwinding algorithm already does this but I don't think the value is printed in the logs.
TBD: the format in which to print the stack depth. one option is to include the SP value in each backtrace frame, like this: