
【C++软件实战问题排查经验分享系列 ②】 调试遇到0xfeeefeee、0xcdcdcdcd等异常值 | 动态库加载失败 | 程序启动报错与启动失败 | 软件操作权限系列问题总结
本文对C++软件开发联调过程中遇到0xfeeefeee、0xcdcdcdcd等异常值、动态库加载失败、程序启动报错与启动失败、软件操作权限等常见问题的排查思路与方法进行详细的总结,以供大家借鉴或参考。
目录
1、Debug调试时遇到0xfeeefeee、0xcdcdcdcd等异常值(要熟悉这些常见的异常值)
2.3.1、dll位数与依赖它的模块位数不一致,导致dll库加载失败
2.3.2.2、dll库调用其依赖的库中的接口,但该接口在被依赖的库中找不到
2.3.3、在某些系统上会出现LoadLibrary加载dll库失败的问题,需要使用LoadLibraryEx接口
2.4、使用Process Explorer工具查看程序加载了哪些动态库以及动态加载的库有没有加载起来
2.5、使用Dependency Walker工具查看dll库的依赖关系以及调用的接口,排查动态库加载失败问题
C++软件异常排查从入门到精通系列教程(核心精品专栏,订阅量已达5000多个,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C/C++实战专栏(重点专栏,专栏文章已更新500多篇,订阅量已达3000多个,欢迎订阅,持续更新中...)
https://blog.csdn.net/chenlycly/article/details/140824370C++ 软件开发从入门到实战(重点专栏,专栏文章已更新300多篇,欢迎订阅,持续更新中...)
https://blog.csdn.net/chenlycly/category_12695902.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)
https://blog.csdn.net/chenlycly/article/details/124272585C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)
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++软件的过程中,时常会遇到0xfeeefeee、0xcdcdcdcd等异常值 、动态库加载失败、程序启动报错与启动失败、软件操作权限等问题,本文对这些常见问题的分析与排查方法进行了详细的总结,并给出相关的实战分析实例,供大家借鉴或参考。
当前是C++软件实战问题排查经验分享系列第2期,第1期的分享已经发布,感兴趣的话,可以去查看文章:
1、Debug调试时遇到0xfeeefeee、0xcdcdcdcd等异常值(要熟悉这些常见的异常值)
大家在Visual Studio中进行Debug下代码调试时,时常会遇到0xfeeefeee、0xcccccccc、0xcdcdcdcd和0xdddddddd等异常值,如果把这些异常值当做地址去访问,则会触发内存访问违例,导致程序崩溃。我相信很多人都遇到过这几个异常值,如果对这几个异常值的含义比较熟悉,则能够结合现有代码快速定位问题。
为什么访问0xfeeefeee、0xcccccccc、0xcdcdcdcd和0xdddddddd这几个内存地址会触发内存访问违例呢?
因为对于32程序,0xfeeefeee、0xcccccccc、0xcdcdcdcd和0xdddddddd这几个内存地址是属于内核态内存地址的范围,而我们的业务代码一般是运行在用户态的,用户态的代码是禁止访问内核态内存地址的,所以会触发内存访问违例,导致软件异常!
这几个常见的异常值的说明,如下所示:(主要关注0xfeeefeee、0xcccccccc、0xcdcdcdcd和0xdddddddd这4个异常值)
* 0xcccccccc : Used by Microsoft's C++ debugging runtime library to mark uninitialised stack memory.
* 0xcdcdcdcd : Used by Microsoft's C++ debugging runtime library to mark uninitialised heap memory.
* 0xfeeefeee : Used by Microsoft's HeapFree() to mark freed heap memory.
* 0xdddddddd:Used by MicroQuill's SmartHeap and Microsoft's C/C++ debug free() function to mark freed heap memory.
* 0xabababab : Used by Microsoft's HeapAlloc() to mark "no man's land" guard bytes after allocated heap memory.
* 0xabadcafe : A startup to this value to initialize all free memory to catch errant pointers.
* 0xbaadf00d : Used by Microsoft's LocalAlloc(LMEM_FIXED) to mark uninitialised allocated heap memory.
* 0xbadcab1e : Error Code returned to the Microsoft eVC debugger when connection is severed to the debugger.
* 0xbeefcace : Used by Microsoft .NET as a magic number in resource files.
1.1、异常值0xcccccccc和0xcdcdcdcd
对于0xcccccccc和0xcdcdcdcd,在 debug 模式下,Visual Studio会把未初始化的栈内存全部填充成0xcccccccc,当成字符串看就是“烫烫烫烫……”;Visual Studio会把未初始化的堆内存全部填充成 0xcdcdcdcd,当成字符串看就是 “屯屯屯屯……”。所以遇到这两个值时,一般是变量未初始化引起的。
关于排查0xcdcdcdcd异常值引发异常崩溃的实战排查案例,可以查看我之前写的文章:
排查软件启动时访问了0xcdcdcdcd内存地址导致内存访问违例的崩溃https://blog.csdn.net/chenlycly/article/details/1252667350xcdcdcdcd异常值引发C++程序崩溃问题的详细分析
https://blog.csdn.net/chenlycly/article/details/128380751
1.2、异常值0xfeeefeee
对于0xfeeefeee,是Debug下用来标记堆上已经释放掉的内存,即已经释放的堆内存中会被填充成0xfeeefeee。注意,如果指针指向的内存被释放了,指针变量本身的地址是没做改变的,还是其之前指向的内存的地址,只是其指向的堆内存中被填充成0xfeeefeee。如果该指针是一个类的成员变量,并且该类对象是在堆上分配内存的,则该类对象的堆内存被释放后(对于C++类,通常是执行delete操作),类对象中的指针变量就会被赋值为0xfeeefeee。所以,遇到这个0xfeeefeee时,可能是相关内存被释放引起的。
关于排查0xfeeefeee异常值引发异常崩溃的实战排查案例,可以查看我之前写的文章:
排查软件关闭时访问了0xfeeefeee内存地址导致内存访问违例的崩溃https://blog.csdn.net/chenlycly/article/details/125267046
1.3、异常值0xdddddddd
对于0xdddddddd,是Debug下用来标记堆上已经释放掉的内存,即已经释放的堆内存会被填充成0xdddddddd。之前在项目问题中看到的基本都是0xfeeefeee,没见到过0xdddddddd,但前段时间在Debug下调试代码时遇到了,代码中访问了已经释放的内存,内存中都被置为0xdddddddd,这还是第一次遇到0xdddddddd异常值!正是通过0xdddddddd的说明,得知这个0xdddddddd是用来填充已经释放的堆内存,以这个为线索,快速地定位了问题!
关于排查0xdddddddd异常值引发异常崩溃的实战排查案例,可以查看我之前写的文章:
访问0xdddddddd内存地址引发软件崩溃的问题排查https://blog.csdn.net/chenlycly/article/details/139800126访问0xdddddddd内存地址引发软件崩溃的又一问题排查
https://blog.csdn.net/chenlycly/article/details/132631020 关于上述0xcccccccc、0xcdcdcdcd、0xfeeefeee 和 0xdddddddd异常值的详细说明,可以查看我之前写的文章:
2、动态库加载失败详细总结
程序启动时,系统会给根据程序的位数(32位 / 64位)分配指定大小的进程空间(虚拟内存空间),系统先将exe主程序依赖的多个dll库加载到进程空间中,然后再将exe主程序文件加载到进程空间中,然后进入main函数,程序启动起来开始运行。如果启动过程有dll库加载失败(非动态加载,且会弹出报错提示框),则会终止程序启动的进程,程序启动失败!
2.1、动态库的隐式加载与显式加载
在详细讨论动态库加载失败的主题之前,需要先讲解一下dll动态库的隐式加载与显式加载。 程序中引用dll(调用dll中的接口),在dll加载时,可分隐式加载和动态显式加载两种方式。
dll动态库隐式加载,是包含dll库的头文件并引入dll库对应的lib导入库,然后在代码中调用dll库的导出接口(编译链接时,会到dll对应的lib导入库中链接代码中调用的dll库的导出接口)。在程序中使用#pragma预编译指令引入dll库对应的lib导入库:
#pragma comment( lib, "libcurl.lib")
或者在VS的工程配置中配置导入库:
dll库的显式加载,是调用LoadLibrary或者LoadLibraryEx去动态地加载dll库。 对于显式加载的库,需要调用GetProcAddress接口去获取dll库中API接口的函数,然后通过该地址去调用API函数。比如如下的一段代码:
BOOL AutoRegsvr32( LPCTSTR lpszDllPath )
{
if ( lpszDllPath == NULL )
{
return;
}
// 改用LoadLibraryEx,并使用LOAD_WITH_ALTERED_SEARCH_PATH参数,避免部分系统无法加载到dll的问题。
HINSTANCE hInstLib = LoadLibraryEx( lpszDllPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH );
if ( NULL == hInstLib )
{
return FALSE;
}
typedef HRESULT (*DllRegisterServer)(void);
DllRegisterServer dllRegisterServer = (DllRegisterServer)GetProcAddress( hInstLib, "DllRegisterServer" );
if ( dllRegisterServer != NULL )
{
HRESULT hr = dllRegisterServer();
}
else
{
FreeLibrary(hInstLib);
return FALSE;
}
FreeLibrary(hInstLib);
return TRUE;
}
显式加载的dll库,不会在程序启动时加载,而是在代码执行到LoadLibrary或者LoadLibraryEx函数的调用时才会动态的加载。如果dll库加载失败,也不会导致程序启动失败。
2.2、动态库加载失败对程序的影响
要看动态库加载失败对程序的影响,要考虑动态库是隐式加载还是显式动态加载的。
对于隐式加载的动态库,会在程序启动时加载,如果动态库加载失败,则会终止程序的启动,程序启动失败。如果隐式加载的动态库加载失败是因为找不到接口,则会弹出如下的提示框:
如果因为找不到依赖库,则会弹出如下的错误:
对于显式加载的动态库,是在代码中调用LoadLibrary或LoadLibraryEx去加载的。不是在程序启动时加载的,是运行到加载库的LoadLibrary或LoadLibraryEx接口调用时才会加载。如果显式动态加载的库加载失败,不会对程序启动产生影响,只是会对程序中的业务功能带来影响(因为库加载不起来,就无法调用库中的接口去实现业务功能)。对于显式加载,不管是因为找不到接口还是找不到依赖的库,动态加载的库加载失败,不会报错。
2.3、动态库加载失败的原因分析与总结
这里我们详细讨论一下dll加载失败的原因,不管是通过dll库的导入库隐式加载的,还是通过调用LoadLibrary或者LoadLibraryEx动态加载的,原因都是一样的,主要有以下三个原因:
1)问题dll的位数与依赖它的模块位数不一致;
2)问题dll依赖的库在当前系统中找不到;
3)问题dll调用底层库的接口,在底层库中找不到。可能接口改参数了(新增或删除了部分参数,或者修改了原有参数的类型)、可能接口改名称了。
在这里,给大家重点推荐一下我的几个热门畅销专栏,欢迎订阅:(博客主页还有其他专栏,可以去查看)
专栏1:(该精品技术专栏的订阅量已达到8000多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,已经更新到200篇以上!欢迎订阅!)
C++软件调试与异常排查从入门到精通系列文章汇总https://blog.csdn.net/chenlycly/article/details/125529931
本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的项目问题实战分析实例(很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!
考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!
专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!
专栏2:(本专栏涵盖了C++多方面的内容,是当前重点打造的专栏,订阅量已达5000多个,专栏文章已经更新到500多篇,持续更新中...)
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++ 软件开发从入门到精通(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.html
根据多年C++软件开发实践,详细地总结了C/C++软件开发相关技术实现细节,分享了大量的实战案例,很有实战参考价值。
2.3.1、dll位数与依赖它的模块位数不一致,导致dll库加载失败
如果dll库加载失败,我们可以先确认一下dll库的位数是否与依赖该dll的模块(依赖该dll的模块可能是其他dll,也可能是exe主程序)的位数一致,因为32位dll库是不能和64位模块混用的,位数必须要一致才能使用。当然这种情况在项目中比较少见,这只是一种可能的原因,实际项目中比较少,一般不用特别关注这种情况。比如32位的exe主程序是不能加载使用64位dll库的,如果将位数不同的模块混在一起,程序启动时回报0xC000007B错误(对应dll库是隐式加载的,隐式加载才会报错),如下所示:
导致程序启动报0xC000007B错误。关于0xC000007B错误的实战分析案例,可以查看我之前写的文章:
使用Dependency Walker和Process Explorer排查程序启动时缺少ucrtbase.dll等运行时库以及报0xC000007B错误https://blog.csdn.net/chenlycly/article/details/131505299https://blog.csdn.net/chenlycly/article/details/131505299 至于如何查看dll、exe等二进制文件的位数,可以用dumpbin.exe或者PE查看工具EXE Explorer,相关查看方法可以查看我的文章:
使用Dumpbin工具查看C++二进制文件的位数、时间戳及dll库的依赖关系 https://blog.csdn.net/chenlycly/article/details/140153214如何查看exe和dll等二进制文件的生成时间(时间戳)和位数(32位/64位)
http://%20https//blog.csdn.net/chenlycly/article/details/140043291
2.3.2、dll库依赖的库有问题,导致dll加载失败
dll库依赖的库有问题(当前问题dll依赖的更下层的库),主要有两种情况:
1)dll库依赖的库在当前系统中找不到;
2)dll库中调用了其依赖库中的接口,但该接口在被依赖的库中找不到。可能接口改参数了(新增或删除了部分参数,或者修改了原有参数的类型)、可能接口改名称了。
2.3.2.1、dll库依赖的库在当前系统中找不到
如果dll库依赖的库在当前系统中找不到,则该dll库会加载失败(加载dll之前,会先将当前dll依赖的库先加载起来,依赖的库都加载起来后,才会去加载当前的dll)。
如果该dll库是隐式加载的,则一般在程序启动时加载,会报类似这个错误:
这个问题在我们项目中也经常出现,引发这类问题可能有两个典型的场景:
1)一个场景是,在发布程序时,没有将程序模块依赖的C/C++运行时dll库一起发布,而这些运行时库在其他电脑上可能没有,所以我们在发布程序时要将相关运行时库带上。
2)另一个场景是,底层模块因为重构或者业务需要,新增了一个dll模块,没有通知上层产品,导致上层软件产品在打包时没有将新增的dll库打包进去。
2.3.2.2、dll库调用其依赖的库中的接口,但该接口在被依赖的库中找不到
如果dll库调用其依赖的库中的接口在被依赖的库中找不到,则该dll库也会加载失败。如果该dll库是隐式加载的,则在程序启动时会报类似这个错误:
出现这类问题,一般是库与库的版本不一致导致的。在编译时引用了正确的dll库的导入库lib,编译是没问题的,但在程序打包时拷贝的是老版本的dll库,导致程序运行时出现调用的接口在底层的库中找不到。
至于为什么在依赖库中找不到接口呢?可能找不到的接口,在新版本库中被删除了,或者接口名称被修改了,亦或是接口的参数被修改了(比如增加了参数、删除了参数或者修改了参数类型)。关于接口参数被修改导致找不到接口的实例,可以查看我之前写的文章:
使用Process Explorer和Dependency Walker排查dll动态库没法调试的问题(dll库加载失败导致没法动态调试)https://blog.csdn.net/chenlycly/article/details/140803687
2.3.3、在某些系统上会出现LoadLibrary加载dll库失败的问题,需要使用LoadLibraryEx接口
我们在项目中遇到过几次使用LoadLibrary加载dll库失败的问题,dll库的路径是正确的(文件是存在的),但就是加载失败!当时深入研究了一下,参考Reactos开源系统中的regsvr32.exe中加载dll库的源码实现,将LoadLibrary改成了LoadLibraryEx,并传入LOAD_WITH_ALTERED_SEARCH_PATH参数,相关代码如下所示:
BOOL AutoRegsvr32( LPCTSTR lpszDllPath )
{
if ( lpszDllPath == NULL )
{
return;
}
// 改用LoadLibraryEx,并使用LOAD_WITH_ALTERED_SEARCH_PATH参数,避免部分系统无法加载到dll的问题。
HINSTANCE hInstLib = LoadLibraryEx( lpszDllPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH );
if ( NULL == hInstLib )
{
return FALSE;
}
typedef HRESULT (*DllRegisterServer)(void);
DllRegisterServer dllRegisterServer = (DllRegisterServer)GetProcAddress( hInstLib, "DllRegisterServer" );
if ( dllRegisterServer != NULL )
{
HRESULT hr = dllRegisterServer();
}
else
{
FreeLibrary(hInstLib);
return FALSE;
}
FreeLibrary(hInstLib);
return TRUE;
}
关于使用LoadLibrary加载dll库失败的实战分析案例,可以查看我之前的文章:(参考开源操作系统ReactOS的内部实现源码,将LoadLibrary改成了LoadLibraryEx,并传入LOAD_WITH_ALTERED_SEARCH_PATH参数)
【C++动态库】DLL动态库加载失败导致程序启动报错以及DLL库加载失败的常见原因分析与总结https://blog.csdn.net/chenlycly/article/details/142714236 据说这个用LoadLibrary加载dll库失败的问题,是因为部分Win7系统中LoadLibrary系统API函数内部实现的有缺陷导致的(当时项目中遇到的问题好像就是发生在Win7系统中的), 可以安装一个微软官方的补丁去解决。但给每个出问题的电脑都去安装补丁,是不现实的,最好的解决办法还是使用上面讲到的LoadLibraryEx接口去解决。
2.4、使用Process Explorer工具查看程序加载了哪些动态库以及动态加载的库有没有加载起来
Process Explorer类似于Windows任务管理器,可以查看当前系统中启动的进程信息,但功能比Windows任务管理器要强大很多。使用Process Explorer工具可以查看目标程序加载了哪些动态库,也可以确认哪些动态加载的库有没有加载起来。
打开Process Explorer后,在进程列表中选中目标进程,就可以查看到当前进程记载了哪些dll库,可以查看这些库的路径:
隐式加载的动态库,在程序启动时加载,如果加载失败,则会导致程序启动失败。而显式动态加载的dll库,加载失败不会对程序启动产生影响,有时我们需要去确认显式加载的dll库有没有加载起来,可以用Process Explorer查看,如果加载的dll库列表中找不到动态加载的库,则说明该库动态加载失败。
2.5、使用Dependency Walker工具查看dll库的依赖关系以及调用的接口,排查动态库加载失败问题
对于因为接口找不到以及依赖的库找不到导致的动态库加载失败或者exe程序启动失败,直接使用Dependency Walker工具打开这个问题dll或者exe主程序查看库与库之间的依赖关系即可找到原因。
具体分析方法和步骤,直接查看使用Dependency Walker查看库的依赖关系排查问题的项目问题实战分析案例即可,查看我的文章:
使用Process Explorer和Dependency Walker排查C++程序中dll库动态加载失败问题https://blog.csdn.net/chenlycly/article/details/140731158使用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
3、程序启动报错与启动失败
程序启动时,会优先将程序依赖的所有dll库加载到进程空间中,待所有库加载成功后,进入exe主程序的main函数,exe主程序才启动运行起来。如果程序依赖的dll库有问题,则会终止程序启动进程,程序启动失败。程序启动报错或者启动失败,大体有以下几个原因:
1)依赖的dll库在当前系统中找不到;
2)调用的接口在依赖的dll库中找不到;
3)依赖的dll库位数(32位/64位)不一致;
4)UI线程发生死循环或者死锁,导致UI界面显示不出来。
3.1、依赖的dll库找不到与调用的接口在依赖的库中找不到
对于隐式加载的库,都是在程序启动时加载的,如果隐式加载的库加载失败,会弹出报错提示框,则会终止当前主程序的启动,程序启动失败。
隐式加载的动态库加载失败,如果是因为依赖的库找不到,会弹出这样的提示框:
如果是因为调用的接口在依赖的库中找不到,则会弹出如下的提示框:
不仅是动态库会依赖其他动态库,exe主程序会直接调用动态库的接口,也是依赖很多动态库的。如果exe直接依赖的这些动态库有问题,也会弹出上述类似的报错提示框。exe主程序直接依赖一些动态库,这些动态库又依赖其他动态库,这样就形成了一个动态库依赖链,exe主程序启动时会把这个依赖链中的所有动态库都加载起来,都加载起来后才会进入exe主程序的main函数,exe主程序才运行起来。所以exe主程序启动报错,可能是exe主程序直接依赖的库有问题,也有可能是直接依赖的这些库依赖的底层库有问题。
3.2、依赖的dll库位数(32位/64位)不一致
我们上面讲到,除了依赖库或者调用的接口找不到,如果32位模块(exe/dll)与64位模块混用(exe/dll),会报关于0xC000007B错误,如下所示:
关于0xC000007B错误的实战分析案例,可以查看我之前写的文章:
使用Dependency Walker和Process Explorer排查程序启动时缺少ucrtbase.dll等运行时库以及报0xC000007B错误https://blog.csdn.net/chenlycly/article/details/131505299
3.3、UI线程发生死循环或死锁
还有一种特殊的启动失败,就是在启动过程中,exe主程序的UI线程卡住了,可能是UI线程中调用了某个接口内部发生了死循环或者发生了死锁,导致该接口一直没有返回,导致UI线程卡死,UI界面没有显示出来。给一种感觉,就是程序启动没反应,其实到任务管理器中能看到程序进程,就是没有将UI界面显示出来。
那如何确实是死循环导致的还是死锁导致的呢?如何进行排查呢?首先看一下任务管理器,看看软件进程的CPU占用是否高。程序启动时不会干很多事情,正常情况下CPU占用不会高,如果CPU占用高,那可以基本断定程序中发生死循环。关于高CPU占用的实战分析案例,我写过多篇文章,可以查看:
使用Process Explorer/Process Hacker和Windbg高效排查C++程序高CPU占用问题https://blog.csdn.net/chenlycly/article/details/140731953使用Process Explorer和Clumsy工具定位软件高CPU占用问题
https://blog.csdn.net/chenlycly/article/details/130038272 如果能排除死循环,那可能是发生死锁了,可以将Windbg附加到当前进程上进行分析。将Windbg附加到出问题的进程上,查看所有线程的函数调用堆栈,看看哪些线程调用了WaitForSingleObject等等待函数,结合这些线程堆栈中调用的函数,确定这些线程和哪些业务有关。必要时也要结合日志去排查并最终定位问题。之前写过使用Windbg排查多线程死锁的实战案例,可以去查看我的文章:
使用Windbg分析多线程临界区死锁问题分享https://blog.csdn.net/chenlycly/article/details/128532743
4、软件操作权限问题
4.1、管理员权限与标准用户权限
在Windows中,程序的运行权限主要有两种,一种是标准用户权限(非管理员权限),一种是管理员权限。有些操作是需要管理员权限的,比如向系统中注册控件、向C:\Program Files等系统关键路径中执行写操作(创建文件、向文件中写入内容等)、向HKEY_LOCAL_MACHINE注册表路径下执行写操作(创建注册表项、向注册表项写入内容等)。如果程序中要执行这些需要管理员权限的操作,需要以管理员权限运行,否则会操作失败。
一般安装程序需要以管理员权限运行,因为可能需要执行上述需要管理员权限的操作。
要让一个程序以管理员权限运行,一般有以下三种方法:
1)在程序的工程属性中设置requireAdministrator配置选项;
2)在启动程序时,选择右键点击程序或者程序的快捷方式,在弹出的右键菜单中选择以管理员身份运行;
3)在程序中启动另一个程序,可以调用ShellExecuteEx接口传入runas参数,将另一个程序以管理员权限启动起来。
4.2、权限不对等导致程序相互操作失败
权限不对等(运行权限不同,一个以标准用户权限运行,一个以管理员权限运行)的两个程序,在相互操作时会出现失败的问题。不同权限的两个程序进程,在相互操作时遵循以下的规则:
1)同等权限的两个程序进程,可以相互操作;
2)低权限(标准用户权限)的程序进程,不能操作高权限(管理员权限)的程序进程;
3)高权限(管理员权限)的程序进程,可以操作低权限(标准用户权限)的程序进程;
比如当我们需要将Windbg附加到目标进程上进行动态调试时,如果目标程序是以管理员权限运行的,而Windbg是直接双击桌面快捷方式启动的,是以标准用户权限运行,将Windbg附加到进程上会提示附加失败:
因为Windbg是以标准用户权限运行的,而要附加的目标进程是以管理员权限运行的,两个程序权限不对等,所以附加失败。
再比如我们使用API Monitor工具去监测目标程序对API函数调用时,如果API Monitor是直接双击以标准用户权限运行,那在API Monitor中显示的进程列表中是看不到以管理员权限运行的程序进程的。比如我们要监测QQ安装包程序的函数调用,QQ安装包是以管理员权限运行的,API Monitor是直接双击以标准用户权限运行,在API Monitor显示的进程列表中是看不到QQ安装包进程的。此时必须以管理员权限运行API Monitor,进程列表中才能看到QQ安装包进程:
看到QQ安装包进程,才能开启对QQ安装包进程的监测。
因为权限不够导致的不能操作问题,直接右键以管理员权限运行程序即可。
关于Windows应用程序运行权限以及因为权限不对等引发软件工具无法正常使用的实例分析,可以查看我之前写的文章:
Windows UAC权限详解以及因为权限不对等引发软件工具无法正常使用的实例分析https://blog.csdn.net/chenlycly/article/details/142304665
5、最后
上述内容均是从项目中遇到的实战问题总结归纳出来的,很有实战参考价值,希望能帮到大家。
当前是C++软件实战问题排查经验分享系列第2期,第1期的分享已经发布,感兴趣的话,可以去查看文章:
更多推荐
所有评论(0)