[Lldb-commits] [lldb] [lldb] Add support for ScriptedFrame to provide values/variables. (PR #178575)
Aman LaChapelle via lldb-commits
lldb-commits at lists.llvm.org
Thu Jan 29 08:31:51 PST 2026
https://github.com/bzcheeseman updated https://github.com/llvm/llvm-project/pull/178575
>From 9f9aa221ba85db24da3d66960c73eeeeec2ccc1a Mon Sep 17 00:00:00 2001
From: bzcheeseman <aman.lachapelle at gmail.com>
Date: Wed, 28 Jan 2026 15:40:02 -0800
Subject: [PATCH 1/3] [lldb] Move ValueImpl and ValueLocker to ValueObject,
NFC.
This patch moves ValueImpl and ValueLocker to ValueObject.{h,cpp}. This follows the example set in TypeImpl/SBType, where we have something that SBType uses internally that needs to be exposed in the layer below. In this case, SBValue uses ValueImpl, which wraps ValueObject. The wrapper helps avoid bugs, so we want to keep it, but the script interpreter needs to use it and said interpreter is conceptually *below* the SB layer...which means we can't use methods on SBValue.
This patch is purely the code motion part of that, future patches will actually make use of this moved code.
stack-info: PR: https://github.com/llvm/llvm-project/pull/178573, branch: users/bzcheeseman/stack/4
---
lldb/include/lldb/API/SBValue.h | 10 +-
lldb/include/lldb/ValueObject/ValueObject.h | 78 +++++++++
lldb/source/API/SBValue.cpp | 166 --------------------
lldb/source/ValueObject/ValueObject.cpp | 92 +++++++++++
4 files changed, 176 insertions(+), 170 deletions(-)
diff --git a/lldb/include/lldb/API/SBValue.h b/lldb/include/lldb/API/SBValue.h
index dead11fba19fe..d4cc2f05c39e3 100644
--- a/lldb/include/lldb/API/SBValue.h
+++ b/lldb/include/lldb/API/SBValue.h
@@ -13,10 +13,9 @@
#include "lldb/API/SBDefines.h"
#include "lldb/API/SBType.h"
+namespace lldb_private {
class ValueImpl;
class ValueLocker;
-
-namespace lldb_private {
namespace python {
class SWIGBridge;
}
@@ -490,7 +489,7 @@ class LLDB_API SBValue {
/// \return
/// A ValueObjectSP of the best kind (static, dynamic or synthetic) we
/// can cons up, in accordance with the SBValue's settings.
- lldb::ValueObjectSP GetSP(ValueLocker &value_locker) const;
+ lldb::ValueObjectSP GetSP(lldb_private::ValueLocker &value_locker) const;
// these calls do the right thing WRT adjusting their settings according to
// the target's preferences
@@ -506,8 +505,11 @@ class LLDB_API SBValue {
void SetSP(const lldb::ValueObjectSP &sp, lldb::DynamicValueType use_dynamic,
bool use_synthetic, const char *name);
+protected:
+ friend class lldb_private::ScriptInterpreter;
+
private:
- typedef std::shared_ptr<ValueImpl> ValueImplSP;
+ typedef std::shared_ptr<lldb_private::ValueImpl> ValueImplSP;
ValueImplSP m_opaque_sp;
void SetSP(ValueImplSP impl_sp);
diff --git a/lldb/include/lldb/ValueObject/ValueObject.h b/lldb/include/lldb/ValueObject/ValueObject.h
index 3f9f2b5de8dbe..8a528bad15f94 100644
--- a/lldb/include/lldb/ValueObject/ValueObject.h
+++ b/lldb/include/lldb/ValueObject/ValueObject.h
@@ -1121,6 +1121,84 @@ class ValueObject {
const ValueObject &operator=(const ValueObject &) = delete;
};
+// The two classes below are used by the public SBValue API implementation. This
+// is useful here because we need them in order to access the underlying
+// ValueObject from SBValue without introducing a back-dependency from the API
+// library to the more core libs.
+
+class ValueImpl {
+public:
+ ValueImpl() = default;
+
+ ValueImpl(lldb::ValueObjectSP in_valobj_sp,
+ lldb::DynamicValueType use_dynamic, bool use_synthetic,
+ const char *name = nullptr);
+
+ ValueImpl(const ValueImpl &rhs) = default;
+
+ ValueImpl &operator=(const ValueImpl &rhs);
+
+ bool IsValid();
+
+ lldb::ValueObjectSP GetRootSP() { return m_valobj_sp; }
+
+ lldb::ValueObjectSP GetSP(Process::StopLocker &stop_locker,
+ std::unique_lock<std::recursive_mutex> &lock,
+ Status &error);
+
+ void SetUseDynamic(lldb::DynamicValueType use_dynamic) {
+ m_use_dynamic = use_dynamic;
+ }
+
+ void SetUseSynthetic(bool use_synthetic) { m_use_synthetic = use_synthetic; }
+
+ lldb::DynamicValueType GetUseDynamic() { return m_use_dynamic; }
+
+ bool GetUseSynthetic() { return m_use_synthetic; }
+
+ // All the derived values that we would make from the m_valobj_sp will share
+ // the ExecutionContext with m_valobj_sp, so we don't need to do the
+ // calculations in GetSP to return the Target, Process, Thread or Frame. It
+ // is convenient to provide simple accessors for these, which I do here.
+ lldb::TargetSP GetTargetSP() {
+ return m_valobj_sp ? m_valobj_sp->GetTargetSP() : lldb::TargetSP{};
+ }
+
+ lldb::ProcessSP GetProcessSP() {
+ return m_valobj_sp ? m_valobj_sp->GetProcessSP() : lldb::ProcessSP{};
+ }
+
+ lldb::ThreadSP GetThreadSP() {
+ return m_valobj_sp ? m_valobj_sp->GetThreadSP() : lldb::ThreadSP{};
+ }
+
+ lldb::StackFrameSP GetFrameSP() {
+ return m_valobj_sp ? m_valobj_sp->GetFrameSP() : lldb::StackFrameSP{};
+ }
+
+private:
+ lldb::ValueObjectSP m_valobj_sp;
+ lldb::DynamicValueType m_use_dynamic;
+ bool m_use_synthetic;
+ ConstString m_name;
+};
+
+class ValueLocker {
+public:
+ ValueLocker() = default;
+
+ lldb::ValueObjectSP GetLockedSP(ValueImpl &in_value) {
+ return in_value.GetSP(m_stop_locker, m_lock, m_lock_error);
+ }
+
+ Status &GetError() { return m_lock_error; }
+
+private:
+ Process::StopLocker m_stop_locker;
+ std::unique_lock<std::recursive_mutex> m_lock;
+ Status m_lock_error;
+};
+
} // namespace lldb_private
#endif // LLDB_VALUEOBJECT_VALUEOBJECT_H
diff --git a/lldb/source/API/SBValue.cpp b/lldb/source/API/SBValue.cpp
index 5b67270859da2..adc03785602e1 100644
--- a/lldb/source/API/SBValue.cpp
+++ b/lldb/source/API/SBValue.cpp
@@ -52,172 +52,6 @@
using namespace lldb;
using namespace lldb_private;
-class ValueImpl {
-public:
- ValueImpl() = default;
-
- ValueImpl(lldb::ValueObjectSP in_valobj_sp,
- lldb::DynamicValueType use_dynamic, bool use_synthetic,
- const char *name = nullptr)
- : m_use_dynamic(use_dynamic), m_use_synthetic(use_synthetic),
- m_name(name) {
- if (in_valobj_sp) {
- if ((m_valobj_sp = in_valobj_sp->GetQualifiedRepresentationIfAvailable(
- lldb::eNoDynamicValues, false))) {
- if (!m_name.IsEmpty())
- m_valobj_sp->SetName(m_name);
- }
- }
- }
-
- ValueImpl(const ValueImpl &rhs) = default;
-
- ValueImpl &operator=(const ValueImpl &rhs) {
- if (this != &rhs) {
- m_valobj_sp = rhs.m_valobj_sp;
- m_use_dynamic = rhs.m_use_dynamic;
- m_use_synthetic = rhs.m_use_synthetic;
- m_name = rhs.m_name;
- }
- return *this;
- }
-
- bool IsValid() {
- if (m_valobj_sp.get() == nullptr)
- return false;
- else {
- // FIXME: This check is necessary but not sufficient. We for sure don't
- // want to touch SBValues whose owning
- // targets have gone away. This check is a little weak in that it
- // enforces that restriction when you call IsValid, but since IsValid
- // doesn't lock the target, you have no guarantee that the SBValue won't
- // go invalid after you call this... Also, an SBValue could depend on
- // data from one of the modules in the target, and those could go away
- // independently of the target, for instance if a module is unloaded.
- // But right now, neither SBValues nor ValueObjects know which modules
- // they depend on. So I have no good way to make that check without
- // tracking that in all the ValueObject subclasses.
- TargetSP target_sp = m_valobj_sp->GetTargetSP();
- return target_sp && target_sp->IsValid();
- }
- }
-
- lldb::ValueObjectSP GetRootSP() { return m_valobj_sp; }
-
- lldb::ValueObjectSP GetSP(Process::StopLocker &stop_locker,
- std::unique_lock<std::recursive_mutex> &lock,
- Status &error) {
- if (!m_valobj_sp) {
- error = Status::FromErrorString("invalid value object");
- return m_valobj_sp;
- }
-
- lldb::ValueObjectSP value_sp = m_valobj_sp;
-
- Target *target = value_sp->GetTargetSP().get();
- // If this ValueObject holds an error, then it is valuable for that.
- if (value_sp->GetError().Fail())
- return value_sp;
-
- if (!target)
- return ValueObjectSP();
-
- lock = std::unique_lock<std::recursive_mutex>(target->GetAPIMutex());
-
- ProcessSP process_sp(value_sp->GetProcessSP());
- if (process_sp && !stop_locker.TryLock(&process_sp->GetRunLock())) {
- // We don't allow people to play around with ValueObject if the process
- // is running. If you want to look at values, pause the process, then
- // look.
- error = Status::FromErrorString("process must be stopped.");
- return ValueObjectSP();
- }
-
- if (m_use_dynamic != eNoDynamicValues) {
- ValueObjectSP dynamic_sp = value_sp->GetDynamicValue(m_use_dynamic);
- if (dynamic_sp)
- value_sp = dynamic_sp;
- }
-
- if (m_use_synthetic) {
- ValueObjectSP synthetic_sp = value_sp->GetSyntheticValue();
- if (synthetic_sp)
- value_sp = synthetic_sp;
- }
-
- if (!value_sp)
- error = Status::FromErrorString("invalid value object");
- if (!m_name.IsEmpty())
- value_sp->SetName(m_name);
-
- return value_sp;
- }
-
- void SetUseDynamic(lldb::DynamicValueType use_dynamic) {
- m_use_dynamic = use_dynamic;
- }
-
- void SetUseSynthetic(bool use_synthetic) { m_use_synthetic = use_synthetic; }
-
- lldb::DynamicValueType GetUseDynamic() { return m_use_dynamic; }
-
- bool GetUseSynthetic() { return m_use_synthetic; }
-
- // All the derived values that we would make from the m_valobj_sp will share
- // the ExecutionContext with m_valobj_sp, so we don't need to do the
- // calculations in GetSP to return the Target, Process, Thread or Frame. It
- // is convenient to provide simple accessors for these, which I do here.
- TargetSP GetTargetSP() {
- if (m_valobj_sp)
- return m_valobj_sp->GetTargetSP();
- else
- return TargetSP();
- }
-
- ProcessSP GetProcessSP() {
- if (m_valobj_sp)
- return m_valobj_sp->GetProcessSP();
- else
- return ProcessSP();
- }
-
- ThreadSP GetThreadSP() {
- if (m_valobj_sp)
- return m_valobj_sp->GetThreadSP();
- else
- return ThreadSP();
- }
-
- StackFrameSP GetFrameSP() {
- if (m_valobj_sp)
- return m_valobj_sp->GetFrameSP();
- else
- return StackFrameSP();
- }
-
-private:
- lldb::ValueObjectSP m_valobj_sp;
- lldb::DynamicValueType m_use_dynamic;
- bool m_use_synthetic;
- ConstString m_name;
-};
-
-class ValueLocker {
-public:
- ValueLocker() = default;
-
- ValueObjectSP GetLockedSP(ValueImpl &in_value) {
- return in_value.GetSP(m_stop_locker, m_lock, m_lock_error);
- }
-
- Status &GetError() { return m_lock_error; }
-
-private:
- Process::StopLocker m_stop_locker;
- std::unique_lock<std::recursive_mutex> m_lock;
- Status m_lock_error;
-};
-
SBValue::SBValue() { LLDB_INSTRUMENT_VA(this); }
SBValue::SBValue(const lldb::ValueObjectSP &value_sp) {
diff --git a/lldb/source/ValueObject/ValueObject.cpp b/lldb/source/ValueObject/ValueObject.cpp
index 121054e3e92ed..ee553b2b3925c 100644
--- a/lldb/source/ValueObject/ValueObject.cpp
+++ b/lldb/source/ValueObject/ValueObject.cpp
@@ -3768,3 +3768,95 @@ ValueObjectSP ValueObject::Persist() {
lldb::ValueObjectSP ValueObject::GetVTable() {
return ValueObjectVTable::Create(*this);
}
+
+ValueImpl::ValueImpl(lldb::ValueObjectSP in_valobj_sp,
+ lldb::DynamicValueType use_dynamic, bool use_synthetic,
+ const char *name)
+ : m_use_dynamic(use_dynamic), m_use_synthetic(use_synthetic), m_name(name) {
+ if (in_valobj_sp) {
+ if ((m_valobj_sp = in_valobj_sp->GetQualifiedRepresentationIfAvailable(
+ lldb::eNoDynamicValues, false))) {
+ if (!m_name.IsEmpty())
+ m_valobj_sp->SetName(m_name);
+ }
+ }
+}
+
+ValueImpl &ValueImpl::operator=(const ValueImpl &rhs) {
+ if (this != &rhs) {
+ m_valobj_sp = rhs.m_valobj_sp;
+ m_use_dynamic = rhs.m_use_dynamic;
+ m_use_synthetic = rhs.m_use_synthetic;
+ m_name = rhs.m_name;
+ }
+ return *this;
+}
+
+bool ValueImpl::IsValid() {
+ if (m_valobj_sp.get() == nullptr)
+ return false;
+ else {
+ // FIXME: This check is necessary but not sufficient. We for sure don't
+ // want to touch SBValues whose owning
+ // targets have gone away. This check is a little weak in that it
+ // enforces that restriction when you call IsValid, but since IsValid
+ // doesn't lock the target, you have no guarantee that the SBValue won't
+ // go invalid after you call this... Also, an SBValue could depend on
+ // data from one of the modules in the target, and those could go away
+ // independently of the target, for instance if a module is unloaded.
+ // But right now, neither SBValues nor ValueObjects know which modules
+ // they depend on. So I have no good way to make that check without
+ // tracking that in all the ValueObject subclasses.
+ TargetSP target_sp = m_valobj_sp->GetTargetSP();
+ return target_sp && target_sp->IsValid();
+ }
+}
+
+lldb::ValueObjectSP
+ValueImpl::GetSP(Process::StopLocker &stop_locker,
+ std::unique_lock<std::recursive_mutex> &lock, Status &error) {
+ if (!m_valobj_sp) {
+ error = Status::FromErrorString("invalid value object");
+ return m_valobj_sp;
+ }
+
+ lldb::ValueObjectSP value_sp = m_valobj_sp;
+
+ Target *target = value_sp->GetTargetSP().get();
+ // If this ValueObject holds an error, then it is valuable for that.
+ if (value_sp->GetError().Fail())
+ return value_sp;
+
+ if (!target)
+ return ValueObjectSP();
+
+ lock = std::unique_lock<std::recursive_mutex>(target->GetAPIMutex());
+
+ ProcessSP process_sp(value_sp->GetProcessSP());
+ if (process_sp && !stop_locker.TryLock(&process_sp->GetRunLock())) {
+ // We don't allow people to play around with ValueObject if the process
+ // is running. If you want to look at values, pause the process, then
+ // look.
+ error = Status::FromErrorString("process must be stopped.");
+ return ValueObjectSP();
+ }
+
+ if (m_use_dynamic != eNoDynamicValues) {
+ ValueObjectSP dynamic_sp = value_sp->GetDynamicValue(m_use_dynamic);
+ if (dynamic_sp)
+ value_sp = dynamic_sp;
+ }
+
+ if (m_use_synthetic) {
+ ValueObjectSP synthetic_sp = value_sp->GetSyntheticValue();
+ if (synthetic_sp)
+ value_sp = synthetic_sp;
+ }
+
+ if (!value_sp)
+ error = Status::FromErrorString("invalid value object");
+ if (!m_name.IsEmpty())
+ value_sp->SetName(m_name);
+
+ return value_sp;
+}
>From 10310df5f1e7279c56f32fc65440e0ea0ce7c956 Mon Sep 17 00:00:00 2001
From: bzcheeseman <aman.lachapelle at gmail.com>
Date: Wed, 28 Jan 2026 16:03:57 -0800
Subject: [PATCH 2/3] [lldb] Add conversions for SBValueList and SBValue to the
python bridge.
This patch adds support for:
- PyObject -> SBValueList (which was surprisingly not there before!)
- PyObject -> SBValue
- SBValue -> ValueObjectSP using the ScriptInterpreter
These three are the main remaining plumbing changes necessary before we can get to the meat of actually using ScriptedFrame to provide values to the printer/etc. Future patches build off this change in order to allow ScriptedFrames to provide variables and get values for variable expressions.
stack-info: PR: https://github.com/llvm/llvm-project/pull/178574, branch: users/bzcheeseman/stack/5
---
lldb/bindings/python/python-wrapper.swig | 12 ++++++
.../lldb/Interpreter/ScriptInterpreter.h | 3 ++
lldb/source/Interpreter/ScriptInterpreter.cpp | 10 +++++
.../Interfaces/ScriptedPythonInterface.cpp | 38 +++++++++++++++++++
.../Interfaces/ScriptedPythonInterface.h | 14 +++++++
.../Python/SWIGPythonBridge.h | 1 +
6 files changed, 78 insertions(+)
diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig
index 0ba152166522b..bf59569920470 100644
--- a/lldb/bindings/python/python-wrapper.swig
+++ b/lldb/bindings/python/python-wrapper.swig
@@ -545,6 +545,18 @@ void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBValue(PyObject * data
return sb_ptr;
}
+void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBValueList(PyObject * data) {
+ lldb::SBValueList *sb_ptr = NULL;
+
+ int valid_cast =
+ SWIG_ConvertPtr(data, (void **)&sb_ptr, SWIGTYPE_p_lldb__SBValueList, 0);
+
+ if (valid_cast == -1)
+ return NULL;
+
+ return sb_ptr;
+}
+
void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBMemoryRegionInfo(PyObject *
data) {
lldb::SBMemoryRegionInfo *sb_ptr = NULL;
diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index 0b91d6756552d..557d73a415452 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -609,6 +609,9 @@ class ScriptInterpreter : public PluginInterface {
lldb::StackFrameListSP
GetOpaqueTypeFromSBFrameList(const lldb::SBFrameList &exe_ctx) const;
+ lldb::ValueObjectSP
+ GetOpaqueTypeFromSBValue(const lldb::SBValue &value) const;
+
protected:
Debugger &m_debugger;
lldb::ScriptLanguage m_script_lang;
diff --git a/lldb/source/Interpreter/ScriptInterpreter.cpp b/lldb/source/Interpreter/ScriptInterpreter.cpp
index 7bad10ff3ea61..5e8478c2670bb 100644
--- a/lldb/source/Interpreter/ScriptInterpreter.cpp
+++ b/lldb/source/Interpreter/ScriptInterpreter.cpp
@@ -15,6 +15,7 @@
#include "lldb/Utility/Status.h"
#include "lldb/Utility/Stream.h"
#include "lldb/Utility/StringList.h"
+#include "lldb/ValueObject/ValueObject.h"
#if defined(_WIN32)
#include "lldb/Host/windows/ConnectionGenericFileWindows.h"
#endif
@@ -162,6 +163,15 @@ lldb::StackFrameListSP ScriptInterpreter::GetOpaqueTypeFromSBFrameList(
return frame_list.m_opaque_sp;
}
+lldb::ValueObjectSP
+ScriptInterpreter::GetOpaqueTypeFromSBValue(const lldb::SBValue &value) const {
+ if (!value.m_opaque_sp)
+ return lldb::ValueObjectSP();
+
+ lldb_private::ValueLocker locker;
+ return locker.GetLockedSP(*value.m_opaque_sp);
+}
+
lldb::ScriptLanguage
ScriptInterpreter::StringToLanguage(const llvm::StringRef &language) {
if (language.equals_insensitive(LanguageToString(eScriptLanguageNone)))
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
index ba4473cf9ec4d..f5fd8b2d2d802 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp
@@ -18,6 +18,7 @@
#include "../ScriptInterpreterPythonImpl.h"
#include "ScriptedPythonInterface.h"
#include "lldb/Symbol/SymbolContext.h"
+#include "lldb/ValueObject/ValueObjectList.h"
#include <optional>
using namespace lldb;
@@ -273,4 +274,41 @@ ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StackFrameListSP>(
return m_interpreter.GetOpaqueTypeFromSBFrameList(*sb_frame_list);
}
+template <>
+lldb::ValueObjectSP
+ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ValueObjectSP>(
+ python::PythonObject &p, Status &error) {
+ lldb::SBValue *sb_value = reinterpret_cast<lldb::SBValue *>(
+ python::LLDBSWIGPython_CastPyObjectToSBValue(p.get()));
+ if (!sb_value) {
+ error = Status::FromErrorStringWithFormat(
+ "couldn't cast lldb::SBValue to lldb::ValueObjectSP");
+ return {};
+ }
+
+ return m_interpreter.GetOpaqueTypeFromSBValue(*sb_value);
+}
+
+template <>
+lldb::ValueObjectListSP
+ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ValueObjectListSP>(
+ python::PythonObject &p, Status &error) {
+ lldb::SBValueList *sb_value_list = reinterpret_cast<lldb::SBValueList *>(
+ python::LLDBSWIGPython_CastPyObjectToSBValueList(p.get()));
+
+ if (!sb_value_list) {
+ error = Status::FromErrorStringWithFormat(
+ "couldn't cast lldb::SBValueList to lldb::ValueObjectListSP");
+ return {};
+ }
+
+ lldb::ValueObjectListSP out = std::make_shared<ValueObjectList>();
+ for (uint32_t i = 0, e = sb_value_list->GetSize(); i < e; ++i) {
+ SBValue value = sb_value_list->GetValueAtIndex(i);
+ out->Append(m_interpreter.GetOpaqueTypeFromSBValue(value));
+ }
+
+ return out;
+}
+
#endif
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
index b737f945845f6..5e3df8f18c2be 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
@@ -656,6 +656,10 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
return python::SWIGBridge::ToSWIGWrapper(arg);
}
+ python::PythonObject Transform(lldb::ValueObjectSP arg) {
+ return python::SWIGBridge::ToSWIGWrapper(arg);
+ }
+
template <typename T, typename U>
void ReverseTransform(T &original_arg, U transformed_arg, Status &error) {
// If U is not a PythonObject, don't touch it!
@@ -814,6 +818,16 @@ lldb::StackFrameListSP
ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StackFrameListSP>(
python::PythonObject &p, Status &error);
+template <>
+lldb::ValueObjectSP
+ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ValueObjectSP>(
+ python::PythonObject &p, Status &error);
+
+template <>
+lldb::ValueObjectListSP
+ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ValueObjectListSP>(
+ 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 32948ffd30023..9f68445d0d72b 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
@@ -269,6 +269,7 @@ void *LLDBSWIGPython_CastPyObjectToSBThread(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBFrame(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBSymbolContext(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBValue(PyObject *data);
+void *LLDBSWIGPython_CastPyObjectToSBValueList(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBMemoryRegionInfo(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBExecutionContext(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBFrameList(PyObject *data);
>From cd2c4674e4901214dd1da492c13fa416e134fabb Mon Sep 17 00:00:00 2001
From: bzcheeseman <aman.lachapelle at gmail.com>
Date: Wed, 28 Jan 2026 16:35:22 -0800
Subject: [PATCH 3/3] [lldb] Add support for ScriptedFrame to provide
values/variables.
This patch adds plumbing to support the implementations of StackFrame::Get{*}Variable{*} on ScriptedFrame. The major pieces required are:
- A modification to ScriptedFrameInterface, so that we can actually call the python methods.
- A corresponding update to the python implementation to call the python methods.
- An implementation in ScriptedFrame that can get the variable list on construction inside ScriptedFrame::Create, and pass that list into the ScriptedFrame so it can get those values on request.
There is a major caveat, which is that if the values from the python side don't have variables attached, right now, they won't be passed into the scripted frame to be stored in the variable list. Future discussions around adding support for 'extended variables' when printing frame variables may create a reason to change the VariableListSP into a ValueObjectListSP, and generate the VariableListSP on the fly, but that should be addressed at a later time.
This patch also adds tests to the frame provider test suite to prove these changes all plumb together correctly.
stack-info: PR: https://github.com/llvm/llvm-project/pull/178575, branch: users/bzcheeseman/stack/6
---
.../Interfaces/ScriptedFrameInterface.h | 11 +++
.../Process/scripted/ScriptedFrame.cpp | 77 +++++++++++++++--
.../Plugins/Process/scripted/ScriptedFrame.h | 21 ++++-
.../ScriptedFramePythonInterface.cpp | 29 +++++++
.../Interfaces/ScriptedFramePythonInterface.h | 6 ++
.../TestScriptedFrameProvider.py | 53 ++++++++++++
.../scripted_frame_provider/main.cpp | 4 +
.../test_frame_providers.py | 82 +++++++++++++++++++
8 files changed, 277 insertions(+), 6 deletions(-)
diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h
index 8ef4b37d6ba12..90170d3bead5f 100644
--- a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h
+++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h
@@ -10,6 +10,7 @@
#define LLDB_INTERPRETER_INTERFACES_SCRIPTEDFRAMEINTERFACE_H
#include "ScriptedInterface.h"
+#include "lldb/API/SBValueList.h"
#include "lldb/Core/StructuredDataImpl.h"
#include "lldb/Symbol/SymbolContext.h"
#include "lldb/lldb-private.h"
@@ -49,6 +50,16 @@ class ScriptedFrameInterface : virtual public ScriptedInterface {
virtual std::optional<std::string> GetRegisterContext() {
return std::nullopt;
}
+
+ virtual std::optional<lldb::ValueObjectListSP> GetVariables() {
+ return std::nullopt;
+ }
+
+ virtual lldb::ValueObjectSP
+ GetValueObjectForVariableExpression(llvm::StringRef expr, uint32_t options,
+ Status &error) {
+ return nullptr;
+ }
};
} // namespace lldb_private
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
index 70ce101c6c834..caf37dc71e03e 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
+++ b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
@@ -9,6 +9,7 @@
#include "ScriptedFrame.h"
#include "Plugins/Process/Utility/RegisterContextMemory.h"
+#include "lldb/API/SBDeclaration.h"
#include "lldb/Core/Address.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Core/Module.h"
@@ -20,6 +21,7 @@
#include "lldb/Symbol/CompileUnit.h"
#include "lldb/Symbol/SymbolContext.h"
#include "lldb/Symbol/SymbolFile.h"
+#include "lldb/Symbol/VariableList.h"
#include "lldb/Target/ExecutionContext.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/RegisterContext.h"
@@ -28,6 +30,8 @@
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/StructuredData.h"
+#include "lldb/ValueObject/ValueObject.h"
+#include "lldb/ValueObject/ValueObjectList.h"
using namespace lldb;
using namespace lldb_private;
@@ -106,6 +110,30 @@ ScriptedFrame::Create(ThreadSP thread_sp,
if (maybe_sym_ctx)
sc = *maybe_sym_ctx;
+ // If we have any variables from the scripted interface, pull them out and add
+ // them to the ScriptedFrame object.
+ std::optional<ValueObjectListSP> maybe_variables =
+ scripted_frame_interface->GetVariables();
+ lldb::VariableListSP variable_list_sp = nullptr;
+ if (maybe_variables && *maybe_variables) {
+ variable_list_sp = std::make_shared<VariableList>();
+ ValueObjectListSP value_list_sp = *maybe_variables;
+
+ for (uint32_t i = 0, e = value_list_sp->GetSize(); i < e; ++i) {
+ ValueObjectSP v = value_list_sp->GetValueObjectAtIndex(i);
+ if (!v)
+ continue;
+
+ VariableSP var = v->GetVariable();
+ // TODO: We could in theory ask the scripted frame to *produce* a
+ // variable for this value object.
+ if (!var)
+ continue;
+
+ variable_list_sp->AddVariable(var);
+ }
+ }
+
lldb::RegisterContextSP reg_ctx_sp;
auto regs_or_err =
CreateRegisterContext(*scripted_frame_interface, *thread_sp, frame_id);
@@ -114,9 +142,10 @@ ScriptedFrame::Create(ThreadSP thread_sp,
else
reg_ctx_sp = *regs_or_err;
- return std::make_shared<ScriptedFrame>(thread_sp, scripted_frame_interface,
- frame_id, pc, sc, reg_ctx_sp,
- owned_script_object_sp);
+ return std::make_shared<ScriptedFrame>(
+ thread_sp, scripted_frame_interface, frame_id, pc, sc, reg_ctx_sp,
+ owned_script_object_sp, variable_list_sp,
+ maybe_variables.value_or(nullptr));
}
ScriptedFrame::ScriptedFrame(ThreadSP thread_sp,
@@ -124,13 +153,16 @@ ScriptedFrame::ScriptedFrame(ThreadSP thread_sp,
lldb::user_id_t id, lldb::addr_t pc,
SymbolContext &sym_ctx,
lldb::RegisterContextSP reg_ctx_sp,
- StructuredData::GenericSP script_object_sp)
+ StructuredData::GenericSP script_object_sp,
+ lldb::VariableListSP variables_sp,
+ lldb::ValueObjectListSP values_sp)
: 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),
m_scripted_frame_interface_sp(interface_sp),
- m_script_object_sp(script_object_sp) {
+ m_script_object_sp(script_object_sp), m_variable_list_sp(variables_sp),
+ m_value_objects_sp(values_sp) {
// FIXME: This should be part of the base class constructor.
m_stack_frame_kind = StackFrame::Kind::Synthetic;
}
@@ -265,3 +297,38 @@ lldb::RegisterContextSP ScriptedFrame::GetRegisterContext() {
return m_reg_context_sp;
}
+
+VariableList *ScriptedFrame::GetVariableList(bool get_file_globals,
+ Status *error_ptr) {
+ return m_variable_list_sp.get();
+}
+
+lldb::VariableListSP
+ScriptedFrame::GetInScopeVariableList(bool get_file_globals,
+ bool must_have_valid_location) {
+ return m_variable_list_sp;
+}
+
+lldb::ValueObjectSP ScriptedFrame::GetValueObjectForFrameVariable(
+ const lldb::VariableSP &variable_sp, lldb::DynamicValueType use_dynamic) {
+ for (size_t i = 0, e = m_variable_list_sp->GetSize(); i < e; ++i) {
+ if (m_variable_list_sp->GetVariableAtIndex(i) == variable_sp) {
+ return m_value_objects_sp->GetValueObjectAtIndex(i);
+ }
+ }
+ return nullptr;
+}
+
+lldb::ValueObjectSP ScriptedFrame::GetValueForVariableExpressionPath(
+ llvm::StringRef var_expr, lldb::DynamicValueType use_dynamic,
+ uint32_t options, lldb::VariableSP &var_sp, Status &error) {
+ // Unless the frame implementation knows how to create variables (which it
+ // doesn't), we can't construct anything for the variable. This may seem
+ // somewhat out of place, but it's basically because of how this API is used -
+ // the print command uses this API to fill in var_sp; and this implementation
+ // can't do that!
+ (void)var_sp;
+ // Otherwise, delegate to the scripted frame interface pointer.
+ return m_scripted_frame_interface_sp->GetValueObjectForVariableExpression(
+ var_expr, options, error);
+}
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.h b/lldb/source/Plugins/Process/scripted/ScriptedFrame.h
index 0545548e912e6..5aef8f58b1aae 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.h
+++ b/lldb/source/Plugins/Process/scripted/ScriptedFrame.h
@@ -26,7 +26,9 @@ class ScriptedFrame : public lldb_private::StackFrame {
lldb::ScriptedFrameInterfaceSP interface_sp,
lldb::user_id_t frame_idx, lldb::addr_t pc,
SymbolContext &sym_ctx, lldb::RegisterContextSP reg_ctx_sp,
- StructuredData::GenericSP script_object_sp = nullptr);
+ StructuredData::GenericSP script_object_sp = nullptr,
+ lldb::VariableListSP variables_sp = nullptr,
+ lldb::ValueObjectListSP values_sp = nullptr);
~ScriptedFrame() override;
@@ -63,6 +65,21 @@ class ScriptedFrame : public lldb_private::StackFrame {
lldb::RegisterContextSP GetRegisterContext() override;
+ VariableList *GetVariableList(bool get_file_globals,
+ lldb_private::Status *error_ptr) override;
+
+ lldb::VariableListSP
+ GetInScopeVariableList(bool get_file_globals,
+ bool must_have_valid_location = false) override;
+
+ lldb::ValueObjectSP
+ GetValueObjectForFrameVariable(const lldb::VariableSP &variable_sp,
+ lldb::DynamicValueType use_dynamic) override;
+
+ lldb::ValueObjectSP GetValueForVariableExpressionPath(
+ llvm::StringRef var_expr, lldb::DynamicValueType use_dynamic,
+ uint32_t options, lldb::VariableSP &var_sp, Status &error) override;
+
bool isA(const void *ClassID) const override {
return ClassID == &ID || StackFrame::isA(ClassID);
}
@@ -82,6 +99,8 @@ class ScriptedFrame : public lldb_private::StackFrame {
lldb::ScriptedFrameInterfaceSP m_scripted_frame_interface_sp;
lldb_private::StructuredData::GenericSP m_script_object_sp;
+ lldb::VariableListSP m_variable_list_sp;
+ lldb::ValueObjectListSP m_value_objects_sp;
static char ID;
};
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.cpp
index 20ca7a2c01356..e083c19d6eed2 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.cpp
@@ -154,4 +154,33 @@ std::optional<std::string> ScriptedFramePythonInterface::GetRegisterContext() {
return obj->GetAsString()->GetValue().str();
}
+std::optional<lldb::ValueObjectListSP>
+ScriptedFramePythonInterface::GetVariables() {
+ Status error;
+ auto vals = Dispatch<lldb::ValueObjectListSP>("get_variables", error);
+
+ if (error.Fail()) {
+ return ErrorWithMessage<lldb::ValueObjectListSP>(LLVM_PRETTY_FUNCTION,
+ error.AsCString(), error);
+ }
+
+ return vals;
+}
+
+lldb::ValueObjectSP
+ScriptedFramePythonInterface::GetValueObjectForVariableExpression(
+ llvm::StringRef expr, uint32_t options, Status &status) {
+ Status dispatch_error;
+ auto val = Dispatch<lldb::ValueObjectSP>("get_value_for_variable_expression",
+ dispatch_error, expr.data(), options,
+ status);
+
+ if (dispatch_error.Fail()) {
+ return ErrorWithMessage<lldb::ValueObjectSP>(
+ LLVM_PRETTY_FUNCTION, dispatch_error.AsCString(), dispatch_error);
+ }
+
+ return val;
+}
+
#endif
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.h
index 3aff237ae65d5..bb866c9146640 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.h
@@ -52,6 +52,12 @@ class ScriptedFramePythonInterface : public ScriptedFrameInterface,
StructuredData::DictionarySP GetRegisterInfo() override;
std::optional<std::string> GetRegisterContext() override;
+
+ std::optional<lldb::ValueObjectListSP> GetVariables() override;
+
+ lldb::ValueObjectSP
+ GetValueObjectForVariableExpression(llvm::StringRef expr, uint32_t options,
+ Status &status) override;
};
} // namespace lldb_private
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
index 964d213b16887..7dd74013b90f8 100644
--- a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
+++ b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
@@ -730,3 +730,56 @@ def test_chained_frame_providers(self):
frame3 = thread.GetFrameAtIndex(3)
self.assertIsNotNone(frame3)
self.assertIn("thread_func", frame3.GetFunctionName())
+
+ def test_get_values(self):
+ """Test a frame that provides values."""
+ self.build()
+ # Set the breakpoint after the variable_in_main variable exists and can be queried.
+ target, process, thread, bkpt = lldbutil.run_to_line_breakpoint(
+ self, lldb.SBFileSpec(self.source), 35, only_one_thread=False
+ )
+
+ # Get original frame count.
+ original_frame_count = thread.GetNumFrames()
+ self.assertGreaterEqual(
+ original_frame_count, 2, "Should have at least 2 real frames"
+ )
+
+ # Import the test frame providers.
+ script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py")
+ self.runCmd("command script import " + script_path)
+
+ # Register a provider that can provide variables.
+ error = lldb.SBError()
+ target.RegisterScriptedFrameProvider(
+ "test_frame_providers.ValueProvidingFrameProvider",
+ lldb.SBStructuredData(),
+ error,
+ )
+ self.assertTrue(error.Success(), f"Failed to register provider: {error}")
+
+ # Verify we have 1 more frame.
+ new_frame_count = thread.GetNumFrames()
+ self.assertEqual(
+ new_frame_count,
+ original_frame_count + 1,
+ "Should have original frames + 1 extra frames",
+ )
+
+ # Check that we can get variables from this frame.
+ frame0 = thread.GetFrameAtIndex(0)
+ self.assertIsNotNone(frame0)
+ # Get every variable visible at this point
+ variables = frame0.GetVariables(True, True, True, False)
+ self.assertTrue(variables.IsValid() and variables.GetSize() == 1)
+
+ # Check that we can get values from paths. `_handler_one` is a special
+ # value we provide through only our expression handler in the frame
+ # implementation.
+ one = frame0.GetValueForVariablePath("_handler_one")
+ self.assertEqual(one.unsigned, 1)
+ var = frame0.GetValueForVariablePath("variable_in_main")
+ # The names won't necessarily match, but the values should (the frame renames the SBValue)
+ self.assertEqual(var.unsigned, variables.GetValueAtIndex(0).unsigned)
+ varp1 = frame0.GetValueForVariablePath("variable_in_main + 1")
+ self.assertEqual(varp1.unsigned, 124)
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/main.cpp b/lldb/test/API/functionalities/scripted_frame_provider/main.cpp
index 0298e88e4de16..e1d346c29052b 100644
--- a/lldb/test/API/functionalities/scripted_frame_provider/main.cpp
+++ b/lldb/test/API/functionalities/scripted_frame_provider/main.cpp
@@ -29,6 +29,10 @@ void thread_func(int thread_num) {
int main(int argc, char **argv) {
std::thread threads[NUM_THREADS];
+ // Used as an existing C++ variable we can anchor on.
+ int variable_in_main = 123;
+ (void)variable_in_main;
+
for (int i = 0; i < NUM_THREADS; i++) {
threads[i] = std::thread(thread_func, i);
}
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
index 6233041f68a51..3a30e4fa96d6e 100644
--- a/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py
+++ b/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py
@@ -458,3 +458,85 @@ def get_frame_at_index(self, index):
# Pass through input frames (shifted by 1)
return index - 1
return None
+
+
+class ValueProvidingFrame(ScriptedFrame):
+ """Scripted frame with a valid PC but no associated module."""
+
+ def __init__(self, thread, idx, pc, function_name, variable):
+ args = lldb.SBStructuredData()
+ super().__init__(thread, args)
+
+ self.idx = idx
+ self.pc = pc
+ self.function_name = function_name
+ self.variable = variable
+
+ 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):
+ """Not artificial."""
+ return False
+
+ def is_hidden(self):
+ """Not hidden."""
+ return False
+
+ def get_register_context(self):
+ """No register context."""
+ return None
+
+ def get_variables(self):
+ """"""
+ out = lldb.SBValueList()
+ out.Append(self.variable)
+ return out
+
+ def get_value_for_variable_expression(self, expr, options, error: lldb.SBError):
+ out = lldb.SBValue()
+ if expr == "_handler_one":
+ out = self.variable.CreateValueFromExpression("_handler_one", "(uint32_t)1")
+ elif self.variable.name in expr:
+ out = self.variable.CreateValueFromExpression("_expr", expr)
+
+ if out.IsValid():
+ return out
+
+ error.SetErrorString(f"expression {expr} failed")
+ return None
+
+
+class ValueProvidingFrameProvider(ScriptedFrameProvider):
+ """Add a single 'value-provider' frame at the beginning."""
+
+ def __init__(self, input_frames, args):
+ super().__init__(input_frames, args)
+
+ @staticmethod
+ def get_description():
+ """Return a description of this provider."""
+ return "Add 'value-provider' frame at beginning"
+
+ def get_frame_at_index(self, index):
+ if index == 0:
+ f = self.input_frames.GetFrameAtIndex(index)
+ # Find some variable we can give to the frame.
+ variable = f.FindVariable("variable_in_main")
+ # Return synthetic "value-provider" frame
+ return ValueProvidingFrame(
+ self.thread, 0, 0xF00, "value-provider", variable
+ )
+ elif index - 1 < len(self.input_frames):
+ # Pass through input frames (shifted by 1)
+ return index - 1
+ return None
More information about the lldb-commits
mailing list