[原]调试实战——使用windbg调试TerminateThread导致的死锁
前言
项目里的一个升级程序偶尔会死锁,查看dump
后发现是死在了ShellExecuteExW
里。经验少,不知道为什么,于是在高端调试论坛里发帖求助,链接如下http://advdbg.org/forums/6520/ShowPost.aspx
根据张银奎老师的描述可知,应该是拥有关键段的线程意外结束了。仔细检查项目中的代码,发现程序中有使用TerminateThread()
来强制杀线程的代码。很可疑,于是写了一个测试程序,还原了这个问题。
{% note info %}
这也是几年前在项目中遇到的一个问题,我对之前的笔记进行了整理重新发布于此。
{% endnote %}
问题重现
重现方法
主程序会加载一个DLL
,并调用该DLL
的导出函数创建一个线程,然后调用TerminateThread()
强制杀死这个线程,然后调用RunProcess()
(内部封装了对ShellExecuteEx()
的调用)执行一个新进程,会卡死在ShellExecuteEx()
。为了让问题更容易重现,特地在DllMain()
的参数ul_reason_for_call
为DLL_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 up
Sleep(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);
return;
}
unsigned __stdcall testProc(void *)
{
OutputCurrentThreadId();
return 0;
}
HANDLE GenerateThread()
{
HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, &testProc, NULL, 0, NULL);
return hThread;
}
debug-deadlock-caused-by-TerminateThread-Demo.zip
10.63 KB
问题分析
运行测试程序前先打开DbgView
监视调试信息,然后运行测试程序。
从日志可知,我们启动的测试线程的线程id
为0x1400
。
当程序hang
住后,使用windbg
附加。附加成功后,先运行~*kvn
查看线程及每个线程的的调用栈信息。发现只有一个0
号线程(1
号线程是windbg
附加到进程时产生的)。
0:001> ~*kvn
0 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 section = 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
[原]调试实战——使用windbg调试TerminateThread导致的死锁的更多相关文章
- [原]调试实战——使用windbg调试崩溃在ComFriendlyWaitMtaThreadProc
原调试debugwindbgcrash崩溃COM 前言 这是几年前在项目中遇到的一个崩溃问题,崩溃在了ComFriendlyWaitMtaThreadProc()里,没有源码.耗费了我很大精力,最终通 ...
- [原]调试实战——使用windbg调试崩溃在ole32!CStdMarshal::DisconnectSrvIPIDs
原调试debugwindbg崩溃crash 前言 最近程序会不定期崩溃,很是头疼!今晚终于忍无可忍,下决心要干掉它!从之前的几个相关的dump可以猜到是有接口未释放导致的问题,但没有确认到底是哪个接口 ...
- [原]调试实战——使用windbg调试excel启动时死锁
原调试debugwindbg死锁deadlock 前言 这是几年前在项目中遇到的一个死锁问题,在博客园发布过.我对之前的笔记进行了整理重新发布于此. 本文假设小伙伴们知道一些基本概念,比如什么是.du ...
- [原]调试实战——使用windbg调试DLL卸载时的死锁
原调试debugwindbg死锁deadlock 前言 最近我们的程序在退出时会卡住,调查发现是在卸载dll时死锁了.大概流程是这样的:我们的dll在加载的时候会创建一个工作线程,在卸载的时候,会设置 ...
- .NET高级调试系列-Windbg调试入门篇
Windbg是.NET高级调试领域中不可或缺的一个工具和利器,也是日常我们分析解决问题的必备.准备近期写2篇精华文章,集中给大家分享一下如果通过Windbg进行.NET高级调试. 今天我们来一篇入门的 ...
- Windbg调试命令详解
作者:张佩][原文:http://www.yiiyee.cn/Blog] 1. 概述 用户成功安装微软Windows调试工具集后,能够在安装目录下发现四个调试器程序,分别是:cdb.exe.ntsd. ...
- Windbg调试命令详解(3)
3 进程与线程 既可以显示进程和线程列表,又可以显示指定进程或线程的详细信息.调试命令可以提供比taskmgr更详尽的进程资料,在调试过程中不可或缺. 3.1 进程命令 进程命令包括这些内容:显示进程 ...
- Windbg调试命令详解(2)
转载注明>> [作者:张佩][原文:http://blog.csdn.net/blog_index] 2. 符号与源码 符号与源码是调试过程中的重要因素,它们使得枯燥生硬的调试内容更容易 ...
- 调试SQLSERVER (二)使用Windbg调试SQLSERVER的环境设置
调试SQLSERVER (二)使用Windbg调试SQLSERVER的环境设置 调试SQLSERVER (一)生成dump文件的方法调试SQLSERVER (三)使用Windbg调试SQLSERVER ...
随机推荐
- 062-PHP函数按值传参,交换数值函数
<?php function swap($x,$y){ //定义交换数值函数 $temp=$x; $x=$y; $y=$temp; } $m=5; $n=15; echo "交换前:& ...
- 【数据结构】C++语言无锁环形队列的实现
无锁环形队列 1.Ring_Queue在payload前加入一个头,来表示当前节点的状态 2.当前节点的状态包括可以读.可以写.正在读.正在写 3.当读完成后将节点状态改为可以写,当写完成后将节点状态 ...
- Ajax请求传递数组参数的方法
方法一:拼接字符串参数 var arr={params:['param','param2']}; $.ajax({url:请求地址, data:arr, type:"POST", ...
- POJ 1284:Primitive Roots 求原根的数量
Primitive Roots Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 3381 Accepted: 1980 D ...
- jquery 获取同级元素
$(".userinfo-three:eq(0)").css({ "width": winWidth * 300 / 1080, ...
- 19 01 17 Django 模型 使用mysql数据库
今天演示使用MySQL数据库,这是Web项目首选的数据库. 进入虚拟环境py_django. workon py_django 在/home/python/pytest目录下创建项目test2. dj ...
- 当spring单元测试需要用到临时表的时候
需要将整个单元测试的方法交给spring的事务管理器控制. 两种解决方法: 1.加载的spring配置文件中advice要切到需要测试的方法. 2.单元测试类继承AbstractTransaction ...
- VS Code 配置vue开发环境
一.插件 网上搜索vscode插件的文章,动辄十几个,其实根本用不了那么多,很多插件的作用还有重叠,电脑性能还被白白浪费.这里精简为主,每一个插件都发挥它最大的作用,并尽量说明它们的作用 Vetur ...
- Windows10 与 WSL(Ubuntu)的文件互访
从WSL访问win10的文件 > cd /mnt 从win10访问WSL的文件 打开Ubuntu > explorer.exe . (后面的点不要漏掉)
- cf1208 D Restore Permutation (二分+树状数组)
题意 让你构造一个长度为n的序列,记为p1……pn,(这个序列是1~n的全排列的一种) 给你n个数,记为s1……sn,si的值为p1……pi-1中小于pi的数的和. 思路 显然,应该倒着来,也就是从p ...