Heim > Artikel > System-Tutorial > Ein tiefer Einblick in fortgeschrittene Linux-Debugger-Themen
Einführung | Endlich sind wir beim letzten Artikel dieser Serie angelangt! Dieses Mal gebe ich einen allgemeinen Überblick über einige der fortgeschritteneren Konzepte beim Debuggen: Remote-Debugging, Unterstützung gemeinsam genutzter Bibliotheken, Ausdrucksauswertung und Multithreading-Unterstützung. Die Umsetzung dieser Ideen ist komplexer, daher werde ich nicht näher auf die Umsetzung eingehen, beantworte aber gerne Fragen zu diesen Konzepten, wenn Sie Fragen haben. |
Remote-Debugging ist sehr nützlich für eingebettete Systeme oder das Debuggen verschiedener Umgebungen. Außerdem wird eine schmale Grenze zwischen High-Level-Debugger-Operationen und der Interaktion mit dem Betriebssystem und der Hardware gezogen. Tatsächlich können Debugger wie GDB und LLDB auch beim Debuggen lokaler Programme als Remote-Debugger ausgeführt werden. Die allgemeine Struktur ist wie folgt:
debugarch
Der Debugger ist die Komponente, mit der wir über die Befehlszeile interagieren. Wenn Sie eine IDE verwenden, gibt es möglicherweise eine weitere Ebene darüber, die über die Maschinenschnittstelle mit dem Debugger kommuniziert. Auf dem Zielcomputer (wahrscheinlich derselbe wie der native Computer) gibt es einen Debug-Stub, der theoretisch ein Wrapper um eine sehr kleine Debugging-Bibliothek des Betriebssystems ist, die alle Debugging-Aufgaben auf niedriger Ebene ausführt, wie das Setzen von Haltepunkten für Adressen. Ich sage „theoretisch“, weil Debug-Stubs heutzutage immer größer werden. Beispielsweise beträgt die Größe des LLDB-Debug-Stubs auf meinem Computer 7,6 MB. Der Debug-Stub kommuniziert mit dem debuggten Prozess und dem Debugger über das Remote-Protokoll, indem er einige betriebssystemspezifische Funktionen nutzt (in unserem Fall ptrace).
Das gebräuchlichste Remote-Debugging-Protokoll ist das GDB-Remote-Protokoll. Dabei handelt es sich um ein textbasiertes Paketformat, das zur Weitergabe von Befehlen und Informationen zwischen dem Debugger und den Debug-Stubs verwendet wird. Ich werde nicht näher darauf eingehen, aber Sie können hier weiterlesen. Wenn Sie LLDB starten und den Befehl log enable gdb-remote packets ausführen, erhalten Sie einen Trace aller über das Remote-Protokoll gesendeten Pakete. Auf GDB können Sie dasselbe mit set remotelogfile tun.
Als einfaches Beispiel ist dies das Paket, für das ein Haltepunkt festgelegt werden soll:
$Z0,400570,1#43
$ markiert den Anfang des Pakets. Z0 ist der Befehl zum Einfügen eines Speicherhaltepunkts. 400570 und 1 sind Parameter, wobei ersterer die Adresse zum Festlegen des Haltepunkts und letzterer der Haltepunkttypbezeichner für ein bestimmtes Ziel ist. Schließlich ist #43 eine Prüfsumme, um sicherzustellen, dass die Daten nicht beschädigt sind.
Das GDB-Remote-Protokoll lässt sich sehr einfach mit benutzerdefinierten Paketen erweitern, was für die Implementierung plattform- oder sprachspezifischer Funktionen nützlich ist.
Freigegebene Bibliotheken und Unterstützung für dynamisches LadenDer Debugger muss wissen, welche gemeinsam genutzten Bibliotheken vom zu debuggenden Programm geladen werden, damit er Haltepunkte setzen, Informationen und Symbole auf Quellcodeebene abrufen usw. kann. Neben der Suche nach dynamisch verknüpften Bibliotheken muss der Debugger auch Bibliotheken verfolgen, die zur Laufzeit über dlopen geladen werden. Um diesen Zweck zu erreichen, unterhält der dynamische Linker eine Schnittstruktur. Diese Struktur verwaltet eine verknüpfte Liste gemeinsam genutzter Bibliotheksdeskriptoren sowie einen Zeiger auf eine Funktion, die immer dann aufgerufen wird, wenn die verknüpfte Liste aktualisiert wird. Diese Struktur wird im Abschnitt .dynamic der ELF-Datei gespeichert und vor der Programmausführung initialisiert.
Ein einfacher Tracking-Algorithmus:
Ich habe ein kleines Beispiel dieser Konzepte geschrieben, das Sie hier finden können. Ich kann in Zukunft ausführlicher schreiben, wenn jemand Interesse hat.
AusdrucksberechnungAusdrucksauswertung ist eine Funktion des Programms, die es Benutzern ermöglicht, Ausdrücke in der ursprünglichen Quellsprache auszuwerten, während sie das Programm debuggen. Beispielsweise können Sie in LLDB oder GDB print foo() ausführen, um die foo-Funktion aufzurufen und das Ergebnis zu drucken.
Je nach Komplexität des Ausdrucks gibt es verschiedene Berechnungsmethoden. Wenn es sich bei dem Ausdruck nur um einen einfachen Bezeichner handelt, kann der Debugger die Debug-Informationen einsehen, die Variable finden und den Wert ausdrucken, genau wie wir es im letzten Teil dieser Serie getan haben. Wenn der Ausdruck etwas komplex ist, kann es möglich sein, den Code in einen Zwischenausdruck (IR) zu kompilieren und ihn zu interpretieren, um das Ergebnis zu erhalten. Beispielsweise verwendet LLDB für einige Ausdrücke Clang, um den Ausdruck in eine LLVM-IR zu kompilieren und zu interpretieren. Wenn der Ausdruck komplexer ist oder den Aufruf bestimmter Funktionen erfordert, muss der Code möglicherweise per JIT an das Ziel gesendet und im Adressraum des Debuggers ausgeführt werden. Dazu gehört der Aufruf von mmap, um ausführbaren Speicher zu reservieren, dann das Kopieren des kompilierten Codes in diesen Block und die Ausführung. LLDB wird mithilfe der JIT-Funktionen von LLVM implementiert.
Wenn Sie mehr über die JIT-Kompilierung erfahren möchten, kann ich Eli Benderskys Artikel zu diesem Thema wärmstens empfehlen.
多线程调试支持本系列展示的调试器仅支持单线程应用程序,但是为了调试大多数真实程序,多线程支持是非常需要的。支持这一点的最简单的方法是跟踪线程的创建,并解析 procfs 以获取所需的信息。
Linux 线程库称为 pthreads。当调用 pthread_create 时,库会使用 clone 系统调用来创建一个新的线程,我们可以用 ptrace 跟踪这个系统调用(假设你的内核早于 2.5.46)。为此,你需要在连接到调试器之后设置一些 ptrace 选项:
ptrace(PTRACE_SETOPTIONS, m_pid, nullptr, PTRACE_O_TRACECLONE);
现在当 clone 被调用时,该进程将收到我们的老朋友 SIGTRAP 信号。对于本系列中的调试器,你可以将一个例子添加到 handle_sigtrap 来处理新线程的创建:
case (SIGTRAP | (PTRACE_EVENT_CLONE << 8)): //get the new thread ID unsigned long event_message = 0; ptrace(PTRACE_GETEVENTMSG, pid, nullptr, message); //handle creation //...
一旦收到了,你可以看看 /proc//task/ 并查看内存映射之类来获得所需的所有信息。
GDB 使用 libthread_db,它提供了一堆帮助函数,这样你就不需要自己解析和处理。设置这个库很奇怪,我不会在这展示它如何工作,但如果你想使用它,你可以去阅读这个教程。
多线程支持中最复杂的部分是调试器中线程状态的建模,特别是如果你希望支持不间断模式或当你计算中涉及不止一个 CPU 的某种异构调试。
最后!呼!这个系列花了很长时间才写完,但是我在这个过程中学到了很多东西,我希望它是有帮助的。如果你有关于调试或本系列中的任何问题,请在 Twitter @TartanLlama或评论区联系我。如果你有想看到的其他任何调试主题,让我知道我或许会再发其他的文章。
Das obige ist der detaillierte Inhalt vonEin tiefer Einblick in fortgeschrittene Linux-Debugger-Themen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!