Skip to content

Commit 0e0dc2a

Browse files
razainaraz4ina
andauthored
Implement GetCurrentTTDPosition and SetTTDPosition for esReven adapter (#1004)
Use the custom rvn:get-current-transition and rvn:set-current-transition packets to get and set the current TTD position (transition ID). Also implement GetTTDCallsForSymbols using rvn:get-calls-by-symbol with support for optional return address range filtering. Update ReceiveRspData in rspconnector to remove the explicit timeout parameter, using a fixed 10-second default instead. Co-authored-by: Tiana <tiana.razafindralambo@eshard.com>
1 parent c7e0cf1 commit 0e0dc2a

4 files changed

Lines changed: 274 additions & 9 deletions

File tree

core/adapters/esrevenadapter.cpp

Lines changed: 261 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1703,7 +1703,12 @@ bool EsrevenAdapter::SupportFeature(DebugAdapterCapacity feature)
17031703
case DebugAdapterSupportThreads:
17041704
return true;
17051705
case DebugAdapterSupportTTD:
1706-
return m_canReverseContinue && m_canReverseStep;
1706+
{
1707+
bool ttdSupported = m_canReverseContinue && m_canReverseStep;
1708+
LogInfo("SupportFeature(DebugAdapterSupportTTD): m_canReverseContinue=%d, m_canReverseStep=%d, returning %d",
1709+
m_canReverseContinue, m_canReverseStep, ttdSupported);
1710+
return ttdSupported;
1711+
}
17071712
default:
17081713
return false;
17091714
}
@@ -2078,10 +2083,265 @@ Ref<Settings> EsrevenAdapterType::RegisterAdapterSettings()
20782083
"readOnly" : false
20792084
})");
20802085

2086+
settings->RegisterSetting("ttd.queryTimeout",
2087+
R"JSON({
2088+
"title" : "TTD Query Timeout",
2089+
"type" : "number",
2090+
"default" : 60000,
2091+
"minValue" : 5000,
2092+
"maxValue" : 600000,
2093+
"description" : "Timeout in milliseconds for TTD query operations (calls, memory, events). Increase for large wildcard queries. Default: 60000ms (60s)",
2094+
"readOnly" : false
2095+
})JSON");
2096+
2097+
settings->RegisterSetting("ttd.maxCallsQueryResults",
2098+
R"JSON({
2099+
"title" : "Max Calls Query Results",
2100+
"type" : "number",
2101+
"default" : 10000,
2102+
"minValue" : 0,
2103+
"maxValue" : 18446744073709551615,
2104+
"description" : "Maximum number of results to return from TTD Calls queries. Set to 0 for no limit.",
2105+
"readOnly" : false
2106+
})JSON");
2107+
20812108
return settings;
20822109
}
20832110

20842111

2112+
TTDPosition EsrevenAdapter::GetCurrentTTDPosition()
2113+
{
2114+
if (!m_rspConnector)
2115+
return TTDPosition();
2116+
2117+
auto reply = m_rspConnector->TransmitAndReceive(
2118+
RspData("rvn:get-current-transition"), "ack_then_reply", nullptr,
2119+
std::chrono::milliseconds(5000));
2120+
2121+
std::string json = reply.AsString();
2122+
2123+
if (json.empty() || json[0] == 'E')
2124+
return TTDPosition();
2125+
2126+
// Server returns JSON null when no transition is available
2127+
if (json == "null" || json.empty())
2128+
return TTDPosition();
2129+
2130+
// Manual JSON extraction — no external library, same pattern as GetTTDCallsForSymbols
2131+
std::string objectStr = json;
2132+
auto extractUInt64 = [&objectStr](const std::string& fieldName) -> uint64_t {
2133+
std::string searchStr = "\"" + fieldName + "\":";
2134+
size_t pos = objectStr.find(searchStr);
2135+
if (pos == std::string::npos)
2136+
return 0;
2137+
pos += searchStr.length();
2138+
while (pos < objectStr.length() && std::isspace(objectStr[pos]))
2139+
pos++;
2140+
if (objectStr.substr(pos, 4) == "null")
2141+
return 0;
2142+
size_t end = pos;
2143+
while (end < objectStr.length() && (std::isdigit(objectStr[end]) || objectStr[end] == '-'))
2144+
end++;
2145+
if (end > pos)
2146+
{
2147+
try { return std::stoull(objectStr.substr(pos, end - pos)); } catch (...) {}
2148+
}
2149+
return 0;
2150+
};
2151+
2152+
uint64_t transitionId = extractUInt64("transition_id");
2153+
// TTDPosition: sequence = transition_id, step = 0 (REVEN has no sub-step granularity)
2154+
return TTDPosition(transitionId, 0);
2155+
}
2156+
2157+
2158+
bool EsrevenAdapter::SetTTDPosition(const TTDPosition& position)
2159+
{
2160+
if (!m_rspConnector)
2161+
return false;
2162+
2163+
DebuggerEvent dbgevt;
2164+
dbgevt.type = ResumeEventType;
2165+
PostDebuggerEvent(dbgevt);
2166+
2167+
InvalidateCache();
2168+
2169+
auto stopReason = GenericGo(fmt::format("rvn:set-current-transition:{}", position.sequence));
2170+
return stopReason != InternalError;
2171+
}
2172+
2173+
2174+
std::vector<TTDCallEvent> EsrevenAdapter::GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress, uint64_t endReturnAddress)
2175+
{
2176+
std::vector<TTDCallEvent> events;
2177+
2178+
if (symbols.empty())
2179+
{
2180+
LogError("No symbols provided for TTD calls query");
2181+
return events;
2182+
}
2183+
2184+
// Get settings
2185+
auto adapterSettings = GetAdapterSettings();
2186+
BNSettingsScope scope = SettingsResourceScope;
2187+
auto timeoutMs = adapterSettings->Get<uint64_t>("ttd.queryTimeout", GetData(), &scope);
2188+
auto maxResults = adapterSettings->Get<uint64_t>("ttd.maxCallsQueryResults", GetData(), &scope);
2189+
auto timeout = std::chrono::milliseconds(timeoutMs);
2190+
2191+
LogInfo("GetTTDCallsForSymbols: symbols='%s', timeout=%lldms, maxResults=%llu",
2192+
symbols.c_str(), timeoutMs, maxResults);
2193+
2194+
try
2195+
{
2196+
// Detect wildcard patterns to add max_symbols limit
2197+
bool isWildcard = (symbols.find('*') != std::string::npos) ||
2198+
(symbols.find('?') != std::string::npos);
2199+
2200+
// Send rvn:get-calls-by-symbol packet with optional return address range and max_symbols
2201+
// Format: rvn:get-calls-by-symbol:<symbol>[:<start_ret_addr>:<end_ret_addr>[:<max_symbols>]]
2202+
std::string packet;
2203+
if (startReturnAddress != 0 || endReturnAddress != 0)
2204+
{
2205+
// Include return address range for server-side filtering
2206+
packet = fmt::format("rvn:get-calls-by-symbol:{}:{:x}:{:x}{}",
2207+
symbols,
2208+
startReturnAddress != 0 ? startReturnAddress : 0,
2209+
endReturnAddress != 0 ? endReturnAddress : 0xFFFFFFFFFFFFFFFF,
2210+
isWildcard ? ":50" : ""); // Limit wildcards to 50 symbols
2211+
}
2212+
else
2213+
{
2214+
// No filtering - query all calls
2215+
packet = fmt::format("rvn:get-calls-by-symbol:{}{}",
2216+
symbols,
2217+
isWildcard ? ":::50" : ""); // Format: symbol:::max_symbols
2218+
}
2219+
2220+
// Send with custom timeout
2221+
auto reply = m_rspConnector->TransmitAndReceive(
2222+
RspData(packet),
2223+
"ack_then_reply",
2224+
nullptr,
2225+
timeout // Use configured timeout
2226+
);
2227+
2228+
// Check for error response
2229+
if (reply.m_data[0] == 'E')
2230+
{
2231+
LogError("Failed to get calls for symbol: %s", symbols.c_str());
2232+
return events;
2233+
}
2234+
2235+
std::string jsonData = reply.AsString();
2236+
if (jsonData.empty() || jsonData == "[]")
2237+
{
2238+
return events;
2239+
}
2240+
2241+
// Manual JSON parsing (no external library dependency)
2242+
// Expected format: [{"transition_id":...,"function_name":"...","function_address":...,"call_instruction_address":...,"return_address":...,"thread_id":...}, ...]
2243+
2244+
size_t pos = 0;
2245+
while ((pos = jsonData.find('{', pos)) != std::string::npos)
2246+
{
2247+
size_t endPos = jsonData.find('}', pos);
2248+
if (endPos == std::string::npos)
2249+
break;
2250+
2251+
std::string objectStr = jsonData.substr(pos, endPos - pos + 1);
2252+
2253+
// Helper lambda to extract uint64 field
2254+
auto extractUInt64 = [&objectStr](const std::string& fieldName) -> uint64_t {
2255+
std::string searchStr = "\"" + fieldName + "\":";
2256+
size_t fieldPos = objectStr.find(searchStr);
2257+
if (fieldPos == std::string::npos)
2258+
return 0;
2259+
2260+
fieldPos += searchStr.length();
2261+
// Skip whitespace
2262+
while (fieldPos < objectStr.length() && std::isspace(objectStr[fieldPos]))
2263+
fieldPos++;
2264+
2265+
// Check for null
2266+
if (objectStr.substr(fieldPos, 4) == "null")
2267+
return 0;
2268+
2269+
// Extract number
2270+
size_t endNum = fieldPos;
2271+
while (endNum < objectStr.length() && (std::isdigit(objectStr[endNum]) || objectStr[endNum] == '-'))
2272+
endNum++;
2273+
2274+
if (endNum > fieldPos)
2275+
{
2276+
try {
2277+
return std::stoull(objectStr.substr(fieldPos, endNum - fieldPos));
2278+
} catch (...) {
2279+
return 0;
2280+
}
2281+
}
2282+
return 0;
2283+
};
2284+
2285+
// Helper lambda to extract string field
2286+
auto extractString = [&objectStr](const std::string& fieldName) -> std::string {
2287+
std::string searchStr = "\"" + fieldName + "\":\"";
2288+
size_t fieldPos = objectStr.find(searchStr);
2289+
if (fieldPos == std::string::npos)
2290+
return "";
2291+
2292+
fieldPos += searchStr.length();
2293+
size_t endQuote = objectStr.find('\"', fieldPos);
2294+
if (endQuote == std::string::npos)
2295+
return "";
2296+
2297+
return objectStr.substr(fieldPos, endQuote - fieldPos);
2298+
};
2299+
2300+
// Extract fields
2301+
uint64_t transition_id = extractUInt64("transition_id");
2302+
std::string function_name = extractString("function_name");
2303+
uint64_t function_address = extractUInt64("function_address");
2304+
uint64_t call_instruction_address = extractUInt64("call_instruction_address");
2305+
uint64_t return_address = extractUInt64("return_address");
2306+
uint64_t thread_id = extractUInt64("thread_id");
2307+
2308+
// Note: Return address filtering is done server-side for performance
2309+
2310+
// Create TTDCallEvent
2311+
TTDCallEvent event;
2312+
event.eventType = "Call";
2313+
event.threadId = static_cast<uint32_t>(thread_id);
2314+
event.uniqueThreadId = static_cast<uint32_t>(thread_id);
2315+
event.function = function_name;
2316+
event.functionAddress = function_address;
2317+
event.returnAddress = return_address;
2318+
event.returnValue = 0;
2319+
event.hasReturnValue = false;
2320+
event.timeStart = TTDPosition(transition_id, 0);
2321+
event.timeEnd = TTDPosition(transition_id, 0); // Same as timeStart for call events
2322+
2323+
events.push_back(event);
2324+
2325+
pos = endPos + 1;
2326+
}
2327+
2328+
LogInfo("Retrieved %zu call events for symbol: %s", events.size(), symbols.c_str());
2329+
2330+
// Apply client-side result limiting (Option 2 pattern)
2331+
if (maxResults > 0 && events.size() > maxResults)
2332+
{
2333+
LogWarn("Query returned %zu results, limiting to %llu", events.size(), maxResults);
2334+
events.resize(maxResults);
2335+
}
2336+
}
2337+
catch (const std::exception& e)
2338+
{
2339+
LogError("Exception while getting calls for symbol %s: %s", symbols.c_str(), e.what());
2340+
}
2341+
2342+
return events;
2343+
}
2344+
20852345
Ref<Settings> EsrevenAdapterType::GetAdapterSettings()
20862346
{
20872347
static Ref<Settings> settings = RegisterAdapterSettings();

core/adapters/esrevenadapter.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,11 @@ namespace BinaryNinjaDebugger
173173
bool AddHardwareWriteBreakpoint(uint64_t address);
174174
bool RemoveHardwareWriteBreakpoint(uint64_t address);
175175

176+
// TTD (Time Travel Debugging) support
177+
TTDPosition GetCurrentTTDPosition() override;
178+
bool SetTTDPosition(const TTDPosition& position) override;
179+
std::vector<TTDCallEvent> GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress = 0, uint64_t endReturnAddress = 0) override;
180+
176181
void GenerateDefaultAdapterSettings(BinaryView* data);
177182
Ref<Settings> GetAdapterSettings() override;
178183
};

core/adapters/rspconnector.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -227,14 +227,12 @@ void RspConnector::SendPayload(const RspData& data)
227227
this->SendRaw(RspData(packet));
228228
}
229229

230-
RspData RspConnector::ReceiveRspData()
230+
RspData RspConnector::ReceiveRspData(std::chrono::milliseconds timeoutDuration)
231231
{
232232
std::unique_lock lock(m_socketLock);
233233

234234
std::vector<char> buffer{};
235235
auto startTime = std::chrono::steady_clock::now();
236-
// TODO: We might wish to make this timeout configurable, but for now waiting 10 seconds I think is good enough
237-
const std::chrono::milliseconds timeoutDuration(10000);
238236

239237
while (true)
240238
{
@@ -250,7 +248,7 @@ RspData RspConnector::ReceiveRspData()
250248
auto elapsedTime = std::chrono::steady_clock::now() - startTime;
251249
if (elapsedTime > timeoutDuration)
252250
{
253-
LogWarn("ReceiveRspData timeout: failed to receive data within the timeout period");
251+
LogWarn("ReceiveRspData timeout: failed to receive data within %lldms", timeoutDuration.count());
254252
return {}; // Return an empty RspData object
255253
}
256254

@@ -303,7 +301,8 @@ RspData RspConnector::ReceiveRspData()
303301
}
304302

305303
RspData RspConnector::TransmitAndReceive(const RspData& data, const std::string& expect,
306-
std::function<void(const RspData& data)> asyncPacketHandler)
304+
std::function<void(const RspData& data)> asyncPacketHandler,
305+
std::chrono::milliseconds timeout)
307306
{
308307
std::unique_lock lock(m_socketLock);
309308

@@ -315,7 +314,7 @@ RspData RspConnector::TransmitAndReceive(const RspData& data, const std::string&
315314
reply = RspData("");
316315
else if ( expect == "ack_then_reply" ) {
317316
this->ExpectAck();
318-
reply = this->ReceiveRspData();
317+
reply = this->ReceiveRspData(timeout);
319318
}
320319
else if ( expect == "mixed_output_ack_then_reply" ) {
321320
bool ack_received = false;

core/adapters/rspconnector.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,10 @@ namespace BinaryNinjaDebugger
192192
void SendRaw(const RspData& data);
193193
void SendPayload(const RspData& data);
194194

195-
RspData ReceiveRspData();
195+
RspData ReceiveRspData(std::chrono::milliseconds timeout = std::chrono::milliseconds(10000));
196196
RspData TransmitAndReceive(const RspData& data, const std::string& expect = "ack_then_reply",
197-
std::function<void(const RspData& data)> asyncPacketHandler = nullptr);
197+
std::function<void(const RspData& data)> asyncPacketHandler = nullptr,
198+
std::chrono::milliseconds timeout = std::chrono::milliseconds(10000));
198199
int32_t HostFileIO(const RspData& data, RspData& output, int32_t& error);
199200

200201
std::string GetXml(const std::string& name);

0 commit comments

Comments
 (0)