use std::io::{Read, Write};
use std::net::{TcpStream, ToSocketAddrs};
use std::time::Duration;
use crate::display::{DisplayMode, Endianness, format_memory_display};
use crate::error::Error;
const CONNECT_TIMEOUT_SECS: u64 = 5;
pub struct GdbClient {
stream: Option<TcpStream>,
server_address: String,
server_port: u16,
connected: bool,
}
impl GdbClient {
pub fn new() -> Self {
GdbClient {
stream: None,
server_address: String::new(),
server_port: 0,
connected: false,
}
}
pub fn connect<A: ToSocketAddrs>(&mut self, addr: A) -> Result<(), Error> {
match TcpStream::connect_timeout(
&addr
.to_socket_addrs()?
.next()
.ok_or_else(|| Error::ConnectionError("Invalid address".to_string()))?,
Duration::from_secs(CONNECT_TIMEOUT_SECS),
) {
Ok(stream) => {
stream.set_read_timeout(Some(Duration::from_secs(2)))?;
if let Ok(addr) = stream.peer_addr() {
self.server_address = addr.ip().to_string();
self.server_port = addr.port();
}
self.stream = Some(stream);
self.connected = true;
self.send_packet(b"qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+")?;
Ok(())
}
Err(e) => Err(Error::ConnectionError(format!("Failed to connect: {}", e))),
}
}
pub fn disconnect(&mut self) -> Result<(), Error> {
if !self.connected {
return Err(Error::ConnectionError("Not connected".to_string()));
}
self.stream = None;
self.connected = false;
Ok(())
}
pub fn reconnect(&mut self) -> Result<(), Error> {
if self.connected {
self.disconnect()?;
}
let addr = format!("{}:{}", self.server_address, self.server_port);
self.connect(addr)
}
pub fn is_connected(&self) -> bool {
self.connected
}
fn send_packet(&mut self, data: &[u8]) -> Result<Vec<u8>, Error> {
if !self.connected {
return Err(Error::ConnectionError("Not connected".to_string()));
}
let stream = self
.stream
.as_mut()
.ok_or_else(|| Error::ConnectionError("Stream not available".to_string()))?;
let mut packet = Vec::new();
packet.push(b'$');
packet.extend_from_slice(data);
let checksum = data.iter().fold(0u8, |acc, &x| acc.wrapping_add(x));
packet.push(b'#');
packet.push(hex_digit((checksum >> 4) & 0xf));
packet.push(hex_digit(checksum & 0xf));
stream.write_all(&packet)?;
stream.flush()?;
self.read_packet()
}
fn read_packet(&mut self) -> Result<Vec<u8>, Error> {
if !self.connected {
return Err(Error::ConnectionError("Not connected".to_string()));
}
let stream = self
.stream
.as_mut()
.ok_or_else(|| Error::ConnectionError("Stream not available".to_string()))?;
let mut buffer = [0u8; 1];
let mut response = Vec::new();
let in_packet;
loop {
stream.read_exact(&mut buffer)?;
match buffer[0] {
b'$' => {
in_packet = true;
break;
}
_ => continue,
}
}
loop {
stream.read_exact(&mut buffer)?;
match buffer[0] {
b'#' => break,
_ if in_packet => response.push(buffer[0]),
_ => continue,
}
}
let mut checksum_buf = [0u8; 2];
stream.read_exact(&mut checksum_buf)?;
stream.write_all(b"+")?;
stream.flush()?;
Ok(response)
}
pub fn read_memory(
&mut self,
address: u64,
size: usize,
display_mode: DisplayMode,
endianness: Endianness,
) -> Result<String, Error> {
if !self.connected {
return Err(Error::ConnectionError("Not connected".to_string()));
}
let packet = format!("m {:x},{:x}", address, size);
let response = self.send_packet(packet.as_bytes())?;
if response.len() > 0 && response[0] == b'E' {
let error_code = String::from_utf8_lossy(&response[1..3]);
return Err(Error::ProtocolError(format!(
"GDB returned error: {}",
error_code
)));
}
let mut binary_data = Vec::with_capacity(response.len() / 2);
let hex_string = String::from_utf8_lossy(&response);
for i in (0..hex_string.len()).step_by(2) {
if i + 1 < hex_string.len() {
let hex_byte = &hex_string[i..i + 2];
if let Ok(byte) = u8::from_str_radix(hex_byte, 16) {
binary_data.push(byte);
} else {
return Err(Error::ParseError(format!("Invalid hex data: {}", hex_byte)));
}
}
}
Ok(format_memory_display(
&binary_data,
address,
display_mode,
endianness,
))
}
pub fn set_breakpoint(&mut self, address: u64) -> Result<u64, Error> {
if !self.connected {
return Err(Error::ConnectionError("Not connected".to_string()));
}
let packet = format!("Z0,{:x},1", address);
let response = self.send_packet(packet.as_bytes())?;
if response == b"OK" {
Ok(address)
} else if response.len() > 0 && response[0] == b'E' {
let error_code = String::from_utf8_lossy(&response[1..3]);
Err(Error::ProtocolError(format!(
"Failed to set breakpoint: {}",
error_code
)))
} else {
Err(Error::ProtocolError(
"Unknown response for breakpoint".to_string(),
))
}
}
pub fn remove_breakpoint(&mut self, address: u64) -> Result<(), Error> {
if !self.connected {
return Err(Error::ConnectionError("Not connected".to_string()));
}
let packet = format!("z0,{:x},1", address);
let response = self.send_packet(packet.as_bytes())?;
if response == b"OK" {
Ok(())
} else if response.len() > 0 && response[0] == b'E' {
let error_code = String::from_utf8_lossy(&response[1..3]);
Err(Error::ProtocolError(format!(
"Failed to remove breakpoint: {}",
error_code
)))
} else {
Err(Error::ProtocolError(
"Unknown response for removing breakpoint".to_string(),
))
}
}
pub fn continue_execution(&mut self) -> Result<(), Error> {
if !self.connected {
return Err(Error::ConnectionError("Not connected".to_string()));
}
let response = self.send_packet(b"c")?;
if response.len() > 0 && response[0] == b'T' {
let signal = String::from_utf8_lossy(&response[1..3]);
println!("Program stopped (signal {})", signal);
Ok(())
} else if response.len() > 0 && response[0] == b'W' {
let exit_code = String::from_utf8_lossy(&response[1..3]);
println!("Program exited with code {}", exit_code);
Ok(())
} else {
Err(Error::ProtocolError(format!(
"Unexpected response: {:?}",
String::from_utf8_lossy(&response)
)))
}
}
pub fn step(&mut self) -> Result<(), Error> {
if !self.connected {
return Err(Error::ConnectionError("Not connected".to_string()));
}
let response = self.send_packet(b"s")?;
if response.len() > 0 && response[0] == b'T' {
let signal = String::from_utf8_lossy(&response[1..3]);
println!("Program stopped after step (signal {})", signal);
Ok(())
} else {
Err(Error::ProtocolError(format!(
"Unexpected response: {:?}",
String::from_utf8_lossy(&response)
)))
}
}
pub fn read_registers(&mut self) -> Result<String, Error> {
if !self.connected {
return Err(Error::ConnectionError("Not connected".to_string()));
}
let response = self.send_packet(b"g")?;
if response.len() > 0 && response[0] == b'E' {
let error_code = String::from_utf8_lossy(&response[1..3]);
return Err(Error::ProtocolError(format!(
"Failed to read registers: {}",
error_code
)));
}
let mut result = String::new();
let hex_data = String::from_utf8_lossy(&response);
const REG_SIZE: usize = 16; let reg_names = [
"rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp", "r8", "r9", "r10", "r11",
"r12", "r13", "r14", "r15", "rip", "eflags", "cs", "ss", "ds", "es", "fs", "gs",
];
for (i, chunk) in hex_data.as_bytes().chunks(REG_SIZE).enumerate() {
if i >= reg_names.len() {
break;
}
let reg_value = String::from_utf8_lossy(chunk);
result.push_str(&format!("{:<8}: 0x{}\n", reg_names[i], reg_value));
}
Ok(result)
}
pub fn write_memory(&mut self, address: u64, data: &[u8]) -> Result<(), Error> {
if !self.connected {
return Err(Error::ConnectionError("Not connected".to_string()));
}
let mut hex_data = String::new();
for byte in data {
hex_data.push_str(&format!("{:02x}", byte));
}
let packet = format!("M {:x},{:x}:{}", address, data.len(), hex_data);
let response = self.send_packet(packet.as_bytes())?;
if response == b"OK" {
Ok(())
} else if response.len() > 0 && response[0] == b'E' {
let error_code = String::from_utf8_lossy(&response[1..3]);
Err(Error::ProtocolError(format!(
"Failed to write memory: {}",
error_code
)))
} else {
Err(Error::ProtocolError(
"Unknown response for memory write".to_string(),
))
}
}
}
fn hex_digit(digit: u8) -> u8 {
match digit {
0..=9 => b'0' + digit,
10..=15 => b'a' + (digit - 10),
_ => b'0',
}
}