name: multiversx-wasm-debug description: 分析编译的 WASM 二进制文件,进行大小优化、panic 分析和使用 DWARF 符号调试。用于解决合约部署问题、优化二进制大小或调试运行时错误。
MultiversX WASM 调试
分析编译的 output.wasm 文件,进行大小优化、panic 调查和源码级调试。此技能有助于解决部署问题和运行时错误。
何时使用
- 合约部署因大小限制失败
- 调查运行时 panic/trap 错误
- 优化 WASM 二进制大小
- 理解编译合约中的内容
- 将 WASM 错误映射回 Rust 源代码
1. 二进制大小分析
使用 Twiggy
Twiggy 分析 WASM 二进制文件以识别占用空间的内容:
# 安装 twiggy
cargo install twiggy
# 空间消耗前几名
twiggy top output/my-contract.wasm
# 支配者分析(什么在二进制中保持什么)
twiggy dominators output/my-contract.wasm
# 到特定函数的路径
twiggy paths output/my-contract.wasm "function_name"
# 完整调用图
twiggy callgraph output/my-contract.wasm > graph.dot
Twiggy 输出示例
Shallow Bytes │ Shallow % │ Item
───────────────┼───────────┼─────────────────────────────────
12847 │ 18.52% │ data[0]
8291 │ 11.95% │ "function names" subsection
5738 │ 8.27% │ core::fmt::Formatter::pad
4521 │ 6.52% │ alloc::string::String::push_str
常见大小膨胀原因
| 原因 | 大小影响 | 解决方案 |
|---|---|---|
| Panic 消息 | 高 | 使用 sc_panic! 或在发布版本中剥离 |
| 格式化字符串 | 高 | 避免 format!,使用静态字符串 |
| JSON 序列化 | 非常高 | 使用二进制编码 |
| 大型静态数组 | 高 | 运行时生成或存储 off-chain |
| 未使用的依赖 | 可变 | 审计 Cargo.toml |
| 调试符号 | 高 | 在发布模式下构建 |
大小减少技术
# Cargo.toml - 为大小优化
[profile.release]
opt-level = "z" # 为大小优化
lto = true # 链接时优化
codegen-units = 1 # 更好的优化,编译更慢
panic = "abort" # 更小的 panic 处理
strip = true # 剥离符号
# 构建优化的发布版本
sc-meta all build --release
# 使用 wasm-opt 进一步优化
wasm-opt -Oz output/contract.wasm -o output/contract.opt.wasm
2. Panic 分析
理解合约陷阱
当合约陷入陷阱(panic)时,您会看到:
error: execution terminated with signal: abort
常见陷阱原因
| 症状 | 可能原因 | 调查 |
|---|---|---|
unreachable |
没有消息的 panic | 检查 unwrap(), expect() |
out of gas |
达到计算限制 | 检查循环、存储访问 |
memory access |
缓冲区溢出 | 检查数组索引 |
integer overflow |
数学操作 | 检查算术运算 |
在 WASM 中查找 Panics
# 列出 WASM 中的所有函数
wasm-objdump -x output/contract.wasm | grep "func\["
# 反汇编以查找 unreachable 指令
wasm-objdump -d output/contract.wasm | grep -B5 "unreachable"
# 计算 panic 相关代码
wasm-objdump -d output/contract.wasm | grep -c "panic"
Panic 消息剥离
默认情况下,sc_panic! 包含消息字符串。在生产中:
// 开发 - 完整消息
sc_panic!("详细错误: 无效金额 {}", amount);
// 生产 - 剥离消息
// 使用 --release 构建,wasm-opt 移除字符串
或使用错误代码:
const ERR_INVALID_AMOUNT: u32 = 1;
const ERR_UNAUTHORIZED: u32 = 2;
// 更小的二进制,描述性较低
if amount == 0 {
sc_panic!(ERR_INVALID_AMOUNT);
}
3. DWARF 调试信息
构建带调试符号
# 构建带源码映射的调试版本
sc-meta all build --wasm-symbols
# 替代方法(等效)
sc-meta all build --wasm-symbols
调试构建输出
调试构建产生:
contract.wasm- 合约字节码contract.wasm.map- 源码映射(如果可用)- 带 DWARF 部分的更大文件大小
使用调试信息
# 查看 DWARF 信息
wasm-objdump --debug output/contract.wasm
# 列出调试部分
wasm-objdump -h output/contract.wasm | grep "debug"
源码级调试
带调试符号,您可以:
- 将 WASM 指令地址映射到 Rust 源码行
- 在源码位置设置断点
- 检查变量值(在兼容的调试器中)
# 使用 wasmtime 进行调试
wasmtime run --invoke function_name -g output/contract.wasm
4. WASM 结构分析
检查合约结构
# 完整 WASM 转储
wasm-objdump -x output/contract.wasm
# 部分概述
wasm-objdump -h output/contract.wasm
# 导出函数(端点)
wasm-objdump -j Export -x output/contract.wasm
# 导入函数(VM API 调用)
wasm-objdump -j Import -x output/contract.wasm
理解 WASM 部分
| 部分 | 目的 | 审计重点 |
|---|---|---|
| Type | 函数签名 | API 表面 |
| Import | 使用的 VM API 函数 | 能力 |
| Function | 内部函数 | 代码大小 |
| Export | 公共端点 | 攻击表面 |
| Code | 实际字节码 | 逻辑 |
| Data | 静态数据 | 嵌入的秘密? |
| Name | 调试名称 | 信息泄露 |
检查导出
# 列出所有导出函数
wasm-objdump -j Export -x output/contract.wasm | grep "func"
# MultiversX 的预期导出:
# - init: 构造函数
# - upgrade: 升级处理器
# - callBack: 回调处理器
# - <endpoint_names>: 您的端点
5. Gas 性能分析
估计 Gas 成本
# 使用 sc-meta 或交互器部署到 devnet
sc-meta all deploy --proxy https://devnet-gateway.multiversx.com --chain D
# 或使用 Rust 交互器进行程序化部署
# 详见 multiversx-sc 交互器模式细节
识别 Gas 密集代码
常见 gas 密集型模式:
- 存储读取/写入
- 加密操作
- 大数据序列化
- 循环迭代
// Gas 昂贵
for item in self.large_list().iter() { // N 次存储读取
self.process(item);
}
// Gas 优化
let batch_size = 10;
for i in 0..batch_size {
let item = self.large_list().get(start_index + i);
self.process(item);
}
6. 常见调试场景
场景:合约部署失败
# 检查二进制大小
ls -la output/contract.wasm
# 部署的最大大小通常为 256KB
# 如果太大,分析并优化
twiggy top output/contract.wasm
场景:交易失败并显示 unreachable
- 检查
unwrap()调用 - 检查数组索引越界
- 检查除以零
- 构建带调试并检查 DWARF 信息
场景:Gas 超出
# 构建带调试以获得更好的错误位置
sc-meta all build --wasm-symbols
# 分析特定函数
# 添加日志以识别哪个循环/存储访问昂贵
场景:意外行为
// 添加调试日志(在生产中移除)
#[endpoint]
fn debug_function(&self, input: BigUint) {
// 记录到事件以调试
self.debug_event(&input);
// 您的逻辑
let result = self.compute(input);
self.debug_event(&result);
}
#[event("debug")]
fn debug_event(&self, value: &BigUint);
7. 工具总结
| 工具 | 目的 | 安装 |
|---|---|---|
twiggy |
大小分析 | cargo install twiggy |
wasm-objdump |
WASM 检查 | wabt 的一部分 |
wasm-opt |
大小优化 | cargo install wasm-opt 或 binaryen 的一部分 |
wasmtime |
WASM 运行时/调试 | cargo install wasmtime |
sc-meta |
MultiversX 构建工具 | cargo install multiversx-sc-meta |
8. 最佳实践
- 始终在部署前检查发布大小
- 在主网部署前在 devnet 上分析
- 使用事件进行调试 而非存储(更便宜)
- 在生产构建中剥离调试信息
- 随着合约演变监控 gas 成本
- 保留 twiggy 报告 以跟踪大小变化