Ändern der Binärdatei während der Ausführung

10

Ich stoße beim Entwickeln oft auf die Situation, in der ich eine Binärdatei ausführe, etwa a.outim Hintergrund, da diese einen längeren Job erledigt. Währenddessen nehme ich Änderungen am C-Code vor, der erstellt a.outund a.outerneut kompiliert wurde. Bisher hatte ich damit keine Probleme. Der ausgeführte Prozess wird a.outnormal fortgesetzt, stürzt nie ab und führt immer den alten Code aus, von dem aus er ursprünglich gestartet wurde.

Es handelte sich jedoch a.outum eine riesige Datei, die möglicherweise mit der Größe des Arbeitsspeichers vergleichbar ist. Was würde in diesem Fall passieren? Und sagen wir, es ist mit einer gemeinsam genutzten Objektdatei verknüpft. libblas.soWas passiert, wenn ich es zur libblas.soLaufzeit geändert habe ? Was würde passieren?

Meine Hauptfrage ist - hat das Betriebssystem gewährleistet , dass , wenn ich laufe a.out, dann der ursprüngliche Code immer normal ausgeführt wird, gemäß der ursprünglichen binär, unabhängig von der Größe der binären oder .soDateien , die es Links zu, auch wenn diese .ound .soDateien werden modfied während Laufzeit?

Ich weiß, dass es diese Fragen gibt, die ähnliche Probleme angehen : /programming/8506865/when-a-binary-file-runs-does-it-copy-its-entire-binary-data-into-memory -at-einmal Was passiert, wenn Sie ein Skript während der Ausführung bearbeiten? Wie ist es möglich, ein Live-Update durchzuführen, während ein Programm ausgeführt wird?

Das hat mir geholfen, ein bisschen mehr darüber zu verstehen, aber ich glaube nicht, dass sie genau fragen, was ich will, was eine allgemeine Regel für die Konsequenzen der Änderung einer Binärdatei während der Ausführung ist

Texasflood
quelle
Für mich bieten die von Ihnen verknüpften Fragen (insbesondere die Frage zum Stapelüberlauf) bereits eine wichtige Hilfe zum Verständnis dieser Konsequenzen (oder deren Fehlen). Da der Kernel Ihr Programm in Speichertextbereiche / -segmente lädt , sollte es nicht von Änderungen betroffen sein, die über das Dateisubsystem vorgenommen werden.
John WH Smith
@JohnWHSmith Auf Stackoverflow heißt es in der Top-Antwort if they are read-only copies of something already on disc (like an executable, or a shared object file), they just get de-allocated and are reloaded from their source, also habe ich den Eindruck, dass wenn Ihre Binärdatei riesig ist, wenn ein Teil Ihrer Binärdatei aus dem RAM geht, aber dann wieder benötigt wird, sie "von der Quelle neu geladen" wird - also alle Änderungen in Die .(s)oDatei wird während der Ausführung wiedergegeben. Aber natürlich kann ich falsch verstanden haben - weshalb ich diese spezifischere Frage stelle
Texasflood
@ JohnWHSmith Auch die zweite Antwort besagt, dass No, it only loads the necessary pages into memory. This is demand paging.ich tatsächlich den Eindruck hatte, dass das, wonach ich gefragt habe, nicht garantiert werden kann.
Texasflood

Antworten:

11

Obwohl die Frage zum Stapelüberlauf zunächst ausreichend zu sein schien, verstehe ich aus Ihren Kommentaren, warum Sie möglicherweise noch Zweifel daran haben. Für mich ist dies genau die Art von kritischer Situation , in der die beiden UNIX-Subsysteme (Prozesse und Dateien) kommunizieren.

Wie Sie vielleicht wissen, werden UNIX-Systeme normalerweise in zwei Subsysteme unterteilt: das Dateisubsystem und das Prozesssubsystem. Sofern dies nicht durch einen Systemaufruf anders angegeben wird, sollte der Kernel diese beiden Subsysteme nicht miteinander interagieren lassen. Es gibt jedoch eine Ausnahme: das Laden einer ausführbaren Datei in die Textbereiche eines Prozesses . Natürlich kann man argumentieren, dass diese Operation auch durch einen Systemaufruf ( execve) ausgelöst wird , aber dies ist normalerweise der einzige Fall, in dem das Prozesssubsystem eine implizite Anforderung an das Dateisubsystem sendet.

Da das Prozesssubsystem natürlich keine Möglichkeit hat, Dateien zu verarbeiten (andernfalls wäre es sinnlos, das Ganze in zwei Teile zu teilen), muss es alles verwenden, was das Dateisubsystem für den Zugriff auf Dateien bereitstellt. Dies bedeutet auch, dass das Prozesssubsystem jeder Maßnahme unterzogen wird, die das Dateisubsystem hinsichtlich der Ausgabe / Löschung von Dateien ergreift. In diesem Punkt würde ich empfehlen, Gilles 'Antwort auf diese U & L-Frage zu lesen . Der Rest meiner Antwort basiert auf dieser allgemeineren von Gilles.

Das erste, was beachtet werden sollte, ist, dass auf Dateien intern nur über Inodes zugegriffen werden kann . Wenn dem Kernel ein Pfad zugewiesen wird, besteht sein erster Schritt darin, ihn in einen Inode zu übersetzen, der für alle anderen Operationen verwendet wird. Wenn ein Prozess eine ausführbare Datei in den Speicher lädt, erfolgt dies über den Inode, der vom Dateisubsystem nach der Übersetzung eines Pfads bereitgestellt wurde. Inodes können mehreren Pfaden (Links) zugeordnet sein, und Programme können nur Links löschen. Um eine Datei und ihren Inode zu löschen, muss userland alle vorhandenen Links zu diesem Inode entfernen und sicherstellen, dass sie vollständig nicht verwendet wird. Wenn diese Bedingungen erfüllt sind, löscht der Kernel die Datei automatisch von der Festplatte.

Wenn Sie sich den Teil zum Ersetzen ausführbarer Dateien in Gilles 'Antwort ansehen, werden Sie feststellen , dass der Kernel je nachdem, wie Sie die Datei bearbeiten / löschen , unterschiedlich reagiert / sich anpasst, immer über einen im Dateisubsystem implementierten Mechanismus.

  • Wenn Sie Strategie 1 ausprobieren ( Öffnen / Abschneiden auf Null / Schreiben oder Öffnen / Schreiben / Abschneiden auf neue Größe ), werden Sie feststellen, dass der Kernel Ihre Anfrage nicht bearbeitet. Sie erhalten eine Fehlermeldung 26: Textdatei beschäftigt ( ETXTBSY). Keine Konsequenzen.
  • Wenn Sie Strategie zwei versuchen, besteht der erste Schritt darin, Ihre ausführbare Datei zu löschen. Da es jedoch von einem Prozess verwendet wird, wird das Dateisubsystem aktiviert und verhindert, dass die Datei (und ihr Inode) wirklich von der Festplatte gelöscht wird. Ab diesem Zeitpunkt besteht die einzige Möglichkeit, auf den Inhalt der alten Datei zuzugreifen, darin, dies über den Inode zu tun. Dies geschieht, wenn das Prozesssubsystem neue Daten in Textabschnitte laden muss (intern ist es sinnlos, Pfade zu verwenden, außer bei der Übersetzung in Inodes). Auch wenn Sie nicht verbunden sindWenn die Datei alle Pfade entfernt hat, kann der Prozess sie weiterhin verwenden, als hätten Sie nichts getan. Das Erstellen einer neuen Datei mit dem alten Pfad ändert nichts: Die neue Datei erhält einen völlig neuen Inode, von dem der laufende Prozess nichts weiß.

Die Strategien 2 und 3 sind auch für ausführbare Dateien sicher: Obwohl das Ausführen von ausführbaren Dateien (und dynamisch geladenen Bibliotheken) keine offenen Dateien im Sinne eines Dateideskriptors sind, verhalten sie sich sehr ähnlich. Solange ein Programm den Code ausführt, bleibt die Datei auch ohne Verzeichniseintrag auf der Festplatte.

  • Strategie drei ist ziemlich ähnlich, da mves sich um eine atomare Operation handelt. Dies erfordert wahrscheinlich die Verwendung des renameSystemaufrufs. Da Prozesse im Kernelmodus nicht unterbrochen werden können, kann nichts diesen Vorgang stören, bis er abgeschlossen ist (erfolgreich oder nicht). Auch hier gibt es keine Änderung des Inodes der alten Datei: Es wird ein neuer erstellt, und bereits ausgeführte Prozesse wissen nichts davon, selbst wenn er mit einem der Links des alten Inodes verknüpft ist.

Bei Strategie 3 wird beim Verschieben der neuen Datei in den vorhandenen Namen der Verzeichniseintrag entfernt, der zum alten Inhalt führt, und ein Verzeichniseintrag erstellt, der zum neuen Inhalt führt. Dies geschieht in einer atomaren Operation, daher hat diese Strategie einen großen Vorteil: Wenn ein Prozess die Datei zu einem beliebigen Zeitpunkt öffnet, wird entweder der alte oder der neue Inhalt angezeigt - es besteht kein Risiko, dass gemischte Inhalte oder die Datei nicht angezeigt werden bestehender.

Neukompilieren einer Datei : Wenn Sie verwenden gcc(und das Verhalten ist wahrscheinlich für viele andere Compiler ähnlich), verwenden Sie Strategie 2. Sie können dies sehen, indem Sie einen straceder Prozesse Ihres Compilers ausführen:

stat("a.out", {st_mode=S_IFREG|0750, st_size=8511, ...}) = 0
unlink("a.out") = 0
open("a.out", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3
chmod("a.out", 0750) = 0
  • Der Compiler erkennt durch die Systemaufrufe statund lstat, dass die Datei bereits vorhanden ist .
  • Die Datei ist nicht verbunden . Während der Zugriff über den Namen nicht mehr möglich ist a.out, verbleiben Inode und Inhalt auf der Festplatte, solange sie von bereits ausgeführten Prozessen verwendet werden.
  • Eine neue Datei wird erstellt und unter dem Namen ausführbar gemacht a.out. Dies ist eine brandneue Inode und brandneue Inhalte, die bereits laufende Prozesse nicht interessieren.

Wenn es nun um gemeinsam genutzte Bibliotheken geht, gilt das gleiche Verhalten. Solange ein Bibliotheksobjekt von einem Prozess verwendet wird, wird es nicht von der Festplatte gelöscht, unabhängig davon, wie Sie seine Links ändern. Immer wenn etwas in den Speicher geladen werden muss, führt der Kernel dies über den Inode der Datei durch und ignoriert daher die Änderungen, die Sie an den Links vorgenommen haben (z. B. das Verknüpfen mit neuen Dateien).

John WH Smith
quelle
Fantastische, detaillierte Antwort. Das erklärt meine Verwirrung. Ich gehe also zu Recht davon aus, dass sich die Daten aus der ursprünglichen Binärdatei noch auf der Festplatte befinden, da der Inode noch verfügbar ist. Daher dfist es falsch, die Anzahl der freien Bytes auf der Festplatte zu ermitteln, da keine Inodes benötigt werden Wurden alle entfernten Dateisystem-Links berücksichtigt? Also sollte ich verwenden df -i? (Dies ist nur eine technische Kuriosität, ich muss die genaue Festplattennutzung nicht wirklich kennen!)
texasflood
1
Nur um dies für zukünftige Leser zu verdeutlichen - meine Verwirrung war, dass ich bei der Ausführung dachte, die gesamte Binärdatei würde in den RAM geladen. Wenn also der RAM klein wäre, würde ein Teil der Binärdatei den RAM verlassen und müsste von der Festplatte neu geladen werden - was der Fall wäre Probleme verursachen, wenn Sie die Datei geändert haben. Die Antwort hat jedoch deutlich gemacht, dass die Binärdatei nie wirklich von der Festplatte entfernt wird, selbst wenn Sie rmoder mvsie als Inode zur Originaldatei nicht entfernt werden, bis alle Prozesse ihre Verknüpfung zu dieser Inode entfernen.
Texasflood
@texasflood Genau. Sobald alle Pfade entfernt wurden, kann kein neuer Prozess ( dfenthalten) Informationen über den Inode erhalten. Alle neuen Informationen, die Sie finden, beziehen sich auf die neue Datei und den neuen Inode. Der Hauptpunkt hierbei ist, dass das Prozesssubsystem kein Interesse an diesem Problem hat, sodass die Begriffe der Speicherverwaltung (Anforderungs-Paging, Prozessaustausch, Seitenfehler usw.) völlig irrelevant sind. Dies ist ein Problem mit dem Dateisubsystem, das vom Dateisubsystem behoben wird. Das Prozess-Subsystem kümmert sich nicht darum, dafür ist es nicht da.
John WH Smith
@texasflood Ein Hinweis zu df -i: Dieses Tool ruft wahrscheinlich Informationen aus dem Superblock oder dem Cache des fs ab, was bedeutet, dass es möglicherweise den Inode der alten Binärdatei enthält (für die alle Links gelöscht wurden). Dies bedeutet jedoch nicht, dass neue Prozesse diese alten Daten verwenden können.
John WH Smith
2

Nach meinem Verständnis würde der Kernel aufgrund der Speicherzuordnung eines laufenden Prozesses keine Aktualisierung eines reservierten Teils der zugeordneten Datei zulassen. Ich denke, falls ein Prozess ausgeführt wird, ist die gesamte Datei reserviert, sodass die Aktualisierung, da Sie eine neue Version Ihrer Quelle kompiliert haben, tatsächlich zur Erstellung eines neuen Satzes von Inodes führt. Kurz gesagt, die älteren Versionen Ihrer ausführbaren Datei bleiben über Seitenfehlerereignisse auf der Festplatte verfügbar. Selbst wenn Sie eine große Datei aktualisieren, sollte diese weiterhin verfügbar sein und der Kernel sollte die unberührte Version so lange sehen, wie der Prozess ausgeführt wird. Die ursprünglichen Datei-Inodes sollten nicht wiederverwendet werden, solange der Prozess ausgeführt wird.

Dies muss natürlich bestätigt werden.


quelle
2

Dies ist beim Ersetzen einer JAR-Datei nicht immer der Fall. Jar-Ressourcen und einige Lader zur Laufzeitreflexionsklasse werden erst von der Festplatte gelesen, wenn das Programm die Informationen explizit anfordert.

Dies ist nur ein Problem, da ein JAR lediglich ein Archiv ist und keine einzelne ausführbare Datei, die dem Speicher zugeordnet wird. Dies ist etwas off-stopic, aber immer noch ein Ableger Ihrer Frage und etwas, mit dem ich mir in den Fuß geschossen habe.

Also für ausführbare Dateien: ja. Für JAR-Dateien: möglicherweise (abhängig von der Implementierung).

Zhro
quelle