高版本安卓注入RenderDoc

几年前在公司内部KM分享过如何方便的将RenderDoc内嵌到内部版本中从而方便调试,以及如何向下兼容更低版本的Android系统(RenderDoc Android最低系统要求是6.0)。

手法其实非常简单: 直接将libVkLayer_GLES_RenderDoc.so这个打包进apk中然后手动加载下即可; 这样做额外的好处是不需要依赖其hook系统的机制,所以不需要interceptor-lib,进而压低系统版本要求。

但挺哥在合并这个方案回引擎的时候发现高版本的安卓表现有点奇怪,就一起查了下还有点意思。

高版本安卓内嵌so之后启动闪退

这个问题的表现在于内嵌libVkLayer_GLES_RenderDoc.so之后,同一个apk在低版本安卓能够正常启动并hook上,高版本安卓会直接闪退或者卡死。

遇事不决打LOG,先本地编译了一个DEBUG版本替换进去,看看日志信息:

2023-02-06 15:36:16.893 20537-20537 renderdoc@63ebadfo00000of@ RDOC 020537: [15:36:16]	gl_hooks.cpp( 167) - Error - No function pointer for 'glGetString' while doing replay fallback!

翻了下这部分的代码,猜测是引擎调用glGetString的时候走了RenderDoc的实现,但是RenderDoc内部没有hook系统库所以返回了空指针。

突然想起来这个问题其实在RenderDoc在Android Q上无效遇到过: 如果存在EGL_ANDROID_GLES_layers,那么RenderDoc就不会去hook GL函数,进而导致翻车。

定位问题之后其实就很好办了,直接暴力注释这块代码强制启用Hook:

bool ShouldHookEGL()
{
void *egl_handle = dlopen("libEGL.so", RTLD_LAZY);
PFN_eglQueryString query_string = (PFN_eglQueryString)dlsym(egl_handle, "eglQueryString");
if(!query_string)
{
RDCERR("Unable to find eglQueryString entry point, enabling EGL hooking");
return true;
}

rdcstr ignore_layers = Process::GetEnvVariable("IGNORE_LAYERS");

// if we set IGNORE_LAYERS externally that means the layers are broken or can't be configured, so
// hook EGL in spite of the layers being present
if(ignore_layers.size() >= 1 && ignore_layers[0] == '1')
return true;
/*
const char *eglExts = query_string(EGL_NO_DISPLAY, EGL_EXTENSIONS);

if(eglExts && strstr(eglExts, "EGL_ANDROID_GLES_layers"))
{
RDCLOG("EGL_ANDROID_GLES_layers detected, disabling EGL hooks - GLES layering in effect");
return false;
}
*/
return true;
}

试了下果然可以了~

编译过程补充

按照上面的试验结果,感觉逃不掉本地维护一个fork来编译维护libVkLayer_GLES_RenderDoc.so了。

Windows上的编译环境搭建直接看下Windows上编译Renderdoc Android即可。需要补充的是前文偷懒,默认编译出来的是unstrip的debug版本; 对于实际项目使用记得补上-DSTRIP_ANDROID_LIBRARY=On -DCMAKE_BUILD_TYPE=Release这两个选项:

mkdir build-android64
cd build-android64
cmake -DBUILD_ANDROID=On -DANDROID_ABI=arm64-v8a -DSTRIP_ANDROID_LIBRARY=On -DCMAKE_BUILD_TYPE=Release -G "MinGW Makefiles" ..
mingw32-make -j20

ps. RenderDoc的build流程还有一个有趣的步骤是读取Git信息然后编译进去,所以维护的时候最好跟着release tag走。

更优雅做法的探讨

回过头看,这个问题本身是因为RenderDoc使用的EGL_ANDROID_GLES_layers机制在高版本安卓上是系统推荐的行为; 但对于我们这个使用场景反而需要强制走Hook机制。

其实我也在思考有没有更加优雅的方式,毕竟维护一份fork然后定期编译也是工作量。
从代码来看,其实是会去环境变量里检查IGNORE_LAYERS的——但是这个环境变量如果使用adb去设置略显麻烦(同时也有前文提到的权限问题),如果使用App本身去设置感觉时机上不一定来得及。

回头有空再研究研究Add System Properties吧。