Index: include/lldb/Target/CPPLanguageRuntime.h =================================================================== --- include/lldb/Target/CPPLanguageRuntime.h +++ include/lldb/Target/CPPLanguageRuntime.h @@ -56,6 +56,19 @@ bool GetObjectDescription(Stream &str, Value &value, ExecutionContextScope *exe_scope) override; + /// Obtain a ThreadPlan to get us into C++ constructs such as std::function. + /// + /// @param[in] thread + /// Curent thrad of execution. + /// + /// @param[in] stop_others + /// True if other threads should pause during execution. + /// + /// @return + /// A ThreadPlan Shared pointer + lldb::ThreadPlanSP GetStepThroughTrampolinePlan(Thread &thread, + bool stop_others); + protected: //------------------------------------------------------------------ // Classes that inherit from CPPLanguageRuntime can see and modify these Index: packages/Python/lldbsuite/test/lang/cpp/std-function-step-into-callable/Makefile =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/lang/cpp/std-function-step-into-callable/Makefile @@ -0,0 +1,7 @@ +LEVEL = ../../../make + +CXX_SOURCES := main.cpp +CXXFLAGS += -std=c++11 +USE_LIBCPP := 1 + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/lang/cpp/std-function-step-into-callable/TestStdFunctionStepIntoCallable.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/lang/cpp/std-function-step-into-callable/TestStdFunctionStepIntoCallable.py @@ -0,0 +1,71 @@ +""" +Test stepping into std::function +""" + +from __future__ import print_function + + +import lldb +import sys +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class LibCxxFunctionTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + NO_DEBUG_INFO_TESTCASE = True + + @add_test_categories(["libc++"]) + def test(self): + """Test that std::function as defined by libc++ is correctly printed by LLDB""" + self.build() + + self.main_source = "main.cpp" + self.main_source_spec = lldb.SBFileSpec(self.main_source) + self.source_foo_line = line_number( + self.main_source, '// Source foo start line') + self.source_lambda_f2_line = line_number( + self.main_source, '// Source lambda used by f2 start line') + self.source_lambda_f3_line = line_number( + self.main_source, '// Source lambda used by f3 start line') + self.source_bar_operator_line = line_number( + self.main_source, '// Source Bar::operator()() start line') + self.source_bar_add_num_line = line_number( + self.main_source, '// Source Bar::add_num start line') + self.source_main_invoking_f1 = line_number( + self.main_source, '// Source main invoking f1') + + (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint( + self, "// Set break point at this line.", self.main_source_spec) + + thread.StepInto() + self.assertEqual( thread.GetFrameAtIndex(0).GetLineEntry().GetLine(), self.source_main_invoking_f1 ) ; + self.assertEqual( thread.GetFrameAtIndex(0).GetLineEntry().GetFileSpec().GetFilename(), self.main_source) ; + + thread.StepInto() + self.assertEqual( thread.GetFrameAtIndex(0).GetLineEntry().GetLine(), self.source_foo_line ) ; + self.assertEqual( thread.GetFrameAtIndex(0).GetLineEntry().GetFileSpec().GetFilename(), self.main_source) ; + process.Continue() + + thread.StepInto() + self.assertEqual( thread.GetFrameAtIndex(0).GetLineEntry().GetLine(), self.source_lambda_f2_line ) ; + self.assertEqual( thread.GetFrameAtIndex(0).GetLineEntry().GetFileSpec().GetFilename(), self.main_source) ; + process.Continue() + + thread.StepInto() + self.assertEqual( thread.GetFrameAtIndex(0).GetLineEntry().GetLine(), self.source_lambda_f3_line ) ; + self.assertEqual( thread.GetFrameAtIndex(0).GetLineEntry().GetFileSpec().GetFilename(), self.main_source) ; + process.Continue() + + thread.StepInto() + self.assertEqual( thread.GetFrameAtIndex(0).GetLineEntry().GetLine(), self.source_bar_operator_line ) ; + self.assertEqual( thread.GetFrameAtIndex(0).GetLineEntry().GetFileSpec().GetFilename(), self.main_source) ; + process.Continue() + + thread.StepInto() + self.assertEqual( thread.GetFrameAtIndex(0).GetLineEntry().GetLine(), self.source_bar_add_num_line ) ; + self.assertEqual( thread.GetFrameAtIndex(0).GetLineEntry().GetFileSpec().GetFilename(), self.main_source) ; + process.Continue() Index: packages/Python/lldbsuite/test/lang/cpp/std-function-step-into-callable/main.cpp =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/lang/cpp/std-function-step-into-callable/main.cpp @@ -0,0 +1,38 @@ +#include + +int foo(int x, int y) { + return x + y - 1; // Source foo start line +} + +struct Bar { + int operator()() { + return 66 ; // Source Bar::operator()() start line + } + int add_num(int i) const { return i + 3 ; } // Source Bar::add_num start line + int num_ = 0 ; +} ; + +int main (int argc, char *argv[]) +{ + int acc = 42; + std::function f1 = foo; + std::function f2 = [acc,f1] (int x) -> int { + return x+f1(acc,x); // Source lambda used by f2 start line + }; + + auto f = [](int x, int y) { return x + y; }; // Source lambda used by f3 start line + auto g = [](int x, int y) { return x * y; } ; + std::function f3 = argc %2 ? f : g ; + + Bar bar1 ; + std::function f4( bar1 ) ; + std::function f5 = &Bar::add_num; + std::function f_mem = &Bar::num_; + + return f_mem(bar1) + // Set break point at this line. + f1(acc,acc) + // Source main invoking f1 + f2(acc) + // Set break point at this line. + f3(acc+1,acc+2) + // Set break point at this line. + f4() + // Set break point at this line. + f5(bar1, 10); // Set break point at this line. +} Index: source/Target/CPPLanguageRuntime.cpp =================================================================== --- source/Target/CPPLanguageRuntime.cpp +++ source/Target/CPPLanguageRuntime.cpp @@ -26,6 +26,7 @@ #include "lldb/Target/SectionLoadList.h" #include "lldb/Target/StackFrame.h" #include "lldb/Target/ThreadPlanRunToAddress.h" +#include "lldb/Target/ThreadPlanStepInRange.h" using namespace lldb; using namespace lldb_private; @@ -158,7 +159,6 @@ // We do this by find the first < and , and extracting in between. // // This covers the case of the lambda known at compile time. - // size_t first_open_angle_bracket = vtable_name.find('<') + 1; size_t first_comma = vtable_name.find_first_of(','); @@ -262,3 +262,76 @@ return optional_info; } + +lldb::ThreadPlanSP +CPPLanguageRuntime::GetStepThroughTrampolinePlan(Thread &thread, + bool stop_others) { + ThreadPlanSP ret_plan_sp; + + lldb::addr_t curr_pc = thread.GetRegisterContext()->GetPC(); + + TargetSP target_sp(thread.CalculateTarget()); + + if (target_sp->GetSectionLoadList().IsEmpty()) + return ret_plan_sp; + + Address pc_addr_resolved; + SymbolContext sc; + Symbol *symbol; + + if (!target_sp->GetSectionLoadList().ResolveLoadAddress(curr_pc, + pc_addr_resolved)) + return ret_plan_sp; + + target_sp->GetImages().ResolveSymbolContextForAddress( + pc_addr_resolved, eSymbolContextEverything, sc); + symbol = sc.symbol; + + if (symbol == nullptr) + return ret_plan_sp; + + llvm::StringRef function_name(symbol->GetName().GetCString()); + + // Handling the case where we are attempting to step into std::function. + // The behavior will be that we will attempt to obtain the wrapped + // callable via FindLibCppStdFunctionCallableInfo() and if we find it we + // will return a ThreadPlanRunToAddress to the callable. Therefore we will + // step into the wrapped callable. + // + bool found_expected_start_string = + function_name.startswith("std::__1::function<"); + + if (!found_expected_start_string) + return ret_plan_sp; + + AddressRange range_of_curr_func; + sc.GetAddressRange(eSymbolContextEverything, 0, false, range_of_curr_func); + + StackFrameSP frame = thread.GetStackFrameAtIndex(0); + + if (frame) { + ValueObjectSP value_sp = frame->FindVariable(ConstString("this")); + + CPPLanguageRuntime::LibCppStdFunctionCallableInfo callable_info = + FindLibCppStdFunctionCallableInfo(value_sp); + + if (callable_info.callable_case != LibCppStdFunctionCallableCase::Invalid && + value_sp->GetValueIsValid()) { + // We found the std::function wrapped callable and we have its address. + // We now create a ThreadPlan to run to the callable. + ret_plan_sp.reset(new ThreadPlanRunToAddress( + thread, callable_info.callable_address, stop_others)); + return ret_plan_sp; + } else { + // We are in std::function but we could not obtain the callable. + // We create a ThreadPlan to keep stepping through using the address range + // of the current function. + ret_plan_sp.reset(new ThreadPlanStepInRange(thread, range_of_curr_func, + sc, eOnlyThisThread, + eLazyBoolYes, eLazyBoolYes)); + return ret_plan_sp; + } + } + + return ret_plan_sp; +} Index: source/Target/ThreadPlanStepThrough.cpp =================================================================== --- source/Target/ThreadPlanStepThrough.cpp +++ source/Target/ThreadPlanStepThrough.cpp @@ -13,6 +13,7 @@ // Project includes #include "lldb/Target/ThreadPlanStepThrough.h" #include "lldb/Breakpoint/Breakpoint.h" +#include "lldb/Target/CPPLanguageRuntime.h" #include "lldb/Target/DynamicLoader.h" #include "lldb/Target/ObjCLanguageRuntime.h" #include "lldb/Target/Process.h" @@ -95,6 +96,15 @@ if (objc_runtime) m_sub_plan_sp = objc_runtime->GetStepThroughTrampolinePlan(m_thread, m_stop_others); + + CPPLanguageRuntime *cpp_runtime = + m_thread.GetProcess()->GetCPPLanguageRuntime(); + + // If the ObjC runtime did not provide us with a step though plan then if we + // have it check the C++ runtime for a step though plan. + if (!m_sub_plan_sp.get() && cpp_runtime) + m_sub_plan_sp = + cpp_runtime->GetStepThroughTrampolinePlan(m_thread, m_stop_others); } Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_STEP));