Skip to content

Commit 20d3cf9

Browse files
committed
Enable GDB debugging with event-driven coroutine
Add full GDB remote debugging support using mini-gdbstub with event-driven I/O (kqueue/epoll) to enable non-blocking debugging without interfering with the existing WFI coroutine system. This provides breakpoints, single-step, register/memory inspection, and multi-hart debugging for SMP configurations. Key components: - gdbctrl.c/h: GDB control layer for breakpoint and hart suspension management - coro.c/h: Debug-specific suspend/resume functions for SMP debugging - main.c: Integrate GDB callbacks with hart execution loop and single-step logic - riscv.h: Debug state structures (hart_state_t, hart_debug_info_t, vm_debug_ctx_t) - scripts/test-gdb.sh: Automated test suite validating GDB functionality Architecture: - Single-hart mode (n_hart=1): Direct execution with debug state tracking - SMP mode (n_hart>1): Coroutine-based with per-hart debug suspend/resume - Event-driven GDB I/O prevents blocking during debug operations - Preserves existing WFI coroutine semantics unchanged
1 parent 51e1115 commit 20d3cf9

File tree

9 files changed

+988
-6
lines changed

9 files changed

+988
-6
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ OBJS := \
170170
main.o \
171171
aclint.o \
172172
coro.o \
173+
gdbctrl.o \
173174
$(OBJS_EXTRA)
174175

175176
deps := $(OBJS:%.o=.%.o.d)

coro.c

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
/* Lightweight coroutine for multi-hart execution */
22

3-
#include "coro.h"
43
#include <stdio.h>
54
#include <stdlib.h>
65
#include <string.h>
76

7+
#include "coro.h"
8+
89
/* Platform detection */
910

1011
#if !defined(CORO_USE_UCONTEXT) && !defined(CORO_USE_ASM)
@@ -22,7 +23,8 @@
2223
/* Coroutine state */
2324

2425
typedef enum {
25-
CORO_STATE_SUSPENDED,
26+
CORO_STATE_SUSPENDED, /* WFI suspension */
27+
CORO_STATE_DEBUG_SUSPENDED, /* Debug breakpoint suspension */
2628
CORO_STATE_RUNNING,
2729
CORO_STATE_DEAD
2830
} coro_state_t;
@@ -613,3 +615,69 @@ uint32_t coro_current_hart_id(void)
613615

614616
return coro_state.current_hart;
615617
}
618+
619+
/* Debug-related coroutine functions for GDB integration */
620+
621+
void coro_suspend_hart_debug(uint32_t hart_id)
622+
{
623+
if (hart_id >= coro_state.hart_slots || !coro_state.initialized)
624+
return;
625+
626+
/* Mark the hart as suspended for debugging.
627+
* The scheduler should skip this hart until resumed.
628+
*/
629+
coro_t *co = coro_state.coroutines[hart_id];
630+
if (co && co->state != CORO_STATE_DEAD)
631+
co->state = CORO_STATE_DEBUG_SUSPENDED;
632+
}
633+
634+
void coro_resume_hart_debug(uint32_t hart_id)
635+
{
636+
if (hart_id >= coro_state.hart_slots || !coro_state.initialized)
637+
return;
638+
639+
/* Resume the hart from debug suspension.
640+
* Transition from DEBUG_SUSPENDED to SUSPENDED, then delegate to
641+
* coro_resume_hart which handles the actual context switch.
642+
*/
643+
coro_t *co = coro_state.coroutines[hart_id];
644+
if (co && co->state == CORO_STATE_DEBUG_SUSPENDED) {
645+
co->state = CORO_STATE_SUSPENDED;
646+
coro_resume_hart(hart_id);
647+
}
648+
}
649+
650+
bool coro_is_debug_suspended(uint32_t hart_id)
651+
{
652+
if (hart_id >= coro_state.hart_slots || !coro_state.initialized)
653+
return false;
654+
655+
coro_t *co = coro_state.coroutines[hart_id];
656+
return (co && co->state == CORO_STATE_DEBUG_SUSPENDED);
657+
}
658+
659+
bool coro_step_hart(uint32_t hart_id)
660+
{
661+
if (hart_id >= coro_state.hart_slots || !coro_state.initialized)
662+
return false;
663+
664+
coro_t *co = coro_state.coroutines[hart_id];
665+
if (!co)
666+
return false;
667+
668+
/* Only allow stepping if hart is currently debug-suspended */
669+
if (co->state != CORO_STATE_DEBUG_SUSPENDED)
670+
return false;
671+
672+
/* Transition to SUSPENDED state so coro_resume_hart can proceed */
673+
co->state = CORO_STATE_SUSPENDED;
674+
675+
/* Resume the coroutine for one instruction.
676+
* The hart coroutine should check single_step_mode and yield after one
677+
* instruction, transitioning back to DEBUG_SUSPENDED state.
678+
*/
679+
coro_resume_hart(hart_id);
680+
681+
/* Verify that the hart actually yielded back after single-step */
682+
return (co->state == CORO_STATE_DEBUG_SUSPENDED);
683+
}

coro.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,23 @@ bool coro_is_suspended(uint32_t slot_id);
3737

3838
/* Get currently executing hart ID */
3939
uint32_t coro_current_hart_id(void);
40+
41+
/* Debug-related coroutine functions for GDB integration */
42+
43+
/* Suspend a hart for debugging (e.g., hit breakpoint)
44+
* The hart will not be scheduled until explicitly resumed.
45+
* This is different from WFI suspension.
46+
*/
47+
void coro_suspend_hart_debug(uint32_t hart_id);
48+
49+
/* Resume a hart from debug suspension */
50+
void coro_resume_hart_debug(uint32_t hart_id);
51+
52+
/* Check if a hart is suspended for debugging */
53+
bool coro_is_debug_suspended(uint32_t hart_id);
54+
55+
/* Execute exactly one instruction on a hart (for single-step debugging)
56+
* This will resume the hart, execute one instruction, then suspend again.
57+
* Returns: true on success, false if hart is not in valid state
58+
*/
59+
bool coro_step_hart(uint32_t hart_id);

gdbctrl.c

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/* GDB control layer for coroutine-based multi-hart execution */
2+
3+
#include <string.h>
4+
5+
#include "coro.h"
6+
#include "gdbctrl.h"
7+
8+
bool gdb_debug_init(vm_t *vm)
9+
{
10+
if (!vm)
11+
return false;
12+
13+
/* Initialize debug context */
14+
memset(&vm->debug_ctx, 0, sizeof(vm_debug_ctx_t));
15+
16+
/* Initialize debug info for all harts */
17+
for (uint32_t i = 0; i < vm->n_hart; i++) {
18+
hart_t *hart = vm->hart[i];
19+
if (!hart)
20+
continue;
21+
22+
memset(&hart->debug_info, 0, sizeof(hart_debug_info_t));
23+
hart->debug_info.state = HART_STATE_RUNNING;
24+
hart->debug_info.single_step_mode = false;
25+
hart->debug_info.breakpoint_pending = false;
26+
}
27+
28+
return true;
29+
}
30+
31+
void gdb_debug_cleanup(vm_t *vm)
32+
{
33+
if (!vm)
34+
return;
35+
36+
/* Clear all breakpoints */
37+
gdb_clear_all_breakpoints(vm);
38+
39+
/* Reset all hart debug states */
40+
for (uint32_t i = 0; i < vm->n_hart; i++) {
41+
hart_t *hart = vm->hart[i];
42+
if (!hart)
43+
continue;
44+
45+
hart->debug_info.state = HART_STATE_RUNNING;
46+
hart->debug_info.single_step_mode = false;
47+
hart->debug_info.breakpoint_pending = false;
48+
}
49+
}
50+
51+
bool gdb_check_breakpoint(hart_t *hart)
52+
{
53+
if (!hart || !hart->vm)
54+
return false;
55+
56+
vm_t *vm = hart->vm;
57+
uint32_t pc = hart->pc;
58+
59+
/* Check if current PC matches any enabled breakpoint */
60+
for (uint32_t i = 0; i < vm->debug_ctx.bp_count; i++) {
61+
breakpoint_t *bp = &vm->debug_ctx.breakpoints[i];
62+
if (bp->enabled && bp->addr == pc) {
63+
/* Breakpoint hit */
64+
hart->debug_info.breakpoint_pending = true;
65+
return true;
66+
}
67+
}
68+
69+
return false;
70+
}
71+
72+
bool gdb_set_breakpoint(vm_t *vm, uint32_t addr)
73+
{
74+
if (!vm)
75+
return false;
76+
77+
/* Check if breakpoint already exists at this address */
78+
for (uint32_t i = 0; i < vm->debug_ctx.bp_count; i++) {
79+
if (vm->debug_ctx.breakpoints[i].addr == addr) {
80+
/* Already exists, just ensure it's enabled */
81+
vm->debug_ctx.breakpoints[i].enabled = true;
82+
return true;
83+
}
84+
}
85+
86+
/* Check if we have room for a new breakpoint */
87+
if (vm->debug_ctx.bp_count >= MAX_BREAKPOINTS)
88+
return false;
89+
90+
/* Add new breakpoint */
91+
breakpoint_t *bp = &vm->debug_ctx.breakpoints[vm->debug_ctx.bp_count];
92+
bp->addr = addr;
93+
bp->enabled = true;
94+
vm->debug_ctx.bp_count++;
95+
96+
return true;
97+
}
98+
99+
bool gdb_del_breakpoint(vm_t *vm, uint32_t addr)
100+
{
101+
if (!vm)
102+
return false;
103+
104+
/* Find the breakpoint */
105+
for (uint32_t i = 0; i < vm->debug_ctx.bp_count; i++) {
106+
if (vm->debug_ctx.breakpoints[i].addr == addr) {
107+
/* Found it - remove by shifting remaining breakpoints down */
108+
uint32_t remaining = vm->debug_ctx.bp_count - i - 1;
109+
if (remaining > 0) {
110+
memmove(&vm->debug_ctx.breakpoints[i],
111+
&vm->debug_ctx.breakpoints[i + 1],
112+
remaining * sizeof(breakpoint_t));
113+
}
114+
vm->debug_ctx.bp_count--;
115+
return true;
116+
}
117+
}
118+
119+
return false;
120+
}
121+
122+
void gdb_clear_all_breakpoints(vm_t *vm)
123+
{
124+
if (!vm)
125+
return;
126+
127+
memset(&vm->debug_ctx.breakpoints, 0, sizeof(vm->debug_ctx.breakpoints));
128+
vm->debug_ctx.bp_count = 0;
129+
}
130+
131+
uint32_t gdb_get_breakpoint_count(vm_t *vm)
132+
{
133+
if (!vm)
134+
return 0;
135+
136+
return vm->debug_ctx.bp_count;
137+
}
138+
139+
void gdb_suspend_hart(hart_t *hart)
140+
{
141+
if (!hart)
142+
return;
143+
144+
/* Mark hart as suspended for debugging */
145+
hart->debug_info.state = HART_STATE_DEBUG_BREAK;
146+
147+
/* Suspend the coroutine (only in SMP mode) */
148+
if (hart->vm && hart->vm->n_hart > 1)
149+
coro_suspend_hart_debug(hart->mhartid);
150+
}
151+
152+
void gdb_resume_hart(hart_t *hart)
153+
{
154+
if (!hart)
155+
return;
156+
157+
/* Clear breakpoint pending flag */
158+
hart->debug_info.breakpoint_pending = false;
159+
160+
/* If single-step mode is enabled, mark as DEBUG_STEP instead of RUNNING */
161+
if (hart->debug_info.single_step_mode) {
162+
hart->debug_info.state = HART_STATE_DEBUG_STEP;
163+
} else {
164+
hart->debug_info.state = HART_STATE_RUNNING;
165+
}
166+
167+
/* Resume the coroutine (only in SMP mode) */
168+
if (hart->vm && hart->vm->n_hart > 1) {
169+
coro_resume_hart_debug(hart->mhartid);
170+
}
171+
}
172+
173+
void gdb_enable_single_step(hart_t *hart)
174+
{
175+
if (!hart)
176+
return;
177+
178+
hart->debug_info.single_step_mode = true;
179+
hart->debug_info.state = HART_STATE_DEBUG_STEP;
180+
}
181+
182+
void gdb_disable_single_step(hart_t *hart)
183+
{
184+
if (!hart)
185+
return;
186+
187+
hart->debug_info.single_step_mode = false;
188+
if (hart->debug_info.state == HART_STATE_DEBUG_STEP)
189+
hart->debug_info.state = HART_STATE_RUNNING;
190+
}
191+
192+
bool gdb_is_single_stepping(hart_t *hart)
193+
{
194+
if (!hart)
195+
return false;
196+
197+
return hart->debug_info.single_step_mode;
198+
}

0 commit comments

Comments
 (0)