From f88bedc20c93cc9578b35e47f137d9f54bdeafda Mon Sep 17 00:00:00 2001 From: arkpar Date: Mon, 2 Mar 2015 01:13:10 +0100 Subject: [PATCH] basic source level debugging --- libsolidity/CompilerStack.cpp | 10 ++++ libsolidity/CompilerStack.h | 11 ++++ mix/ClientModel.cpp | 20 ++++++- mix/CodeModel.cpp | 2 + mix/CodeModel.h | 10 +++- mix/DebuggingStateWrapper.cpp | 9 +-- mix/DebuggingStateWrapper.h | 31 ++++++++-- mix/MachineStates.h | 13 ++++- mix/MixClient.cpp | 6 +- mix/qml/CodeEditorView.qml | 10 ++++ mix/qml/Debugger.qml | 19 ++++++- mix/qml/MainContent.qml | 9 +++ mix/qml/WebCodeEditor.qml | 4 ++ mix/qml/html/cm/solarized.css | 6 ++ mix/qml/html/codeeditor.js | 8 +++ mix/qml/js/Debugger.js | 103 +++++++++++++++++++++++++++++----- 16 files changed, 232 insertions(+), 39 deletions(-) diff --git a/libsolidity/CompilerStack.cpp b/libsolidity/CompilerStack.cpp index 69dbced00..10504a245 100644 --- a/libsolidity/CompilerStack.cpp +++ b/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; diff --git a/libsolidity/CompilerStack.h b/libsolidity/CompilerStack.h index 812f41863..74617836e 100644 --- a/libsolidity/CompilerStack.h +++ b/libsolidity/CompilerStack.h @@ -26,11 +26,18 @@ #include #include #include +#include #include #include #include namespace dev { + +namespace eth { + class AssemblyItem; + using AssemblyItems = std::vector; +} + 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; diff --git a/mix/ClientModel.cpp b/mix/ClientModel.cpp index 110bdc141..670093f30 100644 --- a/mix/ClientModel.cpp +++ b/mix/ClientModel.cpp @@ -305,8 +305,23 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t) QDebugData* debugData = new QDebugData(); QQmlEngine::setObjectOwnership(debugData, QQmlEngine::JavaScriptOwnership); QList 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 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); diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp index 6978cc936..cc2a29b42 100644 --- a/mix/CodeModel.cpp +++ b/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()) diff --git a/mix/CodeModel.h b/mix/CodeModel.h index 48dbbcd6c..511d2e01f 100644 --- a/mix/CodeModel.h +++ b/mix/CodeModel.h @@ -30,6 +30,7 @@ #include #include #include +#include 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; }; diff --git a/mix/DebuggingStateWrapper.cpp b/mix/DebuggingStateWrapper.cpp index d32be4989..6cb29bbae 100644 --- a/mix/DebuggingStateWrapper.cpp +++ b/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); diff --git a/mix/DebuggingStateWrapper.h b/mix/DebuggingStateWrapper.h index 606a6b3ae..2160b1cc0 100644 --- a/mix/DebuggingStateWrapper.h +++ b/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); diff --git a/mix/MachineStates.h b/mix/MachineStates.h index 9506dcf63..310d5cacd 100644 --- a/mix/MachineStates.h +++ b/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 machineStates; std::vector transactionData; - std::vector executionCode; + std::vector 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::max(); } + bool isConstructor() const { return !isCall() && !address; } }; using ExecutionResults = std::vector; diff --git a/mix/MixClient.cpp b/mix/MixClient.cpp index a78bc017d..e6c06fd5f 100644 --- a/mix/MixClient.cpp +++ b/mix/MixClient.cpp @@ -107,7 +107,7 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c execution.setup(&rlp); std::vector machineStates; std::vector levels; - std::vector codes; + std::vector codes; std::map codeIndexes; std::vector data; std::map 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})); }; diff --git a/mix/qml/CodeEditorView.qml b/mix/qml/CodeEditorView.qml index 439c36199..f401b42d9 100644 --- a/mix/qml/CodeEditorView.qml +++ b/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 { diff --git a/mix/qml/Debugger.qml b/mix/qml/Debugger.qml index 52587dd4e..07d1eedd8 100644 --- a/mix/qml/Debugger.qml +++ b/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 { diff --git a/mix/qml/MainContent.qml b/mix/qml/MainContent.qml index 15a7a638f..a2e273506 100644 --- a/mix/qml/MainContent.qml +++ b/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 diff --git a/mix/qml/WebCodeEditor.qml b/mix/qml/WebCodeEditor.qml index a1c273698..7fe7c76c0 100644 --- a/mix/qml/WebCodeEditor.qml +++ b/mix/qml/WebCodeEditor.qml @@ -37,6 +37,10 @@ Item { } } + function highlightExecution(location) { + editorBrowser.runJavaScript("highlightExecution(" + location.start + "," + location.end + ")"); + } + Connections { target: appContext onClipboardChanged: syncClipboard() diff --git a/mix/qml/html/cm/solarized.css b/mix/qml/html/cm/solarized.css index 07ef40680..f07b8b43b 100644 --- a/mix/qml/html/cm/solarized.css +++ b/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); +} + diff --git a/mix/qml/html/codeeditor.js b/mix/qml/html/codeeditor.js index ee93d5bf6..6595ef48f 100644 --- a/mix/qml/html/codeeditor.js +++ b/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" }); +} diff --git a/mix/qml/js/Debugger.js b/mix/qml/js/Debugger.js index 872d0aa36..7c9d268a7 100644 --- a/mix/qml/js/Debugger.js +++ b/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)