Skip to content

Commit

Permalink
Merge pull request #45 from Jon-Becker/fix/decompile
Browse files Browse the repository at this point in the history
⚡ perf: decompilation improvements and optimizations
  • Loading branch information
Jon-Becker authored Jan 23, 2023
2 parents ad93f27 + b9cfbbe commit be45e91
Show file tree
Hide file tree
Showing 6 changed files with 33 additions and 54 deletions.
2 changes: 1 addition & 1 deletion common/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "heimdall-common"
version = "0.2.4"
version = "0.2.5"
edition = "2021"
license = "MIT"
readme = "README.md"
Expand Down
2 changes: 1 addition & 1 deletion config/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "heimdall-config"
version = "0.2.4"
version = "0.2.5"
edition = "2021"
license = "MIT"
readme = "README.md"
Expand Down
2 changes: 1 addition & 1 deletion heimdall/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ keywords = ["ethereum", "web3", "decompiler", "evm", "crypto"]
license = "MIT"
name = "heimdall"
readme = "README.md"
version = "0.2.4"
version = "0.2.5"

[dependencies]
backtrace = "0.3"
Expand Down
3 changes: 2 additions & 1 deletion heimdall/scripts/benchmark
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ cargo test --release --package heimdall-common -- benchmark_ | grep -E "±|bench
clear
echo "Benchmark results:\n"
cat stdout
rm stdout
rm stdout
echo "\n"
19 changes: 10 additions & 9 deletions heimdall/src/decompile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,24 +287,25 @@ pub fn decompile(args: DecompilerArgs) {
);

// get a map of possible jump destinations
let (map, jumpdests) = map_selector(&evm.clone(), selector.clone(), function_entry_point);
let (map, jumpdest_count) = map_selector(&evm.clone(), selector.clone(), function_entry_point);

trace.add_debug(
func_analysis_trace,
function_entry_point.try_into().unwrap(),
format!("execution tree {}",

match jumpdests.len() {
match jumpdest_count {
0 => "appears to be linear".to_string(),
_ => format!("has {} branches", jumpdests.len()+1)
_ => format!("has {} branches", jumpdest_count)
}
).to_string()
);

if jumpdests.len() >= 1000 {
if jumpdest_count >= 1000 {
trace.add_error(
func_analysis_trace,
function_entry_point.try_into().unwrap(),
format!("Execution tree truncated to {} branches", jumpdests.len()).to_string()
format!("Execution tree truncated to {} branches", jumpdest_count).to_string()
);
}

Expand Down Expand Up @@ -335,8 +336,8 @@ pub fn decompile(args: DecompilerArgs) {
);

// add notice for long execution trees
if jumpdests.len() >= 1000 {
analyzed_function.notices.push(format!("execution tree truncated to {} branches", jumpdests.len()));
if jumpdest_count >= 1000 {
analyzed_function.notices.push(format!("execution tree truncated to {} branches", jumpdest_count));
}

let argument_count = analyzed_function.arguments.len();
Expand Down Expand Up @@ -381,10 +382,10 @@ pub fn decompile(args: DecompilerArgs) {
let resolved_functions = match resolved_selectors.get(&selector) {
Some(func) => func.clone(),
None => {
trace.add_error(
trace.add_warn(
func_analysis_trace,
line!(),
"failed to resolve function.".to_string()
"failed to resolve function signature".to_string()
);
continue;
}
Expand Down
59 changes: 18 additions & 41 deletions heimdall/src/decompile/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,7 @@ pub struct VMTrace {
pub instruction: u128,
pub operations: Vec<State>,
pub children: Vec<VMTrace>,
pub loop_detected: bool,
pub depth: usize,
pub loop_detected: bool
}

// returns the compiler version used to compile the contract.
Expand Down Expand Up @@ -281,7 +280,7 @@ pub fn map_selector(
evm: &VM,
selector: String,
entry_point: u64,
) -> (VMTrace, Vec<String>) {
) -> (VMTrace, u32) {
let mut vm = evm.clone();
vm.calldata = selector.clone();

Expand All @@ -298,7 +297,7 @@ pub fn map_selector(
}

// the VM is at the function entry point, begin tracing
let mut handled_jumpdests = Vec::new();
let mut handled_jumpdests = 0;
(
recursive_map(
&vm.clone(),
Expand All @@ -311,7 +310,7 @@ pub fn map_selector(

pub fn recursive_map(
evm: &VM,
handled_jumpdests: &mut Vec<String>,
handled_jumpdests: &mut u32,
path: &mut String,
) -> VMTrace {
let mut vm = evm.clone();
Expand All @@ -322,42 +321,35 @@ pub fn recursive_map(
operations: Vec::new(),
children: Vec::new(),
loop_detected: false,
depth: 0,
};

if handled_jumpdests.len() >= 1000 {

return vm_trace
}
if handled_jumpdests >= &mut 1000 { return vm_trace; }

// step through the bytecode until we find a JUMPI instruction
while vm.bytecode.len() >= (vm.instruction * 2 + 2) as usize {
let state = vm.step();
vm_trace.operations.push(state.clone());
*handled_jumpdests += 1;

// if we encounter a JUMPI, create children taking both paths and break
if state.last_instruction.opcode == "57" {
vm_trace.depth += 1;

path.push_str(&format!("{}->{};", state.last_instruction.instruction, state.last_instruction.inputs[0]));

// we need to create a trace for the path that wasn't taken.
if state.last_instruction.inputs[1] == U256::from(0) {

// break out of loops
match LOOP_DETECTION_REGEX.is_match(&path) {
Ok(result) => {
if result {
vm_trace.loop_detected = true;
break;
}
}
Err(_) => {
return vm_trace
// break out of loops
match LOOP_DETECTION_REGEX.is_match(&path) {
Ok(result) => {
if result {
vm_trace.loop_detected = true;
break;
}
}
Err(_) => {
return vm_trace
}
}

handled_jumpdests.push(format!("{}@{}", vm_trace.depth, state.last_instruction.instruction));
// we need to create a trace for the path that wasn't taken.
if state.last_instruction.inputs[1] == U256::from(0) {

// push a new vm trace to the children
let mut trace_vm = vm.clone();
Expand All @@ -377,21 +369,6 @@ pub fn recursive_map(
break;
} else {

// break out of loops
match LOOP_DETECTION_REGEX.is_match(&path) {
Ok(result) => {
if result {
vm_trace.loop_detected = true;
break;
}
}
Err(_) => {
return vm_trace
}
}

handled_jumpdests.push(format!("{}@{}", vm_trace.depth, state.last_instruction.instruction));

// push a new vm trace to the children
let mut trace_vm = vm.clone();
trace_vm.instruction = state.last_instruction.instruction + 1;
Expand Down

0 comments on commit be45e91

Please sign in to comment.