Skip to content

Commit fdfba0e

Browse files
razainaraz4ina
andauthored
Implement GetThreadList and GetFramesOfThread for esReven adapter (#1005)
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. Co-authored-by: Tiana <tiana.razafindralambo@eshard.com>
1 parent 0e0dc2a commit fdfba0e

2 files changed

Lines changed: 304 additions & 23 deletions

File tree

core/adapters/esrevenadapter.cpp

Lines changed: 294 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -361,32 +361,302 @@ bool EsrevenAdapter::Quit()
361361

362362
std::vector<DebugThread> EsrevenAdapter::GetThreadList()
363363
{
364-
return {};
364+
// Return cached data if available
365+
if (m_threadCache.has_value())
366+
{
367+
std::vector<DebugThread> threads;
368+
for (const auto& cached : m_threadCache.value())
369+
{
370+
threads.emplace_back(cached.tid, cached.rip);
371+
}
372+
return threads;
373+
}
374+
375+
if (m_isTargetRunning || !m_rspConnector)
376+
return {};
377+
378+
// Use the custom rvn:list-threads packet
379+
auto response = m_rspConnector->TransmitAndReceive(RspData("rvn:list-threads"));
380+
std::string jsonStr = response.AsString();
381+
382+
// Check if we got a valid JSON response
383+
if (jsonStr.empty() || jsonStr[0] != '[')
384+
return {};
385+
386+
std::vector<ThreadFrameCache> cache;
387+
std::vector<DebugThread> threads;
388+
389+
// Parse JSON array of threads
390+
size_t pos = 0;
391+
while (pos < jsonStr.length())
392+
{
393+
// Find the start of a thread object
394+
size_t threadStart = jsonStr.find('{', pos);
395+
if (threadStart == std::string::npos)
396+
break;
397+
398+
// Find the end of the thread object (handles nested frames array)
399+
int braceCount = 0;
400+
size_t threadEnd = threadStart;
401+
for (size_t i = threadStart; i < jsonStr.length(); i++)
402+
{
403+
if (jsonStr[i] == '{')
404+
braceCount++;
405+
else if (jsonStr[i] == '}')
406+
{
407+
braceCount--;
408+
if (braceCount == 0)
409+
{
410+
threadEnd = i;
411+
break;
412+
}
413+
}
414+
}
415+
416+
if (threadEnd == threadStart)
417+
break;
418+
419+
std::string threadObj = jsonStr.substr(threadStart, threadEnd - threadStart + 1);
420+
421+
// Parse thread fields
422+
ThreadFrameCache threadData;
423+
threadData.tid = 0;
424+
threadData.rip = 0;
425+
426+
// Extract "tid"
427+
size_t tidPos = threadObj.find("\"tid\"");
428+
if (tidPos != std::string::npos)
429+
{
430+
size_t colonPos = threadObj.find(':', tidPos);
431+
if (colonPos != std::string::npos)
432+
{
433+
colonPos++;
434+
while (colonPos < threadObj.length() && std::isspace(threadObj[colonPos]))
435+
colonPos++;
436+
437+
size_t numEnd = colonPos;
438+
while (numEnd < threadObj.length() && std::isdigit(threadObj[numEnd]))
439+
numEnd++;
440+
441+
if (numEnd > colonPos)
442+
{
443+
std::string tidStr = threadObj.substr(colonPos, numEnd - colonPos);
444+
threadData.tid = std::stoull(tidStr);
445+
}
446+
}
447+
}
448+
449+
// Extract "rip"
450+
size_t ripPos = threadObj.find("\"rip\"");
451+
if (ripPos != std::string::npos)
452+
{
453+
size_t colonPos = threadObj.find(':', ripPos);
454+
if (colonPos != std::string::npos)
455+
{
456+
colonPos++;
457+
while (colonPos < threadObj.length() && std::isspace(threadObj[colonPos]))
458+
colonPos++;
459+
460+
size_t numEnd = colonPos;
461+
while (numEnd < threadObj.length() && std::isdigit(threadObj[numEnd]))
462+
numEnd++;
463+
464+
if (numEnd > colonPos)
465+
{
466+
std::string ripStr = threadObj.substr(colonPos, numEnd - colonPos);
467+
threadData.rip = std::stoull(ripStr);
468+
}
469+
}
470+
}
365471

366-
// if (m_isTargetRunning || !m_rspConnector)
367-
// return {};
368-
//
369-
// std::vector<DebugThread> threads{};
370-
//
371-
// auto reply = this->m_rspConnector->TransmitAndReceive(RspData("qfThreadInfo"));
372-
// while(reply.m_data[0] != 'l') {
373-
// if (reply.m_data[0] != 'm') {
374-
// LogWarn("RSP thread list error");
375-
// return threads;
376-
// }
377-
//
378-
// const auto shortened_string =
379-
// reply.AsString().substr(1);
380-
// const auto tids = RspConnector::Split(shortened_string, ",");
381-
// for ( const auto& tid : tids )
382-
// threads.emplace_back(std::stoi(tid, nullptr, 16));
383-
//
384-
// reply = this->m_rspConnector->TransmitAndReceive(RspData("qsThreadInfo"));
385-
// }
386-
//
387-
// return threads;
472+
// Extract "frames" array
473+
size_t framesPos = threadObj.find("\"frames\"");
474+
if (framesPos != std::string::npos)
475+
{
476+
size_t arrayStart = threadObj.find('[', framesPos);
477+
if (arrayStart != std::string::npos)
478+
{
479+
// Find the matching closing bracket
480+
int bracketCount = 0;
481+
size_t arrayEnd = arrayStart;
482+
for (size_t i = arrayStart; i < threadObj.length(); i++)
483+
{
484+
if (threadObj[i] == '[')
485+
bracketCount++;
486+
else if (threadObj[i] == ']')
487+
{
488+
bracketCount--;
489+
if (bracketCount == 0)
490+
{
491+
arrayEnd = i;
492+
break;
493+
}
494+
}
495+
}
496+
497+
if (arrayEnd > arrayStart)
498+
{
499+
std::string framesArray = threadObj.substr(arrayStart + 1, arrayEnd - arrayStart - 1);
500+
501+
// Parse individual frame objects
502+
size_t framePos = 0;
503+
while (framePos < framesArray.length())
504+
{
505+
size_t frameStart = framesArray.find('{', framePos);
506+
if (frameStart == std::string::npos)
507+
break;
508+
509+
int frameBraceCount = 0;
510+
size_t frameEnd = frameStart;
511+
for (size_t i = frameStart; i < framesArray.length(); i++)
512+
{
513+
if (framesArray[i] == '{')
514+
frameBraceCount++;
515+
else if (framesArray[i] == '}')
516+
{
517+
frameBraceCount--;
518+
if (frameBraceCount == 0)
519+
{
520+
frameEnd = i;
521+
break;
522+
}
523+
}
524+
}
525+
526+
if (frameEnd == frameStart)
527+
break;
528+
529+
std::string frameObj = framesArray.substr(frameStart, frameEnd - frameStart + 1);
530+
531+
// Parse frame fields
532+
DebugFrame frame;
533+
frame.m_index = 0;
534+
frame.m_pc = 0;
535+
frame.m_sp = 0;
536+
frame.m_fp = 0;
537+
frame.m_functionName = "";
538+
frame.m_functionStart = 0;
539+
frame.m_module = "<unknown>";
540+
541+
// Helper lambda to extract integer value
542+
auto extractInt = [](const std::string& obj, const std::string& key) -> uint64_t {
543+
size_t keyPos = obj.find("\"" + key + "\"");
544+
if (keyPos != std::string::npos)
545+
{
546+
size_t colonPos = obj.find(':', keyPos);
547+
if (colonPos != std::string::npos)
548+
{
549+
colonPos++;
550+
while (colonPos < obj.length() && std::isspace(obj[colonPos]))
551+
colonPos++;
552+
553+
// Check for null
554+
if (obj.substr(colonPos, 4) == "null")
555+
return 0;
556+
557+
size_t numEnd = colonPos;
558+
while (numEnd < obj.length() && std::isdigit(obj[numEnd]))
559+
numEnd++;
560+
561+
if (numEnd > colonPos)
562+
{
563+
std::string numStr = obj.substr(colonPos, numEnd - colonPos);
564+
return std::stoull(numStr);
565+
}
566+
}
567+
}
568+
return 0;
569+
};
570+
571+
// Helper lambda to extract string value
572+
auto extractString = [](const std::string& obj, const std::string& key) -> std::string {
573+
size_t keyPos = obj.find("\"" + key + "\"");
574+
if (keyPos != std::string::npos)
575+
{
576+
size_t colonPos = obj.find(':', keyPos);
577+
if (colonPos != std::string::npos)
578+
{
579+
size_t quoteStart = obj.find('"', colonPos);
580+
if (quoteStart != std::string::npos)
581+
{
582+
size_t quoteEnd = obj.find('"', quoteStart + 1);
583+
if (quoteEnd != std::string::npos)
584+
{
585+
return obj.substr(quoteStart + 1, quoteEnd - quoteStart - 1);
586+
}
587+
}
588+
else
589+
{
590+
// Check for null
591+
colonPos++;
592+
while (colonPos < obj.length() && std::isspace(obj[colonPos]))
593+
colonPos++;
594+
if (obj.substr(colonPos, 4) == "null")
595+
return "";
596+
}
597+
}
598+
}
599+
return "";
600+
};
601+
602+
frame.m_index = extractInt(frameObj, "index");
603+
frame.m_pc = extractInt(frameObj, "pc");
604+
frame.m_sp = extractInt(frameObj, "sp");
605+
frame.m_fp = extractInt(frameObj, "fp");
606+
frame.m_functionStart = extractInt(frameObj, "function_start");
607+
608+
std::string funcName = extractString(frameObj, "function_name");
609+
if (!funcName.empty())
610+
frame.m_functionName = funcName;
611+
612+
std::string moduleName = extractString(frameObj, "module");
613+
if (!moduleName.empty())
614+
frame.m_module = moduleName;
615+
616+
threadData.frames.push_back(frame);
617+
framePos = frameEnd + 1;
618+
}
619+
}
620+
}
621+
}
622+
623+
cache.push_back(threadData);
624+
threads.emplace_back(threadData.tid, threadData.rip);
625+
pos = threadEnd + 1;
626+
}
627+
628+
// Cache the results
629+
m_threadCache = cache;
630+
631+
return threads;
632+
}
633+
634+
635+
std::vector<DebugFrame> EsrevenAdapter::GetFramesOfThread(std::uint32_t tid)
636+
{
637+
// If cache is empty, call GetThreadList() to populate it
638+
if (!m_threadCache.has_value())
639+
{
640+
GetThreadList();
641+
}
642+
643+
// Search for the thread in the cache
644+
if (m_threadCache.has_value())
645+
{
646+
for (const auto& threadData : m_threadCache.value())
647+
{
648+
if (threadData.tid == tid)
649+
{
650+
return threadData.frames;
651+
}
652+
}
653+
}
654+
655+
// Thread not found, return empty vector
656+
return {};
388657
}
389658

659+
390660
DebugThread EsrevenAdapter::GetActiveThread() const
391661
{
392662
// TODO: GetInstructionOffset() should really be const, but changing it requires changes in lots of files,
@@ -1719,6 +1989,7 @@ void EsrevenAdapter::InvalidateCache()
17191989
{
17201990
m_regCache.reset();
17211991
m_moduleCache.reset();
1992+
m_threadCache.reset();
17221993
}
17231994

17241995

core/adapters/esrevenadapter.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ namespace BinaryNinjaDebugger
6161

6262
std::optional<std::vector<DebugModule>> m_moduleCache{};
6363

64+
// Cache for thread list with frames (from rvn:list-threads)
65+
struct ThreadFrameCache
66+
{
67+
std::uint32_t tid;
68+
std::uintptr_t rip;
69+
std::vector<DebugFrame> frames;
70+
};
71+
std::optional<std::vector<ThreadFrameCache>> m_threadCache{};
72+
6473
std::uint32_t m_lastActiveThreadId{};
6574
std::uint32_t m_processPid{};
6675
uint8_t m_exitCode{};
@@ -113,6 +122,7 @@ namespace BinaryNinjaDebugger
113122
std::uint32_t GetActiveThreadId() const override;
114123
bool SetActiveThread(const DebugThread& thread) override;
115124
bool SetActiveThreadId(std::uint32_t tid) override;
125+
std::vector<DebugFrame> GetFramesOfThread(std::uint32_t tid) override;
116126

117127
DebugBreakpoint AddBreakpoint(std::uintptr_t address, unsigned long breakpoint_type = 0) override;
118128

0 commit comments

Comments
 (0)