From 03673b58462e076a2d842708d10f63fffe9e7e15 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Sun, 11 Jun 2023 21:41:11 +0200 Subject: [PATCH] feat: Added basic Pattern Language debugger --- lib/external/imgui/source/TextEditor.cpp | 15 +- lib/libimhex/include/hex/api/keybinding.hpp | 14 +- lib/libimhex/source/api/keybinding.cpp | 19 +- .../content/views/view_pattern_editor.hpp | 5 + plugins/builtin/romfs/lang/en_US.json | 5 + plugins/builtin/romfs/themes/classic.json | 2 +- plugins/builtin/romfs/themes/dark.json | 2 +- plugins/builtin/romfs/themes/light.json | 2 +- .../source/content/views/view_bookmarks.cpp | 3 + .../content/views/view_pattern_editor.cpp | 199 ++++++++++++------ 10 files changed, 184 insertions(+), 82 deletions(-) diff --git a/lib/external/imgui/source/TextEditor.cpp b/lib/external/imgui/source/TextEditor.cpp index 449634c67..2de3e225c 100644 --- a/lib/external/imgui/source/TextEditor.cpp +++ b/lib/external/imgui/source/TextEditor.cpp @@ -823,14 +823,8 @@ void TextEditor::Render() { drawList->AddRectFilled(vstart, vend, mPalette[(int)PaletteIndex::Selection]); } - // Draw breakpoints auto start = ImVec2(lineStartScreenPos.x + scrollX, lineStartScreenPos.y); - if (mBreakpoints.count(lineNo + 1) != 0) { - auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, lineStartScreenPos.y + mCharAdvance.y); - drawList->AddRectFilled(start, end, mPalette[(int)PaletteIndex::Breakpoint]); - } - // Draw error markers auto errorIt = mErrorMarkers.find(lineNo + 1); if (errorIt != mErrorMarkers.end()) { @@ -856,6 +850,15 @@ void TextEditor::Render() { auto lineNoWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr).x; drawList->AddText(ImVec2(lineStartScreenPos.x + mTextStart - lineNoWidth, lineStartScreenPos.y), mPalette[(int)PaletteIndex::LineNumber], buf); + // Draw breakpoints + if (mBreakpoints.count(lineNo + 1) != 0) { + auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, lineStartScreenPos.y + mCharAdvance.y); + drawList->AddRectFilled(start + ImVec2(mTextStart, 0), end, mPalette[(int)PaletteIndex::Breakpoint]); + + drawList->AddCircleFilled(start + ImVec2(0, mCharAdvance.y) / 2, mCharAdvance.y / 3, mPalette[(int)PaletteIndex::Breakpoint]); + drawList->AddCircle(start + ImVec2(0, mCharAdvance.y) / 2, mCharAdvance.y / 3, mPalette[(int)PaletteIndex::Default]); + } + if (mState.mCursorPosition.mLine == lineNo) { auto focused = ImGui::IsWindowFocused(); diff --git a/lib/libimhex/include/hex/api/keybinding.hpp b/lib/libimhex/include/hex/api/keybinding.hpp index 3fad177e5..7445c2656 100644 --- a/lib/libimhex/include/hex/api/keybinding.hpp +++ b/lib/libimhex/include/hex/api/keybinding.hpp @@ -15,8 +15,7 @@ namespace hex { class View; - enum class Keys - { + enum class Keys { Space = GLFW_KEY_SPACE, Apostrophe = GLFW_KEY_APOSTROPHE, Comma = GLFW_KEY_COMMA, @@ -144,11 +143,12 @@ namespace hex { }; - constexpr static auto CTRL = Key(static_cast(0x0100'0000)); - constexpr static auto ALT = Key(static_cast(0x0200'0000)); - constexpr static auto SHIFT = Key(static_cast(0x0400'0000)); - constexpr static auto SUPER = Key(static_cast(0x0800'0000)); - constexpr static auto CurrentView = Key(static_cast(0x1000'0000)); + constexpr static auto CTRL = Key(static_cast(0x0100'0000)); + constexpr static auto ALT = Key(static_cast(0x0200'0000)); + constexpr static auto SHIFT = Key(static_cast(0x0400'0000)); + constexpr static auto SUPER = Key(static_cast(0x0800'0000)); + constexpr static auto CurrentView = Key(static_cast(0x1000'0000)); + constexpr static auto AllowWhileTyping = Key(static_cast(0x2000'0000)); #if defined (OS_MACOS) constexpr static auto CTRLCMD = SUPER; diff --git a/lib/libimhex/source/api/keybinding.cpp b/lib/libimhex/source/api/keybinding.cpp index c2cf8a2ab..7cc8c0f7e 100644 --- a/lib/libimhex/source/api/keybinding.cpp +++ b/lib/libimhex/source/api/keybinding.cpp @@ -37,18 +37,23 @@ namespace hex { void ShortcutManager::process(const std::unique_ptr ¤tView, bool ctrl, bool alt, bool shift, bool super, bool focused, u32 keyCode) { Shortcut pressedShortcut = getShortcut(ctrl, alt, shift, super, focused, keyCode); - if (ImGui::GetIO().WantTextInput) - return; - - if (currentView->m_shortcuts.contains(pressedShortcut)) - currentView->m_shortcuts[pressedShortcut](); + if (currentView->m_shortcuts.contains(pressedShortcut + AllowWhileTyping)) { + currentView->m_shortcuts[pressedShortcut + AllowWhileTyping](); + } else if (currentView->m_shortcuts.contains(pressedShortcut)) { + if (!ImGui::GetIO().WantTextInput) + currentView->m_shortcuts[pressedShortcut](); + } } void ShortcutManager::processGlobals(bool ctrl, bool alt, bool shift, bool super, u32 keyCode) { Shortcut pressedShortcut = getShortcut(ctrl, alt, shift, super, false, keyCode); - if (ShortcutManager::s_globalShortcuts.contains(pressedShortcut)) - ShortcutManager::s_globalShortcuts[pressedShortcut](); + if (ShortcutManager::s_globalShortcuts.contains(pressedShortcut + AllowWhileTyping)) { + ShortcutManager::s_globalShortcuts[pressedShortcut + AllowWhileTyping](); + } else if (ShortcutManager::s_globalShortcuts.contains(pressedShortcut)) { + if (!ImGui::GetIO().WantTextInput) + ShortcutManager::s_globalShortcuts[pressedShortcut](); + } } void ShortcutManager::clearShortcuts() { diff --git a/plugins/builtin/include/content/views/view_pattern_editor.hpp b/plugins/builtin/include/content/views/view_pattern_editor.hpp index c5620c871..b54e4db10 100644 --- a/plugins/builtin/include/content/views/view_pattern_editor.hpp +++ b/plugins/builtin/include/content/views/view_pattern_editor.hpp @@ -165,12 +165,17 @@ namespace hex::plugin::builtin { PerProvider> m_envVarEntries; PerProvider m_shouldAnalyze; + PerProvider m_breakpointHit; + PerProvider m_temporaryBreakpointLine; + PerProvider m_debuggerDrawer; + std::atomic m_resetDebuggerVariables; private: void drawConsole(ImVec2 size, const std::vector> &console); void drawEnvVars(ImVec2 size, std::list &envVars); void drawVariableSettings(ImVec2 size, std::map &patternVariables); void drawSectionSelector(ImVec2 size, std::map §ions); + void drawDebugger(ImVec2 size); void drawPatternTooltip(pl::ptrn::Pattern *pattern); diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index 2e133c37b..7bff69a21 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -819,6 +819,11 @@ "hex.builtin.view.pattern_editor.console": "Console", "hex.builtin.view.pattern_editor.dangerous_function.desc": "This pattern tried to call a dangerous function.\nAre you sure you want to trust this pattern?", "hex.builtin.view.pattern_editor.dangerous_function.name": "Allow dangerous function?", + "hex.builtin.view.pattern_editor.debugger": "Debugger", + "hex.builtin.view.pattern_editor.debugger.add_tooltip": "Add breakpoint", + "hex.builtin.view.pattern_editor.debugger.continue": "Continue", + "hex.builtin.view.pattern_editor.debugger.halted_line": "Halted at line {0}", + "hex.builtin.view.pattern_editor.debugger.remove_tooltip": "Remove breakpoint", "hex.builtin.view.pattern_editor.env_vars": "Environment Variables", "hex.builtin.view.pattern_editor.evaluating": "Evaluating...", "hex.builtin.view.pattern_editor.menu.edit.place_pattern": "Place pattern...", diff --git a/plugins/builtin/romfs/themes/classic.json b/plugins/builtin/romfs/themes/classic.json index d63718a96..6bdbb5c92 100644 --- a/plugins/builtin/romfs/themes/classic.json +++ b/plugins/builtin/romfs/themes/classic.json @@ -131,7 +131,7 @@ }, "text-editor": { "background": "#000080FF", - "breakpoint": "#0080FF80", + "breakpoint": "#FF200080", "char-literal": "#008080FF", "comment": "#808080FF", "current-line-edge": "#00000040", diff --git a/plugins/builtin/romfs/themes/dark.json b/plugins/builtin/romfs/themes/dark.json index e8beea84f..28a8be954 100644 --- a/plugins/builtin/romfs/themes/dark.json +++ b/plugins/builtin/romfs/themes/dark.json @@ -131,7 +131,7 @@ }, "text-editor": { "background": "#101010FF", - "breakpoint": "#0080F040", + "breakpoint": "#FF200040", "char-literal": "#E0A070FF", "comment": "#206020FF", "current-line-edge": "#A0A0A040", diff --git a/plugins/builtin/romfs/themes/light.json b/plugins/builtin/romfs/themes/light.json index 48a5041f5..deb4fc5fc 100644 --- a/plugins/builtin/romfs/themes/light.json +++ b/plugins/builtin/romfs/themes/light.json @@ -131,7 +131,7 @@ }, "text-editor": { "background": "#FFFFFFFF", - "breakpoint": "#0080F080", + "breakpoint": "#FF200080", "char-literal": "#704030FF", "comment": "#205020FF", "current-line-edge": "#00000040", diff --git a/plugins/builtin/source/content/views/view_bookmarks.cpp b/plugins/builtin/source/content/views/view_bookmarks.cpp index 5bd413806..9b187877d 100644 --- a/plugins/builtin/source/content/views/view_bookmarks.cpp +++ b/plugins/builtin/source/content/views/view_bookmarks.cpp @@ -382,6 +382,9 @@ namespace hex::plugin::builtin { void ViewBookmarks::registerMenuItems() { /* Create bookmark */ ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.menu.edit.bookmark.create" }, 1900, CTRLCMD + Keys::B, [&] { + if (!ImHexApi::HexEditor::isSelectionValid()) + return; + auto selection = ImHexApi::HexEditor::getSelection(); ImHexApi::Bookmarks::add(selection->getStartAddress(), selection->getSize(), {}, {}); }, []{ return ImHexApi::Provider::isValid() && ImHexApi::HexEditor::isSelectionValid(); }); diff --git a/plugins/builtin/source/content/views/view_pattern_editor.cpp b/plugins/builtin/source/content/views/view_pattern_editor.cpp index f9e10b5e4..99a2a0979 100644 --- a/plugins/builtin/source/content/views/view_pattern_editor.cpp +++ b/plugins/builtin/source/content/views/view_pattern_editor.cpp @@ -137,6 +137,10 @@ namespace hex::plugin::builtin { this->drawSectionSelector(settingsSize, *this->m_sections); ImGui::EndTabItem(); } + if (ImGui::BeginTabItem("hex.builtin.view.pattern_editor.debugger"_lang)) { + this->drawDebugger(settingsSize); + ImGui::EndTabItem(); + } ImGui::EndTabBar(); } @@ -421,7 +425,6 @@ namespace hex::plugin::builtin { } void ViewPatternEditor::drawSectionSelector(ImVec2 size, std::map §ions) { - auto lock = std::scoped_lock(ContentRegistry::PatternLanguage::getRuntimeLock()); auto &runtime = ContentRegistry::PatternLanguage::getRuntime(); if (ImGui::BeginTable("##sections_table", 3, ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, size)) { @@ -432,75 +435,130 @@ namespace hex::plugin::builtin { ImGui::TableHeadersRow(); - for (auto &[id, section] : sections) { - ImGui::PushID(id); + if (TRY_LOCK(ContentRegistry::PatternLanguage::getRuntimeLock())) { + for (auto &[id, section] : sections) { + ImGui::PushID(id); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); - ImGui::TextUnformatted(section.name.c_str()); - ImGui::TableNextColumn(); - ImGui::TextFormatted("{} | 0x{:02X}", hex::toByteString(section.data.size()), section.data.size()); - ImGui::TableNextColumn(); - if (ImGui::IconButton(ICON_VS_OPEN_PREVIEW, ImGui::GetStyleColorVec4(ImGuiCol_Text))) { - auto dataProvider = std::make_unique(); - dataProvider->resize(section.data.size()); - dataProvider->writeRaw(0x00, section.data.data(), section.data.size()); - dataProvider->setReadOnly(true); + ImGui::TextUnformatted(section.name.c_str()); + ImGui::TableNextColumn(); + ImGui::TextFormatted("{} | 0x{:02X}", hex::toByteString(section.data.size()), section.data.size()); + ImGui::TableNextColumn(); + if (ImGui::IconButton(ICON_VS_OPEN_PREVIEW, ImGui::GetStyleColorVec4(ImGuiCol_Text))) { + auto dataProvider = std::make_unique(); + dataProvider->resize(section.data.size()); + dataProvider->writeRaw(0x00, section.data.data(), section.data.size()); + dataProvider->setReadOnly(true); - auto hexEditor = auto(this->m_sectionHexEditor); + auto hexEditor = auto(this->m_sectionHexEditor); - hexEditor.setBackgroundHighlightCallback([this, id, &runtime](u64 address, const u8 *, size_t) -> std::optional { - if (this->m_runningEvaluators != 0) - return std::nullopt; - if (!ImHexApi::Provider::isValid()) - return std::nullopt; + hexEditor.setBackgroundHighlightCallback([this, id, &runtime](u64 address, const u8 *, size_t) -> std::optional { + if (this->m_runningEvaluators != 0) + return std::nullopt; + if (!ImHexApi::Provider::isValid()) + return std::nullopt; - std::optional color; - for (const auto &pattern : runtime.getPatternsAtAddress(address, id)) { - if (pattern->getVisibility() != pl::ptrn::Visibility::Visible) - continue; + std::optional color; + for (const auto &pattern : runtime.getPatternsAtAddress(address, id)) { + if (pattern->getVisibility() != pl::ptrn::Visibility::Visible) + continue; - if (color.has_value()) - color = ImAlphaBlendColors(*color, pattern->getColor()); - else - color = pattern->getColor(); - } + if (color.has_value()) + color = ImAlphaBlendColors(*color, pattern->getColor()); + else + color = pattern->getColor(); + } - return color; - }); - - auto patternProvider = ImHexApi::Provider::get(); - - - this->m_sectionWindowDrawer[patternProvider] = [this, id, patternProvider, dataProvider = std::move(dataProvider), hexEditor, patternDrawer = ui::PatternDrawer(), &runtime] mutable { - hexEditor.setProvider(dataProvider.get()); - hexEditor.draw(480_scaled); - patternDrawer.setSelectionCallback([&](const auto ®ion) { - hexEditor.setSelection(region); + return color; }); - const auto &patterns = [&, this] -> const auto& { - if (patternProvider->isReadable() && *this->m_executionDone) - return runtime.getPatterns(id); - else { - static const std::vector> empty; - return empty; - } - }(); + auto patternProvider = ImHexApi::Provider::get(); - if (*this->m_executionDone) - patternDrawer.draw(patterns, &runtime, 150_scaled); - }; + + this->m_sectionWindowDrawer[patternProvider] = [this, id, patternProvider, dataProvider = std::move(dataProvider), hexEditor, patternDrawer = ui::PatternDrawer(), &runtime] mutable { + hexEditor.setProvider(dataProvider.get()); + hexEditor.draw(480_scaled); + patternDrawer.setSelectionCallback([&](const auto ®ion) { + hexEditor.setSelection(region); + }); + + const auto &patterns = [&, this] -> const auto& { + if (patternProvider->isReadable() && *this->m_executionDone) + return runtime.getPatterns(id); + else { + static const std::vector> empty; + return empty; + } + }(); + + if (*this->m_executionDone) + patternDrawer.draw(patterns, &runtime, 150_scaled); + }; + } + + ImGui::PopID(); } - - ImGui::PopID(); } ImGui::EndTable(); } } + void ViewPatternEditor::drawDebugger(ImVec2 size) { + auto &runtime = ContentRegistry::PatternLanguage::getRuntime(); + auto &evaluator = runtime.getInternals().evaluator; + + if (ImGui::BeginChild("##debugger", size, true, ImGuiWindowFlags_AlwaysVerticalScrollbar)) { + const auto &breakpoints = evaluator->getBreakpoints(); + auto line = this->m_textEditor.GetCursorPosition().mLine + 1; + + if (!breakpoints.contains(line)) { + if (ImGui::IconButton(ICON_VS_DEBUG_BREAKPOINT, ImGui::GetCustomColorVec4(ImGuiCustomCol_ToolbarRed))) { + evaluator->addBreakpoint(line); + this->m_textEditor.SetBreakpoints(breakpoints); + } + ImGui::InfoTooltip("hex.builtin.view.pattern_editor.debugger.add_tooltip"_lang); + } else { + if (ImGui::IconButton(ICON_VS_DEBUG_BREAKPOINT_UNVERIFIED, ImGui::GetCustomColorVec4(ImGuiCustomCol_ToolbarRed))) { + evaluator->removeBreakpoint(line); + this->m_textEditor.SetBreakpoints(breakpoints); + } + ImGui::InfoTooltip("hex.builtin.view.pattern_editor.debugger.remove_tooltip"_lang); + } + + ImGui::SameLine(0, 20_scaled); + + if (*this->m_breakpointHit) { + auto pauseLine = evaluator->getPauseLine(); + + if (ImGui::IconButton(ICON_VS_DEBUG_CONTINUE, ImGui::GetCustomColorVec4(ImGuiCustomCol_ToolbarGreen))) { + *this->m_breakpointHit = false; + } + ImGui::InfoTooltip("hex.builtin.view.pattern_editor.debugger.continue"_lang); + + if (pauseLine.has_value()) { + ImGui::SameLine(); + ImGui::TextFormatted("hex.builtin.view.pattern_editor.debugger.halted_line"_lang, pauseLine.value()); + } + + auto &variables = *evaluator->getScope(0).scope; + + if (this->m_resetDebuggerVariables) { + this->m_debuggerDrawer->reset(); + this->m_resetDebuggerVariables = false; + + if (pauseLine.has_value()) + this->m_textEditor.SetCursorPosition({ int(pauseLine.value() - 1), 0 }); + } + + this->m_debuggerDrawer->draw(variables, &runtime, size.y - ImGui::GetTextLineHeightWithSpacing() * 4); + } + } + ImGui::EndChild(); + } + void ViewPatternEditor::drawAlwaysVisible() { auto provider = ImHexApi::Provider::get(); @@ -576,16 +634,16 @@ namespace hex::plugin::builtin { value = wolv::util::trim(value); if (value.empty()) - return std::nullopt; + return std::nullopt; if (!value.starts_with('[')) - return std::nullopt; + return std::nullopt; value = value.substr(1); auto end = value.find(']'); if (end == std::string::npos) - return std::nullopt; + return std::nullopt; value = value.substr(0, end - 1); value = wolv::util::trim(value); @@ -597,11 +655,11 @@ namespace hex::plugin::builtin { value = wolv::util::trim(value); if (value.empty()) - return std::nullopt; + return std::nullopt; auto start = value.find('@'); if (start == std::string::npos) - return std::nullopt; + return std::nullopt; value = value.substr(start + 1); value = wolv::util::trim(value); @@ -609,7 +667,7 @@ namespace hex::plugin::builtin { size_t end = 0; auto result = std::stoull(value, &end, 0); if (end != value.length()) - return std::nullopt; + return std::nullopt; return result; }(); @@ -788,6 +846,13 @@ namespace hex::plugin::builtin { auto &runtime = ContentRegistry::PatternLanguage::getRuntime(); ContentRegistry::PatternLanguage::configureRuntime(runtime, provider); + runtime.getInternals().evaluator->setBreakpointHitCallback([this]{ + *this->m_breakpointHit = true; + this->m_resetDebuggerVariables = true; + while (*this->m_breakpointHit) { + std::this_thread::yield(); + } + }); task.setInterruptCallback([&runtime] { runtime.abort(); }); @@ -1113,6 +1178,22 @@ namespace hex::plugin::builtin { return true; } }); + + ShortcutManager::addShortcut(this, Keys::F8 + AllowWhileTyping, [this] { + auto line = this->m_textEditor.GetCursorPosition().mLine + 1; + auto &runtime = ContentRegistry::PatternLanguage::getRuntime(); + + auto &evaluator = runtime.getInternals().evaluator; + auto &breakpoints = evaluator->getBreakpoints(); + + if (breakpoints.contains(line)) { + evaluator->removeBreakpoint(line); + } else { + evaluator->addBreakpoint(line); + } + + this->m_textEditor.SetBreakpoints(breakpoints); + }); } } \ No newline at end of file