目录
1、什么是pdb文件?pdb文件有哪些用途?
2、pdb文件的时间戳与pdb文件名称
3、常用软件分析工具有哪些?
4、使用Windbg调试器查看函数调用堆栈时需要加载pdb文件
4.1、给Windbg设置pdb文件路径
4.2、为什么要设置系统库pdb文件下载服务器地址?
5、使用IDA反汇编工具查看二进制文件的汇编代码时需要加载pdb文件
6、使用Process Explorer查看线程的函数调用堆栈时需要加载pdb文件
7、使用Process Monitor查看函数调用堆栈时需要使用到pdb文件
8、API Monitor可以查看函数调用堆栈,但不支持加载pdb文件
9、最后
VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具案例集锦(专栏文章正在更新中...)https://blog.csdn.net/chenlycly/article/details/131405795 日常工作中分析C++软件问题时,有些分析工具是可以查看发生问题时的线程函数调用堆栈,比如Windbg、IDA、Process Explorer/Process Hacker、Process Monitor和API Monitor等。如果要在函数调用堆栈中能看到具体的函数名,甚至代码的行号,则需要加载pdb符号文件。今天我们就来详细讲讲哪些分析工具需要加载pdb符号文件。
1、什么是pdb文件?pdb文件有哪些用途?
PDB - Program Databse File,程序数据库文件,存放了二进制文件中所有函数及变量的符号,还有一些调试用的信息,要查看完整的函数调用堆栈信息及变量信息,都需要用到pdb文件。pdb文件是在编译C++工程代码时产生的,它是与对应的exe或dll模块一起生成的。
在Visual Studio中,不管是Debug还是Release下,默认都会生成pdb文件,默认配置如下所示:
本地程序之所以能调试,是因为其二进制文件在本地编译生成时会自动写入对应的pdb文件的绝对路径,而pdb文件中存放着用于调试的各种调试信息。启动调试时调试器会根据二进制文件中记录的pdb文件路径去尝试加载pdb文件,加载成功后,就能获取pdb文件中的调试信息,这样就可以调试了。
如果手动将对应目录中的pdb文件删除,在没有重新生成的情况下,程序肯定是无法调试的!
2、pdb文件的时间戳与pdb文件名称
在使用pdb文件时,有两点需要注意一下,一个是pdb文件的时间戳,一个是pdb文件的名称。
加载pdb文件时会严格校验pdb文件的时间戳(就是生成pdb文件的时间),必须要和二进制文件完全一致。即使是同样的代码,没有修改任何代码,在不同时间点编译生成的pdb文件,也是不能交叉使用的。
pdb文件的名称必须和工程的名称一致,否则也是无法加载的。比如某个工程名称为videocodec_hp.vcxproj,生成的pdb文件的名称默认应为videocodec_hp.pdb(不区分大小写的),但在执行文件拷贝时将文件名称改成videocodec.pdb,这种情况下Windbg也是无法加载的。必须手动将pdb文件的名称改成Visual Studio工程文件的名称后,pdb文件才能正常加载。这个问题我们遇到过几次,就是因为pdb文件名称与工程名称不一致导致pdb加载不起来的。
3、常用软件分析工具有哪些?
在日常工作中,常用的软件分析工具有PE工具、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Clumsy、DebugDiag、Windbg、IDA等,关于这些工具的详细介绍与说明,可以参见我之前写的文章:
C++软件开发值得推荐的十大高效软件分析工具https://blog.csdn.net/chenlycly/article/details/127608247这些工具中不是都要使用pdb,一般可以查看函数调用堆栈的,都要使用到pdb文件。Process Explorer、Process Monitor、DebugDiag、Windbg和IDA需要使用到,下面就来详细讲述一下。
4、使用Windbg调试器查看函数调用堆栈时需要加载pdb文件
Windbg分析软件异常有两种方式,一种是静态分析dump文件,一种是动态调试目标进程。一般都需要在Windbg中查看发生异常时的函数调用堆栈,然后将函数调用堆栈与C++源码对照起来看,去分析软件异常。
但光看到函数调用堆栈是不够的,需要在堆栈中看到具体的函数名,甚至代码行号,这样才好去分析问题。如果没有pdb文件,则堆栈中显示的是相对模块(起始地址)的偏移,或者相对于模块导出函数的偏移(模块的导出函数符号是公开的,外部是可见的),一般看不到真实具体的函数名。要看到包含具体函数名和代码行号的函数调用堆栈,则需要加载相关dll或exe二进制文件的pdb符号文件。通过pdb文件中包含的函数及变量的符号,去显示更为详细的函数调用堆栈。
4.1、给Windbg设置pdb文件路径
在未加载pdb文件之前,通过查看函数调用堆栈中的模块名称,然后使用lm命令去查看模块的生成时间戳去找对应时间点的pdb文件。要让Windbg加载pdb文件,则需要将pdb文件所在的路径设置到Windbg中。可以将多个模块的pdb文件统一拷贝到本机的某个文件夹中,然后在Windbg菜单栏中点击File -> Symbol File Path...,会打开设置pdb文件路径的窗口:
将pdb文件路径设置给Windbg。
一般我们设置如下格式的pdb文件路径组合:
C:\Users\Administrator\Desktop\pdbdir;srv*f:\mss0616*http://msdl.microsoft.com/download/symbols
这个一长串组合路径主要由下面两个路径构成:(多个路径之间使用分号隔开)
1)应用程序库的pdb文件路径(非系统库):
C:\Users\Administrator\Desktop\pdbdir。我们将相关开发模块的pdb文件集中拷贝到该路径中,然后将路径设置进来。多个pdb文件也可以放在不同路径下,可以设置多个路径,路径之间使用分号隔开。
2)Windows系统库的pdb文件在线下载地址:
srv*f:\mss0616*http://msdl.microsoft.com/download/symbols,其中http://msdl.microsoft.com/download/symbols,是微软提供的在线系统pdb文件下载服务器。如果设置了该地址,Windbg会自动连接该服务器,去自动下载与当前dump文件中用到的系统库版本一致的pdb文件。另外,f:\mss0616路径是从微软pdb文件服务器上下载下来的pdb文件的存放路径。
4.2、为什么要设置系统库pdb文件下载服务器地址?
设置系统库的pdb文件下载服务器地址,是为了让Windbg自动去下载Windows系统库的pdb文件,并自动将系统库的pdb文件加载进去。加载系统库的pdb符号库文件之后,不仅能看到具体的系统接口的调用,甚至还可以看到更多行的函数调用:
看到的可能是更多的系统层函数的调用记录,也可能是更多的应用层函数的调用记录。有时能直接从具体的系统函数调用接口中能得到一定的线索。另外,看到更多行的函数调用,可能更有利于快速定位发生崩溃的具体位置。
关于设置系统库pdb下载地址去看到更多函数调用堆栈的典型问题实例,可以参见我之前写的一篇文章:
播放WebRTC开源库回调出来的视频码流时遇到的内存越界问题排查https://blog.csdn.net/chenlycly/article/details/131222627 此外,这里有个细节需要注意一下,有时微软这个服务器会有连接不上或卡顿的情况,会直接导致Windbg卡顿,这个问题我们之前遇到过很多次。所以遇到Windbg严重卡顿的时候,可以先将该服务器下载地址删除掉。但有时我们需要设置该在线地址,因为有时我们想去看到底是调用了系统库中的哪个接口触发的崩溃。
5、使用IDA反汇编工具查看二进制文件的汇编代码时需要加载pdb文件
有时我们需要使用IDA反汇编工具去查看二进制文件的汇编代码上下文,去辅助分析C++软件异常问题。在分析问题时,要看懂汇编代码的片段或者上下文,需要将汇编代码和程序源码对应起来,但直接查看汇编很难和程序关联起来。所以在查看汇编代码时,需要和C++源码对照着看,即通过源码去辅助汇编代码的阅读。但C++代码在编译时,Release下编译器会对代码进行优化,有些代码会被优化掉,所以C++源码很难完全和汇编代码对应起来。
关于编译器优化的例子,比如C++源码中有个函数调用(调用了某个函数),编译器在编译时为了避免函数调用的开销(传递的参数要入栈出栈,要保护现场和恢复现场),直接将函数调用给优化掉,使用几句汇编代码将函数调用替换掉了。
正因为汇编代码很难完全和C++源码对应起来,我们希望借助其他信息去将对应关系找出来。如果没有pdb文件,IDA中显示的汇编代码看不到具体的函数及变量信息,一般较难去直接阅读汇编代码上下文,是需要很强的汇编功底才行。
如果IDA加载了当前二进制文件的pdb文件后,汇编代码中会显示具体的函数及变量信息,还会在汇编代码中额外添加一些注释,比如:
有了这些信息,阅读汇编代码上下文会显得容易很多,能够较容易地将汇编代码与C++源码对应起来。如果有pdb文件,只要将pdb文件放在二进制文件的同级目录中,这样在Windbg打开二进制文件时会自动到二进制文件所在的目录中去搜索,搜索到即会自动去加载pdb文件。
也可以到IDA的安装路径下找到cfg文件夹,在该文件夹中找到pdb.cfg配置文件,在配置文件中设置pdb路径,不过这个操作略显麻烦,没有直接放到安装路径下简单。
6、使用Process Explorer查看线程的函数调用堆栈时需要加载pdb文件
在使用Process Explorer工具排查程序CPU占用高的问题时,在Process Explorer显示的进程列表中双击目标进程,在打开的进程属性窗口中点击Threads标签页,切换到进程的线程列表页面,就能看到进程中的所有线程信息,找到那个CPU占用高的线程:
双击查看该线程的函数调用堆栈,然后根据函数调用堆栈去查看C++源码,去分析为什么会占用较高的CPU。如果没有pdb文件,显示出来的函数调用堆栈中看不到具体的函数名,看不到具体的函数名就没法去做跟踪分析了。
此时如何去查看相关模块的pdb文件呢?此时没用Windbg,所以没法使用Windb的lm命令去查看二进制模块的时间戳,那如何获取去获取二进制文件的时间戳呢?直接查看文件属性中的修改时间是不靠谱的,它只代表系统中该文件的修改时间,和文件的编译生成时间没关系的。其实很简单,我们只要下载个PE工具,然后将二进制文件拖入到PE工具中即可查看文件的生成时间戳,如下所示:
然后通过该时间戳找到二进制模块对应时间点的pdb文件。
只要将pdb文件放在对应模块所在的文件夹中,Process Explorer就会自动去搜索加载的。当然也可以在Process Explorer中配置pdb文件的路径,点击Process Explorer菜单栏中的Options -> Configure Symbols...,打开如下的配置窗口:
配置pdb文件的路径即可。
7、使用Process Monitor查看函数调用堆栈时需要使用到pdb文件
Process Monitor主要用来监测目标进程的注册表活动和文件活动的,如果监测到要监测的条目,双击即可查看执行注册表或文件操作的函数调用堆栈,通过函数调用堆栈就可以知道是哪个函数操作了注册表或文件了。
涉及到显示线程的函数调用堆栈,就需要pdb文件,否则函数调用堆栈中就看不到具体的函数名,看不到具体的函数就较难定位问题的。与Process Explorer工具类似,pdb文件可以直接放置在二进制模块所在的文件夹中,Process Monitor会自动去搜索并加载。
Process Monitor中也支持设置pdb文件路径,点击Process Monitor菜单栏中的Options -> Configure Symbols...,打开配置pdb文件路径的窗口,如下所示:
默认已经将微软系统库pdb下载地址设置进去了,如果要新增路径,以分号隔开就好了。
8、API Monitor可以查看函数调用堆栈,但不支持加载pdb文件
API Monitor主要用来监测目标进程对系统API函数或者第三方库接口的调用情况。在监测到调用记录时,在call stack窗口中会显示所在线程的调用堆栈,默认只显示5条:
可以点击API Monitor菜单栏中的Tools -> Options...,在打开的Options配置窗口中点击Monitoring按钮,进入对应的标签页,如下所示:
在页面中可以配置显示函数调用堆栈的条目个数,好像最多只显示15个。
有函数调用堆栈的地方,为了查看更详细的函数调用堆栈,需要pdb文件,但不幸的是,API Monitor不支持加载pdb文件,所以也就没有设置pdb文件路径的入口。其实这类监测接口调用的场景,函数调用堆栈的作用不大,我们只关注是否调用了接口,调用接口时传递了哪些参数。
9、最后
本文详细介绍了Windbg、IDA、Process Explorer和Process Monitor等工具使用pdb的场景,希望能给大家提供一些借鉴和参考。