gdb_tcp/
gdb_tcp.rs

1//! GDB TCP Server Example
2//!
3//! To use it, you must have installed `riscv32-unknown-elf-gdb` from the [RISC-V toolchain](https://github.com/riscv-collab/riscv-gnu-toolchain).
4//!
5//! Example:
6//! -> Run the example with `cargo run --example gdb_tcp <binary.elf>`
7//! -> Connect to the gdb server with `riscv32-unknown-elf-gdb <binary.elf> -ex "target remote localhost:9001"`
8//! -> Use gdb commands to debug the program (ex: `step`, `info registers`, `break`, etc)
9//!
10use std::env;
11use std::net::Ipv4Addr;
12use std::net::SocketAddr;
13use std::net::TcpListener;
14use std::net::TcpStream;
15use std::num::NonZeroI32;
16
17use embive::interpreter::memory::{Memory, MemoryType, SliceMemory};
18use embive::interpreter::Debugger;
19use embive::interpreter::Error;
20use embive::interpreter::Interpreter;
21use embive::interpreter::SYSCALL_ARGS;
22use embive::transpiler::transpile_elf;
23use gdbstub::conn::{Connection, ConnectionExt};
24use gdbstub::stub::{DisconnectReason, GdbStub};
25
26// A simple connection implementation for gdbstub using TcpStream.
27struct TcpConnection {
28    stream: TcpStream,
29}
30
31impl TcpConnection {
32    fn new(stream: TcpStream) -> Self {
33        Self { stream }
34    }
35}
36
37impl Connection for TcpConnection {
38    type Error = std::io::Error;
39
40    fn write(&mut self, byte: u8) -> Result<(), Self::Error> {
41        use std::io::Write;
42
43        Write::write_all(&mut self.stream, &[byte])
44    }
45
46    fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
47        use std::io::Write;
48
49        Write::write_all(&mut self.stream, buf)
50    }
51
52    fn flush(&mut self) -> Result<(), Self::Error> {
53        use std::io::Write;
54
55        Write::flush(&mut self.stream)
56    }
57
58    fn on_session_start(&mut self) -> Result<(), Self::Error> {
59        // see https://github.com/daniel5151/gdbstub/issues/28
60        self.stream.set_nodelay(true)
61    }
62}
63
64impl ConnectionExt for TcpConnection {
65    fn read(&mut self) -> Result<u8, Self::Error> {
66        use std::io::Read;
67
68        self.stream.set_nonblocking(false)?;
69
70        let mut buf = [0u8];
71        match Read::read_exact(&mut self.stream, &mut buf) {
72            Ok(_) => Ok(buf[0]),
73            Err(e) => Err(e),
74        }
75    }
76
77    fn peek(&mut self) -> Result<Option<u8>, Self::Error> {
78        self.stream.set_nonblocking(true)?;
79
80        let mut buf = [0u8];
81        match self.stream.peek(&mut buf) {
82            Ok(_) => Ok(Some(buf[0])),
83            Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(None),
84            Err(e) => Err(e),
85        }
86    }
87}
88
89// A simple syscall implementation
90fn syscall<M: Memory>(
91    nr: i32,
92    args: &[i32; SYSCALL_ARGS],
93    memory: &mut M,
94) -> Result<Result<i32, NonZeroI32>, Error> {
95    // Match the syscall number
96    Ok(match nr {
97        // Add two numbers (arg[0] + arg[1])
98        1 => Ok(args[0] + args[1]),
99        // Load from RAM (arg[0])
100        2 => match i32::load(memory, args[0] as u32) {
101            Ok(val) => Ok(val),
102            Err(_) => Err(1.try_into().unwrap()), // Error loading
103        },
104        _ => Err(2.try_into().unwrap()), // Not implemented
105    })
106}
107
108fn main() -> Result<(), Box<dyn std::error::Error>> {
109    // Get the ELF file path from the command line arguments
110    let args: Vec<String> = env::args().collect();
111    if args.len() != 2 {
112        eprintln!("Usage: {} <binary.elf>", args[0]);
113        return Err(std::io::Error::from(std::io::ErrorKind::InvalidInput).into());
114    }
115
116    // Read the ELF file
117    println!("Reading ELF: {}", args[1]);
118    let elf = std::fs::read(&args[1])?;
119
120    // Transpile the ELF file
121    let mut code = [0; 256 * 1024];
122    println!("Transpiling ELF...");
123    transpile_elf(&elf, &mut code)?;
124    println!("ELF transpiled!");
125
126    // Initialize the debugger
127    let mut ram = [0; 64 * 1024];
128    let mut memory = SliceMemory::new(code.as_slice(), &mut ram);
129    let mut debugger: Debugger<'_, _, TcpConnection, _> = Debugger::new(&mut memory, syscall);
130
131    // Wait for GDB client to connect
132    println!("Waiting for GDB client to connect (localhost:9001)...");
133    let sock = TcpListener::bind(SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), 9001))?;
134    let (stream, addr) = sock.accept()?;
135    let conn = TcpConnection::new(stream);
136    println!("Connected to: {}", addr);
137
138    // Create a GDB server
139    let mut buffer = [0; 4096];
140    let gdb = GdbStub::builder(conn)
141        .with_packet_buffer(&mut buffer)
142        .build()
143        .map_err(|e| std::io::Error::other(e.to_string()))?;
144
145    // Run the GDB server
146    match gdb.run_blocking::<Debugger<'_, _, TcpConnection, _>>(&mut debugger) {
147        Ok(disconnect_reason) => match disconnect_reason {
148            DisconnectReason::Disconnect => {
149                println!("GDB client has disconnected.");
150            }
151            DisconnectReason::TargetExited(code) => {
152                println!("Target exited with code {}!", code)
153            }
154            DisconnectReason::TargetTerminated(sig) => {
155                println!("Target terminated with signal {}!", sig)
156            }
157            DisconnectReason::Kill => println!("GDB sent a kill command!"),
158        },
159        Err(e) => {
160            if e.is_target_error() {
161                println!(
162                    "target encountered a fatal error: {}",
163                    e.into_target_error().unwrap()
164                )
165            } else if e.is_connection_error() {
166                let (e, kind) = e.into_connection_error().unwrap();
167                println!("connection error: {:?} - {}", kind, e,)
168            } else {
169                println!("gdbstub encountered a fatal error: {}", e)
170            }
171        }
172    }
173
174    // Interpreter state after debugging ended
175    let mut interpreter: Interpreter<'_, SliceMemory> = debugger.into();
176    println!("");
177    println!("Interpreter State:");
178    println!("Registers: {:?}", interpreter.registers.cpu);
179    println!("Program Counter: {:#08x}", interpreter.program_counter);
180    println!("Instruction: {:?}", interpreter.fetch());
181
182    return Ok(());
183}