Techniki debugowania CRT

Podczas debugowania programu korzystającego z biblioteki czasu wykonywania języka C te techniki debugowania mogą być przydatne.

Korzystanie z biblioteki debugowania CRT

Biblioteka środowiska uruchomieniowego języka C (CRT) zapewnia rozbudowaną obsługę debugowania. Aby użyć jednej z bibliotek debugowania CRT, należy połączyć się z elementami i skompilować z elementami /DEBUG/MDd, /MTdlub ./LDd

Główne definicje i makra debugowania CRT można znaleźć w pliku nagłówka<crtdbg.h> .

Funkcje w bibliotekach debugowania CRT są kompilowane przy użyciu informacji debugowania (/Z7, /Zd, /Zi, /ZI, /ZI (Format informacji debugowania)) i bez optymalizacji. Niektóre funkcje zawierają asercji w celu zweryfikowania przekazanych do nich parametrów i podano kod źródłowy. Za pomocą tego kodu źródłowego możesz przejść do funkcji CRT, aby potwierdzić, że funkcje działają zgodnie z oczekiwaniami i sprawdzić nieprawidłowe parametry lub stany pamięci. (Niektóre technologie CRT są własnością i nie zapewniają kodu źródłowego do obsługi wyjątków, zmiennoprzecinku i kilku innych procedur).

Aby uzyskać więcej informacji na temat różnych bibliotek czasu wykonywania, których można użyć, zobacz Biblioteki czasu wykonywania języka C.

Makra raportowania

Do debugowania można użyć _RPTn makr i _RPTFn zdefiniowanych w<crtdbg.h> , aby zastąpić użycie instrukcji printf . Nie musisz ująć ich w #ifdef dyrektywach, ponieważ automatycznie znikają w kompilacji wydania, gdy _DEBUG nie są zdefiniowane.

Macro opis
_RPT0, _RPT1, _RPT2, _RPT3, _RPT4 Zwraca ciąg komunikatu i zero do czterech argumentów. W przypadku _RPT1 elementu _RPT4ciąg komunikatu służy jako ciąg formatowania w stylu printf dla argumentów.
_RPTF0, _RPTF1, _RPTF2, _RPTF3, _RPTF4 Takie same jak _RPTn, ale te makra również wyświetlają nazwę pliku i numer wiersza, w którym znajduje się makro.

Rozważmy następujący przykład:

#ifdef _DEBUG
    if ( someVar > MAX_SOMEVAR )
        printf( "OVERFLOW! In NameOfThisFunc( ),
               someVar=%d, otherVar=%d.\n",
               someVar, otherVar );
#endif

Ten kod zwraca wartości parametrów someVar i otherVar do stdout. Możesz użyć następującego wywołania, aby zgłosić _RPTF2 te same wartości, a także nazwę pliku i numer wiersza:

if (someVar > MAX_SOMEVAR) _RPTF2(_CRT_WARN, "In NameOfThisFunc( ), someVar= %d, otherVar= %d\n", someVar, otherVar );

Niektóre aplikacje mogą wymagać raportowania debugowania, które nie udostępniają makr dostarczonych z biblioteką czasu wykonywania języka C. W takich przypadkach można napisać makro zaprojektowane specjalnie zgodnie z własnymi wymaganiami. Na przykład w jednym z plików nagłówkowych możesz dołączyć kod podobny do poniższego, aby zdefiniować makro o nazwie ALERT_IF2:

#ifndef _DEBUG                  /* For RELEASE builds */
#define  ALERT_IF2(expr, msg, arg1, arg2)  do {} while (0)
#else                           /* For DEBUG builds   */
#define  ALERT_IF2(expr, msg, arg1, arg2) \
    do { \
        if ((expr) && \
            (1 == _CrtDbgReport(_CRT_ERROR, \
                __FILE__, __LINE__, msg, arg1, arg2))) \
            _CrtDbgBreak( ); \
    } while (0)
#endif

Jedno wywołanie metody może wykonać ALERT_IF2 wszystkie funkcje printf kodu:

ALERT_IF2(someVar > MAX_SOMEVAR, "OVERFLOW! In NameOfThisFunc( ),
someVar=%d, otherVar=%d.\n", someVar, otherVar );

Możesz łatwo zmienić niestandardowe makro, aby zgłosić więcej lub mniej informacji do różnych miejsc docelowych. Takie podejście jest przydatne, gdy wymagania dotyczące debugowania ewoluują.

Pisanie funkcji debugowania punktów zaczepienia

Możesz napisać kilka rodzajów niestandardowych funkcji punktu zaczepienia debugowania, które umożliwiają wstawianie kodu do niektórych wstępnie zdefiniowanych punktów wewnątrz normalnego przetwarzania debugera.

Funkcje punktu zaczepienia bloku klienta

Jeśli chcesz zweryfikować lub zgłosić zawartość danych przechowywanych w _CLIENT_BLOCK blokach, możesz napisać funkcję specjalnie w tym celu. Napisana funkcja musi mieć prototyp podobny do następującego, zgodnie z definicją w<crtdbg.h>:

void YourClientDump(void *, size_t)

Innymi słowy, funkcja haka powinna akceptować void wskaźnik na początku bloku alokacji wraz z wartością typu wskazującą size_t rozmiar alokacji i zwrócić wartość void. W przeciwnym razie jego zawartość jest do Ciebie.

Po zainstalowaniu funkcji haka przy użyciu _CrtSetDumpClient będzie ona wywoływana za każdym razem, gdy _CLIENT_BLOCK blok zostanie po cenach dumpingowych. Następnie możesz użyć _CrtReportBlockType , aby uzyskać informacje o typie lub podtypie bloków dumpingowych.

Wskaźnik do przekazanej funkcji _CrtSetDumpClient ma typ _CRT_DUMP_CLIENT, zgodnie z definicją w<crtdbg.h>:

typedef void (__cdecl *_CRT_DUMP_CLIENT)
   (void *, size_t);

Funkcje punktu zaczepienia alokacji

Funkcja punktu zaczepienia alokacji, zainstalowana przy użyciu metody _CrtSetAllocHook, jest wywoływana za każdym razem, gdy pamięć jest przydzielana, przydzielana w sposób rzeczywisty lub zwalniana. Tego typu haka można używać w wielu różnych celach. Służy do testowania sposobu, w jaki aplikacja obsługuje niewystarczające sytuacje pamięci, takie jak badanie wzorców alokacji lub informacje o alokacji dziennika na potrzeby późniejszej analizy.

Uwaga

Należy pamiętać o ograniczeniu używania funkcji biblioteki środowiska uruchomieniowego języka C w funkcji punktu zaczepienia alokacji, opisane w temacie Alokacja haki i alokacje pamięci crt.

Funkcja punktu zaczepienia alokacji powinna mieć prototyp podobny do następującego przykładu:

int YourAllocHook(int nAllocType, void *pvData,
        size_t nSize, int nBlockUse, long lRequest,
        const unsigned char * szFileName, int nLine )

Wskaźnik przekazywany do _CrtSetAllocHook jest typu _CRT_ALLOC_HOOK, zgodnie z definicją w<crtdbg.h>:

typedef int (__cdecl * _CRT_ALLOC_HOOK)
    (int, void *, size_t, int, long, const unsigned char *, int);

Gdy biblioteka czasu wykonywania wywołuje punkt zaczepienia, argument wskazuje, nAllocType jaka operacja alokacji ma zostać wykonana (_HOOK_ALLOC, _HOOK_REALLOC, lub _HOOK_FREE). W miejscu wolnym lub w lokalizacji rzeczywistej pvData ma wskaźnik do artykułu użytkownika bloku, który ma zostać zwolniony. Jednak w przypadku alokacji ten wskaźnik ma wartość null, ponieważ alokacja nie wystąpiła. Pozostałe argumenty zawierają rozmiar alokacji, jego typ bloku, numer żądania sekwencyjnego i wskaźnik do nazwy pliku. Jeśli są dostępne, argumenty zawierają również numer wiersza, w którym dokonano alokacji. Gdy funkcja haka wykonuje dowolną analizę i inne zadania, których chce autor, musi zwrócić TRUEwartość , wskazującą, że operacja alokacji może kontynuować lub FALSE, wskazując, że operacja powinna zakończyć się niepowodzeniem. Prosty hak tego typu może sprawdzić ilość pamięci przydzielonej do tej pory i zwrócić FALSE , jeśli ta ilość przekroczyła niewielki limit. Aplikacja napotkałaby wówczas rodzaj błędów alokacji, które zwykle występują tylko wtedy, gdy dostępna pamięć była niska. Bardziej złożone haki mogą śledzić wzorce alokacji, analizować użycie pamięci lub zgłaszać, gdy wystąpią określone sytuacje.

Przypinacze alokacji i alokacje pamięci CRT

Ważnym ograniczeniem funkcji punktu zaczepienia alokacji jest to, że muszą jawnie ignorować _CRT_BLOCK bloki. Te bloki to alokacje pamięci wykonywane wewnętrznie przez funkcje biblioteki czasu wykonywania języka C, jeśli są wykonywane wywołania funkcji biblioteki czasu wykonywania języka C, które przydzielają pamięć wewnętrzną. Bloki można zignorować _CRT_BLOCK , dołączając następujący kod na początku funkcji punktu zaczepienia alokacji:

if ( nBlockUse == _CRT_BLOCK )
    return( TRUE );

Jeśli punkt zaczepienia alokacji nie ignoruje _CRT_BLOCK bloków, każda funkcja biblioteki czasu wykonywania języka C wywoływana w haku może wychwycić program w nieskończonej pętli. Na przykład printf tworzy wewnętrzną alokację. Jeśli kod punktu zaczepienia wywołuje printfmetodę , wynikowa alokacja spowoduje ponowne wywołanie punktu zaczepienia, co spowoduje ponowne wywołanie printf i tak dalej do momentu przepełnienia stosu. Jeśli musisz zgłosić _CRT_BLOCK operacje alokacji, jednym ze sposobów obejścia tego ograniczenia jest użycie funkcji interfejsu API systemu Windows, a nie funkcji czasu wykonywania języka C do formatowania i danych wyjściowych. Ponieważ interfejsy API systemu Windows nie używają sterty biblioteki czasu wykonywania języka C, nie będą one wychwytywały haka alokacji w nieskończonej pętli.

Jeśli zbadasz pliki źródłowe biblioteki czasu wykonywania, zobaczysz, że domyślna funkcja _CrtDefaultAllocHook punktu zaczepienia alokacji (która po prostu zwraca TRUEwartość ), znajduje się w osobnym pliku własnego, debug_heap_hook.cpp. Jeśli chcesz, aby punkt zaczepienia alokacji był wywoływany nawet dla alokacji wykonanych przez kod uruchamiania w czasie wykonywania przed funkcją aplikacji main , możesz zastąpić tę funkcję domyślną własną, zamiast używać polecenia _CrtSetAllocHook.

Raportowanie funkcji punktów zaczepienia

Funkcja punktów zaczepienia raportu, zainstalowana przy użyciu metody _CrtSetReportHook, jest wywoływana za każdym razem, gdy _CrtDbgReport generuje raport debugowania. Można go używać między innymi do filtrowania raportów, aby skoncentrować się na określonych typach alokacji. Funkcja punktów zaczepienia raportu powinna mieć prototyp podobny do tego przykładu:

int AppReportHook(int nRptType, char *szMsg, int *retVal);

Wskaźnik przekazywany do _CrtSetReportHook jest typu _CRT_REPORT_HOOK, zgodnie z definicją w pliku :<crtdbg.h>

typedef int (__cdecl *_CRT_REPORT_HOOK)(int, char *, int *);

Gdy biblioteka czasu wykonywania wywołuje funkcję hook, argument zawiera kategorię raportu (, lub ), zawiera wskaźnik do w pełni zmontowanego ciągu komunikatu raportu i retVal określa, nRptType czy _CrtDbgReport należy kontynuować normalne wykonywanie po wygenerowaniu raportu, czy uruchomić debuger. szMsg_CRT_ASSERT_CRT_ERROR_CRT_WARN retVal(Wartość zero kontynuuje wykonywanie, wartość 1 uruchamia debugera).

Jeśli punkt zaczepienia całkowicie obsłuży komunikat, aby nie było wymagane dalsze raportowanie, powinien zwrócić wartość TRUE. Jeśli zwraca FALSEwartość , _CrtDbgReport będzie zgłaszać komunikat normalnie.

W tej sekcji

  • Wersja debugowania funkcji alokacji sterty

    Omówienie specjalnych wersji debugowania funkcji alokacji sterty, w tym: sposobu wywoływania map CRT, korzyści z ich jawnego wywoływania, sposobu unikania konwersji, śledzenia oddzielnych typów alokacji w blokach klienta oraz wyników niezdefiniowania _DEBUG.

  • Szczegóły dotyczące sterty debugowania CRT

    Opisuje zarządzanie pamięcią i stertę debugowania, typy bloków na stercie debugowania, funkcje raportowania stanu sterty sterty oraz sposób użycia sterty debugowania do śledzenia żądań alokacji.

  • Znajdowanie przecieków pamięci przy użyciu biblioteki CRT

    Obejmuje techniki wykrywania i izolowania przecieków pamięci przy użyciu debugera i biblioteki czasu wykonywania języka C.

Zobacz też