目录
1、问题描述
2、使用Windbg启动Debug版本的exe程序进行分析
3、进一步分析
4、问题复盘
5、为什么Debug库与Release库混用可能会出异常?
6、最后
VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931 Debug和Release版本的库,在内存管理和实现机制上是有差异的,所以一般情况下Debug版本的库是不能和Release版本库混用的,如果混用则可能会产生一些异常。今天我们讲述一个因为Debug库与Release库混用引发的异常崩溃问题实例,以供大家借鉴或参考。
1、问题描述
我们的软件产品相对比较复杂,从上到下包含了上百个模块,新人需要搭建Debug编译及调试运行环境。手动将底层模块的.lib库(编译时使用)和.dll库(运行时要加载)拷贝到项目代码目录中,然后编译主工程所依赖的多个工程,最后将exe主工程程序给运行起来。结果用Visual Studio刚将主工程启动起来就遇到了崩溃,弹出了如下的崩溃提示框:
这是个Debug断言错误,是在调用_CtrlsValidHeapPointer系统函数时产生的断言,估计是在对某个堆内存执行操作时产生了异常。于是切换到call stack页面,查看到如下的函数调用堆栈:
从调用堆栈得知,在delete释放堆内存时产生了异常,但VS中显示的堆栈中只能看到模块名,看不到具体的函数。
最开始我们没想到是Release版本库与Debug版本库混用导致的,只能一步一步向后分析。
2、使用Windbg启动Debug版本的exe程序进行分析
既然VS中看不到有效的函数堆栈,于是想到使用Windbg去分析一下。因为程序在启动时就崩溃了,所以需要使用Windbg去启动目标exe程序。在Windbg中点击菜单栏中的File -> Open Executable...,弹出打开exe文件的提示框:
找到要启动的目标进程的路径,打开之即可。
启动后,Windbg捕获到了异常,中断了下来,于是使用kn命令查看此时的函数调用堆栈。从堆栈中可以看到异常是mtpbstructdll.dll的某个函数触发的,于是使用lm vm xxpbstructdll*命令查看该库的时间戳,如下:
通过该二进制的时间戳得知,该库是2022年11月28日12点08分17秒生成的,于是通过该时间点到版本服务器上找到对应时间点的pdb文件,然后将pdb文件的路径设置到Windbg中,然后重新输入kn命令才看加载pdb文件后的详细的函数调用堆栈,如下所示:
调用堆栈中显示了详细的函数名和行号,其中从第9帧中显示的信息:
09 004eaa44 5c6c5158 xxpbstructdll!google::protobuf::RepeatedPtrField<mt::TMTSingleSysPathPrefix>::~RepeatedPtrField<mt::TMTSingleSysPathPrefix>+0x54 [k:\cbb\xxcomponent\git\xxcomponent_soft\10-common\include\thirdparty\google\protobuf\repeated_field.h @ 1020]
可以看出是在RepeatedPtrField类析构时触发了delete去释放堆内存,最终导致了异常。这个RepeatedPtrField类位于thirdparty\google\protobuf\repeated_field.h头文件的1020行,估计RepeatedPtrField类是模板类,其实现是放在头文件中的。
xxpbstructdll库中定义了很多protobuf结构体,并实现C++结构体与protobuf结构体的转换,主要是通过protobuf可以将C++结构体中的数据进行序列化。这个mtpbstructdll.dll库好久都没改动了,如果有问题,早就应该暴露出来了。并且Release版本的安装包安装后是可以正常运行的,这就奇怪了。
3、进一步分析
于是沿着函数调用堆栈继续向上看,是调用组件的XXX_SetSysWorkPathPrefix函数触发上述问题的,调用该函数的上下文代码是用来初始化底层库的,并且XXX_SetSysWorkPathPrefix是调用底层库的第一个接口!第一个接口就报错了,说明还是库有问题啊!
听维护xxpbstructdll.dll库的同事说,mtpbstructdll.dll库还依赖了protobuf.dll开源库。新人搭建的是Debug下的运行环境,难道是Release库与Debug库混用导致好的?于是问新人,protobuf.dll库是从哪里拷贝过来的,他说在他的系统中直接搜索protobuf.dll文件,是从一个Release路径下拷贝过来的,这就对了,Release版本的protobuf与Debug库混用了,肯定是混用引发的问题。于是让其从某个Debug目录中拷贝一个protobuf.dll文件过来,然后运行就没问题了。
4、问题复盘
为啥protobuf.dll库会出现这样的问题,而其他dll库没有这样的问题呢?是因为这个protobuf.dll库有点特殊,只给我们代码流发布了release版本的库,没发布debug版本的库,所以在Debug下启动运行时报如下的错误:
提示程序启动时找不到protobuf.dll库,于是同事使用Everything工具在机器上搜索了一下protobuf.dll库,于是就找到了上面说到的一个release版本的protobuf.dll库,拷贝到Debug目录中,所以就出现了release库与debug库混用的场景,导致本例中异常的发生。
5、为什么Debug库与Release库混用可能会出异常?
至此,可以确定是Release版本的protobuf.dll与其他Debug版本库混用导致的。那为什么Debug库与Release库混用可能会出异常呢?因为Debug版本与Release版本的内存管理机制是不太一样的,Debug下编译器会给程序申请的内存多分配一些额外的内存,用来存放一些调试信息和桩信息,而在Release下是不会额外分配内存的,所以在释放堆内存时也要对应起来的,Debug下申请的堆内存要用Debug下的函数去释放,用Release下的函数去释放则会有问题。
比如在Debug版本库中申请的堆内存,到Release库中去释放;Release版本库中申请的堆内存,到Debug版本库中去释放,都会出现内存释放异常问题。所以,一般情况下我们是严禁Release版本库与Debug版本库混用的,如果混用,可能会带来一些莫名其妙的内存问题。
6、最后
本文讲述了一个因为Release库与Debug库混用导致内存释放时的异常崩溃问题,这是一个很有隐蔽性的错误,本文详细进行了相关的阐述,以给大家提供借鉴和参考。