probe_rs/gdb_server/stub.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
use crate::{CoreType, Session};
use parking_lot::FairMutex;
use std::net::{SocketAddr, ToSocketAddrs};
use std::time::Duration;
use itertools::Itertools;
use super::target;
const CONNECTION_STRING: &str = "127.0.0.1:1337";
/// Configuration for a single GDB endpoint
pub struct GdbInstanceConfiguration {
/// The core type that will be sent to GDB
pub core_type: CoreType,
/// The list of cores to expose. Each ID corresponds to the value passed to [Session::core()].
pub cores: Vec<usize>,
/// The list of [SocketAddr] addresses to bind to
pub socket_addrs: Vec<SocketAddr>,
}
impl GdbInstanceConfiguration {
/// Build a GDB configuration from a session object. All cores are included.
///
/// # Arguments
///
/// * session - the [Session] object to load target information from
/// * connection_string - The optional connection string to use.
/// If not specified `localhost:1337` is used.
/// Multiple instances are bound by adding an offset to the supplied port.
///
/// # Returns
/// Vec with the computed configuration
pub fn from_session(
session: &Session,
connection_string: Option<impl AsRef<str>>,
) -> Vec<Self> {
let connection_string = connection_string
.as_ref()
.map(|s| s.as_ref())
.unwrap_or(CONNECTION_STRING);
let addrs: Vec<SocketAddr> = connection_string.to_socket_addrs().unwrap().collect();
// Build a grouped list of cores by core type
// GDB only supports one architecture per stub so if we have two core types,
// such as ARMv7-a + ARMv7-m, we must create two stubs to connect to.
let groups = session
.target()
.cores
.iter()
.enumerate()
.map(|(i, core)| (core.core_type, i))
.into_group_map();
// Create a GDB instance for each group, starting at the specified connection and adding one to the port each time
// For example - consider two groups computed above and an input of localhost:1337.
// Group 1 will bind to localhost:1337
// Group 2 will bind to localhost:1338
groups
.into_iter()
.enumerate()
.map(|(i, (core_type, cores))| GdbInstanceConfiguration {
core_type,
cores,
socket_addrs: adjust_addrs(&addrs, i),
})
.collect()
}
}
/// Run a new GDB session.
///
/// # Arguments
///
/// * session - The [Session] to use, protected by a [FairMutex]
/// * instances - a list of [GdbInstanceConfiguration] objects used to configure the GDB session
///
/// # Remarks
///
/// A default configuration can be created by calling [GdbInstanceConfiguration::from_session()]
pub fn run<'a>(
session: &FairMutex<Session>,
instances: impl Iterator<Item = &'a GdbInstanceConfiguration>,
) -> anyhow::Result<()> {
// Turn our group list into GDB targets
let mut targets = instances
.map(|instance| {
target::RuntimeTarget::new(session, instance.cores.to_vec(), &instance.socket_addrs[..])
})
.collect::<Result<Vec<_>, _>>()?;
// Avoid getting stuck in an infinite loop if we have no targets
if targets.is_empty() {
return Ok(());
}
// Process every target in a loop
loop {
let mut wait_time = Duration::MAX;
for target in targets.iter_mut() {
wait_time = wait_time.min(target.process()?);
}
// Wait until we were asked to check again
std::thread::sleep(wait_time);
}
}
/// Given a list of socket addresses, adjust the port by `offset` and return
/// the new values
fn adjust_addrs(addrs: &[SocketAddr], offset: usize) -> Vec<SocketAddr> {
addrs
.iter()
.map(|addr| {
let mut new_addr = *addr;
new_addr.set_port(new_addr.port() + offset as u16);
new_addr
})
.collect()
}