Browse Source

Debugger.

cl-refactor
Gav Wood 11 years ago
parent
commit
42f7833b97
  1. 56
      alethzero/Main.ui
  2. 138
      alethzero/MainWin.cpp
  3. 11
      alethzero/MainWin.h
  4. 148
      libethereum/State.cpp
  5. 33
      libethereum/State.h
  6. 3
      libethereum/VM.cpp
  7. 29
      libethereum/VM.h

56
alethzero/Main.ui

@ -131,8 +131,8 @@
<string>&amp;Debug</string>
</property>
<addaction name="enableDebug"/>
<addaction name="actionStep"/>
<addaction name="actionContinue"/>
<addaction name="debugStep"/>
<addaction name="debugContinue"/>
</widget>
<addaction name="menu_File"/>
<addaction name="menu_Network"/>
@ -835,20 +835,54 @@
<number>8</number>
</attribute>
<widget class="QWidget" name="debugPanel">
<property name="enabled">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QSplitter" name="splitter_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QListWidget" name="debugCode"/>
<widget class="QListWidget" name="debugStack"/>
<widget class="QListWidget" name="debugCode">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
<widget class="QSplitter" name="splitter_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QPlainTextEdit" name="debugMemory"/>
<widget class="QPlainTextEdit" name="debugStorage"/>
<widget class="QListWidget" name="debugStack">
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
</widget>
<widget class="QTextEdit" name="debugMemory">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::NoTextInteraction</set>
</property>
</widget>
<widget class="QTextEdit" name="debugStorage">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::NoTextInteraction</set>
</property>
</widget>
</widget>
</widget>
</item>
@ -934,7 +968,10 @@
<string>&amp;Debug EVM Execution</string>
</property>
</action>
<action name="actionStep">
<action name="debugStep">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Single Step</string>
</property>
@ -942,7 +979,10 @@
<string>F10</string>
</property>
</action>
<action name="actionContinue">
<action name="debugContinue">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Continue</string>
</property>

138
alethzero/MainWin.cpp

@ -9,6 +9,7 @@
#include <libethereum/Client.h>
#include <libethereum/Instruction.h>
#include <libethereum/PeerServer.h>
#include <libethereum/VM.h>
#include "BuildInfo.h"
#include "MainWin.h"
#include "ui_Main.h"
@ -31,6 +32,7 @@ using eth::PeerInfo;
using eth::RLP;
using eth::Secret;
using eth::Transaction;
using eth::Executive;
// functions
using eth::toHex;
@ -702,8 +704,8 @@ void Main::on_contracts_currentItemChanged()
auto h = h160((byte const*)hba.data(), h160::ConstructFromPointer);
stringstream s;
auto mem = state().contractStorage(h);
for (auto const& i: mem)
auto storage = state().contractStorage(h);
for (auto const& i: storage)
s << "@" << showbase << hex << i.first << "&nbsp;&nbsp;&nbsp;&nbsp;" << showbase << hex << i.second << "<br/>";
s << "<h4>Body Code</h4>" << disassemble(state().contractCode(h));
ui->contractInfo->appendHtml(QString::fromStdString(s.str()));
@ -929,19 +931,42 @@ void Main::on_send_clicked()
{
m_client->unlock();
Secret s = i.secret();
if (isCreation())
if (ui->enableDebug->checked())
if (ui->enableDebug->isChecked())
{
m_client->lock();
m_executiveState = state();
m_client->unlock();
m_currentExecution = unique_ptr<Executive>(new Executive(m_executiveState));
Transaction t;
t.nonce = m_executiveState.transactionsFrom(toAddress(s));
t.value = value();
t.gasPrice = gasPrice();
t.gas = ui->gas->value();
t.data = m_data;
if (isCreation())
{
t.receiveAddress = Address();
t.init = m_init;
}
else
m_client->transact(s, value(), m_data, m_init, ui->gas->value(), gasPrice());
else
if (ui->enableDebug->checked())
{
t.receiveAddress = fromString(ui->destination->text());
t.data = m_data;
}
t.sign(s);
auto r = t.rlp();
m_currentExecution->setup(&r);
initDebugger();
updateDebugger();
}
else
{
if (isCreation())
m_client->transact(s, value(), m_data, m_init, ui->gas->value(), gasPrice());
else
m_client->transact(s, value(), fromString(ui->destination->text()), m_data, ui->gas->value(), gasPrice());
refresh();
refresh();
}
return;
}
m_client->unlock();
@ -954,47 +979,100 @@ void Main::on_create_triggered()
m_keysChanged = true;
}
class ExecutionContext
{
public:
bool go(unsigned _steps = (unsigned)-1);
};
bool ExecutionContext::go(unsigned _steps)
{
}
void Main::on_enableDebug_triggered()
{
ui->debugPanel->setEnabled(ui->enableDebug->checked());
ui->debugStep->setEnabled(ui->enableDebug->checked());
ui->debugContinue->setEnabled(ui->enableDebug->checked());
ui->send->setText(ui->enableDebug->checked() ? "D&ebug" : "&Execute");
ui->debugPanel->setEnabled(ui->enableDebug->isChecked());
ui->send->setText(ui->enableDebug->isChecked() ? "D&ebug" : "&Execute");
}
void Main::on_step_triggered()
void Main::on_debugStep_triggered()
{
if (!m_currentExecution)
return;
if (m_currentExecution->go(1))
finished();
debugFinished();
else
updateExecution();
updateDebugger();
}
void Main::on_continue_triggered()
void Main::on_debugContinue_triggered()
{
if (!m_currentExecution)
return;
if (m_currentExecution->go())
finished();
debugFinished();
else
updateExecution();
updateDebugger();
}
void Main::debugFinished()
{
m_currentExecution.reset();
ui->debugCode->clear();
ui->debugStack->clear();
ui->debugMemory->setHtml("");
ui->debugStorage->setHtml("");
ui->debugStateInfo->setText("");
ui->send->setEnabled(true);
ui->enableDebug->setEnabled(true);
ui->debugStep->setEnabled(false);
ui->debugContinue->setEnabled(false);
ui->debugPanel->setEnabled(false);
}
void Main::updateExecution()
void Main::initDebugger()
{
ui->send->setEnabled(false);
ui->enableDebug->setEnabled(false);
ui->debugStep->setEnabled(true);
ui->debugContinue->setEnabled(true);
ui->debugPanel->setEnabled(true);
ui->debugCode->setEnabled(false);
QListWidget* dc = ui->debugCode;
dc->clear();
if (m_currentExecution)
{
for (unsigned i = 0; i <= m_currentExecution->ext().code.size(); ++i)
{
byte b = i < m_currentExecution->ext().code.size() ? m_currentExecution->ext().code[i] : 0;
QString s = c_instructionInfo.at((Instruction)b).name;
m_pcWarp[i] = dc->count();
ostringstream out;
out << hex << setw(4) << setfill('0') << i;
if (b >= (byte)Instruction::PUSH1 && b <= (byte)Instruction::PUSH32)
{
unsigned bc = b - (byte)Instruction::PUSH1 + 1;
s = "PUSH 0x" + QString::fromStdString(toHex(bytesConstRef(&m_currentExecution->ext().code[i + 1], bc)));
i += bc;
}
dc->addItem(QString::fromStdString(out.str()) + " " + s);
}
}
}
void Main::updateDebugger()
{
QListWidget* ds = ui->debugStack;
ds->clear();
if (m_currentExecution)
{
eth::VM const& vm = m_currentExecution->vm();
for (auto i: vm.stack())
ds->insertItem(0, QString::fromStdString(toHex(((h256)i).asArray())));
ui->debugMemory->setHtml(QString::fromStdString(htmlDump(vm.memory(), 16)));
ui->debugCode->setCurrentRow(m_pcWarp[(unsigned)vm.curPC()]);
ostringstream ss;
ss << hex << "PC: 0x" << vm.curPC() << " | GAS: 0x" << vm.gas();
ui->debugStateInfo->setText(QString::fromStdString(ss.str()));
stringstream s;
auto storage = m_currentExecution->state().contractStorage(m_currentExecution->ext().myAddress);
for (auto const& i: storage)
s << "@" << showbase << hex << i.first << "&nbsp;&nbsp;&nbsp;&nbsp;" << showbase << hex << i.second << "<br/>";
ui->debugStorage->setHtml(QString::fromStdString(s.str()));
}
}
// extra bits needed to link on VS

11
alethzero/MainWin.h

@ -7,6 +7,7 @@
#include <QtCore/QMutex>
#include <QtWidgets/QMainWindow>
#include <libethereum/CommonEth.h>
#include <libethereum/State.h>
#include <libethereum/RLP.h>
namespace Ui {
@ -191,8 +192,6 @@ private:
Q_PROPERTY(QEthereum* ethereum READ ethereum WRITE setEthereum NOTIFY ethChanged)
};
class ExecutionContext;
class QEthereum: public QObject
{
Q_OBJECT
@ -302,7 +301,9 @@ signals:
private:
QString pretty(eth::Address _a) const;
void updateExecution();
void initDebugger();
void updateDebugger();
void debugFinished();
QString render(eth::Address _a) const;
eth::Address fromString(QString const& _a) const;
@ -335,7 +336,9 @@ private:
unsigned m_backupGas;
std::shared_ptr<ExecutionContext> m_currentExecution;
eth::State m_executiveState;
std::unique_ptr<eth::Executive> m_currentExecution;
QMap<unsigned, unsigned> m_pcWarp;
QNetworkAccessManager m_webCtrl;

148
libethereum/State.cpp

@ -601,23 +601,30 @@ bytes const& State::contractCode(Address _contract) const
return m_cache[_contract].code();
}
void State::prepExecution(bytesConstRef _rlp)
Executive::~Executive()
{
// TODO: Make safe.
delete m_ext;
delete m_vm;
}
void Executive::setup(bytesConstRef _rlp)
{
// Entry point for a user-executed transaction.
Transaction t(_rlp);
m_t = Transaction(_rlp);
auto sender = t.sender();
auto sender = m_t.sender();
// Avoid invalid transactions.
auto nonceReq = transactionsFrom(sender);
if (t.nonce != nonceReq)
auto nonceReq = m_s.transactionsFrom(sender);
if (m_t.nonce != nonceReq)
{
clog(StateChat) << "Invalid Nonce.";
throw InvalidNonce(nonceReq, t.nonce);
throw InvalidNonce(nonceReq, m_t.nonce);
}
// Don't like transactions whose gas price is too low. NOTE: this won't stay here forever - it's just until we get a proper gas price discovery protocol going.
if (t.gasPrice < 10 * szabo)
if (m_t.gasPrice < 10 * szabo)
{
clog(StateChat) << "Offered gas-price is too low.";
throw GasPriceTooLow();
@ -625,59 +632,133 @@ void State::prepExecution(bytesConstRef _rlp)
// Check gas cost is enough.
u256 gasCost;
if (t.isCreation())
gasCost = (t.init.size() + t.data.size()) * c_txDataGas + c_createGas;
if (m_t.isCreation())
gasCost = (m_t.init.size() + m_t.data.size()) * c_txDataGas + c_createGas;
else
gasCost = t.data.size() * c_txDataGas + c_callGas;
gasCost = m_t.data.size() * c_txDataGas + c_callGas;
if (t.gas < gasCost)
if (m_t.gas < gasCost)
{
clog(StateChat) << "Not enough gas to pay for the transaction.";
throw OutOfGas();
}
u256 cost = t.value + t.gas * t.gasPrice;
m_startGas = m_t.gas;
u256 cost = m_t.value + m_t.gas * m_t.gasPrice;
// Avoid unaffordable transactions.
if (balance(sender) < cost)
if (m_s.balance(sender) < cost)
{
clog(StateChat) << "Not enough cash.";
throw NotEnoughCash();
}
u256 gas = t.gas - gasCost;
// Increment associated nonce for sender.
noteSending(sender);
m_s.noteSending(sender);
// Pay...
cnote << "Paying" << formatBalance(cost) << "from sender (includes" << t.gas << "gas at" << formatBalance(t.gasPrice) << ")";
subBalance(sender, cost);
cnote << "Paying" << formatBalance(cost) << "from sender (includes" << m_t.gas << "gas at" << formatBalance(m_t.gasPrice) << ")";
m_s.subBalance(sender, cost);
if (t.isCreation())
create(sender, t.value, t.gasPrice, &gas, &t.data, &t.init);
if (m_t.isCreation())
create(sender, m_t.value, m_t.gasPrice, m_t.gas - gasCost, &m_t.data, &m_t.init, sender);
else
call(t.receiveAddress, sender, t.value, t.gasPrice, bytesConstRef(&t.data), &gas, bytesRef());
call(m_t.receiveAddress, sender, m_t.value, m_t.gasPrice, bytesConstRef(&m_t.data), m_t.gas - gasCost, sender);
}
cnote << "Refunding" << formatBalance(gas * t.gasPrice) << "to sender (=" << gas << "*" << formatBalance(t.gasPrice) << ")";
addBalance(sender, gas * t.gasPrice);
void Executive::call(Address _receiveAddress, Address _senderAddress, u256 _value, u256 _gasPrice, bytesConstRef _data, u256 _gas, Address _originAddress)
{
cnote << "Transferring" << formatBalance(_value) << "to receiver.";
m_s.addBalance(_receiveAddress, _value);
u256 gasSpent = (t.gas - gas) * t.gasPrice;
if (m_s.isContractAddress(_receiveAddress))
{
m_vm = new VM(_gas);
m_ext = new ExtVM(m_s, _receiveAddress, _senderAddress, _originAddress, _value, _gasPrice, _data, &m_s.contractCode(_receiveAddress));
}
else
m_endGas = _gas;
}
void Executive::create(Address _sender, u256 _endowment, u256 _gasPrice, u256 _gas, bytesConstRef _code, bytesConstRef _init, Address _origin)
{
m_newAddress = right160(sha3(rlpList(_sender, m_s.transactionsFrom(_sender) - 1)));
while (m_s.isContractAddress(m_newAddress) || m_s.isNormalAddress(m_newAddress))
m_newAddress = (u160)m_newAddress + 1;
// Set up new account...
m_s.m_cache[m_newAddress] = AddressState(0, 0, _code);
// Execute _init.
m_vm = new VM(_gas);
m_ext = new ExtVM(m_s, m_newAddress, _sender, _origin, _endowment, _gasPrice, bytesConstRef(), _init);
}
bool Executive::go(uint64_t _steps)
{
if (m_vm)
{
bool revert = false;
try
{
m_vm->go(*m_ext, _steps);
m_endGas = m_vm->gas();
}
catch (StepsDone const&)
{
return false;
}
catch (OutOfGas const& /*_e*/)
{
clog(StateChat) << "Out of Gas! Reverting.";
revert = true;
}
catch (VMException const& _e)
{
clog(StateChat) << "VM Exception: " << _e.description();
}
catch (Exception const& _e)
{
clog(StateChat) << "Exception in VM: " << _e.description();
}
catch (std::exception const& _e)
{
clog(StateChat) << "std::exception in VM: " << _e.what();
}
// Write state out only in the case of a non-excepted transaction.
if (revert)
{
m_ext->revert();
if (m_newAddress)
{
m_s.m_cache.erase(m_newAddress);
m_newAddress = Address();
}
}
}
return true;
}
u256 Executive::gas() const
{
return m_vm->gas();
}
void Executive::finalize()
{
cnote << "Refunding" << formatBalance(m_endGas * m_ext->gasPrice) << "to origin (=" << m_endGas << "*" << formatBalance(m_ext->gasPrice) << ")";
m_s.addBalance(m_ext->origin, m_endGas * m_ext->gasPrice);
u256 gasSpent = (m_startGas - m_endGas) * m_ext->gasPrice;
/* unsigned c_feesKept = 8;
u256 feesEarned = gasSpent - (gasSpent / c_feesKept);
cnote << "Transferring" << (100.0 - 100.0 / c_feesKept) << "% of" << formatBalance(gasSpent) << "=" << formatBalance(feesEarned) << "to miner (" << formatBalance(gasSpent - feesEarned) << "is burnt).";
*/
u256 feesEarned = gasSpent;
cnote << "Transferring" << formatBalance(gasSpent) << "to miner.";
addBalance(m_currentBlock.coinbaseAddress, feesEarned);
// Add to the user-originated transactions that we've executed.
m_transactions.push_back(t);
m_transactionSet.insert(t.sha3());
}
void State::finaliseExecution()
{
m_s.addBalance(m_s.m_currentBlock.coinbaseAddress, feesEarned);
}
void State::execute(bytesConstRef _rlp)
@ -750,6 +831,7 @@ void State::execute(bytesConstRef _rlp)
cnote << "Transferring" << formatBalance(gasSpent) << "to miner.";
addBalance(m_currentBlock.coinbaseAddress, feesEarned);
// !!!!!!!!!!!!!!!!!!!!! If moving to use Executive, this still needs to be done - Executive won't do it.
// Add to the user-originated transactions that we've executed.
m_transactions.push_back(t);
m_transactionSet.insert(t.sha3());

33
libethereum/State.h

@ -50,17 +50,42 @@ static const bytes EmptyBytes;
struct StateChat: public LogChannel { static const char* name() { return "=S="; } static const int verbosity = 4; };
class VM;
class ExtVM;
class State;
class ExecutionState
class Executive
{
public:
ExecutionState(State& _s): m_s(_s) {}
Executive(State& _s): m_s(_s) {}
~Executive();
void setup(bytesConstRef _transaction);
void create(Address _txSender, u256 _endowment, u256 _gasPrice, u256 _gas, bytesConstRef _code, bytesConstRef _init, Address _originAddress);
void call(Address _myAddress, Address _txSender, u256 _txValue, u256 _gasPrice, bytesConstRef _txData, u256 _gas, Address _originAddress);
bool go(uint64_t _steps = (unsigned)-1);
void finalize();
u256 gas() const;
bytesConstRef out() const { return m_out; }
h160 newAddress() const { return m_newAddress; }
VM const& vm() const { return *m_vm; }
State const& state() const { return m_s; }
ExtVM const& ext() const { return *m_ext; }
private:
State& m_s;
ExtVM* m_ext = nullptr; // TODO: make safe.
VM* m_vm = nullptr;
bytesConstRef m_out;
Address m_newAddress;
Transaction m_t;
u256 m_startGas;
u256 m_endGas;
};
/**
@ -72,10 +97,11 @@ class State
{
template <unsigned T> friend class UnitTest;
friend class ExtVM;
friend class Executive;
public:
/// Construct state object.
State(Address _coinbaseAddress, Overlay const& _db);
State(Address _coinbaseAddress = Address(), Overlay const& _db = Overlay());
/// Copy state object.
State(State const& _s);
@ -135,7 +161,6 @@ public:
/// This will append @a _t to the transaction list and change the state accordingly.
void execute(bytes const& _rlp) { return execute(&_rlp); }
void execute(bytesConstRef _rlp);
std::shared_ptr<ExecutionState> executionState(bytesConstRef _rlp);
/// Check if the address is a valid normal (non-contract) account address.
bool isNormalAddress(Address _address) const;

3
libethereum/VM.cpp

@ -28,7 +28,4 @@ void VM::reset(u256 _gas)
{
m_gas = _gas;
m_curPC = 0;
m_nextPC = 1;
m_stepCount = 0;
m_runFee = 0;
}

29
libethereum/VM.h

@ -64,17 +64,17 @@ public:
void require(u256 _n) { if (m_stack.size() < _n) throw StackTooSmall(_n, m_stack.size()); }
void requireMem(unsigned _n) { if (m_temp.size() < _n) { m_temp.resize(_n); } }
u256 runFee() const { return m_runFee; }
u256 gas() const { return m_gas; }
u256 curPC() const { return m_curPC; }
bytes const& memory() const { return m_temp; }
u256s const& stack() const { return m_stack; }
private:
u256 m_gas = 0;
u256 m_curPC = 0;
u256 m_nextPC = 1;
uint64_t m_stepCount = 0;
bytes m_temp;
std::vector<u256> m_stack;
u256 m_runFee = 0;
u256s m_stack;
};
}
@ -82,10 +82,9 @@ private:
// INLINE:
template <class Ext> eth::bytesConstRef eth::VM::go(Ext& _ext, uint64_t _steps)
{
for (bool stopped = false; !stopped && _steps--; m_curPC = m_nextPC, m_nextPC = m_curPC + 1)
u256 nextPC = m_curPC + 1;
for (bool stopped = false; !stopped && _steps--; m_curPC = nextPC, nextPC = m_curPC + 1)
{
m_stepCount++;
// INSTRUCTION...
Instruction inst = (Instruction)_ext.getCode(m_curPC);
@ -310,7 +309,7 @@ template <class Ext> eth::bytesConstRef eth::VM::go(Ext& _ext, uint64_t _steps)
case Instruction::CALLDATALOAD:
{
require(1);
if ((unsigned)m_stack.back() + 32 < _ext.data.size())
if ((unsigned)m_stack.back() + 31 < _ext.data.size())
m_stack.back() = (u256)*(h256 const*)(_ext.data.data() + (unsigned)m_stack.back());
else
{
@ -379,10 +378,10 @@ template <class Ext> eth::bytesConstRef eth::VM::go(Ext& _ext, uint64_t _steps)
case Instruction::PUSH32:
{
int i = (int)inst - (int)Instruction::PUSH1 + 1;
m_nextPC = m_curPC + 1;
nextPC = m_curPC + 1;
m_stack.push_back(0);
for (; i--; m_nextPC++)
m_stack.back() = (m_stack.back() << 8) | _ext.getCode(m_nextPC);
for (; i--; nextPC++)
m_stack.back() = (m_stack.back() << 8) | _ext.getCode(nextPC);
break;
}
case Instruction::POP:
@ -456,13 +455,13 @@ template <class Ext> eth::bytesConstRef eth::VM::go(Ext& _ext, uint64_t _steps)
break;
case Instruction::JUMP:
require(1);
m_nextPC = m_stack.back();
nextPC = m_stack.back();
m_stack.pop_back();
break;
case Instruction::JUMPI:
require(2);
if (m_stack[m_stack.size() - 2])
m_nextPC = m_stack.back();
nextPC = m_stack.back();
m_stack.pop_back();
m_stack.pop_back();
break;
@ -557,7 +556,7 @@ template <class Ext> eth::bytesConstRef eth::VM::go(Ext& _ext, uint64_t _steps)
throw BadInstruction();
}
}
if (_steps == (unsigned)-1)
if (_steps == (uint64_t)-1)
throw StepsDone();
return bytesConstRef();
}

Loading…
Cancel
Save