Max OS 0.3
Loading...
Searching...
No Matches
gdbstub.cpp
Go to the documentation of this file.
1
9#include <runtime/gdbstub.h>
10#include <processes/scheduler.h>
11
12using namespace MaxOS;
13using namespace MaxOS::runtime;
14using namespace MaxOS::hardwarecommunication;
15using namespace MaxOS::common;
16using namespace MaxOS::console;
17using namespace MaxOS::processes;
18using namespace MaxOS::system;
19using namespace MaxOS::memory;
20
29: InterruptHandler(0x24, 4, 0x18),
30 m_serial(serial),
31 m_trap_flag_handler(this),
32 m_break_point_handler(this)
33{
34}
35
36GDBServer::~GDBServer() = default;
37
44
45 // Pause the thread
46 thread->thread_state = ThreadState::WAITING;
47
48 // Read the thread info
49 m_attached_thread = thread;
50 thread_regs = (const cpu_status_t&)m_attached_thread->execution_state;
51
52}
53
57void GDBServer::handle_interrupt() {
58
59 // Read the packet
60 while (m_serial->can_read())
61 current_command_buffer += m_serial->read_char();
62
63 try_handle_command();
64}
65
69void GDBServer::try_handle_command() {
70
71 // Find the checksum
72 int checksum_start = -1;
73 for (int i = 0; i < current_command_buffer.length(); ++i)
74 if (current_command_buffer[i] == '#') {
75 checksum_start = i;
76 break;
77 }
78
79 // Haven't received the full command yet
80 if (checksum_start == -1 || current_command_buffer.length() < checksum_start + 3)
81 return;
82
83 // Find the command
84 int command_start = -1;
85 for (int i = 0; i < current_command_buffer.length(); ++i)
86 if (current_command_buffer[i] == '$') {
87 command_start = i;
88 break;
89 }
90
91 // Parse the command
92 string command = current_command_buffer.substring(command_start + 1, checksum_start - command_start - 1);
93 string checksum = current_command_buffer.substring(checksum_start + 1, 2);
94 current_command_buffer = current_command_buffer.substring(checksum_start + 3, current_command_buffer.length() - checksum_start - 2);
95
96 // Discard any garbage messages
97 if (!check_checksum(command, checksum)) {
98 Logger::DEBUG() << "Dropping: " << command << " (expected: " << checksum << " calced " << get_checksum(command) << ")\n";
99 return;
100 }
101
102 // Handle the command
103 delegate_command(command);
104
105 // More commands to process
106 if (current_command_buffer.length() > 0)
107 try_handle_command();
108}
109
115void GDBServer::delegate_command(const string &command) {
116
117 // GDB querying why halted?
118 if (command == "?") {
119 send_message("S05");
120 return;
121 }
122
123 // GDB step instruction
124 if (command == "s") {
126 return;
127 }
128
129 // GDB requesting register dump
130 if (command == "g") {
132 return;
133 }
134
135 // GDB continue execution
136 if (command == "c") {
138 return;
139 }
140
141 // GDB Detaching
142 if (command == "D"){
144 return;
145 }
146
147 // GDB requesting mem dump
148 if (command.starts_with("m")) {
149 send_memory(command);
150 return;
151 }
152
153 // GDB requesting insert breakpoint (software bp)
154 if (command.starts_with("Z0")) {
155 handle_set_breakpoint(command);
156 return;
157 }
158
159 // GDB requesting remove breakpoint (software bp)
160 if (command.starts_with("z0")) {
162 return;
163 }
164
165 // GDB requesting what features this server supports
166 if (command.starts_with("qSupported")){
167 send_supported(command);
168 return;
169 }
170
171 // GDB asking if already attached
172 if (command.starts_with("qAttached")){
173 send_message("1");
174 return;
175 }
176
177 // GDB asking for arch
178 if (command.starts_with("qXfer")) {
179 send_message("l<target version=\"1.0\"><architecture>i386:x86-64</architecture></target>");
180 return;
181 }
182
183 // Unknown command
184 send_empty();
185
186}
187
197string GDBServer::format_register(uint64_t value, int num_bytes) {
198
199 string out = "";
200 constexpr char* HEX_CHARS = "0123456789abcdef";
201
202
203 for (int i = 0; i < num_bytes; ++i) {
204
205 // Get as little endian
206 uint8_t byte = (value >> (i * 8)) & 0xFF;
207
208 // Reverse
209 out += String(HEX_CHARS[(byte >> 4) & 0x0F]);
210 out += String(HEX_CHARS[byte & 0x0F]);
211 }
212
213 return out;
214}
215
222string GDBServer::get_checksum(const string &payload) {
223
224 // Sum each character
225 uint8_t checksum = 0;
226 for (int i = 0; i < payload.length(); ++i)
227 checksum += (uint8_t)payload[i];
228
229 // Take first 8 bits
230 checksum &= 0xFF;
231
232 // Convert to hex
233 string hex = string((uint64_t)checksum);
234 if (hex.length() == 1)
235 hex = "0"s + hex;
236
237 return hex;
238
239}
240
248bool GDBServer::check_checksum(const string &payload, const string &checksum) {
249 return get_checksum(payload).to_lower() == checksum;
250}
251
258string GDBServer::build_message(const string &payload) {
259 return "+$"s + payload + "#" + get_checksum(payload);
260}
261
267void GDBServer::send_message(const string &payload) {
268 string message = build_message(payload.c_str());
269 m_serial->write(message);
270}
271
276 send_message("");
277}
278
284void GDBServer::send_supported(const string& peer_supported) {
285
286 string out = "PacketSize=4000;qXfer:features:read+;";
287
288 // Must support hardware breakpoints
289 Vector<string> features = peer_supported.split("+;");
290 for (const auto& feature : features)
291 if (feature == "hwbreak")
292 out += "hwbreak+;";
293
294 send_message(out);
295}
296
301
302 string payload = "";
303
304 // General Purpose Registers
305 payload += format_register(thread_regs.rax, 8);
306 payload += format_register(thread_regs.rbx, 8);
307 payload += format_register(thread_regs.rcx, 8);
308 payload += format_register(thread_regs.rdx, 8);
309 payload += format_register(thread_regs.rsi, 8);
310 payload += format_register(thread_regs.rdi, 8);
311 payload += format_register(thread_regs.rbp, 8);
312 payload += format_register(thread_regs.rsp, 8);
313
314 // Secondary General Purpose Registers
315 payload += format_register(thread_regs.r8, 8);
316 payload += format_register(thread_regs.r9, 8);
317 payload += format_register(thread_regs.r10, 8);
318 payload += format_register(thread_regs.r11, 8);
319 payload += format_register(thread_regs.r12, 8);
320 payload += format_register(thread_regs.r13, 8);
321 payload += format_register(thread_regs.r14, 8);
322 payload += format_register(thread_regs.r15, 8);
323
324 // Instruction Pointer
325 payload += format_register(thread_regs.rip, 8);
326
327 // Flags / Segments
328 payload += format_register(thread_regs.rflags, 4);
329 payload += format_register(thread_regs.cs, 4);
330 payload += format_register(thread_regs.ss, 4);
331
332 // Remaining
333 payload += "x"s * (8 * 4);
334
335 send_message(payload);
336}
337
343void GDBServer::send_memory(const string &message) {
344
345 // Parse
346 Vector<String> parts = message.substring(1,message.length( ) - 1).split(",");
347 string address_str = parts[0].to_upper();
348 string length_str = parts[1].to_upper();
349
350 send_memory((void*)address_str.hex_to_uint64(), length_str.hex_to_uint64());
351}
352
359void GDBServer::send_memory(void* address, size_t length) {
360
361 // Cant read
362 if (address < (void*)0x1000) {
363 send_message("E01");
364 return;
365 }
366
367 // Switch into the processes' address space
369 MemoryManager::switch_active_memory_manager(GlobalScheduler::get_process(m_attached_thread->parent_pid)->memory_manager);
370
371 // Send data
372 String payload(address, length);
373 send_message(payload.to_lower());
374
375 // Restore executing process's address space
376 MemoryManager::switch_active_memory_manager(old);
377
378}
379
384
385 // Acknowledge
386 m_serial->write_char('+');
387
388 // Enable trap flag for the thread and allow it to run
389 thread_regs.rflags |= (1 << 8);
390 m_attached_thread->thread_state = ThreadState::READY;
391
392 // Make sure change is mirrored if not in kernel
393 if (thread_regs.rip == m_attached_thread->execution_state.rip)
394 m_attached_thread->execution_state.rflags |= (1 << 8);
395
396 // wait for the step to occur and be caught by the exception handler
397 // ...
398}
399
405void GDBServer::handle_set_breakpoint(const string &message) {
406
407 // Parse
408 Vector<String> parts = message.substring(2,message.length( ) - 2).split(",");
409 string address_str = parts[1].to_upper();
410
411 handle_set_breakpoint((void*)address_str.hex_to_uint64());
412
413}
414
421
422 // Invaild address
423 if (address < (void*)0x1000) {
424 send_message("OK");
425 return;
426 }
427
428 // Already set or invalid address
429 if (m_breakpoints.find(address) != m_breakpoints.end()) {
430 send_message("OK");
431 return;
432 }
433
434 // Switch into the processes' address space
436 MemoryManager::switch_active_memory_manager(GlobalScheduler::get_process(m_attached_thread->parent_pid)->memory_manager);
437
438 // Store old byte
439 uint8_t old_byte = *(uint8_t*)address;
440 m_breakpoints.insert(address, old_byte);
441
442 // Replace with breakpoint (INT3)
443 CPU::disable_write_protect();
444 *(uint8_t*)address = 0xCC;
445 CPU::enable_write_protect();
446
447 // Restore executing process's address space
448 MemoryManager::switch_active_memory_manager(old);
449 send_message("OK");
450}
451
457void GDBServer::handle_remove_breakpoint(const string &message) {
458
459 // Parse
460 Vector<String> parts = message.substring(2,message.length( ) - 2).split(",");
461 string address_str = parts[1].to_upper();
462
463 handle_remove_breakpoint((void*)address_str.hex_to_uint64());
464
465}
466
473
474 // Invaild address
475 if (address < (void*)0x1000) {
476 send_message("OK");
477 return;
478 }
479
480 // Doesnt exist
481 auto breakpoint = m_breakpoints.find(address);
482 if (breakpoint == m_breakpoints.end()) {
483 send_message("OK");
484 return;
485 }
486
487 // Switch into the processes' address space
489 MemoryManager::switch_active_memory_manager(GlobalScheduler::get_process(m_attached_thread->parent_pid)->memory_manager);
490
491 // Replace with old byte
492 CPU::disable_write_protect();
493 *(uint8_t*)address = breakpoint->second;
494 CPU::enable_write_protect();
495
496 // Remove reference
497 m_breakpoints.erase(breakpoint);
498
499 // Restore executing process's address space
500 MemoryManager::switch_active_memory_manager(old);
501 send_message("OK");
502
503}
504
509
510 // Acknowledge
511 m_serial->write_char('+');
512
513 // Let the thread be scheduled
514 m_attached_thread->thread_state = ThreadState::READY;
515}
516
521
522 // Remove all breakpoints
523 for (const auto& bp : m_breakpoints)
524 handle_remove_breakpoint(bp.first);
525
526 // Allow program to continue rest of execution
527 m_attached_thread->thread_state = ThreadState::READY;
528
529}
530
537
538 return m_attached_thread;
539
540}
541
548: ExceptionHandler(0x1),
549 m_gdb_handler(server)
550{
551
552}
553
554TrapFlagExcpetion::~TrapFlagExcpetion() = default;
555
563
564 // Clear flag
565 status->rflags &= ~(1 << 8);
566 m_gdb_handler->thread_regs = *status;
567 m_gdb_handler->attached_thread()->thread_state = ThreadState::WAITING;
568
569 // Notify the server that it stopped
570 m_gdb_handler->send_message("S05");
571
572 // Dont execute further until instructed by GDB
574
575 // Copy the registers back as they may have been modified elsewhere
576 *status = m_gdb_handler->thread_regs;
577 return status;
578}
579
586: ExceptionHandler(0x3),
587 m_gdb_handler(server)
588{
589
590}
591
592BreakPointException::~BreakPointException() = default;
593
601
602 // Move back
603 status->rip -= 1;
604 m_gdb_handler->thread_regs = *status;
605 m_gdb_handler->attached_thread()->thread_state = ThreadState::WAITING;
606
607 // Notify the server that it stopped
608 m_gdb_handler->send_message("S05");
609
610 // Dont execute further until instructed by GDB
612
613 // Copy the registers back as they may have been modified elsewhere
614 *status = m_gdb_handler->thread_regs;
615 return status;
616}
static LogRecord DEBUG()
Gets the active logger for debug logs.
Definition logger.cpp:204
A driver for the serial output.
Definition serial.h:25
char read_char()
Read data from the serial port.
Definition serial.cpp:117
void write_char(char c) final
Waits for the serial port to be ready, then writes a character to it.
Definition serial.cpp:99
bool can_read()
Checks if data can be read from the serial port.
Definition serial.cpp:108
Base class for handing CPU expection interrupts.
Definition interrupts.h:61
Handles a certain interrupt number.
Definition interrupts.h:44
virtual void handle_interrupt()
Handles an interrupt.
Handles memory allocation and deallocation.
static Process * get_process(uint64_t pid)
Gets a process on the currently executing core by its PID.
static Process * current_process()
Gets the process on the currently executing core.
static Thread * current_thread()
Gets the thread on the currently executing core.
memory::MemoryManager * memory_manager
The manager for memory used by this process.
Definition process.h:121
The execution context of a sub-process thread.
Definition process.h:50
uint64_t parent_pid
The parent process ID.
Definition process.h:69
volatile system::cpu_status_t execution_state
The CPU state of the thread.
Definition process.h:71
void yield()
Pass execution to the next ready thread.
Definition process.cpp:103
volatile thread_state_t thread_state
The current state of the thread.
Definition process.h:72
BreakPointException(GDBServer *server)
Registers a handler for the BreakPointException (interrupt 0x1)
Definition gdbstub.cpp:585
Handler for running a GDB server over serial.
Definition gdbstub.h:71
processes::Thread * attached_thread()
Get the attached thread.
Definition gdbstub.cpp:536
GDBServer(console::SerialConsole *serial)
Constructs a handler for remote debugging using GDB, also sets up interrupt handlers for traps and br...
Definition gdbstub.cpp:28
void handle_step_instruction()
Set the trap flag and allow the thread to run.
Definition gdbstub.cpp:383
string build_message(const string &payload)
Converts a message into the form GDB expects, including and ACK package.
Definition gdbstub.cpp:258
void send_message(const string &payload)
Converts a message to the required form and sends it over serial.
Definition gdbstub.cpp:267
void handle_set_breakpoint(const string &message)
Set a software break point by replacing the instruction with a INT3.
Definition gdbstub.cpp:405
string get_checksum(const string &payload)
Calculates the GDB checksum of a command.
Definition gdbstub.cpp:222
bool check_checksum(const string &payload, const string &checksum)
Validates the checksum of a commnad.
Definition gdbstub.cpp:248
void send_supported(const string &peer_supported)
Sends the features that this GDB stub supports to the GDB server.
Definition gdbstub.cpp:284
void send_memory(const string &message)
Send a chunk of memory to the GDB server.
Definition gdbstub.cpp:343
void handle_remove_breakpoint(const string &message)
Remove a software break point, and replace the original opcode.
Definition gdbstub.cpp:457
void handle_continue()
Continues the exeuction of the thread, acknowledges the request and allows the thread to run.
Definition gdbstub.cpp:508
void send_empty()
Send an empty message to the GDB server.
Definition gdbstub.cpp:275
void send_registers()
Sends the registers of the thread to the GDB server.
Definition gdbstub.cpp:300
void attach(processes::Thread *thread)
Begin debugging a thread, will start paused.
Definition gdbstub.cpp:43
void handle_detach()
Detaches the thread from the GDB, removing all breakpoints and allowing it to be scheduled as usual.
Definition gdbstub.cpp:520
TrapFlagExcpetion(GDBServer *server)
Registers a handler for the TrapFlagException (interrupt 0x1)
Definition gdbstub.cpp:547
struct PACKED MaxOS::system::CPUStatus cpu_status_t
Alias for CPUStatus struct.
GDB Stub definition for remote debugging userspace processes.
Defines a GlobalScheduler and Scheduler for managing processes and threads.