use crate::client::GdbClient;
use crate::display::{DisplayMode, Endianness};
use crate::error::Error;
use regex::Regex;
use std::str::FromStr;
#[derive(Debug, Clone)]
pub enum Command {
Connect(String, u16),
Disconnect,
Reconnect,
ReadMemory {
address: u64,
size: usize,
mode: DisplayMode,
endianness: Endianness,
},
WriteMemory {
address: u64,
data: Vec<u8>,
},
SetBreakpoint(u64),
RemoveBreakpoint(u64),
Continue,
Step,
ReadRegisters,
Help,
Quit,
Unknown(String),
}
impl FromStr for Command {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
let connect_re = Regex::new(r"^connect\s+([^\s:]+)(?::(\d+))?$").unwrap();
let read_mem_re = Regex::new(
r"^(?:readmem|rm)\s+(0x[0-9a-fA-F]+|\d+)\s+(\d+)(?:\s+([a-z0-9]+))?(?:\s+([a-z]+))?$",
)
.unwrap();
let write_mem_re = Regex::new(r"^(?:writemem|wm)\s+(0x[0-9a-fA-F]+|\d+)\s+(.+)$").unwrap();
let break_re = Regex::new(r"^(?:break|b)\s+(0x[0-9a-fA-F]+|\d+)$").unwrap();
let delete_break_re = Regex::new(r"^(?:delete|d)\s+(0x[0-9a-fA-F]+|\d+)$").unwrap();
if s.is_empty() {
return Ok(Command::Unknown(s.to_string()));
} else if connect_re.is_match(s) {
let caps = connect_re.captures(s).unwrap();
let host = caps.get(1).map_or("localhost", |m| m.as_str());
let port = caps
.get(2)
.map_or("55555", |m| m.as_str())
.parse::<u16>()
.unwrap_or(55555);
Ok(Command::Connect(host.to_string(), port))
} else if s == "disconnect" {
Ok(Command::Disconnect)
} else if s == "reconnect" {
Ok(Command::Reconnect)
} else if read_mem_re.is_match(s) {
let caps = read_mem_re.captures(s).unwrap();
let addr_str = caps.get(1).unwrap().as_str();
let address = if addr_str.starts_with("0x") {
u64::from_str_radix(&addr_str[2..], 16)
.map_err(|_| Error::ParseError("Invalid address".to_string()))?
} else {
addr_str
.parse::<u64>()
.map_err(|_| Error::ParseError("Invalid address".to_string()))?
};
let size = caps
.get(2)
.unwrap()
.as_str()
.parse::<usize>()
.map_err(|_| Error::ParseError("Invalid size".to_string()))?;
let mode = match caps.get(3).map(|m| m.as_str().to_lowercase()) {
Some(m) if m == "str" || m == "ascii" => DisplayMode::Ascii,
Some(m) if m == "int16" => DisplayMode::Int16,
Some(m) if m == "int32" => DisplayMode::Int32,
Some(m) if m == "int64" => DisplayMode::Int64,
_ => DisplayMode::Hex,
};
let endianness = match caps.get(4).map(|m| m.as_str().to_lowercase()) {
Some(e) if e == "big" => Endianness::Big,
_ => Endianness::Little, };
Ok(Command::ReadMemory {
address,
size,
mode,
endianness,
})
} else if write_mem_re.is_match(s) {
let caps = write_mem_re.captures(s).unwrap();
let addr_str = caps.get(1).unwrap().as_str();
let address = if addr_str.starts_with("0x") {
u64::from_str_radix(&addr_str[2..], 16)
.map_err(|_| Error::ParseError("Invalid address".to_string()))?
} else {
addr_str
.parse::<u64>()
.map_err(|_| Error::ParseError("Invalid address".to_string()))?
};
let data_str = caps.get(2).unwrap().as_str();
let data = if data_str.starts_with("0x") {
let hex_str = data_str[2..].replace(" ", "");
if hex_str.len() % 2 != 0 {
return Err(Error::ParseError(
"Hex data must have even length".to_string(),
));
}
let mut data = Vec::with_capacity(hex_str.len() / 2);
for i in (0..hex_str.len()).step_by(2) {
if i + 1 < hex_str.len() {
let hex_byte = &hex_str[i..i + 2];
data.push(
u8::from_str_radix(hex_byte, 16)
.map_err(|_| Error::ParseError("Invalid hex data".to_string()))?,
);
}
}
data
} else if data_str.starts_with("\"") && data_str.ends_with("\"") {
let str_data = &data_str[1..data_str.len() - 1];
str_data.as_bytes().to_vec()
} else {
let bytes: Result<Vec<u8>, _> = data_str
.split_whitespace()
.map(|s| s.parse::<u8>())
.collect();
match bytes {
Ok(b) => b,
Err(_) => return Err(Error::ParseError("Invalid data format".to_string())),
}
};
Ok(Command::WriteMemory { address, data })
} else if break_re.is_match(s) {
let caps = break_re.captures(s).unwrap();
let addr_str = caps.get(1).unwrap().as_str();
let address = if addr_str.starts_with("0x") {
u64::from_str_radix(&addr_str[2..], 16)
.map_err(|_| Error::ParseError("Invalid address".to_string()))?
} else {
addr_str
.parse::<u64>()
.map_err(|_| Error::ParseError("Invalid address".to_string()))?
};
Ok(Command::SetBreakpoint(address))
} else if delete_break_re.is_match(s) {
let caps = delete_break_re.captures(s).unwrap();
let addr_str = caps.get(1).unwrap().as_str();
let address = if addr_str.starts_with("0x") {
u64::from_str_radix(&addr_str[2..], 16)
.map_err(|_| Error::ParseError("Invalid address".to_string()))?
} else {
addr_str
.parse::<u64>()
.map_err(|_| Error::ParseError("Invalid address".to_string()))?
};
Ok(Command::RemoveBreakpoint(address))
} else if s == "continue" || s == "c" {
Ok(Command::Continue)
} else if s == "step" || s == "s" {
Ok(Command::Step)
} else if s == "registers" || s == "regs" || s == "info registers" {
Ok(Command::ReadRegisters)
} else if s == "help" || s == "h" || s == "?" {
Ok(Command::Help)
} else if s == "quit" || s == "exit" || s == "q" {
Ok(Command::Quit)
} else {
Ok(Command::Unknown(s.to_string()))
}
}
}
pub fn execute_command(
cmd: Command,
client: &mut GdbClient,
use_colors: bool,
) -> Result<(), Error> {
match cmd {
Command::Connect(host, port) => {
let addr = format!("{}:{}", host, port);
println!("Connecting to {}...", addr);
client.connect(addr)?;
println!("Connected to GDB server at {}:{}", host, port);
Ok(())
}
Command::Disconnect => {
client.disconnect()?;
println!("Disconnected from GDB server");
Ok(())
}
Command::Reconnect => {
println!("Reconnecting to GDB server...");
client.reconnect()?;
println!("Reconnected to GDB server");
Ok(())
}
Command::ReadMemory {
address,
size,
mode,
endianness,
} => {
let result = client.read_memory(address, size, mode, endianness)?;
println!("{}", crate::display::colorize_output(&result, use_colors));
Ok(())
}
Command::WriteMemory { address, data } => {
client.write_memory(address, &data)?;
println!("Memory written at address 0x{:x}", address);
Ok(())
}
Command::SetBreakpoint(addr) => {
client.set_breakpoint(addr)?;
println!("Breakpoint set at address 0x{:x}", addr);
Ok(())
}
Command::RemoveBreakpoint(addr) => {
client.remove_breakpoint(addr)?;
println!("Breakpoint removed from address 0x{:x}", addr);
Ok(())
}
Command::Continue => {
println!("Continuing execution...");
client.continue_execution()?;
Ok(())
}
Command::Step => {
println!("Stepping...");
client.step()?;
Ok(())
}
Command::ReadRegisters => {
let registers = client.read_registers()?;
println!(
"{}",
crate::display::colorize_output(®isters, use_colors)
);
Ok(())
}
Command::Help => {
print_help();
Ok(())
}
Command::Quit => {
println!("Exiting...");
if client.is_connected() {
client.disconnect()?;
}
std::process::exit(0);
}
Command::Unknown(cmd) => {
if cmd.is_empty() {
return Ok(());
}
println!("Unknown command: {}", cmd);
println!("Type 'help' for available commands");
Ok(())
}
}
}
fn print_help() {
use colored::*;
println!("{}", "Available commands:".bold().underline());
let cmd_width: usize = 24;
let format_cmd = |cmd: &str, args: &str, desc: &str| {
let main_cmd = cmd.bright_green().bold();
let args_fmt = args.yellow();
let full_cmd = format!(" {} {}", main_cmd, args_fmt);
let pad_len = cmd_width.saturating_sub(cmd.len() + args.len() + 3);
let padding = " ".repeat(pad_len);
println!("{}{}- {}", full_cmd, padding, desc);
};
format_cmd(
"connect",
"HOST[:PORT]",
"Connect to GDB server (default port 55555)",
);
format_cmd("disconnect", "", "Disconnect from server");
format_cmd("reconnect", "", "Reconnect to the last server");
format_cmd("readmem|rm", "ADDR SIZE [MODE] [ENDIAN]", "Read memory");
println!(
"{}MODE: {}, {}, {}, {}, {}",
" ".repeat(cmd_width + 2),
"hex".cyan().italic(),
"ascii/str".cyan().italic(),
"int16".cyan().italic(),
"int32".cyan().italic(),
"int64".cyan().italic()
);
println!(
"{}ENDIAN: {}, {}",
" ".repeat(cmd_width + 2),
"little".cyan().italic(),
"big".cyan().italic()
);
format_cmd(
"writemem|wm",
"ADDR DATA",
"Write memory (DATA as hex, string, or bytes)",
);
format_cmd("break|b", "ADDR", "Set breakpoint");
format_cmd("delete|d", "ADDR", "Remove breakpoint");
format_cmd("continue|c", "", "Continue execution");
format_cmd("step|s", "", "Single step");
format_cmd("registers|regs", "", "Read registers");
format_cmd("help|h|?", "", "Show this help");
format_cmd("quit|exit|q", "", "Exit the program");
println!("\n{}", "Examples:".bold().underline());
let example_width: usize = 28;
let format_example = |cmd: &str, desc: &str| {
let example = format!(" {}", cmd).bright_blue();
let pad_len = example_width.saturating_sub(cmd.len() + 2);
let padding = " ".repeat(pad_len);
println!("{}{}- {}", example, padding, desc);
};
format_example("readmem 0x1000 16", "Read 16 bytes at 0x1000 (hex display)");
format_example(
"rm 0x1000 100 str",
"Read 100 bytes at 0x1000 as ASCII string",
);
format_example(
"rm 0x1000 16 int32 big",
"Read 4 int32 values in big endian",
);
format_example("writemem 0x1000 \"Hello\"", "Write string at 0x1000");
format_example("wm 0x1000 0x01020304", "Write hex bytes at 0x1000");
}