diff --git a/crates/vestal/src/main.rs b/crates/vestal/src/main.rs index 2ca7700..14b5624 100644 --- a/crates/vestal/src/main.rs +++ b/crates/vestal/src/main.rs @@ -10,11 +10,12 @@ fn main () -> Usually<()> { use clap::{arg, command, value_parser, ArgAction, Command}; let matches = command!() .arg(arg!([path] "Path to VST DLL").value_parser(value_parser!(PathBuf))) - .arg(arg!(-i --inspect "Show info, don't run").required(false)) //.arg(arg!(-s --stub "Provide a stub import").required(false)) + .arg(arg!(-i --inspect "Show info, don't run").required(false)) + .arg(arg!(-v --verbose "Show a lot of info").required(false)) .get_matches(); - let path = matches.get_one::("path") - .unwrap_or_else(||panic!("Pass a path to a VST DLL.")); + let path = matches.get_one::("path").unwrap_or_else(||panic!("pass path to VST DLL")); + let verbose = *(matches.get_one::("verbose").unwrap_or(&false)); let mut rebuilder = Rebuilder::new(&[ std::env::current_dir()?, canonicalize(path.clone().parent().expect("invalid parent path"))?, @@ -26,9 +27,9 @@ fn main () -> Usually<()> { } let path = rebuilder.find(path.to_str().expect("path must be unicode"), false)? .unwrap_or_else(||panic!("Could not find: {path:?}")); - let name = rebuilder.load(&path, true)?; + let name = rebuilder.load(&path, true, verbose)?; let main = rebuilder.dlls.get(&name).unwrap(); - rebuilder.resolve_calls(&main, true, true)?; + rebuilder.resolve_calls(&main, true, verbose)?; Ok(()) } @@ -49,7 +50,7 @@ impl Rebuilder { ..Default::default() } } - fn load (&mut self, path: &impl AsRef, recurse: bool) -> Usually> { + fn load (&mut self, path: &impl AsRef, recurse: bool, verbose: bool) -> Usually> { let path: Arc = Arc::from(PathBuf::from(path.as_ref())); if self.visited.contains(&path) { let name = path.file_name().expect("no file name"); @@ -57,12 +58,12 @@ impl Rebuilder { return Ok(name) } self.visited.insert(path.clone()); - let dll = Arc::new(Dll::new(&Arc::new(PathBuf::from(path.as_ref())), false)?); + let dll = Arc::new(Dll::new(&Arc::new(PathBuf::from(path.as_ref())), verbose)?); self.dlls.insert(dll.name.clone(), dll.clone()); if recurse { for dep in dll.deps_by_library.keys() { - if let Some(dep) = self.find(dep, false)? { - self.load(&dep, recurse)?; + if let Some(dep) = self.find(dep, verbose)? { + self.load(&dep, recurse, verbose)?; } else { panic!("not found: {dep:?}"); } @@ -97,43 +98,45 @@ impl Rebuilder { &self, dll: &Dll, addr: u32, call: &Arc, recurse: bool, verbose: bool, ) -> Usually<()> { let addr = (addr - dll.code_base) as usize; - if verbose { + //if verbose { println!("--------------------------------"); dll.print_call(addr, call.module.as_ref(), call.method.as_ref()); dll.print_hex(addr, 1); - } + //} if let Some(method) = dll.parse_call(call) { let module_name = call.module.as_ref().unwrap(); let method_name = call.method.as_ref().unwrap(); - println!("0x{method:>08x} {module_name:20} {method_name}"); + println!("{BOLD}0x{method:>08x}{RESET} {module_name:20} {method_name}"); if let Some(path) = self.find(module_name, false)? { let name = path.file_name().expect("no file name"); let name: Arc = name.to_str().map(Arc::from).expect("non-unicode filename"); if let Some(dll) = self.dlls.get(&name) { if let Some(thunk) = dll.exports.get(method_name) { if let ThunkData::Function(rva) = thunk { - println!("# found {:?}::{} at 0x{:>08x}", &dll.name, method_name, rva.0); let addr = (rva.0 - dll.code_base) as usize; - dll.print_call(addr, None, None); + println!(" ⋮ "); + println!("{BOLD}0x{:>08x}{RESET} {}::{} found", rva.0, &dll.name, method_name); dll.print_hex(addr, 1); if recurse { let mut decoder = Decoder::with_ip( - 64, &dll.code[addr..], 0x1000, DecoderOptions::NONE + 64, &dll.code[addr..], 0, DecoderOptions::NONE ); while decoder.can_decode() { - let position = decoder.position(); + let position = decoder.position(); let instruction = decoder.decode(); - //println!("..."); dll.print_call(addr + position, None, None); - //dll.print_hex(addr, 0); + let opcodes = &dll.code[position..position+instruction.len()]; + if Call::matches(&instruction) && !Call::skip(opcodes) { + let start = 0x0; + let offset = (position + start) as u32; + let offset_rva = dll.pe.offset_to_rva(Offset(offset))?.0 as u32; + println!(" └--> {:?}", Call::target(opcodes, 0)); + } + if dll.code[addr + position] == 0xc3 { + break + } } } - //println!("{:?}", &dll.code[addr..addr + 128].hex_dump()); - //if let Some(path) = self.find(&dll.name, true)? { - //println!("# at {path:?}"); - //} else { - //panic!("# not found {:?}", &dll.name); - //} } else { panic!("# unsupported {thunk:?}"); } @@ -190,12 +193,15 @@ impl Dll { let mut calls_by_target = Default::default(); let mut deps_by_library = Default::default(); let mut deps_by_address = Default::default(); - let exports = collect_exports(&pe).unwrap_or_default(); + let exports = collect_exports( + &pe, + verbose + ).unwrap_or_default(); let (modules_count, methods_count) = collect_deps( &mut deps_by_library, &mut deps_by_address, &pe, - false + verbose, )?; let calls = collect_calls( &mut calls_by_source, @@ -205,7 +211,7 @@ impl Dll { &pe, start, text, - false + verbose )?; Ok(Self { name: name.clone(), @@ -276,7 +282,7 @@ fn collect_deps ( deps_by_address.insert(call_via, (module_name.clone(), method.clone())); methods += 1; if verbose { - println!(" ({index:5} 0x{call_via:08x} {module_name:>20} {method})"); + println!(" (import 0x{call_via:08x} {module_name}::{method})"); } } } @@ -295,7 +301,8 @@ fn collect_deps ( } fn collect_exports ( - pe: &VecPE + pe: &VecPE, + _verbose: bool, ) -> Usually, ThunkData>> { Ok(ImageExportDirectory::parse(pe)? .get_export_map(pe)? diff --git a/crates/vestal/src/show.rs b/crates/vestal/src/show.rs index 3bc0077..cfa8686 100644 --- a/crates/vestal/src/show.rs +++ b/crates/vestal/src/show.rs @@ -1,34 +1,82 @@ use crate::*; +use std::fmt::Write; impl Dll { pub fn print_call (&self, addr: usize, module: Option<&Arc>, method: Option<&Arc>) { let mut decoder = Decoder::with_ip(64, &self.code[addr..], 0x1000, DecoderOptions::NONE); let instruction = decoder.decode(); - let opcodes = &self.code[addr..addr+instruction.len()].iter() - .map(|x|format!("{x:02x}")) - .join(" "); - println!("{BOLD}0x{addr:>08x}{RESET} {:20} {DIM}{opcodes}{RESET} {BOLD}{instruction}{RESET} {module:?} {method:?}", &self.name); + let is_stack = instruction.is_stack_instruction(); + let is_link = Call::matches(&instruction); + let style = if is_stack || is_link { BOLD } else { DIM }; + print!("{style}0x{addr:>08x} {:20}{RESET}", &self.name); + print!(" {style}{:26}{RESET}", Self::fmt_bytes(&self.code[addr..addr+instruction.len()])); + println!(" {style}{instruction}{RESET}"); + if module.is_some() || method.is_some() { + println!(" └--> {}{}{}", + module.map(|x|x.as_ref()).unwrap_or(&""), + if module.is_some() && method.is_some() { "::" } else { "" }, + method.map(|x|x.as_ref()).unwrap_or(&"")); + } } - pub fn print_hex (&self, addr: usize, n: usize) { - let cfg = HexConfig {title: false, width: 16, group: 4, chunk: 1, ..HexConfig::default()}; - let snap = |x|(x/16)*16; - let a = snap(addr - 16*n); - let b = addr; - let c = snap(addr + 16); - let d = snap(c + 16*n); - if n > 0 { - println!("{DIM}{:?}{RESET}", &self.code[a..b].hex_conf(HexConfig { - display_offset: self.code_base as usize + a, ..cfg - })); - } - println!("{BOLD}{:?}{RESET}", &self.code[b..c].hex_conf(HexConfig { - display_offset: self.code_base as usize + b, ..cfg - })); - if n > 0 { - println!("{DIM}{:?}{RESET}", &self.code[c..d].hex_conf(HexConfig { - display_offset: self.code_base as usize + c, ..cfg - })); + pub fn print_hex (&self, addr: usize, context: usize) { + let base = self.code_base as usize; + let line = 16; + let group = 2; + let snap = |x|(x/line)*line; + let byte_start = snap(addr).saturating_sub(context*line); + let byte_end = snap(addr) + (context + 1) * line; + let mut output = String::new(); + for (index, byte) in (byte_start..byte_end).enumerate() { + write!(&mut output, "{DIM}"); + if byte % line == 0 { + if (byte >= snap(addr)) && (byte < snap(addr) + line) { + write!(&mut output, "{RESET}{BOLD}"); + } + write!(&mut output, " {byte:08x}"); + } + write!(&mut output, "{DIM}"); + if byte % group == 0 { + write!(&mut output, " "); + } + if (byte >= addr) && (byte < addr + 8) { + write!(&mut output, "{RESET}{BOLD}"); + } + write!(&mut output, "{:02x}", self.code[byte]); + if byte % line == line - 1 { + write!(&mut output, "\n"); + } } + println!("{output}"); + //let cfg = HexConfig {title: false, width: 16, group: 4, chunk: 1, ..HexConfig::default()}; + //if n > 0 { + //println!(" {DIM}{}{RESET}", Self::fmt_hex_line(a, &self.code[a..b])); + //} + //println!(" {BOLD}{}{RESET}", Self::fmt_hex_line(b, &self.code[b..c])); + //if n > 0 { + //println!(" {DIM}{}{RESET}", Self::fmt_hex_line(c, &self.code[c..d])); + //} + } + //pub fn fmt_hex_block (data: &[u8], addr: usize, length: usize, context: usize) -> Arc { + //let start = addr; + //let end = addr + length; + //let line = 16; + //let snap = |x|(x/line)*line; + //let addr = snap(addr); + //let prev = addr - line * context; + //let next = addr + line * (context + 1); + //if n > 0 { + //println!(" {DIM}{}{RESET}", Self::fmt_hex_line(prev, &self.code[prev..addr])); + //} + //println!(" {BOLD}{}{RESET}", Self::fmt_hex_line(b, &self.code[addr..addr+length])); + //if n > 0 { + //println!(" {DIM}{}{RESET}", Self::fmt_hex_line(c, &self.code[addr+length..next])); + //} + //} + pub fn fmt_hex_line (addr: usize, data: &[u8]) -> Arc { + format!("{:>08x} {}", addr, Self::fmt_bytes(data)).into() + } + pub fn fmt_bytes (bytes: &[u8]) -> Arc { + bytes.iter().map(|x|format!("{x:02x}")).join(" ").into() } }