diff options
author | Jose Fonseca <jfonseca@vmware.com> | 2015-11-17 00:20:17 +0000 |
---|---|---|
committer | Jose Fonseca <jfonseca@vmware.com> | 2015-11-22 22:53:20 +0000 |
commit | f16f95c147ed21de11345f8b3ccf9a3615c9d8c3 (patch) | |
tree | eff473af01ace3c141dce464a8b0c50352eb443b | |
parent | 1366e67590014b395ff28cc875a0a3434db284ea (diff) |
wrappers: Intercept dlsym where possible (WIP).dlsym
-rw-r--r-- | dispatch/dlopen.hpp | 38 | ||||
-rw-r--r-- | dispatch/glproc_egl.cpp | 8 | ||||
-rw-r--r-- | dispatch/glproc_gl.cpp | 4 | ||||
-rwxr-xr-x | retrace/glretrace_main.cpp | 13 | ||||
-rw-r--r-- | wrappers/CMakeLists.txt | 4 | ||||
-rw-r--r-- | wrappers/dlsym.cpp | 265 | ||||
-rw-r--r-- | wrappers/egltrace.py | 63 | ||||
-rw-r--r-- | wrappers/egltrace.version | 1 | ||||
-rw-r--r-- | wrappers/glxtrace.py | 50 | ||||
-rw-r--r-- | wrappers/glxtrace.version | 1 |
10 files changed, 267 insertions, 180 deletions
diff --git a/dispatch/dlopen.hpp b/dispatch/dlopen.hpp deleted file mode 100644 index 94d9f62c..00000000 --- a/dispatch/dlopen.hpp +++ /dev/null @@ -1,38 +0,0 @@ -/************************************************************************** - * - * Copyright 2011 Jose Fonseca - * All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - **************************************************************************/ - -/* - * Provides access to real dlopen, as tracing libraries interpose it. - */ - -#pragma once - -#include <dlfcn.h> - -/* - * Invoke the true dlopen() function. - */ -void * -_dlopen(const char *filename, int flag); diff --git a/dispatch/glproc_egl.cpp b/dispatch/glproc_egl.cpp index 739a72c9..0f0683f2 100644 --- a/dispatch/glproc_egl.cpp +++ b/dispatch/glproc_egl.cpp @@ -29,7 +29,7 @@ #include <string.h> #if !defined(_WIN32) -#include "dlopen.hpp" +#include <dlfcn.h> #endif @@ -90,7 +90,7 @@ _getPublicProcAddress(const char *procName) if (procName[0] == 'e' && procName[1] == 'g' && procName[2] == 'l') { static void *libEGL = NULL; if (!libEGL) { - libEGL = _dlopen("libEGL.so", RTLD_LOCAL | RTLD_LAZY); + libEGL = dlopen("libEGL.so", RTLD_LOCAL | RTLD_LAZY); if (!libEGL) { return NULL; } @@ -133,7 +133,7 @@ _getPublicProcAddress(const char *procName) static void *libGLESv2 = NULL; if (!libGLESv2) { - libGLESv2 = _dlopen("libGLESv2.so", RTLD_LOCAL | RTLD_LAZY); + libGLESv2 = dlopen("libGLESv2.so", RTLD_LOCAL | RTLD_LAZY); } if (libGLESv2) { proc = dlsym(libGLESv2, procName); @@ -144,7 +144,7 @@ _getPublicProcAddress(const char *procName) static void *libGLESv1 = NULL; if (!libGLESv1) { - libGLESv1 = _dlopen("libGLESv1_CM.so", RTLD_LOCAL | RTLD_LAZY); + libGLESv1 = dlopen("libGLESv1_CM.so", RTLD_LOCAL | RTLD_LAZY); } if (libGLESv1) { proc = dlsym(libGLESv1, procName); diff --git a/dispatch/glproc_gl.cpp b/dispatch/glproc_gl.cpp index 8ae40d42..ce3ee67c 100644 --- a/dispatch/glproc_gl.cpp +++ b/dispatch/glproc_gl.cpp @@ -30,7 +30,7 @@ #if !defined(_WIN32) #include <unistd.h> // for symlink -#include "dlopen.hpp" +#include <dlfcn.h> #endif @@ -197,7 +197,7 @@ void * _libgl_sym(const char *symbol) * exposes symbols to it. */ - _libGlHandle = _dlopen(libgl_filename, RTLD_GLOBAL | RTLD_LAZY); + _libGlHandle = dlopen(libgl_filename, RTLD_GLOBAL | RTLD_LAZY); if (!_libGlHandle) { os::log("apitrace: error: couldn't find libGL.so\n"); return NULL; diff --git a/retrace/glretrace_main.cpp b/retrace/glretrace_main.cpp index 8527126a..525822ad 100755 --- a/retrace/glretrace_main.cpp +++ b/retrace/glretrace_main.cpp @@ -689,16 +689,3 @@ void retrace::cleanUp(void) { glws::cleanup(); } - - -#ifndef _WIN32 - -#include "dlopen.hpp" - -void * -_dlopen(const char *filename, int flag) -{ - return dlopen(filename, flag); -} - -#endif diff --git a/wrappers/CMakeLists.txt b/wrappers/CMakeLists.txt index 2ceac332..27dc7eb1 100644 --- a/wrappers/CMakeLists.txt +++ b/wrappers/CMakeLists.txt @@ -351,7 +351,7 @@ elseif (X11_FOUND) # Prevent symbol relocations internal to our wrapper library to be # overwritten by the application. And fail if there are missing # symbols. - LINK_FLAGS "-Wl,-Bsymbolic -Wl,-Bsymbolic-functions -Wl,-z,defs" + LINK_FLAGS "-Wl,-Bsymbolic -Wl,-Bsymbolic-functions -Wl,-z,defs -Wl,-defsym=_begin=0" ) target_link_libraries (glxtrace @@ -402,7 +402,7 @@ if (ENABLE_EGL AND NOT WIN32 AND NOT APPLE) # Prevent symbol relocations internal to our wrapper library to be # overwritten by the application. And fail if there are missing # symbols. - LINK_FLAGS "-Wl,-Bsymbolic -Wl,-Bsymbolic-functions -Wl,-z,defs" + LINK_FLAGS "-Wl,-Bsymbolic -Wl,-Bsymbolic-functions -Wl,-z,defs -Wl,-defsym=_begin=0" ) target_link_libraries (egltrace diff --git a/wrappers/dlsym.cpp b/wrappers/dlsym.cpp index 24d3a087..8a22c989 100644 --- a/wrappers/dlsym.cpp +++ b/wrappers/dlsym.cpp @@ -26,24 +26,171 @@ /* - * Provides access to real dlopen, as tracing libraries interpose it. + * Intercept dlsym or dlopen. */ #include "os.hpp" -#include "dlopen.hpp" -#ifdef __GLIBC__ - +#if !defined(_WIN32) && !defined(__APPLE__) #include <dlfcn.h> +#include <execinfo.h> +#if defined(__GLIBC__) extern "C" void * __libc_dlopen_mode(const char * filename, int flag); extern "C" void * __libc_dlsym(void * handle, const char * symbol); +#endif + + +static const int verbosity = 2; + + +extern "C" char _begin[]; // obtained via -Wl,-defsym=_begin=0 +extern "C" char _etext[]; // builtin + + +/* + * Whether the given address belongs to this shared object or not. + */ +static inline bool +isInternalAddress(void *addr) +{ + return _begin <= addr && addr < _etext; +} + + + +#define DLSYM_INTERCEPTION 1 + + +#if DLSYM_INTERCEPTION && defined(__GLIBC__) + +#include <link.h> // for link_map + + +static void * self_handle = NULL; + + +// Try to get GCC to do tail-call optimization +// XXX: Unfortunately not effective with calls to function pointers +#pragma GCC optimize ("omit-frame-pointer") +#pragma GCC optimize ("optimize-sibling-calls") + + +/* + * Intercept dlsym. + * + * Intercepting dlopen has several disadvantages: + * + */ +PUBLIC +void * +dlsym(void * handle, const char * symbol) +{ + void * returnAddr = __builtin_extract_return_addr(__builtin_return_address(0)); + + bool internal = isInternalAddress(returnAddr); + + /* + * We rely on glibc's internal __libc_dlsym. See also + * http://www.linuxforu.com/2011/08/lets-hook-a-library-function/ + * + * Use use it to obtain the true dlsym. We don't use __libc_dlsym directly + * because it does not support things such as RTLD_NEXT. + */ + typedef void * (*PFN_DLSYM)(void *, const char *); + static PFN_DLSYM dlsym_ptr = NULL; + static void *libdl_handle; + if (!dlsym_ptr) { + libdl_handle = __libc_dlopen_mode("libdl.so.2", RTLD_LOCAL | RTLD_NOW); + if (libdl_handle) { + dlsym_ptr = (PFN_DLSYM)__libc_dlsym(libdl_handle, "dlsym"); + } + if (!dlsym_ptr) { + os::log("apitrace: error: failed to look up real dlsym\n"); + os::abort(); + } + } + + const char *s = symbol; + bool intercept = (s[0] == 'g' && s[1] == 'l' && (s[2] >= 'A' && s[2] <= 'Z')) || + (s[0] == 'e' && s[1] == 'g' && s[2] == 'l' && (s[3] >= 'A' && s[3] <= 'Z')); + + void *addr = NULL; + if (!internal) { + + if (intercept) { + // Get our own handle + if (!self_handle) { + static const int dummy = 0xdeadbeef; + Dl_info info; + if (!dladdr(&dummy, &info)) { + os::log("apitrace: error: dladdr() failed\n"); + os::abort(); + } + self_handle = __libc_dlopen_mode(info.dli_fname, RTLD_LAZY | RTLD_NOLOAD); + if (!self_handle) { + os::log("apitrace: error: dlopen(\"%s\") failed\n", info.dli_fname); + os::abort(); + } + } + + addr = __libc_dlsym(self_handle, symbol); + } + + if (verbosity > 1 || + (verbosity == 1 && addr)) { + const char *filename = NULL; + const char *quote = ""; + if (handle == RTLD_DEFAULT) { + filename = "RTLD_DEFAULT"; + } else if (handle == RTLD_NEXT) { + filename = "RTLD_NEXT"; + } else if (handle == libdl_handle) { + filename = "libdl.so.2"; + } else { + struct link_map *map; + if (dlinfo(handle, RTLD_DI_LINKMAP, &map) == 0) { + filename = map->l_name; + if (filename && filename[0] == '\0') { + filename = NULL; + } else { + quote = "\""; + } + } + } + const char *verb = addr ? "intercepting" : "ignoring"; + if (filename) { + os::log("apitrace: %s dlsym(%s%s%s, \"%s\")\n", verb, quote, filename, quote, symbol); + } else { + os::log("apitrace: %s dlsym(%p, \"%s\")\n", verb, handle, symbol); + } + } + + if (addr) { + return addr; + } + } + + addr = dlsym_ptr(handle, symbol); + + if (addr && isInternalAddress(addr)) { + os::log("apitrace: error: infinite recursing looking up \"%s\"\n", symbol); + os::abort(); + } + + return addr; +} +#else // !DLSYM_INTERCEPTION + + +#ifdef __GLIBC__ + /* * Protect against dlsym interception. * @@ -58,6 +205,7 @@ PRIVATE void * dlsym(void * handle, const char * symbol) { + os::log("apitrace: info: _dlsym(..., \"%s\")\n", symbol); /* * We rely on glibc's internal __libc_dlsym. See also * http://www.linuxforu.com/2011/08/lets-hook-a-library-function/ @@ -78,18 +226,27 @@ dlsym(void * handle, const char * symbol) } } - return dlsym_ptr(handle, symbol); -} + void *addr = dlsym_ptr(handle, symbol); + if (addr && isInternalAddress(addr)) { + os::log("apitrace: error: infinite recursing looking up \"%s\"\n", symbol); + os::abort(); + } + + return addr; +} #endif /* __GLIBC__ */ +extern void * _libGlHandle; + + /* * Invoke the true dlopen() function. */ -void * -_dlopen(const char *filename, int flag) +static void * +real_dlopen(const char *filename, int flag) { typedef void * (*PFN_DLOPEN)(const char *, int); static PFN_DLOPEN dlopen_ptr = NULL; @@ -115,3 +272,95 @@ _dlopen(const char *filename, int flag) return dlopen_ptr(filename, flag); } + + +/* + * Several applications, such as Quake3, use dlopen("libGL.so.1"), but + * LD_PRELOAD does not intercept symbols obtained via dlopen/dlsym, therefore + * we need to intercept the dlopen() call here, and redirect to our wrapper + * shared object. + */ +static void * +fake_dlopen(const char *filename, int flag) +{ + bool intercept = false; + + os::log("apitrace: info: dlopen(\"%s\", 0x%x)\n", filename, flag); + + if (filename) { + intercept = + strcmp(filename, "libEGL.so") == 0 || + strcmp(filename, "libEGL.so.1") == 0 || + strcmp(filename, "libGLESv1_CM.so") == 0 || + strcmp(filename, "libGLESv1_CM.so.1") == 0 || + strcmp(filename, "libGLESv2.so") == 0 || + strcmp(filename, "libGLESv2.so.2") == 0 || + strcmp(filename, "libGL.so") == 0 || + strcmp(filename, "libGL.so.1") == 0; + + if (intercept) { + os::log("apitrace: redirecting dlopen(\"%s\", 0x%x)\n", filename, flag); + + /* The current dispatch implementation relies on core entry-points + * to be globally available, so force this. + * + * TODO: A better approach would be note down the entry points here + * and use them latter. Another alternative would be to reopen the + * library with RTLD_NOLOAD | RTLD_GLOBAL. + */ + flag &= ~RTLD_LOCAL; + flag |= RTLD_GLOBAL; + } + } + + void *handle = real_dlopen(filename, flag); + + if (intercept) { + + // FIXME: handle absolute paths and other versions + if (strcmp(filename, "libGL.so") == 0 || + strcmp(filename, "libGL.so.1") == 0) { + + // Use the true libGL.so handle instead of RTLD_NEXT from now on + _libGlHandle = handle; + } + + // Get the file path for our shared object, and use it instead + static const int dummy = 0xdeadbeef; + Dl_info info; + if (dladdr(&dummy, &info)) { + handle = real_dlopen(info.dli_fname, flag); + } else { + os::log("apitrace: warning: dladdr() failed\n"); + } + + // SDL will skip dlopen'ing libEGL.so after it spots EGL symbols on our + // wrapper, so force loading it here. + // (https://github.com/apitrace/apitrace/issues/291#issuecomment-59734022) + if (strcmp(filename, "libEGL.so") != 0 && + strcmp(filename, "libEGL.so.1") != 0) { + real_dlopen("libEGL.so.1", RTLD_GLOBAL | RTLD_LAZY); + } + } + + return handle; +} + + +extern "C" PUBLIC +void * +dlopen(const char *filename, int flag) +{ + void * returnAddr = __builtin_return_address(0); + bool internal = isInternalAddress(returnAddr); + if (internal) { + return real_dlopen(filename, flag); + } else { + return fake_dlopen(filename, flag); + } +} + + +#endif // !DLSYM_INTERCEPTION + +#endif // !defined(_WIN32) && !defined(__APPLE__) diff --git a/wrappers/egltrace.py b/wrappers/egltrace.py index 7d2d182c..d9c597c9 100644 --- a/wrappers/egltrace.py +++ b/wrappers/egltrace.py @@ -113,7 +113,6 @@ if __name__ == '__main__': print '#define GL_GLEXT_PROTOTYPES' print '#define EGL_EGLEXT_PROTOTYPES' print - print '#include "dlopen.hpp"' print '#include "glproc.hpp"' print '#include "glsize.hpp"' print '#include "eglsize.hpp"' @@ -130,68 +129,6 @@ if __name__ == '__main__': print r''' - -/* - * Several applications, such as Quake3, use dlopen("libGL.so.1"), but - * LD_PRELOAD does not intercept symbols obtained via dlopen/dlsym, therefore - * we need to intercept the dlopen() call here, and redirect to our wrapper - * shared object. - */ -extern "C" PUBLIC -void * dlopen(const char *filename, int flag) -{ - bool intercept = false; - - if (filename) { - intercept = - strcmp(filename, "libEGL.so") == 0 || - strcmp(filename, "libEGL.so.1") == 0 || - strcmp(filename, "libGLESv1_CM.so") == 0 || - strcmp(filename, "libGLESv1_CM.so.1") == 0 || - strcmp(filename, "libGLESv2.so") == 0 || - strcmp(filename, "libGLESv2.so.2") == 0 || - strcmp(filename, "libGL.so") == 0 || - strcmp(filename, "libGL.so.1") == 0; - - if (intercept) { - os::log("apitrace: redirecting dlopen(\"%s\", 0x%x)\n", filename, flag); - - /* The current dispatch implementation relies on core entry-points to be globally available, so force this. - * - * TODO: A better approach would be note down the entry points here and - * use them latter. Another alternative would be to reopen the library - * with RTLD_NOLOAD | RTLD_GLOBAL. - */ - flag &= ~RTLD_LOCAL; - flag |= RTLD_GLOBAL; - } - } - - void *handle = _dlopen(filename, flag); - - if (intercept) { - // Get the file path for our shared object, and use it instead - static int dummy = 0xdeedbeef; - Dl_info info; - if (dladdr(&dummy, &info)) { - handle = _dlopen(info.dli_fname, flag); - } else { - os::log("apitrace: warning: dladdr() failed\n"); - } - - // SDL will skip dlopen'ing libEGL.so after it spots EGL symbols on our - // wrapper, so force loading it here. - // (https://github.com/apitrace/apitrace/issues/291#issuecomment-59734022) - if (strcmp(filename, "libEGL.so") != 0 && - strcmp(filename, "libEGL.so.1") != 0) { - _dlopen("libEGL.so.1", RTLD_GLOBAL | RTLD_LAZY); - } - } - - return handle; -} - - #if defined(ANDROID) /* diff --git a/wrappers/egltrace.version b/wrappers/egltrace.version index 0e7e62b9..e15a37c1 100644 --- a/wrappers/egltrace.version +++ b/wrappers/egltrace.version @@ -5,6 +5,7 @@ egl[A-Z]*; gl[A-Z]*; dlopen; + dlsym; local: *; }; diff --git a/wrappers/glxtrace.py b/wrappers/glxtrace.py index e9c43a9c..d9dce953 100644 --- a/wrappers/glxtrace.py +++ b/wrappers/glxtrace.py @@ -166,7 +166,6 @@ if __name__ == '__main__': print '#define GL_GLEXT_PROTOTYPES' print '#define GLX_GLXEXT_PROTOTYPES' print - print '#include "dlopen.hpp"' print '#include "glproc.hpp"' print '#include "glsize.hpp"' print @@ -178,52 +177,3 @@ if __name__ == '__main__': api.addModule(module) tracer = GlxTracer() tracer.traceApi(api) - - print r''' - - -/* - * Several applications, such as Quake3, use dlopen("libGL.so.1"), but - * LD_PRELOAD does not intercept symbols obtained via dlopen/dlsym, therefore - * we need to intercept the dlopen() call here, and redirect to our wrapper - * shared object. - */ -extern "C" PUBLIC -void * dlopen(const char *filename, int flag) -{ - void *handle; - - handle = _dlopen(filename, flag); - - const char * libgl_filename = getenv("TRACE_LIBGL"); - - if (filename && handle && !libgl_filename) { - if (0) { - os::log("apitrace: warning: dlopen(\"%s\", 0x%x)\n", filename, flag); - } - - // FIXME: handle absolute paths and other versions - if (strcmp(filename, "libGL.so") == 0 || - strcmp(filename, "libGL.so.1") == 0) { - - // Use the true libGL.so handle instead of RTLD_NEXT from now on - _libGlHandle = handle; - - // Get the file path for our shared object, and use it instead - static int dummy = 0xdeedbeef; - Dl_info info; - if (dladdr(&dummy, &info)) { - os::log("apitrace: redirecting dlopen(\"%s\", 0x%x)\n", filename, flag); - handle = _dlopen(info.dli_fname, flag); - } else { - os::log("apitrace: warning: dladdr() failed\n"); - } - } - } - - return handle; -} - - - -''' diff --git a/wrappers/glxtrace.version b/wrappers/glxtrace.version index 7d7a1624..69d111a6 100644 --- a/wrappers/glxtrace.version +++ b/wrappers/glxtrace.version @@ -4,6 +4,7 @@ _fini; gl[A-Z]*; dlopen; + dlsym; local: *; }; |