Es scheint, dass viele Antworten bereits vollständig sind. Ich möchte noch ein weiteres Codebeispiel hinzufügen, um zu zeigen, wie die Anweisungen lea und move unterschiedlich funktionieren, obwohl sie das gleiche Ausdrucksformat haben.
Um es kurz zu machen: Die Befehle lea und mov können beide mit den Klammern verwendet werden, die den src-Operanden der Befehle einschließen. Wenn sie mit der Klammer () ist der Ausdruck in der () wird auf die gleiche Weise berechnet; zwei Anweisungen interpretieren den berechneten Wert im src-Operanden jedoch auf unterschiedliche Weise.
Unabhängig davon, ob der Ausdruck mit lea oder mov verwendet wird, wird der src-Wert wie folgt berechnet.
D ( Rb, Ri, S ) \=> (Reg[Rb]+S*Reg[Ri]+ D)
Wenn er jedoch mit dem mov-Befehl verwendet wird, versucht er, auf den Wert zuzugreifen, auf den die durch den obigen Ausdruck erzeugte Adresse zeigt, und ihn im Ziel zu speichern.
Wird dagegen die Anweisung lea mit dem obigen Ausdruck ausgeführt, lädt sie den erzeugten Wert so, wie er ist, in das Ziel.
Der folgende Code führt den Befehl lea und den Befehl mov mit demselben Parameter aus. Um jedoch den Unterschied festzustellen, habe ich einen Signalhandler auf Benutzerebene hinzugefügt, um den Segmentierungsfehler abzufangen, der durch den Zugriff auf eine falsche Adresse als Ergebnis der mov-Anweisung verursacht wird.
Beispiel-Code
#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
);
}
Ergebnis der Ausführung
Result of lea instruction: 14
inst at 0x4007b5 tries to access memory at 14, but failed
8 Stimmen
Siehe auch LEA auf Werte anwenden, die keine Adressen/Zeiger sind? : LEA ist nur ein Shift-and-Add-Befehl. Er wurde wahrscheinlich zum 8086 hinzugefügt, weil die Hardware bereits vorhanden ist, um Adressierungsmodi zu dekodieren und zu berechnen, nicht weil er nur für die Verwendung mit Adressen "gedacht" ist. Denken Sie daran, dass Zeiger in Assembler nur ganze Zahlen sind.