您的位置:首页 > 移动开发 > Android开发

在android平台hook OpenGL es的API

2016-02-12 22:28 1606 查看
在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呢?嗯,典型的应用场景包括:

外挂
Debug
对第三方应用/引擎做一些深度定制
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: