FEX supports special guest libraries that call out to host code for speed and compatibility.
We support both guest->host thunks, as well as host->guest callbacks
## Building and using
The thunked libraries can be built via the `guest-libs` and `host-libs` targets of the main FEX project. The outputs are in `$BUILDDIR/Guest` and `$BUILDDIR/Host`
After that, a guest rootfs is needed with the guest-libs installed. Typically this is done with symlinks that replace the native guest libraries. eg
- Opcode 0xF 0x3F (IR::OP_THUNK) is used for the Guest -> Host transition. Register RSI (arg0 in guest) is passed as arg0 in host. Thunks are identified by a string in the form `library:function` that directly follows the Guest opcode.
-`Context::HandleCallback` does the Host -> Guest transition, and returns when the Guest function returns.
- A special thunk, `fex:loadlib` is used to load and initialize a matching host lib. For more details, look in `ThunkHandler_impl::LoadLib`
-`ThunkHandler_impl::CallCallback` is provided to the host libs, so they can call callbacks. It prepares guest arguments and uses `Context::HandleCallback`
ThunkLibs, Library loading
- In Guest code, when a thunking library is loaded it has a constructor that calls the `fex:loadlib` thunk, with the library name and callback unpackers, if any.
- In FEX, a matching host library is loaded using dlopen, `fexthunks_exports_$libname(CallCallbackPtr, GuestUnpackers)` is called to initialize the host library.
- In Host code, the real host library is loaded using dlopen and dlsym (see ldr generation)
- In Guest code (guest packer), a packer takes care of packing the arguments & return value into a struct in Guest stack. The packer is usually exported as a symbol from the Guest library.
- In Guest code (guest thunk), a thunk does the Guest -> Host transition via OP_THUNK, and passes the struct pointer as an argument
- FEX handles OP_THUNK and looks up the Host function from the opcode argument
- In Host code (host unpacker), an unpacker takes the arguments from the struct, and calls a function pointer with the implementation of that function. It also stores the return value, if any, to the struct.
Boilerplate code is automated using a dedicated code generator tool, which parses a C++ source file (`libX_interface.cpp`) that specializes
a templated `fex_gen_config` struct for each thunked function. The generator will pull all required function signatures from the original
library's header files and emit the appropriate boilerplate (guest->host thunks, argument packers/unpackers, host library loader, ...).
In most cases, an empty `fex_gen_config` specialization is sufficient, but if needed the generator behavior can be customized on a
function-by-function basis using an annotation-syntax: Binary properties are toggled by inheriting from a fixed set of tag types
(e.g. `fexgen::custom_host_impl`), whereas complicated properties are customized by defining struct members/aliases with a magic name
detected by the generator (e.g. `using uniform_va_type = char`).
For each thunked library, the generator outputs the following files:
-`thunks.inl`: Guest -> Host transition functions that use 0xF 0x3F
-`function_packs.inl`: Guest argument packers / rv handling, private to the SO. These are used to solve symbol resolution issues with glxGetProc*, etc.
-`function_packs_public.inl`: Guest argument packers / rv handling, exported from the SO. These are identical to the function_packs, but exported from the SO
There are two kinds of libs, simpler ones with no callbacks, and complex ones with callbacks. You can see how `libX11` is implemented for a callbacks example, and `libasound` for a non-callbacks example.