Quelques mythes brumeux du développement C
Posté le 23/04/2015 10:57
Vous les avez sans doute déjà croisés si vous programmez vos chères Casio en C, sans toutefois être en mesure de comprendre leur rôle. Voici quelques explications.
Les #pragma du code d'exemple
Le code d'exemple se termine par ceci :
//****************************************************************************
//************** ****************
//************** Notice! ****************
//************** ****************
//************** Please do not change the following source. ****************
//************** ****************
//****************************************************************************
#pragma section _BR_Size
unsigned long BR_Size;
#pragma section
#pragma section _TOP
//****************************************************************************
// InitializeSystem
//
// param : isAppli : 1 = Application / 0 = eActivity
// OptionNum : Option Number (only eActivity)
//
// retval : 1 = No error / 0 = Error
//
//****************************************************************************
int InitializeSystem(int isAppli, unsigned short OptionNum)
{
return INIT_ADDIN_APPLICATION(isAppli, OptionNum);
}
#pragma section
On va débroussailler un peu et voir ce que ça donne.
#pragma section _BR_Size
unsigned long BR_Size;
#pragma section
#pragma section _TOP
int InitializeSystem(int isAppli, unsigned short OptionNum)
{
return INIT_ADDIN_APPLICATION(isAppli, OptionNum);
}
#pragma section
Bon, c'est déjà plus clair. Pas pour vous ? Ça va venir.
Vous savez déjà que tout ce qui commence par un '#' est une instruction à destination du préprocesseur. Outre les #include, #define, #if et autres #warning, il y a les pragma (#pragma, vous l'aurez compris) qui permettent de donner des instructions au compilateur.
Dans notre cas, ces instructions sont liées aux
sections. Dans le code objet, une section est un segment du fichier dans lequel on regroupe du binaire « de même nature ». Par exemple, il y a classiquement les sections .text pour le code, .data pour les données et la section .bss qui elle, est initialisée à zéro au début de l'exécution (on y fout des variables initialisées à zéro et plein d'autres trucs).
Néanmoins, on appelle les sections comme on veut, on les met où on veut, et on en fait ce qu'on veut, au risque toutefois de faire n'importe quoi. Là, le code crée une section appelée _BR_size qui contient juste une variable (l'instruction #pragma section ferme la section ouverte au-dessus). L'intérêt, c'est que quand l'éditeur de liens va assembler les sections pour faire le fichier g1a, on pourra connaître l'adresse de cette variable, et du coup on pourra la modifier (sinon on ne peut pas avoir son adresse).
La deuxième section s'apelle _TOP. Son rôle est clair : en fait, c'est le morceau de code qui est dans cette section qui sera appelé au lancement de l'add-in.
Quand j'ai dit qu'on pouvait arranger les sections comme on voulait, c'est vrai, à quelques exceptions près. Et en l'occurrence, il faut que le point d'entrée soit au bon endroit. Le point d'entrée, c'est là où commence l'exécution du programme, et ce n'est pas toujours main(). Il y a souvent des trucs à initialiser avant et là, c'est INIT_ADDIN_APPLICATION() qui s'en charge.
Vous savez que lorsqu'on lance un add-in, le fichier est chargé à l'adresse 0x300000, et, une fois les 0x200 octets de header passés, on se retrouve à 0x300200 et le code commence à s'exécuter. Oui mais voilà : encore faut-il être sûr que le code d'initialisation a bien été mis à cet endroit. Dans notre cas, la section _TOP est unique (c'est le but) donc il suffit de mettre les sections _TOP en premières et on est sûrs que le point d'entrée est bien positionné.
Bon, il se sont sentis obligés de créer une fonction qui appelle INIT_ADDIN_APPLICATION(), cela dit si la fonction INIT_ADDIN_APPLICATION() s'était tout de suite trouvée dans la section _TOP, ça aurait fonctionné aussi (enfin, ils devaient avoir une bonne raison de ne pas faire ainsi, je suppose).
Du coup, j'ai pas donné le rôle de la variable _BR_Size : en fait, je pense qu'elle sert à stocker, après calcul, la taille des sections .data et .bss réunies. Je ne sais pas à quoi ça sert, ni pourquoi c'est fait, mais je pense que c'est ça. À mon avis, ce n'est pas utilisé à l'exécution, mais à l'édition des liens.
Enfin, voyez plus bas pour ces explications.
The size of B and R section should be 0x2000 bytes or less
Une erreur assez classique :
The size of B and R section should be 0x2000 bytes or less.
Je n'ai pas de certitudes sur cette erreur mais des éléments déjà assez solides pour en parler.
À mon avis, la variable _BR_Size plus haut permet de calculer la taille des sections .data et .bss, et un morceau de code s'assure que cette taille ne dépasse pas 0x2000 (8192) octets.
Pourquoi ? Tout simplement, je pense, parce que les sections .bss et .data sont chargées en RAM et que la zone de RAM prévue pour ça ne fait que 8192 octets. Donc si vous voulez en mettre plus, il renvoie une erreur pour vous empêcher de faire des conneries.
À noter que sous Linux, la taille est calculée mais aucun test n'est fait dessus, donc on peut faire des conneries. Remerciez donc plutôt cette erreur qui vous empêche de massacrer votre machine.
Nonexisting memory by data read/write access at ...
Ça, c'est parce que le programme fait des conneries, encore une fois.
Vous savez qu'avec les pointeurs on peut représenter des adresses mémoire, mais combien de pointeurs différents y a-t-il ? Ouch. 4 milliards, soit 4 Gio de données.
C'est clair que la calculatrice n'a pas 4 Gio de mémoire. En fait, elle a 4 Mio. Mais alors, où sont passées les autres adresses ?
Il faut savoir que lorsque vous demandez les deux premiers octets à l'adresse 0x300200 (c'est pour l'exemple, c'est la première instruction exécutée au lancement du programme), un module appelé MMU, pour Memory Management Unit (Unité de Gestion de la Mémoire pour les anglophobes prononcés), va transformer votre adresse (0x300200) en une autre adresse qui elle pointera dans la mémoire physique.
En fait, ce système existe pour permettre d'avoir des espaces d'adressage différents selon les processus.
Pour faire simple, le MMU va diviser la mémoire logique (entre 0x00000000 et 0xffffffff, les 4 Gio) entre les processus et à chaque accès, va récupérer l'adresse physique correspondante. Ça permet d'éviter de la contention entre les processus et d'autres problèmes assez chiants. Je vais pas détailler ici (c'est un peu compliqué), si vous voulez plus d'infos voyez la section 3, « Memory Management Unit (MMU) » de la
documentation hardware du groupe sh7705.
Et donc, si vous demandez au MMU d'accéder à une adresse logique aléatoire, il y a une très forte probabilité pour qu'il n'y ait pas de mémoire physique en face ! D'où la fameuse erreur « mémoire inexistante pour l'accès en lecture/écriture à l'adresse ... ».
Surveillez donc vos pointeurs si vous avez cette erreur ! Ah oui, et si elle se produit à la fin de l'exécution de l'add-in, quand vous revenez au menu, voyez le paragraphe suivant.
Nonexisting memory... à la fin de l'add-in
Si vous avez cette erreur à la fin de l'exécution, et que le programme n'utilise pas de GetKey() mais que des IsKeyDown() et affiliés, alors... c'est normal.
J'ai fait pas mal de tests sur ma propre calculatrice et j'ai pu constater le fait suivant :
Quelle que soit l'application, compilée avec gcc ou le SDK, un add-in lancé juste après un démarrage de la calculatrice qui ne réponde pas à certaines conditions provoquera un reset une fois l'exécution terminée, lorsqu'une autre application sera lancée depuis le menu.
Parmi les conditions qui permettent d'éviter ça, il y a « utiliser GetKey() », c'est pour ça que les applications du système ne plantent pas. Je pense que c'est lié au gestionnaire d'interruptions.
D'un autre côté, je vous avais prévenus qu'il fallait utiliser GetKey() (xD). Comme l'émulateur redémarre à chaque fois, le bug se produit tout le temps et produit une erreur d'accès mémoire. Sur la calculatrice, c'est sans doute la même, mais ça cause un reset processeur et la machine redémarre.
Cannot delete internal file
Cette erreur-là est purement liée au SDK : vous l'avez peut-être déjà rencontrée en tentant d'y compiler un programme.
En fait, le compilateur utilise tout un tas de fichiers avant de parvenir à votre cher exécutable g1a : citons, entre autres intéressants, FXADDINror.bin, qui doit, sous réserve que je ne dis pas trop de bêtises, contenir le code binaire de l'application, avant le passage au wrapper qui génère 0x200 octets d'en-tête. Au passage, FXADDINror.map décrit l'arrangement des sections dans le code.
Lorsqu'on recompile, le SDK supprime certains de ces fichiers pour en ré-assembler les composants et/ou les recréer. Le message d'erreur « Cannot delete internal file » signifie simplement qu'il n'a, pour une raison quelconque, pas pu le supprimer, et que par conséquent il ne peut pas terminer la compilation.
Warning : the following dependant file does not exist
Celle-là aussi est classique et uniquement liée au SDK. C'est une bizarrerie du logiciel, mais le meilleur moyen de gérer les fichiers d'en-tête est simplement de ne pas les ajouter dans la liste des fichiers du projet.
On ne sait pas trop comment le SDK les gère, mais si on les ajoute on obtient ce message d'erreur à ce sujet. Pour s'en débarrasser, il suffit de retirer le fichier concerné de la liste des fichiers du projet.
System ERROR !!
Celle-là est classique, mais on ne sait pas toujours d'où elle vient. En fait, c'est un cas de gestion des exceptions.
Lorsque vous faites un truc interdit par le kernel, celui-ci n'apprécie pas et il vous le fait savoir. Le cas classique c'est déréférencer le pointeur nul, ce qui est
par convention (eh oui) illégal. Il y a plein d'autres cas comme la division par zéro mais ils ne sont pas spécifiquement intéressants ici.
Dans tous les cas, c'est votre programme qui fait n'importe quoi. Les infos ne sont pas anodines : l'adresse est celle indiquée dans le registre tea (voyez la doc pour plus d'infos) et la valeur PC est le contenu du registre pc (program counter) au moment où l'exception s'est produite, c'est-à-dire l'adresse de l'instruction qui a causé l'exception plus 4.
Exception Blocked Reset
Sans doute la meilleure. Tous ceux qui ont travaillé avec le moteur de gris en ont au moins déjà rencontré une. En gros, c'est une erreur qui apparaît aléatoirement quand on utilise le moteur de gris et qui provoque le reset de la machine. Pas mal hein ?
En fait, l'EBR n'est pas une exception hyper grave pire que 100 System ERRORS cumulées, en fait non, c'est juste une exception. Le message parle de lui-même : « Exception Blocked Reset ». En gros, elle signale que l'exception n'a pas pu être traitée parce que le processeur était déjà en train de traiter une exception.
Ce que Kucalc a fait c'est qu'il a remplacé le gestionnaire d'interruptions par le moteur de gris. Oh, wait... je dois parler chinois là. Quelques explications :
Lorsque vous faites un truc illégal avec un programme (genre diviser par 0), ça génère une exception. L'exception est traitée dans un mode d'exécution spécial et parfois le système affiche une popup avec marqué « System ERROR !! » et quelques infos dedans. Les exceptions sont gérées par une routine qu'on appelle
exception handler. Dans notre cas, il y en a trois, selon le type d'exception :
→ exception générique : division par zéro, déréférencement d'un pointeur nul, etc.
→ exception tlb : en gros, quand vous demander n'importe quelle adresse au MMU.
→ interruption : pression de touches, connexion du port usb, etc.
Les trois gestionnaires sont liés parce qu'ils sont dans un espace de mémoire logique appelé espace vectoriel. L'adresse de base de cet espace est contenue dans un registre appelé vbr, qui signifie « vector base register ». Le calcul est simple :
→ l'exception handler est à l'adresse vbr + 0x100.
→ le tlb exception handler est à vbr + 0x400.
→ l'interrupt handler est à vbr + 0x600.
Ce que Kucalc a fait c'est qu'il a bougé vbr pour que vbr + 0x600 tombe sur sa fonction pour switcher les écrans. Il a aussi fait un autre truc horrible, c'est qu'il a forcé le processeur à ignorer toutes les interruptions, sauf celles de son timer. Donc quand vous appuyiez sur une touche, çe ne faisait strictement rien. Même le système ne le savait pas.
Le problème, c'est que à vbr + 0x600, on avait sa fonction, mais à vbr + 0x100 et vbr + 0x400 ? Eh bien, rien. Du code aléatoire. Des données. On ne sait pas. Donc, vous imaginez, si votre code produit une exception quelconque, on va aller à vbr + 0x100 pour demander à l'exception handler de la gérer, et on va trouver n'importe quoi. Du genre, des opcodes illégaux qui produisent eux-mêmes des exceptions.
Oui mais voilà, on est en mode d'exception donc on pas lancer la procédure de gestion. Donc, ben on arrête tout et ça fait un reset. Voilà, en gros, l'EBR, c'est ça. Une exception stupide dégénérée par un moteur stupide.
Personnellement, je n'en ai rencontré qu'une et ça m'a bien suffi. En général, il n'y a pas besoin de regarder votre code si vous en avez une... à moins que vous ne programmiez des applications ésotériques instables.
Voilà, c'est à peu près tout. Maintenant, vous n'avez plus de droit de dire que vous ne comprenez pas les erreurs qui vous tombent dessus !
Citer : Posté le 23/04/2015 12:02 | #
Moi j'y comprends rien
Citer : Posté le 23/04/2015 12:04 | #
Moi j'y comprends rien
Si tu es débutant en C (je cite), tu vas avoir du mal oui, c'est vrai
Au fait, si j'ai laissé passé des erreurs ou des trucs, dites-le, je les ajouterai dans la limite de mes humbles connaissances
Citer : Posté le 23/04/2015 12:05 | #
Je pense qu un renommage aiderais mieux genre : "D ou viennent les TLB(...) errors ?
Sinon bravo cest tres recherche peut etre a passer en topic de qualite ?
-Mon Fall Down
-Mon jeu de mains
-Mon starwars
-Mon dessinatout
-Mon niaiseux version 2.0
-Mon niaiseux version 3.0
-Inferno
-Mon super labyrinthe (en cours)
-Mon call of duty en 3D
-Casion (avec Az)
Citer : Posté le 23/04/2015 12:06 | #
et de montrer que tu es plus fort que les autres.d'écrire des articlesCiter : Posté le 23/04/2015 12:07 | #
Je pense qu un renommage aiderais mieux genre : "D ou viennent les TLB(...) errors ?
Ben, ça concerne bien plus que les tlb errors (émulateur, code, ebr, etc )
Sinon bravo cest tres recherche peut etre a passer en topic de qualite ?
C'est pas à moi d'en décider ça
Moi je réponds, merci d'être sur le site,
et de montrer que tu es plus fort que les autres.d'écrire des articlesPartager ses connaissances est de loin le meilleur moyen de les rendre utiles
Citer : Posté le 23/04/2015 12:12 | #
Personnellement, je n'en rencontrée qu'une
Moi je n'en rencontrée pas !
Coïncidence ? Je ne pense pas.
Citer : Posté le 23/04/2015 12:14 | #
Personnellement, je n'en rencontrée qu'une
Moi je n'en rencontrée pas !
Oh ça va hein !
Citer : Posté le 23/04/2015 12:16 | #
j'ai pas trop le temps de lire, je lirais ça pendant les vacances; ça promet d'être très intéressant
Citer : Posté le 23/04/2015 16:03 | #
Premièrement, il y a une faute. Soit il y a un problème orthographique, soit ta phrase ne contient pas de verbe conjugué. Au choix.
Deuxièmement, ce n'est pas très clair.
Qu'entends-tu par "code objet" ?
Peux-tu définir ce terme ?
Le considères-tu comme un sous-ensemble de l'ensemble "code" ou de l'ensemble "objet" ? Exclusif ou non ?
En résumé, ne crois-tu pas qu'il serait temps de faire preuve d'un peu de rigueur au moins une fois dans ta vie ?
Bon d'accord j'arrête de faire mon chiant.Bref, quand tu dis "fichier objet" tu parles du .o, c'est ça ?
Citer : Posté le 23/04/2015 17:04 | #
Alors, Positon, il faut savoir qu'en programmation les termes de « code objet » et « code source » relèvent du même ordre d'évidence que les termes « passé », « présent » et « futur » dans le cadre d'une situation temporelle. Donc je ne pense pas avoir spécialement besoin de les définir plus
Le fichier .o est un exemple de fichier censé contenir du code objet, ceci dit il y a de nombreux autres types tels que des fichiers .obj ou .robj.
J'ai corrigé la faute, merci.
Citer : Posté le 25/04/2015 09:50 | #
Très bon boulot ! Même si je savais une partie des ces infos, ça fait toujours du bien d'avoir un rappel approfondi
Je pense que ça sera utile pour les futurs afficionados du C !
Citer : Posté le 27/04/2015 20:10 | #
Très instructif, merci
Elle est définie où cette fonction : INIT_ADDIN_APPLICATION()
Citer : Posté le 28/04/2015 02:02 | #
Vraiment intéressant! Je me posais toujours des questions sur ce bout de code a la fin...
Y a-t il une explication pour "Can not delete internal file" , quand on doit faire "Rebuild all"?
Citer : Posté le 28/04/2015 10:39 | #
Vraiment intéressant! Je me posais toujours des questions sur ce bout de code a la fin...
Y a-t il une explication pour "Can not delete internal file" , quand on doit faire "Rebuild all"?
Ça m'est arrive mais jamais avec le SDK casio.
Souvent c'est parce que tu as un des fichiers ouvert ou qui bloque quand le compilo veux le supprimer
Citer : Posté le 15/05/2015 21:25 | #
Y a-t il une explication pour "Can not delete internal file" , quand on doit faire "Rebuild all"?
J'ai ajouté quelques informations sur ce message, purement lié au SDK lui-même
J'ai aussi ajouté quelques infos sur « Warning : the following dependant file does not exist ».
Citer : Posté le 15/05/2015 23:27 | #
Moi j'ai juste compris que Kucalc a fait n'importe quoi, et que le SDK fonctionne d'une façon étrange...
C'est exactement le genre d'article à montrer en disant "Tu vois, chez Planète Casio, on a des experts en la matière."
Ny Invité
Citer : Posté le 10/04/2016 05:43 | #
si votre code produit une exception quelconque, on va alle
zr à vbr + 0x100Mais à par ça, merci pour cet article
Citer : Posté le 10/04/2016 07:20 | #
Bien joué pour l'avoir trouvée ! C'est corrigé.