在运行 Android 10(API 级别 29)及更高版本的设备上,可以使用 OpenGL ES (GLES) 分层功能。可调试的应用可以从其 APK、其基本目录或选定的图层 APK 加载 GLES 图层。
GLES 图层的用法与 Vulkan 验证层的用法相似。
要求
GLES 图层仅在 GLES 版本 2.0+ 上受支持。
图层初始化
填充标准入口点后,EGL 加载程序会实例化 GLES LayerLoader
。如果启用了调试层,则和 Vulkan 加载程序一样,LayerLoader
会在指定目录中查找图层。
如果启用了分层,LayerLoader
会搜索并枚举指定的图层列表。图层列表中包含了一系列文件名,这些文件名用冒号分隔。
LayerLoader
会按照您指定的顺序遍历图层,因此应用下面就是第一个图层。对于每个图层,LayerLoader
会跟踪 AndroidGLESLayer_Initialize
和 AndroidGLESLayer_GetProcAddress
入口点。这些图层必须提供这些接口才能被加载。
typedef void* (*PFNEGLGETNEXTLAYERPROCADDRESSPROC)(void*, const char*); void* AndroidGLESLayer_Initialize(void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address))
AndroidGLESLayer_Initialize()
提供要使用的图层的标识符 (layer_id
) 和一个入口点,调用该入口点可以查找该图层下面的函数。可以按照以下代码示例所示来使用该入口点:
const char* func = "eglFoo"; void* gpa = get_next_layer_proc_address(layer_id, func);
AndroidGLESLayer_GetProcAddress
会获取图层执行完毕后应调用的链中下一个调用的地址。如果只有一个图层,对于大多数函数而言,next
会直接指向驱动程序。
typedef __eglMustCastToProperFunctionPointerType EGLFuncPointer; void* AndroidGLESLayer_GetProcAddress(const char *funcName, EGLFuncPointer next)
对于找到的每一个图层,GLES LayerLoader
都会调用 AndroidGLESLayer_Initialize
,遍历 libEGL
的函数列表,然后针对所有已知函数调用 AndroidGLESLayer_GetProcAddress
。如何跟踪下一个地址由图层决定。如果图层截获了某个函数,它会跟踪该函数的地址。如果图层没有截获到函数,AndroidGLESLayer_GetProcAddress
会返回它之前所收到的函数地址。然后,LayerLoader
会更新函数钩子列表,使其指向图层的入口点。
这些图层不需要对 AndroidGLESLayer_Initialize
和 get_next_layer_proc_address
提供的信息进行任何操作,但提供这些数据可以让现有图层(如 Android GPU 检查器 和 RenderDoc)更容易支持 Android。利用这些数据,图层可以独立查找函数,而不必等待调用 AndroidGLESLayer_GetProcAddress
。如果图层选择在加载程序查询所有入口点之前对其自身进行初始化,它们必须使用 get_next_layer_proc_address
。必须沿着链向下将 eglGetProcAddress
传递到平台。
放置图层
GLES LayerLoader
按以下优先级在以下位置搜索图层:
1. 系统根位置
这需要 root 访问权限
adb root adb disable-verity adb reboot adb root adb shell setenforce 0 adb shell mkdir -p /data/local/debug/gles adb push <layer>.so /data/local/debug/gles/
2. 应用的基本目录
目标应用必须是可调试的,或者您必须拥有 root 访问权限:
adb push libGLTrace.so /data/local/tmp adb shell run-as com.android.gl2jni cp /data/local/tmp/libGLTrace.so . adb shell run-as com.android.gl2jni ls | grep libGLTrace libGLTrace.so
3. 外部 APK
确定目标应用的 ABI,然后安装包含您要加载的图层的 APK:
adb install --abi armeabi-v7a layers.apk
4. 在目标应用的 APK 中
以下代码示例展示了如何在应用 APK 中放置图层:
$ jar tf GLES_layers.apk lib/arm64-v8a/libGLES_glesLayer1.so lib/arm64-v8a/libGLES_glesLayer2.so lib/arm64-v8a/libGLES_glesLayer3.so lib/armeabi-v7a/libGLES_glesLayer1.so lib/armeabi-v7a/libGLES_glesLayer2.so lib/armeabi-v7a/libGLES_glesLayer3.so resources.arsc AndroidManifest.xml META-INF/CERT.SF META-INF/CERT.RSA META-INF/MANIFEST.MF
启用图层
您可以按应用启用 GLES 图层,也可全局启用 GLES 图层。针对应用的设置会在重启后保留,而全局属性则会在重启时被清除。
Android 的安全模型和政策与其他平台有很大不同。如需加载外部层,必须满足以下条件之一:
目标应用的清单文件包含以下元数据元素(仅适用于以 Android 11(API 级别 30)或更高版本为目标的应用):
<meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true" />
您应使用此选项对应用进行剖析。
目标应用是可调试的。此选项可为您提供更多调试信息,但可能会降低应用性能。
目标应用在授予 root 访问权限的操作系统的用户调试 build 上运行。
按应用启用层:
# Enable layers adb shell settings put global enable_gpu_debug_layers 1 # Specify target application adb shell settings put global gpu_debug_app <package_name> # Specify layer list (from top to bottom) # Layers are identified by their filenames, such as "libGLLayer.so" adb shell settings put global gpu_debug_layers_gles <layer1:layer2:layerN> # Specify packages to search for layers adb shell settings put global gpu_debug_layer_app <package1:package2:packageN>
按应用禁用图层:
# Delete the global setting that enables layers adb shell settings delete global enable_gpu_debug_layers # Delete the global setting that selects target application adb shell settings delete global gpu_debug_app # Delete the global setting that specifies layer list adb shell settings delete global gpu_debug_layers_gles # Delete the global setting that specifies layer packages adb shell settings delete global gpu_debug_layer_app
全局启用图层:
# This attempts to load layers for all applications, including native # executables adb shell setprop debug.gles.layers <layer1:layer2:layerN>
创建图层
图层必须公开 EGL 加载程序初始化中所述的以下两个函数:
AndroidGLESLayer_Initialize AndroidGLESLayer_GetProcAddress
被动图层
对于只截获少量函数的图层,采用被动初始化的图层效果最佳。被动初始化的图层会等待 GLES LayerLoader
初始化其需要的函数。
以下代码示例展示了如何创建被动图层。
namespace { std::unordered_map<std::string, EGLFuncPointer> funcMap; EGLAPI EGLBoolean EGLAPIENTRY glesLayer_eglChooseConfig ( EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config) { EGLFuncPointer entry = funcMap["eglChooseConfig"]; typedef EGLBoolean (*PFNEGLCHOOSECONFIGPROC)( EGLDisplay, const EGLint*, EGLConfig*, EGLint, EGLint*); PFNEGLCHOOSECONFIGPROC next = reinterpret_cast<PFNEGLCHOOSECONFIGPROC>(entry); return next(dpy, attrib_list, configs, config_size, num_config); } EGLAPI EGLFuncPointer EGLAPIENTRY eglGPA(const char* funcName) { #define GETPROCADDR(func) if(!strcmp(funcName, #func)) { \ return (EGLFuncPointer)glesLayer_##func; } GETPROCADDR(eglChooseConfig); // Don't return anything for unrecognized functions return nullptr; } EGLAPI void EGLAPIENTRY glesLayer_InitializeLayer( void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address) { // This function is purposefully empty, since this layer does not proactively // look up any entrypoints } EGLAPI EGLFuncPointer EGLAPIENTRY glesLayer_GetLayerProcAddress( const char* funcName, EGLFuncPointer next) { EGLFuncPointer entry = eglGPA(funcName); if (entry != nullptr) { funcMap[std::string(funcName)] = next; return entry; } return next; } } // namespace extern "C" { __attribute((visibility("default"))) EGLAPI void AndroidGLESLayer_Initialize( void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address) { return (void)glesLayer_InitializeLayer(layer_id, get_next_layer_proc_address); } __attribute((visibility("default"))) EGLAPI void* AndroidGLESLayer_GetProcAddress( const char *funcName, EGLFuncPointer next) { return (void*)glesLayer_GetLayerProcAddress(funcName, next); } }
主动图层
对于需要预先完全初始化的更规范的图层,或者需要查找 EGL 加载程序不知道的扩展的图层,则需要采用主动图层初始化。该图层使用 AndroidGLESLayer_Initialize
提供的 get_next_layer_proc_address
来查找函数。该图层仍然必须响应加载程序发送的 AndroidGLESLayer_GetProcAddress
请求,以使平台知道将调用传送到何处。以下代码示例展示了如何创建主动图层。
namespace { std::unordered_map<std::string, EGLFuncPointer> funcMap; EGLAPI EGLBoolean EGLAPIENTRY glesLayer_eglChooseConfig ( EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config) { EGLFuncPointer entry = funcMap["eglChooseConfig"]; typedef EGLBoolean (*PFNEGLCHOOSECONFIGPROC)( EGLDisplay, const EGLint*, EGLConfig*, EGLint, EGLint*); PFNEGLCHOOSECONFIGPROC next = reinterpret_cast<PFNEGLCHOOSECONFIGPROC>(entry); return next(dpy, attrib_list, configs, config_size, num_config); } EGLAPI EGLFuncPointer EGLAPIENTRY eglGPA(const char* funcName) { #define GETPROCADDR(func) if(!strcmp(funcName, #func)) { \ return (EGLFuncPointer)glesLayer_##func; } GETPROCADDR(eglChooseConfig); // Don't return anything for unrecognized functions return nullptr; } EGLAPI void EGLAPIENTRY glesLayer_InitializeLayer( void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address) { // Note: This is where the layer would populate its function map with all the // functions it cares about const char* func = “eglChooseConfig”; funcMap[func] = get_next_layer_proc_address(layer_id, func); } EGLAPI EGLFuncPointer EGLAPIENTRY glesLayer_GetLayerProcAddress( const char* funcName, EGLFuncPointer next) { EGLFuncPointer entry = eglGPA(funcName); if (entry != nullptr) { return entry; } return next; } } // namespace extern "C" { __attribute((visibility("default"))) EGLAPI void AndroidGLESLayer_Initialize( void* layer_id, PFNEGLGETNEXTLAYERPROCADDRESSPROC get_next_layer_proc_address) { return (void)glesLayer_InitializeLayer(layer_id, get_next_layer_proc_address); } __attribute((visibility("default"))) EGLAPI void* AndroidGLESLayer_GetProcAddress( const char *funcName, EGLFuncPointer next) { return (void*)glesLayer_GetLayerProcAddress(funcName, next); } }