Skip to content

Commit

Permalink
Adding support to step into the callable wrapped by libc++ std::function
Browse files Browse the repository at this point in the history
rdar://problem/14365983

Differential Revision: https://reviews.llvm.org/D52851

llvm-svn: 344371
  • Loading branch information
shafik committed Oct 12, 2018
1 parent e2441bd commit aa30268
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 1 deletion.
13 changes: 13 additions & 0 deletions lldb/include/lldb/Target/CPPLanguageRuntime.h
Expand Up @@ -56,6 +56,19 @@ class CPPLanguageRuntime : public LanguageRuntime {
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
Expand Down
@@ -0,0 +1,7 @@
LEVEL = ../../../make

CXX_SOURCES := main.cpp
CXXFLAGS += -std=c++11
USE_LIBCPP := 1

include $(LEVEL)/Makefile.rules
@@ -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()
@@ -0,0 +1,38 @@
#include <functional>

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<int (int,int)> f1 = foo;
std::function<int (int)> 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<int (int,int)> f3 = argc %2 ? f : g ;

Bar bar1 ;
std::function<int ()> f4( bar1 ) ;
std::function<int (const Bar&, int)> f5 = &Bar::add_num;
std::function<int(Bar const&)> 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.
}
75 changes: 74 additions & 1 deletion lldb/source/Target/CPPLanguageRuntime.cpp
Expand Up @@ -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;
Expand Down Expand Up @@ -158,7 +159,6 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
// 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(',');

Expand Down Expand Up @@ -262,3 +262,76 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(

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;
}
10 changes: 10 additions & 0 deletions lldb/source/Target/ThreadPlanStepThrough.cpp
Expand Up @@ -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"
Expand Down Expand Up @@ -95,6 +96,15 @@ void ThreadPlanStepThrough::LookForPlanToStepThroughFromCurrentPC() {
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));
Expand Down

0 comments on commit aa30268

Please sign in to comment.