目录
1、概述
2、程序启动报错的原因分析与排查方法
2.1、程序启动时报找不到依赖的dll库
2.1.1、找不到C/C++运行时库
2.1.2、找不到依赖的业务库
2.2、程序启动时报在依赖的dll库中找不到接口
2.3、程序启动时报0xC000007B错误码
3、程序启动不了(启动失败)的原因分析与排查方法
3.1、程序对Win10等新系统不兼容
3.2、程序启动时发生了堵塞卡死
3.2.1、死循环导致线程卡死
3.2.2、多线程死锁导致线程卡死
3.2.3、安全软件拦截导致线程卡死
3.2、程序启动时发生了崩溃
3.2.1、尝试找到dump文件,用Windbg静态分析dump文件
3.2.2、没有生成dump文件,则需要将Windbg附加到程序进程上动态调试
4、最后
C++软件异常排查从入门到精通系列教程(核心专栏,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C/C++实战专栏(重点专栏,专栏文章已更新到480多篇,持续更新中...)https://blog.csdn.net/chenlycly/article/details/140824370VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585Windows C++ 软件开发从入门到精通(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.htmlC++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_2276111.html 在C++软件的开发联调及后期维护的过程中,时不时会遇到程序启动报错或启动失败的问题,调试经验不够的开发人员会比较棘手,甚至一些老的开发人员也会感觉无措。最近项目中遇到过几次这类问题,技术群中也有讨论过这类话题,今天就来给大家系统地总结一下引发C++程序启动报错或启动失败的常见原因及相关排查办法,并给出具体的项目问题实战分析实例,供大家借鉴或参考。
1、概述
程序启动报错或启动失败,是一类常见的C++程序异常问题,调试经验不足的开发人员可能会比较棘手,甚至一些老的开发人员也会感觉无措。对于这类问题,其实有很多常用的排查思路和方法。
程序启动报错,一般是程序启动时找不到依赖的dll库或者在依赖的dll库中找不到程序中调用的接口弹出报错提示框,比如:
当然也可能是其他类型的报错,比如报内存访问的错误、程序发生崩溃的报错等。
程序启动失败,可能是程序启动时发生了崩溃,程序进程直接闪退了。也有可能是程序中发生了死循环或多线程死锁,导致调用的接口发生阻塞(一直没返回),进而导致程序卡死,此时程序进程还在的,但程序的UI界面显示不出来(因为代码卡死了)。
下面详细地总结一下程序启动报错与程序启动失败的常见原因和场景,给出相应的排查思路和方法,并给出部分项目问题实战分析实例,以供大家借鉴或参考。
2、程序启动报错的原因分析与排查方法
程序启动报错,可能是程序启动时找不到依赖的dll库或者在依赖的dll库中找不到程序中调用的接口弹出报错提示框,也可能是启动时发生了崩溃弹出读或写内存失败。
2.1、程序启动时报找不到依赖的dll库
程序启动时弹出找不到某个dll库,比如:
这个找不到的dll库,可能是C/C++运行时库,也可能是程序中的业务库,可能没有将这些dll库打包到程序的安装包中导致的。对于免安装的程序,则要将这些dll放在exe主程序的目录中。
2.1.1、找不到C/C++运行时库
可能是C++程序打包时没有将C/C++运行时库带上。用Visual Studio开发的程序,如果使用的是动态的C/C++的运行时库(不是运行时静态库),则打包程序时需要将这些dll运行时库带上。
不同版本的Visual Studio,对应的C/C++的运行时库在名称可能是有区别的:(很多人可能会有疑惑,这里给大家简单地罗列一下,以d结尾的是Debug版本的运行时库)
1)VS2010对应的运行时库文件(对应100版本):msvcp100.dll(msvcp100d.dll)、msvcr100.dll(msvcr100d.dll);
2)VS2012对应的运行时库文件(对应110版本):msvcp110.dll(msvcp110d.dll)、msvcr110.dll(msvcr110d.dll);
3)VS2013对应的运行时库文件(对应120版本):msvcp120.dll(msvcp120d.dll)、msvcr120.dll(msvcr120d.dll);
4)VS2017对应的运行时库文件(对应140版本):msvcp140.dll(msvcp140d.dll)、ucrtbase.dll(ucrtbased.dll)、vcruntime140.dll(vcruntime140d.dll); (VS2017引入了两个新库ucrtbase.dll、vcruntime140.dll,不再有msvcr140.dll库,只保留了msvcp140.dll库)
如果不确定某个运行时库对应的Visual Studio版本,可以右键查看文件属性,在属性中可以看到,以msvcp110.dll文件,在文件属性中可以看到其对应的版本为VS2012,如下所示:
2.1.2、找不到依赖的业务库
也可能找不到的dll库,是软件新增的业务模块,程序在打包时忘记带上这个新增的dll库了。比如底层的业务模块新增了一个dll库,而底层业务模块的开发维护人员没有告知上层产品侧,导致上层产品侧没有将该新增的dll库打包到程序的安装包中。
如果想知道程序中哪个模块依赖了当前缺失的库,可以使用Dependency Walker工具查看,也可以使用Visual Studio自带的dumpbin.exe工具查看,具体方法可以查看我的文章:
使用Dependency Walker和Process Explorer排查瑞芯微工具软件RKPQTool.exe启动报错的问题https://blog.csdn.net/chenlycly/article/details/140731614使用Process Explorer和Dependency Walker排查dll动态库没法调试的问题(dll库加载失败导致没法动态调试)https://blog.csdn.net/chenlycly/article/details/140803687使用Dumpbin工具查看C++二进制文件的位数、时间戳及dll库的依赖关系https://blog.csdn.net/chenlycly/article/details/140153214
2.2、程序启动时报在依赖的dll库中找不到接口
比如程序启动时弹出如下的找不到接口的提示框:
之所以在依赖的dll库中找不到该接口,可能是该依赖的dll库根本就没有这个接口,或者是该接口在当前依赖的dll库中参数发生了改变,接口对应不上了。C++中会默认进行接口名称改编,改编后的名称会带上参数信息,参数改变了,改编后的接口名称就不一样了。
之所以出现这个问题,可能有多种原因:
1)某些系统API函数只在高版本的Windows系统才支持
比如,当前的问题接口,可能是Windows API接口,只在新版本的Windows中才支持(新版本的Windows系统才提供该接口),比如Win10系统中新增的API接口,如果将程序拿到Win7和Win8中运行,就会报在系统dll库中找不到该接口。
2)库与库的版本不一致导致的比如A库依赖B库,调用了B库中新增的接口,在编译A库时使用了最新版本的B库,而依赖A库的上层产品模块在编译时链接了A库(使用了新版本的A库),结果在上层产品打安装包时因为一些原因打入的是老版本的B库,这样程序安装后就出现了新版本的A库与老版本的B库在一起使用的场景,而新版本A库中调用的新接口在老版本的库中找不到(可能是新增的接口,也可能是接口的参数变了),程序启动时就会报找不到该新接口的问题。
2.3、程序启动时报0xC000007B错误码
上面提到了,新版本的库与老版本的库混用,可能会因为版本不一致出现问题。还有两类库混用也会出问题:
1)Debug版本库与Release版本库混用
一般不允许Debug版本库与Release版本库混在一起使用。Debug与Release下的内存分配与管理机制是不同的,Debug下为了存放一些调试信息,会在用户申请的内存长度的基础上多分配一些内存。如果Debug库中申请的内存,到依赖该Debug库的Release版本库中去释放,就会出问题,可能会出现崩溃。之前就遇到过这样的案例,可以查看我的文章:
【C++软件异常问题分析】不同版本的库混用导致程序启动异常 | Telnet不上服务端口问题https://blog.csdn.net/chenlycly/article/details/143156626Release库与Debug库混用导致释放堆内存时产生异常的详细分析https://blog.csdn.net/chenlycly/article/details/128503700
2)32位库与64位库混用
严禁32位库与64位库混在一起使用,因为位数不同,涉及到的内存寻址范围不同。如果混在一起,程序启动时就会报错,会报0xC000007B错误:
相关案例可以查看我的文章:
使用Dependency Walker和Process Explorer排查程序启动时缺少ucrtbase.dll等运行时库以及报0xC000007B错误https://blog.csdn.net/chenlycly/article/details/131505299一旦程序报0xC000007B错误,可以看看是否是32位库与64位库混用引起的。
如何查看dll库、exe等二进制文件的位数呢?其实很简单,二进制文件的PE头信息中就包含位数信息,我们可以用PE文件查看工具Exe Explorer或者Visual Studio自带的dumpbin.exe工具去查看,具体查看方法可以查看我的文章:
如何查看exe和dll等二进制文件的生成时间(时间戳)和位数(32位/64位)https://blog.csdn.net/chenlycly/article/details/130085431此外,使用这些工具可以查看二进制文件生成的时间戳。一般在PC的资源管理器中查看文件属性,只能看到本机上的修改时间,不是二进制文件的编译生成时间,而我们在排查问题时可能需要知道二进制文件的生成时间以确定文件的版本,可以用上述工具查看。
关于dll动态库加载失败导致程序启动报错以及dll库加载失败的常见原因的详细说明,可以查看我的文章:
【C++动态库】DLL动态库加载失败导致程序启动报错以及DLL库加载失败的常见原因分析与总结https://blog.csdn.net/chenlycly/article/details/142714236
在这里,给大家重点推荐一下我的几个热门畅销专栏,欢迎订阅:(博客主页还有其他专栏,可以去查看)
专栏1:(该精品技术专栏的订阅量已达到580多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章已经更新到200篇以上,持续更新中!欢迎订阅!)
C++软件调试与异常排查从入门到精通系列文章汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/125529931
本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的项目问题实战分析实例(很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!
考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!
专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!
专栏2:(本专栏涵盖了C++多方面的内容,是当前重点打造的专栏,订阅量已达220多个,专栏文章已经更新到480多篇,持续更新中!欢迎订阅!)
C/C++实战进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html
以多年的开发实战为基础,总结并讲解一些的C/C++基础与项目实战进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域多个方面的内容,包括C++基础及编程要点(模版泛型编程、STL容器及算法函数的使用等)、数据结构与算法、C++11及以上新特性(不仅看开源代码会用到,日常编码中也会用到部分新特性,面试时也会涉及到)、常用C++开源库的介绍与使用、代码分享(调用系统API、使用开源库)、常用编程技术(动态库、多线程、多进程、数据库及网络编程等)、软件UI编程(Win32/duilib/QT/MFC)、C++软件调试技术(排查软件异常的手段与方法、分析C++软件异常的基础知识、常用软件分析工具使用、实战问题分析案例等)、设计模式、网络基础知识与网络问题分析进阶内容等。
专栏3:
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++常用功能开发汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/124272585
将10多年C++开发实践中常用的功能,以高质量的代码展现出来。这些常用的高质量规范代码,可以直接拿到项目中使用,能有效地解决软件开发过程中遇到的问题。
专栏5: (本专栏涵盖了C++多方面的内容,是当前重点打造的专栏,专栏文章已经更新到260多篇,持续更新中!欢迎订阅!)
C++ 软件开发从入门到实战(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.html
根据多年C++软件开发实践,详细地总结了C/C++软件开发相关技术实现细节,分享了大量的实战案例,很有实战参考价值。
3、程序启动不了(启动失败)的原因分析与排查方法
程序启动不了,可能会弹出报错窗口,可能是启动时没反应,此处我们主要讨论后面的这种场景,即讨论启动时没反应的场景。
正常情况下,双击程序将程序启动起来,然后程序将UI界面窗口显示出来,供用户查看和操作。程序启动没反应,没有弹出UI界面,可能有多种原因,比如程序对Win10等新系统不兼容、程序启动时发生卡死(调用的接口没有返回,导致程序卡死,此时程序进程还在的)、程序启动时发生了崩溃(启动时程序就发生了崩溃闪退)。
3.1、程序对Win10等新系统不兼容
可能程序是用老版本的IDE开发的,对Win10等新系统存在兼容性问题,程序无法启动。比如我们前段时间遇到的Visual Assist X助手的安装包程序,在某个Win10系统中双击安装时,安装包程序一直没反应,即安装包程序启动不了。本以为可能是杀毒软件拦截的,但机器上并没有安装杀毒软件。
后来才知道可能是Visual Assist X助手的安装包程序是好几年前开发的版本,对Win10等新系统兼容性不好,导致安装包程序启动不了。解决办法很简单,将该安装包程序设置以兼容模式运行就好了。具体设置方法是,右键点击安装包程序,在弹出菜单中点击属性,打开属性窗口。然后点击兼容性标签页,在页面中勾选“以兼容模式运行这个程序”选项即可,如下所示:
很多老的程序安装包拿到Win7及以上的系统中安装时,可以会遇到兼容性问题,比如启动不起来或者启动报错,或者安装完成后提示可能存在兼容问题,都可以尝试将安装包设置以兼容模式运行试试。
关于安装Visual Assist X助手可能会遇到的问题,可以查看我的文章:
【Visual Assist X安装问题】Visual Assist X无法安装(双击安装包没反应) | Visual Assist X安装后在Visual Studio中找不到https://blog.csdn.net/chenlycly/article/details/144534269
3.2、程序启动时发生了堵塞卡死
怎么确定程序启动时发生了堵塞卡死呢?其实很简单,如果程序启动时迟迟显示不出来UI界面,到任务管理器中查看程序进程还在的,那可能是程序发生堵塞卡死。
程序启动时UI界面主线程发生了堵塞卡死,导致UI线程无法将UI界面显示出来。导致线程堵塞卡死,一般是调用的接口一直没返回,可能有多种原因,比如调用的接口中发生死循环或死锁、接口中执行的操作被安全软件或杀毒软件拦截,导致接口一直不返回,从而导致调用该接口的线程发生卡死(此时程序进程还在的),这些我们在项目中都遇到过。如果堵塞卡死发生在UI线程,则会导致UI界面显示不出来或者显示出来的界面“点不动”。
这类问题主要通过查看对应线程的函数调用堆栈进行分析。
3.2.1、死循环导致线程卡死
对于死循环导致线程卡死,死循环会导致所在线程占用的CPU很高,可以使用Process Explorer或Process Hacker工具去查看占用CPU比较高线程的函数调用堆栈去分析,相关实战分析案例可以查看我的文章:
使用Process Explorer/Process Hacker和Windbg高效排查软件高CPU占用问题https://blog.csdn.net/chenlycly/article/details/134180480 当然也可以使用Windbg分析高cpu占用问题,先将Windbg附加到问题程序进程上,使用!runaway命令查看占用高CPU的那个线程,然后切换到该线程,查看函数调用堆栈。使用WIndbg的好处是,可以设置断点进行动态调试。
3.2.2、多线程死锁导致线程卡死
对于多线程死锁导致的线程卡死,程序进程还在的,可以将Windbg附加到程序进程上,输入*kn命令查看程序中所有线程的函数调用堆栈。发生死锁的线程,其堆栈中可以看到WaitForSingleObject等阻塞函数的调用,比如:
相关实战分析案例,可以查看我的文章:
使用Windbg分析多线程临界区死锁问题分享https://blog.csdn.net/chenlycly/article/details/128532743使用Windbg排查线程死锁引起的连不上服务器问题https://blog.csdn.net/chenlycly/article/details/126341187
3.2.3、安全软件拦截导致线程卡死
对于安全软件或杀毒软件拦截导致的线程卡死,一般是因为安全软件监测到了程序中执行了危险的行为后对程序的操作进行强行拦截,导致接口不返回,从而导致线程阻塞卡死。比如程序向系统关键路径中写入内容、向注册表中写入内容,安全软件可能觉得有安全问题,可能会进行拦截。
之前有客户在使用我们软件安装包就遇到过安全软件拦截的场景,当执行到写注册表的操作时被客户机器上安装的腾讯电脑管家拦截了,导致安装线程卡死,导致安装流程被卡在最后一点进度上,始终没法完成安装。最开始排查时,就怀疑可能是安全软件拦截的,出问题的机器上安装了腾讯电脑管家。尝试将腾讯电脑管家退出,重新执行安装包还是有问题。后来发现,即便退出了腾讯电脑管家程序,但腾讯电脑管家的实时防护模块还在后台以系统服务的方式运行,所以还是会被拦截。对应的实战分析案例,可以查看我的文章:
使用Windbg排查C++软件安装包安装时被安全软件(腾讯电脑管家)拦截导致安装堵塞(线程卡住)的问题https://blog.csdn.net/chenlycly/article/details/144426365
3.2、程序启动时发生了崩溃
怎么确定程序启动时发生了崩溃呢?其实也简单,查看进程是否还在,如果不在,则大概率是发生了崩溃。
程序在启动或运行的过程中发生闪退(进程退出),除了程序发生崩溃的原因之外,还可能是程序代码检测到了异常直接调用C函数exit或者abort强行退出进程了,之前我们讲过,开源库jsoncpp和WebRTC中都有检测到异常强行结束进程的机制,具体细节可以查看我的文章:
C++程序中执行abort等操作导致没有生成dump文件的问题案例分析https://blog.csdn.net/chenlycly/article/details/129003869 此处我们主要讨论发生崩溃的场景。如果崩溃时有生成dump文件,则用Windbg静态分析dump文件。如果没有生成dump文件,则需要将Windbg附加到进程上跑,去复现异常。
3.2.1、尝试找到dump文件,用Windbg静态分析dump文件
一般程序中会安装异常捕获模块,当程序发生异常时,异常捕获模块感知到并自动生成包含异常上下文的dump文件。我们到指定的dump文件存放路径中看看有没有生成dump文件。
但异常捕获模块不是万能的,只能捕获大部分异常,有少部分异常或场景是捕获不到的。对于这种异常捕获模块没有生成dump文件的场景,可以尝试到系统日志中查看Windows系统有没有帮我们自动生成dump文件。系统会感知到程序发生了异常,可以到系统的应用程序日志中查看有没有生成dump文件的记录,如果有记录,根据记录中的dump文件路径找到dump文件,然后进行分析。我们在项目中多次遇到过程序中安装的异常捕获模块没有生成dump文件但操作系统帮我们生成的场景,并通过分析操作系统生成dump文件成功的定位了问题。发生的崩溃可能是很难复现的,如果能找到操作系统自动生成dump文件并快速定位问题,是极好极好的!关于如何到系统应用程序日志中查看dump文件记录的具体方法,可以参考我这篇文章:
使用Windbg分析从系统应用程序日志中找到的系统自动生成的dump文件去排查程序崩溃问题https://blog.csdn.net/chenlycly/article/details/132024253 关于dump文件的分类以及使用Windbg分析dump文件的一般步骤,可以查看我的文章:
dump文件类型与dump文件生成方法详解https://blog.csdn.net/chenlycly/article/details/140742911使用Windbg分析dump文件排查C++软件异常的一般步骤与要点分享https://blog.csdn.net/chenlycly/article/details/142970834《C++软件调试与异常排查从入门到精通教程》专栏中包含了大量的项目问题实战分析案例,如果要学习使用Windbg分析排查C++软件问题,可以订阅该专栏进行详细的学习!
3.2.2、没有生成dump文件,则需要将Windbg附加到程序进程上动态调试
如果程序中安装的异常捕获模块没有生成dump文件,且操作系统没有帮我们生成dump文件,则需要将Windbg附加到程序进程上去尝试复现崩溃。对于程序启动过程中的崩溃,则没有附加的机会,可以直接用Windbg去启动程序,这样就能跟踪启动过程了。
如果问题发生在公司的测试环境中,则可以要求测试人员每次启动程序时手动将Windbg附加到程序进程上,或者每次都用Windbg启动程序,然后去复现问题。当崩溃问题复现后,当前附加的到进程上的Windbg会立即感知到并中断下来,就可以查看函数调用堆栈进行分析了。
关于使用Windbg动态调试目标进程的一般步骤,可以查看我的文章:
使用Windbg调试目标进程的一般步骤及要点详解https://blog.csdn.net/chenlycly/article/details/131029795此外,关于何时使用Windbg静态分析、何时使用Windbg动态调试,我的这篇文章中进行了详细的总结与阐述:
何时使用Windbg静态分析?何时使用Windbg动态调试?https://blog.csdn.net/chenlycly/article/details/131806819 在使用Windbg静态分析dump和动态调试目标进程时有诸多的细节和技巧,感兴趣的话,可以查看我的文章:
使用Windbg分析软件异常时的诸多细节与技巧总结https://blog.csdn.net/chenlycly/article/details/140742933
4、最后
本文对引发C++程序启动报错或启动失败的常见原因及相关排查办法进行了详细地总结,并给出具体的项目问题实战分析实例。相关的总结及实战分析案例均来自于项目实战,有很强的实战参考价值。