[lldb/Plugins] Add support of multiple ScriptedThreads in a ScriptedProcess

This patch adds support of multiple Scripted Threads in a ScriptedProcess.

This is done by fetching the Scripted Threads info dictionary at every
ScriptedProcess::DoUpdateThreadList and iterate over each element to
create a new ScriptedThread using the object instance, if it was not
already available.

This patch also adds the ability to pass a pointer of a script interpreter
object instance to initialize a ScriptedInterface instead of having to call
the script object initializer in the ScriptedInterface constructor.

This is used to instantiate the ScriptedThreadInterface from the
ScriptedThread constructor, to be able to perform call on that script
interpreter object instance.

Finally, the patch also updates the scripted process test to check for
multiple threads.

rdar://84507704

Differential Revision: https://reviews.llvm.org/D117071

Signed-off-by: Med Ismail Bennani <medismail.bennani@gmail.com>
This commit is contained in:
Med Ismail Bennani 2022-01-18 12:45:57 +01:00
parent 1b86344fa8
commit d3e0f7e150
16 changed files with 175 additions and 72 deletions

View File

@ -70,7 +70,7 @@ class ScriptedProcess:
tid (int): Thread ID to look for in the scripted process.
Returns:
Dict: The thread represented as a dictionary, withr the
Dict: The thread represented as a dictionary, with the
tid thread ID. None if tid doesn't match any of the scripted
process threads.
"""
@ -212,11 +212,12 @@ class ScriptedThread:
self.target = None
self.process = None
self.args = None
if isinstance(process, lldb.SBProcess) and process.IsValid():
self.process = process
self.target = process.GetTarget()
if isinstance(process, ScriptedProcess):
self.target = process.target
self.process = self.target.GetProcess()
self.id = None
self.idx = None
self.name = None
self.queue = None
self.state = None

View File

@ -27,7 +27,8 @@ public:
virtual StructuredData::GenericSP
CreatePluginObject(llvm::StringRef class_name, ExecutionContext &exe_ctx,
StructuredData::DictionarySP args_sp) = 0;
StructuredData::DictionarySP args_sp,
StructuredData::Generic *script_obj = nullptr) = 0;
template <typename Ret>
Ret ErrorWithMessage(llvm::StringRef caller_name, llvm::StringRef error_msg,

View File

@ -23,7 +23,8 @@ class ScriptedProcessInterface : virtual public ScriptedInterface {
public:
StructuredData::GenericSP
CreatePluginObject(llvm::StringRef class_name, ExecutionContext &exe_ctx,
StructuredData::DictionarySP args_sp) override {
StructuredData::DictionarySP args_sp,
StructuredData::Generic *script_obj = nullptr) override {
return nullptr;
}
@ -77,7 +78,8 @@ class ScriptedThreadInterface : virtual public ScriptedInterface {
public:
StructuredData::GenericSP
CreatePluginObject(llvm::StringRef class_name, ExecutionContext &exe_ctx,
StructuredData::DictionarySP args_sp) override {
StructuredData::DictionarySP args_sp,
StructuredData::Generic *script_obj = nullptr) override {
return nullptr;
}

View File

@ -164,9 +164,6 @@ Status ScriptedProcess::DoLaunch(Module *exe_module,
SetPrivateState(eStateStopped);
UpdateThreadListIfNeeded();
GetThreadList();
return {};
}
@ -304,19 +301,55 @@ bool ScriptedProcess::DoUpdateThreadList(ThreadList &old_thread_list,
.str(),
error);
lldb::ThreadSP thread_sp;
thread_sp = std::make_shared<ScriptedThread>(*this, error);
StructuredData::DictionarySP thread_info_sp = GetInterface().GetThreadsInfo();
if (!thread_sp || error.Fail())
return GetInterface().ErrorWithMessage<bool>(LLVM_PRETTY_FUNCTION,
error.AsCString(), error);
RegisterContextSP reg_ctx_sp = thread_sp->GetRegisterContext();
if (!reg_ctx_sp)
if (!thread_info_sp)
return GetInterface().ErrorWithMessage<bool>(
LLVM_PRETTY_FUNCTION, "Invalid Register Context", error);
LLVM_PRETTY_FUNCTION,
"Couldn't fetch thread list from Scripted Process.", error);
new_thread_list.AddThread(thread_sp);
auto create_scripted_thread =
[this, &old_thread_list, &error,
&new_thread_list](ConstString key, StructuredData::Object *val) -> bool {
if (!val)
return GetInterface().ErrorWithMessage<bool>(
LLVM_PRETTY_FUNCTION, "Invalid thread info object", error);
lldb::tid_t tid = LLDB_INVALID_THREAD_ID;
if (!llvm::to_integer(key.AsCString(), tid))
return GetInterface().ErrorWithMessage<bool>(LLVM_PRETTY_FUNCTION,
"Invalid thread id", error);
if (ThreadSP thread_sp =
old_thread_list.FindThreadByID(tid, false /*=can_update*/)) {
// If the thread was already in the old_thread_list,
// just add it back to the new_thread_list.
new_thread_list.AddThread(thread_sp);
return true;
}
lldb::ThreadSP thread_sp =
std::make_shared<ScriptedThread>(*this, error, val->GetAsGeneric());
if (!thread_sp || error.Fail())
return GetInterface().ErrorWithMessage<bool>(LLVM_PRETTY_FUNCTION,
error.AsCString(), error);
RegisterContextSP reg_ctx_sp = thread_sp->GetRegisterContext();
if (!reg_ctx_sp)
return GetInterface().ErrorWithMessage<bool>(
LLVM_PRETTY_FUNCTION,
llvm::Twine("Invalid Register Context for thread " +
llvm::Twine(key.AsCString()))
.str(),
error);
new_thread_list.AddThread(thread_sp);
return true;
};
thread_info_sp->ForEach(create_scripted_thread);
return new_thread_list.GetSize(false) > 0;
}

View File

@ -28,7 +28,8 @@ void ScriptedThread::CheckInterpreterAndScriptObject() const {
lldbassert(GetInterface() && "Invalid Scripted Thread Interface.");
}
ScriptedThread::ScriptedThread(ScriptedProcess &process, Status &error)
ScriptedThread::ScriptedThread(ScriptedProcess &process, Status &error,
StructuredData::Generic *script_object)
: Thread(process, LLDB_INVALID_THREAD_ID), m_scripted_process(process),
m_scripted_thread_interface_sp(
m_scripted_process.GetInterface().CreateScriptedThreadInterface()) {
@ -54,18 +55,23 @@ ScriptedThread::ScriptedThread(ScriptedProcess &process, Status &error)
ExecutionContext exe_ctx(process);
StructuredData::GenericSP object_sp =
scripted_thread_interface->CreatePluginObject(
class_name->c_str(), exe_ctx,
process.m_scripted_process_info.GetArgsSP());
if (!object_sp || !object_sp->IsValid()) {
error.SetErrorString("Failed to create valid script object");
m_script_object_sp = scripted_thread_interface->CreatePluginObject(
class_name->c_str(), exe_ctx, process.m_scripted_process_info.GetArgsSP(),
script_object);
if (!m_script_object_sp) {
error.SetErrorString("Failed to create script object");
return;
}
m_script_object_sp = object_sp;
if (!m_script_object_sp->IsValid()) {
m_script_object_sp = nullptr;
error.SetErrorString("Created script object is invalid");
return;
}
SetID(scripted_thread_interface->GetThreadID());
lldb::tid_t tid = scripted_thread_interface->GetThreadID();
SetID(tid);
}
ScriptedThread::~ScriptedThread() { DestroyThread(); }

View File

@ -26,7 +26,8 @@ namespace lldb_private {
class ScriptedThread : public lldb_private::Thread {
public:
ScriptedThread(ScriptedProcess &process, Status &error);
ScriptedThread(ScriptedProcess &process, Status &error,
StructuredData::Generic *script_object = nullptr);
~ScriptedThread() override;

View File

@ -32,7 +32,7 @@ ScriptedProcessPythonInterface::ScriptedProcessPythonInterface(
StructuredData::GenericSP ScriptedProcessPythonInterface::CreatePluginObject(
llvm::StringRef class_name, ExecutionContext &exe_ctx,
StructuredData::DictionarySP args_sp) {
StructuredData::DictionarySP args_sp, StructuredData::Generic *script_obj) {
if (class_name.empty())
return {};
@ -47,9 +47,6 @@ StructuredData::GenericSP ScriptedProcessPythonInterface::CreatePluginObject(
class_name.str().c_str(), m_interpreter.GetDictionaryName(), target_sp,
args_impl, error_string);
if (!ret_val)
return {};
m_object_instance_sp =
StructuredData::GenericSP(new StructuredPythonObject(std::move(ret_val)));

View File

@ -25,7 +25,8 @@ public:
StructuredData::GenericSP
CreatePluginObject(const llvm::StringRef class_name,
ExecutionContext &exe_ctx,
StructuredData::DictionarySP args_sp) override;
StructuredData::DictionarySP args_sp,
StructuredData::Generic *script_obj = nullptr) override;
Status Launch() override;

View File

@ -31,8 +31,7 @@ ScriptedThreadPythonInterface::ScriptedThreadPythonInterface(
StructuredData::GenericSP ScriptedThreadPythonInterface::CreatePluginObject(
const llvm::StringRef class_name, ExecutionContext &exe_ctx,
StructuredData::DictionarySP args_sp) {
StructuredData::DictionarySP args_sp, StructuredData::Generic *script_obj) {
if (class_name.empty())
return {};
@ -43,9 +42,15 @@ StructuredData::GenericSP ScriptedThreadPythonInterface::CreatePluginObject(
Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN,
Locker::FreeLock);
PythonObject ret_val = LLDBSwigPythonCreateScriptedThread(
class_name.str().c_str(), m_interpreter.GetDictionaryName(), process_sp,
args_impl, error_string);
PythonObject ret_val;
if (!script_obj)
ret_val = LLDBSwigPythonCreateScriptedThread(
class_name.str().c_str(), m_interpreter.GetDictionaryName(), process_sp,
args_impl, error_string);
else
ret_val = PythonObject(PyRefType::Borrowed,
static_cast<PyObject *>(script_obj->GetValue()));
if (!ret_val)
return {};

View File

@ -24,7 +24,8 @@ public:
StructuredData::GenericSP
CreatePluginObject(llvm::StringRef class_name, ExecutionContext &exe_ctx,
StructuredData::DictionarySP args_sp) override;
StructuredData::DictionarySP args_sp,
StructuredData::Generic *script_obj = nullptr) override;
lldb::tid_t GetThreadID() override;

View File

@ -1,4 +1,4 @@
C_SOURCES := main.c
CXX_SOURCES := main.cpp
ENABLE_THREADS := YES
include Makefile.rules

View File

@ -130,7 +130,8 @@ class ScriptedProcesTestCase(TestBase):
def create_stack_skinny_corefile(self, file):
self.build()
target, process, thread, _ = lldbutil.run_to_source_breakpoint(self, "// break here", lldb.SBFileSpec("main.c"))
target, process, thread, _ = lldbutil.run_to_source_breakpoint(self, "// break here",
lldb.SBFileSpec("main.cpp"))
self.assertTrue(process.IsValid(), "Process is invalid.")
# FIXME: Use SBAPI to save the process corefile.
self.runCmd("process save-core -s stack " + file)
@ -186,14 +187,14 @@ class ScriptedProcesTestCase(TestBase):
self.assertTrue(process, PROCESS_IS_VALID)
self.assertEqual(process.GetProcessID(), 42)
self.assertEqual(process.GetNumThreads(), 1)
self.assertEqual(process.GetNumThreads(), 3)
thread = process.GetSelectedThread()
self.assertTrue(thread, "Invalid thread.")
self.assertEqual(thread.GetName(), "StackCoreScriptedThread.thread-1")
self.assertEqual(thread.GetName(), "StackCoreScriptedThread.thread-0")
self.assertEqual(thread.GetNumFrames(), 3)
self.assertEqual(thread.GetNumFrames(), 2)
frame = thread.GetSelectedFrame()
self.assertTrue(frame, "Invalid frame.")
self.assertEqual(frame.GetFunctionName(), "bar")
self.assertEqual(int(frame.FindValue("i", lldb.eValueTypeVariableArgument).GetValue()), 42)
self.assertEqual(int(frame.FindValue("j", lldb.eValueTypeVariableLocal).GetValue()), 42 * 42)
# self.assertEqual(frame.GetFunctionName(), "bar")
# self.assertEqual(int(frame.FindValue("i", lldb.eValueTypeVariableArgument).GetValue()), 42)
# self.assertEqual(int(frame.FindValue("j", lldb.eValueTypeVariableLocal).GetValue()), 42 * 42)

View File

@ -9,6 +9,7 @@ from lldb.plugins.scripted_process import ScriptedThread
class InvalidScriptedProcess(ScriptedProcess):
def __init__(self, target: lldb.SBTarget, args : lldb.SBStructuredData):
super().__init__(target, args)
self.threads[0] = InvalidScriptedThread(self, None)
def get_memory_region_containing_address(self, addr: int) -> lldb.SBMemoryRegionInfo:
return None
@ -81,4 +82,4 @@ def __lldb_init_module(debugger, dict):
InvalidScriptedProcess.__name__))
else:
print("Name of the class that will manage the scripted process: '%s.%s'"
% (__name__, InvalidScriptedProcess.__name__))
% (__name__, InvalidScriptedProcess.__name__))

View File

@ -1,8 +0,0 @@
int bar(int i) {
int j = i * i;
return j; // break here
}
int foo(int i) { return bar(i); }
int main() { return foo(42); }

View File

@ -0,0 +1,34 @@
#include <iostream>
#include <mutex>
#include <thread>
int bar(int i) {
int j = i * i;
return j; // break here
}
int foo(int i) { return bar(i); }
void call_and_wait(int &n) {
std::cout << "waiting for computation!" << std::endl;
while (n != 42 * 42)
;
std::cout << "finished computation!" << std::endl;
}
void compute_pow(int &n) { n = foo(n); }
int main() {
int n = 42;
std::mutex mutex;
std::unique_lock<std::mutex> lock(mutex);
std::thread thread_1(call_and_wait, std::ref(n));
std::thread thread_2(compute_pow, std::ref(n));
lock.unlock();
thread_1.join();
thread_2.join();
return 0;
}

View File

@ -1,4 +1,4 @@
import os,struct,signal
import os,json,struct,signal
from typing import Any, Dict
@ -21,6 +21,14 @@ class StackCoreScriptedProcess(ScriptedProcess):
idx = int(self.backing_target_idx.GetStringValue(100))
self.corefile_target = target.GetDebugger().GetTargetAtIndex(idx)
self.corefile_process = self.corefile_target.GetProcess()
for corefile_thread in self.corefile_process:
structured_data = lldb.SBStructuredData()
structured_data.SetFromJSON(json.dumps({
"backing_target_idx" : idx,
"thread_idx" : corefile_thread.GetIndexID()
}))
self.threads[corefile_thread.GetThreadID()] = StackCoreScriptedThread(self, structured_data)
def get_memory_region_containing_address(self, addr: int) -> lldb.SBMemoryRegionInfo:
mem_region = lldb.SBMemoryRegionInfo()
@ -70,23 +78,43 @@ class StackCoreScriptedProcess(ScriptedProcess):
class StackCoreScriptedThread(ScriptedThread):
def __init__(self, process, args):
super().__init__(process, args)
self.backing_target_idx = args.GetValueForKey("backing_target_idx")
backing_target_idx = args.GetValueForKey("backing_target_idx")
thread_idx = args.GetValueForKey("thread_idx")
def extract_value_from_structured_data(data, default_val):
if data and data.IsValid():
if data.GetType() == lldb.eStructuredDataTypeInteger:
return data.GetIntegerValue(default_val)
if data.GetType() == lldb.eStructuredDataTypeString:
return int(data.GetStringValue(100))
return None
#TODO: Change to Walrus operator (:=) with oneline if assignment
# Requires python 3.8
val = extract_value_from_structured_data(thread_idx, 0)
if val is not None:
self.idx = val
self.corefile_target = None
self.corefile_process = None
if (self.backing_target_idx and self.backing_target_idx.IsValid()):
if self.backing_target_idx.GetType() == lldb.eStructuredDataTypeInteger:
idx = self.backing_target_idx.GetIntegerValue(42)
if self.backing_target_idx.GetType() == lldb.eStructuredDataTypeString:
idx = int(self.backing_target_idx.GetStringValue(100))
self.corefile_target = self.target.GetDebugger().GetTargetAtIndex(idx)
self.corefile_thread = None
#TODO: Change to Walrus operator (:=) with oneline if assignment
# Requires python 3.8
val = extract_value_from_structured_data(backing_target_idx, 42)
if val is not None:
self.corefile_target = self.target.GetDebugger().GetTargetAtIndex(val)
self.corefile_process = self.corefile_target.GetProcess()
self.corefile_thread = self.corefile_process.GetThreadByIndexID(self.idx)
if self.corefile_thread:
self.id = self.corefile_thread.GetThreadID()
def get_thread_id(self) -> int:
return 0x19
return self.id
def get_name(self) -> str:
return StackCoreScriptedThread.__name__ + ".thread-1"
return StackCoreScriptedThread.__name__ + ".thread-" + str(self.id)
def get_stop_reason(self) -> Dict[str, Any]:
return { "type": lldb.eStopReasonSignal, "data": {
@ -109,10 +137,9 @@ class StackCoreScriptedThread(ScriptedThread):
return self.frame_zero[0:0]
def get_register_context(self) -> str:
thread = self.corefile_process.GetSelectedThread()
if not thread or thread.GetNumFrames() == 0:
if not self.corefile_thread or self.corefile_thread.GetNumFrames() == 0:
return None
frame = thread.GetFrameAtIndex(0)
frame = self.corefile_thread.GetFrameAtIndex(0)
GPRs = None
registerSet = frame.registers # Returns an SBValueList.