In che modo GDB mette in pausa un'esecuzione


16

Come forse saprai, possiamo usare GDB e impostare punti di interruzione sul nostro codice per mettere in pausa l'esecuzione per il debug.

La mia domanda è: in che modo GDB mette in pausa un processo e consente di visualizzare il contenuto dei registri usando i rad esempio. Quei registri non sono costantemente utilizzati da altri processi del sistema operativo? come non vengono sovrascritti?

È solo un'istantanea del contenuto e non dei dati in tempo reale?


2
Come mai tutti i registri non vengono sovrascritti quando il sistema operativo decide di mettere in pausa il programma per un momento ed eseguirne uno diverso?
user253751

CppCon 2018: Simon Brand “How C ++ Debuggers Work” youtube.com/watch?v=0DDrseUomfU
Robert Andrzejuk

Risposte:


24

Varia leggermente con l'architettura, ma i punti importanti si applicano quasi universalmente:

  • L'interruzione della manutenzione fa sì che lo stato della CPU (compresi i registri) venga salvato in memoria prima di eseguire l'ISR e ripristinato all'uscita dall'ISR.

  • Se una routine di servizio di interruzione scambia il contenuto della posizione di memoria in cui sono salvati quei registri, può eseguire un cambio di contesto . Ogni thread ha una regione di memoria in cui i suoi registri vengono salvati quando il thread non è in esecuzione.

  • Il cambio di contesto è controllato da un programmatore di thread che tiene conto del fatto che un thread sia in attesa di I / O, sincronizzazione, qual è la sua priorità, consegna del segnale, ecc. Spesso viene conteggiato un conteggio delle sospensioni.

  • Il debugger può incrementare il conteggio delle sospensioni, il che garantisce che il thread non è eseguibile. Quindi può ispezionare (e modificare) la copia salvata dei registri del thread.


14

Oltre alle ottime informazioni di @BenVoigt, permettimi di fare alcune aggiunte:

Il debugger imposta un punto di interruzione sostituendo un valore del codice macchina (un'istruzione o parte di un'istruzione) nel processo in fase di debug con una particolare istruzione trap nella posizione nel codice che corrisponde alla riga (sorgente) desiderata in cui interrompere. Questa particolare istruzione trap è pensata per essere utilizzata come breakpoint: il debugger lo sa e anche il sistema operativo.

Quando il processo / thread in fase di debug colpisce l'istruzione trap, ciò innesca il processo descritto da @Ben, che include la metà di uno scambio di contesto che sospende il thread attualmente in esecuzione (che include il salvataggio dello stato della CPU in memoria) per una potenziale ripresa successiva. Poiché questa trap è una trap breakpoint, il sistema operativo mantiene sospeso il processo in fase di debug usando forse un meccanismo descritto da @Ben e avvisa e infine ripristina il debugger.

Il debugger utilizza quindi le chiamate di sistema per accedere allo stato salvato del processo / thread sospeso in fase di debug.

Per eseguire (riprendere) la riga di codice interrotta (che ora ha la particolare istruzione trap), il debugger ripristinerà il valore del codice macchina originale che ha sovrascritto con l'istruzione trap punto di interruzione, eventualmente impostare un'altra trap da qualche altra parte (ad es. oppure l'utente crea nuovi punti di interruzione) e contrassegna il processo / thread come eseguibile, magari utilizzando un meccanismo come descritto da @Ben.

I dettagli effettivi possono essere più complicati, in quanto mantenere un breakpoint di lunga durata che viene colpito significa fare qualcosa come scambiare la trappola del breakpoint con codice reale in modo che la linea possa essere eseguita, quindi ricambiare nuovamente il breakpoint ...

Quei registri non sono costantemente utilizzati da altri processi del sistema operativo? come non vengono sovrascritti?

Come descritto da @Ben, utilizzando la funzione di sospensione / ripresa del thread già esistente (il cambio di contesto / scambio di multitasking ) che consente ai processori di essere condivisi da più processi / thread utilizzando la suddivisione del tempo.

È solo un'istantanea del contenuto e non dei dati in tempo reale?

È entrambi. Poiché il thread che ha raggiunto il punto di interruzione è sospeso, è un'istantanea dei dati live (registri CPU, ecc.) Al momento della sospensione e il master autorevole dei valori del registro CPU da ripristinare nel processore nel caso in cui il thread dovesse essere ripreso . Se si utilizza l'interfaccia utente del debugger per leggere e / o modificare i registri della CPU (del processo in fase di debug) leggerà e / o modificherà questa istantanea / master utilizzando le chiamate di sistema.


1
Bene, la maggior parte delle architetture di processori supporta trap di debug che, ad esempio, si attivano quando l'IP (puntatore alle istruzioni) è uguale all'indirizzo memorizzato in un registro dei punti di interruzione, risparmiando la necessità di riscrivere il codice. (Abbinando i registri diversi dall'IP, è possibile ottenere punti di interruzione dei dati e, dopo ogni istruzione, è possibile ottenere un singolo passo) Ciò che è stato descritto è ovviamente possibile, purché il codice non sia in una memoria di sola lettura.
Ben Voigt,

Ri "Se modifichi i registri della CPU ..." nell'ultimo paragrafo, penso che intendi "Se modifichi la copia salvata dei registri CUP ..." Quindi quando il sistema operativo riprende il processo, i dati modificati vengono riscritti ai registri effettivi.
jamesqf,

@jamesqf, sì, grazie!
Erik Eidt,

@BenVoigt, d'accordo. anche se mentre i debugger possono gestire un numero illimitato di punti di interruzione, l'hardware può gestire zero o pochi, quindi il debugger deve fare un po 'di giocoleria.
Erik Eidt,

@jamesqf: descriverlo come una copia è un po 'fuorviante. È la memoria ufficiale per lo stato del thread mentre il thread non è in esecuzione.
Ben Voigt,

5

A rigor di termini, almeno nei casi più tipici, gdb stesso non mette in pausa l'esecuzione. Piuttosto, gdb chiede al sistema operativo e il sistema operativo mette in pausa l'esecuzione.

Inizialmente potrebbe sembrare una distinzione senza differenze - ma onestamente, c'è davvero una differenza. La differenza è questa: quell'abilità è già integrata nel tipico sistema operativo, perché deve essere in grado di mettere in pausa e riavviare comunque l'esecuzione del thread - quando un thread non è programmato per essere eseguito (ad esempio, ha bisogno di alcune risorse che non è attualmente disponibile) il sistema operativo deve metterlo in pausa fino a quando non può essere pianificato per l'esecuzione.

Per fare ciò, il sistema operativo in genere ha un blocco di memoria riservato a ciascun thread per salvare lo stato corrente della macchina. Quando è necessario mettere in pausa un thread, lo stato corrente della macchina viene salvato in quell'area. Quando deve riprendere un thread, lo stato della macchina viene ripristinato da quell'area.

Quando il debugger deve mettere in pausa un thread, il sistema operativo mette in pausa quel thread esattamente come farebbe per altri motivi. Quindi, per leggere lo stato del thread in pausa, il debugger esamina lo stato salvato del thread. Se si modifica lo stato, il debugger scrive nello stato salvato, quando diventa effettivo quando il thread viene ripreso.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.