使用 Process Explorer 和 Windbg 排查软件线程堵塞案例分享

news2024/11/18 7:49:10

目录

1、问题说明

2、线程堵塞的可能原因分析

3、使用Windbg和Process Explorer确定线程中发生了死循环

4、根据Windbg中显示的函数调用堆栈去查看源码,找到问题

4.1、在Windbg定位发生死循环的函数的方法

4.2、在Windbg中查看变量的值去辅助分析

4.3、是循环计数值没有累加导致的

5、可以从动态调试的Windbg中导出dump文件

6、最后


C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_2276111.html       软件运行过程中有线程发生堵塞,是时常发生的事,最近就遇到一个典型的案例,虽然分析过程不是很复杂,但很有代表性,在这里给大家分享一下这个问题的详细排查过程,希望能给大家提供一个借鉴或参考。使用Process Explorer和Windbg排查软件线程堵塞问题

1、问题说明

       某天,客户在使用我们软件的过程中遇到了问题,在其Windows10系统中运行我们的客户端软件,加入了一个会议,一直在开会,没有做其他的操作,中间某个时间点用鼠标去点击软件窗口时发现点击没反应,好像是软件UI界面卡死了!但会议窗口中还能看到正在动的远端视频,能听到远端的声音,应该是UI线程卡死了,其他线程还在正常运行。这个问题比较严重,无法操作软件界面了,如果领导那边出现这样的问题,就比较麻烦了。于是联系到我们,希望我们尽快协调研发人员排查定位一下。

       我们研发这边接到任务后,和客户取得了联系,通过远程软件远程连到他的电脑上。我们初步怀疑是软件的UI界面所在的主线程卡死或堵塞了,于是将SPY++、Process Explorer和Windbg等工具拷贝到客户的电脑上准备详细分析一下。


       在这里,给大家重点推荐一下我的几个热门畅销专栏:

专栏1:(该专栏订阅量接近350个,有很强的实战参考价值,广受好评!专栏文章持续更新中,预计更新到200篇以上!)

C++软件调试与异常排查从入门到精通系列文章汇总icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931

本专栏根据近几年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的实战问题分析实例,带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!

专栏中的文章均是通过项目实战总结出来的(通过项目实战积累了大量的异常排查素材和案例),有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!

专栏2: 

C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.html

以多年的开发实战为基础,总结并讲解一些的C/C++基础与进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域的多个方面的内容,同时给出C/C++及网络方面的常见笔试面试题,并详细讲述Visual Studio常用调试手段与技巧!

专栏3: 

开源组件及数据库技术icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_12458859.html

以多年的开发实战为基础,分享一些开源组件及数据库技术! 


2、线程堵塞的可能原因分析

       软件为了并发处理事务一般都使用了多线程,如果代码处理不当,可能就会出现线程堵塞或卡死问题。一般是个别线程的堵塞,其他线程还是正常执行的,发生堵塞的线程中处理的业务出现异常。

       对于客户端软件,一旦有线程发生异常,我们可以通过软件界面的异常表现感知到,然后通过日志去大概地确定发生异常的业务线程。在本例中,UI界面不能操作了,判断应该是UI界面所在的主线程堵塞了会议中音视频的解码播放是在底层模块的其他线程中进行的,这些线程是正常运行的,所以还能看到视频画面、听到会议中的声音。

       导致线程发生堵塞或卡死的原因主要有两种

1)死锁:软件中多线程发生了死锁,某个线程需要获取某个锁,但因为死锁导致该锁一直没释放,导致线程一直卡在WaitForSingleObject等等待函数接口上没返回,所以线程卡住了;
2)死循环:当前线程中某个函数中发生了死循环,接口调用一直没返回,导致线程卡住了。如果是死循环,还会导致一个现象,死循环会占用大量的CPU时间片,导致程序进程占用较高的CPU,用Process Explorer工具则可以看到某个线程的CPU占用的特别高,这个线程就是发生死循环的线程。

       在本案例中,出问题的是UI客户端软件,客户端界面没法操作了,可能是界面所在的UI线程(UI程序的主线程)发生了堵塞,可能是死锁或死循环导致的UI界面没法操作,还有另外一个原因,可能是窗口被disable了,窗口之前在执行某个操作时被disable了(比如弹出了一个模态框),在执行完操作后出现异常,没有将窗口恢复到enable状态,这个问题场景我们以前在项目中遇到过几次。我们可以用SPY++工具查看一下当前不能操作的窗口属性,如果是当前的窗口被disable导致窗口不能操作的,那么使用SPY++查看的窗口属性中应该能看到 WS_DISABLED 窗口风格,如下所示:

本例中界面不能操作不是窗口被disable导致的

       对于腾讯会议、企业微信、字节飞书、阿里钉钉等这类Windows桌面客户端软件,UI界面所在的线程称为UI线程,也是软件的主线程。如果UI界面或窗口不能操作了,可能就是UI线程出问题,发生堵塞了。

在Windbg中,被分析的软件有多个线程,线程除了有线程id,还有个线程编号,编号从0开始,UI主线程的编号就是0,Windbg切换线程的命令~ns中的n就是线程编号。

3、使用Windbg和Process Explorer确定线程中发生了死循环

       当时软件的UI窗口不能操作了,使用SPY++查看窗口属性,窗口并没有被disable掉,所以基本可以断定,UI界面所在的UI线程发生堵塞了。

       那这个线程堵塞是死锁引起的,还是死循环引起的呢其实要确定这个问题,很简单,当前程序进程还在的,只需要将Windbg附加到问题进程上,切换到0号UI主线程(上面已经讲了,UI程序的UI界面就在UI主线程中),然后查看该线程的函数调用堆栈,如果堆栈中调用了WaitForSingleObject或NtWaitForAlertByThreadId(进入临界区时会调用该函数)等等待函数时,就能确定是线程发生死锁了。

       这个地方需要提一下,线程堵塞和程序崩溃是两个完全不同的概念和场景,要注意区分一下

1)对于堵塞,堵塞只是发生在个别线程中,其他线程还是正常的,程序进程没有退出,进程还在的,此时可以将Windbg附加到进程上调试的。
2)对于程序崩溃,如果没弹出崩溃提示框,程序直接闪退,程序进程就不存在了,就没有机会将Windbg附加到进程上分析了。如果程序没有生成dump文件,只能在下次运行程序时将Windbg附加到进程上动态调试(Windbg和目标程序一起跑),然后去复现崩溃,一旦程序发生崩溃,Windbg就会感知到并中断下来,就可以分析了。此外,如果程序崩溃时弹出了系统报错提示框,只要不将该提示框点掉,则进程还在的,此时还有机会将Windbg附加到进程上进行分析的,我们在项目中遇到过这样的场景。

       将Windbg附加到当前出问题的程序进程上,使用~0s命令切换到UI线程中,然后输入kn命令查看UI线程此刻的函数调用堆栈,如下所示:

因为没有加载pdb符号文件,所以堆栈中看不到具体的函数名。于是使用lm命令查看堆栈中dll模块的时间戳,到我们的文件服务器上找到对应时间点的pdb文件(我们已经将各个版本的安装包、二进制文件和pdb文件保存到文件服务器上,维护起来了),然后将pdb文件路径设置到Windbg中,重新执行~0s和kn命令查看UI线程的详细函数调用堆栈信息,如下所示:

加载pdb文件后,调用堆栈中就可以看到具体的函数名了,但没有看到WaitForSingleObject或NtWaitForAlertByThreadId(进入临界区时会调用该函数)等等待函数的调用,所以基本可以断定UI线程的堵塞和死锁没有关系

       基本只有一种可能,UI线程中的某个函数发生死循环了。死循环就是一直在执行代码,会占用大量的CPU时间片,死循环所在的线程会占用较高的CPU比例,直接导致程序进程占用较高的CPU。这个我们可以使用Process Explorer工具核验一下,打开Process Explorer,在进程列表中找到当前出问题的进程,然后双击该进程条目,查看进程的属性,在弹出的属性页面中点击Threads标签页,在该页面中可以查看到当前进程的各个线程信息,包括线程占用的CPU比例以及线程此刻的函数调用堆栈。

Process Explorer查看线程的函数调用堆栈可能不准确,可以使用另一个类似的工具Process Hacker,这个工具看线程的函数调用堆栈比较准确!

       查看到如下的线程列表:

果然看到了一个线程占用了20%的CPU,但只能看到线程号,我们如何确定这个线程就是当前出问题的UI主线程呢?

       其实很简单,我们可以回到Windbg中,使用~命令打印出当前问题进程的所有线程,如下所示:

我们上面讲了,UI应用程序的UI主线程在Windbg中的线程序号就是0,看着上述线程列表,第一个条目就是0号线程,就是UI主线程,找到其对应的线程id为0x2e20(16进制),转换成10进制数据为:11808,即UI线程的线程id为11808,然后跳回到Process Explorer的线程列表页面,占用CPU高的线程就是UI主线程。所以,可以确定UI线程中有函数发生死循环了

4、根据Windbg中显示的函数调用堆栈去查看源码,找到问题

       确定UI线程发生了死循环,下面只要根据Windbg中显示的UI线程的函数调用堆栈,去查看C++源码去分析为什么出现死循环就可以了。

       回到Windbg中,使用~0s命令切换到UI线程中,然后输入kn命令查看UI线程的函数调用堆栈,如下所示:

之前已经设置pdb到windbg中了,所以堆栈中显示了详细的函数名和代码行号,看到最后调用的一个函数是xxxlib!LoginManager::OnSrvAddrsChangedNtf。

4.1、在Windbg定位发生死循环的函数的方法

       这个地方说几个关于使用Windbg排查死循环的技巧。

我们当前的问题相对较简单,没用到此处提的方法。大家后面可能会用到,所以在此说明一下。

       如果当前要确定当前线程是否发生死循环,可以尝试多次输入g命令,让程序继续跑,然后再break中断下来,如果每次堆栈都是一样的话,可能发生死循环了。但这个需要我们根据调用堆栈辨别一下,不是堆栈一样就一定是死循环,比如我们的某个业务线程就在循环执行业务,每次查看的堆栈肯定是一样的,这不是程序中发生了死循环。业务线程中虽然是个循环,但中间会人为sleep一下,不让线程跑满。

       此外,我们在Windbg中可以使用bp命令设置断点,比如可以在函数调用堆栈中的多个函数中设置断点,然后输入g命令让程序继续跑,看都命中了哪些函数中的断点,这样就能确定死循环发生在哪个函数中了。

Windbg中支持设置断点,相关的命令如下:

1)bp:添加断点;

2)bc:清除断点;

3)bl:列出断点;

4)ba:设置数据断点。

这几个命令的详细说明及使用方法,可以查看Windbg帮助文档,文档中有相关的示例可以参考。

4.2、在Windbg中查看变量的值去辅助分析

       以前我们多次讲过,可以尝试在Windbg中查看函数中局部变量或者函数所在类的成员变量的值,某些变量的值可能是分析问题的关键线索。在Windbg中查看变量的值有几个场景,此处说明一下:

1)当前用Windbg分析的是小dump文件(程序中安装的异常捕获模块捕获到的,文件比较小,可能就几MB大小),dump文件中只保存了少部分变量的值,能不能看到自己想看的变量的值,要看运气的。
2)当前用Windbg分析的是全dump文件(从Windows任务管理器中导出的或者从正在动态调试的Windbg中使用.dump命令导出的),全dump保存了所有内存信息,其大小接近当前进程占用的用户态虚拟内存的大小,可以看到所有变量的值。

如果要查看程序中全局变量的值,可以使用x命令去搜索,前提是要有pdb符号文件,因为pdb文件中有变量的符号信息。

       当前函数调用堆栈显示的最后一个函数是LoginManager::OnSrvAddrsChangedNtf,代码如下:

估计是函数中的for循环发生了死循环,for循环的条件中访问了TServerAddrs_Api结构体变量的dwCount成员的值,是不是这个dwCount值有问题?是一个异常大的值,导致循环一直跳不出来?于是想查看该指针变量的值,于是点击函数调用堆栈前面的序号:

将所在函数的栈展开:

只能看到函数所在类对象指针this值,但我们想看TServerAddrs_Api* ptServerAddrs指针变量展开的结构体对象中的值。并没有显示,但该结构体指针对象的值就是wParam值,wParam值是能看到的,所以我们是有办法看到TServerAddrs_Api* ptServerAddrs指针变量展开的结构体对象中的值的。

       我们把wParam局部变量中的值0x336200a0当成指针变量TServerAddrs_Api* ptServerAddrs的值去解析,需要使用Windbg的一个较复杂的命令,但我们并没有记这个命令,该怎么办呢?我们点击this指针的超链接:

这个超链接自动生成查看这个指针中变量的值的Windbg命令并执行该命令,我们直接借鉴这个自动生成的命令:

dx -r1 ((xxxlib!LoginManager *)0x55a8cc0)

将之改成:

dx -r1 ((xxxlib!TServerAddrs_Api *)0x336200a0)

然后执行该命令:

可以看到dwCount值为1,所以底层传上来的数据不是异常值,不是上面假定的问题。

4.3、是循环计数值没有累加导致的

       我们继续查看函数pcmt_mtclib!LoginManager::OnSrvAddrsChangedNtf:

这个接口是处理底层模块投递上来的消息的(消息的处理函数),乍一看函数没有明显问题,难道是底层一直在抛这个消息,导致我们的代码一直在执行,导致CPU占用高?

       我甚至找来了维护底层模块的同事,让他们看日志,看看是不是底层一直在持续抛同一个消息。同事并没有找到问题,后来我无意中瞄了一下代码,居然是for循环中索引值没有累加导致的如下所示:

没有对索引值进行自加操作,导致了死循环。这么简单的问题,居然如此动干戈!属实不应该啊!

5、可以从动态调试的Windbg中导出dump文件

       当前我们到客户的电脑上将Windbg附加到目标进程上调试时,如果一时半会没查出问题,我们不能一直占用别人的电脑,他们还有自己的事情,我们可以使用.dump命令将进程的上下文信息导出到dump文件中:

.dump /ma D:\1214.dmp

然后将dump文件发回去,我们事后再去详细分析。

6、最后

       这个案例给我们展示了如何使用工具高效地排查软件运行过程中遇到的问题,虽然不复杂,但讲到了使用Windbg等工具的多个细节,有很大的参考价值!我们在研学技能时,要多关注细节,多关注分析问题的思路和办法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1352147.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ModStartCMS v7.9.0 内容推荐支持,用户授权升级

ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用,支持后台一键快速安装,让开发者能快的实现业务功能开发。 系统完全开源,基于 Apache 2.0 开源协议,免费且不限制商业使用。 功能特性 丰富的模块市…

CMake入门教程【基础篇】HelloCMake

文章目录 概述核心实现代码示例使用技巧注意事项 总结 概述 CMake是一个强大的跨平台构建系统,广泛用于C和C项目。它使用简单的配置文件来生成标准的构建文件,从而使得构建过程跨平台且易于管理。本教程将通过一个“Hello World”示例(命名为…

必看 | 如何用「八爪鱼RPA」搬迁旧帮助中心站点到「HelpLook」?

对于工具类产品而言,帮助中心的搭建是非常重要的:一个好用的帮助中心,不仅可以帮助用户快速找到所需内容,提升用户的满意度;还可以减轻客服人员的压力,为公司节约大量的人力资源。 以八爪鱼采集器的帮助中心…

【Leetcode】466. 统计重复个数

文章目录 题目思路代码 题目 466. 统计重复个数 思路 题目要求找出一个最大整数 m,使得经过 n2 个字符串 s2 组成的字符串能够被经过 n1 个字符串 s1 组成的字符串完全包含的次数。使用动态规划来记录每个位置匹配的情况,并通过循环节的分析来计算最…

安全数据交换系统:有效提升网间文件交换能力

各级政府部门和金融、能源、电力这些行业,以及一些大中型企业组织,为了保护内部的重要数据不外泄,普遍都采用了多网络并行的方式,也是做了网络隔离划分,不同的网络拥有不同的密级以及人员权限。然后再通过安全数据交换…

C++Qt6 哈夫曼编码求解 数据结构课程设计 | JorbanS

一、 问题描述 在进行程序设计时,通常给每一个字符标记一个单独的代码来表示一组字符,即编码。在进行二进制编码时,假设所有的代码都等长,那么表示 n 个不同的字符需要 位,称为等长编码。如果每个字符的使用频率相等&…

系列七、Ribbon

一、Ribbon 1.1、概述 Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具,是Netflix发布的一款开源项目,其主要功能是提供客户端的软件负载均衡算法和服务调用,Ribbon客户端组件提供一系列完善的配置项,例如&#xff1a…

免费证书Let’s Encrypt

免费SSL证书是一种用于保护网站数据传输安全的加密技术。它能够确保用户与网站之间的信息传输是加密的,防止被黑客窃取或篡改。随着网络安全意识的提高,越来越多的网站开始使用SSL证书来保护用户的隐私和数据安全。 在过去,SSL证书需要购买才…

OpenAI官方发布ChatGPT 提示词指南,六大策略让你玩转大语言模型!

OpenAI前段时间官方放出了自己的提示工程指南,从大模型小白到开发者,都可以从中消化出不少营养。看看全世界最懂大模型的人,是怎么写提示词的。官方给出了6 个大提示策略: 1、清晰的指令: 告诉AI你具体想要什么。比如…

ElasticSearch的DSL查询语法解析

Elasticsearch提供了基于ISON的DSL (Domain Specific Lanquage)来定义查询。 目录 一、常见查询类型 二、DSLQuery基本语法 三、全文检索查询 3.1 match查询:会对用户输入内容分词,常用于搜索框搜索 ,语法: 3.2 multi match…

RK3568 学习笔记 : 解决 linux_sdk 编译 python 版本报错问题

前言 最近买了 【正点原子】 的 RK3568 开发板,下载了 开发板的资料,包括 Linux SDK,这个 Linux SDK 占用的空间比较大,扩展了一下 VM 虚拟机 ubuntu 20.04 的硬盘空间,编译才正常通过。 编译 RK3568 Linux SDK 时&am…

STM32存储左右互搏 SPI总线读写FRAM MB85RS2M

STM32存储左右互搏 SPI总线读写FRAM MB85RS2M 在中低容量存储领域,除了FLASH的使用,,还有铁电存储器FRAM的使用,相对于FLASH,FRAM写操作时不需要预擦除,所以执行写操作时可以达到更高的速度,其…

Postgresql源码(119)PL/pgSQL中ExprContext的生命周期

前言 在PL/pgSQL语言中,执行任何SQL都需要通过SPI调用SQL层解析执行,例如在SQL层执行表达式的入口: static bool exec_eval_simple_expr(PLpgSQL_execstate *estate,PLpgSQL_expr *expr,Datum *result,bool *isNull,Oid *rettype,int32 *re…

RK3568驱动指南|第九篇 设备模型-第95章 创建属性文件并实现读写功能实验1

瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工…

《PySpark大数据分析实战》-23.Pandas介绍DataFrame介绍

📋 博主简介 💖 作者简介:大家好,我是wux_labs。😜 热衷于各种主流技术,热爱数据科学、机器学习、云计算、人工智能。 通过了TiDB数据库专员(PCTA)、TiDB数据库专家(PCTP…

STM32H5XX和STM32H7XX选型对比

文章目录 STM32H563/H573STM32H743/753对比内核不同H5独有安全管理器H7的外设资源更丰富 STM32H563/H573 STM32H563和STM32H573微控制器扩展了STM32高性能产品组合。这两款微控制器具有增强的性能和安全性、更高的能效和更多的片内外设。 STM32H563/573产品系列提供1至2 MB的…

ChatGPT 4.0真的值得花钱买入吗?

性能提升: ChatGPT 4.0的推出不仅意味着更先进的技术,还代表着更强大的性能。相较于3.5,4.0在处理任务时更为高效,响应更迅速。 更智能的理解: 随着版本的升级,ChatGPT 4.0对语境的理解能力得到了进一步的…

Unity游戏资源更新(AB包)

目录 前言: 一、什么是AssetBundle 二、AssetBudle的基本使用 1.AssetBundle打包 2.BuildAssetBundle BuildAssetBundleOptions BuildTarget 示例 3.AssetBundle的加载 LoadFromFile LoadFromMemory LoadFromMemoryAsync UnityWebRequestAsssetBundle 前…

集群部署篇--Redis 集群动态伸缩

文章目录 前言一、redis 节点的添加1.1 redis 的实例部署:1.2 redis 节点添加:1.3 槽位分配:1.4 添加从节点: 二、redis 节点的减少2.1 移除主节点2.1.1 迁移槽位2.1.1 删除节点: 三、redis 删除节点的重新加入3.1 加入…

【数据不完整?用EM算法填补缺失】期望值最大化 EM 算法:睹始知终

期望值最大化算法 EM:睹始知终 算法思想算法推导算法流程E步骤:期望M步骤:最大化陷入局部最优的原因 算法应用高斯混合模型(Gaussian Mixture Model, GMM)问题描述输入输出Python代码实现 算法思想 期望值最大化方法&a…