Diferencia entre movq y movabsq en x86-64
Me refiero a instrucciones de movimiento de datos en la arquitectura Intel x86-64. He leído que la movq
instrucción normal solo puede tener operandos de origen inmediatos que pueden representarse como números en complemento a dos de 32 bits, mientras que la movabsq
instrucción puede tener un valor inmediato arbitrario de 64 bits como operando de origen y solo puede tener un registro como destino. .
¿Podría dar más detalles sobre esto? ¿Eso significa que puedo mover un valor inmediato de 64 bits usando movabsq
solo la instrucción? ¿Y sólo del valor inmediato al registro? No veo cómo puedo mover un valor inmediato de 64 bits a la memoria. O tal vez me equivoqué en algo importante aquí.
A menos que su valor de 64 bits pueda codificarse como un inmediato extendido con signo de 32 bits, primero debe moverlo a un registro y luego almacenarlo. (O haga dos almacenes separados de 32 bits u otra solución peor para obtener los bytes donde los desea).
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
. Sólo 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:movabs
omov
/movq
con una constante amplia.
(Los recuentos de bytes solo son para destinos de registro o modos de direccionamiento que no necesitan un byte SIB o disp8/disp32: solo código de operación + ModR/M + imm32 como mov dword [rdi], 123
)
Algunos ensambladores de sintaxis Intel (pero no GAS a menos que use as -Os
o gcc -Wa,-Os
) optimizarán constantes de 32 bits como mov rax, 1
5 bytes mov r32, imm32
(NASM hace esto), mientras que otros (como YASM) usarán 7 bytes mov r/m64, sign-extended-imm32
. Ambos eligen la codificación imm64 sólo para constantes grandes, sin tener que utilizar una mnemónica especial.
O con una equ
constante, desafortunadamente, YASM a veces usará la versión de 10 bytes incluso con constantes pequeñas.
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 inmediata o una dirección de memoria absoluta. (Hay otro grupo de formas especiales de mov
esa carga/almacenamiento al/ax/eax/rax desde/hacia 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
como bueno, por ejemplo movabs 0x123456789abc0, %eax
).
Incluso si el número es pequeño, como movabs $1, %rax
, seguirás obteniendo la versión de 10 bytes.
Algo de esto se menciona en esta guía de novedades en x86-64 usando la sintaxis de AT&T.
Sin embargo, el mov
mnemotécnico (con o sin q
sufijo de tamaño de operando) elegirá entre el tamaño del inmediato mov r/m64, imm32
y dependiendo del mismo. mov r64, imm64
(Consulte ¿ Cuál es la diferencia entre las instrucciones movq y movabsq de AT&T x86-64? , 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 apunta a archivos objeto ELF de Linux, GAS supone que si no usó movabs
, pretendía 32 bits absolutos. (YASM hace lo mismo mov rsi, string
con una reubicación R_X86_64_32, pero NASM tiene por defecto movabs
, lo que produce una reubicación R_X86_64_64).
Si por alguna razón desea utilizar un nombre de símbolo como inmediato absoluto (en lugar de un LEA relativo a RIP que normalmente es mejor), necesitamovabs
(En objetivos como Mach-O64 en OS X, movq $symbol, %rax
siempre se puede elegir la codificación imm64, porque las direcciones absolutas de 32 bits nunca son válidas. Hay algunas preguntas y respuestas de MacOS sobre SO en las que 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
Ensamblados con GAS en un archivo objeto (con .bss; symbol:
), obtenemos estas reubicaciones. Tenga en cuenta la diferencia entre reubicaciones de 32 bits R_X86_64_32S
(firmadas) , ( R_X86_64_32
sin firmar) y (relativas a la PC).R_X86_64_PC32
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 que no es 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 normalidad gcc foo.S
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 crear un ejecutable estático, sin utilizar realmente movabs
).
movq
es siempre el formato de 7 bytes o 10 bytes, así que no lo use mov $1, %rax
a 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 de manera eficiente la longitud de las instrucciones en x86 moderno? ). Úselo mov $1, %eax
para obtener el formato de 5 bytes.
Tenga en cuenta que movq $0xFFFFFFFF, %rax
no se puede utilizar el formato de 7 bytes, porque no se puede representar con un inmediato de 32 bits con extensión de signo%eax
y necesita la codificación imm64 o la codificación de destino. GAS no hará esta optimización por usted, por lo que quedará atrapado con la codificación de 10 bytes. Definitivamente quieres mov $0xFFFFFFFF, %eax
.
movabs
con una fuente inmediata es siempre la forma imm64.
( movabs
También puede ser la codificación MOV con una dirección absoluta de 64 bits y RAX como origen o destino: me gusta 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 aparte y la respuesta es: no puedes. La entrada manual insn ref para MOV deja esto claro: el único formulario que tiene un operando inmediato imm64 solo tiene un destino de registro, no r/m64.
Si su valor cabe en un inmediato de 32 bits con signo extendido, movq $0x123456, 32(%rdi)
realizará un almacenamiento de 8 bytes en la memoria . La limitación es que los 32 bits superiores deben ser copias del bit 31, porque debe poder codificarse como un signo-imm32 extendido.
Relacionado:
- ¿Por qué no podemos mover un valor inmediato de 64 bits a la memoria? - arquitectura de computadora / razones de diseño ISA.
- Cómo cargar la dirección de una función o etiqueta en el registro (use 5 bytes
mov r32, imm32
como optimización o LEA relativa a RIP en cualquier caso, excepto en un modelo de memoria grande donde un símbolo puede estar a más de 2 GiB de distancia).