Lenguaje ensamblador - Instrucciones aritméticas
Las instrucciones aritméticas son la base de las operaciones matemáticas que ejecuta la CPU. Este capítulo detalla las instrucciones de suma, resta, multiplicación, división y las operaciones relacionadas con el acarreo en la arquitectura x86.
ADD - Instrucción de suma
ADDSuma el operando fuente y el operando destino, y almacena el resultado en el operando destino.
Ejemplo
; Ejemplo de la instrucción ADD: calcular 100 + 200 + 300
section .data
a dd 100
b dd 200
c dd 300
sum dd 0
section .text
global _start
_start:
; Método 1: registro += inmediato
mov eax, [a] ; eax = 100
add eax, 200 ; eax = 100 + 200 = 300
; Método 2: registro += registro
mov ebx, [b] ; ebx = 200
add eax, ebx ; eax = 300 + 200 = 500
; Método 3: registro += memoria
add eax, [c] ; eax = 500 + 300 = 800
; Almacenar el resultado
mov [sum], eax ; sum = 800
; Efecto de la suma en los flags
mov eax, 0xFFFFFFFF ; eax = el número sin signo más grande de 32 bits
add eax, 1 ; eax = 0 (desbordamiento con wrap-around)
; CF = 1 (se genera acarreo)
; ZF = 1 (el resultado es 0)
; OF = 0 (sin desbordamiento desde el punto de vista con signo)
mov eax, 1
mov ebx, 0
int 0x80
SUB - Instrucción de resta
SUBResta el operando fuente del operando destino y almacena el resultado en el operando destino.
Ejemplo
; Ejemplo de la instrucción SUB
section .data
x dd 1000
y dd 300
section .text
global _start
_start:
; Resta básica
mov eax, [x] ; eax = 1000
sub eax, [y] ; eax = 1000 - 300 = 700
; Efecto de la resta en los flags
mov eax, 10
sub eax, 20 ; eax = -10 (es decir, 0xFFFFFFF6)
; CF = 1 (se genera préstamo: 10 < 20)
; SF = 1 (el resultado es negativo)
; ZF = 0 (el resultado no es cero)
; OF = 0 (desbordamiento sin signo)
; Restar un registro de sí mismo: se usa frecuentemente para poner a cero
mov eax, 12345
sub eax, eax ; eax = 0
; ZF = 1, CF = 0
; Esta es una forma clásica de poner un registro a cero (más eficiente que mov eax, 0)
mov eax, 1
mov ebx, 0
int 0x80
sub eax, eaxes la técnica clásica para poner un registro a cero, ocupa solo 2 bytes, mientras quemov eax, 0ocupa 5 bytes. Se usa en escenarios que requieren optimización extrema del tamaño (como en shellcode).
INC / DEC - Instrucciones de incremento/decremento
Instrucciones de incrementar en 1 y decrementar en 1 más concisas que ADD/SUB:
Ejemplo
section .data
counter dd 0
section .text
global _start
_start:
mov dword [counter], 0 ; counter = 0
inc dword [counter] ; counter = 1 (operando en memoria)
inc dword [counter] ; counter = 2
mov ecx, 10
dec ecx ; ecx = 9
dec ecx ; ecx = 8
; INC/DEC no afectan el flag CF (esta es una diferencia importante respecto a ADD/SUB)
; Pero afectan a ZF, SF, OF, PF
; Usar INC en un bucle
mov ecx, 5
mov eax, 0
loop_inc:
inc eax ; eax se incrementa en 1 cada vez
loop loop_inc ; Repetir 5 veces, eax al final = 5
mov ebx, eax ; Valor de retorno = 5
mov eax, 1
int 0x80
MUL - Multiplicación sin signo
MULRealiza multiplicación sin signo. Las reglas de la multiplicación son algo especiales:
| Tamaño del operando | Multiplicador | Multiplicando (implícito) | Almacenamiento del resultado |
|---|---|---|---|
| 1 byte | Cualquier registro de 8 bits o memoria | AL | AX = AL × operando |
| 2 bytes | Cualquier registro de 16 bits o memoria | AX | DX:AX = AX × operando |
| 4 bytes | Cualquier registro de 32 bits o memoria | EAX | EDX:EAX = EAX × operando |
Ejemplo
; Ejemplo de multiplicación sin signo MUL
section .data
val1 dd 1000
val2 dd 2000
result_low dd 0
result_high dd 0
section .text
global _start
_start:
; Multiplicación de 32 bits: EDX:EAX = EAX × operando
mov eax, [val1] ; eax = 1000
mul dword [val2] ; edx:eax = 1000 × 2000 = 2,000,000
; Resultado = 2,000,000 = 0x001E8480
; EAX = 0x001E8480 (32 bits bajos)
; EDX = 0x00000000 (32 bits altos, porque el resultado no excede 32 bits)
; Demostración de multiplicación de números grandes (resultado que excede 32 bits)
mov eax, 0xFFFFFFFF ; eax = 4,294,967,295
mov ebx, 2 ; ebx = 2
mul ebx ; edx:eax = 0xFFFFFFFF × 2
; EAX = 0xFFFFFFFE
; EDX = 0x00000001 (32 bits altos)
; CF = 1 (el resultado excede 32 bits)
; Guardar el resultado
mov [result_low], eax
mov [result_high], edx
mov eax, 1
mov ebx, 0
int 0x80
IMUL - Multiplicación con signo
IMULSe usa para la multiplicación de números con signo. Hay tres formas:
Ejemplo
; Ejemplo de multiplicación con signo IMUL
section .data
a dd -100
b dd 3
section .text
global _start
_start:
; Forma de un operando: igual que MUL
mov eax, [a] ; eax = -100
imul dword [b] ; edx:eax = -100 × 3 = -300
; EAX = -300(0xFFFFFED4)
; EDX = 0xFFFFFFFF (extensión de signo)
; Forma de dos operandos: reg = reg × operando
mov ebx, [a] ; ebx = -100
imul ebx, [b] ; ebx = -100 × 3 = -300
; El resultado debe caber en 32 bits
; Forma de tres operandos: reg = operando1 × inmediato
imul ecx, [a], 5 ; ecx = -100 × 5 = -500
mov eax, 1
mov ebx, 0
int 0x80
MULyIMULLa principal diferencia es el manejo del signo:MULSe interpreta como número sin signo,IMULSe interpreta como número con signo. Por ejemplo, 0xFFFFFFFF en MUL es 4294967295, y en IMUL es -1.
DIV - División sin signo
DIVRealiza división sin signo. Las reglas son simétricas a las de MUL:
| Tamaño del divisor | Dividendo (implícito) | cociente | Resto |
|---|---|---|---|
| 1 byte | AX | AL | AH |
| 2 bytes | DX:AX | AX | DX |
| 4 bytes | EDX:EAX | EAX | EDX |
Ejemplo
; Ejemplo de división sin signo DIV
section .data
dividend dd 1000
divisor dd 7
quotient dd 0
remainder dd 0
section .text
global _start
_start:
; División de 32 bits: edx:eax / divisor
; El dividendo debe expandirse primero a EDX:EAX
mov eax, [dividend] ; eax = 1000
mov edx, 0 ; edx = 0 (parte alta de 32 bits puesta a cero)
div dword [divisor] ; edx:eax / 7
; EAX = 142 (cociente)
; EDX = 6 (resto: 1000 = 142×7 + 6)
mov [quotient], eax ; quotient = 142
mov [remainder], edx ; remainder = 6
; Ejemplo de división de bytes: 55 / 4
mov ax, 55 ; Dividendo
mov bl, 4 ; Divisor
div bl ; al = 13 (cociente), ah = 3 (resto)
mov eax, 1
mov ebx, 0
int 0x80
Antes de hacer la división, asegúrate de usar
mov edx, 0oxor edx, edxponer EDX a cero. Si EDX tiene datos antiguos, el valor del dividendo no será correcto. Este es el error de división más común entre los principiantes.
IDIV - División con signo
IDIVSe usa para división con signo. Antes de hacer la división, es necesario usarCDQla instrucción para extender el signo de EAX a EDX:EAX:
Ejemplo
; Calcular -100 / 3
mov eax, -100 ; eax = -100
cdq ; Extensión de signo: edx:eax = -100
; cdq copia el bit de signo de eax a todos los bits de edx
mov ebx, 3 ; Divisor
idiv ebx ; eax = -33 (cociente), edx = -1 (resto)
; Verificación: -100 = -33 × 3 + (-1)
ADC / SBB - Operaciones con acarreo/préstamo
Se usa paraoperaciones con números grandes(datos que superan 32 bits):
Ejemplo
; Suma de 64 bits: sumar dos números de 64 bits
section .data
; Número de 64 bits a = 0x00000001 FFFFFFFF
a_low dd 0xFFFFFFFF
a_high dd 0x00000001
; Número de 64 bits b = 0x00000000 00000005
b_low dd 0x00000005
b_high dd 0x00000000
; Resultado
result_low dd 0
result_high dd 0
section .text
global _start
_start:
; Sumar los 32 bits bajos
mov eax, [a_low]
add eax, [b_low] ; eax = 0xFFFFFFFF + 5 = 0x00000004
; CF = 1 (¡se genera acarreo!)
mov [result_low], eax ; result_low = 0x00000004
; Sumar los 32 bits altos más el acarreo
mov eax, [a_high]
adc eax, [b_high] ; adc = add + CF
; eax = 1 + 0 + 1 (acarreo) = 2
mov [result_high], eax ; result_high = 2
; Resultado final de 64 bits: 0x00000002 00000004
; Verificación: 0x1FFFFFFF + 5 = 0x200000004
; SBB: resta análoga (con préstamo)
mov eax, [a_low]
sub eax, [b_low] ; Resta de los 32 bits bajos
mov [result_low], eax
mov eax, [a_high]
sbb eax, [b_high] ; sbb = sub - CF
mov eax, 1
mov ebx, 0
int 0x80
Tabla de referencia rápida de instrucciones aritméticas
| Instrucción | Formato | Función | Banderas afectadas |
|---|---|---|---|
| ADD | add dest, src | dest = dest + src | CF, ZF, SF, OF, PF |
| SUB | sub dest, src | dest = dest - src | CF, ZF, SF, OF, PF |
| INC | inc dest | dest = dest + 1 | ZF, SF, OF, PF (no afecta a CF) |
| DEC | dec dest | dest = dest - 1 | ZF, SF, OF, PF (no afecta a CF) |
| MUL | mul src | Multiplicación sin signo | CF, OF |
| IMUL | imul src | Multiplicación con signo | CF, OF |
| DIV | div src | División sin signo | Sin definir (indeterminado) |
| IDIV | idiv src | División con signo | Sin definir (indeterminado) |
| ADC | adc dest, src | dest = dest + src + CF | CF, ZF, SF, OF, PF |
| SBB | sbb dest, src | dest = dest - src - CF | CF, ZF, SF, OF, PF |
| NEG | neg dest | dest = -dest (negación) | CF, ZF, SF, OF, PF |