mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 16:57:46 +00:00
DevTools: Move to Userland/DevTools/
This commit is contained in:
parent
13d7c09125
commit
4055b03291
125 changed files with 2 additions and 2 deletions
78
Userland/DevTools/HackStudio/Debugger/BacktraceModel.cpp
Normal file
78
Userland/DevTools/HackStudio/Debugger/BacktraceModel.cpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
|
||||
* 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 "BacktraceModel.h"
|
||||
#include "Debugger.h"
|
||||
#include <LibDebug/StackFrameUtils.h>
|
||||
|
||||
namespace HackStudio {
|
||||
|
||||
NonnullRefPtr<BacktraceModel> BacktraceModel::create(const Debug::DebugSession& debug_session, const PtraceRegisters& regs)
|
||||
{
|
||||
return adopt(*new BacktraceModel(create_backtrace(debug_session, regs)));
|
||||
}
|
||||
|
||||
GUI::Variant BacktraceModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
|
||||
{
|
||||
if (role == GUI::ModelRole::Display) {
|
||||
auto& frame = m_frames.at(index.row());
|
||||
return frame.function_name;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
GUI::ModelIndex BacktraceModel::index(int row, int column, const GUI::ModelIndex&) const
|
||||
{
|
||||
if (row < 0 || row >= static_cast<int>(m_frames.size()))
|
||||
return {};
|
||||
return create_index(row, column, &m_frames.at(row));
|
||||
}
|
||||
|
||||
Vector<BacktraceModel::FrameInfo> BacktraceModel::create_backtrace(const Debug::DebugSession& debug_session, const PtraceRegisters& regs)
|
||||
{
|
||||
u32 current_ebp = regs.ebp;
|
||||
u32 current_instruction = regs.eip;
|
||||
Vector<BacktraceModel::FrameInfo> frames;
|
||||
do {
|
||||
auto lib = debug_session.library_at(regs.eip);
|
||||
if (!lib)
|
||||
continue;
|
||||
String name = lib->debug_info->name_of_containing_function(current_instruction - lib->base_address);
|
||||
if (name.is_null()) {
|
||||
dbgln("BacktraceModel: couldn't find containing function for address: {:p}", current_instruction);
|
||||
name = "<missing>";
|
||||
}
|
||||
|
||||
frames.append({ name, current_instruction, current_ebp });
|
||||
auto frame_info = Debug::StackFrameUtils::get_info(*Debugger::the().session(), current_ebp);
|
||||
ASSERT(frame_info.has_value());
|
||||
current_instruction = frame_info.value().return_address;
|
||||
current_ebp = frame_info.value().next_ebp;
|
||||
} while (current_ebp && current_instruction);
|
||||
return frames;
|
||||
}
|
||||
|
||||
}
|
78
Userland/DevTools/HackStudio/Debugger/BacktraceModel.h
Normal file
78
Userland/DevTools/HackStudio/Debugger/BacktraceModel.h
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
|
||||
* 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 <AK/Vector.h>
|
||||
#include <LibGUI/ListView.h>
|
||||
#include <LibGUI/Model.h>
|
||||
#include <sys/arch/i386/regs.h>
|
||||
|
||||
namespace Debug {
|
||||
|
||||
class DebugSession;
|
||||
|
||||
}
|
||||
|
||||
namespace HackStudio {
|
||||
|
||||
class BacktraceModel final : public GUI::Model {
|
||||
public:
|
||||
static NonnullRefPtr<BacktraceModel> create(const Debug::DebugSession&, const PtraceRegisters& regs);
|
||||
|
||||
virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return m_frames.size(); }
|
||||
virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return 1; }
|
||||
|
||||
virtual String column_name(int) const override
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
|
||||
|
||||
virtual void update() override { }
|
||||
virtual GUI::ModelIndex index(int row, int column, const GUI::ModelIndex&) const override;
|
||||
|
||||
struct FrameInfo {
|
||||
String function_name;
|
||||
u32 instruction_address;
|
||||
u32 frame_base;
|
||||
};
|
||||
|
||||
const Vector<FrameInfo>& frames() const { return m_frames; }
|
||||
|
||||
private:
|
||||
explicit BacktraceModel(Vector<FrameInfo>&& frames)
|
||||
: m_frames(move(frames))
|
||||
{
|
||||
}
|
||||
|
||||
static Vector<FrameInfo> create_backtrace(const Debug::DebugSession&, const PtraceRegisters&);
|
||||
|
||||
Vector<FrameInfo> m_frames;
|
||||
};
|
||||
|
||||
}
|
42
Userland/DevTools/HackStudio/Debugger/BreakpointCallback.h
Normal file
42
Userland/DevTools/HackStudio/Debugger/BreakpointCallback.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
|
||||
* 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 <AK/Function.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Types.h>
|
||||
|
||||
namespace HackStudio {
|
||||
|
||||
enum class BreakpointChange {
|
||||
Added,
|
||||
Removed,
|
||||
};
|
||||
|
||||
typedef Function<void(const String& file, size_t line, BreakpointChange)> BreakpointChangeCallback;
|
||||
|
||||
}
|
188
Userland/DevTools/HackStudio/Debugger/DebugInfoWidget.cpp
Normal file
188
Userland/DevTools/HackStudio/Debugger/DebugInfoWidget.cpp
Normal file
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
|
||||
* 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 "BacktraceModel.h"
|
||||
#include "Debugger.h"
|
||||
#include "RegistersModel.h"
|
||||
#include "VariablesModel.h"
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/InputBox.h>
|
||||
#include <LibGUI/Layout.h>
|
||||
#include <LibGUI/ListView.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/Model.h>
|
||||
#include <LibGUI/Splitter.h>
|
||||
#include <LibGUI/TabWidget.h>
|
||||
#include <LibGUI/TreeView.h>
|
||||
|
||||
namespace HackStudio {
|
||||
|
||||
void DebugInfoWidget::init_toolbar()
|
||||
{
|
||||
m_continue_action = GUI::Action::create("Continue", Gfx::Bitmap::load_from_file("/res/icons/16x16/debug-continue.png"), [](auto&) {
|
||||
Debugger::the().set_requested_debugger_action(Debugger::DebuggerAction::Continue);
|
||||
});
|
||||
|
||||
m_singlestep_action = GUI::Action::create("Step Over", { Mod_None, Key_F10 }, Gfx::Bitmap::load_from_file("/res/icons/16x16/debug-step-over.png"), [](auto&) {
|
||||
Debugger::the().set_requested_debugger_action(Debugger::DebuggerAction::SourceStepOver);
|
||||
});
|
||||
|
||||
m_step_in_action = GUI::Action::create("Step In", { Mod_None, Key_F11 }, Gfx::Bitmap::load_from_file("/res/icons/16x16/debug-step-in.png"), [](auto&) {
|
||||
Debugger::the().set_requested_debugger_action(Debugger::DebuggerAction::SourceSingleStep);
|
||||
});
|
||||
|
||||
m_step_out_action = GUI::Action::create("Step Out", { Mod_Shift, Key_F11 }, Gfx::Bitmap::load_from_file("/res/icons/16x16/debug-step-out.png"), [](auto&) {
|
||||
Debugger::the().set_requested_debugger_action(Debugger::DebuggerAction::SourceStepOut);
|
||||
});
|
||||
|
||||
m_toolbar->add_action(*m_continue_action);
|
||||
m_toolbar->add_action(*m_singlestep_action);
|
||||
m_toolbar->add_action(*m_step_in_action);
|
||||
m_toolbar->add_action(*m_step_out_action);
|
||||
|
||||
set_debug_actions_enabled(false);
|
||||
}
|
||||
|
||||
DebugInfoWidget::DebugInfoWidget()
|
||||
{
|
||||
set_layout<GUI::VerticalBoxLayout>();
|
||||
auto& toolbar_container = add<GUI::ToolBarContainer>();
|
||||
m_toolbar = toolbar_container.add<GUI::ToolBar>();
|
||||
init_toolbar();
|
||||
auto& bottom_box = add<GUI::Widget>();
|
||||
bottom_box.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& splitter = bottom_box.add<GUI::HorizontalSplitter>();
|
||||
m_backtrace_view = splitter.add<GUI::ListView>();
|
||||
auto& variables_tab_widget = splitter.add<GUI::TabWidget>();
|
||||
variables_tab_widget.set_tab_position(GUI::TabWidget::TabPosition::Bottom);
|
||||
variables_tab_widget.add_widget("Variables", build_variables_tab());
|
||||
variables_tab_widget.add_widget("Registers", build_registers_tab());
|
||||
|
||||
m_backtrace_view->on_selection = [this](auto& index) {
|
||||
auto& model = static_cast<BacktraceModel&>(*m_backtrace_view->model());
|
||||
|
||||
// Note: The reconstruction of the register set here is obviously incomplete.
|
||||
// We currently only reconstruct eip & ebp. Ideally would also reconstruct the other registers somehow.
|
||||
// (Other registers may be needed to get the values of variables who are not stored on the stack)
|
||||
PtraceRegisters frame_regs {};
|
||||
frame_regs.eip = model.frames()[index.row()].instruction_address;
|
||||
frame_regs.ebp = model.frames()[index.row()].frame_base;
|
||||
|
||||
m_variables_view->set_model(VariablesModel::create(frame_regs));
|
||||
};
|
||||
}
|
||||
|
||||
NonnullRefPtr<GUI::Widget> DebugInfoWidget::build_variables_tab()
|
||||
{
|
||||
auto variables_widget = GUI::Widget::construct();
|
||||
variables_widget->set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
m_variables_view = variables_widget->add<GUI::TreeView>();
|
||||
|
||||
auto is_valid_index = [](auto& index) {
|
||||
if (!index.is_valid())
|
||||
return false;
|
||||
auto* variable = static_cast<const Debug::DebugInfo::VariableInfo*>(index.internal_data());
|
||||
if (variable->location_type != Debug::DebugInfo::VariableInfo::LocationType::Address)
|
||||
return false;
|
||||
return variable->is_enum_type() || variable->type_name.is_one_of("int", "bool");
|
||||
};
|
||||
|
||||
m_variables_view->on_context_menu_request = [this, is_valid_index](auto& index, auto& event) {
|
||||
if (!is_valid_index(index))
|
||||
return;
|
||||
m_variable_context_menu->popup(event.screen_position());
|
||||
};
|
||||
|
||||
m_variables_view->on_activation = [this, is_valid_index](auto& index) {
|
||||
if (!is_valid_index(index))
|
||||
return;
|
||||
|
||||
String value;
|
||||
if (GUI::InputBox::show(value, window(), "Enter new value:", "Set variable value") == GUI::InputBox::ExecOK) {
|
||||
auto& model = static_cast<VariablesModel&>(*m_variables_view->model());
|
||||
model.set_variable_value(index, value, window());
|
||||
}
|
||||
};
|
||||
|
||||
auto edit_variable_action = GUI::Action::create("Change value", [this](auto&) {
|
||||
m_variables_view->on_activation(m_variables_view->selection().first());
|
||||
});
|
||||
|
||||
m_variable_context_menu = GUI::Menu::construct();
|
||||
m_variable_context_menu->add_action(edit_variable_action);
|
||||
|
||||
return variables_widget;
|
||||
}
|
||||
|
||||
NonnullRefPtr<GUI::Widget> DebugInfoWidget::build_registers_tab()
|
||||
{
|
||||
auto registers_widget = GUI::Widget::construct();
|
||||
registers_widget->set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
m_registers_view = registers_widget->add<GUI::TableView>();
|
||||
|
||||
return registers_widget;
|
||||
}
|
||||
|
||||
void DebugInfoWidget::update_state(const Debug::DebugSession& debug_session, const PtraceRegisters& regs)
|
||||
{
|
||||
m_variables_view->set_model(VariablesModel::create(regs));
|
||||
m_backtrace_view->set_model(BacktraceModel::create(debug_session, regs));
|
||||
if (m_registers_view->model()) {
|
||||
auto& previous_registers = static_cast<RegistersModel*>(m_registers_view->model())->raw_registers();
|
||||
m_registers_view->set_model(RegistersModel::create(regs, previous_registers));
|
||||
} else {
|
||||
m_registers_view->set_model(RegistersModel::create(regs));
|
||||
}
|
||||
auto selected_index = m_backtrace_view->model()->index(0);
|
||||
if (!selected_index.is_valid()) {
|
||||
dbgln("Warning: DebugInfoWidget: backtrace selected index is invalid");
|
||||
return;
|
||||
}
|
||||
m_backtrace_view->selection().set(selected_index);
|
||||
}
|
||||
|
||||
void DebugInfoWidget::program_stopped()
|
||||
{
|
||||
m_variables_view->set_model({});
|
||||
m_backtrace_view->set_model({});
|
||||
m_registers_view->set_model({});
|
||||
}
|
||||
|
||||
void DebugInfoWidget::set_debug_actions_enabled(bool enabled)
|
||||
{
|
||||
m_continue_action->set_enabled(enabled);
|
||||
m_singlestep_action->set_enabled(enabled);
|
||||
m_step_in_action->set_enabled(enabled);
|
||||
m_step_out_action->set_enabled(enabled);
|
||||
}
|
||||
|
||||
}
|
71
Userland/DevTools/HackStudio/Debugger/DebugInfoWidget.h
Normal file
71
Userland/DevTools/HackStudio/Debugger/DebugInfoWidget.h
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
|
||||
* 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 <AK/NonnullOwnPtr.h>
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/ListView.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/Model.h>
|
||||
#include <LibGUI/TableView.h>
|
||||
#include <LibGUI/ToolBar.h>
|
||||
#include <LibGUI/ToolBarContainer.h>
|
||||
#include <LibGUI/TreeView.h>
|
||||
#include <LibGUI/Widget.h>
|
||||
#include <sys/arch/i386/regs.h>
|
||||
|
||||
namespace HackStudio {
|
||||
|
||||
class DebugInfoWidget final : public GUI::Widget {
|
||||
C_OBJECT(DebugInfoWidget)
|
||||
public:
|
||||
virtual ~DebugInfoWidget() override { }
|
||||
|
||||
void update_state(const Debug::DebugSession&, const PtraceRegisters&);
|
||||
void program_stopped();
|
||||
void set_debug_actions_enabled(bool enabled);
|
||||
|
||||
private:
|
||||
explicit DebugInfoWidget();
|
||||
void init_toolbar();
|
||||
|
||||
NonnullRefPtr<GUI::Widget> build_variables_tab();
|
||||
NonnullRefPtr<GUI::Widget> build_registers_tab();
|
||||
|
||||
RefPtr<GUI::TreeView> m_variables_view;
|
||||
RefPtr<GUI::TableView> m_registers_view;
|
||||
RefPtr<GUI::ListView> m_backtrace_view;
|
||||
RefPtr<GUI::Menu> m_variable_context_menu;
|
||||
RefPtr<GUI::ToolBar> m_toolbar;
|
||||
RefPtr<GUI::Action> m_continue_action;
|
||||
RefPtr<GUI::Action> m_singlestep_action;
|
||||
RefPtr<GUI::Action> m_step_in_action;
|
||||
RefPtr<GUI::Action> m_step_out_action;
|
||||
};
|
||||
|
||||
}
|
295
Userland/DevTools/HackStudio/Debugger/Debugger.cpp
Normal file
295
Userland/DevTools/HackStudio/Debugger/Debugger.cpp
Normal file
|
@ -0,0 +1,295 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
|
||||
* 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 "Debugger.h"
|
||||
#include <LibDebug/StackFrameUtils.h>
|
||||
|
||||
namespace HackStudio {
|
||||
|
||||
static Debugger* s_the;
|
||||
|
||||
Debugger& Debugger::the()
|
||||
{
|
||||
ASSERT(s_the);
|
||||
return *s_the;
|
||||
}
|
||||
|
||||
void Debugger::initialize(
|
||||
String source_root,
|
||||
Function<HasControlPassedToUser(const PtraceRegisters&)> on_stop_callback,
|
||||
Function<void()> on_continue_callback,
|
||||
Function<void()> on_exit_callback)
|
||||
{
|
||||
s_the = new Debugger(source_root, move(on_stop_callback), move(on_continue_callback), move(on_exit_callback));
|
||||
}
|
||||
|
||||
bool Debugger::is_initialized()
|
||||
{
|
||||
return s_the;
|
||||
}
|
||||
|
||||
Debugger::Debugger(
|
||||
String source_root,
|
||||
Function<HasControlPassedToUser(const PtraceRegisters&)> on_stop_callback,
|
||||
Function<void()> on_continue_callback,
|
||||
Function<void()> on_exit_callback)
|
||||
: m_source_root(source_root)
|
||||
, m_on_stopped_callback(move(on_stop_callback))
|
||||
, m_on_continue_callback(move(on_continue_callback))
|
||||
, m_on_exit_callback(move(on_exit_callback))
|
||||
{
|
||||
pthread_mutex_init(&m_ui_action_mutex, nullptr);
|
||||
pthread_cond_init(&m_ui_action_cond, nullptr);
|
||||
}
|
||||
|
||||
void Debugger::on_breakpoint_change(const String& file, size_t line, BreakpointChange change_type)
|
||||
{
|
||||
auto position = create_source_position(file, line);
|
||||
|
||||
if (change_type == BreakpointChange::Added) {
|
||||
Debugger::the().m_breakpoints.append(position);
|
||||
} else {
|
||||
Debugger::the().m_breakpoints.remove_all_matching([&](Debug::DebugInfo::SourcePosition val) { return val == position; });
|
||||
}
|
||||
|
||||
auto session = Debugger::the().session();
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
auto address = session->get_address_from_source_position(position.file_path, position.line_number);
|
||||
if (!address.has_value()) {
|
||||
dbgln("Warning: couldn't get instruction address from source");
|
||||
// TODO: Currently, the GUI will indicate that a breakpoint was inserted/removed at this line,
|
||||
// regardless of whether we actually succeeded to insert it. (For example a breakpoint on a comment, or an include statement).
|
||||
// We should indicate failure via a return value from this function, and not update the breakpoint GUI if we fail.
|
||||
return;
|
||||
}
|
||||
|
||||
if (change_type == BreakpointChange::Added) {
|
||||
bool success = session->insert_breakpoint(reinterpret_cast<void*>(address.value().address));
|
||||
ASSERT(success);
|
||||
} else {
|
||||
bool success = session->remove_breakpoint(reinterpret_cast<void*>(address.value().address));
|
||||
ASSERT(success);
|
||||
}
|
||||
}
|
||||
|
||||
Debug::DebugInfo::SourcePosition Debugger::create_source_position(const String& file, size_t line)
|
||||
{
|
||||
if (!file.starts_with('/') && !file.starts_with("./"))
|
||||
return { String::formatted("./{}", file), line + 1 };
|
||||
return { file, line + 1 };
|
||||
}
|
||||
|
||||
int Debugger::start_static()
|
||||
{
|
||||
Debugger::the().start();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Debugger::start()
|
||||
{
|
||||
m_debug_session = Debug::DebugSession::exec_and_attach(m_executable_path, m_source_root);
|
||||
ASSERT(!!m_debug_session);
|
||||
|
||||
for (const auto& breakpoint : m_breakpoints) {
|
||||
dbgln("inserting breakpoint at: {}:{}", breakpoint.file_path, breakpoint.line_number);
|
||||
auto address = m_debug_session->get_address_from_source_position(breakpoint.file_path, breakpoint.line_number);
|
||||
if (address.has_value()) {
|
||||
bool success = m_debug_session->insert_breakpoint(reinterpret_cast<void*>(address.value().address));
|
||||
ASSERT(success);
|
||||
} else {
|
||||
dbgln("couldn't insert breakpoint");
|
||||
}
|
||||
}
|
||||
|
||||
debugger_loop();
|
||||
}
|
||||
|
||||
int Debugger::debugger_loop()
|
||||
{
|
||||
ASSERT(m_debug_session);
|
||||
|
||||
m_debug_session->run(Debug::DebugSession::DesiredInitialDebugeeState::Running, [this](Debug::DebugSession::DebugBreakReason reason, Optional<PtraceRegisters> optional_regs) {
|
||||
if (reason == Debug::DebugSession::DebugBreakReason::Exited) {
|
||||
dbgln("Program exited");
|
||||
m_on_exit_callback();
|
||||
return Debug::DebugSession::DebugDecision::Detach;
|
||||
}
|
||||
remove_temporary_breakpoints();
|
||||
ASSERT(optional_regs.has_value());
|
||||
const PtraceRegisters& regs = optional_regs.value();
|
||||
|
||||
auto source_position = m_debug_session->get_source_position(regs.eip);
|
||||
if (!source_position.has_value())
|
||||
return Debug::DebugSession::DebugDecision::SingleStep;
|
||||
|
||||
// We currently do no support stepping through assembly source
|
||||
if (source_position.value().file_path.ends_with(".S"))
|
||||
return Debug::DebugSession::DebugDecision::SingleStep;
|
||||
|
||||
ASSERT(source_position.has_value());
|
||||
if (m_state.get() == Debugger::DebuggingState::SingleStepping) {
|
||||
if (m_state.should_stop_single_stepping(source_position.value())) {
|
||||
m_state.set_normal();
|
||||
} else {
|
||||
return Debug::DebugSession::DebugDecision::SingleStep;
|
||||
}
|
||||
}
|
||||
|
||||
auto control_passed_to_user = m_on_stopped_callback(regs);
|
||||
|
||||
if (control_passed_to_user == HasControlPassedToUser::Yes) {
|
||||
pthread_mutex_lock(&m_ui_action_mutex);
|
||||
pthread_cond_wait(&m_ui_action_cond, &m_ui_action_mutex);
|
||||
pthread_mutex_unlock(&m_ui_action_mutex);
|
||||
|
||||
if (m_requested_debugger_action != DebuggerAction::Exit)
|
||||
m_on_continue_callback();
|
||||
|
||||
} else {
|
||||
m_requested_debugger_action = DebuggerAction::Continue;
|
||||
}
|
||||
|
||||
switch (m_requested_debugger_action) {
|
||||
case DebuggerAction::Continue:
|
||||
m_state.set_normal();
|
||||
return Debug::DebugSession::DebugDecision::Continue;
|
||||
case DebuggerAction::SourceSingleStep:
|
||||
m_state.set_single_stepping(source_position.value());
|
||||
return Debug::DebugSession::DebugDecision::SingleStep;
|
||||
case DebuggerAction::SourceStepOut:
|
||||
m_state.set_stepping_out();
|
||||
do_step_out(regs);
|
||||
return Debug::DebugSession::DebugDecision::Continue;
|
||||
case DebuggerAction::SourceStepOver:
|
||||
m_state.set_stepping_over();
|
||||
do_step_over(regs);
|
||||
return Debug::DebugSession::DebugDecision::Continue;
|
||||
case DebuggerAction::Exit:
|
||||
// NOTE: Is detaching from the debuggee the best thing to do here?
|
||||
// We could display a dialog in the UI, remind the user that there is
|
||||
// a live debugged process, and ask whether they want to terminate/detach.
|
||||
dbgln("Debugger exiting");
|
||||
return Debug::DebugSession::DebugDecision::Detach;
|
||||
}
|
||||
ASSERT_NOT_REACHED();
|
||||
});
|
||||
m_debug_session.clear();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Debugger::DebuggingState::set_normal()
|
||||
{
|
||||
m_state = State::Normal;
|
||||
m_original_source_position.clear();
|
||||
}
|
||||
|
||||
void Debugger::DebuggingState::set_single_stepping(Debug::DebugInfo::SourcePosition original_source_position)
|
||||
{
|
||||
m_state = State::SingleStepping;
|
||||
m_original_source_position = original_source_position;
|
||||
}
|
||||
|
||||
bool Debugger::DebuggingState::should_stop_single_stepping(const Debug::DebugInfo::SourcePosition& current_source_position) const
|
||||
{
|
||||
ASSERT(m_state == State::SingleStepping);
|
||||
return m_original_source_position.value() != current_source_position;
|
||||
}
|
||||
|
||||
void Debugger::remove_temporary_breakpoints()
|
||||
{
|
||||
for (auto breakpoint_address : m_state.temporary_breakpoints()) {
|
||||
ASSERT(m_debug_session->breakpoint_exists((void*)breakpoint_address));
|
||||
bool rc = m_debug_session->remove_breakpoint((void*)breakpoint_address);
|
||||
ASSERT(rc);
|
||||
}
|
||||
m_state.clear_temporary_breakpoints();
|
||||
}
|
||||
|
||||
void Debugger::DebuggingState::clear_temporary_breakpoints()
|
||||
{
|
||||
m_addresses_of_temporary_breakpoints.clear();
|
||||
}
|
||||
void Debugger::DebuggingState::add_temporary_breakpoint(u32 address)
|
||||
{
|
||||
m_addresses_of_temporary_breakpoints.append(address);
|
||||
}
|
||||
|
||||
void Debugger::do_step_out(const PtraceRegisters& regs)
|
||||
{
|
||||
// To step out, we simply insert a temporary breakpoint at the
|
||||
// instruction the current function returns to, and continue
|
||||
// execution until we hit that instruction (or some other breakpoint).
|
||||
insert_temporary_breakpoint_at_return_address(regs);
|
||||
}
|
||||
|
||||
void Debugger::do_step_over(const PtraceRegisters& regs)
|
||||
{
|
||||
// To step over, we insert a temporary breakpoint at each line in the current function,
|
||||
// as well as at the current function's return point, and continue execution.
|
||||
auto lib = m_debug_session->library_at(regs.eip);
|
||||
if (!lib)
|
||||
return;
|
||||
auto current_function = lib->debug_info->get_containing_function(regs.eip - lib->base_address);
|
||||
if (!current_function.has_value()) {
|
||||
dbgln("cannot perform step_over, failed to find containing function of: {:p}", regs.eip);
|
||||
return;
|
||||
}
|
||||
ASSERT(current_function.has_value());
|
||||
auto lines_in_current_function = lib->debug_info->source_lines_in_scope(current_function.value());
|
||||
for (const auto& line : lines_in_current_function) {
|
||||
insert_temporary_breakpoint(line.address_of_first_statement.value() + lib->base_address);
|
||||
}
|
||||
insert_temporary_breakpoint_at_return_address(regs);
|
||||
}
|
||||
|
||||
void Debugger::insert_temporary_breakpoint_at_return_address(const PtraceRegisters& regs)
|
||||
{
|
||||
auto frame_info = Debug::StackFrameUtils::get_info(*m_debug_session, regs.ebp);
|
||||
ASSERT(frame_info.has_value());
|
||||
u32 return_address = frame_info.value().return_address;
|
||||
insert_temporary_breakpoint(return_address);
|
||||
}
|
||||
|
||||
void Debugger::insert_temporary_breakpoint(FlatPtr address)
|
||||
{
|
||||
if (m_debug_session->breakpoint_exists((void*)address))
|
||||
return;
|
||||
bool success = m_debug_session->insert_breakpoint(reinterpret_cast<void*>(address));
|
||||
ASSERT(success);
|
||||
m_state.add_temporary_breakpoint(address);
|
||||
}
|
||||
|
||||
void Debugger::set_requested_debugger_action(DebuggerAction action)
|
||||
{
|
||||
pthread_mutex_lock(continue_mutex());
|
||||
m_requested_debugger_action = action;
|
||||
pthread_cond_signal(continue_cond());
|
||||
pthread_mutex_unlock(continue_mutex());
|
||||
}
|
||||
|
||||
}
|
139
Userland/DevTools/HackStudio/Debugger/Debugger.h
Normal file
139
Userland/DevTools/HackStudio/Debugger/Debugger.h
Normal file
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
|
||||
* 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 "BreakpointCallback.h"
|
||||
#include <AK/Function.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibDebug/DebugSession.h>
|
||||
#include <LibThread/Lock.h>
|
||||
#include <LibThread/Thread.h>
|
||||
|
||||
namespace HackStudio {
|
||||
|
||||
class Debugger {
|
||||
public:
|
||||
static Debugger& the();
|
||||
|
||||
enum class HasControlPassedToUser {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
static void initialize(
|
||||
String source_root,
|
||||
Function<HasControlPassedToUser(const PtraceRegisters&)> on_stop_callback,
|
||||
Function<void()> on_continue_callback,
|
||||
Function<void()> on_exit_callback);
|
||||
|
||||
static bool is_initialized();
|
||||
|
||||
static void on_breakpoint_change(const String& file, size_t line, BreakpointChange change_type);
|
||||
|
||||
void set_executable_path(const String& path) { m_executable_path = path; }
|
||||
|
||||
Debug::DebugSession* session() { return m_debug_session.ptr(); }
|
||||
|
||||
// Thread entry point
|
||||
static int start_static();
|
||||
|
||||
pthread_mutex_t* continue_mutex() { return &m_ui_action_mutex; }
|
||||
pthread_cond_t* continue_cond() { return &m_ui_action_cond; }
|
||||
|
||||
enum class DebuggerAction {
|
||||
Continue,
|
||||
SourceSingleStep,
|
||||
SourceStepOut,
|
||||
SourceStepOver,
|
||||
Exit,
|
||||
};
|
||||
|
||||
void set_requested_debugger_action(DebuggerAction);
|
||||
void reset_breakpoints() { m_breakpoints.clear(); }
|
||||
|
||||
private:
|
||||
class DebuggingState {
|
||||
public:
|
||||
enum State {
|
||||
Normal, // Continue normally until we hit a breakpoint / program terminates
|
||||
SingleStepping,
|
||||
SteppingOut,
|
||||
SteppingOver,
|
||||
};
|
||||
State get() const { return m_state; }
|
||||
|
||||
void set_normal();
|
||||
void set_single_stepping(Debug::DebugInfo::SourcePosition original_source_position);
|
||||
void set_stepping_out() { m_state = State::SteppingOut; }
|
||||
void set_stepping_over() { m_state = State::SteppingOver; }
|
||||
|
||||
bool should_stop_single_stepping(const Debug::DebugInfo::SourcePosition& current_source_position) const;
|
||||
void clear_temporary_breakpoints();
|
||||
void add_temporary_breakpoint(u32 address);
|
||||
const Vector<u32>& temporary_breakpoints() const { return m_addresses_of_temporary_breakpoints; }
|
||||
|
||||
private:
|
||||
State m_state { Normal };
|
||||
Optional<Debug::DebugInfo::SourcePosition> m_original_source_position; // The source position at which we started the current single step
|
||||
Vector<u32> m_addresses_of_temporary_breakpoints;
|
||||
};
|
||||
|
||||
explicit Debugger(
|
||||
String source_root,
|
||||
Function<HasControlPassedToUser(const PtraceRegisters&)> on_stop_callback,
|
||||
Function<void()> on_continue_callback,
|
||||
Function<void()> on_exit_callback);
|
||||
|
||||
static Debug::DebugInfo::SourcePosition create_source_position(const String& file, size_t line);
|
||||
|
||||
void start();
|
||||
int debugger_loop();
|
||||
|
||||
void remove_temporary_breakpoints();
|
||||
void do_step_out(const PtraceRegisters&);
|
||||
void do_step_over(const PtraceRegisters&);
|
||||
void insert_temporary_breakpoint(FlatPtr address);
|
||||
void insert_temporary_breakpoint_at_return_address(const PtraceRegisters&);
|
||||
|
||||
OwnPtr<Debug::DebugSession> m_debug_session;
|
||||
String m_source_root;
|
||||
DebuggingState m_state;
|
||||
|
||||
pthread_mutex_t m_ui_action_mutex {};
|
||||
pthread_cond_t m_ui_action_cond {};
|
||||
DebuggerAction m_requested_debugger_action { DebuggerAction::Continue };
|
||||
|
||||
Vector<Debug::DebugInfo::SourcePosition> m_breakpoints;
|
||||
|
||||
String m_executable_path;
|
||||
|
||||
Function<HasControlPassedToUser(const PtraceRegisters&)> m_on_stopped_callback;
|
||||
Function<void()> m_on_continue_callback;
|
||||
Function<void()> m_on_exit_callback;
|
||||
};
|
||||
|
||||
}
|
137
Userland/DevTools/HackStudio/Debugger/DisassemblyModel.cpp
Normal file
137
Userland/DevTools/HackStudio/Debugger/DisassemblyModel.cpp
Normal file
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Luke Wilde <luke.wilde@live.co.uk>
|
||||
* 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 "DisassemblyModel.h"
|
||||
#include <AK/MappedFile.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibDebug/DebugSession.h>
|
||||
#include <LibELF/Image.h>
|
||||
#include <LibX86/Disassembler.h>
|
||||
#include <LibX86/ELFSymbolProvider.h>
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace HackStudio {
|
||||
|
||||
DisassemblyModel::DisassemblyModel(const Debug::DebugSession& debug_session, const PtraceRegisters& regs)
|
||||
{
|
||||
auto lib = debug_session.library_at(regs.eip);
|
||||
if (!lib)
|
||||
return;
|
||||
auto containing_function = lib->debug_info->get_containing_function(regs.eip - lib->base_address);
|
||||
if (!containing_function.has_value()) {
|
||||
dbgln("Cannot disassemble as the containing function was not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
OwnPtr<ELF::Image> kernel_elf;
|
||||
const ELF::Image* elf = nullptr;
|
||||
|
||||
if (containing_function.value().address_low >= 0xc0000000) {
|
||||
auto file_or_error = MappedFile::map("/boot/Kernel");
|
||||
if (file_or_error.is_error())
|
||||
return;
|
||||
kernel_elf = make<ELF::Image>(file_or_error.value()->bytes());
|
||||
elf = kernel_elf.ptr();
|
||||
} else {
|
||||
elf = &lib->debug_info->elf();
|
||||
}
|
||||
|
||||
auto symbol = elf->find_symbol(containing_function.value().address_low);
|
||||
if (!symbol.has_value())
|
||||
return;
|
||||
ASSERT(symbol.has_value());
|
||||
|
||||
auto view = symbol.value().raw_data();
|
||||
|
||||
X86::ELFSymbolProvider symbol_provider(*elf);
|
||||
X86::SimpleInstructionStream stream((const u8*)view.characters_without_null_termination(), view.length());
|
||||
X86::Disassembler disassembler(stream);
|
||||
|
||||
size_t offset_into_symbol = 0;
|
||||
for (;;) {
|
||||
auto insn = disassembler.next();
|
||||
if (!insn.has_value())
|
||||
break;
|
||||
FlatPtr address_in_profiled_program = symbol.value().value() + offset_into_symbol;
|
||||
auto disassembly = insn.value().to_string(address_in_profiled_program, &symbol_provider);
|
||||
StringView instruction_bytes = view.substring_view(offset_into_symbol, insn.value().length());
|
||||
m_instructions.append({ insn.value(), disassembly, instruction_bytes, address_in_profiled_program });
|
||||
|
||||
offset_into_symbol += insn.value().length();
|
||||
}
|
||||
}
|
||||
|
||||
DisassemblyModel::~DisassemblyModel()
|
||||
{
|
||||
}
|
||||
|
||||
int DisassemblyModel::row_count(const GUI::ModelIndex&) const
|
||||
{
|
||||
return m_instructions.size();
|
||||
}
|
||||
|
||||
String DisassemblyModel::column_name(int column) const
|
||||
{
|
||||
switch (column) {
|
||||
case Column::Address:
|
||||
return "Address";
|
||||
case Column::InstructionBytes:
|
||||
return "Insn Bytes";
|
||||
case Column::Disassembly:
|
||||
return "Disassembly";
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
GUI::Variant DisassemblyModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
|
||||
{
|
||||
auto& insn = m_instructions[index.row()];
|
||||
|
||||
if (role == GUI::ModelRole::Display) {
|
||||
if (index.column() == Column::Address)
|
||||
return String::formatted("{:p}", insn.address);
|
||||
if (index.column() == Column::InstructionBytes) {
|
||||
StringBuilder builder;
|
||||
for (auto ch : insn.bytes)
|
||||
builder.appendff("{:02x} ", static_cast<unsigned char>(ch));
|
||||
return builder.to_string();
|
||||
}
|
||||
if (index.column() == Column::Disassembly)
|
||||
return insn.disassembly;
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void DisassemblyModel::update()
|
||||
{
|
||||
did_update();
|
||||
}
|
||||
|
||||
}
|
77
Userland/DevTools/HackStudio/Debugger/DisassemblyModel.h
Normal file
77
Userland/DevTools/HackStudio/Debugger/DisassemblyModel.h
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Luke Wilde <luke.wilde@live.co.uk>
|
||||
* 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 <AK/Vector.h>
|
||||
#include <LibGUI/Model.h>
|
||||
#include <LibX86/Instruction.h>
|
||||
#include <sys/arch/i386/regs.h>
|
||||
|
||||
namespace Debug {
|
||||
|
||||
class DebugSession;
|
||||
|
||||
}
|
||||
|
||||
namespace HackStudio {
|
||||
|
||||
struct InstructionData {
|
||||
X86::Instruction insn;
|
||||
String disassembly;
|
||||
StringView bytes;
|
||||
FlatPtr address { 0 };
|
||||
};
|
||||
|
||||
class DisassemblyModel final : public GUI::Model {
|
||||
public:
|
||||
static NonnullRefPtr<DisassemblyModel> create(const Debug::DebugSession& debug_session, const PtraceRegisters& regs)
|
||||
{
|
||||
return adopt(*new DisassemblyModel(debug_session, regs));
|
||||
}
|
||||
|
||||
enum Column {
|
||||
Address,
|
||||
InstructionBytes,
|
||||
Disassembly,
|
||||
__Count
|
||||
};
|
||||
|
||||
virtual ~DisassemblyModel() override;
|
||||
|
||||
virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
|
||||
virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return Column::__Count; }
|
||||
virtual String column_name(int) const override;
|
||||
virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
|
||||
virtual void update() override;
|
||||
|
||||
private:
|
||||
DisassemblyModel(const Debug::DebugSession&, const PtraceRegisters&);
|
||||
|
||||
Vector<InstructionData> m_instructions;
|
||||
};
|
||||
|
||||
}
|
105
Userland/DevTools/HackStudio/Debugger/DisassemblyWidget.cpp
Normal file
105
Userland/DevTools/HackStudio/Debugger/DisassemblyWidget.cpp
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Luke Wilde <luke.wilde@live.co.uk>
|
||||
* 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 "DisassemblyWidget.h"
|
||||
#include "DisassemblyModel.h"
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGfx/Palette.h>
|
||||
|
||||
namespace HackStudio {
|
||||
|
||||
void UnavailableDisassemblyWidget::paint_event(GUI::PaintEvent& event)
|
||||
{
|
||||
Frame::paint_event(event);
|
||||
if (reason().is_empty())
|
||||
return;
|
||||
GUI::Painter painter(*this);
|
||||
painter.add_clip_rect(event.rect());
|
||||
painter.draw_text(frame_inner_rect(), reason(), Gfx::TextAlignment::Center, palette().window_text(), Gfx::TextElision::Right);
|
||||
}
|
||||
|
||||
DisassemblyWidget::DisassemblyWidget()
|
||||
{
|
||||
set_layout<GUI::VerticalBoxLayout>();
|
||||
|
||||
m_top_container = add<GUI::Widget>();
|
||||
m_top_container->set_layout<GUI::HorizontalBoxLayout>();
|
||||
m_top_container->set_fixed_height(20);
|
||||
|
||||
m_function_name_label = m_top_container->add<GUI::Label>("");
|
||||
|
||||
m_disassembly_view = add<GUI::TableView>();
|
||||
|
||||
m_unavailable_disassembly_widget = add<UnavailableDisassemblyWidget>("");
|
||||
|
||||
hide_disassembly("Program isn't running");
|
||||
}
|
||||
|
||||
void DisassemblyWidget::update_state(const Debug::DebugSession& debug_session, const PtraceRegisters& regs)
|
||||
{
|
||||
m_disassembly_view->set_model(DisassemblyModel::create(debug_session, regs));
|
||||
|
||||
if (m_disassembly_view->model()->row_count() > 0) {
|
||||
auto lib = debug_session.library_at(regs.eip);
|
||||
if (!lib)
|
||||
return;
|
||||
auto containing_function = lib->debug_info->get_containing_function(regs.eip - lib->base_address);
|
||||
if (containing_function.has_value())
|
||||
m_function_name_label->set_text(containing_function.value().name);
|
||||
else
|
||||
m_function_name_label->set_text("<missing>");
|
||||
show_disassembly();
|
||||
} else {
|
||||
hide_disassembly("No disassembly to show for this function");
|
||||
}
|
||||
}
|
||||
|
||||
void DisassemblyWidget::program_stopped()
|
||||
{
|
||||
m_disassembly_view->set_model({});
|
||||
m_function_name_label->set_text("");
|
||||
hide_disassembly("Program isn't running");
|
||||
}
|
||||
|
||||
void DisassemblyWidget::show_disassembly()
|
||||
{
|
||||
m_top_container->set_visible(true);
|
||||
m_disassembly_view->set_visible(true);
|
||||
m_function_name_label->set_visible(true);
|
||||
m_unavailable_disassembly_widget->set_visible(false);
|
||||
}
|
||||
|
||||
void DisassemblyWidget::hide_disassembly(const String& reason)
|
||||
{
|
||||
m_top_container->set_visible(false);
|
||||
m_disassembly_view->set_visible(false);
|
||||
m_function_name_label->set_visible(false);
|
||||
m_unavailable_disassembly_widget->set_visible(true);
|
||||
m_unavailable_disassembly_widget->set_reason(reason);
|
||||
}
|
||||
|
||||
}
|
78
Userland/DevTools/HackStudio/Debugger/DisassemblyWidget.h
Normal file
78
Userland/DevTools/HackStudio/Debugger/DisassemblyWidget.h
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Luke Wilde <luke.wilde@live.co.uk>
|
||||
* 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 <AK/NonnullOwnPtr.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/Model.h>
|
||||
#include <LibGUI/TableView.h>
|
||||
#include <LibGUI/Widget.h>
|
||||
#include <sys/arch/i386/regs.h>
|
||||
|
||||
namespace HackStudio {
|
||||
|
||||
class UnavailableDisassemblyWidget final : public GUI::Frame {
|
||||
C_OBJECT(UnavailableDisassemblyWidget)
|
||||
public:
|
||||
virtual ~UnavailableDisassemblyWidget() override { }
|
||||
|
||||
const String& reason() const { return m_reason; }
|
||||
void set_reason(const String& text) { m_reason = text; }
|
||||
|
||||
private:
|
||||
UnavailableDisassemblyWidget(const String& reason)
|
||||
: m_reason(reason)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void paint_event(GUI::PaintEvent& event) override;
|
||||
|
||||
String m_reason;
|
||||
};
|
||||
|
||||
class DisassemblyWidget final : public GUI::Widget {
|
||||
C_OBJECT(DisassemblyWidget)
|
||||
public:
|
||||
virtual ~DisassemblyWidget() override { }
|
||||
|
||||
void update_state(const Debug::DebugSession&, const PtraceRegisters&);
|
||||
void program_stopped();
|
||||
|
||||
private:
|
||||
DisassemblyWidget();
|
||||
|
||||
void show_disassembly();
|
||||
void hide_disassembly(const String&);
|
||||
|
||||
RefPtr<GUI::Widget> m_top_container;
|
||||
RefPtr<GUI::TableView> m_disassembly_view;
|
||||
RefPtr<GUI::Label> m_function_name_label;
|
||||
RefPtr<UnavailableDisassemblyWidget> m_unavailable_disassembly_widget;
|
||||
};
|
||||
|
||||
}
|
121
Userland/DevTools/HackStudio/Debugger/RegistersModel.cpp
Normal file
121
Userland/DevTools/HackStudio/Debugger/RegistersModel.cpp
Normal file
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Luke Wilde <luke.wilde@live.co.uk>
|
||||
* 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 "RegistersModel.h"
|
||||
|
||||
namespace HackStudio {
|
||||
|
||||
RegistersModel::RegistersModel(const PtraceRegisters& regs)
|
||||
: m_raw_registers(regs)
|
||||
{
|
||||
m_registers.append({ "eax", regs.eax });
|
||||
m_registers.append({ "ebx", regs.ebx });
|
||||
m_registers.append({ "ecx", regs.ecx });
|
||||
m_registers.append({ "edx", regs.edx });
|
||||
m_registers.append({ "esp", regs.esp });
|
||||
m_registers.append({ "ebp", regs.ebp });
|
||||
m_registers.append({ "esi", regs.esi });
|
||||
m_registers.append({ "edi", regs.edi });
|
||||
m_registers.append({ "eip", regs.eip });
|
||||
m_registers.append({ "eflags", regs.eflags });
|
||||
m_registers.append({ "cs", regs.cs });
|
||||
m_registers.append({ "ss", regs.ss });
|
||||
m_registers.append({ "ds", regs.ds });
|
||||
m_registers.append({ "es", regs.es });
|
||||
m_registers.append({ "fs", regs.fs });
|
||||
m_registers.append({ "gs", regs.gs });
|
||||
}
|
||||
|
||||
RegistersModel::RegistersModel(const PtraceRegisters& current_regs, const PtraceRegisters& previous_regs)
|
||||
: m_raw_registers(current_regs)
|
||||
{
|
||||
m_registers.append({ "eax", current_regs.eax, current_regs.eax != previous_regs.eax });
|
||||
m_registers.append({ "ebx", current_regs.ebx, current_regs.ebx != previous_regs.ebx });
|
||||
m_registers.append({ "ecx", current_regs.ecx, current_regs.ecx != previous_regs.ecx });
|
||||
m_registers.append({ "edx", current_regs.edx, current_regs.edx != previous_regs.edx });
|
||||
m_registers.append({ "esp", current_regs.esp, current_regs.esp != previous_regs.esp });
|
||||
m_registers.append({ "ebp", current_regs.ebp, current_regs.ebp != previous_regs.ebp });
|
||||
m_registers.append({ "esi", current_regs.esi, current_regs.esi != previous_regs.esi });
|
||||
m_registers.append({ "edi", current_regs.edi, current_regs.edi != previous_regs.edi });
|
||||
m_registers.append({ "eip", current_regs.eip, current_regs.eip != previous_regs.eip });
|
||||
m_registers.append({ "eflags", current_regs.eflags, current_regs.eflags != previous_regs.eflags });
|
||||
m_registers.append({ "cs", current_regs.cs, current_regs.cs != previous_regs.cs });
|
||||
m_registers.append({ "ss", current_regs.ss, current_regs.ss != previous_regs.ss });
|
||||
m_registers.append({ "ds", current_regs.ds, current_regs.ds != previous_regs.ds });
|
||||
m_registers.append({ "es", current_regs.es, current_regs.es != previous_regs.es });
|
||||
m_registers.append({ "fs", current_regs.fs, current_regs.ds != previous_regs.fs });
|
||||
m_registers.append({ "gs", current_regs.gs, current_regs.gs != previous_regs.gs });
|
||||
}
|
||||
|
||||
RegistersModel::~RegistersModel()
|
||||
{
|
||||
}
|
||||
|
||||
int RegistersModel::row_count(const GUI::ModelIndex&) const
|
||||
{
|
||||
return m_registers.size();
|
||||
}
|
||||
|
||||
String RegistersModel::column_name(int column) const
|
||||
{
|
||||
switch (column) {
|
||||
case Column::Register:
|
||||
return "Register";
|
||||
case Column::Value:
|
||||
return "Value";
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
GUI::Variant RegistersModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
|
||||
{
|
||||
auto& reg = m_registers[index.row()];
|
||||
|
||||
if (role == GUI::ModelRole::ForegroundColor) {
|
||||
if (reg.changed)
|
||||
return Color(Color::Red);
|
||||
else
|
||||
return Color(Color::Black);
|
||||
}
|
||||
|
||||
if (role == GUI::ModelRole::Display) {
|
||||
if (index.column() == Column::Register)
|
||||
return reg.name;
|
||||
if (index.column() == Column::Value)
|
||||
return String::formatted("{:08x}", reg.value);
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void RegistersModel::update()
|
||||
{
|
||||
did_update();
|
||||
}
|
||||
|
||||
}
|
77
Userland/DevTools/HackStudio/Debugger/RegistersModel.h
Normal file
77
Userland/DevTools/HackStudio/Debugger/RegistersModel.h
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Luke Wilde <luke.wilde@live.co.uk>
|
||||
* 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 <AK/Vector.h>
|
||||
#include <LibGUI/Model.h>
|
||||
#include <sys/arch/i386/regs.h>
|
||||
|
||||
namespace HackStudio {
|
||||
|
||||
struct RegisterData {
|
||||
String name;
|
||||
u32 value;
|
||||
bool changed { false };
|
||||
};
|
||||
|
||||
class RegistersModel final : public GUI::Model {
|
||||
public:
|
||||
static RefPtr<RegistersModel> create(const PtraceRegisters& regs)
|
||||
{
|
||||
return adopt(*new RegistersModel(regs));
|
||||
}
|
||||
|
||||
static RefPtr<RegistersModel> create(const PtraceRegisters& current_regs, const PtraceRegisters& previous_regs)
|
||||
{
|
||||
return adopt(*new RegistersModel(current_regs, previous_regs));
|
||||
}
|
||||
|
||||
enum Column {
|
||||
Register,
|
||||
Value,
|
||||
__Count
|
||||
};
|
||||
|
||||
virtual ~RegistersModel() override;
|
||||
|
||||
virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
|
||||
virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return Column::__Count; }
|
||||
virtual String column_name(int) const override;
|
||||
virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
|
||||
virtual void update() override;
|
||||
|
||||
const PtraceRegisters& raw_registers() const { return m_raw_registers; }
|
||||
|
||||
private:
|
||||
explicit RegistersModel(const PtraceRegisters& regs);
|
||||
RegistersModel(const PtraceRegisters& current_regs, const PtraceRegisters& previous_regs);
|
||||
|
||||
PtraceRegisters m_raw_registers;
|
||||
Vector<RegisterData> m_registers;
|
||||
};
|
||||
|
||||
}
|
194
Userland/DevTools/HackStudio/Debugger/VariablesModel.cpp
Normal file
194
Userland/DevTools/HackStudio/Debugger/VariablesModel.cpp
Normal file
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
|
||||
* 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 "VariablesModel.h"
|
||||
#include <LibGUI/Application.h>
|
||||
#include <LibGUI/MessageBox.h>
|
||||
|
||||
namespace HackStudio {
|
||||
|
||||
GUI::ModelIndex VariablesModel::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<const Debug::DebugInfo::VariableInfo*>(parent_index.internal_data());
|
||||
auto* child = &parent->members[row];
|
||||
return create_index(row, column, child);
|
||||
}
|
||||
|
||||
GUI::ModelIndex VariablesModel::parent_index(const GUI::ModelIndex& index) const
|
||||
{
|
||||
if (!index.is_valid())
|
||||
return {};
|
||||
auto* child = static_cast<const Debug::DebugInfo::VariableInfo*>(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++) {
|
||||
Debug::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 VariablesModel::row_count(const GUI::ModelIndex& index) const
|
||||
{
|
||||
if (!index.is_valid())
|
||||
return m_variables.size();
|
||||
auto* node = static_cast<const Debug::DebugInfo::VariableInfo*>(index.internal_data());
|
||||
return node->members.size();
|
||||
}
|
||||
|
||||
static String variable_value_as_string(const Debug::DebugInfo::VariableInfo& variable)
|
||||
{
|
||||
if (variable.location_type != Debug::DebugInfo::VariableInfo::LocationType::Address)
|
||||
return "N/A";
|
||||
|
||||
auto variable_address = variable.location_data.address;
|
||||
|
||||
if (variable.is_enum_type()) {
|
||||
auto value = Debugger::the().session()->peek((u32*)variable_address);
|
||||
ASSERT(value.has_value());
|
||||
auto it = variable.type->members.find_if([&enumerator_value = value.value()](const auto& enumerator) {
|
||||
return enumerator->constant_data.as_u32 == enumerator_value;
|
||||
});
|
||||
ASSERT(!it.is_end());
|
||||
return String::formatted("{}::{}", variable.type_name, (*it)->name);
|
||||
}
|
||||
|
||||
if (variable.type_name == "int") {
|
||||
auto value = Debugger::the().session()->peek((u32*)variable_address);
|
||||
ASSERT(value.has_value());
|
||||
return String::formatted("{}", static_cast<int>(value.value()));
|
||||
}
|
||||
|
||||
if (variable.type_name == "char") {
|
||||
auto value = Debugger::the().session()->peek((u32*)variable_address);
|
||||
ASSERT(value.has_value());
|
||||
return String::formatted("'{0:c}' ({0:d})", value.value());
|
||||
}
|
||||
|
||||
if (variable.type_name == "bool") {
|
||||
auto value = Debugger::the().session()->peek((u32*)variable_address);
|
||||
ASSERT(value.has_value());
|
||||
return (value.value() & 1) ? "true" : "false";
|
||||
}
|
||||
|
||||
return String::formatted("type: {} @ {:p}, ", variable.type_name, variable_address);
|
||||
}
|
||||
|
||||
static Optional<u32> string_to_variable_value(const StringView& string_value, const Debug::DebugInfo::VariableInfo& variable)
|
||||
{
|
||||
if (variable.is_enum_type()) {
|
||||
auto prefix_string = String::formatted("{}::", variable.type_name);
|
||||
auto string_to_use = string_value;
|
||||
if (string_value.starts_with(prefix_string))
|
||||
string_to_use = string_value.substring_view(prefix_string.length(), string_value.length() - prefix_string.length());
|
||||
|
||||
auto it = variable.type->members.find_if([string_to_use](const auto& enumerator) {
|
||||
return enumerator->name == string_to_use;
|
||||
});
|
||||
|
||||
if (it.is_end())
|
||||
return {};
|
||||
return (*it)->constant_data.as_u32;
|
||||
}
|
||||
|
||||
if (variable.type_name == "int") {
|
||||
auto value = string_value.to_int();
|
||||
if (value.has_value())
|
||||
return value.value();
|
||||
return {};
|
||||
}
|
||||
|
||||
if (variable.type_name == "bool") {
|
||||
if (string_value == "true")
|
||||
return true;
|
||||
if (string_value == "false")
|
||||
return false;
|
||||
return {};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void VariablesModel::set_variable_value(const GUI::ModelIndex& index, const StringView& string_value, GUI::Window* parent_window)
|
||||
{
|
||||
auto variable = static_cast<const Debug::DebugInfo::VariableInfo*>(index.internal_data());
|
||||
|
||||
auto value = string_to_variable_value(string_value, *variable);
|
||||
|
||||
if (value.has_value()) {
|
||||
auto success = Debugger::the().session()->poke((u32*)variable->location_data.address, value.value());
|
||||
ASSERT(success);
|
||||
return;
|
||||
}
|
||||
|
||||
GUI::MessageBox::show(
|
||||
parent_window,
|
||||
String::formatted("String value \"{}\" could not be converted to a value of type {}.", string_value, variable->type_name),
|
||||
"Set value failed",
|
||||
GUI::MessageBox::Type::Error);
|
||||
}
|
||||
|
||||
GUI::Variant VariablesModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
|
||||
{
|
||||
auto* variable = static_cast<const Debug::DebugInfo::VariableInfo*>(index.internal_data());
|
||||
switch (role) {
|
||||
case GUI::ModelRole::Display: {
|
||||
auto value_as_string = variable_value_as_string(*variable);
|
||||
return String::formatted("{}: {}", variable->name, value_as_string);
|
||||
}
|
||||
case GUI::ModelRole::Icon:
|
||||
return m_variable_icon;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void VariablesModel::update()
|
||||
{
|
||||
did_update();
|
||||
}
|
||||
|
||||
RefPtr<VariablesModel> VariablesModel::create(const PtraceRegisters& regs)
|
||||
{
|
||||
auto lib = Debugger::the().session()->library_at(regs.eip);
|
||||
if (!lib)
|
||||
return nullptr;
|
||||
auto variables = lib->debug_info->get_variables_in_current_scope(regs);
|
||||
return adopt(*new VariablesModel(move(variables), regs));
|
||||
}
|
||||
|
||||
}
|
63
Userland/DevTools/HackStudio/Debugger/VariablesModel.h
Normal file
63
Userland/DevTools/HackStudio/Debugger/VariablesModel.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
|
||||
* 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 <AK/NonnullOwnPtr.h>
|
||||
#include <LibGUI/Model.h>
|
||||
#include <LibGUI/TreeView.h>
|
||||
#include <sys/arch/i386/regs.h>
|
||||
|
||||
namespace HackStudio {
|
||||
|
||||
class VariablesModel final : public GUI::Model {
|
||||
public:
|
||||
static RefPtr<VariablesModel> create(const PtraceRegisters& regs);
|
||||
|
||||
void set_variable_value(const GUI::ModelIndex&, const StringView&, GUI::Window*);
|
||||
|
||||
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, GUI::ModelRole role) 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:
|
||||
explicit VariablesModel(NonnullOwnPtrVector<Debug::DebugInfo::VariableInfo>&& 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"));
|
||||
}
|
||||
NonnullOwnPtrVector<Debug::DebugInfo::VariableInfo> m_variables;
|
||||
PtraceRegisters m_regs;
|
||||
|
||||
GUI::Icon m_variable_icon;
|
||||
};
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue