diff options
Diffstat (limited to 'src/debugger')
| -rw-r--r-- | src/debugger/debugger.cpp | 266 | ||||
| -rw-r--r-- | src/debugger/debugger.h | 99 | ||||
| -rw-r--r-- | src/debugger/types.h | 120 |
3 files changed, 485 insertions, 0 deletions
diff --git a/src/debugger/debugger.cpp b/src/debugger/debugger.cpp new file mode 100644 index 0000000..7cd2d01 --- /dev/null +++ b/src/debugger/debugger.cpp @@ -0,0 +1,266 @@ +#include "debugger/debugger.h" + +#include "core/log.h" + +#include <algorithm> +#include <source_location> + +namespace Hsdbg +{ + namespace + { + // every call site reports itself, so the stubs stay one line each until + // there is an lldb session behind them + auto not_implemented(std::source_location location = std::source_location::current()) + -> std::unexpected<std::string> + { + Log::warn("{} is not implemented yet", location.function_name()); + return fail("{} is not implemented yet", location.function_name()); + } + } + + Debugger::Debugger() + { + Log::info("debugger: ready"); + } + + Debugger::~Debugger() + { + unload_target(); + } + + auto Debugger::load_target(const std::filesystem::path& executable) -> Result<void> + { + std::error_code error; + + if (!std::filesystem::exists(executable, error)) + return fail("'{}' does not exist", executable.string()); + + if (!std::filesystem::is_regular_file(executable, error)) + return fail("'{}' is not a file", executable.string()); + + m_target_path = std::filesystem::absolute(executable, error); + if (error) + m_target_path = executable; + + m_process_id = 0; + m_stop_reason = StopReason::None; + set_state(TargetState::Loaded); + + Log::info("debugger: loaded target '{}'", m_target_path.string()); + + return {}; + } + + auto Debugger::unload_target() -> void + { + m_target_path.clear(); + m_process_id = 0; + m_stop_reason = StopReason::None; + + m_threads.clear(); + m_call_stack.clear(); + m_locals.clear(); + m_registers.clear(); + m_console_output.clear(); + + m_selected_thread = 0; + m_selected_frame = 0; + + for (Breakpoint& breakpoint : m_breakpoints) + { + breakpoint.resolved = false; + breakpoint.hit_count = 0; + breakpoint.address = 0; + } + + set_state(TargetState::NoTarget); + } + + auto Debugger::launch(const LaunchSpec& spec) -> Result<void> + { + (void)spec; + return not_implemented(); + } + + auto Debugger::attach(uint64_t process_id) -> Result<void> + { + (void)process_id; + return not_implemented(); + } + + auto Debugger::detach() -> Result<void> + { + return not_implemented(); + } + + auto Debugger::terminate() -> Result<void> + { + return not_implemented(); + } + + auto Debugger::resume() -> Result<void> + { + return not_implemented(); + } + + auto Debugger::pause() -> Result<void> + { + return not_implemented(); + } + + auto Debugger::step_over(StepMode mode) -> Result<void> + { + (void)mode; + return not_implemented(); + } + + auto Debugger::step_into(StepMode mode) -> Result<void> + { + (void)mode; + return not_implemented(); + } + + auto Debugger::step_out() -> Result<void> + { + return not_implemented(); + } + + auto Debugger::run_to(const std::filesystem::path& file, uint32_t line) -> Result<void> + { + (void)file; + (void)line; + return not_implemented(); + } + + auto Debugger::add_breakpoint(const std::filesystem::path& file, uint32_t line) -> uint32_t + { + const auto existing = std::ranges::find_if(m_breakpoints, [&](const Breakpoint& candidate) + { + return candidate.file == file && candidate.line == line; + }); + + if (existing != m_breakpoints.end()) + return existing->id; + + Breakpoint breakpoint; + breakpoint.id = m_next_breakpoint_id++; + breakpoint.file = file; + breakpoint.line = line; + + m_breakpoints.push_back(std::move(breakpoint)); + + Log::info("debugger: breakpoint {} at {}:{}", + m_breakpoints.back().id, + file.filename().string(), + line); + + return m_breakpoints.back().id; + } + + auto Debugger::add_function_breakpoint(std::string_view function) -> uint32_t + { + const auto existing = std::ranges::find_if(m_breakpoints, [&](const Breakpoint& candidate) + { + return candidate.function == function && candidate.line == 0; + }); + + if (existing != m_breakpoints.end()) + return existing->id; + + Breakpoint breakpoint; + breakpoint.id = m_next_breakpoint_id++; + breakpoint.function = function; + + m_breakpoints.push_back(std::move(breakpoint)); + + Log::info("debugger: breakpoint {} at {}()", m_breakpoints.back().id, function); + + return m_breakpoints.back().id; + } + + auto Debugger::remove_breakpoint(uint32_t id) -> bool + { + const auto entry = std::ranges::find(m_breakpoints, id, &Breakpoint::id); + if (entry == m_breakpoints.end()) + return false; + + m_breakpoints.erase(entry); + + Log::info("debugger: removed breakpoint {}", id); + + return true; + } + + auto Debugger::set_breakpoint_enabled(uint32_t id, bool enabled) -> bool + { + Breakpoint* breakpoint = find_breakpoint(id); + if (breakpoint == nullptr) + return false; + + breakpoint->enabled = enabled; + + return true; + } + + auto Debugger::clear_breakpoints() -> void + { + m_breakpoints.clear(); + } + + auto Debugger::find_breakpoint(uint32_t id) -> Breakpoint* + { + const auto entry = std::ranges::find(m_breakpoints, id, &Breakpoint::id); + + return entry == m_breakpoints.end() ? nullptr : &*entry; + } + + auto Debugger::evaluate(std::string_view expression) -> Result<std::string> + { + (void)expression; + return not_implemented(); + } + + auto Debugger::read_memory(uint64_t address, size_t size) -> Result<std::vector<uint8_t>> + { + (void)address; + (void)size; + return not_implemented(); + } + + auto Debugger::select_thread(uint64_t thread_id) -> bool + { + const auto entry = std::ranges::find(m_threads, thread_id, &Thread::id); + if (entry == m_threads.end()) + return false; + + m_selected_thread = thread_id; + m_selected_frame = 0; + + return true; + } + + auto Debugger::select_frame(uint32_t frame_index) -> bool + { + if (frame_index >= m_call_stack.size()) + return false; + + m_selected_frame = frame_index; + + return true; + } + + auto Debugger::update() -> void + { + } + + auto Debugger::set_state(TargetState state) -> void + { + if (m_state == state) + return; + + Log::debug("debugger: {} -> {}", to_string(m_state), to_string(state)); + + m_state = state; + } +} diff --git a/src/debugger/debugger.h b/src/debugger/debugger.h new file mode 100644 index 0000000..c810417 --- /dev/null +++ b/src/debugger/debugger.h @@ -0,0 +1,99 @@ +#pragma once + +#include "core/result.h" +#include "debugger/types.h" + +#include <cstdint> +#include <filesystem> +#include <span> +#include <string> +#include <string_view> +#include <vector> + +namespace Hsdbg +{ + class Debugger + { + public: + Debugger(); + ~Debugger(); + + Debugger(const Debugger&) = delete; + Debugger(Debugger&&) = delete; + auto operator=(const Debugger&) -> Debugger& = delete; + auto operator=(Debugger&&) -> Debugger& = delete; + + // target lifetime + auto load_target(const std::filesystem::path& executable) -> Result<void>; + auto unload_target() -> void; + auto launch(const LaunchSpec& spec) -> Result<void>; + auto attach(uint64_t process_id) -> Result<void>; + auto detach() -> Result<void>; + auto terminate() -> Result<void>; + + // execution control + auto resume() -> Result<void>; + auto pause() -> Result<void>; + auto step_over(StepMode mode = StepMode::Line) -> Result<void>; + auto step_into(StepMode mode = StepMode::Line) -> Result<void>; + auto step_out() -> Result<void>; + auto run_to(const std::filesystem::path& file, uint32_t line) -> Result<void>; + + // breakpoints, the only part that keeps real state for now + auto add_breakpoint(const std::filesystem::path& file, uint32_t line) -> uint32_t; + auto add_function_breakpoint(std::string_view function) -> uint32_t; + auto remove_breakpoint(uint32_t id) -> bool; + auto set_breakpoint_enabled(uint32_t id, bool enabled) -> bool; + auto clear_breakpoints() -> void; + auto find_breakpoint(uint32_t id) -> Breakpoint*; + auto breakpoints() const -> std::span<const Breakpoint> { return m_breakpoints; } + + // inspection + auto threads() const -> std::span<const Thread> { return m_threads; } + auto call_stack() const -> std::span<const StackFrame> { return m_call_stack; } + auto locals() const -> std::span<const Variable> { return m_locals; } + auto registers() const -> std::span<const Register> { return m_registers; } + auto evaluate(std::string_view expression) -> Result<std::string>; + auto read_memory(uint64_t address, size_t size) -> Result<std::vector<uint8_t>>; + auto console_output() const -> std::span<const std::string> { return m_console_output; } + + // selection, what the ui is currently looking at + auto select_thread(uint64_t thread_id) -> bool; + auto select_frame(uint32_t frame_index) -> bool; + auto selected_thread() const -> uint64_t { return m_selected_thread; } + auto selected_frame() const -> uint32_t { return m_selected_frame; } + + // pumps whatever the debug session has to say, called once per frame + auto update() -> void; + + auto state() const -> TargetState { return m_state; } + auto stop_reason() const -> StopReason { return m_stop_reason; } + auto target_path() const -> const std::filesystem::path& { return m_target_path; } + auto process_id() const -> uint64_t { return m_process_id; } + + auto has_target() const -> bool { return m_state != TargetState::NoTarget; } + auto is_running() const -> bool { return m_state == TargetState::Running; } + auto is_stopped() const -> bool { return m_state == TargetState::Stopped; } + + private: + auto set_state(TargetState state) -> void; + + TargetState m_state = TargetState::NoTarget; + StopReason m_stop_reason = StopReason::None; + + std::filesystem::path m_target_path; + uint64_t m_process_id = 0; + + std::vector<Breakpoint> m_breakpoints; + uint32_t m_next_breakpoint_id = 1; + + std::vector<Thread> m_threads; + std::vector<StackFrame> m_call_stack; + std::vector<Variable> m_locals; + std::vector<Register> m_registers; + std::vector<std::string> m_console_output; + + uint64_t m_selected_thread = 0; + uint32_t m_selected_frame = 0; + }; +} diff --git a/src/debugger/types.h b/src/debugger/types.h new file mode 100644 index 0000000..2f9a698 --- /dev/null +++ b/src/debugger/types.h @@ -0,0 +1,120 @@ +#pragma once + +#include <cstdint> +#include <filesystem> +#include <string> +#include <string_view> +#include <vector> + +namespace Hsdbg +{ + enum class TargetState : uint8_t + { + NoTarget, + Loaded, + Launching, + Running, + Stopped, + Exited, + Crashed, + }; + + enum class StopReason : uint8_t + { + None, + Breakpoint, + Step, + Signal, + Exception, + Watchpoint, + }; + + enum class StepMode : uint8_t + { + Line, + Instruction, + }; + + constexpr auto to_string(TargetState state) -> std::string_view + { + switch (state) + { + case TargetState::NoTarget: return "no target"; + case TargetState::Loaded: return "loaded"; + case TargetState::Launching: return "launching"; + case TargetState::Running: return "running"; + case TargetState::Stopped: return "stopped"; + case TargetState::Exited: return "exited"; + case TargetState::Crashed: return "crashed"; + } + + return "unknown"; + } + + constexpr auto to_string(StopReason reason) -> std::string_view + { + switch (reason) + { + case StopReason::None: return "none"; + case StopReason::Breakpoint: return "breakpoint"; + case StopReason::Step: return "step"; + case StopReason::Signal: return "signal"; + case StopReason::Exception: return "exception"; + case StopReason::Watchpoint: return "watchpoint"; + } + + return "unknown"; + } + + struct LaunchSpec + { + std::filesystem::path executable; + std::filesystem::path working_directory; + std::vector<std::string> arguments; + std::vector<std::string> environment; + bool stop_at_entry = true; + }; + + struct Breakpoint + { + uint32_t id = 0; + std::filesystem::path file; + uint32_t line = 0; + std::string function; + uint64_t address = 0; + std::string condition; + uint32_t hit_count = 0; + bool enabled = true; + bool resolved = false; + }; + + struct StackFrame + { + uint32_t index = 0; + uint64_t program_counter = 0; + std::string function; + std::filesystem::path file; + uint32_t line = 0; + }; + + struct Thread + { + uint64_t id = 0; + std::string name; + StopReason stop_reason = StopReason::None; + }; + + struct Variable + { + std::string name; + std::string type; + std::string value; + std::vector<Variable> children; + }; + + struct Register + { + std::string name; + uint64_t value = 0; + }; +} |
