diff --git a/src/dispatch_common.c b/src/dispatch_common.c index f6d69e0..d5420bf 100644 --- a/src/dispatch_common.c +++ b/src/dispatch_common.c @@ -179,6 +179,8 @@ get_dlopen_handle(void **handle, const char *lib_name, bool exit_on_fail) abort(); } + fprintf(stderr, "loading %s\n", lib_name); + #ifdef _WIN32 *handle = LoadLibraryA(lib_name); #else diff --git a/test/.gitignore b/test/.gitignore index cbc9d71..a33ea93 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,3 +1,6 @@ +egl_and_glx_different_pointers_egl +egl_and_glx_different_pointers_egl_glx +egl_and_glx_different_pointers_glx egl_has_extension_nocontext egl_gles1_without_glx egl_gles2_without_glx diff --git a/test/Makefile.am b/test/Makefile.am index 7cf7d19..b23ddfa 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -53,13 +53,19 @@ AM_CFLAGS = $(CWARNFLAGS) TESTS = \ $(EGL_TESTS) \ $(GLX_TESTS) \ + $(EGL_AND_GLX_TESTS) \ $(WGL_TESTS) \ headerguards$(EXEEXT) \ miscdefines$(EXEEXT) \ khronos_typedefs$(EXEEXT) \ $() +check_BINARIES = $(EGL_AND_GLX_BIN) + XFAIL_TESTS = \ + egl_and_glx_different_pointers_egl_glx \ + egl_and_glx_different_pointers_egl \ + egl_and_glx_different_pointers_glx \ $() check_PROGRAMS = $(TESTS) @@ -79,6 +85,16 @@ if HAS_ZNOW GLX_SHARED_ZNOW = glx_shared_znow endif +if BUILD_EGL +if BUILD_GLX +EGL_AND_GLX_TESTS = \ + egl_and_glx_different_pointers_egl_glx \ + egl_and_glx_different_pointers_egl \ + egl_and_glx_different_pointers_glx \ + $() +endif +endif + GLX_TESTS = \ glx_beginend \ glx_public_api \ @@ -110,6 +126,21 @@ egl_gles2_without_glx_CPPFLAGS = $(AM_CPPFLAGS) -DGLES_VERSION=2 egl_gles2_without_glx_SOURCES = egl_without_glx.c egl_gles2_without_glx_LDADD = $(EPOXY) $(DLOPEN_LIBS) libegl_common.la $(X11_LIBS) +egl_and_glx_different_pointers_egl_SOURCES = egl_and_glx_different_pointers.c dlwrap.c dlwrap.h +egl_and_glx_different_pointers_egl_LDADD = libegl_common.la libglx_common.la $(DLOPEN_LIBS) $(EPOXY) $(X11_LIBS) +egl_and_glx_different_pointers_egl_LDFLAGS = -rdynamic +egl_and_glx_different_pointers_egl_CPPFLAGS = $(AM_CPPFLAGS) -DUSE_EGL + +egl_and_glx_different_pointers_glx_SOURCES = egl_and_glx_different_pointers.c dlwrap.c dlwrap.h +egl_and_glx_different_pointers_glx_LDADD = libegl_common.la libglx_common.la $(DLOPEN_LIBS) $(EPOXY) $(X11_LIBS) +egl_and_glx_different_pointers_glx_LDFLAGS = -rdynamic +egl_and_glx_different_pointers_glx_CPPFLAGS = $(AM_CPPFLAGS) -DUSE_GLX + +egl_and_glx_different_pointers_egl_glx_SOURCES = egl_and_glx_different_pointers.c dlwrap.c dlwrap.h +egl_and_glx_different_pointers_egl_glx_LDADD = libegl_common.la libglx_common.la $(DLOPEN_LIBS) $(EPOXY) $(X11_LIBS) +egl_and_glx_different_pointers_egl_glx_LDFLAGS = -rdynamic +egl_and_glx_different_pointers_egl_glx_CPPFLAGS = $(AM_CPPFLAGS) -DUSE_EGL -DUSE_GLX + glx_beginend_LDADD = $(EPOXY) libglx_common.la $(GL_LIBS) $(X11_LIBS) glx_public_api_LDADD = $(EPOXY) libglx_common.la $(X11_LIBS) diff --git a/test/dlwrap.c b/test/dlwrap.c index 3af6ceb..7b28c37 100644 --- a/test/dlwrap.c +++ b/test/dlwrap.c @@ -19,77 +19,74 @@ * 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 "fips.h" - #include "dlwrap.h" -#include "glwrap.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); -static const char *wrapped_libs[] = { - "libGL.so", - "libEGL.so", - "libGLESv2.so" +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 }, }; -static void *orig_handles[ARRAY_SIZE(wrapped_libs)]; - /* 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 bool -find_wrapped_library_index(const char *filename, unsigned *index_ret) +static struct libwrap * +find_wrapped_library(const char *filename) { unsigned i; for (i = 0; i < ARRAY_SIZE(wrapped_libs); i++) { - if (strncmp(wrapped_libs[i], filename, - strlen(wrapped_libs[i])) == 0) - { - *index_ret = i; - return true; - } - } - - return false; -} - -/* Perform a dlopen on the libfips library itself. - * - * Many places in fips need to lookup symbols within the libfips - * library itself, (and not in any other library). This function - * provides a reliable way to get a handle for performing such - * lookups. - * - * The returned handle can be passed to dlwrap_real_dlsym for the - * lookups. */ -void * -dlwrap_dlopen_libfips(void) -{ - Dl_info info; - - /* We first find our own filename by looking up a function - * known to exist only in libfips. This function itself - * (dlwrap_dlopen_libfips) is a good one for that purpose. */ - if (dladdr(dlwrap_dlopen_libfips, &info) == 0) { - fprintf(stderr, "Internal error: Failed to lookup filename of " - "libfips library with dladdr\n"); - exit(1); + if (strncmp(wrapped_libs[i].filename, filename, + strlen(wrapped_libs[i].filename)) == 0) { + return &wrapped_libs[i]; + } } - return dlwrap_real_dlopen(info.dli_fname, RTLD_NOW); + return NULL; } /* Many (most?) OpenGL programs dlopen libGL.so.1 rather than linking @@ -101,7 +98,7 @@ void * dlopen(const char *filename, int flag) { void *ret; - unsigned index; + struct libwrap *wrap; /* Before deciding whether to redirect this dlopen to our own * library, we call the real dlopen. This assures that any @@ -111,27 +108,29 @@ dlopen(const char *filename, int flag) ret = dlwrap_real_dlopen(filename, flag); /* If filename is not a wrapped library, just return real dlopen */ - if (!find_wrapped_library_index(filename, &index)) + wrap = find_wrapped_library(filename); + if (!wrap) return ret; - /* When the application dlopens any wrapped library starting - * with 'libGL', (whether libGL.so.1 or libGLESv2.so.2), let's - * continue to use that library handle for future lookups of - * OpenGL functions. */ - if (STRNCMP_LITERAL(filename, "libGL") == 0) - glwrap_set_gl_handle(ret); + wrap->handle = ret; - assert(index < ARRAY_SIZE(orig_handles)); - orig_handles[index] = ret; + /* We use wrapped_libs as our handles to libraries. */ + return wrap; +} - if (libfips_handle == NULL) - libfips_handle = dlwrap_dlopen_libfips(); +/** + * Wraps dlclose to hide our faked handles from it. + */ +void +__dlclose(void *handle) +{ + struct libwrap *wrap = handle; - /* Otherwise, we return our own handle so that we can intercept - * future calls to dlsym. We encode the index in the return value - * so that we can later map back to the originally requested - * dlopen-handle if necessary. */ - return libfips_handle + index; + 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 * @@ -150,6 +149,22 @@ dlwrap_real_dlopen(const char *filename, int flag) 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; + + asprintf(&wrap_name, "override_%s_%s", prefix, name); + 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 @@ -160,27 +175,24 @@ dlwrap_real_dlopen(const char *filename, int flag) void * dlsym(void *handle, const char *name) { - static void *symbol; - unsigned index; + struct libwrap *wrap = handle; - /* All gl* and egl* symbols are preferentially looked up in libfips. */ - if (STRNCMP_LITERAL(name, "gl") == 0 || STRNCMP_LITERAL(name, "egl") == 0) { - symbol = dlwrap_real_dlsym(libfips_handle, name); - if (symbol) - return symbol; + /* 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. */ - /* We subtract the index back out of the handle (see the addition - * of the index in our wrapper for dlopen above) to then use the - * correct, original dlopen'ed handle for the library of - * interest. */ - index = handle - libfips_handle; - if (index < ARRAY_SIZE(orig_handles)) { - return dlwrap_real_dlsym(orig_handles[index], name); + 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 @@ -251,3 +263,50 @@ dlwrap_real_dlsym(void *handle, const char *name) 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)); +} diff --git a/test/dlwrap.h b/test/dlwrap.h index 7e943e0..39ec9ec 100644 --- a/test/dlwrap.h +++ b/test/dlwrap.h @@ -53,5 +53,15 @@ dlwrap_dlopen_libfips(void); void * dlwrap_real_dlsym(void *handle, const char *symbol); +#define DEFER_TO_GL(library, func, name, args) \ +({ \ + void *lib = dlwrap_real_dlopen(library, RTLD_LAZY | RTLD_LOCAL); \ + typeof(&func) real_func = dlwrap_real_dlsym(lib, name); \ + /* gcc extension -- func's return value is the return value of \ + * the statement. \ + */ \ + real_func args; \ +}) + #endif diff --git a/test/egl_and_glx_different_pointers.c b/test/egl_and_glx_different_pointers.c new file mode 100644 index 0000000..2a2ff3c --- /dev/null +++ b/test/egl_and_glx_different_pointers.c @@ -0,0 +1,246 @@ +/* + * Copyright © 2014 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 (including the next + * paragraph) 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 egl_and_glx_different_pointers.c + * + * Tests that epoxy correctly handles an EGL and GLX implementation + * that return different function pointers between the two. + * + * This is the case for EGL and GLX on nvidia binary drivers + * currently, but is also the case if someone has nvidia binary GLX + * installed but still has Mesa (software) EGL installed. This seems + * common enough that we should make sure things work. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include "epoxy/gl.h" +#include "epoxy/egl.h" +#include "epoxy/glx.h" + +#include "egl_common.h" +#include "glx_common.h" +#include "dlwrap.h" + +#define GLX_FAKED_VENDOR_STRING "libepoxy override GLX" +#define EGL_FAKED_VENDOR_STRING "libepoxy override EGL" + +#define GL_CREATESHADER_VALUE 1234 +#define GLES2_CREATESHADER_VALUE 5678 + +const char *override_GLES2_glGetString(GLenum e); +const char *override_GL_glGetString(GLenum e); +GLuint override_GLES2_glCreateShader(GLenum e); +GLuint override_GL_glCreateShader(GLenum e); + +const char * +override_GL_glGetString(GLenum e) +{ + if (e == GL_VENDOR) + return GLX_FAKED_VENDOR_STRING; + + return DEFER_TO_GL("libGL.so.1", override_GL_glGetString, + "glGetString", (e)); +} + +const char * +override_GLES2_glGetString(GLenum e) +{ + if (e == GL_VENDOR) + return EGL_FAKED_VENDOR_STRING; + + return DEFER_TO_GL("libGLESv2.so.2", override_GLES2_glGetString, + "glGetString", (e)); +} + +GLuint +override_GL_glCreateShader(GLenum type) +{ + return GL_CREATESHADER_VALUE; +} + +GLuint +override_GLES2_glCreateShader(GLenum type) +{ + return GLES2_CREATESHADER_VALUE; +} + +#ifdef USE_GLX +static bool +make_glx_current_and_test(Display *dpy, GLXContext ctx, Drawable draw) +{ + const char *string; + GLuint shader; + bool pass = true; + + glXMakeCurrent(dpy, draw, ctx); + + if (!epoxy_is_desktop_gl()) { + fprintf(stderr, "Claimed to be ES\n"); + pass = false; + } + + string = (const char *)glGetString(GL_VENDOR); + printf("GLX vendor: %s\n", string); + + shader = glCreateShader(GL_FRAGMENT_SHADER); + if (shader != GL_CREATESHADER_VALUE) { + fprintf(stderr, "glCreateShader() returned %d instead of %d\n", + shader, GL_CREATESHADER_VALUE); + pass = false; + } + + pass = pass && !strcmp(string, GLX_FAKED_VENDOR_STRING); + + return pass; +} + +static void +init_glx(Display **out_dpy, GLXContext *out_ctx, Drawable *out_draw) +{ + Display *dpy = get_display_or_skip(); + make_glx_context_current_or_skip(dpy); + + *out_dpy = dpy; + *out_ctx = glXGetCurrentContext(); + *out_draw= glXGetCurrentDrawable(); +} +#endif /* USE_GLX */ + +#ifdef USE_EGL +static bool +make_egl_current_and_test(EGLDisplay *dpy, EGLContext ctx) +{ + const char *string; + GLuint shader; + bool pass = true; + + eglMakeCurrent(dpy, NULL, NULL, ctx); + + if (epoxy_is_desktop_gl()) { + fprintf(stderr, "Claimed to be desktop\n"); + pass = false; + } + + if (epoxy_gl_version() < 20) { + fprintf(stderr, "Claimed to be GL version %d\n", + epoxy_gl_version()); + pass = false; + } + + shader = glCreateShader(GL_FRAGMENT_SHADER); + if (shader != GLES2_CREATESHADER_VALUE) { + fprintf(stderr, "glCreateShader() returned %d instead of %d\n", + shader, GLES2_CREATESHADER_VALUE); + pass = false; + } + + string = (const char *)glGetString(GL_VENDOR); + printf("EGL vendor: %s\n", string); + + pass = pass && !strcmp(string, EGL_FAKED_VENDOR_STRING); + + return pass; +} + +static void +init_egl(EGLDisplay **out_dpy, EGLContext *out_ctx) +{ + EGLDisplay *dpy = get_egl_display_or_skip(); + static const EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + EGL_NONE + }; + static const EGLint context_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + EGLContext ctx; + EGLConfig cfg; + EGLint count; + + if (!epoxy_has_egl_extension(dpy, "EGL_KHR_surfaceless_context")) + errx(77, "Test requires EGL_KHR_surfaceless_context"); + + eglBindAPI(EGL_OPENGL_ES_API); + + if (!eglChooseConfig(dpy, config_attribs, &cfg, 1, &count)) + errx(77, "Couldn't get an EGLConfig\n"); + + ctx = eglCreateContext(dpy, cfg, NULL, context_attribs); + if (!ctx) + errx(77, "Couldn't create a GLES2 context\n"); + + *out_dpy = dpy; + *out_ctx = ctx; +} +#endif /* USE_EGL */ + +int +main(int argc, char **argv) +{ + bool pass = true; +#ifdef USE_EGL + EGLDisplay *egl_dpy; + EGLContext egl_ctx; +#endif +#ifdef USE_GLX + Display *glx_dpy; + GLXContext glx_ctx; + Drawable glx_draw; +#endif + + /* Force epoxy to have loaded both EGL and GLX libs already -- we + * can't assume anything about symbol resolution based on having + * EGL or GLX loaded. + */ + (void)glXGetCurrentContext(); + (void)eglGetCurrentContext(); + +#ifdef USE_GLX + init_glx(&glx_dpy, &glx_ctx, &glx_draw); + pass = make_glx_current_and_test(glx_dpy, glx_ctx, glx_draw) && pass; +#endif +#ifdef USE_EGL + init_egl(&egl_dpy, &egl_ctx); + pass = make_egl_current_and_test(egl_dpy, egl_ctx) && pass; +#endif + +#if defined(USE_GLX) && defined(USE_EGL) + pass = make_glx_current_and_test(glx_dpy, glx_ctx, glx_draw) && pass; + pass = make_egl_current_and_test(egl_dpy, egl_ctx) && pass; +#endif + + return pass != true; +}