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.