@@ -804,6 +804,68 @@ def __repr__(self):
804804 return f"<TTDMemoryEvent: { self .event_type } @ { self .address :#x} , thread { self .thread_id } >"
805805
806806
807+ class TTDPositionRangeIndexedMemoryEvent :
808+ """
809+ TTDPositionRangeIndexedMemoryEvent represents a memory access event in a TTD trace with position information.
810+ This is used for memory queries filtered by both address range and time range.
811+
812+ * ``position``: TTD position when the memory access occurred
813+ * ``thread_id``: OS thread ID that performed the memory access
814+ * ``unique_thread_id``: unique thread ID across the trace
815+ * ``address``: memory address that was accessed
816+ * ``instruction_address``: address of the instruction that performed the access
817+ * ``size``: size of the memory access in bytes
818+ * ``access_type``: type of access (read/write/execute)
819+ * ``value``: value that was read/written/executed (truncated to size)
820+ * ``data``: the next 8 bytes of data at the memory address (as a bytes object)
821+ """
822+
823+ def __init__ (self , position : TTDPosition , thread_id : int , unique_thread_id : int ,
824+ address : int , instruction_address : int , size : int ,
825+ access_type : int , value : int , data : bytes ):
826+ self .position = position
827+ self .thread_id = thread_id
828+ self .unique_thread_id = unique_thread_id
829+ self .address = address
830+ self .instruction_address = instruction_address
831+ self .size = size
832+ self .access_type = access_type
833+ self .value = value
834+ self .data = data
835+
836+ def __eq__ (self , other ):
837+ if not isinstance (other , self .__class__ ):
838+ return NotImplemented
839+ return (self .position == other .position and
840+ self .thread_id == other .thread_id and
841+ self .unique_thread_id == other .unique_thread_id and
842+ self .address == other .address and
843+ self .instruction_address == other .instruction_address and
844+ self .size == other .size and
845+ self .access_type == other .access_type and
846+ self .value == other .value and
847+ self .data == other .data )
848+
849+ def __ne__ (self , other ):
850+ if not isinstance (other , self .__class__ ):
851+ return NotImplemented
852+ return not (self == other )
853+
854+ def __hash__ (self ):
855+ return hash ((self .position , self .thread_id , self .unique_thread_id ,
856+ self .address , self .instruction_address , self .size ,
857+ self .access_type , self .value , self .data ))
858+
859+ def __setattr__ (self , name , value ):
860+ try :
861+ object .__setattr__ (self , name , value )
862+ except AttributeError :
863+ raise AttributeError (f"attribute '{ name } ' is read only" )
864+
865+ def __repr__ (self ):
866+ return f"<TTDPositionRangeIndexedMemoryEvent: @ { self .address :#x} , pos { self .position } , thread { self .thread_id } >"
867+
868+
807869class TTDCallEvent :
808870 """
809871 TTDCallEvent represents a function call event in a TTD trace. It has the following fields:
@@ -2540,6 +2602,75 @@ def get_ttd_memory_access_for_address(self, address: int, end_address: int, acce
25402602 dbgcore .BNDebuggerFreeTTDMemoryEvents (events , count .value )
25412603 return result
25422604
2605+ def get_ttd_memory_access_for_position_range (self , start_address : int , end_address : int , access_type ,
2606+ start_time : TTDPosition = None , end_time : TTDPosition = None ) -> List [TTDPositionRangeIndexedMemoryEvent ]:
2607+ """
2608+ Get TTD memory access events for a specific address range and time range.
2609+
2610+ This method is only available when debugging with TTD (Time Travel Debugging).
2611+ Use the is_ttd property to check if TTD is available before calling this method.
2612+
2613+ :param start_address: starting memory address to query
2614+ :param end_address: ending memory address to query
2615+ :param access_type: type of memory access to query - can be:
2616+ - DebuggerTTDMemoryAccessType enum values
2617+ - String specification like "r", "w", "e", "rw", "rwe", etc.
2618+ - Integer values (for backward compatibility)
2619+ :param start_time: starting TTD position (default: start of trace)
2620+ :param end_time: ending TTD position (default: end of trace)
2621+ :return: list of TTDPositionRangeIndexedMemoryEvent objects
2622+ :raises: May raise an exception if TTD is not available
2623+ """
2624+ # Parse access type if it's a string
2625+ parsed_access_type = parse_ttd_access_type (access_type )
2626+
2627+ # Set default time range if not provided
2628+ if start_time is None :
2629+ start_time = TTDPosition (0 , 0 )
2630+ if end_time is None :
2631+ end_time = TTDPosition (0xFFFFFFFFFFFFFFFF , 0xFFFFFFFFFFFFFFFF )
2632+
2633+ # Create ctypes structures for positions
2634+ bn_start_pos = dbgcore .BNDebuggerTTDPosition ()
2635+ bn_start_pos .sequence = start_time .sequence
2636+ bn_start_pos .step = start_time .step
2637+
2638+ bn_end_pos = dbgcore .BNDebuggerTTDPosition ()
2639+ bn_end_pos .sequence = end_time .sequence
2640+ bn_end_pos .step = end_time .step
2641+
2642+ count = ctypes .c_ulonglong ()
2643+ events = dbgcore .BNDebuggerGetTTDMemoryAccessForPositionRange (
2644+ self .handle , start_address , end_address , parsed_access_type ,
2645+ bn_start_pos , bn_end_pos , count )
2646+
2647+ if not events :
2648+ return []
2649+
2650+ result = []
2651+ for i in range (count .value ):
2652+ event = events [i ]
2653+ position = TTDPosition (event .position .sequence , event .position .step )
2654+
2655+ # Convert data array to bytes
2656+ data = bytes (event .data [j ] for j in range (8 ))
2657+
2658+ memory_event = TTDPositionRangeIndexedMemoryEvent (
2659+ position = position ,
2660+ thread_id = event .threadId ,
2661+ unique_thread_id = event .uniqueThreadId ,
2662+ address = event .address ,
2663+ instruction_address = event .instructionAddress ,
2664+ size = event .size ,
2665+ access_type = event .accessType ,
2666+ value = event .value ,
2667+ data = data
2668+ )
2669+ result .append (memory_event )
2670+
2671+ dbgcore .BNDebuggerFreeTTDPositionRangeIndexedMemoryEvents (events , count .value )
2672+ return result
2673+
25432674 def get_ttd_calls_for_symbols (self , symbols : str , start_return_address : int = 0 , end_return_address : int = 0 ) -> List [TTDCallEvent ]:
25442675 """
25452676 Get TTD call events for specific symbols/functions.
@@ -2748,6 +2879,84 @@ def get_all_ttd_events(self) -> List[TTDEvent]:
27482879 dbgcore .BNDebuggerFreeTTDEvents (events , count .value )
27492880 return result
27502881
2882+ def run_code_coverage_analysis (self , start_address : int , end_address : int ,
2883+ start_time : TTDPosition = None , end_time : TTDPosition = None ) -> bool :
2884+ """
2885+ Run code coverage analysis on a specific address range and time range.
2886+
2887+ This method is only available when debugging with TTD (Time Travel Debugging).
2888+ Use the is_ttd property to check if TTD is available before calling this method.
2889+
2890+ The code coverage analysis identifies which instructions within the specified address range
2891+ were executed during the specified time range. Results can be queried using the
2892+ is_instruction_executed() method.
2893+
2894+ :param start_address: starting address of the range to analyze
2895+ :param end_address: ending address of the range to analyze
2896+ :param start_time: starting TTD position (default: start of trace)
2897+ :param end_time: ending TTD position (default: end of trace)
2898+ :return: True if analysis succeeded, False otherwise
2899+ :raises: May raise an exception if TTD is not available
2900+ """
2901+ # Set default time range if not provided
2902+ if start_time is None :
2903+ start_time = TTDPosition (0 , 0 )
2904+ if end_time is None :
2905+ end_time = TTDPosition (0xFFFFFFFFFFFFFFFF , 0xFFFFFFFFFFFFFFFF )
2906+
2907+ # Create ctypes structures for positions
2908+ bn_start_pos = dbgcore .BNDebuggerTTDPosition ()
2909+ bn_start_pos .sequence = start_time .sequence
2910+ bn_start_pos .step = start_time .step
2911+
2912+ bn_end_pos = dbgcore .BNDebuggerTTDPosition ()
2913+ bn_end_pos .sequence = end_time .sequence
2914+ bn_end_pos .step = end_time .step
2915+
2916+ return dbgcore .BNDebuggerRunCodeCoverageAnalysisRange (
2917+ self .handle , start_address , end_address , bn_start_pos , bn_end_pos )
2918+
2919+ def is_instruction_executed (self , address : int ) -> bool :
2920+ """
2921+ Check if an instruction at a specific address was executed during code coverage analysis.
2922+
2923+ This method requires that run_code_coverage_analysis() has been called first.
2924+
2925+ :param address: address of the instruction to check
2926+ :return: True if the instruction was executed, False otherwise
2927+ """
2928+ return dbgcore .BNDebuggerIsInstructionExecuted (self .handle , address )
2929+
2930+ def get_executed_instruction_count (self ) -> int :
2931+ """
2932+ Get the count of executed instructions from the last code coverage analysis.
2933+
2934+ This method requires that run_code_coverage_analysis() has been called first.
2935+
2936+ :return: number of unique executed instructions
2937+ """
2938+ return dbgcore .BNDebuggerGetExecutedInstructionCount (self .handle )
2939+
2940+ def save_code_coverage_to_file (self , file_path : str ) -> bool :
2941+ """
2942+ Save code coverage results to a file.
2943+
2944+ This method requires that run_code_coverage_analysis() has been called first.
2945+
2946+ :param file_path: path to the file where results should be saved
2947+ :return: True if save succeeded, False otherwise
2948+ """
2949+ return dbgcore .BNDebuggerSaveCodeCoverageToFile (self .handle , file_path .encode ('utf-8' ))
2950+
2951+ def load_code_coverage_from_file (self , file_path : str ) -> bool :
2952+ """
2953+ Load code coverage results from a file.
2954+
2955+ :param file_path: path to the file containing code coverage results
2956+ :return: True if load succeeded, False otherwise
2957+ """
2958+ return dbgcore .BNDebuggerLoadCodeCoverageFromFile (self .handle , file_path .encode ('utf-8' ))
2959+
27512960 def __del__ (self ):
27522961 if dbgcore is not None :
27532962 dbgcore .BNDebuggerFreeController (self .handle )
0 commit comments