Débogage du code optimisé pour les performances
Microsoft utilise certaines techniques pour réorganiser le code compilé et lié afin qu’il s’exécute plus efficacement. Ces techniques optimisent le composant pour les hiérarchies de mémoire et sont basées sur des scénarios de formation.
L’optimisation qui en résulte réduit la pagination (et les fautes de page) et augmente la localité spatiale entre le code et les données. Elle traite un goulet d’étranglement de performance clé qui serait introduit par une mauvaise position du code d’origine. Un composant ayant subi cette optimisation peut avoir son code ou son bloc de données à l’intérieur d’une fonction déplacé vers différents emplacements du binaire.
Dans les modules qui ont été optimisés par ces techniques, les emplacements des blocs de code et de données se trouvent souvent à des adresses mémoire différentes de celles où ils se trouveraient après une compilation et un lien normaux. De plus, les fonctions peuvent avoir été divisées en plusieurs blocs non contigus, de sorte que les chemins de code les plus couramment utilisés peuvent être situés les uns près des autres sur les mêmes pages.
Par conséquent, une fonction (ou tout symbole) plus un décalage n’auront pas nécessairement la même signification qu’ils auraient dans un code non optimisé.
Débogage du code optimisé pour les performances
Lors du débogage, vous pouvez voir si un module a été optimisé pour les performances en utilisant la commande d’extension !lmi sur tout module pour lequel des symboles ont été chargés :
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
Dans cette sortie, remarquez le terme perf sur la ligne « Characteristics ». Cela indique que cette optimisation des performances a été appliquée à ntdll.dll.
Le débogueur est capable de comprendre une fonction ou un autre symbole sans décalage ; cela vous permet de définir des points d’arrêt sur des fonctions ou d’autres étiquettes sans problème. Cependant, la sortie d’une opération de désassemblage peut être déroutante, car ce désassemblage reflétera les modifications apportées par l’optimiseur.
Puisque le débogueur essaiera de rester proche du code d’origine, vous pourriez voir des résultats amusants. La règle d’or lorsqu’on travaille avec des codes optimisés pour les performances est simplement que vous ne pouvez pas effectuer une arithmétique d’adresses fiable sur le code optimisé.
Voici un exemple :
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
Vous pouvez voir dans la liste des points d’arrêt que l’adresse de IPTransmit est 0xF8640CA6.
Lorsque vous désassemblez une section de code dans cette fonction à 0xF864B4CB, la sortie indique qu’il s’agit de 0xE48 octets après le début de la fonction. Cependant, si vous soustrayez la base de la fonction de cette adresse, le décalage réel semble être 0xA825.
Voici ce qui se passe : Le débogueur montre effectivement un désassemblage des instructions binaires commençant à 0xF864B4CB. Mais au lieu de calculer le décalage par simple soustraction, le débogueur affiche - du mieux qu’il peut - le décalage par rapport à l’entrée de la fonction telle qu’elle existait dans le code d’origine avant que les optimisations ne soient effectuées. Cette valeur est 0xE48.
En revanche, si vous essayez de regarder IPTransmit+0xE48, vous verrez ceci :
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
Ce qui se passe ici, c’est que le débogueur reconnaît le symbole IPTransmit comme équivalent à l’adresse 0xF8640CA6, et l’analyseur de commandes effectue une addition simple pour trouver que 0xF8640CA6 + 0xE48 = 0xF8641AEE. Cette adresse est ensuite utilisée comme argument pour la commande u (Unassemble). Mais une fois cet emplacement analysé, le débogueur découvre que ce n’est pas IPTransmit plus un décalage de 0xE48. En effet, cela ne fait pas partie de cette fonction du tout. En fait, cela correspond à la fonction ARPTransmit plus un décalage de 0xD8.
La raison pour laquelle cela se produit est que l’optimisation des performances n’est pas réversible par l’arithmétique d’adresse. Bien que le débogueur puisse prendre une adresse et déduire son symbole et son décalage d’origine, il n’a pas suffisamment d’informations pour prendre un symbole et un décalage et les traduire en l’adresse correcte. Par conséquent, le désassemblage n’est pas utile dans ces cas.