线程栈溢出异常,程序崩溃在汇编代码test dword ptr [eax],eax上的问题排查

news2024/12/25 22:12:08

目录

1、问题描述

2、使用Windbg静态分析dump文件

3、将Windbg附加到进程上进行动态调试

4、使用Visual Studio进行Debug调试

4.1、使用if条件断点和汇编代码单步调试

4.2、分析消息响应函数入口处为什么会产生崩溃

4.3、解决办法

5、线程栈溢出的相关细节点说明

6、引发线程栈溢出的常见原因和场景总结

7、调试汇编代码

8、最后


VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具案例集锦(专栏文章正在更新中...)https://blog.csdn.net/chenlycly/article/details/131405795       上周在新版本软件的测试过程中发现,执行某个点击操作时程序就会发生闪退崩溃,问题是必现的。经排查,是崩溃在校验栈空间的运行时汇编代码文件chkstk.asm中,这个问题有较强的隐蔽性,今天就来总结一下,详细地讲述整个问题的完整排查过程。

1、问题描述

        在软件中切换到会议列表页面,回去自动刷新会议列表,然后手动去点击列表中的某个会议item,会去自动请求对应会议的详细信息,然后程序就发生闪退崩溃了。这个问题是必现的,所以可能会相对好排查一些,但事实上这个问题并没有最开始想象的那么好查,这个问题具有一定的隐蔽性。

2、使用Windbg静态分析dump文件

       我们在软件中安装了异常捕获模块,异常捕获模块捕获到了这次异常,并自动生成了dump文件。于是我们到对应的目录中取出dump文件使用Windbg进行静态分析。用Windbg打开dump文件后,先是看到了Access violation内存访问违例的异常

于是使用.ecxr命令切换到发生异常的那个线程,看到发生异常的那条汇编指令,如下所示:

这条汇编指令中访问了一个较小的内存地址,所以导致了上面的Access violation内存访问违例?访问的内存地址是0x00e00000,而我们之前讲的64KB小地址内存区是指0x00000000 - 0x0000FFFF这个地址范围,显然当前的0x00e00000不在这个64KB小地址内存区域中。至于为什么会崩溃,就要继续向下分析了。

      于是输入kn命令查看函数调用堆栈:

因为没有加载相关模块的pdb文件,所以函数调用堆栈中看不到具体的函数。这时需要根据函数堆栈中显示的模块,依次使用lm命令查看模块的时间戳信息,查找对应版本的时间戳。以directui.dll为例,如下所示:

然后根据时间戳(模块的编译生成时间)到文件服务器上去找pdb文件。

       将函数调用堆栈中相关模块的pdb文件拿来后,将pdb文件路径设置到Windbg中,然后重新输入.ecxr命令切换到发生异常的线程上下文,然后重新输入kn命令查看包含具体函数名等详细信息的函数调用堆栈,如下所示:

从上述函数调用堆栈看,问题好像出在directui的开源框架代码中,这个就比较奇怪了,以前从来没遇到过框架代码出问题的场景。这套框架已经用了好几年了,一直都没问题的,应该不是框架的问题。

        所以,静态分析dump文件也没有分析出来,陷入了僵局,这比原先想象的要复杂不少。刚开始联调就出现这样的问题,着实有些棘手,耽误联调时间。这是主要功能点,没法绕过去,必须要继续排查。 

3、将Windbg附加到进程上进行动态调试

       既然静态分析dump文件查不出问题,所以尝试将程序重新启动起来,然后将Windbg附加到进程上进行动态调试,看看动态调试能否得到有用的线索。问题是必现的,按照之前的复现步骤复现异常,Windbg感知到并中断下来,如下所示:

动态调试果然有效果,看到了软件发生了Stack overflow线程栈溢出的异常。

       但使用kn命令看到的函数调用堆栈,和之前dump文件中显示的函数调用堆栈是一样的,如下所示:

虽然知道了发生线程栈溢出的异常,但从函数调用堆栈中还是分析不出来问题,directui框架不应该有问题,都用了好几年了,如果有缺陷,应该之前就会暴露出来了,至此这个问题还是很迷茫。

       于是沿着当前的函数调用堆栈,一个函数一个函数看,但始终没看到明显会引发线程栈溢出的代码,比如定义了一个很大的局部结构体变量、函数递归调用或者函数调用上的死循环。但查看了当前线程的函数调用中的所有的函数,都没找到问题。

4、使用Visual Studio进行Debug调试

       既然Windbg静态和动态调试都没定位问题,因为问题是必现的,Debug下也是必现的,所以决定使用Visual Studio进行Debug下的调试按照问题的复现步骤操作,程序产生异常,Visual Studio中断下来,如下所示:

代码崩溃在汇编文件chkstk.asm中的这行汇编代码上:test  dword ptr [eax],eax,这点和Windbg看到的是一致的。弹出的提示框中也提示发生了Stack overflow线程栈溢出异常!

       打开调用堆栈页面查看当前的函数调用堆栈:

调用堆栈和Windbg中显示一致,于是进入最后一个函数,到函数内部去详细看一下。

4.1、使用if条件断点和汇编代码单步调试

       点击会议列表中的会议item时会向服务器发起请求会议详情的请求,等服务器回应后,底层会给UI层投递一个消息EVENT_UI_CONF_DETAILINFO_NTF,在消息中携带会议详细信息数据。如果将获取会议详情的代码注释掉,程序就不会崩溃,所以肯定和这个会议详情请求有关。

       既然崩溃在上述函数中,于是在上述函数中打断点进行单步调试。上述函数的功能是处理窗口的自定义消息,收到消息后到映射表中找到消息对应的响应函数去执行响应函数,所以人为地在代码中添加一个条件断点,以EVENT_UI_CONF_DETAILINFO_NTF消息id进行过过滤(消息id宏EVENT_UI_CONF_DETAILINFO_NTF的值为12054),如下所示:

人为添加if条件断点,很好用,比Visual Studio中的条件断点要灵活方便很多,建议使用这种人为添加的if条件断点,我们在项目中基本都用这种方式。 

       问题应该就出在:

(this->*mmf.pfn_bwlb)( wParam, lParam, bHandled );

 这行源码就一行,怎么进行单步调试呢?C++源码是只有一行,但程序最终执行的是汇编代码,一行C++源码可能对应多行汇编代码,可以在运行到断点处,右键点击:

在弹出的右键菜单中点击“转到反汇编”菜单项,跳转到汇编代码页面,单步去调试汇编代码。就在这时突然想起来,好像忽略了一个问题排查点,有没有可能是EVENT_UI_CONF_DETAILINFO_NTF消息响应函数CXXXXMsgRecver::OnConfDetailInfoInd中有问题,于是又在该函数的入口处添加断点

然后命中断点后右键查看汇编代码,然后单步调试汇编代码。经调试发现,问题果然出在响应函数CXXXXMsgRecver::OnConfDetailInfoInd的入口处:

单步调试上面的汇编代码,发现最终崩溃在_chkstk这个函数调用的地方,所以就是崩溃在当前函数的入口处,在崩溃时显示的函数调用堆栈中不显示当前的响应函数,可能和崩溃在函数入口处有关。

4.2、分析消息响应函数入口处为什么会产生崩溃

      已经确定问题出在EVENT_UI_CONF_DETAILINFO_NTF消息的响应函数CXXXXMsgRecver::OnConfDetailInfoInd中了。下面就要看看这个函数入口处为啥会发生崩溃了!

       于是详细查看了CXXXXMsgRecver::OnConfDetailInfoInd函数的代码,不看不知道,一看就知道,函数中根据不同的会议类型分了几个if条件分支,每个分支都使用对应的结构体定义了局部变量,代码片截图如下所示:

即分别使用了TMTInstantConferenceInfo_Api、TMTBookConferenceInfo_Api和TMTVConfDetailInfo_Api三个结构体定义了局部变量,这三个结构体都是比较大的,定义的局部变量都是在栈上分配的,问题就出在这里了。

       此处,需要注意一下,虽然这三个结构体变量是定义在if条件体内部的,作用域位于这些if条件体中,即生命周期在if条件体中,但三个结构体变量占用的栈空间在函数入口处就分配好了(关于这一点可以编写几个分支代码,在Visual Studio中查看汇编代码去验证)。因为结构体很大,所以在分配好三个结构体变量栈内存后,调用_chkstk汇编实现的函数去校验线程栈内存时,发现超过了当前线程的栈的上限,所以触发了Stack overflow线程栈溢出的异常。

       为啥同样的代码在之前的版本中运行不会产生线程栈溢出,现在就会出现呢?初步估计可能是TMTInstantConferenceInfo_Api、TMTBookConferenceInfo_Api和TMTVConfDetailInfo_Api这三个结构体的定义有变动了,比如新增了一些字段,于是去这三个结构体的头文件中去查看svn上的修改记录,果然发现前段时间有人修改了TMTBookConferenceInfo_Api结构体,新增了以下若干个字段:

正因为这些新增的字段导致原本不会栈溢出的,现在发生栈溢出了! 

4.3、解决办法

       问题定位出来了,修改办法其实很简单,只要将在栈上分配内存的局部变量改成指针或者到堆上申请内存的对象即可。修改后的代码片段为:(不再定义局部变量接收,直接定义指针接收即可)

5、线程栈溢出的相关细节点说明

       哪些对象会占用线程的栈内存呢?函数中的局部变量是在线程栈上分配的,函数调用时主调函数传给被调函数的参数时通过栈内存传递的(将要传递的参数内存中的值压到栈上),他们会占用线程的栈内存。

       系统在创建线程时,会给线程分配指定大小的栈内存,在默认情况下,Windows系统默认会给线程分配1MB栈内存、Linux系统默认会给线程分配2MB栈内存。某个线程在某一时刻函数调用堆栈中所有函数占用的栈内存总和,就是当前线程在此时的总的栈内存占用。如果某时刻线程占用的总的栈内存超过系统给该线程分配的栈内存上限时,就会触发Stack overflow线程栈溢出异常

6、引发线程栈溢出的常见原因和场景总结

       引发线程栈溢出问题可能有以下几个可能:

1)函数递归调用的深度过深
因为一直在递归调用,在到达最底下的那层调用之前,递归函数一直没返回,栈空间一直没有释放,导致当前线程占用的栈空间越来越多,达到上限。
2)消息上触发函数的死循环调用
消息触发的函数死循环调用,因为死循环调用了,函数的栈空间一直没释放,导致当前线程占用的栈空间越来越多。这个问题我们在实际项目中遇到过两次。
3)定义了一个占用内存很大的局部变量
比如定义了一个很庞大的结构体,在一个函数中用该结构体定义了一个局部变量,假设该结构体接近或者大于1MB,则会直接导致线程栈溢出。
4)函数中使用switch...case语句,包含了大量的case分支
每个case分支中都定义了局部变量,导致当前函数占用了大量的栈空间。case分支中的局部变量的生命周期是在case分支中的,即代码运行到对应的case分支中时该分支中的局部变量才有“生命”,但其实这个局部变量的栈空间已经在函数入口处分配好栈空间了,并不是代码执行到case子句中才分配栈空间的。这点可以通过编写测试代码,查看函数入口处给当前函数分配栈空间的汇编代码就能看出来了,可以先顶一个变量查看汇编代码看看分配了多少栈空间,然后再增加一个变量,看看分配的栈空间是否变大。

7、调试汇编代码

        CPU中执行的是一句一句汇编代码(或者叫二进制机器码,两者是等价的),看汇编代码才能看到代码的执行细节。一行C++源码可能对应多行汇编代码,有时我们要知道C++源码为何有问题,可以尝试在Visual Studio中转到汇编代码页面,去单步调试汇编代码,通过调试汇编代码去寻找真相。本案例就是一个通过调试汇编代码去排查问题的典型实例。

       此外,本问题也说明,一个方法行不通,可以产生尝试其他方法,甚至可以把知道的方法都试一遍。

8、最后

       本文详细讲述了一个有一定隐蔽性的线程栈溢出异常的问题排查过程,并详细阐述了线程栈溢出的相关细节,系统总结了引发线程栈溢出常见场景和原因,希望能给大家提供一定的借鉴和参考。

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

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

相关文章

JS-24 async异步函数、 await关键字;异步函数的执行流程;进程和线程;浏览器的事件循环;宏任务和微任务;Promise面试题

目录 1_异步函数1.1_async1.2_异步函数的执行流程2_await关键字 2_浏览器进程、线程2.1_进程和线程2.2_操作系统的工作方式2.3_浏览器中的JavaScript线程2.4_浏览器的事件循环2.3_宏任务和微任务2.4_Promise面试题题一题二 1_异步函数 1.1_async async是asynchronous单词的缩…

大模型基础:理论与技术的演进概述

大模型基础:理论与技术的演进概述 人工智能发展历程 人工智能发展历程可以概括为以下几个主要阶段: 起源阶段(1956-1980年代),这一时期被称为人工智能的“黄金时代”, 达特茅斯会议首次提出人工智能概念, 开发出传统人工智能系统, 如ELIZA、深蓝等。知…

【InsCode Stable Diffusion 美图活动一期】生成着玩

此为内容创作模板,请按照格式补充内容,在发布之前请将不必要的内容删除 一、 Stable Diffusion 模型在线使用地址: https://inscode.csdn.net/inscode/Stable-Diffusion 二、模型相关版本和参数配置: 三、图片生成提示词与反向…

C++ | 多态

目录 前言 一、多态的概念 二、多态的定义与使用 1、多态的构成条件 2、虚函数 3、虚函数的重写(覆盖) 4、虚函数重写的两个例外 (1)协变 (2)析构函数的重写 5、子类的指针或者引用调用 6、C11的…

unity对象池系统

当游戏场景中出现大量的可重复利用的物体时,通过Destory来销毁再创建会触发不必要的GC回收机制,浪费性能,我们可以利用unity自带的对象池系统,从而节约性能来得到同样的效果。 为了使用这个对象池系统,我写了一个瞬间产…

element-plus 报错 ResizeObserver loop limit exceeded 解决

使用Element-plus,页面重置大小时,出现如下报错: Uncaught runtime errors: ERROR ResizeObserver loop limit exceeded at handleError (webpack-internal:///./node_modules/webpack-dev-server/client/overlay.js:252:58) at ev…

DETR3D: 3D Object Detection from Multi-view Images via 3D-to-2D Queries

DETR3D: 3D Object Detection from Multi-view Images via 3D-to-2D Queries 目的 本文提出了一个 仅使用 2D 信息的,3D 目标检测网络,并且比依赖与 密集的深度预测 或者 3D 重建的过程。该网络使用了 和 DETR 相似的 trasformer decoder ,…

SpringBoot中集成jasypt-spring-boot实现配置文件数据加密脱敏

场景 经常会遇到这样一种情况:项目的配置文件中总有一些敏感信息,比如数据源的url、用户名、 密码....这些信息一旦被暴露那么整个数据库都将会被泄漏,那么如何将这些配置隐藏呢。 除了使用手动将加密之后的配置写入到配置文件中&#xff…

Win把老外惹恼了!

随着Windows 10创意者更新(民间称Win10.3)推送规模的加大,其暴露出来的槽点越来越多。 此前,我们已经报道过新版的Defender升级为安全助手,类似于国内有名的xx管家和数字卫士,全面接管PC健康事务。 然而&a…

【css】用css样式快速写右上角badge徽标,颜色设置为渐变色

先看效果展示&#xff0c;已公开显示在图片卡片的右上角。 首先是dom代码&#xff1a;需要两个view或者div&#xff0c;public-badge是“已公开”那个矩形&#xff0c;show-signal是右边那个下三角&#xff0c;也就是阴影部分&#xff0c;这样看起来比较有立体感。 <view…

Linux驱动进阶(四)——内外存访问

文章目录 前言内存分配kmalloc函数vmalloc()函数后备高速缓存 页面分配内存分配物理地址和虚拟地址之间的转换 设备I/O端口的访问Linux I/O端口读写函数I/O内存读写使用I/O端口 小结 前言 驱动程序加载成功的一个关键因素&#xff0c;就是内核能够为驱动程序分配足够的内存空间…

论文解读:Inpaint Anything: Segment Anything Meets Image Inpainting

论文&#xff1a;https://arxiv.org/pdf/2304.06790.pdf 代码&#xff1a;https://github.com/geekyutao/Inpaint-Anything 图1&#xff1a;Inpaint Anything示意图。用户可以通过点击图像中的任何对象来选择它。借助强大的视觉模型&#xff0c;例如SAM[7]、LaMa [13]和稳定扩散…

我叫李明,我是一名开发人员

目录 一、这是一个故事 二、不屈不挠的李明 三、化解于无形 四、总结 一、这是一个故事 这个故事的主人公是一个年轻的程序员&#xff0c;他叫做李明。李明是一名技术过硬、工作认真负责的程序员&#xff0c;他的工作是开发一款新的软件产品。这款软件是一款在线购物平…

【Java基础】第四章 Object 类应用

系列文章目录 [Java基础] 第一章 String类应用及分析 [Java基础] 第二章 数组应用及源码分析 [Java基础] 第三章 StringBuffer 和 StringBuilder 类应用及源码分析 [Java基础] 第四章 Object 类应用 文章目录 系列文章目录前言一、如何使用Object&#xff1f;1.1、显式继承1.2…

c++内存映射文件

概念 将一个文件直接映射到进程的进程空间中&#xff08;“映射”就是建立一种对应关系,这里指硬盘上文件的位置与进程逻辑地址空间中一块相同区域之间一 一对应,这种关系纯属是逻辑上的概念&#xff0c;物理上是不存在的&#xff09;&#xff0c;这样可以通过内存指针用读写内…

Web-登录功能实现(含JWT令牌)

登录功能 这个登陆功能先不返回JWT令牌 登陆会返回JWT令牌 一会在登陆验证时讲解JWT令牌&#xff08;返回的data就是它&#xff09; 登录校验 概述 就是你比如复制一个url 用一个未曾登陆对应url系统的浏览器访问 他会先进入登陆页面 登陆校验就是实现这个功能 简而言之…

基于EasyExcel的单元格合并自定义算法处理

基于EasyExcel导出Excel后&#xff0c;通过对合并单元格的简单规则配置&#xff0c;实现如下图所示的单元格合并效果&#xff1a; 效果截图 原表格数据如下&#xff1a; 通过配置单元格合并规则后&#xff0c;生成的合并后的表格如下&#xff1a; 注&#xff1a;其中第三列&a…

Android Studio连接安卓手机

1. 创建项目 2. 下载Google USB Driver 点击右上角红框的【SDK Manager】->【SDK Tools】。 也可以在 【tools】->【SDK Manager】->【SDK Tools】下进入。 点击Google USB Driver&#xff0c;下载后点ok。 3. 环境变量 右键【我的电脑】->【高级系统设置】-&g…

基于微信小程序的高校新生自助报道系统设计与实现(Java+spring boot+MySQL+小程序)

获取源码或者论文请私信博主 演示视频&#xff1a; 基于微信小程序的高校新生自助报道系统设计与实现&#xff08;Javaspring bootMySQL微信小程序&#xff09; 使用技术&#xff1a; 前端&#xff1a;html css javascript jQuery ajax thymeleaf 微信小程序 后端&#xff1…

123、仿真-基于51单片机的电流控制仿真系统设计(Proteus仿真+程序+原理图+参考论文+配套资料等)

方案选择 单片机的选择 方案一&#xff1a;STM32系列单片机控制&#xff0c;该型号单片机为LQFP44封装&#xff0c;内部资源足够用于本次设计。STM32F103系列芯片最高工作频率可达72MHZ&#xff0c;在存储器的01等等待周期仿真时可达到1.25Mip/MHZ(Dhrystone2.1)。内部128k字节…