Compartilhar via


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.