Skip to content

TTD Python API

This document describes the Python API for TTD (Time Travel Debugging) functionality in the Binary Ninja Debugger.

Overview

The TTD Python API provides access to Time Travel Debugging capabilities, allowing you to:

  • Query memory access events (reads, writes, executes) from TTD traces
  • Query function call events from TTD traces
  • Navigate through TTD trace positions
  • Analyze historical execution data

Prerequisites

The TTD functionality is only available when: 1. Using a TTD-capable debugger adapter (e.g., DbgEngTTD) 2. Debugging a TTD trace file (not live debugging) 3. The debugger session supports TTD operations

Always check debugger_controller.is_ttd before using TTD-specific methods.

Classes

TTDPosition

Represents a position in a TTD trace.

class TTDPosition:
    """
    TTDPosition represents a position in a TTD trace.
    
    Attributes:
        sequence (int): Sequence number in the trace
        step (int): Step number within the sequence
    """

TTDMemoryEvent

Represents a memory access event in a TTD trace.

class TTDMemoryEvent:
    """
    TTDMemoryEvent represents a memory access event in a TTD trace.
    
    Attributes:
        event_type (str): Type of the event (e.g., "Memory")
        thread_id (int): OS thread ID that performed the memory access
        unique_thread_id (int): Unique thread ID across the trace
        time_start (TTDPosition): TTD position when the memory access started
        time_end (TTDPosition): TTD position when the memory access ended
        address (int): Memory address that was accessed
        size (int): Size of the memory access
        memory_address (int): Actual memory address (may differ from address field)
        instruction_address (int): Address of the instruction that performed the access
        value (int): Value that was read/written/executed
        access_type (int): Type of access (DebuggerTTDMemoryAccessType enum)
    """

TTDCallEvent

Represents a function call event in a TTD trace.

class TTDCallEvent:
    """
    TTDCallEvent represents a function call event in a TTD trace.
    
    Attributes:
        event_type (str): Type of the event (always "Call" for TTD.Calls objects)
        thread_id (int): OS thread ID that made the call
        unique_thread_id (int): Unique thread ID across the trace
        function (str): Symbolic name of the function
        function_address (int): Function's address in memory
        return_address (int): Instruction to return to after the call
        return_value (int): Return value of the function (if not void)
        has_return_value (bool): Whether the function has a return value
        parameters (List[str]): List of parameters passed to the function
        time_start (TTDPosition): TTD position when call started
        time_end (TTDPosition): TTD position when call ended
    """

TTDEvent

Represents important events that happened during a TTD trace.

class TTDEvent:
    """
    TTDEvent represents important events that happened during a TTD trace.
    
    Attributes:
        type (int): Type of event (TTDEventType enum value)
        position (TTDPosition): Position where event occurred
        module (TTDModule or None): Module information for ModuleLoaded/ModuleUnloaded events
        thread (TTDThread or None): Thread information for ThreadCreated/ThreadTerminated events
        exception (TTDException or None): Exception information for Exception events
    """

TTDModule

Represents information about modules that were loaded/unloaded during a TTD trace.

class TTDModule:
    """
    TTDModule represents information about modules that were loaded/unloaded during a TTD trace.
    
    Attributes:
        name (str): Name and path of the module
        address (int): Address where the module was loaded
        size (int): Size of the module in bytes
        checksum (int): Checksum of the module
        timestamp (int): Timestamp of the module
    """

TTDThread

Represents information about threads and their lifetime during a TTD trace.

class TTDThread:
    """
    TTDThread represents information about threads and their lifetime during a TTD trace.
    
    Attributes:
        unique_id (int): Unique ID for the thread across the trace
        id (int): TID of the thread
        lifetime_start (TTDPosition): Lifetime start position
        lifetime_end (TTDPosition): Lifetime end position
        active_time_start (TTDPosition): Active time start position
        active_time_end (TTDPosition): Active time end position
    """

TTDException

Represents information about exceptions that occurred during a TTD trace.

class TTDException:
    """
    TTDException represents information about exceptions that occurred during a TTD trace.
    
    Attributes:
        type (int): Type of exception (TTDExceptionType.Software or TTDExceptionType.Hardware)
        program_counter (int): Instruction where exception was thrown
        code (int): Exception code
        flags (int): Exception flags
        record_address (int): Where in memory the exception record is found
        position (TTDPosition): Position where exception occurred
    """

TTDEventType

TTD Event Type enumeration for different types of events in TTD traces.

class TTDEventType:
    """
    TTD Event Type enumeration for different types of events in TTD traces.
    """
    ThreadCreated = 0      # Thread creation events
    ThreadTerminated = 1   # Thread termination events
    ModuleLoaded = 2       # Module load events
    ModuleUnloaded = 3     # Module unload events
    Exception = 4          # Exception events

TTDExceptionType

TTD Exception Type enumeration for different types of exceptions.

class TTDExceptionType:
    """
    TTD Exception Type enumeration for different types of exceptions.
    """
    Software = 0  # Software exceptions
    Hardware = 1  # Hardware exceptions

Constants and Access Types

DebuggerTTDMemoryAccessType Enum

TTD memory access types are provided through the auto-generated DebuggerTTDMemoryAccessType enum:

# TTD Memory Access Types (auto-generated enum)
DebuggerTTDMemoryAccessType.DebuggerTTDMemoryRead       # Memory read operations
DebuggerTTDMemoryAccessType.DebuggerTTDMemoryWrite      # Memory write operations  
DebuggerTTDMemoryAccessType.DebuggerTTDMemoryExecute    # Memory execute operations

# Can be combined with bitwise OR:
DebuggerTTDMemoryAccessType.DebuggerTTDMemoryRead | DebuggerTTDMemoryAccessType.DebuggerTTDMemoryWrite

String-based Access Type Parsing

For convenience, you can also specify access types using string notation:

# String specifications (case-insensitive)
"r"     # Read only
"w"     # Write only  
"e"     # Execute only
"rw"    # Read and write
"rwe"   # Read, write, and execute
"we"    # Write and execute
# etc.

Access Type Parsing Function

def parse_ttd_access_type(access_spec):
    """
    Parse TTD memory access type from string specification.
    
    Args:
        access_spec: String containing access type specification or enum value
                     
    Returns:
        DebuggerTTDMemoryAccessType enum value
    """

DebuggerController Methods

is_ttd (property)

@property
def is_ttd(self) -> bool:
    """Check if the current debugging session supports TTD."""

get_ttd_memory_access_for_address()

def get_ttd_memory_access_for_address(
    self,
    address: int,
    end_address: int,
    access_type = DebuggerTTDMemoryAccessType.DebuggerTTDMemoryRead
) -> List[TTDMemoryEvent]:
    """
    Get TTD memory access events for a specific address range.

    Args:
        address: Starting memory address to query
        end_address: Ending memory address to query
        access_type: Type of memory access to query - can be:
                    - DebuggerTTDMemoryAccessType enum values
                    - String specification like "r", "w", "e", "rw", "rwe", etc.
                    - Integer values (for backward compatibility)

    Returns:
        List of TTDMemoryEvent objects
    """

get_ttd_calls_for_symbols()

def get_ttd_calls_for_symbols(
    self,
    symbols: str,
    start_return_address: int = 0,
    end_return_address: int = 0
) -> List[TTDCallEvent]:
    """
    Get TTD call events for specific symbols/functions.

    Args:
        symbols: Symbol or function name to query. Must include module name.
                 Examples: "user32!MessageBoxA", "kernel32!*", "*!malloc"
                 Multiple symbols can be comma-separated: "ntdll!NtCreateFile, kernel32!CreateFileA"
        start_return_address: Optional start return address filter (0 = no filter)
        end_return_address: Optional end return address filter (0 = no filter)

    Returns:
        List of TTDCallEvent objects
    """

get_ttd_events()

def get_ttd_events(self, event_type: int) -> List[TTDEvent]:
    """
    Get TTD events for a specific event type.
    
    Args:
        event_type: Type of event to query (TTDEventType enum value)
                   - TTDEventType.ThreadCreated: Thread creation events
                   - TTDEventType.ThreadTerminated: Thread termination events  
                   - TTDEventType.ModuleLoaded: Module load events
                   - TTDEventType.ModuleUnloaded: Module unload events
                   - TTDEventType.Exception: Exception events
                   
    Returns:
        List of TTDEvent objects containing event details
    """

get_current_ttd_position()

def get_current_ttd_position(self) -> TTDPosition:
    """Get the current position in the TTD trace."""

set_ttd_position()

def set_ttd_position(self, position: TTDPosition) -> bool:
    """
    Set the current position in the TTD trace.
    
    Args:
        position: TTDPosition object to navigate to
        
    Returns:
        True if successful, False otherwise
    """

Usage Examples

Basic TTD Check

# Always check TTD availability first
if not dbg.is_ttd:
    print("TTD is not available in this session")
    return

Memory Access Analysis

# Using enum values
memory_events = dbg.get_ttd_memory_access_for_address(
    0x401000, 0x401004, DebuggerTTDMemoryAccessType.DebuggerTTDMemoryRead
)

# Using string specification (more convenient)
memory_events = dbg.get_ttd_memory_access_for_address(0x401000, 0x401004, "r")

print(f"Found {len(memory_events)} memory read events at {0x401000:#x}")
for event in memory_events:
    print(f"  Thread {event.thread_id}: read {event.value:#x} at {event.time_start}")

# Get all memory access types - using string specification
all_events = dbg.get_ttd_memory_access_for_address(0x401000, 0x401004, "rwe")

# Or using enum values with bitwise OR
all_events = dbg.get_ttd_memory_access_for_address(
    0x401000, 0x401004,
    DebuggerTTDMemoryAccessType.DebuggerTTDMemoryRead |
    DebuggerTTDMemoryAccessType.DebuggerTTDMemoryWrite |
    DebuggerTTDMemoryAccessType.DebuggerTTDMemoryExecute
)

Function Call Analysis

# Get all calls to a specific function (module name required)
call_events = dbg.get_ttd_calls_for_symbols("user32!MessageBoxA")

print(f"Found {len(call_events)} calls to MessageBoxA")
for call in call_events:
    print(f"  Call at {call.time_start}:")
    print(f"    Function: {call.function} @ {call.function_address:#x}")
    print(f"    Return address: {call.return_address:#x}")
    print(f"    Parameters: {call.parameters}")
    if call.has_return_value:
        print(f"    Return value: {call.return_value:#x}")

TTD Navigation

# Save current position
current_pos = dbg.get_current_ttd_position()
print(f"Current position: {current_pos.sequence:#x}:{current_pos.step:#x}")

# Navigate to a specific memory event
if memory_events:
    target_event = memory_events[0]
    if dbg.set_ttd_position(target_event.time_start):
        print(f"Navigated to position {target_event.time_start}")
    else:
        print("Failed to navigate to target position")

# Return to saved position
dbg.set_ttd_position(current_pos)

TTD Events Analysis

# Query different types of events
thread_events = dbg.get_ttd_events(TTDEventType.ThreadCreated)
print(f"Found {len(thread_events)} thread creation events")

for event in thread_events:
    print(f"Thread created at {event.position}")
    if event.thread:
        print(f"  TID: {event.thread.id}, Unique ID: {event.thread.unique_id}")
        print(f"  Lifetime: {event.thread.lifetime_start} to {event.thread.lifetime_end}")

# Query module load events
module_events = dbg.get_ttd_events(TTDEventType.ModuleLoaded)
print(f"\\nFound {len(module_events)} module load events")

for event in module_events:
    if event.module:
        print(f"Module loaded: {event.module.name} @ {event.module.address:#x}")
        print(f"  Size: {event.module.size} bytes")
        print(f"  Position: {event.position}")

# Query exception events
exception_events = dbg.get_ttd_events(TTDEventType.Exception)
print(f"\\nFound {len(exception_events)} exception events")

for event in exception_events:
    if event.exception:
        exc_type = "Hardware" if event.exception.type == TTDExceptionType.Hardware else "Software"
        print(f"{exc_type} exception at {event.exception.program_counter:#x}")
        print(f"  Code: {event.exception.code:#x}, Flags: {event.exception.flags:#x}")
        print(f"  Position: {event.position}")

Advanced Analysis Example

def analyze_function_memory_usage(dbg, function_symbol, address_range):
    """Analyze memory usage during specific function calls.

    Args:
        function_symbol: Symbol with module name, e.g., "kernel32!CreateFileA"
        address_range: Tuple of (start_address, end_address)
    """

    if not dbg.is_ttd:
        print("TTD not available")
        return

    # Get function calls (module name required)
    calls = dbg.get_ttd_calls_for_symbols(function_symbol)
    print(f"Analyzing {len(calls)} calls to {function_symbol}")

    for call in calls:
        print(f"\\nCall at {call.time_start}:")

        # Get memory events during this call (using string specification)
        start_addr, end_addr = address_range
        memory_events = dbg.get_ttd_memory_access_for_address(start_addr, end_addr, "rw")
        
        # Filter events that occurred during this call
        call_memory_events = [
            event for event in memory_events
            if (call.time_start.sequence <= event.time_start.sequence <= call.time_end.sequence)
        ]
        
        print(f"  Memory accesses during call: {len(call_memory_events)}")
        for event in call_memory_events[:5]:  # Show first 5
            access_type = "read" if event.access_type == DebuggerTTDMemoryAccessType.DebuggerTTDMemoryRead else "write"
            print(f"    {access_type} @ {event.address:#x}: {event.value:#x}")

# Usage
analyze_function_memory_usage(dbg, "kernel32!CreateFileA", (0x401000, 0x100))

Error Handling

TTD methods may raise exceptions if: - TTD is not available in the current session - Invalid parameters are provided
- The underlying TTD system encounters an error

Always wrap TTD calls in try-catch blocks for production code:

try:
    if dbg.is_ttd:
        events = dbg.get_ttd_memory_access_for_address(address, end_address)
        # Process events...
    else:
        print("TTD not available")
except Exception as e:
    print(f"TTD operation failed: {e}")

Performance Considerations

  • TTD queries can be expensive for large traces or wide address ranges
  • Consider filtering by specific access types to reduce result sets
  • Use return address filters for call queries when analyzing specific code regions
  • Cache TTD results when possible to avoid repeated queries

Code Coverage Analysis API

is_instruction_executed()

def is_instruction_executed(self, address: int) -> bool:
    """
    Check if a specific instruction was executed during the TTD trace.

    Note: Requires code coverage analysis to have been run first.

    Args:
        address: Instruction address to check

    Returns:
        True if the instruction was executed, False otherwise
    """

run_code_coverage_analysis()

def run_code_coverage_analysis(self) -> bool:
    """
    Run code coverage analysis on the entire TTD trace.

    This analyzes the trace to determine which instructions were executed
    and stores the results for visualization and querying.

    Returns:
        True if analysis completed successfully, False otherwise
    """

get_executed_instruction_count()

def get_executed_instruction_count(self) -> int:
    """
    Get the number of unique instructions that were executed.

    Note: Requires code coverage analysis to have been run first.

    Returns:
        Number of executed instructions
    """

save_code_coverage_to_file()

def save_code_coverage_to_file(self, file_path: str) -> bool:
    """
    Save code coverage analysis results to a file.

    Args:
        file_path: Path where coverage data should be saved

    Returns:
        True if save was successful, False otherwise
    """

load_code_coverage_from_file()

def load_code_coverage_from_file(self, file_path: str) -> bool:
    """
    Load code coverage analysis results from a file.

    Args:
        file_path: Path to coverage data file

    Returns:
        True if load was successful, False otherwise
    """

Code Coverage Example

# Run code coverage analysis
if dbg.is_ttd:
    print("Running code coverage analysis...")
    if dbg.run_code_coverage_analysis():
        count = dbg.get_executed_instruction_count()
        print(f"Analysis complete: {count} instructions executed")

        # Check specific instructions
        if dbg.is_instruction_executed(0x401000):
            print("Instruction at 0x401000 was executed")

        # Save results for later
        dbg.save_code_coverage_to_file("/path/to/coverage.data")
    else:
        print("Analysis failed")

# Later, load cached results
if dbg.load_code_coverage_from_file("/path/to/coverage.data"):
    print(f"Loaded {dbg.get_executed_instruction_count()} executed instructions")

Best Practices

Query Optimization

  • Use specific address ranges when possible to limit result sets
  • Filter by access type to get only relevant memory events
  • Cache TTD query results if you'll need them multiple times
  • Use return address filters for call queries when analyzing specific code regions

Position Management

# Save current position before analysis
saved_pos = dbg.get_current_ttd_position()

# Do analysis work...
for event in memory_events:
    dbg.set_ttd_position(event.time_start)
    # Analyze state at this position

# Restore original position
dbg.set_ttd_position(saved_pos)

Error Handling

Always check if TTD is available and handle potential errors:

try:
    if not dbg.is_ttd:
        print("TTD not available")
        return

    events = dbg.get_ttd_memory_access_for_address(address, end_address, "rw")
    # Process events...

except Exception as e:
    print(f"TTD operation failed: {e}")

See Also