Wat is het doel van de LEA-instructie?

Voor mij lijkt het gewoon een funky MOV. Wat is het doel en wanneer moet ik het gebruiken?


Antwoord 1, autoriteit 100%

Zoals anderen al hebben opgemerkt, wordt LEA (laadeffectief adres) vaak gebruikt als een “truc” om bepaalde berekeningen uit te voeren, maar dat is niet het primaire doel. De x86-instructieset is ontworpen om talen op hoog niveau te ondersteunen, zoals Pascal en C, waar arrays & mdash; vooral arrays van ints of kleine structs & mdash; gebruikelijk zijn. Beschouw bijvoorbeeld een struct die (x, y) coördinaten vertegenwoordigt:

struct Point
{
     int xcoord;
     int ycoord;
};

Stel je nu een uitspraak voor als:

int y = points[i].ycoord;

waarbij points[] een array is van Point. Ervan uitgaande dat de basis van de array zich al in ebx bevindt, en variabele i in EAX, en xcoord en ycoord zijn elk 32 bits (dus ycoord heeft een offset van 4 bytes in de struct), deze verklaring kan worden gecompileerd tot:

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

die y zal landen in EDX. De schaalfactor van 8 is omdat elk Point 8 bytes groot is. Beschouw nu dezelfde uitdrukking die wordt gebruikt met de operator “adres van” &:

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

In dit geval wil je niet de waarde van ycoord, maar het adres. Dat is waar LEA (laad effectief adres) om de hoek komt kijken. In plaats van een MOV kan de compiler

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

die het adres in ESI laadt.


Antwoord 2, autoriteit 68%

Van de "Zen of Assembly" door Abrash:

LEA, de enige instructie die geheugenadresseringsberekeningen uitvoert, maar het geheugen niet daadwerkelijk adresseert. LEA accepteert een standaard geheugenadresseringsoperand, maar doet niets meer dan de berekende geheugenoffset op te slaan in het opgegeven register, dat een willekeurig register voor algemene doeleinden kan zijn.

Wat levert dat ons op? Twee dingen die ADD niet biedt:

  1. de mogelijkheid om optellingen uit te voeren met twee of drie operanden, en
  2. de mogelijkheid om het resultaat op te slaan in elk register; niet alleen een van de bronoperanden.

En LEA verandert de vlaggen niet.

Voorbeelden

  • LEA EAX, [ EAX + EBX + 1234567 ] berekent EAX + EBX + 1234567 (dat zijn drie operanden)
  • LEA EAX, [ EBX + ECX ] berekent EBX + ECX zonder een van beide te overschrijven met het resultaat.
  • vermenigvuldiging met constante (met twee, drie, vijf of negen), als je het gebruikt zoals LEA EAX, [ EBX + N * EBX ] (N kan 1,2,4,8 zijn ).

Andere usecase is handig in loops: het verschil tussen LEA EAX, [ EAX + 1 ] en INC EAX is dat de laatste EFLAGS maar de eerste niet; hierdoor blijft de status CMP behouden.


Antwoord 3, autoriteit 14%

Een ander belangrijk kenmerk van de LEA-instructie is dat het de conditiecodes zoals CF en ZF niet verandert tijdens het berekenen van het adres door rekenkundige instructies zoals ADD of MUL doet. Deze functie vermindert de mate van afhankelijkheid tussen instructies en maakt zo ruimte voor verdere optimalisatie door de compiler of hardwareplanner.


Antwoord 4, autoriteit 12%

Ondanks alle uitleg is LEA een rekenkundige bewerking:

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

Het is gewoon dat de naam extreem dom is voor een shift+add-bewerking. De reden daarvoor werd al uitgelegd in de best beoordeelde antwoorden (d.w.z. het was ontworpen om direct geheugenreferenties op hoog niveau in kaart te brengen).


Antwoord 5, autoriteit 10%

Misschien nog iets over LEA-instructie.
U kunt LEA ook gebruiken voor het snel vermenigvuldigen van registers met 3, 5 of 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

Antwoord 6, autoriteit 7%

LEA is een afkorting van “laad effectief adres”. Het laadt het adres van de locatiereferentie door de bronoperand naar de bestemmingsoperand. U kunt het bijvoorbeeld gebruiken om:

lea ebx, [ebx+eax*8]

om ebx pointer EAX items verder te verplaatsen (in een 64-bit/element array) met een enkele instructie. Kortom, u profiteert van complexe adresseringsmodi die worden ondersteund door x86-architectuur om pointers efficiënt te manipuleren.


Antwoord 7, autoriteit 3%

De belangrijkste reden dat u LEA gebruikt in plaats van een MOV is als u rekenkunde moet uitvoeren op de registers die u gebruikt om het adres te berekenen. In feite kunt u wat neerkomt op aanwijzerberekeningen op verschillende van de registers in combinatie effectief “gratis” uitvoeren.

Wat er echt verwarrend aan is, is dat je meestal een LEA schrijft, net als een MOV, maar dat je het geheugen niet echt derefeert. Met andere woorden:

MOV EAX, [ESP+4]

Hierdoor wordt de inhoud van waar ESP+4 naar verwijst, verplaatst naar EAX.

LEA EAX, [EBX*8]

Hierdoor wordt het effectieve adres EBX * 8 naar EAX verplaatst, niet naar het adres dat op die locatie wordt gevonden. Zoals je kunt zien, is het ook mogelijk om te vermenigvuldigen met factoren van twee (schalen) terwijl een MOV beperkt is tot optellen/aftrekken.


Antwoord 8, autoriteit 3%

De 8086 heeft een grote reeks instructies die een registeroperand en een effectief adres accepteren, enkele berekeningen uitvoeren om het offset-gedeelte van dat effectieve adres te berekenen, en een bewerking uitvoeren met betrekking tot het register en het geheugen waarnaar wordt verwezen door het berekende adres . Het was vrij eenvoudig om een ​​van de instructies in die familie zich te laten gedragen zoals hierboven, behalve dat de eigenlijke geheugenbewerking werd overgeslagen. Dus de instructies:

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

werden intern bijna identiek geïmplementeerd. Het verschil is een overgeslagen stap. Beide instructies werken ongeveer als volgt:

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

Waarom Intel dacht dat deze instructie de moeite waard was, weet ik niet precies, maar het feit dat het goedkoop te implementeren was, zou een grote factor zijn geweest. Een andere factor zou het feit zijn geweest dat Intel’s assembler toestond dat symbolen werden gedefinieerd ten opzichte van het BP-register. Als fnord werd gedefinieerd als een BP-relatief symbool (bijv. BP+8), zou men kunnen zeggen:

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

Als men iets als stosw zou willen gebruiken om gegevens op te slaan op een BP-relatief adres, kunnen zeggen

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

was handiger dan:

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

Merk op dat het vergeten van de wereld "offset" zou ervoor zorgen dat de inhoud van locatie [BP+8], in plaats van de waarde 8, wordt toegevoegd aan DI. Oeps.


Antwoord 9

Zoals de bestaande antwoorden vermeldden, heeft LEA de voordelen van het uitvoeren van geheugenadresseringsberekeningen zonder toegang tot het geheugen, waarbij het rekenkundige resultaat wordt opgeslagen in een ander register in plaats van de eenvoudige vorm van optelinstructie. Het echte onderliggende prestatievoordeel is dat de moderne processor een aparte LEA ALU-eenheid en poort heeft voor effectieve adresgeneratie (inclusief LEA en ander geheugenreferentie-adres), dit betekent de rekenkundige bewerking in LEA en andere normale rekenkundige bewerkingen in ALU kunnen parallel in één kern worden uitgevoerd.

Bekijk dit artikel van Haswell-architectuur voor enkele details over LEA-eenheid:
http://www.realworldtech.com/haswell-cpu/4/

Een ander belangrijk punt dat niet in andere antwoorden wordt genoemd, is de LEA REG, [MemoryAddress]-instructie is PIC (positie-onafhankelijke code) die het relatieve pc-adres in deze instructie codeert om te verwijzen naar MemoryAddress. Dit is anders dan MOV REG, MemoryAddress dat een relatief virtueel adres codeert en moet worden verplaatst/gepatcht in moderne besturingssystemen (zoals ASLR een veelvoorkomend kenmerk is). Dus LEA kan worden gebruikt om dergelijke niet-PIC naar PIC te converteren.


Antwoord 10

De LEA-instructie (Load Effective Address) is een manier om het adres te verkrijgen dat voortkomt uit een van de geheugenadresseringsmodi van de Intel-processor.

Dat wil zeggen, als we een gegevensverplaatsing als deze hebben:

MOV EAX, <MEM-OPERAND>

het verplaatst de inhoud van de aangewezen geheugenlocatie naar het doelregister.

Als we de MOV vervangen door LEA, dan wordt het adres van de geheugenlocatie op precies dezelfde manier berekend door de <MEM-OPERAND> adresuitdrukking. Maar in plaats van de inhoud van de geheugenlocatie, krijgen we de locatie zelf in de bestemming.

LEA is geen specifieke rekenkundige instructie; het is een manier om het effectieve adres te onderscheppen dat voortkomt uit een van de geheugenadresseringsmodi van de processor.

We kunnen bijvoorbeeld LEA gebruiken op slechts een eenvoudig direct adres. Er komt helemaal geen rekenkunde bij kijken:

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

Dit is geldig; we kunnen het testen bij de Linux-prompt:

$ 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

Hier is er geen toevoeging van een geschaalde waarde en geen offset. Nul wordt verplaatst naar EAX. We zouden dat ook kunnen doen met MOV met een onmiddellijke operand.

Dit is de reden waarom mensen die denken dat de haakjes in LEA overbodig zijn, zich ernstig vergissen; de haakjes zijn geen LEA-syntaxis, maar maken deel uit van de adresseringsmodus.

LEA is echt op hardwareniveau. De gegenereerde instructie codeert de feitelijke adresseringsmodus en de processor voert deze uit tot het punt waarop het adres wordt berekend. Vervolgens verplaatst het dat adres naar de bestemming in plaats van een geheugenreferentie te genereren. (Aangezien de adresberekening van een adresseringsmodus in een andere instructie geen effect heeft op CPU-vlaggen, heeft LEA geen effect op CPU-vlaggen.)

Contrast met het laden van de waarde vanaf adres nul:

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

Het is een zeer vergelijkbare codering, zie je? Alleen de 8d van LEA is gewijzigd in 8b.

Natuurlijk is deze LEA-codering langer dan het verplaatsen van een onmiddellijke nul naar EAX:

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

Er is echter geen reden voor LEA om deze mogelijkheid uit te sluiten, alleen omdat er een korter alternatief is; het is gewoon een orthogonale combinatie met de beschikbare adresseringsmodi.


Antwoord 11

De LEA-instructie kan worden gebruikt om tijdrovende berekeningen van effectieve adressen door de CPU te vermijden. Als een adres herhaaldelijk wordt gebruikt, is het effectiever om het in een register op te slaan in plaats van het effectieve adres telkens te berekenen wanneer het wordt gebruikt.


Antwoord 12

Het lijkt erop dat veel antwoorden al zijn voltooid, ik zou graag nog een voorbeeldcode willen toevoegen om te laten zien hoe de lea- en move-instructie anders werken wanneer ze hetzelfde expressieformaat hebben.

Om een ​​lang verhaal kort te maken, lea-instructie en mov-instructies kunnen beide worden gebruikt met de haakjes die de src-operand van de instructies omsluiten. Als ze tussen de () staan, wordt de uitdrukking in de () op dezelfde manier berekend; twee instructies zullen de berekende waarde in de src-operand echter op een andere manier interpreteren.

Of de uitdrukking nu wordt gebruikt met de lea of ​​mov, de src-waarde wordt als volgt berekend.

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

Wanneer het echter wordt gebruikt met de mov-instructie, probeert het toegang te krijgen tot de waarde waarnaar wordt verwezen door het adres dat wordt gegenereerd door de bovenstaande expressie en het op te slaan op de bestemming.

In tegenstelling hiermee, wanneer de lea-instructie wordt uitgevoerd met de bovenstaande uitdrukking, laadt het de gegenereerde waarde zoals het is naar de bestemming.

De onderstaande code voert de lea-instructie en mov-instructie uit met dezelfde parameter. Om het verschil op te vangen, heb ik echter een signaalhandler op gebruikersniveau toegevoegd om de segmentatiefout op te vangen die wordt veroorzaakt door toegang tot een verkeerd adres als gevolg van mov-instructie.

Voorbeeldcode

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

Uitvoeringsresultaat

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

Antwoord 13

Hier is een voorbeeld.

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

Met -O (optimaliseren) als compileroptie, zal gcc de lea-instructie voor de aangegeven coderegel vinden.


Antwoord 14

LEA: gewoon een “rekenkundige” instructie..

MOV draagt ​​gegevens over tussen operanden, maar lea is gewoon aan het berekenen


Antwoord 15

Alle normale “bereken” instructies zoals vermenigvuldiging toevoegen, exclusief of de statusvlaggen instellen als nul, teken. Als u een ingewikkeld adres gebruikt, AX xor:= mem[0x333 +BX + 8*CX], worden de vlaggen ingesteld volgens de xor-bewerking.

Nu wilt u misschien het adres meerdere keren gebruiken. Het laden van een dergelijk adres in een register is nooit bedoeld om statusvlaggen in te stellen en gelukkig ook niet. De zinsnede “laad effectief adres” maakt de programmeur zich daarvan bewust. Dat is waar de rare uitdrukking vandaan komt.

Het is duidelijk dat als de processor eenmaal in staat is om het gecompliceerde adres te gebruiken om de inhoud te verwerken, hij deze ook voor andere doeleinden kan berekenen. Het kan inderdaad worden gebruikt om een ​​transformatie x <- 3*x+1 in één instructie uit te voeren. Dit is een algemene regel bij het programmeren van assemblages: Gebruik de instructies, hoe je boot ook schommelt.
Het enige dat telt, is of de specifieke transformatie die door de instructie wordt belichaamd, nuttig voor u is.

Kortom

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

en

LEA, AX'| [BX]

hebben hetzelfde effect op AX maar niet op de statusvlaggen.
(Dit is de notatie ciasdis.)


Antwoord 16

Vergeef me als iemand het al zei, maar in de dagen van x86 toen geheugensegmentatie nog relevant was, krijg je misschien niet dezelfde resultaten met deze twee instructies:

LEA AX, DS:[0x1234]

en

LEA AX, CS:[0x1234]

LEAVE A REPLY

Please enter your comment!
Please enter your name here

16 − seven =

Other episodes