Search code examples
c++ld-preload

C++: define a function alias which is not a function pointer


I want to write a small LD_PRELOAD library to intercept and log X11 calls (X11 is not significant here: choose any other library you'd like to log calls to). I would like to design a macro GEN_WRAPPER that automatically creates the wrapper for a certain call. This is my current attempt:

#include <iostream>
#include <X11/Xlib.h>
#include <dlfcn.h>

#define GEN_WRAPPER(x)                                                  \
    template<typename Func>                                             \
    struct wrapper_ ## x;                                               \
    template<typename T, typename... S>                                 \
    struct wrapper_ ## x<T(S...)> {                                     \
        static T exec(S... s) {                                         \
            static decltype(x) *orig = reinterpret_cast<decltype(orig)>(dlsym(RTLD_NEXT, #x)); \
            std::cout << "Calling " #x;                                 \
            const auto ret = orig(std::move(s)...);                     \
            std::cout << " -> " << ret << std::endl;                    \
            return ret;                                                 \
        }                                                               \
    };                                                                  \
    decltype(x) *x ## _ptr = wrapper_ ## x<decltype(x)>::exec;
    /*decltype(x) x = wrapper_ ## x<decltype(x)>::exec;*/

GEN_WRAPPER(XOpenDisplay)

(ignore the problems related to perfect forwarding for this example, it is not the point)

If I call GEN_WRAPPER(XOpenDisplay), the macro generates a variable called XOpenDisplay_ptr which is a pointer to a function with the same signature and return type as XOpenDisplay, but also has some simple logging code (which could be made more fancy, but this is not the point of the question either).

The problem is that XOpenDisplay_ptr is a pointer to a function, not a function. From the point of view of C++ code, this is nearly the same thing, but if I want to use this in a LD_PRELOAD-ed library I also need ABI compatibility, and of course the ABI is different between a function and a pointer to a function. What I would need is to define the function XOpenDisplay to be an alias of the static function wrapper_XOpenDisplay<decltype(XOpenDisplay)>::exec (which the macro already generates). Something like the commented line does, except the commented line is not valid C++.

Conceptually (at least for architecture x86_64), the thing the compiler has to do is very simple: it just has to generate the function XOpenDisplay with a simple jmp to the wrapper function (or copy its body). But I don't know how to express this in C++, if it's possible at all.

The only solution I know of is to explicitly write the function like this:

Display *XOpenDisplay(const char *display_name) {
    return wrapper_XOpenDisplay<decltype(XOpenDisplay)>::exec(display_name);
}

But I cannot generate this inside the macro, because I cannot retrieve the arguments and return type.

Using C++20 is fine, as well as using GCC directives if required.


Solution

  • For lack of a better answer, I came up with the following solution, which is compiler, architecture and ABI dependent, but gets the job done in my case.

    #if defined(__i386__)
    #define JUMP_EAX "jmp *%eax"
    #elif defined(__x86_64__)
    #define JUMP_EAX "jmp *%rax"
    #else
    #error "Architecture unsupported"
    #endif
    
    #define GEN_WRAPPER(x)                                                  \
        namespace gio::wrapper {                                            \
        template<typename Func>                                             \
        struct wrapper_ ## x;                                               \
        template<typename T, typename... S>                                 \
        struct wrapper_ ## x<T(S...)> {                                     \
            static inline T exec(S... s) noexcept {                         \
                std::cout << "Backtrace for " #x << std::endl;              \
                backtrace();                                                \
                const static decltype(x) *orig = reinterpret_cast<decltype(orig)>(dlsym(RTLD_NEXT, #x)); \
                std::cout << #x "(";                                        \
                printer<S...>()(s...);                                      \
                std::cout << ")";                                           \
                std::cout.flush();                                          \
                const auto ret = orig(std::move(s)...);                     \
                std::cout << " = ";                                         \
                print_one<T>()(ret);                                        \
                std::cout << std::endl;                                     \
                return ret;                                                 \
            }                                                               \
        };                                                                  \
        __attribute__((unused)) static decltype(x) *__get_ ## x ## _ptr() asm("__get_" #x "_ptr"); \
        static decltype(x) *__get_ ## x ## _ptr() {                         \
            return &wrapper_ ## x<decltype(x)>::exec;                       \
        }                                                                   \
        __asm__ (".global " #x "\n"                                         \
                 #x ":\n"                                                   \
                 "call __get_" #x "_ptr\n"                                  \
                 JUMP_EAX "\n");                                            \
        }