Debug di funzioni di codice e inline ottimizzate

Per Windows 8, il debugger e il compilatore Windows sono stati migliorati in modo da poter eseguire il debug di funzioni inline ottimizzate per il codice e il debug. Il debugger visualizza parametri e variabili locali indipendentemente dal fatto che siano archiviati nei registri o nello stack. Il debugger visualizza anche funzioni inline nello stack di chiamate. Per le funzioni inline, il debugger visualizza le variabili locali, ma non i parametri.

Quando il codice viene ottimizzato, viene trasformato per eseguire più velocemente e usare meno memoria. A volte le funzioni vengono rimosse in seguito alla rimozione del codice non risolto, al codice unito o alle funzioni inserite inline. È anche possibile rimuovere variabili e parametri locali. Molte ottimizzazioni del codice eliminano le variabili locali non necessarie o usate; altre ottimizzazioni rimuove le variabili di induzione nei cicli. L'eliminazione di espressioni secondarie comuni unisce le variabili locali.

Le build di vendita al dettaglio di Windows sono ottimizzate. Quindi, se si esegue una compilazione al dettaglio di Windows, è particolarmente utile avere un debugger progettato per funzionare bene con il codice ottimizzato. Per rendere efficace il debug del codice ottimizzato, sono necessarie due funzionalità principali: 1) visualizzazione accurata delle variabili locali e 2) visualizzazione di funzioni inline nello stack di chiamate.

Visualizzazione accurata delle variabili e dei parametri locali

Per facilitare la visualizzazione accurata delle variabili e dei parametri locali, il compilatore registra informazioni sulle posizioni delle variabili locali e dei parametri nei file PDB (Symbol). Questi record di posizione tengono traccia delle posizioni di archiviazione delle variabili e degli intervalli di codice specifici in cui queste posizioni sono valide. Questi record non consentono solo di tenere traccia delle posizioni (nei registri o negli slot dello stack) delle variabili, ma anche dello spostamento delle variabili. Ad esempio, un parametro potrebbe essere in registrazione RCX, ma viene spostato in uno slot dello stack per liberare RCX, quindi spostato per registrare R8 quando viene usato in modo pesante in un ciclo e quindi spostato in uno slot stack diverso quando il codice è fuori dal ciclo. Il debugger di Windows usa i record di posizione avanzati nei file PDB e usa il puntatore delle istruzioni corrente per selezionare i record di posizione appropriati per le variabili e i parametri locali.

Questa schermata della finestra Variabili locali in Visual Studio mostra i parametri e le variabili locali per una funzione in un'applicazione a 64 bit ottimizzata. La funzione non è inline, quindi vengono visualizzati sia i parametri che le variabili locali.

Screenshot della finestra Variabili locali in Visual Studio che visualizza parametri e variabili locali per una funzione in un'applicazione a 64 bit ottimizzata.

È possibile usare il comando dv -v per visualizzare i percorsi dei parametri e delle variabili locali.

Screenshot dell'output del comando che visualizza i percorsi dei parametri e delle variabili locali usando il comando dv -v.

Si noti che la finestra Variabili locali visualizza correttamente i parametri anche se vengono archiviati nei registri.

Oltre a tenere traccia delle variabili con tipi primitivi, la posizione registra i membri dei dati delle strutture e delle classi locali. L'output del debugger seguente visualizza le strutture locali.

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

Ecco alcune osservazioni sull'output del debugger precedente.

  • La struttura locale My1 illustra che il compilatore può distribuire i membri dei dati della struttura locale per registrare e slot stack non contigui.
  • L'output del comando dt My2 sarà diverso dall'output del comando dt _IntSum 0xefa60. Non è possibile presupporre che la struttura locale occupa un blocco contiguo di memoria dello stack. Nel caso di My2, rimane solo sum4 nel blocco dello stack originale. Gli altri tre membri dati vengono spostati nei registri.
  • Alcuni membri dei dati possono avere più posizioni. Ad esempio, My2.sum2 ha due percorsi: uno è registrare ECX (che il debugger di Windows sceglie) e l'altro è 0xefa60+0x4 (lo slot dello stack originale). Ciò potrebbe verificarsi anche per le variabili locali di tipo primitivo e il debugger di Windows impone l'euristica precedente per determinare quale posizione usare. Ad esempio, registrare le posizioni sempre trump stack location.

Visualizzazione di funzioni inline nello stack di chiamate

Durante l'ottimizzazione del codice, alcune funzioni vengono posizionate in riga. Ovvero, il corpo della funzione viene inserito direttamente nel codice come un'espansione della macro. Non esiste alcuna chiamata alla funzione e non viene restituito al chiamante. Per facilitare la visualizzazione delle funzioni inline, il compilatore archivia i dati nei file PDB che consentono di decodificare i blocchi di codice per le funzioni inline, ovvero sequenze di blocchi di codice nelle funzioni chiamanti che appartengono alle funzioni di chiamata che vengono posizionate inline, nonché le variabili locali (variabili locali con ambito in tali blocchi di codice). Questi dati consentono al debugger di includere funzioni inline come parte dello stack di rimozione.

Si supponga di compilare un'applicazione e forzare una funzione denominata func1 inline.

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

È possibile usare il comando bm per impostare un punto di interruzione in 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)]

Dopo aver eseguito un passaggio in func1, è possibile usare il comando k per visualizzare func1 nello stack di chiamate. È possibile usare il comando dv per visualizzare le variabili locali per func1. Si noti che la variabile num3 locale viene visualizzata come non disponibile. Una variabile locale può non essere disponibile nel codice ottimizzato per diversi motivi. Potrebbe essere che la variabile non esiste nel codice ottimizzato. Potrebbe essere che la variabile non sia ancora stata inizializzata o che la variabile non venga più usata.

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>

Se si esamina il frame 1 nella traccia dello stack, è possibile visualizzare le variabili locali per la main funzione. Si noti che due delle variabili vengono archiviate nei registri.

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

Il debugger Windows aggrega i dati dei file PDB per trovare tutte le posizioni in cui è stata posizionata una funzione specifica. È possibile usare il comando x per elencare tutti i siti chiamante di una funzione 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

Poiché il debugger di Windows può enumerare tutti i siti chiamanti di una funzione inline, può impostare un punto di interruzione all'interno della funzione inline calcolando gli offset dai siti del chiamante. È possibile usare il comando bm (usato per impostare i punti di interruzione che corrispondono ai modelli di espressione regolari) per impostare i punti di interruzione per le funzioni inline.

Il debugger di Windows raggruppa tutti i punti di interruzione impostati per una funzione inline specifica in un contenitore di punti di interruzione. È possibile modificare il contenitore del punto di interruzione nel suo complesso usando comandi come be, bd, bc. Vedere gli esempi di comando bd 3 e bc3 seguenti. È anche possibile modificare singoli punti di interruzione. Vedere l'esempio seguente di comando 2 .

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

Poiché non sono presenti istruzioni esplicite per le funzioni inline, l'esecuzione a livello di origine è particolarmente complessa per un debugger. Ad esempio, è possibile eseguire un passaggio involontariamente in una funzione inline (se l'istruzione successiva fa parte di una funzione inline) oppure è possibile eseguire il passaggio e uscire dalla stessa funzione inline più volte (perché i blocchi di codice per la funzione inline sono stati suddivisi e spostati dal compilatore). Per mantenere l'esperienza di passaggio familiare, il debugger di Windows gestisce un piccolo stack di chiamate concettuali per ogni indirizzo di istruzione del codice e compila un computer di stato interno per eseguire operazioni dettagliate, dettagliate e dettagliate. Ciò offre un'approssimazione ragionevolmente accurata per l'esperienza di esecuzione per le funzioni non inline.

Informazioni aggiuntive

Nota È possibile usare il comando .inline 0 per disabilitare il debug delle funzioni inline. Il comando .inline 1 abilita il debug delle funzioni inline. Tecniche di debug standard

Vedere anche

Tecniche di debug standard