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

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

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

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


Любой и все эти режимы адресации могут использоваться с 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 для ссылок.