Skip to content

Commit 169c7e4

Browse files
committed
Reland "[lldb] Introduce ScriptedFrameProvider for real threads (llvm#161870)" (llvm#170236)
This patch re-lands llvm#161870 with fixes to the previous test failures. rdar://161834688 Signed-off-by: Med Ismail Bennani <[email protected]> (cherry picked from commit c50802c)
1 parent d051a98 commit 169c7e4

Some content is hidden

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

47 files changed

+2788
-64
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

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
from abc import ABCMeta, abstractmethod
2+
3+
import lldb
4+
5+
6+
class ScriptedFrameProvider(metaclass=ABCMeta):
7+
"""
8+
The base class for a scripted frame provider.
9+
10+
A scripted frame provider allows you to provide custom stack frames for a
11+
thread, which can be used to augment or replace the standard unwinding
12+
mechanism. This is useful for:
13+
14+
- Providing frames for custom calling conventions or languages
15+
- Reconstructing missing frames from crash dumps or core files
16+
- Adding diagnostic or synthetic frames for debugging
17+
- Visualizing state machines or async execution contexts
18+
19+
Most of the base class methods are `@abstractmethod` that need to be
20+
overwritten by the inheriting class.
21+
22+
Example usage:
23+
24+
.. code-block:: python
25+
26+
# Attach a frame provider to a thread
27+
thread = process.GetSelectedThread()
28+
error = thread.SetScriptedFrameProvider(
29+
"my_module.MyFrameProvider",
30+
lldb.SBStructuredData()
31+
)
32+
"""
33+
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
62+
@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+
82+
def __init__(self, input_frames, args):
83+
"""Construct a scripted frame provider.
84+
85+
Args:
86+
input_frames (lldb.SBFrameList): The frame list to use as input.
87+
This allows you to access frames by index. The frames are
88+
materialized lazily as you access them.
89+
args (lldb.SBStructuredData): A Dictionary holding arbitrary
90+
key/value pairs used by the scripted frame provider.
91+
"""
92+
self.input_frames = None
93+
self.args = None
94+
self.thread = None
95+
self.target = None
96+
self.process = None
97+
98+
if isinstance(input_frames, lldb.SBFrameList) and input_frames.IsValid():
99+
self.input_frames = input_frames
100+
self.thread = input_frames.GetThread()
101+
if self.thread and self.thread.IsValid():
102+
self.process = self.thread.GetProcess()
103+
if self.process and self.process.IsValid():
104+
self.target = self.process.GetTarget()
105+
106+
if isinstance(args, lldb.SBStructuredData) and args.IsValid():
107+
self.args = args
108+
109+
@abstractmethod
110+
def get_frame_at_index(self, index):
111+
"""Get a single stack frame at the given index.
112+
113+
This method is called lazily when a specific frame is needed in the
114+
thread's backtrace (e.g., via the 'bt' command). Each frame is
115+
requested individually as needed.
116+
117+
Args:
118+
index (int): The frame index to retrieve (0 for youngest/top frame).
119+
120+
Returns:
121+
Dict or None: A frame dictionary describing the stack frame, or None
122+
if no frame exists at this index. The dictionary should contain:
123+
124+
Required fields:
125+
- idx (int): The synthetic frame index (0 for youngest/top frame)
126+
- pc (int): The program counter address for the synthetic frame
127+
128+
Alternatively, you can return:
129+
- A ScriptedFrame object for full control over frame behavior
130+
- An integer representing an input frame index to reuse
131+
- None to indicate no more frames exist
132+
133+
Example:
134+
135+
.. code-block:: python
136+
137+
def get_frame_at_index(self, index):
138+
# Return None when there are no more frames
139+
if index >= self.total_frames:
140+
return None
141+
142+
# Re-use an input frame by returning its index
143+
if self.should_use_input_frame(index):
144+
return index # Returns input frame at this index
145+
146+
# Or create a custom frame dictionary
147+
if index == 0:
148+
return {
149+
"idx": 0,
150+
"pc": 0x100001234,
151+
}
152+
153+
return None
154+
155+
Note:
156+
The frames are indexed from 0 (youngest/top) to N (oldest/bottom).
157+
This method will be called repeatedly with increasing indices until
158+
None is returned.
159+
"""
160+
pass

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"
@@ -979,6 +980,35 @@ class LLDB_API SBTarget {
979980

980981
lldb::SBMutex GetAPIMutex() const;
981982

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

lldb/include/lldb/API/SBThread.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ class LLDB_API SBThread {
249249
friend class SBThreadPlan;
250250
friend class SBTrace;
251251

252+
friend class lldb_private::ScriptInterpreter;
252253
friend class lldb_private::python::SWIGBridge;
253254

254255
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;

0 commit comments

Comments
 (0)