1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 08:08:12 +00:00

HackStudio: Add cpp debugger

The HackStudio debugger integrates with LibDebug to provide
source-level debugging.

The user can set breakpoints at various positions in the source code,
and then run the program in debug mode.

When the program is stopped, the current execution position is
displayed, and the user can insert/remove breakpoints, continue
execution, or single step the program.
This commit is contained in:
Itamar 2020-04-25 00:01:19 +03:00 committed by Andreas Kling
parent 393560d8a2
commit 74f41d5f98
8 changed files with 368 additions and 2 deletions

View file

@ -25,6 +25,7 @@
*/
#include "CursorTool.h"
#include "Debugger.h"
#include "Editor.h"
#include "EditorWrapper.h"
#include "FindInFilesWidget.h"
@ -36,7 +37,10 @@
#include "WidgetTool.h"
#include "WidgetTreeModel.h"
#include <AK/StringBuilder.h>
#include <LibCore/Event.h>
#include <LibCore/EventLoop.h>
#include <LibCore/File.h>
#include <LibDebug/DebugSession.h>
#include <LibGUI/AboutDialog.h>
#include <LibGUI/Action.h>
#include <LibGUI/ActionGroup.h>
@ -62,6 +66,8 @@
#include <LibGUI/TreeView.h>
#include <LibGUI/Widget.h>
#include <LibGUI/Window.h>
#include <LibThread/Lock.h>
#include <LibThread/Thread.h>
#include <LibVT/TerminalWidget.h>
#include <stdio.h>
#include <sys/wait.h>
@ -84,7 +90,7 @@ static RefPtr<GUI::TabWidget> s_action_tab_widget;
void add_new_editor(GUI::Widget& parent)
{
auto wrapper = EditorWrapper::construct();
auto wrapper = EditorWrapper::construct(Debugger::on_breakpoint_change);
if (s_action_tab_widget) {
parent.insert_child_before(wrapper, *s_action_tab_widget);
} else {
@ -120,6 +126,24 @@ Editor& current_editor()
return current_editor_wrapper().editor();
}
NonnullRefPtr<EditorWrapper> get_editor_of_file(const String& file)
{
for (auto& wrapper : g_all_editor_wrappers) {
String wrapper_file = wrapper.filename_label().text();
if (wrapper_file == file || String::format("./%s", wrapper_file.characters()) == file) {
return wrapper;
}
}
ASSERT_NOT_REACHED();
}
String get_project_executable_path()
{
// e.g /my/project.files => /my/project
// TODO: Perhaps a Makefile rule for getting the value of $(PROGRAM) would be better?
return g_project->path().substring(0, g_project->path().index_of(".").value());
}
static void build(TerminalWrapper&);
static void run(TerminalWrapper&);
void open_project(String);
@ -531,13 +555,78 @@ int main(int argc, char** argv)
run(terminal_wrapper);
stop_action->set_enabled(true);
});
RefPtr<LibThread::Thread> debugger_thread;
auto debug_action = GUI::Action::create("Debug", Gfx::Bitmap::load_from_file("/res/icons/16x16/play-debug.png"), [&](auto&) {
if (g_project->type() != ProjectType::Cpp) {
GUI::MessageBox::show(String::format("Cannot debug current project type", get_project_executable_path().characters()), "Error", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK, g_window);
return;
}
if (!GUI::FilePicker::file_exists(get_project_executable_path())) {
GUI::MessageBox::show(String::format("Could not find file: %s. (did you build the project?)", get_project_executable_path().characters()), "Error", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK, g_window);
return;
}
if (Debugger::the().session()) {
GUI::MessageBox::show("Debugger is already running", "Error", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK, g_window);
return;
}
Debugger::the().set_executable_path(get_project_executable_path());
debugger_thread = adopt(*new LibThread::Thread(Debugger::start_static));
debugger_thread->start();
});
auto continue_action = GUI::Action::create("Continue", Gfx::Bitmap::load_from_file("/res/icons/16x16/go-last.png"), [&](auto&) {
pthread_mutex_lock(Debugger::the().continue_mutex());
Debugger::the().set_continue_type(Debugger::ContinueType::Continue);
pthread_cond_signal(Debugger::the().continue_cond());
pthread_mutex_unlock(Debugger::the().continue_mutex());
});
auto single_step_action = GUI::Action::create("Single Step", Gfx::Bitmap::load_from_file("/res/icons/16x16/single-step.png"), [&](auto&) {
pthread_mutex_lock(Debugger::the().continue_mutex());
Debugger::the().set_continue_type(Debugger::ContinueType::SourceSingleStep);
pthread_cond_signal(Debugger::the().continue_cond());
pthread_mutex_unlock(Debugger::the().continue_mutex());
});
continue_action->set_enabled(false);
single_step_action->set_enabled(false);
toolbar.add_action(run_action);
toolbar.add_action(stop_action);
toolbar.add_action(debug_action);
toolbar.add_action(continue_action);
toolbar.add_action(single_step_action);
RefPtr<EditorWrapper> current_editor_in_execution;
Debugger::initialize(
[&](DebugInfo::SourcePosition source_position) {
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);
continue_action->set_enabled(true);
single_step_action->set_enabled(true);
},
[&]() {
dbg() << "Program continued";
continue_action->set_enabled(false);
single_step_action->set_enabled(false);
if (current_editor_in_execution) {
current_editor_in_execution->editor().clear_execution_position();
}
},
[&]() {
dbg() << "Program exited";
Core::EventLoop::main().post_event(*g_window, make<Core::DeferredInvocationEvent>([=](auto&) {
GUI::MessageBox::show("Program Exited", "Debugger", GUI::MessageBox::Type::Information, GUI::MessageBox::InputType::OK, g_window);
}));
Core::EventLoop::wake();
});
auto& build_menu = menubar->add_menu("Build");
build_menu.add_action(build_action);
build_menu.add_action(run_action);
build_menu.add_action(stop_action);
build_menu.add_action(debug_action);
auto& view_menu = menubar->add_menu("View");
view_menu.add_action(hide_action_tabs_action);
@ -613,6 +702,9 @@ void open_project(String filename)
g_project_tree_view->toggle_index(g_project_tree_view->model()->index(0, 0));
g_project_tree_view->update();
}
if (Debugger::is_initialized()) {
Debugger::the().reset_breakpoints();
}
}
void open_file(const String& filename)