第三章 Android 设备抓帧

Android 设备准备

开发者选项

在进行抓帧前,需要确保 Android 设备已开启开发者选项:

  1. 进入”设置” “关于手机”,连续点击”版本号”七次,解锁开发者选项。
  2. 进入”设置” “开发者选项”,开启以下选项:
    • USB 调试:允许 adb 通过 USB 或网络连接到设备。
    • 禁用 Verity(部分设备需要):配合 userdebug/root 构建使用。

adb 连接

通过 USB 连接设备后,验证连接状态:

adb devices

正常输出示例:

List of devices attached
AB1234567890    device

若设备显示为 unauthorized,在设备上点击”允许 USB 调试”授权。

若需通过网络连接:

adb tcpip 5555
adb connect <设备IP>:5555

userdebug / 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

设备选择器操作步骤

  1. 启动 PC 端 RenderDoc(qrenderdoc)。
  2. 在主界面工具栏点击 “Android Device” 下拉菜单,选择已连接的设备(通过 adb 识别)。
  3. RenderDoc 会通过 adb forward 建立与设备端 replay server 的通信隧道:
adb forward tcp:38969 localabstract:renderdoc_39920
  1. 设备选择成功后,“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.systemuisurfaceflinger 等系统进程的 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:

  1. RenderDoc APK 提供 Vulkan/GLES validation layer(VK_LAYER_RENDERDOC_Capture),作为拦截层插入图形 API 调用链。
  2. 系统属性 gpu_debug_layersgpu_debug_app 告知系统在目标进程启动时加载指定 layer。
  3. Layer 加载后建立 abstract socket(renderdoc_38920),PC 端通过 adb forward 的 TCP 隧道(port 38960)连接进行控制。
  4. 抓帧指令通过 Target Control 协议发送,layer 在帧边界(vkQueuePresentKHRvkFrameBoundaryANDROID)时序列化当前帧的所有 GPU 命令和资源,输出 .rdc 文件。

关键端口

用途Abstract SocketADB forward TCP
Target Control (触发抓帧)renderdoc_3892038960
Remote Server (replay)renderdoc_3992038969

操作步骤

方式一:通过 MCP tool(推荐)

调用 renderdoc-mcp server 的 hook_app tool:

hook_app(package_name="com.example.mygame")

tool 内部自动执行以下操作:

  1. 验证 adb 连接,检查并安装 RenderDoc APK。
  2. 清理旧 hook 设置(5 个 global settings)。
  3. 建立 adb forward:adb forward tcp:38960 localabstract:renderdoc_38920
  4. 设置系统属性(setenforce 0、setprop debug.rdoc.*)。
  5. 根据目标类型(SurfaceFlinger / 普通 App)分支配置。
  6. 处理 SurfaceFlinger > App 的切换场景。
  7. 清理设备上的旧 .rdc 文件。
  8. 重启目标进程。
  9. 轮询 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 操作

  1. 在 RenderDoc 主界面,目标进程 hook 成功后,“Capture” 按钮(相机图标)变为可点击状态。
  2. 点击 “Capture Frame(s)” 按钮,输入抓帧帧数(默认 1 帧)。
  3. 等待设备端完成当前帧渲染并输出 .rdc 文件。
  4. 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 协议完成完整流程:

  1. EnumerateRemoteTargets 发现活跃的目标 ident。
  2. CreateTargetControl 连接并握手。
  3. TriggerCapture(num_frames) 触发抓帧。
  4. 等待 NewCapture 消息(超时 120s)。
  5. CopyCapture 将 .rdc 文件下载到本地 local_path
  6. 等待 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.apk

3. SELinux 阻止 layer 加载

现象:hook 命令执行成功,但进程启动后 socket 未建立,logcat 中有 avc: denied 日志。

排查

adb shell setenforce 0
adb shell getenforce  # 期望输出: Permissive

同时检查 dmesg 或 logcat:

adb logcat -s "auditd" | grep denied

4. 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 抓帧的问题)。