Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6b5252e

Browse files
bzcheesemanc-rhodes
authored andcommitted
[lldb] Add support for ScriptedFrame to provide values/variables. (llvm#178575)
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. Related radar: rdar://165708771 (cherry picked from commit 10f2611)
1 parent 4baddb7 commit 6b5252e

8 files changed

Lines changed: 269 additions & 0 deletions

File tree

lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameInterface.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#define LLDB_INTERPRETER_INTERFACES_SCRIPTEDFRAMEINTERFACE_H
1111

1212
#include "ScriptedInterface.h"
13+
#include "lldb/API/SBValueList.h"
1314
#include "lldb/Core/StructuredDataImpl.h"
1415
#include "lldb/Symbol/SymbolContext.h"
1516
#include "lldb/lldb-private.h"
@@ -49,6 +50,14 @@ class ScriptedFrameInterface : virtual public ScriptedInterface {
4950
virtual std::optional<std::string> GetRegisterContext() {
5051
return std::nullopt;
5152
}
53+
54+
virtual lldb::ValueObjectListSP GetVariables() { return nullptr; }
55+
56+
virtual lldb::ValueObjectSP
57+
GetValueObjectForVariableExpression(llvm::StringRef expr, uint32_t options,
58+
Status &error) {
59+
return nullptr;
60+
}
5261
};
5362
} // namespace lldb_private
5463

lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "ScriptedFrame.h"
1010
#include "Plugins/Process/Utility/RegisterContextMemory.h"
1111

12+
#include "lldb/API/SBDeclaration.h"
1213
#include "lldb/Core/Address.h"
1314
#include "lldb/Core/Debugger.h"
1415
#include "lldb/Core/Module.h"
@@ -20,6 +21,7 @@
2021
#include "lldb/Symbol/CompileUnit.h"
2122
#include "lldb/Symbol/SymbolContext.h"
2223
#include "lldb/Symbol/SymbolFile.h"
24+
#include "lldb/Symbol/VariableList.h"
2325
#include "lldb/Target/ExecutionContext.h"
2426
#include "lldb/Target/Process.h"
2527
#include "lldb/Target/RegisterContext.h"
@@ -28,6 +30,8 @@
2830
#include "lldb/Utility/LLDBLog.h"
2931
#include "lldb/Utility/Log.h"
3032
#include "lldb/Utility/StructuredData.h"
33+
#include "lldb/ValueObject/ValueObject.h"
34+
#include "lldb/ValueObject/ValueObjectList.h"
3135

3236
using namespace lldb;
3337
using namespace lldb_private;
@@ -265,3 +269,65 @@ lldb::RegisterContextSP ScriptedFrame::GetRegisterContext() {
265269

266270
return m_reg_context_sp;
267271
}
272+
273+
VariableList *ScriptedFrame::GetVariableList(bool get_file_globals,
274+
Status *error_ptr) {
275+
PopulateVariableListFromInterface();
276+
return m_variable_list_sp.get();
277+
}
278+
279+
lldb::VariableListSP
280+
ScriptedFrame::GetInScopeVariableList(bool get_file_globals,
281+
bool must_have_valid_location) {
282+
PopulateVariableListFromInterface();
283+
return m_variable_list_sp;
284+
}
285+
286+
void ScriptedFrame::PopulateVariableListFromInterface() {
287+
// Fetch values from the interface.
288+
ValueObjectListSP value_list_sp = GetInterface()->GetVariables();
289+
if (!value_list_sp)
290+
return;
291+
292+
// Convert what we can into a variable.
293+
m_variable_list_sp = std::make_shared<VariableList>();
294+
for (uint32_t i = 0, e = value_list_sp->GetSize(); i < e; ++i) {
295+
ValueObjectSP v = value_list_sp->GetValueObjectAtIndex(i);
296+
if (!v)
297+
continue;
298+
299+
VariableSP var = v->GetVariable();
300+
// TODO: We could in theory ask the scripted frame to *produce* a
301+
// variable for this value object.
302+
if (!var)
303+
continue;
304+
305+
m_variable_list_sp->AddVariable(var);
306+
}
307+
}
308+
309+
lldb::ValueObjectSP ScriptedFrame::GetValueObjectForFrameVariable(
310+
const lldb::VariableSP &variable_sp, lldb::DynamicValueType use_dynamic) {
311+
// Fetch values from the interface.
312+
ValueObjectListSP values = m_scripted_frame_interface_sp->GetVariables();
313+
if (!values)
314+
return {};
315+
316+
return values->FindValueObjectByValueName(variable_sp->GetName().AsCString());
317+
}
318+
319+
lldb::ValueObjectSP ScriptedFrame::GetValueForVariableExpressionPath(
320+
llvm::StringRef var_expr, lldb::DynamicValueType use_dynamic,
321+
uint32_t options, lldb::VariableSP &var_sp, Status &error) {
322+
// Unless the frame implementation knows how to create variables (which it
323+
// doesn't), we can't construct anything for the variable. This may seem
324+
// somewhat out of place, but it's basically because of how this API is used -
325+
// the print command uses this API to fill in var_sp; and this implementation
326+
// can't do that!
327+
// FIXME: We should make it possible for the frame implementation to create
328+
// Variable objects.
329+
(void)var_sp;
330+
// Otherwise, delegate to the scripted frame interface pointer.
331+
return m_scripted_frame_interface_sp->GetValueObjectForVariableExpression(
332+
var_expr, options, error);
333+
}

lldb/source/Plugins/Process/scripted/ScriptedFrame.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,21 @@ class ScriptedFrame : public lldb_private::StackFrame {
6363

6464
lldb::RegisterContextSP GetRegisterContext() override;
6565

66+
VariableList *GetVariableList(bool get_file_globals,
67+
lldb_private::Status *error_ptr) override;
68+
69+
lldb::VariableListSP
70+
GetInScopeVariableList(bool get_file_globals,
71+
bool must_have_valid_location = false) override;
72+
73+
lldb::ValueObjectSP
74+
GetValueObjectForFrameVariable(const lldb::VariableSP &variable_sp,
75+
lldb::DynamicValueType use_dynamic) override;
76+
77+
lldb::ValueObjectSP GetValueForVariableExpressionPath(
78+
llvm::StringRef var_expr, lldb::DynamicValueType use_dynamic,
79+
uint32_t options, lldb::VariableSP &var_sp, Status &error) override;
80+
6681
bool isA(const void *ClassID) const override {
6782
return ClassID == &ID || StackFrame::isA(ClassID);
6883
}
@@ -75,13 +90,19 @@ class ScriptedFrame : public lldb_private::StackFrame {
7590
CreateRegisterContext(ScriptedFrameInterface &interface, Thread &thread,
7691
lldb::user_id_t frame_id);
7792

93+
// Populate m_variable_list_sp from the scripted frame interface. Right now
94+
// this doesn't take any options because the implementation can't really do
95+
// anything with those options anyway, so there's no point.
96+
void PopulateVariableListFromInterface();
97+
7898
ScriptedFrame(const ScriptedFrame &) = delete;
7999
const ScriptedFrame &operator=(const ScriptedFrame &) = delete;
80100

81101
std::shared_ptr<DynamicRegisterInfo> GetDynamicRegisterInfo();
82102

83103
lldb::ScriptedFrameInterfaceSP m_scripted_frame_interface_sp;
84104
lldb_private::StructuredData::GenericSP m_script_object_sp;
105+
lldb::VariableListSP m_variable_list_sp;
85106

86107
static char ID;
87108
};

lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,32 @@ std::optional<std::string> ScriptedFramePythonInterface::GetRegisterContext() {
154154
return obj->GetAsString()->GetValue().str();
155155
}
156156

157+
lldb::ValueObjectListSP ScriptedFramePythonInterface::GetVariables() {
158+
Status error;
159+
auto vals = Dispatch<lldb::ValueObjectListSP>("get_variables", error);
160+
161+
if (error.Fail()) {
162+
return ErrorWithMessage<lldb::ValueObjectListSP>(LLVM_PRETTY_FUNCTION,
163+
error.AsCString(), error);
164+
}
165+
166+
return vals;
167+
}
168+
169+
lldb::ValueObjectSP
170+
ScriptedFramePythonInterface::GetValueObjectForVariableExpression(
171+
llvm::StringRef expr, uint32_t options, Status &status) {
172+
Status dispatch_error;
173+
auto val = Dispatch<lldb::ValueObjectSP>("get_value_for_variable_expression",
174+
dispatch_error, expr.data(), options,
175+
status);
176+
177+
if (dispatch_error.Fail()) {
178+
return ErrorWithMessage<lldb::ValueObjectSP>(
179+
LLVM_PRETTY_FUNCTION, dispatch_error.AsCString(), dispatch_error);
180+
}
181+
182+
return val;
183+
}
184+
157185
#endif

lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFramePythonInterface.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ class ScriptedFramePythonInterface : public ScriptedFrameInterface,
5252
StructuredData::DictionarySP GetRegisterInfo() override;
5353

5454
std::optional<std::string> GetRegisterContext() override;
55+
56+
lldb::ValueObjectListSP GetVariables() override;
57+
58+
lldb::ValueObjectSP
59+
GetValueObjectForVariableExpression(llvm::StringRef expr, uint32_t options,
60+
Status &status) override;
5561
};
5662
} // namespace lldb_private
5763

lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,3 +730,56 @@ def test_chained_frame_providers(self):
730730
frame3 = thread.GetFrameAtIndex(3)
731731
self.assertIsNotNone(frame3)
732732
self.assertIn("thread_func", frame3.GetFunctionName())
733+
734+
def test_get_values(self):
735+
"""Test a frame that provides values."""
736+
self.build()
737+
# Set the breakpoint after the variable_in_main variable exists and can be queried.
738+
target, process, thread, bkpt = lldbutil.run_to_line_breakpoint(
739+
self, lldb.SBFileSpec(self.source), 35, only_one_thread=False
740+
)
741+
742+
# Get original frame count.
743+
original_frame_count = thread.GetNumFrames()
744+
self.assertGreaterEqual(
745+
original_frame_count, 2, "Should have at least 2 real frames"
746+
)
747+
748+
# Import the test frame providers.
749+
script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py")
750+
self.runCmd("command script import " + script_path)
751+
752+
# Register a provider that can provide variables.
753+
error = lldb.SBError()
754+
target.RegisterScriptedFrameProvider(
755+
"test_frame_providers.ValueProvidingFrameProvider",
756+
lldb.SBStructuredData(),
757+
error,
758+
)
759+
self.assertTrue(error.Success(), f"Failed to register provider: {error}")
760+
761+
# Verify we have 1 more frame.
762+
new_frame_count = thread.GetNumFrames()
763+
self.assertEqual(
764+
new_frame_count,
765+
original_frame_count + 1,
766+
"Should have original frames + 1 extra frames",
767+
)
768+
769+
# Check that we can get variables from this frame.
770+
frame0 = thread.GetFrameAtIndex(0)
771+
self.assertIsNotNone(frame0)
772+
# Get every variable visible at this point
773+
variables = frame0.GetVariables(True, True, True, False)
774+
self.assertTrue(variables.IsValid() and variables.GetSize() == 1)
775+
776+
# Check that we can get values from paths. `_handler_one` is a special
777+
# value we provide through only our expression handler in the frame
778+
# implementation.
779+
one = frame0.GetValueForVariablePath("_handler_one")
780+
self.assertEqual(one.unsigned, 1)
781+
var = frame0.GetValueForVariablePath("variable_in_main")
782+
# The names won't necessarily match, but the values should (the frame renames the SBValue)
783+
self.assertEqual(var.unsigned, variables.GetValueAtIndex(0).unsigned)
784+
varp1 = frame0.GetValueForVariablePath("variable_in_main + 1")
785+
self.assertEqual(varp1.unsigned, 124)

lldb/test/API/functionalities/scripted_frame_provider/main.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ void thread_func(int thread_num) {
2929
int main(int argc, char **argv) {
3030
std::thread threads[NUM_THREADS];
3131

32+
// Used as an existing C++ variable we can anchor on.
33+
int variable_in_main = 123;
34+
(void)variable_in_main;
35+
3236
for (int i = 0; i < NUM_THREADS; i++) {
3337
threads[i] = std::thread(thread_func, i);
3438
}

lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,3 +458,85 @@ def get_frame_at_index(self, index):
458458
# Pass through input frames (shifted by 1)
459459
return index - 1
460460
return None
461+
462+
463+
class ValueProvidingFrame(ScriptedFrame):
464+
"""Scripted frame with a valid PC but no associated module."""
465+
466+
def __init__(self, thread, idx, pc, function_name, variable):
467+
args = lldb.SBStructuredData()
468+
super().__init__(thread, args)
469+
470+
self.idx = idx
471+
self.pc = pc
472+
self.function_name = function_name
473+
self.variable = variable
474+
475+
def get_id(self):
476+
"""Return the frame index."""
477+
return self.idx
478+
479+
def get_pc(self):
480+
"""Return the program counter."""
481+
return self.pc
482+
483+
def get_function_name(self):
484+
"""Return the function name."""
485+
return self.function_name
486+
487+
def is_artificial(self):
488+
"""Not artificial."""
489+
return False
490+
491+
def is_hidden(self):
492+
"""Not hidden."""
493+
return False
494+
495+
def get_register_context(self):
496+
"""No register context."""
497+
return None
498+
499+
def get_variables(self):
500+
""""""
501+
out = lldb.SBValueList()
502+
out.Append(self.variable)
503+
return out
504+
505+
def get_value_for_variable_expression(self, expr, options, error: lldb.SBError):
506+
out = lldb.SBValue()
507+
if expr == "_handler_one":
508+
out = self.variable.CreateValueFromExpression("_handler_one", "(uint32_t)1")
509+
elif self.variable.name in expr:
510+
out = self.variable.CreateValueFromExpression("_expr", expr)
511+
512+
if out.IsValid():
513+
return out
514+
515+
error.SetErrorString(f"expression {expr} failed")
516+
return None
517+
518+
519+
class ValueProvidingFrameProvider(ScriptedFrameProvider):
520+
"""Add a single 'value-provider' frame at the beginning."""
521+
522+
def __init__(self, input_frames, args):
523+
super().__init__(input_frames, args)
524+
525+
@staticmethod
526+
def get_description():
527+
"""Return a description of this provider."""
528+
return "Add 'value-provider' frame at beginning"
529+
530+
def get_frame_at_index(self, index):
531+
if index == 0:
532+
f = self.input_frames.GetFrameAtIndex(index)
533+
# Find some variable we can give to the frame.
534+
variable = f.FindVariable("variable_in_main")
535+
# Return synthetic "value-provider" frame
536+
return ValueProvidingFrame(
537+
self.thread, 0, 0xF00, "value-provider", variable
538+
)
539+
elif index - 1 < len(self.input_frames):
540+
# Pass through input frames (shifted by 1)
541+
return index - 1
542+
return None

0 commit comments

Comments
 (0)