⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/debug/
gdb_stub.rs

1//! GDB Remote Serial Protocol (RSP) Stub
2//!
3//! Implements the GDB remote serial protocol over COM2 (I/O port 0x2F8).
4//! Supports core RSP commands: register read/write, memory read/write,
5//! continue/step, breakpoints, and thread queries.
6//!
7//! Protocol: `$packet-data#checksum` framing with `+`/`-` acknowledgment.
8
9#[cfg(feature = "alloc")]
10use alloc::vec::Vec;
11use core::sync::atomic::{AtomicBool, Ordering};
12
13use spin::Mutex;
14
15use crate::sync::once_lock::OnceLock;
16
17// COM2 I/O port base
18const COM2_BASE: u16 = 0x2F8;
19const COM2_DATA: u16 = COM2_BASE;
20const COM2_IER: u16 = COM2_BASE + 1;
21const COM2_FCR: u16 = COM2_BASE + 2;
22const COM2_LCR: u16 = COM2_BASE + 3;
23const COM2_MCR: u16 = COM2_BASE + 4;
24const COM2_LSR: u16 = COM2_BASE + 5;
25const COM2_DLL: u16 = COM2_BASE;
26const COM2_DLH: u16 = COM2_BASE + 1;
27
28// LSR bits
29const LSR_DATA_READY: u8 = 0x01;
30const LSR_TX_EMPTY: u8 = 0x20;
31
32/// Maximum packet size (register dump + overhead)
33const MAX_PACKET_SIZE: usize = 4096;
34
35/// GDB is actively connected and should handle exceptions
36static GDB_ACTIVE: AtomicBool = AtomicBool::new(false);
37
38/// Saved register state from the last exception/breakpoint
39#[derive(Debug, Clone, Copy, Default)]
40#[repr(C)]
41pub(crate) struct GdbRegisters {
42    pub(crate) rax: u64,
43    pub(crate) rbx: u64,
44    pub(crate) rcx: u64,
45    pub(crate) rdx: u64,
46    pub(crate) rsi: u64,
47    pub(crate) rdi: u64,
48    pub(crate) rbp: u64,
49    pub(crate) rsp: u64,
50    pub(crate) r8: u64,
51    pub(crate) r9: u64,
52    pub(crate) r10: u64,
53    pub(crate) r11: u64,
54    pub(crate) r12: u64,
55    pub(crate) r13: u64,
56    pub(crate) r14: u64,
57    pub(crate) r15: u64,
58    pub(crate) rip: u64,
59    pub(crate) rflags: u64,
60    pub(crate) cs: u64,
61    pub(crate) ss: u64,
62    pub(crate) ds: u64,
63    pub(crate) es: u64,
64    pub(crate) fs: u64,
65    pub(crate) gs: u64,
66}
67
68impl GdbRegisters {
69    const NUM_REGS: usize = 24;
70
71    fn reg_by_index(&self, idx: usize) -> Option<u64> {
72        match idx {
73            0 => Some(self.rax),
74            1 => Some(self.rbx),
75            2 => Some(self.rcx),
76            3 => Some(self.rdx),
77            4 => Some(self.rsi),
78            5 => Some(self.rdi),
79            6 => Some(self.rbp),
80            7 => Some(self.rsp),
81            8 => Some(self.r8),
82            9 => Some(self.r9),
83            10 => Some(self.r10),
84            11 => Some(self.r11),
85            12 => Some(self.r12),
86            13 => Some(self.r13),
87            14 => Some(self.r14),
88            15 => Some(self.r15),
89            16 => Some(self.rip),
90            17 => Some(self.rflags),
91            18 => Some(self.cs),
92            19 => Some(self.ss),
93            20 => Some(self.ds),
94            21 => Some(self.es),
95            22 => Some(self.fs),
96            23 => Some(self.gs),
97            _ => None,
98        }
99    }
100
101    fn set_reg_by_index(&mut self, idx: usize, val: u64) -> bool {
102        match idx {
103            0 => self.rax = val,
104            1 => self.rbx = val,
105            2 => self.rcx = val,
106            3 => self.rdx = val,
107            4 => self.rsi = val,
108            5 => self.rdi = val,
109            6 => self.rbp = val,
110            7 => self.rsp = val,
111            8 => self.r8 = val,
112            9 => self.r9 = val,
113            10 => self.r10 = val,
114            11 => self.r11 = val,
115            12 => self.r12 = val,
116            13 => self.r13 = val,
117            14 => self.r14 = val,
118            15 => self.r15 = val,
119            16 => self.rip = val,
120            17 => self.rflags = val,
121            18 => self.cs = val,
122            19 => self.ss = val,
123            20 => self.ds = val,
124            21 => self.es = val,
125            22 => self.fs = val,
126            23 => self.gs = val,
127            _ => return false,
128        }
129        true
130    }
131}
132
133/// GDB stub state
134struct GdbState {
135    registers: GdbRegisters,
136    connected: bool,
137    no_ack_mode: bool,
138    /// Currently selected thread for register/memory operations
139    current_thread: u64,
140    /// Thread enumeration state for qsThreadInfo
141    thread_enum_index: usize,
142    /// Cached thread IDs for enumeration
143    thread_ids_cache: Vec<u64>,
144}
145
146impl GdbState {
147    fn new() -> Self {
148        Self {
149            registers: GdbRegisters::default(),
150            connected: false,
151            no_ack_mode: false,
152            current_thread: 1,
153            thread_enum_index: 0,
154            thread_ids_cache: Vec::new(),
155        }
156    }
157}
158
159/// Collect thread IDs from the kernel task registry
160#[cfg(feature = "alloc")]
161fn collect_thread_ids() -> Vec<u64> {
162    // Use process table to enumerate known PIDs
163    // Fall back to just thread 1 if registry is empty
164    let mut ids = Vec::new();
165    for pid in 1..=256u64 {
166        if crate::sched::scheduler::get_task_ptr(pid).is_some() {
167            ids.push(pid);
168        }
169    }
170    if ids.is_empty() {
171        alloc::vec![1] // fallback: report at least thread 1
172    } else {
173        ids
174    }
175}
176
177/// Check if a thread exists in the task registry
178fn thread_exists(tid: u64) -> bool {
179    crate::sched::scheduler::get_task_ptr(tid).is_some() || tid == 1
180}
181
182/// Format a thread ID as hex bytes
183#[cfg(feature = "alloc")]
184fn format_thread_id_hex(tid: u64) -> Vec<u8> {
185    if tid == 0 {
186        return alloc::vec![b'0'];
187    }
188    let mut result = Vec::new();
189    let val = tid;
190    let mut started = false;
191    for shift in (0..16).rev() {
192        let nibble = ((val >> (shift * 4)) & 0xF) as u8;
193        if nibble != 0 || started {
194            started = true;
195            result.push(if nibble < 10 {
196                b'0' + nibble
197            } else {
198                b'a' + nibble - 10
199            });
200        }
201    }
202    if result.is_empty() {
203        result.push(b'0');
204    }
205    result
206}
207
208/// Load registers from a thread's saved context
209#[cfg(all(feature = "alloc", target_arch = "x86_64"))]
210fn load_thread_registers(tid: u64) -> Option<GdbRegisters> {
211    let task_ptr = crate::sched::scheduler::get_task_ptr(tid)?;
212    // SAFETY: task_ptr is a valid NonNull returned by the scheduler; task lifetime
213    // outlives this read.
214    let task = unsafe { task_ptr.as_ref() };
215    match &task.context {
216        crate::sched::task::TaskContext::X86_64(ctx) => Some(GdbRegisters {
217            rax: ctx.rax,
218            rbx: ctx.rbx,
219            rcx: ctx.rcx,
220            rdx: ctx.rdx,
221            rsi: ctx.rsi,
222            rdi: ctx.rdi,
223            rbp: ctx.rbp,
224            rsp: ctx.rsp,
225            r8: ctx.r8,
226            r9: ctx.r9,
227            r10: ctx.r10,
228            r11: ctx.r11,
229            r12: ctx.r12,
230            r13: ctx.r13,
231            r14: ctx.r14,
232            r15: ctx.r15,
233            rip: ctx.rip,
234            rflags: ctx.rflags,
235            cs: ctx.cs as u64,
236            ss: ctx.ss as u64,
237            ds: ctx.ds as u64,
238            es: ctx.es as u64,
239            fs: ctx.fs as u64,
240            gs: ctx.gs as u64,
241        }),
242    }
243}
244
245#[cfg(all(feature = "alloc", not(target_arch = "x86_64")))]
246fn load_thread_registers(_tid: u64) -> Option<GdbRegisters> {
247    None // GDB stub is x86_64 only
248}
249
250static GDB_STATE: OnceLock<Mutex<GdbState>> = OnceLock::new();
251
252// ---------------------------------------------------------------------------
253// COM2 low-level I/O (x86_64 only)
254// ---------------------------------------------------------------------------
255
256#[cfg(target_os = "none")]
257fn com2_init() {
258    // SAFETY: Programming COM2 UART registers via x86 port I/O during
259    // initialization.
260    unsafe {
261        // Disable interrupts
262        outb(COM2_IER, 0x00);
263        // Enable DLAB for baud rate
264        outb(COM2_LCR, 0x80);
265        // 115200 baud (divisor = 1)
266        outb(COM2_DLL, 0x01);
267        outb(COM2_DLH, 0x00);
268        // 8N1, disable DLAB
269        outb(COM2_LCR, 0x03);
270        // Enable FIFO, clear, 14-byte threshold
271        outb(COM2_FCR, 0xC7);
272        // RTS/DSR set, IRQs enabled
273        outb(COM2_MCR, 0x0B);
274    }
275}
276
277#[cfg(not(target_os = "none"))]
278fn com2_init() {}
279
280#[cfg(target_os = "none")]
281unsafe fn outb(port: u16, val: u8) {
282    core::arch::asm!("out dx, al", in("dx") port, in("al") val, options(nostack, preserves_flags));
283}
284
285#[cfg(target_os = "none")]
286unsafe fn inb(port: u16) -> u8 {
287    let val: u8;
288    core::arch::asm!("in al, dx", out("al") val, in("dx") port, options(nostack, preserves_flags));
289    val
290}
291
292#[cfg(target_os = "none")]
293fn com2_read_byte() -> u8 {
294    // SAFETY: Reading COM2 LSR and data registers via x86 port I/O.
295    unsafe {
296        // Wait for data ready
297        while (inb(COM2_LSR) & LSR_DATA_READY) == 0 {
298            core::hint::spin_loop();
299        }
300        inb(COM2_DATA)
301    }
302}
303
304#[cfg(target_os = "none")]
305fn com2_write_byte(byte: u8) {
306    // SAFETY: Reading COM2 LSR and writing data register via x86 port I/O.
307    unsafe {
308        // Wait for TX empty
309        while (inb(COM2_LSR) & LSR_TX_EMPTY) == 0 {
310            core::hint::spin_loop();
311        }
312        outb(COM2_DATA, byte);
313    }
314}
315
316#[cfg(target_os = "none")]
317fn _com2_data_available() -> bool {
318    // SAFETY: Reading COM2 line status register via x86 port I/O.
319    unsafe { (inb(COM2_LSR) & LSR_DATA_READY) != 0 }
320}
321
322#[cfg(not(target_os = "none"))]
323fn com2_read_byte() -> u8 {
324    0
325}
326
327#[cfg(not(target_os = "none"))]
328fn com2_write_byte(_byte: u8) {}
329
330// ---------------------------------------------------------------------------
331// RSP packet framing
332// ---------------------------------------------------------------------------
333
334fn compute_checksum(data: &[u8]) -> u8 {
335    let mut sum: u8 = 0;
336    for &b in data {
337        sum = sum.wrapping_add(b);
338    }
339    sum
340}
341
342fn hex_char(nibble: u8) -> u8 {
343    let n = nibble & 0x0F;
344    if n < 10 {
345        b'0' + n
346    } else {
347        b'a' + (n - 10)
348    }
349}
350
351pub(crate) fn hex_digit(c: u8) -> Option<u8> {
352    match c {
353        b'0'..=b'9' => Some(c - b'0'),
354        b'a'..=b'f' => Some(c - b'a' + 10),
355        b'A'..=b'F' => Some(c - b'A' + 10),
356        _ => None,
357    }
358}
359
360fn hex_to_u8(hi: u8, lo: u8) -> Option<u8> {
361    let h = hex_digit(hi)?;
362    let l = hex_digit(lo)?;
363    Some((h << 4) | l)
364}
365
366/// Parse a hex string into a u64
367pub(crate) fn parse_hex_u64(s: &[u8]) -> Option<u64> {
368    if s.is_empty() {
369        return None;
370    }
371    let mut val: u64 = 0;
372    for &c in s {
373        let d = hex_digit(c)?;
374        val = val.checked_shl(4)?.wrapping_add(d as u64);
375    }
376    Some(val)
377}
378
379/// Receive a single RSP packet (blocking).
380/// Returns the packet data (between `$` and `#`), or None on error.
381fn receive_packet() -> Option<Vec<u8>> {
382    // Wait for '$'
383    loop {
384        let c = com2_read_byte();
385        if c == b'$' {
386            break;
387        }
388        if c == 0x03 {
389            // Ctrl-C: interrupt
390            return Some(alloc::vec![0x03]);
391        }
392    }
393
394    let mut buf = Vec::with_capacity(MAX_PACKET_SIZE);
395
396    // Read until '#'
397    loop {
398        let c = com2_read_byte();
399        if c == b'#' {
400            break;
401        }
402        if buf.len() >= MAX_PACKET_SIZE {
403            return None;
404        }
405        buf.push(c);
406    }
407
408    // Read 2-char hex checksum
409    let hi = com2_read_byte();
410    let lo = com2_read_byte();
411    let checksum_received = hex_to_u8(hi, lo).unwrap_or(0);
412
413    let computed = compute_checksum(&buf);
414    if computed == checksum_received {
415        // ACK
416        com2_write_byte(b'+');
417        Some(buf)
418    } else {
419        // NAK
420        com2_write_byte(b'-');
421        None
422    }
423}
424
425/// Send an RSP packet
426fn send_packet(data: &[u8]) {
427    com2_write_byte(b'$');
428    for &b in data {
429        com2_write_byte(b);
430    }
431    com2_write_byte(b'#');
432    let cksum = compute_checksum(data);
433    com2_write_byte(hex_char(cksum >> 4));
434    com2_write_byte(hex_char(cksum & 0x0F));
435}
436
437/// Send an OK response
438fn send_ok() {
439    send_packet(b"OK");
440}
441
442/// Send an error response
443fn send_error(code: u8) {
444    let mut buf = [b'E', 0, 0];
445    buf[1] = hex_char(code >> 4);
446    buf[2] = hex_char(code & 0x0F);
447    send_packet(&buf);
448}
449
450/// Send an empty response (unsupported command)
451fn send_empty() {
452    send_packet(b"");
453}
454
455// ---------------------------------------------------------------------------
456// Hex encoding helpers
457// ---------------------------------------------------------------------------
458
459fn encode_hex_u64(val: u64, buf: &mut Vec<u8>) {
460    // GDB expects little-endian byte order for register values
461    for i in 0..8 {
462        let byte = ((val >> (i * 8)) & 0xFF) as u8;
463        buf.push(hex_char(byte >> 4));
464        buf.push(hex_char(byte & 0x0F));
465    }
466}
467
468fn decode_hex_u64_le(data: &[u8]) -> Option<u64> {
469    if data.len() < 16 {
470        return None;
471    }
472    let mut val: u64 = 0;
473    for i in 0..8 {
474        let byte = hex_to_u8(data[i * 2], data[i * 2 + 1])?;
475        val |= (byte as u64) << (i * 8);
476    }
477    Some(val)
478}
479
480// ---------------------------------------------------------------------------
481// Command handlers
482// ---------------------------------------------------------------------------
483
484/// Handle 'g' command: read all registers
485fn handle_read_registers(state: &GdbState) -> Vec<u8> {
486    let mut buf = Vec::with_capacity(GdbRegisters::NUM_REGS * 16);
487    for i in 0..GdbRegisters::NUM_REGS {
488        if let Some(val) = state.registers.reg_by_index(i) {
489            encode_hex_u64(val, &mut buf);
490        }
491    }
492    buf
493}
494
495/// Handle 'G' command: write all registers
496fn handle_write_registers(state: &mut GdbState, data: &[u8]) -> bool {
497    if data.len() < GdbRegisters::NUM_REGS * 16 {
498        return false;
499    }
500    for i in 0..GdbRegisters::NUM_REGS {
501        let offset = i * 16;
502        if let Some(val) = decode_hex_u64_le(&data[offset..offset + 16]) {
503            state.registers.set_reg_by_index(i, val);
504        }
505    }
506    true
507}
508
509/// Handle 'p' command: read single register
510fn handle_read_single_reg(state: &GdbState, data: &[u8]) -> Option<Vec<u8>> {
511    let reg_num = parse_hex_u64(data)? as usize;
512    let val = state.registers.reg_by_index(reg_num)?;
513    let mut buf = Vec::with_capacity(16);
514    encode_hex_u64(val, &mut buf);
515    Some(buf)
516}
517
518/// Handle 'P' command: write single register
519fn handle_write_single_reg(state: &mut GdbState, data: &[u8]) -> bool {
520    // Format: Pnn=rrrrrrrrrrrrrrrrr
521    let eq_pos = data.iter().position(|&c| c == b'=');
522    let eq_pos = match eq_pos {
523        Some(p) => p,
524        None => return false,
525    };
526
527    let reg_num = match parse_hex_u64(&data[..eq_pos]) {
528        Some(n) => n as usize,
529        None => return false,
530    };
531
532    let val = match decode_hex_u64_le(&data[eq_pos + 1..]) {
533        Some(v) => v,
534        None => return false,
535    };
536
537    state.registers.set_reg_by_index(reg_num, val)
538}
539
540/// Handle 'm' command: read memory
541fn handle_read_memory(data: &[u8]) -> Option<Vec<u8>> {
542    // Format: maddr,length
543    let comma = data.iter().position(|&c| c == b',')?;
544    let addr = parse_hex_u64(&data[..comma])?;
545    let len = parse_hex_u64(&data[comma + 1..])? as usize;
546
547    if len > MAX_PACKET_SIZE / 2 {
548        return None;
549    }
550
551    let mut buf = Vec::with_capacity(len * 2);
552
553    for i in 0..len {
554        let ptr = (addr + i as u64) as *const u8;
555        // SAFETY: We read from the address GDB requested. If the address is
556        // invalid, we may fault -- the page fault handler should catch this
557        // in a production stub. For now, we do a best-effort read.
558        let byte = unsafe { core::ptr::read_volatile(ptr) };
559        buf.push(hex_char(byte >> 4));
560        buf.push(hex_char(byte & 0x0F));
561    }
562
563    Some(buf)
564}
565
566/// Handle 'M' command: write memory
567fn handle_write_memory(data: &[u8]) -> bool {
568    // Format: Maddr,length:XX...
569    let comma = match data.iter().position(|&c| c == b',') {
570        Some(p) => p,
571        None => return false,
572    };
573    let colon = match data.iter().position(|&c| c == b':') {
574        Some(p) => p,
575        None => return false,
576    };
577
578    let addr = match parse_hex_u64(&data[..comma]) {
579        Some(a) => a,
580        None => return false,
581    };
582    let len = match parse_hex_u64(&data[comma + 1..colon]) {
583        Some(l) => l as usize,
584        None => return false,
585    };
586
587    let hex_data = &data[colon + 1..];
588    if hex_data.len() < len * 2 {
589        return false;
590    }
591
592    for i in 0..len {
593        let byte = match hex_to_u8(hex_data[i * 2], hex_data[i * 2 + 1]) {
594            Some(b) => b,
595            None => return false,
596        };
597        let ptr = (addr + i as u64) as *mut u8;
598        // SAFETY: Writing to the address GDB requested. Same caveats as read.
599        unsafe {
600            core::ptr::write_volatile(ptr, byte);
601        }
602    }
603
604    true
605}
606
607/// Handle '?' command: halt reason
608fn handle_halt_reason() -> Vec<u8> {
609    // Signal 5 = SIGTRAP (breakpoint)
610    alloc::vec![b'S', b'0', b'5']
611}
612
613/// Handle 'q' queries
614fn handle_query(_state: &mut GdbState, data: &[u8]) -> Option<Vec<u8>> {
615    if data.starts_with(b"Supported") {
616        return Some(
617            b"PacketSize=1000;QStartNoAckMode+;qXfer:features:read+;multiprocess-".to_vec(),
618        );
619    }
620    if data.starts_with(b"Attached") {
621        return Some(b"1".to_vec());
622    }
623    if data == b"fThreadInfo" {
624        // Enumerate all threads from task registry
625        _state.thread_ids_cache = collect_thread_ids();
626        _state.thread_enum_index = 0;
627        if _state.thread_ids_cache.is_empty() {
628            return Some(b"l".to_vec());
629        }
630        let mut response = alloc::vec![b'm'];
631        for (i, &tid) in _state.thread_ids_cache.iter().enumerate() {
632            if i > 0 {
633                response.push(b',');
634            }
635            response.extend_from_slice(&format_thread_id_hex(tid));
636        }
637        _state.thread_enum_index = _state.thread_ids_cache.len();
638        return Some(response);
639    }
640    if data == b"sThreadInfo" {
641        // All threads reported in fThreadInfo
642        return Some(b"l".to_vec());
643    }
644    if data == b"C" {
645        // Current thread ID
646        let mut resp = b"QC".to_vec();
647        resp.extend_from_slice(&format_thread_id_hex(_state.current_thread));
648        return Some(resp);
649    }
650    if data.starts_with(b"Xfer:features:read:target.xml:") {
651        let xml = b"l<?xml version=\"1.0\"?>\
652            <!DOCTYPE target SYSTEM \"gdb-target.dtd\">\
653            <target version=\"1.0\">\
654            <architecture>i386:x86-64</architecture>\
655            </target>";
656        return Some(xml.to_vec());
657    }
658
659    // Check for QStartNoAckMode
660    if data.starts_with(b"StartNoAckMode") {
661        // This is a 'Q' command, not 'q', handled separately
662    }
663
664    None
665}
666
667/// Handle 'Q' set commands
668fn handle_set_command(state: &mut GdbState, data: &[u8]) -> Option<Vec<u8>> {
669    if data.starts_with(b"StartNoAckMode") {
670        state.no_ack_mode = true;
671        return Some(b"OK".to_vec());
672    }
673    None
674}
675
676/// Handle 'H' command: set thread for subsequent operations
677fn handle_set_thread(state: &mut GdbState, data: &[u8]) -> Vec<u8> {
678    if data.is_empty() {
679        return b"E01".to_vec();
680    }
681    let _op = data[0]; // 'g' for register ops, 'c' for continue ops
682    let tid_str = &data[1..];
683    if tid_str.is_empty() {
684        return b"E01".to_vec();
685    }
686
687    // Parse thread ID (hex)
688    let tid = if tid_str == b"-1" {
689        // -1 means "all threads"
690        0xFFFF_FFFF_FFFF_FFFF
691    } else {
692        let mut val = 0u64;
693        for &b in tid_str {
694            let nibble = match b {
695                b'0'..=b'9' => b - b'0',
696                b'a'..=b'f' => b - b'a' + 10,
697                b'A'..=b'F' => b - b'A' + 10,
698                _ => return b"E01".to_vec(),
699            };
700            val = val.wrapping_shl(4) | nibble as u64;
701        }
702        val
703    };
704
705    // 0 means "any thread", -1 means "all threads" — both accepted
706    if tid == 0 || tid == 0xFFFF_FFFF_FFFF_FFFF {
707        // Keep current thread unchanged
708        return b"OK".to_vec();
709    }
710
711    // Validate thread exists
712    if thread_exists(tid) {
713        state.current_thread = tid;
714        b"OK".to_vec()
715    } else {
716        b"E01".to_vec()
717    }
718}
719
720/// Handle 'T' command: is thread alive?
721fn handle_thread_alive(data: &[u8]) -> Vec<u8> {
722    if data.is_empty() {
723        return b"OK".to_vec();
724    }
725    let mut tid = 0u64;
726    for &b in data {
727        let nibble = match b {
728            b'0'..=b'9' => b - b'0',
729            b'a'..=b'f' => b - b'a' + 10,
730            b'A'..=b'F' => b - b'A' + 10,
731            _ => return b"E01".to_vec(),
732        };
733        tid = tid.wrapping_shl(4) | nibble as u64;
734    }
735    if thread_exists(tid) {
736        b"OK".to_vec()
737    } else {
738        b"E01".to_vec()
739    }
740}
741
742/// Handle 'vAttach;pid': attach to a process
743#[cfg(feature = "alloc")]
744fn handle_vattach(state: &mut GdbState, data: &[u8]) -> Vec<u8> {
745    // Parse PID from hex after "Attach;"
746    if !data.starts_with(b"Attach;") {
747        return b"E01".to_vec();
748    }
749    let pid_hex = &data[7..];
750    let mut pid = 0u64;
751    for &b in pid_hex {
752        let nibble = match b {
753            b'0'..=b'9' => b - b'0',
754            b'a'..=b'f' => b - b'a' + 10,
755            b'A'..=b'F' => b - b'A' + 10,
756            _ => return b"E01".to_vec(),
757        };
758        pid = pid.wrapping_shl(4) | nibble as u64;
759    }
760    if thread_exists(pid) {
761        state.current_thread = pid;
762        b"S05".to_vec() // SIGTRAP stop reply
763    } else {
764        b"E01".to_vec()
765    }
766}
767
768/// Handle 'vKill;pid': kill a process
769#[cfg(feature = "alloc")]
770fn handle_vkill(data: &[u8]) -> Vec<u8> {
771    if !data.starts_with(b"Kill;") {
772        return b"E01".to_vec();
773    }
774    let pid_hex = &data[5..];
775    let mut pid = 0u64;
776    for &b in pid_hex {
777        let nibble = match b {
778            b'0'..=b'9' => b - b'0',
779            b'a'..=b'f' => b - b'a' + 10,
780            b'A'..=b'F' => b - b'A' + 10,
781            _ => return b"E01".to_vec(),
782        };
783        pid = pid.wrapping_shl(4) | nibble as u64;
784    }
785    // Signal the process to terminate
786    let process_id = crate::process::pcb::ProcessId(pid);
787    if crate::process::exit::kill_process(process_id, 9).is_ok() {
788        b"OK".to_vec()
789    } else {
790        b"E01".to_vec()
791    }
792}
793
794// ---------------------------------------------------------------------------
795// Public API
796// ---------------------------------------------------------------------------
797
798/// Initialize the GDB stub on COM2
799pub(crate) fn gdb_init() {
800    com2_init();
801
802    let state = GdbState::new();
803    let _ = GDB_STATE.set(Mutex::new(state));
804
805    #[cfg(target_os = "none")]
806    crate::serial_println!("[GDB] Stub initialized on COM2 (0x2F8), waiting for connection...");
807}
808
809/// Check if GDB is active
810pub(crate) fn is_gdb_active() -> bool {
811    GDB_ACTIVE.load(Ordering::Relaxed)
812}
813
814/// Handle an exception/trap by entering GDB command loop.
815/// `signal` is the Unix signal number (e.g., 5 for SIGTRAP).
816/// `regs` provides the saved register context.
817pub(crate) fn gdb_handle_exception(signal: u8, regs: &mut GdbRegisters) {
818    let state_lock = match GDB_STATE.get() {
819        Some(s) => s,
820        None => return,
821    };
822
823    {
824        let mut state = state_lock.lock();
825        state.registers = *regs;
826        state.connected = true;
827    }
828    GDB_ACTIVE.store(true, Ordering::Release);
829
830    // Send stop reply
831    let sig_reply = [b'S', hex_char(signal >> 4), hex_char(signal & 0x0F)];
832    send_packet(&sig_reply);
833
834    // Command loop
835    loop {
836        let packet = match receive_packet() {
837            Some(p) => p,
838            None => continue,
839        };
840
841        if packet.is_empty() {
842            send_empty();
843            continue;
844        }
845
846        let cmd = packet[0];
847        let args = &packet[1..];
848
849        match cmd {
850            // Ctrl-C interrupt
851            0x03 => {
852                let reply = handle_halt_reason();
853                send_packet(&reply);
854            }
855            // '?' - halt reason
856            b'?' => {
857                let reply = handle_halt_reason();
858                send_packet(&reply);
859            }
860            // 'g' - read registers
861            b'g' => {
862                let state = state_lock.lock();
863                let reply = handle_read_registers(&state);
864                send_packet(&reply);
865            }
866            // 'G' - write registers
867            b'G' => {
868                let mut state = state_lock.lock();
869                if handle_write_registers(&mut state, args) {
870                    send_ok();
871                } else {
872                    send_error(0x01);
873                }
874            }
875            // 'p' - read single register
876            b'p' => {
877                let state = state_lock.lock();
878                match handle_read_single_reg(&state, args) {
879                    Some(reply) => send_packet(&reply),
880                    None => send_error(0x01),
881                }
882            }
883            // 'P' - write single register
884            b'P' => {
885                let mut state = state_lock.lock();
886                if handle_write_single_reg(&mut state, args) {
887                    send_ok();
888                } else {
889                    send_error(0x01);
890                }
891            }
892            // 'm' - read memory
893            b'm' => match handle_read_memory(args) {
894                Some(reply) => send_packet(&reply),
895                None => send_error(0x01),
896            },
897            // 'M' - write memory
898            b'M' => {
899                if handle_write_memory(args) {
900                    send_ok();
901                } else {
902                    send_error(0x01);
903                }
904            }
905            // 'c' - continue execution
906            b'c' => {
907                // Optional address argument
908                if !args.is_empty() {
909                    if let Some(addr) = parse_hex_u64(args) {
910                        let mut state = state_lock.lock();
911                        state.registers.rip = addr;
912                    }
913                }
914                // Update regs and return to execution
915                let state = state_lock.lock();
916                *regs = state.registers;
917                return;
918            }
919            // 's' - single step
920            b's' => {
921                // Set TF (trap flag) in RFLAGS for single-step
922                let mut state = state_lock.lock();
923                if !args.is_empty() {
924                    if let Some(addr) = parse_hex_u64(args) {
925                        state.registers.rip = addr;
926                    }
927                }
928                state.registers.rflags |= 0x100; // TF bit
929                *regs = state.registers;
930                return;
931            }
932            // 'D' - detach
933            b'D' => {
934                send_ok();
935                GDB_ACTIVE.store(false, Ordering::Release);
936                let mut state = state_lock.lock();
937                state.connected = false;
938                *regs = state.registers;
939                return;
940            }
941            // 'k' - kill
942            b'k' => {
943                GDB_ACTIVE.store(false, Ordering::Release);
944                let mut state = state_lock.lock();
945                state.connected = false;
946                *regs = state.registers;
947                return;
948            }
949            // 'H' - set thread
950            b'H' => {
951                let mut state = state_lock.lock();
952                let reply = handle_set_thread(&mut state, args);
953                send_packet(&reply);
954            }
955            // 'T' - thread alive
956            b'T' => {
957                let reply = handle_thread_alive(args);
958                send_packet(&reply);
959            }
960            // 'v' - extended/verbose commands
961            b'v' => {
962                if args.starts_with(b"Cont?") {
963                    send_packet(b"vCont;c;s;t");
964                } else if args.starts_with(b"Cont;c") {
965                    let state = state_lock.lock();
966                    *regs = state.registers;
967                    return;
968                } else if args.starts_with(b"Cont;s") {
969                    let mut state = state_lock.lock();
970                    state.registers.rflags |= 0x100;
971                    *regs = state.registers;
972                    return;
973                } else if args.starts_with(b"Kill;") {
974                    let reply = handle_vkill(args);
975                    send_packet(&reply);
976                    GDB_ACTIVE.store(false, Ordering::Release);
977                    let mut state = state_lock.lock();
978                    state.connected = false;
979                    *regs = state.registers;
980                    return;
981                } else if args.starts_with(b"Attach;") {
982                    let mut state = state_lock.lock();
983                    let reply = handle_vattach(&mut state, args);
984                    send_packet(&reply);
985                } else {
986                    send_empty();
987                }
988            }
989            // 'q' - general query
990            b'q' => {
991                let mut state = state_lock.lock();
992                match handle_query(&mut state, args) {
993                    Some(reply) => send_packet(&reply),
994                    None => send_empty(),
995                }
996            }
997            // 'Q' - general set
998            b'Q' => {
999                let mut state = state_lock.lock();
1000                match handle_set_command(&mut state, args) {
1001                    Some(reply) => send_packet(&reply),
1002                    None => send_empty(),
1003                }
1004            }
1005            // 'Z' - insert breakpoint/watchpoint
1006            b'Z' => {
1007                if let Some(reply) = crate::debug::breakpoint::handle_insert(args) {
1008                    send_packet(&reply);
1009                } else {
1010                    send_empty();
1011                }
1012            }
1013            // 'z' - remove breakpoint/watchpoint
1014            b'z' => {
1015                if let Some(reply) = crate::debug::breakpoint::handle_remove(args) {
1016                    send_packet(&reply);
1017                } else {
1018                    send_empty();
1019                }
1020            }
1021            _ => {
1022                send_empty();
1023            }
1024        }
1025    }
1026}
1027
1028// ---------------------------------------------------------------------------
1029// Tests
1030// ---------------------------------------------------------------------------
1031
1032#[cfg(test)]
1033mod tests {
1034    #[allow(unused_imports)]
1035    use alloc::vec;
1036
1037    use super::*;
1038
1039    #[test]
1040    fn test_compute_checksum() {
1041        assert_eq!(compute_checksum(b"OK"), b'O'.wrapping_add(b'K'));
1042        assert_eq!(compute_checksum(b""), 0);
1043        assert_eq!(
1044            compute_checksum(b"S05"),
1045            b'S'.wrapping_add(b'0').wrapping_add(b'5')
1046        );
1047    }
1048
1049    #[test]
1050    fn test_hex_char() {
1051        assert_eq!(hex_char(0), b'0');
1052        assert_eq!(hex_char(9), b'9');
1053        assert_eq!(hex_char(10), b'a');
1054        assert_eq!(hex_char(15), b'f');
1055    }
1056
1057    #[test]
1058    fn test_hex_digit() {
1059        assert_eq!(hex_digit(b'0'), Some(0));
1060        assert_eq!(hex_digit(b'9'), Some(9));
1061        assert_eq!(hex_digit(b'a'), Some(10));
1062        assert_eq!(hex_digit(b'f'), Some(15));
1063        assert_eq!(hex_digit(b'A'), Some(10));
1064        assert_eq!(hex_digit(b'F'), Some(15));
1065        assert_eq!(hex_digit(b'g'), None);
1066    }
1067
1068    #[test]
1069    fn test_hex_to_u8() {
1070        assert_eq!(hex_to_u8(b'0', b'0'), Some(0x00));
1071        assert_eq!(hex_to_u8(b'f', b'f'), Some(0xFF));
1072        assert_eq!(hex_to_u8(b'4', b'2'), Some(0x42));
1073        assert_eq!(hex_to_u8(b'g', b'0'), None);
1074    }
1075
1076    #[test]
1077    fn test_parse_hex_u64() {
1078        assert_eq!(parse_hex_u64(b"0"), Some(0));
1079        assert_eq!(parse_hex_u64(b"ff"), Some(0xFF));
1080        assert_eq!(parse_hex_u64(b"deadbeef"), Some(0xDEADBEEF));
1081        assert_eq!(parse_hex_u64(b""), None);
1082    }
1083
1084    #[test]
1085    fn test_encode_decode_u64_le() {
1086        let val: u64 = 0x0102030405060708;
1087        let mut buf = Vec::new();
1088        encode_hex_u64(val, &mut buf);
1089        let decoded = decode_hex_u64_le(&buf);
1090        assert_eq!(decoded, Some(val));
1091    }
1092
1093    #[test]
1094    fn test_registers_default() {
1095        let regs = GdbRegisters::default();
1096        assert_eq!(regs.rax, 0);
1097        assert_eq!(regs.rip, 0);
1098        assert_eq!(regs.rflags, 0);
1099    }
1100
1101    #[test]
1102    fn test_register_read_write() {
1103        let mut regs = GdbRegisters::default();
1104        assert!(regs.set_reg_by_index(0, 0x42));
1105        assert_eq!(regs.reg_by_index(0), Some(0x42));
1106        assert_eq!(regs.rax, 0x42);
1107
1108        assert!(regs.set_reg_by_index(16, 0xDEAD));
1109        assert_eq!(regs.rip, 0xDEAD);
1110
1111        assert!(!regs.set_reg_by_index(99, 0));
1112        assert_eq!(regs.reg_by_index(99), None);
1113    }
1114
1115    #[test]
1116    fn test_handle_read_registers() {
1117        let state = GdbState::new();
1118        let reply = handle_read_registers(&state);
1119        assert_eq!(reply.len(), GdbRegisters::NUM_REGS * 16);
1120        // All zeros
1121        assert!(reply.iter().all(|&b| b == b'0'));
1122    }
1123
1124    #[test]
1125    fn test_handle_write_registers() {
1126        let mut state = GdbState::new();
1127        // Write register 0 (rax) = 0x0102030405060708 in LE hex
1128        let mut data = Vec::new();
1129        for _ in 0..GdbRegisters::NUM_REGS {
1130            encode_hex_u64(0x42, &mut data);
1131        }
1132        assert!(handle_write_registers(&mut state, &data));
1133        assert_eq!(state.registers.rax, 0x42);
1134        assert_eq!(state.registers.rip, 0x42);
1135    }
1136
1137    #[test]
1138    fn test_handle_single_reg() {
1139        let mut state = GdbState::new();
1140        state.registers.rax = 0xCAFE;
1141
1142        let reply = handle_read_single_reg(&state, b"0").unwrap();
1143        assert_eq!(reply.len(), 16);
1144
1145        // Write P0=4200000000000000
1146        let mut cmd = Vec::new();
1147        cmd.extend_from_slice(b"0=");
1148        encode_hex_u64(0x1234, &mut cmd);
1149        assert!(handle_write_single_reg(&mut state, &cmd));
1150        assert_eq!(state.registers.rax, 0x1234);
1151    }
1152
1153    #[test]
1154    fn test_halt_reason() {
1155        let reply = handle_halt_reason();
1156        assert_eq!(&reply, b"S05");
1157    }
1158
1159    #[test]
1160    fn test_handle_set_thread_any() {
1161        let mut state = GdbState::new();
1162        let reply = handle_set_thread(&mut state, b"g0");
1163        assert_eq!(&reply, b"OK");
1164    }
1165
1166    #[test]
1167    fn test_handle_set_thread_all() {
1168        let mut state = GdbState::new();
1169        let reply = handle_set_thread(&mut state, b"g-1");
1170        assert_eq!(&reply, b"OK");
1171    }
1172
1173    #[test]
1174    fn test_handle_set_thread_specific() {
1175        let mut state = GdbState::new();
1176        // Thread 1 always exists as fallback
1177        let reply = handle_set_thread(&mut state, b"g1");
1178        assert_eq!(&reply, b"OK");
1179        assert_eq!(state.current_thread, 1);
1180    }
1181
1182    #[test]
1183    fn test_handle_set_thread_empty_error() {
1184        let mut state = GdbState::new();
1185        let reply = handle_set_thread(&mut state, b"");
1186        assert_eq!(&reply, b"E01");
1187    }
1188
1189    #[test]
1190    fn test_handle_set_thread_continue_op() {
1191        let mut state = GdbState::new();
1192        let reply = handle_set_thread(&mut state, b"c1");
1193        assert_eq!(&reply, b"OK");
1194    }
1195
1196    #[test]
1197    fn test_format_thread_id_hex() {
1198        assert_eq!(format_thread_id_hex(0), vec![b'0']);
1199        assert_eq!(format_thread_id_hex(1), vec![b'1']);
1200        assert_eq!(format_thread_id_hex(0xFF), vec![b'f', b'f']);
1201        assert_eq!(format_thread_id_hex(0x1234), vec![b'1', b'2', b'3', b'4']);
1202    }
1203
1204    #[test]
1205    fn test_gdb_state_default_thread() {
1206        let state = GdbState::new();
1207        assert_eq!(state.current_thread, 1);
1208    }
1209
1210    #[test]
1211    fn test_collect_thread_ids_fallback() {
1212        // With empty registry, should return [1]
1213        let ids = collect_thread_ids();
1214        assert!(!ids.is_empty());
1215    }
1216}