/* Copyright © 2013, Intel Corporation * * 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. */ /** @file dlwrap.c * * Implements a wrapper for dlopen() and dlsym() so that epoxy will * end up finding symbols from the testcases named * "override_EGL_eglWhatever()" or "override_GLES2_glWhatever()" or * "override_GL_glWhatever()" when it tries to dlopen() and dlsym() * the real GL or EGL functions in question. * * This lets us simulate some target systems in the test suite, or * just stub out GL functions so we can be sure of what's being * called. */ /* dladdr is a glibc extension */ #define _GNU_SOURCE #include #include #include #include #include #include #include "dlwrap.h" #define STRNCMP_LITERAL(var, literal) \ strncmp ((var), (literal), sizeof (literal) - 1) #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) void *libfips_handle; typedef void *(*fips_dlopen_t)(const char *filename, int flag); typedef void *(*fips_dlsym_t)(void *handle, const char *symbol); void *override_EGL_eglGetProcAddress(const char *name); void *override_GL_glXGetProcAddress(const char *name); void *override_GL_glXGetProcAddressARB(const char *name); void __dlclose(void *handle); static struct libwrap { const char *filename; const char *symbol_prefix; void *handle; } wrapped_libs[] = { { "libGL.so", "GL", NULL }, { "libEGL.so", "EGL", NULL }, { "libGLESv2.so", "GLES2", NULL }, { "libOpenGL.so", "GL", NULL}, }; /* Match 'filename' against an internal list of libraries for which * libfips has wrappers. * * Returns true and sets *index_ret if a match is found. * Returns false if no match is found. */ static struct libwrap * find_wrapped_library(const char *filename) { unsigned i; if (!filename) return NULL; for (i = 0; i < ARRAY_SIZE(wrapped_libs); i++) { if (strncmp(wrapped_libs[i].filename, filename, strlen(wrapped_libs[i].filename)) == 0) { return &wrapped_libs[i]; } } return NULL; } /* Many (most?) OpenGL programs dlopen libGL.so.1 rather than linking * against it directly, which means they would not be seeing our * wrapped GL symbols via LD_PRELOAD. So we catch the dlopen in a * wrapper here and redirect it to our library. */ void * dlopen(const char *filename, int flag) { void *ret; struct libwrap *wrap; /* Before deciding whether to redirect this dlopen to our own * library, we call the real dlopen. This assures that any * expected side-effects from loading the intended library are * resolved. Below, we may still return a handle pointing to * our own library, and not what is opened here. */ ret = dlwrap_real_dlopen(filename, flag); /* If filename is not a wrapped library, just return real dlopen */ wrap = find_wrapped_library(filename); if (!wrap) return ret; wrap->handle = ret; /* We use wrapped_libs as our handles to libraries. */ return wrap; } /** * Wraps dlclose to hide our faked handles from it. */ void __dlclose(void *handle) { struct libwrap *wrap = handle; if (wrap < wrapped_libs || wrap >= wrapped_libs + ARRAY_SIZE(wrapped_libs)) { void (*real_dlclose)(void *handle) = dlwrap_real_dlsym(RTLD_NEXT, "__dlclose"); real_dlclose(handle); } } void * dlwrap_real_dlopen(const char *filename, int flag) { static fips_dlopen_t real_dlopen = NULL; if (!real_dlopen) { real_dlopen = (fips_dlopen_t) dlwrap_real_dlsym(RTLD_NEXT, "dlopen"); if (!real_dlopen) { fputs("Error: Failed to find symbol for dlopen.\n", stderr); exit(1); } } return real_dlopen(filename, flag); } /** * Return the dlsym() on the application's namespace for * "override__" */ static void * wrapped_dlsym(const char *prefix, const char *name) { char *wrap_name; void *symbol; if (asprintf(&wrap_name, "override_%s_%s", prefix, name) < 0) { fputs("Error: Failed to allocate memory.\n", stderr); abort(); } symbol = dlwrap_real_dlsym(RTLD_DEFAULT, wrap_name); free(wrap_name); return symbol; } /* Since we redirect dlopens of libGL.so and libEGL.so to libfips we * need to ensure that dlysm succeeds for all functions that might be * defined in the real, underlying libGL library. But we're far too * lazy to implement wrappers for function that would simply * pass-through, so instead we also wrap dlysm and arrange for it to * pass things through with RTLD_next if libfips does not have the * function desired. */ void * dlsym(void *handle, const char *name) { struct libwrap *wrap = handle; /* Make sure that handle is actually one of our wrapped libs. */ if (wrap < wrapped_libs || wrap >= wrapped_libs + ARRAY_SIZE(wrapped_libs)) { wrap = NULL; } /* Failing that, anything specifically requested from the * libfips library should be redirected to a real GL * library. */ if (wrap) { void *symbol = wrapped_dlsym(wrap->symbol_prefix, name); if (symbol) return symbol; else return dlwrap_real_dlsym(wrap->handle, name); } /* And anything else is some unrelated dlsym. Just pass it * through. (This also covers the cases of lookups with * special handles such as RTLD_DEFAULT or RTLD_NEXT.) */ return dlwrap_real_dlsym(handle, name); } void * dlwrap_real_dlsym(void *handle, const char *name) { static fips_dlsym_t real_dlsym = NULL; if (!real_dlsym) { /* FIXME: This brute-force, hard-coded searching for a versioned * symbol is really ugly. The only reason I'm doing this is because * I need some way to lookup the "dlsym" function in libdl, but * I can't use 'dlsym' to do it. So dlvsym works, but forces me * to guess what the right version is. * * Potential fixes here: * * 1. Use libelf to actually inspect libdl.so and * find the right version, (finding the right * libdl.so can be made easier with * dl_iterate_phdr). * * 2. Use libelf to find the offset of the 'dlsym' * symbol within libdl.so, (and then add this to * the base address at which libdl.so is loaded * as reported by dl_iterate_phdr). * * In the meantime, I'll just keep augmenting this * hard-coded version list as people report bugs. */ const char *version[] = { "GLIBC_2.17", "GLIBC_2.4", "GLIBC_2.3", "GLIBC_2.2.5", "GLIBC_2.2", "GLIBC_2.0", "FBSD_1.0" }; int num_versions = sizeof(version) / sizeof(version[0]); int i; for (i = 0; i < num_versions; i++) { real_dlsym = (fips_dlsym_t) dlvsym(RTLD_NEXT, "dlsym", version[i]); if (real_dlsym) break; } if (i == num_versions) { fputs("Internal error: Failed to find real dlsym\n", stderr); fputs("This may be a simple matter of fips not knowing about the version of GLIBC that\n" "your program is using. Current known versions are:\n\n\t", stderr); for (i = 0; i < num_versions; i++) fprintf(stderr, "%s ", version[i]); fputs("\n\nYou can inspect your version by first finding libdl.so.2:\n" "\n" "\tldd | grep libdl.so\n" "\n" "And then inspecting the version attached to the dlsym symbol:\n" "\n" "\treadelf -s /path/to/libdl.so.2 | grep dlsym\n" "\n" "And finally, adding the version to dlwrap.c:dlwrap_real_dlsym.\n", stderr); exit(1); } } return real_dlsym(handle, name); } void * override_GL_glXGetProcAddress(const char *name) { void *symbol; symbol = wrapped_dlsym("GL", name); if (symbol) return symbol; return DEFER_TO_GL("libGL.so.1", override_GL_glXGetProcAddress, "glXGetProcAddress", (name)); } void * override_GL_glXGetProcAddressARB(const char *name) { void *symbol; symbol = wrapped_dlsym("GL", name); if (symbol) return symbol; return DEFER_TO_GL("libGL.so.1", override_GL_glXGetProcAddressARB, "glXGetProcAddressARB", (name)); } void * override_EGL_eglGetProcAddress(const char *name) { void *symbol; if (!STRNCMP_LITERAL(name, "gl")) { symbol = wrapped_dlsym("GLES2", name); if (symbol) return symbol; } if (!STRNCMP_LITERAL(name, "egl")) { symbol = wrapped_dlsym("EGL", name); if (symbol) return symbol; } return DEFER_TO_GL("libEGL.so.1", override_EGL_eglGetProcAddress, "eglGetProcAddress", (name)); }