![]() The goal of the separate stacks was to allow this, but I never quite finished the job. Now, when a SEH exception (generally a Rust panic in a guest syscall handler, or a C# Exception in a callback) tries to unwind through guest code, it works. Note that we don't actually unwind the guest stack, as there's nothing useful to be gained from that; When an emulator core throws an exception like this, it should be considered completely hosed. Throw it out and get a new one. There were two bugs stopping this from working. First of all, we had custom thunks that lacked sufficient unwind information for RtlUnwind to get through. For the sysv <-> msabi adapter, this was fixed by making it regular Rust code instead of hand assembled junkus. So the compiler generates valid unwind information for all of that. Then we just JIT a small stub on top in the MsHostSysVGuest code, which needs no unwind information because it won't throw an exception itself and transparently passes execution to something with valid unwind information without invalidating that information. (NB: Clr JIT stubs use the same strategy.) For the host <-> guest stack transition code, a small hand generated unwind stub was added to interop.s that is registered with `RtlAddFunctionTable`. I've seen the unwind work successfully without this second set of unwind information, but better safe than sorry. Secondly, our misuse of SubSystemTib caught up with us. It's an old field, allegedly from OS/2, that we repurposed to hold TLS information needed for the waterbox stack transitions. Most people think nothing uses it any more, but in fact if it's set to a non-NULL value, but doesn't contain valid information, `KERNELBASE!GetModuleFileNameW` will crash when it tries to get a module name from there. The fix here was to simply tighten up our usage of SubSystemTib: We were already nulling it out when returning from guest code, but not when calling back to host code in guest code. Fixes #2487. Unwinding of this sort has never worked well in waterbox; the reason why that issue is more recent is that the particular reproducing case of firmware didn't cause an exception in a callback in older code; the exception happened in pure managed code. |
||
---|---|---|
.. | ||
.vscode | ||
src | ||
.gitignore | ||
Cargo.lock | ||
Cargo.toml | ||
README.md | ||
build-debug-no-dirty-detection.bat | ||
build-debug-no-dirty-detection.sh | ||
build-debug.bat | ||
build-debug.sh | ||
build-release.bat | ||
build-release.sh |
README.md
Waterboxhost
This is the native support code for Waterbox. It's intended to be consumed as a shared library from the host environment with a C api. For most work with Waterbox cores, you don't need to get into this at all.
API
The public api is mostly all in src/cinterface.rs
and has basic documentation on it. Bare minimum sequence of calls to
get going:
- (Optional) In a release environment, turn off certain checks to speed things up
wbx_set_always_evict_blocks()
- Create an environment, and load the ELF into it
wbx_create_host()
wbx_activate_host()
- Connect exports from the guest executable to your host system
wbx_get_proc_addr()
- Run the guest system's init, using function pointers it exposed through
wbx_get_proc_addr()
- Get ready to take savestates
wbx_seal()
- Run emulation, using frameadvance or other advance functions exposed by the guest through
wbx_get_proc_addr()
- Save and load states as needed
wbx_save_state()
wbx_load_state()
- Tear down the environment when done with it. (One shot processes that are about to exit can skip this; the OS will clean everything up)
wbx_deactivate_host()
wbx_destroy_host()
Some more advanced features:
- If you're keeping around multiple hosts that may compete for the same address space,
use
wbx_activate_host()
andwbx_deactivate_host()
to switch between them. - If you'd like to expose files to the virtual filesystem, see
wbx_mount_file()
andwbx_unmount_file()
. - If you need to call dynamically exposed functions that are not part of the static exports, see
wbx_get_callin_addr()
. - If you'd like the guest code to be able to call callbacks that you pass to it, see
wbx_get_callback_addr()
.
Building
Standard rust build infrastructure is used and can be installed with rustup
. At the moment, we're using the nightly-x86_64-pc-windows-gnu
chain on Windows, and the nightly-x86_64-unknown-linux-gnu
chain on linux. I don't know much about crosspiling, but presumably that will work.
The linux chain works fine in WSL, anyway. When used in a Windows environment with the right default chain, build-release.bat
will build
waterboxhost.dll and copy it to the right place. When used in a Linux (or WSL) environment with the right default chain, build-release.sh
will build libwaterboxhost.so and copy it to the right place.