Skip to content

Commit b74af88

Browse files
committed
[lldb] Add support for ScriptedFrame with real threads
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]>
1 parent 4cd17ee commit b74af88

32 files changed

+1368
-62
lines changed

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: 37 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,42 @@ 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 is already registered on
992+
/// 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[in] thread_filter
1001+
/// The list of threads where to register the frame provider class. If
1002+
/// empty, apply scripted frame provider to all threads.
1003+
///
1004+
/// \return
1005+
/// An error object indicating success or failure.
1006+
lldb::SBError
1007+
RegisterScriptedFrameProvider(const char *class_name,
1008+
lldb::SBStructuredData args_dict,
1009+
lldb::SBThreadCollection thread_filter = {});
1010+
1011+
/// Remove a scripted frame provider from this target by name.
1012+
///
1013+
/// \param[in] provider_name
1014+
/// The name of the frame provider class to remove.
1015+
///
1016+
/// \return
1017+
/// An error object indicating success or failure.
1018+
lldb::SBError RemoveScriptedFrameProvider(const char *provider_name);
1019+
1020+
/// Clear all scripted frame providers for this target.
1021+
///
1022+
/// \return
1023+
/// An error object indicating success or failure.
1024+
lldb::SBError ClearScriptedFrameProvider();
1025+
9891026
protected:
9901027
friend class SBAddress;
9911028
friend class SBAddressRange;

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/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

lldb/include/lldb/Target/SyntheticFrameProvider.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ struct SyntheticFrameProviderDescriptor {
6464
/// Check if this descriptor has valid metadata for script-based providers.
6565
bool IsValid() const { return scripted_metadata_sp != nullptr; }
6666

67+
/// Dump a description of this descriptor to the given stream.
6768
void Dump(Stream *s) const;
6869
};
6970

lldb/include/lldb/Target/Target.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "lldb/Target/PathMappingList.h"
3333
#include "lldb/Target/SectionLoadHistory.h"
3434
#include "lldb/Target/Statistics.h"
35+
#include "lldb/Target/SyntheticFrameProvider.h"
3536
#include "lldb/Target/ThreadSpec.h"
3637
#include "lldb/Utility/ArchSpec.h"
3738
#include "lldb/Utility/Broadcaster.h"
@@ -697,6 +698,37 @@ class Target : public std::enable_shared_from_this<Target>,
697698
Status Attach(ProcessAttachInfo &attach_info,
698699
Stream *stream); // Optional stream to receive first stop info
699700

701+
/// Add or update a scripted frame provider descriptor for this target.
702+
/// The descriptor's name is extracted from its scripted metadata class name.
703+
/// All new threads in this target will check if they match any descriptors
704+
/// to create their frame providers.
705+
///
706+
/// \param[in] descriptor
707+
/// The descriptor to add or update.
708+
Status AddScriptedFrameProviderDescriptor(
709+
const SyntheticFrameProviderDescriptor &descriptor);
710+
711+
/// Remove a scripted frame provider descriptor by name.
712+
///
713+
/// \param[in] name
714+
/// The name/key of the descriptor to remove (the class name).
715+
///
716+
/// \return
717+
/// True if a descriptor was removed, false if no descriptor with that
718+
/// name existed.
719+
bool RemoveScriptedFrameProviderDescriptor(llvm::StringRef name);
720+
721+
/// Clear all scripted frame provider descriptors for this target.
722+
void ClearScriptedFrameProviderDescriptors();
723+
724+
/// Get all scripted frame provider descriptors for this target.
725+
const llvm::StringMap<SyntheticFrameProviderDescriptor> &
726+
GetScriptedFrameProviderDescriptors() const;
727+
728+
/// Get a specific scripted frame provider descriptor by name.
729+
std::optional<SyntheticFrameProviderDescriptor>
730+
GetScriptedFrameProviderDescriptor(llvm::StringRef name) const;
731+
700732
// This part handles the breakpoints.
701733

702734
BreakpointList &GetBreakpointList(bool internal = false);
@@ -1689,6 +1721,13 @@ class Target : public std::enable_shared_from_this<Target>,
16891721
PathMappingList m_image_search_paths;
16901722
TypeSystemMap m_scratch_type_system_map;
16911723

1724+
/// Map of scripted frame provider descriptors for this target.
1725+
/// Keys are the provider class names, values are the descriptors.
1726+
/// Used to initialize frame providers for new threads.
1727+
llvm::StringMap<SyntheticFrameProviderDescriptor>
1728+
m_frame_provider_descriptors;
1729+
mutable std::recursive_mutex m_frame_provider_descriptors_mutex;
1730+
16921731
typedef std::map<lldb::LanguageType, lldb::REPLSP> REPLMap;
16931732
REPLMap m_repl_map;
16941733

lldb/include/lldb/Target/Thread.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,6 +1297,15 @@ class Thread : public std::enable_shared_from_this<Thread>,
12971297

12981298
lldb::StackFrameListSP GetStackFrameList();
12991299

1300+
llvm::Error
1301+
LoadScriptedFrameProvider(const SyntheticFrameProviderDescriptor &descriptor);
1302+
1303+
void ClearScriptedFrameProvider();
1304+
1305+
lldb::SyntheticFrameProviderSP GetFrameProvider() const {
1306+
return m_frame_provider_sp;
1307+
}
1308+
13001309
protected:
13011310
friend class ThreadPlan;
13021311
friend class ThreadList;
@@ -1400,6 +1409,9 @@ class Thread : public std::enable_shared_from_this<Thread>,
14001409
/// The Thread backed by this thread, if any.
14011410
lldb::ThreadWP m_backed_thread;
14021411

1412+
/// The Scripted Frame Provider, if any.
1413+
lldb::SyntheticFrameProviderSP m_frame_provider_sp;
1414+
14031415
private:
14041416
bool m_extended_info_fetched; // Have we tried to retrieve the m_extended_info
14051417
// for this thread?

lldb/include/lldb/Target/ThreadSpec.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ class ThreadSpec {
3434
public:
3535
ThreadSpec();
3636

37+
ThreadSpec(Thread &thread);
38+
3739
static std::unique_ptr<ThreadSpec>
3840
CreateFromStructuredData(const StructuredData::Dictionary &data_dict,
3941
Status &error);

lldb/include/lldb/Utility/ScriptedMetadata.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ class ScriptedMetadata {
2727
}
2828
}
2929

30+
ScriptedMetadata(const ScriptedMetadata &other)
31+
: m_class_name(other.m_class_name), m_args_sp(other.m_args_sp) {}
32+
3033
explicit operator bool() const { return !m_class_name.empty(); }
3134

3235
llvm::StringRef GetClassName() const { return m_class_name; }

0 commit comments

Comments
 (0)