Domanda

Continuo a chiedermi come funziona un debugger? In particolare quello che può essere "collegato" all'eseguibile già in esecuzione. Comprendo che il compilatore traduce il codice in linguaggio macchina, ma come fa il debugger a "sapere" a cosa è stato collegato?

È stato utile?

Soluzione

I dettagli su come funziona un debugger dipenderanno da ciò che si sta eseguendo il debug e dal sistema operativo. Per il debug nativo su Windows puoi trovare alcuni dettagli su MSDN: API di debug Win32 .

L'utente comunica al debugger a quale processo collegarsi, per nome o per ID processo. Se è un nome, il debugger cercherà l'ID del processo e avvierà la sessione di debug tramite una chiamata di sistema; sotto Windows questo sarebbe DebugActiveProcess .

Una volta collegato, il debugger entrerà in un ciclo di eventi molto simile a qualsiasi interfaccia utente, ma invece di eventi provenienti dal sistema a finestre, il sistema operativo genererà eventi in base a ciò che accade nel processo in fase di debug & # 8211; ad esempio si verifica un'eccezione. Vedi WaitForDebugEvent .

Il debugger è in grado di leggere e scrivere la memoria virtuale del processo di destinazione e persino di regolare i suoi valori di registro tramite le API fornite dal sistema operativo. Consulta l'elenco di funzioni di debug per Windows.

Il debugger è in grado di utilizzare le informazioni dai file dei simboli per tradurre da indirizzi a nomi di variabili e posizioni nel codice sorgente. Le informazioni sul file dei simboli sono un insieme separato di API e non sono una parte fondamentale del sistema operativo in quanto tale. Su Windows questo avviene tramite SDK di accesso all'interfaccia di debug .

Se si esegue il debug di un ambiente gestito (.NET, Java, ecc.), il processo apparirà in genere simile, ma i dettagli sono diversi, poiché l'ambiente della macchina virtuale fornisce l'API di debug anziché il sistema operativo sottostante.

Altri suggerimenti

A quanto ho capito:

Per i punti di interruzione del software su x86, il debugger sostituisce il primo byte dell'istruzione con CC ( int3 ). Questo viene fatto con WriteProcessMemory su Windows. Quando la CPU arriva a quell'istruzione ed esegue il int3 , questo fa sì che la CPU generi un'eccezione di debug. Il sistema operativo riceve questo interrupt, si rende conto che il processo è in fase di debug e notifica al processo di debug che il punto di interruzione è stato raggiunto.

Dopo che il punto di interruzione viene raggiunto e il processo viene arrestato, il debugger cerca nel suo elenco di punti di interruzione e sostituisce il CC con il byte che si trovava lì originariamente. Il debugger imposta TF , la Flag Trap in EFLAGS (modificando CONTESTO ) e continua il processo. Il trap flag fa sì che la CPU generi automaticamente un'eccezione a passaggio singolo ( INT 1 ) sulla prossima istruzione.

Quando il processo in fase di debug si interrompe la volta successiva, il debugger sostituisce nuovamente il primo byte dell'istruzione breakpoint con CC e il processo continua.

Non sono sicuro che questo sia esattamente il modo in cui è implementato da tutti i debugger, ma ho scritto un programma Win32 che riesce a eseguire il debug usando questo meccanismo. Completamente inutile, ma educativo.

In Linux, il debug di un processo inizia con la ptrace (2) . Questo articolo contiene un ottimo tutorial su come usare ptrace per implementare costrutti di debug semplici.

Se utilizzi un sistema operativo Windows, una grande risorsa sarebbe "quotare le applicazioni di debug per Microsoft .NET e Microsoft Windows" di John Robbins:

(o anche l'edizione precedente: " Debugging Applications " )

Il libro ha un capitolo su come funziona un debugger che include il codice per un paio di debugger semplici (ma funzionanti).

Dal momento che non ho familiarità con i dettagli del debug Unix / Linux, questa roba potrebbe non essere applicabile ad altri sistemi operativi. Ma immagino che come introduzione a un argomento molto complesso i concetti - se non i dettagli e le API - dovrebbero "port" su quasi tutti i sistemi operativi.

Un'altra fonte preziosa per comprendere il debug è il manuale della CPU Intel (architetture Intel® 64 e IA-32 Manuale per gli sviluppatori di software). Nel volume 3A, capitolo 16, è stato introdotto il supporto hardware del debug, quali eccezioni speciali e registri di debug dell'hardware. Segue quel capitolo:

Flag T (trap), TSS - Genera un'eccezione di debug (#DB) quando c'è un tentativo fatto per passare a un'attività con il flag T impostato nel suo TSS.

Non sono sicuro che Window o Linux utilizzino o meno questo flag, ma è molto interessante leggere quel capitolo.

Spero che questo aiuti qualcuno.

La mia comprensione è che quando compili un'applicazione o un file DLL, qualunque cosa compili contiene simboli che rappresentano le funzioni e le variabili.

Quando si dispone di una build di debug, questi simboli sono molto più dettagliati rispetto a quando si tratta di una build di rilascio, consentendo così al debugger di fornire ulteriori informazioni. Quando si collega il debugger a un processo, guarda a quali funzioni si sta accedendo e risolve tutti i simboli di debug disponibili da qui (poiché sa come appaiono gli interni del file compilato, può verificare quale potrebbe essere nella memoria , con contenuti di ints, float, stringhe, ecc.). Come ha detto il primo poster, queste informazioni e il modo in cui funzionano questi simboli dipendono fortemente dall'ambiente e dalla lingua.

Penso che ci siano due domande principali a cui rispondere qui:

1. Come fa il debugger a sapere che si è verificata un'eccezione?

Quando si verifica un'eccezione in un processo in fase di debug, il debugger viene avvisato dal sistema operativo prima che a tutti i gestori di eccezioni utente definiti nel processo di destinazione venga data la possibilità di rispondere all'eccezione. Se il debugger sceglie di non gestire questa notifica di eccezione (prima possibilità), la sequenza di dispacciamento dell'eccezione procede ulteriormente e al thread di destinazione viene quindi data la possibilità di gestire l'eccezione se lo desidera. Se l'eccezione SEH non viene gestita dal processo di destinazione, al debugger viene quindi inviato un altro evento di debug, chiamato notifica di seconda possibilità, per informarlo che si è verificata un'eccezione non gestita nel processo di destinazione. Fonte

 inserisci qui la descrizione dell'immagine


2. In che modo il debugger sa come fermarsi su un breakpoint?

La risposta semplificata è: quando si inserisce un punto di interruzione nel programma, il debugger sostituisce il codice in quel punto con un'istruzione int3 che è un interruzione software . Di conseguenza, il programma viene sospeso e viene chiamato il debugger.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top