Hemos llegado al último capítulo de este libro y de nuestro viaje por los entresijos de la programación para Sega Mega Drive. Desde que comenzamos a hablar sobre qué es la Mega Drive, su historia, arquitectura, configurar nuestro entorno, hasta ver todas las herramientas disponibles para crear nuestros juegos.
Solo queda por hablar de un tema que es bastante importante para todo aquel que se enfrente a la programación; y es a la hora de depurar y poder ver la trazabilidad de nuestro código. Cosa importante, para poder detectar posibles errores de ejecución o comúnmente llamados "bugs".
Existen muchas herramientas útiles a la hora de encontrar dichos errores. Como puede ser el poder visualizar la memoria de nuestro dispositivo, y poder ver el valor de nuestras variables, instrucción que estamos ejecutando, etc.
Este tipo de herramientas como depuradores, herramientas de trazabilidad (log) e incluso ver una imagen de la memoria, están disponibles en muchos de los emuladores que hemos mencionado en este libro; como puede ser Gens KMod, Blastem o Kega Fusion. Además, el propio SGDK nos provee algunas de estas herramientas.
En este capítulo, vamos a ver qué herramientas podemos usar para depurar nuestros juegos y poder detectar los fallos o errores.
Como hemos estado hablando en este capítulo, es importante conocer y utilizar herramientas para poder detectar los errores de nuestro juego y ver que está haciendo correctamente lo que debe; en muchas ocasiones este tipo de errores no se detectan a simple vista y necesitamos herramientas para ver que está ocurriendo.
Por ello, necesitamos poder tener una trazabilidad y la posibilidad de utilizar un depurador para nuestro juego. SGDK, nos provee de herramientas para poder realizar esta trazabilidad.
El uso de ficheros de Traza (o logs) es común a la hora de trabajar con sistemas. Algunos emuladores son capaces de escribir una traza con mensajes que el propio programador utiliza; en muchas ocasiones se podrían poner por pantalla. Pero, es mucho mejor poder tener un fichero de trazas con estos mensajes.
Algunos emuladores como Gens KMod o Blastem, tienen un apartado para ver estas trazas y mostrarlo por dicha consola o fichero. Veamos cómo se podrían ver los mensajes en el emulador Gens con la modificación KMod.
Para acceder a este apartado, puedes encontrarlo en el menú CPU->Debug->Messages; y aquí podremos ver los mensajes que enviemos con una función especial que contiene SGDK.
La función para enviar información a esta traza es kprintf
; la cual escribe un mensaje en dicha traza en vez de escribirlo por pantalla. Esta función es análoga al uso de printf
en C estándar; es decir que recibe 1 o varios parámetros:
- fmt: cadena de caracteres que puede contener una serie de formateadores que empiezan por
%
; que nos permitirán escribir variables de distintos tipos y formatos. Para saber cómo utilizar estos formatos específicos, puedes revisar la documentación estándar de C (dejamos información en las referencias). Es importante saber, que esta función tiene un buffer interno de 255 bytes; por lo que tenemos que tener esto en cuenta a la hora de poner un mensaje muy largo. - ...: el resto de parámetros, será cada una de las variables que sustituirá a cada uno de los formateadores incluidos en el anterior parámetro.
La función kprintf
, devuelve el número de bytes escritos (hasta 255) en la traza.
Hemos hablado de cómo utilizar la consola que trae algunos emuladores para escribir una serie de trazas. Pero en muchas ocasiones nos ocurre que necesitamos utilizar herramientas como depuradores para poder ver qué está pasando en un momento dado en nuestro programa.
Vamos a mostrar en primer lugar, como se podría hacer esta depuración para nuestros juegos; primero de forma más teórica, y después entraremos en más detalle dependiendo de nuestro emulador o herramientas a utilizar.
Si has trabajado con la programación anteriormente, habrás tenido que depurar muchos programas; normalmente en la propia máquina y compilar el código para la misma arquitectura que estás trabajando (normalmente x86_64 o ARM); sin embargo, en este caso no vamos a trabajar con estas arquitecturas; sino con la del Motorola 68000. Por ello necesitamos una forma de depurar este código utilizando un emulador por ejemplo. (Existen formas de depurar con hardware real; pero son mucho más costosas). Veamos un esquema para entender qué es lo que queremos hacer.
Como podemos ver en el esquema, se disponen de distintos elementos; algunos en la máquina local que sería el computador en el que estamos trabajando, y otros en una máquina remota que sería en este caso la propia Mega Drive o un emulador.
Si nos centramos en la máquina local, podemos ver que tenemos el editor, que puede ser cualquier editor de código o entorno de desarrollo integrado, con capacidad de conectarse a un depurador; en la imagen puedes ver que se trata de Visual Studio Code.
Por otro lado, necesitaremos utilizar un programa que nos permita conectarnos a una máquina remota (o local) para poder obtener la información necesaria para depurar; como la memoria, instrucción actual, ejecución paso a paso,etc. Para ello, utilizaremos el depurador GDB 1; el cual nos va a permitir conectarnos a un emulador (Normalmente utilizando un puerto de red), para poder depurar nuestro juego. SGDK, incluye GDB para poder depurar nuestros juegos.
Por último, la máquina remota que puede ser un emulador, a la que GDB se conectará y proveerá toda la información que necesita el depurador. El emulador, tiene que ser capaz de recibir y enviar esta información al depurador para poder tener un correcto funcionamiento; para una mejor comprensión de cómo se podría realizar esto o quó herramientas disponemos, vamos a ver en detalle algunos emuladores ya mencionados.
Como hemos podido ver en otros capítulos, Gens es un emulador de código abierto que nos provee una serie de herramientas adicionales para ayudarnos al desarrollo. Por ejemplo, podemos ver el estado de los registros del procesador y como se encuentra:
Esto puede ser útil para ver el estado del procesador; pero no es lo que estamos buscando; ya que necesitaremos la modificación KMod, para poder definir las opciones de depuración remota. En el menú options->Debug, podemos establecer los puertos y opciones relacionadas con la depuración remota.
Estas opciones, abrirán un puerto (por defecto el 6868); para poder depurar el procesador M68k. Este puerto será utilizado por GDB para conectarse.
Puedes encontrar más herramientas para depuración o poder visualizar la memoria del VDP,etc. Para más información, consulta la ayuda de Gens KMod.
Otro emulador que hemos mencionado por este libro, es el uso de Blastem. También dispone de opciones de depuración, incluyendo una depuración remota. Sin embargo, no funciona correctamente o está aún en desarrollo. Por lo que el uso de depuración remota, solo está soportado por las últimas versiones de Blastem (Recomendamos utilizar la versión Nightly; puedes encontrar más información en las referencias).
En este caso, podremos utilizar un depurador interno, arrancando Blastem con la opción -d; el cual nos permitirá ejecutar paso a paso. Sin embargo, esta opción nos mostrará las instrucciones en ensamblador y tendremos que ser nosotros quienes las traduzcamos para ver qué está ocurriendo.
Si queremos utilizar una depuración remota, necesitaremos ejecutar el siguiente comando dentro de GDB:
target remote:1234| blastem.exe rom.bin -D
Esto dirá a GDB, que se debe de conectar al puerto 1234 (se puede cambiar) y que inicie Blastem emulando nuestra ROM, además de activar la emulación remota. Con esto, podremos depurar nuestro juego a través de GDB.
Sin embargo, aunque hemos podido iniciar la emulación, todavía nos queda algo; el poder conectar GDB con nuestro entorno de desarrollo para poder ver las instrucciones de nuestro código C y poder visualizar también paso a paso dichas instrucciones.
Para ello, vamos a ver un ejemplo usando Visual Studio Code; con la extensión Genesis Code, que hemos podido ver en este libro. Esta extensión, cuando crea un proyecto, genera una configuración en el directorio .vscode; esta configuración, nos ayuda a poder gestionar los ficheros .h, y además a generar la configuración para iniciar la depuración; si echamos un vistazo al fichero launch.json, podemos ver.
...
"name": "Debug with gdb remote",
"request": "launch",
"type": "cppdbg",
"program": "%CD%\\out\\rom.out",
"miDebuggerServerAddress": "localhost:6868",
"sourceFileMap": {
"d:\\apps\\sgdk\\src\\": "${env:GDK}\\src\\",
},
"args": [],
"stopAtEntry": true,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"launchCompleteCommand": "exec-continue",
"miDebuggerPath": "${env:GDK}\\gdb.exe",
"setupCommands": [
{
"text":
"set directories '${workspaceFolder}
;$cwd;$cdir'"
}
],
...
Podemos ver algunas propiedades en este fichero:
- name: Nombre de la configuración que podremos ver en VSCODE.
- program: Indica el nombre del binario que usará gdb para iniciar la depuración; se trata del fichero rom.out que se genera al compilar en modo depuración.
- miDebuggerServerAddress: Indica la dirección y puerto donde se conectará gdb para hacer la depuración remota. Debe coincidir con el puerto del emulador.
- sourceFileMap: Esta propiedad es importante ya que SGDK tiene establecidas unas rutas con las que se compiló y se configuró; por lo tanto se debe de mapear a nuestra carpeta de fuentes de SGDK.
- cwd: Indica el directorio de trabajo.
- MIMode: Indica el modo de depurador en este caso se trata de gdb.
- miDebuggerPath: Ruta donde se encuentra GDB; en este caso se usa el integrado en SGDK. Puede definirse otro.
- setupCommands: Indica los comandos y configuraciones que se pasará a GDB. Entre ellas se establece el directorio de los fuentes.
Tras visualizar esta configuración, podemos generar la rom con opciones de depuración, usando el comando de Genesis Code: Compile For Debugging; para generar la rom añadiendo la tabla de símbolos y todo lo necesario para depurar.
Por último, ya podemos ejecutar la depuración en el propio editor de Visual Studio Code; Si establecemos un punto de ruptura y todo va correctamente, podremos ver algo como la siguiente pantalla.
NOTA: Para Sistemas Linux usando GENDEV, no está disponible la depuración debido a que GENDEV no incluye las tablas de símbolos de SGDK.
NOTA2: Un punto de ruptura, es una línea o punto en el código fuente donde el depurador detendrá la ejecución del programa y podremos ver tanto las instrucciones como las variables y sus valores.
Tras ver las herramientas y cómo poder depurar, vamos a mostrar el ejemplo de esta sección y de cómo poder utilizar las trazas correctamente; para ello, vamos a crear un ejemplo muy sencillo que nos mandará una traza cuando pulsemos un botón (A, B o C). Recuerda que este ejemplo, lo puedes encontrar en el repositorio de ejemplos que acompaña a este libro; correspondiente a la carpeta ej17.klog.
En primer lugar definimos la función que gestionará los controles. Para este ejemplo utilizaremos los controles de forma asíncrona. Por lo que crearemos la función handleAsyncInput
para gestionar los controles. En esta función, vamos a revisar cuando se pulsa el botón A, B o C; guardando cada botón en una variable. Veamos un fragmento:
char button='\0';
...
if(state & changed & BUTTON_A){
button='A';
}else{
...
Vemos que para cada botón, la variable button
, almacena un carácter con el botón correspondiente; y más adelante veremos que usamos la función kprintf
, para mostrar la traza correspondiente; veamos un fragmento.
#ifdef DEBUG
kprintf("Button Pushed: %c \n",button);
#endif
Como habrás podido ver, la instrucción se encuentra entre dos instrucciones de preprocesador; estas instrucciones, harán que este código solo este disponible, si la constante DEBUG
está definida. Esto es una buena práctica; ya que si nuestro juego va a ser publicado, no necesitamos dichas trazas; solo serán útiles mientras se está desarrollando.
Si ya compilamos y ejecutamos este ejemplo, al pulsar una tecla, podemos ver en la consola de Blastem (o en el apartado correspondiente de Gens KMod), nuestro mensaje.
Ejemplo 17: Consola Depuración Blastem
Tras ver nuestro último ejemplo, ya damos por finalizado nuestro viaje por la consola de 16 bits; y esperemos que al lector le haya gustado. Además de que esperamos que esto te anime a crear tus propios juegos y publicar más software casero o "HomeBrew". Por supuesto no me olvido de darte las gracias personalmente por tu lectura.
- Printf (Documentación C): https://cplusplus.com/reference/cstdio/printf/.
- Gens KMod: https://segaretro.org/Gens_KMod.
- Blastem: https://www.retrodev.com/blastem/nightlies/.
- GNU GDB: https://www.sourceware.org/gdb/.
- Artículo sobre Depuración: https://zerasul.me/blog/debug.
- Visual Studio Code C Debug: https://code.visualstudio.com/docs/cpp/cpp-debug.
Footnotes
-
GDB: Gnu Project Debugger, nos va a permitir ver que ocurre dentro de un programa; además de permitir detener la ejecución y poder visualizar las variables o cambiar los valores de estas. ↩