3

你好,祝你有美好的一天。

这里需要一点帮助:

情况
我有一个晦涩的 DirectX 9 应用程序(名称和应用程序详细信息与问题无关),自某些驱动程序版本以来,它会导致所有 nvidia 卡(GeForce 8400GS 及更高版本)出现蓝屏死机。我认为该问题是由 DirectX 9 调用或触发驱动程序错误的标志间接引起的。

目标
我想追踪有问题的标志/函数调用(为了好玩,这不是我的工作/家庭作业)并通过编写代理 dll 绕过错误条件。我已经有一个完成的代理 dll,它为 IDirect3D9、IDirect3DDevice9、IDirect3DVertexBuffer9 和 IDirect3DIndexBuffer9 提供包装器,并提供 Direct3D 调用的基本日志记录/跟踪。但是,我无法确定导致崩溃的功能。

问题

  1. 没有可用的源代码或技术支持。不会有任何帮助,也没有其他人可以解决问题。
  2. 内核产生的内存转储没有帮助 - 显然在 nv4_disp.dll 中发生了访问冲突,但我不能使用堆栈跟踪去 IDirect3DDevice9 方法调用,另外还有可能异步发生错误。
  3. (主要问题)由于大量 Direct3D9Device 方法调用,我无法可靠地将它们记录到文件中或通过网络:
    1. 即使没有刷新,登录到文件也会导致显着减慢,因此,当系统蓝屏死机时,日志的所有最后内容都会丢失。
    2. 通过网络登录(使用 UDP 和 WINSOck 的sendto)也会导致显着减速,并且不能异步完成(异步数据包在 BSOD 上丢失),加上数据包(崩溃周围的数据包)有时即使在同步发送时也会丢失。
    3. 当应用程序通过记录例程“减慢”速度时,BSOD 不太可能发生,这使得跟踪它变得更加困难。

问题
我通常不编写驱动程序,也不进行这种级别的调试,所以我觉得我遗漏了一些重要的东西,有一种比使用自定义日志记录机制编写 IDirect3DDevice9 代理 dll 更简单的方法来追踪问题. 它是什么?诊断/处理/修复此类问题的标准方法是什么(没有源代码,COM接口方法触发BSOD)?

小型转储分析(WinDBG)

加载用户符号
加载卸载的模块列表
............
无法加载图像 nv4_disp.dll,Win32 错误 0n2
*** 警告:无法验证 nv4_disp.dll 的时间戳
*** 错误:模块加载完成,但无法为 nv4_disp.dll 加载符号
****************************************************** *****************************
* *
*错误检查分析*
* *
****************************************************** *****************************

使用 !analyze -v 获取详细的调试信息。

错误检查 1000008E,{c0000005,bd0a2fd0,b0562b40,0}

可能是由于:nv4_disp.dll (nv4_disp+90fd0)

跟进:MachineOwner
---------

0: kd>!分析-v
****************************************************** *****************************
* *
*错误检查分析*
* *
****************************************************** *****************************

KERNEL_MODE_EXCEPTION_NOT_HANDLED_M (1000008e)
这是一个非常常见的错误检查。通常异常地址会查明
导致问题的驱动程序/功能。总是记下这个地址
以及包含此地址的驱动程序/图像的链接日期。
一些常见问题是异常代码 0x80000003。这意味着一个硬
编码断点或断言被命中,但该系统已启动
/节点调试。这不应该发生,因为开发人员不应该有
零售代码中的硬编码断点,但是...
如果发生这种情况,请确保已连接调试器,并且
系统已启动 /DEBUG。这将让我们看看为什么这个断点是
正在发生。
论据:
Arg1: c0000005, 未处理的异常代码
Arg2: bd0a2fd0, 异常发生的地址
Arg3:b0562b40,陷阱帧
Arg4: 00000000

调试细节:
------------------


EXCEPTION_CODE:(NTSTATUS)0xc0000005 - “0x%08lx”处的指令引用了“0x%08lx”处的内存。内存不能是“%s”。

FAULTING_IP:
nv4_disp+90fd0
bd0a2fd0 39b8f8000000 cmp dword ptr [eax+0F8h],edi

TRAP_FRAME: b0562b40 -- (.trap 0xffffffffb0562b40)
错误代码 = 00000000
eax=00000808 ebx=e37f8200 ecx=e4ae1c68 edx=e37f8328 esi=e37f8400 edi=00000000
eip=bd0a2fd0 esp=b0562bb4 ebp=e37e09c0 iopl=0 nv up ei pl nz na po nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010202
nv4_disp+0x90fd0:
bd0a2fd0 39b8f8000000 cmp dword ptr [eax+0F8h],edi ds:0023:00000900=????????
重置默认范围

CUSTOMER_CRASH_COUNT: 3

DEFAULT_BUCKET_ID:DRIVER_FAULT

BUGCHECK_STR:0x8E

LAST_CONTROL_TRANSFER:从 bd0a2e33 到 bd0a2fd0

堆栈文本:  
警告:堆栈展开信息不可用。以下框架可能是错误的。
b0562bc4 bd0a2e33 e37f8200 e37f8200 e4ae1c68 nv4_disp+0x90fd0
b0562c3c bf8edd6b b0562cfc e2601714 e4ae1c58 nv4_disp+0x90e33
b0562c74 bd009530 b0562cfc bf8ede06 e2601714 win32k!WatchdogDdDestroySurface+0x38
b0562d30 bd00b3a4 e2601008 e4ae1c58 b0562d50 dxg!vDdDisableSurfaceObject+0x294
b0562d54 8054161c e2601008 00000001 0012c518 dxg!DxDdDestroySurface+0x42
b0562d54 7c90e4f4 e2601008 00000001 0012c518 nt!KiFastCallEntry+0xfc
0012c518 00000000 00000000 00000000 00000000 0x7c90e4f4


堆栈命令:kb

FOLLOWUP_IP:
nv4_disp+90fd0
bd0a2fd0 39b8f8000000 cmp dword ptr [eax+0F8h],edi

SYMBOL_STACK_INDEX:0

SYMBOL_NAME:nv4_disp+90fd0

FOLLOWUP_NAME:机器所有者

模块名称:nv4_disp

IMAGE_NAME:nv4_disp.dll

DEBUG_FLR_IMAGE_TIMESTAMP:4e390d56

FAILURE_BUCKET_ID:0x8E_nv4_disp+90fd0

BUCKET_ID:0x8E_nv4_disp+90fd0

跟进:MachineOwner
4

3 回答 3

6
nv4_disp+90fd0
bd0a2fd0 39b8f8000000    cmp     dword ptr [eax+0F8h],edi

这是重要的部分。看这个,很可能 eax 是无效的,因此试图访问一个无效的内存地址。

您需要做的是将 nv4_disp.dll 加载到 IDA 中(您可以获得免费版本),检查 IDA 加载 nv4_disp 的图像库并点击“g”到转到地址,尝试将 90fd0 添加到 IDA 正在使用的图像库中,它应该将您直接带到有问题的指令(取决于部分结构)。

从这里您可以分析控制流,以及如何设置和使用 eax。如果你有一个好的内核级调试器,你可以在这个地址上设置一个断点并尝试让它命中。

分析函数时,您应该尝试弄清楚函数的作用,此时 eax 应该指向什么,它实际指向什么,以及为什么。这是困难的部分,也是逆向工程的难度和技巧的很大一部分。

于 2011-09-29T04:02:37.897 回答
6

找到了解决方案。

问题
日志记录不可靠,因为消息(当转储到文件时)在 bsod 期间消失,通过网络登录时数据包有时会丢失,并且由于日志记录而减速。

解决方案
配置系统以在 BSOD 上产生完整的物理内存转储并将所有消息记录到任何内存缓冲区,而不是记录到文件或通过网络。它会更快。一旦系统崩溃,它会将整个内存转储到文件中,并且可以使用 WinDBG 的dt(如果您有调试符号)命令查看日志文件缓冲区的内容,或者您​​将能够搜索和定位日志文件使用“内存”视图存储在内存中。

我使用 std::strings 的循环缓冲区来存储消息,并使用单独的 const char* 数组来使在 WinDBG 中更易于阅读,但是您可以简单地创建巨大的 char 数组并将所有消息以纯文本形式存储在其中。

详细信息
winxp上的整个过程:

  1. 确保最小页面文件大小等于或大于 RAM 总量 + 1 兆字节。(右键“我的电脑”->属性->高级->性能->高级->更改)
  2. 配置系统以在 BSOD 上生成完整的内存转储(右键单击“我的电脑”->属性->高级->启动和恢复->设置->写入调试信息。选择“完整的内存转储”并指定所需的路径)。
  3. 确保磁盘(将写入文件的位置)具有所需的可用空间量(系统上的 RAM 总量。
  4. 使用调试符号构建 app/dll(进行日志记录的),然后触发 BSOD。
  5. 等到内存转储完成,重新启动。在系统写入内存转储并重新启动时,请随意向驱动程序开发人员发誓。
  6. 将生成的MEMORY.DMP系统复制到安全的地方,这样如果系统再次崩溃,您不会失去一切。
  7. 启动windbg。
  8. 打开内存转储(文件->打开故障转储)。
  9. 如果您想查看发生了什么,请使用!analyze -v命令。
  10. 使用以下方法之一访问存储记录消息的内存缓冲区:
    1. 要查看全局变量的内容,请使用dt module!variable其中“模块”是库的名称(不带 *.dll),“变量”是变量的名称。您可以使用通配符。您可以不使用地址module!variable
    2. 要查看全局变量的一个字段的内容(如果全局变量是结构),请使用dt module!variable fieldwhere “field” 是变量成员。
    3. 要查看有关变量(数组和子结构的内容)的更多详细信息,请使用dt -b module!variable fielddt -b module!variable
    4. 如果您没有符号,则需要使用内存窗口搜索“日志文件”。

此时,您将能够看到存储在内存中的日志内容,并且您将获得整个系统崩溃时的快照。

还...

  1. 要查看有关使系统崩溃的进程的信息,请使用!process.
  2. 要查看加载的模块,请使用lm
  3. 有关线程的信息,id 是您在输出中!thread id看到的十六进制 id 。!process
于 2011-09-30T22:14:11.097 回答
1

看起来崩溃可能是由错误的指针或堆损坏引起的。您可以告诉这一点,因为崩溃发生在内存释放函数 ( DxDdDestroySurface) 中。销毁表面是你绝对需要做的——你不能只是把它存根,当程序退出时表面仍然会被释放,如果你在内核中禁用它,你会用完卡上的内存很快,也以这种方式崩溃。

您可以尝试找出导致此堆损坏的事件顺序,但这里没有灵丹妙药 - 正如 fileoffset 所建议的那样,您需要实际对驱动程序进行逆向工程以了解发生这种情况的原因(这可能有助于比较驱动程序在有问题的驱动程序版本之前和之后!)

于 2011-09-30T22:20:41.167 回答