-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Close #444: Add evm debugger #452
base: master
Are you sure you want to change the base?
Changes from 47 commits
bfe5c71
0c25cfa
bd8a9e2
50a511c
d189efc
ba31acb
c904358
76db327
89cbbee
dbc538e
fde9062
953cfc1
f7f3ab6
4bdc66f
a6f97b4
0fb8697
2e853e2
faabeb1
f96639f
0d883ed
1d3d01e
5c48939
79cad98
3acb30c
fc64733
447dd9e
3f18b2a
2249da6
95cca81
cbdc78f
13a66f2
ecd4160
676d08f
21002b4
b7f6e42
bd510bd
7ec9f56
88f0800
94db51e
3084b33
b084bb9
6592554
f69a89f
ac1065b
f283e51
b7838fa
3ecd693
ec9c9df
40381ad
e71c635
ed5bd36
44dd91a
2a48a6f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<!-- | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove these files, they are outdated |
||
THIS FILE IS GENERATED. DO NOT EDIT MANUALLY! | ||
--> | ||
## ReadEvmWord | ||
|
||
### Id | ||
|
||
`0x06` | ||
### Signature | ||
|
||
`(index: bigint, array: array): bytes` | ||
|
||
### Description | ||
|
||
Takes byte array, index from stack. Returns 32 bytes, truncated if necessary, from given index in the given array. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<!-- | ||
THIS FILE IS GENERATED. DO NOT EDIT MANUALLY! | ||
--> | ||
## SliceEvmMemory | ||
|
||
### Id | ||
|
||
`0x08` | ||
### Signature | ||
|
||
`(size: bigint, ind: bigint, array: array): bytes` | ||
|
||
### Description | ||
|
||
Takes byte array, index, size stack. Returns slice of the array between ind and ind + size. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<!-- | ||
THIS FILE IS GENERATED. DO NOT EDIT MANUALLY! | ||
--> | ||
## WriteEvmWord | ||
|
||
### Id | ||
|
||
`0x07` | ||
### Signature | ||
|
||
`(bytes: bytes, index: bigint, array: array): array` | ||
|
||
### Description | ||
|
||
Takes byte array, index from stack, bytes to write. Writes 32 bytes, fill with zeros if necessary, from given index in the given array. Returns reference to array |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,62 @@ | ||||
/* | ||||
* Copyright (C) 2018 Expload.com | ||||
* | ||||
* This program is free software: you can redistribute it and/or modify | ||||
* it under the terms of the GNU Affero General Public License as | ||||
* published by the Free Software Foundation, either version 3 of the | ||||
* License, or (at your option) any later version. | ||||
* | ||||
* This program 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 Affero General Public License for more details. | ||||
* | ||||
* You should have received a copy of the GNU Affero General Public License | ||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
*/ | ||||
|
||||
package pravda.evm.debug | ||||
|
||||
import java.nio.ByteBuffer | ||||
|
||||
import com.google.protobuf.ByteString | ||||
import pravda.common.domain | ||||
import pravda.common.domain.Address | ||||
import pravda.vm._ | ||||
import pravda.vm.impl.MemoryImpl | ||||
import pravda.vm.sandbox.VmSandbox.StorageSandbox | ||||
|
||||
//'extends Vm' is required for using 'this' in SystemOperation constructor | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||
trait DebugVm extends Vm { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use aggregation, not inheritance here |
||||
override def spawn(initialProgram: ByteString, environment: Environment, wattLimit: Long): ExecutionResult = | ||||
throw new Exception("It's debug vm") | ||||
|
||||
override def run(programAddress: Address, | ||||
environment: Environment, | ||||
memory: Memory, | ||||
wattCounter: WattCounter, | ||||
pcallAllowed: Boolean): Unit = | ||||
throw new Exception("It's debug vm. You can't use pcall, lcall opcodes") | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's bad, it's necessary feature |
||||
|
||||
def debugBytes[S](program: ByteBuffer, | ||||
env: Environment, | ||||
mem: MemoryImpl, | ||||
counter: WattCounter, | ||||
maybeStorage: Option[StorageSandbox], | ||||
maybePA: Option[domain.Address], | ||||
pcallAllowed: Boolean)(implicit debugger: Debugger[S]): List[S] | ||||
} | ||||
|
||||
object DebugVm { | ||||
|
||||
sealed trait ExecutionResult | ||||
|
||||
final case class UnitExecution(f: () => Unit) extends ExecutionResult { | ||||
f() | ||||
} | ||||
|
||||
case object InterruptedExecution extends ExecutionResult | ||||
|
||||
final case class MetaExecution(meta: Meta) extends ExecutionResult | ||||
|
||||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,30 @@ | ||||||
/* | ||||||
* Copyright (C) 2018 Expload.com | ||||||
* | ||||||
* This program is free software: you can redistribute it and/or modify | ||||||
* it under the terms of the GNU Affero General Public License as | ||||||
* published by the Free Software Foundation, either version 3 of the | ||||||
* License, or (at your option) any later version. | ||||||
* | ||||||
* This program 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 Affero General Public License for more details. | ||||||
* | ||||||
* You should have received a copy of the GNU Affero General Public License | ||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||||
*/ | ||||||
|
||||||
package pravda.evm.debug | ||||||
|
||||||
import java.nio.ByteBuffer | ||||||
|
||||||
import pravda.evm.debug.DebugVm.ExecutionResult | ||||||
import pravda.vm.impl.MemoryImpl | ||||||
import pravda.vm.sandbox.VmSandbox.StorageSandbox | ||||||
|
||||||
trait Debugger[State] { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
def debugOp(program: ByteBuffer, op: Int, mem: MemoryImpl, storage: StorageSandbox)( | ||||||
execResult: Either[Throwable, ExecutionResult]): State | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
/* | ||
* Copyright (C) 2018 Expload.com | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Affero General Public License as | ||
* published by the Free Software Foundation, either version 3 of the | ||
* License, or (at your option) any later version. | ||
* | ||
* This program 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 Affero General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
package pravda.evm.debug | ||
|
||
import java.nio.ByteBuffer | ||
|
||
import pravda.common.domain | ||
import pravda.vm.Error.PcallDenied | ||
import pravda.vm.Opcodes._ | ||
import pravda.vm.WattCounter.CpuBasic | ||
import pravda.vm._ | ||
import pravda.vm.impl.MemoryImpl | ||
import pravda.vm.operations._ | ||
|
||
import scala.annotation.tailrec | ||
import scala.util.Try | ||
import pravda.evm.debug.DebugVm.{InterruptedExecution, MetaExecution, UnitExecution} | ||
import pravda.vm.sandbox.VmSandbox.StorageSandbox | ||
|
||
class VmImplDebug extends DebugVm { | ||
|
||
def debugBytes[S](program: ByteBuffer, | ||
env: Environment, | ||
mem: MemoryImpl, | ||
counter: WattCounter, | ||
maybeStorage: Option[StorageSandbox], | ||
maybePA: Option[domain.Address], | ||
pcallAllowed: Boolean)(implicit debugger: Debugger[S]): List[S] = { | ||
|
||
val logicalOperations = new LogicalOperations(mem, counter) | ||
val arithmeticOperations = new ArithmeticOperations(mem, counter) | ||
val storageOperations = new StorageOperations(mem, maybeStorage, counter) | ||
val heapOperations = new HeapOperations(mem, program, counter) | ||
val stackOperations = new StackOperations(mem, program, counter) | ||
val controlOperations = new ControlOperations(program, mem, counter) | ||
val nativeCoinOperations = new NativeCoinOperations(mem, env, counter, maybePA) | ||
val systemOperations = | ||
new SystemOperations(program, mem, maybeStorage, counter, env, maybePA, StandardLibrary.Index, this) | ||
val dataOperations = new DataOperations(mem, counter) | ||
|
||
val Some(storage) = maybeStorage | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ??? |
||
|
||
@tailrec def proc(acc: List[S]): List[S] = { | ||
counter.cpuUsage(CpuBasic) | ||
val op = program.get() & 0xff | ||
|
||
import DebugVm.ExecutionResult | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move up |
||
val executionResult = Try[ExecutionResult] { | ||
mem.setCounter(program.position()) | ||
op match { | ||
// Control operations | ||
case CALL => UnitExecution(() => controlOperations.call()) | ||
case RET => UnitExecution(() => controlOperations.ret()) | ||
case JUMP => UnitExecution(() => controlOperations.jump()) | ||
case JUMPI => UnitExecution(() => controlOperations.jumpi()) | ||
// Native coin operations | ||
case TRANSFER => UnitExecution(() => nativeCoinOperations.transfer()) | ||
case PTRANSFER => UnitExecution(() => nativeCoinOperations.ptransfer()) | ||
case BALANCE => UnitExecution(() => nativeCoinOperations.balance()) | ||
// Stack operations | ||
case POP => UnitExecution(() => stackOperations.pop()) | ||
case PUSHX => UnitExecution(() => stackOperations.push()) | ||
case DUP => UnitExecution(() => stackOperations.dup()) | ||
case DUPN => UnitExecution(() => stackOperations.dupN()) | ||
case SWAP => UnitExecution(() => stackOperations.swap()) | ||
case SWAPN => UnitExecution(() => stackOperations.swapN()) | ||
// Heap operations | ||
case NEW => UnitExecution(() => heapOperations.`new`()) | ||
case ARRAY_GET => UnitExecution(() => heapOperations.arrayGet()) | ||
case STRUCT_GET => UnitExecution(() => heapOperations.structGet()) | ||
case STRUCT_GET_STATIC => UnitExecution(() => heapOperations.structGetStatic()) | ||
case ARRAY_MUT => UnitExecution(() => heapOperations.arrayMut()) | ||
case STRUCT_MUT => UnitExecution(() => heapOperations.structMut()) | ||
case STRUCT_MUT_STATIC => UnitExecution(() => heapOperations.structMutStatic()) | ||
case PRIMITIVE_PUT => UnitExecution(() => heapOperations.primitivePut()) | ||
case PRIMITIVE_GET => UnitExecution(() => heapOperations.primitiveGet()) | ||
case NEW_ARRAY => UnitExecution(() => heapOperations.newArray()) | ||
case LENGTH => UnitExecution(() => heapOperations.length()) | ||
// Storage operations | ||
case SPUT => UnitExecution(() => storageOperations.put()) | ||
case SGET => UnitExecution(() => storageOperations.get()) | ||
case SDROP => UnitExecution(() => storageOperations.drop()) | ||
case SEXIST => UnitExecution(() => storageOperations.exists()) | ||
// Arithmetic operations | ||
case ADD => UnitExecution(() => arithmeticOperations.add()) | ||
case MUL => UnitExecution(() => arithmeticOperations.mul()) | ||
case DIV => UnitExecution(() => arithmeticOperations.div()) | ||
case MOD => UnitExecution(() => arithmeticOperations.mod()) | ||
// Logical operations | ||
case NOT => UnitExecution(() => logicalOperations.not()) | ||
case AND => UnitExecution(() => logicalOperations.and()) | ||
case OR => UnitExecution(() => logicalOperations.or()) | ||
case XOR => UnitExecution(() => logicalOperations.xor()) | ||
case EQ => UnitExecution(() => logicalOperations.eq()) | ||
case LT => UnitExecution(() => logicalOperations.lt()) | ||
case GT => UnitExecution(() => logicalOperations.gt()) | ||
// Data operations | ||
case CAST => UnitExecution(() => dataOperations.cast()) | ||
case CONCAT => UnitExecution(() => dataOperations.concat()) | ||
case SLICE => UnitExecution(() => dataOperations.slice()) | ||
// System operations | ||
case STOP => InterruptedExecution | ||
case FROM => UnitExecution(() => systemOperations.from()) | ||
case LCALL => UnitExecution(() => systemOperations.lcall()) | ||
case SCALL => UnitExecution(() => systemOperations.scall()) | ||
case PCREATE => UnitExecution(() => systemOperations.pcreate()) | ||
case SEAL => UnitExecution(() => systemOperations.seal()) | ||
case PUPDATE => UnitExecution(() => systemOperations.pupdate()) | ||
case PADDR => UnitExecution(() => systemOperations.paddr()) | ||
case PCALL => | ||
if (pcallAllowed) { | ||
UnitExecution(() => systemOperations.pcall()) | ||
} else { | ||
throw ThrowableVmError(PcallDenied) | ||
} | ||
case THROW => UnitExecution(() => systemOperations.`throw`()) | ||
case EVENT => UnitExecution(() => systemOperations.event()) | ||
case CALLERS => UnitExecution(() => systemOperations.callers()) | ||
case HEIGHT => UnitExecution(() => systemOperations.chainHeight()) | ||
case HASH => UnitExecution(() => systemOperations.lastBlockHash()) | ||
case CODE => UnitExecution(() => systemOperations.code()) | ||
case META => | ||
MetaExecution(Meta.readFromByteBuffer(program)) | ||
case _ => UnitExecution(() => ()) | ||
} | ||
}.toEither | ||
val state = debugger.debugOp(program, op, mem, storage)(executionResult) | ||
val res = state :: acc | ||
executionResult match { | ||
case Right(InterruptedExecution) | Left(_) => | ||
res | ||
case _ => if (program.hasRemaining) proc(res) else res | ||
} | ||
} | ||
proc(Nil).reverse | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
/* | ||
* Copyright (C) 2018 Expload.com | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Affero General Public License as | ||
* published by the Free Software Foundation, either version 3 of the | ||
* License, or (at your option) any later version. | ||
* | ||
* This program 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 Affero General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
package pravda.evm.debug | ||
|
||
import com.google.protobuf.ByteString | ||
import pravda.common.domain.Address | ||
import pravda.vm | ||
import pravda.vm._ | ||
import pravda.vm.impl.{MemoryImpl, WattCounterImpl} | ||
import pravda.vm.sandbox.VmSandbox.{EnvironmentSandbox, Preconditions, StorageSandbox} | ||
|
||
import scala.collection.mutable | ||
import scala.collection.mutable.ArrayBuffer | ||
|
||
object VmSandboxDebug { | ||
|
||
def run[S](input: Preconditions, code: ByteString)(implicit debugger: Debugger[S]): List[S] = { | ||
val sandboxVm = new VmImplDebug() | ||
val heap = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. smells like copy-paste |
||
if (input.heap.nonEmpty) { | ||
val length = input.heap.map(_._1.data).max + 1 | ||
val buffer = ArrayBuffer.fill[Data](length)(Data.Primitive.Null) | ||
input.heap.foreach { case (ref, value) => buffer(ref.data) = value } | ||
buffer | ||
} else { | ||
ArrayBuffer[Data]() | ||
} | ||
} | ||
val memory = MemoryImpl(ArrayBuffer(input.stack: _*), heap) | ||
val wattCounter = new WattCounterImpl(input.`watts-limit`) | ||
|
||
val pExecutor = input.executor.getOrElse { | ||
Address @@ ByteString.copyFrom((1 to 32).map(_.toByte).toArray) | ||
} | ||
|
||
val effects = mutable.Buffer[vm.Effect]() | ||
val environment: Environment = new EnvironmentSandbox( | ||
effects, | ||
input.`program-storage`, | ||
input.balances.toSeq, | ||
input.programs.toSeq, | ||
pExecutor, | ||
input.`app-state-info` | ||
) | ||
val storage = new StorageSandbox(Address.Void, effects, input.storage.toSeq) | ||
|
||
memory.enterProgram(Address.Void) | ||
val res = sandboxVm.debugBytes( | ||
code.asReadOnlyByteBuffer(), | ||
environment, | ||
memory, | ||
wattCounter, | ||
Some(storage), | ||
Some(Address.Void), | ||
pcallAllowed = true | ||
) | ||
memory.exitProgram() | ||
res | ||
|
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.