Gỡ lỗi WebAssembly bằng các công cụ hiện đại

Ingvar Stepanyan
Ingvar Stepanyan

Đường đã đi đến hiện tại

Một năm trước, Chrome đã công bố về việc hỗ trợ ban đầu cho tính năng gỡ lỗi WebAssembly gốc trong Công cụ của Chrome cho nhà phát triển.

Chúng tôi đã trình bày hỗ trợ từng bước cơ bản và trình bày về các cơ hội sử dụng thông tin DWARF thay vì bản đồ nguồn sẽ mở ra cho chúng tôi trong tương lai:

  • Phân giải tên biến
  • Các loại tạo bản in đẹp
  • Đánh giá biểu thức bằng ngôn ngữ nguồn
  • ...và hơn thế nữa!

Hôm nay, chúng tôi rất vui được giới thiệu các tính năng đã hứa hẹn đã đi vào hoạt động cũng như tiến trình mà các nhóm Emscripten và Công cụ của Chrome cho nhà phát triển đã đạt được trong năm nay, đặc biệt là đối với các ứng dụng C và C++.

Trước khi chúng tôi bắt đầu, xin lưu ý rằng đây vẫn là phiên bản beta của trải nghiệm mới. Bạn tự chịu rủi ro khi sử dụng phiên bản mới nhất của tất cả công cụ. Nếu gặp phải vấn đề nào, vui lòng báo cáo cho https://bugs.chromium.org/p/chromium/issues/entry?template=DevTools+issue.

Hãy bắt đầu với ví dụ C đơn giản như lần trước:

#include <stdlib.h>

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

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

Để biên dịch mã này, chúng ta sử dụng Emscripten mới nhất và truyền một cờ -g, giống như trong bài đăng gốc, để đưa vào thông tin gỡ lỗi:

emcc -g temp.c -o temp.html

Bây giờ, chúng ta có thể phân phát trang đã tạo từ máy chủ HTTP localhost (ví dụ: bằng tính năng phân phát) và mở trang đó trong Chrome Canary mới nhất.

Lần này, chúng ta cũng cần một tiện ích trợ giúp tích hợp với Công cụ phát triển của Chrome và giúp hiểu được tất cả thông tin gỡ lỗi được mã hoá trong tệp WebAssembly. Vui lòng cài đặt tiện ích này bằng cách truy cập vào đường liên kết này: goo.gle/wasm-debugging-extension

Bạn cũng nên bật tính năng gỡ lỗi WebAssembly trong Thử nghiệm Công cụ cho nhà phát triển. Mở Công cụ của Chrome cho nhà phát triển, nhấp vào biểu tượng bánh răng () ở góc trên cùng bên phải của ngăn Công cụ cho nhà phát triển, chuyển đến bảng điều khiển Thử nghiệm và đánh dấu vào Gỡ lỗi webAssembly: Bật hỗ trợ DWARF.

Ngăn thử nghiệm của phần cài đặt Công cụ cho nhà phát triển

Khi bạn đóng phần Cài đặt, Công cụ cho nhà phát triển sẽ đề xuất tự tải lại để áp dụng các chế độ cài đặt, vì vậy, hãy thực hiện việc đó. Đó là quá trình thiết lập một lần.

Bây giờ, chúng ta có thể quay lại bảng điều khiển Sources (Nguồn), bật tuỳ chọn Pause onexceptions (Tạm dừng trên trường hợp ngoại lệ) (biểu tượng ⏸), sau đó đánh dấu Pause on release (Tạm dừng) khi phát hiện trường hợp ngoại lệ rồi tải lại trang. Bạn sẽ thấy Công cụ cho nhà phát triển bị tạm dừng trong một trường hợp ngoại lệ:

Ảnh chụp màn hình bảng điều khiển Nguồn cho thấy cách bật chế độ &quot;Tạm dừng khi phát hiện trường hợp ngoại lệ&quot;

Theo mặc định, quá trình này sẽ dừng trên mã kết nối do Emscripten tạo, nhưng ở bên phải, bạn có thể thấy khung hiển thị Call Stack (Ngăn xếp lệnh gọi) biểu thị dấu vết ngăn xếp của lỗi và có thể chuyển đến dòng C ban đầu đã gọi abort:

Công cụ cho nhà phát triển bị tạm dừng trong hàm &quot;assert_less&quot; và hiện các giá trị của &quot;x&quot; và &quot;y&quot; trong chế độ xem Phạm vi

Giờ đây, nếu xem trong chế độ xem Scope (Phạm vi), bạn có thể thấy tên và giá trị gốc của các biến trong mã C/C++ và không còn phải tìm ý nghĩa của những tên bị hỏng như $localN cũng như mối liên hệ giữa chúng với mã nguồn bạn đã viết.

Điều này không chỉ áp dụng với các giá trị gốc như số nguyên mà còn áp dụng với các loại phức hợp như cấu trúc, lớp, mảng, v.v.!

Hỗ trợ loại đa dạng thức

Hãy xem một ví dụ phức tạp hơn để chỉ ra các yếu tố đó. Lần này, chúng ta sẽ vẽ một hình phân dạng Mandelbrot có mã C++ sau đây:

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

Bạn có thể thấy rằng ứng dụng này vẫn khá nhỏ – một tệp duy nhất chứa 50 dòng mã – nhưng lần này tôi cũng đang sử dụng một số API bên ngoài, như thư viện SDL cho đồ hoạ cũng như số phức tạp từ thư viện chuẩn C++.

Tôi sẽ biên dịch bằng cùng một cờ -g như trên để bao gồm thông tin gỡ lỗi. Tôi cũng sẽ yêu cầu Emscripten cung cấp thư viện SDL2 và cho phép bộ nhớ có kích thước tuỳ ý:

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

Khi truy cập trang đã tạo trong trình duyệt, tôi có thể thấy hình dạng phân dạng tuyệt đẹp với một số màu ngẫu nhiên:

Trang minh hoạ

Khi mở Công cụ cho nhà phát triển, một lần nữa, tôi có thể thấy tệp C++ gốc. Tuy nhiên, lần này chúng ta không có lỗi trong mã (Ồ!). Vì vậy, hãy đặt một số điểm ngắt ở đầu mã.

Khi chúng tôi tải lại trang, trình gỡ lỗi sẽ tạm dừng ngay bên trong nguồn C++:

Công cụ cho nhà phát triển bị tạm dừng trong lệnh gọi &quot;SDL_Init&quot;

Chúng ta có thể thấy tất cả biến ở bên phải, nhưng hiện tại chỉ có widthheight được khởi tạo, nên bạn không cần kiểm tra nhiều.

Hãy đặt một điểm ngắt khác bên trong vòng lặp Mandelbrot chính và tiếp tục thực thi để bỏ qua một chút.

Công cụ cho nhà phát triển bị tạm dừng bên trong vòng lặp lồng nhau

Tại thời điểm này, palette của chúng ta đã được thêm một số màu ngẫu nhiên và chúng ta có thể mở rộng cả mảng, cũng như các cấu trúc SDL_Color riêng lẻ và kiểm tra các thành phần của chúng để xác minh rằng mọi thứ đều ổn (ví dụ: kênh "alpha" luôn được đặt thành độ mờ hoàn toàn). Tương tự, chúng ta có thể mở rộng và kiểm tra phần thực và phần ảo của số phức được lưu trữ trong biến center.

Nếu muốn truy cập vào một thuộc tính được lồng sâu mà khó di chuyển đến thông qua chế độ xem Scope (Phạm vi), bạn cũng có thể sử dụng tính năng đánh giá Console (Bảng điều khiển)! Tuy nhiên, xin lưu ý rằng chúng tôi hiện chưa hỗ trợ các biểu thức C++ phức tạp hơn.

Bảng điều khiển cho thấy kết quả của &quot;palette[10].r&quot;

Hãy tiếp tục thực thi một vài lần và chúng ta có thể thấy x bên trong đang thay đổi như thế nào bằng cách xem lại trong chế độ xem Scope (Phạm vi), thêm tên biến vào danh sách theo dõi, đánh giá biến trong bảng điều khiển hoặc di chuột qua biến trong mã nguồn:

Chú thích trên biến `x` trong nguồn cho thấy giá trị `3`

Từ đây, chúng ta có thể hướng dẫn hoặc từng bước các câu lệnh C++ và quan sát xem các biến khác cũng thay đổi như thế nào:

Chú giải công cụ và chế độ xem Phạm vi hiển thị giá trị của &quot;màu sắc&quot;, &quot;điểm&quot; và các biến khác

Tất cả đều hoạt động hiệu quả khi có thông tin gỡ lỗi. Tuy nhiên, điều gì sẽ xảy ra nếu chúng ta muốn gỡ lỗi một mã không được tạo bằng các tuỳ chọn gỡ lỗi?

Gỡ lỗi WebAssembly thô

Ví dụ: chúng tôi đã yêu cầu Emscripten cung cấp một thư viện SDL tạo sẵn cho chúng tôi thay vì tự biên dịch từ nguồn, vì vậy, ít nhất hiện tại trình gỡ lỗi không có cách nào để tìm thấy các nguồn liên quan. Hãy cùng bước lại để truy cập vào SDL_RenderDrawColor:

Công cụ cho nhà phát triển cho thấy chế độ xem tháo rời của &quot;mandelbrot.wasm&quot;

Chúng ta trở lại trải nghiệm gỡ lỗi WebAssembly thô.

Hiện tại, thư viện này có vẻ hơi đáng sợ và không phải là điều mà hầu hết các nhà phát triển web cần xử lý, nhưng đôi khi bạn có thể muốn gỡ lỗi một thư viện được tạo mà không có thông tin gỡ lỗi – cho dù đó là thư viện của bên thứ 3 mà bạn không có quyền kiểm soát hay bạn đang gặp phải một trong những lỗi chỉ xảy ra khi phát hành chính thức.

Để hỗ trợ những trường hợp đó, chúng tôi cũng đã cải thiện trải nghiệm gỡ lỗi cơ bản.

Trước hết, nếu trước đây từng sử dụng tính năng gỡ lỗi WebAssembly thô, bạn có thể nhận thấy toàn bộ quá trình tháo rời hiện được hiển thị trong một tệp duy nhất – bạn không còn phải đoán hàm mà mục Sources (Nguồn) wasm-53834e3e/ wasm-53834e3e-7 có thể tương ứng với hàm nào.

Lược đồ tạo tên mới

Chúng tôi cũng cải thiện tên trong khung hiển thị tháo rời. Trước đây, bạn chỉ thấy các chỉ mục dạng số, hoặc đối với hàm thì không có tên nào cả.

Hiện tại, chúng ta đang tạo tên tương tự như các công cụ tháo rời khác, bằng cách sử dụng gợi ý trong phần tên WebAssembly, đường dẫn nhập/xuất và cuối cùng, nếu không có gì khác, hãy tạo các tên đó dựa trên loại và chỉ mục của mục như $func123. Bạn có thể thấy cách thức, trong ảnh chụp màn hình ở trên, điều này đã giúp việc có được các dấu vết ngăn xếp và tháo rời dễ đọc hơn một chút.

Khi không có thông tin về loại, có thể khó kiểm tra được bất kỳ giá trị nào ngoài dữ liệu gốc – ví dụ: con trỏ sẽ hiển thị dưới dạng số nguyên thông thường mà không có cách nào để biết nội dung được lưu trữ phía sau trong bộ nhớ.

Kiểm tra bộ nhớ

Trước đây, bạn chỉ có thể mở rộng đối tượng bộ nhớ WebAssembly, được biểu thị bằng env.memory trong chế độ xem Scope (Phạm vi) để tra cứu từng byte. Cách này hiệu quả trong một số trường hợp đơn giản, nhưng không đặc biệt thuận tiện cho việc mở rộng và không cho phép diễn giải lại dữ liệu ở các định dạng khác ngoài giá trị byte. Chúng tôi cũng đã thêm một tính năng mới để hỗ trợ việc này: trình kiểm tra bộ nhớ tuyến tính.

Nếu nhấp chuột phải vào env.memory, bạn sẽ thấy một tuỳ chọn mới có tên Kiểm tra bộ nhớ:

Trình đơn theo bối cảnh trên &quot;env.memory&quot; trong ngăn Phạm vi hiển thị mục &quot;Inspect Memory&quot; (Kiểm tra bộ nhớ)

Sau khi nhấp vào, thao tác này sẽ hiển thị Trình kiểm tra bộ nhớ, trong đó bạn có thể kiểm tra bộ nhớ WebAssembly ở chế độ xem thập lục phân và ASCII, điều hướng đến các địa chỉ cụ thể, cũng như diễn giải dữ liệu ở các định dạng khác nhau:

Ngăn Trình kiểm tra bộ nhớ trong Công cụ cho nhà phát triển hiển thị chế độ xem theo hệ thập lục phân và ASCII của bộ nhớ

Các cảnh báo và tình huống nâng cao

Phân tích mã WebAssembly

Khi bạn mở Công cụ cho nhà phát triển, mã WebAssembly sẽ được "giảm dần" thành phiên bản chưa được tối ưu hoá để cho phép gỡ lỗi. Phiên bản này chậm hơn nhiều, nghĩa là bạn không thể dựa vào console.time, performance.now và các phương pháp đo lường tốc độ khác của mã trong khi Công cụ cho nhà phát triển đang mở, vì các số liệu bạn nhận được sẽ hoàn toàn không thể hiện hiệu suất thực tế.

Thay vào đó, bạn nên sử dụng bảng Hiệu suất của Công cụ cho nhà phát triển. Bảng này sẽ chạy mã ở tốc độ tối đa và cung cấp cho bạn bảng phân tích chi tiết về thời gian dành cho các chức năng khác nhau:

Bảng điều khiển phân tích tài nguyên cho thấy nhiều chức năng của Wasm

Ngoài ra, bạn có thể chạy ứng dụng khi đã đóng Công cụ cho nhà phát triển và mở ứng dụng đó sau khi hoàn tất để kiểm tra Bảng điều khiển.

Chúng tôi sẽ cải thiện các tình huống lập hồ sơ trong tương lai, nhưng hiện tại bạn cần lưu ý. Nếu bạn muốn tìm hiểu thêm về các trường hợp phân cấp WebAssembly, hãy xem tài liệu của chúng tôi về quy trình biên dịch WebAssembly.

Xây dựng và gỡ lỗi trên nhiều máy (bao gồm cả Docker / máy chủ)

Khi tạo trong Docker, máy ảo hoặc trên máy chủ bản dựng từ xa, bạn có thể gặp phải tình huống mà đường dẫn đến tệp nguồn được sử dụng trong quá trình tạo bản dựng không khớp với đường dẫn trên hệ thống tệp của riêng bạn mà Công cụ của Chrome đang chạy. Trong trường hợp này, các tệp sẽ xuất hiện trong bảng điều khiển Sources (Nguồn) nhưng không tải được.

Để khắc phục vấn đề này, chúng tôi đã triển khai chức năng ánh xạ đường dẫn trong các tuỳ chọn tiện ích C/C++. Bạn có thể sử dụng công cụ này để ánh xạ lại các đường dẫn tuỳ ý và giúp Công cụ cho nhà phát triển xác định vị trí các nguồn.

Ví dụ: nếu dự án trên máy chủ của bạn nằm theo đường dẫn C:\src\my_project nhưng được tạo bên trong một vùng chứa Docker mà đường dẫn đó được biểu thị là /mnt/c/src/my_project, thì bạn có thể gán lại đường dẫn đó trong quá trình gỡ lỗi bằng cách chỉ định các đường dẫn đó làm tiền tố:

Trang các tuỳ chọn của tiện ích gỡ lỗi C/C++

Tiền tố phù hợp đầu tiên là "wins". Nếu bạn đã quen với các trình gỡ lỗi C++ khác, thì tuỳ chọn này cũng tương tự như lệnh set substitute-path trong GDB hoặc chế độ cài đặt target.source-map trong LLDB.

Gỡ lỗi bản dựng được tối ưu hoá

Giống như với mọi ngôn ngữ khác, tính năng gỡ lỗi hoạt động hiệu quả nhất nếu bạn tắt tính năng tối ưu hoá. Tính năng tối ưu hoá có thể cùng dòng các hàm với nhau, sắp xếp lại thứ tự mã hoặc xoá hoàn toàn các phần của mã. Tất cả những điều này có thể khiến trình gỡ lỗi nhầm lẫn và do đó, bạn là người dùng.

Nếu bạn không bận tâm đến trải nghiệm gỡ lỗi hạn chế hơn và vẫn muốn gỡ lỗi một bản dựng được tối ưu hoá, thì hầu hết tính năng tối ưu hoá sẽ hoạt động như mong đợi, ngoại trừ chức năng cùng dòng. Chúng tôi dự định giải quyết các vấn đề còn lại trong tương lai, nhưng hiện tại, vui lòng sử dụng -fno-inline để tắt trình phân tích này khi biên dịch bằng bất kỳ tính năng tối ưu hoá cấp -O nào, ví dụ:

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

Tách riêng thông tin gỡ lỗi

Thông tin gỡ lỗi lưu giữ nhiều thông tin chi tiết về mã của bạn, các kiểu, biến, hàm, phạm vi và vị trí đã xác định – bất kỳ phần nào có thể hữu ích với trình gỡ lỗi. Do đó, mã này thường có thể lớn hơn chính mã.

Để tăng tốc độ tải và biên dịch mô-đun WebAssembly, bạn có thể muốn tách thông tin gỡ lỗi này thành một tệp WebAssembly riêng. Để làm việc đó trong Emscripten, hãy truyền một cờ -gseparate-dwarf=… có tên tệp mong muốn:

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

Trong trường hợp này, ứng dụng chính sẽ chỉ lưu trữ tên tệp temp.debug.wasm và tiện ích trình trợ giúp sẽ có thể xác định và tải tệp đó khi bạn mở Công cụ cho nhà phát triển.

Khi kết hợp với các tính năng tối ưu hoá như mô tả ở trên, bạn thậm chí có thể dùng tính năng này để gửi các bản dựng chính thức đã được tối ưu hoá của ứng dụng, sau đó gỡ lỗi chúng bằng tệp phụ cục bộ. Trong trường hợp này, chúng tôi sẽ cần ghi đè URL đã lưu trữ để giúp tiện ích tìm thấy tệp phụ, ví dụ:

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]

Cần được tiếp tục...

Ồ, có rất nhiều tính năng mới!

Với tất cả các công cụ tích hợp mới đó, Công cụ của Chrome cho nhà phát triển trở thành một trình gỡ lỗi khả thi, mạnh mẽ không chỉ dành cho JavaScript mà còn cho các ứng dụng C và C++, giúp bạn lấy các ứng dụng, được tích hợp nhiều công nghệ và đưa chúng lên một môi trường Web dùng chung đa nền tảng.

Tuy nhiên, hành trình của chúng tôi vẫn chưa dừng lại. Một số việc chúng tôi sẽ thực hiện từ đây về sau:

  • Dọn dẹp những khó khăn trong quá trình gỡ lỗi.
  • Thêm tính năng hỗ trợ cho các trình định dạng kiểu tuỳ chỉnh.
  • Cải thiện tính năng phân tích tài nguyên cho các ứng dụng WebAssembly.
  • Thêm tính năng hỗ trợ cho mức độ sử dụng mã để bạn dễ dàng tìm thấy mã không sử dụng.
  • Cải thiện khả năng hỗ trợ các biểu thức trong hoạt động đánh giá trên bảng điều khiển.
  • Hỗ trợ thêm nhiều ngôn ngữ.
  • …và nhiều hình thức khác!

Trong thời gian chờ đợi, vui lòng giúp chúng tôi bằng cách dùng thử phiên bản beta hiện tại trên mã của riêng bạn và báo cáo mọi vấn đề phát hiện được cho https://bugs.chromium.org/p/chromium/issues/entry?template=DevTools+issue.

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.