Udostępnij za pośrednictwem


Szczegóły dotyczące sterty debugowania CRT

Sterta debugowania CRT i powiązane funkcje zapewniają wiele sposobów śledzenia i debugowania problemów z zarządzaniem pamięcią w kodzie. Można go użyć do znajdowania przepełnień buforu oraz do śledzenia i raportowania alokacji pamięci i stanu pamięci. Zapewnia również obsługę tworzenia własnych funkcji alokacji debugowania dla unikatowych potrzeb aplikacji.

Znajdowanie przepełnień buforu za pomocą sterta debugowania

Dwa z najbardziej typowych i niezniszalnych problemów napotykanych przez programistów zastępują koniec przydzielonego buforu i przecieków pamięci (brak wolnych alokacji po tym, jak nie są już potrzebne). Sterta debugowania udostępnia zaawansowane narzędzia do rozwiązywania problemów z alokacją pamięci tego rodzaju.

Wersje debugowania funkcji stosu wywołają standardowe lub podstawowe wersje używane w kompilacjach wydania. Gdy żądasz bloku pamięci, menedżer stert debugowania przydziela z sterta podstawowego nieco większy blok pamięci niż zażądano i zwraca wskaźnik do części tego bloku. Załóżmy na przykład, że aplikacja zawiera wywołanie : malloc( 10 ). W kompilacji malloc wydania wywoła procedurę alokacji sterty podstawowej żądającą alokacji 10 bajtów. Jednak w kompilacji malloc debugowania wywołałaby _malloc_dbgmetodę , która następnie wywołałaby procedurę alokacji sterty podstawowej żądającą alokacji 10 bajtów i około 36 bajtów dodatkowej pamięci. Wszystkie wynikowe bloki pamięci w stercie debugowania są połączone z jedną połączoną listą uporządkowaną zgodnie z tym, kiedy zostały przydzielone.

Dodatkowa pamięć przydzielona przez procedury stert debugowania jest używana na potrzeby informacji księgowych. Zawiera on wskaźniki łączące bloki pamięci debugowania ze sobą i małe po obu stronach danych w celu przechwycenia zastąpień przydzielonego regionu.

Obecnie struktura nagłówka bloku używana do przechowywania informacji księgowych sterty debugowania jest zadeklarowana w nagłówku <crtdbg.h> i zdefiniowana w pliku źródłowym <debug_heap.cpp> CRT. Koncepcyjnie jest to podobne do tej struktury:

typedef struct _CrtMemBlockHeader
{
// Pointer to the block allocated just before this one:
    _CrtMemBlockHeader* _block_header_next;
// Pointer to the block allocated just after this one:
    _CrtMemBlockHeader* _block_header_prev;
    char const*         _file_name;
    int                 _line_number;

    int                 _block_use;      // Type of block
    size_t              _data_size;      // Size of user block

    long                _request_number; // Allocation number
// Buffer just before (lower than) the user's memory:
    unsigned char       _gap[no_mans_land_size];

    // Followed by:
    // unsigned char    _data[_data_size];
    // unsigned char    _another_gap[no_mans_land_size];
} _CrtMemBlockHeader;

no_mans_land po obu stronach obszaru danych użytkownika bloku mają obecnie 4 bajty rozmiaru i są wypełnione znaną wartością bajtów używaną przez procedury sterty debugowania w celu sprawdzenia, czy limity bloku pamięci użytkownika nie zostały zastąpione. Sterta debugowania wypełnia również nowe bloki pamięci znanymi wartościami. Jeśli zdecydujesz się zachować uwolnione bloki na liście połączonej sterty, te uwolnione bloki są również wypełnione znaną wartością. Obecnie rzeczywiste używane wartości bajtów są następujące:

no_mans_land (0xFD)
"no_mans_land" po obu stronach pamięci używanej przez aplikację są obecnie wypełnione 0xFD.

Wolne bloki (0xDD)
Uwolnione bloki pozostają nieużywane na liście połączonej sterty debugowania, gdy flaga _CRTDBG_DELAY_FREE_MEM_DF jest ustawiona, są obecnie wypełnione 0xDD.

Nowe obiekty (0xCD)
Nowe obiekty są wypełniane 0xCD po ich przydzieleniu.

Typy bloków na stercie debugowania

Każdy blok pamięci w stercie debugowania jest przypisywany do jednego z pięciu typów alokacji. Te typy są śledzone i zgłaszane inaczej na potrzeby wykrywania wycieków i raportowania stanu. Typ bloku można określić, przydzielając go przy użyciu bezpośredniego wywołania do jednej z funkcji alokacji sterty debugowania, takich jak _malloc_dbg. Pięć typów bloków pamięci w stercie debugowania (ustawionym w nBlockUse składowej _CrtMemBlockHeader struktury) jest w następujący sposób:

_NORMAL_BLOCK
Wywołanie lub malloc calloc utworzenie bloku Normal. Jeśli zamierzasz używać tylko bloków Normalny i nie ma potrzeby używania bloków klienta, możesz zdefiniować _CRTDBG_MAP_ALLOCwartość . _CRTDBG_MAP_ALLOC powoduje mapowanie wszystkich wywołań alokacji sterty na ich odpowiedniki debugowania w kompilacjach debugowania. Umożliwia przechowywanie informacji o nazwie pliku i numerze wiersza dotyczących każdego wywołania alokacji w odpowiednim nagłówku bloku.

_CRT_BLOCK
Bloki pamięci przydzielone wewnętrznie przez wiele funkcji biblioteki czasu wykonywania są oznaczone jako bloki CRT, dzięki czemu mogą być obsługiwane oddzielnie. W związku z tym wykrywanie wycieków i inne operacje mogą pozostać bez wpływu na nie. Alokacja nigdy nie może przydzielić, przydzielić ani zwolnić żadnego bloku typu CRT.

_CLIENT_BLOCK
Aplikacja może śledzić specjalną grupę alokacji na potrzeby debugowania, przydzielając je jako ten typ bloku pamięci przy użyciu jawnych wywołań funkcji sterty debugowania. MFC przydziela na przykład wszystkie CObject obiekty jako bloki klienta. Inne aplikacje mogą przechowywać różne obiekty pamięci w blokach klienta. Podtypy bloków klienta można również określić w celu uzyskania większego stopnia szczegółowości śledzenia. Aby określić podtypy bloków klienta, przesuń liczbę pozostawioną w lewo o 16 bitów i OR na _CLIENT_BLOCK. Na przykład:

#define MYSUBTYPE 4
freedbg(pbData, _CLIENT_BLOCK|(MYSUBTYPE<<16));

Funkcja haka dostarczana przez klienta do dumpingu obiektów przechowywanych w blokach klienta może być instalowana przy użyciu metody _CrtSetDumpClient, a następnie będzie wywoływana za każdym razem, gdy blok klienta zostanie po cenach dumpingowych przez funkcję debugowania. _CrtDoForAllClientObjects Ponadto można użyć do wywołania danej funkcji dostarczonej przez aplikację dla każdego bloku klienta w stercie debugowania.

_FREE_BLOCK
Zwykle bloki, które są zwalniane, są usuwane z listy. Aby sprawdzić, czy zwolniona pamięć nie jest zapisywana lub czy symulować niskie warunki pamięci, możesz zachować wolne bloki na liście połączonej, oznaczone jako Bezpłatna i wypełnione znaną wartością bajtu (obecnie 0xDD).

_IGNORE_BLOCK
Istnieje możliwość wyłączenia operacji sterty debugowania dla pewnego interwału. W tym czasie bloki pamięci są przechowywane na liście, ale są oznaczone jako Ignoruj bloki.

Aby określić typ i podtyp danego bloku, użyj funkcji _CrtReportBlockType oraz makr _BLOCK_TYPE i _BLOCK_SUBTYPE. Makra są definiowane w <crtdbg.h> następujący sposób:

#define _BLOCK_TYPE(block)          (block & 0xFFFF)
#define _BLOCK_SUBTYPE(block)       (block >> 16 & 0xFFFF)

Sprawdzanie integralności sterta i przecieków pamięci

Wiele funkcji sterta debugowania musi być dostępnych z poziomu kodu. W poniższej sekcji opisano niektóre funkcje i sposób ich używania.

_CrtCheckMemory
Możesz na przykład użyć wywołania metody , _CrtCheckMemoryaby sprawdzić integralność sterta w dowolnym momencie. Ta funkcja sprawdza każdy blok pamięci w stercie. Sprawdza, czy informacje nagłówka bloku pamięci są prawidłowe i potwierdza, że nie zostały zmodyfikowane.

_CrtSetDbgFlag
Sterta debugowania umożliwia śledzenie alokacji przy użyciu flagi wewnętrznej , _crtDbgFlagktóra może być odczytywana i ustawiana przy użyciu _CrtSetDbgFlag funkcji . Zmieniając tę flagę, możesz poinstruować stertę debugowania, aby sprawdzić przecieki pamięci po zakończeniu działania programu i zgłosić wszelkie wykryte przecieki. Podobnie można poinformować stertę o pozostawieniu wolnych bloków pamięci na liście połączonej w celu symulowania sytuacji z małą ilością pamięci. Po sprawdzeniu sterty te uwolnione bloki są sprawdzane w całości, aby upewnić się, że nie zostały zakłócone.

Flaga _crtDbgFlag zawiera następujące pola bitowe:

Pole bitowe Wartość domyślna opis
_CRTDBG_ALLOC_MEM_DF Włączone Włącza alokację debugowania. Gdy ten bit jest wyłączony, alokacje pozostają połączone w łańcuch, ale ich typ bloku to _IGNORE_BLOCK.
_CRTDBG_DELAY_FREE_MEM_DF Wyłączona Zapobiega zwalnianiu pamięci, ponieważ w przypadku symulowania warunków małej ilości pamięci. Gdy ten bit jest włączony, wolne bloki są przechowywane na połączonej liście sterty debugowania, ale są oznaczone jako _FREE_BLOCK i wypełnione specjalną wartością bajtu.
_CRTDBG_CHECK_ALWAYS_DF Wyłączona Przyczyny _CrtCheckMemory wywoływane przy każdej alokacji i cofaniu przydziału. Wykonywanie jest wolniejsze, ale szybko przechwytuje błędy.
_CRTDBG_CHECK_CRT_DF Wyłączona Powoduje, że bloki oznaczone jako typ _CRT_BLOCK są uwzględniane w operacjach wykrywania przecieków i różnic stanu. Gdy ten bit jest wyłączony, pamięć używana wewnętrznie przez bibliotekę czasu wykonywania jest ignorowana podczas takich operacji.
_CRTDBG_LEAK_CHECK_DF Wyłączona Powoduje, że sprawdzanie wycieku jest wykonywane podczas zamykania programu za pośrednictwem wywołania metody _CrtDumpMemoryLeaks. Raport o błędach jest generowany, jeśli aplikacja nie może zwolnić całej przydzielonej pamięci.

Konfigurowanie stert debugowania

Wszystkie wywołania funkcji stertowania, takich jak malloc, , freecalloc, realloc, newi delete są rozpoznawane w celu debugowania wersji tych funkcji działających w stercie debugowania. Gdy zwolnisz blok pamięci, sterta debugowania automatycznie sprawdza integralność po obu stronach przydzielonego obszaru i wystawia raport o błędach w przypadku wystąpienia zastąpienia.

Aby użyć stert debugowania

  • Połącz kompilację debugowania aplikacji z wersją debugowania biblioteki środowiska uruchomieniowego języka C.

Aby zmienić jedno lub więcej _crtDbgFlag pól bitowych i utworzyć nowy stan flagi

  1. Wywołaj _CrtSetDbgFlag metodę z parametrem ustawionym newFlag na _CRTDBG_REPORT_FLAG (aby uzyskać bieżący _crtDbgFlag stan) i zapisz zwróconą wartość w zmiennej tymczasowej.

  2. Włącz wszystkie bity przy użyciu operatora bitowego | ("lub") w zmiennej tymczasowej z odpowiednimi maskami bitów (reprezentowanymi w kodzie aplikacji przez stałe manifestu).

  3. Wyłącz inne bity przy użyciu operatora bitowego & ("i") dla zmiennej z operatorem bitowym ~ ("nie" lub uzupełnieniem) odpowiednich masek bitowych.

  4. Wywołaj metodę _CrtSetDbgFlag z parametrem ustawionym newFlag na wartość przechowywaną w zmiennej tymczasowej, aby utworzyć nowy stan dla _crtDbgFlagelementu .

    Na przykład następujące wiersze kodu umożliwiają automatyczne wykrywanie przecieków i wyłączanie kontroli bloków typu _CRT_BLOCK:

    // Get current flag
    int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
    
    // Turn on leak-checking bit.
    tmpFlag |= _CRTDBG_LEAK_CHECK_DF;
    
    // Turn off CRT block checking bit.
    tmpFlag &= ~_CRTDBG_CHECK_CRT_DF;
    
    // Set flag to the new value.
    _CrtSetDbgFlag( tmpFlag );
    

new, deletei _CLIENT_BLOCK alokacje w stercie debugowania języka C++

Wersje debugowania biblioteki środowiska uruchomieniowego języka C zawierają wersje debugowania języka C++ new i delete operatorów. Jeśli używasz _CLIENT_BLOCK typu alokacji, musisz wywołać wersję new debugowania operatora bezpośrednio lub utworzyć makra, które zastępują new operator w trybie debugowania, jak pokazano w poniższym przykładzie:

/* MyDbgNew.h
 Defines global operator new to allocate from
 client blocks
*/

#ifdef _DEBUG
   #define DEBUG_CLIENTBLOCK   new( _CLIENT_BLOCK, __FILE__, __LINE__)
#else
   #define DEBUG_CLIENTBLOCK
#endif // _DEBUG

/* MyApp.cpp
        Use a default workspace for a Console Application to
 *      build a Debug version of this code
*/

#include "crtdbg.h"
#include "mydbgnew.h"

#ifdef _DEBUG
#define new DEBUG_CLIENTBLOCK
#endif

int main( )   {
    char *p1;
    p1 =  new char[40];
    _CrtMemDumpAllObjectsSince( NULL );
}

Wersja debugowania operatora działa ze wszystkimi delete typami bloków i nie wymaga żadnych zmian w programie podczas kompilowania wersji.

Funkcje raportowania stanu stert

Aby przechwycić migawkę podsumowania stanu sterty w danym momencie, użyj struktury zdefiniowanej _CrtMemState w pliku <crtdbg.h>:

typedef struct _CrtMemState
{
    // Pointer to the most recently allocated block:
    struct _CrtMemBlockHeader * pBlockHeader;
    // A counter for each of the 5 types of block:
    size_t lCounts[_MAX_BLOCKS];
    // Total bytes allocated in each block type:
    size_t lSizes[_MAX_BLOCKS];
    // The most bytes allocated at a time up to now:
    size_t lHighWaterCount;
    // The total bytes allocated at present:
    size_t lTotalCount;
} _CrtMemState;

Ta struktura zapisuje wskaźnik w pierwszym bloku (ostatnio przydzielonym) na liście połączonej stert debugowania. Następnie w dwóch tablicach rejestruje liczbę każdego typu bloku pamięci (_NORMAL_BLOCK, _CLIENT_BLOCK, _FREE_BLOCKitd.) na liście oraz liczbę bajtów przydzielonych w każdym typie bloku. Na koniec rejestruje największą liczbę bajtów przydzielonych w stercie jako całość do tego momentu i liczbę bajtów, które są obecnie przydzielone.

Inne funkcje raportowania CRT

Poniższe funkcje zgłaszają stan i zawartość sterta oraz używają informacji, aby pomóc wykrywać przecieki pamięci i inne problemy.

Function opis
_CrtMemCheckpoint Zapisuje migawkę sterta w strukturze dostarczonej _CrtMemState przez aplikację.
_CrtMemDifference Porównuje dwie struktury stanu pamięci, zapisuje różnicę między nimi w trzeciej strukturze stanu i zwraca wartość TRUE, jeśli dwa stany są różne.
_CrtMemDumpStatistics Zrzuty danej _CrtMemState struktury. Struktura może zawierać migawkę stanu sterta debugowania w danym momencie lub różnicę między dwiema migawkami.
_CrtMemDumpAllObjectsSince Zrzuty informacji o wszystkich obiektach przydzielonych od momentu utworzenia danej migawki sterty lub od początku wykonywania. Za każdym razem, gdy zrzutuje _CLIENT_BLOCK blok, wywołuje funkcję haka dostarczaną przez aplikację, jeśli została zainstalowana przy użyciu polecenia _CrtSetDumpClient.
_CrtDumpMemoryLeaks Określa, czy wystąpiły jakiekolwiek przecieki pamięci od rozpoczęcia wykonywania programu, a jeśli tak, zrzuty wszystkich przydzielonych obiektów. Za każdym razem, gdy _CrtDumpMemoryLeaks zrzuty _CLIENT_BLOCK bloku, wywołuje funkcję haka dostarczaną przez aplikację, jeśli została zainstalowana przy użyciu programu _CrtSetDumpClient.

Śledzenie żądań alokacji sterty

Znajomość nazwy pliku źródłowego i numeru wiersza makra asercji lub raportowania jest często przydatna podczas lokalizowania przyczyny problemu. To samo nie jest tak prawdopodobne, aby było prawdziwe w przypadku funkcji alokacji sterty. Chociaż makra można wstawiać w wielu odpowiednich punktach w drzewie logiki aplikacji, alokacja jest często pochowana w funkcji wywoływanej z wielu różnych miejsc w wielu różnych momentach. Pytanie nie jest tym, jaki wiersz kodu dokonał nieprawidłowej alokacji. Zamiast tego jest to jeden z tysięcy alokacji wykonanych przez ten wiersz kodu był zły i dlaczego.

Unikatowe numery żądań alokacji i _crtBreakAlloc

Istnieje prosty sposób identyfikowania konkretnego wywołania alokacji sterty, które poszło źle. Korzysta z unikatowego numeru żądania alokacji skojarzonego z każdym blokiem w stercie debugowania. Gdy informacje o bloku są zgłaszane przez jedną z funkcji zrzutu, ten numer żądania alokacji jest ujęty w nawiasy klamrowe (na przykład "{36}").

Gdy znasz numer żądania alokacji nieprawidłowo przydzielonego bloku, możesz przekazać tę liczbę, aby _CrtSetBreakAlloc utworzyć punkt przerwania. Wykonanie spowoduje przerwanie tuż przed alokacją bloku i można cofnąć śledzenie, aby określić, jaka rutyna była odpowiedzialna za złe wywołanie. Aby uniknąć ponownego komplikowania, możesz wykonać to samo w debugerze, ustawiając _crtBreakAlloc numer żądania alokacji, który cię interesuje.

Tworzenie wersji debugowania procedur alokacji

Bardziej złożone podejście polega na tworzeniu wersji debugowania własnych procedur alokacji, porównywalnych z _dbg wersjami funkcji alokacji sterty. Następnie możesz przekazać plik źródłowy i argumenty numeru wiersza do podstawowych procedur alokacji sterty i natychmiast zobaczyć, skąd pochodzi nieprawidłowa alokacja.

Załóżmy na przykład, że aplikacja zawiera często używaną procedurę podobną do poniższego przykładu:

int addNewRecord(struct RecStruct * prevRecord,
                 int recType, int recAccess)
{
    // ...code omitted through actual allocation...
    if ((newRec = malloc(recSize)) == NULL)
    // ... rest of routine omitted too ...
}

W pliku nagłówkowym można dodać kod, taki jak poniższy przykład:

#ifdef _DEBUG
#define  addNewRecord(p, t, a) \
            addNewRecord(p, t, a, __FILE__, __LINE__)
#endif

Następnie możesz zmienić alokację w procedurze tworzenia rekordów w następujący sposób:

int addNewRecord(struct RecStruct *prevRecord,
                int recType, int recAccess
#ifdef _DEBUG
               , const char *srcFile, int srcLine
#endif
    )
{
    /* ... code omitted through actual allocation ... */
    if ((newRec = _malloc_dbg(recSize, _NORMAL_BLOCK,
            srcFile, scrLine)) == NULL)
    /* ... rest of routine omitted too ... */
}

Teraz nazwa pliku źródłowego i numer wiersza, w którym addNewRecord została wywołana, będą przechowywane w każdym wynikowym bloku przydzielonym na stercie debugowania i będą zgłaszane po zbadaniu tego bloku.

Zobacz też

Debugowanie kodu natywnego