From a9d2f1bc09c2e55d7a2768e5dc25805c16f9b650 Mon Sep 17 00:00:00 2001 From: Matt Johnson-Pint Date: Thu, 8 Aug 2024 11:12:55 -0700 Subject: [PATCH] Initial commit --- .gitignore | 25 ++++++++ LICENSE | 21 +++++++ README.md | 3 + cli/.gitignore | 2 + cli/go.mod | 7 +++ cli/main.go | 59 +++++++++++++++++ go.mod | 3 + wasmextractor.go | 161 +++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 281 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 cli/.gitignore create mode 100644 cli/go.mod create mode 100644 cli/main.go create mode 100644 go.mod create mode 100644 wasmextractor.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6f72f89 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +go.work.sum + +# env file +.env diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..515ad78 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Hypermode, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..81e7470 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# WASM Extractor + +Reads a `.wasm` file and prints its imports and exports diff --git a/cli/.gitignore b/cli/.gitignore new file mode 100644 index 0000000..1208c67 --- /dev/null +++ b/cli/.gitignore @@ -0,0 +1,2 @@ +wasmextractor +wasmextractor.exe diff --git a/cli/go.mod b/cli/go.mod new file mode 100644 index 0000000..d7d35fd --- /dev/null +++ b/cli/go.mod @@ -0,0 +1,7 @@ +module wasmextractor + +go 1.22.5 + +require github.com/hypermodeAI/wasmextractor v0.0.0 + +replace github.com/hypermodeAI/wasmextractor => ../ diff --git a/cli/main.go b/cli/main.go new file mode 100644 index 0000000..885389b --- /dev/null +++ b/cli/main.go @@ -0,0 +1,59 @@ +/* + * Copyright 2024 Hypermode, Inc. + * MIT License - https://opensource.org/licenses/MIT + */ + +package main + +import ( + "fmt" + "os" + + "github.com/hypermodeAI/wasmextractor" +) + +func main() { + + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: %s \n", os.Args[0]) + return + } + + wasmFilePath := os.Args[1] + + wasmBytes, err := wasmextractor.ReadWasmFile(wasmFilePath) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + info, err := wasmextractor.ExtractWasmInfo(wasmBytes) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + fmt.Println() + + fmt.Println("IMPORTS") + fmt.Println("-------") + if len(info.Imports) == 0 { + fmt.Println("(none)") + } else { + for _, item := range info.Imports { + fmt.Println(&item) + } + } + fmt.Println() + + fmt.Println("EXPORTS") + fmt.Println("-------") + if len(info.Exports) == 0 { + fmt.Println("(none)") + } else { + for _, item := range info.Exports { + fmt.Println(&item) + } + } + fmt.Println() +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..34aef85 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/hypermodeAI/wasmextractor + +go 1.22.5 diff --git a/wasmextractor.go b/wasmextractor.go new file mode 100644 index 0000000..1e3f160 --- /dev/null +++ b/wasmextractor.go @@ -0,0 +1,161 @@ +/* + * Copyright 2024 Hypermode, Inc. + * MIT License - https://opensource.org/licenses/MIT + */ + +package wasmextractor + +import ( + "bytes" + "encoding/binary" + "fmt" + "os" +) + +type WasmInfo struct { + Imports []WasmItem + Exports []WasmItem +} + +type WasmItem struct { + Name string + Kind WasmItemKind + Index uint32 +} + +func (i *WasmItem) String() string { + return fmt.Sprintf("%s: %s (index: %d)", i.Kind, i.Name, i.Index) +} + +type WasmItemKind int32 + +const ( + WasmFunction WasmItemKind = 0 + WasmTable WasmItemKind = 1 + WasmMemory WasmItemKind = 2 + WasmGlobal WasmItemKind = 3 +) + +func (k WasmItemKind) String() string { + switch k { + case WasmFunction: + return "Function" + case WasmTable: + return "Table" + case WasmMemory: + return "Memory" + case WasmGlobal: + return "Global" + default: + return "Unknown" + } +} + +func ReadWasmFile(wasmFilePath string) ([]byte, error) { + wasmBytes, err := os.ReadFile(wasmFilePath) + if err != nil { + return nil, fmt.Errorf("error reading wasm file: %v", err) + } + + magic := []byte{0x00, 0x61, 0x73, 0x6D} // "\0asm" + if len(wasmBytes) < 8 || !bytes.Equal(wasmBytes[:4], magic) { + return nil, fmt.Errorf("invalid wasm file") + } + + if binary.LittleEndian.Uint32(wasmBytes[4:8]) != 1 { + return nil, fmt.Errorf("unsupported wasm version") + } + + return wasmBytes, nil +} + +func ExtractWasmInfo(wasmBytes []byte) (*WasmInfo, error) { + info := &WasmInfo{} + + offset := 8 + for offset < len(wasmBytes) { + sectionID := wasmBytes[offset] + offset++ + + size, n := binary.Uvarint(wasmBytes[offset:]) + offset += n + + switch sectionID { + case 2: // Import section + info.Imports = readImports(wasmBytes[offset : offset+int(size)]) + + case 7: // Export section + info.Exports = readExports(wasmBytes[offset : offset+int(size)]) + } + + offset += int(size) + } + + return info, nil +} + +func readImports(data []byte) []WasmItem { + + numItems, n := binary.Uvarint(data) + offset := n + + imports := make([]WasmItem, numItems) + + for i := 0; i < int(numItems); i++ { + moduleLen, n := binary.Uvarint(data[offset:]) + offset += n + + moduleName := string(data[offset : offset+int(moduleLen)]) + offset += int(moduleLen) + + fieldLen, n := binary.Uvarint(data[offset:]) + offset += n + + fieldName := string(data[offset : offset+int(fieldLen)]) + offset += int(fieldLen) + + kind := data[offset] + offset++ + + index, n := binary.Uvarint(data[offset:]) + offset += n + + imports[i] = WasmItem{ + Name: fmt.Sprintf("%s.%s", moduleName, fieldName), + Kind: WasmItemKind(kind), + Index: uint32(index), + } + } + + return imports +} + +func readExports(data []byte) []WasmItem { + + numItems, n := binary.Uvarint(data) + offset := n + + exports := make([]WasmItem, numItems) + + for i := 0; i < int(numItems); i++ { + fieldLen, n := binary.Uvarint(data[offset:]) + offset += n + + fieldName := string(data[offset : offset+int(fieldLen)]) + offset += int(fieldLen) + + kind := data[offset] + offset++ + + index, n := binary.Uvarint(data[offset:]) + offset += n + + exports[i] = WasmItem{ + Name: fieldName, + Kind: WasmItemKind(kind), + Index: uint32(index), + } + } + + return exports +}