Подтвердить что ты не робот

Воспроизведение непредвиденного поведения с помощью кросс-модификационного кода на процессорах x86-64

Вопрос

Каковы некоторые идеи для кросс-модификационного кода, которые могут вызвать неожиданное поведение в системах x86 или x86-x64, где все сделано правильно в коде кросс-модификации, за исключением выполнения команды сериализации на исполняющем процессоре ранее для выполнения измененного кода?

Как отмечено ниже, у меня есть процессор Core 2 Duo E6600 для тестирования, который явно упоминается как процессор, подверженный проблемам. Я буду тестировать любые идеи, которые есть у меня на этом компьютере, и давать обновления.

Фон

В системах x86 и x64 официальное руководство по написанию кросс-модификационного кода состоит в следующем:

; Action of Modifying Processor
Store modified code (as data) into code segment;
Memory_Flag ← 1; 

; Action of Executing Processor
WHILE (Memory_Flag ≠ 1)
  Wait for code to update;
ELIHW;
Execute serializing instruction; (* For example, CPUID instruction *)
Begin executing modified code;

Инструкция сериализации в явном виде упоминается в случае необходимости для некоторых процессоров. Например, у процессоров Intel Core 2 Duo E6000 есть следующие ошибки: (от http://www.mathemainzel.info/files/intelX6800andintelE6000.pdf)

Действие одного процессора или главного системного шифра, запись данных в в настоящее время выполняется сегмент кода второго процессора с намерением того, чтобы второй процессор выполнял эти данные при вызове кода кросс-модификационный код (XMC). XMC, который не заставляет второй процессор для выполнения команды синхронизации до выполнения нового кода, называется несинхронизированным XMC.

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

Есть некоторые предположения о том, почему неожиданное поведение выполнения может произойти, если инструкция сериализации не используется в http://linux.kernel.narkive.com/FDc9TB0d/patch-linux-kernel-markers:

Когда i-выборка была выполнена, и микрооперации находятся в следе кеш, тогда уже нет прямой корреляции между оригиналом границы машинной инструкции и микрооперации. Это связано с оптимизация. Например (искусственный для иллюстративных целей):

mov eax, ebx

mov memory, eax

mov eax, 1

(с использованием нотации Intel не ATT - сила привычки)

В кеше трассировки не будет микроопераций для обновления eax с помощью ebx.

Изменение "mov eax, ebx" на "mov ecx, ebx" на лету делает недействительным оптимизированный тайник трассировки, следовательно, рекурсия onlhy - это GPF. Если модификация не отменяет кеш трассировки, а не GPF. вопрос заключается в следующем: "можем ли мы предсказать обстоятельства, когда кеш трассировки не было признано недействительным", и ответ в целом микроархитектура не является общедоступной. Но можно догадаться, что изменение однобайтовый код операции с инструкцией прерывания - int3 - не вызывают несогласованность, которую невозможно обработать. И то, что Intel подтверждено. Идем дальше и храним int3 без необходимости синхронизации (т.е. заставить кеш трассировки очищаться).

Там также немного больше информации на https://sourceware.org/ml/systemtap/2005-q3/msg00208.html:

Когда мы узнали об этом, я долго беседовал с Intel микроархитектуры. Оказывается, причина этого erratum (который, кстати, Intel не намерен исправлять), объясняется тем, что трассировка cache - поток микропор, полученный в результате инструкции интерпретация - не может быть гарантирована. Чтение между Я предполагаю, что эта проблема возникает из-за оптимизации, сделанной в trace cache, где уже невозможно идентифицировать оригинал границы инструкций. Если первопроходцы CPU используют кеш трассировки была признана недействительной из-за несинхронизированной перекрестной модификации, тогда выполнение команды будет прервано с помощью GPF. Дальнейшее обсуждение с Intel показало, что замена первого байта кода операции на int3 не будет подлежать этой ошибке.

Помимо того, что я разместил здесь, я не слишком много видел в Интернете по этой проблеме. Кроме того, я не нашел публичных примеров того, как люди укушены, не выполнив инструкцию по сериализации при использовании кросс-модификационного кода в системах x86 и x86-64.

У меня есть компьютер с процессором Intel Core 2 Duo E6600, который явно документирован как подверженный этой проблеме, и я не смог написать код, который вызывает эту проблему.

Написание кода для этого - личное любопытство для меня. В производственном коде я просто соблюдаю правила, но я считаю, что, возможно, что-то для меня, чтобы научиться воспроизводить это.

4b9b3361

Ответ 1

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

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

Сериализация команды - это еще один способ очистки конвейера. Когда он достигает конца конвейера, конвейер очищается и снова начинает считывание после инструкции сериализации.

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

Чтобы воспроизвести поведение, описанное в errata, вам необходимо убедиться, что код, который вы изменяете от одного процессора, находится внутри конвейера другого процессора. Взгляните на предсказание ветвей (решает, какой путь кода находится внутри конвейера) и примитивы синхронизации.

Ответ 2

Скорее всего, вы можете воспроизвести это поведение очень близко к нулю. Прежде всего имейте в виду, что само-и кросс-модификационный код не является чем-то необычным. Бывает каждый день, когда, скажем, вы используете отладчик и устанавливаете точку останова или изменяете память. Или когда DLL загружается, и ее необходимо переместить на другой адрес.

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

Кроме того, исправления и цитированное вами письмо FUD являются старыми, они относятся ко времени, когда многоядерные процессоры впервые стали общедоступными. Intel всегда документирует рекомендуемые подходы, которые работают на любом процессоре, включая те, у которых не было исправлено erratum. Неясно, нужны ли текущие модели на самом деле для команды сериализации.

Лучше не тратить время на это.