Thunks/X11: Fixes variadic packing and callbacks.

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.
This commit is contained in:
Ryan Houdek 2023-07-11 20:09:26 -07:00
parent 597da88035
commit e8fcb070b3
4 changed files with 288 additions and 119 deletions

View File

@ -0,0 +1,25 @@
#pragma once
#include <array>
#include <string>
extern "C" {
#include <X11/Xlib.h>
}
namespace X11 {
constexpr static std::array<std::string_view, 13> CallbackKeys = {{
XNGeometryCallback,
XNDestroyCallback,
XNPreeditStartCallback,
XNPreeditDoneCallback,
XNPreeditDrawCallback,
XNPreeditCaretCallback,
XNPreeditStateNotifyCallback,
XNStatusStartCallback,
XNStatusDoneCallback,
XNStatusDrawCallback,
XNR6PreeditCallback,
XNStringConversionCallback,
}};
}

View File

@ -20,134 +20,214 @@ extern "C" {
#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<unsigned long> args;
va_start(ap, ic);
for (;;) {
auto arg = va_arg(ap, unsigned long);
if (arg == 0)
break;
args.push_back(arg);
fprintf(stderr, "%016lX\n", arg);
}
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;
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<unsigned long> args;
va_start(ap, ic);
for (;;) {
auto arg = va_arg(ap, unsigned long);
if (arg == 0)
break;
args.push_back(arg);
fprintf(stderr, "%016lX\n", arg);
}
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;
va_end(ap);
auto rv = fexfn_pack_XSetICValues_internal(ic, args.size(), &args[0]);
fprintf(stderr, "RV: %p\n", rv);
return rv;
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);
}
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;
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*> 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);
}
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;
va_end(ap);
auto rv = fexfn_pack_XSetIMValues_internal(ic, args.size(), &args[0]);
fprintf(stderr, "RV: %p\n", rv);
return rv;
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<unsigned long> args;
va_start(ap, im);
for (;;) {
auto arg = va_arg(ap, unsigned long);
if (arg == 0)
break;
args.push_back(arg);
fprintf(stderr, "%016lX\n", arg);
}
fprintf(stderr, "XCreateIC\n");
va_list ap;
std::vector<void*> IncomingArguments;
va_end(ap);
auto rv = fexfn_pack_XCreateIC_internal(im, args.size(), &args[0]);
fprintf(stderr, "RV: %p\n", rv);
return rv;
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*> args;
va_start(ap, unused_arg);
for (;;) {
auto arg = va_arg(ap, void*);
if (arg == 0)
break;
args.push_back(arg);
fprintf(stderr, "%p\n", arg);
}
fprintf(stderr, "XVaCreateNestedList\n");
va_list ap;
std::vector<void*> IncomingArguments;
va_end(ap);
auto rv = fexfn_pack_XVaCreateNestedList_internal(unused_arg, args.size(), &args[0]);
fprintf(stderr, "RV: %p\n", rv);
return rv;
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) {
@ -265,6 +345,27 @@ extern "C" {
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;

View File

@ -5,7 +5,9 @@ desc: Handles callbacks and varargs
$end_info$
*/
#include <algorithm>
#include <atomic>
#include <cassert>
#include <cstdlib>
#include <stdio.h>
@ -31,6 +33,7 @@ extern "C" {
#include <utility>
#include "thunkgen_host_libX11.inl"
#include "X11Common.h"
#ifdef _M_ARM_64
// This Variadic asm only works for one signature
@ -46,15 +49,15 @@ extern "C" {
//
// Incoming:
// x0 = XIM
// x1 = count
// x2 = array of 64-bit values
// x1 = count (excluding final nullptr)
// x2 = array of 64-bit values (excluding final nullptr)
// x3 = Function to call
//
// Outgoing:
// x0: Ptr
__attribute__((naked))
void *libX11_Variadic_u64(uint64_t a_0, size_t count, unsigned long *list, void *Func) {
void *libX11_Variadic_u64(uint64_t a_0, size_t count, void **list, void *Func) {
asm volatile(R"(
# Move our function to x8, which will be unused
mov x8, x3
@ -122,10 +125,11 @@ void *libX11_Variadic_u64(uint64_t a_0, size_t count, unsigned long *list, void
# x1 = <count>
# x9 = <list ptr>
# Stack objects
# Count >= 7
# The number of arguments on the stack will always be (Count - 7) + 1
# Subtract 6 count objects
# Leaves us at least 1 (nullptr)
# Gives us the total number of objects that need to be stored on to the stack.
# If exactly 7 objects then only nullptr ends up on the stack.
sub x1, x1, 6
# Round up to the nearest pair
@ -141,29 +145,33 @@ void *libX11_Variadic_u64(uint64_t a_0, size_t count, unsigned long *list, void
# Store how much data we added to the stack in our callee saved register we stole
mov x28, x10
# Subtract one member due to nullptr ender
sub x10, x1, 1
# x11 - stack offset
# x11 - stack offset - for stack Arguments
mov x11, sp
# x12 - load offset
# x12 - load offset into input array (skipping the first 7 arguments)
add x12, x9, (7 * 8)
cmp x10, 1
# Compute the number of elements excluding the terminating nullptr
subs x10, x1, 1
b.eq .single%=
b.lt .no_single%=
.load_pair%=:
# Copy data from x12 to x11
ldp x1, x2, [x12], 16
stp x1, x2, [x11], 16
sub x10, x10, 8
# Stored two so subtract from the count.
sub x10, x10, 2
cmp x10, 1
b.gt .load_pair%=
b.lt .no_single%=
# One variable at most
.single%=:
# One variable at most
# Load the argument from the source.
# Then store that and the terminating nullptr.
ldr x1, [x12]
stp x1, xzr, [x11]
b .top_reg_args%=
@ -201,7 +209,32 @@ void *libX11_Variadic_u64(uint64_t a_0, size_t count, unsigned long *list, void
#endif
_XIC *fexfn_impl_libX11_XCreateIC_internal(XIM a_0, size_t count, unsigned long *list) {
// Walks the array of key:value pairs provided from the guest code side.
// Finalizing any callback functions that the guest side prepared for us.
template<typename CallbackType>
void FinalizeIncomingCallbacks(size_t Count, void **list) {
assert(Count % 2 == 0 && "Incoming arguments needs to be in pairs");
for (size_t i = 0; i < (Count / 2); ++i) {
const char *Key = static_cast<const char*>(list[i * 2]);
void** Data = &list[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;
}
CallbackType *IncomingCallback = reinterpret_cast<CallbackType*>(*Data);
FinalizeHostTrampolineForGuestFunction(IncomingCallback->callback);
}
}
_XIC *fexfn_impl_libX11_XCreateIC_internal(XIM a_0, size_t count, void **list) {
FinalizeIncomingCallbacks<XICCallback>(count, list);
switch(count) {
case 0: return fexldr_ptr_libX11_XCreateIC(a_0, nullptr); break;
case 1: return fexldr_ptr_libX11_XCreateIC(a_0, list[0], nullptr); break;
@ -222,7 +255,7 @@ _XIC *fexfn_impl_libX11_XCreateIC_internal(XIM a_0, size_t count, unsigned long
}
}
char* fexfn_impl_libX11_XGetICValues_internal(XIC a_0, size_t count, unsigned long *list) {
char* fexfn_impl_libX11_XGetICValues_internal(XIC a_0, size_t count, void **list) {
switch(count) {
case 0: return fexldr_ptr_libX11_XGetICValues(a_0, nullptr); break;
case 1: return fexldr_ptr_libX11_XGetICValues(a_0, list[0], nullptr); break;
@ -243,7 +276,9 @@ char* fexfn_impl_libX11_XGetICValues_internal(XIC a_0, size_t count, unsigned lo
}
}
char* fexfn_impl_libX11_XSetICValues_internal(XIC a_0, size_t count, unsigned long *list) {
char* fexfn_impl_libX11_XSetICValues_internal(XIC a_0, size_t count, void **list) {
FinalizeIncomingCallbacks<XICCallback>(count, list);
switch(count) {
case 0: return fexldr_ptr_libX11_XSetICValues(a_0, nullptr); break;
case 1: return fexldr_ptr_libX11_XSetICValues(a_0, list[0], nullptr); break;
@ -277,7 +312,7 @@ char* fexfn_impl_libX11_XGetIMValues_internal(XIM a_0, size_t count, void **list
case 8: return fexldr_ptr_libX11_XGetIMValues(a_0, list[0], list[1], list[2], list[3], list[4], list[5], list[6], list[7], nullptr); break;
default:
#ifdef _M_ARM_64
return reinterpret_cast<char*>(libX11_Variadic_u64(reinterpret_cast<uint64_t>(a_0), count, reinterpret_cast<unsigned long *>(list), reinterpret_cast<void*>(fexldr_ptr_libX11_XGetIMValues)));
return reinterpret_cast<char*>(libX11_Variadic_u64(reinterpret_cast<uint64_t>(a_0), count, list, reinterpret_cast<void*>(fexldr_ptr_libX11_XGetIMValues)));
#else
fprintf(stderr, "XGetIMValues_internal FAILURE\n");
abort();
@ -286,6 +321,8 @@ char* fexfn_impl_libX11_XGetIMValues_internal(XIM a_0, size_t count, void **list
}
char* fexfn_impl_libX11_XSetIMValues_internal(XIM a_0, size_t count, void **list) {
FinalizeIncomingCallbacks<XIMCallback>(count, list);
switch(count) {
case 0: return fexldr_ptr_libX11_XSetIMValues(a_0, nullptr); break;
case 1: return fexldr_ptr_libX11_XSetIMValues(a_0, list[0], nullptr); break;
@ -298,7 +335,7 @@ char* fexfn_impl_libX11_XSetIMValues_internal(XIM a_0, size_t count, void **list
case 8: return fexldr_ptr_libX11_XSetIMValues(a_0, list[0], list[1], list[2], list[3], list[4], list[5], list[6], list[7], nullptr); break;
default:
#ifdef _M_ARM_64
return reinterpret_cast<char*>(libX11_Variadic_u64(reinterpret_cast<uint64_t>(a_0), count, reinterpret_cast<unsigned long *>(list), reinterpret_cast<void*>(fexldr_ptr_libX11_XSetIMValues)));
return reinterpret_cast<char*>(libX11_Variadic_u64(reinterpret_cast<uint64_t>(a_0), count, list, reinterpret_cast<void*>(fexldr_ptr_libX11_XSetIMValues)));
#else
fprintf(stderr, "XSetIMValues_internal FAILURE\n");
abort();
@ -306,7 +343,9 @@ char* fexfn_impl_libX11_XSetIMValues_internal(XIM a_0, size_t count, void **list
}
}
XVaNestedList fexfn_impl_libX11_XVaCreateNestedList_internal(int unused_arg, size_t count, void** list) {
XVaNestedList fexfn_impl_libX11_XVaCreateNestedList_internal(int unused_arg, size_t count, void **list) {
FinalizeIncomingCallbacks<XIMCallback>(count, list);
switch(count) {
case 0: return fexldr_ptr_libX11_XVaCreateNestedList(unused_arg, nullptr); break;
case 1: return fexldr_ptr_libX11_XVaCreateNestedList(unused_arg, list[0], nullptr); break;
@ -319,7 +358,7 @@ XVaNestedList fexfn_impl_libX11_XVaCreateNestedList_internal(int unused_arg, siz
case 8: return fexldr_ptr_libX11_XVaCreateNestedList(unused_arg, list[0], list[1], list[2], list[3], list[4], list[5], list[6], list[7], nullptr); break;
default:
#ifdef _M_ARM_64
return reinterpret_cast<XVaNestedList>(libX11_Variadic_u64(unused_arg, count, reinterpret_cast<unsigned long *>(list), reinterpret_cast<void*>(fexldr_ptr_libX11_XVaCreateNestedList)));
return reinterpret_cast<XVaNestedList>(libX11_Variadic_u64(unused_arg, count, list, reinterpret_cast<void*>(fexldr_ptr_libX11_XVaCreateNestedList)));
#else
fprintf(stderr, "XVaCreateNestedList_internal FAILURE\n");
abort();

View File

@ -45,6 +45,10 @@ template<> struct fex_gen_type<std::remove_pointer_t<XIOErrorExitHandler>> {}; /
template<> struct fex_gen_type<Bool(Display*, xReply*, char*, int, XPointer)> {}; // XDisplay::async_handlers->handler
template<> struct fex_gen_type<Bool(XIM, XPointer, XPointer)> {}; // XIMProc
template<> struct fex_gen_type<Bool(XIC, XPointer, XPointer)> {}; // XICProc
template<> struct fex_gen_type<void(Display*, XPointer, XPointer)> {}; // XIDProc
template<> struct fex_gen_type<int(XImage*)> {}; // XImage::f.destroy_image
template<> struct fex_gen_type<unsigned long(XImage*, int, int)> {}; // XImage::f.get_pixel
template<> struct fex_gen_type<int(XImage*, int, int, unsigned long)> {}; // XImage::f.put_pixel
@ -653,15 +657,15 @@ template<> struct fex_gen_config<Xutf8DrawImageString> {};
template<> struct fex_gen_config<XOpenIM> {};
template<> struct fex_gen_config<XCloseIM> {};
template<> struct fex_gen_config<XGetIMValues> {
using uniform_va_type = void*;
using uniform_va_type = void*;
};
template<> struct fex_gen_config<XSetIMValues> {
using uniform_va_type = void*;
using uniform_va_type = void*;
};
template<> struct fex_gen_config<XDisplayOfIM> {};
template<> struct fex_gen_config<XLocaleOfIM> {};
template<> struct fex_gen_config<XCreateIC> {
using uniform_va_type = unsigned long;
using uniform_va_type = void*;
};
template<> struct fex_gen_config<XDestroyIC> {};
template<> struct fex_gen_config<XSetICFocus> {};
@ -670,11 +674,11 @@ template<> struct fex_gen_config<XwcResetIC> {};
template<> struct fex_gen_config<XmbResetIC> {};
template<> struct fex_gen_config<Xutf8ResetIC> {};
template<> struct fex_gen_config<XSetICValues> {
using uniform_va_type = unsigned long;
using uniform_va_type = void*;
};
template<> struct fex_gen_config<XGetICValues> {
using uniform_va_type = unsigned long;
using uniform_va_type = void*;
};
template<> struct fex_gen_config<XIMOfIC> {};
@ -683,11 +687,11 @@ template<> struct fex_gen_config<XmbLookupString> {};
template<> struct fex_gen_config<XwcLookupString> {};
template<> struct fex_gen_config<Xutf8LookupString> {};
template<> struct fex_gen_config<XVaCreateNestedList> {
using uniform_va_type = void*;
using uniform_va_type = void*;
};
template<> struct fex_gen_config<XRegisterIMInstantiateCallback> {};
template<> struct fex_gen_config<XUnregisterIMInstantiateCallback> {};
template<> struct fex_gen_config<XRegisterIMInstantiateCallback> : fexgen::custom_guest_entrypoint {};
template<> struct fex_gen_config<XUnregisterIMInstantiateCallback> : fexgen::custom_guest_entrypoint {};
template<> struct fex_gen_config<XInternalConnectionNumbers> {};
template<> struct fex_gen_config<XProcessInternalConnection> {};
template<> struct fex_gen_config<XAddConnectionWatch> {};