1. 摘要
WebAssembly(WASM)是一种高效的低级字节码格式,可以在现代浏览器中运行近乎原生速度的代码。然而,由于其编译产物是二进制字节码,调试 WebAssembly 模块常常变得复杂和困难。C++ 与 Emscripten 是常见的组合,用于将 C++ 代码编译为 WebAssembly 模块。
本文将介绍如何使用 Emscripten 编译 C++ 代码生成 WebAssembly 模块,并生成和利用 DWARF 调试信息,在浏览器中实现高效的调试过程。
2. 准备工作
安装 C/C++ DevTools Support (DWARF) 插件(chromewebstore.google.com/detail/cc++…
DWARF 是什么?
它的全称是 "Debugging With Attributed Record Formats"。DWARF 格式允许编译器在生成的二进制文件中嵌入或分离详细的调试信息,这些信息可以被调试器读取和使用,以便开发者在调试代码时能够查看源代码、变量值、调用栈等。
插件安装成功并开启后会有这个图标:
3.编译携带调试信息的产物
3.1 编译过程
以下是一段会抛出异常的代码 temp.cc:
#include <stdlib.h>
void assert_less(int x, int y) {
if (x >= y) {
abort();
}
}
int main() {
assert_less(10, 20);
assert_less(30, 20);
return 0;
}
使用 emscripten 编译携带调试信息的产物:
emcc -g temp.cc -o temp.js
编译产物:
加载编译产物:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script>
Module = {};
Module.onRuntimeInitialized = function () {
console.log('onRuntimeInitialized');
}
</script>
<script src="./temp.js"></script>
</head>
<body>
</body>
</html>
3.2 调试演示
关闭插件时的错误信息:
启用插件时的错误信息:
3.3 反查出错误的 C++ 代码
应用上线后我们的拿到的错误信息是这样的 temp.wasm.0x260:
可以在加载了插件的环境下,在 temp.wasm.0x260 这个位置打断点,这时候断点会自动映射到 C++ 代码:
4. 优化编译,分离调试信息
如 3 章节所示,我们成功的实现了错误调试,但是使用 -g 编译携带调试信息的产物会导致包体变大很多,执行效率可能也会下降很多,下面介绍如何优化编译并把调试信息分离出去。
4.1 编译过程
编译命令:
| -fno-inline | 禁用内敛 |
|---|---|
| -O3 | 编译时优化 |
| -gseparate-dwarf | 把调试信息拆分为单独的 WebAssembly 文件 |
| -s SEPARATE_DWARF_URL | 指定一下调试信息的地址 C/C++ DevTools Support (DWARF) 插件会自动去相应地址拉取 |
emcc -g temp.cc -o temp.js \
-O3 -fno-inline \
-gseparate-dwarf=temp.debug.wasm \
-s SEPARATE_DWARF_URL=http://127.0.0.1:5500/temp.debug.wasm
编译产物:
4.2 使用单独拆出去的 WebAssembly 调试文件
如编译命令所示,让 http://127.0.0.1:5500/temp.debug.wasm 这个连接对应到 temp.debug.wasm 这个文件,之后就和 3 章节是一样的了。
5. 总结
通过这些步骤,我们不仅可以在浏览器中高效地调试 WebAssembly 模块,还能在保持调试能力的同时,优化编译产物的大小和性能。这为开发者提供了一种有效的方法来处理 WebAssembly 调试的复杂性,使其更易于维护和优化,不过也要考虑一下增加 DWARF 的风险点,测试性能是否有损耗。
-
参考