Les membres ayant 30 points peuvent parler sur les canaux annonces, projets et hs du chat.

Forum Casio - Projets de programmation


Index du Forum » Projets de programmation » gint : un noyau pour développer des add-ins
Lephenixnoir Hors ligne Administrateur Points: 22584 Défis: 149 Message

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), même un peu de C++ (voir µSTL)
  • 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.

VersionDateInfos supplémentaires
gint 2.9.021 Août 2022
gint 2.8.017 Mai 2022Effets dynamiques sur les imagesAPI de manipulations d'images
Overclock intégré
gint 2.7.119 Mars 2022Tutoriel capture des flux standards
gint 2.7.031 Décembre 2021
gint 2.6.029 Août 2021Tutoriel de capture vidéo par USB
gint 2.5.28 Juin 2021
gint 2.5.12 Juin 2021
gint 2.5.026 Mai 2021Intégration de fxlibc (dépôt) — Tutoriel de communication par USB
gint 2.4.027 Avril 2021Api GINT_CALL() pour les callbacks
gint 2.3.12 Février 2021
gint 2.3.029 Janvier 2021
gint 2.2.112 Janvier 2021
gint 2.2.011 Janvier 2021
gint 2.1.116 Septembre 2020
gint 2.1.021 Août 2020Polices UnicodeNouvelle API du moteur de gris
gint 2.0.3-beta10 Juillet 2020Modifications de l'API timer
gint 2.0.2-beta17 Juin 2020
gint 2.0.1-beta1er Juin 2020

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 (2022-08-21)

gint 2.10
  1. 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. Je vais peut-être coder des effets algébriques pour m'amuser un peu.
  2. Fignoler le driver USB. Ajouter la communication PC→calto, des pipes interruption/isochrones. Ajouter le support de descripteurs de fichiers USB. Potentiellement pousser jusqu'à avoir GDB pour debugger.

Non classé

  • Support de scanf() dans la fxlibc.
  • 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.
  • Overclock sur Graph mono.



Précédente 1, 2, 3 ··· 10 ··· 20 ··· 30 ··· 35, 36, 37, 38, 39, 40, 41 ··· 50 ··· 60 ··· 67, 68, 69 Suivante
Lephenixnoir Hors ligne Administrateur Points: 22584 Défis: 149 Message

Citer : Posté le 15/03/2020 13:13 | #


Le format n'est pas difficile à obtenir. Je te conseille de prendre ce que tu as trouvé et on peut l'adapter ensemble.
Teusner Hors ligne Membre Points: 107 Défis: 0 Message

Citer : Posté le 15/03/2020 14:47 | # | Fichier joint


Voila c'est le fichier en pièces jointes que j'aimerai adapter. Bon je me doutes qu'il faudrait crop certains trucs comme le nom de la police sur le sprite, mais je ne pouvais pas par exemple définir le nombre de caractères sur chaque ligne dans gint (ici il n'y a que 3 lignes alors que gint prends par défaut 6 lignes)
Lephenixnoir Hors ligne Administrateur Points: 22584 Défis: 149 Message

Citer : Posté le 15/03/2020 15:11 | # | Fichier joint


Oh mais ça existe encore cette vieille police

Ci-joint la version appropriée. Les paramètres fxconv sont les suivants :

charset:print grid.size:3x5 grid.border:1

Teusner Hors ligne Membre Points: 107 Défis: 0 Message

Citer : Posté le 17/03/2020 15:20 | #


Merci pour la police au fait,
J'ai une interrogation là : J'ai des choses bizarre qui se passent entre les "gimage" et les "dimage". Globalement je mixe de l'affichage gris et classique sans trop faire gaffe (sur une même fenetre je peux avoir du classique et du gris)...

Est ce que ca peut poser des problèmes ? Est ce que ca ne serait pas trop degeu/non-opti de tout mettre en gray même si j'ai certaines images en noir et blanc ?

PS : je précise tout de même que je met bien des gray_stop !
Lephenixnoir Hors ligne Administrateur Points: 22584 Défis: 149 Message

Citer : Posté le 17/03/2020 15:25 | #


L'idée derrière les d*() et les g*() est que la méthode de rendu est assez différente. En noir et blanc il n'y a qu'une VRAM, en gris il y en a deux. Les d*() modifient une seule VRAM, les g*() en modifient deux.

Si tu le moteur de gris est actif, tu dois toujours dessiner avec g*(), même si tes images sont noir et blanc, car le noir et blanc sur deux VRAMs ne se produit pas de la même façon que le noir et blanc sur une seule VRAM. Si le moteur de gris est éteint, tu dois toujours dessiner avec d*().

-

J'ajoute que j'ai pensé l'autre jour à un système qui élimine cette distinction en déterminant automatiquement quelle fonction appeler. Dans le futur, il est probable que seuls les d*() existent. Je laisserai les g*() comme des alias des d*() jusqu'à la version majeure suivante.
Teusner Hors ligne Membre Points: 107 Défis: 0 Message

Citer : Posté le 17/03/2020 15:27 | #


Okay, merci pour la précision !

Ajouté le 17/03/2020 à 15:55 :
Du coup toujours le même soucis ...

Comme je veux tout passer en g* (vu qu'il y a du gris sur mes vues), j'ai une fonction principale, et un timer qui est lancé dans cette fonction pour modifier un peu l'affichage à l'écran qui est maintenant en niveau de gris.

Le problème c'est que ce qu'il y a dans le callback du timer ne semble pas s'executer (ou n'affiche rien en tout cas ...)

Pour l'instant grosso merdo j'ai ça :
int main() {
    gray_start();
    timer_setup(0, timer_delay(0, 50*1000), 0, &callback, NULL);
    timer_start(0);
    getkey();
    gray_stop();
}

void angle_callback(volatile void *arg) {
    gimage(1, 1, &img_mypic);
    gupdate();
}


J'ai l'impression (sans doute mauvaise) que le fait que le gray engine ne soit pas connu/lancé dans le callback fait qu'il ne sait pas où tracer ... Alors que tout le reste a survécu au passage au g*
Lephenixnoir Hors ligne Administrateur Points: 22584 Défis: 149 Message

Citer : Posté le 17/03/2020 16:28 | #


Le paramètre que tu envoies à timer_setup() dans cet exemple n'est pas le callback que tu décris.

Accessoirement, "modifier un peu" ça ne marche pas à cause du triple buffering actif quand le moteur de gris existe. Tu dois redessiner tous les frames de zéro parce que gupdate() « modifie la VRAM ».
Teusner Hors ligne Membre Points: 107 Défis: 0 Message

Citer : Posté le 17/03/2020 16:34 | #


Oui effectivement c'est angle_callback qu'il aurait fallu mettre dans timer_setup ...
Du coup si j'ai bien compris l'idée serait de tout redessiner dans le callback ?

Ajouté le 17/03/2020 à 17:59 :
Bon je reviens vers toi Lephe car ca ne marche toujours pas ...

int main() {
    gray_start();
    timer_setup(0, timer_delay(0, 50*1000), 0, &angle_callback, NULL);
    timer_start(0);
    getkey();
    timer_stop(0);
    gray_stop();
}

void angle_callback(volatile void *arg) {
    gclear(C_WHITE);
    gimage(1, 1, &img_mypic);
    gupdate();
}


Cela ne m'affiche absolument rien à l'écran. Donc j'ai essayé de faire le tracé dans la fonction principale. Donc la avec ca :

int main() {
    gray_start();
    while (1) {
        gclear(C_WHITE);
        gimage(1, 1, &img_mypic);
        gupdate();
        sleep_us(0, 500*1000);
    }
    gray_stop();
}


Le problème c'est que je perd les interruptions pour l'affichage et donc je ne peux plus gérer les touches.

Est ce qu'il y a une autre solution, ou alors est ce que je m'y prends mal ? J'aimerai comprendre pourquoi mon affichage ne marche plus avec mon timer ...

Merci d'avance,
Teusner
Lephenixnoir Hors ligne Administrateur Points: 22584 Défis: 149 Message

Citer : Posté le 17/03/2020 20:35 | #


Oh, je viens de voir autre chose, peut-être un problème. Ton callback n'est pas du bon type. Il devrait renvoyer un int. Si la valeur renvoyée n'est pas 0 le timer est arrêté. Tu devrais activer les warnings et ne pas le laisser passer !

Dis-moi si ça aide, je vais tester juste après (c'est pas que je veux pas mettre les mains dedans, juste que j'essaie de pas me faire engloutir ^^")

KikooDX a écrit :
Le code compile et fonctionne avec les changements donnés, mais le return 0; de main ne retourne pas au menu.

Donc ça je l'ai traqué, c'est une concurrence entre l'arrêt de l'application et la fin du transfert DMA provoqué par dupdate() quand le triple buffering est actif. Je sais comment le résoudre, faut juste que je le fasse proprement et le pousse dans gint.
Teusner Hors ligne Membre Points: 107 Défis: 0 Message

Citer : Posté le 17/03/2020 21:23 | #


Bon j'ai remplacé le type de retour de mon callback et les paramètres, puisque je n'en ai pas (donc j'avais aussi un warn)

int angle_callback() {
    gclear(C_WHITE);
    gimage(13, h-3, &img_cross);
    gupdate();
    return 0;
}


Et la
Spoiler
Ca ne marche toujours pas
Lephenixnoir Hors ligne Administrateur Points: 22584 Défis: 149 Message

Citer : Posté le 18/03/2020 13:27 | #


Ha ha.

Le moteur de gris utilise le timer 0 pour fonctionner, donc ton timer est refusé et ton callback n'est jamais appelé. Tu l'aurais vu si tu avais vérifié la valeur de retour de timer_setup(). Utilise un autre timer.

Je note toutefois que c'est pas écrit dans la description de gray_start(). J'ai ajouté ça en local, je le pousserai quand j'aurai nettoyé mon dépôt local qui est dans un état expérimental.
Teusner Hors ligne Membre Points: 107 Défis: 0 Message

Citer : Posté le 18/03/2020 13:35 | #


Merci bcp ... J'avoue que j'ai bien lu la doc maintenant, mais je commencais à perdre espoir. Ca explique pourquoi ca marchait parfaitement en d* et que le passage en g* était foireux. Je teste ca et je te redis ! Merci encore

Ajouté le 18/03/2020 à 13:48 :
Bon bah ca marche niquel : Du coup penses à l'ajouter dans le header timer.h car ca pourrait en coincer plus d'un !

Ajouté le 19/03/2020 à 11:15 :
J'ai besoin de quelques explications pour la lecture/enregistrement de fichiers ...

j'ai ce code plus ou moins récupéré/adapté (car depuis les noms de fonctions ont changées) du jeu Arena que tu as fait :
static const unsigned short filename[] = {
    '\\', '\\', 'f', 'l', 's', '0', '\\',
    's', 'p', 'a', 'c', 'e', '.', 's', 'a', 'v', 0x00
};

void save_write(int n) {
    BFile_Remove(filename);
    BFile_Create(filename, BFile_File, sizeof(n));
    int fd = BFile_Open(filename, BFile_WriteOnly);
    BFile_Write(fd, &n, sizeof(n));
    BFile_Close(fd);
}


Le problème c'est que quand j'appelle par exemple save_write(5) je m'attends à trouver dans la memoire secondaire un fichier space.sav avec l'entier 5 dedans .... chose qui ne se fait pas en réalité. Est ce que tu pourrais m'aider ?
Yatis En ligne Membre Points: 575 Défis: 0 Message

Citer : Posté le 19/03/2020 11:21 | #


Alors, il faut savoir que la gestion de l'écriture sur l'EEPROM par Casio est catastrophique.
Pour pouvoir écrire dans l'EEPROM en utilisant Bfile_WriteFile() il faut que :
* la taille du buffer que tu veux écrire sois un multiple de 2.
* l'adresse du buffer sois multiple de 2.

Ces restrictions sont dues à la configuration en écriture de l'EEPROM par Casio.
Lephenixnoir Hors ligne Administrateur Points: 22584 Défis: 149 Message

Citer : Posté le 19/03/2020 11:22 | #


Comme remarque préliminaire (chose que je ne savais pas quand j'ai codé Arena), pour obtenir un tableau de u16 tu peux écrire ta chaîne normalement avec juste un "u" devant :

static const unsigned short filename[] = u"\\\\fls0\\space.sav";
/* Mieux encore */
static uint16_t const *filename = u"\\\\fls0\\space.sav";

Vu le code, je pense que ça devrait marcher. Pour commencer à debugger, je te conseille de vérifier la valeur de retour de tous ces appels à BFile car il y en a probablement au moins qui plante. Les codes d'erreur de BFile sont bien faits et permettent généralement de trouver rapidement les problèmes.
Teusner Hors ligne Membre Points: 107 Défis: 0 Message

Citer : Posté le 19/03/2020 13:34 | #


Yo, alors quand tu dis "Les codes d'erreur de BFile sont bien faits et permettent généralement de trouver rapidement les problèmes", j'ai l'impression que toutes les lignes renvoient des erreurs ...

static uint16_t const *filename = u"\\\\fls0\\space.sav";

void save_write(int n) {
    dclear(C_WHITE);
    int a = BFile_Remove(filename);
    int b = BFile_Create(filename, BFile_File, sizeof(n));
    int fd = BFile_Open(filename, BFile_WriteOnly);
    int c = BFile_Write(fd, &n, sizeof(n));
    int d = BFile_Close(fd);
    dprint(1, 1, C_BLACK, C_NONE, "a=%d", a);
    dprint(1, 9, C_BLACK, C_NONE, "b=%d", b);
    dprint(1, 18, C_BLACK, C_NONE, "c=%d", fd);
    dprint(1, 27, C_BLACK, C_NONE, "d=%d", c);
    dprint(1, 36, C_BLACK, C_NONE, "e=%d", d);
    dupdate();
}


m'écrit à l'écran :

a = -1
b = -4
c = -1
d = -5
e = -5

Que dois-je en conclure ? (La première ligne doit "planter" puisque le fichier n'existe pas sur ma calto ...)
Lephenixnoir Hors ligne Administrateur Points: 22584 Défis: 149 Message

Citer : Posté le 19/03/2020 13:38 | #


Oui donc la première erreur on s'en fout, le fichier n'existe pas mais c'est pas grave.

La deuxième erreur à la création du fichier est IML_FILEERR_DEVICEFULL. Ta mémoire de stockage est peut-être pleine, ou pas optimisée, ou 4 octets pour un fichier ça ne passe peut-être pas.

Donc le fichier n'est pas créé donc évidemment tout le reste échoue.

La liste complète des codes est dans filebios.h (header de fxlib), je te la remets là :

// File system standard error code
#define IML_FILEERR_NOERROR             0
#define IML_FILEERR_ENTRYNOTFOUND       -1
#define IML_FILEERR_ILLEGALPARAM        -2
#define IML_FILEERR_ILLEGALPATH         -3
#define IML_FILEERR_DEVICEFULL          -4
#define IML_FILEERR_ILLEGALDEVICE       -5
#define IML_FILEERR_ILLEGALFILESYS      -6
#define IML_FILEERR_ILLEGALSYSTEM       -7
#define IML_FILEERR_ACCESSDENYED        -8
#define IML_FILEERR_ALREADYLOCKED       -9
#define IML_FILEERR_ILLEGALTASKID       -10
#define IML_FILEERR_PERMISSIONERROR     -11
#define IML_FILEERR_ENTRYFULL           -12
#define IML_FILEERR_ALREADYEXISTENTRY   -13
#define IML_FILEERR_READONLYFILE        -14
#define IML_FILEERR_ILLEGALFILTER       -15
#define IML_FILEERR_ENUMRATEEND         -16
#define IML_FILEERR_DEVICECHANGED       -17
//#define IML_FILEERR_NOTRECORDFILE     -18     // Not used
#define IML_FILEERR_ILLEGALSEEKPOS      -19
#define IML_FILEERR_ILLEGALBLOCKFILE    -20
//#define IML_FILEERR_DEVICENOTEXIST    -21     // Not used
//#define IML_FILEERR_ENDOFFILE         -22     // Not used
#define IML_FILEERR_NOTMOUNTDEVICE      -23
#define IML_FILEERR_NOTUNMOUNTDEVICE    -24
#define IML_FILEERR_CANNOTLOCKSYSTEM    -25
#define IML_FILEERR_RECORDNOTFOUND      -26
//#define IML_FILEERR_NOTDUALRECORDFILE -27     // Not used
#define IML_FILEERR_NOTALARMSUPPORT     -28
#define IML_FILEERR_CANNOTADDALARM      -29
#define IML_FILEERR_FILEFINDUSED        -30
#define IML_FILEERR_DEVICEERROR         -31
#define IML_FILEERR_SYSTEMNOTLOCKED     -32
#define IML_FILEERR_DEVICENOTFOUND      -33
#define IML_FILEERR_FILETYPEMISMATCH    -34
#define IML_FILEERR_NOTEMPTY            -35
#define IML_FILEERR_BROKENSYSTEMDATA    -36
#define IML_FILEERR_MEDIANOTREADY       -37
#define IML_FILEERR_TOOMANYALARMITEM    -38
#define IML_FILEERR_SAMEALARMEXIST      -39
#define IML_FILEERR_ACCESSSWAPAREA      -40
#define IML_FILEERR_MULTIMEDIACARD      -41
#define IML_FILEERR_COPYPROTECTION      -42
#define IML_FILEERR_ILLEGALFILEDATA     -43
Lephenixnoir Hors ligne Administrateur Points: 22584 Défis: 149 Message

Citer : Posté le 01/05/2020 12:02 | # | Fichier joint


Je viens de résoudre un bug parmi les plus bizarres (en termes d'apparence) que j'ai rencontrés, donc je voulais vous partager un peu l'histoire...

Ce bug est décevant parce qu'il est compliqué de l'intérieur mais possède une explication très simple. De loin, j'avais un programme qui dessinait une animation avec des images placées de façon aléatoires. Le moteur physique tournait à 50 Hz dans un timer et le moteur graphique tournait dans la boucle principale, à 50 Hz aussi parce que le rendu de chaque frame était bien plus rapide que 20 ms. J'utilisais ce vieux LCG pour avoir des nombres pseudo-aléatoires :

/* Quick and bad random number generator */
static uint32_t seed = 0xb7b7b7b7;

uint32_t rand(void)
{
    seed = (214013 * seed + 2531011);
    return (seed >> 16) & 32767;
}

Et l'animation finissait par planter sur une System ERROR au bout d'un certain temps. Variable, bien sûr.

Le premier indicateur d'un bug chiant c'est quand il faut 5 minutes montre en main pour le reproduire à chaque fois. Ça prend un temps fou. Alors j'ai commencé à réduire le code, et fini par trouver que le bug se produisait dans... une routine de bopti appelée par gimage(). "Génial" je me dis, "les bugs de bopti sont généralement des formules ratées, donc je vais relire le truc posément et trouver tout de suite. Il y a sans doute une position où mon flocon animé de 3x3 (un truc un peu extrême) n'est pas bien dessiné, et le bug prend du temps à se produire parce que les flocons sont placés aléatoirement et il faut un moment avant qu'un arrive pile à la bonne place."

Donc j'ai modifié le programme pour afficher des flocons à toutes les positions possibles en attendant le bug. Qui a disparu sur le champ. À ce stade, je suis déjà obligé de lancer l'add-in 15 ou 20 fois d'affilée parce que je ne sais pas si le bug a été corrigé ou s'il ne s'est simplement pas manifesté parce que j'ai pas eu de chance. En tous cas, à l'écran, rien. Je commence à restaurer le programme complet, boum le bug revient. ^^"

Donc là ça commence à m'énerver un peu, et je finis par mettre le doigt sur le fait que le timer qui lance le moteur physique doit être actif pour que le bug se manifeste. C'est un "bon signe" dans le sens où les timers, du fait qu'ils invoquent le callback un peu n'importe quand, sont souvent un coupable tout désigné pour les bugs non-déterministes. Sauf que ce callback fait que des calculs tous cons dans un tableau de flocons qui a une taille fixe, et appeler la fonction rand().

Je vous saute une bonne partie (~2 jours) que j'ai passée à tenter des myriades de choses pour comprendre le bug. J'ai cherché des dépassements de tableaux, j'ai cherché des recouvrements d'adresses dans le linker script, j'ai debuggé avec des rapports de crashs toujours plus tordus, lu le code assembleur produit par GCC des dizaines de fois, et rien. Entre-temps j'ai découvert que le moteur physique doit appeler rand() pour que le plantage se produise, et réussi à reproduire le bug à tous les coups en boostant sa fréquence de 50 Hz à 2000 Hz. À ce stade-là c'est certain que rand() fait planter le programme, mais comment ?

Du côté de bopti où ça plante, j'ai réussi à remarquer qu'une variable change de valeur subitement entre deux appels de fonction. Cette variable est une métrique de l'image à afficher, et comme elle devient complètement aléatoire, on dépasse complètement de l'image, ce qui provoque une erreur d'accès mémoire. Bien sûr une variable ne change pas de valeur toute seule, donc certainement l'interruption du timer a quelque chose à voir avec l'histoire. À ce moment-là je n'ai pas vu le lien avec le fait que la variable est calculée comme ça :

      .data_stride  = ((img_columns - columns) << 2) * layers,

Vous le voyez peut-être déjà. J'ai continué à chercher comme un idiot en commentant les bouts de code que je pouvais jusqu'à ce que je trouve que la multiplication dans rand() était nécessaire pour faire planter le programme.

C'est le bon moment pour une parenthèse bas niveau. Dans le processeur, les calculs sont fait entre un certain nombre fixe de "registres" qu'on peut voir comme des variables. Généralement on met dedans des entiers et des pointeurs, et le processeur nous permet soit de faire des opérations arithmétiques soit d'aller lire ou écrire les valeurs pointées. On peut donc, par exemple, utiliser une instruction pour additionner les contenus de deux registres. Pour la multiplication, c'est un peu différent. D'une part la multiplication est une opération plus lente que les additions/soustractions de base, et d'autre part quand on multiple deux nombres de 32 bits le résultat fait jusqu'à 64 bits. Pour cette raison, le résultat d'une multiplication est stocké dans un registre mac de 64 bits qui sert exclusivement à ça.

Une autre chose à savoir est les conventions d'appel. Chaque fonction fait ses calculs avec les registres du processeur. Mais si une sous-fonction est appelée, elle va aussi utiliser les registres et donc remplacer les précieux résultats de calculs de son appelant. Pour cette raison, une sous-fonction va normalement sauvegarder et restaurer les registres qu'elle utilise pour que la fonction appelante puisse reprendre ses calculs après coup. Pour différentes raisons d'optimisation, il y a deux genre de registres : ceux que la sous-fonction sauvegarde avant d'utiliser, et ceux qu'elle peut utiliser et modifier sans avertissement, et que la fonction appelante doit sauvegarder elle-même si elle en a besoin.

Là je suis essentiellement à 15 minutes de trouver la solution. mac est un registre que l'appelant doit sauvegarder lui-même, autrement dit une sous-fonction comme rand() est libre de modifier sa valeur sans le restaurer après coup. Et ça c'est un gros problème... vous voyez, si le calcul de data_stride se fait interrompre pendant la multiplication, la valeur de mac peut être modifiée par rand() pendant l'interruption, ce qui provoque une corruption du résultat, et ensuite c'est la porte ouverte à toutes les dérives. Pour être exact, il faut que l'interruption se produise entre le moment où bopti exécute l'instruction de multiplication et le moment où il récupère le résultat.

Si bopti avait appelé la fonction rand() lui-même, bien sûr il aurait sauvegardé mac, car c'est la procédure. Mais une interruption ne s'annonce pas, elle apparaît de nulle part sans prévenir. Donc mac n'étant pas sauvegardé parce que bopti ne s'attendait pas à l'interruption, il était tout à fait possible de le corrompre par rand(). Il y a bien sûr d'autres registres que l'appelant doit sauvegarder et qui auraient pu provoquer ce bug, mais sans rentrer dans les détails ceux-là sont déjà sauvegardés automatiquement par un mécanisme matériel pour gagner du temps. mac est l'un des seuls candidats restants.

Pour vous montrer le côté pété de ce bug, voici les trois endroits où bopti fait une multiplication autour du calcul de data_stride. Les instructions mul exécutent la multiplication, et les sts macl lisent le résultat dans mac. Tout ce qu'il y a entre les deux n'a rien à voir, et a été mis là par le compilateur pour optimiser le programme. En effet, comme la multiplication prend 5 cycles à calculer, si on met le sts juste derrière le mul, il faut attendre 5 cycles pour avoir le résultat. On peut, à la place, faire quelques calculs en attendant le résultat pour gagner du temps.

  303c30:       0c 17           mul.l   r1,r12
  303c32:       47 2c           shad    r2,r7
  303c34:       02 1a           sts     macl,r2

  303c38:       02 37           mul.l   r3,r2
  303c3a:       04 1a           sts     macl,r4

  303c76:       01 37           mul.l   r3,r1
  303c78:       16 75           mov.l   r7,@(20,r6)
  303c7a:       e1 ff           mov     #-1,r1
  303c7c:       07 29           movt    r7
  303c7e:       2a a8           tst     r10,r10
  303c80:       61 1a           negc    r1,r1
  303c82:       16 19           mov.l   r1,@(36,r6)
  303c84:       05 1a           sts     macl,r5

Dans les deux premiers extraits, il y a 5 cycles où l'interruption peut se produire entre le mul et le sts, et dans le troisième il y a 7 cycles. Le processeur tourne à 30 MHz, ce qui laisse à l'interruption 0.5 µs pour s'insérer dans un des slots et provoquer le bug en corrompant mac par un appel à rand() avant que le résultat ne soit lu.

La solution de ce bug, je pense que vous l'avez devinée maintenant, ce que j'aurais dû moi-même programmer l'interruption du timer pour sauvegarder mac avant d'appeler le callback. Un oubli qui pose des questions sur les appels par pointeur de fonction, d'ailleurs... x)

Voilà qui est corrigé en tous cas, et vous voyez qu'on ne déconne pas avec les bugs qui ont une poignée de cycles pour se manifester. Parce qu'ils finissent toujours par le faire.

En fichier joint, quelques lignes que j'ai écrites sur #dev au moment où j'ai trouvé le problème.
Dark storm Hors ligne Labélisateur Points: 11566 Défis: 176 Message

Citer : Posté le 08/05/2020 17:47 | #


Mmmh, petit problème par ici…

git clone https://gitea.planet-casio.com/Lephenixnoir/gint.git
Clonage dans 'gint'...
remote: Énumération des objets: 2485, fait.
remote: Décompte des objets: 100% (2485/2485), fait.
remote: Compression des objets: 100% (1325/1325), fait.
remote: Total 2485 (delta 1611), réutilisés 1681 (delta 1078), réutilisés du pack 0
Réception d'objets: 100% (2485/2485), 1023.54 Kio | 10.55 Mio/s, fait.
Résolution des deltas: 100% (1611/1611), fait.
warning: la HEAD distante réfère à une référence non existante, impossible de l'extraire.


Faut effectivement checkout la branche compat, mais du coup si c'est la branche par défaut, y'a pas moyen de la merge sur master histoire d'avoir un HEAD propre ?
Finir est souvent bien plus difficile que commencer. — Jack Beauregard
Lephenixnoir Hors ligne Administrateur Points: 22584 Défis: 149 Message

Citer : Posté le 08/05/2020 18:00 | #


Je voulais le faire à la prochaine release... je vais me débrouiller pour publier une bêta *vite* et la fusionner.
Dark storm Hors ligne Labélisateur Points: 11566 Défis: 176 Message

Citer : Posté le 08/05/2020 18:20 | #


Ok, nice

Ajouté le 08/05/2020 à 19:03 :
Bon, du coup le paquet AUR de gint en pâtit

~/P/P/gint-git $ makepkg -i
==> Création du paquet gint-git r155.61da7de-1 (ven. 08 mai 2020 18:59:29)
==> Vérification des dépendances pour l’exécution…
==> Vérification des dépendances pour la compilation…
==> Récupération des sources…
  -> Mise à jour du dépôt gint-git git…
Récupération de origin
==> Validation des fichiers source avec sha256sums…
    gint-git ... Ignoré
==> Extraction des sources…
  -> Création d’une copie de travail du dépot gint-git git…
fatal: 'origin/HEAD' n'est pas un commit et une branche 'makepkg' ne peut pas en être créée depuis
==> ERREUR : Échec lors de la création d’une copie de travail du dépôt gint-git git
    Abandon…


Je vais essayer de trouver un workaround, mais je suis pas hyper confiant.

Ajouté le 08/05/2020 à 19:11 :
Trouvé. On peut sélectionner un commit particulier : https://bbs.archlinux.org/viewtopic.php?id=247781

Par contre ça veut dire que le paquet ne sera pas compilé automatiquement avec la dernière version disponible tant que la branche master sera pas ok.

Ajouté le 08/05/2020 à 19:17 :
Bon, du coup le paquet gint-git est à jour o/
Finir est souvent bien plus difficile que commencer. — Jack Beauregard
Lephenixnoir Hors ligne Administrateur Points: 22584 Défis: 149 Message

Citer : Posté le 08/05/2020 21:13 | #


Merci ! Tu peux juste demander la branche compat et ce sera bon pour l'instant
Précédente 1, 2, 3 ··· 10 ··· 20 ··· 30 ··· 35, 36, 37, 38, 39, 40, 41 ··· 50 ··· 60 ··· 67, 68, 69 Suivante

LienAjouter une imageAjouter une vidéoAjouter un lien vers un profilAjouter du codeCiterAjouter un spoiler(texte affichable/masquable par un clic)Ajouter une barre de progressionItaliqueGrasSoulignéAfficher du texte barréCentréJustifiéPlus petitPlus grandPlus de smileys !
Cliquez pour épingler Cliquez pour détacher Cliquez pour fermer
Alignement de l'image: Redimensionnement de l'image (en pixel):
Afficher la liste des membres
:bow: :cool: :good: :love: ^^
:omg: :fusil: :aie: :argh: :mdr:
:boulet2: :thx: :champ: :whistle: :bounce:
valider
 :)  ;)  :D  :p
 :lol:  8)  :(  :@
 0_0  :oops:  :grr:  :E
 :O  :sry:  :mmm:  :waza:
 :'(  :here:  ^^  >:)

Σ π θ ± α β γ δ Δ σ λ
Veuillez donner la réponse en chiffre
Vous devez activer le Javascript dans votre navigateur pour pouvoir valider ce formulaire.

Si vous n'avez pas volontairement désactivé cette fonctionnalité de votre navigateur, il s'agit probablement d'un bug : contactez l'équipe de Planète Casio.

Planète Casio v42 © créé par Neuronix et Muelsaco 2004 - 2022 | Il y a 70 connectés | Nous contacter | Qui sommes-nous ? | Licences et remerciements

Planète Casio est un site communautaire non affilié à Casio. Toute reproduction de Planète Casio, même partielle, est interdite.
Les programmes et autres publications présentes sur Planète Casio restent la propriété de leurs auteurs et peuvent être soumis à des licences ou copyrights.
CASIO est une marque déposée par CASIO Computer Co., Ltd