Skip to content

Commit 9296ae7

Browse files
Copilotxusheng6
andcommitted
Add TTD Heap UI widget and Python API integration
Co-authored-by: xusheng6 <94503187+xusheng6@users.noreply.github.com>
1 parent a325992 commit 9296ae7

File tree

9 files changed

+1141
-0
lines changed

9 files changed

+1141
-0
lines changed

api/debuggercontroller.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,6 +1063,64 @@ std::vector<TTDCallEvent> DebuggerController::GetTTDCallsForSymbols(const std::s
10631063
}
10641064

10651065

1066+
std::vector<TTDHeapEvent> DebuggerController::GetTTDHeapObjects()
1067+
{
1068+
std::vector<TTDHeapEvent> result;
1069+
1070+
size_t count = 0;
1071+
BNDebuggerTTDHeapEvent* events = BNDebuggerGetTTDHeapObjects(m_object, &count);
1072+
1073+
if (events && count > 0)
1074+
{
1075+
result.reserve(count);
1076+
for (size_t i = 0; i < count; i++)
1077+
{
1078+
TTDHeapEvent event;
1079+
event.eventType = events[i].eventType ? std::string(events[i].eventType) : "";
1080+
event.action = events[i].action ? std::string(events[i].action) : "";
1081+
event.threadId = events[i].threadId;
1082+
event.uniqueThreadId = events[i].uniqueThreadId;
1083+
event.heap = events[i].heap;
1084+
event.address = events[i].address;
1085+
event.previousAddress = events[i].previousAddress;
1086+
event.size = events[i].size;
1087+
event.baseAddress = events[i].baseAddress;
1088+
event.flags = events[i].flags;
1089+
event.result = events[i].result;
1090+
event.reserveSize = events[i].reserveSize;
1091+
event.commitSize = events[i].commitSize;
1092+
event.makeReadOnly = events[i].makeReadOnly;
1093+
event.timeStart.sequence = events[i].timeStart.sequence;
1094+
event.timeStart.step = events[i].timeStart.step;
1095+
event.timeEnd.sequence = events[i].timeEnd.sequence;
1096+
event.timeEnd.step = events[i].timeEnd.step;
1097+
1098+
// Convert parameters array
1099+
if (events[i].parameters && events[i].parameterCount > 0)
1100+
{
1101+
event.parameters.reserve(events[i].parameterCount);
1102+
for (size_t j = 0; j < events[i].parameterCount; j++)
1103+
{
1104+
if (events[i].parameters[j])
1105+
{
1106+
event.parameters.push_back(std::string(events[i].parameters[j]));
1107+
}
1108+
else
1109+
{
1110+
event.parameters.push_back("");
1111+
}
1112+
}
1113+
}
1114+
1115+
result.push_back(event);
1116+
}
1117+
BNDebuggerFreeTTDHeapEvents(events, count);
1118+
}
1119+
1120+
return result;
1121+
}
1122+
1123+
10661124
bool DebuggerController::IsInstructionExecuted(uint64_t address)
10671125
{
10681126
return BNDebuggerIsInstructionExecuted(m_object, address);

api/ffi.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,28 @@ extern "C"
339339
BNDebuggerTTDPosition timeEnd; // Position when call ended
340340
} BNDebuggerTTDCallEvent;
341341

342+
typedef struct BNDebuggerTTDHeapEvent
343+
{
344+
char* eventType; // Event type (always "Heap" for TTD.Heap objects)
345+
char* action; // Heap action: Alloc, ReAlloc, Free, Create, Protect, Lock, Unlock, Destroy
346+
uint32_t threadId; // OS thread ID of thread that made the heap call
347+
uint32_t uniqueThreadId; // Unique ID for the thread across the trace
348+
uint64_t heap; // Handle for the Win32 heap
349+
uint64_t address; // Address of the allocated object (if applicable)
350+
uint64_t previousAddress; // Address before reallocation (for ReAlloc)
351+
uint64_t size; // Size of allocated object (if applicable)
352+
uint64_t baseAddress; // Base address of allocated object (if applicable)
353+
uint64_t flags; // Heap API flags (meaning depends on API)
354+
uint64_t result; // Result of heap API call (non-zero = success)
355+
uint64_t reserveSize; // Amount of memory to reserve (for Create)
356+
uint64_t commitSize; // Initial committed size (for Create)
357+
uint64_t makeReadOnly; // Non-zero = make heap read-only (for Protect)
358+
char** parameters; // Array containing raw parameters from the heap call
359+
size_t parameterCount; // Number of parameters
360+
BNDebuggerTTDPosition timeStart; // Position when heap operation started
361+
BNDebuggerTTDPosition timeEnd; // Position when heap operation ended
362+
} BNDebuggerTTDHeapEvent;
363+
342364

343365
// This should really be a union, but gcc complains...
344366
typedef struct BNDebuggerEventData
@@ -555,10 +577,12 @@ extern "C"
555577
uint64_t address, uint64_t size, BNDebuggerTTDMemoryAccessType accessType, size_t* count);
556578
DEBUGGER_FFI_API BNDebuggerTTDCallEvent* BNDebuggerGetTTDCallsForSymbols(BNDebuggerController* controller,
557579
const char* symbols, uint64_t startReturnAddress, uint64_t endReturnAddress, size_t* count);
580+
DEBUGGER_FFI_API BNDebuggerTTDHeapEvent* BNDebuggerGetTTDHeapObjects(BNDebuggerController* controller, size_t* count);
558581
DEBUGGER_FFI_API BNDebuggerTTDPosition BNDebuggerGetCurrentTTDPosition(BNDebuggerController* controller);
559582
DEBUGGER_FFI_API bool BNDebuggerSetTTDPosition(BNDebuggerController* controller, BNDebuggerTTDPosition position);
560583
DEBUGGER_FFI_API void BNDebuggerFreeTTDMemoryEvents(BNDebuggerTTDMemoryEvent* events, size_t count);
561584
DEBUGGER_FFI_API void BNDebuggerFreeTTDCallEvents(BNDebuggerTTDCallEvent* events, size_t count);
585+
DEBUGGER_FFI_API void BNDebuggerFreeTTDHeapEvents(BNDebuggerTTDHeapEvent* events, size_t count);
562586

563587
// TTD Code Coverage Analysis Functions
564588
DEBUGGER_FFI_API bool BNDebuggerIsInstructionExecuted(BNDebuggerController* controller, uint64_t address);

api/python/debuggercontroller.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,93 @@ def __repr__(self):
721721
return f"<TTDCallEvent: {self.function} @ {self.function_address:#x}, thread {self.thread_id}>"
722722

723723

724+
class TTDHeapEvent:
725+
"""
726+
TTDHeapEvent represents a heap operation event in a TTD trace. It has the following fields:
727+
728+
* ``event_type``: type of the event (always "Heap" for TTD.Heap objects)
729+
* ``action``: heap action that occurred (Alloc, ReAlloc, Free, Create, Protect, Lock, Unlock, Destroy)
730+
* ``thread_id``: OS thread ID that performed the heap operation
731+
* ``unique_thread_id``: unique thread ID across the trace
732+
* ``heap``: handle for the Win32 heap
733+
* ``address``: address of the allocated object (if applicable)
734+
* ``previous_address``: address before reallocation (for ReAlloc operations)
735+
* ``size``: size of allocated object (if applicable)
736+
* ``base_address``: base address of allocated object (if applicable)
737+
* ``flags``: heap API flags (meaning depends on the specific API)
738+
* ``result``: result of heap API call (non-zero means success)
739+
* ``reserve_size``: amount of memory to reserve (for Create operations)
740+
* ``commit_size``: initial committed size (for Create operations)
741+
* ``make_read_only``: non-zero indicates request to make heap read-only
742+
* ``parameters``: list of raw parameters from the heap call
743+
* ``time_start``: TTD position when heap operation started
744+
* ``time_end``: TTD position when heap operation ended
745+
"""
746+
747+
def __init__(self, event_type: str, action: str, thread_id: int, unique_thread_id: int,
748+
heap: int, address: int, previous_address: int, size: int, base_address: int,
749+
flags: int, result: int, reserve_size: int, commit_size: int, make_read_only: int,
750+
parameters: List[str], time_start: TTDPosition, time_end: TTDPosition):
751+
self.event_type = event_type
752+
self.action = action
753+
self.thread_id = thread_id
754+
self.unique_thread_id = unique_thread_id
755+
self.heap = heap
756+
self.address = address
757+
self.previous_address = previous_address
758+
self.size = size
759+
self.base_address = base_address
760+
self.flags = flags
761+
self.result = result
762+
self.reserve_size = reserve_size
763+
self.commit_size = commit_size
764+
self.make_read_only = make_read_only
765+
self.parameters = parameters
766+
self.time_start = time_start
767+
self.time_end = time_end
768+
769+
def __eq__(self, other):
770+
if not isinstance(other, self.__class__):
771+
return NotImplemented
772+
return (self.event_type == other.event_type and
773+
self.action == other.action and
774+
self.thread_id == other.thread_id and
775+
self.unique_thread_id == other.unique_thread_id and
776+
self.heap == other.heap and
777+
self.address == other.address and
778+
self.previous_address == other.previous_address and
779+
self.size == other.size and
780+
self.base_address == other.base_address and
781+
self.flags == other.flags and
782+
self.result == other.result and
783+
self.reserve_size == other.reserve_size and
784+
self.commit_size == other.commit_size and
785+
self.make_read_only == other.make_read_only and
786+
self.parameters == other.parameters and
787+
self.time_start == other.time_start and
788+
self.time_end == other.time_end)
789+
790+
def __ne__(self, other):
791+
if not isinstance(other, self.__class__):
792+
return NotImplemented
793+
return not (self == other)
794+
795+
def __hash__(self):
796+
return hash((self.event_type, self.action, self.thread_id, self.unique_thread_id,
797+
self.heap, self.address, self.previous_address, self.size, self.base_address,
798+
self.flags, self.result, self.reserve_size, self.commit_size, self.make_read_only,
799+
tuple(self.parameters), self.time_start, self.time_end))
800+
801+
def __setattr__(self, name, value):
802+
try:
803+
object.__setattr__(self, name, value)
804+
except AttributeError:
805+
raise AttributeError(f"attribute '{name}' is read only")
806+
807+
def __repr__(self):
808+
return f"<TTDHeapEvent: {self.action} @ heap {self.heap:#x}, thread {self.thread_id}>"
809+
810+
724811
class DebuggerController:
725812
"""
726813
The ``DebuggerController`` object is the core of the debugger. Most debugger operations can be performed on it.
@@ -2042,6 +2129,58 @@ def get_ttd_calls_for_symbols(self, symbols: str, start_return_address: int = 0,
20422129
dbgcore.BNDebuggerFreeTTDCallEvents(events, count.value)
20432130
return result
20442131

2132+
def get_ttd_heap_objects(self) -> List[TTDHeapEvent]:
2133+
"""
2134+
Get TTD heap operation events.
2135+
2136+
This method is only available when debugging with TTD (Time Travel Debugging).
2137+
Use the is_ttd property to check if TTD is available before calling this method.
2138+
2139+
:return: list of TTDHeapEvent objects representing heap operations
2140+
:raises: May raise an exception if TTD is not available
2141+
"""
2142+
count = ctypes.c_ulonglong()
2143+
events = dbgcore.BNDebuggerGetTTDHeapObjects(self.handle, count)
2144+
2145+
if not events:
2146+
return []
2147+
2148+
result = []
2149+
for i in range(count.value):
2150+
event = events[i]
2151+
time_start = TTDPosition(event.timeStart.sequence, event.timeStart.step)
2152+
time_end = TTDPosition(event.timeEnd.sequence, event.timeEnd.step)
2153+
2154+
# Convert parameters array to Python list
2155+
parameters = []
2156+
if event.parameters and event.parameterCount > 0:
2157+
for j in range(event.parameterCount):
2158+
parameters.append(event.parameters[j])
2159+
2160+
heap_event = TTDHeapEvent(
2161+
event_type=event.eventType if event.eventType else "",
2162+
action=event.action if event.action else "",
2163+
thread_id=event.threadId,
2164+
unique_thread_id=event.uniqueThreadId,
2165+
heap=event.heap,
2166+
address=event.address,
2167+
previous_address=event.previousAddress,
2168+
size=event.size,
2169+
base_address=event.baseAddress,
2170+
flags=event.flags,
2171+
result=event.result,
2172+
reserve_size=event.reserveSize,
2173+
commit_size=event.commitSize,
2174+
make_read_only=event.makeReadOnly,
2175+
parameters=parameters,
2176+
time_start=time_start,
2177+
time_end=time_end
2178+
)
2179+
result.append(heap_event)
2180+
2181+
dbgcore.BNDebuggerFreeTTDHeapEvents(events, count.value)
2182+
return result
2183+
20452184
def __del__(self):
20462185
if dbgcore is not None:
20472186
dbgcore.BNDebuggerFreeController(self.handle)

core/ffi.cpp

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1272,6 +1272,101 @@ void BNDebuggerFreeTTDCallEvents(BNDebuggerTTDCallEvent* events, size_t count)
12721272
}
12731273

12741274

1275+
BNDebuggerTTDHeapEvent* BNDebuggerGetTTDHeapObjects(BNDebuggerController* controller, size_t* count)
1276+
{
1277+
if (!count)
1278+
return nullptr;
1279+
1280+
*count = 0;
1281+
1282+
auto events = controller->object->GetTTDHeapObjects();
1283+
if (events.empty())
1284+
return nullptr;
1285+
1286+
*count = events.size();
1287+
auto result = new BNDebuggerTTDHeapEvent[events.size()];
1288+
1289+
for (size_t i = 0; i < events.size(); ++i)
1290+
{
1291+
// Copy string fields
1292+
result[i].eventType = BNAllocString(events[i].eventType.c_str());
1293+
result[i].action = BNAllocString(events[i].action.c_str());
1294+
1295+
// Copy primitive fields
1296+
result[i].threadId = events[i].threadId;
1297+
result[i].uniqueThreadId = events[i].uniqueThreadId;
1298+
result[i].heap = events[i].heap;
1299+
result[i].address = events[i].address;
1300+
result[i].previousAddress = events[i].previousAddress;
1301+
result[i].size = events[i].size;
1302+
result[i].baseAddress = events[i].baseAddress;
1303+
result[i].flags = events[i].flags;
1304+
result[i].result = events[i].result;
1305+
result[i].reserveSize = events[i].reserveSize;
1306+
result[i].commitSize = events[i].commitSize;
1307+
result[i].makeReadOnly = events[i].makeReadOnly;
1308+
1309+
// Copy parameters array
1310+
result[i].parameterCount = events[i].parameters.size();
1311+
if (result[i].parameterCount > 0)
1312+
{
1313+
result[i].parameters = new char*[result[i].parameterCount];
1314+
for (size_t j = 0; j < result[i].parameterCount; ++j)
1315+
{
1316+
result[i].parameters[j] = BNAllocString(events[i].parameters[j].c_str());
1317+
}
1318+
}
1319+
else
1320+
{
1321+
result[i].parameters = nullptr;
1322+
}
1323+
1324+
// Copy TTD positions
1325+
result[i].timeStart.sequence = events[i].timeStart.sequence;
1326+
result[i].timeStart.step = events[i].timeStart.step;
1327+
result[i].timeEnd.sequence = events[i].timeEnd.sequence;
1328+
result[i].timeEnd.step = events[i].timeEnd.step;
1329+
}
1330+
1331+
return result;
1332+
}
1333+
1334+
1335+
void BNDebuggerFreeTTDHeapEvents(BNDebuggerTTDHeapEvent* events, size_t count)
1336+
{
1337+
if (!events || count == 0)
1338+
return;
1339+
1340+
// Free all strings for each event
1341+
for (size_t i = 0; i < count; ++i)
1342+
{
1343+
if (events[i].eventType)
1344+
{
1345+
BNFreeString(events[i].eventType);
1346+
}
1347+
if (events[i].action)
1348+
{
1349+
BNFreeString(events[i].action);
1350+
}
1351+
1352+
// Free parameter strings
1353+
if (events[i].parameters && events[i].parameterCount > 0)
1354+
{
1355+
for (size_t j = 0; j < events[i].parameterCount; ++j)
1356+
{
1357+
if (events[i].parameters[j])
1358+
{
1359+
BNFreeString(events[i].parameters[j]);
1360+
}
1361+
}
1362+
delete[] events[i].parameters;
1363+
}
1364+
}
1365+
1366+
delete[] events;
1367+
}
1368+
1369+
12751370

12761371
void BNDebuggerPostDebuggerEvent(BNDebuggerController* controller, BNDebuggerEvent* event)
12771372
{

debuggerui.qrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
<file alias="stop">icons/stop.png</file>
2525
<file alias="ttd-memory">icons/ttd-memory.png</file>
2626
<file alias="ttd-calls">icons/ttd-calls.png</file>
27+
<file alias="ttd-heap">icons/ttd-heap.png</file>
2728
<file alias="ttd-timestamp">icons/ttd-timestamp.png</file>
2829
</qresource>
2930
</RCC>

icons/ttd-heap.png

394 Bytes
Loading

0 commit comments

Comments
 (0)