Browse Source

basic source level debugging

cl-refactor
arkpar 10 years ago
parent
commit
f88bedc20c
  1. 10
      libsolidity/CompilerStack.cpp
  2. 11
      libsolidity/CompilerStack.h
  3. 20
      mix/ClientModel.cpp
  4. 2
      mix/CodeModel.cpp
  5. 10
      mix/CodeModel.h
  6. 9
      mix/DebuggingStateWrapper.cpp
  7. 31
      mix/DebuggingStateWrapper.h
  8. 13
      mix/MachineStates.h
  9. 6
      mix/MixClient.cpp
  10. 10
      mix/qml/CodeEditorView.qml
  11. 19
      mix/qml/Debugger.qml
  12. 9
      mix/qml/MainContent.qml
  13. 4
      mix/qml/WebCodeEditor.qml
  14. 6
      mix/qml/html/cm/solarized.css
  15. 8
      mix/qml/html/codeeditor.js
  16. 103
      mix/qml/js/Debugger.js

10
libsolidity/CompilerStack.cpp

@ -155,6 +155,16 @@ bytes const& CompilerStack::compile(string const& _sourceCode, bool _optimize)
return getBytecode();
}
eth::AssemblyItems const& CompilerStack::getAssemblyItems(string const& _contractName) const
{
return getContract(_contractName).compiler->getAssemblyItems();
}
eth::AssemblyItems const& CompilerStack::getRuntimeAssemblyItems(string const& _contractName) const
{
return getContract(_contractName).compiler->getRuntimeAssemblyItems();
}
bytes const& CompilerStack::getBytecode(string const& _contractName) const
{
return getContract(_contractName).bytecode;

11
libsolidity/CompilerStack.h

@ -26,11 +26,18 @@
#include <ostream>
#include <string>
#include <memory>
#include <vector>
#include <boost/noncopyable.hpp>
#include <libdevcore/Common.h>
#include <libdevcore/FixedHash.h>
namespace dev {
namespace eth {
class AssemblyItem;
using AssemblyItems = std::vector<AssemblyItem>;
}
namespace solidity {
// forward declarations
@ -85,6 +92,10 @@ public:
bytes const& getBytecode(std::string const& _contractName = "") const;
/// @returns the runtime bytecode for the contract, i.e. the code that is returned by the constructor.
bytes const& getRuntimeBytecode(std::string const& _contractName = "") const;
/// @returns normal contract assembly items
eth::AssemblyItems const& getAssemblyItems(std::string const& _contractName = "") const;
/// @returns runtime contract assembly items
eth::AssemblyItems const& getRuntimeAssemblyItems(std::string const& _contractName = "") const;
/// @returns hash of the runtime bytecode for the contract, i.e. the code that is returned by the constructor.
dev::h256 getContractCodeHash(std::string const& _contractName = "") const;

20
mix/ClientModel.cpp

@ -305,8 +305,23 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t)
QDebugData* debugData = new QDebugData();
QQmlEngine::setObjectOwnership(debugData, QQmlEngine::JavaScriptOwnership);
QList<QCode*> codes;
for (bytes const& code: _t.executionCode)
codes.push_back(QMachineState::getHumanReadableCode(debugData, code));
for (MachineCode const& code: _t.executionCode)
{
codes.push_back(QMachineState::getHumanReadableCode(debugData, code.address, code.code));
//try to resolve contract for source level debugging
auto nameIter = m_contractNames.find(code.address);
if (nameIter != m_contractNames.end())
{
CompiledContract const& compilerRes = m_context->codeModel()->contract(nameIter->second);
eth::AssemblyItems assemblyItems = !_t.isConstructor() ? compilerRes.assemblyItems() : compilerRes.constructorAssemblyItems();
QVariantList locations;
for (eth::AssemblyItem const& item: assemblyItems)
{
locations.push_back(QVariant::fromValue(new QSourceLocation(debugData, item.getLocation().start, item.getLocation().end)));
}
codes.back()->setLocations(compilerRes.documentId(), std::move(locations));
}
}
QList<QCallData*> data;
for (bytes const& d: _t.transactionData)
@ -325,7 +340,6 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t)
debugDataReady(debugData);
}
void ClientModel::debugRecord(unsigned _index)
{
ExecutionResult const& e = m_client->executions().at(_index);

2
mix/CodeModel.cpp

@ -56,6 +56,8 @@ CompiledContract::CompiledContract(const dev::solidity::CompilerStack& _compiler
m_contract.reset(new QContractDefinition(&contractDefinition));
QQmlEngine::setObjectOwnership(m_contract.get(), QQmlEngine::CppOwnership);
m_bytes = _compiler.getBytecode(_contractName.toStdString());
m_assemblyItems = _compiler.getRuntimeAssemblyItems(_contractName.toStdString());
m_constructorAssemblyItems = _compiler.getAssemblyItems(_contractName.toStdString());
dev::solidity::InterfaceHandler interfaceHandler;
m_contractInterface = QString::fromStdString(*interfaceHandler.getABIInterface(contractDefinition));
if (m_contractInterface.isEmpty())

10
mix/CodeModel.h

@ -30,6 +30,7 @@
#include <QHash>
#include <libdevcore/Common.h>
#include <libdevcore/Guards.h>
#include <libevmcore/Assembly.h>
class QTextDocument;
@ -70,7 +71,7 @@ class CompiledContract: public QObject
Q_PROPERTY(QContractDefinition* contract READ contract)
Q_PROPERTY(QString contractInterface READ contractInterface CONSTANT)
Q_PROPERTY(QString codeHex READ codeHex CONSTANT)
Q_PROPERTY(QString documentId MEMBER m_documentId CONSTANT)
Q_PROPERTY(QString documentId READ documentId CONSTANT)
public:
/// Successful compilation result constructor
@ -86,6 +87,11 @@ public:
QString codeHex() const;
/// @returns contract definition in JSON format
QString contractInterface() const { return m_contractInterface; }
/// @return assebly item locations
eth::AssemblyItems const& assemblyItems() const { return m_assemblyItems; }
eth::AssemblyItems const& constructorAssemblyItems() const { return m_constructorAssemblyItems; }
/// @returns contract source Id
QString documentId() const { return m_documentId; }
private:
uint m_sourceHash;
@ -94,6 +100,8 @@ private:
dev::bytes m_bytes;
QString m_contractInterface;
QString m_documentId;
eth::AssemblyItems m_assemblyItems;
eth::AssemblyItems m_constructorAssemblyItems;
friend class CodeModel;
};

9
mix/DebuggingStateWrapper.cpp

@ -69,7 +69,7 @@ namespace
}
}
QCode* QMachineState::getHumanReadableCode(QObject* _owner, const bytes& _code)
QCode* QMachineState::getHumanReadableCode(QObject* _owner, const Address& _address, const bytes& _code)
{
QVariantList codeStr;
for (unsigned i = 0; i <= _code.size(); ++i)
@ -96,7 +96,7 @@ QCode* QMachineState::getHumanReadableCode(QObject* _owner, const bytes& _code)
break; // probably hit data segment
}
}
return new QCode(_owner, std::move(codeStr));
return new QCode(_owner, QString::fromStdString(toString(_address)), std::move(codeStr));
}
QBigInt* QMachineState::gasCost()
@ -152,11 +152,6 @@ QVariantList QMachineState::levels()
return levelList;
}
QString QMachineState::address()
{
return QString::fromStdString(toString(m_state.address));
}
QString QMachineState::instruction()
{
return QString::fromStdString(dev::eth::instructionInfo(m_state.inst).name);

31
mix/DebuggingStateWrapper.h

@ -53,6 +53,22 @@ private:
int m_processIndex;
};
class QSourceLocation: public QObject
{
Q_OBJECT
Q_PROPERTY(int start MEMBER m_start CONSTANT)
Q_PROPERTY(int end MEMBER m_end CONSTANT)
public:
QSourceLocation(QObject* _owner, int _start, int _end): QObject(_owner), m_start(_start), m_end(_end) {}
private:
int m_start;
int m_end;
};
/**
* @brief Shared container for lines
*/
@ -60,12 +76,19 @@ class QCode: public QObject
{
Q_OBJECT
Q_PROPERTY(QVariantList instructions MEMBER m_instructions CONSTANT)
Q_PROPERTY(QVariantList locations MEMBER m_locations CONSTANT)
Q_PROPERTY(QString address MEMBER m_address CONSTANT)
Q_PROPERTY(QString documentId MEMBER m_document CONSTANT)
public:
QCode(QObject* _owner, QVariantList&& _instrunctions): QObject(_owner), m_instructions(_instrunctions) {}
QCode(QObject* _owner, QString const& _address, QVariantList&& _instrunctions): QObject(_owner), m_instructions(_instrunctions), m_address(_address) {}
void setLocations(QString const& _document, QVariantList&& _locations) { m_document = _document; m_locations = _locations; }
private:
QVariantList m_instructions;
QString m_address;
QString m_document;
QVariantList m_locations;
};
/**
@ -110,7 +133,6 @@ class QMachineState: public QObject
Q_PROPERTY(QBigInt* gasCost READ gasCost CONSTANT)
Q_PROPERTY(QBigInt* gas READ gas CONSTANT)
Q_PROPERTY(QString instruction READ instruction CONSTANT)
Q_PROPERTY(QString address READ address CONSTANT)
Q_PROPERTY(QStringList debugStack READ debugStack CONSTANT)
Q_PROPERTY(QStringList debugStorage READ debugStorage CONSTANT)
Q_PROPERTY(QVariantList debugMemory READ debugMemory CONSTANT)
@ -121,6 +143,7 @@ class QMachineState: public QObject
Q_PROPERTY(QVariantList levels READ levels CONSTANT)
Q_PROPERTY(unsigned codeIndex READ codeIndex CONSTANT)
Q_PROPERTY(unsigned dataIndex READ dataIndex CONSTANT)
//Q_PROPERTY(unsigned locationIndex READ locationIndex CONSTANT)
public:
QMachineState(QObject* _owner, MachineState const& _state, QCode* _code, QCallData* _callData):
@ -133,8 +156,6 @@ public:
unsigned codeIndex() { return m_state.codeIndex; }
/// Get the call data id
unsigned dataIndex() { return m_state.dataIndex; }
/// Get address for call stack
QString address();
/// Get gas cost.
QBigInt* gasCost();
/// Get gas used.
@ -158,7 +179,7 @@ public:
/// Set the current processed machine state.
void setState(MachineState _state) { m_state = _state; }
/// Convert all machine states in human readable code.
static QCode* getHumanReadableCode(QObject* _owner, bytes const& _code);
static QCode* getHumanReadableCode(QObject* _owner, const Address& _address, const bytes& _code);
/// Convert call data into human readable form
static QCallData* getDebugCallData(QObject* _owner, bytes const& _data);

13
mix/MachineStates.h

@ -42,7 +42,6 @@ namespace mix
struct MachineState
{
uint64_t steps;
dev::Address address;
dev::u256 curPC;
dev::eth::Instruction inst;
dev::bigint newMemSize;
@ -56,6 +55,15 @@ namespace mix
unsigned dataIndex;
};
/**
* @brief Executed conract code info
*/
struct MachineCode
{
dev::Address address;
bytes code;
};
/**
* @brief Store information about a machine states.
*/
@ -65,7 +73,7 @@ namespace mix
std::vector<MachineState> machineStates;
std::vector<bytes> transactionData;
std::vector<bytes> executionCode;
std::vector<MachineCode> executionCode;
bytes returnValue;
dev::Address address;
dev::Address sender;
@ -74,6 +82,7 @@ namespace mix
unsigned transactionIndex;
bool isCall() const { return transactionIndex == std::numeric_limits<unsigned>::max(); }
bool isConstructor() const { return !isCall() && !address; }
};
using ExecutionResults = std::vector<ExecutionResult>;

6
mix/MixClient.cpp

@ -107,7 +107,7 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c
execution.setup(&rlp);
std::vector<MachineState> machineStates;
std::vector<unsigned> levels;
std::vector<bytes> codes;
std::vector<MachineCode> codes;
std::map<bytes const*, unsigned> codeIndexes;
std::vector<bytes> data;
std::map<bytesConstRef const*, unsigned> dataIndexes;
@ -127,7 +127,7 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c
else
{
codeIndex = codes.size();
codes.push_back(ext.code);
codes.push_back(MachineCode({ext.myAddress, ext.code}));
codeIndexes[&ext.code] = codeIndex;
}
lastCode = &ext.code;
@ -152,7 +152,7 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c
else
levels.resize(ext.depth);
machineStates.emplace_back(MachineState({steps, ext.myAddress, vm.curPC(), inst, newMemSize, vm.gas(),
machineStates.emplace_back(MachineState({steps, vm.curPC(), inst, newMemSize, vm.gas(),
vm.stack(), vm.memory(), gasCost, ext.state().storage(ext.myAddress), levels, codeIndex, dataIndex}));
};

10
mix/qml/CodeEditorView.qml

@ -48,6 +48,16 @@ Item {
editor.setText(data, document.syntaxMode);
}
function highlightExecution(documentId, location) {
for (var i = 0; i < editorListModel.count; i++)
if (editorListModel.get(i).documentId === documentId) {
var editor = editors.itemAt(i).item;
if (editor)
editor.highlightExecution(location);
}
}
Component.onCompleted: projectModel.codeEditor = codeEditorView;
Connections {

19
mix/qml/Debugger.qml

@ -11,7 +11,9 @@ import "."
Rectangle {
id: debugPanel
property alias transactionLog : transactionLog
property alias transactionLog: transactionLog
signal debugExecuteLocation(string documentId, var location)
property bool assemblyMode: false
objectName: "debugPanel"
color: "#ededed"
@ -23,6 +25,12 @@ Rectangle {
forceActiveFocus();
}
onAssemblyModeChanged:
{
Debugger.updateMode();
}
function update(data, giveFocus)
{
if (statusPane && codeModel.hasContract)
@ -46,6 +54,10 @@ Rectangle {
forceActiveFocus();
}
ListModel {
id: breakpointModel;
}
Connections {
target: clientModel
onDebugDataReady: {
@ -173,7 +185,7 @@ Rectangle {
anchors.bottom: parent.bottom
anchors.left: parent.left
color: "transparent"
width: stateListContainer.width
width: parent.width * 0.4
RowLayout {
anchors.horizontalCenter: parent.horizontalCenter
id: jumpButtons
@ -256,7 +268,7 @@ Rectangle {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
width: debugInfoContainer.width
width: parent.width * 0.6
color: "transparent"
Slider {
id: statesSlider
@ -291,6 +303,7 @@ Rectangle {
height: 405
implicitHeight: 405
color: "transparent"
visible: assemblyMode
Rectangle
{

9
mix/qml/MainContent.qml

@ -40,6 +40,14 @@ Rectangle {
}
}
Connections {
target: rightView
onDebugExecuteLocation: {
codeEditor.highlightExecution(documentId, location);
}
}
function startQuickDebugging()
{
ensureRightView();
@ -168,6 +176,7 @@ Rectangle {
anchors.fill: parent
orientation: Qt.Vertical
CodeEditorView {
id: codeEditor
height: parent.height * 0.6
anchors.top: parent.top
Layout.fillWidth: true

4
mix/qml/WebCodeEditor.qml

@ -37,6 +37,10 @@ Item {
}
}
function highlightExecution(location) {
editorBrowser.runJavaScript("highlightExecution(" + location.start + "," + location.end + ")");
}
Connections {
target: appContext
onClipboardChanged: syncClipboard()

6
mix/qml/html/cm/solarized.css

@ -163,3 +163,9 @@ view-port
.cm-s-solarized.cm-s-light .CodeMirror-activeline-background {
background: rgba(0, 0, 0, 0.10);
}
/* Code execution */
.CodeMirror-exechighlight {
background: rgba(255, 255, 255, 0.10);
}

8
mix/qml/html/codeeditor.js

@ -60,3 +60,11 @@ setMode = function(mode) {
setClipboardBase64 = function(text) {
clipboard = window.atob(text);
};
var executionMark;
highlightExecution = function(start, end) {
if (executionMark)
executionMark.clear();
executionMark = editor.markText(editor.posFromIndex(start), editor.posFromIndex(end), { className: "CodeMirror-exechighlight" });
//executionMark = editor.markText(editor.posFromIndex(start), editor.posFromIndex(end), { css: "color: #fe3" });
}

103
mix/qml/js/Debugger.js

@ -5,6 +5,8 @@ var currentSelectedState = null;
var currentDisplayedState = null;
var debugData = null;
var codeMap = null;
var locations = [];
var locationMap = {};
function init(data)
{
@ -22,6 +24,8 @@ function init(data)
currentSelectedState = null;
currentDisplayedState = null;
debugData = null;
locations = [];
locationMap = {};
return;
}
@ -30,9 +34,50 @@ function init(data)
currentDisplayedState = 0;
setupInstructions(currentSelectedState);
setupCallData(currentSelectedState);
statesSlider.maximumValue = data.states.length - 1;
initLocations();
initSlider();
selectState(currentSelectedState);
}
function updateMode()
{
initSlider();
}
function initLocations()
{
locations = [];
if (debugData.states.length === 0)
return;
var prevLocation = { start: -1, end: -1, documentId: "", state: 0 };
var nullLocation = { start: -1, end: -1, documentId: "", state: 0};
for (var i = 0; i < debugData.states.length - 1; i++) {
var code = debugData.states[i].code;
var location = (code.documentId !== "") ? code.locations[i] : nullLocation;
if (location.start !== prevLocation.start || location.end !== prevLocation.end || code.documentId !== prevLocation.documentId)
{
prevLocation = { start: location.start, end: location.end, documentId: code.documentId, state: i };
locations.push(prevLocation);
}
locationMap[i] = locations.length - 1;
}
}
function srcMode()
{
return !assemblyMode && locations.length;
}
function initSlider()
{
if (srcMode()) {
statesSlider.maximumValue = locations.length - 1;
} else {
statesSlider.maximumValue = debugData.states.length - 1;
}
statesSlider.value = 0;
select(currentSelectedState);
}
function setupInstructions(stateIndex)
@ -54,11 +99,13 @@ function setupCallData(stateIndex)
function moveSelection(incr)
{
var prevState = currentSelectedState;
if (currentSelectedState + incr >= 0)
{
if (currentSelectedState + incr < debugData.states.length)
select(currentSelectedState + incr);
if (srcMode()) {
var locationIndex = locationMap[currentSelectedState];
if (locationIndex + incr >= 0 && locationIndex + incr < locations.length)
selectState(locations[locationIndex + incr].state);
} else {
if (currentSelectedState + incr >= 0 && currentSelectedState + incr < debugData.states.length)
selectState(currentSelectedState + incr);
}
}
@ -77,6 +124,9 @@ function display(stateIndex)
highlightSelection(codeLine);
completeCtxInformation(state);
currentDisplayedState = stateIndex;
var docId = debugData.states[stateIndex].code.documentId;
if (docId)
debugExecuteLocation(docId, debugData.states[stateIndex].code.locations[stateIndex]);
}
function displayFrame(frameIndex)
@ -88,12 +138,20 @@ function displayFrame(frameIndex)
display(state.levels[frameIndex - 1]);
}
function select(stateIndex)
function select(index)
{
if (srcMode()) {
selectState(locations[index].state);
}
else
selectState(index);
}
function selectState(stateIndex)
{
display(stateIndex);
currentSelectedState = stateIndex;
var state = debugData.states[stateIndex];
statesSlider.value = stateIndex;
jumpIntoForwardAction.enabled(stateIndex < debugData.states.length - 1)
jumpIntoBackAction.enabled(stateIndex > 0);
jumpOverForwardAction.enabled(stateIndex < debugData.states.length - 1);
@ -103,11 +161,15 @@ function select(stateIndex)
var callStackData = [];
for (var l = 0; l < state.levels.length; l++) {
var address = debugData.states[state.levels[l] + 1].address;
var address = debugData.states[state.levels[l] + 1].code.address;
callStackData.push(address);
}
callStackData.push(debugData.states[0].address);
callStackData.push(debugData.states[0].code.address);
callStack.listModel = callStackData;
if (srcMode())
statesSlider.value = locationMap[stateIndex];
else
statesSlider.value = stateIndex;
}
function codeStr(stateIndex)
@ -147,6 +209,11 @@ function isReturnInstruction(index)
return state.instruction === "RETURN"
}
function breakpointHit(i)
{
return false;
}
function stepIntoBack()
{
moveSelection(-1);
@ -177,21 +244,26 @@ function stepOutBack()
{
var i = currentSelectedState - 1;
var depth = 0;
while (--i >= 0)
while (--i >= 0) {
if (breakpointHit(i))
break;
if (isCallInstruction(i))
if (depth == 0)
break;
else depth--;
else if (isReturnInstruction(i))
depth++;
select(i);
}
selectState(i);
}
function stepOutForward()
{
var i = currentSelectedState;
var depth = 0;
while (++i < debugData.states.length)
while (++i < debugData.states.length) {
if (breakpointHit(i))
break;
if (isReturnInstruction(i))
if (depth == 0)
break;
@ -199,7 +271,8 @@ function stepOutForward()
depth--;
else if (isCallInstruction(i))
depth++;
select(i + 1);
}
selectState(i + 1);
}
function jumpTo(value)

Loading…
Cancel
Save