2010-12-15 11 views
10

Nach den Kommentaren zu einer meiner Fragen bin ich gespannt, was passiert, wenn man eine ausführbare Datei überschreibt. Ich muss mein Verständnis in dieser Angelegenheit überprüfen.Was passiert, wenn Sie eine speicherprogrammierbare ausführbare Datei überschreiben?

Sag ich habe /usr/bin/myprog. Ich betreibe es und so lädt das OS /usr/bin/myprog, wahrscheinlich über http://en.wikipedia.org/wiki/Memory-mapped_file#Common_uses.

Aus welchem ​​Grund auch immer dieser Prozess im Speicher bleibt und ich entscheide tatsächlich habe ich einen Fehler behoben und überschreiben /usr/bin/myprog.

Also, soweit ich es verstehe:

  • Wenn eine Instanz von myprog bereits geladen ist und ich ersetzen Sie die Datei, von der myprog bereits geladen wurde, ist die Instanz von myprog unverändert.
  • Wenn ich eine neue Instanz von myprog ausführen, wird es den neuen Code verwenden.

Bin ich richtig?

Gemäß dem Artikel zu Memory-Mapped-Dateien ermöglicht eine solche Technik einem Entwickler jedoch, Teile einer Datei so zu behandeln, als wären sie physischer Speicher.

So sehe ich einen Widerspruch darin, wie ich die Dinge verstand. Wenn Seiten wirklich nur bei Bedarf geladen werden, dann wird unter der Annahme, dass myprog nicht zu 100% ausgelagert ist, dieser Wikipedia-Artikel impliziert, dass neue Seiten von der Datei auf der Platte geladen werden, die seit dem Laden des Originalbildes geändert wurde.

Ich bin mir jedoch ziemlich sicher, dass meine beiden kompilierten Bilder nicht die gleichen wären und dass die relevanten Adressoffsets für jede Datei nicht identisch sind. Unter der Annahme, dass dies der Fall ist, wird der Befehlszeiger sehr verloren gehen ... Ich bin ziemlich sicher, dass ein Betriebssystem Teile von zwei verschiedenen Bildern nicht als Teil des gleichen Prozesses in den Speicher lädt.

Wie also funktioniert die Kombination von Memory-Mapping/Demand-Paging für die Ausführung von Programmen, bitte? Würde das Überschreiben dieser Datei einen Seitenfehler auf jeder der ausführbaren Seiten auslösen, um sicherzustellen, dass sie für den gerade laufenden Prozess geladen ist?

habe ich einen schnellen Versuch mit diesem:

#include <stdio.h> 
#include <unistd.h> 

int main(int argc, char** argv) 
{ 
    printf("Program resident..."); 
    while(1) 
    { 
     printf("??? Just notifying you I'm still here...\n"); 
     usleep(1000000); 
    } 

    return 0; 
} 

Und sicher genug, um a) ersetzen diese ausführbare Datei konnte ich während es ausgeführt wurde und b) seine Ausgabe nicht verändert wird.

SO was ist los? Ich würde mich über Anregungen freuen, was ich tun kann, um zu sehen, was passiert (Linux oder Windows).

Vielen Dank.

Edit: Frage, auf die ich bezog, dass diese Frage ausgelöst: Upgrades without reboot - what kinds of problems happen in practice?

Auch ich bin mir bewusst, dies speziell bezieht sich nicht auf die Programmierung, aber das Ergebnis eine ausführbare Datei zu aktualisieren. Ich bin jedoch immer noch interessiert und ich kann mir keinen besseren Ort dafür vorstellen.

Antwort

8

Wenn Sie unter Linux eine ausführbare Datei ersetzen, während sie ausgeführt wird, sind die Ergebnisse unvorhersehbar und können abstürzen. Seiten, die modifiziert wurden (z. B. "initialisierte Daten"), werden nicht beeinflusst, aber Seiten, die nicht modifiziert wurden (z. B. der meiste Code).

Meine Vermutung ist, dass in Ihrem Fall die Zeichenfolge in einem Teil war, der eine modifizierte (kopierte) Seite war, so wurde nicht beeinflusst.

Das passiert jedoch nur, wenn Sie tatsächlich die gleiche Datei überschreiben.

Wenn Sie eine ausführbare Datei ersetzen, ersetzen Sie den Verzeichniseintrag in den meisten Fällen durch eine andere Datei. Dies geschieht normalerweise durch Umbenennen einer temporären Datei (im selben Verzeichnis) über die bestehende. Das tun beispielsweise Paketmanager.

Im Fall des Ersetzens von Verzeichniseinträgen besteht die vorherige ausführbare Datei weiterhin als vollständig separate (noch ausführbare) Datei, und die vorherige ausführbare Datei kann ohne Probleme verworfen und neu geladen werden - sie sieht immer noch das alte Datei.

Ganz was der Linker mit seiner Ausgabe macht, weiß ich nicht. Aber/usr/bin/install erstellt eine neue Datei. Ich erwarte, dass dieses Verhalten ziemlich bewusst ist.

+0

Ich verstehe. Paketmanager sind sich bewusst, dass der Prozess ... interessant ist. Gut. Ich denke, das beantwortet es, kombiniert mit Teds Antwort weiß ich jetzt viel mehr darüber, was tatsächlich passiert. –

+0

Für das, was es wert ist, gilt dies auch für OS X (und BSD im Allgemeinen) und ist in posix gut definiert, siehe "unlink (2)". – elslooo

10
  1. Was hängt davon ab, in erster Linie geschieht, ob Sie rm /usr/bin/myprog und dann einen neuen erstellen, oder ob Sie open() und write() zum bestehenden /usr/bin/myprog.

  2. Wenn Sie die alte /usr/bin/myprog Datei rm und dann einen neuen mit dem gleichen Namen erstellen, dann dem Kern/Dateisystemtreiber gibt die neue Version einen neuen inode, und die alte Inode bleibt herumliegen im /proc Dateisystem bis Der Prozess, der es geöffnet hat, schließt es. Ihr vorhandener Prozess /usr/bin/myprog hat seine eigene private Version der Datei, unverändert, bis sie den Dateideskriptor close() hat.

  3. Alle Betriebssysteme (Windows, Linux, wahrscheinlich OS X) verwenden Bedarf ausgelagerten Speicher-Mapping (mmap() für Posix, kann ich nicht das Äquivalent für Windows erinnern - VirtualAlloc()) für die Prozessbelastung. Auf diese Weise werden alle Abschnitte der ausführbaren Datei, die nicht berührt werden, niemals in den Speicher geladen.

  4. Wenn dies eine herkömmliche mmap() ‚d war Datei und zwei Prozesse sowohl geöffnet/kartiert es, und keiner von ihnen angegebenen MAP_PRIVATE (dh copy-on-write) in dem Aufruf von mmap(), dann wird die beiden Prozesse im Wesentlichen auf die gleiche Seite des physikalischen Speichers schauen und beide mit der Bezeichnung mmap() mit PROT_READ | PROT_WRITE, sie werden sich gegenseitig Modifikationen sehen.

  5. Wenn dies eine konventionelle mmap() ‚d-Datei und Prozess 1 hatte geöffnet/abgebildet, und dann Prozess 2 begann mit der Datei auf der Festplatte herumzudaddeln sich durch write() Anrufe (nicht meine mmap ing) , Prozess 1 sieht diese Änderungen tatsächlich. Ich denke, der Kernel bemerkt, dass die Datei geändert wird und lädt die betroffenen Seiten neu.

  6. Ich weiß nicht genau, ob es spezielle mmap() Verhalten für ausführbare Bilder gibt? Wenn ich einen Zeiger auf eine meiner Funktionen hackte und den Code änderte, würde er die Seite als schmutzig markieren? Würde die schmutzige Seite zurück auf /usr/bin/myprog geschrieben werden? Wenn ich dies versuche, segfaults, also denke ich, dass, während _TEXT Seiten mit MAP_SHARED zugeordnet werden, sie auch nicht PROT_WRITE und daher segfault erhalten, wenn sie geschrieben werden. _DATA-Sektionen werden natürlich auch in den Speicher geladen, und diese müssen modifiziert werden, aber diese können als MAP_PRIVATE (Copy-on-Write) markiert werden - also würden sie wahrscheinlich ihre Verbindung zur Datei /usr/bin/myprog nicht behalten.

  7. Punkt 6 betrifft die ausführbare Datei, die sich direkt ändert. Punkt 5 betrifft das Ändern einer beliebigen Datei mmap() d unter der Ebene write(). Wenn ich versuche, eine ausführbare Datei (das ist mmap() 'd) in einem anderen Prozess mit write() zu ändern, bekomme ich nicht die gleichen Ergebnisse wie in Punkt 5. Ich kann alle Arten von schrecklichen Änderungen an der ausführbaren Datei mit bloßen write() Aufrufe, und nichts passiert. Dann, wenn ich den Prozess verlasse und versuche, es erneut auszuführen, stürzt es ab (natürlich - nach allem, was ich getan habe, um die ausführbare Datei). Das verwirrt mich. Ich kann die Parameter nicht auf mmap() permutieren, damit sie sich so verhält - nicht beim Schreiben, aber nicht von Änderungen an der zugeordneten Datei.

  8. Nun, ich ging zurück zur Bibel (Stevens) und das große Problem ist MAP_PRIVATE vs MAP_SHARED.MAP_PRIVATE ist Copy-on-Write und MAP_SHARED ist nicht. MAP_PRIVATE erstellt eine Kopie der zugeordneten Seite, sobald Sie Änderungen daran vornehmen. Es ist nicht definiert, ob eine Änderung an der Originaldatei auf die zugeordneten MAP_PRIVATE Seiten übertragen wird, bei OS X jedoch nicht. MAP_SHARED verwaltet die Verbindung zur Originaldatei, sodass Aktualisierungen der Datei auf die Speicherseiten übertragen werden können und umgekehrt. Wenn ein Speicherblock MAP_PRIVATE zugeordnet ist, wird keine Änderung, die Sie daran vornehmen, jemals auf die Festplatte geschrieben. MAP_SHARED OTOH ermöglicht Änderungen an der Datei durch Schreiben auf die zugeordneten Seiten.

  9. Der Image Loader ordnet ausführbare Dateien als MAP_PRIVATE zu. Dies erklärt das Verhalten in Punkt 6 - einen Zeiger auf den Code einer Funktion zu hacken und ihn dann zu ändern, selbst wenn Sie dazu berechtigt wären, würden die Daten nicht zurück auf den Datenträger schreiben. Theoretisch sollte es möglich sein, die ausführbare Datei /usr/bin/myprog unmittelbar nach dem OS Image Loader mmap() zu ändern, aber wenn ich sehr große ausführbare Dateien mit vmmap betrachte, scheint ihre TEXT-Sektion immer vollständig resident zu sein. Ich weiß nicht, ob dies daran liegt, dass der Image Loader von OS X alle Seiten berührt, um sicherzustellen, dass sie kopiert werden, oder ob der Seiten-Manager von OS X sehr aggressiv ist, Seiten resident zu machen (ist es), aber ich nicht war in der Lage, eine ausführbare Datei in OS X zu erstellen, deren TEXT-Abschnitt nicht vollständig resident war, sobald main() gestartet wurde.

  10. Der OS X-Image-Loader ist sehr aggressiv beim Laden von zugeordneten Seiten. Ich habe festgestellt, dass eine Datei, wenn sie eine Datei enthält, sehr groß sein muss, bevor OS X beschließt, eine davon nicht resident zu lassen. Eine 1-GB-Datei wird vollständig geladen, aber nur etwa 1,7 GB einer 3-GB-Datei werden resident gemacht. Dies ist auf einem Computer mit 8 GB physischem RAM und dem Ausführen des 64-Bit OS X-Kernels.

+0

+1 für die Mühe von mir denke ich. +2 wenn ich es geben könnte. Ich bin auch verwirrt; Ich kann noch keine vernünftige Erklärung dafür finden, was vor sich geht. Ich würde die gleichen Dinge erwarten - ich weiß, dass ich/usr/bin/myprog neu kompilieren kann und was auch immer ich mache, spiegelt sich nicht in dem, was läuft, aber alles deutet auf Last-auf-Nachfrage hin. Ich habe durch den Linux-Baum geschaut, um zu sehen, ob ich herausfinden kann, was passiert (wenn eine Kopie gemacht wird usw.). –

0

Sie sind in der Regel korrekt: Binärdatei speichert nur ein Bild der ausführbaren Datei. Was tatsächlich zu einem bestimmten Zeitpunkt im Speicher gespeichert ist, ist eine Kopie.

Aber diese Bilder haben normalerweise mehrere Abschnitte - oder Segmente. Einer von ihnen speichert die tatsächlichen Maschinenanweisungen, den kompilierten Code Ihres Programms - und es wird immer vollständig geladen. Andere Abschnitte können statische Daten enthalten - verschiedene Konstanten (insbesondere Zeichenfolgen), die Sie wahrscheinlich im gesamten Code verwendet haben. Diese Abschnitte können vom Betriebssystem bei Bedarf geladen werden.

1

Wenn ich unter Mac OS X eine laufende Anwendung aktualisiere, fordert das Aktualisierungsprogramm die vollständige Herunterfahren der Anwendung für das Upgrade, in einigen Fällen wird es fortgesetzt, aber Upgrades werden erst wirksam Anwendungsneustart. Es listet manchmal die Namen der verwendeten Bibliotheken auf, die das Upgrade blockieren. Ich sage das, weil die Anwendung scheint, die Bibliotheken zu sperren, auf denen es abhängt, und das Aktualisierungsprogramm scheint zu wissen, sie nicht zu berühren, wenn sie verwendet werden. Also nicht über das Schreiben eines Teils oder einer ganzen laufenden Anwendung.

Google fragt zum Beispiel nach einem Neustart, damit Upgrades installiert werden und unter Windows, Linux und Mac OS X wirksam werden. Ich hoffe, dass Sie damit einen Anhaltspunkt erhalten, wo Sie anfangen sollten.

2

Ich fand diesen Link eine viel prägnantere Erklärung. Sehen Sie sich den Teil unter "Update" an, in dem der Autor seinen ursprünglichen Beitrag aktualisiert hat.

http://it.toolbox.com/blogs/locutus/why-linux-can-be-updated-without-rebooting-12826

Die gesamte Datei in den Speicher nicht geladen wird, sie in die Puffer in Cluster gelesen werden (was ein technischer Begriff ist, es ist in der Regel 4k, aber Sie können es, wenn Sie Ihre Dateisysteme einrichten).

Wenn Sie eine Datei öffnen, folgt der Kernel dem Link und weist dem Inode einen Dateideskriptor zu (eine Nummer, die intern nachverfolgt wird). Wenn Sie die Datei löschen, lösen Sie die Verknüpfung zum Inode. Der Dateideskriptor zeigt immer noch darauf hin. Sie können eine neue Datei mit exakt demselben Namen wie die alte Datei erstellen, nachdem Sie sie gelöscht haben. Dadurch wird die Datei "ersetzt", aber sie zeigt auf einen anderen Inode. Alle Programme, die noch über die alte Datei verfügen, können weiterhin über den Dateideskriptor auf die alte Datei zugreifen, haben das Programm jedoch effektiv aktualisiert. Sobald das Programm beendet wird (oder die Datei schließt) und startet (oder versucht, erneut darauf zuzugreifen), greift es auf die neue Datei zu, und dort haben Sie es, eine vollständig in-Ort-Ersetzung einer Datei!

Also, in Linux, die ausführbare Datei nur von Seite auf Anfrage gelesen werden kann, wie Sie gesagt hat, aber es wird über die ursprüngliche offene Dateihandle und nicht die aktualisierte Inode der neuen Datei zu lesen, dass Ihr ausführbares Programm ersetzt .