moonbit-wasm-inspect

A MoonBit-native WebAssembly binary inspection and analysis library.

wasm
webassembly
binary
inspection
analysis
moon add clbbbb/moonbit-wasm-inspect@0.1.0
Download zip
Author
Version
0.1.0
License
Apache-2.0
Last updated
8 hours ago
Downloads
1

Dependencies

README

#moonbit-wasm-inspect

MoonBit 原生的 WebAssembly 二进制检查与分析库,并提供可直接运行的命令行工具。

它面向需要读取、审计或比较 .wasm 文件结构的工具作者:输入 WASM 二进制字节,得到 section、导入导出、函数元数据、数据/元素段、名称元数据、特性需求与校验结果。它不是 WebAssembly 运行时、编译器、反编译器,也不是其他语言 WASM 库的薄封装。

#适用场景

  • CI 中检查交付物是否为结构合法的 WASM v1 模块;
  • 分析插件、合约或边缘函数的导入导出和所需 proposal;
  • 比较两个构建产物的 section 结构;
  • 为 MoonBit 编辑器工具、审计器和教学工具提供轻量二进制读取能力。

#安装与构建

先按 MoonBit 安装说明 安装工具链,然后:

git clone https://github.com/clbbbb/moonbit-wasm-inspect.git cd moonbit-wasm-inspect moon check --deny-warn moon test --deny-warn

作为依赖使用时,在你的 moon.pkg 中导入:

///|
import {
"clbbbb/moonbit-wasm-inspect" @inspect,
}

发布到 mooncakes.io 后可通过 moon add clbbbb/moonbit-wasm-inspect 安装。

#CLI 使用

命令以源码方式运行,不需要预先安装可执行文件:

moon run cmd/main inspect path/to/module.wasm moon run cmd/main inspect --json path/to/module.wasm moon run cmd/main validate path/to/module.wasm moon run cmd/main validate --json path/to/module.wasm moon run cmd/main features path/to/module.wasm moon run cmd/main diff left.wasm right.wasm

inspect 输出结构或已解码元数据;validate 发现语义问题时返回退出码 1;路径、格式或参数错误返回 2。四个命令均可加 --json 获取机器可读输出。

#可运行示例

示例不依赖外部 fixture,在内存中构造一个真实的 WASM v1 模块并输出检查报告:

moon run examples/basic

最小 API 调用如下:

match @inspect.parse_module(bytes) {
Err(error) => println(error.message())
Ok(binary) => match @inspect.decode_module(binary) {
Err(error) => println(error.message())
Ok(decoded) => println(@inspect.render_decoded(decoded))
}
}

#已实现功能

  • 有边界检查的字节游标、u32/i32 LEB128、magic/version、section 大小与顺序验证;
  • Type、Import、Function、Table、Memory、Global、Export、Start、Code、Data、DataCount、Tag 与基础 Element section 解码;
  • 标准 name custom section 的模块名和函数名映射;
  • 数据/元素段、函数/标签类型、导出、start、memory/table/function 索引空间的交叉校验;
  • 指令流摘要:控制块、分支、直接调用边、局部变量、常量、内存访问,以及常见 bulk-memory/SIMD immediate;
  • 调用图摘要、文本/JSON 结构报告、校验报告、结构 diff 与保守特性识别。

#功能边界

本项目当前不执行模块、不生成或优化 WASM,也不实现完整的验证器或反汇编器。Element 的 expression-based 形式、非 ASCII 名称、完整 SIMD 指令语义、异常处理指令流、GC/component model 与全部 proposal 仍不支持;遇到无法安全解码的格式会返回明确错误。特性报告是保守的“确定需要”,不承诺未报告的 proposal 一定未出现。

设计取舍见 docs/DESIGN.md,版本变化见 CHANGELOG.md

#来源与许可证

项目实现为原创 MoonBit 代码;二进制格式依据 WebAssembly Core Specification 编写,没有移植或复制其他 WASM 解析器的实现。CLI 文件读取使用 moonbitlang/x/fs 0.5.1(Apache-2.0),作为正常依赖而非复制代码。测试 fixture 是项目内手工构造的最小 WASM 二进制。

本项目采用 Apache-2.0 许可证。

#
CodeBody

pub struct CodeBody {
locals : Array[ValueType]
instruction_span : Span
byte_length : Int
}

#
Cursor

pub struct Cursor {
bytes : Array[Int]
position : Int
limit : Int
}

#
Cursor::is_finished

fn Cursor::is_finished(self : Cursor) -> Bool

#
Cursor::new

fn Cursor::new(bytes : Array[Int]) -> Cursor

#
Cursor::peek_u8

fn Cursor::peek_u8(self : Cursor) -> Result[Int, DecodeError]

#
Cursor::read_name

fn Cursor::read_name(self : Cursor) -> Result[String, DecodeError]

#
Cursor::read_slice

fn Cursor::read_slice(self : Cursor, count : Int) -> Result[Array[Int], DecodeError]

#
Cursor::read_u8

fn Cursor::read_u8(self : Cursor) -> Result[Int, DecodeError]

relying on a filesystem or a host-specific byte type.

#
Cursor::read_var_i32

fn Cursor::read_var_i32(self : Cursor) -> Result[Int, DecodeError]

#
Cursor::read_var_u32

fn Cursor::read_var_u32(self : Cursor) -> Result[Int, DecodeError]

#
Cursor::remaining

fn Cursor::remaining(self : Cursor) -> Int

#
Cursor::skip

fn Cursor::skip(self : Cursor, count : Int) -> Result[Unit, DecodeError]

#
Cursor::span

fn Cursor::span(self : Cursor) -> Span

#
Cursor::subcursor

fn Cursor::subcursor(self : Cursor, count : Int) -> Result[Cursor, DecodeError]

#
DataMode

pub enum DataMode {
ActiveData(Int, Span)
PassiveData
}

suitable for large modules and for tools that need to hash only one blob.

#
DataSegment

pub struct DataSegment {
mode : DataMode
bytes : Span
}

#
DataSegment::byte_length

fn DataSegment::byte_length(self : DataSegment) -> Int

#
DataSegment::description

fn DataSegment::description(self : DataSegment) -> String

#
DecodeError

pub enum DecodeError {
UnexpectedEof(Int)
InvalidLeb128(Int, String)
InvalidHeader(String)
InvalidSection(Int, Int, String)
UnsupportedFeature(String, Int)
InvalidUtf8(Int)
ValidationError(Int, String)
}

Core public data types used by the binary reader and module inspector.

#
DecodeError::message

fn DecodeError::message(self : DecodeError) -> String

#
DecodedModule

pub struct DecodedModule {
binary : Module
types : Array[FunctionType]
imports : Array[Import]
function_types : Array[Int]
tables : Array[TableInfo]
memories : Array[MemoryInfo]
globals : Array[GlobalInfo]
exports : Array[Export]
start_function : Int?
code_bodies : Array[CodeBody]
element_segments : Array[ElementSegment]
data_segments : Array[DataSegment]
data_count : Int?
tags : Array[TagInfo]
warnings : Array[String]
}

#
DecodedModule::binary

fn DecodedModule::binary(self : DecodedModule) -> Module

#
DecodedModule::defined_function_count

fn DecodedModule::defined_function_count(self : DecodedModule) -> Int

#
DecodedModule::defined_tag_count

fn DecodedModule::defined_tag_count(self : DecodedModule) -> Int

#
DecodedModule::exports

fn DecodedModule::exports(self : DecodedModule) -> Array[Export]

#
DecodedModule::imported_function_count

fn DecodedModule::imported_function_count(self : DecodedModule) -> Int

#
DecodedModule::imports

fn DecodedModule::imports(self : DecodedModule) -> Array[Import]

#
DecodedModule::types

#
DecodedModule::warnings

fn DecodedModule::warnings(self : DecodedModule) -> Array[String]

#
ElementMode

pub enum ElementMode {
ActiveElement(Int, Span)
PassiveElement
DeclarativeElement
}

core binary format and reports expression-based reference forms explicitly.

#
ElementSegment

pub struct ElementSegment {
mode : ElementMode
functions : Array[Int]
}

#
ElementSegment::description

fn ElementSegment::description(self : ElementSegment) -> String

#
Export

pub struct Export {
name : String
kind : ExternalKind
index : Int
}

#
ExternalKind

pub enum ExternalKind {
FunctionExternal
TableExternal
MemoryExternal
GlobalExternal
TagExternal
} derive(Eq)

#
ExternalKind::label

fn ExternalKind::label(self : ExternalKind) -> String

#
FunctionCallSummary

pub struct FunctionCallSummary {
function_index : Int
callees : Array[Int]
}

#
FunctionName

pub struct FunctionName {
index : Int
name : String
}

preserves unknown subsections so new producers do not break inspection.

#
FunctionType

pub struct FunctionType {
params : Array[ValueType]
results : Array[ValueType]
}

#
FunctionType::params

fn FunctionType::params(self : FunctionType) -> Array[ValueType]

#
FunctionType::results

fn FunctionType::results(self : FunctionType) -> Array[ValueType]

#
FunctionType::signature

fn FunctionType::signature(self : FunctionType) -> String

#
GlobalInfo

pub struct GlobalInfo {
value_type : ValueType
mutable : Bool
init_span : Span
}

#
Import

pub struct Import {
module_name : String
name : String
kind : ExternalKind
type_index : Int?
}

#
InstructionStats

pub struct InstructionStats {
instruction_count : Int
control_depth : Int
control_block_count : Int
branch_count : Int
call_count : Int
called_functions : Array[Int]
memory_access_count : Int
local_access_count : Int
constant_count : Int
bulk_memory_instruction_count : Int
simd_instruction_count : Int
}

returns compact call-graph and control-flow facts without executing code.

#
InstructionStats::summary

fn InstructionStats::summary(self : InstructionStats) -> String

#
IssueLevel

pub enum IssueLevel {
ErrorIssue
WarningIssue
} derive(Eq)

index spaces and returns all findings instead of stopping at the first one.

#
IssueLevel::label

fn IssueLevel::label(self : IssueLevel) -> String

#
Limits

pub struct Limits {
minimum : Int
maximum : Int?
shared : Bool
memory64 : Bool
}

#
Limits::description

fn Limits::description(self : Limits) -> String

#
MemoryInfo

pub struct MemoryInfo {
limits : Limits
}

#
Module

pub struct Module {
bytes : Array[Int]
sections : Array[Section]
version : Int
}

#
Module::byte_length

fn Module::byte_length(self : Module) -> Int

#
Module::custom_sections

fn Module::custom_sections(self : Module, name : String) -> Array[Section]

#
Module::find_section

fn Module::find_section(self : Module, kind : SectionKind) -> Section?

#
Module::has_section

fn Module::has_section(self : Module, kind : SectionKind) -> Bool

#
Module::section_bytes

fn Module::section_bytes(self : Module, section : Section) -> Array[Int]

#
Module::section_count

fn Module::section_count(self : Module) -> Int

#
Module::section_labels

fn Module::section_labels(self : Module) -> Array[String]

#
Module::sections

fn Module::sections(self : Module) -> Array[Section]

#
Module::stats

fn Module::stats(self : Module) -> ModuleStats

#
Module::version

fn Module::version(self : Module) -> Int

#
ModuleStats

pub struct ModuleStats {
byte_length : Int
section_count : Int
custom_section_count : Int
payload_bytes : Int
}

#
ModuleStats::description

fn ModuleStats::description(self : ModuleStats) -> String

#
NameSection

pub struct NameSection {
module_name : String?
function_names : Array[FunctionName]
unknown_subsection_ids : Array[Int]
}

#
NameSection::function_name

fn NameSection::function_name(self : NameSection, index : Int) -> String?

#
NameSection::render

fn NameSection::render(self : NameSection) -> String

#
Section

pub struct Section {
id : Int
kind : SectionKind
payload : Span
full : Span
custom_name : String?
}

#
Section::is_named

fn Section::is_named(self : Section, name : String) -> Bool

#
Section::payload_size

fn Section::payload_size(self : Section) -> Int

#
SectionDifference

pub struct SectionDifference {
label : String
left_size : Int?
right_size : Int?
}

#
SectionDifference::description

fn SectionDifference::description(self : SectionDifference) -> String

#
SectionKind

pub enum SectionKind {
Custom
Type
Import
Function
Table
Memory
Global
Export
Start
Element
Code
Data
DataCount
Tag
} derive(Eq)

for the sections they need.

#
SectionKind::id

fn SectionKind::id(self : SectionKind) -> Int

#
SectionKind::label

fn SectionKind::label(self : SectionKind) -> String

#
Span

pub struct Span {
start : Int
end : Int
}

#
Span::contains

fn Span::contains(self : Span, position : Int) -> Bool

#
Span::size

fn Span::size(self : Span) -> Int

#
TableInfo

pub struct TableInfo {
element_type : ValueType
limits : Limits
}

#
TagInfo

pub struct TagInfo {
type_index : Int
}

#
ValidationIssue

pub struct ValidationIssue {
level : IssueLevel
rule : String
message : String
}

#
ValidationIssue::description

fn ValidationIssue::description(self : ValidationIssue) -> String

#
ValidationReport

pub struct ValidationReport {
issues : Array[ValidationIssue]
}

#
ValidationReport::has_errors

fn ValidationReport::has_errors(self : ValidationReport) -> Bool

#
ValidationReport::issues

#
ValidationReport::render

fn ValidationReport::render(self : ValidationReport) -> String

#
ValueType

pub enum ValueType {
I32
I64
F32
F64
V128
FuncRef
ExternRef
}

#
ValueType::label

fn ValueType::label(self : ValueType) -> String

#
WasmFeature

pub enum WasmFeature {
MultiMemory
Memory64
Threads
BulkMemory
ReferenceTypes
ExceptionHandling
Simd
}

currently decoded metadata did not require it.

#
WasmFeature::label

fn WasmFeature::label(self : WasmFeature) -> String

#
build_call_graph

fn build_call_graph(decoded : DecodedModule) -> Result[Array[FunctionCallSummary], DecodeError]

#
data_segment_bytes

fn data_segment_bytes(binary : Module, segment : DataSegment) -> Array[Int]

#
decode_data_segments

fn decode_data_segments(binary : Module) -> Result[Array[DataSegment], DecodeError]

#
decode_element_segments

fn decode_element_segments(binary : Module) -> Result[Array[ElementSegment], DecodeError]

#
decode_module

fn decode_module(binary : Module) -> Result[DecodedModule, DecodeError]

#
decode_name_section

fn decode_name_section(binary : Module) -> Result[NameSection?, DecodeError]

#
detect_features

fn detect_features(decoded : DecodedModule) -> Array[WasmFeature]

#
diff_structure

fn diff_structure(left : Module, right : Module) -> Array[SectionDifference]

#
parse_module

fn parse_module(bytes : Array[Int]) -> Result[Module, DecodeError]

#
render_data_segments

fn render_data_segments(segments : Array[DataSegment]) -> String

#
render_decoded

fn render_decoded(decoded : DecodedModule) -> String

#
render_element_segments

fn render_element_segments(segments : Array[ElementSegment]) -> String

#
render_features

fn render_features(features : Array[WasmFeature]) -> String

#
render_structure

fn render_structure(binary : Module) -> String

#
render_structure_diff

fn render_structure_diff(left : Module, right : Module) -> String

#
render_structure_json

fn render_structure_json(binary : Module) -> String

#
render_validation_json

fn render_validation_json(report : ValidationReport) -> String

#
scan_code_body

fn scan_code_body(binary : Module, body : CodeBody) -> Result[InstructionStats, DecodeError]

#
scan_instructions

fn scan_instructions(bytes : Array[Int], span : Span) -> Result[InstructionStats, DecodeError]

#
section_kind_from_id

fn section_kind_from_id(id : Int) -> SectionKind?

#
validate

fn validate(decoded : DecodedModule) -> ValidationReport