[ASM] Curso de Ensamblador / Assembly (con juegos)

Me gustaría crear un curso del ensamblador x86_64, es decir de 64 bits. Pero en lugar de ver texto en tu terminal, editar pixeles, viendo cómo puedes colorear pixeles de una ventana.
El curso es similar al del canal “Profesor Retroman”, pero el hizo uno del ensamblador de 8 bits para amstrad cpc (similar al comodoro 64), mientras que yo lo intentaré hacer en 64 bits (para linux).

Requisitos:
Tener make instalado en tu sistema.
Un ensamblador (puede ser nasm o yasm, se consiguen en pacman, apt-get o yay).
Un linker (puede ser ld del linker de gcc).
La biblioteca de gráficos, tinyPTC, puedes compilarla descargando:

Descargas y descomprimes la .zip.
Hacerle make a secas, esto generara una biblioteca estática .a.
Crear la carpeta dónde estaremos trabajando, y mueves la .a ahí.

Para compilar y enlazar:

yasm -f elf64 main.asm
Puede ser con nasm, donde yasm (o nasm) con “-f elf64” para el formato del binario y el punto .asm, dónde estará el código ensamblador.

ld main.o libtinyptc.a -I. -lX11 -lc -lm --dynamic-linker /usr/lib64/ld-linux-x86-64.so.2 -o a
Para enlazar, el linker ld, llamar decir dónde esta libtinyptc.a (puede estar en un directorio) y luego algunas dependencias:
-lc (estandar de c)
-lm (funciones matematicas)
-lX11 (para los graficos)
–dynamic-linker /usr/lib64/ld-linux-x86-64.so.2 (si no nuestro ejecutable no sabrá cómo se cargan las bibliotecas dinámicas .so)

Pues, ahora que tenemos a tinyPTC y el ensamblador, pintemos un pixel.

Empecemos marcando la rutina (función) principal y las funciones externas:

global _start
extern ptc_open
extern ptc_update
extern ptc_process_events
extern ptc_close

Luego la sección .text y punto .bss:

section .text
_start:
mov rdi, title
mov rsi, 640
mov rdx, 360
call ptc_open    ;; crea una ventana X11

mainloop:                                  ;; inicio del bucle principal, todo con etiquetas
                                      ;; No existen funciones, solo etiquetas
lea rdx, [rel screen + 640 * 1200 + 1200]                              ;; Calcula la direccion de de memoria de la memoria de video, calculando una cordenada
                                          ;; Ya que cada pixel son 4 bytes, 1200 / 4 = 300 por ende, el pixel se dibujará en las coordenadas (300,300)
mov [rdx], DWORD 0x00ff0000       ;; Carga en el primer pixel el color rojo
lea rdi, [rel screen]         ;; Calcula la direccion de la memoria de video
call ptc_update             ;; actualiza la ventana
call ptc_process_events    ;; Revisa si se cierra la ventana
test rax, 0x00                    ;; Si ptc_process_events devuelve cero
je mainloop                       ;; Si el anterior es igual, salta, si no continua abajo

call ptc_close     ;; termina con la ventana X11
mov rax, 60        ;; termina con el proceso de nuestro programa
xor rdi, rdi
syscall

section .bss
screen: resd 640*360    ;; Nuestra "memoria de video"

section .data
title: db "My Window!",0x00      ;; Nombre de la ventana, termina en 0x00 (NULL)

Pues en la seccion .bss se creo la “memoria de video” con el ancho y alto de la ventana (cada pixel son 4 bytes).
con lea rdx, [rel screen] le digo que calcule la dirección de memoria de la etiqueta screen (la de la “memoria de video”) a partir del registro de ejecución del programa y lo guarde en el registro rdx.
Luego, con mov [rdx], DWORD 0x00ff0000 le digo que cargue el valor 0x00ff0000 (hexadecimal) a dónde apunte rdx, el cual equivale al primer pixel, si le sumo 4 (con, por ejemplo add rdx, 4) llego al siguiente pixel.
El valor 0x00ff0000 significa
0x → que es un valor hexadecimal
00 → alpha
ff → red
00 → green
00 → blue
Del “rgb”
El primero es la transparencia, 00 es desactivado, y los otros 3, de dos digitos en dos, son rgb, representando numeros del 00 al ff para cada color:
00 → sin ese color
0x00ff0000 → rojo
0x0000ff00 → verde
0x000000ff → azul

Para expandir y resolver dudas, puede visitar:
https://cs.lmu.edu/~ray/notes/nasmtutorial/

O, preguntarme por aquí, que estaría encantado de responderlas, si sabes más de ensamblador que yo, puedes aportar.

No puedes usar un call dentro de una rutina que llamaste con un call, hasta que no explique cómo funciona la pila, ya que tras el primer call la pila se te va a desalinear y te tirara una violación de segmento. Solo puedes llamar a una función a la vez, sin funciones dentro de funciones hasta no ver la pila.

Ejercicios:
1_ Colorea todo el fondo
2_ Haz mover el pixel

6 Me gusta

Me parece interesante la idea. La duda principal que tengo es la aplicación,
utilidad y empleo del mismo. Por ejemplo, el assembler que conozco es el empleado para microcontroladores PICs, solo se emplea en sistemas embebidos donde el tiempo de procesamiento es critico, ya que el tiempo de ejecución de cada instrucción es importante. Pero si el microcontrolador es muy potente o la ejecución no es en tiempo real, puedo pasar a C++.

Pues, aprender ensamblador puede dar unas bases sumamente sólidas para programar en c o c++.
Pero, no solo bases, si no que poder entender y solucionar problemas que no entenderías si no sabes ensamblador.
Cómo los problemas de linkado con bibliotecas dinámicas y estáticas, las referencias indefinidas…

Además, ensamblador es compatible con c y c++, ya que las rutinas (funciones) pueden ser llamadas desde c y c++, y al revés, las funciones ser llamadas desde ensamblador (cómo en este primer capítulo).

Aunque, hay que decir, en ensamblador, cuando armas una rutina con su etiqueta (nombre de la “función”), en el código objeto (la .o) el nombre sigue siendo el mismo, exactamente el mismo. En c, lo mismo, los nombres de las funciones se conserva tal cual. Pero en c++, para poder conservar la sobrecarga de funciones, se implemento un “mangling” (si lo puse bien…) que le agrega caracteres al nombre según el valor de retorno y parámetros, haciendo que una llamada externa sea difícil de hacer (a lo sumo que sepas cómo genero la etiqueta c++), pero está la forma de desactivar este “mangling” (aunque también la sobrecarga) que es poniéndole adelante de la función un extern "C".

Las etiquetas en ensamblador o el archivo objeto (que puede ser visto con objdump) tienen que ser únicas e irrepetibles).

Por lo cuál, saber ensamblador puede hacerte mejor programador de c++, al poder bajarte al bajo nivel para solucionar o mejorar ciertas áreas o sectores…