Skip to content

Commit 5e4b750

Browse files
Copilotxusheng6
andcommitted
Complete TTD.Events implementation with Python API and documentation
Co-authored-by: xusheng6 <[email protected]>
1 parent 3056fa7 commit 5e4b750

File tree

3 files changed

+462
-0
lines changed

3 files changed

+462
-0
lines changed

api/python/debuggercontroller.py

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
724946
class 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

Comments
 (0)