Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -49,6 +50,14 @@ class ScriptedFrameInterface : virtual public ScriptedInterface {
virtual std::optional<std::string> 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

Expand Down
66 changes: 66 additions & 0 deletions lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand All @@ -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;
Expand Down Expand Up @@ -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<VariableList>();
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);
}
21 changes: 21 additions & 0 deletions lldb/source/Plugins/Process/scripted/ScriptedFrame.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -75,13 +90,19 @@ 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;

std::shared_ptr<DynamicRegisterInfo> GetDynamicRegisterInfo();

lldb::ScriptedFrameInterfaceSP m_scripted_frame_interface_sp;
lldb_private::StructuredData::GenericSP m_script_object_sp;
lldb::VariableListSP m_variable_list_sp;

static char ID;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,32 @@ std::optional<std::string> ScriptedFramePythonInterface::GetRegisterContext() {
return obj->GetAsString()->GetValue().str();
}

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
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ class ScriptedFramePythonInterface : public ScriptedFrameInterface,
StructuredData::DictionarySP GetRegisterInfo() override;

std::optional<std::string> GetRegisterContext() override;

lldb::ValueObjectListSP GetVariables() override;

lldb::ValueObjectSP
GetValueObjectForVariableExpression(llvm::StringRef expr, uint32_t options,
Status &status) override;
};
} // namespace lldb_private

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading