mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 14:00:16 +02:00
Two bugs here that caused thunking X11 thunking in Wine/Proton to not work. The easier of the two. The various variadic functions that we thunk actually take key:value pairs where the first is a string pointer, and the value can be various things. We need to handle these as true key:value pairs rather than finding the first nullptr and dropping the remainder. Additionally, there are 12 keys that specify a callback that FEX needs to catch and convert to host callable. Wine is the first application that I have seen that actually uses this. If these callbacks aren't wired up then it it can miss events. The harder of the two problems is the `libX11_Variadic_u64` function was subtly incorrect. Nothing had previously truly exercised this and my test program didn't notice anything wrong while writing it. The first incorrect thing was that it was subtracting the nullptr ender variable before the stack size calculation, causing the value to overwrite the stack if the number of remaining elements was event. Secondly the assembly that was storing two elements per step was decrementing the counter by 8 instead of two. Didn't pick this up before since I believe the code was only hitting the non-pair path before. This gets Proton thunking working under FEX now.
375 lines
11 KiB
C++
375 lines
11 KiB
C++
/*
|
|
$info$
|
|
tags: thunklibs|X11
|
|
desc: Handles callbacks and varargs
|
|
$end_info$
|
|
*/
|
|
|
|
extern "C" {
|
|
#define XUTIL_DEFINE_FUNCTIONS
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xutil.h>
|
|
#include <X11/Xresource.h>
|
|
|
|
#include <X11/Xproto.h>
|
|
#include <X11/XKBlib.h>
|
|
|
|
// Include Xlibint.h and undefine some of its macros that clash with the standard library
|
|
#include <X11/Xlibint.h>
|
|
#undef min
|
|
#undef max
|
|
}
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
#include <stdio.h>
|
|
#include <cstring>
|
|
#include <map>
|
|
#include <list>
|
|
#include <string>
|
|
#include <unistd.h>
|
|
|
|
#include "common/Guest.h"
|
|
#include <stdarg.h>
|
|
|
|
#include "thunkgen_guest_libX11.inl"
|
|
#include "X11Common.h"
|
|
|
|
// Custom implementations //
|
|
|
|
#include <vector>
|
|
|
|
namespace {
|
|
// The various X11 variadic functions take a flattened sequence of key:value pairs as arguments.
|
|
// The key element is a pointer to a string.
|
|
// The value element is a pointer to the data of that key type.
|
|
//
|
|
// Some keys describe function callbacks for various events that the server interface can call.
|
|
// FEX needs to walk these keys and ensure any callback function has a trampoline so the native
|
|
// X11 library can call it.
|
|
template<typename CallbackType>
|
|
static std::list<CallbackType> ConvertCallbackArguments(std::vector<void*> &IncomingArguments) {
|
|
assert(IncomingArguments.size() % 2 == 0 && "Incoming arguments needs to be in pairs");
|
|
|
|
std::list<CallbackType> Callbacks;
|
|
// Walk the arguments and convert any callbacks.
|
|
const size_t ArgumentPairs = IncomingArguments.size() / 2;
|
|
for (size_t i = 0; i < ArgumentPairs; ++i) {
|
|
const char *Key = static_cast<const char*>(IncomingArguments[i * 2]);
|
|
void** Data = &IncomingArguments[i * 2 + 1];
|
|
if (!*Data) {
|
|
continue;
|
|
}
|
|
|
|
// Check if the key is a callback and needs to be modified.
|
|
auto KeyIt = std::find(X11::CallbackKeys.begin(), X11::CallbackKeys.end(), Key);
|
|
if (KeyIt == X11::CallbackKeys.end()) {
|
|
continue;
|
|
}
|
|
|
|
// Key matches a callback, we need to wrap this.
|
|
CallbackType *IncomingCallback = reinterpret_cast<CallbackType*>(*Data);
|
|
CallbackType *ConvertedCallback = &Callbacks.emplace_back(CallbackType {
|
|
// Client data stays the same.
|
|
.client_data = IncomingCallback->client_data,
|
|
|
|
// Callback needs a trampoline.
|
|
.callback = AllocateHostTrampolineForGuestFunction(IncomingCallback->callback),
|
|
});
|
|
|
|
// Add this converted back in.
|
|
*Data = ConvertedCallback;
|
|
}
|
|
|
|
return Callbacks;
|
|
}
|
|
}
|
|
|
|
extern "C" {
|
|
char* XGetICValues(XIC ic, ...) {
|
|
fprintf(stderr, "XGetICValues\n");
|
|
va_list ap;
|
|
std::vector<void*> args;
|
|
va_start(ap, ic);
|
|
for (;;) {
|
|
auto arg = va_arg(ap, void*);
|
|
if (arg == 0)
|
|
break;
|
|
args.push_back(arg);
|
|
fprintf(stderr, "%p\n", arg);
|
|
}
|
|
|
|
va_end(ap);
|
|
|
|
auto rv = fexfn_pack_XGetICValues_internal(ic, args.size(), &args[0]);
|
|
fprintf(stderr, "RV: %p\n", rv);
|
|
return rv;
|
|
}
|
|
|
|
char* XSetICValues(XIC ic, ...) {
|
|
fprintf(stderr, "XSetICValues\n");
|
|
va_list ap;
|
|
std::vector<void*> IncomingArguments;
|
|
va_start(ap, ic);
|
|
for (;;) {
|
|
auto Key = va_arg(ap, void*);
|
|
if (Key == 0)
|
|
break;
|
|
|
|
auto Value = va_arg(ap, void*);
|
|
IncomingArguments.emplace_back(Key);
|
|
IncomingArguments.emplace_back(Value);
|
|
}
|
|
|
|
va_end(ap);
|
|
|
|
// Callback memory needs to live beyond internal function call.
|
|
std::list<XICCallback> Callbacks = ConvertCallbackArguments<XICCallback>(IncomingArguments);
|
|
|
|
auto rv = fexfn_pack_XSetICValues_internal(ic, IncomingArguments.size(), &IncomingArguments[0]);
|
|
fprintf(stderr, "RV: %p\n", rv);
|
|
return rv;
|
|
}
|
|
|
|
char* XGetIMValues(XIM ic, ...) {
|
|
fprintf(stderr, "XGetIMValues\n");
|
|
va_list ap;
|
|
std::vector<void*> args;
|
|
va_start(ap, ic);
|
|
for (;;) {
|
|
auto arg = va_arg(ap, void*);
|
|
if (arg == 0)
|
|
break;
|
|
args.push_back(arg);
|
|
fprintf(stderr, "%p\n", arg);
|
|
}
|
|
|
|
va_end(ap);
|
|
auto rv = fexfn_pack_XGetIMValues_internal(ic, args.size(), &args[0]);
|
|
fprintf(stderr, "RV: %p\n", rv);
|
|
return rv;
|
|
}
|
|
|
|
char* XSetIMValues(XIM ic, ...) {
|
|
fprintf(stderr, "XSetIMValues\n");
|
|
va_list ap;
|
|
std::vector<void*> IncomingArguments;
|
|
va_start(ap, ic);
|
|
for (;;) {
|
|
auto Key = va_arg(ap, void*);
|
|
if (Key == 0)
|
|
break;
|
|
|
|
auto Value = va_arg(ap, void*);
|
|
IncomingArguments.emplace_back(Key);
|
|
IncomingArguments.emplace_back(Value);
|
|
fprintf(stderr, "%s\n", (char*)Key);
|
|
}
|
|
|
|
va_end(ap);
|
|
|
|
// Callback memory needs to live beyond internal function call.
|
|
std::list<XIMCallback> Callbacks = ConvertCallbackArguments<XIMCallback>(IncomingArguments);
|
|
|
|
// Send a count (not including nullptr);
|
|
auto rv = fexfn_pack_XSetIMValues_internal(ic, IncomingArguments.size(), &IncomingArguments[0]);
|
|
fprintf(stderr, "RV: %p\n", rv);
|
|
return rv;
|
|
}
|
|
|
|
_XIC* XCreateIC(XIM im, ...) {
|
|
fprintf(stderr, "XCreateIC\n");
|
|
va_list ap;
|
|
std::vector<void*> IncomingArguments;
|
|
|
|
va_start(ap, im);
|
|
for (;;) {
|
|
auto Key = va_arg(ap, void*);
|
|
if (Key == 0)
|
|
break;
|
|
|
|
auto Value = va_arg(ap, void*);
|
|
IncomingArguments.emplace_back(Key);
|
|
IncomingArguments.emplace_back(Value);
|
|
}
|
|
|
|
va_end(ap);
|
|
|
|
// Callback memory needs to live beyond internal function call.
|
|
std::list<XICCallback> Callbacks = ConvertCallbackArguments<XICCallback>(IncomingArguments);
|
|
|
|
auto rv = fexfn_pack_XCreateIC_internal(im, IncomingArguments.size(), &IncomingArguments[0]);
|
|
return rv;
|
|
}
|
|
|
|
XVaNestedList XVaCreateNestedList(int unused_arg, ...) {
|
|
fprintf(stderr, "XVaCreateNestedList\n");
|
|
va_list ap;
|
|
std::vector<void*> IncomingArguments;
|
|
|
|
va_start(ap, unused_arg);
|
|
for (;;) {
|
|
auto Key = va_arg(ap, void*);
|
|
if (Key == 0)
|
|
break;
|
|
|
|
auto Value = va_arg(ap, void*);
|
|
IncomingArguments.emplace_back(Key);
|
|
IncomingArguments.emplace_back(Value);
|
|
}
|
|
|
|
va_end(ap);
|
|
|
|
// Callback memory needs to live beyond internal function call.
|
|
std::list<XICCallback> Callbacks = ConvertCallbackArguments<XICCallback>(IncomingArguments);
|
|
|
|
auto rv = fexfn_pack_XVaCreateNestedList_internal(unused_arg, IncomingArguments.size(), &IncomingArguments[0]);
|
|
fprintf(stderr, "RV: %p\n", rv);
|
|
return rv;
|
|
}
|
|
|
|
static void LockMutexFunction(LockInfoPtr) {
|
|
fprintf(stderr, "libX11: LockMutex\n");
|
|
}
|
|
|
|
static void UnlockMutexFunction(LockInfoPtr) {
|
|
fprintf(stderr, "libX11: LockMutex\n");
|
|
}
|
|
|
|
int XFree(void* ptr) {
|
|
// This function must be able to handle both guest heap pointers *and* host heap pointers,
|
|
// so it only forwards to the native host library for the latter.
|
|
//
|
|
// This is because Xlibint users allocate memory using internal macros aliasing to libc's
|
|
// malloc but then they free using the function XFree. For libX11, this is not a problem since
|
|
// the allocation happens in a thunked API function (and hence on the host heap), but if a
|
|
// function from an unthunked library accesses Xlibint, it will allocate on the guest heap.
|
|
//
|
|
// One notable example where this was encountered is XF86VidModeGetAllModeLines.
|
|
|
|
if (!ptr || IsHostHeapAllocation(ptr)) {
|
|
return fexfn_pack_XFree(ptr);
|
|
} else {
|
|
free(ptr);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
void XFreeEventData(Display* display, XGenericEventCookie* cookie) {
|
|
// Has the same heap-mismatch issue as XFree, so we have to reimplement it manually
|
|
if (_XIsEventCookie(display, (XEvent*)cookie) && cookie->data) {
|
|
XFree(cookie->data);
|
|
cookie->data = nullptr;
|
|
cookie->cookie = 0;
|
|
}
|
|
}
|
|
|
|
Display* XOpenDisplay(const char* name) {
|
|
auto ret = fexfn_pack_XOpenDisplay(name);
|
|
|
|
for (auto& funcptr : ret->event_vec) {
|
|
if (!funcptr) {
|
|
continue;
|
|
}
|
|
MakeHostFunctionGuestCallable(funcptr);
|
|
}
|
|
|
|
for (auto& funcptr : ret->wire_vec) {
|
|
if (!funcptr) {
|
|
continue;
|
|
}
|
|
MakeHostFunctionGuestCallable(funcptr);
|
|
}
|
|
|
|
MakeHostFunctionGuestCallable(ret->resource_alloc);
|
|
MakeHostFunctionGuestCallable(ret->idlist_alloc);
|
|
#if (X11_VERSION_MAJOR >= 1 && X11_VERSION_MINOR >= 7 && X11_VERSION_PATCH >= 0)
|
|
// Doesn't exist on older X11
|
|
MakeHostFunctionGuestCallable(ret->exit_handler);
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
Status _XReply(Display* display, xReply* reply, int extra, Bool discard) {
|
|
for(auto handler = display->async_handlers; handler; handler = handler->next) {
|
|
// Make host-callable and overwrite in-place
|
|
// NOTE: This data seems to be stack-allocated specifically for XReply usually, so it's *probably* safe to overwrite
|
|
handler->handler = AllocateHostTrampolineForGuestFunction(handler->handler);
|
|
}
|
|
return fexfn_pack__XReply(display, reply, extra, discard);
|
|
}
|
|
|
|
static int _XInitDisplayLock(Display* display) {
|
|
MakeHostFunctionGuestCallable(display->lock_fns->lock_display);
|
|
MakeHostFunctionGuestCallable(display->lock_fns->unlock_display);
|
|
return 0;
|
|
}
|
|
|
|
Status XInitThreads() {
|
|
return fexfn_pack_XInitThreadsInternal((uintptr_t)_XInitDisplayLock, (uintptr_t)CallbackUnpack<decltype(_XInitDisplayLock)>::Unpack);
|
|
}
|
|
|
|
// Register the host function pointers written by _XInitImageFuncPtrs (and
|
|
// its callers) to be guest-callable
|
|
static void FixupImageFuncPtrs(XImage* image) {
|
|
image->f.create_image = XCreateImage;
|
|
MakeHostFunctionGuestCallable(image->f.destroy_image);
|
|
MakeHostFunctionGuestCallable(image->f.get_pixel);
|
|
MakeHostFunctionGuestCallable(image->f.put_pixel);
|
|
// TODO: image->f.sub_image
|
|
MakeHostFunctionGuestCallable(image->f.add_pixel);
|
|
}
|
|
|
|
void _XInitImageFuncPtrs(XImage* image) {
|
|
fexfn_pack__XInitImageFuncPtrs(image);
|
|
FixupImageFuncPtrs(image);
|
|
}
|
|
|
|
XImage *XCreateImage(
|
|
Display* display, Visual* visual,
|
|
unsigned int depth, int format,
|
|
int offset, char* data,
|
|
unsigned int width, unsigned int height,
|
|
int pad, int bpp) {
|
|
auto ret = fexfn_pack_XCreateImage(display, visual, depth, format, offset, data, width, height, pad, bpp);
|
|
FixupImageFuncPtrs(ret);
|
|
return ret;
|
|
}
|
|
|
|
Status XInitImage(XImage* image) {
|
|
auto ret = fexfn_pack_XInitImage(image);
|
|
FixupImageFuncPtrs(image);
|
|
return ret;
|
|
}
|
|
|
|
Bool XRegisterIMInstantiateCallback(Display* dpy,
|
|
struct _XrmHashBucketRec* rdb,
|
|
char* res_name,
|
|
char* res_class,
|
|
XIDProc callback,
|
|
XPointer client_data
|
|
) {
|
|
return fexfn_pack_XRegisterIMInstantiateCallback(dpy, rdb, res_name, res_class, AllocateHostTrampolineForGuestFunction(callback), client_data);
|
|
}
|
|
|
|
Bool XUnregisterIMInstantiateCallback(
|
|
Display* dpy,
|
|
struct _XrmHashBucketRec* rdb,
|
|
char* res_name,
|
|
char* res_class,
|
|
XIDProc callback,
|
|
XPointer client_data
|
|
) {
|
|
return fexfn_pack_XUnregisterIMInstantiateCallback(dpy, rdb, res_name, res_class, AllocateHostTrampolineForGuestFunction(callback), client_data);
|
|
}
|
|
|
|
void (*_XLockMutex_fn)(LockInfoPtr) = LockMutexFunction;
|
|
void (*_XUnlockMutex_fn)(LockInfoPtr) = UnlockMutexFunction;
|
|
LockInfoPtr _Xglobal_lock = (LockInfoPtr)0x4142434445464748ULL;
|
|
}
|
|
|
|
LOAD_LIB(libX11)
|