From 24e7a3b083d10b63e25446b7d7b6854c872026ba Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 2 Mar 2026 14:39:56 +0000 Subject: [PATCH] pr 2.4 --- crates/console/prometeu-vm/src/lib.rs | 2 +- .../prometeu-vm/src/virtual_machine.rs | 222 +++++++++++++++++- .../console/prometeu-vm/src/vm_init_error.rs | 22 ++ 3 files changed, 242 insertions(+), 4 deletions(-) diff --git a/crates/console/prometeu-vm/src/lib.rs b/crates/console/prometeu-vm/src/lib.rs index e03c6243..d51be1f8 100644 --- a/crates/console/prometeu-vm/src/lib.rs +++ b/crates/console/prometeu-vm/src/lib.rs @@ -15,4 +15,4 @@ mod vm_init_error; pub use prometeu_hal::{HostContext, HostReturn, NativeInterface, SyscallId}; pub use virtual_machine::{BudgetReport, LogicalFrameEndingReason, VirtualMachine}; -pub use vm_init_error::VmInitError; +pub use vm_init_error::{LoaderPatchError, VmInitError}; diff --git a/crates/console/prometeu-vm/src/virtual_machine.rs b/crates/console/prometeu-vm/src/virtual_machine.rs index 77f6dffd..e792b175 100644 --- a/crates/console/prometeu-vm/src/virtual_machine.rs +++ b/crates/console/prometeu-vm/src/virtual_machine.rs @@ -4,9 +4,11 @@ use crate::object::ObjectKind; use crate::roots::{RootVisitor, visit_value_for_roots}; use crate::scheduler::Scheduler; use crate::verifier::Verifier; -use crate::vm_init_error::VmInitError; +use crate::vm_init_error::{LoaderPatchError, VmInitError}; use crate::{HostContext, NativeInterface}; use prometeu_bytecode::HeapRef; +use prometeu_bytecode::decode_next; +use prometeu_bytecode::model::BytecodeModule; use prometeu_bytecode::ProgramImage; use prometeu_bytecode::Value; use prometeu_bytecode::isa::core::CoreOpCode as OpCode; @@ -17,6 +19,66 @@ use prometeu_bytecode::{ use prometeu_hal::syscalls::caps::ALL; use prometeu_hal::vm_fault::VmFault; +fn patch_module_hostcalls( + module: &mut BytecodeModule, + capabilities: prometeu_hal::syscalls::CapFlags, +) -> Result<(), LoaderPatchError> { + let resolved = prometeu_hal::syscalls::resolve_declared_program_syscalls( + &module.syscalls, + capabilities, + ) + .map_err(LoaderPatchError::ResolveFailed)?; + + let mut used = vec![false; module.syscalls.len()]; + let mut pc = 0usize; + while pc < module.code.len() { + let instr = decode_next(pc, &module.code).map_err(LoaderPatchError::DecodeFailed)?; + let next_pc = instr.next_pc; + + if instr.opcode == OpCode::Hostcall { + let sysc_index = instr.imm_u32().map_err(LoaderPatchError::DecodeFailed)?; + let Some(resolved_syscall) = resolved.get(sysc_index as usize) else { + return Err(LoaderPatchError::HostcallIndexOutOfBounds { + pc, + sysc_index, + syscalls_len: resolved.len(), + }); + }; + + used[sysc_index as usize] = true; + module.code[pc..pc + 2].copy_from_slice(&(OpCode::Syscall as u16).to_le_bytes()); + module.code[pc + 2..pc + 6].copy_from_slice(&resolved_syscall.id.to_le_bytes()); + } + + pc = next_pc; + } + + for (index, (was_used, decl)) in used.iter().zip(&module.syscalls).enumerate() { + if !was_used { + return Err(LoaderPatchError::UnusedSyscallDecl { + sysc_index: index as u32, + module: decl.module.clone(), + name: decl.name.clone(), + version: decl.version, + }); + } + } + + let mut pc = 0usize; + while pc < module.code.len() { + let instr = decode_next(pc, &module.code).map_err(LoaderPatchError::DecodeFailed)?; + if instr.opcode == OpCode::Hostcall { + return Err(LoaderPatchError::HostcallRemaining { + pc, + sysc_index: instr.imm_u32().map_err(LoaderPatchError::DecodeFailed)?, + }); + } + pc = instr.next_pc; + } + + Ok(()) +} + /// Reason why the Virtual Machine stopped execution during a specific run. /// This allows the system to decide if it should continue execution in the next tick /// or if the frame is finalized. @@ -214,7 +276,10 @@ impl VirtualMachine { // Only recognized format is loadable: PBS v0 industrial format let program = if program_bytes.starts_with(b"PBS\0") { match prometeu_bytecode::BytecodeLoader::load(&program_bytes) { - Ok(module) => { + Ok(mut module) => { + patch_module_hostcalls(&mut module, self.capabilities) + .map_err(VmInitError::LoaderPatchFailed)?; + // Run verifier on the module let max_stacks = Verifier::verify(&module.code, &module.functions) .map_err(|e| VmInitError::VerificationFailed(format!("{:?}", e)))?; @@ -1576,7 +1641,10 @@ mod tests { vm } use crate::HostReturn; - use prometeu_bytecode::{FunctionMeta, TRAP_INVALID_LOCAL, TRAP_STACK_UNDERFLOW}; + use prometeu_bytecode::model::{BytecodeModule, SyscallDecl}; + use prometeu_bytecode::{ + FunctionMeta, TRAP_INVALID_LOCAL, TRAP_STACK_UNDERFLOW, assemble, disassemble, + }; use prometeu_hal::expect_int; struct MockNative; @@ -1592,6 +1660,23 @@ mod tests { } } + fn serialized_single_function_module(code: Vec, syscalls: Vec) -> Vec { + BytecodeModule { + version: 0, + const_pool: vec![], + functions: vec![FunctionMeta { + code_offset: 0, + code_len: code.len() as u32, + ..Default::default() + }], + code, + debug_info: None, + exports: vec![], + syscalls, + } + .serialize() + } + #[test] fn sleep_delays_execution_by_ticks() { let mut native = MockNative; @@ -2691,6 +2776,137 @@ mod tests { assert_eq!(vm.cycles, 0); } + #[test] + fn test_loader_patching_rewrites_hostcall_before_verification() { + let mut vm = VirtualMachine::default(); + vm.set_capabilities(prometeu_hal::syscalls::caps::GFX); + + let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble"); + let bytes = serialized_single_function_module( + code, + vec![SyscallDecl { + module: "gfx".into(), + name: "clear".into(), + version: 1, + arg_slots: 1, + ret_slots: 0, + }], + ); + + let res = vm.initialize(bytes, ""); + + assert!(res.is_ok(), "patched hostcall should initialize"); + + let text = disassemble(&vm.program.rom).expect("disassemble patched rom"); + assert!(text.contains("SYSCALL 0x1001")); + assert!(!text.contains("HOSTCALL")); + } + + #[test] + fn test_loader_patching_rejects_hostcall_index_out_of_bounds() { + let mut vm = VirtualMachine::default(); + vm.set_capabilities(prometeu_hal::syscalls::caps::GFX); + + let code = assemble("HOSTCALL 1\nHALT").expect("assemble"); + let bytes = serialized_single_function_module( + code, + vec![SyscallDecl { + module: "gfx".into(), + name: "clear".into(), + version: 1, + arg_slots: 1, + ret_slots: 0, + }], + ); + + let res = vm.initialize(bytes, ""); + + assert_eq!( + res, + Err(VmInitError::LoaderPatchFailed( + crate::vm_init_error::LoaderPatchError::HostcallIndexOutOfBounds { + pc: 0, + sysc_index: 1, + syscalls_len: 1, + }, + )) + ); + } + + #[test] + fn test_loader_patching_rejects_unused_syscall_decl() { + let mut vm = VirtualMachine::default(); + vm.set_capabilities(prometeu_hal::syscalls::caps::GFX); + + let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble"); + let bytes = serialized_single_function_module( + code, + vec![ + SyscallDecl { + module: "gfx".into(), + name: "clear".into(), + version: 1, + arg_slots: 1, + ret_slots: 0, + }, + SyscallDecl { + module: "gfx".into(), + name: "draw_text".into(), + version: 1, + arg_slots: 4, + ret_slots: 0, + }, + ], + ); + + let res = vm.initialize(bytes, ""); + + assert_eq!( + res, + Err(VmInitError::LoaderPatchFailed( + crate::vm_init_error::LoaderPatchError::UnusedSyscallDecl { + sysc_index: 1, + module: "gfx".into(), + name: "draw_text".into(), + version: 1, + }, + )) + ); + } + + #[test] + fn test_loader_patching_propagates_resolution_failure() { + let mut vm = VirtualMachine::default(); + vm.set_capabilities(prometeu_hal::syscalls::caps::GFX); + + let code = assemble("HOSTCALL 0\nHALT").expect("assemble"); + let bytes = serialized_single_function_module( + code, + vec![SyscallDecl { + module: "gfx".into(), + name: "missing".into(), + version: 1, + arg_slots: 0, + ret_slots: 0, + }], + ); + + let res = vm.initialize(bytes, ""); + + assert_eq!( + res, + Err(VmInitError::LoaderPatchFailed( + crate::vm_init_error::LoaderPatchError::ResolveFailed( + prometeu_hal::syscalls::DeclaredLoadError::UnknownSyscall { + module: "gfx".into(), + name: "missing".into(), + version: 1, + }, + ), + )) + ); + } + #[test] fn test_calling_convention_add() { let mut native = MockNative; diff --git a/crates/console/prometeu-vm/src/vm_init_error.rs b/crates/console/prometeu-vm/src/vm_init_error.rs index f29a4c55..0ed9951c 100644 --- a/crates/console/prometeu-vm/src/vm_init_error.rs +++ b/crates/console/prometeu-vm/src/vm_init_error.rs @@ -1,8 +1,30 @@ +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum LoaderPatchError { + DecodeFailed(prometeu_bytecode::DecodeError), + ResolveFailed(prometeu_hal::syscalls::DeclaredLoadError), + HostcallIndexOutOfBounds { + pc: usize, + sysc_index: u32, + syscalls_len: usize, + }, + UnusedSyscallDecl { + sysc_index: u32, + module: String, + name: String, + version: u16, + }, + HostcallRemaining { + pc: usize, + sysc_index: u32, + }, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum VmInitError { InvalidFormat, UnsupportedFormat, ImageLoadFailed(prometeu_bytecode::LoadError), + LoaderPatchFailed(LoaderPatchError), EntrypointNotFound, VerificationFailed(String), }