目录
1、问题说明
2、导出dump文件时只是遇到了INT 3软中断,并没有发生异常崩溃
3、函数中发生了栈内存越界,导致线程的栈回溯出异常,堆栈中只显示一行函数调用记录
3.1、处理Json数据时产生了异常
3.2、函数中发生栈内存越界,可能会导致线程的栈回溯出异常
3.3、将打印出来的Json数据与代码对照起来看,发现是数组越界了(栈内存越界)
4、遇到INT 3中断,可能此时代码出现了问题,可以查看此时的函数调用堆栈(附上INT 3软中断的详细说明)
5、最后
C++软件异常排查从入门到精通系列教程(核心精品专栏,订阅量已达8000多个,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C/C++实战专栏(重点专栏,专栏文章已更新500多篇,订阅量已达6000多个,欢迎订阅,持续更新中...)
https://blog.csdn.net/chenlycly/article/details/140824370C++ 软件开发从入门到实战(重点专栏,专栏文章已更新300多篇,欢迎订阅,持续更新中...)
https://blog.csdn.net/chenlycly/category_12695902.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)
https://blog.csdn.net/chenlycly/article/details/124272585C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)
https://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)
https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)
https://blog.csdn.net/chenlycly/category_2276111.html 近日项目中遇到了一个比较有代表性的软件异常案例,其中有两点值得关注,一是在崩溃前先产生了INT 3软中断(系统模块中人为添加了INT 3软中断),二是代码中发生了栈内存越界导致线程栈回溯出异常。本文来详细讲述一下该案例的排查过程以及涉及到的这两个要点,以供大家借鉴或参考。
1、问题说明
测试同事那边反馈了一个崩溃,但软件中安装的异常捕获模块并没有感知到,没有生成dump文件。这个问题在当前场景下是必现的,于是测试同事直接将Windbg附加到目标进程上进行调试,然后复现问题,尝试通过动态调试的Windbg去感知并捕获异常。
问题复现后,Windbg中断下来,测试同事输入kn命令将此时的函数调用堆栈打印出来,然后将堆栈截图发到讨论群中:
然后用.dump命令从当前正在调试的Windbg中导出了dump文件,将dump文件发给我。
取到dump文件后,用Windbg打开,输入.ecxr切换到异常上下文,然后输入kn,打印出来的堆栈只显示一行,如下所示:
这行中调用的是系统库接口(ntdll库中的RtlCaptureStackContext接口),并没有看到我们软件业务模块的接口调用,这点很奇怪。
2、导出dump文件时只是遇到了INT 3软中断,并没有发生异常崩溃
后来看到讨论群里的截图,截图中可以看到当时只是产生了一个INT 3中断:
并不是崩溃,这个INT 3会让正在调试的Windbg中断下来,测试同事以为Windbg中断下来就是发生崩溃了(测试同事只会简单地使用Windbg,对于一些细节不了解),于是导出了dump文件。所以后面我们拿到dump文件后,使用.ecxr和kn命令并没看到有效的函数调用堆栈,只能看到系统库接口的调用。
因为这个问题是必现的,我这边想直接用Windbg抓一次,抓一次发生异常崩溃时的dump。我先将软件运行起来,然后将Windbg附加到软件进程上,让测试同事把问题复现出来。问题复现时,先产生了一个INT 3中断:
输入g命令将这个中断跳过去,然后就发生了Access Violation内存访问违例的异常(此时才发生异常崩溃),于是用.dump命令导出了dump文件。
因为Windbg动态调试时非常卡,根本没法输入命令,使用事先拷贝好的.dump命令导出了dump文件。
关于如何使用Windbg静态分析dump文件、如何使用Windbg动态调试目标进程、何时使用Windbg静态分析及何时动态调试以及使用Windbg分析问题的诸多细节与技巧,我在此就不赘述了,可以查看我之前写的专题文章:
使用Windbg分析dump文件定位软件异常的方法与操作步骤https://blog.csdn.net/chenlycly/article/details/146005441使用Windbg调试目标进程排查C++软件异常的一般步骤与要点分享
https://blog.csdn.net/chenlycly/article/details/145826705何时使用Windbg静态分析?何时使用Windbg动态调试?
https://blog.csdn.net/chenlycly/article/details/131806819【C++软件调试技术】使用Windbg分析软件异常时的诸多细节与技巧总结
https://blog.csdn.net/chenlycly/article/details/140742933
3、函数中发生了栈内存越界,导致线程的栈回溯出异常,堆栈中只显示一行函数调用记录
3.1、处理Json数据时产生了异常
接下来用Windbg打开dump文件进行分析,输入.ecxr切换到发生异常的上下文,输入kn命令,结果堆栈中也只显示一行,不过这行中看到了业务模块接口的调用:
于是找到堆栈中模块的pdb文件,设置到Windbg中,重新将函数调用堆栈打印出来,可以看到函数中的具体行号,提供给该模块的维护人员,让他们详细分析一下崩溃的原因。
点开这行前面的索引链接,显示出当前函数中相关变量的值,看到传入该函数的是Json数据,函数中要把这个Json数据要按节点读取出来,保存结构体变量中,然后投递给上层。看到Json数据,猜测可能是处理Json数据时出了异常,可能是以下原因导致的:
1)在解析Json数据时产生了异常,比如平台侧修改了Json数据的格式,没通知终端侧,导致终端侧解析Json数据的代码产生了异常,这样的问题我们以前遇到过。
2)可能是Json串中某个节点的值是不正常的异常值,导致代码处理逻辑出问题。比如是个异常大的值,如果用该值作为for循环的条件,则会导致死循环。
3)Json数据中包含的数据对象个数大于本地对应的数组的个数,而本地没有判断数组是否超过长度,从而导致操作数组越界。本案例中的问题,就是这个原因导致的。下面会详细讲解本案例的这个问题场景。
3.2、函数中发生栈内存越界,可能会导致线程的栈回溯出异常
此处堆栈中只显示一行,有些奇怪,难道是当前函数中发生了栈内存越界,直接把堆栈中保存的主调函数的ebp栈基址覆盖了(通过函数中的栈基址将函数调用堆栈回溯出来),导致线程栈回溯不出来了?经后面分析,确实是这个原因导致的。
因为栈是从大地址向小地址方向分配使用的,如果当前函数中发生在栈内存越界,是从操作的起始地址向后面的大地址方向越界,可能会越界到存放主调函数的ebp栈基址值的内存区域(篡改了栈基址值),这样会导致回溯函数调用堆栈时出现问题,所以上面只能看到当前函数的调用, 且只显示一行调用记录:
关于函数调用的栈分布以及线程函数调用堆栈的栈回溯原理,可以查看我之前写的专题文章:
C++函数调用栈分布详解(理解多个问题的切入点)https://blog.csdn.net/chenlycly/article/details/121001096C++栈回溯原理(C++异常排查面试题)
https://blog.csdn.net/chenlycly/article/details/121002139
在这里,给大家重点推荐一下我的几个热门畅销专栏,欢迎订阅:(博客主页还有其他专栏,可以去查看)
专栏1:【C++软件异常与异常排查从入门到精通系列教程】(该精品技术专栏的订阅量已达到10000多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,已经更新到200篇以上!欢迎订阅!)
C++软件调试与异常排查从入门到精通系列文章汇总https://blog.csdn.net/chenlycly/article/details/125529931
本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,详细介绍分析C++软件问题的常用分析工具,以图文并茂的方式给出具体的项目问题实战分析实例(详细讲述分析排查过程,很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!
考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!
专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!
专栏2:【C/C++实战进阶】(该专栏涵盖了C++多方面的内容,是当前重点打造的专栏,订阅量已达8000多个,专栏文章已经更新到500多篇,持续更新中...)
C/C++实战进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html
以多年的开发实战为基础,总结并讲解一些的C/C++基础与项目实战进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域多个方面的内容,包括C++基础及编程要点(模版泛型编程、STL容器及算法函数的使用等)、数据结构与算法、C++11及以上新特性(开源代码中可能会用到很多新特性(比如WebRTC开源库),日常编码中也会用到部分新特性,面试时也会频繁地涉及到,学习新特性很有必要)、常用C++开源库的介绍与使用(比如SQLite、libcurl、libwebsockets、libevent、jsoncpp/RapidJson、Redis、RabbitMQ、MongoDB、MQTT、ZooKeeper、OpenCV、FFmpeg、SDL、GStreamer、Live555、ReactOS等)、代码分享(调用系统API、使用开源库)、常用编程技术(动态库、多线程、多进程、数据库及网络编程等)、软件UI编程(Win32/duilib/QT/MFC)、C++软件调试技术(引发C++软件异常的常见原因分析与总结、排查C++软件异常的手段与方法、分析C++软件异常的基础知识、使用常用软件分析工具分析C++软件问题、多个项目实战问题分析案例分享等)、设计模式(单例模式、工厂模式、观察者模式、状态模式等)、网络基础知识与网络问题分析进阶内容(实战问题分析实例分享)等。本专栏的内容都是建立在项目实践的基础上,来源于项目实战,服务于项目实战,很有实战参考价值!
专栏3:【分析C++软件问题的实用软件与高效工具实战案例集锦】
C++常用软件分析工具从入门到精通案例集锦汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795
常用的C++软件辅助分析工具有SPY++、PE工具、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Clumsy、Windbg、IDA Pro以及内存泄漏检测工具等,本专栏详细介绍如何使用这些工具去巧妙地分析和解决日常工作中遇到的问题,很有实战参考价值!
专栏4:【VC++常用功能代码封装】
VC++常用功能开发汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/124272585
将10多年C++开发实践中常用的功能,以高质量的代码展现出来。这些常用的高质量规范代码,可以直接拿到项目中使用,能有效地解决软件开发过程中遇到的问题。
专栏5:【C/C++软件开发从入门到实战】(本专栏涵盖了C++多方面的内容,是当前重点打造的专栏,专栏文章已经更新到300多篇,持续更新中!欢迎订阅!)
C++ 软件开发从入门到精通(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.html
根据多年C++软件开发实践,详细地总结了C/C++软件开发相关技术实现细节,分享了大量的实战案例,很有实战参考价值。
3.3、将打印出来的Json数据与代码对照起来看,发现是数组越界了(栈内存越界)
查看运行日志看到平台传过来的Json数据中某类对象有26个:
但本端存放这些对象的数组中只有10个元素(数组长度是10),如下所示:
代码中在将Json串中某对象数据保存到数组中时,没有判断数组的长度,直接将26个对象保存进来了,所以导致操作数组时越界了。该数组所在的结构体定义成局部变量,所以发生的内存越界应该是栈内存越界,然后将堆栈中存放的主调函数的ebp栈基址给覆盖了,导致栈回溯出现了问题,所以堆栈中只看到一行记录。这样引发问题的原因和现象就完全一致了,能对得上了!
4、遇到INT 3中断,可能此时代码出现了问题,可以查看此时的函数调用堆栈(附上INT 3软中断的详细说明)
如果要查看完整的函数调用堆栈,可能产生INT 3中断时可以看到,查看此时的函数调用堆栈可能也能找到问题。
在代码的异常分支中可以插入软中断的汇编代码_asm INT 3:
// manual breakpoint
_asm INT 3;
printt("HeLlo INT 3!\n");
当代码执行到该异常分支时就会产生INT 3软中断,所以遇到INT 3中断可能就是为了提示当前代码可能出问题了。
关于INT 3软中断的详细说明,翻看了张银奎老师的《软件调试》一书部分章节:(这本书推荐大家看一下,要多看书、看好书!)
可能是在代码的问题分支中添加了_asm INT 3这条语句,当代码执行到该异常分支时就会产生INT 3软中断,让调试器中断下来,提示用户当前程序可能出了异常,中断下来好让用户查看此时的函数调用堆栈,然后进行分析。
5、最后
软件中发生栈内存越界,将主调函数的ebp栈基址给改写了,导致函数调用堆栈回溯出问题,以及代码中人为加入INT 3软中断,之前很少遇到,这次算是见识到实战案例了,所以在此记录总结一下,分享给大家。