x64 significa saber registers que como amd64 assembly x86 x86-64 x86-16

assembly - significa - x86 registers



¿Cuál es el propósito de la instrucción LEA? (14)

Para mí, solo parece un funky MOV. ¿Cuál es su propósito y cuándo debo usarlo?


A pesar de todas las explicaciones, LEA es una operación aritmética:

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

Es solo que su nombre es extremadamente estúpido para una operación de cambio + agregar. El motivo de esto ya se explicó en las respuestas más valoradas (es decir, fue diseñado para asignar directamente referencias de memoria de alto nivel).


Aquí hay un ejemplo.

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

Con -O (optimizar) como opción del compilador, gcc encontrará la instrucción lea para la línea de código indicada.


Como han señalado otros, LEA (dirección de carga efectiva) se usa a menudo como un "truco" para hacer ciertos cálculos, pero ese no es su propósito principal. El conjunto de instrucciones x86 fue diseñado para admitir lenguajes de alto nivel como Pascal y C, donde las matrices, especialmente las matrices de caracteres o pequeñas estructuras, son comunes. Considere, por ejemplo, una estructura que representa las coordenadas (x, y):

struct Point { int xcoord; int ycoord; };

Ahora imagine una afirmación como:

int y = points[i].ycoord;

donde points[] es una matriz de Point . Suponiendo que la base de la matriz ya está en EBX , y la variable i está en EAX , y xcoord y ycoord son cada uno de 32 bits (por lo que ycoord está en el offset de 4 bytes en la estructura), esta declaración se puede compilar para:

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

el cual aterrizará y en EDX . El factor de escala de 8 se debe a que cada Point tiene un tamaño de 8 bytes. Ahora considere la misma expresión utilizada con el operador "dirección de" y:

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

En este caso, no desea el valor de ycoord , sino su dirección. Ahí es donde entra LEA (dirección de carga efectiva). En lugar de un MOV , el compilador puede generar

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

que cargará la dirección en ESI .


Como se mencionó en las respuestas existentes, LEA tiene las ventajas de realizar aritmética de direccionamiento de memoria sin acceder a la memoria, guardando el resultado aritmético en un registro diferente en lugar de la forma simple de agregar instrucciones. El beneficio real subyacente es que el procesador moderno tiene una unidad y puerto LEA ALU separados para la generación efectiva de direcciones (incluidas LEA y otras direcciones de referencia de memoria), esto significa que la operación aritmética en LEA y otra operación aritmética normal en ALU se podrían realizar en paralelo en un núcleo

Consulte este artículo de la arquitectura de Haswell para obtener detalles sobre la unidad LEA: http://www.realworldtech.com/haswell-cpu/4/

Otro punto importante que no se menciona en otras respuestas es LEA REG, [MemoryAddress] instrucción LEA REG, [MemoryAddress] es PIC (código independiente de la posición) que codifica la dirección relativa de la PC en esta instrucción para hacer referencia a MemoryAddress . Esto es diferente de MOV REG, MemoryAddress que codifica la dirección virtual relativa y requiere reubicación / parcheo en los sistemas operativos modernos (como ASLR es una característica común). Por lo tanto, LEA se puede usar para convertir este tipo de PIC a PIC.


Del "Zen de la Asamblea" por Abrash:

LEA , la única instrucción que realiza los cálculos de direccionamiento de la memoria pero que no aborda la memoria. LEA acepta un operando de direccionamiento de memoria estándar, pero no hace nada más que almacenar el desplazamiento de memoria calculado en el registro especificado, que puede ser cualquier registro de propósito general.

¿Qué nos da eso? Dos cosas que ADD no proporciona:

  1. la capacidad de realizar sumas con dos o tres operandos, y
  2. la posibilidad de almacenar el resultado en cualquier registro; No solo uno de los operandos fuente.

Y LEA no altera las banderas.

Ejemplos

  • LEA EAX, [ EAX + EBX + 1234567 ] calcula EAX + EBX + 1234567 (son tres operandos)
  • LEA EAX, [ EBX + ECX ] calcula EBX + ECX sin anular tampoco con el resultado.
  • multiplicación por constante (por dos, tres, cinco o nueve), si lo usa como LEA EAX, [ EBX + N * EBX ] (N puede ser 1,2,4,8).

Otros casos de uso son útiles en bucles: la diferencia entre LEA EAX, [ EAX + 1 ] y INC EAX es que este último cambia a EFLAGS pero el primero no; Esto preserva el estado CMP .


El 8086 tiene una gran familia de instrucciones que aceptan un operando de registro y una dirección efectiva, realizan algunos cálculos para calcular la parte de compensación de esa dirección efectiva y realizan algunas operaciones que involucran el registro y la memoria a la que hace referencia la dirección calculada. Era bastante simple que una de las instrucciones de esa familia se comportara como se indica arriba, a excepción de omitir esa operación de memoria real. Esto, las instrucciones:

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

Se implementaron de manera casi idéntica internamente. La diferencia es un paso omitido. Ambas instrucciones funcionan algo así como:

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

En cuanto a por qué Intel pensó que valía la pena incluir esta instrucción, no estoy seguro, pero el hecho de que su implementación fuera barata hubiera sido un factor importante. Otro factor habría sido el hecho de que el ensamblador de Intel permitió que los símbolos se definieran en relación con el registro de BP. Si fnord se definió como un símbolo relativo a BP (por ejemplo, BP + 8), se podría decir:

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

Si uno quisiera usar algo como stosw para almacenar datos en una dirección relativa a BP, poder decir

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

Fue más conveniente que:

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

Tenga en cuenta que si se olvida el "desplazamiento" del mundo, los contenidos de la ubicación [BP + 8], en lugar del valor 8, se agregarán a DI. Ups.


LEA: sólo una instrucción "aritmética" ..

MOV transfiere datos entre operandos, pero lea es solo cálculo


La instrucción LEA (Load Effective Address) es una forma de obtener la dirección que surge de cualquiera de los modos de direccionamiento de memoria del procesador Intel.

Es decir, si tenemos un movimiento de datos como este:

MOV EAX, <MEM-OPERAND>

Mueve el contenido de la ubicación de memoria designada en el registro de destino.

Si reemplazamos el MOV por LEA , entonces la dirección de la ubicación de la memoria se calcula exactamente de la misma manera mediante la expresión de direccionamiento <MEM-OPERAND> . Pero en lugar de los contenidos de la ubicación de la memoria, obtenemos la ubicación en el destino.

LEA no es una instrucción aritmética específica; es una forma de interceptar la dirección efectiva que surge de cualquiera de los modos de direccionamiento de memoria del procesador.

Por ejemplo, podemos usar LEA en una simple dirección directa. No hay aritmética involucrada en absoluto:

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

Esto es válido; Podemos probarlo en el indicador de 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

Aquí, no hay una adición de un valor escalado, y ningún desplazamiento. Cero se mueve a EAX. Podríamos hacer eso usando MOV con un operando inmediato también.

Esta es la razón por la que las personas que piensan que los paréntesis en LEA son superfluos están muy equivocados; los corchetes no son sintaxis de LEA pero son parte del modo de direccionamiento.

LEA es real a nivel de hardware. La instrucción generada codifica el modo de direccionamiento real y el procesador lo lleva al punto de calcular la dirección. Luego mueve esa dirección al destino en lugar de generar una referencia de memoria. (Dado que el cálculo de la dirección de un modo de direccionamiento en cualquier otra instrucción no tiene efecto en los indicadores de CPU, LEA no tiene ningún efecto en los indicadores de CPU).

Contraste con la carga del valor de la dirección cero:

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

Es una codificación muy similar, ¿ves? Solo el 8d de LEA ha cambiado a 8b .

Por supuesto, esta codificación LEA es más larga que mover un cero inmediato a EAX :

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

No hay razón para que LEA excluya esta posibilidad, aunque solo sea porque hay una alternativa más corta; Simplemente se combina de forma ortogonal con los modos de direccionamiento disponibles.


La instrucción LEA se puede usar para evitar que la CPU haga cálculos de direcciones efectivas que consumen mucho tiempo. Si una dirección se usa repetidamente, es más efectivo almacenarla en un registro en lugar de calcular la dirección efectiva cada vez que se usa.


La razón principal por la que usa LEA sobre un MOV es si necesita realizar operaciones aritméticas en los registros que está utilizando para calcular la dirección. Efectivamente, puede realizar lo que equivale a la aritmética de punteros en varios de los registros en combinación de manera efectiva para "gratis".

Lo que es realmente confuso al respecto es que normalmente escribes un LEA como un MOV pero en realidad no estás desviando la referencia de la memoria. En otras palabras:

MOV EAX, [ESP+4]

Esto moverá el contenido de lo que ESP+4 apunta a EAX .

LEA EAX, [EBX*8]

Esto moverá la dirección efectiva EBX * 8 a EAX, no a lo que se encuentra en esa ubicación. Como puede ver, también, es posible multiplicar por factores de dos (escala) mientras que un MOV se limita a sumar / restar.


Otra característica importante de la instrucción LEA es que no altera los códigos de condición, como CF y ZF , mientras que el cálculo de la dirección mediante instrucciones aritméticas como ADD o MUL hace. Esta característica reduce el nivel de dependencia entre las instrucciones y, por lo tanto, deja espacio para una mayor optimización por parte del compilador o el programador de hardware.


Tal vez solo otra cosa sobre la instrucción LEA. También puede usar LEA para los registros de multiplicación rápida por 3, 5 o 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


es porque en lugar de eso escribes el código

mov dx,offset something

simplemente puedes escribir

lea dx,something


lea es una abreviatura de "carga efectiva". Carga la dirección de la referencia de ubicación por el operando de origen al operando de destino. Por ejemplo, podrías usarlo para:

lea ebx, [ebx+eax*8]

para mover más lejos los elementos eax puntero ebx (en una matriz de elementos / 64 bits) con una sola instrucción. Básicamente, se beneficia de los complejos modos de direccionamiento admitidos por la arquitectura x86 para manipular los punteros de manera eficiente.