Wie kann der Aufruf eines Funktionszeigers, der von einer vorherigen Ausführung gespeichert wurde, fehlschlagen?

8

Ich war neugierig, ob Funktionszeiger in einer Datei gespeichert und zu einem späteren Zeitpunkt verwendet werden könnten, wenn das Programm beendet und neu gestartet wurde. Zum Beispiel sah mein erstes Testprogramm so aus wie dieser Pseudocode:

%Vor%

Das ist nur die Logik dessen, was ich tun wollte. Ich würde es mit der Eingabe 1 starten, es beenden lassen und es mit der Eingabe 2 erneut ausführen. Betrachten Sie das nicht als meinen wirklichen Code, da ich den ursprünglichen Test gelöscht habe, weil ...

Das funktioniert nur, wenn ich das Verzeichnis, in dem sich die ausführbare Datei befindet, nicht ändere!

Das Hinzufügen einer neuen Datei zum Verzeichnis (vermutlich auch das Entfernen einer Datei) und das Verschieben der ausführbaren Datei an einer neuen Stelle würde dazu führen, dass fp(); abstürzt. Die neue Funktionsadresse wäre ein anderer Wert.

Also habe ich einen neuen Test gemacht, der den Unterschied zwischen einem alten Funktionszeiger und einer aktuellen Funktionsadresse berechnet. Wenn man diesen Offset auf einen alten Funktionszeiger anwendet und ihn aufruft, erhält man den korrekten Funktionsaufruf, unabhängig davon, was ich mit dem Verzeichnis mache.

Ich bin sicher, das ist UB. Wenn jedoch ein Nullzeiger de-referenziert, wird ein segfault verursacht, der UB ist ziemlich konsistent.

Wie wahrscheinlich ist diese Methode, abgesehen von dem Umschreiben der Daten mit Garbage und unter der Annahme, dass die Funktionen nicht in einer DLL geladen werden? Inwiefern wird es immer noch nicht funktionieren?

    
James Root 06.11.2015, 07:25
quelle

2 Antworten

2

Wie bereits erwähnt, wird dieses Problem durch die "Address Space Layout Randomization" (ASLR) verursacht. Diese Randomisierung erfolgt für jedes Modul (d. H. Jedes ausführbare Bild). Das bedeutet, wenn alle Ihre Funktionen in Ihrer .exe enthalten sind, haben sie garantiert immer denselben Offset von der Basis des Moduls. Wenn sich einige Funktionen in einer DLL befinden, gelten dieselben, aber von der Basis des DLL-Moduls. Es ist wichtig, dass die relativen Moduladressen gleich bleiben, da sonst Einstiegspunkte und DLL-Funktionen nicht gefunden werden können.

In einer Windows-Umgebung:

In Visual Studio (und MSVC) ist ASLR standardmäßig aktiviert, Sie können es jedoch in der Option "Linker & gt; Erweitert & gt; Randomisierte Basisadresse" deaktivieren (/ DYNAMICBASE: NO in der Befehlszeile). Wenn Sie diese Option deaktivieren, haben die Funktionen immer dieselbe Adresse.

Sie können den Offset auch zur Laufzeit bestimmen. Die Modulbasisadresse kann mit GetModuleHandle() erhalten werden (das Modulhandle ist tatsächlich die Basisadresse). Damit können Sie mit relativen Adressen für Ihre Zeiger arbeiten.

%Vor%     
ElderBug 06.11.2015, 09:01
quelle
1

Der Funktionszeiger funktioniert NUR, wenn das Programm jedes Mal an der gleichen Adresse geladen wird. Moderne OS haben eine "Adressraum-Randomisierung", die bewirkt, dass die tatsächliche Adresse von Code, Daten und Stapel zufällig verschoben wird - um Stapelüberlaufangriffe zu vermeiden, die die Rücksprungadresse modifizieren - da es nicht möglich ist, die Adresse zu kennen, zu der "zurückkehrt" "Wenn es zufällig ausgewählt wird.

Es gibt Einstellungen, um die zufälligen Änderungen zu deaktivieren.

Offensichtlich wird es auch nicht funktionieren, wenn Code geändert wird, der zwischen dem Anfang des Codeabschnitts liegt, in dem die aufgerufene Funktion arbeitet.

Der Zeiger wird in ein void * konvertiert, was möglich sein sollte - offensichtlich funktioniert der Dateiinhalt nicht auf einer anderen Betriebssystem- oder Prozessorarchitektur, aber ich sehe keinen besonderen Grund dafür, dass dies nicht funktioniert.

Eine portablere Methode wäre jedoch die Speicherung einer Sequenznummer der Operation, die Sie verwenden, anstatt eines Funktionszeigers. Und dann etwas wie das tun:

%Vor%

Bei einem Fehler speichern Sie sequence (oder sequence - 1 ).

Dies setzt voraus, dass die Funktionen f und g eine Ausnahme auslösen oder longjmp zum Beenden verwenden [oder dass ... auf Fehler prüft].

Darüber hinaus sehe ich keinen technischen Grund, warum

    
Mats Petersson 06.11.2015 08:34
quelle

Tags und Links