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
8pub 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#[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 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#[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#[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#[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#[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#[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#[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 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#[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 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}