在android平台hook OpenGL es的API

来源:互联网 发布:淘宝账号登录不了 编辑:程序博客网 时间:2024/06/18 14:40

在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_ENTRYEGLAPI void setGlThreadSpecific(gl_hooks_t const *value);// We have a dedicated TLS slot in bionicinline 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;// glActiveShaderProgramEXTvoid* foo2;// glActiveTexture} gl;};GL_APICALL void GL_APIENTRY my_glActiveTexture(GLenum texture){LogInfo("my_glActiveTexture %u", texture);}#define TLS_SLOT_OPENGL_API 3void 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.inhooks->gl.foo2 = (void*)my_glActiveTexture;// will call to "my_glActiveTexture" functionglActiveTexture(999);hooks->gl.foo2 = origFunPtr;}
更彻底一些的做法是直接修改TLS中跳转表的入口地址,一次性hook所有的API。

那么现在问题来了,我们为啥要hook GLES的API呢?嗯,典型的应用场景包括:

  1. 外挂
  2. Debug
  3. 对第三方应用/引擎做一些深度定制


0 0
原创粉丝点击