Fu

Fundamentos en x86-64 Assembly

1 ejercicio

Acerca de Fundamentos

Comentarios

El track x86-64 de Exercism usa NASM (The Netwide Assembler) como ensamblador. En NASM, los comentarios van precedidos de un punto y coma (;).

Los comentarios pueden colocarse en cualquier parte del programa, y todo lo que va después de un ; lo ignora el ensamblador.

Constantes

Una constante en tiempo de ensamblado se puede definir en NASM con equ. Por ejemplo, esto define una constante llamada UNIVERSE con el valor 42:

UNIVERSE equ 42

Las constantes se evalúan una sola vez, cuando se definen, y no se pueden redefinir más adelante.

Registros de propósito general (GPR)

En ensamblador, en lugar de variables, normalmente usamos registros para almacenar valores. Un registro de CPU es una porción de memoria rápida dentro del procesador de un ordenador. La mayoría de los cálculos se realizan en los registros de la CPU.

Algunos registros se usan para diversos cálculos y se denominan registros de propósito general (GPR). Otros tienen propósitos especiales o dedicados.

En x86-64 hay 16 registros de propósito general (GPR) de 64 bits, a los que también se puede acceder como de 32, 16 u 8 bits. Los GPR se describen a continuación, donde n en rn va del 8 al 15: r8, r9, r10, r11, r12, r13, r14 y r15.

64-bit 32-bit 16-bit 8-bit
rax eax ax ah/al
rbx ebx bx bh/bl
rcx ecx cx ch/cl
rdx edx dx dh/dl
rsi esi si sil
rdi edi di dil
rbp ebp bp bpl
rsp esp sp spl
rn rnd rnw rnb

Cuando se usan menos de 64 bits, los bits a los que se accede suelen ser de la parte baja del registro. La excepción a esta regla son ah, bh, ch y dh, que acceden a los 8 bits superiores de la parte de 16 bits del registro.

Ilustración de cómo se accede a los bits del registro rax:

+--------+---------------------------------------+
| 64-bit |                  rax                  |
+--------+-------------------+-------------------+
| 32-bit |                   |        eax        |
+--------+-------------------+---------+---------+
| 16-bit |                             |    ax   |
+--------+-----------------------------+----+----+
| 8-bit  |                             | ah | al |
+--------+-----------------------------+----+----+

Algunos de esos registros deben conservarse entre llamadas a funciones: rbp, rsp, rbx, r12, r13, r14 y r15. No conservarlos puede provocar un error o un comportamiento indefinido.

Los demás no se conservan y se pueden usar libremente: rax, rcx, rdx, rdi, rsi, r8, r9, r10 y r11.

Note

Los registros suelen conservarse en la pila con la instrucción push. Después se restauran antes del final de la función con la instrucción pop.

Instrucciones

Las instrucciones son fragmentos de cálculo que puede realizar una CPU. Normalmente tienen la siguiente forma:

name destination, source

Así, el nombre de la instrucción va primero, después al menos un espacio en blanco, seguido del operando de destino, una coma (,) y, por último, un operando de origen. La instrucción normalmente no modifica el operando de origen, solo el operando de destino.

Ambos operandos deben tener el mismo tamaño, salvo que se indique lo contrario. Por ejemplo, si el operando de origen tiene 16 bits, el operando de destino también debe tener 16 bits.

Por ejemplo, para almacenar un valor en un registro podemos usar la instrucción mov:

mov rax, rdx  ; rax = rdx
              ; mov is the name, rax is the destination operand and rdx is the source operand

El fragmento anterior copia el contenido de los 64 bits de rdx, el operando de origen, a los 64 bits de rax, que es el operando de destino de la instrucción.

En la mayoría de las instrucciones se pueden usar números enteros constantes como operandos de origen (pero no como operandos de destino):

mov rcx, 42 ; rcx = 42
            ; rcx is the destination operand and 42 is the source operand

Ten en cuenta que escribir en un registro de 32 bits también borra los bits superiores, así que mov eax, 42 es lo mismo que mov rax, 42. Esto no ocurre con los registros de 16 y 8 bits.

Para las operaciones aritméticas de suma, resta y multiplicación podemos usar las instrucciones add, sub e imul:

add rax, rsi ; rax = rax + rsi
imul rax, rdi ; rax = rax * rdi
sub rax, r8 ; rax = rax - r8

Funciones

Las instrucciones se organizan en funciones. Todas las funciones se colocan en la sección .text del archivo fuente.

La declaración de una función consta de:

  1. Una etiqueta con el nombre de la función, seguida de un :.
  2. Las instrucciones que definen la función.
  3. La instrucción de retorno, ret.

Hay convenciones específicas sobre cómo se pasan los argumentos a una función y cómo se devuelven desde ella. Las dos convenciones de llamada principales son System V AMD64 ABI y Microsoft x64.

Este track usa la convención de llamada System V AMD64 ABI, y los seis primeros argumentos enteros se pasan a una función en registros. Se pasan en el siguiente orden: rdi, rsi, rdx, rcx, r8 y r9.

Un valor entero se devuelve desde la función en el registro rax.

Para llamar a una función usamos la instrucción call. Se puede llamar a una función desde cualquier parte del mismo archivo fuente. Para que una función sea visible desde otros archivos fuente hay que usar la directiva global.

Por ejemplo, esto declara una función sum:

section .text ; functions are placed here

global sum ; sum is visible to other source files

sum:
    ; first argument is passed in rdi
    ; second argument is passed in rsi
    ; return value is placed in rax

    mov rax, rdi ; rax is now equal to rdi
    add rax, rsi ; rax = rax + rsi

    ret ; function return

Y esto llama a nuestra función sum con los argumentos 3 y 5:

mov rdi, 3  ; First argument in rdi
mov rsi, 5  ; Second argument in rsi
call sum

; The rax register now contains the value 8 (3 + 5), after sum returns
Editar en GitHub El enlace se abre en una nueva ventana o pestaña

Aprende Fundamentos