Мне сказали и прочитали из руководств Intel, что можно писать инструкции в память, но очередь предварительной выборки команд уже вытащила устаревшие инструкции и выполнит эти старые инструкции. Я не увенчался успехом в наблюдении за этим поведением. Моя методология такова.
В руководстве по разработке программного обеспечения Intel из раздела 11.6 указано, что
Запись в ячейку памяти в сегменте кода, который в настоящее время кэшируется в процессоре, приводит к недействительности связанной строки (или строк) кэша. Эта проверка основана на физическом адресе инструкции. Кроме того, процессоры семейства P6 и Pentium проверяют, может ли запись в сегмент кода изменять инструкцию, предварительно запрограммированную для выполнения. Если запись влияет на предварительно запрограммированную команду, очередь предварительной выборки становится недействительной. Эта последняя проверка основана на линейном адресе инструкции.
Итак, похоже, если я надеюсь выполнить устаревшие инструкции, мне нужно, чтобы два разных линейных адреса ссылались на одну и ту же физическую страницу. Итак, я память сопоставляет файл с двумя разными адресами.
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);
У меня есть функция сборки, которая принимает один аргумент, указатель на инструкцию, которую я хочу изменить.
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
В C я копирую код в файл с отображением памяти. Я вызываю функцию из линейного адреса a1
, но я передаю указатель на a2
в качестве цели модификации кода.
#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);
Если ЦП взял модифицированный код, val == 1. В противном случае, если выполнялись устаревшие инструкции (два nops), val == 0.
Я запускаю это на 1,7 ГГц Intel Core i5 (2011 macbook air) и Intel (R) Xeon (R) CPU X3460 @2,80 ГГц. Однако каждый раз, когда я вижу val == 1, CPU всегда замечает новую инструкцию.
Есть ли у кого-нибудь опыт в поведении, которое я хочу наблюдать? Правильно ли я рассуждаю? Я немного запутался в руководстве, в котором упоминаются процессоры P6 и Pentium, и о том, что отсутствие упоминания моего процессора Core i5. Возможно, происходит что-то еще, что заставляет процессор сбросить свою очередь предварительной выборки команд? Любое понимание было бы очень полезно!