hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/src/debugger/debugger.h
blob: d7885b59f9fbd99b930fa7c8f0be40f983a6a18b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
#pragma once

#include "core/result.h"
#include "debugger/types.h"

#include <chrono>
#include <cstdint>
#include <filesystem>
#include <unordered_map>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <vector>

namespace lldb
{
    class SBThread;
}

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;

        static auto backend_version() -> std::string;

        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 pid) -> Result<void>;
        auto detach() -> Result<void>;
        auto terminate() -> Result<void>;

        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>;

        auto add_breakpoint(const std::filesystem::path& file, uint32_t line) -> uint32_t;
        auto add_function_breakpoint(std::string_view function) -> uint32_t;
        auto add_address_breakpoint(uint64_t file_address) -> uint32_t;
        auto remove_breakpoint(uint32_t id) -> bool;
        auto set_breakpoint_enabled(uint32_t id, bool enabled) -> bool;
        auto set_breakpoint_condition(uint32_t id, std::string_view condition) -> bool;
        auto set_breakpoint_ignore_count(uint32_t id, uint32_t count) -> bool;
        auto clear_breakpoints() -> void;
        auto find_breakpoint(uint32_t id) -> Breakpoint*;
        auto breakpoints() const -> std::span<const Breakpoint> { return m_breakpoints; }

        auto add_trace(std::string_view function) -> uint32_t;
        auto remove_trace(uint32_t id) -> bool;
        auto clear_traces() -> void;
        auto traces() const -> std::span<const FunctionTrace> { return m_traces; }

        auto timeline() const -> std::span<const TimelineSpan> { return m_timeline; }

        auto span_label(uint32_t trace_id) const -> const char*;

        auto instrumentation_active() const -> bool { return m_instr_available; }

        auto set_sampling_enabled(bool enabled) -> void { m_sampling_enabled = enabled; }
        auto sampling_enabled() const -> bool { return m_sampling_enabled; }

        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 symbols() const -> std::span<const Symbol> { return m_symbols; }
        auto source_files() const -> std::span<const std::filesystem::path> { return m_source_files; }
        auto disassembly() const -> std::span<const Instruction> { return m_disassembly; }
        auto disassembly_name() const -> std::string_view { return m_disassembly_name; }
        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; }

        auto resident_memory() const -> uint64_t { return m_resident_memory; }

        auto select_thread(uint64_t thread_id) -> bool;
        auto select_frame(uint32_t frame_index) -> bool;
        auto select_symbol(uint64_t file_address) -> bool;
        auto selected_thread() const -> uint64_t { return m_selected_thread; }
        auto selected_frame() const -> uint32_t { return m_selected_frame; }
        auto selected_symbol() const -> uint64_t { return m_selected_symbol; }

        auto update() -> void;

        auto state() const -> TargetState { return m_state; }
        auto stop_reason() const -> StopReason { return m_stop_reason; }

        auto stop_count() const -> uint64_t { return m_stop_count; }

        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:
        struct Session;

        auto set_state(TargetState next_state) -> void;
        auto resolve_breakpoint(Breakpoint& breakpoint) -> void;
        auto sync_breakpoints() -> void;

        auto require_stopped() const -> Result<void>;
        auto sync_after_start() -> void;
        auto pump_events() -> void;
        auto drain_output() -> void;
        auto on_stopped() -> void;
        auto on_exited() -> void;
        auto refresh_call_stack() -> void;
        auto refresh_frame_data() -> void;
        auto refresh_symbols() -> void;
        auto refresh_source_files() -> void;
        auto refresh_disassembly() -> void;
        auto load_disassembly(uint64_t file_address) -> void;
        auto sample_process_stats() -> void;

        auto resolve_trace(FunctionTrace& trace) -> void;

        auto handle_trace_stop() -> bool;
        auto record_trace_entry(FunctionTrace& trace, lldb::SBThread& thread) -> void;
        auto record_trace_return(lldb::SBThread& thread) -> void;
        auto trace_now() const -> double;

        struct InstrRecord;
        auto resolve_instrumentation() -> void;
        auto read_instrumentation() -> void;
        auto apply_instr_record(const InstrRecord& record) -> void;
        auto intern_instr_function(uint64_t address) -> uint32_t;
        auto symbol_load_address(const char* name) -> uint64_t;

        struct InstrOpenCall
        {
            uint32_t trace_id = 0;
            uint64_t start_ns = 0;
            size_t span_index = 0;
        };

        auto maybe_request_sample() -> void;
        auto take_sample_and_resume() -> bool;
        auto take_sample() -> void;
        auto fold_sample(uint64_t thread_id, const std::vector<uint32_t>& stack, double now) -> void;
        auto intern_named_function(std::string_view name) -> uint32_t;

        struct OpenSample
        {
            uint32_t trace_id = 0;
            size_t span_index = 0;
        };

        auto ensure_return_breakpoint(uint64_t address) -> int32_t;
        auto clear_return_breakpoints() -> void;

        struct PendingCall
        {
            uint32_t trace_id = 0;
            uint64_t thread_id = 0;
            uint64_t return_pc = 0;
            uint64_t frame_sp = 0;
            double start = 0.0;
            size_t call_index = 0;
            size_t span_index = 0;
        };

        std::unique_ptr<Session> m_session;

        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<FunctionTrace> m_traces;
        uint32_t m_next_trace_id = 1;
        std::vector<PendingCall> m_pending_calls;
        std::vector<TimelineSpan> m_timeline;

        std::unordered_map<uint64_t, int32_t> m_return_breakpoints;

        std::vector<Thread> m_threads;
        std::vector<StackFrame> m_call_stack;
        std::vector<Variable> m_locals;
        std::vector<Register> m_registers;
        std::vector<Symbol> m_symbols;
        std::vector<std::filesystem::path> m_source_files;
        std::vector<Instruction> m_disassembly;
        std::string m_disassembly_name;
        std::vector<std::string> m_console_output;

        uint64_t m_selected_thread = 0;
        uint32_t m_selected_frame = 0;
        uint64_t m_selected_symbol = 0;
        uint64_t m_stop_count = 0;
        uint64_t m_resident_memory = 0;

        std::chrono::steady_clock::time_point m_trace_epoch = std::chrono::steady_clock::now();

        bool m_instr_checked = false;
        bool m_instr_available = false;
        uint64_t m_instr_head_addr = 0;
        uint64_t m_instr_records_addr = 0;
        uint64_t m_instr_capacity = 0;
        uint64_t m_instr_read_count = 0;
        uint64_t m_instr_base_ns = 0;
        bool m_instr_base_set = false;
        std::unordered_map<uint64_t, uint32_t> m_instr_functions;
        std::unordered_map<uint32_t, std::string> m_instr_names;
        std::unordered_map<uint64_t, std::vector<InstrOpenCall>> m_instr_stacks;

        bool m_sampling_enabled = false;
        bool m_sample_pending = false;
        std::chrono::steady_clock::time_point m_sample_last{};
        std::unordered_map<std::string, uint32_t> m_sample_functions;
        std::unordered_map<uint64_t, std::vector<OpenSample>> m_sample_stacks;
    };
}