From 3dd0f755d05c807649eb86bd8e0472ce77661b41 Mon Sep 17 00:00:00 2001 From: Itamar Date: Mon, 4 May 2020 12:23:30 +0300 Subject: [PATCH] HackStudio: Show local variables in the debugger We now have a Debug Information tab, which displays the variables in the current scope in a tree view. --- DevTools/HackStudio/DebugInfoWidget.cpp | 132 ++++++++++++++++++++++++ DevTools/HackStudio/DebugInfoWidget.h | 69 +++++++++++++ DevTools/HackStudio/Debugger.cpp | 11 +- DevTools/HackStudio/Debugger.h | 7 +- DevTools/HackStudio/Makefile | 1 + DevTools/HackStudio/main.cpp | 13 ++- 6 files changed, 219 insertions(+), 14 deletions(-) create mode 100644 DevTools/HackStudio/DebugInfoWidget.cpp create mode 100644 DevTools/HackStudio/DebugInfoWidget.h diff --git a/DevTools/HackStudio/DebugInfoWidget.cpp b/DevTools/HackStudio/DebugInfoWidget.cpp new file mode 100644 index 0000000000..c84bd8325e --- /dev/null +++ b/DevTools/HackStudio/DebugInfoWidget.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2020, Itamar S. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "DebugInfoWidget.h" +#include "Debugger.h" +#include +#include +#include +#include + +GUI::ModelIndex DebugInfoModel::index(int row, int column, const GUI::ModelIndex& parent_index) const +{ + if (!parent_index.is_valid()) + return create_index(row, column, &m_variables[row]); + auto* parent = static_cast(parent_index.internal_data()); + auto* child = &parent->members[row]; + return create_index(row, column, child); +} + +GUI::ModelIndex DebugInfoModel::parent_index(const GUI::ModelIndex& index) const +{ + if (!index.is_valid()) + return {}; + auto* child = static_cast(index.internal_data()); + auto* parent = child->parent; + if (parent == nullptr) + return {}; + + if (parent->parent == nullptr) { + for (size_t row = 0; row < m_variables.size(); row++) + if (m_variables.ptr_at(row).ptr() == parent) + return create_index(row, 0, parent); + ASSERT_NOT_REACHED(); + } + for (size_t row = 0; row < parent->parent->members.size(); row++) { + DebugInfo::VariableInfo* child_at_row = parent->parent->members.ptr_at(row).ptr(); + if (child_at_row == parent) + return create_index(row, 0, parent); + } + ASSERT_NOT_REACHED(); +} + +int DebugInfoModel::row_count(const GUI::ModelIndex& index) const +{ + if (!index.is_valid()) + return m_variables.size(); + auto* node = static_cast(index.internal_data()); + return node->members.size(); +} + +String variable_value_as_string(const DebugInfo::VariableInfo& variable) +{ + if (variable.location_type != DebugInfo::VariableInfo::LocationType::Address) + return "N/A"; + + auto variable_address = variable.location_data.address; + + if (variable.type == "int") { + auto value = Debugger::the().session()->peek((u32*)variable_address); + ASSERT(value.has_value()); + return String::format("%d", static_cast(value.value())); + } + + if (variable.type == "char") { + auto value = Debugger::the().session()->peek((u32*)variable_address); + ASSERT(value.has_value()); + return String::format("'%c' (%d)", static_cast(value.value()), static_cast(value.value())); + } + + return String::format("address: %08x, ", variable_address); +} + +GUI::Variant DebugInfoModel::data(const GUI::ModelIndex& index, Role role) const +{ + auto* variable = static_cast(index.internal_data()); + switch (role) { + case Role::Display: { + auto value_as_string = variable_value_as_string(*variable); + return String::format("%s: %s", variable->name.characters(), value_as_string.characters()); + } + case Role::Icon: + return m_variable_icon; + default: + return {}; + } +} + +void DebugInfoModel::update() +{ + did_update(); +} + +static RefPtr create_model(const PtraceRegisters& regs) +{ + auto variables = Debugger::the().session()->debug_info().get_variables_in_current_scope(regs); + return adopt(*new DebugInfoModel(move(variables), regs)); +} + +DebugInfoWidget::DebugInfoWidget() +{ + set_layout(); + m_info_view = add(); +} + +void DebugInfoWidget::update_variables(const PtraceRegisters& regs) +{ + auto model = create_model(regs); + m_info_view->set_model(model); +} diff --git a/DevTools/HackStudio/DebugInfoWidget.h b/DevTools/HackStudio/DebugInfoWidget.h new file mode 100644 index 0000000000..83fad11d8c --- /dev/null +++ b/DevTools/HackStudio/DebugInfoWidget.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020, Itamar S. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "Debugger.h" +#include +#include +#include +#include + +class DebugInfoModel final : public GUI::Model { +public: + explicit DebugInfoModel(NonnullOwnPtrVector&& variables, const PtraceRegisters& regs) + : m_variables(move(variables)) + , m_regs(regs) + { + m_variable_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/inspector-object.png")); + } + + virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override; + virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return 1; } + virtual GUI::Variant data(const GUI::ModelIndex& index, Role role = Role::Display) const override; + virtual void update() override; + virtual GUI::ModelIndex parent_index(const GUI::ModelIndex&) const override; + virtual GUI::ModelIndex index(int row, int column = 0, const GUI::ModelIndex& = GUI::ModelIndex()) const override; + +private: + NonnullOwnPtrVector m_variables; + PtraceRegisters m_regs; + + GUI::Icon m_variable_icon; +}; + +class DebugInfoWidget final : public GUI::Widget { + C_OBJECT(DebugInfoWidget) +public: + virtual ~DebugInfoWidget() override {} + + void update_variables(const PtraceRegisters&); + +private: + explicit DebugInfoWidget(); + + RefPtr m_info_view; +}; diff --git a/DevTools/HackStudio/Debugger.cpp b/DevTools/HackStudio/Debugger.cpp index 2db430f016..e2b5f95f8b 100644 --- a/DevTools/HackStudio/Debugger.cpp +++ b/DevTools/HackStudio/Debugger.cpp @@ -35,7 +35,7 @@ Debugger& Debugger::the() } void Debugger::initialize( - Function on_stop_callback, + Function on_stop_callback, Function on_continue_callback, Function on_exit_callback) { @@ -48,7 +48,7 @@ bool Debugger::is_initialized() } Debugger::Debugger( - Function on_stop_callback, + Function on_stop_callback, Function on_continue_callback, Function on_exit_callback) : m_on_stopped_callback(move(on_stop_callback)) @@ -129,10 +129,6 @@ int Debugger::debugger_loop() } ASSERT(optional_regs.has_value()); const PtraceRegisters& regs = optional_regs.value(); - auto source_position = m_debug_session->debug_info().get_source_position(regs.eip); - if (!source_position.has_value()) { - return DebugSession::DebugDecision::Continue; - } if (in_single_step_mode) { for (auto address : temporary_breakpoints) { @@ -142,8 +138,7 @@ int Debugger::debugger_loop() in_single_step_mode = false; } - dbg() << "Debugee stopped @ " << source_position.value().file_path << ":" << source_position.value().line_number; - m_on_stopped_callback(source_position.value()); + m_on_stopped_callback(regs); pthread_mutex_lock(&m_continue_mutex); pthread_cond_wait(&m_continue_cond, &m_continue_mutex); diff --git a/DevTools/HackStudio/Debugger.h b/DevTools/HackStudio/Debugger.h index 8af2b38460..0df030bb99 100644 --- a/DevTools/HackStudio/Debugger.h +++ b/DevTools/HackStudio/Debugger.h @@ -26,6 +26,7 @@ */ #pragma once + #include "BreakpointCallback.h" #include #include @@ -37,7 +38,7 @@ class Debugger { public: static Debugger& the(); static void initialize( - Function on_stop_callback, + Function on_stop_callback, Function on_continue_callback, Function on_exit_callback); @@ -65,7 +66,7 @@ public: private: explicit Debugger( - Function on_stop_callback, + Function on_stop_callback, Function on_continue_callback, Function on_exit_callback); @@ -82,7 +83,7 @@ private: Vector m_breakpoints; String m_executable_path; - Function m_on_stopped_callback; + Function m_on_stopped_callback; Function m_on_continue_callback; Function m_on_exit_callback; diff --git a/DevTools/HackStudio/Makefile b/DevTools/HackStudio/Makefile index 5ae18fde67..3b3f2f1955 100644 --- a/DevTools/HackStudio/Makefile +++ b/DevTools/HackStudio/Makefile @@ -6,6 +6,7 @@ OBJS = \ ProcessStateWidget.o \ FormEditorWidget.o \ FormWidget.o \ + DebugInfoWidget.o \ Editor.o \ EditorWrapper.o \ Locator.o \ diff --git a/DevTools/HackStudio/main.cpp b/DevTools/HackStudio/main.cpp index d8d729032a..0376d7eb2c 100644 --- a/DevTools/HackStudio/main.cpp +++ b/DevTools/HackStudio/main.cpp @@ -25,6 +25,7 @@ */ #include "CursorTool.h" +#include "DebugInfoWidget.h" #include "Debugger.h" #include "Editor.h" #include "EditorWrapper.h" @@ -508,6 +509,7 @@ int main(int argc, char** argv) auto& find_in_files_widget = s_action_tab_widget->add_tab("Find in files"); auto& terminal_wrapper = s_action_tab_widget->add_tab("Build", false); + auto& debug_info_widget = s_action_tab_widget->add_tab("Debug"); auto& locator = widget.add(); @@ -602,12 +604,17 @@ int main(int argc, char** argv) RefPtr current_editor_in_execution; Debugger::initialize( - [&](DebugInfo::SourcePosition source_position) { + [&](const PtraceRegisters& regs) { dbg() << "Program stopped"; - current_editor_in_execution = get_editor_of_file(source_position.file_path); - current_editor_in_execution->editor().set_execution_position(source_position.line_number - 1); + + auto source_position = Debugger::the().session()->debug_info().get_source_position(regs.eip); + ASSERT(source_position.has_value()); + current_editor_in_execution = get_editor_of_file(source_position.value().file_path); + current_editor_in_execution->editor().set_execution_position(source_position.value().line_number - 1); + debug_info_widget.update_variables(regs); continue_action->set_enabled(true); single_step_action->set_enabled(true); + reveal_action_tab(debug_info_widget); }, [&]() { dbg() << "Program continued";