Adrian Prantl f01f80ce6c
[lldb] Extend frame recognizers to hide frames from backtraces (#104523)
Compilers and language runtimes often use helper functions that are
fundamentally uninteresting when debugging anything but the
compiler/runtime itself. This patch introduces a user-extensible
mechanism that allows for these frames to be hidden from backtraces and
automatically skipped over when navigating the stack with `up` and
`down`.

This does not affect the numbering of frames, so `f <N>` will still
provide access to the hidden frames. The `bt` output will also print a
hint that frames have been hidden.

My primary motivation for this feature is to hide thunks in the Swift
programming language, but I'm including an example recognizer for
`std::function::operator()` that I wished for myself many times while
debugging LLDB.

rdar://126629381


Example output. (Yes, my proof-of-concept recognizer could hide even
more frames if we had a method that returned the function name without
the return type or I used something that isn't based off regex, but it's
really only meant as an example).

before:
```
(lldb) thread backtrace --filtered=false
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10
    frame #1: 0x0000000100003a00 a.out`decltype(std::declval<int (*&)(int, int)>()(std::declval<int>(), std::declval<int>())) std::__1::__invoke[abi:se200000]<int (*&)(int, int), int, int>(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25
    frame #2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:se200000]<int (*&)(int, int), int, int>(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12
    frame #3: 0x0000000100003968 a.out`std::__1::__function::__alloc_func<int (*)(int, int), std::__1::allocator<int (*)(int, int)>, int (int, int)>::operator()[abi:se200000](this=0x000000016fdff280, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:171:12
    frame #4: 0x00000001000026bc a.out`std::__1::__function::__func<int (*)(int, int), std::__1::allocator<int (*)(int, int)>, int (int, int)>::operator()(this=0x000000016fdff278, __arg=0x000000016fdff224, __arg=0x000000016fdff220) at function.h:313:10
    frame #5: 0x0000000100003c38 a.out`std::__1::__function::__value_func<int (int, int)>::operator()[abi:se200000](this=0x000000016fdff278, __args=0x000000016fdff224, __args=0x000000016fdff220) const at function.h:430:12
    frame #6: 0x0000000100002038 a.out`std::__1::function<int (int, int)>::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10
    frame #7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10
    frame #8: 0x0000000183cdf154 dyld`start + 2476
(lldb) 
```

after

```
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100001f04 a.out`foo(x=1, y=1) at main.cpp:4:10
    frame #1: 0x0000000100003a00 a.out`decltype(std::declval<int (*&)(int, int)>()(std::declval<int>(), std::declval<int>())) std::__1::__invoke[abi:se200000]<int (*&)(int, int), int, int>(__f=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:149:25
    frame #2: 0x000000010000399c a.out`int std::__1::__invoke_void_return_wrapper<int, false>::__call[abi:se200000]<int (*&)(int, int), int, int>(__args=0x000000016fdff280, __args=0x000000016fdff224, __args=0x000000016fdff220) at invoke.h:216:12
    frame #6: 0x0000000100002038 a.out`std::__1::function<int (int, int)>::operator()(this= Function = foo(int, int) , __arg=1, __arg=1) const at function.h:989:10
    frame #7: 0x0000000100001f64 a.out`main(argc=1, argv=0x000000016fdff4f8) at main.cpp:9:10
    frame #8: 0x0000000183cdf154 dyld`start + 2476
Note: Some frames were hidden by frame recognizers
```
2024-08-20 16:01:22 -07:00

275 lines
12 KiB
C++

//===-- ScriptInterpreterPython.h -------------------------------*- C++ -*-===//
//
// 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_PLUGINS_SCRIPTINTERPRETER_PYTHON_SWIGPYTHONBRIDGE_H
#define LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_SWIGPYTHONBRIDGE_H
#include <optional>
#include <string>
#include "lldb/Host/Config.h"
#if LLDB_ENABLE_PYTHON
// LLDB Python header must be included first
#include "lldb-python.h"
#include "Plugins/ScriptInterpreter/Python/PythonDataObjects.h"
#include "lldb/lldb-forward.h"
#include "lldb/lldb-types.h"
#include "llvm/Support/Error.h"
namespace lldb {
class SBEvent;
class SBCommandReturnObject;
class SBValue;
class SBStream;
class SBStructuredData;
class SBFileSpec;
class SBModuleSpec;
class SBStringList;
} // namespace lldb
namespace lldb_private {
namespace python {
typedef struct swig_type_info swig_type_info;
python::PythonObject ToSWIGHelper(void *obj, swig_type_info *info);
/// A class that automatically clears an SB object when it goes out of scope.
/// Use for cases where the SB object points to a temporary/unowned entity.
template <typename T> class ScopedPythonObject : PythonObject {
public:
ScopedPythonObject(T *sb, swig_type_info *info)
: PythonObject(ToSWIGHelper(sb, info)), m_sb(sb) {}
~ScopedPythonObject() {
if (m_sb)
*m_sb = T();
}
ScopedPythonObject(ScopedPythonObject &&rhs)
: PythonObject(std::move(rhs)), m_sb(std::exchange(rhs.m_sb, nullptr)) {}
ScopedPythonObject(const ScopedPythonObject &) = delete;
ScopedPythonObject &operator=(const ScopedPythonObject &) = delete;
ScopedPythonObject &operator=(ScopedPythonObject &&) = delete;
const PythonObject &obj() const { return *this; }
private:
T *m_sb;
};
// TODO: We may want to support other languages in the future w/ SWIG (we
// already support Lua right now, for example). We could create a generic
// SWIGBridge class and have this one specialize it, something like this:
//
// <typename T>
// class SWIGBridge {
// static T ToSWIGWrapper(...);
// };
//
// class SWIGPythonBridge : public SWIGBridge<PythonObject> {
// template<> static PythonObject ToSWIGWrapper(...);
// };
//
// And we should be able to more easily support things like Lua
class SWIGBridge {
public:
static PythonObject ToSWIGWrapper(std::unique_ptr<lldb::SBValue> value_sb);
static PythonObject ToSWIGWrapper(lldb::ValueObjectSP value_sp);
static PythonObject ToSWIGWrapper(lldb::TargetSP target_sp);
static PythonObject ToSWIGWrapper(lldb::ProcessSP process_sp);
static PythonObject ToSWIGWrapper(lldb::ThreadPlanSP thread_plan_sp);
static PythonObject ToSWIGWrapper(lldb::BreakpointSP breakpoint_sp);
static PythonObject ToSWIGWrapper(const Status &status);
static PythonObject ToSWIGWrapper(const StructuredDataImpl &data_impl);
static PythonObject ToSWIGWrapper(lldb::ThreadSP thread_sp);
static PythonObject ToSWIGWrapper(lldb::StackFrameSP frame_sp);
static PythonObject ToSWIGWrapper(lldb::DebuggerSP debugger_sp);
static PythonObject ToSWIGWrapper(lldb::WatchpointSP watchpoint_sp);
static PythonObject ToSWIGWrapper(lldb::BreakpointLocationSP bp_loc_sp);
static PythonObject ToSWIGWrapper(lldb::TypeImplSP type_impl_sp);
static PythonObject ToSWIGWrapper(lldb::ExecutionContextRefSP ctx_sp);
static PythonObject ToSWIGWrapper(const TypeSummaryOptions &summary_options);
static PythonObject ToSWIGWrapper(const SymbolContext &sym_ctx);
static PythonObject ToSWIGWrapper(const Stream *stream);
static PythonObject ToSWIGWrapper(std::shared_ptr<lldb::SBStream> stream_sb);
static PythonObject ToSWIGWrapper(Event *event);
static PythonObject ToSWIGWrapper(lldb::ProcessAttachInfoSP attach_info_sp);
static PythonObject ToSWIGWrapper(lldb::ProcessLaunchInfoSP launch_info_sp);
static PythonObject ToSWIGWrapper(lldb::DataExtractorSP data_extractor_sp);
static PythonObject
ToSWIGWrapper(std::unique_ptr<lldb::SBStructuredData> data_sb);
static PythonObject
ToSWIGWrapper(std::unique_ptr<lldb::SBFileSpec> file_spec_sb);
static PythonObject
ToSWIGWrapper(std::unique_ptr<lldb::SBModuleSpec> module_spec_sb);
static python::ScopedPythonObject<lldb::SBCommandReturnObject>
ToSWIGWrapper(CommandReturnObject &cmd_retobj);
// These prototypes are the Pythonic implementations of the required
// callbacks. Although these are scripting-language specific, their definition
// depends on the public API.
static llvm::Expected<bool> LLDBSwigPythonBreakpointCallbackFunction(
const char *python_function_name, const char *session_dictionary_name,
const lldb::StackFrameSP &sb_frame,
const lldb::BreakpointLocationSP &sb_bp_loc,
const lldb_private::StructuredDataImpl &args_impl);
static bool LLDBSwigPythonWatchpointCallbackFunction(
const char *python_function_name, const char *session_dictionary_name,
const lldb::StackFrameSP &sb_frame, const lldb::WatchpointSP &sb_wp);
static bool
LLDBSwigPythonFormatterCallbackFunction(const char *python_function_name,
const char *session_dictionary_name,
lldb::TypeImplSP type_impl_sp);
static bool LLDBSwigPythonCallTypeScript(
const char *python_function_name, const void *session_dictionary,
const lldb::ValueObjectSP &valobj_sp, void **pyfunct_wrapper,
const lldb::TypeSummaryOptionsSP &options_sp, std::string &retval);
static python::PythonObject
LLDBSwigPythonCreateSyntheticProvider(const char *python_class_name,
const char *session_dictionary_name,
const lldb::ValueObjectSP &valobj_sp);
static python::PythonObject
LLDBSwigPythonCreateCommandObject(const char *python_class_name,
const char *session_dictionary_name,
lldb::DebuggerSP debugger_sp);
static python::PythonObject LLDBSwigPythonCreateScriptedBreakpointResolver(
const char *python_class_name, const char *session_dictionary_name,
const StructuredDataImpl &args, const lldb::BreakpointSP &bkpt_sp);
static unsigned int
LLDBSwigPythonCallBreakpointResolver(void *implementor,
const char *method_name,
lldb_private::SymbolContext *sym_ctx);
static python::PythonObject LLDBSwigPythonCreateScriptedStopHook(
lldb::TargetSP target_sp, const char *python_class_name,
const char *session_dictionary_name, const StructuredDataImpl &args,
lldb_private::Status &error);
static bool
LLDBSwigPythonStopHookCallHandleStop(void *implementor,
lldb::ExecutionContextRefSP exc_ctx,
lldb::StreamSP stream);
static size_t LLDBSwigPython_CalculateNumChildren(PyObject *implementor,
uint32_t max);
static PyObject *LLDBSwigPython_GetChildAtIndex(PyObject *implementor,
uint32_t idx);
static int LLDBSwigPython_GetIndexOfChildWithName(PyObject *implementor,
const char *child_name);
static lldb::ValueObjectSP
LLDBSWIGPython_GetValueObjectSPFromSBValue(void *data);
static bool LLDBSwigPython_UpdateSynthProviderInstance(PyObject *implementor);
static bool
LLDBSwigPython_MightHaveChildrenSynthProviderInstance(PyObject *implementor);
static PyObject *
LLDBSwigPython_GetValueSynthProviderInstance(PyObject *implementor);
static bool
LLDBSwigPythonCallCommand(const char *python_function_name,
const char *session_dictionary_name,
lldb::DebuggerSP debugger, const char *args,
lldb_private::CommandReturnObject &cmd_retobj,
lldb::ExecutionContextRefSP exe_ctx_ref_sp);
static bool
LLDBSwigPythonCallCommandObject(PyObject *implementor,
lldb::DebuggerSP debugger, const char *args,
lldb_private::CommandReturnObject &cmd_retobj,
lldb::ExecutionContextRefSP exe_ctx_ref_sp);
static bool
LLDBSwigPythonCallParsedCommandObject(PyObject *implementor,
lldb::DebuggerSP debugger,
StructuredDataImpl &args_impl,
lldb_private::CommandReturnObject &cmd_retobj,
lldb::ExecutionContextRefSP exe_ctx_ref_sp);
static std::optional<std::string>
LLDBSwigPythonGetRepeatCommandForScriptedCommand(PyObject *implementor,
std::string &command);
static bool LLDBSwigPythonCallModuleInit(const char *python_module_name,
const char *session_dictionary_name,
lldb::DebuggerSP debugger);
static python::PythonObject
LLDBSWIGPythonCreateOSPlugin(const char *python_class_name,
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);
static std::optional<std::string>
LLDBSWIGPythonRunScriptKeywordThread(const char *python_function_name,
const char *session_dictionary_name,
lldb::ThreadSP thread);
static bool LLDBSWIGPythonRunScriptKeywordTarget(
const char *python_function_name, const char *session_dictionary_name,
const lldb::TargetSP &target, std::string &output);
static std::optional<std::string>
LLDBSWIGPythonRunScriptKeywordFrame(const char *python_function_name,
const char *session_dictionary_name,
lldb::StackFrameSP frame);
static bool LLDBSWIGPythonRunScriptKeywordValue(
const char *python_function_name, const char *session_dictionary_name,
const lldb::ValueObjectSP &value, std::string &output);
static void *
LLDBSWIGPython_GetDynamicSetting(void *module, const char *setting,
const lldb::TargetSP &target_sp);
};
void *LLDBSWIGPython_CastPyObjectToSBData(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBBreakpoint(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBAttachInfo(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBLaunchInfo(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBError(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBEvent(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBStream(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBValue(PyObject *data);
void *LLDBSWIGPython_CastPyObjectToSBMemoryRegionInfo(PyObject *data);
} // namespace python
} // namespace lldb_private
#endif // LLDB_ENABLE_PYTHON
#endif // LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_SWIGPYTHONBRIDGE_H