Udostępnij za pośrednictwem


Szczegóły dotyczące stosu debugowania CRT

Ten temat zawiera szczegółowe omówienie stosu debugowania CRT.

Zawartość

Znajdowanie przepełnień bufora z debugowaniem sterty

Types of blocks on the debug heap

Sprawdź szczelność integralności i przecieki pamięci

Konfiguruj debugowanie sterty

new, delete, i _CLIENT_BLOCKs w stosie debugowania C++

Funkcje raportowania stanu stosu

Track Heap Allocation Requests

Znajdź przepełnienia bufora z debugowaniem sterty

Dwie najbardziej typowe i trudne problemy, które napotykają programiści to zastępowanie końca przydzielonego buforu i przecieki pamięci (nie można zwolnić alokacji, które nie są już potrzebne).Stos debugowania oferuje zaawansowane narzędzia do rozwiązywania tego rodzaju problemów z alokacją pamięci.

Wersje do debugowania funkcji stosu wywołują wersje standardowe lub podstawowe używane w kompilacjach do wydania.Żądając bloku pamięci, menedżer stosu debugowania przydziela ze stosu podstawowego nieco większy blok pamięci niż żądany, a następnie zwraca wskaźnik do Twojej części tego bloku.Załóżmy na przykład, że aplikacja zawiera wywołanie: malloc( 10 ).W kompilacji wydania funkcja malloc wywołuje procedurę alokacji stosu podstawowego z żądaniem o przydział 10 bajtów.Jednak w kompilacji do debugowania element malloc wywoływałby funkcję _malloc_dbg, która następnie wywoływałaby procedurę alokacji stosu podstawowego z żądaniem przydziału 10 bajtów plus około 36 bajtów dodatkowej pamięci.Wszystkie wynikające bloki pamięci w stercie debugowania są połączone na pojedynczej liście połączonej, uporządkowane według daty alokacji.

Dodatkowa pamięć przydzielana przez procedury stosu debugowania jest używana na potrzeby informacji księgowych, wskaźników, które łączą razem bloki pamięci debugowania, oraz małych buforów po obu stronach danych służących do przechwytywania zastąpień przydzielonego regionu.

Obecnie struktura nagłówka bloku używana do przechowywania informacji księgowych sterty debugowania jest zadeklarowana w następujący sposób w pliku nagłówkowym DBGINT.H:

typedef struct _CrtMemBlockHeader
{
// Pointer to the block allocated just before this one:
    struct _CrtMemBlockHeader *pBlockHeaderNext;
// Pointer to the block allocated just after this one:
    struct _CrtMemBlockHeader *pBlockHeaderPrev;
    char *szFileName;    // File name
    int nLine;           // Line number
    size_t nDataSize;    // Size of user block
    int nBlockUse;       // Type of block
    long lRequest;       // Allocation number
// Buffer just before (lower than) the user's memory:
    unsigned char gap[nNoMansLandSize];
} _CrtMemBlockHeader;

/* In an actual memory block in the debug heap,
 * this structure is followed by:
 *   unsigned char data[nDataSize];
 *   unsigned char anotherGap[nNoMansLandSize];
 */

Bufory NoMansLand po obu stronach obszaru danych bloku użytkownika mają obecnie rozmiar 4 bajtów i są wypełnione znaną wartością bajtową używaną przez procedury stosu debugowania w celu sprawdzenia, czy limity bloku pamięci użytkownika nie zostały zastąpione.Stos debugowania wypełnia również nowe bloki pamięci znaną wartością.Jeśli zostanie wybrana opcja zachowania zwolnionych bloków na połączonej liście stosu jak wyjaśniono poniżej, te bloki zwolnione również są wypełnione znaną wartością.Obecnie rzeczywiste wartości bajt są następujące:

  • NoMansLand ( 0xFD)
    Bufory NoMansLand po obu stronach pamięci używanej przez aplikację są obecnie wypełniane wartością 0xFD.

  • Bloki zwolnione (0xDD)
    Zwolnione bloki, które pozostają nieużywane na liście dwukierunkowej stosu debugowania, jeśli jest ustawiona flaga _CRTDBG_DELAY_FREE_MEM_DF, są obecnie wypełnione wartością 0xDD.

  • Nowe obiekty (0xCD)
    Nowe obiekty są wypełnione 0xCD, gdy są przydzielone.

Powrót do początkuZawartość

Typy bloków na stercie debugowania

Każdy blok pamięci w stosie debugowania jest przypisany do jednego z pięciu typów alokacji.Te typy są śledzone i raportowane inaczej do celów wykrywania przecieków i raportowania stanu.Można określić typ bloku, przydzielając go za pomocą bezpośredniego wywołania jednej z funkcji alokacji sterty debugowania, takiej jak _malloc_dbg.Pięć typów bloków pamięci w stosie debugowania (ustawionych w elemencie członkowskim nBlockUse struktury _CrtMemBlockHeader) to:

  • _NORMAL_BLOCK
    Wywołanie malloc lub calloc tworzy Blok normalny.Jeśli zamierzasz używać tylko bloków normalnych i nie potrzebujesz bloków klienta, warto zdefiniować element _CRTDBG_MAP_ALLOC, który powoduje, że wszystkie wywołania alokacji stosów mają być mapowane do ich odpowiedników debugowania w kompilacjach debugowania.Dzięki temu informacje dotyczące nazwy pliku i numeru wiersza dla każdego wywołania alokacji mogą być przechowywane w odpowiednim nagłówku bloku.

  • _CRT_BLOCK
    Bloki pamięci przydzielane wewnętrznie przez wiele funkcji biblioteki wykonawczej są oznaczane jako bloki CRT, więc mogą być obsługiwane osobno.W rezultacie nie będą one miały wpływu na wykrywanie przecieków i inne operacje.Przydział nie może przydzielać, ponownie przydzielać ani uwalniać bloku typu CRT.

  • _CLIENT_BLOCK
    Aplikacja może śledzić daną grupę alokacji na potrzeby debugowania, alokując je jako ten typ bloku pamięci za pomocą jawnych wywołań funkcji debugowania sterty.MFC, na przykład przydziela wszystkim CObjects jako bloki klienta; inne aplikacje mogą zachować pamięci różnych obiektów w blokach klienta.Można również określić podtypy bloków klienta, aby zwiększyć ziarnistość śledzenia.Aby określić podtypy bloków klienta, przesuń liczbę w lewo o 16 bitów, uzupełniając oOR z _CLIENT_BLOCK.Na przykład:

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

    Dostarczana przez klienta funkcja hak służąca do zrzucania obiektów przechowywanych w blokach klienta może być instalowana za pomocą _CrtSetDumpClient, a następnie wywoływana zawsze wtedy, gdy blok klienta jest zrzucany przez funkcję debugowania.Ponadto obiekty _CrtDoForAllClientObjects mogą służyć do wywołania danej funkcji dostarczanej przez aplikację dla każdego bloku Klient w stercie debugowania.

  • _FREE_BLOCK
    Normalnie, bloki, które są zwalniane, są usuwane z listy.Aby sprawdzić, czy zwolniona pamięć nadal nie jest zapisywana lub do symulacji warunków braku pamięci, można zachować zwolnione bloki na liście połączonej, oznaczone jako wolne i wypełnione za pomocą znanej wartości bajtu (obecnie 0xDD).

  • _IGNORE_BLOCK
    Istnieje możliwość wyłączyć operacje debugowania sterty przez pewien okres czasu.W tym czasie bloki pamięci są przechowywane na liście, ale są oznaczone jako bloki do ignorowania.

Aby określić typ i podtyp bloku, należy użyć funkcji _CrtReportBlockType i makr _BLOCK_TYPE oraz _BLOCK_SUBTYPE.Makra są zdefiniowane (w pliku crtdbg.h) w następujący sposób:

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

Powrót do początkuZawartość

Sprawdź szczelność integralności i przecieki pamięci

Wiele funkcji sterty debugowania musi być dostępnych w kodzie.W poniższej sekcji opisano niektóre funkcje i sposoby ich użycia.

  • _CrtCheckMemory
    Można użyć wywołania _CrtCheckMemory, na przykład, aby sprawdzić integralność sterty 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 bufory nie zostały zmodyfikowane.

  • _CrtSetDbgFlag
    Można kontrolować sposób, w jaki debugowanie stosu śledzi alokacje za pomocą flagi wewnętrznej, _crtDbgFlag, która może być odczytywana i ustawiona za pomocą funkcji _CrtSetDbgFlag.Zmieniając tę flagę, możesz poinstruować stertę debugowania, aby sprawdzała przecieki pamięci, gdy zamyka program i zgłaszała wszelkie wykryte nieszczelności.Podobnie, można określić, że zwolnione bloki pamięci nie będą usuwane z listy dwukierunkowej, aby symulować sytuacje małej ilości pamięci.Podczas sprawdzania stosu zwolnione bloki są kontrolowane w całości w celu zapewnienia, że nie zostały zajęte.

    Flaga _CrtDbgFlag zawiera następujące pola bitowe:

    Pole bitowe

    Domyślny

    wartość

    Opis

    _CRTDBG_ALLOC_MEM_DF

    On

    Włącza debugowanie alokacji.Jeśli ten bit jest wyłączony, alokacje są powiązane ze sobą, ale ich typem bloku jest _IGNORE_BLOCK.

    _CRTDBG_DELAY_FREE_MEM_DF

    Off

    Zapobiega faktycznemu zwolnieniu pamięci, jeśli chodzi o symulowanie warunków małej ilości pamięci.Jeśli ten bit jest włączony, zwolnione bloki są trzymane w połączonej liście stosu debugowania, ale są oznaczone jako _FREE_BLOCK i wypełnione specjalną wartością bajtu.

    _CRTDBG_CHECK_ALWAYS_DF

    Off

    Powoduje wywołanie _CrtCheckMemory na każdej alokacji i dezalokacji.To spowalnia wykonanie, ale zapewnia szybkie wyłapywanie błędów.

    _CRTDBG_CHECK_CRT_DF

    Off

    Powoduje, że bloki oznaczone jako typ _CRT_BLOCK mają zostać uwzględnione w operacjach wykrywania przecieków i różnic stanów.Jeśli ten bit jest wyłączony, pamięć używana wewnętrznie przez bibliotekę uruchomieniową jest ignorowana podczas takich operacji.

    _CRTDBG_LEAK_CHECK_DF

    Off

    Powoduje sprawdzanie przecieków, wykonywane przy zamykaniu programu poprzez wywołanie _CrtDumpMemoryLeaks.Raport o błędzie jest generowany, gdy aplikacja nie może zwolnić całej wolnej pamięci, jaką przydzieliła.

Powrót do początkuZawartość

Skonfiguruj debugowanie sterty

Wszystkie wywołania do funkcji sterty, takich jak malloc, free, calloc, realloc, new, i delete odnoszą się do wersji debugowania funkcji, które działają na stercie debugowania.Po zwolnieniu bloku pamięci stos debugowania automatycznie sprawdza spójność buforów po obu stronach obszaru przydzielonego i generuje raport o błędach, jeżeli zastąpienie wystąpiło.

Aby używać debugowania sterty

  • Połącz debugera kompilacji swojej aplikacji z wersją debugera biblioteki uruchomieniowej C.

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

  1. Wywołaj _CrtSetDbgFlag z parametrem newFlag ustawionym na _CRTDBG_REPORT_FLAG (aby uzyskać bieżący stan _crtDbgFlag) i przechowuj zwracaną wartość w zmiennej tymczasowej.

  2. Włącz wszystkie bity, używając operatora OR (symbol | na poziomie bitowym) ze zmienną tymczasową i odpowiednimi maskami bitowymi (reprezentowane przez stałe manifestu w kodzie aplikacji).

  3. Należy wyłączyć pozostałe bity, używając operatora AND (symbol & na poziomie bitowym) ze zmienną z operatorem NOT (symbol ~ na poziomie bitowym) odpowiednich masek bitowych.

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

Na przykład następujące wiersze kodu włączają funkcję automatycznego wykrywania przecieków i wyłączają sprawdzanie 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 );

Powrót do początkuZawartość

new, delete, i _CLIENT_BLOCKs w stosie debugowania C++

Wersje do debugowania biblioteki wykonawczej języka C zawierają wersje do debugowania w języku C++ operatorów new i delete.Jeśli używasz typu alokacji _CLIENT_BLOCK, należy wywołać wersję debugowania operatora new bezpośrednio lub utworzyć makra, które zastępują operator new w trybie debugowania, jak pokazano w następującym 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 do debugowania operatora delete działa ze wszystkimi typami bloków i nie wymaga żadnych zmian w programie podczas kompilowania wersji do wydania.

Powrót do początkuZawartość

Funkcje raportowania stanu stosu

_CrtMemState

Aby przechwycić migawkę podsumowania stanu stosu w danym momencie, użyj struktury _CrtMemState zdefiniowanej w 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;

Struktura ta zapisuje wskaźnik do pierwszego bloku (najbardziej niedawno przydzielonego) na połączonej liście stosu debugowania.Następnie w dwóch tablicach rejestruje, ile poszczególnych typów bloków pamięci (_NORMAL_BLOCK, _CLIENT_BLOCK, _FREE_BLOCK itd) jest na liście, oraz liczbę bajtów alokowanych w każdym typie bloku.Na koniec rejestruje największą liczbę bajtów alokowaną w stercie jako całość do tego punktu oraz liczbę aktualnie przydzielonych bajtów.

Inne funkcje raportowania CRT

Poniższe funkcje raportują stan i zawartość stosu i używają tych informacji do wykrywania przecieków pamięci i innych problemów.

Funkcja

Opis

_CrtMemCheckpoint

Zapisuje migawkę stosu w strukturze _CrtMemState dostarczonej przez aplikację.

_CrtMemDifference

Porównuje dwie struktury stanu pamięci, zapisuje różnicę między nimi w trzeciej strukturze i zwraca wartość PRAWDA, jeśli dwa stany są różne.

_CrtMemDumpStatistics

Zrzuca daną strukturę _CrtMemState.Struktura może zawierać migawkę stanu stosu debugowania w danym momencie lub różnicę między dwiema migawkami.

_CrtMemDumpAllObjectsSince

Zrzuca informacje o wszystkich obiektach przydzielonych od wykonania danej migawki stosu lub od początku wykonywania.Za każdym razem, gdy zrzuca blok _CLIENT_BLOCK, wywołuje funkcję zaczepienia dostarczoną przez aplikację, jeśli została ona zainstalowana za pomocą funkcji _CrtSetDumpClient.

_CrtDumpMemoryLeaks

Określa, czy jakiekolwiek przecieki pamięci miały miejsce od momentu rozpoczęcia wykonywania programu, a jeśli tak, zrzuca wszystkie przydzielone obiekty.Za każdym razem, gdy funkcja _CrtDumpMemoryLeaks zrzuca blok _CLIENT_BLOCK, wywołuje funkcję zaczepienia dostarczoną przez aplikację, jeśli została ona zainstalowana za pomocą funkcji _CrtSetDumpClient.

Powrót do początkuZawartość

Śledź zlecenia Alokacji sterty

Chociaż wskazywanie nazwy pliku źródłowego i numeru wiersza w którym działa makro raportowania lub zapewnienia jest często bardzo pomocne w odnalezieniu przyczyny problemu, to samo nie sprawdza się już tak dobrze w przypadku funkcji alokacji sterty.Podczas gdy makra można wstawiać w wielu odpowiednich punktach w drzewie logiki aplikacji, przydział jest często ukryty w specjalnej procedurze, która jest wywoływana z wielu różnych miejsc w wielu różnych okresach.Pytaniem nie jest zazwyczaj, który wiersz kodu dokonał złej alokacji, ale która z tysięcy alokacji dokonanych przez ten wiersz kodu była zła i dlaczego.

Unikatowe numery żądań alokacji i _crtBreakAlloc

Najprostszym sposobem określenia konkretnej alokacji stosu, która się nie udała, jest skorzystanie z unikatowego numeru żądania alokacji związanego z każdym blokiem w stosie debugowania.Gdy informacje o bloku są raportowane przez jedną z funkcji zrzutu, ten numer żądania alokacji jest ujęty w nawiasy klamrowe (na przykład {36}).

Znając numer żądania alokacji nieprawidłowo zaalokowanego bloku, możesz przekazać ten numer do _CrtSetBreakAlloc w celu utworzenia punktu przerwania.Wykonywanie zostanie przerwane tuż przed alokowanie bloku. Można wtedy wykonać nawrót i ustalić, która procedura była odpowiedzialna za błędne wywołanie.Aby uniknąć ponownej kompilacji, można osiągnąć to samo w debugerze, ustawiając _crtBreakAlloc na właściwy numer żądania alokacji.

Tworzenie wersji debugowania procedur Twojej alokacji

Nieco bardziej skomplikowane podejście dotyczy tworzenie wersji debugowania własnych procedur alokacji, porównywalnych z wersjami _dbgfunkcji alokacji stosów.Można następnie przekazać plik źródłowy i argumenty numeru wiersza za pośrednictwem źródłowych procedur alokacji sterty, a od razu będzie można zobaczyć, skąd pochodzi zła alokacja.

Na przykład załóżmy, że aplikacja zawiera często używaną procedurę podobną do następującej:

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łówka można dodać kod taki jak poniżej:

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

Następnie można zmienić przydział w rutynowym tworzeniu 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 źródła pliku nazwa i numer wiersza, gdzie addNewRecord została wywołana, będzie przechowywana w każdym bloku wynikowym alokowanym na stercie debugowania i będą raportowane przy badaniu tego bloku.

Powrót do początkuZawartość

Zobacz też

Inne zasoby

Debugowanie kodu natywnego