在 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.wasmfib.htmlfib.js, 如果我们通过浏览器访问 fib.html, 可以在浏览器的调试控制台 (F12) 看到对应的斐波那契数的输出.

fib.html 是 emscripten 生成的演示页面, 背后会调用 fib.js 胶水层代码, 加载生成的 WEbAssembly 并执行对应 C 代码中的 main 函数. 具体原理可以查看源码并了解相关知识去理解.
fib

如果本地环境安装有 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

debug in nodejs

连接到浏览器进行调试

区别于上一小节中提到的 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

debug in chrome

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

debug with sourcemap

微信开发者工具中的调试

现在有很多的基于 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

debug in wetchat dev

注意

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, 我们能看到如下堆栈:

unalign stacktrace

参考

  • https://marketplace.visualstudio.com/items?itemName=ms-vscode.wasm-dwarf-debugging
  • VSCode调试的两种模式: launch 和 attach

本文由博客一文多发平台 OpenWrite 发布!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/14977.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

deepseek API开发简介

1、申请deepseek api key&#xff1a; https://platform.deepseek.com/api_keys创建API Key&#xff0c;并复制Key 2、安装python、pip&#xff0c;然后安装requests pip install requests3、.示例代码 import requests import json# DeepSeek API 地址 API_URL "ht…

uniapp开发微信小程序请求超时设置【亲测有效】

在Hbuilderx中 使用uniapp开发微信小程序时 封装请求方法 请求代码如下 function requestFun(app) {// get请求app.config.globalProperties._get function(path, data, success, fail, complete) {data data || {};data.token uni.getStorageSync(token) || ;uni.request…

【03】 区块链分布式网络

3-1 P2P网络 传统中心化网络由中央服务器保存全量数据。客户端之间无法直接连接&#xff0c;必须通过中央服务器作为桥梁。客户端必须和中央服务器建立连接后访问资源。客户端之间并无连通。 在P2P网络中通过将数据资源分散在网络各个节点中存储以及节点间交互连接&#xff0…

DeepSeek-R1 论文解析——人工智能领域的 RL LLM 新时代?

简介 最近几年&#xff0c;AI领域真是突飞猛进&#xff0c;尤其是大型语言模型&#xff08;LLM&#xff09;&#xff0c;它们为通用人工智能&#xff08;AGI&#xff09;的发展打下了基础。OpenAI的o1模型就是个很好的例子&#xff0c;它用了一种创新的推理时间扩展技术&#…

第七节 文件与流

基本的输入输出&#xff08;iostream&#xff09; C标准库提供了一组丰富的输入/输出功能&#xff0c;C的I/O发生在流中&#xff0c;流是字节序列。如果字节流是从设备&#xff08;键盘、磁盘驱动器、网络连接等&#xff09;流向内存&#xff0c;叫做输入操作。如果字节流是从…

算法篇——动态规划

核心思想&#xff1a; 将问题分解为重叠的子问题&#xff0c;并储存子问题的解&#xff08;使用字典、数组或哈希表&#xff09;&#xff0c;避免重复计算&#xff0c;从而提高效率。 题目特点&#xff1a;重叠子问题&#xff08;特殊地&#xff0c;是最优子结构&#xff09; …

redis高级数据结构Stream

文章目录 背景stream概述消息 ID消息内容常见操作独立消费创建消费组消费 Stream弊端Stream 消息太多怎么办?消息如果忘记 ACK 会怎样?PEL 如何避免消息丢失?分区 Partition Stream 的高可用总结 背景 为了解决list作为消息队列是无法支持消息多播问题&#xff0c;Redis5.0…

ASP.NET Core WebSocket、SignalR

目录 WebSocket SignalR SignalR的基本使用 WebSocket WebSocket基于TCP协议&#xff0c;支持二进制通信&#xff0c;双工通信。性能和并发能力更强。WebSocket独立于HTTP协议&#xff0c;不过我们一般仍然把WebSocket服务器端部署到Web服务器上&#xff0c;因为可以借助HT…

多路文件IO

一、思维导图

在CT107D单片机综合训练平台上,8个数码管分别单独依次显示0~9的值,然后所有数码管一起同时显示0~F的值,如此往复。

题目&#xff1a;在CT107D单片机综合训练平台上&#xff0c;8个数码管分别单独依次显示0~9的值&#xff0c;然后所有数码管一起同时显示0~F的值&#xff0c;如此往复。 延时函数分析LED首先实现8个数码管单独依次显示0~9的数字所有数码管一起同时显示0~F的值&#xff0c;如此往…

小红书提出新面部视频交换方法DynamicFace,可生成高质量且一致的视频面部图像。

DynamicFace是一种新颖的面部视频交换方法&#xff0c;旨在生成高质量且一致的视频面部图像。该方法结合了扩散模型的强大能力和可插拔的时间层&#xff0c;以解决传统面部交换技术面临的两个主要挑战&#xff1a;在保持源面部身份的同时&#xff0c;准确传递目标面部的运动信息…

2025.2.9机器学习笔记:PINN文献阅读

2025.2.9周报 文献阅读题目信息摘要Abstract创新点网络架构实验结论缺点以及后续展望 文献阅读 题目信息 题目&#xff1a; GPT-PINN:Generative Pre-Trained Physics-Informed Neural Networks toward non-intrusive Meta-learning of parametric PDEs期刊&#xff1a; Fini…

天津三石峰科技——汽车生产厂的设备振动检测项目案例

汽车产线有很多传动设备需要长期在线运行&#xff0c;会出现老化、疲劳、磨损等 问题&#xff0c;为了避免意外停机造成损失&#xff0c;需要加装一些健康监测设备&#xff0c;监测设备运 行状态。天津三石峰科技采用 12 通道振动信号采集卡&#xff08;下图 1&#xff09;对…

CSGHub高效管理|解锁DeepSeek R1蒸馏模型 :高效推理的新选择

在大模型的新时代&#xff0c;如何在保持高推理能力的同时降低计算成本&#xff0c;已经成为企业和开发者们关注的核心问题。 你是否也在寻找一个既强大又高效的AI模型&#xff1f; DeepSeek R1&#xff0c;作为目前领先的AI模型之一&#xff0c;不仅推出了强大的671B参数旗舰模…

来自国外的实用软件 ,已接触所有限制!

今天我给大家带来了一款超棒的全自动抠图软件&#xff0c;真的是一个来自国外的宝藏工具&#xff01;而且好消息是&#xff0c;它现在完全解除了限制&#xff0c;可以无限畅快地使用了。 Teorex PhotoScissors 抠图软件 这款软件特别贴心&#xff0c;根本不需要安装&#xff0…

win32汇编环境,结构体的使用示例一

;运行效果 ;win32汇编环境,结构体的使用示例一 ;举例说明结构体的定义&#xff0c;如何访问其中的成员&#xff0c;使用assume指令指向某个结构体&#xff0c;利用偏移得到成员值等 ;直接抄进RadAsm可编译运行。重要部分加备注。 ;下面为asm文件 ;>>>>>>>…

Ai无限免费生成高质量ppt教程(deepseek+kimi)

第一步&#xff1a;打开deepseek官网&#xff08;DeepSeek) 1.如果deepseek官网网络繁忙&#xff0c;解决方案如下&#xff1a; (1)使用easychat官网&#xff08;EasyChat&#xff09;使用deepseek模型&#xff0c;如图所示&#xff1a; &#xff08;2&#xff09;本地部署&…

C#常用集合优缺点对比

先上结论&#xff1a; 在C#中&#xff0c;链表、一维数组、字典、List<T>和ArrayList是常见的数据集合类型&#xff0c;它们各有优缺点&#xff0c;适用于不同的场景。以下是它们的比较&#xff1a; 1. 一维数组 (T[]) 优点&#xff1a; 性能高&#xff1a;数组在内存中…

大数据项目2a:基于spark的电影推荐和分析系统设计与实现

1、项目目的 本项目的目的是设计并实现一个基于Spark的电影推荐系统&#xff0c;以应对大数据环境下电影推荐服务的挑战。通过整合电影、评分和用户数据集&#xff0c;并利用SparkSql框架进行高效处理&#xff0c;系统能够为用户提供个性化的电影推荐。项目采用多种先进技术&…

CANoe工具使用技巧 --- 如何使用 “on ethernetPacket “事件处理程序

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…