nitrokey-3-firmware
nitrokey-3-firmware copied to clipboard
Add stack analysis tool
With -Z emit-stack-sizes
we can get the stack sizes of each function in the final firmware. This could be used to analyze stack usage to fix stack-overflows. This could be made into a small util. Currently this issue will be used to document the (hacky) process:
Steps:
- use nightly
- add to the linker script
SECTIONS
(need to be added torunners/embedded/ld/cortex-m-rt_0.6.15_link.x
):
/* `INFO` makes the section not allocatable so it won't be loaded into memory */
.stack_sizes (INFO) :
{
KEEP(*(.stack_sizes));
}
- add
strip = false
to release profile - compile with
RUSTFLAGS="-Z emit-stack-sizes" make flash-develop EXTRA_FEATURES=...
(fromutils/nrf-builder
) - use the following script to get the functions (path may need to be adjusted):
#!/usr/bin/env cargo
//! ```cargo
//! [package]
//! edition = "2021"
//! [dependencies]
//! stack-sizes = "0.5.0"
//! ```
use std::fs::read;
use stack_sizes::analyze_executable;
fn main() {
let path = "../nitrokey-3-firmware/target/thumbv7em-none-eabihf/release/nrf52_runner";
let data = read(path).unwrap();
let functions = analyze_executable(&data).unwrap();
let mut sorted: Vec<_> = functions.defined.into_values().collect();
sorted.sort_by_key(|f| f.stack().unwrap_or(0));
println!("{sorted:#?}");
}
Other tools that can help make the output more readable: c++filt
Branch stack-sizes contains the hacks used.
I've improved output format, env call for +x
, and it reads from stdin now.
#!/usr/bin/env -S cargo +nightly -Zscript
//! ```cargo
//! [package]
//! edition = "2021"
//! [dependencies]
//! stack-sizes = "0.5.0"
//!```
use std::io::Read;
use std::io;
use stack_sizes::analyze_executable;
fn main() {
let mut stdin = io::stdin().lock();
let mut buffer = Vec::new();
stdin.read_to_end(&mut buffer).unwrap();
let functions = analyze_executable(&buffer).unwrap();
let mut sorted: Vec<_> = functions.defined.into_values().collect();
sorted.sort_by_key(|f| f.stack().unwrap_or(0));
for f in sorted {
println!("{:5?} {:5} {:?}", f.stack(), f.size(), f.names() );
}
}
Next iteration:
- reverse sort order (highest stack usage first)
- demangle identifiers
- make output prettier
#!/usr/bin/env -S cargo +nightly -Zscript
//! ```cargo
//! [package]
//! edition = "2021"
//! [dependencies]
//! stack-sizes = "0.5.0"
//! symbolic = { version = "12.4.1", features = ["demangle"] }
//!```
use std::io::Read;
use std::io;
use stack_sizes::analyze_executable;
use symbolic::demangle;
fn main() {
let mut stdin = io::stdin().lock();
let mut buffer = Vec::new();
stdin.read_to_end(&mut buffer).unwrap();
let functions = analyze_executable(&buffer).unwrap();
let mut sorted: Vec<_> = functions.defined.into_values().collect();
sorted.sort_by_key(|f| f.stack().unwrap_or(0));
for f in sorted.into_iter().rev() {
if let Some(stack) = f.stack() {
print!("{:6}", stack);
} else {
print!("None");
}
print!(" {:6} ", f.size());
for (i, name) in f.names().into_iter().enumerate() {
if i > 0 {
print!(", ");
}
print!("{}", demangle::demangle(name));
}
println!();
}
}
Example output:
108568 652 lpc55_runner::app::rtic_ext::main::__rtic_init_resources
22920 264 <iso7816::command::Command<_> as core::convert::TryFrom<&[u8]>>::try_from
22896 176 usbd_ctaphid::pipe::Pipe<Bus>::handle_response
22320 14032 ctap_types::ctap2::Request::deserialize
22152 3508 embedded_runner_lib::soc::init::Stage2::next
21800 6020 OS_EVENT
21440 408 apdu_dispatch::dispatch::ApduDispatch::handle_reply
19832 15260 trussed::service::ServiceResources<P>::reply_to
18064 1048 webcrypt::ctap_app::try_handle_ctap2
17392 364 webcrypt::ctap_app::try_handle_ctap1