Как определить длину кода операции инструкции x86-64, используя сам процессор?
Я знаю, что есть библиотеки, которые могут "анализировать" двоичный машинный код / код операции, чтобы определить длину инструкции процессора x86-64.
Но мне интересно, так как процессор имеет внутреннюю схему, чтобы определить это, есть ли способ использовать сам процессор, чтобы определить размер команды из двоичного кода? (Может быть, даже взломать?)
1 ответ
Флаг Trap (TF) в EFLAGS/RFLAGS делает CPU одношаговым, т.е. делает исключение после выполнения одной инструкции.
Поэтому, если вы пишете отладчик, вы можете использовать возможность одноступенчатого ЦП, чтобы найти границы команд в блоке кода. Но только запустив его, и если он выйдет из строя (например, загрузка с не отображенного адреса), вы получите это исключение вместо одношагового исключения TF.
(Большинство ОС имеют средства для подключения и пошагового выполнения другого процесса, например, Linux ptrace
Таким образом, вы можете создать непривилегированный процесс песочницы, в котором вы можете пройти через некоторые неизвестные байты машинного кода...)
Или, как указывает @Rbmn, вы можете использовать средства отладки, поддерживаемые ОС, для самостоятельного выполнения действий.
@Harold и @MargaretBloom также указывают на то, что вы можете поместить байты в конец страницы (за которой следует не отображенная страница) и запустить их. Посмотрите, получите ли вы #UD, ошибку страницы или исключение #GP.
#UD
: декодеры увидели полную, но неверную инструкцию.- ошибка страницы на не отображенной странице: декодеры нажимают на не отображенную страницу, прежде чем решить, что это недопустимая инструкция.
#GP
: инструкция была привилегированной или ошибочной по другим причинам.
Чтобы исключить декодирование + выполнение в качестве полной инструкции и затем ошибку на не отображенной странице, начните только с 1 байта перед не отображенной страницей и продолжайте добавлять дополнительные байты, пока не прекратите получать ошибки страницы.
Взлом x86 ISA Кристофером Домасом более подробно описывает эту технику, включая ее использование для поиска недокументированных нелегальных инструкций, например: 9a13065b8000d7
7-байтовая недопустимая инструкция; вот когда это останавливает сбой страницы. (objdump -d
просто говорит 0x9a (bad)
и декодирует остальные байты, но, по-видимому, реальное оборудование Intel не удовлетворено тем, что оно плохо, пока не получит еще 6 байтов).
Счетчики производительности HW, такие как instructions_retired.any
также показывать количество команд, но, ничего не зная о конце инструкции, вы не знаете, куда поместить rdpmc
инструкция. Обивка с 0x90
NOP и просмотр количества выполненных команд, вероятно, не сработают, потому что вам нужно знать, где вырезать и начинать заполнение.
Мне интересно, почему бы Intel и AMD не ввести инструкцию для этого
Для отладки обычно вы хотите полностью разобрать инструкцию, а не просто найти границы insn. Итак, вам нужна полная библиотека программного обеспечения.
Не было бы смысла помещать микрокодированный дизассемблер за каким-то новым кодом операции.
Кроме того, аппаратные декодеры подключены только для работы как часть внешнего интерфейса в пути выборки кода, а не для передачи им произвольных данных. Они уже заняты декодированием инструкций в большинстве циклов и не готовы работать с данными. Добавление инструкций, которые декодируют байты машинного кода x86, почти наверняка будет выполнено путем репликации этого оборудования в исполнительный блок ALU, а не путем запроса кэш-памяти decoded-uop или L1i (в схемах, где границы команд отмечены в L1i) или отправки данных через фактические предварительные декодеры внешнего интерфейса и захват результата вместо постановки его в очередь для остальной части внешнего интерфейса.
Единственный реальный высокопроизводительный сценарий использования, о котором я могу подумать, - это эмуляция или поддержка новых инструкций, таких как Эмулятор разработки программного обеспечения Intel (SDE). Но если вы хотите запускать новые инструкции на старых процессорах, суть в том, что старые процессоры не знают об этих новых инструкциях.
Количество процессорного времени, затрачиваемого на разборку машинного кода, довольно мало по сравнению с количеством времени, которое процессоры затрачивают на выполнение математических операций с плавающей запятой или обработку изображений. Есть причина, по которой у нас есть такие вещи, как SIMD FMA и AVX2 vpsadbw
в наборе инструкций для ускорения тех специальных задач, которые процессоры проводят много времени, но не для вещей, которые мы можем легко сделать с помощью программного обеспечения.
Помните, смысл набора команд - сделать возможным создание высокопроизводительного кода, а не получать всю мета-информацию и специализироваться на самом декодировании.
На верхнем уровне сложности специального назначения в Nehalem были введены строковые инструкции SSE4.2. Они могут делать классные вещи, но их сложно использовать. https://www.strchr.com/strcmp_and_strlen_using_sse_4.2 (также включает в себя strstr, который является реальным вариантом использования, где pcmpistri can be faster than SSE2 or AVX2, unlike for strlen / strcmp where plain old
pcmpeqb /
pminub` работает очень хорошо, если используется эффективно (см. рукописный asm glibc). В любом случае, эти новые инструкции все еще являются многопользовательскими даже в Skylake и не используются широко. Я думаю, что компиляторам трудно выполнять автоматическое векторизацию с ними, и большая часть обработки строк выполняется на языках, где не так просто тесно интегрировать несколько встроенных функций с низкими издержками.
установка батута (для горячей установки двоичной функции.)
Даже это требует расшифровки инструкций, а не просто определения их длины.
Если первые несколько байтов инструкции функции использовали режим адресации RIP (или jcc rel8/rel32
или даже jmp
или же call
), перемещение его в другое место нарушит код. (Спасибо @Rbmn за указание этого углового случая.)