Skip to content

Commit b4655fe

Browse files
committed
Added bytecode .evm extension, show jumps within a block in CFG
1 parent 72d7548 commit b4655fe

File tree

10 files changed

+66
-22
lines changed

10 files changed

+66
-22
lines changed

README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ There are already tools that allow you to debug Ethereum transactions (Solidity)
1616

1717
# Usage
1818

19+
### Download
20+
1921
Use one of these releases:
2022

21-
* solc 0.4.24 compatible with ganache use: [v2.1.0](https://github.com/fergarrui/ethereum-graph-debugger/releases/tag/v2.1.0)
23+
* solc 0.4.24 compatible with ganache use: [v2.2.0](https://github.com/fergarrui/ethereum-graph-debugger/releases/tag/v2.2.0)
2224
* solc 0.5.8 (not compatible with ganache) use: [v3.0.2](https://github.com/fergarrui/ethereum-graph-debugger/releases/tag/v3.0.2)
2325

2426
If you want to use master (it can be more unstable), clone and start the application
@@ -38,6 +40,20 @@ npm start
3840

3941
Go to localhost:9090
4042

43+
### Use
44+
45+
* Go to localhost:9090
46+
* Enter the path where the contracts are in the input text (it will load Solidity contracts recursively)
47+
* A tab per file found will be created
48+
* Under a file tab there are a few actions using the left menu
49+
50+
### How to debug bytecode (with no source code) [Experimental]
51+
52+
* Create a file with extension `.evm` and paste the runtime bytecode (:warning: important: with `0x` as prefix)
53+
* For example: create a file named: `contract1.evm` with content `0x60806040`
54+
* Scan the directory as described above
55+
* You won't get source code mappings when clicking in operations of the CFG
56+
4157
# Features
4258

4359
* Now interactive :star2:: it has a sepparate frontend and API instead of building a static HTML file like in earlier versions
@@ -49,6 +65,7 @@ Go to localhost:9090
4965
* EVM state in transaction: it is shown below the editor when selecting an opcode present in the execution trace of the provided transaction hash
5066
* Settings: right now, there are settings to point to a different chain (by default it connects to http://127.0.0.1:8545) and basic authentication can be configured, so the RPC endpoint can be accessed if authentication is needed (to be compatible with platforms like [Kaleido](http://kaleido.io))
5167
* When building the CFG a basic dynamic execution is made to calculate jumps and to remove most of orphan blocks (this will be improved in the future, probably with SymExec)
68+
* To debug directly bytecode, use `.evm` extension files
5269

5370
# Limitations/Considerations
5471

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "ethereum-graph-debugger",
33
"author": "Fernando Garcia",
44
"license": "GPL",
5-
"version": "2.1.0",
5+
"version": "2.2.0",
66
"description": "Ethereum graph debugger",
77
"main": "dist/run-server.js",
88
"scripts": {

src/api/bytecode/EVMDisassembler.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,17 @@ export class EVMDisassembler implements Disassembler {
4141
}
4242

4343
disassembleContract(bytecode: string): DisassembledContract {
44-
let code = bytecode
44+
let code = bytecode.trim()
4545

4646
if (bytecode.startsWith('0x')) {
4747
code = bytecode.slice(2)
4848
}
49-
49+
if (code.includes(EVMDisassembler.metadataPrefix)) {
50+
code = code.split(EVMDisassembler.metadataPrefix)[0]
51+
}
52+
5053
if (code.length % 2 !== 0) {
51-
throw new Error(`Bad input, bytecode length not even: ${code}`)
54+
throw new Error(`Bad input, bytecode length not even: ${code}, length: ${code.length}`)
5255
}
5356

5457
const operations: Operation[] = this.disassembleBytecode(bytecode)
@@ -70,7 +73,7 @@ export class EVMDisassembler implements Disassembler {
7073
}
7174

7275
disassembleBytecode(bytecode: string): Operation[] {
73-
let code = bytecode
76+
let code = bytecode.trim()
7477

7578
if (bytecode.startsWith('0x')) {
7679
code = bytecode.slice(2)
@@ -79,9 +82,8 @@ export class EVMDisassembler implements Disassembler {
7982
if (code.includes(EVMDisassembler.metadataPrefix)) {
8083
code = code.split(EVMDisassembler.metadataPrefix)[0]
8184
}
82-
8385
if (code.length % 2 !== 0) {
84-
throw new Error(`Bad input, bytecode length not even: ${code}`)
86+
throw new Error(`Bad input, bytecode length not even: ${code}, length: ${code.length}`)
8587
}
8688
let offset = 0
8789
const operations = code.match(/.{1,2}/g)

src/api/cfg/CFGBlocks.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,22 @@ export class CFGBlocks {
77
this.blocks[offset] = block
88
}
99

10+
// getBlock(offset: number): OperationBlock {
11+
// return this.blocks[offset]
12+
// }
13+
1014
get(offset: number): OperationBlock {
11-
return this.blocks[offset]
15+
const block: OperationBlock = this.blocks[offset]
16+
if(!block) {
17+
for (const key of Object.keys(this.blocks)) {
18+
const b: OperationBlock = this.blocks[key]
19+
const found = b.operations.find(op => op.offset === offset)
20+
if (found) {
21+
return b
22+
}
23+
}
24+
}
25+
return block
1226
}
1327

1428
keys(): number[] {

src/api/service/service/CFGService.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,15 @@ export class CFGService {
1818
) {}
1919

2020
buildCFGFromSource(contractName: string, source: string, path: string): CFGContract {
21-
const contract: DisassembledContract = this.disassembler.disassembleSourceCode(contractName, source, path)
22-
return this.buildCfgContract(contract)
21+
let contract: DisassembledContract
22+
if(source.startsWith('0x')) {
23+
const cfg: CFGContract = this.buildCFGFromBytecode(source)
24+
cfg.contractRuntime.rawBytecode = source
25+
return cfg
26+
} else {
27+
contract = this.disassembler.disassembleSourceCode(contractName, source, path)
28+
return this.buildCfgContract(contract)
29+
}
2330
}
2431

2532
buildCFGFromBytecode(bytecode: string): CFGContract {

src/api/service/service/FileServiceDefault.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ var fs = require('fs')
88
@injectable()
99
export class FileServiceDefault implements FileService {
1010
async findContractssWithExtension(dir: string, extension: string): Promise<ContractFile[]> {
11-
const files = await recursive(dir, [`!*.${extension}`])
11+
const files = await recursive(dir, [`!*.{${extension},evm}`])
1212

1313
return await files
1414
.map(file => {

src/api/symbolic/evm/EVMExecutor.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export class EVMExecutor {
1111
evm: EVM
1212
blocks: CFGBlocks
1313
executor: OpcodeExecutor
14+
alreadyRunOffsets: number[] = []
1415

1516
constructor(blocks: CFGBlocks, executor: OpcodeExecutor) {
1617
this.evm = new EVM()
@@ -24,6 +25,7 @@ export class EVMExecutor {
2425
throw new Error(`Could not find block with offset ${offset}`)
2526
}
2627
this.runBlock(block)
28+
this.alreadyRunOffsets.push(offset)
2729
const nextBlocks: OperationBlock[] = this.findNextBlocks(block)
2830
for (const nextBlock of nextBlocks) {
2931
if (block.childA !== nextBlock.offset && block.childB !== nextBlock.offset) {
@@ -57,15 +59,17 @@ export class EVMExecutor {
5759
const jumpLocation = this.evm.nextJumpLocation
5860
this.evm.nextJumpLocation = undefined
5961
if (jumpLocation && !jumpLocation.isSymbolic) {
60-
const locationBlock: OperationBlock = this.blocks.get(jumpLocation.value.toNumber())
61-
if (locationBlock) {
62+
const nextOffset = jumpLocation.value.toNumber()
63+
const locationBlock: OperationBlock = this.blocks.get(nextOffset)
64+
if (locationBlock && !this.alreadyRunOffsets.includes(nextOffset)) {
6265
nextBlocks.push(locationBlock)
6366
}
6467
}
6568
}
6669
if (!this.NO_NEXT_BLOCK.includes(lastOp.opcode.name)) {
67-
const nextBlock = this.blocks.get(lastOp.offset + 1)
68-
if (nextBlock) {
70+
const nextOffset = lastOp.offset + 1
71+
const nextBlock = this.blocks.get(nextOffset)
72+
if (nextBlock && !this.alreadyRunOffsets.includes(nextOffset)) {
6973
nextBlocks.push(nextBlock)
7074
}
7175
}

src/client/components/Graph/main.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class ConnectedGraph extends React.Component {
2727
componentDidMount() {
2828
const { cfg, graphId, graphType } = this.props;
2929

30-
const graphclass = graphId.replace('.sol', '');
30+
const graphclass = graphId.replace('.sol', '').replace('.evm', '');
3131
const graphviz = d3.select(`.graph--${graphclass}--${graphType}`).graphviz()
3232
graphviz.totalMemory(537395200)
3333
graphviz.renderDot(cfg);
@@ -71,7 +71,7 @@ class ConnectedGraph extends React.Component {
7171
render() {
7272
const { cfg, graphId, graphType } = this.props;
7373

74-
const graphclass = `${graphId.replace('.sol', '')}--${graphType}`;
74+
const graphclass = `${graphId.replace('.sol', '').replace('.evm', '')}--${graphType}`;
7575

7676
return (
7777
<div className={styles['graph-container']}>

src/client/components/Tab/TabPanel/main.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ class ConnectedTabPanel extends React.Component {
140140
});
141141

142142
const params = {
143-
name: name.replace('.sol', ''),
143+
name: name.replace('.sol', '').replace('.evm', ''),
144144
path: encodeURIComponent(path),
145145
source: encodeURIComponent(code),
146146
blockchainHost: localStorage.getItem('host'),
@@ -180,7 +180,7 @@ class ConnectedTabPanel extends React.Component {
180180
const { name, path, code } = this.props;
181181

182182
const params = {
183-
name: name.replace('.sol', ''),
183+
name: name.replace('.sol', '').replace('.evm', ''),
184184
path: encodeURIComponent(path),
185185
source: encodeURIComponent(code),
186186
'constructor': 'false'
@@ -194,7 +194,7 @@ class ConnectedTabPanel extends React.Component {
194194
const { name, code, path } = this.props;
195195

196196
const params = {
197-
name: name.replace('.sol', ''),
197+
name: name.replace('.sol', '').replace('.evm', ''),
198198
path: encodeURIComponent(path),
199199
source: encodeURIComponent(code)
200200
}

src/routes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ const models: TsoaRoute.Models = {
6666
},
6767
"Storage": {
6868
"properties": {
69-
"storage": { "dataType": "any", "required": true },
69+
"storage": { "dataType": "any", "default": {} },
7070
},
7171
},
7272
};

0 commit comments

Comments
 (0)