forked from mirror/go-ethereum
commit
617804c327
File diff suppressed because it is too large
Load Diff
@ -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