Наблюдение за устаревшими инструкциями на x86 с самоизменяющимся кодом
сказали и прочитали от Intels руководства, которые можно записывать инструкции в память, но очередь предварительной выборки инструкций уже выбрала устаревшие инструкции и выполнит эти старые инструкции. Мне не удалось наблюдать за этим поведением. Моя методология заключается в следующем.
В руководстве по разработке программного обеспечения 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.I '
мы работали с 1,7-ГГц процессором Intel Core i5 (MacBook Air 2011) и процессором Intel® Xeon® X3460 с частотой 2,80 ГГц. Однако каждый раз, когда я вижу val == 1, CPU указывает на новую инструкцию.
Кто-нибудь сталкивался с поведением, которое я хочу наблюдать? Правильно ли мои рассуждения? Я'Я немного сбит с толку из-за того, что в руководстве упоминаются процессоры P6 и Pentium, а также из-за отсутствия упоминания моего процессора Core i5. Возможно, происходит что-то еще, что заставляет ЦП сбрасывать свою очередь предварительной выборки команд? Любое понимание было бы очень полезно!