修复PhysX Visual Debugger复杂场景闪退

Author Avatar
Kanglai Qian 11月 30, 2024

最近忙于各种优化,有遇到PxScene::simulate()耗时很长的情况——这时的第一反应就是掏出PVD,想看下是不是场景里是不是又被摆了一堆奇怪的玩意儿。
结果在游戏里Connect Debugger后、PVD直接原地crash,令人尴尬。尝试了下直接在游戏里保存出pvd文件倒是没问题,只不过打开时依然闪退。
感觉就像某些场景里有脏东西,一碰就炸;而另一些场景则是可以正常使用PVD查看。

鉴于PVD本身是没有源代码的,先找找有无平替:

  • OmniPVD号称是PVD的替代品

    The PhysX Omniverse Visual Debugger (OmniPVD), is the next iteration of PhysX Visual Debugger (PVD), and it consists of a library that is used to both record PhysX simulations as well as to read them back.

  • OpenPVD来自毅哥的推荐,作者开坑之初也是因为PVD不开源+N久没更新了,不过看上去项目不火有点心虚…

找雨桐帮忙快速试了手: OmniPVD和PhysX不兼容; OpenPVD虽然没有图形界面,却有意外之喜——使用filter处理pvd文件之后,就可以用PVD打开了!
这样虽然不知道脏东西是什么,但是好歹能看到一些数据了,同时也说明pvd文件本身是合法的。

一时陷入僵局,只能回头看下PVD的crash堆栈有无线索——emmm看上去是准备绘制的时候出幺蛾子:

==============================================================================
A Program Fault occurred:
Error code 0xC0000005: EXCEPTION_ACCESS_VIOLATION
Address: 0x00007FFF5A1111FB
Flags: 0x00000000
==============================================================================
== STACK TRACE ==
==============================================================================
function: memcmp at 0x7FFF5A1111FB
*** 0 called from 0x00007FFF5A1111FB STACK 0x000000BF190FA418 AddrReturn 0x00007FF7C7CAFEEB
function: OpenGLWidget::addGeometry at 0x7FF7C7CAFEEB
*** 1 called from 0x00007FF7C7CAFEEB STACK 0x000000BF190FA430 AddrReturn 0x00007FF7C7CCAB5F
function: RenderTranslator::prepareTriangleMesh at 0x7FF7C7CCAB5F
*** 2 called from 0x00007FF7C7CCAB5F STACK 0x000000BF190FA4B0 AddrReturn 0x00007FF7C7CEBFB2
function: PVDRenderer::loadGraphicsFromFrame at 0x7FF7C7CEBFB2
*** 3 called from 0x00007FF7C7CEBFB2 STACK 0x000000BF190FA540 AddrReturn 0x00007FF7C7CA01EE
function: PVDMainWindow::playFrame at 0x7FF7C7CA01EE
*** 4 called from 0x00007FF7C7CA01EE STACK 0x000000BF190FA740 AddrReturn 0x00007FF7C7CF556C

而且为啥这个堆栈解析的这么清晰?翻了下PVD安装文件夹,看到了PVDNext.pdb之后,不禁产生了一个想法: 要不试试分析下具体原因看看能不能盲修一发?

先用procdump抓个full dump,快速验证了一下确实是memcmp的第一个参数越界了

接下来看OpenGLWidget::addGeometry(std::vector<physx::PxVec3>&, std::vector<unsigned short>&, int)
ps. 看这个函数声明,盲猜传入的应该是positionindicescount

  • a1相当于是OpenGLWidgetthis指针
  • v21=a1+808应该是OpenGLWidget的某个成员变量,应该是类似偏移量
  • 闪退堆栈对应memmove(dst, src, num)里出现write access violation,就是a1+768(类似buffer)加偏移量2*v21到非法地址了

结合后面的glBindBufferglBufferSubData调用,继续猜测

  • 这儿应该是在更新position和indices数据到GL里
  • a1+796a1+804是两个GL Target
  • a1+800a1+808是两个size
    • 盲猜前者是position、后者是indices,正好对上sizeof(vec3f)=12, sizeof(short)=2

到目前为止感觉都能自圆其说,而且让我写也估计就是类似写法来着。索性整理下变量名+盲猜一手成员变量:

越看越觉得靠谱hhh 索性看下对应的构造函数,这个=nullptr看上去也没问题,那就还得找找到底哪里初始化了这俩buffer来着

最后在OpenGLWidget::initializeGeometryBuffers里看到默认size给了0x80000,之后使用的时候也完全没考虑扩容…
搞了半天是只要场景足够复杂,PVD就会爆炸orz

来都来了…直接把这俩buffer的初始大小常量左移一发

尝试了下前面保存的pvd文件,完美打开! 有种感受到出题者思路的快乐(划走)嘿嘿嘿