From 82d113f2a71e8b5e43437cfa227d5bf2761700f6 Mon Sep 17 00:00:00 2001 From: Corbin Crutchley Date: Fri, 24 Jul 2026 10:27:59 -0700 Subject: [PATCH] feat: execute CPython scripts --- PLAN.md | 2 + crates/aarch64/src/lib.rs | 113 ++++++++ crates/cli/src/main.rs | 45 +++- crates/cli/tests/run.rs | 2 + crates/linux-abi/src/lib.rs | 6 + crates/linux-runtime/src/lib.rs | 247 +++++++++++++++++- .../ghidra-aarch64/languages/AARCH64base.sinc | 13 +- .../languages/AARCH64instructions.sinc | 4 +- 8 files changed, 411 insertions(+), 21 deletions(-) diff --git a/PLAN.md b/PLAN.md index 7eaa285..439a80b 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1857,6 +1857,8 @@ The Phase 2 control checkpoint is complete: the Worker accepts explicit C, Rust, Phase 2 is complete. `binarrow-memory-fs` supplies a bounded ephemeral `/tmp` regular-file store behind the project host filesystem trait, while `binarrow-linux-runtime` owns guest descriptors and the observed `openat`, `close`, `lseek`, `read`, and file `write` ABI. A freestanding C fixture creates, writes, seeks, reads, and closes `/tmp/roundtrip.txt` before printing the recovered bytes; native, CLI, and Chromium tests all run the identical ELF. ADR-0002 records the interpreter's interim browser memory design: project-owned sparse 64-bit guest mappings backed by bounded Wasm32 allocations, with Memory64 retained as a required feature gate for the future translator. Every Phase 2 deliverable and acceptance criterion now has an automated regression. Phase 3 begins with a mountable VFS, directory and metadata syscalls, OPFS persistence, terminal input, clock/random services, and the suspension model needed by CPython. +The first CPython checkpoint is complete in the native runtime. A static AArch64 CPython 3.12.13 image loads the packaged standard library, compiles `encodings`, executes `-c 'print(6 * 7)'`, prints `42`, and exits 0 under a reproducible CLI random seed. The observed path fixed negative `ADR`/`ADRP` offsets and BFXIL field preservation, and added `writev`, descriptor `fstat`, and standard-descriptor `fcntl` behavior. The source-only runtime image still reports the expected missing platform-dependent `lib-dynload` directory; packaging extension modules and adding this long-running command as an automated fixture are the next CPython tasks. + Do not begin the full web IDE before item 30 passes. --- diff --git a/crates/aarch64/src/lib.rs b/crates/aarch64/src/lib.rs index 6575596..5f455ba 100644 --- a/crates/aarch64/src/lib.rs +++ b/crates/aarch64/src/lib.rs @@ -19,6 +19,7 @@ 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 RECENT_PC_COUNT: usize = 64; const NZCV_MASK: u32 = 0xf000_0000; const NEGATIVE_FLAG_BIT: u32 = 31; const ZERO_FLAG_BIT: u32 = 30; @@ -305,6 +306,9 @@ pub struct Interpreter { language: &'static SleighData, runtime: SleighRuntime, decode_cache: BTreeMap<(u64, u32), CachedInstruction>, + recent_pcs: [GuestAddress; RECENT_PC_COUNT], + recent_pc_cursor: usize, + recent_pc_count: usize, } #[derive(Clone)] @@ -325,9 +329,25 @@ impl Interpreter { language: language()?, runtime: SleighRuntime::new(0), decode_cache: BTreeMap::new(), + recent_pcs: [GuestAddress::NULL; RECENT_PC_COUNT], + recent_pc_cursor: 0, + recent_pc_count: 0, }) } + /// Return the most recently attempted instruction addresses, oldest first. + #[must_use] + pub fn recent_pcs(&self) -> Vec { + let start = if self.recent_pc_count == RECENT_PC_COUNT { + self.recent_pc_cursor + } else { + 0 + }; + (0..self.recent_pc_count) + .map(|offset| self.recent_pcs[(start + offset) % RECENT_PC_COUNT]) + .collect() + } + /// Fetch, decode, and interpret one instruction. /// /// # Errors @@ -341,6 +361,9 @@ impl Interpreter { memory: &mut AddressSpace, ) -> Result { let pc = state.pc(); + self.recent_pcs[self.recent_pc_cursor] = pc; + self.recent_pc_cursor = (self.recent_pc_cursor + 1) % RECENT_PC_COUNT; + self.recent_pc_count = (self.recent_pc_count + 1).min(RECENT_PC_COUNT); if !pc.get().is_multiple_of(INSTRUCTION_SIZE) { return Err(Trap::InstructionAlignment { pc }); } @@ -2679,6 +2702,96 @@ mod tests { assert_eq!(state.pc(), branch.next_pc); } + #[test] + fn sign_extends_cpython_negative_adrp_offsets() { + const PC: GuestAddress = GuestAddress::new(0x12e_de6c); + const CODE: &[u8] = &[ + 0xab, 0xeb, 0xff, 0xd0, // adrp x11, 0x1063000 + ]; + let mut memory = AddressSpace::new(4); + memory + .map_zeroed(PC, 4, Permissions::READ_EXECUTE, RegionKind::Anonymous) + .unwrap(); + memory.initialize(PC, CODE).unwrap(); + let mut state = Aarch64State::new(PC, GuestAddress::new(0x8000)); + let mut interpreter = Interpreter::new().unwrap(); + + interpreter.step(&mut state, &mut memory).unwrap(); + + assert_eq!(state.x(11), Some(0x106_3000)); + } + + #[test] + fn preserves_cpython_dispatch_registers_while_building_the_jump_target() { + const PC: GuestAddress = GuestAddress::new(0x12e_de68); + const CODE: &[u8] = &[ + 0x88, 0x06, 0x00, 0x51, // sub w8, w20, #1 + 0xab, 0xeb, 0xff, 0xd0, // adrp x11, 0x1063000 + 0x6b, 0x81, 0x1b, 0x91, // add x11, x11, #0x6e0 + 0x09, 0x01, 0x00, 0x10, // adr x9, 0x12ede94 + ]; + let mut memory = AddressSpace::new(CODE.len() as u64); + memory + .map_zeroed( + PC, + CODE.len() as u64, + Permissions::READ_EXECUTE, + RegionKind::Anonymous, + ) + .unwrap(); + memory.initialize(PC, CODE).unwrap(); + let mut state = Aarch64State::new(PC, GuestAddress::new(0x8000)); + state.set_x(23, 0x3fe5_18dc).unwrap(); + state.set_x(24, 0x3fe5_18dc).unwrap(); + let mut interpreter = Interpreter::new().unwrap(); + + for _ in 0..4 { + interpreter.step(&mut state, &mut memory).unwrap(); + } + + assert_eq!(state.x(9), Some(0x12e_de94)); + assert_eq!(state.x(11), Some(0x106_36e0)); + assert_eq!(state.x(23), Some(0x3fe5_18dc)); + assert_eq!(state.x(24), Some(0x3fe5_18dc)); + } + + #[test] + fn executes_cpython_exception_table_bitfield_insert() { + const CODE: &[u8] = &[ + 0x55, 0x65, 0x1a, 0x33, // bfi w21, w10, #6, #26 + ]; + let mut memory = executable_memory(CODE); + let mut state = Aarch64State::new(CODE_ADDRESS, GuestAddress::new(0x8000)); + state.set_x(10, 1).unwrap(); + state.set_x(21, 2).unwrap(); + let mut interpreter = Interpreter::new().unwrap(); + + interpreter.step(&mut state, &mut memory).unwrap(); + + assert_eq!(state.x(21), Some(66)); + } + + #[test] + fn preserves_cpython_exception_table_continuation_bit() { + const CODE: &[u8] = &[ + 0x0c, 0x2d, 0x06, 0x33, // bfxil w12, w8, #6, #6 + 0x49, 0x05, 0x40, 0xb3, // bfxil x9, x10, #0, #2 + ]; + let mut memory = executable_memory(CODE); + let mut state = Aarch64State::new(CODE_ADDRESS, GuestAddress::new(0x8000)); + state.set_x(8, 589).unwrap(); + state.set_x(9, 0x40).unwrap(); + state.set_x(10, 3).unwrap(); + state.set_x(12, 0x40).unwrap(); + let mut interpreter = Interpreter::new().unwrap(); + + interpreter.step(&mut state, &mut memory).unwrap(); + interpreter.step(&mut state, &mut memory).unwrap(); + + assert_eq!(state.x(9), Some(0x43)); + assert_eq!(state.x(12), Some(0x49)); + } + #[test] fn reports_compiler_emitted_store_faults_without_advancing_state() { let mut memory = executable_memory(STORE_CODE); diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index b370270..841d6c3 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -15,12 +15,13 @@ 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 [run options] [guest arguments...]\n binarrow trace [run options] [guest arguments...]\n binarrow image pack [--guest-root ] \n\nrun options:\n --instruction-budget \n --filesystem-limit \n --filesystem-snapshot \n --argv0 "; +const USAGE: &str = "usage: binarrow inspect \n binarrow run [run options] [guest arguments...]\n binarrow trace [run options] [guest arguments...]\n binarrow image pack [--guest-root ] \n\nrun options:\n --instruction-budget \n --filesystem-limit \n --filesystem-snapshot \n --random-seed \n --argv0 "; struct RunOptions { instruction_budget: Option, filesystem_limit: Option, filesystem_snapshot: Option, + random_seed: Option, argv0: Option, executable: PathBuf, arguments: Vec, @@ -66,6 +67,7 @@ fn parse_run_options(arguments: impl Iterator) -> Result) -> Result { filesystem_snapshot = Some(PathBuf::from(arguments.next().ok_or(USAGE)?)); } + Some("--random-seed") => { + random_seed = Some(parse_positive_u64( + &arguments.next().ok_or(USAGE)?, + "random seed", + )?); + } Some("--argv0") => argv0 = Some(arguments.next().ok_or(USAGE)?), Some("--") => break PathBuf::from(arguments.next().ok_or(USAGE)?), Some(value) if value.starts_with('-') => { @@ -97,6 +105,7 @@ fn parse_run_options(arguments: impl Iterator) -> Result Result { let bytes = fs::read(&options.executable) .map_err(|error| format!("could not read {}: {error}", options.executable.display()))?; - let mut random_bytes = [0; 16]; - getrandom::fill(&mut random_bytes) - .map_err(|error| format!("could not initialize guest randomness: {error}"))?; + let random_bytes = if let Some(seed) = options.random_seed { + seeded_random_bytes(seed) + } else { + let mut bytes = [0; 16]; + getrandom::fill(&mut bytes) + .map_err(|error| format!("could not initialize guest randomness: {error}"))?; + bytes + }; let argv0 = options .argv0 .unwrap_or_else(|| options.executable.as_os_str().to_owned()); @@ -338,10 +352,31 @@ fn run_guest(options: RunOptions, print_trace: bool) -> Result { .map_err(|error| format!("could not write trace: {error}"))?; } } - let result = execution.map_err(|error| format!("{error}; pc={}", process.state().pc()))?; + let result = execution.map_err(|error| { + let mut recent_pcs = Vec::new(); + for address in process.recent_instruction_pcs() { + recent_pcs.push(address.get()); + } + format!( + "{error}; pc={}; instructions={}; recent_pcs={recent_pcs:#x?}", + process.state().pc(), + process.executed_instructions(), + ) + })?; Ok(result.exit_code) } +fn seeded_random_bytes(mut state: u64) -> [u8; 16] { + let mut bytes = [0; 16]; + for byte in &mut bytes { + state ^= state << 13; + state ^= state >> 7; + state ^= state << 17; + *byte = state.to_le_bytes()[0]; + } + bytes +} + struct NativeTerminal; impl HostTerminal for NativeTerminal { diff --git a/crates/cli/tests/run.rs b/crates/cli/tests/run.rs index 6d6a602..248b5bd 100644 --- a/crates/cli/tests/run.rs +++ b/crates/cli/tests/run.rs @@ -53,6 +53,8 @@ fn trace_command_prints_syscalls_after_guest_output() { .arg("trace") .arg("--instruction-budget") .arg("10") + .arg("--random-seed") + .arg("1") .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 8f02b50..745b976 100644 --- a/crates/linux-abi/src/lib.rs +++ b/crates/linux-abi/src/lib.rs @@ -15,8 +15,10 @@ pub enum Syscall { Lseek = 62, Read = 63, Write = 64, + Writev = 66, Readlinkat = 78, Newfstatat = 79, + Fstat = 80, Ppoll = 73, Exit = 93, ExitGroup = 94, @@ -50,8 +52,10 @@ impl Syscall { 63 => Some(Self::Read), 73 => Some(Self::Ppoll), 64 => Some(Self::Write), + 66 => Some(Self::Writev), 78 => Some(Self::Readlinkat), 79 => Some(Self::Newfstatat), + 80 => Some(Self::Fstat), 93 => Some(Self::Exit), 94 => Some(Self::ExitGroup), 96 => Some(Self::SetTidAddress), @@ -121,8 +125,10 @@ mod tests { assert_eq!(Syscall::from_number(62), Some(Syscall::Lseek)); assert_eq!(Syscall::from_number(63), Some(Syscall::Read)); assert_eq!(Syscall::from_number(64), Some(Syscall::Write)); + assert_eq!(Syscall::from_number(66), Some(Syscall::Writev)); assert_eq!(Syscall::from_number(78), Some(Syscall::Readlinkat)); assert_eq!(Syscall::from_number(79), Some(Syscall::Newfstatat)); + assert_eq!(Syscall::from_number(80), Some(Syscall::Fstat)); assert_eq!(Syscall::from_number(73), Some(Syscall::Ppoll)); assert_eq!(Syscall::from_number(93), Some(Syscall::Exit)); assert_eq!(Syscall::from_number(94), Some(Syscall::ExitGroup)); diff --git a/crates/linux-runtime/src/lib.rs b/crates/linux-runtime/src/lib.rs index 5953494..2339db2 100644 --- a/crates/linux-runtime/src/lib.rs +++ b/crates/linux-runtime/src/lib.rs @@ -54,6 +54,7 @@ const STAT_BLOCK_SIZE_OFFSET: usize = 56; const STAT_BLOCK_COUNT_OFFSET: usize = 64; const STAT_REGULAR_MODE: u32 = 0o100_644; const STAT_DIRECTORY_MODE: u32 = 0o040_755; +const STAT_CHARACTER_MODE: u32 = 0o020_620; const DIRECTORY_ENTRY_HEADER_SIZE: usize = 19; const DIRECTORY_ENTRY_ALIGNMENT: usize = 8; const DIRECTORY_TYPE: u8 = 4; @@ -180,6 +181,11 @@ impl fmt::Display for SyscallEvent { "read(fd={}, buf={:#x}, count={})", self.arguments[0], self.arguments[1], self.arguments[2], )?, + Some(Syscall::Fstat) => write!( + formatter, + "fstat(fd={}, statbuf={:#x})", + self.arguments[0], self.arguments[1], + )?, Some(Syscall::Ppoll) => write!( formatter, "ppoll(fds={:#x}, nfds={}, timeout={:#x}, sigmask={:#x}, sigsetsize={})", @@ -189,11 +195,9 @@ impl fmt::Display for SyscallEvent { self.arguments[3], self.arguments[4], )?, - Some(Syscall::Write) => write!( - formatter, - "write(fd={}, buf={:#x}, count={})", - self.arguments[0], self.arguments[1], self.arguments[2], - )?, + Some(syscall @ (Syscall::Write | Syscall::Writev)) => { + format_write_syscall(formatter, syscall, self.arguments)?; + } Some(Syscall::Exit) => write!(formatter, "exit(status={})", self.arguments[0])?, Some(Syscall::ExitGroup) => { write!(formatter, "exit_group(status={})", self.arguments[0])?; @@ -243,6 +247,26 @@ impl fmt::Display for SyscallEvent { } } +fn format_write_syscall( + formatter: &mut fmt::Formatter<'_>, + syscall: Syscall, + arguments: [u64; 6], +) -> fmt::Result { + match syscall { + Syscall::Write => write!( + formatter, + "write(fd={}, buf={:#x}, count={})", + arguments[0], arguments[1], arguments[2], + ), + Syscall::Writev => write!( + formatter, + "writev(fd={}, iov={:#x}, iovcnt={})", + arguments[0], arguments[1], arguments[2], + ), + _ => unreachable!("only write syscalls are delegated to the write formatter"), + } +} + fn format_memory_syscall( formatter: &mut fmt::Formatter<'_>, syscall: Syscall, @@ -392,6 +416,7 @@ pub struct Process { signal_mask: u64, next_mmap_address: GuestAddress, file_descriptors: BTreeMap, + descriptor_paths: BTreeMap>, descriptor_flags: BTreeMap, current_directory: Vec, executable_path: Vec, @@ -421,6 +446,7 @@ impl Process { signal_mask: 0, next_mmap_address: GuestAddress::new(MMAP_ARENA_START), file_descriptors: BTreeMap::new(), + descriptor_paths: BTreeMap::new(), descriptor_flags: BTreeMap::new(), current_directory: INITIAL_CURRENT_DIRECTORY.to_vec(), executable_path: image.executable_path, @@ -459,6 +485,12 @@ impl Process { self.output_bytes } + /// Return the most recently attempted guest instruction addresses. + #[must_use] + pub fn recent_instruction_pcs(&self) -> Vec { + self.interpreter.recent_pcs() + } + /// Run until `exit`/`exit_group`, a host failure, or a project resource trap. /// /// Unsupported syscall numbers return `-ENOSYS` in `x0` and execution @@ -609,6 +641,7 @@ impl Process { Some(Syscall::Lseek) => self.dispatch_lseek(filesystem), Some(Syscall::Readlinkat) => self.dispatch_readlinkat(filesystem), Some(Syscall::Newfstatat) => self.dispatch_newfstatat(filesystem), + Some(Syscall::Fstat) => self.dispatch_fstat(filesystem), Some(Syscall::Read) => { if self.register(0) == STANDARD_INPUT { if let Some(request) = self.dispatch_terminal_read(input, arguments) { @@ -620,6 +653,7 @@ impl Process { } Some(Syscall::Ppoll) => self.set_return(0), Some(Syscall::Write) => self.dispatch_write(terminal, filesystem)?, + Some(Syscall::Writev) => self.dispatch_writev(terminal, filesystem)?, Some(Syscall::SetTidAddress) => { self.clear_child_tid = Some(GuestAddress::new(self.register(0))); self.set_return(MAIN_THREAD_ID); @@ -735,6 +769,41 @@ impl Process { self.set_return(0); } + fn dispatch_fstat(&mut self, filesystem: &mut F) { + let file_descriptor = self.register(0); + let stat = if matches!( + file_descriptor, + STANDARD_INPUT | STANDARD_OUTPUT | STANDARD_ERROR + ) { + linux_terminal_stat_bytes() + } else { + let Ok(file_descriptor) = u32::try_from(file_descriptor) else { + self.set_return(Errno::BadFileDescriptor.return_value()); + return; + }; + let Some(path) = self.descriptor_paths.get(&file_descriptor) else { + self.set_return(Errno::BadFileDescriptor.return_value()); + return; + }; + match filesystem.metadata(path) { + Ok(metadata) => linux_stat_bytes(metadata), + Err(error) => { + self.set_return(filesystem_error_return(error)); + return; + } + } + }; + if self + .memory + .write(GuestAddress::new(self.register(1)), &stat) + .is_err() + { + self.set_return(Errno::Fault.return_value()); + return; + } + self.set_return(0); + } + fn dispatch_write( &mut self, terminal: &mut T, @@ -776,6 +845,93 @@ impl Process { Ok(()) } + fn dispatch_writev( + &mut self, + terminal: &mut T, + filesystem: &mut F, + ) -> Result<(), ExecutionError> { + const IOVEC_SIZE: u64 = 16; + const MAX_IOVEC_COUNT: u64 = 1_024; + + let file_descriptor = self.register(0); + let iovecs = GuestAddress::new(self.register(1)); + let iovec_count = self.register(2); + if iovec_count > MAX_IOVEC_COUNT { + self.set_return(Errno::InvalidArgument.return_value()); + return Ok(()); + } + let mut bytes = Vec::new(); + for index in 0..iovec_count { + let Some(offset) = index.checked_mul(IOVEC_SIZE) else { + self.set_return(Errno::Fault.return_value()); + return Ok(()); + }; + let Some(address) = iovecs.checked_add(offset) else { + self.set_return(Errno::Fault.return_value()); + return Ok(()); + }; + let mut iovec = [0; 16]; + if self.memory.read_exact(address, &mut iovec).is_err() { + self.set_return(Errno::Fault.return_value()); + return Ok(()); + } + let base = u64::from_le_bytes(iovec[..8].try_into().expect("fixed iovec base")); + let length = u64::from_le_bytes(iovec[8..].try_into().expect("fixed iovec length")); + let Some(total) = (bytes.len() as u64).checked_add(length) else { + self.set_return(Errno::InvalidArgument.return_value()); + return Ok(()); + }; + if total > self.limits.max_memory_bytes { + self.set_return(Errno::InvalidArgument.return_value()); + return Ok(()); + } + let host_length = usize::try_from(length).expect("bounded iovec length fits usize"); + let previous_length = bytes.len(); + bytes.resize(previous_length + host_length, 0); + if length != 0 + && self + .memory + .read_exact(GuestAddress::new(base), &mut bytes[previous_length..]) + .is_err() + { + self.set_return(Errno::Fault.return_value()); + return Ok(()); + } + } + + if matches!(file_descriptor, STANDARD_OUTPUT | STANDARD_ERROR) { + let total = bytes.len() as u64; + let next_total = self + .output_bytes + .checked_add(total) + .filter(|output| *output <= self.limits.max_output_bytes) + .ok_or(ExecutionError::Trap(Trap::ResourceLimit( + ResourceLimit::Output, + )))?; + let stream = if file_descriptor == STANDARD_OUTPUT { + TerminalStream::StandardOutput + } else { + TerminalStream::StandardError + }; + terminal + .write(stream, &bytes) + .map_err(ExecutionError::Host)?; + self.output_bytes = next_total; + self.set_return(total); + return Ok(()); + } + + let Some(handle) = self.file_handle(file_descriptor) else { + self.set_return(Errno::BadFileDescriptor.return_value()); + return Ok(()); + }; + match filesystem.write(handle, &bytes) { + Ok(written) => self.set_return(written as u64), + Err(error) => self.set_return(filesystem_error_return(error)), + } + Ok(()) + } + fn dispatch_mkdirat(&mut self, filesystem: &mut F) { if self.register(0) != AT_FDCWD { self.set_return(Errno::InvalidArgument.return_value()); @@ -989,6 +1145,7 @@ impl Process { ) { Ok(handle) => { self.file_descriptors.insert(file_descriptor, handle); + self.descriptor_paths.insert(file_descriptor, path); self.descriptor_flags.insert( file_descriptor, if flags & OPEN_CLOEXEC == 0 { @@ -1015,6 +1172,7 @@ impl Process { match filesystem.close(handle) { Ok(()) => { self.file_descriptors.remove(&file_descriptor); + self.descriptor_paths.remove(&file_descriptor); self.descriptor_flags.remove(&file_descriptor); self.set_return(0); } @@ -1027,7 +1185,8 @@ impl Process { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; - if !self.file_descriptors.contains_key(&file_descriptor) { + let standard_descriptor = file_descriptor <= 2; + if !standard_descriptor && !self.file_descriptors.contains_key(&file_descriptor) { self.set_return(Errno::BadFileDescriptor.return_value()); return; } @@ -1040,7 +1199,11 @@ impl Process { .insert(file_descriptor, self.register(2)); self.set_return(0); } - FCNTL_GET_STATUS_FLAGS => self.set_return(0), + FCNTL_GET_STATUS_FLAGS => self.set_return(if standard_descriptor { + u64::from(file_descriptor != 0) + } else { + 0 + }), FCNTL_SET_STATUS_FLAGS if self.register(2) == 0 => self.set_return(0), _ => self.set_return(Errno::InvalidArgument.return_value()), } @@ -1616,6 +1779,18 @@ fn linux_stat_bytes(metadata: FileMetadata) -> [u8; STAT_SIZE] { bytes } +fn linux_terminal_stat_bytes() -> [u8; STAT_SIZE] { + let mut bytes = [0; STAT_SIZE]; + bytes[..8].copy_from_slice(&1_u64.to_le_bytes()); + bytes[8..16].copy_from_slice(&1_u64.to_le_bytes()); + bytes[STAT_MODE_OFFSET..STAT_MODE_OFFSET + 4] + .copy_from_slice(&STAT_CHARACTER_MODE.to_le_bytes()); + bytes[STAT_LINK_COUNT_OFFSET..STAT_LINK_COUNT_OFFSET + 4].copy_from_slice(&1_u32.to_le_bytes()); + bytes[STAT_BLOCK_SIZE_OFFSET..STAT_BLOCK_SIZE_OFFSET + 4] + .copy_from_slice(&4096_u32.to_le_bytes()); + bytes +} + const fn filesystem_error_return(error: FileSystemError) -> u64 { match error { FileSystemError::NotFound => Errno::NoEntry, @@ -1646,8 +1821,9 @@ mod tests { use binarrow_runtime_core::{GuestAddress, ResourceLimit, Trap}; use super::{ - AT_FDCWD, ExecutionError, ExecutionEvent, MAIN_THREAD_ID, Process, STAT_FILE_SIZE_OFFSET, - STAT_MODE_OFFSET, STAT_REGULAR_MODE, SyscallEvent, SyscallOutcome, + AT_FDCWD, ExecutionError, ExecutionEvent, FCNTL_GET_DESCRIPTOR_FLAGS, MAIN_THREAD_ID, + Process, STANDARD_OUTPUT, STAT_CHARACTER_MODE, STAT_FILE_SIZE_OFFSET, STAT_MODE_OFFSET, + STAT_REGULAR_MODE, STAT_SIZE, SyscallEvent, SyscallOutcome, }; const MESSAGE: &[u8] = b"hello, world\n"; @@ -2085,6 +2261,54 @@ mod tests { } } + #[test] + fn writev_gathers_guest_vectors_for_terminal_output() { + let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); + let mut process = Process::new(image).unwrap(); + let mut terminal = RecordingTerminal::default(); + let iovecs = process.state.sp().checked_sub(32).unwrap(); + let mut bytes = [0; 32]; + bytes[..8].copy_from_slice(&u64::from(MESSAGE_ADDRESS).to_le_bytes()); + bytes[8..16].copy_from_slice(&5_u64.to_le_bytes()); + bytes[16..24].copy_from_slice(&(u64::from(MESSAGE_ADDRESS) + 5).to_le_bytes()); + bytes[24..].copy_from_slice(&8_u64.to_le_bytes()); + process.memory.write(iovecs, &bytes).unwrap(); + process.state.set_x(0, STANDARD_OUTPUT).unwrap(); + process.state.set_x(1, iovecs.get()).unwrap(); + process.state.set_x(2, 2).unwrap(); + + process + .dispatch_writev(&mut terminal, &mut NullFileSystem) + .unwrap(); + + assert_eq!(process.register(0), MESSAGE.len() as u64); + assert_eq!(process.output_bytes(), MESSAGE.len() as u64); + assert_eq!(terminal.standard_output, MESSAGE); + } + + #[test] + fn fstat_describes_standard_streams_as_character_devices() { + let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); + let mut process = Process::new(image).unwrap(); + let stat = process.state.sp().checked_sub(STAT_SIZE as u64).unwrap(); + process.state.set_x(0, STANDARD_OUTPUT).unwrap(); + process.state.set_x(1, stat.get()).unwrap(); + + process.dispatch_fstat(&mut NullFileSystem); + + assert_eq!(process.register(0), 0); + let mut bytes = [0; STAT_SIZE]; + process.memory.read_exact(stat, &mut bytes).unwrap(); + assert_eq!( + u32::from_le_bytes( + bytes[STAT_MODE_OFFSET..STAT_MODE_OFFSET + 4] + .try_into() + .unwrap() + ), + STAT_CHARACTER_MODE + ); + } + #[test] fn getcwd_reports_the_project_directory_and_validates_the_buffer() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); @@ -2193,6 +2417,11 @@ mod tests { fn fcntl_tracks_close_on_exec_descriptor_flags() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); let mut process = Process::new(image).unwrap(); + process.state.set_x(0, STANDARD_OUTPUT).unwrap(); + process.state.set_x(1, FCNTL_GET_DESCRIPTOR_FLAGS).unwrap(); + process.dispatch_fcntl(); + assert_eq!(process.register(0), 0); + process.file_descriptors.insert(3, 1); process.descriptor_flags.insert(3, 1); process.state.set_x(0, 3).unwrap(); diff --git a/third_party/ghidra-aarch64/languages/AARCH64base.sinc b/third_party/ghidra-aarch64/languages/AARCH64base.sinc index 392862f..65439a6 100644 --- a/third_party/ghidra-aarch64/languages/AARCH64base.sinc +++ b/third_party/ghidra-aarch64/languages/AARCH64base.sinc @@ -789,9 +789,11 @@ is ImmS_LT_ImmR=0 & sf=0 & opc=1 & b_2428=0x13 & b_2323=0 & n=0 & b_21=0 & b_15= { dst:4 = Rd_GPR32; src:4 = Rn_GPR32; - mask:4 = (0xffffffff >> (32 - BFextractWidth32)); + mask:4 = 1; + mask = (mask << BFextractWidth32) - 1; + inv_mask:4 = mask ^ 0xffffffff; tmp:4 = (src >> ImmRConst32) & mask; - Rd_GPR64 = zext((dst & ~(mask)) | tmp); + Rd_GPR64 = zext((dst & inv_mask) | tmp); } # C6.2.31 BFXIL page C6-1199 line 70700 MATCH x33000000/mask=x7f800000 @@ -808,9 +810,11 @@ is ImmS_LT_ImmR=0 & sf=1 & opc=1 & b_2428=0x13 & b_2323=0 & n=1 & ImmRConst64 & { dst:8 = Rd_GPR64; src:8 = Rn_GPR64; - mask:8 = (0xffffffffffffffff >> (64 - BFextractWidth64)); + mask:8 = 1; + mask = (mask << BFextractWidth64) - 1; + inv_mask:8 = mask ^ 0xffffffffffffffff; tmp:8 = (src >> ImmRConst64) & mask; - Rd_GPR64 = ((dst & ~(mask)) | tmp); + Rd_GPR64 = ((dst & inv_mask) | tmp); } # C6.2.32 BIC (shifted register) page C6-1201 line 70793 MATCH x0a200000/mask=x7f200000 @@ -8396,4 +8400,3 @@ is b_1231=0b11010101000000000100 & b_0007=0b00111111 CY = tmpCY; OV = tmpOV; } - diff --git a/third_party/ghidra-aarch64/languages/AARCH64instructions.sinc b/third_party/ghidra-aarch64/languages/AARCH64instructions.sinc index 9b4bae0..584acb6 100644 --- a/third_party/ghidra-aarch64/languages/AARCH64instructions.sinc +++ b/third_party/ghidra-aarch64/languages/AARCH64instructions.sinc @@ -2116,8 +2116,8 @@ Addr19: AddrLoc19 is AddrLoc19 { export *:8 AddrLoc19; } Addr26: AddrLoc26 is AddrLoc26 { export *:8 AddrLoc26; } -AdrReloff: reloff is b_31=1 & immlo & immhi [ reloff = ((inst_start) & ~0xfff) + ( ((immhi << 2) | immlo) << 12 ); ] { export *[const]:8 reloff; } -AdrReloff: reloff is b_31=0 & immlo & immhi [ reloff = (inst_start) + ( ((immhi << 2) | immlo) ); ] { export *[const]:8 reloff; } +AdrReloff: reloff is b_31=1 & immlo & immhi [ reloff = ((inst_start) & ~0xfff) + ( ((immhi * 4) + immlo) * 4096 ); ] { export *[const]:8 reloff; } +AdrReloff: reloff is b_31=0 & immlo & immhi [ reloff = (inst_start) + ( (immhi * 4) + immlo ); ] { export *[const]:8 reloff; } ImmShift32: "#"^imm12 is aa_extreg_shift=0 & imm12 { export *[const]:4 imm12; } ImmShift32: "#"^imm12, "LSL #12" is aa_extreg_shift=1 & imm12 { tmp:4 = imm12 << 12; export tmp; } -- 2.51.2