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.
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"); \
}