Skip to content

Commit 70d72a2

Browse files
committed
[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 <[email protected]>
1 parent c2765b7 commit 70d72a2

37 files changed

+1405
-54
lines changed

lldb/bindings/python/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ function(finish_swig_python swig_target lldb_python_bindings_dir lldb_python_tar
107107
"plugins"
108108
FILES
109109
"${LLDB_SOURCE_DIR}/examples/python/templates/parsed_cmd.py"
110+
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_frame_provider.py"
110111
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_process.py"
111112
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_platform.py"
112113
"${LLDB_SOURCE_DIR}/examples/python/templates/operating_system.py"
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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 = lldb.SBError()
29+
thread.SetScriptedFrameProvider(
30+
"my_module.MyFrameProvider",
31+
lldb.SBStructuredData()
32+
)
33+
"""
34+
35+
@abstractmethod
36+
def __init__(self, thread, args):
37+
"""Construct a scripted frame provider.
38+
39+
Args:
40+
thread (lldb.SBThread): The thread for which to provide frames.
41+
args (lldb.SBStructuredData): A Dictionary holding arbitrary
42+
key/value pairs used by the scripted frame provider.
43+
"""
44+
self.thread = None
45+
self.args = None
46+
self.target = None
47+
self.process = None
48+
49+
if isinstance(thread, lldb.SBThread) and thread.IsValid():
50+
self.thread = thread
51+
self.process = thread.GetProcess()
52+
if self.process and self.process.IsValid():
53+
self.target = self.process.GetTarget()
54+
55+
if isinstance(args, lldb.SBStructuredData) and args.IsValid():
56+
self.args = args
57+
58+
def get_merge_strategy(self):
59+
"""Get the merge strategy for how scripted frames should be integrated.
60+
61+
The merge strategy determines how the scripted frames are combined with the
62+
real unwound frames from the thread's normal unwinder.
63+
64+
Returns:
65+
int: One of the following lldb.ScriptedFrameProviderMergeStrategy values:
66+
67+
- lldb.eScriptedFrameProviderMergeStrategyReplace: Replace the entire stack
68+
with scripted frames. The thread will only show frames provided
69+
by this provider.
70+
71+
- lldb.eScriptedFrameProviderMergeStrategyPrepend: Prepend scripted frames
72+
before the real unwound frames. Useful for adding synthetic frames
73+
at the top of the stack while preserving the actual callstack below.
74+
75+
- lldb.eScriptedFrameProviderMergeStrategyAppend: Append scripted frames
76+
after the real unwound frames. Useful for showing additional context
77+
after the actual callstack ends.
78+
79+
- lldb.eScriptedFrameProviderMergeStrategyReplaceByIndex: Replace specific
80+
frames at given indices with scripted frames, keeping other real frames
81+
intact. The idx field in each frame dictionary determines which real
82+
frame to replace (e.g., idx=0 replaces frame 0, idx=2 replaces frame 2).
83+
84+
The default implementation returns Replace strategy.
85+
86+
Example:
87+
88+
.. code-block:: python
89+
90+
def get_merge_strategy(self):
91+
# Only show our custom frames
92+
return lldb.eScriptedFrameProviderMergeStrategyReplace
93+
94+
def get_merge_strategy(self):
95+
# Add diagnostic frames on top of real stack
96+
return lldb.eScriptedFrameProviderMergeStrategyPrepend
97+
98+
def get_merge_strategy(self):
99+
# Replace frame 0 and frame 2 with custom frames, keep others
100+
return lldb.eScriptedFrameProviderMergeStrategyReplaceByIndex
101+
"""
102+
return lldb.eScriptedFrameProviderMergeStrategyReplace
103+
104+
@abstractmethod
105+
def get_stackframes(self):
106+
"""Get the list of stack frames to provide.
107+
108+
This method is called when the thread's backtrace is requested
109+
(e.g., via the 'bt' command). The returned frames will be integrated
110+
with the real frames according to the mode returned by get_mode().
111+
112+
Returns:
113+
List[Dict]: A list of frame dictionaries, where each dictionary
114+
describes a single stack frame. Each dictionary should contain:
115+
116+
Required fields:
117+
- idx (int): The frame index (0 for innermost/top frame)
118+
- pc (int): The program counter address for this frame
119+
120+
Alternatively, you can return a list of ScriptedFrame objects
121+
for more control over frame behavior.
122+
123+
Example:
124+
125+
.. code-block:: python
126+
127+
def get_stackframes(self):
128+
frames = []
129+
130+
# Frame 0: Current function
131+
frames.append({
132+
"idx": 0,
133+
"pc": 0x100001234,
134+
})
135+
136+
# Frame 1: Caller
137+
frames.append({
138+
"idx": 1,
139+
"pc": 0x100001000,
140+
})
141+
142+
return frames
143+
144+
Note:
145+
The frames are indexed from 0 (innermost/newest) to N (outermost/oldest).
146+
"""
147+
pass

lldb/include/lldb/API/SBThread.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,11 @@ class LLDB_API SBThread {
229229

230230
SBValue GetSiginfo();
231231

232+
void RegisterFrameProvider(const char *class_name,
233+
SBStructuredData &args_data);
234+
235+
void ClearScriptedFrameProvider();
236+
232237
private:
233238
friend class SBBreakpoint;
234239
friend class SBBreakpointLocation;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//===-- ScriptedFrameProviderInterface.h ------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLDB_INTERPRETER_INTERFACES_SCRIPTEDFRAMEPROVIDERINTERFACE_H
10+
#define LLDB_INTERPRETER_INTERFACES_SCRIPTEDFRAMEPROVIDERINTERFACE_H
11+
12+
#include "lldb/lldb-private.h"
13+
14+
#include "ScriptedInterface.h"
15+
16+
namespace lldb_private {
17+
class ScriptedFrameProviderInterface : public ScriptedInterface {
18+
public:
19+
virtual llvm::Expected<StructuredData::GenericSP>
20+
CreatePluginObject(llvm::StringRef class_name, lldb::ThreadSP thread_sp,
21+
StructuredData::DictionarySP args_sp) = 0;
22+
23+
/// Get the merge strategy for how scripted frames should be integrated with
24+
/// real frames
25+
virtual lldb::ScriptedFrameProviderMergeStrategy GetMergeStrategy() {
26+
return lldb::eScriptedFrameProviderMergeStrategyReplace;
27+
}
28+
29+
virtual StructuredData::ArraySP
30+
GetStackFrames(lldb::StackFrameListSP real_frames) {
31+
return {};
32+
}
33+
};
34+
} // namespace lldb_private
35+
36+
#endif // LLDB_INTERPRETER_INTERFACES_SCRIPTEDFRAMEPROVIDERINTERFACE_H

lldb/include/lldb/Interpreter/ScriptInterpreter.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "lldb/Host/StreamFile.h"
2828
#include "lldb/Interpreter/Interfaces/OperatingSystemInterface.h"
2929
#include "lldb/Interpreter/Interfaces/ScriptedFrameInterface.h"
30+
#include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h"
3031
#include "lldb/Interpreter/Interfaces/ScriptedPlatformInterface.h"
3132
#include "lldb/Interpreter/Interfaces/ScriptedProcessInterface.h"
3233
#include "lldb/Interpreter/Interfaces/ScriptedThreadInterface.h"
@@ -536,6 +537,11 @@ class ScriptInterpreter : public PluginInterface {
536537
return {};
537538
}
538539

540+
virtual lldb::ScriptedFrameProviderInterfaceSP
541+
CreateScriptedFrameProviderInterface() {
542+
return {};
543+
}
544+
539545
virtual lldb::ScriptedThreadPlanInterfaceSP
540546
CreateScriptedThreadPlanInterface() {
541547
return {};

lldb/source/Plugins/Process/scripted/ScriptedFrame.h renamed to lldb/include/lldb/Interpreter/ScriptedFrame.h

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,27 @@
1-
//===----------------------------------------------------------------------===//
1+
//===-- ScriptedFrame.h -----------------------------------------*- C++ -*-===//
22
//
33
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
44
// See https://llvm.org/LICENSE.txt for license information.
55
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
66
//
77
//===----------------------------------------------------------------------===//
88

9-
#ifndef LLDB_SOURCE_PLUGINS_SCRIPTED_FRAME_H
10-
#define LLDB_SOURCE_PLUGINS_SCRIPTED_FRAME_H
9+
#ifndef LLDB_INTERPRETER_SCRIPTEDFRAME_H
10+
#define LLDB_INTERPRETER_SCRIPTEDFRAME_H
1111

12-
#include "Plugins/Process/Utility/RegisterContextMemory.h"
13-
#include "ScriptedThread.h"
14-
#include "lldb/Interpreter/ScriptInterpreter.h"
1512
#include "lldb/Target/DynamicRegisterInfo.h"
1613
#include "lldb/Target/StackFrame.h"
14+
#include "lldb/lldb-forward.h"
15+
#include "llvm/Support/Error.h"
16+
#include <memory>
1717
#include <string>
1818

19-
namespace lldb_private {
20-
class ScriptedThread;
21-
}
22-
2319
namespace lldb_private {
2420

2521
class ScriptedFrame : public lldb_private::StackFrame {
2622

2723
public:
28-
ScriptedFrame(ScriptedThread &thread,
24+
ScriptedFrame(lldb::ThreadSP thread_sp,
2925
lldb::ScriptedFrameInterfaceSP interface_sp,
3026
lldb::user_id_t frame_idx, lldb::addr_t pc,
3127
SymbolContext &sym_ctx, lldb::RegisterContextSP reg_ctx_sp,
@@ -34,8 +30,28 @@ class ScriptedFrame : public lldb_private::StackFrame {
3430

3531
~ScriptedFrame() override;
3632

33+
/// Create a ScriptedFrame from a script object.
34+
///
35+
/// \param[in] thread_sp
36+
/// The thread this frame belongs to.
37+
///
38+
/// \param[in] scripted_thread_interface_sp
39+
/// The scripted thread interface (needed for ScriptedThread
40+
/// compatibility). Can be nullptr for frames on real threads.
41+
///
42+
/// \param[in] args_sp
43+
/// Arguments to pass to the frame creation.
44+
///
45+
/// \param[in] script_object
46+
/// The script object representing this frame.
47+
///
48+
/// \return
49+
/// An Expected containing the ScriptedFrame shared pointer if successful,
50+
/// otherwise an error.
3751
static llvm::Expected<std::shared_ptr<ScriptedFrame>>
38-
Create(ScriptedThread &thread, StructuredData::DictionarySP args_sp,
52+
Create(lldb::ThreadSP thread_sp,
53+
lldb::ScriptedThreadInterfaceSP scripted_thread_interface_sp,
54+
StructuredData::DictionarySP args_sp,
3955
StructuredData::Generic *script_object = nullptr);
4056

4157
bool IsInlined() override;
@@ -60,4 +76,4 @@ class ScriptedFrame : public lldb_private::StackFrame {
6076

6177
} // namespace lldb_private
6278

63-
#endif // LLDB_SOURCE_PLUGINS_SCRIPTED_FRAME_H
79+
#endif // LLDB_INTERPRETER_SCRIPTEDFRAME_H
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//===-- ScriptedFrameProvider.h --------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLDB_INTERPRETER_SCRIPTEDFRAMEPROVIDER_H
10+
#define LLDB_INTERPRETER_SCRIPTEDFRAMEPROVIDER_H
11+
12+
#include "lldb/Utility/ScriptedMetadata.h"
13+
#include "lldb/Utility/Status.h"
14+
#include "lldb/lldb-forward.h"
15+
#include "llvm/Support/Error.h"
16+
17+
namespace lldb_private {
18+
19+
class ScriptedFrameProvider {
20+
public:
21+
/// Constructor that initializes the scripted frame provider.
22+
///
23+
/// \param[in] thread_sp
24+
/// The thread for which to provide scripted frames.
25+
///
26+
/// \param[in] scripted_metadata
27+
/// The metadata containing the class name and arguments for the
28+
/// scripted frame provider.
29+
///
30+
/// \param[out] error
31+
/// Status object to report any errors during initialization.
32+
ScriptedFrameProvider(lldb::ThreadSP thread_sp,
33+
const ScriptedMetadata &scripted_metadata,
34+
Status &error);
35+
~ScriptedFrameProvider();
36+
37+
/// Get the stack frames from the scripted frame provider.
38+
///
39+
/// \return
40+
/// An Expected containing the StackFrameListSP if successful,
41+
/// otherwise an error describing what went wrong.
42+
llvm::Expected<lldb::StackFrameListSP>
43+
GetStackFrames(lldb::StackFrameListSP real_frames);
44+
45+
/// Get the merge strategy for how scripted frames should be integrated.
46+
///
47+
/// \return
48+
/// The ScriptedFrameProviderMergeStrategy indicating how to merge frames.
49+
lldb::ScriptedFrameProviderMergeStrategy GetMergeStrategy();
50+
51+
private:
52+
lldb::ThreadSP m_thread_sp;
53+
lldb::ScriptedFrameProviderInterfaceSP m_interface_sp;
54+
};
55+
56+
} // namespace lldb_private
57+
58+
#endif // LLDB_INTERPRETER_SCRIPTEDFRAMEPROVIDER_H

lldb/source/Plugins/Process/Utility/RegisterContextMemory.h renamed to lldb/include/lldb/Target/RegisterContextMemory.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
//
77
//===----------------------------------------------------------------------===//
88

9-
#ifndef LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_REGISTERCONTEXTMEMORY_H
10-
#define LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_REGISTERCONTEXTMEMORY_H
9+
#ifndef LLDB_TARGET_REGISTERCONTEXTMEMORY_H
10+
#define LLDB_TARGET_REGISTERCONTEXTMEMORY_H
1111

1212
#include <vector>
1313

@@ -72,4 +72,4 @@ class RegisterContextMemory : public lldb_private::RegisterContext {
7272
operator=(const RegisterContextMemory &) = delete;
7373
};
7474

75-
#endif // LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_REGISTERCONTEXTMEMORY_H
75+
#endif // LLDB_TARGET_REGISTERCONTEXTMEMORY_H

lldb/include/lldb/Target/StackFrameList.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ class StackFrameList {
103103

104104
protected:
105105
friend class Thread;
106+
friend class ScriptedFrameProvider;
106107
friend class ScriptedThread;
107108

108109
/// Use this API to build a stack frame list (used for scripted threads, for

0 commit comments

Comments
 (0)