Яка мета інструкції LEA?


676

Для мене це просто здається прикольним MOV. Яке його призначення і коли я повинен його використовувати?


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 байт. Тепер розглянемо той самий вираз, який використовується з оператором "адреса" &:

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

У цьому випадку потрібно не значення ycoord, а його адресу. Ось де LEA(завантажується ефективна адреса). Замість MOVкомпілятора можна генерувати

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

який завантажить адресу в ESI.


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
Ерік

29
@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. Я досі не розумію, значить, він завантажує вказівник на інше місце?

6
@ ripDaddy69 так, начебто - якщо під "навантаженням" ви маєте на увазі "виконує обчислення адреси / арифметику вказівника". Він не має доступу до пам’яті (тобто не «перенаправляє» покажчик, як би його називали в термінах програмування на C).
Френк.

2
+1: Це дає чітке уявлення про те, які види "трюків" LEAможна використовувати для ... (див. "LEA (ефективна адреса для завантаження) часто використовується як" трюк "для виконання певних обчислень" у популярній відповіді IJ Kennedy вище ")
Ассад Ебрагім

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: Чи має це значення? )
Peter Кордес

2
@PeterCordes: Ненавиджу це доводити тут, але - чи я один думаю, що цей новий тег [x86-lea] є зайвим і непотрібним?
Майкл Петч

2
@MichaelPetch: Так, я думаю, що це занадто конкретно. Здається, це бентежить початківців, які не розуміють машинну мову, і що все (включаючи вказівники) - це лише біти / байти / цілі числа, тому існує багато питань щодо цього з величезною кількістю голосів. Але тег має на увазі, що є місце для відкритого числа майбутніх питань, коли насправді є приблизно 2 або 3, які не є лише дублікатами. (Що це таке? Як його використовувати для множення цілих чисел , і як вона працює всередині на AGUS проти АЛУ і з тим, що затримка / пропускна здатність А може бути , це «призначена» мета ?.)
Пітер Кордес

@PeterCordes: Я погоджуюся, і якщо що-небудь все ці публікації редагуються, це майже дублікат декількох питань, що стосуються LEA. Замість тегу будь-які дублікати повинні бути ідентифіковані та позначені imho.
Майкл Петч

1
@EvanCarroll: покладіть на позначення всіх питань LEA, якщо ви ще не закінчили. Як обговорювалося вище, ми вважаємо, що x86-lea занадто специфічний для тегу, і не існує великого простору для майбутніх не повторюваних питань. Я думаю, що було б дуже багато роботи, щоб насправді вибрати найкращий Q&A в якості дубльованої цілі для більшості з них, чи вирішити, які саме з них отримати мод для об'єднання.
Пітер Кордес

93

Незважаючи на всі пояснення, LEA - це арифметична операція:

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

Просто його ім'я надзвичайно дурне для операції shift + add. Причину цього було вже роз'яснено у відповідях з найвищим рейтингом (тобто він був розроблений для прямого відображення посилань на пам'ять високого рівня).


8
І що арифметика виконується апаратом обчислення адреси.
Ben Voigt

30
@BenVoigt Я говорив про це, тому що я старий хлопець :-) Традиційно x86 процесори використовували для цього адреси блоків, погодившись. Але "розлука" стала дуже розмитою в ці дні. Деякі процесори більше не мають виділених AGU, інші вирішили не виконувати LEAна AGU, а на звичайних цілих ALU. Треба дуже уважно прочитати технічні характеристики процесора в ці дні, щоб дізнатися, "куди біжать речі" ...
FrankH.

2
@FrankH. Процесорні процесори поза замовленнями зазвичай запускають LEA на ALU, тоді як деякі процесори на замовлення (наприклад, Atom) іноді запускають його на AGU (оскільки вони не можуть бути зайняті обробкою доступу до пам'яті).
Пітер Кордес

3
Ні, ім’я не дурне. LEAдає вам адресу, яка виникає з будь-якого режиму адресації, пов'язаного з пам'яттю. Це не зсув і додавання операції.
Каз

3
FWIW є дуже мало (якщо такі є) поточних процесорів x86, які виконують операцію на AGU. Більшість або всі просто використовують АЛУ, як і будь-які інші арифметичні оп.
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]?
Абід Рахман K

13
@Abid Rahman K: Не існує такої інструкції, як інструкція unde x86 набір інструкцій CPU.
GJ.

50
@AbidRahmanK, незважаючи на синтаксис інтелекту ASM, робить його схожим на множення, інструкція lea може кодувати лише операції зсуву. Опкод має 2 біти для опису зрушення, отже, ви можете помножити лише на 1,2,4 або 8.
ithkuil

6
@Koray Tugay: Ви можете використовувати зсув вліво, як shlінструкція для множення регістрів на 2,4,8,16 ... це швидше і коротше. Але для множення на числа, різні за потужністю 2, ми зазвичай використовуємо mulінструкцію, яка є більш претензійною і повільнішою.
GJ.

7
@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само, як, MOVале ти фактично не перенаправляєш пам'ять. Іншими словами:

MOV EAX, [ESP+4]

Це перемістить зміст того, на що ESP+4вказує EAX.

LEA EAX, [EBX*8]

Це перемістить ефективну адресу EBX * 8в EAX, а не те, що знаходиться в цьому місці. Як бачите, також можна помножити на два коефіцієнти (масштабування), а а 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 для зберігання даних за адресою, що стосується ВР, маючи змогу сказати

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

Зауважте, що забуття світового "зміщення" призведе до того, що вміст розташування [BP + 8], а не значення 8, буде додано до DI. На жаль


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.


2
Частина "окремої LEA ALU" здебільшого не відповідає дійсності. Сучасні процесори виконуються leaна одному або декількох тих же ALU, які виконують інші арифметичні вказівки (але загалом їх менше, ніж інші арифметичні). Наприклад, згаданий процесор Haswell може виконувати addабо subбільшість інших основних арифметичних операцій на чотирьох різних АЛУ, але може виконуватись лише leaна одному (складному lea) або двох (простих lea). Що ще важливіше, ці leaАРУ з двома можливостями - це просто два з чотирьох, які можуть виконувати інші вказівки, тому немає переваги паралелізму, як заявлено.
BeeOnRope

У статті, яку ви зв'язали (правильно), видно, що LEA знаходиться на тому самому порту, як ціле ALU (add / sub / boolean) та ціла одиниця MUL в Haswell. (І векторні ALU, включаючи FP ADD / MUL / FMA). Простий модуль LEA розміщений на порту 5, на якому також працює ADD / SUB / що завгодно, і перетасовування вектора та ін. Єдина причина, на яку я не звертаюсь із заявою, - це те, що ви вказуєте на використання REA, що стосується RIP (лише для x86-64).
Пітер Кордес

8

Інструкція LEA може бути використана для уникнення трудомістких розрахунків ефективних адрес центральним процесором. Якщо адреса використовується неодноразово, ефективніше зберігати її в регістрі, а не обчислювати ефективну адресу кожного разу, коли вона використовується.


Не обов'язково на сучасних 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]тому, що це спрощення, яке пропонує ОП (наскільки я розумію): 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виключати цю можливість, хоча тільки тому, що існує коротша альтернатива; це просто поєднання ортогональним способом із доступними режимами адресації.


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 (оптимізувати) як варіант компілятора, gcc знайде інструкцію lea для вказаного рядка коду.


6

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

Щоб зробити короткий короткий опис, інструкції lea та 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

1
Розбивання вбудованого тла на окремі заяви небезпечно, а ваші списки клобер неповні. Блок basic-asm повідомляє, що компілятор не має клобер, але він фактично модифікує кілька регістрів. Крім того, ви можете використовувати, =dщоб повідомити компілятору, що результат знаходиться в EDX, зберігаючи mov. Ви також пропустили ранню заяву про вихід. Це наочно демонструє те, що ви намагаєтесь продемонструвати, але також є оманливим поганим прикладом вбудованої ASM, яка порушиться, якщо використовувати її в інших контекстах. Це погана річ для відповіді на переповнення стека.
Пітер Кордес

Якщо ви не хочете писати %%всі ці імена реєстру у Розширеному asm, використовуйте обмеження введення. як asm("lea 4(%%ebx, %%eax, 8), %%edx" : "=d"(result_of_lea) : "a"(1), "b"(2));. Якщо дозволити компілятору init регістри, це означає, що вам також не потрібно оголошувати клобери. Ви надмірно ускладнюєте речі за допомогою xor-zeroing перед тим, як mov-негайд також перепише весь реєстр.
Пітер Кордес

@PeterCordes Спасибі, Пітер, ти хочеш, щоб я видалив цю відповідь або змінив її після ваших коментарів?
Jaehyuk Lee

1
Якщо ви виправляєте вбудований асм, він не робить ніякої шкоди і, можливо, робить хороший конкретний приклад для початківців, які не розуміли інших відповідей. Видаляти не потрібно, і це просте виправлення, як я показав у своєму останньому коментарі. Я вважаю, що варто було б підкреслити, якби поганий приклад inline asm був зафіксований у "хорошому" прикладі. (Я не заявив)
Пітер Кордес

1
Де хто каже, що mov 4(%ebx, %eax, 8), %edxце недійсно? Так чи інакше, так як movбуло б сенс написати, "a"(1ULL)щоб сказати компілятору, що ви маєте 64-бітове значення, і, таким чином, йому потрібно переконатися, що він розширений, щоб заповнити весь реєстр. На практиці це все ще використовуватиметься mov $1, %eax, оскільки запис EAX zero-extends в RAX, якщо тільки у вас не дивна ситуація з оточуючим кодом, де компілятор знав, що RAX = 0xff00000001чи щось таке. Тому що leaви все ще використовуєте 32-розрядний розмір операнду, тому будь-які збиті високі біти у вхідних регістрах не впливають на 32-бітний результат.
Пітер Кордес

4

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

MOV передає дані між операндами, але lea просто обчислює


LEA очевидно переміщує дані; він має операнд призначення. LEA не завжди обчислює; він обчислює, чи обчислює ефективна адреса, виражена у вихідному операнді. LEA EAX, GLOBALVAR не обчислює; він просто переміщує адресу GLOBALVAR в EAX.
Каз

@Kaz дякую за відгук. моє джерело було: "LEA (ефективні навантаження адреси) - це по суті арифметична інструкція - вона не виконує жодного фактичного доступу до пам'яті, але зазвичай використовується для обчислення адрес (хоча за допомогою неї можна обчислити цілі цілі загального призначення)." форма Елдад-Ейлам книга сторінка 149
бухгалтер

@Kaz: Тому LEA є надлишковим, коли адреса вже є постійною ланкою часу; використовувати mov eax, offset GLOBALVARзамість цього. Ви можете використовувати LEA, але це трохи більший розмір коду, ніж mov r32, imm32і працює на меншій кількості портів, оскільки він все ще проходить процес обчислення адреси . lea reg, symbolкорисний лише в 64-розрядному для RIP-відносного 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

@ The6P4C Це корисний застереження. Однак якщо немає альтернативи зробити передбачення галузевим нещасним, треба зробити це. Існує ще одне загальне правило в складі програмування. Можливо, існують альтернативні способи щось зробити, і ви мусите вибирати розумно з альтернатив. Існує сотні способів введення вмісту регістру BL в реєстр AL. Якщо залишок RAX не потрібно зберігати, LEA може бути варіантом. Не зачіпати прапори може бути хорошою ідеєю для деяких з тисяч типів процесорів x86. Groetjes Albert
Альберт ван дер Хорст

-1

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

LEA AX, DS:[0x1234]

і

LEA AX, CS:[0x1234]

1
"Ефективна адреса" - це лише "зміщена" частина seg:offпари. База сегментів не впливає на LEA; обидві ці інструкції (неефективно) будуть введені 0x1234в AX. На жаль, x86 не має простого способу обчислити повну лінійну адресу (ефективна + база сегмента) в регістр або пара-регістр.
Пітер Кордес

@PeterCordes Дуже корисно, дякую, що мене виправили.
цоз
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.