在android平台上(其他平台也差不多),OpenGL es API的调用是通过一个一个跳转表实现的。这个跳转表的首地址被保存到当前线程的TLS ( Thread Local Storage)中。
我们来看一下Android系统中相关的源代码。我这里引用的是android 4.4.2的代码,不同版本中源码文件所在的位置略有不同。
先来看frameworks\native\opengl\libs\GLES2\gl2.cpp
#if defined(__arm__) && !USE_SLOW_BINDING #define GET_TLS(reg) "mrc p15, 0, " #reg ", c13, c0, 3 \n" #define API_ENTRY(_api) __attribute__((noinline)) _api #define CALL_GL_API(_api, ...) \ asm volatile( \ GET_TLS(r12) \ "ldr r12, [r12, %[tls]] \n" \ "cmp r12, #0 \n" \ "ldrne pc, [r12, %[api]] \n" \ : \ : [tls] "J"(TLS_SLOT_OPENGL_API*4), \ [api] "J"(__builtin_offsetof(gl_hooks_t, gl._api)) \ : \ ); #elif defined(__mips__) && !USE_SLOW_BINDING #define API_ENTRY(_api) __attribute__((noinline)) _api #define CALL_GL_API(_api, ...) \ register unsigned int _t0 asm("t0"); \ register unsigned int _fn asm("t1"); \ register unsigned int _tls asm("v1"); \ register unsigned int _v0 asm("v0"); \ asm volatile( \ ".set push\n\t" \ ".set noreorder\n\t" \ ".set mips32r2\n\t" \ "rdhwr %[tls], $29\n\t" \ "lw %[t0], %[OPENGL_API](%[tls])\n\t" \ "beqz %[t0], 1f\n\t" \ " move %[fn],$ra\n\t" \ "lw %[fn], %[API](%[t0])\n\t" \ "movz %[fn], $ra, %[fn]\n\t" \ "1:\n\t" \ "j %[fn]\n\t" \ " move %[v0], $0\n\t" \ ".set pop\n\t" \ : [fn] "=c"(_fn), \ [tls] "=&r"(_tls), \ [t0] "=&r"(_t0), \ [v0] "=&r"(_v0) \ : [OPENGL_API] "I"(TLS_SLOT_OPENGL_API*4), \ [API] "I"(__builtin_offsetof(gl_hooks_t, gl._api)) \ : \ ); #else #define API_ENTRY(_api) _api #define CALL_GL_API(_api, ...) \ gl_hooks_t::gl_t const * const _c = &getGlThreadSpecific()->gl; \ if (_c) return _c->_api(__VA_ARGS__); #endif #define CALL_GL_API_RETURN(_api, ...) \ CALL_GL_API(_api, __VA_ARGS__) \ return 0; extern "C" { #include "gl3_api.in" #include "gl2ext_api.in" #include "gl3ext_api.in" }列举几行gl3_api.in中的代码,宏展开后其实就是一个个GLES的函数实现
void API_ENTRY(glActiveTexture)(GLenum texture) { CALL_GL_API(glActiveTexture, texture); } void API_ENTRY(glAttachShader)(GLuint program, GLuint shader) { CALL_GL_API(glAttachShader, program, shader); } void API_ENTRY(glBindAttribLocation)(GLuint program, GLuint index, const GLchar* name) { CALL_GL_API(glBindAttribLocation, program, index, name); }以上的代码相当于定义了所有GLES API的实现,只不过这些实现都是简单的跳转。在arm平台下的实现很简单,首先通过协处理指令获取TLS指针,通过查表找到相应的函数指针,直接将pc跳转到该地址,这样可以省去函数调用的开销。为了更清楚的了解函数跳转表的结构,我们来看一下getGlThreadSpecific的实现。位置在frameworks\native\opengl\libs\hooks.h
struct gl_hooks_t { struct gl_t { #include "entries.in" } gl; struct gl_ext_t { __eglMustCastToProperFunctionPointerType extensions[MAX_NUMBER_OF_GL_EXTENSIONS]; } ext; }; #undef GL_ENTRY #undef EGL_ENTRY EGLAPI void setGlThreadSpecific(gl_hooks_t const *value); // We have a dedicated TLS slot in bionic inline gl_hooks_t const * volatile * get_tls_hooks() { volatile void *tls_base = __get_tls(); gl_hooks_t const * volatile * tls_hooks = reinterpret_cast<gl_hooks_t const * volatile *>(tls_base); return tls_hooks; } inline EGLAPI gl_hooks_t const* getGlThreadSpecific() { gl_hooks_t const * volatile * tls_hooks = get_tls_hooks(); gl_hooks_t const* hooks = tls_hooks[TLS_SLOT_OPENGL_API]; return hooks; }其中entries.in是GLES的API的列表。TLS_SLOT_OPENGL_API在bionic\libc\private\bionic_tls.h中定义,值为3。即函数跳转表的结构是这样的。
有一点需要注意的是,API跳转表的首地址指针是在eglMakeCurrent调用中写入到当前线程的TLS中的,与此同时如果当前的context正在被另一个线程使用,那么前一个线程的TLS中API跳转表的指针会被置空。EGL就是通过这种方式实现一个context在同一时刻只能被一个线程使用的。
基于以上的信息,我们可以很容易的将GLES的调用重定向到我们自己的函数中来,即修改跳转表中的函数地址即可。代码如下
struct gl_hooks_t { struct gl_t { void* foo1; // glActiveShaderProgramEXT void* foo2; // glActiveTexture } gl; }; GL_APICALL void GL_APIENTRY my_glActiveTexture(GLenum texture) { LogInfo("my_glActiveTexture %u", texture); } #define TLS_SLOT_OPENGL_API 3 void glesApiHookTest() { void *tls_base = NULL; asm volatile( "mrc p15, 0, r12, c13, c0, 3" "\n\t" "mov %0, r12" "\n\t" : "=r" (tls_base) ); LogInfo("tls_base %p", tls_base); if (tls_base == NULL) { return; } gl_hooks_t * volatile * tls_hooks = reinterpret_cast<gl_hooks_t * volatile *>(tls_base); gl_hooks_t * hooks = tls_hooks[TLS_SLOT_OPENGL_API]; LogInfo("hooks %p", hooks); void* origFunPtr = hooks->gl.foo2; // function list in android code/frameworks/Native/Opengl/Libs/Entries.in hooks->gl.foo2 = (void*)my_glActiveTexture; // will call to "my_glActiveTexture" function glActiveTexture(999); hooks->gl.foo2 = origFunPtr; }更彻底一些的做法是直接修改TLS中跳转表的入口地址,一次性hook所有的API。
那么现在问题来了,我们为啥要hook GLES的API呢?嗯,典型的应用场景包括: