pr 2.4
This commit is contained in:
parent
df9b248c98
commit
24e7a3b083
@ -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};
|
||||
|
||||
@ -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<u8>, syscalls: Vec<SyscallDecl>) -> Vec<u8> {
|
||||
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;
|
||||
|
||||
@ -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),
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user