Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W tym laboratorium przedstawiono debugowanie podróży w czasie (TTD) przy użyciu małego przykładowego programu z wadą kodu. TTD służy do debugowania, identyfikowania i głównej przyczyny problemu. Chociaż problem w tym małym programie jest łatwy do znalezienia, ogólna procedura może być używana w bardziej złożonym kodzie. Tę ogólną procedurę można podsumować w następujący sposób.
- Przechwyć ślad podróży czasowej programu, który zakończył się niepowodzeniem.
- Użyj dx (Display Debugger Object Model Expression) polecenie, aby znaleźć zdarzenie wyjątku przechowywane w nagraniu.
- Użyj polecenia !tt (podróż czasowa), aby przenieść się do pozycji zdarzenia wyjątku w trace’u.
- Od tego momentu śledź krok po kroku wstecz, aż kod, którego dotyczy błąd, znajdzie się w zasięgu.
- Mając na uwadze błędny kod, przyjrzyj się wartościom lokalnym i opracuj hipotezę dotyczącą zmiennej, która może zawierać niepoprawną wartość.
- Określ adres pamięci zmiennej z nieprawidłową wartością.
- Ustaw punkt przerwania dostępu do pamięci (ba) na adres podejrzanej zmiennej za pomocą polecenia ba (Break on Access).
- Użyj g- aby powrócić do ostatniego punktu dostępu do pamięci podejrzanej zmiennej.
- Sprawdź, czy ta lokalizacja lub kilka instrukcji wcześniej jest punktem błędu kodu. Jeśli tak, to skończone. Jeśli nieprawidłowa wartość pochodzi z innej zmiennej, ustaw kolejną przerwę w punkcie przerwania dostępu w drugiej zmiennej.
- Użyj g- aby powrócić do ostatniego punktu dostępu do pamięci na drugiej podejrzanej zmiennej. Sprawdź, czy ta lokalizacja lub kilka instrukcji przed zawiera wadę kodu. Jeśli tak, to skończone.
- Powtarzaj ten proces, cofając się, aż znajdziesz kod, który ustawił niepoprawną wartość powodującą błąd.
Chociaż ogólne techniki opisane w tej procedurze dotyczą szerokiego zestawu problemów z kodem, istnieją unikatowe problemy z kodem, które będą wymagały unikatowego podejścia. Techniki przedstawione w instrukcji krok po kroku powinny służyć do rozszerzenia zestawu narzędzi debugowania i zilustrują pewne możliwości ze śladem TTD.
Cele laboratorium
Po ukończeniu tego laboratorium będziesz mieć możliwość użycia ogólnej procedury ze śladem podróży czasowej w celu zlokalizowania problemów w kodzie.
Konfiguracja laboratorium
Aby ukończyć laboratorium, potrzebny będzie następujący sprzęt.
- Komputer przenośny lub komputer stacjonarny (host) z systemem Windows 10 lub Windows 11
Aby ukończyć laboratorium, potrzebne będzie następujące oprogramowanie.
- The WinDbg. Aby uzyskać informacje na temat instalowania windbg, zobacz WinDbg — instalacja
- Program Visual Studio umożliwia skompilowanie przykładowego kodu C++.
Laboratorium zawiera następujące trzy sekcje.
- Sekcja 1. Tworzenie przykładowego kodu
- Sekcja 2. Rejestrowanie śladu próbki "DisplayGreeting"
- Sekcja 3. Analizowanie nagrania pliku śledzenia w celu zidentyfikowania problemu z kodem
Sekcja 1. Tworzenie przykładowego kodu
W sekcji 1 utworzysz przykładowy kod przy użyciu programu Visual Studio.
Tworzenie przykładowej aplikacji w programie Visual Studio
W programie Microsoft Visual Studio kliknij pozycję Plik>nowy>projekt/rozwiązanie... i kliknij szablony Visual C++ .
Wybierz aplikację konsolową Win32.
Podaj nazwę projektu DisplayGreeting i kliknij przycisk OK.
Usuń zaznaczenie sprawdzania cyklu projektowania zabezpieczeń (SDL).
Kliknij przycisk Zakończ.
Wklej następujący tekst do okienka DisplayGreeting.cpp w programie Visual Studio.
// DisplayGreeting.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <array> #include <stdio.h> #include <string.h> void GetCppConGreeting(wchar_t* buffer, size_t size) { wchar_t const* const message = L"HELLO FROM THE WINDBG TEAM. GOOD LUCK IN ALL OF YOUR TIME TRAVEL DEBUGGING!"; wcscpy_s(buffer, size, message); } int main() { std::array <wchar_t, 50> greeting{}; GetCppConGreeting(greeting.data(), sizeof(greeting)); wprintf(L"%ls\n", greeting.data()); return 0; }W programie Visual Studio kliknij pozycję Projekt>Właściwości DisplayGreeting. Następnie kliknij pozycję C/C++ i Generowanie kodu.
Ustaw następujące właściwości.
Ustawienie Wartość Sprawdzanie zabezpieczeń Wyłącz sprawdzanie zabezpieczeń (/GS-) Podstawowe testy środowiska uruchomieniowego Wartość domyślna Uwaga
Chociaż to ustawienie nie jest zalecane, można sobie wyobrazić scenariusz, w którym ktoś zaleci użycie tych ustawień w celu przyspieszenia kodowania lub ułatwienia niektórych środowisk testowych.
W programie Visual Studio kliknij Kompiluj>Skompiluj rozwiązanie.
Jeśli wszystko pójdzie dobrze, okna kompilacji powinny wyświetlić komunikat wskazujący, że kompilacja zakończyła się pomyślnie.
Lokalizowanie skompilowanych przykładowych plików aplikacji
W Eksploratorze rozwiązań kliknij prawym przyciskiem myszy projekt DisplayGreeting i wybierz polecenie Otwórz folder w Eksploratorze plików.
Przejdź do folderu Debug, który zawiera skompilowany plik exe i symbol pdb dla przykładu. Na przykład przejdź do folderu C:\Projects\DisplayGreeting\Debug, jeśli jest to folder, w którym są przechowywane projekty.
Uruchamianie przykładowej aplikacji z wadą kodu
Kliknij dwukrotnie plik exe, aby uruchomić przykładową aplikację.
Jeśli zostanie wyświetlone to okno dialogowe, wybierz pozycję Zamknij program
W następnej sekcji przewodnika zarejestrujemy wykonanie przykładowej aplikacji, aby sprawdzić, dlaczego ten wyjątek występuje.
Sekcja 2. Rejestrowanie śladu próbki "DisplayGreeting"
W sekcji 2 zarejestrujesz ślad nieprawidłowego zachowania aplikacji "DisplayGreeting"
Aby uruchomić przykładową aplikację i zarejestrować ślad TTD, wykonaj następujące kroki. Aby uzyskać ogólne informacje na temat rejestrowania śladów TTD, odwiedź stronę Debugowanie podróży w czasie — rejestrowanie śladu
Uruchom windbg jako administrator, aby móc rejestrować ślady podróży w czasie.
W usłudze WinDbg wybierz pozycję Plik>Rozpocznij debugowanie>Uruchom plik wykonywalny (zaawansowane).
Wprowadź ścieżkę do pliku wykonywalnego trybu użytkownika, który chcesz uruchomić, lub wybierz opcję Przeglądaj, aby przejść do tego pliku. Aby uzyskać informacje na temat pracy z menu uruchamiania pliku wykonywalnego w usłudze WinDbg, zobacz WinDbg — uruchamianie sesji trybu użytkownika.
Zaznacz pole Rekord z debugowaniem podróży czasowej , aby zarejestrować ślad po uruchomieniu pliku wykonywalnego.
Kliknij pozycję Konfiguruj i zarejestruj , aby rozpocząć nagrywanie.
Po wyświetleniu okna dialogowego "Konfigurowanie nagrywania" kliknij pozycję Rekord , aby uruchomić plik wykonywalny i rozpocząć nagrywanie.
Zostanie wyświetlone okno dialogowe nagrywania wskazujące, że śledzenie jest rejestrowane. Wkrótce potem aplikacja ulegnie awarii.
Kliknij pozycję Zamknij program, aby odrzucić okno dialogowe "DisplayGreeting przestało działać".
Gdy program ulegnie awarii, plik śledzenia zostanie zamknięty i zapisany na dysku.
Debuger automatycznie otworzy plik śledzenia i zaindeksuje go. Indeksowanie to proces, który umożliwia wydajne debugowanie pliku śledzenia. Ten proces indeksowania trwa dłużej w przypadku większych plików śledzenia.
(5120.2540): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: D:0 [Unindexed] Index !index Indexed 10/22 keyframes Indexed 20/22 keyframes Indexed 22/22 keyframes Successfully created the index in 755ms.
Uwaga
Klatka kluczowa to lokalizacja w śladzie używana do indeksowania. Ramki kluczowe są generowane automatycznie. Większe ślady będą zawierać więcej klatek kluczowych.
W tym momencie jesteś na początku pliku śledzenia i możesz poruszać się do przodu i do tyłu w czasie.
Po zarejestrowaniu śladu TTD możesz go odtworzyć lub pracować z plikiem śladu, na przykład udostępniając go współpracownikowi. Aby uzyskać więcej informacji na temat pracy z plikami śledzenia, zobacz Debugowanie podróży w czasie — praca z plikami śledzenia
W następnej sekcji tego laboratorium przeanalizujemy plik śledzenia, aby zlokalizować problem z naszym kodem.
Sekcja 3. Analizowanie nagrania pliku śledzenia w celu zidentyfikowania problemu z kodem
W sekcji 3 przeanalizujesz nagranie pliku śledzenia, aby zidentyfikować problem z kodem.
Konfigurowanie środowiska WinDbg
Dodaj lokalną lokalizację symboli do ścieżki symboli i załaduj ponownie symbole, wpisując następujące polecenia.
.sympath+ C:\MyProjects\DisplayGreeting\Debug .reloadDodaj lokalizację kodu lokalnego do ścieżki źródłowej, wpisując następujące polecenie.
.srcpath+ C:\MyProjects\DisplayGreeting\DisplayGreetingAby wyświetlić stan stosu i zmiennych lokalnych, na wstążce WinDbg wybierz pozycję Widok i Zmiennych lokalnych oraz Widok i Stos. Organizuj okna, aby umożliwić wyświetlanie ich, kod źródłowy i okna poleceń w tym samym czasie.
Na wstążce WinDbg wybierz pozycję Source (Źródło) i Open Source File (Plik typu open source). Znajdź plik DisplayGreeting.cpp i otwórz go.
Sprawdzanie wyjątku
Po załadowaniu pliku śledzenia zostaną wyświetlone informacje o wystąpieniu wyjątku.
2fa8.1fdc): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: 15:0 eax=68ef8100 ebx=00000000 ecx=77a266ac edx=69614afc esi=6961137c edi=004da000 eip=77a266ac esp=0023f9b4 ebp=0023fc04 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 ntdll!LdrpInitializeProcess+0x1d1c: 77a266ac 83bdbcfeffff00 cmp dword ptr [ebp-144h],0 ss:002b:0023fac0=00000000Użyj polecenia dx, aby wyświetlić listę wszystkich zdarzeń w nagraniu. Zdarzenie wyjątku jest wyświetlane w zdarzeniach.
0:000> dx -r1 @$curprocess.TTD.Events ... [0x2c] : Module Loaded at position: 9967:0 [0x2d] : Exception at 9BDC:0 [0x2e] : Thread terminated at 9C43:0 ...Uwaga
W tym przewodniku są używane trzy okresy, aby wskazać, że nadmiarowe dane wyjściowe zostały usunięte.
Kliknij zdarzenie wyjątku, aby wyświetlić informacje o tym zdarzeniu TTD.
0:000> dx -r1 @$curprocess.TTD.Events[17] @$curprocess.TTD.Events[17] : Exception at 68:0 Type : Exception Position : 68:0 [Time Travel] Exception : Exception of type Hardware at PC: 0X540020Kliknij pole Wyjątek, aby przejść do szczegółów danych wyjątku.
0:000> dx -r1 @$curprocess.TTD.Events[17].Exception @$curprocess.TTD.Events[17].Exception : Exception of type Hardware at PC: 0X540020 Position : 68:0 [Time Travel] Type : Hardware ProgramCounter : 0x540020 Code : 0xc0000005 Flags : 0x0 RecordAddress : 0x0Dane wyjątku wskazują, że jest to błąd sprzętowy zgłaszany przez procesor CPU. Zawiera również kod wyjątku 0xc0000005, który wskazuje, że jest to naruszenie dostępu. Zazwyczaj oznacza to, że próbujemy zapisać w pamięci, do której nie mamy dostępu.
Kliknij link [Time Travel] w zdarzeniu wyjątkowym, aby przejść do tej pozycji w śledzeniu.
0:000> dx @$curprocess.TTD.Events[17].Exception.Position.SeekTo() Setting position: 68:0 @$curprocess.TTD.Events[17].Exception.Position.SeekTo() (16c8.1f28): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: 68:0 eax=00000000 ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046 eip=00540020 esp=00effe4c ebp=00520055 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 00540020 ??Warto zauważyć, że w tym wyjściu stos i wskaźnik podstawowy wskazują na dwa bardzo różne adresy.
esp=00effe4c ebp=00520055Może to oznaczać uszkodzenie stosu — prawdopodobnie funkcja zakończyła się działaniem, a następnie doszło do uszkodzenia stosu. Aby to sprawdzić, musimy cofnąć się do momentu, zanim stan CPU został uszkodzony, i sprawdzić, czy możemy określić, kiedy doszło do uszkodzenia stosu.
Sprawdzanie zmiennych lokalnych i ustawianie punktu przerwania kodu
W punkcie awarii w procesie śledzenia często znajduje się kilka kroków po rzeczywistej przyczynie w kodzie obsługi błędów. Dzięki podróży czasowej możemy cofać się instrukcja po instrukcji, aby zlokalizować prawdziwą przyczynę.
Na wstążce Strona główna użyj polecenia Krok do tyłu, aby cofnąć trzy instrukcje. W trakcie tego kontynuuj obserwowanie okien stosu i pamięci.
W oknie poleceń zostanie wyświetlona pozycja podróży w czasie i rejestry, gdy cofniesz się o trzy instrukcje.
0:000> t- Time Travel Position: 67:40 eax=00000000 ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046 eip=00540020 esp=00effe4c ebp=00520055 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 00540020 ?? ??? 0:000> t- Time Travel Position: 67:3F eax=00000000 ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046 eip=0019193d esp=00effe48 ebp=00520055 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 DisplayGreeting!main+0x4d: 0019193d c3 0:000> t- Time Travel Position: 67:39 eax=0000004c ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046 eip=00191935 esp=00effd94 ebp=00effe44 iopl=0 nv up ei pl nz ac po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212 DisplayGreeting!main+0x45:Uwaga
W tym przewodniku dane wyjściowe polecenia pokazują polecenia, których można użyć zamiast opcji menu interfejsu użytkownika, aby umożliwić użytkownikom z preferencjami użycia wiersza polecenia używanie poleceń.
W tym momencie śladu nasz stos i wskaźnik podstawowy mają wartości, które mają większe znaczenie, więc wydaje się, że zbliżamy się do punktu w kodzie, w którym wystąpiło uszkodzenie.
esp=00effd94 ebp=00effe44Interesujące jest również to, że okno zmiennych lokalnych zawiera wartości z naszej aplikacji docelowej, a okno kodu źródłowego wyróżnia wiersz kodu, który jest gotowy do wykonania w tym momencie w przebiegu śladu.
Aby dokładniej zbadać, możemy otworzyć okno pamięci, aby wyświetlić zawartość w pobliżu adresu podstawowego wskaźnika pamięci 0x00effe44.
Aby wyświetlić skojarzone znaki ASCII, na wstążce Pamięć wybierz pozycję Tekst , a następnie pozycję ASCII.
Zamiast wskaźnika podstawowego wskazującego instrukcję wskazuje na nasz tekst wiadomości. Więc coś nie jest w tym miejscu, może to być blisko punktu w czasie, w którym uszkodziliśmy stos. Aby dokładniej zbadać, ustawimy punkt przerwania.
Uwaga
W tym bardzo małym przykładzie byłoby dość łatwo po prostu spojrzeć w kod, ale jeśli istnieją setki wierszy kodu i dziesiątki podprogramów, techniki opisane tutaj można użyć do zmniejszenia czasu niezbędnego do zlokalizowania problemu.
TTD i punkty przerwania
Używanie punktów przerwania to typowe podejście do wstrzymania wykonywania kodu w momencie zainteresowania. Funkcja TTD umożliwia ustawienie punktu przerwania i powrót w czasie do momentu osiągnięcia tego punktu przerwania po zarejestrowaniu śladu. Możliwość sprawdzenia stanu procesu po wystąpieniu problemu w celu określenia najlepszej lokalizacji punktu przerwania umożliwia dodatkowe debugowanie przepływów pracy unikatowych dla TTD.
Punkty przerwania dostępu do pamięci
Można ustawić punkty przerwania, które są uruchamiane, gdy lokalizacja pamięci jest dostępna. Użyj polecenia ba (break on access) z następującą składnią.
ba <access> <size> <address> {options}
| Opcja | Opis |
|---|---|
| e | wykonaj (gdy procesor CPU pobiera instrukcję z adresu) |
| r | odczyt/zapis (gdy procesor CPU odczytuje lub zapisuje pod adresem) |
| w | zapis (gdy CPU zapisuje do adresu) |
Należy pamiętać, że w danym momencie można ustawić tylko cztery punkty przerwania danych i należy upewnić się, że dane są poprawnie wyrównane, w przeciwnym razie nie zostanie uruchomiony punkt przerwania (słowa muszą być na adresach podzielnych przez 2, podwójne słowa muszą być podzielne przez 4, a poczwórne słowa przez 8).
Ustawianie przerwania w punkcie przerwania dostępu do pamięci dla wskaźnika podstawowego
W tym momencie w śladzie chcemy ustawić punkt przerwania na zapis do pamięci wskaźnika bazowego — ebp, który w naszym przykładzie to 00effe44. Aby to zrobić, użyj polecenia ba przy użyciu adresu, który chcemy monitorować. Chcemy monitorować zapisy dla czterech bajtów, więc ustawiamy parametr w4.
0:000> ba w4 00effe44Wybierz Widok, a następnie Punkty przerwania, aby potwierdzić, że są ustawione zgodnie z oczekiwaniami.
Z menu głównego wybierz pozycję Przejdź wstecz, aby wrócić w czasie do momentu osiągnięcia punktu przerwania.
0:000> g- Breakpoint 0 hit Time Travel Position: 5B:92 eax=0000000f ebx=003db000 ecx=00000000 edx=00cc1a6c esi=00d41046 edi=0053fde8 eip=00d4174a esp=0053fcf8 ebp=0053fde8 iopl=0 nv up ei pl nz ac pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000216 DisplayGreeting!DisplayGreeting+0x3a: 00d4174a c745e000000000 mov dword ptr [ebp-20h],0 ss:002b:0053fdc8=ccccccccWybierz pozycję Wyświetl , a następnie pozycję Ustawienia lokalne. W oknie ustawień lokalnych widać, że zmienna docelowa ma tylko część komunikatu, a źródło zawiera cały tekst. Te informacje wspierają założenie, że stos został uszkodzony.
W tym momencie możemy zbadać stos programu, aby zobaczyć, jaki kod jest aktywny. Na wstążce Widok wybierz Stos.
Ponieważ jest bardzo mało prawdopodobne, że funkcja wscpy_s() firmy Microsoft będzie zawierać usterkę kodu podobną do tej, przyjrzymy się dalej w stosie. Stos pokazuje, że Greeting!Główne wywołuje Greeting!GetCppConGreeting. W naszym bardzo małym przykładzie kodu możemy po prostu otworzyć kod w tym momencie i prawdopodobnie znaleźć błąd dość łatwo. Jednak aby zilustrować techniki, które mogą być używane z większym, bardziej złożonym programem, ustawimy nowy punkt przerwania, aby dokładniej zbadać.
Ustaw punkt przerwania przy dostępie dla funkcji GetCppConGreeting
Użyj okna punktów przerwania, aby wyczyścić istniejący punkt przerwania, klikając prawym przyciskiem myszy istniejący punkt przerwania i wybierając polecenie Usuń.
Określ adres funkcji DisplayGreeting!GetCppConGreeting przy użyciu polecenia dx.
0:000> dx &DisplayGreeting!GetCppConGreeting &DisplayGreeting!GetCppConGreeting : 0xb61720 [Type: void (__cdecl*)(wchar_t *,unsigned int)] [Type: void __cdecl(wchar_t *,unsigned int)]Użyj polecenia ba , aby ustawić punkt przerwania na dostęp do pamięci. Ponieważ funkcja będzie tylko odczytywana z pamięci na potrzeby wykonywania, musimy ustawić punkt przerwania r — odczyt.
0:000> ba r4 b61720Upewnij się, że w oknie punktów przerwania aktywny jest punkt przerwania odczytu sprzętu.
Zastanawiając się nad rozmiarem ciągu powitania, ustawimy okno obserwacji, aby wyświetlić wartość sizeof(greeting). Na wstążce Widok wybierz pozycję Obejrzyj i podaj parametr sizeof(greeting). Jeśli wartość nie znajduje się w zakresie, okno śledzenia wyświetli błąd: Nie można powiązać zmiennej 'greeting'.
W menu Podróż czasowa, użyj polecenia Podróż czasowa, aby rozpocząć lub użyj
!tt 0polecenia, aby przejść do początku śladu.0:000> !tt 0 Setting position to the beginning of the trace Setting position: 15:0 (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: 15:0 eax=68e28100 ebx=00000000 ecx=77a266ac edx=69e34afc esi=69e3137c edi=00fa2000 eip=77a266ac esp=00ddf3b8 ebp=00ddf608 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 ntdll!LdrpInitializeProcess+0x1d1c: 77a266ac 83bdbcfeffff00 cmp dword ptr [ebp-144h],0 ss:002b:00ddf4c4=00000000W menu Głównym wybierz pozycję Przejdź lub użyj polecenia
g, aby przejść do przodu w kodzie do momentu trafienia punktu przerwania.0:000> g Breakpoint 2 hit Time Travel Position: 4B:1AD eax=00ddf800 ebx=00fa2000 ecx=00ddf800 edx=00b61046 esi=00b61046 edi=00b61046 eip=00b61721 esp=00ddf7a4 ebp=00ddf864 iopl=0 nv up ei pl nz na po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202 DisplayGreeting!GetCppConGreeting+0x1: 00b61721 8bec mov ebp,espW menu głównym wybierz pozycję Wyjdź jeden krok do tyłu lub użyj polecenia
g-u, aby wykonać krok wstecz.0:000> g-u Time Travel Position: 4B:1AA eax=00ddf800 ebx=00fa2000 ecx=00ddf800 edx=00b61046 esi=00b61046 edi=00b61046 eip=00b61917 esp=00ddf7ac ebp=00ddf864 iopl=0 nv up ei pl nz na po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202 DisplayGreeting!main+0x27: 00b61917 e8def7ffff call DisplayGreeting!ILT+245(?GetCppConGreetingYAXPA_WIZ) (00b610fa)Wygląda na to, że znaleźliśmy główną przyczynę. Zadeklarowana tablica powitania ma długość 50 znaków, a parametr sizeof(greeting), który przekazujemy do metody GetCppConGreeting, jest 0x64, 100.
Jak dalej przyjrzymy się problemowi dotyczące rozmiaru, zauważymy również, że komunikat ma długość 75 znaków i jest 76, gdy zawiera znak końca ciągu.
HELLO FROM THE WINDBG TEAM. GOOD LUCK IN ALL OF YOUR TIME TRAVEL DEBUGGING!Jednym ze sposobów naprawienia kodu byłoby rozszerzenie rozmiaru tablicy znaków na 100.
std::array <wchar_t, 100> greeting{};Musimy również zmienić sizeof(greeting) na size(greeting) w tym wierszu kodu.
GetCppConGreeting(greeting.data(), size(greeting));Aby zweryfikować te poprawki, możemy ponownie skompilować kod i potwierdzić, że działa bez błędu.
Ustawianie punktu przerwania przy użyciu okna źródłowego
Alternatywnym sposobem wykonania tego badania jest ustawienie punktu przerwania przez kliknięcie dowolnego wiersza kodu. Na przykład kliknięcie po prawej stronie linii definicji std:array w oknie źródłowym spowoduje ustawienie tam punktu przerwania.
Na menu Podróż czasowa użyj polecenia Podróż czasowa, aby rozpocząć, aby przejść na początek śladu.
0:000> !tt 0 Setting position to the beginning of the trace Setting position: 15:0 (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: 15:0 eax=68e28100 ebx=00000000 ecx=77a266ac edx=69e34afc esi=69e3137c edi=00fa2000 eip=77a266ac esp=00ddf3b8 ebp=00ddf608 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 ntdll!LdrpInitializeProcess+0x1d1c: 77a266ac 83bdbcfeffff00 cmp dword ptr [ebp-144h],0 ss:002b:00ddf4c4=00000000Na wstążce Narzędzia główne kliknij pozycję Przejdź , aby wrócić do momentu trafienia punktu przerwania.
Breakpoint 0 hit Time Travel Position: 5B:AF eax=0000000f ebx=00c20000 ecx=00000000 edx=00000000 esi=013a1046 edi=00effa60 eip=013a17c1 esp=00eff970 ebp=00effa60 iopl=0 nv up ei pl nz na po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202 DisplayGreeting!DisplayGreeting+0x41: 013a17c1 8bf4 mov esi,esp
Ustaw przerwanie na dostęp dla zmiennej greeting
Innym alternatywnym sposobem przeprowadzenia tego badania byłoby ustawienie punktu przerwania dla podejrzanych zmiennych i sprawdzenie, jaki kod je zmienia. Aby na przykład ustawić punkt przerwania w zmiennej powitania w metodzie GetCppConGreeting, użyj tej procedury.
W tej części przewodnika zakłada się, że nadal znajdujesz się na punkcie przerwania z poprzedniej części.
W widoku , a następnie w ustawieniach lokalnych. W oknie ustawień lokalnych powitanie jest dostępne w bieżącym kontekście, więc będziemy mogli określić jego lokalizację pamięci.
Użyj polecenia dx, aby zbadać tablicę greeting.
0:000> dx &greeting &greeting : ddf800 : { size=50 } [Type: std::array<wchar_t,50> *] [<Raw View>] [Type: std::array<wchar_t,50>] [0] : 3 [Type: wchar_t] [1] : 0 [Type: wchar_t]W tym odcinku powitanie znajduje się w pamięci na adresie ddf800.
Użyj okna punktów przerwania, aby wyczyścić istniejący punkt przerwania, klikając prawym przyciskiem myszy istniejący punkt przerwania i wybierając polecenie Usuń.
Ustaw punkt przerwania za pomocą polecenia ba przy użyciu adresu pamięci, który chcemy monitorować pod kątem dostępu do zapisu.
ba w4 ddf800W menu Podróż czasowa, użyj polecenia Podróż czasowa, aby przejść na początek śladu.
0:000> !tt 0 Setting position to the beginning of the trace Setting position: 15:0 (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: 15:0 eax=68e28100 ebx=00000000 ecx=77a266ac edx=69e34afc esi=69e3137c edi=00fa2000 eip=77a266ac esp=00ddf3b8 ebp=00ddf608 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 ntdll!LdrpInitializeProcess+0x1d1c: 77a266ac 83bdbcfeffff00 cmp dword ptr [ebp-144h],0 ss:002b:00ddf4c4=00000000W menu Narzędzia główne wybierz pozycję Przejdź , aby przejść do przodu do pierwszego punktu dostępu do pamięci tablicy powitania.
0:000> g- Breakpoint 0 hit Time Travel Position: 5B:9C eax=cccccccc ebx=002b1000 ecx=00000000 edx=68d51a6c esi=013a1046 edi=001bf7d8 eip=013a1735 esp=001bf6b8 ebp=001bf7d8 iopl=0 nv up ei pl nz na po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202 DisplayGreeting!GetCppConGreeting+0x25: 013a1735 c745ec04000000 mov dword ptr [ebp-14h],4 ss:002b:001bf7c4=ccccccccAlternatywnie moglibyśmy przejść na koniec śladu i pracować odwrotnie przez kod, aby znaleźć ten ostatni punkt w śladzie, do którego zapisano lokalizację pamięci tablicy.
Użyj obiektów pamięci TTD.Memory do wyświetlania dostępu do pamięci
Innym sposobem określenia, w którym momencie w pamięci śladu uzyskiwano do niej dostęp, jest użycie obiektów TTD.Memory i polecenia dx.
Użyj polecenia dx, aby zbadać tablicę greeting.
0:000> dx &greeting &greeting : 0xddf800 [Type: std::array<wchar_t,50> *] [+0x000] _Elems : "꽘棶檙瞝???" [Type: wchar_t [50]]W tym trace "powitanie" znajduje się w pamięci pod adresem ddf800.
Użyj polecenia dx , aby przyjrzeć się czterem bajtom w pamięci, zaczynając od tego adresu z dostępem do odczytu zapisu.
0:000> dx -r1 @$cursession.TTD.Memory(0xddf800,0xddf804, "rw") @$cursession.TTD.Memory(0x1bf7d0,0x1bf7d4, "rw") [0x0] [0x1] [0x2] [0x3] [0x4] [0x5] [0x6] [0x7] [0x8] [0x9] [0xa] ...Kliknij dowolne z wystąpień, aby wyświetlić więcej informacji o tym wystąpieniu dostępu do pamięci.
0:000> dx -r1 @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5] @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5] EventType : MemoryAccess ThreadId : 0x710 UniqueThreadId : 0x2 TimeStart : 27:3C1 [Time Travel] TimeEnd : 27:3C1 [Time Travel] AccessType : Write IP : 0x6900432f Address : 0xddf800 Size : 0x4 Value : 0xddf818 OverwrittenValue : 0x0 SystemTimeStart : Monday, November 18, 2024 23:01:43.400 SystemTimeEnd : Monday, November 18, 2024 23:01:43.400Kliknij pozycję [Time Travel] w polu TimeStart, aby umieścić ślad w punkcie w czasie.
0:000> dx @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5].TimeStart.SeekTo() @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5].TimeStart.SeekTo() (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: 27:3C1 eax=00ddf81c ebx=00fa2000 ecx=00ddf818 edx=ffffffff esi=00000000 edi=00b61046 eip=6900432f esp=00ddf804 ebp=00ddf810 iopl=0 nv up ei pl nz ac po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212 ucrtbased!_register_onexit_function+0xf: 6900432f 51 push ecxJeśli interesuje nas ostatnie wystąpienie dostępu do pamięci odczytu/zapisu w śledzeniu, możemy kliknąć na ostatni element na liście lub dołączyć funkcję .Last() na końcu polecenia dx.
0:000> dx -r1 @$cursession.TTD.Memory(0xddf800,0xddf804, "rw").Last() @$cursession.TTD.Memory(0xddf800,0xddf804, "rw").Last() EventType : MemoryAccess ThreadId : 0x710 UniqueThreadId : 0x2 TimeStart : 53:100E [Time Travel] TimeEnd : 53:100E [Time Travel] AccessType : Read IP : 0x690338e4 Address : 0xddf802 Size : 0x2 Value : 0x45 SystemTimeStart : Monday, November 18, 2024 23:01:43.859 SystemTimeEnd : Monday, November 18, 2024 23:01:43.859Następnie możemy kliknąć pozycję [Time Travel], aby przejść do tej pozycji w śladzie i dokładniej przyjrzeć się wykonaniu kodu w tym momencie, korzystając z technik opisanych wcześniej w tym laboratorium.
Aby uzyskać więcej informacji na temat obiektów pamięci TTD, zobacz TTD.Obiekt pamięci.
Podsumowanie
W tym bardzo małym przykładzie problem mógł zostać określony przez przyjrzenie się kilku wierszom kodu, ale w większych programach techniki przedstawione tutaj mogą służyć do skrócenia czasu niezbędnego do zlokalizowania problemu.
Po zarejestrowaniu śladu można udostępnić kroki śledzenia i odtwarzania, a problem będzie powtarzalny na dowolnym komputerze.
Zobacz też
Debugowanie podróży w czasie — omówienie
Debugowanie podróży w czasie — nagrywanie