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

; Ruta del archivo: add_demo.asm
; 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

; Ruta del archivo: sub_demo.asm
; 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

; Ejemplo de INC y DEC

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 operandoMultiplicadorMultiplicando (implícito)Almacenamiento del resultado
1 byteCualquier registro de 8 bits o memoriaALAX = AL × operando
2 bytesCualquier registro de 16 bits o memoriaAXDX:AX = AX × operando
4 bytesCualquier registro de 32 bits o memoriaEAXEDX:EAX = EAX × operando

Ejemplo

; Ruta del archivo: mul_demo.asm
; 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

; Ruta del archivo: imul_demo.asm
; 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 divisorDividendo (implícito)cocienteResto
1 byteAXALAH
2 bytesDX:AXAXDX
4 bytesEDX:EAXEAXEDX

Ejemplo

; Ruta del archivo: div_demo.asm
; 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 usarmov 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

; Ejemplo de división con signo IDIV

    ; 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

; Ruta del archivo: adc_sbb_demo.asm
; 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ónFormatoFunciónBanderas afectadas
ADDadd dest, srcdest = dest + srcCF, ZF, SF, OF, PF
SUBsub dest, srcdest = dest - srcCF, ZF, SF, OF, PF
INCinc destdest = dest + 1ZF, SF, OF, PF (no afecta a CF)
DECdec destdest = dest - 1ZF, SF, OF, PF (no afecta a CF)
MULmul srcMultiplicación sin signoCF, OF
IMULimul srcMultiplicación con signoCF, OF
DIVdiv srcDivisión sin signoSin definir (indeterminado)
IDIVidiv srcDivisión con signoSin definir (indeterminado)
ADCadc dest, srcdest = dest + src + CFCF, ZF, SF, OF, PF
SBBsbb dest, srcdest = dest - src - CFCF, ZF, SF, OF, PF
NEGneg destdest = -dest (negación)CF, ZF, SF, OF, PF
Otras extensiones