使用 Process Explorer 和 Windbg 排查软件线程堵塞问题

目录

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.rhkb.cn/news/235631.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Qt 窗口阴影边框

环境:Qt 5.15 VS2019 方法一:QGraphicsDropShadowEffect 实现方法参考链接:https://blog.csdn.net/goforwardtostep/article/details/99549750 使用此方法添加窗口阴影,会出现警告信息: 且窗口最大化与还原切换时会…

HCIA-Datacom题库(自己整理分类的)_09_Telent协议【13道题】

一、单选 1.某公司网络管理员希望能够远程管理分支机构的网络设备,则下面哪个协议会被用到? RSTP CIDR Telnet VLSM 2.以下哪种远程登录方式最安全? Telnet Stelnet v100 Stelnet v2 Stelnet v1 解析: Telnet 明文传输…

spring Security源码讲解-Sevlet过滤器调用springSecurty过滤器的流程

承接上文 上一节 http://t.csdnimg.cn/ueSAl 最后讲到了过滤器收集完成注入容器,这节我们来讲Security的Filter是怎么被Spring调用的。 我们先看webSecurity的performBuild方法(), 也就是说,最终返回的过滤器对象实例有两种情况当我们配置debugEnabl…

NIO通信代码示例

NIO通信架构图 1.Client NioClient package nio;import constant.Constant;import java.io.IOException; import java.util.Scanner;public class NioClient {private static NioClientHandle nioClientHandle;public static void start() {nioClientHandle new NioClientHa…

MongoDB快速实战与基本原理

MongoDB 介绍 什么是 MongoDB MongoDB 是一个文档数据库(以 JSON 为数据模型),由 C 语言编写,旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。文档来自于“JSON Document”,并非我们一般理解的 PDF、WORD 文档…

【数据采集与预处理】流数据采集工具Flume

目录 一、Flume简介 (一)Flume定义 (二)Flume作用 二、Flume组成架构 三、Flume安装配置 (一)下载Flume (二)解压安装包 (三)配置环境变量 &#xf…

python高校舆情分析系统+可视化+情感分析 舆情分析+Flask框架(源码+文档)✅

毕业设计:2023-2024年计算机专业毕业设计选题汇总(建议收藏) 毕业设计:2023-2024年最新最全计算机专业毕设选题推荐汇总 🍅感兴趣的可以先收藏起来,点赞、关注不迷路,大家在毕设选题&#xff…

适用于 Windows 的 12 个最佳免费磁盘分区管理器软件

分区是与其他部分分开的硬盘驱动器部分。它使您能够将硬盘划分为不同的逻辑部分。分区软件是一种工具,可帮助您执行基本选项,例如创建、调整大小和删除物理磁盘的分区。许多此类程序允许您更改磁盘片的标签以便于识别数据。 适用于 Windows 的 12 个最佳…

【PaperReading】3. PTP

Category Content 论文题目 Position-guided Text Prompt for Vision-Language Pre-training Code: ptp 作者 Alex Jinpeng Wang (Sea AI Lab), Pan Zhou (Sea AI Lab), Mike Zheng Shou (Show Lab, National University of Singapore), Shuicheng Yan (Sea AI Lab) 另一篇…

爬虫01-爬虫原理以及爬虫前期准备工作

文章目录 1 爬虫基本原理什么是爬虫爬虫功能详解爬虫基本流程两个概念:request和response 2 一些问题爬虫能抓取什么样的数据?抓取的数据怎么提取部分内容?数据解析方式。为什么我爬虫抓取的数据和浏览器看到的不一样怎样解决JavaScript渲染的…

计算数学表达式的程序(Java课程设计)

1. 课设团队介绍 团队名称 团队成 员介绍 任务分配 团队成员博客 XQ Warriors 徐维辉 负责计算器数据的算法操作,如平方数、加减乘除,显示历史计算记录 无 邱良厦(组长) 负责计算器的图形设计,把输入和结果显…

公共用例库计划--个人版(二)主体界面设计

1、任务概述 计划内容:完成公共用例库的开发实施工作,包括需求分析、系统设计、开发、测试、打包、运行维护等工作。 1.1、 已完成: 需求分析、数据库表的设计:公共用例库计划–个人版(一) 1.2、 本次待完…

2024新年烟花代码完整版

文章目录 前言烟花效果展示使用教程查看源码HTML代码CSS代码JavaScript 新年祝福 前言 在这个充满希望和激动的2024年,新的一年即将拉开帷幕,而数字科技的创新与发展也如火如荼。烟花绚丽多彩的绽放,一直以来都是新年庆典中不可或缺的元素。…

微信小程序 组件component ts用法

还在为 使用了ts 但是组件内显示this.setData/this.data.xxx ts报错 觉得难看吗? 还在为明明定义了applyInfo,明明应该有setData为何报错? 还在为不知道如何写类型而烦心吗? 不如转变思路将methods看成为一个对象 增加断言 as a…

实现多级缓存(Redis+Caffeine)

文章目录 多级缓存的概述多级缓存的优势 多级缓存的概述 在高性能的服务架构设计中,缓存是一个不可或缺的环节。在实际的项目中,我们通常会将一些热点数据存储到Redis或MemCache这类缓存中间件中,只有当缓存的访问没有命中时再查询数据库。在…

公网环境使用移动端设备+cpolar远程访问本地群晖nas上的影视资源

文章目录 1.使用环境要求:2.下载群晖videostation:3.公网访问本地群晖videostation中的电影:4.公网条件下使用电脑浏览器访问本地群晖video station5.公网条件下使用移动端(搭载安卓,ios,ipados等系统的设备…

小家电应用解决方案以及选型指南

电磁炉是现代厨房中常见的一种小家电产品,它利用电磁感应加热原理,可以快速、高效地进行烹饪。在电磁炉的设计和制造过程中,功率开关芯片的选择对于产品的性能和成本有着重要的影响。 针对电磁炉的应用需求,推荐采用LED驱动芯片S…

蓝桥杯省赛无忧 STL 课件12 vector

01 vector的定义和特性 02 vector的常用函数 03 vector排序去重 示例&#xff1a; #include<bits/stdc.h> using namespace std; int main(){vector<int> vec {5,2,8,1,9};sort(vec.begin(),vec.end());for(const auto& num : vec){cout<<num<<&q…

Centos7升级openssl到openssl1.1.1

Centos7升级openssl到openssl1.1.1 1、先查看openssl版本&#xff1a;openssl version 2、Centos7升级openssl到openssl1.1.1 升级步骤 #1、更新所有现有的软件包列表并安装最新的软件包&#xff1a; $sudo yum update #2、接下来&#xff0c;我们需要从源代码编译和构建OpenS…

【原生部署】SpringBoot+Vue前后端分离项目

本次主要讲解SpringBootVue前后端完全分离项目在CentOS云服务器上的环境搭建与部署过程&#xff0c;我们主要讲解原生部署。 一.原生部署概念 原生部署是指将应用程序&#xff08;一般是指软件、应用或服务&#xff09;在底层的操作系统环境中直接运行和部署&#xff0c;而不…