Rozszerzanie narzędzia Memory Investor na potrzeby debugowania C/C++

W Chrome 92 wprowadziliśmy inspektora pamięci, czyli narzędzie do sprawdzania liniowych buforów pamięci. W tym artykule omówimy ulepszenia, które wprowadziliśmy w narzędzie Inspekcja na potrzeby debugowania w językach C/C++, oraz problemy techniczne, z którymi się przy tym spotkaliśmy.

Jeśli dopiero zaczynasz debugować w C/C++ i używać przeglądarki pamięci, zapoznaj się z tymi postami na blogu:

Wprowadzenie

Inspektor pamięci udostępnia bardziej zaawansowane opcje debugowania dla liniowych buforów pamięci. W przypadku C/C++ możesz sprawdzić obiekty pamięci C/C++ w pamięci WebAssembly.

Rozpoznawanie bajtów obiektu wśród sąsiadujących danych pamięci WebAssembly było problemem. Musisz znać rozmiar obiektu i zliczać bajty od jego początku. Na poniższym zrzucie ekranu wybrany jest pierwszy bajt 10-elementowej tablicy int32, ale nie jest od razu jasne, które inne bajty do niej należą. Czy nie byłoby miło, gdyby można było od razu rozpoznać wszystkie bajty należące do obiektu?

Zrzut ekranu oryginalnego narzędzia do inspekcji pamięci z wyróżnionym pojedynczym bajtem

Podświetlanie obiektów w narzędziu do inspekcji pamięci

Począwszy od Chrome 107, kontroler pamięci wyróżnia wszystkie bajty obiektu pamięci C/C++. Dzięki temu można je odróżnić od pamięci sąsiadującej.

Zrzut ekranu z aktualizowanym narzędziem do sprawdzania pamięci z wyróżnionym macierzą

Aby zobaczyć, jak działa Memory Inspector, obejrzyj film poniżej. Gdy ujawnisz tablicę x w narzędzie do inspekcji pamięci, w przeglądarce pamięci pojawi się wyróżniona pamięć wraz z nowym elementem graficznym tuż nad nią. Ten element przypomina nazwę i typ wyróżnionej pamięci. Kliknij chip, aby przejść do pamięci obiektu. Jeśli najedziesz kursorem na element, pojawi się ikona krzyżyka. Kliknij ją, aby usunąć wyróżnienie.

Gdy wybierzesz bajt poza sprawdzanym obiektem, podświetlenie zostanie rozmyte, aby nie rozpraszać. Aby ponownie ustawić ostrość, kliknij dowolny bajt obiektu lub ponownie kliknij chip.

Podświetlenie obiektów nie jest ograniczone do tablic. Możesz też sprawdzać struktury, obiekty i wskaźniki. Dzięki tym zmianom eksplorowanie pamięci aplikacji C/C++ jest teraz łatwiejsze niż kiedykolwiek.

Chcesz spróbować? Musisz:

  • mieć przeglądarkę Chrome w wersji 107 lub nowszej,
  • Zainstaluj rozszerzenie DWARF dla C/C++.
  • Włącz debugowanie DWARF w Narzędzia deweloperskieUstawienia. Ustawienia > Eksperymenty > Debugowanie WebAssembly: włącz obsługę DWARF.
  • Otwórz tę stronę demonstracyjną.
  • Postępuj zgodnie z instrukcjami wyświetlanymi na stronie.

Przykład debugowania

W tej sekcji przyjrzymy się błądowi testowemu, aby pokazać, jak można używać kontrolera pamięci do debugowania kodu C/C++. W przykładowym kodzie poniżej programista tworzy tablicę liczb całkowitych i decyduje się użyć arytmetyki wskaźnika, aby wybrać ostatni element. Niestety programista popełnił błąd przy obliczaniu wskaźnika i teraz zamiast drukowania ostatniego elementu program drukuje bezsensowne wartości.

#include <iostream>

int main()
{
    int numbers[] = {1, 2, 3, 4};
    int *ptr = numbers;
    int arraySize = sizeof(numbers)/sizeof(int);
    int* lastNumber = ptr + arraySize;  // Can you notice the bug here?
    std::cout <<../ *lastNumber <<../ '\n';
    return 0;
}

Programista korzysta z kontrolera pamięci, aby debugować problem. Możesz śledzić to demonstracja. Najpierw sprawdzają tablicę w inspektorze pamięci i widzi, że tablica numbers zawiera tylko liczby całkowite 1, 2, 34, zgodnie z oczekiwaniami.

Zrzut ekranu z otwartym narzędziem do inspekcji pamięci z przeglądanym tablicą int32. Wszystkie elementy tablicy są wyróżnione.

Następnie ujawniają zmienną lastNumber w panelu Zakres i zauważają, że wskaźnik wskazuje na liczbę całkowitą spoza tablicy. Dzięki tej wiedzy programista zdaje sobie sprawę, że w wierszu 8 błędnie policzył przesunięcie wskaźnika. Powinien być to ptr + arraySize - 1.

Zrzut ekranu z otwartą funkcją inspekcji pamięci pokazujący wyróżnioną pamięć wskazywaną przez wskaźnik o nazwie „lastNumber”. Wyróżniona pamięć znajduje się tuż za ostatnim bajtem wcześniej wyróżnionego tablicę.

Chociaż jest to przykładowa aplikacja, pokazuje ona, jak podświetlenie obiektu skutecznie przekazuje rozmiar i położenie obiektów pamięci, co może pomóc w lepszym zrozumieniu tego, co dzieje się w pamięci aplikacji C/C++.

Jak Narzędzia deweloperskie określają, co wyróżnić

W tej sekcji przyjrzymy się ekosystemowi narzędzi do debugowania kodu C/C++. W szczególności dowiesz się, jak Debugowanie w Chrome umożliwia debugowanie w języku C/C++ za pomocą Narzędzi deweloperskich, V8, rozszerzenia C/C++ DWARF i Emscripten.

Aby w pełni wykorzystać możliwości debugowania kodu C/C++ w Narzędziach deweloperskich, musisz mieć:

  • rozszerzenie DWARF w C/C++ zainstalowane w Chrome,
  • pliki źródłowe C/C++, skompilowane do WebAssembly za pomocą najnowszego kompilatora Emscripten zgodnie z instrukcjami podanymi w tym wpisie na blogu;

Ale dlaczego? V8, czyli silnik JavaScriptu i WebAssembly w Chrome, nie wie, jak wykonać kod w języku C ani C++. Dzięki Emscriptenowi, czyli kompilatorowi C/C++ na WebAssembly, możesz kompilować aplikacje napisane w C lub C++ jako WebAssembly i wykonywywać je w przeglądarce.

Podczas kompilacji emscripten umieszcza dane debugowania DWARF w pliku binarnym. Ogólnie rzecz biorąc, te dane pomagają rozszerzeniu ustalić, które zmienne WebAssembly odpowiadają zmiennym C/C++ i nie tylko. Dzięki temu DevTools może wyświetlać zmienne C++, mimo że V8 faktycznie wykonuje WebAssembly. Jeśli chcesz zobaczyć przykład danych debugowania DWARF, przeczytaj ten post na blogu.

Co się dzieje, gdy odsłonisz lastNumber? Gdy tylko klikniesz ikonę pamięci, Narzędzia dla programistów sprawdzą, którą zmienną chcesz sprawdzić. Następnie wysyła zapytanie do rozszerzenia o typ danych i lokalizację lastNumber. Gdy rozszerzenie odpowie z tymi informacjami, Memory Inspector może wyświetlić odpowiedni fragment pamięci. Wiedząc, jaki jest typ obiektu, może też wyświetlić jego rozmiar.

Jeśli spojrzysz na lastNumber w poprzednim przykładzie, możesz zauważyć, że skontrolowaliśmy lastNumber: int *, ale chip w Memory Inspector podaje *lastNumber: int. Co się dzieje? Inspektor używa odwołania do wskaźnika w stylu C++, aby wskazać typ wyświetlanego obiektu. Jeśli skontrolujesz wskaźnik, zobaczysz, na co wskazuje.

Zachowywanie wyróżnień w krokach w debugerze

Gdy ujawnisz obiekt w Narzędzie do inspekcji pamięci i przejdziesz do debugera, Narzędzie do inspekcji zachowa podświetlenie, jeśli uzna, że nadal jest ono aktualne. Początkowo nie było to uwzględnione w planach, ale szybko zdaliśmy sobie sprawę, że utrudnia to debugowanie. Wyobraź sobie, że po każdym kroku musisz ponownie sprawdzić tablicę, tak jak w tym filmie poniżej.

Gdy debuger dotrze do nowego punktu przerwania, Memory Inspector ponownie wysyła zapytanie do V8 i do rozszerzenia o zmienną powiązaną z poprzednim wyróżnieniem. Następnie porównuje ich lokalizacje i typy. Jeśli pasują, wyróżnienie pozostanie. W tym filmie występuje pętla for, która zapisuje dane do tablicy x. Te operacje nie zmieniają typu ani pozycji tablicy, więc pozostaje ona podświetlona.

Zastanawiasz się pewnie, jak to wpływa na wskaźniki. Jeśli masz podświetlony wskaźnik i przypiszesz go do innego obiektu, stare i nowe położenie podświetlonego obiektu będą się różnić, a podświetlenie zniknie. Ponieważ nowo wskazany obiekt może znajdować się w dowolnym miejscu w pamięci WebAssembly i prawdopodobnie będzie miał niewiele wspólnego z poprzednim miejscem w pamięci, usunięcie podświetlenia jest prostsze niż przejście do nowego miejsca w pamięci. Możesz ponownie wyróżnić wskaźnik, klikając jego ikonę pamięci w panelu Zakres.

Podsumowanie

W tym artykule opisaliśmy ulepszenia narzędzia Memory Inspector na potrzeby debugowania kodu C/C++. Mamy nadzieję, że nowe funkcje ułatwią debugowanie pamięci w aplikacjach C/C++. Jeśli masz sugestie dotyczące dalszego ulepszania tej funkcji, zgłoś błąd.

Co dalej?

Aby dowiedzieć się więcej, zapoznaj się z tymi artykułami: