Saltar a contenido

Arithmetic Instructions

El segundo tipo de instrucciones básicas son las Arithmetic Instructions. Con estas instrucciones, podemos realizar varios cálculos matemáticos en datos almacenados en registros y direcciones de memoria. Estas instrucciones suelen ser procesadas por la ALU en la CPU, entre otras instrucciones. Dividiremos las instrucciones aritméticas en dos tipos: instrucciones que toman solo un operando (Unary) e instrucciones que toman dos operandos (Binary).


Unary Instructions

Las siguientes son las principales Unary Arithmetic Instructions (asumiremos que rax comienza como 1 para cada instrucción):

Instruction Description Example
inc Incrementar en 1 inc rax -> rax++ o rax += 1 -> rax = 2
dec Decrementar en 1 dec rax -> rax-- o rax -= 1 -> rax = 0

Practiquemos estas instrucciones volviendo a nuestro código fib.s. Hasta ahora, hemos inicializado rax y rbx con los valores iniciales 0 y 1 usando la instrucción mov. En lugar de mover el valor inmediato de 1 a bl, movamos 0 y luego usemos inc para convertirlo en 1:

global  _start
section .text
_start:
    mov al, 0
    mov bl, 0
    inc bl

Recuerda, usamos al en lugar de rax por eficiencia. Ahora, ensamblamos nuestro código y lo ejecutamos con gdb:

$ ./assembler.sh fib.s -g
...SNIP...
─────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
    0x401005 <_start+5>      mov    al, 0x0
───────────────────────────────────────────────────────────────────────────────────── registers ────
$rbx   : 0x0
...SNIP...
─────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
    0x40100a <_start+10>      inc    bl
───────────────────────────────────────────────────────────────────────────────────── registers ────
$rbx   : 0x1

Como podemos ver, rbx comenzó con el valor 0, y con inc rbx, se incrementó a 1. La instrucción dec es similar a inc, pero decrementa en 1 en lugar de incrementar.

Este conocimiento será muy útil más adelante.


Binary Instructions

A continuación, tenemos las Binary Arithmetic Instructions, y las principales son: (asumiremos que tanto rax como rbx comienzan como 1 para cada instrucción).

Instruction Description Example
add Suma ambos operandos add rax, rbx -> rax = 1 + 1 -> 2
sub Resta el origen del destino (i.e rax = rax - rbx) sub rax, rbx -> rax = 1 - 1 -> 0
imul Multiplica ambos operandos imul rax, rbx -> rax = 1 * 1 -> 1

Nota que en todas las instrucciones anteriores, el resultado siempre se almacena en el operando de destino, mientras que el operando de origen no se ve afectado.

Comencemos discutiendo la instrucción add. Sumar dos números es el paso central para calcular una Fibonacci Sequence, ya que el número Fibonacci actual (Fn) es la suma de los dos anteriores (Fn = Fn-1 + Fn-2).

Entonces, agreguemos add rax, rbx al final de nuestro código fib.s:

global  _start

section .text
_start:
   mov al, 0
   mov bl, 0
   inc bl
   add rax, rbx

Ahora, ensamblamos nuestro código y lo ejecutamos con gdb:

$ ./assembler.sh fib.s -g
gef  b _start
Breakpoint 1 at 0x401000
gef  r
...SNIP...
─────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x401004 <_start+4>       inc    bl
    0x401006 <_start+6>       add    rax, rbx
───────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x1
$rbx   : 0x1

Como podemos ver, después de que la instrucción se procesa, rax es igual a 0x1 + 0x0, que es 0x1. Siguiendo el mismo principio, si tuviéramos otros números de Fibonacci en rax y rbx, obtendríamos el nuevo Fibonacci usando add.

Tanto sub como imul son similares a add, como se muestra en los ejemplos de la tabla anterior. Prueba agregar sub e imul al código anterior, ensamblarlo y luego ejecutarlo en gdb para ver cómo funcionan.


Bitwise Instructions

Ahora, pasemos a las Bitwise Instructions, que son instrucciones que trabajan a nivel de bits (asumiremos que rax = 1 y rbx = 2 para cada instrucción):

Instruction Description Example
not NOT a nivel de bits (invierte todos los bits, 0->1 y 1->0) not rax -> NOT 00000001 -> 11111110
and AND a nivel de bits (si ambos bits son 1 -> 1, si son diferentes -> 0) and rax, rbx -> 00000001 AND 00000010 -> 00000000
or OR a nivel de bits (si cualquiera de los bits es 1 -> 1, si ambos son 0 -> 0) or rax, rbx -> 00000001 OR 00000010 -> 00000011
xor XOR a nivel de bits (si los bits son iguales -> 0, si son diferentes -> 1) xor rax, rbx -> 00000001 XOR 00000010 -> 00000011

Estas instrucciones pueden parecer confusas al principio, pero son sencillas una vez que las entendemos. Por ejemplo, not irá a cada bit e invertirá su valor. Intenta agregar not rax al final de nuestro código anterior, ensamblarlo y ejecutarlo en gdb para ver cómo funciona.

La instrucción más utilizada será xor. Esta tiene varios usos, pero un uso clave es poner cualquier valor en 0 al aplicar xor a un registro consigo mismo:

global  _start

section .text
_start:
    xor rax, rax
    xor rbx, rbx
    inc rbx
    add rax, rbx

Este código debería realizar exactamente las mismas operaciones, pero ahora de una manera más eficiente. Vamos a ensamblar nuestro código y ejecutarlo con gdb:

$ ./assembler.sh fib.s -g
gef  b _start
Breakpoint 1 at 0x401000
gef  r
...SNIP...
─────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
    0x401001 <_start+1>       xor    eax, eax
     0x401003 <_start+3>       xor    ebx, ebx
───────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0
$rbx   : 0x0
...SNIP...
─────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
    0x40100c                  add    BYTE PTR [rax], al
───────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x1
$rbx   : 0x1

Como podemos ver, al hacer xor con nuestros registros y ellos mismos, cada uno se establece en 0. El resto del código ejecuta las mismas operaciones que antes, por lo que terminamos con los mismos valores finales para ambos registros, rax y rbx.