目前为止的道路
一年前,Chrome 宣布初步支持在 Chrome 开发者工具中调试原生 WebAssembly。
我们演示了基本的步进支持,并讨论了我们未来可以使用 DWARF 信息(而非源映射)的机会:
- 解析变量名称
- 美观打印类型
- 对源语言中的表达式求值
- ...等等!
今天,我们很高兴地展示所承诺的功能面世,以及 Emscripten 和 Chrome 开发者工具团队今年取得的进展,尤其是在 C 和 C++ 应用上。
在开始之前,请注意,这仍是全新体验的 Beta 版,您需要使用最新版本的所有工具,风险自担;如果您遇到任何问题,请向 https://bugs.chromium.org/p/chromium/issues/entry?template=DevTools+issue 报告。
我们先使用上一次同样简单的 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
现在,我们可以从 localhost HTTP 服务器提供生成的页面(例如,使用 serve),并在最新版的 Chrome Canary 中打开该页面。
这一次,我们还需要一个与 Chrome DevTools 集成的辅助扩展程序,并帮助其理解 WebAssembly 文件中编码的所有调试信息。请前往以下链接进行安装:goo.gle/wasm-debugging-extension
您还需要在开发者工具 Experimentals 中启用 WebAssembly 调试。打开 Chrome 开发者工具,点击开发者工具窗格右上角的齿轮 (⚙) 图标,转到⚙面板并勾选 ⚙。
当您关闭 Settings 时,开发者工具会建议重新加载自身以应用设置,让我们来一探究竟。一次性设置到这里就结束了
现在,我们可以返回来源面板,启用在异常上暂停(⏸ 图标),然后选中在捕获异常时暂停并重新加载页面。您应该会看到开发者工具在发生异常时暂停:
默认情况下,它会停止在 Emscripten 生成的粘合代码上,但您可以在右侧看到一个表示错误堆栈轨迹的 Call Stack 视图,并且可以导航到调用 abort
的原始 C 代码行:
现在,如果查看 Scope 视图,您可以看到 C/C++ 代码中的变量的原始名称和值,而无需再弄清楚 $localN
等乱码名称的含义以及它们与您编写的源代码的关系。
这不仅适用于整数等基元值,也适用于结构、类、数组等复合类型!
富媒体类型支持
我们通过一个更复杂的示例来说明这一点。这一次,我们将使用以下 C++ 代码绘制 Mandelbrot 分形:
#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++ 源代码中:
我们右侧已经可以看到所有变量,但目前只有 width
和 height
进行了初始化,因此没有什么需要检查的了。
我们在主 Mandelbrot 循环内设置另一个断点,然后恢复执行以向前跳过一点。
此时,palette
已填充了一些随机颜色,我们可以扩展数组本身以及单个 SDL_Color
结构,并检查其组件以验证一切是否正常(例如,“alpha”通道始终设置为完全不透明)。同样,我们可以扩展并检查存储在 center
变量中的复数的实部和虚部。
如果要访问深层嵌套属性,否则很难通过 Scope 视图转到该属性,也可以使用 Console 评估!但请注意,尚不支持更复杂的 C++ 表达式。
我们继续执行几次之后,可以看到内部 x
的变化情况,具体方法是:再次查看 Scope 视图,将变量名称添加到监视列表中,在控制台中对其进行评估,或者将鼠标悬停在源代码中的变量上:
在这里,我们可以执行单步或单步退出 C++ 语句,并观察其他变量如何变化:
好了,有了调试信息,一切都很不错,但如果我们要调试未使用调试选项构建的代码,该怎么办?
原始 WebAssembly 调试
例如,我们要求 Emscripten 为我们提供一个预构建的 SDL 库,而不是我们自行从源代码进行编译,因此至少目前,调试程序无法查找关联的源代码。让我们再次进入,以进入 SDL_RenderDrawColor
:
我们回到原始的 WebAssembly 调试体验。
现在,这看起来有点可怕,大多数 Web 开发者都不需要处理这个问题,但有时您可能想要对在没有调试信息的情况下构建的库进行调试,原因可能是该库是您无法控制的第三方库,或者您遇到了只在生产环境中发生的 bug。
为了有助于应对此类情况,我们还对基本调试体验进行了一些改进。
首先,如果您之前使用过原始 WebAssembly 调试,您可能会发现整个反汇编现在显示在单个文件中,而无需猜测 Sources 条目 wasm-53834e3e/
wasm-53834e3e-7
可能对应于哪个函数。
新的名称生成方案
我们还改进了反汇编视图中的名称。以前,您只会看到数字索引;如果是函数,则不会看到任何名称。
现在,我们生成与其他反汇编工具类似的名称,方法是使用 WebAssembly 名称部分中的提示、导入/导出路径,最后,如果其他一切都失败,则根据项的类型和索引生成这些名称,例如 $func123
。您可以看到,从上面的屏幕截图中可以看出,这样做已经有助于获得略显易读的堆栈轨迹和反汇编。
如果没有可用的类型信息,则可能很难检查基元之外的任何值。例如,指针将显示为常规整数,无法得知内存中其背后存储的内容。
内存检查
以前,您只能展开 WebAssembly 内存对象(在 Scope 视图中由 env.memory
表示)来查找各个字节。这种方法在某些无关紧要的场景中有效,但对扩展不是特别方便,并且不允许以字节值以外的格式重新解释数据。此外,我们还添加了一个线性内存检查器的新功能来帮助实现此目的。
如果您右键点击 env.memory
,现在应该会看到一个名为 Inspect memory 的新选项:
点击该按钮后,系统会显示内存检查器,您可以在其中以十六进制视图和 ASCII 视图检查 WebAssembly 内存、导航到特定地址,以及解读不同格式的数据:
高级场景和注意事项
对 WebAssembly 代码进行性能分析
当您打开开发者工具时,WebAssembly 代码会“分层”为未经优化的版本,以启用调试功能。此版本的速度要慢得多,这意味着,在开发者工具处于打开状态时,您不能依赖 console.time
、performance.now
和其他方法来测量代码速度,因为您获得的数据完全不代表实际性能。
相反,您应该使用开发者工具“Performance”面板,该面板将全速运行代码,并为您提供不同函数所用时间的详细明细:
或者,您也可以在关闭开发者工具的情况下运行应用,并在完成后打开它们以检查 Console。
我们将来改进性能分析场景,但目前需要注意。如果您想详细了解 WebAssembly 分层场景,请参阅有关 WebAssembly 编译流水线的文档。
在不同机器(包括 Docker / 主机)上构建和调试
在 Docker、虚拟机或远程构建服务器上进行构建时,您可能会遇到构建期间所用源文件的路径与运行 Chrome 开发者工具的您自己的文件系统上的路径不一致的情况。在这种情况下,文件会显示在来源面板中,但无法加载。
为了解决此问题,我们在 C/C++ 扩展选项中实现了路径映射功能。您可以使用它重新映射任意路径,并帮助开发者工具找到源代码。
例如,如果宿主机上的项目位于路径 C:\src\my_project
下,但是在 Docker 容器内构建,该路径以 /mnt/c/src/my_project
表示,您可以在调试期间将这些路径指定为前缀来重新映射:
第一个匹配的前缀“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
,在您打开开发者工具时,帮助程序扩展程序将能够找到并加载此文件名。
与上述优化措施结合使用时,此功能甚至可以用于交付几乎优化的应用正式版 build,之后再通过本地辅助文件调试这些 build。在这种情况下,我们还需要替换存储的网址,以帮助扩展程序查找辅助文件,例如:
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 DevTools 成为一个可行且强大的调试程序,不局限于 JavaScript,也适用于 C 和 C++ 应用。这样一来,就能比以往更轻松地部署采用各种技术构建的应用,并将其引入共享的跨平台 Web 应用中。
不过,我们的旅程尚未结束。从现在开始,我们将逐步完成以下事项:
- 清理调试体验中的粗糙边缘。
- 添加对自定义类型格式设置工具的支持。
- 我们正在努力改进 WebAssembly 应用的性能分析。
- 添加了对代码覆盖率的支持,以便更轻松地查找未使用的代码。
- 改进了对控制台评估中表达式的支持。
- 增加对更多语言的支持。
- …等等!
与此同时,请使用您自己的代码试用当前 Beta 版,并向 https://bugs.chromium.org/p/chromium/issues/entry?template=DevTools+issue 报告发现的任何问题,以帮助我们解决这个问题。
下载预览渠道
您可以考虑将 Chrome Canary 版、Dev 版或 Beta 版用作默认开发浏览器。通过这些预览渠道,您可以使用最新的开发者工具功能,测试先进的网络平台 API,并在用户采取行动之前发现网站上的问题!
与 Chrome 开发者工具团队联系
使用以下选项讨论博文中的新功能和变化,或讨论与开发者工具有关的任何其他内容。
- 通过 crbug.com 提交建议或反馈。
- 使用开发者工具中的更多选项 > Help > Report a DevTools issues来报告开发者工具问题。
- 发推文:@ChromeDevTools。
- 请在 YouTube 视频或“开发者工具提示”YouTube 视频中留言说明“开发者工具的新变化”。