Diese Frage könnte ein guter Ausgangspunkt sein:Wie kann ich in gdb einen Haltepunkt auf "etwas wird auf dem Terminal gedruckt" setzen?
Sie könnten also zumindest brechen, wenn etwas nach stdout geschrieben wird. Die Methode besteht im Wesentlichen darin, einen Haltepunkt auf write
zu setzen Systemaufruf mit der Bedingung, dass das erste Argument 1
ist (z. B. STDOUT). In den Kommentaren findet sich auch ein Hinweis, wie man den String-Parameter der write
überprüfen könnte 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 des an write
übergebenen Strings , 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 die Scratch-Register %rdi
geleitet 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 Stack verwenden.
Varianten
Andere Funktionen als strcmp
kann in den obigen Snippets verwendet werden:
strncmp
ist nützlich, wenn Sie den erstenn
abgleichen möchten Anzahl der Zeichen des zu schreibenden Stringsstrstr
kann verwendet werden, um Übereinstimmungen innerhalb einer Zeichenfolge zu finden, da Sie nicht immer sicher sein können, dass die gesuchte Zeichenfolge am Anfang steht der Zeichenfolge, die durchwrite
geschrieben wird Funktion.
Bearbeiten: Ich habe diese Frage genossen und die nachfolgende Antwort gefunden. Ich beschloss, einen Blogbeitrag darüber zu schreiben.
catch
+ strstr
Zustand
Das Coole an dieser Methode ist, dass sie nicht von glibc write
abhängt verwendet wird:es verfolgt den tatsächlichen Systemaufruf.
Außerdem ist es widerstandsfähiger gegenüber printf()
Puffern, da es sogar Zeichenfolgen abfangen kann, die über mehrere printf()
gedruckt werden 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:Unterbrechungen bei:
qwer1
qwer2
fflush
. Der vorherigeprintf
druckte eigentlich nichts, sie wurden gepuffert! Diewrite
syacall ist nur auffflush
aufgetreten .
Hinweise:
$bpnum
danke an Tromey unter:https://sourceware.org/bugzilla/show_bug.cgi?id=18727rdi
:Register, das die Nummer des Linux-Systemaufrufs in x86_64 enthält,1
ist fürwrite
rsi
:erstes Argument des Systemaufrufs, fürwrite
es zeigt auf den Pufferstrstr
:Standard-C-Funktionsaufruf, sucht nach Unterübereinstimmungen, gibt NULL zurück, wenn nicht gefunden
Getestet in Ubuntu 17.10, gdb 8.0.1.
Strich
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 verwenden können, um strace
zu manipulieren ausgegeben, und es erfordert kein tiefes GDB-fu.
Erklärung:
-i
macht strace Ausgabe RIPsetarch -R
deaktiviert ASLR für einen Prozess mit einempersonality
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.
Anthonys Antwort ist großartig. Nach seiner Antwort habe ich eine andere Lösung unter Windows ausprobiert (x86-64-Bit-Windows). Ich weiß, dass diese Frage hier für GDB ist 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 von printf
würde zum Aufruf der API write
führen . 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 WriteFile bereit. 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 schließlich der WriteFile
aufgerufen wurde API und setzen Sie dort einen Haltepunkt. Nach dem Debuggen des Beispielcodes habe ich den printf
gefunden -Methode könnte zu einem Aufruf von _write_nolock
führen wobei 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 zum 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 einen condition breakpoint
setzen in _write_nolock
beziehen Sie sich einfach auf die Lösungen oben, mit nur einigen Unterschieden im Detail.
Portable Lösung für Win32 und x64
Es ist ein großes Glück, dass wir den Namen von Parametern direkt verwenden konnten 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 Haltepunkte in
_write_nolock
hinzuHINWEIS :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 etwas Problemumgehung:Verwenden Sie die Position in der Funktion und nicht den Funktionsnamen, um die Haltepunkte zu setzen, z. B. die 1. Zeile der Funktion. Dort sind die Parameter bereits initialisiert. (Ich meine, benutze den
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. ) -
Bedingung einschränken:
fh == 1 && strstr((char *)buf, "Hello World") != 0
HINWEIS :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 Strings in _write_nolock
schreiben Funktion auf einmal. Jedoch std::cout
würde nur zeichenweise an _write_nolock
übergeben , was bedeutet, dass die API strlen("your string")
heißen würde 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 Anthony
verwenden bereitgestellt:Setzen Sie die Bedingung von Haltepunkten durch Register.
Für ein Win32-Programm ist die Lösung fast dieselbe mit GDB
auf Linux. Sie werden vielleicht bemerken, dass es einen dekorativen __cdecl
gibt 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 Groß-/Kleinschreibung durchgeführt.
Hier gibt es eine Beschreibung. Und es gibt ein Beispiel, das verwendet wird, um die Register und Stacks auf der Microsoft-Website anzuzeigen. Das Ergebnis war hier zu finden.
Dann ist es sehr einfach, die Bedingung von Breakpoints zu setzen:
- Setzen Sie einen Haltepunkt in
_write_nolock
. -
Bedingung einschränken:
*(int *)($esp + 4) == 1 && strstr(*(char **)($esp + 8), "Hello") != 0
Es ist die gleiche Methode wie unter Linux. Die erste Bedingung ist sicherzustellen, dass der String in stdout
geschrieben wird . Der zweite muss mit der angegebenen Zeichenfolge übereinstimmen.
x64-Lösung
Zwei wichtige Änderungen von x86 zu x64 sind die 64-Bit-Adressierungsfähigkeit und ein flacher Satz von 16 64-Bit-Registern für den allgemeinen Gebrauch. Als Erhöhung der Register verwendet x64 nur __fastcall
als Aufrufkonvention. Die ersten vier Integer-Argumente werden in Registern übergeben. Argumente ab fünf werden auf dem Stack übergeben.
Sie können auf der Seite Parameter Passing auf der Microsoft-Website nachsehen. Die vier Register (von links nach rechts) sind RCX
, RDX
, R8
und R9
. Es ist also sehr einfach, die Bedingung einzuschränken:
-
Setzen Sie einen Haltepunkt in
_write_nolock
.HINWEIS :Es unterscheidet sich von der obigen portablen Lösung, wir könnten einfach die Position des Haltepunkts auf die Funktion setzen und nicht auf die 1. Zeile der Funktion. Der Grund ist, dass alle Register bereits am Eingang initialisiert sind.
-
Einschränkungsbedingung:
$rcx == 1 && strstr((char *)$rdx, "Hello") != 0
Der Grund, warum wir auf esp
umwandeln und dereferenzieren müssen ist das $esp
greift auf ESP
zu registrieren und ist in jeder Hinsicht ein void*
. Während die Register hier direkt die Werte von Parametern speichern. Eine weitere Indirektionsebene wird also nicht mehr benötigt.
Posten
Mir macht diese Frage auch sehr viel Spaß, also habe ich Anthonys Post ins Chinesische übersetzt und meine Antwort als Ergänzung hineingelegt. Den Beitrag finden Sie hier. Danke für die Erlaubnis von @anthony-arnold .