Kiedy i dlaczego kompilator zainicjalizuje pamięć do 0xcd, 0xdd, itd. na malloc / free / new / delete?

Wiem, że kompilator czasami inicjalizuje pamięć za pomocą pewnych wzorców, takich jak 0xCD i 0xDD. To co chcę wiedzieć to Kiedy i dlaczego tak się dzieje.

Kiedy

Czy jest to specyficzne dla używanego kompilatora?

Czy malloc/new i free/delete działają w ten sam sposób?

Czy jest specyficzna dla platformy?

Czy pojawi się na innych systemach operacyjnych, takich jak Linux czy VxWorks?

Dlaczego

Rozumiem, że występuje to tylko w konfiguracji debugowania Win32 i jest używany do wykrywania przekroczeń pamięci i pomagania kompilatorowi wyłapywać wyjątki.

Czy możesz podać jakieś praktyczne przykłady, jak ta inicjalizacja jest przydatna?

Pamiętam, że czytałem coś (może w Code Complete 2), że dobrze jest zainicjować pamięć do znanego wzorca przy alokacji, a niektóre wzorce wywołują przerwania w Win32, które spowoduje wyświetlenie WYJĄTKÓW w debuggerze.

Jak przenośne to jest?

Author: user207421, 2008-12-16

9 answers

W 2004 roku Microsoft udostępnił kompilatorowi wiele nowych funkcji, w tym m.in.: tworzenie kopii zapasowych, tworzenie kopii zapasowych, tworzenie kopii zapasowych, tworzenie kopii zapasowych, tworzenie kopii zapasowych, tworzenie kopii zapasowych, tworzenie kopii zapasowych, tworzenie kopii zapasowych, tworzenie kopii zapasowych, tworzenie kopii zapasowych, tworzenie kopii zapasowych, tworzenie kopii zapasowych.]}

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(). 

Disclaimer: tabela pochodzi z kilku notatek, które mam-mogą nie być w 100% poprawne (lub spójne).

Wiele z tych wartości jest zdefiniowanych w 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 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 */

Istnieje również kilka przypadków, w których debug runtime wypełni bufory (lub części buforów) znanym wartość, na przykład przestrzeń "slack" w alokacji std::string lub bufor przekazany do fread(). W tych przypadkach używa się wartości o nazwie _SECURECRT_FILL_BUFFER_PATTERN (zdefiniowanej w crtdefs.h). Nie jestem pewien, kiedy to zostało wprowadzone, ale było to w środowisku debug przez co najmniej VS 2005 (VC++8).

Początkowo, wartość użyta do wypełnienia tych buforów była 0xFD - ta sama wartość używana dla ziemi niczyjej. Jednak w VS 2008 (VC++9) wartość została zmieniona na 0xFE. Zakładam, że to dlatego, że mogą być sytuacje, w których wypełnienie operacja przebiegałaby poza końcem bufora, na przykład, jeśli wywołujący przekazałby bufor o rozmiarze zbyt dużym dla fread(). W takim przypadku wartość 0xFD może nie wywołać wykrywania tego przekroczenia, ponieważ jeśli rozmiar bufora byłby zbyt duży o jeden, wartość wypełnienia byłaby taka sama jak wartość ziemi niczyjej użyta do zainicjowania kanarka. Brak zmian na ziemi niczyjej oznacza, że najazd nie zostanie zauważony.

Więc wartość wypełnienia została zmieniona w VS 2008 tak, że taki przypadek zmieni kanarka ziemi niczyjej, co skutkuje wykryciem problemu przez runtime.

Jak zauważyli inni, jedną z kluczowych właściwości tych wartości jest to, że jeśli zmienna WSKAŹNIKA z jedną z tych wartości zostanie usunięta, spowoduje to naruszenie dostępu, ponieważ w standardowej 32-bitowej konfiguracji systemu Windows adresy trybu użytkownika nie będą wyższe niż 0x7fffffff.

 196
Author: Michael Burr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-10 08:50:16

Jedną z ładnych właściwości o wartości wypełnienia 0xccccccc jest to, że w x86 assembly, opcode 0xcc jest opcode int3, który jest przerwaniem punktu przerwania oprogramowania. Tak więc, jeśli kiedykolwiek spróbujesz wykonać kod w niezainicjowanej pamięci, która została wypełniona tą wartością wypełnienia, natychmiast trafisz w punkt przerwania, a system operacyjny pozwoli ci dołączyć debugger (lub zabić proces).

 38
Author: Adam Rosenfield,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-24 00:06:05

Jest specyficzny dla kompilatora i systemu operacyjnego, Visual studio ustawia różne rodzaje pamięci na różne wartości, dzięki czemu w debuggerze możesz łatwo sprawdzić, czy masz overun do malloced memory, stałej tablicy lub niezainicjowanego obiektu. Ktoś zamieści szczegóły, podczas gdy ja je googluję...

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

 10
Author: Martin Beckett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-16 00:56:27

To nie OS - to kompilator. Możesz też zmodyfikować zachowanie-zobacz na dole tego posta.

Microsoft Visual Studio generuje (w trybie debugowania) plik binarny, który wstępnie wypełnia pamięć stosu za pomocą 0xCC. Wstawia również spację między każdą ramką stosu w celu wykrycia przepełnienia bufora. Bardzo prosty przykład tego, gdzie jest to przydatne, znajduje się tutaj (w praktyce Visual Studio zauważy ten problem i wyda ostrzeżenie): {]}

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

Gdyby Visual Studio nie preinicjalizowało zmiennych do znanej wartości, wtedy ten błąd może być potencjalnie trudny do znalezienia. W przypadku preinicjalizowanych zmiennych (a raczej preinicjalizowanej pamięci stosu) problem jest powtarzalny przy każdym uruchomieniu.

Jest jednak mały problem. Wartość użyta przez Visual Studio jest prawdziwa - wszystko poza 0 byłoby prawdziwe. Jest całkiem prawdopodobne, że podczas uruchamiania kodu w trybie Release, że unitialized zmienne mogą być przypisane do kawałka pamięci stosu, który zdarza się zawierać 0, co oznacza, że można mają błąd zmiennej unitialized, który objawia się tylko w trybie Release.

To mnie zirytowało, więc napisałem skrypt , aby zmodyfikować wartość pre-fill poprzez bezpośrednią edycję pliku binarnego, pozwalając mi znaleźć problemy ze zmiennymi, które pojawiają się tylko wtedy, gdy stos zawiera zero. Ten skrypt modyfikuje tylko pre-fill stosu; nigdy nie eksperymentowałem z pre-fill stosu, chociaż powinno to być możliwe. Może obejmować edycję biblioteki DLL w czasie wykonywania, może nie.

 4
Author: Airsource Ltd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-24 00:08:46

Czy jest to specyficzne dla używanego kompilatora?

W rzeczywistości jest to prawie zawsze funkcja biblioteki runtime (jak biblioteka runtime C). Runtime jest zwykle silnie skorelowany z kompilatorem, ale są pewne kombinacje, które można zamienić.

Wierzę w Windows, sterta debugowania (HeapAlloc, itp.) używa również specjalnych wzorców wypełnienia, które są inne niż te, które pochodzą z malloc i darmowych implementacji w bibliotece uruchomieniowej debug C. Więc może również być funkcją systemu operacyjnego, ale w większości przypadków jest to tylko Biblioteka uruchomieniowa języka.

Czy malloc / new i free / delete działają w ten sam sposób?

Część zarządzania pamięcią new I delete są zwykle zaimplementowane z malloc i free, więc pamięć przydzielona z new i delete zwykle mają te same funkcje.

Czy jest specyficzna dla platformy?

Szczegóły są specyficzne dla środowiska runtime. Wartości rzeczywiste są często wybierane do nie tylko wyglądają nietypowo i oczywiste, patrząc na zrzut sześciokątny, ale są zaprojektowane tak, aby miały pewne właściwości, które mogą korzystać z funkcji procesora. Na przykład często używane są wartości nieparzyste, ponieważ mogą powodować błąd wyrównania. Duże wartości są używane (w przeciwieństwie do 0), ponieważ powodują one zaskakujące opóźnienia, jeśli pętla do niezainicjowanego licznika. Na x86, 0xcc jest instrukcją int 3, więc jeśli wykonasz niezainicjalizowaną pamięć, zostanie ona przechwycona.

Czy wystąpi na innych systemy operacyjne, takie jak Linux czy VxWorks?

Zależy to głównie od używanej biblioteki uruchomieniowej.

Czy możesz podać jakieś praktyczne przykłady, jak ta inicjalizacja jest przydatna?

Wymieniłem kilka powyżej. Wartości są zazwyczaj wybierane, aby zwiększyć szanse, że stanie się coś niezwykłego, jeśli zrobisz coś z nieprawidłowymi częściami pamięci: duże opóźnienia, pułapki, błędy wyrównania itp. Menedżerowie sterty również czasami używają specjalnych wartości wypełnienia dla luk między przydziałami. Jeśli te wzorce kiedykolwiek się zmienią, wie, że gdzieś był zły zapis (jak przepełnienie bufora).

Pamiętam, że czytałem coś (może w Code Complete 2) , że dobrze jest zainicjować pamięć do znanego wzorca podczas alokacji, a niektóre wzorce wywołają przerwania w Win32, co spowoduje wyświetlenie WYJĄTKÓW w debuggerze.

Jak przenośne to jest?

pisanie solidnego kodu (a może Kod kompletny ) mówi o rzeczach, które należy wziąć pod uwagę przy wyborze wzorów wypełnienia. Niektóre z nich wymieniłem tutaj, a Artykuł Wikipedii na temat Magiczna liczba (programowanie) również je podsumowuje. Niektóre sztuczki zależą od specyfiki procesora, którego używasz (np. czy wymaga wyrównanych odczytów i zapisów oraz jakie wartości odwzorowują instrukcje, które zostaną uwięzione). Inne sztuczki, takie jak używanie dużych wartości i nietypowych wartości, które wyróżniają się w zrzut pamięci, są bardziej przenośne.

 4
Author: Adrian McCarthy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

Ten artykuł opisuje nietypowe wzorce bitów pamięci i różne techniki, których możesz użyć, jeśli napotkasz te wartości.

 2
Author: Stephen Kellett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-13 09:20:13

Oczywistym powodem "dlaczego" jest to, że Załóżmy, że masz klasę taką jak Ta:

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

private:
    SomeObject *_obj;
}

A następnie tworzysz instancję a Foo i wywołujesz SomeFunction, to spowoduje naruszenie dostępu próbując odczytać 0xCDCDCDCD. Oznacza to, że zapomniałeś coś zainicjować. To jest"dlaczego". Jeśli nie, to wskaźnik może być połączony z inną pamięcią, a debugowanie byłoby trudniejsze. Po prostu informuję cię, Dlaczego masz naruszenie dostępu. Zauważ, że ta sprawa była ładna proste, ale w większej klasie łatwo jest popełnić ten błąd.

W przeciwieństwie do wersji release, program Visual Studio działa tylko w trybie debugowania (w przeciwieństwie do wersji release).]}
 2
Author: FryGuy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-05 11:49:40

Jest to, aby łatwo zobaczyć, że pamięć zmieniła się od początkowej wartości początkowej, zazwyczaj podczas debugowania, ale czasami dla kodu Wydania, jak również, ponieważ można dołączyć debuggery do procesu, gdy jest uruchomiony.

To nie tylko pamięć, wiele debuggerów ustawia zawartość rejestru na wartość sentinel podczas uruchamiania procesu (niektóre wersje AIX ustawiają niektóre rejestry na 0xdeadbeef, co jest lekko humorystyczne).

 2
Author: paxdiablo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-05 13:43:26

Kompilator IBM XLC ma opcję "INITAUTO", która przypisze automatycznie zmienne, które podasz. Użyłem następujących Dla Moich kompilacji debug:

-Wc,'initauto(deadbeef,word)'

Gdybym spojrzał na przechowywanie niezainicjowanej zmiennej, byłaby ona ustawiona na 0xdeadbeef

 1
Author: Anthony Giorgio,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-19 19:52:49