Cómo leer registros e instrucciones x86-64
Desde registros parciales, tamaños de operando, MOV y LEA hasta la codificación máquina
Cómo leer registros e instrucciones x86-64
Estos artículos del blog se han creado con fines didácticos. El código real puede variar según la generación de CPU, el sistema operativo, la ABI, el ensamblador, el compilador y las opciones de construcción; identifique siempre el entorno objetivo.
Al principio, rax, eax, ax y al parecen cuatro registros distintos. La habilidad importante no es memorizar todos los mnemónicos, sino seguir qué anchura se lee, de dónde viene el valor, dónde se escribe y qué estado de la máquina cambia.
Separar tres niveles
Expresión C total += values[i];
Notación asm add rax, qword ptr [rcx + rdx*8]
Bytes máquina 48 03 04 D1
C expresa tipos e intención; el ensamblador representa instrucciones y operandos de forma legible; el código máquina es la secuencia de bytes que decodifica la CPU. No existe una correspondencia obligatoria de una línea de fuente por una instrucción.
Registros generales y parciales
x86-64 ofrece 16 registros generales de 64 bits: rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp y r8–r15. Algunas funciones son arquitectónicas, como el uso de rsp por las instrucciones de pila. Los registros de argumentos los decide la ABI.
| Nombre | Parte accesible |
|---|---|
rax | los 64 bits |
eax | 32 bits inferiores |
ax | 16 bits inferiores |
al | 8 bits inferiores |
ah | bits 8–15, nombre histórico |
En modo de 64 bits, escribir un registro general de 32 bits borra sus 32 bits superiores.
mov rax, 0FFFFFFFFFFFFFFFFh
mov eax, 7
; RAX = 0000000000000007h
Una escritura de 8 o 16 bits deja intactos los demás bits.
mov rax, 1122334455667788h
mov al, 0AAh
; RAX = 11223344556677AAh
Por eso el compilador puede poner xor eax, eax antes de setcc al. Los nombres ah, bh, ch y dh no pueden codificarse en una instrucción que necesita prefijo REX; normalmente conviene evitarlos en código nuevo.
La anchura forma parte del significado
mov al, byte ptr [rcx] ; 1 byte
mov ax, word ptr [rcx] ; 2 bytes
mov eax, dword ptr [rcx] ; 4 bytes
mov rax, qword ptr [rcx] ; 8 bytes
La dirección es la misma, pero cambia el tamaño del acceso. Una anchura equivocada puede leer o sobrescribir un campo vecino, cruzar una página o romper el contrato de layout de una estructura.
mov copia
Un mov normal no borra el origen. La mayoría de sus formas generales tampoco permiten copiar directamente de memoria a memoria.
mov rax, [rsi]
mov [rdi], rax
Esto no es una regla universal de que x86 solo pueda tocar una posición de memoria. Las instrucciones de cadena y las read-modify-write tienen sus propias formas; consulte la referencia de cada instrucción.
La extensión revela la interpretación entera
El byte 0xFF puede representar 255 sin signo o -1 con signo.
movzx eax, byte ptr [rcx] ; 255
movsx eax, byte ptr [rcx] ; extensión de signo a -1
movsxd rax, eax ; signed de 32 a 64 bits
movzx rellena con ceros y movsx replica el bit de signo. El desensamblado no conserva los nombres de tipos C; extensión, comparación y salto son evidencias, no una reconstrucción perfecta.
lea calcula una dirección sin leer memoria
lea rax, [rcx + rdx*4 + 16]
rax recibe la dirección efectiva; no se lee el valor almacenado allí.
lea rax, [rcx + 8] ; rax = rcx + 8
mov rax, [rcx + 8] ; carga los 8 bytes almacenados
lea eax, [rcx + rcx*2] ; eax = rcx * 3
Los compiladores también usan lea para sumas y multiplicaciones constantes que no modifican los flags aritméticos.
La aritmética sobrescribe el destino
add rax, rdx
sub rcx, 8
and eax, 0FFh
xor edx, edx ; todo rdx queda en cero
En sintaxis Intel suele cumplirse destination = destination op source. Estas instrucciones también cambian los flags. Ante un jcc, setcc o cmovcc, encuentre la instrucción que realmente produjo el estado consumido.
Sintaxis Intel y AT&T
; Intel: destino primero
mov rax, qword ptr [rbx + 8]
# AT&T: origen primero
movq 8(%rbx), %rax
La operación de CPU es la misma, pero invertir los operandos invierte por completo el flujo de datos.
Longitud variable
Una instrucción x86-64 mide entre 1 y 15 bytes y puede contener prefijos, opcode, ModR/M, SIB, displacement e immediate.
48 89 D8 mov rax, rbx
B8 2A 00 00 00 mov eax, 42
48 8B 44 D1 10 mov rax, [rcx + rdx*8 + 16]
No es necesario memorizar bytes, pero la estructura importa al comprobar límites de instrucciones, parches, relocations o un desensamblador que empezó un byte tarde.
Seguir una función pequeña
Bajo System V, int transform(int x) { return x * 3 + 5; } puede compilarse así:
transform:
lea eax, [rdi + rdi*2]
add eax, 5
ret
El argumento está en edi y el resultado en eax. El wraparound del hardware no define el overflow signed de C, que es comportamiento indefinido.
Lista de lectura
- ¿Sintaxis Intel o AT&T?
- ¿Qué anchura tienen los operandos?
- ¿Qué estado se lee y cuál se sobrescribe?
- ¿Una escritura de 32 bits limpia la mitad superior?
- ¿Cómo se calcula cada dirección efectiva?
- ¿Se producen o consumen flags?
- ¿La ABI asigna un rol de argumento, retorno o preservación?
Resumen
x86-64 se lee como transiciones de estado. Los registros parciales se solapan, una escritura de 32 bits limpia la mitad superior, mov copia, las extensiones muestran la interpretación entera y lea calcula una dirección efectiva sin acceder a memoria.