gint : un noyau pour développer des add-ins
Posté le 20/02/2015 17:30
Ce topic fait partie de la série de topics du fxSDK.
En plus des options de programmation intégrée comme le Basic Casio ou Python, la plupart des calculatrices Casio supportent des
add-ins, des programmes natifs très polyvalents avec d'excellentes performances. Les add-ins sont généralement programmés en C/C++ avec l'aide d'un ensemble d'outils appelé SDK.
Plusieurs SDK ont été utilisés par la communauté avec le temps. D'abord le
fx-9860G SDK de Casio avec fxlib pour Graph monochromes (plus maintenu depuis longtemps). Puis le
PrizmSDK avec libfxcg pour Prizm et Graph 90+E (encore un peu actif sur Cemetech). Et plus récemment celui que je maintiens, le
fxSDK, dont gint est le composant principal.
gint est un unikernel, ce qui veut dire qu'il embarque essentiellement un OS indépendant dans les add-ins au lieu d'utiliser les fonctions de l'OS de Casio. Ça lui permet beaucoup de finesse sur le contrôle du matériel, notamment la mémoire, le clavier, l'écran et les horloges ; mais aussi de meilleures performances sur le dessin, les drivers et la gestion des interruptions, plus des choses entièrement nouvelles comme le moteur de gris sur Graph monochromes.
Les sources de gint sont sur la forge de Planète Casio :
dépôt Gitea Lephenixnoir/gint
Aperçu des fonctionnalités
Les fonctionnalités phares de gint (avec le fxSDK) incluent :
- Toutes vos images et polices converties automatiquement depuis le PNG, sans code à copier (via fxconv)
- Un contrôle détaillé du clavier, avec un GetKey() personnalisable et un système d'événements à la SDL
- Une bibliothèque standard C plus fournie que celle de Casio (voir fxlibc), et la majorité de la bibliothèque C++
- Plein de raccourcis pratiques, comme pour afficher la valeur d'une variable : dprint(1,1,"x=%d",x)
- Des fonctions de dessin, d'images et de texte optimisées à la main et super rapides, surtout sur Graph 90+E
- Des timers très précis (60 ns / 30 µs selon les cas, au lieu des 25 ms de l'OS), indispensables pour les jeux
- Captures d'écran et capture vidéo des add-ins par USB, en temps réel (via fxlink)
Avec quelques mentions spéciales sur les Graph monochromes :
Un moteur de gris pour faire des jeux en 4 couleurs !
La compatibilité SH3, SH4 et Graph 35+E II, avec un seul fichier g1a
Une API Unix/POSIX et standard C pour accéder au système de fichiers (Graph 35+E II seulement)
Et quelques mentions spéciales sur les Graph 90+E :
Une nouvelle police de texte, plus lisible et économe en espace
Le dessin en plein écran, sans les bordures blanches et la barre de statut !
Un driver écran capable de triple-buffering
Une API Unix/POSIX et standard C pour accéder au système de fichiers
Galerie d'add-ins et de photos
Voici quelques photos et add-ins réalisés avec gint au cours des années !
Arena (2016) — Plague (2021)
Rogue Life (2021)
Momento (2021)
Communication avec le PC (cliquez pour agrandir)
Utiliser gint pour développer des add-ins
Les instructions pour installer et utiliser gint sont données dans les divers tutoriels recensés dans le
topic du fxSDK. Il y a différentes méthodes de la plus automatique (GiteaPC) à la plus manuelle (compilation/installation de chaque dépôt). Le fxSDK est compatible avec Linux, Mac OS, et marche aussi sous Windows avec l'aide de WSL, donc normalement tout le monde est couvert
Notez en particulier qu'il y a des
tutoriels de développement qui couvrent les bases ; tout le reste est expliqué dans les en-têtes (fichiers
.h) de la bibliothèque que vous pouvez
consulter en ligne, ou dans les ajouts aux changelogs ci-dessous.
Changelog et informations techniques
Pour tester les fonctionnalités et la compatibilité de gint, j'utilise un add-in de test appelé gintctl (
dépôt Gitea Lephenixnoir/gintctl). Il contient aussi une poignée d'utilitaires d'ordre général.
Ci-dessous se trouve la liste des posts indiquant les nouvelles versions de gint, et des liens vers des instructions/tutoriels supplémentaires qui accompagnent ces versions.
Anecdotes et bugs pétés
Ô amateurs de bas niveau, j'espère que vous ne tomberez pas dans les mêmes pièges que moi.
TODO list pour les prochaines versions (2023-04-03)
gint 2.11
- Changements de contextes CPU. À reprendre du prototype de threading de Yatis pour permettre l'implémentation d'un véritable ordonnanceur. Demandé par si pour faire du threading Java.
- Applications USB. Ajouter le support de descripteurs de fichiers USB. Potentiellement pousser jusqu'à avoir GDB pour debugger.
- Support de scanf() dans la fxlibc. Codé par SlyVTT, plus qu'à nettoyer et fusionner.
Non classé
- Regarder du côté serial (plus facile que l'USB) pour la communication inter-calculatrices (multijoueur) et ultimement l'audio (libsnd de TSWilliamson).
- Un système pour recompiler des add-ins mono sur la Graph 90+E avec une adaptation automatique.
- Support des fichiers en RAM pour pouvoir utiliser l'API haut-niveau sur tous les modèles et éviter la lenteur de BFile à l'écriture quand on a assez de RAM.
Citer : Posté le 02/04/2023 11:36 | #
Si tu es sous Debian/Ubuntu ou dérivé, essaie d'installer le paquet ?
Citer : Posté le 02/04/2023 11:42 | #
Oh merci ça a l'air de marcher !
Citer : Posté le 02/04/2023 13:44 | #
Est ce que tu pourrais expliquer comment utiliser l'usb pour pouvoir envoyer des donnés de l'ordi à la calculatrice et inversement ?
Citer : Posté le 05/04/2023 15:09 | #
Il y a déjà presque tout :
- Le tuto de gint 2.5.0 pour la comm calto → PC : https://www.planet-casio.com/Fr/forums/topic13572-56-gint-un-noyau-pour-developper-des-add-ins.html#183085
- Le tuto fxSDK 2.10 sur l'utilisation de libfxlink côté PC : https://www.planet-casio.com/Fr/forums/topic13164-38-fxsdk-un-sdk-alternatif-pour-ecrire-des-add-ins.html#191167
En gros ce qui manque c'est comment utiliser usb_read_*() dans gint... mais c'est pas très dur, c'est très proche de usb_write_*().
Citer : Posté le 08/05/2023 09:55 | #
Pour gint il y a pas juste un wiki/ une liste des commandes et ce qu'elle font ? au lieu d'avoir a chercher dans le tuto
Caltos : G35+EII, G90+E (briquée )
Citer : Posté le 08/05/2023 09:58 | #
Les headers fournissent cette fonction, ils sont conçus pour pouvoir être lus de haut en bas un peu comme une page de wiki. Je n'ai jamais franchi le pas de maintenir une doc complète (surtout en français), ça me paraissait plus productif de développer la lib...
Citer : Posté le 19/08/2023 23:08 | # | Fichier joint
Petite update très sympa : la bannière de l'OS au retour du menu principal sur Graph 90+E a disparu grâce au travail de reverse-engineering de Dr-Carlos.
Pour rappel, quand vous retournez au menu sur Graph 90+E (soit en appuyant sur MENU durant getkey(), soit en appelant gint_osmenu() manuellement) et que vous revenez dans l'add-in, vous avez un frame où la bannière de l'OS est visible et l'appli est à la taille de l'OS au lieu d'être en plein écran, et il faut appuyer sur une touche pour revenir à la normale :
Désormais, l'add-in reprend dès qu'on revient du menu principal, il suffit donc de redessiner l'écran au retour du menu pour faire disparaître complètement ce désagrément. getkey() le fait automatiquement en appelant dupdate(), ce qui conviendra pour à peu près tout le monde. (Pour les applications qui ne gardent pas l'image en VRAM, vous pouvez remplacer l'option GETKEY_MENU_DUPDATE par GETKEY_MENU_EVENT, voir le commit pour les détails.) Au retour de gint_osmenu(), les programmes bien écrits redessineront probablement déjà l'écran, sinon vous pouvez adapter le code pour que ce soit fait automatiquement.
Et hop, un petit changement qui fait du bien :3
Citer : Posté le 03/02/2024 16:26 | #
j'ai essayer ce code pour 35+EII:
#include <gint/keyboard.h>
int main(void)
{
dclear(C_WHITE);
dtext(1, 1, C_BLACK, "HI guys!");
dtext(1, 3, C_LIGHT, "1");
dtext(1, 5, C_DARK, "This is Tuper :D");
dupdate();
getkey();
return 1;
}
est ce que c'est normal?
Tuper
Citer : Posté le 03/02/2024 16:28 | #
Oui, tu n'as pas allumé le moteur de gris avec dgray(DGRAY_ON). Vois <gint/gray.h> pour des détails sur la façon de l'utiliser.
Citer : Posté le 03/02/2024 16:43 | #
Alors j'ai essayé ce code sans succes :/
#include <gint/keyboard.h>
#include <gint/gray.h>
int main(void)
{
dgray(DGRAY_ON)
dclear(C_WHITE);
dtext(1, 1, C_BLACK, "HI guys!");
dtext(1, 20, C_LIGHT, "1");
dtext(1, 10, C_DARK, "This is Tuper :D");
dupdate();
getkey();
return 1;
}
Avant:
Apres:
Merci!
Citer : Posté le 03/02/2024 17:21 | #
Et y a t-il un moyen pour génerer des nombres aléatoires?
Citer : Posté le 03/02/2024 17:23 | #
Utilise les fonctions standard srand() et rand() de <stdlib.h>. Tu peux initialiser srand() avec le nombre de ticks comptés par l'horloge.
#include <stdlib.h>
// une fois au début du programme
srand(rtc_ticks());
// et ensuite à chaque fois que tu veux générer un nombre aléatoire
int x = rand();
// x est entre 0 et RAND_MAX
Citer : Posté le 03/02/2024 17:26 | #
Utilise les fonctions standard srand() et rand() de <stdlib.h>. Tu peux initialiser srand() avec le nombre de ticks comptés par l'horloge.
#include <stdlib.h>
// une fois au début du programme
srand(rtc_ticks());
// et ensuite à chaque fois que tu veux générer un nombre aléatoire
int x = rand();
// x est entre 0 et RAND_MAX
Citer : Posté le 03/02/2024 17:31 | #
et y a t-il un moyen de faire l'équivalent de
Citer : Posté le 03/02/2024 17:39 | #
ChatGPT t'a probablement dit de faire srand(clock()) avec <time.h> ce qui marche aussi.
Pour le délai de 10 secondes :
sleep_ms(10000);
Tu as aussi sleep_us() qui permet d'attendre un nombre choisi de microsecondes (c'est pas précis à la microseconde près mais pas loin).
Edit : Note que pour réguler la vitesse du programme il vaut mieux utiliser un timer comme dans le tutoriel.
Citer : Posté le 07/02/2024 19:45 | #
Salut encore une fois
je me demandais comment faire pour dtext() une variable? car quand je le fais il me montre une série de caracteres qui n'est pas le nombre
Tuper
Citer : Posté le 07/02/2024 19:48 | #
Il faut passer par dprint(), pas par dtext()
dprint accepte un nombre variable de paramètres (fonction variadique) qui est contrôlé par une chaîne de format comme printf() en C.
Citer : Posté le 07/02/2024 19:49 | #
ahh ok merci! je te citerai dans mon programme
Citer : Posté le 07/02/2024 19:51 | #
Une variable n'est pas du texte, donc il faut soit que tu génères un texte correspondant à la valeur de la variable (genre pour l'entier -42 le texte composé de trois caractères "-", "4" et "2") soit que tu utilises une fonction qui peut le faire à ta place.
En C, on utilise habituellement les fonctions standard de la famille de printf() (printf formatted) dont tout le job est justement de faire ce genre de conversions. En première approche, tu peux générer le texte dans une variable chaîne de caractères à l'aide de sprintf() et ensuite afficher ça :
char str[20];
sprintf(str, "%d", x);
dtext(x, y, C_BLACK, str);
Je te laisse chercher autour de l'internet comment utiliser les formats de printf() (ici %d) pour spécifier la conversion.
gint a un raccourci qui s'appelle dprint() pour gagner du temps :
dprint(x, y, C_BLACK, "%d", x);
Le fonctionnement est le même et en particulier ça cache du printf() dont les formats sont à aller chercher là-bas.
Citer : Posté le 24/03/2024 11:06 | #
Ce post sera dans les notes de publication de gint 2.11.
La fonctionnalité est actuellement disponible sur les branches dev du fxSDK et de gint.
Compilation des add-ins mono pour Graph 90+E
Suite à une idée de SlyVTT et des PR associées l'an dernier (fxSDK#11, gint#25) j'ai implémenté cette semaine un mécanisme pour compiler les programmes gint conçus pour la Graph mono sous la forme d'un g3a exécutable sur la Graph 90+E.
Gauche : Builder (Mb88) || Droite : Arena (Lephe)
Quels add-ins sont concernés ?
Ce système contient essentiellement une version de gint qui est proche de la version gint pour Graph 90+E mais utilise les fonctions de dessin de la Graph monos (et quelques autres détails, mais principalement ça). Donc tous les add-ins qui utilisent uniquement des fonctions gint disponibles à la fois sur Graph mono et Graph 90+E peuvent être compilés comme ça.
Attention : les add-ins qui utilisent directement des syscalls de la Graph mono, ou accèdent à la mémoire explicitement avec des addresses spécifiques de la Graph mono, ou autres fonctionnalités qui n'ont pas d'équivalent sur la Graph 90+E, ne peuvent pas être portés automatiquement par cette méthode. Ce n'est pas un émulateur de g1a !
Notez aussi que les accès aux fichiers sont souvent beaucoup plus lents sur la Prizm/Graph 90+E que les modèles de Graph avant la 35+E II, donc ne soyez pas étonnés si ça prend du temps sur les sauvegardes.
Comment l'utiliser ?
Pour les nouveaux add-ins créés avec fxsdk new après la publication de cette version du fxSDK, il suffit d'utiliser la commande fxsdk build-fxg3a.
Pour les add-ins existants, il faut rajouter quelques détails dans CMakeLists.txt. Spécifiquement, un bloc elseif() tout à la fin pour spécifier comment générer le g3a. Typiquement, ça ressemble à ça :
generate_g3a(TARGET myaddin OUTPUT "MyAddin-fx.g3a"
NAME "MyAddin-fx" ICONS assets-cg/icon-uns.png assets-cg/icon-sel.png)
C'est tout ce qui est nécessaire techniquement, mais je vous conseille aussi d'ajouter /build-fxg3a dans le .gitignore pour ne pas le pousser accidentellement sur vos dépôts distants.
/build-fxg3a
Notes
Dans les versions actuelles de gint les fonctions "génériques" disponibles sur tous les modèles et les fonctions "spécifiques" (par exemple des drivers) ne sont pas clairement listées et séparées. C'est un problème sur lequel je vais travailler progressivement.
Ultimement, ce sera résolu à la prochaine version majeure (gint 3) puisqu'il faudra sans aucun doute changer les APIs d'une façon qui cassera le code existant. Je vous donnerai plus de détails sur le plan quand ça approchera, en tous cas je peut clarifier tout de suite qu'il sera possible d'installer en même temps gint 2 et gint 3 donc tous les add-ins resteront compilables sans problèmes.
Les PR originales de SlyVTT ont tout juste un an, ce qui est vachement lent de mon côté, mais avec tout ce que j'ai dépilé ces derniers temps je gagne du terrain. Il y a d'autres trucs cools dans ma TODO list (dans ma signature) qui arrivent bientôt
Edit : @RDP
Citer : Posté le 10/04/2024 19:37 | # | Fichier joint
Ce post sera dans les notes de publication de gint 2.11. @RDP
La fonctionnalité est actuellement disponible sur les branches dev du fxSDK et de gint.
Support élémentaire du debuggage à distance
Grâce à une contribution de Redoste (gint#27), nous avons maintenant la possibilité de debugger les add-ins gint à distance en utilisant GDB.
Pour l'instant fxlink est nécessaire donc le debuggage à distance n'est pas possible sous Windows/WSL.
Installation
Il vous faura mettre à jour gint ainsi que le fxSDK, et installer le nouveau dépôt Lephenixnoir/sh-elf-gdb qui fournit GDB pour la calculatrice.
% giteapc install Lephenixnoir/sh-elf-gdb
Note: Pour ceux qui ont déjà sh-elf-gdb parce qu'ils m'ont aidé à tester, je vous conseille de le désinstaller puis réinstaller : j'ai depuis ajouté un patch qui évite une erreur bizarre quand on debugge depuis Add-In Push.
Utilisation sur la calculatrice
Pour que le debugger soit utile il faut compiler avec les infos de debug activées, c'est à dire avec l'option -g. C'est fait par défaut dans les nouveaux projets avec la nouvelle version du fxSDK, mais pour les projets existants il faut l'ajouter manuellement dans le target_compile_options() de CMakeLists.txt :
Le debugger démarre automatiquement lorsqu'une exception se produit après que vous ayez appelé la fonction gdb_start_on_exception(), typiquement au début de votre main(). L'appel de cette fonction est ce qui cause l'inclusion du code approprié dans votre add-in.
int main(void)
{
gdb_start_on_exception();
/* ... */
}
Ce démarrage automatique est très utile si un crash se produit mais que vous ne l'attendiez pas : lorsque vous voyez le crash, vous pouvez brancher la calculatrice et commencer à debugger.
Par contre, ça ne permet pas de préconfigurer le programme (e.g. pour mettre des breakpoints avant de commencer l'exécution). Pour ça, vous pouvez simplement créer une exception passagère avec trapa au début de main().
{
gdb_start_on_exception();
/* Forcer une exception pour ajouter un breakpoint */
__asm__("trapa #42");
/* ... */
}
Indicateurs visuels du processus de debuggage à distance
Pour ne pas se perdre (et diagnostiquer tout potentiel bug avec le système), le debugger à distance affiche des icônes en haut à droite de l'écran.
De haut en bas :
Le plus important est que lorsque le debuggage à distance avec GDB est activé, il n'y a plus d'écran System ERROR. Et donc un crash se verra uniquement à la présence d'un indicateur visuel en haut à droite de l'écran. Ils sont petits surtout sur la Graph 90+E donc faites attention.
Utilisation sur le PC
Avant ou après que la calculatrice n'initie la connexion à GDB du fait d'un crash, lancez GDB sur l'ordinateur à l'aide du wrapper fxsdk gdb.
Les arguments de fxsdk gdb sont les mêmes que ceux de la commande gdb classique, et en particulier vous devez spécifier le nom du fichier ELF que vous êtes en train d'exécuter sur la calculatrice. Typiquement c'est build-*/myaddin. Attention à bien sélectionner build-cg-push si vous envoyez votre add-in avec Add-In Push parce que ce n'est pas le même ELF.
La différence entre fxsdk gdb et sh-elf-gdb directement est que le premier démarre automatiquement un bridge qui permet de communiquer avec la calculatrice et lance ensuite GDB en mode debuggage à distance. (L'option --bridge-only permet de ne démarrer que le bridge, si vous connaissez bien GDB et que vous voulez voir passer les paquets échangés.)
En gros c'est tout ce qu'il y a à faire, mais pour ne pas vous laisser sans détail sur GDB voilà quand même quelques explications de base sur son utilisation.
Exemples d'utilisation
Je pars du programme suivant, qui a pour bug évident qu'un pointeur invalide s'est retrouvé dans le tableau avec la liste des entrées du menu.
#include <gint/keyboard.h>
#include <gint/gdb.h>
void draw_menu(void)
{
const char *entries[5] = {
"Play", "Settings", "Credits", "Quit", (char *)0x14141414
};
for(int i = 0; i < 5; i++)
dtext(4, 4+12*i, C_BLACK, entries[i]);
}
int main(void)
{
gdb_start_on_exception();
dclear(C_WHITE);
draw_menu();
dupdate();
getkey();
return 0;
}
J'ai remarqué un crash tout à l'heure donc j'ai rajouté l'include ainsi que l'appel à la fonction gdb_start_on_exception() au début du programme. Je le lance sur ma Graph 90+E, et j'obtiens la pastille bleue. Je lance donc GDB sur le PC :
[19:26] calculators: waiting for calculator to connect...
[19:26] calculators 001:103: successfully claimed interface!
[19:26] socket: waiting for client on "/tmp/fxsdk-gdb-bridge-001-103.socket"...
Reading symbols from build-cg-push/myaddin...
The target architecture is set to "sh4al-dsp".
Remote debugging using /tmp/fxsdk-gdb-bridge-001-103.socket
0x8c202ecc in dtext_utf8_next (str_pointer=0x8c1dff40) at /home/el/Projects/gint/src/render/topti.c:59
59 uint8_t lead = *str++;
(gdb)
Les quelques messages commençant par "[19:26]" sont affichés par le bridge qui gère la connexion à la calculatrice, le reste est ensuite affiché par GDB. La partie qui nous intéresse commence à 0x8c202ecc, elle nous indique :
Et ensuite le prompt, "(gdb)". Clairement le crash s'est produit dans une fonction de gint, mais ce n'est pas forcément un bug dans gint ; on a peut-être mal appelé une fonction. La commande backtrace (raccourci "bt") affiche la série d'appels par laquelle on est arrivés au niveau du crash.
#0 0x8c202ecc in dtext_utf8_next (str_pointer=0x8c1dff40) at /home/el/Projects/gint/src/render/topti.c:59
#1 0x8c20347c in topti_render (size=-1, bg=-1, fg=0, f=0x8c20d2b0, str_char=0x14141414 <error: Cannot access memory at address 0x14141414>, y=52, x=4) at /home/el/Projects/gint/src/render-cg/topti.c:84
#2 dtext_opt (x=4, y=<optimized out>, fg=0, bg=-1, halign=0, valign=0, str=0x14141414 <error: Cannot access memory at address 0x14141414>, size=-1) at /home/el/Projects/gint/src/render-cg/topti.c:140
#3 0x8c202df2 in dtext (x=<optimized out>, y=<optimized out>, fg=<optimized out>, str=<optimized out>) at /home/el/Projects/gint/src/render/dtext.c:6
#4 0x8c200264 in draw_menu () at /home/el/Programs/tmp/gint-remote-debugging-example/src/main.c:12
#5 0x8c2002c4 in main () at /home/el/Programs/tmp/gint-remote-debugging-example/src/main.c:20
Vous pouvez voir pour chaque fonction son nom, ses paramètres, et le fichier source associé, et on remonte jusqu'à main() et draw_menu(), les fonctions de notre programme source.
Notez que pas mal de paramètres sont "<optimized out>" : c'est le résultat du fait que le compilateur a optimisé le programme et donc certaines variables ont sauté, et certains bouts de code auront sauté aussi d'ailleurs. Ça vous complique un peu la tâche. Si vous y tenez vous pouvez compiler avec -O0 ce qui vous donnera des informations de debug de meilleure qualité, par contre la taille de l'add-in et les perfs vont en prendre un sacré coup.
Plus important encore, vous remarquez qu'il y a un paramètre pour lequel il est écrit <error: Cannot access memory at address 0x14141414>, et ça déjà ça nous révèle qu'on a un pointeur invalide qui se balade dans le programme.
Ça se verra encore mieux si on inspecte les variables locales de la fonction dtext_utf8_next() qui a planté avec la commande info locals.
str = 0x14141415 <error: Cannot access memory at address 0x14141415>
lead = <optimized out>
n2 = <optimized out>
n3 = 253 '\375'
n4 = <optimized out>
Vous ne connaissez pas forcément le code de cette fonction interne de gint mais on peut se douter que str est une chaîne de caractères. Et le pointeur est bien invalide : vous pouvez le vérifier en essayant d'afficher ses contenus avec la commande print qui peut afficher à peu près n'importe quelle expression C.
Cannot access memory at address 0x14141415
J'en profite pour mentionner que les expressions qu'on évalue sont interprétées dans le contexte de la fonction dtext_utf8_next(). Pour inspecter les variables locales d'une autre fonction listée par bt et faire des calculs sur les valeurs associées, il faut d'abord sélectionner la fonction avec la commande frame suivi du numéro affiché par bt (par exemple frame 3 pour monter dans la fonction dtext()).
Normalement à ce stade on irait plutôt essayer de comprendre d'où le pointeur vient, mais pour s'amuser un peu et montrer les fonctionnalités cool implémentées par Redoste supposons qu'on veut continuer l'exécution en contournant l'erreur. On peut aller chercher l'instruction exacte qui a planté en affichant le code autour de la valeur $pc (x c'est "examine" pour regarder la mémoire, 3i demande 3 instructions, et pc est l'adresse de l'instruction qui a planté).
=> 0x8c202ecc <dtext_utf8_next+6>: mov.b @r6+,r1
0x8c202ece <dtext_utf8_next+8>: extu.b r1,r1
0x8c202ed0 <dtext_utf8_next+10>: mov r1,r0
Ici c'est donc r6 qui contient le pointeur invalide, ce qu'on peut confirmer avec info registers.
r0 0xb 11
r1 0x0 0
(...)
r6 0x14141414 336860180
(...)
r15 0x8c1dff10 0x8c1dff10
pc 0x8c202ecc 0x8c202ecc <dtext_utf8_next+6>
pr 0x8c20347c 0x8c20347c <dtext_opt+212>
(...)
Pour contourner l'erreur d'accès mémoire, je peux assigner une valeur factice à r1 (un caractère de fin de chaîne, donc la valeur 0) puis placer pc sur l'instruction d'après :
(gdb) set $pc = 0x8c202ece
Ensuite je continue l'exécution. Ici on ne peut pas utiliser la commande continue habituelle parce que sinon GDB enverrait à l'add-in la segfault qu'il était sur le point de se prendre (et le programme s'arrêterait, vous ramenant au menu principal). Il faut être plus explicite et demander de continuer sans signal avec la commande signal 0.
Continuing with no signal.
Le programme continue et se termine normalement. Voilà pour une première démo.
Maintenant, je recommence mais cette fois je vous montre comment on peut insérer un breakpoint et surveiller l'exécution avant que le bug ne se produise pour voir où ça part en vrille. D'abord on va forcer un arrêt avant que le bug ne se produise en ajoutant un petit trapa dans main() :
{
gdb_start_on_exception();
__asm__("trapa #42");
dclear(C_WHITE);
draw_menu();
/* ... */
}
Et cette fois le programme a une erreur dès l'entrée dans main(), ce qu'on voit dans GDB.
[23:18] calculators: waiting for calculator to connect...
[23:18] calculators 001:024: successfully claimed interface!
[23:18] socket: waiting for client on "/tmp/fxsdk-gdb-bridge-001-024.socket"...
Reading symbols from build-cg-push/myaddin...
The target architecture is set to "sh4al-dsp".
Remote debugging using /tmp/fxsdk-gdb-bridge-001-024.socket
main () at /home/el/Programs/tmp/gint-remote-debugging-example/src/main.c:20
20 dclear(C_WHITE);
On place alors un breakpoint. La commande habituelle pour faire ça dans GDB est break (raccourci "b"), mais ça vous donne un "breakpoint logiciel", et sans rentrer dans les détails on ne supporte que les "breakpoints matériels". Donc il faut utiliser la commande hbreak (raccourci "hb") à la place. Attention, si vous utilisez b ça risque de ne pas vous donner d'erreur (sur Graph 90+E avec Add-In Push) mais le breakpoint ne marchera pas.
Hardware assisted breakpoint 1 at 0x8c202ddc: file
/home/el/Projects/gint/src/render/dtext.c, line 6.
(gdb) c
Continuing.
Breakpoint 1, dtext (x=4, y=4, fg=0, str=0x8c20c0d0 "Play") at /home/el/Projects/gint/src/render/dtext.c:6
6 dtext_opt(x, y, fg, C_NONE, DTEXT_LEFT, DTEXT_TOP, str);
En spécifiant hb dtext j'indique à GDB que je veux m'arrêter chaque fois qu'on rentre dans la fonction dtext(). Une fois le breakpoint placé je reprends l'exécution avec la commande continue (raccourci "c").
Le programme s'arrête ensuite puisque le breakpoint est atteint, et vous pouvez voir aux paramètres qu'on est dans le premier appel, sur le point d'afficher le texte "Play". Je continue encore, et en spécifiant 4 en paramètre à continue j'indique que je veux sauter les 3 prochaines occurrences de ce breakpoint.
Will ignore next 3 crossings of breakpoint 1. Continuing.
Breakpoint 1, dtext (x=4, y=52, fg=0, str=0x14141414 <error: Cannot access memory at address 0x14141414>)
at /home/el/Projects/gint/src/render/dtext.c:6
6 dtext_opt(x, y, fg, C_NONE, DTEXT_LEFT, DTEXT_TOP, str);
Avec ça je me retrouve à l'arrêt au début de l'exécution du dtext() fatidique qui va nous créer le crash ; vous pouvez voir qu'on retrouve le pointeur invalide dans le paramètre str. Notez qu'à ce stade la segfault ne s'est pas encore produite !
Pour progresser plus lentement dans l'exécution, on peut utiliser la commande step (raccourci "s") pour avancer une ligne de code à la fois. Ici j'exécute la commande plusieurs fois à la suite, et vous pouvez voir qu'on descend dans les fonctions de rendu de texte de gint doucement mais sûrement.
dtext_opt (x=4, y=52, fg=0, bg=-1, halign=0, valign=0, str=0x14141414 <error: Cannot access memory at address 0x14141414>, size=-1)
at /home/el/Projects/gint/src/render-cg/topti.c:129
129 if(halign != DTEXT_LEFT || valign != DTEXT_TOP)
(gdb) s
140 topti_render(x, y, str, topti_font, fg, bg, size);
(gdb) s
topti_render (size=<optimized out>, bg=<optimized out>, fg=<optimized out>, f=<optimized out>, str_char=<optimized out>,
y=<optimized out>, x=<optimized out>) at /home/el/Projects/gint/src/render-cg/topti.c:140
140 topti_render(x, y, str, topti_font, fg, bg, size);
(gdb) s
63 if(x >= dwindow.right || y >= dwindow.bottom) return;
(gdb) s
64 if(y + height <= dwindow.top) return;
Je vous passe quelques étapes parce qu'il y a encore pas mal de lignes avant qu'on atteigne le crash ; je continue sans m'arrêter avec continue.
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x8c202ecc in dtext_utf8_next (str_pointer=0x8c1dff40) at /home/el/Projects/gint/src/render/topti.c:59
59 uint8_t lead = *str++;
Et là on retrouve le crash. Vous pouvez voir que cette fois il y a un message "Program received... Segmentation fault". Pour des raisons techniques ce message n'est pas affiché au tout premier arrêt quand vous lancez fxsdk gdb, mais vous pouvez connaître la raison de l'arrêt avec info program.
Dans tous les cas, on a bien compris d'où vient le bug maintenant, donc on peut arrêter le programme. On pourrait continue à ce stade, auquel cas GDB enverrait la segfault à l'add-in qui s'arrêterait tout seul (retour au menu), mais on peut aussi utiliser la commande kill (raccourci "k") qui est plus explicite et produit le même effet. Vous pouvez vous en servir pour quitter l'add-in à tout moment pendant une session de debuggage.
Kill the program being debugged? (y or n) y
[Inferior 1 (Remote target) killed]
(gdb) %
Limitations
Les seules choses qui ont été testées sont les suivantes :
Les breakpoints logiciels ne sont pas supportés pour l'instant donc pour mettre un breakpoint sur une fonction en RAM il faut nécessairement utiliser la commande hbreak et non break. On ne supporte qu'un ou deux breakpoints selon les cas, et on ne le dit pas vraiment à GDB, donc si vous en mettez plus ils risquent de ne pas marcher sans raison (i.e. le programme passe sur un breakpoint mais il ne s'arrête pas pour communiquer avec le debugger).
Normalement il ne doit pas y avoir de cas où le programme freeze et vous n'avez pas le contrôle sur le debugger pour pouvoir le kill. Si ça se produit (et que c'est pas une boucle infinie dans votre code) faites-moi signe.
Voilà voilà, enjoy! o/