Cenni preliminari sul debug CLR
L'API di debug di Common Language Runtime consente ai fornitori di strumenti di scrivere debugger per il debug di applicazioni in esecuzione nell'ambiente CLR. Il codice di cui eseguire il debug può essere di qualsiasi tipo supportato da CLR.
L'API di debug di CLR viene implementata principalmente con codice non gestito. Pertanto, viene presentata come un set di interfacce COM (Component Object Model). L'API è costituita dagli elementi seguenti:
Un insieme di interfacce e oggetti COM implementati da CLR.
Un insieme di interfacce di callback COM che devono essere implementate dal debugger.
In questa panoramica sono incluse le sezioni seguenti:
Scenari di debug CLR
Categorie di API
Connessione o avvio di un programma
Controllo dell'esecuzione
Esame dello stato del programma
Modifica dello stato del programma
Utilizzo di Modifica e continuazione
Valutazione delle funzioni
Inserimento dinamico di codice
Ambienti supportati
Argomenti correlati
Scenari di debug CLR
Nelle sezioni seguenti viene descritto il modo in cui l'API di debug di Common Language Runtime gestisce gli scenari di debug tipici. Si noti che il runtime supporta alcuni scenari direttamente e interagisce con i metodi correnti per supportarne altri.
Debug out-of-process
Nel debug out-of-process, il debugger si trova in un processo diverso da quello sottoposto a debug, ossia è esterno all'oggetto del debug. Questo scenario riduce le interazioni tra il debugger e l'oggetto del debug. Fornisce pertanto un quadro più preciso del processo.
L'API di debug di CLR supporta direttamente il debug out-of-process. L'API gestisce tutte le comunicazioni tra il debugger e le parti gestite dell'oggetto del debug per supportare il debug del codice gestito.
Sebbene l'API di debug di CLR venga utilizzata in modalità out-of-process, una parte della logica di debug (ad esempio la sincronizzazione dei thread) avrà luogo in-process con l'oggetto del debug. Nella maggior parte dei casi, si tratta di un dettaglio di implementazione che deve essere trasparente per il debugger. Per ulteriori informazioni sulla sincronizzazione dei thread, vedere Architettura di debug CLR. L'utilizzo out-of-process dell'API di debug comporta lo svantaggio dell'impossibilità di utilizzare l'API per controllare i dettagli arresto anomalo del sistema.
Debug in-process
In .NET Framework versioni 1.0 e 1.1, l'API di debug di CLR supportava un debug in-process limitato, in cui un profiler poteva utilizzare le funzionalità di verifica dell'API di debug. In .NET Framework 2.0 il debug in-process è stato sostituito con un set di funzionalità più coerente con l'API di profilatura. Per ulteriori informazioni su queste modifiche, fare riferimento alle funzionalità snapshot dello stack e verifica degli oggetti in Cenni preliminari sulla profilatura.
Debug di un processo remoto
Nel debug di un processo remoto, l'interfaccia utente del debugger si trova su un computer diverso da quello del processo sottoposto a debug. Questo scenario può essere utile se il debugger interferisce con l'oggetto del debug quando i due sono in esecuzione nello stesso computer. L'interferenza può essere provocata dalle cause seguenti:
Risorse limitate.
Dipendenze tra percorsi.
Bug che interferiscono con il sistema operativo.
L'API di debug di CLR non supporta il debug diretto di un processo remoto. Un debugger basato sull'API di debug di CLR deve comunque essere di tipo out-of-process rispetto all'oggetto del debug. Questa soluzione richiede pertanto un processo proxy nel computer in cui è presente l'oggetto del debug.
Debug di codice non gestito
Spesso nello stesso processo sono presenti sia codice gestito che codice non gestito. Il debug simultaneo di entrambi i tipi di codice rappresenta una necessità comune.
L'API di debug di CLR supporta l'esecuzione di istruzioni passando tra codice gestito e non gestito, ma non supporta direttamente il debug del codice non gestito. L'API di debug di CLR può tuttavia coesistere con un debugger di codice non gestito, mediante la condivisione delle funzionalità di debug di Win32.
L'API di debug di CLR fornisce due opzioni per il debug di un processo:
Un'opzione soft-attach in cui vengono sottoposte al debug solo le parti gestite del processo. Tale opzione consente a un debugger di disconnettersi dal processo in cui è in corso il debug al termine dell'operazione.
Un'opzione hard-attach, in cui sia le parti gestite, sia quelle non gestite di un processo sono sottoposte al debug e tutti gli eventi di debug di Win32 vengono esposti tramite l'API di debug.
Ambienti con linguaggi misti
Nel software dei componenti, componenti diversi possono essere stati compilati con linguaggi differenti. Un debugger deve riconoscere le differenze tra i linguaggi, in modo da poter visualizzare i dati nel formato corretto, valutare le espressioni con la sintassi appropriata e così via.
L'API di debug di CLR non fornisce supporto diretto per gli ambienti con linguaggi misti, in quanto CLR non riconosce il linguaggio di origine. Le funzionalità di mapping dell'origine disponibili in un debugger devono consentire l'esecuzione del mapping di una determinata funzione al linguaggio in cui la funzione è stata implementata.
Processi multipli e programmi distribuiti
Il programma di un componente può includere l'interazione tra componenti in esecuzione in processi diversi o anche su computer diversi di una rete. Un debugger deve essere in grado di tracciare la logica di esecuzione tra processi e computer per fornire una visualizzazione logica delle operazioni in corso.
L'API di debug di CLR non fornisce supporto diretto per il debug di più processi. Anche in questo caso, un debugger che sta utilizzando l'API deve fornire direttamente tale supporto e garantire il funzionamento dei metodi disponibili a tal fine.
Torna all'inizio
Categorie di API
L'API di debug include i tre gruppi di interfacce seguenti, tutti utilizzati da un debugger CLR e tutti implementati come codice non gestito:
Interfacce che supportano il debug delle applicazioni CLR.
Interfacce che forniscono accesso a informazioni di debug sui simboli, in genere archiviate nei file del database di programma (PDB).
Interfacce che supportano le query dei processi e dei domini dell'applicazione di un computer.
L'API di debug si basa su due set aggiuntivi di interfacce:
API dei metadati per gestire la verifica delle informazioni statiche del programma, ad esempio le informazioni sulle classi e sul tipo di metodo.
API di archiviazione dei simboli per supportare il debug a livello di origine per i debugger del codice gestito.
Le interfacce di debug, inoltre, possono essere organizzate nelle categorie funzionali mostrate nella tabella riportata di seguito.
Categoria API |
Descrizione |
---|---|
Registrazione |
Interfacce che il debugger chiama per registrarsi con CLR e richiedere notifica quando si verificano eventi specifici. |
Notification |
Interfacce di callback utilizzate da CLR per notificare al debugger i vari eventi e restituire le informazioni richieste. Queste interfacce devono essere implementate dal debugger. |
Punto di interruzione |
Interfacce che il debugger chiama per recuperare informazioni sui punti di interruzione. |
Esecuzione |
Interfacce che il debugger chiama per controllare l'esecuzione dell'oggetto del debug e accedere agli stack di chiamate. |
Informazioni |
Interfacce che il debugger chiama per ottenere informazioni sull'oggetto del debug. |
Enumerazione |
Interfacce che il debugger chiama per enumerare gli oggetti. |
Ultima modifica |
Interfacce che il debugger chiama per modificare il codice del quale si sta eseguendo il debug. |
Nelle sezioni seguenti vengono descritte le funzionalità fornite dai servizi di debug di Common Language Runtime.
Torna all'inizio
Connessione o avvio di un programma
CLR consente di connettere il debugger a un programma in esecuzione o avviare un processo. I servizi di debug di CLR supportano un debug just-in-time (JIT) consentendo di connettere il debugger a un programma che genera un'eccezione non gestita. Tuttavia, in un programma che non è in esecuzione in modalità sottoponibile a debug potrebbe essere disponibile una quantità minore di informazioni di debug. Per evitare questo problema, un programma può essere sempre eseguito in modalità debug. Per ulteriori informazioni sulla modalità debug, vedere i seguenti argomenti:
Torna all'inizio
Controllo dell'esecuzione
I servizi di debug di CLR forniscono numerose modalità per controllare l'esecuzione di un programma. Esse comprendono punti di interruzione, debug passo a passo, notifiche di eccezione, valutazione delle funzioni e altri eventi relativi all'avvio e all’arresto di un programma.
L'API di debug CLR fornisce il controllo dell'esecuzione solo per codice gestito. Se si desidera eseguire il controllo dell'esecuzione in codice non gestito, è necessario implementare separatamente tale funzionalità nel debugger.
Punti di interruzione
È possibile creare punti di interruzione specificando il codice e il Microsoft Intermediate Language Microsoft (MSIL) o l'offset nativo della posizione in cui deve verificarsi l'interruzione. Al debugger verrà notificato quando viene incontrato il punto di interruzione. L'API di debug non supporta direttamente i punti di interruzione condizionali. Un debugger può tuttavia implementare questi elementi valutando un'espressione in risposta a un punto di interruzione e stabilendo se informare l'utente dell'interruzione.
Esecuzione di istruzioni
I servizi di debug CLR forniscono un'ampia varietà della funzionalità di debug passo a passo. Un programma può anailizzare il codice eseguendo un'istruzione alla volta (debug passo a passo) o un intervallo di istruzioni alla volta (debug per intervallo). Può ignorare, analizzare o uscire da un'istruzione di una funzione. I servizi di debug CLR possono anche avvisare il debugger se si verifica un'eccezione che interrompe l'operazione di debug passo a passo.
Sebbene i servizi di debug non supportino direttamente l’esecuzione passo a passo di istruzioni con codice non gestito, forniranno callback quando un'operazione di debug passo a passo raggiunge un codice non gestito, per passare il controllo al debugger. Essi forniscono anche funzionalità che consentono al debugger di determinare quando si sta per passare da codice non gestito a codice gestito.
Il CLR non fornisce direttamente il debug passo a passo a livello di sorgente. Un debugger può fornire questa funzionalità utilizzando il debug per intervallo con le proprie informazioni di mapping del sorgente. È possibile utilizzare le interfacce dell'archivio dei simboli per ottenere informazioni a livello di sorgente. Per ulteriori informazioni su queste interfacce, vedere Archivio dei simboli di diagnostica (riferimenti alle API non gestite).
Eccezioni
I servizi di debug di CLR consentono a un debugger di essere informato sia delle eccezioni first-chance sia delle eccezioni second-chance nel codice gestito. L'oggetto generato è disponibile per l'ispezione a ogni punto.
CLR non gestisce eccezioni native in codice non gestito a meno che non si propaghino fino a codice gestito. Tuttavia, è comunque, possibile utilizzare i servizi di debug Win32 condivisi con i servizi di debug CLR per gestire eccezioni non gestite.
Eventi
I servizi di debug CLR avvisano un debugger quando si verificano numerosi eventi. Tali eventi includono la creazione e l’uscita da un processo, la creazione e l’uscita da un thread, la creazione e l’uscita da un dominio applicazione, il caricamento e lo scaricamento di un assembly, il caricamento e lo scaricamento di un modulo e il caricamento e lo scaricamento di classi. Per garantire buone prestazioni è possibile disabilitare gli eventi di caricamento e di scaricamento di classi per un modulo. Per impostazione predefinita, gli eventi per il caricamento e lo scaricamento di classi sono disabilitati.
Controllo dei thread
I servizi di debug di CLR forniscono interfacce per sospendere e riprendere singoli thread (gestiti).
Torna all'inizio
Esame dello stato del programma
I servizi di debug di CLR forniscono un modo dettagliato per esaminare le parti di un processo che eseguono codice gestito quando il processo è in stato di interruzione. Un processo può essere esaminato per ottenere un elenco di thread fisici.
Un thread può essere esaminato per controllare lo stack di chiamate. Lo stack di chiamate di un thread viene scomposto a due livelli: a livello della catena e a livello dello stack frame. Lo stack di chiamate viene in primo luogo secomposto in catene. Una catena è un segmento di stack di chiamate logico contiguo. Contiene stack frame gestiti o non gestiti, ma non entrambi. Inoltre, tutti i frame delle chiamate gestite in una singola catena condividono lo stesso contesto CLR. Una catena può essere gestita o non gestita.
Ogni catena gestita può essere ulteriormente scomposta in singoli stack frame. Ogni stack frame rappresenta una chiamata a un metodo. È possibile eseguire una query su uno stack frame per ottenerne il codice in esecuzione o ottenerne gli argomenti, le variabili locali e i registri nativi.
Una catena non gestita non contiene alcuno stack frame. Al contrario, fornisce l'intervallo degli indirizzi dello stack allocati al codice non gestito. È compito di un debugger di codice non gestito decodificare la parte non gestita dello stack e fornire una traccia dello stack.
Nota |
---|
I servizi di debug di CLR non supportano il concetto di variabili locali nel codice sorgente.È compito del debugger eseguire il mapping delle variabili locali rispetto alle rispettive allocazioni. |
I servizi di debug di CLR forniscono anche l’accesso alle variabili globali, statiche di classe e locali di thread.
Torna all'inizio
Modifica dello stato del programma
I servizi di debug di CLR consentono a un debugger di modificare la posizione fisica del puntatore all'istruzione durante l'esecuzione, anche se questa operazione può essere pericolosa. Il puntatore all'istruzione può essere modificato senza problemi quando sono vere le condizioni seguenti:
Il puntatore all'istruzione corrente e il puntatore all'istruzione di destinazione sono entrambi in corrispondenza di punti di sequenza. I punti di sequenza rappresentano approssimativamente limiti di un'istruzione.
Il puntatore all'istruzione di destinazione non si trova in un filtro eccezioni, in un blocco catch o in un blocco finally.
Il puntatore all'istruzione corrente si trova all'interno di un blocco catch, il puntatore all'istruzione di destinazione non si trova al di fuori del blocco catch.
Il puntatore all'istruzione di destinazione si trova nello stesso frame del puntatore dell'istruzione corrente.
Quando la posizione fisica del puntatore all’istruzione si modifica, sulle variabili nella posizione del puntatore all'istruzione corrente verrà eseguito il mapping delle variabili in corrispondenza della posizione del puntatore all'istruzione di destinazione. I riferimenti di Garbage Collection alla posizione del puntatore all'istruzione di destinazione verranno inizializzati correttamente.
Dopo la modifica del puntatore all'istruzione, i servizi di debug di CLR contrassegnano tutte le informazioni sullo stack memorizzate nella cache come non valide e aggiornano le informazioni quando sono necessarie. I debugger che memorizzano nella cache le informazioni dei puntatori allo stack come frame e catene, devono aggiornare queste informazioni dopo avere modificato il puntatore all'istruzione.
Il debugger può anche modificare i dati di un programma quando il programma non è in esecuzione. Il debugger può modificare le variabili locali e gli argomenti di una funzione quando la funzione è in esecuzione, in modo simile a un’ispezione. Il debugger può anche aggiornare campi di matrici e oggetti, nonché campi statici e variabili globali.
Torna all'inizio
Utilizzo di Modifica e continuazione
Durante una sessione di debug, è possibile utilizzare la funzione Modifica e continuazione per effettuare le operazioni seguenti:
Modificare il codice sorgente.
Ricompilare l'origine modificata.
Mantenere il resto dello stato del runtime del file eseguibile sottoposto a debug.
Continuare la sessione di debug senza dovere eseguire di nuovo il file eseguibile dall'inizio.
Torna all'inizio
Valutazione delle funzioni
Per valutare espressioni utente e proprietà dinamiche di oggetti, un debugger deve disporre di una modalità per eseguire il codice del processo sul quale si sta eseguendo il debug. I servizi di debug di CLR consentono al debugger di eseguire una chiamata a una funzione o a un metodo ed eseguirla all'interno del processo dell'oggetto del debug.
CLR consente al debugger di interrompere tale operazione perché potrebbe essere pericolosa (ad esempio, potrebbe generare un deadlock con il codice esistente). Se la valutazione viene interrotta correttamente, il thread viene trattato come se la valutazione non fosse mai avvenuta, a parte qualche effetto collaterale sulle variabili locali a causa della valutazione parziale. Se la funzione chiama un codice non gestito o per qualche motivo si blocca, potrebbe essere impossibile terminare la valutazione.
Una volta completata la valutazione della funzione, CLR utilizza un callback per notificare al debugger se la valutazione è stata completata correttamente o se la funzione ha generato un'eccezione. È possibile utilizzare i metodi ICorDebugValue e ICorDebugValue2 per esaminare i risultati di una valutazione.
Il thread sul quale deve verificarsi la valutazione della funzione deve essere arrestato in codice gestito, in un punto che sia sicuro per l’operazione di Garbage Collection. La valutazione di una funzione è consentita anche per le eccezioni non gestite. In codice di non ottimizzato, questi punti sicuri sono molto comuni; la maggior parte dei punti di interruzione o delle operazioni di passaggio a livello di MSIL lo saranno. Tuttavia, questi punti possono non essere frequenti in codice ottimizzato. Talvolta un’intera funzione può non avere alcun punto sicuro. La frequenza di punti sicuri per l’operazione di Garbage Collection varierà da funzione a funzione. Anche in codice non ottimizzato, è possibile non arrestarsi in alcuno. In codice ottimizzato o non ottimizzato, il metodo ICorDebugController::Stop raramente termina in un punto sicuro.
I servizi di debug di CLR configureranno una nuova catena sul thread per avviare la valutazione di una funzione e chiamare la funzione richiesta. Non appena la valutazione è avviata, sono disponibili tutte le caratteristiche dell'API di debug: controllo dell'esecuzione, ispezione, valutazione delle funzioni e così via. Sono supportate valutazioni annidate e i punti di interruzione sono gestiti nel modo consueto.
Torna all'inizio
Inserimento dinamico di codice
Alcuni debugger consentono agli utenti di inserire istruzioni arbitrarie nella finestra Controllo immediato e di eseguirle. I servizi di debug di CLR supportano questo scenario. In pratica, non è presente alcuna restrizione su quale codice è possibile inserire dinamicamente. Le istruzioni goto non locali non sono ad esempio consentite.
L'inserimento dinamico di codice viene implementato utilizzando una combinazione di operazioni Modifica e continuazione e di valutazione della funzione. Sul codice da inserire viene eseguito il wrapping in una funzione e viene inserito mediante Modifica e continuazione. La funzione inserita viene quindi valutata. Se si desidera, è possibile passare argomenti ByRef alla funzione wrapper, in modo tale che gli effetti collaterali siano immediati e permanenti.
Torna all'inizio
Ambienti supportati
Le funzionalità di debug di CLR sono disponibili su tutti i processori e i sistemi operativi supportati da CLR, con le seguenti eccezioni:
Il debug Modifica e continuazione e in modalità mista non sono supportati sui sistemi operativi a 64 bit. I metodi SetIP (ICorDebugILFrame::SetIP e ICorDebugNativeFrame::SetIP) presentano restrizioni aggiuntive sui sistemi operativi a 64 bit. Le altre funzionalità sono equivalenti in tutti i processori, sebbene vi siano rappresentazioni dei dati specifiche del processore, come dimensioni del puntatore, contesti del registro e così via.
Il debug Modifica e continuazione e in modalità mista non sono supportati sui sistemi operativi basati su Win9x. Le altre funzionalità devono essere equivalenti in tutti i sistemi operativi. Vi sono, tuttavia, alcune eccezioni specifiche, segnalate nella documentazione relativa alle singole funzioni.
Torna all'inizio
Argomenti correlati
Titolo |
Descrizione |
---|---|
Viene descritto il modo in cui i diversi componenti dell'API di debug di CLR interagiscono con CLR e il debugger. |
|
Vengono descritte le modifiche e i miglioramenti apportati al debug in .NET Framework versione 2.0. |
|
Viene descritto come alcune delle interfacce di debug CLR richiedano che il processo del quale si sta eseguendo il debug si trovi in uno stato specifico. |
|
Fornisce una descrizione dettagliata di come viene eseguito il debug di un processo runtime. |
|
Descrive come il debugger usa l'API di debug di CLR per impostare punti di interruzione, utilizzare codice gestito e non gestito e gestire le eccezioni. |
|
Descrive come il debugger utilizza l'API di debug di CLR per accedere agli stack frame gestiti e valutare le espressioni. |
|
Descrive l’inserimento dinamico di codice nel quale CLR assume il controllo di un thread attivo per eseguire codice che non era presente nel file eseguibile di tipo PE originale. |
|
Riepiloga le interfacce di pubblicazione CLR che enumerano e forniscono informazioni sui processi e domini applicazione su un computer. |
|
Espone alcune considerazioni sulla sicurezza per l'utilizzo dell'API di debug di CLR |
|
Descrive le coclassi non gestite utilizzate dall'API di debug. |
|
Descrive le interfacce non gestite che gestiscono il debug di un programma in esecuzione in Common Language Runtime. |
|
Descrive le funzioni statiche globali non gestite utilizzate dall'API di debug. |
|
Descrive le enumerazioni non gestite utilizzate dall'API di debug. |
|
Descrive le strutture non gestite utilizzate dall'API di debug. |
Torna all'inizio