Skip to content

Commit 4b3f8f5

Browse files
committed
Enable GDB debugging with event-driven coroutine
This adds 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. 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 bb10925 commit 4b3f8f5

File tree

9 files changed

+968
-7
lines changed

9 files changed

+968
-7
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: 66 additions & 1 deletion
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)
@@ -602,3 +603,67 @@ uint32_t coro_current_hart_id(void)
602603
{
603604
return coro_state.current_hart;
604605
}
606+
607+
/* Debug-related coroutine functions for GDB integration */
608+
609+
void coro_suspend_hart_debug(uint32_t hart_id)
610+
{
611+
if (hart_id >= coro_state.n_hart || !coro_state.initialized)
612+
return;
613+
614+
/* Mark the hart as suspended for debugging.
615+
* The scheduler should skip this hart until resumed.
616+
*/
617+
coro_t *co = coro_state.coroutines[hart_id];
618+
if (co && co->state != CORO_STATE_DEAD)
619+
co->state = CORO_STATE_SUSPENDED;
620+
}
621+
622+
void coro_resume_hart_debug(uint32_t hart_id)
623+
{
624+
if (hart_id >= coro_state.n_hart || !coro_state.initialized)
625+
return;
626+
627+
/* Resume the hart from debug suspension by delegating to coro_resume_hart.
628+
* This ensures proper state transition and actually jumps into the
629+
* coroutine, rather than just setting the state flag which would cause the
630+
* scheduler to refuse resumption on the next iteration.
631+
*/
632+
coro_t *co = coro_state.coroutines[hart_id];
633+
if (co && co->state == CORO_STATE_SUSPENDED)
634+
coro_resume_hart(hart_id);
635+
}
636+
637+
bool coro_is_debug_suspended(uint32_t hart_id)
638+
{
639+
if (hart_id >= coro_state.n_hart || !coro_state.initialized)
640+
return false;
641+
642+
coro_t *co = coro_state.coroutines[hart_id];
643+
return (co && co->state == CORO_STATE_SUSPENDED);
644+
}
645+
646+
bool coro_step_hart(uint32_t hart_id)
647+
{
648+
if (hart_id >= coro_state.n_hart || !coro_state.initialized)
649+
return false;
650+
651+
coro_t *co = coro_state.coroutines[hart_id];
652+
if (!co)
653+
return false;
654+
655+
/* Only allow stepping if hart is currently suspended */
656+
if (co->state != CORO_STATE_SUSPENDED)
657+
return false;
658+
659+
/* Resume the coroutine for one instruction.
660+
* DO NOT manually set state to RUNNING before calling coro_resume_hart(),
661+
* as that function requires SUSPENDED state and will handle the transition.
662+
* The hart coroutine should check single_step_mode and yield after one
663+
* instruction.
664+
*/
665+
coro_resume_hart(hart_id);
666+
667+
/* It should have yielded after one instruction and be suspended again */
668+
return true;
669+
}

coro.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,23 @@ bool coro_is_suspended(uint32_t hart_id);
4242
* Returns: Hart ID of the currently executing coroutine, or UINT32_MAX if idle
4343
*/
4444
uint32_t coro_current_hart_id(void);
45+
46+
/* Debug-related coroutine functions for GDB integration */
47+
48+
/* Suspend a hart for debugging (e.g., hit breakpoint)
49+
* The hart will not be scheduled until explicitly resumed.
50+
* This is different from WFI suspension.
51+
*/
52+
void coro_suspend_hart_debug(uint32_t hart_id);
53+
54+
/* Resume a hart from debug suspension */
55+
void coro_resume_hart_debug(uint32_t hart_id);
56+
57+
/* Check if a hart is suspended for debugging */
58+
bool coro_is_debug_suspended(uint32_t hart_id);
59+
60+
/* Execute exactly one instruction on a hart (for single-step debugging)
61+
* This will resume the hart, execute one instruction, then suspend again.
62+
* Returns: true on success, false if hart is not in valid state
63+
*/
64+
bool coro_step_hart(uint32_t hart_id);

gdbctrl.c

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

gdbctrl.h

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/* GDB control layer for coroutine-based multi-hart execution */
2+
3+
#pragma once
4+
5+
#include <stdbool.h>
6+
#include <stdint.h>
7+
8+
#include "riscv.h"
9+
10+
/* Initialize GDB debug subsystem for a VM
11+
* @vm: VM instance to initialize debugging for
12+
* Returns: true on success, false on failure
13+
*/
14+
bool gdb_debug_init(vm_t *vm);
15+
16+
/* Cleanup GDB debug subsystem
17+
* @vm: VM instance to cleanup
18+
*/
19+
void gdb_debug_cleanup(vm_t *vm);
20+
21+
/* Check if a hart has hit a breakpoint at its current PC
22+
* This should be called before executing each instruction.
23+
* @hart: Hart to check
24+
* Returns: true if breakpoint hit, false otherwise
25+
*/
26+
bool gdb_check_breakpoint(hart_t *hart);
27+
28+
/* Set a breakpoint at the specified address
29+
* @vm: VM instance
30+
* @addr: Virtual address for breakpoint
31+
* Returns: true on success, false if breakpoint table is full
32+
*/
33+
bool gdb_set_breakpoint(vm_t *vm, uint32_t addr);
34+
35+
/* Delete a breakpoint at the specified address
36+
* @vm: VM instance
37+
* @addr: Virtual address of breakpoint to remove
38+
* Returns: true if breakpoint was found and removed, false otherwise
39+
*/
40+
bool gdb_del_breakpoint(vm_t *vm, uint32_t addr);
41+
42+
/* Clear all breakpoints
43+
* @vm: VM instance
44+
*/
45+
void gdb_clear_all_breakpoints(vm_t *vm);
46+
47+
/* Get breakpoint count
48+
* @vm: VM instance
49+
* Returns: Number of active breakpoints
50+
*/
51+
uint32_t gdb_get_breakpoint_count(vm_t *vm);
52+
53+
/* Suspend a hart for debugging (breakpoint or user interrupt)
54+
* This marks the hart as DEBUG_BREAK and prevents it from being scheduled.
55+
* @hart: Hart to suspend
56+
*/
57+
void gdb_suspend_hart(hart_t *hart);
58+
59+
/* Resume a hart from debug suspension
60+
* This marks the hart as RUNNING and allows it to be scheduled.
61+
* @hart: Hart to resume
62+
*/
63+
void gdb_resume_hart(hart_t *hart);
64+
65+
/* Enable single-step mode for a hart
66+
* The hart will execute one instruction then suspend again.
67+
* @hart: Hart to single-step
68+
*/
69+
void gdb_enable_single_step(hart_t *hart);
70+
71+
/* Disable single-step mode for a hart
72+
* @hart: Hart to disable single-stepping
73+
*/
74+
void gdb_disable_single_step(hart_t *hart);
75+
76+
/* Check if a hart is in single-step mode
77+
* @hart: Hart to check
78+
* Returns: true if single-stepping, false otherwise
79+
*/
80+
bool gdb_is_single_stepping(hart_t *hart);

0 commit comments

Comments
 (0)