Debugging Tools for Windows

性能优化后的代码

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。

这样的原因在于优化对于地址计算来说是不可逆的。调试器可以拿到一个地址并推断出它原始的符号和偏移,但是由于没有足够信息,不能通过符号和偏移来计算出正确的地址。因此,这些情况下反汇编不是那么有用。

Build machine: CAPEBUILD