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 2020, lần đầu tiên, chúng tôi đã minh hoạ tính năng hỗ trợ gỡ lỗi của Chrome dành cho các ứng dụng WebAssembly trên web. Kể từ đó, đội ngũ của họ đã đầu tư rất nhiều công sức vào việc xây dựng trải nghiệm dành cho nhà phát triển trên các ứng dụng lớn và thậm chí rất lớn. Trong bài đăng này, chúng tôi sẽ chỉ cho bạn các nút mà chúng tôi đã thêm (hoặc đã hoàn thiệ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ừ nơi chúng ta đã dừng lại trong bài đăng năm 2020. Sau đây là ví dụ mà chúng ta đã xem trướ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();
}

Đâ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 sự nào 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ì. Quá trình thiết lập nhanh chóng, dễ dàng và bạn có thể 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 cùng thực hiện lại các thao tác đó, nhưng cũ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 có kích thước 3 MB. Và phần lớn trong số đó, như bạn có thể mong đợi, 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 trong số đó là các mục WebAssembly tiêu 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 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, mất khoảng 1,5 giây để chạy. Những con số này tạo ra một cơ sở nhỏ, nhưng chúng quá nhỏ nên có lẽ không ai để ý đến chúng. Mặc dù vậy, 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à mất vài phút để xây dựng!

Bỏ qua Binaryen

Khi tạo ứng dụng wasm bằng Emscripten, một trong những bước tạo bản 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 giúp tối ưu hoá và 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 phải đáp ứng 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ể đẩy nhanh đáng kể thời gian xây dựng nếu không cần truyền tệp Binaryen. Thẻ Binaryen yêu cầu 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. Bằng cách 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 ta đã gửi cờ -sERROR_ON_WASM_CHANGES_AFTER_LINK để đo lường thích hợp. Công cụ này giúp phát hiện thời điểm Binaryen đang chạy và đột ngột viết lại tệp nhị phân. Bằng cách này, chúng ta có thể đảm bảo mình duy trì được lộ trình nhanh chóng.

Mặc dù ví dụ của chúng ta khá nhỏ, nhưng 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 chưa đến 1 giây, vì vậy, nhanh hơn nửa giây so với trước đây!

Chỉnh sửa nâng cao

Bỏ qua bước quét tệp nhập

Thông thường, khi bạn liên kết một dự án Emscripten, emcc sẽ quét mọi thư viện và tệp đối tượng đầu vào. Công cụ này thực hiện việc này nhằm 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à ký hiệu gốc trong chương trình của bạn. Đối với các dự án lớn, việc quét thêm các 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ả phần phụ thuộc gốc có thể có của các hàm JavaScript. Cách 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 tạo ra 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ể giúp cải thiện đáng kể thời gian liên kết.

Xoá phần "name"

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" WebAssembly có thể rất lớn. Trong ví dụ của chúng tôi, đây chỉ là một phần nhỏ trong tổng kích thước tệp (xem kết quả của llvm-objdump ở trên), nhưng trong một số trường hợp, con số này có thể rất quan trọng. Nếu phần "name" của ứng dụng rất lớn và thông tin gỡ lỗi ngắn là đủ cho nhu cầu gỡ lỗi của bạn, thì bạn nên bỏ phần "name":

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

Thao tác này sẽ bỏ phần "tên" WebAssembly trong khi vẫn giữ nguyên các phần gỡ lỗi DWARF.

Gỡ lỗi phân hạch

Các 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 tạo bản dựng mà còn cả thời gian gỡ lỗi. Trình gỡ lỗi cần tải dữ liệu và cần tạo chỉ mục cho dữ liệu đó để có thể phản hồi nhanh các truy vấn, chẳng hạn như "Biến cục bộ x thuộc kiểu gì?".

Tính năng phân tách khi gỡ lỗi cho phép chúng ta chia thông tin gỡ lỗi của một tệp nhị phân thành hai phần: một phần vẫn ở 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 là các lệnh và những tệp được tạo bằng cách biên dịch mà không cần dữ liệu gỡ lỗi, với dữ liệu gỡ lỗi và cuối cùng là với 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 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 tôi chỉ có một tệp nguồn, nhưng thường thì các dự án sẽ lớn hơn và bao gồm nhiều tệp. Quá trình phân chia 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 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 DWARF (.dwp) như sau:

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

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

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át thêm một tệp! Chúng tôi hiện đang cố gắng tải tất cả đối tượng riêng lẻ trong một bản phát hành sau này.

DWARF 5 có gì?

Bạn có thể nhận thấy, chúng tôi đã chèn một cờ khác vào lệnh emcc ở trên, -gdwarf-5. Bật phiên bản 5 của biểu tượng DWARF (hiện không phải là chế độ mặc định) là một thủ thuật khác giúp chúng ta bắt đầu gỡ lỗi nhanh hơn. Cùng với nó, 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ỏ qua. 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. Nhờ vậy, trình gỡ lỗi có thể 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 các đ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 gỡ lỗi bằng biểu tượng phân tách nhanh hơn rất nhiều. Vì vậy, chúng ta luôn sử dụng kết hợp cờ dòng lệnh -gsplit-dwarf-gdwarf-5!

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 sẽ đượ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 một 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. Ví dụ: khi tìm kiếm biến hoặc loại. Chỉ mục tên giúp 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, chúng ta 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ên mà chúng ta đang tìm kiếm.

Dành cho người muốn tò mò: Xem xét 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

Việc này mang đến cho chúng ta thông tin tổng quan về "Đơn vị biên dịch" (nói gần đúng là tệp nguồn) mà chúng tôi 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 chưa có dữ liệu trong tệp này, đồng thời 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, chẳng hạn như ở bảng dòng cho thấy cách liên kết mã byte wasm với các dòng C++ (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 ích của việc sử dụng tính năng phân tách gỡ lỗi là gì?

Có một số lợi ích của việc chia nhỏ thông tin gỡ lỗi nếu một thông tin gỡ lỗi đang hoạt động 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 trong tệp nhị phân. Bằng cách loại bỏ các phần lớn của 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, dẫn đến thời gian liên kết nhanh hơn (đặc biệt đúng 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 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ư yêu cầu liên kết giữ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 do không cần phải tải và phân tích cú pháp dữ liệu gỡ lỗi bổ sung.

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

Tải 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 cung cấp cho bạn quyề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, thử nghiệm API nền tảng web tiên tiến và tìm ra sự cố trên trang web của bạn trước khi người dùng của bạn 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 đây để thảo luận về các tính năng mới và thay đổi trong bài đăng hoặc bất cứ vấn đề nào khác liên quan đến Công cụ cho nhà phát triển.

  • Hãy 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ủa Công cụ cho nhà phát triển bằng cách sử dụng phần Tuỳ chọn khác   Thêm   > Trợ giúp > Báo cáo sự cố về Công cụ cho nhà phát triển trong Công cụ cho nhà phát triển.
  • Tweet tại @ChromeDevTools.
  • Hãy để lại bình luận về tính năng mới trong video trên YouTube của Công cụ cho nhà phát triển hoặc video trên YouTube.