目录
1、VS2017默认编译出来的程序,不支持XP系统
1.1、新版本软件为什么要选择VS2017?
1.2、VS2017如何配置才能编出支持XP系统的程序?
1.3、最终选择使用VS2010编出的版本
2、程序在XP系统中启动报错,无法启动
2.1、提示在系统库MFPlat.dll中找不到MFGetStrideForBitmapInfoHeader接口
2.2、Microsoft Media Foundation多媒体库不支持XP系统
2.3、为啥会报找不到MFGetStrideForBitmapInfoHeader接口
2.4、在两个XP系统中表现现象不太一样
2.5、此问题的解决办法
3、程序执行某一操作时发生了线程卡死问题
3.1、将Windbg附加到目标进程上初步分析卡死的原因
3.2、从动态调试的Windbg中导出dump文件,事后进行进一步分析
3.3、加载系统pdb文件后可能会看到更详细的函数调用堆栈
3.4、本问题的解决办法
4、总结
VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具案例集锦(正在更新中...)https://blog.csdn.net/chenlycly/category_12279968.html 在最近某个项目中,客户环境中还有安装很老的XP系统的机器,要求我们的软件支持XP系统。在XP系统中进行测试验证时遇到程序启动失败和线程卡死两个典型的异常问题,今天在这里分享一下这两个问题的排查过程。
1、VS2017默认编译出来的程序,不支持XP系统
最近因项目需要,引导客户对系统升级扩容,将服务器系统升级到一个比较新的版本,但客户用的客户端软件是几年前发布的老版本,为了能很好的兼容新平台,需要将客户用的客户端软件升级到最新版本。在客户环境中还有部分电脑使用的Windows XP系统,所以客户端软件需要支持XP系统。
现在使用XP系统的应该非常少了,可能有小部分老电脑还在使用。使用Win7的应该也不多了,大部分人都在使用Win10或者最新的Win11系统,特别是新买的电脑上默认安装的都是最新的Win11系统。
但最新版本的客户端的IDE开发环境已经升级到VS2017,默认情况下VS2017编译出来的程序是不支持XP系统的,但客户的环境中还有部分机器在用XP系统,所以不能使用最新的客户端软件。
1.1、新版本软件为什么要选择VS2017?
为啥我们的客户端的开发工具要升级到VS2017呢?因为我们项目中使用到了开源的WebRTC库,开源的WebRTC库需要使用VS2017或以上版本编译。因为不同VS版本的库混用可能会出现不匹配的问题,所以需要将产品从上到下的模块都升级到VS2017版本。
那开源WebRTC库能否使用低版本的Visual Studio去编译呢?我们软件以前是使用VS2010编译的,是支持XP系统的,能否使用VS2010编译开源的WebRTC库呢?我们使用的WebRTC库是比较新的开源版本,新版本的WebRTC代码中大规模地使用到了C++11及以上的新特性(很多开源代码都热衷于使用C++的新特性),而VS2010中只支持部分C++11的新特性(我们的项目中使用过auto关键字、匿名函数等C++11新特性,这些特性VS2010是支持的),所以将WebRTC代码拿到VS2010中编译肯定会有编译错误的。
C++11及以上的新特性,使得C++变得更加高效灵活,但也使得C++变得越来越臃肿,越来越难以驾驭!
其实直到VS2015才全面支持C++11,VS2015完对C++14的支持也基本完成,并支持部分C++17标准。
1.2、VS2017如何配置才能编出支持XP系统的程序?
VS2017默认编译出来的程序只支持Vista及以上系统的。那能否修改配置,让VS2017编译出支持XP系统的程序呢?答案是可以的,这点我们之前有专门对demo程序测试过。在安装VS2017时,有个“对C++的WindowsXP的支持”组件选项:
勾选上该选项安装后,在工程属性的常规->平台工具集中切换平台工具集:
可以选择VisualStudio2017-WindowsXP(v141_xp)、VisualStudio2015-WindowsXP(v140_xp)或者VisualStudio2010(v100)工具集去编译出支持XP系统的版本。
如果大家在安装VS2017时,没有勾选“对C++的WindowsXP的支持”组件选项,可以直接在卸载程序中对其修改,勾选上该选项,在线安装即可。
但对于我们的产品,从上到下有上百个模块,都切换同样的平台工具集去编译,工作量会比较大,最终是否有问题还不得而知。如果是个简单的工具软件,模块很少,可以使用这个方法去解决。
1.3、最终选择使用VS2010编出的版本
所以最终还是推荐使用VS2010编译的客户端版本,这个版本也是相对较新的,主要它是支持XP系统的。但这个版本没有引入WebRTC库,没法使用到WebRTC的特性。
2、程序在XP系统中启动报错,无法启动
将VS2010编译出来的版本拿到XP系统上运行,启动会报错,程序启动失败。
2.1、提示在系统库MFPlat.dll中找不到MFGetStrideForBitmapInfoHeader接口
将这个版本拿到客户的XP系统上安装后启动运行,结果刚启动就报错了,弹出如下的提示框:
在系统库MFPlat.dll中找不到MFGetStrideForBitmapInfoHeader接口,应该是我们的程序中调用到MFGetStrideForBitmapInfoHeader接口了,但在客户的系统中的MFPlat.dll库中找不到该接口。
在exe主程序启动时,系统需要先将exe主程序依赖的dll先加载到进程空间中(即先加载依赖的dll库),并且会校验各个模块中调用的接口在业务库或者系统库中能否找到接口,如果找不到接口,则会报错,就会终止程序的启动,程序启动失败。
2.2、Microsoft Media Foundation多媒体库不支持XP系统
对于这个MF开头的接口和库名称,我是有点印象的,一两年前和音视频编解码组的同事一起排查某个客户遇到的USB摄像头采集出的图像会异常闪烁的问题,当时音视频编解码组的同事尝试将摄像头采集方案换成微软的Media Foundation多媒体解决方案,看看能不能解决问题,于是在代码中引入了Media Foundation库(几个MF开头的dll库)。
Media Foundation是微软在WindowsVista上推出的新一代多媒体应用库,目的是提供Windows平台一个统一的多媒体影音解决方案,开发者可以通过Media Foundation播放视频或声音文件、进行多媒体文件格式转码,或者将一连串图片编码为视频等等。
Media Foundation是DirectShow为主的旧式多媒体应用程序接口的替代者与继承者,在微软的计划下将逐步汰换DirectShow技术。Media Foundation要求WindowsVista或更高版本,不支持较早期的Windows版本,特别是WindowsXP。
此处既然是和MFGetStrideForBitmapInfoHeader接口,于是到MSDN上查看该接口的requirements(要求说明)。随便以一个常用的API函数和msdn为关键子,比如“GetWindowText msdn”,到网上搜索,进入msdn网页,然后再以MFGetStrideForBitmapInfoHeader函数名去搜素:
按上述步骤找到MFGetStrideForBitmapInfoHeader接口说明页面:
在MFGetStrideForBitmapInfoHeader函数的requirements部分看到,只有vista以上的系统才支持该接口,所以XP系统中没有,所以运行程序时就报错了。
2.3、为啥会报找不到MFGetStrideForBitmapInfoHeader接口
有人可能会说,虽然代码中调用了MFGetStrideForBitmapInfoHeader接口,但实际运行不会执行到该接口的调用处(因为之前的函数的调用代码已经被删除掉),为啥还会报找不到这个接口呢?这个地方有个误区,并不是说,实际代码不会运行到就不检测,实际上只要代码中调用了其他库中的接口,不管程序是否会执行到,都会去做检测。
有人可能会问,系统怎么知道要到MFPlat.dll库中去找MFGetStrideForBitmapInfoHeader接口的呢?是因为在编译代码时,这个接口是从MFPlat.dll库对应的.lib导入库中引入的,链接时会去检测能否链接到。在编译生成的二进制文件的PE信息中会有导入表,记录从哪些库中导入了哪些接口,而程序启动时会去读导入表中的信息,这样系统就知道了。
2.4、在两个XP系统中表现现象不太一样
在客户XP系统上运行有问题,我们不能一直在客户机器上试错,我们需要在公司环境中找到XP环境去进行验证和测试。但现在基本都没人用XP了,很难找到装XP系统的机器了,后来我们找到了一台安装了XP虚拟机的机器。于是在该XP虚拟机系统中安装软件,安装完成后启动报错,如下:
这个报错和客户的XP系统中的报错有些不一样,在XP虚拟机中直接报找不到MFPlat.dll库,而在客户的XP系统中可以找到MFPlat.dll库,只是在MFPlat.dll中找不到MFGetStrideForBitmapInfoHeader接口。
为啥两个都是XP系统,一个有MFPlat.dll库,一个没有MFPlat.dll库呢?可能客户的XP机器上之前安装了其他软件,向系统目录中拷贝了MFPlat.dll库。上面讲到Media Foundation时,明确不支持XP系统的,这个和MSDN上的函数说明也一致的。
2.5、此问题的解决办法
这个问题的解决办法是,将Media Foundation相关的函数调用全部注释掉,这样就不会去链接和加载Media Foundation相关的库了。修改后重新编译版本,安装后启动程序就没问题了,程序可以正常启动起来了。
其实这也暴露一个问题,我们的软件测试场景没有覆盖全,这点是有问题的,不能拿到客户环境中试错,应该在公司环境中进行充分全面的测试,要对多个系统进行兼容性测试。
3、程序执行某一操作时发生了线程卡死问题
本以为上述问题解决了,就不再有问题了,结果在软件在执行某个常用的操作后就卡死了,整个UI界面卡死了,无法操作了!应该是UI线程出问题了!这个问题是必现的,于是重新启动程序,然后将Windbg附加到进程上,即使用Windbg去分析这个卡死的问题。
3.1、将Windbg附加到目标进程上初步分析卡死的原因
挂上Windbg,复现问题,点击菜单栏Debug->Break,让Windbg中断下来。因为是UI界面卡死,UI界面属于UI主线程,对于UI程序来说,UI线程是线程列表中的0号线程,所以使用~0s命令切换到UI主线程,查看UI主线程的函数调用堆栈。
切换到UI主线程之后,输入kn命令查看函数调用堆栈,果然卡住了,卡在ntdll!KiFastSystemCallRet函数调用处:
一直没返回!多次go,还是卡在这个函数上,该函数是从用户态转到内核态的,因为当前Windbg进行的用户态的调试,看不到内核模块的接口,应该是内核态卡死了,导致ntdll!KiFastSystemCallRet函数一直没返回,所以导致UI线程卡死了。
从函数调用堆栈上看,当前的卡死是调用系统多媒体库winmm中的API接口mixerClose触发的。为啥会调用这个接口呢?因为程序需要获取系统的麦克风设备,会去调用mixerOpen接口,后面需要调用mixerClose将句柄关闭。
3.2、从动态调试的Windbg中导出dump文件,事后进行进一步分析
为什么会出现卡死,需要对着C++源码进行详细分析了,于是使用.dump命令将当前进程的上下文导出到dump文件中:
.dump /ma D:\0512.dmp
然后将dump文件拿到我工作机器上,对着C++源码分析。
在我机器上用Windbg打开这个dump文件,因为这个问题中函数卡住了,并没有发生C++异常或崩溃,所以不需要执行.excr命令。上面已经说过,问题发生在UI主线程,即0号线程,我们只需要使用~0s命令切换到UI线程,然后使用kn命令查看函数调用堆栈即可。
由于此时Windbg没有加载pdb符号库文件,从现有堆栈中我们看不到具体的函数名称。于是使用lm命令查看函数调用堆栈中模块的时间戳,通过时间戳到文件服务器上找到对应时间点的pdb文件。比如:lm vm directui*:
找到pdb文件后,将pdb文件路径设置到Windbg中,然后再使用kn命令查看函数调用堆栈,看到了具体的函数接口,如下所示:
3.3、加载系统pdb文件后可能会看到更详细的函数调用堆栈
此外,此处还可以设置Windows系统库pdb在线下载地址:
srv*f:\mss0616*http://msdl.microsoft.com/download/symbols
这样Windbg就会自动到这个服务器上去下载系统库对应版本的pdb文件了。其中http://msdl.microsoft.com/download/symbols,是微软系统库pdb文件下载服务器地址,然后f:\mss0616是本地下载系统pdb文件的存放路径。
加载系统库pdb的好处是,可能能看到系统库中更详细的函数调用,在本例中设置系统库在线pdb下载地址后Windbg去自动下载系统库pdb,加载系统库的pdb后显示的系统库相关的调用堆栈要详细很多,如下:
在本问题中,函数调用堆栈相对于之前的看到的,要多出来几行,还能看到是调用了ntdll!ZwWaitForSingleObject去等待一个内核对象,于是调用ntdll!KiFastSystemCallRet进入内核态。
3.4、本问题的解决办法
引发上述问题的代码是以前为了辅助解决一个小问题而引入的,因为项目比较紧急,就没有深入地研究为什么会发生死锁,临时将相关的代码注释掉去临时解决这个问题。
4、总结
在问题中积累,在问题中进步!在解决问题的过程中增长见识,积累经验!要多关注细节,多思考多总结,处理的问题多了,素材也就多了,积累和总结的也就多了!可以将以往的案例、知识点和经验串联起来,进行梳理和归纳,就能形成一套相对完整的知识体系和技能!
这是多年来处理C++软件异常问题的最大的心得和体会,我这一路就是这么走过来的,通过历史的积累和总结,输出了实战型技术专栏《C++软件调试与异常排查从入门到精通系列教程》:
C++软件调试与异常排查从入门到精通系列教程https://blog.csdn.net/chenlycly/article/details/125529931希望专栏里总结的大量实战经验能帮助到大家。
此外,积累的素材多了,写文章时就有大量的主题可写了;积累的素材多了,做技术分享和培训时就有很多实战案例去讲了。