mirror of https://github.com/ethereum/go-ethereum
parent
b87442a9f7
commit
56524d13b5
@ -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 +0,0 @@ |
|||||||
package vm |
|
Loading…
Reference in new issue