原调试debugwindbgcrash崩溃COM

前言

这是几年前在项目中遇到的一个崩溃问题,崩溃在了ComFriendlyWaitMtaThreadProc()里,没有源码。耗费了我很大精力,最终通过反汇编并结合原代码才最终搞清楚了事情的来龙去脉。本文的分析还是基于真实项目进行的,中间略去了很多反汇编的分析工作。文末有我整理的测试代码,大家可以实际体验一把TerminateThread()的杀伤力。

背景介绍

大概情况是这样的:程序启动的时候,会通过LoadLibrary()加载插件模块。其中的UIA模块会开启一个工作线程,工作线程会安装UIA相关的钩子来监听UIA事件,程序在退出的时候会调用每个插件模块的导出函数做清理工作,然后调用FreeLibrary()释放这个插件模块。UIA模块的清理函数会通知工作线程退出,工作线程收到退出命令后会卸载相关钩子。清理函数会等待工作线程一段时间,等待超时就通过TerminateThread()强制杀死工作线程。程序退出时偶尔会崩溃在ComFriendlyWaitMtaThreadProc()中。背景介绍完毕,下面开始分析dump文件。

问题分析

使用windbg载入dump文件,输入.ecxr


ecxr

从输出结果可以看出是访问到无效的地址0x07acf914了,使用命令!address 07acf914查看该地址的信息:


address-07acf914

从输出结果可以看出该地址确实是不可访问的。我们需要看看0x07acf914 是从哪里来的,该值来自edi+4指向的地址所存储的值,那么edi的值是哪里来的呢?让我们看看前几条汇编指令是什么,输入ub 7303f614 L10


ub-7303f614-L10

{% note info %}

说明:

7303f614这个地址是我通过7303f611+3算来的(3是地址7303f611对应的指令长度),这样就可以在输出结果中看到导致崩溃的这条指令啦。当然这里输入ub 7303f611也没关系(我们关心的是edi的值是哪里来的),只不过我们看不到7303f611对应的指令了。

{% endnote %}

我们发现edi的值来自ebp+8对应的地址内容。研究过反汇编的小伙伴儿应该对ebp+n比较敏感,有木有?在windows下,32位进程中,ebp+8指向了调用约定为__stdcall的函数的第一个参数。这里的ebp+8是否指向第一个参数,我们需要通过ComFriendlyWaitMtaThreadProc()的调用约定来判断。

输入k查看调用栈:


k

从调用栈可知,ComFriendlyWaitMtaThreadProc()是在新线程中执行的,通过查看CreateThread()的原型我们可以知道 ComFriendlyWaitMtaThreadProc() 原型应该满足typedef DWORD (__stdcall LPTHREAD_START_ROUTINE)(LPVOID lpThreadParameter);

综上可知ebp+8确实指向了第一个参数,这个参数指向了一个非法的地址!

我猜测有如下两种可能:

  1. 调用函数传递了一个合法地址,由于某种原因这个地址无效了。(最后证明,我们的代码里传递了一个栈上的局部变量,但是调用线程挂掉了,栈对应的内存无效了!)
  2. 代码中存在bug,传递参数的时候就传的有问题!(可能性太低了,对自己的代码比较有信心:joy:)

追本溯源

单纯从dump看不出更多的信息了!于是我决定给 ComFriendlyWaitMtaThreadProc()下断点,看看是否能找到是谁创建了这个线程! 执行如下命令:

bu uiautomationcore!ComFriendlyWaitMtaThreadProc
g

断下来后,使用~*k查看所有线程的调用栈,经过排查,11号线程18号线程最值得怀疑。


thread-11

thread-18

18号线程是出问题的线程。

11号线程包含我们自己的代码,而且ComFriendlyWaitForSingleObject()ComFriendlyWaitMtaThreadProc()相似度不要太高。

大胆猜测内部逻辑应该是:函数AddWinEvent()内部会创建一个工作线程,uiautomationcore!ComFriendlyWaitMtaThreadProc()是新线程的入口函数,创建完线程后通过调用ComFriendlyWaitForSingleObject()等待一个内核对象(通过反汇编确认该对象为Event)来等待工作线程结束。理所当然的,uiautomationcore!ComFriendlyWaitMtaThreadProc()结束后应该会激活这个内核对象。

经过一系列的小()心()谨()慎()的反汇编,检查代码,确认逻辑,最终得到如下结论:

当主程序退出时,主线程做清理工作,会等待11号线程一段时间,如果等待超时就会调用TerminateThread()将其强行杀死(正是这个TerminateThread()的调用导致了崩溃)! 而18号线程会用到11号线程传过来的线程参数(11号线程的一个局部变量),如果11号线程被意外杀死了,那么11号线程的局部变量对应的地址就无效了,对这块内存的操作就是未定义的!至此真相大白!(中间还有很多相关细节太琐碎了,没有一一列出,这里直接写出了结论。)

解决

知道原因了,解决起来就很简单了。去掉对TerminateThread()的调用,由操作系统来清理未结束的线程即可。由于主程序会调用FreeLibrary()释放插件模块,所以主程序还需要特殊处理下,在退出的时候不调用FreeLibrary()释放UIA模块。

为了让大家更好的理解问题的本质,更直观的感受下TerminateThread()的杀伤力,我特意编写了如下测试代码来模拟我在项目里遇到的问题。

测试代码

#include "stdafx.h"
#include "windows.h"
#include "process.h" unsigned __stdcall SubWorkProc(void* param)
{
int* data = (int*)param;
while (1)
{
*data = 1;
Sleep(1000);
} return 0;
} unsigned __stdcall WorkProc(void* param)
{
int data = 0;
_beginthreadex(NULL, 0, &SubWorkProc, &data, 0, NULL);
while (1)
{
Sleep(1000);
} return 0;
} int _tmain(int argc, _TCHAR* argv[])
{
auto hThread = (HANDLE)_beginthreadex(NULL, 0, &WorkProc, NULL, 0, NULL);
Sleep(1000);
TerminateThread(hThread, 0xdead);
Sleep(INFINITE);
return 0;
}

总结

  • 永远不要使用TerminateThread()强制杀线程!除非你想故意埋坑!:joy:
  • windbg真是windows下的调试利器,再向大家安利一波。
  • 调试崩溃,死锁问题要大胆推测,小心求证。

参考资料

  • windbg帮助文档
  • 《格蠹汇编》

[原]调试实战——使用windbg调试崩溃在ComFriendlyWaitMtaThreadProc的更多相关文章

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

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

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

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

  3. [原]调试实战——使用windbg调试TerminateThread导致的死锁

    原调试debugwindbg死锁deadlock 前言 项目里的一个升级程序偶尔会死锁,查看dump后发现是死在了ShellExecuteExW里.经验少,不知道为什么,于是在高端调试论坛里发帖求助, ...

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

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

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

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

  6. Windbg调试命令详解

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

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

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

  8. windbg调试.net程序

    1. 解决线上.NET应用程序的如下问题: 崩溃 CPU高 程序异常 程序Hang死 2. 安装WinDbg: http://msdn.microsoft.com/en-us/windows/hard ...

  9. WinDbg调试DMP格式文件

    前言:WinDbg是微软开发的免费源代码级的调试工具.WinDbg可以用于Kernel模式调试和用户模式调试,还可以调试Dump文件.本文的讨论是在安装了Debugging Tools for Win ...

随机推荐

  1. bzoj 1832: [AHOI2008]聚会

    良心题2333 三个点两两求一遍就行,最小肯定是在某2个点的lca处,(肯定让第三个人去找2个人,不能让2个人一起去找第三个人233) #include<bits/stdc++.h> #d ...

  2. SQL的查询结果复制到Excel 带标题Head 有换行符导致换行错乱 的解决方案

    将SQL查询到的结果保存为excel有很多方法,其中最简单的就是直接复制粘贴了 1.带Head的复制粘贴 1)先左击红色区域实现选择所有数据 2)随后右击选择Copy with Headers  再粘 ...

  3. Mac下使用Hexo搭建个人博客

    Hexo介绍 利用原作者的一句话:A fast,simple&powerful blog framework,powered by Node.js Hexo是基于Node.js的博客平台,He ...

  4. Android进阶——Crash异常捕获并发送到服务器

    在项目中,我们常常会遇到Crash的现象,也就是程序崩溃的时候,这个时候最常看到的就是这个界面 如果你的项目已经发布到市场上了,这样的崩溃对于开发人员是看不到的,所以我们得想方法将崩溃信息发送到服务器 ...

  5. 2020/2/13 bluecmsv1.6sp1代码审计

    0x00 前言 从今天开始审计一些小的cms,一周内至少审计一种,中间可能会写点别的有趣的东西 0x01 安装好后,看到登陆框,用万能密码打一发,无果,尝试重装,可以重装.有robots.txt 看u ...

  6. 新iPhone上市会让富士康迎来新一轮的“用工荒”吗?

    新iPhone上市,在接下来的几个月内,中国制造会迎来一段非常忙碌或者说忙乱的日子,之所以说忙碌,是在于苹果的订单量常常大得吓人,而之所以说忙乱,则在于连同富士康.和硕.绿点科技等制造业巨头都可能遭遇 ...

  7. CSS的Flex弹性布局概念

    1.Flex概念: Flex是Flexible Box的缩写,顾名思义为“弹性布局”,用来为盒装模型提供最大的灵活性. 任何一个容器都可以指定为Flex 布局. 设为flex布局以后,子元素的floa ...

  8. group_concat用法以及字符串太长显示不全

    由于group_concat默认的长度是1024,所以要将最大长度修改 首先执行 SET SESSION group_concat_max_len = 10240;#一次查询有效 然后再进行拼接 se ...

  9. Centos7 死循环登录问题

    问题:用户名和密码输入正确,登录后屏幕闪一下又回到初始的登录界面.不知道具体什么原因引起的,先记录下不知道是否正确的解决方案,网上找了些相关的方案有的也实现不了,可能这个问题跟装的虚拟机的版本也有关系 ...

  10. go 汇编

    汇编文件 go tool compile -S main.go>main.S go tool compile 可以看帮助 -N 关闭优化