Skip to content

Commit

Permalink
Added bytecode .evm extension, show jumps within a block in CFG
Browse files Browse the repository at this point in the history
  • Loading branch information
fergarrui committed May 6, 2019
1 parent 72d7548 commit b4655fe
Show file tree
Hide file tree
Showing 10 changed files with 66 additions and 22 deletions.
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ There are already tools that allow you to debug Ethereum transactions (Solidity)

# Usage

### Download

Use one of these releases:

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

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

Go to localhost:9090

### Use

* Go to localhost:9090
* Enter the path where the contracts are in the input text (it will load Solidity contracts recursively)
* A tab per file found will be created
* Under a file tab there are a few actions using the left menu

### How to debug bytecode (with no source code) [Experimental]

* Create a file with extension `.evm` and paste the runtime bytecode (:warning: important: with `0x` as prefix)
* For example: create a file named: `contract1.evm` with content `0x60806040`
* Scan the directory as described above
* You won't get source code mappings when clicking in operations of the CFG

# Features

* Now interactive :star2:: it has a sepparate frontend and API instead of building a static HTML file like in earlier versions
Expand All @@ -49,6 +65,7 @@ Go to localhost:9090
* EVM state in transaction: it is shown below the editor when selecting an opcode present in the execution trace of the provided transaction hash
* 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))
* 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)
* To debug directly bytecode, use `.evm` extension files

# Limitations/Considerations

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "ethereum-graph-debugger",
"author": "Fernando Garcia",
"license": "GPL",
"version": "2.1.0",
"version": "2.2.0",
"description": "Ethereum graph debugger",
"main": "dist/run-server.js",
"scripts": {
Expand Down
14 changes: 8 additions & 6 deletions src/api/bytecode/EVMDisassembler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,17 @@ export class EVMDisassembler implements Disassembler {
}

disassembleContract(bytecode: string): DisassembledContract {
let code = bytecode
let code = bytecode.trim()

if (bytecode.startsWith('0x')) {
code = bytecode.slice(2)
}

if (code.includes(EVMDisassembler.metadataPrefix)) {
code = code.split(EVMDisassembler.metadataPrefix)[0]
}

if (code.length % 2 !== 0) {
throw new Error(`Bad input, bytecode length not even: ${code}`)
throw new Error(`Bad input, bytecode length not even: ${code}, length: ${code.length}`)
}

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

disassembleBytecode(bytecode: string): Operation[] {
let code = bytecode
let code = bytecode.trim()

if (bytecode.startsWith('0x')) {
code = bytecode.slice(2)
Expand All @@ -79,9 +82,8 @@ export class EVMDisassembler implements Disassembler {
if (code.includes(EVMDisassembler.metadataPrefix)) {
code = code.split(EVMDisassembler.metadataPrefix)[0]
}

if (code.length % 2 !== 0) {
throw new Error(`Bad input, bytecode length not even: ${code}`)
throw new Error(`Bad input, bytecode length not even: ${code}, length: ${code.length}`)
}
let offset = 0
const operations = code.match(/.{1,2}/g)
Expand Down
16 changes: 15 additions & 1 deletion src/api/cfg/CFGBlocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,22 @@ export class CFGBlocks {
this.blocks[offset] = block
}

// getBlock(offset: number): OperationBlock {
// return this.blocks[offset]
// }

get(offset: number): OperationBlock {
return this.blocks[offset]
const block: OperationBlock = this.blocks[offset]
if(!block) {
for (const key of Object.keys(this.blocks)) {
const b: OperationBlock = this.blocks[key]
const found = b.operations.find(op => op.offset === offset)
if (found) {
return b
}
}
}
return block
}

keys(): number[] {
Expand Down
11 changes: 9 additions & 2 deletions src/api/service/service/CFGService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,15 @@ export class CFGService {
) {}

buildCFGFromSource(contractName: string, source: string, path: string): CFGContract {
const contract: DisassembledContract = this.disassembler.disassembleSourceCode(contractName, source, path)
return this.buildCfgContract(contract)
let contract: DisassembledContract
if(source.startsWith('0x')) {
const cfg: CFGContract = this.buildCFGFromBytecode(source)
cfg.contractRuntime.rawBytecode = source
return cfg
} else {
contract = this.disassembler.disassembleSourceCode(contractName, source, path)
return this.buildCfgContract(contract)
}
}

buildCFGFromBytecode(bytecode: string): CFGContract {
Expand Down
2 changes: 1 addition & 1 deletion src/api/service/service/FileServiceDefault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ var fs = require('fs')
@injectable()
export class FileServiceDefault implements FileService {
async findContractssWithExtension(dir: string, extension: string): Promise<ContractFile[]> {
const files = await recursive(dir, [`!*.${extension}`])
const files = await recursive(dir, [`!*.{${extension},evm}`])

return await files
.map(file => {
Expand Down
12 changes: 8 additions & 4 deletions src/api/symbolic/evm/EVMExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export class EVMExecutor {
evm: EVM
blocks: CFGBlocks
executor: OpcodeExecutor
alreadyRunOffsets: number[] = []

constructor(blocks: CFGBlocks, executor: OpcodeExecutor) {
this.evm = new EVM()
Expand All @@ -24,6 +25,7 @@ export class EVMExecutor {
throw new Error(`Could not find block with offset ${offset}`)
}
this.runBlock(block)
this.alreadyRunOffsets.push(offset)
const nextBlocks: OperationBlock[] = this.findNextBlocks(block)
for (const nextBlock of nextBlocks) {
if (block.childA !== nextBlock.offset && block.childB !== nextBlock.offset) {
Expand Down Expand Up @@ -57,15 +59,17 @@ export class EVMExecutor {
const jumpLocation = this.evm.nextJumpLocation
this.evm.nextJumpLocation = undefined
if (jumpLocation && !jumpLocation.isSymbolic) {
const locationBlock: OperationBlock = this.blocks.get(jumpLocation.value.toNumber())
if (locationBlock) {
const nextOffset = jumpLocation.value.toNumber()
const locationBlock: OperationBlock = this.blocks.get(nextOffset)
if (locationBlock && !this.alreadyRunOffsets.includes(nextOffset)) {
nextBlocks.push(locationBlock)
}
}
}
if (!this.NO_NEXT_BLOCK.includes(lastOp.opcode.name)) {
const nextBlock = this.blocks.get(lastOp.offset + 1)
if (nextBlock) {
const nextOffset = lastOp.offset + 1
const nextBlock = this.blocks.get(nextOffset)
if (nextBlock && !this.alreadyRunOffsets.includes(nextOffset)) {
nextBlocks.push(nextBlock)
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/client/components/Graph/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class ConnectedGraph extends React.Component {
componentDidMount() {
const { cfg, graphId, graphType } = this.props;

const graphclass = graphId.replace('.sol', '');
const graphclass = graphId.replace('.sol', '').replace('.evm', '');
const graphviz = d3.select(`.graph--${graphclass}--${graphType}`).graphviz()
graphviz.totalMemory(537395200)
graphviz.renderDot(cfg);
Expand Down Expand Up @@ -71,7 +71,7 @@ class ConnectedGraph extends React.Component {
render() {
const { cfg, graphId, graphType } = this.props;

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

return (
<div className={styles['graph-container']}>
Expand Down
6 changes: 3 additions & 3 deletions src/client/components/Tab/TabPanel/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class ConnectedTabPanel extends React.Component {
});

const params = {
name: name.replace('.sol', ''),
name: name.replace('.sol', '').replace('.evm', ''),
path: encodeURIComponent(path),
source: encodeURIComponent(code),
blockchainHost: localStorage.getItem('host'),
Expand Down Expand Up @@ -180,7 +180,7 @@ class ConnectedTabPanel extends React.Component {
const { name, path, code } = this.props;

const params = {
name: name.replace('.sol', ''),
name: name.replace('.sol', '').replace('.evm', ''),
path: encodeURIComponent(path),
source: encodeURIComponent(code),
'constructor': 'false'
Expand All @@ -194,7 +194,7 @@ class ConnectedTabPanel extends React.Component {
const { name, code, path } = this.props;

const params = {
name: name.replace('.sol', ''),
name: name.replace('.sol', '').replace('.evm', ''),
path: encodeURIComponent(path),
source: encodeURIComponent(code)
}
Expand Down
2 changes: 1 addition & 1 deletion src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const models: TsoaRoute.Models = {
},
"Storage": {
"properties": {
"storage": { "dataType": "any", "required": true },
"storage": { "dataType": "any", "default": {} },
},
},
};
Expand Down

0 comments on commit b4655fe

Please sign in to comment.