Windows调试的基石——符号(1)
当应用程序被链接以后,代码被逐一地翻译为一个个的地址,优化以后的代码可能初看起来更是面目全非。每当我们使用vs或者windbg等微软的调试工具进行调试的时候,我们可以方便地使用变量名来查看内存、可以使用函数名称来下断点、甚至可以指定某个文件的某一行来下断点。这一切背后是什么在指导调试器工作呢?答案就是符号——pdb或者dbg文件(.NET自己有元数据,符号不需要元数据已有的信息)。
程序运行的时候,计算机只需要逐条执行指令即可。而与源代码对应的关系是完全不需要知道的。这就给调试带来了困难,所以无论什么编译都有自己的一套用于对应代码和可执行程序。各种编译器都有自己保存类似这种对应关系的办法,有的直接嵌入可执行文件,有的则是独立出来的。而微软的编译器则是独立产生了这种文件,它就被成为符号文件。
符号文件的历史有兴趣可以网上查查,dbg文件十分古老,微软在新的产品中也不再使用了。所以今天我们新产生的符号文件一般都是pdb文件。而pdb可以理解成提供给调试器用于对应可执行文件和源代码的东西,这个东西运行的时候是没有任何作用的,但是对于调试器和我们调试则有很大的帮助。
那么pdb文件里面到底存储了什么东西呢?根据微软官方的解释有:
1、 全局变量;
2、 局部变量;
3、 函数名及入口点;
4、 FPO记录;
5、 源代码行号。
如果使用vs2010随便写一个本机C语言,那么链接的时候编译器就会帮我们产生一个pdb文件。里面包含大量的符号,包括上面提及的内容。
每次调试程序或者查dmp的时候,我们都必须使用正确的符号。否则我们看到的栈等信息可能不准确。同时我们也无法建立应用程序和源代码之间的关系,没有符号你所面对只有地址。
符号同时又分为两种:public symbols and private symbols。至于他们的区别以后再具体介绍。
调试器是如何来判别EXE、DLL等是否和一个pdb文件匹配呢?每次我们链接EXE或者DLL或者SYS的时候,链接器都将产生一个唯一的GUID,然后将其写入到PDB和可执行文件。调试器加载的时候将检查两者的GUID,如果一致就表示他们匹配。
很多时候我们对PDB都不够重视。如果足够自信发布的东西一定不会产生bug,而且确实也没有产生bug,或者用户为0。那么PDB对我们确实没有多大作用,但是如果我们需要调试,我们需要查dmp文件,那么请妥善保管好自己的代码和pdb。每次重新编译,即使所有代码均没有变化,他们的GUID也不同(PDB还有age的概念,以后再解释)。
想想每个版本从测试到发布得编译多少次,每次都得辛苦去找PDB那么不是很痛苦啊。所幸我们有符号服务器这种东西。微软有自己HTTP符号服务器,我们自己也可以在20s内迅速搭建(以后会介绍如何搭自己的建符号服务器)。而且较新的vs或者windbg都能智能得对符号服务器进行搜索,避免了自己找符号的麻烦。
为了提供一个基本的感性认识,我们看看符号和DLL之间的关系:
0:012> !lmi ntdll
Loaded Module Info: [ntdll]
Module: ntdll
Base Address: 77040000
Image Name: ntdll.dll
Machine Type: 332 (I386)
Time Stamp: 4a5bdadb Tue Jul 14 09:09:47 2009
Size: 13c000
CheckSum: 14033f
Characteristics: 2102 perf
Debug Data Dirs: Type Size VA Pointer
CODEVIEW 22, d5308, d4708 RSDS - GUID: {F0164DA7-1FAF-4765-B8F3-DB4F2D7650EA}
Age: 2, Pdb: ntdll.pdb
CLSID 4, d5304, d4704 [Data not mapped]
Image Type: FILE - Image read successfully from debugger.
C:\Windows\SYSTEM32\ntdll.dll
Symbol Type: PDB - Symbols loaded successfully from symbol server.
c:\symcache\ntdll.pdb\F0164DA71FAF4765B8F3DB4F2D7650EA2\ntdll.pdb
Load Report: public symbols , not source indexed
c:\symcache\ntdll.pdb\F0164DA71FAF4765B8F3DB4F2D7650EA2\ntdll.pdb
从上面红色的地方我们可以看到ntdll里面的GUID、age等信息。同时我们从微软的符号服务器下载了对应的符号,然后保存到了本地的c:\symcache里。
当我们使用vs进行调试的时候,编译器总是能帮我们找到我们编译的应用程序或者DLL的符号,所以往往我们不会遇到和符号相关的太多麻烦。但是如果我们使用的是其他调试工具,或者查dmp的时候,符号的问题就来了。如果我们给调试器指定了正确符号文件,那么一切都很正常,否则我们将看到令人困惑的东西。
本文简单介绍了一下符号的概念,以后陆续会对符号做一个比较系统的介绍。