Several tracer and disassembler improvments.

This commit is contained in:
Ege Balcı
2024-07-10 20:04:00 +02:00
parent 2c25c1635b
commit a045dab178
7 changed files with 223 additions and 252 deletions
+4 -5
View File
@@ -1,3 +1,5 @@
// use crate::x86_64::disassembler;
use crate::x86_64::disassembler::disassemble;
use base64::prelude::*;
use colored::Colorize;
use log::{error, info, warn, LevelFilter};
@@ -53,16 +55,13 @@ fn main() {
info!("Input file size: {}", file.len());
let mut deopt = x86_64::Deoptimizer::new();
deopt.freq = opts.freq;
deopt.trace = opts.trace;
deopt.allow_invalid = opts.allow_invalid;
deopt.set_skipped_offsets(opts.skip_offsets);
if let Err(e) = deopt.set_transform_gadgets(opts.transforms) {
error!("{}", e);
return;
}
if let Err(e) = deopt.set_syntax(opts.syntax) {
error!("{}", e);
return;
}
let start_addr = match u64::from_str_radix(opts.addr.trim_start_matches("0x"), 16) {
Ok(addr) => addr,
Err(e) => {
@@ -103,7 +102,7 @@ fn main() {
}
info!("De-optimized binary written into {}", opts.outfile);
} else {
let source = match deopt.disassemble(opts.bitness, start_addr, output) {
let source = match disassemble(&output, opts.bitness, start_addr, opts.syntax) {
Ok(s) => s,
Err(e) => {
error!("{}", e);
+36 -16
View File
@@ -17,6 +17,8 @@ pub enum ArgParseError {
FileNotFound,
#[error("Invalid offset values.")]
InvalidOffsetValues,
#[error("Invalid formatter syntax.")]
InvalidSyntax,
#[error("Address parsing failed: {0}")]
AddressParseError(#[from] FromHexError),
#[error("Integer parsing failed: {0}")]
@@ -35,55 +37,59 @@ pub struct Options {
#[arg(long, short = 'a', default_value_t = String::from("x86"))]
pub arch: String,
/// target binary file name.
/// Target binary file name.
#[arg(long, short = 'f', default_value_t = String::new())]
pub file: String,
/// output file name.
/// Output file name.
#[arg(long, short = 'o', default_value_t = String::new())]
pub outfile: String,
/// source assembly file.
/// Source assembly file.
#[arg(long, short = 's', default_value_t = String::new())]
pub source: String,
/// assembler formatter syntax (nasm/masm/intel/gas).
/// Assembler formatter syntax (nasm/masm/intel/gas).
#[arg(long, default_value_t = String::from("keystone"))]
pub syntax: String,
/// bitness of the binary file (16/32/64).
/// Bitness of the binary file (16/32/64).
#[arg(long, short = 'b', default_value_t = 64)]
pub bitness: u32,
/// start address in hexadecimal form.
/// Start address in hexadecimal form.
#[arg(long, short = 'A', default_value_t = String::from("0x0000000000000000"))]
pub addr: String,
/// File offset range for skipping deoptimization (eg: 0-10 for skipping first ten bytes).
#[arg(long, value_parser=parse_offset, num_args = 1.., value_delimiter = ',')]
pub skip_offsets: Vec<(u32, u32)>,
pub skip_offsets: Vec<(u64, u64)>,
/// total number of deoptimization cycles.
/// Auto-skip dead-code and strings by control flow tracing.
#[arg(long, short = 'T')]
pub trace: bool,
/// Total number of deoptimization cycles.
#[arg(long, short = 'c', default_value_t = 1)]
pub cycle: u32,
/// deoptimization frequency.
/// Deoptimization frequency.
#[arg(long, short = 'F', default_value_t = 0.5)]
pub freq: f32,
/// allowed transform routines (ap/li/lp/om/rs).
/// Allowed transform routines (ap/li/lp/om/rs).
#[arg(long, default_value_t = String::from("ap,li,lp,om,rs"))]
pub transforms: String,
/// allow processing of invalid instructions.
/// Allow processing of invalid instructions.
#[arg(long)]
pub allow_invalid: bool,
/// verbose output mode.
/// Verbose output mode.
#[arg(long, short = 'v')]
pub verbose: bool,
/// debug output mode.
/// Debug output mode.
#[arg(long)]
pub debug: bool,
}
@@ -106,6 +112,13 @@ pub fn parse_options() -> Result<Options, ArgParseError> {
opts.outfile = opts.source.clone();
}
if !matches!(
opts.syntax.to_uppercase().as_str(),
"KEYSTONE" | "NASM" | "MASM" | "INTEL" | "GAX"
) {
return Err(ArgParseError::InvalidSyntax);
}
if opts.outfile.is_empty() {
opts.outfile = format!("{}_deopt.bin", opts.file);
}
@@ -119,14 +132,14 @@ pub fn parse_options() -> Result<Options, ArgParseError> {
Ok(opts)
}
pub fn parse_offset(offsets: &str) -> Result<(u32, u32), ArgParseError> {
pub fn parse_offset(offsets: &str) -> Result<(u64, u64), ArgParseError> {
if offsets.matches('-').count() != 1 {
return Err(ArgParseError::InvalidOffsetValues);
}
let mut off: Vec<u32> = Vec::new();
let mut off: Vec<u64> = Vec::new();
for part in offsets.split('-') {
if part.starts_with("0x") {
off.push(u32::from_str_radix(part.trim_start_matches("0x"), 16)?)
off.push(u64::from_str_radix(part.trim_start_matches("0x"), 16)?)
} else {
off.push(part.parse()?)
}
@@ -147,6 +160,7 @@ pub fn print_summary(opts: &Options) {
}
let freq_str = format!("%{:.4}", opts.freq * 100.0);
let trace_str = format!("{:?}", opts.trace);
println!(
"\n[ {} {} {} ]",
"#".repeat(wspace / 2 + 2).yellow().bold(),
@@ -201,5 +215,11 @@ pub fn print_summary(opts: &Options) {
opts.transforms,
" ".repeat(wspace - opts.transforms.len())
);
println!(
"[ {} {}{}]",
"Auto Trace: ".blue().bold(),
opts.trace,
" ".repeat(wspace - trace_str.len())
);
println!();
}
+73 -145
View File
@@ -2,10 +2,11 @@ use crate::x86_64::*;
use bitflags::bitflags;
use iced_x86::code_asm::*;
use iced_x86::*;
use log::{error, info, trace, warn};
use log::{debug, error, info, trace, warn};
use rand::Rng;
use std::collections::HashMap;
use thiserror::Error;
use tracer::*;
#[derive(Error, Debug)]
pub enum DeoptimizerError {
@@ -21,8 +22,6 @@ pub enum DeoptimizerError {
// UnexpectedMemorySize,
// #[error("Offset skipping failed!")]
// OffsetSkipFail,
#[error("Invalid formatter syntax.")]
InvalidSyntax,
#[error("Invalid processor mode(bitness). (16/32/64 accepted)")]
InvalidProcessorMode,
#[error("All available instruction transform gadgets failed.")]
@@ -47,16 +46,18 @@ pub enum DeoptimizerError {
TransposeFailed,
#[error("Invalid transform gadget.")]
InvalidTransformGadget,
#[error("Tracer Error: {0}")]
TracerError(#[from] TracerError),
#[error("Instruction encoding failed: {0}")]
EncodingFail(#[from] IcedError),
}
enum AssemblySyntax {
Keystone,
Nasm,
Masm,
Intel,
Gas,
#[allow(dead_code)]
pub enum FileType {
Pe,
Elf,
Coff,
Shellcode,
}
bitflags! {
@@ -78,26 +79,38 @@ impl AvailableTransforms {
}
}
#[allow(dead_code)]
pub struct AnalyzedCode {
bitness: u32,
bytes: Vec<u8>,
bitness: u32,
start_addr: u64,
code: Vec<Instruction>,
file_type: FileType,
instructions: Vec<Instruction>,
known_addr_table: Vec<u64>,
branch_targets: Vec<u64>,
// addr_map: HashMap<u64, Instruction>,
trace_results: Option<TraceResults>,
}
impl AnalyzedCode {
fn is_known_address(&self, addr: u64) -> bool {
// pub fn get_bytes(&self) -> Vec<u8> {
// self.bytes
// }
// pub fn get_instructions(&self) -> Vec<Instruction> {
// self.instructions
// }
// pub fn get_bitness(&self) -> u32 {
// self.bitness
// }
// pub fn get_start_address(&self) -> u64 {
// self.start_addr
// }
pub fn is_known_address(&self, addr: u64) -> bool {
self.known_addr_table.contains(&addr)
}
fn is_branch_target(&self, addr: u64) -> bool {
pub fn is_branch_target(&self, addr: u64) -> bool {
self.branch_targets.contains(&addr)
}
// fn get_random_cfe_addr(&self) -> Option<u64> {
// self.cfe_addr_table.choose(&mut rand::thread_rng()).copied()
// }
// pub fn is_affecting_cf(&self, inst: &Instruction) -> Result<bool, DeoptimizerError> {
// // Check if the instruction exists in self.code
// if self.addr_map.get(&inst.ip()).is_none() {
@@ -135,9 +148,9 @@ pub struct Deoptimizer {
pub transforms: AvailableTransforms,
/// Allow processing of invalid instructions.
pub allow_invalid: bool,
/// Disassembler syntax.
syntax: AssemblySyntax,
skipped_offsets: Option<Vec<(u32, u32)>>,
/// Trace the control flow of the given binary
pub trace: bool,
skipped_offsets: HashMap<u64, bool>,
}
impl Deoptimizer {
@@ -145,10 +158,10 @@ impl Deoptimizer {
Self {
cycle: 1,
freq: 0.5,
trace: false,
allow_invalid: false,
transforms: AvailableTransforms::All,
syntax: AssemblySyntax::Nasm,
skipped_offsets: None,
skipped_offsets: HashMap::new(),
}
}
@@ -169,32 +182,13 @@ impl Deoptimizer {
Ok(())
}
pub fn set_syntax(&mut self, syntax: String) -> Result<(), DeoptimizerError> {
match syntax.to_lowercase().as_str() {
"keystone" => self.syntax = AssemblySyntax::Keystone,
"nasm" => self.syntax = AssemblySyntax::Nasm,
"masm" => self.syntax = AssemblySyntax::Masm,
"intel" => self.syntax = AssemblySyntax::Intel,
"gas" => self.syntax = AssemblySyntax::Gas,
_ => return Err(DeoptimizerError::InvalidSyntax),
}
Ok(())
}
pub fn set_skipped_offsets(&mut self, skipped: Vec<(u32, u32)>) {
self.skipped_offsets = Some(skipped);
}
fn is_offset_skipped(&self, offset: u32) -> bool {
if self.skipped_offsets.is_none() {
return false;
}
for (o1, o2) in self.skipped_offsets.clone().unwrap() {
if offset >= o1 && offset <= o2 {
return true;
pub fn set_skipped_offsets(&mut self, skipped: Vec<(u64, u64)>) {
for range in skipped {
let (o1, o2) = range;
for o in o1..o2 {
self.skipped_offsets.insert(o, true);
}
}
false
}
fn replace_skipped_offsets(
@@ -202,13 +196,13 @@ impl Deoptimizer {
bytes: &[u8],
fill: u8,
) -> Result<Vec<u8>, DeoptimizerError> {
if self.skipped_offsets.is_none() {
if self.skipped_offsets.is_empty() {
return Ok(bytes.to_vec());
}
let mut replaced_bytes = Vec::new();
trace!("Replacing skipped offsets with NOPs...");
for (i, b) in bytes.iter().enumerate() {
if self.is_offset_skipped(i as u32) {
if self.skipped_offsets.contains_key(&(i as u64)) {
replaced_bytes.push(fill);
continue;
}
@@ -229,11 +223,30 @@ impl Deoptimizer {
bitness
);
// let trace_results = tracer::trace(bytes, bitness, start_addr)?;
// Determine the file type here...
if bytes.len() > 1000 && self.trace {
warn!("Given shellcode seems to be too large for effective tracing.")
}
let mut trace_results = None;
if self.trace {
info!("Tracing the execution control flow...");
let tr = tracer::trace(bytes, bitness, start_addr)?;
info!("Done tracing!");
info!("Found {} possible strings.", tr.possible_strings.len());
debug!("Total coverage: {}", tr.total_coverage);
debug!("Coverage without strings: {}", tr.coverage_whitout_strings);
for o in 0..bytes.len() {
if !tr.active_offsets.contains(&(o as u64 + start_addr)) {
self.skipped_offsets.insert(o as u64 + start_addr, true);
}
}
trace_results = Some(tr);
}
let mut decoder = Decoder::with_ip(bitness, bytes, start_addr, DecoderOptions::NONE);
let replaced_bytes: Vec<u8>;
if self.skipped_offsets.is_some() {
if !self.skipped_offsets.is_empty() {
replaced_bytes = self.replace_skipped_offsets(bytes, 0x90)?;
decoder = Decoder::with_ip(bitness, &replaced_bytes, start_addr, DecoderOptions::NONE);
}
@@ -241,22 +254,23 @@ impl Deoptimizer {
let mut inst = Instruction::default();
let mut known_addr_table = Vec::new();
let mut branch_targets = Vec::new();
let mut code = Vec::new();
let mut instructions = Vec::new();
let mut addr_map: HashMap<u64, Instruction> = HashMap::new();
let mut offset = 0;
while decoder.can_decode() {
decoder.decode_out(&mut inst);
if self.is_offset_skipped(offset) {
if self.skipped_offsets.contains_key(&offset) {
let mut db = Instruction::with_declare_byte_1(bytes[offset as usize]);
db.set_ip(inst.ip());
db.set_code(Code::DeclareByte);
// Push to known address table
known_addr_table.push(db.ip());
addr_map.insert(db.ip(), db);
code.push(db);
instructions.push(db);
offset += 1;
continue;
}
if inst.is_invalid() {
warn!("Found invalid instruction at: 0x{:016X}", inst.ip());
if !self.allow_invalid {
@@ -266,21 +280,13 @@ impl Deoptimizer {
// Push to known address table
known_addr_table.push(inst.ip());
addr_map.insert(inst.ip(), inst);
code.push(inst);
instructions.push(inst);
let bt = get_branch_target(&inst).unwrap_or(0);
if bt != 0 {
branch_targets.push(bt);
}
// // Push to control flow exit address table if it is a JMP of RET
// if inst.mnemonic() == Mnemonic::Ret
// || inst.mnemonic() == Mnemonic::Retf
// || inst.mnemonic() == Mnemonic::Jmp
// {
// cfe_addr_table.push(inst.ip())
// }
offset += inst.len() as u32;
offset += inst.len() as u64;
}
for bt in branch_targets.iter() {
@@ -296,92 +302,14 @@ impl Deoptimizer {
bitness,
bytes: bytes.to_vec(),
start_addr,
code,
file_type: FileType::Shellcode,
instructions,
known_addr_table,
branch_targets,
// addr_map,
trace_results,
})
}
pub fn format_instruction(&self, inst: &Instruction) -> String {
let mut result = String::new();
match self.syntax {
AssemblySyntax::Keystone => {
let mut formatter = IntelFormatter::new();
formatter.options_mut().set_uppercase_keywords(false);
formatter
.options_mut()
.set_memory_size_options(iced_x86::MemorySizeOptions::Always);
formatter.options_mut().set_hex_prefix("0x");
formatter.options_mut().set_hex_suffix("");
formatter.format(inst, &mut result);
}
AssemblySyntax::Nasm => {
let mut formatter = NasmFormatter::new();
formatter.format(inst, &mut result);
}
AssemblySyntax::Masm => {
let mut formatter = MasmFormatter::new();
formatter.format(inst, &mut result);
}
AssemblySyntax::Intel => {
let mut formatter = IntelFormatter::new();
formatter.format(inst, &mut result);
}
AssemblySyntax::Gas => {
let mut formatter = GasFormatter::new();
formatter.format(inst, &mut result);
}
};
result
}
pub fn disassemble(
&mut self,
bitness: u32,
start_addr: u64,
bytes: Vec<u8>,
) -> Result<String, DeoptimizerError> {
info!(
"Disassembling at -> 0x{:016X} (mode={})",
start_addr, bitness
);
let acode = self.analyze(bytes.as_slice(), bitness, start_addr)?;
let mut result = String::new();
let mut decoder = Decoder::new(acode.bitness, bytes.as_slice(), DecoderOptions::NONE);
let mut inst = Instruction::default();
while decoder.can_decode() {
decoder.decode_out(&mut inst);
if inst.is_invalid() {
warn!(
"Inlining invalid instruction bytes at: 0x{:016X}",
inst.ip()
);
let start_index = (inst.ip() - acode.start_addr) as usize;
let instr_bytes = &acode.bytes[start_index..start_index + inst.len()];
result += &format!("loc_{:016X}: {}\n", inst.ip(), to_db_mnemonic(instr_bytes));
continue;
}
let temp = self.format_instruction(&inst);
let nbt = inst.near_branch_target();
if nbt != 0 {
if acode.is_known_address(nbt) {
result += &format!(
"loc_{:016X}: {} {}\n",
inst.ip(),
temp.split(' ').next().unwrap(),
&format!("loc_{:016X}", nbt)
);
continue;
} else {
warn!("Misaligned instruction detected at {}", inst.ip())
}
}
result += &format!("loc_{:016X}: {}\n", inst.ip(), temp);
}
Ok(result)
}
pub fn apply_transform(
bitness: u32,
inst: &Instruction,
@@ -455,7 +383,7 @@ impl Deoptimizer {
let mut ip_to_index_table: HashMap<u64, usize> = HashMap::new();
let mut index_to_index_table: HashMap<usize, usize> = HashMap::new();
// let mut new_ip: u64 = acode.start_addr;
for inst in acode.code.clone() {
for inst in acode.instructions.clone() {
if acode.is_branch_target(inst.ip()) {
ip_to_index_table.insert(inst.ip(), result.len());
}
+82
View File
@@ -0,0 +1,82 @@
use crate::x86_64::*;
use iced_x86::*;
use log::{error, info, warn};
pub fn format_instruction(syntax: String, inst: &Instruction) -> String {
let mut result = String::new();
match syntax.to_uppercase().as_str() {
"KEYSTONE" => {
let mut formatter = IntelFormatter::new();
formatter.options_mut().set_uppercase_keywords(false);
formatter
.options_mut()
.set_memory_size_options(iced_x86::MemorySizeOptions::Always);
formatter.options_mut().set_hex_prefix("0x");
formatter.options_mut().set_hex_suffix("");
formatter.format(inst, &mut result);
}
"NASM" => {
let mut formatter = NasmFormatter::new();
formatter.format(inst, &mut result);
}
"MASM" => {
let mut formatter = MasmFormatter::new();
formatter.format(inst, &mut result);
}
"INTEL" => {
let mut formatter = IntelFormatter::new();
formatter.format(inst, &mut result);
}
"GAS" => {
let mut formatter = GasFormatter::new();
formatter.format(inst, &mut result);
}
_ => {
error!("Unknown disassembler syntax: {}", syntax);
panic!("Instruction formatting failed!")
}
};
result
}
pub fn disassemble(
bytes: &[u8],
bitness: u32,
start_addr: u64,
syntax: String,
) -> Result<String, DeoptimizerError> {
info!(
"Disassembling at -> 0x{:016X} (mode={})",
start_addr, bitness
);
let mut result = String::new();
let mut decoder = Decoder::with_ip(bitness, bytes, start_addr, DecoderOptions::NONE);
let mut inst = Instruction::default();
while decoder.can_decode() {
decoder.decode_out(&mut inst);
if inst.is_invalid() {
warn!(
"Inlining invalid instruction bytes at: 0x{:016X}",
inst.ip()
);
let start_index = (inst.ip() - start_addr) as usize;
let instr_bytes = &bytes[start_index..start_index + inst.len()];
result += &format!("loc_{:016X}: {}\n", inst.ip(), to_db_mnemonic(instr_bytes));
continue;
}
let temp = format_instruction(syntax.clone(), &inst);
let nbt = inst.near_branch_target();
if nbt != 0 {
result += &format!(
"loc_{:016X}: {} {}\n",
inst.ip(),
temp.split(' ').next().unwrap(),
&format!("loc_{:016X}", nbt)
);
continue;
}
result += &format!("loc_{:016X}: {}\n", inst.ip(), temp);
}
Ok(result)
}
+1
View File
@@ -1,4 +1,5 @@
mod deoptimizer;
pub mod disassembler;
mod helpers;
mod tests;
mod tracer;
+1 -1
View File
@@ -529,7 +529,7 @@ mod tests {
}
#[test]
fn test_trace_exec_flow() {
fn test_trace() {
let code_64: &[u8] = &[
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xcc, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50,
0x52, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52, 0x60, 0x51, 0x48, 0x8b, 0x52, 0x18,
+26 -85
View File
@@ -1,51 +1,15 @@
use crate::x86_64::*;
use iced_x86::code_asm::*;
use iced_x86::*;
use log::{error, info, trace, warn};
use log::{error, trace};
use regex::bytes::Regex;
use std::collections::HashMap;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum TracerError {
// #[error("Instruction with unexpected operand count.")]
// UnexpectedOperandCount,
// #[error("Given instruction not found in code map.")]
// InstructionNotFound,
// #[error("Code analysis results are not found.")]
// MissingCodeAnalysis,
// #[error("Near branch value too large.")]
// NearBranchTooBig,
// #[error("Unexpected memory size given.")]
// UnexpectedMemorySize,
// #[error("Offset skipping failed!")]
// OffsetSkipFail,
#[error("Unknown stack operation.")]
UnknownStackOp,
#[error("Invalid formatter syntax.")]
InvalidSyntax,
#[error("Invalid processor mode(bitness). (16/32/64 accepted)")]
InvalidProcessorMode,
#[error("All available instruction transform gadgets failed.")]
AllTransformsFailed,
#[error("Far branch value too large.")]
FarBranchTooBig,
#[error("Found invalid instruction.")]
InvalidInstruction,
#[error("Branch target not found.")]
BracnhTargetNotFound,
#[error("This transform not possible for given instruction.")]
TransformNotPossible,
#[error("Unexpected register size given.")]
UnexpectedRegisterSize,
#[error("Unexpected operand type encountered.")]
UnexpectedOperandType,
#[error("No GP register found with given parameters.")]
RegisterNotFound,
#[error("Instruction transpose attempt failed.")]
TransposeFailed,
#[error("Invalid transform gadget.")]
InvalidTransformGadget,
#[error("Instruction encoding failed: {0}")]
EncodingFail(#[from] IcedError),
}
@@ -81,29 +45,29 @@ pub struct TraceResults {
pub coverage_whitout_strings: f64,
}
impl TraceResults {
pub fn print_dead_code(&self) {
let mut last = 0;
for (i, b) in self.bytes.iter().enumerate() {
if self.active_offsets.contains(&(i as u64)) {
continue;
}
if i - last != 1 {
print!("\n0x{:016X}:\t", i);
}
if *b >= 0x20 && *b <= 0x7E {
print!("{}", String::from_utf8_lossy(&[*b]));
} else if *b == 0x00 {
continue;
} else {
print!("\\x{:X}", b);
}
last = i
}
println!("\n");
}
}
// impl TraceResults {
// pub fn print_dead_code(&self) {
// let mut last = 0;
// for (i, b) in self.bytes.iter().enumerate() {
// if self.active_offsets.contains(&(i as u64)) {
// continue;
// }
// if i - last != 1 {
// print!("\n0x{:016X}:\t", i);
// }
//
// if *b >= 0x20 && *b <= 0x7E {
// print!("{}", String::from_utf8_lossy(&[*b]));
// } else if *b == 0x00 {
// continue;
// } else {
// print!("\\x{:X}", b);
// }
// last = i
// }
// println!("\n");
// }
// }
impl Tracer {
fn new(bytes: &[u8], bitness: u32) -> Self {
@@ -121,7 +85,7 @@ impl Tracer {
pso.push(o as u64);
}
}
info!("Found {} possible strings.", ps.len());
// info!("Found {} possible strings.", ps.len());
Self {
bytes: bytes.to_vec(),
bitness,
@@ -141,7 +105,7 @@ impl Tracer {
}
fn trace_code_paths(&mut self, start_offset: u64) -> Result<HaltResason, TracerError> {
info!("[TRACER] Tracing -> 0x{:016X}", start_offset);
trace!("[TRACER] Tracing -> 0x{:016X}", start_offset);
let mut ip = start_offset;
loop {
if ip >= self.bytes.len() as u64 {
@@ -293,32 +257,9 @@ fn is_conditional_branch(inst: Instruction) -> bool {
|| inst.is_loopcc()
}
// NO_BRANCH
// - Check if the stack is not empty
// - Check if current instruction is POP
// - Save poped address in current context with register
// - Pop the value from stack
// - Add to cf_addr_map
// - Add to active_offsets
// - Advance to next_ip
// CONDITIONAL_BRANCH
// - Step-in
// - Advance to next_ip
// ADDRESS_CALL
// - Push next_ip to stack
// - Step-in
// - Continue if HaltResason is Return
// REGISTER_CALL
// - Check if current context contains the register value
// - Step-in
// - Halt->DynamicBranch
//
pub fn trace(bytes: &[u8], bitness: u32, start_addr: u64) -> Result<TraceResults, TracerError> {
let mut tracer = Tracer::new(bytes, bitness);
info!("Tracing execution flow and possible code paths...");
tracer.trace_code_paths(start_addr)?;
info!("Done tracing!");
tracer.active_offsets.sort_unstable();
tracer.active_offsets.dedup();