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 prometeu_hal::{HostContext, HostReturn, NativeInterface, SyscallId};
|
||||||
pub use virtual_machine::{BudgetReport, LogicalFrameEndingReason, VirtualMachine};
|
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::roots::{RootVisitor, visit_value_for_roots};
|
||||||
use crate::scheduler::Scheduler;
|
use crate::scheduler::Scheduler;
|
||||||
use crate::verifier::Verifier;
|
use crate::verifier::Verifier;
|
||||||
use crate::vm_init_error::VmInitError;
|
use crate::vm_init_error::{LoaderPatchError, VmInitError};
|
||||||
use crate::{HostContext, NativeInterface};
|
use crate::{HostContext, NativeInterface};
|
||||||
use prometeu_bytecode::HeapRef;
|
use prometeu_bytecode::HeapRef;
|
||||||
|
use prometeu_bytecode::decode_next;
|
||||||
|
use prometeu_bytecode::model::BytecodeModule;
|
||||||
use prometeu_bytecode::ProgramImage;
|
use prometeu_bytecode::ProgramImage;
|
||||||
use prometeu_bytecode::Value;
|
use prometeu_bytecode::Value;
|
||||||
use prometeu_bytecode::isa::core::CoreOpCode as OpCode;
|
use prometeu_bytecode::isa::core::CoreOpCode as OpCode;
|
||||||
@ -17,6 +19,66 @@ use prometeu_bytecode::{
|
|||||||
use prometeu_hal::syscalls::caps::ALL;
|
use prometeu_hal::syscalls::caps::ALL;
|
||||||
use prometeu_hal::vm_fault::VmFault;
|
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.
|
/// 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
|
/// This allows the system to decide if it should continue execution in the next tick
|
||||||
/// or if the frame is finalized.
|
/// or if the frame is finalized.
|
||||||
@ -214,7 +276,10 @@ impl VirtualMachine {
|
|||||||
// Only recognized format is loadable: PBS v0 industrial format
|
// Only recognized format is loadable: PBS v0 industrial format
|
||||||
let program = if program_bytes.starts_with(b"PBS\0") {
|
let program = if program_bytes.starts_with(b"PBS\0") {
|
||||||
match prometeu_bytecode::BytecodeLoader::load(&program_bytes) {
|
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
|
// Run verifier on the module
|
||||||
let max_stacks = Verifier::verify(&module.code, &module.functions)
|
let max_stacks = Verifier::verify(&module.code, &module.functions)
|
||||||
.map_err(|e| VmInitError::VerificationFailed(format!("{:?}", e)))?;
|
.map_err(|e| VmInitError::VerificationFailed(format!("{:?}", e)))?;
|
||||||
@ -1576,7 +1641,10 @@ mod tests {
|
|||||||
vm
|
vm
|
||||||
}
|
}
|
||||||
use crate::HostReturn;
|
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;
|
use prometeu_hal::expect_int;
|
||||||
|
|
||||||
struct MockNative;
|
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]
|
#[test]
|
||||||
fn sleep_delays_execution_by_ticks() {
|
fn sleep_delays_execution_by_ticks() {
|
||||||
let mut native = MockNative;
|
let mut native = MockNative;
|
||||||
@ -2691,6 +2776,137 @@ mod tests {
|
|||||||
assert_eq!(vm.cycles, 0);
|
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]
|
#[test]
|
||||||
fn test_calling_convention_add() {
|
fn test_calling_convention_add() {
|
||||||
let mut native = MockNative;
|
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)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum VmInitError {
|
pub enum VmInitError {
|
||||||
InvalidFormat,
|
InvalidFormat,
|
||||||
UnsupportedFormat,
|
UnsupportedFormat,
|
||||||
ImageLoadFailed(prometeu_bytecode::LoadError),
|
ImageLoadFailed(prometeu_bytecode::LoadError),
|
||||||
|
LoaderPatchFailed(LoaderPatchError),
|
||||||
EntrypointNotFound,
|
EntrypointNotFound,
|
||||||
VerificationFailed(String),
|
VerificationFailed(String),
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user