原调试debugwindbg死锁deadlock

前言

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

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

{% note info %}

这也是几年前在项目中遇到的一个问题,我对之前的笔记进行了整理重新发布于此。

{% endnote %}

问题重现

重现方法

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

代码摘录

主工程 testTerminateThread

  1. //testTerminateThread.cpp
  2. #include "stdafx.h"
  3. #include "windows.h"
  4. #include "process.h"
  5. typedef HANDLE (*pfnGenerateThread)();
  6. HANDLE RunProcess(const TCHAR* app_name, const TCHAR* cmd)
  7. {
  8. SHELLEXECUTEINFO shex = {sizeof(SHELLEXECUTEINFO)};
  9. shex.fMask = SEE_MASK_NOCLOSEPROCESS;
  10. shex.lpVerb = _T("open");
  11. shex.lpFile = app_name;
  12. shex.lpParameters = cmd;
  13. shex.lpDirectory = NULL;
  14. shex.nShow = SW_NORMAL;
  15. if (!::ShellExecuteEx(&shex))
  16. {
  17. return INVALID_HANDLE_VALUE;
  18. }
  19. return shex.hProcess;
  20. }
  21. int _tmain(int argc, _TCHAR* argv[])
  22. {
  23. while ( 1 )
  24. {
  25. HMODULE hModule = LoadLibrary(_T("testDll.dll"));
  26. if ( NULL == hModule )
  27. return 0;
  28. pfnGenerateThread pfn = (pfnGenerateThread)GetProcAddress(hModule, "GenerateThread");
  29. if ( NULL == pfn )
  30. return 0;
  31. HANDLE hThread = pfn();
  32. // give thread time to start up
  33. Sleep(1000);
  34. // terminate thread.
  35. BOOL bOk = TerminateThread(hThread, 0);
  36. // dead lock in this function...
  37. RunProcess(argv[0], NULL);
  38. FreeLibrary(hModule);
  39. }
  40. return 0;
  41. }

DLL工程 testDll

  1. // DllMain.cpp
  2. #include "stdafx.h"
  3. #include "windows.h"
  4. BOOL APIENTRY DllMain( HMODULE hModule,
  5. DWORD ul_reason_for_call,
  6. LPVOID lpReserved
  7. )
  8. {
  9. switch (ul_reason_for_call)
  10. {
  11. case DLL_PROCESS_ATTACH:
  12. OutputDebugString(L"====> DLL_PROCESS_ATTACH called.\n");
  13. break;
  14. case DLL_THREAD_ATTACH:
  15. OutputDebugString(L"----> DLL_THREAD_ATTACH called.\n");
  16. break;
  17. case DLL_THREAD_DETACH:
  18. OutputDebugString(L"<---- DLL_THREAD_DETACH called.\n");
  19. // with LdrpLoaderLock held! sleep 5 seconds.
  20. Sleep(5000);
  21. break;
  22. case DLL_PROCESS_DETACH:
  23. OutputDebugString(L"<==== DLL_PROCESS_DETACH called.\n");
  24. break;
  25. }
  26. return TRUE;
  27. }
  1. // testDll.cpp
  2. #include "stdafx.h"
  3. #include "stdio.h"
  4. #include "process.h"
  5. #include "windows.h"
  6. void OutputCurrentThreadId()
  7. {
  8. TCHAR szBuffer[1024];
  9. swprintf_s(szBuffer, L"thread [0x%x], running & exiting...\n", GetCurrentThreadId());
  10. OutputDebugString(szBuffer);
  11. return;
  12. }
  13. unsigned __stdcall testProc(void *)
  14. {
  15. OutputCurrentThreadId();
  16. return 0;
  17. }
  18. HANDLE GenerateThread()
  19. {
  20. HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, &testProc, NULL, 0, NULL);
  21. return hThread;
  22. }


debug-deadlock-caused-by-TerminateThread-Demo.zip
10.63 KB

问题分析

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


DebugView

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

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

  1. 0:001> ~*kvn
  2. 0 Id: 18c0.1008 Suspend: 1 Teb: 7ffdf000 Unfrozen
  3. # ChildEBP RetAddr Args to Child
  4. 00 002bf614 775a6a64 77592278 00000064 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
  5. 01 002bf618 77592278 00000064 00000000 00000000 ntdll!NtWaitForSingleObject+0xc (FPO: [3,0,0])
  6. 02 002bf67c 7759215c 00000000 00000000 00000001 ntdll!RtlpWaitOnCriticalSection+0x13e (FPO: [Non-Fpo])
  7. 03 002bf6a4 775c00e1 77637340 77bf1b77 00000000 ntdll!RtlEnterCriticalSection+0x150 (FPO: [Non-Fpo])
  8. 04 002bf6dc 75587bc3 00000001 00000000 002bf704 ntdll!LdrLockLoaderLock+0xe4 (FPO: [Non-Fpo])
  9. 05 002bf728 7679215d 00000000 002bf73c 00000104 KERNELBASE!GetModuleFileNameW+0x75 (FPO: [Non-Fpo])
  10. 06 002bf948 76792112 002bfbb0 002bf968 7ffdb000 SHELL32!InRunDllProcess+0x39 (FPO: [Non-Fpo])
  11. *** WARNING: Unable to verify checksum for C:\Users\BianChengNan\Documents\Visual Studio 2012\Projects\testTerminateThread\Debug\testTerminateThread.exe
  12. 07 002bf95c 013714db 002bfa44 002bfcbc 002bfbc0 SHELL32!ShellExecuteExW+0x51 (FPO: [Non-Fpo])
  13. 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]
  14. 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]
  15. 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]
  16. 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]
  17. 0c 002bfd20 775c37eb 7ffdb000 77bf10cb 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
  18. 0d 002bfd60 775c37be 01371082 7ffdb000 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])
  19. 0e 002bfd78 00000000 01371082 7ffdb000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
  20. # 1 Id: 18c0.193c Suspend: 1 Teb: 7ffde000 Unfrozen
  21. # ChildEBP RetAddr Args to Child
  22. 00 0133fbac 775ff20f 76a71677 00000000 00000000 ntdll!DbgBreakPoint (FPO: [0,0,0])
  23. 01 0133fbdc 758ced6c 00000000 0133fc28 775c37eb ntdll!DbgUiRemoteBreakin+0x3c (FPO: [Non-Fpo])
  24. 02 0133fbe8 775c37eb 00000000 76a71183 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
  25. 03 0133fc28 775c37be 775ff1d3 00000000 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])
  26. 04 0133fc40 00000000 775ff1d3 00000000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

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

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

  1. 0:001> !cs -l
  2. -----------------------------------------
  3. DebugInfo = 0x77637540
  4. Critical section = 0x77637340 (ntdll!LdrpLoaderLock+0x0)
  5. LOCKED
  6. LockCount = 0x1
  7. WaiterWoken = No
  8. OwningThread = 0x00001400
  9. RecursionCount = 0x1
  10. LockSemaphore = 0x64
  11. SpinCount = 0x00000000

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

至此,真相大白。

总结

  • 不要随便用TerminateThread来强行杀死线程!
  • windbg真是windows下的调试神器。
  • !cs -l可以帮助我们快速的查找到死锁的关键段。

参考资料

[原]调试实战——使用windbg调试TerminateThread导致的死锁的更多相关文章

  1. [原]调试实战——使用windbg调试崩溃在ComFriendlyWaitMtaThreadProc

    原调试debugwindbgcrash崩溃COM 前言 这是几年前在项目中遇到的一个崩溃问题,崩溃在了ComFriendlyWaitMtaThreadProc()里,没有源码.耗费了我很大精力,最终通 ...

  2. [原]调试实战——使用windbg调试崩溃在ole32!CStdMarshal::DisconnectSrvIPIDs

    原调试debugwindbg崩溃crash 前言 最近程序会不定期崩溃,很是头疼!今晚终于忍无可忍,下决心要干掉它!从之前的几个相关的dump可以猜到是有接口未释放导致的问题,但没有确认到底是哪个接口 ...

  3. [原]调试实战——使用windbg调试excel启动时死锁

    原调试debugwindbg死锁deadlock 前言 这是几年前在项目中遇到的一个死锁问题,在博客园发布过.我对之前的笔记进行了整理重新发布于此. 本文假设小伙伴们知道一些基本概念,比如什么是.du ...

  4. [原]调试实战——使用windbg调试DLL卸载时的死锁

    原调试debugwindbg死锁deadlock 前言 最近我们的程序在退出时会卡住,调查发现是在卸载dll时死锁了.大概流程是这样的:我们的dll在加载的时候会创建一个工作线程,在卸载的时候,会设置 ...

  5. .NET高级调试系列-Windbg调试入门篇

    Windbg是.NET高级调试领域中不可或缺的一个工具和利器,也是日常我们分析解决问题的必备.准备近期写2篇精华文章,集中给大家分享一下如果通过Windbg进行.NET高级调试. 今天我们来一篇入门的 ...

  6. Windbg调试命令详解

    作者:张佩][原文:http://www.yiiyee.cn/Blog] 1. 概述 用户成功安装微软Windows调试工具集后,能够在安装目录下发现四个调试器程序,分别是:cdb.exe.ntsd. ...

  7. Windbg调试命令详解(3)

    3 进程与线程 既可以显示进程和线程列表,又可以显示指定进程或线程的详细信息.调试命令可以提供比taskmgr更详尽的进程资料,在调试过程中不可或缺. 3.1 进程命令 进程命令包括这些内容:显示进程 ...

  8. Windbg调试命令详解(2)

    转载注明>>  [作者:张佩][原文:http://blog.csdn.net/blog_index] 2. 符号与源码 符号与源码是调试过程中的重要因素,它们使得枯燥生硬的调试内容更容易 ...

  9. 调试SQLSERVER (二)使用Windbg调试SQLSERVER的环境设置

    调试SQLSERVER (二)使用Windbg调试SQLSERVER的环境设置 调试SQLSERVER (一)生成dump文件的方法调试SQLSERVER (三)使用Windbg调试SQLSERVER ...

随机推荐

  1. Centos7.4 kafka集群安装与kafka-eagle1.3.9的安装

    Centos7.4 kafka集群安装与kafka-eagle1.3.9的安装 集群规划: hostname Zookeeper Kafka kafka-eagle kafka01 √ √ √ kaf ...

  2. TX2安装pycharm&tensorflow

    https://blog.csdn.net/zt1091574181/article/details/88899668 TX2 (JetPack4.2)安装 Pycharm&TensorFlo ...

  3. Day2-T3

    原题目 Describe:质数问题 code: #pragma GCC optimize(2) #include<bits/stdc++.h> #define KKK 1200 using ...

  4. Android Studio Madual作为application的使用以及工作空间和modual的区别

    Android Studio Madual作为application的使用以及工作空间和modual的区别 前言: 写这篇文章的目的是因为自己使用Android Studio开发时进入了一个误区,后面 ...

  5. discuz伪静态问题(简单)

    提前声明一下我用的是宝塔面板.Linux系统.Nginx Web Server.经过一上午的摸索(我很菜了),终于在一个很无语的地方成功搞了伪静态1.2.点击查看当前的 Rewrite 规则3.我的是 ...

  6. Nginx负载均衡(转发)

    http://www.cnblogs.com/jalja/p/6117881.html 一.反向代理 正向代理: 客户端要获取的资源就在服务器上,客户端请求的资源路径就是最终响应资源的服务器路径,这就 ...

  7. Alpha版(内部测试版发布)

    使用说明: 使用环境:android 5.0以上 使用流程: 1.注册与登陆 可以通过游客和用户两个模式登陆 用户模式:进入后会有模拟位置图,每一环代表不同的距离 底部菜单栏表示不同的功能,消息栏可以 ...

  8. arm 裸机学习笔记

    位置无关码 bl 是位置无关码,指令中带的数值是,编译的时候,编译器计算好的,需要跳转的位置减去 bl 指令所在位置的结果.这样当程序最开始在 4k sram 中运行的时候,跳转的位置是在 0 + o ...

  9. Android自定义View——仿滴滴出行十大司机评选活动说明

    滴滴出行原版图 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 仿图 ? ? ? ? ? ? 1.分 ...

  10. git使用散记

    1.从远程clone一个项目 git clone ‘项目地址’ //clone项目地 git checkout -b dev origin/dev //远程已有dev分支,新建本地dev分支与远程相对 ...