From 3ddc42fdf1266ac5dea91568b6348ab39923eabe Mon Sep 17 00:00:00 2001 From: Luke Date: Tue, 25 Aug 2020 04:36:55 +0100 Subject: [PATCH] HackStudio: Add a disassembly view for the current function in debug mode --- DevTools/HackStudio/CMakeLists.txt | 4 +- .../HackStudio/Debugger/DisassemblyModel.cpp | 133 ++++++++++++++++++ .../HackStudio/Debugger/DisassemblyModel.h | 77 ++++++++++ .../HackStudio/Debugger/DisassemblyWidget.cpp | 103 ++++++++++++++ .../HackStudio/Debugger/DisassemblyWidget.h | 78 ++++++++++ DevTools/HackStudio/main.cpp | 4 + 6 files changed, 398 insertions(+), 1 deletion(-) create mode 100644 DevTools/HackStudio/Debugger/DisassemblyModel.cpp create mode 100644 DevTools/HackStudio/Debugger/DisassemblyModel.h create mode 100644 DevTools/HackStudio/Debugger/DisassemblyWidget.cpp create mode 100644 DevTools/HackStudio/Debugger/DisassemblyWidget.h diff --git a/DevTools/HackStudio/CMakeLists.txt b/DevTools/HackStudio/CMakeLists.txt index ff85a9adf1..b60225946e 100644 --- a/DevTools/HackStudio/CMakeLists.txt +++ b/DevTools/HackStudio/CMakeLists.txt @@ -3,6 +3,8 @@ set(SOURCES Debugger/BacktraceModel.cpp Debugger/Debugger.cpp Debugger/DebugInfoWidget.cpp + Debugger/DisassemblyModel.cpp + Debugger/DisassemblyWidget.cpp Debugger/VariablesModel.cpp Editor.cpp EditorWrapper.cpp @@ -22,4 +24,4 @@ set(SOURCES ) serenity_bin(HackStudio) -target_link_libraries(HackStudio LibWeb LibMarkdown LibGUI LibGfx LibCore LibVT LibDebug) +target_link_libraries(HackStudio LibWeb LibMarkdown LibGUI LibGfx LibCore LibVT LibDebug LibX86) diff --git a/DevTools/HackStudio/Debugger/DisassemblyModel.cpp b/DevTools/HackStudio/Debugger/DisassemblyModel.cpp new file mode 100644 index 0000000000..fed6660afe --- /dev/null +++ b/DevTools/HackStudio/Debugger/DisassemblyModel.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2020, Luke Wilde + * 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 +#include +#include +#include +#include +#include +#include +#include + +namespace HackStudio { + +DisassemblyModel::DisassemblyModel(const Debug::DebugSession& debug_session, const PtraceRegisters& regs) +{ + auto containing_function = debug_session.debug_info().get_containing_function(regs.eip); + if (!containing_function.has_value()) { + dbg() << "Cannot disassemble as the containing function was not found."; + return; + } + + RefPtr elf_loader; + + if (containing_function.value().address_low >= 0xc0000000) { + auto kernel_file = make("/boot/Kernel"); + if (!kernel_file->is_valid()) + return; + elf_loader = ELF::Loader::create((const u8*)kernel_file->data(), kernel_file->size()); + } else { + elf_loader = debug_session.elf(); + } + + auto symbol = elf_loader->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_loader); + 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::format("%#08x", insn.address); + if (index.column() == Column::InstructionBytes) { + StringBuilder builder; + for (auto ch : insn.bytes) { + builder.appendf("%02x ", (u8)ch); + } + return builder.to_string(); + } + if (index.column() == Column::Disassembly) + return insn.disassembly; + return {}; + } + return {}; +} + +void DisassemblyModel::update() +{ + did_update(); +} + +} diff --git a/DevTools/HackStudio/Debugger/DisassemblyModel.h b/DevTools/HackStudio/Debugger/DisassemblyModel.h new file mode 100644 index 0000000000..c09d23a04a --- /dev/null +++ b/DevTools/HackStudio/Debugger/DisassemblyModel.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020, Luke Wilde + * 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 +#include +#include +#include + +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 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 m_instructions; +}; + +} diff --git a/DevTools/HackStudio/Debugger/DisassemblyWidget.cpp b/DevTools/HackStudio/Debugger/DisassemblyWidget.cpp new file mode 100644 index 0000000000..eec5aaefd5 --- /dev/null +++ b/DevTools/HackStudio/Debugger/DisassemblyWidget.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2020, Luke Wilde + * 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 +#include +#include + +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(); + + m_top_container = add(); + m_top_container->set_layout(); + m_top_container->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); + m_top_container->set_preferred_size(0, 20); + + m_function_name_label = m_top_container->add(""); + m_function_name_label->set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fill); + + m_disassembly_view = add(); + + m_unavailable_disassembly_widget = add(""); + + 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 containing_function = debug_session.debug_info().get_containing_function(regs.eip); + if (containing_function.has_value()) + m_function_name_label->set_text(containing_function.value().name); + else + m_function_name_label->set_text(""); + 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(""); +} + +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); +} + +} diff --git a/DevTools/HackStudio/Debugger/DisassemblyWidget.h b/DevTools/HackStudio/Debugger/DisassemblyWidget.h new file mode 100644 index 0000000000..b84bc9fedb --- /dev/null +++ b/DevTools/HackStudio/Debugger/DisassemblyWidget.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2020, Luke Wilde + * 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 +#include +#include + +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 m_top_container; + RefPtr m_disassembly_view; + RefPtr m_function_name_label; + RefPtr m_unavailable_disassembly_widget; +}; + +} diff --git a/DevTools/HackStudio/main.cpp b/DevTools/HackStudio/main.cpp index 5de2cfc823..7d33bd8b20 100644 --- a/DevTools/HackStudio/main.cpp +++ b/DevTools/HackStudio/main.cpp @@ -27,6 +27,7 @@ #include "CursorTool.h" #include "Debugger/DebugInfoWidget.h" #include "Debugger/Debugger.h" +#include "Debugger/DisassemblyWidget.h" #include "Editor.h" #include "EditorWrapper.h" #include "FindInFilesWidget.h" @@ -540,6 +541,7 @@ int main_impl(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& disassembly_widget = s_action_tab_widget->add_tab("Disassembly"); auto& locator = widget.add(); @@ -630,6 +632,7 @@ int main_impl(int argc, char** argv) current_editor_in_execution->editor().set_execution_position(source_position.value().line_number - 1); debug_info_widget.update_state(*Debugger::the().session(), regs); debug_info_widget.set_debug_actions_enabled(true); + disassembly_widget.update_state(*Debugger::the().session(), regs); reveal_action_tab(debug_info_widget); })); Core::EventLoop::wake(); @@ -648,6 +651,7 @@ int main_impl(int argc, char** argv) [&]() { Core::EventLoop::main().post_event(*g_window, make([&](auto&) { debug_info_widget.program_stopped(); + disassembly_widget.program_stopped(); hide_action_tabs(); GUI::MessageBox::show(g_window, "Program Exited", "Debugger", GUI::MessageBox::Type::Information); }));