web-dev-qa-db-de.com

Wann und warum initialisiert ein Betriebssystem den Speicher unter malloc / free / new / delete auf 0xCD, 0xDD usw.?

Ich weiß, dass das Betriebssystem manchmal Speicher mit bestimmten Mustern wie 0xCD und 0xDD initialisiert. Was ich wissen möchte, ist wann und warum das passiert.

Wann

Ist dies spezifisch für den verwendeten Compiler?

Funktionieren malloc/new und free/delete diesbezüglich in gleicher Weise?

Ist es plattformspezifisch?

Wird es auf anderen Betriebssystemen wie Linux oder VxWorks auftreten?

Warum

Meines Wissens tritt dies nur in der Win32-Debug-Konfiguration auf und wird verwendet, um Speicherüberläufe zu erkennen und dem Compiler zu helfen, Ausnahmen abzufangen.

Können Sie praktische Beispiele nennen, wie diese Initialisierung nützlich ist?

Ich erinnere mich, dass ich etwas gelesen habe (vielleicht in Code Complete 2), dass es gut ist, den Speicher bei der Zuweisung eines bekannten Musters zu initialisieren, und bestimmte Muster unterbrechen Win32, was zu Ausnahmen im Debugger führt.

Wie portabel ist das?

117

Eine kurze Zusammenfassung dessen, was die Compiler von Microsoft für verschiedene Bits des nicht besessenen/nicht initialisierten Speichers beim Kompilieren für den Debug-Modus verwenden (Unterstützung kann je nach Compilerversion variieren):

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::string` 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 compiled 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(). 

Haftungsausschluss: Die Tabelle stammt von einigen Notizen, die ich herumliegen habe - sie sind möglicherweise nicht 100% korrekt (oder nicht zusammenhängend).

Viele dieser Werte sind in vc/crt/src/dbgheap.c definiert:

/*
 * 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 (i.e. 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 */

Es gibt auch einige Male, in denen die Debug-Laufzeit Puffer (oder Teile von Puffern) mit einem bekannten Wert füllt, z. B. den Leerraum in der Zuweisung von std::string Oder den Puffer, der an fread(). In diesen Fällen wird ein Wert mit dem Namen _SECURECRT_FILL_BUFFER_PATTERN Verwendet (definiert in crtdefs.h). Ich bin nicht sicher, wann genau es eingeführt wurde, aber es war in der Debug-Laufzeit von mindestens VS 2005 (VC++ 8).

Anfänglich war der Wert, der zum Füllen dieser Puffer verwendet wurde, 0xFD - der gleiche Wert, der für Niemandsland verwendet wurde. In VS 2008 (VC++ 9) wurde der Wert jedoch in 0xFE Geändert. Ich gehe davon aus, dass es Situationen geben könnte, in denen die Fülloperation nach dem Ende des Puffers ausgeführt wird, zum Beispiel, wenn der Aufrufer eine Puffergröße übergeben hat, die zu groß für fread() ist. In diesem Fall löst der Wert 0xFD Möglicherweise nicht die Erkennung dieses Überlaufs aus, da der Füllwert mit dem Niemandslandwert übereinstimmt, der zum Initialisieren dieses Kanarienvogels verwendet wird, wenn die Puffergröße nur um eins zu groß ist. Keine Veränderung im Niemandsland bedeutet, dass der Überlauf nicht bemerkt wird.

Daher wurde der Füllwert in VS 2008 geändert, sodass in einem solchen Fall der Niemandsland-Kanarienvogel geändert und das Problem zur Laufzeit erkannt werden konnte.

Wie andere angemerkt haben, ist eine der Schlüsseleigenschaften dieser Werte, dass eine Zeigervariable mit einem dieser Werte dereferenziert wird. Dies führt zu einer Zugriffsverletzung, da bei einer 32-Bit-Windows-Standardkonfiguration Adressen im Benutzermodus nicht dereferenziert werden gehe höher als 0x7fffffff.

180
Michael Burr

Eine nette Eigenschaft über den Füllwert 0xCCCCCCCC ist, dass in x86-Assembly der Opcode 0xCC der int -Opcode ist, der der Software-Haltepunkt-Interrupt ist. Wenn Sie also jemals versuchen, Code in nicht initialisiertem Speicher auszuführen, der mit diesem Füllwert gefüllt ist, treffen Sie sofort einen Haltepunkt, und das Betriebssystem lässt Sie einen Debugger anhängen (oder den Prozess beenden).

33
Adam Rosenfield

Es ist compiler- und betriebssystemspezifisch. Visual Studio setzt verschiedene Arten von Speicher auf unterschiedliche Werte, sodass Sie im Debugger leicht sehen können, ob Sie in einen Speicher mit Speicherzuordnung, ein festes Array oder ein nicht initialisiertes Objekt übergegangen sind. Jemand wird die Details posten, während ich sie google ...

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

7
Martin Beckett

Es ist nicht das Betriebssystem - es ist der Compiler. Sie können das Verhalten auch ändern - siehe unten in diesem Beitrag.

Microsoft Visual Studio generiert (im Debug-Modus) eine Binärdatei, die den Stapelspeicher vorab mit 0xCC füllt. Außerdem wird zwischen jedem Stack-Frame ein Leerzeichen eingefügt, um Pufferüberläufe zu erkennen. Ein sehr einfaches Beispiel dafür, wo dies nützlich ist, finden Sie hier (in der Praxis würde Visual Studio dieses Problem erkennen und eine Warnung ausgeben):

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

Wenn Visual Studio die Variablen nicht auf einen bekannten Wert vorinitialisiert hat, ist dieser Fehler möglicherweise schwer zu finden. Bei vorinitialisierten Variablen (bzw. vorinitialisiertem Stapelspeicher) ist das Problem bei jedem Durchlauf reproduzierbar.

Es gibt jedoch ein kleines Problem. Der von Visual Studio verwendete Wert ist TRUE - alles außer 0 wäre TRUE. Es ist sehr wahrscheinlich, dass beim Ausführen Ihres Codes im Release-Modus unitialisierte Variablen einem Teil des Stapelspeichers zugewiesen werden, der zufällig 0 enthält. Dies bedeutet, dass Sie einen Fehler mit unitialisierten Variablen haben können, der sich nur im Release-Modus bemerkbar macht.

Das hat mich geärgert, also habe ich schrieb ein Skript den Vorfüllwert durch direktes Bearbeiten der Binärdatei geändert, sodass ich nicht initialisierte Variablenprobleme finden kann, die nur angezeigt werden, wenn der Stapel eine Null enthält. Dieses Skript ändert nur die Stapelvorfüllung. Ich habe nie mit dem Vorfüllen von Haufen experimentiert, obwohl es möglich sein sollte. Möglicherweise muss die Laufzeit-DLL nicht bearbeitet werden.

4
Airsource Ltd

Ist dies spezifisch für den verwendeten Compiler?

Eigentlich ist es fast immer ein Feature der Laufzeitbibliothek (wie die C-Laufzeitbibliothek). Die Laufzeit ist normalerweise stark mit dem Compiler korreliert, aber es gibt einige Kombinationen, die Sie austauschen können.

Ich glaube, unter Windows verwendet der Debug-Heap (HeapAlloc usw.) auch spezielle Füllmuster, die sich von denen unterscheiden, die aus den malloc- und freien Implementierungen in der Debug-C-Laufzeitbibliothek stammen. Es kann also auch eine Betriebssystemfunktion sein, aber meistens handelt es sich nur um die Sprachlaufzeitbibliothek.

Funktionieren malloc/new und free/delete diesbezüglich gleichermaßen?

Der Speicherverwaltungsabschnitt von new und delete wird normalerweise mit malloc und free implementiert, sodass der mit new und delete zugewiesene Speicher normalerweise die gleichen Funktionen aufweist.

Ist es plattformspezifisch?

Die Details sind laufzeitspezifisch. Die tatsächlich verwendeten Werte werden oft gewählt, um nicht nur ungewöhnlich und offensichtlich auszusehen, wenn ein Hex-Dump betrachtet wird, sondern um bestimmte Eigenschaften zu haben, die die Merkmale des Prozessors ausnutzen können. Beispielsweise werden häufig ungerade Werte verwendet, da sie einen Ausrichtungsfehler verursachen können. Große Werte werden verwendet (im Gegensatz zu 0), da sie überraschende Verzögerungen verursachen, wenn Sie eine Schleife zu einem nicht initialisierten Zähler durchführen. Auf x86 ist 0xCC ein int 3-Anweisung. Wenn Sie also einen nicht initialisierten Speicher ausführen, wird er abgefangen.

Wird es auf anderen Betriebssystemen wie Linux oder VxWorks auftreten?

Dies hängt hauptsächlich von der verwendeten Laufzeitbibliothek ab.

Können Sie praktische Beispiele nennen, wie diese Initialisierung nützlich ist?

Ich habe oben einige aufgeführt. Die Werte werden im Allgemeinen ausgewählt, um die Wahrscheinlichkeit zu erhöhen, dass etwas Ungewöhnliches passiert, wenn Sie etwas mit ungültigen Speicherbereichen tun: lange Verzögerungen, Überfüllungen, Ausrichtungsfehler usw. Heap-Manager verwenden manchmal auch spezielle Füllwerte für die Lücken zwischen Zuweisungen. Wenn sich diese Muster jemals ändern, weiß es, dass irgendwo ein fehlerhafter Schreibvorgang stattgefunden hat (z. B. ein Pufferüberlauf).

Ich erinnere mich, dass ich etwas gelesen habe (vielleicht in Code Complete 2), dass es sinnvoll ist, den Speicher bei der Zuweisung auf ein bekanntes Muster zu initialisieren, und bestimmte Muster in Win32 Interrupts auslösen, die dazu führen, dass im Debugger Ausnahmen angezeigt werden.

Wie portabel ist das?

Festen Code schreiben (und vielleicht Code vervollständigen ) spricht über Dinge, die berücksichtigt werden müssen bei der Auswahl von Füllmustern. Ich habe einige von ihnen hier erwähnt, und der Wikipedia-Artikel über Magic Number (programming) fasst sie auch zusammen. Einige der Tricks hängen von den Besonderheiten des verwendeten Prozessors ab (z. B. ob ausgerichtetes Lesen und Schreiben erforderlich ist und welche Werte Anweisungen zugeordnet werden, die abfangen). Andere Tricks, wie die Verwendung großer und ungewöhnlicher Werte in einem Speicherauszug, sind portabler.

3
Adrian McCarthy

Der offensichtliche Grund für das "Warum" ist, dass Sie eine Klasse wie diese haben:

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

private:
    SomeObject *_obj;
}

Und dann instanziiert man ein Foo und ruft SomeFunction auf, was zu einer Zugriffsverletzung führt, die versucht, 0xCDCDCDCD Zu lesen. Dies bedeutet, dass Sie vergessen haben, etwas zu initialisieren. Das ist das "Warum". Wenn dies nicht der Fall ist, hat der Zeiger möglicherweise eine Reihe mit einem anderen Speicher und das Debuggen ist schwieriger. Sie werden lediglich über den Grund informiert, aus dem Sie eine Zugriffsverletzung erhalten. Beachten Sie, dass dieser Fall ziemlich einfach war, aber in einer größeren Klasse ist es leicht, diesen Fehler zu machen.

AFAIK, dies funktioniert nur auf dem Visual Studio-Compiler im Debug-Modus (im Gegensatz zum Release)

2
FryGuy

Es ist leicht zu erkennen, dass sich der Speicher von seinem ursprünglichen Startwert geändert hat, im Allgemeinen während des Debuggens, manchmal aber auch für den Release-Code, da Sie Debugger an den Prozess anhängen können, während er ausgeführt wird.

Es ist auch nicht nur Speicher, sondern viele Debugger setzen den Registerinhalt beim Start des Prozesses auf einen Sentinel-Wert (einige Versionen von AIX setzen einige Register auf 0xdeadbeef was leicht humorvoll ist).

2
paxdiablo

Dieser Artikel beschreibt ngewöhnliche Speicherbitmuster und verschiedene Techniken, die Sie verwenden können, wenn Sie auf diese Werte stoßen.

2
Stephen Kellett

Der IBM XLC-Compiler verfügt über eine "initauto" -Option, die automatischen Variablen einen von Ihnen angegebenen Wert zuweist. Für meine Debug-Builds habe ich Folgendes verwendet:

-Wc,'initauto(deadbeef,Word)'

Wenn ich mir die Speicherung einer nicht initialisierten Variablen anschaue, wird sie auf 0xdeadbeef gesetzt

1
Anthony Giorgio