Skip to content
Open
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
14 changes: 14 additions & 0 deletions api/debuggerapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,19 @@ namespace BinaryNinjaDebuggerAPI {
: position(pos), viewAddress(addr), note(n) {}
};

struct TTDStringEntry
{
uint64_t id;
std::string data;
uint64_t address;
uint64_t size;
TTDPosition firstAccess;
TTDPosition lastAccess;
std::string encoding;

TTDStringEntry() : id(0), address(0), size(0) {}
};


typedef BNDebugAdapterConnectionStatus DebugAdapterConnectionStatus;
typedef BNDebugAdapterTargetStatus DebugAdapterTargetStatus;
Expand Down Expand Up @@ -842,6 +855,7 @@ namespace BinaryNinjaDebuggerAPI {
bool SetTTDPosition(const TTDPosition& position);
std::pair<bool, TTDMemoryEvent> GetTTDNextMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType);
std::pair<bool, TTDMemoryEvent> GetTTDPrevMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType);
std::vector<TTDStringEntry> GetTTDStrings(const std::string& pattern = "", uint64_t maxResults = 0);

// TTD Position History Navigation
bool TTDNavigateBack();
Expand Down
32 changes: 32 additions & 0 deletions api/debuggercontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1461,6 +1461,38 @@ std::vector<TTDEvent> DebuggerController::GetAllTTDEvents()
}


std::vector<TTDStringEntry> DebuggerController::GetTTDStrings(const std::string& pattern, uint64_t maxResults)
{
std::vector<TTDStringEntry> result;

size_t count = 0;
BNDebuggerTTDStringEntry* entries = BNDebuggerGetTTDStrings(m_object, pattern.c_str(), maxResults, &count);

if (entries && count > 0)
{
result.reserve(count);
for (size_t i = 0; i < count; i++)
{
TTDStringEntry entry;
entry.id = entries[i].id;
entry.data = entries[i].data ? std::string(entries[i].data) : "";
entry.address = entries[i].address;
entry.size = entries[i].size;
entry.firstAccess.sequence = entries[i].firstAccess.sequence;
entry.firstAccess.step = entries[i].firstAccess.step;
entry.lastAccess.sequence = entries[i].lastAccess.sequence;
entry.lastAccess.step = entries[i].lastAccess.step;
entry.encoding = entries[i].encoding ? std::string(entries[i].encoding) : "";

result.push_back(entry);
}
BNDebuggerFreeTTDStrings(entries, count);
}

return result;
}


std::vector<TTDBookmark> DebuggerController::GetTTDBookmarks()
{
std::vector<TTDBookmark> result;
Expand Down
16 changes: 16 additions & 0 deletions api/ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,22 @@ extern "C"
DEBUGGER_FFI_API void BNDebuggerFreeTTDCallEvents(BNDebuggerTTDCallEvent* events, size_t count);
DEBUGGER_FFI_API void BNDebuggerFreeTTDEvents(BNDebuggerTTDEvent* events, size_t count);

// TTD String Entry structures and functions
typedef struct BNDebuggerTTDStringEntry
{
uint64_t id;
char* data;
uint64_t address;
uint64_t size;
BNDebuggerTTDPosition firstAccess;
BNDebuggerTTDPosition lastAccess;
char* encoding;
} BNDebuggerTTDStringEntry;

DEBUGGER_FFI_API BNDebuggerTTDStringEntry* BNDebuggerGetTTDStrings(
BNDebuggerController* controller, const char* pattern, uint64_t maxResults, size_t* count);
DEBUGGER_FFI_API void BNDebuggerFreeTTDStrings(BNDebuggerTTDStringEntry* entries, size_t count);

// TTD Bookmark structures and functions
typedef struct BNDebuggerTTDBookmark
{
Expand Down
70 changes: 70 additions & 0 deletions api/python/debuggercontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -1192,6 +1192,35 @@ def __repr__(self):
return f"<TTDEvent: {type_str} @ {self.position}>"


class TTDStringEntry:
"""
TTDStringEntry represents a string found in a TTD trace.

Attributes:
id (int): unique identifier for the string
data (str): the string content
address (int): linear address where the string begins
size (int): size in bytes
first_access (TTDPosition): position of the first access in the trace
last_access (TTDPosition): position of the last access in the trace
encoding (str): string encoding ("utf8" or "utf16")
"""

def __init__(self, id: int, data: str, address: int, size: int,
first_access: 'TTDPosition', last_access: 'TTDPosition', encoding: str):
self.id = id
self.data = data
self.address = address
self.size = size
self.first_access = first_access
self.last_access = last_access
self.encoding = encoding

def __repr__(self):
preview = self.data[:40] + "..." if len(self.data) > 40 else self.data
return f"<TTDStringEntry: \"{preview}\" @ {self.address:#x}, {self.encoding}>"


class DebuggerController:
"""
The ``DebuggerController`` object is the core of the debugger. Most debugger operations can be performed on it.
Expand Down Expand Up @@ -3122,6 +3151,47 @@ def get_all_ttd_events(self) -> List[TTDEvent]:
dbgcore.BNDebuggerFreeTTDEvents(events, count.value)
return result

def get_ttd_strings(self, pattern: str = "", max_results: int = 0) -> List[TTDStringEntry]:
"""
Get strings found in the TTD trace, optionally filtered by a pattern.

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.

:param pattern: substring pattern to search for (empty string for all strings)
:param max_results: maximum number of results to return
:return: list of TTDStringEntry objects
:rtype: List[TTDStringEntry]
"""
if self.handle is None:
return []

count = ctypes.c_ulonglong()
entries = dbgcore.BNDebuggerGetTTDStrings(self.handle, pattern, max_results, ctypes.byref(count))

result = []
if not entries or count.value == 0:
return result

for i in range(count.value):
entry = entries[i]
first_access = TTDPosition(entry.firstAccess.sequence, entry.firstAccess.step)
last_access = TTDPosition(entry.lastAccess.sequence, entry.lastAccess.step)

string_entry = TTDStringEntry(
id=entry.id,
data=entry.data,
address=entry.address,
size=entry.size,
first_access=first_access,
last_access=last_access,
encoding=entry.encoding
)
result.append(string_entry)

dbgcore.BNDebuggerFreeTTDStrings(entries, count.value)
return result

def run_code_coverage_analysis(self, start_address: int, end_address: int,
start_time: TTDPosition = None, end_time: TTDPosition = None) -> bool:
"""
Expand Down
134 changes: 134 additions & 0 deletions core/adapters/esrevenadapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3102,6 +3102,140 @@ std::vector<TTDCallEvent> EsrevenAdapter::GetTTDCallsForSymbols(const std::strin
return events;
}

std::vector<TTDStringEntry> EsrevenAdapter::GetTTDStrings(const std::string& pattern, uint64_t maxResults)
{
if (m_isTargetRunning)
return {};

if (!m_rspConnector)
return {};

// Build the RSP packet: rvn:get-strings[:<pattern>][:<max_results>]
// maxResults=0 means no limit
std::string packet = "rvn:get-strings";
if (!pattern.empty() || maxResults != 0)
{
packet += ":" + pattern;
if (maxResults != 0)
packet += ":" + std::to_string(maxResults);
}

auto response = m_rspConnector->TransmitAndReceive(RspData(packet));
std::string jsonStr = response.AsString();

if (jsonStr.empty() || jsonStr[0] != '[')
return {};

std::vector<TTDStringEntry> result;

// Helper lambda to extract uint64_t value from JSON object
auto extractUInt64 = [](const std::string& json, const std::string& key) -> uint64_t {
size_t keyPos = json.find("\"" + key + "\"");
if (keyPos == std::string::npos)
return 0;

size_t colonPos = json.find(':', keyPos);
if (colonPos == std::string::npos)
return 0;

size_t valueStart = colonPos + 1;
while (valueStart < json.length() && std::isspace(json[valueStart]))
valueStart++;

if (json.substr(valueStart, 4) == "null")
return 0;

size_t valueEnd = valueStart;
while (valueEnd < json.length() && std::isdigit(json[valueEnd]))
valueEnd++;

if (valueEnd > valueStart)
return std::stoull(json.substr(valueStart, valueEnd - valueStart));
return 0;
};

// Helper lambda to extract string value from JSON object
auto extractString = [](const std::string& json, const std::string& key) -> std::string {
size_t keyPos = json.find("\"" + key + "\"");
if (keyPos == std::string::npos)
return "";

size_t colonPos = json.find(':', keyPos);
if (colonPos == std::string::npos)
return "";

size_t valueStart = json.find('"', colonPos);
if (valueStart == std::string::npos)
return "";

// Handle escaped characters in strings
std::string value;
size_t i = valueStart + 1;
while (i < json.length())
{
if (json[i] == '\\' && i + 1 < json.length())
{
switch (json[i + 1])
{
case '"': value += '"'; break;
case '\\': value += '\\'; break;
case 'n': value += '\n'; break;
case 't': value += '\t'; break;
case 'r': value += '\r'; break;
default: value += json[i + 1]; break;
}
i += 2;
}
else if (json[i] == '"')
{
break;
}
else
{
value += json[i];
i++;
}
}

return value;
};

size_t pos = 0;
while (pos < jsonStr.length())
{
size_t objStart = jsonStr.find('{', pos);
if (objStart == std::string::npos)
break;

// Find matching closing brace (handle nested braces)
size_t objEnd = jsonStr.find('}', objStart);
if (objEnd == std::string::npos)
break;

std::string objStr = jsonStr.substr(objStart, objEnd - objStart + 1);

TTDStringEntry entry;
entry.id = extractUInt64(objStr, "id");
entry.data = extractString(objStr, "data");
entry.address = extractUInt64(objStr, "address");
entry.size = extractUInt64(objStr, "size");

uint64_t firstAccess = extractUInt64(objStr, "first_access");
uint64_t lastAccess = extractUInt64(objStr, "last_access");
entry.firstAccess = TTDPosition(firstAccess, 0);
entry.lastAccess = TTDPosition(lastAccess, 0);

entry.encoding = extractString(objStr, "encoding");

result.push_back(entry);

pos = objEnd + 1;
}

return result;
}


Ref<Settings> EsrevenAdapterType::GetAdapterSettings()
{
static Ref<Settings> settings = RegisterAdapterSettings();
Expand Down
1 change: 1 addition & 0 deletions core/adapters/esrevenadapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ namespace BinaryNinjaDebugger
TTDPosition GetCurrentTTDPosition() override;
bool SetTTDPosition(const TTDPosition& position) override;
std::vector<TTDCallEvent> GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress = 0, uint64_t endReturnAddress = 0) override;
std::vector<TTDStringEntry> GetTTDStrings(const std::string& pattern = "", uint64_t maxResults = 0) override;

void GenerateDefaultAdapterSettings(BinaryView* data);
Ref<Settings> GetAdapterSettings() override;
Expand Down
6 changes: 6 additions & 0 deletions core/debugadapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -275,3 +275,9 @@ std::pair<bool, TTDMemoryEvent> DebugAdapter::GetTTDPrevMemoryAccess(uint64_t ad
}


std::vector<TTDStringEntry> DebugAdapter::GetTTDStrings(const std::string& pattern, uint64_t maxResults)
{
return {};
}


1 change: 1 addition & 0 deletions core/debugadapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ namespace BinaryNinjaDebugger {
virtual bool SetTTDPosition(const TTDPosition& position);
virtual std::pair<bool, TTDMemoryEvent> GetTTDNextMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType);
virtual std::pair<bool, TTDMemoryEvent> GetTTDPrevMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType);
virtual std::vector<TTDStringEntry> GetTTDStrings(const std::string& pattern = "", uint64_t maxResults = 0);

};
}; // namespace BinaryNinjaDebugger
14 changes: 14 additions & 0 deletions core/debuggercommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,20 @@ namespace BinaryNinjaDebugger {
TTDEvent(TTDEventType eventType) : type(eventType) {}
};

// TTD String Entry - represents a string found in the trace
struct TTDStringEntry
{
uint64_t id;
std::string data; // The string content
uint64_t address; // Linear address where string begins
uint64_t size; // Size in bytes
TTDPosition firstAccess; // Position of first access in the trace
TTDPosition lastAccess; // Position of last access in the trace
std::string encoding; // "utf8" or "utf16"

TTDStringEntry() : id(0), address(0), size(0) {}
};

// Breakpoint types - used to specify the type of breakpoint to set
enum DebugBreakpointType
{
Expand Down
12 changes: 12 additions & 0 deletions core/debuggercontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3147,6 +3147,18 @@ std::vector<TTDEvent> DebuggerController::GetAllTTDEvents()
}


std::vector<TTDStringEntry> DebuggerController::GetTTDStrings(const std::string& pattern, uint64_t maxResults)
{
if (!m_state->IsConnected() || !IsTTD())
{
LogWarn("Current adapter does not support TTD");
return {};
}

return m_adapter->GetTTDStrings(pattern, maxResults);
}


void DebuggerController::RecordTTDPosition()
{
if (!m_adapter || !m_adapter->SupportFeature(DebugAdapterSupportTTD) || m_suppressTTDPositionRecording)
Expand Down
1 change: 1 addition & 0 deletions core/debuggercontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ namespace BinaryNinjaDebugger {
bool SetTTDPosition(const TTDPosition& position);
std::pair<bool, TTDMemoryEvent> GetTTDNextMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType);
std::pair<bool, TTDMemoryEvent> GetTTDPrevMemoryAccess(uint64_t address, uint64_t size, TTDMemoryAccessType accessType);
std::vector<TTDStringEntry> GetTTDStrings(const std::string& pattern = "", uint64_t maxResults = 0);

// TTD Position History Navigation
bool TTDNavigateBack();
Expand Down
Loading