1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 17:14:58 +00:00
serenity/Userland/DevTools/HackStudio/Debugger/DebugInfoWidget.cpp
kleines Filmröllchen 7e34b88ed4 LibGUI: Fully support TabWidget in GML
TabWidgets couldn't be used in GML properly, as the GML creation
routines didn't actually call the necessary functions in the TabWidget
to get a new tab added. This commit fixes that by making the name of the
tab a normal property, the previously introduced "title", which can be
trivially set from GML. Therefore, try_add_widget() loses an argument
(while try_add_tab doesn't, because it newly constructs the widget).
This allows us to get rid of the silly "fixing my widget tree after the
fact" code in Help and will make it super easy to use TabWidget in
future GML. :^)
2022-04-03 12:21:05 +02:00

193 lines
7.8 KiB
C++

/*
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#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/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::try_load_from_file("/res/icons/16x16/debug-continue.png").release_value_but_fixme_should_propagate_errors(), [](auto&) {
Debugger::the().set_requested_debugger_action(Debugger::DebuggerAction::Continue);
});
m_singlestep_action = GUI::Action::create("Step Over", { Mod_None, Key_F10 }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/debug-step-over.png").release_value_but_fixme_should_propagate_errors(), [](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::try_load_from_file("/res/icons/16x16/debug-step-in.png").release_value_but_fixme_should_propagate_errors(), [](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::try_load_from_file("/res/icons/16x16/debug-step-out.png").release_value_but_fixme_should_propagate_errors(), [](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(build_variables_tab());
variables_tab_widget.add_widget(build_registers_tab());
m_backtrace_view->on_selection_change = [this] {
const auto& index = m_backtrace_view->selection().first();
if (!index.is_valid()) {
return;
}
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 {};
auto backtrace_frame = model.frames()[index.row()];
frame_regs.set_ip(backtrace_frame.instruction_address);
frame_regs.set_bp(backtrace_frame.frame_base);
m_variables_view->set_model(VariablesModel::create(static_cast<VariablesModel&>(*m_variables_view->model()).inspector(), frame_regs));
if (on_backtrace_frame_selection && backtrace_frame.m_source_position.has_value()) {
on_backtrace_frame_selection(*backtrace_frame.m_source_position);
} else {
dbgln("no source position info");
}
};
}
bool DebugInfoWidget::does_variable_support_writing(Debug::DebugInfo::VariableInfo const* variable)
{
if (variable->location_type != Debug::DebugInfo::VariableInfo::LocationType::Address)
return false;
return variable->is_enum_type() || variable->type_name.is_one_of("int", "bool");
}
RefPtr<GUI::Menu> DebugInfoWidget::get_context_menu_for_variable(const GUI::ModelIndex& index)
{
if (!index.is_valid())
return nullptr;
auto context_menu = GUI::Menu::construct();
auto* variable = static_cast<Debug::DebugInfo::VariableInfo const*>(index.internal_data());
if (does_variable_support_writing(variable)) {
context_menu->add_action(GUI::Action::create("Change value", [&](auto&) {
String value;
if (GUI::InputBox::show(window(), value, "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 variable_address = variable->location_data.address;
if (Debugger::the().session()->watchpoint_exists(variable_address)) {
context_menu->add_action(GUI::Action::create("Remove watchpoint", [variable_address](auto&) {
Debugger::the().session()->remove_watchpoint(variable_address);
}));
} else {
auto& backtrace_model = static_cast<BacktraceModel&>(*m_backtrace_view->model());
auto current_frame = m_backtrace_view->selection().first().row();
auto ebp = backtrace_model.frames()[current_frame].frame_base;
context_menu->add_action(GUI::Action::create("Add watchpoint", [variable_address, ebp](auto&) {
Debugger::the().session()->insert_watchpoint(variable_address, ebp);
}));
}
return context_menu;
}
NonnullRefPtr<GUI::Widget> DebugInfoWidget::build_variables_tab()
{
auto variables_widget = GUI::Widget::construct();
variables_widget->set_title("Variables");
variables_widget->set_layout<GUI::HorizontalBoxLayout>();
m_variables_view = variables_widget->add<GUI::TreeView>();
m_variables_view->on_context_menu_request = [this](auto& index, auto& event) {
m_variable_context_menu = get_context_menu_for_variable(index);
if (m_variable_context_menu)
m_variable_context_menu->popup(event.screen_position());
};
return variables_widget;
}
NonnullRefPtr<GUI::Widget> DebugInfoWidget::build_registers_tab()
{
auto registers_widget = GUI::Widget::construct();
registers_widget->set_title("Registers");
registers_widget->set_layout<GUI::HorizontalBoxLayout>();
m_registers_view = registers_widget->add<GUI::TableView>();
return registers_widget;
}
void DebugInfoWidget::update_state(Debug::ProcessInspector& inspector, PtraceRegisters const& regs)
{
m_variables_view->set_model(VariablesModel::create(inspector, regs));
m_backtrace_view->set_model(BacktraceModel::create(inspector, 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);
}
}