这是继上一篇 [原]调试DLL卸载时的死锁 后的又一篇使用windbg调试死锁的文章。希望能对大家有所帮助。

前言

之前项目里的一个升级程序偶尔会死锁,查看dump后发现是死在了ShellExecuteExW里。经验少,不知道为什么,于是在高端调试论坛里发帖求助,链接如下 http://advdbg.org/forums/6520/ShowPost.aspx

根据张银奎老师的描述可知,应该是拥有关键段的线程意外结束了。仔细检查项目中的代码,发现程序中有使用TerminateThread()来强制杀线程的代码。很可疑,于是写了一个测试程序,还原了这个问题。

问题重现

重现方法

主程序会加载一个DLL,并调用该DLL的导出函数创建一个线程,然后调用TerminateThread()强制杀死这个线程,然后调用RunProcess()(内部封装了对ShellExecuteEx()的调用)执行一个新进程,会卡死在ShellExecuteEx()。为了让问题更容易重现,特地在DllMain()的参数ul_reason_for_callDLL_THREAD_DETACH时,强制睡眠了5秒。

代码摘录

主工程 testTerminateThread

//testTerminateThread.cpp
#include "stdafx.h"
#include "windows.h"
#include "process.h"typedef HANDLE (*pfnGenerateThread)();HANDLE RunProcess(const TCHAR* app_name, const TCHAR* cmd)
{SHELLEXECUTEINFO shex = {sizeof(SHELLEXECUTEINFO)};shex.fMask = SEE_MASK_NOCLOSEPROCESS;shex.lpVerb = _T("open");shex.lpFile = app_name;shex.lpParameters = cmd;shex.lpDirectory = NULL;shex.nShow = SW_NORMAL;if (!::ShellExecuteEx(&shex)){return INVALID_HANDLE_VALUE;}return shex.hProcess;
}int _tmain(int argc, _TCHAR* argv[])
{while ( 1 ){HMODULE hModule = LoadLibrary(_T("testDll.dll"));if ( NULL == hModule )return 0;pfnGenerateThread pfn = (pfnGenerateThread)GetProcAddress(hModule, "GenerateThread");if ( NULL == pfn )return 0;HANDLE hThread = pfn();// give thread time to start upSleep(1000);// terminate thread.BOOL bOk = TerminateThread(hThread, 0);// dead lock in this function...RunProcess(argv[0], NULL);FreeLibrary(hModule);}return 0;
}

DLL 工程 testDll

// DllMain.cpp
#include "stdafx.h"
#include "windows.h"BOOL APIENTRY DllMain( HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved
)
{switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:OutputDebugString(L"====> DLL_PROCESS_ATTACH called.\n");break;case DLL_THREAD_ATTACH:OutputDebugString(L"----> DLL_THREAD_ATTACH called.\n");break;case DLL_THREAD_DETACH:OutputDebugString(L"<---- DLL_THREAD_DETACH called.\n");// with LdrpLoaderLock held! sleep 5 seconds.Sleep(5000);break;case DLL_PROCESS_DETACH:OutputDebugString(L"<==== DLL_PROCESS_DETACH called.\n");break;}return TRUE;
}
// testDll.cpp
#include "stdafx.h"
#include "stdio.h"
#include "process.h"
#include "windows.h"void OutputCurrentThreadId()
{TCHAR szBuffer[1024];swprintf_s(szBuffer, L"thread [0x%x], running & exiting...\n",GetCurrentThreadId());OutputDebugString(szBuffer);
}unsigned __stdcall testProc(void *)
{OutputCurrentThreadId();return 0;
}HANDLE GenerateThread()
{HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, &testProc, NULL, 0, NULL);return hThread;
}

点我下载测试工程[1]

问题分析

运行测试程序前先打开DbgView监视调试信息,然后运行测试程序。

从日志可知,我们启动的测试线程的线程id0x1400

当程序hang住后,使用windbg附加。附加成功后,先运行~*kvn查看线程及每个线程的的调用栈信息。发现只有一个0号线程(1号线程是windbg附加到进程时产生的)。

0:001> ~*kvn0  Id: 18c0.1008 Suspend: 1 Teb: 7ffdf000 Unfrozen# ChildEBP RetAddr  Args to Child
00 002bf614 775a6a64 77592278 00000064 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
01 002bf618 77592278 00000064 00000000 00000000 ntdll!NtWaitForSingleObject+0xc (FPO: [3,0,0])
02 002bf67c 7759215c 00000000 00000000 00000001 ntdll!RtlpWaitOnCriticalSection+0x13e (FPO: [Non-Fpo])
03 002bf6a4 775c00e1 77637340 77bf1b77 00000000 ntdll!RtlEnterCriticalSection+0x150 (FPO: [Non-Fpo])
04 002bf6dc 75587bc3 00000001 00000000 002bf704 ntdll!LdrLockLoaderLock+0xe4 (FPO: [Non-Fpo])
05 002bf728 7679215d 00000000 002bf73c 00000104 KERNELBASE!GetModuleFileNameW+0x75 (FPO: [Non-Fpo])
06 002bf948 76792112 002bfbb0 002bf968 7ffdb000 SHELL32!InRunDllProcess+0x39 (FPO: [Non-Fpo])
*** WARNING: Unable to verify checksum for C:\Users\BianChengNan\Documents\Visual Studio 2012\Projects\testTerminateThread\Debug\testTerminateThread.exe
07 002bf95c 013714db 002bfa44 002bfcbc 002bfbc0 SHELL32!ShellExecuteExW+0x51 (FPO: [Non-Fpo])
08 002bfbb0 01371685 000ac518 00000000 00000000 testTerminateThread!RunProcess+0xdb (FPO: [Non-Fpo]) (CONV: cdecl) [c:\users\bianchengnan\documents\visual studio 2012\projects\testterminatethread\testterminatethread\testterminatethread.cpp @ 28]
09 002bfcbc 01371c69 00000001 000ac510 000ae660 testTerminateThread!wmain+0xc5 (FPO: [Non-Fpo]) (CONV: cdecl) [c:\users\bianchengnan\documents\visual studio 2012\projects\testterminatethread\testterminatethread\testterminatethread.cpp @ 59]
0a 002bfd0c 01371e5d 002bfd20 758ced6c 7ffdb000 testTerminateThread!__tmainCRTStartup+0x199 (FPO: [Non-Fpo]) (CONV: cdecl) [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 533]
0b 002bfd14 758ced6c 7ffdb000 002bfd60 775c37eb testTerminateThread!wmainCRTStartup+0xd (FPO: [Non-Fpo]) (CONV: cdecl) [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 377]
0c 002bfd20 775c37eb 7ffdb000 77bf10cb 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
0d 002bfd60 775c37be 01371082 7ffdb000 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])
0e 002bfd78 00000000 01371082 7ffdb000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])#  1  Id: 18c0.193c Suspend: 1 Teb: 7ffde000 Unfrozen# ChildEBP RetAddr  Args to Child
00 0133fbac 775ff20f 76a71677 00000000 00000000 ntdll!DbgBreakPoint (FPO: [0,0,0])
01 0133fbdc 758ced6c 00000000 0133fc28 775c37eb ntdll!DbgUiRemoteBreakin+0x3c (FPO: [Non-Fpo])
02 0133fbe8 775c37eb 00000000 76a71183 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
03 0133fc28 775c37be 775ff1d3 00000000 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])
04 0133fc40 00000000 775ff1d3 00000000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

通过调用栈,我们发现程序卡在了ShellExecuteExW里。

运行!cs -l看下输出结果:

0:001> !cs -l
-----------------------------------------
DebugInfo          = 0x77637540
Critical p   = 0x77637340 (ntdll!LdrpLoaderLock+0x0)
LOCKED
LockCount          = 0x1
WaiterWoken        = No
OwningThread       = 0x00001400
RecursionCount     = 0x1
LockSemaphore      = 0x64
SpinCount          = 0x00000000

注意OwningThread的值0x00001400 正是我们生成的测试线程,与我们在DbgView里看到的线程id一致。但是该线程已经被我们杀死了,它在被杀死前获得了进程加载锁0x77637340 (ntdll!LdrpLoaderLock+0x0)

至此,真相大白。

总结

  • 不要随便用TerminateThread来强行杀死线程!

  • windbg真是windows下的调试神器。

  • !cs -l可以帮助我们快速的查找到死锁的关键段。

参考资料

  • 《软件调试》

  • 《格蠹汇编》

  • 《windows 核心编程(第 5 版)》尤其是第 20 章

  • Dynamic-Link Library Best Practices[2]

References

[1] 点我下载测试工程:
https://bianchengnan.gitee.io/downloads/debug-deadlock-caused-by-TerminateThread-Demo.zip
[2] Dynamic-Link Library Best Practices:
https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-best-practices

[原]调试TerminateThread导致的死锁相关推荐

  1. [原]调试PInvoke导致的内存破坏

    缘起 最近项目中遇到一个诡异的问题,程序在升级到.net4.6.1后,执行某个功能时会崩溃,提示访问只读内存区.大概规律如下: debug版不崩溃,release版稳定崩溃. 只有x64位的程序崩溃, ...

  2. TerminateThread 导致LoadLibary 死锁

    运行此代码,a2线程会卡死,因为在LoadLibrary过程中会有一个临界区被占用,如果正好是这个时候此线程被TerminateThread终止,那么所有的loadlibrary及其他使用此锁的线程都 ...

  3. [原]调试实战——程序CPU占用率飙升,你知道如何快速定位吗?

    前言 如果我们自己的程序的CPU Usage(CPU占用率)飙升,并且居高不下,很有可能陷入了死循环.你知道怎么快速定位并解决吗?今天跟大家分享几种定位方法,希望对你有所帮助. 如何判断是否有死循环? ...

  4. terminatethread导致内存泄露

    terminatethread导致内存泄露,waitforsingleobject又容易导致UI卡死,真头疼 听过无数次不要TerminateThread,只是工作中常用,貌似也没有什么问题.今天在高 ...

  5. 线上MySQL死锁分析——索引设置不当导致的死锁

    文章目录 1. 背景 2. MySQL InnoDB的锁机制 2.1 MySQL中的锁类型 2.2 行锁的加锁规则 2.3 死锁检测机制 3. 本文案例分析 3.1 分析InnoDB status日志 ...

  6. MySQL 5.6.35 索引优化导致的死锁案例解析

    一.背景 随着公司业务的发展,商品库存从商品中心独立出来成为一个独立的系统,承接主站商品库存校验.订单库存扣减.售后库存释放等业务.在上线之前我们对于核心接口进行了压测,压测过程中出现了 MySQL ...

  7. 由于不当的执行顺序导致的死锁

    为了保证线程的安全,我们引入了加锁机制,但是如果不加限制的使用加锁,就有可能会导致顺序死锁(Lock-Ordering Deadlock).上篇文章我们也提到了在线程词中因为资源的不足而导致的资源死锁 ...

  8. ajaxReturn 之前dump调试,导致$.ajax不能正常运行

    ajaxReturn 之前dump调试,导致$.ajax不能正常运行 以后调试的时候,注意下这个情况 转载于:https://www.cnblogs.com/bushe/p/5180317.html

  9. postgresql 并发访问_postgresql 并发update下导致的死锁问题

    一.死锁问题背景 在收据批量打印时,由于采用异步并发触发打印,同时触发打印(九千多数据 每隔50ms触发一次),导致了并发执行引起在接口更新打印次数时postgresql发生死锁问题, 具体报错如下: ...

最新文章

  1. Android开发学习笔记:对话框浅析
  2. Elasticsearch集群部署的相关问题记录
  3. java swing鼠标事件监听_java swing鼠标监听问题
  4. Python基础教程:列表(list)切片详细操作
  5. I/O复用函数的使用——poll
  6. Python 项目实践三(Web应用程序)第四篇
  7. Python模块之hashlib:提供hash算法
  8. 联想平板刷机机器人_消费终端年出货量破1.2亿,杨元庆:联想是智能化转型的赋能者...
  9. HTML5营销代理商设计机构网站模板
  10. Java的FTP协议级客户端实现详解
  11. 案例实现jquery.cookie的操作
  12. 苹果从来不飙配置,也从不关注配置,即使一般的配置也能卖好价钱,为啥没人喷?
  13. ASCII、ANSI、UNICODE及UTF-8编码
  14. LimeSurvey
  15. j2me怎么运行java_Eclipse开发J2ME环境安装及配置
  16. linux富文本软件,CherryTree(技术人员专用富文本笔记软件)
  17. 循环中使用switch在执行条件后使用break和continue的区别
  18. 正则化之weight-decay
  19. 如何将.keystore 文件转成.key文件
  20. 将《2020中国统计年鉴》中的GDP数据换算成不变GDP数据

热门文章

  1. 中南大学汪炼成教授课题组:三维金属-半导体-金属AlN深紫外探测器
  2. Qt配置项目编码格式
  3. 计算机vb考试程序试题,2014中职计算机VB考试试题.doc
  4. python实践意义_在Python学习中过程比结果更有意义
  5. 2023NHOI数三角形题解
  6. 2022年宜昌助理工程师职称评审流程和条件是什么呢?甘建二
  7. 国外的WEB CRACK TEAM的破解发布网站
  8. Codeforces Round#539(Div. 2)
  9. 用python实现解常微分方程组的简单示例以及用odeint解常微分方程的范例
  10. oracle 19c pdb搭建adg,ORACLE12C_ADG删除pdb