Quand et pourquoi un système d’exploitation va-t-il initialiser la mémoire à 0xCD, 0xDD, etc. sur malloc / free / new / delete?

Je sais que le système d’exploitation initialise parfois la mémoire avec certains modèles tels que 0xCD et 0xDD. Ce que je veux savoir, c’est quand et pourquoi cela se produit.

Quand

Est-ce spécifique au compilateur utilisé?

Est-ce que malloc / new et free / delete fonctionnent de la même manière à cet égard?

Est-ce que la plate-forme est spécifique?

Cela se produira-t-il sur d’autres systèmes d’exploitation, tels que Linux ou VxWorks?

Pourquoi

D’après ce que je comprends, cela ne se produit que dans la configuration de débogage Win32, et il est utilisé pour détecter les dépassements de mémoire et pour aider le compilateur à intercepter des exceptions.

Pouvez-vous donner des exemples pratiques sur la manière dont cette initialisation est utile?

Je me souviens d’avoir lu quelque chose (peut-être dans Code Complete 2) qu’il est bon d’initialiser la mémoire à un motif connu lors de son allocation, et certains modèles déclencheront des interruptions dans Win32 qui entraîneront des exceptions dans le débogueur.

Comment est-ce portable?

    Un résumé rapide de ce que les compilateurs de Microsoft utilisent pour divers bits de mémoire non possédée / non initialisée lorsqu’ils sont compilés pour le mode de débogage (le support peut varier selon la version du compilateur):

    Value Name Description ------ -------- ------------------------- 0xCD Clean Memory Allocated memory via malloc or new but never written by the application. 0xDD Dead Memory Memory that has been released with delete or free. Used to detect writing through dangling pointers. 0xED or Aligned Fence 'No man's land' for aligned allocations. Using a 0xBD different value here than 0xFD allows the runtime to detect not only writing outside the allocation, but to also detect mixing alignment-specific allocation/deallocation routines with the regular ones. 0xFD Fence Memory Also known as "no mans land." This is used to wrap the allocated memory (surrounding it with a fence) and is used to detect indexing arrays out of bounds or other accesses (especially writes) past the end (or start) of an allocated block. 0xFD or Buffer slack Used to fill slack space in some memory buffers 0xFE (unused parts of `std::ssortingng` or the user buffer passed to `fread()`). 0xFD is used in VS 2005 (maybe some prior versions, too), 0xFE is used in VS 2008 and later. 0xCC When the code is comstackd with the /GZ option, uninitialized variables are automatically assigned to this value (at byte level). // the following magic values are done by the OS, not the C runtime: 0xAB (Allocated Block?) Memory allocated by LocalAlloc(). 0xBAADF00D Bad Food Memory allocated by LocalAlloc() with LMEM_FIXED,but not yet written to. 0xFEEEFEEE OS fill heap memory, which was marked for usage, but wasn't allocated by HeapAlloc() or LocalAlloc(). Or that memory just has been freed by HeapFree(). 

    Disclaimer: le tableau est tiré de quelques notes que j’ai en train de traîner – elles peuvent ne pas être 100% correctes (ou cohérentes).

    Beaucoup de ces valeurs sont définies dans vc / crt / src / dbgheap.c:

     /* * The following values are non-zero, constant, odd, large, and atypical * Non-zero values help find bugs assuming zero filled data. * Constant values are good so that memory filling is deterministic * (to help make bugs reproducable). Of course it is bad if * the constant filling of weird values masks a bug. * Mathematically odd numbers are good for finding bugs assuming a cleared * lower bit. * Large numbers (byte values at least) are less typical, and are good * at finding bad addresses. * Atypical values (ie not too often) are good since they typically * cause early detection in code. * For the case of no-man's land and free blocks, if you store to any * of these locations, the memory integrity checker will detect it. * * _bAlignLandFill has been changed from 0xBD to 0xED, to ensure that * 4 bytes of that (0xEDEDEDED) would give an inaccessible address under 3gb. */ static unsigned char _bNoMansLandFill = 0xFD; /* fill no-man's land with this */ static unsigned char _bAlignLandFill = 0xED; /* fill no-man's land for aligned routines */ static unsigned char _bDeadLandFill = 0xDD; /* fill free objects with this */ static unsigned char _bCleanLandFill = 0xCD; /* fill new objects with this */ 

    Il y a également quelques fois où le runtime de débogage remplit des tampons (ou des parties de tampons) avec une valeur connue, par exemple l’espace «slack» dans l’allocation std::ssortingng ou le tampon passé à fread() . Ces cas utilisent une valeur _SECURECRT_FILL_BUFFER_PATTERN (définie dans crtdefs.h ). Je ne sais pas exactement quand il a été introduit, mais il était au moins à la version de débogage de VS 2005 (VC ++ 8).

    Initialement, la valeur utilisée pour remplir ces tampons était 0xFD – la même valeur utilisée pour no man’s land. Cependant, dans VS 2008 (VC ++ 9), la valeur a été modifiée en 0xFE . Je suppose que c’est parce qu’il pourrait y avoir des situations où l’opération de remplissage serait exécutée au-delà de la fin du tampon, par exemple si l’appelant passait dans une taille de tampon trop grande pour être fread() . Dans ce cas, la valeur 0xFD pourrait ne pas déclencher la détection de ce dépassement, car si la taille du tampon était trop grande par une seule, la valeur de remplissage serait la même que celle utilisée pour initialiser ce code. Aucun changement dans le no man’s land signifie que le dépassement ne serait pas remarqué.

    La valeur de remplissage a donc été modifiée dans VS 2008 de sorte qu’un tel cas changerait le canari de no man’s land, entraînant la détection du problème par le runtime.

    Comme d’autres l’ont noté, l’une des propriétés clés de ces valeurs est qu’une variable de pointeur avec une de ces valeurs est déréférencée, cela entraînera une violation d’access, car dans une configuration Windows 32 bits standard, les adresses de mode utilisateur ne seront pas aller plus haut que 0x7fffffff.

    Une propriété intéressante concernant la valeur de remplissage 0xCCCCCCCC est que, dans l’assemblage x86, l’opcode 0xCC est l’opcode int3 , qui est l’interruption du point d’arrêt du logiciel. Donc, si vous essayez d’exécuter du code dans une mémoire non initialisée remplie avec cette valeur de remplissage, vous atteindrez immédiatement un point d’arrêt et le système d’exploitation vous permettra de joindre un débogueur (ou de tuer le processus).

    Il est spécifique au compilateur et au système d’exploitation, Visual Studio définit différents types de mémoire à différentes valeurs. Ainsi, dans le débogueur, vous pouvez facilement voir si vous avez dépassé la mémoire de Malloced, un tableau fixe ou un object non initialisé. Quelqu’un affichera les détails pendant que je les google …

    http://msdn.microsoft.com/en-us/library/974tc9t1.aspx

    Ce n’est pas le système d’exploitation – c’est le compilateur. Vous pouvez également modifier le comportement – voir le bas de ce billet.

    Microsoft Visual Studio génère (en mode débogage) un fichier binary qui pré-remplit la mémoire de stack avec 0xCC. Il insère également un espace entre chaque frame de stack afin de détecter les dépassements de tampon. Voici un exemple très simple d’utilité (Visual Studio détecte ce problème et émet un avertissement):

     ... bool error; // uninitialised value if(something) { error = true; } return error; 

    Si Visual Studio ne préinitialisait pas les variables à une valeur connue, ce bogue pourrait être difficile à trouver. Avec les variables préinitialisées (ou plutôt la mémoire de stack préinitialisée), le problème est reproductible à chaque exécution.

    Cependant, il y a un léger problème. La valeur utilisée par Visual Studio est TRUE – tout ce qui est sauf 0 serait. Il est en fait très probable que lorsque vous exécutez votre code en mode Release, les variables unitialisées peuvent être affectées à un morceau de mémoire contenant 0, ce qui signifie que vous pouvez avoir un bogue de variable unialisé qui ne se manifeste qu’en mode Release.

    Cela m’a ennuyé, j’ai donc écrit un script pour modifier la valeur de pré-remplissage en éditant directement le binary, ce qui m’a permis de trouver des problèmes de variables non initialisés qui n’apparaissent que lorsque la stack contient un zéro. Ce script ne modifie que le pré-remplissage de la stack; Je n’ai jamais expérimenté le pré-remplissage du tas, même si cela devrait être possible. Peut-être impliquer l’édition de la DLL d’exécution, peut-être pas.

    Est-ce spécifique au compilateur utilisé?

    En fait, c’est presque toujours une fonctionnalité de la bibliothèque d’exécution (comme la bibliothèque d’exécution C). Le runtime est généralement fortement corrélé avec le compilateur, mais vous pouvez échanger certaines combinaisons.

    Je pense que sous Windows, le tas de débogage (HeapAlloc, etc.) utilise également des motifs de remplissage spéciaux différents de ceux qui proviennent des implémentations malloc et free dans la bibliothèque d’exécution du débogage C. Donc, il peut également s’agir d’une fonctionnalité de système d’exploitation, mais la plupart du temps, il ne s’agit que de la bibliothèque d’exécution du langage.

    Est-ce que malloc / new et free / delete fonctionnent de la même manière à cet égard?

    La partie de gestion de la mémoire de new et delete est généralement implémentée avec malloc et free, donc la mémoire allouée avec new et delete a généralement les mêmes caractéristiques.

    Est-ce que la plate-forme est spécifique?

    Les détails sont spécifiques à l’exécution. Les valeurs réelles utilisées sont souvent choisies non seulement pour sembler inhabituelles et évidentes lorsque l’on regarde un vidage hexadécimal, mais elles sont conçues pour avoir certaines propriétés qui peuvent tirer parti des fonctionnalités du processeur. Par exemple, les valeurs impaires sont souvent utilisées, car elles peuvent provoquer une erreur d’alignement. Les grandes valeurs sont utilisées (par opposition à 0), car elles entraînent des retards surprenants si vous effectuez une boucle vers un compteur non initialisé. Sur x86, 0xCC est une instruction int 3 , donc si vous exécutez une mémoire non initialisée, cela va piéger.

    Cela se produira-t-il sur d’autres systèmes d’exploitation, tels que Linux ou VxWorks?

    Cela dépend principalement de la bibliothèque d’exécution que vous utilisez.

    Pouvez-vous donner des exemples pratiques sur la manière dont cette initialisation est utile?

    J’en ai énuméré ci-dessus. Les valeurs sont généralement choisies pour augmenter les chances que quelque chose d’inhabituel se produise si vous faites quelque chose avec des portions de mémoire non valides: délais importants, interruptions, erreurs d’alignement, etc. Si ces modèles changent jamais, il sait qu’il y a eu une mauvaise écriture (comme un dépassement de tampon) quelque part.

    Je me souviens d’avoir lu quelque chose (peut-être dans Code Complete 2) qu’il est bon d’initialiser la mémoire à un motif connu lors de son allocation, et certains modèles déclencheront des interruptions dans Win32 qui entraîneront des exceptions dans le débogueur.

    Comment est-ce portable?

    Ecrire un code solide (et peut-être un code complet ) parle de choses à considérer lors du choix des motifs de remplissage. J’en ai mentionné quelques-uns ici, et l’article de Wikipedia sur Magic Number (programmation) les résume également. Certaines des astuces dépendent des spécificités du processeur que vous utilisez (par exemple, si elles nécessitent des lectures et des écritures alignées et quelles valeurs correspondent aux instructions qui vont piéger). D’autres astuces, comme l’utilisation de valeurs importantes et de valeurs inhabituelles qui se détachent dans un vidage de mémoire, sont plus portables.

    Cet article décrit des modèles de bits de mémoire inhabituels et diverses techniques que vous pouvez utiliser si vous rencontrez ces valeurs.

    La raison évidente du “pourquoi” est que supposons que vous ayez une classe comme celle-ci:

     class Foo { public: void SomeFunction() { cout << _obj->value << endl; } private: SomeObject *_obj; } 

    Et puis vous instanciez un Foo et appelez SomeFunction , cela donnera une violation d'access en essayant de lire 0xCDCDCDCD . Cela signifie que vous avez oublié d'initialiser quelque chose. C'est la "pourquoi partie". Sinon, le pointeur pourrait s'être aligné sur une autre mémoire, et il serait plus difficile de le déboguer. C'est juste pour vous faire savoir la raison pour laquelle vous obtenez une violation d'access. Notez que cette affaire était assez simple, mais dans une classe plus importante, il est facile de faire cette erreur.

    AFAIK, cela ne fonctionne que sur le compilateur Visual Studio en mode débogage (par opposition à la version)

    C’est pour voir facilement que la mémoire a changé depuis sa valeur initiale, généralement pendant le débogage, mais parfois aussi pour le code de la version, car vous pouvez attacher des débogueurs au processus pendant son exécution.

    Ce n’est pas seulement de la mémoire non plus, de nombreux débogueurs définiront le contenu du registre sur une valeur sentinelle au démarrage du processus (certaines versions d’AIX 0xdeadbeef certains registres sur 0xdeadbeef ce qui est légèrement humoristique).

    Le compilateur IBM XLC a une option “initauto” qui atsortingbue aux variables automatiques une valeur que vous spécifiez. J’ai utilisé ce qui suit pour mes versions de débogage:

    -Wc,'initauto(deadbeef,word)'

    Si je regardais le stockage d’une variable non initialisée, elle serait définie sur 0xdeadbeef