Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion src/mcp_windbg/cdb_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import re
import os
import platform
import signal
from typing import List, Optional

# Regular expression to detect CDB prompts
Expand Down Expand Up @@ -97,13 +98,19 @@ def __init__(
cmd_args.extend(additional_args)

try:
# Only create a new process group for remote sessions where CTRL+BREAK is needed
creationflags = 0
if os.name == 'nt' and self.remote_connection:
creationflags = subprocess.CREATE_NEW_PROCESS_GROUP

self.process = subprocess.Popen(
cmd_args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1
bufsize=1,
creationflags=creationflags,
)
except Exception as e:
raise CDBError(f"Failed to start CDB process: {str(e)}")
Expand Down Expand Up @@ -239,6 +246,21 @@ def shutdown(self):
finally:
self.process = None

def send_ctrl_break(self) -> None:
"""Send a CTRL+BREAK event to the CDB process to break in.

Raises:
CDBError: If the signal cannot be delivered or the process is not running.
"""
if not self.process or self.process.poll() is not None:
raise CDBError("CDB process is not running")

try:
# On Windows, deliver CTRL+BREAK to the new process group we created
self.process.send_signal(signal.CTRL_BREAK_EVENT)
except Exception as e:
raise CDBError(f"Failed to send CTRL+BREAK: {str(e)}")

def get_session_id(self) -> str:
"""Get a unique identifier for this CDB session."""
if self.dump_path:
Expand Down
35 changes: 35 additions & 0 deletions src/mcp_windbg/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,20 @@ class ListWindbgDumpsParams(BaseModel):
)


class SendCtrlBreakParams(BaseModel):
"""Parameters for sending CTRL+BREAK to a CDB/WinDbg session."""
dump_path: Optional[str] = Field(default=None, description="Path to the Windows crash dump file")
connection_string: Optional[str] = Field(default=None, description="Remote connection string (e.g., 'tcp:Port=5005,Server=192.168.0.100')")

@model_validator(mode='after')
def validate_connection_params(self):
if not self.dump_path and not self.connection_string:
raise ValueError("Either dump_path or connection_string must be provided")
if self.dump_path and self.connection_string:
raise ValueError("dump_path and connection_string are mutually exclusive")
return self


def get_or_create_session(
dump_path: Optional[str] = None,
connection_string: Optional[str] = None,
Expand Down Expand Up @@ -315,6 +329,14 @@ async def list_tools() -> list[Tool]:
""",
inputSchema=RunWindbgCmdParams.model_json_schema(),
),
Tool(
name="send_ctrl_break",
description="""
Send a CTRL+BREAK event to the active CDB/WinDbg session, causing it to break in.
Useful for interrupting a running target or breaking into a remote session.
""",
inputSchema=SendCtrlBreakParams.model_json_schema(),
),
Tool(
name="close_windbg_dump",
description="""
Expand Down Expand Up @@ -452,6 +474,19 @@ async def call_tool(name, arguments: dict) -> list[TextContent]:
text=f"Command: {args.command}\n\nOutput:\n```\n" + "\n".join(output) + "\n```"
)]

elif name == "send_ctrl_break":
args = SendCtrlBreakParams(**arguments)
session = get_or_create_session(
dump_path=args.dump_path, connection_string=args.connection_string,
cdb_path=cdb_path, symbols_path=symbols_path, timeout=timeout, verbose=verbose
)
session.send_ctrl_break()
target = args.dump_path if args.dump_path else f"remote: {args.connection_string}"
return [TextContent(
type="text",
text=f"Sent CTRL+BREAK to CDB session ({target})."
)]

elif name == "close_windbg_dump":
args = CloseWindbgDumpParams(**arguments)
success = unload_session(dump_path=args.dump_path)
Expand Down