Wie pausiert GDB eine Ausführung?

16

Wie Sie vielleicht wissen, können wir GDB verwenden und Haltepunkte für unseren Code festlegen, um die Ausführung für das Debuggen anzuhalten.

Meine Frage ist, wie pausiert GDB einen Prozess und lässt Sie den Inhalt von Registern i rbeispielsweise anzeigen . Werden diese Register nicht ständig von anderen Betriebssystemprozessen verwendet? Wie werden sie nicht überschrieben?

Ist es nur ein Schnappschuss des Inhalts und keine Live-Daten?

Joe
quelle
2
Wie kommt es, dass alle Register nicht überschrieben werden, wenn das Betriebssystem Ihr Programm für einen Moment anhält und ein anderes ausführt?
user253751
CppCon 2018: Simon Brand „Wie C ++ - Debugger funktionieren“ youtube.com/watch?v=0DDrseUomfU
Robert Andrzejuk

Antworten:

24

Es variiert leicht mit der Architektur, aber die wichtigen Punkte gelten fast universell:

  • Durch die Unterbrechung der Wartung wird der CPU-Status (einschließlich der Register) vor dem Ausführen des ISR gespeichert und beim Beenden des ISR wiederhergestellt.

  • Wenn eine Interrupt-Serviceroutine den Inhalt des Speicherorts austauscht, in dem diese Register gespeichert sind, kann sie eine Kontextumschaltung durchführen . Jeder Thread hat einen Speicherbereich, in dem seine Register gespeichert werden, wenn der Thread nicht ausgeführt wird.

  • Der Kontextwechsel wird von einem Thread-Scheduler gesteuert, der berücksichtigt, ob ein Thread auf E / A, Synchronisation, Priorität, Signalübermittlung usw. wartet. Oft wird eine Anzahl von Unterbrechungen berücksichtigt.

  • Der Debugger kann die Anzahl der Unterbrechungen erhöhen, wodurch sichergestellt wird, dass der Thread nicht ausgeführt werden kann. Dann kann es die gespeicherte Kopie der Register des Threads überprüfen (und ändern).

Ben Voigt
quelle
14

Gestatten Sie mir zusätzlich zu den großartigen Informationen von @BenVoigt einige Ergänzungen:

Ein Haltepunkt wird vom Debugger gesetzt, indem ein Maschinencodewert (ein Befehl oder ein Teil eines Befehls) im zu debuggenden Prozess durch einen bestimmten Trap-Befehl an der Stelle im Code ersetzt wird, an der die gewünschte (Quell-) Zeile unterbrochen werden soll. Dieser spezielle Trap-Befehl ist als Haltepunkt gedacht - der Debugger weiß das und das Betriebssystem auch.

Wenn der zu debuggende Prozess / Thread auf den Trap-Befehl trifft, der den Prozess @Ben auslöst, wird beschrieben, welcher die Hälfte eines Context-Swaps enthält, der den aktuell ausgeführten Thread anhält (einschließlich des Speicherns seines CPU-Status im Speicher), um ihn möglicherweise später wiederaufzunehmen. Da es sich bei diesem Trap um einen Haltepunkt handelt, hält das Betriebssystem den zu debuggenden Prozess möglicherweise mithilfe eines von @Ben beschriebenen Mechanismus angehalten und benachrichtigt den Debugger und setzt ihn schließlich fort.

Der Debugger verwendet dann Systemaufrufe, um auf den gespeicherten Status des angehaltenen Prozesses / Threads zuzugreifen, der debuggt wird.

Um die unterbrochene Codezeile (die jetzt den speziellen Trap-Befehl enthält) auszuführen (fortzusetzen), stellt der Debugger den ursprünglichen Maschinencode-Wert wieder her, den er mit dem Breakpoint-Trap-Befehl überschrieben hat. oder der Benutzer erstellt neue Haltepunkte) und markiert den Prozess / Thread als ausführbar, möglicherweise unter Verwendung eines Mechanismus, wie er von @Ben beschrieben wird.

Tatsächliche Details können komplizierter sein, da das Beibehalten eines langen Haltepunkts, der getroffen wird, das Austauschen der Haltepunktfalle gegen echten Code bedeutet, damit die Zeile ausgeführt werden kann, und das erneute Eintauschen des Haltepunkts ...

Werden diese Register nicht ständig von anderen Betriebssystemprozessen verwendet? Wie werden sie nicht überschrieben?

Wie @Ben beschreibt, können mit der bereits vorhandenen Funktion zum Anhalten / Fortsetzen von Threads (Kontextwechsel / Austauschen von Multitasking ) Prozessoren von mehreren Prozessen / Threads mithilfe von Time Slicing gemeinsam genutzt werden.

Ist es nur ein Schnappschuss des Inhalts und keine Live-Daten?

Es ist beides. Da der Thread, der den Haltepunkt erreicht hat, angehalten wird, handelt es sich um eine Momentaufnahme der Live-Daten (CPU-Register usw.) zum Zeitpunkt des Anhaltens und des autorisierenden Masters der CPU-Registerwerte, die im Prozessor wiederhergestellt werden sollen, falls der Thread wieder aufgenommen wird . Wenn Sie die Benutzeroberfläche des Debuggers zum Lesen und / oder Ändern der CPU-Register (des zu debuggenden Prozesses) verwenden, wird dieser Snapshot / Master mithilfe von Systemaufrufen gelesen und / oder geändert.

Erik Eidt
quelle
1
Nun, die meisten Prozessorarchitekturen unterstützen Debug-Traps, die beispielsweise ausgelöst werden, wenn der IP-Wert (Anweisungszeiger) der in einem Haltepunktregister gespeicherten Adresse entspricht, sodass kein Code neu geschrieben werden muss. (Wenn Sie andere Register als IP abgleichen, können Sie Daten-Breakpoints abrufen, und wenn Sie nach jedem Befehl ein Trapping ausführen.) Was Sie beschrieben haben, ist natürlich auch möglich, solange sich der Code nicht in einem schreibgeschützten Speicher befindet.
Ben Voigt
Betreff "Wenn Sie die CPU-Register ändern ..." im letzten Absatz, meine ich "Wenn Sie die gespeicherte Kopie der CUP-Register ändern ..." Wenn das Betriebssystem den Vorgang fortsetzt, werden die geänderten Daten zurückgeschrieben zu den eigentlichen Registern.
Jamesqf
@ Jamesqf, ja, danke!
Erik Eidt
@BenVoigt, einverstanden. Während Debugger eine unbegrenzte Anzahl von Breakpoints verarbeiten können, kann die Hardware null oder einige wenige verarbeiten, sodass der Debugger ein wenig jonglieren muss.
Erik Eidt
@jamesqf: Das als Kopie zu beschreiben, ist etwas irreführend. Dies ist der offizielle Speicher für den Thread-Status, während der Thread nicht ausgeführt wird.
Ben Voigt
5

Genau genommen pausiert gdb die Ausführung nicht, zumindest in den meisten typischen Fällen. Stattdessen fragt gdb das Betriebssystem, und das Betriebssystem hält die Ausführung an.

Das mag zunächst wie eine Unterscheidung ohne Unterschied erscheinen - aber ehrlich, es gibt wirklich einen Unterschied. Der Unterschied besteht darin, dass diese Funktion bereits in das typische Betriebssystem integriert ist, da es in der Lage sein muss, die Ausführung eines Threads anzuhalten und erneut zu starten - wenn ein Thread nicht ausgeführt werden soll (z. B. eine Ressource, die benötigt wird) ist derzeit nicht verfügbar) muss das Betriebssystem angehalten werden, bis die Ausführung geplant werden kann.

Zu diesem Zweck verfügt das Betriebssystem normalerweise über einen Speicherblock, der für jeden Thread reserviert wird, um den aktuellen Status der Maschine zu speichern. Wenn ein Thread angehalten werden muss, wird der aktuelle Status der Maschine in diesem Bereich gespeichert. Wenn ein Thread fortgesetzt werden muss, wird der Status des Computers in diesem Bereich wiederhergestellt.

Wenn der Debugger einen Thread anhalten muss, hält das Betriebssystem diesen Thread genauso an wie aus anderen Gründen. Um dann den Status des angehaltenen Threads zu lesen, überprüft der Debugger den gespeicherten Status des Threads. Wenn Sie den Status ändern, schreibt der Debugger in den gespeicherten Status. Dieser wird dann wirksam, wenn der Thread fortgesetzt wird.

Jerry Sarg
quelle