Skip to content

Commit 60ddcf0

Browse files
committed
Initial ewasm parsing
1 parent 9d3586c commit 60ddcf0

25 files changed

+1041
-29
lines changed

package-lock.json

Lines changed: 401 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,13 @@
7575
"dependencies": {
7676
"@babel/runtime": "^7.7.7",
7777
"@types/express": "^4.16.0",
78+
"@webassemblyjs/leb128": "^1.8.5",
79+
"@webassemblyjs/wasm-parser": "^1.8.5",
7880
"async-polling": "^0.2.1",
7981
"bn.js": "^4.11.8",
8082
"body-parser": "^1.18.3",
8183
"brace": "^0.11.1",
84+
"command-exists": "^1.2.8",
8285
"copyfiles": "^2.1.0",
8386
"d3": "^5.7.0",
8487
"d3-graphviz": "^2.6.0",
@@ -101,7 +104,9 @@
101104
"reflect-metadata": "^0.1.12",
102105
"solc": "^0.5.8",
103106
"terser": "3.14.1",
107+
"tmp": "^0.1.0",
104108
"tsoa": "2.3.81",
109+
"wabt": "^1.0.12",
105110
"web3": "1.0.0-beta.37",
106111
"winston": "^2.2.0"
107112
},
@@ -135,6 +140,7 @@
135140
"sinon": "^8.0.2",
136141
"style-loader": "^0.23.1",
137142
"ts-jest": "^22.4.6",
143+
"ts-node": "^8.5.4",
138144
"tslint-config-prettier": "^1.14.0",
139145
"typescript": "^3.1.3",
140146
"webpack": "^4.25.1",

src/api/bytecode/EVMDisassembler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Disassembler } from './Disassembler'
22
import { Operation } from './Operation'
3-
import { Opcodes } from './Opcodes'
3+
import { EVMOpcodes } from './EVMOpcodes'
44
import { injectable, inject } from 'inversify'
55
import { Opcode } from './Opcode'
66
import { DisassembledContract } from './DisassembledContract'
@@ -106,7 +106,7 @@ export class EVMDisassembler implements Disassembler {
106106

107107
for (let i = 0; i < operations.length; i++) {
108108
const code = operations[i]
109-
const opcode: Opcode = Opcodes.opcodes[parseInt(code, 16)] || Opcodes.opcodes[-1]
109+
const opcode: Opcode = EVMOpcodes.opcodes[parseInt(code, 16)] || EVMOpcodes.opcodes[-1]
110110
if (this.isPush(opcode)) {
111111
const parameters = opcode.parameters
112112
const argument = `${operations.slice(i + 1, i + parameters + 1).join('')}`
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Opcode } from './Opcode'
22

3-
export class Opcodes {
3+
export class EVMOpcodes {
44
static opcodes = {}
55

66
static populate() {
@@ -161,4 +161,4 @@ export class Opcodes {
161161
}
162162
}
163163

164-
Opcodes.populate()
164+
EVMOpcodes.populate()
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
export class BytesReader {
2+
3+
private pointer: number
4+
private buffer: Buffer
5+
6+
constructor(buffer: Buffer) {
7+
this.buffer = buffer
8+
this.pointer = 0
9+
}
10+
11+
readBytes(bytesNumber: number): Buffer {
12+
const result = this.buffer.slice(this.pointer, bytesNumber + this.pointer)
13+
this.pointer += bytesNumber
14+
return result
15+
}
16+
17+
readBytesToHex(bytesNumber: number): string {
18+
const result = this.readBytes(bytesNumber)
19+
return result.toString('hex')
20+
}
21+
22+
readBytesToNumber(bytesNumber: number): number {
23+
const result = this.readBytesToHex(bytesNumber)
24+
return parseInt(result, 16)
25+
}
26+
27+
readBytesToUtf8String(bytesNumber: number): string {
28+
const result = this.readBytes(bytesNumber)
29+
return result.toString('utf8')
30+
}
31+
32+
readVarUint32(): number {
33+
var result = 0
34+
var shift = 0
35+
while (true) {
36+
var byte = parseInt(this.readBytesToHex(1), 16)
37+
result |= (byte & 0x7F) << shift
38+
shift += 7
39+
if ((byte & 0x80) === 0){
40+
break
41+
}
42+
}
43+
return result
44+
}
45+
46+
private readVarInt32(): number {
47+
var result = 0
48+
var shift = 0
49+
while (true) {
50+
var byte = parseInt(this.readBytesToHex(1), 16)
51+
result |= (byte & 0x7F) << shift
52+
shift += 7
53+
if ((byte & 0x80) === 0) {
54+
break;
55+
}
56+
}
57+
if (shift >= 32) {
58+
return result
59+
}
60+
var ashift = (32 - shift)
61+
return (result << ashift) >> ashift
62+
}
63+
64+
getPointer(): number {
65+
return this.pointer
66+
}
67+
68+
finished(): boolean {
69+
return this.pointer >= this.buffer.length
70+
}
71+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { WasmExternalKind } from "./wasmTypes";
2+
3+
export interface ExportEntry {
4+
name: string
5+
kind: WasmExternalKind
6+
index: number
7+
}

src/api/bytecode/ewasm/FuncType.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { WasmValueType } from "./wasmTypes";
2+
3+
export interface FuncType {
4+
index: number
5+
params: WasmValueType[]
6+
results: WasmValueType[]
7+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { WasmValueType } from "./wasmTypes";
2+
3+
export interface FunctionBody {
4+
locals: FunctionLocal[]
5+
bytecodeHex: string
6+
}
7+
8+
export interface FunctionLocal {
9+
count: number
10+
valueType: WasmValueType
11+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { WasmSection } from "./WasmSection";
2+
3+
export interface WasmBinary {
4+
sections: WasmSection[]
5+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import { injectable } from "inversify";
2+
import { WasmBinary } from "./WasmBinary";
3+
import { BytesReader } from "./BytesReader";
4+
import { WasmSection, WasmTypeSectionPayload, WasmSectionPayload, WasmExportSectionPayload, WasmCodeSectionPayload } from "./WasmSection";
5+
import { WasmSectionType, WasmType, WasmValueType, getWasmValueType, getExternalType } from "./wasmTypes";
6+
import { FuncType } from "./FuncType";
7+
import { FunctionBody, FunctionLocal } from "./FunctionBody";
8+
9+
10+
@injectable()
11+
export class WasmBinaryParser {
12+
13+
private readonly WASM_MAGIC_NUMBER = '0061736d'
14+
private readonly WASM_V1 = '01000000'
15+
16+
private sectionParsers: Map<number, Function> = new Map<number, Function>()
17+
18+
constructor() {
19+
this.sectionParsers.set(WasmSectionType.Type, this.parseTypeSection)
20+
this.sectionParsers.set(WasmSectionType.Export, this.parseExportSection)
21+
this.sectionParsers.set(WasmSectionType.Code, this.parseCodeSection)
22+
}
23+
24+
parse(binary: Buffer): WasmBinary {
25+
const reader: BytesReader = new BytesReader(binary)
26+
27+
const magicNumberRead: string = reader.readBytesToHex(4)
28+
29+
if(magicNumberRead !== this.WASM_MAGIC_NUMBER) {
30+
throw new Error(`WASM Magic number not found`)
31+
}
32+
const version = reader.readBytesToHex(4)
33+
34+
if(version !== this.WASM_V1) {
35+
throw new Error(`WASM binary version=${version}, supported=${this.WASM_V1}`)
36+
}
37+
38+
const wasmSections: WasmSection[] = []
39+
while(!reader.finished()) {
40+
const sectionId = reader.readBytesToNumber(1)
41+
const sectionType = WasmSectionType[sectionId.toString()]
42+
const payloadLength = reader.readVarUint32()
43+
const payloadData = reader.readBytes(payloadLength)
44+
const payloadHex = payloadData.toString('hex')
45+
const payload = this.parseSectionPayload(payloadData, sectionId)
46+
wasmSections.push({
47+
sectionType,
48+
payloadLength,
49+
payloadData,
50+
payloadHex,
51+
payload
52+
})
53+
}
54+
console.log(JSON.stringify(wasmSections))
55+
return
56+
}
57+
58+
parseSectionPayload(payload: Buffer, sectionId: number): WasmSectionPayload {
59+
const sectionParser = this.sectionParsers.get(sectionId)
60+
if(!sectionParser) {
61+
return
62+
}
63+
return sectionParser(payload, this)
64+
}
65+
66+
parseTypeSection(payload: Buffer, self: any): WasmTypeSectionPayload {
67+
const reader = new BytesReader(payload)
68+
69+
// parsing Vector - refactor
70+
const sectionPayload: WasmTypeSectionPayload = {
71+
functions : []
72+
}
73+
const numberOfElements = reader.readVarUint32()
74+
let index = 0;
75+
while(index < numberOfElements) {
76+
const funcType: FuncType = self.parseFuncType(reader, index)
77+
sectionPayload.functions.push(funcType)
78+
index++
79+
}
80+
return sectionPayload
81+
}
82+
83+
parseExportSection(payload: Buffer, self: any): WasmExportSectionPayload {
84+
const reader = new BytesReader(payload)
85+
// parsing exports vector
86+
const numberOfElements = reader.readVarUint32()
87+
let exportsCounter = 0
88+
const exportSection: WasmExportSectionPayload = {
89+
exports: []
90+
}
91+
while(exportsCounter < numberOfElements) {
92+
const strLength = reader.readVarUint32()
93+
const name = reader.readBytesToUtf8String(strLength)
94+
const exportKind = reader.readBytesToNumber(1)
95+
const kind = getExternalType(exportKind.toString())
96+
const index = reader.readVarUint32()
97+
exportSection.exports.push({
98+
name,
99+
index,
100+
kind
101+
})
102+
exportsCounter++
103+
}
104+
return exportSection
105+
}
106+
107+
parseCodeSection(payload: Buffer, self: any): WasmCodeSectionPayload {
108+
const reader = new BytesReader(payload)
109+
const numberOfElements = reader.readVarUint32()
110+
const codeSection: WasmCodeSectionPayload = {
111+
functions: []
112+
}
113+
console.log(`ParsingCodeSection, noElements=${numberOfElements}`)
114+
let bodiesCounter = 0
115+
while(bodiesCounter < numberOfElements) {
116+
const functionBody = self.parseFunctionCode(reader)
117+
codeSection.functions.push(functionBody)
118+
bodiesCounter++
119+
}
120+
return codeSection
121+
}
122+
123+
parseFunctionCode(reader: BytesReader): FunctionBody {
124+
125+
const bodySize = reader.readVarUint32()
126+
const body = reader.readBytes(bodySize)
127+
return this.parseFunctionBody(body)
128+
}
129+
130+
parseFunctionBody(body: Buffer): FunctionBody {
131+
const reader = new BytesReader(body)
132+
const numberOfLocals = reader.readVarUint32()
133+
const locals: FunctionLocal[] = []
134+
let localsCounter = 0
135+
while(localsCounter < numberOfLocals) {
136+
const count = reader.readVarUint32()
137+
const valueType = getWasmValueType(reader.readBytesToNumber(1).toString())
138+
139+
locals.push({
140+
count,
141+
valueType
142+
})
143+
localsCounter++
144+
}
145+
const functionInstructions = reader.readBytes(body.length - reader.getPointer())
146+
const bytecodeHex = functionInstructions.toString('hex')
147+
return {
148+
bytecodeHex,
149+
locals
150+
}
151+
}
152+
153+
parseFuncType(reader: BytesReader, index: number): FuncType {
154+
const typeByte = reader.readBytesToNumber(1)
155+
if(typeByte !== WasmType.FunctionType) {
156+
throw new Error(`Error parsing FuncType - type=${typeByte} not function `)
157+
}
158+
// reading params
159+
const params: WasmValueType[] = []
160+
const numParams = reader.readVarUint32()
161+
let paramsCounter = 0
162+
while(paramsCounter < numParams) {
163+
const paramByte = reader.readBytesToNumber(1)
164+
params.push(getWasmValueType(paramByte.toString()))
165+
paramsCounter++
166+
}
167+
// reading results
168+
const results: WasmValueType[] = []
169+
const numResults = reader.readVarUint32()
170+
let resultsCounter = 0
171+
while(resultsCounter < numResults) {
172+
const resultByte = reader.readBytesToNumber(1)
173+
results.push(getWasmValueType(resultByte.toString()))
174+
resultsCounter++
175+
}
176+
return {
177+
index,
178+
params,
179+
results
180+
}
181+
}
182+
183+
}

0 commit comments

Comments
 (0)