Skip to main content
Glama

MCP Server GDB

commands.rs17.8 kB
use std::ffi::OsString; use std::fmt; use std::io::Error; use std::path::Path; use std::str::FromStr; use serde::{Deserialize, Serialize, de}; use tokio::io::AsyncWriteExt; use tracing::info; use crate::models::PrintValue; #[derive(Debug, Clone, Default)] pub struct MiCommand { pub operation: &'static str, pub options: Option<Vec<OsString>>, pub parameters: Option<Vec<OsString>>, } pub enum DisassembleMode { DisassemblyOnly = 0, DisassemblyWithRawOpcodes = 2, MixedSourceAndDisassembly = 1, /* deprecated and 4 would be preferred, but might not be * available in older gdb(mi) versions */ MixedSourceAndDisassemblyWithRawOpcodes = 3, /* deprecated and 5 would be preferred, same * as above */ } pub enum WatchMode { Read, Write, Access, } /// Register format pub enum RegisterFormat { Binary, Hex, Decimal, Octal, Raw, Natural, } impl FromStr for RegisterFormat { type Err = String; fn from_str(s: &str) -> Result<Self, Self::Err> { Ok(match s { "b" => RegisterFormat::Binary, "x" => RegisterFormat::Hex, "d" => RegisterFormat::Decimal, "o" => RegisterFormat::Octal, "r" => RegisterFormat::Raw, "N" => RegisterFormat::Natural, _ => return Err(format!("Invalid register format: {}", s)), }) } } impl fmt::Display for RegisterFormat { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { RegisterFormat::Binary => write!(f, "b"), RegisterFormat::Hex => write!(f, "x"), RegisterFormat::Decimal => write!(f, "d"), RegisterFormat::Octal => write!(f, "o"), RegisterFormat::Raw => write!(f, "r"), RegisterFormat::Natural => write!(f, "N"), } } } pub enum BreakPointLocation<'a> { Address(usize), Function(&'a Path, &'a str), Line(&'a Path, usize), } #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize)] pub struct BreakPointNumber { pub major: usize, pub minor: Option<usize>, } impl<'de> Deserialize<'de> for BreakPointNumber { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de>, { let s: String = serde::Deserialize::deserialize(deserializer)?; if let Some(dot_pos) = s.find('.') { Ok(BreakPointNumber { major: s[..dot_pos].parse::<usize>().map_err(de::Error::custom)?, minor: Some(s[dot_pos + 1..].parse::<usize>().map_err(de::Error::custom)?), }) } else { Ok(BreakPointNumber { major: s.parse::<usize>().map_err(de::Error::custom)?, minor: None, }) } } } impl fmt::Display for BreakPointNumber { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let Some(minor) = self.minor { write!(f, "{}.{}", self.major, minor) } else { write!(f, "{}", self.major) } } } fn escape_command(input: &str) -> String { let mut output = '\"'.to_string(); for c in input.chars() { match c { '\\' => output.push_str("\\\\"), '\"' => output.push_str("\\\""), '\r' => output.push_str("\\\r"), '\n' => output.push_str("\\\n"), other => output.push(other), } } output.push('\"'); output } impl MiCommand { pub async fn write_interpreter_string<S: AsyncWriteExt + Unpin>( &self, sink: &mut S, token: u64, ) -> Result<(), Error> { // use std::os::unix::ffi::OsStrExt; let mut command = OsString::new(); if !self.operation.is_empty() { command.push(format!("{}-{}", token, self.operation)); } if let Some(options) = &self.options { for option in options { command.push(" "); command.push(option); } } if let Some(parameters) = &self.parameters { if self.options.is_some() { command.push(" --"); } for parameter in parameters { command.push(" "); command.push(parameter); } } command.push("\n"); info!("Writing GDB command: {}", String::from_utf8_lossy(command.as_encoded_bytes())); sink.write_all(command.as_encoded_bytes()).await?; Ok(()) } pub fn interpreter_exec<S1: Into<OsString>, S2: Into<OsString>>( interpreter: S1, command: S2, ) -> MiCommand { MiCommand { operation: "interpreter-exec", options: Some(vec![interpreter.into(), command.into()]), parameters: None, } } pub fn cli_exec(command: &str) -> MiCommand { Self::interpreter_exec("console".to_owned(), escape_command(command)) } pub fn data_disassemble_file<P: AsRef<Path>>( file: P, linenum: usize, lines: Option<usize>, mode: DisassembleMode, ) -> MiCommand { MiCommand { operation: "data-disassemble", options: Some(vec![ OsString::from("-f"), OsString::from(file.as_ref()), OsString::from("-l"), OsString::from(linenum.to_string()), OsString::from("-n"), OsString::from(lines.map(|l| l as isize).unwrap_or(-1).to_string()), ]), parameters: Some(vec![OsString::from((mode as u8).to_string())]), } } pub fn data_disassemble_address( start_addr: usize, end_addr: usize, mode: DisassembleMode, ) -> MiCommand { MiCommand { operation: "data-disassemble", options: Some(vec![ OsString::from("-s"), OsString::from(start_addr.to_string()), OsString::from("-e"), OsString::from(end_addr.to_string()), ]), parameters: Some(vec![OsString::from((mode as u8).to_string())]), } } pub fn data_evaluate_expression(expression: String) -> MiCommand { MiCommand { operation: "data-evaluate-expression", options: Some(vec![OsString::from(format!("\"{}\"", expression))]), /* TODO: maybe we need to quote existing " in expression. Is this even possible? */ parameters: None, } } pub fn insert_breakpoint(location: BreakPointLocation) -> MiCommand { MiCommand { operation: "break-insert", options: match location { BreakPointLocation::Address(addr) => { Some(vec![OsString::from(format!("*0x{:x}", addr))]) } BreakPointLocation::Function(path, func_name) => { let mut ret = OsString::from(path); ret.push(":"); ret.push(func_name); Some(vec![ret]) // Not available in old gdb(mi) versions //vec![ // OsString::from("--source"), // OsString::from(path), // OsString::from("--function"), // OsString::from(func_name), //] } BreakPointLocation::Line(path, line_number) => { let mut ret = OsString::from(path); ret.push(":"); ret.push(line_number.to_string()); Some(vec![ret]) // Not available in old gdb(mi) versions //vec![ //OsString::from("--source"), //OsString::from(path), //OsString::from("--line"), //OsString::from(format!("{}", line_number)), //], } }, parameters: None, } } pub fn delete_breakpoints(breakpoint_numbers: Vec<BreakPointNumber>) -> MiCommand { //GDB is broken: see http://sourceware-org.1504.n7.nabble.com/Bug-breakpoints-20133-New-unable-to-delete-a-sub-breakpoint-td396197.html let mut options = breakpoint_numbers; options.sort_by_key(|n| n.major); options.dedup(); MiCommand { operation: "break-delete", options: Some(options.iter().map(|n| n.to_string().into()).collect()), parameters: None, } } pub fn breakpoints_list() -> MiCommand { MiCommand { operation: "break-list", ..Default::default() } } pub fn insert_watchpoint(expression: &str, mode: WatchMode) -> MiCommand { let options = match mode { WatchMode::Write => None, WatchMode::Read => Some(vec!["-r".into()]), WatchMode::Access => Some(vec!["-a".into()]), }; MiCommand { operation: "break-watch", options, parameters: Some(vec![expression.into()]) } } pub fn environment_pwd() -> MiCommand { MiCommand { operation: "environment-pwd", ..Default::default() } } // Be aware: This does not seem to always interrupt execution. // Use gdb.interrupt_execution instead. pub fn exec_interrupt() -> MiCommand { MiCommand { operation: "exec-interrupt", ..Default::default() } } pub fn exec_run() -> MiCommand { MiCommand { operation: "exec-run", ..Default::default() } } pub fn exec_continue() -> MiCommand { MiCommand { operation: "exec-continue", ..Default::default() } } pub fn exec_step() -> MiCommand { MiCommand { operation: "exec-step", ..Default::default() } } pub fn exec_next() -> MiCommand { MiCommand { operation: "exec-next", ..Default::default() } } // Warning: This cannot be used to pass special characters like \n to gdb // because (unlike it is said in the spec) there is apparently no way to // pass \n unescaped to gdb, and for "exec-arguments" gdb somehow does not // unescape these chars... pub fn exec_arguments(args: Vec<OsString>) -> MiCommand { MiCommand { operation: "exec-arguments", options: Some(args), parameters: None } } pub fn exit() -> MiCommand { MiCommand { operation: "gdb-exit", ..Default::default() } } pub fn select_frame(frame_number: u64) -> MiCommand { MiCommand { operation: "stack-select-frame", options: Some(vec![frame_number.to_string().into()]), parameters: None, } } pub fn stack_info_frame(frame_number: Option<u64>) -> MiCommand { MiCommand { operation: "stack-info-frame", options: if let Some(frame_number) = frame_number { Some(vec![frame_number.to_string().into()]) } else { None }, parameters: None, } } pub fn stack_info_depth() -> MiCommand { MiCommand { operation: "stack-info-depth", ..Default::default() } } pub fn stack_list_variables( thread_number: Option<usize>, frame_number: Option<usize>, print_values: Option<PrintValue>, ) -> MiCommand { let mut parameters = vec![]; if let Some(thread_number) = thread_number { parameters.push("--thread".into()); parameters.push(thread_number.to_string().into()); } if let Some(frame_number) = frame_number { parameters.push("--frame".into()); parameters.push(frame_number.to_string().into()); } if let Some(values) = print_values { parameters.push(values.to_string().into()); } else { parameters.push("--simple-values".into()); } MiCommand { operation: "stack-list-variables", options: None, parameters: Some(parameters) } } pub fn stack_list_frames(low_frame: Option<usize>, high_frame: Option<usize>) -> MiCommand { let options = if let Some(low) = low_frame { if let Some(high) = high_frame { if low > high { Some(vec![high.to_string().into(), low.to_string().into()]) } else { Some(vec![low.to_string().into(), high.to_string().into()]) } } else { // large enough number to include all frames, only existing frames will be shown Some(vec![low.to_string().into(), String::from("99999").into()]) } } else { if let Some(high) = high_frame { Some(vec![String::from("0").into(), high.to_string().into()]) } else { None } }; MiCommand { operation: "stack-list-frames", options, parameters: None } } pub fn thread_info(thread_id: Option<u64>) -> MiCommand { MiCommand { operation: "thread-info", options: if let Some(id) = thread_id { Some(vec![id.to_string().into()]) } else { None }, parameters: None, } } pub fn file_exec_and_symbols(file: &Path) -> MiCommand { MiCommand { operation: "file-exec-and-symbols", options: Some(vec![file.into()]), parameters: None, } } pub fn file_symbol_file(file: Option<&Path>) -> MiCommand { MiCommand { operation: "file-symbol-file", options: if let Some(file) = file { Some(vec![file.into()]) } else { None }, parameters: None, } } pub fn list_thread_groups(list_all_available: bool, thread_group_ids: &[u32]) -> MiCommand { MiCommand { operation: "list-thread-groups", options: if list_all_available { Some(vec![OsString::from("--available")]) } else { None }, parameters: Some(thread_group_ids.iter().map(|id| id.to_string().into()).collect()), } } pub fn var_create( name: Option<OsString>, /* none: generate name */ expression: &str, frame_addr: Option<u64>, /* none: current frame */ ) -> MiCommand { MiCommand { operation: "var-create", options: None, parameters: Some(vec![ name.unwrap_or_else(|| "\"-\"".into()), frame_addr.map(|s| s.to_string()).unwrap_or_else(|| "\"*\"".to_string()).into(), escape_command(expression).into(), ]), } } pub fn var_delete(name: impl Into<OsString>, delete_children: bool) -> MiCommand { let mut parameters = vec![]; if delete_children { parameters.push("-c".into()); } parameters.push(name.into()); MiCommand { operation: "var-delete", options: None, parameters: Some(parameters) } } pub fn var_list_children( name: impl Into<OsString>, print_values: bool, from_to: Option<std::ops::Range<u64>>, ) -> MiCommand { let mut cmd = MiCommand { operation: "var-list-children", options: None, parameters: Some(vec![ if print_values { "--all-values" } else { "--no-values" }.into(), name.into(), ]), }; if let (Some(from_to), Some(params)) = (from_to, &mut cmd.parameters) { params.push(OsString::from(from_to.start.to_string())); params.push(OsString::from(from_to.end.to_string())); } cmd } pub fn data_list_register_names(reg_list: Option<Vec<usize>>) -> MiCommand { MiCommand { operation: "data-list-register-names", options: if let Some(list) = reg_list { Some(list.iter().map(|x| x.to_string().into()).collect()) } else { None }, parameters: None, } } /// fmt: "x": hex, "d": decimal, "o": octal, "r": raw, "N": natural pub fn data_list_register_values( fmt: RegisterFormat, reg_list: Option<Vec<usize>>, ) -> MiCommand { MiCommand { operation: "data-list-register-values", options: if let Some(list) = &reg_list { Some( vec![fmt.to_string().into()] .into_iter() .chain(list.iter().map(|x| x.to_string().into())) .collect(), ) } else { Some(vec![fmt.to_string().into()]) }, parameters: None, } } /// List registers that have changed since the last stop. #[allow(dead_code)] pub fn data_list_changed_registers() -> MiCommand { MiCommand { operation: "data-list-changed-registers", ..Default::default() } } /// Read all accessible memory regions in the specified range pub fn data_read_memory_bytes( offset: Option<isize>, address: String, count: usize, ) -> MiCommand { let mut options: Vec<OsString> = if let Some(offset) = offset { vec![format!("-o {}", offset).into()] } else { vec![] }; options.push(address.into()); options.push(count.to_string().into()); MiCommand { operation: "data-read-memory-bytes", options: Some(options), parameters: None } } /// Empty command, used for testing purposes pub fn empty() -> MiCommand { MiCommand { operation: "", ..Default::default() } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pansila/mcp_server_gdb'

If you have feedback or need assistance with the MCP directory API, please join our Discord server