程序启动时访问了未初始化的类指针引发内存访问违例导致程序崩溃的问题排查

目录

1、问题说明

2、使用Windbg动态调试去初步分析

3、使用Windbg详细分析

4、最后


VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.html开源组件及数据库技术(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_2276111.html       最近在联调程序的新功能时,更新了底层模块的库之后,出现了启动报错导致程序启动失败的问题。这个问题有一定的代表性,在这里给大家分享一下问题的排查过程。

1、问题说明

       当前软件的新需求基本开发完成,目前正处于调试与bug修改的过程中,因为登录当前的平台出现注册失败的问题,软件重启了几次还是有问题,于是手动将代码中的dll都换成最新的版本(拷贝终端组件整体编译的库,更新加入到版本控制的底层库),并将头文件更新成最新的。更新完成后,在VS中重新编译代码,开启调试,结果程序一启动就报错了,弹出如下的提示框:

查看此时的函数调用堆栈,显示崩溃在medaisdk.dll中,但看不到中间的模块,如下:

这个崩溃是必现的,程序始终启动不起来,导致没法继续进行业务联调。

       此外,还有个奇怪的现象,同一个release版本的软件,在一个测试同事的Win10系统上启动并没有问题,可以正常运行。但在另一个测试同事的Win7系统中一启动就崩溃,是必现的!

后来排查得知,是代码中访问了一个未初始化的指针变量(野指针)引发内存访问违例导致的。在release下,未初始化的变量值不会自动初始化,是分配内存时内存中残留的值,是随机值。
所以从Win7和Win10系统中的不同表现可以看出,两个系统的内存管理机制是有差异的,正是因为有差异,导致同一个版本的软件在两个系统中有不同的表现。


    在这里,给大家重点推荐一下我的几个热门畅销专栏:

专栏1:(该专栏订阅量接近350个,有很强的实战参考价值,广受好评!)

C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931

本专栏根据近几年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的实战问题分析实例,带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!

专栏中的文章都是通过项目实战总结出来的(通过项目实战积累了大量的异常排查素材和案例),有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!

专栏2: 

C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.html

以多年的开发实战为基础,总结并讲解一些的C/C++基础与进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域的多个方面的内容,同时给出C/C++及网络方面的常见笔试面试题,并详细讲述Visual Studio常用调试手段与技巧!

专栏3: 

开源组件及数据库技术icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_12458859.html

以多年的开发实战为基础,分享一些开源组件及数据库技术!


2、使用Windbg动态调试去初步分析

       于是决定使用Windbg动态分析一下,看看到底是什么原因导致的。因为程序启动时就发生了崩溃,没法先启动程序再把Windbg附加上去,所以需要直接使用Windbg去启动程序,这样才能监测到程序启动过程中的异常。

       用Windbg动态启动程序,感知到了异常,查看此时的函数调用堆栈,然后找来函数调用堆栈中相关模块的pdb文件,设置到Widnbg中,重新查看函数调用堆栈,堆栈中显示了详细的函数名称和代码行号:

从函数调用堆栈得知,在程序启动时调用了终端组件终端组件业务初始化接口,然后终端组件层在创建音视频组的编解码器时产生了内存访问违例,进而产生崩溃。

       初步怀疑可能是终端组件库与音视频库mediasdk.dll版本不一致导致的,可能是mediasdk.dll的头文件修改了(修改了函数参数或者修改了结构体),只发布了dll库文件,没有发布头文件导致的。当时因为手头有很多事情要处理,没有去深究这个问题,于是尝试让音视频组重新发布mediasdk.dll库和头文件,看看发布后还没有问题。

3、使用Windbg详细分析

       但mediasdk.dll库文件和头文件重新发布后,终端组件的相关模块重新编译,然后拷贝最新的终端组件及底层的库重新编译主程序,启动调试运行,结果启动时还是报错。看来并不是版本不对导致的问题。

      于是使用Windbg启动程序,感知到异常中断,拿来pdb文件,查看详细的函数调用堆栈,和维护mediasdk.dll模块的同事一起对照代码进行详细分析。崩溃时的堆栈如下:

从堆栈中可以看出,代码崩溃在mediasdk!CVidEncWrapper::Id函数中,查看该函数的源码,该函数中只是简单地返回一个整型变量的值:

所以引发问题的点应该不在该函数中,需要沿着函数调用堆栈往上看,看调用mediasdk!CVidEncWrapper::Id接口的函数。

       另外,查看发生崩溃的这条汇编指令,首先是访问了一个内核态地址0xcdcdf001引发的内存访问违例(用户态的代码不能访问内核态的内存地址)。然后查看这条汇编指令中用到的寄存器eax,崩溃时该寄存器的值为0xcdcdcdcd。以前我讲过一些C++程序中常见的异常值0xcdcdcdcd、0xdddddddd、0xfeeefeee等,这几个异常值的说明如下:

所以第一眼看到这个0xcdcdcdcd,根据上面的含义说明,就能大概判断代码中使用了未初始化的堆内存变量,可能是mediasdk!CVidEncWrapper::Id函数所在的类对象有问题,查看这条返回整型变量的汇编指令,按讲返回成员变量的值,当前类对象地址应该是存放在ecx寄存器中的,按讲返回成员变量的值,直接使用ecx就行,为啥会使用eax寄存器呢?

       要确定这个问题很简单,直接查看汇编代码上下文就知道了。于是在菜单栏中点击 View -> Disassembly,查看汇编代码上下文:

从汇编代码就找到答案了。对于被调用函数CVidEncWrapper::Id,主调函数肯定是将CVidEncWrapper类对象的首地址通过ecx传进来的,汇编代码中先将ecx中的C++类对象首地址,拷贝到[ebp-4]栈内存上,然后又将[ebp-4]栈内存中的值拷贝到eax中,然后执行发生崩溃的这条指令,所以执行该条崩溃指令时,eax中存放的就是当前类对象的首地址。

       所以给CVidEncWrapper::Id函数传入的CVidEncWrapper对象首地址为0xcdcdcdcd,肯定使用的是一个未初始化的指针变量导致的。所以沿着函数调用堆栈,查看调用CVidEncWrapper::Id的函数mediasdk!CKdvEncoder::CKdvEncoder,这是CKdvEncoder类的构造函数。根据函数调用堆栈中显示的cpp路径及代码行号,找到对应的源码位置,如下:

这行代码是一个打印日志的宏Mc_Enter,这就是个宏,并没有看到对CVidEncWrapper::Id函数调用啊,是不是Windbg中指示的行号有问题!
我不了解音视频组的代码,音视频组维护代码的也是一个刚接手的刚毕业小哥,对代码也不熟悉。于是以“Id()”为关键字搜索,看看是哪些地方调用了CVidEncWrapper::Id函数。结果刚才的那个打印日志的宏定义中调用了:

这就对上了,说明Windbg指示的行号是没问题的。对于宏,在代码编译时会被替换成定义的内容。

       这个打印日志的宏是放置在CXXXEncoder类的构造函数的入口处,而对指针变量m pcXXXVideoEncoder的初始化放在宏的下一行,所以在宏定义中访问了没有初始化的指针变量m_pcXXXVideoEncoder,该指针变量在Debug下会被初始化为0xcdcdcdcd(指针变量的内存区域中会被填充成0xcdcdcdcd),所以将0xcdcdcdcd作为一个CVidEncWrapper类对象的首地址,调用CVidEncWrapper::Id接口去读取类中的成员变量m dwIndex的值,读成员变量m dwIndex的值,就是去读取该变量在内存中的内容,即访问该变量的内存。

       类成员变量的内存位于所在CVidEncWrapper类对象中,是相对于类对象首地址的偏移,即eax+2234h = 0xcdcdcdcd + 2234h = 0xcdcdf001,所以要读取成员变量m dwIndex的值,就是去访问该变量的内存地址0xcdcdf001中的内容,但这个地址对于32为程序,是个内核态内存地址,当前代码是用户态的代码,是不能访问内核态地址的,所以产生了内存访问违例,程序进而发生崩溃。

       解决办法是,在CXXXEncoder构造函数中将对指针变量m_pcXXXVideoEncoder初始化的代码调整到打印日志那句宏Mc_Enter代码前面去就好了。保证在使用前就被初始化。

4、最后

       当前这个问题是必现的,为啥之前没有出过问题呢?查看音视频组代码的修改记录,在打印宏Mc_Enter的定义处,修改了宏的实现代码。当时修改代码后,只在release下做了测试,没有测试Debug版本的,这个问题在Debug下是必现的。

       最开始我们说过,使用问题库的Relase软件版本(通过release安装包安装的),在Win10系统上可以正常运行的,没有暴露出问题。但这个版本在Win7上启动会直接报错的,这是Win7和Win10中的内存管理机制不同导致的。现在大部分人用的都是Win10,所以可能很难将问题暴露出来。所以有两点需要注意一下:

1)Release版本没问题,不代表Debug版本没问题;
2)Win10系统上运行没问题,不代表在其他系统(比如Win7)上运行没问题。

此外,还有两点值得注意一下:

1)通过异常值0xcdcdcdcd,初步推断出是变量未初始化引起的,然后以这个线索为切入点,快速定位问题;
2)在崩溃的那条汇编代码中,没有通过ecx去访问类中成员变量的内存,而是使用eax,查看一下CVidEncWrapper::Id函数的汇编代码就知道了。查看上下文便知道,当前类对象的首地址已经传给了eax了,所以在崩溃的额那条汇编指令中使用了eax。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【WPF.NET开发】WPF中的窗口

目录 1、窗口类 2、实现窗口 2.1 为 MSBuild 配置窗口 3、窗口生存期 3.1 打开窗口 3.2 窗口激活 3.3 关闭窗口 3.4 窗口生存期事件 4、窗口位置 4.1 最顶层窗口和 z 顺序 5、窗口大小 6、大小调整属性的优先级顺序 7、窗口状态 8、窗口外观 8.1 重设大小模式 …

C#泛型(详解)

前言 介绍C# 入门经典第8版书中的第12章《泛型》 一、泛型的含义 为引出泛型的概念,我们先来看看我们前面提到的 集合类https://blog.csdn.net/qq_71897293/article/details/134684612?spm1001.2014.3001.5501 这些集合是没有具体类型化的,所以…

dockerfile:创建镜像的方式,船舰自定义的镜像

dockerfile:创建镜像的方式,船舰自定义的镜像 包括配置文件,挂载点,对外暴露的端口,设置环境变量 docker创建镜像的方式 1、基于已有镜像进行创建。 根据官方提供的镜像源,创建镜像,然后拉起…

安装Nacos2.2.3集群

目录 一、传统方式安装 二、Docker安装 一、传统方式安装 1、配置jdk环境 vi /etc/profile JAVA_HOME/usr/local/java JRE_HOME/usr/local/java/jre CLASSPATH.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib PATH$JAVA_HOME/bin:$PATH export PATH JAVA_…

Vue3: 给表格多个字段添加排序功能

问题 在Vue3项目中,使用element-plus的表格组件绘制表格后,需要令表格的多个字段可以进行选择排序(选择升序或者降序)但是排序功能好像有时候会出错,需要排序的字段多了之后,排序功能有时候会不起作用 解…

酷滴科技出席浦发银行第七届国际金融科技创新大赛

12月7日,浦发银行全球金融科技创新大赛在上海展开决赛。本届大会以“科技金融,激发创新力量”为主题,聚焦金融行业数字化转型过程中的痛点与难点,旨在探讨新时代下金融科技的新角色、新机遇以及新挑战。酷滴科技CEO张沈分享了酷滴…

7+共病+PPI分析+转录调控,纯生信靠这个思路也能拿7+

今天给同学们分享一篇生信文章“Exploring the Pathogenesis of Psoriasis Complicated With Atherosclerosis via Microarray Data Analysis”,这篇文章发表在Front Immunol期刊上,影响因子为7.3。 结果解读: 差异表达基因的鉴定 该研究的…

selenium库的使用

来都来了给我点个赞收藏一下再走呗🌹🌹🌹🌹🌹 目录 一、下载需要用到的python库selenium 二、selenium的基本使用 1.在python代码引入库 2.打开浏览器 3.元素定位 1)通过id定位 2)通过标…

centos 7 卸载图形化界面步骤记录

centos7 服务器操作系统,挺小一配置,装了图形化界面,现在运行程序的时候跑不动了,我想这图形界面也没啥用,卸载了算了! 卸载步骤 yum grouplist 查询已经安装的组件 可以看到 图形化界面 等是以分组存在的…

docker Compose-网络设置

目录 一、概述 二、容器网络模型(了解) CNM主要有三部分组成 CNM驱动接口 Docker内置网络驱动 三、Docker Compose-网络设置二 一、概述 二、使用 links 三、自定义网络 四、配置默认网络 五、已存在的网络 一、概述 随着微服务的事件,应用的越来越多&a…

详解http请求头,响应头以及在实际开发中

HTTP (Hypertext Transfer Protocol) 协议是一种用于传输超文本的标准协议,它是 Web 通信的基础。HTTP 协议是无状态的,即每次请求是相互独立的,服务器不会记住上一次请求的信息。HTTP 协议采用客户端-服务器模式,客户端发起请求&…

深入探讨JavaScript高阶编程技巧:突破技能瓶颈的高级实践

大家有关于JavaScript知识点不知道可以去 🎉博客主页:阿猫的故乡 🎉系列专栏:JavaScript专题栏 🎉欢迎关注:👍点赞🙌收藏✍️留言 目录 学习目标: 学习内容&#xff1a…