blob: 5ab8465b028265fcf3e6b69db489c690502327be [file] [log] [blame]
/**************************************************************************
*
* 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 <assert.h>
#include <stdlib.h>
#include <libgen.h>
#include <memory>
#include "os.hpp"
#if defined(__GLIBC__) && !defined(__UCLIBC__)
#include <dlfcn.h>
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<char, decltype(free) *> 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 = "<unknown>";
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__ */