Skip to content

Commit 42d7550

Browse files
xusheng6claude
andauthored
Feature/ttd next prev memory access (#1011)
* Add TTD NextMemoryAccess/PrevMemoryAccess API and UI Add efficient methods to find the next/previous memory access from the current TTD position using @$curprocess.TTD.NextMemoryAccess and PrevMemoryAccess, avoiding the need to pull all accesses and search. Adds API through all layers (adapter, controller, FFI, C++ API, Python). UI changes: - Right-click context menu actions (TTD Next/Prev Memory Access with Read, Write, Read/Write sub-options) that use the selection range and immediately time travel to the result. - Next/Prev buttons in the TTD Memory sidebar widget that add the found event to the results table and highlight it. - When the returned position is 0:0, show an informational dialog instead of navigating. Fix #997 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Various UI fixes --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b313411 commit 42d7550

16 files changed

Lines changed: 829 additions & 12 deletions

api/debuggerapi.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,8 @@ namespace BinaryNinjaDebuggerAPI {
828828
std::vector<TTDEvent> GetAllTTDEvents();
829829
TTDPosition GetCurrentTTDPosition();
830830
bool SetTTDPosition(const TTDPosition& position);
831+
std::pair<bool, TTDMemoryEvent> GetTTDNextMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType);
832+
std::pair<bool, TTDMemoryEvent> GetTTDPrevMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType);
831833

832834
// TTD Code Coverage Analysis Methods
833835
bool IsInstructionExecuted(uint64_t address);

api/debuggercontroller.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,6 +1182,62 @@ bool DebuggerController::SetTTDPosition(const TTDPosition& position)
11821182
return BNDebuggerSetTTDPosition(m_object, pos);
11831183
}
11841184

1185+
std::pair<bool, TTDMemoryEvent> DebuggerController::GetTTDNextMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType)
1186+
{
1187+
BNDebuggerTTDMemoryEvent bnEvent = {};
1188+
BNDebuggerTTDMemoryAccessType type = static_cast<BNDebuggerTTDMemoryAccessType>(accessType);
1189+
1190+
bool success = BNDebuggerGetTTDNextMemoryAccess(m_object, address, size, type, &bnEvent);
1191+
if (!success)
1192+
return {false, TTDMemoryEvent()};
1193+
1194+
TTDMemoryEvent event;
1195+
event.eventType = bnEvent.eventType ? std::string(bnEvent.eventType) : "";
1196+
event.threadId = bnEvent.threadId;
1197+
event.uniqueThreadId = bnEvent.uniqueThreadId;
1198+
event.timeStart = TTDPosition(bnEvent.timeStart.sequence, bnEvent.timeStart.step);
1199+
event.timeEnd = TTDPosition(bnEvent.timeEnd.sequence, bnEvent.timeEnd.step);
1200+
event.address = bnEvent.address;
1201+
event.size = bnEvent.size;
1202+
event.memoryAddress = bnEvent.memoryAddress;
1203+
event.instructionAddress = bnEvent.instructionAddress;
1204+
event.value = bnEvent.value;
1205+
event.accessType = static_cast<TTDMemoryAccessType>(bnEvent.accessType);
1206+
1207+
if (bnEvent.eventType)
1208+
BNDebuggerFreeString(bnEvent.eventType);
1209+
1210+
return {true, event};
1211+
}
1212+
1213+
std::pair<bool, TTDMemoryEvent> DebuggerController::GetTTDPrevMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType)
1214+
{
1215+
BNDebuggerTTDMemoryEvent bnEvent = {};
1216+
BNDebuggerTTDMemoryAccessType type = static_cast<BNDebuggerTTDMemoryAccessType>(accessType);
1217+
1218+
bool success = BNDebuggerGetTTDPrevMemoryAccess(m_object, address, size, type, &bnEvent);
1219+
if (!success)
1220+
return {false, TTDMemoryEvent()};
1221+
1222+
TTDMemoryEvent event;
1223+
event.eventType = bnEvent.eventType ? std::string(bnEvent.eventType) : "";
1224+
event.threadId = bnEvent.threadId;
1225+
event.uniqueThreadId = bnEvent.uniqueThreadId;
1226+
event.timeStart = TTDPosition(bnEvent.timeStart.sequence, bnEvent.timeStart.step);
1227+
event.timeEnd = TTDPosition(bnEvent.timeEnd.sequence, bnEvent.timeEnd.step);
1228+
event.address = bnEvent.address;
1229+
event.size = bnEvent.size;
1230+
event.memoryAddress = bnEvent.memoryAddress;
1231+
event.instructionAddress = bnEvent.instructionAddress;
1232+
event.value = bnEvent.value;
1233+
event.accessType = static_cast<TTDMemoryAccessType>(bnEvent.accessType);
1234+
1235+
if (bnEvent.eventType)
1236+
BNDebuggerFreeString(bnEvent.eventType);
1237+
1238+
return {true, event};
1239+
}
1240+
11851241
std::vector<TTDCallEvent> DebuggerController::GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress, uint64_t endReturnAddress)
11861242
{
11871243
std::vector<TTDCallEvent> result;

api/ffi.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,10 @@ extern "C"
692692
DEBUGGER_FFI_API BNDebuggerTTDEvent* BNDebuggerGetAllTTDEvents(BNDebuggerController* controller, size_t* count);
693693
DEBUGGER_FFI_API BNDebuggerTTDPosition BNDebuggerGetCurrentTTDPosition(BNDebuggerController* controller);
694694
DEBUGGER_FFI_API bool BNDebuggerSetTTDPosition(BNDebuggerController* controller, BNDebuggerTTDPosition position);
695+
DEBUGGER_FFI_API bool BNDebuggerGetTTDNextMemoryAccess(BNDebuggerController* controller,
696+
uint64_t address, uint64_t size, BNDebuggerTTDMemoryAccessType accessType, BNDebuggerTTDMemoryEvent* result);
697+
DEBUGGER_FFI_API bool BNDebuggerGetTTDPrevMemoryAccess(BNDebuggerController* controller,
698+
uint64_t address, uint64_t size, BNDebuggerTTDMemoryAccessType accessType, BNDebuggerTTDMemoryEvent* result);
695699
DEBUGGER_FFI_API void BNDebuggerFreeTTDMemoryEvents(BNDebuggerTTDMemoryEvent* events, size_t count);
696700
DEBUGGER_FFI_API void BNDebuggerFreeTTDPositionRangeIndexedMemoryEvents(BNDebuggerTTDPositionRangeIndexedMemoryEvent* events, size_t count);
697701
DEBUGGER_FFI_API void BNDebuggerFreeTTDCallEvents(BNDebuggerTTDCallEvent* events, size_t count);

api/python/debuggercontroller.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2553,6 +2553,74 @@ def navigate_to_timestamp(self, timestamp_str):
25532553
binaryninja.log_error(f"Invalid timestamp format: {e}")
25542554
return False
25552555

2556+
def get_ttd_next_memory_access(self, address: int, size: int, access_type = DebuggerTTDMemoryAccessType.DebuggerTTDMemoryRead):
2557+
"""
2558+
Get the next memory access to a specific address from the current TTD position.
2559+
2560+
This is more efficient than pulling all accesses and searching, as it uses
2561+
the TTD NextMemoryAccess API directly.
2562+
2563+
:param address: memory address to monitor
2564+
:param size: size of the memory region in bytes
2565+
:param access_type: type of memory access to query (read/write/execute)
2566+
:return: TTDMemoryEvent if found, None if no subsequent access exists
2567+
"""
2568+
parsed_access_type = parse_ttd_access_type(access_type)
2569+
result = dbgcore.BNDebuggerTTDMemoryEvent()
2570+
success = dbgcore.BNDebuggerGetTTDNextMemoryAccess(self.handle, address, size, parsed_access_type, result)
2571+
if not success:
2572+
return None
2573+
2574+
time_start = TTDPosition(result.timeStart.sequence, result.timeStart.step)
2575+
time_end = TTDPosition(result.timeEnd.sequence, result.timeEnd.step)
2576+
return TTDMemoryEvent(
2577+
event_type=result.eventType if result.eventType else "",
2578+
thread_id=result.threadId,
2579+
unique_thread_id=result.uniqueThreadId,
2580+
time_start=time_start,
2581+
time_end=time_end,
2582+
address=result.address,
2583+
size=result.size,
2584+
memory_address=result.memoryAddress,
2585+
instruction_address=result.instructionAddress,
2586+
value=result.value,
2587+
access_type=result.accessType
2588+
)
2589+
2590+
def get_ttd_prev_memory_access(self, address: int, size: int, access_type = DebuggerTTDMemoryAccessType.DebuggerTTDMemoryRead):
2591+
"""
2592+
Get the previous memory access to a specific address from the current TTD position.
2593+
2594+
This is more efficient than pulling all accesses and searching, as it uses
2595+
the TTD PrevMemoryAccess API directly.
2596+
2597+
:param address: memory address to monitor
2598+
:param size: size of the memory region in bytes
2599+
:param access_type: type of memory access to query (read/write/execute)
2600+
:return: TTDMemoryEvent if found, None if no prior access exists
2601+
"""
2602+
parsed_access_type = parse_ttd_access_type(access_type)
2603+
result = dbgcore.BNDebuggerTTDMemoryEvent()
2604+
success = dbgcore.BNDebuggerGetTTDPrevMemoryAccess(self.handle, address, size, parsed_access_type, result)
2605+
if not success:
2606+
return None
2607+
2608+
time_start = TTDPosition(result.timeStart.sequence, result.timeStart.step)
2609+
time_end = TTDPosition(result.timeEnd.sequence, result.timeEnd.step)
2610+
return TTDMemoryEvent(
2611+
event_type=result.eventType if result.eventType else "",
2612+
thread_id=result.threadId,
2613+
unique_thread_id=result.uniqueThreadId,
2614+
time_start=time_start,
2615+
time_end=time_end,
2616+
address=result.address,
2617+
size=result.size,
2618+
memory_address=result.memoryAddress,
2619+
instruction_address=result.instructionAddress,
2620+
value=result.value,
2621+
access_type=result.accessType
2622+
)
2623+
25562624
def get_ttd_memory_access_for_address(self, address: int, end_address: int, access_type = DebuggerTTDMemoryAccessType.DebuggerTTDMemoryRead) -> List[TTDMemoryEvent]:
25572625
"""
25582626
Get TTD memory access events for a specific address range.

core/adapters/dbgengttdadapter.cpp

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,75 @@ bool DbgEngTTDAdapter::SetTTDPosition(const TTDPosition& position)
531531
return success;
532532
}
533533

534+
535+
std::pair<bool, TTDMemoryEvent> DbgEngTTDAdapter::GetTTDNextMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType)
536+
{
537+
if (!m_debugControl)
538+
{
539+
LogError("Debug control interface not available");
540+
return {false, TTDMemoryEvent()};
541+
}
542+
543+
try
544+
{
545+
std::string accessTypeStr;
546+
if (accessType & TTDMemoryRead) accessTypeStr += "r";
547+
if (accessType & TTDMemoryWrite) accessTypeStr += "w";
548+
if (accessType & TTDMemoryExecute) accessTypeStr += "e";
549+
550+
if (accessTypeStr.empty())
551+
{
552+
LogError("Invalid access type specified");
553+
return {false, TTDMemoryEvent()};
554+
}
555+
556+
std::string expression = fmt::format("@$curprocess.TTD.NextMemoryAccess(\"{}\",0x{:x},0x{:x})", accessTypeStr, address, size);
557+
LogInfo("Executing TTD NextMemoryAccess query: %s", expression.c_str());
558+
559+
return ParseSingleTTDMemoryObject(expression, accessType);
560+
}
561+
catch (const std::exception& e)
562+
{
563+
LogError("Exception in GetTTDNextMemoryAccess: %s", e.what());
564+
return {false, TTDMemoryEvent()};
565+
}
566+
}
567+
568+
569+
std::pair<bool, TTDMemoryEvent> DbgEngTTDAdapter::GetTTDPrevMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType)
570+
{
571+
if (!m_debugControl)
572+
{
573+
LogError("Debug control interface not available");
574+
return {false, TTDMemoryEvent()};
575+
}
576+
577+
try
578+
{
579+
std::string accessTypeStr;
580+
if (accessType & TTDMemoryRead) accessTypeStr += "r";
581+
if (accessType & TTDMemoryWrite) accessTypeStr += "w";
582+
if (accessType & TTDMemoryExecute) accessTypeStr += "e";
583+
584+
if (accessTypeStr.empty())
585+
{
586+
LogError("Invalid access type specified");
587+
return {false, TTDMemoryEvent()};
588+
}
589+
590+
std::string expression = fmt::format("@$curprocess.TTD.PrevMemoryAccess(\"{}\",0x{:x},0x{:x})", accessTypeStr, address, size);
591+
LogInfo("Executing TTD PrevMemoryAccess query: %s", expression.c_str());
592+
593+
return ParseSingleTTDMemoryObject(expression, accessType);
594+
}
595+
catch (const std::exception& e)
596+
{
597+
LogError("Exception in GetTTDPrevMemoryAccess: %s", e.what());
598+
return {false, TTDMemoryEvent()};
599+
}
600+
}
601+
602+
534603
bool DbgEngTTDAdapter::QueryMemoryAccessByAddress(uint64_t startAddress, uint64_t endAddress, TTDMemoryAccessType accessType, std::vector<TTDMemoryEvent>& events)
535604
{
536605
if (!m_debugControl)
@@ -1272,6 +1341,146 @@ bool DbgEngTTDAdapter::ParseTTDPositionRangeIndexedMemoryObjects(const std::stri
12721341
}
12731342

12741343

1344+
std::pair<bool, TTDMemoryEvent> DbgEngTTDAdapter::ParseSingleTTDMemoryObject(const std::string& expression, TTDMemoryAccessType accessType)
1345+
{
1346+
TTDMemoryEvent event;
1347+
1348+
if (!m_hostEvaluator)
1349+
{
1350+
LogError("Data model evaluator not available");
1351+
return {false, event};
1352+
}
1353+
1354+
try
1355+
{
1356+
std::wstring wExpression(expression.begin(), expression.end());
1357+
1358+
ComPtr<IDebugHostContext> hostContext;
1359+
if (FAILED(m_debugHost->GetCurrentContext(hostContext.GetAddressOf())))
1360+
{
1361+
LogError("Failed to get current debug host context");
1362+
return {false, event};
1363+
}
1364+
1365+
ComPtr<IModelObject> result;
1366+
ComPtr<IKeyStore> metadata;
1367+
HRESULT hr = m_hostEvaluator->EvaluateExtendedExpression(
1368+
hostContext.Get(),
1369+
wExpression.c_str(),
1370+
nullptr,
1371+
result.GetAddressOf(),
1372+
metadata.GetAddressOf()
1373+
);
1374+
1375+
if (FAILED(hr))
1376+
{
1377+
LogError("Failed to evaluate expression '%s': 0x%08x", expression.c_str(), hr);
1378+
return {false, event};
1379+
}
1380+
1381+
if (!result)
1382+
{
1383+
LogError("Null result from expression '%s'", expression.c_str());
1384+
return {false, event};
1385+
}
1386+
1387+
// The result is a single memory access object (not a collection).
1388+
// Parse Position
1389+
ComPtr<IModelObject> positionObj;
1390+
if (SUCCEEDED(result->GetKeyValue(L"Position", &positionObj, nullptr)))
1391+
{
1392+
ParseTTDPosition(positionObj.Get(), event.timeStart);
1393+
}
1394+
1395+
// Parse OriginalPosition into timeEnd (represents the position from which the query was made)
1396+
ComPtr<IModelObject> origPositionObj;
1397+
if (SUCCEEDED(result->GetKeyValue(L"OriginalPosition", &origPositionObj, nullptr)))
1398+
{
1399+
ParseTTDPosition(origPositionObj.Get(), event.timeEnd);
1400+
}
1401+
1402+
// Get UniqueThreadId
1403+
ComPtr<IModelObject> uniqueThreadIdObj;
1404+
if (SUCCEEDED(result->GetKeyValue(L"UniqueThreadId", &uniqueThreadIdObj, nullptr)))
1405+
{
1406+
VARIANT vtUniqueThreadId;
1407+
VariantInit(&vtUniqueThreadId);
1408+
if (SUCCEEDED(uniqueThreadIdObj->GetIntrinsicValueAs(VT_UI4, &vtUniqueThreadId)))
1409+
{
1410+
event.uniqueThreadId = vtUniqueThreadId.ulVal;
1411+
}
1412+
VariantClear(&vtUniqueThreadId);
1413+
}
1414+
1415+
// Get Address
1416+
ComPtr<IModelObject> addressObj;
1417+
if (SUCCEEDED(result->GetKeyValue(L"Address", &addressObj, nullptr)))
1418+
{
1419+
VARIANT vtAddress;
1420+
VariantInit(&vtAddress);
1421+
if (SUCCEEDED(addressObj->GetIntrinsicValueAs(VT_UI8, &vtAddress)))
1422+
{
1423+
event.address = vtAddress.ullVal;
1424+
}
1425+
VariantClear(&vtAddress);
1426+
}
1427+
1428+
// Get Size
1429+
ComPtr<IModelObject> sizeObj;
1430+
if (SUCCEEDED(result->GetKeyValue(L"Size", &sizeObj, nullptr)))
1431+
{
1432+
VARIANT vtSize;
1433+
VariantInit(&vtSize);
1434+
if (SUCCEEDED(sizeObj->GetIntrinsicValueAs(VT_UI8, &vtSize)))
1435+
{
1436+
event.size = vtSize.ullVal;
1437+
}
1438+
VariantClear(&vtSize);
1439+
}
1440+
1441+
// Get AccessType
1442+
ComPtr<IModelObject> accessTypeObj;
1443+
if (SUCCEEDED(result->GetKeyValue(L"AccessType", &accessTypeObj, nullptr)))
1444+
{
1445+
VARIANT vtAccessType;
1446+
VariantInit(&vtAccessType);
1447+
if (SUCCEEDED(accessTypeObj->GetIntrinsicValueAs(VT_BSTR, &vtAccessType)))
1448+
{
1449+
_bstr_t bstr(vtAccessType.bstrVal);
1450+
std::string accessTypeStr = std::string(bstr);
1451+
1452+
TTDMemoryAccessType parsedAccessType = static_cast<TTDMemoryAccessType>(0);
1453+
if (accessTypeStr.find("Read") != std::string::npos)
1454+
parsedAccessType = static_cast<TTDMemoryAccessType>(parsedAccessType | TTDMemoryRead);
1455+
if (accessTypeStr.find("Write") != std::string::npos)
1456+
parsedAccessType = static_cast<TTDMemoryAccessType>(parsedAccessType | TTDMemoryWrite);
1457+
if (accessTypeStr.find("Execute") != std::string::npos)
1458+
parsedAccessType = static_cast<TTDMemoryAccessType>(parsedAccessType | TTDMemoryExecute);
1459+
1460+
event.accessType = parsedAccessType;
1461+
}
1462+
else
1463+
{
1464+
event.accessType = accessType;
1465+
}
1466+
VariantClear(&vtAccessType);
1467+
}
1468+
else
1469+
{
1470+
event.accessType = accessType;
1471+
}
1472+
1473+
LogInfo("Successfully parsed single TTD memory access at position %llx:%llx", event.timeStart.sequence, event.timeStart.step);
1474+
return {true, event};
1475+
}
1476+
catch (const std::exception& e)
1477+
{
1478+
LogError("Exception in ParseSingleTTDMemoryObject: %s", e.what());
1479+
return {false, event};
1480+
}
1481+
}
1482+
1483+
12751484
std::vector<TTDCallEvent> DbgEngTTDAdapter::GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress, uint64_t endReturnAddress)
12761485
{
12771486
std::vector<TTDCallEvent> events;

core/adapters/dbgengttdadapter.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ namespace BinaryNinjaDebugger {
5454
TTDPosition GetCurrentTTDPosition() override;
5555
bool SetTTDPosition(const TTDPosition& position) override;
5656

57+
// TTD Next/Prev Memory Access Methods
58+
std::pair<bool, TTDMemoryEvent> GetTTDNextMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType) override;
59+
std::pair<bool, TTDMemoryEvent> GetTTDPrevMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType) override;
60+
5761
// TTD Calls Analysis Methods
5862
std::vector<TTDCallEvent> GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress = 0, uint64_t endReturnAddress = 0) override;
5963

@@ -92,6 +96,7 @@ namespace BinaryNinjaDebugger {
9296
std::string EvaluateDataModelExpression(const std::string& expression);
9397
bool ParseTTDMemoryObjects(const std::string& expression, TTDMemoryAccessType accessType, std::vector<TTDMemoryEvent>& events);
9498
bool ParseTTDPositionRangeIndexedMemoryObjects(const std::string& expression, TTDMemoryAccessType accessType, std::vector<TTDPositionRangeIndexedMemoryEvent>& events);
99+
std::pair<bool, TTDMemoryEvent> ParseSingleTTDMemoryObject(const std::string& expression, TTDMemoryAccessType accessType);
95100

96101
// Data model interfaces for TTD
97102
IHostDataModelAccess* m_dataModelManager;

0 commit comments

Comments
 (0)