/* This file is part of cpp-ethereum. cpp-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. cpp-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 cpp-ethereum. If not, see . */ /** @file Debugger.cpp * @author Gav Wood * @date 2015 */ #include "Debugger.h" #include #include #include #include #include #include "ui_Debugger.h" using namespace std; using namespace dev; using namespace dev::eth; Debugger::Debugger(Context* _c, QWidget* _parent): QDialog(_parent), ui(new Ui::Debugger), m_context(_c) { ui->setupUi(this); } Debugger::~Debugger() { delete ui; } void Debugger::init() { if (m_session.history.size()) { alterDebugStateGroup(true); ui->debugCode->setEnabled(false); ui->debugTimeline->setMinimum(0); ui->debugTimeline->setMaximum(m_session.history.size()); ui->debugTimeline->setValue(0); } } void Debugger::populate(dev::eth::Executive& _executive, dev::eth::Transaction const& _transaction) { finished(); if (m_session.populate(_executive, _transaction)) init(); update(); } bool DebugSession::populate(dev::eth::Executive& _executive, dev::eth::Transaction const& _transaction) { try { _executive.initialize(_transaction); if (_executive.execute()) return false; } catch (...) { // Invalid transaction return false; } vector levels; bytes lastExtCode; bytesConstRef lastData; h256 lastHash; h256 lastDataHash; auto onOp = [&](uint64_t steps, Instruction inst, dev::bigint newMemSize, dev::bigint gasCost, VM* voidVM, ExtVMFace const* voidExt) { VM& vm = *voidVM; ExtVM const& ext = *static_cast(voidExt); if (ext.code != lastExtCode) { lastExtCode = ext.code; lastHash = sha3(lastExtCode); if (!codes.count(lastHash)) codes[lastHash] = ext.code; } if (ext.data != lastData) { lastData = ext.data; lastDataHash = sha3(lastData); if (!codes.count(lastDataHash)) codes[lastDataHash] = ext.data.toBytes(); } if (levels.size() < ext.depth) levels.push_back(&history.back()); else levels.resize(ext.depth); history.append(WorldState({steps, ext.myAddress, vm.curPC(), inst, newMemSize, vm.gas(), lastHash, lastDataHash, vm.stack(), vm.memory(), gasCost, ext.state().storage(ext.myAddress), levels})); }; _executive.go(onOp); _executive.finalize(); return true; } void Debugger::finished() { m_session = DebugSession(); ui->callStack->clear(); ui->debugCode->clear(); ui->debugStack->clear(); ui->debugMemory->setHtml(""); ui->debugStorage->setHtml(""); ui->debugStateInfo->setText(""); alterDebugStateGroup(false); } void Debugger::update() { if (m_session.history.size()) { WorldState const& nws = m_session.history[min((int)m_session.history.size() - 1, ui->debugTimeline->value())]; WorldState const& ws = ui->callStack->currentRow() > 0 ? *nws.levels[nws.levels.size() - ui->callStack->currentRow()] : nws; if (ui->debugTimeline->value() >= m_session.history.size()) { if (ws.gasCost > ws.gas) ui->debugMemory->setHtml("

OUT-OF-GAS

"); else if (ws.inst == Instruction::RETURN && ws.stack.size() >= 2) { unsigned from = (unsigned)ws.stack.back(); unsigned size = (unsigned)ws.stack[ws.stack.size() - 2]; unsigned o = 0; bytes out(size, 0); for (; o < size && from + o < ws.memory.size(); ++o) out[o] = ws.memory[from + o]; ui->debugMemory->setHtml("

RETURN

" + QString::fromStdString(dev::memDump(out, 16, true))); } else if (ws.inst == Instruction::STOP) ui->debugMemory->setHtml("

STOP

"); else if (ws.inst == Instruction::SUICIDE && ws.stack.size() >= 1) ui->debugMemory->setHtml("

SUICIDE

0x" + QString::fromStdString(toString(right160(ws.stack.back())))); else ui->debugMemory->setHtml("

EXCEPTION

"); ostringstream ss; ss << dec << "EXIT | GAS: " << dec << max(0, (dev::bigint)ws.gas - ws.gasCost); ui->debugStateInfo->setText(QString::fromStdString(ss.str())); ui->debugStorage->setHtml(""); ui->debugCallData->setHtml(""); m_session.currentData = h256(); ui->callStack->clear(); m_session.currentLevels.clear(); ui->debugCode->clear(); m_session.currentCode = h256(); ui->debugStack->setHtml(""); } else { if (m_session.currentLevels != nws.levels || !ui->callStack->count()) { m_session.currentLevels = nws.levels; ui->callStack->clear(); for (unsigned i = 0; i <= nws.levels.size(); ++i) { WorldState const& s = i ? *nws.levels[nws.levels.size() - i] : nws; ostringstream out; out << s.cur.abridged(); if (i) out << " " << instructionInfo(s.inst).name << " @0x" << hex << s.curPC; ui->callStack->addItem(QString::fromStdString(out.str())); } } if (ws.code != m_session.currentCode) { m_session.currentCode = ws.code; bytes const& code = m_session.codes[ws.code]; QListWidget* dc = ui->debugCode; dc->clear(); m_session.pcWarp.clear(); for (unsigned i = 0; i <= code.size(); ++i) { byte b = i < code.size() ? code[i] : 0; try { QString s = QString::fromStdString(instructionInfo((Instruction)b).name); ostringstream out; out << hex << setw(4) << setfill('0') << i; m_session.pcWarp[i] = dc->count(); if (b >= (byte)Instruction::PUSH1 && b <= (byte)Instruction::PUSH32) { unsigned bc = b - (byte)Instruction::PUSH1 + 1; s = "PUSH 0x" + QString::fromStdString(toHex(bytesConstRef(&code[i + 1], bc))); i += bc; } dc->addItem(QString::fromStdString(out.str()) + " " + s); } catch (...) { cerr << "Unhandled exception!" << endl << boost::current_exception_diagnostic_information(); break; // probably hit data segment } } } if (ws.callData != m_session.currentData) { m_session.currentData = ws.callData; if (ws.callData) { assert(m_session.codes.count(ws.callData)); ui->debugCallData->setHtml(QString::fromStdString(dev::memDump(m_session.codes[ws.callData], 16, true))); } else ui->debugCallData->setHtml(""); } QString stack; for (auto i: ws.stack) stack.prepend("
" + m_context->prettyU256(i) + "
"); ui->debugStack->setHtml(stack); ui->debugMemory->setHtml(QString::fromStdString(dev::memDump(ws.memory, 16, true))); assert(m_session.codes.count(ws.code)); if (m_session.codes[ws.code].size() >= (unsigned)ws.curPC) { int l = m_session.pcWarp[(unsigned)ws.curPC]; ui->debugCode->setCurrentRow(max(0, l - 5)); ui->debugCode->setCurrentRow(min(ui->debugCode->count() - 1, l + 5)); ui->debugCode->setCurrentRow(l); } else cwarn << "PC (" << (unsigned)ws.curPC << ") is after code range (" << m_session.codes[ws.code].size() << ")"; ostringstream ss; ss << dec << "STEP: " << ws.steps << " | PC: 0x" << hex << ws.curPC << " : " << instructionInfo(ws.inst).name << " | ADDMEM: " << dec << ws.newMemSize << " words | COST: " << dec << ws.gasCost << " | GAS: " << dec << ws.gas; ui->debugStateInfo->setText(QString::fromStdString(ss.str())); stringstream s; for (auto const& i: ws.storage) s << "@" << m_context->prettyU256(i.first).toStdString() << "    " << m_context->prettyU256(i.second).toStdString() << "
"; ui->debugStorage->setHtml(QString::fromStdString(s.str())); } } } void Debugger::on_callStack_currentItemChanged() { update(); } void Debugger::alterDebugStateGroup(bool _enable) const { ui->stepOver->setEnabled(_enable); ui->stepInto->setEnabled(_enable); ui->stepOut->setEnabled(_enable); ui->backOver->setEnabled(_enable); ui->backInto->setEnabled(_enable); ui->backOut->setEnabled(_enable); ui->dump->setEnabled(_enable); ui->dumpStorage->setEnabled(_enable); ui->dumpPretty->setEnabled(_enable); } void Debugger::on_debugTimeline_valueChanged() { update(); } void Debugger::on_stepOver_clicked() { if (ui->debugTimeline->value() < m_session.history.size()) { auto l = m_session.history[ui->debugTimeline->value()].levels.size(); if ((ui->debugTimeline->value() + 1) < m_session.history.size() && m_session.history[ui->debugTimeline->value() + 1].levels.size() > l) { on_stepInto_clicked(); if (m_session.history[ui->debugTimeline->value()].levels.size() > l) on_stepOut_clicked(); } else on_stepInto_clicked(); } } void Debugger::on_stepInto_clicked() { ui->debugTimeline->setValue(ui->debugTimeline->value() + 1); ui->callStack->setCurrentRow(0); } void Debugger::on_stepOut_clicked() { if (ui->debugTimeline->value() < m_session.history.size()) { auto ls = m_session.history[ui->debugTimeline->value()].levels.size(); auto l = ui->debugTimeline->value(); for (; l < m_session.history.size() && m_session.history[l].levels.size() >= ls; ++l) {} ui->debugTimeline->setValue(l); ui->callStack->setCurrentRow(0); } } void Debugger::on_backInto_clicked() { ui->debugTimeline->setValue(ui->debugTimeline->value() - 1); ui->callStack->setCurrentRow(0); } void Debugger::on_backOver_clicked() { auto l = m_session.history[ui->debugTimeline->value()].levels.size(); if (ui->debugTimeline->value() > 0 && m_session.history[ui->debugTimeline->value() - 1].levels.size() > l) { on_backInto_clicked(); if (m_session.history[ui->debugTimeline->value()].levels.size() > l) on_backOut_clicked(); } else on_backInto_clicked(); } void Debugger::on_backOut_clicked() { if (ui->debugTimeline->value() > 0 && m_session.history.size() > 0) { auto ls = m_session.history[min(ui->debugTimeline->value(), m_session.history.size() - 1)].levels.size(); int l = ui->debugTimeline->value(); for (; l > 0 && m_session.history[l].levels.size() >= ls; --l) {} ui->debugTimeline->setValue(l); ui->callStack->setCurrentRow(0); } } void Debugger::on_dump_clicked() { QString fn = QFileDialog::getSaveFileName(this, "Select file to output EVM trace"); ofstream f(fn.toStdString()); if (f.is_open()) for (WorldState const& ws: m_session.history) f << ws.cur << " " << hex << toHex(dev::toCompactBigEndian(ws.curPC, 1)) << " " << hex << toHex(dev::toCompactBigEndian((int)(byte)ws.inst, 1)) << " " << hex << toHex(dev::toCompactBigEndian((uint64_t)ws.gas, 1)) << endl; } void Debugger::on_dumpPretty_clicked() { QString fn = QFileDialog::getSaveFileName(this, "Select file to output EVM trace"); ofstream f(fn.toStdString()); if (f.is_open()) for (WorldState const& ws: m_session.history) { f << endl << " STACK" << endl; for (auto i: ws.stack) f << (h256)i << endl; f << " MEMORY" << endl << dev::memDump(ws.memory); f << " STORAGE" << endl; for (auto const& i: ws.storage) f << showbase << hex << i.first << ": " << i.second << endl; f << dec << ws.levels.size() << " | " << ws.cur << " | #" << ws.steps << " | " << hex << setw(4) << setfill('0') << ws.curPC << " : " << instructionInfo(ws.inst).name << " | " << dec << ws.gas << " | -" << dec << ws.gasCost << " | " << ws.newMemSize << "x32"; } } void Debugger::on_dumpStorage_clicked() { QString fn = QFileDialog::getSaveFileName(this, "Select file to output EVM trace"); ofstream f(fn.toStdString()); if (f.is_open()) for (WorldState const& ws: m_session.history) { if (ws.inst == Instruction::STOP || ws.inst == Instruction::RETURN || ws.inst == Instruction::SUICIDE) for (auto i: ws.storage) f << toHex(dev::toCompactBigEndian(i.first, 1)) << " " << toHex(dev::toCompactBigEndian(i.second, 1)) << endl; f << ws.cur << " " << hex << toHex(dev::toCompactBigEndian(ws.curPC, 1)) << " " << hex << toHex(dev::toCompactBigEndian((int)(byte)ws.inst, 1)) << " " << hex << toHex(dev::toCompactBigEndian((uint64_t)ws.gas, 1)) << endl; } }