Na konferencji Chrome Dev Summit 2020 po raz pierwszy zaprezentowaliśmy w internecie obsługę debugowania w Chrome dla aplikacji WebAssembly. Od tego czasu zespół zainwestował dużo energii w rozwój środowiska programistycznego na potrzeby dużych, a nawet bardzo dużych aplikacji. W tym poście pokażemy, które elementy zostały dodane (lub wprowadzone) w różnych narzędziach, i jak z nich korzystać.
Skalowalne debugowanie
Zacznijmy od tego samego miejsca w poście z 2020 roku. Oto przykład:
#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();
}
To nadal niewielki przykład i prawdopodobnie nie spotkasz się z rzeczywistymi problemami, które pojawiłyby się w bardzo dużych aplikacjach. Możemy jednak pokazać Ci, jakie to nowe funkcje. Konfiguracja jest szybka i łatwa, a Ty możesz ją wypróbować.
W poprzednim poście omówiliśmy, jak kompilować i debugować ten przykład. Zróbmy to jeszcze raz, ale przyjrzyjmy się też //performance//:
$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH
To polecenie tworzy plik binarny Wasm o rozmiarze 3 MB. Większość z nich, jak można by oczekiwać, to informacje na potrzeby debugowania. Możesz to sprawdzić za pomocą narzędzia llvm-objdump
[1], na przykład:
$ llvm-objdump -h mandelbrot.wasm
mandelbrot.wasm: file format wasm
Sections:
Idx Name Size VMA Type
0 TYPE 0000026f 00000000
1 IMPORT 00001f03 00000000
2 FUNCTION 0000043e 00000000
3 TABLE 00000007 00000000
4 MEMORY 00000007 00000000
5 GLOBAL 00000021 00000000
6 EXPORT 0000014a 00000000
7 ELEM 00000457 00000000
8 CODE 0009308a 00000000 TEXT
9 DATA 0000e4cc 00000000 DATA
10 name 00007e58 00000000
11 .debug_info 000bb1c9 00000000
12 .debug_loc 0009b407 00000000
13 .debug_ranges 0000ad90 00000000
14 .debug_abbrev 000136e8 00000000
15 .debug_line 000bb3ab 00000000
16 .debug_str 000209bd 00000000
Te dane wyjściowe pokazują wszystkie sekcje znajdujące się w wygenerowanym pliku Wasm. Większość z nich to standardowe sekcje WebAssembly, ale jest też kilka sekcji niestandardowych, których nazwa zaczyna się od .debug_
. W tym pliku binarnym znajdują się dane debugowania. Jeśli zsumujemy wszystkie rozmiary, zobaczymy, że dane debugowania stanowią około 2,3 MB z pliku 3 MB. Jeśli użyjemy również polecenia time
emcc
, zobaczymy, że uruchomienie na naszym komputerze zajęło około 1,5 s. Te liczby stanowią sporą wartość bazową, ale są tak małe, że prawdopodobnie nikt nie zauważy ich. Jednak w prawdziwych aplikacjach plik binarny debugowania może łatwo osiągnąć rozmiar w GB, a kompilacja trwa kilka minut.
Pomijam plik Binaryen
Podczas tworzenia aplikacji Wasm za pomocą narzędzia Emscripten jednym z ostatnich etapów kompilacji jest uruchomienie optymalizatora Binaryen. Binaryen to zestaw narzędzi do kompilacji, który optymalizuje i legalizuje pliki binarne WebAssembly. Uruchomienie Binaryen w ramach kompilacji jest dość kosztowne, ale wymagane tylko pod pewnymi warunkami. W przypadku kompilacji przeznaczonych do debugowania możemy znacznie skrócić czas kompilacji, jeśli unikniemy stosowania kart Binaryen. Najczęściej wymagana czynność Binaryen służy do legalności podpisów funkcji obejmujących 64-bitowe wartości całkowite. Możemy tego uniknąć, włączając integrację WebAssembly BigInt za pomocą interfejsu -sWASM_BIGINT
.
$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK
W trosce o bezpieczeństwo wprowadziliśmy flagę -sERROR_ON_WASM_CHANGES_AFTER_LINK
. Pomaga wykryć, kiedy plik Binaryen jest uruchomiony, i nieoczekiwanie przepisywać plik binarny. W ten sposób możemy być na bieżąco.
Mimo że nasz przykład jest stosunkowo mały, i tak możemy zauważyć efekt pomijania Binaryen. Zgodnie z informacjami w usłudze time
to polecenie działa poniżej 1 s, czyli o pół sekundy szybciej niż wcześniej.
Sztuczki zaawansowane
Pomijam skanowanie plików wejściowych
Zwykle podczas łączenia z projektem Emscripten usługa emcc
skanuje wszystkie biblioteki i pliki wejściowych obiektów. Pozwala to wdrożyć w programie dokładne zależności między funkcjami biblioteki JavaScript a symbolami natywnymi. W przypadku większych projektów to dodatkowe skanowanie plików wejściowych (przy użyciu funkcji llvm-nm
) może znacznie wydłużyć czas połączenia.
Możesz zamiast tego użyć polecenia -sREVERSE_DEPS=all
, który informuje element emcc
o uwzględnieniu wszystkich możliwych natywnych zależności funkcji JavaScript. Wiąże się to z niewielkim narzutem kodu, ale może skrócić czasy połączeń i może być przydatne przy debugowaniu kompilacji.
W przypadku tak małego projektu, jak nasz przykład, nie ma to znaczącej różnicy, ale jeśli masz w projekcie setki, a nawet tysiące plików obiektów, może to znacznie skrócić czasy linku.
Usuwanie sekcji „name”
W dużych projektach, zwłaszcza tych z dużym wykorzystaniem szablonów w C++, sekcja WebAssembly „name” może być bardzo duża. W naszym przykładzie jest to tylko niewielka część całkowitego rozmiaru pliku (zobacz dane wyjściowe funkcji llvm-objdump
powyżej), ale w niektórych przypadkach może być bardzo znacząca. Jeśli sekcja „name” aplikacji jest bardzo duża, a dane debugowania DARF są wystarczające do celów debugowania, warto usunąć sekcję „name”:
$ emstrip --no-strip-all --remove-section=name mandelbrot.wasm
Spowoduje to usunięcie sekcji „name” WebAssembly przy jednoczesnym zachowaniu sekcji debugowania DWARF.
Debugowanie rozszczepienia
Pliki binarne z dużą ilością danych debugowania wywierają presję nie tylko na czas kompilacji, ale także na debugowanie. Debuger musi wczytać dane i utworzyć dla nich indeks, by szybko odpowiadać na zapytania, np. „Jakiego typu jest zmienna lokalna x?”.
Funkcja debugowania umożliwia podzielenie danych debugowania na potrzeby pliku binarnego na dwie części: pierwszą, która znajduje się w pliku binarnym, i drugą, która znajduje się w osobnym pliku danych typu DWARF (.dwo
). Aby go włączyć, przekaż do Emscripten flagę -gsplit-dwarf
:
$ emcc -sUSE_SDL=2 -g -gsplit-dwarf -gdwarf-5 -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK
Poniżej pokazujemy różne polecenia i informacje o tym, jakie pliki są generowane w wyniku kompilacji bez danych debugowania, z danymi debugowania, a na końcu z danymi i danymi debugowania.
Podczas dzielenia danych DWARF część danych debugowania jest przechowywana razem z plikiem binarnym, a duża część – w pliku mandelbrot.dwo
(jak pokazano powyżej).
W przypadku usługi mandelbrot
mamy tylko 1 plik źródłowy, ale zwykle projekty są większe i zawierają więcej niż 1 plik. Narzędzie debugowania generuje plik .dwo
dla każdej z nich. Aby bieżąca wersja beta debugera (0.1.6.1615) mogła wczytać dane debugowania, musimy je zebrać w tak zwany pakiet DWARF (.dwp
):
$ emdwp -e mandelbrot.wasm -o mandelbrot.dwp
Utworzenie pakietu DWARF z pojedynczych obiektów ma tę zaletę, że wystarczy przesłać 1 dodatkowy plik. Obecnie pracujemy nad wczytaniem wszystkich pojedynczych obiektów w przyszłej wersji.
O co chodzi z DWARF 5?
Jak pewnie zauważyliście, do polecenia emcc
powyżej dodaliśmy kolejną flagę: -gdwarf-5
. Włączenie wersji 5 symboli DWARF, która obecnie nie jest ustawieniem domyślnym, to kolejna sztuczka, która pomoże nam szybciej rozpocząć debugowanie. Dzięki temu pewne informacje są przechowywane w głównym pliku binarnym, który został pominięty w domyślnej wersji 4. W szczególności możemy określić pełny zbiór plików źródłowych na podstawie głównego pliku binarnego. Dzięki temu debuger może wykonywać podstawowe działania, takie jak wyświetlanie pełnego drzewa źródłowego i ustawianie punktów przerwania bez wczytywania i analizowania danych pełnych symboli. Dzięki temu debugowanie z użyciem symboli podzielonych trwa o wiele szybciej, dlatego zawsze używamy flag wiersza poleceń -gsplit-dwarf
i -gdwarf-5
razem.
Format debugowania DWARF5 zapewnia również dostęp do innej przydatnej funkcji. Wprowadza indeks nazw do danych debugowania, które są generowane podczas przekazywania flagi -gpubnames
:
$ emcc -sUSE_SDL=2 -g -gdwarf-5 -gsplit-dwarf -gpubnames -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK
W trakcie sesji debugowania wyszukiwanie symboli często odbywa się poprzez wyszukiwanie encji według nazwy, np. podczas szukania zmiennej lub typu. Indeks nazw przyspiesza wyszukiwanie, wskazując bezpośrednio jednostkę kompilacji, która określa tę nazwę. Bez indeksu nazw niezbędne byłoby wyczerpujące przeszukiwanie wszystkich danych debugowania, aby znaleźć odpowiednią jednostkę kompilacji, która określa pożądaną nazwę.
Dla ciekawych: dane debugowania
Aby mieć wgląd w dane DWARF, użyj narzędzia llvm-dwarfdump
. Wypróbujmy to:
llvm-dwarfdump mandelbrot.wasm
Tutaj znajdziesz omówienie „jednostek kompilacji” (czyli plików źródłowych), dla których dostępne są informacje o debugowaniu. W tym przykładzie mamy tylko dane debugowania dla: mandelbrot.cc
. Informacje ogólne dadzą nam znać, że mamy szkielet jednostki, co oznacza, że mamy niepełne dane w tym pliku i że istnieje oddzielny plik .dwo
zawierający pozostałe informacje debugowania:
Możesz też wyświetlić inne tabele w tym pliku, np. w tabeli wierszy, które pokazują mapowanie kodu bajtowego Wasm na wiersze C++ (spróbuj użyć llvm-dwarfdump -debug-line
).
Znajdziesz tu też informacje debugowania w osobnym pliku .dwo
:
llvm-dwarfdump mandelbrot.dwo
TL;DR: Jaka jest zaleta używania debugowania?
Rozdzielenie informacji na potrzeby debugowania w przypadku dużych aplikacji ma kilka zalet:
Szybsze linki: tag łączący nie musi już analizować wszystkich informacji na potrzeby debugowania. Łączniki zwykle analizują wszystkie dane DWARF zawarte w pliku binarnym. Dzięki wydzieleniu dużych części informacji o debugowaniu do osobnych plików tagi łączące obsługują mniejsze pliki binarne, co pozwala skrócić czas łączenia (zwłaszcza w przypadku dużych aplikacji).
Szybsze debugowanie: w przypadku niektórych wyszukiwań symboli debuger może pominąć analizę dodatkowych symboli w plikach
.dwo
/.dwp
. W przypadku niektórych wyszukiwań (np. żądań dotyczących mapowania wierszy plików Wasm-na-C++) nie musimy analizować dodatkowych danych debugowania. Dzięki temu oszczędzamy czas i nie musimy wczytywać i analizować dodatkowych danych debugowania.
1. Jeśli używasz emsdk
i nie masz w systemie najnowszej wersji llvm-objdump
, znajdziesz ją w katalogu emsdk/upstream/bin
.
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
Użyj 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 > 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.