目录
1、问题说明
2、Process Explorer与Windbg工具介绍
2.1、Process Explorer工具
2.2、Windbg调试器
3、引发程序高CPU占用的原因分析
4、在任务管理器和Process Explorer中查看目标程序的CPU占用
5、使用Process Explorer和Process Hacker查看占用CPU高的线程
5.1、使用Process Explorer查看占用CPU高的线程
4.2、使用Process Hacker查看占用CPU高的线程
4.3、Process Hacker介绍
6、使用Windbg分析占用线程较高的线程
6.1、使用!runaway命令查看线程占用的CPU时间
6.2、Windbg中不仅能看到详细函数调用堆栈,还能看到函数中变量的值
VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html开源组件及数据库技术(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12458859.html 无论软件工程的理念多么先进,开发进度安排多么的合理,亦或是开发人员的经验多么丰富,大家在开发调试软件的过程中总会遇到这样那样的问题。随着软件规模和复杂性的不断增加,错误的出现频次以及分析调试的难度也在以非线性的方式持续增长。要提高分析与处理问题的效率,采用合适的分析思路与分析调试工具是非常重要的。有时,一个需要数天时间才能解决的问题,如果换一种分析调试思路或者借助某个合适的排查工具,或许只需1个小时就可以解决。今天我们以一个项目中遇到的问题实例,给大家分享排查软件CPU占用高的思路以及常用的分析工具,以供大家借鉴或参考。
1、问题说明
上周一领导反馈在运行我们的软件时笔记本出现明显发烫,到任务管理器中查看软件进程的CPU占用值达到了20%之多:(这个地方使用示例程序的截图为例)
在当前系统中占用的CPU明显高于其他进程。这台笔记本是采购没多久的,机器的性能还是不错的,按讲不应该出现这么大的CPU消耗。 并且软件在登录成功后,并没有做任何操作,没有执行任何消耗CPU的音视频编解码操作。
之前遇到过类似的问题,分析下来怀疑是系统中自带的defender杀毒程序引起的,因为一般杀毒软件会远程注入到程序进程中,程序执行的一些比较可疑的话,杀毒软件会持续的跟踪与监测,可能会导致程序占用的CPU比较。后来,该领导在其电脑上安装了其他杀毒软件,以为就不会出现上述CPU占用高的问题,结果这次又出现了!所以估计还是我们的软件有问题,还是要从软件内部找原因!
当时在排查这个问题时没有保留相关截图和素材,所以下面将以一个示例程序,详细介绍如何使用Process Explorer和Windbg去高效地分析这类CPU占用高的问题。
2、Process Explorer与Windbg工具介绍
在日常工作中,常用的排查软件问题排查工具有Dependency Walker、GDIView、Process Explorer(或者Process Hacker)、Process Monitor、API Monitor、Windbg调试器、IDA汇编工具等。使用这些工具,可以高效的分析和排查软件运行过程遇到的多种问题。关于这些工具的详细说明,在此我就不再赘述了,可以参见我写的文章:
C++软件开发值得推荐的十大高效软件分析工具https://blog.csdn.net/chenlycly/article/details/127608247 关于使用这些工具排查项目问题的实例,可以参考我的两篇专栏,专栏中有详细的项目案例介绍,有很强的实战参考价值:
C++软件调试与异常排查从入门到精通系列文章汇总https://blog.csdn.net/chenlycly/article/details/125529931C++常用软件分析工具从入门到精通案例集锦汇总https://blog.csdn.net/chenlycly/article/details/131405795
2.1、Process Explorer工具
2.1.1、Process Explorer功能介绍
Process Explorer是Winternals公司(该公司已经被微软收购)开发的增强版任务管理器软件工具(procexp.exe),功能要比Windows系统自带的任务管理器的功能多很多。使用该工具可以查看服务、进程、线程(可以查看进程及进程中各个线程的CPU占用情况,可以用来分析程序高CPU占用问题)、模块(可以查看程序都加载了哪些dll模块,可以确定动态加载的dll有没有加载起来)、句柄(查看进程中使用的所有句柄,可以分析句柄泄漏问题)、磁盘使用状态、网络使用状态等多类信息。日常工作中使用较多免费工具Process Explorer和Process Monitor,都是Winternals公司开发的免费工具,被放置在Sysinternals Suite工具包中。
Sysinternals Suite包含了一系列免费的系统工具,其中有大名鼎鼎的Process Explorer、Process Monitor、FileMon、RegMon等,如果把系统管理员比喻成战士的话,那么Sysinternals Suite就是战士手中的良兵利器。熟悉和掌握这些工具,并且对Windows的体系有一定的了解,将大幅度的提高日常的诊断和排错能力。
2.1.2、Process Explorer的开发者Mark Russinovich
说到Sysinternals Suite工具包,就必须要说一下该工具包的开发者Mark Russinovich(马克·拉希诺维奇),他是技术界的一个传奇人物。Mark Russinovich,是全球顶尖的Windows内核技术专家、现任微软Azure云(微软云计算平台)CTO。
当前微软云在全球云计算市场的占有率仅次于亚马逊云,全球排名第三的则是中国的阿里云。
2.1.2.1、Mark Russinovich与微软的”情缘“
Mark Russinovich(马克·拉希诺维奇),1994年博士毕业于美国卡内基梅隆大学,开始从事 Windows 相关的软件开发工作。拥有计算机天赋的Mark,很快就走上了创业之路,于1996年与Bryce Cogswell(布赖·科格斯韦尔)创立Winternals软件公司。该公司为了解决工程师平常在工作上遇到的各种问题,开发出Process Explorer、Process Monitor等许多小工具。之后他们将这些工具集合起来称为Sysinternals,并完全免费,得到众多技术人的好评。
90 年代后期,微软依靠 Windows 操作系统主宰科技界,从台式机到笔记本电脑、工作站、服务器都运行着 Windows。那个时候 Mark Russinovich 白天为一家小型软件公司开发软件,晚上与周末就在家里寻找 Windows NT 中的错误、缺陷和秘密,并且将这些信息发布到网络上,也因此经常激怒微软。Mark Russinovich 还对当时的 NT Workstation 和 NT Server 进行了逆向工程,并发现了微软人为改变市场使其以有利于自己的方式运作的行为,即微软可以强制所有网络服务使用更昂贵的操作系统,同时为其他任务销售更便宜的版本。Mark Russinovich 还为此开发了一款可以将 NT Workstation 转换为 NT Server 的工具,这也惹怒了微软,几天后他在参加微软活动的时候被拒绝进入大楼。不过,Mark Russinovich 与微软的爱恨情仇也帮他打开了加入微软的大门。
2006年,微软为了得到Mark Russinovich这个全球顶尖的Windows内核专家,一举收购了Winternals软件公司。Mark在加盟微软后,以技术小组成员的身份,负责Windows操作系统内核的开发工作,并成为微软的技术院士,这也是全公司最重要的技术职位。
微软对Mark心仪已久,时任微软平台和服务部门工程师杰森对Mark评价道:“他是目前世界上Windows系统最需要的五个人之一!”
时任微软公司部门总裁吉姆·阿辛 (Jim Allchin)也表示:“我已经关注马克很久了,他和合作伙伴创造出了一套完善的系统恢复和数据保护软件,相信他的加盟将为 Windows操作系统的开发带来巨大的推动作用。”
2.1.2.2、Mark Russinovich与微软 Azure 云
Mark除了拥有众多跨界技能外,在技术研究领域里也横跨系统、安全、云计算等。其中在2010年云计算刚发展时,马克已开始涉足。2010年,微软云计算产品Azure全面上线。Azure是基于云计算的操作系统,主要目标是为开发者提供一个平台,帮助开发可运行在云服务器、数据中心、Web和PC上的应用程序。开发者可使用微软全球数据中心的储存、计算能力和网络基础服务。
2010年的Azure还叫“Windows Azure”,此时马克已成为Azure技术团队的一员,主力解决与Fabric控制器相关的难题。Fabric控制器是Windows Azure操作系统内核,它为运行在Azure上的应用程序提供服务和管理基础设施。
Mark曾撰写博客《Windows Azure 主机更新:原因、时间和方式》,详细介绍Azure主机操作系统和原理、PaaS更新流程等技术细节。
Azure刚推出时,微软将其打造为PaaS产品,推出计算服务、Azure Blob存储服务、SQL Azure数据库服务、Azure Service Bus四大服务。后来随着开源软件和云计算的发展,微软发现用IaaS的形式更易于控制。微软一是调整自身云发展战略,宣布将 Windows Azure 更名为 Microsoft Azure;二是进行技术调整,Mark与时任开发者工具与平台集团副总裁的Scott Guthrie挑起这重任,打破重重技术难关,联手重构了PaaS版本的Windows Azure,打造出微软Azure的IaaS全新形态。
在Mark等人的努力下,如今Azure 是一个很完整的云平台,集成开发、测试、部署和管理应用程序所需的云服务,提供计算、AI、存储、混合、安全等综合服务平台。作为微软Azure云的重要参与者与见证人,Mark成为了微软Azure CTO(目前仍在任)。
2.1.2.3、C/C++与Rust语言之争
“说到语言,现在是时候停止用 C/C++ 启动任何新项目了,并在那些需要使用 non-GC 语言的场景中使用 Rust。为了安全性和可靠性,业界应该宣布弃用这些语言。”这是去年Mark Russinovich在其社交账号上公开发布的动态。
作为Azure CTO,他的言论一石激起千层浪。随后C++之父 Bjarne Stroustrup的隔空回应又让这场辩论再添一丝火药味道:“新的语言通常需要多年的时间和重大的努力,才能在其广泛的应用领域中与成熟的语言相媲美。发烧友们很少看到这一点,他们的评论往往是相当片面的。”
且不论这场battle结果如何,微软官方虽然没有明确表态,但在拥抱Rust的力度上却是在稳步推进的。
由于各种技术和历史原因,Windows主要是用C和C++编写的。大多数用户模式代码现在都是用C++编写的,但大多数内核代码仍然是用C编写的。微软一些新推出的技术模块已经开始使用Rust语言去开发了。
2.2、Windbg调试器
Windbg是微软出品的Windows平台下强大的用户态和内核态调试工具,与微软的IDE工具Visual Studio相比,它是一个轻量级的调试工具。所谓轻量级指的是安装快速轻便,但其调试功能,却比Visual Studio更为强大。
Windbg的主界面如下所示:
我们主要使用Windbg去分析软件异常与崩溃问题。在日常工作中,大部分情况下我们都是用Windbg去事后静态分析dump文件去排查问题,有时也需要使用Windbg去动态调试目标进程。
至于何时使用Windbg静态分析,何时使用Windbg进行动态调试,可以参见我的文章:
何时使用Windbg静态分析?何时使用Windbg动态调试?https://blog.csdn.net/chenlycly/article/details/131806819 使用Windbg静态分析dump文件的完整步骤,可以参见我的文章:
使用Windbg静态分析dump文件的一般步骤及要点详解https://blog.csdn.net/chenlycly/article/details/130873143 使用Windbg动态调试目标进程的完整步骤,可以参见我的文章:
使用Windbg动态调试目标进程的一般步骤及要点详解https://blog.csdn.net/chenlycly/article/details/131029795
3、引发程序高CPU占用的原因分析
一般情况下,导致程序高CPU都是因为程序中出现了死循环导致的,死循环导致代码一直在不停歇的执行,占用了大量的CPU资源,表现为CPU占用高。通过项目实战,我们可以总结出引发死循环的几个常见的原因,主要有以下几种:
1)for或while循环中的循环条件有问题
可能是因为写代码时手误,将循环条件中的i <=5,误写成了i = 5,导致循环条件一直为真,所以出现了死循环。也有可能是循环条件中使用了服务器传过来的数值,结果这个数值是个异常大的值(可能是服务器传过来的源数据有问题,也可能是数据接收端在解析数据时解析错了),导致死循环。比如服务器传过来的视频通道数nVideoChannelNum,正常情况下是个10以内的整数值,结果实际传过来的是个异常大的值,所以循环条件为int i < nVideoChannelNum出现了死循环。
2)函数调用上的死循环
可能是函数调用上的死循环,比如A函数调用了B函数,B函数调用了C函数,而C函数又调用了A函数,所以形成了函数调用上的闭环,即函数调用上的死循环。也有可能是Windows窗口消息触发的,比如某窗口消息WM_XXXXX的响应函数A中调用了B函数,而B函数中的代码又促使该窗口产生了WM_XXXXX消息,这样间接地导致了函数调用上的死循环(消息引发的)。这两个问题场景,我们在实际的项目中都遇到过。
3)线程函数中没有加Sleep
我们在程序中会大量地使用线程,去并发地执行相关的任务。线程函数中一般都会弄一个while循环,在程序运行期间循环条件一直为真,线程代码一直在不停歇的运行。但要注意的是,我们一般都需要在线程函数的循环体中人为地添加Sleep,让线程时不时休息一下,不能让其一直在执行任务,否则就类似于死循环,会导致高CPU占用。这点很多开发新手不太了解,需要注意一下。我们以前排查过一个掩藏很深的问题,就于这种场景有关,可以参见我之前写的文章:使用Process Explorer查看线程的函数调用堆栈去排查程序高CPU占用问题https://blog.csdn.net/chenlycly/article/details/132830803
4、在任务管理器和Process Explorer中查看目标程序的CPU占用
笔记本发烫,一般都是CPU高频度使用引起的,所以第一反应就是去任务管理器中查看CPU占用情况。下面我们以TestDlg.exe为例,故意在该程序的线程执行死循环的代码,使相关线程一直在跑,模拟真实场景中的实例。
打开任务管理器看到我们的TestDlg.exe程序占用了最高比例的CPU,如下所示:
于是打开Process Explorer,看看到底是程序中的哪个线程CPU占用高。结果打开后,发现Process Explorer中显示的CPU占用居然和任务管理器中的不一样:
难道是Process Explorer版本太老,对Win10的兼容性不太好?因为显示的不准,查看线程的CPU占用比例也会受影响。
于是想到另一个类似的工具Process Hacker,下载之,打开后查看好像CPU占用比例也和系统的任务管理器不一样,如下:
后来无意中发现,在Windows任务管理器中切换到详细信息标签页下:
居然和进程标签页下的CPU占用(25%)不一样,大概相差了一半,这个着实有点让人费解啊!
5、使用Process Explorer和Process Hacker查看占用CPU高的线程
对于CPU占用高的问题,我们需要使用工具Process Explorer或Process Hacker,看看是哪个线程占用CPU高,然后去查看线程的函数调用堆栈。通过调用堆栈中的函数去确定目标线程对应源码中的哪个业务线程。
Process Explorer和Process Hacker在功能上是类似的,但在某些细节上还是有一些差异的,下面我们分别使用这两个工具去查看占用CPU高的线程信息,看看相关的差异。
5.1、使用Process Explorer查看占用CPU高的线程
先看Process Explorer,在进程列表中直接双击目标进程TestDlg.exe,弹出进程的属性页面,然后点击Treads标签(Tab)页,就能看到进程的线程列表:
我们可以看到进程中有两个线程,其中线程id为28564的线程占用了较高的CPU。但单从线程id无法确定其是源代码中的哪个线程,于是双击线程行,弹出该线程的函数调用堆栈,如下所示:
通过函数调用堆栈,我们可以对照着源码,就知道其是源码中的哪个线程了。从堆栈中看到,调用了CTestDlgDlg::OnBnClickedButton1接口,确实也是该接口中的for循环出现死循环,导致线程一直在不停歇的运行,导致了高CPU占用。
有时我们需要在上述调用堆栈页面中,点击Refresh多次刷新函数调用堆栈,才能看到有效的调用堆栈。但有时会刷新出完全不相干的函数调用堆栈,比如在此处我们点击Refreash按钮刷新一下,堆栈就不完整了,也显示不全了:
4.2、使用Process Hacker查看占用CPU高的线程
使用Process Hacker查看线程信息的步骤和Process Explorer是类似的,现在进程列表中双击目标进程,然后在弹出的进程属性窗口中点击Threads标签页切换到线程列表页面,也可以看到占用CPU较高的线程:
然后双击占用CPU较高的那个线程,就能查看到该线程的函数调用堆栈,如下:
也看到了CTestDlgDlg::OnBnClickedButton1接口的调用。可以多次点击Refresh按钮刷新函数调用堆栈,每次都可以显示有效的函数调用堆栈,在本问题中每次刷新后的函数调用堆栈中都能看到CTestDlgDlg::OnBnClickedButton1接口的调用。
所以,要查看线程的有效的函数调用堆栈,用Process Hacker更好一点!
4.3、Process Hacker介绍
上面我们已经详细介绍了Process Explorer工具,我们再来大概地介绍一下Process Hacker工具。
Process Hacker 是一个免费开源的进程浏览器和内存编辑器,可以用来显示 Windows 系统下的服务、进程、线程、模块、句柄以及内存等信息它不仅能够帮助你查看管理进程,同时也能进行系统监视和内存编辑,帮助你监视系统资源、调试软件和检测恶意软件。
如果在Process Hacker官网下载不了该工具,可以到其他下载网站上去下载即可。
Process Hacker相对于Process Explorer,功能差不多,UI界面更炫一点,在查看线程的函数调用堆栈时更有效一些。
此外,从关于页面可以看出,该软件工具是一个华人开发的:
最重要一点,Process Hacker是完全免费开源的,如果要实现该工具中的部分功能,可以直接去下载源码参考。
6、使用Windbg分析占用线程较高的线程
可以直接将Windbg附加到CPU占用高的进程上,然后使用!runaway命令查看进程中各线程的CPU时间片占用情况,时间片占用多的线程可能就是当前占用CPU较高的线程。关于!runaway命令,可以参看Windbg自带的帮助文档:(在菜单栏中点击Help -> Index,即可打开chm帮助文档)
6.1、使用!runaway命令查看线程占用的CPU时间
打开Windbg,在菜单栏中点击File -> Attach to a Process...,在弹出的进程列表中找到目标进程TestDlg.exe,然后点击确定,即附加到该进程上。附加到目标进程上后,Windbg自动中断下来,按下g命令就可以让目标程序继续运行。我们此刻要输入!runaway 7命令,查看当前进程中的线程时间片占用情况,则需要先让Windbg中断下来,点击菜单栏中的Debug -> Break,即可中断下来。于是输入!runaway 7命令,回车,看到如下的信息:
显示的时间片信息中包含用户态时间片、内核态时间片和从程序启动到当前的时间,我们主要看用户态的时间片,因为我们的业务代码基本都是运行在用户态的。
从上图中可以看出,线程id为0x6f94(16进制)的线程占用的用户态时间片最多。但这个地方需要注意一下,占用时间片多,不代表就是当前时刻占用CPU最高的线程。可以辅助Process Explorer或Process Hacker查看占用CPU较高的那个线程id,如下所示:
从图中看到,占用CPU较高线程的线程id为28564,而0x6f94 = 28564,说明Windbg中显示的0号线程id为0x6f94的线程就是CPU占用最高的线程。
于是使用~0s切换到0号线程(线程id前面的序号就是线程序号),然后输入kn查看此时的函数调用堆栈:
Windbg中显示的函数调用堆栈应该是最精准的!调用堆栈中也看到了CTestDlgDlg::OnBnClickedButton1接口的调用。
6.2、Windbg中不仅能看到详细函数调用堆栈,还能看到函数中变量的值
有人可能会说,既然Process Explorer中能看到函数调用堆栈,为啥还要用Windbg呢?一方面是要介绍使用Windbg排查的方法,另一方面Windbg中除了可以看到完整的函数调用堆栈,还能查看到函数调用堆栈中函数中的变量的值,可以辅助定位问题。
在本例中,可以点击函数调用堆栈每一行前面的序号,展开该函数去查看函数中相关变量的值。比如点击TestDlg!CTestDlgDlg::OnBnClickedButton1+0x99所在函数行前面的序号0e,就能查看CTestDlgDlg::OnBnClickedButton1函数中相关变量的值,如下所示:
从图中可以看到for循环中的变量i是一个异常大的值,这正是问题所在。