Raymond Chen 2007年04月26日
使用 “gu” 调试器命令寻找死循环
有人说,“你的程序占用了100%的CPU”,然后递给你一个调试会话。 这通常发生是因为一个线程陷入了一个死循环中。 如果你幸运的话,这是一个容易诊断的死循环类型,因为它只是一个没有返回的函数。 (更复杂的情况是,一个函数做了一些工作然后返回,然后这些工作有一些延迟效果导致函数再次运行,如此等等。) 让我们假设我们是幸运的,因为,嗯,调试是一种好的练习。
第一步是找到使用所有CPU的线程。 借助 !runaway
调试器扩展,这实际上是相当容易的。
0:011> !runaway
User Mode Time
Thread Time
192c 0 days 0:05:22.457
1384 0 days 0:00:16.063
14ac 0 days 0:00:08.392
48c 0 days 0:00:03.955
1db0 0 days 0:00:00.010
1888 0 days 0:00:00.010
1078 0 days 0:00:00.000
1470 0 days 0:00:00.000
1f84 0 days 0:00:00.000
1d60 0 days 0:00:00.000
1850 0 days 0:00:00.000
134c 0 days 0:00:00.000
19fc 0 days 0:00:00.000
1b4 0 days 0:00:00.000
哇,线程 0x192c 肯定使用了很多CPU时间,但这并不意味着它是处于100% CPU循环中的线程,因为CPU时间是线程生命周期内的累积时间。也许那个线程有很多CPU时间是因为它存在的时间最长。 你需要做的是恢复执行一小会儿,然后再次中断,看看谁的CPU时间增加了。
0:011> g
^C
(1928.1d34): Break instruction exception - code 80000003 (first chance)
eax=7ffd9000 ebx=00000001 ecx=00000002 edx=00000003 esi=00000004 edi=00000005
eip=7c901230 esp=0124ffcc ebp=0124fff4 iopl=0 nv up ei pl zr na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000246
ntdll!DbgBreakPoint:
7c901230 cc int 3
0:011> !runaway
User Mode Time
Thread Time
192c 0 days 0:05:23.679
1384 0 days 0:00:16.063
14ac 0 days 0:00:08.392
48c 0 days 0:00:03.955
1db0 0 days 0:00:00.010
1888 0 days 0:00:00.010
1078 0 days 0:00:00.000
1470 0 days 0:00:00.000
1ea4 0 days 0:00:00.000
1d60 0 days 0:00:00.000
1850 0 days 0:00:00.000
134c 0 days 0:00:00.000
19fc 0 days 0:00:00.000
1b4 0 days 0:00:00.000
啊哈,我们看到线程 0x192c 是唯一一个CPU时间有明显增加的线程。 那很可能就是导致100% CPU使用率的线程。
0:011> ~~[192c]s
eax=00000000 ebx=77d5e581 ecx=0012daa0 edx=0000000c esi=01d18140 edi=00000000
eip=77d5e590 esp=0012da78 ebp=0012da88 iopl=0 nv up ei pl zr na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
USER32!FindWindowA+0xf:
77d5e590 50 push eax
ChildEBP RetAddr
0012da88 1000714b USER32!FindWindowA+0xf
0012dbc8 100061f3 ABC!CAlpha::FindTarget+0x27f
0012dbf4 603517e8 ABC!CBeta::TransferData+0x18a
0012dc28 10002d9d DEF!CGamma:TransferData+0xc
0012dc48 00505303 ABC!CBeta::BeginAsync+0x51
0012dc5c 0090a21a GHI!CPrintSession::Open+0x2a51
0012dd20 009099d8 GHI!CPrintSession::Init+0x252
0012e060 009097e6 GHI!CPrintOptions::GetSettings+0x24a
0012e0a4 0090973d GHI!CPrintOptions::OpenSettings+0x248
0012e130 00909664 GHI!CDocumentMenu::OnInvoke+0x24
...
现在,神奇的 “gu” 命令就派上用场了。 你输入 “gu” 来运行当前函数直到它返回。 如果你得到另一个提示符,那么再次输入 “gu”, 来运行那个函数直到它返回。 以此类推,直到你找到那个不返回的函数。 那就是包含无限循环的函数。
现在你可以开始调查为什么那个函数卡住了。