目录
1、什么是pdb文件?
2、如何配置生成pdb文件?
3、pdb文件的时间戳和文件名称
3.1、pdb文件的时间戳
3.2、pdb文件的文件名称
4、有pdb文件才能在Visual Studio中调试代码
5、在Windbg中使用pdb文件
5.1、使用lm命令查看二进制文件的时间戳,去查找对应版本的pdb文件
5.2、在Windbg中配置pdb文件路径
5.3、如何确定pdb是否加载成功?如果加载失败,可以尝试去强制加载
5.4、pdb文件加载失败的可能原因有哪些?
5.5、有时需要使用到系统库的pdb文件
5.6、关于微软系统库在线pdb下载服务器的说明
5.7、有时需要在Windbg中查看相关变量的值
6、使用Process Explorer、Process Monitor等工具查看函数调用堆栈时需要用到pdb文件
6.1、使用Process Explorer中查看函数调用堆栈时需要使用pdb文件
6.2、使用Process Monitor中查看函数调用堆栈时需要使用pdb文件
7、在反汇编工具IDA中查看汇编代码也需要用到pdb文件
7.1、使用反汇编工具IDA查看汇编代码上下文
7.2、编译器优化代码对我们查看汇编上下文的影响
7.3、查看汇编上下文去辅助定位软件异常问题的实例
7.4、排查软件异常需要掌握哪些基础汇编知识?
7.5、通过查看C++代码对应的汇编代码去学习汇编,将C++源码与汇编代码对照着学
7.6、学习汇编有哪些好处?
8、最后
C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C/C++基础与进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.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++软件异常问题时,需要借助多个软件工具去分析(比如SPY++、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Windbg和IDA等),很多时候都需要去查看异常发生时的函数调用堆栈及变量信息。要查看到详细的函数调用的堆栈(看到具体的函数)及变量信息,需要使用到pdb符号文件。本文结合多年来排查C++软件异常的实践,详细介绍什么是pdb文件,哪些工具需要使用到pdb文件,以及如何去使用pdb文件,以供大家借鉴或参考。
1、什么是pdb文件?
pdb(Program Database)文件是Windows平台用于存储程序调试信息的文件格式,pdb文件中包含了编译后的可执行文件或动态链接库的符号表、源代码文件路径、局部变量和全局变量的信息等。pdb文件通常与对应的可执行文件(如.exe或.dll)一起使用,以支持程序的调试和分析。
pdb文件主要包含如下的信息:
1)符号表信息:pdb文件包含了编译后的程序的符号表信息,包括函数名、类名、结构体名、变量名等。这些符号信息可以帮助调试器在调试过程中准确地定位和查看变量的值、函数的调用关系等。
2)源代码文件路径:pdb文件记录了源代码文件的路径信息,这使得调试器能够在需要时自动加载对应的源代码文件,以便开发人员在调试过程中查看和修改源代码。
3)局部变量和全局变量信息:pdb文件还包含了局部变量和全局变量的信息,包括变量名、类型和内存地址等。这些信息对于调试器来说很重要,它们使得开发人员可以在调试过程中查看和修改变量的值,有助于分析程序中的问题。
4)回溯栈信息:pdb文件中还包含了堆栈帧和函数调用关系的信息,这对于调试器来说非常重要。通过pdb文件,调试器可以在堆栈回溯时准确地还原函数调用的顺序,帮助开发人员理解程序的执行流程。
pdb文件是Windows平台开发工具链(比如Visual Studio、QT Creator等)生成的包含函数变量符号及调试信息的符号文件。在进行程序的调试和分析时,通常需要将pdb文件与对应的可执行文件一起使用。调试器(比如Visual Studio、Windbg等)会根据pdb文件中存放的函数符号及变量符号等信息,来展示变量值、源代码行号、函数调用堆栈等相关信息,方便调试分析。
有了pdb,函数调用堆栈中才能看到具体的函数名及代码的行号,才能看到变量的信息,查看到变量的值。无论是在Visual Studio中调试,还是在Windbg中调试,都亦如此!
2、如何配置生成pdb文件?
以Visual Studio为例,在Visual Studio中,不管是Debug还是Release下,默认都会生成pdb文件。在链接器->调试->生成程序数据库配置项中,如下所示:
QT Creator中也支持配置生成pdb文件。
本地程序之所以能调试,是因为其二进制文件在本地编译生成时会自动写入对应的pdb文件的绝对路径:
pdb文件中存放着用于调试的各种调试信息。启动调试时调试器会根据二进制文件中记录的pdb文件路径去尝试加载pdb文件,加载成功后,就能获取pdb文件中的调试信息,这样就可以调试了。
在Linux系统中,符号信息是内置到二进制文件中的,不是单独出来的符号文件。
3、pdb文件的时间戳和文件名称
在使用pdb文件时,有两点需要注意一下,一个是pdb文件的时间戳,一个是pdb文件的名称。
3.1、pdb文件的时间戳
加载pdb文件时会严格校验pdb文件的时间戳(就是生成pdb文件的时间),必须要和对应的二进制文件生成时间完全一致。即使是同样的代码,即没有修改任何代码,在不同时间点编译生成的pdb文件,也是不能和二进制文件交叉使用的。
3.2、pdb文件的文件名称
在默认情况下,Visual Studio车工程pdb文件是和工程名称一样的:
也可以在工程配置中指定名称。可以在工程配置中修改pdb文件的名称,但一旦pdb文件生成之后,是不能修改名称的!如果修改了,调试器是无法加载的。
这点是在我们排查某个项目的崩溃问题时发现的。比如某个工程名称为videocodec_hp.vcxproj,生成的pdb文件的名称默认应为videocodec_hp.pdb(不区分大小写),但在编译脚本中执行文件拷贝时将pdb文件的名称改成videocodec.pdb(去掉了_hp的后缀),这种情况下Windbg是无法加载的。必须手动将pdb文件名改成生成时的名称,才能正常加载使用。
所以还是要多动手实践,在实践的过程中,才能发现更多的细节和问题!
4、有pdb文件才能在Visual Studio中调试代码
二进制文件中会默认写入pdb文件的完整路径,比如:
调试时会通过完整路径去寻找pdb文件,加载文件中的变量函数等符号信息及其他调试信息,去调试代码。并在调试器中显示完整的函数调用堆栈和变量值信息。
在Visual Studio中可以进行Debug调试、Release调试和附加到进程调试。对于Debug调试和Release调试,编译生成的二进制文件和pdb文件在同一个路径下,调试是没问题的。
对于附加到进程调试,一般用于程序底层模块的调试。负责维护底层模块代码的同事,事先将程序安装到其开发机器上,或者直接拷贝包含exe主程序的程序包到其机器上。然后在其机器上用Visual Studio打开要调试的工程,然后将工程代码编译一下,将生成的dll文件拷贝到exe主程序的路径中,然后将exe主程序运行起来,此时exe主程序使用的就是新编译生成的dll文件。此时可以将打开dll文件工程的Visual Studio附加到exe主程序进程上,用Visual Studio调试dll工程的源码。具体附加方法是,点击菜单栏中的调试->附加到进程,在弹出的窗口中找到exe进程:
点击附加按钮即可,就可以打断点调试了。
关于Visual Studio常用调试方式说明,可以查看我的文章:
Visual Studio调试方式详解https://blog.csdn.net/chenlycly/article/details/125587329 关于Visual Studio的常用调试技巧与方法,可以查看我的文章:
Visual Studio调试技巧与实用方法总结(实战经验分享)https://blog.csdn.net/chenlycly/article/details/124884225 曾经有人问我,将dll库拷贝到exe主程序的路径中,为啥附加调试时还可以调试该dll库的源码呢?dll文件和对应的pdb文件已经不在一个地方了(同一个路径中)。其实之所以能调试,是因为编译时生成的调试信息都保存到pdb文件中了,虽然调试的dll二进制文件与其对应的pdb文件不在一个路径中了,但pdb文件的完整路径已经被写到dll二进制文件中,Visual Studio在开始调试时,会到dll二进制文件中将pdb文件的完整路径读出来,然后通过这个路径找到pdb文件,并将pdb文件加载到Visual Studio中,所以就可以调试了。因为dll文件与其对应的pdb文件在同一个电脑上,通过dll文件中写入的pdb文件完整路径可以找到pdb文件。如果将dll文件拿到另一个电脑上,这样从dll中读取的pdb路径去找pdb文件肯定是找不到的,因为另一个电脑上对应的路径中根本没有这个pdb文件。
在这里,给大家重点推荐一下我的几个热门畅销专栏,欢迎订阅:(博客主页还有其他专栏,可以去查看)
专栏1:(该精品技术专栏的订阅量已达到430多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,预计更新到200篇以上!欢迎订阅!)
C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931
本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的项目问题实战分析实例(很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!
考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!
专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!
专栏2:
C/C++基础与进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html
以多年的开发实战经验为基础,总结并讲解一些的C/C++基础与进阶内容,以图文并茂的方式对C++相关知识点进行详细地展开与剖析!专栏涉及了C/C++开发领域多个方面的内容,同时给出C/C++及网络方面的常见笔试面试题,并详细讲述Visual Studio常用调试手段与技巧!
专栏3:
VC++常用功能开发汇总https://blog.csdn.net/chenlycly/article/details/124272585
专栏将10多年C++开发实践中常用的功能,以高质量的代码展现出来,并对相关功能的实现细节进行了详细的说明。这些常用的代码,其质量与稳定性是有保证的,可以直接拿过去使用,可以有效地解决C++软件开发过程中遇到的问题。
5、在Windbg中使用pdb文件
我们在使用Windbg分析软件问题时,无论是静态分析dump文件,还是动态调试目标进程,都需要查看异常发生时的函数调用堆栈。要在函数调用堆栈中看到具体的函数名和代码的行号,必须要加载堆栈中相关模块的pdb文件。因为函数名称及代码行号等符号信息都保存在pdb文件中的。
5.1、使用lm命令查看二进制文件的时间戳,去查找对应版本的pdb文件
根据函数调用堆栈中显示的那些模块,确定需要去找哪些模块的pdb文件。一般不需要找堆栈中所有模块的pdb文件,一般只要找离崩溃点较近的一个或多个模块。可以使用lm命令查看模块的时间戳:
然后通过这个时间戳去找对应时间点的pdb文件。要事先将所有版本的pdb文件统一保存维护起来,当某个版本出问题时,根据时间戳查找对应时间点的pdb文件即可。
5.2、在Windbg中配置pdb文件路径
点击Windbg菜单栏中的File -> Symbol File Path ...,打开设置pdb文件路径的窗口,如下所示:
一般我们设置如下格式的pdb组合文件路径:
C:\Users\Administrator\Desktop\pdbdir;srv*f:\mss0616*http://msdl.microsoft.com/download/symbols
这么个一长串组合路径主要由下面两个路径构成:(路径之间使用分号隔开)
1)应用程序库pdb文件路径(非系统库):
C:\Users\Administrator\Desktop\pdbdir。我们开发的模块的pdb文件集中拷贝到该路径中,路径名称可以随意选择。
2)Windows系统库在线下载路径:
srv*f:\mss0616*http://msdl.microsoft.com/download/symbols,其中http://msdl.microsoft.com/download/symbols,是微软提供的在线系统pdb文件下载服务器,如果设置了该地址,Windbg会自动连接该服务器,去自动下载与当前dump文件中用到的系统库版本一致的pdb文件。另外,f:\mss0616路径是从微软pdb文件服务器上下载下来的pdb文件的存放路径。
一般情况下,我们不用一上来就设置Windows系统库的pdb路径(微软在线系统库pdb文件下载地址),我们只需要设置软件业务库(非系统库)的pdb文件路径即可!至于什么时候需要用到系统库的pdb文件,我们下面会讲到。
关于Windows系统库的pdb文件及路径说明,可以查看微软官网的说明:
Symbol path for Windows debuggershttps://learn.microsoft.com/zh-cn/windows-hardware/drivers/debugger/symbol-path
5.3、如何确定pdb是否加载成功?如果加载失败,可以尝试去强制加载
有时即使在Windbg中已经设置了正确的pdb路径,勾选了reload选项,查看函数调用堆栈,可能还是看不到部分模块中的具体函数名及代码的行号,比如:
只能看到模块名TestDlg,这是exe主程序,但看不到具体的函数,应该是Windbg没加载到TestDlg模块对应的pdb文件导致的。
可以使用 lm vm TestDlg* 命令查看TestDlg模块的详细信息,如果看不到pdb路径:
说明Windbg没加载到当前模块的pdb文件。如果加载到pdb文件,则会显示pdb文件的完整路径:
这里需要额外注意一下,即使在Windbg中设置了pdb所在的路径,并且勾选了reload选项,如下:
可能Windbg也不会自动将pdb加载起来。此时,可以尝试用.reload命令强制加载一下,命令如下:
.reload /f TestDlg.exe
说不定可以加载起来,这点我们在项目中遇到过很多次了!
注意,使用.reload命令去强制加载,不代表可以去强制加载不对应的pdb文件。如果pdb文件的时间戳和二进制文件不一致,即不是一个时间点生成的,即便强制加载,也加载不了的。
5.4、pdb文件加载失败的可能原因有哪些?
pdb文件加载失败可能是以下某个原因导致的:
1)没有设置正确的pdb文件路径
没有将pdb文件路径设置到Windbg中,或者设置了错误的pdb文件路径。
2)pdb文件的时间戳与二进制文件不一致
pdb文件的时间戳和对应的二进制文件(dll或exe文件)不一致,即pdb文件和二进制文件不是同一个时间点生成的。加载pdb文件时,会严格校验pdb文件的时间戳,必须要和对应的二进制文件完全一致。即便代码没修改过,不同时间点编译的pdb文件,也是不能交叉使用的。
3)pdb文件生成后名称不能修改
在Visual Studio的工程属性中,默认配置了生成pdb文件的,pdb文件的名称默认就是工程名称。在QT Creator中编译的程序,也可以配置生成pdb文件。这些pdb生成时,会自动将pdb文件的名称写到对应的二进制文件中:
后面Windbg去根据写入的pdb名称去加载pdb文件的。一旦pdb文件生成了,其名称是不能人为修改的,人为修改后会导致pdb文件无法加载的。
对于pdb文件生成后被修改名称导致Windbg加载失败的实战案例,我前几天正好遇到过一次,可以查看我的文章:
pdb文件名称被修改导致pdb文件加载失败的实战排查案例分享https://blog.csdn.net/chenlycly/article/details/139393894
5.5、有时需要使用到系统库的pdb文件
有时从函数调用堆栈看,程序崩溃在系统库中,但基本都不是系统库有问题,问题最终还是出在上层的业务模块中,比如上层业务模块在调用系统库的接口(比如memcpy等)时,传入了不合法的参数,或者传入了错误的内存地址或不可访问的内存地址,导致系统库的接口内部发生异常。
我们在设置业务模块的pdb文件后无法最终确定问题时,可能会需要加载系统库的pdb文件。加载系统库pdb文件后,我们可以看到调用那些系统库的接口,这样可能便于问题的分析。此外,加载系统库pdb文件之后,可能能看到更多行的函数调用堆栈,我们在实际的项目问题中遇到多次了。
5.6、关于微软系统库在线pdb下载服务器的说明
Windbg会根据函数调用堆栈中涉及的系统库的模块,去自动下载这些系统库的pdb文件。软件可能在不同版本的Windows系统中崩溃,比如可能是Win7、Win8、Win10、Win11等,即便是系统名称是一样的,可能对应着不同的子版本。不同版本的系统,对应的系统dll库的版本也是不同的。微软系统库在线pdb下载服务器中存放了所有版本的系统库的pdb文件,Windbg会根据要下载的系统dll库的特征值(比如md5)到服务器上去下载对应版本的pdb文件,然后自动将pdb加载到Windbg中。
5.7、有时需要在Windbg中查看相关变量的值
有时不仅要看函数调用堆栈中具体的函数名和代码的行号,还要看函数中相关变量在内存中的值,在有些场景下,相关变量的值可能是分析问题的关键线索。
我之前写过两篇通过变量的值去快速定位问题的案例,可以查看文章:
通过查看Windbg中变量值去定位C++软件异常问题https://blog.csdn.net/chenlycly/article/details/125731044通过查看Windbg中变量值去定位C++软件异常的又一典型案例分享https://blog.csdn.net/chenlycly/article/details/125793532
6、使用Process Explorer、Process Monitor等工具查看函数调用堆栈时需要用到pdb文件
有时我们在分析软件问题时,需要先借助一些软件工具去分析,比如使用Process Explorer排查CPU高占用问题、使用Process Monitor监测目标程序的文件活动(读写了哪些文件)和注册表活动(读写了哪些注册表)。
6.1、使用Process Explorer中查看函数调用堆栈时需要使用pdb文件
我们在使用Process Explorer排查CPU高占用问题时,现在进程列表中找到目标进程,然后双击之,在弹出的进程属性窗口中,切换到Threads标签页中,找到CPU占用高的那个线程,双击之,查看线程的函数调用堆栈,如下所示:
如果要在堆栈中看到具体的函数名和行号,则需要加载pdb文件。只需要将pdb文件放在对应的二进制文件的同级目录中,Process Explorer就会去搜索加载的。
关于使用Process Explorer排查程序高CPU占用的实战分析案例,可以查看我之前写的文章:
使用Process Explorer/Process Hacker和Windbg高效排查软件高CPU占用问题https://blog.csdn.net/chenlycly/article/details/135822428
6.2、使用Process Monitor中查看函数调用堆栈时需要使用pdb文件
我们可以使用Process Monitor监测目标程序的注册表活动和文件活动。比如监测程序都读写了哪些注册表项,都读写了哪些文件,当监测到记录时双击可以查看执行这些操作时的函数调用堆栈:
这样就知道是哪一处的代码执行了这个操作。
关于使用Process Monitor监测注册表活动和文件活动的实战分析案例,可以查看我之前写的文章:
使用Process Monitor探测Windows系统高DPI缩放设置的注册表项https://blog.csdn.net/chenlycly/article/details/130586460%C2%A0%C2%A0使用Process Monitor工具探测日志文件是程序哪个模块生成的https://blog.csdn.net/chenlycly/article/details/133339034
7、在反汇编工具IDA中查看汇编代码也需要用到pdb文件
这部分内容涉及到汇编相关内容,我会进行详细的展开,比如如何学习汇编、排查软件异常需要掌握哪些基础的汇编知识、如何查看C++源码对应的汇编代码以及通过查看汇编上下文去辅助定位软件异常的实例。
7.1、使用反汇编工具IDA查看汇编代码上下文
程序一般是崩溃在某一条汇编指令上,但有时通过查看这条汇编指令及异常发生时的函数调用堆栈可能无法定位问题,这时候可能就需要借助查看汇编代码上下文去辅助定位问题了。
CPU中最终执行的二进制机器码,等同于汇编代码。二进制机器码可读性差,汇编指令作为二进制指令的助记符,可读性好,我们阅读汇编代码等同于阅读二进制机器码。汇编代码能直观地反映出问题的原因所在,比如汇编指令中访问了一个很小的内存地址,引发内存访问违例。
一句C++源码可能对应几行汇编代码,在查看汇编代码上下文(发生崩溃的那条汇编指令附近的上下文)时,需要将C++源码与汇编代码对照着看,找到二者的对应关系,这样才好读懂汇编上下文。抛开C++源码,直接去阅读汇编代码上下文,很难读懂,因为这需要很深的汇编功底才能做到。
可以直接在Windbg中查看汇编代码上下文,但Windbg中显示的汇编代码没有注解,很难和C++源码对应起来。通常我们使用反汇编工具IDA打开二进制文件去查看汇编代码上下文。为了读懂汇编代码上下文,我们需要在IDA显示的汇编代码中看到具体的函数名、变量符号及相关注释信息:
通过这些信息去确定C++源码与汇编代码的对应关系,这样更便于我们读懂汇编上下文。如果要看到具体的函数名、变量符号及相关注释信息,IDA要加载当前打开的二进制文件的pdb文件。IDA从pdb中读出函数及变量等符号信息,然后利用这些符号信息去展示汇编代码。将pdb文件放在二进制文件的当前路径下即可,IDA会从当前路径中去搜索加载pdb文件。
关于如何使用反汇编工具IDA,可以查看我的文章:
反汇编工具IDA使用详解https://blog.csdn.net/chenlycly/article/details/120635120
7.2、编译器优化代码对我们查看汇编上下文的影响
有一点需要注意一下,C++源码在release下编译时编译器会对源码进行优化:(比如下图中的Visual Studio工程编译选项,默认使用最大速度优化)
生成的汇编代码是优化后的,这对我们去找C++源码与汇编代码的对应关系会产生一定的障碍。比如在C++源码中一个函数调用了另一个函数,如果编译器没对代码进行优化,则我们会看到call被调用函数指令。编译器可能会对代码进行优化,直接把函数调用给优化掉了,即用几句汇编代码就把函数调用给替代了。
编译器之所以对代码进行优化,就是为了提高代码执行的效率。以优化函数调用为例,如果不优化,在发生函数调用时,需要将参数压到栈上传递给被调用函数,进入被调用函数后要保护主调函数的现场(保护现场,即保存相关主调函数相关寄存器的值),然后在即将退出函数时恢复主调函数的现场(恢复现场),最后将传递参数占用的栈空间给清理掉,这些都是函数调用的开销。
将函数调用给优化掉,可以避免函数调用的开销,从而提高代码的执行速度。举一个最简单的例子,代码中调用了C函数memcpy执行内存拷贝,优化后的汇编代码中是看不到call memcpy这个函数的操作,几句汇编代码就代替了函数调用。
7.3、查看汇编上下文去辅助定位软件异常问题的实例
关于如何通过查看汇编上下文去辅助定位问题,举一个简单的例子。在C++中,一个虚函数的调用(用类指针去调用一个虚函数),会对应三句汇编代码。这三句汇编代码涉及到两次寻址,虚函数的寻址过程如下所示:
虚函数调用的实例汇编代码如下:
.text:005103EB call ds:__imp__GetContactPtr
.text:005103F1 mov [ebp+var_2AF4], eax
.text:005103F7 mov ecx, [ebp+var_2AF4] // 1、ecx存放要调用的虚函数所属类对象的首地址
.text:005103FD mov edx, [ecx] // 2、【第一次寻址】类对象的首地址就是类中隐藏的成员变量虚函数表指针的首地址,对该地址寻址,从该地址读出的内容就是虚函数表的首地址
.text:005103FF mov ecx, [ebp+var_2AF4] // 3、调用类的成员函数前,要将类对象首地址同ecx寄存器传给被调用的成员函数
.text:00510405 mov eax, [edx+78h] // 4、【第二次寻址】根据目标虚函数在虚函数表中的偏移,找到目标虚函数在虚函数表中的位置,然后将该位置中的内容读出来,就是目标虚函数代码段地址
.text:00510408 call eax // 5、直接call目标虚函数代码段地址,完成虚函数的调用
1)第一次寻址,是从类对象的虚函数表指针变量(内存)中读出变量值,该值就是虚函数的首地址;
2)第二次寻址,根据要调用的虚函数在虚函数表中的偏移位置,到虚函数表对应位置的内存中读出要调用的虚函数首地址(代码段的地址),然后去call这个虚函数。
如果通过ecx寄存器传入的C++类对象的首地址是个空指针,则会导致三句汇编代码中的第一句出现崩溃。我们再反过来看,我们看出问题的这句汇编是虚函数调用的汇编(通过将源码与汇编代码对照着看来确定),如果崩溃的汇编指令中访问了一个很小的内存地址,则说明传入的C++类对象的首地址有问题,比如传入了空指针。
如果要查看虚函数调用的汇编代码实现的详细说明,可以查看我之前写的文章:(文章中有具体的汇编实例)
几秒读懂C++虚函数调用的汇编代码实现https://blog.csdn.net/chenlycly/article/details/121046234 有次通过查看C++业务模块的汇编代码上下文,结合安卓系统生成的Tombstone文件,去快速定位安卓app底层的C++业务模块的崩溃问题。这一案例也写成了文章,感兴趣可以去查看:
使用IDA查看汇编代码上下文,结合安卓系统生成的Tombstone文件,排查安卓app程序底层库崩溃问题https://blog.csdn.net/chenlycly/article/details/135484104
7.4、排查软件异常需要掌握哪些基础汇编知识?
有朋友可能会问,既然要用到汇编,要去阅读汇编代码上下文,那我们应该如何学习汇编?需要掌握哪些汇编相关的基础知识呢?之前我总结过排查C++软件异常所需要掌握的基础汇编知识,可以查看我的文章:
分析C++软件异常需要掌握的汇编知识汇总https://blog.csdn.net/chenlycly/article/details/124758670
7.5、通过查看C++代码对应的汇编代码去学习汇编,将C++源码与汇编代码对照着学
大家在上大学时很多人都学习汇编(比如8086微机原理),只是孤立地学习汇编,没能和实际应用结合起来(这是大学教育中一个普遍存在的痛点问题),很难将汇编与C++等高级语言的源代码对应起来。不仅学着枯燥,而且感觉没什么实际应用价值。现在要解决项目中的实际问题,需要将汇编代码与C++源代码对应起来看,学起来会有意思、有价值很多。
查看C++源代码去查看对应的汇编代码是一个学习汇编的途径,特别是看一些典型的C++语句对应的汇编代码实现,比如C++虚函数调用的汇编代码实现、函数调用时参数是如何压到栈上传递给被调用函数的。
可以在正在调试C++代码的Visual Studio中查看C++代码片对应的汇编代码,也可以使用反汇编工具IDA打开二进制文件去查看汇编代码(Release版本二进制文件中是经过编译器优化后的汇编代码)。
7.6、学习汇编有哪些好处?
其实,通过项目实践发现,熟悉汇编是很有用的,不仅可以辅助排查软件异常问题,还可以从汇编代码的角度理解很多高级语言不好理解的代码执行细节(比如多线程问题)。对于C++程序,在CPU中执行的是二进制文件中的一条条汇编指令,汇编代码能最直观地反映出程序的执行路径和细节。
之前针对学习汇编的诸多好处进行了说明和总结,可以查看我的文章:
为什么要学习汇编?学习汇编有哪些好处?https://blog.csdn.net/chenlycly/article/details/130935428
8、最后
本文是在多年的C++软件异常排查实践的基础上总结出来的,希望能给大家提供一定的借鉴或参考。