diff --git a/core/adapters/esrevenadapter.cpp b/core/adapters/esrevenadapter.cpp index 922a8ec5..878908dc 100644 --- a/core/adapters/esrevenadapter.cpp +++ b/core/adapters/esrevenadapter.cpp @@ -1986,10 +1986,231 @@ 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"); + + settings->RegisterSetting("ttd.maxSymbolsLimit", + R"JSON({ + "title" : "Max Symbols Wildcard Limit", + "type" : "number", + "default" : 50, + "minValue" : 0, + "maxValue" : 10000, + "description" : "Maximum number of symbols to search when using wildcard patterns (e.g. 'kernel32!C*'). Set to 0 for no limit (searches all matching symbols). Increase for broader wildcard queries at the cost of performance.", + "readOnly" : false + })JSON"); + return settings; } +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 maxSymbols = adapterSettings->Get("ttd.maxSymbolsLimit", GetData(), &scope); + auto timeout = std::chrono::milliseconds(timeoutMs); + + LogInfo("GetTTDCallsForSymbols: symbols='%s', timeout=%lldms, maxResults=%llu, maxSymbols=%llu", + symbols.c_str(), timeoutMs, maxResults, maxSymbols); + + 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 + // maxSymbols == 0 means no limit: omit the suffix entirely + packet = fmt::format("rvn:get-calls-by-symbol:{}:{:x}:{:x}{}", + symbols, + startReturnAddress != 0 ? startReturnAddress : 0, + endReturnAddress != 0 ? endReturnAddress : 0xFFFFFFFFFFFFFFFF, + (isWildcard && maxSymbols > 0) ? fmt::format(":{}", maxSymbols) : ""); + } + else + { + // No filtering - query all calls + // maxSymbols == 0 means no limit: omit :::N so the server searches all symbols + packet = fmt::format("rvn:get-calls-by-symbol:{}{}", + symbols, + (isWildcard && maxSymbols > 0) ? fmt::format(":::{}", maxSymbols) : ""); + } + + // 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 colonPos = objectStr.find(':', fieldPos); + if (colonPos == std::string::npos) + return ""; + + // Skip whitespace after colon (handles both `":"` and `": "`) + size_t quotePos = colonPos + 1; + while (quotePos < objectStr.length() && std::isspace(objectStr[quotePos])) + quotePos++; + + if (quotePos >= objectStr.length() || objectStr[quotePos] != '"') + return ""; + + quotePos++; // skip opening quote + size_t endQuote = objectStr.find('\"', quotePos); + if (endQuote == std::string::npos) + return ""; + + return objectStr.substr(quotePos, endQuote - quotePos); + }; + + // 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..2b9dc34f 100644 --- a/core/adapters/esrevenadapter.h +++ b/core/adapters/esrevenadapter.h @@ -126,6 +126,9 @@ namespace BinaryNinjaDebugger std::string GetRemoteFile(const std::string& path); std::vector GetModuleList() override; + // TTD Calls support + std::vector GetTTDCallsForSymbols(const std::string& symbols, uint64_t startReturnAddress = 0, uint64_t endReturnAddress = 0) override; + std::string GetTargetArchitecture() override; DebugStopReason StopReason() override;