Obserwując pobieranie starych instrukcji na x86 z samo modyfikującym się kodem

Zostałem poinformowany i przeczytałem z podręczników Intela, że ​​możliwe jest zapisanie instrukcji w pamięci, ale kolejka pobierania instrukcji już pobrała przestarzałe instrukcje i wykona te stare instrukcje. Nie udało mi się zaobserwować tego zachowania. Moja metodologia jest następująca.

Podręcznik rozwoju oprogramowania firmy Intel stanowi w sekcji 11.6

Zapis do lokalizacji pamięci w segmencie kodu, który jest aktualnie buforowany w procesorze, powoduje unieważnienie powiązanej linii (lub linii) pamięci podręcznej. To sprawdzenie opiera się na fizycznym adresie instrukcji.Ponadto rodziny procesorów P6 i Pentium sprawdzają, czy zapis do segmentu kodu może modyfikować instrukcję, która została wstępnie pobrana do wykonania. Jeśli zapis wpływa na wstępnie pobraną instrukcję, kolejka pobierania wstępnego jest unieważniana. Ta ostatnia kontrola opiera się na adresie liniowym instrukcji.

Wygląda więc na to, że jeśli mam nadzieję wykonać stare instrukcje, muszę mieć dwa różne adresy liniowe odnoszące się do tej samej strony fizycznej. Tak więc pamięć mapuje plik na dwa różne adresy.

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);

Mam funkcję zespołu, która pobiera pojedynczy argument, wskaźnik do instrukcji, którą chcę zmienić.

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

W C kopiuję kod do pliku odwzorowanego w pamięci. Wywoływam funkcję z adresu liniowegoa1, ale przekazuję wskaźnik doa2 jako cel modyfikacji kodu.

#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);

Jeśli CPU odebrał zmodyfikowany kod, val == 1. W przeciwnym razie, jeśli stare instrukcje zostały wykonane (dwa nopy), val == 0.

Uruchomiłem to na Intel Core i5 1,7 GHz (2011 macbook air) i CPU Intel Xeon X3460 @ 2,80 GHz. Za każdym razem jednak widzę, że val == 1 wskazuje, że CPU zawsze zauważa nową instrukcję.

Czy ktoś doświadczył zachowania, które chcę obserwować? Czy moje rozumowanie jest prawidłowe? Trochę się mylę z instrukcją dotyczącą procesorów P6 i Pentium, a co z brakiem wzmianki o moim procesorze Core i5. Być może dzieje się coś innego, co powoduje, że procesor opróżnia kolejkę pobierania wstępnego instrukcji? Każdy wgląd byłby bardzo pomocny!

questionAnswers(3)

yourAnswerToTheQuestion