@@ -721,6 +721,228 @@ def __repr__(self):
721721 return f"<TTDCallEvent: { self .function } @ { self .function_address :#x} , thread { self .thread_id } >"
722722
723723
724+ class TTDEventType :
725+ """
726+ TTD Event Type enumeration for different types of events in TTD traces.
727+ """
728+ ThreadCreated = 0
729+ ThreadTerminated = 1
730+ ModuleLoaded = 2
731+ ModuleUnloaded = 3
732+ Exception = 4
733+
734+
735+ class TTDModule :
736+ """
737+ TTDModule represents information about modules that were loaded/unloaded during a TTD trace.
738+
739+ Attributes:
740+ name (str): name and path of the module
741+ address (int): address where the module was loaded
742+ size (int): size of the module in bytes
743+ checksum (int): checksum of the module
744+ timestamp (int): timestamp of the module
745+ """
746+
747+ def __init__ (self , name : str , address : int , size : int , checksum : int , timestamp : int ):
748+ self .name = name
749+ self .address = address
750+ self .size = size
751+ self .checksum = checksum
752+ self .timestamp = timestamp
753+
754+ def __eq__ (self , other ):
755+ if not isinstance (other , self .__class__ ):
756+ return NotImplemented
757+ return (self .name == other .name and
758+ self .address == other .address and
759+ self .size == other .size and
760+ self .checksum == other .checksum and
761+ self .timestamp == other .timestamp )
762+
763+ def __ne__ (self , other ):
764+ if not isinstance (other , self .__class__ ):
765+ return NotImplemented
766+ return not (self == other )
767+
768+ def __hash__ (self ):
769+ return hash ((self .name , self .address , self .size , self .checksum , self .timestamp ))
770+
771+ def __setattr__ (self , name , value ):
772+ try :
773+ object .__setattr__ (self , name , value )
774+ except AttributeError :
775+ raise AttributeError (f"attribute '{ name } ' is read only" )
776+
777+ def __repr__ (self ):
778+ return f"<TTDModule: { self .name } @ { self .address :#x} , size { self .size } >"
779+
780+
781+ class TTDThread :
782+ """
783+ TTDThread represents information about threads and their lifetime during a TTD trace.
784+
785+ Attributes:
786+ unique_id (int): unique ID for the thread across the trace
787+ id (int): TID of the thread
788+ lifetime_start (TTDPosition): lifetime start position
789+ lifetime_end (TTDPosition): lifetime end position
790+ active_time_start (TTDPosition): active time start position
791+ active_time_end (TTDPosition): active time end position
792+ """
793+
794+ def __init__ (self , unique_id : int , id : int , lifetime_start : TTDPosition , lifetime_end : TTDPosition ,
795+ active_time_start : TTDPosition , active_time_end : TTDPosition ):
796+ self .unique_id = unique_id
797+ self .id = id
798+ self .lifetime_start = lifetime_start
799+ self .lifetime_end = lifetime_end
800+ self .active_time_start = active_time_start
801+ self .active_time_end = active_time_end
802+
803+ def __eq__ (self , other ):
804+ if not isinstance (other , self .__class__ ):
805+ return NotImplemented
806+ return (self .unique_id == other .unique_id and
807+ self .id == other .id and
808+ self .lifetime_start == other .lifetime_start and
809+ self .lifetime_end == other .lifetime_end and
810+ self .active_time_start == other .active_time_start and
811+ self .active_time_end == other .active_time_end )
812+
813+ def __ne__ (self , other ):
814+ if not isinstance (other , self .__class__ ):
815+ return NotImplemented
816+ return not (self == other )
817+
818+ def __hash__ (self ):
819+ return hash ((self .unique_id , self .id , self .lifetime_start , self .lifetime_end ,
820+ self .active_time_start , self .active_time_end ))
821+
822+ def __setattr__ (self , name , value ):
823+ try :
824+ object .__setattr__ (self , name , value )
825+ except AttributeError :
826+ raise AttributeError (f"attribute '{ name } ' is read only" )
827+
828+ def __repr__ (self ):
829+ return f"<TTDThread: TID { self .id } , UniqueID { self .unique_id } >"
830+
831+
832+ class TTDExceptionType :
833+ """
834+ TTD Exception Type enumeration for different types of exceptions.
835+ """
836+ Software = 0
837+ Hardware = 1
838+
839+
840+ class TTDException :
841+ """
842+ TTDException represents information about exceptions that occurred during a TTD trace.
843+
844+ Attributes:
845+ type (int): type of exception (TTDExceptionType.Software or TTDExceptionType.Hardware)
846+ program_counter (int): instruction where exception was thrown
847+ code (int): exception code
848+ flags (int): exception flags
849+ record_address (int): where in memory the exception record is found
850+ position (TTDPosition): position where exception occurred
851+ """
852+
853+ def __init__ (self , type : int , program_counter : int , code : int , flags : int ,
854+ record_address : int , position : TTDPosition ):
855+ self .type = type
856+ self .program_counter = program_counter
857+ self .code = code
858+ self .flags = flags
859+ self .record_address = record_address
860+ self .position = position
861+
862+ def __eq__ (self , other ):
863+ if not isinstance (other , self .__class__ ):
864+ return NotImplemented
865+ return (self .type == other .type and
866+ self .program_counter == other .program_counter and
867+ self .code == other .code and
868+ self .flags == other .flags and
869+ self .record_address == other .record_address and
870+ self .position == other .position )
871+
872+ def __ne__ (self , other ):
873+ if not isinstance (other , self .__class__ ):
874+ return NotImplemented
875+ return not (self == other )
876+
877+ def __hash__ (self ):
878+ return hash ((self .type , self .program_counter , self .code , self .flags ,
879+ self .record_address , self .position ))
880+
881+ def __setattr__ (self , name , value ):
882+ try :
883+ object .__setattr__ (self , name , value )
884+ except AttributeError :
885+ raise AttributeError (f"attribute '{ name } ' is read only" )
886+
887+ def __repr__ (self ):
888+ type_str = "Hardware" if self .type == TTDExceptionType .Hardware else "Software"
889+ return f"<TTDException: { type_str } @ { self .program_counter :#x} , code { self .code :#x} >"
890+
891+
892+ class TTDEvent :
893+ """
894+ TTDEvent represents important events that happened during a TTD trace.
895+
896+ Attributes:
897+ type (int): type of event (TTDEventType enum value)
898+ position (TTDPosition): position where event occurred
899+ module (TTDModule or None): module information for ModuleLoaded/ModuleUnloaded events
900+ thread (TTDThread or None): thread information for ThreadCreated/ThreadTerminated events
901+ exception (TTDException or None): exception information for Exception events
902+ """
903+
904+ def __init__ (self , type : int , position : TTDPosition , module = None , thread = None , exception = None ):
905+ self .type = type
906+ self .position = position
907+ self .module = module
908+ self .thread = thread
909+ self .exception = exception
910+
911+ def __eq__ (self , other ):
912+ if not isinstance (other , self .__class__ ):
913+ return NotImplemented
914+ return (self .type == other .type and
915+ self .position == other .position and
916+ self .module == other .module and
917+ self .thread == other .thread and
918+ self .exception == other .exception )
919+
920+ def __ne__ (self , other ):
921+ if not isinstance (other , self .__class__ ):
922+ return NotImplemented
923+ return not (self == other )
924+
925+ def __hash__ (self ):
926+ return hash ((self .type , self .position , self .module , self .thread , self .exception ))
927+
928+ def __setattr__ (self , name , value ):
929+ try :
930+ object .__setattr__ (self , name , value )
931+ except AttributeError :
932+ raise AttributeError (f"attribute '{ name } ' is read only" )
933+
934+ def __repr__ (self ):
935+ type_names = {
936+ TTDEventType .ThreadCreated : "ThreadCreated" ,
937+ TTDEventType .ThreadTerminated : "ThreadTerminated" ,
938+ TTDEventType .ModuleLoaded : "ModuleLoaded" ,
939+ TTDEventType .ModuleUnloaded : "ModuleUnloaded" ,
940+ TTDEventType .Exception : "Exception"
941+ }
942+ type_str = type_names .get (self .type , f"Unknown({ self .type } )" )
943+ return f"<TTDEvent: { type_str } @ { self .position } >"
944+
945+
724946class DebuggerController :
725947 """
726948 The ``DebuggerController`` object is the core of the debugger. Most debugger operations can be performed on it.
@@ -2042,6 +2264,86 @@ def get_ttd_calls_for_symbols(self, symbols: str, start_return_address: int = 0,
20422264 dbgcore .BNDebuggerFreeTTDCallEvents (events , count .value )
20432265 return result
20442266
2267+ def get_ttd_events (self , event_type : int ) -> List [TTDEvent ]:
2268+ """
2269+ Get TTD events for a specific event type.
2270+
2271+ This method is only available when debugging with TTD (Time Travel Debugging).
2272+ Use the is_ttd property to check if TTD is available before calling this method.
2273+
2274+ :param event_type: type of event to query (TTDEventType enum value)
2275+ :return: list of TTDEvent objects
2276+ :rtype: List[TTDEvent]
2277+ """
2278+ if self .handle is None :
2279+ return []
2280+
2281+ count = ctypes .c_size_t ()
2282+ events = dbgcore .BNDebuggerGetTTDEvents (self .handle , event_type , ctypes .byref (count ))
2283+
2284+ result = []
2285+ if not events or count .value == 0 :
2286+ return result
2287+
2288+ for i in range (count .value ):
2289+ event = events [i ]
2290+
2291+ position = TTDPosition (event .position .sequence , event .position .step )
2292+
2293+ # Convert optional module details
2294+ module = None
2295+ if event .module :
2296+ module = TTDModule (
2297+ name = event .module .contents .name if event .module .contents .name else "" ,
2298+ address = event .module .contents .address ,
2299+ size = event .module .contents .size ,
2300+ checksum = event .module .contents .checksum ,
2301+ timestamp = event .module .contents .timestamp
2302+ )
2303+
2304+ # Convert optional thread details
2305+ thread = None
2306+ if event .thread :
2307+ lifetime_start = TTDPosition (event .thread .contents .lifetimeStart .sequence , event .thread .contents .lifetimeStart .step )
2308+ lifetime_end = TTDPosition (event .thread .contents .lifetimeEnd .sequence , event .thread .contents .lifetimeEnd .step )
2309+ active_time_start = TTDPosition (event .thread .contents .activeTimeStart .sequence , event .thread .contents .activeTimeStart .step )
2310+ active_time_end = TTDPosition (event .thread .contents .activeTimeEnd .sequence , event .thread .contents .activeTimeEnd .step )
2311+
2312+ thread = TTDThread (
2313+ unique_id = event .thread .contents .uniqueId ,
2314+ id = event .thread .contents .id ,
2315+ lifetime_start = lifetime_start ,
2316+ lifetime_end = lifetime_end ,
2317+ active_time_start = active_time_start ,
2318+ active_time_end = active_time_end
2319+ )
2320+
2321+ # Convert optional exception details
2322+ exception = None
2323+ if event .exception :
2324+ exception_position = TTDPosition (event .exception .contents .position .sequence , event .exception .contents .position .step )
2325+
2326+ exception = TTDException (
2327+ type = event .exception .contents .type ,
2328+ program_counter = event .exception .contents .programCounter ,
2329+ code = event .exception .contents .code ,
2330+ flags = event .exception .contents .flags ,
2331+ record_address = event .exception .contents .recordAddress ,
2332+ position = exception_position
2333+ )
2334+
2335+ ttd_event = TTDEvent (
2336+ type = event .type ,
2337+ position = position ,
2338+ module = module ,
2339+ thread = thread ,
2340+ exception = exception
2341+ )
2342+ result .append (ttd_event )
2343+
2344+ dbgcore .BNDebuggerFreeTTDEvents (events , count .value )
2345+ return result
2346+
20452347 def __del__ (self ):
20462348 if dbgcore is not None :
20472349 dbgcore .BNDebuggerFreeController (self .handle )
0 commit comments