[OpenVR] OpenVR 驱动开发调试的方法小记

发布时间 2023-12-05 17:38:36作者: dousha99

OpenVR 驱动开发调试方法

VR 也许是下一个风口,也许只是一场耀眼的烟花晚会。这年轻的平台还有许多未成形的构建,其中恰好就包括一个完整的调试架构。这使得 VR 开发,尤其是驱动层的开发,变得十分得痛苦。

因为我个人的习惯,我会尽可能避免调用 Visual Studio. 但是由于在 Windows 下开发,Visual Studio 的一系列工具是不可能绕开的。所以我们仍然需要安装以下部件:

  • Visual Studio Community 20xx - 包含 MSVC 编译器、必要的库和构建工具
  • Windows SDK - 这个是默认没有勾选的,需要手动勾选,主要用到里面的 WinDbg 经典版
  • WinDbg - 调试工具

完成以上安装后,你应该可以在开始菜单找到这些项目:

  • Visual Studio 20xx
  • WinDbg
  • Windows Kits

SteamVR 的结构

SteamVR 2.1.x 的结构如下(和调试无关的部分已经略去):

Steam/
|__ logs/
|   |_ vrserver.previous.txt - vrserver 日志信息,rotate 出去的版本
|   |_ vrserver.txt - vrserver 日志信息
|
|__ steamapps/
    |__ common/
        |__ SteamVR/
            |__ bin/
                |_ vrpathreg.exe - 注册和反注册驱动
                |_ vrstartup.exe - 负责启动 VR 环境
                |_ vrserver.exe - 负责运行 VR 环境

使用自己的日志系统

因为是驱动开发,所以 printf 调试大法是逃不开的。SteamVR SDK 提供了 vr::VRDriverLog()->Log(const char *) 函数来向它的日志里输出信息。

抛开这个东西压根没有格式化输出不谈;如果你的测试对象包含 PICO 头显,那么使用自己的日志系统几乎是必须项目。截至 2023-12-04, PICO 头显在进行串流的时候会疯狂触发错误,导致整个 SteamVR 日志里面除了它的信息别的啥都看不见。而且由于触发的错误太多,在性能不是很好的机器上打开日志就会导致 SteamVR 卡死。

尽管我们可以关掉 SteamVR 去读 logs/ 下的文件,但是由于 SteamVR 的日志滚动空间很小,你感兴趣的调试信息可能已经被 PICO 的错误信息冲掉了,两份文件里全都是报错。

我们要调试的目标

SteamVR 的驱动以 DLL 的形式加载,而 SteamVR 本身的启动就是一个很繁复的过程。在 Steam 验证程序的授权等等之后,由 vrstartup.exe 开始设置各项运行环境。

完成环境设置后,vrstartup.exe 会启动 vrserver.exe 进行后续的工作,同时自身会退出。这就导致我们无法直接将 vrstartup.exe 设置为调试目标。在 Steam 的官方开发文档中,提到了可以通过 Microsoft Child Process Debugging Power Tool 来进行调试,但是由于我的项目使用了 CMake, 而且 Visual Studio 对于使用 CMake 的项目的支持还是有一些问题,所以没法用这个工具调试。

如果我们直接将 vrserver.exe 作为调试目标启动,它会由于环境设置不正确而直接退出。我实在没有闲工夫去逆向 vrstartup.exe 是怎么给 vrserver.exe 设置环境的,所以我们需要另寻他法。

调试 vrserver.exe

既然我们没法直接启动 vrserver.exe, 那么我们可以等这个程序启动之后再挂调试器。幸而 vrserver.exe 本身没有任何反调试措施,所以这个做法是完全可行的。

首先启动 SteamVR. 等待 SteamVR 启动且稳定后,打开 WinDbg 的 Attach to Process 功能,选择 vrserver.exe 即可开始调试。

img

在 WinDbg 中,方法名通过 模块名!方法全名 表示。比如你的驱动的 DLL 文件为 driver_test.dll, 那么你的驱动获取函数就为 driver_test!HmdDriverFactory. 如果你不确定你的模块名时什么,可以用 lm 指令列出所有加载的 DLL 文件。如果你不确定你的方法名是什么,则可以用 x 指令列出模块内的符号。x 指令支持通配符,要列出模块内的所有符号,可以使用 x driver_test!*.

但很多时候我们会遇到 SteamVR 一启动就崩溃的情况。以及许多头显在接入之后都会重启 SteamVR, 如果我们只是简单地附加调试器,上述的这些问题就无法调试了。不过我们可以通过 WinDbg 的 Postmortem debugging 能力来抓住程序崩溃的瞬间,进而展开调试。

img

但是 SteamVR 2.x 会自动检测崩溃,而且会控制 vrserver.exe 自动退出和重启,导致 WinDbg 的崩溃现场调试没法正确地附加到 vrserver.exe 上。如果我们需要抓一个一启动就崩溃的错误,我们必须想别的办法。

通过 WinDbg 的子进程调试 vrstartup.exe

WinDbg 本身也支持子进程调试。当我们启动 vrstartup.exe 的调试时,使用

.childdbg 1

指令即可要求 WinDbg 调试由该进程启动的所有子进程。

但是 vrstartup.exe 启动的不仅仅是 vrserver.exe. 还有一大堆和我们调试无关的进程,比如 vrmonitor.exe, 也会随着启动。这使得我们需要辨识具体哪个调试会话是 vrserver.exe 的。而且这些进程之间还存在等待超时,如果我们在调试中断状态操作太久,vrserver.exe 会被判定无法启动而被守护进程杀掉重启,导致调试会话丢失。

通过 GFlags 设置启动时调试 vrserver.exe

好在我们可以通过 GFlags 来设置当一个程序启动时立刻附加调试器。在 Windows Kits 中存在 GFlags 程序,打开对应的 GFlags x86/x64 程序,切换到 Image File 选项卡;在 Image 栏目中键入 vrserver.exe 并按下 Tab 键加载;勾选 Debugger 并填入 WinDbg 经典版的路径即可。应用保存之后,vrserver.exe 启动时便会直接挂载调试器。

img

不过 SteamVR 也仍然不是省油的灯:即使此时有调试器正在调试 vrserver.exe, SteamVR 守护程序也会杀掉该进程,导致崩溃现场的丢失。所以通过这个方法抓取错误时,一定要第一时间通过 .dump 命令保存崩溃现场。从崩溃到进程被杀掉有不到十秒的时间,所以手速上还有一定要求。

完成调试后,仍然需要通过 GFlags 工具取消对于 vrserver.exe 的调试。做法和配置调试相仿,只是要取消勾选 Debugger 项。