Je n'avais pas une grande expérience du C++ pendant les années où j'ai travaillé sur mon projet d'OS, mais j'avais suivi avec intérêt les investigations de DasCandy pour parvenir à faire fonctionner les objets globaux (cf. http://wiki.osdev.org/C_PlusPlus). Et j'en avais retenu une chose : n'utiliser ces choses qu'en cas d'urgence, et avec la prudence qui s'impose quand on manipule de la nitro.
D'un autre côté, pour pouvoir définir le comportement de mon appleman, il me faut beaucoup plus de contrôleurs, qui sont appelés en séquence pour définir le comportement réel pour un état. Au moment de la lecture du script qui définit le niveau, je vais donc traiter une série de lignes "using tracker(Bilou)" "using Momentum(5,-1)" "using walker()", chacune de ces lignes donnant le nom d'une classe de contrôleur à instancier. J'ai donc pour chaque classe dérivée de iGobController une classe dérivée de iGobFactory capable de construire le bon objet à partir de la chaîne de caractères entre parenthèses.
So i've started decomposing the former "big chunk" controller such as 'walk' and 'gravity' into 'micro-gob-controllers' and make sure they are published in the std::map used by GameScript to prepare the GobState objects according to the rules found in the script, such as "using tracker(Bilou) ; using Momentum(5,1)", "using walker". The corresponding output is a chain of controllers that each do a specific job, easing code reuse. The BerryBat and the Appleman can for instance share the same "tracker" controller that tell them where Bilou stands, while Bilou and the appleman share "Momentum" and "walker" controllers, but Bilou receives its input from "dpad" rather than from "tracker".
Bon. Mais il faut que ces factories -- qui sont du coup des singletons -- soient enregistrées dans une map pour que le script les retrouve (c'est pas du Java: exit le 'Class.forname("tracker")'). Et je voulais pouvoir ajouter encore et encore des contrôleurs à mon code sans devoir maintenir une grosse fonction du genre "register_all_controllers". Cyril proposait donc évidemment des objets globaux (qu'on appelle parfois aussi "objets statiques"), mais je n'étais pas chaud: avec eux, il y a toujours anguille sous roche ...
Comme je n'avais pas d'argument vraiment convaincant, j'ai essayé quand-même. Première surprise : les "printf" ne marchent plus de manière fiable! J'ai encore quelques messages imprimés quand j'utilise "printf" sans aucun argument, mais c'est tout... un problème que j'avais déjà rencontré.
Un peu de désassemblage, et je constate que les messages qui sont effectivement imprimés n'utilisent pas véritablement printf: ils ont été convertis en interne en puts. Conclusion: en appelant printf dans l'initialisation des objets globaux, qui a lieu avant ConsoleInit(), j'ai empêché l'initialisation correcte de la console et rien ne va plus.
I stumbled upon two issues while implementing those factories -- which i initially chose to build with static objects. The first problem is a "known issue" of my libnds version: if i ever invoke a printf() function before consoleInit(), i screw up the stdlib's internal state and all subsequent prints using this function won't output a single pixel >_<.
Je change d'approche: initialisation "en aveugle", et une petite méthode "test" indépendante, histoire de vérifier que mes contrôleurs sont bien présents, appelée juste avant la boucle principale du programme. Deuxième surprise: il y a bien des contrôleurs enregistrés mais pas tous. Pourtant, mon code est bien compilé, tel que je l'ai écrit. Et il avait été appelé, puisqu'il était capable de foutre en l'air l'affichage...
La map est une variable de classe, statique, initialement vide. "As plain as can be" ...
... statique ? ...
Eh là, une minute ... Et comment le compilateur sait-il que parmi ces objets statiques, la bonne initialisation des GobFactories dépend du fait que la map ait déjà été initialisée ? Bin il ne le sait pas, évidemment. Le code d'initialisation appelle tous les constructeurs dans un ordre qui lui convient, et si ça ne me plaît pas, c'est tant pis pour ma faute.
Once this was fixed, i realised that i was f00lish to use static objects to register things in a static map. When i say "static", here, i actually mean "some object defined at the top level that should be instanciated before main() is ever called". The problem with such object is that their initialization order is undefined. In other words, my factory F could very well inserts itself in the map and then the map itself initializes (and thus clears what F modified).
Retour temporaire à "register_all_controllers" pendant que j'évalue les options... Voilà qui donne un nouvel éclairage au dicton "In C++ it's harder to shoot yourself in the foot, but when you do, you blow off your whole leg." (Stroustrup himself ;)
Showing posts with label libnds. Show all posts
Showing posts with label libnds. Show all posts
Sunday, August 23, 2009
Dans le Pied++
Tags: coding, cxx, iGobController, libnds
Thursday, August 16, 2007
ARM7 et ARM9 sont sur un bateau ... ARM9 tombe à l'eau. Qui reset?
here: br[here]
I'm trying to make sense of the "execute .nds file" libraries ... Somewhere in an old version of the DSWiki, i found this piece of code (the trick only works with GBamp cartridge, afaik):
REG_IME = IME_DISABLE; // Disable interrupts
WAIT_CR |= (0x8080); // ARM7 has access to GBA cart
*((vu32*)0x027FFFFC) = fileCluster; // Start cluster of NDS to load
*((vu32*)0x027FFE04) = (u32)0xE59FF018; // ldr pc, 0x027FFE24
*((vu32*)0x027FFE24) = (u32)0x027FFE04; // Set ARM9 Loop address
swiSoftReset(); // Reset
which suggests that the software reset will branch the code on 0x27FFE04 at some point, and that the file to be loaded is found at 0x27FFFFC (its first cluster in the FAT flashsystem, at least, which suggests much reverse-engineering of the GBAMP firmware, imho).
J'essaie de comprendre un peu les mécanismes mis en jeu lors du redémarrage de la nintendo DS, et en particulier comment s'y prendre pour lui faire démarrer un fichier .nds précis (lancer le Sprite Editor depuis un programme "menu", par exemple). Une des propositions dans ce sens venait de DSwiki (cf supra), mais ne fonctionne apparemment que pour l'adaptateur GBAmp. Après le reset software, le processeur finit par se placer en 27FFE04, et le mot mémoire @27FFFFC contient le numéro du premier cluster du fichier à démarrer (l'adresse sur le disque, pour ceux qui n'ont pas passé 3 ans à décortiquer le FAT32 ;)
Bien sûr, il est nécessaire d'obtenir la coopération du 2eme processeur, le ARM7 (code ci-dessous), ce qui nous donne à contempler le genre de "magie noire" mise en oeuvre pour synchroniser les deux processeurs de la DS: périodiquement, le ARM7 va inspecter la mémoire (partagée) à l'adresse 27FFE24 (le "pointeur magique"), et si il y trouve l'adresse prévue pour la "boucle Passme", il va déclencher son propre reset.
The trick of course only works if the ARM7 side co-operates, which gives us an idea of how the dual-cpu-with-shared-memory can turn simple things in obscure wizardry:
if (*((vu32*)0x027FFE24) == (u32)0x027FFE04) // Check for ARM9 reset
{
REG_IME = IME_DISABLE; // Disable interrupts
REG_IF = REG_IF; // Acknowledge interrupt
*((vu32*)0x027FFE34) = (u32)0x08000000; // Bootloader start address
swiSoftReset(); // Jump to boot loader
}
In other words, the ARM7 cpu will periodically check whether the "reboot" has been invoked on the ARM9 cpu (in which case, it is trapped in the so-called "passme loop" that consists of a "MAGIC_HERE: jump [MAGIC_POINTER]" instruction where the memory at address MAGIC_POINTER contains the address of the jump instruction, i.e. MAGIC_HERE.
'MAGIC_POINTER (0x27FFE24)' is actually something that is nintendo-firmware related and this is where swiSoftReset() will send us back on the ARM9 cpu. On the ARM7, swiSoftReset() will do the same, but using another magic pointer at 0x27FFE34, which we reprogrammed with the "cartridge entry point" address (0x8000000). Voilà. that should do the trick: ARM9 resets and enters a loop, ARM7 detects it and resets as well. ARM7 then execute the cartridge's initialization as well (which will take care of taking ARM9 out of that loop, hopefully).
Après un petit tour sur "gba&ds tek", il apparaît que le service "softreset" du BIOS de la DS se sert du contenu de 0x027FFE24 (notre "pointeur magique") et de 0x027FFE34 respectivement pour savoir où reprendre le code après la réinitialisation. Pendant que l'ARM9 tourne en rond (le code sur lequel on l'a envoyer lit en permanence le contenu du 'pointeur magique' et s'y rebranche: c'est ça notre "boucle passme" et il suffira à l'ARM7 de modifier le contenu du "pointeur magique" pour débloquer l'ARM9 et de l'envoyer vers le programme de son choix), l'ARM7 relance pour sa part l'exécution vers le point d'entrée de la cartouche en ROM, et l'on devrait se retrouver à nouveau dans la partie du firmware GBAmp qui lance un fichier... sauf qu'il ne s'agira plus de son propre menu, cette fois!
stubs join the game
Now, if we look at the "run .nds file" code in rebootlib or in grizzlyadams' DSchannels, it works a bit differently. The new thing here is the presence of stubs, chunks of code that will help the transition from old software to the new one. The stub will typically take care of filling the RAM with the new program while trying to avoid being destroyed. Some of them simply live on the ARM7 and will thus only support programs that do not use a "custom" loop (basically denying execution of anything that is WiFi-related :-/)
Jetons à présent un oeil au code "executer(char* ndsfilename)" de la rebootlib de lick ou du DSchannels de grizzlyadams. Tout d'abord, ce qui est nouveau ici, ce sont les stubs, des petits bouts de programme qui vont assurer temporairement le chargement entre le programme appelant et le programme appelé. Un stub se charge généralement de préparer l'environnement du nouveau programme et de l'amener en mémoire en prenant soin de ne pas se supprimer lui-même. Dans le plus simple des cas, il s'agira simplement d'un exécutable pour le ARM7 un peu particulier qui se comporte comme le programme-modèle une fois le chargement effectué. L'inconvénient de cette technique, c'est évidemment que les programmes qui ont un "coté 7" spécifique (et notamment faisant appel au WiFi, etc) échoueront lamentablement :-/
In DS channels, grizzlyadams rather use the video memory to store the stub. This of course require special rules for building the stub (as the memory layout of the produced binary has nothing to do with a regular DS environment)
/*
We assume, that VRAM is set up as follows:
- VRAM_A is mapped to 0x6000000 for text screen
- VRAM_B is mapped to 0x6820000 for passed vars
- VRAM_C is mapped to ARM7 WRAM for arm7 stub
- VRAM_D is mapped to 0x6860000 for arm9 stub
*/
(*(vu32*)0x027FFFF8) = 0x06000000; // ARM7 Entry
(*(vu32*)0x027FFE24) = 0x06860000; // ARM9 Entry
Nothing like a "passme loop" here (see specs/ds_arm9vram_crt0.s, the assembly file that contains the initialization code for the stub) the ARM9 prepares the environment and enters "main()". Once the stub has loaded your requested .nds file (optionnally applying DLDI patching), the 'Execute()' function will play hide-and-seek with the ARM7, which is waiting for instructions through the FIFO hardware.
Le stub de DSchannel est un peu particulier en ce sens qu'il va squatter la mémoire vidéo pendant son exécution. Et celle-ci est tellement vaste par rapport à ce qui est nécessaire pour quelques messages texte que cela passera complètement inaperçu pour l'utilisateur. Pourtant, aussi bien le stub7, le stub9 que les arguments qu'ils reçoive (p.ex. le nom du fichier à lancer ;) tiennent dedans ^_^.
C'est le stub9 qui entre en jeu le premier, suite au 'soft reset'. le code d'initialisation (specs/ds_arm9vram_crt0.s) prépare à nouveau les cache, mais ne contient pas l'ombre d'une "boucle passme". Il démarre la fonction main() du stub qui à son tour, va charger l'exécutable NDS en mémoire (ajustant les drivers DLDI au passage, si nécessaire) avant de commencer à piloter l'ARM7 à travers quelques commandes via le FIFO hardware.
Celà signifie donc qu'au moins une commande 'EXEC' (qui ne fait rien de plus que de provoquer un reboot de l'ARM7 vers une adresse indiquée à l'avance par l'ARM9: celle du stub7) avant de faire une petite partie de 'cache-cache' entre les commandes FIFO et les valeurs de retour annoncées par une variable partagée ("synchro" à l'adresse 0x027FFFF8) jusqu'à ce que les deux moitiés de l'exécutable nds soient en place.
Another original thing here is that the original ARM7 code is still running while the ARM9 is already executing its own stub. Things will only change with the 'EXEC' command sent through the FIFO.
/* in stub_main9.c */The ARM7 stub and the ARM9 stub then start playing hide-and-seek again, exchanging more commands through the FIFO and using a special "synchro" shared variable (0x27FFFF8) to report each other's progression.if(DebugEnabled) { a7(); iprintf("Booting...\n"); }
FIFO_Send(IPC_CMD_EXEC, 0);
/* in myapp_main7.c */
void IPC_Exec()
{
swiWaitForVBlank();
*(vu16*)(0x04000208) = 0; //REG_IME = IME_DISABLE;
*((vu32*)0x027FFE34) = *((vu32*)0x027FFFF8);
asm("swi 0x00"); //swiSoftReset();
asm("bx lr");
}
Subscribe to:
Posts (Atom)

Vote for your favourite post
