Skip to main content
Glama

MCP Server GDB

models.rs18.8 kB
use core::fmt; use std::collections::{HashMap, VecDeque}; use std::fmt::Display; use std::ops::{Add, Sub}; use std::path::{Path, PathBuf}; use std::str::FromStr; use nom::branch::alt; use nom::bytes::complete::{tag, take_until}; use nom::character::char; use nom::character::complete::{alphanumeric1, space0}; use nom::combinator::map; use nom::sequence::{delimited, preceded, separated_pair}; use nom::{IResult, Parser}; use serde::{Deserialize, Serialize, de}; use serde_with::{DisplayFromStr, serde_as, skip_serializing_none}; use tracing::debug; use crate::error::AppError; use crate::mi::commands::BreakPointNumber; /// GDB session information #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GDBSession { /// Session ID pub id: String, /// Session status pub status: GDBSessionStatus, /// Creation time pub created_at: u64, } /// GDB session status #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum GDBSessionStatus { /// Created but not started Created, /// Running Running, /// Program stopped at breakpoint Stopped, /// Session terminated Terminated, } /// GDB command request #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GDBCommandRequest { /// GDB/MI command pub command: String, } /// Create session request #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreateSessionRequest { /// Executable file path (optional) pub executable_path: Option<String>, } #[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SrcPosition { pub fullname: PathBuf, #[serde_as(as = "DisplayFromStr")] pub line: usize, } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[serde(from = "String", into = "String")] pub struct Address<T: FromStr<Err = std::num::ParseIntError> + Display + Clone + std::fmt::LowerHex>( pub T, ); impl<T: FromStr<Err = std::num::ParseIntError> + Display + Clone + std::fmt::LowerHex> From<String> for Address<T> { fn from(s: String) -> Self { let s = if s.starts_with("0x") { &s[2..] } else { &s }; match u128::from_str_radix(s, 16) { Ok(val) => { Address(T::from_str(&val.to_string()).unwrap_or_else(|_| T::from_str("0").unwrap())) } Err(_) => Address(T::from_str(s).unwrap_or_else(|_| T::from_str("0").unwrap())), } } } impl<T: FromStr<Err = std::num::ParseIntError> + Display + Clone + std::fmt::LowerHex> From<Address<T>> for String { fn from(addr: Address<T>) -> Self { format!("0x{:x}", addr.0) } } impl< T: FromStr<Err = std::num::ParseIntError> + Display + Clone + std::fmt::LowerHex + Add<Output = T>, > Add<T> for Address<T> { type Output = Self; fn add(self, rhs: T) -> Self { Address(self.0 + rhs) } } impl< T: FromStr<Err = std::num::ParseIntError> + Display + Clone + std::fmt::LowerHex + Sub<Output = T>, > Sub<T> for Address<T> { type Output = Self; fn sub(self, rhs: T) -> Self { Address(self.0 - rhs) } } // Type aliases for common address sizes pub type Address32 = Address<u32>; pub type Address64 = Address<u64>; pub type Address128 = Address<u128>; impl Address128 { pub fn new(low: Address64, high: Address64) -> Self { let mut val = Address::<u128>(high.0 as u128); val.0 = val.0 << 64; val.0 += low.0 as u128; val } } #[derive(Debug, Clone, Serialize)] pub struct Enabled(bool); impl<'de> Deserialize<'de> for Enabled { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de>, { let s: String = serde::Deserialize::deserialize(deserializer)?; if s == "y" { Ok(Enabled(true)) } else { Ok(Enabled(false)) } } } #[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BreakPoint { pub number: BreakPointNumber, #[serde(rename = "addr")] pub address: Option<Address64>, pub enabled: Enabled, #[serde(flatten)] pub src_pos: Option<SrcPosition>, // not present if debug information is missing! pub r#type: String, #[serde(rename = "disp")] pub display: String, } pub struct BreakPointSet { map: HashMap<BreakPointNumber, BreakPoint>, pub last_change: std::time::Instant, } impl Default for BreakPointSet { fn default() -> Self { Self { map: HashMap::new(), last_change: std::time::Instant::now() } } } impl BreakPointSet { fn notify_change(&mut self) { self.last_change = std::time::Instant::now(); } pub fn update_breakpoint(&mut self, new_bp: BreakPoint) { let _ = self.map.insert(new_bp.number, new_bp); //debug_assert!(res.is_some(), "Modified non-existent breakpoint"); self.notify_change(); } pub fn remove_breakpoint(&mut self, bp_num: BreakPointNumber) { self.map.remove(&bp_num); if bp_num.minor.is_none() { //TODO: ensure removal of child breakpoints } self.notify_change(); } } impl std::ops::Deref for BreakPointSet { type Target = HashMap<BreakPointNumber, BreakPoint>; fn deref(&self) -> &Self::Target { &self.map } } /// Stack frame information #[serde_as] #[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct StackFrame { /// Frame level #[serde_as(as = "DisplayFromStr")] pub level: u32, /// Function name #[serde(rename = "func")] pub function: String, /// File name pub file: Option<String>, /// Full name of the file pub fullname: Option<String>, /// Line number #[serde_as(as = "Option<DisplayFromStr>")] pub line: Option<u32>, /// Address #[serde(rename = "addr")] pub address: Option<Address64>, /// Arch pub arch: Option<String>, } pub enum PrintValue { /// print only the names of the variables, equivalent to "--no-values" NoValues, /// print also their values, equivalent to "--all-values" AllValues, /// print the name, type and value for simple data types, and the name and /// type for arrays, structures and unions, equivalent to "--simple-values" SimpleValues, } impl FromStr for PrintValue { type Err = AppError; fn from_str(s: &str) -> Result<Self, Self::Err> { match s.parse::<usize>()? { 0 => Ok(PrintValue::NoValues), 1 => Ok(PrintValue::AllValues), 2 => Ok(PrintValue::SimpleValues), _ => Err(AppError::InvalidArgument("only 0,1,2 are valid".to_string())), } } } impl Display for PrintValue { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self) } } /// Variable information #[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Variable { /// Variable name pub name: String, /// Variable type, only present if --all-values or --simple-values pub r#type: Option<String>, /// Variable value, only present if --simple-values pub value: Option<String>, } #[derive(Debug, Clone, Serialize, PartialEq, Eq)] pub enum RegisterRaw { U32(Address32), U64(Address64), U128(Address128), U256(Address128, Address128), } // Define Register struct to hold register data #[serde_as] #[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Register { // Not exist in the register value output but can be amended afterwards pub name: Option<String>, #[serde_as(as = "DisplayFromStr")] pub number: usize, pub value: Option<RegisterRaw>, pub v2_int128: Option<String>, pub v8_int32: Option<String>, pub v4_int64: Option<String>, pub v8_float: Option<String>, pub v16_int8: Option<String>, pub v4_int32: Option<String>, pub error: Option<String>, } // impl Register { // /// Value is not set to anything readable // pub fn is_set(&self) -> bool { // self.error.is_none() && self.value != // Some("<unavailable>".to_string()) } // } fn pair<'a>() -> impl Parser<&'a str, Output = (&'a str, &'a str), Error = nom::error::Error<&'a str>> { delimited( char('['), separated_pair(alphanumeric1, (char(','), space0), alphanumeric1), char(']'), ) } fn register_data(input: &str) -> IResult<&str, RegisterRaw> { let v128bits = separated_pair(tag("v2_int64"), (char(':'), space0), pair()); let v256bits = separated_pair(tag("v2_int128"), (char(':'), space0), pair()); map( preceded(take_until("v2_int"), alt((v128bits, v256bits))), |(r#type, (v1, v2))| match r#type { "v2_int64" => RegisterRaw::U128(Address128::new( Address64::from(v1.to_owned()), Address64::from(v2.to_owned()), )), "v2_int128" => { RegisterRaw::U256(Address128::from(v1.to_owned()), Address128::from(v2.to_owned())) } _ => unreachable!(), }, ) .parse(input) } impl<'de> Deserialize<'de> for RegisterRaw { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de>, { let s: String = serde::Deserialize::deserialize(deserializer)?; if s.starts_with("0x") { Ok(RegisterRaw::U64(Address64::from(s[2..].to_owned()))) } else { register_data(&s).map(|(_, o)| o).map_err(|e| de::Error::custom(e.to_string())) } } } #[derive(Debug, Default, Serialize, Deserialize)] pub struct BT { pub location: u64, pub function: Option<String>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ASM { pub address: u64, pub inst: String, pub offset: u64, pub func_name: Option<String>, } #[derive(Debug, Clone)] pub struct TrackedRegister { pub register: Option<Register>, pub resolve: ResolveSymbol, } impl TrackedRegister { pub fn new(register: Option<Register>, resolve: ResolveSymbol) -> Self { Self { register, resolve } } } pub enum MemoryType { Unknown, Stack, Heap, Exec, } // Define memory output layout #[skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Memory { /// The start address of the memory block, as hexadecimal literal. pub begin: String, /// The end address of the memory block, as hexadecimal literal. pub end: String, /// The offset of the memory block, as hexadecimal literal, relative to the /// start address passed to -data-read-memory-bytes. pub offset: String, /// The contents of the memory block, in hex bytes. pub contents: String, } #[derive(Debug, Clone)] pub struct MemoryMapping { pub start_address: u64, pub end_address: u64, pub size: u64, pub offset: u64, pub permissions: Option<String>, pub path: Option<PathBuf>, } impl MemoryMapping { /// Mapping is the stack pub fn is_stack(&self) -> bool { self.path.as_ref().map_or(false, |p| p == Path::new("[stack]")) } /// Mapping is the heap pub fn is_heap(&self) -> bool { self.path.as_ref().map_or(false, |p| p == Path::new("[heap]")) } /// Mapping filepath matches `filepath` /// /// This could be set from something like "file /// test-assets/test_render_app/a.out" so we make sure to match with a /// mapping such as: "/home/wcampbell/projects/wcampbell/heretek/ /// test-assets/test_render_app/a.out" pub fn is_path(&self, filepath: &Path) -> bool { if let Some(path) = &self.path { path.ends_with(filepath) } else { false } } pub fn is_exec(&self) -> bool { if let Some(permissions) = &self.permissions { permissions.contains('x') } else { false } } /// Mapping contains the `addr` pub fn contains(&self, addr: u64) -> bool { (self.start_address..self.end_address).contains(&addr) } } impl MemoryMapping { /// Parse from `MEMORY_MAP_START_STR_NEW` fn from_str_new(line: &str) -> Result<Self, String> { let parts: Vec<&str> = line.split_whitespace().collect(); if let Some([start_address, end_address, size, offset, permissions]) = parts.first_chunk() { Ok(MemoryMapping { start_address: u64::from_str_radix(&start_address[2..], 16) .map_err(|_| "Invalid start address")?, end_address: u64::from_str_radix(&end_address[2..], 16) .map_err(|_| "Invalid end address")?, size: u64::from_str_radix(&size[2..], 16).map_err(|_| "Invalid size")?, offset: u64::from_str_radix(&offset[2..], 16).map_err(|_| "Invalid offset")?, permissions: Some(permissions.to_string()), path: None, }) } else if let Some([start_address, end_address, size, offset, permissions, path]) = parts.first_chunk() { Ok(MemoryMapping { start_address: u64::from_str_radix(&start_address[2..], 16) .map_err(|_| "Invalid start address")?, end_address: u64::from_str_radix(&end_address[2..], 16) .map_err(|_| "Invalid end address")?, size: u64::from_str_radix(&size[2..], 16).map_err(|_| "Invalid size")?, offset: u64::from_str_radix(&offset[2..], 16).map_err(|_| "Invalid offset")?, permissions: Some(permissions.to_string()), path: Some(PathBuf::from(path)), }) } else { return Err(format!("Invalid line format: {}", line)); } } /// Parse from `MEMORY_MAP_START_STR_OLD` fn from_str_old(line: &str) -> Result<Self, String> { let parts: Vec<&str> = line.split_whitespace().collect(); if let Some([start_address, end_address, size, offset, path]) = parts.first_chunk() { Ok(MemoryMapping { start_address: u64::from_str_radix(&start_address[2..], 16) .map_err(|_| "Invalid start address")?, end_address: u64::from_str_radix(&end_address[2..], 16) .map_err(|_| "Invalid end address")?, size: u64::from_str_radix(&size[2..], 16).map_err(|_| "Invalid size")?, offset: u64::from_str_radix(&offset[2..], 16).map_err(|_| "Invalid offset")?, permissions: None, path: Some(PathBuf::from(path)), }) } else { Err(format!("Invalid line format: {}", line)) } } } /// Parse from `MEMORY_MAP_START_STR_NEW` pub fn parse_memory_mappings_new(input: &str) -> Vec<MemoryMapping> { input.lines().skip(1).filter_map(|line| MemoryMapping::from_str_new(line).ok()).collect() } /// Parse from `MEMORY_MAP_START_STR_OLD` pub fn parse_memory_mappings_old(input: &str) -> Vec<MemoryMapping> { input.lines().skip(1).filter_map(|line| MemoryMapping::from_str_old(line).ok()).collect() } #[derive(Debug, Clone)] pub struct ResolveSymbol { pub map: VecDeque<u64>, pub repeated_pattern: bool, pub final_assembly: String, } impl ResolveSymbol { /// Attempts to insert a `u64` value and prevents repeated patterns /// /// Returns `true` if inserted, `false` otherwise. pub fn try_push(&mut self, value: u64) -> bool { self.map.push_back(value); if self.has_repeating_pattern() { self.repeated_pattern = true; self.map.pop_back(); return false; } true } fn has_repeating_pattern(&self) -> bool { if self.map.len() == 1 { return false; } if self.map.len() == 2 { return self.map[0] == self.map[1]; } debug!("map: {:02x?}", self.map); for pattern_length in 2..=self.map.len() / 2 { for start in 0..(self.map.len() - pattern_length) { let first_section: &Vec<u64> = &self.map.range(start..start + pattern_length).cloned().collect(); debug!("1: {:02x?}", first_section); for second_start in start + 1..(self.map.len() - pattern_length + 1) { let second_section: &Vec<u64> = &self .map .range(second_start..second_start + pattern_length) .cloned() .collect(); debug!("2: {:02x?}", second_section); if first_section == second_section { debug!("found matching"); return true; } } } } false } } #[cfg(test)] mod test { use super::*; #[test] fn test_address() { #[derive(Deserialize)] struct Test { addr: Address<u64>, opt_addr: Option<Address<u64>>, } let test: Test = serde_json::from_str("{\"addr\": \"0x1234abcd\", \"opt_addr\":\"0xABCD1234\"}") .unwrap(); assert_eq!(test.addr, Address(0x1234abcd)); assert_eq!(test.opt_addr, Some(Address(0xabcd1234))); } #[test] fn test_register_normal_value() { #[derive(Deserialize)] struct Test { reg: Register, } let test: Test = serde_json::from_str("{\"reg\":{\"number\": \"1\", \"value\": \"0x1234\"}}").unwrap(); assert_eq!(test.reg.number, 1); } #[test] fn test_register_composite_value() { #[derive(Deserialize)] struct Test { reg: Register, } let test: Test = serde_json::from_str("{\"reg\":{\"number\": \"1\", \"value\": \"builtin_type_vec256i {v16_bfloat16: [0x4, 0x0, 0x0, 0x0, 0x7c80, 0x556b, 0x5555, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0], v16_half: [0x4, 0x0, 0x0, 0x0, 0x7c80, 0x556b, 0x5555, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0], v8_float: [0x4, 0x0, 0x556b7c80, 0x5555, 0x0, 0x0, 0x0, 0x0], v4_double: [0x4, 0x5555556b7c80, 0x0, 0x0], v32_int8: [0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x7c, 0x6b, 0x55, 0x55, 0x55, 0x0 <repeats 18 times>], v16_int16: [0x4, 0x0, 0x0, 0x0, 0x7c80, 0x556b, 0x5555, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0], v8_int32: [0x4, 0x0, 0x556b7c80, 0x5555, 0x0, 0x0, 0x0, 0x0], v4_int64: [0x4, 0x5555556b7c80, 0x0, 0x0], v2_int128: [0x5555556b7c800000000000000004, 0x0]}\"}}").unwrap(); assert_eq!( test.reg.value, Some(RegisterRaw::U256( Address::<u128>(0x5555556b7c800000000000000004), Address::<u128>(0) )) ); } }

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