死锁破解大招,解决程序卡死的所有烦恼!

Posted 51CTO技术栈

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了死锁破解大招,解决程序卡死的所有烦恼!相关的知识,希望对你有一定的参考价值。

"卡死"本文指的是程序永久无响应。出现这种状况一般有3个原因:

  • 主线程与其它线程相互等待死锁;

  • 主线程被别人挂起; 

  • 主线程陷入死循环。


遇到这样的问题该如何排查?往往让人无从下手。常规的办法就是不断增加日志,然后逼近现场,但是如果现场偶发,则这个过程会比较漫长。而且有的时候卡死往往是系统级的,日志可能都不知道写哪里。碰到此类问题往往让人崩溃。 


有没有一招制敌的秘籍呢?看了下面死锁案例破解过程,你可能就会觉得这类问题也是小菜一碟了。


案例:测试现场在执行日初始化时界面卡死。

开发已经定位到某个线程在准备调用 A.dll 的一个导出函数 B 时,加载 A.dll 卡死。虽然现场是必现的,但是由于是在加载 dll 时卡死,开发连加日志的地方都找不到。现在就可以参考一下案例中的破解过程。


【准备工具】

1、Procdump -- 抓现场 dump 的工具。

2、Windbg -- 分析 dump 的工具。Windbg 如何使用这里不作详细介绍。

1、用 procdump 工具抓取卡死进程的 dump。

2、DOS 界面执行命令,procdump -ma 进程 ID 保存路径。


【Windbg打开dump分析】

一、首先确定主线程的进程

输入 kv 命令

最后的调用堆栈:
▪ ntdll!RtlEnterCriticalSection  主线程在尝试进入临界区。
▪ ntdll!ZwWaitForSingleObject 然后等待内核对象。


可以看出主线程应该是发生死锁了。
往上回溯调用栈 rtl60!ClassesTThreadListLockList$qqrv+0xc

死锁破解大招,解决程序卡死的所有烦恼!


这个是 delphi 的 bpl 导出函数,bpl 导出函数可以直接通过名称就对应到源代码。

死锁破解大招,解决程序卡死的所有烦恼!


继续往前回溯

死锁破解大招,解决程序卡死的所有烦恼!

通过导出函数名字可以直接定位到函数 TWinControl.MainWndProc

死锁破解大招,解决程序卡死的所有烦恼!


死锁破解大招,解决程序卡死的所有烦恼!

死锁破解大招,解决程序卡死的所有烦恼!

然后 delphi 建一个空工程,带包 vcl,任意找一行代码下断点。
运行到断点,菜单 view → Debug Windows → CPU 打开 CPU 窗口。

死锁破解大招,解决程序卡死的所有烦恼!


死锁破解大招,解决程序卡死的所有烦恼!


对应源代码是

死锁破解大招,解决程序卡死的所有烦恼!


定位到函数 FreeDeviceContexts

死锁破解大招,解决程序卡死的所有烦恼!

死锁破解大招,解决程序卡死的所有烦恼!


输入指令!cs 03281d00 可以获得该临界的详细信息

死锁破解大招,解决程序卡死的所有烦恼!


其中 OwningThread 指是该临界被哪个线程所持有
31b0 是持有临界的线程 ID,对应 53 号线程

死锁破解大招,解决程序卡死的所有烦恼!


三、53号线程的进程排查
输入命令 ~53 kv,显示 53 号线程调用栈
死锁破解大招,解决程序卡死的所有烦恼!


调用栈 vcl60!FormsTCustomFormCreateWnd$qqrv+0x12d,可以直接定位到代码

死锁破解大招,解决程序卡死的所有烦恼!

源代码 SetWindowPos 和调用栈 user32!NtUserSetWindowPos+0x15 吻合
这说明 53 号线程正在创建一个 StayOnTop 类型的窗口

四、谁是 StayOnStop 类型的窗口

由于 vcl 和 rtl 代码无法确定创建了什么窗口,因此堆栈往上回溯,进入业务函数

死锁破解大招,解决程序卡死的所有烦恼!

这个需要源代码和 dump 的版本完全一致;

  1. 打开 delphi 调试状态,手工计算;

  2. 如果相同,则定位的源代码是正确的,否则这个办法走不通。


▲方法二:特征代码搜索法

特征代码搜索法的优点:源代码和 dump 版本不一致问题也不大,只要出问题的那小段代码没有改变,是可以定位到的。另外如果 C++ 的 DLL 符号文件缺失,或者不一致的情况下也可以启用这种办法定位代码。


下面在这个案例中演示下这种方法。
查看 dump 中 fixedincometrade!GetTmpStockSQL+0x3c947 的汇编代码。

死锁破解大招,解决程序卡死的所有烦恼!


死锁破解大招,解决程序卡死的所有烦恼!


死锁破解大招,解决程序卡死的所有烦恼!

另外选择特征码需具备唯一性,即在进程中,这段代码需要不容易重复,一般来说长度越长越不容易重复。

打开 delphi 开启 fixedincometrade 新进程(进程只要启动起来就可以),PID=2668
启动新 windbg,Attach 到进程 2668,选择非侵入式。
执行命令如下:

s -b 17710000 La04000 64 ff 30 64 89 20 83 2d

死锁破解大招,解决程序卡死的所有烦恼!


死锁破解大招,解决程序卡死的所有烦恼!


死锁破解大招,解决程序卡死的所有烦恼!


找到 uBuildReport.pas 1561 源代码。

死锁破解大招,解决程序卡死的所有烦恼!

可以看到单元的初始化在创建一个窗体。这个初始化是 dll 加载时自动执行的,和现场吻合。经检查该窗体也的确是 StayOnTop 类型的。

至此卡死原因已豁然开朗,找到了原因接下来的工作就简单了。

六、程序为什么会卡死
▲53号日初始化线程的执行轨迹如下:

加载fixedincometrade,执行单元初始化 →
创建StayOnTop窗口 →
CreateHandle先锁定CanvasList →
GetDeviceContext → TCustomForm.CreateWnd →
SetWindowPos 主线程发生交互


▲主线程的执行轨迹如下:
MainWndProc消息循环 →
FreeDeviceContexts →
等待 CanvasList 锁

死锁由此发生。而直接原因是因为线程去操作界面。

之所以标题称为破解,是因为从上面的排查过程来看,用这种方法,不用熟悉模块代码,不用了解业务场景,反向行之,只要捕捉到一次现场,就可以由现场而还原到代码环境,一击而中,无所遁形。


来源:http://rdc.hundsun.com/portal/article/702.html



IT技术群,期待你的加入

后台回复“入群”审核受邀


广 告

以上是关于死锁破解大招,解决程序卡死的所有烦恼!的主要内容,如果未能解决你的问题,请参考以下文章

mysql中alter语句卡死的一个解决方法

phpssh2卡死

XP下切换输入法造成程序卡死的原因及解决方案

公司的erp数据库最近会出现不定时卡死的问题,重启数据库服务后恢复,但是过一段时间又会卡。

解决浏览器加载不出静态页面和程序卡死的问题

JupyterLab内存溢出导致卡死的解决方法