Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,4 @@ api/python/__pycache__
# TTD files
*.run
*.idx
__pycache__/
26 changes: 26 additions & 0 deletions api/debuggerapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,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<std::string> 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) {}
};


typedef BNDebugAdapterConnectionStatus DebugAdapterConnectionStatus;
typedef BNDebugAdapterTargetStatus DebugAdapterTargetStatus;
Expand Down Expand Up @@ -687,6 +712,7 @@ namespace BinaryNinjaDebuggerAPI {
// TTD Memory Analysis Methods
std::vector<TTDMemoryEvent> GetTTDMemoryAccessForAddress(uint64_t address, uint64_t size, TTDMemoryAccessType accessType = TTDMemoryRead);
std::vector<TTDCallEvent> GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress = 0, uint64_t endReturnAddress = 0);
std::vector<TTDHeapEvent> GetTTDHeapObjects();
TTDPosition GetCurrentTTDPosition();
bool SetTTDPosition(const TTDPosition& position);

Expand Down
58 changes: 58 additions & 0 deletions api/debuggercontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,64 @@ std::vector<TTDCallEvent> DebuggerController::GetTTDCallsForSymbols(const std::s
}


std::vector<TTDHeapEvent> DebuggerController::GetTTDHeapObjects()
{
std::vector<TTDHeapEvent> 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;
}


bool DebuggerController::IsInstructionExecuted(uint64_t address)
{
return BNDebuggerIsInstructionExecuted(m_object, address);
Expand Down
24 changes: 24 additions & 0 deletions api/ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,28 @@ extern "C"
BNDebuggerTTDPosition timeEnd; // Position when call ended
} BNDebuggerTTDCallEvent;

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
Expand Down Expand Up @@ -555,10 +577,12 @@ extern "C"
uint64_t address, uint64_t size, BNDebuggerTTDMemoryAccessType accessType, size_t* count);
DEBUGGER_FFI_API BNDebuggerTTDCallEvent* BNDebuggerGetTTDCallsForSymbols(BNDebuggerController* controller,
const char* symbols, uint64_t startReturnAddress, uint64_t endReturnAddress, size_t* count);
DEBUGGER_FFI_API 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 BNDebuggerFreeTTDHeapEvents(BNDebuggerTTDHeapEvent* events, size_t count);

// TTD Code Coverage Analysis Functions
DEBUGGER_FFI_API bool BNDebuggerIsInstructionExecuted(BNDebuggerController* controller, uint64_t address);
Expand Down
139 changes: 139 additions & 0 deletions api/python/debuggercontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,93 @@ def __repr__(self):
return f"<TTDCallEvent: {self.function} @ {self.function_address:#x}, thread {self.thread_id}>"


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"<TTDHeapEvent: {self.action} @ heap {self.heap:#x}, thread {self.thread_id}>"


class DebuggerController:
"""
The ``DebuggerController`` object is the core of the debugger. Most debugger operations can be performed on it.
Expand Down Expand Up @@ -2042,6 +2129,58 @@ def get_ttd_calls_for_symbols(self, symbols: str, start_return_address: int = 0,
dbgcore.BNDebuggerFreeTTDCallEvents(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)
Expand Down
Loading