diff --git a/Cargo.toml b/Cargo.toml index 5c6f4517d75407c094c58cc20e6e95a4c5a0edfc..11c23a9cf42af94ede368a51574dd6e2643f143f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,8 @@ edition = "2018" [dependencies] libc = "0.2.43" +pest = "2.0.2" +pest_derive = "2.0.1" +stack-sizes = "0.1.0" +rustc-demangle = "0.1.9" +petgraph = "0.4.13" diff --git a/src/main.rs b/src/main.rs index 2eb47d8e85bad5970eca2e3304313946c6add051..cca278b0c94d0f1cc764c8c9c5be7685104e9ea7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,30 @@ use std::io::{self, Read, Write}; use std::process::{Command, Stdio}; use std::{env, str}; +#[macro_use] +extern crate pest_derive; +extern crate rustc_demangle; +extern crate stack_sizes; + +use pest::Parser; +use rustc_demangle::demangle; + +use std::fs::File; + +use petgraph::{ + algo::is_cyclic_directed, + dot::{Config, Dot}, + graph::NodeIndex, + visit::DfsPostOrder, + Graph, Incoming, +}; +use std::collections::hash_map::HashMap; +use std::fmt; + +#[derive(Parser)] +#[grammar = "ident.pest"] +struct IdentParser; + #[derive(Debug)] struct Out { hash: Option<String>, @@ -47,6 +71,112 @@ fn parse_out(out_str: &str) -> Out { out } +struct Node { + name: String, + local_stack: u32, + call_stack: u32, +} + +impl fmt::Debug for Node { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{:#}\\n stack local = {}, call = {}", + demangle(&self.name), + self.local_stack, + self.call_stack + ) + } +} + +fn new_node( + hm: &mut HashMap<String, NodeIndex<u32>>, + g: &mut Graph<Node, ()>, + s: String, + v: u32, +) -> NodeIndex<u32> { + let n = Node { + name: s.to_string(), + local_stack: v, + call_stack: v, + }; + let i = g.add_node(n); + hm.insert(s, i); + i +} + +fn add_edge<'a>( + hm: &mut HashMap<String, NodeIndex<u32>>, + g: &mut Graph<Node, ()>, + from: &str, + to: &str, +) { + let i_from = hm.get(from).expect("from not found"); + if let Some(i_to) = hm.get(to) { + g.add_edge(*i_from, *i_to, ()); + } +} + +fn read_elf(path: &str, buffer: &mut Vec<u8>) { + let mut elf = File::open(path).expect(format!("error opening '{}'", path).as_str()); + elf.read_to_end(buffer) + .expect(format!("error opening '{}'", path).as_str()); +} + +fn parse_elf<'a>( + hm: &mut HashMap<String, NodeIndex<u32>>, + g: &mut Graph<Node, ()>, + path: &str, + vec: &'a mut Vec<u8>, +) { + read_elf(path, vec); + + // read and analyse stack-sizes of elf + let ss = stack_sizes::analyze(&vec).expect("error analysing elf"); + if let Some(v32) = ss.left() { + // put into graph + for s in v32.iter() { + let d_name = s.name().to_string(); // format!("{:#}", demangle(s.name())); + let _ = new_node(hm, g, d_name, s.stack() as u32); + } + } else { + panic!("64 bit elfs not supported"); + } +} + +fn parse_callgraph(hm: &mut HashMap<String, NodeIndex<u32>>, g: &mut Graph<Node, ()>, input: &str) { + let functions = IdentParser::parse(Rule::ident_list, &input) + .unwrap_or_else(|e| panic!("parse error {}", e)); + + for f in functions { + match f.as_rule() { + Rule::fun => { + let mut p = f.into_inner(); + let mut i = p.next().unwrap().into_inner(); + let from = i.next().unwrap().into_inner(); + + for c in p { + match c.as_rule() { + Rule::callext => {} + Rule::callfun => { + let mut p = c.into_inner(); + p.next().unwrap(); + let to = p.next().unwrap().into_inner().as_str().to_string(); + add_edge(hm, g, from.as_str(), to.as_str()); + } + _ => panic!("internal error"), + } + } + } + Rule::null => (), + Rule::EOI => (), + _ => { + panic!("internal error"); + } + } + } +} + fn main() { println!("start sub command"); // first argument is the path to this binary; the second argument is always "call-stack" -- both can @@ -107,26 +237,61 @@ fn main() { let mut s = String::new(); write!( &mut s, - "{}/{}{}.ll", + "{}/{}{}", out.out_dir.expect("--out-dir missing"), out.crate_name.expect("--crate-name missing"), out.hash.expect("extra-filename missing") ) .expect("internal error"); - println!("s: {}", s); + let mut ir = s.clone(); + write!(&mut ir, ".ll").expect("internal error"); + println!("s: {}", ir); let mut c = Command::new("opt"); c.arg("-analyze") .arg("-print-callgraph") - .arg(s) + .arg(ir) .output() .expect("failed to execute process"); let dump = c.output().expect("failed to execute process"); - println!("{:?}", dump); - println!("{:?}", dump.status); - println!("{:?}", str::from_utf8(&dump.stdout).unwrap()); - println!("{:?}", str::from_utf8(&dump.stderr).unwrap()); + //println!("{:?}", dump); + println!("\nstatus: {:?}", dump.status); + println!("\nstdout: {:?}", str::from_utf8(&dump.stdout).unwrap()); + println!("\nstderr: {:?}", str::from_utf8(&dump.stderr).unwrap()); + + let mut g = Graph::<_, ()>::new(); + let mut hm = HashMap::new(); + + let mut buffer = Vec::new(); + println!("elf = {}", &s); + + parse_elf(&mut hm, &mut g, &s, &mut buffer); + + parse_callgraph(&mut hm, &mut g, &str::from_utf8(&dump.stderr).unwrap()); + + if is_cyclic_directed(&g) { + println!("/* warning, cyclic graph */"); + } + + let mut v = Vec::new(); + for ext in g.externals(Incoming) { + v.push(ext); + } + + let mut dfs = DfsPostOrder::empty(&g); + while let Some(ext) = v.pop() { + dfs.move_to(ext); + while let Some(node) = dfs.next(&g) { + // use a detached neighbors walker + let mut edges = g.neighbors_directed(node, Incoming).detach(); + while let Some(to) = edges.next_node(&g) { + g[to].call_stack = g[to].call_stack.max(g[to].local_stack + g[node].call_stack); + } + } + } + + println!("{:?}", Dot::with_config(&g, &[Config::EdgeNoLabel])); // let hello = c.stdout();