[lldb] Add frame recognizers for libc++ std::invoke (#105695)

With this commit, we also hide the implementation details of
`std::invoke`. To do so, the `LibCXXFrameRecognizer` got a couple more
regular expressions.

The regular expression passed into `AddRecognizer` became problematic,
as it was evaluated on the demangled name. Those names also included
result types for C++ symbols. For `std::__invoke` the return type is a
huge `decltype(...)`, making the regular expresison really hard to
write.

Instead, I added support to `AddRecognizer` for matching on the
demangled names without result type and argument types.

By hiding the implementation details of `invoke`, also the back traces
for `std::function` become even nicer, because `std::function` is using
`__invoke` internally.

Co-authored-by: Adrian Prantl <aprantl@apple.com>
This commit is contained in:
Adrian Vogelsgesang
2024-08-27 19:15:42 +02:00
committed by GitHub
parent 4baf29e81e
commit dd060bdede
16 changed files with 234 additions and 63 deletions

View File

@@ -105,19 +105,30 @@ private:
/// Class that provides a registry of known stack frame recognizers.
class StackFrameRecognizerManager {
public:
/// Add a new recognizer that triggers on a given symbol name.
///
/// \param symbol_mangling controls whether the symbol name should be
/// compared to the mangled or demangled name.
void AddRecognizer(lldb::StackFrameRecognizerSP recognizer,
ConstString module, llvm::ArrayRef<ConstString> symbols,
Mangled::NamePreference symbol_mangling,
bool first_instruction_only = true);
/// Add a new recognizer that triggers on a symbol regex.
///
/// \param symbol_mangling controls whether the regex should apply
/// to the mangled or demangled name.
void AddRecognizer(lldb::StackFrameRecognizerSP recognizer,
lldb::RegularExpressionSP module,
lldb::RegularExpressionSP symbol,
Mangled::NamePreference symbol_mangling,
bool first_instruction_only = true);
void ForEach(std::function<
void(uint32_t recognizer_id, std::string recognizer_name,
std::string module, llvm::ArrayRef<ConstString> symbols,
bool regexp)> const &callback);
Mangled::NamePreference name_reference, bool regexp)> const
&callback);
bool RemoveRecognizerWithID(uint32_t recognizer_id);
@@ -142,6 +153,7 @@ private:
lldb::RegularExpressionSP module_regexp;
std::vector<ConstString> symbols;
lldb::RegularExpressionSP symbol_regexp;
Mangled::NamePreference symbol_mangling;
bool first_instruction_only;
};

View File

@@ -168,8 +168,7 @@ protected:
// We've already handled the case where the value object sp is null, so
// this is just to make sure future changes don't skip that:
assert(valobj_sp.get() && "Must have a valid ValueObject to print");
ValueObjectPrinter printer(*valobj_sp, &result.GetOutputStream(),
options);
ValueObjectPrinter printer(*valobj_sp, &result.GetOutputStream(), options);
if (llvm::Error error = printer.PrintValueObject())
result.AppendError(toString(std::move(error)));
}
@@ -899,13 +898,16 @@ void CommandObjectFrameRecognizerAdd::DoExecute(Args &command,
auto func =
RegularExpressionSP(new RegularExpression(m_options.m_symbols.front()));
GetTarget().GetFrameRecognizerManager().AddRecognizer(
recognizer_sp, module, func, m_options.m_first_instruction_only);
recognizer_sp, module, func, Mangled::NamePreference::ePreferDemangled,
m_options.m_first_instruction_only);
} else {
auto module = ConstString(m_options.m_module);
std::vector<ConstString> symbols(m_options.m_symbols.begin(),
m_options.m_symbols.end());
GetTarget().GetFrameRecognizerManager().AddRecognizer(
recognizer_sp, module, symbols, m_options.m_first_instruction_only);
recognizer_sp, module, symbols,
Mangled::NamePreference::ePreferDemangled,
m_options.m_first_instruction_only);
}
#endif
@@ -927,6 +929,34 @@ protected:
}
};
static void
PrintRecognizerDetails(Stream &strm, const std::string &name,
const std::string &module,
llvm::ArrayRef<lldb_private::ConstString> symbols,
Mangled::NamePreference symbol_mangling, bool regexp) {
strm << name << ", ";
if (!module.empty())
strm << "module " << module << ", ";
switch (symbol_mangling) {
case Mangled::NamePreference ::ePreferMangled:
strm << "mangled symbol ";
break;
case Mangled::NamePreference ::ePreferDemangled:
strm << "demangled symbol ";
break;
case Mangled::NamePreference ::ePreferDemangledWithoutArguments:
strm << "demangled (no args) symbol ";
break;
}
if (regexp)
strm << "regex ";
llvm::interleaveComma(symbols, strm);
}
class CommandObjectFrameRecognizerDelete : public CommandObjectParsed {
public:
CommandObjectFrameRecognizerDelete(CommandInterpreter &interpreter)
@@ -947,19 +977,13 @@ public:
GetTarget().GetFrameRecognizerManager().ForEach(
[&request](uint32_t rid, std::string rname, std::string module,
llvm::ArrayRef<lldb_private::ConstString> symbols,
bool regexp) {
Mangled::NamePreference symbol_mangling, bool regexp) {
StreamString strm;
if (rname.empty())
rname = "(internal)";
strm << rname;
if (!module.empty())
strm << ", module " << module;
if (!symbols.empty())
for (auto &symbol : symbols)
strm << ", symbol " << symbol;
if (regexp)
strm << " (regexp)";
PrintRecognizerDetails(strm, rname, module, symbols, symbol_mangling,
regexp);
request.TryCompleteCurrentArg(std::to_string(rid), strm.GetString());
});
@@ -1016,22 +1040,18 @@ protected:
void DoExecute(Args &command, CommandReturnObject &result) override {
bool any_printed = false;
GetTarget().GetFrameRecognizerManager().ForEach(
[&result, &any_printed](
uint32_t recognizer_id, std::string name, std::string module,
llvm::ArrayRef<ConstString> symbols, bool regexp) {
[&result,
&any_printed](uint32_t recognizer_id, std::string name,
std::string module, llvm::ArrayRef<ConstString> symbols,
Mangled::NamePreference symbol_mangling, bool regexp) {
Stream &stream = result.GetOutputStream();
if (name.empty())
name = "(internal)";
stream << std::to_string(recognizer_id) << ": " << name;
if (!module.empty())
stream << ", module " << module;
if (!symbols.empty())
for (auto &symbol : symbols)
stream << ", symbol " << symbol;
if (regexp)
stream << " (regexp)";
stream << std::to_string(recognizer_id) << ": ";
PrintRecognizerDetails(stream, name, module, symbols, symbol_mangling,
regexp);
stream.EOL();
stream.Flush();

View File

@@ -1049,7 +1049,7 @@ let Command = "thread backtrace" in {
def thread_backtrace_extended : Option<"extended", "e">, Group<1>,
Arg<"Boolean">, Desc<"Show the extended backtrace, if available">;
def thread_backtrace_unfiltered : Option<"unfiltered", "u">, Group<1>,
Desc<"Filter out frames according to installed frame recognizers">;
Desc<"Do not filter out frames according to installed frame recognizers">;
}
let Command = "thread step scope" in {

View File

@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include <cstring>
#include <iostream>
#include <memory>
@@ -44,7 +45,7 @@ char CPPLanguageRuntime::ID = 0;
/// A frame recognizer that is installed to hide libc++ implementation
/// details from the backtrace.
class LibCXXFrameRecognizer : public StackFrameRecognizer {
RegularExpression m_hidden_function_regex;
std::array<RegularExpression, 4> m_hidden_regex;
RecognizedStackFrameSP m_hidden_frame;
struct LibCXXHiddenFrame : public RecognizedStackFrame {
@@ -53,10 +54,30 @@ class LibCXXFrameRecognizer : public StackFrameRecognizer {
public:
LibCXXFrameRecognizer()
: m_hidden_function_regex(
R"(^std::__.*::(__function.*::operator\(\)|__invoke))"
R"((\[.*\])?)" // ABI tag.
R"(( const)?$)"), // const.
: m_hidden_regex{
// internal implementation details of std::function
// std::__1::__function::__alloc_func<void (*)(), std::__1::allocator<void (*)()>, void ()>::operator()[abi:ne200000]
// std::__1::__function::__func<void (*)(), std::__1::allocator<void (*)()>, void ()>::operator()
// std::__1::__function::__value_func<void ()>::operator()[abi:ne200000]() const
RegularExpression{""
R"(^std::__[^:]*::)" // Namespace.
R"(__function::.*::operator\(\))"},
// internal implementation details of std::function in ABI v2
// std::__2::__function::__policy_invoker<void (int, int)>::__call_impl[abi:ne200000]<std::__2::__function::__default_alloc_func<int (*)(int, int), int (int, int)>>
RegularExpression{""
R"(^std::__[^:]*::)" // Namespace.
R"(__function::.*::__call_impl)"},
// internal implementation details of std::invoke
// std::__1::__invoke[abi:ne200000]<void (*&)()>
RegularExpression{
R"(^std::__[^:]*::)" // Namespace.
R"(__invoke)"},
// internal implementation details of std::invoke
// std::__1::__invoke_void_return_wrapper<void, true>::__call[abi:ne200000]<void (*&)()>
RegularExpression{
R"(^std::__[^:]*::)" // Namespace.
R"(__invoke_void_return_wrapper<.*>::__call)"}
},
m_hidden_frame(new LibCXXHiddenFrame()) {}
std::string GetName() override { return "libc++ frame recognizer"; }
@@ -69,8 +90,9 @@ public:
if (!sc.function)
return {};
if (m_hidden_function_regex.Execute(sc.function->GetNameNoArguments()))
return m_hidden_frame;
for (RegularExpression &r : m_hidden_regex)
if (r.Execute(sc.function->GetNameNoArguments()))
return m_hidden_frame;
return {};
}
@@ -81,8 +103,9 @@ CPPLanguageRuntime::CPPLanguageRuntime(Process *process)
if (process)
process->GetTarget().GetFrameRecognizerManager().AddRecognizer(
StackFrameRecognizerSP(new LibCXXFrameRecognizer()), {},
std::make_shared<RegularExpression>("^std::__.*::"),
/*first_instruction_only*/ false);
std::make_shared<RegularExpression>("^std::__[^:]*::"),
/*mangling_preference=*/Mangled::ePreferDemangledWithoutArguments,
/*first_instruction_only=*/false);
}
bool CPPLanguageRuntime::IsAllowedRuntimeValue(ConstString name) {
@@ -108,8 +131,7 @@ bool contains_lambda_identifier(llvm::StringRef &str_ref) {
CPPLanguageRuntime::LibCppStdFunctionCallableInfo
line_entry_helper(Target &target, const SymbolContext &sc, Symbol *symbol,
llvm::StringRef first_template_param_sref,
bool has_invoke) {
llvm::StringRef first_template_param_sref, bool has_invoke) {
CPPLanguageRuntime::LibCppStdFunctionCallableInfo optional_info;
@@ -190,7 +212,7 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
ValueObjectSP sub_member_f_(member_f_->GetChildMemberWithName("__f_"));
if (sub_member_f_)
member_f_ = sub_member_f_;
member_f_ = sub_member_f_;
}
if (!member_f_)

View File

@@ -3465,6 +3465,6 @@ static void RegisterObjCExceptionRecognizer(Process *process) {
process->GetTarget().GetFrameRecognizerManager().AddRecognizer(
StackFrameRecognizerSP(new ObjCExceptionThrowFrameRecognizer()),
module.GetFilename(), symbols,
module.GetFilename(), symbols, Mangled::NamePreference::ePreferDemangled,
/*first_instruction_only*/ true);
}

View File

@@ -36,8 +36,9 @@ void RegisterAbortWithPayloadFrameRecognizer(Process *process) {
return;
process->GetTarget().GetFrameRecognizerManager().AddRecognizer(
std::make_shared<AbortWithPayloadFrameRecognizer>(), module_name,
sym_name, /*first_instruction_only*/ false);
std::make_shared<AbortWithPayloadFrameRecognizer>(), module_name,
sym_name, Mangled::NamePreference::ePreferDemangled,
/*first_instruction_only*/ false);
}
RecognizedStackFrameSP

View File

@@ -89,6 +89,7 @@ void RegisterAssertFrameRecognizer(Process *process) {
target.GetFrameRecognizerManager().AddRecognizer(
std::make_shared<AssertFrameRecognizer>(),
location.module_spec.GetFilename(), location.symbols,
Mangled::ePreferDemangled,
/*first_instruction_only*/ false);
return;
}
@@ -112,6 +113,7 @@ void RegisterAssertFrameRecognizer(Process *process) {
std::make_shared<AssertFrameRecognizer>(),
std::make_shared<RegularExpression>(std::move(module_re)),
std::make_shared<RegularExpression>(std::move(symbol_re)),
Mangled::ePreferDemangled,
/*first_instruction_only*/ false);
}

View File

@@ -62,25 +62,29 @@ void StackFrameRecognizerManager::BumpGeneration() {
void StackFrameRecognizerManager::AddRecognizer(
StackFrameRecognizerSP recognizer, ConstString module,
llvm::ArrayRef<ConstString> symbols, bool first_instruction_only) {
llvm::ArrayRef<ConstString> symbols,
Mangled::NamePreference symbol_mangling, bool first_instruction_only) {
m_recognizers.push_front({(uint32_t)m_recognizers.size(), recognizer, false,
module, RegularExpressionSP(), symbols,
RegularExpressionSP(), first_instruction_only});
RegularExpressionSP(), symbol_mangling,
first_instruction_only});
BumpGeneration();
}
void StackFrameRecognizerManager::AddRecognizer(
StackFrameRecognizerSP recognizer, RegularExpressionSP module,
RegularExpressionSP symbol, bool first_instruction_only) {
RegularExpressionSP symbol, Mangled::NamePreference symbol_mangling,
bool first_instruction_only) {
m_recognizers.push_front({(uint32_t)m_recognizers.size(), recognizer, true,
ConstString(), module, std::vector<ConstString>(),
symbol, first_instruction_only});
symbol, symbol_mangling, first_instruction_only});
BumpGeneration();
}
void StackFrameRecognizerManager::ForEach(
const std::function<void(uint32_t, std::string, std::string,
llvm::ArrayRef<ConstString>, bool)> &callback) {
const std::function<
void(uint32_t, std::string, std::string, llvm::ArrayRef<ConstString>,
Mangled::NamePreference name_reference, bool)> &callback) {
for (auto entry : m_recognizers) {
if (entry.is_regexp) {
std::string module_name;
@@ -92,11 +96,13 @@ void StackFrameRecognizerManager::ForEach(
symbol_name = entry.symbol_regexp->GetText().str();
callback(entry.recognizer_id, entry.recognizer->GetName(), module_name,
llvm::ArrayRef(ConstString(symbol_name)), true);
llvm::ArrayRef(ConstString(symbol_name)), entry.symbol_mangling,
true);
} else {
callback(entry.recognizer_id, entry.recognizer->GetName(),
entry.module.GetCString(), entry.symbols, false);
entry.module.GetCString(), entry.symbols, entry.symbol_mangling,
false);
}
}
}
@@ -125,7 +131,6 @@ StackFrameRecognizerSP
StackFrameRecognizerManager::GetRecognizerForFrame(StackFrameSP frame) {
const SymbolContext &symctx = frame->GetSymbolContext(
eSymbolContextModule | eSymbolContextFunction | eSymbolContextSymbol);
ConstString function_name = symctx.GetFunctionName();
ModuleSP module_sp = symctx.module_sp;
if (!module_sp)
return StackFrameRecognizerSP();
@@ -145,6 +150,8 @@ StackFrameRecognizerManager::GetRecognizerForFrame(StackFrameSP frame) {
if (!entry.module_regexp->Execute(module_name.GetStringRef()))
continue;
ConstString function_name = symctx.GetFunctionName(entry.symbol_mangling);
if (!entry.symbols.empty())
if (!llvm::is_contained(entry.symbols, function_name))
continue;

View File

@@ -116,7 +116,8 @@ void RegisterVerboseTrapFrameRecognizer(Process &process) {
std::make_shared<VerboseTrapFrameRecognizer>();
process.GetTarget().GetFrameRecognizerManager().AddRecognizer(
srf_recognizer_sp, module_regex_sp, symbol_regex_sp, false);
srf_recognizer_sp, module_regex_sp, symbol_regex_sp,
Mangled::ePreferDemangled, false);
}
} // namespace lldb_private

View File

@@ -35,7 +35,9 @@ class FrameRecognizerTestCase(TestBase):
self.expect(
"frame recognizer list",
substrs=["0: recognizer.MyFrameRecognizer, module a.out, symbol foo"],
substrs=[
"0: recognizer.MyFrameRecognizer, module a.out, demangled symbol foo"
],
)
self.runCmd(
@@ -45,8 +47,8 @@ class FrameRecognizerTestCase(TestBase):
self.expect(
"frame recognizer list",
substrs=[
"1: recognizer.MyOtherFrameRecognizer, module a.out, symbol bar (regexp)",
"0: recognizer.MyFrameRecognizer, module a.out, symbol foo",
"1: recognizer.MyOtherFrameRecognizer, module a.out, demangled symbol regex bar",
"0: recognizer.MyFrameRecognizer, module a.out, demangled symbol foo",
],
)
@@ -56,7 +58,7 @@ class FrameRecognizerTestCase(TestBase):
self.expect(
"frame recognizer list",
substrs=[
"1: recognizer.MyOtherFrameRecognizer, module a.out, symbol bar (regexp)"
"1: recognizer.MyOtherFrameRecognizer, module a.out, demangled symbol bar (regexp)"
],
)
self.expect(
@@ -79,7 +81,7 @@ class FrameRecognizerTestCase(TestBase):
self.expect(
"frame recognizer list",
substrs=[
"1: recognizer.MyOtherFrameRecognizer, module a.out, symbol bar (regexp)"
"1: recognizer.MyOtherFrameRecognizer, module a.out, demangled symbol regexp bar"
],
)
self.expect(
@@ -224,7 +226,7 @@ class FrameRecognizerTestCase(TestBase):
self.expect(
"frame recognizer list",
substrs=[
"recognizer.MyFrameRecognizer, module a.out, symbol foo, symbol bar"
"recognizer.MyFrameRecognizer, module a.out, demangled symbol foo, bar"
],
)
@@ -279,7 +281,7 @@ class FrameRecognizerTestCase(TestBase):
self.expect(
"frame recognizer list",
substrs=[
"recognizer.MyFrameRecognizer, module a.out, symbol foo, symbol bar"
"recognizer.MyFrameRecognizer, module a.out, demangled symbol foo, bar"
],
)
@@ -305,7 +307,9 @@ class FrameRecognizerTestCase(TestBase):
self.expect(
"frame recognizer list",
substrs=["recognizer.MyFrameRecognizer, module a.out, symbol bar"],
substrs=[
"recognizer.MyFrameRecognizer, module a.out, demangled symbol bar"
],
)
# Now the new target should also recognize the frame.

View File

@@ -708,7 +708,7 @@ class CommandLineCompletionTestCase(TestBase):
)
self.check_completion_with_desc(
"frame recognizer delete ",
[["0", "py_class, module module_name, symbol recognizer_name"]],
[["0", "py_class, module module_name, demangled symbol recognizer_name"]],
)
def test_platform_install_local_file(self):

View File

@@ -7,6 +7,28 @@ from lldbsuite.test import lldbutil
class LibCxxStdFunctionRecognizerTestCase(TestBase):
NO_DEBUG_INFO_TESTCASE = True
@add_test_categories(["libc++"])
def test_frame_recognizer(self):
"""Test that std::function all implementation details are hidden in SBFrame"""
self.build()
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
self, "// break here", lldb.SBFileSpec("main.cpp")
)
self.assertIn("foo", thread.GetFrameAtIndex(0).GetFunctionName())
# Skip all hidden frames
frame_id = 1
while (
frame_id < thread.GetNumFrames()
and thread.GetFrameAtIndex(frame_id).IsHidden()
):
frame_id = frame_id + 1
# Expect `std::function<...>::operator()` to be the direct parent of `foo`
self.assertIn(
"::operator()", thread.GetFrameAtIndex(frame_id).GetFunctionName()
)
# And right above that, there should be the `main` frame
self.assertIn("main", thread.GetFrameAtIndex(frame_id + 1).GetFunctionName())
@add_test_categories(["libc++"])
def test_backtrace(self):
"""Test that std::function implementation details are hidden in bt"""
@@ -27,12 +49,12 @@ class LibCxxStdFunctionRecognizerTestCase(TestBase):
self.expect(
"thread backtrace -u",
ordered=True,
patterns=["frame.*foo", "frame.*std::__.*::__function", "frame.*main"],
patterns=["frame.*foo", "frame.*std::__[^:]*::__function", "frame.*main"],
)
self.expect(
"thread backtrace --unfiltered",
ordered=True,
patterns=["frame.*foo", "frame.*std::__.*::__function", "frame.*main"],
patterns=["frame.*foo", "frame.*std::__[^:]*::__function", "frame.*main"],
)
@add_test_categories(["libc++"])

View File

@@ -0,0 +1,5 @@
CXX_SOURCES := main.cpp
USE_LIBCPP := 1
CXXFLAGS_EXTRAS := -std=c++17
include Makefile.rules

View File

@@ -0,0 +1,44 @@
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class LibCxxStdFunctionRecognizerTestCase(TestBase):
NO_DEBUG_INFO_TESTCASE = True
@add_test_categories(["libc++"])
def test_frame_recognizer(self):
"""Test that implementation details of `std::invoke` are hidden"""
self.build()
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
self, "break here", lldb.SBFileSpec("main.cpp")
)
stop_cnt = 0
while process.GetState() != lldb.eStateExited:
stop_cnt += 1
self.assertTrue(
any(
f in thread.GetFrameAtIndex(0).GetFunctionName()
for f in ["consume_number", "add", "Callable"]
)
)
# Skip all hidden frames
frame_id = 1
while (
frame_id < thread.GetNumFrames()
and thread.GetFrameAtIndex(frame_id).IsHidden()
):
frame_id = frame_id + 1
# Expect `std::invoke` to be the direct parent
self.assertIn(
"::invoke", thread.GetFrameAtIndex(frame_id).GetFunctionName()
)
# And right above that, there should be the `main` frame
self.assertIn(
"main", thread.GetFrameAtIndex(frame_id + 1).GetFunctionName()
)
process.Continue()
self.assertEqual(stop_cnt, 4)

View File

@@ -0,0 +1,30 @@
#include <functional>
void consume_number(int i) { __builtin_printf("break here"); }
int add(int i, int j) {
// break here
return i + j;
}
struct Callable {
Callable(int num) : num_(num) {}
void operator()(int i) const { __builtin_printf("break here"); }
void member_function(int i) const { __builtin_printf("break here"); }
int num_;
};
int main() {
// Invoke a void-returning function
std::invoke(consume_number, -9);
// Invoke a non-void-returning function
std::invoke(add, 1, 10);
// Invoke a member function
const Callable foo(314159);
std::invoke(&Callable::member_function, foo, 1);
// Invoke a function object
std::invoke(Callable(12), 18);
}

View File

@@ -55,7 +55,7 @@ void RegisterDummyStackFrameRecognizer(StackFrameRecognizerManager &manager) {
StackFrameRecognizerSP dummy_recognizer_sp(new DummyStackFrameRecognizer());
manager.AddRecognizer(dummy_recognizer_sp, module_regex_sp, symbol_regex_sp,
false);
Mangled::NamePreference::ePreferDemangled, false);
}
} // namespace
@@ -72,6 +72,7 @@ TEST_F(StackFrameRecognizerTest, NullModuleRegex) {
manager.ForEach([&any_printed](uint32_t recognizer_id, std::string name,
std::string function,
llvm::ArrayRef<ConstString> symbols,
Mangled::NamePreference symbol_mangling,
bool regexp) { any_printed = true; });
EXPECT_TRUE(any_printed);