Eine kurze Zusammenfassung dessen, was Microsoft-Compiler für verschiedene Teile des nicht besessenen / nicht initialisierten Speichers verwenden, wenn sie für den Debug-Modus kompiliert werden (die Unterstützung kann je nach Compiler-Version 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.
It is 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 identify 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 aus einigen Notizen, die ich herumliegen habe - sie sind möglicherweise nicht 100% korrekt (oder kohärent).
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 reproducible). 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. dem "Slack" -Platz in std::string
der Zuweisung oder dem Puffer, an den übergeben wird fread()
. In diesen Fällen wird ein Wert mit dem Namen _SECURECRT_FILL_BUFFER_PATTERN
(definiert in crtdefs.h
) verwendet. Ich bin mir nicht sicher, wann es genau eingeführt wurde, aber es war in der Debug-Laufzeit von mindestens VS 2005 (VC ++ 8).
Anfangs 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 geändert 0xFE
. Ich nehme an, das liegt daran, dass es Situationen geben kann, in denen die Fülloperation über das Ende des Puffers hinaus ausgeführt wird, z. B. wenn der Aufrufer eine Puffergröße übergeben hat, die zu groß ist, um fread()
. In diesem Fall 0xFD
löst der Wert möglicherweise keine Erkennung dieses Überlaufs aus, da bei einer zu großen Puffergröße von nur eins der Füllwert dem Landwert des Niemands entspricht, der zum Initialisieren dieses Kanarienvogels verwendet wird. 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 Niemandslandkanarienvogel geändert wird, wodurch das Problem zur Laufzeit erkannt wird.
Wie andere angemerkt haben, besteht eine der Schlüsseleigenschaften dieser Werte darin, dass eine Zeigervariable mit einem dieser Werte, wenn die Referenzierung aufgehoben wird, zu einer Zugriffsverletzung führt, da in einer 32-Bit-Standardkonfiguration Benutzermodusadressen verwendet werden wird nicht höher als 0x7fffffff gehen.
Eine nette Eigenschaft des Füllwerts 0xCCCCCCCC ist, dass in der x86-Assembly der Opcode 0xCC der int3- Opcode ist, der der Software-Haltepunkt-Interrupt ist. Wenn Sie also jemals versuchen, Code in einem nicht initialisierten Speicher auszuführen, der mit diesem Füllwert gefüllt ist, erreichen Sie sofort einen Haltepunkt, und das Betriebssystem ermöglicht es Ihnen, einen Debugger anzuhängen (oder den Prozess abzubrechen).
quelle
int
Anweisung. Wenn Sie also 0xCD 0xCD ausführen, wird eine generiertint CD
, die ebenfalls abfängt .VirtualProtect()
odermprotect()
ausführbar machen.Visual Studio ist compiler- und betriebssystemspezifisch und setzt verschiedene Arten von Speicher auf unterschiedliche Werte, sodass Sie im Debugger leicht erkennen können, ob Sie in malloced Speicher, 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
quelle
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 mit 0xCC vorfüllt. Außerdem wird zwischen jedem Stapelrahmen 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):
Wenn Visual Studio Variablen nicht auf einen bekannten Wert vorinitialisiert hat, ist dieser Fehler möglicherweise schwer zu finden. Mit vorinitialisierten Variablen (oder besser gesagt vorinitialisiertem Stapelspeicher) ist das Problem bei jedem Lauf reproduzierbar.
Es gibt jedoch ein kleines Problem. Der von Visual Studio verwendete Wert ist TRUE - alles außer 0 wäre. Es ist tatsächlich sehr wahrscheinlich, dass, wenn Sie Ihren Code im Release-Modus ausführen, unitialisierte Variablen einem Stapelspeicher zugewiesen werden, der zufällig 0 enthält. Dies bedeutet, dass Sie einen Fehler in unitialisierten Variablen haben können, der sich nur im Release-Modus manifestiert.
Das hat mich geärgert, also habe ich ein Skript geschrieben , um den Vorfüllwert durch direktes Bearbeiten der Binärdatei zu ändern, 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 Heap-Pre-Fill experimentiert, obwohl es möglich sein sollte. Möglicherweise müssen Sie die Laufzeit-DLL bearbeiten, möglicherweise nicht.
quelle
Tatsächlich ist es fast immer eine Funktion der Laufzeitbibliothek (wie die C-Laufzeitbibliothek). Die Laufzeit ist normalerweise stark mit dem Compiler korreliert, es gibt jedoch 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 dem Malloc stammen, und kostenlose Implementierungen in der Debug-C-Laufzeitbibliothek. Es kann also auch eine Betriebssystemfunktion sein, aber meistens ist es nur die Sprachlaufzeitbibliothek.
Der Speicherverwaltungsteil von new und delete wird normalerweise mit malloc und free implementiert, sodass der mit new und delete zugewiesene Speicher normalerweise dieselben Funktionen aufweist.
Die Details sind laufzeitspezifisch. Die tatsächlich verwendeten Werte werden häufig so gewählt, dass sie nicht nur beim Betrachten eines Hex-Dumps ungewöhnlich und offensichtlich aussehen, sondern auch bestimmte Eigenschaften aufweisen, die die Funktionen des Prozessors nutzen können. Beispielsweise werden häufig ungerade Werte verwendet, da sie einen Ausrichtungsfehler verursachen können. Es werden große Werte verwendet (im Gegensatz zu 0), da sie überraschende Verzögerungen verursachen, wenn Sie eine Schleife zu einem nicht initialisierten Zähler durchführen. Unter x86 ist 0xCC eine
int 3
Anweisung. Wenn Sie also einen nicht initialisierten Speicher ausführen , wird er abgefangen .Dies hängt hauptsächlich von der verwendeten Laufzeitbibliothek ab.
Ich habe oben einige aufgelistet. 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, Traps, 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 schlechter Schreibvorgang (wie ein Pufferüberlauf) aufgetreten ist.
Beim Schreiben von Solid Code (und möglicherweise von Code Complete ) werden wichtige Aspekte bei der Auswahl von Füllmustern berücksichtigt. Ich habe einige davon hier erwähnt, und der Wikipedia-Artikel über Magic Number (Programmierung) fasst sie ebenfalls zusammen. Einige der Tricks hängen von den Besonderheiten des von Ihnen verwendeten Prozessors ab (z. B. ob ausgerichtete Lese- und Schreibvorgänge erforderlich sind und welche Werte Anweisungen zugeordnet werden, die abgefangen werden). Andere Tricks, wie die Verwendung großer Werte und ungewöhnlicher Werte, die in einem Speicherauszug auffallen, sind portabler.
quelle
Dieser Artikel beschreibt ungewöhnliche Speicherbitmuster und verschiedene Techniken, die Sie verwenden können, wenn Sie auf diese Werte stoßen.
quelle
Der offensichtliche Grund für das "Warum" ist, dass Sie eine Klasse wie diese haben:
Und dann instanziieren Sie eine a
Foo
und rufen anSomeFunction
, es wird eine Zugriffsverletzung geben, die versucht zu lesen0xCDCDCDCD
. Dies bedeutet, dass Sie vergessen haben, etwas zu initialisieren. Das ist das "Warum". Wenn nicht, hat sich der Zeiger möglicherweise mit einem anderen Speicher ausgerichtet, und das Debuggen ist schwieriger. Sie erfahren nur, warum 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 im Visual Studio-Compiler im Debug-Modus (im Gegensatz zur Veröffentlichung).
quelle
0x00000000
würden, die genauso nützlich wäre (oder mehr als eine schlechte Adresse). Wie ich in einem anderen Kommentar auf dieser Seite ausgeführt habe, besteht der wahre Grund für0xCD
(und0xCC
) darin, dass es sich um interpretierbare x86-Opcodes handelt, die einen Software-Interrupt auslösen. Dies ermöglicht eine ordnungsgemäße Wiederherstellung im Debugger in nur einem bestimmten und seltenen Fehlertyp nämlich wenn die CPU fälschlicherweise versucht, Bytes in einem Nicht-Code-Bereich auszuführen. Abgesehen von dieser funktionalen Verwendung sind Füllwerte nur Hinweise, wie Sie bemerken.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 dem Prozess während der Ausführung Debugger hinzufügen können.
Es ist nicht nur Speicher, viele Debugger setzen den Registerinhalt zu Beginn des Prozesses auf einen Sentinel-Wert (einige Versionen von AIX setzen einige Register, auf
0xdeadbeef
die leicht humorvoll ist).quelle
Der IBM XLC-Compiler verfügt über die Option "initauto", mit der automatischen Variablen ein von Ihnen angegebener Wert zugewiesen wird. Ich habe Folgendes für meine Debug-Builds verwendet:
-Wc,'initauto(deadbeef,word)'
Wenn ich mir die Speicherung einer nicht initialisierten Variablen ansehen würde, würde sie auf 0xdeadbeef gesetzt
quelle