Compartir a través de


Depuración de código optimizado para rendimiento

Microsoft tiene ciertas técnicas que usa para volver a organizar el código compilado y vinculado para que se ejecute con mayor eficacia. Estas técnicas optimizan el componente para las jerarquías de memoria y se basan en escenarios de entrenamiento.

La optimización resultante reduce la paginación (y los errores de página) y aumenta la localidad espacial entre el código y los datos. Aborda un cuello de botella clave de rendimiento que sería introducido por un posicionamiento deficiente del código original. Un componente que ha pasado por esta optimización puede tener su código o bloque de datos dentro de una función movida a diferentes ubicaciones del archivo binario.

En los módulos optimizados por estas técnicas, las ubicaciones de los bloques de código y datos a menudo se encuentran en direcciones de memoria diferentes de las ubicaciones donde residirían después de la compilación y la vinculación normales. Además, es posible que las funciones se hayan dividido en muchos bloques no contiguos, con el fin de que las rutas de acceso de código más usadas se puedan ubicar entre sí en las mismas páginas.

Por lo tanto, una función (o cualquier símbolo) más un desplazamiento no tendrá necesariamente el mismo significado que tendría en código no optimizado.

Depuración de código optimizado para rendimiento

Al depurar, puede ver si un módulo se ha optimizado para el rendimiento mediante el comando de extensión !lmi en cualquier módulo para el que se han cargado símbolos:

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

En esta salida, observe el término perf en la línea "Características". Esto indica que esta optimización de rendimiento se ha aplicado a ntdll.dll.

El depurador puede comprender una función u otro símbolo sin desplazamiento; esto le permite establecer puntos de interrupción en funciones u otras etiquetas sin ningún problema. Sin embargo, la salida de una operación de desensamblaje puede resultar confusa, ya que este desensamblaje reflejará los cambios realizados por el optimizador.

Dado que el depurador intentará mantenerse cerca del código original, es posible que vea algunos resultados divertidos. La regla general al trabajar con códigos optimizados para rendimiento es simplemente que no se puede realizar una aritmética de direcciones fiable en código optimizado.

Este es un ejemplo:

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

Puede ver en la lista de puntos de interrupción que la dirección de IPTransmit es 0xF8640CA6.

Al desensamblar una sección de código dentro de esta función en 0xF864B4CB, la salida indica que está 0xE48 bytes más allá del principio de la función. Sin embargo, si resta la base de la función de esta dirección, el desplazamiento real parece ser 0xA825.

Lo que sucede es esto: el depurador muestra realmente un desensamblaje de las instrucciones binarias que comienzan en 0xF864B4CB. Pero en lugar de calcular el desplazamiento por resta simple, el depurador muestra, lo mejor que puede, el desplazamiento a la entrada de función tal y como existía en el código original antes de realizar las optimizaciones. Ese valor es 0xE48.

Por otro lado, si intenta ver IPTransmit+0xE48, verá lo siguiente:

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

Lo que sucede aquí es que el depurador reconoce el símbolo IPTransmit como equivalente a la dirección 0xF8640CA6, y el analizador de comandos realiza una adición sencilla para encontrar que 0xF8640CA6 + 0xE48 = 0xF8641AEE. A continuación, esta dirección se usa como argumento para el comando u (Desensamblar). Pero una vez que se analiza esta ubicación, el depurador detecta que no es IPTransmit más un desplazamiento de 0xE48. De hecho, no forma parte de esta función. En su lugar, corresponde a la función ARPTransmit más un desplazamiento de 0xD8.

La razón por la que esto sucede es que la optimización del rendimiento no es reversible a través de la aritmética de direcciones. Aunque el depurador puede tomar una dirección e deducir su símbolo y desplazamiento original, no tiene suficiente información para tomar un símbolo y desplazamiento y traducirlo a la dirección correcta. Por lo tanto, el desensamblaje no es útil en estos casos.