Jaki jest cel instrukcji LEA?


676

Dla mnie to po prostu funky MOV. Jaki jest jego cel i kiedy powinienem go używać?


2
Zobacz także Używanie LEA na wartościach, które nie są adresami / wskaźnikami? : LEA jest tylko instrukcją zmiany i dodawania. Prawdopodobnie został dodany do 8086, ponieważ sprzęt już tam jest do dekodowania i obliczania trybów adresowania, a nie dlatego, że jest „przeznaczony” tylko do użytku z adresami. Pamiętaj, że wskaźniki to tylko liczby całkowite w asemblerze.
Peter Cordes,

Odpowiedzi:


798

Jak zauważyli inni, LEA (adres efektywnego ładowania) jest często używany jako „sztuczka” do wykonywania pewnych obliczeń, ale to nie jest jego główny cel. Zestaw instrukcji x86 został zaprojektowany do obsługi języków wysokiego poziomu, takich jak Pascal i C, gdzie tablice - szczególnie tablice int lub małe struktury - są powszechne. Rozważmy na przykład strukturę reprezentującą współrzędne (x, y):

struct Point
{
     int xcoord;
     int ycoord;
};

Teraz wyobraź sobie zdanie takie jak:

int y = points[i].ycoord;

gdzie points[]jest tablica Point. Przy założeniu, że podstawa tablicy jest już EBXi zmienna iw EAXi xcoorda ycoordmają po 32 bitów (to ycoordjest przy przesunięciu 4 bajty struktury), stwierdzenie można zestawiać do:

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

które wylądują yw EDX. Współczynnik skali wynosi 8, ponieważ każdy Pointma rozmiar 8 bajtów. Teraz rozważ to samo wyrażenie, którego użyto z operatorem „adres” i:

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

W tym przypadku nie chcesz wartości ycoord, ale jej adres. Tam właśnie LEApojawia się (adres efektywny). Zamiast a MOVkompilator może generować

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

który załaduje adres ESI.


112
Czy nie byłoby lepiej przedłużyć movinstrukcję i pominąć nawiasy? MOV EDX, EBX + 8*EAX + 4
Natan Yellin

14
@imacake Zastępując LEA wyspecjalizowanym MOV, utrzymujesz czystość składni: nawiasy kwadratowe są zawsze równoważne dereferencjacji wskaźnika w C. Bez nawiasów zawsze masz do czynienia z samym wskaźnikiem.
Natan Yellin

139
Wykonywanie matematyki w instrukcji MOV (EBX + 8 * EAX + 4) jest nieprawidłowe. LEA ESI, [EBX + 8 * EAX + 4] jest prawidłowy, ponieważ jest to tryb adresowania obsługiwany przez x86. en.wikipedia.org/wiki/X86#Addressing_modes
Erik

29
@JathanathanDickinson LEA jest jak MOVz pośrednim źródłem, z tą różnicą, że działa tylko pośrednio, a nie MOV. W rzeczywistości nie odczytuje z obliczonego adresu, po prostu go oblicza.
hobbs

24
Erik, komentarz do trasy nie jest dokładny. MOV eax, [ebx + 8 * ecx + 4] jest prawidłowy. Jednak MOV zwraca zawartość pierwszej lokalizacji pamięci, a LEA zwraca adres
Olorin

562

Z „Zen of Assembly” Abrasha:

LEA, jedyna instrukcja, która wykonuje obliczenia adresowania pamięci, ale tak naprawdę nie adresuje pamięci. LEAakceptuje standardowy operand adresowania pamięci, ale robi jedynie przechowywanie obliczonego przesunięcia pamięci w określonym rejestrze, którym może być dowolny rejestr ogólnego przeznaczenia.

Co nam to daje? Dwie rzeczy, które ADDnie zapewniają:

  1. możliwość wykonania dodawania za pomocą dwóch lub trzech operandów, oraz
  2. możliwość przechowywania wyniku w dowolnym rejestrze; nie tylko jeden z operandów źródłowych.

I LEAnie zmienia flag.

Przykłady

  • LEA EAX, [ EAX + EBX + 1234567 ]oblicza EAX + EBX + 1234567(to trzy operandy)
  • LEA EAX, [ EBX + ECX ]oblicza EBX + ECXbez nadpisywania wyniku.
  • pomnożenie przez stałą (przez dwa, trzy, pięć lub dziewięć), jeśli użyjesz jej podobnie LEA EAX, [ EBX + N * EBX ](N może wynosić 1,2,4,8).

Inny przypadek użycia jest przydatny w pętlach: różnica między LEA EAX, [ EAX + 1 ]i INC EAXpolega na tym, że ten drugi się zmienia, EFLAGSale pierwszy nie; to zachowuje CMPstan.


42
@AbidRahmanK kilka przykładów: LEA EAX, [ EAX + EBX + 1234567 ]oblicza sumę EAX, EBXa 1234567(to trzy argumenty). LEA EAX, [ EBX + ECX ]oblicza EBX + ECX bez nadpisywania wyniku. Trzecią rzeczą, której LEAsię używa (nie wymienionej przez Franka), jest mnożenie przez stałą (przez dwa, trzy, pięć lub dziewięć), jeśli używasz jej w podobny sposób LEA EAX, [ EBX + N * EBX ]( Nmoże być 1,2,4,8). Inny przypadek użycia jest przydatny w pętlach: różnica między LEA EAX, [ EAX + 1 ]i INC EAXpolega na tym, że ten drugi się zmienia, EFLAGSale pierwszy nie; to zachowuje CMPstan
FrankH.

@FrankH. Nadal nie rozumiem, więc ładuje wskaźnik w inne miejsce?

6
@ ripDaddy69 tak, w pewnym sensie - jeśli przez „load” rozumiesz „wykonuje obliczenia adresu / arytmetykę wskaźnika”. Nie ma dostępu do pamięci (tzn. Nie „odciąża” wskaźnika, jak można by go nazwać w terminologii programowania C).
FrankH.

2
+1: Wyraźnie określa, jakiego rodzaju „trików” LEAmożna użyć do ... (patrz „LEA (ładowanie adresu efektywnego) jest często używany jako„ sztuczka ”do wykonania pewnych obliczeń” w popularnej odpowiedzi IJ Kennedy'ego powyżej)
Assad Ebrahim

3
Istnieje duża różnica między 2 operandami LEA, które są szybkie, a 3 operandami LEA, które są wolne. Podręcznik optymalizacji Intel mówi, że szybka ścieżka LEA jest jednym cyklem, a wolna ścieżka LEA zajmuje trzy cykle. Ponadto w Skylake istnieją dwie jednostki funkcjonalne szybkiej ścieżki (porty 1 i 5) i tylko jedna jednostka funkcjonalna wolnej ścieżki (port 1). Kodowanie zestawu / kompilatora Reguła 33 w instrukcji ostrzega nawet przed użyciem 3 operandów LEA.
Olsonist

110

Inną ważną cechą LEAinstrukcji jest to, że nie zmienia ona kodów warunków, takich jak CFi ZF, podczas obliczania adresu za pomocą instrukcji arytmetycznych, takich jak ADDlub MULrobi. Ta funkcja zmniejsza poziom zależności między instrukcjami, a tym samym umożliwia dalszą optymalizację kompilatora lub harmonogramu sprzętowego.


1
Tak, leaczasem przydatne jest, aby kompilator (lub koder ludzki) wykonywał matematykę bez blokowania wyniku flagi. Ale leanie jest szybszy niż add. Większość instrukcji x86 zapisuje flagi. Wysokowydajne implementacje x86 muszą zmienić nazwę EFLAGS lub w inny sposób uniknąć zagrożenia zapisu po zapisie, aby normalny kod działał szybko, więc instrukcje unikające zapisywania flag nie są z tego powodu lepsze. ( częściowe flagi mogą powodować problemy, patrz instrukcja INC vs ADD 1: Czy to ważne? )
Peter Cordes

2
@PeterCordes: Nienawidzę omawiać tego tutaj, ale - czy jestem sam, myśląc, że ten nowy tag [x86-lea] jest zbędny i niepotrzebny?
Michael Petch

2
@MichaelPetch: Tak, myślę, że to zbyt specyficzne. Wydaje się, że myli początkujących, którzy nie rozumieją języka maszynowego i że wszystko (łącznie ze wskaźnikami) to tylko bity / bajty / liczby całkowite, więc jest wiele pytań na ten temat z ogromną liczbą głosów. Ale posiadanie tagu oznacza, że ​​jest miejsce na nieograniczoną liczbę przyszłych pytań, podczas gdy w rzeczywistości jest około 2 lub 3 łącznie, które nie są tylko duplikatami. (co to jest? Jak używać go do mnożenia liczb całkowitych? i jak działa wewnętrznie na AGU vs. ALU i przy jakim opóźnieniu / przepustowości. A może to cel „zamierzony”)
Peter Cordes

@PeterCordes: Zgadzam się, i jeśli cokolwiek, wszystkie te edytowane posty są w zasadzie duplikatem kilku pytań, które odeszły z LEA. Zamiast znacznika wszelkie duplikaty powinny zostać zidentyfikowane i oznaczone imho.
Michael Petch

1
@EvanCarroll: poczekaj na oznaczenie wszystkich pytań LEA, jeśli jeszcze tego nie zrobiłeś. Jak omówiono powyżej, uważamy, że x86-lea jest zbyt specyficzna dla tagu, i nie ma zbyt dużego zakresu dla przyszłych niedublicznych pytań. Myślę, że byłoby dużo pracy, aby właściwie wybrać „najlepsze” Q & A jako dup cel dla większości z nich, chociaż, czy faktycznie zdecydować, które z nich uzyskać modów do scalenia.
Peter Cordes

93

Pomimo wszystkich wyjaśnień, LEA jest operacją arytmetyczną:

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

Po prostu jego nazwa jest wyjątkowo głupia jak na operację shift + add. Powód tego został już wyjaśniony w najwyżej ocenianych odpowiedziach (tj. Został zaprojektowany do bezpośredniego mapowania odniesień do pamięci wysokiego poziomu).


8
I że arytmetyka jest wykonywana przez sprzęt do obliczania adresu.
Ben Voigt,

30
@BenVoigt Zwykłem to mówić, ponieważ jestem starym facetem :-) Tradycyjnie procesory x86 używały do ​​tego jednostek adresujących. Ale „separacja” stała się obecnie bardzo rozmyta. Niektóre procesory nie mają już dedykowanych jednostek AGU, inne zdecydowały się nie wykonywać LEAna AGU, ale na zwykłych liczbach całkowitych ALU. W dzisiejszych czasach trzeba bardzo uważnie zapoznać się ze specyfikacją procesora, aby dowiedzieć się, „gdzie
biegają

2
@FrankH .: Poza kolejnością procesory zwykle uruchamiają LEA na ALU, podczas gdy niektóre procesory w kolejności (np. Atom) czasami działają na AGU (ponieważ nie mogą być zajęte obsługą dostępu do pamięci).
Peter Cordes,

3
Nie, nazwa nie jest głupia. LEApodaje adres, który wynika z dowolnego trybu adresowania związanego z pamięcią. To nie jest operacja zmiany i dodania.
Kaz

3
FWIW jest bardzo niewiele (jeśli w ogóle) obecnych procesorów x86, które wykonują operację na AGU. Większość lub wszyscy po prostu używają ALU jak każdej innej operacji arytmetycznej.
BeeOnRope 26.04.17

77

Może to kolejna rzecz dotycząca instrukcji LEA. Możesz także użyć LEA do szybkiego pomnożenia rejestrów przez 3, 5 lub 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 za lewę. Ale chciałbym zadać pytanie (może być głupie), dlaczego nie pomnożyć bezpośrednio z trzema takimi LEA EAX, [EAX*3]?
Abid Rahman K.

13
@Abid Rahman K: Nie ma takich instrukcji jak zestaw instrukcji procesora x86.
GJ.

50
@AbidRahmanK pomimo składni intel asm sprawia, że ​​wygląda to na mnożenie, instrukcja lea może kodować tylko operacje shift. Kod operacji ma 2 bity opisujące przesunięcie, dlatego możesz pomnożyć tylko 1,2,4 lub 8.
ithkuil

6
@Koray Tugay: Możesz użyć shlinstrukcji shift w lewo do pomnożenia rejestrów przez 2,4,8,16 ... jest to szybsze i krótsze. Ale do mnożenia przez liczby różniące się potęgą 2 zwykle używamy mulinstrukcji, które są bardziej pretensjonalne i wolniejsze.
GJ.

7
@GJ. chociaż nie ma takiego kodowania, niektórzy asemblery akceptują to jako skrót, np. fasm. Tak więc np. lea eax,[eax*3]Tłumaczy się na ekwiwalent lea eax,[eax+eax*2].
Ruslan

59

leajest skrótem „efektywnego adresu ładowania”. Ładuje adres odwołania do lokalizacji przez operand źródłowy do operandu docelowego. Możesz na przykład użyć go do:

lea ebx, [ebx+eax*8]

aby przesunąć elementy ebxwskaźnika eaxdalej (w tablicy 64-bit / element) za pomocą jednej instrukcji. Zasadniczo korzystasz ze złożonych trybów adresowania obsługiwanych przez architekturę x86, aby efektywnie manipulować wskaźnikami.


23

Największy powód, dla którego używasz LEAponadMOV jest to, że musisz wykonać arytmetykę w rejestrach, których używasz do obliczania adresu. W efekcie można efektywnie wykonać „dowolną” kombinację arytmetyki wskaźnika na kilku rejestrach w połączeniu.

To, co jest naprawdę mylące, to fakt, że zwykle piszesz LEAcoś takiego, MOVale tak naprawdę nie dereferentujesz pamięci. Innymi słowy:

MOV EAX, [ESP+4]

Spowoduje to przeniesienie zawartości tego, na co ESP+4wskazuje EAX.

LEA EAX, [EBX*8]

Spowoduje to przeniesienie efektywnego adresu EBX * 8do EAX, a nie tego, co znajduje się w tej lokalizacji. Jak widać, możliwe jest także pomnożenie przez dwa czynniki (skalowanie), podczas gdy a MOVogranicza się do dodawania / odejmowania.


Przepraszam wszystkich @ big.heart oszukał mnie, udzielając odpowiedzi na to trzy godziny temu, sprawiając, że pojawił się on jako „nowy” w mojej dyskusji w Zgromadzeniu.
David Hoelzer

1
Dlaczego w składni używa się nawiasów, gdy nie zajmuje się adresowaniem pamięci?
gołopot

3
@ q4w56 To jedna z tych rzeczy, na które odpowiedź brzmi: „Właśnie tak to robisz”. Uważam, że jest to jeden z powodów, dla których ludzie mają trudności z ustaleniem, co się LEAdzieje.
David Hoelzer

2
@ q4w56: jest to instrukcja shift + add, która wykorzystuje składnię operandu pamięci i kodowanie kodu maszynowego. Na niektórych procesorach może nawet używać sprzętu AGU, ale to historyczny szczegół. Wciąż istotny jest fakt, że sprzęt dekodujący już istnieje do dekodowania tego rodzaju shift + add, a LEA pozwala nam używać go do arytmetyki zamiast adresowania pamięci. (Lub do obliczeń adresu, jeśli jedno wejście faktycznie jest wskaźnikiem).
Peter Cordes

20

8086 ma dużą rodzinę instrukcji, które akceptują operand rejestru i adres efektywny, wykonują pewne obliczenia w celu obliczenia przesuniętej części tego adresu efektywnego i wykonują pewne operacje obejmujące rejestr i pamięć, do których odnosi się obliczony adres. To było dość proste, aby jedna z instrukcji w tej rodzinie zachowywała się tak, jak powyżej, z wyjątkiem pomijania faktycznej operacji pamięci. To instrukcje:

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

zostały wdrożone prawie identycznie wewnętrznie. Różnica polega na pominięciu kroku. Obie instrukcje działają mniej więcej tak:

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

Jeśli chodzi o to, dlaczego Intel uważał tę instrukcję za wartą włączenia, nie jestem do końca pewien, ale fakt, że jej wdrożenie było tanie, byłby dużym czynnikiem. Innym czynnikiem byłby fakt, że asembler Intela pozwalał na definiowanie symboli w stosunku do rejestru BP. Jeśli fnordzostał zdefiniowany jako symbol względny BP (np. BP + 8), można powiedzieć:

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

Jeśli ktoś chce użyć czegoś takiego jak stosw do przechowywania danych pod adresem względnym BP, jest w stanie powiedzieć

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

było wygodniejsze niż:

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

Zauważ, że zapomnienie o „offsecie” świata spowodowałoby dodanie zawartości DI [BP + 8] zamiast wartości 8. Ups


12

Jak wspomniano w istniejących odpowiedziach, LEAma tę zaletę, że wykonuje arytmetykę adresowania pamięci bez dostępu do pamięci, zapisując wynik arytmetyki do innego rejestru zamiast prostej instrukcji dodawania. Prawdziwą korzyścią wynikającą z wydajności jest to, że nowoczesny procesor ma osobną jednostkę LEA ALU i port do efektywnego generowania adresu (w tym LEAi inny adres odniesienia pamięci), co oznacza, że ​​operacje arytmetyczne LEAi inne normalne operacje arytmetyczne na ALU mogą być wykonywane równolegle w jednym rdzeń.

Zapoznaj się z tym artykułem o architekturze Haswell, aby uzyskać szczegółowe informacje na temat jednostki LEA: http://www.realworldtech.com/haswell-cpu/4/

Innym ważnym punktem, który nie jest wspomniany w innych odpowiedziach, jest LEA REG, [MemoryAddress]instrukcja PIC (kod niezależny od pozycji), który koduje względny adres komputera w tej instrukcji w celu odniesienia MemoryAddress. Różni się to od tego, MOV REG, MemoryAddressktóry koduje względny adres wirtualny i wymaga relokacji / łatania we współczesnych systemach operacyjnych (np. ASLR jest wspólną cechą). Więc LEAmoże być użyty do konwersji takiego non PIC PIC.


2
Część „osobna LEA ALU” jest w większości nieprawdziwa. Współczesne procesory wykonują leana jednej lub więcej takich samych ALU, które wykonują inne instrukcje arytmetyczne (ale generalnie mniej z nich niż inne arytmetyki). Na przykład wspomniany procesor Haswella może wykonywać addlub subwiększość innych podstawowych operacji arytmetycznych na czterech różnych jednostkach ALU, ale może wykonywać tylko leana jednej (złożonej lea) lub dwóch (prostej lea). Co ważniejsze, te dwie leazdolne do pracy jednostki ALU to po prostu dwie z czterech, które mogą wykonywać inne instrukcje, więc nie ma korzyści z równoległości, jak twierdzono.
BeeOnRope

Artykuł, który podłączyłeś (poprawnie) pokazuje, że LEA znajduje się na tym samym porcie, na którym znajduje się liczba całkowita ALU (add / sub / boolean), a liczba całkowita MUL w Haswell. (I wektorowe ALU, w tym FP ADD / MUL / FMA). Prosta jednostka LEA znajduje się na porcie 5, który również obsługuje ADD / SUB / cokolwiek, a także tasuje wektor i inne rzeczy. Jedynym powodem, dla którego nie głosuję za odrzuceniem, jest to, że zwracasz uwagę na użycie LEA zależnego od RIP (tylko dla x86-64).
Peter Cordes

8

Można użyć instrukcji LEA, aby uniknąć czasochłonnego obliczania efektywnych adresów przez CPU. Jeśli adres jest używany wielokrotnie, bardziej efektywne jest przechowywanie go w rejestrze zamiast obliczania adresu efektywnego za każdym razem, gdy jest używany.


Niekoniecznie na nowoczesnym x86. Większość trybów adresowania ma ten sam koszt, z pewnymi zastrzeżeniami. Tak więc [esi]jest rzadko tańszy niż powiedzieć [esi + 4200]i tylko rzadko jest tańszy niż [esi + ecx*8 + 4200].
BeeOnRope

@BeeOnRope [esi]nie jest tańszy niż [esi + ecx*8 + 4200]. Ale po co zawracać sobie głowę porównywaniem? Nie są równoważne. Jeśli chcesz, aby to pierwsze wyznaczyło tę samą lokalizację pamięci co drugie, potrzebujesz dodatkowych instrukcji: musisz dodać do esiwartości ecxpomnożonej przez 8. Uh, och, mnożenie spowoduje zatkanie flag procesora! Następnie musisz dodać 4200. Te dodatkowe instrukcje zwiększają rozmiar kodu (zajmowanie miejsca w pamięci podręcznej instrukcji, cykle do pobrania).
Kaz

2
@Kaz - Myślę, że nie trafiłeś w mój punkt (w przeciwnym razie przegapiłem punkt PO). Rozumiem, że OP mówi, że jeśli zamierzasz używać czegoś takiego jak [esi + 4200]wielokrotnie w sekwencji instrukcji, lepiej najpierw załadować efektywny adres do rejestru i użyć tego. Na przykład, zamiast pisać add eax, [esi + 4200]; add ebx, [esi + 4200]; add ecx, [esi + 4200], powinieneś preferować lea edi, [esi + 4200]; add eax, [edi]; add ebx, [edi]; add ecx, [edi], co rzadko jest szybsze. Przynajmniej taka jest prosta interpretacja tej odpowiedzi.
BeeOnRope

Powodem, dla którego porównywałem [esi]i [esi + 4200](lub [esi + ecx*8 + 4200]jest to, że jest to uproszczenie, jakie proponuje OP (jak rozumiem): że N instrukcji o tym samym złożonym adresie jest przekształcanych w N instrukcji z prostym adresowaniem (jeden reg) plus jeden lea, ponieważ złożone adresowanie jest „czasochłonne”. W rzeczywistości jest wolniejsze nawet na współczesnych procesorach x86, ale tylko pod względem opóźnień, co wydaje się mało prawdopodobne w przypadku kolejnych instrukcji o tym samym adresie
BeeOnRope 26.04.17

1
Być może zmniejszasz presję rejestru, tak - ale może być odwrotnie: jeśli rejestry, z którymi wygenerowałeś adres efektywny, są aktywne, potrzebujesz innego rejestru, aby zapisać wynik, leawięc zwiększy to presję w tym przypadku. Ogólnie rzecz biorąc, przechowywanie półproduktów jest przyczyną presji rejestru, a nie rozwiązaniem tego problemu - ale myślę, że w większości sytuacji jest to pranie. @Kaz
BeeOnRope

7

Instrukcja LEA (Load Effective Address) to sposób na uzyskanie adresu, który wynika z dowolnego z trybów adresowania pamięci procesora Intel.

To znaczy, jeśli mamy taki ruch danych:

MOV EAX, <MEM-OPERAND>

przenosi zawartość wyznaczonej lokalizacji pamięci do rejestru docelowego.

Jeśli zastąpimy MOVBy LEA, a następnie adres miejsca pamięci jest obliczana w ten sam sposób przez <MEM-OPERAND>wypowiedzi adresowania. Ale zamiast zawartości lokalizacji pamięci, dostajemy samą lokalizację do miejsca docelowego.

LEAnie jest konkretną instrukcją arytmetyczną; jest to sposób na przechwycenie efektywnego adresu wynikającego z dowolnego z trybów adresowania pamięci procesora.

Na przykład możemy użyć LEAtylko na prosty adres bezpośredni. W ogóle nie jest zaangażowana arytmetyka:

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

To jest ważne; możemy to przetestować po znaku zachęty 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

Tutaj nie ma dodanej wartości skalowanej ani przesunięcia. Zero zostaje przeniesione do EAX. Możemy to zrobić za pomocą MOV z natychmiastowym operandem.

To jest powód, dla którego ludzie, którzy uważają, że nawiasy LEAsą zbyteczne, są w błędzie; nawiasy klamrowe nie sąLEA składniowe, ale są częścią trybu adresowania.

LEA jest prawdziwa na poziomie sprzętowym. Wygenerowana instrukcja koduje aktualny tryb adresowania, a procesor wykonuje go do momentu obliczenia adresu. Następnie przenosi ten adres do miejsca docelowego zamiast generowania odwołania do pamięci. (Ponieważ obliczanie adresu trybu adresowania w dowolnej innej instrukcji nie ma wpływu na flagi procesora,LEA nie ma wpływu na flagi procesora).

Porównaj z ładowaniem wartości z adresu zero:

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

To bardzo podobne kodowanie, rozumiesz? Tylko 8dod LEAzmienił się8b .

Oczywiście to LEAkodowanie jest dłuższe niż przesunięcie natychmiastowego zera do EAX:

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

Nie ma jednak powodu, LEAaby wykluczać tę możliwość tylko dlatego, że istnieje krótsza alternatywa; po prostu łączy się w ortogonalny sposób z dostępnymi trybami adresowania.


6

Oto przykład.

// 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;
}

Przy opcji -O (optymalizacja) jako opcji kompilatora, gcc znajdzie instrukcję lea dla wskazanej linii kodu.


6

Wygląda na to, że wiele odpowiedzi jest już kompletnych. Chciałbym dodać jeszcze jeden przykładowy kod pokazujący, jak instrukcja lea i move działają inaczej, gdy mają ten sam format wyrażenia.

Krótko mówiąc, instrukcja lea i instrukcja mov mogą być używane z nawiasami otaczającymi argument src instrukcji. Gdy są one ujęte w () , wyrażenie w () jest obliczane w ten sam sposób; jednak dwie instrukcje interpretują obliczoną wartość w operandzie src w inny sposób.

Bez względu na to, czy wyrażenie jest używane z lea czy mov, wartość src jest obliczana jak poniżej.

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

Jednak gdy jest używane z instrukcją mov, próbuje uzyskać dostęp do wartości wskazanej przez adres wygenerowany przez powyższe wyrażenie i zapisać ją w miejscu docelowym.

W przeciwieństwie do tego, gdy instrukcja lea jest wykonywana z powyższym wyrażeniem, ładuje wygenerowaną wartość tak, jak jest do miejsca docelowego.

Poniższy kod wykonuje instrukcję lea i instrukcję mov z tym samym parametrem. Jednak, aby uchwycić różnicę, dodałem moduł obsługi sygnału na poziomie użytkownika, aby wychwycić błąd segmentacji spowodowany dostępem do niewłaściwego adresu w wyniku instrukcji mov.

Przykładowy kod

#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
          );
}

Wynik wykonania

Result of lea instruction: 14
inst at 0x4007b5 tries to access memory at 14, but failed

1
Podział wbudowanego asm na osobne instrukcje jest niebezpieczny, a twoje listy clobbers są niekompletne. Blok Basic-asm informuje, że kompilator nie ma blokad, ale w rzeczywistości modyfikuje kilka rejestrów. Możesz także użyć, =daby poinformować kompilator, że wynik jest w EDX, zapisując a mov. Pominięto również deklarację wczesnego clobbera na wyjściu. To pokazuje, co próbujesz zademonstrować, ale jest także mylącym złym przykładem wbudowanego asm, który się zepsuje, jeśli zostanie użyty w innych kontekstach. To zła rzecz dla odpowiedzi na przepełnienie stosu.
Peter Cordes

Jeśli nie chcesz pisać %%na wszystkich nazwach rejestrów w Extended asm, użyj ograniczeń wejściowych. jak asm("lea 4(%%ebx, %%eax, 8), %%edx" : "=d"(result_of_lea) : "a"(1), "b"(2));. Pozwolenie kompilatorowi na zarejestrowanie się oznacza, że ​​nie musisz też deklarować clobberów. Nadmiernie komplikujesz rzeczy przez xor-zerowanie, zanim mov-instant również nadpisze cały rejestr.
Peter Cordes

@PeterCordes Dzięki, Peter, czy chcesz, żebym usunął tę odpowiedź lub zmodyfikował ją po komentarzach?
Jaehyuk Lee

1
Jeśli naprawisz wbudowany asm, nie wyrządzi to żadnej szkody i może będzie dobrym konkretnym przykładem dla początkujących, którzy nie rozumieli innych odpowiedzi. Nie trzeba go usuwać, a jest to łatwa naprawa, jak pokazałem w moim ostatnim komentarzu. Myślę, że warto byłoby pozytywnie zareagować, gdyby zły przykład wbudowanego asm został ustawiony na „dobry” przykład. (Nie przegłosowałem)
Peter Cordes

1
Gdzie ktoś mówi, że mov 4(%ebx, %eax, 8), %edxjest nieważny? W każdym razie tak, movponieważ sensowne byłoby napisanie, "a"(1ULL)aby poinformować kompilator, że masz wartość 64-bitową, a zatem musi upewnić się, że jest rozszerzony, aby wypełnić cały rejestr. W praktyce będzie nadal używany mov $1, %eax, ponieważ pisanie EAX-zero rozszerza się do RAX, chyba że masz dziwną sytuację otaczającego kodu, w której kompilator wiedział, że RAX = 0xff00000001czy coś. Bo leanadal używasz 32-bitowego rozmiaru argumentu, więc wszelkie zbłąkane wysokie bity w rejestrach wejściowych nie mają wpływu na wynik 32-bitowy.
Peter Cordes

4

LEA: tylko instrukcja „arytmetyczna”.

MOV przesyła dane między operandami, ale lea tylko oblicza


LEA oczywiście przenosi dane; ma operand docelowy. LEA nie zawsze oblicza; oblicza, czy obliczany jest efektywny adres wyrażony w operandzie źródłowym. LEA EAX, GLOBALVAR nie oblicza; po prostu przenosi adres GLOBALVAR do EAX.
Kaz

@Kaz dzięki za opinie. moje źródło brzmiało: „LEA (efektywny adres ładowania) jest zasadniczo instrukcją arytmetyczną - nie wykonuje żadnego faktycznego dostępu do pamięci, ale jest powszechnie używany do obliczania adresów (chociaż można obliczyć za jej pomocą liczby całkowite ogólnego przeznaczenia”). tworzą Eldad-Eilam book Page 149
księgowego

@Kaz: Dlatego LEA jest redundantne, gdy adres jest już stałą czasową łącza; użyj mov eax, offset GLOBALVARzamiast tego. Państwo może używać Lea, ale jest nieco większy niż rozmiar kodu mov r32, imm32i działa na mniejszej liczbie portów, gdyż nadal przechodzi proces obliczania adresu . lea reg, symboljest użyteczny tylko w wersji 64-bitowej dla LEA zależnego od RIP, gdy potrzebujesz PIC i / lub adresów spoza niskich 32 bitów. W 32 lub 16-bitowym kodzie nie ma przewagi. LEA jest instrukcją arytmetyczną, która ujawnia zdolność CPU do dekodowania / obliczania trybów adresowania.
Peter Cordes

@Kaz: przez ten sam argument można powiedzieć, że imul eax, edx, 1nie oblicza: po prostu kopiuje edx do eax. Ale tak naprawdę uruchamia twoje dane przez multiplikator z 3 opóźnieniami cyklu. Lub to rorx eax, edx, 0po prostu kopiuje (obraca o zero).
Peter Cordes

@PeterCordes Chodzi mi o to, że zarówno LEA EAX, GLOBALVAL, jak i MOV EAX, GLOBALVAR po prostu pobierają adres z natychmiastowego argumentu. Nie stosuje się mnożnika 1 ani przesunięcia 0; może tak być na poziomie sprzętowym, ale nie jest to widoczne w języku asemblera lub zestawie instrukcji.
Kaz

1

Wszystkie normalne instrukcje „obliczania”, takie jak dodawanie mnożenia, wyłączanie lub ustawianie flag statusu jak zero, znak. Jeśli używasz skomplikowanego adresu, AX xor:= mem[0x333 +BX + 8*CX] flagi są ustawiane zgodnie z operacją xor.

Teraz możesz użyć adresu wiele razy. Ładowanie takiego adresu do rejestru nigdy nie ma na celu ustawiania flag statusu i na szczęście tak nie jest. Fraza „ładuj efektywny adres” uświadamia programiście. Stąd pochodzi ten dziwny wyraz.

Oczywiste jest, że gdy procesor będzie mógł użyć skomplikowanego adresu do przetworzenia jego zawartości, będzie mógł go obliczyć do innych celów. Rzeczywiście można go użyć do wykonania transformacji x <- 3*x+1w jednej instrukcji. Jest to ogólna zasada w programowaniu asemblera: postępuj zgodnie z instrukcjami, jakkolwiek kołyszesz łódź. Liczy się tylko to, czy konkretna transformacja zawarta w instrukcji jest dla Ciebie przydatna.

Dolna linia

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

i

LEA, AX'| [BX]

mają taki sam wpływ na AX, ale nie na flagi stanu. (To jest notacja ciasdis .)


„Jest to ogólna zasada w programowaniu asemblera: postępuj zgodnie z instrukcjami, jakkolwiek kołyszesz łódź” Nie rozdałbym tej porady osobiście, ponieważ call lbl lbl: pop raxtechnicznie „działa” jako sposób na uzyskanie wartości rip, ale sprawisz, że przewidywanie gałęzi będzie bardzo niezadowolone.
Postępuj

@ The6P4C To przydatne zastrzeżenie. Jeśli jednak nie ma alternatywy dla nieszczęśliwego przewidywania gałęzi, trzeba to zrobić. Istnieje inna ogólna zasada w programowaniu asemblera. Mogą istnieć alternatywne sposoby zrobienia czegoś i musisz mądrze wybierać spośród alternatyw. Istnieją setki sposobów na przeniesienie zawartości rejestru BL do rejestru AL. Jeśli pozostała część RAX nie musi być zachowana, LEA może być opcją. Nie wpływanie na flagi może być dobrym pomysłem na niektórych tysiącach typów procesorów x86. Groetjes Albert
Albert van der Horst

-1

Wybacz mi, jeśli ktoś już wspomniał, ale w dniach x86, kiedy segmentacja pamięci była nadal istotna, możesz nie uzyskać takich samych wyników z tych dwóch instrukcji:

LEA AX, DS:[0x1234]

i

LEA AX, CS:[0x1234]

1
„Adres efektywny” to tylko część „przesunięcia” seg:offpary. Baza segmentów nie ma wpływu na LEA; obie instrukcje zostaną (nieefektywnie) wprowadzone 0x1234do AX. x86 niestety nie ma łatwego sposobu na obliczenie pełnego adresu liniowego (efektywna + podstawa segmentu) do rejestru lub pary rejestrów.
Peter Cordes

@PeterCordes Bardzo przydatne, dziękuję za poprawienie mnie.
tzoz
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.