Skip to content

Commit 67941ec

Browse files
NicoleFayexusheng6
andauthored
TTD code coverage time range filter (#962)
* Add Time Range Filter option for ttd code coverage analysis * Fix Info message showing in error stream * add method to query memory accress for a time range in ttd * use time based memory access query instead of filtering * use new time based api in TTD memory widget * Added visual studio build related files to gitignore revert gitignore changes because i built in the wrong dir, wasnt an issue * Add in TTD api for GetTTDMemoryAccessForPositionRange and associated structs. In order to fix ttdmemorywidget results display * increase alpha to 100% for code coverage highlight * remove note taking comment made before concrete type for PositionRangeIndexedMemoryEvent * fix results table logical column enum. And tt on double click * add in address parsing that matches windbg default console output format * Add in explicit declaration of base and manual trim to 2 acceptable formats, 0x and windbg hex output for addresses * remove unused vars * Various fixes --------- Co-authored-by: Xusheng <xusheng@vector35.com>
1 parent b72e83b commit 67941ec

19 files changed

Lines changed: 1020 additions & 90 deletions

api/debuggerapi.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,23 @@ namespace BinaryNinjaDebuggerAPI {
520520
TTDMemoryEvent() : threadId(0), uniqueThreadId(0), accessType(TTDMemoryRead), address(0), size(0), memoryAddress(0), instructionAddress(0), value(0) {}
521521
};
522522

523+
struct TTDPositionRangeIndexedMemoryEvent{
524+
TTDPosition position; // Position of the memory event
525+
uint32_t threadId; // Thread ID that performed the access
526+
uint32_t uniqueThreadId; // Unique thread ID that performed the access
527+
uint64_t address; // Memory address accessed
528+
uint64_t instructionAddress; // Instruction pointer at time of access
529+
uint64_t size; // Size of memory access
530+
TTDMemoryAccessType accessType; // Type of memory access (parsed from object)
531+
uint64_t value; // Value that was read/written/executed
532+
uint8_t data[8]; // The next 8 bytes of data at the memory address
533+
534+
TTDPositionRangeIndexedMemoryEvent() : threadId(0), uniqueThreadId(0), address(0), size(0), instructionAddress(0), accessType(TTDMemoryRead), value(0)
535+
{
536+
memset(data, 0, sizeof(data));
537+
}
538+
};
539+
523540
struct TTDCallEvent
524541
{
525542
std::string eventType; // Event type (always "Call" for TTD.Calls objects)
@@ -805,6 +822,7 @@ namespace BinaryNinjaDebuggerAPI {
805822

806823
// TTD Memory Analysis Methods
807824
std::vector<TTDMemoryEvent> GetTTDMemoryAccessForAddress(uint64_t address, uint64_t endAddress, TTDMemoryAccessType accessType = TTDMemoryRead);
825+
std::vector<TTDPositionRangeIndexedMemoryEvent> GetTTDMemoryAccessForPositionRange(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime);
808826
std::vector<TTDCallEvent> GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress = 0, uint64_t endReturnAddress = 0);
809827
std::vector<TTDEvent> GetTTDEvents(TTDEventType eventType);
810828
std::vector<TTDEvent> GetAllTTDEvents();
@@ -813,7 +831,7 @@ namespace BinaryNinjaDebuggerAPI {
813831

814832
// TTD Code Coverage Analysis Methods
815833
bool IsInstructionExecuted(uint64_t address);
816-
bool RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t endAddress);
834+
bool RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t endAddress, TTDPosition startTime, TTDPosition endTime);
817835
size_t GetExecutedInstructionCount() const;
818836
bool SaveCodeCoverageToFile(const std::string& filePath) const;
819837
bool LoadCodeCoverageFromFile(const std::string& filePath);

api/debuggercontroller.cpp

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,6 +1096,43 @@ bool DebuggerController::IsTTD()
10961096
return BNDebuggerIsTTD(m_object);
10971097
}
10981098

1099+
std::vector<TTDPositionRangeIndexedMemoryEvent> DebuggerController::GetTTDMemoryAccessForPositionRange(uint64_t address, uint64_t endAddress, TTDMemoryAccessType accessType, const TTDPosition startTime, const TTDPosition endTime)
1100+
{
1101+
std::vector<TTDPositionRangeIndexedMemoryEvent> result;
1102+
1103+
BNDebuggerTTDMemoryAccessType type = static_cast<BNDebuggerTTDMemoryAccessType>(accessType);
1104+
BNDebuggerTTDPosition bnStartTime = {startTime.sequence, startTime.step};
1105+
BNDebuggerTTDPosition bnEndTime = {endTime.sequence, endTime.step};
1106+
1107+
size_t count = 0;
1108+
BNDebuggerTTDPositionRangeIndexedMemoryEvent* events = BNDebuggerGetTTDMemoryAccessForPositionRange(m_object, address, endAddress, type, bnStartTime, bnEndTime, &count);
1109+
1110+
if (events && count > 0)
1111+
{
1112+
result.reserve(count);
1113+
for (size_t i = 0; i < count; i++)
1114+
{
1115+
TTDPositionRangeIndexedMemoryEvent event;
1116+
event.threadId = events[i].threadId;
1117+
event.uniqueThreadId = events[i].uniqueThreadId;
1118+
event.position.sequence = events[i].position.sequence;
1119+
event.position.step = events[i].position.step;
1120+
event.accessType = static_cast<TTDMemoryAccessType>(events[i].accessType);
1121+
event.address = events[i].address;
1122+
event.size = events[i].size;
1123+
event.instructionAddress = events[i].instructionAddress;
1124+
event.value = events[i].value;
1125+
for (size_t j = 0; j < 8; j++)
1126+
{
1127+
event.data[j] = events[i].data[j];
1128+
}
1129+
result.push_back(event);
1130+
}
1131+
BNDebuggerFreeTTDPositionRangeIndexedMemoryEvents(events, count);
1132+
}
1133+
1134+
return result;
1135+
}
10991136

11001137
std::vector<TTDMemoryEvent> DebuggerController::GetTTDMemoryAccessForAddress(uint64_t address, uint64_t endAddress, TTDMemoryAccessType accessType)
11011138
{
@@ -1343,9 +1380,14 @@ bool DebuggerController::IsInstructionExecuted(uint64_t address)
13431380
}
13441381

13451382

1346-
bool DebuggerController::RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t endAddress)
1383+
bool DebuggerController::RunCodeCoverageAnalysis(uint64_t startAddress, uint64_t endAddress, TTDPosition startTime, TTDPosition endTime)
13471384
{
1348-
return BNDebuggerRunCodeCoverageAnalysisRange(m_object, startAddress, endAddress);
1385+
BNDebuggerTTDPosition startPos, endPos;
1386+
startPos.sequence = startTime.sequence;
1387+
startPos.step = startTime.step;
1388+
endPos.sequence = endTime.sequence;
1389+
endPos.step = endTime.step;
1390+
return BNDebuggerRunCodeCoverageAnalysisRange(m_object, startAddress, endAddress, startPos, endPos);
13491391
}
13501392

13511393

api/ffi.h

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,19 @@ extern "C"
335335
BNDebuggerTTDMemoryAccessType accessType;
336336
} BNDebuggerTTDMemoryEvent;
337337

338+
typedef struct BNDebuggerTTDPositionRangeIndexedMemoryEvent
339+
{
340+
BNDebuggerTTDPosition position;
341+
uint32_t threadId;
342+
uint32_t uniqueThreadId;
343+
uint64_t address;
344+
uint64_t instructionAddress;
345+
uint64_t size;
346+
BNDebuggerTTDMemoryAccessType accessType;
347+
uint64_t value;
348+
uint8_t data[8];
349+
} BNDebuggerTTDPositionRangeIndexedMemoryEvent;
350+
338351
typedef struct BNDebuggerTTDCallEvent
339352
{
340353
char* eventType; // Event type (always "Call" for TTD.Calls objects)
@@ -669,6 +682,9 @@ extern "C"
669682
// TTD Memory Analysis Functions
670683
DEBUGGER_FFI_API BNDebuggerTTDMemoryEvent* BNDebuggerGetTTDMemoryAccessForAddress(BNDebuggerController* controller,
671684
uint64_t address, uint64_t endAddress, BNDebuggerTTDMemoryAccessType accessType, size_t* count);
685+
DEBUGGER_FFI_API BNDebuggerTTDPositionRangeIndexedMemoryEvent* BNDebuggerGetTTDMemoryAccessForPositionRange(BNDebuggerController* controller,
686+
uint64_t address, uint64_t endAddress, BNDebuggerTTDMemoryAccessType accessType ,BNDebuggerTTDPosition startPosition, BNDebuggerTTDPosition endPosition,
687+
size_t* count);
672688
DEBUGGER_FFI_API BNDebuggerTTDCallEvent* BNDebuggerGetTTDCallsForSymbols(BNDebuggerController* controller,
673689
const char* symbols, uint64_t startReturnAddress, uint64_t endReturnAddress, size_t* count);
674690
DEBUGGER_FFI_API BNDebuggerTTDEvent* BNDebuggerGetTTDEvents(BNDebuggerController* controller,
@@ -677,12 +693,13 @@ extern "C"
677693
DEBUGGER_FFI_API BNDebuggerTTDPosition BNDebuggerGetCurrentTTDPosition(BNDebuggerController* controller);
678694
DEBUGGER_FFI_API bool BNDebuggerSetTTDPosition(BNDebuggerController* controller, BNDebuggerTTDPosition position);
679695
DEBUGGER_FFI_API void BNDebuggerFreeTTDMemoryEvents(BNDebuggerTTDMemoryEvent* events, size_t count);
696+
DEBUGGER_FFI_API void BNDebuggerFreeTTDPositionRangeIndexedMemoryEvents(BNDebuggerTTDPositionRangeIndexedMemoryEvent* events, size_t count);
680697
DEBUGGER_FFI_API void BNDebuggerFreeTTDCallEvents(BNDebuggerTTDCallEvent* events, size_t count);
681698
DEBUGGER_FFI_API void BNDebuggerFreeTTDEvents(BNDebuggerTTDEvent* events, size_t count);
682699

683700
// TTD Code Coverage Analysis Functions
684701
DEBUGGER_FFI_API bool BNDebuggerIsInstructionExecuted(BNDebuggerController* controller, uint64_t address);
685-
DEBUGGER_FFI_API bool BNDebuggerRunCodeCoverageAnalysisRange(BNDebuggerController* controller, uint64_t startAddress, uint64_t endAddress);
702+
DEBUGGER_FFI_API bool BNDebuggerRunCodeCoverageAnalysisRange(BNDebuggerController* controller, uint64_t startAddress, uint64_t endAddress, BNDebuggerTTDPosition startTime, BNDebuggerTTDPosition endTime);
686703
DEBUGGER_FFI_API size_t BNDebuggerGetExecutedInstructionCount(BNDebuggerController* controller);
687704
DEBUGGER_FFI_API bool BNDebuggerSaveCodeCoverageToFile(BNDebuggerController* controller, const char* filePath);
688705
DEBUGGER_FFI_API bool BNDebuggerLoadCodeCoverageFromFile(BNDebuggerController* controller, const char* filePath);

api/python/debuggercontroller.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,68 @@ def __repr__(self):
804804
return f"<TTDMemoryEvent: {self.event_type} @ {self.address:#x}, thread {self.thread_id}>"
805805

806806

807+
class TTDPositionRangeIndexedMemoryEvent:
808+
"""
809+
TTDPositionRangeIndexedMemoryEvent represents a memory access event in a TTD trace with position information.
810+
This is used for memory queries filtered by both address range and time range.
811+
812+
* ``position``: TTD position when the memory access occurred
813+
* ``thread_id``: OS thread ID that performed the memory access
814+
* ``unique_thread_id``: unique thread ID across the trace
815+
* ``address``: memory address that was accessed
816+
* ``instruction_address``: address of the instruction that performed the access
817+
* ``size``: size of the memory access in bytes
818+
* ``access_type``: type of access (read/write/execute)
819+
* ``value``: value that was read/written/executed (truncated to size)
820+
* ``data``: the next 8 bytes of data at the memory address (as a bytes object)
821+
"""
822+
823+
def __init__(self, position: TTDPosition, thread_id: int, unique_thread_id: int,
824+
address: int, instruction_address: int, size: int,
825+
access_type: int, value: int, data: bytes):
826+
self.position = position
827+
self.thread_id = thread_id
828+
self.unique_thread_id = unique_thread_id
829+
self.address = address
830+
self.instruction_address = instruction_address
831+
self.size = size
832+
self.access_type = access_type
833+
self.value = value
834+
self.data = data
835+
836+
def __eq__(self, other):
837+
if not isinstance(other, self.__class__):
838+
return NotImplemented
839+
return (self.position == other.position and
840+
self.thread_id == other.thread_id and
841+
self.unique_thread_id == other.unique_thread_id and
842+
self.address == other.address and
843+
self.instruction_address == other.instruction_address and
844+
self.size == other.size and
845+
self.access_type == other.access_type and
846+
self.value == other.value and
847+
self.data == other.data)
848+
849+
def __ne__(self, other):
850+
if not isinstance(other, self.__class__):
851+
return NotImplemented
852+
return not (self == other)
853+
854+
def __hash__(self):
855+
return hash((self.position, self.thread_id, self.unique_thread_id,
856+
self.address, self.instruction_address, self.size,
857+
self.access_type, self.value, self.data))
858+
859+
def __setattr__(self, name, value):
860+
try:
861+
object.__setattr__(self, name, value)
862+
except AttributeError:
863+
raise AttributeError(f"attribute '{name}' is read only")
864+
865+
def __repr__(self):
866+
return f"<TTDPositionRangeIndexedMemoryEvent: @ {self.address:#x}, pos {self.position}, thread {self.thread_id}>"
867+
868+
807869
class TTDCallEvent:
808870
"""
809871
TTDCallEvent represents a function call event in a TTD trace. It has the following fields:
@@ -2540,6 +2602,75 @@ def get_ttd_memory_access_for_address(self, address: int, end_address: int, acce
25402602
dbgcore.BNDebuggerFreeTTDMemoryEvents(events, count.value)
25412603
return result
25422604

2605+
def get_ttd_memory_access_for_position_range(self, start_address: int, end_address: int, access_type,
2606+
start_time: TTDPosition = None, end_time: TTDPosition = None) -> List[TTDPositionRangeIndexedMemoryEvent]:
2607+
"""
2608+
Get TTD memory access events for a specific address range and time range.
2609+
2610+
This method is only available when debugging with TTD (Time Travel Debugging).
2611+
Use the is_ttd property to check if TTD is available before calling this method.
2612+
2613+
:param start_address: starting memory address to query
2614+
:param end_address: ending memory address to query
2615+
:param access_type: type of memory access to query - can be:
2616+
- DebuggerTTDMemoryAccessType enum values
2617+
- String specification like "r", "w", "e", "rw", "rwe", etc.
2618+
- Integer values (for backward compatibility)
2619+
:param start_time: starting TTD position (default: start of trace)
2620+
:param end_time: ending TTD position (default: end of trace)
2621+
:return: list of TTDPositionRangeIndexedMemoryEvent objects
2622+
:raises: May raise an exception if TTD is not available
2623+
"""
2624+
# Parse access type if it's a string
2625+
parsed_access_type = parse_ttd_access_type(access_type)
2626+
2627+
# Set default time range if not provided
2628+
if start_time is None:
2629+
start_time = TTDPosition(0, 0)
2630+
if end_time is None:
2631+
end_time = TTDPosition(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF)
2632+
2633+
# Create ctypes structures for positions
2634+
bn_start_pos = dbgcore.BNDebuggerTTDPosition()
2635+
bn_start_pos.sequence = start_time.sequence
2636+
bn_start_pos.step = start_time.step
2637+
2638+
bn_end_pos = dbgcore.BNDebuggerTTDPosition()
2639+
bn_end_pos.sequence = end_time.sequence
2640+
bn_end_pos.step = end_time.step
2641+
2642+
count = ctypes.c_ulonglong()
2643+
events = dbgcore.BNDebuggerGetTTDMemoryAccessForPositionRange(
2644+
self.handle, start_address, end_address, parsed_access_type,
2645+
bn_start_pos, bn_end_pos, count)
2646+
2647+
if not events:
2648+
return []
2649+
2650+
result = []
2651+
for i in range(count.value):
2652+
event = events[i]
2653+
position = TTDPosition(event.position.sequence, event.position.step)
2654+
2655+
# Convert data array to bytes
2656+
data = bytes(event.data[j] for j in range(8))
2657+
2658+
memory_event = TTDPositionRangeIndexedMemoryEvent(
2659+
position=position,
2660+
thread_id=event.threadId,
2661+
unique_thread_id=event.uniqueThreadId,
2662+
address=event.address,
2663+
instruction_address=event.instructionAddress,
2664+
size=event.size,
2665+
access_type=event.accessType,
2666+
value=event.value,
2667+
data=data
2668+
)
2669+
result.append(memory_event)
2670+
2671+
dbgcore.BNDebuggerFreeTTDPositionRangeIndexedMemoryEvents(events, count.value)
2672+
return result
2673+
25432674
def get_ttd_calls_for_symbols(self, symbols: str, start_return_address: int = 0, end_return_address: int = 0) -> List[TTDCallEvent]:
25442675
"""
25452676
Get TTD call events for specific symbols/functions.
@@ -2748,6 +2879,84 @@ def get_all_ttd_events(self) -> List[TTDEvent]:
27482879
dbgcore.BNDebuggerFreeTTDEvents(events, count.value)
27492880
return result
27502881

2882+
def run_code_coverage_analysis(self, start_address: int, end_address: int,
2883+
start_time: TTDPosition = None, end_time: TTDPosition = None) -> bool:
2884+
"""
2885+
Run code coverage analysis on a specific address range and time range.
2886+
2887+
This method is only available when debugging with TTD (Time Travel Debugging).
2888+
Use the is_ttd property to check if TTD is available before calling this method.
2889+
2890+
The code coverage analysis identifies which instructions within the specified address range
2891+
were executed during the specified time range. Results can be queried using the
2892+
is_instruction_executed() method.
2893+
2894+
:param start_address: starting address of the range to analyze
2895+
:param end_address: ending address of the range to analyze
2896+
:param start_time: starting TTD position (default: start of trace)
2897+
:param end_time: ending TTD position (default: end of trace)
2898+
:return: True if analysis succeeded, False otherwise
2899+
:raises: May raise an exception if TTD is not available
2900+
"""
2901+
# Set default time range if not provided
2902+
if start_time is None:
2903+
start_time = TTDPosition(0, 0)
2904+
if end_time is None:
2905+
end_time = TTDPosition(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF)
2906+
2907+
# Create ctypes structures for positions
2908+
bn_start_pos = dbgcore.BNDebuggerTTDPosition()
2909+
bn_start_pos.sequence = start_time.sequence
2910+
bn_start_pos.step = start_time.step
2911+
2912+
bn_end_pos = dbgcore.BNDebuggerTTDPosition()
2913+
bn_end_pos.sequence = end_time.sequence
2914+
bn_end_pos.step = end_time.step
2915+
2916+
return dbgcore.BNDebuggerRunCodeCoverageAnalysisRange(
2917+
self.handle, start_address, end_address, bn_start_pos, bn_end_pos)
2918+
2919+
def is_instruction_executed(self, address: int) -> bool:
2920+
"""
2921+
Check if an instruction at a specific address was executed during code coverage analysis.
2922+
2923+
This method requires that run_code_coverage_analysis() has been called first.
2924+
2925+
:param address: address of the instruction to check
2926+
:return: True if the instruction was executed, False otherwise
2927+
"""
2928+
return dbgcore.BNDebuggerIsInstructionExecuted(self.handle, address)
2929+
2930+
def get_executed_instruction_count(self) -> int:
2931+
"""
2932+
Get the count of executed instructions from the last code coverage analysis.
2933+
2934+
This method requires that run_code_coverage_analysis() has been called first.
2935+
2936+
:return: number of unique executed instructions
2937+
"""
2938+
return dbgcore.BNDebuggerGetExecutedInstructionCount(self.handle)
2939+
2940+
def save_code_coverage_to_file(self, file_path: str) -> bool:
2941+
"""
2942+
Save code coverage results to a file.
2943+
2944+
This method requires that run_code_coverage_analysis() has been called first.
2945+
2946+
:param file_path: path to the file where results should be saved
2947+
:return: True if save succeeded, False otherwise
2948+
"""
2949+
return dbgcore.BNDebuggerSaveCodeCoverageToFile(self.handle, file_path.encode('utf-8'))
2950+
2951+
def load_code_coverage_from_file(self, file_path: str) -> bool:
2952+
"""
2953+
Load code coverage results from a file.
2954+
2955+
:param file_path: path to the file containing code coverage results
2956+
:return: True if load succeeded, False otherwise
2957+
"""
2958+
return dbgcore.BNDebuggerLoadCodeCoverageFromFile(self.handle, file_path.encode('utf-8'))
2959+
27512960
def __del__(self):
27522961
if dbgcore is not None:
27532962
dbgcore.BNDebuggerFreeController(self.handle)

0 commit comments

Comments
 (0)