一:遭遇挑战
小鸟:
"咦?这软件怎么没有响应了"
小菜:
"哦,从dump文件看,可能是某个线程占用了一个锁,但在该锁被释放前,该线程却被强杀了吧...."
小鸟:
"这可能吗?是什么线程消失了?当时它在做什么呢?"
小菜:
"这个..只是猜测,线程都消失了,怎么知道是什么线程,并且知道当时在做什么呢?"
二:动动脑
小鸟:
"线程被强制结束,会导致资源泄漏的. 不知道泄漏的资源是什么?"
小菜:
"也是,会是什么资源呢,想想,当强制结束线程时,线程并没有被告之自己将被结束,那么该线程的调用栈该不会被回收吧?"
小鸟:
"如果被强制结束的线程的调用栈没有被释放掉,那我们是不是可以在dump文件中搜索调用栈的某个标志,来找到这个调用栈呢?"
小菜:
"嗯,有道理,线程的调用栈中一般都有kernel32!BaseThreadStart+0x37 这个函数调用帧,在调用栈中,我们可以以其为标识,来搜索调用栈"
三:动动手
写一个测试程序,开启一个线程,然后强制结束该线程,抓取该测试程序的dump文件.
1.计算kernel32!BaseThreadStart+0x37 在该dump文件中的地址
0:002> ? kernel32!BaseThreadStart+0x37
Evaluate expression: 2088809739 = 7c80b50b
2.在内存中搜索值为 7c80b50b的内存块
0:002> s -b 0 L?80000000 0b b5 80 7c
00d1ffb8 0b b5 80 7c f0 54 37 00-68 00 d7 77 cc 44 00 00 ...|.T7.h..w.D..
00e1ffb8 0b b5 80 7c d8 57 37 00-68 00 d7 77 cc 44 00 00 ...|.W7.h..w.D..
3.检查搜索到的内存块的内存属性
0:002> !address 00d1ffb8
00c20000 : 00d1f000 - 00001000
Type 00020000 MEM_PRIVATE
Protect 00000004 PAGE_READWRITE
State 00001000 MEM_COMMIT
Usage RegionUsageIsVAD
0:002> !address 00e1ffb8
00d20000 : 00e1f000 - 00001000
Type 00020000 MEM_PRIVATE
Protect 00000004 PAGE_READWRITE
State 00001000 MEM_COMMIT
Usage RegionUsageStack
Pid.Tid 44cc.2b70
发现00e1ffb8 的内存属性为RegionUsageStack 表明它在用户调用栈上,
而 00d1ffb8 的内存属性为RegionUsageIsVAD 说明它不是用户调用栈.
4.检查 00d1ffb8 是否作为用户调用栈存在过,检查的依据就是是否能以该内存上的数据重建回一个调用栈
0:002> dds 00d1ffb8-0x200 l0x50
00d1fdb8 00375688
00d1fdbc 1020b5c4 MSVCRTD!_unlock+0x14 [mlock.c @ 276]
00d1fdc0 10264780 MSVCRTD!hlcritsect
00d1fdc4 00d1ff70
00d1fdc8 102127f8 MSVCRTD!_except_handler3
00d1fdcc 10257130 MSVCRTD!`string'+0x8
00d1fdd0 ffffffff
00d1fdd4 00d1fdf4
00d1fdd8 102129ef MSVCRTD!_malloc_dbg+0x1f [dbgheap.c @ 165]
00d1fddc 0000007c
00d1fde0 00000000
00d1fde4 00000001
00d1fde8 5f4d0af4 MFC42D!THIS_FILE
00d1fdec 0000001f
00d1fdf0 00375688
00d1fdf4 00d1fe18
00d1fdf8 5f429a4b MFC42D!operator new+0x24 [afxmem.cpp @ 373]
00d1fdfc 0000007c
00d1fe00 00000001
00d1fe04 5f4d0af4 MFC42D!THIS_FILE
00d1fe08 00000778
00d1fe0c 00000001
00d1fe10 00d1fe48
00d1fe14 00000000
00d1fe18 7c92d85c ntdll!NtDelayExecution+0xc
00d1fe1c 7c8023ed kernel32!SleepEx+0x61
00d1fe20 00000000
00d1fe24 00d1fe48
00d1fe28 00d1fedc
00d1fe2c 00d1fe8c
00d1fe30 003754f0
00d1fe34 00000014
00d1fe38 00000001
00d1fe3c 00000000
00d1fe40 00000000
00d1fe44 00000010
00d1fe48 c4653600
00d1fe4c ffffffff
00d1fe50 00375688
00d1fe54 00d1fe48
00d1fe58 00d1fe28
00d1fe5c 7ffde000
00d1fe60 00d1ff70
00d1fe64 7c8399f3 kernel32!_except_handler3
00d1fe68 7c802458 kernel32!`string'+0x100
00d1fe6c 00000000
00d1fe70 00d1fe80
00d1fe74 7c802451 kernel32!Sleep+0xf
00d1fe78 000186a0
00d1fe7c 00000000
00d1fe80 00d1fedc
00d1fe84 004021aa thread!CMyThread::Run+0x2a [E:\test\thread\MyThread.cpp @ 52]
00d1fe88 000186a0
00d1fe8c 77d70068 USER32!gcCallUserApiHook
00d1fe90 000044cc
00d1fe94 003754f0
00d1fe98 cccccccc
00d1fe9c cccccccc
00d1fea0 cccccccc
00d1fea4 cccccccc
00d1fea8 cccccccc
00d1feac cccccccc
00d1feb0 cccccccc
00d1feb4 cccccccc
00d1feb8 cccccccc
00d1febc cccccccc
00d1fec0 cccccccc
00d1fec4 cccccccc
00d1fec8 cccccccc
00d1fecc cccccccc
00d1fed0 cccccccc
00d1fed4 cccccccc
00d1fed8 00375448
00d1fedc 00d1ff7c
00d1fee0 5f43aa46 MFC42D!_AfxThreadEntry+0x326 [thrdcore.cpp @ 125]
00d1fee4 77d70068 USER32!gcCallUserApiHook
00d1fee8 000044cc
00d1feec 003754f0
00d1fef0 001b0b8e
00d1fef4 004167c0 thread!theApp
从以上的符号来看,根据函数的调用过程,重建回调用栈为:
kernel32!Sleep+0xf
thread!CMyThread::Run+0x2a [E:\test\thread\MyThread.cpp @ 52]
MFC42D!_AfxThreadEntry+0x326 [thrdcore.cpp @ 125]
MSVCRTD!_threadstartex+0xb2 [threadex.c @ 227]
kernel32!BaseThreadStart+0x37
以上为一个完整的调用栈无疑, 也就说明我们找到了被强制结束的线程的调用栈了
PS 线程的第一个调用帧不一定是kernel32!BaseThreadStart+0x37 这个根据不同线程启动方式不同而不同, 可以从正常的进程中观察线程启动的第一个调用帧, 以下的讲到的线程的第一个帧就为kernel32!BaseThreadStart+0x34