[lldb/script] Migrate frame recognizers onto ScriptedPythonInterface (#209805)

Give `frame recognizer add -l` a formal
ScriptedStackFrameRecognizerInterface, matching the architecture already
used by ScriptedProcess, ScriptedBreakpoint, etc.: a C++ interface
header, a Python-backed implementation built on
ScriptedPythonInterface's CreatePluginObject/Dispatch machinery,
PluginManager registration, and a generatable Python ABC template
(scripted_stackframe_recognizer.py).

get_recognized_arguments' documented contract returns a plain list of
lldb.SBValue (not an lldb.SBValueList), so it bypasses the generic
Dispatch<ValueObjectListSP>() extractor and calls the SWIG bridge
directly, matching the legacy behavior exactly.

ScriptedStackFrameRecognizer now holds a single interface object created
once in its constructor and reused across every RecognizeFrame() call,
same lifecycle as before.

Signed-off-by: Med Ismail Bennani <ismail@bennani.ma>
(cherry picked from commit a07b91d8243b8a8051dbbe5739eacc7942269c61)
This commit is contained in:
Med Ismail Bennani authored and Tobias Hieta committed 2026-07-23 08:09:06 +02:00
1 parent 694db4d296
commit e28ea20ce0
23 files changed
+485 -212

No files matched your search

+1
View File
@@ -119,6 +119,7 @@ function(finish_swig_python swig_target lldb_python_bindings_dir lldb_python_tar
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_thread_plan.py"
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_breakpoint.py"
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_hook.py"
"${LLDB_SOURCE_DIR}/examples/python/templates/scripted_stackframe_recognizer.py"
)
if(APPLE)
-47
View File
@@ -798,53 +798,6 @@ PythonObject lldb_private::python::SWIGBridge::LLDBSWIGPythonCreateOSPlugin(
return pfunc(SWIGBridge::ToSWIGWrapper(process_sp));
}
PythonObject lldb_private::python::SWIGBridge::LLDBSWIGPython_CreateFrameRecognizer(
const char *python_class_name, const char *session_dictionary_name) {
if (python_class_name == NULL || python_class_name[0] == '\0' ||
!session_dictionary_name)
return PythonObject();
PyErr_Cleaner py_err_cleaner(true);
auto dict = PythonModule::MainModule().ResolveName<PythonDictionary>(
session_dictionary_name);
auto pfunc = PythonObject::ResolveNameWithDictionary<PythonCallable>(
python_class_name, dict);
if (!pfunc.IsAllocated())
return PythonObject();
return pfunc();
}
PyObject *lldb_private::python::SWIGBridge::LLDBSwigPython_GetRecognizedArguments(
PyObject *implementor, const lldb::StackFrameSP &frame_sp) {
static char callee_name[] = "get_recognized_arguments";
PythonObject arg = SWIGBridge::ToSWIGWrapper(frame_sp);
PythonString str(callee_name);
PyObject *result =
PyObject_CallMethodObjArgs(implementor, str.get(), arg.get(), NULL);
return result;
}
bool lldb_private::python::SWIGBridge::LLDBSwigPython_ShouldHide(
PyObject *implementor, const lldb::StackFrameSP &frame_sp) {
static char callee_name[] = "should_hide";
PythonObject arg = SWIGBridge::ToSWIGWrapper(frame_sp);
PythonString str(callee_name);
PyObject *result =
PyObject_CallMethodObjArgs(implementor, str.get(), arg.get(), NULL);
bool ret_val = result ? PyObject_IsTrue(result) : false;
Py_XDECREF(result);
return ret_val;
}
void *lldb_private::python::SWIGBridge::LLDBSWIGPython_GetDynamicSetting(
void *module, const char *setting, const lldb::TargetSP &target_sp) {
if (!module || !setting)
+1
View File
@@ -32,6 +32,7 @@ if (LLDB_ENABLE_PYTHON AND SPHINX_FOUND)
COMMAND "${CMAKE_COMMAND}" -E copy "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_thread_plan.py" "${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/"
COMMAND "${CMAKE_COMMAND}" -E copy "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_breakpoint.py" "${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/"
COMMAND "${CMAKE_COMMAND}" -E copy "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_hook.py" "${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/"
COMMAND "${CMAKE_COMMAND}" -E copy "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_stackframe_recognizer.py" "${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/"
COMMENT "Copying lldb.py to pretend its a Python package.")
add_dependencies(lldb-python-doc-package swig_wrapper_python)
@@ -0,0 +1,102 @@
from abc import ABCMeta
from typing import Optional
import lldb
class ScriptedStackFrameRecognizer(metaclass=ABCMeta):
"""
The base class for a scripted stack frame recognizer.
A frame recognizer inspects a stack frame at stop time and can:
- attach recognized arguments to it (surfaced the same way as args
for unrecognized frames);
- hide the frame from backtraces;
- override the stop description reported for it;
- expose an exception object associated with it (e.g. a runtime's
thrown value);
- redirect auto-selection to a more relevant frame (e.g. bounce past
a language-runtime trampoline).
Register the recognizer with `frame recognizer add -l <ClassName> ...`.
Every method is optional: a recognizer only implements the ones it
cares about. A recognizer that just hides frames can implement only
`should_hide`; one that surfaces exception info can implement only
`get_exception` and `get_stop_description`.
"""
def __init__(self):
"""Construct a scripted stack frame recognizer.
Recognizers are constructed with no arguments and are shared across
every frame they're asked to recognize.
"""
pass
def get_recognized_arguments(self, frame: lldb.SBFrame) -> list:
"""Get the arguments recognized for this frame.
Args:
frame (lldb.SBFrame): The frame to inspect.
Returns:
list of lldb.SBValue: The recognized arguments, or an empty list
if none could be recognized.
"""
return []
def should_hide(self, frame: lldb.SBFrame) -> bool:
"""Whether this frame should be hidden when displaying backtraces.
Args:
frame (lldb.SBFrame): The frame to inspect.
Returns:
bool: `True` if this frame should be hidden, `False` otherwise.
Defaults to `False`.
"""
return False
def select_most_relevant_frame(self, frame: lldb.SBFrame) -> Optional[lldb.SBFrame]:
"""Pick a different frame to auto-select when the process stops in
this recognized frame. Useful when the recognized frame is trampoline
code that the user probably didn't want to land in (e.g. a language
runtime's exception-throw path).
Args:
frame (lldb.SBFrame): The recognized frame.
Returns:
lldb.SBFrame: The frame to select instead, or `None` to keep
the default selection.
"""
return None
def get_exception(self, frame: lldb.SBFrame) -> Optional[lldb.SBValue]:
"""Get the exception value associated with this recognized frame,
if any (e.g. the exception object at a language runtime's throw
site).
Args:
frame (lldb.SBFrame): The recognized frame.
Returns:
lldb.SBValue: The exception value, or `None` if this frame
doesn't correspond to an exception.
"""
return None
def get_stop_description(self, frame: lldb.SBFrame) -> str:
"""Get the stop description to surface for this recognized frame,
replacing whatever description lldb would have printed.
Args:
frame (lldb.SBFrame): The recognized frame.
Returns:
str: The stop description, or the empty string to keep the
default.
"""
return ""
@@ -0,0 +1,43 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLDB_INTERPRETER_INTERFACES_SCRIPTEDSTACKFRAMERECOGNIZERINTERFACE_H
#define LLDB_INTERPRETER_INTERFACES_SCRIPTEDSTACKFRAMERECOGNIZERINTERFACE_H
#include "ScriptedInterface.h"
#include "lldb/lldb-private.h"
namespace lldb_private {
class ScriptedStackFrameRecognizerInterface : virtual public ScriptedInterface {
public:
virtual llvm::Expected<StructuredData::GenericSP>
CreatePluginObject(const ScriptedMetadata &scripted_metadata) = 0;
virtual lldb::ValueObjectListSP
GetRecognizedArguments(lldb::StackFrameSP frame_sp) {
return lldb::ValueObjectListSP();
}
virtual bool ShouldHide(lldb::StackFrameSP frame_sp) { return false; }
virtual lldb::StackFrameSP
SelectMostRelevantFrame(lldb::StackFrameSP frame_sp) {
return nullptr;
}
virtual lldb::ValueObjectSP GetException(lldb::StackFrameSP frame_sp) {
return lldb::ValueObjectSP();
}
virtual std::string GetStopDescription(lldb::StackFrameSP frame_sp) {
return "";
}
};
} // namespace lldb_private
#endif // LLDB_INTERPRETER_INTERFACES_SCRIPTEDSTACKFRAMERECOGNIZERINTERFACE_H
@@ -248,22 +248,6 @@ public:
return StructuredData::GenericSP();
}
virtual StructuredData::GenericSP
CreateFrameRecognizer(const char *class_name) {
return StructuredData::GenericSP();
}
virtual lldb::ValueObjectListSP GetRecognizedArguments(
const StructuredData::ObjectSP &implementor,
lldb::StackFrameSP frame_sp) {
return lldb::ValueObjectListSP();
}
virtual bool ShouldHide(const StructuredData::ObjectSP &implementor,
lldb::StackFrameSP frame_sp) {
return false;
}
virtual StructuredData::ObjectSP
LoadPluginModule(const FileSpec &file_spec, lldb_private::Status &error) {
return StructuredData::ObjectSP();
@@ -572,6 +556,11 @@ public:
return {};
}
virtual lldb::ScriptedStackFrameRecognizerInterfaceSP
CreateScriptedStackFrameRecognizerInterface() {
return {};
}
virtual StructuredData::ObjectSP
CreateStructuredDataFromScriptObject(ScriptObject obj) {
return {};
@@ -77,8 +77,7 @@ public:
/// tracks a particular Python classobject, which will be asked to recognize
/// stack frames.
class ScriptedStackFrameRecognizer : public StackFrameRecognizer {
lldb_private::ScriptInterpreter *m_interpreter;
lldb_private::StructuredData::ObjectSP m_python_object_sp;
lldb::ScriptedStackFrameRecognizerInterfaceSP m_interface_sp;
std::string m_python_class;
+2 -1
View File
@@ -267,7 +267,8 @@ enum ScriptedExtension {
eScriptedExtensionScriptedHook,
eScriptedExtensionScriptedThread,
eScriptedExtensionScriptedFrame,
kLastScriptedExtension = eScriptedExtensionScriptedFrame
eScriptedExtensionScriptedStackFrameRecognizer,
kLastScriptedExtension = eScriptedExtensionScriptedStackFrameRecognizer
};
/// Register numbering types.
+3
View File
@@ -198,6 +198,7 @@ class ScriptedPlatformInterface;
class ScriptedProcessInterface;
class ScriptedThreadInterface;
class ScriptedThreadPlanInterface;
class ScriptedStackFrameRecognizerInterface;
class ScriptedSyntheticChildren;
class SearchFilter;
class Section;
@@ -435,6 +436,8 @@ typedef std::shared_ptr<lldb_private::ScriptedThreadPlanInterface>
ScriptedThreadPlanInterfaceSP;
typedef std::shared_ptr<lldb_private::ScriptedBreakpointInterface>
ScriptedBreakpointInterfaceSP;
typedef std::shared_ptr<lldb_private::ScriptedStackFrameRecognizerInterface>
ScriptedStackFrameRecognizerInterfaceSP;
typedef std::shared_ptr<lldb_private::Section> SectionSP;
typedef std::unique_ptr<lldb_private::SectionList> SectionListUP;
typedef std::weak_ptr<lldb_private::Section> SectionWP;
@@ -212,6 +212,8 @@ ScriptInterpreter::ExtensionToString(lldb::ScriptedExtension extension) {
return "ScriptedThread";
case eScriptedExtensionScriptedFrame:
return "ScriptedFrame";
case eScriptedExtensionScriptedStackFrameRecognizer:
return "ScriptedStackFrameRecognizer";
}
llvm_unreachable("unhandled ScriptedExtension");
}
@@ -230,6 +232,8 @@ ScriptInterpreter::StringToExtension(llvm::StringRef string) {
.CaseLower("ScriptedHook", eScriptedExtensionScriptedHook)
.CaseLower("ScriptedThread", eScriptedExtensionScriptedThread)
.CaseLower("ScriptedFrame", eScriptedExtensionScriptedFrame)
.CaseLower("ScriptedStackFrameRecognizer",
eScriptedExtensionScriptedStackFrameRecognizer)
.Default(eScriptedExtensionInvalid);
}
@@ -32,6 +32,7 @@ set(python_plugin_sources
Interfaces/ScriptedPythonInterface.cpp
Interfaces/ScriptedHookPythonInterface.cpp
Interfaces/ScriptedBreakpointPythonInterface.cpp
Interfaces/ScriptedStackFrameRecognizerPythonInterface.cpp
Interfaces/ScriptedThreadPlanPythonInterface.cpp
Interfaces/ScriptedThreadPythonInterface.cpp
)
@@ -29,6 +29,7 @@ void ScriptInterpreterPythonInterfaces::Initialize() {
ScriptedFrameProviderPythonInterface::Initialize();
ScriptedThreadPythonInterface::Initialize();
ScriptedFramePythonInterface::Initialize();
ScriptedStackFrameRecognizerPythonInterface::Initialize();
}
void ScriptInterpreterPythonInterfaces::Terminate() {
@@ -41,4 +42,5 @@ void ScriptInterpreterPythonInterfaces::Terminate() {
ScriptedFrameProviderPythonInterface::Terminate();
ScriptedThreadPythonInterface::Terminate();
ScriptedFramePythonInterface::Terminate();
ScriptedStackFrameRecognizerPythonInterface::Terminate();
}
@@ -19,6 +19,7 @@
#include "ScriptedHookPythonInterface.h"
#include "ScriptedPlatformPythonInterface.h"
#include "ScriptedProcessPythonInterface.h"
#include "ScriptedStackFrameRecognizerPythonInterface.h"
#include "ScriptedThreadPlanPythonInterface.h"
#include "ScriptedThreadPythonInterface.h"
@@ -305,22 +305,57 @@ 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 {};
}
// Two Python return shapes are accepted here so callers can go through
// Dispatch<ValueObjectListSP>() uniformly: an `SBValueList` wrapper
// (what most extension methods return) and a plain Python `list` of
// `SBValue` (what `get_recognized_arguments` is documented to 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));
if (auto *sb_value_list = reinterpret_cast<lldb::SBValueList *>(
python::LLDBSWIGPython_CastPyObjectToSBValueList(p.get()))) {
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;
}
return out;
// Fallback: a plain Python `list` of `SBValue`. Round-trip through
// `CreateStructuredObject` so we don't touch the `PyList_*` C API
// directly; unknown-shape items surface as `StructuredData::Generic`
// holding their opaque `PyObject*`, which we hand back to SWIG to
// recover the SBValue wrapper.
StructuredData::ObjectSP structured = p.CreateStructuredObject();
StructuredData::Array *arr = structured ? structured->GetAsArray() : nullptr;
if (arr) {
size_t index = 0;
bool aborted = false;
arr->ForEach([&](StructuredData::Object *item) {
StructuredData::Generic *generic = item ? item->GetAsGeneric() : nullptr;
if (!generic) {
error = Status::FromErrorStringWithFormatv(
"ValueObjectList item at index {0} is not a "
"StructuredData::Generic",
index);
aborted = true;
return false;
}
auto *sb_value = reinterpret_cast<lldb::SBValue *>(
python::LLDBSWIGPython_CastPyObjectToSBValue(
static_cast<PyObject *>(generic->GetValue())));
if (sb_value)
if (auto valobj_sp = m_interpreter.GetOpaqueTypeFromSBValue(*sb_value))
out->Append(valobj_sp);
++index;
return true;
});
if (aborted)
return {};
return out;
}
error = Status::FromErrorStringWithFormat(
"couldn't extract ValueObjectList from Python return value");
return {};
}
template <>
@@ -0,0 +1,97 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "../lldb-python.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Target/StackFrame.h"
#include "lldb/lldb-enumerations.h"
#include "../SWIGPythonBridge.h"
#include "../ScriptInterpreterPythonImpl.h"
#include "ScriptedStackFrameRecognizerPythonInterface.h"
using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::python;
ScriptedStackFrameRecognizerPythonInterface::
ScriptedStackFrameRecognizerPythonInterface(
ScriptInterpreterPythonImpl &interpreter)
: ScriptedStackFrameRecognizerInterface(),
ScriptedPythonInterface(interpreter) {}
llvm::Expected<StructuredData::GenericSP>
ScriptedStackFrameRecognizerPythonInterface::CreatePluginObject(
const ScriptedMetadata &scripted_metadata) {
return ScriptedPythonInterface::CreatePluginObject(scripted_metadata,
nullptr);
}
lldb::ValueObjectListSP
ScriptedStackFrameRecognizerPythonInterface::GetRecognizedArguments(
lldb::StackFrameSP frame_sp) {
Status error;
return Dispatch<lldb::ValueObjectListSP>("get_recognized_arguments", error,
frame_sp);
}
bool ScriptedStackFrameRecognizerPythonInterface::ShouldHide(
lldb::StackFrameSP frame_sp) {
Status error;
StructuredData::ObjectSP obj = Dispatch("should_hide", error, frame_sp);
if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
error))
return false;
return obj->GetBooleanValue();
}
lldb::StackFrameSP
ScriptedStackFrameRecognizerPythonInterface::SelectMostRelevantFrame(
lldb::StackFrameSP frame_sp) {
Status error;
return Dispatch<lldb::StackFrameSP>("select_most_relevant_frame", error,
frame_sp);
}
lldb::ValueObjectSP ScriptedStackFrameRecognizerPythonInterface::GetException(
lldb::StackFrameSP frame_sp) {
Status error;
return Dispatch<lldb::ValueObjectSP>("get_exception", error, frame_sp);
}
std::string ScriptedStackFrameRecognizerPythonInterface::GetStopDescription(
lldb::StackFrameSP frame_sp) {
Status error;
StructuredData::ObjectSP obj =
Dispatch("get_stop_description", error, frame_sp);
if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj,
error))
return "";
return obj->GetStringValue().str();
}
void ScriptedStackFrameRecognizerPythonInterface::Initialize() {
const std::vector<llvm::StringRef> ci_usages = {
"frame recognizer add -l <script-name> [-s <shlib> ...] "
"[-n <symbol> ... | -x <symbol-regex>] [-f false] "
"[--mangled-name-preference <mode>]"};
PluginManager::RegisterPlugin(
GetPluginNameStatic(),
"Recognize a stack frame and provide extra information about it "
"(recognized arguments, exception object, stop description, "
"hidden/most-relevant frame).",
CreateInstance, eScriptedExtensionScriptedStackFrameRecognizer,
eScriptLanguagePython, {ci_usages, {}});
}
void ScriptedStackFrameRecognizerPythonInterface::Terminate() {
PluginManager::UnregisterPlugin(CreateInstance);
}
@@ -0,0 +1,57 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDSTACKFRAMERECOGNIZERPYTHONINTERFACE_H
#define LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDSTACKFRAMERECOGNIZERPYTHONINTERFACE_H
#include "lldb/Interpreter/Interfaces/ScriptedStackFrameRecognizerInterface.h"
#include "ScriptedPythonInterface.h"
namespace lldb_private {
class ScriptedStackFrameRecognizerPythonInterface
: public ScriptedStackFrameRecognizerInterface,
public ScriptedPythonInterface,
public PluginInterface {
public:
ScriptedStackFrameRecognizerPythonInterface(
ScriptInterpreterPythonImpl &interpreter);
llvm::Expected<StructuredData::GenericSP>
CreatePluginObject(const ScriptedMetadata &scripted_metadata) override;
llvm::SmallVector<AbstractMethodRequirement>
GetAbstractMethodRequirements() const override {
return {};
}
lldb::ValueObjectListSP
GetRecognizedArguments(lldb::StackFrameSP frame_sp) override;
bool ShouldHide(lldb::StackFrameSP frame_sp) override;
lldb::StackFrameSP
SelectMostRelevantFrame(lldb::StackFrameSP frame_sp) override;
lldb::ValueObjectSP GetException(lldb::StackFrameSP frame_sp) override;
std::string GetStopDescription(lldb::StackFrameSP frame_sp) override;
static void Initialize();
static void Terminate();
static llvm::StringRef GetPluginNameStatic() {
return "ScriptedStackFrameRecognizerPythonInterface";
}
llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
};
} // namespace lldb_private
#endif // LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDSTACKFRAMERECOGNIZERPYTHONINTERFACE_H
@@ -214,17 +214,6 @@ public:
const char *session_dictionary_name,
const lldb::ProcessSP &process_sp);
static python::PythonObject
LLDBSWIGPython_CreateFrameRecognizer(const char *python_class_name,
const char *session_dictionary_name);
static PyObject *
LLDBSwigPython_GetRecognizedArguments(PyObject *implementor,
const lldb::StackFrameSP &frame_sp);
static bool LLDBSwigPython_ShouldHide(PyObject *implementor,
const lldb::StackFrameSP &frame_sp);
static bool LLDBSWIGPythonRunScriptKeywordProcess(
const char *python_function_name, const char *session_dictionary_name,
const lldb::ProcessSP &process, std::string &output);
@@ -1624,92 +1624,6 @@ bool ScriptInterpreterPythonImpl::GenerateTypeSynthClass(
return true;
}
StructuredData::GenericSP
ScriptInterpreterPythonImpl::CreateFrameRecognizer(const char *class_name) {
if (class_name == nullptr || class_name[0] == '\0')
return StructuredData::GenericSP();
Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, Locker::FreeLock);
PythonObject ret_val = SWIGBridge::LLDBSWIGPython_CreateFrameRecognizer(
class_name, m_dictionary_name.c_str());
return StructuredData::GenericSP(
new StructuredPythonObject(std::move(ret_val)));
}
lldb::ValueObjectListSP ScriptInterpreterPythonImpl::GetRecognizedArguments(
const StructuredData::ObjectSP &os_plugin_object_sp,
lldb::StackFrameSP frame_sp) {
Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, Locker::FreeLock);
if (!os_plugin_object_sp)
return ValueObjectListSP();
StructuredData::Generic *generic = os_plugin_object_sp->GetAsGeneric();
if (!generic)
return nullptr;
PythonObject implementor(PyRefType::Borrowed,
(PyObject *)generic->GetValue());
if (!implementor.IsAllocated())
return ValueObjectListSP();
PythonObject py_return(PyRefType::Owned,
SWIGBridge::LLDBSwigPython_GetRecognizedArguments(
implementor.get(), frame_sp));
// if it fails, print the error but otherwise go on
if (PyErr_Occurred()) {
PyErr_Print();
PyErr_Clear();
}
if (py_return.get()) {
PythonList result_list(PyRefType::Borrowed, py_return.get());
ValueObjectListSP result = std::make_shared<ValueObjectList>();
for (size_t i = 0; i < result_list.GetSize(); i++) {
PyObject *item = result_list.GetItemAtIndex(i).get();
lldb::SBValue *sb_value_ptr =
(lldb::SBValue *)LLDBSWIGPython_CastPyObjectToSBValue(item);
auto valobj_sp =
SWIGBridge::LLDBSWIGPython_GetValueObjectSPFromSBValue(sb_value_ptr);
if (valobj_sp)
result->Append(valobj_sp);
}
return result;
}
return ValueObjectListSP();
}
bool ScriptInterpreterPythonImpl::ShouldHide(
const StructuredData::ObjectSP &os_plugin_object_sp,
lldb::StackFrameSP frame_sp) {
Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, Locker::FreeLock);
if (!os_plugin_object_sp)
return false;
StructuredData::Generic *generic = os_plugin_object_sp->GetAsGeneric();
if (!generic)
return false;
PythonObject implementor(PyRefType::Borrowed,
(PyObject *)generic->GetValue());
if (!implementor.IsAllocated())
return false;
bool result =
SWIGBridge::LLDBSwigPython_ShouldHide(implementor.get(), frame_sp);
// if it fails, print the error but otherwise go on
if (PyErr_Occurred()) {
PyErr_Print();
PyErr_Clear();
}
return result;
}
ScriptedProcessInterfaceUP
ScriptInterpreterPythonImpl::CreateScriptedProcessInterface() {
return std::make_unique<ScriptedProcessPythonInterface>(*this);
@@ -1725,6 +1639,11 @@ ScriptInterpreterPythonImpl::CreateScriptedBreakpointInterface() {
return std::make_shared<ScriptedBreakpointPythonInterface>(*this);
}
ScriptedStackFrameRecognizerInterfaceSP
ScriptInterpreterPythonImpl::CreateScriptedStackFrameRecognizerInterface() {
return std::make_shared<ScriptedStackFrameRecognizerPythonInterface>(*this);
}
ScriptedThreadInterfaceSP
ScriptInterpreterPythonImpl::CreateScriptedThreadInterface() {
return std::make_shared<ScriptedThreadPythonInterface>(*this);
@@ -76,16 +76,6 @@ public:
StructuredData::ObjectSP
CreateStructuredDataFromScriptObject(ScriptObject obj) override;
StructuredData::GenericSP
CreateFrameRecognizer(const char *class_name) override;
lldb::ValueObjectListSP
GetRecognizedArguments(const StructuredData::ObjectSP &implementor,
lldb::StackFrameSP frame_sp) override;
bool ShouldHide(const StructuredData::ObjectSP &implementor,
lldb::StackFrameSP frame_sp) override;
lldb::ScriptedProcessInterfaceUP CreateScriptedProcessInterface() override;
lldb::ScriptedHookInterfaceSP CreateScriptedHookInterface() override;
@@ -93,6 +83,9 @@ public:
lldb::ScriptedBreakpointInterfaceSP
CreateScriptedBreakpointInterface() override;
lldb::ScriptedStackFrameRecognizerInterfaceSP
CreateScriptedStackFrameRecognizerInterface() override;
lldb::ScriptedThreadInterfaceSP CreateScriptedThreadInterface() override;
lldb::ScriptedFrameInterfaceSP CreateScriptedFrameInterface() override;
+39 -11
View File
@@ -8,39 +8,62 @@
#include "lldb/Target/StackFrameRecognizer.h"
#include "lldb/Core/Module.h"
#include "lldb/Interpreter/Interfaces/ScriptedStackFrameRecognizerInterface.h"
#include "lldb/Interpreter/ScriptInterpreter.h"
#include "lldb/Symbol/Symbol.h"
#include "lldb/Target/StackFrame.h"
#include "lldb/Utility/RegularExpression.h"
#include "lldb/Utility/ScriptedMetadata.h"
using namespace lldb;
using namespace lldb_private;
class ScriptedRecognizedStackFrame : public RecognizedStackFrame {
bool m_hidden;
lldb::StackFrameSP m_most_relevant_frame;
lldb::ValueObjectSP m_exception;
public:
ScriptedRecognizedStackFrame(ValueObjectListSP args, bool hidden)
: m_hidden(hidden) {
ScriptedRecognizedStackFrame(ValueObjectListSP args, bool hidden,
lldb::StackFrameSP most_relevant_frame,
lldb::ValueObjectSP exception,
std::string stop_desc)
: m_hidden(hidden), m_most_relevant_frame(std::move(most_relevant_frame)),
m_exception(std::move(exception)) {
m_arguments = std::move(args);
m_stop_desc = std::move(stop_desc);
}
bool ShouldHide() override { return m_hidden; }
lldb::StackFrameSP GetMostRelevantFrame() override {
return m_most_relevant_frame;
}
lldb::ValueObjectSP GetExceptionObject() override { return m_exception; }
};
ScriptedStackFrameRecognizer::ScriptedStackFrameRecognizer(
ScriptInterpreter *interpreter, const char *pclass)
: m_interpreter(interpreter), m_python_class(pclass) {
m_python_object_sp =
m_interpreter->CreateFrameRecognizer(m_python_class.c_str());
: m_python_class(pclass) {
if (!interpreter)
return;
m_interface_sp = interpreter->CreateScriptedStackFrameRecognizerInterface();
if (!m_interface_sp)
return;
ScriptedMetadata scripted_metadata(m_python_class, nullptr);
auto obj_or_err = m_interface_sp->CreatePluginObject(scripted_metadata);
if (!obj_or_err) {
llvm::consumeError(obj_or_err.takeError());
m_interface_sp.reset();
}
}
RecognizedStackFrameSP
ScriptedStackFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame) {
if (!m_python_object_sp || !m_interpreter)
if (!m_interface_sp)
return RecognizedStackFrameSP();
ValueObjectListSP args =
m_interpreter->GetRecognizedArguments(m_python_object_sp, frame);
ValueObjectListSP args = m_interface_sp->GetRecognizedArguments(frame);
auto args_synthesized = std::make_shared<ValueObjectList>();
if (args) {
for (const auto &o : args->GetObjects())
@@ -48,10 +71,15 @@ ScriptedStackFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame) {
*o, eValueTypeVariableArgument));
}
bool hidden = m_interpreter->ShouldHide(m_python_object_sp, frame);
bool hidden = m_interface_sp->ShouldHide(frame);
lldb::StackFrameSP most_relevant =
m_interface_sp->SelectMostRelevantFrame(frame);
lldb::ValueObjectSP exception = m_interface_sp->GetException(frame);
std::string stop_desc = m_interface_sp->GetStopDescription(frame);
return RecognizedStackFrameSP(
new ScriptedRecognizedStackFrame(args_synthesized, hidden));
return RecognizedStackFrameSP(new ScriptedRecognizedStackFrame(
args_synthesized, hidden, std::move(most_relevant), std::move(exception),
std::move(stop_desc)));
}
void StackFrameRecognizerManager::BumpGeneration() {
@@ -222,6 +222,46 @@ class FrameRecognizerTestCase(TestBase):
frame = thread.GetSelectedFrame()
self.assertIn("main", frame.name)
def test_frame_recognizer_optional_hooks(self):
"""Exercise select_most_relevant_frame, get_exception, and
get_stop_description via a recognizer that implements all three.
Each hook records the frame name it saw in a class-level list so
the test can assert the hook actually fired."""
self.build()
target, process, thread, _ = lldbutil.run_to_name_breakpoint(self, "nested")
self.expect("frame recognizer clear")
self.expect(
"command script import "
+ os.path.join(self.getSourceDir(), "recognizer.py")
)
# Reset the call trackers to isolate this test's activity from
# anything the recognizer might have observed earlier.
import recognizer
recognizer.NestedFrameRecognizer.select_most_relevant_frame_calls.clear()
recognizer.NestedFrameRecognizer.get_exception_calls.clear()
recognizer.NestedFrameRecognizer.get_stop_description_calls.clear()
self.expect(
"frame recognizer add -l recognizer.NestedFrameRecognizer "
"-f false -s a.out -n nested"
)
# Backtracing triggers recognition on each frame lldb walks.
self.runCmd("thread backtrace")
self.assertIn(
"nested", recognizer.NestedFrameRecognizer.select_most_relevant_frame_calls
)
self.assertIn("nested", recognizer.NestedFrameRecognizer.get_exception_calls)
self.assertIn(
"nested",
recognizer.NestedFrameRecognizer.get_stop_description_calls,
)
def test_frame_recognizer_multi_symbol(self):
self.build()
exe = self.getBuildArtifact("a.out")
@@ -3,7 +3,7 @@
import lldb
class MyFrameRecognizer(object):
class MyFrameRecognizer:
def get_recognized_arguments(self, frame):
if frame.name == "foo":
arg1 = frame.EvaluateExpression("$arg1").signed
@@ -33,11 +33,38 @@ class MyFrameRecognizer(object):
return []
class MyOtherFrameRecognizer(object):
class MyOtherFrameRecognizer:
def get_recognized_arguments(self, frame):
return []
class BazFrameRecognizer(object):
class BazFrameRecognizer:
def should_hide(self, frame):
return "baz" in frame.name
class NestedFrameRecognizer:
"""Exercises the optional hooks beyond `should_hide`: replace the
stop description, redirect the selected frame, and expose a synthetic
exception object. Tracks invocation via class-level lists so a test
can assert each hook actually fires.
"""
select_most_relevant_frame_calls = []
get_exception_calls = []
get_stop_description_calls = []
def select_most_relevant_frame(self, frame):
self.select_most_relevant_frame_calls.append(frame.name)
# Redirect from `nested` (frame 0) to its caller `baz` (frame 1).
return frame.thread.frames[1] if frame.thread.num_frames > 1 else None
def get_exception(self, frame):
self.get_exception_calls.append(frame.name)
return frame.thread.process.target.CreateValueFromExpression(
"recognized_exception", "42"
)
def get_stop_description(self, frame):
self.get_stop_description_calls.append(frame.name)
return "recognized nested()"
@@ -260,18 +260,6 @@ lldb_private::python::SWIGBridge::LLDBSWIGPythonCreateOSPlugin(
return python::PythonObject();
}
python::PythonObject
lldb_private::python::SWIGBridge::LLDBSWIGPython_CreateFrameRecognizer(
const char *python_class_name, const char *session_dictionary_name) {
return python::PythonObject();
}
PyObject *
lldb_private::python::SWIGBridge::LLDBSwigPython_GetRecognizedArguments(
PyObject *implementor, const lldb::StackFrameSP &frame_sp) {
return nullptr;
}
bool lldb_private::python::SWIGBridge::LLDBSWIGPythonRunScriptKeywordProcess(
const char *python_function_name, const char *session_dictionary_name,
const lldb::ProcessSP &process, std::string &output) {