libglvnd: Add support for ARMv7

This commit adds static and dynamic TSD stubs generation support for ARMv7.

Testing coverage includes all ARM / Thumb mode combinations between
libglvnd and a vendor implementation.
This commit is contained in:
Damien Leone 2015-07-14 17:24:05 -07:00
parent 59aef8dacc
commit 66f478867c
15 changed files with 431 additions and 14 deletions

View file

@ -103,6 +103,9 @@ if test "x$enable_asm" = xyes; then
;;
esac
;;
armv7l)
asm_arch=armv7l
;;
sparc*)
case "$host_os" in
linux*)
@ -121,6 +124,10 @@ if test "x$enable_asm" = xyes; then
DEFINES="$DEFINES -DUSE_X86_64_ASM"
AC_MSG_RESULT([yes, x86_64])
;;
armv7l)
DEFINES="$DEFINES -DUSE_ARMV7_ASM"
AC_MSG_RESULT([yes, armv7l])
;;
sparc)
DEFINES="$DEFINES -DUSE_SPARC_ASM"
AC_MSG_RESULT([yes, sparc])
@ -183,7 +190,7 @@ AC_ARG_ENABLE([tls],
)
AC_MSG_CHECKING([for initial-exec TLS])
if test "x$asm_arch" != xx86 && test "x$enable_tls" = "xyes"; then
if test "x$asm_arch" != xx86 && test "x$asm_arch" != xarmv7l && test "x$enable_tls" = "xyes"; then
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
__thread int foo __attribute__((tls_model("initial-exec")));
])],

View file

@ -39,7 +39,8 @@ libGL_la_SOURCES = \
libgl.c \
$(MAPI)/entry.c \
$(MAPI)/stub.c \
g_libglglxwrapper.c
g_libglglxwrapper.c \
$(top_srcdir)/src/util/utils_misc.c
BUILT_SOURCES = glapi_mapi_tmp.h g_libglglxwrapper.c
CLEANFILES = $(BUILT_SOURCES)

View file

@ -63,6 +63,7 @@ enum {
__GLDISPATCH_STUB_X86_TSD,
__GLDISPATCH_STUB_PURE_C,
__GLDISPATCH_STUB_X86_64_TSD,
__GLDISPATCH_STUB_ARMV7_THUMB_TSD,
__GLDISPATCH_STUB_NUM_TYPES
};

View file

@ -48,6 +48,8 @@ static inline void UNUSED __unused_tls_type_check(void)
TLS_TYPE_CHECK(X86_64_TLS);
TLS_TYPE_CHECK(X86_TSD);
TLS_TYPE_CHECK(PURE_C);
TLS_TYPE_CHECK(X86_64_TSD);
TLS_TYPE_CHECK(ARMV7_THUMB_TSD);
TLS_TYPE_CHECK(NUM_TYPES);
}

View file

@ -16,7 +16,8 @@ MAPI_GLAPI_FILES = \
$(TOP)/$(MAPI_PREFIX)/mapi_glapi.c \
$(TOP)/$(MAPI_PREFIX)/stub.c \
$(TOP)/$(MAPI_PREFIX)/table.c \
$(MAPI_UTIL_FILES)
$(MAPI_UTIL_FILES) \
$(TOP)/src/util/utils_misc.c
libglapi_la_SOURCES = \
$(MAPI_GLAPI_FILES) \

View file

@ -46,6 +46,8 @@
# else
# include "entry_x86-64_tsd.h"
# endif
#elif defined(USE_ARMV7_ASM) && defined(__GNUC__)
# include "entry_armv7_tsd.h"
#else
# include "entry_pure_c.h"
#endif

View file

@ -38,6 +38,7 @@ enum {
ENTRY_X86_TSD,
ENTRY_PURE_C,
ENTRY_X86_64_TSD,
ENTRY_ARMV7_THUMB_TSD,
ENTRY_NUM_TYPES
};

View file

@ -0,0 +1,228 @@
/*
* Copyright (c) 2015, NVIDIA CORPORATION.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and/or associated documentation files (the
* "Materials"), to deal in the Materials without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Materials, and to
* permit persons to whom the Materials are furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* unaltered in all copies or substantial portions of the Materials.
* Any additions, deletions, or changes to the original source files
* must be clearly indicated in accompanying documentation.
*
* If only executable code is distributed, then the accompanying
* documentation must state that "this software is based in part on the
* work of the Khronos Group."
*
* THE MATERIALS ARE 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
* MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
*/
#include <string.h>
#include <stdint.h>
#include <assert.h>
#include "entry.h"
#include "u_macros.h"
#include "u_current.h"
#include "u_execmem.h"
#include "utils_misc.h"
/*
* See: https://sourceware.org/binutils/docs/as/ARM-Directives.html
*/
__asm__(".syntax unified\n\t");
/*
* u_execmem_alloc() allocates 64 bytes per stub.
*/
#define ARMV7_ENTRY_SIZE 64
/*
* This runs in Thumb mode.
*
* libglvnd on armv7 is built with -march=armv7-a, which uses the AAPCS ABI
* that has ARM/Thumb interworking enabled by default.
*
* See: https://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html
*/
#define STUB_ASM_ENTRY(func) \
".balign " U_STRINGIFY(ARMV7_ENTRY_SIZE) "\n\t" \
".thumb_func\n\t" \
".global " func "\n\t" \
".type " func ", %function\n\t" \
func ":\n\t"
/*
* Looks up the current dispatch table, finds the stub address at the given slot
* then jumps to it.
*
* First tries to find a dispatch table in u_current[GLAPI_CURRENT_DISPATCH],
* if not found then it jumps to the 'lookup_dispatch' and calls
* u_current_get_internal() then jumps back to the 'found_dispatch' label.
*
* The 'found_dispatch' section computes the correct offset in the dispatch
* table then does a branch without link to the function address.
*
* This routine preserves the r0-r3 volatile registers as they store the
* parameters of the entry point that is being looked up.
*/
#define STUB_ASM_CODE(slot) \
"push {r0-r3}\n\t" \
"ldr r0, 1f\n\t" \
"ldr r0, [r0]\n\t" \
"cmp r0, #0\n\t" \
"it eq\n\t" \
"beq 10f\n\t" \
"11:\n\t" /* found_dispatch */ \
"ldr r1, 3f\n\t" \
"mov r2, #4\n\t" /* sizeof(void *) */ \
"mul r1, r1, r2\n\t" \
"ldr ip, [r0, +r1]\n\t" \
"pop {r0-r3}\n\t" \
"bx ip\n\t" \
"10:\n\t" /* lookup_dispatch */ \
"push {lr}\n\t" \
"ldr r0, 2f\n\t" \
"blx r0\n\t" \
"pop {lr}\n\t" \
"b 11b\n\t" \
"1:\n\t" \
".word " ENTRY_CURRENT_TABLE "\n\t" \
"2:\n\t" \
".word " ENTRY_CURRENT_TABLE_GET "\n\t" \
"3:\n\t" \
".word " slot "\n\t"
/*
* Bytecode for STUB_ASM_CODE()
*/
static unsigned char BYTECODE_TEMPLATE[] =
{
0xb4, 0x0f,
0xf8, 0xdf, 0x00, 0x28,
0x68, 0x00,
0x28, 0x00,
0xbf, 0x08,
0xe0, 0x08,
0x49, 0x09,
0xf0, 0x4f, 0x02, 0x04,
0xfb, 0x01, 0xf1, 0x02,
0xf8, 0x50, 0xc0, 0x01,
0xbc, 0x0f,
0x47, 0x60,
0xb5, 0x00,
0x48, 0x03,
0x47, 0x80,
0xf8, 0x5d, 0xeb, 0x04,
0xe7, 0xf0,
// Offsets that need to be patched
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};
#define ARMV7_BYTECODE_SIZE sizeof(BYTECODE_TEMPLATE)
__asm__(".balign " U_STRINGIFY(ARMV7_ENTRY_SIZE) "\n\t"
".section wtext, \"awx\"\n\t"
"armv7_entry_start:\n\t");
#define MAPI_TMP_STUB_ASM_GCC
#include "mapi_tmp.h"
__asm__(".balign " U_STRINGIFY(ARMV7_ENTRY_SIZE) "\n\t"
".armv7_entry_end:\n\t"
".text\n\t");
static const char armv7_entry_start[];
static const char armv7_entry_end[];
/*
* If built with -marm, let the assembler know that we are done with Thumb
*/
#if !defined(__thumb__)
__asm__(".arm\n\t");
#endif
const int entry_type = ENTRY_ARMV7_THUMB_TSD;
const int entry_stub_size = ARMV7_ENTRY_SIZE;
static const int TEMPLATE_OFFSET_CURRENT_TABLE = ARMV7_BYTECODE_SIZE - 3*4;
static const int TEMPLATE_OFFSET_CURRENT_TABLE_GET = ARMV7_BYTECODE_SIZE - 2*4;
static const int TEMPLATE_OFFSET_SLOT = ARMV7_BYTECODE_SIZE - 4;
static const int TEMPLATE_OFFSETS_SIZE = 3*4;
void
entry_init_public(void)
{
STATIC_ASSERT(ARMV7_BYTECODE_SIZE <= ARMV7_ENTRY_SIZE);
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
glvnd_byte_swap16((uint16_t *)BYTECODE_TEMPLATE,
entry_stub_size - TEMPLATE_OFFSETS_SIZE);
#endif
}
void
entry_generate_default_code(char *entry, int slot)
{
// Make sure the base address has the Thumb mode bit
assert((uintptr_t)entry & (uintptr_t)0x1);
// Get the actual beginning of the stub allocation
entry -= 1;
memcpy(entry, BYTECODE_TEMPLATE, ARMV7_BYTECODE_SIZE);
*((uint32_t *)(entry + TEMPLATE_OFFSET_SLOT)) = slot;
*((uint32_t *)(entry + TEMPLATE_OFFSET_CURRENT_TABLE)) =
(uint32_t)u_current;
*((uint32_t *)(entry + TEMPLATE_OFFSET_CURRENT_TABLE_GET)) =
(uint32_t)u_current_get_internal;
// See http://community.arm.com/groups/processors/blog/2010/02/17/caches-and-self-modifying-code
__builtin___clear_cache(entry, entry + ARMV7_BYTECODE_SIZE);
}
mapi_func
entry_get_public(int slot)
{
// Add 1 to the base address to force Thumb mode when jumping to the stub
return (mapi_func)(armv7_entry_start + (slot * ARMV7_ENTRY_SIZE) + 1);
}
#if !defined(STATIC_DISPATCH_ONLY)
void
entry_patch(mapi_func entry, int slot)
{
entry_generate_default_code((char *)entry, slot);
}
mapi_func
entry_generate(int slot)
{
void *code;
code = u_execmem_alloc(ARMV7_BYTECODE_SIZE);
if (!code)
return NULL;
// Add 1 to the base address to force Thumb mode when jumping to the stub
code = (void *)((char *)code + 1);
entry_generate_default_code(code, slot);
return (mapi_func)code;
}
#endif // !defined(STATIC_DISPATCH_ONLY)

View file

@ -36,7 +36,8 @@ noinst_HEADERS = \
libOpenGL_la_SOURCES = \
libopengl.c \
$(MAPI)/entry.c \
$(MAPI)/stub.c
$(MAPI)/stub.c \
$(top_srcdir)/src/util/utils_misc.c
BUILT_SOURCES = glapi_mapi_tmp.h
CLEANFILES = $(BUILT_SOURCES)

View file

@ -1,4 +1,34 @@
/*
* Copyright (c) 2015, NVIDIA CORPORATION.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and/or associated documentation files (the
* "Materials"), to deal in the Materials without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Materials, and to
* permit persons to whom the Materials are furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* unaltered in all copies or substantial portions of the Materials.
* Any additions, deletions, or changes to the original source files
* must be clearly indicated in accompanying documentation.
*
* If only executable code is distributed, then the accompanying
* documentation must state that "this software is based in part on the
* work of the Khronos Group."
*
* THE MATERIALS ARE 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
* MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
*/
#include "glvnd_genentry.h"
#include "utils_misc.h"
#include <string.h>
#include <stdint.h>
@ -7,9 +37,11 @@
#include <sys/mman.h>
#include <assert.h>
#include "utils_misc.h"
#define USE_ASM (defined(USE_X86_ASM) || \
defined(USE_X86_64_ASM) || \
defined(USE_ARMV7_ASM))
#if defined(__GNUC__) && (defined(USE_X86_ASM) || defined(USE_X86_64_ASM))
#if defined(__GNUC__) && USE_ASM
/// The maximum number of entrypoints that we can generate.
#define GENERATED_ENTRYPOINT_MAX 4096
@ -28,7 +60,6 @@ static const int DISPATCH_FUNC_OFFSET = 1;
static const int DISPATCH_FUNC_OFFSET_REL = 5;
#elif defined(USE_X86_64_ASM)
// For x86_64, the offset from the entrypoint to the dispatch function might be
// more than 2^31, and there's no JMP instruction that takes a 64-bit offset.
static unsigned char STUB_TEMPLATE[] =
@ -39,6 +70,23 @@ static unsigned char STUB_TEMPLATE[] =
static const int DISPATCH_FUNC_OFFSET = 2;
#elif defined(USE_ARMV7_ASM)
// Thumb bytecode
static unsigned char STUB_TEMPLATE[] =
{
// ldr ip, 1f
0xf8, 0xdf, 0xc0, 0x04,
// bx ip
0x47, 0x60,
// nop
0xbf, 0x00,
// Offset that needs to be patched
// 1:
0x00, 0x00, 0x00, 0x00,
};
static const int DISPATCH_FUNC_OFFSET = 8;
#else
#error "Can't happen -- not implemented"
#endif
@ -166,6 +214,12 @@ int InitEntrypoints(void)
}
entrypointBufferWrite = (uint8_t *) writeBuf;
entrypointBufferExec = (uint8_t *) execBuf;
#if defined(USE_ARMV7_ASM)
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
glvnd_byte_swap16((uint16_t *)STUB_TEMPLATE, sizeof(STUB_TEMPLATE) - 4);
#endif
#endif
}
return 0;
}
@ -181,6 +235,11 @@ void GenerateEntrypointFunc(GLVNDGenEntrypoint *entry, int index)
// Copy the template into our buffer.
memcpy(entry->entrypointWrite, STUB_TEMPLATE, sizeof(STUB_TEMPLATE));
#if defined(USE_ARMV7_ASM)
// Add 1 to the base address to force Thumb mode when jumping to the stub
entry->entrypointExec = (GLVNDentrypointStub)((char *)entry->entrypointExec + 1);
#endif
// Assign DefaultDispatchFunc as the dispatch function.
SetDispatchFuncPointer(entry, (GLVNDentrypointStub) DefaultDispatchFunc);
}
@ -197,11 +256,19 @@ void SetDispatchFuncPointer(GLVNDGenEntrypoint *entry,
*((intptr_t *) (code + DISPATCH_FUNC_OFFSET)) = offset;
#elif defined(USE_X86_64_ASM)
// For x86_64, we have to use a movabs instruction, which needs the
// absolute address of the dispatch function.
*((GLVNDentrypointStub *) (code + DISPATCH_FUNC_OFFSET)) = dispatch;
#elif defined(USE_ARMV7_ASM)
*((uint32_t *)(code + DISPATCH_FUNC_OFFSET)) = (uint32_t)dispatch;
// Make sure the base address has the Thumb mode bit
assert((uintptr_t)entry->entrypointExec & (uintptr_t)0x1);
// See http://community.arm.com/groups/processors/blog/2010/02/17/caches-and-self-modifying-code
__builtin___clear_cache((char *)entry->entrypointExec - 1,
(char *)entry->entrypointExec + sizeof(STUB_TEMPLATE));
#else
#error "Can't happen -- not implemented"
#endif
@ -213,7 +280,7 @@ void *DefaultDispatchFunc(void)
return NULL;
}
#else // defined(__GNUC__) && (defined(USE_X86_ASM) || defined(USE_X86_64_ASM))
#else // defined(__GNUC__) && USE_ASM
GLVNDentrypointStub glvndGenerateEntrypoint(const char *procName)
{
@ -228,4 +295,4 @@ void glvndUpdateEntrypoints(GLVNDentrypointUpdateCallback callback, void *param)
{
}
#endif // defined(__GNUC__) && (defined(USE_X86_ASM) || defined(USE_X86_64_ASM))
#endif // defined(__GNUC__) && USE_ASM

View file

@ -1,3 +1,32 @@
/*
* Copyright (c) 2015, NVIDIA CORPORATION.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and/or associated documentation files (the
* "Materials"), to deal in the Materials without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Materials, and to
* permit persons to whom the Materials are furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* unaltered in all copies or substantial portions of the Materials.
* Any additions, deletions, or changes to the original source files
* must be clearly indicated in accompanying documentation.
*
* If only executable code is distributed, then the accompanying
* documentation must state that "this software is based in part on the
* work of the Khronos Group."
*
* THE MATERIALS ARE 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
* MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
*/
#ifndef GLVND_GENENTRY_H
#define GLVND_GENENTRY_H

View file

@ -30,7 +30,6 @@
#include "utils_misc.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <sys/mman.h>
@ -240,3 +239,14 @@ void GetTempDirs(const char **dirs)
dirs[count] = NULL;
}
void glvnd_byte_swap16(uint16_t* array, const size_t size)
{
int i;
assert((size % 2) == 0);
for (i = 0; i < size / 2; i++) {
array[i] = (array[i] << 8) | (array[i] >> 8);
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, NVIDIA CORPORATION.
* Copyright (c) 2013-2015, NVIDIA CORPORATION.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and/or associated documentation files (the
@ -31,6 +31,8 @@
#define __UTILS_MISC_H
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
/*
* Various macros which may prove useful in various places
@ -81,4 +83,12 @@ int AllocExecPages(size_t size, void **writePtr, void **execPtr);
*/
void FreeExecPages(size_t size, void *writePtr, void *execPtr);
/*!
* Swaps the bytes of an array.
*
* @param array The array.
* @param size The size in bytes of the array, must be a multiple of 2.
*/
void glvnd_byte_swap16(uint16_t* array, const size_t size);
#endif // !defined(__UTILS_MISC_H)

View file

@ -540,12 +540,62 @@ static void patch_x86_tls(char *writeEntry,
#endif
}
static void patch_armv7_thumb_tsd(char *writeEntry,
const char *execEntry,
int stubSize)
{
#if defined(__arm__)
char *pSawVertex3fv = (char *)&__glXSawVertex3fv;
// Thumb bytecode
char tmpl[] = {
// ldr r0, 1f
0x48, 0x02,
// ldr r1, [r0]
0x68, 0x01,
// add r1, r1, #1
0xf1, 0x01, 0x01, 0x01,
// str r1, [r0]
0x60, 0x01,
// bx lr
0x47, 0x70,
// 1:
0x00, 0x00, 0x00, 0x00,
};
int offsetAddr = sizeof(tmpl) - 4;
if (stubSize < sizeof(tmpl)) {
return;
}
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
glvnd_byte_swap16((uint16_t *)tmpl, offsetAddr);
#endif
*((uint32_t *)(tmpl + offsetAddr)) = (uint32_t)pSawVertex3fv;
// Make sure the base address has the Thumb mode bit
assert((uintptr_t)writeEntry & (uintptr_t)0x1);
// Get the actual beginning of the stub allocation
writeEntry -= 1;
memcpy(writeEntry, tmpl, sizeof(tmpl));
__builtin___clear_cache(writeEntry, writeEntry + sizeof(tmpl));
#else
assert(0); // Should not be calling this
#endif
}
static GLboolean dummyCheckPatchSupported(int type, int stubSize)
{
switch (type) {
case __GLDISPATCH_STUB_X86_64_TLS:
case __GLDISPATCH_STUB_X86_TLS:
case __GLDISPATCH_STUB_X86_64_TSD:
case __GLDISPATCH_STUB_ARMV7_THUMB_TSD:
return GL_TRUE;
default:
return GL_FALSE;
@ -573,6 +623,9 @@ static GLboolean dummyInitiatePatch(int type,
case __GLDISPATCH_STUB_X86_TLS:
patch_x86_tls(writeAddr, execAddr, stubSize);
break;
case __GLDISPATCH_STUB_ARMV7_THUMB_TSD:
patch_armv7_thumb_tsd(writeAddr, execAddr, stubSize);
break;
default:
assert(0);
}

View file

@ -18,10 +18,14 @@ COMMON_CFLAGS = \
-I$(top_srcdir)/include \
-Wno-error=unused-function
COMMON_SOURCES = \
GLX_dummy.c \
$(top_srcdir)/src/util/utils_misc.c
libGLX_dummy_la_CFLAGS = $(COMMON_CFLAGS)
libGLX_dummy_la_SOURCES = GLX_dummy.c
libGLX_dummy_la_SOURCES = $(COMMON_SOURCES)
libGLX_dummy_la_LIBADD = $(top_builddir)/src/util/trace/libtrace.la
libGLX_patchentry_la_CFLAGS = $(COMMON_CFLAGS) -DPATCH_ENTRYPOINTS
libGLX_patchentry_la_SOURCES = GLX_dummy.c
libGLX_patchentry_la_SOURCES = $(COMMON_SOURCES)
libGLX_patchentry_la_LIBADD = $(top_builddir)/src/util/trace/libtrace.la