diff --git a/core/adapters/esrevenadapter.cpp b/core/adapters/esrevenadapter.cpp index c0f51a85..6a2a2107 100644 --- a/core/adapters/esrevenadapter.cpp +++ b/core/adapters/esrevenadapter.cpp @@ -1609,7 +1609,12 @@ bool EsrevenAdapter::SupportFeature(DebugAdapterCapacity feature) case DebugAdapterSupportThreads: return true; case DebugAdapterSupportTTD: - return m_canReverseContinue && m_canReverseStep; + { + bool ttdSupported = m_canReverseContinue && m_canReverseStep; + LogInfo("SupportFeature(DebugAdapterSupportTTD): m_canReverseContinue=%d, m_canReverseStep=%d, returning %d", + m_canReverseContinue, m_canReverseStep, ttdSupported); + return ttdSupported; + } default: return false; } @@ -1984,10 +1989,265 @@ Ref EsrevenAdapterType::RegisterAdapterSettings() "readOnly" : false })"); + settings->RegisterSetting("ttd.queryTimeout", + R"JSON({ + "title" : "TTD Query Timeout", + "type" : "number", + "default" : 60000, + "minValue" : 5000, + "maxValue" : 600000, + "description" : "Timeout in milliseconds for TTD query operations (calls, memory, events). Increase for large wildcard queries. Default: 60000ms (60s)", + "readOnly" : false + })JSON"); + + settings->RegisterSetting("ttd.maxCallsQueryResults", + R"JSON({ + "title" : "Max Calls Query Results", + "type" : "number", + "default" : 10000, + "minValue" : 0, + "maxValue" : 18446744073709551615, + "description" : "Maximum number of results to return from TTD Calls queries. Set to 0 for no limit.", + "readOnly" : false + })JSON"); + return settings; } +TTDPosition EsrevenAdapter::GetCurrentTTDPosition() +{ + if (!m_rspConnector) + return TTDPosition(); + + auto reply = m_rspConnector->TransmitAndReceive( + RspData("rvn:get-current-transition"), "ack_then_reply", nullptr, + std::chrono::milliseconds(5000)); + + std::string json = reply.AsString(); + + if (json.empty() || json[0] == 'E') + return TTDPosition(); + + // Server returns JSON null when no transition is available + if (json == "null" || json.empty()) + return TTDPosition(); + + // Manual JSON extraction — no external library, same pattern as GetTTDCallsForSymbols + std::string objectStr = json; + auto extractUInt64 = [&objectStr](const std::string& fieldName) -> uint64_t { + std::string searchStr = "\"" + fieldName + "\":"; + size_t pos = objectStr.find(searchStr); + if (pos == std::string::npos) + return 0; + pos += searchStr.length(); + while (pos < objectStr.length() && std::isspace(objectStr[pos])) + pos++; + if (objectStr.substr(pos, 4) == "null") + return 0; + size_t end = pos; + while (end < objectStr.length() && (std::isdigit(objectStr[end]) || objectStr[end] == '-')) + end++; + if (end > pos) + { + try { return std::stoull(objectStr.substr(pos, end - pos)); } catch (...) {} + } + return 0; + }; + + uint64_t transitionId = extractUInt64("transition_id"); + // TTDPosition: sequence = transition_id, step = 0 (REVEN has no sub-step granularity) + return TTDPosition(transitionId, 0); +} + + +bool EsrevenAdapter::SetTTDPosition(const TTDPosition& position) +{ + if (!m_rspConnector) + return false; + + DebuggerEvent dbgevt; + dbgevt.type = ResumeEventType; + PostDebuggerEvent(dbgevt); + + InvalidateCache(); + + auto stopReason = GenericGo(fmt::format("rvn:set-current-transition:{}", position.sequence)); + return stopReason != InternalError; +} + + +std::vector EsrevenAdapter::GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress, uint64_t endReturnAddress) +{ + std::vector events; + + if (symbols.empty()) + { + LogError("No symbols provided for TTD calls query"); + return events; + } + + // Get settings + auto adapterSettings = GetAdapterSettings(); + BNSettingsScope scope = SettingsResourceScope; + auto timeoutMs = adapterSettings->Get("ttd.queryTimeout", GetData(), &scope); + auto maxResults = adapterSettings->Get("ttd.maxCallsQueryResults", GetData(), &scope); + auto timeout = std::chrono::milliseconds(timeoutMs); + + LogInfo("GetTTDCallsForSymbols: symbols='%s', timeout=%lldms, maxResults=%llu", + symbols.c_str(), timeoutMs, maxResults); + + try + { + // Detect wildcard patterns to add max_symbols limit + bool isWildcard = (symbols.find('*') != std::string::npos) || + (symbols.find('?') != std::string::npos); + + // Send rvn:get-calls-by-symbol packet with optional return address range and max_symbols + // Format: rvn:get-calls-by-symbol:[::[:]] + std::string packet; + if (startReturnAddress != 0 || endReturnAddress != 0) + { + // Include return address range for server-side filtering + packet = fmt::format("rvn:get-calls-by-symbol:{}:{:x}:{:x}{}", + symbols, + startReturnAddress != 0 ? startReturnAddress : 0, + endReturnAddress != 0 ? endReturnAddress : 0xFFFFFFFFFFFFFFFF, + isWildcard ? ":50" : ""); // Limit wildcards to 50 symbols + } + else + { + // No filtering - query all calls + packet = fmt::format("rvn:get-calls-by-symbol:{}{}", + symbols, + isWildcard ? ":::50" : ""); // Format: symbol:::max_symbols + } + + // Send with custom timeout + auto reply = m_rspConnector->TransmitAndReceive( + RspData(packet), + "ack_then_reply", + nullptr, + timeout // Use configured timeout + ); + + // Check for error response + if (reply.m_data[0] == 'E') + { + LogError("Failed to get calls for symbol: %s", symbols.c_str()); + return events; + } + + std::string jsonData = reply.AsString(); + if (jsonData.empty() || jsonData == "[]") + { + return events; + } + + // Manual JSON parsing (no external library dependency) + // Expected format: [{"transition_id":...,"function_name":"...","function_address":...,"call_instruction_address":...,"return_address":...,"thread_id":...}, ...] + + size_t pos = 0; + while ((pos = jsonData.find('{', pos)) != std::string::npos) + { + size_t endPos = jsonData.find('}', pos); + if (endPos == std::string::npos) + break; + + std::string objectStr = jsonData.substr(pos, endPos - pos + 1); + + // Helper lambda to extract uint64 field + auto extractUInt64 = [&objectStr](const std::string& fieldName) -> uint64_t { + std::string searchStr = "\"" + fieldName + "\":"; + size_t fieldPos = objectStr.find(searchStr); + if (fieldPos == std::string::npos) + return 0; + + fieldPos += searchStr.length(); + // Skip whitespace + while (fieldPos < objectStr.length() && std::isspace(objectStr[fieldPos])) + fieldPos++; + + // Check for null + if (objectStr.substr(fieldPos, 4) == "null") + return 0; + + // Extract number + size_t endNum = fieldPos; + while (endNum < objectStr.length() && (std::isdigit(objectStr[endNum]) || objectStr[endNum] == '-')) + endNum++; + + if (endNum > fieldPos) + { + try { + return std::stoull(objectStr.substr(fieldPos, endNum - fieldPos)); + } catch (...) { + return 0; + } + } + return 0; + }; + + // Helper lambda to extract string field + auto extractString = [&objectStr](const std::string& fieldName) -> std::string { + std::string searchStr = "\"" + fieldName + "\":\""; + size_t fieldPos = objectStr.find(searchStr); + if (fieldPos == std::string::npos) + return ""; + + fieldPos += searchStr.length(); + size_t endQuote = objectStr.find('\"', fieldPos); + if (endQuote == std::string::npos) + return ""; + + return objectStr.substr(fieldPos, endQuote - fieldPos); + }; + + // Extract fields + uint64_t transition_id = extractUInt64("transition_id"); + std::string function_name = extractString("function_name"); + uint64_t function_address = extractUInt64("function_address"); + uint64_t call_instruction_address = extractUInt64("call_instruction_address"); + uint64_t return_address = extractUInt64("return_address"); + uint64_t thread_id = extractUInt64("thread_id"); + + // Note: Return address filtering is done server-side for performance + + // Create TTDCallEvent + TTDCallEvent event; + event.eventType = "Call"; + event.threadId = static_cast(thread_id); + event.uniqueThreadId = static_cast(thread_id); + event.function = function_name; + event.functionAddress = function_address; + event.returnAddress = return_address; + event.returnValue = 0; + event.hasReturnValue = false; + event.timeStart = TTDPosition(transition_id, 0); + event.timeEnd = TTDPosition(transition_id, 0); // Same as timeStart for call events + + events.push_back(event); + + pos = endPos + 1; + } + + LogInfo("Retrieved %zu call events for symbol: %s", events.size(), symbols.c_str()); + + // Apply client-side result limiting (Option 2 pattern) + if (maxResults > 0 && events.size() > maxResults) + { + LogWarn("Query returned %zu results, limiting to %llu", events.size(), maxResults); + events.resize(maxResults); + } + } + catch (const std::exception& e) + { + LogError("Exception while getting calls for symbol %s: %s", symbols.c_str(), e.what()); + } + + return events; +} + Ref EsrevenAdapterType::GetAdapterSettings() { static Ref settings = RegisterAdapterSettings(); diff --git a/core/adapters/esrevenadapter.h b/core/adapters/esrevenadapter.h index b702bfe5..7fb87fcd 100644 --- a/core/adapters/esrevenadapter.h +++ b/core/adapters/esrevenadapter.h @@ -169,6 +169,11 @@ namespace BinaryNinjaDebugger bool AddHardwareWriteBreakpoint(uint64_t address); bool RemoveHardwareWriteBreakpoint(uint64_t address); + // TTD (Time Travel Debugging) support + TTDPosition GetCurrentTTDPosition() override; + bool SetTTDPosition(const TTDPosition& position) override; + std::vector GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress = 0, uint64_t endReturnAddress = 0) override; + void GenerateDefaultAdapterSettings(BinaryView* data); Ref GetAdapterSettings() override; }; diff --git a/core/adapters/rspconnector.cpp b/core/adapters/rspconnector.cpp index 5a39d6a1..15b80af7 100644 --- a/core/adapters/rspconnector.cpp +++ b/core/adapters/rspconnector.cpp @@ -227,14 +227,12 @@ void RspConnector::SendPayload(const RspData& data) this->SendRaw(RspData(packet)); } -RspData RspConnector::ReceiveRspData() +RspData RspConnector::ReceiveRspData(std::chrono::milliseconds timeoutDuration) { std::unique_lock lock(m_socketLock); std::vector buffer{}; auto startTime = std::chrono::steady_clock::now(); - // TODO: We might wish to make this timeout configurable, but for now waiting 10 seconds I think is good enough - const std::chrono::milliseconds timeoutDuration(10000); while (true) { @@ -250,7 +248,7 @@ RspData RspConnector::ReceiveRspData() auto elapsedTime = std::chrono::steady_clock::now() - startTime; if (elapsedTime > timeoutDuration) { - LogWarn("ReceiveRspData timeout: failed to receive data within the timeout period"); + LogWarn("ReceiveRspData timeout: failed to receive data within %lldms", timeoutDuration.count()); return {}; // Return an empty RspData object } @@ -303,7 +301,8 @@ RspData RspConnector::ReceiveRspData() } RspData RspConnector::TransmitAndReceive(const RspData& data, const std::string& expect, - std::function asyncPacketHandler) + std::function asyncPacketHandler, + std::chrono::milliseconds timeout) { std::unique_lock lock(m_socketLock); @@ -315,7 +314,7 @@ RspData RspConnector::TransmitAndReceive(const RspData& data, const std::string& reply = RspData(""); else if ( expect == "ack_then_reply" ) { this->ExpectAck(); - reply = this->ReceiveRspData(); + reply = this->ReceiveRspData(timeout); } else if ( expect == "mixed_output_ack_then_reply" ) { bool ack_received = false; diff --git a/core/adapters/rspconnector.h b/core/adapters/rspconnector.h index b38c6d11..2c9c7bb1 100644 --- a/core/adapters/rspconnector.h +++ b/core/adapters/rspconnector.h @@ -192,9 +192,10 @@ namespace BinaryNinjaDebugger void SendRaw(const RspData& data); void SendPayload(const RspData& data); - RspData ReceiveRspData(); + RspData ReceiveRspData(std::chrono::milliseconds timeout = std::chrono::milliseconds(10000)); RspData TransmitAndReceive(const RspData& data, const std::string& expect = "ack_then_reply", - std::function asyncPacketHandler = nullptr); + std::function asyncPacketHandler = nullptr, + std::chrono::milliseconds timeout = std::chrono::milliseconds(10000)); int32_t HostFileIO(const RspData& data, RspData& output, int32_t& error); std::string GetXml(const std::string& name);