[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:
1 parent
694db4d296
commit
e28ea20ce0
23 files changed
+485
-212
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -267,7 +267,8 @@ enum ScriptedExtension {
|
||||
eScriptedExtensionScriptedHook,
|
||||
eScriptedExtensionScriptedThread,
|
||||
eScriptedExtensionScriptedFrame,
|
||||
kLastScriptedExtension = eScriptedExtensionScriptedFrame
|
||||
eScriptedExtensionScriptedStackFrameRecognizer,
|
||||
kLastScriptedExtension = eScriptedExtensionScriptedStackFrameRecognizer
|
||||
};
|
||||
|
||||
/// Register numbering types.
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
+2
@@ -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();
|
||||
}
|
||||
+1
@@ -19,6 +19,7 @@
|
||||
#include "ScriptedHookPythonInterface.h"
|
||||
#include "ScriptedPlatformPythonInterface.h"
|
||||
#include "ScriptedProcessPythonInterface.h"
|
||||
#include "ScriptedStackFrameRecognizerPythonInterface.h"
|
||||
#include "ScriptedThreadPlanPythonInterface.h"
|
||||
#include "ScriptedThreadPythonInterface.h"
|
||||
|
||||
|
||||
+48
-13
@@ -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 <>
|
||||
|
||||
+97
@@ -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);
|
||||
}
|
||||
+57
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user