"""WinDbg command builders and output parsers."""
import re
from typing import Dict, List, Optional, Any
class WinDbgCommands:
"""Helper class for building WinDbg commands."""
@staticmethod
def break_execution() -> str:
"""Command to break into debugger (Ctrl+Break).
Note: This returns a special marker that signals the debugger manager
to send CTRL_BREAK_EVENT instead of executing a text command.
"""
return "::CTRL_BREAK::"
@staticmethod
def resume_execution() -> str:
"""Command to continue execution (go)."""
return "g"
@staticmethod
def step_into() -> str:
"""Command to step into (trace)."""
return "t"
@staticmethod
def step_over() -> str:
"""Command to step over."""
return "p"
@staticmethod
def step_out() -> str:
"""Command to step out of current function."""
return "gu"
@staticmethod
def set_breakpoint(address_or_symbol: str, condition: Optional[str] = None) -> str:
"""
Set a breakpoint at address or symbol.
Args:
address_or_symbol: Memory address (hex) or symbol name
condition: Optional condition for conditional breakpoint
Returns:
WinDbg breakpoint command
"""
if condition:
return f'bp {address_or_symbol} ".if ({condition}) {{}} .else {{gc}}"'
return f"bp {address_or_symbol}"
@staticmethod
def set_breakpoint_with_command(address_or_symbol: str, command: str) -> str:
"""
Set a breakpoint that executes a command when hit.
Args:
address_or_symbol: Memory address or symbol
command: Command to execute on breakpoint hit
Returns:
WinDbg breakpoint command
"""
return f'bp {address_or_symbol} "{command}"'
@staticmethod
def remove_breakpoint(breakpoint_id: int) -> str:
"""
Remove a breakpoint by ID.
Args:
breakpoint_id: Breakpoint ID number
Returns:
WinDbg command to clear breakpoint
"""
return f"bc {breakpoint_id}"
@staticmethod
def remove_all_breakpoints() -> str:
"""Remove all breakpoints."""
return "bc *"
@staticmethod
def list_breakpoints() -> str:
"""List all breakpoints."""
return "bl"
@staticmethod
def enable_breakpoint(breakpoint_id: int) -> str:
"""Enable a breakpoint."""
return f"be {breakpoint_id}"
@staticmethod
def disable_breakpoint(breakpoint_id: int) -> str:
"""Disable a breakpoint."""
return f"bd {breakpoint_id}"
@staticmethod
def read_memory(address: str, length: int, format: str = "b") -> str:
"""
Read memory at address.
Args:
address: Memory address (hex string)
length: Number of units to read
format: Display format:
- 'b': bytes (default)
- 'w': words (2 bytes)
- 'd': dwords (4 bytes)
- 'q': qwords (8 bytes)
- 'a': ASCII
- 'u': Unicode
- 'f': floats
- 'D': doubles
Returns:
WinDbg memory display command
"""
format_map = {
'b': 'db',
'w': 'dw',
'd': 'dd',
'q': 'dq',
'a': 'da',
'u': 'du',
'f': 'df',
'D': 'dD'
}
cmd = format_map.get(format, 'db')
return f"{cmd} {address} L{length}"
@staticmethod
def write_memory(address: str, values: List[int], format: str = "b") -> str:
"""
Write memory at address.
Args:
address: Memory address (hex string)
values: List of values to write
format: Data format (b=byte, w=word, d=dword, q=qword)
Returns:
WinDbg memory write command
"""
format_map = {
'b': 'eb',
'w': 'ew',
'd': 'ed',
'q': 'eq'
}
cmd = format_map.get(format, 'eb')
value_str = ' '.join(f"{v:x}" for v in values)
return f"{cmd} {address} {value_str}"
@staticmethod
def read_registers() -> str:
"""Display all registers."""
return "r"
@staticmethod
def read_register(register_name: str) -> str:
"""Read specific register."""
return f"r {register_name}"
@staticmethod
def write_register(register_name: str, value: str) -> str:
"""
Write to a register.
Args:
register_name: Register name (e.g., 'rax', 'eip')
value: Value to write (hex string)
Returns:
WinDbg register write command
"""
return f"r {register_name}={value}"
@staticmethod
def get_callstack(frames: Optional[int] = None) -> str:
"""
Get call stack.
Args:
frames: Number of frames to display (default: all)
Returns:
WinDbg stack trace command
"""
if frames:
return f"k {frames}"
return "k"
@staticmethod
def get_callstack_verbose() -> str:
"""Get detailed call stack with parameters."""
return "kv"
@staticmethod
def list_modules() -> str:
"""List all loaded modules."""
return "lm"
@staticmethod
def list_modules_verbose() -> str:
"""List loaded modules with detailed information."""
return "lmv"
@staticmethod
def module_info(module_name: str) -> str:
"""Get detailed information about a specific module."""
return f"lmv m {module_name}"
@staticmethod
def evaluate_expression(expression: str) -> str:
"""
Evaluate an expression.
Args:
expression: Expression to evaluate (address, symbol, calculation)
Returns:
WinDbg evaluate command
"""
return f"? {expression}"
@staticmethod
def display_symbol(symbol: str) -> str:
"""Display symbol information."""
return f"x {symbol}"
@staticmethod
def list_threads() -> str:
"""List all threads."""
return "~"
@staticmethod
def switch_thread(thread_id: int) -> str:
"""Switch to a specific thread."""
return f"~{thread_id}s"
@staticmethod
def get_thread_context(thread_id: int) -> str:
"""Get context for specific thread."""
return f"~{thread_id}r"
@staticmethod
def search_memory(start: str, end: str, pattern: str, data_type: str = "b") -> str:
"""
Search memory for a pattern.
Args:
start: Start address
end: End address or length
pattern: Pattern to search for (hex bytes)
data_type: Data type (b=byte, w=word, d=dword, q=qword, a=ascii, u=unicode)
Returns:
WinDbg search command
"""
return f"s -{data_type} {start} {end} {pattern}"
@staticmethod
def disassemble(address: str, length: Optional[int] = None) -> str:
"""
Disassemble code at address.
Args:
address: Start address
length: Number of instructions (optional)
Returns:
WinDbg disassembly command
"""
if length:
return f"u {address} L{length}"
return f"u {address}"
@staticmethod
def examine_data_type(address: str, type_name: str) -> str:
"""
Display memory as a specific data type.
Args:
address: Memory address
type_name: Type name or structure
Returns:
WinDbg display type command
"""
return f"dt {type_name} {address}"
@staticmethod
def get_process_info() -> str:
"""Get current process information."""
return "|"
@staticmethod
def get_exception_info() -> str:
"""Get last exception information."""
return ".lastevent"
@staticmethod
def reload_symbols() -> str:
"""Reload symbols."""
return ".reload"
@staticmethod
def set_symbol_path(path: str) -> str:
"""Set symbol path."""
return f".sympath {path}"
@staticmethod
def add_symbol_path(path: str) -> str:
"""Add to symbol path."""
return f".sympath+ {path}"
class WinDbgParser:
"""Parser for WinDbg command output."""
@staticmethod
def parse_breakpoints(output: str) -> List[Dict[str, Any]]:
"""
Parse breakpoint list output.
Returns:
List of breakpoint information dictionaries
"""
breakpoints = []
# Pattern: ID status address [flags] module!symbol
pattern = r'(\d+)\s+([ed])\s+([0-9a-f`]+)\s+(.*?)(?:!(.+?))?(?:\s+\((.+?)\))?$'
for line in output.split('\n'):
match = re.match(pattern, line.strip(), re.IGNORECASE)
if match:
bp_id, status, address, module, symbol, info = match.groups()
breakpoints.append({
'id': int(bp_id),
'enabled': status == 'e',
'address': address,
'module': module or '',
'symbol': symbol or '',
'info': info or ''
})
return breakpoints
@staticmethod
def parse_registers(output: str) -> Dict[str, str]:
"""
Parse register output.
Returns:
Dictionary of register names to values
"""
registers = {}
# Pattern: rax=0000000000000000
pattern = r'(\w+)=([0-9a-f]+)'
for match in re.finditer(pattern, output, re.IGNORECASE):
reg_name, value = match.groups()
registers[reg_name] = value
return registers
@staticmethod
def parse_callstack(output: str) -> List[Dict[str, Any]]:
"""
Parse call stack output.
Returns:
List of stack frame information
"""
frames = []
# Pattern for stack frames
pattern = r'([0-9a-f]+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+(.*?)(?:!(.+?))?(?:\+0x([0-9a-f]+))?'
for line in output.split('\n'):
match = re.match(pattern, line.strip(), re.IGNORECASE)
if match:
child_sp, ret_addr, call_site, module, symbol, offset = match.groups()
frames.append({
'child_sp': child_sp,
'return_address': ret_addr,
'call_site': call_site,
'module': module or '',
'symbol': symbol or '',
'offset': offset or '0'
})
return frames
@staticmethod
def parse_modules(output: str) -> List[Dict[str, Any]]:
"""
Parse module list output.
Returns:
List of module information dictionaries
"""
modules = []
# Pattern: start end module_name
pattern = r'([0-9a-f`]+)\s+([0-9a-f`]+)\s+(\S+)\s*(.*)?'
for line in output.split('\n'):
match = re.match(pattern, line.strip(), re.IGNORECASE)
if match:
start, end, name, info = match.groups()
modules.append({
'start_address': start,
'end_address': end,
'name': name,
'info': info.strip() if info else ''
})
return modules
@staticmethod
def parse_memory_bytes(output: str) -> Dict[str, List[int]]:
"""
Parse memory dump output (db, dw, dd, dq).
Returns:
Dictionary mapping addresses to byte lists
"""
memory = {}
# Pattern: address byte byte byte...
pattern = r'([0-9a-f`]+)\s+([0-9a-f\s]+)'
for line in output.split('\n'):
match = re.match(pattern, line.strip(), re.IGNORECASE)
if match:
address, bytes_str = match.groups()
bytes_list = [int(b, 16) for b in bytes_str.split() if b]
memory[address] = bytes_list
return memory
@staticmethod
def parse_threads(output: str) -> List[Dict[str, Any]]:
"""
Parse thread list output.
Returns:
List of thread information
"""
threads = []
# Pattern: . ID TEB Address
pattern = r'([.\#])\s*(\d+)\s+Id:\s*([0-9a-f]+\.[0-9a-f]+)\s+Suspend:\s*(\d+)\s+Teb:\s*([0-9a-f`]+)'
for line in output.split('\n'):
match = re.search(pattern, line, re.IGNORECASE)
if match:
marker, thread_num, thread_id, suspend_count, teb = match.groups()
threads.append({
'current': marker == '.',
'number': int(thread_num),
'id': thread_id,
'suspend_count': int(suspend_count),
'teb': teb
})
return threads
@staticmethod
def extract_address(output: str) -> Optional[str]:
"""
Extract a memory address from output.
Returns:
First address found or None
"""
pattern = r'([0-9a-f`]{8,})'
match = re.search(pattern, output, re.IGNORECASE)
if match:
return match.group(1)
return None
@staticmethod
def extract_value(output: str) -> Optional[str]:
"""
Extract a value from evaluate output.
Returns:
Evaluated value or None
"""
# Pattern: Evaluate expression: value = address
pattern = r'Evaluate expression:\s*(-?\d+)\s*=\s*([0-9a-f`]+)'
match = re.search(pattern, output, re.IGNORECASE)
if match:
decimal_val, hex_val = match.groups()
return hex_val
# Try to find any hex number
pattern = r'([0-9a-f`]{8,})'
match = re.search(pattern, output, re.IGNORECASE)
if match:
return match.group(1)
return None