From c9058d7b50f2377eabd446235676848ca71fc187 Mon Sep 17 00:00:00 2001 From: Med Ismail Bennani Date: Wed, 8 Oct 2025 14:21:35 +0100 Subject: [PATCH 1/2] [lldb] Introduce SBFrameList for lazy frame iteration This patch introduces `SBFrameList`, a new SBAPI class that allows iterating over stack frames lazily without calling `SBThread::GetFrameAtIndex` in a loop. The new `SBThread::GetFrames()` method returns an `SBFrameList` that supports Python iteration (`for frame in frame_list:`), indexing (`frame_list[0]`, `frame_list[-1]`), and length queries (`len()`). The implementation uses `StackFrameListSP` as the opaque pointer, sharing the thread's underlying frame list to ensure frames are materialized on-demand. This is particularly useful for ScriptedFrameProviders, where user scripts can now iterate, filter, and replace frames lazily without materializing the entire stack upfront. Signed-off-by: Med Ismail Bennani --- .../interface/SBFrameListDocstrings.i | 13 ++ .../interface/SBFrameListExtensions.i | 46 +++++ lldb/bindings/interface/SBThreadExtensions.i | 3 +- lldb/bindings/interfaces.swig | 3 + lldb/include/lldb/API/LLDB.h | 1 + lldb/include/lldb/API/SBDefines.h | 1 + lldb/include/lldb/API/SBFrame.h | 1 + lldb/include/lldb/API/SBFrameList.h | 55 ++++++ lldb/include/lldb/API/SBStream.h | 1 + lldb/include/lldb/API/SBThread.h | 2 + lldb/include/lldb/Target/Thread.h | 12 ++ lldb/source/API/CMakeLists.txt | 1 + lldb/source/API/SBFrameList.cpp | 101 ++++++++++ lldb/source/API/SBThread.cpp | 21 +++ lldb/test/API/python_api/frame_list/Makefile | 3 + .../python_api/frame_list/TestSBFrameList.py | 172 ++++++++++++++++++ lldb/test/API/python_api/frame_list/main.cpp | 22 +++ 17 files changed, 457 insertions(+), 1 deletion(-) create mode 100644 lldb/bindings/interface/SBFrameListDocstrings.i create mode 100644 lldb/bindings/interface/SBFrameListExtensions.i create mode 100644 lldb/include/lldb/API/SBFrameList.h create mode 100644 lldb/source/API/SBFrameList.cpp create mode 100644 lldb/test/API/python_api/frame_list/Makefile create mode 100644 lldb/test/API/python_api/frame_list/TestSBFrameList.py create mode 100644 lldb/test/API/python_api/frame_list/main.cpp diff --git a/lldb/bindings/interface/SBFrameListDocstrings.i b/lldb/bindings/interface/SBFrameListDocstrings.i new file mode 100644 index 0000000000000..2ca10b3b4c72b --- /dev/null +++ b/lldb/bindings/interface/SBFrameListDocstrings.i @@ -0,0 +1,13 @@ +%feature("docstring", +"Represents a list of :py:class:`SBFrame` objects." +) lldb::SBFrameList; + +%feature("autodoc", "GetSize(SBFrameList self) -> uint32_t") lldb::SBFrameList::GetSize; +%feature("docstring", " + Returns the number of frames in the list." +) lldb::SBFrameList::GetSize; + +%feature("autodoc", "GetFrameAtIndex(SBFrameList self, uint32_t idx) -> SBFrame") lldb::SBFrameList::GetFrameAtIndex; +%feature("docstring", " + Returns the frame at the given index." +) lldb::SBFrameList::GetFrameAtIndex; \ No newline at end of file diff --git a/lldb/bindings/interface/SBFrameListExtensions.i b/lldb/bindings/interface/SBFrameListExtensions.i new file mode 100644 index 0000000000000..6466234577aae --- /dev/null +++ b/lldb/bindings/interface/SBFrameListExtensions.i @@ -0,0 +1,46 @@ +%extend lldb::SBFrameList { + +#ifdef SWIGPYTHON + %nothreadallow; +#endif + std::string lldb::SBFrameList::__str__ (){ + lldb::SBStream description; + const size_t n = $self->GetSize(); + if (n) + { + for (size_t i=0; iGetFrameAtIndex(i).GetDescription(description); + } + else + { + description.Printf(" lldb.SBFrameList()"); + } + const char *desc = description.GetData(); + size_t desc_len = description.GetSize(); + if (desc_len > 0 && (desc[desc_len-1] == '\n' || desc[desc_len-1] == '\r')) + --desc_len; + return std::string(desc, desc_len); + } +#ifdef SWIGPYTHON + %clearnothreadallow; +#endif + +#ifdef SWIGPYTHON + %pythoncode %{ + def __iter__(self): + '''Iterate over all frames in a lldb.SBFrameList object.''' + return lldb_iter(self, 'GetSize', 'GetFrameAtIndex') + + def __len__(self): + return int(self.GetSize()) + + def __getitem__(self, key): + count = len(self) + if type(key) is int: + if -count <= key < count: + key %= count + return self.GetFrameAtIndex(key) + return None + %} +#endif +} \ No newline at end of file diff --git a/lldb/bindings/interface/SBThreadExtensions.i b/lldb/bindings/interface/SBThreadExtensions.i index 4ec9f10b1a256..c9ae4103d7b60 100644 --- a/lldb/bindings/interface/SBThreadExtensions.i +++ b/lldb/bindings/interface/SBThreadExtensions.i @@ -41,7 +41,8 @@ STRING_EXTENSION_OUTSIDE(SBThread) def get_thread_frames(self): '''An accessor function that returns a list() that contains all frames in a lldb.SBThread object.''' frames = [] - for frame in self: + frame_list = self.GetFrames() + for frame in frame_list: frames.append(frame) return frames diff --git a/lldb/bindings/interfaces.swig b/lldb/bindings/interfaces.swig index e71ed136f20e6..f87dc8a5ce4a8 100644 --- a/lldb/bindings/interfaces.swig +++ b/lldb/bindings/interfaces.swig @@ -39,6 +39,7 @@ %include "./interface/SBFileSpecListDocstrings.i" %include "./interface/SBFormatDocstrings.i" %include "./interface/SBFrameDocstrings.i" +%include "./interface/SBFrameListDocstrings.i" %include "./interface/SBFunctionDocstrings.i" %include "./interface/SBHostOSDocstrings.i" %include "./interface/SBInstructionDocstrings.i" @@ -118,6 +119,7 @@ %include "lldb/API/SBFileSpecList.h" %include "lldb/API/SBFormat.h" %include "lldb/API/SBFrame.h" +%include "lldb/API/SBFrameList.h" %include "lldb/API/SBFunction.h" %include "lldb/API/SBHostOS.h" %include "lldb/API/SBInstruction.h" @@ -192,6 +194,7 @@ %include "./interface/SBFileSpecExtensions.i" %include "./interface/SBFileSpecListExtensions.i" %include "./interface/SBFrameExtensions.i" +%include "./interface/SBFrameListExtensions.i" %include "./interface/SBFunctionExtensions.i" %include "./interface/SBInstructionExtensions.i" %include "./interface/SBInstructionListExtensions.i" diff --git a/lldb/include/lldb/API/LLDB.h b/lldb/include/lldb/API/LLDB.h index 6485f35302a1c..6ac35bb4a364b 100644 --- a/lldb/include/lldb/API/LLDB.h +++ b/lldb/include/lldb/API/LLDB.h @@ -37,6 +37,7 @@ #include "lldb/API/SBFileSpecList.h" #include "lldb/API/SBFormat.h" #include "lldb/API/SBFrame.h" +#include "lldb/API/SBFrameList.h" #include "lldb/API/SBFunction.h" #include "lldb/API/SBHostOS.h" #include "lldb/API/SBInstruction.h" diff --git a/lldb/include/lldb/API/SBDefines.h b/lldb/include/lldb/API/SBDefines.h index 85f6bbeea5bf9..5fcc685050c0b 100644 --- a/lldb/include/lldb/API/SBDefines.h +++ b/lldb/include/lldb/API/SBDefines.h @@ -76,6 +76,7 @@ class LLDB_API SBFileSpec; class LLDB_API SBFileSpecList; class LLDB_API SBFormat; class LLDB_API SBFrame; +class LLDB_API SBFrameList; class LLDB_API SBFunction; class LLDB_API SBHostOS; class LLDB_API SBInstruction; diff --git a/lldb/include/lldb/API/SBFrame.h b/lldb/include/lldb/API/SBFrame.h index 92917e57fc125..5283cdfe53faa 100644 --- a/lldb/include/lldb/API/SBFrame.h +++ b/lldb/include/lldb/API/SBFrame.h @@ -222,6 +222,7 @@ class LLDB_API SBFrame { protected: friend class SBBlock; friend class SBExecutionContext; + friend class SBFrameList; friend class SBInstruction; friend class SBThread; friend class SBValue; diff --git a/lldb/include/lldb/API/SBFrameList.h b/lldb/include/lldb/API/SBFrameList.h new file mode 100644 index 0000000000000..87b72a76384df --- /dev/null +++ b/lldb/include/lldb/API/SBFrameList.h @@ -0,0 +1,55 @@ +//===-- SBFrameList.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_API_SBFRAMELIST_H +#define LLDB_API_SBFRAMELIST_H + +#include "lldb/API/SBDefines.h" + +namespace lldb { + +class LLDB_API SBFrameList { +public: + SBFrameList(); + + SBFrameList(const lldb::SBFrameList &rhs); + + ~SBFrameList(); + + const lldb::SBFrameList &operator=(const lldb::SBFrameList &rhs); + + explicit operator bool() const; + + bool IsValid() const; + + uint32_t GetSize() const; + + lldb::SBFrame GetFrameAtIndex(uint32_t idx) const; + + void Clear(); + + void Append(const lldb::SBFrame &frame); + + void Append(const lldb::SBFrameList &frame_list); + + bool GetDescription(lldb::SBStream &description) const; + +protected: + friend class SBThread; + +private: + SBFrameList(const lldb::StackFrameListSP &frame_list_sp); + + void SetOpaque(const lldb::StackFrameListSP &frame_list_sp); + + lldb::StackFrameListSP m_opaque_sp; +}; + +} // namespace lldb + +#endif // LLDB_API_SBFRAMELIST_H diff --git a/lldb/include/lldb/API/SBStream.h b/lldb/include/lldb/API/SBStream.h index d230da6123fb3..21f9d21e0e717 100644 --- a/lldb/include/lldb/API/SBStream.h +++ b/lldb/include/lldb/API/SBStream.h @@ -81,6 +81,7 @@ class LLDB_API SBStream { friend class SBFileSpec; friend class SBFileSpecList; friend class SBFrame; + friend class SBFrameList; friend class SBFunction; friend class SBInstruction; friend class SBInstructionList; diff --git a/lldb/include/lldb/API/SBThread.h b/lldb/include/lldb/API/SBThread.h index e9fe5858d125e..9293a7f9ebab9 100644 --- a/lldb/include/lldb/API/SBThread.h +++ b/lldb/include/lldb/API/SBThread.h @@ -178,6 +178,8 @@ class LLDB_API SBThread { lldb::SBFrame GetFrameAtIndex(uint32_t idx); + lldb::SBFrameList GetFrames(); + lldb::SBFrame GetSelectedFrame(); lldb::SBFrame SetSelectedFrame(uint32_t frame_idx); diff --git a/lldb/include/lldb/Target/Thread.h b/lldb/include/lldb/Target/Thread.h index 688c056da2633..ca356d546db5d 100644 --- a/lldb/include/lldb/Target/Thread.h +++ b/lldb/include/lldb/Target/Thread.h @@ -1295,6 +1295,8 @@ class Thread : public std::enable_shared_from_this, /// an empty std::optional is returned in that case. std::optional GetPreviousFrameZeroPC(); + lldb::StackFrameListSP GetStackFrameList(); + protected: friend class ThreadPlan; friend class ThreadList; @@ -1336,8 +1338,18 @@ class Thread : public std::enable_shared_from_this, return StructuredData::ObjectSP(); } +<<<<<<< HEAD + lldb::StackFrameListSP GetStackFrameList(); + +||||||| parent of e4e31827a1bf ([lldb] Introduce SBFrameList for lazy frame iteration) lldb::StackFrameListSP GetStackFrameList(); + llvm::Expected GetScriptedFrameList(); + +======= + llvm::Expected GetScriptedFrameList(); + +>>>>>>> e4e31827a1bf ([lldb] Introduce SBFrameList for lazy frame iteration) void SetTemporaryResumeState(lldb::StateType new_state) { m_temporary_resume_state = new_state; } diff --git a/lldb/source/API/CMakeLists.txt b/lldb/source/API/CMakeLists.txt index ce59ee505cd3d..ac47580d60840 100644 --- a/lldb/source/API/CMakeLists.txt +++ b/lldb/source/API/CMakeLists.txt @@ -69,6 +69,7 @@ add_lldb_library(liblldb SHARED ${option_framework} SBFileSpecList.cpp SBFormat.cpp SBFrame.cpp + SBFrameList.cpp SBFunction.cpp SBHostOS.cpp SBInstruction.cpp diff --git a/lldb/source/API/SBFrameList.cpp b/lldb/source/API/SBFrameList.cpp new file mode 100644 index 0000000000000..07cb29518401f --- /dev/null +++ b/lldb/source/API/SBFrameList.cpp @@ -0,0 +1,101 @@ +//===-- SBFrameList.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/API/SBFrameList.h" +#include "lldb/API/SBFrame.h" +#include "lldb/API/SBStream.h" +#include "lldb/Target/StackFrameList.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/Instrumentation.h" + +using namespace lldb; +using namespace lldb_private; + +SBFrameList::SBFrameList() : m_opaque_sp() { LLDB_INSTRUMENT_VA(this); } + +SBFrameList::SBFrameList(const SBFrameList &rhs) + : m_opaque_sp(rhs.m_opaque_sp) { + LLDB_INSTRUMENT_VA(this, rhs); +} + +SBFrameList::~SBFrameList() = default; + +const SBFrameList &SBFrameList::operator=(const SBFrameList &rhs) { + LLDB_INSTRUMENT_VA(this, rhs); + + if (this != &rhs) + m_opaque_sp = rhs.m_opaque_sp; + return *this; +} + +SBFrameList::SBFrameList(const lldb::StackFrameListSP &frame_list_sp) + : m_opaque_sp(frame_list_sp) {} + +void SBFrameList::SetOpaque(const lldb::StackFrameListSP &frame_list_sp) { + m_opaque_sp = frame_list_sp; +} + +SBFrameList::operator bool() const { + LLDB_INSTRUMENT_VA(this); + + return m_opaque_sp.get() != nullptr; +} + +bool SBFrameList::IsValid() const { + LLDB_INSTRUMENT_VA(this); + return this->operator bool(); +} + +uint32_t SBFrameList::GetSize() const { + LLDB_INSTRUMENT_VA(this); + + if (m_opaque_sp) + return m_opaque_sp->GetNumFrames(); + return 0; +} + +SBFrame SBFrameList::GetFrameAtIndex(uint32_t idx) const { + LLDB_INSTRUMENT_VA(this, idx); + + SBFrame sb_frame; + if (m_opaque_sp) + sb_frame.SetFrameSP(m_opaque_sp->GetFrameAtIndex(idx)); + return sb_frame; +} + +void SBFrameList::Clear() { + LLDB_INSTRUMENT_VA(this); + + if (m_opaque_sp) + m_opaque_sp->Clear(); +} + +void SBFrameList::Append(const SBFrame &frame) { + LLDB_INSTRUMENT_VA(this, frame); + + // Note: StackFrameList doesn't have an Append method, so this is a no-op + // This method is kept for API consistency with other SB*List classes +} + +void SBFrameList::Append(const SBFrameList &frame_list) { + LLDB_INSTRUMENT_VA(this, frame_list); + + // Note: StackFrameList doesn't have an Append method, so this is a no-op + // This method is kept for API consistency with other SB*List classes +} + +bool SBFrameList::GetDescription(SBStream &description) const { + LLDB_INSTRUMENT_VA(this, description); + + Stream &strm = description.ref(); + if (m_opaque_sp) { + m_opaque_sp->Dump(&strm); + return true; + } + return false; +} diff --git a/lldb/source/API/SBThread.cpp b/lldb/source/API/SBThread.cpp index 4e4aa48bc9a2e..ba1a492069ea6 100644 --- a/lldb/source/API/SBThread.cpp +++ b/lldb/source/API/SBThread.cpp @@ -14,6 +14,7 @@ #include "lldb/API/SBFileSpec.h" #include "lldb/API/SBFormat.h" #include "lldb/API/SBFrame.h" +#include "lldb/API/SBFrameList.h" #include "lldb/API/SBProcess.h" #include "lldb/API/SBStream.h" #include "lldb/API/SBStructuredData.h" @@ -1079,6 +1080,26 @@ SBFrame SBThread::GetFrameAtIndex(uint32_t idx) { return sb_frame; } +lldb::SBFrameList SBThread::GetFrames() { + LLDB_INSTRUMENT_VA(this); + + SBFrameList sb_frame_list; + llvm::Expected exe_ctx = + GetStoppedExecutionContext(m_opaque_sp); + if (!exe_ctx) { + LLDB_LOG_ERROR(GetLog(LLDBLog::API), exe_ctx.takeError(), "{0}"); + return SBFrameList(); + } + + if (exe_ctx->HasThreadScope()) { + StackFrameListSP frame_list_sp = + exe_ctx->GetThreadPtr()->GetStackFrameList(); + sb_frame_list.SetOpaque(frame_list_sp); + } + + return sb_frame_list; +} + lldb::SBFrame SBThread::GetSelectedFrame() { LLDB_INSTRUMENT_VA(this); diff --git a/lldb/test/API/python_api/frame_list/Makefile b/lldb/test/API/python_api/frame_list/Makefile new file mode 100644 index 0000000000000..2bb9ce046a907 --- /dev/null +++ b/lldb/test/API/python_api/frame_list/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules \ No newline at end of file diff --git a/lldb/test/API/python_api/frame_list/TestSBFrameList.py b/lldb/test/API/python_api/frame_list/TestSBFrameList.py new file mode 100644 index 0000000000000..88345f97080f5 --- /dev/null +++ b/lldb/test/API/python_api/frame_list/TestSBFrameList.py @@ -0,0 +1,172 @@ +""" +Test SBFrameList API. +""" + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class FrameListAPITestCase(TestBase): + def test_frame_list_api(self): + """Test SBThread.GetFrames() returns a valid SBFrameList.""" + self.build() + self.frame_list_api() + + def test_frame_list_iterator(self): + """Test SBFrameList iterator functionality.""" + self.build() + self.frame_list_iterator() + + def test_frame_list_indexing(self): + """Test SBFrameList indexing and length.""" + self.build() + self.frame_list_indexing() + + def setUp(self): + TestBase.setUp(self) + self.main_source = "main.cpp" + self.break_line = line_number( + self.main_source, "// Set break point at this line" + ) + + def frame_list_api(self): + """Test SBThread.GetFrames() returns a valid SBFrameList.""" + exe = self.getBuildArtifact("a.out") + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + + breakpoint = target.BreakpointCreateByLocation( + self.main_source, self.break_line + ) + self.assertTrue(breakpoint, VALID_BREAKPOINT) + + process = target.LaunchSimple(None, None, self.get_process_working_directory()) + + thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonBreakpoint) + self.assertTrue( + thread.IsValid(), "There should be a thread stopped due to breakpoint" + ) + + # Test GetFrames() returns a valid SBFrameList + frame_list = thread.GetFrames() + self.assertTrue(frame_list.IsValid(), "Frame list should be valid") + self.assertGreater( + frame_list.GetSize(), 0, "Frame list should have at least one frame" + ) + + # Verify frame list size matches thread frame count + self.assertEqual( + frame_list.GetSize(), + thread.GetNumFrames(), + "Frame list size should match thread frame count", + ) + + # Verify frames are the same + for i in range(frame_list.GetSize()): + frame_from_list = frame_list.GetFrameAtIndex(i) + frame_from_thread = thread.GetFrameAtIndex(i) + self.assertTrue( + frame_from_list.IsValid(), f"Frame {i} from list should be valid" + ) + self.assertEqual( + frame_from_list.GetPC(), + frame_from_thread.GetPC(), + f"Frame {i} PC should match", + ) + + def frame_list_iterator(self): + """Test SBFrameList iterator functionality.""" + exe = self.getBuildArtifact("a.out") + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + + breakpoint = target.BreakpointCreateByLocation( + self.main_source, self.break_line + ) + self.assertTrue(breakpoint, VALID_BREAKPOINT) + + process = target.LaunchSimple(None, None, self.get_process_working_directory()) + + thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonBreakpoint) + frame_list = thread.GetFrames() + + # Test iteration + frame_count = 0 + for frame in frame_list: + self.assertTrue(frame.IsValid(), "Each frame should be valid") + frame_count += 1 + + self.assertEqual( + frame_count, + frame_list.GetSize(), + "Iterator should visit all frames", + ) + + # Test that we can iterate multiple times + second_count = 0 + for frame in frame_list: + second_count += 1 + + self.assertEqual( + frame_count, second_count, "Should be able to iterate multiple times" + ) + + def frame_list_indexing(self): + """Test SBFrameList indexing and length.""" + exe = self.getBuildArtifact("a.out") + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + + breakpoint = target.BreakpointCreateByLocation( + self.main_source, self.break_line + ) + self.assertTrue(breakpoint, VALID_BREAKPOINT) + + process = target.LaunchSimple(None, None, self.get_process_working_directory()) + + thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonBreakpoint) + frame_list = thread.GetFrames() + + # Test len() + self.assertEqual( + len(frame_list), frame_list.GetSize(), "len() should return frame count" + ) + + # Test positive indexing + first_frame = frame_list[0] + self.assertTrue(first_frame.IsValid(), "First frame should be valid") + self.assertEqual( + first_frame.GetPC(), + thread.GetFrameAtIndex(0).GetPC(), + "Indexed frame should match", + ) + + # Test negative indexing + if len(frame_list) > 0: + last_frame = frame_list[-1] + self.assertTrue(last_frame.IsValid(), "Last frame should be valid") + self.assertEqual( + last_frame.GetPC(), + thread.GetFrameAtIndex(len(frame_list) - 1).GetPC(), + "Negative indexing should work", + ) + + # Test out of bounds returns None + out_of_bounds = frame_list[10000] + self.assertIsNone(out_of_bounds, "Out of bounds index should return None") + + # Test bool conversion + self.assertTrue(bool(frame_list), "Non-empty frame list should be truthy") + + # Test Clear() + frame_list.Clear() + # Note: Clear() clears the underlying StackFrameList cache, + # but the frame list object itself should still be valid + self.assertTrue( + frame_list.IsValid(), "Frame list should still be valid after Clear()" + ) diff --git a/lldb/test/API/python_api/frame_list/main.cpp b/lldb/test/API/python_api/frame_list/main.cpp new file mode 100644 index 0000000000000..a10ae5cf11f80 --- /dev/null +++ b/lldb/test/API/python_api/frame_list/main.cpp @@ -0,0 +1,22 @@ +#include + +int c(int val) { + // Set break point at this line + return val + 3; +} + +int b(int val) { + int result = c(val); + return result; +} + +int a(int val) { + int result = b(val); + return result; +} + +int main() { + int result = a(1); + printf("Result: %d\n", result); + return 0; +} \ No newline at end of file From 25a5fcdcf12fcddd83f6a165573671d5ac49def8 Mon Sep 17 00:00:00 2001 From: Med Ismail Bennani Date: Wed, 8 Oct 2025 15:47:22 +0100 Subject: [PATCH 2/2] [lldb] Introduce ScriptedFrameProvider for real threads This patch introduces a new scripting affordance: `ScriptedFrameProvider`. This allows users to provide custom stack frames for real native threads, augmenting or replacing the standard unwinding mechanism. This is useful for: - Providing frames for custom calling conventions or languages - Reconstructing missing frames from crash dumps or core files - Adding diagnostic or synthetic frames for debugging The frame provider supports four merge strategies: - Replace: Replace entire stack with scripted frames - Prepend: Add scripted frames before real stack - Append: Add scripted frames after real stack - ReplaceByIndex: Replace specific frames by index With this change, frames can be synthesized from different sources: - Either from a dictionary containing a PC address and frame index - Or by creating a ScriptedFrame python object for full control To use it, first register the scripted frame provider then use existing commands: (lldb) frame provider register -C my_module.MyFrameProvider or (lldb) script thread.RegisterFrameProvider("my_module.MyFrameProvider", lldb.SBStructuredData()) then (lldb) bt See examples/python/templates/scripted_frame_provider.py for details. Architecture changes: - Moved ScriptedFrame from `Plugins` to `Interpreter` to avoid layering violations - Moved `RegisterContextMemory` from `Plugins` to `Target` as it only depends on Target and Utility layers - Added `ScriptedFrameProvider` C++ wrapper and Python interface - Updated `Thread::GetStackFrameList` to apply merge strategies rdar://161834688 Signed-off-by: Med Ismail Bennani --- lldb/bindings/python/CMakeLists.txt | 1 + lldb/bindings/python/python-swigsafecast.swig | 5 + lldb/bindings/python/python-wrapper.swig | 12 + .../templates/scripted_frame_provider.py | 154 +++++++++++ .../python/templates/scripted_process.py | 41 ++- lldb/include/lldb/API/SBFrameList.h | 14 + lldb/include/lldb/API/SBThread.h | 5 + .../ScriptedFrameProviderInterface.h | 36 +++ .../lldb/Interpreter/ScriptInterpreter.h | 10 + .../lldb/Interpreter}/ScriptedFrame.h | 42 ++- .../lldb/Interpreter/ScriptedFrameProvider.h | 58 ++++ .../lldb/Target}/RegisterContextMemory.h | 6 +- lldb/include/lldb/Target/StackFrame.h | 5 +- lldb/include/lldb/Target/StackFrameList.h | 1 + lldb/include/lldb/Target/Thread.h | 20 +- lldb/include/lldb/lldb-enumerations.h | 13 + lldb/include/lldb/lldb-forward.h | 6 + lldb/source/API/SBThread.cpp | 30 +++ lldb/source/Commands/CommandObjectFrame.cpp | 96 +++++++ lldb/source/Commands/CommandObjectThread.cpp | 1 + lldb/source/Interpreter/CMakeLists.txt | 2 + lldb/source/Interpreter/ScriptInterpreter.cpp | 5 + .../ScriptedFrame.cpp | 84 ++++-- .../Interpreter/ScriptedFrameProvider.cpp | 197 ++++++++++++++ .../Python/OperatingSystemPython.cpp | 2 +- .../Plugins/Process/Utility/CMakeLists.txt | 1 - .../Plugins/Process/scripted/CMakeLists.txt | 1 - .../Process/scripted/ScriptedThread.cpp | 5 +- .../Plugins/Process/scripted/ScriptedThread.h | 2 +- .../Python/Interfaces/CMakeLists.txt | 1 + .../ScriptInterpreterPythonInterfaces.h | 1 + .../ScriptedFrameProviderPythonInterface.cpp | 71 +++++ .../ScriptedFrameProviderPythonInterface.h | 45 ++++ .../Interfaces/ScriptedPythonInterface.cpp | 18 ++ .../Interfaces/ScriptedPythonInterface.h | 13 + .../Python/SWIGPythonBridge.h | 2 + .../Python/ScriptInterpreterPython.cpp | 5 + .../Python/ScriptInterpreterPythonImpl.h | 3 + lldb/source/Target/CMakeLists.txt | 1 + .../RegisterContextMemory.cpp | 2 +- lldb/source/Target/Thread.cpp | 136 +++++++++- .../scripted_frame_provider/Makefile | 3 + .../TestScriptedFrameProvider.py | 248 ++++++++++++++++++ .../scripted_frame_provider/main.c | 14 + .../test_frame_providers.py | 185 +++++++++++++ .../Python/PythonTestSuite.cpp | 10 + 46 files changed, 1536 insertions(+), 77 deletions(-) create mode 100644 lldb/examples/python/templates/scripted_frame_provider.py create mode 100644 lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h rename lldb/{source/Plugins/Process/scripted => include/lldb/Interpreter}/ScriptedFrame.h (60%) create mode 100644 lldb/include/lldb/Interpreter/ScriptedFrameProvider.h rename lldb/{source/Plugins/Process/Utility => include/lldb/Target}/RegisterContextMemory.h (92%) rename lldb/source/{Plugins/Process/scripted => Interpreter}/ScriptedFrame.cpp (66%) create mode 100644 lldb/source/Interpreter/ScriptedFrameProvider.cpp create mode 100644 lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp create mode 100644 lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h rename lldb/source/{Plugins/Process/Utility => Target}/RegisterContextMemory.cpp (99%) create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/Makefile create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/main.c create mode 100644 lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py diff --git a/lldb/bindings/python/CMakeLists.txt b/lldb/bindings/python/CMakeLists.txt index ef6def3f26872..28a8af8f06319 100644 --- a/lldb/bindings/python/CMakeLists.txt +++ b/lldb/bindings/python/CMakeLists.txt @@ -107,6 +107,7 @@ function(finish_swig_python swig_target lldb_python_bindings_dir lldb_python_tar "plugins" FILES "${LLDB_SOURCE_DIR}/examples/python/templates/parsed_cmd.py" + "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_frame_provider.py" "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_process.py" "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_platform.py" "${LLDB_SOURCE_DIR}/examples/python/templates/operating_system.py" diff --git a/lldb/bindings/python/python-swigsafecast.swig b/lldb/bindings/python/python-swigsafecast.swig index 3ea24f1a31414..a86dc44ce4106 100644 --- a/lldb/bindings/python/python-swigsafecast.swig +++ b/lldb/bindings/python/python-swigsafecast.swig @@ -37,6 +37,11 @@ PythonObject SWIGBridge::ToSWIGWrapper(lldb::ThreadPlanSP thread_plan_sp) { SWIGTYPE_p_lldb__SBThreadPlan); } +PythonObject SWIGBridge::ToSWIGWrapper(lldb::StackFrameListSP frames_sp) { + return ToSWIGHelper(new lldb::SBFrameList(std::move(frames_sp)), + SWIGTYPE_p_lldb__SBFrameList); +} + PythonObject SWIGBridge::ToSWIGWrapper(lldb::BreakpointSP breakpoint_sp) { return ToSWIGHelper(new lldb::SBBreakpoint(std::move(breakpoint_sp)), SWIGTYPE_p_lldb__SBBreakpoint); diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig index 64b7dc8381073..6ba0276fcb05e 100644 --- a/lldb/bindings/python/python-wrapper.swig +++ b/lldb/bindings/python/python-wrapper.swig @@ -556,6 +556,18 @@ void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBExecutionContext(PyOb return sb_ptr; } +void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBFrameList(PyObject *data) { + lldb::SBFrameList *sb_ptr = NULL; + + int valid_cast = SWIG_ConvertPtr(data, (void **)&sb_ptr, + SWIGTYPE_p_lldb__SBFrameList, 0); + + if (valid_cast == -1) + return NULL; + + return sb_ptr; +} + bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallCommand( const char *python_function_name, const char *session_dictionary_name, lldb::DebuggerSP debugger, const char *args, diff --git a/lldb/examples/python/templates/scripted_frame_provider.py b/lldb/examples/python/templates/scripted_frame_provider.py new file mode 100644 index 0000000000000..056f9135bc602 --- /dev/null +++ b/lldb/examples/python/templates/scripted_frame_provider.py @@ -0,0 +1,154 @@ +from abc import ABCMeta, abstractmethod + +import lldb + + +class ScriptedFrameProvider(metaclass=ABCMeta): + """ + The base class for a scripted frame provider. + + A scripted frame provider allows you to provide custom stack frames for a + thread, which can be used to augment or replace the standard unwinding + mechanism. This is useful for: + + - Providing frames for custom calling conventions or languages + - Reconstructing missing frames from crash dumps or core files + - Adding diagnostic or synthetic frames for debugging + - Visualizing state machines or async execution contexts + + Most of the base class methods are `@abstractmethod` that need to be + overwritten by the inheriting class. + + Example usage: + + .. code-block:: python + + # Attach a frame provider to a thread + thread = process.GetSelectedThread() + error = thread.SetScriptedFrameProvider( + "my_module.MyFrameProvider", + lldb.SBStructuredData() + ) + """ + + @abstractmethod + def __init__(self, thread, args): + """Construct a scripted frame provider. + + Args: + thread (lldb.SBThread): The thread for which to provide frames. + args (lldb.SBStructuredData): A Dictionary holding arbitrary + key/value pairs used by the scripted frame provider. + """ + self.thread = None + self.args = None + self.target = None + self.process = None + + if isinstance(thread, lldb.SBThread) and thread.IsValid(): + self.thread = thread + self.process = thread.GetProcess() + if self.process and self.process.IsValid(): + self.target = self.process.GetTarget() + + if isinstance(args, lldb.SBStructuredData) and args.IsValid(): + self.args = args + + def get_merge_strategy(self): + """Get the merge strategy for how scripted frames should be integrated. + + The merge strategy determines how the scripted frames are combined with the + real unwound frames from the thread's normal unwinder. + + Returns: + int: One of the following lldb.ScriptedFrameProviderMergeStrategy values: + + - lldb.eScriptedFrameProviderMergeStrategyReplace: Replace the entire stack + with scripted frames. The thread will only show frames provided + by this provider. + + - lldb.eScriptedFrameProviderMergeStrategyPrepend: Prepend scripted frames + before the real unwound frames. Useful for adding synthetic frames + at the top of the stack while preserving the actual callstack below. + + - lldb.eScriptedFrameProviderMergeStrategyAppend: Append scripted frames + after the real unwound frames. Useful for showing additional context + after the actual callstack ends. + + - lldb.eScriptedFrameProviderMergeStrategyReplaceByIndex: Replace specific + frames at given indices with scripted frames, keeping other real frames + intact. The idx field in each frame dictionary determines which real + frame to replace (e.g., idx=0 replaces frame 0, idx=2 replaces frame 2). + + The default implementation returns Replace strategy. + + Example: + + .. code-block:: python + + def get_merge_strategy(self): + # Only show our custom frames + return lldb.eScriptedFrameProviderMergeStrategyReplace + + def get_merge_strategy(self): + # Add diagnostic frames on top of real stack + return lldb.eScriptedFrameProviderMergeStrategyPrepend + + def get_merge_strategy(self): + # Replace frame 0 and frame 2 with custom frames, keep others + return lldb.eScriptedFrameProviderMergeStrategyReplaceByIndex + """ + return lldb.eScriptedFrameProviderMergeStrategyReplace + + @abstractmethod + def get_stackframes(self, real_frames): + """Get the list of stack frames to provide. + + This method is called when the thread's backtrace is requested + (e.g., via the 'bt' command). The returned frames will be integrated + with the real frames according to the mode returned by get_mode(). + + Args: + real_frames (lldb.SBFrameList): The actual unwound frames from the + thread's normal unwinder. This allows you to iterate, filter, + and selectively replace frames. The frames are materialized + lazily as you access them. + + Returns: + List[Dict]: A list of frame dictionaries, where each dictionary + describes a single stack frame. Each dictionary should contain: + + Required fields: + - idx (int): The frame index (0 for innermost/top frame) + - pc (int): The program counter address for this frame + + Alternatively, you can return a list of ScriptedFrame objects + for more control over frame behavior. + + Example: + + .. code-block:: python + + def get_stackframes(self, real_frames): + frames = [] + + # Iterate over real frames and filter/augment them + for i, frame in enumerate(real_frames): + if self.should_include_frame(frame): + frames.append({ + "idx": i, + "pc": frame.GetPC(), + }) + + # Or create custom frames + frames.append({ + "idx": 0, + "pc": 0x100001234, + }) + + return frames + + Note: + The frames are indexed from 0 (innermost/newest) to N (outermost/oldest). + """ + pass diff --git a/lldb/examples/python/templates/scripted_process.py b/lldb/examples/python/templates/scripted_process.py index 49059d533f38a..6242978100711 100644 --- a/lldb/examples/python/templates/scripted_process.py +++ b/lldb/examples/python/templates/scripted_process.py @@ -245,6 +245,7 @@ def __init__(self, process, args): key/value pairs used by the scripted thread. """ self.target = None + self.arch = None self.originating_process = None self.process = None self.args = None @@ -266,6 +267,9 @@ def __init__(self, process, args): and process.IsValid() ): self.target = process.target + triple = self.target.triple + if triple: + self.arch = triple.split("-")[0] self.originating_process = process self.process = self.target.GetProcess() self.get_register_info() @@ -352,17 +356,14 @@ def get_stackframes(self): def get_register_info(self): if self.register_info is None: self.register_info = dict() - if "x86_64" in self.originating_process.arch: + if "x86_64" in self.arch: self.register_info["sets"] = ["General Purpose Registers"] self.register_info["registers"] = INTEL64_GPR - elif ( - "arm64" in self.originating_process.arch - or self.originating_process.arch == "aarch64" - ): + elif "arm64" in self.arch or self.arch == "aarch64": self.register_info["sets"] = ["General Purpose Registers"] self.register_info["registers"] = ARM64_GPR else: - raise ValueError("Unknown architecture", self.originating_process.arch) + raise ValueError("Unknown architecture", self.arch) return self.register_info @abstractmethod @@ -405,11 +406,12 @@ def __init__(self, thread, args): """Construct a scripted frame. Args: - thread (ScriptedThread): The thread owning this frame. + thread (ScriptedThread/lldb.SBThread): The thread owning this frame. args (lldb.SBStructuredData): A Dictionary holding arbitrary key/value pairs used by the scripted frame. """ self.target = None + self.arch = None self.originating_thread = None self.thread = None self.args = None @@ -424,10 +426,14 @@ def __init__(self, thread, args): or isinstance(thread, lldb.SBThread) and thread.IsValid() ): - self.target = thread.target self.process = thread.process + self.target = self.process.target + triple = self.target.triple + if triple: + self.arch = triple.split("-")[0] + tid = thread.tid if isinstance(thread, ScriptedThread) else thread.id self.originating_thread = thread - self.thread = self.process.GetThreadByIndexID(thread.tid) + self.thread = self.process.GetThreadByIndexID(tid) self.get_register_info() @abstractmethod @@ -508,7 +514,18 @@ def get_variables(self, filters): def get_register_info(self): if self.register_info is None: - self.register_info = self.originating_thread.get_register_info() + if isinstance(self.originating_thread, ScriptedThread): + self.register_info = self.originating_thread.get_register_info() + elif isinstance(self.originating_thread, lldb.SBThread): + self.register_info = dict() + if "x86_64" in self.arch: + self.register_info["sets"] = ["General Purpose Registers"] + self.register_info["registers"] = INTEL64_GPR + elif "arm64" in self.arch or self.arch == "aarch64": + self.register_info["sets"] = ["General Purpose Registers"] + self.register_info["registers"] = ARM64_GPR + else: + raise ValueError("Unknown architecture", self.arch) return self.register_info @abstractmethod @@ -642,12 +659,12 @@ def get_stop_reason(self): # TODO: Passthrough stop reason from driving process if self.driving_thread.GetStopReason() != lldb.eStopReasonNone: - if "arm64" in self.originating_process.arch: + if "arm64" in self.arch: stop_reason["type"] = lldb.eStopReasonException stop_reason["data"]["desc"] = ( self.driving_thread.GetStopDescription(100) ) - elif self.originating_process.arch == "x86_64": + elif self.arch == "x86_64": stop_reason["type"] = lldb.eStopReasonSignal stop_reason["data"]["signal"] = signal.SIGTRAP else: diff --git a/lldb/include/lldb/API/SBFrameList.h b/lldb/include/lldb/API/SBFrameList.h index 87b72a76384df..680dd62f9ce4a 100644 --- a/lldb/include/lldb/API/SBFrameList.h +++ b/lldb/include/lldb/API/SBFrameList.h @@ -11,6 +11,16 @@ #include "lldb/API/SBDefines.h" +namespace lldb_private { +class ScriptInterpreter; +namespace python { +class SWIGBridge; +} +namespace lua { +class SWIGBridge; +} +} // namespace lldb_private + namespace lldb { class LLDB_API SBFrameList { @@ -42,6 +52,10 @@ class LLDB_API SBFrameList { protected: friend class SBThread; + friend class lldb_private::python::SWIGBridge; + friend class lldb_private::lua::SWIGBridge; + friend class lldb_private::ScriptInterpreter; + private: SBFrameList(const lldb::StackFrameListSP &frame_list_sp); diff --git a/lldb/include/lldb/API/SBThread.h b/lldb/include/lldb/API/SBThread.h index 9293a7f9ebab9..de91a2c366d8c 100644 --- a/lldb/include/lldb/API/SBThread.h +++ b/lldb/include/lldb/API/SBThread.h @@ -231,6 +231,11 @@ class LLDB_API SBThread { SBValue GetSiginfo(); + SBError RegisterFrameProvider(const char *class_name, + SBStructuredData &args_data); + + void ClearScriptedFrameProvider(); + private: friend class SBBreakpoint; friend class SBBreakpointLocation; diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h new file mode 100644 index 0000000000000..99df9b765a3a7 --- /dev/null +++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h @@ -0,0 +1,36 @@ +//===-- ScriptedFrameProviderInterface.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_INTERFACES_SCRIPTEDFRAMEPROVIDERINTERFACE_H +#define LLDB_INTERPRETER_INTERFACES_SCRIPTEDFRAMEPROVIDERINTERFACE_H + +#include "lldb/lldb-private.h" + +#include "ScriptedInterface.h" + +namespace lldb_private { +class ScriptedFrameProviderInterface : public ScriptedInterface { +public: + virtual llvm::Expected + CreatePluginObject(llvm::StringRef class_name, lldb::ThreadSP thread_sp, + StructuredData::DictionarySP args_sp) = 0; + + /// Get the merge strategy for how scripted frames should be integrated with + /// real frames + virtual lldb::ScriptedFrameProviderMergeStrategy GetMergeStrategy() { + return lldb::eScriptedFrameProviderMergeStrategyReplace; + } + + virtual StructuredData::ArraySP + GetStackFrames(lldb::StackFrameListSP real_frames) { + return {}; + } +}; +} // namespace lldb_private + +#endif // LLDB_INTERPRETER_INTERFACES_SCRIPTEDFRAMEPROVIDERINTERFACE_H diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h index 6c0054a1ec1d1..f4c204d5c08e5 100644 --- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h +++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h @@ -16,6 +16,7 @@ #include "lldb/API/SBError.h" #include "lldb/API/SBEvent.h" #include "lldb/API/SBExecutionContext.h" +#include "lldb/API/SBFrameList.h" #include "lldb/API/SBLaunchInfo.h" #include "lldb/API/SBMemoryRegionInfo.h" #include "lldb/API/SBStream.h" @@ -28,6 +29,7 @@ #include "lldb/Host/StreamFile.h" #include "lldb/Interpreter/Interfaces/OperatingSystemInterface.h" #include "lldb/Interpreter/Interfaces/ScriptedFrameInterface.h" +#include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h" #include "lldb/Interpreter/Interfaces/ScriptedPlatformInterface.h" #include "lldb/Interpreter/Interfaces/ScriptedProcessInterface.h" #include "lldb/Interpreter/Interfaces/ScriptedThreadInterface.h" @@ -537,6 +539,11 @@ class ScriptInterpreter : public PluginInterface { return {}; } + virtual lldb::ScriptedFrameProviderInterfaceSP + CreateScriptedFrameProviderInterface() { + return {}; + } + virtual lldb::ScriptedThreadPlanInterfaceSP CreateScriptedThreadPlanInterface() { return {}; @@ -596,6 +603,9 @@ class ScriptInterpreter : public PluginInterface { lldb::ExecutionContextRefSP GetOpaqueTypeFromSBExecutionContext( const lldb::SBExecutionContext &exe_ctx) const; + lldb::StackFrameListSP + GetOpaqueTypeFromSBFrameList(const lldb::SBFrameList &exe_ctx) const; + protected: Debugger &m_debugger; lldb::ScriptLanguage m_script_lang; diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.h b/lldb/include/lldb/Interpreter/ScriptedFrame.h similarity index 60% rename from lldb/source/Plugins/Process/scripted/ScriptedFrame.h rename to lldb/include/lldb/Interpreter/ScriptedFrame.h index 6e01e2fd7653e..6d3f1e5506bc4 100644 --- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.h +++ b/lldb/include/lldb/Interpreter/ScriptedFrame.h @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===-- ScriptedFrame.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. @@ -6,26 +6,22 @@ // //===----------------------------------------------------------------------===// -#ifndef LLDB_SOURCE_PLUGINS_SCRIPTED_FRAME_H -#define LLDB_SOURCE_PLUGINS_SCRIPTED_FRAME_H +#ifndef LLDB_INTERPRETER_SCRIPTEDFRAME_H +#define LLDB_INTERPRETER_SCRIPTEDFRAME_H -#include "Plugins/Process/Utility/RegisterContextMemory.h" -#include "ScriptedThread.h" -#include "lldb/Interpreter/ScriptInterpreter.h" #include "lldb/Target/DynamicRegisterInfo.h" #include "lldb/Target/StackFrame.h" +#include "lldb/lldb-forward.h" +#include "llvm/Support/Error.h" +#include #include -namespace lldb_private { -class ScriptedThread; -} - namespace lldb_private { class ScriptedFrame : public lldb_private::StackFrame { public: - ScriptedFrame(ScriptedThread &thread, + ScriptedFrame(lldb::ThreadSP thread_sp, lldb::ScriptedFrameInterfaceSP interface_sp, lldb::user_id_t frame_idx, lldb::addr_t pc, SymbolContext &sym_ctx, lldb::RegisterContextSP reg_ctx_sp, @@ -34,8 +30,28 @@ class ScriptedFrame : public lldb_private::StackFrame { ~ScriptedFrame() override; + /// Create a ScriptedFrame from a script object. + /// + /// \param[in] thread_sp + /// The thread this frame belongs to. + /// + /// \param[in] scripted_thread_interface_sp + /// The scripted thread interface (needed for ScriptedThread + /// compatibility). Can be nullptr for frames on real threads. + /// + /// \param[in] args_sp + /// Arguments to pass to the frame creation. + /// + /// \param[in] script_object + /// The script object representing this frame. + /// + /// \return + /// An Expected containing the ScriptedFrame shared pointer if successful, + /// otherwise an error. static llvm::Expected> - Create(ScriptedThread &thread, StructuredData::DictionarySP args_sp, + Create(lldb::ThreadSP thread_sp, + lldb::ScriptedThreadInterfaceSP scripted_thread_interface_sp, + StructuredData::DictionarySP args_sp, StructuredData::Generic *script_object = nullptr); bool IsInlined() override; @@ -60,4 +76,4 @@ class ScriptedFrame : public lldb_private::StackFrame { } // namespace lldb_private -#endif // LLDB_SOURCE_PLUGINS_SCRIPTED_FRAME_H +#endif // LLDB_INTERPRETER_SCRIPTEDFRAME_H \ No newline at end of file diff --git a/lldb/include/lldb/Interpreter/ScriptedFrameProvider.h b/lldb/include/lldb/Interpreter/ScriptedFrameProvider.h new file mode 100644 index 0000000000000..5a84afd29d7e0 --- /dev/null +++ b/lldb/include/lldb/Interpreter/ScriptedFrameProvider.h @@ -0,0 +1,58 @@ +//===-- ScriptedFrameProvider.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_SCRIPTEDFRAMEPROVIDER_H +#define LLDB_INTERPRETER_SCRIPTEDFRAMEPROVIDER_H + +#include "lldb/Utility/ScriptedMetadata.h" +#include "lldb/Utility/Status.h" +#include "lldb/lldb-forward.h" +#include "llvm/Support/Error.h" + +namespace lldb_private { + +class ScriptedFrameProvider { +public: + /// Constructor that initializes the scripted frame provider. + /// + /// \param[in] thread_sp + /// The thread for which to provide scripted frames. + /// + /// \param[in] scripted_metadata + /// The metadata containing the class name and arguments for the + /// scripted frame provider. + /// + /// \param[out] error + /// Status object to report any errors during initialization. + ScriptedFrameProvider(lldb::ThreadSP thread_sp, + const ScriptedMetadata &scripted_metadata, + Status &error); + ~ScriptedFrameProvider(); + + /// Get the stack frames from the scripted frame provider. + /// + /// \return + /// An Expected containing the StackFrameListSP if successful, + /// otherwise an error describing what went wrong. + llvm::Expected + GetStackFrames(lldb::StackFrameListSP real_frames); + + /// Get the merge strategy for how scripted frames should be integrated. + /// + /// \return + /// The ScriptedFrameProviderMergeStrategy indicating how to merge frames. + lldb::ScriptedFrameProviderMergeStrategy GetMergeStrategy(); + +private: + lldb::ThreadSP m_thread_sp; + lldb::ScriptedFrameProviderInterfaceSP m_interface_sp; +}; + +} // namespace lldb_private + +#endif // LLDB_INTERPRETER_SCRIPTEDFRAMEPROVIDER_H diff --git a/lldb/source/Plugins/Process/Utility/RegisterContextMemory.h b/lldb/include/lldb/Target/RegisterContextMemory.h similarity index 92% rename from lldb/source/Plugins/Process/Utility/RegisterContextMemory.h rename to lldb/include/lldb/Target/RegisterContextMemory.h index 2aad99ec9b210..d156a5c881267 100644 --- a/lldb/source/Plugins/Process/Utility/RegisterContextMemory.h +++ b/lldb/include/lldb/Target/RegisterContextMemory.h @@ -6,8 +6,8 @@ // //===----------------------------------------------------------------------===// -#ifndef LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_REGISTERCONTEXTMEMORY_H -#define LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_REGISTERCONTEXTMEMORY_H +#ifndef LLDB_TARGET_REGISTERCONTEXTMEMORY_H +#define LLDB_TARGET_REGISTERCONTEXTMEMORY_H #include @@ -72,4 +72,4 @@ class RegisterContextMemory : public lldb_private::RegisterContext { operator=(const RegisterContextMemory &) = delete; }; -#endif // LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_REGISTERCONTEXTMEMORY_H +#endif // LLDB_TARGET_REGISTERCONTEXTMEMORY_H diff --git a/lldb/include/lldb/Target/StackFrame.h b/lldb/include/lldb/Target/StackFrame.h index cdbe8ae3c6779..9cfbf58df0e2d 100644 --- a/lldb/include/lldb/Target/StackFrame.h +++ b/lldb/include/lldb/Target/StackFrame.h @@ -442,7 +442,10 @@ class StackFrame : public ExecutionContextScope, uint32_t GetFrameIndex() const; /// Set this frame's synthetic frame index. - void SetFrameIndex(uint32_t index) { m_frame_index = index; } + void SetFrameIndex(uint32_t index) { + m_frame_index = index; + m_concrete_frame_index = index; + } /// Query this frame to find what frame it is in this Thread's /// StackFrameList, not counting inlined frames. diff --git a/lldb/include/lldb/Target/StackFrameList.h b/lldb/include/lldb/Target/StackFrameList.h index ea9aab86b8ea1..c99f9fd27dd3a 100644 --- a/lldb/include/lldb/Target/StackFrameList.h +++ b/lldb/include/lldb/Target/StackFrameList.h @@ -103,6 +103,7 @@ class StackFrameList { protected: friend class Thread; + friend class ScriptedFrameProvider; friend class ScriptedThread; /// Use this API to build a stack frame list (used for scripted threads, for diff --git a/lldb/include/lldb/Target/Thread.h b/lldb/include/lldb/Target/Thread.h index ca356d546db5d..237bcbc11325e 100644 --- a/lldb/include/lldb/Target/Thread.h +++ b/lldb/include/lldb/Target/Thread.h @@ -1297,6 +1297,10 @@ class Thread : public std::enable_shared_from_this, lldb::StackFrameListSP GetStackFrameList(); + Status SetScriptedFrameProvider(const ScriptedMetadata &scripted_metadata); + + void ClearScriptedFrameProvider(); + protected: friend class ThreadPlan; friend class ThreadList; @@ -1338,18 +1342,9 @@ class Thread : public std::enable_shared_from_this, return StructuredData::ObjectSP(); } -<<<<<<< HEAD - lldb::StackFrameListSP GetStackFrameList(); - -||||||| parent of e4e31827a1bf ([lldb] Introduce SBFrameList for lazy frame iteration) - lldb::StackFrameListSP GetStackFrameList(); - - llvm::Expected GetScriptedFrameList(); + llvm::Expected + GetScriptedFrameList(lldb::StackFrameListSP real_frames_sp); -======= - llvm::Expected GetScriptedFrameList(); - ->>>>>>> e4e31827a1bf ([lldb] Introduce SBFrameList for lazy frame iteration) void SetTemporaryResumeState(lldb::StateType new_state) { m_temporary_resume_state = new_state; } @@ -1412,6 +1407,9 @@ class Thread : public std::enable_shared_from_this, /// The Thread backed by this thread, if any. lldb::ThreadWP m_backed_thread; + /// The Scripted Frame Provider, if any. + lldb::ScriptedFrameProviderSP m_frame_provider_sp; + private: bool m_extended_info_fetched; // Have we tried to retrieve the m_extended_info // for this thread? diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h index fec9fdef44df9..d52823f3674c0 100644 --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -266,6 +266,19 @@ enum StopReason { eStopReasonHistoryBoundary, }; +/// Scripted Frame Provider Merge Strategies. +enum ScriptedFrameProviderMergeStrategy { + /// Replace the entire stack with scripted frames + eScriptedFrameProviderMergeStrategyReplace = 0, + /// Prepend scripted frames before real unwound frames + eScriptedFrameProviderMergeStrategyPrepend, + /// Append scripted frames after real unwound frames + eScriptedFrameProviderMergeStrategyAppend, + /// Replace specific frame indices with scripted frames, keeping other real + /// frames + eScriptedFrameProviderMergeStrategyReplaceByIndex, +}; + /// Command Return Status Types. enum ReturnStatus { eReturnStatusInvalid, diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h index af5656b3dcad1..85045a803b07a 100644 --- a/lldb/include/lldb/lldb-forward.h +++ b/lldb/include/lldb/lldb-forward.h @@ -188,6 +188,8 @@ class Scalar; class ScriptInterpreter; class ScriptInterpreterLocker; class ScriptedFrameInterface; +class ScriptedFrameProvider; +class ScriptedFrameProviderInterface; class ScriptedMetadata; class ScriptedBreakpointInterface; class ScriptedPlatformInterface; @@ -411,6 +413,10 @@ typedef std::shared_ptr typedef std::shared_ptr ScriptInterpreterSP; typedef std::shared_ptr ScriptedFrameInterfaceSP; +typedef std::shared_ptr + ScriptedFrameProviderSP; +typedef std::shared_ptr + ScriptedFrameProviderInterfaceSP; typedef std::shared_ptr ScriptedMetadataSP; typedef std::unique_ptr ScriptedPlatformInterfaceUP; diff --git a/lldb/source/API/SBThread.cpp b/lldb/source/API/SBThread.cpp index ba1a492069ea6..1f13d32d3c6c9 100644 --- a/lldb/source/API/SBThread.cpp +++ b/lldb/source/API/SBThread.cpp @@ -40,6 +40,7 @@ #include "lldb/Target/ThreadPlanStepOut.h" #include "lldb/Target/ThreadPlanStepRange.h" #include "lldb/Utility/Instrumentation.h" +#include "lldb/Utility/ScriptedMetadata.h" #include "lldb/Utility/State.h" #include "lldb/Utility/Stream.h" #include "lldb/Utility/StructuredData.h" @@ -1345,3 +1346,32 @@ SBValue SBThread::GetSiginfo() { return SBValue(); return thread_sp->GetSiginfoValue(); } + +SBError SBThread::RegisterFrameProvider(const char *class_name, + SBStructuredData &dict) { + LLDB_INSTRUMENT_VA(this, class_name, dict); + + ThreadSP thread_sp = m_opaque_sp->GetThreadSP(); + if (!thread_sp) + return SBError("invalid thread"); + + if (!dict.m_impl_up) + return SBError("invalid dictionary"); + + StructuredData::DictionarySP dict_sp = + std::make_shared( + dict.m_impl_up->GetObjectSP()); + if (!dict_sp) + return SBError("invalid dictionary"); + + ScriptedMetadata metadata(class_name, dict_sp); + return SBError(thread_sp->SetScriptedFrameProvider(metadata)); +} + +void SBThread::ClearScriptedFrameProvider() { + LLDB_INSTRUMENT_VA(this); + + ThreadSP thread_sp = m_opaque_sp->GetThreadSP(); + if (thread_sp) + thread_sp->ClearScriptedFrameProvider(); +} diff --git a/lldb/source/Commands/CommandObjectFrame.cpp b/lldb/source/Commands/CommandObjectFrame.cpp index 88a02dce35b9d..7dabc8f22b8ad 100644 --- a/lldb/source/Commands/CommandObjectFrame.cpp +++ b/lldb/source/Commands/CommandObjectFrame.cpp @@ -16,6 +16,7 @@ #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Interpreter/OptionArgParser.h" #include "lldb/Interpreter/OptionGroupFormat.h" +#include "lldb/Interpreter/OptionGroupPythonClassWithDict.h" #include "lldb/Interpreter/OptionGroupValueObjectDisplay.h" #include "lldb/Interpreter/OptionGroupVariable.h" #include "lldb/Interpreter/Options.h" @@ -29,6 +30,7 @@ #include "lldb/Target/Target.h" #include "lldb/Target/Thread.h" #include "lldb/Utility/Args.h" +#include "lldb/Utility/ScriptedMetadata.h" #include "lldb/ValueObject/ValueObject.h" #include @@ -1223,6 +1225,98 @@ class CommandObjectFrameRecognizer : public CommandObjectMultiword { ~CommandObjectFrameRecognizer() override = default; }; +#pragma mark CommandObjectFrameProvider + +#define LLDB_OPTIONS_frame_provider_register +#include "CommandOptions.inc" + +class CommandObjectFrameProviderRegister : public CommandObjectParsed { +public: + CommandObjectFrameProviderRegister(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "frame provider register", + "Register frame provider into current thread.", + nullptr, eCommandRequiresThread), + + m_class_options("frame provider", true, 'C', 'k', 'v', 0) { + m_all_options.Append(&m_class_options, LLDB_OPT_SET_1 | LLDB_OPT_SET_2, + LLDB_OPT_SET_ALL); + m_all_options.Finalize(); + + AddSimpleArgumentList(eArgTypeRunArgs, eArgRepeatOptional); + } + + ~CommandObjectFrameProviderRegister() override = default; + + Options *GetOptions() override { return &m_all_options; } + + std::optional GetRepeatCommand(Args ¤t_command_args, + uint32_t index) override { + // No repeat for "process launch"... + return std::string(""); + } + +protected: + void DoExecute(Args &launch_args, CommandReturnObject &result) override { + ScriptedMetadata metadata(m_class_options.GetName(), + m_class_options.GetStructuredData()); + + Thread *thread = m_exe_ctx.GetThreadPtr(); + if (!thread) { + result.AppendError("invalid thread"); + return; + } + + Status error = thread->SetScriptedFrameProvider(metadata); + if (error.Success()) + result.AppendMessageWithFormat( + "Successfully registered scripted frame provider '%s'\n", + m_class_options.GetName().c_str()); + result.SetError(std::move(error)); + } + + OptionGroupPythonClassWithDict m_class_options; + OptionGroupOptions m_all_options; +}; + +class CommandObjectFrameProviderClear : public CommandObjectParsed { +public: + CommandObjectFrameProviderClear(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "frame provider clear", + "Delete registered frame provider.", nullptr) {} + + ~CommandObjectFrameProviderClear() override = default; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + Thread *thread = m_exe_ctx.GetThreadPtr(); + if (!thread) { + result.AppendError("invalid thread"); + return; + } + + thread->ClearScriptedFrameProvider(); + + result.SetStatus(eReturnStatusSuccessFinishResult); + } +}; + +class CommandObjectFrameProvider : public CommandObjectMultiword { +public: + CommandObjectFrameProvider(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "frame provider", + "Commands for registering and viewing frame providers.", + "frame provider [] ") { + LoadSubCommand( + "register", + CommandObjectSP(new CommandObjectFrameProviderRegister(interpreter))); + LoadSubCommand("clear", CommandObjectSP(new CommandObjectFrameProviderClear( + interpreter))); + } + + ~CommandObjectFrameProvider() override = default; +}; + #pragma mark CommandObjectMultiwordFrame // CommandObjectMultiwordFrame @@ -1243,6 +1337,8 @@ CommandObjectMultiwordFrame::CommandObjectMultiwordFrame( LoadSubCommand("variable", CommandObjectSP(new CommandObjectFrameVariable(interpreter))); #if LLDB_ENABLE_PYTHON + LoadSubCommand("provider", + CommandObjectSP(new CommandObjectFrameProvider(interpreter))); LoadSubCommand("recognizer", CommandObjectSP(new CommandObjectFrameRecognizer( interpreter))); #endif diff --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp index bbec714642ec9..0092151a13dd8 100644 --- a/lldb/source/Commands/CommandObjectThread.cpp +++ b/lldb/source/Commands/CommandObjectThread.cpp @@ -35,6 +35,7 @@ #include "lldb/Target/ThreadPlanStepInRange.h" #include "lldb/Target/Trace.h" #include "lldb/Target/TraceDumper.h" +#include "lldb/Utility/ScriptedMetadata.h" #include "lldb/Utility/State.h" #include "lldb/ValueObject/ValueObject.h" diff --git a/lldb/source/Interpreter/CMakeLists.txt b/lldb/source/Interpreter/CMakeLists.txt index 8af7373702c38..7efc5b4efad89 100644 --- a/lldb/source/Interpreter/CMakeLists.txt +++ b/lldb/source/Interpreter/CMakeLists.txt @@ -53,6 +53,8 @@ add_lldb_library(lldbInterpreter NO_PLUGIN_DEPENDENCIES OptionGroupWatchpoint.cpp Options.cpp Property.cpp + ScriptedFrame.cpp + ScriptedFrameProvider.cpp ScriptInterpreter.cpp ADDITIONAL_HEADER_DIRS diff --git a/lldb/source/Interpreter/ScriptInterpreter.cpp b/lldb/source/Interpreter/ScriptInterpreter.cpp index ca768db1199c1..211868b51facb 100644 --- a/lldb/source/Interpreter/ScriptInterpreter.cpp +++ b/lldb/source/Interpreter/ScriptInterpreter.cpp @@ -150,6 +150,11 @@ ScriptInterpreter::GetOpaqueTypeFromSBExecutionContext( return exe_ctx.m_exe_ctx_sp; } +lldb::StackFrameListSP ScriptInterpreter::GetOpaqueTypeFromSBFrameList( + const lldb::SBFrameList &frame_list) const { + return frame_list.m_opaque_sp; +} + lldb::ScriptLanguage ScriptInterpreter::StringToLanguage(const llvm::StringRef &language) { if (language.equals_insensitive(LanguageToString(eScriptLanguageNone))) diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp b/lldb/source/Interpreter/ScriptedFrame.cpp similarity index 66% rename from lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp rename to lldb/source/Interpreter/ScriptedFrame.cpp index 6519df9185df0..2c31a2a2b6e72 100644 --- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp +++ b/lldb/source/Interpreter/ScriptedFrame.cpp @@ -1,4 +1,4 @@ -//===----------------------------------------------------------------------===// +//===-- ScriptedFrame.cpp -------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,9 +6,23 @@ // //===----------------------------------------------------------------------===// -#include "ScriptedFrame.h" - +#include "lldb/Interpreter/ScriptedFrame.h" + +#include "lldb/Core/Address.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Interpreter/Interfaces/ScriptedFrameInterface.h" +#include "lldb/Interpreter/Interfaces/ScriptedThreadInterface.h" +#include "lldb/Interpreter/ScriptInterpreter.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/RegisterContextMemory.h" +#include "lldb/Target/Thread.h" #include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/StructuredData.h" using namespace lldb; using namespace lldb_private; @@ -19,42 +33,56 @@ void ScriptedFrame::CheckInterpreterAndScriptObject() const { } llvm::Expected> -ScriptedFrame::Create(ScriptedThread &thread, +ScriptedFrame::Create(ThreadSP thread_sp, + ScriptedThreadInterfaceSP scripted_thread_interface_sp, StructuredData::DictionarySP args_sp, StructuredData::Generic *script_object) { - if (!thread.IsValid()) - return llvm::createStringError("Invalid scripted thread."); + if (!thread_sp || !thread_sp->IsValid()) + return llvm::createStringError("Invalid thread."); + + ProcessSP process_sp = thread_sp->GetProcess(); + if (!process_sp || !process_sp->IsValid()) + return llvm::createStringError("Invalid process."); - thread.CheckInterpreterAndScriptObject(); + ScriptInterpreter *script_interp = + process_sp->GetTarget().GetDebugger().GetScriptInterpreter(); + if (!script_interp) + return llvm::createStringError("No script interpreter."); - auto scripted_frame_interface = - thread.GetInterface()->CreateScriptedFrameInterface(); + auto scripted_frame_interface = script_interp->CreateScriptedFrameInterface(); if (!scripted_frame_interface) - return llvm::createStringError("failed to create scripted frame interface"); + return llvm::createStringError("Failed to create scripted frame interface"); llvm::StringRef frame_class_name; if (!script_object) { - std::optional class_name = - thread.GetInterface()->GetScriptedFramePluginName(); - if (!class_name || class_name->empty()) + // If no script object is provided and we have a scripted thread interface, + // try to get the frame class name from it + if (scripted_thread_interface_sp) { + std::optional class_name = + scripted_thread_interface_sp->GetScriptedFramePluginName(); + if (!class_name || class_name->empty()) + return llvm::createStringError( + "Failed to get scripted frame class name"); + frame_class_name = *class_name; + } else { return llvm::createStringError( - "failed to get scripted thread class name"); - frame_class_name = *class_name; + "No script object provided and no scripted thread interface"); + } } - ExecutionContext exe_ctx(thread); + ExecutionContext exe_ctx(thread_sp); auto obj_or_err = scripted_frame_interface->CreatePluginObject( frame_class_name, exe_ctx, args_sp, script_object); if (!obj_or_err) return llvm::createStringError( - "failed to create script object: %s", + "Failed to create script object: %s", llvm::toString(obj_or_err.takeError()).c_str()); StructuredData::GenericSP owned_script_object_sp = *obj_or_err; if (!owned_script_object_sp->IsValid()) - return llvm::createStringError("created script object is invalid"); + return llvm::createStringError("Created script object is invalid"); lldb::user_id_t frame_id = scripted_frame_interface->GetID(); @@ -62,7 +90,7 @@ ScriptedFrame::Create(ScriptedThread &thread, SymbolContext sc; Address symbol_addr; if (pc != LLDB_INVALID_ADDRESS) { - symbol_addr.SetLoadAddress(pc, &thread.GetProcess()->GetTarget()); + symbol_addr.SetLoadAddress(pc, &process_sp->GetTarget()); symbol_addr.CalculateSymbolContext(&sc); } @@ -77,11 +105,11 @@ ScriptedFrame::Create(ScriptedThread &thread, if (!reg_info) return llvm::createStringError( - "failed to get scripted thread registers info"); + "Failed to get scripted frame registers info"); std::shared_ptr register_info_sp = - DynamicRegisterInfo::Create( - *reg_info, thread.GetProcess()->GetTarget().GetArchitecture()); + DynamicRegisterInfo::Create(*reg_info, + process_sp->GetTarget().GetArchitecture()); lldb::RegisterContextSP reg_ctx_sp; @@ -92,31 +120,31 @@ ScriptedFrame::Create(ScriptedThread &thread, std::make_shared(reg_data->c_str(), reg_data->size())); if (!data_sp->GetByteSize()) - return llvm::createStringError("failed to copy raw registers data"); + return llvm::createStringError("Failed to copy raw registers data"); std::shared_ptr reg_ctx_memory = std::make_shared( - thread, frame_id, *register_info_sp, LLDB_INVALID_ADDRESS); + *thread_sp, frame_id, *register_info_sp, LLDB_INVALID_ADDRESS); if (!reg_ctx_memory) - return llvm::createStringError("failed to create a register context."); + return llvm::createStringError("Failed to create a register context."); reg_ctx_memory->SetAllRegisterData(data_sp); reg_ctx_sp = reg_ctx_memory; } return std::make_shared( - thread, scripted_frame_interface, frame_id, pc, sc, reg_ctx_sp, + thread_sp, scripted_frame_interface, frame_id, pc, sc, reg_ctx_sp, register_info_sp, owned_script_object_sp); } -ScriptedFrame::ScriptedFrame(ScriptedThread &thread, +ScriptedFrame::ScriptedFrame(ThreadSP thread_sp, ScriptedFrameInterfaceSP interface_sp, lldb::user_id_t id, lldb::addr_t pc, SymbolContext &sym_ctx, lldb::RegisterContextSP reg_ctx_sp, std::shared_ptr reg_info_sp, StructuredData::GenericSP script_object_sp) - : StackFrame(thread.shared_from_this(), /*frame_idx=*/id, + : StackFrame(thread_sp, /*frame_idx=*/id, /*concrete_frame_idx=*/id, /*reg_context_sp=*/reg_ctx_sp, /*cfa=*/0, /*pc=*/pc, /*behaves_like_zeroth_frame=*/!id, /*symbol_ctx=*/&sym_ctx), diff --git a/lldb/source/Interpreter/ScriptedFrameProvider.cpp b/lldb/source/Interpreter/ScriptedFrameProvider.cpp new file mode 100644 index 0000000000000..4c2d8f7d41cb7 --- /dev/null +++ b/lldb/source/Interpreter/ScriptedFrameProvider.cpp @@ -0,0 +1,197 @@ +//===-- ScriptedFrameProvider.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/ScriptedFrameProvider.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h" +#include "lldb/Interpreter/ScriptInterpreter.h" +#include "lldb/Interpreter/ScriptedFrame.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/ScriptedMetadata.h" +#include "lldb/Utility/Status.h" +#include "llvm/Support/Error.h" + +using namespace lldb; +using namespace lldb_private; + +ScriptedFrameProvider::ScriptedFrameProvider( + ThreadSP thread_sp, const ScriptedMetadata &scripted_metadata, + Status &error) + : m_thread_sp(thread_sp), m_interface_sp(nullptr) { + if (!m_thread_sp) { + error = Status::FromErrorString( + "cannot create scripted frame provider: Invalid thread"); + return; + } + + ProcessSP process_sp = m_thread_sp->GetProcess(); + if (!process_sp) { + error = Status::FromErrorString( + "cannot create scripted frame provider: Invalid process"); + return; + } + + ScriptInterpreter *script_interp = + process_sp->GetTarget().GetDebugger().GetScriptInterpreter(); + if (!script_interp) { + error = Status::FromErrorString("cannot create scripted frame provider: No " + "script interpreter installed"); + return; + } + + m_interface_sp = script_interp->CreateScriptedFrameProviderInterface(); + if (!m_interface_sp) { + error = Status::FromErrorString( + "cannot create scripted frame provider: Script interpreter couldn't " + "create Scripted Frame Provider Interface"); + return; + } + + auto obj_or_err = m_interface_sp->CreatePluginObject( + scripted_metadata.GetClassName(), m_thread_sp, + scripted_metadata.GetArgsSP()); + if (!obj_or_err) { + error = Status::FromError(obj_or_err.takeError()); + return; + } + + StructuredData::ObjectSP object_sp = *obj_or_err; + if (!object_sp || !object_sp->IsValid()) { + error = Status::FromErrorString( + "cannot create scripted frame provider: Failed to create valid script " + "object"); + return; + } + + error.Clear(); +} + +ScriptedFrameProvider::~ScriptedFrameProvider() = default; + +lldb::ScriptedFrameProviderMergeStrategy +ScriptedFrameProvider::GetMergeStrategy() { + if (!m_interface_sp) + return lldb::eScriptedFrameProviderMergeStrategyReplace; + + return m_interface_sp->GetMergeStrategy(); +} + +llvm::Expected +ScriptedFrameProvider::GetStackFrames(StackFrameListSP real_frames) { + if (!m_interface_sp) + return llvm::createStringError( + "cannot get stack frames: Scripted frame provider not initialized"); + + StructuredData::ArraySP arr_sp = m_interface_sp->GetStackFrames(real_frames); + + Status error; + if (!arr_sp) + return llvm::createStringError( + "Failed to get scripted thread stackframes."); + + size_t arr_size = arr_sp->GetSize(); + if (arr_size > std::numeric_limits::max()) + return llvm::createStringError(llvm::Twine( + "StackFrame array size (" + llvm::Twine(arr_size) + + llvm::Twine( + ") is greater than maximum authorized for a StackFrameList."))); + + auto create_frame_from_dict = + [this, arr_sp](size_t iteration_idx) -> llvm::Expected { + std::optional maybe_dict = + arr_sp->GetItemAtIndexAsDictionary(iteration_idx); + if (!maybe_dict) + return llvm::createStringError("invalid scripted frame dictionary."); + StructuredData::Dictionary *dict = *maybe_dict; + + lldb::addr_t pc; + if (!dict->GetValueForKeyAsInteger("pc", pc)) + return llvm::createStringError( + "missing 'pc' key from scripted frame dictionary."); + + // For ReplaceByIndex strategy, use the idx from dictionary if provided + // For other strategies (Replace, Prepend, Append), use iteration index + uint64_t frame_idx = iteration_idx; + if (!dict->GetValueForKeyAsInteger("idx", frame_idx)) + return llvm::createStringError( + "missing 'idx' key from scripted frame dictionary."); + + Address symbol_addr; + symbol_addr.SetLoadAddress(pc, &m_thread_sp->GetProcess()->GetTarget()); + + lldb::addr_t cfa = LLDB_INVALID_ADDRESS; + bool cfa_is_valid = false; + const bool artificial = false; + const bool behaves_like_zeroth_frame = false; + SymbolContext sc; + symbol_addr.CalculateSymbolContext(&sc); + + return std::make_shared(m_thread_sp, frame_idx, frame_idx, cfa, + cfa_is_valid, pc, + StackFrame::Kind::Synthetic, artificial, + behaves_like_zeroth_frame, &sc); + }; + + auto create_frame_from_script_object = + [this, arr_sp](size_t idx) -> llvm::Expected { + Status error; + StructuredData::ObjectSP object_sp = arr_sp->GetItemAtIndex(idx); + if (!object_sp || !object_sp->GetAsGeneric()) + return error.ToError(); + + auto frame_or_error = ScriptedFrame::Create(m_thread_sp, nullptr, nullptr, + object_sp->GetAsGeneric()); + + if (!frame_or_error) { + ScriptedInterface::ErrorWithMessage( + LLVM_PRETTY_FUNCTION, toString(frame_or_error.takeError()), error); + return error.ToError(); + } + + StackFrameSP frame_sp = frame_or_error.get(); + lldbassert(frame_sp && "Couldn't initialize scripted frame."); + + return frame_sp; + }; + + StackFrameListSP scripted_frames = + std::make_shared(*m_thread_sp, StackFrameListSP(), true); + + for (size_t idx = 0; idx < arr_size; idx++) { + StackFrameSP synth_frame_sp = nullptr; + + auto frame_from_dict_or_err = create_frame_from_dict(idx); + if (!frame_from_dict_or_err) { + auto frame_from_script_obj_or_err = create_frame_from_script_object(idx); + + if (!frame_from_script_obj_or_err) { + return llvm::createStringError( + llvm::Twine("Couldn't add artificial frame (" + llvm::Twine(idx) + + llvm::Twine(") to ScriptedThread StackFrameList."))); + } else { + llvm::consumeError(frame_from_dict_or_err.takeError()); + synth_frame_sp = *frame_from_script_obj_or_err; + } + } else { + synth_frame_sp = *frame_from_dict_or_err; + } + + if (!scripted_frames->SetFrameAtIndex(static_cast(idx), + synth_frame_sp)) + return llvm::createStringError( + llvm::Twine("Couldn't add frame (" + llvm::Twine(idx) + + llvm::Twine(") to ScriptedThread StackFrameList."))); + } + + // Mark that all frames have been fetched to prevent automatic unwinding + scripted_frames->SetAllFramesFetched(); + + return scripted_frames; +} diff --git a/lldb/source/Plugins/OperatingSystem/Python/OperatingSystemPython.cpp b/lldb/source/Plugins/OperatingSystem/Python/OperatingSystemPython.cpp index 96b2b9d9ee088..5c559b36b555d 100644 --- a/lldb/source/Plugins/OperatingSystem/Python/OperatingSystemPython.cpp +++ b/lldb/source/Plugins/OperatingSystem/Python/OperatingSystemPython.cpp @@ -13,7 +13,6 @@ #include "OperatingSystemPython.h" #include "Plugins/Process/Utility/RegisterContextDummy.h" -#include "Plugins/Process/Utility/RegisterContextMemory.h" #include "Plugins/Process/Utility/ThreadMemory.h" #include "lldb/Core/Debugger.h" #include "lldb/Core/Module.h" @@ -23,6 +22,7 @@ #include "lldb/Symbol/ObjectFile.h" #include "lldb/Symbol/VariableList.h" #include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContextMemory.h" #include "lldb/Target/StopInfo.h" #include "lldb/Target/Target.h" #include "lldb/Target/Thread.h" diff --git a/lldb/source/Plugins/Process/Utility/CMakeLists.txt b/lldb/source/Plugins/Process/Utility/CMakeLists.txt index b1e326ec064e4..2f07cf2de6dd3 100644 --- a/lldb/source/Plugins/Process/Utility/CMakeLists.txt +++ b/lldb/source/Plugins/Process/Utility/CMakeLists.txt @@ -36,7 +36,6 @@ add_lldb_library(lldbPluginProcessUtility RegisterContextLinux_s390x.cpp RegisterContextMach_arm.cpp RegisterContextMach_x86_64.cpp - RegisterContextMemory.cpp RegisterContextNetBSD_i386.cpp RegisterContextNetBSD_x86_64.cpp RegisterContextOpenBSD_i386.cpp diff --git a/lldb/source/Plugins/Process/scripted/CMakeLists.txt b/lldb/source/Plugins/Process/scripted/CMakeLists.txt index 1516ad3132e3b..590166591a41e 100644 --- a/lldb/source/Plugins/Process/scripted/CMakeLists.txt +++ b/lldb/source/Plugins/Process/scripted/CMakeLists.txt @@ -1,7 +1,6 @@ add_lldb_library(lldbPluginScriptedProcess PLUGIN ScriptedProcess.cpp ScriptedThread.cpp - ScriptedFrame.cpp LINK_COMPONENTS BinaryFormat diff --git a/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp b/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp index 491efac5aadef..5ad2a896e6129 100644 --- a/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp +++ b/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp @@ -7,10 +7,10 @@ //===----------------------------------------------------------------------===// #include "ScriptedThread.h" -#include "ScriptedFrame.h" #include "Plugins/Process/Utility/RegisterContextThreadMemory.h" #include "Plugins/Process/Utility/StopInfoMachException.h" +#include "lldb/Interpreter/ScriptedFrame.h" #include "lldb/Target/OperatingSystem.h" #include "lldb/Target/Process.h" #include "lldb/Target/RegisterContext.h" @@ -232,7 +232,8 @@ bool ScriptedThread::LoadArtificialStackFrames() { } auto frame_or_error = - ScriptedFrame::Create(*this, nullptr, object_sp->GetAsGeneric()); + ScriptedFrame::Create(this->shared_from_this(), GetInterface(), nullptr, + object_sp->GetAsGeneric()); if (!frame_or_error) { ScriptedInterface::ErrorWithMessage( diff --git a/lldb/source/Plugins/Process/scripted/ScriptedThread.h b/lldb/source/Plugins/Process/scripted/ScriptedThread.h index ee5ace4059673..a66d344f8316a 100644 --- a/lldb/source/Plugins/Process/scripted/ScriptedThread.h +++ b/lldb/source/Plugins/Process/scripted/ScriptedThread.h @@ -13,9 +13,9 @@ #include "ScriptedProcess.h" -#include "Plugins/Process/Utility/RegisterContextMemory.h" #include "lldb/Interpreter/ScriptInterpreter.h" #include "lldb/Target/DynamicRegisterInfo.h" +#include "lldb/Target/RegisterContextMemory.h" #include "lldb/Target/Thread.h" namespace lldb_private { diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt index 09103573b89c5..50569cdefaafa 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt @@ -23,6 +23,7 @@ add_lldb_library(lldbPluginScriptInterpreterPythonInterfaces PLUGIN OperatingSystemPythonInterface.cpp ScriptInterpreterPythonInterfaces.cpp ScriptedFramePythonInterface.cpp + ScriptedFrameProviderPythonInterface.cpp ScriptedPlatformPythonInterface.cpp ScriptedProcessPythonInterface.cpp ScriptedPythonInterface.cpp diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h index 3814f46615078..b2a347951d0f2 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h @@ -17,6 +17,7 @@ #include "OperatingSystemPythonInterface.h" #include "ScriptedBreakpointPythonInterface.h" +#include "ScriptedFrameProviderPythonInterface.h" #include "ScriptedFramePythonInterface.h" #include "ScriptedPlatformPythonInterface.h" #include "ScriptedProcessPythonInterface.h" diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp new file mode 100644 index 0000000000000..ff9efd8f34688 --- /dev/null +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp @@ -0,0 +1,71 @@ +//===-- ScriptedFrameProviderPythonInterface.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/Target/Thread.h" +#include "lldb/Utility/Log.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 "ScriptedFrameProviderPythonInterface.h" +#include + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::python; +using Locker = ScriptInterpreterPythonImpl::Locker; + +ScriptedFrameProviderPythonInterface::ScriptedFrameProviderPythonInterface( + ScriptInterpreterPythonImpl &interpreter) + : ScriptedFrameProviderInterface(), ScriptedPythonInterface(interpreter) {} + +llvm::Expected +ScriptedFrameProviderPythonInterface::CreatePluginObject( + const llvm::StringRef class_name, lldb::ThreadSP thread_sp, + StructuredData::DictionarySP args_sp) { + if (!thread_sp) + return llvm::createStringError("Invalid thread"); + + StructuredDataImpl sd_impl(args_sp); + return ScriptedPythonInterface::CreatePluginObject(class_name, nullptr, + thread_sp, sd_impl); +} + +lldb::ScriptedFrameProviderMergeStrategy +ScriptedFrameProviderPythonInterface::GetMergeStrategy() { + Status error; + StructuredData::ObjectSP obj = Dispatch("get_merge_strategy", error); + + if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj, + error)) + return {}; + + return static_cast( + obj->GetUnsignedIntegerValue()); +} + +StructuredData::ArraySP ScriptedFrameProviderPythonInterface::GetStackFrames( + lldb::StackFrameListSP real_frames) { + Status error; + StructuredData::ArraySP arr = + Dispatch("get_stackframes", error, real_frames); + + if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, arr, + error)) + return {}; + + return arr; +} + +#endif diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h new file mode 100644 index 0000000000000..9f5db55560ee9 --- /dev/null +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h @@ -0,0 +1,45 @@ +//===-- ScriptedFrameProviderPythonInterface.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_INTERFACES_SCRIPTEDFRAMEPROVIDERPYTHONINTERFACE_H +#define LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDFRAMEPROVIDERPYTHONINTERFACE_H + +#include "lldb/Host/Config.h" + +#if LLDB_ENABLE_PYTHON + +#include "ScriptedPythonInterface.h" +#include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h" +#include + +namespace lldb_private { +class ScriptedFrameProviderPythonInterface + : public ScriptedFrameProviderInterface, + public ScriptedPythonInterface { +public: + ScriptedFrameProviderPythonInterface( + ScriptInterpreterPythonImpl &interpreter); + + llvm::Expected + CreatePluginObject(llvm::StringRef class_name, lldb::ThreadSP thread_sp, + StructuredData::DictionarySP args_sp) override; + + llvm::SmallVector + GetAbstractMethodRequirements() const override { + return llvm::SmallVector({{"get_stackframes"}}); + } + + lldb::ScriptedFrameProviderMergeStrategy GetMergeStrategy() override; + + StructuredData::ArraySP + GetStackFrames(lldb::StackFrameListSP real_frames) override; +}; +} // namespace lldb_private + +#endif // LLDB_ENABLE_PYTHON +#endif // LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDFRAMEPROVIDERPYTHONINTERFACE_H diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp index 4fdf2b12a5500..c88a63cd1e058 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp @@ -243,4 +243,22 @@ ScriptedPythonInterface::ExtractValueFromPythonObject( return static_cast(unsigned_val); } + +template <> +lldb::StackFrameListSP +ScriptedPythonInterface::ExtractValueFromPythonObject( + python::PythonObject &p, Status &error) { + + lldb::SBFrameList *sb_frame_list = reinterpret_cast( + python::LLDBSWIGPython_CastPyObjectToSBFrameList(p.get())); + + if (!sb_frame_list) { + error = Status::FromErrorStringWithFormat( + "Couldn't cast lldb::SBFrameList to lldb::StackFrameListSP."); + return {}; + } + + return m_interpreter.GetOpaqueTypeFromSBFrameList(*sb_frame_list); +} + #endif diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h index 2335b2ef0f171..ec1dd9910d8a6 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h @@ -444,6 +444,14 @@ class ScriptedPythonInterface : virtual public ScriptedInterface { return python::SWIGBridge::ToSWIGWrapper(arg); } + python::PythonObject Transform(lldb::ThreadSP arg) { + return python::SWIGBridge::ToSWIGWrapper(arg); + } + + python::PythonObject Transform(lldb::StackFrameListSP arg) { + return python::SWIGBridge::ToSWIGWrapper(arg); + } + python::PythonObject Transform(lldb::ThreadPlanSP arg) { return python::SWIGBridge::ToSWIGWrapper(arg); } @@ -628,6 +636,11 @@ lldb::DescriptionLevel ScriptedPythonInterface::ExtractValueFromPythonObject( python::PythonObject &p, Status &error); +template <> +lldb::StackFrameListSP +ScriptedPythonInterface::ExtractValueFromPythonObject( + python::PythonObject &p, Status &error); + } // namespace lldb_private #endif // LLDB_ENABLE_PYTHON diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h index 7b39d29ba2b20..bf31ce9c7760e 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h @@ -93,6 +93,7 @@ class SWIGBridge { static PythonObject ToSWIGWrapper(const StructuredDataImpl &data_impl); static PythonObject ToSWIGWrapper(lldb::ThreadSP thread_sp); static PythonObject ToSWIGWrapper(lldb::StackFrameSP frame_sp); + static PythonObject ToSWIGWrapper(lldb::StackFrameListSP frames_sp); static PythonObject ToSWIGWrapper(lldb::DebuggerSP debugger_sp); static PythonObject ToSWIGWrapper(lldb::WatchpointSP watchpoint_sp); static PythonObject ToSWIGWrapper(lldb::BreakpointLocationSP bp_loc_sp); @@ -268,6 +269,7 @@ void *LLDBSWIGPython_CastPyObjectToSBSymbolContext(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBValue(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBMemoryRegionInfo(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBExecutionContext(PyObject *data); +void *LLDBSWIGPython_CastPyObjectToSBFrameList(PyObject *data); } // namespace python } // namespace lldb_private diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp index 73c5c72932ff1..9ef5ac4acb6a3 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp @@ -1526,6 +1526,11 @@ ScriptInterpreterPythonImpl::CreateScriptedFrameInterface() { return std::make_shared(*this); } +ScriptedFrameProviderInterfaceSP +ScriptInterpreterPythonImpl::CreateScriptedFrameProviderInterface() { + return std::make_shared(*this); +} + ScriptedThreadPlanInterfaceSP ScriptInterpreterPythonImpl::CreateScriptedThreadPlanInterface() { return std::make_shared(*this); diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h index dedac280788f4..0dd5ae52c8955 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h @@ -101,6 +101,9 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython { lldb::ScriptedFrameInterfaceSP CreateScriptedFrameInterface() override; + lldb::ScriptedFrameProviderInterfaceSP + CreateScriptedFrameProviderInterface() override; + lldb::ScriptedThreadPlanInterfaceSP CreateScriptedThreadPlanInterface() override; diff --git a/lldb/source/Target/CMakeLists.txt b/lldb/source/Target/CMakeLists.txt index b7788e80eecac..f270ec0ce5300 100644 --- a/lldb/source/Target/CMakeLists.txt +++ b/lldb/source/Target/CMakeLists.txt @@ -33,6 +33,7 @@ add_lldb_library(lldbTarget QueueItem.cpp QueueList.cpp RegisterContext.cpp + RegisterContextMemory.cpp RegisterContextUnwind.cpp RegisterFlags.cpp RegisterNumber.cpp diff --git a/lldb/source/Plugins/Process/Utility/RegisterContextMemory.cpp b/lldb/source/Target/RegisterContextMemory.cpp similarity index 99% rename from lldb/source/Plugins/Process/Utility/RegisterContextMemory.cpp rename to lldb/source/Target/RegisterContextMemory.cpp index 84a19d5b13035..245b90de75cd3 100644 --- a/lldb/source/Plugins/Process/Utility/RegisterContextMemory.cpp +++ b/lldb/source/Target/RegisterContextMemory.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -#include "RegisterContextMemory.h" +#include "lldb/Target/RegisterContextMemory.h" #include "lldb/Target/Process.h" #include "lldb/Target/Thread.h" diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp index 8c3e19725f8cb..fd738f5c3f35d 100644 --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -13,9 +13,13 @@ #include "lldb/Core/Module.h" #include "lldb/Core/StructuredDataImpl.h" #include "lldb/Host/Host.h" +#include "lldb/Interpreter/Interfaces/ScriptedFrameInterface.h" +#include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h" #include "lldb/Interpreter/OptionValueFileSpecList.h" #include "lldb/Interpreter/OptionValueProperties.h" #include "lldb/Interpreter/Property.h" +#include "lldb/Interpreter/ScriptInterpreter.h" +#include "lldb/Interpreter/ScriptedFrameProvider.h" #include "lldb/Symbol/Function.h" #include "lldb/Target/ABI.h" #include "lldb/Target/DynamicLoader.h" @@ -45,6 +49,7 @@ #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/RegularExpression.h" +#include "lldb/Utility/ScriptedMetadata.h" #include "lldb/Utility/State.h" #include "lldb/Utility/Stream.h" #include "lldb/Utility/StreamString.h" @@ -1439,13 +1444,140 @@ void Thread::CalculateExecutionContext(ExecutionContext &exe_ctx) { StackFrameListSP Thread::GetStackFrameList() { std::lock_guard guard(m_frame_mutex); - if (!m_curr_frames_sp) - m_curr_frames_sp = + if (!m_curr_frames_sp) { + + StackFrameListSP real_frames_sp = std::make_shared(*this, m_prev_frames_sp, true); + if (m_frame_provider_sp) { + lldb::ScriptedFrameProviderMergeStrategy strategy = + m_frame_provider_sp->GetMergeStrategy(); + + auto scripted_list_or_err = GetScriptedFrameList(real_frames_sp); + if (!scripted_list_or_err) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), + scripted_list_or_err.takeError(), + "Failed to get scripted frame list: {0}"); + m_curr_frames_sp = real_frames_sp; + return m_curr_frames_sp; + } + + StackFrameListSP scripted_frames_sp = *scripted_list_or_err; + uint32_t num_real = real_frames_sp->GetNumFrames(true); + uint32_t num_scripted = scripted_frames_sp->GetNumFrames(false); + + switch (strategy) { + case lldb::eScriptedFrameProviderMergeStrategyReplace: { + m_curr_frames_sp = *scripted_list_or_err; + return m_curr_frames_sp; + } + + case lldb::eScriptedFrameProviderMergeStrategyReplaceByIndex: { + // Create normal frame list first + for (uint32_t i = 0; i < num_scripted; i++) { + StackFrameSP scripted_frame = scripted_frames_sp->GetFrameAtIndex(i); + if (scripted_frame) { + uint32_t frame_idx = scripted_frame->GetFrameIndex(); + m_curr_frames_sp->SetFrameAtIndex(frame_idx, scripted_frame); + } + } + return m_curr_frames_sp; + } + + case lldb::eScriptedFrameProviderMergeStrategyPrepend: { + // Prepend: Scripted frames go first (0..n-1), real frames follow + // (n..n+m-1) Start with scripted frames and add adjusted real frames + m_curr_frames_sp = scripted_frames_sp; + + // Real frames need to be shifted by num_scripted + for (uint32_t i = 0; i < num_real; i++) { + StackFrameSP real_frame = real_frames_sp->GetFrameAtIndex(i); + if (real_frame) { + uint32_t new_idx = num_scripted + i; + real_frame->SetFrameIndex(new_idx); + m_curr_frames_sp->SetFrameAtIndex(new_idx, real_frame); + } + } + + m_curr_frames_sp->SetAllFramesFetched(); + return m_curr_frames_sp; + } + + case lldb::eScriptedFrameProviderMergeStrategyAppend: { + // Append: Real frames go first (0..m-1), scripted frames follow + // (m..m+n-1) + m_curr_frames_sp = real_frames_sp; + + // Scripted frames should be at indices m, m+1, ... + // But they were created with indices 0, 1, ... + // Update their indices to place them after real frames + for (uint32_t i = 0; i < num_scripted; i++) { + StackFrameSP scripted_frame = scripted_frames_sp->GetFrameAtIndex(i); + if (scripted_frame) { + uint32_t new_idx = num_real + i; + scripted_frame->SetFrameIndex(new_idx); + m_curr_frames_sp->SetFrameAtIndex(new_idx, scripted_frame); + } + } + + m_curr_frames_sp->SetAllFramesFetched(); + return m_curr_frames_sp; + } + } + } + + m_curr_frames_sp = real_frames_sp; + } + return m_curr_frames_sp; } +llvm::Expected +Thread::GetScriptedFrameList(StackFrameListSP real_frames_sp) { + std::lock_guard guard(m_frame_mutex); + + if (!m_frame_provider_sp) + return llvm::createStringError("No scripted frame provider has been set"); + + // Pass the real unwound frames to the scripted frame provider. + // This ensures the provider can iterate, filter, and replace frames without + // causing infinite recursion (if the provider calls GetFrames() on the + // thread, it would re-enter GetStackFrameList() which would call us again). + return m_frame_provider_sp->GetStackFrames(real_frames_sp); +} + +Status +Thread::SetScriptedFrameProvider(const ScriptedMetadata &scripted_metadata) { + std::lock_guard guard(m_frame_mutex); + + Status error; + m_frame_provider_sp = std::make_shared( + shared_from_this(), scripted_metadata, error); + + if (error.Fail()) { + m_frame_provider_sp.reset(); + return error; + } + + // Clear cached frames to ensure scripted frames are used + if (m_curr_frames_sp) + m_curr_frames_sp.reset(); + if (m_prev_frames_sp) + m_prev_frames_sp.reset(); + + return {}; +} + +void Thread::ClearScriptedFrameProvider() { + std::lock_guard guard(m_frame_mutex); + if (m_frame_provider_sp) + m_frame_provider_sp.reset(); + if (m_curr_frames_sp) + m_curr_frames_sp.reset(); + if (m_prev_frames_sp) + m_prev_frames_sp.reset(); +} + std::optional Thread::GetPreviousFrameZeroPC() { return m_prev_framezero_pc; } diff --git a/lldb/test/API/functionalities/scripted_frame_provider/Makefile b/lldb/test/API/functionalities/scripted_frame_provider/Makefile new file mode 100644 index 0000000000000..451278a0946ef --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules \ No newline at end of file diff --git a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py new file mode 100644 index 0000000000000..d776dcf673a24 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py @@ -0,0 +1,248 @@ +""" +Test scripted frame provider functionality with all merge strategies. +""" + +import os + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class ScriptedFrameProviderTestCase(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def setUp(self): + TestBase.setUp(self) + self.source = "main.c" + + def test_replace_strategy(self): + """Test that Replace strategy replaces entire stack.""" + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec(self.source) + ) + + # Import the test frame provider + script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") + self.runCmd("command script import " + script_path) + + # Attach the Replace provider + thread.RegisterFrameProvider( + "test_frame_providers.ReplaceFrameProvider", lldb.SBStructuredData() + ) + + # Verify we have exactly 3 synthetic frames + self.assertEqual(thread.GetNumFrames(), 3, "Should have 3 synthetic frames") + + # Verify frame indices and PCs (dictionary-based frames don't have custom function names) + frame0 = thread.GetFrameAtIndex(0) + self.assertIsNotNone(frame0) + self.assertEqual(frame0.GetPC(), 0x1000) + + frame1 = thread.GetFrameAtIndex(1) + self.assertIsNotNone(frame1) + self.assertEqual(frame1.GetPC(), 0x2000) + + frame2 = thread.GetFrameAtIndex(2) + self.assertIsNotNone(frame2) + self.assertEqual(frame2.GetPC(), 0x3000) + + def test_prepend_strategy(self): + """Test that Prepend strategy adds frames before real stack.""" + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec(self.source) + ) + + # Get original frame count and PC + original_frame_count = thread.GetNumFrames() + self.assertGreaterEqual( + original_frame_count, 2, "Should have at least 2 real frames" + ) + original_frame_0_pc = thread.GetFrameAtIndex(0).GetPC() + + # Import and attach Prepend provider + script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") + self.runCmd("command script import " + script_path) + + thread.RegisterFrameProvider( + "test_frame_providers.PrependFrameProvider", lldb.SBStructuredData() + ) + + # Verify we have 2 more frames + new_frame_count = thread.GetNumFrames() + self.assertEqual(new_frame_count, original_frame_count + 2) + + # Verify first 2 frames are synthetic (check PCs, not function names) + frame0 = thread.GetFrameAtIndex(0) + self.assertEqual(frame0.GetPC(), original_frame_0_pc) + + frame1 = thread.GetFrameAtIndex(1) + self.assertEqual(frame1.GetPC(), original_frame_0_pc - 0x10) + + # Verify frame 2 is the original real frame 0 + frame2 = thread.GetFrameAtIndex(2) + self.assertIn("foo", frame2.GetFunctionName()) + + def test_append_strategy(self): + """Test that Append strategy adds frames after real stack.""" + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec(self.source) + ) + + # Get original frame count + original_frame_count = thread.GetNumFrames() + + # Import and attach Append provider + script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") + self.runCmd("command script import " + script_path) + + thread.RegisterFrameProvider( + "test_frame_providers.AppendFrameProvider", lldb.SBStructuredData() + ) + + # Verify we have 2 more frames + new_frame_count = thread.GetNumFrames() + self.assertEqual(new_frame_count, original_frame_count + 2) + + # Verify first frames are still real + frame0 = thread.GetFrameAtIndex(0) + self.assertIn("foo", frame0.GetFunctionName()) + + # Verify last 2 frames are synthetic (check PCs, not function names) + frame_n = thread.GetFrameAtIndex(new_frame_count - 2) + self.assertEqual(frame_n.GetPC(), 0x9000) + + frame_n_plus_1 = thread.GetFrameAtIndex(new_frame_count - 1) + self.assertEqual(frame_n_plus_1.GetPC(), 0xA000) + + def test_replace_by_index_strategy(self): + """Test that ReplaceByIndex strategy replaces specific frames.""" + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec(self.source) + ) + + # Get original frame count and info + original_frame_count = thread.GetNumFrames() + self.assertGreaterEqual(original_frame_count, 3, "Need at least 3 frames") + + original_frame_0_pc = thread.GetFrameAtIndex(0).GetPC() + original_frame_1 = thread.GetFrameAtIndex(1) + original_frame_1_name = original_frame_1.GetFunctionName() + original_frame_2_pc = thread.GetFrameAtIndex(2).GetPC() + + # Import and attach ReplaceByIndex provider + script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") + self.runCmd("command script import " + script_path) + + thread.RegisterFrameProvider( + "test_frame_providers.ReplaceByIndexFrameProvider", lldb.SBStructuredData() + ) + + # Verify frame count unchanged + self.assertEqual( + thread.GetNumFrames(), + original_frame_count, + "Frame count should remain the same", + ) + + # Verify frame 0 is replaced (PC should match original since provider uses it) + frame0 = thread.GetFrameAtIndex(0) + self.assertIsNotNone(frame0) + self.assertEqual( + frame0.GetPC(), original_frame_0_pc, "Frame 0 should be replaced" + ) + + # Verify frame 1 is still the original (not replaced) + frame1 = thread.GetFrameAtIndex(1) + self.assertIsNotNone(frame1) + self.assertEqual( + frame1.GetFunctionName(), + original_frame_1_name, + "Frame 1 should remain unchanged", + ) + + # Verify frame 2 is replaced (PC should match original since provider uses it) + frame2 = thread.GetFrameAtIndex(2) + self.assertIsNotNone(frame2) + self.assertEqual( + frame2.GetPC(), original_frame_2_pc, "Frame 2 should be replaced" + ) + + def test_clear_frame_provider(self): + """Test that clearing provider restores normal unwinding.""" + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec(self.source) + ) + + # Get original state + original_frame_0 = thread.GetFrameAtIndex(0) + original_frame_0_name = original_frame_0.GetFunctionName() + original_frame_count = thread.GetNumFrames() + + # Import and attach provider + script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") + self.runCmd("command script import " + script_path) + + thread.RegisterFrameProvider( + "test_frame_providers.ReplaceFrameProvider", lldb.SBStructuredData() + ) + + # Verify frames are synthetic (3 frames with specific PCs) + self.assertEqual(thread.GetNumFrames(), 3) + frame0 = thread.GetFrameAtIndex(0) + self.assertEqual(frame0.GetPC(), 0x1000) + + # Clear the provider + thread.ClearScriptedFrameProvider() + + # Verify frames are back to normal + self.assertEqual(thread.GetNumFrames(), original_frame_count) + frame0 = thread.GetFrameAtIndex(0) + self.assertEqual( + frame0.GetFunctionName(), + original_frame_0_name, + "Should restore original frames after clearing provider", + ) + + def test_scripted_frame_objects(self): + """Test that provider can return ScriptedFrame objects.""" + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec(self.source) + ) + + # Import the provider that returns ScriptedFrame objects + script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") + self.runCmd("command script import " + script_path) + + thread.RegisterFrameProvider( + "test_frame_providers.ScriptedFrameObjectProvider", lldb.SBStructuredData() + ) + + # Verify we have 3 frames + self.assertEqual( + thread.GetNumFrames(), 3, "Should have 3 custom scripted frames" + ) + + # Verify frame properties from CustomScriptedFrame + frame0 = thread.GetFrameAtIndex(0) + self.assertIsNotNone(frame0) + self.assertEqual(frame0.GetFunctionName(), "custom_scripted_frame_0") + self.assertEqual(frame0.GetPC(), 0x5000) + self.assertTrue(frame0.IsArtificial(), "Frame should be marked as artificial") + + frame1 = thread.GetFrameAtIndex(1) + self.assertIsNotNone(frame1) + self.assertEqual(frame1.GetFunctionName(), "custom_scripted_frame_1") + self.assertEqual(frame1.GetPC(), 0x6000) + + frame2 = thread.GetFrameAtIndex(2) + self.assertIsNotNone(frame2) + self.assertEqual(frame2.GetFunctionName(), "custom_scripted_frame_2") + self.assertEqual(frame2.GetPC(), 0x7000) diff --git a/lldb/test/API/functionalities/scripted_frame_provider/main.c b/lldb/test/API/functionalities/scripted_frame_provider/main.c new file mode 100644 index 0000000000000..3ca941fb587a5 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/main.c @@ -0,0 +1,14 @@ +// Simple test program with a few stack frames + +#include + +int foo(int x) { + printf("In foo: %d\n", x); // Break here + return x * 2; +} + +int main(int argc, char **argv) { + int result = foo(42); + printf("Result: %d\n", result); + return 0; +} \ No newline at end of file diff --git a/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py b/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py new file mode 100644 index 0000000000000..648c9b801a403 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py @@ -0,0 +1,185 @@ +""" +Test frame providers for scripted frame provider functionality. + +These providers exercise all merge strategies: +- Replace: Replace entire stack +- Prepend: Add frames before real stack +- Append: Add frames after real stack +- ReplaceByIndex: Replace specific frames by index +""" + +import lldb +from lldb.plugins.scripted_process import ScriptedFrame +from lldb.plugins.scripted_frame_provider import ScriptedFrameProvider + + +class ReplaceFrameProvider(ScriptedFrameProvider): + """Replace entire stack with custom frames.""" + + def __init__(self, thread, args): + super().__init__(thread, args) + + def get_merge_strategy(self): + return lldb.eScriptedFrameProviderMergeStrategyReplace + + def get_stackframes(self, real_frames): + return [ + { + "idx": 0, + "pc": 0x1000, + }, + { + "idx": 1, + "pc": 0x2000, + }, + { + "idx": 2, + "pc": 0x3000, + }, + ] + + +class PrependFrameProvider(ScriptedFrameProvider): + """Prepend synthetic frames before real stack.""" + + def __init__(self, thread, args): + super().__init__(thread, args) + + def get_merge_strategy(self): + return lldb.eScriptedFrameProviderMergeStrategyPrepend + + def get_stackframes(self, real_frames): + # Get real frame 0 PC from the provided frame list + real_pc = 0x1000 + if len(real_frames) > 0: + real_frame_0 = real_frames[0] + if real_frame_0 and real_frame_0.IsValid(): + real_pc = real_frame_0.GetPC() + + return [ + { + "idx": 0, + "pc": real_pc, + }, + { + "idx": 1, + "pc": real_pc - 0x10, + }, + ] + + +class AppendFrameProvider(ScriptedFrameProvider): + """Append synthetic frames after real stack.""" + + def __init__(self, thread, args): + super().__init__(thread, args) + + def get_merge_strategy(self): + return lldb.eScriptedFrameProviderMergeStrategyAppend + + def get_stackframes(self, real_frames): + # Count real frames + num_real_frames = len(real_frames) + + return [ + { + "idx": num_real_frames, + "pc": 0x9000, + }, + { + "idx": num_real_frames + 1, + "pc": 0xA000, + }, + ] + + +class ReplaceByIndexFrameProvider(ScriptedFrameProvider): + """Replace only frames 0 and 2, keep frame 1 real.""" + + def __init__(self, thread, args): + super().__init__(thread, args) + + def get_merge_strategy(self): + return lldb.eScriptedFrameProviderMergeStrategyReplaceByIndex + + def get_stackframes(self, real_frames): + frames = [] + + # Replace frame 0 + if len(real_frames) > 0: + real_frame_0 = real_frames[0] + if real_frame_0 and real_frame_0.IsValid(): + frames.append( + { + "idx": 0, + "pc": real_frame_0.GetPC(), + } + ) + + # Replace frame 2 + if len(real_frames) > 2: + real_frame_2 = real_frames[2] + if real_frame_2 and real_frame_2.IsValid(): + frames.append( + { + "idx": 2, + "pc": real_frame_2.GetPC(), + } + ) + + return frames + + +class CustomScriptedFrame(ScriptedFrame): + """Custom scripted frame with full control over frame behavior.""" + + def __init__(self, thread, idx, pc, function_name): + # Initialize structured data args + args = lldb.SBStructuredData() + super().__init__(thread, args) + + self.idx = idx + self.pc = pc + self.function_name = function_name + + def get_id(self): + """Return the frame index.""" + return self.idx + + def get_pc(self): + """Return the program counter.""" + return self.pc + + def get_function_name(self): + """Return the function name.""" + return self.function_name + + def is_artificial(self): + """Mark as artificial frame.""" + return True + + def is_hidden(self): + """Not hidden.""" + return False + + def get_register_context(self): + """No register context for this test.""" + return None + + +class ScriptedFrameObjectProvider(ScriptedFrameProvider): + """Provider that returns ScriptedFrame objects instead of dictionaries.""" + + def __init__(self, thread, args): + super().__init__(thread, args) + + def get_merge_strategy(self): + return lldb.eScriptedFrameProviderMergeStrategyReplace + + def get_stackframes(self, real_frames): + """Return list of ScriptedFrame objects.""" + return [ + CustomScriptedFrame(self.thread, 0, 0x5000, "custom_scripted_frame_0"), + CustomScriptedFrame(self.thread, 1, 0x6000, "custom_scripted_frame_1"), + CustomScriptedFrame(self.thread, 2, 0x7000, "custom_scripted_frame_2"), + ] diff --git a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp index 6f5d9fd97ee28..b0680ef15b42d 100644 --- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp +++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp @@ -160,6 +160,11 @@ void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBExecutionContext( return nullptr; } +void * +lldb_private::python::LLDBSWIGPython_CastPyObjectToSBFrameList(PyObject *data) { + return nullptr; +} + lldb::ValueObjectSP lldb_private::python::SWIGBridge::LLDBSWIGPython_GetValueObjectSPFromSBValue( void *data) { @@ -328,6 +333,11 @@ lldb_private::python::SWIGBridge::ToSWIGWrapper(lldb::ProcessSP) { return python::PythonObject(); } +python::PythonObject +lldb_private::python::SWIGBridge::ToSWIGWrapper(lldb::StackFrameListSP) { + return python::PythonObject(); +} + python::PythonObject lldb_private::python::SWIGBridge::ToSWIGWrapper( const lldb_private::StructuredDataImpl &) { return python::PythonObject();