Gỡ lỗi WebAssembly nhanh hơn

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

Tại Hội nghị Nhà phát triển Chrome năm 2020, lần đầu tiên chúng tôi đã 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 công sức để mang trải nghiệm của nhà phát triển lên quy mô lớn đối với 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ẽ giới thiệu cho bạn những nút bấm mà chúng tôi đã thêm (hoặc đã cải tiến) trong các công cụ và cách sử dụng chúng!

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

Hãy tiếp tục từ thời điểm chúng ta dừng lại lần trước trong bài đăng của chúng tôi vào năm 2020. Sau đây là ví dụ 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 chỉ 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 là gì. Bạn có thể thiết lập một cách nhanh chóng và dễ dàng để tự mình thử nghiệm!

Trong bài đăng trước, chúng ta đã thảo luận cách biên dịch và gỡ lỗi ví dụ này. Hãy làm lại điều đó, nhưng hãy xem nhanh //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 3MB. 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 mục trong tệp wasm đã tạo, hầu hết đều là các mục WebAssembly chuẩn, nhưng cũng có một số mục 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 thấy rằng 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 trên máy của mình mất khoảng 1,5 giây để chạy. Những con số này tạo ra một đường cơ sở nhỏ thú vị, nhưng chúng quá nhỏ để không ai để ý đến chúng. 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 tính bằng GB và chỉ mất vài phút để tạo tệp!

Bỏ qua tệp nhị phân

Khi xây dựng ứ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 hóa vừa hợp pháp hoá các tệp nhị phân WebAssembly (tương tự như). 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 đáng kể thời gian xây dựng nếu tránh cần phải truyền tệp Binaryen. Đường liên kết nhị phân bắt buộc phổ biến nhất là để hợp pháp hoá các chữ ký hàm liên quan đến các giá trị số nguyên 64 bit. Khi chọn tích hợp WebAssembly BigInt 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 tôi đã gửi vào cờ -sERROR_ON_WASM_CHANGES_AFTER_LINK để đo lường chính xác. Nó giúp phát hiện khi Binaryen đang chạy và ghi lại tệp nhị phân một cách đột ngột. Bằng cách này, chúng tôi có thể đảm bảo mình luôn đi đúng hướng.

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

Chỉnh sửa nâng cao

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

Thường thì khi liên kết một dự án Emscripten, emcc sẽ quét mọi 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à biểu tượng 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 tệp đầu vào (sử dụng llvm-nm) có thể làm tăng đáng kể thời gian liên kết.

Thay vào đó, bạn có thể chạy bằng -sREVERSE_DEPS=all để yêu cầu emcc bao gồm tất cả các phần phụ thuộc gốc có thể có của hàm JavaScript. Phương pháp 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 tôi, điều này không có sự khác biệt thực sự 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 của mình 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 mẫu C++ nhiều, phần “tên” WebAssembly có thể rất lớn. Trong ví dụ này, nó chỉ là một phần nhỏ trong kích thước tệp tổng thể (xem kết quả của llvm-objdump ở trên), nhưng trong một số trường hợp, nó có thể rất quan trọng. Nếu phần "tên" của ứng dụng rất lớn và thông tin gỡ lỗi lùn đã đủ cho nhu cầu gỡ lỗi của bạn, thì việc loại bỏ phần "tên" có thể là một lợi thế:

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

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

Gỡ lỗi phân phối

Các tệp nhị phân có nhiều dữ liệu gỡ lỗi không chỉ tạo áp lực lên thời gian xây dựng mà còn gây áp lực lên thời gian gỡ lỗi. Trình gỡ lỗi cần tải dữ liệu và xây dựng 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ư "Biến cục bộ x có kiểu gì?".

Tính năng Debug fission (Phân phối 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 nằm trong tệp nhị phân và một phần nằm trong một tệp riêng biệt có tên là đối tượng DWARF (.dwo). Bạn có thể bật tính năng này bằng cách truyền cờ -gsplit-dwarf đến 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 sẽ trình bày các lệnh và tệp được tạo bằng cách biên dịch mà không có dữ liệu gỡ lỗi, không có dữ liệu gỡ lỗi, và cuối cùng là cả dữ liệu gỡ lỗi và phân tích gỡ lỗi.

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

Khi chia tách dữ liệu DWARF, một phần dữ liệu gỡ lỗi 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 tệp này và bao gồm nhiều tệp. Phân phối gỡ lỗi sẽ tạo một tệp .dwo cho từng phân phối. Để 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 phân tách này, chúng ta cần gói tất cả những thông tin đó vào một gói có tên DWARF (.dwp) như sau:

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

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

Việc xây dựng gói DWARF từ các đối tượng riêng lẻ có lợi thế là bạn chỉ cần phân phối thêm một tệp! Chúng tôi hiện đang xử lý việc tải tất cả đối tượng riêng lẻ trong bản phát hành sau này.

DWARF 5 là gì?

Bạn có thể nhận thấy rằng chúng ta đã chèn một cờ khác vào lệnh emcc ở trên là -gdwarf-5. Bật phiên bản 5 của các ký hiệu DWARF (hiện không phải là mặc định) là một mẹo khác để giúp chúng tôi bắt đầu gỡ lỗi nhanh hơn. Nhờ vậ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 mặc định 4 bỏ đi. Cụ thể, chúng ta có thể xác định tập hợp đầy đủ các 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ị cây nguồn đầy đủ và đặt điểm ngắt mà không cần tải và phân tích cú pháp dữ liệu biểu tượng đầy đủ. Điều này giúp việc 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ờ hiệu 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 truy cập vào một tính năng hữu ích khác. Thao tác này giới thiệu một chỉ mục tên trong dữ liệu gỡ lỗi được tạo khi chuyể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 phiên gỡ lỗi, hoạt động tra cứu biểu tượng thường diễn ra bằng cách tìm kiếm một thực thể theo tên, chẳng hạn như khi tìm một biến hoặc một loại. Chỉ mục tên tăng tốc độ 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 phải tìm toàn bộ dữ liệu gỡ lỗi để tìm đúng đơn vị biên dịch giúp xác định thực thể có tên mà chúng ta đang tìm.

Dành cho người muốn biết: 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 giúp chúng ta có được cái nhìn tổng quan về các tệp "Biên dịch đơn vị" (tức là các tệp nguồn) 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 bộ khung, tức là tệp này không có dữ liệu đầ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ụ: tại bảng dòng cho thấy mối liên kết giữa mã byte wasm với các dòng C++ (thử 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 ích của việc sử dụng cơ chế phân phối gỡ lỗi là gì?

Việc chia nhỏ thông tin gỡ lỗi nếu hoạt động với các ứng dụng lớn có một số ưu điểm:

  1. Liên kết nhanh hơn: Trình liên kết không cần phải 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ải phân tích cú pháp toàn bộ dữ liệu DWARF 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 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 quá trình phân tích cú pháp các ký hiệu bổ sung trong tệp .dwo/.dwp đối với một số hoạt động tra cứu biểu tượng. Đối với một số hoạt động tra cứu (chẳng hạn như các yêu cầu về ánh xạ dòng của các tệp wasm-to-C++), chúng ta không cần xem xét dữ liệu gỡ lỗi bổ sung. Việc này giúp chúng tôi tiết kiệm thời gian vì 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 gần đây trên hệ thống và đang sử dụng emsdk, thì bạn có thể tìm thấy phiên bản này trong thư mục emsdk/upstream/bin.

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

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

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

Sử dụng các lựa chọn sau để thảo luận về các tính năng và thay đổi mới trong bài đăng, 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.

  • Gửi đề xuất hoặc phản hồi cho chúng tôi qua crbug.com.
  • Báo cáo sự cố Công cụ cho nhà phát triển bằng cách sử dụng Tuỳ chọn khác   Thêm > Trợ giúp > Báo cáo vấn đề về Công cụ cho nhà phát triển trong Công cụ cho nhà phát triển.
  • Tweet tại @ChromeDevTools.
  • Để lại bình luận về Tính năng mới của chúng tôi trong Video trên YouTube hoặc Mẹo trong Công cụ cho nhà phát triển Video trên YouTube.