
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 write
also 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).
$ gdb break write if 1 == *(int*)($esp + 4) && strcmp((char*)*(int*)($esp + 8), "your string") == 0
x86 64-Bit-Modus
Wenn Ihr Prozess im x86-64-Modus ausgeführt wird, werden die Parameter durch Scratch-Register geleitet %rdi
und %rsi
$ gdb break write if 1 == $rdi && strcmp((char*)($rsi), "your string") == 0
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.

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.
x86_64-Version:
define stdout
catch syscall write
commands
printf "rsi = %s\n", $rsi
bt
end
condition $bpnum $rdi == 1 && strstr((char *)$rsi, "$arg0") != NULL
end
stdout qwer
Testprogramm:
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
write(STDOUT_FILENO, "asdf1", 5);
write(STDOUT_FILENO, "qwer1", 5);
write(STDOUT_FILENO, "zxcv1", 5);
write(STDOUT_FILENO, "qwer2", 5);
printf("as");
printf("df");
printf("qw");
printf("er");
printf("zx");
printf("cv");
fflush(stdout);
return EXIT_SUCCESS;
}
Ergebnis: Pausen bei:
qwer1
qwer2
fflush
. Der Vorherige printf
druckte eigentlich nichts, sie wurden gepuffert! Das write
syacall passierte nur auf dem fflush
.
Anmerkungen:
$bpnum
danke an Tromey unter: https://sourceware.org/bugzilla/show_bug.cgi?id=18727
rdi
: Register, das die Nummer des Linux-Systemaufrufs in x86_64 enthält, 1
ist für write
rsi
: erstes Argument des Systemaufrufs, z write
es zeigt auf den Puffer
strstr
: Standard-C-Funktionsaufruf, sucht nach Unterübereinstimmungen, gibt NULL zurück, wenn nicht gefunden
Getestet in Ubuntu 17.10, gdb 8.0.1.
spur
Eine weitere Option, wenn Sie sich interaktiv fühlen:
setarch "$(uname -m)" -R strace -i ./stdout.out |& grep '\] write'
Beispielausgabe:
[00007ffff7b00870] write(1, "a\nb\n", 4a
Kopieren Sie nun diese Adresse und fügen Sie sie ein in:
setarch "$(uname -m)" -R strace -i ./stdout.out |& grep -E '\] write\(1, "a'
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.

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
)
Im Vergleich zu den write
API unter Linux:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
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. )
-
Einschränkung der Bedingung:
fh == 1 && strstr((char *)buf, "Hello World") != 0
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_nolock
was 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:
- Legen Sie einen Haltepunkt fest
_write_nolock
.
-
Einschränkung der Bedingung:
*(int *)($esp + 4) == 1 && strstr(*(char **)($esp + 8), "Hello") != 0
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.
gcc -shared -fPIC io_helper.c -o libio_helper.so -g
Und Sie laden es, bevor Sie gdb ausführen.
LD_PRELOAD=$PWD/libio_helper.so; gdb test
Wobei test das Programm ist, das Sie debuggen.
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