Condividi tramite


WinDbg - Sequenze temporali

Logo WinDbg con una lente di ingrandimento che controlla i bit.

Il debug TTD (Time Travel Debugging) consente agli utenti di registrare tracce, che sono registrazioni dell'esecuzione di un programma. Le sequenze temporali sono una rappresentazione visiva degli eventi che si verificano durante l'esecuzione. Questi eventi possono essere percorsi di: punti di interruzione, lettura/scrittura della memoria, chiamate di funzione e restituzioni e eccezioni.

Sequenza temporale nel debugger che visualizza eccezioni, accesso alla memoria, punti di interruzione e chiamate di funzione.

Usare la finestra delle sequenze temporali per visualizzare rapidamente eventi importanti, comprendere la posizione relativa e passare facilmente alla posizione nel file di traccia TTD. Usare più sequenze temporali per esplorare visivamente gli eventi nella traccia di viaggio temporale e individuare la correlazione degli eventi.

La finestra delle sequenze temporali viene visualizzata all'apertura di un file di traccia TTD e mostra gli eventi chiave senza dover creare manualmente query del modello di dati. Allo stesso tempo, tutti gli oggetti di spostamento temporale sono disponibili per consentire query di dati più complesse.

Per altre informazioni sulla creazione e sull'uso dei file di traccia di Viaggi temporali, vedere Time Travel Debugging - Overview (Debug di viaggi temporali - Panoramica).

Tipi di sequenze temporali

La finestra sequenze temporali può visualizzare gli eventi seguenti:

  • Eccezioni (è possibile filtrare ulteriormente in base a un codice di eccezione specifico)
  • Punti di interruzione
  • Chiamate di funzione (ricerca sotto forma di modulo!funzione)
  • Accessi alla memoria (lettura/scrittura/esecuzione tra due indirizzi di memoria)

Passare il puntatore del mouse su ogni evento per ottenere altre informazioni tramite la descrizione comando. Facendo clic su un evento verrà eseguita la query per l'evento e verranno visualizzate altre informazioni. Facendo doppio clic su un evento si passerà a tale percorso nel file di traccia TTD.

Eccezioni

Quando si carica un file di traccia e la sequenza temporale è attiva, verranno visualizzate automaticamente eventuali eccezioni nella registrazione.

Quando si passa il puntatore del mouse su un punto di interruzione, ad esempio il tipo di eccezione e viene visualizzato il codice eccezione.

Sequenza temporale nel debugger che visualizza eccezioni con informazioni su un codice di eccezione specifico.

È possibile filtrare ulteriormente in base a un codice di eccezione specifico usando il campo del codice di eccezione facoltativo.

Finestra di dialogo Eccezione del debugger sequenza temporale con tipo di sequenza temporale impostata su eccezione e codice eccezione impostato su 0xC0000004.

È anche possibile aggiungere una nuova sequenza temporale per un tipo di eccezione specifico.

Punti di interruzione

Dopo aver aggiunto un punto di interruzione, è possibile visualizzare le posizioni di quando il punto di interruzione viene raggiunto su una sequenza temporale. Questa operazione può essere eseguita, ad esempio, usando il comando bp Set Breakpoint .This be done for example using the bp Set Breakpoint command. Quando si passa il puntatore del mouse su un punto di interruzione, viene visualizzato l'indirizzo e il puntatore all'istruzione associato al punto di interruzione.

Sequenza temporale nel debugger che visualizza circa 30 indicatori di punti di interruzione.

Quando il punto di interruzione viene cancellato, la sequenza temporale del punto di interruzione associata viene rimossa automaticamente.

Chiamate di funzioni

È possibile visualizzare le posizioni delle chiamate di funzione nella sequenza temporale. A tale scopo, specificare la ricerca sotto forma di module!function, ad esempio TimelineTestCode!multiplyTwo. È anche possibile specificare caratteri jolly, ad esempio TimelineTestCode!m*.

Aggiunta di una sequenza temporale nel debugger con il nome della chiamata di funzione immesso.

Quando si passa il puntatore del mouse su una funzione chiamare il nome della funzione, i parametri di input, i relativi valori e il valore restituito vengono visualizzati. Questo esempio mostra il buffer e le dimensioni perché sono i parametri di DisplayGreeting. GetCppConGreeting.

Sequenza temporale nel debugger che visualizza chiamate di funzione e registra la finestra.

Accesso alla memoria

Usare la sequenza temporale di accesso alla memoria per visualizzare quando è stata letta o scritta un intervallo specifico di memoria o in cui è stata eseguita l'esecuzione del codice. Un indirizzo di avvio e arresto viene usato per definire un intervallo tra due indirizzi di memoria.

Aggiunta di una finestra di dialogo di accesso alla memoria della sequenza temporale con il pulsante di scrittura selezionato.

Quando si passa il puntatore del mouse su un elemento di accesso alla memoria, viene visualizzato il valore e il puntatore all'istruzione.

Sequenza temporale nel debugger che visualizza gli eventi di accesso alla memoria.

Usare le sequenze temporali

Una linea grigia verticale segue il cursore quando si passa il mouse sulla sequenza temporale. La linea blu verticale indica la posizione corrente nella traccia.

Fare clic sulle icone della lente di ingrandimento per ingrandire e ridurre la sequenza temporale.

Nell'area di controllo della sequenza temporale superiore usare il rettangolo per eseguire la panoramica della sequenza temporale. Trascinare i delimitatori esterni del rettangolo per ridimensionare la visualizzazione sequenza temporale corrente.

Sequenza temporale nel debugger che mostra l'area superiore usata per selezionare il riquadro di visualizzazione attivo.

Movimenti del mouse

Zoom avanti e indietro con CTRL+Rotellina di scorrimento.

Panoramica da lato a lato usando MAIUSC + Rotellina di scorrimento.

Tecniche di debug della sequenza temporale

Per illustrare le tecniche della sequenza temporale di debug, la procedura dettagliata relativa al debug di viaggi temporali viene riutilizzata qui. Questa dimostrazione presuppone che siano stati completati i primi due passaggi per compilare il codice di esempio e creare la registrazione TTD usando i primi due passaggi descritti.

Sezione 1: Compilare il codice di esempio

Sezione 2: Registrare una traccia dell'esempio "DisplayGreeting"

In questo scenario, il primo passaggio consiste nel trovare l'eccezione nella traccia di viaggio temporale. A tale scopo, fare doppio clic sull'unica eccezione presente nella sequenza temporale.

Quando si fa clic sull'eccezione, nella finestra di comando viene visualizzato il comando seguente.

(2dcc.6600): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: CC:0
@$curprocess.TTD.Events.Where(t => t.Type == "Exception")[0x0].Position.SeekTo()

Selezionare Visualizza>>registri per visualizzare i registri a questo punto nella sequenza temporale per avviare l'indagine.

Sequenza temporale nel debugger che visualizza l'eccezione demolab e la finestra registra.

Nell'output del comando si noti che lo stack (esp) e il puntatore di base (ebp) puntano a due indirizzi molto diversi. Ciò potrebbe indicare che il danneggiamento dello stack, probabilmente una funzione restituita e quindi danneggiata lo stack. Per convalidare questo problema, è necessario tornare indietro prima che lo stato della CPU sia danneggiato e verificare se è possibile determinare quando si è verificato il danneggiamento dello stack.

A questo scopo, verranno esaminati i valori delle variabili locali e dello stack.

Selezionare Visualizza>>variabili locali per visualizzare i valori locali.

Selezionare Visualizza>>stack per visualizzare lo stack di esecuzione del codice.

Al momento dell'errore nella traccia è comune terminare alcuni passaggi dopo la vera causa nel codice di gestione degli errori. Con il viaggio del tempo è possibile tornare indietro un'istruzione alla volta, per individuare la vera causa radice.

Nella barra multifunzione Home usare il comando Esegui istruzione indietro per eseguire il passaggio indietro di tre istruzioni. A questo scopo, continuare a esaminare lo stack, le variabili locali e registrare le finestre.

La finestra di comando visualizzerà la posizione di spostamento del tempo e i registri mentre si esegue il passaggio indietro di tre istruzioni.

0:000> t-
Time Travel Position: CB:41
eax=00000000 ebx=00564000 ecx=c0d21d62 edx=7a1e4a6c esi=00061299 edi=00061299
eip=00540020 esp=003cf7d0 ebp=00520055 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
00540020 ??              ???
0:000> t-
Time Travel Position: CB:40
eax=00000000 ebx=00564000 ecx=c0d21d62 edx=7a1e4a6c esi=00061299 edi=00061299
eip=00061767 esp=003cf7cc ebp=00520055 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
DisplayGreeting!main+0x57:
00061767 c3              ret
0:000> t-
Time Travel Position: CB:3A
eax=0000004c ebx=00564000 ecx=c0d21d62 edx=7a1e4a6c esi=00061299 edi=00061299
eip=0006175f esp=003cf718 ebp=003cf7c8 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
DisplayGreeting!main+0x4f:
0006175f 33c0            xor     eax,eax

A questo punto della traccia lo stack e il puntatore di base hanno valori che hanno più senso, quindi sembra che ci si avvicini al punto nel codice in cui si è verificato il danneggiamento.

esp=003cf718 ebp=003cf7c8

Inoltre, la finestra variabili locali contiene valori dell'app di destinazione e la finestra del codice sorgente evidenzia la riga di codice che è pronta per essere eseguita nel codice sorgente in questo punto della traccia.

Per ulteriori indagini, è possibile aprire una finestra di memoria per visualizzare il contenuto vicino all'indirizzo di memoria del puntatore dello stack (esp). In questo esempio ha un valore pari a 003cf7c8. Selezionare Memory Text ASCII (Testo>>memoria>>ASCII) per visualizzare il testo ASCII archiviato in tale indirizzo.

Debugger che visualizza registri, stack e finestre di memoria.

Sequenza temporale di accesso alla memoria

Dopo aver identificato una posizione di memoria di interesse, aggiungere una sequenza temporale di accesso alla memoria usando tale valore. Fare clic su + Aggiungi sequenza temporale e compilare l'indirizzo iniziale. Verranno esaminati 4 byte, quindi si aggiungerà tale valore all'indirizzo iniziale di 003cf7c8, 003cf7cb. L'impostazione predefinita consiste nell'esaminare tutte le scritture di memoria, ma è anche possibile esaminare solo operazioni di scrittura o esecuzione del codice in tale indirizzo.

Aggiunta di una finestra di dialogo di accesso alla memoria della sequenza temporale con il pulsante di scrittura selezionato e un valore iniziale pari a 003cf7c8.

È ora possibile attraversare la sequenza temporale inversa per esaminare in che punto in questo periodo di viaggio traccia questa posizione di memoria è stata scritta per vedere cosa è possibile trovare. Facendo clic su questa posizione nella sequenza temporale si noterà che i valori variabili locali hanno valori diversi per la stringa copiata. Il valore di destinazione non è completo, come se la lunghezza della stringa non sia corretta.

Sequenza temporale dell'accesso alla memoria e finestra variabili locali che visualizzano valori variabili locali con valori di origine e di destinazione diversi.

Sequenza temporale del punto di interruzione

L'uso dei punti di interruzione è un approccio comune per sospendere l'esecuzione del codice a un certo evento di interesse. TTD consente di impostare un punto di interruzione e tornare indietro nel tempo fino a quando tale punto di interruzione non viene raggiunto dopo la registrazione della traccia. La possibilità di esaminare lo stato del processo dopo che si è verificato un problema, per determinare la posizione migliore per un punto di interruzione, consente flussi di lavoro di debug aggiuntivi univoci per TTD.

Per esplorare una tecnica alternativa di debug della sequenza temporale, fare clic sull'eccezione nella sequenza temporale e, ancora una volta, spostarsi di nuovo tre passaggi indietro, usando il comando Esegui istruzione indietro sulla barra multifunzione Home .

In questo esempio molto piccolo sarebbe piuttosto facile guardare nel codice, ma se ci sono centinaia di righe di codice e decine di subroutine le tecniche descritte qui possono essere usate per ridurre il tempo necessario per individuare il problema.

Come accennato in precedenza, il puntatore di base (esp) invece di puntare a un'istruzione, punta al testo del messaggio.

Usare il comando ba per impostare un punto di interruzione sull'accesso alla memoria. Verrà impostato un punto di interruzione w- write per vedere quando questa area di memoria viene scritta.

0:000> ba w4 003cf7c8

Anche se si userà un semplice punto di interruzione dell'accesso alla memoria, i punti di interruzione possono essere costruiti per essere istruzioni condizionali più complesse. Per altre informazioni, vedere bp, bu, bm (Set Breakpoint).

Dal menu Home selezionare Torna indietro per tornare indietro nel tempo fino a quando non viene raggiunto il punto di interruzione.

A questo punto è possibile esaminare lo stack di programmi per vedere quale codice è attivo.

Sequenza temporale nel debugger che visualizza la sequenza temporale di accesso alla memoria e le finestre dello stack.

Poiché è molto improbabile che la funzione microsoft fornita wscpy_s() abbia un bug di codice simile al seguente, verrà esaminato ulteriormente nello stack. Lo stack mostra che Greeting!main chiama Greeting! GetCppConGreeting. Nell'esempio di codice molto piccolo è possibile aprire il codice a questo punto e probabilmente trovare l'errore abbastanza facilmente. Tuttavia, per illustrare le tecniche che possono essere usate con un programma più grande e più complesso, verrà impostata una sequenza temporale delle chiamate di funzione.

Sequenza temporale delle chiamate di funzione

Fare clic su + Aggiungi sequenza temporale e compilare per DisplayGreeting!GetCppConGreeting la stringa di ricerca della funzione.

Le caselle di controllo Percorso inizio e fine indicano che l'inizio e la fine di una chiamata di funzione nella traccia.

È possibile usare il comando dx per visualizzare l'oggetto chiamata di funzione per visualizzare i campi TimeStart e TimeEnd associati che corrispondono alla posizione iniziale e alla posizione finale della chiamata di funzione.

dx @$cursession.TTD.Calls("DisplayGreeting!GetCppConGreeting")[0x0]
    EventType        : 0x0
    ThreadId         : 0x6600
    UniqueThreadId   : 0x2
    TimeStart        : 6D:BD [Time Travel]
    SystemTimeStart  : Thursday, October 31, 2019 23:36:05
    TimeEnd          : 6D:742 [Time Travel]
    SystemTimeEnd    : Thursday, October 31, 2019 23:36:05
    Function         : DisplayGreeting!GetCppConGreeting
    FunctionAddress  : 0x615a0
    ReturnAddress    : 0x61746
    Parameters  

È necessario selezionare le caselle Inizio o Fine o Entrambe le caselle Percorso inizio e Fine.

Aggiungere una nuova finestra di dialogo Sequenza temporale che visualizza l'aggiunta della sequenza temporale delle chiamate di funzione con una stringa di ricerca di funzione displayGreeting. GetCppConGreeting.

Poiché il codice non è ricorsivo o rientrante, è piuttosto facile individuare la riga di tempo quando viene chiamato il metodo GetCppConGreeting. Anche la chiamata a GetCppConGreeting viene eseguita contemporaneamente al punto di interruzione e all'evento di accesso alla memoria definito. Sembra quindi che sia stato ristretto su un'area di codice per esaminare attentamente la causa radice dell'arresto anomalo dell'applicazione.

Sequenza temporale nel debugger che visualizza la sequenza temporale di accesso alla memoria e la finestra variabili locali con messaggio e buffer contenente valori stringa diversi.

Esplorare l'esecuzione del codice visualizzando più sequenze temporali

Anche se l'esempio di codice è ridotto, la tecnica di uso di più sequenze temporali consente l'esplorazione visiva di una traccia di viaggio temporale. È possibile esaminare il file di traccia per porre domande, ad esempio "quando si trova un'area di memoria a cui si accede prima che venga raggiunto un punto di interruzione?".

Sequenza temporale nel debugger che visualizza la sequenza temporale di accesso alla memoria e le finestre delle variabili locali.

La possibilità di visualizzare correlazioni aggiuntive e trovare elementi che potrebbero non essere previsti, differenzia lo strumento della sequenza temporale dall'interazione con la traccia di spostamento temporale usando i comandi della riga di comando.

Segnalibri sequenza temporale

Aggiungere un segnalibro alle posizioni di Time Travel importanti in WinDbg invece di copiare manualmente la posizione nel Blocco note. I segnalibri semplificano la visualizzazione a colpo d'occhio di posizioni diverse nella traccia rispetto ad altri eventi e per annotarle.

È possibile specificare un nome descrittivo per i segnalibri.

Finestra di dialogo Nuovo segnalibro che mostra il nome di esempio per la prima chiamata API nell'app Visualizza messaggio di saluto.

Accedere ai segnalibri tramite la finestra Sequenza temporale disponibile in Visualizza > sequenza temporale. Quando si passa il puntatore del mouse su un segnalibro, verrà visualizzato il nome del segnalibro.

Sequenza temporale che visualizza tre segnalibri con il puntatore del mouse su uno, rivelando il nome del segnalibro.

È possibile fare clic con il pulsante destro del mouse sul segnalibro per spostarsi in tale posizione, rinominare o eliminare il segnalibro.

Segnalibro fare clic con il pulsante destro del mouse sul menu popup che visualizza le opzioni per spostarsi in posizione, modificare e rimuovere.

Nota

Nella versione 1.2402.24001.0 del debugger la funzionalità segnalibro non è disponibile.

Vedi anche

Funzionalità di WinDbg

Procedura dettagliata per il debug di viaggi temporali