Debuggen der Softwarebereitstellung mit Strace

Debuggen der Softwarebereitstellung mit Strace

Mein Tagesjob besteht hauptsächlich aus der Softwarebereitstellung, was bedeutet, dass ich viel Zeit damit verbringe, Fragen zu beantworten wie:

  • Diese Software funktioniert für den Entwickler, aber nicht für mich. Warum?
  • Gestern hat diese Software bei mir funktioniert, heute jedoch nicht mehr. Warum?

Dies ist eine Art Debugging, die sich geringfügig vom regulären Software-Debugging unterscheidet. Beim regulären Debuggen geht es um die Logik des Codes, beim Deployment-Debugging geht es jedoch um die Interaktion zwischen dem Code und der Umgebung. Auch wenn die Ursache des Problems ein logischer Fehler ist, bedeutet die Tatsache, dass auf einer Maschine alles funktioniert und auf einer anderen nicht, dass das Problem irgendwie in der Umgebung liegt.

Also statt der üblichen Debugging-Tools wie gdb Ich habe andere Tools zum Debuggen der Bereitstellung. Und mein Lieblingstool zur Lösung von Problemen wie „Warum funktioniert diese Software bei mir nicht?“ angerufen strace.

Was ist Strace?

strace ist ein Tool zur „Systemaufrufverfolgung“. Es wurde ursprünglich für Linux erstellt, aber die gleichen Debugging-Tricks können auch mit Tools für andere Systeme durchgeführt werden (DTrace oder ktrace).

Die grundsätzliche Anwendung ist sehr einfach. Sie müssen nur strace mit einem beliebigen Befehl ausführen und alle Systemaufrufe werden gelöscht (obwohl Sie es wahrscheinlich zuerst selbst installieren müssen). strace):

$ strace echo Hello
...Snip lots of stuff...
write(1, "Hellon", 6)                  = 6
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

Was sind diese Systemaufrufe? Dabei handelt es sich um so etwas wie eine API für den Betriebssystemkernel. Es war einmal, dass Software direkten Zugriff auf die Hardware hatte, auf der sie lief. Wenn zum Beispiel etwas auf dem Bildschirm angezeigt werden musste, spielte es mit Ports oder speicherabgebildeten Registern für Videogeräte. Als Multitasking-Computersysteme populär wurden, herrschte Chaos, da verschiedene Anwendungen um die Hardware kämpften. Fehler in einer Anwendung können zum Ausfall anderer, wenn nicht sogar des gesamten Systems führen. Dann erschienen Privilegienmodi (oder „Ringschutz“) in der CPU. Der Kernel wurde zum privilegiertesten Kernel: Er erhielt vollen Zugriff auf die Hardware und brachte weniger privilegierte Anwendungen hervor, die bereits Zugriff vom Kernel anfordern mussten, um über Systemaufrufe mit der Hardware zu interagieren.

Auf binärer Ebene unterscheidet sich ein Systemaufruf geringfügig von einem einfachen Funktionsaufruf, die meisten Programme verwenden jedoch einen Wrapper in der Standardbibliothek. Diese. Die POSIX-C-Standardbibliothek enthält einen Funktionsaufruf schreibe (), das den gesamten architekturspezifischen Code für den Systemaufruf enthält schreiben.

Debuggen der Softwarebereitstellung mit Strace

Kurz gesagt, jede Interaktion zwischen einer Anwendung und ihrer Umgebung (Computersystemen) erfolgt über Systemaufrufe. Wenn also Software auf einem Computer funktioniert, auf einem anderen jedoch nicht, wäre es sinnvoll, sich die Ergebnisse der Systemaufrufverfolgung anzusehen. Genauer gesagt finden Sie hier eine Liste typischer Punkte, die mithilfe eines Systemaufruf-Trace analysiert werden können:

  • Konsolen-E/A
  • Netzwerk-E/A
  • Dateisystemzugriff und Datei-E/A
  • Verwalten der Lebensdauer eines Prozessthreads
  • Speicherverwaltung auf niedriger Ebene
  • Zugriff auf bestimmte Gerätetreiber

Wann sollte Strace verwendet werden?

In der Theorie, strace Wird mit jedem Programm im Benutzerbereich verwendet, da jedes Programm im Benutzerbereich Systemaufrufe durchführen muss. Es funktioniert effizienter mit kompilierten Low-Level-Programmen, funktioniert aber auch mit High-Level-Sprachen wie Python, wenn Sie den zusätzlichen Lärm von Laufzeit und Interpreter vermeiden können.

In seiner ganzen Pracht strace manifestiert sich beim Debuggen von Software, die auf einem Computer gut funktioniert, auf einem anderen jedoch plötzlich nicht mehr funktioniert und vage Meldungen über Dateien, Berechtigungen oder erfolglose Versuche, einige Befehle oder etwas anderes auszuführen, erzeugt ... Schade, aber das ist nicht der Fall lassen sich so gut mit hochrangigen Problemen wie Fehlern bei der Zertifikatsüberprüfung kombinieren. Normalerweise ist hierfür eine Kombination erforderlich stracemanchmal ltrace und übergeordnete Tools (wie das Befehlszeilentool). openssl um das Zertifikat zu debuggen).

Wir verwenden als Beispiel einen eigenständigen Server, die Systemaufrufverfolgung kann jedoch häufig auf komplexeren Bereitstellungsplattformen durchgeführt werden. Sie müssen nur die richtigen Werkzeuge auswählen.

Einfaches Debugging-Beispiel

Nehmen wir an, Sie möchten die fantastische Serveranwendung foo ausführen und erhalten Folgendes:

$ foo
Error opening configuration file: No such file or directory

Anscheinend konnte die von Ihnen geschriebene Konfigurationsdatei nicht gefunden werden. Dies liegt daran, dass Paketmanager beim Kompilieren einer Anwendung manchmal die erwarteten Dateispeicherorte überschreiben. Und wenn Sie der Installationsanleitung für eine Distribution folgen, finden Sie in einer anderen Dateien völlig andere Dateien als erwartet. Das Problem könnte in ein paar Sekunden gelöst werden, wenn in der Fehlermeldung angegeben würde, wo nach der Konfigurationsdatei gesucht werden soll, dies aber nicht der Fall ist. Wo also suchen?

Wenn Sie Zugriff auf den Quellcode haben, können Sie ihn lesen und alles erfahren. Ein guter Backup-Plan, aber nicht die schnellste Lösung. Sie können auf einen Schritt-für-Schritt-Debugger zurückgreifen gdb und sehen Sie, was das Programm macht, aber es ist viel effektiver, ein Tool zu verwenden, das speziell dafür entwickelt wurde, die Interaktion mit der Umgebung zu zeigen: strace.

Abschluss strace mag überflüssig erscheinen, aber die gute Nachricht ist, dass das meiste davon getrost ignoriert werden kann. Es ist oft nützlich, den Operator -o zu verwenden, um Trace-Ergebnisse in einer separaten Datei zu speichern:

$ strace -o /tmp/trace foo
Error opening configuration file: No such file or directory
$ cat /tmp/trace
execve("foo", ["foo"], 0x7ffce98dc010 /* 16 vars */) = 0
brk(NULL)                               = 0x56363b3fb000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=25186, ...}) = 0
mmap(NULL, 25186, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f2f12cf1000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "177ELF2113 3 > 1 260A2 "..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1824496, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2f12cef000
mmap(NULL, 1837056, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f2f12b2e000
mprotect(0x7f2f12b50000, 1658880, PROT_NONE) = 0
mmap(0x7f2f12b50000, 1343488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x22000) = 0x7f2f12b50000
mmap(0x7f2f12c98000, 311296, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16a000) = 0x7f2f12c98000
mmap(0x7f2f12ce5000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b6000) = 0x7f2f12ce5000
mmap(0x7f2f12ceb000, 14336, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f2f12ceb000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f2f12cf0500) = 0
mprotect(0x7f2f12ce5000, 16384, PROT_READ) = 0
mprotect(0x56363b08b000, 4096, PROT_READ) = 0
mprotect(0x7f2f12d1f000, 4096, PROT_READ) = 0
munmap(0x7f2f12cf1000, 25186)           = 0
openat(AT_FDCWD, "/etc/foo/config.json", O_RDONLY) = -1 ENOENT (No such file or directory)
dup(2)                                  = 3
fcntl(3, F_GETFL)                       = 0x2 (flags O_RDWR)
brk(NULL)                               = 0x56363b3fb000
brk(0x56363b41c000)                     = 0x56363b41c000
fstat(3, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x8), ...}) = 0
write(3, "Error opening configuration file"..., 60) = 60
close(3)                                = 0
exit_group(1)                           = ?
+++ exited with 1 +++

Ungefähr die gesamte erste Seite der Ausgabe strace – Dies ist normalerweise eine Vorbereitung auf den Start auf niedrigem Niveau. (Viele Anrufe mmap, mschützen, brk für Dinge wie das Erkennen von Low-Level-Speicher und das Anzeigen dynamischer Bibliotheken.) Tatsächlich während des Debuggens der Ausgabe strace Es ist besser, von ganz zum Ende zu lesen. Unten wird es eine Herausforderung geben schreiben, wodurch eine Fehlermeldung angezeigt wird. Wir schauen nach oben und sehen den ersten fehlerhaften Systemaufruf – den Aufruf öffnen, was einen Fehler auslöst ENOENT („Datei oder Verzeichnis nicht gefunden“) versucht zu öffnen /etc/foo/config.json. Hier sollte sich die Konfigurationsdatei befinden.

Dies war nur ein Beispiel, aber ich würde sagen, 90 % der Zeit, die ich verwende strace, es gibt nichts Schwierigeres als das. Nachfolgend finden Sie eine vollständige Schritt-für-Schritt-Anleitung zum Debuggen:

  • Ärgern Sie sich über eine vage Meldung über einen Systemfehler eines Programms
  • Starten Sie das Programm mit neu strace
  • Suchen Sie die Fehlermeldung in den Trace-Ergebnissen
  • Gehen Sie höher, bis Sie den ersten fehlgeschlagenen Systemaufruf erreichen

Es ist sehr wahrscheinlich, dass der Systemaufruf in Schritt 4 verrät, was schief gelaufen ist.

Tipps

Bevor ich Ihnen ein Beispiel für ein komplexeres Debugging zeige, zeige ich Ihnen einige Tricks für den effektiven Einsatz strace:

Der Mann ist dein Freund

Auf vielen *nix-Systemen kann durch Ausführen eine vollständige Liste der Systemaufrufe an den Kernel abgerufen werden man Systemaufrufe. Sie werden Dinge sehen wie brk(2), was bedeutet, dass durch Ausführen mehr Informationen abgerufen werden können Mann 2 Brk.

Kleiner Rechen: Mann 2 Gabel zeigt mir die Seite für die Shell Gabel() в GNU-libc, was, wie sich herausstellt, durch Aufruf implementiert wird Klon(). Aufrufsemantik Gabel bleibt gleich, wenn Sie ein Programm mit schreiben Gabel(), und führen Sie einen Trace durch – ich werde keine Anrufe finden Gabel, stattdessen wird es welche geben Klon(). Solche Rechen verwirren Sie nur, wenn Sie beginnen, die Quelle mit der Ausgabe zu vergleichen strace.

Verwenden Sie -o, um die Ausgabe in einer Datei zu speichern

strace kann eine umfangreiche Ausgabe generieren, daher ist es oft sinnvoll, Trace-Ergebnisse in separaten Dateien zu speichern (wie im Beispiel oben). Dies trägt auch dazu bei, eine Verwechslung der Programmausgabe mit der Ausgabe zu vermeiden strace in der Konsole.

Verwenden Sie -s, um weitere Argumentdaten anzuzeigen

Möglicherweise ist Ihnen aufgefallen, dass die zweite Hälfte der Fehlermeldung im obigen Beispiel-Trace nicht angezeigt wird. Es ist, weil strace Standardmäßig werden nur die ersten 32 Bytes des Zeichenfolgenarguments angezeigt. Wenn Sie mehr sehen möchten, fügen Sie etwas hinzu wie -s 128 zum Anruf strace.

-y erleichtert das Verfolgen von Dateien, Sockets usw.

„Alles ist Datei“ bedeutet, dass *nix-Systeme alle E/A-Vorgänge mithilfe von Dateideskriptoren durchführen, unabhängig davon, ob dies für eine Datei, ein Netzwerk oder Interprozess-Pipes gilt. Das ist praktisch für die Programmierung, macht es aber schwierig, den Überblick darüber zu behalten, was wirklich passiert, wenn man Gemeinsamkeiten sieht lesen и schreiben in den Systemaufruf-Trace-Ergebnissen.

Durch Hinzufügen eines Operators -y, du wirst zwingen strace Kommentieren Sie jeden Dateideskriptor in der Ausgabe mit einer Notiz darüber, worauf er verweist.

Mit -p** an einen bereits laufenden Prozess anhängen

Wie Sie dem folgenden Beispiel entnehmen können, müssen Sie manchmal ein Programm verfolgen, das bereits ausgeführt wird. Wenn bekannt ist, dass es als Prozess 1337 ausgeführt wird (z. B. aus der Ausgabe). ps), dann können Sie es wie folgt verfolgen:

$ strace -p 1337
...system call trace output...

Möglicherweise benötigen Sie Root-Rechte.

Verwenden Sie -f, um untergeordnete Prozesse zu überwachen

strace Standardmäßig wird nur ein Prozess verfolgt. Wenn dieser Prozess untergeordnete Prozesse erzeugt, ist der Systemaufruf zum Erzeugen des untergeordneten Prozesses sichtbar, die Systemaufrufe des untergeordneten Prozesses werden jedoch nicht angezeigt.

Wenn Sie glauben, dass der Fehler in einem untergeordneten Prozess liegt, verwenden Sie die Anweisung -f, dies ermöglicht die Rückverfolgung. Der Nachteil dabei ist, dass die Ausgabe Sie noch mehr verwirren wird. Wann strace Verfolgt einen Prozess oder einen Thread und zeigt einen einzelnen Strom von Aufrufereignissen an. Wenn mehrere Prozesse gleichzeitig verfolgt werden, kann es sein, dass der Beginn eines Anrufs durch eine Nachricht unterbrochen wird , dann - eine Reihe von Aufrufen für andere Ausführungszweige und erst dann - das Ende des ersten <…Foocall wieder aufgenommen>. Oder teilen Sie alle Trace-Ergebnisse in verschiedene Dateien auf, ebenfalls mithilfe des Operators -ff (Details in leiten auf strace).

Filtern Sie Spuren mit -e

Wie Sie sehen, ist das Ergebnis des Trace ein echter Haufen aller möglichen Systemaufrufe. Flagge -e Sie können den Trace filtern (siehe руководство auf strace). Der Hauptvorteil besteht darin, dass es schneller ist, eine gefilterte Ablaufverfolgung auszuführen als eine vollständige Ablaufverfolgung und anschließend grep`at. Ehrlich gesagt ist es mir fast immer egal.

Nicht alle Fehler sind schlecht

Ein einfaches und häufiges Beispiel ist ein Programm, das an mehreren Stellen gleichzeitig nach einer Datei sucht, ähnlich wie eine Shell, die nach einem Verzeichnis sucht, das eine ausführbare Datei enthält:

$ strace sh -c uname
...
stat("/home/user/bin/uname", 0x7ffceb817820) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/uname", 0x7ffceb817820) = -1 ENOENT (No such file or directory)
stat("/usr/bin/uname", {st_mode=S_IFREG|0755, st_size=39584, ...}) = 0
...

Heuristiken wie „Letzte fehlgeschlagene Anfrage vor der Meldung eines Fehlers“ eignen sich gut zum Auffinden relevanter Fehler. Wie dem auch sei, es ist logisch, ganz am Ende zu beginnen.

C-Programmier-Tutorials können Ihnen helfen, Systemaufrufe zu verstehen.

Standardaufrufe an C-Bibliotheken sind keine Systemaufrufe, sondern nur eine dünne Oberflächenschicht. Wenn Sie also zumindest ein wenig verstehen, wie und was in C zu tun ist, können Sie die Ergebnisse der Systemaufrufverfolgung leichter verstehen. Wenn Sie beispielsweise Probleme beim Debuggen von Aufrufen an Netzwerksysteme haben, schauen Sie sich den gleichen Klassiker an Bijas Leitfaden zur Netzwerkprogrammierung.

Ein komplexeres Debugging-Beispiel

Ich habe bereits gesagt, dass das Beispiel des einfachen Debuggens ein Beispiel dafür ist, womit ich mich bei der Arbeit am häufigsten befassen muss strace. Manchmal ist jedoch eine echte Untersuchung erforderlich, daher finden Sie hier ein reales Beispiel für ein fortgeschritteneres Debugging.

bcron - Aufgabenverarbeitungsplaner, eine weitere Implementierung des *nix-Daemons cron. Es ist auf dem Server installiert, aber wenn jemand versucht, den Zeitplan zu bearbeiten, passiert Folgendes:

# crontab -e -u logs
bcrontab: Fatal: Could not create temporary file

Okay, das heißt bcron hat versucht, eine bestimmte Datei zu schreiben, aber es hat nicht geklappt, und er gibt nicht zu, warum. Aufdeckung strace:

# strace -o /tmp/trace crontab -e -u logs
bcrontab: Fatal: Could not create temporary file
# cat /tmp/trace
...
openat(AT_FDCWD, "bcrontab.14779.1573691864.847933", O_RDONLY) = 3
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f82049b4000
read(3, "#Ansible: logsaggn20 14 * * * lo"..., 8192) = 150
read(3, "", 8192)                       = 0
munmap(0x7f82049b4000, 8192)            = 0
close(3)                                = 0
socket(AF_UNIX, SOCK_STREAM, 0)         = 3
connect(3, {sa_family=AF_UNIX, sun_path="/var/run/bcron-spool"}, 110) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f82049b4000
write(3, "156:Slogs #Ansible: logsaggn20 1"..., 161) = 161
read(3, "32:ZCould not create temporary f"..., 8192) = 36
munmap(0x7f82049b4000, 8192)            = 0
close(3)                                = 0
write(2, "bcrontab: Fatal: Could not creat"..., 49) = 49
unlink("bcrontab.14779.1573691864.847933") = 0
exit_group(111)                         = ?
+++ exited with 111 +++

Ganz am Ende erscheint eine Fehlermeldung schreiben, aber dieses Mal ist etwas anders. Erstens liegt kein relevanter Systemaufruffehler vor, der normalerweise vorher auftritt. Zweitens ist klar, dass irgendwo schon jemand die Fehlermeldung gelesen hat. Es sieht so aus, als ob das eigentliche Problem woanders liegt, und bcrontab spielt die Nachricht einfach ab.

Wenn du siehst Mann 2 gelesen, können Sie sehen, dass das erste Argument (3) ein Dateideskriptor ist, den *nix für die gesamte E/A-Verarbeitung verwendet. Wie finde ich heraus, was Dateideskriptor 3 darstellt? In diesem speziellen Fall können Sie ausführen strace mit Betreiber -y (siehe oben) und es wird Ihnen automatisch Bescheid geben, aber um solche Dinge herauszufinden, ist es nützlich zu wissen, wie man Trace-Ergebnisse liest und analysiert.

Die Quelle eines Dateideskriptors kann einer von vielen Systemaufrufen sein (alles hängt davon ab, wozu der Deskriptor dient – ​​eine Konsole, ein Netzwerk-Socket, die Datei selbst oder etwas anderes), aber wie dem auch sei, wir suchen danach Aufrufe durch Rückgabe von 3 (d. h. wir suchen nach „= 3“ in den Tracing-Ergebnissen). In diesem Ergebnis gibt es 2 davon: öffnen ganz oben und Buchse in der Mitte. öffnen öffnet die Datei aber schließen(3) zeigt dann, dass es wieder schließt. (Rake: Dateideskriptoren können beim Öffnen und Schließen wiederverwendet werden). Anruf Socket () passend, da es das letzte davor ist lesen(), und es stellt sich heraus, dass bcrontab mit etwas über einen Socket funktioniert. Die nächste Zeile zeigt, dass der Dateideskriptor verknüpft ist Unix-Domain-Socket auf dem Weg /var/run/bcron-spool.

Wir müssen also den damit verbundenen Prozess finden Unix-Socket andererseits. Zu diesem Zweck gibt es ein paar nette Tricks, die beide beim Debuggen von Serverbereitstellungen nützlich sind. Die erste ist die Verwendung netstat oder neuer ss (Socket-Status). Beide Befehle zeigen die aktiven Netzwerkverbindungen des Systems an und übernehmen die Anweisung -l Abhörsteckdosen sowie den Bediener zu beschreiben -p um als Client mit dem Socket verbundene Programme anzuzeigen. (Es gibt noch viele weitere nützliche Optionen, aber diese beiden reichen für diese Aufgabe aus.)

# ss -pl | grep /var/run/bcron-spool
u_str LISTEN 0   128   /var/run/bcron-spool 1466637   * 0   users:(("unixserver",pid=20629,fd=3))

Dies deutet darauf hin, dass der Zuhörer der Befehl ist inixserver, läuft mit der Prozess-ID 20629. (Und zufälligerweise verwendet es Dateideskriptor 3 als Socket.)

Das zweite wirklich nützliche Tool zum Auffinden derselben Informationen heißt lsof. Es listet alle geöffneten Dateien (oder Dateideskriptoren) auf dem System auf. Oder Sie erhalten Informationen zu einer bestimmten Datei:

# lsof /var/run/bcron-spool
COMMAND   PID   USER  FD  TYPE  DEVICE              SIZE/OFF  NODE    NAME
unixserve 20629 cron  3u  unix  0x000000005ac4bd83  0t0       1466637 /var/run/bcron-spool type=STREAM

Prozess 20629 ist ein langlebiger Server, sodass Sie ihn anhängen können strace etwas verwenden wie strace -o /tmp/trace -p 20629. Wenn Sie einen Cron-Job in einem anderen Terminal bearbeiten, erhalten Sie eine Trace-Ausgabe mit einem Fehler. Und hier ist das Ergebnis:

accept(3, NULL, NULL)                   = 4
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21181
close(4)                                = 0
accept(3, NULL, NULL)                   = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=21181, si_uid=998, si_status=0, si_utime=0, si_stime=0} ---
wait4(0, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG|WSTOPPED, NULL) = 21181
wait4(0, 0x7ffe6bc36764, WNOHANG|WSTOPPED, NULL) = -1 ECHILD (No child processes)
rt_sigaction(SIGCHLD, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, 8) = 0
rt_sigreturn({mask=[]})                 = 43
accept(3, NULL, NULL)                   = 4
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21200
close(4)                                = 0
accept(3, NULL, NULL)                   = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=21200, si_uid=998, si_status=111, si_utime=0, si_stime=0} ---
wait4(0, [{WIFEXITED(s) && WEXITSTATUS(s) == 111}], WNOHANG|WSTOPPED, NULL) = 21200
wait4(0, 0x7ffe6bc36764, WNOHANG|WSTOPPED, NULL) = -1 ECHILD (No child processes)
rt_sigaction(SIGCHLD, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, 8) = 0
rt_sigreturn({mask=[]})                 = 43
accept(3, NULL, NULL

(Zuletzt accept () (wird beim Tracing nicht abgeschlossen.) Auch dieses Ergebnis enthält leider nicht den gesuchten Fehler. Wir sehen keine Nachrichten, die bcrontag an den Socket sendet oder von diesem empfängt. Stattdessen ist eine vollständige Prozesskontrolle (klonen, warte4, SIGCHLD usw.) Dieser Prozess erzeugt einen untergeordneten Prozess, der, wie Sie sich vorstellen können, die eigentliche Arbeit erledigt. Und wenn Sie ihre Spur verfolgen möchten, ergänzen Sie den Anruf strace -f. Das finden wir, wenn wir mit strace nach der Fehlermeldung im neuen Ergebnis suchen -f -o /tmp/trace -p 20629:

21470 openat(AT_FDCWD, "tmp/spool.21470.1573692319.854640", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EACCES (Permission denied) 
21470 write(1, "32:ZCould not create temporary f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logs:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (No such file or directory)
21470 exit_group(111)                   = ?
21470 +++ exited with 111 +++

Nun, das ist etwas. Prozess 21470 erhält beim Versuch, eine Datei im Pfad zu erstellen, die Fehlermeldung „Zugriff verweigert“. tmp/spool.21470.1573692319.854640 (bezogen auf das aktuelle Arbeitsverzeichnis). Wenn wir nur das aktuelle Arbeitsverzeichnis kennen würden, wüssten wir auch den vollständigen Pfad und könnten herausfinden, warum der Prozess seine temporäre Datei nicht darin erstellen kann. Leider wurde der Prozess bereits beendet, sodass Sie ihn nicht einfach verwenden können lsof -p 21470 Um das aktuelle Verzeichnis zu finden, können Sie aber auch in die entgegengesetzte Richtung vorgehen: Suchen Sie nach PID 21470-Systemaufrufen, die das Verzeichnis ändern. (Wenn es keine gibt, muss PID 21470 sie von seinem übergeordneten Element geerbt haben, und dies ist bereits erledigt lsof -p kann nicht herausgefunden werden.) Dieser Systemaufruf ist chdir (was mit Hilfe moderner Online-Suchmaschinen leicht herauszufinden ist). Und hier ist das Ergebnis der umgekehrten Suche basierend auf den Trace-Ergebnissen bis hin zur Server-PID 20629:

20629 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21470
...
21470 execve("/usr/sbin/bcron-spool", ["bcron-spool"], 0x55d2460807e0 /* 27 vars */) = 0
...
21470 chdir("/var/spool/cron")          = 0
...
21470 openat(AT_FDCWD, "tmp/spool.21470.1573692319.854640", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EACCES (Permission denied) 
21470 write(1, "32:ZCould not create temporary f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logs:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (No such file or directory)
21470 exit_group(111)                   = ?
21470 +++ exited with 111 +++

(Wenn Sie sich verlaufen haben, möchten Sie vielleicht meinen vorherigen Beitrag lesen über *nix-Prozessmanagement und Shells.) Die Server-PID 20629 hat also keine Berechtigung zum Erstellen einer Datei auf dem Pfad erhalten /var/spool/cron/tmp/spool.21470.1573692319.854640. Der Grund dafür sind höchstwahrscheinlich die klassischen Dateisystem-Berechtigungseinstellungen. Lass uns das Prüfen:

# ls -ld /var/spool/cron/tmp/
drwxr-xr-x 2 root root 4096 Nov  6 05:33 /var/spool/cron/tmp/
# ps u -p 20629
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
cron     20629  0.0  0.0   2276   752 ?        Ss   Nov14   0:00 unixserver -U /var/run/bcron-spool -- bcron-spool

Dort ist der Hund begraben! Der Server läuft als Benutzer cron, aber nur root hat die Berechtigung, in das Verzeichnis zu schreiben /var/spool/cron/tmp/. Einfacher Befehl chown cron /var/spool/cron/tmp/ wird machen bcron richtig funktionieren. (Wenn das nicht das Problem wäre, dann wäre der nächstwahrscheinlichste Verdächtige ein Kernel-Sicherheitsmodul wie SELinux oder AppArmor, also würde ich das Kernel-Meldungsprotokoll mit überprüfen dmesg.)

Insgesamt

Systemaufruf-Traces können für einen Anfänger überwältigend sein, aber ich hoffe, ich habe gezeigt, dass sie eine schnelle Möglichkeit zum Debuggen einer ganzen Klasse häufiger Bereitstellungsprobleme darstellen. Stellen Sie sich vor, Sie versuchen, einen Multiprozess zu debuggen bcronmit einem Schritt-für-Schritt-Debugger.

Das Rückwärtsparsen von Trace-Ergebnissen entlang einer Kette von Systemaufrufen erfordert Geschick, aber wie gesagt fast immer die Verwendung strace, erhalte ich einfach das Trace-Ergebnis und suche vom Ende an nach Fehlern. Auf jeden Fall, strace Hilft mir, beim Debuggen viel Zeit zu sparen. Ich hoffe, dass es auch für Sie nützlich sein wird.

Source: habr.com

Kommentar hinzufügen