Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement basic gdb server functionality. #52

Merged
merged 2 commits into from
Jan 5, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
341 changes: 206 additions & 135 deletions Cargo.lock

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ use zmu_cortex_m::Processor;

use zmu_cortex_m::system::simulation::simulate_trace;
use zmu_cortex_m::system::simulation::{simulate, SimulationError};
use zmu_cortex_m::gdb::server::GdbServer;

mod errors {
// Create the Error, ErrorKind, ResultExt, and Result types
Expand All @@ -59,6 +60,7 @@ fn run_bin(
trace: bool,
option_trace_start: Option<u64>,
itm_file: Option<Box<dyn io::Write + 'static>>,
gdb: bool,
) -> Result<u32> {
let res = Object::parse(buffer).unwrap();

Expand Down Expand Up @@ -122,6 +124,22 @@ fn run_bin(
let trace_start = option_trace_start.unwrap_or(0);
let semihost_func = Box::new(get_semihost_func(Instant::now()));

if gdb {
let gdb = GdbServer::new(
&flash_mem,
semihost_func,
if flash_start_address != 0 {
Some(MemoryMapConfig::new(flash_start_address, 0, flash_size))
} else {
None
},
flash_size,
);

let exit_code = gdb?.start().expect("GDB server failed");
return Ok(exit_code);
}

let statistics = if trace {
debug!("Configuring tracing.");

Expand Down Expand Up @@ -234,6 +252,7 @@ fn run(args: &ArgMatches) -> Result<u32> {
run_matches.get_flag("trace"),
trace_start,
itm_output,
run_matches.get_flag("gdb"),
)?
}
Some((_, _)) => unreachable!(),
Expand Down Expand Up @@ -289,6 +308,13 @@ fn main() {
.help("List of free arguments to pass to runtime as parameters")
.index(2)
.action(ArgAction::Append),
)
.arg(
Arg::new("gdb")
.action(ArgAction::SetTrue)
.long("gdb")
.help("Enable the gdb server")
.num_args(0)
),
)
.get_matches();
Expand Down
2 changes: 2 additions & 0 deletions zmu_cortex_m/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ edition = "2021"
[dependencies]
byteorder = "1"
enum-set = "0.0.8"
gdbstub = "0.7"
gdbstub_arch = "0.3"


[features]
Expand Down
90 changes: 90 additions & 0 deletions zmu_cortex_m/src/gdb/conn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use gdbstub::conn::{Connection, ConnectionExt};
use std::{
io::Read, net::{Shutdown, TcpListener, TcpStream}, str
};

pub struct TcpConnection {
stream: TcpStream,
}

impl TcpConnection {
pub fn new_localhost(port: u16) -> Result<TcpConnection, &'static str> {
let listener = TcpListener::bind(("127.0.0.1", port)).unwrap();

for stream in listener.incoming() {
let stream = stream.unwrap();
stream.set_read_timeout(Some(std::time::Duration::from_millis(1)))
.expect("set_read_timeout call failed");
// stream.set_nonblocking(true).expect("set_nonblocking call failed");
return Ok(TcpConnection { stream });
};

Err("could not accept socket connection")
}
}

impl Drop for TcpConnection {
fn drop(&mut self) {
self.stream.shutdown(Shutdown::Both).expect("shutdown failed");
}
}

impl Connection for TcpConnection {
type Error = &'static str;

fn write(&mut self, b: u8) -> Result<(), &'static str> {
match self.stream.write(b) {
Ok(_) => Ok(()),
Err(_) => Err("socket write failed")
}
}

fn flush(&mut self) -> Result<(), &'static str> {
match self.stream.flush() {
Ok(_) => Ok(()),
Err(_) => Err("socket flush failed")
}
}
}

impl ConnectionExt for TcpConnection {

fn read(&mut self) -> std::result::Result<u8, Self::Error> {
let mut buf: [u8; 1] = [0];
loop {
match self.stream.read_exact(&mut buf)
{
Ok(_) => break,
Err(e) => match e.kind() {
#[cfg(windows)]
std::io::ErrorKind::TimedOut => continue,
#[cfg(unix)]
std::io::ErrorKind::WouldBlock => continue,
_ => return Err("socket read failed")
}
}
}
Ok(buf[0])
}

fn peek(&mut self) -> std::result::Result<Option<u8>, Self::Error> {
let mut buf: [u8; 1] = [0];
loop {
match self.stream.peek(&mut buf)
{
Ok(_) => break,
Err(e) => match e.kind() {
#[cfg(windows)]
std::io::ErrorKind::TimedOut => return Ok(None),
#[cfg(unix)]
std::io::ErrorKind::WouldBlock => return Ok(None),
_ => {
println!("peek error: {:?}", e);
return Err("socket peek failed")
}
}
}
}
Ok(Some(buf[0]))
}
}
8 changes: 8 additions & 0 deletions zmu_cortex_m/src/gdb/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//!
//! Gdb Module
//!

pub mod server;
mod conn;
mod simulation;
mod target;
171 changes: 171 additions & 0 deletions zmu_cortex_m/src/gdb/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
//!
//! Flash Memory simulation
//!
//!

use gdbstub::stub::run_blocking;
use gdbstub::stub::SingleThreadStopReason;
use gdbstub::conn::Connection;
use gdbstub::conn::ConnectionExt;
use gdbstub::target::Target;
use gdbstub::common::Signal;
use gdbstub::stub::GdbStub;
use gdbstub::stub::DisconnectReason;

use crate::{MemoryMapConfig};
use crate::gdb::conn;
use conn::TcpConnection;
use crate::gdb::simulation::SimulationEvent;
use crate::gdb::simulation::SimulationRunEvent;

use crate::gdb::target::ZmuTarget;

use crate::semihosting::SemihostingCommand;
use crate::semihosting::SemihostingResponse;

///
/// The gdb Server
///
pub struct GdbServer {
// number: i32,
target: ZmuTarget,
}

///
impl GdbServer {

///
pub fn new(
code: &[u8],
semihost_func: Box<dyn FnMut(&SemihostingCommand) -> SemihostingResponse + 'static>,
map: Option<MemoryMapConfig>,
flash_size: usize,
) -> Result<GdbServer, &'static str> {

let target = ZmuTarget::new(code, semihost_func, map, flash_size);

Ok(GdbServer {target})
}

///
pub fn start(&mut self) -> Result<u32, &'static str> {
println!("Starting GDB server");
let mut exit_code = 0;
let conn = match conn::TcpConnection::new_localhost(9001) {
Ok(conn) => conn,
Err(e) => return Err(e),
};

let gdb = GdbStub::new(conn);

match gdb.run_blocking::<EventLoop>(&mut self.target) {
Ok(disconnect_reason) => match disconnect_reason {
DisconnectReason::Disconnect => {
println!("GDB client has disconnected. Running to completion...");
loop {
match self.target.step() {
SimulationEvent::Halted => break,
SimulationEvent::Finalized(code) => {
exit_code = code;
break;
}
_ => {}
}
}
}
DisconnectReason::TargetExited(code) => {
println!("\nTarget exited with code {}!", code)
}
DisconnectReason::TargetTerminated(sig) => {
println!("\nTarget terminated with signal {}!", sig)
}
DisconnectReason::Kill => println!("\nGDB sent a kill command!"),
},
Err(e) => {
if e.is_target_error() {
println!(
"target encountered a fatal error: {}",
e.into_target_error().unwrap()
)
} else if e.is_connection_error() {
let (e, kind) = e.into_connection_error().unwrap();
println!("connection error: {:?} - {}", kind, e,)
} else {
println!("gdbstub encountered a fatal error: {}", e)
}
}
}
Ok(exit_code)
}
}


enum EventLoop {}

impl run_blocking::BlockingEventLoop for EventLoop {
type Target = ZmuTarget;
type Connection = TcpConnection;
type StopReason = SingleThreadStopReason<u32>;

#[allow(clippy::type_complexity)]
fn wait_for_stop_reason(
target: &mut ZmuTarget,
conn: &mut Self::Connection,
) -> Result<
run_blocking::Event<SingleThreadStopReason<u32>>,
run_blocking::WaitForStopReasonError<
<Self::Target as Target>::Error,
<Self::Connection as Connection>::Error,
>,
> {

let poll_incoming_data = || {
// gdbstub takes ownership of the underlying connection, so the `borrow_conn`
// method is used to borrow the underlying connection back from the stub to
// check for incoming data.
conn.peek().map(|b| b.is_some()).unwrap_or(true)
};

match target.run(poll_incoming_data) {
SimulationRunEvent::IncomingData => {
let byte = conn
.read()
.map_err(run_blocking::WaitForStopReasonError::Connection)?;
Ok(run_blocking::Event::IncomingData(byte))
}
SimulationRunEvent::Event(event) => {
use gdbstub::target::ext::breakpoints::WatchKind;

// translate emulator stop reason into GDB stop reason
let stop_reason = match event {
SimulationEvent::DoneStep => SingleThreadStopReason::DoneStep,
SimulationEvent::Halted => SingleThreadStopReason::Terminated(Signal::SIGSTOP),
SimulationEvent::Break => SingleThreadStopReason::SwBreak(()),
SimulationEvent::WatchWrite(addr) => SingleThreadStopReason::Watch {
tid: (),
kind: WatchKind::Write,
addr,
},
SimulationEvent::WatchRead(addr) => SingleThreadStopReason::Watch {
tid: (),
kind: WatchKind::Read,
addr,
},
SimulationEvent::Finalized(exit_code) => SingleThreadStopReason::Exited(exit_code as u8),
};

Ok(run_blocking::Event::TargetStopped(stop_reason))
}
}
}

fn on_interrupt(
_target: &mut ZmuTarget,
) -> Result<Option<SingleThreadStopReason<u32>>, <ZmuTarget as Target>::Error> {
// Because this emulator runs as part of the GDB stub loop, there isn't any
// special action that needs to be taken to interrupt the underlying target. It
// is implicitly paused whenever the stub isn't within the
// `wait_for_stop_reason` callback.
Ok(Some(SingleThreadStopReason::Signal(Signal::SIGINT)))
}
}
Loading
Loading