mib created this revision.
mib added reviewers: JDevlieghere, jingham, labath.
mib added a project: LLDB.
Herald added a project: All.
mib requested review of this revision.
Herald added a subscriber: lldb-commits.

This patch introduces a new core component to LLDB: Multiplexer.

The goal of a multiplexer is to stand between the debugger and a list of
processes to intercept events and re-dispatch them to the right processes.

This patch also introduces a special kind of multiplexer: ScriptedMultiplexer.
As the name suggests, it lets the user have a custom implementation of
the multiplexer in a script file that can be imported into lldb.

The ScriptedMultiplexer implements a `FilterEvent(SBEvent&)` method that
will allow the user to iterate over the list of multiplexed processes
and decide to which one the event should be sent to.

The multiplexer gets instanciated when a ScriptedProcess implements the
`get_scripted_multiplexer` method that returns a well-formed dictionary.
The multiplexer instance gets stored in the debugger object.

For the process to be handled by the multiplexer, they all need to
provide the same multiplexer name in the dictionary.

Signed-off-by: Med Ismail Bennani <medismail.benn...@gmail.com>


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D143213

Files:
  lldb/bindings/python/CMakeLists.txt
  lldb/bindings/python/python-swigsafecast.swig
  lldb/bindings/python/python-wrapper.swig
  lldb/examples/python/scripted_process/scripted_multiplexer.py
  lldb/examples/python/scripted_process/scripted_process.py
  lldb/include/lldb/API/SBEvent.h
  lldb/include/lldb/Core/Debugger.h
  lldb/include/lldb/Interpreter/ScriptInterpreter.h
  lldb/include/lldb/Interpreter/ScriptedMultiplexer.h
  lldb/include/lldb/Interpreter/ScriptedMultiplexerInterface.h
  lldb/include/lldb/Interpreter/ScriptedProcessInterface.h
  lldb/include/lldb/Target/Multiplexer.h
  lldb/include/lldb/lldb-forward.h
  lldb/source/Core/Debugger.cpp
  lldb/source/Interpreter/CMakeLists.txt
  lldb/source/Interpreter/ScriptInterpreter.cpp
  lldb/source/Interpreter/ScriptedMultiplexer.cpp
  lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp
  lldb/source/Plugins/Process/scripted/ScriptedProcess.h
  lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
  lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
  lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
  
lldb/source/Plugins/ScriptInterpreter/Python/ScriptedMultiplexerPythonInterface.cpp
  
lldb/source/Plugins/ScriptInterpreter/Python/ScriptedMultiplexerPythonInterface.h
  
lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.cpp
  lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.h
  lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.cpp
  lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.h
  lldb/source/Target/CMakeLists.txt
  lldb/source/Target/Multiplexer.cpp
  lldb/test/API/functionalities/scripted_multiplexer/Makefile
  lldb/test/API/functionalities/scripted_multiplexer/TestScriptedMultiplexer.py
  lldb/test/API/functionalities/scripted_multiplexer/main.cpp
  
lldb/test/API/functionalities/scripted_multiplexer/multiplexed_scripted_process.py
  lldb/test/API/functionalities/scripted_platform/Makefile
  lldb/test/API/functionalities/scripted_platform/TestScriptedPlatform.py
  lldb/test/API/functionalities/scripted_platform/main.cpp
  lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp

Index: lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
===================================================================
--- lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
+++ lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
@@ -159,6 +159,10 @@
   return nullptr;
 }
 
+void *lldb_private::LLDBSWIGPython_CastPyObjectToSBEvent(PyObject *data) {
+  return nullptr;
+}
+
 void *lldb_private::LLDBSWIGPython_CastPyObjectToSBError(PyObject *data) {
   return nullptr;
 }
Index: lldb/test/API/functionalities/scripted_platform/main.cpp
===================================================================
--- /dev/null
+++ lldb/test/API/functionalities/scripted_platform/main.cpp
@@ -0,0 +1,30 @@
+#include <iostream>
+#include <mutex>
+#include <thread>
+#include <vector>
+
+int main() {
+  size_t num_threads = 10;
+  std::vector<std::thread> threads;
+
+  std::mutex mutex;
+  std::unique_lock<std::mutex> lock(mutex);
+
+  for (size_t i = 0; i < num_threads; i++) {
+    std::cout << "Spawning thread " << i << std::endl;
+    threads.push_back(std::thread([]() {
+      while (true) {
+        ;
+      }
+    }));
+  }
+
+  for (auto &t : threads) {
+    if (t.joinable())
+      t.join();
+  }
+
+  lock.unlock();
+
+  return 0;
+}
Index: lldb/test/API/functionalities/scripted_platform/TestScriptedPlatform.py
===================================================================
--- /dev/null
+++ lldb/test/API/functionalities/scripted_platform/TestScriptedPlatform.py
@@ -0,0 +1,151 @@
+"""
+Test python scripted process in lldb
+"""
+
+import os, shutil
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+from lldbsuite.test import lldbtest
+
+class ScriptedProcesTestCase(TestBase):
+
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def test_python_plugin_package(self):
+        """Test that the lldb python module has a `plugins.scripted_process`
+        package."""
+        self.expect('script import lldb.plugins',
+                    substrs=["ModuleNotFoundError"], matching=False)
+
+        self.expect('script dir(lldb.plugins)',
+                    substrs=["scripted_process"])
+
+        self.expect('script import lldb.plugins.scripted_process',
+                    substrs=["ModuleNotFoundError"], matching=False)
+
+        self.expect('script dir(lldb.plugins.scripted_process)',
+                    substrs=["ScriptedProcess"])
+
+        self.expect('script from lldb.plugins.scripted_process import ScriptedProcess',
+                    substrs=["ImportError"], matching=False)
+
+        self.expect('script dir(ScriptedProcess)',
+                    substrs=["launch"])
+
+    def move_blueprint_to_dsym(self, blueprint_name):
+        blueprint_origin_path = os.path.join(self.getSourceDir(), blueprint_name)
+        dsym_bundle = self.getBuildArtifact("a.out.dSYM")
+        blueprint_destination_path = os.path.join(dsym_bundle, "Contents",
+                                                  "Resources", "Python")
+        if not os.path.exists(blueprint_destination_path):
+            os.mkdir(blueprint_destination_path)
+
+        blueprint_destination_path = os.path.join(blueprint_destination_path, "a_out.py")
+        shutil.copy(blueprint_origin_path, blueprint_destination_path)
+
+    @skipUnlessDarwin
+    def test_invalid_scripted_register_context(self):
+        """Test that we can launch an lldb scripted process with an invalid
+        Scripted Thread, with invalid register context."""
+        self.build()
+
+        os.environ['SKIP_SCRIPTED_PROCESS_LAUNCH'] = '1'
+        def cleanup():
+          del os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"]
+        self.addTearDownHook(cleanup)
+
+        self.runCmd("settings set target.load-script-from-symbol-file true")
+        self.move_blueprint_to_dsym('invalid_scripted_process.py')
+        target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
+        self.assertTrue(target, VALID_TARGET)
+        log_file = self.getBuildArtifact('thread.log')
+        self.runCmd("log enable lldb thread -f " + log_file)
+        self.assertTrue(os.path.isfile(log_file))
+
+        launch_info = lldb.SBLaunchInfo(None)
+        launch_info.SetProcessPluginName("ScriptedProcess")
+        launch_info.SetScriptedProcessClassName("a_out.InvalidScriptedProcess")
+        error = lldb.SBError()
+
+        process = target.Launch(launch_info, error)
+
+        self.assertSuccess(error)
+        self.assertTrue(process, PROCESS_IS_VALID)
+        self.assertEqual(process.GetProcessID(), 666)
+        self.assertEqual(process.GetNumThreads(), 0)
+
+        addr = 0x500000000
+        buff = process.ReadMemory(addr, 4, error)
+        self.assertEqual(buff, None)
+        self.assertTrue(error.Fail())
+        self.assertEqual(error.GetCString(), "This is an invalid scripted process!")
+
+        with open(log_file, 'r') as f:
+            log = f.read()
+
+        self.assertIn("Failed to get scripted thread registers data.", log)
+
+    @skipUnlessDarwin
+    def test_scripted_process_and_scripted_thread(self):
+        """Test that we can launch an lldb scripted process using the SBAPI,
+        check its process ID, read string from memory, check scripted thread
+        id, name stop reason and register context.
+        """
+        self.build()
+        target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
+        self.assertTrue(target, VALID_TARGET)
+
+        os.environ['SKIP_SCRIPTED_PROCESS_LAUNCH'] = '1'
+        def cleanup():
+          del os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"]
+        self.addTearDownHook(cleanup)
+
+        scripted_process_example_relpath = 'dummy_scripted_process.py'
+        self.runCmd("command script import " + os.path.join(self.getSourceDir(),
+                                                            scripted_process_example_relpath))
+
+        launch_info = lldb.SBLaunchInfo(None)
+        launch_info.SetProcessPluginName("ScriptedProcess")
+        launch_info.SetScriptedProcessClassName("dummy_scripted_process.DummyScriptedProcess")
+
+        error = lldb.SBError()
+        process = target.Launch(launch_info, error)
+        self.assertTrue(process and process.IsValid(), PROCESS_IS_VALID)
+        self.assertEqual(process.GetProcessID(), 42)
+        self.assertEqual(process.GetNumThreads(), 1)
+
+        addr = 0x500000000
+        message = "Hello, world!"
+        buff = process.ReadCStringFromMemory(addr, len(message) + 1, error)
+        self.assertSuccess(error)
+        self.assertEqual(buff, message)
+
+        thread = process.GetSelectedThread()
+        self.assertTrue(thread, "Invalid thread.")
+        self.assertEqual(thread.GetThreadID(), 0x19)
+        self.assertEqual(thread.GetName(), "DummyScriptedThread.thread-1")
+        self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonSignal)
+
+        self.assertGreater(thread.GetNumFrames(), 0)
+
+        frame = thread.GetFrameAtIndex(0)
+        GPRs = None
+        register_set = frame.registers # Returns an SBValueList.
+        for regs in register_set:
+            if 'general purpose' in regs.name.lower():
+                GPRs = regs
+                break
+
+        self.assertTrue(GPRs, "Invalid General Purpose Registers Set")
+        self.assertGreater(GPRs.GetNumChildren(), 0)
+        for idx, reg in enumerate(GPRs, start=1):
+            if idx > 21:
+                break
+            self.assertEqual(idx, int(reg.value, 16))
+
+        self.assertTrue(frame.IsArtificial(), "Frame is not artificial")
+        pc = frame.GetPCAddress().GetLoadAddress(target)
+        self.assertEqual(pc, 0x0100001b00)
Index: lldb/test/API/functionalities/scripted_platform/Makefile
===================================================================
--- /dev/null
+++ lldb/test/API/functionalities/scripted_platform/Makefile
@@ -0,0 +1,6 @@
+CXX_SOURCES := main.cpp
+ENABLE_THREADS := YES
+
+all: a.out
+
+include Makefile.rules
Index: lldb/test/API/functionalities/scripted_multiplexer/multiplexed_scripted_process.py
===================================================================
--- /dev/null
+++ lldb/test/API/functionalities/scripted_multiplexer/multiplexed_scripted_process.py
@@ -0,0 +1,186 @@
+import os,json,struct,signal
+
+from typing import Any, Dict
+
+import lldb
+from lldb.plugins.scripted_process import ScriptedProcess
+from lldb.plugins.scripted_process import ScriptedThread
+
+class StackCoreScriptedProcess(ScriptedProcess):
+    def get_module_with_name(self, target, name):
+        for module in target.modules:
+            if name in module.GetFileSpec().GetFilename():
+                return module
+        return None
+
+    def __init__(self, exe_ctx: lldb.SBExecutionContext, args : lldb.SBStructuredData):
+        super().__init__(exe_ctx, args)
+
+        self.corefile_target = None
+        self.corefile_process = None
+
+        self.backing_target_idx = args.GetValueForKey("backing_target_idx")
+        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_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)
+
+        if len(self.threads) == 2:
+            self.threads[len(self.threads) - 1].is_stopped = True
+
+        corefile_module = self.get_module_with_name(self.corefile_target,
+                                                    "libbaz.dylib")
+        if not corefile_module or not corefile_module.IsValid():
+            return
+        module_path = os.path.join(corefile_module.GetFileSpec().GetDirectory(),
+                                   corefile_module.GetFileSpec().GetFilename())
+        if not os.path.exists(module_path):
+            return
+        module_load_addr = corefile_module.GetObjectFileHeaderAddress().GetLoadAddress(self.corefile_target)
+
+        self.loaded_images.append({"path": module_path,
+                                   "load_addr": module_load_addr})
+
+    def get_memory_region_containing_address(self, addr: int) -> lldb.SBMemoryRegionInfo:
+        mem_region = lldb.SBMemoryRegionInfo()
+        error = self.corefile_process.GetMemoryRegionInfo(addr, mem_region)
+        if error.Fail():
+            return None
+        return mem_region
+
+    def get_thread_with_id(self, tid: int):
+        return {}
+
+    def get_registers_for_thread(self, tid: int):
+        return {}
+
+    def read_memory_at_address(self, addr: int, size: int, error: lldb.SBError) -> lldb.SBData:
+        data = lldb.SBData()
+        bytes_read = self.corefile_process.ReadMemory(addr, size, error)
+
+        if error.Fail():
+            return data
+
+        data.SetDataWithOwnership(error, bytes_read,
+                                  self.corefile_target.GetByteOrder(),
+                                  self.corefile_target.GetAddressByteSize())
+
+        return data
+
+    def get_loaded_images(self):
+        return self.loaded_images
+
+    def get_process_id(self) -> int:
+        return 42
+
+    def should_stop(self) -> bool:
+        return True
+
+    def is_alive(self) -> bool:
+        return True
+
+    def get_scripted_thread_plugin(self):
+        return StackCoreScriptedThread.__module__ + "." + StackCoreScriptedThread.__name__
+
+
+class StackCoreScriptedThread(ScriptedThread):
+    def __init__(self, process, args):
+        super().__init__(process, args)
+        backing_target_idx = args.GetValueForKey("backing_target_idx")
+        thread_idx = args.GetValueForKey("thread_idx")
+        self.is_stopped = False
+
+        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
+        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 self.id
+
+    def get_name(self) -> str:
+        return StackCoreScriptedThread.__name__ + ".thread-" + str(self.id)
+
+    def get_stop_reason(self) -> Dict[str, Any]:
+        stop_reason = { "type": lldb.eStopReasonInvalid, "data": {  }}
+
+        if self.corefile_thread and self.corefile_thread.IsValid() \
+            and self.get_thread_id() == self.corefile_thread.GetThreadID():
+            stop_reason["type"] = lldb.eStopReasonNone
+
+            if self.is_stopped:
+                if 'arm64' in self.scripted_process.arch:
+                    stop_reason["type"] = lldb.eStopReasonException
+                    stop_reason["data"]["desc"] = self.corefile_thread.GetStopDescription(100)
+                elif self.scripted_process.arch == 'x86_64':
+                    stop_reason["type"] = lldb.eStopReasonSignal
+                    stop_reason["data"]["signal"] = signal.SIGTRAP
+                else:
+                    stop_reason["type"] = self.corefile_thread.GetStopReason()
+
+        return stop_reason
+
+    def get_register_context(self) -> str:
+        if not self.corefile_thread or self.corefile_thread.GetNumFrames() == 0:
+            return None
+        frame = self.corefile_thread.GetFrameAtIndex(0)
+
+        GPRs = None
+        registerSet = frame.registers # Returns an SBValueList.
+        for regs in registerSet:
+            if 'general purpose' in regs.name.lower():
+                GPRs = regs
+                break
+
+        if not GPRs:
+            return None
+
+        for reg in GPRs:
+            self.register_ctx[reg.name] = int(reg.value, base=16)
+
+        return struct.pack("{}Q".format(len(self.register_ctx)), *self.register_ctx.values())
+
+
+def __lldb_init_module(debugger, dict):
+    if not 'SKIP_SCRIPTED_PROCESS_LAUNCH' in os.environ:
+        debugger.HandleCommand(
+            "process launch -C %s.%s" % (__name__,
+                                     StackCoreScriptedProcess.__name__))
+    else:
+        print("Name of the class that will manage the scripted process: '%s.%s'"
+                % (__name__, StackCoreScriptedProcess.__name__))
Index: lldb/test/API/functionalities/scripted_multiplexer/main.cpp
===================================================================
--- /dev/null
+++ lldb/test/API/functionalities/scripted_multiplexer/main.cpp
@@ -0,0 +1,26 @@
+#include <functional>
+#include <thread>
+#include <vector>
+
+enum class Parity {
+  Even,
+  Odd,
+};
+
+void even_or_odd(size_t thread_id) {
+  while (true) {
+    // break here
+    Parity parity = static_cast<Parity>(thread_id % 2);
+  }
+}
+
+int main() {
+  std::vector<std::thread> threads;
+  for (size_t i = 0; i < 10; i++)
+    threads.push_back(std::thread(even_or_odd, i));
+
+  for (std::thread &thread : threads)
+    thread.join();
+
+  return 0;
+}
Index: lldb/test/API/functionalities/scripted_multiplexer/TestScriptedMultiplexer.py
===================================================================
--- /dev/null
+++ lldb/test/API/functionalities/scripted_multiplexer/TestScriptedMultiplexer.py
@@ -0,0 +1,152 @@
+"""
+Test python scripted process in lldb
+"""
+
+import os, shutil
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+from lldbsuite.test import lldbtest
+
+class ScriptedProcesTestCase(TestBase):
+
+    NO_DEBUG_INFO_TESTCASE = True
+
+    @skipUnlessDarwin
+    def test_python_plugin_package(self):
+        """Test that the lldb python module has a `plugins.scripted_process`
+        package."""
+        self.expect('script import lldb.plugins',
+                    substrs=["ModuleNotFoundError"], matching=False)
+
+        self.expect('script dir(lldb.plugins)',
+                    substrs=["scripted_process"])
+
+        self.expect('script import lldb.plugins.scripted_process',
+                    substrs=["ModuleNotFoundError"], matching=False)
+
+        self.expect('script dir(lldb.plugins.scripted_process)',
+                    substrs=["ScriptedProcess"])
+
+        self.expect('script from lldb.plugins.scripted_process import ScriptedProcess',
+                    substrs=["ImportError"], matching=False)
+
+        self.expect('script dir(ScriptedProcess)',
+                    substrs=["launch"])
+
+    def move_blueprint_to_dsym(self, blueprint_name):
+        blueprint_origin_path = os.path.join(self.getSourceDir(), blueprint_name)
+        dsym_bundle = self.getBuildArtifact("a.out.dSYM")
+        blueprint_destination_path = os.path.join(dsym_bundle, "Contents",
+                                                  "Resources", "Python")
+        if not os.path.exists(blueprint_destination_path):
+            os.mkdir(blueprint_destination_path)
+
+        blueprint_destination_path = os.path.join(blueprint_destination_path, "a_out.py")
+        shutil.copy(blueprint_origin_path, blueprint_destination_path)
+
+    @skipUnlessDarwin
+    def test_invalid_scripted_register_context(self):
+        """Test that we can launch an lldb scripted process with an invalid
+        Scripted Thread, with invalid register context."""
+        self.build()
+
+        os.environ['SKIP_SCRIPTED_PROCESS_LAUNCH'] = '1'
+        def cleanup():
+          del os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"]
+        self.addTearDownHook(cleanup)
+
+        self.runCmd("settings set target.load-script-from-symbol-file true")
+        self.move_blueprint_to_dsym('invalid_scripted_process.py')
+        target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
+        self.assertTrue(target, VALID_TARGET)
+        log_file = self.getBuildArtifact('thread.log')
+        self.runCmd("log enable lldb thread -f " + log_file)
+        self.assertTrue(os.path.isfile(log_file))
+
+        launch_info = lldb.SBLaunchInfo(None)
+        launch_info.SetProcessPluginName("ScriptedProcess")
+        launch_info.SetScriptedProcessClassName("a_out.InvalidScriptedProcess")
+        error = lldb.SBError()
+
+        process = target.Launch(launch_info, error)
+
+        self.assertSuccess(error)
+        self.assertTrue(process, PROCESS_IS_VALID)
+        self.assertEqual(process.GetProcessID(), 666)
+        self.assertEqual(process.GetNumThreads(), 0)
+
+        addr = 0x500000000
+        buff = process.ReadMemory(addr, 4, error)
+        self.assertEqual(buff, None)
+        self.assertTrue(error.Fail())
+        self.assertEqual(error.GetCString(), "This is an invalid scripted process!")
+
+        with open(log_file, 'r') as f:
+            log = f.read()
+
+        self.assertIn("Failed to get scripted thread registers data.", log)
+
+    @skipUnlessDarwin
+    def test_scripted_process_and_scripted_thread(self):
+        """Test that we can launch an lldb scripted process using the SBAPI,
+        check its process ID, read string from memory, check scripted thread
+        id, name stop reason and register context.
+        """
+        self.build()
+        target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
+        self.assertTrue(target, VALID_TARGET)
+
+        os.environ['SKIP_SCRIPTED_PROCESS_LAUNCH'] = '1'
+        def cleanup():
+          del os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"]
+        self.addTearDownHook(cleanup)
+
+        scripted_process_example_relpath = 'dummy_scripted_process.py'
+        self.runCmd("command script import " + os.path.join(self.getSourceDir(),
+                                                            scripted_process_example_relpath))
+
+        launch_info = lldb.SBLaunchInfo(None)
+        launch_info.SetProcessPluginName("ScriptedProcess")
+        launch_info.SetScriptedProcessClassName("dummy_scripted_process.DummyScriptedProcess")
+
+        error = lldb.SBError()
+        process = target.Launch(launch_info, error)
+        self.assertTrue(process and process.IsValid(), PROCESS_IS_VALID)
+        self.assertEqual(process.GetProcessID(), 42)
+        self.assertEqual(process.GetNumThreads(), 1)
+
+        addr = 0x500000000
+        message = "Hello, world!"
+        buff = process.ReadCStringFromMemory(addr, len(message) + 1, error)
+        self.assertSuccess(error)
+        self.assertEqual(buff, message)
+
+        thread = process.GetSelectedThread()
+        self.assertTrue(thread, "Invalid thread.")
+        self.assertEqual(thread.GetThreadID(), 0x19)
+        self.assertEqual(thread.GetName(), "DummyScriptedThread.thread-1")
+        self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonSignal)
+
+        self.assertGreater(thread.GetNumFrames(), 0)
+
+        frame = thread.GetFrameAtIndex(0)
+        GPRs = None
+        register_set = frame.registers # Returns an SBValueList.
+        for regs in register_set:
+            if 'general purpose' in regs.name.lower():
+                GPRs = regs
+                break
+
+        self.assertTrue(GPRs, "Invalid General Purpose Registers Set")
+        self.assertGreater(GPRs.GetNumChildren(), 0)
+        for idx, reg in enumerate(GPRs, start=1):
+            if idx > 21:
+                break
+            self.assertEqual(idx, int(reg.value, 16))
+
+        self.assertTrue(frame.IsArtificial(), "Frame is not artificial")
+        pc = frame.GetPCAddress().GetLoadAddress(target)
+        self.assertEqual(pc, 0x0100001b00)
Index: lldb/test/API/functionalities/scripted_multiplexer/Makefile
===================================================================
--- /dev/null
+++ lldb/test/API/functionalities/scripted_multiplexer/Makefile
@@ -0,0 +1,8 @@
+CXX_SOURCES := main.cpp
+ENABLE_THREADS := YES
+
+override ARCH := $(shell uname -m)
+
+all: a.out
+
+include Makefile.rules
Index: lldb/source/Target/Multiplexer.cpp
===================================================================
--- /dev/null
+++ lldb/source/Target/Multiplexer.cpp
@@ -0,0 +1,31 @@
+//===-- Multiplexer.cpp ---------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Target/Multiplexer.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+Multiplexer::Multiplexer(Process &driving_process)
+    : m_driving_process_sp(driving_process.shared_from_this()),
+      m_listener_sp(Listener::MakeListener("lldb.ScriptedPlatform")) {
+  m_driving_process_sp->HijackBroadcaster(m_listener_sp);
+}
+
+bool Multiplexer::AddProcess(lldb::ProcessSP process_sp) {
+  if (!process_sp)
+    return false;
+  m_connected_processes[process_sp->GetID()] = process_sp;
+  return true;
+}
+
+bool Multiplexer::DropProcess(lldb::pid_t pid) {
+  if (pid == LLDB_INVALID_PROCESS_ID)
+    return false;
+  return static_cast<bool>(m_connected_processes.erase(pid));
+}
Index: lldb/source/Target/CMakeLists.txt
===================================================================
--- lldb/source/Target/CMakeLists.txt
+++ lldb/source/Target/CMakeLists.txt
@@ -21,6 +21,7 @@
   MemoryHistory.cpp
   MemoryRegionInfo.cpp
   MemoryTagMap.cpp
+  Multiplexer.cpp
   ModuleCache.cpp
   OperatingSystem.cpp
   PathMappingList.cpp
Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.h
===================================================================
--- lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.h
+++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.h
@@ -133,6 +133,10 @@
     return python::ToSWIGWrapper(arg);
   }
 
+  python::PythonObject Transform(lldb::EventSP arg) {
+    return python::ToSWIGWrapper(arg);
+  }
+
   template <typename T, typename U>
   void ReverseTransform(T &original_arg, U transformed_arg, Status &error) {
     // If U is not a PythonObject, don't touch it!
@@ -237,6 +241,11 @@
 lldb::ProcessLaunchInfoSP ScriptedPythonInterface::ExtractValueFromPythonObject<
     lldb::ProcessLaunchInfoSP>(python::PythonObject &p, Status &error);
 
+template <>
+lldb::EventSP
+ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::EventSP>(
+    python::PythonObject &p, Status &error);
+
 template <>
 lldb::DataExtractorSP
 ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DataExtractorSP>(
Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.cpp
===================================================================
--- lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.cpp
+++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptedPythonInterface.cpp
@@ -146,6 +146,22 @@
   return m_interpreter.GetOpaqueTypeFromSBLaunchInfo(*sb_launch_info);
 }
 
+template <>
+lldb::EventSP
+ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::EventSP>(
+    python::PythonObject &p, Status &error) {
+  lldb::SBEvent *sb_event = reinterpret_cast<lldb::SBEvent *>(
+      LLDBSWIGPython_CastPyObjectToSBLaunchInfo(p.get()));
+
+  if (!sb_event) {
+    error.SetErrorString(
+        "Couldn't cast lldb::SBLaunchInfo to lldb::ProcessLaunchInfoSP.");
+    return nullptr;
+  }
+
+  return m_interpreter.GetOpaqueTypeFromSBEvent(*sb_event);
+}
+
 template <>
 std::optional<MemoryRegionInfo>
 ScriptedPythonInterface::ExtractValueFromPythonObject<
Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.h
===================================================================
--- lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.h
+++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.h
@@ -60,9 +60,11 @@
 
   bool IsAlive() override;
 
+  StructuredData::DictionarySP GetMetadata() override;
+
   std::optional<std::string> GetScriptedThreadPluginName() override;
 
-  StructuredData::DictionarySP GetMetadata() override;
+  StructuredData::DictionarySP GetScriptedMultiplexer() override;
 
 private:
   lldb::ScriptedThreadInterfaceSP CreateScriptedThreadInterface() override;
Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.cpp
===================================================================
--- lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.cpp
+++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptedProcessPythonInterface.cpp
@@ -208,4 +208,16 @@
   return dict;
 }
 
+StructuredData::DictionarySP
+ScriptedProcessPythonInterface::GetScriptedMultiplexer() {
+  Status error;
+  StructuredData::DictionarySP dict =
+      Dispatch<StructuredData::DictionarySP>("get_scripted_multiplexer", error);
+
+  if (!CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, dict, error))
+    return {};
+
+  return dict;
+}
+
 #endif
Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptedMultiplexerPythonInterface.h
===================================================================
--- /dev/null
+++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptedMultiplexerPythonInterface.h
@@ -0,0 +1,37 @@
+//===-- ScriptedMultiplexerPythonInterface.h ------------------------*- C++
+//-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_SCRIPTED_MUX_PYTHON_INTERFACE_H
+#define LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_SCRIPTED_MUX_PYTHON_INTERFACE_H
+
+#include "lldb/Host/Config.h"
+
+#if LLDB_ENABLE_PYTHON
+
+#include "ScriptedPythonInterface.h"
+#include "lldb/Interpreter/ScriptedMultiplexerInterface.h"
+#include <optional>
+
+namespace lldb_private {
+class ScriptedMultiplexerPythonInterface : public ScriptedMultiplexerInterface,
+                                           public ScriptedPythonInterface {
+public:
+  ScriptedMultiplexerPythonInterface(ScriptInterpreterPythonImpl &interpreter);
+
+  StructuredData::GenericSP
+  CreatePluginObject(llvm::StringRef class_name, ExecutionContext &exe_ctx,
+                     StructuredData::DictionarySP args_sp,
+                     StructuredData::Generic *script_obj = nullptr) override;
+
+  lldb::ProcessSP FilterEvent(lldb::EventSP event_sp) override;
+};
+} // namespace lldb_private
+
+#endif // LLDB_ENABLE_PYTHON
+#endif // LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_SCRIPTED_MUX_PYTHON_INTERFACE_H
Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptedMultiplexerPythonInterface.cpp
===================================================================
--- /dev/null
+++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptedMultiplexerPythonInterface.cpp
@@ -0,0 +1,67 @@
+//===-- ScriptedMultiplexerPythonInterface.cpp ----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Host/Config.h"
+#include "lldb/Utility/Log.h"
+#include "lldb/Utility/Status.h"
+#include "lldb/lldb-enumerations.h"
+
+#if LLDB_ENABLE_PYTHON
+
+// LLDB Python header must be included first
+#include "lldb-python.h"
+
+#include "SWIGPythonBridge.h"
+#include "ScriptInterpreterPythonImpl.h"
+#include "ScriptedMultiplexerPythonInterface.h"
+
+#include <optional>
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::python;
+
+using Locker = ScriptInterpreterPythonImpl::Locker;
+
+ScriptedMultiplexerPythonInterface::ScriptedMultiplexerPythonInterface(
+    ScriptInterpreterPythonImpl &interpreter)
+    : ScriptedMultiplexerInterface(), ScriptedPythonInterface(interpreter) {}
+
+StructuredData::GenericSP
+ScriptedMultiplexerPythonInterface::CreatePluginObject(
+    llvm::StringRef class_name, ExecutionContext &exe_ctx,
+    StructuredData::DictionarySP args_sp, StructuredData::Generic *script_obj) {
+  if (class_name.empty())
+    return {};
+
+  StructuredDataImpl args_impl;
+  std::string error_string;
+
+  Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN,
+                 Locker::FreeLock);
+
+  lldb::ExecutionContextRefSP exe_ctx_ref_sp =
+      std::make_shared<ExecutionContextRef>(exe_ctx);
+
+  PythonObject ret_val = LLDBSwigPythonCreateScriptedObject(
+      class_name.str().c_str(), m_interpreter.GetDictionaryName(),
+      exe_ctx_ref_sp, args_impl, error_string);
+
+  m_object_instance_sp =
+      StructuredData::GenericSP(new StructuredPythonObject(std::move(ret_val)));
+
+  return m_object_instance_sp;
+}
+
+lldb::ProcessSP
+ScriptedMultiplexerPythonInterface::FilterEvent(lldb::EventSP event_sp) {
+  Status error;
+  return Dispatch<ProcessSP>("filter_event", error, event_sp);
+}
+
+#endif
Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
===================================================================
--- lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
+++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
@@ -18,6 +18,7 @@
 #include "PythonReadline.h"
 #include "SWIGPythonBridge.h"
 #include "ScriptInterpreterPythonImpl.h"
+#include "ScriptedMultiplexerPythonInterface.h"
 #include "ScriptedPlatformPythonInterface.h"
 #include "ScriptedProcessPythonInterface.h"
 
@@ -416,6 +417,8 @@
       std::make_unique<ScriptedProcessPythonInterface>(*this);
   m_scripted_platform_interface_up =
       std::make_unique<ScriptedPlatformPythonInterface>(*this);
+  m_scripted_multiplexer_interface_up =
+      std::make_unique<ScriptedMultiplexerPythonInterface>(*this);
 
   m_dictionary_name.append("_dict");
   StreamString run_string;
Index: lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
===================================================================
--- lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
+++ lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
@@ -78,6 +78,7 @@
 PythonObject ToSWIGWrapper(const SymbolContext &sym_ctx);
 PythonObject ToSWIGWrapper(lldb::ProcessAttachInfoSP attach_info_sp);
 PythonObject ToSWIGWrapper(lldb::ProcessLaunchInfoSP launch_info_sp);
+PythonObject ToSWIGWrapper(lldb::EventSP event_sp);
 PythonObject ToSWIGWrapper(std::unique_ptr<lldb::SBValue> value_sb);
 PythonObject ToSWIGWrapper(std::unique_ptr<lldb::SBStream> stream_sb);
 PythonObject ToSWIGWrapper(std::unique_ptr<lldb::SBStructuredData> data_sb);
@@ -94,6 +95,7 @@
 void *LLDBSWIGPython_CastPyObjectToSBTarget(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBAttachInfo(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBLaunchInfo(PyObject *data);
+void *LLDBSWIGPython_CastPyObjectToSBEvent(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBError(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBValue(PyObject *data);
 void *LLDBSWIGPython_CastPyObjectToSBMemoryRegionInfo(PyObject *data);
Index: lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
===================================================================
--- lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
+++ lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt
@@ -18,6 +18,7 @@
   PythonReadline.cpp
   ScriptInterpreterPython.cpp
   ScriptedPythonInterface.cpp
+  ScriptedMultiplexerPythonInterface.cpp
   ScriptedProcessPythonInterface.cpp
   ScriptedThreadPythonInterface.cpp
   ScriptedPlatformPythonInterface.cpp
Index: lldb/source/Plugins/Process/scripted/ScriptedProcess.h
===================================================================
--- lldb/source/Plugins/Process/scripted/ScriptedProcess.h
+++ lldb/source/Plugins/Process/scripted/ScriptedProcess.h
@@ -97,6 +97,8 @@
   Status DoGetMemoryRegionInfo(lldb::addr_t load_addr,
                                MemoryRegionInfo &range_info) override;
 
+  bool Multiplex(StructuredData::DictionarySP dict_sp, Status &error);
+
   Status DoAttach();
 
 private:
Index: lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp
===================================================================
--- lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp
+++ lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp
@@ -19,6 +19,7 @@
 #include "lldb/Interpreter/OptionGroupBoolean.h"
 #include "lldb/Interpreter/ScriptInterpreter.h"
 #include "lldb/Interpreter/ScriptedMetadata.h"
+#include "lldb/Interpreter/ScriptedMultiplexer.h"
 #include "lldb/Symbol/LocateSymbolFile.h"
 #include "lldb/Target/MemoryRegionInfo.h"
 #include "lldb/Target/Queue.h"
@@ -113,6 +114,10 @@
   }
 
   m_script_object_sp = object_sp;
+
+  // Setup multiplexer
+  if (auto dict_sp = GetInterface().GetScriptedMultiplexer())
+    Multiplex(dict_sp, error);
 }
 
 ScriptedProcess::~ScriptedProcess() {
@@ -137,6 +142,45 @@
   PluginManager::UnregisterPlugin(ScriptedProcess::CreateInstance);
 }
 
+bool ScriptedProcess::Multiplex(StructuredData::DictionarySP dict_sp,
+                                Status &error) {
+  // sanitize dictionary
+  llvm::StringRef managing_class;
+  if (!dict_sp->GetValueForKeyAsString("managing_class", managing_class))
+    return ScriptedInterface::ErrorWithMessage<bool>(
+        LLVM_PRETTY_FUNCTION,
+        "Missing key 'managing_class' in Scripted Multiplexer dictionary",
+        error);
+  uint32_t driving_target_index = UINT32_MAX;
+  if (!dict_sp->GetValueForKeyAsInteger("driving_target_idx",
+                                        driving_target_index))
+    return ScriptedInterface::ErrorWithMessage<bool>(
+        LLVM_PRETTY_FUNCTION,
+        "Missing key 'driving_target_idx' in Scripted Multiplexer dictionary",
+        error);
+
+  Debugger &debugger = GetTarget().GetDebugger();
+  TargetSP target_sp =
+      debugger.GetTargetList().GetTargetAtIndex(driving_target_index);
+  if (!target_sp || !target_sp->IsValid())
+    return ScriptedInterface::ErrorWithMessage<bool>(
+        LLVM_PRETTY_FUNCTION, "Provided driving target is invalid.", error);
+  ProcessSP driving_process_sp = target_sp->GetProcessSP();
+  if (!driving_process_sp || !driving_process_sp->IsValid())
+    return ScriptedInterface::ErrorWithMessage<bool>(
+        LLVM_PRETTY_FUNCTION, "Provided driving target has invalid process.",
+        error);
+
+  if (!debugger.GetMultiplexer()) {
+    debugger.SetMultiplexer(lldb::MultiplexerSP(
+        new ScriptedMultiplexer(managing_class, *driving_process_sp)));
+  }
+
+  debugger.GetMultiplexer()->AddProcess(shared_from_this());
+
+  return error.Success();
+}
+
 Status ScriptedProcess::DoLoadCore() {
   ProcessLaunchInfo launch_info = GetTarget().GetProcessLaunchInfo();
 
Index: lldb/source/Interpreter/ScriptedMultiplexer.cpp
===================================================================
--- /dev/null
+++ lldb/source/Interpreter/ScriptedMultiplexer.cpp
@@ -0,0 +1,53 @@
+//===-- ScriptedMultiplexer.cpp -------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Interpreter/ScriptedMultiplexer.h"
+#include "lldb/Core/Debugger.h"
+#include "lldb/Interpreter/ScriptInterpreter.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+ScriptedMultiplexer::ScriptedMultiplexer(llvm::StringRef managing_class,
+                                         Process &driving_process)
+    : Multiplexer(driving_process), m_managing_class(managing_class.str()),
+      m_debugger(driving_process.GetTarget().GetDebugger()) {
+
+  Status error;
+  m_interpreter = m_debugger.GetScriptInterpreter();
+
+  if (!m_interpreter) {
+    ScriptedInterface::ErrorWithMessage<bool>(
+        LLVM_PRETTY_FUNCTION, "Debugger has no Script Interpreter", error);
+    return;
+  }
+
+  ExecutionContext exe_ctx(driving_process.shared_from_this());
+
+  StructuredData::GenericSP object_sp =
+      GetInterface().CreatePluginObject(managing_class, exe_ctx, nullptr);
+
+  if (!object_sp || !object_sp->IsValid()) {
+    error.SetErrorStringWithFormat("ScriptedProcess::%s () - ERROR: %s",
+                                   __FUNCTION__,
+                                   "Failed to create valid script object");
+    return;
+  }
+
+  m_script_object_sp = object_sp;
+}
+
+ScriptedMultiplexerInterface &ScriptedMultiplexer::GetInterface() const {
+  return m_interpreter->GetScriptedMultiplexerInterface();
+}
+
+lldb::ProcessSP
+ScriptedMultiplexer::FilterEvent(const lldb::EventSP event_sp) const {
+  // FIXME: Call into ScriptedMultiplexerInterface
+  return m_driving_process_sp;
+}
Index: lldb/source/Interpreter/ScriptInterpreter.cpp
===================================================================
--- lldb/source/Interpreter/ScriptInterpreter.cpp
+++ lldb/source/Interpreter/ScriptInterpreter.cpp
@@ -30,11 +30,14 @@
 ScriptInterpreter::ScriptInterpreter(
     Debugger &debugger, lldb::ScriptLanguage script_lang,
     lldb::ScriptedProcessInterfaceUP scripted_process_interface_up,
-    lldb::ScriptedPlatformInterfaceUP scripted_platform_interface_up)
+    lldb::ScriptedPlatformInterfaceUP scripted_platform_interface_up,
+    lldb::ScriptedMultiplexerInterfaceUP scripted_multiplexer_interface_up)
     : m_debugger(debugger), m_script_lang(script_lang),
       m_scripted_process_interface_up(std::move(scripted_process_interface_up)),
       m_scripted_platform_interface_up(
-          std::move(scripted_platform_interface_up)) {}
+          std::move(scripted_platform_interface_up)),
+      m_scripted_multiplexer_interface_up(
+          std::move(scripted_multiplexer_interface_up)) {}
 
 void ScriptInterpreter::CollectDataForBreakpointCommandCallback(
     std::vector<std::reference_wrapper<BreakpointOptions>> &bp_options_vec,
@@ -108,6 +111,11 @@
       *reinterpret_cast<ProcessLaunchInfo *>(launch_info.m_opaque_sp.get()));
 }
 
+lldb::EventSP
+ScriptInterpreter::GetOpaqueTypeFromSBEvent(const lldb::SBEvent &event) const {
+  return event.m_event_sp;
+}
+
 Status
 ScriptInterpreter::GetStatusFromSBError(const lldb::SBError &error) const {
   if (error.m_opaque_up)
Index: lldb/source/Interpreter/CMakeLists.txt
===================================================================
--- lldb/source/Interpreter/CMakeLists.txt
+++ lldb/source/Interpreter/CMakeLists.txt
@@ -52,6 +52,7 @@
   Options.cpp
   Property.cpp
   ScriptInterpreter.cpp
+  ScriptedMultiplexer.cpp
 
   LINK_LIBS
     lldbCommands
Index: lldb/source/Core/Debugger.cpp
===================================================================
--- lldb/source/Core/Debugger.cpp
+++ lldb/source/Core/Debugger.cpp
@@ -866,6 +866,7 @@
     GetInputFile().Close();
 
     m_command_interpreter_up->Clear();
+    m_multiplexer_sp.reset();
   });
 }
 
@@ -878,6 +879,14 @@
   //    m_input_comm.SetCloseOnEOF(b);
 }
 
+void Debugger::SetMultiplexer(lldb::MultiplexerSP mux_sp) {
+  if (m_multiplexer_sp)
+    ReportWarning(llvm::formatv("Replacing debugger multiplex {0} by {1}",
+                                m_multiplexer_sp->GetName(),
+                                mux_sp->GetName()));
+  m_multiplexer_sp = mux_sp;
+}
+
 bool Debugger::GetAsyncExecution() {
   return !m_command_interpreter_up->GetSynchronous();
 }
Index: lldb/include/lldb/lldb-forward.h
===================================================================
--- lldb/include/lldb/lldb-forward.h
+++ lldb/include/lldb/lldb-forward.h
@@ -128,6 +128,7 @@
 class ModuleList;
 class ModuleSpec;
 class ModuleSpecList;
+class Multiplexer;
 class ObjectContainer;
 class ObjectFile;
 class ObjectFileJITDelegate;
@@ -181,6 +182,7 @@
 class Scalar;
 class ScriptInterpreter;
 class ScriptInterpreterLocker;
+class ScriptedMultiplexerInterface;
 class ScriptedPlatformInterface;
 class ScriptedProcessInterface;
 class ScriptedThreadInterface;
@@ -352,6 +354,7 @@
 typedef std::shared_ptr<lldb_private::MemoryRegionInfo> MemoryRegionInfoSP;
 typedef std::shared_ptr<lldb_private::Module> ModuleSP;
 typedef std::weak_ptr<lldb_private::Module> ModuleWP;
+typedef std::shared_ptr<lldb_private::Multiplexer> MultiplexerSP;
 typedef std::shared_ptr<lldb_private::ObjectFile> ObjectFileSP;
 typedef std::shared_ptr<lldb_private::ObjectContainer> ObjectContainerSP;
 typedef std::shared_ptr<lldb_private::ObjectFileJITDelegate>
@@ -380,6 +383,8 @@
 typedef std::shared_ptr<lldb_private::ScriptSummaryFormat>
     ScriptSummaryFormatSP;
 typedef std::shared_ptr<lldb_private::ScriptInterpreter> ScriptInterpreterSP;
+typedef std::unique_ptr<lldb_private::ScriptedMultiplexerInterface>
+    ScriptedMultiplexerInterfaceUP;
 typedef std::unique_ptr<lldb_private::ScriptedPlatformInterface>
     ScriptedPlatformInterfaceUP;
 typedef std::unique_ptr<lldb_private::ScriptedProcessInterface>
Index: lldb/include/lldb/Target/Multiplexer.h
===================================================================
--- /dev/null
+++ lldb/include/lldb/Target/Multiplexer.h
@@ -0,0 +1,53 @@
+//===-- Multiplexer.h ----------------------------------------------*- C++
+//-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_TARGET_MULTIPLEXER_H
+#define LLDB_TARGET_MULTIPLEXER_H
+
+#include <map>
+
+#include "lldb/Interpreter/ScriptedProcessInterface.h"
+#include "lldb/Target/Process.h"
+#include "lldb/Utility/Listener.h"
+#include "lldb/lldb-public.h"
+
+namespace lldb_private {
+
+/// \class Multiplexer Multiplexer.h "lldb/Target/Multiplexer.h"
+/// A plug-in interface definition class for debug multiplexer that
+/// includes many multiplexer abilities such as:
+///     \li getting multiplexer information such as supported architectures,
+///         supported binary file formats and more
+///     \li launching new processes
+///     \li attaching to existing processes
+///     \li download/upload files
+///     \li execute shell commands
+///     \li listing and getting info for existing processes
+///     \li attaching and possibly debugging the multiplexer's kernel
+class Multiplexer {
+public:
+  /// Default Constructor
+  Multiplexer(Process &driving_process);
+  virtual ~Multiplexer() = default;
+
+  bool AddProcess(lldb::ProcessSP process_sp);
+  bool DropProcess(lldb::pid_t pid);
+
+  virtual llvm::StringRef GetName() const = 0;
+  virtual lldb::ProcessSP FilterEvent(const lldb::EventSP event_sp) const = 0;
+
+protected:
+  lldb::ProcessSP m_driving_process_sp = nullptr;
+  lldb::ListenerSP m_listener_sp = nullptr;
+  std::map<lldb::pid_t, lldb::ProcessSP> m_connected_processes;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_TARGET_MULTIPLEXER_H
Index: lldb/include/lldb/Interpreter/ScriptedProcessInterface.h
===================================================================
--- lldb/include/lldb/Interpreter/ScriptedProcessInterface.h
+++ lldb/include/lldb/Interpreter/ScriptedProcessInterface.h
@@ -67,11 +67,13 @@
 
   virtual bool IsAlive() { return true; }
 
+  virtual StructuredData::DictionarySP GetMetadata() { return {}; }
+
   virtual std::optional<std::string> GetScriptedThreadPluginName() {
     return std::nullopt;
   }
 
-  virtual StructuredData::DictionarySP GetMetadata() { return {}; }
+  virtual StructuredData::DictionarySP GetScriptedMultiplexer() { return {}; }
 
 protected:
   friend class ScriptedThread;
Index: lldb/include/lldb/Interpreter/ScriptedMultiplexerInterface.h
===================================================================
--- /dev/null
+++ lldb/include/lldb/Interpreter/ScriptedMultiplexerInterface.h
@@ -0,0 +1,31 @@
+//===-- ScriptedMultiplexerInterface.h ------------------------------*- C++
+//-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_INTERPRETER_SCRIPTED_MUX_INTERFACE_H
+#define LLDB_INTERPRETER_SCRIPTED_MUX_INTERFACE_H
+
+#include "lldb/Interpreter/ScriptedInterface.h"
+
+#include "lldb/lldb-private.h"
+
+namespace lldb_private {
+class ScriptedMultiplexerInterface : virtual public ScriptedInterface {
+public:
+  StructuredData::GenericSP
+  CreatePluginObject(llvm::StringRef class_name, ExecutionContext &exe_ctx,
+                     StructuredData::DictionarySP args_sp,
+                     StructuredData::Generic *script_obj = nullptr) override {
+    return {};
+  }
+
+  virtual lldb::ProcessSP FilterEvent(lldb::EventSP event_sp) { return {}; }
+};
+} // namespace lldb_private
+
+#endif // LLDB_INTERPRETER_SCRIPTED_MUX_INTERFACE_H
Index: lldb/include/lldb/Interpreter/ScriptedMultiplexer.h
===================================================================
--- /dev/null
+++ lldb/include/lldb/Interpreter/ScriptedMultiplexer.h
@@ -0,0 +1,48 @@
+//===-- ScriptedMultiplexer.h -------------------------------------*- C++
+//-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_INTERPRETER_SCRIPTEDMULTIPLEXER_H
+#define LLDB_INTERPRETER_SCRIPTEDMULTIPLEXER_H
+
+// #include "lldb/Interpreter/ScriptedPlatformInterface.h"
+#include "lldb/Target/Multiplexer.h"
+#include "lldb/lldb-private.h"
+
+namespace lldb_private {
+
+class ScriptedMultiplexer : public Multiplexer {
+public:
+  ScriptedMultiplexer(llvm::StringRef managing_class, Process &driving_process);
+
+  ~ScriptedMultiplexer() override = default;
+
+  llvm::StringRef GetName() const override { return m_managing_class; };
+  lldb::ProcessSP FilterEvent(const lldb::EventSP event_sp) const override;
+
+protected:
+  std::string m_managing_class;
+
+private:
+  inline void CheckInterpreterAndScriptObject() const {
+    lldbassert(m_interpreter && "Invalid Script Interpreter.");
+    lldbassert(m_script_object_sp && "Invalid Script Object.");
+  }
+
+  ScriptedMultiplexerInterface &GetInterface() const;
+
+  static bool IsScriptLanguageSupported(lldb::ScriptLanguage language);
+
+  Debugger &m_debugger;
+  lldb_private::ScriptInterpreter *m_interpreter = nullptr;
+  lldb_private::StructuredData::ObjectSP m_script_object_sp = nullptr;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_INTERPRETER_SCRIPTEDMULTIPLEXER_H
Index: lldb/include/lldb/Interpreter/ScriptInterpreter.h
===================================================================
--- lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -13,6 +13,7 @@
 #include "lldb/API/SBData.h"
 #include "lldb/API/SBDebugger.h"
 #include "lldb/API/SBError.h"
+#include "lldb/API/SBEvent.h"
 #include "lldb/API/SBLaunchInfo.h"
 #include "lldb/API/SBMemoryRegionInfo.h"
 #include "lldb/API/SBProcess.h"
@@ -23,6 +24,7 @@
 #include "lldb/Core/StreamFile.h"
 #include "lldb/Core/ThreadedCommunication.h"
 #include "lldb/Host/PseudoTerminal.h"
+#include "lldb/Interpreter/ScriptedMultiplexerInterface.h"
 #include "lldb/Interpreter/ScriptedPlatformInterface.h"
 #include "lldb/Interpreter/ScriptedProcessInterface.h"
 #include "lldb/Utility/Broadcaster.h"
@@ -155,7 +157,9 @@
       lldb::ScriptedProcessInterfaceUP scripted_process_interface_up =
           std::make_unique<ScriptedProcessInterface>(),
       lldb::ScriptedPlatformInterfaceUP scripted_platform_interface_up =
-          std::make_unique<ScriptedPlatformInterface>());
+          std::make_unique<ScriptedPlatformInterface>(),
+      lldb::ScriptedMultiplexerInterfaceUP scripted_multiplexer_interface_up =
+          std::make_unique<ScriptedMultiplexerInterface>());
 
   virtual StructuredData::DictionarySP GetInterpreterInfo();
 
@@ -583,6 +587,10 @@
     return *m_scripted_platform_interface_up;
   }
 
+  ScriptedMultiplexerInterface &GetScriptedMultiplexerInterface() {
+    return *m_scripted_multiplexer_interface_up;
+  }
+
   lldb::DataExtractorSP
   GetDataExtractorFromSBData(const lldb::SBData &data) const;
 
@@ -602,6 +610,8 @@
   lldb::ProcessLaunchInfoSP
   GetOpaqueTypeFromSBLaunchInfo(const lldb::SBLaunchInfo &launch_info) const;
 
+  lldb::EventSP GetOpaqueTypeFromSBEvent(const lldb::SBEvent &event) const;
+
   std::optional<MemoryRegionInfo> GetOpaqueTypeFromSBMemoryRegionInfo(
       const lldb::SBMemoryRegionInfo &mem_region) const;
 
@@ -610,6 +620,7 @@
   lldb::ScriptLanguage m_script_lang;
   lldb::ScriptedProcessInterfaceUP m_scripted_process_interface_up;
   lldb::ScriptedPlatformInterfaceUP m_scripted_platform_interface_up;
+  lldb::ScriptedMultiplexerInterfaceUP m_scripted_multiplexer_interface_up;
 };
 
 } // namespace lldb_private
Index: lldb/include/lldb/Core/Debugger.h
===================================================================
--- lldb/include/lldb/Core/Debugger.h
+++ lldb/include/lldb/Core/Debugger.h
@@ -24,6 +24,7 @@
 #include "lldb/Host/HostThread.h"
 #include "lldb/Host/Terminal.h"
 #include "lldb/Target/ExecutionContext.h"
+#include "lldb/Target/Multiplexer.h"
 #include "lldb/Target/Platform.h"
 #include "lldb/Target/TargetList.h"
 #include "lldb/Utility/Broadcaster.h"
@@ -377,6 +378,9 @@
   Target &GetSelectedOrDummyTarget(bool prefer_dummy = false);
   Target &GetDummyTarget() { return *m_dummy_target_sp; }
 
+  lldb::MultiplexerSP GetMultiplexer() const { return m_multiplexer_sp; }
+  void SetMultiplexer(lldb::MultiplexerSP mux_sp);
+
   lldb::BroadcasterManagerSP GetBroadcasterManager() {
     return m_broadcaster_manager_sp;
   }
@@ -589,6 +593,7 @@
   lldb::ListenerSP m_forward_listener_sp;
   llvm::once_flag m_clear_once;
   lldb::TargetSP m_dummy_target_sp;
+  lldb::MultiplexerSP m_multiplexer_sp;
 
   // Events for m_sync_broadcaster
   enum {
Index: lldb/include/lldb/API/SBEvent.h
===================================================================
--- lldb/include/lldb/API/SBEvent.h
+++ lldb/include/lldb/API/SBEvent.h
@@ -14,6 +14,10 @@
 #include <cstdio>
 #include <vector>
 
+namespace lldb_private {
+class ScriptInterpreter;
+}
+
 namespace lldb {
 
 class SBBroadcaster;
@@ -69,6 +73,8 @@
   friend class SBThread;
   friend class SBWatchpoint;
 
+  friend class lldb_private::ScriptInterpreter;
+
   lldb::EventSP &GetSP() const;
 
   void reset(lldb::EventSP &event_sp);
Index: lldb/examples/python/scripted_process/scripted_process.py
===================================================================
--- lldb/examples/python/scripted_process/scripted_process.py
+++ lldb/examples/python/scripted_process/scripted_process.py
@@ -224,6 +224,16 @@
         """
         return self.metadata
 
+    def get_scripted_multiplexer(self):
+        """ Get the scripted process multiplexer class name and driving target
+        index in a dictionary.
+
+        Returns:
+            Dict: A dictionary containing scripted multiplexer information.
+                  None if the process is not multiplexed.
+        """
+        return None
+
 class ScriptedThread(metaclass=ABCMeta):
 
     """
Index: lldb/examples/python/scripted_process/scripted_multiplexer.py
===================================================================
--- /dev/null
+++ lldb/examples/python/scripted_process/scripted_multiplexer.py
@@ -0,0 +1,44 @@
+from abc import ABCMeta, abstractmethod
+
+import lldb
+
+class ScriptedMultiplexer(metaclass=ABCMeta):
+
+    """
+    The base class for a scripted multiplexer.
+
+    Most of the base class methods are `@abstractmethod` that need to be
+    overwritten by the inheriting class.
+
+    DISCLAIMER: THIS INTERFACE IS STILL UNDER DEVELOPMENT AND NOT STABLE.
+                THE METHODS EXPOSED MIGHT CHANGE IN THE FUTURE.
+    """
+
+    driving_process = None
+
+    @abstractmethod
+    def __init__(self, exe_ctx, args):
+        """ Construct a scripted process.
+
+        Args:
+            exe_ctx (lldb.SBExecutionContext): The execution context for the scripted process.
+            args (lldb.SBStructuredData): A Dictionary holding arbitrary
+                key/value pairs used by the scripted process.
+        """
+        process = None
+        if isinstance(exe_ctx, lldb.SBExecutionContext):
+            process = exe_ctx.process
+        if process and process.IsValid():
+            self.driving_process = process
+
+    @abstractmethod
+    def filter_event(self, event : lldb.SBEvent) -> lldb.SBProcess:
+        """ Get a dictionary containing the process capabilities.
+
+        Returns:
+            Dict[str:bool]: The dictionary of capability, with the capability
+            name as the key and a boolean flag as the value.
+            The dictionary can be empty.
+        """
+        pass
+
Index: lldb/bindings/python/python-wrapper.swig
===================================================================
--- lldb/bindings/python/python-wrapper.swig
+++ lldb/bindings/python/python-wrapper.swig
@@ -776,6 +776,18 @@
   return sb_ptr;
 }
 
+void *lldb_private::LLDBSWIGPython_CastPyObjectToSBEvent(PyObject * data) {
+  lldb::SBEvent *sb_ptr = nullptr;
+
+  int valid_cast =
+      SWIG_ConvertPtr(data, (void **)&sb_ptr, SWIGTYPE_p_lldb__SBEvent, 0);
+
+  if (valid_cast == -1)
+    return NULL;
+
+  return sb_ptr;
+}
+
 void *lldb_private::LLDBSWIGPython_CastPyObjectToSBError(PyObject * data) {
   lldb::SBError *sb_ptr = nullptr;
 
Index: lldb/bindings/python/python-swigsafecast.swig
===================================================================
--- lldb/bindings/python/python-swigsafecast.swig
+++ lldb/bindings/python/python-swigsafecast.swig
@@ -98,6 +98,11 @@
                       SWIGTYPE_p_lldb__SBLaunchInfo);
 }
 
+PythonObject ToSWIGWrapper(lldb::EventSP event_sp) {
+  return ToSWIGHelper(new lldb::EventSP(std::move(event_sp)),
+                      SWIGTYPE_p_lldb__SBEvent);
+}
+
 PythonObject ToSWIGWrapper(lldb::ProcessAttachInfoSP attach_info_sp) {
   return ToSWIGHelper(new lldb::ProcessAttachInfoSP(std::move(attach_info_sp)),
                       SWIGTYPE_p_lldb__SBAttachInfo);
Index: lldb/bindings/python/CMakeLists.txt
===================================================================
--- lldb/bindings/python/CMakeLists.txt
+++ lldb/bindings/python/CMakeLists.txt
@@ -104,7 +104,8 @@
     "plugins"
     FILES
     "${LLDB_SOURCE_DIR}/examples/python/scripted_process/scripted_process.py"
-    "${LLDB_SOURCE_DIR}/examples/python/scripted_process/scripted_platform.py")
+    "${LLDB_SOURCE_DIR}/examples/python/scripted_process/scripted_platform.py"
+    "${LLDB_SOURCE_DIR}/examples/python/scripted_process/scripted_multiplexer.py")
 
   if(APPLE)
     create_python_package(
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to