软件调试系列:死锁篇之寻找消失的线程

一:遭遇挑战

小鸟:

"咦?这软件怎么没有响应了"

小菜:

"哦,从dump文件看,可能是某个线程占用了一个锁,但在该锁被释放前,该线程却被强杀了吧....

小鸟:

"这可能吗?是什么线程消失了?当时它在做什么呢?"

小菜:

"这个..只是猜测,线程都消失了,怎么知道是什么线程,并且知道当时在做什么呢?

二:动动脑

小鸟:

线程被强制结束,会导致资源泄漏的. 不知道泄漏的资源是什么?"

小菜:

"也是,会是什么资源呢,想想,当强制结束线程时,线程并没有被告之自己将被结束,那么该线程的调用栈该不会被回收吧?"

小鸟:

"如果被强制结束的线程的调用栈没有被释放掉,那我们是不是可以在dump文件中搜索调用栈的某个标志,来找到这个调用栈呢?"

小菜:

"嗯,有道理,线程的调用栈中一般都有kernel32!BaseThreadStart+0x37 这个函数调用帧,在调用栈中,我们可以以其为标识,来搜索调用栈"

三:动动手

写一个测试程序,开启一个线程,然后强制结束该线程,抓取该测试程序的dump文件.

.计算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

 

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值