Release库与Debug库混用导致释放堆内存时产生异常的详细分析

Posted dvlinker

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Release库与Debug库混用导致释放堆内存时产生异常的详细分析相关的知识,希望对你有一定的参考价值。

目录

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库混用导致内存释放时的异常崩溃问题,这是一个很有隐蔽性的错误,本文详细进行了相关的阐述,以给大家提供借鉴和参考。

以上是关于Release库与Debug库混用导致释放堆内存时产生异常的详细分析的主要内容,如果未能解决你的问题,请参考以下文章

如何将debug版本的so库变成release版本?

debug可运行,release不可运行

qt : debug版本正常,release版本总提示错误:无法定位程序输入点.......于动态链接库QtCore4.dll 上。

C ++释放共享库中动态分配的内存导致崩溃

debug和release 模式有啥区别

Flutter在Debug和Release下分别使用啥编译模式,有啥区别?