Skip to main content

incode/tools/
breakpoints.rs

1use async_trait::async_trait;
2use serde_json::{json, Value};
3use std::collections::HashMap;
4use crate::error::{IncodeError, IncodeResult};
5use crate::lldb_manager::LldbManager;
6use super::{Tool, ToolResponse};
7
8// Breakpoint Management Tools (8 tools)
9pub struct SetBreakpointTool;
10pub struct SetWatchpointTool;
11pub struct ListBreakpointsTool;
12pub struct DeleteBreakpointTool;
13pub struct EnableBreakpointTool;
14pub struct DisableBreakpointTool;
15pub struct SetConditionalBreakpointTool;
16pub struct BreakpointCommandsTool;
17
18
19// F0014: set_breakpoint - Fully implemented
20#[async_trait]
21impl Tool for SetBreakpointTool {
22    fn name(&self) -> &'static str {
23        "set_breakpoint"
24    }
25
26    fn description(&self) -> &'static str {
27        "Set breakpoint by address, function name, or file:line"
28    }
29
30    fn parameters(&self) -> Value {
31        json!({
32            "location": {
33                "type": "string",
34                "description": "Breakpoint location - address (0x1234), function name (main), or file:line (main.c:42)"
35            },
36            "address": {
37                "type": "string",
38                "description": "Memory address for breakpoint (alternative to location)"
39            },
40            "function": {
41                "type": "string",
42                "description": "Function name for breakpoint (alternative to location)"
43            },
44            "file": {
45                "type": "string",
46                "description": "Source file name (use with line parameter)"
47            },
48            "line": {
49                "type": "integer",
50                "description": "Line number (use with file parameter)",
51                "minimum": 1
52            },
53            "enabled": {
54                "type": "boolean",
55                "description": "Whether breakpoint should be enabled after creation",
56                "default": true
57            }
58        })
59    }
60
61    async fn execute(
62        &self,
63        arguments: HashMap<String, Value>,
64        lldb_manager: &mut LldbManager,
65    ) -> IncodeResult<ToolResponse> {
66        // Determine breakpoint location from various parameter combinations
67        let location = if let Some(loc) = arguments.get("location").and_then(|v| v.as_str()) {
68            loc.to_string()
69        } else if let Some(addr) = arguments.get("address").and_then(|v| v.as_str()) {
70            addr.to_string()
71        } else if let Some(func) = arguments.get("function").and_then(|v| v.as_str()) {
72            func.to_string()
73        } else if let (Some(file), Some(line)) = (
74            arguments.get("file").and_then(|v| v.as_str()),
75            arguments.get("line").and_then(|v| v.as_u64())
76        ) {
77            format!("{}:{}", file, line)
78        } else {
79            return Ok(ToolResponse::Error("Must specify location, address, function, or file:line parameters".to_string()));
80        };
81
82        let _enabled = arguments.get("enabled")
83            .and_then(|v| v.as_bool())
84            .unwrap_or(true);
85
86        match lldb_manager.set_breakpoint(&location) {
87            Ok(breakpoint_id) => {
88                Ok(ToolResponse::Json(json!({
89                    "success": true,
90                    "breakpoint_id": breakpoint_id,
91                    "location": location,
92                    "enabled": _enabled,
93                    "message": format!("Successfully created breakpoint {} at {}", breakpoint_id, location)
94                })))
95            }
96            Err(e) => Ok(ToolResponse::Error(e.to_string())),
97        }
98    }
99}
100// F0015: set_watchpoint - Fully implemented
101#[async_trait]
102impl Tool for SetWatchpointTool {
103    fn name(&self) -> &'static str {
104        "set_watchpoint"
105    }
106
107    fn description(&self) -> &'static str {
108        "Set memory watchpoint to monitor memory access (read/write/access)"
109    }
110
111    fn parameters(&self) -> Value {
112        json!({
113            "address": {
114                "type": "string",
115                "description": "Memory address to watch (hexadecimal, e.g., '0x7fff12345678')"
116            },
117            "size": {
118                "type": "integer",
119                "description": "Number of bytes to watch",
120                "default": 8,
121                "minimum": 1,
122                "maximum": 256
123            },
124            "access_type": {
125                "type": "string",
126                "description": "Type of access to monitor",
127                "enum": ["read", "write", "read_write"],
128                "default": "write"
129            }
130        })
131    }
132
133    async fn execute(
134        &self,
135        arguments: HashMap<String, Value>,
136        lldb_manager: &mut LldbManager,
137    ) -> IncodeResult<ToolResponse> {
138        let address_str = arguments.get("address")
139            .and_then(|v| v.as_str())
140            .ok_or_else(|| IncodeError::mcp("Missing address parameter"))?;
141
142        let address = if address_str.starts_with("0x") {
143            u64::from_str_radix(&address_str[2..], 16)
144        } else {
145            u64::from_str_radix(address_str, 16)
146        }.map_err(|_| IncodeError::mcp(format!("Invalid address format: {}", address_str)))?;
147
148        let size = arguments.get("size")
149            .and_then(|v| v.as_u64())
150            .unwrap_or(8) as u32;
151
152        let access_type = arguments.get("access_type")
153            .and_then(|v| v.as_str())
154            .unwrap_or("write");
155
156        let (read, write) = match access_type {
157            "read" => (true, false),
158            "write" => (false, true),
159            "read_write" => (true, true),
160            _ => return Ok(ToolResponse::Error(format!("Invalid access_type: {}", access_type))),
161        };
162
163        match lldb_manager.set_watchpoint(address, size, read, write) {
164            Ok(watchpoint_id) => {
165                Ok(ToolResponse::Json(json!({
166                    "success": true,
167                    "watchpoint_id": watchpoint_id,
168                    "address": format!("0x{:x}", address),
169                    "size": size,
170                    "access_type": access_type,
171                    "message": format!("Successfully created watchpoint {} at 0x{:x} ({} access, {} bytes)", 
172                                     watchpoint_id, address, access_type, size)
173                })))
174            }
175            Err(e) => Ok(ToolResponse::Error(e.to_string())),
176        }
177    }
178}
179// F0016: list_breakpoints - Fully implemented
180#[async_trait]
181impl Tool for ListBreakpointsTool {
182    fn name(&self) -> &'static str {
183        "list_breakpoints"
184    }
185
186    fn description(&self) -> &'static str {
187        "List all active breakpoints with details including IDs, locations, and hit counts"
188    }
189
190    fn parameters(&self) -> Value {
191        json!({
192            "enabled_only": {
193                "type": "boolean",
194                "description": "Only list enabled breakpoints",
195                "default": false
196            },
197            "include_hit_count": {
198                "type": "boolean", 
199                "description": "Include hit count information",
200                "default": true
201            }
202        })
203    }
204
205    async fn execute(
206        &self,
207        arguments: HashMap<String, Value>,
208        lldb_manager: &mut LldbManager,
209    ) -> IncodeResult<ToolResponse> {
210        let enabled_only = arguments.get("enabled_only")
211            .and_then(|v| v.as_bool())
212            .unwrap_or(false);
213
214        let _include_hit_count = arguments.get("include_hit_count")
215            .and_then(|v| v.as_bool())
216            .unwrap_or(true);
217
218        match lldb_manager.list_breakpoints() {
219            Ok(breakpoints) => {
220                let filtered_breakpoints: Vec<_> = if enabled_only {
221                    breakpoints.into_iter().filter(|bp| bp.enabled).collect()
222                } else {
223                    breakpoints
224                };
225
226                let breakpoint_data: Vec<Value> = filtered_breakpoints.iter().map(|bp| {
227                    json!({
228                        "id": bp.id,
229                        "enabled": bp.enabled,
230                        "location": bp.location,
231                        "hit_count": bp.hit_count,
232                        "condition": bp.condition
233                    })
234                }).collect();
235
236                Ok(ToolResponse::Json(json!({
237                    "breakpoints": breakpoint_data,
238                    "total_count": breakpoint_data.len(),
239                    "enabled_count": filtered_breakpoints.iter().filter(|bp| bp.enabled).count(),
240                    "message": format!("Found {} breakpoints", breakpoint_data.len())
241                })))
242            }
243            Err(e) => Ok(ToolResponse::Error(e.to_string())),
244        }
245    }
246}
247// F0017: delete_breakpoint - Fully implemented
248#[async_trait]
249impl Tool for DeleteBreakpointTool {
250    fn name(&self) -> &'static str {
251        "delete_breakpoint"
252    }
253
254    fn description(&self) -> &'static str {
255        "Remove specific breakpoint by ID"
256    }
257
258    fn parameters(&self) -> Value {
259        json!({
260            "breakpoint_id": {
261                "type": "integer",
262                "description": "ID of the breakpoint to delete",
263                "minimum": 1
264            },
265            "confirm": {
266                "type": "boolean",
267                "description": "Confirmation flag to prevent accidental deletion",
268                "default": false
269            }
270        })
271    }
272
273    async fn execute(
274        &self,
275        arguments: HashMap<String, Value>,
276        lldb_manager: &mut LldbManager,
277    ) -> IncodeResult<ToolResponse> {
278        let breakpoint_id = arguments.get("breakpoint_id")
279            .and_then(|v| v.as_u64())
280            .ok_or_else(|| IncodeError::mcp("Missing breakpoint_id parameter"))? as u32;
281
282        let confirm = arguments.get("confirm")
283            .and_then(|v| v.as_bool())
284            .unwrap_or(false);
285
286        if !confirm {
287            return Ok(ToolResponse::Error(format!(
288                "Deletion requires confirmation. Set confirm=true to delete breakpoint {}", 
289                breakpoint_id
290            )));
291        }
292
293        match lldb_manager.delete_breakpoint(breakpoint_id) {
294            Ok(_) => {
295                Ok(ToolResponse::Json(json!({
296                    "success": true,
297                    "breakpoint_id": breakpoint_id,
298                    "message": format!("Successfully deleted breakpoint {}", breakpoint_id)
299                })))
300            }
301            Err(e) => Ok(ToolResponse::Error(e.to_string())),
302        }
303    }
304}
305// F0018: enable_breakpoint - Fully implemented
306#[async_trait]
307impl Tool for EnableBreakpointTool {
308    fn name(&self) -> &'static str {
309        "enable_breakpoint"
310    }
311
312    fn description(&self) -> &'static str {
313        "Enable a previously disabled breakpoint by ID"
314    }
315
316    fn parameters(&self) -> Value {
317        json!({
318            "breakpoint_id": {
319                "type": "integer",
320                "description": "ID of the breakpoint to enable",
321                "minimum": 1
322            }
323        })
324    }
325
326    async fn execute(
327        &self,
328        arguments: HashMap<String, Value>,
329        lldb_manager: &mut LldbManager,
330    ) -> IncodeResult<ToolResponse> {
331        let breakpoint_id = arguments.get("breakpoint_id")
332            .and_then(|v| v.as_u64())
333            .ok_or_else(|| IncodeError::mcp("Missing breakpoint_id parameter"))? as u32;
334
335        match lldb_manager.enable_breakpoint(breakpoint_id) {
336            Ok(is_enabled) => {
337                if is_enabled {
338                    Ok(ToolResponse::Json(json!({
339                        "breakpoint_id": breakpoint_id,
340                        "enabled": true,
341                        "success": true,
342                        "message": format!("Breakpoint {} enabled successfully", breakpoint_id)
343                    })))
344                } else {
345                    Ok(ToolResponse::Error(format!("Failed to enable breakpoint {}", breakpoint_id)))
346                }
347            }
348            Err(e) => Ok(ToolResponse::Error(e.to_string())),
349        }
350    }
351}
352// F0019: disable_breakpoint - Fully implemented
353#[async_trait]
354impl Tool for DisableBreakpointTool {
355    fn name(&self) -> &'static str {
356        "disable_breakpoint"
357    }
358
359    fn description(&self) -> &'static str {
360        "Disable a breakpoint without removing it (can be re-enabled later)"
361    }
362
363    fn parameters(&self) -> Value {
364        json!({
365            "breakpoint_id": {
366                "type": "integer",
367                "description": "ID of the breakpoint to disable",
368                "minimum": 1
369            }
370        })
371    }
372
373    async fn execute(
374        &self,
375        arguments: HashMap<String, Value>,
376        lldb_manager: &mut LldbManager,
377    ) -> IncodeResult<ToolResponse> {
378        let breakpoint_id = arguments.get("breakpoint_id")
379            .and_then(|v| v.as_u64())
380            .ok_or_else(|| IncodeError::mcp("Missing breakpoint_id parameter"))? as u32;
381
382        match lldb_manager.disable_breakpoint(breakpoint_id) {
383            Ok(is_disabled) => {
384                if is_disabled {
385                    Ok(ToolResponse::Json(json!({
386                        "breakpoint_id": breakpoint_id,
387                        "enabled": false,
388                        "disabled": true,
389                        "success": true,
390                        "message": format!("Breakpoint {} disabled successfully", breakpoint_id)
391                    })))
392                } else {
393                    Ok(ToolResponse::Error(format!("Failed to disable breakpoint {}", breakpoint_id)))
394                }
395            }
396            Err(e) => Ok(ToolResponse::Error(e.to_string())),
397        }
398    }
399}
400// F0020: set_conditional_breakpoint - Fully implemented
401#[async_trait]
402impl Tool for SetConditionalBreakpointTool {
403    fn name(&self) -> &'static str {
404        "set_conditional_breakpoint"
405    }
406
407    fn description(&self) -> &'static str {
408        "Set a breakpoint that only triggers when a specified condition is true"
409    }
410
411    fn parameters(&self) -> Value {
412        json!({
413            "location": {
414                "type": "string",
415                "description": "Breakpoint location (function name, address, or file:line)"
416            },
417            "condition": {
418                "type": "string",
419                "description": "C/C++ expression that must evaluate to true for breakpoint to trigger (e.g., 'x > 10', 'strcmp(str, \"test\") == 0')"
420            },
421            "ignore_count": {
422                "type": "integer",
423                "description": "Number of times to ignore this breakpoint before checking condition",
424                "default": 0,
425                "minimum": 0
426            },
427            "description": {
428                "type": "string",
429                "description": "Optional description for the conditional breakpoint",
430                "default": ""
431            }
432        })
433    }
434
435    async fn execute(
436        &self,
437        arguments: HashMap<String, Value>,
438        lldb_manager: &mut LldbManager,
439    ) -> IncodeResult<ToolResponse> {
440        let location = arguments.get("location")
441            .and_then(|v| v.as_str())
442            .ok_or_else(|| IncodeError::mcp("Missing location parameter"))?;
443
444        let condition = arguments.get("condition")
445            .and_then(|v| v.as_str())
446            .ok_or_else(|| IncodeError::mcp("Missing condition parameter"))?;
447
448        let ignore_count = arguments.get("ignore_count")
449            .and_then(|v| v.as_u64())
450            .unwrap_or(0) as u32;
451
452        let description = arguments.get("description")
453            .and_then(|v| v.as_str())
454            .unwrap_or("");
455
456        // Validate condition for basic safety
457        if Self::is_unsafe_condition(condition) {
458            return Ok(ToolResponse::Error(format!("Unsafe condition detected: {}", condition)));
459        }
460
461        match lldb_manager.set_conditional_breakpoint(location, condition) {
462            Ok(breakpoint_id) => {
463                Ok(ToolResponse::Json(json!({
464                    "breakpoint_id": breakpoint_id,
465                    "location": location,
466                    "condition": condition,
467                    "ignore_count": ignore_count,
468                    "description": description,
469                    "enabled": true,
470                    "type": "conditional",
471                    "success": true,
472                    "message": format!("Conditional breakpoint {} set at {} with condition: {}", breakpoint_id, location, condition)
473                })))
474            }
475            Err(e) => Ok(ToolResponse::Error(e.to_string())),
476        }
477    }
478}
479
480impl SetConditionalBreakpointTool {
481    fn is_unsafe_condition(condition: &str) -> bool {
482        let dangerous_patterns = [
483            "system(", "exec(", "fork(", "kill(",
484            "delete ", "free(", "malloc(", "realloc(",
485            "exit(", "abort(", "_exit(",
486            "remove(", "unlink(", "rmdir(",
487        ];
488        
489        dangerous_patterns.iter().any(|pattern| condition.contains(pattern))
490    }
491}
492// F0021: breakpoint_commands - Fully implemented
493#[async_trait]
494impl Tool for BreakpointCommandsTool {
495    fn name(&self) -> &'static str {
496        "breakpoint_commands"
497    }
498
499    fn description(&self) -> &'static str {
500        "Set commands to execute automatically when a breakpoint is hit"
501    }
502
503    fn parameters(&self) -> Value {
504        json!({
505            "breakpoint_id": {
506                "type": "integer",
507                "description": "ID of the breakpoint to attach commands to",
508                "minimum": 1
509            },
510            "commands": {
511                "type": "array",
512                "description": "List of LLDB commands to execute when breakpoint hits",
513                "items": {
514                    "type": "string"
515                },
516                "minItems": 1
517            },
518            "stop_on_command_failure": {
519                "type": "boolean",
520                "description": "Whether to stop execution if any command fails",
521                "default": false
522            },
523            "continue_after_commands": {
524                "type": "boolean",
525                "description": "Whether to continue execution automatically after running commands",
526                "default": false
527            }
528        })
529    }
530
531    async fn execute(
532        &self,
533        arguments: HashMap<String, Value>,
534        lldb_manager: &mut LldbManager,
535    ) -> IncodeResult<ToolResponse> {
536        let breakpoint_id = arguments.get("breakpoint_id")
537            .and_then(|v| v.as_u64())
538            .ok_or_else(|| IncodeError::mcp("Missing breakpoint_id parameter"))? as u32;
539
540        let commands_array = arguments.get("commands")
541            .and_then(|v| v.as_array())
542            .ok_or_else(|| IncodeError::mcp("Missing commands parameter"))?;
543
544        let commands: Vec<String> = commands_array.iter()
545            .filter_map(|v| v.as_str().map(|s| s.to_string()))
546            .collect();
547
548        if commands.is_empty() {
549            return Ok(ToolResponse::Error("No valid commands provided".to_string()));
550        }
551
552        let stop_on_failure = arguments.get("stop_on_command_failure")
553            .and_then(|v| v.as_bool())
554            .unwrap_or(false);
555
556        let continue_after = arguments.get("continue_after_commands")
557            .and_then(|v| v.as_bool())
558            .unwrap_or(false);
559
560        // Validate commands for basic safety
561        for command in &commands {
562            if Self::is_unsafe_command(command) {
563                return Ok(ToolResponse::Error(format!("Unsafe command detected: {}", command)));
564            }
565        }
566
567        match lldb_manager.set_breakpoint_commands(breakpoint_id, &commands) {
568            Ok(success) => {
569                if success {
570                    Ok(ToolResponse::Json(json!({
571                        "breakpoint_id": breakpoint_id,
572                        "commands": commands,
573                        "command_count": commands.len(),
574                        "stop_on_command_failure": stop_on_failure,
575                        "continue_after_commands": continue_after,
576                        "success": true,
577                        "message": format!("Set {} commands for breakpoint {}", commands.len(), breakpoint_id)
578                    })))
579                } else {
580                    Ok(ToolResponse::Error(format!("Failed to set commands for breakpoint {}", breakpoint_id)))
581                }
582            }
583            Err(e) => Ok(ToolResponse::Error(e.to_string())),
584        }
585    }
586}
587
588impl BreakpointCommandsTool {
589    fn is_unsafe_command(command: &str) -> bool {
590        let dangerous_patterns = [
591            "process kill", "process detach", "quit", "exit",
592            "target delete", "settings clear", "platform disconnect",
593            "script import", "command script", "process connect",
594            "gdb-remote", "kdp-remote", "platform connect",
595        ];
596        
597        dangerous_patterns.iter().any(|pattern| command.to_lowercase().contains(&pattern.to_lowercase()))
598    }
599}