第三章 Android 设备抓帧
Android 设备准备
开发者选项
在进行抓帧前,需要确保 Android 设备已开启开发者选项:
- 进入”设置” → “关于手机”,连续点击”版本号”七次,解锁开发者选项。
- 进入”设置” → “开发者选项”,开启以下选项:
- USB 调试:允许 adb 通过 USB 或网络连接到设备。
- 禁用 Verity(部分设备需要):配合 userdebug/root 构建使用。
adb 连接
通过 USB 连接设备后,验证连接状态:
adb devices正常输出示例:
List of devices attached
AB1234567890 device
若设备显示为 unauthorized,在设备上点击”允许 USB 调试”授权。
若需通过网络连接:
adb tcpip 5555
adb connect <设备IP>:5555userdebug / root 要求
RenderDoc 的 hook 机制依赖以下权限:
- userdebug 构建或已 root 设备:需要
adb shell setenforce 0关闭 SELinux 强制模式,以及adb shell setprop写入系统属性。 - user 构建(未 root):无法设置
gpu_debug_layers等系统属性,抓帧功能受限,一般不支持。
验证构建类型:
adb shell getprop ro.build.type
# 期望输出: userdebug 或 eng验证 root 权限:
adb shell id
# 期望输出包含: uid=0(root)RenderDoc 连接 Android 设备
前置准备
RenderDoc APK(包名 org.renderdoc.renderdoccmd.arm64)需要预先安装在设备上。APK 由项目编译产出,路径为:
build-android/bin/org.renderdoc.renderdoccmd.arm64.apk
安装命令:
adb install -r build-android/bin/org.renderdoc.renderdoccmd.arm64.apk验证安装:
adb shell pm list packages org.renderdoc.renderdoccmd.arm64设备选择器操作步骤
- 启动 PC 端 RenderDoc(
qrenderdoc)。 - 在主界面工具栏点击 “Android Device” 下拉菜单,选择已连接的设备(通过 adb 识别)。
- RenderDoc 会通过 adb forward 建立与设备端 replay server 的通信隧道:
adb forward tcp:38969 localabstract:renderdoc_39920- 设备选择成功后,“Launch Application”对话框中的目标进程列表将从 Android 设备上读取。
选择目标进程
普通 App
对于普通 Android App,通过包名指定目标,例如:
com.example.mygame
hook 机制将通过以下系统属性注入 GPU debug layer:
adb shell setprop debug.vulkan.layers "VK_LAYER_RENDERDOC_Capture"
adb shell settings put global gpu_debug_app com.example.mygame
adb shell settings put global gpu_debug_layers "VK_LAYER_RENDERDOC_Capture"
adb shell settings put global gpu_debug_layer_app org.renderdoc.renderdoccmd.arm64进程重启后,RenderDoc layer 将被自动加载。
SurfaceFlinger 及系统进程
com.android.systemui、surfaceflinger 等系统进程的 hook 方式与普通 App 不同:
- SurfaceFlinger:需要将 RenderDoc layer SO 推送到
/data/local/tmp/,并通过以下属性注入 GLES layer:
adb push <layer.so> /data/local/tmp/
adb shell setprop debug.gles.layers "libVkLayer_rdoc.so"
adb shell setprop debug.gles.layerspath "/data/local/tmp"hook 完成后需要重启 SurfaceFlinger:
adb shell service call SurfaceFlinger 1008- SystemUI:同样需要设置
gpu_debug_layers,并通过杀进程触发重启:
adb shell am force-stop com.android.systemui- 进程切换:从 hook 普通 App 切换到 hook SurfaceFlinger(或反向切换),需要先清理旧的 hook 设置,防止两者冲突。
Hook 注入原理与操作步骤
注入原理
RenderDoc 通过 Android 的 GPU debug layer 机制实现 hook:
- RenderDoc APK 提供 Vulkan/GLES validation layer(
VK_LAYER_RENDERDOC_Capture),作为拦截层插入图形 API 调用链。 - 系统属性
gpu_debug_layers和gpu_debug_app告知系统在目标进程启动时加载指定 layer。 - Layer 加载后建立 abstract socket(
renderdoc_38920),PC 端通过 adb forward 的 TCP 隧道(port 38960)连接进行控制。 - 抓帧指令通过 Target Control 协议发送,layer 在帧边界(
vkQueuePresentKHR或vkFrameBoundaryANDROID)时序列化当前帧的所有 GPU 命令和资源,输出.rdc文件。
关键端口
| 用途 | Abstract Socket | ADB forward TCP |
|---|---|---|
| Target Control (触发抓帧) | renderdoc_38920 | 38960 |
| Remote Server (replay) | renderdoc_39920 | 38969 |
操作步骤
方式一:通过 MCP tool(推荐)
调用 renderdoc-mcp server 的 hook_app tool:
hook_app(package_name="com.example.mygame")tool 内部自动执行以下操作:
- 验证 adb 连接,检查并安装 RenderDoc APK。
- 清理旧 hook 设置(5 个 global settings)。
- 建立 adb forward:
adb forward tcp:38960 localabstract:renderdoc_38920。 - 设置系统属性(setenforce 0、setprop debug.rdoc.*)。
- 根据目标类型(SurfaceFlinger / 普通 App)分支配置。
- 处理 SurfaceFlinger ←> App 的切换场景。
- 清理设备上的旧 .rdc 文件。
- 重启目标进程。
- 轮询 abstract socket 确认 hook 成功(超时 30s)。
方式二:手动 adb 命令
# 1. 清理旧 hook
adb shell settings delete global gpu_debug_app
adb shell settings delete global gpu_debug_layers
adb shell settings delete global gpu_debug_layer_app
adb shell settings delete global enable_gpu_debug_layers
adb shell settings delete global gpu_debug_layers_gles
# 2. 建立 ADB forward
adb forward tcp:38960 localabstract:renderdoc_38920
adb forward tcp:38969 localabstract:renderdoc_39920
# 3. 配置系统属性
adb shell setenforce 0
adb shell setprop debug.vulkan.layers "VK_LAYER_RENDERDOC_Capture"
# 4. 配置 hook 目标(以普通 App 为例)
adb shell settings put global enable_gpu_debug_layers 1
adb shell settings put global gpu_debug_app com.example.mygame
adb shell settings put global gpu_debug_layers "VK_LAYER_RENDERDOC_Capture"
adb shell settings put global gpu_debug_layer_app org.renderdoc.renderdoccmd.arm64
# 5. 重启目标进程
adb shell am force-stop com.example.mygame
# 手动在设备上重新启动 App
# 6. 等待 hook 完成(检查 socket)
adb shell "cat /proc/net/unix | grep renderdoc_38920"触发抓帧
方式一:PC 端 UI 操作
- 在 RenderDoc 主界面,目标进程 hook 成功后,“Capture” 按钮(相机图标)变为可点击状态。
- 点击 “Capture Frame(s)” 按钮,输入抓帧帧数(默认 1 帧)。
- 等待设备端完成当前帧渲染并输出 .rdc 文件。
- PC 端自动下载并在帧列表中显示。
方式二:命令行触发(raw TCP)
在 adb forward 已建立的情况下,通过向 TCP 38960 端口发送 ePacket_Cycled(DumpMem) 包(值为 16)触发:
python3 -c "
import socket, struct
sock = socket.create_connection(('127.0.0.1', 38960), timeout=30)
sock.sendall(struct.pack('<iii', 16, 0, 0))
sock.recv(4096)
sock.close()
"方式三:MCP tool(推荐)
调用 renderdoc-mcp server 的 trigger_capture tool:
trigger_capture(num_frames=1, local_path="out/20260610_1430_capture")tool 内部通过 RenderDoc Target Control 协议完成完整流程:
EnumerateRemoteTargets发现活跃的目标 ident。CreateTargetControl连接并握手。TriggerCapture(num_frames)触发抓帧。- 等待
NewCapture消息(超时 120s)。 CopyCapture将 .rdc 文件下载到本地local_path。- 等待
CaptureCopied确认后Shutdown()关闭连接。
拉取 .rdc 文件到本地
通过 MCP tool(推荐)
trigger_capture tool 已内置 .rdc 拉取逻辑,完成后直接返回本地路径,无需手动操作。
返回示例:
{
"status": "success",
"captures": [
{
"local_path": "out/20260610_1430_capture/frame_00001.rdc",
"remote_path": "/sdcard/Android/data/org.renderdoc.renderdoccmd.arm64/files/frame_00001.rdc",
"frame_number": 1,
"byte_size": 52428800
}
]
}手动拉取
# 查找设备上最新的 rdc 文件
REMOTE_RDC=$(adb shell ls -t /sdcard/Android/data/org.renderdoc.renderdoccmd.arm64/files/*.rdc 2>/dev/null | head -1)
# 建立本地输出目录
OUT_DIR="out/$(date +%Y%m%d_%H%M)_capture"
mkdir -p "$OUT_DIR"
# 拉取文件
adb pull "$REMOTE_RDC" "$OUT_DIR/"常见失败原因及排查方法
1. ADB 连接失败
现象:adb devices 返回空列表或设备显示 offline。
排查:
adb kill-server
adb start-server
adb devices检查 USB 线缆和驱动,或确认无线 adb 连接参数正确。
2. RenderDoc APK 未安装或版本不匹配
现象:hook 步骤中无法找到 layer,进程启动后 socket 未出现。
排查:
adb shell pm list packages org.renderdoc.renderdoccmd.arm64若无输出,重新安装 APK:
adb install -r build-android/bin/org.renderdoc.renderdoccmd.arm64.apk3. SELinux 阻止 layer 加载
现象:hook 命令执行成功,但进程启动后 socket 未建立,logcat 中有 avc: denied 日志。
排查:
adb shell setenforce 0
adb shell getenforce # 期望输出: Permissive同时检查 dmesg 或 logcat:
adb logcat -s "auditd" | grep denied4. Hook 超时(socket 未出现)
现象:等待 30s 后仍无法在 /proc/net/unix 中找到 renderdoc_38920。
排查:
# 检查目标进程是否正在运行
adb shell ps | grep com.example.mygame
# 检查 hook 属性是否生效
adb shell settings get global gpu_debug_app
adb shell settings get global gpu_debug_layers
# 查看 layer 加载日志
adb logcat | grep -i "renderdoc\|RDOC\|vulkan.*layer"常见原因:
- 目标进程未重启(需要彻底 force-stop 后重新启动)。
gpu_debug_app包名与实际进程包名不一致。- 设备构建为 user 类型,无法写入 global settings(需要 userdebug/root)。
5. 抓帧超时(trigger_capture 等待 NewCapture 超时)
现象:触发命令发出后,120s 内未收到 NewCapture 消息。
排查:
# 确认 ADB forward 有效
adb forward --list | grep 38960
# 确认目标进程仍在运行
adb shell ps | grep com.example.mygame
# 检查设备存储空间
adb shell df /sdcard常见原因:
- 目标进程已崩溃或退出,导致 socket 断开。
- 设备存储空间不足,.rdc 文件无法写入。
- 进程处于后台被系统冻结,GPU 帧无法触发。
6. SurfaceFlinger 抓帧特殊问题
现象:hook SurfaceFlinger 后,设备画面变黑或 SF 无法重启。
排查:
# 检查 layer SO 是否推送成功
adb shell ls -la /data/local/tmp/*.so
# 检查 GLES layer 属性
adb shell getprop debug.gles.layers
adb shell getprop debug.gles.layerspath
# 重启 SurfaceFlinger
adb shell service call SurfaceFlinger 1008注意:SurfaceFlinger hook 失败可能导致整个显示系统异常,需要重启设备恢复。
7. usap64 与 SurfaceFlinger 抢占冲突
现象:多进程场景下,抓帧目标被意外切换,或抓到的不是目标进程的帧。
说明:usap64(Unspecialized App Process)在某些系统版本上会与 SurfaceFlinger 的 hook 配置产生冲突。解决方案参考代码提交 5506445(修复 usap64 抢占 SF 抓帧的问题)。