asm.go 3.62 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

17
// Package asm provides support for dealing with EVM assembly instructions (e.g., disassembling them).
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
package asm

import (
	"encoding/hex"
	"fmt"

	"github.com/ethereum/go-ethereum/core/vm"
)

// Iterator for disassembled EVM instructions
type instructionIterator struct {
	code    []byte
	pc      uint64
	arg     []byte
	op      vm.OpCode
	error   error
	started bool
}

37
// NewInstructionIterator create a new instruction iterator.
38 39 40 41 42 43
func NewInstructionIterator(code []byte) *instructionIterator {
	it := new(instructionIterator)
	it.code = code
	return it
}

44
// Next returns true if there is a next instruction and moves on.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
func (it *instructionIterator) Next() bool {
	if it.error != nil || uint64(len(it.code)) <= it.pc {
		// We previously reached an error or the end.
		return false
	}

	if it.started {
		// Since the iteration has been already started we move to the next instruction.
		if it.arg != nil {
			it.pc += uint64(len(it.arg))
		}
		it.pc++
	} else {
		// We start the iteration from the first instruction.
		it.started = true
	}

	if uint64(len(it.code)) <= it.pc {
		// We reached the end.
		return false
	}

	it.op = vm.OpCode(it.code[it.pc])
	if it.op.IsPush() {
		a := uint64(it.op) - uint64(vm.PUSH1) + 1
		u := it.pc + 1 + a
		if uint64(len(it.code)) <= it.pc || uint64(len(it.code)) < u {
			it.error = fmt.Errorf("incomplete push instruction at %v", it.pc)
			return false
		}
		it.arg = it.code[it.pc+1 : u]
	} else {
		it.arg = nil
	}
	return true
}

82
// Error returns any error that may have been encountered.
83 84 85 86
func (it *instructionIterator) Error() error {
	return it.error
}

87
// PC returns the PC of the current instruction.
88 89 90 91
func (it *instructionIterator) PC() uint64 {
	return it.pc
}

92
// Op returns the opcode of the current instruction.
93 94 95 96
func (it *instructionIterator) Op() vm.OpCode {
	return it.op
}

97
// Arg returns the argument of the current instruction.
98 99 100 101
func (it *instructionIterator) Arg() []byte {
	return it.arg
}

102
// PrintDisassembled pretty-print all disassembled EVM instructions to stdout.
103 104 105 106 107 108 109 110 111
func PrintDisassembled(code string) error {
	script, err := hex.DecodeString(code)
	if err != nil {
		return err
	}

	it := NewInstructionIterator(script)
	for it.Next() {
		if it.Arg() != nil && 0 < len(it.Arg()) {
112
			fmt.Printf("%05x: %v %#x\n", it.PC(), it.Op(), it.Arg())
113
		} else {
114
			fmt.Printf("%05x: %v\n", it.PC(), it.Op())
115 116
		}
	}
117
	return it.Error()
118 119
}

120
// Disassemble returns all disassembled EVM instructions in human-readable format.
121 122 123 124 125 126
func Disassemble(script []byte) ([]string, error) {
	instrs := make([]string, 0)

	it := NewInstructionIterator(script)
	for it.Next() {
		if it.Arg() != nil && 0 < len(it.Arg()) {
127
			instrs = append(instrs, fmt.Sprintf("%05x: %v %#x\n", it.PC(), it.Op(), it.Arg()))
128
		} else {
129
			instrs = append(instrs, fmt.Sprintf("%05x: %v\n", it.PC(), it.Op()))
130 131 132 133 134 135 136
		}
	}
	if err := it.Error(); err != nil {
		return nil, err
	}
	return instrs, nil
}