Partager via


Débogage de fonctions intégrées et de code optimisés

Par Windows 8, le débogueur et le compilateur Windows ont été améliorés afin que vous puissiez déboguer du code optimisé et déboguer des fonctions inline. Le débogueur affiche les paramètres et les variables locales, qu’ils soient stockés dans des registres ou sur la pile. Le débogueur affiche également les fonctions inline dans la pile des appels. Pour les fonctions inline, le débogueur affiche des variables locales, mais pas des paramètres.

Lorsque le code est optimisé, il est transformé pour s’exécuter plus rapidement et utiliser moins de mémoire. Parfois, les fonctions sont supprimées à la suite de la suppression de code mort, de la fusion du code ou de la mise en ligne des fonctions. Les variables et paramètres locaux peuvent également être supprimés. De nombreuses optimisations de code suppriment les variables locales qui ne sont pas nécessaires ou utilisées ; d’autres optimisations suppriment les variables d’induction dans les boucles. L’élimination courante des sous-expressions fusionne les variables locales.

Les builds de vente au détail de Windows sont optimisées. Par conséquent, si vous exécutez une version commerciale de Windows, il est particulièrement utile d’avoir un débogueur conçu pour fonctionner correctement avec du code optimisé. Pour que le débogage du code optimisé soit efficace, deux fonctionnalités principales sont requises : 1) l’affichage précis des variables locales et 2) l’affichage des fonctions inline sur la pile d’appels.

Affichage précis des variables et paramètres locaux

Pour faciliter l’affichage précis des variables et paramètres locaux, le compilateur enregistre des informations sur les emplacements des variables et paramètres locaux dans les fichiers de symboles (PDB). Ces enregistrements d’emplacement suivent les emplacements de stockage des variables et les plages de code spécifiques où ces emplacements sont valides. Ces enregistrements permettent non seulement de suivre les emplacements (dans les registres ou dans les emplacements de pile) des variables, mais également le déplacement des variables. Par exemple, un paramètre peut d’abord se trouver dans le registre RCX, mais il est déplacé vers un emplacement de pile pour libérer RCX, puis déplacé vers l’inscription R8 lorsqu’il est fortement utilisé dans une boucle, puis déplacé vers un autre emplacement de pile lorsque le code est hors de la boucle. Le débogueur Windows utilise les enregistrements d’emplacement enrichis dans les fichiers PDB et utilise le pointeur d’instruction actuel pour sélectionner les enregistrements d’emplacement appropriés pour les variables et paramètres locaux.

Cette capture d’écran de la fenêtre Locals dans Visual Studio montre les paramètres et les variables locales d’une fonction dans une application 64 bits optimisée. La fonction n’étant pas inline, nous voyons à la fois les paramètres et les variables locales.

Capture d’écran de la fenêtre Locals dans Visual Studio affichant les paramètres et variables locales d’une fonction dans une application 64 bits optimisée.

Vous pouvez utiliser la commande dv -v pour afficher les emplacements des paramètres et des variables locales.

Capture d’écran de la sortie de commande affichant les emplacements des paramètres et des variables locales à l’aide de la commande dv -v.

Notez que la fenêtre Locals affiche correctement les paramètres même s’ils sont stockés dans des registres.

En plus du suivi des variables avec des types primitifs, les enregistrements d’emplacement suivent les membres de données des structures et classes locales. La sortie de débogueur suivante affiche les structures locales.

0:000> dt My1
Local var Type _LocalStruct
   +0x000 i1               : 0n0 (edi)
   +0x004 i2               : 0n1 (rsp+0x94)
   +0x008 i3               : 0n2 (rsp+0x90)
   +0x00c i4               : 0n3 (rsp+0x208)
   +0x010 i5               : 0n4 (r10d)
   +0x014 i6               : 0n7 (rsp+0x200)

0:000> dt My2
Local var @ 0xefa60 Type _IntSum
   +0x000 sum1             : 0n4760 (edx)
   +0x004 sum2             : 0n30772 (ecx)
   +0x008 sum3             : 0n2 (r12d)
   +0x00c sum4             : 0n0

Voici quelques observations sur la sortie du débogueur précédent.

  • La structure locale My1 montre que le compilateur peut répartir les membres de données de structure locale sur des registres et des emplacements de pile non contigus.
  • La sortie de la commande dt My2 sera différente de la sortie de la commande dt _IntSum 0xefa60. Vous ne pouvez pas supposer que la structure locale occupera un bloc contigu de mémoire de pile. Dans le cas de My2, reste uniquement sum4 dans le bloc de pile d’origine ; les trois autres membres de données sont déplacés vers des registres.
  • Certains membres de données peuvent avoir plusieurs emplacements. Par exemple, My2.sum2 a deux emplacements : l’un inscrit ECX (que le débogueur Windows choisit) et l’autre est 0xefa60+0x4 (l’emplacement de pile d’origine). Cela peut également se produire pour les variables locales de type primitif, et le débogueur Windows impose des heuristiques antérieures pour déterminer l’emplacement à utiliser. Par exemple, les emplacements d’inscription l’emportent toujours sur les emplacements de pile.

Affichage des fonctions inline sur la pile d’appels

Pendant l’optimisation du code, certaines fonctions sont placées en ligne. Autrement dit, le corps de la fonction est placé directement dans le code, comme une extension de macro. Il n’y a pas d’appel de fonction et aucun retour à l’appelant. Pour faciliter l’affichage des fonctions inline, le compilateur stocke des données dans les fichiers PDB qui permettent de décoder les blocs de code pour les fonctions inline (c’est-à-dire les séquences de blocs de code dans les fonctions appelantes qui appartiennent aux fonctions appelées placées en ligne) ainsi que les variables locales (variables locales délimitées dans ces blocs de code). Ces données aident le débogueur à inclure des fonctions inline dans le cadre du déroulement de la pile.

Supposons que vous compilez une application et que vous forcez une fonction nommée func1 à être inline.

__forceinline int func1(int p1, int p2, int p3)
{
   int num1 = 0;
   int num2 = 0;
   int num3 = 0;
   ...
}

Vous pouvez utiliser la commande bm pour définir un point d’arrêt sur func1.

0:000> bm MyApp!func1
  1: 000007f6`8d621088 @!"MyApp!func1" (MyApp!func1 inlined in MyApp!main+0x88)
0:000> g

Breakpoint 1 hit
MyApp!main+0x88:
000007f6`8d621088 488d0d21110000  lea     rcx,[MyApp!`string' (000007f6`8d6221b0)]

Après avoir franchi une étape dans func1, vous pouvez utiliser la commande k pour voir func1 sur la pile des appels. Vous pouvez utiliser la commande dv pour afficher les variables locales pour func1. Notez que la variable num3 locale est affichée comme indisponible. Une variable locale peut être indisponible dans le code optimisé pour plusieurs raisons. Il se peut que la variable n’existe pas dans le code optimisé. Il se peut que la variable n’ait pas encore été initialisée ou que la variable ne soit plus utilisée.

0:000> p
MyApp!func1+0x7:
000007f6`8d62108f 8d3c33          lea     edi,[rbx+rsi]

0:000> knL
# Child-SP          RetAddr           Call Site
00 (Inline Function) --------`-------- MyApp!func1+0x7
01 00000000`0050fc90 000007f6`8d6213f3 MyApp!main+0x8f
02 00000000`0050fcf0 000007ff`c6af0f7d MyApp!__tmainCRTStartup+0x10f
03 00000000`0050fd20 000007ff`c7063d6d KERNEL32!BaseThreadInitThunk+0xd
04 00000000`0050fd50 00000000`00000000 ntdll!RtlUserThreadStart+0x1d

0:000> dv -v
00000000`0050fcb0            num1 = 0n0
00000000`0050fcb4            num2 = 0n0
<unavailable>                num3 = <value unavailable>

Si vous examinez l’image 1 dans la trace de pile, vous pouvez voir les variables locales pour la main fonction. Notez que deux des variables sont stockées dans des registres.

0:000> .frame 1
01 00000000`0050fc90 000007f6`8d6213f3 MyApp!main+0x8f

0:000> dv -v
00000000`0050fd08               c = 0n7
@ebx                            b = 0n13
@esi                            a = 0n6

Le débogueur Windows agrège les données des fichiers PDB pour rechercher tous les emplacements où une fonction spécifique a été placée en ligne. Vous pouvez utiliser la commande x pour répertorier tous les sites appelants de la fonction inline.

0:000> x simple!MoreCalculate
00000000`ff6e1455 simple!MoreCalculate =  (inline caller) simple!wmain+8d
00000000`ff6e1528 simple!MoreCalculate =  (inline caller) simple!wmain+160

0:000> x simple!Calculate
00000000`ff6e141b simple!Calculate =  (inline caller) simple!wmain+53

Étant donné que le débogueur Windows peut énumérer tous les sites appelants d’une fonction inline, il peut définir des points d’arrêt à l’intérieur de la fonction inline en calculant les décalages des sites appelants. Vous pouvez utiliser la commande bm (qui est utilisée pour définir des points d’arrêt qui correspondent à des modèles d’expression régulière) pour définir des points d’arrêt pour les fonctions inline.

Le débogueur Windows regroupe tous les points d’arrêt définis pour une fonction inline spécifique dans un conteneur de points d’arrêt. Vous pouvez manipuler le conteneur de point d’arrêt dans son ensemble à l’aide de commandes telles que be, bd, bc. Consultez les exemples de commandes bd 3 et bc 3 suivants. Vous pouvez également manipuler des points d’arrêt individuels. Consultez l’exemple de commande be 2 suivant.

0:000> bm simple!MoreCalculate
  2: 00000000`ff6e1455 @!"simple!MoreCalculate" (simple!MoreCalculate inlined in simple!wmain+0x8d)
  4: 00000000`ff6e1528 @!"simple!MoreCalculate" (simple!MoreCalculate inlined in simple!wmain+0x160)

0:000> bl
 0 e 00000000`ff6e13c8 [n:\win7\simple\simple.cpp @ 52]    0001 (0001)  0:**** simple!wmain
 3 e <inline function>     0001 (0001)  0:**** {simple!MoreCalculate}
     2 e 00000000`ff6e1455 [n:\win7\simple\simple.cpp @ 58]    0001 (0001)  0:**** simple!wmain+0x8d (inline function simple!MoreCalculate)
     4 e 00000000`ff6e1528 [n:\win7\simple\simple.cpp @ 72]    0001 (0001)  0:**** simple!wmain+0x160 (inline function simple!MoreCalculate)

0:000> bd 3
0:000> be 2

0:000> bl
 0 e 00000000`ff6e13c8 [n:\win7\simple\simple.cpp @ 52]    0001 (0001)  0:**** simple!wmain
 3 d <inline function>     0001 (0001)  0:**** {simple!MoreCalculate}
     2 e 00000000`ff6e1455 [n:\win7\simple\simple.cpp @ 58]    0001 (0001)  0:**** simple!wmain+0x8d (inline function simple!MoreCalculate)
     4 d 00000000`ff6e1528 [n:\win7\simple\simple.cpp @ 72]    0001 (0001)  0:**** simple!wmain+0x160 (inline function simple!MoreCalculate)

0:000> bc 3

0:000> bl
 0 e 00000000`ff6e13c8 [n:\win7\simple\simple.cpp @ 52]    0001 (0001)  0:**** simple!wmain

Étant donné qu’il n’existe pas d’instructions d’appel ou de retour explicites pour les fonctions inline, le pas à pas au niveau de la source est particulièrement difficile pour un débogueur. Par exemple, vous pouvez involontairement accéder à une fonction inline (si l’instruction suivante fait partie d’une fonction inline), ou vous pouvez intervenir et sortir de la même fonction inline plusieurs fois (car les blocs de code de la fonction inline ont été fractionnés et déplacés par le compilateur). Pour préserver l’expérience de pas à pas familière, le débogueur Windows gère une petite pile d’appels conceptuels pour chaque adresse d’instruction de code et génère une machine d’état interne pour exécuter des opérations d’étape, d’étape et d’extraction. Cela donne une approximation relativement précise de l’expérience de pas à pas pour les fonctions non inline.

Informations supplémentaires

Note Vous pouvez utiliser la commande .inline 0 pour désactiver le débogage de fonction inline. La commande .inline 1 active le débogage de fonction inline. Techniques de débogage standard

Voir aussi

Techniques de débogage standard