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 đã công bố hỗ trợ ban đầu cho tính năng gỡ lỗi WebAssembly gốc trong Công cụ dành cho nhà phát triển Chrome.

Chúng tôi đã trình bày hỗ trợ đi bộ cơ bản và nói về các cơ hội việc sử dụng thông tin DWARF thay vì bản đồ nguồn sẽ mở ra 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 điểm cải tiến khác!

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 trong thực tế và tiến trình của nhóm Emscripten và Công cụ của Chrome cho nhà phát triển đã đạt được 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 cách tự chịu rủi ro. 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 tệp này, chúng ta sử dụng Emscripten mới nhất và chuyển một cờ -g, giống như trong bài đăng gốc, để bao gồm gỡ lỗi của bạn:

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 cục bộ (ví dụ: bằng serve) và mở trang đó trong Chrome Canary mới nhất.

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

Bạn cũng cần bật tính năng gỡ lỗi WebAssembly trong Công cụ cho nhà phát triển Thử nghiệm. 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ụ của Chrome cho nhà phát triển, chuyển đến bảng điều khiển Thử nghiệm rồi đánh dấu vào Gỡ lỗi WebAssembly: 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. Vậy là hết rồi thiết lập.

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 on exceptions (Tạm dừng khi có ngoại lệ) (biểu tượng ⏸), sau đó đánh dấu vào Pause on caught exceptions (Tạm dừng khi phát hiện ngoại lệ) 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, công cụ này dừng trên mã keo do Emscripten tạo, nhưng trên Bạn có thể thấy chế độ xem 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ể điều hướng đế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 Scope (Phạm vi), bạn có thể thấy các tên ban đầu 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 những tên bị hỏng như $localN và chúng liên quan như thế nào đến mã nguồn 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 fractal Mandelbrot 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 ứng dụng tệp 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 hình ảnh cũng như số phức từ Thư viện chuẩn C++.

Tôi sẽ biên dịch mã này với cùng một cờ -g như trên để đưa vào thông tin gỡ lỗi, đồng thời tôi cũng sẽ yêu cầu Emscripten cung cấp SDL2 thư viện 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 tôi truy cập trang được tạo trong trình duyệt, tôi có thể thấy giao diện hình fractal 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 chỉ có widthheight hiện được khởi tạo nên không cần 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

Lúc này, palette của chúng ta đã được tô bằng một số màu ngẫu nhiên, và chúng ta có thể mở rộng cả mảng, cũng như từng cá nhân SDL_Color cấu trúc 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 đến độ mờ hoàn toàn). Tương tự, chúng ta có thể mở rộng và kiểm tra các biến thực phần ảo của số phức được lưu trữ trong biến center.

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

Bảng điều khiển cho thấy kết quả của &quot;bảng điều khiển[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 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

Được rồi, tất cả tính năng này đề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 tính năng gỡ lỗi thì sao không?

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 thực hiện lại một lần nữa để tìm hiểu về SDL_RenderDrawColor:

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

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

Hiện tại, việc 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 sẽ cần phải 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 vì bạn đang gặp phải một trong những lỗi chỉ xảy ra trong quá trình phát hành chính thức.

Để hỗ trợ trong những trường hợp đó, chúng tôi đã thực hiện một số cải tiến đối với trải nghiệm gỡ lỗi của bạn.

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

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. 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 trong việc mở rộng và không cho phép diễn giải lại dữ liệu ở đị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 để 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 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 Scope hiển thị thông tin &quot;Inspect Memory&quot; (Kiểm tra bộ nhớ) mục

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 tài nguyên 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 DevTools. 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 bằng Công cụ cho nhà phát triển đã đóng và hãy mở chú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, cần lưu ý. Nếu bạn muốn tìm hiểu thêm về WebAssembly các trường hợp phân theo tầng, 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 bản dựng 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 tệp nguồn được sử dụng trong 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 nơi Chrome DevTools đang chạy. Trong trường hợp này, 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ể dùng công cụ này để gán 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í nguồn.

Ví dụ: nếu dự án trên máy chủ lưu trữ của bạn nằm trong một đường dẫn C:\src\my_project, nhưng được xây dựng bên trong một vùng chứa Docker nơi đường dẫn đó được biểu thị dưới dạng /mnt/c/src/my_project, nên bạn có thể gán lại nó trở lại 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 "wins". 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, tính năng gỡ lỗi hoạt động hiệu quả nhất nếu bạn sử dụng tính năng tối ưu hoá tắt. Tối ưu hoá có thể cùng dòng hàm này với hàm khác, sắp xếp lại thứ tự hoặc xóa hoàn toàn các phần của mã-và tất cả thao tác này đều có cơ hội gây nhầm lẫn cho trình gỡ lỗi và do đó bạn với tư cách là người 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 trong tương lai, nhưng hiện tại, vui lòng sử dụng -fno-inline để hãy tắt chế độ đó 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

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=… bằng 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ể xác định và tải công cụ đó 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, 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ả những công cụ tích hợp mới đó, Công cụ của Chrome cho nhà phát triển trở nên khả thi, trình gỡ lỗi mạnh mẽ không chỉ dành cho JavaScript mà còn cho ứng dụng C và C++, giúp họ sử dụng ứng dụng dễ dàng hơn bao giờ hết, được tích hợp sẵn nhiều công nghệ và đưa chúng đến với Web chia sẻ, đa nền tảng.

Tuy nhiên, hành trình của chúng ta vẫn chưa dừng lại. Sau đây là một số việc chúng tôi sẽ làm từ giờ trở đi:

  • Vệ sinh các cạnh thô 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.
  • 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 bất kỳ lỗi nào tìm được vấn đề đến https://issues.chromium.org/issues/new?noWizard=true&amp;template=0&amp;component=1456350.

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 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à tìm vấn đề trên trang web của bạn 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 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.