Наблюдение за устаревшей инструкцией по x86 с самомодифицируемым кодом

Мне сказали и прочитали из руководств 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. Возможно, происходит что-то еще, что заставляет процессор сбросить свою очередь предварительной выборки команд? Любое понимание было бы очень полезно!

Ответ 1

Я думаю, вы должны проверить счетчик производительности MACHINE_CLEARS.SMC (часть события MACHINE_CLEARS) процессора (он доступен в Sandy Bridge 1, который используется в вашей электронной книге питания, а также доступен на вашем Xeon, который является Nehalem 2 - поиск "smc" ). Вы можете использовать oprofile, perf или Intel Vtune, чтобы найти его значение:

http://software.intel.com/sites/products/documentation/doclib/iss/2013/amplifier/lin/ug_docs/GUID-F0FD7660-58B5-4B5D-AA9A-E1AF21DDCA0E.htm

Очистка машины

Метрическое описание

Некоторые события требуют, чтобы весь конвейер был очищен и перезапущен сразу после последней команды, вышедшей на пенсию. Эта метрика измеряет три таких события: нарушения порядка памяти, самомодифицирующийся код и некоторые нагрузки на недопустимые диапазоны адресов.

Возможные проблемы

Значительная часть времени выполнения тратится на очистку машины. Изучите события MACHINE_CLEARS, чтобы определить конкретную причину.

SMC: http://software.intel.com/sites/products/documentation/doclib/stdxe/2013/amplifierxe/win/win_reference/snb/events/machine_clears.html

MACHINE_CLEARS Код события: 0xC3 Маска SMC: 0x04

Обнаружен самомодифицирующий код (SMC).

Число обнаруженных самообслуживающихся машин.

Intel также говорит о smc http://software.intel.com/en-us/forums/topic/345561 (связано с Аналитическая система анализа производительности узких мест Intel

Это событие срабатывает при обнаружении самомодифицирующего кода. Обычно это могут использоваться людьми, которые выполняют двоичное редактирование, чтобы заставить его пройти определенный путь (например, хакеры). Это событие подсчитывает количество раз, которое программа записывает в раздел кода. Самомодифицирующийся код вызывает серьезное наказание во всех процессорах Intel 64 и IA-32. Измененная строка кеша записывается обратно в кэши L2 и LLC. Кроме того, инструкции необходимо перезагрузить, что приведет к снижению производительности.

Думаю, вы увидите такие события. Если это так, то ЦПУ смог обнаружить акт самомодификации кода и поднял "Machine Clear" - полный перезапуск трубопровода. Первые этапы - Fetch, и они будут запрашивать кеш второго уровня для нового кода операции. Меня очень интересует точное количество SMC-событий за выполнение вашего кода - это даст нам некоторую оценку задержек.. (SMC подсчитывается в некоторых единицах, где 1 единица считается 1,5 ц.п. циклов - B.6.2. 6 руководства по оптимизации Intel)

Мы видим, что Intel говорит, что "перезапущен сразу после последней отставкой.", поэтому я думаю, что последняя отставная инструкция будет mov; и ваши nops уже находятся в стадии разработки. Но SMC будет поднят в момент выхода на пенсию, и он убьет все подряд, включая nops.

Этот перезапуск SMC-индуцированного транспорта не является дешевым, у Agner есть некоторые измерения в Optimizing_assembly.pdf - "17.10 Самомодифицирующий код (All процессоры)" (я думаю, что любой Core2/CoreiX похож на PM здесь):

Штраф за выполнение части кода сразу после его модификации составляет приблизительно 19 тактов для P1, 31 для PMMX и 150-300 для PPro, P2, P3, PM. P4 очистит весь кеш следов после самомодифицирующего кода. Процессоры 80486 и более ранние версии требуют перехода между модифицирующим и модифицированным кодами, чтобы очистить кеш-код....

Самомодифицирующийся код не считается хорошей практикой программирования. Его следует использовать, только если выигрыш в скорости является существенным, и модифицированный код выполняется столько раз, что преимущество перевешивает штрафы за использование самомодифицирующегося кода.

Использование различных линейных адресов для отказа SMC-детектора было рекомендовано здесь: fooobar.com/questions/4524/... - Я попытаюсь найти фактическую документацию Intel... На самом деле я не могу ответить на ваш настоящий вопрос.

Здесь могут быть некоторые подсказки: Руководство по оптимизации, 248966-026, апрель 2012 г. "3.6.9 Смешивание кода и данных":

Размещение записываемых данных в сегменте кода может быть невозможно отличить от самомодифицирующего кода. Записываемые данные в сегменте кода могут такое же снижение производительности, как и самомодифицирующийся код.

и следующий раздел

Программное обеспечение должно избегать записи на кодовую страницу на той же 1-килобайтной подстранице, которая выполнение или выбор кода на той же 2-килобайтной подстранице, которая является написано. Кроме того, совместное использование страницы, содержащей прямо или спекулятивно выполненную код с другим процессором, поскольку страница данных может инициировать условие SMC, которое вызывает весь конвейер машины и кеш трассировки должны быть очищены. Это связано с тем, что самомодифицирующееся условие кода.

Итак, возможно, есть некоторые схемы, которые контролируют пересечения записываемых и исполняемых подстраниц.

Вы можете попробовать выполнить модификацию из другого потока (кросс-модификационный код), но необходима очень тщательная синхронизация потоков и промывка конвейера (возможно, вы захотите включить некоторые принудительные задержки в потоке записи, CPUID просто после того, как требуется синхронизация). Но вы должны знать, что THEY уже исправили это с помощью " nukes" - отметьте US6857064 патент.

Я немного запутался в руководстве, в котором упоминаются процессоры P6 и Pentium.

Это возможно, если вы извлекли, расшифровали и выполнили какую-то устаревшую версию руководства по эксплуатации Intel. Вы можете reset конвейер и проверить эту версию: Номер для заказа: 325462-047US, июнь 2013 г. "11.6 САМО-ИЗМЕНЕНИЕ КОДА". Эта версия все еще ничего не говорит о новых процессорах, но упоминает, что при изменении с использованием разных виртуальных адресов поведение может быть несовместимо между микроархитектурами (оно может работать на вашем Nehalem/Sandy Bridge и может не работать. Skymont)

11.6 САМО-ИЗМЕНЕНИЕ КОДАЗапись в ячейку памяти в сегменте кода, который в настоящее время кэшируется в процессоре, приводит к недействительности связанной строки (или строк) кэша. Эта проверка основана на физическом адресе инструкции. Кроме того, процессоры семейства P6 и Pentium проверяют, может ли запись в сегмент кода изменять инструкцию, предварительно запрограммированную для выполнения. Если запись влияет на предварительно запрограммированную команду, очередь предварительной выборки становится недействительной. Последняя проверка основана на линейном адресе инструкции. Для процессоров Pentium 4 и Intel Xeon запись или snoop инструкции в сегменте кода, где целевая команда уже декодирована и находится в кэше трассировки, делает недействительным весь кеш трассировки. Последнее поведение означает, что программы, которые меняют код, могут привести к серьезному ухудшению производительности при работе на процессорах Pentium 4 и Intel Xeon.

На практике проверка линейных адресов не должна создавать проблемы совместимости между процессорами IA-32. Приложения, которые включают самомодифицирующийся код, используют один и тот же линейный адрес для изменения и получения инструкции.

Системное программное обеспечение, такое как отладчик, который может изменить команду с использованием другого линейного адреса, чем тот, который используется для извлечения инструкции, выполнит операцию сериализации, такую ​​как инструкцию CPUID, до того, как будет выполнена модифицированная инструкция, которая автоматически пересинхронизирует кеш команд и очередь предварительной выборки. (См. Раздел 8.1.3 "Обработка кода самообучения и перекрестной модификации" для получения дополнительной информации об использовании самомодифицирующего кода.)

Для процессоров Intel486 запись в инструкцию в кеше будет изменять ее как в кеше, так и в памяти, но если команда была предварительно запрограммирована перед записью, то старая версия инструкции может быть выполнена. Чтобы предотвратить выполнение старой инструкции, очистите блок предварительной выборки команд, закодировав инструкцию перехода сразу после любой записи, которая модифицирует команду

REAL Update, googled для "Обнаружение SMC" (с кавычками), и есть некоторые сведения о том, как современный Core2/Core iX обнаруживает SMC, а также множество списков ошибок в Xeons и Pentiums, висящих в детекторе SMC:

  • http://www.google.com/patents/US6237088 Система и метод отслеживания инструкций в полете в трубопроводе @2001

  • DOI 10.1535/itj.1203.03 (для него есть бесплатная версия на citeseerx.ist.psu.edu) - в Penryn добавлен "INCLUSION FILTER", чтобы уменьшить количество ложных обнаружений SMC; "существующий механизм обнаружения включения" изображен на рис. 9

  • http://www.google.com/patents/US6405307 - более старый патент на логику обнаружения SMC

В соответствии с патентом US6237088 (фиг. 5, сводка) имеется "буфер адресной линии" (со многими линейными адресами, один адрес на выбранную команду, или, в другом слове, буфер, заполненный выбранными IP-адресами с точностью до кеш-строки). Каждый магазин, или более точная фаза "store address" каждого магазина будет подаваться в параллельный компаратор для проверки, будет хранить пересечения с любыми исполняемыми инструкциями или нет.

В обоих патентах четко не сказано, будут ли они использовать физический или логический адрес в логике SMC... L1i в Sandy bridge - это VIPT (Практически индексируется, физически помеченный, виртуальный адрес для индекса и физического адреса в теге.) в соответствии с http://nick-black.com/dankwiki/index.php/Sandy_Bridge, поэтому у нас есть физический адрес, когда кеш L1 возвращает данные. Я думаю, что Intel может использовать физические адреса в логике обнаружения SMC.

Более того, http://www.google.com/patents/US6594734 @1999 (опубликовано в 2003 году, просто помните, что цикл проектирования ЦП составляет около 3-5 лет) говорится в раздел "Сводка", который SMC теперь находится в TLB и использует физические адреса (или другими словами - пожалуйста, не пытайтесь обмануть детектор SMC):

Самомодифицирующий код обнаруживается с использованием буфера просмотра перевода. [который] содержит в себе адреса физических страниц, по которым можно отслеживать snoops с использованием адреса физической памяти хранилища в памяти.... Чтобы обеспечить более точную детализацию, чем страницу адресов, бит FINE HIT включается в каждую запись в кеше, связывающую информацию в кеше с частями страницы в памяти.

(часть страницы, называемая квадрантами в патенте US6594734, звучит как 1K подстраниц, не так ли?)

Затем они говорят

Поэтому snoops, запускаемые инструкциями магазина в память, могут выполнять обнаружение SMC, сравнивая физический адрес всех инструкций, хранящихся в кэше команд, с адресом всех инструкций, хранящихся на соответствующей странице или страницах памяти. Если есть соответствие адреса, это указывает на то, что место памяти было изменено. В случае сопоставления адресов, указывающего условие SMC, кеш команд и конвейер команд очищаются блоком выхода на пенсию, а новые инструкции извлекаются из памяти для хранения в кеш инструкций.

Поскольку отслеживания для обнаружения SMC являются физическими, и ITLB обычно принимает в качестве входных данных линейный адрес для перевода в физический адрес, ITLB дополнительно формируется как адресно-адресуемая память на физических адресах и включает дополнительный порт сравнения ввода (называемый портом снэйпа или обратным портом трансляции)

- Таким образом, чтобы обнаружить SMC, они заставляют хранилища перенаправлять физический адрес обратно в буфер команд через snoop (подобные snoops будут доставлены из других ядер /cpus или из записей DMA в наши кеши....), если snoop физическое. конфликты адресов с линиями кэша, хранящиеся в буфере команд, мы перезапустим конвейер через сигнал SMC, доставленный из iTLB в блок отставки. Можете представить, сколько часов процессора будет потрачено впустую в такой петле snoop из dTLB через iTLB и в отставку (он не может уйти в отставку после следующей инструкции "nop", хотя он был выполнен раньше, чем mov и не имеет побочных эффектов). Но WAT? ITLB имеет физический адрес и второй CAM (большой и горячий), чтобы поддерживать и защищать от сумасшедшего и обмана самомодифицирующего кода.

PS: И что, если мы будем работать с огромными страницами (4M или может быть 1G)? L1TLB имеет огромные записи страниц, и может быть много ложных обнаружений SMC для 1/4 из 4 MB страницы...

PPS: существует вариант, что ошибочная обработка SMC с разными линейными адресами присутствовала только в ранних версиях P6/Ppro/P2...

Ответ 2

Мне сказали и читали из руководств Intel, что это возможно для записи инструкций в память, но очередь предварительной выборки команд имеет [может иметь] уже выписанные устаревшие инструкции и [может] выполните те старые инструкции. Я не увенчался успехом в наблюдении за этим поведением.

Да, вы были бы.

Все или почти все современные процессоры Intel строже, чем руководство:

Они отслеживают конвейер на основе физического адреса, а не только линейного.

Реализации процессора могут быть более строгими, чем руководства.

Они могут выбрать так, потому что они столкнулись с кодом, который не соблюдает правила в руководствах, которые они не хотят нарушать.

Или... потому что самый простой способ придерживаться архитектурной спецификации (которая в случае SMC была официально "до следующей инструкции по сериализации", но на практике для устаревшего кода была "до следующего" (более чем в байтах) ") может быть более строгим.