在 Visual Studio Code 与微信开发者工具中调试使用 emscripten 基于 C 生成的 WASM 代码
最近在尝试将一些 C/C++、Lua 项目挪到 Web 上跑, 接触到了 emscripten. 这里会介绍下在 Visual Studio Code 与微信开发者工具中调试使用 emscripten 基于 C 生成的 WASM 代码 (WebAssembly) 的一些方法.
Emscripten 与 WebAssebmly
WebAssembly 是一种新的编码方式, 可以在现代的 Web 浏览器中运行——它是一种低级的类汇编语言, 具有紧凑的二进制格式, 可以接近原生的性能运行, 并为诸如 C/C++、C# 和 Rust 等语言提供编译目标, 以便它们可以在 Web 上运行. 它也被设计为可以与 JavaScript 共存, 允许两者一起工作. –来自 MDN
Emscripten 基于大名鼎鼎的 LLVM 提供了 C/C++ 生态下的编译工具链, 可以很方便的将 C/C++ 项目编译到 WASM, 然后放到 JS 环境 (Web、微信小程序/游戏、nodejs 等) 执行.
有很多著名的 C/C++ 生态下的工具通过它移植到了现代浏览器 (chrome、firefox 等) 中执行.
Emscripten 官方提供了 emsdk 可以很方便的我们管理多个版本的编译工具链.
vscode 中调试 WebAssembly 的基本方法
现在在 vscode 中调试 WebAssembly 还是很方便的, 巨硬 (Microsoft) 在 2023 年就开发好了一个 vscode 插件去做支持, 见: WebAssembly DWARF Debugging.
请确保你已经安装并启用了该扩展插件. 同时安装好了 Emscripten 相关编译工具链, 这里我们使用的版本如下:
❯ emcc -v
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.74 (1092ec30a3fb1d46b1782ff1b4db5094d3d06ae5)
clang version 20.0.0git (https:/github.com/llvm/llvm-project 322eb1a92e6d4266184060346616fa0dbe39e731)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /home/yeshan333/workspace/github/emsdk/upstream/bin
这里我们已简单的单个 fib.c
文件的调试为例, fib.c
的内容如下:
#include <stdio.h>// 递归方法
int fibonacci_recursive(int n) {if (n <= 1)return n;return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2);
}// 迭代方法
int fibonacci_iterative(int n) {if (n <= 1)return n;int a = 0, b = 1, c;for(int i = 2; i <= n; ++i){c = a + b;a = b;b = c;}return b;
}int main() {int number = 10;printf("Recursive Fibonacci of %d is %d\n", number, fibonacci_recursive(number));printf("Iterative Fibonacci of %d is %d\n", number, fibonacci_iterative(number));return 0;
}
我们先通过 emcc 将 C 代码编译出 WASM, 如下:
emcc -v fib.c -o fib.html
上述命令执行完成后, 会生成三个文件: fib.wasm
、fib.html
、fib.js
, 如果我们通过浏览器访问 fib.html
, 可以在浏览器的调试控制台 (F12) 看到对应的斐波那契数的输出.
fib.html
是 emscripten 生成的演示页面, 背后会调用fib.js
胶水层代码, 加载生成的 WEbAssembly 并执行对应 C 代码中的 main 函数. 具体原理可以查看源码并了解相关知识去理解.
如果本地环境安装有 Node.js. 那么我们也可以通过 node 执行胶水层代码 fib.js
, 结果如下:
❯ node fib.js
Recursive Fibonacci of 10 is 55
Iterative Fibonacci of 10 is 55
接下来我们将演示通过 vscode 的 debugger 调试器在 C 文件和 JS 文件中打断点调试生成的 WASM & 胶水层 JS 代码, 实现单步调试.
nodejs 中调试演示
这里我们使用如下 launch.json
配置去调试 fib.js
:
{// Use IntelliSense to learn about possible attributes.// Hover to view descriptions of existing attributes.// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387"version": "0.2.0","configurations": [{"type": "node","request": "launch","name": "WASM Debug","skipFiles": ["<node_internals>/**"],"program": "${workspaceFolder}/fib.js"}]
}
vscode 下的 C 代码断点调试需要依赖 DWARF 调试信息 (注: 如果没有调试信息, 我们只能调试生成的 js 代码, 而不能直接在 C 中打断点), 我们使用 emcc 的 -g 编译参数, 让生成的 wasm 带上调试信息. 我们先通过如下命令编译 C 文件:
emcc -g -v fib.c -o fib.html
在 run()
函数处打一个断点, 然后在 fib.c
中 main 函数的两个 printf 中各打一个断点, 使用 F5 启动调试器即可开始调试. 演示 (GIF 加载可能稍久):
https://img-home.csdnimg.cn/images/20230724024159.png?be=1&origin_url=https://pub-a8b9801c20ad491b964fc0e49c81cdb7.r2.dev/debug_in_nodejs.gif
连接到浏览器进行调试
区别于上一小节中提到的 Node.js 环境下的调试方法, vscode 会负责启动 node 执行 fib.js
. 这里介绍的 vscode 结合浏览器的调试方法, WASM 和 JS 代码将由浏览器负责执行, 我们使用 vscode 的 task 让 vscode 帮我们启动浏览器.
我们使用的 vscode launch.json
调试配置如下:
{// Use IntelliSense to learn about possible attributes.// Hover to view descriptions of existing attributes.// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387"version": "0.2.0","configurations": [{"name": "Launch Chrome (fib.html)","type": "chrome","request": "launch","url": "http://127.0.0.1:3000/fib.html","preLaunchTask": "StartHTTPServer",},]
}
"preLaunchTask": "StartHTTPServer"
说明会在调试开始前, 先执行一个名为 StartHTTPServer
的 vscode task. task 的配置同样可以放置于 .vscode 目录的 tasks.json
中
tasks.json
配置如下, 这里会使用到微软提供的插件 Live Preview, 它会帮我们起一个 HTTP Server 去托管 HTML 文件 (fib.html
):
{"version": "2.0.0","tasks": [{"label": "StartHTTPServer","type": "process","command": "${input:StartHTTPServer}"}],"inputs": [{"id": "StartHTTPServer","type": "command","command": "livePreview.runServerLoggingTask"}]
}
我们延用之前打断点的位置: 在 run()
函数处打一个断点, 然后在 fib.c
中 main 函数的两个 printf 中各打一个断点. 启动配置好的调试配置.
注: 编译命令仍然是:
emcc -g -v fib.c -o fib.html
演示 (GIF 加载可能稍久):
https://img-home.csdnimg.cn/images/20230724024159.png?be=1&origin_url=https://pub-a8b9801c20ad491b964fc0e49c81cdb7.r2.dev/debug_in_chrome.gif
F5 启动调试后, 会有一个 chrome 浏览器调试窗口被拉起, 在 vscode 编译器可以观察到, 断点能正常执行. 于此同时, 我们也可以在浏览器开发者工具的 Debugger 中观察到断点的执行.
如果你细心观察可以看到, 调试器执行到 C 文件时, 区别于 vscode 编辑器会跳转到对应的 C 代码行, chrome 浏览器开发者工具跳转的却是 wasm 文本格式代码, 这个问题我们可以在编译的时候生成 wasm 文件的 source-map 去解决, 编译命令如下:
# 确保生成的 source-map 文件 fib.wasm.map 能在 --source-map-base 指定的 HTTP Server 中找到
emcc -g -v fib.c -o fib.html -gsource-map --source-map-base=http://localhost:3000/
此后, 重新启动调试器, 我们也可以在浏览器的开发者工具中观察到随着调试的执行, 可以正确跳到被打断点的对应 C 代码行, 而不是对应的 wasm 文本表示格式中的代码行, 浏览器会自动读取 source-map 文件找到对应代码文件的位置.
演示 (GIF 加载可能稍久):
https://img-home.csdnimg.cn/images/20230724024159.png?be=1&origin_url=https://pub-a8b9801c20ad491b964fc0e49c81cdb7.r2.dev/debug_in_chrome_with_sourcemap.gif
微信开发者工具中的调试
现在有很多的基于 C/C++ 写的游戏移植到了微信平台上, 基于上文浏览器的调试方法, 我们可以在微信开发者工具中达到类似的效果, 在 C 中打断点, 进行小程序/小游戏项目的调试. 我创建了一个小型项目, 可以将其导入 微信的开发者工具 进行尝试. 快速尝试下:
git clone https://github.com/yeshan333/emcc_playground.gitcd emcc_playground/debug-blogpost# 编译出 wasm
emcc -g -v unalign.cc -o unalign.html -gsource-map --source-map-base=http://localhost:3000/ -sSAFE_HEAP=1# 启动一个 http server 将 debug-blogpost 目录暴露出去, 使用的地址端口需要与 --source-map-base 中指定的一致, 方便微信开发者工具读取
npx serve .
微信开发者工具打开 debug-blogpost 目录, 打开调试器, 在 Sources -> Page --> localhost:3000 出能看到对应的 C 文件, 并且可以使用 debugger 打断点:
演示(Windows + 微信开发者工具预发布版 RC Build (1.06.2412031) + Wechat Lib:3.7.2, 2024.12.23 10:35:40): (GIF 加载可能稍久)
https://img-home.csdnimg.cn/images/20230724024159.png?be=1&origin_url=https://pub-a8b9801c20ad491b964fc0e49c81cdb7.r2.dev/debug_in_wechatdev.gif
注意
Node.js 环境目前尚未支持读取 WebAssembly 的 source-map, 编译出的 wasm 即便带了 DWARF 调试信息, 堆栈只能看到符号, 看不到 C 符号对应的源文件, 例如有这样
一个 C++ 文件 unalign.cc
:
// https://github.com/3dgen/cppwasm-book/blob/master/wasm-in-action-book-examples/ch5/02/unaligned.cc
#ifndef EM_PORT_API
# if defined(__EMSCRIPTEN__)
# include <emscripten.h>
# if defined(__cplusplus)
# define EM_PORT_API(rettype) extern "C" rettype EMSCRIPTEN_KEEPALIVE
# else
# define EM_PORT_API(rettype) rettype EMSCRIPTEN_KEEPALIVE
# endif
# else
# if defined(__cplusplus)
# define EM_PORT_API(rettype) extern "C" rettype
# else
# define EM_PORT_API(rettype) rettype
# endif
# endif
#endif#include <stdio.h>
#include <malloc.h>
#include <memory.h>
#include <stdint.h>struct ST {uint8_t c[4];float f;
};void throw_unalign_err() {printf("Hello, World!\n");char *buf = (char*)malloc(100);ST *pst = (ST*)(buf + 2);pst->c[0] = pst->c[1] = pst->c[2] = pst->c[3] = 123;pst->f = 3.14f;printf("c[0]:%d,c[1]:%d,c[2]:%d,c[3]:%d,f:%f\n",pst->c[0], pst->c[1], pst->c[2], pst->c[3], pst->f);free(buf);
}int main() {throw_unalign_err();return 0;
}
使用 emcc 编译它, 命令如下:
emcc -g -v unalign.cc -o unalign.html -gsource-map --source-map-base=http://localhost:3000/ -sSAFE_HEAP=1
然后使用 Node.js 执行编译出来的胶水文件, 会得到类似下面的结果, 有一个内存对齐错误, 堆栈上可以看到问题出现在 throw_unalign_err
. 但我们看不到符号对应的源文件.
❯ node --enable-source-maps unalign.js
Hello, World!
Aborted(alignment fault)
/home/yeshan333/workspace/playground/emcc_playground/debug-blogpost/unalign.js:613/** @suppress {checkTypes} */ var e = new WebAssembly.RuntimeError(what);^RuntimeError: Aborted(alignment fault)at abort (/home/yeshan333/workspace/playground/emcc_playground/debug-blogpost/unalign.js:613:41)at alignfault (/home/yeshan333/workspace/playground/emcc_playground/debug-blogpost/unalign.js:335:3)at unalign.wasm (wasm://wasm/unalign.wasm-00091ee6:wasm-function[107]:0x56e4)at unalign.wasm.throw_unalign_err() (wasm://wasm/unalign.wasm-00091ee6:wasm-function[6]:0x42f)at unalign.wasm.__original_main (wasm://wasm/unalign.wasm-00091ee6:wasm-function[7]:0x4f2)at unalign.wasm.main (wasm://wasm/unalign.wasm-00091ee6:wasm-function[8]:0x50f)at /home/yeshan333/workspace/playground/emcc_playground/debug-blogpost/unalign.js:682:12at callMain (/home/yeshan333/workspace/playground/emcc_playground/debug-blogpost/unalign.js:1383:15)at doRun (/home/yeshan333/workspace/playground/emcc_playground/debug-blogpost/unalign.js:1421:23)at run (/home/yeshan333/workspace/playground/emcc_playground/debug-blogpost/unalign.js:1431:5)
但是如果是在 Web 浏览器环境(chrome)中, 我们能看到符号所在的 C 源文件, 打开 unalign.html
, 我们能看到如下堆栈:
参考
- https://marketplace.visualstudio.com/items?itemName=ms-vscode.wasm-dwarf-debugging
- VSCode调试的两种模式: launch 和 attach
本文由博客一文多发平台 OpenWrite 发布!
相关文章:

在 Visual Studio Code 与微信开发者工具中调试使用 emscripten 基于 C 生成的 WASM 代码
最近在尝试将一些 C/C、Lua 项目挪到 Web 上跑, 接触到了 emscripten. 这里会介绍下在 Visual Studio Code 与微信开发者工具中调试使用 emscripten 基于 C 生成的 WASM 代码 (WebAssembly) 的一些方法. Emscripten 与 WebAssebmly WebAssembly 是一种新的编码方式, 可以在现代…...
elasticsearch实战应用从入门到高效使用java集成es快速上手
Elasticsearch 因其出色的性能、可扩展性和易用性,成为了处理大规模数据和构建搜索引擎的首选工具。本文将通过一个实际案例,详细讲解如何在 Spring Boot 项目中集成 Elasticsearch,进行数据索引、搜索、聚合分析等操作。 一、Elasticsearch 简介 Elasticsearch 是一个基于…...
【OneAPI】通过网页预渲染让搜索引擎收录网页
API简介 网页预渲染,适用于动态网页以及单页面的SEO,支持网页缓存。 您无须更改代码即可让搜索引擎收录您的网页。只要将需要预渲染的页面转发的本接口即可。 如果您使用Nginx作为网页服务器,推荐使用以下配置: #您的网站locat…...
【网络安全.渗透测试】Cobalt strike(CS)工具使用说明
目录 前言 一、工具显著优势 二、安装 Java 运行环境 三、实验环境搭建要点 四、核心操作流程详解 (一)环境准备与连接步骤 (二)主机上线与深度渗透流程 五、其他实用功能应用指南 (一)office 宏 payload 应用 (二)Https Payload 应用 (三)信息收集策略 …...

港中文腾讯提出可穿戴3D资产生成方法BAG,可自动生成服装和配饰等3D资产如,并适应特定的人体模型。
今天给大家介绍一种名为BAG(Body-Aligned 3D Wearable Asset Generation)的新方法,可以自动生成可穿戴的3D资产,如服装和配饰,以适应特定的人体模型。BAG方法通过构建一个多视图图像扩散模型,生成与人体对齐…...
【C语言标准库函数】标准输入输出函数详解[4]:二进制文件读写函数
目录 一、fread() 函数 1.1. 函数简介 1.2. fread 使用场景 1.3. 注意事项 1.4. 示例 二、fwrite() 函数 2.1. 函数简介 2.2. fwrite 使用场景 2.3. 注意事项 2.4. 示例 三、总结 在 C 语言中,二进制文件读写函数允许以二进制形式对文件进行读写操作&…...

Python:凯撒密码
题目内容: 凯撒密码是古罗马恺撒大帝用来对军事情报进行加密的算法,它采用了替换方法对信息中的每一个英文字符循环替换为字母表序列该字符后面第三个字符,对应关系如下: 原文:A B C D E F G H I J K L M N O P Q R …...

C++引用深度详解
C引用深度详解 前言1. 引用的本质与核心特性1.1 引用概念1.2 核心特性 2. 常引用与权限控制2.1 权限传递规则2.2 常量引用2.3 临时变量保护1. 样例2. 样例3. 测试 三、引用使用场景分析3.1 函数参数传递输出型参数避免多级指针高效传参 3.2 做函数返回值正确使用危险案例 4. 性…...

C++ Primer 语句作用域
欢迎阅读我的 【CPrimer】专栏 专栏简介:本专栏主要面向C初学者,解释C的一些基本概念和基础语言特性,涉及C标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级…...
github - 使用
注册账户以及创建仓库 要想使用github第一步当然是注册github账号了, github官网地址:https://github.com/。 之后就可以创建仓库了(免费用户只能建公共仓库),Create a New Repository,填好名称后Create,之后会出现一些仓库的配置信息,这也是一个git的简单教程。 Git…...

内网ip网段记录
1.介绍 常见的内网IP段有: A类: 10.0.0.0/8 大型企业内部网络(如 AWS、阿里云) 10.0.0.0 - 10.255.255.255 B类:172.16.0.0/12 中型企业、学校 172.16.0.0 - 172.31.255.255 C类:192.168.0.0/16 家庭…...

k8s部署logstash
1. 编写logstash.yaml配置文件 --- apiVersion: v1 kind: Service metadata:name: logstash spec:type: ClusterIPclusterIP: Noneports:- name: logstash-tcpport: 5000targetPort: 5000- name: logstash-beatsport: 5044targetPort: 5044- name: logstash-apiport: 9600targ…...

EF Core中实现值对象
目录 值对象优点 值对象的需求 值类型的实现 值类型GEO的实现 值类型MultilingualString的实现 案例:构建表达式树,简化值对象的比较 值对象优点 把有紧密关系的属性打包为一个类型把领域知识放到类的定义中 class shangjia {long id;string nam…...

【分布式理论9】分布式协同:分布式系统进程互斥与互斥算法
文章目录 一、互斥问题及分布式系统的特性二、分布式互斥算法1. 集中互斥算法调用流程优缺点 2. 基于许可的互斥算法(Lamport 算法)调用流程优缺点 3. 令牌环互斥算法调用流程优缺点 三、三种算法对比 在分布式系统中,多个应用服务可能会同时…...

木材表面缺陷检测数据集,支持YOLO+COCO JSON+PASICAL VOC XML+DARKNET格式标注信息,平均正确识别率95.0%
数据集说明 木材表面缺陷检测数据集是用于训练和验证人工智能算法,以帮助自动识别和检测木材表面的缺陷,如裂纹、疤痕、孔洞等。这对于木材行业非常重要,可以提高生产过程的效率和质量控制水平。 本文提供的木材表面缺陷检测数据集࿰…...
Leetcodehot 力扣热题100 二叉搜索树中第 K 小的元素
class Solution { public:int res; // 用于存储第 k 小的元素int kthSmallest(TreeNode* root, int k) {inorder(root, k); // 进行中序遍历并找到第 k 小的元素return res; // 返回结果}private:// 中序遍历:遍历树的左子树、根节点和右子树void inorder(TreeNod…...
Awtk 如何添加开机画面
场景 我们知道在工程中,Ui是一个线程,并且需要一直存在,当我们使用的开机画面在这个线程开启就直接展示的时候,因为awtk的界面是window_open入栈的,即首次打开的窗口会记录在top,往后的窗口会依次往后存放&…...

关于多语言商城系统的开发流程
建设多语言商城系统是现在很多传统外贸企业的选择,外贸企业通过多语言电商系统开展海外业务,那么多语言商城系统的开发流程是怎么样的呢?接下来就跟着小来一起来看看吧。 1、页面UI设计 多语言商城系统的原型图经过反复推敲修正后࿰…...

IDEA中常见问题汇总
🍓 简介:java系列技术分享(👉持续更新中…🔥) 🍓 初衷:一起学习、一起进步、坚持不懈 🍓 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正🙏 🍓 希望这篇文章对你有所帮助,欢…...

计算机视觉-拟合
一、拟合 拟合的作用主要是给物体有一个更好的描述 根据任务选择对应的方法(最小二乘,全最小二乘,鲁棒最小二乘,RANSAC) 边缘提取只能告诉边,但是给不出来数学描述(应该告诉这个点线是谁的&a…...

超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...

linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...

华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...

通过 Ansible 在 Windows 2022 上安装 IIS Web 服务器
拓扑结构 这是一个用于通过 Ansible 部署 IIS Web 服务器的实验室拓扑。 前提条件: 在被管理的节点上安装WinRm 准备一张自签名的证书 开放防火墙入站tcp 5985 5986端口 准备自签名证书 PS C:\Users\azureuser> $cert New-SelfSignedCertificate -DnsName &…...
Docker拉取MySQL后数据库连接失败的解决方案
在使用Docker部署MySQL时,拉取并启动容器后,有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致,包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因,并提供解决方案。 一、确认MySQL容器的运行状态 …...

论文阅读:Matting by Generation
今天介绍一篇关于 matting 抠图的文章,抠图也算是计算机视觉里面非常经典的一个任务了。从早期的经典算法到如今的深度学习算法,已经有很多的工作和这个任务相关。这两年 diffusion 模型很火,大家又开始用 diffusion 模型做各种 CV 任务了&am…...

图解JavaScript原型:原型链及其分析 | JavaScript图解
忽略该图的细节(如内存地址值没有用二进制) 以下是对该图进一步的理解和总结 1. JS 对象概念的辨析 对象是什么:保存在堆中一块区域,同时在栈中有一块区域保存其在堆中的地址(也就是我们通常说的该变量指向谁&…...

java高级——高阶函数、如何定义一个函数式接口类似stream流的filter
java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用(Math::max) 2 函数接口…...
C/Python/Go示例 | Socket Programing与RPC
Socket Programming介绍 Computer networking这个领域围绕着两台电脑或者同一台电脑内的不同进程之间的数据传输和信息交流,会涉及到许多有意思的话题,诸如怎么确保对方能收到信息,怎么应对数据丢失、被污染或者顺序混乱,怎么提高…...