BizHawk/waterbox/waterboxhost/src/cinterface.rs

380 lines
14 KiB
Rust

use crate::*;
use host::{WaterboxHost};
use std::{os::raw::c_char, io, ffi::{/*CString, */CStr}};
use context::ExternalCallback;
/// The memory template for a WaterboxHost. Don't worry about
/// making every size as small as possible, since the savestater handles sparse regions
/// well enough. All values should be PAGESIZE aligned.
#[repr(C)]
pub struct MemoryLayoutTemplate {
/// Memory space to serve brk(2)
pub sbrk_size: usize,
/// Memory space to serve alloc_sealed(3)
pub sealed_size: usize,
/// Memory space to serve alloc_invisible(3)
pub invis_size: usize,
/// Memory space to serve alloc_plain(3)
pub plain_size: usize,
/// Memory space to serve mmap(2) and friends.
/// Calls without MAP_FIXED or MREMAP_FIXED will be placed in this area.
/// TODO: Are we allowing fixed calls to happen anywhere in the block?
pub mmap_size: usize,
}
impl MemoryLayoutTemplate {
/// checks a memory layout for validity
pub fn make_layout(&self, elf_addr: AddressRange) -> anyhow::Result<WbxSysLayout> {
let mut res = unsafe { std::mem::zeroed::<WbxSysLayout>() };
res.elf = elf_addr.align_expand();
let mut end = res.elf.end();
let mut add_area = |size| {
let a = AddressRange {
start: end,
size: align_up(size)
};
end = a.end();
a
};
res.main_thread = add_area(1 << 20);
res.sbrk = add_area(self.sbrk_size);
res.sealed = add_area(self.sealed_size);
res.invis = add_area(self.invis_size);
res.plain = add_area(self.plain_size);
res.mmap = add_area(self.mmap_size);
if res.all().start >> 32 != (res.all().end() - 1) >> 32 {
Err(anyhow!("HostMemoryLayout must fit into a single 4GiB region!"))
} else {
Ok(res)
}
}
}
/// "return" struct. On successful funtion call, error_message[0] will be 0 and data will be the return value.
/// On failed call, error_message will contain a string describing the error, and data will be unspecified.
/// Any function that takes this object as an argument can fail and should be checked for failure, even if
/// it does not return data.
#[repr(C)]
pub struct Return<T> {
pub error_message: [u8; 1024],
pub data: T,
}
impl<T> Return<T> {
pub fn put(&mut self, result: anyhow::Result<T>) {
match result {
Err(e) => {
let s = format!("Waterbox Error: {:?}", e);
let len = std::cmp::min(s.len(), 1023);
self.error_message[0..len].copy_from_slice(&s.as_bytes()[0..len]);
self.error_message[len] = 0;
},
Ok(t) => {
self.error_message[0] = 0;
self.data = t;
}
}
}
}
/// write bytes. Return 0 on success, or < 0 on failure.
/// Must write all provided bytes in one call or fail, not permitted to write less (unlike reader).
pub type WriteCallback = extern fn(userdata: usize, data: *const u8, size: usize) -> i32;
struct CWriter {
/// will be passed to callback
pub userdata: usize,
pub callback: WriteCallback,
}
impl Write for CWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let res = (self.callback)(self.userdata, buf.as_ptr(), buf.len());
if res < 0 {
Err(io::Error::new(io::ErrorKind::Other, "Callback signaled abnormal failure"))
} else {
Ok(buf.len())
}
}
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
self.write(buf)?;
Ok(())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
/// Read bytes into the buffer. Return number of bytes read on success, or < 0 on failure.
/// permitted to read less than the provided buffer size, but must always read at least 1
/// byte if EOF is not reached. If EOF is reached, should return 0.
pub type ReadCallback = extern fn(userdata: usize, data: *mut u8, size: usize) -> isize;
struct CReader {
pub userdata: usize,
pub callback: ReadCallback,
}
impl Read for CReader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let res = (self.callback)(self.userdata, buf.as_mut_ptr(), buf.len());
if res < 0 {
Err(io::Error::new(io::ErrorKind::Other, "Callback signaled abnormal failure"))
} else {
Ok(res as usize)
}
}
}
// #[repr(C)]
// pub struct MissingFileCallback {
// pub userdata: usize,
// pub callback: extern fn(userdata: usize, name: *const c_char) -> *mut MissingFileResult,
// }
// #[repr(C)]
// pub struct MissingFileResult {
// pub reader: CReader,
// pub writable: bool,
// }
fn arg_to_str(arg: *const c_char) -> anyhow::Result<String> {
let cs = unsafe { CStr::from_ptr(arg as *const c_char) };
match cs.to_str() {
Ok(s) => Ok(s.to_string()),
Err(_) => Err(anyhow!("Bad UTF-8 string")),
}
}
fn read_whole_file(reader: &mut CReader) -> anyhow::Result<Vec<u8>> {
let mut res = Vec::<u8>::new();
io::copy(reader, &mut res)?;
Ok(res)
}
/// Given a guest executable and a memory layout, create a new host environment. All data will be immediately consumed from the reader,
/// which will not be used after this call.
#[no_mangle]
pub extern fn wbx_create_host(layout: &MemoryLayoutTemplate, module_name: *const c_char, callback: ReadCallback, userdata: usize, ret: &mut Return<*mut WaterboxHost>) {
let mut reader = CReader {
userdata,
callback
};
let res = (|| {
let data = read_whole_file(&mut reader)?;
WaterboxHost::new(data, &arg_to_str(module_name)?[..], layout)
})();
ret.put(res.map(|boxed| Box::into_raw(boxed)));
}
/// Tear down a host environment. If called while the environment is active, will deactivate it first.
#[no_mangle]
pub extern fn wbx_destroy_host(obj: *mut WaterboxHost, ret: &mut Return<()>) {
let res = (|| {
unsafe {
Box::from_raw(obj);
Ok(())
}
})();
ret.put(res);
}
/// Activate a host environment. This swaps it into memory and makes it available for use.
/// Pointers to inside the environment are only valid while active. Callbacks into the environment can only be called
/// while active. Uses a mutex internally so as to not stomp over other host environments in the same 4GiB slice.
/// Ignored if host is already active.
#[no_mangle]
pub extern fn wbx_activate_host(obj: &mut WaterboxHost, ret: &mut Return<()>) {
let res = (|| {
obj.activate();
Ok(())
})();
ret.put(res);
}
/// Deactivates a host environment, and releases the mutex.
/// Ignored if host is not active
#[no_mangle]
pub extern fn wbx_deactivate_host(obj: &mut WaterboxHost, ret: &mut Return<()>) {
obj.deactivate();
ret.put(Ok(()));
}
/// Returns a thunk suitable for calling an exported function from the guest executable. This pointer is only valid
/// while the host is active. A missing proc is not an error and simply returns 0. The guest function must be,
/// and the returned callback will be, sysv abi, and will only pass up to 6 int/ptr args and no other arg types.
#[no_mangle]
pub extern fn wbx_get_proc_addr(obj: &mut WaterboxHost, name: *const c_char, ret: &mut Return<usize>) {
match arg_to_str(name) {
Ok(s) => {
ret.put(obj.get_proc_addr(&s));
},
Err(e) => {
ret.put(Err(e))
}
}
}
/// Returns a thunk suitable for calling an arbitrary entry point into the guest executable. This pointer is only valid
/// while the host is active. wbx_get_proc_addr already calls this internally on pointers it returns, so this call is
/// only needed if the guest exposes callin pointers that aren't named exports (for instance, if a function returns
/// a pointer to another function).
#[no_mangle]
pub extern fn wbx_get_callin_addr(obj: &mut WaterboxHost, ptr: usize, ret: &mut Return<usize>) {
ret.put(obj.get_external_callin_ptr(ptr));
}
/// Returns the raw address of a function exported from the guest. `wbx_get_proc_addr()` is equivalent to
/// `wbx_get_callin_addr(wbx_get_proc_addr_raw()). Most things should not use this directly, as the returned
/// pointer will not have proper stack hygiene and will crash on syscalls from the guest.
#[no_mangle]
pub extern fn wbx_get_proc_addr_raw(obj: &mut WaterboxHost, name: *const c_char, ret: &mut Return<usize>) {
match arg_to_str(name) {
Ok(s) => {
ret.put(obj.get_proc_addr_raw(&s));
},
Err(e) => {
ret.put(Err(e))
}
}
}
/// Returns a function pointer suitable for passing to the guest to allow it to call back while active.
/// Slot number is an integer that is used to keep pointers consistent across runs: If the host is loaded
/// at a different address, and some external function `foo` moves from run to run, things will still work out
/// in the guest because `foo` was bound to the same slot and a particular slot gives a consistent pointer.
/// The returned thunk will be, and the callback must be, sysv abi and will only pass up to 6 int/ptr args and no other arg types.
#[no_mangle]
pub extern fn wbx_get_callback_addr(obj: &mut WaterboxHost, callback: ExternalCallback, slot: usize, ret: &mut Return<usize>) {
ret.put(obj.get_external_callback_ptr(callback, slot));
}
/// Calls the seal operation, which is a one time action that prepares the host to save states.
#[no_mangle]
pub extern fn wbx_seal(obj: &mut WaterboxHost, ret: &mut Return<()>) {
ret.put(obj.seal());
}
/// Mounts a file in the environment. All data will be immediately consumed from the reader, which will not be used after this call.
/// To prevent nondeterminism, adding and removing files is very limited WRT savestates. Every file added must either exist
/// in every savestate, or never appear in any savestates. All savestateable files must be added in the same order for every run.
#[no_mangle]
pub extern fn wbx_mount_file(obj: &mut WaterboxHost, name: *const c_char, callback: ReadCallback, userdata: usize, writable: bool, ret: &mut Return<()>) {
let mut reader = CReader {
userdata,
callback
};
let res: anyhow::Result<()> = (|| {
obj.mount_file(arg_to_str(name)?, read_whole_file(&mut reader)?, writable)?;
Ok(())
})();
ret.put(res);
}
/// Remove a file previously added. Writer is optional; if provided, the contents of the file at time of removal will be dumped to it.
/// It is an error to remove a file which is currently open in the guest.
/// If the file has been used in savestates, it does not make sense to remove it here, but nothing will stop you.
#[no_mangle]
pub extern fn wbx_unmount_file(obj: &mut WaterboxHost, name: *const c_char, callback_opt: Option<WriteCallback>, userdata: usize, ret: &mut Return<()>) {
let res: anyhow::Result<()> = (|| {
let data = obj.unmount_file(&arg_to_str(name)?)?;
if let Some(callback) = callback_opt {
let mut writer = CWriter {
userdata,
callback
};
io::copy(&mut &data[..], &mut writer)?;
}
Ok(())
})();
ret.put(res);
}
/// Set (or clear, with None) a callback to be called whenever the guest tries to load a nonexistant file.
/// The callback will be provided with the name of the requested load, and can either return null to signal the waterbox
/// to return ENOENT to the guest, or a struct to immediately load that file. You may not call any wbx methods
/// in the callback. If the MissingFileResult is provided, it will be consumed immediately and will have the same effect
/// as wbx_mount_file(). You may free resources associated with the MissingFileResult whenever control next returns to your code.
// #[no_mangle]
// pub extern fn wbx_set_missing_file_callback(obj: &mut WaterboxHost, mfc_o: Option<&MissingFileCallback>) {
// match mfc_o {
// None => obj.set_missing_file_callback(None),
// Some(mfc) => {
// let userdata = mfc.userdata;
// let callback = mfc.callback;
// obj.set_missing_file_callback(Some(Box::new(move |name| {
// let namestr = CString::new(name).unwrap();
// let mfr = callback(userdata, namestr.as_ptr() as *const c_char);
// if mfr == 0 as *mut MissingFileResult {
// return None
// }
// unsafe {
// let data = read_whole_file(&mut (*mfr).reader);
// match data {
// Ok(d) => Some(fs::MissingFileResult {
// data: d,
// writable: (*mfr).writable
// }),
// Err(_) => None,
// }
// }
// })));
// }
// }
// }
/// Save state. Must not be called before seal. Must not be called with any writable files mounted.
/// Must always be called with the same sequence and contents of readonly files.
#[no_mangle]
pub extern fn wbx_save_state(obj: &mut WaterboxHost, callback: WriteCallback, userdata: usize, ret: &mut Return<()>) {
let mut writer = CWriter {
userdata,
callback
};
let res: anyhow::Result<()> = (|| {
obj.save_state(&mut writer)?;
Ok(())
})();
ret.put(res);
}
/// Load state. Must not be called before seal. Must not be called with any writable files mounted.
/// Must always be called with the same sequence and contents of readonly files that were in the save state.
/// Must be called with the same wbx executable and memory layout as in the savestate.
/// Errors generally poison the environment; sorry!
#[no_mangle]
pub extern fn wbx_load_state(obj: &mut WaterboxHost, callback: ReadCallback, userdata: usize, ret: &mut Return<()>) {
let mut reader = CReader {
userdata,
callback
};
ret.put(obj.load_state(&mut reader));
}
/// Control whether the host automatically evicts blocks from memory when they are not active. For the best performance,
/// this should be set to false. Set to true to help catch dangling pointer issues. Will be ignored (and forced to true)
/// if waterboxhost was built in debug mode. This is a single global setting.
#[no_mangle]
pub extern fn wbx_set_always_evict_blocks(_val: bool) {
#[cfg(not(debug_assertions))]
{
unsafe { ALWAYS_EVICT_BLOCKS = _val; }
}
}
/// Retrieve the number of pages of guest memory that this host is tracking
#[no_mangle]
pub extern fn wbx_get_page_len(obj: &mut WaterboxHost, ret: &mut Return<usize>) {
ret.put(Ok(obj.page_len()))
}
/// Retrieve basic information for a tracked guest page. Index should be in 0..wbx_get_page_len().
/// 1 - readable, implies allocated
/// 2 - writable
/// 4 - executable
/// 0x10 - stack
/// 0x20 - allocated but not readable (guest-generated "guard")
/// 0x40 - invisible
/// 0x80 - dirty
#[no_mangle]
pub extern fn wbx_get_page_data(obj: &mut WaterboxHost, index: usize, ret: &mut Return<u8>) {
if index >= obj.page_len() {
ret.put(Err(anyhow!("Index out of range")))
} else {
ret.put(Ok(obj.page_info(index)))
}
}