GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Wie kann ich überwachen, was in den Standardausgabepuffer gesteckt wird und brechen, wenn eine bestimmte Zeichenfolge in der Pipe abgelegt wird?

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 ersten n abgleichen möchten 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 die gesuchte Zeichenfolge am Anfang steht der Zeichenfolge, die durch write 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 vorherige printf druckte eigentlich nichts, sie wurden gepuffert! Die write syacall ist nur auf fflush aufgetreten .

Hinweise:

  • $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, für 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.

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 RIP
  • setarch -R deaktiviert ASLR für einen Prozess mit einem 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.

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:

  1. Fügen Sie Haltepunkte in _write_nolock hinzu

    HINWEIS :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. )

  2. 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:

  1. Setzen Sie einen Haltepunkt in _write_nolock .
  2. 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:

  1. 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.

  2. 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 .


Linux
  1. Wie überwacht man den TCP-Verkehr zwischen dem Localhost und der IP-Adresse?

  2. So finden Sie heraus, welche ausführbaren Dateien der dynamischen Bibliotheken beim Ausführen geladen werden

  3. Wie kann ich die Größe von Dateien und Verzeichnissen in Linux sehen?

  4. Wie kann ich tcpdump in eine Datei schreiben und die entsprechenden Daten standardmäßig ausgeben lassen?

  5. Wie kann ich die IP- und Gateway-Adressen dauerhaft ändern?

So finden Sie eine bestimmte Zeichenfolge oder ein bestimmtes Wort in Dateien und Verzeichnissen

So finden Sie heraus, wer das Linux-System wann neu gestartet hat

So überwachen Sie den Fortschritt von Daten durch eine Pipe mit dem Befehl „pv“.

Wie kann die Nutzung des für den Kernel dedizierten Speichers überwacht werden?

Was bedeutet * * * im Befehl traceroute zu haben? und Wie können Sie mit dieser Situation umgehen?

Wie kann ich mit Bash den Durchschnitt, das Maximum und das Minimum aus einer Liste von Zahlen herausfinden?