assembly - Diferencia entre movq y movabsq en x86-64
att instruction-set (1)
Soy un recién llegado aquí y recién estoy comenzando a estudiar lenguaje ensamblador. Así que corrígeme si me equivoco, o si esta publicación no tiene ningún sentido, la eliminaré.
Estoy hablando de instrucciones de movimiento de datos en la arquitectura Intel x86-64.
He leído que la instrucción
movq
normal solo puede tener operandos fuente inmediatos que se pueden representar como números complementarios de dos de 32 bits, mientras que la instrucción
movabsq
puede tener un valor inmediato arbitrario de 64 bits como su operando fuente y solo puede tener un registro como un destino.
¿Podría por favor elaborar sobre esto?
¿
movabsq
significa que puedo mover el valor inmediato de 64 bits usando solo la instrucción
movabsq
?
¿Y solo desde el valor inmediato al registro?
No veo cómo puedo mover un valor inmediato de 64 bits a la memoria.
O tal vez me equivoqué con algo importante aquí.
En la sintaxis NASM / Intel,
mov r64, 0x...
selecciona
una codificación MOV
basada en la constante.
Hay cuatro para elegir con operandos inmediatos:
-
5 bytes
mov r32, imm32
. ( cero extendido para llenar el registro de 64 bits como siempre ). AT&T:mov
/movl
-
6+ bytes
mov r/m32, imm32
. solo es útil para destinos de memoria. AT&T:mov
/movl
-
7+ bytes
mov r/m64, sign-extended-imm32
. Puede almacenar 8 bytes en la memoria o establecer un registro de 64 bits en un valor negativo. AT&T:mov
/movq
-
10 bytes
mov r64, imm64
. (Esta es la versión REX.W = 1 del mismo código de operación sin ModRM quemov r32, imm32
) AT&T:mov
/movq
/movabs
(Los recuentos de bytes solo son para destinos de registro o modos de direccionamiento que no necesitan un byte SIB o disp8 / disp32: solo opcode + ModR / M + imm32).
Algunos ensambladores de sintaxis Intel (pero no GAS) optimizarán constantes de 32 bits como
mov rax, 1
a 5 bytes
mov r32, imm32
(NASM hace esto), mientras que otros (como YASM) usarán
mov r/m64, sign-extended-imm32
7 bytes
mov r/m64, sign-extended-imm32
.
Ambos eligen la codificación imm64 solo para constantes grandes, sin tener que usar un mnemónico especial.
O con una constante
equ
, YASM utilizará la versión de 10 bytes incluso con constantes pequeñas, desafortunadamente.
En GAS con sintaxis de AT&T
movabsq
significa que la codificación del código de máquina contendrá un valor de 64 bits: ya sea una constante constante o una dirección de memoria absoluta.
(Hay otro grupo de formas especiales de
mov
que cargan / almacenan al / ax / eax / rax de / a una dirección absoluta, y la versión de 64 bits usa una dirección absoluta de 64 bits, no relativa. La sintaxis de AT&T llama a eso
movabs
también, p. ej.
movabs 0x123456789abc0, %eax
).
Incluso si el número es pequeño, como
movabs $1, %rax
, todavía obtienes la versión de 10 bytes.
Algo de esto se menciona en esta guía de novedades de x86-64 con la sintaxis de AT&T.
Sin embargo, el mnemónico
mov
(con o sin sufijo de tamaño de operando
q
) elegirá entre
mov r/m64, imm32
y
mov r64, imm64
dependiendo del tamaño del inmediato.
(Consulte
¿Cuál es la diferencia entre las instrucciones x86-64 AT&T movq y movabsq?,
Un seguimiento que existe porque la primera versión de esta respuesta adivinó mal lo que hizo GAS con grandes constantes de tiempo de ensamblaje para
movq
).
Pero las direcciones de los símbolos no se conocen hasta el momento del enlace, por lo que no están disponibles cuando el ensamblador selecciona una codificación.
Al menos cuando se dirige a archivos de objetos ELF de Linux, GAS supone que si no usó
movabs
, pretendía un absoluto de 32 bits.
(YASM hace lo mismo para
mov rsi, string
con una reubicación R_X86_64_32, pero NASM por defecto es
movabs
, produciendo una reubicación R_X86_64_64).
Si por alguna razón desea utilizar un nombre de símbolo como un absoluto inmediato (en lugar de una LEA relativa a RIP normalmente mejor), necesita
movabs
(En objetivos como Mach-O64 en OS X,
movq $symbol, %rax
siempre puede elegir la codificación imm64, porque las direcciones absolutas de 32 bits nunca son válidas. Hay algunas preguntas y respuestas sobre MacOS en SO donde creo que la gente dijo que su código funcionaba
movq
para poner una dirección de datos en un registro).
Ejemplo en Linux / ELF con un
$symbol
inmediato
mov $symbol, %rdi # GAS assumes the address fits in 32 bits
movabs $symbol, %rdi # GAS is forced to use an imm64
lea symbol(%rip), %rdi # 7 byte RIP-relative addressing, normally the best choice for position-independent code or code loaded outside the low 32 bits
mov $symbol, %edi # optimal in position-dependent code
Ensamblado con GAS en un archivo de objeto (con
.bss; symbol:
, obtenemos estas reubicaciones.
Tenga en cuenta la diferencia entre
R_X86_64_32S
(firmado) frente a
R_X86_64_32
(sin signo) frente a
R_X86_64_PC32
(
R_X86_64_PC32
PC) de 32 bits.
0000000000000000 <.text>:
0: 48 c7 c7 00 00 00 00 mov $0x0,%rdi 3: R_X86_64_32S .bss
7: 48 bf 00 00 00 00 00 00 00 00 movabs $0x0,%rdi 9: R_X86_64_64 .bss
11: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 18 <.text+0x18> 14: R_X86_64_PC32 .bss-0x4
18: bf 00 00 00 00 mov $0x0,%edi 19: R_X86_64_32 .bss
Vinculado a un ejecutable no PIE (
gcc -no-pie -nostdlib foo.s
), obtenemos:
4000d4: 48 c7 c7 f1 00 60 00 mov $0x6000f1,%rdi
4000db: 48 bf f1 00 60 00 00 00 00 00 movabs $0x6000f1,%rdi
4000e5: 48 8d 3d 05 00 20 00 lea 0x200005(%rip),%rdi # 6000f1 <__bss_start>
4000ec: bf f1 00 60 00 mov $0x6000f1,%edi
Y, por supuesto, esto no se vinculará a un ejecutable PIE, debido a las reubicaciones absolutas de 32 bits.
movq $symbol, %rax
no funcionará con
gcc foo.S
normal en las distribuciones modernas de Linux
.
¿Ya no se permiten direcciones absolutas de 32 bits en Linux x86-64?
.
(Recuerde, la solución correcta es LEA relativa a RIP, o hacer un ejecutable estático, que en realidad no usa
movabs
).
movq
es siempre la forma de 7 bytes o 10 bytes, así que no use
mov $1, %rax
menos que desee una instrucción más larga para fines de alineación (en lugar de rellenar con NOP más adelante.
¿Qué métodos se pueden usar para extender eficientemente la longitud de la instrucción? en x86 moderno?
).
Use
mov $1, %eax
para obtener la forma de 5 bytes.
Observe que
movq $0xFFFFFFFF, %rax
no puede usar la forma de 7 bytes, porque no es representable con un
signo extendido de
32 bits inmediato, y necesita la codificación imm64 o la codificación de destino
%eax
.
GAS no hará esta optimización por usted, por lo que está atascado con la codificación de 10 bytes.
Definitivamente quieres
mov $0xFFFFFFFF, %eax
.
movabs
con una fuente inmediata es siempre la forma imm64.
(los
movabs
también pueden ser la
codificación MOV
con una dirección absoluta de 64 bits y RAX como fuente o
REX.W + A3
: como
REX.W + A3
MOV moffs64, RAX
).
No veo cómo puedo mover un valor inmediato de 64 bits a la memoria.
Esa es una pregunta separada, y la respuesta es: no puedes. La entrada manual insn ref para MOV lo deja claro: la única forma que tiene un operando inmediato imm64 solo tiene un destino de registro, no r / m64.
Si su valor encaja en un signo extendido de 32 bits inmediato,
movq $0x123456, 32(%rdi)
hará un almacenamiento de 8 bytes en la memoria
.
La limitación es que los 32 bits superiores tienen que ser copias del bit 31, porque debe ser codificable como un signo-extendido-imm32.