Debugowanie WebAssembly za pomocą nowoczesnych narzędzi

Ingvar Stepanyan
Ingvar Stepanyan

Droga do tej pory

Rok temu ogłosiliśmy wprowadzenie pomocy w Chrome do natywnego debugowania WebAssembly w Narzędziach deweloperskich w Chrome.

Zademonstrowaliśmy podstawowe wsparcie i rozmawialiśmy o możliwościach użycie informacji DWARF zamiast mapy źródłowe otworzy się w przyszłości:

  • Rozpoznawanie nazw zmiennych
  • Rodzaje druku estetycznego
  • Obliczanie wyrażeń w językach źródłowych
  • …i wiele innych.

Z przyjemnością przedstawiamy, w jaki sposób weszły w życie obiecane funkcje oraz postęp, jaki zespoły Emscripten i Chrome Dev Tools osiągnęły w tym roku, zwłaszcza w przypadku aplikacji w języku C i C++.

Zanim zaczniemy, miej na uwadze, że jest to nadal wersja beta musisz używać najnowszych wersji wszystkich narzędzi, na własne ryzyko. W razie jakichkolwiek problemów zgłoś je do https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350.

Zacznijmy od tego samego prostego przykładu w języku C, co ostatnio:

#include <stdlib.h>

void assert_less(int x, int y) {
  if (x >= y) {
    abort();
  }
}

int main() {
  assert_less(10, 20);
  assert_less(30, 20);
}

Do skompilowania używamy najnowszej wersji Emscripten i przekazujemy flagę -g, tak jak w pierwotnym poście, aby uwzględnić informacje debugowania:

emcc -g temp.c -o temp.html

Teraz możemy wyświetlić stronę wygenerowaną z serwera HTTP hosta lokalnego (dla np. obsługa), otwórz ją w najnowszej wersji Chrome Canary.

Tym razem potrzebne będzie rozszerzenie pomocnicze, które integruje się z Chrome. Narzędzia deweloperskie i pomagają w interpretowaniu wszystkich informacji na potrzeby debugowania. zakodowane w pliku WebAssembly. Zainstaluj ją, przechodząc pod ten Link: goo.gle/wasm-debugging-extension

W DevTools możesz też włączyć debugowanie WebAssembly w sekcji Eksperymenty. Otwórz Narzędzia deweloperskie w Chrome i kliknij ikonę koła zębatego () w w prawym górnym rogu panelu Narzędzia deweloperskie otwórz panel Eksperymenty. i zaznacz WebAssembly Debugging: Włącz obsługę DWARF.

Panel Eksperymenty w ustawieniach Narzędzi deweloperskich

Gdy zamkniesz Ustawienia, Narzędzia deweloperskie zasugerują ponowne załadowanie strony. aby zastosować ustawienia, więc zróbmy to tak. To by było na tyle, jeśli chodzi o jednorazowe wydarzenie konfiguracji.

Teraz wróćmy do panelu Źródła, włącz opcję Wstrzymaj wyjątki (ikona ⏸), a następnie zaznacz opcję Wstrzymaj przy wykrytych wyjątkach i odśwież stronę. Narzędzia deweloperskie powinny być wstrzymane przy wyjątku:

Zrzut ekranu panelu Źródła pokazujące, jak włączyć opcję „Wstrzymaj przy wykrytych wyjątkach”

Domyślnie zatrzymuje się on na kodzie kleju wygenerowanym przez Emscripten, ale na stronie zobaczysz widok Stosunek wywołań, który przedstawia zrzut stosu i może przejść do oryginalnego wiersza C, który wywołał abort:

Narzędzia deweloperskie zostały wstrzymane w funkcji `assert_less` i wyświetlają wartości „x” i „y” w widoku zakresu

Teraz w widoku Zakres zobaczysz oryginalne nazwy i wartości zmiennych w kodzie C/C++, dzięki czemu nie trzeba co oznaczają zniekształcone nazwy, takie jak $localN, i jaki mają związek z napisany przez Ciebie kod źródłowy.

Dotyczy to nie tylko wartości podstawowych, takich jak liczby całkowite, np. struktury, klasy, tablice itp.

Obsługa typów rozszerzonych

Spójrzmy na bardziej skomplikowany przykład. Ten narysujemy fraktal Mandelbrota z ten kod w C++:

#include <SDL2/SDL.h>
#include <complex>

int main() {
  // Init SDL.
  int width = 600, height = 600;
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Window* window;
  SDL_Renderer* renderer;
  SDL_CreateWindowAndRenderer(width, height, SDL_WINDOW_OPENGL, &window,
                              &renderer);

  // Generate a palette with random colors.
  enum { MAX_ITER_COUNT = 256 };
  SDL_Color palette[MAX_ITER_COUNT];
  srand(time(0));
  for (int i = 0; i < MAX_ITER_COUNT; ++i) {
    palette[i] = {
        .r = (uint8_t)rand(),
        .g = (uint8_t)rand(),
        .b = (uint8_t)rand(),
        .a = 255,
    };
  }

  // Calculate and draw the Mandelbrot set.
  std::complex<double> center(0.5, 0.5);
  double scale = 4.0;
  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      std::complex<double> point((double)x / width, (double)y / height);
      std::complex<double> c = (point - center) * scale;
      std::complex<double> z(0, 0);
      int i = 0;
      for (; i < MAX_ITER_COUNT - 1; i++) {
        z = z * z + c;
        if (abs(z) > 2.0)
          break;
      }
      SDL_Color color = palette[i];
      SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
      SDL_RenderDrawPoint(renderer, x, y);
    }
  }

  // Render everything we've drawn to the canvas.
  SDL_RenderPresent(renderer);

  // SDL_Quit();
}

Jak widać, aplikacja jest nadal dość mała – to pojedynczy plik zawierający 50 wierszy kodu. Tym razem używam też niektórych zewnętrznych interfejsów API, takich jak bibliotek SDL do grafiki oraz liczbami zespolonymi z biblioteki standardowej C++.

Skompiluję go z tą samą flagą -g co powyżej, aby uwzględnić debugowania. Poproszę też Emscripten o przesłanie pliku SDL2 pozwalająca korzystać z pamięci o dowolnym rozmiarze:

emcc -g mandelbrot.cc -o mandelbrot.html \
     -s USE_SDL=2 \
     -s ALLOW_MEMORY_GROWTH=1

Gdy wchodzę na wygenerowaną stronę w przeglądarce, widzę fraktalny kształt o losowych kolorach:

Strona demonstracyjna

Po otwarciu Narzędzi deweloperskich ponownie widzę oryginalny plik C++. Ten ale w kodzie nie ma błędu (ale!), więc na początku kodu.

Po ponownym załadowaniu strony debuger zatrzyma się Kod źródłowy w języku C++:

Narzędzia deweloperskie zostały wstrzymane na wywołaniu `SDL_Init`

Wszystkie zmienne są już widoczne po prawej stronie, ale tylko width i height są w tej chwili zainicjowane, więc nie ma za wiele do zrobienia sprawdzić.

Ustawmy kolejny punkt przerwania w głównej pętli Mandelbrota i wznowimy aby przewinąć film do przodu.

Narzędzia deweloperskie zostały wstrzymane wewnątrz zagnieżdżonych pętli

Na tym etapie pole palette zostało wypełnione losowymi kolorami, i możemy rozszerzyć zarówno samą tablicę, jak i pojedynczą SDL_Color i sprawdź ich komponenty, aby sprawdzić, wszystko wygląda dobrze (np. kanał alfa jest zawsze ustawiony, do pełnej nieprzezroczystości). Możemy również rozwijać i sprawdzać rzeczywiste urojone części liczby zespolonej przechowywane w zmiennej center.

Jeśli chcesz uzyskać dostęp do głęboko zagnieżdżonej właściwości, która jest trudna do użycia w innym miejscu przejdź przez widok Zakres, możesz użyć Konsoli. także jego ocenę. Pamiętaj jednak, że bardziej złożone wyrażenia w języku C++ nie są jeszcze obsługiwane.

Panel konsoli z wynikiem dla atrybutu „palette[10].r”

Wznówmy wykonanie kilka razy, aby zobaczyć, jak działa wewnętrzna strona x możesz zmienić, patrząc ponownie w widoku Zakres, dodając nazwę zmiennej do listy obserwacyjnej, oceny jej w konsoli lub najedź kursorem na zmienną w kodzie źródłowym.

Etykietka nad zmienną „x” w źródle z jej wartością „3”

Następnie możemy dodać stwierdzenia w języku C++ i zobaczyć, jak inne zmienne również się zmieniają:

Wskaźniki i widok zakresu pokazujące wartości zmiennych „color”, „point” i innych

Wszystko działa świetnie, gdy dostępne są informacje debugowania, ale co, jeśli chcemy debugować kod, który nie został skompilowany z opcjami debugowania?

Debugowanie nieprzetworzonego zasobu WebAssembly

Na przykład poprosiliśmy Emscripten o udostępnienie gotowej biblioteki SDL do zamiast kompilować sami na podstawie źródła, obecnie debuger nie może znaleźć powiązanych źródeł. Aby dowiedzieć się więcej o SDL_RenderDrawColor, zapoznaj się z tymi informacjami:

Narzędzia deweloperskie przedstawiające widok rozkładu pliku „mandelbrot.wasm”

Wracamy do nieprzetworzonego interfejsu debugowania WebAssembly.

Teraz wygląda to trochę strasznie, a większość programistów stron internetowych nie ale od czasu do czasu warto biblioteka utworzona bez danych debugowania. Zewnętrzna biblioteka, nad którą nie masz kontroli lub dlatego, że na któryś z błędów, które pojawiają się tylko w wersji produkcyjnej.

Aby im w tym pomóc, wprowadziliśmy kilka ulepszeń debugowania.

Jeśli już wcześniej zdarzyło Ci się korzystać z debugowania nieprzetworzonego WebAssembly, że cała demontaż jest teraz przedstawiony w jednym pliku bardziej zgadywanie, której funkcji prawdopodobnie odpowiada wpis Źródła wasm-53834e3e/ wasm-53834e3e-7.

Nowy schemat generowania nazw

Poprawiliśmy też nazwy w widoku rozłożenia. Poprzednio tylko indeksy liczbowe, a w przypadku funkcji – brak nazwy.

Teraz generujemy nazwy podobne do innych narzędzi do demontażu, korzystając ze wskazówek z sekcji nazwy WebAssembly, importowanie/eksportowanie i, jeśli wszystko inne zawiodło, generowanie na podstawie typu i indeksu elementu takiego jak $func123. Dostępne opcje Na zrzucie ekranu powyżej pozwala to uzyskać nieco czytelniejsze schematy i demontaż.

Gdy informacje o typie są niedostępne, ich sprawdzenie może być trudne wartości inne niż podstawowe – na przykład wskaźniki pojawią się jako zwykłe liczby całkowite, bez możliwości sprawdzenia, co za nimi jest pamięci.

Sprawdzenie pamięci

Wcześniej można było rozwinąć tylko obiekt pamięci WebAssembly, reprezentowany przez env.memory w widoku Zakres, aby wyszukać poszczególne bajty. Sprawdzało się to w kilku prostych sytuacjach, ale nie było co jest szczególnie wygodne i nie pozwala na ponowną interpretację danych, w formatach innych niż bajty. Dodaliśmy też nową funkcję, która pomoże Ci w tym zakresie: inspektor pamięci liniowej.

Po kliknięciu env.memory prawym przyciskiem myszy powinien być widoczny nowy opcja Sprawdź pamięć:

Menu kontekstowe „env.memory” w panelu Zakres z elementem „Sprawdź pamięć”

Po kliknięciu otworzy się Inspektor pamięci, który można sprawdzić pamięć WebAssembly w widokach szesnastkowych i ASCII, przejść do konkretnych adresów, a także zinterpretować dane w różne formaty:

Panel inspektora pamięci w Narzędziach deweloperskich z widokiem pamięci w postaci szesnastkowej i ASCII.

Zaawansowane scenariusze i zastrzeżenia

Profilowanie kodu WebAssembly

Gdy otworzysz Narzędzia deweloperskie, kod WebAssembly zostanie „zredukowany” do wersji nieoptymalizowanej, aby umożliwić debugowanie. Ta wersja jest znacznie wolniejsza, co oznacza, że nie możesz polegać na console.time, performance.nowi innych metodach pomiaru szybkości kodu, gdy DevTools są otwarte, ponieważ uzyskane liczby w żaden sposób nie odzwierciedlają rzeczywistej wydajności.

Zamiast tego użyj panelu wydajności w DevTools, który uruchomi kod z pełną prędkością i zapewni szczegółowy podział czasu spędzonego na różnych funkcjach:

Panel profilowania przedstawiający różne funkcje Wasm

Możesz też uruchomić aplikację z zamkniętymi Narzędziami deweloperskimi, otwórz je po zakończeniu sprawdzania, aby sprawdzić konsolę.

W przyszłości będziemy ulepszać scenariusze profilowania, ale na razie jest to należy pamiętać o zastrzeżeniu. Jeśli chcesz dowiedzieć się więcej o WebAssembly scenariuszy na poziomie warstw znajdziesz w dokumentacji dotyczącej potoku kompilacji WebAssembly.

kompilowanie i debugowanie na różnych komputerach (w tym Dockerze / hostze).

Podczas tworzenia w Dockerze, maszynie wirtualnej lub na zdalnym serwerze kompilacji: możesz napotkać ścieżki do plików źródłowych, używane podczas kompilacji, nie pasują do ścieżek w Twoim systemie plików, uruchomionych Narzędzi deweloperskich w Chrome. W takim przypadku pliki pojawią się w panelu Źródła, ale nie można go wczytać.

Aby rozwiązać ten problem, wdrożyliśmy funkcję mapowania ścieżek w z opcjami rozszerzeń C/C++. Można go używać do mapowania dowolnych ścieżek aby pomóc Narzędziom deweloperskim zlokalizować źródła.

Jeśli np. projekt na maszynie hosta znajduje się pod ścieżką C:\src\my_project, ale został utworzony w kontenerze Dockera, w którym ta ścieżka jest reprezentowana jako /mnt/c/src/my_project, możesz ją ponownie przypisać podczas debugowania, podając te ścieżki jako prefiksy:

Strona opcji rozszerzenia do debugowania C/C++

Pierwszy pasujący prefiks „wygrane”. Jeśli znacie inne wersje C++ debugerów, ta opcja jest podobna do polecenia set substitute-path w GDB lub w ustawieniu target.source-map w LLDB.

Debugowanie zoptymalizowanych kompilacji

Podobnie jak w przypadku każdego innego języka, debugowanie działa najlepiej, jeśli optymalizacje wyłączono. Optymalizacje mogą wbudować funkcje do siebie, zmienić ich kolejność lub całkowicie usunąć jego fragmenty. mogą zmylić debugera, a tym samym – użytkownika.

Jeśli chcesz mieć bardziej ograniczone możliwości debugowania debugowanie zoptymalizowanej kompilacji, większość optymalizacji będzie działać z wyjątkiem wbudowania funkcji. Pozostałe problemy planujemy rozwiązać w przyszłości, ale na razie zalecamy użycie opcji -fno-inline, aby wyłączyć kompilację z jakąkolwiek optymalizacją na poziomie -O, np.:

emcc -g temp.c -o temp.html \
     -O3 -fno-inline

Rozdzielanie danych debugowania

Dane debugowania zachowują wiele szczegółów dotyczących kodu, typów, zmiennych, funkcji, zakresów i lokalizacji. może być przydatna dla debugera. W efekcie może być ona często większa niż właściwy kod.

Aby przyspieszyć ładowanie i kompilację modułu WebAssembly, możesz chcesz podzielić te informacje debugowania na osobny element WebAssembly . Aby to zrobić w Emscripten, przekaż flagę -gseparate-dwarf=… z parametrem żądaną nazwę pliku:

emcc -g temp.c -o temp.html \
     -gseparate-dwarf=temp.debug.wasm

W tym przypadku główna aplikacja przechowuje tylko nazwę pliku. temp.debug.wasm, a rozszerzenie pomocnicze będą w stanie zlokalizować i znaleźć nie wczytuje się po otwarciu Narzędzi deweloperskich.

W połączeniu z optymalizacjami opisanymi powyżej ta funkcja może mogą nawet wysyłać prawie zoptymalizowane kompilacje produkcyjne aplikacji, a później debugować je za pomocą lokalnego pliku. W tym przypadku musimy też zastąpić zapisany URL, by rozszerzenie znajdź plik boczny, na przykład:

emcc -g temp.c -o temp.html \
     -O3 -fno-inline \
     -gseparate-dwarf=temp.debug.wasm \
     -s SEPARATE_DWARF_URL=file://[local path to temp.debug.wasm]

Aby kontynuować...

Ale sporo nowych funkcji!

Dzięki tym nowym integracjom Narzędzia deweloperskie w Chrome zaawansowany debuger nie tylko do obsługi JavaScriptu, ale też aplikacji w językach C i C++, który ułatwia pobieranie aplikacji. i udostępniania ich we wspólnej, wieloplatformowej sieci.

To jednak jeszcze nie koniec. Oto kilka kwestii, nad którymi będziemy pracować w przyszłości:

  • Pozbywanie się niedoskonałości interfejsu debugowania.
  • Dodano obsługę niestandardowych programów formatujących.
  • Pracujemy nad ulepszeniami profilowania na potrzeby aplikacji WebAssembly.
  • Dodaliśmy obsługę zasięgu kodu, aby ułatwić wyszukiwanie nieużywanego kodu.
  • Poprawiono obsługę wyrażeń podczas oceny konsoli.
  • Dodawanie obsługi kolejnych języków.
  • …i nie tylko

Tymczasem możesz pomóc nam, testując wersję beta własnego kodu we własnym kodzie i zgłaszając znalezione problemów na https://issues.chromium.org/issues/new?noWizard=true&amp;template=0&amp;component=1456350.

Pobierz kanały podglądu

Zastanów się, czy nie ustawić Chrome w wersji Canary, Dev lub beta jako domyślnej przeglądarki do programowania. Te kanały wersji testowej dają dostęp do najnowszych funkcji Narzędzi deweloperskich, umożliwiają testowanie najnowocześniejszych interfejsów API platformy internetowej i wykrywanie problemów w witrynie, zanim użytkownicy ją zobaczą.

Kontakt z zespołem ds. Narzędzi deweloperskich w Chrome

Skorzystaj z poniższych opcji, aby porozmawiać o nowych funkcjach i zmianach w poście lub o innych kwestiach związanych z Narzędziami deweloperskimi.

  • Prześlij nam sugestię lub opinię na crbug.com.
  • Aby zgłosić problem z Narzędziami deweloperskimi, kliknij Więcej opcji   Więcej > Pomoc > Zgłoś problemy z Narzędziami deweloperskimi w Narzędziach deweloperskich.
  • Opublikuj tweeta na stronie @ChromeDevTools.
  • Napisz komentarz pod filmem dotyczącym nowości w Narzędziach deweloperskich w Narzędziach deweloperskich w YouTube lub filmach w YouTube ze wskazówkami dotyczącymi Narzędzi deweloperskich.