dlwrap.c revision f71742df
1/* Copyright © 2013, Intel Corporation
2 *
3 * Permission is hereby granted, free of charge, to any person obtaining a copy
4 * of this software and associated documentation files (the "Software"), to deal
5 * in the Software without restriction, including without limitation the rights
6 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 * copies of the Software, and to permit persons to whom the Software is
8 * furnished to do so, subject to the following conditions:
9 *
10 * The above copyright notice and this permission notice shall be included in
11 * all copies or substantial portions of the Software.
12 *
13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 * THE SOFTWARE.
20 */
21
22/** @file dlwrap.c
23 *
24 * Implements a wrapper for dlopen() and dlsym() so that epoxy will
25 * end up finding symbols from the testcases named
26 * "override_EGL_eglWhatever()" or "override_GLES2_glWhatever()" or
27 * "override_GL_glWhatever()" when it tries to dlopen() and dlsym()
28 * the real GL or EGL functions in question.
29 *
30 * This lets us simulate some target systems in the test suite, or
31 * just stub out GL functions so we can be sure of what's being
32 * called.
33 */
34
35/* dladdr is a glibc extension */
36#define _GNU_SOURCE
37#include <dlfcn.h>
38
39#include <stdbool.h>
40#include <stdio.h>
41#include <stdlib.h>
42#include <string.h>
43#include <assert.h>
44
45#include "dlwrap.h"
46
47#define STRNCMP_LITERAL(var, literal) \
48    strncmp ((var), (literal), sizeof (literal) - 1)
49
50#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
51
52void *libfips_handle;
53
54typedef void *(*fips_dlopen_t)(const char *filename, int flag);
55typedef void *(*fips_dlsym_t)(void *handle, const char *symbol);
56
57void *override_EGL_eglGetProcAddress(const char *name);
58void *override_GL_glXGetProcAddress(const char *name);
59void *override_GL_glXGetProcAddressARB(const char *name);
60void __dlclose(void *handle);
61
62static struct libwrap {
63    const char *filename;
64    const char *symbol_prefix;
65    void *handle;
66} wrapped_libs[] = {
67    { "libGL.so", "GL", NULL },
68    { "libEGL.so", "EGL", NULL },
69    { "libGLESv2.so", "GLES2", NULL },
70};
71
72/* Match 'filename' against an internal list of libraries for which
73 * libfips has wrappers.
74 *
75 * Returns true and sets *index_ret if a match is found.
76 * Returns false if no match is found. */
77static struct libwrap *
78find_wrapped_library(const char *filename)
79{
80    unsigned i;
81
82    if (!filename)
83        return NULL;
84
85    for (i = 0; i < ARRAY_SIZE(wrapped_libs); i++) {
86        if (strncmp(wrapped_libs[i].filename, filename,
87                    strlen(wrapped_libs[i].filename)) == 0) {
88            return &wrapped_libs[i];
89        }
90    }
91
92    return NULL;
93}
94
95/* Many (most?) OpenGL programs dlopen libGL.so.1 rather than linking
96 * against it directly, which means they would not be seeing our
97 * wrapped GL symbols via LD_PRELOAD. So we catch the dlopen in a
98 * wrapper here and redirect it to our library.
99 */
100void *
101dlopen(const char *filename, int flag)
102{
103    void *ret;
104    struct libwrap *wrap;
105
106    /* Before deciding whether to redirect this dlopen to our own
107     * library, we call the real dlopen. This assures that any
108     * expected side-effects from loading the intended library are
109     * resolved. Below, we may still return a handle pointing to
110     * our own library, and not what is opened here. */
111    ret = dlwrap_real_dlopen(filename, flag);
112
113    /* If filename is not a wrapped library, just return real dlopen */
114    wrap = find_wrapped_library(filename);
115    if (!wrap)
116        return ret;
117
118    wrap->handle = ret;
119
120    /* We use wrapped_libs as our handles to libraries. */
121    return wrap;
122}
123
124/**
125 * Wraps dlclose to hide our faked handles from it.
126 */
127void
128__dlclose(void *handle)
129{
130    struct libwrap *wrap = handle;
131
132    if (wrap < wrapped_libs ||
133        wrap >= wrapped_libs + ARRAY_SIZE(wrapped_libs)) {
134        void (*real_dlclose)(void *handle) = dlwrap_real_dlsym(RTLD_NEXT, "__dlclose");
135        real_dlclose(handle);
136    }
137}
138
139void *
140dlwrap_real_dlopen(const char *filename, int flag)
141{
142    static fips_dlopen_t real_dlopen = NULL;
143
144    if (!real_dlopen) {
145        real_dlopen = (fips_dlopen_t) dlwrap_real_dlsym(RTLD_NEXT, "dlopen");
146        if (!real_dlopen) {
147            fprintf(stderr, "Error: Failed to find symbol for dlopen.\n");
148            exit(1);
149        }
150    }
151
152    return real_dlopen(filename, flag);
153}
154
155/**
156 * Return the dlsym() on the application's namespace for
157 * "override_<prefix>_<name>"
158 */
159static void *
160wrapped_dlsym(const char *prefix, const char *name)
161{
162    char *wrap_name;
163    void *symbol;
164
165    asprintf(&wrap_name, "override_%s_%s", prefix, name);
166    symbol = dlwrap_real_dlsym(RTLD_DEFAULT, wrap_name);
167    free(wrap_name);
168    return symbol;
169}
170
171/* Since we redirect dlopens of libGL.so and libEGL.so to libfips we
172 * need to ensure that dlysm succeeds for all functions that might be
173 * defined in the real, underlying libGL library. But we're far too
174 * lazy to implement wrappers for function that would simply
175 * pass-through, so instead we also wrap dlysm and arrange for it to
176 * pass things through with RTLD_next if libfips does not have the
177 * function desired.  */
178void *
179dlsym(void *handle, const char *name)
180{
181    struct libwrap *wrap = handle;
182
183    /* Make sure that handle is actually one of our wrapped libs. */
184    if (wrap < wrapped_libs ||
185        wrap >= wrapped_libs + ARRAY_SIZE(wrapped_libs)) {
186        wrap = NULL;
187    }
188
189    /* Failing that, anything specifically requested from the
190     * libfips library should be redirected to a real GL
191     * library. */
192
193    if (wrap) {
194        void *symbol = wrapped_dlsym(wrap->symbol_prefix, name);
195        if (symbol)
196            return symbol;
197        else
198            return dlwrap_real_dlsym(wrap->handle, name);
199    }
200
201    /* And anything else is some unrelated dlsym. Just pass it
202     * through.  (This also covers the cases of lookups with
203     * special handles such as RTLD_DEFAULT or RTLD_NEXT.)
204     */
205    return dlwrap_real_dlsym(handle, name);
206}
207
208void *
209dlwrap_real_dlsym(void *handle, const char *name)
210{
211    static fips_dlsym_t real_dlsym = NULL;
212
213    if (!real_dlsym) {
214        /* FIXME: This brute-force, hard-coded searching for a versioned
215         * symbol is really ugly. The only reason I'm doing this is because
216         * I need some way to lookup the "dlsym" function in libdl, but
217         * I can't use 'dlsym' to do it. So dlvsym works, but forces me
218         * to guess what the right version is.
219         *
220         * Potential fixes here:
221         *
222         *   1. Use libelf to actually inspect libdl.so and
223         *      find the right version, (finding the right
224         *      libdl.so can be made easier with
225         *      dl_iterate_phdr).
226         *
227         *   2. Use libelf to find the offset of the 'dlsym'
228         *      symbol within libdl.so, (and then add this to
229         *      the base address at which libdl.so is loaded
230         *      as reported by dl_iterate_phdr).
231         *
232         * In the meantime, I'll just keep augmenting this
233         * hard-coded version list as people report bugs. */
234        const char *version[] = {
235            "GLIBC_2.17",
236            "GLIBC_2.4",
237            "GLIBC_2.3",
238            "GLIBC_2.2.5",
239            "GLIBC_2.2",
240            "GLIBC_2.0"
241        };
242        int num_versions = sizeof(version) / sizeof(version[0]);
243        int i;
244        for (i = 0; i < num_versions; i++) {
245            real_dlsym = (fips_dlsym_t) dlvsym(RTLD_NEXT, "dlsym", version[i]);
246            if (real_dlsym)
247                break;
248        }
249        if (i == num_versions) {
250            fprintf(stderr, "Internal error: Failed to find real dlsym\n");
251            fprintf(stderr,
252                    "This may be a simple matter of fips not knowing about the version of GLIBC that\n"
253                    "your program is using. Current known versions are:\n\n\t");
254            for (i = 0; i < num_versions; i++)
255                fprintf(stderr, "%s ", version[i]);
256            fprintf(stderr,
257                    "\n\nYou can inspect your version by first finding libdl.so.2:\n"
258                    "\n"
259                    "\tldd <your-program> | grep libdl.so\n"
260                    "\n"
261                    "And then inspecting the version attached to the dlsym symbol:\n"
262                    "\n"
263                    "\treadelf -s /path/to/libdl.so.2 | grep dlsym\n"
264                    "\n"
265                    "And finally, adding the version to dlwrap.c:dlwrap_real_dlsym.\n");
266
267            exit(1);
268        }
269    }
270
271    return real_dlsym(handle, name);
272}
273
274void *
275override_GL_glXGetProcAddress(const char *name)
276{
277    void *symbol;
278
279    symbol = wrapped_dlsym("GL", name);
280    if (symbol)
281        return symbol;
282
283    return DEFER_TO_GL("libGL.so.1", override_GL_glXGetProcAddress,
284                       "glXGetProcAddress", (name));
285}
286
287void *
288override_GL_glXGetProcAddressARB(const char *name)
289{
290    void *symbol;
291
292    symbol = wrapped_dlsym("GL", name);
293    if (symbol)
294        return symbol;
295
296    return DEFER_TO_GL("libGL.so.1", override_GL_glXGetProcAddressARB,
297                       "glXGetProcAddressARB", (name));
298}
299
300void *
301override_EGL_eglGetProcAddress(const char *name)
302{
303    void *symbol;
304
305    if (!STRNCMP_LITERAL(name, "gl")) {
306        symbol = wrapped_dlsym("GLES2", name);
307        if (symbol)
308            return symbol;
309    }
310
311    if (!STRNCMP_LITERAL(name, "egl")) {
312        symbol = wrapped_dlsym("EGL", name);
313        if (symbol)
314            return symbol;
315    }
316
317    return DEFER_TO_GL("libEGL.so.1", override_EGL_eglGetProcAddress,
318                       "eglGetProcAddress", (name));
319}
320