assembly - lea - Hacer referencia a los contenidos de una ubicación de memoria.(modos de direccionamiento x86)
x86 instruction reference (2)
Tengo una ubicación de memoria que contiene un personaje que quiero comparar con otro personaje (y no está en la parte superior de la pila, por lo que no puedo
pop
).
¿Cómo hago referencia al contenido de una ubicación de memoria para poder compararlo?
Básicamente, ¿cómo lo hago sintácticamente?
Para una discusión más extensa sobre los modos de direccionamiento (16/32/64 bits), consulte la guía "Optimización del ensamblaje" de Agner Fog , sección 3.3. Esa guía tiene muchos más detalles que esta respuesta para la reubicación de símbolos y / o código independiente de la posición de 32 bits, entre otras cosas.
Ver también: tabla de sintaxis de AT&T (GNU) versus sintaxis NASM para diferentes modos de direccionamiento , incluidos saltos / llamadas indirectos.
También vea la colección de enlaces al final de esta respuesta.
Sugerencias de bienvenida, especialmente. en qué partes fueron útiles / interesantes, y qué partes no lo son.
x86 (32 y 64 bits) tiene varios modos de direccionamiento para elegir. Son todos de la forma:
[base_reg + index_reg*scale + displacement] ; or a subset of this
[RIP + displacement] ; or RIP-relative: 64bit only. No index reg is allowed
(donde la escala es 1, 2, 4 u 8, y el desplazamiento es una constante de 32 bits con signo).
Todas las otras formas (excepto RIP-relativo) son subconjuntos de esto que dejan de lado uno o más componentes
.
Esto significa que no necesita un
index_reg
a cero para acceder a
[rsi]
por ejemplo.
En el código fuente de ASM, no importa en qué orden escribas:
[5 + rax + rsp + 15*4 + MY_ASSEMBLER_MACRO*2]
funciona bien.
(Todas las matemáticas en constantes ocurren en el momento del ensamblaje, lo que resulta en un solo desplazamiento constante).
Todos los registros deben tener el mismo tamaño que el modo en el que se encuentra, a menos que use un tamaño de dirección alternativo , que requiera un byte de prefijo adicional. Los punteros estrechos rara vez son útiles fuera del x32 ABI (ILP32 en modo largo) .
Si desea
utilizar
al
como un índice de matriz, por ejemplo
, necesita extenderlo a cero o con signo al ancho del puntero.
(A veces es posible tener los bits superiores de
rax
cero antes de jugar con los registros de bytes, y es una buena manera de lograrlo).
Todos los subconjuntos posibles del caso general son codificables, excepto los que usan la
e/rsp*scale
(obviamente inútil en el código "normal" que siempre mantiene un puntero para apilar la memoria en
esp
).
Normalmente, el tamaño del código de las codificaciones es:
- 1B para modos de un registro (mod / rm (Modo / Registro o memoria))
- 2B para modos de dos registros (mod / rm + SIB (Base de índice de escala) byte)
-
el desplazamiento puede ser 0, 1 o 4 bytes (signo extendido a 32 o 64, dependiendo del tamaño de la dirección).
Por lo tanto, los desplazamientos de
[-128 to +127]
pueden usar la codificacióndisp8
más compacta, ahorrando 3 bytes frente adisp32
.
excepciones de tamaño de código:
-
[reg*scale]
por sí solo solo puede codificarse con un desplazamiento de 32 bits. Los ensambladores inteligentes evitan eso codificandolea eax, [rdx*2]
comolea eax, [rdx + rdx]
, pero ese truco solo funciona para escalar en 2. -
Es imposible codificar
e/rbp
or13
como el registro base sin un byte de desplazamiento, por lo que[ebp]
se codifica como[ebp + byte 0]
. Las codificaciones sin desplazamiento conebp
como registro base significan que no hay registro base (por ejemplo, para[disp + reg*scale]
). -
[e/rsp]
requiere un byte SIB incluso si no hay registro de índice. (si hay o no un desplazamiento). La codificación mod / rm que especificaría[rsp]
significa que hay un byte SIB.
Consulte la Tabla 2-5 en el manual de referencia de Intel, y la sección que lo rodea, para obtener detalles sobre los casos especiales. (Son iguales en el modo de 32 y 64 bits. Agregar codificación relativa a RIP no entraba en conflicto con ninguna otra codificación, incluso sin un prefijo REX).
Para el rendimiento, generalmente no vale la pena gastar una instrucción adicional solo para obtener un código de máquina x86 más pequeño. En las CPU Intel con un caché uop, es más pequeño que L1 I $ y es un recurso más valioso. Minimizar uops de dominio fusionado suele ser más importante.
El tamaño de la dirección de 16 bits no puede usar un byte SIB, por lo que todos los modos de direccionamiento de uno y dos registros están codificados en el byte mod / rm único.
reg1
puede ser BX o BP, y
reg2
puede ser SI o DI (o puede usar cualquiera de esos 4 registros por sí mismo).
El escalado no está disponible.
El código de 16 bits es obsoleto por muchas razones, incluida esta, y no vale la pena aprenderlo si no es necesario.
Tenga en cuenta que las restricciones de 16 bits se aplican en el código de 32 bits cuando se utiliza el prefijo de tamaño de dirección, por lo que la matemática LEA de 16 bits es muy restrictiva.
Sin embargo, puede evitar eso:
lea eax, [edx + ecx*2]
establece
ax = dx + cx*2
,
porque la basura en los bits superiores de los registros de origen no tiene ningún efecto
.
Cómo se usan
Esta tabla no coincide exactamente con las codificaciones de hardware de los posibles modos de direccionamiento, ya que estoy distinguiendo entre usar una etiqueta (por ejemplo, datos globales o estáticos) frente a usar un pequeño desplazamiento constante. Así que estoy cubriendo modos de direccionamiento de hardware + soporte de enlazador para símbolos.
Si tiene un puntero
char array[]
en
esi
,
-
mov al, esi
: inválido, no se ensamblará. Sin corchetes, no es una carga en absoluto. Es un error porque los registros no son del mismo tamaño. -
mov al, [esi]
carga el byte apuntado. -
mov al, [esi + ecx]
carga laarray[ecx]
. -
mov al, [esi + 10]
carga laarray[10]
. -
mov al, [esi + ecx*8 + 200]
carga laarray[ecx*8 + 200]
-
mov al, [global_array + 10]
cargas deglobal_array[10]
. En el modo de 64 bits, esta puede ser una dirección relativa a RIP. Se recomienda usarDEFAULT REL
, para generar direcciones relativas a RIP por defecto en lugar de tener que usar siempre[rel global_array + 10]
. No hay forma de usar un registro de índice con una dirección relativa a RIP directamente. El método normal eslea rax, [global_array]
mov al, [rax + rcx*8 + 10]
o similar. -
mov al, [global_array + ecx + edx*2 + 10]
cargas deglobal_array[ecx + edx*2 + 10]
Obviamente, puede indexar una matriz estática / global con un solo registro. Incluso es posible una matriz 2D que use dos registros separados. (preescalar uno con una instrucción adicional, para factores de escala distintos de 2, 4 u 8). Tenga en cuenta queglobal_array + 10
matemática se realiza en el momento del enlace. El archivo objeto (salida del ensamblador, entrada del enlazador) informa al enlazador del +10 para agregar a la dirección absoluta final, para colocar el desplazamiento correcto de 4 bytes en el ejecutable (salida del enlazador). Es por eso que no puede usar expresiones arbitrarias en constantes de tiempo de enlace que no son constantes de tiempo de ensamblaje (por ejemplo, direcciones de símbolos). -
mov al, 0ABh
No es una carga en absoluto, sino una constante inmediata que se almacenó dentro de la instrucción. (Tenga en cuenta que necesita prefijar un0
para que el ensamblador sepa que es una constante, no un símbolo. Algunos ensambladores también aceptarán0xAB
). Puede usar un símbolo como la constante inmediata, para obtener una dirección en un registro.-
NASM:
mov esi, global_array
ensambla en unmov esi, imm32
que pone la dirección en esi. -
MASM:
mov esi, OFFSET global_array
es necesario para hacer lo mismo. -
MASM:
mov esi, global_array
ensambla en una carga:mov esi, dword [global_array]
.
En el modo de 64 bits, el direccionamiento de símbolos globales generalmente se realiza con el direccionamiento relativo a RIP, que su ensamblador realizará de forma predeterminada con la directiva
DEFAULT REL
, o conmov al, [rel global_array + 10]
. No se puede usar ningún registro de índice con direcciones relativas a RIP, solo desplazamientos constantes. Todavía puede hacer direccionamiento absoluto, e incluso hay una forma especial demov
que se puede cargar desde una dirección absoluta de 64 bits (en lugar de la señal de 32 bits habitual extendida ). La sintaxis de AT&T llama a los códigos de operaciónmovabs
(también se usa paramov r64, imm64
), mientras que la sintaxis Intel / NASM todavía lo llama una forma demov
.Use
lea rsi, [rel global_array]
para obtener direcciones relativas a lalea rsi, [rel global_array]
en registros, ya quemov reg, imm
codificaría una dirección no relativa en los bytes de instrucciones.Tenga en cuenta que OS X carga todo el código en una dirección fuera de los 32 bits bajos, por lo que el direccionamiento absoluto de 32 bits no se puede utilizar. El código independiente de la posición no es necesario para los ejecutables, pero también podría serlo porque el direccionamiento absoluto de 64 bits es menos eficiente que el relativo a RIP. El formato de archivo de objeto macho64 no admite reubicaciones para direcciones absolutas de 32 bits como lo hace Linux ELF. Asegúrese de no utilizar un nombre de etiqueta como una constante de tiempo de compilación en ningún lugar, excepto en una dirección efectiva como
[global_array + constant]
, porque puede ensamblarse en un modo de direccionamiento relativo a RIP. por ejemplo,[global_array + rcx]
no está permitido, porque RIP no se puede usar con ningún otro registro, por lo que tendría que ensamblarse con la dirección absoluta deglobal_array
codificada como el desplazamiento de 32 bits ( que se extenderá a 64b ). -
NASM:
Cualquiera y todos estos modos de direccionamiento se pueden
usar con
LEA
para hacer cálculos enteros con la ventaja de no afectar las banderas
, independientemente de si se trata de una dirección válida.
[esi*4 + 10]
generalmente solo es útil con LEA (a menos que el desplazamiento sea un símbolo, en lugar de una pequeña constante).
En el código de máquina, no hay codificación para el registro escalado solo, por lo que
[esi*4]
tiene que ensamblarse a
[esi*4 + 0]
, con 4 bytes de ceros para un desplazamiento de 32 bits.
A menudo vale la pena copiar + shift en una instrucción en lugar de un mov + shl más corto, porque generalmente el rendimiento de uop es más un cuello de botella que el tamaño del código, especialmente en las CPU con un caché de uop decodificado.
Puede especificar anulaciones de segmento como
mov al, fs:[esi]
.
Una anulación de segmento solo agrega un prefijo-byte delante de la codificación habitual.
Todo lo demás permanece igual, con la misma sintaxis.
Incluso puede usar anulaciones de segmentos con direccionamiento relativo a RIP.
El direccionamiento absoluto de 32 bits requiere un byte más para codificar que el relativo a RIP, por lo que
mov eax, fs:[0]
puede codificarse de manera más eficiente utilizando un desplazamiento relativo que produce una dirección absoluta conocida.
es decir, elija rel32 para RIP + rel32 = 0. YASM hará esto con
mov ecx, [fs: rel 0]
, pero NASM siempre usa direccionamiento absoluto disp32, ignorando el especificador
rel
.
No he probado MASM o gas.
Si el tamaño del operando es ambiguo (por ejemplo, en una instrucción con un operando inmediato y uno de memoria), use
byte
/
word
/
dword
/
qword
/
xmmword
/
ymmword
para especificar:
mov dword [rsi + 10], 0xAB ; NASM
mov dword ptr [rsi + 10], 0xAB ; MASM and GNU .intex_syntax noprefix
movl $0xAB, 10(%rsi) # GNU(AT&T): operand size from insn suffix
Vea los documentos de yasm para obtener direcciones efectivas de sintaxis NASM , y / o la sección de entrada de wikipedia x86 sobre modos de direccionamiento . La página wiki dice lo que está permitido en el modo de 16 bits. Aquí hay otra "hoja de trucos" para los modos de direccionamiento de 32 bits .
También hay una guía más detallada para los modos de direccionamiento, para 16 bits . 16 bits todavía tiene los mismos modos de direccionamiento que 32 bits, por lo que si los modos de direccionamiento son confusos, léalo de todos modos
También vea la página wiki x86 para enlaces.
Aquí hay una hoja de referencia rápida, recuperada de este sitio . Muestra los diversos métodos disponibles para direccionar la memoria principal en el ensamblaje x86:
+------------------------+----------------------------+-----------------------------+
| Mode | Intel | AT&T |
+------------------------+----------------------------+-----------------------------+
| Absolute | MOV EAX, [0100] | movl 0x0100, %eax |
| Register | MOV EAX, [ESI] | movl (%esi), %eax |
| Reg + Off | MOV EAX, [EBP-8] | movl -8(%ebp), %eax |
| Reg*Scale + Off | MOV EAX, [EBX*4 + 0100] | movl 0x100(,%ebx,4), %eax |
| Base + Reg*Scale + Off | MOV EAX, [EDX + EBX*4 + 8] | movl 0x8(%edx,%ebx,4), %eax |
+------------------------+----------------------------+-----------------------------+
En su caso específico, si el artículo se encuentra en un
desplazamiento
de
4
del
EBP
base de la pila, usaría la notación
Reg + Off
:
MOV EAX, [ EBP - 4 ]
Esto copiaría el artículo en el registro
EAX
.