cc682/NetRoc
http://netroc682.spaces.live.com/
符号语法和符号匹配
符号用来直接操作被调试程序的助记符(tokens)。例如,可以通过命令bp main 来在main函数上设置断点,或者使用dd MyInt L1命令来显示整数变量MyInt 的值。
很多情况下,符号可以作为调试器命令的参数。可以支持大多数数字参数,以及一些文本参数。除了常规的符号语法之外,也有一些适用于各种单独情况的语法规则。
常规符号语法规则
符号名由一个或多个字符组成,但是总是以字母、下划线(_)、问号(?)或美元符号($)开头。
符号名可以用模块名来进行限制。使用一个感叹号(!)来分隔模块名和符号(例如,mymodule!main)。没有使用模块名的时候,符号也可以用感叹号作为前缀。
符号名是完全不区分大小写的。这意味着如果程序中同时存在myInt 和MyInt ,不能被调试器正确识别;不管怎么写,访问它们的命令都有可能访问到另外一个。
数值表达式中的符号语法
调试器可以识别两种不同的表达式:Microsoft宏汇编(MASM)表达式和C++表达式。由于和符号相关,这两种语法形式有以下区别:
- 在MASM表达式中,每个符号都被解释成地址。根据该符号引用的内容不同,它可以是全局变量、局部变量、函数、段、模块的地址或者其他任何可识别的标志。
- 在C++ 表达式中,符号根据它的类型来解释。根据引用内容的不同,可能被解释为整数、数据结构、函数指针或任何其他数据类型。不符合C++数据类型的符号(例如未更改的模块名)都会产生语法错误。
关于如何使用每一种语法的说明,查看表达式求值。
如果使用MSAM表达式语法,任何可以被解释为16进制数或寄存器的符号(例如,BadFeed, ebX)都必须用感叹号作为前缀。这能确保调试器将它识别为符号。
ss (Set Symbol Suffix)命令可以用来设置符号后缀。这使得调试器在找不到符号的情况下自动在符号名后添加"A" 或者"W"。
很多Win32函数存在ASCII和Unicode版本。这些函数的名字后被分别添加了"A" 或"W"。使用符号后缀可以帮助调试器搜索这些符号。
后缀匹配默认没有激活。
文本表达式中的符号语法
符号可以用于一些命令的文本参数——例如bm (Set Breakpoint) 和x (Examine Symbols)。
这些文本参数支持很多通配符和说明。查看字符串通配符语法获取详细信息。除标准字符串通配符之外,用来指定符号的文本表达式也可以使用一个下划线作为前缀。当对这样的符号进行匹配时,调试器把它当作任意数量的下划线,包括零。
匹配文本表达式中的符号时,不会使用符号后缀。
符号状态缩写
符号文件的类型和它们的加载状态可以通过lm (List Loaded Modules)命令、 !lmi 扩展命令或 WinDbg的Debug | Modules菜单命令获取。
它们都可以显示已加载的模块和它们的符号的信息。
下面这些缩写是这些命令产生的输出中使用的:
缩写
|
含义
|
deferred
|
模块已经加载,但是调试器还没有尝试加载它的符号。符号将在需要的时候被加载。查看延迟符号加载获取详细信息。
|
#
|
符号文件和可执行文件的时间戳或者校验和有一些不匹配。
|
T
|
时间戳丢失、不能访问或者等于0。
|
C
|
校验和丢失、不可访问或者等于0。
|
DIA
|
符号文件通过调试接口访问(Debug Interface Access (DIA))被加载。
|
Export
|
没有找到实际的符号文件,所以符号信息是从二进制文件的导出表中获得的。
|
M
|
符号文件和可执行文件得时间戳或校验和有些不匹配。但是,因为符号选项的原因符号文件仍然被加载了。
|
PERF
|
该二进制文件包含性能优化后的代码。标准的地址计算方法可能产生不正确的结果。
|
Stripped
|
调试信息已经从映像文件中剥离出来了。
|
PDB
|
符号是.pdb格式的。
|
COFF
|
符号是通用对象文件格式(common object file format (COFF))的。
|
性能优化后的代码
Microsoft有一些技术用于重新调整编译和链接之后的代码,以使得它们执行得更高效。这些技术会根据情况优化内存结构。
这样的优化减少了分页(和页面错误),并且增加代码和数据之间的空间位置。它会定位原始代码位置不好造成的性能瓶颈。经过优化的组件中可能有一些代码或数据块被移动到另外的地方。
在经过这些技术优化后的模块中,代码和数据块的位置经常不在按照通常的编译和链接之后所应该在的地方。另外,函数可能会被分割为很多不相邻的块,以使得最常使用的代码路径能放到相同的页面中。
因此,函数(或任何符号)加上偏移的意义不像在非优化代码中一样。
调试优化后的代码
调试中可以对任何已经加载了符号的模块使用!lmi扩展命令来查看是否经过了性能优化:
0:000> !lmi ntdll
Loaded Module Info: [ntdll]
Module: ntdll
Base Address: 77f80000
Image Name: ntdll.dll
Machine Type: 332 (I386)
Time Stamp: 394193d2 Fri Jun 09 18:03:14 2000
CheckSum: 861b1
Characteristics: 230e stripped perf
Debug Data Dirs: Type Size VA Pointer
MISC 110, 0, 76c00 [Data not mapped]
Image Type: DBG - Image read successfully from symbol server.
c:\symbols\dll\ntdll.dbg
Symbol Type: DIA PDB - Symbols loaded successfully from symbol server.
c:\symbols\dll\ntdll.pdb
在这个输出重,注意"Characteristics" 这一行上的perf 术语。何表明ntdll.dll是经过了性能优化的。
调试器可以识别没有偏移的寒暑或其他符号,这使得可以成功在函数或其他标签上设置断点。但是,反汇编操作的结果可能令人困惑,因为它会反映出优化器进行过的改变。
由于调试器会尝试靠近原始代码,所以可能会看到一些有趣的结果。调试性能优化后的代码的首要规则是,不能对优化后的代码进行可靠的地址计算。
这里是一个示例:
kd> bl
0 e f8640ca6 0001 (0001) tcpip!IPTransmit
1 e f8672660 0001 (0001) tcpip!IPFragment
kd> u f864b4cb
tcpip!IPTransmit+e48:
f864b4cb f3a4 rep movsb
f864b4cd 8b75cc mov esi,[ebp-0x34]
f864b4d0 8b4d10 mov ecx,[ebp+0x10]
f864b4d3 8b7da4 mov edi,[ebp-0x5c]
f864b4d6 8bc6 mov eax,esi
f864b4d8 6a10 push 0x10
f864b4da 034114 add eax,[ecx+0x14]
f864b4dd 57 push edi
可以从断点列表中看到IPTransmit 的地址是0xF8640CA6。
当反汇编该函数中0xF864B4CB 开始的一段代码时,输出显示这是从函数开始之后的第0xE48的字节。但是,如果用该地址减去函数的基地址,会发现实际的偏移是0xA825。
发生的事情是这样的:调试器确实从 0xF864B4CB 开始的指令反汇编二进制机器码。但是不是使用简单的减法计算偏移,调试器尽可能好的显示出了优化之前该代码相对于函数入口点的偏移。这个值为0xE48。
另一方面,如果想在IPTransmit+0xE48查看,会看到这些内容:
kd> u tcpip!iptransmit+e48
tcpip!ARPTransmit+d8:
f8641aee 0856ff or [esi-0x1],dl
f8641af1 75fc jnz tcpip!ARPTransmit+0xd9 (f8641aef)
f8641af3 57 push edi
f8641af4 e828eeffff call tcpip!ARPSendData (f8640921)
f8641af9 5f pop edi
f8641afa 5e pop esi
f8641afb 5b pop ebx
f8641afc c9 leave
调试器识别出来IPTransmit 等于地址0xF8640CA6,并且命令解析器简单的进行加法,发现0xF8640CA6 + 0xE48 = 0xF8641AEE 。这个地址作为了u (Unassemble)命令的参数。但是当分析了该位置后,调试器发现它不是IPTransmit 加上偏移0xE48。实际上,这根本不是该函数的一部分。所以它修改为函数ARPTransmit 加上偏移0xD8。
这样的原因在于优化对于地址计算来说是不可逆的。调试器可以拿到一个地址并推断出它原始的符号和偏移,但是由于没有足够信息,不能通过符号和偏移来计算出正确的地址。因此,这些情况下反汇编不是那么有用。