Skip to content
Snippets Groups Projects
Commit c18aeead authored by Per Lindgren's avatar Per Lindgren
Browse files

initial commit

parents
No related branches found
No related tags found
No related merge requests found
/target/
**/*.rs.bk
.gdb_history
Cargo.lock
klee-*
*.bc
*.ll
[package]
name = "cargo-call-stack"
version = "0.1.0"
authors = ["Per Lindgren <per.lindgren@ltu.se>"]
edition = "2018"
[dependencies]
libc = "0.2.43"
# Cargo call-stack
A cargo sub-commond for spanning the call stack for a `cortex-m` bare metal application.
## Installing
> cargo install --path <PATH_TO cargo-call-stack>
## Usage
Assume we have have an existing `cortex-m` bare metal application, e.g., ...
> cargo call-stack --release --example hello1
## Tech notes
### Cargo sub-commands
Cargo allows third party sub-commands to be installed, giving extended functionality to the build system. The crate name for a sub command should be pre-fixed with "cargo-X", X being the command name, in this case "cargo-call-stack". For further information see, ...
### Paths
Cargo uses the `target` directory for storing build artifacts. In our case we are only interested in `--release` builds, since we want to apply `lto` optimization for obtaining a single file comprising the complete call-graph. So what remains to resolve is the concrete path for the generated `elf` binary and the corresponding `llvm-ir`. To that end, we parse the command line arguments to determine wether its an `--example <name>` or a `src` build. In the former case the generated `llvm-ir` file has the path `../release/example/<name><hash>.ll`, while `src` builds have `llvm-ir` path `../release/deps/<crate>-<hash>.ll`. The `elf` files have the paths `../release/example/<name>` and `../release/<crate>` correspondingly.
### Hash suffix
Cargo identifies a build context by a unique hash in order to distinguish cashed compilation units between builds (under different configurations). To that end, we need to determine the current hash to determine the full path of the generated llvm ir (e.g. `target/thumbv7m-none-eabi/release/examples/hello1-f3268774e62977a5.ll`, `f3268774e62977a5` being the hash). While `Cargo` can be accessed as a library, it does not (AFAIK) provide a stable API. A pragmatic solotion is to parse the actual invocation parameters to `rustc` to determine the current hash (as identified by the `extra-filename` option).
extern crate libc;
use std::io::{self, Read, Write};
use std::process::{Command, Stdio};
use std::{env, str};
fn main() {
println!("start sub command");
// first argument is the path to this binary; the second argument is always "call-stack" -- both can
// be ignored
let args = env::args().skip(2).collect::<Vec<_>>();
// turn `cargo klee --example foo` into `cargo rustc --example foo -- (..)`
let mut c = Command::new("cargo");
c.arg("rustc")
.args(&args)
// verbose output for debugging purposes
.arg("-v")
.arg("--color=always")
.arg("--")
// ignore linking
//.args(&["-C", "linker=true"])
// link for embedded with retained stack section
// .args(&["-C", "link-arg=-Tlink.x"])
// .arg("codegen-units = 1") // for now assume that this is already in the user crate
// force LTO
.args(&["-C", "lto"])
// also output the LLVM-IR (.ll file) for debugging purposes
.arg("--emit=llvm-bc,llvm-ir")
// emit stack sizes for the top level (not dependencies)
.args(&["-Z", "emit-stack-sizes"])
// force panic=abort in all crates
//.env("RUSTFLAGS", "-C panic=abort")
// collect stderr
.stderr(Stdio::piped());
// print full command for debugging purposes
eprintln!("{:?}", c);
// spawn process and drive it completion while replicating its stderr in ours
let mut p = c.spawn().unwrap();
let mut pstderr = p.stderr.take().unwrap();
let mut buf = [0; 1024];
let mut output = vec![];
let stderr = io::stderr();
let mut stderr = stderr.lock();
loop {
if let Ok(n) = pstderr.read(&mut buf) {
stderr.write(&buf[..n]).unwrap();
output.extend_from_slice(&buf[..n]);
}
if let Some(status) = p.try_wait().unwrap() {
assert!(status.success());
break;
}
}
// find out the argument passed to `--example`, if used at all
let mut example = None;
let mut iargs = args.iter();
while let Some(arg) = iargs.next() {
if arg == "--example" {
example = iargs.next();
break;
}
}
// get the suffix of the example
let (example, suffix) = if let Some(example) = example {
let mut suffix = None;
let output = str::from_utf8(&output).unwrap();
println!("here ...");
for line in output.lines() {
if line.contains(&format!("examples/{}.rs", example)) {
for part in line.split(' ') {
if part.starts_with("extra-filename=") {
suffix = part.split('=').nth(1);
}
}
}
}
match suffix {
None => panic!(format!(
"Cannot determine hash, please run `touch example/{}.rs` to force recompiltaion.",
example
)),
Some(s) => (example, s),
}
} else {
// nothing to do if the user didn't use `--example`
println!("try to determinte hash");
let output = str::from_utf8(&output).unwrap();
println!("here ...");
let mut suffix = None;
for line in output.lines() {
for part in line.split(' ') {
if part.starts_with("extra-filename=") {
suffix = part.split('=').nth(1);
}
}
}
match suffix {
None => panic!(format!(
"Cannot determine hash, please force recompiltaion."
)),
Some(s) => panic!("main hash {}", s),
}
// return;
};
println!("{:?}", (example, suffix));
/*
let profile = if args.iter().any(|a| a == "--release") {
"release"
} else {
"debug"
};
let mut docker = Command::new("docker");
docker
.arg("run")
.arg("--rm")
.args(&["--user", &format!("{}:{}", uid(), gid())])
.args(&[
"-v",
&format!("{}:/mnt", env::current_dir().unwrap().display()),
])
.args(&["-w", "/mnt"])
.arg("-it")
.arg("afoht/llvm-klee-4")
.args(&[
"/usr/bin/klee",
&format!("target/{}/examples/{}{}.bc", profile, example, suffix),
]);
// print full command for debugging purposes
eprintln!("{:?}", docker);
assert!(docker.status().unwrap().success());
}
// NOTE from cross
fn gid() -> u32 {
unsafe { libc::getgid() }
}
// NOTE from cross
fn uid() -> u32 {
unsafe { libc::getuid() }
*/
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment