Library Forwarding/wayland: For 32-bit guests, support repacking wl_interface/wl_message/wl_argument

This commit is contained in:
Tony Wasserka 2024-02-14 18:20:36 +01:00
parent b9f2389d74
commit 7edab7ee3b
3 changed files with 266 additions and 24 deletions

View File

@ -167,6 +167,14 @@ struct guest_layout<T*> {
const guest_layout<T>* get_pointer() const {
return reinterpret_cast<const guest_layout<T>*>(uintptr_t { data });
}
T* force_get_host_pointer() {
return reinterpret_cast<T*>(uintptr_t { data });
}
const T* force_get_host_pointer() const {
return reinterpret_cast<const T*>(uintptr_t { data });
}
};
template<typename T>

View File

@ -5,6 +5,7 @@ $end_info$
*/
#include <string_view>
#include <unordered_map>
#include <wayland-client.h>
#include <stdio.h>
@ -19,15 +20,219 @@ $end_info$
#include <charconv>
#include <cstring>
#include <map>
#include <span>
#include <string>
#include <ranges>
template<>
struct guest_layout<wl_argument> {
#ifdef IS_32BIT_THUNK
using type = uint32_t;
#else
using type = wl_argument;
#endif
type data;
guest_layout& operator=(const wl_argument from) {
#ifdef IS_32BIT_THUNK
data = from.u;
#else
data = from;
#endif
return *this;
}
};
#include "thunkgen_host_libwayland-client.inl"
// Maps guest interface to host_interfaces
static std::unordered_map<guest_layout<const wl_interface>*, wl_interface*> guest_to_host_interface;
static wl_interface* get_proxy_interface(wl_proxy* proxy) {
// wl_proxy is a private struct, but its first member is the wl_interface pointer
return *reinterpret_cast<wl_interface**>(proxy);
}
static void assert_is_valid_host_interface(const wl_interface* interface) {
// The 32-bit data layout of wl_interface differs from the 64-bit one due to
// its pointer members. Our repacking code takes care of these differences.
//
// To ensure this indeed functions properly, a simple consistency check is
// applied here: If any of the message counts are absurdly high, it means
// data from pointer members leaked into other members.
if ((uint32_t)interface->method_count >= 0x1000 || (uint32_t)interface->event_count >= 0x1000) {
fprintf(stderr, "ERROR: Expected %p to be a host wl_interface, but it's not\n", interface);
std::abort();
}
}
#ifdef IS_32BIT_THUNK
static void assert_is_valid_guest_interface(guest_layout<const wl_interface*> guest_interface) {
// Consistency check for expected data layout.
// See assert_is_valid_host_interface for details
const wl_interface* as_host_interface = (const wl_interface*)guest_interface.force_get_host_pointer();
if ((uint32_t)as_host_interface->method_count < 0x1000 && (uint32_t)as_host_interface->event_count < 0x1000) {
fprintf(stderr, "ERROR: Expected %p to be a guest wl_interface, but it's not\n", guest_interface.force_get_host_pointer());
std::abort();
}
}
static void repack_guest_wl_interface_to_host(guest_layout<const wl_interface*> guest_interface_ptr, wl_interface* host_interface) {
auto& guest_interface = *guest_interface_ptr.get_pointer();
static_assert(sizeof(guest_interface) == 24);
*host_interface = host_layout<wl_interface> { guest_interface }.data;
fex_apply_custom_repacking_entry(reinterpret_cast<host_layout<wl_interface>&>(*host_interface), guest_interface);
}
// Maps guest interface pointers to host pointers
static const wl_interface* lookup_wl_interface(guest_layout<const wl_interface*> interface) {
// Used e.g. for wl_shm_pool_destroy
if (interface.force_get_host_pointer() == nullptr) {
return nullptr;
}
auto [host_interface_it, inserted] = guest_to_host_interface.emplace(interface.get_pointer(), nullptr);
if (!inserted) {
assert_is_valid_host_interface(host_interface_it->second);
return host_interface_it->second;
}
assert_is_valid_guest_interface(interface);
fprintf(stderr, "Unknown wayland interface %p, adding to registry\n", interface.get_pointer());
host_interface_it->second = new wl_interface;
wl_interface* host_interface = host_interface_it->second;
repack_guest_wl_interface_to_host(interface, host_interface);
return host_interface_it->second;
}
void fex_custom_repack_entry(host_layout<wl_interface>& into, guest_layout<wl_interface> const& from) {
// NOTE: These arrays are complements to global symbols in the guest, so we
// never explicitly free this memory
auto& host_interface = into.data;
into.data.methods = new wl_message[into.data.method_count];
into.data.events = new wl_message[into.data.event_count];
memset((void*)host_interface.methods, 0, sizeof(wl_message) * host_interface.method_count);
for (int i = 0; i < host_interface.method_count; ++i) {
const auto& guest_method { from.data.methods.get_pointer()[i] };
host_layout<wl_message> host_method { guest_method };
fex_apply_custom_repacking_entry(host_method, guest_method);
memcpy((void*)&host_interface.methods[i], &host_method, sizeof(host_method));
}
memset((void*)host_interface.events, 0, sizeof(wl_message) * host_interface.event_count);
for (int i = 0; i < host_interface.event_count; ++i) {
const auto& guest_event { from.data.events.get_pointer()[i] };
host_layout<wl_message> host_event { guest_event };
fex_apply_custom_repacking_entry(host_event, guest_event);
memcpy((void*)&host_interface.events[i], &host_event, sizeof(host_event));
}
}
bool fex_custom_repack_exit(guest_layout<wl_interface>&, host_layout<wl_interface> const&) {
fprintf(stderr, "Should not be called: %s\n", __PRETTY_FUNCTION__);
std::abort();
}
void fex_custom_repack_entry(host_layout<wl_message>& into, guest_layout<wl_message> const& from) {
auto& host_method = into.data;
auto num_types = std::ranges::count_if(std::string_view { host_method.signature }, isalpha);
if (num_types) {
host_method.types = new const wl_interface*[num_types];
for (int type = 0; type < num_types; ++type) {
auto guest_interface_addr = from.data.types.get_pointer()[type];
host_method.types[type] = guest_interface_addr.force_get_host_pointer() ? lookup_wl_interface(guest_interface_addr) : nullptr;
}
}
}
bool fex_custom_repack_exit(guest_layout<wl_message>&, host_layout<wl_message> const&) {
fprintf(stderr, "Should not be called: %s\n", __PRETTY_FUNCTION__);
std::abort();
}
#else
const wl_interface* lookup_wl_interface(guest_layout<const wl_interface*> interface) {
return interface.force_get_host_pointer();
}
#endif
static wl_proxy* fexfn_impl_libwayland_client_wl_proxy_create(wl_proxy* proxy, guest_layout<const wl_interface*> guest_interface_raw) {
auto host_interface = lookup_wl_interface(guest_interface_raw);
return fexldr_ptr_libwayland_client_wl_proxy_create(proxy, host_interface);
}
#define WL_CLOSURE_MAX_ARGS 20
static auto
fex_wl_remap_argument_list(guest_layout<wl_argument*> args, const wl_message& message) {
#ifndef IS_32BIT_THUNK
// Cast to host layout and return as std::span
wl_argument* host_args = host_layout<wl_argument*> { args }.data;
return std::span<wl_argument, WL_CLOSURE_MAX_ARGS> { host_args, WL_CLOSURE_MAX_ARGS };
#else
// Return a new array of elements zero-extended to 64-bit
std::array<wl_argument, WL_CLOSURE_MAX_ARGS> host_args;
int arg_count = std::ranges::count_if(std::string_view { message.signature }, isalpha);
for (int i = 0; i < arg_count; ++i) {
// NOTE: wl_argument can store a pointer argument, so for 32-bit guests
// we need to make sure the upper 32-bits are explicitly zeroed
std::memset(&host_args[i], 0, sizeof(host_args[i]));
std::memcpy(&host_args[i], &args.get_pointer()[i], sizeof(args.get_pointer()[i]));
}
return host_args;
#endif
}
extern "C" void
fexfn_impl_libwayland_client_wl_proxy_marshal_array(
wl_proxy *proxy, uint32_t opcode,
guest_layout<wl_argument*> args) {
auto host_args = fex_wl_remap_argument_list(args, get_proxy_interface(proxy)->methods[opcode]);
fexldr_ptr_libwayland_client_wl_proxy_marshal_array(proxy, opcode, host_args.data());
}
static wl_proxy*
fex_wl_proxy_marshal_array(
wl_proxy *proxy, uint32_t opcode,
guest_layout<wl_argument*> args,
guest_layout<const wl_interface*> guest_interface,
std::optional<uint32_t> version,
std::optional<uint32_t> flags) {
auto interface = lookup_wl_interface(guest_interface);
assert_is_valid_host_interface(get_proxy_interface(proxy));
auto host_args = fex_wl_remap_argument_list(args, get_proxy_interface(proxy)->methods[opcode]);
if (false) {
} else if (!version && !flags) {
return nullptr;
} else if (version && flags) {
// wl_proxy_marshal_array_flags is only available starting from Wayland 1.19.91
#if WAYLAND_VERSION_MAJOR * 10000 + WAYLAND_VERSION_MINOR * 100 + WAYLAND_VERSION_MICRO >= 11991
return fexldr_ptr_libwayland_client_wl_proxy_marshal_array_flags(proxy, opcode, interface, version.value(), flags.value(), host_args.data());
#else
fprintf(stderr, "Host Wayland version is too old to support FEX thunking\n");
__builtin_trap();
#endif
} else {
fprintf(stderr, "Invalid configuration\n");
__builtin_trap();
}
}
extern "C" wl_proxy*
fexfn_impl_libwayland_client_wl_proxy_marshal_array_flags(
wl_proxy *proxy, uint32_t opcode,
guest_layout<const wl_interface*> interface,
uint32_t version, uint32_t flags,
guest_layout<wl_argument*> args) {
return fex_wl_proxy_marshal_array(proxy, opcode, args, interface, version, flags);
}
// See wayland-util.h for documentation on protocol message signatures
template<char> struct ArgType;
template<> struct ArgType<'s'> { using type = const char*; };
@ -46,21 +251,25 @@ static void WaylandFinalizeHostTrampolineForGuestListener(void (*callback)()) {
}
extern "C" int fexfn_impl_libwayland_client_wl_proxy_add_listener(struct wl_proxy *proxy,
guest_layout<void (**)(void)> callback_raw, void* data) {
guest_layout<void (**)(void)> callback_table_raw, void* data) {
auto interface = get_proxy_interface(proxy);
assert_is_valid_host_interface(interface);
auto callback_table = callback_table_raw.force_get_host_pointer();
for (int i = 0; i < interface->event_count; ++i) {
auto signature_view = std::string_view { interface->events[i].signature };
// A leading number indicates the minimum protocol version
uint32_t since_version = 0;
auto [ptr, res] = std::from_chars(signature_view.begin(), signature_view.end(), since_version, 10);
std::string signature { ptr, &*signature_view.end() };
auto signature = std::string { signature_view.substr(ptr - signature_view.begin()) };
// ? just indicates that the argument may be null, so it doesn't change the signature
signature.erase(std::remove(signature.begin(), signature.end(), '?'), signature.end());
auto callback = reinterpret_cast<void(*)()>(uintptr_t { callback_raw.get_pointer()[i].data });
auto callback = callback_table[i];
if (signature == "") {
// E.g. xdg_toplevel::close
@ -162,15 +371,15 @@ extern "C" int fexfn_impl_libwayland_client_wl_proxy_add_listener(struct wl_prox
}
// Pass the original function pointer table to the host wayland library. This ensures the table is valid until the listener is unregistered.
return fexldr_ptr_libwayland_client_wl_proxy_add_listener(proxy,
reinterpret_cast<void(**)(void)>(callback_raw.get_pointer()),
data);
return fexldr_ptr_libwayland_client_wl_proxy_add_listener(proxy, callback_table, data);
}
wl_interface* fexfn_impl_libwayland_client_fex_wl_exchange_interface_pointer(wl_interface* guest_interface, char const* name) {
auto host_interface = reinterpret_cast<wl_interface*>(dlsym(fexldr_ptr_libwayland_client_so, (std::string { name } + "_interface").c_str()));
void fexfn_impl_libwayland_client_fex_wl_exchange_interface_pointer(guest_layout<wl_interface*> guest_interface_raw, const char* name) {
auto& guest_interface = *guest_interface_raw.get_pointer();
auto& host_interface = guest_to_host_interface[reinterpret_cast<guest_layout<const wl_interface>*>(&guest_interface)];
host_interface = reinterpret_cast<wl_interface*>(dlsym(fexldr_ptr_libwayland_client_so, (std::string { name } + "_interface").c_str()));
if (!host_interface) {
fprintf(stderr, "Could not find host interface corresponding to %p (%s)\n", guest_interface, name);
fprintf(stderr, "Could not find host interface corresponding to %p (%s)\n", &guest_interface, name);
std::abort();
}
@ -178,23 +387,34 @@ wl_interface* fexfn_impl_libwayland_client_fex_wl_exchange_interface_pointer(wl_
// them into the rodata section of the application itself instead of the
// library. To copy the host information to them on startup, we must
// temporarily disable write-protection on this data hence.
auto page_begin = reinterpret_cast<uintptr_t>(guest_interface) & ~uintptr_t { 0xfff };
auto page_begin = reinterpret_cast<uintptr_t>(guest_interface_raw.force_get_host_pointer()) & ~uintptr_t { 0xfff };
if (0 != mprotect((void*)page_begin, 0x1000, PROT_READ | PROT_WRITE)) {
fprintf(stderr, "ERROR: %s\n", strerror(errno));
std::abort();
}
#ifdef IS_32BIT_THUNK
// Requires struct repacking for wl_interface
#error Not implemented
#ifndef IS_32BIT_THUNK
memcpy(&guest_interface, host_interface, sizeof(wl_interface));
#else
memcpy(guest_interface, host_interface, sizeof(wl_interface));
guest_interface = to_guest(to_host_layout(*host_interface));
// NOTE: These arrays are complements to global symbols in the guest, so we
// never explicitly free this memory
guest_interface.data.methods.data = (uintptr_t)new guest_layout<wl_message>[host_interface->method_count];
for (int i = 0; i < host_interface->method_count; ++i) {
guest_interface.data.methods.get_pointer()[i] = to_guest(to_host_layout(host_interface->methods[i]));
guest_interface.data.methods.get_pointer()[i].data.types = to_guest(to_host_layout(host_interface->methods[i].types));
}
guest_interface.data.events.data = (uintptr_t)new guest_layout<wl_message>[host_interface->event_count];
for (int i = 0; i < host_interface->event_count; ++i) {
guest_interface.data.events.get_pointer()[i] = to_guest(to_host_layout(host_interface->events[i]));
guest_interface.data.events.get_pointer()[i].data.types = to_guest(to_host_layout(host_interface->events[i].types));
}
#endif
// TODO: Disabled until we ensure the interface data is indeed stored in rodata
// mprotect((void*)page_begin, 0x1000, PROT_READ);
return host_interface;
}
void fexfn_impl_libwayland_client_fex_wl_get_method_signature(wl_proxy* proxy, uint32_t opcode, char* out) {

View File

@ -16,25 +16,34 @@ struct fex_gen_param {};
template<> struct fex_gen_type<wl_display> : fexgen::opaque_type {};
template<> struct fex_gen_type<wl_proxy> : fexgen::opaque_type {};
template<> struct fex_gen_type<wl_interface> : fexgen::opaque_type {};
template<> struct fex_gen_type<wl_event_queue> : fexgen::opaque_type {};
// Passed over Wayland's wire protocol for some functions
template<> struct fex_gen_type<wl_array> {};
template<> struct fex_gen_type<wl_array> : fexgen::emit_layout_wrappers {};
#ifdef IS_32BIT_THUNK
// wl_interface and wl_message reference each other through pointers
template<> struct fex_gen_type<wl_interface> : fexgen::emit_layout_wrappers {};
template<> struct fex_gen_config<&wl_interface::methods> : fexgen::custom_repack {};
template<> struct fex_gen_config<&wl_interface::events> : fexgen::custom_repack {};
template<> struct fex_gen_type<wl_message> : fexgen::emit_layout_wrappers {};
template<> struct fex_gen_config<&wl_message::types> : fexgen::custom_repack {};
#else
template<> struct fex_gen_type<wl_interface> : fexgen::assume_compatible_data_layout {};
#endif
template<> struct fex_gen_config<wl_proxy_destroy> : fexgen::custom_guest_entrypoint {};
template<> struct fex_gen_config<wl_display_cancel_read> {};
template<> struct fex_gen_config<wl_display_connect> {};
template<> struct fex_gen_config<wl_display_flush> {};
template<> struct fex_gen_config<wl_display_cancel_read> {};
template<> struct fex_gen_config<wl_display_create_queue> {};
template<> struct fex_gen_config<wl_display_disconnect> {};
template<> struct fex_gen_config<wl_display_dispatch> {};
template<> struct fex_gen_config<wl_display_dispatch_pending> {};
template<> struct fex_gen_config<wl_display_dispatch_queue> {};
template<> struct fex_gen_config<wl_display_dispatch_queue_pending> {};
template<> struct fex_gen_config<wl_display_flush> {};
template<> struct fex_gen_config<wl_display_prepare_read> {};
template<> struct fex_gen_config<wl_display_prepare_read_queue> {};
template<> struct fex_gen_config<wl_display_read_events> {};
@ -49,7 +58,8 @@ template<> struct fex_gen_config<wl_proxy_add_listener> : fexgen::custom_host_im
template<> struct fex_gen_param<wl_proxy_add_listener, 1, void(**)()> : fexgen::ptr_passthrough {};
// User-provided data pointer (not used in caller-provided callback)
template<> struct fex_gen_param<wl_proxy_add_listener, 2, void*> : fexgen::assume_compatible_data_layout {};
template<> struct fex_gen_config<wl_proxy_create> {};
template<> struct fex_gen_config<wl_proxy_create> : fexgen::custom_host_impl {};
template<> struct fex_gen_param<wl_proxy_create, 1, const wl_interface*> : fexgen::ptr_passthrough {};
template<> struct fex_gen_config<wl_proxy_create_wrapper> {};
template<> struct fex_gen_config<wl_proxy_get_listener> {};
template<> struct fex_gen_config<wl_proxy_get_tag> {};
@ -61,15 +71,19 @@ template<> struct fex_gen_config<wl_proxy_set_tag> {};
template<> struct fex_gen_config<wl_proxy_set_user_data> {};
template<> struct fex_gen_config<wl_proxy_wrapper_destroy> {};
template<> struct fex_gen_config<wl_proxy_marshal_array> {};
template<> struct fex_gen_config<wl_proxy_marshal_array> : fexgen::custom_host_impl {};
template<> struct fex_gen_param<wl_proxy_marshal_array, 2, wl_argument*> : fexgen::ptr_passthrough {};
// wl_proxy_marshal_array_flags is only available starting from Wayland 1.19.91
#if WAYLAND_VERSION_MAJOR * 10000 + WAYLAND_VERSION_MINOR * 100 + WAYLAND_VERSION_MICRO >= 11991
template<> struct fex_gen_config<wl_proxy_marshal_array_flags> {};
template<> struct fex_gen_config<wl_proxy_marshal_array_flags> : fexgen::custom_host_impl {};
template<> struct fex_gen_param<wl_proxy_marshal_array_flags, 2, const wl_interface*> : fexgen::ptr_passthrough {};
template<> struct fex_gen_param<wl_proxy_marshal_array_flags, 5, wl_argument*> : fexgen::ptr_passthrough {};
#endif
// Guest notifies host about its interface. Host returns its corresponding interface pointer
wl_interface* fex_wl_exchange_interface_pointer(wl_interface*, const char* name);
void fex_wl_exchange_interface_pointer(wl_interface*, const char* name);
template<> struct fex_gen_config<fex_wl_exchange_interface_pointer> : fexgen::custom_host_impl {};
template<> struct fex_gen_param<fex_wl_exchange_interface_pointer, 0, wl_interface*> : fexgen::ptr_passthrough {};
// This is equivalent to reading proxy->interface->methods[opcode].signature on 64-bit.
// On 32-bit, the data layout differs between host and guest however, so we let the host extract the data.