├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── cli.mjs ├── lib ├── binaryen │ ├── README.md │ └── index.mjs └── stat │ ├── README.md │ ├── backend.mjs │ ├── funcPressure.mjs │ └── index.mjs ├── package.json ├── src ├── decompiler.mjs ├── disassembler.mjs ├── disassembly │ ├── disassembleI.mjs │ ├── disassembleO0.mjs │ ├── disassembleO1.mjs │ └── disassembleO2.mjs └── operations.mjs └── tests ├── minified ├── .gitignore ├── emcc-wasi.wasm ├── emcc-web-2.wasm ├── emcc-web.wasm ├── rust-web.wasm ├── rust-web2.wasm └── unity.wasm └── misc └── smith.wasm /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json 3 | .DS_Store -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json 3 | .DS_Store 4 | tests/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 awt-256 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # diswasm 2 | Disassembler for wasm binaries. At the moment this is more of a proof of code, a more established codebase and library will be published later on after completion of this project. 3 | 4 | > # NOTICE: This package will be migrated over to a new format very soon. Prepare yourself for the next level of wasm analysis. 5 | 6 | The way this program works is it determines the minimization level (called `funcPressure` in the code) of every single function in the wasm file. The minimization level is a category of 4 types of functions: 7 | 1. `O(-1)` Imported functions 8 | - These functions have no content/bodies 9 | 2. `O(0)` Unminified functions (pure) 10 | - These functions are a 1:1 replica of their source, including the stack frame. 11 | 3. `O(1)` Slighty minified functions 12 | - These functions are slightly minified versions of the unminifed functions. They still preserve the stackframe, 13 | but local variables are now inlined into wasm locals instead of being placed in the memory. 14 | 4. `O(2)` Fully minified functions 15 | - These functions have no stack frame, all calculation is done with the wasm stack and wasm locals 16 | 17 | Then it disassembles the function according to its `funcPressure`. All categories have implemented decompilers except for O(1), and as of now, O(1) functions resort to O(2) disassembly. 18 | 19 | ## Installing 20 | 21 | After downloading run `npm i -g diswasm` 22 | 23 | ## Usage 24 | 25 | After installing, `diswasm [-o ]`. 26 | -------------------------------------------------------------------------------- /cli.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import binaryen from "binaryen"; 3 | import { existsSync, fstat, readFileSync, writeFileSync } from "fs"; 4 | import { expandModule } from "./lib/binaryen/index.mjs" 5 | import { Decompiler } from "./src/decompiler.mjs"; 6 | 7 | 8 | let inputFile = null; 9 | let outputFile = null; 10 | let gadgets = false; 11 | for (let i = 2; i < process.argv.length; ++i) { 12 | if (process.argv.at(i) === "-o") { 13 | outputFile = process.argv.at(++i) 14 | } else if (process.argv.at(i) === "--gadgets") { 15 | gadgets = true; 16 | } else if (!inputFile) inputFile = process.argv.at(i) 17 | else { 18 | console.log("Only one input allowed"); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | if (inputFile === null) { 24 | console.log("Invalid input. args: [-o ]"); 25 | process.exit(1); 26 | } 27 | 28 | if (!existsSync(inputFile)) { 29 | console.log("Invalid file"); 30 | process.exit(1); 31 | } 32 | 33 | const wmod = expandModule(binaryen.readBinary(readFileSync(inputFile))); 34 | 35 | const diswasm = new Decompiler(wmod) 36 | 37 | const output = await diswasm.decompile(gadgets); 38 | 39 | if (outputFile) writeFileSync(outputFile, output); 40 | else console.log(output) 41 | -------------------------------------------------------------------------------- /lib/binaryen/README.md: -------------------------------------------------------------------------------- 1 | # `/lib/binaryen` 2 | 3 | This folder is full of wrappers over the binaryenjs, for ease of use. -------------------------------------------------------------------------------- /lib/binaryen/index.mjs: -------------------------------------------------------------------------------- 1 | import binaryen from "binaryen"; 2 | 3 | export const core = binaryen; 4 | // "Invalid Block If Loop Break Switch Call CallIndirect LocalGet LocalSet GlobalGet GlobalSet Load Store Const Unary Binary Select Drop Return MemorySize MemoryGrow Nop Unreachable AtomicCmpxchg AtomicRMW AtomicWait AtomicNotify AtomicFence SIMDExtract SIMDReplace SIMDShuffle SIMDTernary SIMDShift SIMDLoad SIMDLoadStoreLane MemoryInit DataDrop MemoryCopy MemoryFill RefNull RefIs RefFunc RefEq TableGet TableSet TableSize TableGrow Try Throw Rethrow TupleMake TupleExtract Pop I31New I31Get CallRef RefTest RefCast BrOn RttCanon RttSub StructNew StructGet StructSet ArrayNew ArrayInit ArrayGet ArraySet ArrayLen".split(" ") 5 | export const expandType = (m) => { 6 | return (typeof m === "number" ? binaryen.expandType(m) : m).map(t => { 7 | if (t === binaryen.i32) return "i32"; 8 | if (t === binaryen.none || t === 1) return "void"; 9 | if (t === binaryen.f32) return "f32"; 10 | if (t === binaryen.i64) return "i64"; 11 | if (t === binaryen.f64) return "f64"; 12 | console.log("unknown type " + t); 13 | process.exit(1); 14 | }) 15 | } 16 | 17 | export const expandExpression = (exprRef) => { 18 | if (!exprRef) return null; 19 | let info; 20 | try { 21 | info = binaryen.getExpressionInfo(exprRef); 22 | } catch (e) { 23 | console.log(e); 24 | console.log(exprRef); 25 | process.exit(1); 26 | } 27 | switch (info.id) { 28 | case binaryen.LocalGetId: 29 | return { 30 | id: "local.get", 31 | type: expandType(info.type)[0] || "void", 32 | index: info.index 33 | } 34 | case binaryen.LocalSetId: 35 | return { 36 | id: "local.set", 37 | type: expandType(info.type)[0] || "void", 38 | index: info.index, 39 | value: expandExpression(info.value) 40 | } 41 | case binaryen.BlockId: 42 | return { 43 | id: "block", 44 | type: expandType(info.type)[0] || "void", 45 | name: info.name, 46 | children: info.children.map(expandExpression) 47 | } 48 | case binaryen.IfId: 49 | return { 50 | id: "if", 51 | type: expandType(info.type)[0] || "void", 52 | condition: expandExpression(info.condition), 53 | ifTrue: expandExpression(info.ifTrue), 54 | ifFalse: expandExpression(info.ifFalse), 55 | } 56 | case binaryen.LoopId: 57 | return { 58 | id: "loop", 59 | type: expandType(info.type)[0] || "void", 60 | body: expandExpression(info.body) 61 | } 62 | case binaryen.BreakId: 63 | return { 64 | id: "br", 65 | type: expandType(info.type)[0] || "void", 66 | name: info.name, 67 | condition: expandExpression(info.condition), 68 | value: expandExpression(info.value) 69 | } 70 | case binaryen.SwitchId: 71 | return { 72 | id: "switch", 73 | type: expandType(info.type)[0] || "void", 74 | names: info.names, 75 | defaultName: info.defaultName, 76 | condition: expandExpression(info.condition), 77 | value: expandExpression(info.value) 78 | } 79 | case binaryen.CallId: 80 | return { 81 | id: "call", 82 | type: expandType(info.type)[0] || "void", 83 | target: convertIdentifier(info.target), 84 | operands: info.operands.map(expandExpression) 85 | } 86 | case binaryen.CallIndirectId: 87 | return { 88 | id: "call_indirect", 89 | type: expandType(info.type)[0] || "void", 90 | target: expandExpression(info.target), 91 | operands: info.operands.map(expandExpression) 92 | } 93 | case binaryen.GlobalSetId: 94 | return { 95 | id: "global.set", 96 | type: expandType(info.type)[0] || "void", 97 | name: info.name, 98 | value: expandExpression(info.value) 99 | } 100 | case binaryen.GlobalGetId: 101 | return { 102 | id: "global.get", 103 | type: expandType(info.type)[0] || "void", 104 | name: info.name 105 | } 106 | case binaryen.LoadId: 107 | return { 108 | id: "load", 109 | type: expandType(info.type)[0] || "void", 110 | isAtomic: info.isAtomic, 111 | isSigned: info.isSigned, 112 | offset: info.offset, 113 | bytes: info.bytes, 114 | align: info.align, 115 | ptr: expandExpression(info.ptr) 116 | } 117 | case binaryen.StoreId: 118 | return { 119 | id: "store", 120 | type: expandType(info.type)[0] || "void", 121 | isAtomic: info.isAtomic, 122 | offset: info.offset, 123 | bytes: info.bytes, 124 | align: info.align, 125 | ptr: expandExpression(info.ptr), 126 | value: expandExpression(info.value) 127 | } 128 | case binaryen.ConstId: 129 | return { 130 | id: "const", 131 | type: expandType(info.type)[0] || "void", 132 | value: typeof info.value === "object" ? new BigInt64Array(new Uint32Array([info.value.low, info.value.high]).buffer)[0] : info.value 133 | } 134 | case binaryen.UnaryId: 135 | return { 136 | id: "unary", 137 | type: expandType(info.type)[0] || "void", 138 | op: info.op, 139 | value: expandExpression(info.value) 140 | } 141 | case binaryen.BinaryId: 142 | return { 143 | id: "binary", 144 | type: expandType(info.type)[0] || "void", 145 | op: info.op, 146 | left: expandExpression(info.left), 147 | right: expandExpression(info.right), 148 | } 149 | case binaryen.SelectId: 150 | return { 151 | id: "select", 152 | type: expandType(info.type)[0] || "void", 153 | condition: expandExpression(info.condition), 154 | ifTrue: expandExpression(info.ifTrue), 155 | ifFalse: expandExpression(info.ifFalse), 156 | } 157 | case binaryen.DropId: 158 | return { 159 | id: "drop", 160 | type: expandType(info.type)[0] || "void", 161 | value: expandExpression(info.value) 162 | } 163 | case binaryen.ReturnId: 164 | return { 165 | id: "return", 166 | type: expandType(info.type)[0] || "void", 167 | value: expandExpression(info.value) 168 | } 169 | case binaryen.NopId: 170 | return { 171 | id: "nop", 172 | type: expandType(info.type)[0] || "void" 173 | } 174 | case binaryen.UnreachableId: 175 | return { 176 | id: "unreachable", 177 | type: expandType(info.type)[0] || "void" 178 | } 179 | case binaryen.MemorySizeId: 180 | return { 181 | id: "memory.size", 182 | type: expandType(info.type)[0] || "void" 183 | } 184 | case binaryen.MemoryGrowId: 185 | return { 186 | id: "memory.grow", 187 | type: expandType(info.type)[0] || "void", 188 | delta: expandExpression(info.delta) 189 | } 190 | default: 191 | console.log("Invalid id " + info.id) 192 | process.exit(-1); 193 | } 194 | } 195 | 196 | const session = new Set() 197 | 198 | class FuncIdentifier { 199 | constructor(name, index=-1) { 200 | this.name = translateFuncName(name); 201 | this.index = index; 202 | session.add(this); 203 | } 204 | } 205 | const translateFuncName = n => isNaN(n) ? n : ("$func"+ n) 206 | export const convertIdentifier = v => new FuncIdentifier(v.replace(/\\([a-f0-9]{2})/g, (c, g) => String.fromCharCode(parseInt(g, 16)))) 207 | 208 | export const expandFunction = (ref) => { 209 | const funcInfo = binaryen.getFunctionInfo(ref); 210 | const func = { 211 | name: convertIdentifier(translateFuncName(funcInfo.name)), 212 | params: expandType(funcInfo.params), 213 | result: expandType(funcInfo.results)[0] || "void", 214 | imported: false 215 | } 216 | if (funcInfo.base) { // Imported 217 | func.imported = true; 218 | func.base = funcInfo.base; 219 | func.module = funcInfo.module; 220 | } else { 221 | func.locals = expandType(funcInfo.vars) 222 | func.body = expandExpression(funcInfo.body); 223 | } 224 | return func; 225 | } 226 | 227 | export const expandModule = (wmod) => { 228 | const moduleObject = {wmod} 229 | let importedCount = 0; 230 | const exportedFunctions = [] 231 | for (let len = wmod.getNumExports(), i = 0; i < len; ++i) { 232 | const exportInfo = binaryen.getExportInfo(wmod.getExportByIndex(i)); 233 | if (exportInfo.kind === binaryen.ExternalKinds.Function) { 234 | exportedFunctions.push({export: exportInfo.name, func: convertIdentifier(translateFuncName(exportInfo.value))}) 235 | } 236 | } 237 | moduleObject.functions = []; 238 | for (let len = wmod.getNumFunctions(), i = 0; i < len; ++i) { 239 | const funcInfo = expandFunction(wmod.getFunctionByIndex(i)); 240 | funcInfo.name.index = i; 241 | if (funcInfo.imported) importedCount += 1 242 | moduleObject.functions[i] = funcInfo; 243 | } 244 | moduleObject.elementSegments = []; 245 | // TODO: Safe to assume only functions in LLVM, but unsafe for a utility package 246 | for (let len = wmod.getNumElementSegments(), i = 0; i < len; ++i) { 247 | const t = moduleObject.elementSegments[i] = binaryen.getElementSegmentInfo(wmod.getElementSegmentByIndex(i)); 248 | t.data = t.data.map(e => convertIdentifier(e)); 249 | t.offset = expandExpression(t.offset); 250 | } 251 | moduleObject.dataSegments = [] 252 | for (let len = wmod.getNumMemorySegments(), i = 0; i < len; ++i) { 253 | const t = moduleObject.dataSegments[i] = wmod.getMemorySegmentInfoByIndex(i); 254 | t.data = new Uint8Array(t.data); 255 | } 256 | moduleObject.globals = [] 257 | for (let len = wmod.getNumGlobals(), i = 0; i < len; ++i) { 258 | const t = moduleObject.globals[i] = binaryen.getGlobalInfo(wmod.getGlobalByIndex(i)); 259 | t.init = expandExpression(t.init) 260 | binaryen.getGlobalInfo(wmod.getGlobalByIndex(0)).init 261 | } 262 | moduleObject.nameMap = moduleObject.functions.reduce((o, {name}, i) => (o[name.name] = i, o), {}); 263 | for (const t of moduleObject.elementSegments) { 264 | t.data = t.data.map(e => (e.index = moduleObject.nameMap[e.name], e)); 265 | } 266 | for (const id of session) { 267 | id.index = moduleObject.nameMap[translateFuncName(id.name)]; 268 | if (id.index < importedCount && id.name.startsWith("fimport$") && moduleObject.functions[id.index].base.match(/^[a-z$_][a-z$_0-9]*$/gi)) { 269 | id.name = "fimport_" + moduleObject.functions[id.index].base 270 | } 271 | if (id.index === undefined) { 272 | console.log(id, moduleObject.nameMap); 273 | process.exit() 274 | } 275 | } 276 | moduleObject.nameMap = moduleObject.functions.reduce((o, {name}, i) => (o[name.name] = i, o), {}); 277 | for (const f of exportedFunctions) { 278 | moduleObject.functions[f.func.index].exportKey = f.export; 279 | } 280 | session.clear(); 281 | 282 | return moduleObject; 283 | } -------------------------------------------------------------------------------- /lib/stat/README.md: -------------------------------------------------------------------------------- 1 | # `/lib/stat` 2 | This folder is full of tools for statistical analysis on Wasm -------------------------------------------------------------------------------- /lib/stat/backend.mjs: -------------------------------------------------------------------------------- 1 | import binaryen from "binaryen"; 2 | 3 | /* 4 | The following tool estimates the compiler toolchain / backend used for compilation 5 | 6 | Possible ouputs: 7 | - "llvm32" 8 | Means it was compiled with a compiler that uses LLVM to target a 32 bit wasm 9 | - "llvm64" 10 | Means it was compiled with a compiler that uses LLVM to target a 64 bit wasm 11 | - "unknown" 12 | Means the wasm file likely doesn't use a backend, and was either handwritten or 13 | uses a compilation process that doesn't use LLVM 14 | */ 15 | export const get = (moduleObject, getStat) => { 16 | const __stack_pointer = moduleObject.globals.find(g => g.mutable && (g.type === binaryen.i32 || g.type === binaryen.i64) 17 | && g.init && g.init.id === 'const'); 18 | 19 | if (!__stack_pointer) return "unknown" 20 | 21 | if (__stack_pointer.type === binaryen.i32) return "llvm32"; 22 | else if (__stack_pointer.type === binaryen.i64) return "llvm64"; 23 | 24 | throw new Error("unexpected exception - lost type"); 25 | } -------------------------------------------------------------------------------- /lib/stat/funcPressure.mjs: -------------------------------------------------------------------------------- 1 | import binaryen from "binaryen"; 2 | 3 | /* 4 | The following tool estimates a given function's compression "pressure" rating 5 | 6 | Possible ouputs: {t: number, d?: Expression} 7 | For any given `t` 8 | - -1 (Imported) [ no data ] 9 | Imported function 10 | - 0 (Decompressed) [ no data ] 11 | Means it is completely decompressed, and has all code preserved 12 | - 1 (Minor compression) [data = stack_ptr setter ] 13 | Means it was slightly compressed, showing no register-level locals 14 | but heavy use of stack and slight inlining 15 | - 2 (Maximum compression) [ no data ] 16 | Means the function was completely compressed, using with heavy inlining 17 | and little to no use of stack 18 | */ 19 | export const get = (moduleObject, getStat, func) => { 20 | if (!func) { 21 | throw new Error("invalid function"); 22 | } else if (func.imported) return {t:-1}; 23 | const __stack_pointer = moduleObject.globals.find(g => g.mutable && (g.type === binaryen.i32 || g.type === binaryen.i64) 24 | && g.init && g.init.id === 'const'); 25 | const stack_ptr_name = __stack_pointer.name; 26 | 27 | /* -O0 28 | (local.set 0 29 | (global.get $g0)) 30 | (local.set 1 31 | (i32.const n)) 32 | (local.set 2 33 | (i32.add 34 | (local.get 0) 35 | (local.get 1))) 36 | (global.set $g0 37 | (local.get 2)) 38 | */ 39 | // console.log(3, func.func.body) 40 | if (!func.body.children) { 41 | return {t: 2}; 42 | } 43 | const body = func.body.children; 44 | O0: if (body.length >= 4) { 45 | 46 | 47 | let instr = body[0] 48 | 49 | if (instr.id !== 'local.set' || instr.value.id !== 'global.get' || instr.value.name !== stack_ptr_name) break O0; 50 | instr = body[1] 51 | 52 | if (instr.id !== 'local.set' || instr.value.id !== 'const') break O0; 53 | instr = body[2] 54 | 55 | if (instr.id !== 'local.set' || instr.value.id !== 'binary' 56 | || instr.value.left.id !== 'local.get' || (instr.value.left.index !== func.params.length && instr.value.left.index !== func.params.length + 1) 57 | || instr.value.right.id !== 'local.get' || (instr.value.right.index !== func.params.length && instr.value.right.index !== func.params.length + 1)) break O0; 58 | 59 | // instr = body[3] 60 | // if (instr.id !== 'global.set' || instr.name !== stack_ptr_name || instr.value.id !== 'local.get' || instr.value.index !== func.params.length + 2) break O0; 61 | 62 | return {t: 0, d: {stackFrameSize: body[1].value.value}}; 63 | } 64 | 65 | O1: if (body.length > 1) { 66 | for (const instr of body) { 67 | if (instr.id === 'global.set' && instr.name === stack_ptr_name 68 | && instr.value.id === 'local.set' && instr.value.value.id === 'binary') { 69 | let l = instr.value.value.left; 70 | let r = instr.value.value.right; 71 | if (l.id === 'global.get' && l.name === stack_ptr_name 72 | && r.id === 'const') return {t: 1, d: instr}; 73 | r = instr.value.value.left; 74 | l = instr.value.value.right; 75 | if (l.id === 'global.get' && l.name === stack_ptr_name 76 | && r.id === 'const') return {t: 1, d: instr}; 77 | } 78 | } 79 | } 80 | 81 | return {t: 2}; 82 | } -------------------------------------------------------------------------------- /lib/stat/index.mjs: -------------------------------------------------------------------------------- 1 | export const getStat = async (moduleObject, statName, ...arg) => { 2 | if (!moduleObject.__statCache) moduleObject.__statCache = new Map() 3 | else if (moduleObject.__statCache.has(statName)) return moduleObject.__statCache.get(statName); 4 | 5 | const {get} = await import ("./" + statName + ".mjs"); 6 | 7 | const r = await get(moduleObject, getStat, ...arg); 8 | if (arg.length ===0) moduleObject.__statCache.set(statName, r); 9 | return r; 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "diswasm", 3 | "description": "PoC Command line decompiler for wasm", 4 | "version": "0.5.5", 5 | "bin": { 6 | "diswasm": "cli.mjs" 7 | }, 8 | "author": "awt-256", 9 | "license": "MIT", 10 | "dependencies": { 11 | "binaryen": "^108.0.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/decompiler.mjs: -------------------------------------------------------------------------------- 1 | import { getStat } from "../lib/stat/index.mjs"; 2 | import { iDis } from "./disassembly/disassembleI.mjs"; 3 | import { O0Dis } from "./disassembly/disassembleO0.mjs"; 4 | import { O1Dis } from "./disassembly/disassembleO1.mjs"; 5 | import { O2Dis } from "./disassembly/disassembleO2.mjs"; 6 | 7 | const ODisassemblers = { 8 | [-1]: iDis, 9 | [0]: O0Dis, 10 | [1]: O1Dis, 11 | [2]: O2Dis 12 | }; 13 | 14 | export class Decompiler { 15 | constructor(wmod) { 16 | this.wmod = wmod; 17 | this.outputText = ""; 18 | this.mem = {min:0,max:0,mem:new Uint8Array(0)} 19 | } 20 | 21 | async decompile(gadgets) { 22 | const backend = await getStat(this.wmod, "backend"); 23 | if (backend !== "llvm32" && backend !== "llvm64") throw new Error("Unsupported backend"); 24 | 25 | let memDump = "" 26 | 27 | if (this.wmod.dataSegments.length) { 28 | const min = this.wmod.dataSegments.reduce((a,b) => a < b.offset ? a : b.offset, Infinity) 29 | const max = this.wmod.dataSegments.reduce((a,b) => a > b.offset+b.data.length ? a : b.offset + b.data.length, 0) 30 | const mem = new Uint8Array(max - min) 31 | for (const dataSeg of this.wmod.dataSegments) { 32 | mem.set(dataSeg.data, dataSeg.offset - min); 33 | } 34 | this.mem = {mem, min, max}; 35 | 36 | memDump += "\n/****INITIALIZED MEMORY DUMP****/\n" 37 | for (let i = 0; i < mem.length; i += 16) { 38 | memDump += "// " + (i + min).toString(16).padStart(8, "0") + ": " + Array.from(mem.slice(i, i + 16)).map(e => e.toString(16).padStart(2, "0")).join(' ') + " : " + JSON.stringify(new TextDecoder().decode(mem.slice(i, i + 16))).replace(/\\u00[a-f0-9][a-f0-9]/gi, (c) => "\\x" + c.slice(4)) + "\n" 39 | } 40 | } 41 | 42 | const funcBodies = []; 43 | for (const func of this.wmod.functions) { 44 | if (gadgets && !this.wmod.elementSegments.find(seg => seg.data.find(n => n.index === func.name.index))) continue; 45 | const pressure = await getStat(this.wmod, "funcPressure", func); 46 | 47 | funcBodies.push(new ODisassemblers[pressure.t](this, func, pressure.d).disassemble()); 48 | } 49 | 50 | for (const body of funcBodies) { 51 | this.outputText += '\n' + body; 52 | } 53 | 54 | let i = 0; 55 | for (let seg of this.wmod.elementSegments) { 56 | this.outputText += "\n" 57 | if (i++ > 0) this.outputText += "// " 58 | let offset = 0; 59 | if (seg.offset.id === 'const') offset = seg.offset.value 60 | else { 61 | this.outputText += `// Function table (unknown offset ${seg.offset.name})\n(*__function_table[${seg.data.length}])() = {\n ${Array(0).fill("NULL,").concat(Array.from(seg.data).map(e => e.name + ", // $func" + e.index + " " + this.wmod.functions[e.index].result + " (" + this.wmod.functions[e.index].params.join(', ') + ")")).join('\n ')}\n};` 62 | } 63 | this.outputText += `// Function table\n(*__function_table[${seg.data.length + offset}])() = {\n ${Array(offset).fill("NULL,").concat(Array.from(seg.data).map(e => e.name + ", // $func" + e.index + " " + this.wmod.functions[e.index].result + " (" + this.wmod.functions[e.index].params.join(', ') + ")")).join('\n ')}\n};` 64 | } 65 | this.outputText += memDump; 66 | 67 | return this.outputText; 68 | } 69 | } -------------------------------------------------------------------------------- /src/disassembler.mjs: -------------------------------------------------------------------------------- 1 | const TAB = ' ' 2 | class Disassembler { 3 | indentate(str) { 4 | return str.trim().split('\n').map(e => (TAB.repeat(this.indent) + e).trimEnd()).join('\n'); 5 | } 6 | static genParamName(index) { 7 | return "param" + index 8 | } 9 | static genLocalName(index) { 10 | return "local" + index 11 | } 12 | static numToString(num, type) { 13 | if (typeof num === 'bigint') return (num >= 0n ? "0x" : "-0x") + ((num >= 0n ? 1n : -1n) * num).toString(16); 14 | return type.startsWith('f') ? num.toString().includes('.') ? num.toString() : num.toString() + ".0" : ((num >= 0 ? "0x" : "-0x") + Math.abs(num).toString(16)); 15 | } 16 | static typeToText(type,size=null) { 17 | if ((type === "i32" || type === "i64") && size !== null) { 18 | switch (size) { 19 | case 1: return "char"; 20 | case 2: return "short"; 21 | case 4: return "int"; 22 | case 8: return "long"; 23 | } 24 | } 25 | switch (type) { 26 | case "i32": return "int"; 27 | case "i64": return "long"; 28 | case "f32": return "float"; 29 | case "f64": return "double"; 30 | case "void": return "void"; 31 | } 32 | 33 | throw new Error('Unknown') 34 | } 35 | 36 | static O_LEVEL = "invalid"; 37 | 38 | constructor(wdis, wfunc) { 39 | this.indent = 0; 40 | this.wdis = wdis; 41 | this.wmod = wdis.wmod; 42 | this.wfunc = wfunc; 43 | this.params = wfunc.params.map((t,i) => ({ 44 | type: Disassembler.typeToText(t), 45 | name: Disassembler.genParamName(i) 46 | })); 47 | this.locals = wfunc.imported ? [] : wfunc.locals.map((t, index) => ({ 48 | type: Disassembler.typeToText(t), 49 | offset: index + wfunc.params.length, 50 | size: -1 51 | })); 52 | this.returnType = this.wfunc.result; 53 | } 54 | 55 | disassemble() { 56 | let out = this.generateHeaderText() + "{\n"; 57 | this.indent += 1 58 | let s = out.length; 59 | out += this.indentate(this.generateLocalDeclaration()); 60 | this.indent -= 1; 61 | if (s === out.length) out = out.slice(0, s - 1) + "}\n" 62 | else out += "\n}\n" 63 | 64 | return out; 65 | } 66 | 67 | generateLocalDeclaration() { 68 | let out = ''; 69 | for (let l of this.locals) { 70 | const onStack = l.size !== -1; 71 | l.onStack = onStack; 72 | l.name = onStack ? `local_${l.offset.toString(16)}` : `local${l.offset}` 73 | out += onStack ? `// offset=0x${l.offset.toString(16)}` : `// local index=${l.offset}` 74 | out += "\n" + l.type + " " + l.name + ";\n" 75 | } 76 | return out 77 | } 78 | 79 | // Returns the function header text 80 | generateHeaderText() { 81 | let out = `// O[${this.constructor.O_LEVEL}] ${this.constructor.O_LEVEL === 2 ? "Disassembly" : "Decompilation"} of $func${this.wfunc.name.index}, known as ${this.wfunc.name.name}\n` 82 | const result = Disassembler.typeToText(this.returnType); 83 | if (typeof this.wfunc.exportKey === "string") out += "export " + JSON.stringify(this.wfunc.exportKey) + `; // $func${this.wfunc.name.index} is exported to ${JSON.stringify(this.wfunc.exportKey)}\n` 84 | out += result + " " + this.wfunc.name.name + "(" + this.params.map((t,i) => t.type + " " + t.name).join(', ') + ") " 85 | 86 | return out; 87 | } 88 | } 89 | 90 | export { Disassembler }; 91 | -------------------------------------------------------------------------------- /src/disassembly/disassembleI.mjs: -------------------------------------------------------------------------------- 1 | import { Disassembler } from "../disassembler.mjs"; 2 | 3 | // imported funcs 4 | export class iDis extends Disassembler { 5 | static O_LEVEL = -1; 6 | 7 | IR = []; 8 | 9 | disassemble() { 10 | let out = `// [${this.constructor.O_LEVEL}] Imported function $func${this.wfunc.name.index}, known as ${this.wfunc.name.name}\n` 11 | const result = Disassembler.typeToText(this.returnType); 12 | out += "import " + result + " " + this.wfunc.name.name + "(" + this.wfunc.params.map(Disassembler.typeToText).join(', ') + ") from /* module */ " + JSON.stringify(this.wfunc.module) + " /* export */ " + JSON.stringify(this.wfunc.base) + ";" 13 | 14 | return out; 15 | } 16 | } -------------------------------------------------------------------------------- /src/disassembly/disassembleO0.mjs: -------------------------------------------------------------------------------- 1 | import binaryen from "binaryen"; 2 | import { Disassembler } from "../disassembler.mjs"; 3 | import OPERATION_MAP from "../operations.mjs"; 4 | 5 | export class O0Dis extends Disassembler { 6 | static O_LEVEL = 0; 7 | 8 | constructor(wdis, wfunc, d) { 9 | super(wdis, wfunc); 10 | 11 | this.registers = this.wfunc.locals.map(type => ({ 12 | type, 13 | valueExpr: null 14 | })); 15 | this.locals = []; 16 | this.temps = []; 17 | this.IR = []; 18 | this.controlFlow = new Map(); 19 | this.stackframeSize = d.stackFrameSize; 20 | } 21 | 22 | processInstruction(instr) { 23 | if (!instr) return null 24 | switch(instr.id) { 25 | case 'local.set': { // register 26 | // it'll never set to a non param 27 | const register = this.registers[instr.index - this.params.length] 28 | register.valueExpr = this.processInstruction(instr.value); 29 | return null; 30 | } 31 | case 'local.get': { // register 32 | if (instr.index < this.params.length) return { 33 | id: "param.get", 34 | index: instr.index, 35 | type: instr.type 36 | } 37 | 38 | const register = this.registers[instr.index - this.params.length] 39 | if (!register) { 40 | console.log(register, instr) 41 | process.exit() 42 | } 43 | return register.valueExpr; 44 | } 45 | case 'global.get': { 46 | if (instr.name === 'global$0') return null; 47 | return { 48 | id: 'global.get', 49 | type: 'void', 50 | name: instr.name.startsWith("global$") ? "global" + instr.name.slice("global$".length) : instr.name, 51 | }; 52 | } 53 | case 'global.set': { 54 | if (instr.name === 'global$0') return null; 55 | return { 56 | id: 'global.set', 57 | type: 'void', 58 | name: instr.name.startsWith("global$") ? "global" + instr.name.slice("global$".length) : instr.name, 59 | value: this.processInstruction(instr.value) 60 | } 61 | } 62 | case 'call': { 63 | return { 64 | id: 'call', 65 | type: instr.type, 66 | target: instr.target, 67 | operands: instr.operands.map(e => this.processInstruction(e)) 68 | } 69 | } 70 | case 'call_indirect': { 71 | return { 72 | id: 'call_indirect', 73 | type: instr.type, 74 | target: this.processInstruction(instr.target), 75 | operands: instr.operands.map(e => this.processInstruction(e)) 76 | } 77 | } 78 | case 'call_indirect': { 79 | return { 80 | id: 'call_indirect', 81 | type: instr.type, 82 | target: this.processInstruction(instr.target), 83 | operands: instr.operands.map(e => this.processInstruction(e)) 84 | } 85 | } 86 | case 'const': { 87 | return { 88 | id: "const", 89 | type: instr.type, 90 | value: instr.value 91 | } 92 | } 93 | case 'binary': { 94 | let k= { 95 | id: "binary", 96 | type: instr.type, 97 | op: instr.op, 98 | left: this.processInstruction(instr.left) || { id: "global.get", name: "__stack_pointer" }, 99 | right: this.processInstruction(instr.right) || { id: "global.get", name: "__stack_pointer" }, 100 | } 101 | if ((k.left.id === "const" || k.right.id === "const") && (k.left.name === "__stack_pointer" || k.right.name === "__stack_pointer")) { 102 | return { id: "global.get", name: "__stack_base"} 103 | } 104 | return k; 105 | } 106 | case 'unary': { 107 | return { 108 | id: "unary", 109 | type: instr.type, 110 | op: instr.op, 111 | value: this.processInstruction(instr.value) 112 | } 113 | } 114 | case 'return': { 115 | return { 116 | id: 'return', 117 | type: instr.type, 118 | value: this.processInstruction(instr.value) 119 | } 120 | } 121 | case 'nop': { 122 | return { 123 | id: 'nop', 124 | type: instr.type 125 | } 126 | } 127 | case 'unreachable': { 128 | return { 129 | id: 'unreachable', 130 | type: instr.type 131 | } 132 | } 133 | case "load": { 134 | const ptr = this.processInstruction(instr.ptr) 135 | c: if (ptr.id === "global.get" && ptr.name === "__stack_base") { 136 | const offset = instr.offset; 137 | const local = this.locals.find(l => l.size !== -1 && offset >= l.offset && offset < l.offset + l.size) 138 | if (!local) break c; 139 | const diff = offset - local.offset 140 | return { 141 | id: 'local.get', 142 | type: instr.type, 143 | local: local, 144 | diff: diff 145 | } 146 | } 147 | c: if (ptr.id === 'binary') { 148 | if ((ptr.left.id !== "global.get" || ptr.left.name !== "__stack_base") && (ptr.right.id !== "global.get" || ptr.right.name !== "__stack_base")) break c; 149 | if (ptr.left.id !== "const" && ptr.right.id !== "const") break c; 150 | let c = ptr.left.id === "const" ? ptr.left.value : ptr.right.value; 151 | const offset = (instr.offset + c); 152 | const local = this.locals.find(l => l.size !== -1 && offset >= l.offset && offset < l.offset + l.size) 153 | if (!local) break c; 154 | const diff = offset - local.offset 155 | return { 156 | id: 'local.get', 157 | type: instr.type, 158 | local: local, 159 | diff: diff 160 | } 161 | } 162 | return { 163 | id: "load", 164 | type: instr.type, 165 | isAtomic: instr.isAtomic, 166 | isSigned: instr.isSigned, 167 | offset: instr.offset, 168 | bytes: instr.bytes, 169 | align: instr.align, 170 | ptr: ptr 171 | } 172 | } 173 | case "store": { 174 | const ptr = this.processInstruction(instr.ptr); 175 | const value = this.processInstruction(instr.value) 176 | c: if (ptr.id === "global.get" && ptr.name === "__stack_base") { 177 | const offset = instr.offset; 178 | const local = this.locals.find(l => l.size !== -1 && offset >= l.offset && offset < l.offset + l.size) 179 | if (!local) { 180 | this.locals.push({ 181 | size: instr.bytes, 182 | type: Disassembler.typeToText(instr.value.type), 183 | offset 184 | }) 185 | return { 186 | id: 'local.set', 187 | type: instr.type, 188 | local: this.locals[this.locals.length - 1], 189 | value 190 | } 191 | } else { 192 | const diff = offset - local.offset 193 | return { 194 | id: 'local.set', 195 | type: instr.type, 196 | local: local, 197 | value, 198 | diff 199 | } 200 | } 201 | } 202 | c: if (ptr.id === 'binary') { 203 | if ((ptr.left.id !== "global.get" || ptr.left.name !== "__stack_base") && (ptr.right.id !== "global.get" || ptr.right.name !== "__stack_base")) break c; 204 | if (ptr.left.id !== "const" && ptr.right.id !== "const") break c; 205 | let c = ptr.left.id === "const" ? ptr.left.value : ptr.right.value; 206 | const offset = (instr.offset + c); 207 | const local = this.locals.find(l => l.size !== -1 && offset >= l.offset && offset < l.offset + l.size) 208 | 209 | if (!local) { 210 | this.locals.push({ 211 | size: instr.bytes, 212 | type: Disassembler.typeToText(instr.value.type), 213 | offset 214 | }) 215 | return { 216 | id: 'local.set', 217 | type: instr.type, 218 | local: this.locals[this.locals.length - 1], 219 | value 220 | } 221 | } else { 222 | const diff = offset - local.offset 223 | return { 224 | id: 'local.set', 225 | type: instr.type, 226 | local: local, 227 | value, 228 | diff 229 | } 230 | } 231 | } 232 | return { 233 | id: "store", 234 | type: instr.type, 235 | isAtomic: instr.isAtomic, 236 | offset: instr.offset, 237 | bytes: instr.bytes, 238 | align: instr.align, 239 | ptr: ptr, 240 | value 241 | } 242 | } 243 | case 'memory.size': { 244 | return { 245 | id: 'memory.size', 246 | type: instr.type 247 | } 248 | } 249 | case 'memory.grow': { 250 | return { 251 | id: 'memory.size', 252 | type: instr.type, 253 | delta: this.processInstruction(instr.delta) 254 | } 255 | } 256 | case 'drop': { 257 | return { 258 | id: 'drop', 259 | type: instr.type, 260 | value: this.processInstruction(instr.value) 261 | } 262 | } 263 | case "block": { 264 | return { 265 | id: "block", 266 | type: instr.type, 267 | name: instr.name, 268 | children: instr.children.map(e => this.processInstruction(e)) 269 | } 270 | } 271 | case "if": { 272 | return { 273 | id: "if", 274 | type: instr.type, 275 | condition: this.processInstruction(instr.condition), 276 | ifTrue: this.processInstruction(instr.ifTrue), 277 | ifFalse: this.processInstruction(instr.ifFalse), 278 | } 279 | } 280 | case "loop": { 281 | return { 282 | id: 'loop', 283 | type: instr.type, 284 | body: this.processInstruction(instr.body) 285 | } 286 | } 287 | case "br": { 288 | return { 289 | id: 'br', 290 | type: instr.type, 291 | name: instr.name, 292 | condition: this.processInstruction(instr.condition), 293 | value: this.processInstruction(instr.value) 294 | } 295 | } 296 | case "switch": { 297 | return { 298 | id: 'switch', 299 | type: instr.type, 300 | names: instr.names, 301 | defaultName: instr.defaultName, 302 | condition: this.processInstruction(instr.condition), 303 | value: this.processInstruction(instr.value) 304 | } 305 | } 306 | default: 307 | console.log("Invalid id " + instr.id) 308 | process.exit(-1); 309 | } 310 | } 311 | process() { 312 | const body = this.wfunc.body.children; 313 | for (const instr of body) { 314 | const inst = this.processInstruction(instr); 315 | if (inst) this.IR.push(inst); 316 | } 317 | } 318 | 319 | disassembleInstruction(instr, isLoop=false) { 320 | if (!instr) return null 321 | switch(instr.id) { 322 | case 'global.get': { 323 | if (instr.name === '__stack_base') { 324 | let l = this.locals.find(e => e.size >= 0 && e.offset === 0);; 325 | if (l) return '&' + l.name 326 | } 327 | return instr.name; 328 | } 329 | case 'local.set': { 330 | // set by generateLocalDecleration 331 | if (instr.diff) return `*(&${instr.local.name} + ${instr.diff}) = ` + this.disassembleInstruction(instr.value); 332 | return instr.local.name + " = " + this.disassembleInstruction(instr.value); 333 | } 334 | case 'local.get': { 335 | // set by generateLocalDecleration 336 | if (instr.diff) return `*(&${instr.local.name} + ${instr.diff})` 337 | return instr.local.name 338 | } 339 | case 'param.get': { 340 | // set by generateLocalDecleratio 341 | return this.params[instr.index].name 342 | } 343 | case 'global.set': { 344 | return instr.name + " = " + this.disassembleInstruction(instr.value); 345 | } 346 | case 'call': { 347 | return instr.target.name + "(" + instr.operands.map(e => this.disassembleInstruction(e)).join(', ') + ")"; 348 | } 349 | case 'call_indirect': { 350 | return "__function_table[" + this.disassembleInstruction(instr.target) + "](" + instr.operands.map(e => this.disassembleInstruction(e)).join(', ') + ")" 351 | } 352 | case 'const': { 353 | let addition = ""; 354 | if (!Number(instr.value).toString().includes('.')) { 355 | let addr = Number(instr.value); 356 | if (addr >= this.wdis.mem.min && addr < this.wdis.mem.max) { 357 | addr -= this.wdis.mem.min 358 | const str = this.wdis.mem.mem.slice(addr, this.wdis.mem.mem.indexOf(0, addr)); 359 | if (str.length >= 2 && str.every(e => (e >= 0x20 && e < 0x7f) || e === 0x0a || e === 0x09 || e === 0x0d)) { 360 | addition = " /* " + JSON.stringify(String.fromCharCode(...str)) + " */ "; 361 | } 362 | } 363 | } 364 | return Disassembler.numToString(instr.value, instr.type) + addition 365 | } 366 | case "select": { 367 | return `${this.disassembleInstruction(instr.condition)} ? ${this.disassembleInstruction(instr.ifTrue)} : ${this.disassembleInstruction(instr.ifFalse)}` 368 | } 369 | case 'binary': { 370 | k: if (instr.op === binaryen.AddInt32 || instr.op === binaryen.AddInt64) { 371 | if (instr.left.id === 'const' || instr.right.id === 'const') { 372 | if ((instr.left.id === 'global.get' && instr.left.name === "__stack_base") || (instr.right.id === 'global.get' && instr.right.name === "__stack_base")) { 373 | let offset = (instr.left.id === 'const' ? instr.left.value : instr.right.value); 374 | 375 | const local = this.locals.find(l => l.size !== -1 && offset >= l.offset && offset < l.offset + l.size); 376 | if (!local) break k; 377 | const diff = offset - local.offset 378 | if (diff !== 0) { 379 | return "&" + local.name + "[" + diff + "]" 380 | } 381 | return "&" + local.name; 382 | } 383 | } 384 | } 385 | let left = this.disassembleInstruction(instr.left) || "__stack_pointer"; 386 | let right = this.disassembleInstruction(instr.right) || "__stack_pointer"; 387 | return OPERATION_MAP["binary" + instr.op].replace(/\$|\#/g, c => c === "$" ? left : right) 388 | } 389 | case 'unary': { 390 | return OPERATION_MAP["unary" + instr.op].replace(/\$/g, this.disassembleInstruction(instr.value)) 391 | } 392 | case 'return': { 393 | return "return" + (instr.value ? " " + this.disassembleInstruction(instr.value) : "") 394 | } 395 | case 'nop': { 396 | return "" 397 | } 398 | case 'unreachable': { 399 | return "abort(\"unreachable\")" 400 | } 401 | case "load": { 402 | let ptr = typeof instr.ptr === "string" ? instr.ptr : this.disassembleInstruction(instr.ptr); 403 | let offset = Disassembler.numToString(instr.offset, "i"); 404 | let load = (ptr === "0x0" ? offset : (offset === "0x0" ? ptr : (ptr + " + " + offset))) 405 | return "*((" + (instr.isSigned ? "" : "unsigned ") + Disassembler.typeToText(instr.type, instr.bytes) + " *) " + load + ")"; 406 | } 407 | case "store": { 408 | let ptr = typeof instr.ptr === "string" ? instr.ptr : this.disassembleInstruction(instr.ptr); 409 | let offset = Disassembler.numToString(instr.offset, "i"); 410 | let load = (ptr === "0x0" ? offset : (offset === "0x0" ? ptr : (ptr + " + " + offset))) 411 | return "*((" + (instr.isSigned ? "" : "unsigned ") + Disassembler.typeToText(instr.value.type, instr.bytes) + " *) " + load + ") = " + this.disassembleInstruction(instr.value); 412 | } 413 | case 'memory.size': { 414 | return "__get_memory_size()" 415 | } 416 | case 'memory.grow': { 417 | return "__grow_memory_size(" + this.disassembleInstruction(instr.delta) + ")" 418 | } 419 | case 'drop': { 420 | return this.disassembleInstruction(instr.value); 421 | } 422 | case "block": { 423 | this.controlFlow.set(instr.name, isLoop); 424 | let out = (instr.name ? (instr.name + ": ") : "") + "{" 425 | for (const i of instr.children) { 426 | const instr = this.disassembleInstruction(i); 427 | if (!instr) continue; 428 | out += "\n" + this.indentate(instr) + ";" 429 | } 430 | if (isLoop) out += "\nbreak " + instr.name + ";" 431 | out += "\n}" 432 | return out; 433 | } 434 | case "if": { 435 | let out = "if (" + this.disassembleInstruction(instr.condition) + ") {\n" + this.indentate(this.disassembleInstruction(instr.ifTrue)) + ";\n}" 436 | if (instr.ifFalse) out += " else {\n" + this.indentate(this.disassembleInstruction(instr.ifFalse)) + ";\n}" 437 | 438 | return out; 439 | } 440 | case "loop": { 441 | return "while (1) " + this.disassembleInstruction(instr.body, true) 442 | } 443 | case "br": { 444 | const isLoop = this.controlFlow.get(instr.name); 445 | let val = instr.value ? "(" + this.disassembleInstruction(instr.value) + ")" : ""; 446 | let iff = instr.condition ? "if (" + this.disassembleInstruction(instr.condition) + ") " : ""; 447 | let n = isLoop ? "continue" : "break" 448 | return `${iff}${n}${val} ${instr.name}`; 449 | } 450 | case "switch": { 451 | let out = "switch (" + this.disassembleInstruction(instr.condition) + ") {\n" 452 | let val = instr.value ? "(" + this.disassembleInstruction(instr.value) + ")" : ""; 453 | for (let i = 0; i < instr.names.length; ++i) { 454 | out += this.indentate("case " + i + ": break" + val + " " + instr.names[i] + ";") + "\n" 455 | } 456 | if (instr.defaultName) out += this.indentate("default: break" + val + " " + instr.defaultName + ";") + "\n" 457 | out += "}" 458 | return out; 459 | } 460 | default: 461 | console.log("Invalid id " + instr.id) 462 | process.exit(-1); 463 | } 464 | } 465 | 466 | disassemble() { 467 | this.process() 468 | let out = "{\n"; 469 | this.indent += 1 470 | let s = out.length; 471 | out += this.indentate(this.generateLocalDeclaration()) + "\n"; 472 | out += "\n" 473 | for (const i of this.IR) { 474 | if (i.id === 'global.set' && i.name === "__stack_pointer") continue; 475 | const instr = this.disassembleInstruction(i); 476 | if (!instr) continue; 477 | out += this.indentate(instr) + ";" + "\n" 478 | } 479 | this.indent -= 1; 480 | if (s === out.length) out = out.slice(0, s - 1) 481 | out += "}\n" 482 | 483 | return this.generateHeaderText() + out; 484 | } 485 | } 486 | // import binaryen from "binaryen" 487 | // import operations from "../operations.mjs" 488 | 489 | // export const disassembleInstruction = (wmod, instr) => { 490 | // switch (instr.id) { 491 | // case binaryen.BinaryId: 492 | // return operations[instr.op].replace(/\$/, disassembleInstruction) 493 | // case binaryen.UnaryId: 494 | // // return operations[] 495 | // } 496 | // } -------------------------------------------------------------------------------- /src/disassembly/disassembleO1.mjs: -------------------------------------------------------------------------------- 1 | import { Disassembler } from "../disassembler.mjs"; 2 | import { O2Dis } from "./disassembleO2.mjs"; 3 | 4 | // export class O1Dis extends Disassembler { 5 | // static O_LEVEL = 1; 6 | 7 | // disassemble() { 8 | // return this.generateHeaderText() + "{ /* UNSUPPORTED AS OF NOW */ }\n" 9 | // } 10 | // } 11 | 12 | export class O1Dis extends O2Dis { 13 | static O_LEVEL = 1; 14 | } -------------------------------------------------------------------------------- /src/disassembly/disassembleO2.mjs: -------------------------------------------------------------------------------- 1 | import binaryen from "binaryen"; 2 | import { Disassembler } from "../disassembler.mjs"; 3 | import OPERATION_MAP from "../operations.mjs"; 4 | 5 | export class O2Dis extends Disassembler { 6 | static O_LEVEL = 2; 7 | 8 | constructor(wdis, wfunc, d) { 9 | super(wdis, wfunc); 10 | 11 | this.controlFlow = new Map(); 12 | } 13 | 14 | disassembleInstruction(instr, isLoop=false) { 15 | if (!instr) return null 16 | switch(instr.id) { 17 | case 'global.get': { 18 | return instr.name.startsWith("global$") ? "global" + instr.name.slice("global$".length) : instr.name; 19 | } 20 | case 'local.set': { 21 | return (instr.index >= this.params.length ? this.locals[instr.index - this.params.length].name : this.params[instr.index].name) + " = " + this.disassembleInstruction(instr.value); 22 | } 23 | case 'local.get': { 24 | return (instr.index >= this.params.length ? this.locals[instr.index - this.params.length].name : this.params[instr.index].name) 25 | } 26 | case 'global.set': { 27 | return (instr.name.startsWith("global$") ? "global" + instr.name.slice("global$".length) : instr.name) + " = " + this.disassembleInstruction(instr.value); 28 | } 29 | case 'call': { 30 | return instr.target.name + "(" + instr.operands.map(e => this.disassembleInstruction(e)).join(', ') + ")"; 31 | } 32 | case 'call_indirect': { 33 | return "__function_table[" + this.disassembleInstruction(instr.target) + "](" + instr.operands.map(e => this.disassembleInstruction(e)).join(', ') + ")" 34 | } 35 | case 'const': { 36 | let addition = ""; 37 | if (!Number(instr.value).toString().includes('.')) { 38 | let addr = Number(instr.value); 39 | if (addr >= this.wdis.mem.min && addr < this.wdis.mem.max) { 40 | addr -= this.wdis.mem.min 41 | const str = this.wdis.mem.mem.slice(addr, this.wdis.mem.mem.indexOf(0, addr)); 42 | if (str.length >= 2 && str.every(e => e >= 0x20 && e < 0x7f)) { 43 | addition = " /* " + JSON.stringify(String.fromCharCode(...str)) + " */ "; 44 | } 45 | } 46 | } 47 | return Disassembler.numToString(instr.value, instr.type) + addition; 48 | } 49 | case 'binary': { 50 | const left = this.disassembleInstruction(instr.left); 51 | const right = this.disassembleInstruction(instr.right); 52 | return OPERATION_MAP["binary" + instr.op].replace(/\$|\#/g, c => c === "$" ? left : right) 53 | } 54 | case 'unary': { 55 | return OPERATION_MAP["unary" + instr.op].replace(/\$/g, this.disassembleInstruction(instr.value)) 56 | } 57 | case 'return': { 58 | return "return" + (instr.value ? " " + this.disassembleInstruction(instr.value) : "") 59 | } 60 | case 'nop': { 61 | return "" 62 | } 63 | case 'unreachable': { 64 | return "abort(\"unreachable\")" 65 | } 66 | case "load": { 67 | let ptr = this.disassembleInstruction(instr.ptr); 68 | let offset = Disassembler.numToString(instr.offset, "i"); 69 | let load = (ptr === "0x0" ? offset : (offset === "0x0" ? ptr : (ptr + " + " + offset))) 70 | return "*((" + (instr.isSigned ? "" : "unsigned ") + Disassembler.typeToText(instr.type, instr.bytes) + " *) " + load + ")"; 71 | } 72 | case "store": { 73 | let ptr = this.disassembleInstruction(instr.ptr); 74 | let offset = Disassembler.numToString(instr.offset, "i"); 75 | let load = (ptr === "0x0" ? offset : (offset === "0x0" ? ptr : (ptr + " + " + offset))) 76 | return "*((" + (instr.isSigned ? "" : "unsigned ") + Disassembler.typeToText(instr.value.type, instr.bytes) + " *) " + load + ") = " + this.disassembleInstruction(instr.value); 77 | } 78 | case 'memory.size': { 79 | return "__get_memory_size()" 80 | } 81 | case 'memory.grow': { 82 | return "__grow_memory_size(" + this.disassembleInstruction(instr.delta) + ")" 83 | } 84 | case 'drop': { 85 | return this.disassembleInstruction(instr.value); 86 | } 87 | case "block": { 88 | this.controlFlow.set(instr.name, isLoop); 89 | let out = (instr.name ? (instr.name + ": ") : "") + "{" 90 | for (const i of instr.children) { 91 | const instr = this.disassembleInstruction(i); 92 | if (!instr) continue; 93 | out += "\n" + this.indentate(instr) + ";" 94 | } 95 | if (isLoop) out += "\nbreak " + instr.name + ";" 96 | out += "\n}" 97 | return out; 98 | } 99 | case "if": { 100 | let out = "if (" + this.disassembleInstruction(instr.condition) + ") {\n" + this.indentate(this.disassembleInstruction(instr.ifTrue)) + ";\n}" 101 | if (instr.ifFalse) out += " else {\n" + this.indentate(this.disassembleInstruction(instr.ifFalse)) + ";\n}" 102 | 103 | return out; 104 | } 105 | case "loop": { 106 | return "while (1) " + this.disassembleInstruction(instr.body, true) 107 | } 108 | case "br": { 109 | const isLoop = this.controlFlow.get(instr.name); 110 | let val = instr.value ? "(" + this.disassembleInstruction(instr.value) + ")" : ""; 111 | let iff = instr.condition ? "if (" + this.disassembleInstruction(instr.condition) + ") " : ""; 112 | let n = isLoop ? "continue" : "break" 113 | return `${iff}${n}${val} ${instr.name}`; 114 | } 115 | case "switch": { 116 | let out = "switch (" + this.disassembleInstruction(instr.condition) + ") {\n" 117 | let val = instr.value ? "(" + this.disassembleInstruction(instr.value) + ")" : ""; 118 | for (let i = 0; i < instr.names.length; ++i) { 119 | out += this.indentate("case " + i + ": break" + val + " " + instr.names[i] + ";") + "\n" 120 | } 121 | if (instr.defaultName) out += this.indentate("default: break" + val + " " + instr.defaultName + ";") + "\n" 122 | out += "}" 123 | return out; 124 | } 125 | case "select": { 126 | return `${this.disassembleInstruction(instr.condition)} ? ${this.disassembleInstruction(instr.ifTrue)} : ${this.disassembleInstruction(instr.ifFalse)}` 127 | } 128 | default: 129 | console.log("Invalid id " + instr.id) 130 | process.exit(-1); 131 | } 132 | } 133 | 134 | disassemble() { 135 | let out = "{\n"; 136 | this.indent += 1 137 | let s = out.length; 138 | out += this.indentate(this.generateLocalDeclaration()) + "\n"; 139 | if (s === out.length - 1) out = out.slice(0, s - 1) 140 | out += "\n" 141 | if (this.wfunc.body.id !== 'block') out += this.indentate(this.wfunc.body.type !== 'void' ? "return " + this.disassembleInstruction(this.wfunc.body) : this.disassembleInstruction(this.wfunc.body)) + ";\n" 142 | else { 143 | let len = this.wfunc.body.children.length; 144 | let end = -1; 145 | if (this.wfunc.body.type !== 'void') for (let i = len; i > 0; --i) { 146 | if (this.wfunc.body.children[i - 1].type !== 'void'){ 147 | end = i; 148 | break; 149 | } 150 | } 151 | let k = 0; 152 | for (const i of this.wfunc.body.children) { 153 | k++ 154 | if (!i) continue; 155 | let instr = this.disassembleInstruction(i); 156 | if (!instr) continue; 157 | if (k === end && this.wfunc.body.type !== 'void') instr = 'return ' + instr; 158 | out += this.indentate(instr) + ";\n" 159 | } 160 | } 161 | this.indent -= 1; 162 | if (s >= out.length-2) out = out.slice(0, s - 1) 163 | out += "}\n" 164 | 165 | return this.generateHeaderText() + out; 166 | } 167 | } 168 | // import binaryen from "binaryen" 169 | // import operations from "../operations.mjs" 170 | 171 | // export const disassembleInstruction = (wmod, instr) => { 172 | // switch (instr.id) { 173 | // case binaryen.BinaryId: 174 | // return operations[instr.op].replace(/\$/, disassembleInstruction) 175 | // case binaryen.UnaryId: 176 | // // return operations[] 177 | // } 178 | // } -------------------------------------------------------------------------------- /src/operations.mjs: -------------------------------------------------------------------------------- 1 | import binaryen from "binaryen"; 2 | 3 | export default { 4 | ["unary" + binaryen.ClzInt32]: "__clz_i32($)", 5 | ["unary" + binaryen.CtzInt32]: "__ctz_i32($)", 6 | ["unary" + binaryen.PopcntInt32]: "__popcnt_i32($)", 7 | ["unary" + binaryen.NegFloat32]: "(0.0 - $)", 8 | ["unary" + binaryen.AbsFloat32]: "__abs_f32($)", 9 | ["unary" + binaryen.CeilFloat32]: "__ceil_f32($)", 10 | ["unary" + binaryen.FloorFloat32]: "__floor_f32($)", 11 | ["unary" + binaryen.TruncFloat32]: "__trunc_f32($)", 12 | ["unary" + binaryen.NearestFloat32]: "__nearest_f32($)", 13 | ["unary" + binaryen.SqrtFloat32]: "__sqrt_f32($)", 14 | ["unary" + binaryen.EqZInt32]: "($ == 0x0)", 15 | ["unary" + binaryen.ClzInt64]: "__clz_i64($)", 16 | ["unary" + binaryen.CtzInt64]: "__ctz_i64($)", 17 | ["unary" + binaryen.PopcntInt64]: "__popcnt_i64($)", 18 | ["unary" + binaryen.NegFloat64]: "(0.0 - $)", 19 | ["unary" + binaryen.AbsFloat64]: "__abs_f64($)", 20 | ["unary" + binaryen.CeilFloat64]: "__ceil_f64($)", 21 | ["unary" + binaryen.FloorFloat64]: "__floor_f64($)", 22 | ["unary" + binaryen.TruncFloat64]: "__trunc_f64($)", 23 | ["unary" + binaryen.NearestFloat64]: "__nearest_f64($)", 24 | ["unary" + binaryen.SqrtFloat64]: "__sqrt_f64($)", 25 | ["unary" + binaryen.EqZInt64]: "($ == 0x0)", 26 | ["unary" + binaryen.ExtendSInt32]: "((long) $)", 27 | ["unary" + binaryen.ExtendUInt32]: "((unsigned long) $)", 28 | ["unary" + binaryen.WrapInt64]: "((int) $)", 29 | ["unary" + binaryen.TruncSFloat32ToInt32]: "((int) $)", 30 | ["unary" + binaryen.TruncSFloat32ToInt64]: "((long) $)", 31 | ["unary" + binaryen.TruncUFloat32ToInt32]: "((unsigned int) $)", 32 | ["unary" + binaryen.TruncUFloat32ToInt64]: "((unsigned long) $)", 33 | ["unary" + binaryen.TruncSFloat64ToInt32]: "((int) $)", 34 | ["unary" + binaryen.TruncSFloat64ToInt64]: "((long) $)", 35 | ["unary" + binaryen.TruncUFloat64ToInt32]: "((unsigned int) $)", 36 | ["unary" + binaryen.TruncUFloat64ToInt64]: "((unsigned long) $)", 37 | ["unary" + binaryen.TruncSatSFloat32ToInt32]: "((int) /* sat */ $)", 38 | ["unary" + binaryen.TruncSatSFloat32ToInt64]: "((long) /* sat */ $)", 39 | ["unary" + binaryen.TruncSatUFloat32ToInt32]: "((unsigned int) /* sat */ $)", 40 | ["unary" + binaryen.TruncSatUFloat32ToInt64]: "((unsigned long) /* sat */ $)", 41 | ["unary" + binaryen.TruncSatSFloat64ToInt32]: "((int) /* sat */ $)", 42 | ["unary" + binaryen.TruncSatSFloat64ToInt64]: "((long) /* sat */ $)", 43 | ["unary" + binaryen.TruncSatUFloat64ToInt32]: "((unsigned int) /* sat */ $)", 44 | ["unary" + binaryen.TruncSatUFloat64ToInt64]: "((unsigned long) /* sat */ $)", 45 | ["unary" + binaryen.ReinterpretFloat32]: "__reinterpret_f32/* bitwise reinterpret*/($)", 46 | ["unary" + binaryen.ReinterpretFloat64]: "__reinterpret_f64/* bitwise reinterpret*/($)", 47 | ["unary" + binaryen.ConvertSInt32ToFloat32]: "((int) $)", 48 | ["unary" + binaryen.ConvertSInt32ToFloat64]: "((long) $)", 49 | ["unary" + binaryen.ConvertUInt32ToFloat32]: "((unsigned int) $)", 50 | ["unary" + binaryen.ConvertUInt32ToFloat64]: "((unsigned long) $)", 51 | ["unary" + binaryen.ConvertSInt64ToFloat32]: "((int) $)", 52 | ["unary" + binaryen.ConvertSInt64ToFloat64]: "((long) $)", 53 | ["unary" + binaryen.ConvertUInt64ToFloat32]: "((unsigned int) $)", 54 | ["unary" + binaryen.ConvertUInt64ToFloat64]: "((unsigned long) $)", 55 | ["unary" + binaryen.PromoteFloat32]: "__promote_f32tof64($)", 56 | ["unary" + binaryen.DemoteFloat64]: "__demote_f64tof32($)", 57 | ["unary" + binaryen.ReinterpretInt32]: "__reinterpret_i32/* bitwise reinterpret */($)", 58 | ["unary" + binaryen.ReinterpretInt64]: "__reinterpret_i64/* bitwise reinterpret */($)", 59 | ["unary" + binaryen.ExtendS8Int32]: "((int) $)", 60 | ["unary" + binaryen.ExtendS16Int32]: "((int) $)", 61 | ["unary" + binaryen.ExtendS8Int64]: "((long) $)", 62 | ["unary" + binaryen.ExtendS16Int64]: "((long) $)", 63 | ["unary" + binaryen.ExtendS32Int64]: "((long) $)", 64 | ["binary" + binaryen.AddInt64]: "($ + #)", 65 | ["binary" + binaryen.AddFloat32]: "($ + #)", 66 | ["binary" + binaryen.AddFloat64]: "($ + #)", 67 | ["binary" + binaryen.AddInt32]: "($ + #)", 68 | ["binary" + binaryen.SubInt64]: "($ - #)", 69 | ["binary" + binaryen.SubFloat32]: "($ - #)", 70 | ["binary" + binaryen.SubFloat64]: "($ - #)", 71 | ["binary" + binaryen.SubInt32]: "($ - #)", 72 | ["binary" + binaryen.MulInt64]: "($ * #)", 73 | ["binary" + binaryen.MulFloat32]: "($ * #)", 74 | ["binary" + binaryen.MulFloat64]: "($ * #)", 75 | ["binary" + binaryen.MulInt32]: "($ * #)", 76 | ["binary" + binaryen.DivSInt64]: "($ / #)", 77 | ["binary" + binaryen.DivFloat32]: "($ / #)", 78 | ["binary" + binaryen.DivFloat64]: "($ / #)", 79 | ["binary" + binaryen.DivSInt32]: "($ / #)", 80 | ["binary" + binaryen.DivUInt32]: "((unsigned) $ / #)", 81 | ["binary" + binaryen.RemSInt64]: "($ % #)", 82 | ["binary" + binaryen.RemSInt32]: "($ % #)", 83 | ["binary" + binaryen.RemUInt32]: "((unsigned) $ % #)", 84 | ["binary" + binaryen.AndInt64]: "($ & #)", 85 | ["binary" + binaryen.AndInt32]: "($ & #)", 86 | ["binary" + binaryen.OrInt64]:"($ | #)", 87 | ["binary" + binaryen.OrInt32]: "($ | #)", 88 | ["binary" + binaryen.XorInt64]: "($ ^ #)", 89 | ["binary" + binaryen.XorInt32]: "($ ^ #)", 90 | ["binary" + binaryen.ShlInt64]: "($ << #)", 91 | ["binary" + binaryen.ShlInt32]: "($ << #)", 92 | ["binary" + binaryen.ShrUInt64]: "($ >>> #)", 93 | ["binary" + binaryen.ShrUInt32]: "($ >>> #)", 94 | ["binary" + binaryen.ShrSInt64]: "($ >> #)", 95 | ["binary" + binaryen.ShrSInt32]: "($ >> #)", 96 | ["binary" + binaryen.RotLInt32]: "__rotl_i32($, #)", 97 | ["binary" + binaryen.RotRInt32]: "__rotr_i32($, #)", 98 | ["binary" + binaryen.EqInt64]: "($ == #)", 99 | ["binary" + binaryen.EqFloat32]: "($ == #)", 100 | ["binary" + binaryen.EqFloat64]: "($ == #)", 101 | ["binary" + binaryen.EqInt32]: "($ == #)", 102 | ["binary" + binaryen.NeInt64]: "($ != #)", 103 | ["binary" + binaryen.NeFloat32]: "($ != #)", 104 | ["binary" + binaryen.NeFloat64]: "($ != #)", 105 | ["binary" + binaryen.NeInt32]: "($ != #)", 106 | ["binary" + binaryen.LtSInt64]: "($ < #)", 107 | ["binary" + binaryen.LtFloat64]: "($ < #)", 108 | ["binary" + binaryen.LtFloat32]: "($ < #)", 109 | ["binary" + binaryen.LtSInt32]: "($ < #)", 110 | ["binary" + binaryen.LtUInt64]: "((unsigned) $ < #)", 111 | ["binary" + binaryen.LtUInt32]: "((unsigned) $ < #)", 112 | ["binary" + binaryen.LeSInt64]: "($ <= #)", 113 | ["binary" + binaryen.LeFloat64]: "($ <= #)", 114 | ["binary" + binaryen.LeFloat32]: "($ <= #)", 115 | ["binary" + binaryen.LeSInt32]: "($ <= #)", 116 | ["binary" + binaryen.LeUInt64]: "((unsigned) $ <= #)", 117 | ["binary" + binaryen.LeUInt32]: "((unsigned) $ <= #)", 118 | ["binary" + binaryen.GtSInt64]: "($ > #)", 119 | ["binary" + binaryen.GtFloat64]: "($ > #)", 120 | ["binary" + binaryen.GtFloat32]: "($ > #)", 121 | ["binary" + binaryen.GtSInt32]: "($ > #)", 122 | ["binary" + binaryen.GtUInt64]: "((unsigned) $ > #)", 123 | ["binary" + binaryen.GtUInt32]: "((unsigned) $ > #)", 124 | ["binary" + binaryen.GeSInt64]: "($ >= #)", 125 | ["binary" + binaryen.GeFloat64]: "($ >= #)", 126 | ["binary" + binaryen.GeFloat32]: "($ >= #)", 127 | ["binary" + binaryen.GeSInt32]: "($ >= #)", 128 | ["binary" + binaryen.GeUInt64]: "((unsigned) $ >= #)", 129 | ["binary" + binaryen.GeUInt32]: "((unsigned) $ >= #)", 130 | ["binary" + binaryen.DivUInt64]: "((unsigned) $ / #)", 131 | ["binary" + binaryen.RemUInt64]: "((unsigned) $ % #)", 132 | ["binary" + binaryen.RotLInt64]: "__rotl_i64($, #)", 133 | ["binary" + binaryen.RotRInt64]: "__rotr_i64($, #)", 134 | ["binary" + binaryen.CopySignFloat32]: "__copy_sign_f32($, #)", 135 | ["binary" + binaryen.MinFloat32]: "__min_f32($, #)", 136 | ["binary" + binaryen.MaxFloat32]: "__max_f32($, #)", 137 | ["binary" + binaryen.CopySignFloat64]: "__copy_sign_f64($, #)", 138 | ["binary" + binaryen.MinFloat64]: "__min_f64($, #)", 139 | ["binary" + binaryen.MaxFloat64]: "__max_f64($, #)", 140 | } 141 | -------------------------------------------------------------------------------- /tests/minified/.gitignore: -------------------------------------------------------------------------------- 1 | *.wat -------------------------------------------------------------------------------- /tests/minified/emcc-wasi.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasmkit/diswasm/83572a921960704458fa5ae33a8a8f3c59ce7451/tests/minified/emcc-wasi.wasm -------------------------------------------------------------------------------- /tests/minified/emcc-web-2.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasmkit/diswasm/83572a921960704458fa5ae33a8a8f3c59ce7451/tests/minified/emcc-web-2.wasm -------------------------------------------------------------------------------- /tests/minified/emcc-web.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasmkit/diswasm/83572a921960704458fa5ae33a8a8f3c59ce7451/tests/minified/emcc-web.wasm -------------------------------------------------------------------------------- /tests/minified/rust-web.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasmkit/diswasm/83572a921960704458fa5ae33a8a8f3c59ce7451/tests/minified/rust-web.wasm -------------------------------------------------------------------------------- /tests/minified/rust-web2.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasmkit/diswasm/83572a921960704458fa5ae33a8a8f3c59ce7451/tests/minified/rust-web2.wasm -------------------------------------------------------------------------------- /tests/minified/unity.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasmkit/diswasm/83572a921960704458fa5ae33a8a8f3c59ce7451/tests/minified/unity.wasm -------------------------------------------------------------------------------- /tests/misc/smith.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasmkit/diswasm/83572a921960704458fa5ae33a8a8f3c59ce7451/tests/misc/smith.wasm --------------------------------------------------------------------------------