If you are hacking on Linux chances are you are using LD_PRELOAD method described here. It's a very powerful, and easy, way to hook into another process. Unfortunately, when it comes to hooking into Wine *and* glXSwapBuffers (aka. endscene hook) it requires patching its source code. Not anymore! (If you don't care about my brief-yet-mostly-useless explanation and you're here to just copy and paste the code then scroll down to the bottom.)
To understand why Wine needs patching we need to take a peek into its source code:
Code:
#define SONAME_LIBGL "libGL.so"
(...snip...)
opengl_handle = wine_dlopen(SONAME_LIBGL, RTLD_NOW|RTLD_GLOBAL, buffer, sizeof(buffer));
(...snip...)
pglXGetProcAddressARB = wine_dlsym(opengl_handle, "glXGetProcAddressARB", NULL, 0);
(...snip...)
pglXSwapBuffers = pglXGetProcAddressARB("glXSwapBuffers");
Basically, Wine directly opens libGL.so and directly requests glXGetProcAddress from it and then uses that to load glXSwapBuffers; to force Wine into using our version of glXSwapBuffers defined in our LD_PRELOAD library we had to patch this "opengl_handle" to RTLD_DEFAULT, which basically means "I don't care from where it comes from, just give me the damn pointer".
Let's take a closer look at wine_dlsym():
Code:
void *wine_dlsym( void *handle, const char *symbol, char *error, size_t errorsize )
{
(...snip...)
ret = dlsym( handle, symbol );
(...error handling...)
(...snip...)
}
We can see that this is just a simple wrapper over dlsym with some error handling throw in the mix. Now, an idea - we could get away without patching Wine if only we could trick dlsym() to return our version of glXGetProcAddress. How? By hooking into dlsym() with LD_PRELOAD!
Code:
static void * (*dlsym_real)( void * handle, const char * symbol ) = nullptr;
void * dlsym( void * handle, const char * symbol )
{
if( dlsym_real == nullptr )
dlsym_real = ( decltype(dlsym_real) )dlsym( RTLD_NEXT, "dlsym" );
if( !strcmp( symbol, "glXGetProcAddressARB" ) )
return ( void * )glXGetProcAddressARB_fake;
return dlsym_real( handle, symbol );
}
There is just one little problem - this obviously won't work. Our dlsym() will just call dlsym(), and that will call dlsym() again and again. What we need is a function that does what dlsym() does but isn't dlsym(). A quick look at "man dlsym" turned out this:
Code:
Glibc extensions: dladdr() and dlvsym()
Glibc adds two functions not described by POSIX, with prototypes
(...snip...)
void *dlvsym(void *handle, char *symbol, char *version);
(...snip...)
The function dlvsym(), provided by glibc since version 2.1, does the same as dlsym() but takes a version string as an additional argument.
Precisely what we need. Now we just need to figure out what the hell we're supposed to put in the version string; fortunately, objdump comes to the rescue:
Code:
[email protected]:~$ objdump -T /lib32/libdl.so.2
/lib32/libdl.so.2: file format elf32-i386
DYNAMIC SYMBOL TABLE:
(...snip...)
00001940 g DF .text 00000067 (GLIBC_2.0) dlopen
00001430 g DF .text 000000a8 GLIBC_2.3.3 dladdr1
00000000 g DO *ABS* 00000000 GLIBC_PRIVATE GLIBC_PRIVATE
00000d50 g DF .text 00000094 GLIBC_2.0 dlsym
00000000 g DO *ABS* 00000000 GLIBC_2.0 GLIBC_2.0
00000000 g DO *ABS* 00000000 GLIBC_2.1 GLIBC_2.1
00000cd0 g DF .text 0000003f GLIBC_2.0 dlclose
(...snip...)
We have out version string - "GLIBC_2.0", so we can rewrite that code as:
Code:
/* This definition is required to get dlvsym(). */
#define _GNU_SOURCE
#include <dlfcn.h>
static void * (*dlsym_real)( void * handle, const char * symbol ) = nullptr;
void * dlsym( void * handle, const char * symbol )
{
if( dlsym_real == nullptr )
dlsym_real = ( decltype(dlsym_real) )dlvsym( RTLD_DEFAULT, "dlsym", "GLIBC_2.0" );
if( !strcmp( symbol, "glXGetProcAddressARB" ) )
return ( void * )glXGetProcAddressARB_fake;
return dlsym_real( handle, symbol );
}
And this is how you LD_PRELOAD without patching Wine.