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

Ingvar Stepanyan
Ingvar Stepanyan

Hành trình đến nay

Một năm trước, Chrome đã thông báo về tính năng hỗ trợ ban đầu cho tính năng gỡ lỗi WebAssembly gốc trong Chrome DevTools.

Chúng tôi đã minh hoạ tính năng hỗ trợ bước cơ bản và thảo luận về các cơ hội sử dụng thông tin DWARF thay vì bản đồ nguồn trong tương lai:

  • Giải quyết tên biến
  • Các loại in đẹp
  • Đánh giá biểu thức bằng ngôn ngữ nguồn
  • …và nhiều tính năng khác!

Hôm nay, chúng tôi rất vui mừng giới thiệu các tính năng đã hứa hẹn sẽ đi vào hoạt động và tiến độ mà các nhóm Emscripten và Chrome Công cụ 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 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 cần sử dụng phiên bản mới nhất của tất cả các công cụ theo rủi ro của riêng mình. Nếu gặp bất kỳ vấn đề nào, vui lòng báo cáo vấn đề đó tại https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350.

Hãy bắt đầu với cùng một ví dụ đơn giản về C 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, chúng ta sử dụng Emscripten mới nhất và truyền cờ -g, giống như trong bài đăng gốc, để đưa thông tin gỡ lỗi vào:

emcc -g temp.c -o temp.html

Bây giờ, chúng ta có thể phân phát trang đã tạo từ một máy chủ HTTP máy chủ cục bộ (ví dụ: với serve (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 Chrome DevTools và giúp tiện ích này 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ủa Công cụ dành 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 Experimental (Thử nghiệm) rồi đánh dấu vào WebAssembly Debugging: Bật tính năng hỗ trợ DWARF.

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

Khi bạn đóng Settings (Cài đặt), DevTools sẽ đề xuất tự tải lại để áp dụng các chế độ cài đặt. Hãy làm như vậy. Đó là toàn bộ quy 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 tính năng Tạm dừng khi có ngoại lệ (biểu tượng ⏸), sau đó kiểm tra Pause on9 alerts (Tạm dừng khi phát hiện các trường hợp ngoại lệ phát hiện được) rồi tải lại trang. Bạn sẽ thấy Công cụ cho nhà phát triển tạm dừng khi gặp trường hợp ngoại lệ:

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

Theo mặc định, trình gỡ lỗi sẽ dừng ở mã keo do Emscripten tạo, nhưng ở bên phải, bạn có thể thấy chế độ xem Call Stack (Ngăn xếp lệnh gọi) đại diện cho 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:

DevTools tạm dừng trong hàm `assert_less` và hiển thị các giá trị của `x` và `y` trong chế độ xem Phạm vi

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

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

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

Hãy xem một ví dụ phức tạp hơn để minh hoạ những điều đó. Lần này, chúng ta sẽ vẽ một hình ảnh Mandelbrot fractal bằng mã C++ sau:

#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ỏ – đó là một tệp đơn chứa 50 dòng mã, nhưng lần này tôi cũng sử dụng một số API bên ngoài, như thư viện SDL cho đồ hoạ cũng như số phức trong thư viện chuẩn C++.

Tôi sẽ biên dịch bằng cùng một cờ -g như trên để đưa vào thông tin gỡ lỗi, đồng thời 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 vào trang được tạo trong trình duyệt, tôi có thể thấy hình dạng fractal 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 gặp lỗi trong mã (phew!). Vì vậy, hãy đặt một số điểm ngắt ở đầu mã.

Khi chúng ta 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 tạm dừng ở lệnh gọi `SDL_Init`

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

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 để chuyển sang một chút.

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

Tại thời điểm này, palette của chúng ta đã được lấp đầy bằng một số màu ngẫu nhiên, và chúng ta có thể mở rộng cả mảng này 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ờ đầy đủ). 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 lồng sâu mà khó điều hướng qua chế độ xem Phạm vi, bạn cũng có thể sử dụng tính năng đánh giá Bảng điều khiển! Tuy nhiên, xin lưu ý rằng các biểu thức C++ phức tạp hơn chưa được hỗ trợ.

Bảng điều khiển của bảng điều khiển hiển thị kết quả của `palette[10].r`

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 cũng đang thay đổi – bằng cách xem lại chế độ xem 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 bằng cách di chuột qua biến trong mã nguồn:

Chú giải công cụ trên biến &quot;x&quot; trong nguồn cho thấy giá trị &quot;3&quot;

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

Chú giải công cụ và chế độ xem Phạm vi cho thấy giá trị của các biến &quot;color&quot;, &quot;point&quot; và các biến khác

Ok, tất cả đều hoạt động tốt khi có thông tin gỡ lỗi, nhưng 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 thì sao?

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 thư viện đó 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 các nguồn liên kết. Hãy cùng tìm hiểu lại SDL_RenderDrawColor:

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

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

Giờ thì trông có vẻ hơi đáng sợ và không phải là vấn đề mà hầu hết các nhà phát triển web sẽ cần giải quyết. Tuy nhiên, đô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 bên thứ 3 mà bạn không có quyền kiểm soát hay vì bạn đang gặp phải một trong những lỗi chỉ xảy ra khi phát hành công khai.

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

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

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

Chúng tôi cũng đã cải thiện tên trong chế độ xem tháo rời. Trước đây, bạn chỉ thấy các chỉ mục dạng số hoặc trong trường hợp hàm, hoàn toàn không có tên.

Bây giờ, 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 các gợi ý trong phần tên WebAssembly, đường dẫn nhập/xuất và cuối cùng, nếu mọi thứ khác không thành công, hãy tạo chúng dựa trên loại và chỉ mục của mục như $func123. Bạn có thể thấy trong ảnh chụp màn hình ở trên, việc này đã giúp bạn đọc được một chút dấu vết ngăn xếp và tháo rời.

Khi không có thông tin về loại, bạn có thể khó kiểm tra bất kỳ giá trị nào ngoài các giá trị gốc – ví dụ: con trỏ sẽ xuất hiện dưới dạng số nguyên thông thường, không có cách nào để biết nội dung được lưu trữ đằng sau các con trỏ đó 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 Phạm vi để tra cứu từng byte. Phương thức này hoạt động trong một số trường hợp không quan trọng, nhưng không đặc biệt thuận tiện để mở rộng và không cho phép diễn giải lại dữ liệu ở định dạng khác với giá trị byte. Chúng tôi cũng đã thêm một tính năng mới để giúp giải quyết vấn đề 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 là Inspect memory (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;Kiểm tra bộ nhớ&quot;

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

Ngăn Trình kiểm tra bộ nhớ trong Công cụ của Chrome cho nhà phát triển hiển thị chế độ xem bộ nhớ dạng hex và ASCII

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

Phân tích mã WebAssembly

Khi bạn mở Công cụ cho nhà phát triển, mã WebAssembly sẽ được "hạ cấp" xuống phiên bản chưa được tối ưu hoá để bật tính năng 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 thức khác để đo tốc độ mã của mình khi DevTools đang mở, vì các con số bạn nhận được sẽ không thể hiện hiệu suất thực tế.

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

Bảng điều khiển phân tích tài nguyên hiển thị nhiều hàm Wasm

Ngoài ra, bạn có thể chạy ứng dụng khi DevTools đang đóng và mở DevTools 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 phân tích tài nguyên trong tương lai, nhưng hiện tại, bạn cần lưu ý điều này. Nếu bạn muốn tìm hiểu thêm về các tình huống 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.

Tạo bản 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 trường hợp đường dẫn đến các tệp nguồn được dùng trong quá trình xây dựng không khớp với đường dẫn trên hệ thống tệp của riêng bạn nơi Công cụ cho nhà phát triển 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 liên kết đườ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 nguồn.

Ví dụ: nếu dự án trên máy chủ lưu trữ của bạn nằm trong đường dẫn C:\src\my_project, nhưng được tạo bên trong vùng chứa Docker, trong đó đường dẫn đó được biểu thị là /mnt/c/src/my_project, thì bạn có thể ánh xạ 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 tuỳ chọn của tiện ích gỡ lỗi C/C++

Tiền tố khớp đầu tiên là "wins" (thắng). Nếu bạn đã quen thuộc với các trình gỡ lỗi C++ khác, tuỳ chọn này 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ư mọi ngôn ngữ khác, việc gỡ lỗi sẽ hoạt động hiệu quả nhất nếu bạn tắt tính năng tối ưu hoá. Các hoạt động tối ưu hoá có thể đưa các hàm vào cùng một dòng, sắp xếp lại mã hoặc xoá hoàn toàn một số phần của mã. Tất cả những điều này có thể gây nhầm lẫn cho trình gỡ lỗi và do đó, gây khó khăn cho bạn khi sử dụng.

Nếu bạn không ngại trải nghiệm gỡ lỗi bị 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 các tính năng tối ưu hoá sẽ hoạt động như dự kiến, ngoại trừ tính năng nội tuyến hàm. 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 tính năng này khi biên dịch với 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

Phân tách 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ác loại, biến, hàm, phạm vi và vị trí đã xác định – mọi thứ có thể hữu ích cho trình gỡ lỗi. Do đó, tệp này thường có thể lớn hơn bản thân mã.

Để tăng tốc độ tải và biên dịch mô-đun WebAssembly, bạn nên tách thông tin gỡ lỗi này thành một tệp WebAssembly riêng. Để thực hiện việc đó trong Emscripten, hãy truyền cờ -gseparate-dwarf=… với 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ợ giúp sẽ có thể định vị và tải tên tệp đó khi bạn mở DevTools.

Khi kết hợp với các tính năng tối ưu hoá như mô tả ở trên, tính năng này thậm chí có thể được dùng để phân phối các bản dựng sản xuất gần như được tối ưu hoá của ứng dụng, sau đó gỡ lỗi các bản dựng đó bằng tệp phía máy. Trong trường hợp này, chúng ta cũng cần ghi đè URL đã lưu để 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]

Tiếp tục…

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

Với tất cả các tính năng 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 hiệu quả, mạnh mẽ không chỉ cho JavaScript mà còn cho các ứng dụng C và C++, giúp bạn dễ dàng hơn bao giờ hết trong việc đưa các ứng dụng được xây dựng bằng nhiều công nghệ lên một Web dùng chung, đa nền tảng.

Tuy nhiên, hành trình của chúng ta vẫn chưa kết thúc. Từ nay về sau, chúng tôi sẽ làm việc này:

  • Sửa các lỗi còn tồn tại trong trải nghiệm gỡ lỗi.
  • Thêm tính năng hỗ trợ cho các trình định dạng loại tuỳ chỉnh.
  • Đang 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ợ mức độ sử dụng mã để dễ dàng tìm thấy mã không dùng đến.
  • Cải thiện khả năng hỗ trợ biểu thức trong việc đánh giá bảng điều khiển.
  • Đang thêm tính năng hỗ trợ cho nhiều ngôn ngữ hơn.
  • …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://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350.

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

Sử dụng các lựa chọn sau để thảo luận về các tính năng mới, bản cập nhật hoặc bất cứ điều gì khác liên quan đến Công cụ cho nhà phát triển.