调试性能优化的代码
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
在此输出中,请注意“特征”行上的术语 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(取消汇编)命令的参数。 但在对该位置进行分析后,调试器就会发现这并不等于 IPTransmit 加上 0xE48 的偏移量。 事实上,它根本不是此函数的一部分。 相反,它对应于函数 ARPTransmit 加上 0xD8 的偏移量。
之所以发生这种情况,是因为性能优化无法通过地址运算来逆转。 虽然调试器可以获取地址并推导出其原始符号和偏移量,但它没有足够的信息来获取符号和偏移量并将其转换为正确的地址。 因此,在这种情况下,反汇编并不会起作用。