Какова цель инструкции LEA?

676

Для меня это просто похоже на фанк MOV. Каково его назначение и когда я должен его использовать?

user200557
источник
2
Смотрите также Использование LEA для значений, которые не являются адресами / указателями? : LEA - это просто инструкция сдвига и добавления. Вероятно, он был добавлен в 8086, потому что аппаратное обеспечение уже существует для декодирования и вычисления режимов адресации, а не потому, что оно «предназначено» только для использования с адресами. Помните, что указатели - это просто целые числа в сборке.
Питер Кордес

Ответы:

798

Как уже отмечали другие, LEA (эффективный адрес загрузки) часто используется как «хитрость» для выполнения определенных вычислений, но это не является его основной целью. Набор команд x86 был разработан для поддержки языков высокого уровня, таких как Pascal и C, где массивы - особенно массивы целых или небольших структур - распространены. Рассмотрим, например, структуру, представляющую (x, y) координаты:

struct Point
{
     int xcoord;
     int ycoord;
};

Теперь представьте себе заявление вроде:

int y = points[i].ycoord;

где points[]массив Point. Предполагая , что база массива уже EBX, и переменный iв EAX, а xcoordи ycoordкаждый представляет 32 бит (так ycoordкак по смещению 4 байта в структурах), это утверждение может быть составлено с:

MOV EDX, [EBX + 8*EAX + 4]    ; right side is "effective address"

который приземлится yв EDX. Коэффициент масштабирования равен 8, потому что каждый Pointимеет размер 8 байт. Теперь рассмотрим то же выражение, которое используется с оператором "address of" &:

int *p = &points[i].ycoord;

В этом случае вам нужно не значение ycoord, а его адрес. Вот где LEA(эффективный адрес загрузки) приходит. Вместо MOV, компилятор может генерировать

LEA ESI, [EBX + 8*EAX + 4]

который загрузит адрес в ESI.

IJ Кеннеди
источник
112
Разве не было бы чище расширить movинструкцию и убрать скобки? MOV EDX, EBX + 8*EAX + 4
Натан Еллин
14
@imacake Заменив LEA специализированным MOV, вы сохраните синтаксис в чистоте: скобки [] всегда эквивалентны разыменованию указателя в C. Без скобок вы всегда имеете дело с самим указателем.
Натан Еллин
139
Выполнение математических операций в инструкции MOV (EBX + 8 * EAX + 4) недопустимо. LEA ESI, [EBX + 8 * EAX + 4] действительна, потому что это режим адресации, поддерживаемый x86. en.wikipedia.org/wiki/X86#Addressing_modes
Эрик
30
@JonathanDickinson LEA похож MOVна косвенный источник, за исключением того, что он только косвенный, а не MOV. На самом деле он не читает с вычисленного адреса, просто вычисляет его.
Хоббс
24
Эрик, комментарий к туру не точный. MOV eax, [ebx + 8 * ecx + 4] действителен. Однако MOV возвращает содержимое ячейки памяти, тогда как LEA возвращает адрес
Олорин,
562

Из «Дзен Собрания» Абраша:

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

Что это нам дает? Две вещи, которые ADDне обеспечивают:

  1. способность выполнять сложение с двумя или тремя операндами, и
  2. возможность сохранения результата в любом регистре; не только один из исходных операндов.

И LEAне меняет флаги.

Примеры

  • LEA EAX, [ EAX + EBX + 1234567 ]вычисляет EAX + EBX + 1234567(это три операнда)
  • LEA EAX, [ EBX + ECX ]рассчитывает EBX + ECXбез переопределения либо с результатом.
  • умножение на константу (на два, три, пять или девять), если вы используете это как LEA EAX, [ EBX + N * EBX ](N может быть 1,2,4,8).

Другой вариант использования удобен в циклах: разница между LEA EAX, [ EAX + 1 ]и INC EAXзаключается в том, что последний меняется, EFLAGSа первый нет; это сохраняет CMPсостояние.

Фрэнк Крюгер
источник
42
@AbidRahmanK несколько примеров: LEA EAX, [ EAX + EBX + 1234567 ]вычисляет сумму EAX, EBXи 1234567(это три операнда). LEA EAX, [ EBX + ECX ]рассчитывает EBX + ECX без переопределения либо с результатом. Третье, для чего LEAиспользуется (не перечислено Фрэнком) умножение на константу (на два, три, пять или девять), если вы используете это как LEA EAX, [ EBX + N * EBX ]( Nможет быть 1,2,4,8). Другой вариант использования удобен в циклах: разница между LEA EAX, [ EAX + 1 ]и INC EAXзаключается в том, что последний меняется, EFLAGSа первый нет; это сохраняет CMPсостояние
FrankH.
@FrankH. Я до сих пор не понимаю, поэтому он загружает указатель куда-то еще?
6
@ ripDaddy69 да, вроде как - если под «нагрузкой» вы подразумеваете «выполняет вычисление адреса / арифметику указателя». Он не обращается к памяти (то есть не «разыменовывает» указатель, как его называют в терминах программирования на языке Си).
ФрэнкХ.
2
+1: Это ясно LEAпоказывает, какие виды «уловок» можно использовать для ... (см. «LEA (эффективный адрес загрузки) часто используется в качестве« уловки »для выполнения определенных вычислений» в популярном ответе IJ Кеннеди выше)
Асад Эбрахим
3
Существует большая разница между 2 операндами LEA, которые являются быстрыми, и 3 операндами LEA, которые являются медленными. В руководстве по оптимизации Intel говорится, что LEA с быстрым путем - это один цикл, а LEA с быстрым путем - три цикла. Кроме того, в Skylake есть два функциональных блока быстрого доступа (порты 1 и 5) и только один функциональный блок медленного пути (порт 1). Кодировка сборки / компиляции Правило 33 в руководстве даже предостерегает от использования 3 операндов LEA.
Олсонист
110

Еще одной важной особенностью LEAинструкции является то, что она не изменяет коды состояния, такие как CFи ZF, при вычислении адреса с помощью арифметических команд, таких как ADDили MULделает. Эта функция снижает уровень зависимости между инструкциями и тем самым освобождает место для дальнейшей оптимизации компилятором или аппаратным планировщиком.

Ангус Ли
источник
1
Да, leaиногда полезно, чтобы компилятор (или человеческий кодер) выполнял математику, не забивая результат флага. Но leaне быстрее чем add. Большинство инструкций x86 пишут флаги. Высокопроизводительные реализации x86 должны переименовывать EFLAGS или иным образом избегать опасности записи после записи для нормального выполнения кода, поэтому инструкции, которые избегают флаговых записей, не лучше из-за этого. ( неполный флаг может создать проблемы, см. инструкцию INC против ADD 1: имеет ли это значение? )
Питер Кордес
2
@PeterCordes: Ненавижу поднимать это здесь, но - я один думаю, что этот новый тег [x86-lea] является избыточным и ненужным?
Майкл Петч
2
@MichaelPetch: Да, я думаю, что это слишком конкретно. Кажется, смущает новичка, который не понимает машинный язык, и что все (включая указатели) являются просто битами / байтами / целыми числами, поэтому есть много вопросов об этом с огромным количеством голосов. Но наличие тега для него подразумевает, что есть место для неограниченного числа будущих вопросов, когда на самом деле их около 2 или 3, которые не являются просто дубликатами. (что это такое? Как использовать его для умножения целых чисел? и как оно работает внутри AGU против ALU и с какой задержкой / пропускной способностью. И, возможно, это «намеченная» цель)
Питер Кордес,
@PeterCordes: Я согласен, и в любом случае все эти редактируемые посты в значительной степени дублируют некоторые из существующих вопросов, связанных с LEA. Вместо тэга любые дубликаты должны быть идентифицированы и помечены как imho.
Майкл Петч
1
@EvanCarroll: повесьте теги на все вопросы LEA, если вы еще не закончили. Как уже говорилось выше, мы считаем, что x86-lea слишком специфична для тега, и у нас не так много возможностей для вопросов, не повторяющихся в будущем. Я думаю, что было бы много работы, чтобы на самом деле выбрать «лучшие» вопросы и ответы в качестве цели дублирования для большинства из них, или же решить, какие из них получить моды для слияния.
Питер Кордес
93

Несмотря на все объяснения, LEA является арифметической операцией:

LEA Rt, [Rs1+a*Rs2+b] =>  Rt = Rs1 + a*Rs2 + b

Просто его название крайне глупо для операции shift + add. Причина этого уже была объяснена в самых рейтинговых ответах (т. Е. Она была разработана для непосредственного сопоставления высокоуровневых ссылок на память).

hdante
источник
8
И что арифметика выполняется аппаратным обеспечением для вычисления адреса.
Бен Фойгт
30
@BenVoigt Раньше я это говорил, потому что я старый парень :-) Традиционно процессоры x86 для этого использовали единицы адресации, согласились. Но «разделение» стало очень размытым в наши дни. У некоторых процессоров больше нет выделенных AGU, другие решили не выполнять их LEAна AGU, а на обычных целочисленных ALU. В наши дни нужно очень внимательно прочитать спецификации процессора, чтобы выяснить, «где все работает» ...
ФрэнкХ.
2
@FrankH .: ЦП с ошибками обычно запускают LEA на ALU, в то время как некоторые ЦП (например, Atom) работают с AGU (потому что они не могут быть заняты обработкой доступа к памяти).
Питер Кордес
3
Нет, имя не глупое. LEAдает вам адрес, который возникает в любом режиме адресации, связанном с памятью. Это не операция сдвига и добавления.
Каз,
3
FWIW очень мало (если таковые имеются) текущих процессоров x86, которые выполняют операции на AGU. Большинство или все просто используют ALU, как и любой другой арифметический оператор.
BeeOnRope
77

Может быть, просто еще одна вещь о инструкции LEA. Вы также можете использовать LEA для быстрого умножения регистров на 3, 5 или 9.

LEA EAX, [EAX * 2 + EAX]   ;EAX = EAX * 3
LEA EAX, [EAX * 4 + EAX]   ;EAX = EAX * 5
LEA EAX, [EAX * 8 + EAX]   ;EAX = EAX * 9
ГДж.
источник
13
+1 за трюк. Но я хотел бы задать вопрос (может быть, глупо), почему бы не умножить прямо на три, как это LEA EAX, [EAX*3]?
Абид Рахман К
13
@ Абид Рахман К. Нет таких инструкций как набор инструкций для процессора x86.
ГДж.
50
@AbidRahmanK, несмотря на то, что синтаксис intel asm выглядит как умножение, инструкция lea может кодировать только операции сдвига. Код операции имеет 2 бита для описания сдвига, поэтому вы можете умножить его только на 1,2,4 или 8.
ithkuil
6
@Koray Tugay: Вы можете использовать shlкоманду shift left как инструкцию для умножения регистров на 2,4,8,16 ... это быстрее и короче. Но для умножения на числа, отличающиеся степенью 2, мы обычно используем mulинструкцию, которая более претенциозна и медленнее.
ГДж.
8
@GJ. хотя такой кодировки нет, некоторые ассемблеры принимают это как ярлык, например, fasm. Так, например, lea eax,[eax*3]будет переводить в эквивалент lea eax,[eax+eax*2].
Руслан
59

leaэто сокращение от "эффективный адрес загрузки". Он загружает адрес ссылки на местоположение исходного операнда в целевой операнд. Например, вы можете использовать его для:

lea ebx, [ebx+eax*8]

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

Мехрдад Афшари
источник
23

Самая большая причина, по которой вы используете « LEAа», MOVзаключается в том, что вам нужно выполнить арифметику с регистрами, которые вы используете для вычисления адреса. По сути, вы можете выполнить то, что равнозначно арифметике указателей в нескольких регистрах в комбинации, для «бесплатно».

Что действительно сбивает с толку, так это то, что вы обычно пишете LEAкак a, MOVно на самом деле вы не разыменовываете память. Другими словами:

MOV EAX, [ESP+4]

Это переместит содержание того, на что ESP+4указывает EAX.

LEA EAX, [EBX*8]

Это переместит эффективный адрес EBX * 8в EAX, а не тот, который находится в этом месте. Как вы можете видеть, также можно умножить на два (масштабирование), в то время как a MOVограничено сложением / вычитанием.

Дэвид Хоэлзер
источник
Простите всех. @ big.heart одурачил меня, дав ответ на это три часа назад, и он стал «новым» в моем поиске вопросов на Ассамблее.
Дэвид Хоэлзер
1
Почему в синтаксисе используются скобки, если он не выполняет адресацию памяти?
Голопот
3
@ q4w56 Это одна из тех вещей, где ответ: «Вот как ты это делаешь». Я полагаю, что это одна из причин того, что людям так сложно понять, что LEAделает.
Дэвид Хоэлзер
2
@ q4w56: это инструкция shift + add, использующая синтаксис операнда памяти и кодирование машинного кода. На некоторых процессорах он может даже использовать аппаратное обеспечение AGU, но это историческая деталь. Все еще актуальным фактом является то, что аппаратное обеспечение декодера уже существует для декодирования этого вида shift + add, и LEA позволяет нам использовать его для арифметики вместо адресации памяти. (Или для вычисления адреса, если один вход фактически является указателем).
Питер Кордес
20

8086 имеет большое семейство инструкций, которые принимают операнд регистра и эффективный адрес, выполняют некоторые вычисления, чтобы вычислить смещенную часть этого эффективного адреса, и выполняют некоторые операции, включающие регистр и память, на которые ссылается вычисленный адрес. Было довольно просто заставить одну из инструкций в этом семействе вести себя так же, как указано выше, за исключением того, что пропускала эту фактическую операцию с памятью. Это, инструкции:

mov ax,[bx+si+5]
lea ax,[bx+si+5]

были реализованы почти одинаково внутри. Разница - пропущенный шаг. Обе инструкции работают примерно так:

temp = fetched immediate operand (5)
temp += bx
temp += si
address_out = temp  (skipped for LEA)
trigger 16-bit read  (skipped for LEA)
temp = data_in  (skipped for LEA)
ax = temp

Что касается того, почему Intel считает, что эта инструкция стоит того, чтобы ее включить, я не совсем уверен, но тот факт, что ее реализация была дешевой, был бы важным фактором. Другим фактором мог быть тот факт, что ассемблер Intel позволял определять символы относительно регистра BP. Если бы он fnordбыл определен как символ, относящийся к BP (например, BP + 8), можно сказать:

mov ax,fnord  ; Equivalent to "mov ax,[BP+8]"

Если кто-то хотел использовать что-то вроде stosw для хранения данных по адресу, относящемуся к BP, он мог сказать

mov ax,0 ; Data to store
mov cx,16 ; Number of words
lea di,fnord
rep movs fnord  ; Address is ignored EXCEPT to note that it's an SS-relative word ptr

было удобнее чем:

mov ax,0 ; Data to store
mov cx,16 ; Number of words
mov di,bp
add di,offset fnord (i.e. 8)
rep movs fnord  ; Address is ignored EXCEPT to note that it's an SS-relative word ptr

Обратите внимание, что если забыть о «смещении» мира, в DI будет добавлено содержимое местоположения [BP + 8], а не значение 8. К сожалению.

Supercat
источник
12

Как уже упоминалось в существующих ответах, LEAимеет преимущества выполнения арифметики адресации памяти без обращения к памяти, сохранения арифметического результата в другом регистре вместо простой формы инструкции добавления. Реальное основное преимущество в производительности заключается в том, что современный процессор имеет отдельный блок LEA ALU и порт для эффективной генерации адреса (включая LEAи другой ссылочный адрес памяти), это означает, что арифметическая операция в LEAи другая обычная арифметическая операция в ALU может выполняться параллельно в одном ядро.

Посмотрите эту статью об архитектуре Haswell, чтобы узнать подробности об устройстве LEA: http://www.realworldtech.com/haswell-cpu/4/

Другим важным моментом, который не упоминается в других ответах, является LEA REG, [MemoryAddress]инструкция PIC (позиционно-независимый код), которая кодирует относительный адрес ПК в этой инструкции для справки MemoryAddress. Это отличается от того, MOV REG, MemoryAddressчто кодирует относительный виртуальный адрес и требует перемещения / исправления в современных операционных системах (например, ASLR является обычной функцией). Так что LEAможет быть использован для преобразования таких не PIC в PIC.

Thomson
источник
2
«Отдельная часть LEA ALU» в основном не соответствует действительности. Современные процессоры выполняются leaна одном или нескольких из тех же ALU, которые выполняют другие арифметические инструкции (но, как правило, их меньше, чем других арифметических операций). Например, упомянутый процессор Haswell может выполнять addили выполнять subбольшинство других основных арифметических операций на четырех различных ALU, но может выполняться только leaна одном (сложном lea) или двух (простых lea). Что еще более важно, эти два- leaспособных ALU - это просто два из четырех, которые могут выполнять другие инструкции, поэтому, как утверждается, преимущества параллелизма нет.
BeeOnRope
Статья, которую вы связали (правильно), показывает, что LEA находится на том же порту, что и целочисленный ALU (add / sub / boolean), и целочисленный модуль MUL в Haswell. (И векторные ALU, включая FP ADD / MUL / FMA). Простое устройство LEA находится на 5-м порту, который также запускает ADD / SUB / что угодно, векторное перемешивание и другие вещи. Единственная причина, по которой я не одобряю это то, что вы указываете на использование REA-относительного LEA (только для x86-64).
Питер Кордес
8

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

красно-E
источник
Не обязательно на современном x86. Большинство режимов адресации имеют одинаковую стоимость, с некоторыми оговорками. Так [esi]редко бывает дешевле, чем сказать, [esi + 4200]и только редко дешевле, чем [esi + ecx*8 + 4200].
BeeOnRope
@BeeOnRope [esi]не дешевле чем [esi + ecx*8 + 4200]. Но зачем сравнивать? Они не эквивалентны. Если вы хотите, чтобы первое указывало ту же ячейку памяти, что и второе, вам нужны дополнительные инструкции: вам нужно прибавить к esiзначению, ecxумноженному на 8. Ой, умножение приведет к засорению ваших флагов ЦП! Затем вы должны добавить 4200. Эти дополнительные инструкции увеличивают размер кода (занимают место в кеше команд, циклы выборки).
Каз
2
@Kaz - я думаю, что вы упустили мою точку зрения (иначе я пропустил точку зрения ОП). Насколько я понимаю, ОП говорит, что если вы собираетесь использовать что-то подобное [esi + 4200]в последовательности инструкций, то лучше сначала загрузить эффективный адрес в регистр и использовать его. Например, вместо того, чтобы писать add eax, [esi + 4200]; add ebx, [esi + 4200]; add ecx, [esi + 4200], вы должны предпочесть lea edi, [esi + 4200]; add eax, [edi]; add ebx, [edi]; add ecx, [edi], что редко бывает быстрее. По крайней мере, это простое толкование этого ответа.
BeeOnRope
Итак, причина, по которой я сравнивал [esi]и [esi + 4200](или [esi + ecx*8 + 4200]заключается в том, что это упрощение, которое предлагает OP (насколько я понимаю): что N инструкций с одинаковым комплексным адресом преобразуются в N инструкций с простой (одна рег) адресацией плюс одна lea, так как сложная адресация «отнимает много времени». На самом деле она медленнее даже на современном x86, но только с задержкой, что вряд ли будет иметь значение для последовательных инструкций с одним и тем же адресом
BeeOnRope
1
Возможно, вы ослабили некоторое давление в регистре, да, но может быть и обратное: если регистры, с которыми вы сгенерировали эффективный адрес, являются действующими, вам нужен другой регистр, чтобы сохранить результат, leaчтобы в этом случае он увеличился. В общем, хранение промежуточных звеньев - причина регистрационного давления, а не его решение - но я думаю, что в большинстве случаев это промывка. @Kaz
BeeOnRope
7

Инструкция LEA (Load Effective Address) - это способ получения адреса, который возникает в любом из режимов адресации памяти процессора Intel.

То есть, если у нас есть движение данных, как это:

MOV EAX, <MEM-OPERAND>

он перемещает содержимое назначенной ячейки памяти в целевой регистр.

Если заменить свой MOVпуть LEA, то адрес ячейки памяти рассчитывается точно так же, по <MEM-OPERAND>адресации выражения. Но вместо содержимого ячейки памяти мы получаем само местоположение в месте назначения.

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

Например, мы можем использовать LEAтолько простой прямой адрес. Никакая арифметика не используется вообще:

MOV EAX, GLOBALVAR   ; fetch the value of GLOBALVAR into EAX
LEA EAX, GLOBALVAR   ; fetch the address of GLOBALVAR into EAX.

Это действительно; мы можем проверить это в командной строке Linux:

$ as
LEA 0, %eax
$ objdump -d a.out

a.out:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <.text>:
   0:   8d 04 25 00 00 00 00    lea    0x0,%eax

Здесь нет добавления масштабированного значения и смещения. Ноль перемещается в EAX. Мы могли бы сделать это, используя MOV с непосредственным операндом.

Это причина, почему люди, которые считают, что квадратные скобки LEAизлишни, сильно ошибаются; квадратные скобки не являются LEAсинтаксисом, но являются частью режима адресации.

LEA реально на аппаратном уровне. Сгенерированная инструкция кодирует фактический режим адресации, и процессор выполняет его до момента вычисления адреса. Затем он перемещает этот адрес к месту назначения вместо генерации ссылки на память. (Поскольку вычисление адреса в режиме адресации в любой другой инструкции не влияет на флаги ЦП, LEAне влияет на флаги ЦП.)

Контраст с загрузкой значения с нулевого адреса:

$ as
movl 0, %eax
$ objdump -d a.out | grep mov
   0:   8b 04 25 00 00 00 00    mov    0x0,%eax

Это очень похожая кодировка, понимаете? Просто 8dиз LEAизменилось в 8b.

Конечно, эта LEAкодировка длиннее, чем перемещение непосредственного нуля в EAX:

$ as
movl $0, %eax
$ objdump -d a.out | grep mov
   0:   b8 00 00 00 00          mov    $0x0,%eax

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

Kaz
источник
6

Вот пример.

// compute parity of permutation from lexicographic index
int parity (int p)
{
  assert (p >= 0);
  int r = p, k = 1, d = 2;
  while (p >= k) {
    p /= d;
    d += (k << 2) + 6; // only one lea instruction
    k += 2;
    r ^= p;
  }
  return r & 1;
}

С опцией -O (optimize) в качестве опции компилятора gcc найдет инструкцию lea для указанной строки кода.

user3634373
источник
6

Кажется, что многие ответы уже завершены, я хотел бы добавить еще один пример кода для демонстрации того, как команды lea и move работают по-разному, когда они имеют одинаковый формат выражения.

Короче говоря, можно использовать как инструкции Le, так и инструкции MOV с круглыми скобками, включающими операнд src инструкций. Когда они заключены в () , выражение в () вычисляется таким же образом; однако две инструкции по-разному интерпретируют вычисленное значение в операнде src.

Независимо от того, используется ли выражение с lea или mov, значение src рассчитывается, как показано ниже.

D (Rb, Ri, S) => (Reg [Rb] + S * Reg [Ri] + D)

Однако, когда он используется с инструкцией mov, он пытается получить доступ к значению, указанному адресом, сгенерированным вышеприведенным выражением, и сохранить его в месте назначения.

В отличие от этого, когда инструкция lea выполняется с вышеприведенным выражением, она загружает сгенерированное значение в том виде, в котором оно находится, к месту назначения.

Приведенный ниже код выполняет инструкцию lea и инструкцию mov с одним и тем же параметром. Однако, чтобы уловить разницу, я добавил обработчик сигнала уровня пользователя, чтобы отследить ошибку сегментации, вызванную доступом к неправильному адресу в результате команды mov.

Пример кода

#define _GNU_SOURCE 1  /* To pick up REG_RIP */
#include <stdio.h> 
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <signal.h>


uint32_t
register_handler (uint32_t event, void (*handler)(int, siginfo_t*, void*))
{
        uint32_t ret = 0;
        struct sigaction act;

        memset(&act, 0, sizeof(act));
        act.sa_sigaction = handler;
        act.sa_flags = SA_SIGINFO;
        ret = sigaction(event, &act, NULL);
        return ret;
}

void
segfault_handler (int signum, siginfo_t *info, void *priv)
{
        ucontext_t *context = (ucontext_t *)(priv);
        uint64_t rip = (uint64_t)(context->uc_mcontext.gregs[REG_RIP]);
        uint64_t faulty_addr = (uint64_t)(info->si_addr);

        printf("inst at 0x%lx tries to access memory at %ld, but failed\n",
                rip,faulty_addr);
        exit(1);
}

int
main(void)
{
        int result_of_lea = 0;

        register_handler(SIGSEGV, segfault_handler);

        //initialize registers %eax = 1, %ebx = 2

        // the compiler will emit something like
           // mov $1, %eax
           // mov $2, %ebx
        // because of the input operands
        asm("lea 4(%%rbx, %%rax, 8), %%edx \t\n"
            :"=d" (result_of_lea)   // output in EDX
            : "a"(1), "b"(2)        // inputs in EAX and EBX
            : // no clobbers
         );

        //lea 4(rbx, rax, 8),%edx == lea (rbx + 8*rax + 4),%edx == lea(14),%edx
        printf("Result of lea instruction: %d\n", result_of_lea);

        asm volatile ("mov 4(%%rbx, %%rax, 8), %%edx"
                       :
                       : "a"(1), "b"(2)
                       : "edx"  // if it didn't segfault, it would write EDX
          );
}

Результат выполнения

Result of lea instruction: 14
inst at 0x4007b5 tries to access memory at 14, but failed
Jaehyuk Ли
источник
1
Разбивать ваш встроенный ассемблер на отдельные операторы небезопасно, и ваши списки саблберов неполные. Блок basic-asm сообщает компилятору, что он не имеет клобберов, но на самом деле он изменяет несколько регистров. Кроме того, вы можете использовать, =dчтобы сообщить компилятору результат в EDX, сохранив a mov. Вы также пропустили раннюю клобберную декларацию на выходе. Это действительно демонстрирует то, что вы пытаетесь продемонстрировать, но также является вводящим в заблуждение плохим примером встроенного asm, который сломается, если используется в других контекстах. Это плохая вещь для ответа переполнения стека.
Питер Кордес
Если вы не хотите записывать %%все эти имена регистров в Extended asm, используйте ограничения ввода. как asm("lea 4(%%ebx, %%eax, 8), %%edx" : "=d"(result_of_lea) : "a"(1), "b"(2));. Разрешение регистров инициализации компилятора означает, что вам также не нужно объявлять клобберы. Вы слишком усложняете вещи, обнуляя xor до того, как mov-немедленное перезаписывает также весь регистр.
Питер Кордес
@PeterCordes Спасибо, Питер, вы хотите, чтобы я удалил этот ответ или изменил его после ваших комментариев?
Jaehyuk Lee
1
Если вы исправите встроенный ассм, это не принесет никакого вреда и, возможно, станет хорошим конкретным примером для начинающих, которые не понимают других ответов. Нет необходимости удалять, и это легко исправить, как я показал в своем последнем комментарии. Я думаю, что было бы оправданно, если бы плохой пример встроенного asm был исправлен в «хороший» пример. (Я не понизил)
Питер Кордес
1
Где кто-нибудь говорит, что mov 4(%ebx, %eax, 8), %edxэто недействительно? В любом случае, да, поскольку movбыло бы разумно написать "a"(1ULL)компилятору, что у вас есть 64-битное значение, и поэтому ему нужно убедиться, что оно расширено для заполнения всего регистра. На практике он все еще будет использоваться mov $1, %eax, потому что написание EAX с нулевым расширением распространяется на RAX, если только у вас нет странной ситуации с окружающим кодом, когда компилятор знал, что RAX = 0xff00000001или что-то еще. Ведь leaвы все еще используете 32-битный размер операнда, поэтому любые старшие биты во входных регистрах не влияют на 32-битный результат.
Питер Кордес
4

LEA: просто "арифметическая" инструкция ..

MOV передает данные между операндами, но Леа только вычисляет

бухгалтер
источник
LEA явно перемещает данные; у него есть целевой операнд. LEA не всегда рассчитывает; он вычисляет, рассчитывает ли эффективный адрес, указанный в операнде источника. LEA EAX, GLOBALVAR не рассчитывается; он просто перемещает адрес GLOBALVAR в EAX.
Каз
@Kaz спасибо за ваш отзыв. мой источник был «LEA (эффективный адрес загрузки) - это, по сути, арифметическая инструкция - она ​​не выполняет никакого фактического доступа к памяти, но обычно используется для вычисления адресов (хотя вы можете вычислять целые числа общего назначения с ее помощью)». Форма книги Эльдад-Эйлам стр. 149
бухгалтер
@Kaz: Вот почему LEA является избыточным, когда адрес уже является постоянной времени соединения; используйте mov eax, offset GLOBALVARвместо. Вы можете использовать LEA, но он немного больше по размеру кода, чем mov r32, imm32и работает на меньшем количестве портов, потому что он все еще проходит процесс вычисления адреса . lea reg, symbolполезно только в 64-битном режиме для REA-относительного LEA, когда вам нужны PIC и / или адреса за пределами младших 32-битных. В 32 или 16-битном коде преимущество нулевое. LEA - это арифметическая инструкция, раскрывающая способность ЦП декодировать / вычислять режимы адресации.
Питер Кордес
@Kaz: тем же аргументом можно сказать, что imul eax, edx, 1он не рассчитывается: он просто копирует edx в eax. Но на самом деле он пропускает ваши данные через множитель с задержкой в ​​3 цикла. Или это rorx eax, edx, 0просто копии (повернуть на ноль).
Питер Кордес
@PeterCordes Моя точка зрения такова, что LEA EAX, GLOBALVAL и MOV EAX, GLOBALVAR просто получают адрес из непосредственного операнда. Здесь не применяется множитель 1 или смещение 0; Это может быть так на аппаратном уровне, но это не видно на языке ассемблера или в наборе команд.
Каз
1

Все обычные «расчетные» инструкции, такие как добавление умножения, исключение или установка флагов состояния, таких как ноль, знак. Если вы используете сложный адрес, AX xor:= mem[0x333 +BX + 8*CX] флаги устанавливаются в соответствии с операцией xor.

Теперь вы можете использовать адрес несколько раз. Загрузка таких адресов в реестр никогда не предназначена для установки флагов состояния, и, к счастью, это не так. Фраза «загрузить эффективный адрес» заставляет программиста осознать это. Отсюда и странное выражение.

Понятно, что когда процессор способен использовать сложный адрес для обработки своего контента, он может рассчитывать его для других целей. Действительно, его можно использовать для выполнения преобразования x <- 3*x+1в одной инструкции. Это общее правило в программировании на ассемблере: используйте инструкции, однако это раскачивает вашу лодку. Единственное, что имеет значение, это то, полезно ли вам конкретное преобразование, воплощенное в инструкции.

Нижняя граница

MOV, X| T| AX'| R| BX|

а также

LEA, AX'| [BX]

имеют такой же эффект на AX, но не на флаги состояния. (Это запись ciasdis .)

Альберт ван дер Хорст
источник
«Это общее правило в программировании на ассемблере: используйте инструкции, как бы это ни крутило вашу лодку». Я бы лично не раздавал этот совет из-за таких вещей, как call lbl lbl: pop raxтехническая «работа» как способ получить ценность rip, но вы сделаете предсказание ветвлений очень несчастным. Используйте инструкции по
своему
@ The6P4C Это полезная оговорка. Однако, если нет альтернативы тому, чтобы сделать предсказание ветвления несчастным, нужно пойти на это. Есть еще одно общее правило в программировании на ассемблере. Могут быть альтернативные способы сделать что-то, и вы должны мудро выбирать из альтернатив. Существуют сотни способов получить содержимое регистра BL в регистр AL. Если остаток RAX не требуется сохранять, LEA может быть вариантом. Не влияет на флаги может быть хорошей идеей на некоторых из тысяч типов процессоров x86. Groetjes Альберт
Альберт ван дер Хорст
-1

Простите, если кто-то уже упоминал, но во времена x86, когда сегментация памяти все еще была актуальна, вы можете не получить те же результаты из этих двух инструкций:

LEA AX, DS:[0x1234]

а также

LEA AX, CS:[0x1234]
tzoz
источник
1
«Эффективный адрес» - это просто «смещенная» часть seg:offпары. LEA не зависит от базы сегмента; обе эти инструкции будут (неэффективно) помещены 0x1234в AX. К сожалению, в x86 нет простого способа вычислить полный линейный адрес (эффективная + сегментная база) в регистр или пару регистров.
Питер Кордес
@PeterCordes Очень полезно, спасибо, что поправили меня.
Цоз