Ссылка на содержимое ячейки памяти. (режимы адресации x86)

У меня есть место в памяти, которое содержит символ, который я хочу сравнить с другим символом (и это не на вершине стека, поэтому я не могу просто pop Это). Как мне сослаться на содержимое ячейки памяти, чтобы я мог ее сравнить?

В основном, как мне это сделать синтаксически.

1 ответ

Решение

Более подробное обсуждение режимов адресации (16/32/64 бита) см. В руководстве Агнера Фога "Оптимизация сборки", раздел 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-относительных) являются подмножествами этого, которые пропускают один или несколько компонентов. Это означает, что вам не нужно обнулять 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 (Mode / Register-or-memory))
  • 2B для двухрежимных режимов (байт mod/rm + SIB (базовая шкала индекса))
  • смещение может быть 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 I$, и является более ценным ресурсом. Минимизация мопов слитых доменов обычно более важна.


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] Очевидно, что вы можете индексировать статический / глобальный массив с помощью одного регистра. Возможен даже двумерный массив с использованием двух отдельных регистров. (предварительное масштабирование с дополнительной инструкцией для коэффициентов масштабирования, отличных от 2, 4 или 8). Обратите внимание, что global_array + 10 математика делается во время ссылки. Объектный файл (вывод ассемблера, ввод компоновщика) сообщает компоновщику +10, чтобы добавить к окончательному абсолютному адресу, чтобы поместить правильное 4-байтовое смещение в исполняемый файл (вывод компоновщика). Вот почему вы не можете использовать произвольные выражения для констант времени компоновки, которые не являются константами времени сборки (например, адреса символов).

  • mov al, 0ABh Не нагрузка, а непосредственная константа, которая хранилась внутри инструкции. (Обратите внимание, что вам нужно поставить префикс 0 поэтому ассемблер знает, что это константа, а не символ. Некоторые монтажники также примут 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, только с постоянными смещениями. Вы все еще можете сделать абсолютную адресацию, и есть даже особая форма mov который может загружаться с 64- битного абсолютного адреса ( а не обычного 32-битного расширенного знака). Синтаксические вызовы AT & T вызывают код операции movabs (также используется для mov r64, imm64), в то время как синтаксис Intel/NASM все еще называет его формой mov,

    использование lea rsi, [rel global_array] чтобы получить относительные Rip-адреса в регистры, так как mov reg, imm будет жестко кодировать не относительный адрес в байтах инструкции.

    Обратите внимание, что OS X загружает весь код по адресу за пределами младших 32 бит, поэтому 32-битная абсолютная адресация неприменима. Позиционно-независимый код не требуется для исполняемых файлов, но вы также можете это сделать, потому что 64-битная абсолютная адресация менее эффективна, чем RIP-относительная. Формат объектного файла macho64 не поддерживает перемещения для 32-разрядных абсолютных адресов, как это делает Linux ELF. Убедитесь, что имя метки не используется как константа времени компиляции где-либо, кроме как с эффективным адресом [global_array + constant] потому что он может быть собран в режим адресации RIP-относительной. например [global_array + rcx] не допускается, потому что RIP нельзя использовать с любыми другими регистрами, поэтому он должен быть собран с абсолютным адресом global_array жестко запрограммирован как 32-битное смещение ( которое будет расширено до 64b).


Любой из этих режимов адресации можно использовать с LEA делать целочисленную математику с бонусом, не влияющим на флаги, независимо от того, является ли это действительным адресом. [esi*4 + 10] обычно полезно только с LEA (если смещение не является символом, а не небольшой константой). В машинном коде нет кодирования только для масштабируемого регистра, поэтому [esi*4] должен собрать в [esi*4 + 0], с 4 байтами нулей для 32-битного смещения. Часто все же стоит скопировать + сдвиг в одной инструкции вместо более коротких mov + shl, потому что обычно пропускная способность uop является более узким местом, чем размером кода, особенно на процессорах с кэш-памятью декодированного uop.


Вы можете указать переопределения сегментов, такие как mov al, fs:[esi], Переопределение сегмента просто добавляет байт префикса перед обычной кодировкой. Все остальное остается тем же, с тем же синтаксисом.

Вы даже можете использовать переопределения сегментов с RIP-относительной адресацией. 32-битная абсолютная адресация требует еще одного байта для кодирования, чем RIP-относительная, поэтому mov eax, fs:[0] может наиболее эффективно кодироваться с использованием относительного смещения, которое дает известный абсолютный адрес. т.е. выберите rel32, чтобы RIP+rel32 = 0. YASM сделает это с mov ecx, [fs: rel 0], но NASM всегда использует абсолютную адресацию disp32, игнорируя rel спецификатор. Я не проверял MASM или газ.


Если размер операнда неоднозначен (например, в инструкции с непосредственным операндом и операндом памяти), используйте 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 за эффективными адресами синтаксиса NASM и / или разделу Википедии x86 о режимах адресации. На вики-странице написано, что разрешено в 16-битном режиме. Вот еще один "шпаргалка" для 32-битных режимов адресации.

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

Также см. Вики-страницу x86 для ссылок.

Вот краткая шпаргалка, полученная с этого сайта. Он показывает различные методы, доступные для адресации основной памяти в сборке x86:

+------------------------+----------------------------+-----------------------------+
| Mode                   | Intel                      | AT&T                        |
+------------------------+----------------------------+-----------------------------+
| Absolute               | MOV EAX, [0100]            | movl           0x0100, %eax |
| Register               | MOV EAX, [ESI]             | movl           (%esi), %eax |
| Reg + Off              | MOV EAX, [EBP-8]           | movl         -8(%ebp), %eax |
| Reg*Scale + Off        | MOV EAX, [EBX*4 + 0100]    | movl   0x100(,%ebx,4), %eax |
| Base + Reg*Scale + Off | MOV EAX, [EDX + EBX*4 + 8] | movl 0x8(%edx,%ebx,4), %eax |
+------------------------+----------------------------+-----------------------------+

В вашем конкретном случае, если элемент находится на смещение от4 из базы стека EBP, вы бы использовали Reg + Off обозначение:

MOV EAX, [ EBP - 4 ]

Это скопирует элемент в реестр EAX.

Другие вопросы по тегам