You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
380 lines
12 KiB
380 lines
12 KiB
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
/** @file Debugger.cpp
|
|
* @author Gav Wood <i@gavwood.com>
|
|
* @date 2015
|
|
*/
|
|
|
|
#include "Debugger.h"
|
|
|
|
#include <fstream>
|
|
#include <QFileDialog>
|
|
#include <libevm/VM.h>
|
|
#include <libethereum/ExtVM.h>
|
|
#include <libethereum/Executive.h>
|
|
#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 {
|
|
if (_executive.setup(_transaction))
|
|
return false;
|
|
}
|
|
catch (...)
|
|
{
|
|
// Invalid transaction
|
|
return false;
|
|
}
|
|
|
|
vector<WorldState const*> 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<ExtVM const*>(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("<h3>OUT-OF-GAS</h3>");
|
|
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("<h3>RETURN</h3>" + QString::fromStdString(dev::memDump(out, 16, true)));
|
|
}
|
|
else if (ws.inst == Instruction::STOP)
|
|
ui->debugMemory->setHtml("<h3>STOP</h3>");
|
|
else if (ws.inst == Instruction::SUICIDE && ws.stack.size() >= 1)
|
|
ui->debugMemory->setHtml("<h3>SUICIDE</h3>0x" + QString::fromStdString(toString(right160(ws.stack.back()))));
|
|
else
|
|
ui->debugMemory->setHtml("<h3>EXCEPTION</h3>");
|
|
|
|
ostringstream ss;
|
|
ss << dec << "EXIT | GAS: " << dec << max<dev::bigint>(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("<div>" + m_context->prettyU256(i) + "</div>");
|
|
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() << "<br/>";
|
|
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;
|
|
}
|
|
}
|
|
|