Gỡ lỗi WebAssembly nhanh hơn

Philip Pfaffe
Kim-Anh Tran
Kim-Anh Tran
Eric Leese
Eric Leese
Sam Clegg

Tại Chrome Dev Summit 2020, chúng tôi đã lần đầu tiên minh hoạ tính năng hỗ trợ gỡ lỗi của Chrome cho các ứng dụng WebAssembly trên web. Kể từ đó, nhóm đã đầu tư rất nhiều năng lượng để mở rộng trải nghiệm dành cho nhà phát triển cho các ứng dụng lớn và thậm chí là rất lớn. Trong bài đăng này, chúng tôi sẽ cho bạn thấy các núm mà chúng tôi đã thêm (hoặc làm cho hoạt động) trong các công cụ khác nhau và cách sử dụng các núm đó!

Gỡ lỗi có thể mở rộng

Hãy tiếp tục từ nội dung chúng ta đã đề cập trong bài đăng năm 2020. Sau đây là ví dụ mà chúng ta đã xem xét vào thời điểm đó:

#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();
}

Đây vẫn là một ví dụ khá nhỏ và có thể bạn sẽ không thấy bất kỳ vấn đề thực tế nào mà bạn sẽ thấy trong một ứng dụng thực sự lớn, nhưng chúng tôi vẫn có thể cho bạn thấy các tính năng mới. Bạn có thể thiết lập và tự mình dùng thử một cách nhanh chóng và dễ dàng!

Trong bài đăng trước, chúng ta đã thảo luận về cách biên dịch và gỡ lỗi ví dụ này. Hãy làm lại như vậy, nhưng cũng hãy xem qua //performance//:

$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH

Lệnh này tạo ra một tệp nhị phân wasm 3 MB. Và như bạn có thể dự đoán, phần lớn trong số đó là thông tin gỡ lỗi. Bạn có thể xác minh điều này bằng công cụ llvm-objdump [1], ví dụ:

$ 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

Kết quả này cho chúng ta thấy tất cả các phần trong tệp wasm đã tạo, hầu hết các phần đều là phần WebAssembly tiêu chuẩn, nhưng cũng có một số phần tuỳ chỉnh có tên bắt đầu bằng .debug_. Đó là nơi tệp nhị phân chứa thông tin gỡ lỗi của chúng ta! Nếu cộng tất cả các kích thước, chúng ta sẽ thấy thông tin gỡ lỗi chiếm khoảng 2,3 MB trong tệp 3 MB. Nếu cũng time lệnh emcc, chúng ta sẽ thấy rằng trên máy của mình, lệnh này mất khoảng 1,5 giây để chạy. Những con số này tạo thành một đường cơ sở nhỏ nhưng rất đẹp, nhưng chúng quá nhỏ nên có thể không ai để ý đến. Tuy nhiên, trong các ứng dụng thực tế, tệp nhị phân gỡ lỗi có thể dễ dàng đạt đến kích thước vài GB và mất vài phút để tạo!

Bỏ qua Binaryen

Khi tạo một ứng dụng wasm bằng Emscripten, một trong những bước xây dựng cuối cùng là chạy trình tối ưu hoá Binaryen. Binaryen là một bộ công cụ biên dịch vừa tối ưu hoá vừa hợp pháp hoá các tệp nhị phân WebAssembly(-like). Việc chạy Binaryen trong bản dựng khá tốn kém, nhưng chỉ bắt buộc trong một số điều kiện nhất định. Đối với các bản gỡ lỗi, chúng ta có thể tăng tốc đáng kể thời gian xây dựng nếu không cần phải chuyển Binaryen. Lệnh truyền Binaryen bắt buộc phổ biến nhất là để hợp pháp hoá chữ ký hàm liên quan đến các giá trị số nguyên 64 bit. Bằng cách chọn tích hợp BigInt WebAssembly bằng -sWASM_BIGINT, chúng ta có thể tránh được điều này.

$ emcc -sUSE_SDL=2 -g -O0 -o mandelbrot.html mandelbrot.cc -sALLOW_MEMORY_GROWTH -sWASM_BIGINT -sERROR_ON_WASM_CHANGES_AFTER_LINK

Chúng ta đã thêm cờ -sERROR_ON_WASM_CHANGES_AFTER_LINK để đo lường hiệu quả. Tính năng này giúp phát hiện thời điểm Binaryen đang chạy và ghi đè tệp nhị phân một cách không mong muốn. Bằng cách này, chúng ta có thể đảm bảo rằng mình đang đi đúng hướng.

Mặc dù ví dụ của chúng ta khá nhỏ, nhưng chúng ta vẫn có thể thấy hiệu quả của việc bỏ qua Binaryen! Theo time, lệnh này chạy chỉ dưới 1 giây, nhanh hơn nửa giây so với trước!

Tuỳ chỉnh nâng cao

Bỏ qua quá trình quét tệp đầu vào

Thông thường, khi liên kết một dự án Emscripten, emcc sẽ quét tất cả các tệp đối tượng đầu vào và thư viện. Công cụ này thực hiện việc này để triển khai các phần phụ thuộc chính xác giữa các hàm thư viện JavaScript và các ký hiệu gốc trong chương trình của bạn. Đối với các dự án lớn hơn, việc quét thêm các tệp đầu vào (sử dụng llvm-nm) này có thể làm tăng đáng kể thời gian liên kết.

Bạn có thể chạy bằng -sREVERSE_DEPS=all để yêu cầu emcc đưa vào tất cả các phần phụ thuộc gốc có thể có của các hàm JavaScript. Phương thức này có mức hao tổn kích thước mã nhỏ nhưng có thể tăng tốc thời gian liên kết và có thể hữu ích cho các bản gỡ lỗi.

Đối với một dự án nhỏ như ví dụ của chúng ta, điều này không tạo ra sự khác biệt thực sự nào, nhưng nếu bạn có hàng trăm hoặc thậm chí hàng nghìn tệp đối tượng trong dự án, thì điều này có thể cải thiện đáng kể thời gian liên kết.

Xoá phần "tên"

Trong các dự án lớn, đặc biệt là những dự án sử dụng nhiều mẫu C++, phần "tên" của WebAssembly có thể rất lớn. Trong ví dụ của chúng ta, kích thước này chỉ là một phần rất nhỏ so với kích thước tổng thể của tệp (xem kết quả của llvm-objdump ở trên) nhưng trong một số trường hợp, kích thước này có thể rất đáng kể. Nếu phần "tên" của ứng dụng rất lớn và thông tin gỡ lỗi dwarf đủ cho nhu cầu gỡ lỗi của bạn, thì bạn có thể loại bỏ phần "tên":

$ emstrip --no-strip-all --remove-section=name mandelbrot.wasm

Thao tác này sẽ xoá phần "tên" của WebAssembly trong khi vẫn giữ lại các phần gỡ lỗi DWARF.

Gỡ lỗi phân hạch

Tệp nhị phân có nhiều dữ liệu gỡ lỗi không chỉ gây áp lực về thời gian xây dựng mà còn về thời gian gỡ lỗi. Trình gỡ lỗi cần tải dữ liệu và cần tạo một chỉ mục cho dữ liệu đó để có thể nhanh chóng phản hồi các truy vấn, chẳng hạn như "Loại của biến cục bộ x là gì?".

Tính năng Phân tách gỡ lỗi cho phép chúng ta chia thông tin gỡ lỗi cho một tệp nhị phân thành hai phần: một phần vẫn nằm trong tệp nhị phân và một phần nằm trong một tệp đối tượng DWARF (.dwo) riêng biệt. Bạn có thể bật tính năng này bằng cách truyền cờ -gsplit-dwarf vào Emscripten:

$ 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

Dưới đây, chúng tôi trình bày các lệnh khác nhau và những tệp được tạo bằng cách biên dịch mà không có dữ liệu gỡ lỗi, có dữ liệu gỡ lỗi và cuối cùng là có cả dữ liệu gỡ lỗi và tính năng phân tách gỡ lỗi.

các lệnh khác nhau và tệp được tạo

Khi phân tách dữ liệu DWARF, một phần dữ liệu gỡ lỗi sẽ nằm cùng với tệp nhị phân, trong khi phần lớn được đưa vào tệp mandelbrot.dwo (như minh hoạ ở trên).

Đối với mandelbrot, chúng ta chỉ có một tệp nguồn, nhưng thường thì các dự án lớn hơn và bao gồm nhiều tệp. Tính năng phân tách gỡ lỗi sẽ tạo một tệp .dwo cho mỗi tệp. Để phiên bản beta hiện tại của trình gỡ lỗi (0.1.6.1615) có thể tải thông tin gỡ lỗi phân tách này, chúng ta cần gói tất cả thông tin đó vào một gói DWARF (.dwp) như sau:

$ emdwp -e mandelbrot.wasm -o mandelbrot.dwp

gói các tệp dwo vào một gói DWARF

Việc tạo gói DWARF từ các đối tượng riêng lẻ có ưu điểm là bạn chỉ cần phân phát thêm một tệp! Chúng tôi cũng đang nỗ lực để tải tất cả các đối tượng riêng lẻ trong một bản phát hành trong tương lai.

DWARF 5 có gì?

Bạn có thể nhận thấy chúng tôi đã lén thêm một cờ khác vào lệnh emcc ở trên, đó là -gdwarf-5. Việc bật phiên bản 5 của các ký hiệu DWARF (hiện không phải là phiên bản mặc định) là một mẹo khác giúp chúng ta bắt đầu gỡ lỗi nhanh hơn. Với tính năng này, một số thông tin nhất định được lưu trữ trong tệp nhị phân chính mà phiên bản 4 mặc định đã bỏ qua. Cụ thể, chúng ta có thể xác định toàn bộ tập hợp tệp nguồn chỉ từ tệp nhị phân chính. Điều này cho phép trình gỡ lỗi thực hiện các thao tác cơ bản như hiển thị toàn bộ cây nguồn và đặt điểm ngắt mà không cần tải và phân tích cú pháp toàn bộ dữ liệu biểu tượng. Điều này giúp quá trình gỡ lỗi bằng các ký hiệu phân tách nhanh hơn rất nhiều, vì vậy, chúng ta luôn sử dụng các cờ dòng lệnh -gsplit-dwarf-gdwarf-5 cùng nhau!

Với định dạng gỡ lỗi DWARF5, chúng ta cũng có quyền sử dụng một tính năng hữu ích khác. Phương thức này sẽ giới thiệu một chỉ mục tên trong dữ liệu gỡ lỗi sẽ được tạo khi truyền cờ -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

Trong một phiên gỡ lỗi, việc tra cứu biểu tượng thường xảy ra bằng cách tìm kiếm một thực thể theo tên, ví dụ: khi tìm một biến hoặc một loại. Chỉ mục tên tăng tốc quá trình tìm kiếm này bằng cách trỏ trực tiếp đến đơn vị biên dịch xác định tên đó. Nếu không có chỉ mục tên, bạn sẽ phải tìm kiếm toàn bộ dữ liệu gỡ lỗi để tìm đúng đơn vị biên dịch xác định thực thể được đặt tên mà chúng ta đang tìm kiếm.

Dành cho người tò mò: Xem dữ liệu gỡ lỗi

Bạn có thể sử dụng llvm-dwarfdump để xem nhanh dữ liệu DWARF. Hãy thử làm như sau:

llvm-dwarfdump mandelbrot.wasm

Thao tác này cung cấp cho chúng ta thông tin tổng quan về "Compile units" (tạm dịch là các đơn vị biên dịch) mà chúng ta có thông tin gỡ lỗi. Trong ví dụ này, chúng ta chỉ có thông tin gỡ lỗi cho mandelbrot.cc. Thông tin chung sẽ cho chúng ta biết rằng chúng ta có một đơn vị khung, nghĩa là chúng ta có dữ liệu chưa đầy đủ về tệp này và có một tệp .dwo riêng chứa thông tin gỡ lỗi còn lại:

mandelbrot.wasm và thông tin gỡ lỗi

Bạn cũng có thể xem các bảng khác trong tệp này, ví dụ: bảng dòng cho thấy mối liên kết của mã byte wasm với các dòng C++ (hãy thử sử dụng llvm-dwarfdump -debug-line).

Chúng ta cũng có thể xem thông tin gỡ lỗi có trong tệp .dwo riêng biệt:

llvm-dwarfdump mandelbrot.dwo

mandelbrot.wasm và thông tin gỡ lỗi

Tóm tắt: Lợi thế của việc sử dụng tính năng phân tách gỡ lỗi là gì?

Việc phân tách thông tin gỡ lỗi có một số ưu điểm nếu bạn đang làm việc với các ứng dụng lớn:

  1. Liên kết nhanh hơn: Trình liên kết không cần phân tích cú pháp toàn bộ thông tin gỡ lỗi nữa. Trình liên kết thường cần phân tích cú pháp toàn bộ dữ liệu DWARF có trong tệp nhị phân. Bằng cách loại bỏ phần lớn thông tin gỡ lỗi thành các tệp riêng biệt, trình liên kết sẽ xử lý các tệp nhị phân nhỏ hơn, giúp thời gian liên kết nhanh hơn (đặc biệt là đối với các ứng dụng lớn).

  2. Gỡ lỗi nhanh hơn: Trình gỡ lỗi có thể bỏ qua việc phân tích cú pháp các biểu tượng bổ sung trong tệp .dwo/.dwp cho một số lượt tra cứu biểu tượng. Đối với một số lượt tra cứu (chẳng hạn như các yêu cầu về ánh xạ dòng của tệp wasm-to-C++), chúng ta không cần xem xét dữ liệu gỡ lỗi bổ sung. Điều này giúp chúng ta tiết kiệm thời gian, không cần tải và phân tích cú pháp dữ liệu gỡ lỗi bổ sung.

1: Nếu không có phiên bản llvm-objdump mới nhất trên hệ thống và đang sử dụng emsdk, bạn có thể tìm thấy phiên bản này trong thư mục emsdk/upstream/bin.

Tải các kênh xem trước xuống

Hãy cân nhắc sử dụng Chrome Canary, Dev hoặc Beta làm trình duyệt phát triển mặc định. Các kênh xem trước này cho phép bạn sử dụng các tính năng mới nhất của DevTools, kiểm thử các API nền tảng web tiên tiến và giúp bạn tìm thấy vấn đề trên trang web của mình trước khi người dùng phát hiện ra!

Liên hệ với nhóm Công cụ của Chrome cho nhà phát triển

Hãy sử dụng các lựa chọn sau để thảo luận về các tính năng, bản cập nhật mới hoặc bất kỳ nội dung nào khác liên quan đến Công cụ cho nhà phát triển.