La última fué la semana de refrescar ensamblador revisando https://asmtutor.com/ luego de llegar allí mediante los posts Learn Assembly y Assembly Nights.
Sobre el sitio https://asmtutor.com/:
Learn Assembly Language
This project was put together to teach myself NASM x86 assembly language on linux.
Leyendo por arriba, dos detalles:
-
El sitio es sobre (la arquitectura) x86 y allí se utiliza la
int 80h
para llamar a las funciones del kernel linux.Si se está más interesado en la arquitectura amd64 se encuentra que existen varias formas para llamar al código del kernel, siendo la preferida el uso de la instrucción
syscall
(man2 syscall).Teniendo esto en cuentra, el código de ejemplo helloworld64.asm (adaptado de helloworld.asm) nos quedaría como sigue:
_start: mov rax, 1 ; invoke SYS_WRITE mov rdi, 1 ; write to the STDOUT file (file descriptor) mov rsi, msg ; move the memory address of our message string into ecx mov rdx, 13 ; number of bytes to write - one for each letter plus 0Ah (line feed character) syscall
Los números de las llamadas al sistema cambian respecto de x86. Los números de cada syscall para amd64 se pueden encontrar en el código del kernel y hay posts como Linux System Call Table for x86 64 en los que se muestra una tabla con las system calls y la utilización que se le da a los registros.
-
Si bien hay ejemplos en los que se utilizan loops y se introducen subrutinas no se indica como pasar construcciones de pseudo-código tales como expresiones o control de flujo a ensamblador. Esto solo indica que el autor da por sentado cierta familiaridad con ensamblador x86.
Si no se tiene familiaridad con ensamblador sugiero leer los capítulos de generación de código de libros de diseño de compiladores tales como Engineering a Compiler o Compilers: Principles, Techniques, and Tools ya que la mayoría de los libros de ensamblador que he visto no tratan en profundidad como transformar sentencias condicionales o de bucle escritas en pseudo-código a código ensamblador.
Como complemento a lo anterior está el sitio Compiler Explorer, que permite introducir código en C o C++ y convertirlo en código ensamblador utilizando distintos compiladores.
-
Se utiliza la sintaxis de intel (nasm). Para un buen comentario de porqué conviene utilizar la sintaxis de intel sobre la de at&t se puede ver el artículo Why no one should use the AT&T syntax ever, for any reason, under any circumstances.
Redoblando la apuesta - ensamblador de risc-v
Ya que la mayoría de las lecciones implican utilizar llamadas al kernel, ¿porqué no intentar hacer un programa de risc-v y dejar que qemu realice la emulación en modo usuario?.
Nuevamente, vuelve a cambiar la forma de llamar al kernel como se indica en la página del manual (2) syscall:
Arch/ABI Instruction System Ret Ret Error Notes
call # val val2
───────────────────────────────────────────────────────────────────
riscv ecall a7 a0 a1 -
Arch/ABI arg1 arg2 arg3 arg4 arg5 arg6 arg7 Notes
──────────────────────────────────────────────────────────────
riscv a0 a1 a2 a3 a4 a5 -
El número de instrucción a utilizar se debe obtener del archivo include/uapi/asm-generic/unistd.h como se indica en Adding a New System Call:
Some architectures (e.g. x86) have their own architecture-specific syscall tables, but several other architectures share a generic syscall table. Add your new system call to the generic list by adding an entry to the list in include/uapi/asm-generic/unistd.h
Para ver un programa de ejemplo se implementa el programa helloworldrv64.s pero ahora en risc-v:
# Hello World Program
# Compile with: riscv64-linux-gnu-as helloworldrv64.s -o helloworldrv64.o
# Link with: riscv64-linux-gnu-ld helloworldrv64.o -o helloworldrv64
# Run with: qemu-riscv64-static ./helloworldrv64
.data
msg: .asciz "Hello World!\n" # assign msg variable with your message string
.text
.global _start
_start:
li a7, 64 # invoke SYS_WRITE
li a0, 1 # write to the STDOUT file (file descriptor)
la a1, msg # move the memory address of our message string
li a2, 13 # number of bytes to write - one for each character
ecall
li a7, 93 # invoke SYS_EXIT
li a0, 0 # return 0 status on exit - 'No Errors'
ecall
end:
j end
Algunos links sobre ensamblador de risc-v:
- https://github.com/riscv-non-isa/riscv-asm-manual/blob/master/riscv-asm.md
- https://mcyoung.xyz/2021/11/29/assembly-1/
- https://smist08.wordpress.com/2019/09/07/risc-v-assembly-language-hello-world/
- https://shakti.org.in/docs/risc-v-asm-manual.pdf
Para una alternativa a utilizar modo de usuario realizando la emulación del hardware puede verse https://mth.st/blog/riscv-qemu/.
Conclusiones
- Muchos tutoriales en linea que muestran como utilizar ensamblador en linux
son para la arquitectura
x86
y utilizan laint 0x80
lo cual no es reciente. - A mi criterio, es mejor utilizar la sintaxis de intel que la de at&t.
- La mayoría de los libros de ensamblador que vi hacen incapié en la convención
de llamadas de C, lo cual es correcto, pero apenas muestran como convertir las
estructuras
if-then-else
,while-do-end
,do-while
,for
,case
a ensamblador. Se suele mencionar dando un ejemplo y ningún libro de los que pude ver (y vi VARIOS) muestran una definición formal. - Con qemu se pueden ejecutar programas simples que utilicen la especificación Unprivileged (Volume 1, Unprivileged Spec).
- La sintaxis del ensamblador de risc-v se parece (si no es) intel.
- Compiler Explorer es extremadamente útil para verificar como se puede convertir un pseudo-código (trozo de función en C) en ensamblador de risc-v.
¿Porqué ensamblador risc-v? Creo que es simple, limpio, tiene muy pocas instrucciones y seguro va a ser cada vez más relevante. Utilizar intel o arm no me estimula por la cantidad de instrucciones que tienen (intel) o lo denso del conjunto de instrucciones (arm).
Tampoco me convence el realizar como hobbie programas en Z80, 6502 u otra arquitectura que está volviendo de a ser de moda (¿retro?) porque no creo que sea un conocimiento que pueda capitalizar a futuro o quizás estar pensando en como programar una PPU de nintendo o los gráficos de amiga.
Apéndice - Instalación de programas
Para instalar los programas utilizados en este post, en debian, se puede utilizar:
$ doas apt-get install -Vy nasm binutils-riscv64-linux-gnu qemu-user-static