Skip to content

Commit

Permalink
feat(rarity): implement search algorithm (breadth-first)
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristianMoesl committed Mar 1, 2021
1 parent 1675e2b commit 09e5d64
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 48 deletions.
26 changes: 22 additions & 4 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,15 +163,33 @@ pub fn args() -> App<'static> {
.validator(is_u64),
)
.arg(
Arg::new("rounds")
Arg::new("runs")
.about("Number of distinct runs")
.short('r')
.long("rounds")
.long("runs")
.takes_value(true)
.value_name("NUMBER")
.default_value("10")
.default_value("3000")
.validator(is_u64),
),
)
.arg(
Arg::new("selection")
.about("Number of runs to select in every iteration")
.short('s')
.long("selection")
.takes_value(true)
.value_name("NUMBER")
.default_value("50")
.validator(is_u64))
.arg(
Arg::new("iterations")
.about("Iterations of rarity simulation to run")
.short('i')
.long("iterations")
.takes_value(true)
.value_name("NUMBER")
.default_value("20")
.validator(is_u64))
)
.setting(AppSettings::SubcommandRequiredElseHelp)
.global_setting(AppSettings::GlobalVersion)
Expand Down
24 changes: 19 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,27 @@ fn main() -> Result<()> {
.value_of_t::<u64>("cycles")
.expect("value is validated already");

let rounds = args
.value_of_t::<u64>("rounds")
let iterations = args
.value_of_t::<u64>("iterations")
.expect("value is validated already");

if let Some(bug) =
rarity::execute(input, output, ByteSize::mb(megabytes), rounds, cycles)?
{
let runs = args
.value_of_t::<u64>("runs")
.expect("value is validated already");

let selection = args
.value_of_t::<u64>("selection")
.expect("value is validated already");

if let Some(bug) = rarity::execute(
input,
output,
ByteSize::mb(megabytes),
runs,
selection,
cycles,
iterations,
)? {
info!("bug found:\n{}", bug);
} else {
info!("no bug found in binary");
Expand Down
130 changes: 92 additions & 38 deletions src/rarity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,28 @@ use crate::{
};
use byteorder::{ByteOrder, LittleEndian};
use bytesize::ByteSize;
use itertools::Itertools;
use log::{debug, info, trace, warn};
use riscu::{
decode, load_object_file, types::*, DecodingError, Instruction, Program, ProgramSegment,
Register, RiscuError, INSTRUCTION_SIZE as INSTR_SIZE,
};
use std::{
cmp::{min, Reverse},
fmt,
fs::{create_dir_all, File},
io::Write,
mem::size_of,
path::Path,
sync::Arc,
};
use thiserror::Error;

pub type Bug = BugDef<RarityBugInfo>;

const INSTRUCTION_SIZE: u64 = INSTR_SIZE as u64;

#[derive(Debug, Clone)]
pub struct State {
pc: u64,
regs: [Value; 32],
Expand All @@ -31,7 +35,7 @@ pub struct State {

trait StateComparator {
fn score_states(&self, first: &State, second: &State) -> u64;
fn score_states_pairwise(&self, states: &[State]) -> Vec<u64> {
fn score_states_pairwise(&self, states: &[&State]) -> Vec<u64> {
let mut scores = vec![0u64; states.len()];

for (i, state) in states.iter().enumerate() {
Expand Down Expand Up @@ -93,13 +97,14 @@ impl StateComparator for BytewiseStateComparator {
}

impl State {
#[allow(dead_code)]
fn write_to_file<P>(&self, path: P) -> Result<(), EngineError>
where
P: AsRef<Path>,
{
File::create(path)
.and_then(|mut file| write!(file, "{}", self))
.map_err(EngineError::IoError)
.map_err(|e| EngineError::IoError(Arc::new(e)))
}
}

Expand All @@ -125,64 +130,103 @@ pub fn execute<P>(
input: P,
output_dir: P,
memory_size: ByteSize,
rounds: u64,
number_of_states: u64,
selection: u64,
cycles: u64,
iterations: u64,
) -> Result<Option<Bug>, EngineError>
where
P: AsRef<Path>,
{
let program = load_object_file(input).map_err(EngineError::RiscuError)?;

create_and_run(&program, output_dir, memory_size, rounds, cycles)
let program = load_object_file(input).map_err(|e| EngineError::RiscuError(Arc::new(e)))?;

create_and_run(
&program,
output_dir,
memory_size,
number_of_states,
selection,
cycles,
iterations,
)
}

fn create_and_run<P>(
program: &Program,
output_dir: P,
memory_size: ByteSize,
rounds: u64,
number_of_states: u64,
selection: u64,
cycles: u64,
iterations: u64,
) -> Result<Option<Bug>, EngineError>
where
P: AsRef<Path>,
{
if !output_dir.as_ref().is_dir() {
create_dir_all(&output_dir).map_err(EngineError::IoError)?;
create_dir_all(&output_dir).map_err(|e| EngineError::IoError(Arc::new(e)))?;
}

let mut states: Vec<State> = Vec::new();
let mut engines: Vec<Engine> = Vec::new();

for iteration in 0..iterations {
info!("Running rarity simulation round {}...", iteration + 1);

for round in 0..rounds {
info!("Running rarity simulation round {}...", round + 1);
let to_create = number_of_states as usize - engines.len();
info!("Creating {} new states", to_create);
engines.extend((0..to_create).map(|_| Engine::new(&program, memory_size)));

let mut engine = Engine::new(&program, memory_size, cycles);
let results = time_info!("Running engines", {
let results: Vec<_> = engines
.iter_mut()
.map(|engine| engine.run(cycles))
.collect();

if let Some(error_or_bug) = results.clone().iter().find(|result| match result {
Err(EngineError::ExecutionDepthReached(_)) => false,
Err(_) | Ok(Some(_)) => true,
_ => false,
}) {
return error_or_bug.clone();
}

let result = engine.run();
results
});

// remove successfully exited engines
engines = engines
.iter()
.cloned()
.zip(results)
.filter(|(_, r)| matches!(r, Err(EngineError::ExecutionDepthReached(_))))
.map(|(e, _)| e)
.collect();

info!(
" executed {} instructions out of {}",
engine.execution_depth, cycles
"Remove {} successfully exited states from selection",
number_of_states as usize - engines.len()
);

match result {
// TODO: Ok(None) is a state which successfully exited, should we really dump that???
Err(EngineError::ExecutionDepthReached(_)) | Ok(None) => {
states.push(engine.state);
}
Err(_) | Ok(Some(_)) => return result,
}
}
let scores = time_info!("Scoring states", {
let cmp = BytewiseStateComparator {};
let states: Vec<_> = engines.iter().map(|e| e.state()).collect();
cmp.score_states_pairwise(states.as_slice())
});

info!("Writing state dumps...");
for (idx, state) in states.iter().enumerate() {
let state_file = output_dir.as_ref().join(format!("dump_{}.out", idx));
info!(" scored states: {:?}", scores);

state.write_to_file(&state_file)?;
}
info!(" done! State dumps written into {:?}", output_dir.as_ref());
let selection = min(selection as usize, engines.len());

let cmp = BytewiseStateComparator {};
info!(" scored states: {:?}", cmp.score_states_pairwise(&states));
engines = engines
.iter()
.zip(scores)
.sorted_by_key(|x| Reverse(x.1))
.map(|x| (*x.0).clone())
.take(selection)
.collect();

info!(" selecting {} states", selection);
}

Ok(None)
}
Expand All @@ -202,6 +246,7 @@ impl fmt::Display for Value {
}
}

#[derive(Debug, Clone)]
pub struct Engine {
program_break: u64,
state: State,
Expand All @@ -213,7 +258,12 @@ pub struct Engine {

impl Engine {
// creates a machine state with a specific memory size
pub fn new(program: &Program, memory_size: ByteSize, max_exection_depth: u64) -> Self {
pub fn new(program: &Program, memory_size: ByteSize) -> Self {
assert!(
memory_size.as_u64() % 8 == 0,
"memory size has to be a multiple of 8"
);

let mut regs = [Value::Uninitialized; 32];
let mut memory = vec![Value::Uninitialized; memory_size.as_u64() as usize / 8];

Expand Down Expand Up @@ -255,15 +305,19 @@ impl Engine {
program_break,
state: State { pc, regs, memory },
execution_depth: 0,
max_exection_depth,
max_exection_depth: 0,
concrete_inputs: Vec::new(),
is_running: false,
}
}

pub fn run(&mut self) -> Result<Option<Bug>, EngineError> {
pub fn state(&self) -> &State {
&self.state
}

pub fn run(&mut self, number_of_instructions: u64) -> Result<Option<Bug>, EngineError> {
self.is_running = true;
self.concrete_inputs = Vec::new();
self.max_exection_depth += number_of_instructions;

loop {
if self.execution_depth >= self.max_exection_depth {
Expand Down Expand Up @@ -830,13 +884,13 @@ impl Engine {
}
}

#[derive(Debug, Error)]
#[derive(Debug, Clone, Error)]
pub enum EngineError {
#[error("failed to load RISC-U binary")]
RiscuError(RiscuError),
RiscuError(Arc<RiscuError>),

#[error("failed to write State to file")]
IoError(std::io::Error),
IoError(Arc<std::io::Error>),

#[error("engine does not support {0}")]
NotSupported(String),
Expand Down
2 changes: 1 addition & 1 deletion tests/rarity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ fn test_rarity_simulation() {

let output_dir = std::env::current_dir().unwrap().join("dumps");

let result = execute(&object, &output_dir, ByteSize::mb(1), 1, 1);
let result = execute(&object, &output_dir, ByteSize::mb(1), 1, 1, 1, 1);

trace!("execution finished: {:?}", result);

Expand Down

0 comments on commit 09e5d64

Please sign in to comment.