assembly x86 x86-64 att instruction-set

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 que mov 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.