From 06cb85a9a8c09ce87f9f25e0eff23844036aedb8 Mon Sep 17 00:00:00 2001 From: Tiana Date: Mon, 16 Feb 2026 16:48:30 +0100 Subject: [PATCH] Implement GetThreadList and GetFramesOfThread for esReven adapter Use the custom rvn:list-threads packet to retrieve the list of threads and their call stack from REVEN. Parse the JSON response to populate thread and frame data, and cache the result to avoid redundant round trips. GetFramesOfThread serves stack frames from that same cache. --- core/adapters/esrevenadapter.cpp | 317 ++++++++++++++++++++++++++++--- core/adapters/esrevenadapter.h | 10 + 2 files changed, 304 insertions(+), 23 deletions(-) diff --git a/core/adapters/esrevenadapter.cpp b/core/adapters/esrevenadapter.cpp index c0f51a85..fdeccbb0 100644 --- a/core/adapters/esrevenadapter.cpp +++ b/core/adapters/esrevenadapter.cpp @@ -342,32 +342,302 @@ bool EsrevenAdapter::Quit() std::vector EsrevenAdapter::GetThreadList() { - return {}; + // Return cached data if available + if (m_threadCache.has_value()) + { + std::vector threads; + for (const auto& cached : m_threadCache.value()) + { + threads.emplace_back(cached.tid, cached.rip); + } + return threads; + } + + if (m_isTargetRunning || !m_rspConnector) + return {}; + + // Use the custom rvn:list-threads packet + auto response = m_rspConnector->TransmitAndReceive(RspData("rvn:list-threads")); + std::string jsonStr = response.AsString(); + + // Check if we got a valid JSON response + if (jsonStr.empty() || jsonStr[0] != '[') + return {}; + + std::vector cache; + std::vector threads; + + // Parse JSON array of threads + size_t pos = 0; + while (pos < jsonStr.length()) + { + // Find the start of a thread object + size_t threadStart = jsonStr.find('{', pos); + if (threadStart == std::string::npos) + break; + + // Find the end of the thread object (handles nested frames array) + int braceCount = 0; + size_t threadEnd = threadStart; + for (size_t i = threadStart; i < jsonStr.length(); i++) + { + if (jsonStr[i] == '{') + braceCount++; + else if (jsonStr[i] == '}') + { + braceCount--; + if (braceCount == 0) + { + threadEnd = i; + break; + } + } + } + + if (threadEnd == threadStart) + break; + + std::string threadObj = jsonStr.substr(threadStart, threadEnd - threadStart + 1); + + // Parse thread fields + ThreadFrameCache threadData; + threadData.tid = 0; + threadData.rip = 0; + + // Extract "tid" + size_t tidPos = threadObj.find("\"tid\""); + if (tidPos != std::string::npos) + { + size_t colonPos = threadObj.find(':', tidPos); + if (colonPos != std::string::npos) + { + colonPos++; + while (colonPos < threadObj.length() && std::isspace(threadObj[colonPos])) + colonPos++; + + size_t numEnd = colonPos; + while (numEnd < threadObj.length() && std::isdigit(threadObj[numEnd])) + numEnd++; + + if (numEnd > colonPos) + { + std::string tidStr = threadObj.substr(colonPos, numEnd - colonPos); + threadData.tid = std::stoull(tidStr); + } + } + } + + // Extract "rip" + size_t ripPos = threadObj.find("\"rip\""); + if (ripPos != std::string::npos) + { + size_t colonPos = threadObj.find(':', ripPos); + if (colonPos != std::string::npos) + { + colonPos++; + while (colonPos < threadObj.length() && std::isspace(threadObj[colonPos])) + colonPos++; + + size_t numEnd = colonPos; + while (numEnd < threadObj.length() && std::isdigit(threadObj[numEnd])) + numEnd++; + + if (numEnd > colonPos) + { + std::string ripStr = threadObj.substr(colonPos, numEnd - colonPos); + threadData.rip = std::stoull(ripStr); + } + } + } - // if (m_isTargetRunning || !m_rspConnector) - // return {}; - // - // std::vector threads{}; - // - // auto reply = this->m_rspConnector->TransmitAndReceive(RspData("qfThreadInfo")); - // while(reply.m_data[0] != 'l') { - // if (reply.m_data[0] != 'm') { - // LogWarn("RSP thread list error"); - // return threads; - // } - // - // const auto shortened_string = - // reply.AsString().substr(1); - // const auto tids = RspConnector::Split(shortened_string, ","); - // for ( const auto& tid : tids ) - // threads.emplace_back(std::stoi(tid, nullptr, 16)); - // - // reply = this->m_rspConnector->TransmitAndReceive(RspData("qsThreadInfo")); - // } - // - // return threads; + // Extract "frames" array + size_t framesPos = threadObj.find("\"frames\""); + if (framesPos != std::string::npos) + { + size_t arrayStart = threadObj.find('[', framesPos); + if (arrayStart != std::string::npos) + { + // Find the matching closing bracket + int bracketCount = 0; + size_t arrayEnd = arrayStart; + for (size_t i = arrayStart; i < threadObj.length(); i++) + { + if (threadObj[i] == '[') + bracketCount++; + else if (threadObj[i] == ']') + { + bracketCount--; + if (bracketCount == 0) + { + arrayEnd = i; + break; + } + } + } + + if (arrayEnd > arrayStart) + { + std::string framesArray = threadObj.substr(arrayStart + 1, arrayEnd - arrayStart - 1); + + // Parse individual frame objects + size_t framePos = 0; + while (framePos < framesArray.length()) + { + size_t frameStart = framesArray.find('{', framePos); + if (frameStart == std::string::npos) + break; + + int frameBraceCount = 0; + size_t frameEnd = frameStart; + for (size_t i = frameStart; i < framesArray.length(); i++) + { + if (framesArray[i] == '{') + frameBraceCount++; + else if (framesArray[i] == '}') + { + frameBraceCount--; + if (frameBraceCount == 0) + { + frameEnd = i; + break; + } + } + } + + if (frameEnd == frameStart) + break; + + std::string frameObj = framesArray.substr(frameStart, frameEnd - frameStart + 1); + + // Parse frame fields + DebugFrame frame; + frame.m_index = 0; + frame.m_pc = 0; + frame.m_sp = 0; + frame.m_fp = 0; + frame.m_functionName = ""; + frame.m_functionStart = 0; + frame.m_module = ""; + + // Helper lambda to extract integer value + auto extractInt = [](const std::string& obj, const std::string& key) -> uint64_t { + size_t keyPos = obj.find("\"" + key + "\""); + if (keyPos != std::string::npos) + { + size_t colonPos = obj.find(':', keyPos); + if (colonPos != std::string::npos) + { + colonPos++; + while (colonPos < obj.length() && std::isspace(obj[colonPos])) + colonPos++; + + // Check for null + if (obj.substr(colonPos, 4) == "null") + return 0; + + size_t numEnd = colonPos; + while (numEnd < obj.length() && std::isdigit(obj[numEnd])) + numEnd++; + + if (numEnd > colonPos) + { + std::string numStr = obj.substr(colonPos, numEnd - colonPos); + return std::stoull(numStr); + } + } + } + return 0; + }; + + // Helper lambda to extract string value + auto extractString = [](const std::string& obj, const std::string& key) -> std::string { + size_t keyPos = obj.find("\"" + key + "\""); + if (keyPos != std::string::npos) + { + size_t colonPos = obj.find(':', keyPos); + if (colonPos != std::string::npos) + { + size_t quoteStart = obj.find('"', colonPos); + if (quoteStart != std::string::npos) + { + size_t quoteEnd = obj.find('"', quoteStart + 1); + if (quoteEnd != std::string::npos) + { + return obj.substr(quoteStart + 1, quoteEnd - quoteStart - 1); + } + } + else + { + // Check for null + colonPos++; + while (colonPos < obj.length() && std::isspace(obj[colonPos])) + colonPos++; + if (obj.substr(colonPos, 4) == "null") + return ""; + } + } + } + return ""; + }; + + frame.m_index = extractInt(frameObj, "index"); + frame.m_pc = extractInt(frameObj, "pc"); + frame.m_sp = extractInt(frameObj, "sp"); + frame.m_fp = extractInt(frameObj, "fp"); + frame.m_functionStart = extractInt(frameObj, "function_start"); + + std::string funcName = extractString(frameObj, "function_name"); + if (!funcName.empty()) + frame.m_functionName = funcName; + + std::string moduleName = extractString(frameObj, "module"); + if (!moduleName.empty()) + frame.m_module = moduleName; + + threadData.frames.push_back(frame); + framePos = frameEnd + 1; + } + } + } + } + + cache.push_back(threadData); + threads.emplace_back(threadData.tid, threadData.rip); + pos = threadEnd + 1; + } + + // Cache the results + m_threadCache = cache; + + return threads; +} + + +std::vector EsrevenAdapter::GetFramesOfThread(std::uint32_t tid) +{ + // If cache is empty, call GetThreadList() to populate it + if (!m_threadCache.has_value()) + { + GetThreadList(); + } + + // Search for the thread in the cache + if (m_threadCache.has_value()) + { + for (const auto& threadData : m_threadCache.value()) + { + if (threadData.tid == tid) + { + return threadData.frames; + } + } + } + + // Thread not found, return empty vector + return {}; } + DebugThread EsrevenAdapter::GetActiveThread() const { // TODO: GetInstructionOffset() should really be const, but changing it requires changes in lots of files, @@ -1620,6 +1890,7 @@ void EsrevenAdapter::InvalidateCache() { m_regCache.reset(); m_moduleCache.reset(); + m_threadCache.reset(); } diff --git a/core/adapters/esrevenadapter.h b/core/adapters/esrevenadapter.h index b702bfe5..6f2981e0 100644 --- a/core/adapters/esrevenadapter.h +++ b/core/adapters/esrevenadapter.h @@ -61,6 +61,15 @@ namespace BinaryNinjaDebugger std::optional> m_moduleCache{}; + // Cache for thread list with frames (from rvn:list-threads) + struct ThreadFrameCache + { + std::uint32_t tid; + std::uintptr_t rip; + std::vector frames; + }; + std::optional> m_threadCache{}; + std::uint32_t m_lastActiveThreadId{}; std::uint32_t m_processPid{}; uint8_t m_exitCode{}; @@ -109,6 +118,7 @@ namespace BinaryNinjaDebugger std::uint32_t GetActiveThreadId() const override; bool SetActiveThread(const DebugThread& thread) override; bool SetActiveThreadId(std::uint32_t tid) override; + std::vector GetFramesOfThread(std::uint32_t tid) override; DebugBreakpoint AddBreakpoint(std::uintptr_t address, unsigned long breakpoint_type = 0) override;