use super::debugger::Debugger;
use crate::cmd::dap_server::debug_adapter::{dap::adapter::*, protocol::DapAdapter};
use anyhow::{Context, Result};
use probe_rs::{config::Registry, probe::list::Lister};
use serde::Deserialize;
use std::{
fs,
io::{self, Read},
net::TcpListener,
path::Path,
sync::mpsc,
time::{Duration, UNIX_EPOCH},
};
use time::UtcOffset;
#[derive(Copy, Clone, Debug, Deserialize, PartialEq)]
pub(crate) enum TargetSessionType {
AttachRequest,
LaunchRequest,
}
impl std::str::FromStr for TargetSessionType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match &s.to_ascii_lowercase()[..] {
"attach" => Ok(TargetSessionType::AttachRequest),
"launch" => Ok(TargetSessionType::LaunchRequest),
_ => Err(format!(
"'{s}' is not a valid target session type. Can be either 'attach' or 'launch']."
)),
}
}
}
pub async fn debug_tcp(
lister: &Lister,
addr: std::net::SocketAddr,
single_session: bool,
log_file: Option<&Path>,
timestamp_offset: UtcOffset,
) -> Result<()> {
let mut debugger = Debugger::new(timestamp_offset, log_file)?;
let old_hook = std::panic::take_hook();
let logger = debugger.debug_logger.clone();
std::panic::set_hook(Box::new(move |panic_info| {
_ = logger.flush();
old_hook(panic_info);
}));
loop {
let listener = TcpListener::bind(addr)?;
debugger
.debug_logger
.log_to_console(&format!("Listening for requests on port {}", addr.port()))?;
if !single_session {
debugger.debug_logger.flush()?;
}
listener.set_nonblocking(false)?;
match listener.accept() {
Ok((socket, addr)) => {
socket.set_nonblocking(true).with_context(|| {
format!("Failed to negotiate non-blocking socket with request from: {addr}")
})?;
debugger
.debug_logger
.log_to_console(&format!("Starting debug session from: {addr}"))?;
let reader = socket
.try_clone()
.context("Failed to establish a bi-directional Tcp connection.")?;
let writer = socket;
let dap_adapter = DapAdapter::new(reader, writer);
let mut debug_adapter = DebugAdapter::new(dap_adapter);
debugger.debug_logger.flush_to_dap(&mut debug_adapter)?;
let mut registry = Registry::from_builtin_families();
let end_message = match debugger
.debug_session(&mut registry, debug_adapter, lister)
.await
{
Err(error) => {
eprintln!("Session ended with error: {error:?}");
format!("Session ended: {error}")
}
Ok(()) => format!("Closing debug session from: {addr}"),
};
debugger.debug_logger.log_to_console(&end_message)?;
if single_session {
break;
}
}
Err(error) => {
tracing::error!(
"probe-rs-debugger failed to establish a socket connection. Reason: {:?}",
error
);
}
}
debugger.debug_logger.flush()?;
}
debugger
.debug_logger
.log_to_console("DAP Protocol server exiting")?;
debugger.debug_logger.flush()?;
Ok(())
}
pub(crate) struct ChannelReader {
rx: mpsc::Receiver<Vec<u8>>,
buf: Vec<u8>,
pos: usize,
}
impl ChannelReader {
pub(crate) fn new(rx: mpsc::Receiver<Vec<u8>>) -> Self {
Self {
rx,
buf: Vec::new(),
pos: 0,
}
}
}
impl Read for ChannelReader {
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
if self.pos < self.buf.len() {
let n = std::cmp::min(out.len(), self.buf.len() - self.pos);
out[..n].copy_from_slice(&self.buf[self.pos..self.pos + n]);
self.pos += n;
return Ok(n);
}
match self.rx.try_recv() {
Ok(chunk) => {
let n = std::cmp::min(out.len(), chunk.len());
out[..n].copy_from_slice(&chunk[..n]);
if n < chunk.len() {
self.buf = chunk;
self.pos = n;
} else {
self.buf.clear();
self.pos = 0;
}
Ok(n)
}
Err(mpsc::TryRecvError::Empty) => {
Err(io::Error::new(io::ErrorKind::WouldBlock, "no data yet"))
}
Err(mpsc::TryRecvError::Disconnected) => Ok(0),
}
}
}
pub async fn debug_stdio(
lister: &Lister,
log_file: Option<&Path>,
timestamp_offset: UtcOffset,
) -> Result<()> {
let mut debugger = Debugger::new(timestamp_offset, log_file)?;
let old_hook = std::panic::take_hook();
let logger = debugger.debug_logger.clone();
std::panic::set_hook(Box::new(move |panic_info| {
_ = logger.flush();
old_hook(panic_info);
}));
debugger
.debug_logger
.log_to_console("Starting stdio DAP session")?;
let (tx, rx) = mpsc::channel::<Vec<u8>>();
std::thread::spawn(move || {
let stdin = io::stdin();
let mut handle = stdin.lock();
let mut buf = [0u8; 4096];
loop {
match handle.read(&mut buf) {
Ok(0) | Err(_) => break,
Ok(n) => {
if tx.send(buf[..n].to_vec()).is_err() {
break;
}
}
}
}
});
let reader = ChannelReader::new(rx);
let writer = io::stdout();
let dap_adapter = DapAdapter::new(reader, writer);
let mut debug_adapter = DebugAdapter::new(dap_adapter);
debugger.debug_logger.flush_to_dap(&mut debug_adapter)?;
let mut registry = Registry::from_builtin_families();
match debugger
.debug_session(&mut registry, debug_adapter, lister)
.await
{
Err(error) => {
eprintln!("Session ended with error: {error:?}");
debugger
.debug_logger
.log_to_console(&format!("Session ended: {error}"))?;
}
Ok(()) => {
debugger
.debug_logger
.log_to_console("Closing stdio DAP session")?;
}
}
debugger
.debug_logger
.log_to_console("DAP Protocol server exiting")?;
debugger.debug_logger.flush()?;
Ok(())
}
pub(crate) fn get_file_timestamp(path_to_elf: &Path) -> Option<Duration> {
fs::metadata(path_to_elf)
.and_then(|metadata| metadata.modified())
.ok()
.and_then(|modified| modified.duration_since(UNIX_EPOCH).ok())
}