Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add stack analysis tool #313

Open
sosthene-nitrokey opened this issue Jul 12, 2023 · 4 comments · May be fixed by #459
Open

Add stack analysis tool #313

sosthene-nitrokey opened this issue Jul 12, 2023 · 4 comments · May be fixed by #459

Comments

@sosthene-nitrokey
Copy link
Contributor

sosthene-nitrokey commented Jul 12, 2023

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:

  1. use nightly
  2. add to the linker script SECTIONS (need to be added to runners/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));
  }
  1. add strip = false to release profile
  2. compile with RUSTFLAGS="-Z emit-stack-sizes" make flash-develop EXTRA_FEATURES=... (from utils/nrf-builder)
  3. 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:#?}");
}
@sosthene-nitrokey
Copy link
Contributor Author

sosthene-nitrokey commented Jul 12, 2023

Other tools that can help make the output more readable: c++filt

@sosthene-nitrokey
Copy link
Contributor Author

sosthene-nitrokey commented Jul 12, 2023

Branch stack-sizes contains the hacks used.

@szszszsz
Copy link
Member

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() );
    }
}

@robin-nitrokey
Copy link
Member

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

robin-nitrokey added a commit that referenced this issue Mar 11, 2024
This patch adds a CI job that prints the stack sizes for the functions
in the stable NK3 firmware as well as the necessary tooling.

Fixes: #313
@robin-nitrokey robin-nitrokey linked a pull request Mar 11, 2024 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants