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

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

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


person DrakeJacks    schedule 03.12.2015    source источник


Ответы (2)


Более подробное обсуждение режимов адресации (16, 32, 64 бит) см. В руководстве Agner Fog «Оптимизация сборки» , раздел 3.3. В этом руководстве гораздо больше подробностей, чем в этом ответе, среди прочего, для перемещения символов и / или 32-битного независимого от позиции кода.

И, конечно же, в руководствах Intel и AMD есть целые разделы с подробностями о кодировании ModRM (и дополнительных байтов SIB и disp8 / disp32), что дает понять, что кодируется и почему существуют ограничения.

См. Также: таблицу синтаксиса 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 в длинном режиме), где вы, возможно, захотите игнорировать верхние 32 бита регистра, например вместо использования movsxd для расширения знака 32-битного, возможно, отрицательного смещения в регистре до 64-битной ширины указателя.

Если вы хотите использовать al в качестве индекса массива, для например, вам нужно обнулить его или расширить знаком до ширины указателя. (Иногда возможно, что верхние биты rax уже обнулены, прежде чем возиться с байтовыми регистрами, и это хороший способ добиться этого.)


Ограничения отражают то, что кодируется в машинном коде, как обычно для языка ассемблера. Коэффициент масштабирования - это 2-битный счетчик сдвига. Байты ModRM (и необязательный SIB) могут кодировать до 2 регистров, но не более, и не имеют никаких режимов, которые вычитают регистры, только добавляют. Базой может быть любой регистр. Индексом может быть любой регистр, кроме ESP / RSP. См. rbp not allowed as SIB base? для получения информации о кодировке, например, почему [rsp] всегда нужен байт SIB.

Все возможные подмножества общего случая могут быть кодированы, кроме тех, которые используют e/rsp*scale (очевидно, бесполезно в «нормальном» коде, который всегда сохраняет указатель на стековую память в esp).

Обычно размер кода кодировок составляет:

  • 1B для однорегистровых режимов (mod / rm (Mode / Register-or-memory))
  • 2B для двух регистровых режимов (mod / rm + байт SIB (Scale Index Base))
  • смещение может составлять 0, 1 или 4 байта (знаковое расширение до 32 или 64, в зависимости от размера адреса). Таким образом, смещения из [-128 to +127] могут использовать более компактную кодировку disp8, экономя 3 байта по сравнению с disp32.

ModRM присутствует всегда, и его биты сигнализируют, присутствует ли также SIB. Аналогично disp8 / disp32. Исключения размера кода:

  • [reg*scale] сам по себе может быть закодирован только с 32-битным смещением (которое, конечно, может быть нулевым). Умные ассемблеры работают над этим, кодируя lea eax, [rdx*2] как lea eax, [rdx + rdx], но этот трюк работает только для масштабирования на 2. В любом случае требуется байт SIB в дополнение к ModRM.

  • Невозможно закодировать 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 $, и является более ценным ресурсом. Как правило, более важно свести к минимуму число мопов слитных доменов.


Как они используются

(Этот вопрос был помечен как MASM, но часть этого ответа говорит о версии синтаксиса Intel для NASM, особенно там, где они различаются для адресации относительно RIP x86-64. Синтаксис AT&T не рассматривается, но имейте в виду, что это просто еще один синтаксис для того же машинный код, поэтому ограничения те же.)

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

(Примечание: обычно вам нужно movzx eax, byte [esi] или movsx, когда источник является байтом, но mov al, byte_src действительно ассемблируется и часто встречается в старом коде, сливаясь с младшим байтом EAX / RAX. См. Почему GCC не использует частичные регистры? и Как изолировать элементы массива байтов и слов в 64-битном регистре)

Если у вас есть int*, часто вы использовали бы коэффициент масштабирования для масштабирования индекса по размеру элемента массива, если у вас есть индекс элемента вместо байтового смещения. (Предпочитайте смещения байтов или указатели, чтобы избежать режимов индексированной адресации по причинам размера кода и производительности в некоторых случаях, особенно на процессорах Intel, где это может повредить микрослияние). Но вы можете делать и другие вещи.
Если у вас есть указатель char array* в esi:

  • mov al, esi: недействительно, не соберется. Без квадратных скобок это вообще не груз. Это ошибка, потому что регистры разного размера.

  • mov al, [esi] загружает указанный байт, то есть array[0] или *array.

  • 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. Рекомендуется использовать NASM DEFAULT REL, чтобы по умолчанию генерировать адреса, относящиеся к RIP, вместо того, чтобы всегда использовать [rel global_array + 10]. Думаю, MASM делает это по умолчанию. Невозможно напрямую использовать индексный регистр с относительным адресом RIP. Обычный метод - lea rax, [global_array] mov al, [rax + rcx*8 + 10] или аналогичный.

    См. Как работают ли ссылки на относительные переменные RIP, такие как [RIP + _a] в синтаксисе Intel GAS x86-64? для получения дополнительных сведений и синтаксиса для GAS .intel_syntax, NASM и синтаксиса GAS AT&T.

  • mov al, [global_array + ecx + edx*2 + 10] загружается из global_array[ecx + edx*2 + 10] Очевидно, вы можете индексировать статический / глобальный массив с помощью одного регистра. Возможен даже 2D-массив с использованием двух отдельных регистров. (предварительное масштабирование с дополнительной инструкцией для масштабных коэффициентов, отличных от 2, 4 или 8). Обратите внимание, что математика global_array + 10 выполняется во время ссылки. Объектный файл (вывод ассемблера, ввод компоновщика) информирует компоновщик о добавлении +10 к окончательному абсолютному адресу, чтобы поместить правильное 4-байтовое смещение в исполняемый файл (вывод компоновщика). Вот почему вы не можете использовать произвольные выражения для констант времени компоновки, которые не являются константами времени сборки (например, символ адреса).

    В 64-битном режиме для этого по-прежнему требуется global_array как 32-битный абсолютный адрес для части disp32, которая работает только в позиционно-зависимый исполняемый файл Linux или largeaddressaware = no Windows.

  • mov al, 0ABh Совсем не загрузка, а немедленная константа, которая хранилась внутри инструкции. (Обратите внимание, что вам нужно поставить префикс 0, чтобы ассемблер знал, что это константа, а не символ. Некоторые ассемблеры также принимают 0xAB, а некоторые из них не принимают 0ABh: подробнее).

    Вы можете использовать символ как непосредственную константу, чтобы получить адрес в регистре:

    • NASM: mov esi, global_array assembles into a mov esi, imm32 that puts the address into esi.
    • MASM: mov esi, OFFSET global_array нужно сделать то же самое.
    • MASM: mov esi, global_array собирается в нагрузку: mov esi, dword [global_array].

    In 64-bit mode, the standard way to put a symbol address into a register is a RIP-relative LEA. Syntax varies by assembler. MASM does it by default. NASM needs a default rel directive, or [rel global_array]. GAS needs it explicitly in every addressing mode. How to load address of function or label into register in GNU Assembler. mov r64, imm64 is usually supported too, for 64-bit absolute addressing, but is normally the slowest option (code size creates front-end bottlenecks). mov rdi, format_string / call printf typically works in NASM, but is not efficient.

    В качестве оптимизации, когда адреса могут быть представлены как 32-битные абсолютные (вместо смещения rel32 от текущей позиции), mov reg, imm32 по-прежнему оптимален, как и в 32-битном коде. (Исполняемый файл Linux без PIE или Windows с LargeAddressAware = no). Но обратите внимание, что в 32-битном режиме lea eax, [array] не эффективен: он тратит байт размера кода (ModRM + absolute disp32) и не может работать на таком количестве портов выполнения, как mov eax, imm32. 32-битный режим не имеет относительной адресации RIP.

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


Любые и все эти режимы адресации можно использовать с LEA для выполнения целочисленных вычислений с бонус не влияет на флаги, независимо от того, является ли это действительным адресом. Использование LEA для значений, не являющихся адресами / указатели?

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


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

Вы даже можете использовать переопределения сегментов с относительной адресацией 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, чтобы указать:

mov       dword [rsi + 10], 123   ; NASM
mov   dword ptr [rsi + 10], 123   ; MASM and GNU .intex_syntax noprefix

movl      $123, 10(%rsi)         # GNU(AT&T): operand size from mnemonic suffix

См. документацию yasm для эффективных адресов синтаксиса NASM и / или раздел wikipedia x86 о режимах адресации.

На вики-странице указано, что разрешено в 16-битном режиме. Вот еще одна «шпаргалка» для 32-битных режимов адресации.


16-битные режимы адресации

Размер адреса 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, потому что мусор в верхних битах исходных регистров не действует.

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


Похожие темы:

Многие из них также указаны выше, но не все.

person Peter Cordes    schedule 03.12.2015
comment
Однако 16-битный код все еще существует. И пользователь пометил это как DOS, поэтому объяснение 16-битных ограничений, вероятно, будет разумным для любого, кто наткнется на этот вопрос и ответ. Лучшее эмпирическое правило, которое я видел, которое достаточно легко понять и запомнить, можно найти в Разделе 1.2.7 Простой способ запомнить режимы адресации памяти 8086 этого документ. Я считаю, что это лучшее описание, чем статья в Wiki, на которую вы ссылаетесь - person Michael Petch; 03.12.2015
comment
@MichaelPetch: да, вот почему я связал статью в Википедии, поскольку, как я уже сказал, она показывает, какие регистры могут использоваться в качестве какого компонента адреса. - person Peter Cordes; 03.12.2015
comment
Нет, это не статья в Wiki. В вики-статье нет подробного объяснения того, как вы смешиваете и сопоставляете эти строки и столбцы. Я помог кому-то в прошлом году на этом сайте, они не понимали версию Wiki, но соединились с другой. - person Michael Petch; 03.12.2015
comment
@MichaelPetch: Я имел в виду, что увидел тег DOS и приложил больше усилий, чем в противном случае. Я не сказал, что википедал так подробно, как ваша ссылка, что довольно приятно. :) Если бы он также включал такой учебник / руководство для 32-битных режимов адресации, я бы добавил его в вики-страницу тегов x86. Возможно, стоит это сделать, отметив, что 32- и 64-разрядные версии не имеют одинаковых ограничений. Кажется, что не хватает хороших руководств / руководств, которые точно описывают, как писать операндную часть инструкций, которые документируются руководствами insn ref. - person Peter Cordes; 03.12.2015
comment
@MichaelPetch: Я привел в порядок и расширил это, и связал с ним из вики-страницы тегов x86. Не могли бы вы вычитать его, поскольку теперь он является частью официальной документации SO по x86. (или будет, когда эта правка вики-тега будет одобрена). - person Peter Cordes; 03.12.2015
comment
Возможно, стоит упомянуть, что [OSX не разрешает global_array + [10]] (stackoverflow.com/questions/26927278/). - person Z boson; 03.12.2015
comment
Мне любопытно, каков масштаб. Обычно, когда я просматриваю массив, я увеличиваю reg2. Так что я могу либо сделать add 4, reg2 и использовать scale=4, либо сделать add 16, reg2 и использовать scale=1. До сих пор я всегда использовал scale=1. Единственная причина, по которой я могу использовать scale>1, - это то, что мне нужно как значение итератора, так и итератора scale *. - person Z boson; 03.12.2015
comment
@Zboson: если у вас есть два массива элементов разного размера, вы можете использовать один индекс для обхода обоих из них. И да, иногда вам действительно нужно значение итератора. Или кто-то передал вам индекс массива вместо указателя. Или есть структура данных с индексами. i=a[i] - ›mov eax, [esi + eax*4]. В старой школе 8086, я думаю, loop был довольно распространенным явлением, но вы не можете использовать cx в качестве индексного регистра, поэтому IDK. Может, для этого случая люди писали циклы с dec. Может, они не могли придумать, что еще делать с двумя запасными частями? Однако кодирование большего количества регистров имело бы смысл! - person Peter Cordes; 03.12.2015
comment
@Zboson: На самом деле [global_array + 10] разрешен, потому что он может быть закодирован как относительный RIP (поскольку нет смещений регистров). Тем не менее, спасибо за предложение, упомянув предостережение OS X. Хорошая точка зрения. - person Peter Cordes; 04.12.2015
comment
Другой режим адресации - использование RIP-relative. Конечно, вы все еще можете использовать глобальные массивы (я предпочитаю использовать термин статически распределенный массив) с OSX. Было бы довольно глупо, если бы ты не мог. Но вы не можете сделать [абсолютный 32-битный адрес + регистр] с OSX. Это то, что читатель вашего ответа должен предположить, что означает [global_array + 10]. - person Z boson; 04.12.2015
comment
@Zboson: я перейду к abs и rel в более позднем пункте о 64-битной версии, и я не хотел загромождать раннюю часть ответа. Как только вы узнаете об относительной адресации RIP и DEFAULT REL, вы должны думать об этом каждый раз, когда видите метку внутри эффективного адреса. - person Peter Cordes; 04.12.2015
comment
В этом случае я не согласен с тем, что это обычно родственник RIP. Ни NASM, ни YASM, ни GAS по умолчанию не используют RIP. GCC не будет использовать относительный протокол RIP по умолчанию для статически размещенных массивов. Он использует абсолютные адреса. См. Раздел о режимах адресации в руководстве по сборке Agner. - person Z boson; 04.12.2015
comment
он пишет Обратите внимание, что ассемблеры NASM, YASM и Gnu могут создавать 32-битные абсолютные адреса, если вы не указываете явно относительные адреса для копирования. Вы должны указать rel по умолчанию в NASM / YASM или [mem + rip] в Gas, чтобы избежать 32-битных абсолютных адресов. Относительный RIP не используется по умолчанию в Linux. В Linux это не нужно. Это обязательно с OSX. В Windows это необходимо только для библиотек DLL, но MSVC по-прежнему использует RIP по умолчанию. - person Z boson; 04.12.2015
comment
Я не хочу показаться грубым, но я думаю, что Агнер гораздо лучше справляется с объяснением режимов адресации. Конечно, он использует для этого несколько страниц и приводит несколько примеров. Я бы посоветовал людям увидеть раздел 3.3 руководства Агнера в начале вашего вопроса. Он подробно описывает 16-битные, 32-битные и 64-битные версии. - person Z boson; 04.12.2015
comment
@Zboson: спасибо! Я хотел сделать это кратким и компактным, поэтому хороший внешний ресурс для цитирования - это именно то, что мне нужно. Что вы думаете о моем недавнем правке? Вы убедили меня в том, что было бы уместно не замалчивать вопрос, связанный с RIP, так, как я это делал раньше. - person Peter Cordes; 04.12.2015
comment
Думаю, твой новый ответ лучше. Я согласен с тем, что относительный адрес RIP в целом лучше. Но было бы невозможно получить эту идею в моем вопрос здесь без абсолютной 32-битной адресации. - person Z boson; 04.12.2015
comment
@PeterCordes - ваше описание хорошее, как и руководство Anger, но ни одно из них не описывает, какие размеры регистров могут использоваться в различных режимах (32-битный, 64-битный) - в частности, как используется префикс размера адреса. В настоящее время ваш ответ на самом деле совсем не касается этого (поскольку он в основном ориентирован на 32-разрядную и 64-разрядную версии, с некоторыми упоминаниями о более интересных различиях, таких как материал, относящийся к RIP). Руководство Агнера, похоже, даже не упоминает о возможности - для 64-битных он упоминает только 32-битные абсолютные адреса, но это только смещение - регистр 64-битный. - person BeeOnRope; 05.10.2016
comment
@BeeOnRope: Верно, я предполагал размер адреса по умолчанию. Я думаю, добавление строки о том, что регистры имеют тот же размер, что и размер адреса, открыло дверь для обсуждения этого вопроса. Я думаю, если вы напишете функцию, которая принимает 32-битное целое число, которое вы хотите использовать в качестве индекса в статической таблице, вы можете сохранить инструкцию до нуля или расширить ее знаком, если вы просто используете 32-битный размер адреса. Это довольно непонятно, поскольку вы не можете использовать его с произвольными указателями, кроме x32 ABI (ILP32 в длинном режиме). - person Peter Cordes; 06.10.2016
comment
Да, большинство вариантов использования кажутся довольно неясными (это также может пригодиться с lea, помимо фактических вычислений адресов). Мой первоначальный запрос заключался в том, чтобы использовать неявное поведение нулевого расширения, которое еще более эффективно с байтовыми регистрами, поскольку у вас есть как разновидности l, так и h. Итак, если у вас есть rax, который концептуально содержит смещения размером 8 байт, вы можете использовать как ah, так и al, чтобы бесплатно выбрать два смещения при вычислении адресации, а затем сдвинуть вправо на 16 бит. Лучше, чем куча mov ebx, eax, потом and ebx, 0xFF и тд. - person BeeOnRope; 06.10.2016
comment
Другие возможные варианты использования могут заключаться в том, что вам нужно 32-битное поведение переполнения / переноса для вычисления адреса: это может пригодиться в различных сценариях типа JIT / интерпретатора и т. Д., Где вы выделили область 4 ГБ и хотите сохранить все доступы в границах без явной проверки границ (аналогично для 16-битных регистров и 64К регионов). В любом случае, я обновил этот ответ, добавив к нему небольшую заметку, но короткую, поскольку, как вы указываете, она довольно неясна. - person BeeOnRope; 06.10.2016
comment
@BeeOnRope: С LEA это никогда не пригодится. Просто используйте размер операнда по умолчанию для усечения результата до 32-битного вместо использования префиксов размера операнда и размера адреса. Младшие 32 бита 64-битных сложений и сдвигов влево не зависят от старших битов, потому что перенос и сдвиг распространяются справа налево. Дизассемблер objconv Agner Fog даже обращает внимание на избыточные префиксы размера адреса в 32-битных LEA размером с операнд. Однако интересный момент с JIT. - person Peter Cordes; 06.10.2016
comment
@BeeOnRope: В итоге я переписал абзац с размером адреса. Я не думал, что каталогизация всех вариантов была полезной, поскольку изменение размера адреса так редко бывает полезным, что я бы предпочел просто ссылку на ваш вопрос для получения подробной информации. Похоже ли, что мой новый текст сказал вам то, что вы хотели знать, если бы вы нашли его изначально? - person Peter Cordes; 06.10.2016
comment
@BeeOnRope: re: распаковка байтовых индексов: оптимальный способ сделать это с помощью movzx ecx, al / movzx edx, ah / shr rax, 16. Я действительно изучал это при настройке функции GaloisField16 для кодов исправления ошибок Рида-Соломона, используемых par2. (gcc.gnu.org/bugzilla/show_bug.cgi?id=67072 и stackoverflow.com/questions/31734263/). Интересный факт: IvyBridge может выполнять удаление movzx reg, reg, а Haswell - нет. Они решили, что это перебор, особенно. с дополнительным портом ALU Haswell, я думаю. - person Peter Cordes; 06.10.2016
comment
@PeterCordes - Я бы не сказал, что 32-битная lea никогда не нужна. Если я вас правильно понял, вы имеете в виду Просто используйте размер операнда по умолчанию, чтобы усечь результат до 32-битного ... [в последующей операции, которая использует результат lea], но это не всегда возможно. Например, последующая операция может быть 64-битной. Это не гипотетически: я видел, как это произошло, потому что (а) вы хотели использовать неявное усечение до 32-битной версии для реализации операции типа & 0xFFFFFFFFF, а также (б) когда результат был в одной ветви ветви, а в другой leg дает 64-битный действительный результат. - person BeeOnRope; 21.03.2017
comment
@BeeOnRope: Нет, я имею в виду использовать LEA с размером операнда по умолчанию (32-битный), чтобы записать 32-битный результат с расширением нуля до 64. например. (плохо) lea rax, [ecx + ebx*4] всегда дает тот же результат, что и (хорошо) lea eax, [rcx + rbx*4], но требует два дополнительных байта префикса. 32-битный размер адреса для LEA никогда не бывает полезен, потому что вы всегда можете получить тот же результат и без него. Старшие биты входных регистров не могут влиять на младшие биты результата для сложения или сдвига влево. - person Peter Cordes; 21.03.2017
comment
Ах да, да - почему-то я подумал, что размер операнда по умолчанию для lea был также 64-битным, как размер адреса и что вы говорите что-то еще. Итак, я предполагаю, что можно сказать, что из 4 возможных комбинаций размеров lea формы lea eax, [rbx + ...] и lea rax [rbx +...] полезны, а две другие формы совершенно бесполезны (за исключением всегда-эзотерического варианта намеренного использования более длинных инструкций для целей выравнивания). - person BeeOnRope; 21.03.2017
comment
@BeeOnRope: верно. Наличие 64-битного размера операнда LEA по умолчанию, вероятно, будет выигрышем для плотности кода, но, вероятно, будет стоить транзисторов (и мощности?) В декодерах. Возможно, не так много, если бы не было возможности переключить его обратно на 32-битную версию. И вы все равно можете обрезать результат, используя префикс размера адреса. Если в префиксе размера адреса нет чего-то дорогого (в микроархитектуре AMD k8, для которой они проектировали AMD64) ... я забыл. - person Peter Cordes; 22.03.2017
comment
Да, я думаю, это последовательно и непоследовательно одновременно :) - person BeeOnRope; 23.03.2017
comment
Спасибо за такой замечательный ответ! один вопрос, пожалуйста: возможно ли переопределение сегмента для адреса, относящегося к компьютеру? Я знаю, что это было бы немного бессмысленно, но я хочу знать, разрешено ли это - person Zhani Baramidze; 30.03.2018
comment
@ZhaniBaramidze: да. [fs: 0] можно закодировать в 32-битном абсолютном режиме с disp32 = 0 или (на 1 байт короче) с RIP + rel32, где rel32 выбран так, что RIP + rel32 = 0. Использование относительной адресации для генерации абсолютных адресов работает только в коде, зависимом от позиции, потому что адрес кода должен быть известной константой (известной во время связывания). И должен быть в пределах 2 ГБ от желаемого абсолютного адреса. - person Peter Cordes; 30.03.2018

Вот краткая шпаргалка, полученная с этого сайта . Он показывает различные методы, доступные для адресации основной памяти в сборке 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.

person Jet Blue    schedule 12.08.2019