From 42f7833b971e9ed631c86d91e65ee1e655511695 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 10 Apr 2014 14:23:37 -0400 Subject: [PATCH] Debugger. --- alethzero/Main.ui | 56 +++++++++++++--- alethzero/MainWin.cpp | 138 ++++++++++++++++++++++++++++++--------- alethzero/MainWin.h | 11 ++-- libethereum/State.cpp | 148 ++++++++++++++++++++++++++++++++---------- libethereum/State.h | 33 ++++++++-- libethereum/VM.cpp | 3 - libethereum/VM.h | 29 ++++----- 7 files changed, 321 insertions(+), 97 deletions(-) diff --git a/alethzero/Main.ui b/alethzero/Main.ui index 68f27de91..975ac84ee 100644 --- a/alethzero/Main.ui +++ b/alethzero/Main.ui @@ -131,8 +131,8 @@ &Debug - - + + @@ -835,20 +835,54 @@ 8 + + false + Qt::Horizontal - - + + + + 0 + 0 + + + + + + 1 + 0 + + Qt::Vertical - - + + + QAbstractItemView::NoSelection + + + + + true + + + Qt::NoTextInteraction + + + + + true + + + Qt::NoTextInteraction + + @@ -934,7 +968,10 @@ &Debug EVM Execution - + + + false + &Single Step @@ -942,7 +979,10 @@ F10 - + + + false + &Continue diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 41e8b6d3a..f55115a04 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #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 << "    " << showbase << hex << i.second << "
"; s << "

Body Code

" << 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(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 << "    " << showbase << hex << i.second << "
"; + ui->debugStorage->setHtml(QString::fromStdString(s.str())); + } } // extra bits needed to link on VS diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index f9348ade2..758b249bb 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -7,6 +7,7 @@ #include #include #include +#include #include 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 m_currentExecution; + eth::State m_executiveState; + std::unique_ptr m_currentExecution; + QMap m_pcWarp; QNetworkAccessManager m_webCtrl; diff --git a/libethereum/State.cpp b/libethereum/State.cpp index 81b17e3d5..513971565 100644 --- a/libethereum/State.cpp +++ b/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()); diff --git a/libethereum/State.h b/libethereum/State.h index 2b2a79490..82a34cdf8 100644 --- a/libethereum/State.h +++ b/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 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(bytesConstRef _rlp); /// Check if the address is a valid normal (non-contract) account address. bool isNormalAddress(Address _address) const; diff --git a/libethereum/VM.cpp b/libethereum/VM.cpp index 6f69c64b3..5383f4906 100644 --- a/libethereum/VM.cpp +++ b/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; } diff --git a/libethereum/VM.h b/libethereum/VM.h index 99b0a4740..60584ab4e 100644 --- a/libethereum/VM.h +++ b/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 m_stack; - u256 m_runFee = 0; + u256s m_stack; }; } @@ -82,10 +82,9 @@ private: // INLINE: template 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 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 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 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 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(); }