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

AMD64 - инструкция по сборке nopw?

В этом компиляторе я пытаюсь понять, как работает машинное кодирование команды nopw:

00000000004004d0 <main>:
  4004d0:       eb fe                   jmp    4004d0 <main>
  4004d2:       66 66 66 66 66 2e 0f    nopw   %cs:0x0(%rax,%rax,1)
  4004d9:       1f 84 00 00 00 00 00

В разделе http://john.freml.in/amd64-nopl обсуждается обсуждение "nopw". Может ли кто-нибудь объяснить значение 4004d2-4004e0? Из просмотра списка опций кажется, что коды 66 .. являются многобайтовыми расширениями. Я чувствую, что, возможно, я получу лучший ответ на этот вопрос, чем если бы я не попытался перекрыть список опкодов на несколько часов.


Этот вывод asm происходит из следующего (безумного) кода в C, который оптимизируется до простого бесконечного цикла:

long i = 0;

main() {
    recurse();
}

recurse() {
    i++;
    recurse();
}

При компиляции с gcc -O2 компилятор распознает бесконечную рекурсию и превращает ее в бесконечный цикл; он делает это так хорошо, фактически, что он фактически петли в main(), не вызывая функцию recurse().


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

4b9b3361

Ответ 1

Байт 0x66 - это префикс "Опередность размера". Наличие более чем одного из них эквивалентно наличию одного.

0x2e - это префикс "нуль" в 64-битном режиме (в противном случае это переопределение CS: сегмент - вот почему он появляется в сборной мнемонике).

0x0f 0x1f - это 2-байтовый код операции для NOP, который принимает байт ModRM

0x84 ModRM byte, который в этом случае кодирует режим адресации, который использует еще 5 байтов.

Некоторые процессоры медленно декодируют инструкции со многими префиксами (например, более трех), поэтому байт ModRM, который указывает SIB + disp32, является гораздо лучшим способом использования дополнительных 5 байтов, чем еще пять префиксных байтов.

AMD K8 декодеры в микрогартере Agner Fog pdf:

Каждый из декодеров команд может обрабатывать три префикса за такт цикл. Это означает, что три команды с тремя префиксами могут декодироваться в одном такте. Инструкция с 4 - 6 префиксами требуется дополнительный тактовый цикл для декодирования.


По сути, эти байты - это одна длинная инструкция NOP, которая никогда не будет выполнена. Он там, чтобы гарантировать, что следующая функция выровнена на 16-байтной границе, потому что компилятор выбрал директиву .p2align 4, поэтому ассемблер дополнен NOP. gcc default для x86 is
-falign-functions=16
. Для NOP, которые будут выполнены, оптимальный выбор длинного NOP зависит от микроархитектуры. Для микроархитектуры, которая забивает многие префиксы, такие как Intel Silvermont или AMD K8, два NOP с тремя префиксами, каждый из которых мог бы быстрее декодировать.

В статье в блоге связан вопрос (http://john.freml.in/amd64-nopl), объясняет, почему компилятор использует сложную единую инструкцию NOP вместо связки однобайтовые команды 0x90 NOP.

Подробные сведения о кодировке команд в документах технической документации AMD:

В основном в "Руководстве по программированию архитектуры AMD64 Volume Volume 3: General Purpose and System Instructions". Я уверен, что технические рекомендации Intel для архитектуры x64 будут иметь одинаковую информацию (и могут быть даже более понятными).

Ответ 2

Ассемблер (а не компилятор) прокладывает код до следующей границы выравнивания с самой длинной инструкцией NOP, которую он может найти, которая подходит. Это то, что вы видите.

Ответ 3

Я бы предположил, что это всего лишь инструкция задержки ветвления.

Ответ 4

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