Partager via


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.