diff --git a/.gitignore b/.gitignore index ac3ac897..38b40696 100644 --- a/.gitignore +++ b/.gitignore @@ -69,4 +69,6 @@ api/python/__pycache__ # TTD files *.run *.idx -*.bndb \ No newline at end of file +*.bndb + +__pycache__/ diff --git a/api/debuggerapi.h b/api/debuggerapi.h index fc23d425..fb149bda 100644 --- a/api/debuggerapi.h +++ b/api/debuggerapi.h @@ -518,6 +518,31 @@ namespace BinaryNinjaDebuggerAPI { TTDCallEvent() : threadId(0), uniqueThreadId(0), functionAddress(0), returnAddress(0), returnValue(0), hasReturnValue(false) {} }; + struct TTDHeapEvent + { + std::string eventType; // Event type (always "Heap" for TTD.Heap objects) + std::string action; // Heap action: Alloc, ReAlloc, Free, Create, Protect, Lock, Unlock, Destroy + uint32_t threadId; // OS thread ID of thread that made the heap call + uint32_t uniqueThreadId; // Unique ID for the thread across the trace + uint64_t heap; // Handle for the Win32 heap + uint64_t address; // Address of the allocated object (if applicable) + uint64_t previousAddress; // Address before reallocation (for ReAlloc) + uint64_t size; // Size of allocated object (if applicable) + uint64_t baseAddress; // Base address of allocated object (if applicable) + uint64_t flags; // Heap API flags (meaning depends on API) + uint64_t result; // Result of heap API call (non-zero = success) + uint64_t reserveSize; // Amount of memory to reserve (for Create) + uint64_t commitSize; // Initial committed size (for Create) + uint64_t makeReadOnly; // Non-zero = make heap read-only (for Protect) + std::vector parameters; // Raw parameters from the heap call + TTDPosition timeStart; // Position when heap operation started + TTDPosition timeEnd; // Position when heap operation ended + + TTDHeapEvent() : threadId(0), uniqueThreadId(0), heap(0), address(0), previousAddress(0), + size(0), baseAddress(0), flags(0), result(0), reserveSize(0), + commitSize(0), makeReadOnly(0) {} + }; + // TTD Event Types - bitfield flags for filtering events enum TTDEventType { @@ -764,6 +789,7 @@ namespace BinaryNinjaDebuggerAPI { std::vector GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress = 0, uint64_t endReturnAddress = 0); std::vector GetTTDEvents(TTDEventType eventType); std::vector GetAllTTDEvents(); + std::vector GetTTDHeapObjects(); TTDPosition GetCurrentTTDPosition(); bool SetTTDPosition(const TTDPosition& position); diff --git a/api/debuggercontroller.cpp b/api/debuggercontroller.cpp index 644565bf..0c2c45e5 100644 --- a/api/debuggercontroller.cpp +++ b/api/debuggercontroller.cpp @@ -1072,11 +1072,11 @@ 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, + BNDebuggerTTDEvent* events = BNDebuggerGetTTDEvents(m_object, static_cast(eventType), &count); - + if (events && count > 0) { result.reserve(count); @@ -1086,7 +1086,7 @@ std::vector DebuggerController::GetTTDEvents(TTDEventType eventType) 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) { @@ -1098,7 +1098,7 @@ std::vector DebuggerController::GetTTDEvents(TTDEventType eventType) module.timestamp = events[i].module->timestamp; event.module = module; } - + // Copy optional thread details if (events[i].thread) { @@ -1115,7 +1115,7 @@ std::vector DebuggerController::GetTTDEvents(TTDEventType eventType) thread.activeTimeEnd.step = events[i].thread->activeTimeEnd.step; event.thread = thread; } - + // Copy optional exception details if (events[i].exception) { @@ -1129,12 +1129,12 @@ std::vector DebuggerController::GetTTDEvents(TTDEventType eventType) exception.position.step = events[i].exception->position.step; event.exception = exception; } - + result.push_back(event); } BNDebuggerFreeTTDEvents(events, count); } - + return result; } @@ -1142,10 +1142,10 @@ std::vector DebuggerController::GetTTDEvents(TTDEventType eventType) std::vector DebuggerController::GetAllTTDEvents() { std::vector result; - + size_t count = 0; BNDebuggerTTDEvent* events = BNDebuggerGetAllTTDEvents(m_object, &count); - + if (events && count > 0) { result.reserve(count); @@ -1155,7 +1155,7 @@ std::vector DebuggerController::GetAllTTDEvents() 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) { @@ -1167,7 +1167,7 @@ std::vector DebuggerController::GetAllTTDEvents() module.timestamp = events[i].module->timestamp; event.module = module; } - + // Copy optional thread details if (events[i].thread) { @@ -1184,7 +1184,7 @@ std::vector DebuggerController::GetAllTTDEvents() thread.activeTimeEnd.step = events[i].thread->activeTimeEnd.step; event.thread = thread; } - + // Copy optional exception details if (events[i].exception) { @@ -1198,12 +1198,70 @@ std::vector DebuggerController::GetAllTTDEvents() exception.position.step = events[i].exception->position.step; event.exception = exception; } - + result.push_back(event); } BNDebuggerFreeTTDEvents(events, count); } - + + return result; +} + + +std::vector DebuggerController::GetTTDHeapObjects() +{ + std::vector result; + + size_t count = 0; + BNDebuggerTTDHeapEvent* events = BNDebuggerGetTTDHeapObjects(m_object, &count); + + if (events && count > 0) + { + result.reserve(count); + for (size_t i = 0; i < count; i++) + { + TTDHeapEvent event; + event.eventType = events[i].eventType ? std::string(events[i].eventType) : ""; + event.action = events[i].action ? std::string(events[i].action) : ""; + event.threadId = events[i].threadId; + event.uniqueThreadId = events[i].uniqueThreadId; + event.heap = events[i].heap; + event.address = events[i].address; + event.previousAddress = events[i].previousAddress; + event.size = events[i].size; + event.baseAddress = events[i].baseAddress; + event.flags = events[i].flags; + event.result = events[i].result; + event.reserveSize = events[i].reserveSize; + event.commitSize = events[i].commitSize; + event.makeReadOnly = events[i].makeReadOnly; + event.timeStart.sequence = events[i].timeStart.sequence; + event.timeStart.step = events[i].timeStart.step; + event.timeEnd.sequence = events[i].timeEnd.sequence; + event.timeEnd.step = events[i].timeEnd.step; + + // Convert parameters array + if (events[i].parameters && events[i].parameterCount > 0) + { + event.parameters.reserve(events[i].parameterCount); + for (size_t j = 0; j < events[i].parameterCount; j++) + { + if (events[i].parameters[j]) + { + event.parameters.push_back(std::string(events[i].parameters[j])); + } + else + { + event.parameters.push_back(""); + } + } + } + + result.push_back(event); + } + BNDebuggerFreeTTDHeapEvents(events, count); + } + return result; } diff --git a/api/ffi.h b/api/ffi.h index 1f20dc86..3c20b88a 100644 --- a/api/ffi.h +++ b/api/ffi.h @@ -395,13 +395,35 @@ extern "C" { 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; + typedef struct BNDebuggerTTDHeapEvent + { + char* eventType; // Event type (always "Heap" for TTD.Heap objects) + char* action; // Heap action: Alloc, ReAlloc, Free, Create, Protect, Lock, Unlock, Destroy + uint32_t threadId; // OS thread ID of thread that made the heap call + uint32_t uniqueThreadId; // Unique ID for the thread across the trace + uint64_t heap; // Handle for the Win32 heap + uint64_t address; // Address of the allocated object (if applicable) + uint64_t previousAddress; // Address before reallocation (for ReAlloc) + uint64_t size; // Size of allocated object (if applicable) + uint64_t baseAddress; // Base address of allocated object (if applicable) + uint64_t flags; // Heap API flags (meaning depends on API) + uint64_t result; // Result of heap API call (non-zero = success) + uint64_t reserveSize; // Amount of memory to reserve (for Create) + uint64_t commitSize; // Initial committed size (for Create) + uint64_t makeReadOnly; // Non-zero = make heap read-only (for Protect) + char** parameters; // Array containing raw parameters from the heap call + size_t parameterCount; // Number of parameters + BNDebuggerTTDPosition timeStart; // Position when heap operation started + BNDebuggerTTDPosition timeEnd; // Position when heap operation ended + } BNDebuggerTTDHeapEvent; + // This should really be a union, but gcc complains... typedef struct BNDebuggerEventData @@ -623,11 +645,13 @@ extern "C" 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 BNDebuggerTTDHeapEvent* BNDebuggerGetTTDHeapObjects(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); + DEBUGGER_FFI_API void BNDebuggerFreeTTDHeapEvents(BNDebuggerTTDHeapEvent* 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 7058144b..0543bfa1 100644 --- a/api/python/debuggercontroller.py +++ b/api/python/debuggercontroller.py @@ -738,7 +738,7 @@ class TTDEventType: 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 @@ -784,7 +784,7 @@ def __repr__(self): 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 @@ -843,7 +843,7 @@ class TTDExceptionType: 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 @@ -853,7 +853,7 @@ class TTDException: position (TTDPosition): position where exception occurred """ - def __init__(self, type: int, program_counter: int, code: int, flags: int, + 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 @@ -895,7 +895,7 @@ def __repr__(self): 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 @@ -937,7 +937,7 @@ def __setattr__(self, name, value): def __repr__(self): type_names = { TTDEventType.ThreadCreated: "ThreadCreated", - TTDEventType.ThreadTerminated: "ThreadTerminated", + TTDEventType.ThreadTerminated: "ThreadTerminated", TTDEventType.ModuleLoaded: "ModuleLoaded", TTDEventType.ModuleUnloaded: "ModuleUnloaded", TTDEventType.Exception: "Exception" @@ -946,6 +946,93 @@ def __repr__(self): return f"" +class TTDHeapEvent: + """ + TTDHeapEvent represents a heap operation event in a TTD trace. It has the following fields: + + * ``event_type``: type of the event (always "Heap" for TTD.Heap objects) + * ``action``: heap action that occurred (Alloc, ReAlloc, Free, Create, Protect, Lock, Unlock, Destroy) + * ``thread_id``: OS thread ID that performed the heap operation + * ``unique_thread_id``: unique thread ID across the trace + * ``heap``: handle for the Win32 heap + * ``address``: address of the allocated object (if applicable) + * ``previous_address``: address before reallocation (for ReAlloc operations) + * ``size``: size of allocated object (if applicable) + * ``base_address``: base address of allocated object (if applicable) + * ``flags``: heap API flags (meaning depends on the specific API) + * ``result``: result of heap API call (non-zero means success) + * ``reserve_size``: amount of memory to reserve (for Create operations) + * ``commit_size``: initial committed size (for Create operations) + * ``make_read_only``: non-zero indicates request to make heap read-only + * ``parameters``: list of raw parameters from the heap call + * ``time_start``: TTD position when heap operation started + * ``time_end``: TTD position when heap operation ended + """ + + def __init__(self, event_type: str, action: str, thread_id: int, unique_thread_id: int, + heap: int, address: int, previous_address: int, size: int, base_address: int, + flags: int, result: int, reserve_size: int, commit_size: int, make_read_only: int, + parameters: List[str], time_start: TTDPosition, time_end: TTDPosition): + self.event_type = event_type + self.action = action + self.thread_id = thread_id + self.unique_thread_id = unique_thread_id + self.heap = heap + self.address = address + self.previous_address = previous_address + self.size = size + self.base_address = base_address + self.flags = flags + self.result = result + self.reserve_size = reserve_size + self.commit_size = commit_size + self.make_read_only = make_read_only + self.parameters = parameters + self.time_start = time_start + self.time_end = time_end + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return (self.event_type == other.event_type and + self.action == other.action and + self.thread_id == other.thread_id and + self.unique_thread_id == other.unique_thread_id and + self.heap == other.heap and + self.address == other.address and + self.previous_address == other.previous_address and + self.size == other.size and + self.base_address == other.base_address and + self.flags == other.flags and + self.result == other.result and + self.reserve_size == other.reserve_size and + self.commit_size == other.commit_size and + self.make_read_only == other.make_read_only and + self.parameters == other.parameters and + self.time_start == other.time_start and + self.time_end == other.time_end) + + def __ne__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return not (self == other) + + def __hash__(self): + return hash((self.event_type, self.action, self.thread_id, self.unique_thread_id, + self.heap, self.address, self.previous_address, self.size, self.base_address, + self.flags, self.result, self.reserve_size, self.commit_size, self.make_read_only, + tuple(self.parameters), self.time_start, self.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 DebuggerController: """ The ``DebuggerController`` object is the core of the debugger. Most debugger operations can be performed on it. @@ -2301,9 +2388,9 @@ def get_ttd_events(self, event_type: int) -> List[TTDEvent]: 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: @@ -2314,7 +2401,7 @@ def get_ttd_events(self, event_type: int) -> List[TTDEvent]: checksum=event.module.contents.checksum, timestamp=event.module.contents.timestamp ) - + # Convert optional thread details thread = None if event.thread: @@ -2322,7 +2409,7 @@ def get_ttd_events(self, event_type: int) -> List[TTDEvent]: 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, @@ -2331,12 +2418,12 @@ def get_ttd_events(self, event_type: int) -> List[TTDEvent]: 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, @@ -2380,9 +2467,9 @@ def get_all_ttd_events(self) -> List[TTDEvent]: 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: @@ -2393,7 +2480,7 @@ def get_all_ttd_events(self) -> List[TTDEvent]: checksum=event.module.contents.checksum, timestamp=event.module.contents.timestamp ) - + # Convert optional thread details thread = None if event.thread: @@ -2401,7 +2488,7 @@ def get_all_ttd_events(self) -> List[TTDEvent]: 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, @@ -2410,12 +2497,12 @@ def get_all_ttd_events(self) -> List[TTDEvent]: 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, @@ -2437,6 +2524,58 @@ def get_all_ttd_events(self) -> List[TTDEvent]: dbgcore.BNDebuggerFreeTTDEvents(events, count.value) return result + def get_ttd_heap_objects(self) -> List[TTDHeapEvent]: + """ + Get TTD heap operation events. + + 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 TTDHeapEvent objects representing heap operations + :raises: May raise an exception if TTD is not available + """ + count = ctypes.c_ulonglong() + events = dbgcore.BNDebuggerGetTTDHeapObjects(self.handle, count) + + if not events: + return [] + + result = [] + for i in range(count.value): + event = events[i] + time_start = TTDPosition(event.timeStart.sequence, event.timeStart.step) + time_end = TTDPosition(event.timeEnd.sequence, event.timeEnd.step) + + # Convert parameters array to Python list + parameters = [] + if event.parameters and event.parameterCount > 0: + for j in range(event.parameterCount): + parameters.append(event.parameters[j]) + + heap_event = TTDHeapEvent( + event_type=event.eventType if event.eventType else "", + action=event.action if event.action else "", + thread_id=event.threadId, + unique_thread_id=event.uniqueThreadId, + heap=event.heap, + address=event.address, + previous_address=event.previousAddress, + size=event.size, + base_address=event.baseAddress, + flags=event.flags, + result=event.result, + reserve_size=event.reserveSize, + commit_size=event.commitSize, + make_read_only=event.makeReadOnly, + parameters=parameters, + time_start=time_start, + time_end=time_end + ) + result.append(heap_event) + + dbgcore.BNDebuggerFreeTTDHeapEvents(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 317855cf..173df158 100644 --- a/core/adapters/dbgengttdadapter.cpp +++ b/core/adapters/dbgengttdadapter.cpp @@ -174,7 +174,7 @@ bool DbgEngTTDAdapter::Start() void DbgEngTTDAdapter::Reset() { m_aboutToBeKilled = false; - + // Clear TTD events cache when resetting ClearTTDEventsCache(); @@ -1367,7 +1367,7 @@ 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) { @@ -1377,7 +1377,7 @@ std::vector DbgEngTTDAdapter::GetTTDEvents(TTDEventType eventType) return events; } } - + // Filter cached events by type using bitfield operations for (const auto& event : m_cachedEvents) { @@ -1386,7 +1386,7 @@ std::vector DbgEngTTDAdapter::GetTTDEvents(TTDEventType eventType) events.push_back(event); } } - + LogInfo("Successfully retrieved %zu TTD events of type %d from cache", events.size(), eventType); return events; } @@ -1403,7 +1403,7 @@ std::vector DbgEngTTDAdapter::GetAllTTDEvents() return {}; } } - + LogDebug("Successfully retrieved %zu total TTD events from cache", m_cachedEvents.size()); return m_cachedEvents; } @@ -1416,14 +1416,14 @@ bool DbgEngTTDAdapter::QueryAllTTDEvents() 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)) { @@ -1460,7 +1460,7 @@ bool DbgEngTTDAdapter::ParseTTDEventObjects(const std::string& expression, std:: // Execute the expression to get event objects ComPtr resultObject; ComPtr metadataKeyStore; - + HRESULT hr = m_hostEvaluator->EvaluateExtendedExpression( hostContext.Get(), wExpression.c_str(), @@ -1468,19 +1468,19 @@ bool DbgEngTTDAdapter::ParseTTDEventObjects(const std::string& expression, std:: &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); @@ -1489,7 +1489,7 @@ bool DbgEngTTDAdapter::ParseTTDEventObjects(const std::string& expression, std:: LogError("TTD events result is not iterable: 0x%x", hr); return false; } - + // Get iterator ComPtr iterator; hr = iterableConcept->GetIterator(resultObject.Get(), &iterator); @@ -1498,18 +1498,18 @@ bool DbgEngTTDAdapter::ParseTTDEventObjects(const std::string& expression, std:: 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) @@ -1517,10 +1517,10 @@ bool DbgEngTTDAdapter::ParseTTDEventObjects(const std::string& expression, std:: 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))) { @@ -1530,7 +1530,7 @@ bool DbgEngTTDAdapter::ParseTTDEventObjects(const std::string& expression, std:: { _bstr_t bstr(vtType.bstrVal); std::string typeStr = std::string(bstr); - + if (typeStr == "ThreadCreated") eventType = TTDEventThreadCreated; else if (typeStr == "ThreadTerminated") @@ -1544,9 +1544,9 @@ bool DbgEngTTDAdapter::ParseTTDEventObjects(const std::string& expression, std:: } VariantClear(&vtType); } - + TTDEvent event(eventType); - + // Parse Position ComPtr positionObj; if (SUCCEEDED(eventObject->GetKeyValue(L"Position", &positionObj, nullptr))) @@ -1563,7 +1563,7 @@ bool DbgEngTTDAdapter::ParseTTDEventObjects(const std::string& expression, std:: } VariantClear(&vtSequence); } - + // Parse Steps ComPtr stepsObj; if (SUCCEEDED(positionObj->GetKeyValue(L"Steps", &stepsObj, nullptr))) @@ -1577,7 +1577,7 @@ bool DbgEngTTDAdapter::ParseTTDEventObjects(const std::string& expression, std:: VariantClear(&vtSteps); } } - + // Parse event-specific details based on type switch (eventType) { @@ -1593,15 +1593,15 @@ bool DbgEngTTDAdapter::ParseTTDEventObjects(const std::string& expression, std:: 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); @@ -1626,7 +1626,7 @@ void DbgEngTTDAdapter::ParseThreadDetails(IModelObject* eventObject, TTDEvent& e if (SUCCEEDED(eventObject->GetKeyValue(L"Thread", &threadObj, nullptr))) { TTDThread thread; - + // Parse UniqueId ComPtr uniqueIdObj; if (SUCCEEDED(threadObj->GetKeyValue(L"UniqueId", &uniqueIdObj, nullptr))) @@ -1639,7 +1639,7 @@ void DbgEngTTDAdapter::ParseThreadDetails(IModelObject* eventObject, TTDEvent& e } VariantClear(&vtUniqueId); } - + // Parse Id (TID) ComPtr idObj; if (SUCCEEDED(threadObj->GetKeyValue(L"Id", &idObj, nullptr))) @@ -1652,7 +1652,7 @@ void DbgEngTTDAdapter::ParseThreadDetails(IModelObject* eventObject, TTDEvent& e } VariantClear(&vtId); } - + // Parse LifeTime ComPtr lifetimeObj; if (SUCCEEDED(threadObj->GetKeyValue(L"LifeTime", &lifetimeObj, nullptr))) @@ -1663,7 +1663,7 @@ void DbgEngTTDAdapter::ParseThreadDetails(IModelObject* eventObject, TTDEvent& e { ParseTTDPosition(minPosObj.Get(), thread.lifetimeStart); } - + // Parse LifeTime.MaxPosition ComPtr maxPosObj; if (SUCCEEDED(lifetimeObj->GetKeyValue(L"MaxPosition", &maxPosObj, nullptr))) @@ -1671,8 +1671,8 @@ void DbgEngTTDAdapter::ParseThreadDetails(IModelObject* eventObject, TTDEvent& e ParseTTDPosition(maxPosObj.Get(), thread.lifetimeEnd); } } - - // Parse ActiveTime + + // Parse ActiveTime ComPtr activeTimeObj; if (SUCCEEDED(threadObj->GetKeyValue(L"ActiveTime", &activeTimeObj, nullptr))) { @@ -1682,7 +1682,7 @@ void DbgEngTTDAdapter::ParseThreadDetails(IModelObject* eventObject, TTDEvent& e { ParseTTDPosition(minActiveObj.Get(), thread.activeTimeStart); } - + // Parse ActiveTime.MaxPosition ComPtr maxActiveObj; if (SUCCEEDED(activeTimeObj->GetKeyValue(L"MaxPosition", &maxActiveObj, nullptr))) @@ -1690,7 +1690,7 @@ void DbgEngTTDAdapter::ParseThreadDetails(IModelObject* eventObject, TTDEvent& e ParseTTDPosition(maxActiveObj.Get(), thread.activeTimeEnd); } } - + event.thread = thread; } } @@ -1702,7 +1702,7 @@ void DbgEngTTDAdapter::ParseModuleDetails(IModelObject* eventObject, TTDEvent& e if (SUCCEEDED(eventObject->GetKeyValue(L"Module", &moduleObj, nullptr))) { TTDModule module; - + // Parse Name ComPtr nameObj; if (SUCCEEDED(moduleObj->GetKeyValue(L"Name", &nameObj, nullptr))) @@ -1716,7 +1716,7 @@ void DbgEngTTDAdapter::ParseModuleDetails(IModelObject* eventObject, TTDEvent& e } VariantClear(&vtName); } - + // Parse Address ComPtr addressObj; if (SUCCEEDED(moduleObj->GetKeyValue(L"Address", &addressObj, nullptr))) @@ -1729,7 +1729,7 @@ void DbgEngTTDAdapter::ParseModuleDetails(IModelObject* eventObject, TTDEvent& e } VariantClear(&vtAddress); } - + // Parse Size ComPtr sizeObj; if (SUCCEEDED(moduleObj->GetKeyValue(L"Size", &sizeObj, nullptr))) @@ -1742,7 +1742,7 @@ void DbgEngTTDAdapter::ParseModuleDetails(IModelObject* eventObject, TTDEvent& e } VariantClear(&vtSize); } - + // Parse Checksum ComPtr checksumObj; if (SUCCEEDED(moduleObj->GetKeyValue(L"Checksum", &checksumObj, nullptr))) @@ -1755,7 +1755,7 @@ void DbgEngTTDAdapter::ParseModuleDetails(IModelObject* eventObject, TTDEvent& e } VariantClear(&vtChecksum); } - + // Parse Timestamp ComPtr timestampObj; if (SUCCEEDED(moduleObj->GetKeyValue(L"Timestamp", ×tampObj, nullptr))) @@ -1768,7 +1768,7 @@ void DbgEngTTDAdapter::ParseModuleDetails(IModelObject* eventObject, TTDEvent& e } VariantClear(&vtTimestamp); } - + event.module = module; } } @@ -1780,7 +1780,7 @@ void DbgEngTTDAdapter::ParseExceptionDetails(IModelObject* eventObject, TTDEvent if (SUCCEEDED(eventObject->GetKeyValue(L"Exception", &exceptionObj, nullptr))) { TTDException exception; - + // Parse Type ComPtr typeObj; if (SUCCEEDED(exceptionObj->GetKeyValue(L"Type", &typeObj, nullptr))) @@ -1795,7 +1795,7 @@ void DbgEngTTDAdapter::ParseExceptionDetails(IModelObject* eventObject, TTDEvent } VariantClear(&vtType); } - + // Parse ProgramCounter ComPtr pcObj; if (SUCCEEDED(exceptionObj->GetKeyValue(L"ProgramCounter", &pcObj, nullptr))) @@ -1808,7 +1808,7 @@ void DbgEngTTDAdapter::ParseExceptionDetails(IModelObject* eventObject, TTDEvent } VariantClear(&vtPC); } - + // Parse Code ComPtr codeObj; if (SUCCEEDED(exceptionObj->GetKeyValue(L"Code", &codeObj, nullptr))) @@ -1821,7 +1821,7 @@ void DbgEngTTDAdapter::ParseExceptionDetails(IModelObject* eventObject, TTDEvent } VariantClear(&vtCode); } - + // Parse Flags ComPtr flagsObj; if (SUCCEEDED(exceptionObj->GetKeyValue(L"Flags", &flagsObj, nullptr))) @@ -1834,7 +1834,7 @@ void DbgEngTTDAdapter::ParseExceptionDetails(IModelObject* eventObject, TTDEvent } VariantClear(&vtFlags); } - + // Parse RecordAddress ComPtr recordAddrObj; if (SUCCEEDED(exceptionObj->GetKeyValue(L"RecordAddress", &recordAddrObj, nullptr))) @@ -1847,14 +1847,14 @@ void DbgEngTTDAdapter::ParseExceptionDetails(IModelObject* eventObject, TTDEvent } VariantClear(&vtRecordAddr); } - + // Parse Position ComPtr positionObj; if (SUCCEEDED(exceptionObj->GetKeyValue(L"Position", &positionObj, nullptr))) { ParseTTDPosition(positionObj.Get(), exception.position); } - + event.exception = exception; } } @@ -1864,7 +1864,7 @@ void DbgEngTTDAdapter::ParseTTDPosition(IModelObject* positionObj, TTDPosition& { if (!positionObj) return; - + // Parse Sequence ComPtr sequenceObj; if (SUCCEEDED(positionObj->GetKeyValue(L"Sequence", &sequenceObj, nullptr))) @@ -1877,7 +1877,7 @@ void DbgEngTTDAdapter::ParseTTDPosition(IModelObject* positionObj, TTDPosition& } VariantClear(&vtSequence); } - + // Parse Steps ComPtr stepsObj; if (SUCCEEDED(positionObj->GetKeyValue(L"Steps", &stepsObj, nullptr))) @@ -1900,6 +1900,494 @@ void DbgEngTTDAdapter::ClearTTDEventsCache() } +std::vector DbgEngTTDAdapter::GetTTDHeapObjects() +{ + std::vector events; + + if (!QueryTTDHeapObjects(events)) + { + LogError("Failed to query TTD heap objects"); + } + + return events; +} + + +bool DbgEngTTDAdapter::QueryTTDHeapObjects(std::vector& events) +{ + try + { + LogInfo("Querying TTD heap objects using @$cursession.TTD.Data.Heap()"); + + // Use the data model to query heap objects + std::string expression = "@$cursession.TTD.Data.Heap()"; + + // Execute the query and parse results + return ParseTTDHeapObjects(expression, events); + } + catch (const std::exception& e) + { + LogError("Exception in QueryTTDHeapObjects: %s", e.what()); + return false; + } +} + + +bool DbgEngTTDAdapter::ParseTTDHeapObjects(const std::string& expression, std::vector& events) +{ + try + { + LogInfo("Parsing TTD heap 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))) + { + LogError("Failed to get debug host context"); + return false; + } + + // Evaluate the data model expression + ComPtr resultObject; + ComPtr resultMetadata; + if (FAILED(m_hostEvaluator->EvaluateExtendedExpression( + hostContext.Get(), + wExpression.c_str(), + nullptr, + &resultObject, + &resultMetadata))) + { + LogError("Failed to evaluate TTD heap expression: %s", expression.c_str()); + return false; + } + + // Check if result is iterable + ComPtr iterableConcept; + if (FAILED(resultObject->GetConcept(__uuidof(IIterableConcept), + reinterpret_cast(iterableConcept.GetAddressOf()), nullptr))) + { + LogError("TTD heap result is not iterable"); + return false; + } + + // Get iterator + ComPtr iterator; + if (FAILED(iterableConcept->GetIterator(resultObject.Get(), &iterator))) + { + LogError("Failed to get iterator for TTD heap objects"); + return false; + } + + // Iterate through heap objects + while (true) + { + ComPtr current; + ComPtr currentMetadata; + + HRESULT hr = iterator->GetNext(¤t, 0, nullptr, ¤tMetadata); + if (hr == E_BOUNDS) + { + // End of iteration + break; + } + if (FAILED(hr)) + { + LogError("Failed to get next heap object from iterator"); + break; + } + + // Parse individual heap object + TTDHeapEvent heapEvent; + heapEvent.eventType = "Heap"; + + // Parse Action field + ComPtr actionObj; + if (SUCCEEDED(current->GetKeyValue(L"Action", &actionObj, nullptr))) + { + VARIANT actionVar; + VariantInit(&actionVar); + if (SUCCEEDED(actionObj->GetIntrinsicValue(&actionVar))) + { + if (actionVar.vt == VT_BSTR && actionVar.bstrVal) + { + std::wstring wAction(actionVar.bstrVal); + heapEvent.action = std::string(wAction.begin(), wAction.end()); + } + VariantClear(&actionVar); + } + } + + // Parse Heap field + ComPtr heapObj; + if (SUCCEEDED(current->GetKeyValue(L"Heap", &heapObj, nullptr))) + { + VARIANT heapVar; + VariantInit(&heapVar); + if (SUCCEEDED(heapObj->GetIntrinsicValue(&heapVar))) + { + if (heapVar.vt == VT_UI8) + { + heapEvent.heap = heapVar.ullVal; + } + else if (heapVar.vt == VT_UI4) + { + heapEvent.heap = heapVar.ulVal; + } + VariantClear(&heapVar); + } + } + + // Parse Address field (conditional) + ComPtr addressObj; + if (SUCCEEDED(current->GetKeyValue(L"Address", &addressObj, nullptr))) + { + VARIANT addressVar; + VariantInit(&addressVar); + if (SUCCEEDED(addressObj->GetIntrinsicValue(&addressVar))) + { + if (addressVar.vt == VT_UI8) + { + heapEvent.address = addressVar.ullVal; + } + else if (addressVar.vt == VT_UI4) + { + heapEvent.address = addressVar.ulVal; + } + VariantClear(&addressVar); + } + } + + // Parse PreviousAddress field (conditional) + ComPtr prevAddressObj; + if (SUCCEEDED(current->GetKeyValue(L"PreviousAddress", &prevAddressObj, nullptr))) + { + VARIANT prevAddressVar; + VariantInit(&prevAddressVar); + if (SUCCEEDED(prevAddressObj->GetIntrinsicValue(&prevAddressVar))) + { + if (prevAddressVar.vt == VT_UI8) + { + heapEvent.previousAddress = prevAddressVar.ullVal; + } + else if (prevAddressVar.vt == VT_UI4) + { + heapEvent.previousAddress = prevAddressVar.ulVal; + } + VariantClear(&prevAddressVar); + } + } + + // Parse Size field (conditional) + ComPtr sizeObj; + if (SUCCEEDED(current->GetKeyValue(L"Size", &sizeObj, nullptr))) + { + VARIANT sizeVar; + VariantInit(&sizeVar); + if (SUCCEEDED(sizeObj->GetIntrinsicValue(&sizeVar))) + { + if (sizeVar.vt == VT_UI8) + { + heapEvent.size = sizeVar.ullVal; + } + else if (sizeVar.vt == VT_UI4) + { + heapEvent.size = sizeVar.ulVal; + } + VariantClear(&sizeVar); + } + } + + // Parse BaseAddress field (conditional) + ComPtr baseAddressObj; + if (SUCCEEDED(current->GetKeyValue(L"BaseAddress", &baseAddressObj, nullptr))) + { + VARIANT baseAddressVar; + VariantInit(&baseAddressVar); + if (SUCCEEDED(baseAddressObj->GetIntrinsicValue(&baseAddressVar))) + { + if (baseAddressVar.vt == VT_UI8) + { + heapEvent.baseAddress = baseAddressVar.ullVal; + } + else if (baseAddressVar.vt == VT_UI4) + { + heapEvent.baseAddress = baseAddressVar.ulVal; + } + VariantClear(&baseAddressVar); + } + } + + // Parse Flags field (conditional) + ComPtr flagsObj; + if (SUCCEEDED(current->GetKeyValue(L"Flags", &flagsObj, nullptr))) + { + VARIANT flagsVar; + VariantInit(&flagsVar); + if (SUCCEEDED(flagsObj->GetIntrinsicValue(&flagsVar))) + { + if (flagsVar.vt == VT_UI8) + { + heapEvent.flags = flagsVar.ullVal; + } + else if (flagsVar.vt == VT_UI4) + { + heapEvent.flags = flagsVar.ulVal; + } + VariantClear(&flagsVar); + } + } + + // Parse Result field (conditional) + ComPtr resultObj; + if (SUCCEEDED(current->GetKeyValue(L"Result", &resultObj, nullptr))) + { + VARIANT resultVar; + VariantInit(&resultVar); + if (SUCCEEDED(resultObj->GetIntrinsicValue(&resultVar))) + { + if (resultVar.vt == VT_UI8) + { + heapEvent.result = resultVar.ullVal; + } + else if (resultVar.vt == VT_UI4) + { + heapEvent.result = resultVar.ulVal; + } + VariantClear(&resultVar); + } + } + + // Parse ReserveSize field (conditional) + ComPtr reserveSizeObj; + if (SUCCEEDED(current->GetKeyValue(L"ReserveSize", &reserveSizeObj, nullptr))) + { + VARIANT reserveSizeVar; + VariantInit(&reserveSizeVar); + if (SUCCEEDED(reserveSizeObj->GetIntrinsicValue(&reserveSizeVar))) + { + if (reserveSizeVar.vt == VT_UI8) + { + heapEvent.reserveSize = reserveSizeVar.ullVal; + } + else if (reserveSizeVar.vt == VT_UI4) + { + heapEvent.reserveSize = reserveSizeVar.ulVal; + } + VariantClear(&reserveSizeVar); + } + } + + // Parse CommitSize field (conditional) + ComPtr commitSizeObj; + if (SUCCEEDED(current->GetKeyValue(L"CommitSize", &commitSizeObj, nullptr))) + { + VARIANT commitSizeVar; + VariantInit(&commitSizeVar); + if (SUCCEEDED(commitSizeObj->GetIntrinsicValue(&commitSizeVar))) + { + if (commitSizeVar.vt == VT_UI8) + { + heapEvent.commitSize = commitSizeVar.ullVal; + } + else if (commitSizeVar.vt == VT_UI4) + { + heapEvent.commitSize = commitSizeVar.ulVal; + } + VariantClear(&commitSizeVar); + } + } + + // Parse MakeReadOnly field (conditional) + ComPtr makeReadOnlyObj; + if (SUCCEEDED(current->GetKeyValue(L"MakeReadOnly", &makeReadOnlyObj, nullptr))) + { + VARIANT makeReadOnlyVar; + VariantInit(&makeReadOnlyVar); + if (SUCCEEDED(makeReadOnlyObj->GetIntrinsicValue(&makeReadOnlyVar))) + { + if (makeReadOnlyVar.vt == VT_UI8) + { + heapEvent.makeReadOnly = makeReadOnlyVar.ullVal; + } + else if (makeReadOnlyVar.vt == VT_UI4) + { + heapEvent.makeReadOnly = makeReadOnlyVar.ulVal; + } + VariantClear(&makeReadOnlyVar); + } + } + + // Parse TimeStart field + ComPtr timeStartObj; + if (SUCCEEDED(current->GetKeyValue(L"TimeStart", &timeStartObj, nullptr))) + { + // Parse sequence + ComPtr sequenceObj; + if (SUCCEEDED(timeStartObj->GetKeyValue(L"Sequence", &sequenceObj, nullptr))) + { + VARIANT sequenceVar; + VariantInit(&sequenceVar); + if (SUCCEEDED(sequenceObj->GetIntrinsicValue(&sequenceVar))) + { + if (sequenceVar.vt == VT_UI8) + { + heapEvent.timeStart.sequence = sequenceVar.ullVal; + } + else if (sequenceVar.vt == VT_UI4) + { + heapEvent.timeStart.sequence = sequenceVar.ulVal; + } + VariantClear(&sequenceVar); + } + } + + // Parse step + ComPtr stepObj; + if (SUCCEEDED(timeStartObj->GetKeyValue(L"Steps", &stepObj, nullptr))) + { + VARIANT stepVar; + VariantInit(&stepVar); + if (SUCCEEDED(stepObj->GetIntrinsicValue(&stepVar))) + { + if (stepVar.vt == VT_UI8) + { + heapEvent.timeStart.step = stepVar.ullVal; + } + else if (stepVar.vt == VT_UI4) + { + heapEvent.timeStart.step = stepVar.ulVal; + } + VariantClear(&stepVar); + } + } + } + + // Parse TimeEnd field + ComPtr timeEndObj; + if (SUCCEEDED(current->GetKeyValue(L"TimeEnd", &timeEndObj, nullptr))) + { + // Parse sequence + ComPtr sequenceObj; + if (SUCCEEDED(timeEndObj->GetKeyValue(L"Sequence", &sequenceObj, nullptr))) + { + VARIANT sequenceVar; + VariantInit(&sequenceVar); + if (SUCCEEDED(sequenceObj->GetIntrinsicValue(&sequenceVar))) + { + if (sequenceVar.vt == VT_UI8) + { + heapEvent.timeEnd.sequence = sequenceVar.ullVal; + } + else if (sequenceVar.vt == VT_UI4) + { + heapEvent.timeEnd.sequence = sequenceVar.ulVal; + } + VariantClear(&sequenceVar); + } + } + + // Parse step + ComPtr stepObj; + if (SUCCEEDED(timeEndObj->GetKeyValue(L"Steps", &stepObj, nullptr))) + { + VARIANT stepVar; + VariantInit(&stepVar); + if (SUCCEEDED(stepObj->GetIntrinsicValue(&stepVar))) + { + if (stepVar.vt == VT_UI8) + { + heapEvent.timeEnd.step = stepVar.ullVal; + } + else if (stepVar.vt == VT_UI4) + { + heapEvent.timeEnd.step = stepVar.ulVal; + } + VariantClear(&stepVar); + } + } + } + + // Parse Parameters field (raw parameters as strings) + ComPtr parametersObj; + if (SUCCEEDED(current->GetKeyValue(L"@\"Parameters\"", ¶metersObj, nullptr))) + { + // Check if parameters is iterable + ComPtr paramIterableConcept; + if (SUCCEEDED(parametersObj->GetConcept(__uuidof(IIterableConcept), + reinterpret_cast(paramIterableConcept.GetAddressOf()), nullptr))) + { + ComPtr paramIterator; + if (SUCCEEDED(paramIterableConcept->GetIterator(parametersObj.Get(), ¶mIterator))) + { + while (true) + { + ComPtr paramObj; + ComPtr paramMetadataKeyStore; + + HRESULT paramHr = paramIterator->GetNext(¶mObj, 0, nullptr, ¶mMetadataKeyStore); + if (paramHr == E_BOUNDS) + { + break; + } + if (FAILED(paramHr)) + { + break; + } + + VARIANT paramVar; + VariantInit(¶mVar); + if (SUCCEEDED(paramObj->GetIntrinsicValue(¶mVar))) + { + std::string paramStr; + if (paramVar.vt == VT_UI8) + { + paramStr = fmt::format("0x{:x}", paramVar.ullVal); + } + else if (paramVar.vt == VT_UI4) + { + paramStr = fmt::format("0x{:x}", paramVar.ulVal); + } + else if (paramVar.vt == VT_BSTR && paramVar.bstrVal) + { + std::wstring wParam(paramVar.bstrVal); + paramStr = std::string(wParam.begin(), wParam.end()); + } + + if (!paramStr.empty()) + { + heapEvent.parameters.push_back(paramStr); + } + VariantClear(¶mVar); + } + } + } + } + } + + events.push_back(heapEvent); + } + + if (events.empty()) + { + LogWarn("No TTD heap events found"); + } + else + { + LogInfo("Successfully parsed %zu TTD heap events from data model", events.size()); + } + return true; + } + catch (const std::exception& e) + { + LogError("Exception in ParseTTDHeapObjects: %s", e.what()); + return false; + } +} + + void BinaryNinjaDebugger::InitDbgEngTTDAdapterType() { static DbgEngTTDAdapterType localType; diff --git a/core/adapters/dbgengttdadapter.h b/core/adapters/dbgengttdadapter.h index 1d6acc95..d8971a4f 100644 --- a/core/adapters/dbgengttdadapter.h +++ b/core/adapters/dbgengttdadapter.h @@ -58,6 +58,9 @@ namespace BinaryNinjaDebugger { std::vector GetTTDEvents(TTDEventType eventType) override; std::vector GetAllTTDEvents() override; + // TTD Heap Analysis Methods + std::vector GetTTDHeapObjects() override; + void GenerateDefaultAdapterSettings(BinaryView* data); Ref GetAdapterSettings() override; @@ -76,14 +79,18 @@ namespace BinaryNinjaDebugger { 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(); + // Helper methods for TTD heap analysis + bool QueryTTDHeapObjects(std::vector& events); + bool ParseTTDHeapObjects(const std::string& expression, std::vector& events); + // 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 27a66372..32ebd5db 100644 --- a/core/debugadapter.cpp +++ b/core/debugadapter.cpp @@ -207,6 +207,13 @@ std::vector DebugAdapter::GetAllTTDEvents() } +std::vector DebugAdapter::GetTTDHeapObjects() +{ + // 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 04055c21..fefbd5fa 100644 --- a/core/debugadapter.h +++ b/core/debugadapter.h @@ -334,6 +334,7 @@ namespace BinaryNinjaDebugger { 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 std::vector GetTTDHeapObjects(); virtual TTDPosition GetCurrentTTDPosition(); virtual bool SetTTDPosition(const TTDPosition& position); diff --git a/core/debuggercommon.h b/core/debuggercommon.h index ed547c0e..9d76f127 100644 --- a/core/debuggercommon.h +++ b/core/debuggercommon.h @@ -154,6 +154,32 @@ namespace BinaryNinjaDebugger { TTDCallEvent() : threadId(0), uniqueThreadId(0), functionAddress(0), returnAddress(0), returnValue(0), hasReturnValue(false) {} }; + // TTD Heap Event - complete set of fields from Microsoft documentation for TTD.Heap + struct TTDHeapEvent + { + std::string eventType; // Event type (always "Heap" for TTD.Heap objects) + std::string action; // Heap action: Alloc, ReAlloc, Free, Create, Protect, Lock, Unlock, Destroy + uint32_t threadId; // OS thread ID of thread that made the heap call + uint32_t uniqueThreadId; // Unique ID for the thread across the trace + uint64_t heap; // Handle for the Win32 heap + uint64_t address; // Address of the allocated object (if applicable) + uint64_t previousAddress; // Address before reallocation (for ReAlloc) + uint64_t size; // Size of allocated object (if applicable) + uint64_t baseAddress; // Base address of allocated object (if applicable) + uint64_t flags; // Heap API flags (meaning depends on API) + uint64_t result; // Result of heap API call (non-zero = success) + uint64_t reserveSize; // Amount of memory to reserve (for Create) + uint64_t commitSize; // Initial committed size (for Create) + uint64_t makeReadOnly; // Non-zero = make heap read-only (for Protect) + std::vector parameters; // Raw parameters from the heap call + TTDPosition timeStart; // Position when heap operation started + TTDPosition timeEnd; // Position when heap operation ended + + TTDHeapEvent() : threadId(0), uniqueThreadId(0), heap(0), address(0), previousAddress(0), + size(0), baseAddress(0), flags(0), result(0), reserveSize(0), + commitSize(0), makeReadOnly(0) {} + }; + // TTD Event Types - bitfield flags for filtering events enum TTDEventType { diff --git a/core/debuggercontroller.cpp b/core/debuggercontroller.cpp index 6e74b663..9fb39033 100644 --- a/core/debuggercontroller.cpp +++ b/core/debuggercontroller.cpp @@ -2938,6 +2938,20 @@ std::vector DebuggerController::GetAllTTDEvents() } +std::vector DebuggerController::GetTTDHeapObjects() +{ + std::vector events; + + if (!IsTTD()) + { + LogError("Current adapter does not support TTD"); + return events; + } + + return m_adapter->GetTTDHeapObjects(); +} + + TTDPosition DebuggerController::GetCurrentTTDPosition() { TTDPosition position; diff --git a/core/debuggercontroller.h b/core/debuggercontroller.h index d2b31867..ff5046ef 100644 --- a/core/debuggercontroller.h +++ b/core/debuggercontroller.h @@ -367,6 +367,9 @@ namespace BinaryNinjaDebugger { std::vector GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress = 0, uint64_t endReturnAddress = 0); std::vector GetTTDEvents(TTDEventType eventType); std::vector GetAllTTDEvents(); + + std::vector GetTTDHeapObjects(); + TTDPosition GetCurrentTTDPosition(); bool SetTTDPosition(const TTDPosition& position); diff --git a/core/ffi.cpp b/core/ffi.cpp index 0b59285a..2bbb9a59 100644 --- a/core/ffi.cpp +++ b/core/ffi.cpp @@ -1468,6 +1468,101 @@ void BNDebuggerFreeTTDEvents(BNDebuggerTTDEvent* events, size_t count) } +BNDebuggerTTDHeapEvent* BNDebuggerGetTTDHeapObjects(BNDebuggerController* controller, size_t* count) +{ + if (!count) + return nullptr; + + *count = 0; + + auto events = controller->object->GetTTDHeapObjects(); + if (events.empty()) + return nullptr; + + *count = events.size(); + auto result = new BNDebuggerTTDHeapEvent[events.size()]; + + for (size_t i = 0; i < events.size(); ++i) + { + // Copy string fields + result[i].eventType = BNAllocString(events[i].eventType.c_str()); + result[i].action = BNAllocString(events[i].action.c_str()); + + // Copy primitive fields + result[i].threadId = events[i].threadId; + result[i].uniqueThreadId = events[i].uniqueThreadId; + result[i].heap = events[i].heap; + result[i].address = events[i].address; + result[i].previousAddress = events[i].previousAddress; + result[i].size = events[i].size; + result[i].baseAddress = events[i].baseAddress; + result[i].flags = events[i].flags; + result[i].result = events[i].result; + result[i].reserveSize = events[i].reserveSize; + result[i].commitSize = events[i].commitSize; + result[i].makeReadOnly = events[i].makeReadOnly; + + // Copy parameters array + result[i].parameterCount = events[i].parameters.size(); + if (result[i].parameterCount > 0) + { + result[i].parameters = new char*[result[i].parameterCount]; + for (size_t j = 0; j < result[i].parameterCount; ++j) + { + result[i].parameters[j] = BNAllocString(events[i].parameters[j].c_str()); + } + } + else + { + result[i].parameters = nullptr; + } + + // Copy TTD positions + result[i].timeStart.sequence = events[i].timeStart.sequence; + result[i].timeStart.step = events[i].timeStart.step; + result[i].timeEnd.sequence = events[i].timeEnd.sequence; + result[i].timeEnd.step = events[i].timeEnd.step; + } + + return result; +} + + +void BNDebuggerFreeTTDHeapEvents(BNDebuggerTTDHeapEvent* events, size_t count) +{ + if (!events || count == 0) + return; + + // Free all strings for each event + for (size_t i = 0; i < count; ++i) + { + if (events[i].eventType) + { + BNFreeString(events[i].eventType); + } + if (events[i].action) + { + BNFreeString(events[i].action); + } + + // Free parameter strings + if (events[i].parameters && events[i].parameterCount > 0) + { + for (size_t j = 0; j < events[i].parameterCount; ++j) + { + if (events[i].parameters[j]) + { + BNFreeString(events[i].parameters[j]); + } + } + delete[] events[i].parameters; + } + } + + delete[] events; +} + + void BNDebuggerPostDebuggerEvent(BNDebuggerController* controller, BNDebuggerEvent* event) { diff --git a/debuggerui.qrc b/debuggerui.qrc index c8e61334..846c6950 100644 --- a/debuggerui.qrc +++ b/debuggerui.qrc @@ -24,6 +24,7 @@ icons/stop.png icons/ttd-memory.png icons/ttd-calls.png + icons/ttd-heap.png icons/ttd-timestamp.png icons/ttd-events.png diff --git a/docs/ttd-python-api.md b/docs/ttd-python-api.md index a6952473..e85eb40c 100644 --- a/docs/ttd-python-api.md +++ b/docs/ttd-python-api.md @@ -188,6 +188,36 @@ class TTDExceptionType: Hardware = 1 # Hardware exceptions ``` +### TTDHeapEvent + +Represents a heap operation event in a TTD trace. + +```python +class TTDHeapEvent: + """ + TTDHeapEvent represents a heap operation event in a TTD trace. + + Attributes: + event_type (str): Type of the event (always "Heap" for TTD.Heap objects) + action (str): Heap action that occurred (Alloc, ReAlloc, Free, Create, Protect, Lock, Unlock, Destroy) + thread_id (int): OS thread ID that performed the heap operation + unique_thread_id (int): Unique thread ID across the trace + heap (int): Handle for the Win32 heap + address (int): Address of the allocated object (if applicable) + previous_address (int): Address before reallocation (for ReAlloc operations) + size (int): Size of allocated object (if applicable) + base_address (int): Base address of allocated object (if applicable) + flags (int): Heap API flags (meaning depends on the specific API) + result (int): Result of heap API call (non-zero means success) + reserve_size (int): Amount of memory to reserve (for Create operations) + commit_size (int): Initial committed size (for Create operations) + make_read_only (int): Non-zero indicates request to make heap read-only + parameters (List[str]): List of raw parameters from the heap call + time_start (TTDPosition): TTD position when heap operation started + time_end (TTDPosition): TTD position when heap operation ended + """ +``` + ## Constants and Access Types ### DebuggerTTDMemoryAccessType Enum @@ -311,6 +341,22 @@ def get_ttd_events(self, event_type: int) -> List[TTDEvent]: """ ``` +### get_ttd_heap_objects() + +```python +def get_ttd_heap_objects(self) -> List[TTDHeapEvent]: + """ + Get TTD heap operation events. + + This method queries all heap operations that occurred during the TTD trace. + It provides information about heap allocations, deallocations, reallocations, + and other heap management operations. + + Returns: + List of TTDHeapEvent objects representing heap operations + """ +``` + ### get_current_ttd_position() ```python @@ -387,6 +433,58 @@ for call in call_events: print(f" Return value: {call.return_value:#x}") ``` +### Heap Analysis + +```python +# Analyze heap operations during the trace +heap_events = dbg.get_ttd_heap_objects() + +print(f"Found {len(heap_events)} heap operations") + +# Group by action type +actions = {} +for event in heap_events: + if event.action not in actions: + actions[event.action] = [] + actions[event.action].append(event) + +# Display summary +for action, events in actions.items(): + print(f"{action}: {len(events)} operations") + +# Find large allocations +large_allocs = [e for e in heap_events + if e.action == "Alloc" and e.size > 1024*1024] # > 1MB + +print(f"Found {len(large_allocs)} large allocations (>1MB)") +for alloc in large_allocs: + print(f" {alloc.size} bytes at {alloc.address:#x} (heap {alloc.heap:#x})") + print(f" Time: {alloc.time_start}") + +# Analyze heap usage patterns +heap_stats = {} +for event in heap_events: + heap_handle = event.heap + if heap_handle not in heap_stats: + heap_stats[heap_handle] = {'allocs': 0, 'frees': 0, 'total_allocated': 0} + + if event.action == "Alloc": + heap_stats[heap_handle]['allocs'] += 1 + heap_stats[heap_handle]['total_allocated'] += event.size + elif event.action == "Free": + heap_stats[heap_handle]['frees'] += 1 + +print("\nHeap usage statistics:") +for heap_handle, stats in heap_stats.items(): + print(f"Heap {heap_handle:#x}:") + print(f" Allocations: {stats['allocs']}") + print(f" Frees: {stats['frees']}") + print(f" Total allocated: {stats['total_allocated']} bytes") + leaked = stats['allocs'] - stats['frees'] + if leaked > 0: + print(f" Potential leaks: {leaked} allocations") +``` + ### TTD Navigation ```python diff --git a/icons/ttd-heap.png b/icons/ttd-heap.png new file mode 100644 index 00000000..83d80059 Binary files /dev/null and b/icons/ttd-heap.png differ diff --git a/ui/ttdheapwidget.cpp b/ui/ttdheapwidget.cpp new file mode 100644 index 00000000..0481c4f8 --- /dev/null +++ b/ui/ttdheapwidget.cpp @@ -0,0 +1,700 @@ +/* +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 "ttdheapwidget.h" +#include "fmt/format.h" +#include +#include +#include +#include "debuggeruicommon.h" +#include "ui.h" + +using namespace BinaryNinjaDebuggerAPI; + +// TTDHeapQueryWidget implementation +TTDHeapQueryWidget::TTDHeapQueryWidget(QWidget* parent, BinaryViewRef data) + : QWidget(parent), m_data(data), m_hasPopulatedData(false) +{ + m_controller = DebuggerController::GetController(m_data); + if (!m_controller) + { + LogError("Failed to get debugger controller"); + return; + } + + // Initialize column names and visibility + m_columnNames << "Index" << "Event Type" << "Action" << "Time Start" << "Time End" + << "Heap" << "Address" << "Previous Address" << "Size" << "Base Address" + << "Flags" << "Result" << "Reserve Size" << "Commit Size" << "Make Read Only" + << "Thread ID" << "Unique Thread ID" << "Parameters"; + + // Default visibility - show most important columns by default + m_columnVisibility << true << true << true << true << true // Index, Event Type, Action, Time Start, Time End + << true << true << false << true << false // Heap, Address, Previous Address, Size, Base Address + << false << true << false << false << false // Flags, Result, Reserve Size, Commit Size, Make Read Only + << false << false << false; // Thread ID, Unique Thread ID, Parameters + + setupUI(); + setupUIActions(); + + // Register for debugger events + connect(this, &TTDHeapQueryWidget::debuggerEvent, this, &TTDHeapQueryWidget::onDebuggerEvent); + + m_debuggerEventCallback = m_controller->RegisterEventCallback( + [&](const DebuggerEvent& event) { + emit debuggerEvent(event); + }, + "TTD Heap Widget"); +} + +TTDHeapQueryWidget::~TTDHeapQueryWidget() +{ + if (m_controller) + m_controller->RemoveEventCallback(m_debuggerEventCallback); + + if (m_contextMenuManager) + delete m_contextMenuManager; +} + +void TTDHeapQueryWidget::setupUI() +{ + auto* layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + + // Controls section + auto* controlsWidget = new QWidget(); + auto* controlsLayout = new QHBoxLayout(controlsWidget); + controlsLayout->setContentsMargins(8, 8, 8, 8); + + m_queryButton = new QPushButton("Query Heap Objects"); + m_clearButton = new QPushButton("Clear Results"); + + controlsLayout->addWidget(m_queryButton); + controlsLayout->addWidget(m_clearButton); + controlsLayout->addStretch(); + + // Status label + m_statusLabel = new QLabel("Ready to query TTD heap objects"); + m_statusLabel->setStyleSheet("QLabel { color: #666; font-style: italic; }"); + controlsLayout->addWidget(m_statusLabel); + + layout->addWidget(controlsWidget); + + // Results table + m_resultsTable = new QTableWidget(); + setupTable(); + layout->addWidget(m_resultsTable); + + // Connect signals + connect(m_queryButton, &QPushButton::clicked, this, &TTDHeapQueryWidget::performQuery); + connect(m_clearButton, &QPushButton::clicked, this, &TTDHeapQueryWidget::clearResults); + connect(m_resultsTable, &QTableWidget::cellDoubleClicked, this, &TTDHeapQueryWidget::onCellDoubleClicked); +} + +void TTDHeapQueryWidget::setupTable() +{ + m_resultsTable->setColumnCount(m_columnNames.size()); + m_resultsTable->setHorizontalHeaderLabels(m_columnNames); + m_resultsTable->horizontalHeader()->setStretchLastSection(true); + m_resultsTable->setAlternatingRowColors(true); + m_resultsTable->setSelectionBehavior(QAbstractItemView::SelectRows); + m_resultsTable->setSortingEnabled(true); + m_resultsTable->setContextMenuPolicy(Qt::CustomContextMenu); + m_resultsTable->verticalHeader()->setVisible(false); // Hide built-in index column + m_resultsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); // Make cells non-editable + + updateColumnVisibility(); + + connect(m_resultsTable, &QTableWidget::customContextMenuRequested, this, &TTDHeapQueryWidget::showContextMenu); +} + +void TTDHeapQueryWidget::updateStatus(const QString& message) +{ + m_statusLabel->setText(message); + QApplication::processEvents(); +} + +void TTDHeapQueryWidget::performQuery() +{ + if (!m_controller) + { + updateStatus("No debugger controller available"); + return; + } + + updateStatus("Querying TTD heap objects..."); + m_queryButton->setEnabled(false); + QApplication::processEvents(); + + try + { + // Execute the TTD heap query + auto events = m_controller->GetTTDHeapObjects(); + + // Populate the results table + m_resultsTable->setRowCount(events.size()); + + for (size_t i = 0; i < events.size(); ++i) + { + const auto& event = events[i]; + + // Index + m_resultsTable->setItem(i, IndexColumn, new NumericalTableWidgetItem(QString("0x%1").arg(i, 0, 16), i)); + + // Event Type + m_resultsTable->setItem(i, EventTypeColumn, new QTableWidgetItem(QString::fromStdString(event.eventType))); + + // Action + m_resultsTable->setItem(i, ActionColumn, new QTableWidgetItem(QString::fromStdString(event.action))); + + // Time Start + QString timeStartStr = QString("%1:%2") + .arg(event.timeStart.sequence, 0, 16) + .arg(event.timeStart.step, 0, 16); + m_resultsTable->setItem(i, TimeStartColumn, new QTableWidgetItem(timeStartStr)); + + // Time End + QString timeEndStr = QString("%1:%2") + .arg(event.timeEnd.sequence, 0, 16) + .arg(event.timeEnd.step, 0, 16); + m_resultsTable->setItem(i, TimeEndColumn, new QTableWidgetItem(timeEndStr)); + + // Heap + m_resultsTable->setItem(i, HeapColumn, new NumericalTableWidgetItem(QString("0x%1").arg(event.heap, 0, 16), event.heap)); + + // Address + if (event.address != 0) + m_resultsTable->setItem(i, AddressColumn, new NumericalTableWidgetItem(QString("0x%1").arg(event.address, 0, 16), event.address)); + else + m_resultsTable->setItem(i, AddressColumn, new QTableWidgetItem("")); + + // Previous Address + if (event.previousAddress != 0) + m_resultsTable->setItem(i, PreviousAddressColumn, new NumericalTableWidgetItem(QString("0x%1").arg(event.previousAddress, 0, 16), event.previousAddress)); + else + m_resultsTable->setItem(i, PreviousAddressColumn, new QTableWidgetItem("")); + + // Size + if (event.size != 0) + m_resultsTable->setItem(i, SizeColumn, new NumericalTableWidgetItem(QString("0x%1").arg(event.size, 0, 16), event.size)); + else + m_resultsTable->setItem(i, SizeColumn, new QTableWidgetItem("")); + + // Base Address + if (event.baseAddress != 0) + m_resultsTable->setItem(i, BaseAddressColumn, new NumericalTableWidgetItem(QString("0x%1").arg(event.baseAddress, 0, 16), event.baseAddress)); + else + m_resultsTable->setItem(i, BaseAddressColumn, new QTableWidgetItem("")); + + // Flags + if (event.flags != 0) + m_resultsTable->setItem(i, FlagsColumn, new NumericalTableWidgetItem(QString("0x%1").arg(event.flags, 0, 16), event.flags)); + else + m_resultsTable->setItem(i, FlagsColumn, new QTableWidgetItem("")); + + // Result + m_resultsTable->setItem(i, ResultColumn, new NumericalTableWidgetItem(QString("0x%1").arg(event.result, 0, 16), event.result)); + + // Reserve Size + if (event.reserveSize != 0) + m_resultsTable->setItem(i, ReserveSizeColumn, new NumericalTableWidgetItem(QString("0x%1").arg(event.reserveSize, 0, 16), event.reserveSize)); + else + m_resultsTable->setItem(i, ReserveSizeColumn, new QTableWidgetItem("")); + + // Commit Size + if (event.commitSize != 0) + m_resultsTable->setItem(i, CommitSizeColumn, new NumericalTableWidgetItem(QString("0x%1").arg(event.commitSize, 0, 16), event.commitSize)); + else + m_resultsTable->setItem(i, CommitSizeColumn, new QTableWidgetItem("")); + + // Make Read Only + if (event.makeReadOnly != 0) + m_resultsTable->setItem(i, MakeReadOnlyColumn, new NumericalTableWidgetItem(QString("0x%1").arg(event.makeReadOnly, 0, 16), event.makeReadOnly)); + else + m_resultsTable->setItem(i, MakeReadOnlyColumn, new QTableWidgetItem("")); + + // Thread ID + m_resultsTable->setItem(i, ThreadIdColumn, new NumericalTableWidgetItem(QString::number(event.threadId), event.threadId)); + + // Unique Thread ID + m_resultsTable->setItem(i, UniqueThreadIdColumn, new NumericalTableWidgetItem(QString::number(event.uniqueThreadId), event.uniqueThreadId)); + + // Parameters + QStringList paramStrings; + for (const auto& param : event.parameters) + { + paramStrings << QString::fromStdString(param); + } + m_resultsTable->setItem(i, ParametersColumn, new QTableWidgetItem(paramStrings.join(", "))); + } + + // Resize columns to fit contents after populating data + m_resultsTable->resizeColumnsToContents(); + + // Mark that we have populated data + m_hasPopulatedData = true; + + updateStatus(QString("Found %1 heap objects").arg(events.size())); + } + catch (const std::exception& e) + { + updateStatus(QString("Error querying heap objects: %1").arg(e.what())); + LogError("Exception in TTD heap query: %s", e.what()); + } + + m_queryButton->setEnabled(true); +} + +void TTDHeapQueryWidget::clearResults() +{ + m_resultsTable->setRowCount(0); + m_hasPopulatedData = false; + updateStatus("Results cleared"); +} + +void TTDHeapQueryWidget::onCellDoubleClicked(int row, int column) +{ + if (!m_controller) + return; + + // Handle time travel with address navigation for TimeStart + if (column == TimeStartColumn) + { + auto* item = m_resultsTable->item(row, TimeStartColumn); + if (!item) + return; + + QString timeStr = item->text(); + QStringList parts = timeStr.split(':'); + if (parts.size() != 2) + return; + + 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)); + + // After time traveling, navigate to the heap address if available + auto* addressItem = m_resultsTable->item(row, AddressColumn); + if (addressItem && m_data) + { + QString addressStr = addressItem->text(); + if (addressStr.startsWith("0x", Qt::CaseInsensitive)) + { + bool ok; + uint64_t address = addressStr.mid(2).toULongLong(&ok, 16); + if (ok && address != 0) + { + ViewFrame* frame = ViewFrame::viewFrameForWidget(this); + if (frame) + { + frame->navigate(m_data, address); + } + } + } + } + } + else + { + updateStatus("Failed to navigate to position"); + } + } + } + // Handle time travel with address navigation for TimeEnd + else if (column == TimeEndColumn) + { + auto* item = m_resultsTable->item(row, TimeEndColumn); + if (!item) + return; + + QString timeStr = item->text(); + QStringList parts = timeStr.split(':'); + if (parts.size() != 2) + return; + + 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)); + + // After time traveling, navigate to the heap address if available + auto* addressItem = m_resultsTable->item(row, AddressColumn); + if (addressItem && m_data) + { + QString addressStr = addressItem->text(); + if (addressStr.startsWith("0x", Qt::CaseInsensitive)) + { + bool ok; + uint64_t address = addressStr.mid(2).toULongLong(&ok, 16); + if (ok && address != 0) + { + ViewFrame* frame = ViewFrame::viewFrameForWidget(this); + if (frame) + { + frame->navigate(m_data, address); + } + } + } + } + } + else + { + updateStatus("Failed to navigate to position"); + } + } + } + // Navigate to address when Address, PreviousAddress, or BaseAddress columns are double-clicked + else if (column == AddressColumn || column == PreviousAddressColumn || column == BaseAddressColumn) + { + auto* item = m_resultsTable->item(row, column); + if (!item) + return; + + QString addressText = item->text(); + if (addressText.startsWith("0x", Qt::CaseInsensitive)) + { + bool ok; + uint64_t address = addressText.mid(2).toULongLong(&ok, 16); + + if (ok && address != 0) + { + // Navigate to address in Binary Ninja + ViewFrame* frame = ViewFrame::viewFrameForWidget(this); + if (frame) + { + frame->navigate(m_data, address); + } + } + } + } +} + +bool TTDHeapQueryWidget::isUnused() const +{ + return m_resultsTable->rowCount() == 0; +} + +void TTDHeapQueryWidget::setupContextMenu() +{ + m_contextMenuManager = new ContextMenuManager(this); + m_menu = new Menu(); + + // m_menu->addAction(new MenuAction("Column Visibility...", [=]() { showColumnVisibilityDialog(); })); + // m_menu->addAction(new MenuAction("Reset Columns to Default", [=]() { resetColumnsToDefault(); })); + // + // m_contextMenuManager->setMenus(QList{m_menu}); +} + +void TTDHeapQueryWidget::setupUIActions() +{ + setupContextMenu(); + + m_actionHandler.setupActionHandler(this); + m_actionHandler.setActionDisplayName("Copy", "Copy"); + m_actionHandler.bindAction("Copy", UIAction([=]() { copy(); }, [=]() { return canCopy(); })); +} + +void TTDHeapQueryWidget::updateColumnVisibility() +{ + for (int i = 0; i < m_columnVisibility.size() && i < m_resultsTable->columnCount(); ++i) + { + m_resultsTable->setColumnHidden(i, !m_columnVisibility[i]); + } +} + +bool TTDHeapQueryWidget::canCopy() +{ + return m_resultsTable->selectedItems().size() > 0; +} + +void TTDHeapQueryWidget::contextMenuEvent(QContextMenuEvent* event) +{ + if (m_contextMenuManager) + m_contextMenuManager->show(m_menu, &m_actionHandler); +} + +void TTDHeapQueryWidget::showColumnVisibilityDialog() +{ + // Create a simple dialog to toggle column visibility + QDialog dialog(this); + dialog.setWindowTitle("Column Visibility"); + dialog.setModal(true); + + auto* layout = new QVBoxLayout(&dialog); + + QList checkboxes; + for (int i = 0; i < m_columnNames.size(); ++i) + { + auto* checkbox = new QCheckBox(m_columnNames[i]); + checkbox->setChecked(m_columnVisibility[i]); + checkboxes.append(checkbox); + layout->addWidget(checkbox); + } + + auto* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + layout->addWidget(buttonBox); + + connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + + if (dialog.exec() == QDialog::Accepted) + { + for (int i = 0; i < checkboxes.size() && i < m_columnVisibility.size(); ++i) + { + m_columnVisibility[i] = checkboxes[i]->isChecked(); + } + updateColumnVisibility(); + } +} + +void TTDHeapQueryWidget::resetColumnsToDefault() +{ + // Reset to default visibility + m_columnVisibility.clear(); + m_columnVisibility << true << true << true << true << true // Index, Event Type, Action, Time Start, Time End + << true << true << false << true << false // Heap, Address, Previous Address, Size, Base Address + << false << true << false << false << false // Flags, Result, Reserve Size, Commit Size, Make Read Only + << false << false << false; // Thread ID, Unique Thread ID, Parameters + updateColumnVisibility(); +} + +void TTDHeapQueryWidget::showContextMenu(const QPoint& position) +{ + if (m_contextMenuManager) + m_contextMenuManager->show(m_menu, &m_actionHandler); +} + +void TTDHeapQueryWidget::copy() +{ + copySelectedRow(); +} + +void TTDHeapQueryWidget::copySelectedCell() +{ + auto selectedItems = m_resultsTable->selectedItems(); + if (selectedItems.isEmpty()) + return; + + QApplication::clipboard()->setText(selectedItems.first()->text()); +} + +void TTDHeapQueryWidget::copySelectedRow() +{ + auto selectedItems = m_resultsTable->selectedItems(); + if (selectedItems.isEmpty()) + return; + + int row = selectedItems.first()->row(); + QStringList rowData; + + for (int col = 0; col < m_resultsTable->columnCount(); ++col) + { + auto* item = m_resultsTable->item(row, col); + rowData << (item ? item->text() : ""); + } + + QApplication::clipboard()->setText(rowData.join("\t")); +} + +void TTDHeapQueryWidget::copyEntireTable() +{ + QStringList tableData; + + // Add header + QStringList headers; + for (int col = 0; col < m_resultsTable->columnCount(); ++col) + { + headers << m_resultsTable->horizontalHeaderItem(col)->text(); + } + tableData << headers.join("\t"); + + // Add rows + for (int row = 0; row < m_resultsTable->rowCount(); ++row) + { + QStringList rowData; + for (int col = 0; col < m_resultsTable->columnCount(); ++col) + { + auto* item = m_resultsTable->item(row, col); + rowData << (item ? item->text() : ""); + } + tableData << rowData.join("\t"); + } + + QApplication::clipboard()->setText(tableData.join("\n")); +} + +void TTDHeapQueryWidget::onDebuggerEvent(const DebuggerEvent& event) +{ + switch (event.type) + { + case TargetStoppedEventType: + // When the target stops, populate data if not already populated + if (!m_hasPopulatedData && m_controller && m_controller->IsTTD()) + { + performQuery(); + } + break; + default: + break; + } +} + +// TTDHeapWidget implementation (tab container) +TTDHeapWidget::TTDHeapWidget(QWidget* parent, BinaryViewRef data) + : QWidget(parent), m_data(data) +{ + m_controller = DebuggerController::GetController(m_data); + setupUI(); +} + +TTDHeapWidget::~TTDHeapWidget() +{ +} + +void TTDHeapWidget::setupUI() +{ + auto* layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + + // Tab widget with new tab button + auto* headerWidget = new QWidget(); + auto* headerLayout = new QHBoxLayout(headerWidget); + headerLayout->setContentsMargins(0, 0, 0, 0); + + m_tabWidget = new QTabWidget(); + m_tabWidget->setTabsClosable(true); + m_tabWidget->setMovable(true); + + m_newTabButton = new QToolButton(); + m_newTabButton->setText("+"); + m_newTabButton->setToolTip("New Tab"); + m_newTabButton->setAutoRaise(true); + + headerLayout->addWidget(m_tabWidget, 1); + headerLayout->addWidget(m_newTabButton); + + layout->addWidget(headerWidget); + + // Create initial tab + createNewTab(); + + // Connect signals + connect(m_newTabButton, &QToolButton::clicked, this, &TTDHeapWidget::createNewTab); + connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &TTDHeapWidget::closeTab); +} + +void TTDHeapWidget::createNewTab() +{ + auto* queryWidget = new TTDHeapQueryWidget(this, m_data); + int index = m_tabWidget->addTab(queryWidget, "Heap Query"); + m_tabWidget->setCurrentIndex(index); +} + +void TTDHeapWidget::closeTab(int index) +{ + if (m_tabWidget->count() <= 1) + return; // Keep at least one tab + + QWidget* widget = m_tabWidget->widget(index); + m_tabWidget->removeTab(index); + delete widget; +} + +TTDHeapQueryWidget* TTDHeapWidget::getCurrentOrNewQueryWidget() +{ + auto* currentWidget = qobject_cast(m_tabWidget->currentWidget()); + if (!currentWidget || !currentWidget->isUnused()) + { + createNewTab(); + currentWidget = qobject_cast(m_tabWidget->currentWidget()); + } + return currentWidget; +} + +void TTDHeapWidget::performQuery() +{ + auto* queryWidget = getCurrentOrNewQueryWidget(); + if (queryWidget) + queryWidget->performQuery(); +} + +void TTDHeapWidget::performQueryInNewTab() +{ + createNewTab(); + auto* queryWidget = qobject_cast(m_tabWidget->currentWidget()); + if (queryWidget) + queryWidget->performQuery(); +} + +// TTDHeapSidebarWidget implementation +TTDHeapSidebarWidget::TTDHeapSidebarWidget(BinaryViewRef data) + : SidebarWidget("TTD Heap"), m_data(data) +{ + m_controller = DebuggerController::GetController(m_data); + m_heapWidget = new TTDHeapWidget(this, m_data); + + auto* layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(m_heapWidget); +} + +TTDHeapSidebarWidget::~TTDHeapSidebarWidget() +{ +} + +void TTDHeapSidebarWidget::performQuery() +{ + if (m_heapWidget) + m_heapWidget->performQuery(); +} + +void TTDHeapSidebarWidget::performQueryInNewTab() +{ + if (m_heapWidget) + m_heapWidget->performQueryInNewTab(); +} + +// TTDHeapWidgetType implementation +TTDHeapWidgetType::TTDHeapWidgetType() + : SidebarWidgetType(QIcon(":/debugger/ttd-heap").pixmap(QSize(64, 64)).toImage(), "TTD Heap") +{ +} + +SidebarWidget* TTDHeapWidgetType::createWidget(ViewFrame* frame, BinaryViewRef data) +{ + return new TTDHeapSidebarWidget(data); +} + +SidebarContentClassifier* TTDHeapWidgetType::contentClassifier(ViewFrame*, BinaryViewRef data) +{ + return new ActiveDebugSessionSidebarContentClassifier(data); +} + +#include "ttdheapwidget.moc" \ No newline at end of file diff --git a/ui/ttdheapwidget.h b/ui/ttdheapwidget.h new file mode 100644 index 00000000..8a92d842 --- /dev/null +++ b/ui/ttdheapwidget.h @@ -0,0 +1,196 @@ +/* +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 "inttypes.h" +#include "binaryninjaapi.h" +#include "debuggerapi.h" +#include "viewframe.h" +#include "expandablegroup.h" +#include "debuggeruicommon.h" +#include "menus.h" +#include "uitypes.h" + +using namespace BinaryNinja; +using namespace BinaryNinjaDebuggerAPI; + +class TTDHeapQueryWidget : public QWidget +{ + Q_OBJECT + +public: + // Enum for logical column identification + enum LogicalColumn { + IndexColumn = 0, + EventTypeColumn, + ActionColumn, + TimeStartColumn, + TimeEndColumn, + HeapColumn, + AddressColumn, + PreviousAddressColumn, + SizeColumn, + BaseAddressColumn, + FlagsColumn, + ResultColumn, + ReserveSizeColumn, + CommitSizeColumn, + MakeReadOnlyColumn, + ThreadIdColumn, + UniqueThreadIdColumn, + ParametersColumn + }; + +private: + BinaryViewRef m_data; + DbgRef m_controller; + + // Input controls + 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; + + // Event callback + size_t m_debuggerEventCallback; + bool m_hasPopulatedData; + + void setupUI(); + void setupTable(); + void updateStatus(const QString& message); + void setupContextMenu(); + void setupUIActions(); + void updateColumnVisibility(); + bool canCopy(); + + virtual void contextMenuEvent(QContextMenuEvent* event) override; + +public: + TTDHeapQueryWidget(QWidget* parent, BinaryViewRef data); + virtual ~TTDHeapQueryWidget(); + + // Method to execute query from context menu + void performQuery(); + + // Method to check if this tab is unused (no results and default parameters) + bool isUnused() const; + +Q_SIGNALS: + void debuggerEvent(const DebuggerEvent& event); + +private Q_SLOTS: + void clearResults(); + void onCellDoubleClicked(int row, int column); + void showColumnVisibilityDialog(); + void resetColumnsToDefault(); + void showContextMenu(const QPoint& position); + void copy(); + void copySelectedCell(); + void copySelectedRow(); + void copyEntireTable(); + void onDebuggerEvent(const DebuggerEvent& event); +}; + +class TTDHeapWidget : public QWidget +{ + Q_OBJECT + +private: + BinaryViewRef m_data; + DbgRef m_controller; + QTabWidget* m_tabWidget; + QToolButton* m_newTabButton; + + void setupUI(); + +public: + TTDHeapWidget(QWidget* parent, BinaryViewRef data); + virtual ~TTDHeapWidget(); + + // Method to get current query widget or create new tab + TTDHeapQueryWidget* getCurrentOrNewQueryWidget(); + void performQuery(); + void performQueryInNewTab(); + +private Q_SLOTS: + void createNewTab(); + void closeTab(int index); +}; + + +class TTDHeapSidebarWidget : public SidebarWidget +{ + Q_OBJECT + +private: + TTDHeapWidget* m_heapWidget; + BinaryViewRef m_data; + DbgRef m_controller; + +public: + TTDHeapSidebarWidget(BinaryViewRef data); + ~TTDHeapSidebarWidget(); + + // Method to access the TTD Heap widget for context menu actions + void performQuery(); + void performQueryInNewTab(); +}; + + +class TTDHeapWidgetType : public SidebarWidgetType +{ +public: + TTDHeapWidgetType(); + 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 16f284ec..3c31400a 100644 --- a/ui/ui.cpp +++ b/ui/ui.cpp @@ -45,6 +45,7 @@ limitations under the License. #include "ttdmemorywidget.h" #include "ttdcallswidget.h" #include "ttdeventswidget.h" +#include "ttdheapwidget.h" #include "ttdanalysisdialog.h" #include "timestampnavigationdialog.h" #include "freeversion.h" @@ -1688,6 +1689,7 @@ void GlobalDebuggerUI::InitializeUI() Sidebar::addSidebarWidgetType(new TTDMemoryWidgetType()); Sidebar::addSidebarWidgetType(new TTDCallsWidgetType()); Sidebar::addSidebarWidgetType(new TTDEventsWidgetType()); + Sidebar::addSidebarWidgetType(new TTDHeapWidgetType()); }