Compartilhar via


Depurando Performance-Optimized código

A Microsoft tem determinadas técnicas que usa 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 se baseiam em cenários de treinamento.

A otimização resultante reduz a paginação (e falhas de página) e aumenta a localidade espacial entre código e dados. Ele aborda um gargalo de desempenho importante que seria introduzido pelo posicionamento ruim do código original. Um componente que passou por essa otimização pode ter seu 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 em que residiriam após a compilação e vinculação normais. Além disso, as funções podem ter sido divididas em muitos blocos não contíguos, para que os caminhos de código mais comumente usados possam estar 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 código não otimizado.

Depurando Performance-Optimized código

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

Nessa 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 é capaz de 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, pois 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 geral ao trabalhar com códigos com otimização de desempenho é simplesmente que você não pode executar aritmética de endereço confiável no código otimizado.

Veja 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 está 0xF8640CA6.

Quando você desmonta uma seção de código dentro dessa função em 0xF864B4CB, a saída indica que isso é 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 parecerá 0xA825.

O que está acontecendo é o seguinte: o depurador está de fato 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 ela existia no código original antes da execução das otimizações. Esse valor é 0xE48.

Por outro lado, se você tentar examinar 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 que está acontecendo aqui é que o depurador reconhece o símbolo IPTransmit como equivalente ao endereço 0xF8640CA6 e o analisador de comando executa uma adição simples para descobrir que 0xF8640CA6 + 0xE48 = 0xF8641AEE. Esse endereço é usado como o argumento para o comando u (Unassemble). Mas depois que esse local é analisado, o depurador descobre que esse não é IPTransmit mais um deslocamento de 0xE48. Na verdade, não faz parte dessa função. Em vez disso, corresponde à função ARPTransmit mais um deslocamento de 0xD8.

O motivo pelo qual isso acontece é que a otimização de desempenho não é reversível por meio da aritmética de endereço. Embora o depurador possa pegar um endereço e deduzir seu símbolo e deslocamento originais, ele não tem informações suficientes para pegar um símbolo e deslocar e traduzi-lo para o endereço correto. Consequentemente, desmontagem não é útil nesses casos.