diff --git a/ThunkLibs/Generator/analysis.cpp b/ThunkLibs/Generator/analysis.cpp index 802ef573a..fa730a765 100644 --- a/ThunkLibs/Generator/analysis.cpp +++ b/ThunkLibs/Generator/analysis.cpp @@ -366,6 +366,13 @@ void AnalysisAction::ParseInterface(clang::ASTContext& context) { continue; } + // Skip pointers-to-structs passed through to the host in guest_layout. + // This avoids pulling in member types that can't be processed. + if (data.param_annotations[param_idx].is_passthrough && + param_type->isPointerType() && param_type->getPointeeType()->isStructureType()) { + continue; + } + auto check_struct_type = [&](const clang::Type* type) { if (type->isIncompleteType()) { throw report_error(type->getAsTagDecl()->getBeginLoc(), "Unannotated pointer with incomplete struct type; consider using an opaque_type annotation") diff --git a/ThunkLibs/Generator/gen.cpp b/ThunkLibs/Generator/gen.cpp index 3b65963fa..d15cfc03f 100644 --- a/ThunkLibs/Generator/gen.cpp +++ b/ThunkLibs/Generator/gen.cpp @@ -588,11 +588,11 @@ void GenerateThunkLibsAction::OnAnalysisComplete(clang::ASTContext& context) { if (!param_type->isPointerType() || !param_type->getPointeeType()->isStructureType()) { continue; } - auto type = param_type->getPointeeType(); - if (!types.at(context.getCanonicalType(type.getTypePtr())).assumed_compatible && type_compat.at(context.getCanonicalType(type.getTypePtr())) == TypeCompatibility::None) { - // TODO: Factor in "assume_compatible_layout" annotations here - // That annotation should cause the type to be treated as TypeCompatibility::Full - if (!thunk.param_annotations[param_idx].is_passthrough) { + if (!thunk.param_annotations[param_idx].is_passthrough) { + auto type = param_type->getPointeeType(); + if (!types.at(context.getCanonicalType(type.getTypePtr())).assumed_compatible && type_compat.at(context.getCanonicalType(type.getTypePtr())) == TypeCompatibility::None) { + // TODO: Factor in "assume_compatible_layout" annotations here + // That annotation should cause the type to be treated as TypeCompatibility::Full throw report_error(thunk.decl->getLocation(), "Unsupported parameter type %0").AddTaggedVal(param_type); } } diff --git a/ThunkLibs/GuestLibs/CMakeLists.txt b/ThunkLibs/GuestLibs/CMakeLists.txt index 62cb78c43..f68245431 100644 --- a/ThunkLibs/GuestLibs/CMakeLists.txt +++ b/ThunkLibs/GuestLibs/CMakeLists.txt @@ -232,9 +232,6 @@ if (BITNESS EQUAL 64) target_compile_definitions(libxcb-guest-deps INTERFACE -DXCB_VERSION_MINOR=${XCB_VERSION_MINOR}) target_compile_definitions(libxcb-guest-deps INTERFACE -DXCB_VERSION_PATCH=${XCB_VERSION_PATCH}) - generate(libwayland-client ${CMAKE_CURRENT_SOURCE_DIR}/../libwayland-client/libwayland-client_interface.cpp) - add_guest_lib(wayland-client "libwayland-client.so.0.20.0") - generate(libxcb-dri2 ${CMAKE_CURRENT_SOURCE_DIR}/../libxcb-dri2/libxcb-dri2_interface.cpp) add_guest_lib(xcb-dri2 "libxcb-dri2.so.0") @@ -268,6 +265,9 @@ if (BITNESS EQUAL 64) add_guest_lib(drm "libdrm.so.2") endif() +generate(libwayland-client ${CMAKE_CURRENT_SOURCE_DIR}/../libwayland-client/libwayland-client_interface.cpp) +add_guest_lib(wayland-client "libwayland-client.so.0.20.0") + generate(libVDSO ${CMAKE_CURRENT_SOURCE_DIR}/../libVDSO/libVDSO_interface.cpp) add_guest_lib(VDSO "linux-vdso.so.1") # Can't use a stack protector because otherwise cross-compiling fails diff --git a/ThunkLibs/HostLibs/CMakeLists.txt b/ThunkLibs/HostLibs/CMakeLists.txt index f02572750..edc3e1658 100644 --- a/ThunkLibs/HostLibs/CMakeLists.txt +++ b/ThunkLibs/HostLibs/CMakeLists.txt @@ -163,9 +163,6 @@ foreach(GUEST_BITNESS IN LISTS BITNESS_LIST) target_compile_definitions(libxcb-${GUEST_BITNESS}-deps INTERFACE -DXCB_VERSION_MINOR=${XCB_VERSION_MINOR}) target_compile_definitions(libxcb-${GUEST_BITNESS}-deps INTERFACE -DXCB_VERSION_PATCH=${XCB_VERSION_PATCH}) - generate(libwayland-client ${CMAKE_CURRENT_SOURCE_DIR}/../libwayland-client/libwayland-client_interface.cpp ${GUEST_BITNESS}) - add_host_lib(wayland-client ${GUEST_BITNESS}) - generate(libxcb-dri2 ${CMAKE_CURRENT_SOURCE_DIR}/../libxcb-dri2/libxcb-dri2_interface.cpp ${GUEST_BITNESS}) add_host_lib(xcb-dri2 ${GUEST_BITNESS}) @@ -203,6 +200,9 @@ set (BITNESS_LIST "32;64") foreach(GUEST_BITNESS IN LISTS BITNESS_LIST) generate(libfex_thunk_test ${CMAKE_CURRENT_SOURCE_DIR}/../libfex_thunk_test/libfex_thunk_test_interface.cpp ${GUEST_BITNESS}) add_host_lib(fex_thunk_test ${GUEST_BITNESS}) + + generate(libwayland-client ${CMAKE_CURRENT_SOURCE_DIR}/../libwayland-client/libwayland-client_interface.cpp ${GUEST_BITNESS}) + add_host_lib(wayland-client ${GUEST_BITNESS}) endforeach() add_library(fex_thunk_test SHARED ../libfex_thunk_test/lib.cpp) diff --git a/ThunkLibs/include/common/Host.h b/ThunkLibs/include/common/Host.h index e3ae2c661..814c1875b 100644 --- a/ThunkLibs/include/common/Host.h +++ b/ThunkLibs/include/common/Host.h @@ -167,6 +167,14 @@ struct guest_layout { const guest_layout* get_pointer() const { return reinterpret_cast*>(uintptr_t { data }); } + + T* force_get_host_pointer() { + return reinterpret_cast(uintptr_t { data }); + } + + const T* force_get_host_pointer() const { + return reinterpret_cast(uintptr_t { data }); + } }; template diff --git a/ThunkLibs/libwayland-client/Guest.cpp b/ThunkLibs/libwayland-client/Guest.cpp index 5f8fb7e99..b10814688 100644 --- a/ThunkLibs/libwayland-client/Guest.cpp +++ b/ThunkLibs/libwayland-client/Guest.cpp @@ -33,17 +33,11 @@ extern "C" const wl_interface wl_callback_interface {}; #include #include #include -#include #include "common/Guest.h" #include "thunkgen_guest_libwayland-client.inl" -struct wl_proxy_private { - wl_interface* interface; - // Other data members omitted -}; - // See wayland-util.h for documentation on protocol message signatures template struct ArgType; template<> struct ArgType<'s'> { using type = const char*; }; @@ -56,37 +50,31 @@ template<> struct ArgType<'f'> { using type = wl_fixed_t; }; template<> struct ArgType<'h'> { using type = int32_t; }; // fd? template -static void* WaylandAllocateHostTrampolineForGuestListener(void (*callback)()) { +static uint64_t WaylandAllocateHostTrampolineForGuestListener(void (*callback)()) { using cb = void(void*, wl_proxy*, typename ArgType::type...); - return (void*)AllocateHostTrampolineForGuestFunction((cb*)callback); + return (uint64_t)(uintptr_t)(void*)AllocateHostTrampolineForGuestFunction((cb*)callback); } #define WL_CLOSURE_MAX_ARGS 20 -// Per-proxy list of callbacks set up via wl_proxy_add_listener. -// These tables store the host-callable trampolines to the actual listener -// callbacks provided by the guest application. -// NOTE: There can only be one listener per proxy. Wayland will return an error -// if wl_proxy_add_listener is called twice. -// NOTE: Entries should be removed in wl_destroy_proxy. Since proxy wrappers do -// not use their own listeners, wl_proxy_wrapper_destroy does not need to -// be customized. -static std::unordered_map> proxy_listeners; - extern "C" int wl_proxy_add_listener(wl_proxy *proxy, void (**callback)(void), void *data) { - auto interface = ((wl_proxy_private*)proxy)->interface; + // Replace guest-provided callback table with host-callable function pointers + // NOTE: A reference to this table is stored in the wl_proxy, so the data + // must remain valid until the proxy is destroyed (or another listener + // is added) + delete[] (uint64_t*)wl_proxy_get_listener(proxy); // Delete previous substitute, if any + auto host_callbacks = new uint64_t[WL_CLOSURE_MAX_ARGS]; - // NOTE: This table must remain valid past the return of this function. - auto& host_callbacks = proxy_listeners[proxy]; - - for (int i = 0; i < ((wl_proxy_private*)proxy)->interface->event_count; ++i) { - auto signature_view = std::string_view { interface->events[i].signature }; + for (int i = 0; i < fex_wl_get_interface_event_count(proxy); ++i) { + char event_signature[16]; + fex_wl_get_interface_event_signature(proxy, i, event_signature); + auto signature2 = std::string_view { event_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 [ptr, res] = std::from_chars(signature2.begin(), signature2.end(), since_version, 10); + auto signature = std::string { signature2.substr(ptr - signature2.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()); @@ -185,17 +173,18 @@ extern "C" int wl_proxy_add_listener(wl_proxy *proxy, // E.g. zwp_text_input_v3::preedit_string host_callbacks[i] = WaylandAllocateHostTrampolineForGuestListener<'s', 'i', 'i'>(callback[i]); } else { - fprintf(stderr, "Unknown wayland signature descriptor \"%s\" for event \"%s\" in interface \"%s\"\n", signature.data(), interface->events[i].name, interface->name); + fprintf(stderr, "TODO: Unknown wayland event signature descriptor %s\n", signature.data()); std::abort(); } } - return fexfn_pack_wl_proxy_add_listener(proxy, (void(**)())host_callbacks.data(), data); + return fexfn_pack_wl_proxy_add_listener(proxy, (void(**)())host_callbacks, data); } -extern "C" void wl_proxy_destroy(struct wl_proxy *proxy) { - proxy_listeners.erase(proxy); - return fexfn_pack_wl_proxy_destroy(proxy); +extern "C" void wl_proxy_destroy(wl_proxy *proxy) { + // Delete substitute callback table (if any), then the proxy itself + delete[] (uint64_t*)wl_proxy_get_listener(proxy); + fexfn_pack_wl_proxy_destroy(proxy); } // Adapted from the Wayland sources @@ -266,18 +255,48 @@ extern "C" void wl_proxy_marshal(wl_proxy *proxy, uint32_t opcode, ...) { va_list ap; va_start(ap, opcode); -#ifdef IS_32BIT_THUNK -// Must extract signature from host due to different data layout on 32-bit -#error Not implemented -#else - wl_argument_from_va_list(((wl_proxy_private*)proxy)->interface->methods[opcode].signature, - args, WL_CLOSURE_MAX_ARGS, ap); -#endif + // 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. + char signature[64]; + fex_wl_get_method_signature(proxy, opcode, signature); + wl_argument_from_va_list(signature, args, WL_CLOSURE_MAX_ARGS, ap); va_end(ap); wl_proxy_marshal_array(proxy, opcode, args); } +extern "C" wl_proxy *wl_proxy_marshal_constructor(wl_proxy *proxy, uint32_t opcode, + const wl_interface *interface, ...) { + wl_argument args[WL_CLOSURE_MAX_ARGS]; + va_list ap; + + va_start(ap, interface); + // This is equivalent to reading ((wl_proxy_private*)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. + char signature[64]; + fex_wl_get_method_signature(proxy, opcode, signature); + wl_argument_from_va_list(signature, args, WL_CLOSURE_MAX_ARGS, ap); + va_end(ap); + + return wl_proxy_marshal_array_constructor(proxy, opcode, args, interface); +} + +extern "C" wl_proxy *wl_proxy_marshal_constructor_versioned(wl_proxy *proxy, uint32_t opcode, + const wl_interface *interface, uint32_t version, ...) { + wl_argument args[WL_CLOSURE_MAX_ARGS]; + va_list ap; + + va_start(ap, version); + // This is equivalent to reading ((wl_proxy_private*)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. + char signature[64]; + fex_wl_get_method_signature(proxy, opcode, signature); + wl_argument_from_va_list(signature, args, WL_CLOSURE_MAX_ARGS, ap); + va_end(ap); + + return wl_proxy_marshal_array_constructor_versioned(proxy, opcode, args, interface, version); +} + extern "C" wl_proxy *wl_proxy_marshal_flags(wl_proxy *proxy, uint32_t opcode, const wl_interface *interface, uint32_t version, @@ -286,13 +305,11 @@ extern "C" wl_proxy *wl_proxy_marshal_flags(wl_proxy *proxy, uint32_t opcode, va_list ap; va_start(ap, flags); -#ifdef IS_32BIT_THUNK -// Must extract signature from host due to different data layout on 32-bit -#error Not implemented -#else - wl_argument_from_va_list(((wl_proxy_private*)proxy)->interface->methods[opcode].signature, - args, WL_CLOSURE_MAX_ARGS, ap); -#endif + // 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. + char signature[64]; + fex_wl_get_method_signature(proxy, opcode, signature); + wl_argument_from_va_list(signature, args, WL_CLOSURE_MAX_ARGS, ap); va_end(ap); // wl_proxy_marshal_array_flags is only available starting from Wayland 1.19.91 @@ -304,18 +321,23 @@ extern "C" wl_proxy *wl_proxy_marshal_flags(wl_proxy *proxy, uint32_t opcode, #endif } +extern "C" void wl_log_set_handler_client(wl_log_func_t handler) { + // Ignore +} + + void OnInit() { - fex_wl_exchange_interface_pointer(const_cast(&wl_output_interface), "wl_output"); - fex_wl_exchange_interface_pointer(const_cast(&wl_shm_pool_interface), "wl_shm_pool"); - fex_wl_exchange_interface_pointer(const_cast(&wl_pointer_interface), "wl_pointer"); - fex_wl_exchange_interface_pointer(const_cast(&wl_compositor_interface), "wl_compositor"); - fex_wl_exchange_interface_pointer(const_cast(&wl_shm_interface), "wl_shm"); - fex_wl_exchange_interface_pointer(const_cast(&wl_registry_interface), "wl_registry"); - fex_wl_exchange_interface_pointer(const_cast(&wl_buffer_interface), "wl_buffer"); - fex_wl_exchange_interface_pointer(const_cast(&wl_seat_interface), "wl_seat"); - fex_wl_exchange_interface_pointer(const_cast(&wl_surface_interface), "wl_surface"); - fex_wl_exchange_interface_pointer(const_cast(&wl_keyboard_interface), "wl_keyboard"); - fex_wl_exchange_interface_pointer(const_cast(&wl_callback_interface), "wl_callback"); + fex_wl_exchange_interface_pointer(const_cast(&wl_output_interface), "wl_output_interface"); + fex_wl_exchange_interface_pointer(const_cast(&wl_shm_pool_interface), "wl_shm_pool_interface"); + fex_wl_exchange_interface_pointer(const_cast(&wl_pointer_interface), "wl_pointer_interface"); + fex_wl_exchange_interface_pointer(const_cast(&wl_compositor_interface), "wl_compositor_interface"); + fex_wl_exchange_interface_pointer(const_cast(&wl_shm_interface), "wl_shm_interface"); + fex_wl_exchange_interface_pointer(const_cast(&wl_registry_interface), "wl_registry_interface"); + fex_wl_exchange_interface_pointer(const_cast(&wl_buffer_interface), "wl_buffer_interface"); + fex_wl_exchange_interface_pointer(const_cast(&wl_seat_interface), "wl_seat_interface"); + fex_wl_exchange_interface_pointer(const_cast(&wl_surface_interface), "wl_surface_interface"); + fex_wl_exchange_interface_pointer(const_cast(&wl_keyboard_interface), "wl_keyboard_interface"); + fex_wl_exchange_interface_pointer(const_cast(&wl_callback_interface), "wl_callback_interface"); } LOAD_LIB_INIT(libwayland-client, OnInit) diff --git a/ThunkLibs/libwayland-client/Host.cpp b/ThunkLibs/libwayland-client/Host.cpp index 0aaffec7c..1fd5cfb39 100644 --- a/ThunkLibs/libwayland-client/Host.cpp +++ b/ThunkLibs/libwayland-client/Host.cpp @@ -5,6 +5,7 @@ $end_info$ */ #include +#include #include #include @@ -19,14 +20,272 @@ $end_info$ #include #include #include +#include #include +#include + +template<> +struct guest_layout { +#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" -struct wl_proxy_private { - wl_interface* interface; - // Other data members omitted -}; +// Maps guest interface to host_interfaces +static std::unordered_map*, 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(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 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 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 { guest_interface }.data; + fex_apply_custom_repacking_entry(reinterpret_cast&>(*host_interface), guest_interface); +} + +// Maps guest interface pointers to host pointers +static const wl_interface* lookup_wl_interface(guest_layout 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& into, guest_layout 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 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 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&, host_layout const&) { + fprintf(stderr, "Should not be called: %s\n", __PRETTY_FUNCTION__); + std::abort(); +} +void fex_custom_repack_entry(host_layout& into, guest_layout 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&, host_layout const&) { + fprintf(stderr, "Should not be called: %s\n", __PRETTY_FUNCTION__); + std::abort(); +} +#else +const wl_interface* lookup_wl_interface(guest_layout interface) { + return interface.force_get_host_pointer(); +} +#endif + +static wl_proxy* fexfn_impl_libwayland_client_wl_proxy_create(wl_proxy* proxy, guest_layout 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 args, const wl_message& message) { +#ifndef IS_32BIT_THUNK + // Cast to host layout and return as std::span + wl_argument* host_args = host_layout { args }.data; + return std::span { host_args, WL_CLOSURE_MAX_ARGS }; +#else + // Return a new array of elements zero-extended to 64-bit + std::array 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 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 args, + guest_layout guest_interface, + bool constructor, // Call the _constructor variant of the native wayland function + std::optional version, + std::optional 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 (!constructor && !version && !flags) { + return nullptr; + } else if (!constructor && 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 if (constructor && version && !flags) { + return fexldr_ptr_libwayland_client_wl_proxy_marshal_array_constructor_versioned(proxy, opcode, host_args.data(), interface, version.value()); + } else if (constructor && version && !flags) { + return fexldr_ptr_libwayland_client_wl_proxy_marshal_array_constructor(proxy, opcode, host_args.data(), interface); + } else { + fprintf(stderr, "Invalid configuration\n"); + __builtin_trap(); + } +} + +extern "C" wl_proxy* +fexfn_impl_libwayland_client_wl_proxy_marshal_array_constructor_versioned( + wl_proxy *proxy, uint32_t opcode, + guest_layout args, + guest_layout interface, + uint32_t version) { + return fex_wl_proxy_marshal_array(proxy, opcode, args, interface, true, version, std::nullopt); +} + +extern "C" wl_proxy* +fexfn_impl_libwayland_client_wl_proxy_marshal_array_constructor( + wl_proxy *proxy, uint32_t opcode, + guest_layout args, + guest_layout interface) { + return fex_wl_proxy_marshal_array(proxy, opcode, args, interface, true, std::nullopt, std::nullopt); +} + +extern "C" wl_proxy* +fexfn_impl_libwayland_client_wl_proxy_marshal_array_flags( + wl_proxy *proxy, uint32_t opcode, + guest_layout interface, + uint32_t version, uint32_t flags, + guest_layout args) { + return fex_wl_proxy_marshal_array(proxy, opcode, args, interface, false, version, flags); +} + +// Variant of CallbackUnpack::CallGuestPtr that relocates a wl_array parameter +// for 32-bit guests. Relocating this parameter is required since it may +// reference inaccessible memory regions (presumably due to pointing to data +// on the host stack). +#ifndef IS_32BIT_THUNK +template +const auto CallGuestPtrWithWaylandArray = CallbackUnpack::CallGuestPtr; +#else +template +static auto CallGuestPtrWithWaylandArray(Args... args, wl_array *array) -> Result { + GuestcallInfo *guestcall; + LOAD_INTERNAL_GUESTPTR_VIA_CUSTOM_ABI(guestcall); + + using PackedArgumentsType = PackedArguments..., guest_layout>; + + GuestStackBumpAllocator GuestStack; + + auto* guest_array = GuestStack.New>(to_guest(to_host_layout(*array))); + guest_layout guest_array_ptr = { .data = static_cast(reinterpret_cast(guest_array)) }; + + auto& packed_args = *GuestStack.New( + to_guest(to_host_layout(args))..., guest_array_ptr + ); + + guestcall->CallCallback(guestcall->GuestUnpacker, guestcall->GuestTarget, &packed_args); + + if constexpr (!std::is_void_v) { + return packed_args.rv; + } +} +#endif // See wayland-util.h for documentation on protocol message signatures template struct ArgType; @@ -46,28 +305,34 @@ static void WaylandFinalizeHostTrampolineForGuestListener(void (*callback)()) { } extern "C" int fexfn_impl_libwayland_client_wl_proxy_add_listener(struct wl_proxy *proxy, - guest_layout callback_raw, void* data) { - auto guest_interface = ((wl_proxy_private*)proxy)->interface; + guest_layout callback_table_raw, void* data) { + auto interface = get_proxy_interface(proxy); - for (int i = 0; i < guest_interface->event_count; ++i) { - auto signature_view = std::string_view { guest_interface->events[i].signature }; + 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(uintptr_t { callback_raw.get_pointer()[i].data }); + auto callback = callback_table[i]; if (signature == "") { // E.g. xdg_toplevel::close WaylandFinalizeHostTrampolineForGuestListener<>(callback); } else if (signature == "a") { // E.g. xdg_toplevel::wm_capabilities - WaylandFinalizeHostTrampolineForGuestListener<'a'>(callback); + FEXCore::FinalizeHostTrampolineForGuestFunction( + (FEXCore::HostToGuestTrampolinePtr*)callback, + (void*)CallGuestPtrWithWaylandArray); } else if (signature == "hu") { // E.g. zwp_linux_dmabuf_feedback_v1::format_table WaylandFinalizeHostTrampolineForGuestListener<'h', 'u'>(callback); @@ -85,7 +350,9 @@ extern "C" int fexfn_impl_libwayland_client_wl_proxy_add_listener(struct wl_prox WaylandFinalizeHostTrampolineForGuestListener<'i', 'i'>(callback); } else if (signature == "iia") { // E.g. xdg_toplevel::configure - WaylandFinalizeHostTrampolineForGuestListener<'i', 'i', 'a'>(callback); + FEXCore::FinalizeHostTrampolineForGuestFunction( + (FEXCore::HostToGuestTrampolinePtr*)callback, + (void*)CallGuestPtrWithWaylandArray); } else if (signature == "iiiiissi") { // E.g. wl_output_listener::geometry WaylandFinalizeHostTrampolineForGuestListener<'i', 'i', 'i', 'i', 'i', 's', 's', 'i'>(callback); @@ -118,7 +385,9 @@ extern "C" int fexfn_impl_libwayland_client_wl_proxy_add_listener(struct wl_prox WaylandFinalizeHostTrampolineForGuestListener<'u', 'o'>(callback); } else if (signature == "uoa") { // E.g. wl_keyboard_listener::enter - WaylandFinalizeHostTrampolineForGuestListener<'u', 'o', 'a'>(callback); + FEXCore::FinalizeHostTrampolineForGuestFunction( + (FEXCore::HostToGuestTrampolinePtr*)callback, + (void*)CallGuestPtrWithWaylandArray); } else if (signature == "uoff") { // E.g. wl_pointer_listener::enter WaylandFinalizeHostTrampolineForGuestListener<'u', 'o', 'f', 'f'>(callback); @@ -162,15 +431,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(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(dlsym(fexldr_ptr_libwayland_client_so, (std::string { name } + "_interface").c_str())); +void fexfn_impl_libwayland_client_fex_wl_exchange_interface_pointer(guest_layout guest_interface_raw, const char* name) { + auto& guest_interface = *guest_interface_raw.get_pointer(); + auto& host_interface = guest_to_host_interface[reinterpret_cast*>(&guest_interface)]; + host_interface = reinterpret_cast(dlsym(fexldr_ptr_libwayland_client_so, name)); 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 +447,50 @@ 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(guest_interface) & ~uintptr_t { 0xfff }; + auto page_begin = reinterpret_cast(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[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[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) { + strcpy(out, get_proxy_interface(proxy)->methods[opcode].signature); +} + +int fexfn_impl_libwayland_client_fex_wl_get_interface_event_count(wl_proxy* proxy) { + return get_proxy_interface(proxy)->event_count; +} + +void fexfn_impl_libwayland_client_fex_wl_get_interface_event_name(wl_proxy* proxy, int i, char* out) { + strcpy(out, get_proxy_interface(proxy)->events[i].name); +} + +void fexfn_impl_libwayland_client_fex_wl_get_interface_event_signature(wl_proxy* proxy, int i, char* out) { + strcpy(out, get_proxy_interface(proxy)->events[i].signature); } EXPORTS(libwayland_client) diff --git a/ThunkLibs/libwayland-client/libwayland-client_interface.cpp b/ThunkLibs/libwayland-client/libwayland-client_interface.cpp index 077e52c93..a38550606 100644 --- a/ThunkLibs/libwayland-client/libwayland-client_interface.cpp +++ b/ThunkLibs/libwayland-client/libwayland-client_interface.cpp @@ -16,30 +16,41 @@ struct fex_gen_param {}; template<> struct fex_gen_type : fexgen::opaque_type {}; template<> struct fex_gen_type : fexgen::opaque_type {}; -template<> struct fex_gen_type : fexgen::opaque_type {}; template<> struct fex_gen_type : fexgen::opaque_type {}; // Passed over Wayland's wire protocol for some functions -template<> struct fex_gen_type {}; +template<> struct fex_gen_type : fexgen::emit_layout_wrappers {}; +#ifdef IS_32BIT_THUNK +// wl_interface and wl_message reference each other through pointers +template<> struct fex_gen_type : 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 : fexgen::emit_layout_wrappers {}; +template<> struct fex_gen_config<&wl_message::types> : fexgen::custom_repack {}; +#else +template<> struct fex_gen_type : fexgen::assume_compatible_data_layout {}; +#endif template<> struct fex_gen_config : fexgen::custom_guest_entrypoint {}; -template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; +template<> struct fex_gen_config {}; +template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; -template<> struct fex_gen_config {}; +template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; +template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; @@ -49,8 +60,12 @@ template<> struct fex_gen_config : fexgen::custom_host_im template<> struct fex_gen_param : fexgen::ptr_passthrough {}; // User-provided data pointer (not used in caller-provided callback) template<> struct fex_gen_param : fexgen::assume_compatible_data_layout {}; -template<> struct fex_gen_config {}; +template<> struct fex_gen_config : fexgen::custom_host_impl {}; +template<> struct fex_gen_param : fexgen::ptr_passthrough {}; template<> struct fex_gen_config {}; +template<> struct fex_gen_config {}; +template<> struct fex_gen_config {}; +template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; @@ -60,12 +75,33 @@ template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; template<> struct fex_gen_config {}; -template<> struct fex_gen_config {}; +template<> struct fex_gen_config : fexgen::custom_host_impl {}; +template<> struct fex_gen_param : fexgen::ptr_passthrough {}; +template<> struct fex_gen_config : fexgen::custom_host_impl {}; +template<> struct fex_gen_param : fexgen::ptr_passthrough {}; +template<> struct fex_gen_param : fexgen::ptr_passthrough {}; +template<> struct fex_gen_config : fexgen::custom_host_impl {}; +template<> struct fex_gen_param : fexgen::ptr_passthrough {}; +template<> struct fex_gen_param : 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 {}; +template<> struct fex_gen_config : fexgen::custom_host_impl {}; +template<> struct fex_gen_param : fexgen::ptr_passthrough {}; +template<> struct fex_gen_param : 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 : fexgen::custom_host_impl {}; +template<> struct fex_gen_param : 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. +void fex_wl_get_method_signature(wl_proxy*, uint32_t opcode, char*); +template<> struct fex_gen_config : fexgen::custom_host_impl {}; +int fex_wl_get_interface_event_count(wl_proxy*); +template<> struct fex_gen_config : fexgen::custom_host_impl {}; +void fex_wl_get_interface_event_name(wl_proxy*, int, char*); +template<> struct fex_gen_config : fexgen::custom_host_impl {}; +void fex_wl_get_interface_event_signature(wl_proxy*, int, char*); +template<> struct fex_gen_config : fexgen::custom_host_impl {}; diff --git a/unittests/ThunkLibs/generator.cpp b/unittests/ThunkLibs/generator.cpp index b2f641108..93c066459 100644 --- a/unittests/ThunkLibs/generator.cpp +++ b/unittests/ThunkLibs/generator.cpp @@ -716,6 +716,9 @@ TEST_CASE_METHOD(Fixture, "Mapping guest integers to fixed-size") { // applied to pointees as well const std::string ptr = GENERATE("", " *"); + // Run each test with and without the ptr_passthrough annotation + const bool passthrough_guest_type = GENERATE(false, true); + // These types are differently sized on 32-bit guests SECTION("(u)intptr_t / size_t / long") { const std::string type = GENERATE("long", "unsigned long", "uintptr_t", "intptr_t", "size_t"); @@ -725,8 +728,9 @@ TEST_CASE_METHOD(Fixture, "Mapping guest integers to fixed-size") { "#include \n" "#include \n" "void func(" + type + ptr + ");\n" - "template<> struct fex_gen_config {};\n"; - if (!ptr.empty() && guest_abi == GuestABI::X86_32) { + "template<> struct fex_gen_config : fexgen::custom_host_impl {};\n" + + (passthrough_guest_type ? "template<> struct fex_gen_param : fexgen::ptr_passthrough {};\n" : ""); + if (!ptr.empty() && guest_abi == GuestABI::X86_32 && !passthrough_guest_type) { // Guest points to a 32-bit integer, but the host to a 64-bit one. // This should be detected as a failure. CHECK_THROWS_WITH(run_thunkgen_host("", code, guest_abi, true), Catch::Contains("initialization of 'host_layout", Catch::CaseSensitive::No)); @@ -748,6 +752,17 @@ TEST_CASE_METHOD(Fixture, "Mapping guest integers to fixed-size") { has(fieldDecl(hasType(asString(expected_type)))) )))))))) ))); + + // For passthrough parameters, the target function signature should + // match the guest_layout type + if (passthrough_guest_type) { + CHECK_THAT(output, + matches(functionDecl( + hasName("fexfn_impl_libtest_func"), + parameterCountIs(1), + hasParameter(0, hasType(asString(expected_type))) + ))); + } } } @@ -777,7 +792,8 @@ TEST_CASE_METHOD(Fixture, "Mapping guest integers to fixed-size") { "#include \n" "#include \n" "void func(" + type + ptr + ");\n" - "template<> struct fex_gen_config {};\n"; + "template<> struct fex_gen_config : fexgen::custom_host_impl {};\n" + + (passthrough_guest_type ? "template<> struct fex_gen_param : fexgen::ptr_passthrough {};\n" : ""); const auto output = run_thunkgen_host("", code, guest_abi); CHECK_THAT(output, matches(functionDecl( @@ -789,6 +805,17 @@ TEST_CASE_METHOD(Fixture, "Mapping guest integers to fixed-size") { has(fieldDecl(hasType(asString("guest_layout<" + fixed_size_type + ptr + ">")))) )))))))) ))); + + // For passthrough parameters, the target function signature should + // match the guest_layout type + if (passthrough_guest_type) { + CHECK_THAT(output, + matches(functionDecl( + hasName("fexfn_impl_libtest_func"), + parameterCountIs(1), + hasParameter(0, hasType(asString("guest_layout<" + fixed_size_type + ptr + ">"))) + ))); + } } }