use std::{
collections::HashMap,
io::{BufRead, BufReader, Write},
net::{TcpStream, ToSocketAddrs},
time::Duration,
};
pub fn read_reply<R: BufRead>(reader: &mut R) -> std::io::Result<String> {
let mut buf = Vec::new();
reader.read_until(b'#', &mut buf)?;
let mut cbuf = [0];
let _ = reader.read(&mut cbuf)?;
let _ = buf.write(&cbuf)?;
let _ = reader.read(&mut cbuf)?;
let _ = buf.write(&cbuf)?;
let reply = String::from_utf8_lossy(&buf).to_string();
Ok(reply)
}
pub fn gdb_read_memory_cmd(addr: u64, size: usize) -> Vec<u8> {
let payload = format!("m{addr:x},{size:x}");
let checksum: u8 = payload.bytes().fold(0u8, |acc, b| acc.wrapping_add(b));
format!("${payload}#{checksum:02x}").into_bytes()
}
pub fn gdb_parse_packet(input: &str) -> Option<Vec<u8>> {
const GDB_RLE_OFFSET: u8 = 29;
let data = input.strip_prefix('+').unwrap_or(input);
let data = data.strip_prefix('$')?;
let data = data.strip_prefix('O').unwrap_or(data);
let data = data.split('#').next()?;
let mut hex_str = String::new();
let mut chars = data.chars().peekable();
while let Some(c) = chars.next() {
if c == '*' {
let count_char = chars.next()?;
let repeat = (count_char as u8).saturating_sub(GDB_RLE_OFFSET) as usize;
let last = hex_str.chars().last()?;
for _ in 0..repeat {
hex_str.push(last);
}
} else {
hex_str.push(c);
}
}
hex::decode(&hex_str).ok()
}
pub fn gdb_read_register_cmd(reg_num: usize) -> Vec<u8> {
let payload = format!("p{reg_num:x}");
let checksum: u8 = payload.bytes().fold(0u8, |acc, b| acc.wrapping_add(b));
format!("${payload}#{checksum:02x}").into_bytes()
}
pub fn stub_read_memory_chunked<R: BufRead, W: Write>(
writer: &mut W,
reader: &mut R,
addr: u64,
total_size: usize,
chunk_size: usize,
) -> std::io::Result<Vec<u8>> {
let mut result = Vec::with_capacity(total_size);
let mut offset = 0;
while offset < total_size {
let size = std::cmp::min(chunk_size, total_size - offset);
writer.write_all(&gdb_read_memory_cmd(addr + offset as u64, size))?;
writer.flush()?;
let reply = read_reply(reader)?;
let parsed_reply = gdb_parse_packet(&reply).ok_or(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid data",
))?;
result.extend_from_slice(&parsed_reply);
offset += size;
}
Ok(result)
}
pub fn stub_read_register<R: BufRead, W: Write>(
writer: &mut W,
reader: &mut R,
reg_num: usize,
) -> std::io::Result<u64> {
let cmd = gdb_read_register_cmd(reg_num);
writer.write_all(&cmd)?;
writer.flush()?;
let reply = read_reply(reader)?;
let parsed_reply = gdb_parse_packet(&reply).ok_or(std::io::Error::other("invalid packet"))?;
let data = parsed_reply
.get(..8)
.and_then(|s| s.try_into().ok())
.ok_or(std::io::Error::other("expected 8 bytes"))?;
let reg_value = u64::from_le_bytes(data);
Ok(reg_value)
}
pub fn stub_fetch_debug_metadata<R: BufRead, W: Write>(
mut reader: &mut R,
writer: &mut W,
) -> Result<HashMap<String, String>, std::io::Error> {
writer.write_all(b"$qRcmd,6d65746164617461#9d")?;
let reply = read_reply(&mut reader)?;
let parsed = gdb_parse_packet(&reply).ok_or(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Can't parse metadata monitor command reply",
))?;
let reply = read_reply(&mut reader)?;
assert_eq!("$OK#9a", reply);
let parsed = String::from_utf8_lossy(&parsed).trim_end().to_string();
let parsed_map: HashMap<_, _> = parsed
.split(';')
.filter_map(|e| e.split_once('='))
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
Ok(parsed_map)
}
pub fn stub_send_continue_command<R: BufRead, W: Write>(
mut reader: &mut R,
writer: &mut W,
) -> Result<(), std::io::Error> {
writer.write_all(b"$vCont;c:p1.-1#0f")?;
let reply = read_reply(&mut reader)?;
assert_eq!("+$W00#b7", reply);
Ok(())
}
pub fn stub_connect<A: ToSocketAddrs>(
stub_addr: A,
mut retries: usize,
) -> Result<(BufReader<TcpStream>, TcpStream), std::io::Error> {
let (reader, writer) = loop {
match std::net::TcpStream::connect(&stub_addr) {
Err(e) => {
if retries == 0 {
return Err(e);
}
retries -= 1;
std::thread::sleep(Duration::from_millis(100));
continue;
}
Ok(stream) => break (BufReader::new(stream.try_clone()?), stream),
}
};
Ok((reader, writer))
}