Skip to main content
Glama

MCP Server GDB

gdb.rs14.5 kB
use std::collections::HashMap; use std::ffi::OsString; use std::path::{Path, PathBuf}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tokio::sync::{Mutex, mpsc}; use tokio::task::JoinHandle; use tracing::{debug, error, warn}; use uuid::Uuid; use crate::TRANSPORT; use crate::config::Config; use crate::error::{AppError, AppResult}; use crate::mi::commands::{BreakPointLocation, BreakPointNumber, MiCommand, RegisterFormat}; use crate::mi::output::{OutOfBandRecord, ResultClass, ResultRecord}; use crate::mi::{GDB, GDBBuilder}; use crate::models::{ BreakPoint, GDBSession, GDBSessionStatus, Memory, Register, StackFrame, Variable, }; /// GDB Session Manager #[derive(Default)] pub struct GDBManager { /// Configuration config: Config, /// Session mapping table sessions: Mutex<HashMap<String, GDBSessionHandle>>, } /// GDB Session Handle struct GDBSessionHandle { /// Session information info: GDBSession, /// GDB instance gdb: GDB, /// OOB handle oob_handle: JoinHandle<()>, } impl GDBManager { /// Create a new GDB session pub async fn create_session( &self, program: Option<PathBuf>, nh: Option<bool>, nx: Option<bool>, quiet: Option<bool>, cd: Option<PathBuf>, bps: Option<u32>, symbol_file: Option<PathBuf>, core_file: Option<PathBuf>, proc_id: Option<u32>, command: Option<PathBuf>, source_dir: Option<PathBuf>, args: Option<Vec<OsString>>, tty: Option<PathBuf>, gdb_path: Option<PathBuf>, ) -> AppResult<String> { // Generate unique session ID let session_id = Uuid::new_v4().to_string(); let gdb_builder = GDBBuilder { gdb_path: gdb_path.unwrap_or_else(|| PathBuf::from("gdb")), opt_nh: nh.unwrap_or(false), opt_nx: nx.unwrap_or(false), opt_quiet: quiet.unwrap_or(false), opt_cd: cd, opt_bps: bps, opt_symbol_file: symbol_file, opt_core_file: core_file, opt_proc_id: proc_id, opt_command: command, opt_source_dir: source_dir, opt_args: args.unwrap_or(vec![]), opt_program: program, opt_tty: tty, }; let (oob_src, mut oob_sink) = mpsc::channel(100); let gdb = gdb_builder.try_spawn(oob_src)?; let oob_handle = tokio::spawn(async move { loop { match oob_sink.recv().await { Some(record) => match record { OutOfBandRecord::AsyncRecord { results, .. } => { let transport = TRANSPORT.lock().await; if let Some(transport) = transport.as_ref() { if let Err(e) = transport .send_notification("create_session", Some(results)) .await { error!("Failed to send ping to session: {:?}", e); } } else { warn!("Sink Channel closed"); break; } } OutOfBandRecord::StreamRecord { data, .. } => { debug!("StreamRecord: {:?}", data); } }, None => { debug!("Source Channel closed"); break; } } } }); // Create session information let session = GDBSession { id: session_id.clone(), status: GDBSessionStatus::Created, created_at: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(), }; // Store session let handle = GDBSessionHandle { info: session, gdb, oob_handle }; self.sessions.lock().await.insert(session_id.clone(), handle); // Send empty command to GDB to flush the welcome messages let _ = self.send_command(&session_id, &MiCommand::empty()).await?; Ok(session_id) } /// Get all sessions pub async fn get_all_sessions(&self) -> AppResult<Vec<GDBSession>> { let sessions = self.sessions.lock().await; let result = sessions.values().map(|handle| handle.info.clone()).collect(); Ok(result) } /// Get specific session pub async fn get_session(&self, session_id: &str) -> AppResult<GDBSession> { let sessions = self.sessions.lock().await; let handle = sessions .get(session_id) .ok_or_else(|| AppError::NotFound(format!("Session {} does not exist", session_id)))?; Ok(handle.info.clone()) } /// Close session pub async fn close_session(&self, session_id: &str) -> AppResult<()> { let _ = match self.send_command_with_timeout(session_id, &MiCommand::exit()).await { Ok(result) => Some(result), Err(e) => { warn!("GDB exit command timed out, forcing process termination: {}", e.to_string()); // Ignore timeout error, continue to force terminate the process None } }; let mut sessions = self.sessions.lock().await; let handle = sessions.remove(session_id); if let Some(handle) = handle { handle.oob_handle.abort(); // Terminate process let mut process = handle.gdb.process.lock().await; let _ = process.kill().await; // Ignore possible errors, process may have already terminated } Ok(()) } /// Send GDB command pub async fn send_command( &self, session_id: &str, command: &MiCommand, ) -> AppResult<ResultRecord> { let mut sessions = self.sessions.lock().await; let handle = sessions .get_mut(session_id) .ok_or_else(|| AppError::NotFound(format!("Session {} does not exist", session_id)))?; let record = handle.gdb.execute(command).await?; let output = record.results.to_string(); debug!("GDB output: {}", output); Ok(record) } /// Send GDB command with timeout async fn send_command_with_timeout( &self, session_id: &str, command: &MiCommand, ) -> AppResult<ResultRecord> { let command_timeout = self.config.command_timeout; match tokio::time::timeout( Duration::from_secs(command_timeout), self.send_command(session_id, command), ) .await { Ok(Ok(result)) => Ok(result), Ok(Err(e)) => Err(e), Err(_) => Err(AppError::GDBTimeout), } } /// Start debugging pub async fn start_debugging(&self, session_id: &str) -> AppResult<String> { let response = self.send_command_with_timeout(session_id, &MiCommand::exec_run()).await?; // Update session status let mut sessions = self.sessions.lock().await; if let Some(handle) = sessions.get_mut(session_id) { handle.info.status = GDBSessionStatus::Running; } Ok(response.results.to_string()) } /// Stop debugging pub async fn stop_debugging(&self, session_id: &str) -> AppResult<String> { let response = self.send_command_with_timeout(session_id, &MiCommand::exec_interrupt()).await?; // Update session status let mut sessions = self.sessions.lock().await; if let Some(handle) = sessions.get_mut(session_id) { handle.info.status = GDBSessionStatus::Stopped; } Ok(response.results.to_string()) } /// Get breakpoint list pub async fn get_breakpoints(&self, session_id: &str) -> AppResult<Vec<BreakPoint>> { let response = self.send_command_with_timeout(session_id, &MiCommand::breakpoints_list()).await?; let table = response .results .get("BreakpointTable") .ok_or(AppError::NotFound("BreakpointTable not found".to_string()))?; let body = table.get("body").ok_or(AppError::NotFound("body not found".to_string()))?; Ok(serde_json::from_value(body.to_owned())?) } /// Set breakpoint pub async fn set_breakpoint( &self, session_id: &str, file: &Path, line: usize, ) -> AppResult<BreakPoint> { let command = MiCommand::insert_breakpoint(BreakPointLocation::Line(file, line)); let response = self.send_command_with_timeout(session_id, &command).await?; Ok(serde_json::from_value( response .results .get("bkpt") .ok_or(AppError::NotFound("bkpt not found in the result".to_string()))? .to_owned(), )?) } /// Delete breakpoint pub async fn delete_breakpoint( &self, session_id: &str, breakpoints: Vec<String>, ) -> AppResult<()> { let command = MiCommand::delete_breakpoints( breakpoints .iter() .map(|num| serde_json::from_str::<BreakPointNumber>(num)) .collect::<Result<Vec<_>, _>>()?, ); let response = self.send_command_with_timeout(session_id, &command).await?; if response.class != ResultClass::Done { return Err(AppError::GDBError(response.results.to_string())); } Ok(()) } /// Get stack frames pub async fn get_stack_frames(&self, session_id: &str) -> AppResult<Vec<StackFrame>> { let command = MiCommand::stack_list_frames(None, None); let response = self.send_command_with_timeout(session_id, &command).await?; Ok(serde_json::from_value( response .results .get("stack") .ok_or(AppError::NotFound("stack not found".to_string()))? .to_owned(), )?) } /// Get local variables pub async fn get_local_variables( &self, session_id: &str, frame_id: Option<usize>, ) -> AppResult<Vec<Variable>> { let command = MiCommand::stack_list_variables(None, frame_id, None); let response = self.send_command_with_timeout(session_id, &command).await?; Ok(serde_json::from_value( response .results .get("variables") .ok_or(AppError::NotFound("expect variables in result".to_string()))? .to_owned(), )?) } /// Get registers pub async fn get_registers( &self, session_id: &str, reg_list: Option<Vec<String>>, ) -> AppResult<Vec<Register>> { let reg_list = reg_list .map(|s| s.iter().map(|num| num.parse::<usize>()).collect::<Result<Vec<_>, _>>()) .transpose()?; let command = MiCommand::data_list_register_names(reg_list.clone()); let response = self.send_command_with_timeout(session_id, &command).await?; let names: Vec<String> = serde_json::from_value( response .results .get("register-names") .ok_or(AppError::NotFound("register-names not found".to_string()))? .to_owned(), )?; let command = MiCommand::data_list_register_values(RegisterFormat::Hex, reg_list); let response = self.send_command_with_timeout(session_id, &command).await?; let registers: Vec<Register> = serde_json::from_value( response .results .get("register-values") .ok_or(AppError::NotFound("expect register-values".to_string()))? .to_owned(), )?; Ok(registers .into_iter() .map(|mut r| { r.name = names.get(r.number).cloned(); r }) .collect::<_>()) } /// Get register names pub async fn get_register_names( &self, session_id: &str, reg_list: Option<Vec<String>>, ) -> AppResult<Vec<Register>> { let reg_list = reg_list .map(|s| s.iter().map(|num| num.parse::<usize>()).collect::<Result<Vec<_>, _>>()) .transpose()?; let command = MiCommand::data_list_register_names(reg_list); let response = self.send_command_with_timeout(session_id, &command).await?; Ok(serde_json::from_value( response .results .get("register-values") .ok_or(AppError::NotFound("expect register-values".to_string()))? .to_owned(), )?) } /// Read memory contents pub async fn read_memory( &self, session_id: &str, offset: Option<isize>, address: String, count: usize, ) -> AppResult<Vec<Memory>> { let command = MiCommand::data_read_memory_bytes(offset, address, count); let response = self.send_command_with_timeout(session_id, &command).await?; Ok(serde_json::from_value( response .results .get("memory") .ok_or(AppError::NotFound("expect memory".to_string()))? .to_owned(), )?) } /// Continue execution pub async fn continue_execution(&self, session_id: &str) -> AppResult<String> { let response = self.send_command_with_timeout(session_id, &MiCommand::exec_continue()).await?; // Update session status let mut sessions = self.sessions.lock().await; if let Some(handle) = sessions.get_mut(session_id) { handle.info.status = GDBSessionStatus::Running; } Ok(response.results.to_string()) } /// Step execution pub async fn step_execution(&self, session_id: &str) -> AppResult<String> { let response = self.send_command_with_timeout(session_id, &MiCommand::exec_step()).await?; Ok(response.results.to_string()) } /// Next execution pub async fn next_execution(&self, session_id: &str) -> AppResult<String> { let response = self.send_command_with_timeout(session_id, &MiCommand::exec_next()).await?; Ok(response.results.to_string()) } }

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