在android平台hook OpenGL es的API

在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。即函数跳转表的结构是这样的。

在android平台hook OpenGL es的API_第1张图片


有一点需要注意的是,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呢?嗯,典型的应用场景包括:

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


你可能感兴趣的:(android,OpenGL)