From b9cfbbe9d802aed34b2629342367b01e2344ffae Mon Sep 17 00:00:00 2001 From: Jonathan Becker <64037729+Jon-Becker@users.noreply.github.com> Date: Mon, 23 Jan 2023 12:03:59 -0500 Subject: [PATCH] :zap: perf: decompilation improvements and optimizations --- common/Cargo.toml | 2 +- config/Cargo.toml | 2 +- heimdall/Cargo.toml | 2 +- heimdall/scripts/benchmark | 3 +- heimdall/src/decompile/mod.rs | 19 +++++------ heimdall/src/decompile/util.rs | 59 +++++++++++----------------------- 6 files changed, 33 insertions(+), 54 deletions(-) diff --git a/common/Cargo.toml b/common/Cargo.toml index 33a2ddf4..ffc09fff 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "heimdall-common" -version = "0.2.4" +version = "0.2.5" edition = "2021" license = "MIT" readme = "README.md" diff --git a/config/Cargo.toml b/config/Cargo.toml index e3c178ac..cd0460c9 100644 --- a/config/Cargo.toml +++ b/config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "heimdall-config" -version = "0.2.4" +version = "0.2.5" edition = "2021" license = "MIT" readme = "README.md" diff --git a/heimdall/Cargo.toml b/heimdall/Cargo.toml index 59cd5c36..8d1397ea 100644 --- a/heimdall/Cargo.toml +++ b/heimdall/Cargo.toml @@ -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" diff --git a/heimdall/scripts/benchmark b/heimdall/scripts/benchmark index 9626136d..7a780551 100644 --- a/heimdall/scripts/benchmark +++ b/heimdall/scripts/benchmark @@ -12,4 +12,5 @@ cargo test --release --package heimdall-common -- benchmark_ | grep -E "±|bench clear echo "Benchmark results:\n" cat stdout -rm stdout \ No newline at end of file +rm stdout +echo "\n" \ No newline at end of file diff --git a/heimdall/src/decompile/mod.rs b/heimdall/src/decompile/mod.rs index 5b81e4fe..fcc40eac 100644 --- a/heimdall/src/decompile/mod.rs +++ b/heimdall/src/decompile/mod.rs @@ -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() ); } @@ -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(); @@ -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; } diff --git a/heimdall/src/decompile/util.rs b/heimdall/src/decompile/util.rs index e9753eaf..0fc5a49b 100644 --- a/heimdall/src/decompile/util.rs +++ b/heimdall/src/decompile/util.rs @@ -121,8 +121,7 @@ pub struct VMTrace { pub instruction: u128, pub operations: Vec, pub children: Vec, - pub loop_detected: bool, - pub depth: usize, + pub loop_detected: bool } // returns the compiler version used to compile the contract. @@ -281,7 +280,7 @@ pub fn map_selector( evm: &VM, selector: String, entry_point: u64, -) -> (VMTrace, Vec) { +) -> (VMTrace, u32) { let mut vm = evm.clone(); vm.calldata = selector.clone(); @@ -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(), @@ -311,7 +310,7 @@ pub fn map_selector( pub fn recursive_map( evm: &VM, - handled_jumpdests: &mut Vec, + handled_jumpdests: &mut u32, path: &mut String, ) -> VMTrace { let mut vm = evm.clone(); @@ -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(); @@ -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;