From 3609975188bf2203935adb0d39aa60b1e1716618 Mon Sep 17 00:00:00 2001 From: Corbin Crutchley Date: Fri, 24 Jul 2026 07:57:51 -0700 Subject: [PATCH] feat: accelerate guest execution diagnostics --- crates/aarch64/src/lib.rs | 59 +++++++++++++++++++++++++-------- crates/cli/src/main.rs | 38 +++++++++++++++++---- crates/cli/tests/run.rs | 2 ++ crates/linux-abi/src/lib.rs | 3 ++ crates/linux-runtime/src/lib.rs | 2 ++ 5 files changed, 85 insertions(+), 19 deletions(-) diff --git a/crates/aarch64/src/lib.rs b/crates/aarch64/src/lib.rs index f71f813..117f026 100644 --- a/crates/aarch64/src/lib.rs +++ b/crates/aarch64/src/lib.rs @@ -4,7 +4,10 @@ //! inputs. Public consumers see only binarrow state, memory, stop, and trap //! types, keeping Linux services independent of the decoder implementation. -use std::{collections::BTreeMap, sync::OnceLock}; +use std::{ + collections::BTreeMap, + sync::{Arc, OnceLock}, +}; use binarrow_guest_memory::{AddressSpace, MemoryError}; use binarrow_runtime_core::{GuestAddress, MemoryAccess, ResourceLimit, Trap}; @@ -15,6 +18,7 @@ const GENERAL_REGISTER_COUNT: usize = 31; const VECTOR_REGISTER_COUNT: usize = 32; const INSTRUCTION_SIZE: u64 = 4; const INSTRUCTION_BYTES: usize = 4; +const MAX_DECODE_CACHE_ENTRIES: usize = 65_536; const NZCV_MASK: u32 = 0xf000_0000; const NEGATIVE_FLAG_BIT: u32 = 31; const ZERO_FLAG_BIT: u32 = 30; @@ -296,6 +300,13 @@ pub struct RunResult { pub struct Interpreter { language: &'static SleighData, runtime: SleighRuntime, + decode_cache: BTreeMap<(u64, u32), CachedInstruction>, +} + +#[derive(Clone)] +struct CachedInstruction { + next_pc: GuestAddress, + operations: Arc<[Operation]>, } impl Interpreter { @@ -309,6 +320,7 @@ impl Interpreter { Ok(Self { language: language()?, runtime: SleighRuntime::new(0), + decode_cache: BTreeMap::new(), }) } @@ -334,23 +346,37 @@ impl Interpreter { .fetch_exact(pc, &mut bytes) .map_err(|error| memory_trap(&error, pc, MemoryAccess::Execute))?; let encoding = u32::from_le_bytes(bytes); - self.runtime - .decode(self.language, pc.get(), &bytes) - .ok_or(Trap::UnsupportedInstruction { pc, encoding })?; - let next_pc = GuestAddress::new(self.runtime.get_instruction().inst_next); - let block = self - .runtime - .lift(self.language) - .map_err(|_| Trap::UnsupportedInstruction { pc, encoding })?; - let operations = lower_block(self.language, &block.instructions) - .ok_or(Trap::UnsupportedInstruction { pc, encoding })?; + let cache_key = (pc.get(), encoding); + let cached = if let Some(cached) = self.decode_cache.get(&cache_key) { + cached.clone() + } else { + self.runtime + .decode(self.language, pc.get(), &bytes) + .ok_or(Trap::UnsupportedInstruction { pc, encoding })?; + let next_pc = GuestAddress::new(self.runtime.get_instruction().inst_next); + let block = self + .runtime + .lift(self.language) + .map_err(|_| Trap::UnsupportedInstruction { pc, encoding })?; + let operations: Arc<[Operation]> = lower_block(self.language, &block.instructions) + .ok_or(Trap::UnsupportedInstruction { pc, encoding })? + .into(); + let cached = CachedInstruction { + next_pc, + operations, + }; + if self.decode_cache.len() < MAX_DECODE_CACHE_ENTRIES { + self.decode_cache.insert(cache_key, cached.clone()); + } + cached + }; let mut candidate = state.clone(); let semantic_outcome = - execute_operations(&mut candidate, memory, &operations, pc, encoding)?; + execute_operations(&mut candidate, memory, &cached.operations, pc, encoding)?; let resolved_next_pc = match semantic_outcome { SemanticOutcome::Branch(target) => target, - SemanticOutcome::Advanced | SemanticOutcome::SupervisorCall(_) => next_pc, + SemanticOutcome::Advanced | SemanticOutcome::SupervisorCall(_) => cached.next_pc, }; candidate.set_pc(resolved_next_pc); *state = candidate; @@ -2237,6 +2263,13 @@ mod tests { )); assert_eq!(state.x(0), Some(43)); assert_eq!(state.pc(), GuestAddress::new(0x1008)); + assert_eq!(interpreter.decode_cache.len(), 2); + + state.set_pc(CODE_ADDRESS); + state.set_x(0, 0).unwrap(); + interpreter.step(&mut state, &mut memory).unwrap(); + assert_eq!(state.x(0), Some(42)); + assert_eq!(interpreter.decode_cache.len(), 2); } #[test] diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index c373679..c000a10 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,6 +1,6 @@ use std::{ env, - ffi::OsString, + ffi::{OsStr, OsString}, fs, io::{self, Write}, path::PathBuf, @@ -12,7 +12,7 @@ use binarrow_linux_runtime::Process; use binarrow_loader::{Credentials, ProcessConfig, ProcessParameters, load_process}; use binarrow_memory_fs::MemoryFileSystem; -const USAGE: &str = "usage: binarrow inspect \n binarrow run [guest arguments...]\n binarrow trace [guest arguments...]"; +const USAGE: &str = "usage: binarrow inspect \n binarrow run [--instruction-budget ] [guest arguments...]\n binarrow trace [--instruction-budget ] [guest arguments...]"; fn main() -> ExitCode { match run(env::args_os().skip(1)) { @@ -26,17 +26,17 @@ fn main() -> ExitCode { fn run(mut arguments: impl Iterator) -> Result { let command = arguments.next().ok_or(USAGE)?; - let path = PathBuf::from(arguments.next().ok_or(USAGE)?); match command.to_str() { Some("inspect") => { + let path = PathBuf::from(arguments.next().ok_or(USAGE)?); if arguments.next().is_some() { return Err(USAGE.to_owned()); } inspect(&path)?; Ok(0) } - Some("run") => run_guest(&path, arguments, false), - Some("trace") => run_guest(&path, arguments, true), + Some("run") => run_command(arguments, false), + Some("trace") => run_command(arguments, true), _ => Err(format!( "unknown command {}; {USAGE}", command.to_string_lossy() @@ -44,6 +44,28 @@ fn run(mut arguments: impl Iterator) -> Result { } } +fn run_command( + mut arguments: impl Iterator, + print_trace: bool, +) -> Result { + let first = arguments.next().ok_or(USAGE)?; + let (instruction_budget, path) = if first == OsStr::new("--instruction-budget") { + let value = arguments.next().ok_or(USAGE)?; + let value = value + .to_str() + .ok_or("instruction budget must be UTF-8")? + .parse::() + .map_err(|_| "instruction budget must be a positive integer")?; + if value == 0 { + return Err("instruction budget must be a positive integer".to_owned()); + } + (Some(value), PathBuf::from(arguments.next().ok_or(USAGE)?)) + } else { + (None, PathBuf::from(first)) + }; + run_guest(&path, arguments, print_trace, instruction_budget) +} + fn inspect(path: &PathBuf) -> Result<(), String> { let bytes = fs::read(path).map_err(|error| format!("could not read {}: {error}", path.display()))?; @@ -60,6 +82,7 @@ fn run_guest( path: &PathBuf, arguments: impl Iterator, print_trace: bool, + instruction_budget: Option, ) -> Result { let bytes = fs::read(path).map_err(|error| format!("could not read {}: {error}", path.display()))?; @@ -68,7 +91,10 @@ fn run_guest( .map_err(|error| format!("could not initialize guest randomness: {error}"))?; let mut argv = vec![path.to_string_lossy().as_bytes().to_vec()]; argv.extend(arguments.map(|argument| argument.to_string_lossy().as_bytes().to_vec())); - let config = ProcessConfig::default(); + let mut config = ProcessConfig::default(); + if let Some(instruction_budget) = instruction_budget { + config.limits.instruction_budget = instruction_budget; + } let mut filesystem = MemoryFileSystem::new(config.limits.max_filesystem_bytes); let image = load_process( &bytes, diff --git a/crates/cli/tests/run.rs b/crates/cli/tests/run.rs index bad5766..2679d3e 100644 --- a/crates/cli/tests/run.rs +++ b/crates/cli/tests/run.rs @@ -51,6 +51,8 @@ fn trace_command_prints_syscalls_after_guest_output() { let fixture = TempFixture::new("trace", &hello_aarch64_elf(0)); let output = Command::new(env!("CARGO_BIN_EXE_binarrow")) .arg("trace") + .arg("--instruction-budget") + .arg("10") .arg(&fixture.path) .output() .expect("binarrow should start"); diff --git a/crates/linux-abi/src/lib.rs b/crates/linux-abi/src/lib.rs index 15e0db0..08660bf 100644 --- a/crates/linux-abi/src/lib.rs +++ b/crates/linux-abi/src/lib.rs @@ -22,6 +22,7 @@ pub enum Syscall { Sigaltstack = 132, RtSigaction = 134, RtSigprocmask = 135, + Gettid = 178, Munmap = 215, Mmap = 222, Mprotect = 226, @@ -51,6 +52,7 @@ impl Syscall { 132 => Some(Self::Sigaltstack), 134 => Some(Self::RtSigaction), 135 => Some(Self::RtSigprocmask), + 178 => Some(Self::Gettid), 215 => Some(Self::Munmap), 222 => Some(Self::Mmap), 226 => Some(Self::Mprotect), @@ -117,6 +119,7 @@ mod tests { assert_eq!(Syscall::from_number(132), Some(Syscall::Sigaltstack)); assert_eq!(Syscall::from_number(134), Some(Syscall::RtSigaction)); assert_eq!(Syscall::from_number(135), Some(Syscall::RtSigprocmask)); + assert_eq!(Syscall::from_number(178), Some(Syscall::Gettid)); assert_eq!(Syscall::from_number(215), Some(Syscall::Munmap)); assert_eq!(Syscall::from_number(222), Some(Syscall::Mmap)); assert_eq!(Syscall::from_number(226), Some(Syscall::Mprotect)); diff --git a/crates/linux-runtime/src/lib.rs b/crates/linux-runtime/src/lib.rs index 65ae844..48dca3e 100644 --- a/crates/linux-runtime/src/lib.rs +++ b/crates/linux-runtime/src/lib.rs @@ -168,6 +168,7 @@ impl fmt::Display for SyscallEvent { self.arguments[0] )?; } + Some(Syscall::Gettid) => write!(formatter, "gettid()")?, Some(syscall @ (Syscall::ClockGettime | Syscall::Getrandom)) => { format_system_syscall(formatter, syscall, self.arguments)?; } @@ -547,6 +548,7 @@ impl Process { self.clear_child_tid = Some(GuestAddress::new(self.register(0))); self.set_return(MAIN_THREAD_ID); } + Some(Syscall::Gettid) => self.set_return(MAIN_THREAD_ID), Some(Syscall::ClockGettime) => self.dispatch_clock_gettime(system), Some(Syscall::SchedGetaffinity) => self.dispatch_sched_getaffinity(), Some(Syscall::Sigaltstack) => self.dispatch_sigaltstack(), -- 2.51.2