使用现代工具调试 WebAssembly

Ingvar Stepanyan
Ingvar Stepanyan

截至目前的道路

一年前,Chrome 宣布初步支持

我们演示了基本单步调试支持,并讨论了未来使用 DWARF 信息(而非源代码映射)为我们带来的机会:

  • 解析变量名称
  • 美观输出类型
  • 对源语言中的表达式求值
  • ...等等!

今天,我们高兴地向大家介绍所承诺的功能, Emscripten 和 Chrome DevTools 团队在 尤其是 C 和 C++ 应用。

开始之前,请注意,这仍是新体验的 Beta 版,您需要自行承担使用所有工具的最新版本的风险。如果您遇到任何问题,请前往 https://issues.chromium.org/issues/new?noWizard=true&template=0&component=1456350 报告问题。

我们先从上次的那个简单的 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);
}

为了编译该版本,我们使用最新的 Emscripten 并传递 -g 标志(就像在原始博文中一样),以包含调试 信息:

emcc -g temp.c -o temp.html

现在我们可以从本地主机 HTTP 服务器(对于 (使用 serve)以及 在最新的 Chrome Canary 版中打开它。

这次,我们还需要一个能与 Chrome 集成的帮助程序扩展程序 并帮助它理解所有调试信息 在 WebAssembly 文件中编码。请前往以下网址进行安装: 链接:goo.gle/wasm-debugging-extension

您还需要在 DevTools 的 Experiments 中启用 WebAssembly 调试。打开 Chrome 开发者工具,点击工具栏中的齿轮图标 () 在开发者工具窗格的右上角,转到实验面板 然后选中 WebAssembly Debugging: Enable DWARF support

开发者工具设置的“实验”窗格

当您关闭 Settings 时,开发者工具会建议自行重新加载 来应用设置,就这样吧以上就是 设置。

现在,我们可以返回到 Sources 面板,启用 Pause on exceptions(“在出现异常时暂停”图标),然后选中 Pause on caught exceptions(“在捕获到异常时暂停”)并重新加载页面。您应该会看到开发者工具在出现异常时暂停:

“Sources”面板的屏幕截图,显示了如何启用“在遇到异常时暂停”

默认情况下,它会在 Emscripten 生成的粘合代码上停止,但您可以在右侧看到表示错误堆栈轨迹的调用堆栈视图,并可以导航到调用了 abort 的原始 C 代码行:

开发者工具已在 `assert_less` 函数中暂停,并在“Scope”视图中显示“x”和“y”的值

现在,如果查看范围视图,您会看到原始名称 和变量的值,无需再费时费力 了解像“$localN”这样的被破坏名称有什么含义,以及这些名字 您所编写的源代码

这不仅适用于整数等原始值,还适用于复合 例如结构、类、数组等!

丰富的类型支持

我们通过一个更为复杂的示例来展示这些内容。这个 我们将使用下面的模型画一个曼德博分形 以下 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();
}

您可以看到,这个应用仍然相当小, 包含 50 行代码的文件 - 但这一次,我还使用了 外部 API,例如用于实现以下目的的 SDL 库: 以及复数 C++ 标准库。

我将使用与上文相同的 -g 标志进行编译,以包含 调试信息,还会要求 Emscripten 提供 SDL2。 库,并允许任意大小的内存:

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

当我在浏览器中访问生成的页面时 带有一些随机颜色的分形形状:

演示页面

当我打开开发者工具时,可以再次看到原始的 C++ 文件。这个 不过,代码不会出错(哇!), 在代码开头添加一些断点。

当我们再次重新加载页面时,调试程序会在我们的 C++ 源代码:

开发者工具在调用 `SDL_Init` 时暂停

我们可以在右侧看到所有变量,但只有 widthheight 都是初始化阶段, 检查。

我们在 Mandelbrot 主循环内设置另一个断点,然后继续 执行以向前跳转一点。

开发者工具已在嵌套循环内暂停

此时,palette 已填充了一些随机颜色, 我们可以展开数组本身 SDL_Color 结构,并检查其组件,以验证 一切正常(例如,始终将“alpha”渠道设置为 设为完全不透明度)。同样,我们可以扩展并检查 存储在 center 变量中的复数的虚部。

如果需要访问在其他方面难以访问的深层嵌套属性 进入范围视图,您可以使用控制台 评估!不过请注意,更复杂的 C++ 表达式不支持 尚未获得支持。

显示 `palette[10].r` 结果的控制台面板

我们再执行几次,看看内部 x 的效果如何 也可以再次查看 Scope 视图,添加 将变量名称添加到监控列表,在控制台中对其进行评估,或者通过 将鼠标悬停在源代码中的变量上:

关于来源中变量“x”的提示,其值显示值“3”

在这里,我们可以步入或调试 C++ 语句,并观察如何 其他变量也会在发生变化:

显示 `color`、`point` 和其他变量的值的提示和 Scope 视图

好的,在有调试信息的情况下,所有这些都非常有效,但如果我们想调试未使用调试选项构建的代码,该怎么办?

原始 WebAssembly 调试

例如,我们要求 Emscripten 为开发者提供预构建的 SDL 库, 而不是自行从源代码进行编译,至少 。 让我们再次进入 SDL_RenderDrawColor

开发者工具显示 `mandelbrot.wasm` 的分解视图

我们将恢复原始的 WebAssembly 调试体验。

现在,这看起来有点害怕,大多数 Web 开发者都不会遇到 但有时您可能想调试 而构建时没有调试信息,无论是由于 是您无法控制的第三方库,或者您 只是在生产环境中遇到的一种错误

为帮助解决此类问题,我们对基础版的基础 API 功能 调试经验。

首先,如果您以前用过原始的 WebAssembly 调试功能, 请注意,整个拆解过程现在显示在一个文件中, 更多猜测 Sources 条目 wasm-53834e3e/ wasm-53834e3e-7 可能对应于哪个函数。

新的名称生成方案

我们还改进了分解视图中的名称。之前,您会看到 而如果是函数,则不必指定名称。

现在,我们要生成与其他反汇编工具类似的名称 使用 WebAssembly 名称部分中的提示, 导入/导出路径,最后,如果其他一切都失败, 根据项的类型和索引(例如 $func123)替换它们。您可以 在上面的屏幕截图中, 堆栈轨迹和反汇编代码更易读

如果没有类型信息,可能很难检查 除基元外的其他值 常规整数中存储了什么内容, 内存。

内存检查

以前,您只能展开 WebAssembly 内存对象(在 Scope 视图中由 env.memory 表示),以便查找 各个字节这在一些无关紧要的场景中行之有效, 特别方便扩展,且无需重新解读数据 采用非字节值的格式。我们新增了一项功能 线性内存检查器。

如果您右键点击 env.memory,现在应该会看到一个名为检查内存的新选项:

“Scope”窗格中“env.memory”上的上下文菜单,其中显示了“Inspect Memory”商品

点击后,它会调出一个 Memory Inspector,位于 您可以用十六进制和 ASCII 视图检查 WebAssembly 内存, 导航到特定地址,以及解析 不同的格式:

DevTools 中的“Memory Inspector”窗格,显示内存的十六进制和 ASCII 视图

高级场景和注意事项

剖析 WebAssembly 代码的性能

当您打开开发者工具时,WebAssembly 代码会“向下分层”更改为 启用调试功能。这个版本的网速要慢很多 也就是说,您不能依赖于 console.timeperformance.now 以及使用开发者工具来衡量代码速度的其他方法 您得到的数字并不代表实际效果

您应改用开发者工具的性能面板。 该库会全速运行代码,并为您提供 不同职能部门所用时间的明细:

显示各种 Wasm 函数的分析面板

或者,您也可以在关闭开发者工具的情况下运行应用,并且 完成后打开它们以检查控制台

未来我们会改进分析场景,但目前它是一项 一些注意事项如果您想详细了解 WebAssembly 请参阅 WebAssembly 编译流水线文档。

在不同机器(包括 Docker / 主机)上构建和调试

在 Docker、虚拟机或远程构建服务器上进行构建时, 您可能会遇到这样的情况 与您自己的文件系统中的路径不匹配,其中 Chrome 开发者工具正在运行。在这种情况下,文件将显示在 Sources 面板,但无法加载。

为了解决此问题,我们在 Google Cloud 中实现了 C/C++ 扩展选项。您可以使用它来重新映射任意路径, 帮助开发者工具找到源代码。

例如,如果宿主机上的项目位于 C:\src\my_project,但它是在 Docker 容器内构建的, 该路径表示为 /mnt/c/src/my_project,您可以重新映射 将这些路径指定为前缀,以便在调试期间返回:

C/C++ 调试扩展程序的选项页面

第一个匹配的前缀“wins”。如果您熟悉其他 C++ 调试程序,则此选项类似于 GDB 中的 set substitute-path 命令或 LLDB 中的 target.source-map 设置。

调试优化型 build

与其他任何语言一样,在进行优化时,调试效果最好 已停用。优化可能会将一个函数内嵌到另一个函数中、重新排列代码或完全移除代码的某些部分,而所有这些都有可能混淆调试程序,进而混淆您(用户)。

如果您不介意调试体验受到较多限制,但仍想 调试优化型 build 之后,大部分优化措施 (函数内嵌除外)。我们计划日后解决其余问题,但目前,请在使用任何 -O 级优化进行编译时使用 -fno-inline 停用它,例如:

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

分离调试信息

调试信息保留了有关您代码的大量详细信息, 类型、变量、函数、范围和位置 - 任何可能 对调试程序很有用。因此,该值通常会大于 代码本身。

为了加快 WebAssembly 模块的加载和编译速度,您可能需要将此调试信息拆分到单独的 WebAssembly 文件中。如需在 Emscripten 中执行此操作,请传递带有所需文件名的 -gseparate-dwarf=… 标志:

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

在这种情况下,主要应用只会存储文件名 temp.debug.wasm,当您打开 DevTools 时,辅助扩展程序将能够找到并加载该文件名。

与上述优化措施结合使用时,此功能可 甚至可以用于发布几乎经过优化的 应用,稍后使用本地端文件调试它们。在此示例中 我们还需要覆盖存储的网址 找到辅助文件,例如:

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]

未完待续...

哇,真是增加了不少新功能!

得益于所有这些新的集成,Chrome 开发者工具成为 功能强大的调试程序,不仅适用于 JavaScript,也适用于 C 和 C++ 应用, 它内置了各种 并将它们引入一个共享的跨平台网络。

不过,我们的旅程尚未结束。我们将介绍 从现在开始:

  • 清除了调试体验中的粗糙边缘。
  • 添加了对自定义类型格式化程序的支持。
  • 我们正努力改进 性能分析功能。
  • 添加对代码覆盖率的支持,使其更容易查找 未使用的代码。
  • 改进了对控制台计算中的表达式的支持。
  • 增加对更多语言的支持。
  • …等等!

与此同时,欢迎您对您自己的代码试用当前的 Beta 版,并报告我们找到的任何 问题至 https://issues.chromium.org/issues/new?noWizard=true&amp;template=0&amp;component=1456350.

下载预览渠道

请考虑将 Chrome Canary开发者版Beta 版用作您的默认开发浏览器。通过这些预览渠道,您可以访问最新的开发者工具功能,测试先进的网络平台 API,并在用户之前发现您网站上的问题!

与 Chrome 开发者工具团队联系

使用以下选项讨论博文中的新功能和变更,或与开发者工具相关的任何其他内容。

  • 请通过 crbug.com 向我们提交建议或反馈。
  • 使用更多选项报告开发者工具问题 展开 >帮助 >在开发者工具中报告开发者工具问题
  • 请发送电子邮件至 @ChromeDevTools
  • 请对我们的开发者工具新功能 YouTube 视频或开发者工具提示 YouTube 视频发表评论。