/************************************************************************** * * Copyright 2014 VMware, Inc. * 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. * **************************************************************************/ #include #include #include #include #include "os.hpp" #ifdef __GLIBC__ #include extern "C" void * __libc_dlopen_mode(const char * filename, int flag); extern "C" void * __libc_dlsym(void * handle, const char * symbol); /* * Protect against dlsym interception. * * We implement the whole API, so we don't need to intercept dlsym -- dlopen is * enough. However we need to protect against other dynamic libraries * intercepting dlsym, to prevent infinite recursion, * * In particular the Steam Community Overlay exports dlsym. See also * http://lists.freedesktop.org/archives/apitrace/2013-March/000573.html */ PRIVATE void * dlsym(void * handle, const char * symbol) { /* * 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; if (!dlsym_ptr) { void *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"); return NULL; } } return dlsym_ptr(handle, symbol); } #endif /* __GLIBC__ */ #include "dlopen.hpp" extern void * _libGlHandle; enum LibClass { LIB_UNKNOWN = 0, LIB_GL, LIB_EGL, LIB_GLES1, LIB_GLES2, }; inline LibClass classifyLibrary(const char *pathname) { std::unique_ptr dupname { strdup(pathname), free }; char *filename = basename(dupname.get()); assert(filename); if (strcmp(filename, "libGL.so") == 0 || strcmp(filename, "libGL.so.1") == 0) { return LIB_GL; } #ifdef EGLTRACE if (strcmp(filename, "libEGL.so") == 0 || strcmp(filename, "libEGL.so.1") == 0) { return LIB_EGL; } if (strcmp(filename, "libGLESv1_CM.so") == 0 || strcmp(filename, "libGLESv1_CM.so.1") == 0) { return LIB_GLES1; } if (strcmp(filename, "libGLESv2.so") == 0 || strcmp(filename, "libGLESv2.so.2") == 0) { return LIB_GLES2; } #endif /* * TODO: Identify driver SOs (e.g, *_dri.so), to prevent intercepting * dlopen calls from them. * * Another alternative is to ignore dlopen calls when inside wrapped calls. */ return LIB_UNKNOWN; } /* * 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; if (!filename) { return _dlopen(filename, flag); } LibClass libClass = classifyLibrary(filename); bool intercept = libClass != LIB_UNKNOWN; if (intercept) { void *caller = __builtin_return_address(0); Dl_info info; const char *caller_module = ""; if (dladdr(caller, &info)) { caller_module = info.dli_fname; intercept = classifyLibrary(caller_module) == LIB_UNKNOWN; } const char * libgl_filename = getenv("TRACE_LIBGL"); if (libgl_filename) { // Don't intercept when using LD_LIBRARY_PATH instead of LD_PRELOAD intercept = false; } os::log("apitrace: %s dlopen(\"%s\", 0x%x) from %s\n", intercept ? "redirecting" : "ignoring", filename, flag, caller_module); } #ifdef EGLTRACE if (intercept) { /* 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; } #endif handle = _dlopen(filename, flag); if (!handle) { return handle; } if (intercept) { if (libClass == LIB_GL) { // 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)) { handle = _dlopen(info.dli_fname, flag); } else { os::log("apitrace: warning: dladdr() failed\n"); } #ifdef EGLTRACE // 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); } #endif } return handle; } #ifdef __linux__ #include "trace_writer_local.hpp" /* * Intercept _exit so we can flush our trace even when the app (eg. Wine) * aborts. * * TODO: Currently we dispatch to _Exit, but for completness we should * intercept _Exit too. */ void _exit(int status) { trace::localWriter.flush(); _Exit(status); } #endif /* __linux__ */