diff --git a/.gitignore b/.gitignore index 547178fc..ac3ac897 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,4 @@ api/python/__pycache__ # TTD files *.run *.idx +*.bndb \ No newline at end of file diff --git a/api/debuggerapi.h b/api/debuggerapi.h index 2d3cf9e5..913d220a 100644 --- a/api/debuggerapi.h +++ b/api/debuggerapi.h @@ -19,6 +19,7 @@ limitations under the License. #include "binaryninjaapi.h" #include "ffi.h" #include "../vendor/intx/intx.hpp" +#include using namespace BinaryNinja; @@ -517,6 +518,78 @@ namespace BinaryNinjaDebuggerAPI { TTDCallEvent() : threadId(0), uniqueThreadId(0), functionAddress(0), returnAddress(0), returnValue(0), hasReturnValue(false) {} }; + // TTD Event Types - bitfield flags for filtering events + enum TTDEventType + { + TTDEventNone = 0, + TTDEventThreadCreated = 1, + TTDEventThreadTerminated = 2, + TTDEventModuleLoaded = 4, + TTDEventModuleUnloaded = 8, + TTDEventException = 16, + TTDEventAll = TTDEventThreadCreated | TTDEventThreadTerminated | TTDEventModuleLoaded | TTDEventModuleUnloaded | TTDEventException + }; + + // TTD Module - information about modules that were loaded/unloaded during trace + struct TTDModule + { + std::string name; // Name and path of the module + uint64_t address; // Address where the module was loaded + uint64_t size; // Size of the module in bytes + uint32_t checksum; // Checksum of the module + uint32_t timestamp; // Timestamp of the module + + TTDModule() : address(0), size(0), checksum(0), timestamp(0) {} + }; + + // TTD Thread - information about threads and their lifetime during trace + struct TTDThread + { + uint32_t uniqueId; // Unique ID for the thread across the trace + uint32_t id; // TID of the thread + TTDPosition lifetimeStart; // Lifetime start position + TTDPosition lifetimeEnd; // Lifetime end position + TTDPosition activeTimeStart; // Active time start position + TTDPosition activeTimeEnd; // Active time end position + + TTDThread() : uniqueId(0), id(0) {} + }; + + // TTD Exception Types + enum TTDExceptionType + { + TTDExceptionSoftware, + TTDExceptionHardware + }; + + // TTD Exception - information about exceptions that occurred during trace + struct TTDException + { + TTDExceptionType type; // Type of exception (Software/Hardware) + uint64_t programCounter; // Instruction where exception was thrown + uint32_t code; // Exception code + uint32_t flags; // Exception flags + uint64_t recordAddress; // Where in memory the exception record is found + TTDPosition position; // Position where exception occurred + + TTDException() : type(TTDExceptionSoftware), programCounter(0), code(0), flags(0), recordAddress(0) {} + }; + + // TTD Event - represents important events that happened during trace + struct TTDEvent + { + TTDEventType type; // Type of event + TTDPosition position; // Position where event occurred + + // Optional child objects - existence depends on event type + std::optional module; // For ModuleLoaded/ModuleUnloaded events + std::optional thread; // For ThreadCreated/ThreadTerminated events + std::optional exception; // For Exception events + + TTDEvent() : type(TTDEventThreadCreated) {} + TTDEvent(TTDEventType eventType) : type(eventType) {} + }; + typedef BNDebugAdapterConnectionStatus DebugAdapterConnectionStatus; typedef BNDebugAdapterTargetStatus DebugAdapterTargetStatus; @@ -687,6 +760,8 @@ namespace BinaryNinjaDebuggerAPI { // TTD Memory Analysis Methods std::vector GetTTDMemoryAccessForAddress(uint64_t address, uint64_t size, TTDMemoryAccessType accessType = TTDMemoryRead); std::vector GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress = 0, uint64_t endReturnAddress = 0); + std::vector GetTTDEvents(TTDEventType eventType); + std::vector GetAllTTDEvents(); TTDPosition GetCurrentTTDPosition(); bool SetTTDPosition(const TTDPosition& position); diff --git a/api/debuggercontroller.cpp b/api/debuggercontroller.cpp index 859995ab..beffb6d1 100644 --- a/api/debuggercontroller.cpp +++ b/api/debuggercontroller.cpp @@ -1063,6 +1063,145 @@ std::vector DebuggerController::GetTTDCallsForSymbols(const std::s } +std::vector DebuggerController::GetTTDEvents(TTDEventType eventType) +{ + std::vector result; + + size_t count = 0; + BNDebuggerTTDEvent* events = BNDebuggerGetTTDEvents(m_object, + static_cast(eventType), &count); + + if (events && count > 0) + { + result.reserve(count); + for (size_t i = 0; i < count; i++) + { + TTDEvent event; + event.type = static_cast(events[i].type); + event.position.sequence = events[i].position.sequence; + event.position.step = events[i].position.step; + + // Copy optional module details + if (events[i].module) + { + TTDModule module; + module.name = events[i].module->name ? std::string(events[i].module->name) : ""; + module.address = events[i].module->address; + module.size = events[i].module->size; + module.checksum = events[i].module->checksum; + module.timestamp = events[i].module->timestamp; + event.module = module; + } + + // Copy optional thread details + if (events[i].thread) + { + TTDThread thread; + thread.uniqueId = events[i].thread->uniqueId; + thread.id = events[i].thread->id; + thread.lifetimeStart.sequence = events[i].thread->lifetimeStart.sequence; + thread.lifetimeStart.step = events[i].thread->lifetimeStart.step; + thread.lifetimeEnd.sequence = events[i].thread->lifetimeEnd.sequence; + thread.lifetimeEnd.step = events[i].thread->lifetimeEnd.step; + thread.activeTimeStart.sequence = events[i].thread->activeTimeStart.sequence; + thread.activeTimeStart.step = events[i].thread->activeTimeStart.step; + thread.activeTimeEnd.sequence = events[i].thread->activeTimeEnd.sequence; + thread.activeTimeEnd.step = events[i].thread->activeTimeEnd.step; + event.thread = thread; + } + + // Copy optional exception details + if (events[i].exception) + { + TTDException exception; + exception.type = static_cast(events[i].exception->type); + exception.programCounter = events[i].exception->programCounter; + exception.code = events[i].exception->code; + exception.flags = events[i].exception->flags; + exception.recordAddress = events[i].exception->recordAddress; + exception.position.sequence = events[i].exception->position.sequence; + exception.position.step = events[i].exception->position.step; + event.exception = exception; + } + + result.push_back(event); + } + BNDebuggerFreeTTDEvents(events, count); + } + + return result; +} + + +std::vector DebuggerController::GetAllTTDEvents() +{ + std::vector result; + + size_t count = 0; + BNDebuggerTTDEvent* events = BNDebuggerGetAllTTDEvents(m_object, &count); + + if (events && count > 0) + { + result.reserve(count); + for (size_t i = 0; i < count; i++) + { + TTDEvent event; + event.type = static_cast(events[i].type); + event.position.sequence = events[i].position.sequence; + event.position.step = events[i].position.step; + + // Copy optional module details + if (events[i].module) + { + TTDModule module; + module.name = events[i].module->name ? std::string(events[i].module->name) : ""; + module.address = events[i].module->address; + module.size = events[i].module->size; + module.checksum = events[i].module->checksum; + module.timestamp = events[i].module->timestamp; + event.module = module; + } + + // Copy optional thread details + if (events[i].thread) + { + TTDThread thread; + thread.uniqueId = events[i].thread->uniqueId; + thread.id = events[i].thread->id; + thread.lifetimeStart.sequence = events[i].thread->lifetimeStart.sequence; + thread.lifetimeStart.step = events[i].thread->lifetimeStart.step; + thread.lifetimeEnd.sequence = events[i].thread->lifetimeEnd.sequence; + thread.lifetimeEnd.step = events[i].thread->lifetimeEnd.step; + thread.activeTimeStart.sequence = events[i].thread->activeTimeStart.sequence; + thread.activeTimeStart.step = events[i].thread->activeTimeStart.step; + thread.activeTimeEnd.sequence = events[i].thread->activeTimeEnd.sequence; + thread.activeTimeEnd.step = events[i].thread->activeTimeEnd.step; + event.thread = thread; + } + + // Copy optional exception details + if (events[i].exception) + { + TTDException exception; + exception.type = static_cast(events[i].exception->type); + exception.programCounter = events[i].exception->programCounter; + exception.code = events[i].exception->code; + exception.flags = events[i].exception->flags; + exception.recordAddress = events[i].exception->recordAddress; + exception.position.sequence = events[i].exception->position.sequence; + exception.position.step = events[i].exception->position.step; + event.exception = exception; + } + + result.push_back(event); + } + BNDebuggerFreeTTDEvents(events, count); + } + + return result; +} + + bool DebuggerController::IsInstructionExecuted(uint64_t address) { return BNDebuggerIsInstructionExecuted(m_object, address); diff --git a/api/ffi.h b/api/ffi.h index b2f489ae..2b5a14c9 100644 --- a/api/ffi.h +++ b/api/ffi.h @@ -339,6 +339,69 @@ extern "C" BNDebuggerTTDPosition timeEnd; // Position when call ended } BNDebuggerTTDCallEvent; + // TTD Event Types - bitfield flags for filtering events + typedef enum BNDebuggerTTDEventType + { + BNDebuggerTTDEventNone = 0, + BNDebuggerTTDEventThreadCreated = 1, + BNDebuggerTTDEventThreadTerminated = 2, + BNDebuggerTTDEventModuleLoaded = 4, + BNDebuggerTTDEventModuleUnloaded = 8, + BNDebuggerTTDEventException = 16, + BNDebuggerTTDEventAll = BNDebuggerTTDEventThreadCreated | BNDebuggerTTDEventThreadTerminated | BNDebuggerTTDEventModuleLoaded | BNDebuggerTTDEventModuleUnloaded | BNDebuggerTTDEventException + } BNDebuggerTTDEventType; + + // TTD Module + typedef struct BNDebuggerTTDModule + { + char* name; // Name and path of the module + uint64_t address; // Address where the module was loaded + uint64_t size; // Size of the module in bytes + uint32_t checksum; // Checksum of the module + uint32_t timestamp; // Timestamp of the module + } BNDebuggerTTDModule; + + // TTD Thread + typedef struct BNDebuggerTTDThread + { + uint32_t uniqueId; // Unique ID for the thread across the trace + uint32_t id; // TID of the thread + BNDebuggerTTDPosition lifetimeStart; // Lifetime start position + BNDebuggerTTDPosition lifetimeEnd; // Lifetime end position + BNDebuggerTTDPosition activeTimeStart; // Active time start position + BNDebuggerTTDPosition activeTimeEnd; // Active time end position + } BNDebuggerTTDThread; + + // TTD Exception Types + typedef enum BNDebuggerTTDExceptionType + { + BNDebuggerTTDExceptionSoftware, + BNDebuggerTTDExceptionHardware + } BNDebuggerTTDExceptionType; + + // TTD Exception + typedef struct BNDebuggerTTDException + { + BNDebuggerTTDExceptionType type; // Type of exception (Software/Hardware) + uint64_t programCounter; // Instruction where exception was thrown + uint32_t code; // Exception code + uint32_t flags; // Exception flags + uint64_t recordAddress; // Where in memory the exception record is found + BNDebuggerTTDPosition position; // Position where exception occurred + } BNDebuggerTTDException; + + // TTD Event + typedef struct BNDebuggerTTDEvent + { + BNDebuggerTTDEventType type; // Type of event + BNDebuggerTTDPosition position; // Position where event occurred + + // Optional child objects - existence depends on event type + BNDebuggerTTDModule* module; // For ModuleLoaded/ModuleUnloaded events (NULL if not present) + BNDebuggerTTDThread* thread; // For ThreadCreated/ThreadTerminated events (NULL if not present) + BNDebuggerTTDException* exception; // For Exception events (NULL if not present) + } BNDebuggerTTDEvent; + // This should really be a union, but gcc complains... typedef struct BNDebuggerEventData @@ -555,10 +618,14 @@ extern "C" uint64_t address, uint64_t size, BNDebuggerTTDMemoryAccessType accessType, size_t* count); DEBUGGER_FFI_API BNDebuggerTTDCallEvent* BNDebuggerGetTTDCallsForSymbols(BNDebuggerController* controller, const char* symbols, uint64_t startReturnAddress, uint64_t endReturnAddress, size_t* count); + DEBUGGER_FFI_API BNDebuggerTTDEvent* BNDebuggerGetTTDEvents(BNDebuggerController* controller, + BNDebuggerTTDEventType eventType, size_t* count); + DEBUGGER_FFI_API BNDebuggerTTDEvent* BNDebuggerGetAllTTDEvents(BNDebuggerController* controller, size_t* count); DEBUGGER_FFI_API BNDebuggerTTDPosition BNDebuggerGetCurrentTTDPosition(BNDebuggerController* controller); DEBUGGER_FFI_API bool BNDebuggerSetTTDPosition(BNDebuggerController* controller, BNDebuggerTTDPosition position); DEBUGGER_FFI_API void BNDebuggerFreeTTDMemoryEvents(BNDebuggerTTDMemoryEvent* events, size_t count); DEBUGGER_FFI_API void BNDebuggerFreeTTDCallEvents(BNDebuggerTTDCallEvent* events, size_t count); + DEBUGGER_FFI_API void BNDebuggerFreeTTDEvents(BNDebuggerTTDEvent* events, size_t count); // TTD Code Coverage Analysis Functions DEBUGGER_FFI_API bool BNDebuggerIsInstructionExecuted(BNDebuggerController* controller, uint64_t address); diff --git a/api/python/debuggercontroller.py b/api/python/debuggercontroller.py index a99b1a39..75d9d28c 100644 --- a/api/python/debuggercontroller.py +++ b/api/python/debuggercontroller.py @@ -721,6 +721,231 @@ def __repr__(self): return f"" +class TTDEventType: + """ + TTD Event Type enumeration for different types of events in TTD traces. + These are bitfield flags that can be combined. + """ + NONE = 0 + ThreadCreated = 1 + ThreadTerminated = 2 + ModuleLoaded = 4 + ModuleUnloaded = 8 + Exception = 16 + ALL = ThreadCreated | ThreadTerminated | ModuleLoaded | ModuleUnloaded | Exception + + +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 + """ + + def __init__(self, name: str, address: int, size: int, checksum: int, timestamp: int): + self.name = name + self.address = address + self.size = size + self.checksum = checksum + self.timestamp = timestamp + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return (self.name == other.name and + self.address == other.address and + self.size == other.size and + self.checksum == other.checksum and + self.timestamp == other.timestamp) + + def __ne__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return not (self == other) + + def __hash__(self): + return hash((self.name, self.address, self.size, self.checksum, self.timestamp)) + + def __setattr__(self, name, value): + try: + object.__setattr__(self, name, value) + except AttributeError: + raise AttributeError(f"attribute '{name}' is read only") + + def __repr__(self): + return f"" + + +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 + """ + + def __init__(self, unique_id: int, id: int, lifetime_start: TTDPosition, lifetime_end: TTDPosition, + active_time_start: TTDPosition, active_time_end: TTDPosition): + self.unique_id = unique_id + self.id = id + self.lifetime_start = lifetime_start + self.lifetime_end = lifetime_end + self.active_time_start = active_time_start + self.active_time_end = active_time_end + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return (self.unique_id == other.unique_id and + self.id == other.id and + self.lifetime_start == other.lifetime_start and + self.lifetime_end == other.lifetime_end and + self.active_time_start == other.active_time_start and + self.active_time_end == other.active_time_end) + + def __ne__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return not (self == other) + + def __hash__(self): + return hash((self.unique_id, self.id, self.lifetime_start, self.lifetime_end, + self.active_time_start, self.active_time_end)) + + def __setattr__(self, name, value): + try: + object.__setattr__(self, name, value) + except AttributeError: + raise AttributeError(f"attribute '{name}' is read only") + + def __repr__(self): + return f"" + + +class TTDExceptionType: + """ + TTD Exception Type enumeration for different types of exceptions. + """ + Software = 0 + Hardware = 1 + + +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 + """ + + def __init__(self, type: int, program_counter: int, code: int, flags: int, + record_address: int, position: TTDPosition): + self.type = type + self.program_counter = program_counter + self.code = code + self.flags = flags + self.record_address = record_address + self.position = position + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return (self.type == other.type and + self.program_counter == other.program_counter and + self.code == other.code and + self.flags == other.flags and + self.record_address == other.record_address and + self.position == other.position) + + def __ne__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return not (self == other) + + def __hash__(self): + return hash((self.type, self.program_counter, self.code, self.flags, + self.record_address, self.position)) + + def __setattr__(self, name, value): + try: + object.__setattr__(self, name, value) + except AttributeError: + raise AttributeError(f"attribute '{name}' is read only") + + def __repr__(self): + type_str = "Hardware" if self.type == TTDExceptionType.Hardware else "Software" + return f"" + + +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 + """ + + def __init__(self, type: int, position: TTDPosition, module = None, thread = None, exception = None): + self.type = type + self.position = position + self.module = module + self.thread = thread + self.exception = exception + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return (self.type == other.type and + self.position == other.position and + self.module == other.module and + self.thread == other.thread and + self.exception == other.exception) + + def __ne__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return not (self == other) + + def __hash__(self): + return hash((self.type, self.position, self.module, self.thread, self.exception)) + + def __setattr__(self, name, value): + try: + object.__setattr__(self, name, value) + except AttributeError: + raise AttributeError(f"attribute '{name}' is read only") + + def __repr__(self): + type_names = { + TTDEventType.ThreadCreated: "ThreadCreated", + TTDEventType.ThreadTerminated: "ThreadTerminated", + TTDEventType.ModuleLoaded: "ModuleLoaded", + TTDEventType.ModuleUnloaded: "ModuleUnloaded", + TTDEventType.Exception: "Exception" + } + type_str = type_names.get(self.type, f"Unknown({self.type})") + return f"" + + class DebuggerController: """ The ``DebuggerController`` object is the core of the debugger. Most debugger operations can be performed on it. @@ -2042,6 +2267,165 @@ def get_ttd_calls_for_symbols(self, symbols: str, start_return_address: int = 0, dbgcore.BNDebuggerFreeTTDCallEvents(events, count.value) return result + def get_ttd_events(self, event_type: int) -> List[TTDEvent]: + """ + Get TTD events for specific event types using bitfield filtering. + + This method is only available when debugging with TTD (Time Travel Debugging). + Use the is_ttd property to check if TTD is available before calling this method. + + :param event_type: type of events to query (TTDEventType bitfield flags that can be combined with |) + :return: list of TTDEvent objects + :rtype: List[TTDEvent] + """ + if self.handle is None: + return [] + + count = ctypes.c_size_t() + events = dbgcore.BNDebuggerGetTTDEvents(self.handle, event_type, ctypes.byref(count)) + + result = [] + if not events or count.value == 0: + return result + + for i in range(count.value): + event = events[i] + + position = TTDPosition(event.position.sequence, event.position.step) + + # Convert optional module details + module = None + if event.module: + module = TTDModule( + name=event.module.contents.name if event.module.contents.name else "", + address=event.module.contents.address, + size=event.module.contents.size, + checksum=event.module.contents.checksum, + timestamp=event.module.contents.timestamp + ) + + # Convert optional thread details + thread = None + if event.thread: + lifetime_start = TTDPosition(event.thread.contents.lifetimeStart.sequence, event.thread.contents.lifetimeStart.step) + lifetime_end = TTDPosition(event.thread.contents.lifetimeEnd.sequence, event.thread.contents.lifetimeEnd.step) + active_time_start = TTDPosition(event.thread.contents.activeTimeStart.sequence, event.thread.contents.activeTimeStart.step) + active_time_end = TTDPosition(event.thread.contents.activeTimeEnd.sequence, event.thread.contents.activeTimeEnd.step) + + thread = TTDThread( + unique_id=event.thread.contents.uniqueId, + id=event.thread.contents.id, + lifetime_start=lifetime_start, + lifetime_end=lifetime_end, + active_time_start=active_time_start, + active_time_end=active_time_end + ) + + # Convert optional exception details + exception = None + if event.exception: + exception_position = TTDPosition(event.exception.contents.position.sequence, event.exception.contents.position.step) + + exception = TTDException( + type=event.exception.contents.type, + program_counter=event.exception.contents.programCounter, + code=event.exception.contents.code, + flags=event.exception.contents.flags, + record_address=event.exception.contents.recordAddress, + position=exception_position + ) + + ttd_event = TTDEvent( + type=event.type, + position=position, + module=module, + thread=thread, + exception=exception + ) + result.append(ttd_event) + + dbgcore.BNDebuggerFreeTTDEvents(events, count.value) + return result + + def get_all_ttd_events(self) -> List[TTDEvent]: + """ + Get all TTD events from the trace. + + This method is only available when debugging with TTD (Time Travel Debugging). + Use the is_ttd property to check if TTD is available before calling this method. + + :return: list of all TTDEvent objects in the trace + :rtype: List[TTDEvent] + """ + if self.handle is None: + return [] + + count = ctypes.c_size_t() + events = dbgcore.BNDebuggerGetAllTTDEvents(self.handle, ctypes.byref(count)) + + result = [] + if not events or count.value == 0: + return result + + for i in range(count.value): + event = events[i] + + position = TTDPosition(event.position.sequence, event.position.step) + + # Convert optional module details + module = None + if event.module: + module = TTDModule( + name=event.module.contents.name if event.module.contents.name else "", + address=event.module.contents.address, + size=event.module.contents.size, + checksum=event.module.contents.checksum, + timestamp=event.module.contents.timestamp + ) + + # Convert optional thread details + thread = None + if event.thread: + lifetime_start = TTDPosition(event.thread.contents.lifetimeStart.sequence, event.thread.contents.lifetimeStart.step) + lifetime_end = TTDPosition(event.thread.contents.lifetimeEnd.sequence, event.thread.contents.lifetimeEnd.step) + active_time_start = TTDPosition(event.thread.contents.activeTimeStart.sequence, event.thread.contents.activeTimeStart.step) + active_time_end = TTDPosition(event.thread.contents.activeTimeEnd.sequence, event.thread.contents.activeTimeEnd.step) + + thread = TTDThread( + unique_id=event.thread.contents.uniqueId, + id=event.thread.contents.id, + lifetime_start=lifetime_start, + lifetime_end=lifetime_end, + active_time_start=active_time_start, + active_time_end=active_time_end + ) + + # Convert optional exception details + exception = None + if event.exception: + exception_position = TTDPosition(event.exception.contents.position.sequence, event.exception.contents.position.step) + + exception = TTDException( + type=event.exception.contents.type, + program_counter=event.exception.contents.programCounter, + code=event.exception.contents.code, + flags=event.exception.contents.flags, + record_address=event.exception.contents.recordAddress, + position=exception_position + ) + + ttd_event = TTDEvent( + type=event.type, + position=position, + module=module, + thread=thread, + exception=exception + ) + result.append(ttd_event) + + dbgcore.BNDebuggerFreeTTDEvents(events, count.value) + return result + def __del__(self): if dbgcore is not None: dbgcore.BNDebuggerFreeController(self.handle) diff --git a/core/adapters/dbgengttdadapter.cpp b/core/adapters/dbgengttdadapter.cpp index 3a3b40ab..317855cf 100644 --- a/core/adapters/dbgengttdadapter.cpp +++ b/core/adapters/dbgengttdadapter.cpp @@ -11,6 +11,7 @@ using namespace std; DbgEngTTDAdapter::DbgEngTTDAdapter(BinaryView* data) : DbgEngAdapter(data) { m_usePDBFileName = false; + m_eventsCached = false; GenerateDefaultAdapterSettings(data); } @@ -173,6 +174,9 @@ bool DbgEngTTDAdapter::Start() void DbgEngTTDAdapter::Reset() { m_aboutToBeKilled = false; + + // Clear TTD events cache when resetting + ClearTTDEventsCache(); if (!this->m_dbgengInitialized) return; @@ -372,6 +376,16 @@ Ref DbgEngTTDAdapterType::RegisterAdapterSettings() "description" : "Maximum number of results to return from TTD Calls queries. Set to 0 for no limit.", "readOnly" : false })"); + settings->RegisterSetting("ttd.maxEventsQueryResults", + R"({ + "title" : "Max Events Query Results", + "type" : "number", + "default" : 100000, + "minValue" : 0, + "maxValue" : 18446744073709551615, + "description" : "Maximum number of results to return from TTD Events queries. Set to 0 for no limit.", + "readOnly" : false + })"); return settings; } @@ -1350,6 +1364,542 @@ bool DbgEngTTDAdapter::ParseTTDCallObjects(const std::string& expression, std::v } +std::vector DbgEngTTDAdapter::GetTTDEvents(TTDEventType eventType) +{ + std::vector events; + + // Cache all events if not already cached + if (!m_eventsCached) + { + if (!QueryAllTTDEvents()) + { + LogError("Failed to query all TTD events"); + return events; + } + } + + // Filter cached events by type using bitfield operations + for (const auto& event : m_cachedEvents) + { + if (eventType & event.type) + { + events.push_back(event); + } + } + + LogInfo("Successfully retrieved %zu TTD events of type %d from cache", events.size(), eventType); + return events; +} + + +std::vector DbgEngTTDAdapter::GetAllTTDEvents() +{ + // Cache all events if not already cached + if (!m_eventsCached) + { + if (!QueryAllTTDEvents()) + { + LogError("Failed to query all TTD events"); + return {}; + } + } + + LogDebug("Successfully retrieved %zu total TTD events from cache", m_cachedEvents.size()); + return m_cachedEvents; +} + + +bool DbgEngTTDAdapter::QueryAllTTDEvents() +{ + if (!m_debugHost || !m_hostEvaluator) + { + LogError("Data model interfaces not initialized for TTD events query"); + return false; + } + + try + { + // Build the TTD.Events query expression to get all events (using curprocess instead of cursession) + std::string expression = "@$curprocess.TTD.Events"; + + LogInfo("Executing TTD events query: %s", expression.c_str()); + + m_cachedEvents.clear(); + if (ParseTTDEventObjects(expression, m_cachedEvents)) + { + m_eventsCached = true; + return true; + } + return false; + } + catch (const std::exception& e) + { + LogError("Exception in QueryAllTTDEvents: %s", e.what()); + return false; + } +} + + +bool DbgEngTTDAdapter::ParseTTDEventObjects(const std::string& expression, std::vector& events) +{ + try + { + LogInfo("Parsing TTD event objects from expression: %s", expression.c_str()); + + // Convert expression to wide string + std::wstring wExpression(expression.begin(), expression.end()); + + // Create context for evaluation + ComPtr hostContext; + if (FAILED(m_debugHost->GetCurrentContext(hostContext.GetAddressOf()))) + { + LogError("Failed to get current debug host context"); + return false; + } + + // Execute the expression to get event objects + ComPtr resultObject; + ComPtr metadataKeyStore; + + HRESULT hr = m_hostEvaluator->EvaluateExtendedExpression( + hostContext.Get(), + wExpression.c_str(), + nullptr, // bindingContext + &resultObject, + &metadataKeyStore + ); + + if (FAILED(hr)) + { + LogError("Failed to evaluate TTD events expression: 0x%x", hr); + return false; + } + + if (!resultObject) + { + LogError("Null result object from TTD events expression"); + return false; + } + + // Check if the result is iterable + ComPtr iterableConcept; + hr = resultObject->GetConcept(__uuidof(IIterableConcept), &iterableConcept, nullptr); + if (FAILED(hr)) + { + LogError("TTD events result is not iterable: 0x%x", hr); + return false; + } + + // Get iterator + ComPtr iterator; + hr = iterableConcept->GetIterator(resultObject.Get(), &iterator); + if (FAILED(hr)) + { + LogError("Failed to get iterator for TTD events: 0x%x", hr); + return false; + } + + // Get maximum results setting + auto adapterSettings = GetAdapterSettings(); + BNSettingsScope scope = SettingsResourceScope; + auto maxResults = adapterSettings->Get("ttd.maxCallsQueryResults", GetData(), &scope); + size_t resultCounter = 0; + bool wasLimited = false; + + // Iterate through events + ComPtr eventObject; + ComPtr eventMetadataKeyStore; + + while (SUCCEEDED(iterator->GetNext(&eventObject, 0, nullptr, &eventMetadataKeyStore)) && eventObject) + { + if (resultCounter >= maxResults) + { + wasLimited = true; + break; + } + + // Parse event type from the event object first + TTDEventType eventType = TTDEventThreadCreated; // default + + ComPtr typeObj; + if (SUCCEEDED(eventObject->GetKeyValue(L"Type", &typeObj, nullptr))) + { + VARIANT vtType; + VariantInit(&vtType); + if (SUCCEEDED(typeObj->GetIntrinsicValueAs(VT_BSTR, &vtType))) + { + _bstr_t bstr(vtType.bstrVal); + std::string typeStr = std::string(bstr); + + if (typeStr == "ThreadCreated") + eventType = TTDEventThreadCreated; + else if (typeStr == "ThreadTerminated") + eventType = TTDEventThreadTerminated; + else if (typeStr == "ModuleLoaded") + eventType = TTDEventModuleLoaded; + else if (typeStr == "ModuleUnloaded") + eventType = TTDEventModuleUnloaded; + else if (typeStr == "Exception") + eventType = TTDEventException; + } + VariantClear(&vtType); + } + + TTDEvent event(eventType); + + // Parse Position + ComPtr positionObj; + if (SUCCEEDED(eventObject->GetKeyValue(L"Position", &positionObj, nullptr))) + { + // Parse Sequence + ComPtr sequenceObj; + if (SUCCEEDED(positionObj->GetKeyValue(L"Sequence", &sequenceObj, nullptr))) + { + VARIANT vtSequence; + VariantInit(&vtSequence); + if (SUCCEEDED(sequenceObj->GetIntrinsicValueAs(VT_UI8, &vtSequence))) + { + event.position.sequence = vtSequence.ullVal; + } + VariantClear(&vtSequence); + } + + // Parse Steps + ComPtr stepsObj; + if (SUCCEEDED(positionObj->GetKeyValue(L"Steps", &stepsObj, nullptr))) + { + VARIANT vtSteps; + VariantInit(&vtSteps); + if (SUCCEEDED(stepsObj->GetIntrinsicValueAs(VT_UI8, &vtSteps))) + { + event.position.step = vtSteps.ullVal; + } + VariantClear(&vtSteps); + } + } + + // Parse event-specific details based on type + switch (eventType) + { + case TTDEventThreadCreated: + case TTDEventThreadTerminated: + ParseThreadDetails(eventObject.Get(), event); + break; + case TTDEventModuleLoaded: + case TTDEventModuleUnloaded: + ParseModuleDetails(eventObject.Get(), event); + break; + case TTDEventException: + ParseExceptionDetails(eventObject.Get(), event); + break; + } + + events.push_back(event); + resultCounter++; + + // Reset objects for next iteration + eventObject.Reset(); + eventMetadataKeyStore.Reset(); + } + + if (wasLimited) + { + LogWarnF("Successfully parsed {} TTD events from data model (limited by max results setting of {})", events.size(), maxResults); + } + else + { + LogInfo("Successfully parsed %zu TTD events from data model", events.size()); + } + return true; + } + catch (const std::exception& e) + { + LogError("Exception in ParseTTDEventObjects: %s", e.what()); + return false; + } +} + + +void DbgEngTTDAdapter::ParseThreadDetails(IModelObject* eventObject, TTDEvent& event) +{ + ComPtr threadObj; + if (SUCCEEDED(eventObject->GetKeyValue(L"Thread", &threadObj, nullptr))) + { + TTDThread thread; + + // Parse UniqueId + ComPtr uniqueIdObj; + if (SUCCEEDED(threadObj->GetKeyValue(L"UniqueId", &uniqueIdObj, nullptr))) + { + VARIANT vtUniqueId; + VariantInit(&vtUniqueId); + if (SUCCEEDED(uniqueIdObj->GetIntrinsicValueAs(VT_UI4, &vtUniqueId))) + { + thread.uniqueId = vtUniqueId.ulVal; + } + VariantClear(&vtUniqueId); + } + + // Parse Id (TID) + ComPtr idObj; + if (SUCCEEDED(threadObj->GetKeyValue(L"Id", &idObj, nullptr))) + { + VARIANT vtId; + VariantInit(&vtId); + if (SUCCEEDED(idObj->GetIntrinsicValueAs(VT_UI4, &vtId))) + { + thread.id = vtId.ulVal; + } + VariantClear(&vtId); + } + + // Parse LifeTime + ComPtr lifetimeObj; + if (SUCCEEDED(threadObj->GetKeyValue(L"LifeTime", &lifetimeObj, nullptr))) + { + // Parse LifeTime.MinPosition + ComPtr minPosObj; + if (SUCCEEDED(lifetimeObj->GetKeyValue(L"MinPosition", &minPosObj, nullptr))) + { + ParseTTDPosition(minPosObj.Get(), thread.lifetimeStart); + } + + // Parse LifeTime.MaxPosition + ComPtr maxPosObj; + if (SUCCEEDED(lifetimeObj->GetKeyValue(L"MaxPosition", &maxPosObj, nullptr))) + { + ParseTTDPosition(maxPosObj.Get(), thread.lifetimeEnd); + } + } + + // Parse ActiveTime + ComPtr activeTimeObj; + if (SUCCEEDED(threadObj->GetKeyValue(L"ActiveTime", &activeTimeObj, nullptr))) + { + // Parse ActiveTime.MinPosition + ComPtr minActiveObj; + if (SUCCEEDED(activeTimeObj->GetKeyValue(L"MinPosition", &minActiveObj, nullptr))) + { + ParseTTDPosition(minActiveObj.Get(), thread.activeTimeStart); + } + + // Parse ActiveTime.MaxPosition + ComPtr maxActiveObj; + if (SUCCEEDED(activeTimeObj->GetKeyValue(L"MaxPosition", &maxActiveObj, nullptr))) + { + ParseTTDPosition(maxActiveObj.Get(), thread.activeTimeEnd); + } + } + + event.thread = thread; + } +} + + +void DbgEngTTDAdapter::ParseModuleDetails(IModelObject* eventObject, TTDEvent& event) +{ + ComPtr moduleObj; + if (SUCCEEDED(eventObject->GetKeyValue(L"Module", &moduleObj, nullptr))) + { + TTDModule module; + + // Parse Name + ComPtr nameObj; + if (SUCCEEDED(moduleObj->GetKeyValue(L"Name", &nameObj, nullptr))) + { + VARIANT vtName; + VariantInit(&vtName); + if (SUCCEEDED(nameObj->GetIntrinsicValueAs(VT_BSTR, &vtName))) + { + _bstr_t bstr(vtName.bstrVal); + module.name = std::string(bstr); + } + VariantClear(&vtName); + } + + // Parse Address + ComPtr addressObj; + if (SUCCEEDED(moduleObj->GetKeyValue(L"Address", &addressObj, nullptr))) + { + VARIANT vtAddress; + VariantInit(&vtAddress); + if (SUCCEEDED(addressObj->GetIntrinsicValueAs(VT_UI8, &vtAddress))) + { + module.address = vtAddress.ullVal; + } + VariantClear(&vtAddress); + } + + // Parse Size + ComPtr sizeObj; + if (SUCCEEDED(moduleObj->GetKeyValue(L"Size", &sizeObj, nullptr))) + { + VARIANT vtSize; + VariantInit(&vtSize); + if (SUCCEEDED(sizeObj->GetIntrinsicValueAs(VT_UI8, &vtSize))) + { + module.size = vtSize.ullVal; + } + VariantClear(&vtSize); + } + + // Parse Checksum + ComPtr checksumObj; + if (SUCCEEDED(moduleObj->GetKeyValue(L"Checksum", &checksumObj, nullptr))) + { + VARIANT vtChecksum; + VariantInit(&vtChecksum); + if (SUCCEEDED(checksumObj->GetIntrinsicValueAs(VT_UI4, &vtChecksum))) + { + module.checksum = vtChecksum.ulVal; + } + VariantClear(&vtChecksum); + } + + // Parse Timestamp + ComPtr timestampObj; + if (SUCCEEDED(moduleObj->GetKeyValue(L"Timestamp", ×tampObj, nullptr))) + { + VARIANT vtTimestamp; + VariantInit(&vtTimestamp); + if (SUCCEEDED(timestampObj->GetIntrinsicValueAs(VT_UI4, &vtTimestamp))) + { + module.timestamp = vtTimestamp.ulVal; + } + VariantClear(&vtTimestamp); + } + + event.module = module; + } +} + + +void DbgEngTTDAdapter::ParseExceptionDetails(IModelObject* eventObject, TTDEvent& event) +{ + ComPtr exceptionObj; + if (SUCCEEDED(eventObject->GetKeyValue(L"Exception", &exceptionObj, nullptr))) + { + TTDException exception; + + // Parse Type + ComPtr typeObj; + if (SUCCEEDED(exceptionObj->GetKeyValue(L"Type", &typeObj, nullptr))) + { + VARIANT vtType; + VariantInit(&vtType); + if (SUCCEEDED(typeObj->GetIntrinsicValueAs(VT_BSTR, &vtType))) + { + _bstr_t bstr(vtType.bstrVal); + std::string typeStr = std::string(bstr); + exception.type = (typeStr == "Hardware") ? TTDExceptionHardware : TTDExceptionSoftware; + } + VariantClear(&vtType); + } + + // Parse ProgramCounter + ComPtr pcObj; + if (SUCCEEDED(exceptionObj->GetKeyValue(L"ProgramCounter", &pcObj, nullptr))) + { + VARIANT vtPC; + VariantInit(&vtPC); + if (SUCCEEDED(pcObj->GetIntrinsicValueAs(VT_UI8, &vtPC))) + { + exception.programCounter = vtPC.ullVal; + } + VariantClear(&vtPC); + } + + // Parse Code + ComPtr codeObj; + if (SUCCEEDED(exceptionObj->GetKeyValue(L"Code", &codeObj, nullptr))) + { + VARIANT vtCode; + VariantInit(&vtCode); + if (SUCCEEDED(codeObj->GetIntrinsicValueAs(VT_UI4, &vtCode))) + { + exception.code = vtCode.ulVal; + } + VariantClear(&vtCode); + } + + // Parse Flags + ComPtr flagsObj; + if (SUCCEEDED(exceptionObj->GetKeyValue(L"Flags", &flagsObj, nullptr))) + { + VARIANT vtFlags; + VariantInit(&vtFlags); + if (SUCCEEDED(flagsObj->GetIntrinsicValueAs(VT_UI4, &vtFlags))) + { + exception.flags = vtFlags.ulVal; + } + VariantClear(&vtFlags); + } + + // Parse RecordAddress + ComPtr recordAddrObj; + if (SUCCEEDED(exceptionObj->GetKeyValue(L"RecordAddress", &recordAddrObj, nullptr))) + { + VARIANT vtRecordAddr; + VariantInit(&vtRecordAddr); + if (SUCCEEDED(recordAddrObj->GetIntrinsicValueAs(VT_UI8, &vtRecordAddr))) + { + exception.recordAddress = vtRecordAddr.ullVal; + } + VariantClear(&vtRecordAddr); + } + + // Parse Position + ComPtr positionObj; + if (SUCCEEDED(exceptionObj->GetKeyValue(L"Position", &positionObj, nullptr))) + { + ParseTTDPosition(positionObj.Get(), exception.position); + } + + event.exception = exception; + } +} + + +void DbgEngTTDAdapter::ParseTTDPosition(IModelObject* positionObj, TTDPosition& position) +{ + if (!positionObj) + return; + + // Parse Sequence + ComPtr sequenceObj; + if (SUCCEEDED(positionObj->GetKeyValue(L"Sequence", &sequenceObj, nullptr))) + { + VARIANT vtSequence; + VariantInit(&vtSequence); + if (SUCCEEDED(sequenceObj->GetIntrinsicValueAs(VT_UI8, &vtSequence))) + { + position.sequence = vtSequence.ullVal; + } + VariantClear(&vtSequence); + } + + // Parse Steps + ComPtr stepsObj; + if (SUCCEEDED(positionObj->GetKeyValue(L"Steps", &stepsObj, nullptr))) + { + VARIANT vtSteps; + VariantInit(&vtSteps); + if (SUCCEEDED(stepsObj->GetIntrinsicValueAs(VT_UI8, &vtSteps))) + { + position.step = vtSteps.ullVal; + } + VariantClear(&vtSteps); + } +} + + +void DbgEngTTDAdapter::ClearTTDEventsCache() +{ + m_cachedEvents.clear(); + m_eventsCached = false; +} + + void BinaryNinjaDebugger::InitDbgEngTTDAdapterType() { static DbgEngTTDAdapterType localType; diff --git a/core/adapters/dbgengttdadapter.h b/core/adapters/dbgengttdadapter.h index 30ed9749..1d6acc95 100644 --- a/core/adapters/dbgengttdadapter.h +++ b/core/adapters/dbgengttdadapter.h @@ -54,6 +54,10 @@ namespace BinaryNinjaDebugger { // TTD Calls Analysis Methods std::vector GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress = 0, uint64_t endReturnAddress = 0) override; + // TTD Events Analysis Methods + std::vector GetTTDEvents(TTDEventType eventType) override; + std::vector GetAllTTDEvents() override; + void GenerateDefaultAdapterSettings(BinaryView* data); Ref GetAdapterSettings() override; @@ -65,6 +69,21 @@ namespace BinaryNinjaDebugger { bool QueryCallsForSymbols(const std::vector& symbols, uint64_t startReturnAddress, uint64_t endReturnAddress, std::vector& events); bool ParseTTDCallObjects(const std::string& expression, std::vector& events); + // Helper methods for TTD events analysis + bool QueryAllTTDEvents(); + bool ParseTTDEventObjects(const std::string& expression, std::vector& events); + void ParseThreadDetails(IModelObject* eventObject, TTDEvent& event); + void ParseModuleDetails(IModelObject* eventObject, TTDEvent& event); + void ParseExceptionDetails(IModelObject* eventObject, TTDEvent& event); + void ParseTTDPosition(IModelObject* positionObj, TTDPosition& position); + + // TTD Events cache + std::vector m_cachedEvents; + bool m_eventsCached; + + // Method to clear TTD events cache + void ClearTTDEventsCache(); + // Data model helper methods std::string EvaluateDataModelExpression(const std::string& expression); bool ParseTTDMemoryObjects(const std::string& expression, TTDMemoryAccessType accessType, std::vector& events); diff --git a/core/debugadapter.cpp b/core/debugadapter.cpp index 52360905..27a66372 100644 --- a/core/debugadapter.cpp +++ b/core/debugadapter.cpp @@ -193,6 +193,20 @@ std::vector DebugAdapter::GetTTDCallsForSymbols(const std::string& } +std::vector DebugAdapter::GetTTDEvents(TTDEventType eventType) +{ + // Default implementation returns empty results for adapters that don't support TTD + return {}; +} + + +std::vector DebugAdapter::GetAllTTDEvents() +{ + // Default implementation returns empty results for adapters that don't support TTD + return {}; +} + + TTDPosition DebugAdapter::GetCurrentTTDPosition() { // Default implementation returns an empty position for adapters that don't support TTD diff --git a/core/debugadapter.h b/core/debugadapter.h index 548c023a..5849d544 100644 --- a/core/debugadapter.h +++ b/core/debugadapter.h @@ -330,6 +330,8 @@ namespace BinaryNinjaDebugger { // TTD (Time Travel Debugging) methods - default implementations return empty results virtual std::vector GetTTDMemoryAccessForAddress(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType = TTDMemoryRead); virtual std::vector GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress = 0, uint64_t endReturnAddress = 0); + virtual std::vector GetTTDEvents(TTDEventType eventType); + virtual std::vector GetAllTTDEvents(); virtual TTDPosition GetCurrentTTDPosition(); virtual bool SetTTDPosition(const TTDPosition& position); diff --git a/core/debuggercommon.h b/core/debuggercommon.h index 22fafc3f..ed547c0e 100644 --- a/core/debuggercommon.h +++ b/core/debuggercommon.h @@ -16,6 +16,7 @@ limitations under the License. #pragma once #include +#include #ifndef WIN32 #include "libgen.h" #endif @@ -152,4 +153,76 @@ namespace BinaryNinjaDebugger { TTDCallEvent() : threadId(0), uniqueThreadId(0), functionAddress(0), returnAddress(0), returnValue(0), hasReturnValue(false) {} }; + + // TTD Event Types - bitfield flags for filtering events + enum TTDEventType + { + TTDEventNone = 0, + TTDEventThreadCreated = 1, + TTDEventThreadTerminated = 2, + TTDEventModuleLoaded = 4, + TTDEventModuleUnloaded = 8, + TTDEventException = 16, + TTDEventAll = TTDEventThreadCreated | TTDEventThreadTerminated | TTDEventModuleLoaded | TTDEventModuleUnloaded | TTDEventException + }; + + // TTD Module - information about modules that were loaded/unloaded during trace + struct TTDModule + { + std::string name; // Name and path of the module + uint64_t address; // Address where the module was loaded + uint64_t size; // Size of the module in bytes + uint32_t checksum; // Checksum of the module + uint32_t timestamp; // Timestamp of the module + + TTDModule() : address(0), size(0), checksum(0), timestamp(0) {} + }; + + // TTD Thread - information about threads and their lifetime during trace + struct TTDThread + { + uint32_t uniqueId; // Unique ID for the thread across the trace + uint32_t id; // TID of the thread + TTDPosition lifetimeStart; // Lifetime start position + TTDPosition lifetimeEnd; // Lifetime end position + TTDPosition activeTimeStart; // Active time start position + TTDPosition activeTimeEnd; // Active time end position + + TTDThread() : uniqueId(0), id(0) {} + }; + + // TTD Exception Types + enum TTDExceptionType + { + TTDExceptionSoftware, + TTDExceptionHardware + }; + + // TTD Exception - information about exceptions that occurred during trace + struct TTDException + { + TTDExceptionType type; // Type of exception (Software/Hardware) + uint64_t programCounter; // Instruction where exception was thrown + uint32_t code; // Exception code + uint32_t flags; // Exception flags + uint64_t recordAddress; // Where in memory the exception record is found + TTDPosition position; // Position where exception occurred + + TTDException() : type(TTDExceptionSoftware), programCounter(0), code(0), flags(0), recordAddress(0) {} + }; + + // TTD Event - represents important events that happened during trace + struct TTDEvent + { + TTDEventType type; // Type of event + TTDPosition position; // Position where event occurred + + // Optional child objects - existence depends on event type + std::optional module; // For ModuleLoaded/ModuleUnloaded events + std::optional thread; // For ThreadCreated/ThreadTerminated events + std::optional exception; // For Exception events + + TTDEvent() : type(TTDEventThreadCreated) {} + TTDEvent(TTDEventType eventType) : type(eventType) {} + }; }; // namespace BinaryNinjaDebugger diff --git a/core/debuggercontroller.cpp b/core/debuggercontroller.cpp index 3f389b1e..af1c6df7 100644 --- a/core/debuggercontroller.cpp +++ b/core/debuggercontroller.cpp @@ -2874,7 +2874,7 @@ std::vector DebuggerController::GetTTDMemoryAccessForAddress(uin { std::vector events; - if (!IsTTD()) + if (!m_state->IsConnected() || !IsTTD()) { LogError("Current adapter does not support TTD"); return events; @@ -2892,9 +2892,9 @@ std::vector DebuggerController::GetTTDCallsForSymbols(const std::s { std::vector events; - if (!IsTTD()) + if (!m_state->IsConnected() || !IsTTD()) { - LogError("Current adapter does not support TTD"); + LogWarn("Current adapter does not support TTD"); return events; } @@ -2902,13 +2902,41 @@ std::vector DebuggerController::GetTTDCallsForSymbols(const std::s } +std::vector DebuggerController::GetTTDEvents(TTDEventType eventType) +{ + std::vector events; + + if (!m_state->IsConnected() || !IsTTD()) + { + LogWarn("Current adapter does not support TTD"); + return events; + } + + return m_adapter->GetTTDEvents(eventType); +} + + +std::vector DebuggerController::GetAllTTDEvents() +{ + std::vector events; + + if (!m_state->IsConnected() || !IsTTD()) + { + LogWarn("Current adapter does not support TTD"); + return events; + } + + return m_adapter->GetAllTTDEvents(); +} + + TTDPosition DebuggerController::GetCurrentTTDPosition() { TTDPosition position; - if (!IsTTD()) + if (!m_state->IsConnected() || !IsTTD()) { - LogError("Current adapter does not support TTD"); + LogWarn("Current adapter does not support TTD"); return position; } @@ -2922,9 +2950,9 @@ TTDPosition DebuggerController::GetCurrentTTDPosition() bool DebuggerController::SetTTDPosition(const TTDPosition& position) { - if (!IsTTD()) + if (!m_state->IsConnected() || !IsTTD()) { - LogError("Current adapter does not support TTD"); + LogWarn("Current adapter does not support TTD"); return false; } @@ -2939,7 +2967,7 @@ bool DebuggerController::SetTTDPosition(const TTDPosition& position) bool DebuggerController::IsInstructionExecuted(uint64_t address) { - if (!IsTTD()) + if (!m_state->IsConnected() || !IsTTD()) { return false; } @@ -2955,9 +2983,9 @@ bool DebuggerController::IsInstructionExecuted(uint64_t address) bool DebuggerController::RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t endAddress) { - if (!IsTTD()) + if (!m_state->IsConnected() || !IsTTD()) { - LogError("Current adapter does not support TTD"); + LogWarn("Current adapter does not support TTD"); return false; } diff --git a/core/debuggercontroller.h b/core/debuggercontroller.h index b22fa53d..0c67f025 100644 --- a/core/debuggercontroller.h +++ b/core/debuggercontroller.h @@ -364,6 +364,8 @@ namespace BinaryNinjaDebugger { // TTD Memory Analysis Methods std::vector GetTTDMemoryAccessForAddress(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType = TTDMemoryRead); std::vector GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress = 0, uint64_t endReturnAddress = 0); + std::vector GetTTDEvents(TTDEventType eventType); + std::vector GetAllTTDEvents(); TTDPosition GetCurrentTTDPosition(); bool SetTTDPosition(const TTDPosition& position); diff --git a/core/ffi.cpp b/core/ffi.cpp index c3f77a86..035db429 100644 --- a/core/ffi.cpp +++ b/core/ffi.cpp @@ -1272,6 +1272,196 @@ void BNDebuggerFreeTTDCallEvents(BNDebuggerTTDCallEvent* events, size_t count) } +BNDebuggerTTDEvent* BNDebuggerGetTTDEvents(BNDebuggerController* controller, + BNDebuggerTTDEventType eventType, size_t* count) +{ + if (!count) + return nullptr; + + *count = 0; + + auto events = controller->object->GetTTDEvents(static_cast(eventType)); + if (events.empty()) + return nullptr; + + *count = events.size(); + auto result = new BNDebuggerTTDEvent[events.size()]; + + for (size_t i = 0; i < events.size(); ++i) + { + // Copy event type and position + result[i].type = static_cast(events[i].type); + result[i].position.sequence = events[i].position.sequence; + result[i].position.step = events[i].position.step; + + // Copy optional module details + if (events[i].module.has_value()) + { + result[i].module = new BNDebuggerTTDModule(); + result[i].module->name = BNAllocString(events[i].module->name.c_str()); + result[i].module->address = events[i].module->address; + result[i].module->size = events[i].module->size; + result[i].module->checksum = events[i].module->checksum; + result[i].module->timestamp = events[i].module->timestamp; + } + else + { + result[i].module = nullptr; + } + + // Copy optional thread details + if (events[i].thread.has_value()) + { + result[i].thread = new BNDebuggerTTDThread(); + result[i].thread->uniqueId = events[i].thread->uniqueId; + result[i].thread->id = events[i].thread->id; + result[i].thread->lifetimeStart.sequence = events[i].thread->lifetimeStart.sequence; + result[i].thread->lifetimeStart.step = events[i].thread->lifetimeStart.step; + result[i].thread->lifetimeEnd.sequence = events[i].thread->lifetimeEnd.sequence; + result[i].thread->lifetimeEnd.step = events[i].thread->lifetimeEnd.step; + result[i].thread->activeTimeStart.sequence = events[i].thread->activeTimeStart.sequence; + result[i].thread->activeTimeStart.step = events[i].thread->activeTimeStart.step; + result[i].thread->activeTimeEnd.sequence = events[i].thread->activeTimeEnd.sequence; + result[i].thread->activeTimeEnd.step = events[i].thread->activeTimeEnd.step; + } + else + { + result[i].thread = nullptr; + } + + // Copy optional exception details + if (events[i].exception.has_value()) + { + result[i].exception = new BNDebuggerTTDException(); + result[i].exception->type = static_cast(events[i].exception->type); + result[i].exception->programCounter = events[i].exception->programCounter; + result[i].exception->code = events[i].exception->code; + result[i].exception->flags = events[i].exception->flags; + result[i].exception->recordAddress = events[i].exception->recordAddress; + result[i].exception->position.sequence = events[i].exception->position.sequence; + result[i].exception->position.step = events[i].exception->position.step; + } + else + { + result[i].exception = nullptr; + } + } + + return result; +} + + +BNDebuggerTTDEvent* BNDebuggerGetAllTTDEvents(BNDebuggerController* controller, size_t* count) +{ + if (!count) + return nullptr; + + *count = 0; + + auto events = controller->object->GetAllTTDEvents(); + if (events.empty()) + return nullptr; + + *count = events.size(); + auto result = new BNDebuggerTTDEvent[events.size()]; + + for (size_t i = 0; i < events.size(); ++i) + { + // Copy event type and position + result[i].type = static_cast(events[i].type); + result[i].position.sequence = events[i].position.sequence; + result[i].position.step = events[i].position.step; + + // Copy optional module details + if (events[i].module.has_value()) + { + result[i].module = new BNDebuggerTTDModule(); + result[i].module->name = BNAllocString(events[i].module->name.c_str()); + result[i].module->address = events[i].module->address; + result[i].module->size = events[i].module->size; + result[i].module->checksum = events[i].module->checksum; + result[i].module->timestamp = events[i].module->timestamp; + } + else + { + result[i].module = nullptr; + } + + // Copy optional thread details + if (events[i].thread.has_value()) + { + result[i].thread = new BNDebuggerTTDThread(); + result[i].thread->uniqueId = events[i].thread->uniqueId; + result[i].thread->id = events[i].thread->id; + result[i].thread->lifetimeStart.sequence = events[i].thread->lifetimeStart.sequence; + result[i].thread->lifetimeStart.step = events[i].thread->lifetimeStart.step; + result[i].thread->lifetimeEnd.sequence = events[i].thread->lifetimeEnd.sequence; + result[i].thread->lifetimeEnd.step = events[i].thread->lifetimeEnd.step; + result[i].thread->activeTimeStart.sequence = events[i].thread->activeTimeStart.sequence; + result[i].thread->activeTimeStart.step = events[i].thread->activeTimeStart.step; + result[i].thread->activeTimeEnd.sequence = events[i].thread->activeTimeEnd.sequence; + result[i].thread->activeTimeEnd.step = events[i].thread->activeTimeEnd.step; + } + else + { + result[i].thread = nullptr; + } + + // Copy optional exception details + if (events[i].exception.has_value()) + { + result[i].exception = new BNDebuggerTTDException(); + result[i].exception->type = static_cast(events[i].exception->type); + result[i].exception->programCounter = events[i].exception->programCounter; + result[i].exception->code = events[i].exception->code; + result[i].exception->flags = events[i].exception->flags; + result[i].exception->recordAddress = events[i].exception->recordAddress; + result[i].exception->position.sequence = events[i].exception->position.sequence; + result[i].exception->position.step = events[i].exception->position.step; + } + else + { + result[i].exception = nullptr; + } + } + + return result; +} + + +void BNDebuggerFreeTTDEvents(BNDebuggerTTDEvent* events, size_t count) +{ + if (!events || count == 0) + return; + + // Free all allocated objects for each event + for (size_t i = 0; i < count; ++i) + { + // Free module if present + if (events[i].module) + { + if (events[i].module->name) + BNFreeString(events[i].module->name); + delete events[i].module; + } + + // Free thread if present + if (events[i].thread) + { + delete events[i].thread; + } + + // Free exception if present + if (events[i].exception) + { + delete events[i].exception; + } + } + + delete[] events; +} + + void BNDebuggerPostDebuggerEvent(BNDebuggerController* controller, BNDebuggerEvent* event) { diff --git a/debuggerui.qrc b/debuggerui.qrc index f1495492..c8e61334 100644 --- a/debuggerui.qrc +++ b/debuggerui.qrc @@ -25,5 +25,6 @@ icons/ttd-memory.png icons/ttd-calls.png icons/ttd-timestamp.png + icons/ttd-events.png diff --git a/docs/ttd-python-api.md b/docs/ttd-python-api.md index 2d073638..a6952473 100644 --- a/docs/ttd-python-api.md +++ b/docs/ttd-python-api.md @@ -85,6 +85,109 @@ class TTDCallEvent: """ ``` +### TTDEvent + +Represents important events that happened during a TTD trace. + +```python +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. + +```python +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. + +```python +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. + +```python +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. + +```python +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. + +```python +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 @@ -188,6 +291,26 @@ def get_ttd_calls_for_symbols( """ ``` +### get_ttd_events() + +```python +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() ```python @@ -283,6 +406,41 @@ if memory_events: dbg.set_ttd_position(current_pos) ``` +### TTD Events Analysis + +```python +# 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 ```python diff --git a/icons/ttd-events.png b/icons/ttd-events.png new file mode 100644 index 00000000..aff6aa61 Binary files /dev/null and b/icons/ttd-events.png differ diff --git a/ui/ttdeventswidget.cpp b/ui/ttdeventswidget.cpp new file mode 100644 index 00000000..77d82b3b --- /dev/null +++ b/ui/ttdeventswidget.cpp @@ -0,0 +1,1139 @@ +/* +Copyright 2020-2025 Vector 35 Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "ttdeventswidget.h" +#include "ui.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// TTDEventsColumnVisibilityDialog implementation +TTDEventsColumnVisibilityDialog::TTDEventsColumnVisibilityDialog(QWidget* parent, const QStringList& columnNames, const QList& visibility) + : QDialog(parent) +{ + setWindowTitle("Column Visibility"); + setModal(true); + resize(300, 400); + + QVBoxLayout* layout = new QVBoxLayout(this); + + QLabel* label = new QLabel("Select columns to display:"); + layout->addWidget(label); + + m_columnList = new QListWidget(); + + for (int i = 0; i < columnNames.size(); ++i) + { + QListWidgetItem* item = new QListWidgetItem(columnNames[i]); + item->setCheckState(visibility[i] ? Qt::Checked : Qt::Unchecked); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + m_columnList->addItem(item); + } + + layout->addWidget(m_columnList); + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); + connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); + + // Handle restore defaults + connect(buttons->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, [this]() { + // Reset to default visibility (show main columns, hide some detailed ones) + QList defaultVisibility; + defaultVisibility << true // Index + << true // Event Type + << true // Position + << true // Thread ID + << false // Thread Unique ID (hidden by default) + << true // Module Name + << true // Module Address + << false // Module Size (hidden by default) + << true // Exception Type + << true // Exception Code + << true; // Exception PC + + for (int i = 0; i < m_columnList->count() && i < defaultVisibility.size(); ++i) + { + QListWidgetItem* item = m_columnList->item(i); + item->setCheckState(defaultVisibility[i] ? Qt::Checked : Qt::Unchecked); + } + }); + + layout->addWidget(buttons); +} + +QList TTDEventsColumnVisibilityDialog::getColumnVisibility() const +{ + QList visibility; + for (int i = 0; i < m_columnList->count(); ++i) + { + QListWidgetItem* item = m_columnList->item(i); + visibility.append(item->checkState() == Qt::Checked); + } + return visibility; +} + +// TTDEventsQueryWidget implementation +TTDEventsQueryWidget::TTDEventsQueryWidget(QWidget* parent, BinaryViewRef data, WidgetType type) + : QWidget(parent), m_data(data), m_widgetType(type), + m_threadCreatedCheck(nullptr), m_threadTerminatedCheck(nullptr), + m_moduleLoadedCheck(nullptr), m_moduleUnloadedCheck(nullptr), + m_exceptionCheck(nullptr), m_queryButton(nullptr), m_clearButton(nullptr), + m_resultsTable(nullptr), m_statusLabel(nullptr), m_contextMenuManager(nullptr) +{ + m_controller = DebuggerController::GetController(data); + if (!m_controller) + { + // Create a placeholder widget showing no controller available + QVBoxLayout* layout = new QVBoxLayout(this); + QLabel* label = new QLabel("No debugger controller available for TTD Events analysis."); + label->setAlignment(Qt::AlignCenter); + layout->addWidget(label); + return; + } + + setupUI(); + setupTable(); + setupUIActions(); + setupContextMenu(); +} + +TTDEventsQueryWidget::~TTDEventsQueryWidget() +{ + if (m_contextMenuManager) + { + delete m_contextMenuManager; + } +} + +void TTDEventsQueryWidget::setupUI() +{ + QVBoxLayout* mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); // Add padding like TTD memory/calls widget + + // Only show input controls for AllEvents widget type + if (m_widgetType == AllEvents) + { + // Input controls + QGroupBox* inputGroup = new QGroupBox("Event Type Filters"); + QVBoxLayout* inputLayout = new QVBoxLayout(inputGroup); + + // Event type checkboxes + m_threadCreatedCheck = new QCheckBox("Thread Created"); + m_threadCreatedCheck->setChecked(true); + connect(m_threadCreatedCheck, &QCheckBox::toggled, this, &TTDEventsQueryWidget::onFilterChanged); + inputLayout->addWidget(m_threadCreatedCheck); + + m_threadTerminatedCheck = new QCheckBox("Thread Terminated"); + m_threadTerminatedCheck->setChecked(true); + connect(m_threadTerminatedCheck, &QCheckBox::toggled, this, &TTDEventsQueryWidget::onFilterChanged); + inputLayout->addWidget(m_threadTerminatedCheck); + + m_moduleLoadedCheck = new QCheckBox("Module Loaded"); + m_moduleLoadedCheck->setChecked(true); + connect(m_moduleLoadedCheck, &QCheckBox::toggled, this, &TTDEventsQueryWidget::onFilterChanged); + inputLayout->addWidget(m_moduleLoadedCheck); + + m_moduleUnloadedCheck = new QCheckBox("Module Unloaded"); + m_moduleUnloadedCheck->setChecked(true); + connect(m_moduleUnloadedCheck, &QCheckBox::toggled, this, &TTDEventsQueryWidget::onFilterChanged); + inputLayout->addWidget(m_moduleUnloadedCheck); + + m_exceptionCheck = new QCheckBox("Exception"); + m_exceptionCheck->setChecked(true); + connect(m_exceptionCheck, &QCheckBox::toggled, this, &TTDEventsQueryWidget::onFilterChanged); + inputLayout->addWidget(m_exceptionCheck); + + // Query buttons + QHBoxLayout* buttonLayout = new QHBoxLayout(); + + m_queryButton = new QPushButton("Query All TTD Events"); + m_queryButton->setDefault(true); + connect(m_queryButton, &QPushButton::clicked, this, &TTDEventsQueryWidget::performQuery); + buttonLayout->addWidget(m_queryButton); + + m_clearButton = new QPushButton("Clear Results"); + connect(m_clearButton, &QPushButton::clicked, this, &TTDEventsQueryWidget::clearResults); + buttonLayout->addWidget(m_clearButton); + + buttonLayout->addStretch(); + inputLayout->addLayout(buttonLayout); + + mainLayout->addWidget(inputGroup); + } + else + { + // For specialized widgets, initialize checkboxes as null pointers + m_threadCreatedCheck = nullptr; + m_threadTerminatedCheck = nullptr; + m_moduleLoadedCheck = nullptr; + m_moduleUnloadedCheck = nullptr; + m_exceptionCheck = nullptr; + m_queryButton = nullptr; + m_clearButton = nullptr; + } + + // Results table + m_resultsTable = new QTableWidget(); + mainLayout->addWidget(m_resultsTable, 1); // Give table most of the space + + // Status label + m_statusLabel = new QLabel("Ready to query TTD events."); + m_statusLabel->setContentsMargins(5, 5, 5, 5); // Add padding around status text + mainLayout->addWidget(m_statusLabel); + + // Connect double-click on table + connect(m_resultsTable, &QTableWidget::cellDoubleClicked, this, &TTDEventsQueryWidget::onCellDoubleClicked); +} + +void TTDEventsQueryWidget::setupTable() +{ + // Configure columns and visibility based on widget type + switch (m_widgetType) + { + case ModuleEvents: + m_columnNames << "Index" << "Position" << "Event Type" << "Name" + << "Module Address" << "Module Size" << "Module Checksum" << "Module Timestamp" << "Path"; + m_columnVisibility << true // Index + << true // Position + << true // Event Type + << true // Name + << true // Module Address + << true // Module Size + << false // Module Checksum (hidden by default) + << false // Module Timestamp (hidden by default) + << true; // Path (moved to last column) + break; + + case ThreadEvents: + m_columnNames << "Index" << "Position" << "Event Type" << "Thread ID" << "Thread UniqueID" + << "Lifetime Start" << "Lifetime End" << "Active Start" << "Active End"; + m_columnVisibility << true // Index + << true // Position + << true // Event Type + << true // Thread ID + << true // Thread UniqueID + << true // Lifetime Start + << true // Lifetime End + << true // Active Start + << true; // Active End + break; + + case ExceptionEvents: + m_columnNames << "Index" << "Position" << "Exception Type" << "Program Counter" + << "Exception Code" << "Exception Flags" << "Record Address"; + m_columnVisibility << true // Index + << true // Position + << true // Exception Type + << true // Program Counter + << true // Exception Code + << true // Exception Flags + << true; // Record Address + break; + + default: // AllEvents + m_columnNames << "Index" << "Event Type" << "Position" << "Thread ID" << "Thread UniqueID" + << "Module Name" << "Module Address" << "Module Size" << "Exception Type" + << "Exception Code" << "Exception PC"; + // Default column visibility (show main columns, hide some detailed ones) + m_columnVisibility << true // Index + << true // Event Type + << true // Position + << true // Thread ID + << false // Thread Unique ID (hidden by default) + << true // Module Name + << true // Module Address + << false // Module Size (hidden by default) + << true // Exception Type + << true // Exception Code + << true; // Exception PC + break; + } + + m_resultsTable->setColumnCount(m_columnNames.size()); + m_resultsTable->setHorizontalHeaderLabels(m_columnNames); + + // Make cells non-editable + m_resultsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + + // Hide the row numbers (vertical header) but keep the Index column + m_resultsTable->verticalHeader()->setVisible(false); + + // Enable sorting + m_resultsTable->setSortingEnabled(true); + + // Set selection behavior + m_resultsTable->setSelectionBehavior(QAbstractItemView::SelectRows); + m_resultsTable->setAlternatingRowColors(true); + + // Adjust column widths + QHeaderView* header = m_resultsTable->horizontalHeader(); + header->setStretchLastSection(true); + header->setSectionResizeMode(QHeaderView::Interactive); + + // Set context menu policy + m_resultsTable->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_resultsTable, &QTableWidget::customContextMenuRequested, this, &TTDEventsQueryWidget::showContextMenu); + + // Apply initial column visibility + updateColumnVisibility(); +} + +void TTDEventsQueryWidget::updateColumnVisibility() +{ + for (int i = 0; i < m_columnNames.size() && i < m_columnVisibility.size(); ++i) + { + m_resultsTable->setColumnHidden(i, !m_columnVisibility[i]); + } +} + +void TTDEventsQueryWidget::updateStatus(const QString& message) +{ + if (m_statusLabel) + { + m_statusLabel->setText(message); + } +} + +void TTDEventsQueryWidget::setupUIActions() +{ + m_actionHandler.setupActionHandler(this); + m_contextMenuManager = new ContextMenuManager(this); + m_menu = new Menu(); + + // Add Copy action with Ctrl+C support + m_menu->addAction("Copy", "Options", MENU_ORDER_NORMAL); + m_actionHandler.bindAction("Copy", UIAction([&]() { copy(); }, [&]() { return canCopy(); })); + + m_menu->addAction("Copy Row", "Options", MENU_ORDER_NORMAL); + m_actionHandler.bindAction("Copy Row", UIAction([&]() { copySelectedRow(); }, [&]() { return canCopy(); })); + + m_menu->addAction("Copy Table", "Options", MENU_ORDER_NORMAL); + m_actionHandler.bindAction("Copy Table", UIAction([&]() { copyEntireTable(); }, [&]() { return m_resultsTable->rowCount() > 0; })); + + m_menu->addAction("Column Visibility...", "Options", MENU_ORDER_NORMAL); + m_actionHandler.bindAction("Column Visibility...", UIAction([&]() { showColumnVisibilityDialog(); })); + + m_menu->addAction("Reset Columns to Default", "Options", MENU_ORDER_NORMAL); + m_actionHandler.bindAction("Reset Columns to Default", UIAction([&]() { resetColumnsToDefault(); })); + + // Refresh action to clear and re-query from backend + m_menu->addAction("Refresh", "Options", MENU_ORDER_NORMAL); + m_actionHandler.bindAction("Refresh", UIAction([&]() { refreshEvents(); })); +} + +void TTDEventsQueryWidget::setupContextMenu() +{ + m_resultsTable->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_resultsTable, &QTableWidget::customContextMenuRequested, + this, &TTDEventsQueryWidget::showContextMenu); +} + +void TTDEventsQueryWidget::performQuery() +{ + if (!m_controller) + { + updateStatus("No debugger controller available."); + return; + } + + if (!m_controller->IsConnected()) + { + updateStatus("No active debugging session."); + return; + } + + if (!m_controller->IsTTD()) + { + updateStatus("No a TTD debugging session."); + return; + } + + updateStatus("Querying all TTD events..."); + + // Only disable button if it exists (AllEvents widgets have buttons, specialized don't) + if (m_queryButton) + m_queryButton->setEnabled(false); + + try + { + // Get all events and cache them + m_allEvents = m_controller->GetAllTTDEvents(); + + updateStatus(QString("Query completed. Loaded %1 total events.").arg(m_allEvents.size())); + + // Filter and display events based on widget type + if (m_widgetType == AllEvents) + { + filterAndDisplayEvents(); + } + else + { + filterAndDisplaySpecializedEvents(); + } + } + catch (const std::exception& e) + { + updateStatus(QString("Query failed: %1").arg(e.what())); + QMessageBox::warning(this, "TTD Events Query Error", QString("Failed to query TTD events:\n%1").arg(e.what())); + } + + // Only re-enable button if it exists + if (m_queryButton) + m_queryButton->setEnabled(true); +} + +void TTDEventsQueryWidget::filterAndDisplayEvents() +{ + if (m_allEvents.empty()) + { + m_resultsTable->setRowCount(0); + updateStatus("No events to display."); + return; + } + + // Filter events based on checkbox states + std::vector filteredEvents; + + for (const auto& event : m_allEvents) + { + bool shouldInclude = false; + + switch (event.type) + { + case TTDEventThreadCreated: + shouldInclude = m_threadCreatedCheck ? m_threadCreatedCheck->isChecked() : false; + break; + case TTDEventThreadTerminated: + shouldInclude = m_threadTerminatedCheck ? m_threadTerminatedCheck->isChecked() : false; + break; + case TTDEventModuleLoaded: + shouldInclude = m_moduleLoadedCheck ? m_moduleLoadedCheck->isChecked() : false; + break; + case TTDEventModuleUnloaded: + shouldInclude = m_moduleUnloadedCheck ? m_moduleUnloadedCheck->isChecked() : false; + break; + case TTDEventException: + shouldInclude = m_exceptionCheck ? m_exceptionCheck->isChecked() : false; + break; + } + + if (shouldInclude) + { + filteredEvents.push_back(event); + } + } + + // Populate table with filtered results + m_resultsTable->setRowCount(filteredEvents.size()); + + for (size_t i = 0; i < filteredEvents.size(); ++i) + { + const TTDEvent& event = filteredEvents[i]; + + // Index + m_resultsTable->setItem(i, IndexColumn, new QTableWidgetItem(QString::number(i + 1))); + + // Event Type + QString eventTypeStr; + switch (event.type) + { + case TTDEventThreadCreated: + eventTypeStr = "ThreadCreated"; + break; + case TTDEventThreadTerminated: + eventTypeStr = "ThreadTerminated"; + break; + case TTDEventModuleLoaded: + eventTypeStr = "ModuleLoaded"; + break; + case TTDEventModuleUnloaded: + eventTypeStr = "ModuleUnloaded"; + break; + case TTDEventException: + eventTypeStr = "Exception"; + break; + default: + eventTypeStr = "Unknown"; + break; + } + m_resultsTable->setItem(i, EventTypeColumn, new QTableWidgetItem(eventTypeStr)); + + // Position + QString positionStr = QString("%1:%2").arg(event.position.sequence, 0, 16).arg(event.position.step, 0, 16); + m_resultsTable->setItem(i, PositionColumn, new QTableWidgetItem(positionStr)); + + // Thread details (if available) + if (event.thread.has_value()) + { + m_resultsTable->setItem(i, ThreadIdColumn, new QTableWidgetItem(QString::number(event.thread->id))); + m_resultsTable->setItem(i, ThreadUniqueIdColumn, new QTableWidgetItem(QString::number(event.thread->uniqueId))); + } + else + { + m_resultsTable->setItem(i, ThreadIdColumn, new QTableWidgetItem("")); + m_resultsTable->setItem(i, ThreadUniqueIdColumn, new QTableWidgetItem("")); + } + + // Module details (if available) + if (event.module.has_value()) + { + m_resultsTable->setItem(i, ModuleNameColumn, new QTableWidgetItem(QString::fromStdString(event.module->name))); + m_resultsTable->setItem(i, ModuleAddressColumn, new QTableWidgetItem(QString("0x%1").arg(event.module->address, 0, 16))); + m_resultsTable->setItem(i, ModuleSizeColumn, new QTableWidgetItem(QString::number(event.module->size))); + } + else + { + m_resultsTable->setItem(i, ModuleNameColumn, new QTableWidgetItem("")); + m_resultsTable->setItem(i, ModuleAddressColumn, new QTableWidgetItem("")); + m_resultsTable->setItem(i, ModuleSizeColumn, new QTableWidgetItem("")); + } + + // Exception details (if available) + if (event.exception.has_value()) + { + QString exceptionTypeStr = (event.exception->type == TTDExceptionHardware) ? "Hardware" : "Software"; + m_resultsTable->setItem(i, ExceptionTypeColumn, new QTableWidgetItem(exceptionTypeStr)); + m_resultsTable->setItem(i, ExceptionCodeColumn, new QTableWidgetItem(QString("0x%1").arg(event.exception->code, 0, 16))); + m_resultsTable->setItem(i, ExceptionPCColumn, new QTableWidgetItem(QString("0x%1").arg(event.exception->programCounter, 0, 16))); + } + else + { + m_resultsTable->setItem(i, ExceptionTypeColumn, new QTableWidgetItem("")); + m_resultsTable->setItem(i, ExceptionCodeColumn, new QTableWidgetItem("")); + m_resultsTable->setItem(i, ExceptionPCColumn, new QTableWidgetItem("")); + } + } + + m_resultsTable->resizeColumnsToContents(); + updateStatus(QString("Displaying %1 of %2 events.").arg(filteredEvents.size()).arg(m_allEvents.size())); +} + +void TTDEventsQueryWidget::filterAndDisplaySpecializedEvents() +{ + if (m_allEvents.empty()) + { + m_resultsTable->setRowCount(0); + updateStatus("No events to display."); + return; + } + + // Filter events based on widget type + std::vector filteredEvents; + + for (const auto& event : m_allEvents) + { + bool shouldInclude = false; + + switch (m_widgetType) + { + case ModuleEvents: + shouldInclude = (event.type == TTDEventModuleLoaded || event.type == TTDEventModuleUnloaded); + break; + case ThreadEvents: + shouldInclude = (event.type == TTDEventThreadCreated || event.type == TTDEventThreadTerminated); + break; + case ExceptionEvents: + shouldInclude = (event.type == TTDEventException); + break; + default: + shouldInclude = true; // AllEvents + break; + } + + if (shouldInclude) + { + filteredEvents.push_back(event); + } + } + + // Populate table with filtered results using specialized columns + m_resultsTable->setRowCount(filteredEvents.size()); + + for (size_t i = 0; i < filteredEvents.size(); ++i) + { + const TTDEvent& event = filteredEvents[i]; + int col = 0; + + // Index (always first column) + m_resultsTable->setItem(i, col++, new QTableWidgetItem(QString::number(i + 1))); + + switch (m_widgetType) + { + case ModuleEvents: + // Position, Event Type, Name (base name), Module Address, Module Size, Module Checksum, Module Timestamp, Path (full path) + { + QString positionStr = QString("%1:%2").arg(event.position.sequence, 0, 16).arg(event.position.step, 0, 16); + m_resultsTable->setItem(i, col++, new QTableWidgetItem(positionStr)); + + QString eventTypeStr = (event.type == TTDEventModuleLoaded) ? "Loaded" : "Unloaded"; + m_resultsTable->setItem(i, col++, new QTableWidgetItem(eventTypeStr)); + + if (event.module.has_value()) + { + // Extract base name from full path + QString fullPath = QString::fromStdString(event.module->name); + QString baseName = QFileInfo(fullPath).fileName(); + if (baseName.isEmpty()) + baseName = fullPath; // fallback to full path if no filename + + m_resultsTable->setItem(i, col++, new QTableWidgetItem(baseName)); + m_resultsTable->setItem(i, col++, new QTableWidgetItem(QString("0x%1").arg(event.module->address, 0, 16))); + m_resultsTable->setItem(i, col++, new QTableWidgetItem(QString::number(event.module->size))); + m_resultsTable->setItem(i, col++, new QTableWidgetItem(QString("0x%1").arg(event.module->checksum, 0, 16))); + m_resultsTable->setItem(i, col++, new QTableWidgetItem(QString("0x%1").arg(event.module->timestamp, 0, 16))); + m_resultsTable->setItem(i, col++, new QTableWidgetItem(fullPath)); // Full path in last column + } + else + { + // Fill empty cells for all module columns + for (int j = 0; j < 6; j++) + m_resultsTable->setItem(i, col++, new QTableWidgetItem("")); + } + } + break; + + case ThreadEvents: + // Position, Event Type, Thread ID, Thread UniqueID, Lifetime Start, Lifetime End, Active Start, Active End + { + QString positionStr = QString("%1:%2").arg(event.position.sequence, 0, 16).arg(event.position.step, 0, 16); + m_resultsTable->setItem(i, col++, new QTableWidgetItem(positionStr)); + + QString eventTypeStr = (event.type == TTDEventThreadCreated) ? "Created" : "Terminated"; + m_resultsTable->setItem(i, col++, new QTableWidgetItem(eventTypeStr)); + + if (event.thread.has_value()) + { + m_resultsTable->setItem(i, col++, new QTableWidgetItem(QString::number(event.thread->id))); + m_resultsTable->setItem(i, col++, new QTableWidgetItem(QString::number(event.thread->uniqueId))); + + // Lifetime range + QString lifetimeStart = QString("%1:%2").arg(event.thread->lifetimeStart.sequence, 0, 16).arg(event.thread->lifetimeStart.step, 0, 16); + QString lifetimeEnd = QString("%1:%2").arg(event.thread->lifetimeEnd.sequence, 0, 16).arg(event.thread->lifetimeEnd.step, 0, 16); + m_resultsTable->setItem(i, col++, new QTableWidgetItem(lifetimeStart)); + m_resultsTable->setItem(i, col++, new QTableWidgetItem(lifetimeEnd)); + + // Active time range + QString activeStart = QString("%1:%2").arg(event.thread->activeTimeStart.sequence, 0, 16).arg(event.thread->activeTimeStart.step, 0, 16); + QString activeEnd = QString("%1:%2").arg(event.thread->activeTimeEnd.sequence, 0, 16).arg(event.thread->activeTimeEnd.step, 0, 16); + m_resultsTable->setItem(i, col++, new QTableWidgetItem(activeStart)); + m_resultsTable->setItem(i, col++, new QTableWidgetItem(activeEnd)); + } + else + { + m_resultsTable->setItem(i, col++, new QTableWidgetItem("")); + m_resultsTable->setItem(i, col++, new QTableWidgetItem("")); + m_resultsTable->setItem(i, col++, new QTableWidgetItem("")); + m_resultsTable->setItem(i, col++, new QTableWidgetItem("")); + m_resultsTable->setItem(i, col++, new QTableWidgetItem("")); + m_resultsTable->setItem(i, col++, new QTableWidgetItem("")); + } + } + break; + + case ExceptionEvents: + // Position, Exception Type, Program Counter, Exception Code, Exception Flags, Record Address + { + QString positionStr = QString("%1:%2").arg(event.position.sequence, 0, 16).arg(event.position.step, 0, 16); + m_resultsTable->setItem(i, col++, new QTableWidgetItem(positionStr)); + + if (event.exception.has_value()) + { + QString exceptionTypeStr = (event.exception->type == TTDExceptionHardware) ? "Hardware" : "Software"; + m_resultsTable->setItem(i, col++, new QTableWidgetItem(exceptionTypeStr)); + m_resultsTable->setItem(i, col++, new QTableWidgetItem(QString("0x%1").arg(event.exception->programCounter, 0, 16))); + m_resultsTable->setItem(i, col++, new QTableWidgetItem(QString("0x%1").arg(event.exception->code, 0, 16))); + m_resultsTable->setItem(i, col++, new QTableWidgetItem(QString("0x%1").arg(event.exception->flags, 0, 16))); + m_resultsTable->setItem(i, col++, new QTableWidgetItem(QString("0x%1").arg(event.exception->recordAddress, 0, 16))); + } + else + { + m_resultsTable->setItem(i, col++, new QTableWidgetItem("")); + m_resultsTable->setItem(i, col++, new QTableWidgetItem("")); + m_resultsTable->setItem(i, col++, new QTableWidgetItem("")); + m_resultsTable->setItem(i, col++, new QTableWidgetItem("")); + m_resultsTable->setItem(i, col++, new QTableWidgetItem("")); + } + } + break; + + default: + // This shouldn't happen for specialized widgets + break; + } + } + + m_resultsTable->resizeColumnsToContents(); + updateStatus(QString("Displaying %1 events.").arg(filteredEvents.size())); +} + +void TTDEventsQueryWidget::onFilterChanged() +{ + // Re-filter and display events when checkbox states change + // Only applies to AllEvents widget type (specialized widgets don't have checkboxes) + if (m_widgetType == AllEvents && !m_allEvents.empty()) + { + filterAndDisplayEvents(); + } +} + +void TTDEventsQueryWidget::clearResults() +{ + m_resultsTable->setRowCount(0); + m_allEvents.clear(); + updateStatus("Results cleared."); +} + +void TTDEventsQueryWidget::refreshEvents() +{ + // Clear current contents and re-query from backend + clearResults(); + performQuery(); +} + +void TTDEventsQueryWidget::onCellDoubleClicked(int row, int column) +{ + if (!m_controller) + return; + + QTableWidgetItem* item = m_resultsTable->item(row, column); + if (!item) + return; + + QString cellText = item->text(); + + // Check if this is a position column - navigate to TTD position + QString columnName = m_resultsTable->horizontalHeaderItem(column) ? + m_resultsTable->horizontalHeaderItem(column)->text() : ""; + + if (columnName.contains("Position", Qt::CaseInsensitive) || column == PositionColumn) + { + QStringList parts = cellText.split(':'); + if (parts.size() == 2) + { + bool ok1, ok2; + uint64_t sequence = parts[0].toULongLong(&ok1, 16); + uint64_t step = parts[1].toULongLong(&ok2, 16); + + if (ok1 && ok2) + { + TTDPosition position(sequence, step); + if (m_controller->SetTTDPosition(position)) + { + updateStatus(QString("Navigated to position %1:%2").arg(sequence, 0, 16).arg(step, 0, 16)); + } + else + { + updateStatus("Failed to navigate to position"); + } + } + } + } + // Check if this is an address column - jump to address + else if (columnName.contains("Address", Qt::CaseInsensitive) || + columnName.contains("PC", Qt::CaseInsensitive) || + column == ModuleAddressColumn || + column == ExceptionPCColumn) + { + if (cellText.startsWith("0x")) + { + bool ok; + uint64_t address = cellText.mid(2).toULongLong(&ok, 16); + if (ok) + { + // Jump to address in disassembly view + // Navigate to the address in the disassembly view + ViewFrame* frame = ViewFrame::viewFrameForWidget(this); + if (frame) + { + frame->navigate(m_data, address); + updateStatus(QString("Navigated to address %1").arg(cellText)); + } + else + { + updateStatus(QString("Address: %1 (no view frame available)").arg(cellText)); + } + } + } + } +} + +void TTDEventsQueryWidget::contextMenuEvent(QContextMenuEvent* event) +{ + if (m_contextMenuManager) + { + m_contextMenuManager->show(m_menu, &m_actionHandler); + } +} + +void TTDEventsQueryWidget::showContextMenu(const QPoint& position) +{ + QPoint globalPos = m_resultsTable->mapToGlobal(position); + if (m_contextMenuManager) + { + m_contextMenuManager->show(m_menu, &m_actionHandler); + } +} + +void TTDEventsQueryWidget::showColumnVisibilityDialog() +{ + TTDEventsColumnVisibilityDialog dialog(this, m_columnNames, m_columnVisibility); + if (dialog.exec() == QDialog::Accepted) + { + m_columnVisibility = dialog.getColumnVisibility(); + updateColumnVisibility(); + } +} + +void TTDEventsQueryWidget::resetColumnsToDefault() +{ + // Reset to default visibility + m_columnVisibility.clear(); + m_columnVisibility << true // Index + << true // Event Type + << true // Position + << true // Thread ID + << false // Thread Unique ID (hidden by default) + << true // Module Name + << true // Module Address + << false // Module Size (hidden by default) + << true // Exception Type + << true // Exception Code + << true; // Exception PC + + updateColumnVisibility(); +} + +bool TTDEventsQueryWidget::canCopy() +{ + return m_resultsTable->selectionModel()->hasSelection(); +} + +void TTDEventsQueryWidget::copy() +{ + copySelectedRow(); +} + +void TTDEventsQueryWidget::copySelectedCell() +{ + QItemSelectionModel* selectionModel = m_resultsTable->selectionModel(); + if (!selectionModel->hasSelection()) + return; + + QModelIndexList selected = selectionModel->selectedIndexes(); + if (selected.isEmpty()) + return; + + QTableWidgetItem* item = m_resultsTable->item(selected.first().row(), selected.first().column()); + if (item) + { + QClipboard* clipboard = QApplication::clipboard(); + clipboard->setText(item->text()); + } +} + +void TTDEventsQueryWidget::copySelectedRow() +{ + QItemSelectionModel* selectionModel = m_resultsTable->selectionModel(); + if (!selectionModel->hasSelection()) + return; + + QModelIndexList selected = selectionModel->selectedRows(); + if (selected.isEmpty()) + return; + + QStringList rowData; + int row = selected.first().row(); + + for (int col = 0; col < m_resultsTable->columnCount(); ++col) + { + if (!m_resultsTable->isColumnHidden(col)) + { + QTableWidgetItem* item = m_resultsTable->item(row, col); + rowData << (item ? item->text() : ""); + } + } + + QClipboard* clipboard = QApplication::clipboard(); + clipboard->setText(rowData.join('\t')); +} + +void TTDEventsQueryWidget::copyEntireTable() +{ + QStringList tableData; + + // Header row + QStringList headers; + for (int col = 0; col < m_resultsTable->columnCount(); ++col) + { + if (!m_resultsTable->isColumnHidden(col)) + { + headers << m_columnNames[col]; + } + } + tableData << headers.join('\t'); + + // Data rows + for (int row = 0; row < m_resultsTable->rowCount(); ++row) + { + QStringList rowData; + for (int col = 0; col < m_resultsTable->columnCount(); ++col) + { + if (!m_resultsTable->isColumnHidden(col)) + { + QTableWidgetItem* item = m_resultsTable->item(row, col); + rowData << (item ? item->text() : ""); + } + } + tableData << rowData.join('\t'); + } + + QClipboard* clipboard = QApplication::clipboard(); + clipboard->setText(tableData.join('\n')); +} + +void TTDEventsQueryWidget::performInitialQuery() +{ + // For specialized widgets, automatically load and filter events + if (m_widgetType != AllEvents) + { + // First perform the query to get all events + performQuery(); + + // Then filter based on widget type + filterAndDisplaySpecializedEvents(); + } + else + { + // For AllEvents widget, just perform the query + performQuery(); + } +} + +bool TTDEventsQueryWidget::isUnused() const +{ + return m_allEvents.empty(); +} + +// TTDEventsWidget implementation +TTDEventsWidget::TTDEventsWidget(QWidget* parent, BinaryViewRef data) + : QWidget(parent), m_data(data), m_isPopulated(false) +{ + m_controller = DebuggerController::GetController(data); + setupUI(); + + // Only automatically load events if the debugger is connected and running TTD + if (m_controller && m_controller->IsConnected() && m_controller->IsTTD()) + { + loadAllEvents(); + m_isPopulated = true; + } +} + +TTDEventsWidget::~TTDEventsWidget() +{ +} + +void TTDEventsWidget::setupUI() +{ + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + + // Create tab widget + m_tabWidget = new QTabWidget(); + m_tabWidget->setTabsClosable(true); + connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &TTDEventsWidget::closeTab); + layout->addWidget(m_tabWidget); + + // Create "+" button as corner widget + m_newTabButton = new QToolButton(m_tabWidget); + m_newTabButton->setText("+"); + m_newTabButton->setAutoRaise(true); + m_newTabButton->setToolTip("New tab"); + connect(m_newTabButton, &QToolButton::clicked, this, &TTDEventsWidget::createNewTab); + + // Set the button as corner widget + m_tabWidget->setCornerWidget(m_newTabButton, Qt::TopRightCorner); + + // Create the 3 specialized tabs + m_moduleEventsWidget = new TTDEventsQueryWidget(this, m_data, TTDEventsQueryWidget::ModuleEvents); + m_threadEventsWidget = new TTDEventsQueryWidget(this, m_data, TTDEventsQueryWidget::ThreadEvents); + m_exceptionEventsWidget = new TTDEventsQueryWidget(this, m_data, TTDEventsQueryWidget::ExceptionEvents); + + // Add tabs to the widget + m_tabWidget->addTab(m_moduleEventsWidget, "Module Events"); + m_tabWidget->addTab(m_threadEventsWidget, "Thread Events"); + m_tabWidget->addTab(m_exceptionEventsWidget, "Exception Events"); +} + +void TTDEventsWidget::loadAllEvents() +{ + if (m_moduleEventsWidget) + m_moduleEventsWidget->performInitialQuery(); + if (m_threadEventsWidget) + m_threadEventsWidget->performInitialQuery(); + if (m_exceptionEventsWidget) + m_exceptionEventsWidget->performInitialQuery(); +} + +void TTDEventsWidget::refreshAllTabs() +{ + // Only refresh if debugger is connected and data is available and we haven't already populated + if (m_controller && m_controller->IsConnected() && m_controller->IsTTD() && !m_isPopulated) + { + loadAllEvents(); + m_isPopulated = true; + } +} + +void TTDEventsWidget::clearAllTabs() +{ + if (m_moduleEventsWidget) + m_moduleEventsWidget->clearResults(); + if (m_threadEventsWidget) + m_threadEventsWidget->clearResults(); + if (m_exceptionEventsWidget) + m_exceptionEventsWidget->clearResults(); + + // Clear all custom tabs as well + for (int i = 3; i < m_tabWidget->count(); i++) + { + TTDEventsQueryWidget* widget = qobject_cast(m_tabWidget->widget(i)); + if (widget) + widget->clearResults(); + } + + m_isPopulated = false; +} + +TTDEventsQueryWidget* TTDEventsWidget::getCurrentOrNewQueryWidget() +{ + // Get current tab widget + TTDEventsQueryWidget* currentWidget = qobject_cast(m_tabWidget->currentWidget()); + if (currentWidget) + return currentWidget; + + // If no current widget or cast failed, create a new tab + createNewTab(); + return qobject_cast(m_tabWidget->currentWidget()); +} + +void TTDEventsWidget::createNewTab() +{ + // Create new tab with AllEvents type (has filtering controls) + TTDEventsQueryWidget* queryWidget = new TTDEventsQueryWidget(this, m_data, TTDEventsQueryWidget::AllEvents); + int tabIndex = m_tabWidget->addTab(queryWidget, QString("Query %1").arg(m_tabWidget->count() - 2)); // -2 because we have 3 fixed tabs + m_tabWidget->setCurrentIndex(tabIndex); + + // If we've already populated the specialized tabs, also populate this new tab + if (m_isPopulated) + { + queryWidget->performInitialQuery(); + } +} + +void TTDEventsWidget::closeTab(int index) +{ + // Don't allow closing the first 3 specialized tabs + if (index >= 3 && m_tabWidget->count() > 3) + { + QWidget* widget = m_tabWidget->widget(index); + m_tabWidget->removeTab(index); + widget->deleteLater(); + } +} + +// TTDEventsSidebarWidget implementation +TTDEventsSidebarWidget::TTDEventsSidebarWidget(BinaryViewRef data) + : SidebarWidget("TTD Events"), m_data(data) +{ + m_controller = DebuggerController::GetController(data); + + QVBoxLayout* layout = new QVBoxLayout(); + layout->setContentsMargins(0, 0, 0, 0); + + m_eventsWidget = new TTDEventsWidget(this, data); + layout->addWidget(m_eventsWidget); + + setLayout(layout); + + // Register for debugger events + if (m_controller) + { + connect(this, &TTDEventsSidebarWidget::debuggerEvent, this, &TTDEventsSidebarWidget::onDebuggerEvent); + + m_debuggerEventCallback = m_controller->RegisterEventCallback( + [&](const DebuggerEvent& event) { + emit debuggerEvent(event); + }, + "TTD Events Widget"); + } +} + +TTDEventsSidebarWidget::~TTDEventsSidebarWidget() +{ + if (m_controller) + m_controller->RemoveEventCallback(m_debuggerEventCallback); +} + +void TTDEventsSidebarWidget::onDebuggerEvent(const DebuggerEvent& event) +{ + switch (event.type) + { + case TargetStoppedEventType: + // When target stops, refresh all tabs if not already populated + if (m_eventsWidget) + m_eventsWidget->refreshAllTabs(); + break; + case TargetExitedEventType: + case DetachedEventType: + // When target exits or is detached, clear all contents + if (m_eventsWidget) + m_eventsWidget->clearAllTabs(); + break; + default: + break; + } +} + +// TTDEventsWidgetType implementation +TTDEventsWidgetType::TTDEventsWidgetType() + : SidebarWidgetType(QImage(":/debugger/ttd-events"), "TTD Events") +{ +} + +SidebarWidget* TTDEventsWidgetType::createWidget(ViewFrame* frame, BinaryViewRef data) +{ + TTDEventsSidebarWidget* result = new TTDEventsSidebarWidget(data); + return result; +} + +SidebarContentClassifier* TTDEventsWidgetType::contentClassifier(ViewFrame*, BinaryViewRef data) +{ + return new ActiveDebugSessionSidebarContentClassifier(data); +} + +#include "ttdeventswidget.moc" \ No newline at end of file diff --git a/ui/ttdeventswidget.h b/ui/ttdeventswidget.h new file mode 100644 index 00000000..e4b99111 --- /dev/null +++ b/ui/ttdeventswidget.h @@ -0,0 +1,229 @@ +/* +Copyright 2020-2025 Vector 35 Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "inttypes.h" +#include "binaryninjaapi.h" +#include "debuggerapi.h" +#include "viewframe.h" +#include "expandablegroup.h" +#include "debuggeruicommon.h" +#include "menus.h" +#include "uitypes.h" +#include + +using namespace BinaryNinja; +using namespace BinaryNinjaDebuggerAPI; + +class TTDEventsColumnVisibilityDialog : public QDialog +{ + Q_OBJECT + +public: + TTDEventsColumnVisibilityDialog(QWidget* parent, const QStringList& columnNames, const QList& visibility); + QList getColumnVisibility() const; + +private: + QListWidget* m_columnList; +}; + +class TTDEventsQueryWidget : public QWidget +{ + Q_OBJECT + +public: + // Enum for widget specialization + enum WidgetType { + AllEvents, // Shows all events with filtering checkboxes + ModuleEvents, // Shows only module events with relevant columns + ThreadEvents, // Shows only thread events with relevant columns + ExceptionEvents // Shows only exception events with relevant columns + }; + + // Enum for logical column identification + enum LogicalColumn { + IndexColumn = 0, + EventTypeColumn, + PositionColumn, + ThreadIdColumn, + ThreadUniqueIdColumn, + ModuleNameColumn, + ModuleAddressColumn, + ModuleSizeColumn, + ExceptionTypeColumn, + ExceptionCodeColumn, + ExceptionPCColumn + }; + +private: + BinaryViewRef m_data; + DbgRef m_controller; + WidgetType m_widgetType; // Determines specialization mode + + // Input controls - checkboxes for filtering + QCheckBox* m_threadCreatedCheck; + QCheckBox* m_threadTerminatedCheck; + QCheckBox* m_moduleLoadedCheck; + QCheckBox* m_moduleUnloadedCheck; + QCheckBox* m_exceptionCheck; + QPushButton* m_queryButton; + QPushButton* m_clearButton; + + // Results table + QTableWidget* m_resultsTable; + + // Status label + QLabel* m_statusLabel; + + // Column visibility + QStringList m_columnNames; + QList m_columnVisibility; + + // UIAction support + UIActionHandler m_actionHandler; + ContextMenuManager* m_contextMenuManager; + Menu* m_menu; + + // All events cache + std::vector m_allEvents; + + void setupUI(); + void setupTable(); + void updateStatus(const QString& message); + void setupContextMenu(); + void setupUIActions(); + void updateColumnVisibility(); + bool canCopy(); + void filterAndDisplayEvents(); + void filterAndDisplaySpecializedEvents(); // For specialized widget types + + virtual void contextMenuEvent(QContextMenuEvent* event) override; + +public: + TTDEventsQueryWidget(QWidget* parent, BinaryViewRef data, WidgetType type = AllEvents); + virtual ~TTDEventsQueryWidget(); + + // Method to execute query and show all events + void performInitialQuery(); + + // Method to check if this tab is unused (no results) + bool isUnused() const; + +public Q_SLOTS: + void clearResults(); + +private Q_SLOTS: + void performQuery(); + void onCellDoubleClicked(int row, int column); + void showColumnVisibilityDialog(); + void resetColumnsToDefault(); + void showContextMenu(const QPoint& position); + void copy(); + void copySelectedCell(); + void copySelectedRow(); + void copyEntireTable(); + void onFilterChanged(); + void refreshEvents(); // Clear and re-query events from backend +}; + +class TTDEventsWidget : public QWidget +{ + Q_OBJECT + +private: + BinaryViewRef m_data; + DbgRef m_controller; + QTabWidget* m_tabWidget; + QToolButton* m_newTabButton; + TTDEventsQueryWidget *m_moduleEventsWidget, *m_threadEventsWidget, *m_exceptionEventsWidget; + bool m_isPopulated; + + void setupUI(); + void loadAllEvents(); + +public: + TTDEventsWidget(QWidget* parent, BinaryViewRef data); + virtual ~TTDEventsWidget(); + + // Method to get current query widget or create new tab + TTDEventsQueryWidget* getCurrentOrNewQueryWidget(); + + // Methods for event handling + void refreshAllTabs(); + void clearAllTabs(); + +private slots: + void createNewTab(); + void closeTab(int index); +}; + + +class TTDEventsSidebarWidget : public SidebarWidget +{ + Q_OBJECT + +private: + TTDEventsWidget* m_eventsWidget; + BinaryViewRef m_data; + DbgRef m_controller; + size_t m_debuggerEventCallback; + +public: + TTDEventsSidebarWidget(BinaryViewRef data); + ~TTDEventsSidebarWidget(); + +signals: + void debuggerEvent(const DebuggerEvent& event); + +private slots: + void onDebuggerEvent(const DebuggerEvent& event); +}; + + +class TTDEventsWidgetType : public SidebarWidgetType +{ +public: + TTDEventsWidgetType(); + SidebarWidget* createWidget(ViewFrame* frame, BinaryViewRef data) override; + SidebarWidgetLocation defaultLocation() const override { return SidebarWidgetLocation::RightContent; } + SidebarContextSensitivity contextSensitivity() const override { return PerViewTypeSidebarContext; } + SidebarIconVisibility defaultIconVisibility() const override { return HideSidebarIconIfNoContent; } + SidebarContentClassifier* contentClassifier(ViewFrame*, BinaryViewRef) override; +}; \ No newline at end of file diff --git a/ui/ui.cpp b/ui/ui.cpp index 842b9697..16f284ec 100644 --- a/ui/ui.cpp +++ b/ui/ui.cpp @@ -44,6 +44,7 @@ limitations under the License. #include "debuggerinfowidget.h" #include "ttdmemorywidget.h" #include "ttdcallswidget.h" +#include "ttdeventswidget.h" #include "ttdanalysisdialog.h" #include "timestampnavigationdialog.h" #include "freeversion.h" @@ -1025,6 +1026,7 @@ void GlobalDebuggerUI::SetupMenu(UIContext* context) UIAction::registerAction("Copy Table"); UIAction::registerAction("Column Visibility..."); UIAction::registerAction("Reset Columns to Default"); + UIAction::registerAction("Refresh"); #ifdef WIN32 UIAction::registerAction("Record TTD Trace"); @@ -1685,6 +1687,7 @@ void GlobalDebuggerUI::InitializeUI() Sidebar::addSidebarWidgetType(new DebugInfoWidgetType()); Sidebar::addSidebarWidgetType(new TTDMemoryWidgetType()); Sidebar::addSidebarWidgetType(new TTDCallsWidgetType()); + Sidebar::addSidebarWidgetType(new TTDEventsWidgetType()); }