优先选wazero(纯go、零cgo、启动快),若需wasi系统调用则用wasmer(支持完整wasi但需cgo);导出函数名须严格匹配且rust侧加#[no_mangle],每个插件必须创建独立实例避免内存冲突,资源限制须显式配置防失控。

Wasm 插件加载失败:wasmer 与 wazero 怎么选?
Go 原生不支持 Wasm 执行,必须依赖运行时。当前主流是 wasmer 和 wazero,但行为差异大:wasmer 支持 WASI(如文件读写),但需 CGO 且跨平台编译麻烦;wazero 纯 Go 实现、零 CGO、启动快,但默认禁用 WASI——插件若调用 args_get 或 fd_write 会直接 panic。
- 若插件只做纯计算(如 JSON 转换、规则匹配),优先用
wazero,避免 CGO 依赖和构建链路断裂 - 若插件需访问 host 文件或环境变量,用
wasmer,但得在构建时加CGO_ENABLED=1,且 Windows 上需预装 MinGW -
wazero可手动启用 WASI:runtime.NewHostModuleBuilder("wasi_snapshot_preview1").Build(),但仅暴露有限 syscall,别指望它能开 socket
插件函数导出名不匹配:为什么 export "run" 总报 function not found?
Go 加载 Wasm 时按名字查找导出函数,但名字必须完全一致——大小写、下划线、空格都不能错。常见坑是 Rust 编译的 Wasm 默认导出名带前缀(如 __wasm_call_ctors)或被 LTO 优化掉入口函数。
- Rust 侧必须显式标记:
#[no_mangle] pub extern "C" fn run(...) { ... },并关闭 LTO:profile.release.lto = false - 确认导出列表:
wabt-wat2wasm --decode plugin.wasm | grep -A5 "export ",看是否真有"run"这个字符串 - Go 侧调用时用
mod.ExportedFunction("run"),不是"Run"或"RUN"——Wasm 符号表区分大小写
插件间内存隔离失效:为什么两个插件读写了同一块 memory?
Wasm 实例共享模块的 memory 段,若多个实例共用一个 compiledModule,它们的线性内存地址空间实际是同一块 Go 字节数组——一个插件越界写入可能破坏另一个插件的栈或数据。
- 每个插件必须调用
runtime.InstantiateModule(compiled, config)创建独立实例,不能复用Instance - 不要把
compiledModule存全局变量后反复Instantiate——wazero的CompiledModule是线程安全的,但实例不是 - 若插件需传入输入数据,用
instance.ExportedFunction("run").Call(ctx, uint64(inputPtr), uint64(inputLen)),让插件自己从 memory 读,而非直接传 Go slice
调试 panic: runtime error: invalid memory address 时该看哪?
这不是 Go panic,是 Wasm trap,通常因插件访问了未分配的 memory 地址(比如指针为 0 却去 dereference),或 Go 侧传入的偏移超出 memory size。
- 用
wabt-wasm2wat --generate-names plugin.wasm > plugin.wat查看原始指令,重点找load/store操作的 immediate 值 - 在 Go 中启用 trace:
wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfig().WithCoreFeatures(api.CoreFeaturesV2).WithWasmCore2()),配合log.Logger捕获 trap 位置 - 插件初始化阶段就调用
instance.ExportedFunction("__post_instantiate")(Rust 的 ctor)可能触发早期 trap,把它放在Instantiate后单独 try-catch
wazero.Config.WithMemoryLimitPages(65536),漏设就可能被恶意插件拖垮整个服务。











