1 Punkte von GN⁺ 4 시간 전 | 1 Kommentare | Auf WhatsApp teilen
  • Beim wiederholten Kompilieren und Ausführen eines C-Programms mit entr stürzte hello zwar abnormal ab, aber die Meldung Segmentation fault erschien nach der Compiler-Ausgabe nicht
  • Diese Meldung wird nicht vom abgestürzten Programm selbst ausgegeben, sondern von der übergeordneten Shell, die den Kindprozess einsammelt und dessen Beendigung durch SIGSEGV feststellt
  • Selbst wenn man bash -c explizit angibt, kann Bash, falls effektiv nur noch ein einziger Befehl übrig ist, sich per execve selbst ersetzen, statt einen neuen Prozess zu erzeugen, sodass keine übergeordnete Shell mehr da ist, die die Meldung ausgeben könnte
  • In einem separaten Skript startet die per Shebang aufgerufene Bash ./hello als Kindprozess und wartet darauf, erkennt also SIGSEGV und gibt Segmentation fault (core dumped) aus
  • Führt man ./hello in einer Subshell (./hello) aus oder hängt ; true an, sodass noch Folgearbeit übrig bleibt, lässt sich die exec-Optimierung verhindern und die Fehlermeldung wieder sichtbar machen

Der Ausführungspfad, auf dem die Meldung verschwindet

  • Mit folgendem entr-Befehl werden Kompilierung und Ausführung jedes Mal wiederholt, wenn sich hello.c ändert

    ls hello.c | entr -s "gcc -o hello hello.c && ./hello"
    
  • Obwohl hello mit einem Segmentation Fault beendet wurde, erschienen weder eine Segmentation fault-Meldung noch ein sichtbar von null abweichender Exit-Status — es blieb überhaupt keine Ausgabe zurück

  • Auch wenn man den Befehl mit bash -c umschließt, bleibt das Ergebnis gleich

    ls hello.c | entr -s "bash -c 'gcc -o hello hello.c && ./hello'"
    
  • Verschiebt man denselben Befehl dagegen in run.sh und führt ihn aus, wird der Fehler angezeigt

    #!/bin/bash
    gcc -o hello hello.c && ./hello
    
    ls hello.c | entr -s ./run.sh
    ./run.sh: line 2: 104465 Segmentation fault (core dumped) ./hello
    
  • Wer Segmentation fault eigentlich ausgibt

    • Das abgestürzte Programm ist zu diesem Zeitpunkt bereits beendet; die übergeordnete Shell sammelt den Kindprozess ein und gibt die Meldung erst aus, nachdem sie festgestellt hat, dass er durch SIGSEGV gestorben ist
    • Wenn also keine übergeordnete Shell übrig bleibt, die die Meldung ausgeben kann, erscheint gar nichts
    • In bash -c "some_command" kann Bash, wenn effektiv nur noch genau dieser Befehl auszuführen ist, statt eines Forks sich per execve selbst ersetzen
    • Diese Optimierung bleibt bei den meisten Ausführungen nach außen unsichtbar
    • Beim Skript-Ansatz startet entr ./run.sh als Kindprozess, und der Shebang startet eine neue Bash
    • Diese neue Bash führt nach gcc ein Fork für ./hello aus und wartet darauf; deshalb kann sie dessen Beendigung durch SIGSEGV erkennen und die Fehlermeldung ausgeben

Zwei Wege, die übergeordnete Shell zu erhalten

  • Führt man den abstürzenden Befehl in einer Subshell aus, erscheint die Meldung wieder
    ls hello.c | entr -s "gcc -o hello hello.c && (./hello)"
    hello.c: line 1: 106595 Segmentation fault (core dumped) ( ./hello )
    
    • Die Klammern erzwingen, dass ./hello in einer geforkten Subshell ausgeführt wird
    • Dadurch bleibt Bash in der Elternrolle und kann das Ende der Subshell durch SIGSEGV einsammeln und die Meldung ausgeben
  • Auch wenn nach dem abstürzenden Befehl noch weitere Arbeit übrig bleibt, lässt sich die exec-Optimierung verhindern
    ls hello.c | entr -s "bash -c 'gcc -o hello hello.c && ./hello; true'"
    bash: line 1: 109516 Segmentation fault (core dumped) ./hello
    
    • Wegen ; true hat Bash nach dem Ende von ./hello noch etwas zu tun und ersetzt sich deshalb nicht selbst durch dieses Programm
    • So lässt sich die Segmentation-Fault-Meldung auch ohne separates Wrapper-Skript sichtbar machen

1 Kommentare

 
GN⁺ 4 시간 전
Lobste.rs-Meinungen
  • Bei aktuellen Linux-Distributionen sind Core Dumps standardmäßig deaktiviert, was oft lästig ist
    Mit ulimit -c unlimited kann man sie wieder aktivieren, aber die Core-Datei scheint dann auch nicht im selben Verzeichnis wie die ausführbare Datei zu landen, und es wirkt so, als würde systemd sie irgendwohin verschieben
    Es wäre schön, wenn Core Dumps wie bei OpenBSD standardmäßig aktiviert wären und im selben Verzeichnis wie die ausführbare Datei lägen, aber derzeit muss man bei einem Absturz die ausführbare Datei meist erneut in gdb starten

    • Unter systemd kann man mit coredumpctl die letzten Core Dumps auflisten und abrufen
    • Den Core Dump dort abzulegen, wo sich die ausführbare Datei befindet, wirkt wie eine ziemlich seltsame Entscheidung
      Man würde kaum erwarten, einen Core Dump etwa in /usr/bin zu suchen, und solche Orte liegen oft auf kleinen oder schreibgeschützten Dateisystemen
      Als Standardort wäre das Arbeitsverzeichnis nicht deutlich besser?
    • Der Grund, warum Core-Dateien standardmäßig deaktiviert sind, scheint zu sein, dass Sicherheitsexperten davon ausgehen, dass Benutzer Linux nicht sicher betreiben können und herumliegende Core-Dateien missbraucht werden könnten
      Selbst wenn Benutzer Core-Dateien aktivieren, wird offenbar angenommen, dass sie trotzdem nicht wissen, was sie tun, sodass sie an einem selbst für root schwer auffindbaren Ort versteckt werden
  • Wenn man Programme in C oder D schreibt und wissen will, warum sie beendet wurden, sollte man den Rückgabewert des Systemaufrufs wait prüfen; daran lässt sich erkennen, ob ein Programm durch ein Signal beendet wurde
    Wenn das Betriebssystem Core Dumps an einen anderen Ort weiterleitet, hat das auch den Vorteil, dass man bei Bedarf sofort debuggen oder der Distribution einen Fehlerbericht schicken kann
    Der Linux-Kernel liest beim Erzeugen eines Core Dumps die spezielle Datei /proc/sys/kernel/core_pattern, um den Speicherort festzulegen
    Statt in eine Datei zu schreiben, kann er die Daten auch an ein Programm pipen, sodass etwa ein GUI-Dialog angezeigt, ein Debugger sofort gestartet oder ein Bericht per E-Mail an Entwickler gesendet werden kann
    systemd setzt den core_pattern des Kernels auf einen von ihm verwalteten Wert, aber dieselbe Kernel-Funktion lässt sich auch ohne systemd nutzen
    Es gibt auch die Geschichte, dass während der Entwicklung von Windows XP Core Dumps an Microsoft gesendet wurden und dadurch viele Fehler behoben werden konnten, von deren Existenz man sonst nie erfahren hätte; und wenn auf Windows Entwicklungswerkzeuge installiert sind, ist der bedarfsorientierte Debugger beim Entwickeln eigener Programme recht nützlich
    Diese Funktion wurde 2003 in Linux eingeführt; Details stehen in man 5 core

  • Core Dumps sind fürs Debugging großartig, können normale Benutzer aber verwirren, wenn sie neben scheinbar nicht zusammengehörigen Dateien liegen, und als System für Diagnoseberichte können sie furchtbar sein
    Ich war schockiert, als ich zum ersten Mal sah, wie viele Daten in eingereichten Windows-Absturzberichten enthalten sind
    Wenn man ein datenschutzfreundliches Absturzmeldesystem voraussetzt, muss man lernen, die Ursache allein aus Stack- und Registerinformationen herzuleiten, damit eingereichte Abstürze diagnostiziert werden können

  • Dass Bash Befehle in Skripten manchmal direkt per exec ausführt, wusste ich überhaupt nicht; das ist eine überraschende Funktion
    Da der Overhead des Bash-Prozesses selbst nicht besonders groß ist und ich dort, wo es nötig ist, ohnehin gewohnheitsmäßig exec verwende, wirkt das auf mich wie eine etwas schlecht entworfene Funktion
    Allerdings könnte es in der Summe aller Ausführungen doch einen größeren Unterschied machen, als man erwarten würde