Wie kann ich überwachen, was in den Standardausgabepuffer gesteckt wird und brechen, wenn eine bestimmte Zeichenfolge in der Pipe abgelegt wird?
Lesezeit: 12 Minuten
Ross Rogers
Wie können Sie unter Linux mit C/C++-Code und gdb einen gdb-Haltepunkt hinzufügen, um die eingehenden Zeichenfolgen zu scannen, um eine bestimmte Zeichenfolge zu unterbrechen?
Ich habe keinen Zugriff auf den Code einer bestimmten Bibliothek, aber ich möchte brechen, sobald diese Bibliothek eine bestimmte Zeichenfolge an die Standardausgabe sendet, damit ich den Stapel zurückgehen und den Teil meines Codes untersuchen kann, der die Bibliothek aufruft. Natürlich möchte ich nicht warten, bis ein Buffer Flush erfolgt. Ist das möglich? Vielleicht eine Routine in libstdc++ ?
Antonius
Diese Frage könnte ein guter Ausgangspunkt sein: Wie kann ich in gdb einen Haltepunkt auf “etwas wird an das Terminal gedruckt” setzen?
Sie könnten also zumindest brechen, wenn etwas nach stdout geschrieben wird. Die Methode beinhaltet im Wesentlichen das Setzen eines Haltepunkts auf der write Systemaufruf mit einer Bedingung, die das erste Argument ist 1 (dh STDOUT). In den Kommentaren findet sich auch ein Hinweis, wie man den String-Parameter der inspizieren könnte write auch anrufen.
x86 32-Bit-Modus
Ich habe mir Folgendes ausgedacht und es mit gdb 7.0.1-debian getestet. Es scheint ganz gut zu funktionieren. $esp + 8 enthält einen Zeiger auf den Speicherort der übergebenen Zeichenkette writealso wandeln Sie es zuerst in ein Integral um, dann in einen Zeiger auf char. $esp + 4 enthält den Dateideskriptor, in den geschrieben werden soll (1 für STDOUT).
Beachten Sie, dass eine Indirektionsebene entfernt wird, da wir Scratch-Register anstelle von Variablen auf dem Stapel verwenden.
Varianten
Funktionen außer strcmp kann in den obigen Snippets verwendet werden:
strncmp ist nützlich, wenn Sie mit dem ersten übereinstimmen möchten n Anzahl der Zeichen des zu schreibenden Strings
strstr kann verwendet werden, um Übereinstimmungen innerhalb einer Zeichenfolge zu finden, da Sie nicht immer sicher sein können, dass sich die gesuchte Zeichenfolge an der Stelle befindet Anfang der Zeichenfolge, die durch die geschrieben wird write Funktion.
Bearbeiten: Ich habe diese Frage genossen und die nachfolgende Antwort gefunden. Ich entschied mich für eine Blogeintrag darüber.
Diese Antwort hat mich heute wieder gerettet. Ich werde ein Kopfgeld aufstellen, nur damit ich Ihnen mehr Glaubwürdigkeit für diese Antwort geben kann.
– Ross Rogers
19. Januar 2012 um 21:55 Uhr
Hach, das ist toll. Keine Sorge Kumpel!
– Antonius
19. Januar 2012 um 23:21 Uhr
Dadurch geht das Programm für mich in den Hintergrund und danach fg es sagt Error in testing breakpoint condition: 'strcmp' has unknown return type; cast the call to its declared return type
– Riemenfisch
28. Oktober 2021 um 13:51 Uhr
Ciro Santilli Путлер Капут 六四事
catch + strstr Zustand
Das Coole an dieser Methode ist, dass sie nicht von glibc abhängt write verwendet wird: Es verfolgt den tatsächlichen Systemaufruf.
Außerdem ist es widerstandsfähiger gegen printf() Puffern, da es sogar Zeichenfolgen abfangen kann, die über mehrere gedruckt werden printf() Anrufe.
Der Vorteil dieser Methode besteht darin, dass Sie die üblichen UNIX-Tools zur Manipulation verwenden können strace Ausgabe, und es erfordert kein tiefes GDB-fu.
Erläuterung:
-i macht strace Ausgabe RIP
setarch -R deaktiviert ASLR für einen Prozess mit a personality Systemaufruf: Wie man mit strace -i debuggt, wenn die Adresse jedes Mal anders ist GDB macht das bereits standardmäßig, also muss es nicht noch einmal gemacht werden.
Das war absolut augenöffnend! Lief wie am Schnürchen. Besser als andere Lösungen basierend auf strstr, strcmp und so, weil diese “Python”-Funktionen nicht bereits in Ihrem Programm vorhanden sein müssen.
– Dunkles Auge
22. April 2018 um 0:30 Uhr
Für Anfänger wie mich. Schreiben Sie das Skript hinein script.gdb und ausführen $gdb /path/yourprogramTyp (gdb) source script.gdb und führen Sie Ihr Programm mit aus (gdb) run.
– Dunkles Auge
22. April 2018 um 0:32 Uhr
Tut mir leid, ich habe gerade vergessen, dass dieser Beitrag diese Funktionen nicht verwendet, aber ich kann den richtigen Beitrag nicht finden, also schaue für andere einfach hier: sourceware.org/gdb/current/onlinedocs/gdb/…
– Dunkles Auge
22. April 2018 um 0:36 Uhr
feihu
Anthonys Antwort ist großartig. Nach seiner Antwort probierte ich eine andere Lösung aus Fenster(x86-64-Bit-Windows). Ich weiß, diese Frage hier ist für GDB Unter Linux denke ich jedoch, dass diese Lösung eine Ergänzung für diese Art von Frage ist. Es könnte für andere hilfreich sein.
Lösung unter Windows
Unter Linux ein Aufruf an printf würde zu einem Aufruf der API führen write. Und da Linux ein Open-Source-Betriebssystem ist, konnten wir innerhalb der API debuggen. Die API ist jedoch unter Windows anders, sie stellt eine eigene API bereit WriteFile. Da Windows ein kommerzielles Nicht-Open-Source-Betriebssystem ist, konnten Haltepunkte in den APIs nicht hinzugefügt werden.
Aber ein Teil des Quellcodes von VC wird zusammen mit Visual Studio veröffentlicht, sodass wir im Quellcode herausfinden konnten, wo der schließlich aufgerufen wurde WriteFile API und setzen Sie dort einen Haltepunkt. Nach dem Debuggen des Beispielcodes fand ich die printf -Methode könnte zu einem Aufruf von führen _write_nolock in welchem WriteFile wird genannt. Die Funktion befindet sich in:
your_VS_folder\VC\crt\src\write.c
Der Prototyp ist:
/* now define version that doesn't lock/unlock, validate fh */
int __cdecl _write_nolock (
int fh,
const void *buf,
unsigned cnt
)
Sie haben völlig die gleichen Parameter. Wir könnten also einfach a setzen condition breakpoint in _write_nolock beziehen Sie sich einfach auf die obigen Lösungen, mit nur einigen Unterschieden im Detail.
Portable Lösung für Win32 und x64
Es ist ein großes Glück, dass wir verwenden konnten den Namen von Parametern direkt in Visual Studio beim Festlegen einer Bedingung für Haltepunkte sowohl auf Win32 als auch auf x64. So wird es sehr einfach, die Bedingung zu schreiben:
Fügen Sie einen Haltepunkt hinzu _write_nolock
NOTIZ: Es gibt kaum Unterschiede zwischen Win32 und x64. Wir könnten einfach den Funktionsnamen verwenden, um die Position von Haltepunkten auf Win32 festzulegen. Es funktioniert jedoch nicht auf x64, da am Eingang der Funktion die Parameter nicht initialisiert werden. Daher konnten wir den Parameternamen nicht verwenden, um die Bedingung von Haltepunkten festzulegen.
Aber glücklicherweise haben wir eine Lösung: Verwenden Sie die Position in der Funktion und nicht den Funktionsnamen, um die Breakpoints zu setzen, z. B. die 1. Zeile der Funktion. Dort sind die Parameter bereits initialisiert. (Ich meine, benutze die filename+line number um die Haltepunkte zu setzen, oder direkt die Datei öffnen und in der Funktion einen Haltepunkt setzen, nicht den Eingang sondern die erste Zeile. )
NOTIZ: Hier gibt es immer noch ein Problem, ich habe zwei verschiedene Möglichkeiten getestet, etwas in stdout zu schreiben: printf und std::cout. printf würde alle Zeichenfolgen in die schreiben _write_nolock Funktion auf einmal. Jedoch std::cout würde nur zeichenweise an übergeben _write_nolockwas bedeutet, dass die API aufgerufen wird strlen("your string") mal. In diesem Fall konnte die Bedingung nicht für immer aktiviert werden.
Win32-Lösung
Natürlich könnten wir die gleichen Methoden wie verwenden Anthony bereitgestellt: Setzen Sie die Bedingung von Haltepunkten durch Register.
Für ein Win32-Programm ist die Lösung fast identisch mit GDB auf Linux. Sie werden vielleicht bemerken, dass es eine Dekoration gibt __cdecl im Prototyp von _write_nolock. Diese Aufrufkonvention bedeutet:
Die Reihenfolge der Argumentübergabe ist von rechts nach links.
Die aufrufende Funktion holt die Argumente aus dem Stack.
Namensverzierungskonvention: Namen wird ein Unterstrich (_) vorangestellt.
Keine Fallübersetzung durchgeführt.
Es gibt eine Beschreibung hier. Und es gibt eine Beispiel die verwendet wird, um die Register und Stacks auf der Microsoft-Website anzuzeigen. Das Ergebnis konnte gefunden werden hier.
Dann ist es sehr einfach, die Bedingung von Breakpoints zu setzen:
Es ist die gleiche Methode wie unter Linux. Die erste Bedingung besteht darin, sicherzustellen, dass in die Zeichenfolge geschrieben wird stdout. Die zweite ist die Übereinstimmung mit der angegebenen Zeichenfolge.
x64-Lösung
Zwei wichtige Änderung von x86 bis x64 sind die 64-Bit-Adressierungsfunktion und ein flacher Satz von 16 64-Bit-Registern für den allgemeinen Gebrauch. Als Erhöhung der Register wird nur x64 verwendet __fastcall als Aufrufkonvention. Die ersten vier Integer-Argumente werden in Registern übergeben. Argumente ab fünf werden auf dem Stack übergeben.
Sie könnten auf die verweisen Parameterübergabe Seite auf der Microsoft-Website. Die vier Register (in der Reihenfolge von links nach rechts) sind RCX, RDX, R8 und R9. Es ist also sehr einfach, die Bedingung einzuschränken:
Legen Sie einen Haltepunkt fest _write_nolock.
NOTIZ: Es unterscheidet sich von der obigen portablen Lösung, wir könnten einfach die Position des Haltepunkts auf die Funktion und nicht auf die 1. Zeile der Funktion setzen. Der Grund ist, dass alle Register bereits am Eingang initialisiert sind.
Einschränkungsbedingung:
$rcx == 1 && strstr((char *)$rdx, "Hello") != 0
Der Grund, warum wir casten und dereferenzieren müssen esp ist dass $esp greift auf die zu ESP registrieren, und für alle Absichten und Zwecke ist a void*. Während die Register hier direkt die Werte von Parametern speichern. Eine weitere Indirektionsebene wird also nicht mehr benötigt.
Post
Mir macht diese Frage auch sehr viel Spaß, also habe ich übersetzt Anthonys Beitrag ins Chinesische und füge meine Antwort als Ergänzung hinzu. Der Beitrag konnte gefunden werden hier. Danke für die Erlaubnis von @anthony-arnold .
JeanP
Anthonys Antwort ist sehr interessant und liefert definitiv einige Ergebnisse. Ich denke jedoch, dass die Pufferung von printf fehlen könnte. In der Tat können Sie unter Unterschied zwischen write () und printf () Folgendes lesen: “printf ruft nicht unbedingt jedes Mal schreiben auf. Vielmehr puffert printf seine Ausgabe.”
STDIO-WRAPPER-LÖSUNG
Daher kam ich mit einer anderen Lösung, die darin besteht, eine Hilfsbibliothek zu erstellen, die Sie vorab laden können, um die printf-ähnlichen Funktionen zu verpacken. Sie können dann einige Breakpoints auf dieser Bibliotheksquelle setzen und zurückverfolgen, um die Informationen über das Programm zu erhalten, das Sie debuggen.
Es funktioniert unter Linux und zielt auf die libc ab, ich weiß es nicht für c++ IOSTREAM, auch wenn das Programm direkt schreiben verwendet, wird es es vermissen.
Hier ist der Wrapper zum Entführen von printf (io_helper.c).
#include<string.h>
#include<stdio.h>
#include<stdarg.h>
#define MAX_SIZE 0xFFFF
int printf(const char *format, ...){
char target_str[MAX_SIZE];
int i=0;
va_list args1, args2;
/* RESOLVE THE STRING FORMATING */
va_start(args1, format);
vsprintf(target_str,format, args1);
va_end(args1);
if (strstr(target_str, "Hello World")){ /* SEARCH FOR YOUR STRING */
i++; /* BREAK HERE */
}
/* OUTPUT THE STRING AS THE PROGRAM INTENTED TO */
va_start(args2, format);
vprintf(format, args2);
va_end(args2);
return 0;
}
int puts(const char *s)
{
return printf("%s\n",s);
}
Ich habe Puts hinzugefügt, weil gcc dazu neigt, printf durch Puts zu ersetzen, wenn es möglich ist. Also zwinge ich es zurück zu printf.
Als nächstes kompilieren Sie es einfach in eine gemeinsam genutzte Bibliothek.
Dann kannst du mit brechen break io_helper.c:19 weil Sie die Bibliothek mit -g kompiliert haben.
ERKLÄRUNGEN
Unser Glück hier ist, dass printf und andere fprintf, sprintf … nur hier sind, um die variadischen Argumente aufzulösen und ihr ‘v’-Äquivalent zu nennen. (vprintf in unserem Fall). Diese Aufgabe zu erledigen ist einfach, also können wir es tun und die eigentliche Arbeit mit der ‘v’-Funktion der libc überlassen. Um die variablen Argumente von printf zu erhalten, müssen wir nur va_start und va_end verwenden.
Der Hauptvorteil dieser Methode besteht darin, dass Sie sicher sind, dass Sie sich beim Abbrechen in dem Teil des Programms befinden, der Ihre Zielzeichenfolge ausgibt, und dass dies kein Überbleibsel in einem Puffer ist. Auch machen Sie keine Annahmen über die Hardware. Der Nachteil ist, dass Sie davon ausgehen, dass das Programm die libc stdio-Funktion verwendet, um Dinge auszugeben.
10121000cookie-checkWie kann ich überwachen, was in den Standardausgabepuffer gesteckt wird und brechen, wenn eine bestimmte Zeichenfolge in der Pipe abgelegt wird?yes