Depurando o código otimizado para desempenho
A Microsoft tem algumas técnicas para reorganizar o código compilado e vinculado para que ele seja executado com mais eficiência. Essas técnicas otimizam o componente para hierarquias de memória e são baseadas em cenários de treinamento.
A otimização resultante reduz a paginação (e as falhas de página) e aumenta a localidade espacial entre o código e os dados. Ela aborda um gargalo de desempenho importante que seria introduzido pelo mau posicionamento do código original. Um componente que passou por essa otimização pode ter o código ou bloco de dados dentro de uma função movido para locais diferentes do binário.
Em módulos que foram otimizados por essas técnicas, os locais de código e blocos de dados geralmente serão encontrados em endereços de memória diferentes dos locais onde eles residiriam após a compilação e a vinculação normais. Além disso, as funções podem ter sido divididas em muitos blocos não contíguos, a fim de que os caminhos de código mais comumente usados possam ser localizados próximos uns dos outros nas mesmas páginas.
Portanto, uma função (ou qualquer símbolo) mais um deslocamento não terá necessariamente o mesmo significado que teria em um código não otimizado.
Depurando o código otimizado para desempenho
Ao depurar, você pode ver se um módulo foi otimizado para desempenho usando o comando de extensão !lmi em qualquer módulo para o qual os símbolos foram carregados:
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
Nesta saída, observe o termo perf na linha "Características". Isso indica que essa otimização de desempenho foi aplicada a ntdll.dll.
O depurador consegue entender uma função ou outro símbolo sem um deslocamento. Isso permite que você defina pontos de interrupção em funções ou outros rótulos sem nenhum problema. No entanto, a saída de uma operação de desmontagem pode ser confusa, porque essa desmontagem refletirá as alterações feitas pelo otimizador.
Como o depurador tentará ficar perto do código original, você poderá ver alguns resultados divertidos. A regra prática ao trabalhar com códigos otimizados para desempenho é simplesmente que você não pode executar aritmética de endereço confiável em código otimizado.
Este é um exemplo:
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
Você pode ver na lista de pontos de interrupção que o endereço de IPTransmit é 0xF8640CA6.
Quando você desmonta uma seção de código dentro dessa função em 0xF864B4CB, a saída indica que isso está 0xE48 bytes após o início da função. No entanto, se você subtrair a base da função desse endereço, o deslocamento real parece ser 0xA825.
Está acontecendo o seguinte: o depurador está realmente mostrando uma desmontagem das instruções binárias começando em 0xF864B4CB. Mas, em vez de calcular o deslocamento por subtração simples, o depurador exibe, da melhor forma possível, o deslocamento para a entrada de função como existia no código original antes das otimizações serem executadas. Esse valor é 0xE48.
Por outro lado, se você tentar olhar para IPTransmit+0xE48, verá o seguinte:
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
O depurador reconhece o símbolo IPTransmit como equivalente ao endereço 0xF8640CA6, e o analisador de comandos executa uma adição simples para encontrar que 0xF8640CA6 + 0xE48 = 0xF8641AEE. Esse endereço é usado como argumento para o comando u (Unassemble). Mas assim que esse local é analisado, o depurador descobre que isso não é IPTransmit mais um deslocamento de 0xE48. Na verdade, isso não faz parte dessa função. Em vez disso, corresponde à função ARPTransmit mais um deslocamento de 0xD8.
Isso acontece porque a otimização de desempenho não é reversível por meio da aritmética de endereços. Embora o depurador possa pegar um endereço e deduzir o símbolo original e o deslocamento, ele não tem informações suficientes para pegar um símbolo e deslocamento e traduzi-lo para o endereço correto. Consequentemente, a desmontagem não é útil nesses casos.