-
Notifications
You must be signed in to change notification settings - Fork 0
GDB Stub Debugging Guide
The RV32 simulator includes an integrated GDB stub that enables remote debugging using standard GDB (GNU Debugger) tools. This allows you to debug RISC-V programs running in the simulator as if they were running on real hardware, with full support for breakpoints, watchpoints, memory inspection, and single-stepping.
The GDB stub implements the GDB Remote Serial Protocol, a standard protocol that allows GDB to communicate with debugging targets over a network connection. The simulator acts as the "target" and GDB acts as the "client".
┌─────────────────┐ ┌──────────────────────┐
│ GDB Client │ <--- Network ---> │ RV32 Simulator │
│ (localhost) │ (TCP Port 3333) │ (GDB Stub Server) │
└─────────────────┘ └──────────────────────┘
Defines the public API and data structures:
-
gdb_context_t: Main GDB context structure -
breakpoint_t: Breakpoint management -
watchpoint_t: Watchpoint management with type support -
gdb_callbacks_t: Callback functions for simulator integration
Implements the GDB RSP protocol:
- Packet parsing and encoding
- Command handlers for all GDB commands
- Breakpoint and watchpoint management
- TCP socket communication
Integrates GDB stub into the RV32 simulator:
- Callback functions connecting GDB to simulator state
- Breakpoint/watchpoint checking during execution
- Single-step support
- Memory and register access through GDB
The GDB stub implements the following remote protocol commands:
| Command | Format | Description |
|---|---|---|
| Continue | c |
Resume execution |
| Step | s |
Single-step one instruction |
| Step with Address | s[addr] |
Single-step from specific address |
| Continue with Address | c[addr] |
Continue from specific address |
| Kill | k |
Terminate the program |
| Command | Format | Description |
|---|---|---|
| Read All Registers | g |
Read all 32 RISC-V registers |
| Read Register | p[reg] |
Read specific register (hex format) |
| Write Register | P[reg]=[value] |
Write to specific register |
| Command | Format | Description |
|---|---|---|
| Read Memory | m[addr],[length] |
Read memory (hex address/length) |
| Write Memory | M[addr],[length]:[data] |
Write memory data |
| Write Memory (binary) | X[addr],[length]:[data] |
Write binary data |
| Command | Format | Description |
|---|---|---|
| Insert Breakpoint | Z0,[addr],1 |
Set breakpoint at address |
| Remove Breakpoint | z0,[addr],1 |
Clear breakpoint at address |
| Insert Write Watchpoint | Z2,[addr],[len] |
Watch memory writes |
| Remove Write Watchpoint | z2,[addr],[len] |
Remove write watchpoint |
| Insert Read Watchpoint | Z3,[addr],[len] |
Watch memory reads |
| Remove Read Watchpoint | z3,[addr],[len] |
Remove read watchpoint |
| Insert Access Watchpoint | Z4,[addr],[len] |
Watch any memory access |
| Remove Access Watchpoint | z4,[addr],[len] |
Remove access watchpoint |
| Command | Format | Description |
|---|---|---|
| Get Status | ? |
Query stop reason (breakpoint/watchpoint/signal) |
| Read Thread Info | qfThreadInfo |
Get thread list (returns single thread) |
| Get Halt Reason | qRcmd,<cmd> |
Get reason for last halt |
| Command | Format | Description |
|---|---|---|
| Detach | D |
Detach debugger from target |
| Reset | R |
Reset the simulator |
To enable GDB debugging in the simulator, use the --gdb and optional --gdb-port options:
./build/rv32sim --gdb --gdb-port=3333 program.elfThe simulator provides these callback functions to allow GDB to access simulator state:
typedef struct {
uint32_t (*read_reg)(void *sim, int reg_num); // Read RISC-V register
void (*write_reg)(void *sim, int reg_num, uint32_t value); // Write RISC-V register
uint32_t (*read_mem)(void *sim, uint32_t addr, int size); // Read memory
void (*write_mem)(void *sim, uint32_t addr, uint32_t value, int size); // Write memory
uint32_t (*get_pc)(void *sim); // Get program counter
void (*set_pc)(void *sim, uint32_t pc); // Set program counter
void (*single_step)(void *sim); // Execute one instruction
bool (*is_running)(void *sim); // Check if running
} gdb_callbacks_t;During execution, the simulator checks for breakpoints and watchpoints:
// Check breakpoint at current PC
if (gdb_enabled && gdb_ctx) {
if (gdb_stub_check_breakpoint((gdb_context_t*)gdb_ctx, pc)) {
// Stop execution and notify GDB
break;
}
}
// Check watchpoints on memory access
if (gdb_enabled && gdb_ctx && addr != pc) {
if (gdb_stub_check_watchpoint_write((gdb_context_t*)gdb_ctx, addr, size)) {
// Stop execution on watchpoint hit
break;
}
}make build-sim
./build/rv32sim --gdb --gdb-port=3333 sw/simple/simple.elf &The simulator will output:
GDB: enabled on port 3333
Waiting for GDB connection...
In another terminal, start GDB:
riscv64-unknown-elf-gdb sw/simple/simple.elfIn GDB, connect to the simulator:
(gdb) target remote localhost:3333
Remote debugging using localhost:3333
Reading symbols from /path/to/program.elf...
(gdb)Set a breakpoint at main or any function:
(gdb) break main
Breakpoint 1 at 0x80000000: file sw/simple/simple.c, line 5.
(gdb) break *0x80000100
Breakpoint 2 at 0x80000100
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x80000000 main at sw/simple/simple.c:5
2 breakpoint keep y 0x80000100 <code>Continue execution until the first breakpoint:
(gdb) continue
Breakpoint 1, main () at sw/simple/simple.c:5
5 int main() {Or use run to start from the beginning:
(gdb) run
Starting program: /path/to/program.elf
Breakpoint 1, main () at sw/simple/simple.c:5
5 int main() {(gdb) info registers
ra 0x80000004 2147483652
sp 0x80200000 0x80200000
gp 0x80001000 2147517440
tp 0x0 0
t0 0x0 0
...
(gdb) print $sp
$1 = (void *) 0x80200000
(gdb) print/x $a0
$2 = 0x42(gdb) x/4x 0x80000000
0x80000000: 0x00001197 0xe6018193 0x30047073 0x00200117
(gdb) x/10i 0x80000000
0x80000000: auipc gp,0x1
0x80000004: addi gp,gp,-416
0x80000008: csrrci zero,mstatus,8
0x8000000c: auipc sp,0x200
0x80000010: addi sp,sp,-12
...(gdb) print variable_name
(gdb) print *(int*)0x80200000
$3 = 42Execute one instruction at a time:
(gdb) stepi
0x80000004 in main () at sw/simple/simple.c:6
6 printf("Hello, World!\n");
(gdb) stepi 5 # Step 5 instructionsOr step through source code lines:
(gdb) step # Step into function
(gdb) next # Step over function call
(gdb) finish # Run until function returnsWatch memory locations for changes:
(gdb) watch *0x80200000
Hardware watchpoint 1: *0x80200000
(gdb) watch variable_name
Hardware watchpoint 2: variable_name
(gdb) rwatch *0x80200000
Hardware watchpoint 3: *0x80200000 (read)
(gdb) awatch *0x80200000
Hardware watchpoint 4: *0x80200000 (read/write)
(gdb) info watchpoints
Num Type Disp Enb Address What
1 hw watchpoint keep y *0x80200000
2 hw watchpoint keep y variable_nameWhen a watchpoint is hit:
Hardware watchpoint 1: *0x80200000
Old value = 0x0
New value = 0x42
0x80000050 in function_name () from /path/to/program.elfBreak only when a condition is met:
(gdb) break main if x > 10
(gdb) break *0x80000100 if $a0 == 42Resume execution:
(gdb) continue # Run until next breakpointExit debugging:
(gdb) quit# Terminal 1: Start simulator with GDB
$ ./build/rv32sim --gdb --gdb-port=3333 sw/simple/simple.elf
GDB: enabled on port 3333
Waiting for GDB connection...# Terminal 2: Start GDB
$ riscv64-unknown-elf-gdb sw/simple/simple.elf
(gdb) target remote localhost:3333
(gdb) break main
(gdb) run
Breakpoint 1, main () at sw/simple/simple.c:5
5 int main() {
(gdb) info registers
(gdb) x/10i $pc
(gdb) stepi
(gdb) continue
(gdb) quitEclipse CDT (C/C++ Development Tooling) provides a graphical interface for remote debugging with GDB. This section explains how to configure Eclipse to debug RISC-V programs running in the simulator.
- Eclipse CDT - Install Eclipse with C/C++ Development Tooling plugin
-
RISC-V Toolchain - Ensure
riscv64-unknown-elf-gdbis installed and in PATH - Build Project - Import the kcore project into Eclipse or use existing Eclipse project
Open Eclipse and create a new C/C++ project or import the existing kcore project:
File → New → Project → C/C++ → C/C++ Project
(or import existing project)
Ensure the project is configured with RISC-V toolchain:
Project → Properties → C/C++ Build → Tool Chain Editor
Select: RISC-V toolchain
Build your RISC-V program in Eclipse:
Project → Build Project (Ctrl+B)
The executable should be generated (e.g., simple.elf)
Create a new debug configuration for remote debugging:
Run → Debug Configurations...
Right-click on "GDB Hardware Debugging" and select "New":
Main Tab:
- Project: Select your project
- C/C++ Application: Browse to your ELF file (e.g.,
build/simple.elf) - Build configuration: Select appropriate build type
Debugger Tab:
- GDB debugger:
riscv64-unknown-elf-gdb(or path to your GDB) - GDB command file: (leave empty or specify if needed)
Connection Tab:
- Connection type: TCP
- Hostname/IP address:
localhost - Port number:
3333 - Timeout (seconds):
30
Common Options Tab:
- Run commands: Leave default or add initialization commands
Example GDB initialization commands:
set remote hardware-watchpoint-limit 4
set remote hardware-breakpoint-limit 6
define hook-quit
if connected
disconnect
end
end
Click "Debug" to save and start debugging.
Open a terminal and start the simulator with GDB enabled:
./build/rv32sim --gdb --gdb-port=3333 sw/simple/simple.elf &The simulator will wait for GDB connection:
GDB: enabled on port 3333
Waiting for GDB connection...
In Eclipse, the debugger should automatically connect to the simulator. You should see:
- Debug Perspective opens automatically
- Breakpoints view shows available breakpoints
- Debug view shows the current execution state
- Variables view shows local variables and registers
- Disassembly view shows the current instruction
Set breakpoints by clicking in the left margin of the editor:
- Open source file in editor
- Click left margin next to line number to create breakpoint
- Right-click breakpoint to access options:
-
Conditional Breakpoint: Add condition (e.g.,
x > 10) - Enable/Disable: Toggle breakpoint state
- Properties: Configure breakpoint behavior
-
Conditional Breakpoint: Add condition (e.g.,
Use Eclipse Debug toolbar or keyboard shortcuts:
| Action | Button | Shortcut |
|---|---|---|
| Resume | ▶ | F8 |
| Suspend | ⏸ | - |
| Terminate | ⏹ | Ctrl+Alt+W |
| Step Into | ↓ | F5 |
| Step Over | → | F6 |
| Step Return | ↑ | F7 |
| Step to Line | - | Ctrl+Alt+B |
Eclipse provides several views to inspect program state:
Shows local variables and their current values:
- Automatically updates after each step
- Right-click to "Watch" selected variable
- Expandable for structs and arrays
Shows RISC-V register values:
View → Show View → Other → Debug → Registers
Displays all 32 registers with current values in decimal/hex.
Inspect memory contents:
View → Show View → Other → Debug → Memory
- Add memory monitors for specific addresses
- View memory as bytes, words, or formatted data
Evaluate C expressions at runtime:
View → Show View → Other → Debug → Expressions
Add expressions like:
-
*(int*)0x80200000- Read integer at address -
variable_name- Evaluate variable -
$sp + 0x100- Evaluate register expressions
Show assembly instructions:
View → Show View → Other → Debug → Disassembly
Set watchpoints on variables or memory locations:
- Right-click variable in Variables view → "Watch"
- Or in editor: Ctrl+Shift+B to create watchpoint at cursor
- Configure watchpoint type:
- Read: Stop on memory read
- Write: Stop on memory write
- Read/Write: Stop on any access
The Debug view shows the call stack:
Debug View → Stack Frame list
Click on any stack frame to see:
- Source code location
- Local variables at that level
- Register values
1. Set breakpoint at main() by clicking left margin
2. Click "Debug" button to start remote debugging
3. Debugger connects and pauses at breakpoint
4. Inspect variables in Variables view
5. Step into function (F5)
6. View registers in Registers view
7. Right-click variable to add watch expression
8. Continue to next breakpoint (F8)
9. Examine memory in Memory view
10. Terminate when done
Right-click breakpoint → Properties:
Condition: x > 100
Suspend when true (default)
Right-click breakpoint → Convert to Tracepoint:
- Logs value without stopping
- Useful for high-frequency breakpoints
Debug toolbar → Skip All Breakpoints (Ctrl+Alt+K)
- Temporarily disable all breakpoints
- Useful for performance testing
Filter which threads/stack frames to show:
Window → Preferences → Debug → Detail Formatters
For remote simulator (not localhost):
Debug Configuration → Debugger → Connection Tab:
- Hostname/IP:
your.remote.host - Port:
3333(if simulator is listening on that port)
Or use SSH tunneling:
ssh -L 3333:localhost:3333 remote_host
# Then connect to localhost:3333 in EclipseProblem: "Connection refused" error
- Check simulator is running with
--gdbflag - Verify port number matches (3333)
- Check firewall settings
Solution:
# Check if simulator is running
ps aux | grep rv32sim
# Check port is listening
lsof -i :3333
# Verify GDB path
which riscv64-unknown-elf-gdbProblem: Breakpoints don't show in editor margin
- Ensure source files match the ELF file being debugged
- Check project build paths match source paths
- Rebuild project with debug symbols (
-gflag)
Problem: Variables view is empty
- Ensure program is paused at breakpoint
- Check debug symbols are included in ELF
- Verify variables are in scope at current location
Problem: Eclipse freezes when stepping
- Increase GDB timeout in Debug Configuration
- Check system resources (CPU, memory)
- Try limiting watchpoint count
- Pause execution
- Highlight variable name in editor
- Right-click → "Display as → ..."
- Select format (hex, decimal, binary, etc.)
Window → Show View → Display
Type expressions and press Ctrl+U to evaluate:
*(int*)0x80200000
$sp + 100
variable_name * 2
File → Save As → Save Debug Session
Useful for complex debugging scenarios.
| Action | Shortcut |
|---|---|
| Debug Last Launched | F11 |
| Step Into | F5 |
| Step Over | F6 |
| Step Return | F7 |
| Resume | F8 |
| Terminate | Ctrl+Alt+W |
| Conditional Breakpoint | Shift+Ctrl+B |
Save simulator state:
(gdb) set logging on
(gdb) set logging file debug.log
(gdb) info all-registers
(gdb) dump memory memory.bin 0x80000000 0x80200000(gdb) break loop_function if counter > 100
(gdb) continue # Skip to iteration 100(gdb) awatch global_buffer
(gdb) continue # Stop on any accessCreate debug.gdb:
target remote localhost:3333
break main
break *0x80000100
run
info registers
x/10i $pc
continue
quit
Run with:
riscv64-unknown-elf-gdb -x debug.gdb -batch sw/simple/simple.elfProblem: "Cannot connect to remote target"
- Ensure simulator is running with
--gdbflag - Check port number matches in both simulator and GDB
- Verify port is not in use:
lsof -i :3333
Solution:
# Kill process on port
lsof -i :3333 | awk 'NR!=1 {print $2}' | xargs kill -9
# Restart simulator
./build/rv32sim --gdb --gdb-port=3333 program.elfProblem: Breakpoint set but not triggered
- Verify address is correct:
info breakpoints - Check if code execution reaches that address
- Ensure breakpoint is enabled:
enable breakpoint_number
Problem: Watchpoint not catching memory changes
- Verify address and size are correct
- Check if access is through the expected path
- Try simpler watchpoint first:
watch *(int*)0x80200000
- GDB debugging adds overhead due to breakpoint checking
- For performance testing, disable GDB:
--no-gdb - Use breakpoints strategically to minimize performance impact
- Watchpoints have more overhead than breakpoints
- Single-threaded debugging only
- No multi-core debugging support
- Watchpoints limited to 32 per execution
- Breakpoints limited to 64 per execution
- No coprocessor debugging support