Skip to content

Commit e53bf15

Browse files
committed
module callgraph
1 parent 41f13cf commit e53bf15

16 files changed

+225
-15
lines changed

src/api/bytecode/ewasm/EWasmModule.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { WasmBinary } from "./WasmBinary";
2+
3+
export interface EWasmModule {
4+
binary: WasmBinary
5+
dotCallGraph: string
6+
}

src/api/bytecode/ewasm/FunctionBody.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ export const formatOpcodes = (opcodes: WasmOpcode[], importsPayload: WasmImportS
2323
let formattedCode = ''
2424
let indentation = 0
2525
for (const op of opcodes) {
26-
formattedCode += `${tab.repeat(indentation)}${op.opcode.name} ${op.immediates}`
26+
formattedCode += `${tab.repeat(indentation)} <${op.depth}> ${op.opcode.name} ${op.immediates}`
2727
if (WasmOpcodes.isCall(op)) {
2828
const callIdx: number = parseInt(op.immediates[0], 16)
2929
let functionCalledName = ''
3030
if (callIdx < importsPayload.imports.length) {
3131
const imp = importsPayload.imports[callIdx];
32-
functionCalledName = `${imp.moduleName}.${imp.fieldName}`
32+
functionCalledName = `${imp.moduleName}_${imp.fieldName}`
3333
} else {
3434
const fun = codePayload.functions[callIdx]
3535
if (fun) {

src/api/bytecode/ewasm/OpcodeImmediateType.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export enum OpcodeImmediateType {
22

33
U32, // varuint32
4-
I32, // varuint32
4+
I32, // varint32
55
I64, // varint64
66
BYTE,
77
VECTOR_U32

src/api/bytecode/ewasm/WasmBinaryParser.ts

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { WasmSection, WasmTypeSectionPayload, WasmSectionPayload, WasmExportSect
55
import { WasmSectionType, WasmType, WasmValueType, getWasmValueType, getExternalType, WasmExternalKind } from "./wasmTypes";
66
import { FuncType, printSignature } from "./FuncType";
77
import { FunctionBody, FunctionLocal, formatOpcodes } from "./FunctionBody";
8-
import { WasmOpcode, WasmOpcodeDefinition, WasmOpcodes, Immediate } from "./WasmOpcodes";
8+
import { WasmOpcode, WasmOpcodeDefinition, WasmOpcodes, Immediate, BlockType } from "./WasmOpcodes";
99
import { OpcodeImmediateType } from "./OpcodeImmediateType";
1010

1111

@@ -59,6 +59,16 @@ export class WasmBinaryParser {
5959
};
6060
const wasmPostProcessed = this.postProcess(wasmBinary);
6161
// console.log(JSON.stringify(wasmPostProcessed))
62+
const cod: WasmCodeSectionPayload = findSection(wasmPostProcessed.sections, WasmSectionType.Code).payload as WasmCodeSectionPayload
63+
let i = 0
64+
let outp = ''
65+
for (const o of cod.functions) {
66+
outp += `== ${i} == \n`
67+
outp += o.formattedOpcodes
68+
i++
69+
}
70+
const fs = require('fs')
71+
fs.writeFileSync('./output.txt', outp)
6272
return wasmPostProcessed
6373
}
6474

@@ -99,8 +109,6 @@ export class WasmBinaryParser {
99109
for(const fun of codeSectionPayload.functions) {
100110
const formattedOpcodes = formatOpcodes(fun.opcodes, importSectionPayload, codeSectionPayload)
101111
fun.formattedOpcodes = formattedOpcodes
102-
// deleting opcodes for now, not really needed in the response, maybe in the future
103-
delete fun.opcodes
104112
}
105113
return wasmBinary
106114
}
@@ -282,6 +290,8 @@ export class WasmBinaryParser {
282290
parseFunctionBytecode(bytecode: Buffer): WasmOpcode[] {
283291
const reader = new BytesReader(bytecode)
284292
const opcodes: WasmOpcode[] = []
293+
let depth = 0
294+
let blockType: BlockType = BlockType.NONE
285295
while(!reader.finished()) {
286296
const opcodeByte = reader.readBytesToNumber(1)
287297
const immediates: string[] = []
@@ -321,10 +331,35 @@ export class WasmBinaryParser {
321331
immediates.push(valueFormatted)
322332
}
323333
}
324-
opcodes.push({
334+
const newOpcode = {
325335
opcode: opcodeDefinition,
326-
immediates
327-
})
336+
immediates,
337+
depth,
338+
blockType
339+
};
340+
opcodes.push(newOpcode)
341+
if(WasmOpcodes.isBlockEnd(newOpcode)) {
342+
depth--
343+
}
344+
if(WasmOpcodes.isBlockStart(newOpcode)) {
345+
depth++
346+
}
347+
// if(newOpcode.opcode.name === 'block') {
348+
// blockType = BlockType.BLOCK
349+
// }
350+
// if (newOpcode.opcode.name === 'loop') {
351+
// blockType = BlockType.LOOP
352+
// }
353+
// if(newOpcode.opcode.name === 'if') {
354+
// blockType = BlockType.IF
355+
// }
356+
// if(newOpcode.opcode.name === 'else') {
357+
// blockType = BlockType.ELSE
358+
// }
359+
// if(depth === 0) {
360+
// blockType = BlockType.NONE
361+
// }
362+
328363
}
329364
return opcodes
330365
}

src/api/bytecode/ewasm/WasmOpcodes.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,11 @@ export class WasmOpcodes {
215215
static isCall(opcode: WasmOpcode): boolean {
216216
return opcode.opcode.name === 'call'
217217
}
218+
219+
static isDirectBranch(opcode: WasmOpcode): boolean {
220+
return opcode.opcode.name === 'br' ||
221+
opcode.opcode.name === 'br_if'
222+
}
218223
}
219224

220225
export interface WasmOpcodeDefinition {
@@ -227,10 +232,20 @@ export interface Immediate {
227232
type: OpcodeImmediateType
228233
}
229234

235+
export enum BlockType {
236+
NONE,
237+
BLOCK,
238+
LOOP,
239+
IF,
240+
ELSE
241+
}
242+
230243

231244
export interface WasmOpcode {
232245
opcode: WasmOpcodeDefinition
233246
immediates: string[]
247+
depth: number
248+
blockType?: BlockType
234249
}
235250

236251
WasmOpcodes.populate()
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { WasmCallGraphFunction } from "./WasmCallGraphFunction";
2+
3+
export interface WasmCallGraph {
4+
functions: Map<number, WasmCallGraphFunction>
5+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface WasmCallGraphFunction {
2+
name: string
3+
calling: number[]
4+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { injectable } from "inversify";
2+
import { FunctionBody } from "../FunctionBody";
3+
import { WasmOpcodes } from "../WasmOpcodes";
4+
import { WasmBinary } from "../WasmBinary";
5+
import { findSection, WasmCodeSectionPayload, WasmImportSectionPayload } from "../WasmSection";
6+
import { WasmSectionType } from "../wasmTypes";
7+
import { WasmCallGraph } from "./WasmCallGraph";
8+
9+
@injectable()
10+
export class WasmCallgraphCreator {
11+
12+
createCallgraph(wasm: WasmBinary): WasmCallGraph {
13+
const callGraph: WasmCallGraph = {
14+
functions : new Map()
15+
}
16+
let offset = 0
17+
const functions: FunctionBody[] = (findSection(wasm.sections, WasmSectionType.Code).payload as WasmCodeSectionPayload).functions
18+
const importsSection = findSection(wasm.sections, WasmSectionType.Import)
19+
if (importsSection) {
20+
const importsPayload: WasmImportSectionPayload = importsSection.payload as WasmImportSectionPayload
21+
if (importsPayload.imports.length > 0) {
22+
importsPayload.imports.forEach((imp, index) => {
23+
offset++
24+
callGraph.functions.set(index, {
25+
calling: [],
26+
name: `${imp.moduleName}_${imp.fieldName}`
27+
})
28+
})
29+
}
30+
}
31+
let funIndex = 0
32+
for(const fun of functions) {
33+
const funCalling: number[] = []
34+
fun.opcodes.filter(op => WasmOpcodes.isCall(op)).forEach(op => {
35+
const callee: number = parseInt(op.immediates[0], 16)
36+
if(!funCalling.includes(callee)) {
37+
funCalling.push(callee)
38+
}
39+
})
40+
callGraph.functions.set(funIndex + offset, {
41+
calling: funCalling,
42+
name: fun.exportedName? fun.exportedName : fun.name
43+
})
44+
funIndex++
45+
}
46+
return callGraph
47+
}
48+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { WasmCallGraph } from "./WasmCallGraph";
2+
import { injectable } from "inversify";
3+
4+
@injectable()
5+
export class WasmGraphVizService {
6+
7+
convertToDot(callGraph: WasmCallGraph): string {
8+
let graph = `digraph " " {
9+
graph [splines=ortho]
10+
node[shape=plain style=filled fontname="Courier"]
11+
`
12+
13+
graph+= `
14+
${this.createBody(callGraph)}
15+
}`
16+
console.log(graph)
17+
return graph
18+
}
19+
20+
private createBody(callGraph: WasmCallGraph): string {
21+
let body = ''
22+
callGraph.functions.forEach((value, key) => {
23+
body += `${key} [label=${value.name}]
24+
${this.createRelations(value.calling, key)}
25+
`
26+
27+
})
28+
return body
29+
}
30+
31+
private createRelations(calling: number[], index: number): string {
32+
let relations = ''
33+
relations += calling.map(c => `${index} -> ${c}`).join('\n')
34+
return relations
35+
}
36+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { WasmOpcode } from "../WasmOpcodes";
2+
3+
export interface WasmCFGBlock {
4+
opcodes: WasmOpcode[]
5+
6+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { injectable } from "inversify";
2+
import { WasmOpcode } from "../WasmOpcodes";
3+
4+
@injectable()
5+
export class WasmCFGCreator {
6+
7+
createFunctionCfg(opcodes: WasmOpcode[]) {
8+
9+
}
10+
}

src/api/bytecode/ewasm/te.ts

Lines changed: 12 additions & 1 deletion
Large diffs are not rendered by default.

src/api/service/controller/EwasmController.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { EwasmService } from "../service/EwasmService";
66
import { WasmBinary } from "../../bytecode/ewasm/WasmBinary";
77
import { Web3Configuration } from "../../blockchain/Web3Configuration";
88
import { logger } from '../../../Logger'
9+
import { EWasmModule } from "../../bytecode/ewasm/EWasmModule";
910

1011
@Route('ewasm')
1112
@provideSingleton(EwasmController)
@@ -36,7 +37,7 @@ export class EwasmController extends Controller {
3637
}
3738

3839
@Post('analyze')
39-
async analyze(@Body() source: StringBodyRequest ): Promise<WasmBinary> {
40+
async analyze(@Body() source: StringBodyRequest ): Promise<EWasmModule> {
4041
try {
4142
return this.ewasmService.analyze(source.request)
4243
} catch(error) {

src/api/service/service/EwasmService.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ import { WasmBinary } from "../../bytecode/ewasm/WasmBinary";
55
import { Web3Configuration } from "../../blockchain/Web3Configuration";
66
import { IWeb3 } from "../../blockchain/IWeb3";
77
import { Web3Instance } from "../../blockchain/Web3Instance";
8+
import { findSection, WasmCodeSectionPayload } from "../../bytecode/ewasm/WasmSection";
9+
import { WasmSectionType } from "../../bytecode/ewasm/wasmTypes";
10+
import { WasmCFGCreator } from "../../bytecode/ewasm/cfg/WasmCFGCreator";
11+
import { EWasmModule } from "../../bytecode/ewasm/EWasmModule";
12+
import { WasmCallgraphCreator } from "../../bytecode/ewasm/callgraph/WasmCallgraphCreator";
13+
import { WasmCallGraph } from "../../bytecode/ewasm/callgraph/WasmCallGraph";
14+
import { WasmGraphVizService } from "../../bytecode/ewasm/callgraph/WasmGraphVizService";
815

916
const wabt = require("wabt")()
1017
const commandExists = require('command-exists').sync
@@ -15,20 +22,37 @@ var fs = require('fs')
1522
@injectable()
1623
export class EwasmService {
1724

18-
constructor(@inject(TYPES.WasmBinaryParser) private wasmParser: WasmBinaryParser) {}
25+
constructor(
26+
@inject(TYPES.WasmBinaryParser) private wasmParser: WasmBinaryParser,
27+
@inject(TYPES.WasmCFGCreator) private cfgCreator: WasmCFGCreator,
28+
@inject(TYPES.WasmCallgraphCreator) private callGraphCreator: WasmCallgraphCreator,
29+
@inject(TYPES.WasmGraphVizService) private wasmGraphVizService: WasmGraphVizService
30+
) {}
1931

20-
analyze(codeInHex: string): WasmBinary {
32+
analyze(codeInHex: string): EWasmModule {
2133
const wasm: Buffer = this.hexToBuffer(codeInHex)
2234
try {
2335
const binary: WasmBinary = this.wasmParser.parse(wasm)
24-
return binary
36+
const callGraph: WasmCallGraph = this.callGraphCreator.createCallgraph(binary)
37+
const dotCallGraph: string = this.wasmGraphVizService.convertToDot(callGraph)
38+
39+
// removeme
40+
// const sec = findSection(binary.sections, WasmSectionType.Code)
41+
// const pay: WasmCodeSectionPayload = sec.payload as WasmCodeSectionPayload
42+
// const f2 = pay.functions[2]
43+
// this.cfgCreator.createFunctionCfg(f2.opcodes)
44+
// end removeme
45+
return {
46+
binary,
47+
dotCallGraph
48+
}
2549
} catch (error) {
2650
console.log(error)
2751
throw new Error(`Error when analyzing ewasm bytecode: ${error.message}`)
2852
}
2953
}
3054

31-
async analyzeAddress(address: string, config: Web3Configuration): Promise<WasmBinary> {
55+
async analyzeAddress(address: string, config: Web3Configuration): Promise<EWasmModule> {
3256
const iWeb3: IWeb3 = new Web3Instance(config)
3357
const web3 = iWeb3.getInstance()
3458
let contractCode = await web3.eth.getCode(address)

src/inversify/ioc.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import { ContractService } from '../api/service/service/ContractService';
2222
import { Solc } from '../api/service/service/Solc';
2323
import { EwasmService } from '../api/service/service/EwasmService';
2424
import { WasmBinaryParser } from '../api/bytecode/ewasm/WasmBinaryParser';
25+
import { WasmCFGCreator } from '../api/bytecode/ewasm/cfg/WasmCFGCreator';
26+
import { WasmCallgraphCreator } from '../api/bytecode/ewasm/callgraph/WasmCallgraphCreator';
27+
import { WasmGraphVizService } from '../api/bytecode/ewasm/callgraph/WasmGraphVizService';
2528

2629
const iocContainer = new Container()
2730
const provide = makeProvideDecorator(iocContainer)
@@ -43,6 +46,9 @@ iocContainer.bind<ContractService>(TYPES.ContractService).to(ContractService)
4346
iocContainer.bind<WasmBinaryParser>(TYPES.WasmBinaryParser).to(WasmBinaryParser)
4447
iocContainer.bind<Solc>(TYPES.Solc).to(Solc)
4548
iocContainer.bind<EwasmService>(TYPES.EwasmService).to(EwasmService)
49+
iocContainer.bind<WasmCFGCreator>(TYPES.WasmCFGCreator).to(WasmCFGCreator)
50+
iocContainer.bind<WasmCallgraphCreator>(TYPES.WasmCallgraphCreator).to(WasmCallgraphCreator)
51+
iocContainer.bind<WasmGraphVizService>(TYPES.WasmGraphVizService).to(WasmGraphVizService)
4652

4753
const provideNamed = (
4854
identifier: string | symbol | interfaces.Newable<any> | interfaces.Abstract<any>,

src/inversify/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ const TYPES = {
1212
ContractService: Symbol.for('ContractService'),
1313
EwasmService: Symbol.for('EwasmService'),
1414
WasmBinaryParser: Symbol.for('WasmBinaryParser'),
15+
WasmCFGCreator: Symbol.for('WasmCFGCreator'),
16+
WasmCallgraphCreator: Symbol.for('WasmCallgraphCreator'),
17+
WasmGraphVizService: Symbol.for('WasmGraphVizService'),
1518
Solc: Symbol.for('Solc')
1619
}
1720

0 commit comments

Comments
 (0)