diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h index 8ef4b37d6ba12..00994d65fd601 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,14 @@ class ScriptedFrameInterface : virtual public ScriptedInterface { virtual std::optional GetRegisterContext() { return std::nullopt; } + + virtual lldb::ValueObjectListSP GetVariables() { return nullptr; } + + 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..7462c467eb7da 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; @@ -265,3 +269,65 @@ lldb::RegisterContextSP ScriptedFrame::GetRegisterContext() { return m_reg_context_sp; } + +VariableList *ScriptedFrame::GetVariableList(bool get_file_globals, + Status *error_ptr) { + PopulateVariableListFromInterface(); + return m_variable_list_sp.get(); +} + +lldb::VariableListSP +ScriptedFrame::GetInScopeVariableList(bool get_file_globals, + bool must_have_valid_location) { + PopulateVariableListFromInterface(); + return m_variable_list_sp; +} + +void ScriptedFrame::PopulateVariableListFromInterface() { + // Fetch values from the interface. + ValueObjectListSP value_list_sp = GetInterface()->GetVariables(); + if (!value_list_sp) + return; + + // Convert what we can into a variable. + m_variable_list_sp = std::make_shared(); + 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; + + m_variable_list_sp->AddVariable(var); + } +} + +lldb::ValueObjectSP ScriptedFrame::GetValueObjectForFrameVariable( + const lldb::VariableSP &variable_sp, lldb::DynamicValueType use_dynamic) { + // Fetch values from the interface. + ValueObjectListSP values = m_scripted_frame_interface_sp->GetVariables(); + if (!values) + return {}; + + return values->FindValueObjectByValueName(variable_sp->GetName().AsCString()); +} + +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! + // FIXME: We should make it possible for the frame implementation to create + // Variable objects. + (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..fe154792c745b 100644 --- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.h +++ b/lldb/source/Plugins/Process/scripted/ScriptedFrame.h @@ -63,6 +63,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); } @@ -75,6 +90,11 @@ class ScriptedFrame : public lldb_private::StackFrame { CreateRegisterContext(ScriptedFrameInterface &interface, Thread &thread, lldb::user_id_t frame_id); + // Populate m_variable_list_sp from the scripted frame interface. Right now + // this doesn't take any options because the implementation can't really do + // anything with those options anyway, so there's no point. + void PopulateVariableListFromInterface(); + ScriptedFrame(const ScriptedFrame &) = delete; const ScriptedFrame &operator=(const ScriptedFrame &) = delete; @@ -82,6 +102,7 @@ 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; 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..9cc7b04fc9dba 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.cpp @@ -154,4 +154,32 @@ std::optional ScriptedFramePythonInterface::GetRegisterContext() { return obj->GetAsString()->GetValue().str(); } +lldb::ValueObjectListSP ScriptedFramePythonInterface::GetVariables() { + Status error; + auto vals = Dispatch("get_variables", error); + + if (error.Fail()) { + return ErrorWithMessage(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("get_value_for_variable_expression", + dispatch_error, expr.data(), options, + status); + + if (dispatch_error.Fail()) { + return ErrorWithMessage( + 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..d8ac093106bbd 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 GetRegisterContext() override; + + 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