| //===-- 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 <cstring> | |
| #include <iostream> | |
| #include <memory> | |
| #include "CPPLanguageRuntime.h" | |
| #include "CommandObjectCPlusPlus.h" | |
| #include "ItaniumABIRuntime.h" | |
| #include "VerboseTrapFrameRecognizer.h" | |
| #include "llvm/ADT/StringRef.h" | |
| #include "llvm/Support/Error.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/CompileUnit.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/StackFrameRecognizer.h" | |
| #include "lldb/Target/ThreadPlanRunToAddress.h" | |
| #include "lldb/Target/ThreadPlanStepInRange.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/Timer.h" | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| LLDB_PLUGIN_DEFINE_ADV(CPPLanguageRuntime, CPPRuntime) | |
| static ConstString g_this = ConstString("this"); | |
| // Artificial coroutine-related variables emitted by clang. | |
| static ConstString g_promise = ConstString("__promise"); | |
| static ConstString g_coro_frame = ConstString("__coro_frame"); | |
| char CPPLanguageRuntime::ID = 0; | |
| /// A frame recognizer that is installed to hide libc++ implementation | |
| /// details from the backtrace. | |
| class LibCXXFrameRecognizer : public StackFrameRecognizer { | |
| std::array<RegularExpression, 2> m_hidden_regex; | |
| RecognizedStackFrameSP m_hidden_frame; | |
| struct LibCXXHiddenFrame : public RecognizedStackFrame { | |
| bool ShouldHide() override { return true; } | |
| }; | |
| public: | |
| LibCXXFrameRecognizer() | |
| : m_hidden_regex{ | |
| // internal implementation details in the `std::` namespace | |
| // 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 | |
| // std::__2::__function::__policy_invoker<void (int, int)>::__call_impl[abi:ne200000]<std::__2::__function::__default_alloc_func<int (*)(int, int), int (int, int)>> | |
| // std::__1::__invoke[abi:ne200000]<void (*&)()> | |
| // std::__1::__invoke_void_return_wrapper<void, true>::__call[abi:ne200000]<void (*&)()> | |
| RegularExpression{R"(^std::__[^:]*::__)"}, | |
| // internal implementation details in the `std::ranges` namespace | |
| // std::__1::ranges::__sort::__sort_fn_impl[abi:ne200000]<std::__1::__wrap_iter<int*>, std::__1::__wrap_iter<int*>, bool (*)(int, int), std::__1::identity> | |
| RegularExpression{R"(^std::__[^:]*::ranges::__)"}, | |
| }, | |
| m_hidden_frame(new LibCXXHiddenFrame()) {} | |
| std::string GetName() override { return "libc++ frame recognizer"; } | |
| lldb::RecognizedStackFrameSP | |
| RecognizeFrame(lldb::StackFrameSP frame_sp) override { | |
| if (!frame_sp) | |
| return {}; | |
| const auto &sc = frame_sp->GetSymbolContext(lldb::eSymbolContextFunction); | |
| if (!sc.function) | |
| return {}; | |
| // Check if we have a regex match | |
| for (RegularExpression &r : m_hidden_regex) { | |
| if (!r.Execute(sc.function->GetNameNoArguments())) | |
| continue; | |
| // Only hide this frame if the immediate caller is also within libc++. | |
| lldb::ThreadSP thread_sp = frame_sp->GetThread(); | |
| if (!thread_sp) | |
| return {}; | |
| lldb::StackFrameSP parent_frame_sp = | |
| thread_sp->GetStackFrameAtIndex(frame_sp->GetFrameIndex() + 1); | |
| if (!parent_frame_sp) | |
| return {}; | |
| const auto &parent_sc = | |
| parent_frame_sp->GetSymbolContext(lldb::eSymbolContextFunction); | |
| if (!parent_sc.function) | |
| return {}; | |
| if (parent_sc.function->GetNameNoArguments().GetStringRef().starts_with( | |
| "std::")) | |
| return m_hidden_frame; | |
| } | |
| return {}; | |
| } | |
| }; | |
| CPPLanguageRuntime::CPPLanguageRuntime(Process *process) | |
| : LanguageRuntime(process) { | |
| if (process) { | |
| process->GetTarget().GetFrameRecognizerManager().AddRecognizer( | |
| StackFrameRecognizerSP(new LibCXXFrameRecognizer()), {}, | |
| std::make_shared<RegularExpression>("^std::__[^:]*::"), | |
| /*mangling_preference=*/Mangled::ePreferDemangledWithoutArguments, | |
| /*first_instruction_only=*/false); | |
| RegisterVerboseTrapFrameRecognizer(*process); | |
| } | |
| m_abi_runtimes.emplace_back(new ItaniumABIRuntime(process)); | |
| } | |
| bool CPPLanguageRuntime::IsAllowedRuntimeValue(ConstString name) { | |
| return name == g_this || name == g_promise || name == g_coro_frame; | |
| } | |
| llvm::Error CPPLanguageRuntime::GetObjectDescription(Stream &str, | |
| ValueObject &object) { | |
| // C++ has no generic way to do this. | |
| return llvm::createStringError("C++ does not support object descriptions"); | |
| } | |
| llvm::Error | |
| CPPLanguageRuntime::GetObjectDescription(Stream &str, Value &value, | |
| ExecutionContextScope *exe_scope) { | |
| // C++ has no generic way to do this. | |
| return llvm::createStringError("C++ does not support object descriptions"); | |
| } | |
| bool contains_lambda_identifier(llvm::StringRef &str_ref) { | |
| return str_ref.contains("$_") || str_ref.contains("'lambda'"); | |
| } | |
| CPPLanguageRuntime::LibCppStdFunctionCallableInfo | |
| line_entry_helper(Target &target, const SymbolContext &sc, Symbol *symbol, | |
| llvm::StringRef first_template_param_sref, bool has_invoke) { | |
| CPPLanguageRuntime::LibCppStdFunctionCallableInfo optional_info; | |
| Address address = sc.GetFunctionOrSymbolAddress(); | |
| Address addr; | |
| if (target.ResolveLoadAddress(address.GetCallableLoadAddress(&target), | |
| addr)) { | |
| LineEntry line_entry; | |
| addr.CalculateSymbolContextLineEntry(line_entry); | |
| if (contains_lambda_identifier(first_template_param_sref) || has_invoke) { | |
| // Case 1 and 2 | |
| optional_info.callable_case = lldb_private::CPPLanguageRuntime:: | |
| LibCppStdFunctionCallableCase::Lambda; | |
| } else { | |
| // Case 3 | |
| optional_info.callable_case = lldb_private::CPPLanguageRuntime:: | |
| LibCppStdFunctionCallableCase::CallableObject; | |
| } | |
| optional_info.callable_symbol = *symbol; | |
| optional_info.callable_line_entry = line_entry; | |
| optional_info.callable_address = addr; | |
| } | |
| return optional_info; | |
| } | |
| CPPLanguageRuntime::LibCppStdFunctionCallableInfo | |
| CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo( | |
| lldb::ValueObjectSP &valobj_sp) { | |
| LLDB_SCOPED_TIMER(); | |
| 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 objects 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("__f_")); | |
| if (member_f_) { | |
| ValueObjectSP sub_member_f_(member_f_->GetChildMemberWithName("__f_")); | |
| if (sub_member_f_) | |
| member_f_ = sub_member_f_; | |
| } | |
| if (!member_f_) | |
| return optional_info; | |
| lldb::addr_t member_f_pointer_value = member_f_->GetValueAsUnsigned(0); | |
| optional_info.member_f_pointer_value = member_f_pointer_value; | |
| if (!member_f_pointer_value) | |
| return optional_info; | |
| ExecutionContext exe_ctx(valobj_sp->GetExecutionContextRef()); | |
| Process *process = exe_ctx.GetProcessPtr(); | |
| if (process == nullptr) | |
| return optional_info; | |
| uint32_t address_size = process->GetAddressByteSize(); | |
| // First item pointed to by __f_ should be the pointer to the vtable for | |
| // a __base object. | |
| llvm::Expected<lldb::addr_t> vtable_address_or_err = | |
| process->ReadPointerFromMemory(member_f_pointer_value); | |
| if (!vtable_address_or_err) { | |
| llvm::consumeError(vtable_address_or_err.takeError()); | |
| return optional_info; | |
| } | |
| lldb::addr_t vtable_address = *vtable_address_or_err; | |
| ABISP abi_sp = process->GetABI(); | |
| if (abi_sp) | |
| vtable_address = abi_sp->FixCodeAddress(vtable_address); | |
| llvm::Expected<lldb::addr_t> vtable_address_first_entry_or_err = | |
| process->ReadPointerFromMemory(vtable_address + address_size); | |
| if (!vtable_address_first_entry_or_err) { | |
| llvm::consumeError(vtable_address_first_entry_or_err.takeError()); | |
| return optional_info; | |
| } | |
| lldb::addr_t vtable_address_first_entry = *vtable_address_first_entry_or_err; | |
| if (abi_sp) | |
| vtable_address_first_entry = | |
| abi_sp->FixCodeAddress(vtable_address_first_entry); | |
| lldb::addr_t address_after_vtable = member_f_pointer_value + address_size; | |
| // As commented above we may not have a function pointer but if we do we will | |
| // need it. | |
| llvm::Expected<lldb::addr_t> possible_function_address_or_err = | |
| process->ReadPointerFromMemory(address_after_vtable); | |
| if (!possible_function_address_or_err) { | |
| llvm::consumeError(possible_function_address_or_err.takeError()); | |
| return optional_info; | |
| } | |
| lldb::addr_t possible_function_address = *possible_function_address_or_err; | |
| if (abi_sp) | |
| possible_function_address = | |
| abi_sp->FixCodeAddress(possible_function_address); | |
| Target &target = process->GetTarget(); | |
| if (!target.HasLoadedSections()) | |
| return optional_info; | |
| Address vtable_first_entry_resolved; | |
| if (!target.ResolveLoadAddress(vtable_address_first_entry, | |
| vtable_first_entry_resolved)) | |
| return optional_info; | |
| Address vtable_addr_resolved; | |
| SymbolContext sc; | |
| Symbol *symbol = nullptr; | |
| if (!target.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().GetStringRef()); | |
| bool found_expected_start_string = | |
| vtable_name.starts_with("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 could see names such as: | |
| // main::$_0 | |
| // Bar::add_num2(int)::'lambda'(int) | |
| // Bar | |
| // | |
| // 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.ResolveLoadAddress(possible_function_address, | |
| function_address_resolved)) { | |
| target.GetImages().ResolveSymbolContextForAddress( | |
| function_address_resolved, eSymbolContextEverything, sc); | |
| symbol = sc.symbol; | |
| } | |
| // These conditions are used several times to simplify statements later on. | |
| bool has_invoke = | |
| (symbol ? symbol->GetName().GetStringRef().contains("__invoke") : false); | |
| auto calculate_symbol_context_helper = [](auto &t, | |
| SymbolContextList &sc_list) { | |
| SymbolContext sc; | |
| t->CalculateSymbolContext(&sc); | |
| sc_list.Append(sc); | |
| }; | |
| // Case 2 | |
| if (has_invoke) { | |
| SymbolContextList scl; | |
| calculate_symbol_context_helper(symbol, scl); | |
| return line_entry_helper(target, scl[0], symbol, first_template_parameter, | |
| has_invoke); | |
| } | |
| // Case 4 or 5 | |
| if (symbol && !symbol->GetName().GetStringRef().starts_with("vtable for") && | |
| !contains_lambda_identifier(first_template_parameter) && !has_invoke) { | |
| optional_info.callable_case = | |
| LibCppStdFunctionCallableCase::FreeOrMemberFunction; | |
| optional_info.callable_address = function_address_resolved; | |
| optional_info.callable_symbol = *symbol; | |
| return optional_info; | |
| } | |
| std::string func_to_match = first_template_parameter.str(); | |
| auto it = CallableLookupCache.find(func_to_match); | |
| if (it != CallableLookupCache.end()) | |
| return it->second; | |
| SymbolContextList scl; | |
| CompileUnit *vtable_cu = | |
| vtable_first_entry_resolved.CalculateSymbolContextCompileUnit(); | |
| llvm::StringRef name_to_use = func_to_match; | |
| // Case 3, we have a callable object instead of a lambda | |
| // | |
| // TODO | |
| // We currently don't support this case a callable object may have multiple | |
| // operator()() varying on const/non-const and number of arguments and we | |
| // don't have a way to currently distinguish them so we will bail out now. | |
| if (!contains_lambda_identifier(name_to_use)) | |
| return optional_info; | |
| if (vtable_cu && !has_invoke) { | |
| lldb::FunctionSP func_sp = | |
| vtable_cu->FindFunction([name_to_use](const FunctionSP &f) { | |
| auto name = f->GetName().GetStringRef(); | |
| if (name.starts_with(name_to_use) && name.contains("operator")) | |
| return true; | |
| return false; | |
| }); | |
| if (func_sp) { | |
| calculate_symbol_context_helper(func_sp, scl); | |
| } | |
| } | |
| if (symbol == nullptr) | |
| return optional_info; | |
| // Case 1 or 3 | |
| if (scl.GetSize() >= 1) { | |
| optional_info = line_entry_helper(target, scl[0], symbol, | |
| first_template_parameter, has_invoke); | |
| } | |
| CallableLookupCache[func_to_match] = 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->HasLoadedSections()) | |
| return ret_plan_sp; | |
| Address pc_addr_resolved; | |
| SymbolContext sc; | |
| Symbol *symbol; | |
| if (!target_sp->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.starts_with("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) { | |
| Address func_start_address = | |
| sc.function ? sc.function->GetAddress() : symbol->GetAddress(); | |
| lldb::addr_t func_start = | |
| func_start_address.GetLoadAddress(target_sp.get()); | |
| if (func_start == LLDB_INVALID_ADDRESS) | |
| return ret_plan_sp; | |
| // Advance past the prologue if we stopped there. | |
| uint32_t prologue_size = sc.function ? sc.function->GetPrologueByteSize() | |
| : symbol->GetPrologueByteSize(); | |
| if (curr_pc < func_start + prologue_size) { | |
| func_start_address.Slide(prologue_size); | |
| return std::make_shared<ThreadPlanRunToAddress>( | |
| thread, func_start_address, stop_others); | |
| } | |
| 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, std::string(), eOnlyThisThread, | |
| eLazyBoolYes, eLazyBoolYes); | |
| return ret_plan_sp; | |
| } | |
| } | |
| return ret_plan_sp; | |
| } | |
| bool CPPLanguageRuntime::IsSymbolARuntimeThunk(const Symbol &symbol) { | |
| llvm::StringRef mangled_name = | |
| symbol.GetMangled().GetMangledName().GetStringRef(); | |
| // Virtual function overriding from a non-virtual base use a "Th" prefix. | |
| // Virtual function overriding from a virtual base must use a "Tv" prefix. | |
| // Virtual function overriding thunks with covariant returns use a "Tc" | |
| // prefix. | |
| return mangled_name.starts_with("_ZTh") || mangled_name.starts_with("_ZTv") || | |
| mangled_name.starts_with("_ZTc"); | |
| } | |
| bool CPPLanguageRuntime::CouldHaveDynamicValue(ValueObject &in_value) { | |
| const bool check_cxx = true; | |
| const bool check_objc = false; | |
| return in_value.GetCompilerType().IsPossibleDynamicType(nullptr, check_cxx, | |
| check_objc); | |
| } | |
| bool CPPLanguageRuntime::GetDynamicTypeAndAddress( | |
| ValueObject &in_value, lldb::DynamicValueType use_dynamic, | |
| TypeAndOrName &class_type_or_name, Address &dynamic_address, | |
| Value::ValueType &value_type, llvm::ArrayRef<uint8_t> &local_buffer) { | |
| class_type_or_name.Clear(); | |
| value_type = Value::ValueType::Scalar; | |
| if (!CouldHaveDynamicValue(in_value)) | |
| return false; | |
| llvm::Expected<VTableInfoEntry> entry = | |
| GetVTableInfoEntry(in_value, /*check_type=*/false); | |
| if (!entry) { | |
| llvm::consumeError(entry.takeError()); | |
| return false; | |
| } | |
| return entry->runtime->GetDynamicTypeAndAddress( | |
| in_value, use_dynamic, entry->info, class_type_or_name, dynamic_address); | |
| } | |
| TypeAndOrName | |
| CPPLanguageRuntime::FixUpDynamicType(const TypeAndOrName &type_and_or_name, | |
| ValueObject &static_value) { | |
| CompilerType static_type(static_value.GetCompilerType()); | |
| Flags static_type_flags(static_type.GetTypeInfo()); | |
| TypeAndOrName ret(type_and_or_name); | |
| if (type_and_or_name.HasType()) { | |
| // The type will always be the type of the dynamic object. If our parent's | |
| // type was a pointer, then our type should be a pointer to the type of the | |
| // dynamic object. If a reference, then the original type should be | |
| // okay... | |
| CompilerType orig_type = type_and_or_name.GetCompilerType(); | |
| CompilerType corrected_type = orig_type; | |
| if (static_type_flags.AllSet(eTypeIsPointer)) | |
| corrected_type = orig_type.GetPointerType(); | |
| else if (static_type_flags.AllSet(eTypeIsReference)) | |
| corrected_type = orig_type.GetLValueReferenceType(); | |
| ret.SetCompilerType(corrected_type); | |
| } else { | |
| // If we are here we need to adjust our dynamic type name to include the | |
| // correct & or * symbol | |
| std::string corrected_name(type_and_or_name.GetName().GetCString()); | |
| if (static_type_flags.AllSet(eTypeIsPointer)) | |
| corrected_name.append(" *"); | |
| else if (static_type_flags.AllSet(eTypeIsReference)) | |
| corrected_name.append(" &"); | |
| // the parent type should be a correctly pointer'ed or referenc'ed type | |
| ret.SetCompilerType(static_type); | |
| ret.SetName(corrected_name.c_str()); | |
| } | |
| return ret; | |
| } | |
| LanguageRuntime * | |
| CPPLanguageRuntime::CreateInstance(Process *process, | |
| lldb::LanguageType language) { | |
| if (language == eLanguageTypeC_plus_plus || | |
| language == eLanguageTypeC_plus_plus_03 || | |
| language == eLanguageTypeC_plus_plus_11 || | |
| language == eLanguageTypeC_plus_plus_14) | |
| return new CPPLanguageRuntime(process); | |
| else | |
| return nullptr; | |
| } | |
| void CPPLanguageRuntime::Initialize() { | |
| PluginManager::RegisterPlugin( | |
| GetPluginNameStatic(), "C++ language runtime", CreateInstance, | |
| [](CommandInterpreter &interpreter) -> lldb::CommandObjectSP { | |
| return CommandObjectSP(new CommandObjectCPlusPlus(interpreter)); | |
| }); | |
| } | |
| void CPPLanguageRuntime::Terminate() { | |
| PluginManager::UnregisterPlugin(CreateInstance); | |
| } | |
| llvm::Expected<LanguageRuntime::VTableInfo> | |
| CPPLanguageRuntime::GetVTableInfo(ValueObject &in_value, bool check_type) { | |
| llvm::Expected<VTableInfoEntry> entry = | |
| GetVTableInfoEntry(in_value, check_type); | |
| if (!entry) | |
| return entry.takeError(); | |
| return entry->info; | |
| } | |
| BreakpointResolverSP | |
| CPPLanguageRuntime::CreateExceptionResolver(const BreakpointSP &bkpt, | |
| bool catch_bp, bool throw_bp) { | |
| return CreateExceptionResolver(bkpt, catch_bp, throw_bp, false); | |
| } | |
| BreakpointResolverSP | |
| CPPLanguageRuntime::CreateExceptionResolver(const BreakpointSP &bkpt, | |
| bool catch_bp, bool throw_bp, | |
| bool for_expressions) { | |
| std::vector<const char *> exception_names; | |
| for (const auto &runtime : m_abi_runtimes) | |
| runtime->AppendExceptionBreakpointFunctions(exception_names, catch_bp, | |
| throw_bp, for_expressions); | |
| BreakpointResolverSP resolver_sp(new BreakpointResolverName( | |
| bkpt, exception_names.data(), exception_names.size(), | |
| eFunctionNameTypeBase, eLanguageTypeUnknown, 0, eLazyBoolNo)); | |
| return resolver_sp; | |
| } | |
| lldb::SearchFilterSP CPPLanguageRuntime::CreateExceptionSearchFilter() { | |
| Target &target = m_process->GetTarget(); | |
| FileSpecList filter_modules; | |
| for (const auto &runtime : m_abi_runtimes) | |
| runtime->AppendExceptionBreakpointFilterModules(filter_modules, target); | |
| return target.GetSearchFilterForModuleList(&filter_modules); | |
| } | |
| lldb::BreakpointSP CPPLanguageRuntime::CreateExceptionBreakpoint( | |
| bool catch_bp, bool throw_bp, bool for_expressions, bool is_internal) { | |
| Target &target = m_process->GetTarget(); | |
| FileSpecList filter_modules; | |
| BreakpointResolverSP exception_resolver_sp = | |
| CreateExceptionResolver(nullptr, catch_bp, throw_bp, for_expressions); | |
| SearchFilterSP filter_sp(CreateExceptionSearchFilter()); | |
| const bool hardware = false; | |
| const bool resolve_indirect_functions = false; | |
| return target.CreateBreakpoint(filter_sp, exception_resolver_sp, is_internal, | |
| hardware, resolve_indirect_functions); | |
| } | |
| void CPPLanguageRuntime::SetExceptionBreakpoints() { | |
| if (!m_process) | |
| return; | |
| const bool catch_bp = false; | |
| const bool throw_bp = true; | |
| const bool is_internal = true; | |
| const bool for_expressions = true; | |
| // For the exception breakpoints set by the Expression parser, we'll be a | |
| // little more aggressive and stop at exception allocation as well. | |
| if (m_cxx_exception_bp_sp) { | |
| m_cxx_exception_bp_sp->SetEnabled(true); | |
| } else { | |
| m_cxx_exception_bp_sp = CreateExceptionBreakpoint( | |
| catch_bp, throw_bp, for_expressions, is_internal); | |
| if (m_cxx_exception_bp_sp) | |
| m_cxx_exception_bp_sp->SetBreakpointKind("c++ exception"); | |
| } | |
| } | |
| void CPPLanguageRuntime::ClearExceptionBreakpoints() { | |
| if (!m_process) | |
| return; | |
| if (m_cxx_exception_bp_sp) { | |
| m_cxx_exception_bp_sp->SetEnabled(false); | |
| } | |
| } | |
| bool CPPLanguageRuntime::ExceptionBreakpointsAreSet() { | |
| return m_cxx_exception_bp_sp && m_cxx_exception_bp_sp->IsEnabled(); | |
| } | |
| bool CPPLanguageRuntime::ExceptionBreakpointsExplainStop( | |
| lldb::StopInfoSP stop_reason) { | |
| if (!m_process) | |
| return false; | |
| if (!stop_reason || stop_reason->GetStopReason() != eStopReasonBreakpoint) | |
| return false; | |
| uint64_t break_site_id = stop_reason->GetValue(); | |
| return m_process->GetBreakpointSiteList().StopPointSiteContainsBreakpoint( | |
| break_site_id, m_cxx_exception_bp_sp->GetID()); | |
| } | |
| lldb::ValueObjectSP | |
| CPPLanguageRuntime::GetExceptionObjectForThread(lldb::ThreadSP thread_sp) { | |
| for (const auto &runtime : m_abi_runtimes) { | |
| ValueObjectSP valobj = runtime->GetExceptionObjectForThread(thread_sp); | |
| if (valobj) | |
| return valobj; | |
| } | |
| return {}; | |
| } | |
| static llvm::Error TypeHasVTable(CompilerType type) { | |
| // Check to make sure the class has a vtable. | |
| CompilerType original_type = type; | |
| if (type.IsPointerOrReferenceType()) { | |
| CompilerType pointee_type = type.GetPointeeType(); | |
| if (pointee_type) | |
| type = pointee_type; | |
| } | |
| // Make sure this is a class or a struct first by checking the type class | |
| // bitfield that gets returned. | |
| if ((type.GetTypeClass() & (eTypeClassStruct | eTypeClassClass)) == 0) { | |
| return llvm::createStringError( | |
| std::errc::invalid_argument, | |
| "type \"%s\" is not a class or struct or a pointer to one", | |
| original_type.GetTypeName().AsCString("<invalid>")); | |
| } | |
| // Check if the type has virtual functions by asking it if it is polymorphic. | |
| if (!type.IsPolymorphicClass()) { | |
| return llvm::createStringError(std::errc::invalid_argument, | |
| "type \"%s\" doesn't have a vtable", | |
| type.GetTypeName().AsCString("<invalid>")); | |
| } | |
| return llvm::Error::success(); | |
| } | |
| // This function can accept both pointers or references to classes as well as | |
| // instances of classes. If you are using this function during dynamic type | |
| // detection, only valid ValueObjects that return true to | |
| // CouldHaveDynamicValue(...) should call this function and \a check_type | |
| // should be set to false. This function is also used by ValueObjectVTable | |
| // and is can pass in instances of classes which is not suitable for dynamic | |
| // type detection, these cases should pass true for \a check_type. | |
| llvm::Expected<CPPLanguageRuntime::VTableInfoEntry> | |
| CPPLanguageRuntime::GetVTableInfoEntry(ValueObject &in_value, bool check_type) { | |
| CompilerType type = in_value.GetCompilerType(); | |
| if (check_type) { | |
| if (llvm::Error err = TypeHasVTable(type)) | |
| return std::move(err); | |
| } | |
| ExecutionContext exe_ctx(in_value.GetExecutionContextRef()); | |
| Process *process = exe_ctx.GetProcessPtr(); | |
| if (process == nullptr) | |
| return llvm::createStringError(std::errc::invalid_argument, | |
| "invalid process"); | |
| auto [original_ptr, address_type] = | |
| type.IsPointerOrReferenceType() | |
| ? in_value.GetPointerValue() | |
| : in_value.GetAddressOf(/*scalar_is_load_address=*/true); | |
| if (original_ptr == LLDB_INVALID_ADDRESS || address_type != eAddressTypeLoad) | |
| return llvm::createStringError(std::errc::invalid_argument, | |
| "failed to get the address of the value"); | |
| llvm::Expected<lldb::addr_t> vtable_load_addr_or_err = | |
| process->ReadPointerFromMemory(original_ptr); | |
| if (!vtable_load_addr_or_err) | |
| return vtable_load_addr_or_err.takeError(); | |
| lldb::addr_t vtable_load_addr = *vtable_load_addr_or_err; | |
| // The vtable load address can have authentication bits with | |
| // AArch64 targets on Darwin. | |
| vtable_load_addr = process->FixDataAddress(vtable_load_addr); | |
| // Find the symbol that contains the "vtable_load_addr" address | |
| Address vtable_addr; | |
| if (!process->GetTarget().ResolveLoadAddress(vtable_load_addr, vtable_addr)) | |
| return llvm::createStringError(std::errc::invalid_argument, | |
| "failed to resolve vtable pointer 0x%" PRIx64 | |
| "to a section", | |
| vtable_load_addr); | |
| // Check our cache first to see if we already have this info | |
| { | |
| auto vtable_info_map = m_vtable_info_map.Lock(); | |
| auto pos = vtable_info_map->find(vtable_addr); | |
| if (pos != vtable_info_map->end()) | |
| return pos->second; | |
| } | |
| Symbol *symbol = vtable_addr.CalculateSymbolContextSymbol(); | |
| if (symbol == nullptr) | |
| return llvm::createStringError(std::errc::invalid_argument, | |
| "no symbol found for 0x%" PRIx64, | |
| vtable_load_addr); | |
| Mangled &mangled = symbol->GetMangled(); | |
| Log *log = GetLog(LLDBLog::Object); | |
| for (const auto &runtime : m_abi_runtimes) { | |
| if (runtime->IsVTableSymbol(symbol->GetMangled())) { | |
| LLDB_LOG(log, "{0:x16} ({1}): symbol='{2}' matches {3}", original_ptr, | |
| in_value.GetTypeName(), mangled.GetDemangledName(), | |
| runtime->GetName()); | |
| VTableInfoEntry entry{ | |
| /*info=*/VTableInfo{vtable_addr, symbol}, | |
| /*runtime=*/runtime.get(), | |
| }; | |
| (*m_vtable_info_map.Lock())[vtable_addr] = entry; | |
| return entry; | |
| } | |
| } | |
| return llvm::createStringError(std::errc::invalid_argument, | |
| "symbol found that contains 0x%" PRIx64 | |
| " is not a vtable symbol", | |
| vtable_load_addr); | |
| } |