3

我遇到了一个使用我的软件的客户的死胡同。在我们售出的大约 40 份产品中(使用 VB.NET 2005 在 .NET 2.0 中编程的应用程序),大约有 2 份没有响应,双核 CPU 的 1 个核心卡在 100%(程序仅使用 1 个核心)

最合乎逻辑的猜测是导致这种行为的无限循环,但是成千上万行代码带有很多很多循环。这就是我所掌握的所有信息;现在,你建议我如何调试这个问题?

编辑:基本上,该软件负责计算使用其他设备(例如 PC 等)所花费的信用量。它是一个网吧管理程序,间歇性地失败,即它在失败时减去信用。它还在后台执行其他操作,例如检查是否是时候创建数据库备份等。

编辑:解决。这是最不可能的问题。我用作 DBMS 的 Access 数据库引擎实际上是我的应用程序中存在问题的部分。在其中一张表中处理一行——JUST ONE FRIGGIN ROW——有困难。我无法删除它,或者在任何其他表中添加与该行相关的记录;当我尝试使用该行时,即使是 MS Access 2007 也会导致 CPU 达到 100%!

一个简单的“压缩和修复”命令修复了一切。我想我会在每次我的应用程序启动时发出该命令。这将防止这种情况再次发生。

感谢WinDbg,我可以找到问题所在。我建议每个人都学习如何使用它,因为它可以真正节省时间。

4

8 回答 8

7

在目标机器上安装 windbg(Windows 调试器)。调用调试器,并附加到可疑进程,运行程序,然后等待问题发生。当问题发生时,在调试器命令行中调用以下命令

!逃跑

这将显示您的哪些线程大部分时间都在消耗。然后从消耗大部分 cpu 资源的线程中获取几个线程堆栈。

这是一个例子:

0:015> !runaway

用户模式时间 线程时间 0:1074 0 天 0:00:21.637 11:137c 0 天 0:00:02.792 4:12c8 0 天 0:00:00.530 9:1374 0 天 0:00:00.046 15:13d0 0 天0:00:00.000 14:1204 0 天 0:00:00.000 13:154c 0 天 0:00:00.000 12:144c 0 天 0:00:00.000 10:1378 0 天 0:00:00.000 8:1340 0 天0:00:00.000 7:12f0 0 天 0:00:00.000 6:12d4 0 天 0:00:00.000 5:12d0 0 天 0:00:00.000 3:12c4 0 天 0:00:00.000 2:12c0 0 天0:00:00.000 1:12b4 0 天 0:00:00.000

现在假设我们想要列表中的第二个线程线程 11 的调用堆栈,所以我们首先切换到线程 11。这可以通过输入 ~11s 来完成。

0:015> ~11s

EAX = 03FBB270 EBX = FFFFFFFF ECX = 00000002 EDX = 00000060 ESI = 00000000 EDI = 00000000 EIP = 77475CE74 ESP = 0572F60C EBP = 0572F67C IOPL = 0 NV向上EI PL ZR NA PE NC CS = 001B SS = 0023 DS = 0023 ES = 0023 fs=003b gs=0000 efl=00000246 ntdll!KiFastSystemCallRet: 77475e74 c3 ret

现在通过执行 kp 获取该线程的调用堆栈:

0:011> kp
ChildEBP RetAddr  
0572f608 77475620 ntdll!KiFastSystemCallRet
0572f60c 75b09884 ntdll!NtWaitForSingleObject+0xc
0572f67c 75b097f2 kernel32!WaitForSingleObjectEx+0xbe
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Program Files\Mozilla Firefox 3.1 Beta 1\nspr4.dll - 
0572f690 10019a0b kernel32!WaitForSingleObject+0x12
WARNING: Stack unwind information not available. Following frames may be wrong.
0572f6ac 10015979 nspr4!PR_MD_WAIT_CV+0x8b
0572f6c4 10015763 nspr4!PR_GetPrimordialCPU+0x79
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Program Files\Mozilla Firefox 3.1 Beta 1\xul.dll - 
0572f6e0 64d44d6a nspr4!PR_Wait+0x33
0572f708 64dbe67e xul!NS_CycleCollectorForget2_P+0x698a
0572f72c 10019b3f xul!gfxWindowsPlatform::FontEnumProc+0xfd4e
0572f734 10015d32 nspr4!PR_MD_UNLOCK+0x1f
0572f738 1001624b nspr4!PR_Unlock+0x22
0572f754 1001838d nspr4!PRP_TryLock+0x4cb
00000000 00000000 nspr4!PR_Now+0x109d

命令 kp 将打印参数。局部变量可以用 dv 打印。

或者,您可以使用来自 sysinternals 的进程资源管理器。

如果这一切都不可能,因为它是远程客户端计算机,请安装 userdump,它会创建一个转储文件,可以发送给您进行进一步分析。您可以为客户创建一个批处理文件,以使用正确的参数调用 userdump。Userdump 是微软的一个工具,可以从他们的网页下载。

于 2009-08-01T20:24:56.160 回答
4

如果可能,获取进程转储并查看堆栈跟踪。
我从来没有这样做过,但它应该适用于 VS/WinDbg 和 SOS(罢工之子)。这是一篇关于它的博客文章

于 2009-08-01T11:54:20.567 回答
4

如果它是一个无限循环,那么尝试附加一个调试器并点击中断。WinDbg 非常适合这一点。

该技术也适用于循环只是迭代太多次但最终继续执行其余代码的情况。可以花几分钟重复该过程以获得良好的样本。

这种技术救了我好几次,也适用于挂起的应用程序 :)

于 2009-08-01T13:52:39.000 回答
2

那么你需要弄清楚它的紧密循环。您的客户当时使用该软件做什么?该软件首先做什么?

您可能需要考虑在代码中添加大量日志记录,并为客户端提供启用了所有日志记录的副本,帮助您跟踪他们遇到问题的地方。

于 2009-08-01T11:53:26.840 回答
2

使用log4net之类的记录器,您可以使用postsharp将其引入现有代码库。记录所有方法的进入和退出——所以你应该找到错误的方法。然后,如果仍然需要,您可以改进您的日志记录。

看起来这也适用于 vb.net,尽管我在那里没有经验。也许这篇文章对你有所帮助。

于 2009-08-01T11:54:55.497 回答
2

你必须非常彻底地采访这些客户。这并不总是那么容易,但这是缩小问题范围的唯一方法。

然后小心添加Tracing,不要忘记在关键点Flush(或设置AutoFlush)。

但这可能是一个微妙的时间问题,由于跟踪的增加延迟而消失了......

于 2009-08-01T12:20:53.437 回答
1

单核和多核 CPU 的行为可能会出现问题,例如当后台线程尝试更新 UI 时。

(而且我承认我在黑暗时代写了一个应用程序,它没有干净地分离背景和 UI 线程,并在多核 CPU 成为主流时引起问题。解决方案是调用 SetProcessAffinity 将应用程序限制为单核)

如果是这种情况,您应该检查 100% CPU 是否仅发生在特殊类型的 CPU 上,以及使用 SetProcessAffinity 是否可以解决问题。如果是这样,您就知道在代码中的何处查找。

于 2009-08-01T12:37:27.780 回答
0

会不会是线程问题?“间歇性失败”让我想起了它。程序是否从外部接收信号/消息,例如远程处理/DCOM/套接字?用户界面中是否显示与此类消息相关的进度信息?

我曾经检测到一个线程问题,因为我总是使用很多 ASSERT。通过 XML-RPC 接收到的消息的开头有一个健全性检查 ASSERT:

"<?xml " 

并且 ASSERT 捕获了对消息内存的覆盖。通过检查,这原来是由于关键部分中缺少锁。这种检测之所以成为可能,是因为 ASSERT 很早就发现了问题(而且它发生得足够频繁,以至于被检测到)。

这不是非常具体或直接的建议,但我的建议是在可能影响线程问题的地方添加 ASSERT。

请注意,触发 ASSERT 并不一定意味着中止程序或抛出消息框。可以将 ASSERT 重定向到日志文件,包括触发 ASSERT 时的完整堆栈跟踪。

于 2009-08-02T02:44:33.927 回答