Skip to content

Commit

Permalink
[lldb-dap] Add frame recognizers for libc++ std::invoke
Browse files Browse the repository at this point in the history
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 the `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 for `AddRecognizer` to match 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.
  • Loading branch information
vogelsgesang committed Aug 22, 2024
1 parent b765fdd commit 1836531
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 21 deletions.
9 changes: 7 additions & 2 deletions lldb/include/lldb/Target/StackFrameRecognizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,14 @@ class StackFrameRecognizerManager {
public:
void AddRecognizer(lldb::StackFrameRecognizerSP recognizer,
ConstString module, llvm::ArrayRef<ConstString> symbols,
bool first_instruction_only = true);
bool first_instruction_only = true,
Mangled::NamePreference mangling_preference = Mangled::ePreferDemangled);

void AddRecognizer(lldb::StackFrameRecognizerSP recognizer,
lldb::RegularExpressionSP module,
lldb::RegularExpressionSP symbol,
bool first_instruction_only = true);
bool first_instruction_only = true,
Mangled::NamePreference mangling_preference = Mangled::ePreferDemangled);

void ForEach(std::function<
void(uint32_t recognizer_id, std::string recognizer_name,
Expand Down Expand Up @@ -143,10 +145,13 @@ class StackFrameRecognizerManager {
std::vector<ConstString> symbols;
lldb::RegularExpressionSP symbol_regexp;
bool first_instruction_only;
Mangled::NamePreference mangling_preference;
};

std::deque<RegisteredEntry> m_recognizers;
uint16_t m_generation;
std::unordered_set<Mangled::NamePreference> m_used_manglings;

};

/// \class ValueObjectRecognizerSynthesizedValue
Expand Down
2 changes: 1 addition & 1 deletion lldb/source/Commands/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//

#include <cstring>
#include <iostream>

#include <memory>

Expand Down Expand Up @@ -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, 3> m_hidden_regex;
RecognizedStackFrameSP m_hidden_frame;

struct LibCXXHiddenFrame : public RecognizedStackFrame {
Expand All @@ -53,10 +54,32 @@ class LibCXXFrameRecognizer : public StackFrameRecognizer {

public:
LibCXXFrameRecognizer()
: m_hidden_function_regex(
R"(^std::__1::(__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::__[0-9]*::)" // Namespace.
R"(__function::.*::operator\(\))"
R"((\[.*\])?)" // ABI tag.
R"(( const)?$)"}, // const.
// internal implementation details of std::invoke
// std::__1::__invoke[abi:ne200000]<void (*&)()>
RegularExpression{
R"(^std::__[0-9]*::)" // Namespace.
R"(__invoke)"
R"((\[.*\])?)" // ABI tag.
R"(<.*>)"}, // template argument.
// internal implementation details of std::invoke
// std::__1::__invoke_void_return_wrapper<void, true>::__call[abi:ne200000]<void (*&)()>
RegularExpression{
R"(^std::__[0-9]*::)" // Namespace.
R"(__invoke_void_return_wrapper<.*>::__call)"
R"((\[.*\])?)" // ABI tag.
R"(<.*>)"} // template argument.

},
m_hidden_frame(new LibCXXHiddenFrame()) {}

std::string GetName() override { return "libc++ frame recognizer"; }
Expand All @@ -69,8 +92,9 @@ class LibCXXFrameRecognizer : public StackFrameRecognizer {
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 {};
}
Expand All @@ -81,8 +105,9 @@ CPPLanguageRuntime::CPPLanguageRuntime(Process *process)
if (process)
process->GetTarget().GetFrameRecognizerManager().AddRecognizer(
StackFrameRecognizerSP(new LibCXXFrameRecognizer()), {},
std::make_shared<RegularExpression>("^std::__1::"),
/*first_instruction_only*/ false);
std::make_shared<RegularExpression>("std::__[0-9]*::"),
/*first_instruction_only=*/ false,
/*mangling_preference=*/ Mangled::ePreferDemangledWithoutArguments);
}

bool CPPLanguageRuntime::IsAllowedRuntimeValue(ConstString name) {
Expand All @@ -108,8 +133,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;

Expand Down Expand Up @@ -190,7 +214,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_)
Expand Down
41 changes: 37 additions & 4 deletions lldb/source/Target/StackFrameRecognizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,24 @@ void StackFrameRecognizerManager::BumpGeneration() {

void StackFrameRecognizerManager::AddRecognizer(
StackFrameRecognizerSP recognizer, ConstString module,
llvm::ArrayRef<ConstString> symbols, bool first_instruction_only) {
llvm::ArrayRef<ConstString> symbols, bool first_instruction_only,
Mangled::NamePreference mangling_preference) {
m_recognizers.push_front({(uint32_t)m_recognizers.size(), recognizer, false,
module, RegularExpressionSP(), symbols,
RegularExpressionSP(), first_instruction_only});
m_used_manglings.insert(mangling_preference);
BumpGeneration();
}

void StackFrameRecognizerManager::AddRecognizer(
StackFrameRecognizerSP recognizer, RegularExpressionSP module,
RegularExpressionSP symbol, bool first_instruction_only) {
RegularExpressionSP symbol, bool first_instruction_only,
Mangled::NamePreference mangling_preference) {
m_recognizers.push_front({(uint32_t)m_recognizers.size(), recognizer, true,
ConstString(), module, std::vector<ConstString>(),
symbol, first_instruction_only});
symbol, first_instruction_only,
mangling_preference});
m_used_manglings.insert(mangling_preference);
BumpGeneration();
}

Expand Down Expand Up @@ -119,13 +124,30 @@ bool StackFrameRecognizerManager::RemoveRecognizerWithID(
void StackFrameRecognizerManager::RemoveAllRecognizers() {
BumpGeneration();
m_recognizers.clear();
m_used_manglings.clear();
}

StackFrameRecognizerSP
StackFrameRecognizerManager::GetRecognizerForFrame(StackFrameSP frame) {
const SymbolContext &symctx = frame->GetSymbolContext(
eSymbolContextModule | eSymbolContextFunction | eSymbolContextSymbol);
ConstString function_name = symctx.GetFunctionName();
ConstString function_name_mangled;
ConstString function_name_demangled;
ConstString function_name_noargs;
for (Mangled::NamePreference m : m_used_manglings) {
switch (m) {
case Mangled::ePreferMangled:
function_name_mangled = symctx.GetFunctionName(m);
break;
case Mangled::ePreferDemangled:
function_name_demangled = symctx.GetFunctionName(m);
break;
case Mangled::ePreferDemangledWithoutArguments:
function_name_noargs = symctx.GetFunctionName(m);
break;
}
}

ModuleSP module_sp = symctx.module_sp;
if (!module_sp)
return StackFrameRecognizerSP();
Expand All @@ -145,6 +167,17 @@ StackFrameRecognizerManager::GetRecognizerForFrame(StackFrameSP frame) {
if (!entry.module_regexp->Execute(module_name.GetStringRef()))
continue;

ConstString function_name = [&]() {
switch (entry.mangling_preference) {
case Mangled::ePreferMangled:
return function_name_mangled;
case Mangled::ePreferDemangled:
return function_name_demangled;
case Mangled::ePreferDemangledWithoutArguments:
return function_name_noargs;
}
}();

if (!entry.symbols.empty())
if (!llvm::is_contained(entry.symbols, function_name))
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,23 @@
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"""
Expand All @@ -27,12 +44,12 @@ def test_backtrace(self):
self.expect(
"thread backtrace -u",
ordered=True,
patterns=["frame.*foo", "frame.*std::__1::__function", "frame.*main"],
patterns=["frame.*foo", "frame.*std::__[0-9]*::__function", "frame.*main"],
)
self.expect(
"thread backtrace --unfiltered",
ordered=True,
patterns=["frame.*foo", "frame.*std::__1::__function", "frame.*main"],
patterns=["frame.*foo", "frame.*std::__[0-9]*::__function", "frame.*main"],
)

@add_test_categories(["libc++"])
Expand Down
5 changes: 5 additions & 0 deletions lldb/test/API/lang/cpp/std-invoke-recognizer/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CXX_SOURCES := main.cpp
USE_LIBCPP := 1
CXXFLAGS_EXTRAS := -std=c++17

include Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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 details of `std::invoke`"""
self.build()
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
self, "// break here", lldb.SBFileSpec("main.cpp")
)

while process.GetState() != lldb.eStateExited:
self.assertIn("print_num", thread.GetFrameAtIndex(0).GetFunctionName())
self.process.Continue()
# # 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())
40 changes: 40 additions & 0 deletions lldb/test/API/lang/cpp/std-invoke-recognizer/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#include <functional>
#include <iostream>

void print_num(int i) {
// break here
std::cout << i << '\n';
}

int add(int i, int j) {
// break here
return i + j;
}

struct PrintAdder {
PrintAdder(int num) : num_(num) {}
void operator()(int i) const {
// break here
std::cout << i << '\n';
}
void print_add(int i) const {
// break here
std::cout << num_ + i << '\n';
}
int num_;
};

int main() {
// Invoke a void-returning function
std::invoke(print_num, -9);

// Invoke a non-void-returning function
std::cout << std::invoke(add, 1, 10) << '\n';

// Invoke a member function
const PrintAdder foo(314159);
std::invoke(&PrintAdder::print_add, foo, 1);

// Invoke a function object
std::invoke(PrintAdder(12), 18);
}

0 comments on commit 1836531

Please sign in to comment.