Ссылка на содержимое ячейки памяти. (режимы адресации 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).- NASM:
Любой из этих режимов адресации можно использовать с 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
.