|
|||||||||||||||||||||||||||||||||||||||||||||||||||
|
Note : traduction par Olivier Fabre. Cet article est la traduction directe d'un chapitre de la documentation de MuGuardianAngel, l'utilitaire de débogage écrit par Thomas Richter. Merci à lui pour cette documentation très complète et son autorisation de traduction et publication sur Guru-Meditation.net. Certaines parties de cette documentation sont d'un niveau relativement élevé et/ou ne concernent que des usages spécifiques, donc si vous vous sentez un peu perdu, n'hésitez pas à passer à la suite de l'article ! Gestion correcte de la mémoire : éviter les "hits" Si vous vous demandez comment les hits (écriture illégale en mémoire) MuGuardianAngel peuvent être évités, voici les règles à suivre pour allouer et libérer la mémoire correctement. Les règles qui suivent s'appliquent à tous les programmes qui sont supposés tourner d'une façon respectueuse du système. Je ne les ai pas inventées. Ce que vous trouverez ici est plus ou moins une copie des règles situées dans le ROM Kernel Reference Manual, la documentation développeur officielle de l'Amiga. Enfreindre ces règles conduira à des programmes instables, que ce soit avec ou sans utilitaires mémoire additionnels. Un programme qui crée des hits lorsque MuGuardianAngel tourne, mais fonctionne correctement sinon, est quoi qu'il en soit instable et pourrait planter dans certaines situations. Allocations mémoire Le drapeau MEMF_PUBLIC Positionnez l'attribut MEMF_PUBLIC (exec/memory.h). C'est généralement nécessaire ! Ne pas positionner cet attribut entraîne l'allocation de mémoire qui :
Par exemple, VMM requiert une utilisation correcte de cet attribut [NDT : toute mémoire allouée avec le drapeau MEMF_PUBLIC ne sera jamais déplacée sur le disque]. Toute mémoire supposée contenir des structures du système d'exploitation doit être allouée avec le drapeau MEMF_PUBLIC, de même que toute mémoire devant être lue par d'autres tâches, interruptions, exceptions, tampons d'entrées/sorties. Les seules exceptions sont les structures privées qui ne sont lues ou écrites que par votre tâche, qui ne sont jamais passées ni lues ou écrites par d'autres processus ou fonctions du système d'exploitation, et ne sont pas lues lorsque le multitâche est désactivé. Vidages de la mémoire Soyez prêts à ce qu'une allocation mémoire puisse vider les bibliothèques, polices de caractères et périphériques logiques inutilisés de la mémoire. En particulier, n'utilisez pas de ressources fermées. Utiliser "FindName()" sur une liste de ressources d'exec n'est pas suffisant pour utiliser une ressource. Si vous ne voulez pas que des ressources soient vidées de la mémoire, positionnez le drapeau MEMF_NO_EXPUNGE lors de l'allocation mémoire. Cf. exec/memory.h. Voici un moyen sûr de vider la mémoire :
(c'est comme ceci que "Avail flush" vide la mémoire). MuGuardianAngel ne signalera jamais un vidage de mémoire comme étant une allocation mémoire échouée, contrairement à MungWall. La mémoire et les coprocesseurs de l'Amiga La mémoire devant être lue par les coprocesseurs doit être allouée avec le drapeau MEMF_CHIP, sinon les coprocesseurs ne pourront pas y accéder. Ceci est valable pour :
Ne faites aucune supposition quant à l'ordre dans lequel vous obtenez de la mémoire. La deuxième allocation n'est pas forcément située à une adresse plus élevée ! L'attribut MEMF_FAST N'utilisez pas l'attribut MEMF_FAST inutilement si de la mémoire Chip peut être OK pour vous également. Le système d'exploitation est suffisamment intelligent pour allouer de la mémoire Fast s'il y en a de disponible. Il se repliera sur la mémoire Chip s'il n'y a plus de mémoire Fast. Il n'y a en général aucune raison de demander explicitement de la mémoire Fast. Alignement Toute mémoire allouée avec AllocMem() est forcément alignée sur un multiple de deux mots longs [NDT : 8 octets], c'est-à-dire que les bits 0 à 2 de l'adresse seront toujours à zéro. Si vous avez besoin d'un alignement plus important, voyez l'astuce ci-dessous. La taille des tampons Faites bien attention à toujours allouer assez de mémoire pour le pire cas possible. En langage C, une chaîne nécessite n+1 octets de mémoire pour contenir une chaîne de longueur n. Certaines fonctions du système d'exploitation nécessitent, à cause de bogues, un tampon mémoire légèrement plus grand que ce qu'il pourrait vous sembler ; contrôlez la section "Bugs" des Autodocs (ce bogue affecte souvent les fonctions DOS, mais également certaines fonctions d'Intuition). MuGuardianAngel est capable de détecter *certains* accès hors limites à la mémoire, mais en général, seulement lors de la libération de cette mémoire. Attributs mémoire Ne positionnez aucun attribut non documenté pour les attributs mémoire d'AllocMem(). Ils *pourraient* être ignorés sous cette version du système d'exploitation, mais ne le seront probablement pas dès la prochaine version. Regardez le fichier exec/memory.h pour y trouver les drapeaux valides. Dans la version actuelle (V40), les drapeaux suivants sont définis :
Contenu de la mémoire N'émettez aucune hypothèse quant au contenu d'une zone de mémoire à moins que vous n'ayez spécifié l'attribut MEMF_CLEAR pour l'effacer. Ne pas positionner cet attribut rend l'allocation un peu plus rapide, mais dans ce cas, le bloc de mémoire peut contenir tout et n'importe quoi. MuGuardianAngel tente d'être un peu méchant avec les programmes qui n'allouent pas la mémoire avec MEMF_CLEAR : au lieu de laisser la mémoire telle quelle, il la remplit avec un motif spécial non NULL. Code auto-modifiant L'usage de code auto-modifiant n'est pas conseillé. Si vous devez absolument jouer avec ça et ne pouvez pas faire autrement, utilisez l'appel système suivant pour vider les caches processeur une fois que votre code est placé en mémoire et doit s'exécuter :
Ne vous attendez pas à ce qu'il soit en mémoire avant que vous appeliez cette routine. C'est encore plus important pour les routines comme les interruptions qui sont appelées de manière asynchrone. Échecs Soyez prêts à ce que votre requête de mémoire échoue. Un test explicite est requis après un appel à AllocMem() [NDT : ainsi qu'après toutes les autres fonctions d'allocation mémoire, bien sûr]. Se contenter de laisser le Guru apparaître dans le cas d'une allocation échouée n'est pas suffisant. Affichez un message d'alerte, quittez votre programme proprement, vérifiez votre code ! Pour ceux qui programment en assembleur : non, il n'est pas documenté qu'AllocMem() positionne le bit zéro si l'allocation a échoué. Vous devez faire le test vous-même. Si vous l'appelez depuis un Process, les versions 37 et supérieures du système d'exploitation garantissent un code de retour de IoErr() égal à ERROR_NO_FREE_STORE (=103L). MuGuardianAngel montrera les allocations mémoire échouées si l'option SHOWFAIL est activée. De plus, il essaye d'être méchant avec les programmes et change volontairement les registres d1/a0-a1 ou d0-d1/a0-a1 (aussi appelés "scratch registers") avant de quitter les routines de gestion de mémoire. Il positionnera aussi le drapeau Z pour tromper les programmes assembleur qui ne testent pas d0 correctement. AllocMem() et les changements de contexte Ni AllocMem() ni FreeMem() ne "cassent" un état "Forbid" [NDT : c'est-à-dire que si le multitâche est coupé, ces fonctions ne le restaureront pas]. C'est important car c'est la seule façon "d'afficher" une liste dont l'accès est protégé par Forbid() à l'aide de la dos.library et d'autres fonctions. La séquence suivante permet de faire ceci légalement :
AllocMem(), FreeMem(), AllocAbs() et les interruptions Aucune de ces fonctions ne peut être appelée depuis une interruption ou en mode superviseur. Souvenez-vous, cependant, que les gestionnaires d'entrée ("input handlers") de l'input.device ne tournent pas sous interruptions mais dans le contexte de la tâche de l'input.device, bien qu'ils soient construits par-dessus une structure d'interruption. Par conséquent, appeler AllocMem() dans ce cas pour faire une copie d'un événement d'entrée est légal. Libération de mémoire Taille des désallocations La taille d'une désallocation doit correspondre exactement à la taille de l'allocation. Il n'est pas autorisé de :
MuGuardianAngel va détecter un hit si un programme essaye de libérer un bloc de mémoire d'une taille différente de celle avec laquelle il a été alloué. De plus, il détectera un hit si un programme essaye de libérer de la mémoire "non alignée", c'est-à-dire un bloc de mémoire dont l'adresse n'est pas multiple de 8 octets. Cependant, les versions 37 et inférieures de la layers.library ne respectent pas cette règle (beurk !) ; cette bibliothèque est donc explicitement exclue de la gestion mémoire de MuGuardianAngel. Elle libère, malheureusement, des blocs de mémoire partiels. Accès à la mémoire désallouée Ne touchez pas à la mémoire désallouée. Si elle a été libérée, elle a été libérée et vous n'avez plus le droit de l'utiliser, d'y faire référence, de la lire ni d'y écrire. Une autre tâche pourrait la vouloir. MuGuardianAngel marquera la mémoire libérée comme indisponible dès que possible. Si vous essayez d'y accéder, un hit se produira. Une exception qui n'est pas formulée dans les RKRM mais est malheureusement largement utilisée : la désallocation de mémoire à l'intérieur d'une paire Forbid()/Permit(). La mémoire, sauf les huit premiers octets qui sont utilisés pour l'administration, reste non modifiée et prête à être utilisée tant que le multitâche est désactivé. Exécuter un Wait(), directement ou indirectement, annulera l'état "Forbid()" et rendra donc la mémoire inutilisable. Soyez prévenus ! Bien que cet accès soit en quelque sorte légal, et donc toléré par MuGuardianAngel, ceci est tout de même "laid" selon moi et par conséquent fortement déconseillé. Une des très rares exceptions où ce comportement pourrait être utile se trouve dans le bout de code suivant, qui "décharge" le segment d'un programme "qui se charge et reste résident" :
Notez que vous devez absolument être certain que le segment n'est pas un segment "overlay" car UnloadSeg() annulera l'état "Forbid()" dans ce cas. Cependant, ceci ne fonctionne pas pour les programmes qui se chargent et restent résidents de toute façon. MuGuardianAngel garantit actuellement que la mémoire n'est pas rendue indisponible si elle est libérée lorsque le multitâche est coupé (état "Forbid()"). Elle est rendue indisponible dès que le multitâche est réinstauré. Contrairement à MungWall, MuGuardianAngel modifie cette mémoire et contrôle son intégrité dès qu'il en a le droit. Il testera même la mémoire deux fois : dès que FreeMem() est appelé, et une autre fois lorsque le multitâche est restauré. Mémoire Chip et accès par le Blitter La logique du Blitter utilise le DMA (Direct Memory Access = Accès direct à la mémoire) et accède à la mémoire Chip indépendamment du processeur. Si vous utilisez un tampon mémoire temporaire pour le Blitter, prenez garde à ce que le Blitter n'y accède plus avant de le désallouer. Pour en être sûr, appelez WaitBlit() avant de libérer de la mémoire qui a été utilisée comme tampon mémoire pour le Blitter. MuGuardianAngel ne peut malheureusement pas détecter les accès illégaux à la mémoire Chip par le Blitter, car ceci est hors de contrôle de la MMU. La mémoire et les accès DMA matériels Les contrôleurs de disque dur modernes peuvent accéder à la mémoire par DMA, parallèlement au processeur. Si vous avez l'intention d'utiliser ce DMA matériel directement parce que vous écrivez un pilote de périphérique pour ce matériel, préparez-vous à vider les caches du processeur correctement. En particulier, appelez CachePreDMA(...) avant l'opération DMA et CachePostDMA(...) ensuite. Consultez les Autodocs pour les détails de ces fonctions et leurs paramètres. Lisez-les, puis réfléchissez-y, puis lisez-les encore. C'est important que vous les compreniez correctement ! Valeur de retour FreeMem() ne retourne aucune valeur utile, ni ne positionne de code de condition. Comme toujours, MuGuardianAngel essayera d'être méchant avec les programmes et modifiera les registres "perdus" et les codes de condition. AllocAbs() et autres curiosités AllocAbs est destiné à une utilisation spéciale consistant à allouer de la mémoire à un emplacement prédéfini. Ne l'utilisez pas sans de bonnes raisons. Portée de la mémoire allouée AllocAbs() arrondi les adresses. Préparez-vous à ce que le bloc de mémoire que vous obtenez ne soit pas identique à celui que vous avez demandé. Cependant, si l'allocation est réussie, il est garanti que le bloc de mémoire demandé se trouve dans le bloc de mémoire retourné. Attendez-vous à ce que votre requête ne puisse pas être satisfaite parce que la mémoire requise est déjà utilisée par une autre tâche. Dans ce cas, AllocAbs() retourne NULL. Vous devez tester cela explicitement ! Aucun code de condition n'est positionné. AllocAbs() ne positionnera pas le code ERROR_NO_FREE_STORE de IoErr(). MuGuardianAngel, comme toujours, remplira les registres perdus ("scratch registers") avec des valeurs insignifiantes et positionnera le drapeau Z pour amener les programmes mal écrits à une condition d'échec. Contenu de la mémoire allouée N'émettez aucune hypothèse quant au contenu du bloc de mémoire alloué. Le système d'exploitation utilise des parties des blocs de mémoire libres pour des raisons d'administration et pourrait avoir écrit dans des parties de blocs de mémoire. Ceci signifie plus particulièrement pour les programmes résistant à la réinitialisation, dont la mémoire est allouée de cette façon par le mécanisme de KickMemPtr d'exec, que les huit premiers octets seront corrompus. Pensez-y ! MuGuardianAngel corrompra la mémoire allouée volontairement, sauf celle allouée par AllocAbs(). Libération de la mémoire allouée avec AllocAbs() Pour être sûr que la mémoire allouée est réellement libérée complètement, appelez FreeMem() avec l'adresse et la taille de la mémoire que vous avez demandée, pas avec la valeur de retour d'AllocAbs(). Ceci peut paraître étrange, mais la logique de FreeMem() applique les mêmes arrondis de taille et d'adresse qu'AllocAbs(). Si vous passez néanmoins une adresse différente, comme la valeur de retour au lieu de l'adresse demandée, il n'est pas garanti que toute la mémoire sera libérée. Un petit exemple pourrait être utile (en utilisant l'algorithme d'arrondi actuel) :
...alloue 16 octets et retourne 0x300000. Appeler ensuite :
...ne libérera que huit octets en partant de 0x300000 au lieu de 16. Par contre :
...fonctionnera comme souhaité. Cependant, MuGuardianAngel détectera ce dernier FreeMem() comme mal aligné. Il faut ignorer le hit dans ce cas. Utiliser AllocAbs() pour allouer de la mémoire alignée La routine suivante est une astuce pour allouer de la mémoire alignée :
Appelez cette routine avec "alignment" valant 16 pour aligner la mémoire sur une frontière de 16 octets. Il ne faut pas fournir une valeur qui ne soit pas une puissance de 2. Notez que la mémoire est mise à zéro "à la main" si MEMF_CLEAR fait partie des attributs. Ceci doit être fait car AllocAbs() ne garantit pas le contenu de la mémoire, même si l'AllocMem() précédent a déjà initialisé la mémoire.
--------------------
Voilà, tout programme se conformant à ces règles ne causera aucun problème avec MuGuardianAngel !Outils de débogage Si vous écrivez un débogueur et que vous devez absolument lire de la mémoire non allouée, allez en mode superviseur pour la lire. MuGuardianAngel ne détectera que les accès à la mémoire "libre" en mode utilisateur. Les outils de débogage suivants sont recommandés :
D'autres particularités pour les amateurs éclairés. (-: Ce qui suit est une liste de caractéristiques du système d'exploitation dont vous devriez être conscient si vous avez l'intention d'écrire votre propre pool mémoire. Je les ai trouvés en écrivant PoolMem, donc les voici pour votre information. Cependant, n'utilisez pas ces techniques dans votre propre code. Bien que les règles ci-dessus aient été mises en place pour les développeurs, ça ne veut pas dire que le système d'exploitation les respecte ("Quod licet Iovi non licet bovi"). J'ai trouvé les caractéristiques suivantes du système d'exploitation :
|