Mir wurde gesagt und ich habe aus den Handbüchern von Intel gelesen, dass es möglich ist, Anweisungen in den Speicher zu schreiben, aber die Befehls-Prefetch-Warteschlange hat bereits die veralteten Anweisungen abgerufen und wird diese alten Anweisungen ausführen. Es ist mir nicht gelungen, dieses Verhalten zu beobachten. Meine Methodik ist wie folgt.
Das Intel Software-Entwicklungshandbuch besagt ab Abschnitt 11.6, dass
Ein Schreibvorgang in eine Speicherstelle in einem Codesegment, das derzeit im Prozessor zwischengespeichert ist, bewirkt, dass die zugehörige Cache-Zeile (oder -Zeilen) ungültig gemacht werden. Diese Prüfung basiert auf der physikalischen Adresse des Befehls. Darüber hinaus prüfen die P6-Familie und Pentium-Prozessoren, ob ein Schreibvorgang in ein Codesegment einen Befehl modifizieren kann, der zur Ausführung vorab abgerufen wurde. Wenn der Schreibvorgang einen vorab abgerufenen Befehl betrifft, wird die Vorabruf-Warteschlange ungültig gemacht. Diese letztere Prüfung basiert auf der linearen Adresse des Befehls.
Es sieht also so aus, als ob ich, wenn ich veraltete Anweisungen ausführen möchte, zwei verschiedene lineare Adressen haben muss, die auf dieselbe physische Seite verweisen. Also speichere ich eine Datei auf zwei verschiedene Adressen.
int fd = open("code_area", O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO);
assert(fd>=0);
write(fd, zeros, 0x1000);
uint8_t *a1 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_FILE | MAP_SHARED, fd, 0);
uint8_t *a2 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_FILE | MAP_SHARED, fd, 0);
assert(a1 != a2);
Ich habe eine Assembly-Funktion, die ein einziges Argument akzeptiert, einen Zeiger auf die Anweisung, die ich ändern möchte.
fun:
push %rbp
mov %rsp, %rbp
xorq %rax, %rax # Return value 0
# A far jump simulated with a far return
# Push the current code segment %cs, then the address we want to far jump to
xorq %rsi, %rsi
mov %cs, %rsi
pushq %rsi
leaq copy(%rip), %r15
pushq %r15
lretq
copy:
# Overwrite the two nops below with `inc %eax'. We will notice the change if the
# return value is 1, not zero. The passed in pointer at %rdi points to the same physical
# memory location of fun_ins, but the linear addresses will be different.
movw $0xc0ff, (%rdi)
fun_ins:
nop # Two NOPs gives enough space for the inc %eax (opcode FF C0)
nop
pop %rbp
ret
fun_end:
nop
In C kopiere ich den Code in die speicherabgebildete Datei. Ich rufe die Funktion von der linearen Adresse auf a1
aber ich übergebe einen Zeiger auf a2
als Ziel der Codeänderung.
#define DIFF(a, b) ((long)(b) - (long)(a))
long sz = DIFF(fun, fun_end);
memcpy(a1, fun, sz);
void *tochange = DIFF(fun, fun_ins);
int val = ((int (*)(void*))a1)(tochange);
Wenn die CPU den modifizierten Code aufgenommen hat, gilt val==1. Andernfalls, wenn die veralteten Anweisungen ausgeführt wurden (zwei nops), val==0.
Ich habe dies auf einem 1,7 GHz Intel Core i5 (2011 MacBook Air) und einer Intel(R) Xeon(R) CPU X3460 @ 2,80 GHz ausgeführt. Jedes Mal sehe ich jedoch val==1, was darauf hinweist, dass die CPU die neue Anweisung immer bemerkt.
Hat jemand Erfahrung mit dem Verhalten, das ich beobachten möchte? Ist meine Überlegung richtig? Ich bin ein wenig verwirrt darüber, dass das Handbuch P6- und Pentium-Prozessoren erwähnt und dass mein Core i5-Prozessor nicht erwähnt wird. Vielleicht passiert etwas anderes, was dazu führt, dass die CPU ihre Anweisungs-Prefetch-Warteschlange löscht? Jeder Einblick wäre sehr hilfreich!
Welches Handbuch haben Sie verwendet (überprüfen Sie die “Bestellnummer” auf der ersten Seite und tragen Sie es hier ein)?
– osgx
30. Juni 2013 um 23:26 Uhr
Lesen Sie auch den Abschnitt „8.1.3 Handling Self- and Cross-Modifying Code“ der Bedienungsanleitung – download.intel.com/products/processor/manual/325462.pdf
– osgx
30. Juni 2013 um 23:57 Uhr
Hmm, versuchen Sie, PROT_EXEC von a2 zu deaktivieren … Dies kann einige Intel Atoms betreffen
– osgx
1. Juli 2013 um 1:25 Uhr