mirror of https://github.com/ethereum/go-ethereum
commit
53f8f29744
@ -1,367 +0,0 @@ |
|||||||
/* |
|
||||||
This file is part of go-ethereum |
|
||||||
|
|
||||||
go-ethereum is free software: you can redistribute it and/or modify |
|
||||||
it under the terms of the GNU General Public License as published by |
|
||||||
the Free Software Foundation, either version 3 of the License, or |
|
||||||
(at your option) any later version. |
|
||||||
|
|
||||||
go-ethereum 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 General Public License for more details. |
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License |
|
||||||
along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/ |
|
||||||
/** |
|
||||||
* @authors |
|
||||||
* Jeffrey Wilcke <i@jev.io> |
|
||||||
*/ |
|
||||||
package main |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"math/big" |
|
||||||
"strconv" |
|
||||||
"strings" |
|
||||||
"unicode" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils" |
|
||||||
"github.com/ethereum/go-ethereum/core" |
|
||||||
"github.com/ethereum/go-ethereum/core/types" |
|
||||||
"github.com/ethereum/go-ethereum/ethutil" |
|
||||||
"github.com/ethereum/go-ethereum/state" |
|
||||||
"github.com/ethereum/go-ethereum/vm" |
|
||||||
"github.com/obscuren/qml" |
|
||||||
) |
|
||||||
|
|
||||||
type DebuggerWindow struct { |
|
||||||
win *qml.Window |
|
||||||
engine *qml.Engine |
|
||||||
lib *UiLib |
|
||||||
|
|
||||||
vm *vm.Vm |
|
||||||
Db *Debugger |
|
||||||
|
|
||||||
state *state.StateDB |
|
||||||
} |
|
||||||
|
|
||||||
func NewDebuggerWindow(lib *UiLib) *DebuggerWindow { |
|
||||||
engine := qml.NewEngine() |
|
||||||
component, err := engine.LoadFile(lib.AssetPath("debugger/debugger.qml")) |
|
||||||
if err != nil { |
|
||||||
fmt.Println(err) |
|
||||||
|
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
win := component.CreateWindow(nil) |
|
||||||
|
|
||||||
w := &DebuggerWindow{engine: engine, win: win, lib: lib, vm: &vm.Vm{}} |
|
||||||
w.Db = NewDebugger(w) |
|
||||||
|
|
||||||
return w |
|
||||||
} |
|
||||||
|
|
||||||
func (self *DebuggerWindow) Show() { |
|
||||||
context := self.engine.Context() |
|
||||||
context.SetVar("dbg", self) |
|
||||||
|
|
||||||
go func() { |
|
||||||
self.win.Show() |
|
||||||
self.win.Wait() |
|
||||||
}() |
|
||||||
} |
|
||||||
|
|
||||||
func (self *DebuggerWindow) SetCode(code string) { |
|
||||||
self.win.Set("codeText", code) |
|
||||||
} |
|
||||||
|
|
||||||
func (self *DebuggerWindow) SetData(data string) { |
|
||||||
self.win.Set("dataText", data) |
|
||||||
} |
|
||||||
|
|
||||||
func (self *DebuggerWindow) SetAsm(data []byte) { |
|
||||||
self.win.Root().Call("clearAsm") |
|
||||||
|
|
||||||
dis := core.Disassemble(data) |
|
||||||
for _, str := range dis { |
|
||||||
self.win.Root().Call("setAsm", str) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (self *DebuggerWindow) Compile(code string) { |
|
||||||
var err error |
|
||||||
script := ethutil.StringToByteFunc(code, func(s string) (ret []byte) { |
|
||||||
ret, err = ethutil.Compile(s, true) |
|
||||||
return |
|
||||||
}) |
|
||||||
|
|
||||||
if err == nil { |
|
||||||
self.SetAsm(script) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Used by QML
|
|
||||||
func (self *DebuggerWindow) AutoComp(code string) { |
|
||||||
if self.Db.done { |
|
||||||
self.Compile(code) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (self *DebuggerWindow) ClearLog() { |
|
||||||
self.win.Root().Call("clearLog") |
|
||||||
} |
|
||||||
|
|
||||||
func (self *DebuggerWindow) Debug(valueStr, gasStr, gasPriceStr, scriptStr, dataStr string) { |
|
||||||
self.Stop() |
|
||||||
|
|
||||||
defer func() { |
|
||||||
if r := recover(); r != nil { |
|
||||||
self.Logf("compile FAULT: %v", r) |
|
||||||
} |
|
||||||
}() |
|
||||||
|
|
||||||
data := utils.FormatTransactionData(dataStr) |
|
||||||
|
|
||||||
var err error |
|
||||||
script := ethutil.StringToByteFunc(scriptStr, func(s string) (ret []byte) { |
|
||||||
ret, err = ethutil.Compile(s, false) |
|
||||||
return |
|
||||||
}) |
|
||||||
|
|
||||||
if err != nil { |
|
||||||
self.Logln(err) |
|
||||||
|
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
var ( |
|
||||||
gas = ethutil.Big(gasStr) |
|
||||||
gasPrice = ethutil.Big(gasPriceStr) |
|
||||||
value = ethutil.Big(valueStr) |
|
||||||
// Contract addr as test address
|
|
||||||
keyPair = self.lib.eth.KeyManager().KeyPair() |
|
||||||
) |
|
||||||
|
|
||||||
statedb := self.lib.eth.ChainManager().TransState() |
|
||||||
account := self.lib.eth.ChainManager().TransState().GetAccount(keyPair.Address()) |
|
||||||
contract := statedb.NewStateObject([]byte{0}) |
|
||||||
contract.SetCode(script) |
|
||||||
contract.SetBalance(value) |
|
||||||
|
|
||||||
self.SetAsm(script) |
|
||||||
|
|
||||||
block := self.lib.eth.ChainManager().CurrentBlock() |
|
||||||
|
|
||||||
msg := types.NewTransactionMessage(nil, value, gas, gasPrice, data) |
|
||||||
env := core.NewEnv(statedb, self.lib.eth.ChainManager(), msg, block) |
|
||||||
|
|
||||||
self.Logf("callsize %d", len(script)) |
|
||||||
go func() { |
|
||||||
pgas := new(big.Int).Set(gas) |
|
||||||
ret, err := env.Call(account, contract.Address(), data, gas, gasPrice, ethutil.Big0) |
|
||||||
|
|
||||||
rgas := new(big.Int).Sub(pgas, gas) |
|
||||||
tot := new(big.Int).Mul(rgas, gasPrice) |
|
||||||
self.Logf("gas usage %v total price = %v (%v)", rgas, tot, ethutil.CurrencyToString(tot)) |
|
||||||
if err != nil { |
|
||||||
self.Logln("exited with errors:", err) |
|
||||||
} else { |
|
||||||
if len(ret) > 0 { |
|
||||||
self.Logf("exited: % x", ret) |
|
||||||
} else { |
|
||||||
self.Logf("exited: nil") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
statedb.Reset() |
|
||||||
|
|
||||||
if !self.Db.interrupt { |
|
||||||
self.Db.done = true |
|
||||||
} else { |
|
||||||
self.Db.interrupt = false |
|
||||||
} |
|
||||||
}() |
|
||||||
} |
|
||||||
|
|
||||||
func (self *DebuggerWindow) Logf(format string, v ...interface{}) { |
|
||||||
self.win.Root().Call("setLog", fmt.Sprintf(format, v...)) |
|
||||||
} |
|
||||||
|
|
||||||
func (self *DebuggerWindow) Logln(v ...interface{}) { |
|
||||||
str := fmt.Sprintln(v...) |
|
||||||
self.Logf("%s", str[:len(str)-1]) |
|
||||||
} |
|
||||||
|
|
||||||
func (self *DebuggerWindow) Next() { |
|
||||||
self.Db.Next() |
|
||||||
} |
|
||||||
|
|
||||||
func (self *DebuggerWindow) Continue() { |
|
||||||
self.vm.Stepping = false |
|
||||||
self.Next() |
|
||||||
} |
|
||||||
|
|
||||||
func (self *DebuggerWindow) Stop() { |
|
||||||
if !self.Db.done { |
|
||||||
self.Db.Q <- true |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (self *DebuggerWindow) ExecCommand(command string) { |
|
||||||
if len(command) > 0 { |
|
||||||
cmd := strings.Split(command, " ") |
|
||||||
switch cmd[0] { |
|
||||||
case "help": |
|
||||||
self.Logln("Debugger commands:") |
|
||||||
self.Logln("break, bp Set breakpoint on instruction") |
|
||||||
self.Logln("clear [log, break, bp] Clears previous set sub-command(s)") |
|
||||||
case "break", "bp": |
|
||||||
if len(cmd) > 1 { |
|
||||||
lineNo, err := strconv.Atoi(cmd[1]) |
|
||||||
if err != nil { |
|
||||||
self.Logln(err) |
|
||||||
break |
|
||||||
} |
|
||||||
self.Db.breakPoints = append(self.Db.breakPoints, int64(lineNo)) |
|
||||||
self.Logf("break point set on instruction %d", lineNo) |
|
||||||
} else { |
|
||||||
self.Logf("'%s' requires line number", cmd[0]) |
|
||||||
} |
|
||||||
case "clear": |
|
||||||
if len(cmd) > 1 { |
|
||||||
switch cmd[1] { |
|
||||||
case "break", "bp": |
|
||||||
self.Db.breakPoints = nil |
|
||||||
|
|
||||||
self.Logln("Breakpoints cleared") |
|
||||||
case "log": |
|
||||||
self.ClearLog() |
|
||||||
default: |
|
||||||
self.Logf("clear '%s' is not valid", cmd[1]) |
|
||||||
} |
|
||||||
} else { |
|
||||||
self.Logln("'clear' requires sub command") |
|
||||||
} |
|
||||||
|
|
||||||
default: |
|
||||||
self.Logf("Unknown command %s", cmd[0]) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
type Debugger struct { |
|
||||||
N chan bool |
|
||||||
Q chan bool |
|
||||||
done, interrupt bool |
|
||||||
breakPoints []int64 |
|
||||||
main *DebuggerWindow |
|
||||||
win *qml.Window |
|
||||||
} |
|
||||||
|
|
||||||
func NewDebugger(main *DebuggerWindow) *Debugger { |
|
||||||
db := &Debugger{make(chan bool), make(chan bool), true, false, nil, main, main.win} |
|
||||||
|
|
||||||
return db |
|
||||||
} |
|
||||||
|
|
||||||
type storeVal struct { |
|
||||||
Key, Value string |
|
||||||
} |
|
||||||
|
|
||||||
func (self *Debugger) Step(evm vm.VirtualMachine, op vm.OpCode, mem *vm.Memory, stack *vm.Stack, context *vm.Context) { |
|
||||||
} |
|
||||||
|
|
||||||
func (self *Debugger) BreakHook(pc int, op vm.OpCode, mem *vm.Memory, stack *vm.Stack, stateObject *state.StateObject) bool { |
|
||||||
self.main.Logln("break on instr:", pc) |
|
||||||
|
|
||||||
return self.halting(pc, op, mem, stack, stateObject) |
|
||||||
} |
|
||||||
|
|
||||||
func (self *Debugger) StepHook(pc int, op vm.OpCode, mem *vm.Memory, stack *vm.Stack, stateObject *state.StateObject) bool { |
|
||||||
return self.halting(pc, op, mem, stack, stateObject) |
|
||||||
} |
|
||||||
|
|
||||||
func (self *Debugger) SetCode(byteCode []byte) { |
|
||||||
self.main.SetAsm(byteCode) |
|
||||||
} |
|
||||||
|
|
||||||
func (self *Debugger) BreakPoints() []int64 { |
|
||||||
return self.breakPoints |
|
||||||
} |
|
||||||
|
|
||||||
func (d *Debugger) halting(pc int, op vm.OpCode, mem *vm.Memory, stack *vm.Stack, stateObject *state.StateObject) bool { |
|
||||||
d.win.Root().Call("setInstruction", pc) |
|
||||||
d.win.Root().Call("clearMem") |
|
||||||
d.win.Root().Call("clearStack") |
|
||||||
d.win.Root().Call("clearStorage") |
|
||||||
|
|
||||||
addr := 0 |
|
||||||
for i := 0; i+16 <= mem.Len(); i += 16 { |
|
||||||
dat := mem.Data()[i : i+16] |
|
||||||
var str string |
|
||||||
|
|
||||||
for _, d := range dat { |
|
||||||
if unicode.IsGraphic(rune(d)) { |
|
||||||
str += string(d) |
|
||||||
} else { |
|
||||||
str += "?" |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
d.win.Root().Call("setMem", memAddr{fmt.Sprintf("%03d", addr), fmt.Sprintf("%s % x", str, dat)}) |
|
||||||
addr += 16 |
|
||||||
} |
|
||||||
|
|
||||||
for _, val := range stack.Data() { |
|
||||||
d.win.Root().Call("setStack", val.String()) |
|
||||||
} |
|
||||||
|
|
||||||
it := stateObject.Trie().Iterator() |
|
||||||
for it.Next() { |
|
||||||
d.win.Root().Call("setStorage", storeVal{fmt.Sprintf("% x", it.Key), fmt.Sprintf("% x", it.Value)}) |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
stackFrameAt := new(big.Int).SetBytes(mem.Get(0, 32)) |
|
||||||
psize := mem.Len() - int(new(big.Int).SetBytes(mem.Get(0, 32)).Uint64()) |
|
||||||
d.win.Root().ObjectByName("stackFrame").Set("text", fmt.Sprintf(`<b>stack ptr</b>: %v`, stackFrameAt)) |
|
||||||
d.win.Root().ObjectByName("stackSize").Set("text", fmt.Sprintf(`<b>stack size</b>: %d`, psize)) |
|
||||||
d.win.Root().ObjectByName("memSize").Set("text", fmt.Sprintf(`<b>mem size</b>: %v`, mem.Len())) |
|
||||||
|
|
||||||
out: |
|
||||||
for { |
|
||||||
select { |
|
||||||
case <-d.N: |
|
||||||
break out |
|
||||||
case <-d.Q: |
|
||||||
d.interrupt = true |
|
||||||
d.clearBuffers() |
|
||||||
|
|
||||||
return false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
func (d *Debugger) clearBuffers() { |
|
||||||
out: |
|
||||||
// drain
|
|
||||||
for { |
|
||||||
select { |
|
||||||
case <-d.N: |
|
||||||
case <-d.Q: |
|
||||||
default: |
|
||||||
break out |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (d *Debugger) Next() { |
|
||||||
if !d.done { |
|
||||||
d.N <- true |
|
||||||
} |
|
||||||
} |
|
@ -1,10 +0,0 @@ |
|||||||
package vm |
|
||||||
|
|
||||||
import "github.com/ethereum/go-ethereum/state" |
|
||||||
|
|
||||||
type Debugger interface { |
|
||||||
BreakHook(step int, op OpCode, mem *Memory, stack *Stack, object *state.StateObject) bool |
|
||||||
StepHook(step int, op OpCode, mem *Memory, stack *Stack, object *state.StateObject) bool |
|
||||||
BreakPoints() []int64 |
|
||||||
SetCode(byteCode []byte) |
|
||||||
} |
|
@ -0,0 +1,86 @@ |
|||||||
|
package vm |
||||||
|
|
||||||
|
import "math/big" |
||||||
|
|
||||||
|
type req struct { |
||||||
|
stack int |
||||||
|
gas *big.Int |
||||||
|
} |
||||||
|
|
||||||
|
var _baseCheck = map[OpCode]req{ |
||||||
|
// Req stack Gas price
|
||||||
|
ADD: {2, GasFastestStep}, |
||||||
|
LT: {2, GasFastestStep}, |
||||||
|
GT: {2, GasFastestStep}, |
||||||
|
SLT: {2, GasFastestStep}, |
||||||
|
SGT: {2, GasFastestStep}, |
||||||
|
EQ: {2, GasFastestStep}, |
||||||
|
ISZERO: {1, GasFastestStep}, |
||||||
|
SUB: {2, GasFastestStep}, |
||||||
|
AND: {2, GasFastestStep}, |
||||||
|
OR: {2, GasFastestStep}, |
||||||
|
XOR: {2, GasFastestStep}, |
||||||
|
NOT: {1, GasFastestStep}, |
||||||
|
BYTE: {2, GasFastestStep}, |
||||||
|
CALLDATALOAD: {1, GasFastestStep}, |
||||||
|
CALLDATACOPY: {3, GasFastestStep}, |
||||||
|
MLOAD: {1, GasFastestStep}, |
||||||
|
MSTORE: {2, GasFastestStep}, |
||||||
|
MSTORE8: {2, GasFastestStep}, |
||||||
|
CODECOPY: {3, GasFastestStep}, |
||||||
|
MUL: {2, GasFastStep}, |
||||||
|
DIV: {2, GasFastStep}, |
||||||
|
SDIV: {2, GasFastStep}, |
||||||
|
MOD: {2, GasFastStep}, |
||||||
|
SMOD: {2, GasFastStep}, |
||||||
|
SIGNEXTEND: {2, GasFastStep}, |
||||||
|
ADDMOD: {3, GasMidStep}, |
||||||
|
MULMOD: {3, GasMidStep}, |
||||||
|
JUMP: {1, GasMidStep}, |
||||||
|
JUMPI: {2, GasSlowStep}, |
||||||
|
EXP: {2, GasSlowStep}, |
||||||
|
ADDRESS: {0, GasQuickStep}, |
||||||
|
ORIGIN: {0, GasQuickStep}, |
||||||
|
CALLER: {0, GasQuickStep}, |
||||||
|
CALLVALUE: {0, GasQuickStep}, |
||||||
|
CODESIZE: {0, GasQuickStep}, |
||||||
|
GASPRICE: {0, GasQuickStep}, |
||||||
|
COINBASE: {0, GasQuickStep}, |
||||||
|
TIMESTAMP: {0, GasQuickStep}, |
||||||
|
NUMBER: {0, GasQuickStep}, |
||||||
|
CALLDATASIZE: {0, GasQuickStep}, |
||||||
|
DIFFICULTY: {0, GasQuickStep}, |
||||||
|
GASLIMIT: {0, GasQuickStep}, |
||||||
|
POP: {0, GasQuickStep}, |
||||||
|
PC: {0, GasQuickStep}, |
||||||
|
MSIZE: {0, GasQuickStep}, |
||||||
|
GAS: {0, GasQuickStep}, |
||||||
|
BLOCKHASH: {1, GasExtStep}, |
||||||
|
BALANCE: {0, GasExtStep}, |
||||||
|
EXTCODESIZE: {1, GasExtStep}, |
||||||
|
EXTCODECOPY: {4, GasExtStep}, |
||||||
|
SLOAD: {1, GasStorageGet}, |
||||||
|
SSTORE: {2, Zero}, |
||||||
|
SHA3: {1, GasSha3Base}, |
||||||
|
CREATE: {3, GasCreate}, |
||||||
|
CALL: {7, GasCall}, |
||||||
|
CALLCODE: {7, GasCall}, |
||||||
|
JUMPDEST: {0, GasJumpDest}, |
||||||
|
SUICIDE: {1, Zero}, |
||||||
|
RETURN: {2, Zero}, |
||||||
|
} |
||||||
|
|
||||||
|
func baseCheck(op OpCode, stack *stack, gas *big.Int) { |
||||||
|
if r, ok := _baseCheck[op]; ok { |
||||||
|
stack.require(r.stack) |
||||||
|
|
||||||
|
gas.Add(gas, r.gas) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func toWordSize(size *big.Int) *big.Int { |
||||||
|
tmp := new(big.Int) |
||||||
|
tmp.Add(size, u256(31)) |
||||||
|
tmp.Div(tmp, u256(32)) |
||||||
|
return tmp |
||||||
|
} |
@ -0,0 +1,72 @@ |
|||||||
|
package vm |
||||||
|
|
||||||
|
import "fmt" |
||||||
|
|
||||||
|
type Memory struct { |
||||||
|
store []byte |
||||||
|
} |
||||||
|
|
||||||
|
func NewMemory() *Memory { |
||||||
|
return &Memory{nil} |
||||||
|
} |
||||||
|
|
||||||
|
func (m *Memory) Set(offset, size uint64, value []byte) { |
||||||
|
if len(value) > 0 { |
||||||
|
totSize := offset + size |
||||||
|
lenSize := uint64(len(m.store) - 1) |
||||||
|
if totSize > lenSize { |
||||||
|
// Calculate the diff between the sizes
|
||||||
|
diff := totSize - lenSize |
||||||
|
if diff > 0 { |
||||||
|
// Create a new empty slice and append it
|
||||||
|
newSlice := make([]byte, diff-1) |
||||||
|
// Resize slice
|
||||||
|
m.store = append(m.store, newSlice...) |
||||||
|
} |
||||||
|
} |
||||||
|
copy(m.store[offset:offset+size], value) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (m *Memory) Resize(size uint64) { |
||||||
|
if uint64(m.Len()) < size { |
||||||
|
m.store = append(m.store, make([]byte, size-uint64(m.Len()))...) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (self *Memory) Get(offset, size int64) (cpy []byte) { |
||||||
|
if size == 0 { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
if len(self.store) > int(offset) { |
||||||
|
cpy = make([]byte, size) |
||||||
|
copy(cpy, self.store[offset:offset+size]) |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func (m *Memory) Len() int { |
||||||
|
return len(m.store) |
||||||
|
} |
||||||
|
|
||||||
|
func (m *Memory) Data() []byte { |
||||||
|
return m.store |
||||||
|
} |
||||||
|
|
||||||
|
func (m *Memory) Print() { |
||||||
|
fmt.Printf("### mem %d bytes ###\n", len(m.store)) |
||||||
|
if len(m.store) > 0 { |
||||||
|
addr := 0 |
||||||
|
for i := 0; i+32 <= len(m.store); i += 32 { |
||||||
|
fmt.Printf("%03d: % x\n", addr, m.store[i:i+32]) |
||||||
|
addr++ |
||||||
|
} |
||||||
|
} else { |
||||||
|
fmt.Println("-- empty --") |
||||||
|
} |
||||||
|
fmt.Println("####################") |
||||||
|
} |
Loading…
Reference in new issue