use std::sync::{Arc, Mutex};
use hyperlight_common::flatbuffer_wrappers::function_types::{
ParameterValue, ReturnType, ReturnValue,
};
use tracing::{instrument, Span};
use super::host_funcs::HostFuncsWrapper;
use super::{MemMgrWrapper, WrapperGetter};
use crate::func::call_ctx::MultiUseGuestCallContext;
use crate::func::guest_dispatch::call_function_on_guest;
use crate::hypervisor::hypervisor_handler::HypervisorHandler;
use crate::mem::shared_mem::HostSharedMemory;
use crate::sandbox_state::sandbox::{DevolvableSandbox, EvolvableSandbox, Sandbox};
use crate::sandbox_state::transition::{MultiUseContextCallback, Noop};
use crate::Result;
pub struct MultiUseSandbox {
pub(super) _host_funcs: Arc<Mutex<HostFuncsWrapper>>,
pub(crate) mem_mgr: MemMgrWrapper<HostSharedMemory>,
hv_handler: HypervisorHandler,
}
impl Drop for MultiUseSandbox {
fn drop(&mut self) {
match self.hv_handler.kill_hypervisor_handler_thread() {
Ok(_) => {}
Err(e) => {
log::error!("[POTENTIAL THREAD LEAK] Potentially failed to kill hypervisor handler thread when dropping MultiUseSandbox: {:?}", e);
}
}
}
}
impl MultiUseSandbox {
#[instrument(skip_all, parent = Span::current(), level = "Trace")]
pub(super) fn from_uninit(
host_funcs: Arc<Mutex<HostFuncsWrapper>>,
mgr: MemMgrWrapper<HostSharedMemory>,
hv_handler: HypervisorHandler,
) -> MultiUseSandbox {
Self {
_host_funcs: host_funcs,
mem_mgr: mgr,
hv_handler,
}
}
#[instrument(skip_all, parent = Span::current())]
pub fn new_call_context(self) -> MultiUseGuestCallContext {
MultiUseGuestCallContext::start(self)
}
#[instrument(err(Debug), skip(self, args), parent = Span::current())]
pub fn call_guest_function_by_name(
&mut self,
func_name: &str,
func_ret_type: ReturnType,
args: Option<Vec<ParameterValue>>,
) -> Result<ReturnValue> {
let res = call_function_on_guest(self, func_name, func_ret_type, args)?;
self.restore_state()?;
Ok(res)
}
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
pub(crate) fn restore_state(&mut self) -> Result<()> {
let mem_mgr = self.mem_mgr.unwrap_mgr_mut();
mem_mgr.restore_state_from_last_snapshot()
}
}
impl WrapperGetter for MultiUseSandbox {
fn get_mgr_wrapper(&self) -> &MemMgrWrapper<HostSharedMemory> {
&self.mem_mgr
}
fn get_mgr_wrapper_mut(&mut self) -> &mut MemMgrWrapper<HostSharedMemory> {
&mut self.mem_mgr
}
fn get_hv_handler(&self) -> &HypervisorHandler {
&self.hv_handler
}
fn get_hv_handler_mut(&mut self) -> &mut HypervisorHandler {
&mut self.hv_handler
}
}
impl Sandbox for MultiUseSandbox {
fn check_stack_guard(&self) -> Result<bool> {
self.mem_mgr.check_stack_guard()
}
}
impl std::fmt::Debug for MultiUseSandbox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MultiUseSandbox")
.field("stack_guard", &self.mem_mgr.get_stack_cookie())
.finish()
}
}
impl DevolvableSandbox<MultiUseSandbox, MultiUseSandbox, Noop<MultiUseSandbox, MultiUseSandbox>>
for MultiUseSandbox
{
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
fn devolve(mut self, _tsn: Noop<MultiUseSandbox, MultiUseSandbox>) -> Result<MultiUseSandbox> {
self.mem_mgr
.unwrap_mgr_mut()
.pop_and_restore_state_from_snapshot()?;
Ok(self)
}
}
impl<'a, F>
EvolvableSandbox<
MultiUseSandbox,
MultiUseSandbox,
MultiUseContextCallback<'a, MultiUseSandbox, F>,
> for MultiUseSandbox
where
F: FnOnce(&mut MultiUseGuestCallContext) -> Result<()> + 'a,
{
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
fn evolve(
self,
transition_func: MultiUseContextCallback<'a, MultiUseSandbox, F>,
) -> Result<MultiUseSandbox> {
let mut ctx = self.new_call_context();
transition_func.call(&mut ctx)?;
let mut sbox = ctx.finish_no_reset();
sbox.mem_mgr.unwrap_mgr_mut().push_state()?;
Ok(sbox)
}
}
#[cfg(test)]
mod tests {
use hyperlight_common::flatbuffer_wrappers::function_types::{
ParameterValue, ReturnType, ReturnValue,
};
use hyperlight_testing::simple_guest_as_string;
use crate::func::call_ctx::MultiUseGuestCallContext;
use crate::sandbox::SandboxConfiguration;
use crate::sandbox_state::sandbox::{DevolvableSandbox, EvolvableSandbox};
use crate::sandbox_state::transition::{MultiUseContextCallback, Noop};
use crate::{GuestBinary, MultiUseSandbox, UninitializedSandbox};
#[test]
fn test_with_small_stack_and_heap() {
let mut cfg = SandboxConfiguration::default();
cfg.set_heap_size(20 * 1024);
cfg.set_stack_size(16 * 1024);
let sbox1: MultiUseSandbox = {
let path = simple_guest_as_string().unwrap();
let u_sbox =
UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg), None, None)
.unwrap();
u_sbox.evolve(Noop::default())
}
.unwrap();
let mut ctx = sbox1.new_call_context();
for _ in 0..1000 {
ctx.call(
"StackAllocate",
ReturnType::Int,
Some(vec![ParameterValue::Int(1)]),
)
.unwrap();
}
let sbox2: MultiUseSandbox = {
let path = simple_guest_as_string().unwrap();
let u_sbox =
UninitializedSandbox::new(GuestBinary::FilePath(path), Some(cfg), None, None)
.unwrap();
u_sbox.evolve(Noop::default())
}
.unwrap();
let mut ctx = sbox2.new_call_context();
for i in 0..1000 {
ctx.call(
"PrintUsingPrintf",
ReturnType::Int,
Some(vec![ParameterValue::String(
format!("Hello World {}\n", i).to_string(),
)]),
)
.unwrap();
}
}
#[test]
fn evolve_devolve_handles_state_correctly() {
let sbox1: MultiUseSandbox = {
let path = simple_guest_as_string().unwrap();
let u_sbox =
UninitializedSandbox::new(GuestBinary::FilePath(path), None, None, None).unwrap();
u_sbox.evolve(Noop::default())
}
.unwrap();
let func = Box::new(|call_ctx: &mut MultiUseGuestCallContext| {
call_ctx.call(
"AddToStatic",
ReturnType::Int,
Some(vec![ParameterValue::Int(5)]),
)?;
Ok(())
});
let transition_func = MultiUseContextCallback::from(func);
let mut sbox2 = sbox1.evolve(transition_func).unwrap();
let res = sbox2
.call_guest_function_by_name("GetStatic", ReturnType::Int, None)
.unwrap();
assert_eq!(res, ReturnValue::Int(5));
let mut sbox3: MultiUseSandbox = sbox2.devolve(Noop::default()).unwrap();
let res = sbox3
.call_guest_function_by_name("GetStatic", ReturnType::Int, None)
.unwrap();
assert_eq!(res, ReturnValue::Int(0));
}
}