diff options
Diffstat (limited to 'lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp')
| -rw-r--r-- | lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp | 353 | 
1 files changed, 353 insertions, 0 deletions
diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp new file mode 100644 index 0000000000000..f38014505a8b7 --- /dev/null +++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp @@ -0,0 +1,353 @@ +//===-- CPPLanguageRuntime.cpp +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include <string.h> + +#include <memory> + +#include "CPPLanguageRuntime.h" + +#include "llvm/ADT/StringRef.h" + +#include "lldb/Symbol/Block.h" +#include "lldb/Symbol/Variable.h" +#include "lldb/Symbol/VariableList.h" + +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/UniqueCStringMap.h" +#include "lldb/Symbol/ClangASTContext.h" +#include "lldb/Target/ABI.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/RegisterContext.h" +#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; + +static ConstString g_this = ConstString("this"); + +char CPPLanguageRuntime::ID = 0; + +// Destructor +CPPLanguageRuntime::~CPPLanguageRuntime() {} + +CPPLanguageRuntime::CPPLanguageRuntime(Process *process) +    : LanguageRuntime(process) {} + +bool CPPLanguageRuntime::IsWhitelistedRuntimeValue(ConstString name) { +  return name == g_this; +} + +bool CPPLanguageRuntime::GetObjectDescription(Stream &str, +                                              ValueObject &object) { +  // C++ has no generic way to do this. +  return false; +} + +bool CPPLanguageRuntime::GetObjectDescription( +    Stream &str, Value &value, ExecutionContextScope *exe_scope) { +  // C++ has no generic way to do this. +  return false; +} + +CPPLanguageRuntime::LibCppStdFunctionCallableInfo +CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo( +    lldb::ValueObjectSP &valobj_sp) { +  LibCppStdFunctionCallableInfo optional_info; + +  if (!valobj_sp) +    return optional_info; + +  // Member __f_ has type __base*, the contents of which will hold: +  // 1) a vtable entry which may hold type information needed to discover the +  //    lambda being called +  // 2) possibly hold a pointer to the callable object +  // e.g. +  // +  // (lldb) frame var -R  f_display +  // (std::__1::function<void (int)>) f_display = { +  //  __buf_ = { +  //  … +  // } +  //  __f_ = 0x00007ffeefbffa00 +  // } +  // (lldb) memory read -fA 0x00007ffeefbffa00 +  // 0x7ffeefbffa00: ... `vtable for std::__1::__function::__func<void (*) ... +  // 0x7ffeefbffa08: ... `print_num(int) at std_function_cppreference_exam ... +  // +  // We will be handling five cases below, std::function is wrapping: +  // +  // 1) a lambda we know at compile time. We will obtain the name of the lambda +  //    from the first template pameter from __func's vtable. We will look up +  //    the lambda's operator()() and obtain the line table entry. +  // 2) a lambda we know at runtime. A pointer to the lambdas __invoke method +  //    will be stored after the vtable. We will obtain the lambdas name from +  //    this entry and lookup operator()() and obtain the line table entry. +  // 3) a callable object via operator()(). We will obtain the name of the +  //    object from the first template parameter from __func's vtable. We will +  //    look up the objectc operator()() and obtain the line table entry. +  // 4) a member function. A pointer to the function will stored after the +  //    we will obtain the name from this pointer. +  // 5) a free function. A pointer to the function will stored after the vtable +  //    we will obtain the name from this pointer. +  ValueObjectSP member__f_( +      valobj_sp->GetChildMemberWithName(ConstString("__f_"), true)); + +  if (member__f_) { +    ValueObjectSP sub_member__f_( +       member__f_->GetChildMemberWithName(ConstString("__f_"), true)); + +    if (sub_member__f_) +        member__f_ = sub_member__f_; +  } + +  lldb::addr_t member__f_pointer_value = member__f_->GetValueAsUnsigned(0); + +  optional_info.member__f_pointer_value = member__f_pointer_value; + +  ExecutionContext exe_ctx(valobj_sp->GetExecutionContextRef()); +  Process *process = exe_ctx.GetProcessPtr(); + +  if (process == nullptr) +    return optional_info; + +  uint32_t address_size = process->GetAddressByteSize(); +  Status status; + +  // First item pointed to by __f_ should be the pointer to the vtable for +  // a __base object. +  lldb::addr_t vtable_address = +      process->ReadPointerFromMemory(member__f_pointer_value, status); + +  if (status.Fail()) +    return optional_info; + +  lldb::addr_t address_after_vtable = member__f_pointer_value + address_size; +  // As commened above we may not have a function pointer but if we do we will +  // need it. +  lldb::addr_t possible_function_address = +      process->ReadPointerFromMemory(address_after_vtable, status); + +  if (status.Fail()) +    return optional_info; + +  Target &target = process->GetTarget(); + +  if (target.GetSectionLoadList().IsEmpty()) +    return optional_info; + +  Address vtable_addr_resolved; +  SymbolContext sc; +  Symbol *symbol; + +  if (!target.GetSectionLoadList().ResolveLoadAddress(vtable_address, +                                                      vtable_addr_resolved)) +    return optional_info; + +  target.GetImages().ResolveSymbolContextForAddress( +      vtable_addr_resolved, eSymbolContextEverything, sc); +  symbol = sc.symbol; + +  if (symbol == nullptr) +    return optional_info; + +  llvm::StringRef vtable_name(symbol->GetName().GetCString()); +  bool found_expected_start_string = +      vtable_name.startswith("vtable for std::__1::__function::__func<"); + +  if (!found_expected_start_string) +    return optional_info; + +  // Given case 1 or 3 we have a vtable name, we are want to extract the first +  // template parameter +  // +  //  ... __func<main::$_0, std::__1::allocator<main::$_0> ... +  //             ^^^^^^^^^ +  // +  // 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(','); + +  llvm::StringRef first_template_parameter = +      vtable_name.slice(first_open_angle_bracket, first_comma); + +  Address function_address_resolved; + +  // Setup for cases 2, 4 and 5 we have a pointer to a function after the +  // vtable. We will use a process of elimination to drop through each case +  // and obtain the data we need. +  if (target.GetSectionLoadList().ResolveLoadAddress( +          possible_function_address, function_address_resolved)) { +    target.GetImages().ResolveSymbolContextForAddress( +        function_address_resolved, eSymbolContextEverything, sc); +    symbol = sc.symbol; +  } + +  auto get_name = [&first_template_parameter, &symbol]() { +    // Given case 1: +    // +    //    main::$_0 +    // +    // we want to append ::operator()() +    if (first_template_parameter.contains("$_")) +      return llvm::Regex::escape(first_template_parameter.str()) + +             R"(::operator\(\)\(.*\))"; + +    if (symbol != nullptr && +        symbol->GetName().GetStringRef().contains("__invoke")) { + +      llvm::StringRef symbol_name = symbol->GetName().GetStringRef(); +      size_t pos2 = symbol_name.find_last_of(':'); + +      // Given case 2: +      // +      //    main::$_1::__invoke(...) +      // +      // We want to slice off __invoke(...) and append operator()() +      std::string lambda_operator = +          llvm::Regex::escape(symbol_name.slice(0, pos2 + 1).str()) + +          R"(operator\(\)\(.*\))"; + +      return lambda_operator; +    } + +    // Case 3 +    return first_template_parameter.str() + R"(::operator\(\)\(.*\))"; +    ; +  }; + +  std::string func_to_match = get_name(); + +  SymbolContextList scl; + +  target.GetImages().FindSymbolsMatchingRegExAndType( +      RegularExpression{R"(^)" + func_to_match}, eSymbolTypeAny, scl); + +  // Case 1,2 or 3 +  if (scl.GetSize() >= 1) { +    SymbolContext sc2 = scl[0]; + +    AddressRange range; +    sc2.GetAddressRange(eSymbolContextEverything, 0, false, range); + +    Address address = range.GetBaseAddress(); + +    Address addr; +    if (target.ResolveLoadAddress(address.GetCallableLoadAddress(&target), +                                  addr)) { +      LineEntry line_entry; +      addr.CalculateSymbolContextLineEntry(line_entry); + +      if (first_template_parameter.contains("$_") || +          (symbol != nullptr && +           symbol->GetName().GetStringRef().contains("__invoke"))) { +        // Case 1 and 2 +        optional_info.callable_case = LibCppStdFunctionCallableCase::Lambda; +      } else { +        // Case 3 +        optional_info.callable_case = +            LibCppStdFunctionCallableCase::CallableObject; +      } + +      optional_info.callable_symbol = *symbol; +      optional_info.callable_line_entry = line_entry; +      optional_info.callable_address = addr; +      return optional_info; +    } +  } + +  // Case 4 or 5 +  if (symbol && !symbol->GetName().GetStringRef().startswith("vtable for")) { +    optional_info.callable_case = +        LibCppStdFunctionCallableCase::FreeOrMemberFunction; +    optional_info.callable_address = function_address_resolved; +    optional_info.callable_symbol = *symbol; + +    return optional_info; +  } + +  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(g_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 = std::make_shared<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 = std::make_shared<ThreadPlanStepInRange>( +          thread, range_of_curr_func, sc, eOnlyThisThread, eLazyBoolYes, +          eLazyBoolYes); +      return ret_plan_sp; +    } +  } + +  return ret_plan_sp; +}  | 
