From a5a3cb3894b1a9770b22be71bdae77e8ac33e16e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Jan 2026 08:01:29 +0000 Subject: [PATCH 1/2] Add WebAssembly Inspector tool A comprehensive tool to inspect WASM files with: - Binary parsing to extract all sections (types, imports, exports, memory, tables, globals, data, custom sections) - Function signature display with parameter and return types - Interactive function executor UI for calling exported functions - Hex dump view of raw binary - Example buttons for all WASM files in this repo (mquickjs, emperl, code counters) - Drag-and-drop file upload support - Collapsible sections for easy navigation --- wasm-inspector.html | 1796 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1796 insertions(+) create mode 100644 wasm-inspector.html diff --git a/wasm-inspector.html b/wasm-inspector.html new file mode 100644 index 0000000..e057734 --- /dev/null +++ b/wasm-inspector.html @@ -0,0 +1,1796 @@ + + + + + + WebAssembly Inspector + + + +

WebAssembly Inspector

+

Load a WASM file to inspect its structure, exports, imports, memory, and execute exported functions

+ +
+
+

Try an example from this repo:

+
+
+ +
+

Drop a WASM file here or click to select

+

Supports .wasm files

+ +
+ +
- or -
+ +
+ + +
+ +
+ + +
+
+ +
+ +
+ +
+
+

Module Summary

+ +
+
+
+
+
+ + +
+
+

Exports

+ 0 + +
+
+ + + + + + + + + +
NameKindDetails
+
+
+ + +
+
+

Function Executor

+ Interactive + +
+
+ +
+
+
+ + +
+
+

Imports

+ 0 + +
+
+ + + + + + + + + + +
ModuleNameKindDetails
+
+
+ + +
+
+

Type Section (Function Signatures)

+ 0 + +
+
+ + + + + + + + + + +
IndexParametersResultsSignature
+
+
+ + +
+
+

Memory

+ 0 + +
+
+
+
+
+ + +
+
+

Tables

+ 0 + +
+
+
+
+
+ + +
+
+

Globals

+ 0 + +
+
+ + + + + + + + + + +
IndexTypeMutableInitial Value
+
+
+ + +
+
+

Data Sections

+ 0 + +
+
+
+
+
+ + +
+
+

Custom Sections

+ 0 + +
+
+
+
+
+ + +
+ + +
+
+ + + + From 36c1d68caf244ecc891cab8a470a582b8be130c8 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Jan 2026 12:56:50 +0000 Subject: [PATCH 2/2] Improve function executor with user-friendly explanations - Add expandable explainer teaching WASM basics (types, no strings, pointers) - Categorize functions as "Simple" (0-1 params) vs "Complex" (may need pointers) - Color-code functions green/yellow based on complexity - Add "String mode" for eval-like functions that auto-writes text to memory - Show type tooltips on parameter inputs - Try to interpret numeric results as string pointers - Read string from memory if result looks like a valid pointer --- wasm-inspector.html | 282 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 237 insertions(+), 45 deletions(-) diff --git a/wasm-inspector.html b/wasm-inspector.html index e057734..d19c362 100644 --- a/wasm-inspector.html +++ b/wasm-inspector.html @@ -1425,11 +1425,37 @@

Raw Binary (First 512 bytes)

return; } - for (const exp of functionExports) { - const div = document.createElement('div'); - div.className = 'function-executor'; + // Add educational explanation + const explainer = document.createElement('div'); + explainer.className = 'executor-explainer'; + explainer.innerHTML = ` +
+ How does this work? (click to expand) +
+

WASM only understands numbers! There are 4 types:

+
    +
  • i32 = 32-bit integer (whole numbers like 42, -7, 1000000)
  • +
  • i64 = 64-bit integer (bigger whole numbers)
  • +
  • f32 = 32-bit float (decimals like 3.14, -0.5)
  • +
  • f64 = 64-bit float (more precise decimals)
  • +
+

What about strings? WASM has no concept of text! Functions that need strings actually expect:

+
    +
  • A memory address (pointer) - where the text is stored in memory
  • +
  • The length of the text in bytes
  • +
+

Functions with names like eval, parse, or taking 2+ i32 params often expect pointer+length pairs.

+

Simple functions (taking 0-1 number params) work great here. Complex functions needing strings/memory won't work without extra setup.

+
+
+ `; + container.appendChild(explainer); + + // Categorize functions + const simpleFuncs = []; + const complexFuncs = []; - // Get function signature + for (const exp of functionExports) { let params = []; let results = []; const binaryExp = binaryInfo.exports.find(e => e.name === exp.name); @@ -1446,35 +1472,126 @@

Raw Binary (First 512 bytes)

} } - const signature = `(${params.join(', ') || 'void'}) -> ${results.join(', ') || 'void'}`; + const funcInfo = { exp, params, results }; + + // Heuristic: simple functions have 0-1 params or are named things like "add", "mul", etc. + const isSimple = params.length <= 1 || + /^(add|sub|mul|div|mod|pow|sqrt|abs|min|max|floor|ceil|round|get_|is_|has_)/i.test(exp.name); + + if (isSimple) { + simpleFuncs.push(funcInfo); + } else { + complexFuncs.push(funcInfo); + } + } + + // Render simple functions first + if (simpleFuncs.length > 0) { + const simpleHeader = document.createElement('h4'); + simpleHeader.style.cssText = 'margin: 15px 0 10px; color: #28a745; font-size: 14px;'; + simpleHeader.innerHTML = '✓ Simple Functions (easy to use)'; + container.appendChild(simpleHeader); + + for (const { exp, params, results } of simpleFuncs) { + container.appendChild(createFunctionUI(exp, params, results, false)); + } + } + + // Render complex functions + if (complexFuncs.length > 0) { + const complexHeader = document.createElement('h4'); + complexHeader.style.cssText = 'margin: 20px 0 10px; color: #dc3545; font-size: 14px;'; + complexHeader.innerHTML = '⚠ Complex Functions (may need memory pointers)'; + container.appendChild(complexHeader); - div.innerHTML = ` -

${exp.name} ${signature}

-
- ${params.length === 0 ? 'No parameters' : ''} + for (const { exp, params, results } of complexFuncs) { + container.appendChild(createFunctionUI(exp, params, results, true)); + } + } + } + + function createFunctionUI(exp, params, results, isComplex) { + const div = document.createElement('div'); + div.className = 'function-executor'; + if (isComplex) { + div.style.borderLeft = '3px solid #ffc107'; + } else { + div.style.borderLeft = '3px solid #28a745'; + } + + const signature = `(${params.join(', ') || 'void'}) -> ${results.join(', ') || 'void'}`; + const safeName = exp.name.replace(/[^a-zA-Z0-9_]/g, '_'); + + // Detect if this looks like a string function (2 i32 params often = ptr + len) + const looksLikeString = params.length === 2 && params.every(p => p === 'i32'); + const looksLikeEval = /eval|exec|run|parse|compile/i.test(exp.name); + + let stringModeHtml = ''; + if (looksLikeString && looksLikeEval) { + stringModeHtml = ` +
+ +
- - `; + } - // Add parameter inputs - const paramsDiv = div.querySelector(`#params-${exp.name}`); - params.forEach((param, i) => { - const inputDiv = document.createElement('div'); - inputDiv.className = 'param-input'; - inputDiv.innerHTML = ` - - - `; - paramsDiv.appendChild(inputDiv); - }); + div.innerHTML = ` +

${exp.name} ${signature}

+ ${isComplex ? '

Multiple params may indicate pointer+length pairs for strings

' : ''} + ${stringModeHtml} +
+ ${params.length === 0 ? 'No parameters needed' : ''} +
+ + + `; - container.appendChild(div); - } + // Add parameter inputs with helpful tooltips + const paramsDiv = div.querySelector(`#params-${safeName}`); + const typeDescriptions = { + 'i32': 'Integer (-2B to 2B)', + 'i64': 'Big integer', + 'f32': 'Decimal number', + 'f64': 'Precise decimal' + }; + + params.forEach((param, i) => { + const inputDiv = document.createElement('div'); + inputDiv.className = 'param-input'; + const desc = typeDescriptions[param] || param; + inputDiv.innerHTML = ` + + + `; + paramsDiv.appendChild(inputDiv); + }); + + return div; } + // Toggle string mode for functions + window.toggleStringMode = function(safeName, enabled) { + const stringInput = document.getElementById(`string-input-${safeName}`); + const paramsDiv = document.getElementById(`params-${safeName}`); + + stringInput.style.display = enabled ? 'block' : 'none'; + + // Disable/enable raw param inputs + const inputs = paramsDiv.querySelectorAll('input'); + inputs.forEach(input => { + input.disabled = enabled; + input.style.opacity = enabled ? '0.5' : '1'; + }); + }; + // Execute a function - window.executeFunction = function(name) { + window.executeFunction = function(name, safeName) { if (!wasmInstance) { alert('Module not instantiated'); return; @@ -1486,43 +1603,118 @@

${exp.name} Writing "${stringValue.substring(0, 50)}${stringValue.length > 50 ? '...' : ''}" to memory at address ${ptr}...\n`; + } else { + // Collect arguments normally + let i = 0; + while (true) { + const input = document.getElementById(`arg-${safeName}-${i}`); + if (!input) break; + + const type = input.dataset.type; + let value = input.value || '0'; + + // Parse based on type + if (type === 'i32' || type === 'i64') { + args.push(parseInt(value)); + } else if (type === 'f32' || type === 'f64') { + args.push(parseFloat(value)); + } else { + args.push(parseInt(value)); + } + i++; } - i++; } const startTime = performance.now(); const result = func(...args); const endTime = performance.now(); + // Try to interpret result + let resultText = `Result: ${result}`; + + // If result is a pointer, try to read string from memory + if (typeof result === 'number' && result > 0 && result < 1000000) { + const memory = getWasmMemory(); + if (memory) { + const str = readStringFromMemory(memory, result, 200); + if (str && str.length > 0 && /^[\x20-\x7E\n\r\t]+$/.test(str)) { + resultText += `\n\nAs string at address ${result}: "${str}"`; + } + } + } + + resultText += `\n\nExecution time: ${(endTime - startTime).toFixed(3)}ms`; + resultDiv.className = 'result-output success'; - resultDiv.textContent = `Result: ${result}\nExecution time: ${(endTime - startTime).toFixed(3)}ms`; + if (useStringMode) { + resultDiv.innerHTML += resultText; + } else { + resultDiv.textContent = resultText; + } } catch (e) { resultDiv.className = 'result-output error'; resultDiv.textContent = `Error: ${e.message}`; } }; + // Helper to get WASM memory + function getWasmMemory() { + if (!wasmInstance) return null; + for (const [key, val] of Object.entries(wasmInstance.exports)) { + if (val instanceof WebAssembly.Memory) { + return val; + } + } + return null; + } + + // Helper to read null-terminated string from memory + function readStringFromMemory(memory, ptr, maxLen = 100) { + const view = new Uint8Array(memory.buffer); + let end = ptr; + while (end < ptr + maxLen && view[end] !== 0) { + end++; + } + const bytes = view.slice(ptr, end); + try { + return new TextDecoder().decode(bytes); + } catch (e) { + return null; + } + } + function displayImports(imports, binaryInfo) { document.getElementById('imports-count').textContent = imports.length; const tbody = document.getElementById('imports-tbody');