blob: 5ab8465b028265fcf3e6b69db489c690502327be [file] [log] [blame]
José Fonseca0e725472014-05-23 23:24:13 +01001/**************************************************************************
2 *
3 * Copyright 2014 VMware, Inc.
4 * Copyright 2011 Jose Fonseca
5 * All Rights Reserved.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE.
24 *
25 **************************************************************************/
26
27
Jose Fonseca4f97da62016-06-30 14:51:54 +010028#include <assert.h>
Jose Fonseca99e72a12016-06-30 20:07:31 +010029#include <stdlib.h>
Jose Fonseca84c1a8e2016-07-12 15:45:01 +010030#include <libgen.h>
Jose Fonseca99e72a12016-06-30 20:07:31 +010031
Jose Fonseca4f97da62016-06-30 14:51:54 +010032#include <memory>
33
José Fonseca0e725472014-05-23 23:24:13 +010034#include "os.hpp"
35
36
Robert Tarasov6ccf5bb2020-01-10 12:31:25 -080037#if defined(__GLIBC__) && !defined(__UCLIBC__)
José Fonseca0e725472014-05-23 23:24:13 +010038
39
40#include <dlfcn.h>
41
42extern "C" void * __libc_dlopen_mode(const char * filename, int flag);
43extern "C" void * __libc_dlsym(void * handle, const char * symbol);
44
45
46/*
47 * Protect against dlsym interception.
48 *
49 * We implement the whole API, so we don't need to intercept dlsym -- dlopen is
50 * enough. However we need to protect against other dynamic libraries
51 * intercepting dlsym, to prevent infinite recursion,
52 *
53 * In particular the Steam Community Overlay exports dlsym. See also
54 * http://lists.freedesktop.org/archives/apitrace/2013-March/000573.html
55 */
56PRIVATE
57void *
58dlsym(void * handle, const char * symbol)
59{
60 /*
61 * We rely on glibc's internal __libc_dlsym. See also
62 * http://www.linuxforu.com/2011/08/lets-hook-a-library-function/
63 *
64 * Use use it to obtain the true dlsym. We don't use __libc_dlsym directly
65 * because it does not support things such as RTLD_NEXT.
66 */
67 typedef void * (*PFN_DLSYM)(void *, const char *);
68 static PFN_DLSYM dlsym_ptr = NULL;
69 if (!dlsym_ptr) {
70 void *libdl_handle = __libc_dlopen_mode("libdl.so.2", RTLD_LOCAL | RTLD_NOW);
71 if (libdl_handle) {
72 dlsym_ptr = (PFN_DLSYM)__libc_dlsym(libdl_handle, "dlsym");
73 }
74 if (!dlsym_ptr) {
75 os::log("apitrace: error: failed to look up real dlsym\n");
76 return NULL;
77 }
78 }
79
80 return dlsym_ptr(handle, symbol);
81}
82
83
84
85#endif /* __GLIBC__ */
Jose Fonseca4f97da62016-06-30 14:51:54 +010086
87
88#include "dlopen.hpp"
89
90
91extern void * _libGlHandle;
92
93
94
95enum LibClass {
96 LIB_UNKNOWN = 0,
97 LIB_GL,
98 LIB_EGL,
99 LIB_GLES1,
100 LIB_GLES2,
101};
102
103
104inline LibClass
105classifyLibrary(const char *pathname)
106{
Jose Fonseca99e72a12016-06-30 20:07:31 +0100107 std::unique_ptr<char, decltype(free) *> dupname { strdup(pathname), free };
Jose Fonseca4f97da62016-06-30 14:51:54 +0100108
109 char *filename = basename(dupname.get());
110 assert(filename);
111
112 if (strcmp(filename, "libGL.so") == 0 ||
113 strcmp(filename, "libGL.so.1") == 0) {
114 return LIB_GL;
115 }
116
117#ifdef EGLTRACE
118 if (strcmp(filename, "libEGL.so") == 0 ||
119 strcmp(filename, "libEGL.so.1") == 0) {
120 return LIB_EGL;
121 }
122
123 if (strcmp(filename, "libGLESv1_CM.so") == 0 ||
124 strcmp(filename, "libGLESv1_CM.so.1") == 0) {
125 return LIB_GLES1;
126 }
127
128 if (strcmp(filename, "libGLESv2.so") == 0 ||
129 strcmp(filename, "libGLESv2.so.2") == 0) {
130 return LIB_GLES2;
131 }
132#endif
133
134 /*
135 * TODO: Identify driver SOs (e.g, *_dri.so), to prevent intercepting
136 * dlopen calls from them.
137 *
138 * Another alternative is to ignore dlopen calls when inside wrapped calls.
139 */
140
141 return LIB_UNKNOWN;
142}
143
144
145/*
146 * Several applications, such as Quake3, use dlopen("libGL.so.1"), but
147 * LD_PRELOAD does not intercept symbols obtained via dlopen/dlsym, therefore
148 * we need to intercept the dlopen() call here, and redirect to our wrapper
149 * shared object.
150 */
151extern "C" PUBLIC
152void * dlopen(const char *filename, int flag)
153{
154 void *handle;
155
156 if (!filename) {
157 return _dlopen(filename, flag);
158 }
159
160 LibClass libClass = classifyLibrary(filename);
161 bool intercept = libClass != LIB_UNKNOWN;
162
163 if (intercept) {
164 void *caller = __builtin_return_address(0);
165 Dl_info info;
166 const char *caller_module = "<unknown>";
167 if (dladdr(caller, &info)) {
168 caller_module = info.dli_fname;
169 intercept = classifyLibrary(caller_module) == LIB_UNKNOWN;
170 }
171
172 const char * libgl_filename = getenv("TRACE_LIBGL");
173 if (libgl_filename) {
174 // Don't intercept when using LD_LIBRARY_PATH instead of LD_PRELOAD
175 intercept = false;
176 }
177
178 os::log("apitrace: %s dlopen(\"%s\", 0x%x) from %s\n",
179 intercept ? "redirecting" : "ignoring",
180 filename, flag, caller_module);
181 }
182
183#ifdef EGLTRACE
184
185 if (intercept) {
186 /* The current dispatch implementation relies on core entry-points to be globally available, so force this.
187 *
188 * TODO: A better approach would be note down the entry points here and
189 * use them latter. Another alternative would be to reopen the library
190 * with RTLD_NOLOAD | RTLD_GLOBAL.
191 */
192 flag &= ~RTLD_LOCAL;
193 flag |= RTLD_GLOBAL;
194 }
195
196#endif
197
198 handle = _dlopen(filename, flag);
199 if (!handle) {
200 return handle;
201 }
202
203 if (intercept) {
204 if (libClass == LIB_GL) {
205 // Use the true libGL.so handle instead of RTLD_NEXT from now on
206 _libGlHandle = handle;
207 }
208
209 // Get the file path for our shared object, and use it instead
210 static int dummy = 0xdeedbeef;
211 Dl_info info;
212 if (dladdr(&dummy, &info)) {
213 handle = _dlopen(info.dli_fname, flag);
214 } else {
215 os::log("apitrace: warning: dladdr() failed\n");
216 }
217
218#ifdef EGLTRACE
219 // SDL will skip dlopen'ing libEGL.so after it spots EGL symbols on our
220 // wrapper, so force loading it here.
221 // (https://github.com/apitrace/apitrace/issues/291#issuecomment-59734022)
222 if (strcmp(filename, "libEGL.so") != 0 &&
223 strcmp(filename, "libEGL.so.1") != 0) {
224 _dlopen("libEGL.so.1", RTLD_GLOBAL | RTLD_LAZY);
225 }
226#endif
227 }
228
229 return handle;
230}
Jose Fonseca886e2ae2016-08-27 19:26:19 +0100231
232
233#ifdef __linux__
234
235#include "trace_writer_local.hpp"
236
237/*
238 * Intercept _exit so we can flush our trace even when the app (eg. Wine)
239 * aborts.
240 *
241 * TODO: Currently we dispatch to _Exit, but for completness we should
242 * intercept _Exit too.
243 */
244void
245_exit(int status)
246{
247 trace::localWriter.flush();
248 _Exit(status);
249}
250
251#endif /* __linux__ */