Skip to content

Commit

Permalink
fix: CFG computation for loops (#57)
Browse files Browse the repository at this point in the history
- update dependencies and refactor code
  • Loading branch information
ChristianMoesl committed Nov 17, 2020
1 parent 4ec4f1d commit 80b8859
Show file tree
Hide file tree
Showing 13 changed files with 259 additions and 283 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
**/*.dot
**/*.png
**/*.s
.DS_Store
34 changes: 17 additions & 17 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/candidate_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ mod tests {

let test_file = Path::new("symbolic/division-by-zero-3-35.riscu.o");

let (graph, _, _) = build_from_file(test_file).unwrap();
let (graph, _, _) = build_cfg_from_file(test_file).unwrap();

let paths = create_candidate_paths(&graph).into_iter().count();

Expand Down
158 changes: 49 additions & 109 deletions src/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,43 +15,18 @@
use crate::elf::{load_file, ElfMetadata};
use byteorder::{ByteOrder, LittleEndian};
use petgraph::dot::Dot;
use petgraph::graph::{EdgeIndex, NodeIndex};
use petgraph::visit::EdgeRef;
use petgraph::Graph;
use riscv_decode::decode;
use riscv_decode::Instruction;
use std::collections::HashSet;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::process::Command;
use std::vec::Vec;
use petgraph::{
dot::Dot,
graph::{EdgeIndex, NodeIndex},
visit::EdgeRef,
Graph,
};
use riscv_decode::{decode, Instruction, Register};
use std::{fs::File, io::prelude::*, path::Path, process::Command, vec::Vec};

type Edge = (NodeIndex, NodeIndex, Option<NodeIndex>);
pub type ControlFlowGraph = Graph<Instruction, Option<NodeIndex>>;

/// Extend sign
pub fn sign_extend(n: u32, b: u32) -> u32 {
// assert: 0 <= n <= 2^b
// assert: 0 < b < CPUBITWIDTH
if n < 2_u32.pow(b - 1) {
n
} else {
n.wrapping_sub(2_u32.pow(b))
}
}

/// Get `NodeIndex` of `beq` destination.
fn calculate_beq_destination(idx: NodeIndex, imm: u32) -> NodeIndex {
NodeIndex::new(sign_extend(imm / 4, 11).wrapping_add(idx.index() as u32) as usize)
}

/// Get `NodeIndex` of `jal` destination.
fn calculate_jal_destination(idx: NodeIndex, imm: u32) -> NodeIndex {
NodeIndex::new(sign_extend(imm / 4, 19).wrapping_add(idx.index() as u32) as usize)
}

/// Create a `ControlFlowGraph` from an `u8` slice without fixing edges
fn create_instruction_graph(binary: &[u8]) -> ControlFlowGraph {
binary
Expand Down Expand Up @@ -79,10 +54,16 @@ fn construct_edge_if_trivial(graph: &ControlFlowGraph, idx: NodeIndex) -> Option
/// Compute pure edges
fn construct_edge_if_pure(graph: &ControlFlowGraph, idx: NodeIndex) -> Option<Edge> {
match graph[idx] {
Instruction::Jal(i) if i.rd() == 0 => {
Some((idx, calculate_jal_destination(idx, i.imm()), None))
}
Instruction::Beq(i) => Some((idx, calculate_beq_destination(idx, i.imm()), None)),
Instruction::Jal(i) if i.rd() == Register::Zero => Some((
idx,
NodeIndex::new((((idx.index() as u64) * 4).wrapping_add(i.imm() as u64) / 4) as usize),
None,
)),
Instruction::Beq(i) => Some((
idx,
NodeIndex::new((((idx.index() as u64) * 4).wrapping_add(i.imm() as u64) / 4) as usize),
None,
)),
_ => None,
}
}
Expand All @@ -99,41 +80,44 @@ where
}

/// Compute all return locations in a given function starting at idx.
fn compute_return_edge_position(graph: &ControlFlowGraph, idx: NodeIndex) -> HashSet<NodeIndex> {
fn compute_return_edge_position(graph: &ControlFlowGraph, idx: NodeIndex) -> NodeIndex {
match graph[idx] {
Instruction::Jalr(_) => {
let mut set = HashSet::new();
set.insert(idx);
set
}
Instruction::Jal(i) if i.rd() != 0 => {
Instruction::Jalr(_) => idx,
Instruction::Jal(i) if i.rd() != Register::Zero => {
compute_return_edge_position(graph, NodeIndex::new(idx.index() + 1))
}
_ => graph
.edges(idx)
.flat_map(|e| compute_return_edge_position(graph, e.target()))
.collect(),
Instruction::Beq(_) => compute_return_edge_position(graph, {
// second edge is the true branch edge, which jumps to the end of the loop (Selfie
graph
.edges(idx)
.find(|e| e.target().index() != idx.index() + 1)
.unwrap()
.target()
}),
_ => compute_return_edge_position(graph, graph.edges(idx).next().unwrap().target()),
}
}

/// Fix stateful edges and return a vector containing them
fn construct_edge_if_stateful(idx: NodeIndex, graph: &ControlFlowGraph) -> Option<Vec<Edge>> {
match graph[idx] {
Instruction::Jal(jtype) if jtype.rd() != 0 => {
Instruction::Jal(jtype) if jtype.rd() != Register::Zero => {
// jump and link => function call
let jump_dest = calculate_jal_destination(idx, jtype.imm());
let jump_dest = NodeIndex::new(
(((idx.index() as u64) * 4).wrapping_add(jtype.imm() as u64) / 4) as usize,
);
let return_dest = NodeIndex::new(idx.index() + 1);
let mark = Some(return_dest);

let mut edges = compute_return_edge_position(graph, jump_dest)
.iter()
.map(|rp| (*rp, return_dest, Some(idx)))
.collect::<Vec<Edge>>();
let return_edge = (
compute_return_edge_position(graph, jump_dest),
return_dest,
mark,
);

let call_edge = (idx, jump_dest, Some(idx));
let call_edge = (idx, jump_dest, mark);

edges.push(call_edge);

Some(edges)
Some(vec![call_edge, return_edge])
}
_ => None,
}
Expand Down Expand Up @@ -168,7 +152,7 @@ fn find_possible_exit_edge(graph: &ControlFlowGraph, idx: NodeIndex) -> Option<E
/// Fix the exit ecall edge
fn fix_exit_ecall(graph: &mut ControlFlowGraph) {
graph.node_indices().for_each(|idx| {
if let Instruction::Ecall = graph[idx] {
if let Instruction::Ecall(_) = graph[idx] {
if let Some(edge) = find_possible_exit_edge(graph, idx) {
graph.remove_edge(edge);
}
Expand Down Expand Up @@ -204,7 +188,12 @@ pub type DataSegment = Vec<u8>;

/// Create a ControlFlowGraph from Path `file`.
// TODO: only tested with Selfie RISC-U file and relies on that ELF format
pub fn build_from_file(file: &Path) -> Result<(ControlFlowGraph, DataSegment, ElfMetadata), &str> {
pub fn build_cfg_from_file<P>(
file: P,
) -> Result<(ControlFlowGraph, DataSegment, ElfMetadata), &'static str>
where
P: AsRef<Path>,
{
match load_file(file, 1024) {
Some((code, data, meta_data)) => Ok((build(code.as_slice()), data, meta_data)),
None => Err("Cannot load RISC-U ELF file"),
Expand Down Expand Up @@ -234,52 +223,3 @@ pub fn convert_dot_to_png(source: &Path, output: &Path) -> Result<(), &'static s

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;
use std::env::current_dir;
use std::string::String;

// TODO: write a unit test without dependency on selfie and external files
#[test]
#[serial]
fn can_build_control_flow_graph() {
let cd = String::from(current_dir().unwrap().to_str().unwrap());

// generate RISC-U binary with Selfie
let _ = Command::new("docker")
.arg("run")
.arg("-v")
.arg(cd + ":/opt/monster")
.arg("cksystemsteaching/selfie")
.arg("/opt/selfie/selfie")
.arg("-c")
.arg("/opt/monster/symbolic/division-by-zero-3-35.c")
.arg("-o")
.arg("/opt/monster/symbolic/division-by-zero-3-35.riscu.o")
.output();

let test_file = Path::new("symbolic/division-by-zero-3-35.riscu.o");

let (graph, _, _) = build_from_file(test_file).unwrap();

let dot_graph = Dot::with_config(&graph, &[]);

let mut f = File::create("tmp-graph.dot").unwrap();
f.write_fmt(format_args!("{:?}", dot_graph)).unwrap();

let _ = Command::new("dot")
.arg("-Tpng")
.arg("tmp-graph.dot")
.arg("-o")
.arg("main.png")
.output();

// TODO: test more than just this result
// assert!(result.is_ok());

let _ = std::fs::remove_file(test_file);
}
}
9 changes: 0 additions & 9 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,6 @@ pub fn args() -> App<'static> {
.takes_value(true)
.value_name("FILE")
.default_value("cfg.dot"),
)
.arg(
Arg::new("format")
.about("File format of the generated CFG")
.short('f')
.long("format")
.takes_value(true)
.possible_values(&["dot", "png"])
.default_value("dot"),
),
)
.subcommand(
Expand Down
2 changes: 1 addition & 1 deletion src/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl<R: RiscU> Decoder<'_, R> {
Instruction::Jal(i) => self.next.jal(i),
Instruction::Jalr(i) => self.next.jalr(i),
Instruction::Beq(i) => self.next.beq(i),
Instruction::Ecall => self.next.ecall(),
Instruction::Ecall(_) => self.next.ecall(),
i => unimplemented!("instruction: {:?}", i),
}
} else {
Expand Down
Loading

0 comments on commit 80b8859

Please sign in to comment.