Ответ 1
Для более расширенного обсуждения режимов адресации (16/32/64 бит) см. Руководство Agner Fog "Оптимизация сборки" , раздел 3.3, В этом руководстве содержится гораздо более подробная информация, чем этот ответ для переноса на символы и 32-разрядный независимый от позиции код, между прочим.
См. также: таблица синтаксиса AT & T (GNU) против синтаксиса NASM для разных режимов адресации, включая косвенные переходы/вызовы.
Также см. коллекцию ссылок в нижней части этого ответа.
Предложения приветствуются, особенно. на которых части были полезными/интересными, а какие нет.
x86 (32 и 64 бит) имеет несколько режимов адресации на выбор. Все они имеют форму:
[base_reg + index_reg*scale + displacement] ; or a subset of this
[RIP + displacement] ; or RIP-relative: 64bit only. No index reg is allowed
(где масштаб равен 1, 2, 4 или 8, а смещение - 32-битная константа). Все остальные формы (кроме RIP-relative) являются подмножествами этого, которые не содержат один или несколько компонентов. Это означает, что вам не нужен обнуленный index_reg
для доступа к [rsi]
, например. В исходном коде ASM не имеет значения, какой порядок вы пишете: [5 + rax + rsp + 15*4 + MY_ASSEMBLER_MACRO*2]
работает нормально. (Вся математика на константах происходит во время сборки, что приводит к одному постоянному смещению.)
Все регистры должны быть того же размера, что и режим, в котором вы находитесь, кроме вы используете альтернативный размер адреса, требуя дополнительный префиксный байт. Узкие указатели редко полезны вне x32 ABI (ILP32 в длинном режиме).
Если вы хотите использовать al
в качестве индекса массива, например, вам нужно указать нуль или знак-расширьте его до ширины указателя. (Имея верхние биты rax
, уже обнуленные, прежде чем беспорядок с байтовыми регистрами иногда возможен, и это хороший способ выполнить это.)
Все возможные подмножества в общем случае кодируются, кроме тех, которые используют e/rsp*scale
(очевидно, бесполезно в "нормальном" коде, который всегда хранит указатель на стек памяти в esp
).
Обычно размер кода кодировки равен:
- 1B для однодисковых режимов (mod/rm (режим/регистр или память))
- 2B для двух регистров (байт mod/rm + SIB (Base Index Base))
- смещение может быть 0, 1 или 4 байта (с расширением знака до 32 или 64, в зависимости от размера адреса). Таким образом, перемещения из
[-128 to +127]
могут использовать более компактное кодированиеdisp8
, сохраняя 3 байта по сравнению сdisp32
.
исключения кода:
-
[reg*scale]
сам по себе может быть закодирован только с 32-битным смещением. Смарт-ассемблеры работают вокруг этого, кодируяlea eax, [rdx*2]
какlea eax, [rdx + rdx]
, но этот трюк работает только для масштабирования на 2. -
Невозможно закодировать
e/rbp
илиr13
как базовый регистр без байта смещения, поэтому[ebp]
кодируется как[ebp + byte 0]
. Кодировки без смещения сebp
в качестве базового регистра вместо этого означают отсутствие базового регистра (например, для[disp + reg*scale]
). -
[e/rsp]
требуется байт SIB, даже если нет индекса. (независимо от того, происходит ли перемещение). Кодирование mod/rm, которое будет указывать вместо[rsp]
, означает, что существует байт SIB.
См. Таблицу 2-5 в справочном руководстве Intel и в соответствующем разделе, где подробно описаны особые случаи. (Они одинаковы в 32-х и 64-битном режиме. Добавление RIP-относительной кодировки не противоречило какой-либо другой кодировке, даже без префикса REX.)
Для производительности обычно не стоит тратить дополнительную инструкцию, чтобы получить меньший машинный код x86. На процессорах Intel с кешем uop он меньше L1 я $и более ценный ресурс. Минимизация скомпилированных доменов, как правило, важнее.
Размер 16-битного адреса не может использовать байты SIB, поэтому все один и два режима адресации регистров кодируются в один бит mod/rm. reg1
может быть BX или BP, а reg2
может быть SI или DI (или вы можете использовать любой из этих 4 регистров самостоятельно). Масштабирование недоступно. 16-битный код устарел по многим причинам, включая этот, и не стоит изучать, если вам не нужно.
Обратите внимание, что 16-битные ограничения применяются в 32-битном коде при использовании префикса размера адреса, поэтому 16-битная LEA-математика является очень ограничительной. Однако вы можете обойти это: lea eax, [edx + ecx*2]
устанавливает ax = dx + cx*2
, потому что мусор в верхних битах исходных регистров не имеет эффекта.
Как они используются
Эта таблица точно не соответствует аппаратным кодировкам возможных режимов адресации, поскольку я различаю между использованием метки (например, глобальных или статических данных) и использованием небольшого постоянного смещения. Поэтому я рассматриваю режимы аппаратной адресации + поддержку ссылок для символов.
Если у вас есть указатель char array[]
в esi
,
-
mov al, esi
: недействительный, не будет собираться. Без квадратных скобок это не нагрузка. Это ошибка, потому что регистры не имеют одинакового размера. -
mov al, [esi]
загружает байт, на который указывает. -
mov al, [esi + ecx]
загружаетarray[ecx]
. -
mov al, [esi + 10]
загружаетarray[10]
. -
mov al, [esi + ecx*8 + 200]
загружаетarray[ecx*8 + 200]
-
mov al, [global_array + 10]
загружается сglobal_array[10]
. В 64-битном режиме это может быть RIP-относительный адрес. Рекомендуется использоватьDEFAULT REL
, чтобы генерировать RIP-относительные адреса по умолчанию вместо того, чтобы всегда использовать[rel global_array + 10]
. Невозможно напрямую использовать индексный регистр с RIP-относительным адресом. Обычный методlea rax, [global_array]
mov al, [rax + rcx*8 + 10]
или аналогичный. -
mov al, [global_array + ecx + edx*2 + 10]
загружается изglobal_array[ecx + edx*2 + 10]
Очевидно, вы можете индексировать статический/глобальный массив с одним регистром. Возможно даже 2D-массив с использованием двух отдельных регистров. (предварительное масштабирование с дополнительной инструкцией, для масштабных коэффициентов, отличных от 2, 4 или 8). Обратите внимание, что математикаglobal_array + 10
выполняется во время связи. Объектный файл (выход ассемблера, вход линкера) сообщает компоновщику +10, чтобы добавить к окончательному абсолютному адресу, чтобы поместить правое 4-байтовое смещение в исполняемый файл (выход компоновщика). Вот почему вы не можете использовать произвольные выражения для констант времени соединения, которые не являются константами времени сборки (например, адреса символов). -
mov al, 0ABh
Не нагрузка вообще, а вместо этого постоянная константа, которая хранилась внутри инструкции. (Обратите внимание, что вам нужно префикс a0
, чтобы ассемблер знал его как константу, а не символ. Некоторые ассемблеры также принимают0xAB
). Вы можете использовать символ в качестве непосредственной константы, чтобы получить адрес в регистр.- NASM:
mov esi, global_array
собирается вmov esi, imm32
, который помещает адрес в esi. - MASM:
mov esi, OFFSET global_array
требуется сделать то же самое. - MASM:
mov esi, global_array
собирается в нагрузку:mov esi, dword [global_array]
.
В 64-битном режиме адресация глобальных символов обычно выполняется с помощью RIP-относительной адресации, которую ваш ассемблер будет выполнять по умолчанию с помощью директивы
DEFAULT REL
или с помощьюmov al, [rel global_array + 10]
. Никакой индексный регистр не может использоваться с RIP-относительными адресами, а только постоянными смещениями. Вы все равно можете выполнять абсолютную адресацию (кроме OS X), и даже специальную формуmov
, которая может загружаться с 64-битного абсолютного адреса (а не обычный 32-битный расширенный знак.) Синтаксис AT & T вызывает код opcodemovabs
(также используемый дляmov r64, imm64
), тогда как синтаксис Intel/NASM по-прежнему называет его формойmov
.Используйте
lea esi, [rel global_array]
, чтобы получить относительные адреса rip в регистры, так какmov reg, imm
будет жестко закодировать не относительный адрес в байтах инструкций.Обратите внимание, что для OS X требуется, чтобы все 64-битные коды были независимыми от позиции, а не только разделяемыми библиотеками. Формат объектного файла macho64 не поддерживает перемещение для абсолютных адресов, как это делает Linux ELF. Обязательно не используйте имя метки как константу времени компиляции в любом месте, кроме эффективного адреса, например
[global_array + constant]
, потому что они могут быть собраны в режим относительной адресации RIP. например[global_array + ecx]
не допускается, поскольку RIP нельзя использовать с любыми другими регистрами, поэтому его нужно будет собрать с абсолютным адресомglobal_array
с жестким кодом, как 32-разрядное перемещение (который будет расширяться до 64b). - NASM:
Любой и все эти режимы адресации могут использоваться с LEA
для выполнения целочисленной математики с бонусом, не влияющим на флаги, независимо от того, является ли он действительным адресом, [esi*4 + 10]
обычно полезен только для LEA (если смещение не является символом, а не малой константой). В машинных кодах нет кодировки только для масштабированного регистра без смещения 32b, но ассемблеры просто используют нулевое смещение для кодирования [esi*4]
.
Вы можете указать переопределение сегмента, например mov al, fs:[esi]
. Отключение сегмента просто добавляет префикс-байт перед обычной кодировкой. Все остальное остается неизменным, с тем же синтаксисом.
Если размер операнда неоднозначен (например, в команде с непосредственным операндом памяти и памяти), используйте byte
/word
/dword
/qword
/xmmword
/ymmword
, чтобы указать
mov dword [rsi + 10], 0xAB ; NASM
mov dword ptr [rsi + 10], 0xAB ; MASM and GNU .intex_syntax noprefix
movl $0xAB, 10(%rsi) # GNU(AT&T): operand size from insn suffix
См. yasm docs для эффективных адресов синтаксиса NASM и/или wikipedia x86 в разделе адресации. На странице wiki говорится, что разрешено в 16-битном режиме. Здесь еще один "чит-лист" для 32-битных режимов адресации.
Там также более подробное руководство по режимам адресации для 16-битного. 16bit все еще имеет все те же режимы адресации, что и 32-битные, поэтому, если вы обнаруживаете, что режимы адресации запутывают, читайте их в любом случае
Также см. x86 wiki page для ссылок.