Skip to content

Commit

Permalink
feat: add Z3 SMT solver support (#68)
Browse files Browse the repository at this point in the history
- also refactor the solver interface
 - fix small bug in boolector interface
 - improve toolchain setup description
  • Loading branch information
ChristianMoesl committed Nov 17, 2020
1 parent 465e35c commit af9ef1b
Show file tree
Hide file tree
Showing 13 changed files with 385 additions and 221 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
**/*.png
**/*.s
.DS_Store
.z3-trace
29 changes: 25 additions & 4 deletions Cargo.lock

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

19 changes: 10 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@ edition = "2018"
description = "Monster is a symbolic execution engine for 64-bit RISC-V code"

[dependencies]
goblin = "0.2"
byteorder = "1.3.4"
goblin = "~0.2.3"
byteorder = "~1.3.4"
clap = "3.0.0-beta.2"
riscv-decode = { git = "https://github.com/cksystemsgroup/riscv-decode" }
petgraph = "0.5.1"
rand = "0.7.3"
petgraph = "~0.5.1"
rand = "~0.7.3"
boolector = { version = "0.4", git = "https://github.com/finga/boolector-rs", branch = "boolector-src", features = ["vendored"] }
modinverse = "0.1.1"
log = "0.4"
env_logger = "0.8.1"
bytesize = "1.0.1"
z3 = { version = "~0.7.1", features = ["static-link-z3"] }
modinverse = "~0.1.1"
log = "~0.4.11"
env_logger = "~0.8.1"
bytesize = "~1.0.1"

[dev-dependencies]
serial_test = "0.4.0"
serial_test = "~0.4.0"

[dev-dependencies.cargo-husky]
version = "1"
Expand Down
34 changes: 21 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# monster
Monster is a symbolic execution engine for 64-bit RISC-V code

# Toolchain setup
## Install rust
### Toolchain setup

#### Linux and Unix-like OS
1. Bootstrap rust
```
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Expand All @@ -21,25 +22,31 @@ $ echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> ~/.zshrc && source ~/.zshrc
$ cargo install cross
$ cargo install mdbook
$ cargo install mdbook-linkcheck
$ cargo install --git https://github.com/christianmoesl/mdbook-graphviz
$ cargo install mdbook-graphviz
```
5. install Docker and LLVM with your favorite package manager

## Docker and llvm
### Debian based
5. Install docker (needed by cross) with [this installation guide](https://docs.docker.com/engine/install/debian/)
6. Make sure you have a recent version of clang/llvm (>= v9) installed:
##### MacOS
6. Install docker (needed by cross) with [this installation guide](https://docs.docker.com/docker-for-mac/install/)
```
$ apt install llvm
$ brew cask install docker
```

### Mac
5. Install docker (needed by cross) with [this installation guide](https://docs.docker.com/docker-for-mac/install/)
6. Make sure you have a recent version of clang/llvm (>= v9) installed:
7. Make sure you have a recent version of clang/llvm (>= v9) installed:
```
$ brew install llvm
```

## Build and test
##### Debian based
6. Install docker (needed by cross) with [this installation guide](https://docs.docker.com/engine/install/debian/)
7. Make sure you have a recent version of clang/llvm (>= v9) installed:
```
$ apt install llvm
```

#### Windows
We do not support Windows directly. But someone can use WSL2 to run/develop for Monster.

### Build and Test from Source
7. Test your toolchain setup by compiling monster:
```
$ cargo build --locked
Expand All @@ -48,3 +55,4 @@ $ cargo build --locked
```
$ cargo test --locked
```

152 changes: 63 additions & 89 deletions src/boolector.rs
Original file line number Diff line number Diff line change
@@ -1,127 +1,101 @@
use crate::bitvec::BitVector;
use crate::solver::{Assignment, Solver};
use crate::symbolic_state::{
BVOperator, Formula,
get_operands, BVOperator, Formula,
Node::{Constant, Input, Operator},
OperandSide,
SymbolId,
};
use boolector::{
option::{BtorOption, ModelGen, OutputFileFormat},
Btor, SolverResult, BV,
};
use log::debug;
use petgraph::{graph::NodeIndex, Direction};
use std::collections::HashMap;
use std::rc::Rc;

fn solve(graph: &Formula, root: NodeIndex) -> Option<Assignment<BitVector>> {
let solver = Rc::new(Btor::new());
solver.set_opt(BtorOption::ModelGen(ModelGen::All));
solver.set_opt(BtorOption::Incremental(true));
solver.set_opt(BtorOption::OutputFileFormat(OutputFileFormat::SMTLIBv2));

let mut bvs = HashMap::new();
let bv = traverse(graph, root, &solver, &mut bvs);
bv.assert();
pub struct Boolector {}

if let SolverResult::Sat = solver.sat() {
let assignments = graph
.node_indices()
.map(|i| BitVector(bvs.get(&i).unwrap().get_a_solution().as_u64().unwrap()))
.collect();
impl Boolector {
pub fn new() -> Self {
Self {}
}
}

Some(assignments)
} else {
None
impl Default for Boolector {
fn default() -> Self {
Self::new()
}
}

fn get_operands(
graph: &Formula,
node: NodeIndex,
solver: &Rc<Btor>,
bvs: &mut HashMap<NodeIndex, BV<Rc<Btor>>>,
) -> (BV<Rc<Btor>>, Option<BV<Rc<Btor>>>) {
let mut operands = graph.neighbors_directed(node, Direction::Incoming).detach();
impl Solver for Boolector {
fn name() -> &'static str {
"Boolector"
}

match operands.next(graph) {
Some(p) if graph[p.0] == OperandSide::Lhs => (traverse(graph, p.1, solver, bvs), {
if let Some(node) = operands.next(graph) {
Some(traverse(graph, node.1, solver, bvs))
} else {
None
}
}),
Some(p) if graph[p.0] == OperandSide::Rhs => (
traverse(graph, p.1, solver, bvs),
if let Some(node) = operands.next(graph) {
Some(traverse(graph, node.1, solver, bvs))
} else {
None
},
),
_ => unreachable!(),
fn solve_impl(&mut self, graph: &Formula, root: SymbolId) -> Option<Assignment<BitVector>> {
let solver = Rc::new(Btor::new());
solver.set_opt(BtorOption::ModelGen(ModelGen::All));
solver.set_opt(BtorOption::Incremental(true));
solver.set_opt(BtorOption::OutputFileFormat(OutputFileFormat::SMTLIBv2));

let mut bvs = HashMap::new();
let bv = traverse(graph, root, &solver, &mut bvs);
bv.assert();

if let SolverResult::Sat = solver.sat() {
let assignments = graph
.node_indices()
.filter(|i| matches!(graph[*i], Input(_)))
.map(|i| BitVector(bvs.get(&i).unwrap().get_a_solution().as_u64().unwrap()))
.collect();

Some(assignments)
} else {
None
}
}
}

fn traverse<'a>(
graph: &Formula,
node: NodeIndex,
node: SymbolId,
solver: &'a Rc<Btor>,
bvs: &mut HashMap<NodeIndex, BV<Rc<Btor>>>,
bvs: &mut HashMap<SymbolId, BV<Rc<Btor>>>,
) -> BV<Rc<Btor>> {
let bv = match &graph[node] {
Operator(op) => {
match get_operands(graph, node, solver, bvs) {
let bv =
match &graph[node] {
Operator(op) => match get_operands(graph, node) {
(lhs, Some(rhs)) => {
match op {
BVOperator::Add => lhs.add(&rhs),
BVOperator::Sub => lhs.sub(&rhs),
BVOperator::Mul => lhs.mul(&rhs),
BVOperator::Equals => lhs._eq(&rhs),
BVOperator::BitwiseAnd => lhs.and(&rhs),
//BVOperator::GreaterThan => lhs.ugt(&rhs),
BVOperator::Add => traverse(graph, lhs, solver, bvs)
.add(&traverse(graph, rhs, solver, bvs)),
BVOperator::Sub => traverse(graph, lhs, solver, bvs)
.sub(&traverse(graph, rhs, solver, bvs)),
BVOperator::Mul => traverse(graph, lhs, solver, bvs)
.mul(&traverse(graph, rhs, solver, bvs)),
BVOperator::Equals => traverse(graph, lhs, solver, bvs)
._eq(&traverse(graph, rhs, solver, bvs)),
BVOperator::BitwiseAnd => traverse(graph, lhs, solver, bvs)
.and(&traverse(graph, rhs, solver, bvs)),
i => unimplemented!("binary operator: {:?}", i),
}
}
(lhs, None) => match op {
BVOperator::Not => lhs._eq(&BV::from_u64(solver.clone(), 0, 1)),
BVOperator::Not => {
traverse(graph, lhs, solver, bvs)._eq(&BV::from_u64(solver.clone(), 0, 1))
}
i => unimplemented!("unary operator: {:?}", i),
},
},
Input(name) => {
if let Some(value) = bvs.get(&node) {
value.clone()
} else {
BV::new(solver.clone(), 64, Some(name))
}
}
}
Input(name) => {
if let Some(value) = bvs.get(&node) {
value.clone()
} else {
BV::new(solver.clone(), 64, Some(name))
}
}
Constant(c) => BV::from_u64(solver.clone(), *c, 64),
};
Constant(c) => BV::from_u64(solver.clone(), *c, 64),
};

bvs.insert(node, bv.clone());
bv
}

pub struct Boolector {}

impl Boolector {
pub fn new() -> Self {
Self {}
}
}

impl Default for Boolector {
fn default() -> Self {
Self::new()
}
}

impl Solver for Boolector {
fn solve(&mut self, graph: &Formula, root: NodeIndex) -> Option<Assignment<BitVector>> {
debug!("try to solve with Boolector");

time_debug!("finished solving formula", { solve(graph, root) })
}
}
5 changes: 3 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use clap::{crate_authors, crate_description, crate_name, crate_version, App, AppSettings, Arg};

pub const LOGGING_LEVELS: [&str; 5] = ["trace", "debug", "info", "warn", "error"];
pub const SOLVER: [&str; 3] = ["monster", "boolector", "z3"];

pub fn args() -> App<'static> {
App::new(crate_name!())
Expand Down Expand Up @@ -70,8 +71,8 @@ pub fn args() -> App<'static> {
.short('s')
.long("solver")
.takes_value(true)
.possible_values(&["monster", "boolector"])
.default_value("monster"),
.possible_values(&SOLVER)
.default_value(SOLVER[0]),
),
)
.setting(AppSettings::SubcommandRequiredElseHelp)
Expand Down
Loading

0 comments on commit af9ef1b

Please sign in to comment.