前言
这是几年前在项目中遇到的一个死锁问题,在博客园发布过。我对之前的笔记进行了整理重新发布于此。
本文假设小伙伴们知道一些基本概念,比如什么是.dump
文件(转储文件,进程在某一时刻的快照),什么是windbg
(windows
下的调试利器),windbg
的基本用法,调用栈,调用约定,等等。
背景介绍
我们的程序会把dll
注入到其它进程,然后后调用SetWinEventHook
安装进程内钩子。然后专门开启一个线程来分发监听到的事件信息,并在该线程内会获取IAccessible
接口并使用该接口获取感兴趣的属性(e.g. 按钮的标题,按钮的位置等等)。运行我们的程序后,再启动excel
,有时候会导致excel
卡死,本文分析的.dump
文件就是其中一次卡死时抓取的。背景介绍完毕,下面开始分析.dump
。
问题分析
首先,我们用windbg打开.dump
文件,因为当时没截图,这里就不放图了。
因为是UI
线程卡死,而且一般情况下,第一个线程就是UI
线程,所以我们使用~0s
命令来切换到UI
线程。
然后用kv
命令列出调用栈,因为比较长,这里只用了kv10
,列出前16
个栈帧(windbg
默认是16
进制)。
我们发现0
号线程进入关键段时卡住了,在等待值为0x544
的句柄,猜测应该是关键段内部的LockSemaphore
(类型为的event
,可以通过!handle 0x544
来验证,之前忘记截图了,这里就不放图了),我们可以用命令!cs 76b18770
来观察关键段76b18770
的内容。
通过OwningThread
字段我们可以知道关键段76b18770
被线程0x000001ac
占用着,而0
号线程的线程id
是0x00000de4
。
我们可以通过? $tid
来观察当前线程的id
(因为我们之前用~0s
切换到0
号线程了,所以观察到的是0
号线程对应的id
)
我们切换到线程id
为0x000001ac
的线程看看, 可以使用~~[0x000001ac]s
来根据线程id
切换线程。输入~~[0x000001ac]s;kv
从上图可知,线程0x000001ac
正是我们新建的线程。从调用栈可知,本线程正在取name
属性。但是因为某些原因触发了异常!frame 2
表示正在调用SuspendThread
挂起句柄为fffffffe
的线程!也就是当前线程!!!我们可以反汇编KERNELBASE!GetCurrentThread
来验证。输入uf KERNELBASE!GetCurrentThread
小结
0
号线程尝试进入关键段76b18770
,而关键段76b18770
正在被线程id
为0x000001ac
的线程占用着,该线程又由于某些异常将自己挂起了!于是:bomb:!死锁了!!!
提示
windbg中有一个很有用的命令可以帮助我们快速找到死锁的关键段!输入!cs -l
我们也可以从上图中看到线程0x000001ac
拥有着关键段76b18770
。
后记
windbg可谓是windows
下分析各种疑难杂症的神兵利器,熟练掌握windbg是每一个优秀的windows
开发人员必备的技能!而掌握windbg中的各种命令是掌握windbg的基础。
参考资料
- 《格蠹汇编》
- windbg帮助文件