Skip to content

Commit 1e467e4

Browse files
authored
[lldb] Introduce ScriptedFrameProvider for real threads (llvm#161870)
This patch extends ScriptedFrame to work with real (non-scripted) threads, enabling frame providers to synthesize frames for native processes. Previously, ScriptedFrame only worked within ScriptedProcess/ScriptedThread contexts. This patch decouples ScriptedFrame from ScriptedThread, allowing users to augment or replace stack frames in real debugging sessions for use cases like custom calling conventions, reconstructing corrupted frames from core files, or adding diagnostic frames. Key changes: - ScriptedFrame::Create() now accepts ThreadSP instead of requiring ScriptedThread, extracting architecture from the target triple rather than ScriptedProcess.arch - Added SBTarget::RegisterScriptedFrameProvider() and ClearScriptedFrameProvider() APIs, with Target storing a SyntheticFrameProviderDescriptor template for new threads - Added "target frame-provider register/clear" commands for CLI access - Thread class gains LoadScriptedFrameProvider(), ClearScriptedFrameProvider(), and GetFrameProvider() methods for per-thread frame provider management - New SyntheticStackFrameList overrides FetchFramesUpTo() to lazily provide frames from either the frame provider or the real stack This enables practical use of the SyntheticFrameProvider infrastructure in real debugging workflows. rdar://161834688 Signed-off-by: Med Ismail Bennani <[email protected]> Signed-off-by: Med Ismail Bennani <[email protected]>
1 parent 75ef0be commit 1e467e4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1918
-79
lines changed

lldb/bindings/python/python-wrapper.swig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,18 @@ void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBBreakpoint(PyObject *
422422
return sb_ptr;
423423
}
424424

425+
void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBThread(PyObject * data) {
426+
lldb::SBThread *sb_ptr = nullptr;
427+
428+
int valid_cast =
429+
SWIG_ConvertPtr(data, (void **)&sb_ptr, SWIGTYPE_p_lldb__SBThread, 0);
430+
431+
if (valid_cast == -1)
432+
return NULL;
433+
434+
return sb_ptr;
435+
}
436+
425437
void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBFrame(PyObject * data) {
426438
lldb::SBFrame *sb_ptr = nullptr;
427439

lldb/examples/python/templates/scripted_frame_provider.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,54 @@ class ScriptedFrameProvider(metaclass=ABCMeta):
3131
)
3232
"""
3333

34+
@staticmethod
35+
def applies_to_thread(thread):
36+
"""Determine if this frame provider should be used for a given thread.
37+
38+
This static method is called before creating an instance of the frame
39+
provider to determine if it should be applied to a specific thread.
40+
Override this method to provide custom filtering logic.
41+
42+
Args:
43+
thread (lldb.SBThread): The thread to check.
44+
45+
Returns:
46+
bool: True if this frame provider should be used for the thread,
47+
False otherwise. The default implementation returns True for
48+
all threads.
49+
50+
Example:
51+
52+
.. code-block:: python
53+
54+
@staticmethod
55+
def applies_to_thread(thread):
56+
# Only apply to thread 1
57+
return thread.GetIndexID() == 1
58+
"""
59+
return True
60+
61+
@staticmethod
3462
@abstractmethod
63+
def get_description():
64+
"""Get a description of this frame provider.
65+
66+
This method should return a human-readable string describing what
67+
this frame provider does. The description is used for debugging
68+
and display purposes.
69+
70+
Returns:
71+
str: A description of the frame provider.
72+
73+
Example:
74+
75+
.. code-block:: python
76+
77+
def get_description(self):
78+
return "Crash log frame provider for thread 1"
79+
"""
80+
pass
81+
3582
def __init__(self, input_frames, args):
3683
"""Construct a scripted frame provider.
3784

lldb/examples/python/templates/scripted_process.py

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ def __init__(self, process, args):
245245
key/value pairs used by the scripted thread.
246246
"""
247247
self.target = None
248+
self.arch = None
248249
self.originating_process = None
249250
self.process = None
250251
self.args = None
@@ -266,6 +267,9 @@ def __init__(self, process, args):
266267
and process.IsValid()
267268
):
268269
self.target = process.target
270+
triple = self.target.triple
271+
if triple:
272+
self.arch = triple.split("-")[0]
269273
self.originating_process = process
270274
self.process = self.target.GetProcess()
271275
self.get_register_info()
@@ -352,17 +356,14 @@ def get_stackframes(self):
352356
def get_register_info(self):
353357
if self.register_info is None:
354358
self.register_info = dict()
355-
if "x86_64" in self.originating_process.arch:
359+
if "x86_64" in self.arch:
356360
self.register_info["sets"] = ["General Purpose Registers"]
357361
self.register_info["registers"] = INTEL64_GPR
358-
elif (
359-
"arm64" in self.originating_process.arch
360-
or self.originating_process.arch == "aarch64"
361-
):
362+
elif "arm64" in self.arch or self.arch == "aarch64":
362363
self.register_info["sets"] = ["General Purpose Registers"]
363364
self.register_info["registers"] = ARM64_GPR
364365
else:
365-
raise ValueError("Unknown architecture", self.originating_process.arch)
366+
raise ValueError("Unknown architecture", self.arch)
366367
return self.register_info
367368

368369
@abstractmethod
@@ -405,11 +406,12 @@ def __init__(self, thread, args):
405406
"""Construct a scripted frame.
406407
407408
Args:
408-
thread (ScriptedThread): The thread owning this frame.
409+
thread (ScriptedThread/lldb.SBThread): The thread owning this frame.
409410
args (lldb.SBStructuredData): A Dictionary holding arbitrary
410411
key/value pairs used by the scripted frame.
411412
"""
412413
self.target = None
414+
self.arch = None
413415
self.originating_thread = None
414416
self.thread = None
415417
self.args = None
@@ -419,15 +421,17 @@ def __init__(self, thread, args):
419421
self.register_ctx = {}
420422
self.variables = []
421423

422-
if (
423-
isinstance(thread, ScriptedThread)
424-
or isinstance(thread, lldb.SBThread)
425-
and thread.IsValid()
424+
if isinstance(thread, ScriptedThread) or (
425+
isinstance(thread, lldb.SBThread) and thread.IsValid()
426426
):
427-
self.target = thread.target
428427
self.process = thread.process
428+
self.target = self.process.target
429+
triple = self.target.triple
430+
if triple:
431+
self.arch = triple.split("-")[0]
432+
tid = thread.tid if isinstance(thread, ScriptedThread) else thread.id
429433
self.originating_thread = thread
430-
self.thread = self.process.GetThreadByIndexID(thread.tid)
434+
self.thread = self.process.GetThreadByIndexID(tid)
431435
self.get_register_info()
432436

433437
@abstractmethod
@@ -508,7 +512,18 @@ def get_variables(self, filters):
508512

509513
def get_register_info(self):
510514
if self.register_info is None:
511-
self.register_info = self.originating_thread.get_register_info()
515+
if isinstance(self.originating_thread, ScriptedThread):
516+
self.register_info = self.originating_thread.get_register_info()
517+
elif isinstance(self.originating_thread, lldb.SBThread):
518+
self.register_info = dict()
519+
if "x86_64" in self.arch:
520+
self.register_info["sets"] = ["General Purpose Registers"]
521+
self.register_info["registers"] = INTEL64_GPR
522+
elif "arm64" in self.arch or self.arch == "aarch64":
523+
self.register_info["sets"] = ["General Purpose Registers"]
524+
self.register_info["registers"] = ARM64_GPR
525+
else:
526+
raise ValueError("Unknown architecture", self.arch)
512527
return self.register_info
513528

514529
@abstractmethod
@@ -642,12 +657,12 @@ def get_stop_reason(self):
642657

643658
# TODO: Passthrough stop reason from driving process
644659
if self.driving_thread.GetStopReason() != lldb.eStopReasonNone:
645-
if "arm64" in self.originating_process.arch:
660+
if "arm64" in self.arch:
646661
stop_reason["type"] = lldb.eStopReasonException
647662
stop_reason["data"]["desc"] = (
648663
self.driving_thread.GetStopDescription(100)
649664
)
650-
elif self.originating_process.arch == "x86_64":
665+
elif self.arch == "x86_64":
651666
stop_reason["type"] = lldb.eStopReasonSignal
652667
stop_reason["data"]["signal"] = signal.SIGTRAP
653668
else:

lldb/include/lldb/API/SBTarget.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "lldb/API/SBLaunchInfo.h"
2020
#include "lldb/API/SBStatisticsOptions.h"
2121
#include "lldb/API/SBSymbolContextList.h"
22+
#include "lldb/API/SBThreadCollection.h"
2223
#include "lldb/API/SBType.h"
2324
#include "lldb/API/SBValue.h"
2425
#include "lldb/API/SBWatchpoint.h"
@@ -986,6 +987,35 @@ class LLDB_API SBTarget {
986987

987988
lldb::SBMutex GetAPIMutex() const;
988989

990+
/// Register a scripted frame provider for this target.
991+
/// If a scripted frame provider with the same name and same argument
992+
/// dictionary is already registered on this target, it will be overwritten.
993+
///
994+
/// \param[in] class_name
995+
/// The name of the Python class that implements the frame provider.
996+
///
997+
/// \param[in] args_dict
998+
/// A dictionary of arguments to pass to the frame provider class.
999+
///
1000+
/// \param[out] error
1001+
/// An error object indicating success or failure.
1002+
///
1003+
/// \return
1004+
/// A unique identifier for the frame provider descriptor that was
1005+
/// registered. 0 if the registration failed.
1006+
uint32_t RegisterScriptedFrameProvider(const char *class_name,
1007+
lldb::SBStructuredData args_dict,
1008+
lldb::SBError &error);
1009+
1010+
/// Remove a scripted frame provider from this target by name.
1011+
///
1012+
/// \param[in] provider_id
1013+
/// The id of the frame provider class to remove.
1014+
///
1015+
/// \return
1016+
/// An error object indicating success or failure.
1017+
lldb::SBError RemoveScriptedFrameProvider(uint32_t provider_id);
1018+
9891019
protected:
9901020
friend class SBAddress;
9911021
friend class SBAddressRange;

lldb/include/lldb/API/SBThread.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ class LLDB_API SBThread {
256256
friend class SBThreadPlan;
257257
friend class SBTrace;
258258

259+
friend class lldb_private::ScriptInterpreter;
259260
friend class lldb_private::python::SWIGBridge;
260261

261262
SBThread(const lldb::ThreadSP &lldb_object_sp);

lldb/include/lldb/API/SBThreadCollection.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class LLDB_API SBThreadCollection {
4646
void SetOpaque(const lldb::ThreadCollectionSP &threads);
4747

4848
private:
49+
friend class SBTarget;
4950
friend class SBProcess;
5051
friend class SBThread;
5152
friend class SBSaveCoreOptions;

lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,29 @@
1616
namespace lldb_private {
1717
class ScriptedFrameProviderInterface : public ScriptedInterface {
1818
public:
19+
virtual bool AppliesToThread(llvm::StringRef class_name,
20+
lldb::ThreadSP thread_sp) {
21+
return true;
22+
}
23+
1924
virtual llvm::Expected<StructuredData::GenericSP>
2025
CreatePluginObject(llvm::StringRef class_name,
2126
lldb::StackFrameListSP input_frames,
2227
StructuredData::DictionarySP args_sp) = 0;
2328

29+
/// Get a description string for the frame provider.
30+
///
31+
/// This is called by the descriptor to fetch a description from the
32+
/// scripted implementation. Implementations should call a static method
33+
/// on the scripting class to retrieve the description.
34+
///
35+
/// \param class_name The name of the scripting class implementing the
36+
/// provider.
37+
///
38+
/// \return A string describing what this frame provider does, or an
39+
/// empty string if no description is available.
40+
virtual std::string GetDescription(llvm::StringRef class_name) { return {}; }
41+
2442
virtual StructuredData::ObjectSP GetFrameAtIndex(uint32_t index) {
2543
return {};
2644
}

lldb/include/lldb/Interpreter/ScriptInterpreter.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "lldb/API/SBMemoryRegionInfo.h"
2222
#include "lldb/API/SBStream.h"
2323
#include "lldb/API/SBSymbolContext.h"
24+
#include "lldb/API/SBThread.h"
2425
#include "lldb/Breakpoint/BreakpointOptions.h"
2526
#include "lldb/Core/PluginInterface.h"
2627
#include "lldb/Core/SearchFilter.h"
@@ -580,6 +581,8 @@ class ScriptInterpreter : public PluginInterface {
580581

581582
lldb::StreamSP GetOpaqueTypeFromSBStream(const lldb::SBStream &stream) const;
582583

584+
lldb::ThreadSP GetOpaqueTypeFromSBThread(const lldb::SBThread &exe_ctx) const;
585+
583586
lldb::StackFrameSP GetOpaqueTypeFromSBFrame(const lldb::SBFrame &frame) const;
584587

585588
SymbolContext

lldb/include/lldb/Target/StackFrame.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,8 +441,11 @@ class StackFrame : public ExecutionContextScope,
441441
/// frames are included in this frame index count.
442442
uint32_t GetFrameIndex() const;
443443

444-
/// Set this frame's synthetic frame index.
445-
void SetFrameIndex(uint32_t index) { m_frame_index = index; }
444+
/// Set this frame's frame index.
445+
void SetFrameIndex(uint32_t index) {
446+
m_frame_index = index;
447+
m_concrete_frame_index = index;
448+
}
446449

447450
/// Query this frame to find what frame it is in this Thread's
448451
/// StackFrameList, not counting inlined frames.

lldb/include/lldb/Target/StackFrameList.h

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ namespace lldb_private {
2020

2121
class ScriptedThread;
2222

23-
class StackFrameList {
23+
class StackFrameList : public std::enable_shared_from_this<StackFrameList> {
2424
public:
2525
// Constructors and Destructors
2626
StackFrameList(Thread &thread, const lldb::StackFrameListSP &prev_frames_sp,
2727
bool show_inline_frames);
2828

29-
~StackFrameList();
29+
virtual ~StackFrameList();
3030

3131
/// Get the number of visible frames. Frames may be created if \p can_create
3232
/// is true. Synthetic (inline) frames expanded from the concrete frame #0
@@ -106,6 +106,7 @@ class StackFrameList {
106106

107107
protected:
108108
friend class Thread;
109+
friend class ScriptedFrameProvider;
109110
friend class ScriptedThread;
110111

111112
/// Use this API to build a stack frame list (used for scripted threads, for
@@ -211,26 +212,51 @@ class StackFrameList {
211212
/// Whether or not to show synthetic (inline) frames. Immutable.
212213
const bool m_show_inlined_frames;
213214

215+
/// Returns true if fetching frames was interrupted, false otherwise.
216+
virtual bool FetchFramesUpTo(uint32_t end_idx,
217+
InterruptionControl allow_interrupt);
218+
214219
private:
215220
uint32_t SetSelectedFrameNoLock(lldb_private::StackFrame *frame);
216221
lldb::StackFrameSP
217222
GetFrameAtIndexNoLock(uint32_t idx,
218223
std::shared_lock<std::shared_mutex> &guard);
219224

225+
/// @{
220226
/// These two Fetch frames APIs and SynthesizeTailCallFrames are called in
221227
/// GetFramesUpTo, they are the ones that actually add frames. They must be
222228
/// called with the writer end of the list mutex held.
223-
224-
/// Returns true if fetching frames was interrupted, false otherwise.
225-
bool FetchFramesUpTo(uint32_t end_idx, InterruptionControl allow_interrupt);
229+
///
226230
/// Not currently interruptible so returns void.
231+
/// }@
227232
void FetchOnlyConcreteFramesUpTo(uint32_t end_idx);
228233
void SynthesizeTailCallFrames(StackFrame &next_frame);
229234

230235
StackFrameList(const StackFrameList &) = delete;
231236
const StackFrameList &operator=(const StackFrameList &) = delete;
232237
};
233238

239+
/// A StackFrameList that wraps another StackFrameList and uses a
240+
/// SyntheticFrameProvider to lazily provide frames from either the provider
241+
/// or the underlying real stack frame list.
242+
class SyntheticStackFrameList : public StackFrameList {
243+
public:
244+
SyntheticStackFrameList(Thread &thread, lldb::StackFrameListSP input_frames,
245+
const lldb::StackFrameListSP &prev_frames_sp,
246+
bool show_inline_frames);
247+
248+
protected:
249+
/// Override FetchFramesUpTo to lazily return frames from the provider
250+
/// or from the actual stack frame list.
251+
bool FetchFramesUpTo(uint32_t end_idx,
252+
InterruptionControl allow_interrupt) override;
253+
254+
private:
255+
/// The input stack frame list that the provider transforms.
256+
/// This could be a real StackFrameList or another SyntheticStackFrameList.
257+
lldb::StackFrameListSP m_input_frames;
258+
};
259+
234260
} // namespace lldb_private
235261

236262
#endif // LLDB_TARGET_STACKFRAMELIST_H

0 commit comments

Comments
 (0)