Ich frage mich, was Sie erreichen wollen. Wenn Ihr Prozess deterministisch ist, sollte das Muster der Zuweisung/Aufhebung der Zuweisung dasselbe sein.
Der einzig mögliche Unterschied könnte die von malloc
zurückgegebene Adresse sein . Aber Sie sollten sich wahrscheinlich nicht darauf verlassen (der einfachste Weg ist, Zeiger nicht als Keymap oder andere Datenstruktur zu verwenden). Und selbst dann sollte es nur Unterschiede geben, wenn die Zuordnung nicht über sbrk
erfolgt (Die glibc verwendet anonym mmap
für große Zuordnungen) oder wenn Sie mmap
verwenden (da standardmäßig die Adresse vom Kernel ausgewählt wird).
Wenn Sie wirklich genau dieselbe Adresse haben möchten, besteht eine Möglichkeit darin, einen großen statischen Puffer zu haben und eine benutzerdefinierte Zuweisung zu schreiben, die Speicher aus diesem Puffer verwendet. Dies hat den Nachteil, dass Sie im Voraus wissen müssen, wie viel Speicher Sie maximal benötigen werden. In einer ausführbaren Nicht-PIE-Datei (gcc -fno-pie -no-pie
), hat ein statischer Puffer jedes Mal dieselbe Adresse. Für eine ausführbare PIE-Datei können Sie die Adressraum-Layout-Randomisierung des Kernels zum Laden von Programmen deaktivieren. In einer gemeinsam genutzten Bibliothek sollte das Deaktivieren von ASLR und das zweimalige Ausführen desselben Programms zu denselben Entscheidungen des dynamischen Linkers führen, wo Bibliotheken zugeordnet werden sollen.
Wenn Sie die maximale Größe des Speichers, den Sie verwenden möchten, nicht vorher wissen oder nicht jedes Mal neu kompilieren möchten, wenn diese Größe zunimmt, können Sie auch mmap
verwenden um einen großen anonymen Puffer auf eine feste Adresse abzubilden. Übergeben Sie einfach die Größe des Puffers und die zu verwendende Adresse als Parameter an Ihren Prozess und verwenden Sie den zurückgegebenen Speicher, um Ihren eigenen malloc
zu implementieren drauf.
static void* malloc_buffer = NULL;
static size_t malloc_buffer_len = 0;
void* malloc(size_t size) {
// Use malloc_buffer & malloc_buffer_len to implement your
// own allocator. If you don't read uninitialized memory,
// it can be deterministic.
return memory;
}
int main(int argc, char** argv) {
size_t buf_size = 0;
uintptr_t buf_addr = 0;
for (int i = 0; i < argv; ++i) {
if (strcmp(argv[i], "--malloc-size") == 0) {
buf_size = atoi(argv[++i]);
}
if (strcmp(argv[i], "--malloc-addr") == 0) {
buf_addr = atoi(argv[++i]);
}
}
malloc_buffer = mmap((void*)buf_addr, buf_size, PROT_WRITE|PROT_READ,
MAP_FIXED|MAP_PRIVATE, 0, 0);
// editor's note: omit MAP_FIXED since you're checking the result anyway
if (malloc_buffer == MAP_FAILED || malloc_buffer != (void*)but_addr) {
// Could not get requested memory block, fail.
exit(1);
}
malloc_size = buf_size;
}
Durch die Verwendung von MAP_FIXED
, weisen wir den Kernel an, alle vorhandenen Zuordnungen, die sich überschneiden, durch diese neue bei buf_addr
zu ersetzen .
(Anmerkung der Redaktion:MAP_FIXED
ist wahrscheinlich nicht das, was Sie wollen . Angabe von buf_addr
als Hinweis statt NULL
fordert diese Adresse nach Möglichkeit bereits an. Mit MAP_FIXED
, mmap
wird entweder einen Fehler oder die von Ihnen angegebene Adresse zurückgeben. Die malloc_buffer != (void*)but_addr
check ist sinnvoll für die Nicht-FIXED
Fall, der kein vorhandenes Mapping Ihres Codes oder eine gemeinsam genutzte Bibliothek oder irgendetwas anderes ersetzt. Linux 4.17 führte MAP_FIXED_NOREPLACE
ein die Sie verwenden können, um mmap dazu zu bringen, einen Fehler anstelle von Speicher an der falschen Adresse zurückzugeben, die Sie nicht verwenden möchten. Aber lassen Sie trotzdem das Einchecken, damit Ihr Code auf älteren Kerneln funktioniert.)
Wenn Sie diesen Block verwenden, um Ihren eigenen Malloc zu implementieren, und keine anderen nicht deterministischen Operationen in Ihrem Code verwenden, können Sie die Zeigerwerte vollständig kontrollieren.
Dies setzt voraus, dass Ihre Musterverwendung von malloc / free deterministisch ist. Und dass Sie keine nicht-deterministischen Bibliotheken verwenden.
Ich denke jedoch, dass eine einfachere Lösung darin besteht, Ihre Algorithmen deterministisch zu halten und nicht von Adressen abhängig zu sein. Das ist möglich. Ich habe an einem groß angelegten Projekt gearbeitet, bei dem mehrere Computer den Status deterministisch aktualisieren mussten (so dass jedes Programm denselben Status hatte, während nur Eingaben übertragen wurden). Wenn Sie den Zeiger nicht für andere Dinge als das Referenzieren von Objekten verwenden (das Wichtigste ist, den Zeigerwert niemals für irgendetwas zu verwenden, nicht als Hash, nicht als Schlüssel in einer Karte, ...), bleibt Ihr Zustand deterministisch .
Es sei denn, Sie möchten in der Lage sein, den gesamten Prozessspeicher zu erfassen und einen binären Diff durchzuführen, um Divergenzen zu erkennen. Ich denke, es ist eine schlechte Idee, denn woher willst du wissen, dass beide den gleichen Punkt in ihrer Berechnung erreicht haben? Es ist viel einfacher, die Ausgabe zu vergleichen oder den Prozess in die Lage zu versetzen, einen Hash des Zustands zu berechnen und diesen zu verwenden, um zu überprüfen, ob sie synchron sind, da Sie steuern können, wann dies geschieht (und somit auch deterministisch wird). andernfalls ist Ihre Messung nicht deterministisch).
Was nicht deterministisch ist, ist nicht nur malloc
aber mmap (der grundlegende Systemaufruf, um mehr Speicherplatz zu erhalten; es ist keine Funktion, es ist ein Systemaufruf, also ist es aus Sicht der Anwendung elementar oder atomar; Sie können es also nicht innerhalb der Anwendung umschreiben) wegen der Randomisierung des Layouts des Adressraums unter Linux.
Sie können es mit
deaktivieren echo 0 > /proc/sys/kernel/randomize_va_space
als root oder über sysctl.
Wenn Sie die Randomisierung des Adressraum-Layouts nicht deaktivieren, stecken Sie fest.
Und Sie haben zuvor eine ähnliche Frage gestellt, wo ich erklärt habe, dass Ihr malloc
-s wird nicht immer deterministisch sein.
Ich denke immer noch, dass für einige praktische Anwendungen malloc
kann nicht deterministisch sein. Stellen Sie sich zum Beispiel ein Programm vor, das eine Hash-Tabelle hat, die mit pid
verschlüsselt ist -s der untergeordneten Prozesse, die es startet. Die Kollision in dieser Tabelle wird nicht in allen Ihren Prozessen gleich sein usw.
Ich glaube also, dass es Ihnen nicht gelingen wird, malloc
zu machen deterministisch in Ihrem Sinne, was auch immer Sie versuchen werden (es sei denn, Sie beschränken sich auf eine sehr enge Klasse von Anwendungen zum Checkpoint, so eng, dass Ihre Software nicht sehr nützlich sein wird).
Einfach ausgedrückt, wie andere gesagt haben:Wenn die Ausführung der Anweisungen Ihres Programms deterministisch ist, wird der Speicher von malloc()
zurückgegeben wird deterministisch sein. Das setzt voraus, dass die Implementierung Ihres Systems keinen Aufruf an random()
hat oder so ähnlich. Wenn Sie sich nicht sicher sind, lesen Sie den Code oder die Dokumentation für malloc
Ihres Systems .
Dies ist mit der möglichen Ausnahme von ASLR, wie andere auch angegeben haben. Wenn Sie keine Root-Rechte haben, können Sie es pro Prozess über personality(2)
deaktivieren syscall und den Parameter ADDR_NO_RANDOMIZE. Weitere Informationen zu den Persönlichkeiten finden Sie hier.
Bearbeiten:Ich sollte auch sagen, wenn Sie es nicht wissen:Was Sie tun, nennt man Bisimulation und ist eine gut untersuchte Technik. Wenn Sie die Terminologie nicht kennen, kann es hilfreich sein, dieses Schlüsselwort für die Suche zu haben.