diff options
Diffstat (limited to 'contrib/llvm-project/lldb/source/Target')
72 files changed, 40060 insertions, 0 deletions
diff --git a/contrib/llvm-project/lldb/source/Target/ABI.cpp b/contrib/llvm-project/lldb/source/Target/ABI.cpp new file mode 100644 index 000000000000..110b5c86fc42 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ABI.cpp @@ -0,0 +1,313 @@ +//===-- ABI.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 "lldb/Target/ABI.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/Value.h" +#include "lldb/Core/ValueObjectConstResult.h" +#include "lldb/Expression/ExpressionVariable.h" +#include "lldb/Symbol/CompilerType.h" +#include "lldb/Symbol/TypeSystem.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "llvm/MC/TargetRegistry.h" +#include <cctype> + +using namespace lldb; +using namespace lldb_private; + +ABISP +ABI::FindPlugin(lldb::ProcessSP process_sp, const ArchSpec &arch) { + ABISP abi_sp; + ABICreateInstance create_callback; + + for (uint32_t idx = 0; + (create_callback = PluginManager::GetABICreateCallbackAtIndex(idx)) != + nullptr; + ++idx) { + abi_sp = create_callback(process_sp, arch); + + if (abi_sp) + return abi_sp; + } + abi_sp.reset(); + return abi_sp; +} + +ABI::~ABI() = default; + +bool RegInfoBasedABI::GetRegisterInfoByName(llvm::StringRef name, + RegisterInfo &info) { + uint32_t count = 0; + const RegisterInfo *register_info_array = GetRegisterInfoArray(count); + if (register_info_array) { + uint32_t i; + for (i = 0; i < count; ++i) { + const char *reg_name = register_info_array[i].name; + if (reg_name == name) { + info = register_info_array[i]; + return true; + } + } + for (i = 0; i < count; ++i) { + const char *reg_alt_name = register_info_array[i].alt_name; + if (reg_alt_name == name) { + info = register_info_array[i]; + return true; + } + } + } + return false; +} + +ValueObjectSP ABI::GetReturnValueObject(Thread &thread, CompilerType &ast_type, + bool persistent) const { + if (!ast_type.IsValid()) + return ValueObjectSP(); + + ValueObjectSP return_valobj_sp; + + return_valobj_sp = GetReturnValueObjectImpl(thread, ast_type); + if (!return_valobj_sp) + return return_valobj_sp; + + // Now turn this into a persistent variable. + // FIXME: This code is duplicated from Target::EvaluateExpression, and it is + // used in similar form in a couple + // of other places. Figure out the correct Create function to do all this + // work. + + if (persistent) { + Target &target = *thread.CalculateTarget(); + PersistentExpressionState *persistent_expression_state = + target.GetPersistentExpressionStateForLanguage( + ast_type.GetMinimumLanguage()); + + if (!persistent_expression_state) + return {}; + + ConstString persistent_variable_name = + persistent_expression_state->GetNextPersistentVariableName(); + + lldb::ValueObjectSP const_valobj_sp; + + // Check in case our value is already a constant value + if (return_valobj_sp->GetIsConstant()) { + const_valobj_sp = return_valobj_sp; + const_valobj_sp->SetName(persistent_variable_name); + } else + const_valobj_sp = + return_valobj_sp->CreateConstantValue(persistent_variable_name); + + lldb::ValueObjectSP live_valobj_sp = return_valobj_sp; + + return_valobj_sp = const_valobj_sp; + + ExpressionVariableSP expr_variable_sp( + persistent_expression_state->CreatePersistentVariable( + return_valobj_sp)); + + assert(expr_variable_sp); + + // Set flags and live data as appropriate + + const Value &result_value = live_valobj_sp->GetValue(); + + switch (result_value.GetValueType()) { + case Value::ValueType::Invalid: + return {}; + case Value::ValueType::HostAddress: + case Value::ValueType::FileAddress: + // we odon't do anything with these for now + break; + case Value::ValueType::Scalar: + expr_variable_sp->m_flags |= + ExpressionVariable::EVIsFreezeDried; + expr_variable_sp->m_flags |= + ExpressionVariable::EVIsLLDBAllocated; + expr_variable_sp->m_flags |= + ExpressionVariable::EVNeedsAllocation; + break; + case Value::ValueType::LoadAddress: + expr_variable_sp->m_live_sp = live_valobj_sp; + expr_variable_sp->m_flags |= + ExpressionVariable::EVIsProgramReference; + break; + } + + return_valobj_sp = expr_variable_sp->GetValueObject(); + } + return return_valobj_sp; +} + +addr_t ABI::FixCodeAddress(lldb::addr_t pc) { + ProcessSP process_sp(GetProcessSP()); + + addr_t mask = process_sp->GetCodeAddressMask(); + if (mask == LLDB_INVALID_ADDRESS_MASK) + return pc; + + // Assume the high bit is used for addressing, which + // may not be correct on all architectures e.g. AArch64 + // where Top Byte Ignore mode is often used to store + // metadata in the top byte, and b55 is the bit used for + // differentiating between low- and high-memory addresses. + // That target's ABIs need to override this method. + bool is_highmem = pc & (1ULL << 63); + return is_highmem ? pc | mask : pc & (~mask); +} + +addr_t ABI::FixDataAddress(lldb::addr_t pc) { + ProcessSP process_sp(GetProcessSP()); + addr_t mask = process_sp->GetDataAddressMask(); + if (mask == LLDB_INVALID_ADDRESS_MASK) + return pc; + + // Assume the high bit is used for addressing, which + // may not be correct on all architectures e.g. AArch64 + // where Top Byte Ignore mode is often used to store + // metadata in the top byte, and b55 is the bit used for + // differentiating between low- and high-memory addresses. + // That target's ABIs need to override this method. + bool is_highmem = pc & (1ULL << 63); + return is_highmem ? pc | mask : pc & (~mask); +} + +ValueObjectSP ABI::GetReturnValueObject(Thread &thread, llvm::Type &ast_type, + bool persistent) const { + ValueObjectSP return_valobj_sp; + return_valobj_sp = GetReturnValueObjectImpl(thread, ast_type); + return return_valobj_sp; +} + +// specialized to work with llvm IR types +// +// for now we will specify a default implementation so that we don't need to +// modify other ABIs +lldb::ValueObjectSP ABI::GetReturnValueObjectImpl(Thread &thread, + llvm::Type &ir_type) const { + ValueObjectSP return_valobj_sp; + + /* this is a dummy and will only be called if an ABI does not override this */ + + return return_valobj_sp; +} + +bool ABI::PrepareTrivialCall(Thread &thread, lldb::addr_t sp, + lldb::addr_t functionAddress, + lldb::addr_t returnAddress, llvm::Type &returntype, + llvm::ArrayRef<ABI::CallArgument> args) const { + // dummy prepare trivial call + llvm_unreachable("Should never get here!"); +} + +bool ABI::GetFallbackRegisterLocation( + const RegisterInfo *reg_info, + UnwindPlan::Row::RegisterLocation &unwind_regloc) { + // Did the UnwindPlan fail to give us the caller's stack pointer? The stack + // pointer is defined to be the same as THIS frame's CFA, so return the CFA + // value as the caller's stack pointer. This is true on x86-32/x86-64 at + // least. + if (reg_info->kinds[eRegisterKindGeneric] == LLDB_REGNUM_GENERIC_SP) { + unwind_regloc.SetIsCFAPlusOffset(0); + return true; + } + + // If a volatile register is being requested, we don't want to forward the + // next frame's register contents up the stack -- the register is not + // retrievable at this frame. + if (RegisterIsVolatile(reg_info)) { + unwind_regloc.SetUndefined(); + return true; + } + + return false; +} + +std::unique_ptr<llvm::MCRegisterInfo> ABI::MakeMCRegisterInfo(const ArchSpec &arch) { + std::string triple = arch.GetTriple().getTriple(); + std::string lookup_error; + const llvm::Target *target = + llvm::TargetRegistry::lookupTarget(triple, lookup_error); + if (!target) { + LLDB_LOG(GetLog(LLDBLog::Process), + "Failed to create an llvm target for {0}: {1}", triple, + lookup_error); + return nullptr; + } + std::unique_ptr<llvm::MCRegisterInfo> info_up( + target->createMCRegInfo(triple)); + assert(info_up); + return info_up; +} + +void RegInfoBasedABI::AugmentRegisterInfo( + std::vector<DynamicRegisterInfo::Register> ®s) { + for (DynamicRegisterInfo::Register &info : regs) { + if (info.regnum_ehframe != LLDB_INVALID_REGNUM && + info.regnum_dwarf != LLDB_INVALID_REGNUM) + continue; + + RegisterInfo abi_info; + if (!GetRegisterInfoByName(info.name.GetStringRef(), abi_info)) + continue; + + if (info.regnum_ehframe == LLDB_INVALID_REGNUM) + info.regnum_ehframe = abi_info.kinds[eRegisterKindEHFrame]; + if (info.regnum_dwarf == LLDB_INVALID_REGNUM) + info.regnum_dwarf = abi_info.kinds[eRegisterKindDWARF]; + if (info.regnum_generic == LLDB_INVALID_REGNUM) + info.regnum_generic = abi_info.kinds[eRegisterKindGeneric]; + } +} + +void MCBasedABI::AugmentRegisterInfo( + std::vector<DynamicRegisterInfo::Register> ®s) { + for (DynamicRegisterInfo::Register &info : regs) { + uint32_t eh, dwarf; + std::tie(eh, dwarf) = GetEHAndDWARFNums(info.name.GetStringRef()); + + if (info.regnum_ehframe == LLDB_INVALID_REGNUM) + info.regnum_ehframe = eh; + if (info.regnum_dwarf == LLDB_INVALID_REGNUM) + info.regnum_dwarf = dwarf; + if (info.regnum_generic == LLDB_INVALID_REGNUM) + info.regnum_generic = GetGenericNum(info.name.GetStringRef()); + } +} + +std::pair<uint32_t, uint32_t> +MCBasedABI::GetEHAndDWARFNums(llvm::StringRef name) { + std::string mc_name = GetMCName(name.str()); + for (char &c : mc_name) + c = std::toupper(c); + int eh = -1; + int dwarf = -1; + for (unsigned reg = 0; reg < m_mc_register_info_up->getNumRegs(); ++reg) { + if (m_mc_register_info_up->getName(reg) == mc_name) { + eh = m_mc_register_info_up->getDwarfRegNum(reg, /*isEH=*/true); + dwarf = m_mc_register_info_up->getDwarfRegNum(reg, /*isEH=*/false); + break; + } + } + return std::pair<uint32_t, uint32_t>(eh == -1 ? LLDB_INVALID_REGNUM : eh, + dwarf == -1 ? LLDB_INVALID_REGNUM + : dwarf); +} + +void MCBasedABI::MapRegisterName(std::string &name, llvm::StringRef from_prefix, + llvm::StringRef to_prefix) { + llvm::StringRef name_ref = name; + if (!name_ref.consume_front(from_prefix)) + return; + uint64_t _; + if (name_ref.empty() || to_integer(name_ref, _, 10)) + name = (to_prefix + name_ref).str(); +} diff --git a/contrib/llvm-project/lldb/source/Target/AssertFrameRecognizer.cpp b/contrib/llvm-project/lldb/source/Target/AssertFrameRecognizer.cpp new file mode 100644 index 000000000000..da7c102645c0 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/AssertFrameRecognizer.cpp @@ -0,0 +1,182 @@ +#include "lldb/Target/AssertFrameRecognizer.h" +#include "lldb/Core/Module.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Symbol/SymbolLocation.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/StackFrameList.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/LLDBLog.h" + +using namespace llvm; +using namespace lldb; +using namespace lldb_private; + +namespace lldb_private { +/// Fetches the abort frame location depending on the current platform. +/// +/// \param[in] os +/// The target's os type. +/// \param[in,out] location +/// The struct that will contain the abort module spec and symbol names. +/// \return +/// \b true, if the platform is supported +/// \b false, otherwise. +bool GetAbortLocation(llvm::Triple::OSType os, SymbolLocation &location) { + switch (os) { + case llvm::Triple::Darwin: + case llvm::Triple::MacOSX: + location.module_spec = FileSpec("libsystem_kernel.dylib"); + location.symbols.push_back(ConstString("__pthread_kill")); + break; + case llvm::Triple::Linux: + location.module_spec = FileSpec("libc.so.6"); + location.symbols.push_back(ConstString("raise")); + location.symbols.push_back(ConstString("__GI_raise")); + location.symbols.push_back(ConstString("gsignal")); + location.symbols.push_back(ConstString("pthread_kill")); + location.symbols_are_regex = true; + break; + default: + Log *log = GetLog(LLDBLog::Unwind); + LLDB_LOG(log, "AssertFrameRecognizer::GetAbortLocation Unsupported OS"); + return false; + } + + return true; +} + +/// Fetches the assert frame location depending on the current platform. +/// +/// \param[in] os +/// The target's os type. +/// \param[in,out] location +/// The struct that will contain the assert module spec and symbol names. +/// \return +/// \b true, if the platform is supported +/// \b false, otherwise. +bool GetAssertLocation(llvm::Triple::OSType os, SymbolLocation &location) { + switch (os) { + case llvm::Triple::Darwin: + case llvm::Triple::MacOSX: + location.module_spec = FileSpec("libsystem_c.dylib"); + location.symbols.push_back(ConstString("__assert_rtn")); + break; + case llvm::Triple::Linux: + location.module_spec = FileSpec("libc.so.6"); + location.symbols.push_back(ConstString("__assert_fail")); + location.symbols.push_back(ConstString("__GI___assert_fail")); + break; + default: + Log *log = GetLog(LLDBLog::Unwind); + LLDB_LOG(log, "AssertFrameRecognizer::GetAssertLocation Unsupported OS"); + return false; + } + + return true; +} + +void RegisterAssertFrameRecognizer(Process *process) { + Target &target = process->GetTarget(); + llvm::Triple::OSType os = target.GetArchitecture().GetTriple().getOS(); + SymbolLocation location; + + if (!GetAbortLocation(os, location)) + return; + + if (!location.symbols_are_regex) { + target.GetFrameRecognizerManager().AddRecognizer( + std::make_shared<AssertFrameRecognizer>(), + location.module_spec.GetFilename(), location.symbols, + /*first_instruction_only*/ false); + return; + } + std::string module_re = "^"; + for (char c : location.module_spec.GetFilename().GetStringRef()) { + if (c == '.') + module_re += '\\'; + module_re += c; + } + module_re += '$'; + std::string symbol_re = "^("; + for (auto it = location.symbols.cbegin(); it != location.symbols.cend(); + ++it) { + if (it != location.symbols.cbegin()) + symbol_re += '|'; + symbol_re += it->GetStringRef(); + } + // Strip the trailing @VER symbol version. + symbol_re += ")(@.*)?$"; + target.GetFrameRecognizerManager().AddRecognizer( + std::make_shared<AssertFrameRecognizer>(), + std::make_shared<RegularExpression>(std::move(module_re)), + std::make_shared<RegularExpression>(std::move(symbol_re)), + /*first_instruction_only*/ false); +} + +} // namespace lldb_private + +lldb::RecognizedStackFrameSP +AssertFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame_sp) { + ThreadSP thread_sp = frame_sp->GetThread(); + ProcessSP process_sp = thread_sp->GetProcess(); + Target &target = process_sp->GetTarget(); + llvm::Triple::OSType os = target.GetArchitecture().GetTriple().getOS(); + SymbolLocation location; + + if (!GetAssertLocation(os, location)) + return RecognizedStackFrameSP(); + + const uint32_t frames_to_fetch = 6; + const uint32_t last_frame_index = frames_to_fetch - 1; + StackFrameSP prev_frame_sp = nullptr; + + // Fetch most relevant frame + for (uint32_t frame_index = 0; frame_index < frames_to_fetch; frame_index++) { + prev_frame_sp = thread_sp->GetStackFrameAtIndex(frame_index); + + if (!prev_frame_sp) { + Log *log = GetLog(LLDBLog::Unwind); + LLDB_LOG(log, "Abort Recognizer: Hit unwinding bound ({1} frames)!", + frames_to_fetch); + break; + } + + SymbolContext sym_ctx = + prev_frame_sp->GetSymbolContext(eSymbolContextEverything); + + if (!sym_ctx.module_sp || + !sym_ctx.module_sp->GetFileSpec().FileEquals(location.module_spec)) + continue; + + ConstString func_name = sym_ctx.GetFunctionName(); + + if (llvm::is_contained(location.symbols, func_name)) { + // We go a frame beyond the assert location because the most relevant + // frame for the user is the one in which the assert function was called. + // If the assert location is the last frame fetched, then it is set as + // the most relevant frame. + + StackFrameSP most_relevant_frame_sp = thread_sp->GetStackFrameAtIndex( + std::min(frame_index + 1, last_frame_index)); + + // Pass assert location to AbortRecognizedStackFrame to set as most + // relevant frame. + return lldb::RecognizedStackFrameSP( + new AssertRecognizedStackFrame(most_relevant_frame_sp)); + } + } + + return RecognizedStackFrameSP(); +} + +AssertRecognizedStackFrame::AssertRecognizedStackFrame( + StackFrameSP most_relevant_frame_sp) + : m_most_relevant_frame(most_relevant_frame_sp) { + m_stop_desc = "hit program assert"; +} + +lldb::StackFrameSP AssertRecognizedStackFrame::GetMostRelevantFrame() { + return m_most_relevant_frame; +} diff --git a/contrib/llvm-project/lldb/source/Target/DynamicRegisterInfo.cpp b/contrib/llvm-project/lldb/source/Target/DynamicRegisterInfo.cpp new file mode 100644 index 000000000000..1a817449fa95 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/DynamicRegisterInfo.cpp @@ -0,0 +1,834 @@ +//===-- DynamicRegisterInfo.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 "lldb/Target/DynamicRegisterInfo.h" +#include "lldb/DataFormatters/FormatManager.h" +#include "lldb/Host/StreamFile.h" +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Utility/ArchSpec.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/RegularExpression.h" +#include "lldb/Utility/StringExtractor.h" +#include "lldb/Utility/StructuredData.h" + +using namespace lldb; +using namespace lldb_private; + +std::unique_ptr<DynamicRegisterInfo> +DynamicRegisterInfo::Create(const StructuredData::Dictionary &dict, + const ArchSpec &arch) { + auto dyn_reg_info = std::make_unique<DynamicRegisterInfo>(); + if (!dyn_reg_info) + return nullptr; + + if (dyn_reg_info->SetRegisterInfo(dict, arch) == 0) + return nullptr; + + return dyn_reg_info; +} + +DynamicRegisterInfo::DynamicRegisterInfo(DynamicRegisterInfo &&info) { + MoveFrom(std::move(info)); +} + +DynamicRegisterInfo & +DynamicRegisterInfo::operator=(DynamicRegisterInfo &&info) { + MoveFrom(std::move(info)); + return *this; +} + +void DynamicRegisterInfo::MoveFrom(DynamicRegisterInfo &&info) { + m_regs = std::move(info.m_regs); + m_sets = std::move(info.m_sets); + m_set_reg_nums = std::move(info.m_set_reg_nums); + m_set_names = std::move(info.m_set_names); + m_value_regs_map = std::move(info.m_value_regs_map); + m_invalidate_regs_map = std::move(info.m_invalidate_regs_map); + + m_reg_data_byte_size = info.m_reg_data_byte_size; + m_finalized = info.m_finalized; + + if (m_finalized) { + const size_t num_sets = m_sets.size(); + for (size_t set = 0; set < num_sets; ++set) + m_sets[set].registers = m_set_reg_nums[set].data(); + } + + info.Clear(); +} + +llvm::Expected<uint32_t> DynamicRegisterInfo::ByteOffsetFromSlice( + uint32_t index, llvm::StringRef slice_str, lldb::ByteOrder byte_order) { + // Slices use the following format: + // REGNAME[MSBIT:LSBIT] + // REGNAME - name of the register to grab a slice of + // MSBIT - the most significant bit at which the current register value + // starts at + // LSBIT - the least significant bit at which the current register value + // ends at + static llvm::Regex g_bitfield_regex( + "([A-Za-z_][A-Za-z0-9_]*)\\[([0-9]+):([0-9]+)\\]"); + llvm::SmallVector<llvm::StringRef, 4> matches; + if (!g_bitfield_regex.match(slice_str, &matches)) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "failed to match against register bitfield regex (slice: %s)", + slice_str.str().c_str()); + + llvm::StringRef reg_name_str = matches[1]; + llvm::StringRef msbit_str = matches[2]; + llvm::StringRef lsbit_str = matches[3]; + uint32_t msbit; + uint32_t lsbit; + if (!llvm::to_integer(msbit_str, msbit) || + !llvm::to_integer(lsbit_str, lsbit)) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), "msbit (%s) or lsbit (%s) are invalid", + msbit_str.str().c_str(), lsbit_str.str().c_str()); + + if (msbit <= lsbit) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "msbit (%u) must be greater than lsbit (%u)", + msbit, lsbit); + + const uint32_t msbyte = msbit / 8; + const uint32_t lsbyte = lsbit / 8; + + const RegisterInfo *containing_reg_info = GetRegisterInfo(reg_name_str); + if (!containing_reg_info) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "invalid concrete register \"%s\"", + reg_name_str.str().c_str()); + + const uint32_t max_bit = containing_reg_info->byte_size * 8; + + if (msbit > max_bit) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "msbit (%u) must be less than the bitsize of the register \"%s\" (%u)", + msbit, reg_name_str.str().c_str(), max_bit); + if (lsbit > max_bit) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "lsbit (%u) must be less than the bitsize of the register \"%s\" (%u)", + lsbit, reg_name_str.str().c_str(), max_bit); + + m_invalidate_regs_map[containing_reg_info->kinds[eRegisterKindLLDB]] + .push_back(index); + m_value_regs_map[index].push_back( + containing_reg_info->kinds[eRegisterKindLLDB]); + m_invalidate_regs_map[index].push_back( + containing_reg_info->kinds[eRegisterKindLLDB]); + + if (byte_order == eByteOrderLittle) + return containing_reg_info->byte_offset + lsbyte; + if (byte_order == eByteOrderBig) + return containing_reg_info->byte_offset + msbyte; + llvm_unreachable("Invalid byte order"); +} + +llvm::Expected<uint32_t> DynamicRegisterInfo::ByteOffsetFromComposite( + uint32_t index, StructuredData::Array &composite_reg_list, + lldb::ByteOrder byte_order) { + const size_t num_composite_regs = composite_reg_list.GetSize(); + if (num_composite_regs == 0) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "\"composite\" list is empty"); + + uint32_t composite_offset = UINT32_MAX; + for (uint32_t composite_idx = 0; composite_idx < num_composite_regs; + ++composite_idx) { + std::optional<llvm::StringRef> maybe_composite_reg_name = + composite_reg_list.GetItemAtIndexAsString(composite_idx); + if (!maybe_composite_reg_name) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "\"composite\" list value is not a Python string at index %d", + composite_idx); + + const RegisterInfo *composite_reg_info = + GetRegisterInfo(*maybe_composite_reg_name); + if (!composite_reg_info) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "failed to find composite register by name: \"%s\"", + maybe_composite_reg_name->str().c_str()); + + composite_offset = + std::min(composite_offset, composite_reg_info->byte_offset); + m_value_regs_map[index].push_back( + composite_reg_info->kinds[eRegisterKindLLDB]); + m_invalidate_regs_map[composite_reg_info->kinds[eRegisterKindLLDB]] + .push_back(index); + m_invalidate_regs_map[index].push_back( + composite_reg_info->kinds[eRegisterKindLLDB]); + } + + return composite_offset; +} + +llvm::Expected<uint32_t> DynamicRegisterInfo::ByteOffsetFromRegInfoDict( + uint32_t index, StructuredData::Dictionary ®_info_dict, + lldb::ByteOrder byte_order) { + uint32_t byte_offset; + if (reg_info_dict.GetValueForKeyAsInteger("offset", byte_offset)) + return byte_offset; + + // No offset for this register, see if the register has a value + // expression which indicates this register is part of another register. + // Value expressions are things like "rax[31:0]" which state that the + // current register's value is in a concrete register "rax" in bits 31:0. + // If there is a value expression we can calculate the offset + llvm::StringRef slice_str; + if (reg_info_dict.GetValueForKeyAsString("slice", slice_str, nullptr)) + return ByteOffsetFromSlice(index, slice_str, byte_order); + + StructuredData::Array *composite_reg_list; + if (reg_info_dict.GetValueForKeyAsArray("composite", composite_reg_list)) + return ByteOffsetFromComposite(index, *composite_reg_list, byte_order); + + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "insufficient data to calculate byte offset"); +} + +size_t +DynamicRegisterInfo::SetRegisterInfo(const StructuredData::Dictionary &dict, + const ArchSpec &arch) { + Log *log = GetLog(LLDBLog::Object); + assert(!m_finalized); + StructuredData::Array *sets = nullptr; + if (dict.GetValueForKeyAsArray("sets", sets)) { + const uint32_t num_sets = sets->GetSize(); + for (uint32_t i = 0; i < num_sets; ++i) { + std::optional<llvm::StringRef> maybe_set_name = + sets->GetItemAtIndexAsString(i); + if (maybe_set_name && !maybe_set_name->empty()) { + m_sets.push_back( + {ConstString(*maybe_set_name).AsCString(), nullptr, 0, nullptr}); + } else { + Clear(); + printf("error: register sets must have valid names\n"); + return 0; + } + } + m_set_reg_nums.resize(m_sets.size()); + } + + StructuredData::Array *regs = nullptr; + if (!dict.GetValueForKeyAsArray("registers", regs)) + return 0; + + const ByteOrder byte_order = arch.GetByteOrder(); + + const uint32_t num_regs = regs->GetSize(); + // typedef std::map<std::string, std::vector<std::string> > + // InvalidateNameMap; + // InvalidateNameMap invalidate_map; + for (uint32_t i = 0; i < num_regs; ++i) { + std::optional<StructuredData::Dictionary *> maybe_reg_info_dict = + regs->GetItemAtIndexAsDictionary(i); + if (!maybe_reg_info_dict) { + Clear(); + printf("error: items in the 'registers' array must be dictionaries\n"); + regs->DumpToStdout(); + return 0; + } + StructuredData::Dictionary *reg_info_dict = *maybe_reg_info_dict; + + // { 'name':'rcx' , 'bitsize' : 64, 'offset' : 16, + // 'encoding':'uint' , 'format':'hex' , 'set': 0, 'ehframe' : 2, + // 'dwarf' : 2, 'generic':'arg4', 'alt-name':'arg4', }, + RegisterInfo reg_info; + std::vector<uint32_t> value_regs; + std::vector<uint32_t> invalidate_regs; + memset(®_info, 0, sizeof(reg_info)); + + llvm::StringRef name_val; + if (!reg_info_dict->GetValueForKeyAsString("name", name_val)) { + Clear(); + printf("error: registers must have valid names and offsets\n"); + reg_info_dict->DumpToStdout(); + return 0; + } + reg_info.name = ConstString(name_val).GetCString(); + + llvm::StringRef alt_name_val; + if (reg_info_dict->GetValueForKeyAsString("alt-name", alt_name_val)) + reg_info.alt_name = ConstString(alt_name_val).GetCString(); + else + reg_info.alt_name = nullptr; + + llvm::Expected<uint32_t> byte_offset = + ByteOffsetFromRegInfoDict(i, *reg_info_dict, byte_order); + if (byte_offset) + reg_info.byte_offset = byte_offset.get(); + else { + LLDB_LOG_ERROR(log, byte_offset.takeError(), + "error while parsing register {1}: {0}", reg_info.name); + Clear(); + reg_info_dict->DumpToStdout(); + return 0; + } + + uint64_t bitsize = 0; + if (!reg_info_dict->GetValueForKeyAsInteger("bitsize", bitsize)) { + Clear(); + printf("error: invalid or missing 'bitsize' key/value pair in register " + "dictionary\n"); + reg_info_dict->DumpToStdout(); + return 0; + } + + reg_info.byte_size = bitsize / 8; + + llvm::StringRef format_str; + if (reg_info_dict->GetValueForKeyAsString("format", format_str, nullptr)) { + if (OptionArgParser::ToFormat(format_str.str().c_str(), reg_info.format, + nullptr) + .Fail()) { + Clear(); + printf("error: invalid 'format' value in register dictionary\n"); + reg_info_dict->DumpToStdout(); + return 0; + } + } else { + reg_info_dict->GetValueForKeyAsInteger("format", reg_info.format, + eFormatHex); + } + + llvm::StringRef encoding_str; + if (reg_info_dict->GetValueForKeyAsString("encoding", encoding_str)) + reg_info.encoding = Args::StringToEncoding(encoding_str, eEncodingUint); + else + reg_info_dict->GetValueForKeyAsInteger("encoding", reg_info.encoding, + eEncodingUint); + + size_t set = 0; + if (!reg_info_dict->GetValueForKeyAsInteger("set", set) || + set >= m_sets.size()) { + Clear(); + printf("error: invalid 'set' value in register dictionary, valid values " + "are 0 - %i\n", + (int)set); + reg_info_dict->DumpToStdout(); + return 0; + } + + // Fill in the register numbers + reg_info.kinds[lldb::eRegisterKindLLDB] = i; + reg_info.kinds[lldb::eRegisterKindProcessPlugin] = i; + uint32_t eh_frame_regno = LLDB_INVALID_REGNUM; + reg_info_dict->GetValueForKeyAsInteger("gcc", eh_frame_regno, + LLDB_INVALID_REGNUM); + if (eh_frame_regno == LLDB_INVALID_REGNUM) + reg_info_dict->GetValueForKeyAsInteger("ehframe", eh_frame_regno, + LLDB_INVALID_REGNUM); + reg_info.kinds[lldb::eRegisterKindEHFrame] = eh_frame_regno; + reg_info_dict->GetValueForKeyAsInteger( + "dwarf", reg_info.kinds[lldb::eRegisterKindDWARF], LLDB_INVALID_REGNUM); + llvm::StringRef generic_str; + if (reg_info_dict->GetValueForKeyAsString("generic", generic_str)) + reg_info.kinds[lldb::eRegisterKindGeneric] = + Args::StringToGenericRegister(generic_str); + else + reg_info_dict->GetValueForKeyAsInteger( + "generic", reg_info.kinds[lldb::eRegisterKindGeneric], + LLDB_INVALID_REGNUM); + + // Check if this register invalidates any other register values when it is + // modified + StructuredData::Array *invalidate_reg_list = nullptr; + if (reg_info_dict->GetValueForKeyAsArray("invalidate-regs", + invalidate_reg_list)) { + const size_t num_regs = invalidate_reg_list->GetSize(); + if (num_regs > 0) { + for (uint32_t idx = 0; idx < num_regs; ++idx) { + if (auto maybe_invalidate_reg_name = + invalidate_reg_list->GetItemAtIndexAsString(idx)) { + const RegisterInfo *invalidate_reg_info = + GetRegisterInfo(*maybe_invalidate_reg_name); + if (invalidate_reg_info) { + m_invalidate_regs_map[i].push_back( + invalidate_reg_info->kinds[eRegisterKindLLDB]); + } else { + // TODO: print error invalid slice string that doesn't follow the + // format + printf("error: failed to find a 'invalidate-regs' register for " + "\"%s\" while parsing register \"%s\"\n", + maybe_invalidate_reg_name->str().c_str(), reg_info.name); + } + } else if (auto maybe_invalidate_reg_num = + invalidate_reg_list->GetItemAtIndexAsInteger<uint64_t>( + idx)) { + if (*maybe_invalidate_reg_num != UINT64_MAX) + m_invalidate_regs_map[i].push_back(*maybe_invalidate_reg_num); + else + printf("error: 'invalidate-regs' list value wasn't a valid " + "integer\n"); + } else { + printf("error: 'invalidate-regs' list value wasn't a python string " + "or integer\n"); + } + } + } else { + printf("error: 'invalidate-regs' contained an empty list\n"); + } + } + + // Calculate the register offset + const size_t end_reg_offset = reg_info.byte_offset + reg_info.byte_size; + if (m_reg_data_byte_size < end_reg_offset) + m_reg_data_byte_size = end_reg_offset; + + m_regs.push_back(reg_info); + m_set_reg_nums[set].push_back(i); + } + Finalize(arch); + return m_regs.size(); +} + +size_t DynamicRegisterInfo::SetRegisterInfo( + std::vector<DynamicRegisterInfo::Register> &®s, + const ArchSpec &arch) { + assert(!m_finalized); + + for (auto it : llvm::enumerate(regs)) { + uint32_t local_regnum = it.index(); + const DynamicRegisterInfo::Register ® = it.value(); + + assert(reg.name); + assert(reg.set_name); + + if (!reg.value_regs.empty()) + m_value_regs_map[local_regnum] = std::move(reg.value_regs); + if (!reg.invalidate_regs.empty()) + m_invalidate_regs_map[local_regnum] = std::move(reg.invalidate_regs); + if (reg.value_reg_offset != 0) { + assert(reg.value_regs.size() == 1); + m_value_reg_offset_map[local_regnum] = reg.value_reg_offset; + } + + struct RegisterInfo reg_info { + reg.name.AsCString(), reg.alt_name.AsCString(), reg.byte_size, + reg.byte_offset, reg.encoding, reg.format, + {reg.regnum_ehframe, reg.regnum_dwarf, reg.regnum_generic, + reg.regnum_remote, local_regnum}, + // value_regs and invalidate_regs are filled by Finalize() + nullptr, nullptr, reg.flags_type + }; + + m_regs.push_back(reg_info); + + uint32_t set = GetRegisterSetIndexByName(reg.set_name, true); + assert(set < m_sets.size()); + assert(set < m_set_reg_nums.size()); + assert(set < m_set_names.size()); + m_set_reg_nums[set].push_back(local_regnum); + }; + + Finalize(arch); + return m_regs.size(); +} + +void DynamicRegisterInfo::Finalize(const ArchSpec &arch) { + if (m_finalized) + return; + + m_finalized = true; + const size_t num_sets = m_sets.size(); + for (size_t set = 0; set < num_sets; ++set) { + assert(m_sets.size() == m_set_reg_nums.size()); + m_sets[set].num_registers = m_set_reg_nums[set].size(); + m_sets[set].registers = m_set_reg_nums[set].data(); + } + + // make sure value_regs are terminated with LLDB_INVALID_REGNUM + + for (reg_to_regs_map::iterator pos = m_value_regs_map.begin(), + end = m_value_regs_map.end(); + pos != end; ++pos) { + if (pos->second.back() != LLDB_INVALID_REGNUM) + pos->second.push_back(LLDB_INVALID_REGNUM); + } + + // Now update all value_regs with each register info as needed + const size_t num_regs = m_regs.size(); + for (size_t i = 0; i < num_regs; ++i) { + if (m_value_regs_map.find(i) != m_value_regs_map.end()) + m_regs[i].value_regs = m_value_regs_map[i].data(); + else + m_regs[i].value_regs = nullptr; + } + + // Expand all invalidation dependencies + for (reg_to_regs_map::iterator pos = m_invalidate_regs_map.begin(), + end = m_invalidate_regs_map.end(); + pos != end; ++pos) { + const uint32_t reg_num = pos->first; + + if (m_regs[reg_num].value_regs) { + reg_num_collection extra_invalid_regs; + for (const uint32_t invalidate_reg_num : pos->second) { + reg_to_regs_map::iterator invalidate_pos = + m_invalidate_regs_map.find(invalidate_reg_num); + if (invalidate_pos != m_invalidate_regs_map.end()) { + for (const uint32_t concrete_invalidate_reg_num : + invalidate_pos->second) { + if (concrete_invalidate_reg_num != reg_num) + extra_invalid_regs.push_back(concrete_invalidate_reg_num); + } + } + } + pos->second.insert(pos->second.end(), extra_invalid_regs.begin(), + extra_invalid_regs.end()); + } + } + + // sort and unique all invalidate registers and make sure each is terminated + // with LLDB_INVALID_REGNUM + for (reg_to_regs_map::iterator pos = m_invalidate_regs_map.begin(), + end = m_invalidate_regs_map.end(); + pos != end; ++pos) { + if (pos->second.size() > 1) { + llvm::sort(pos->second); + reg_num_collection::iterator unique_end = + std::unique(pos->second.begin(), pos->second.end()); + if (unique_end != pos->second.end()) + pos->second.erase(unique_end, pos->second.end()); + } + assert(!pos->second.empty()); + if (pos->second.back() != LLDB_INVALID_REGNUM) + pos->second.push_back(LLDB_INVALID_REGNUM); + } + + // Now update all invalidate_regs with each register info as needed + for (size_t i = 0; i < num_regs; ++i) { + if (m_invalidate_regs_map.find(i) != m_invalidate_regs_map.end()) + m_regs[i].invalidate_regs = m_invalidate_regs_map[i].data(); + else + m_regs[i].invalidate_regs = nullptr; + } + + // Check if we need to automatically set the generic registers in case they + // weren't set + bool generic_regs_specified = false; + for (const auto ® : m_regs) { + if (reg.kinds[eRegisterKindGeneric] != LLDB_INVALID_REGNUM) { + generic_regs_specified = true; + break; + } + } + + if (!generic_regs_specified) { + switch (arch.GetMachine()) { + case llvm::Triple::aarch64: + case llvm::Triple::aarch64_32: + case llvm::Triple::aarch64_be: + for (auto ® : m_regs) { + if (strcmp(reg.name, "pc") == 0) + reg.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_PC; + else if ((strcmp(reg.name, "fp") == 0) || + (strcmp(reg.name, "x29") == 0)) + reg.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_FP; + else if ((strcmp(reg.name, "lr") == 0) || + (strcmp(reg.name, "x30") == 0)) + reg.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_RA; + else if ((strcmp(reg.name, "sp") == 0) || + (strcmp(reg.name, "x31") == 0)) + reg.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_SP; + else if (strcmp(reg.name, "cpsr") == 0) + reg.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_FLAGS; + } + break; + + case llvm::Triple::arm: + case llvm::Triple::armeb: + case llvm::Triple::thumb: + case llvm::Triple::thumbeb: + for (auto ® : m_regs) { + if ((strcmp(reg.name, "pc") == 0) || (strcmp(reg.name, "r15") == 0)) + reg.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_PC; + else if ((strcmp(reg.name, "sp") == 0) || + (strcmp(reg.name, "r13") == 0)) + reg.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_SP; + else if ((strcmp(reg.name, "lr") == 0) || + (strcmp(reg.name, "r14") == 0)) + reg.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_RA; + else if ((strcmp(reg.name, "r7") == 0) && + arch.GetTriple().getVendor() == llvm::Triple::Apple) + reg.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_FP; + else if ((strcmp(reg.name, "r11") == 0) && + arch.GetTriple().getVendor() != llvm::Triple::Apple) + reg.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_FP; + else if (strcmp(reg.name, "fp") == 0) + reg.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_FP; + else if (strcmp(reg.name, "cpsr") == 0) + reg.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_FLAGS; + } + break; + + case llvm::Triple::x86: + for (auto ® : m_regs) { + if ((strcmp(reg.name, "eip") == 0) || (strcmp(reg.name, "pc") == 0)) + reg.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_PC; + else if ((strcmp(reg.name, "esp") == 0) || + (strcmp(reg.name, "sp") == 0)) + reg.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_SP; + else if ((strcmp(reg.name, "ebp") == 0) || + (strcmp(reg.name, "fp") == 0)) + reg.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_FP; + else if ((strcmp(reg.name, "eflags") == 0) || + (strcmp(reg.name, "flags") == 0)) + reg.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_FLAGS; + } + break; + + case llvm::Triple::x86_64: + for (auto ® : m_regs) { + if ((strcmp(reg.name, "rip") == 0) || (strcmp(reg.name, "pc") == 0)) + reg.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_PC; + else if ((strcmp(reg.name, "rsp") == 0) || + (strcmp(reg.name, "sp") == 0)) + reg.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_SP; + else if ((strcmp(reg.name, "rbp") == 0) || + (strcmp(reg.name, "fp") == 0)) + reg.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_FP; + else if ((strcmp(reg.name, "rflags") == 0) || + (strcmp(reg.name, "eflags") == 0) || + (strcmp(reg.name, "flags") == 0)) + reg.kinds[eRegisterKindGeneric] = LLDB_REGNUM_GENERIC_FLAGS; + } + break; + + default: + break; + } + } + + // At this stage call ConfigureOffsets to calculate register offsets for + // targets supporting dynamic offset calculation. It also calculates + // total byte size of register data. + ConfigureOffsets(); + + // Check if register info is reconfigurable + // AArch64 SVE register set has configurable register sizes, as does the ZA + // register that SME added (the streaming state of SME reuses the SVE state). + if (arch.GetTriple().isAArch64()) { + for (const auto ® : m_regs) { + if ((strcmp(reg.name, "vg") == 0) || (strcmp(reg.name, "svg") == 0)) { + m_is_reconfigurable = true; + break; + } + } + } +} + +void DynamicRegisterInfo::ConfigureOffsets() { + // We are going to create a map between remote (eRegisterKindProcessPlugin) + // and local (eRegisterKindLLDB) register numbers. This map will give us + // remote register numbers in increasing order for offset calculation. + std::map<uint32_t, uint32_t> remote_to_local_regnum_map; + for (const auto ® : m_regs) + remote_to_local_regnum_map[reg.kinds[eRegisterKindProcessPlugin]] = + reg.kinds[eRegisterKindLLDB]; + + // At this stage we manually calculate g/G packet offsets of all primary + // registers, only if target XML or qRegisterInfo packet did not send + // an offset explicitly. + uint32_t reg_offset = 0; + for (auto const ®num_pair : remote_to_local_regnum_map) { + if (m_regs[regnum_pair.second].byte_offset == LLDB_INVALID_INDEX32 && + m_regs[regnum_pair.second].value_regs == nullptr) { + m_regs[regnum_pair.second].byte_offset = reg_offset; + + reg_offset = m_regs[regnum_pair.second].byte_offset + + m_regs[regnum_pair.second].byte_size; + } + } + + // Now update all value_regs with each register info as needed + for (auto ® : m_regs) { + if (reg.value_regs != nullptr) { + // Assign a valid offset to all pseudo registers that have only a single + // parent register in value_regs list, if not assigned by stub. Pseudo + // registers with value_regs list populated will share same offset as + // that of their corresponding parent register. + if (reg.byte_offset == LLDB_INVALID_INDEX32) { + uint32_t value_regnum = reg.value_regs[0]; + if (value_regnum != LLDB_INVALID_INDEX32 && + reg.value_regs[1] == LLDB_INVALID_INDEX32) { + reg.byte_offset = + GetRegisterInfoAtIndex(value_regnum)->byte_offset; + auto it = m_value_reg_offset_map.find(reg.kinds[eRegisterKindLLDB]); + if (it != m_value_reg_offset_map.end()) + reg.byte_offset += it->second; + } + } + } + + reg_offset = reg.byte_offset + reg.byte_size; + if (m_reg_data_byte_size < reg_offset) + m_reg_data_byte_size = reg_offset; + } +} + +bool DynamicRegisterInfo::IsReconfigurable() { return m_is_reconfigurable; } + +size_t DynamicRegisterInfo::GetNumRegisters() const { return m_regs.size(); } + +size_t DynamicRegisterInfo::GetNumRegisterSets() const { return m_sets.size(); } + +size_t DynamicRegisterInfo::GetRegisterDataByteSize() const { + return m_reg_data_byte_size; +} + +const RegisterInfo * +DynamicRegisterInfo::GetRegisterInfoAtIndex(uint32_t i) const { + if (i < m_regs.size()) + return &m_regs[i]; + return nullptr; +} + +const RegisterInfo *DynamicRegisterInfo::GetRegisterInfo(uint32_t kind, + uint32_t num) const { + uint32_t reg_index = ConvertRegisterKindToRegisterNumber(kind, num); + if (reg_index != LLDB_INVALID_REGNUM) + return &m_regs[reg_index]; + return nullptr; +} + +const RegisterSet *DynamicRegisterInfo::GetRegisterSet(uint32_t i) const { + if (i < m_sets.size()) + return &m_sets[i]; + return nullptr; +} + +uint32_t +DynamicRegisterInfo::GetRegisterSetIndexByName(const ConstString &set_name, + bool can_create) { + name_collection::iterator pos, end = m_set_names.end(); + for (pos = m_set_names.begin(); pos != end; ++pos) { + if (*pos == set_name) + return std::distance(m_set_names.begin(), pos); + } + + m_set_names.push_back(set_name); + m_set_reg_nums.resize(m_set_reg_nums.size() + 1); + RegisterSet new_set = {set_name.AsCString(), nullptr, 0, nullptr}; + m_sets.push_back(new_set); + return m_sets.size() - 1; +} + +uint32_t +DynamicRegisterInfo::ConvertRegisterKindToRegisterNumber(uint32_t kind, + uint32_t num) const { + reg_collection::const_iterator pos, end = m_regs.end(); + for (pos = m_regs.begin(); pos != end; ++pos) { + if (pos->kinds[kind] == num) + return std::distance(m_regs.begin(), pos); + } + + return LLDB_INVALID_REGNUM; +} + +void DynamicRegisterInfo::Clear() { + m_regs.clear(); + m_sets.clear(); + m_set_reg_nums.clear(); + m_set_names.clear(); + m_value_regs_map.clear(); + m_invalidate_regs_map.clear(); + m_reg_data_byte_size = 0; + m_finalized = false; +} + +void DynamicRegisterInfo::Dump() const { + StreamFile s(stdout, false); + const size_t num_regs = m_regs.size(); + s.Printf("%p: DynamicRegisterInfo contains %" PRIu64 " registers:\n", + static_cast<const void *>(this), static_cast<uint64_t>(num_regs)); + for (size_t i = 0; i < num_regs; ++i) { + s.Printf("[%3" PRIu64 "] name = %-10s", (uint64_t)i, m_regs[i].name); + s.Printf(", size = %2u, offset = %4u, encoding = %u, format = %-10s", + m_regs[i].byte_size, m_regs[i].byte_offset, m_regs[i].encoding, + FormatManager::GetFormatAsCString(m_regs[i].format)); + if (m_regs[i].kinds[eRegisterKindProcessPlugin] != LLDB_INVALID_REGNUM) + s.Printf(", process plugin = %3u", + m_regs[i].kinds[eRegisterKindProcessPlugin]); + if (m_regs[i].kinds[eRegisterKindDWARF] != LLDB_INVALID_REGNUM) + s.Printf(", dwarf = %3u", m_regs[i].kinds[eRegisterKindDWARF]); + if (m_regs[i].kinds[eRegisterKindEHFrame] != LLDB_INVALID_REGNUM) + s.Printf(", ehframe = %3u", m_regs[i].kinds[eRegisterKindEHFrame]); + if (m_regs[i].kinds[eRegisterKindGeneric] != LLDB_INVALID_REGNUM) + s.Printf(", generic = %3u", m_regs[i].kinds[eRegisterKindGeneric]); + if (m_regs[i].alt_name) + s.Printf(", alt-name = %s", m_regs[i].alt_name); + if (m_regs[i].value_regs) { + s.Printf(", value_regs = [ "); + for (size_t j = 0; m_regs[i].value_regs[j] != LLDB_INVALID_REGNUM; ++j) { + s.Printf("%s ", m_regs[m_regs[i].value_regs[j]].name); + } + s.Printf("]"); + } + if (m_regs[i].invalidate_regs) { + s.Printf(", invalidate_regs = [ "); + for (size_t j = 0; m_regs[i].invalidate_regs[j] != LLDB_INVALID_REGNUM; + ++j) { + s.Printf("%s ", m_regs[m_regs[i].invalidate_regs[j]].name); + } + s.Printf("]"); + } + s.EOL(); + } + + const size_t num_sets = m_sets.size(); + s.Printf("%p: DynamicRegisterInfo contains %" PRIu64 " register sets:\n", + static_cast<const void *>(this), static_cast<uint64_t>(num_sets)); + for (size_t i = 0; i < num_sets; ++i) { + s.Printf("set[%" PRIu64 "] name = %s, regs = [", (uint64_t)i, + m_sets[i].name); + for (size_t idx = 0; idx < m_sets[i].num_registers; ++idx) { + s.Printf("%s ", m_regs[m_sets[i].registers[idx]].name); + } + s.Printf("]\n"); + } +} + +const lldb_private::RegisterInfo * +DynamicRegisterInfo::GetRegisterInfo(llvm::StringRef reg_name) const { + for (auto ®_info : m_regs) + if (reg_info.name == reg_name) + return ®_info; + return nullptr; +} + +void lldb_private::addSupplementaryRegister( + std::vector<DynamicRegisterInfo::Register> ®s, + DynamicRegisterInfo::Register new_reg_info) { + assert(!new_reg_info.value_regs.empty()); + const uint32_t reg_num = regs.size(); + regs.push_back(new_reg_info); + + std::map<uint32_t, std::vector<uint32_t>> new_invalidates; + for (uint32_t value_reg : new_reg_info.value_regs) { + // copy value_regs to invalidate_regs + new_invalidates[reg_num].push_back(value_reg); + + // copy invalidate_regs from the parent register + llvm::append_range(new_invalidates[reg_num], + regs[value_reg].invalidate_regs); + + // add reverse invalidate entries + for (uint32_t x : new_invalidates[reg_num]) + new_invalidates[x].push_back(reg_num); + } + + for (const auto &x : new_invalidates) + llvm::append_range(regs[x.first].invalidate_regs, x.second); +} diff --git a/contrib/llvm-project/lldb/source/Target/ExecutionContext.cpp b/contrib/llvm-project/lldb/source/Target/ExecutionContext.cpp new file mode 100644 index 000000000000..b1563d9ceb71 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ExecutionContext.cpp @@ -0,0 +1,613 @@ +//===-- ExecutionContext.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 "lldb/Target/ExecutionContext.h" +#include "lldb/Target/ExecutionContextScope.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/State.h" + +using namespace lldb_private; + +ExecutionContext::ExecutionContext() + : m_target_sp(), m_process_sp(), m_thread_sp(), m_frame_sp() {} + +ExecutionContext::ExecutionContext(const ExecutionContext &rhs) = default; + +ExecutionContext::ExecutionContext(const lldb::TargetSP &target_sp, + bool get_process) + : m_target_sp(), m_process_sp(), m_thread_sp(), m_frame_sp() { + if (target_sp) + SetContext(target_sp, get_process); +} + +ExecutionContext::ExecutionContext(const lldb::ProcessSP &process_sp) + : m_target_sp(), m_process_sp(), m_thread_sp(), m_frame_sp() { + if (process_sp) + SetContext(process_sp); +} + +ExecutionContext::ExecutionContext(const lldb::ThreadSP &thread_sp) + : m_target_sp(), m_process_sp(), m_thread_sp(), m_frame_sp() { + if (thread_sp) + SetContext(thread_sp); +} + +ExecutionContext::ExecutionContext(const lldb::StackFrameSP &frame_sp) + : m_target_sp(), m_process_sp(), m_thread_sp(), m_frame_sp() { + if (frame_sp) + SetContext(frame_sp); +} + +ExecutionContext::ExecutionContext(const lldb::TargetWP &target_wp, + bool get_process) + : m_target_sp(), m_process_sp(), m_thread_sp(), m_frame_sp() { + lldb::TargetSP target_sp(target_wp.lock()); + if (target_sp) + SetContext(target_sp, get_process); +} + +ExecutionContext::ExecutionContext(const lldb::ProcessWP &process_wp) + : m_target_sp(), m_process_sp(), m_thread_sp(), m_frame_sp() { + lldb::ProcessSP process_sp(process_wp.lock()); + if (process_sp) + SetContext(process_sp); +} + +ExecutionContext::ExecutionContext(const lldb::ThreadWP &thread_wp) + : m_target_sp(), m_process_sp(), m_thread_sp(), m_frame_sp() { + lldb::ThreadSP thread_sp(thread_wp.lock()); + if (thread_sp) + SetContext(thread_sp); +} + +ExecutionContext::ExecutionContext(const lldb::StackFrameWP &frame_wp) + : m_target_sp(), m_process_sp(), m_thread_sp(), m_frame_sp() { + lldb::StackFrameSP frame_sp(frame_wp.lock()); + if (frame_sp) + SetContext(frame_sp); +} + +ExecutionContext::ExecutionContext(Target *t, + bool fill_current_process_thread_frame) + : m_target_sp(), m_process_sp(), m_thread_sp(), m_frame_sp() { + if (t) { + m_target_sp = t->shared_from_this(); + if (fill_current_process_thread_frame) { + m_process_sp = t->GetProcessSP(); + if (m_process_sp) { + m_thread_sp = m_process_sp->GetThreadList().GetSelectedThread(); + if (m_thread_sp) + m_frame_sp = + m_thread_sp->GetSelectedFrame(DoNoSelectMostRelevantFrame); + } + } + } +} + +ExecutionContext::ExecutionContext(Process *process, Thread *thread, + StackFrame *frame) + : m_target_sp(), m_process_sp(), m_thread_sp(), m_frame_sp() { + if (process) { + m_process_sp = process->shared_from_this(); + m_target_sp = process->GetTarget().shared_from_this(); + } + if (thread) + m_thread_sp = thread->shared_from_this(); + if (frame) + m_frame_sp = frame->shared_from_this(); +} + +ExecutionContext::ExecutionContext(const ExecutionContextRef &exe_ctx_ref) + : m_target_sp(exe_ctx_ref.GetTargetSP()), + m_process_sp(exe_ctx_ref.GetProcessSP()), + m_thread_sp(exe_ctx_ref.GetThreadSP()), + m_frame_sp(exe_ctx_ref.GetFrameSP()) {} + +ExecutionContext::ExecutionContext(const ExecutionContextRef *exe_ctx_ref_ptr, + bool thread_and_frame_only_if_stopped) + : m_target_sp(), m_process_sp(), m_thread_sp(), m_frame_sp() { + if (exe_ctx_ref_ptr) { + m_target_sp = exe_ctx_ref_ptr->GetTargetSP(); + m_process_sp = exe_ctx_ref_ptr->GetProcessSP(); + if (!thread_and_frame_only_if_stopped || + (m_process_sp && StateIsStoppedState(m_process_sp->GetState(), true))) { + m_thread_sp = exe_ctx_ref_ptr->GetThreadSP(); + m_frame_sp = exe_ctx_ref_ptr->GetFrameSP(); + } + } +} + +ExecutionContext::ExecutionContext(const ExecutionContextRef *exe_ctx_ref_ptr, + std::unique_lock<std::recursive_mutex> &lock) + : m_target_sp(), m_process_sp(), m_thread_sp(), m_frame_sp() { + if (exe_ctx_ref_ptr) { + m_target_sp = exe_ctx_ref_ptr->GetTargetSP(); + if (m_target_sp) { + lock = std::unique_lock<std::recursive_mutex>(m_target_sp->GetAPIMutex()); + + m_process_sp = exe_ctx_ref_ptr->GetProcessSP(); + m_thread_sp = exe_ctx_ref_ptr->GetThreadSP(); + m_frame_sp = exe_ctx_ref_ptr->GetFrameSP(); + } + } +} + +ExecutionContext::ExecutionContext(const ExecutionContextRef &exe_ctx_ref, + std::unique_lock<std::recursive_mutex> &lock) + : m_target_sp(exe_ctx_ref.GetTargetSP()), m_process_sp(), m_thread_sp(), + m_frame_sp() { + if (m_target_sp) { + lock = std::unique_lock<std::recursive_mutex>(m_target_sp->GetAPIMutex()); + + m_process_sp = exe_ctx_ref.GetProcessSP(); + m_thread_sp = exe_ctx_ref.GetThreadSP(); + m_frame_sp = exe_ctx_ref.GetFrameSP(); + } +} + +ExecutionContext::ExecutionContext(ExecutionContextScope *exe_scope_ptr) + : m_target_sp(), m_process_sp(), m_thread_sp(), m_frame_sp() { + if (exe_scope_ptr) + exe_scope_ptr->CalculateExecutionContext(*this); +} + +ExecutionContext::ExecutionContext(ExecutionContextScope &exe_scope_ref) { + exe_scope_ref.CalculateExecutionContext(*this); +} + +void ExecutionContext::Clear() { + m_target_sp.reset(); + m_process_sp.reset(); + m_thread_sp.reset(); + m_frame_sp.reset(); +} + +ExecutionContext::~ExecutionContext() = default; + +uint32_t ExecutionContext::GetAddressByteSize() const { + if (m_target_sp && m_target_sp->GetArchitecture().IsValid()) + return m_target_sp->GetArchitecture().GetAddressByteSize(); + if (m_process_sp) + return m_process_sp->GetAddressByteSize(); + return sizeof(void *); +} + +lldb::ByteOrder ExecutionContext::GetByteOrder() const { + if (m_target_sp && m_target_sp->GetArchitecture().IsValid()) + return m_target_sp->GetArchitecture().GetByteOrder(); + if (m_process_sp) + return m_process_sp->GetByteOrder(); + return endian::InlHostByteOrder(); +} + +RegisterContext *ExecutionContext::GetRegisterContext() const { + if (m_frame_sp) + return m_frame_sp->GetRegisterContext().get(); + else if (m_thread_sp) + return m_thread_sp->GetRegisterContext().get(); + return nullptr; +} + +Target *ExecutionContext::GetTargetPtr() const { + if (m_target_sp) + return m_target_sp.get(); + if (m_process_sp) + return &m_process_sp->GetTarget(); + return nullptr; +} + +Process *ExecutionContext::GetProcessPtr() const { + if (m_process_sp) + return m_process_sp.get(); + if (m_target_sp) + return m_target_sp->GetProcessSP().get(); + return nullptr; +} + +ExecutionContextScope *ExecutionContext::GetBestExecutionContextScope() const { + if (m_frame_sp) + return m_frame_sp.get(); + if (m_thread_sp) + return m_thread_sp.get(); + if (m_process_sp) + return m_process_sp.get(); + return m_target_sp.get(); +} + +Target &ExecutionContext::GetTargetRef() const { + assert(m_target_sp); + return *m_target_sp; +} + +Process &ExecutionContext::GetProcessRef() const { + assert(m_process_sp); + return *m_process_sp; +} + +Thread &ExecutionContext::GetThreadRef() const { + assert(m_thread_sp); + return *m_thread_sp; +} + +StackFrame &ExecutionContext::GetFrameRef() const { + assert(m_frame_sp); + return *m_frame_sp; +} + +void ExecutionContext::SetTargetSP(const lldb::TargetSP &target_sp) { + m_target_sp = target_sp; +} + +void ExecutionContext::SetProcessSP(const lldb::ProcessSP &process_sp) { + m_process_sp = process_sp; +} + +void ExecutionContext::SetThreadSP(const lldb::ThreadSP &thread_sp) { + m_thread_sp = thread_sp; +} + +void ExecutionContext::SetFrameSP(const lldb::StackFrameSP &frame_sp) { + m_frame_sp = frame_sp; +} + +void ExecutionContext::SetTargetPtr(Target *target) { + if (target) + m_target_sp = target->shared_from_this(); + else + m_target_sp.reset(); +} + +void ExecutionContext::SetProcessPtr(Process *process) { + if (process) + m_process_sp = process->shared_from_this(); + else + m_process_sp.reset(); +} + +void ExecutionContext::SetThreadPtr(Thread *thread) { + if (thread) + m_thread_sp = thread->shared_from_this(); + else + m_thread_sp.reset(); +} + +void ExecutionContext::SetFramePtr(StackFrame *frame) { + if (frame) + m_frame_sp = frame->shared_from_this(); + else + m_frame_sp.reset(); +} + +void ExecutionContext::SetContext(const lldb::TargetSP &target_sp, + bool get_process) { + m_target_sp = target_sp; + if (get_process && target_sp) + m_process_sp = target_sp->GetProcessSP(); + else + m_process_sp.reset(); + m_thread_sp.reset(); + m_frame_sp.reset(); +} + +void ExecutionContext::SetContext(const lldb::ProcessSP &process_sp) { + m_process_sp = process_sp; + if (process_sp) + m_target_sp = process_sp->GetTarget().shared_from_this(); + else + m_target_sp.reset(); + m_thread_sp.reset(); + m_frame_sp.reset(); +} + +void ExecutionContext::SetContext(const lldb::ThreadSP &thread_sp) { + m_frame_sp.reset(); + m_thread_sp = thread_sp; + if (thread_sp) { + m_process_sp = thread_sp->GetProcess(); + if (m_process_sp) + m_target_sp = m_process_sp->GetTarget().shared_from_this(); + else + m_target_sp.reset(); + } else { + m_target_sp.reset(); + m_process_sp.reset(); + } +} + +void ExecutionContext::SetContext(const lldb::StackFrameSP &frame_sp) { + m_frame_sp = frame_sp; + if (frame_sp) { + m_thread_sp = frame_sp->CalculateThread(); + if (m_thread_sp) { + m_process_sp = m_thread_sp->GetProcess(); + if (m_process_sp) + m_target_sp = m_process_sp->GetTarget().shared_from_this(); + else + m_target_sp.reset(); + } else { + m_target_sp.reset(); + m_process_sp.reset(); + } + } else { + m_target_sp.reset(); + m_process_sp.reset(); + m_thread_sp.reset(); + } +} + +ExecutionContext &ExecutionContext::operator=(const ExecutionContext &rhs) { + if (this != &rhs) { + m_target_sp = rhs.m_target_sp; + m_process_sp = rhs.m_process_sp; + m_thread_sp = rhs.m_thread_sp; + m_frame_sp = rhs.m_frame_sp; + } + return *this; +} + +bool ExecutionContext::operator==(const ExecutionContext &rhs) const { + // Check that the frame shared pointers match, or both are valid and their + // stack IDs match since sometimes we get new objects that represent the same + // frame within a thread. + if ((m_frame_sp == rhs.m_frame_sp) || + (m_frame_sp && rhs.m_frame_sp && + m_frame_sp->GetStackID() == rhs.m_frame_sp->GetStackID())) { + // Check that the thread shared pointers match, or both are valid and their + // thread IDs match since sometimes we get new objects that represent the + // same thread within a process. + if ((m_thread_sp == rhs.m_thread_sp) || + (m_thread_sp && rhs.m_thread_sp && + m_thread_sp->GetID() == rhs.m_thread_sp->GetID())) { + // Processes and targets don't change much + return m_process_sp == rhs.m_process_sp && m_target_sp == rhs.m_target_sp; + } + } + return false; +} + +bool ExecutionContext::operator!=(const ExecutionContext &rhs) const { + return !(*this == rhs); +} + +bool ExecutionContext::HasTargetScope() const { + return ((bool)m_target_sp && m_target_sp->IsValid()); +} + +bool ExecutionContext::HasProcessScope() const { + return (HasTargetScope() && ((bool)m_process_sp && m_process_sp->IsValid())); +} + +bool ExecutionContext::HasThreadScope() const { + return (HasProcessScope() && ((bool)m_thread_sp && m_thread_sp->IsValid())); +} + +bool ExecutionContext::HasFrameScope() const { + return HasThreadScope() && m_frame_sp; +} + +ExecutionContextRef::ExecutionContextRef() + : m_target_wp(), m_process_wp(), m_thread_wp(), m_stack_id() {} + +ExecutionContextRef::ExecutionContextRef(const ExecutionContext *exe_ctx) + : m_target_wp(), m_process_wp(), m_thread_wp(), m_stack_id() { + if (exe_ctx) + *this = *exe_ctx; +} + +ExecutionContextRef::ExecutionContextRef(const ExecutionContext &exe_ctx) + : m_target_wp(), m_process_wp(), m_thread_wp(), m_stack_id() { + *this = exe_ctx; +} + +ExecutionContextRef::ExecutionContextRef(Target *target, bool adopt_selected) + : m_target_wp(), m_process_wp(), m_thread_wp(), m_stack_id() { + SetTargetPtr(target, adopt_selected); +} + +ExecutionContextRef::ExecutionContextRef(const ExecutionContextRef &rhs) + + = default; + +ExecutionContextRef &ExecutionContextRef:: +operator=(const ExecutionContextRef &rhs) { + if (this != &rhs) { + m_target_wp = rhs.m_target_wp; + m_process_wp = rhs.m_process_wp; + m_thread_wp = rhs.m_thread_wp; + m_tid = rhs.m_tid; + m_stack_id = rhs.m_stack_id; + } + return *this; +} + +ExecutionContextRef &ExecutionContextRef:: +operator=(const ExecutionContext &exe_ctx) { + m_target_wp = exe_ctx.GetTargetSP(); + m_process_wp = exe_ctx.GetProcessSP(); + lldb::ThreadSP thread_sp(exe_ctx.GetThreadSP()); + m_thread_wp = thread_sp; + if (thread_sp) + m_tid = thread_sp->GetID(); + else + m_tid = LLDB_INVALID_THREAD_ID; + lldb::StackFrameSP frame_sp(exe_ctx.GetFrameSP()); + if (frame_sp) + m_stack_id = frame_sp->GetStackID(); + else + m_stack_id.Clear(); + return *this; +} + +void ExecutionContextRef::Clear() { + m_target_wp.reset(); + m_process_wp.reset(); + ClearThread(); + ClearFrame(); +} + +ExecutionContextRef::~ExecutionContextRef() = default; + +void ExecutionContextRef::SetTargetSP(const lldb::TargetSP &target_sp) { + m_target_wp = target_sp; +} + +void ExecutionContextRef::SetProcessSP(const lldb::ProcessSP &process_sp) { + if (process_sp) { + m_process_wp = process_sp; + SetTargetSP(process_sp->GetTarget().shared_from_this()); + } else { + m_process_wp.reset(); + m_target_wp.reset(); + } +} + +void ExecutionContextRef::SetThreadSP(const lldb::ThreadSP &thread_sp) { + if (thread_sp) { + m_thread_wp = thread_sp; + m_tid = thread_sp->GetID(); + SetProcessSP(thread_sp->GetProcess()); + } else { + ClearThread(); + m_process_wp.reset(); + m_target_wp.reset(); + } +} + +void ExecutionContextRef::SetFrameSP(const lldb::StackFrameSP &frame_sp) { + if (frame_sp) { + m_stack_id = frame_sp->GetStackID(); + SetThreadSP(frame_sp->GetThread()); + } else { + ClearFrame(); + ClearThread(); + m_process_wp.reset(); + m_target_wp.reset(); + } +} + +void ExecutionContextRef::SetTargetPtr(Target *target, bool adopt_selected) { + Clear(); + if (target) { + lldb::TargetSP target_sp(target->shared_from_this()); + if (target_sp) { + m_target_wp = target_sp; + if (adopt_selected) { + lldb::ProcessSP process_sp(target_sp->GetProcessSP()); + if (process_sp) { + m_process_wp = process_sp; + if (process_sp) { + // Only fill in the thread and frame if our process is stopped + // Don't just check the state, since we might be in the middle of + // resuming. + Process::StopLocker stop_locker; + + if (stop_locker.TryLock(&process_sp->GetRunLock()) && + StateIsStoppedState(process_sp->GetState(), true)) { + lldb::ThreadSP thread_sp( + process_sp->GetThreadList().GetSelectedThread()); + if (!thread_sp) + thread_sp = process_sp->GetThreadList().GetThreadAtIndex(0); + + if (thread_sp) { + SetThreadSP(thread_sp); + lldb::StackFrameSP frame_sp( + thread_sp->GetSelectedFrame(DoNoSelectMostRelevantFrame)); + if (!frame_sp) + frame_sp = thread_sp->GetStackFrameAtIndex(0); + if (frame_sp) + SetFrameSP(frame_sp); + } + } + } + } + } + } + } +} + +void ExecutionContextRef::SetProcessPtr(Process *process) { + if (process) { + SetProcessSP(process->shared_from_this()); + } else { + m_process_wp.reset(); + m_target_wp.reset(); + } +} + +void ExecutionContextRef::SetThreadPtr(Thread *thread) { + if (thread) { + SetThreadSP(thread->shared_from_this()); + } else { + ClearThread(); + m_process_wp.reset(); + m_target_wp.reset(); + } +} + +void ExecutionContextRef::SetFramePtr(StackFrame *frame) { + if (frame) + SetFrameSP(frame->shared_from_this()); + else + Clear(); +} + +lldb::TargetSP ExecutionContextRef::GetTargetSP() const { + lldb::TargetSP target_sp(m_target_wp.lock()); + if (target_sp && !target_sp->IsValid()) + target_sp.reset(); + return target_sp; +} + +lldb::ProcessSP ExecutionContextRef::GetProcessSP() const { + lldb::ProcessSP process_sp(m_process_wp.lock()); + if (process_sp && !process_sp->IsValid()) + process_sp.reset(); + return process_sp; +} + +lldb::ThreadSP ExecutionContextRef::GetThreadSP() const { + lldb::ThreadSP thread_sp(m_thread_wp.lock()); + + if (m_tid != LLDB_INVALID_THREAD_ID) { + // We check if the thread has been destroyed in cases where clients might + // still have shared pointer to a thread, but the thread is not valid + // anymore (not part of the process) + if (!thread_sp || !thread_sp->IsValid()) { + lldb::ProcessSP process_sp(GetProcessSP()); + if (process_sp && process_sp->IsValid()) { + thread_sp = process_sp->GetThreadList().FindThreadByID(m_tid); + m_thread_wp = thread_sp; + } + } + } + + // Check that we aren't about to return an invalid thread sp. We might + // return a nullptr thread_sp, but don't return an invalid one. + + if (thread_sp && !thread_sp->IsValid()) + thread_sp.reset(); + + return thread_sp; +} + +lldb::StackFrameSP ExecutionContextRef::GetFrameSP() const { + if (m_stack_id.IsValid()) { + lldb::ThreadSP thread_sp(GetThreadSP()); + if (thread_sp) + return thread_sp->GetFrameWithStackID(m_stack_id); + } + return lldb::StackFrameSP(); +} + +ExecutionContext +ExecutionContextRef::Lock(bool thread_and_frame_only_if_stopped) const { + return ExecutionContext(this, thread_and_frame_only_if_stopped); +} diff --git a/contrib/llvm-project/lldb/source/Target/InstrumentationRuntime.cpp b/contrib/llvm-project/lldb/source/Target/InstrumentationRuntime.cpp new file mode 100644 index 000000000000..9da06e8e155a --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/InstrumentationRuntime.cpp @@ -0,0 +1,77 @@ +//===-- InstrumentationRuntime.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 "lldb/Target/InstrumentationRuntime.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleList.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Target/Process.h" +#include "lldb/Utility/RegularExpression.h" +#include "lldb/lldb-private.h" + +using namespace lldb; +using namespace lldb_private; + +void InstrumentationRuntime::ModulesDidLoad( + lldb_private::ModuleList &module_list, lldb_private::Process *process, + InstrumentationRuntimeCollection &runtimes) { + InstrumentationRuntimeCreateInstance create_callback = nullptr; + InstrumentationRuntimeGetType get_type_callback; + for (uint32_t idx = 0;; ++idx) { + create_callback = + PluginManager::GetInstrumentationRuntimeCreateCallbackAtIndex(idx); + if (create_callback == nullptr) + break; + get_type_callback = + PluginManager::GetInstrumentationRuntimeGetTypeCallbackAtIndex(idx); + InstrumentationRuntimeType type = get_type_callback(); + + InstrumentationRuntimeCollection::iterator pos; + pos = runtimes.find(type); + if (pos == runtimes.end()) { + runtimes[type] = create_callback(process->shared_from_this()); + } + } +} + +void InstrumentationRuntime::ModulesDidLoad( + lldb_private::ModuleList &module_list) { + if (IsActive()) + return; + + if (GetRuntimeModuleSP()) { + Activate(); + return; + } + + module_list.ForEach([this](const lldb::ModuleSP module_sp) -> bool { + const FileSpec &file_spec = module_sp->GetFileSpec(); + if (!file_spec) + return true; // Keep iterating. + + const RegularExpression &runtime_regex = GetPatternForRuntimeLibrary(); + if (runtime_regex.Execute(file_spec.GetFilename().GetCString()) || + module_sp->IsExecutable()) { + if (CheckIfRuntimeIsValid(module_sp)) { + SetRuntimeModuleSP(module_sp); + Activate(); + if (!IsActive()) + SetRuntimeModuleSP({}); // Don't cache module if activation failed. + return false; // Stop iterating, we're done. + } + } + + return true; + }); +} + +lldb::ThreadCollectionSP +InstrumentationRuntime::GetBacktracesFromExtendedStopInfo( + StructuredData::ObjectSP info) { + return ThreadCollectionSP(new ThreadCollection()); +} diff --git a/contrib/llvm-project/lldb/source/Target/InstrumentationRuntimeStopInfo.cpp b/contrib/llvm-project/lldb/source/Target/InstrumentationRuntimeStopInfo.cpp new file mode 100644 index 000000000000..7f82581cc601 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/InstrumentationRuntimeStopInfo.cpp @@ -0,0 +1,36 @@ +//===-- InstrumentationRuntimeStopInfo.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 "lldb/Target/InstrumentationRuntimeStopInfo.h" + +#include "lldb/Target/InstrumentationRuntime.h" +#include "lldb/Target/Process.h" +#include "lldb/lldb-private.h" + +using namespace lldb; +using namespace lldb_private; + +InstrumentationRuntimeStopInfo::InstrumentationRuntimeStopInfo( + Thread &thread, std::string description, + StructuredData::ObjectSP additional_data) + : StopInfo(thread, 0) { + m_extended_info = additional_data; + m_description = description; +} + +const char *InstrumentationRuntimeStopInfo::GetDescription() { + return m_description.c_str(); +} + +StopInfoSP +InstrumentationRuntimeStopInfo::CreateStopReasonWithInstrumentationData( + Thread &thread, std::string description, + StructuredData::ObjectSP additionalData) { + return StopInfoSP( + new InstrumentationRuntimeStopInfo(thread, description, additionalData)); +} diff --git a/contrib/llvm-project/lldb/source/Target/JITLoader.cpp b/contrib/llvm-project/lldb/source/Target/JITLoader.cpp new file mode 100644 index 000000000000..8ff349e063b6 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/JITLoader.cpp @@ -0,0 +1,32 @@ +//===-- JITLoader.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 "lldb/Target/JITLoader.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Target/JITLoaderList.h" +#include "lldb/Target/Process.h" +#include "lldb/lldb-private.h" + +using namespace lldb; +using namespace lldb_private; + +void JITLoader::LoadPlugins(Process *process, JITLoaderList &list) { + JITLoaderCreateInstance create_callback = nullptr; + for (uint32_t idx = 0; + (create_callback = + PluginManager::GetJITLoaderCreateCallbackAtIndex(idx)) != nullptr; + ++idx) { + JITLoaderSP instance_sp(create_callback(process, false)); + if (instance_sp) + list.Append(std::move(instance_sp)); + } +} + +JITLoader::JITLoader(Process *process) : m_process(process) {} + +JITLoader::~JITLoader() = default; diff --git a/contrib/llvm-project/lldb/source/Target/JITLoaderList.cpp b/contrib/llvm-project/lldb/source/Target/JITLoaderList.cpp new file mode 100644 index 000000000000..9fa070edd4b8 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/JITLoaderList.cpp @@ -0,0 +1,53 @@ +//===-- JITLoaderList.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 "lldb/Target/JITLoader.h" +#include "lldb/Target/JITLoaderList.h" +#include "lldb/lldb-private.h" + +using namespace lldb; +using namespace lldb_private; + +JITLoaderList::JITLoaderList() : m_jit_loaders_vec(), m_jit_loaders_mutex() {} + +JITLoaderList::~JITLoaderList() = default; + +void JITLoaderList::Append(const JITLoaderSP &jit_loader_sp) { + std::lock_guard<std::recursive_mutex> guard(m_jit_loaders_mutex); + m_jit_loaders_vec.push_back(jit_loader_sp); +} + +void JITLoaderList::Remove(const JITLoaderSP &jit_loader_sp) { + std::lock_guard<std::recursive_mutex> guard(m_jit_loaders_mutex); + llvm::erase(m_jit_loaders_vec, jit_loader_sp); +} + +size_t JITLoaderList::GetSize() const { return m_jit_loaders_vec.size(); } + +JITLoaderSP JITLoaderList::GetLoaderAtIndex(size_t idx) { + std::lock_guard<std::recursive_mutex> guard(m_jit_loaders_mutex); + return m_jit_loaders_vec[idx]; +} + +void JITLoaderList::DidLaunch() { + std::lock_guard<std::recursive_mutex> guard(m_jit_loaders_mutex); + for (auto const &jit_loader : m_jit_loaders_vec) + jit_loader->DidLaunch(); +} + +void JITLoaderList::DidAttach() { + std::lock_guard<std::recursive_mutex> guard(m_jit_loaders_mutex); + for (auto const &jit_loader : m_jit_loaders_vec) + jit_loader->DidAttach(); +} + +void JITLoaderList::ModulesDidLoad(ModuleList &module_list) { + std::lock_guard<std::recursive_mutex> guard(m_jit_loaders_mutex); + for (auto const &jit_loader : m_jit_loaders_vec) + jit_loader->ModulesDidLoad(module_list); +} diff --git a/contrib/llvm-project/lldb/source/Target/Language.cpp b/contrib/llvm-project/lldb/source/Target/Language.cpp new file mode 100644 index 000000000000..d0bffe441f63 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/Language.cpp @@ -0,0 +1,568 @@ +//===-- Language.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 <functional> +#include <map> +#include <mutex> + +#include "lldb/Target/Language.h" + +#include "lldb/Core/PluginManager.h" +#include "lldb/Interpreter/OptionValueProperties.h" +#include "lldb/Symbol/SymbolFile.h" +#include "lldb/Symbol/TypeList.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/Stream.h" + +#include "llvm/BinaryFormat/Dwarf.h" +#include "llvm/Support/Threading.h" + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::formatters; + +typedef std::unique_ptr<Language> LanguageUP; +typedef std::map<lldb::LanguageType, LanguageUP> LanguagesMap; + +#define LLDB_PROPERTIES_language +#include "TargetProperties.inc" + +enum { +#define LLDB_PROPERTIES_language +#include "TargetPropertiesEnum.inc" +}; + +LanguageProperties &Language::GetGlobalLanguageProperties() { + static LanguageProperties g_settings; + return g_settings; +} + +llvm::StringRef LanguageProperties::GetSettingName() { + static constexpr llvm::StringLiteral g_setting_name("language"); + return g_setting_name; +} + +LanguageProperties::LanguageProperties() { + m_collection_sp = std::make_shared<OptionValueProperties>(GetSettingName()); + m_collection_sp->Initialize(g_language_properties); +} + +bool LanguageProperties::GetEnableFilterForLineBreakpoints() const { + const uint32_t idx = ePropertyEnableFilterForLineBreakpoints; + return GetPropertyAtIndexAs<bool>( + idx, g_language_properties[idx].default_uint_value != 0); +} + +static LanguagesMap &GetLanguagesMap() { + static LanguagesMap *g_map = nullptr; + static llvm::once_flag g_initialize; + + llvm::call_once(g_initialize, [] { + g_map = new LanguagesMap(); // NOTE: INTENTIONAL LEAK due to global + // destructor chain + }); + + return *g_map; +} +static std::mutex &GetLanguagesMutex() { + static std::mutex *g_mutex = nullptr; + static llvm::once_flag g_initialize; + + llvm::call_once(g_initialize, [] { + g_mutex = new std::mutex(); // NOTE: INTENTIONAL LEAK due to global + // destructor chain + }); + + return *g_mutex; +} + +Language *Language::FindPlugin(lldb::LanguageType language) { + std::lock_guard<std::mutex> guard(GetLanguagesMutex()); + LanguagesMap &map(GetLanguagesMap()); + auto iter = map.find(language), end = map.end(); + if (iter != end) + return iter->second.get(); + + Language *language_ptr = nullptr; + LanguageCreateInstance create_callback; + + for (uint32_t idx = 0; + (create_callback = + PluginManager::GetLanguageCreateCallbackAtIndex(idx)) != nullptr; + ++idx) { + language_ptr = create_callback(language); + + if (language_ptr) { + map[language] = std::unique_ptr<Language>(language_ptr); + return language_ptr; + } + } + + return nullptr; +} + +Language *Language::FindPlugin(llvm::StringRef file_path) { + Language *result = nullptr; + ForEach([&result, file_path](Language *language) { + if (language->IsSourceFile(file_path)) { + result = language; + return false; + } + return true; + }); + return result; +} + +Language *Language::FindPlugin(LanguageType language, + llvm::StringRef file_path) { + Language *result = FindPlugin(language); + // Finding a language by file path is slower, we so we use this as the + // fallback. + if (!result) + result = FindPlugin(file_path); + return result; +} + +void Language::ForEach(std::function<bool(Language *)> callback) { + // If we want to iterate over all languages, we first have to complete the + // LanguagesMap. + static llvm::once_flag g_initialize; + llvm::call_once(g_initialize, [] { + for (unsigned lang = eLanguageTypeUnknown; lang < eNumLanguageTypes; + ++lang) { + FindPlugin(static_cast<lldb::LanguageType>(lang)); + } + }); + + // callback may call a method in Language that attempts to acquire the same + // lock (such as Language::ForEach or Language::FindPlugin). To avoid a + // deadlock, we do not use callback while holding the lock. + std::vector<Language *> loaded_plugins; + { + std::lock_guard<std::mutex> guard(GetLanguagesMutex()); + LanguagesMap &map(GetLanguagesMap()); + for (const auto &entry : map) { + if (entry.second) + loaded_plugins.push_back(entry.second.get()); + } + } + + for (auto *lang : loaded_plugins) { + if (!callback(lang)) + break; + } +} + +bool Language::IsTopLevelFunction(Function &function) { return false; } + +lldb::TypeCategoryImplSP Language::GetFormatters() { return nullptr; } + +HardcodedFormatters::HardcodedFormatFinder Language::GetHardcodedFormats() { + return {}; +} + +HardcodedFormatters::HardcodedSummaryFinder Language::GetHardcodedSummaries() { + return {}; +} + +HardcodedFormatters::HardcodedSyntheticFinder +Language::GetHardcodedSynthetics() { + return {}; +} + +std::vector<FormattersMatchCandidate> +Language::GetPossibleFormattersMatches(ValueObject &valobj, + lldb::DynamicValueType use_dynamic) { + return {}; +} + +struct language_name_pair { + const char *name; + LanguageType type; +}; + +struct language_name_pair language_names[] = { + // To allow GetNameForLanguageType to be a simple array lookup, the first + // part of this array must follow enum LanguageType exactly. + {"unknown", eLanguageTypeUnknown}, + {"c89", eLanguageTypeC89}, + {"c", eLanguageTypeC}, + {"ada83", eLanguageTypeAda83}, + {"c++", eLanguageTypeC_plus_plus}, + {"cobol74", eLanguageTypeCobol74}, + {"cobol85", eLanguageTypeCobol85}, + {"fortran77", eLanguageTypeFortran77}, + {"fortran90", eLanguageTypeFortran90}, + {"pascal83", eLanguageTypePascal83}, + {"modula2", eLanguageTypeModula2}, + {"java", eLanguageTypeJava}, + {"c99", eLanguageTypeC99}, + {"ada95", eLanguageTypeAda95}, + {"fortran95", eLanguageTypeFortran95}, + {"pli", eLanguageTypePLI}, + {"objective-c", eLanguageTypeObjC}, + {"objective-c++", eLanguageTypeObjC_plus_plus}, + {"upc", eLanguageTypeUPC}, + {"d", eLanguageTypeD}, + {"python", eLanguageTypePython}, + {"opencl", eLanguageTypeOpenCL}, + {"go", eLanguageTypeGo}, + {"modula3", eLanguageTypeModula3}, + {"haskell", eLanguageTypeHaskell}, + {"c++03", eLanguageTypeC_plus_plus_03}, + {"c++11", eLanguageTypeC_plus_plus_11}, + {"ocaml", eLanguageTypeOCaml}, + {"rust", eLanguageTypeRust}, + {"c11", eLanguageTypeC11}, + {"swift", eLanguageTypeSwift}, + {"julia", eLanguageTypeJulia}, + {"dylan", eLanguageTypeDylan}, + {"c++14", eLanguageTypeC_plus_plus_14}, + {"fortran03", eLanguageTypeFortran03}, + {"fortran08", eLanguageTypeFortran08}, + {"renderscript", eLanguageTypeRenderScript}, + {"bliss", eLanguageTypeBLISS}, + {"kotlin", eLanguageTypeKotlin}, + {"zig", eLanguageTypeZig}, + {"crystal", eLanguageTypeCrystal}, + {"<invalid language>", + static_cast<LanguageType>( + 0x0029)}, // Not yet taken by any language in the DWARF spec + // and thus has no entry in LanguageType + {"c++17", eLanguageTypeC_plus_plus_17}, + {"c++20", eLanguageTypeC_plus_plus_20}, + {"c17", eLanguageTypeC17}, + {"fortran18", eLanguageTypeFortran18}, + {"ada2005", eLanguageTypeAda2005}, + {"ada2012", eLanguageTypeAda2012}, + {"HIP", eLanguageTypeHIP}, + {"assembly", eLanguageTypeAssembly}, + {"c-sharp", eLanguageTypeC_sharp}, + {"mojo", eLanguageTypeMojo}, + // Vendor Extensions + {"assembler", eLanguageTypeMipsAssembler}, + // Now synonyms, in arbitrary order + {"objc", eLanguageTypeObjC}, + {"objc++", eLanguageTypeObjC_plus_plus}, + {"pascal", eLanguageTypePascal83}}; + +static uint32_t num_languages = + sizeof(language_names) / sizeof(struct language_name_pair); + +LanguageType Language::GetLanguageTypeFromString(llvm::StringRef string) { + for (const auto &L : language_names) { + if (string.equals_insensitive(L.name)) + return static_cast<LanguageType>(L.type); + } + + return eLanguageTypeUnknown; +} + +const char *Language::GetNameForLanguageType(LanguageType language) { + if (language < num_languages) + return language_names[language].name; + else + return language_names[eLanguageTypeUnknown].name; +} + +void Language::PrintSupportedLanguagesForExpressions(Stream &s, + llvm::StringRef prefix, + llvm::StringRef suffix) { + auto supported = Language::GetLanguagesSupportingTypeSystemsForExpressions(); + for (size_t idx = 0; idx < num_languages; ++idx) { + auto const &lang = language_names[idx]; + if (supported[lang.type]) + s << prefix << lang.name << suffix; + } +} + +void Language::PrintAllLanguages(Stream &s, const char *prefix, + const char *suffix) { + for (uint32_t i = 1; i < num_languages; i++) { + s.Printf("%s%s%s", prefix, language_names[i].name, suffix); + } +} + +void Language::ForAllLanguages( + std::function<bool(lldb::LanguageType)> callback) { + for (uint32_t i = 1; i < num_languages; i++) { + if (!callback(language_names[i].type)) + break; + } +} + +bool Language::LanguageIsCPlusPlus(LanguageType language) { + switch (language) { + case eLanguageTypeC_plus_plus: + case eLanguageTypeC_plus_plus_03: + case eLanguageTypeC_plus_plus_11: + case eLanguageTypeC_plus_plus_14: + case eLanguageTypeC_plus_plus_17: + case eLanguageTypeC_plus_plus_20: + case eLanguageTypeObjC_plus_plus: + return true; + default: + return false; + } +} + +bool Language::LanguageIsObjC(LanguageType language) { + switch (language) { + case eLanguageTypeObjC: + case eLanguageTypeObjC_plus_plus: + return true; + default: + return false; + } +} + +bool Language::LanguageIsC(LanguageType language) { + switch (language) { + case eLanguageTypeC: + case eLanguageTypeC89: + case eLanguageTypeC99: + case eLanguageTypeC11: + return true; + default: + return false; + } +} + +bool Language::LanguageIsCFamily(LanguageType language) { + switch (language) { + case eLanguageTypeC: + case eLanguageTypeC89: + case eLanguageTypeC99: + case eLanguageTypeC11: + case eLanguageTypeC_plus_plus: + case eLanguageTypeC_plus_plus_03: + case eLanguageTypeC_plus_plus_11: + case eLanguageTypeC_plus_plus_14: + case eLanguageTypeC_plus_plus_17: + case eLanguageTypeC_plus_plus_20: + case eLanguageTypeObjC_plus_plus: + case eLanguageTypeObjC: + return true; + default: + return false; + } +} + +bool Language::LanguageIsPascal(LanguageType language) { + switch (language) { + case eLanguageTypePascal83: + return true; + default: + return false; + } +} + +LanguageType Language::GetPrimaryLanguage(LanguageType language) { + switch (language) { + case eLanguageTypeC_plus_plus: + case eLanguageTypeC_plus_plus_03: + case eLanguageTypeC_plus_plus_11: + case eLanguageTypeC_plus_plus_14: + case eLanguageTypeC_plus_plus_17: + case eLanguageTypeC_plus_plus_20: + return eLanguageTypeC_plus_plus; + case eLanguageTypeC: + case eLanguageTypeC89: + case eLanguageTypeC99: + case eLanguageTypeC11: + return eLanguageTypeC; + case eLanguageTypeObjC: + case eLanguageTypeObjC_plus_plus: + return eLanguageTypeObjC; + case eLanguageTypePascal83: + case eLanguageTypeCobol74: + case eLanguageTypeCobol85: + case eLanguageTypeFortran77: + case eLanguageTypeFortran90: + case eLanguageTypeFortran95: + case eLanguageTypeFortran03: + case eLanguageTypeFortran08: + case eLanguageTypeAda83: + case eLanguageTypeAda95: + case eLanguageTypeModula2: + case eLanguageTypeJava: + case eLanguageTypePLI: + case eLanguageTypeUPC: + case eLanguageTypeD: + case eLanguageTypePython: + case eLanguageTypeOpenCL: + case eLanguageTypeGo: + case eLanguageTypeModula3: + case eLanguageTypeHaskell: + case eLanguageTypeOCaml: + case eLanguageTypeRust: + case eLanguageTypeSwift: + case eLanguageTypeJulia: + case eLanguageTypeDylan: + case eLanguageTypeMipsAssembler: + case eLanguageTypeMojo: + case eLanguageTypeUnknown: + default: + return language; + } +} + +std::set<lldb::LanguageType> Language::GetSupportedLanguages() { + std::set<lldb::LanguageType> supported_languages; + ForEach([&](Language *lang) { + supported_languages.emplace(lang->GetLanguageType()); + return true; + }); + return supported_languages; +} + +LanguageSet Language::GetLanguagesSupportingTypeSystems() { + return PluginManager::GetAllTypeSystemSupportedLanguagesForTypes(); +} + +LanguageSet Language::GetLanguagesSupportingTypeSystemsForExpressions() { + return PluginManager::GetAllTypeSystemSupportedLanguagesForExpressions(); +} + +LanguageSet Language::GetLanguagesSupportingREPLs() { + return PluginManager::GetREPLAllTypeSystemSupportedLanguages(); +} + +std::unique_ptr<Language::TypeScavenger> Language::GetTypeScavenger() { + return nullptr; +} + +const char *Language::GetLanguageSpecificTypeLookupHelp() { return nullptr; } + +size_t Language::TypeScavenger::Find(ExecutionContextScope *exe_scope, + const char *key, ResultSet &results, + bool append) { + if (!exe_scope || !exe_scope->CalculateTarget().get()) + return false; + + if (!key || !key[0]) + return false; + + if (!append) + results.clear(); + + size_t old_size = results.size(); + + if (this->Find_Impl(exe_scope, key, results)) + return results.size() - old_size; + return 0; +} + +bool Language::ImageListTypeScavenger::Find_Impl( + ExecutionContextScope *exe_scope, const char *key, ResultSet &results) { + bool result = false; + + Target *target = exe_scope->CalculateTarget().get(); + if (target) { + const auto &images(target->GetImages()); + TypeQuery query(key); + TypeResults type_results; + images.FindTypes(nullptr, query, type_results); + for (const auto &match : type_results.GetTypeMap().Types()) { + if (match) { + CompilerType compiler_type(match->GetFullCompilerType()); + compiler_type = AdjustForInclusion(compiler_type); + if (!compiler_type) + continue; + std::unique_ptr<Language::TypeScavenger::Result> scavengeresult( + new Result(compiler_type)); + results.insert(std::move(scavengeresult)); + result = true; + } + } + } + + return result; +} + +std::pair<llvm::StringRef, llvm::StringRef> +Language::GetFormatterPrefixSuffix(llvm::StringRef type_hint) { + return std::pair<llvm::StringRef, llvm::StringRef>(); +} + +bool Language::DemangledNameContainsPath(llvm::StringRef path, + ConstString demangled) const { + // The base implementation does a simple contains comparision: + if (path.empty()) + return false; + return demangled.GetStringRef().contains(path); +} + +DumpValueObjectOptions::DeclPrintingHelper Language::GetDeclPrintingHelper() { + return nullptr; +} + +LazyBool Language::IsLogicalTrue(ValueObject &valobj, Status &error) { + return eLazyBoolCalculate; +} + +bool Language::IsNilReference(ValueObject &valobj) { return false; } + +bool Language::IsUninitializedReference(ValueObject &valobj) { return false; } + +bool Language::GetFunctionDisplayName(const SymbolContext *sc, + const ExecutionContext *exe_ctx, + FunctionNameRepresentation representation, + Stream &s) { + return false; +} + +void Language::GetExceptionResolverDescription(bool catch_on, bool throw_on, + Stream &s) { + GetDefaultExceptionResolverDescription(catch_on, throw_on, s); +} + +void Language::GetDefaultExceptionResolverDescription(bool catch_on, + bool throw_on, + Stream &s) { + s.Printf("Exception breakpoint (catch: %s throw: %s)", + catch_on ? "on" : "off", throw_on ? "on" : "off"); +} +// Constructor +Language::Language() = default; + +// Destructor +Language::~Language() = default; + +SourceLanguage::SourceLanguage(lldb::LanguageType language_type) { + auto lname = + llvm::dwarf::toDW_LNAME((llvm::dwarf::SourceLanguage)language_type); + if (!lname) + return; + name = lname->first; + version = lname->second; +} + +lldb::LanguageType SourceLanguage::AsLanguageType() const { + if (auto lang = llvm::dwarf::toDW_LANG((llvm::dwarf::SourceLanguageName)name, + version)) + return (lldb::LanguageType)*lang; + return lldb::eLanguageTypeUnknown; +} + +llvm::StringRef SourceLanguage::GetDescription() const { + LanguageType type = AsLanguageType(); + if (type) + return Language::GetNameForLanguageType(type); + return llvm::dwarf::LanguageDescription( + (llvm::dwarf::SourceLanguageName)name); +} +bool SourceLanguage::IsC() const { return name == llvm::dwarf::DW_LNAME_C; } + +bool SourceLanguage::IsObjC() const { + return name == llvm::dwarf::DW_LNAME_ObjC; +} + +bool SourceLanguage::IsCPlusPlus() const { + return name == llvm::dwarf::DW_LNAME_C_plus_plus; +} diff --git a/contrib/llvm-project/lldb/source/Target/LanguageRuntime.cpp b/contrib/llvm-project/lldb/source/Target/LanguageRuntime.cpp new file mode 100644 index 000000000000..ce3646c8b05c --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/LanguageRuntime.cpp @@ -0,0 +1,307 @@ +//===-- LanguageRuntime.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 "lldb/Target/LanguageRuntime.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/SearchFilter.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Target/Language.h" +#include "lldb/Target/Target.h" + +using namespace lldb; +using namespace lldb_private; + +char LanguageRuntime::ID = 0; + +ExceptionSearchFilter::ExceptionSearchFilter(const lldb::TargetSP &target_sp, + lldb::LanguageType language, + bool update_module_list) + : SearchFilter(target_sp, FilterTy::Exception), m_language(language), + m_language_runtime(nullptr), m_filter_sp() { + if (update_module_list) + UpdateModuleListIfNeeded(); +} + +bool ExceptionSearchFilter::ModulePasses(const lldb::ModuleSP &module_sp) { + UpdateModuleListIfNeeded(); + if (m_filter_sp) + return m_filter_sp->ModulePasses(module_sp); + return false; +} + +bool ExceptionSearchFilter::ModulePasses(const FileSpec &spec) { + UpdateModuleListIfNeeded(); + if (m_filter_sp) + return m_filter_sp->ModulePasses(spec); + return false; +} + +void ExceptionSearchFilter::Search(Searcher &searcher) { + UpdateModuleListIfNeeded(); + if (m_filter_sp) + m_filter_sp->Search(searcher); +} + +void ExceptionSearchFilter::GetDescription(Stream *s) { + UpdateModuleListIfNeeded(); + if (m_filter_sp) + m_filter_sp->GetDescription(s); +} + +void ExceptionSearchFilter::UpdateModuleListIfNeeded() { + ProcessSP process_sp(m_target_sp->GetProcessSP()); + if (process_sp) { + bool refreash_filter = !m_filter_sp; + if (m_language_runtime == nullptr) { + m_language_runtime = process_sp->GetLanguageRuntime(m_language); + refreash_filter = true; + } else { + LanguageRuntime *language_runtime = + process_sp->GetLanguageRuntime(m_language); + if (m_language_runtime != language_runtime) { + m_language_runtime = language_runtime; + refreash_filter = true; + } + } + + if (refreash_filter && m_language_runtime) { + m_filter_sp = m_language_runtime->CreateExceptionSearchFilter(); + } + } else { + m_filter_sp.reset(); + m_language_runtime = nullptr; + } +} + +SearchFilterSP ExceptionSearchFilter::DoCreateCopy() { + return SearchFilterSP( + new ExceptionSearchFilter(TargetSP(), m_language, false)); +} + +SearchFilter *ExceptionSearchFilter::CreateFromStructuredData( + Target &target, const StructuredData::Dictionary &data_dict, + Status &error) { + SearchFilter *result = nullptr; + return result; +} + +StructuredData::ObjectSP ExceptionSearchFilter::SerializeToStructuredData() { + StructuredData::ObjectSP result_sp; + + return result_sp; +} + +// The Target is the one that knows how to create breakpoints, so this function +// is meant to be used either by the target or internally in +// Set/ClearExceptionBreakpoints. +class ExceptionBreakpointResolver : public BreakpointResolver { +public: + ExceptionBreakpointResolver(lldb::LanguageType language, bool catch_bp, + bool throw_bp) + : BreakpointResolver(nullptr, BreakpointResolver::ExceptionResolver), + m_language(language), m_catch_bp(catch_bp), m_throw_bp(throw_bp) {} + + ~ExceptionBreakpointResolver() override = default; + + Searcher::CallbackReturn SearchCallback(SearchFilter &filter, + SymbolContext &context, + Address *addr) override { + + if (SetActualResolver()) + return m_actual_resolver_sp->SearchCallback(filter, context, addr); + else + return eCallbackReturnStop; + } + + lldb::SearchDepth GetDepth() override { + if (SetActualResolver()) + return m_actual_resolver_sp->GetDepth(); + else + return lldb::eSearchDepthTarget; + } + + void GetDescription(Stream *s) override { + Language *language_plugin = Language::FindPlugin(m_language); + if (language_plugin) + language_plugin->GetExceptionResolverDescription(m_catch_bp, m_throw_bp, + *s); + else + Language::GetDefaultExceptionResolverDescription(m_catch_bp, m_throw_bp, + *s); + + SetActualResolver(); + if (m_actual_resolver_sp) { + s->Printf(" using: "); + m_actual_resolver_sp->GetDescription(s); + } else + s->Printf(" the correct runtime exception handler will be determined " + "when you run"); + } + + void Dump(Stream *s) const override {} + + /// Methods for support type inquiry through isa, cast, and dyn_cast: + static inline bool classof(const BreakpointResolverName *) { return true; } + static inline bool classof(const BreakpointResolver *V) { + return V->getResolverID() == BreakpointResolver::ExceptionResolver; + } + +protected: + BreakpointResolverSP CopyForBreakpoint(BreakpointSP &breakpoint) override { + BreakpointResolverSP ret_sp( + new ExceptionBreakpointResolver(m_language, m_catch_bp, m_throw_bp)); + ret_sp->SetBreakpoint(breakpoint); + return ret_sp; + } + + bool SetActualResolver() { + BreakpointSP breakpoint_sp = GetBreakpoint(); + if (breakpoint_sp) { + ProcessSP process_sp = breakpoint_sp->GetTarget().GetProcessSP(); + if (process_sp) { + bool refreash_resolver = !m_actual_resolver_sp; + if (m_language_runtime == nullptr) { + m_language_runtime = process_sp->GetLanguageRuntime(m_language); + refreash_resolver = true; + } else { + LanguageRuntime *language_runtime = + process_sp->GetLanguageRuntime(m_language); + if (m_language_runtime != language_runtime) { + m_language_runtime = language_runtime; + refreash_resolver = true; + } + } + + if (refreash_resolver && m_language_runtime) { + m_actual_resolver_sp = m_language_runtime->CreateExceptionResolver( + breakpoint_sp, m_catch_bp, m_throw_bp); + } + } else { + m_actual_resolver_sp.reset(); + m_language_runtime = nullptr; + } + } else { + m_actual_resolver_sp.reset(); + m_language_runtime = nullptr; + } + return (bool)m_actual_resolver_sp; + } + + lldb::BreakpointResolverSP m_actual_resolver_sp; + lldb::LanguageType m_language; + LanguageRuntime *m_language_runtime = nullptr; + bool m_catch_bp; + bool m_throw_bp; +}; + +LanguageRuntime *LanguageRuntime::FindPlugin(Process *process, + lldb::LanguageType language) { + LanguageRuntimeCreateInstance create_callback; + for (uint32_t idx = 0; + (create_callback = + PluginManager::GetLanguageRuntimeCreateCallbackAtIndex(idx)) != + nullptr; + ++idx) { + if (LanguageRuntime *runtime = create_callback(process, language)) + return runtime; + } + return nullptr; +} + +LanguageRuntime::LanguageRuntime(Process *process) : Runtime(process) {} + +BreakpointPreconditionSP +LanguageRuntime::GetExceptionPrecondition(LanguageType language, + bool throw_bp) { + LanguageRuntimeCreateInstance create_callback; + for (uint32_t idx = 0; + (create_callback = + PluginManager::GetLanguageRuntimeCreateCallbackAtIndex(idx)) != + nullptr; + idx++) { + if (auto precondition_callback = + PluginManager::GetLanguageRuntimeGetExceptionPreconditionAtIndex( + idx)) { + if (BreakpointPreconditionSP precond = + precondition_callback(language, throw_bp)) + return precond; + } + } + return BreakpointPreconditionSP(); +} + +BreakpointSP LanguageRuntime::CreateExceptionBreakpoint( + Target &target, lldb::LanguageType language, bool catch_bp, bool throw_bp, + bool is_internal) { + BreakpointResolverSP resolver_sp( + new ExceptionBreakpointResolver(language, catch_bp, throw_bp)); + SearchFilterSP filter_sp( + new ExceptionSearchFilter(target.shared_from_this(), language)); + bool hardware = false; + bool resolve_indirect_functions = false; + BreakpointSP exc_breakpt_sp( + target.CreateBreakpoint(filter_sp, resolver_sp, is_internal, hardware, + resolve_indirect_functions)); + if (exc_breakpt_sp) { + if (auto precond = GetExceptionPrecondition(language, throw_bp)) + exc_breakpt_sp->SetPrecondition(precond); + + if (is_internal) + exc_breakpt_sp->SetBreakpointKind("exception"); + } + + return exc_breakpt_sp; +} + +UnwindPlanSP +LanguageRuntime::GetRuntimeUnwindPlan(Thread &thread, RegisterContext *regctx, + bool &behaves_like_zeroth_frame) { + ProcessSP process_sp = thread.GetProcess(); + if (!process_sp.get()) + return UnwindPlanSP(); + if (process_sp->GetDisableLangRuntimeUnwindPlans() == true) + return UnwindPlanSP(); + for (const lldb::LanguageType lang_type : Language::GetSupportedLanguages()) { + if (LanguageRuntime *runtime = process_sp->GetLanguageRuntime(lang_type)) { + UnwindPlanSP plan_sp = runtime->GetRuntimeUnwindPlan( + process_sp, regctx, behaves_like_zeroth_frame); + if (plan_sp.get()) + return plan_sp; + } + } + return UnwindPlanSP(); +} + +void LanguageRuntime::InitializeCommands(CommandObject *parent) { + if (!parent) + return; + + if (!parent->IsMultiwordObject()) + return; + + LanguageRuntimeCreateInstance create_callback; + + for (uint32_t idx = 0; + (create_callback = + PluginManager::GetLanguageRuntimeCreateCallbackAtIndex(idx)) != + nullptr; + ++idx) { + if (LanguageRuntimeGetCommandObject command_callback = + PluginManager::GetLanguageRuntimeGetCommandObjectAtIndex(idx)) { + CommandObjectSP command = + command_callback(parent->GetCommandInterpreter()); + if (command) { + // the CommandObject vended by a Language plugin cannot be created once + // and cached because we may create multiple debuggers and need one + // instance of the command each - the implementing function is meant to + // create a new instance of the command each time it is invoked. + parent->LoadSubCommand(command->GetCommandName().str().c_str(), command); + } + } + } +} diff --git a/contrib/llvm-project/lldb/source/Target/Memory.cpp b/contrib/llvm-project/lldb/source/Target/Memory.cpp new file mode 100644 index 000000000000..45786415d23b --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/Memory.cpp @@ -0,0 +1,434 @@ +//===-- Memory.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 "lldb/Target/Memory.h" +#include "lldb/Target/Process.h" +#include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/RangeMap.h" +#include "lldb/Utility/State.h" + +#include <cinttypes> +#include <memory> + +using namespace lldb; +using namespace lldb_private; + +// MemoryCache constructor +MemoryCache::MemoryCache(Process &process) + : m_mutex(), m_L1_cache(), m_L2_cache(), m_invalid_ranges(), + m_process(process), + m_L2_cache_line_byte_size(process.GetMemoryCacheLineSize()) {} + +// Destructor +MemoryCache::~MemoryCache() = default; + +void MemoryCache::Clear(bool clear_invalid_ranges) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + m_L1_cache.clear(); + m_L2_cache.clear(); + if (clear_invalid_ranges) + m_invalid_ranges.Clear(); + m_L2_cache_line_byte_size = m_process.GetMemoryCacheLineSize(); +} + +void MemoryCache::AddL1CacheData(lldb::addr_t addr, const void *src, + size_t src_len) { + AddL1CacheData( + addr, DataBufferSP(new DataBufferHeap(DataBufferHeap(src, src_len)))); +} + +void MemoryCache::AddL1CacheData(lldb::addr_t addr, + const DataBufferSP &data_buffer_sp) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + m_L1_cache[addr] = data_buffer_sp; +} + +void MemoryCache::Flush(addr_t addr, size_t size) { + if (size == 0) + return; + + std::lock_guard<std::recursive_mutex> guard(m_mutex); + + // Erase any blocks from the L1 cache that intersect with the flush range + if (!m_L1_cache.empty()) { + AddrRange flush_range(addr, size); + BlockMap::iterator pos = m_L1_cache.upper_bound(addr); + if (pos != m_L1_cache.begin()) { + --pos; + } + while (pos != m_L1_cache.end()) { + AddrRange chunk_range(pos->first, pos->second->GetByteSize()); + if (!chunk_range.DoesIntersect(flush_range)) + break; + pos = m_L1_cache.erase(pos); + } + } + + if (!m_L2_cache.empty()) { + const uint32_t cache_line_byte_size = m_L2_cache_line_byte_size; + const addr_t end_addr = (addr + size - 1); + const addr_t first_cache_line_addr = addr - (addr % cache_line_byte_size); + const addr_t last_cache_line_addr = + end_addr - (end_addr % cache_line_byte_size); + // Watch for overflow where size will cause us to go off the end of the + // 64 bit address space + uint32_t num_cache_lines; + if (last_cache_line_addr >= first_cache_line_addr) + num_cache_lines = ((last_cache_line_addr - first_cache_line_addr) / + cache_line_byte_size) + + 1; + else + num_cache_lines = + (UINT64_MAX - first_cache_line_addr + 1) / cache_line_byte_size; + + uint32_t cache_idx = 0; + for (addr_t curr_addr = first_cache_line_addr; cache_idx < num_cache_lines; + curr_addr += cache_line_byte_size, ++cache_idx) { + BlockMap::iterator pos = m_L2_cache.find(curr_addr); + if (pos != m_L2_cache.end()) + m_L2_cache.erase(pos); + } + } +} + +void MemoryCache::AddInvalidRange(lldb::addr_t base_addr, + lldb::addr_t byte_size) { + if (byte_size > 0) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + InvalidRanges::Entry range(base_addr, byte_size); + m_invalid_ranges.Append(range); + m_invalid_ranges.Sort(); + } +} + +bool MemoryCache::RemoveInvalidRange(lldb::addr_t base_addr, + lldb::addr_t byte_size) { + if (byte_size > 0) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + const uint32_t idx = m_invalid_ranges.FindEntryIndexThatContains(base_addr); + if (idx != UINT32_MAX) { + const InvalidRanges::Entry *entry = m_invalid_ranges.GetEntryAtIndex(idx); + if (entry->GetRangeBase() == base_addr && + entry->GetByteSize() == byte_size) + return m_invalid_ranges.RemoveEntryAtIndex(idx); + } + } + return false; +} + +lldb::DataBufferSP MemoryCache::GetL2CacheLine(lldb::addr_t line_base_addr, + Status &error) { + // This function assumes that the address given is aligned correctly. + assert((line_base_addr % m_L2_cache_line_byte_size) == 0); + + std::lock_guard<std::recursive_mutex> guard(m_mutex); + auto pos = m_L2_cache.find(line_base_addr); + if (pos != m_L2_cache.end()) + return pos->second; + + auto data_buffer_heap_sp = + std::make_shared<DataBufferHeap>(m_L2_cache_line_byte_size, 0); + size_t process_bytes_read = m_process.ReadMemoryFromInferior( + line_base_addr, data_buffer_heap_sp->GetBytes(), + data_buffer_heap_sp->GetByteSize(), error); + + // If we failed a read, not much we can do. + if (process_bytes_read == 0) + return lldb::DataBufferSP(); + + // If we didn't get a complete read, we can still cache what we did get. + if (process_bytes_read < m_L2_cache_line_byte_size) + data_buffer_heap_sp->SetByteSize(process_bytes_read); + + m_L2_cache[line_base_addr] = data_buffer_heap_sp; + return data_buffer_heap_sp; +} + +size_t MemoryCache::Read(addr_t addr, void *dst, size_t dst_len, + Status &error) { + if (!dst || dst_len == 0) + return 0; + + std::lock_guard<std::recursive_mutex> guard(m_mutex); + // FIXME: We should do a more thorough check to make sure that we're not + // overlapping with any invalid ranges (e.g. Read 0x100 - 0x200 but there's an + // invalid range 0x180 - 0x280). `FindEntryThatContains` has an implementation + // that takes a range, but it only checks to see if the argument is contained + // by an existing invalid range. It cannot check if the argument contains + // invalid ranges and cannot check for overlaps. + if (m_invalid_ranges.FindEntryThatContains(addr)) { + error.SetErrorStringWithFormat("memory read failed for 0x%" PRIx64, addr); + return 0; + } + + // Check the L1 cache for a range that contains the entire memory read. + // L1 cache contains chunks of memory that are not required to be the size of + // an L2 cache line. We avoid trying to do partial reads from the L1 cache to + // simplify the implementation. + if (!m_L1_cache.empty()) { + AddrRange read_range(addr, dst_len); + BlockMap::iterator pos = m_L1_cache.upper_bound(addr); + if (pos != m_L1_cache.begin()) { + --pos; + } + AddrRange chunk_range(pos->first, pos->second->GetByteSize()); + if (chunk_range.Contains(read_range)) { + memcpy(dst, pos->second->GetBytes() + (addr - chunk_range.GetRangeBase()), + dst_len); + return dst_len; + } + } + + // If the size of the read is greater than the size of an L2 cache line, we'll + // just read from the inferior. If that read is successful, we'll cache what + // we read in the L1 cache for future use. + if (dst_len > m_L2_cache_line_byte_size) { + size_t bytes_read = + m_process.ReadMemoryFromInferior(addr, dst, dst_len, error); + if (bytes_read > 0) + AddL1CacheData(addr, dst, bytes_read); + return bytes_read; + } + + // If the size of the read fits inside one L2 cache line, we'll try reading + // from the L2 cache. Note that if the range of memory we're reading sits + // between two contiguous cache lines, we'll touch two cache lines instead of + // just one. + + // We're going to have all of our loads and reads be cache line aligned. + addr_t cache_line_offset = addr % m_L2_cache_line_byte_size; + addr_t cache_line_base_addr = addr - cache_line_offset; + DataBufferSP first_cache_line = GetL2CacheLine(cache_line_base_addr, error); + // If we get nothing, then the read to the inferior likely failed. Nothing to + // do here. + if (!first_cache_line) + return 0; + + // If the cache line was not filled out completely and the offset is greater + // than what we have available, we can't do anything further here. + if (cache_line_offset >= first_cache_line->GetByteSize()) + return 0; + + uint8_t *dst_buf = (uint8_t *)dst; + size_t bytes_left = dst_len; + size_t read_size = first_cache_line->GetByteSize() - cache_line_offset; + if (read_size > bytes_left) + read_size = bytes_left; + + memcpy(dst_buf + dst_len - bytes_left, + first_cache_line->GetBytes() + cache_line_offset, read_size); + bytes_left -= read_size; + + // If the cache line was not filled out completely and we still have data to + // read, we can't do anything further. + if (first_cache_line->GetByteSize() < m_L2_cache_line_byte_size && + bytes_left > 0) + return dst_len - bytes_left; + + // We'll hit this scenario if our read straddles two cache lines. + if (bytes_left > 0) { + cache_line_base_addr += m_L2_cache_line_byte_size; + + // FIXME: Until we are able to more thoroughly check for invalid ranges, we + // will have to check the second line to see if it is in an invalid range as + // well. See the check near the beginning of the function for more details. + if (m_invalid_ranges.FindEntryThatContains(cache_line_base_addr)) { + error.SetErrorStringWithFormat("memory read failed for 0x%" PRIx64, + cache_line_base_addr); + return dst_len - bytes_left; + } + + DataBufferSP second_cache_line = + GetL2CacheLine(cache_line_base_addr, error); + if (!second_cache_line) + return dst_len - bytes_left; + + read_size = bytes_left; + if (read_size > second_cache_line->GetByteSize()) + read_size = second_cache_line->GetByteSize(); + + memcpy(dst_buf + dst_len - bytes_left, second_cache_line->GetBytes(), + read_size); + bytes_left -= read_size; + + return dst_len - bytes_left; + } + + return dst_len; +} + +AllocatedBlock::AllocatedBlock(lldb::addr_t addr, uint32_t byte_size, + uint32_t permissions, uint32_t chunk_size) + : m_range(addr, byte_size), m_permissions(permissions), + m_chunk_size(chunk_size) +{ + // The entire address range is free to start with. + m_free_blocks.Append(m_range); + assert(byte_size > chunk_size); +} + +AllocatedBlock::~AllocatedBlock() = default; + +lldb::addr_t AllocatedBlock::ReserveBlock(uint32_t size) { + // We must return something valid for zero bytes. + if (size == 0) + size = 1; + Log *log = GetLog(LLDBLog::Process); + + const size_t free_count = m_free_blocks.GetSize(); + for (size_t i=0; i<free_count; ++i) + { + auto &free_block = m_free_blocks.GetEntryRef(i); + const lldb::addr_t range_size = free_block.GetByteSize(); + if (range_size >= size) + { + // We found a free block that is big enough for our data. Figure out how + // many chunks we will need and calculate the resulting block size we + // will reserve. + addr_t addr = free_block.GetRangeBase(); + size_t num_chunks = CalculateChunksNeededForSize(size); + lldb::addr_t block_size = num_chunks * m_chunk_size; + lldb::addr_t bytes_left = range_size - block_size; + if (bytes_left == 0) + { + // The newly allocated block will take all of the bytes in this + // available block, so we can just add it to the allocated ranges and + // remove the range from the free ranges. + m_reserved_blocks.Insert(free_block, false); + m_free_blocks.RemoveEntryAtIndex(i); + } + else + { + // Make the new allocated range and add it to the allocated ranges. + Range<lldb::addr_t, uint32_t> reserved_block(free_block); + reserved_block.SetByteSize(block_size); + // Insert the reserved range and don't combine it with other blocks in + // the reserved blocks list. + m_reserved_blocks.Insert(reserved_block, false); + // Adjust the free range in place since we won't change the sorted + // ordering of the m_free_blocks list. + free_block.SetRangeBase(reserved_block.GetRangeEnd()); + free_block.SetByteSize(bytes_left); + } + LLDB_LOGV(log, "({0}) (size = {1} ({1:x})) => {2:x}", this, size, addr); + return addr; + } + } + + LLDB_LOGV(log, "({0}) (size = {1} ({1:x})) => {2:x}", this, size, + LLDB_INVALID_ADDRESS); + return LLDB_INVALID_ADDRESS; +} + +bool AllocatedBlock::FreeBlock(addr_t addr) { + bool success = false; + auto entry_idx = m_reserved_blocks.FindEntryIndexThatContains(addr); + if (entry_idx != UINT32_MAX) + { + m_free_blocks.Insert(m_reserved_blocks.GetEntryRef(entry_idx), true); + m_reserved_blocks.RemoveEntryAtIndex(entry_idx); + success = true; + } + Log *log = GetLog(LLDBLog::Process); + LLDB_LOGV(log, "({0}) (addr = {1:x}) => {2}", this, addr, success); + return success; +} + +AllocatedMemoryCache::AllocatedMemoryCache(Process &process) + : m_process(process), m_mutex(), m_memory_map() {} + +AllocatedMemoryCache::~AllocatedMemoryCache() = default; + +void AllocatedMemoryCache::Clear(bool deallocate_memory) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + if (m_process.IsAlive() && deallocate_memory) { + PermissionsToBlockMap::iterator pos, end = m_memory_map.end(); + for (pos = m_memory_map.begin(); pos != end; ++pos) + m_process.DoDeallocateMemory(pos->second->GetBaseAddress()); + } + m_memory_map.clear(); +} + +AllocatedMemoryCache::AllocatedBlockSP +AllocatedMemoryCache::AllocatePage(uint32_t byte_size, uint32_t permissions, + uint32_t chunk_size, Status &error) { + AllocatedBlockSP block_sp; + const size_t page_size = 4096; + const size_t num_pages = (byte_size + page_size - 1) / page_size; + const size_t page_byte_size = num_pages * page_size; + + addr_t addr = m_process.DoAllocateMemory(page_byte_size, permissions, error); + + Log *log = GetLog(LLDBLog::Process); + if (log) { + LLDB_LOGF(log, + "Process::DoAllocateMemory (byte_size = 0x%8.8" PRIx32 + ", permissions = %s) => 0x%16.16" PRIx64, + (uint32_t)page_byte_size, GetPermissionsAsCString(permissions), + (uint64_t)addr); + } + + if (addr != LLDB_INVALID_ADDRESS) { + block_sp = std::make_shared<AllocatedBlock>(addr, page_byte_size, + permissions, chunk_size); + m_memory_map.insert(std::make_pair(permissions, block_sp)); + } + return block_sp; +} + +lldb::addr_t AllocatedMemoryCache::AllocateMemory(size_t byte_size, + uint32_t permissions, + Status &error) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + + addr_t addr = LLDB_INVALID_ADDRESS; + std::pair<PermissionsToBlockMap::iterator, PermissionsToBlockMap::iterator> + range = m_memory_map.equal_range(permissions); + + for (PermissionsToBlockMap::iterator pos = range.first; pos != range.second; + ++pos) { + addr = (*pos).second->ReserveBlock(byte_size); + if (addr != LLDB_INVALID_ADDRESS) + break; + } + + if (addr == LLDB_INVALID_ADDRESS) { + AllocatedBlockSP block_sp(AllocatePage(byte_size, permissions, 16, error)); + + if (block_sp) + addr = block_sp->ReserveBlock(byte_size); + } + Log *log = GetLog(LLDBLog::Process); + LLDB_LOGF(log, + "AllocatedMemoryCache::AllocateMemory (byte_size = 0x%8.8" PRIx32 + ", permissions = %s) => 0x%16.16" PRIx64, + (uint32_t)byte_size, GetPermissionsAsCString(permissions), + (uint64_t)addr); + return addr; +} + +bool AllocatedMemoryCache::DeallocateMemory(lldb::addr_t addr) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + + PermissionsToBlockMap::iterator pos, end = m_memory_map.end(); + bool success = false; + for (pos = m_memory_map.begin(); pos != end; ++pos) { + if (pos->second->Contains(addr)) { + success = pos->second->FreeBlock(addr); + break; + } + } + Log *log = GetLog(LLDBLog::Process); + LLDB_LOGF(log, + "AllocatedMemoryCache::DeallocateMemory (addr = 0x%16.16" PRIx64 + ") => %i", + (uint64_t)addr, success); + return success; +} diff --git a/contrib/llvm-project/lldb/source/Target/MemoryHistory.cpp b/contrib/llvm-project/lldb/source/Target/MemoryHistory.cpp new file mode 100644 index 000000000000..0ebf796a56bb --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/MemoryHistory.cpp @@ -0,0 +1,28 @@ +//===-- MemoryHistory.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 "lldb/Target/MemoryHistory.h" +#include "lldb/Core/PluginManager.h" + +using namespace lldb; +using namespace lldb_private; + +lldb::MemoryHistorySP MemoryHistory::FindPlugin(const ProcessSP process) { + MemoryHistoryCreateInstance create_callback = nullptr; + + for (uint32_t idx = 0; + (create_callback = PluginManager::GetMemoryHistoryCreateCallbackAtIndex( + idx)) != nullptr; + ++idx) { + MemoryHistorySP memory_history_sp(create_callback(process)); + if (memory_history_sp) + return memory_history_sp; + } + + return MemoryHistorySP(); +} diff --git a/contrib/llvm-project/lldb/source/Target/MemoryRegionInfo.cpp b/contrib/llvm-project/lldb/source/Target/MemoryRegionInfo.cpp new file mode 100644 index 000000000000..0d5ebbdbe238 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/MemoryRegionInfo.cpp @@ -0,0 +1,40 @@ +//===-- MemoryRegionInfo.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 "lldb/Target/MemoryRegionInfo.h" + +using namespace lldb_private; + +llvm::raw_ostream &lldb_private::operator<<(llvm::raw_ostream &OS, + const MemoryRegionInfo &Info) { + return OS << llvm::formatv("MemoryRegionInfo([{0}, {1}), {2:r}{3:w}{4:x}, " + "{5}, `{6}`, {7}, {8}, {9})", + Info.GetRange().GetRangeBase(), + Info.GetRange().GetRangeEnd(), Info.GetReadable(), + Info.GetWritable(), Info.GetExecutable(), + Info.GetMapped(), Info.GetName(), Info.GetFlash(), + Info.GetBlocksize(), Info.GetMemoryTagged()); +} + +void llvm::format_provider<MemoryRegionInfo::OptionalBool>::format( + const MemoryRegionInfo::OptionalBool &B, raw_ostream &OS, + StringRef Options) { + assert(Options.size() <= 1); + bool Empty = Options.empty(); + switch (B) { + case lldb_private::MemoryRegionInfo::eNo: + OS << (Empty ? "no" : "-"); + return; + case lldb_private::MemoryRegionInfo::eYes: + OS << (Empty ? "yes" : Options); + return; + case lldb_private::MemoryRegionInfo::eDontKnow: + OS << (Empty ? "don't know" : "?"); + return; + } +} diff --git a/contrib/llvm-project/lldb/source/Target/MemoryTagMap.cpp b/contrib/llvm-project/lldb/source/Target/MemoryTagMap.cpp new file mode 100644 index 000000000000..c5a7c85ac09e --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/MemoryTagMap.cpp @@ -0,0 +1,65 @@ +//===-- MemoryTagMap.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 "lldb/Target/MemoryTagMap.h" +#include <optional> + +using namespace lldb_private; + +MemoryTagMap::MemoryTagMap(const MemoryTagManager *manager) + : m_manager(manager) { + assert(m_manager && "valid tag manager required to construct a MemoryTagMap"); +} + +void MemoryTagMap::InsertTags(lldb::addr_t addr, + const std::vector<lldb::addr_t> tags) { + // We're assuming that addr has no non address bits and is granule aligned. + size_t granule_size = m_manager->GetGranuleSize(); + for (auto tag : tags) { + m_addr_to_tag[addr] = tag; + addr += granule_size; + } +} + +bool MemoryTagMap::Empty() const { return m_addr_to_tag.empty(); } + +std::vector<std::optional<lldb::addr_t>> +MemoryTagMap::GetTags(lldb::addr_t addr, size_t len) const { + // Addr and len might be unaligned + addr = m_manager->RemoveTagBits(addr); + MemoryTagManager::TagRange range(addr, len); + range = m_manager->ExpandToGranule(range); + + std::vector<std::optional<lldb::addr_t>> tags; + lldb::addr_t end_addr = range.GetRangeEnd(); + addr = range.GetRangeBase(); + bool got_valid_tags = false; + size_t granule_size = m_manager->GetGranuleSize(); + + for (; addr < end_addr; addr += granule_size) { + std::optional<lldb::addr_t> tag = GetTag(addr); + tags.push_back(tag); + if (tag) + got_valid_tags = true; + } + + // To save the caller checking if every item is std::nullopt, + // we return an empty vector if we got no tags at all. + if (got_valid_tags) + return tags; + return {}; +} + +std::optional<lldb::addr_t> MemoryTagMap::GetTag(lldb::addr_t addr) const { + // Here we assume that addr is granule aligned, just like when the tags + // were inserted. + auto found = m_addr_to_tag.find(addr); + if (found == m_addr_to_tag.end()) + return std::nullopt; + return found->second; +} diff --git a/contrib/llvm-project/lldb/source/Target/ModuleCache.cpp b/contrib/llvm-project/lldb/source/Target/ModuleCache.cpp new file mode 100644 index 000000000000..29bd2b2394b5 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ModuleCache.cpp @@ -0,0 +1,336 @@ +//===-- ModuleCache.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 "lldb/Target/ModuleCache.h" + +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleList.h" +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Host/File.h" +#include "lldb/Host/LockFile.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/FileUtilities.h" + +#include <cassert> + +#include <cstdio> + +using namespace lldb; +using namespace lldb_private; + +namespace { + +const char *kModulesSubdir = ".cache"; +const char *kLockDirName = ".lock"; +const char *kTempFileName = ".temp"; +const char *kTempSymFileName = ".symtemp"; +const char *kSymFileExtension = ".sym"; +const char *kFSIllegalChars = "\\/:*?\"<>|"; + +std::string GetEscapedHostname(const char *hostname) { + if (hostname == nullptr) + hostname = "unknown"; + std::string result(hostname); + size_t size = result.size(); + for (size_t i = 0; i < size; ++i) { + if ((result[i] >= 1 && result[i] <= 31) || + strchr(kFSIllegalChars, result[i]) != nullptr) + result[i] = '_'; + } + return result; +} + +class ModuleLock { +private: + FileUP m_file_up; + std::unique_ptr<lldb_private::LockFile> m_lock; + FileSpec m_file_spec; + +public: + ModuleLock(const FileSpec &root_dir_spec, const UUID &uuid, Status &error); + void Delete(); +}; + +static FileSpec JoinPath(const FileSpec &path1, const char *path2) { + FileSpec result_spec(path1); + result_spec.AppendPathComponent(path2); + return result_spec; +} + +static Status MakeDirectory(const FileSpec &dir_path) { + namespace fs = llvm::sys::fs; + + return fs::create_directories(dir_path.GetPath(), true, fs::perms::owner_all); +} + +FileSpec GetModuleDirectory(const FileSpec &root_dir_spec, const UUID &uuid) { + const auto modules_dir_spec = JoinPath(root_dir_spec, kModulesSubdir); + return JoinPath(modules_dir_spec, uuid.GetAsString().c_str()); +} + +FileSpec GetSymbolFileSpec(const FileSpec &module_file_spec) { + return FileSpec(module_file_spec.GetPath() + kSymFileExtension); +} + +void DeleteExistingModule(const FileSpec &root_dir_spec, + const FileSpec &sysroot_module_path_spec) { + Log *log = GetLog(LLDBLog::Modules); + UUID module_uuid; + { + auto module_sp = + std::make_shared<Module>(ModuleSpec(sysroot_module_path_spec)); + module_uuid = module_sp->GetUUID(); + } + + if (!module_uuid.IsValid()) + return; + + Status error; + ModuleLock lock(root_dir_spec, module_uuid, error); + if (error.Fail()) { + LLDB_LOGF(log, "Failed to lock module %s: %s", + module_uuid.GetAsString().c_str(), error.AsCString()); + } + + namespace fs = llvm::sys::fs; + fs::file_status st; + if (status(sysroot_module_path_spec.GetPath(), st)) + return; + + if (st.getLinkCount() > 2) // module is referred by other hosts. + return; + + const auto module_spec_dir = GetModuleDirectory(root_dir_spec, module_uuid); + llvm::sys::fs::remove_directories(module_spec_dir.GetPath()); + lock.Delete(); +} + +void DecrementRefExistingModule(const FileSpec &root_dir_spec, + const FileSpec &sysroot_module_path_spec) { + // Remove $platform/.cache/$uuid folder if nobody else references it. + DeleteExistingModule(root_dir_spec, sysroot_module_path_spec); + + // Remove sysroot link. + llvm::sys::fs::remove(sysroot_module_path_spec.GetPath()); + + FileSpec symfile_spec = GetSymbolFileSpec(sysroot_module_path_spec); + llvm::sys::fs::remove(symfile_spec.GetPath()); +} + +Status CreateHostSysRootModuleLink(const FileSpec &root_dir_spec, + const char *hostname, + const FileSpec &platform_module_spec, + const FileSpec &local_module_spec, + bool delete_existing) { + const auto sysroot_module_path_spec = + JoinPath(JoinPath(root_dir_spec, hostname), + platform_module_spec.GetPath().c_str()); + if (FileSystem::Instance().Exists(sysroot_module_path_spec)) { + if (!delete_existing) + return Status(); + + DecrementRefExistingModule(root_dir_spec, sysroot_module_path_spec); + } + + const auto error = MakeDirectory( + FileSpec(sysroot_module_path_spec.GetDirectory().AsCString())); + if (error.Fail()) + return error; + + return llvm::sys::fs::create_hard_link(local_module_spec.GetPath(), + sysroot_module_path_spec.GetPath()); +} + +} // namespace + +ModuleLock::ModuleLock(const FileSpec &root_dir_spec, const UUID &uuid, + Status &error) { + const auto lock_dir_spec = JoinPath(root_dir_spec, kLockDirName); + error = MakeDirectory(lock_dir_spec); + if (error.Fail()) + return; + + m_file_spec = JoinPath(lock_dir_spec, uuid.GetAsString().c_str()); + + auto file = FileSystem::Instance().Open( + m_file_spec, File::eOpenOptionWriteOnly | File::eOpenOptionCanCreate | + File::eOpenOptionCloseOnExec); + if (file) + m_file_up = std::move(file.get()); + else { + m_file_up.reset(); + error = Status(file.takeError()); + return; + } + + m_lock = std::make_unique<lldb_private::LockFile>(m_file_up->GetDescriptor()); + error = m_lock->WriteLock(0, 1); + if (error.Fail()) + error.SetErrorStringWithFormat("Failed to lock file: %s", + error.AsCString()); +} + +void ModuleLock::Delete() { + if (!m_file_up) + return; + + m_file_up->Close(); + m_file_up.reset(); + llvm::sys::fs::remove(m_file_spec.GetPath()); +} + +///////////////////////////////////////////////////////////////////////// + +Status ModuleCache::Put(const FileSpec &root_dir_spec, const char *hostname, + const ModuleSpec &module_spec, const FileSpec &tmp_file, + const FileSpec &target_file) { + const auto module_spec_dir = + GetModuleDirectory(root_dir_spec, module_spec.GetUUID()); + const auto module_file_path = + JoinPath(module_spec_dir, target_file.GetFilename().AsCString()); + + const auto tmp_file_path = tmp_file.GetPath(); + const auto err_code = + llvm::sys::fs::rename(tmp_file_path, module_file_path.GetPath()); + if (err_code) + return Status("Failed to rename file %s to %s: %s", tmp_file_path.c_str(), + module_file_path.GetPath().c_str(), + err_code.message().c_str()); + + const auto error = CreateHostSysRootModuleLink( + root_dir_spec, hostname, target_file, module_file_path, true); + if (error.Fail()) + return Status("Failed to create link to %s: %s", + module_file_path.GetPath().c_str(), error.AsCString()); + return Status(); +} + +Status ModuleCache::Get(const FileSpec &root_dir_spec, const char *hostname, + const ModuleSpec &module_spec, + ModuleSP &cached_module_sp, bool *did_create_ptr) { + const auto find_it = + m_loaded_modules.find(module_spec.GetUUID().GetAsString()); + if (find_it != m_loaded_modules.end()) { + cached_module_sp = (*find_it).second.lock(); + if (cached_module_sp) + return Status(); + m_loaded_modules.erase(find_it); + } + + const auto module_spec_dir = + GetModuleDirectory(root_dir_spec, module_spec.GetUUID()); + const auto module_file_path = JoinPath( + module_spec_dir, module_spec.GetFileSpec().GetFilename().AsCString()); + + if (!FileSystem::Instance().Exists(module_file_path)) + return Status("Module %s not found", module_file_path.GetPath().c_str()); + if (FileSystem::Instance().GetByteSize(module_file_path) != + module_spec.GetObjectSize()) + return Status("Module %s has invalid file size", + module_file_path.GetPath().c_str()); + + // We may have already cached module but downloaded from an another host - in + // this case let's create a link to it. + auto error = CreateHostSysRootModuleLink(root_dir_spec, hostname, + module_spec.GetFileSpec(), + module_file_path, false); + if (error.Fail()) + return Status("Failed to create link to %s: %s", + module_file_path.GetPath().c_str(), error.AsCString()); + + auto cached_module_spec(module_spec); + cached_module_spec.GetUUID().Clear(); // Clear UUID since it may contain md5 + // content hash instead of real UUID. + cached_module_spec.GetFileSpec() = module_file_path; + cached_module_spec.GetPlatformFileSpec() = module_spec.GetFileSpec(); + + error = ModuleList::GetSharedModule(cached_module_spec, cached_module_sp, + nullptr, nullptr, did_create_ptr, false); + if (error.Fail()) + return error; + + FileSpec symfile_spec = GetSymbolFileSpec(cached_module_sp->GetFileSpec()); + if (FileSystem::Instance().Exists(symfile_spec)) + cached_module_sp->SetSymbolFileFileSpec(symfile_spec); + + m_loaded_modules.insert( + std::make_pair(module_spec.GetUUID().GetAsString(), cached_module_sp)); + + return Status(); +} + +Status ModuleCache::GetAndPut(const FileSpec &root_dir_spec, + const char *hostname, + const ModuleSpec &module_spec, + const ModuleDownloader &module_downloader, + const SymfileDownloader &symfile_downloader, + lldb::ModuleSP &cached_module_sp, + bool *did_create_ptr) { + const auto module_spec_dir = + GetModuleDirectory(root_dir_spec, module_spec.GetUUID()); + auto error = MakeDirectory(module_spec_dir); + if (error.Fail()) + return error; + + ModuleLock lock(root_dir_spec, module_spec.GetUUID(), error); + if (error.Fail()) + return Status("Failed to lock module %s: %s", + module_spec.GetUUID().GetAsString().c_str(), + error.AsCString()); + + const auto escaped_hostname(GetEscapedHostname(hostname)); + // Check local cache for a module. + error = Get(root_dir_spec, escaped_hostname.c_str(), module_spec, + cached_module_sp, did_create_ptr); + if (error.Success()) + return error; + + const auto tmp_download_file_spec = JoinPath(module_spec_dir, kTempFileName); + error = module_downloader(module_spec, tmp_download_file_spec); + llvm::FileRemover tmp_file_remover(tmp_download_file_spec.GetPath()); + if (error.Fail()) + return Status("Failed to download module: %s", error.AsCString()); + + // Put downloaded file into local module cache. + error = Put(root_dir_spec, escaped_hostname.c_str(), module_spec, + tmp_download_file_spec, module_spec.GetFileSpec()); + if (error.Fail()) + return Status("Failed to put module into cache: %s", error.AsCString()); + + tmp_file_remover.releaseFile(); + error = Get(root_dir_spec, escaped_hostname.c_str(), module_spec, + cached_module_sp, did_create_ptr); + if (error.Fail()) + return error; + + // Fetching a symbol file for the module + const auto tmp_download_sym_file_spec = + JoinPath(module_spec_dir, kTempSymFileName); + error = symfile_downloader(cached_module_sp, tmp_download_sym_file_spec); + llvm::FileRemover tmp_symfile_remover(tmp_download_sym_file_spec.GetPath()); + if (error.Fail()) + // Failed to download a symfile but fetching the module was successful. The + // module might contain the necessary symbols and the debugging is also + // possible without a symfile. + return Status(); + + error = Put(root_dir_spec, escaped_hostname.c_str(), module_spec, + tmp_download_sym_file_spec, + GetSymbolFileSpec(module_spec.GetFileSpec())); + if (error.Fail()) + return Status("Failed to put symbol file into cache: %s", + error.AsCString()); + + tmp_symfile_remover.releaseFile(); + + FileSpec symfile_spec = GetSymbolFileSpec(cached_module_sp->GetFileSpec()); + cached_module_sp->SetSymbolFileFileSpec(symfile_spec); + return Status(); +} diff --git a/contrib/llvm-project/lldb/source/Target/OperatingSystem.cpp b/contrib/llvm-project/lldb/source/Target/OperatingSystem.cpp new file mode 100644 index 000000000000..75762c05151d --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/OperatingSystem.cpp @@ -0,0 +1,51 @@ +//===-- OperatingSystem.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 "lldb/Target/OperatingSystem.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Target/Thread.h" + +using namespace lldb; +using namespace lldb_private; + +OperatingSystem *OperatingSystem::FindPlugin(Process *process, + const char *plugin_name) { + OperatingSystemCreateInstance create_callback = nullptr; + if (plugin_name) { + create_callback = + PluginManager::GetOperatingSystemCreateCallbackForPluginName( + plugin_name); + if (create_callback) { + std::unique_ptr<OperatingSystem> instance_up( + create_callback(process, true)); + if (instance_up) + return instance_up.release(); + } + } else { + for (uint32_t idx = 0; + (create_callback = + PluginManager::GetOperatingSystemCreateCallbackAtIndex(idx)) != + nullptr; + ++idx) { + std::unique_ptr<OperatingSystem> instance_up( + create_callback(process, false)); + if (instance_up) + return instance_up.release(); + } + } + return nullptr; +} + +OperatingSystem::OperatingSystem(Process *process) : m_process(process) {} + +bool OperatingSystem::IsOperatingSystemPluginThread( + const lldb::ThreadSP &thread_sp) { + if (thread_sp) + return thread_sp->IsOperatingSystemPluginThread(); + return false; +} diff --git a/contrib/llvm-project/lldb/source/Target/PathMappingList.cpp b/contrib/llvm-project/lldb/source/Target/PathMappingList.cpp new file mode 100644 index 000000000000..9c283b0146fe --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/PathMappingList.cpp @@ -0,0 +1,343 @@ +//===-- PathMappingList.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 <climits> +#include <cstring> +#include <optional> + +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/PosixApi.h" +#include "lldb/Target/PathMappingList.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/Stream.h" +#include "lldb/lldb-private-enumerations.h" + +using namespace lldb; +using namespace lldb_private; + +namespace { + // We must normalize our path pairs that we store because if we don't then + // things won't always work. We found a case where if we did: + // (lldb) settings set target.source-map . /tmp + // We would store a path pairs of "." and "/tmp" as raw strings. If the debug + // info contains "./foo/bar.c", the path will get normalized to "foo/bar.c". + // When PathMappingList::RemapPath() is called, it expects the path to start + // with the raw path pair, which doesn't work anymore because the paths have + // been normalized when the debug info was loaded. So we need to store + // nomalized path pairs to ensure things match up. +std::string NormalizePath(llvm::StringRef path) { + // If we use "path" to construct a FileSpec, it will normalize the path for + // us. We then grab the string. + return FileSpec(path).GetPath(); +} +} +// PathMappingList constructor +PathMappingList::PathMappingList() : m_pairs() {} + +PathMappingList::PathMappingList(ChangedCallback callback, void *callback_baton) + : m_pairs(), m_callback(callback), m_callback_baton(callback_baton) {} + +PathMappingList::PathMappingList(const PathMappingList &rhs) + : m_pairs(rhs.m_pairs) {} + +const PathMappingList &PathMappingList::operator=(const PathMappingList &rhs) { + if (this != &rhs) { + std::scoped_lock<std::recursive_mutex, std::recursive_mutex> locks(m_mutex, rhs.m_mutex); + m_pairs = rhs.m_pairs; + m_callback = nullptr; + m_callback_baton = nullptr; + m_mod_id = rhs.m_mod_id; + } + return *this; +} + +PathMappingList::~PathMappingList() = default; + +void PathMappingList::Append(llvm::StringRef path, llvm::StringRef replacement, + bool notify) { + std::lock_guard<std::recursive_mutex> lock(m_mutex); + ++m_mod_id; + m_pairs.emplace_back(pair(NormalizePath(path), NormalizePath(replacement))); + if (notify && m_callback) + m_callback(*this, m_callback_baton); +} + +void PathMappingList::Append(const PathMappingList &rhs, bool notify) { + std::scoped_lock<std::recursive_mutex, std::recursive_mutex> locks(m_mutex, rhs.m_mutex); + ++m_mod_id; + if (!rhs.m_pairs.empty()) { + const_iterator pos, end = rhs.m_pairs.end(); + for (pos = rhs.m_pairs.begin(); pos != end; ++pos) + m_pairs.push_back(*pos); + if (notify && m_callback) + m_callback(*this, m_callback_baton); + } +} + +bool PathMappingList::AppendUnique(llvm::StringRef path, + llvm::StringRef replacement, bool notify) { + auto normalized_path = NormalizePath(path); + auto normalized_replacement = NormalizePath(replacement); + std::lock_guard<std::recursive_mutex> lock(m_mutex); + for (const auto &pair : m_pairs) { + if (pair.first.GetStringRef() == normalized_path && + pair.second.GetStringRef() == normalized_replacement) + return false; + } + Append(path, replacement, notify); + return true; +} + +void PathMappingList::Insert(llvm::StringRef path, llvm::StringRef replacement, + uint32_t index, bool notify) { + std::lock_guard<std::recursive_mutex> lock(m_mutex); + ++m_mod_id; + iterator insert_iter; + if (index >= m_pairs.size()) + insert_iter = m_pairs.end(); + else + insert_iter = m_pairs.begin() + index; + m_pairs.emplace(insert_iter, pair(NormalizePath(path), + NormalizePath(replacement))); + if (notify && m_callback) + m_callback(*this, m_callback_baton); +} + +bool PathMappingList::Replace(llvm::StringRef path, llvm::StringRef replacement, + uint32_t index, bool notify) { + std::lock_guard<std::recursive_mutex> lock(m_mutex); + if (index >= m_pairs.size()) + return false; + ++m_mod_id; + m_pairs[index] = pair(NormalizePath(path), NormalizePath(replacement)); + if (notify && m_callback) + m_callback(*this, m_callback_baton); + return true; +} + +bool PathMappingList::Remove(size_t index, bool notify) { + std::lock_guard<std::recursive_mutex> lock(m_mutex); + if (index >= m_pairs.size()) + return false; + + ++m_mod_id; + iterator iter = m_pairs.begin() + index; + m_pairs.erase(iter); + if (notify && m_callback) + m_callback(*this, m_callback_baton); + return true; +} + +// For clients which do not need the pair index dumped, pass a pair_index >= 0 +// to only dump the indicated pair. +void PathMappingList::Dump(Stream *s, int pair_index) { + std::lock_guard<std::recursive_mutex> lock(m_mutex); + unsigned int numPairs = m_pairs.size(); + + if (pair_index < 0) { + unsigned int index; + for (index = 0; index < numPairs; ++index) + s->Printf("[%d] \"%s\" -> \"%s\"\n", index, + m_pairs[index].first.GetCString(), + m_pairs[index].second.GetCString()); + } else { + if (static_cast<unsigned int>(pair_index) < numPairs) + s->Printf("%s -> %s", m_pairs[pair_index].first.GetCString(), + m_pairs[pair_index].second.GetCString()); + } +} + +llvm::json::Value PathMappingList::ToJSON() { + llvm::json::Array entries; + std::lock_guard<std::recursive_mutex> lock(m_mutex); + for (const auto &pair : m_pairs) { + llvm::json::Array entry{pair.first.GetStringRef().str(), + pair.second.GetStringRef().str()}; + entries.emplace_back(std::move(entry)); + } + return entries; +} + +void PathMappingList::Clear(bool notify) { + std::lock_guard<std::recursive_mutex> lock(m_mutex); + if (!m_pairs.empty()) + ++m_mod_id; + m_pairs.clear(); + if (notify && m_callback) + m_callback(*this, m_callback_baton); +} + +bool PathMappingList::RemapPath(ConstString path, + ConstString &new_path) const { + if (std::optional<FileSpec> remapped = RemapPath(path.GetStringRef())) { + new_path.SetString(remapped->GetPath()); + return true; + } + return false; +} + +/// Append components to path, applying style. +static void AppendPathComponents(FileSpec &path, llvm::StringRef components, + llvm::sys::path::Style style) { + auto component = llvm::sys::path::begin(components, style); + auto e = llvm::sys::path::end(components); + while (component != e && + llvm::sys::path::is_separator(*component->data(), style)) + ++component; + for (; component != e; ++component) + path.AppendPathComponent(*component); +} + +std::optional<FileSpec> PathMappingList::RemapPath(llvm::StringRef mapping_path, + bool only_if_exists) const { + std::lock_guard<std::recursive_mutex> lock(m_mutex); + if (m_pairs.empty() || mapping_path.empty()) + return {}; + LazyBool path_is_relative = eLazyBoolCalculate; + + for (const auto &it : m_pairs) { + llvm::StringRef prefix = it.first.GetStringRef(); + // We create a copy of mapping_path because StringRef::consume_from + // effectively modifies the instance itself. + llvm::StringRef path = mapping_path; + if (!path.consume_front(prefix)) { + // Relative paths won't have a leading "./" in them unless "." is the + // only thing in the relative path so we need to work around "." + // carefully. + if (prefix != ".") + continue; + // We need to figure out if the "path" argument is relative. If it is, + // then we should remap, else skip this entry. + if (path_is_relative == eLazyBoolCalculate) { + path_is_relative = + FileSpec(path).IsRelative() ? eLazyBoolYes : eLazyBoolNo; + } + if (!path_is_relative) + continue; + } + FileSpec remapped(it.second.GetStringRef()); + auto orig_style = FileSpec::GuessPathStyle(prefix).value_or( + llvm::sys::path::Style::native); + AppendPathComponents(remapped, path, orig_style); + if (!only_if_exists || FileSystem::Instance().Exists(remapped)) + return remapped; + } + return {}; +} + +std::optional<llvm::StringRef> +PathMappingList::ReverseRemapPath(const FileSpec &file, FileSpec &fixed) const { + std::string path = file.GetPath(); + llvm::StringRef path_ref(path); + std::lock_guard<std::recursive_mutex> lock(m_mutex); + for (const auto &it : m_pairs) { + llvm::StringRef removed_prefix = it.second.GetStringRef(); + if (!path_ref.consume_front(it.second.GetStringRef())) + continue; + auto orig_file = it.first.GetStringRef(); + auto orig_style = FileSpec::GuessPathStyle(orig_file).value_or( + llvm::sys::path::Style::native); + fixed.SetFile(orig_file, orig_style); + AppendPathComponents(fixed, path_ref, orig_style); + return removed_prefix; + } + return std::nullopt; +} + +std::optional<FileSpec> +PathMappingList::FindFile(const FileSpec &orig_spec) const { + // We must normalize the orig_spec again using the host's path style, + // otherwise there will be mismatch between the host and remote platform + // if they use different path styles. + if (auto remapped = RemapPath(NormalizePath(orig_spec.GetPath()), + /*only_if_exists=*/true)) + return remapped; + + return {}; +} + +bool PathMappingList::Replace(llvm::StringRef path, llvm::StringRef new_path, + bool notify) { + std::lock_guard<std::recursive_mutex> lock(m_mutex); + uint32_t idx = FindIndexForPath(path); + if (idx < m_pairs.size()) { + ++m_mod_id; + m_pairs[idx].second = ConstString(new_path); + if (notify && m_callback) + m_callback(*this, m_callback_baton); + return true; + } + return false; +} + +bool PathMappingList::Remove(ConstString path, bool notify) { + std::lock_guard<std::recursive_mutex> lock(m_mutex); + iterator pos = FindIteratorForPath(path); + if (pos != m_pairs.end()) { + ++m_mod_id; + m_pairs.erase(pos); + if (notify && m_callback) + m_callback(*this, m_callback_baton); + return true; + } + return false; +} + +PathMappingList::const_iterator +PathMappingList::FindIteratorForPath(ConstString path) const { + std::lock_guard<std::recursive_mutex> lock(m_mutex); + const_iterator pos; + const_iterator begin = m_pairs.begin(); + const_iterator end = m_pairs.end(); + + for (pos = begin; pos != end; ++pos) { + if (pos->first == path) + break; + } + return pos; +} + +PathMappingList::iterator +PathMappingList::FindIteratorForPath(ConstString path) { + std::lock_guard<std::recursive_mutex> lock(m_mutex); + iterator pos; + iterator begin = m_pairs.begin(); + iterator end = m_pairs.end(); + + for (pos = begin; pos != end; ++pos) { + if (pos->first == path) + break; + } + return pos; +} + +bool PathMappingList::GetPathsAtIndex(uint32_t idx, ConstString &path, + ConstString &new_path) const { + std::lock_guard<std::recursive_mutex> lock(m_mutex); + if (idx < m_pairs.size()) { + path = m_pairs[idx].first; + new_path = m_pairs[idx].second; + return true; + } + return false; +} + +uint32_t PathMappingList::FindIndexForPath(llvm::StringRef orig_path) const { + const ConstString path = ConstString(NormalizePath(orig_path)); + std::lock_guard<std::recursive_mutex> lock(m_mutex); + const_iterator pos; + const_iterator begin = m_pairs.begin(); + const_iterator end = m_pairs.end(); + + for (pos = begin; pos != end; ++pos) { + if (pos->first == path) + return std::distance(begin, pos); + } + return UINT32_MAX; +} diff --git a/contrib/llvm-project/lldb/source/Target/Platform.cpp b/contrib/llvm-project/lldb/source/Target/Platform.cpp new file mode 100644 index 000000000000..ab80fe4c8ba2 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/Platform.cpp @@ -0,0 +1,2238 @@ +//===-- Platform.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 <algorithm> +#include <csignal> +#include <fstream> +#include <memory> +#include <optional> +#include <vector> + +#include "lldb/Breakpoint/BreakpointIDList.h" +#include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Host/FileCache.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/Host.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Interpreter/OptionValueFileSpec.h" +#include "lldb/Interpreter/OptionValueProperties.h" +#include "lldb/Interpreter/Property.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Target/ModuleCache.h" +#include "lldb/Target/Platform.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/UnixSignals.h" +#include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/StructuredData.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" + +// Define these constants from POSIX mman.h rather than include the file so +// that they will be correct even when compiled on Linux. +#define MAP_PRIVATE 2 +#define MAP_ANON 0x1000 + +using namespace lldb; +using namespace lldb_private; + +// Use a singleton function for g_local_platform_sp to avoid init constructors +// since LLDB is often part of a shared library +static PlatformSP &GetHostPlatformSP() { + static PlatformSP g_platform_sp; + return g_platform_sp; +} + +const char *Platform::GetHostPlatformName() { return "host"; } + +namespace { + +#define LLDB_PROPERTIES_platform +#include "TargetProperties.inc" + +enum { +#define LLDB_PROPERTIES_platform +#include "TargetPropertiesEnum.inc" +}; + +} // namespace + +llvm::StringRef PlatformProperties::GetSettingName() { + static constexpr llvm::StringLiteral g_setting_name("platform"); + return g_setting_name; +} + +PlatformProperties::PlatformProperties() { + m_collection_sp = std::make_shared<OptionValueProperties>(GetSettingName()); + m_collection_sp->Initialize(g_platform_properties); + + auto module_cache_dir = GetModuleCacheDirectory(); + if (module_cache_dir) + return; + + llvm::SmallString<64> user_home_dir; + if (!FileSystem::Instance().GetHomeDirectory(user_home_dir)) + return; + + module_cache_dir = FileSpec(user_home_dir.c_str()); + module_cache_dir.AppendPathComponent(".lldb"); + module_cache_dir.AppendPathComponent("module_cache"); + SetDefaultModuleCacheDirectory(module_cache_dir); + SetModuleCacheDirectory(module_cache_dir); +} + +bool PlatformProperties::GetUseModuleCache() const { + const auto idx = ePropertyUseModuleCache; + return GetPropertyAtIndexAs<bool>( + idx, g_platform_properties[idx].default_uint_value != 0); +} + +bool PlatformProperties::SetUseModuleCache(bool use_module_cache) { + return SetPropertyAtIndex(ePropertyUseModuleCache, use_module_cache); +} + +FileSpec PlatformProperties::GetModuleCacheDirectory() const { + return GetPropertyAtIndexAs<FileSpec>(ePropertyModuleCacheDirectory, {}); +} + +bool PlatformProperties::SetModuleCacheDirectory(const FileSpec &dir_spec) { + return m_collection_sp->SetPropertyAtIndex(ePropertyModuleCacheDirectory, + dir_spec); +} + +void PlatformProperties::SetDefaultModuleCacheDirectory( + const FileSpec &dir_spec) { + auto f_spec_opt = m_collection_sp->GetPropertyAtIndexAsOptionValueFileSpec( + ePropertyModuleCacheDirectory); + assert(f_spec_opt); + f_spec_opt->SetDefaultValue(dir_spec); +} + +/// Get the native host platform plug-in. +/// +/// There should only be one of these for each host that LLDB runs +/// upon that should be statically compiled in and registered using +/// preprocessor macros or other similar build mechanisms. +/// +/// This platform will be used as the default platform when launching +/// or attaching to processes unless another platform is specified. +PlatformSP Platform::GetHostPlatform() { return GetHostPlatformSP(); } + +void Platform::Initialize() {} + +void Platform::Terminate() {} + +PlatformProperties &Platform::GetGlobalPlatformProperties() { + static PlatformProperties g_settings; + return g_settings; +} + +void Platform::SetHostPlatform(const lldb::PlatformSP &platform_sp) { + // The native platform should use its static void Platform::Initialize() + // function to register itself as the native platform. + GetHostPlatformSP() = platform_sp; +} + +Status Platform::GetFileWithUUID(const FileSpec &platform_file, + const UUID *uuid_ptr, FileSpec &local_file) { + // Default to the local case + local_file = platform_file; + return Status(); +} + +FileSpecList +Platform::LocateExecutableScriptingResources(Target *target, Module &module, + Stream &feedback_stream) { + return FileSpecList(); +} + +Status Platform::GetSharedModule( + const ModuleSpec &module_spec, Process *process, ModuleSP &module_sp, + const FileSpecList *module_search_paths_ptr, + llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, bool *did_create_ptr) { + if (IsHost()) + return ModuleList::GetSharedModule(module_spec, module_sp, + module_search_paths_ptr, old_modules, + did_create_ptr, false); + + // Module resolver lambda. + auto resolver = [&](const ModuleSpec &spec) { + Status error(eErrorTypeGeneric); + ModuleSpec resolved_spec; + // Check if we have sysroot set. + if (!m_sdk_sysroot.empty()) { + // Prepend sysroot to module spec. + resolved_spec = spec; + resolved_spec.GetFileSpec().PrependPathComponent(m_sdk_sysroot); + // Try to get shared module with resolved spec. + error = ModuleList::GetSharedModule(resolved_spec, module_sp, + module_search_paths_ptr, old_modules, + did_create_ptr, false); + } + // If we don't have sysroot or it didn't work then + // try original module spec. + if (!error.Success()) { + resolved_spec = spec; + error = ModuleList::GetSharedModule(resolved_spec, module_sp, + module_search_paths_ptr, old_modules, + did_create_ptr, false); + } + if (error.Success() && module_sp) + module_sp->SetPlatformFileSpec(resolved_spec.GetFileSpec()); + return error; + }; + + return GetRemoteSharedModule(module_spec, process, module_sp, resolver, + did_create_ptr); +} + +bool Platform::GetModuleSpec(const FileSpec &module_file_spec, + const ArchSpec &arch, ModuleSpec &module_spec) { + ModuleSpecList module_specs; + if (ObjectFile::GetModuleSpecifications(module_file_spec, 0, 0, + module_specs) == 0) + return false; + + ModuleSpec matched_module_spec; + return module_specs.FindMatchingModuleSpec(ModuleSpec(module_file_spec, arch), + module_spec); +} + +PlatformSP Platform::Create(llvm::StringRef name) { + lldb::PlatformSP platform_sp; + if (name == GetHostPlatformName()) + return GetHostPlatform(); + + if (PlatformCreateInstance create_callback = + PluginManager::GetPlatformCreateCallbackForPluginName(name)) + return create_callback(true, nullptr); + return nullptr; +} + +ArchSpec Platform::GetAugmentedArchSpec(Platform *platform, llvm::StringRef triple) { + if (platform) + return platform->GetAugmentedArchSpec(triple); + return HostInfo::GetAugmentedArchSpec(triple); +} + +/// Default Constructor +Platform::Platform(bool is_host) + : m_is_host(is_host), m_os_version_set_while_connected(false), + m_system_arch_set_while_connected(false), m_max_uid_name_len(0), + m_max_gid_name_len(0), m_supports_rsync(false), m_rsync_opts(), + m_rsync_prefix(), m_supports_ssh(false), m_ssh_opts(), + m_ignores_remote_hostname(false), m_trap_handlers(), + m_calculated_trap_handlers(false), + m_module_cache(std::make_unique<ModuleCache>()) { + Log *log = GetLog(LLDBLog::Object); + LLDB_LOGF(log, "%p Platform::Platform()", static_cast<void *>(this)); +} + +Platform::~Platform() = default; + +void Platform::GetStatus(Stream &strm) { + strm.Format(" Platform: {0}\n", GetPluginName()); + + ArchSpec arch(GetSystemArchitecture()); + if (arch.IsValid()) { + if (!arch.GetTriple().str().empty()) { + strm.Printf(" Triple: "); + arch.DumpTriple(strm.AsRawOstream()); + strm.EOL(); + } + } + + llvm::VersionTuple os_version = GetOSVersion(); + if (!os_version.empty()) { + strm.Format("OS Version: {0}", os_version.getAsString()); + + if (std::optional<std::string> s = GetOSBuildString()) + strm.Format(" ({0})", *s); + + strm.EOL(); + } + + if (IsHost()) { + strm.Printf(" Hostname: %s\n", GetHostname()); + } else { + const bool is_connected = IsConnected(); + if (is_connected) + strm.Printf(" Hostname: %s\n", GetHostname()); + strm.Printf(" Connected: %s\n", is_connected ? "yes" : "no"); + } + + if (const std::string &sdk_root = GetSDKRootDirectory(); !sdk_root.empty()) + strm.Format(" Sysroot: {0}\n", sdk_root); + + if (GetWorkingDirectory()) { + strm.Printf("WorkingDir: %s\n", GetWorkingDirectory().GetPath().c_str()); + } + if (!IsConnected()) + return; + + std::string specific_info(GetPlatformSpecificConnectionInformation()); + + if (!specific_info.empty()) + strm.Printf("Platform-specific connection: %s\n", specific_info.c_str()); + + if (std::optional<std::string> s = GetOSKernelDescription()) + strm.Format(" Kernel: {0}\n", *s); +} + +llvm::VersionTuple Platform::GetOSVersion(Process *process) { + std::lock_guard<std::mutex> guard(m_mutex); + + if (IsHost()) { + if (m_os_version.empty()) { + // We have a local host platform + m_os_version = HostInfo::GetOSVersion(); + m_os_version_set_while_connected = !m_os_version.empty(); + } + } else { + // We have a remote platform. We can only fetch the remote + // OS version if we are connected, and we don't want to do it + // more than once. + + const bool is_connected = IsConnected(); + + bool fetch = false; + if (!m_os_version.empty()) { + // We have valid OS version info, check to make sure it wasn't manually + // set prior to connecting. If it was manually set prior to connecting, + // then lets fetch the actual OS version info if we are now connected. + if (is_connected && !m_os_version_set_while_connected) + fetch = true; + } else { + // We don't have valid OS version info, fetch it if we are connected + fetch = is_connected; + } + + if (fetch) + m_os_version_set_while_connected = GetRemoteOSVersion(); + } + + if (!m_os_version.empty()) + return m_os_version; + if (process) { + // Check with the process in case it can answer the question if a process + // was provided + return process->GetHostOSVersion(); + } + return llvm::VersionTuple(); +} + +std::optional<std::string> Platform::GetOSBuildString() { + if (IsHost()) + return HostInfo::GetOSBuildString(); + return GetRemoteOSBuildString(); +} + +std::optional<std::string> Platform::GetOSKernelDescription() { + if (IsHost()) + return HostInfo::GetOSKernelDescription(); + return GetRemoteOSKernelDescription(); +} + +void Platform::AddClangModuleCompilationOptions( + Target *target, std::vector<std::string> &options) { + std::vector<std::string> default_compilation_options = { + "-x", "c++", "-Xclang", "-nostdsysteminc", "-Xclang", "-nostdsysteminc"}; + + options.insert(options.end(), default_compilation_options.begin(), + default_compilation_options.end()); +} + +FileSpec Platform::GetWorkingDirectory() { + if (IsHost()) { + llvm::SmallString<64> cwd; + if (llvm::sys::fs::current_path(cwd)) + return {}; + else { + FileSpec file_spec(cwd); + FileSystem::Instance().Resolve(file_spec); + return file_spec; + } + } else { + if (!m_working_dir) + m_working_dir = GetRemoteWorkingDirectory(); + return m_working_dir; + } +} + +struct RecurseCopyBaton { + const FileSpec &dst; + Platform *platform_ptr; + Status error; +}; + +static FileSystem::EnumerateDirectoryResult +RecurseCopy_Callback(void *baton, llvm::sys::fs::file_type ft, + llvm::StringRef path) { + RecurseCopyBaton *rc_baton = (RecurseCopyBaton *)baton; + FileSpec src(path); + namespace fs = llvm::sys::fs; + switch (ft) { + case fs::file_type::fifo_file: + case fs::file_type::socket_file: + // we have no way to copy pipes and sockets - ignore them and continue + return FileSystem::eEnumerateDirectoryResultNext; + break; + + case fs::file_type::directory_file: { + // make the new directory and get in there + FileSpec dst_dir = rc_baton->dst; + if (!dst_dir.GetFilename()) + dst_dir.SetFilename(src.GetFilename()); + Status error = rc_baton->platform_ptr->MakeDirectory( + dst_dir, lldb::eFilePermissionsDirectoryDefault); + if (error.Fail()) { + rc_baton->error.SetErrorStringWithFormat( + "unable to setup directory %s on remote end", + dst_dir.GetPath().c_str()); + return FileSystem::eEnumerateDirectoryResultQuit; // got an error, bail out + } + + // now recurse + std::string src_dir_path(src.GetPath()); + + // Make a filespec that only fills in the directory of a FileSpec so when + // we enumerate we can quickly fill in the filename for dst copies + FileSpec recurse_dst; + recurse_dst.SetDirectory(dst_dir.GetPathAsConstString()); + RecurseCopyBaton rc_baton2 = {recurse_dst, rc_baton->platform_ptr, + Status()}; + FileSystem::Instance().EnumerateDirectory(src_dir_path, true, true, true, + RecurseCopy_Callback, &rc_baton2); + if (rc_baton2.error.Fail()) { + rc_baton->error.SetErrorString(rc_baton2.error.AsCString()); + return FileSystem::eEnumerateDirectoryResultQuit; // got an error, bail out + } + return FileSystem::eEnumerateDirectoryResultNext; + } break; + + case fs::file_type::symlink_file: { + // copy the file and keep going + FileSpec dst_file = rc_baton->dst; + if (!dst_file.GetFilename()) + dst_file.SetFilename(src.GetFilename()); + + FileSpec src_resolved; + + rc_baton->error = FileSystem::Instance().Readlink(src, src_resolved); + + if (rc_baton->error.Fail()) + return FileSystem::eEnumerateDirectoryResultQuit; // got an error, bail out + + rc_baton->error = + rc_baton->platform_ptr->CreateSymlink(dst_file, src_resolved); + + if (rc_baton->error.Fail()) + return FileSystem::eEnumerateDirectoryResultQuit; // got an error, bail out + + return FileSystem::eEnumerateDirectoryResultNext; + } break; + + case fs::file_type::regular_file: { + // copy the file and keep going + FileSpec dst_file = rc_baton->dst; + if (!dst_file.GetFilename()) + dst_file.SetFilename(src.GetFilename()); + Status err = rc_baton->platform_ptr->PutFile(src, dst_file); + if (err.Fail()) { + rc_baton->error.SetErrorString(err.AsCString()); + return FileSystem::eEnumerateDirectoryResultQuit; // got an error, bail out + } + return FileSystem::eEnumerateDirectoryResultNext; + } break; + + default: + rc_baton->error.SetErrorStringWithFormat( + "invalid file detected during copy: %s", src.GetPath().c_str()); + return FileSystem::eEnumerateDirectoryResultQuit; // got an error, bail out + break; + } + llvm_unreachable("Unhandled file_type!"); +} + +Status Platform::Install(const FileSpec &src, const FileSpec &dst) { + Status error; + + Log *log = GetLog(LLDBLog::Platform); + LLDB_LOGF(log, "Platform::Install (src='%s', dst='%s')", + src.GetPath().c_str(), dst.GetPath().c_str()); + FileSpec fixed_dst(dst); + + if (!fixed_dst.GetFilename()) + fixed_dst.SetFilename(src.GetFilename()); + + FileSpec working_dir = GetWorkingDirectory(); + + if (dst) { + if (dst.GetDirectory()) { + const char first_dst_dir_char = dst.GetDirectory().GetCString()[0]; + if (first_dst_dir_char == '/' || first_dst_dir_char == '\\') { + fixed_dst.SetDirectory(dst.GetDirectory()); + } + // If the fixed destination file doesn't have a directory yet, then we + // must have a relative path. We will resolve this relative path against + // the platform's working directory + if (!fixed_dst.GetDirectory()) { + FileSpec relative_spec; + std::string path; + if (working_dir) { + relative_spec = working_dir; + relative_spec.AppendPathComponent(dst.GetPath()); + fixed_dst.SetDirectory(relative_spec.GetDirectory()); + } else { + error.SetErrorStringWithFormat( + "platform working directory must be valid for relative path '%s'", + dst.GetPath().c_str()); + return error; + } + } + } else { + if (working_dir) { + fixed_dst.SetDirectory(working_dir.GetPathAsConstString()); + } else { + error.SetErrorStringWithFormat( + "platform working directory must be valid for relative path '%s'", + dst.GetPath().c_str()); + return error; + } + } + } else { + if (working_dir) { + fixed_dst.SetDirectory(working_dir.GetPathAsConstString()); + } else { + error.SetErrorStringWithFormat("platform working directory must be valid " + "when destination directory is empty"); + return error; + } + } + + LLDB_LOGF(log, "Platform::Install (src='%s', dst='%s') fixed_dst='%s'", + src.GetPath().c_str(), dst.GetPath().c_str(), + fixed_dst.GetPath().c_str()); + + if (GetSupportsRSync()) { + error = PutFile(src, dst); + } else { + namespace fs = llvm::sys::fs; + switch (fs::get_file_type(src.GetPath(), false)) { + case fs::file_type::directory_file: { + llvm::sys::fs::remove(fixed_dst.GetPath()); + uint32_t permissions = FileSystem::Instance().GetPermissions(src); + if (permissions == 0) + permissions = eFilePermissionsDirectoryDefault; + error = MakeDirectory(fixed_dst, permissions); + if (error.Success()) { + // Make a filespec that only fills in the directory of a FileSpec so + // when we enumerate we can quickly fill in the filename for dst copies + FileSpec recurse_dst; + recurse_dst.SetDirectory(fixed_dst.GetPathAsConstString()); + std::string src_dir_path(src.GetPath()); + RecurseCopyBaton baton = {recurse_dst, this, Status()}; + FileSystem::Instance().EnumerateDirectory( + src_dir_path, true, true, true, RecurseCopy_Callback, &baton); + return baton.error; + } + } break; + + case fs::file_type::regular_file: + llvm::sys::fs::remove(fixed_dst.GetPath()); + error = PutFile(src, fixed_dst); + break; + + case fs::file_type::symlink_file: { + llvm::sys::fs::remove(fixed_dst.GetPath()); + FileSpec src_resolved; + error = FileSystem::Instance().Readlink(src, src_resolved); + if (error.Success()) + error = CreateSymlink(dst, src_resolved); + } break; + case fs::file_type::fifo_file: + error.SetErrorString("platform install doesn't handle pipes"); + break; + case fs::file_type::socket_file: + error.SetErrorString("platform install doesn't handle sockets"); + break; + default: + error.SetErrorString( + "platform install doesn't handle non file or directory items"); + break; + } + } + return error; +} + +bool Platform::SetWorkingDirectory(const FileSpec &file_spec) { + if (IsHost()) { + Log *log = GetLog(LLDBLog::Platform); + LLDB_LOG(log, "{0}", file_spec); + if (std::error_code ec = llvm::sys::fs::set_current_path(file_spec.GetPath())) { + LLDB_LOG(log, "error: {0}", ec.message()); + return false; + } + return true; + } else { + m_working_dir.Clear(); + return SetRemoteWorkingDirectory(file_spec); + } +} + +Status Platform::MakeDirectory(const FileSpec &file_spec, + uint32_t permissions) { + if (IsHost()) + return llvm::sys::fs::create_directory(file_spec.GetPath(), permissions); + else { + Status error; + error.SetErrorStringWithFormatv("remote platform {0} doesn't support {1}", + GetPluginName(), LLVM_PRETTY_FUNCTION); + return error; + } +} + +Status Platform::GetFilePermissions(const FileSpec &file_spec, + uint32_t &file_permissions) { + if (IsHost()) { + auto Value = llvm::sys::fs::getPermissions(file_spec.GetPath()); + if (Value) + file_permissions = Value.get(); + return Status(Value.getError()); + } else { + Status error; + error.SetErrorStringWithFormatv("remote platform {0} doesn't support {1}", + GetPluginName(), LLVM_PRETTY_FUNCTION); + return error; + } +} + +Status Platform::SetFilePermissions(const FileSpec &file_spec, + uint32_t file_permissions) { + if (IsHost()) { + auto Perms = static_cast<llvm::sys::fs::perms>(file_permissions); + return llvm::sys::fs::setPermissions(file_spec.GetPath(), Perms); + } else { + Status error; + error.SetErrorStringWithFormatv("remote platform {0} doesn't support {1}", + GetPluginName(), LLVM_PRETTY_FUNCTION); + return error; + } +} + +user_id_t Platform::OpenFile(const FileSpec &file_spec, + File::OpenOptions flags, uint32_t mode, + Status &error) { + if (IsHost()) + return FileCache::GetInstance().OpenFile(file_spec, flags, mode, error); + return UINT64_MAX; +} + +bool Platform::CloseFile(user_id_t fd, Status &error) { + if (IsHost()) + return FileCache::GetInstance().CloseFile(fd, error); + return false; +} + +user_id_t Platform::GetFileSize(const FileSpec &file_spec) { + if (!IsHost()) + return UINT64_MAX; + + uint64_t Size; + if (llvm::sys::fs::file_size(file_spec.GetPath(), Size)) + return 0; + return Size; +} + +uint64_t Platform::ReadFile(lldb::user_id_t fd, uint64_t offset, void *dst, + uint64_t dst_len, Status &error) { + if (IsHost()) + return FileCache::GetInstance().ReadFile(fd, offset, dst, dst_len, error); + error.SetErrorStringWithFormatv( + "Platform::ReadFile() is not supported in the {0} platform", + GetPluginName()); + return -1; +} + +uint64_t Platform::WriteFile(lldb::user_id_t fd, uint64_t offset, + const void *src, uint64_t src_len, Status &error) { + if (IsHost()) + return FileCache::GetInstance().WriteFile(fd, offset, src, src_len, error); + error.SetErrorStringWithFormatv( + "Platform::WriteFile() is not supported in the {0} platform", + GetPluginName()); + return -1; +} + +UserIDResolver &Platform::GetUserIDResolver() { + if (IsHost()) + return HostInfo::GetUserIDResolver(); + return UserIDResolver::GetNoopResolver(); +} + +const char *Platform::GetHostname() { + if (IsHost()) + return "127.0.0.1"; + + if (m_hostname.empty()) + return nullptr; + return m_hostname.c_str(); +} + +ConstString Platform::GetFullNameForDylib(ConstString basename) { + return basename; +} + +bool Platform::SetRemoteWorkingDirectory(const FileSpec &working_dir) { + Log *log = GetLog(LLDBLog::Platform); + LLDB_LOGF(log, "Platform::SetRemoteWorkingDirectory('%s')", + working_dir.GetPath().c_str()); + m_working_dir = working_dir; + return true; +} + +bool Platform::SetOSVersion(llvm::VersionTuple version) { + if (IsHost()) { + // We don't need anyone setting the OS version for the host platform, we + // should be able to figure it out by calling HostInfo::GetOSVersion(...). + return false; + } else { + // We have a remote platform, allow setting the target OS version if we + // aren't connected, since if we are connected, we should be able to + // request the remote OS version from the connected platform. + if (IsConnected()) + return false; + else { + // We aren't connected and we might want to set the OS version ahead of + // time before we connect so we can peruse files and use a local SDK or + // PDK cache of support files to disassemble or do other things. + m_os_version = version; + return true; + } + } + return false; +} + +Status +Platform::ResolveExecutable(const ModuleSpec &module_spec, + lldb::ModuleSP &exe_module_sp, + const FileSpecList *module_search_paths_ptr) { + + // We may connect to a process and use the provided executable (Don't use + // local $PATH). + ModuleSpec resolved_module_spec(module_spec); + + // Resolve any executable within a bundle on MacOSX + Host::ResolveExecutableInBundle(resolved_module_spec.GetFileSpec()); + + if (!FileSystem::Instance().Exists(resolved_module_spec.GetFileSpec()) && + !module_spec.GetUUID().IsValid()) + return Status::createWithFormat("'{0}' does not exist", + resolved_module_spec.GetFileSpec()); + + if (resolved_module_spec.GetArchitecture().IsValid() || + resolved_module_spec.GetUUID().IsValid()) { + Status error = + ModuleList::GetSharedModule(resolved_module_spec, exe_module_sp, + module_search_paths_ptr, nullptr, nullptr); + + if (exe_module_sp && exe_module_sp->GetObjectFile()) + return error; + exe_module_sp.reset(); + } + // No valid architecture was specified or the exact arch wasn't found. + // Ask the platform for the architectures that we should be using (in the + // correct order) and see if we can find a match that way. + StreamString arch_names; + llvm::ListSeparator LS; + ArchSpec process_host_arch; + Status error; + for (const ArchSpec &arch : GetSupportedArchitectures(process_host_arch)) { + resolved_module_spec.GetArchitecture() = arch; + error = + ModuleList::GetSharedModule(resolved_module_spec, exe_module_sp, + module_search_paths_ptr, nullptr, nullptr); + if (error.Success()) { + if (exe_module_sp && exe_module_sp->GetObjectFile()) + break; + error.SetErrorToGenericError(); + } + + arch_names << LS << arch.GetArchitectureName(); + } + + if (exe_module_sp && error.Success()) + return {}; + + if (!FileSystem::Instance().Readable(resolved_module_spec.GetFileSpec())) + return Status::createWithFormat("'{0}' is not readable", + resolved_module_spec.GetFileSpec()); + + if (!ObjectFile::IsObjectFile(resolved_module_spec.GetFileSpec())) + return Status::createWithFormat("'{0}' is not a valid executable", + resolved_module_spec.GetFileSpec()); + + return Status::createWithFormat( + "'{0}' doesn't contain any '{1}' platform architectures: {2}", + resolved_module_spec.GetFileSpec(), GetPluginName(), + arch_names.GetData()); +} + +Status Platform::ResolveSymbolFile(Target &target, const ModuleSpec &sym_spec, + FileSpec &sym_file) { + Status error; + if (FileSystem::Instance().Exists(sym_spec.GetSymbolFileSpec())) + sym_file = sym_spec.GetSymbolFileSpec(); + else + error.SetErrorString("unable to resolve symbol file"); + return error; +} + +bool Platform::ResolveRemotePath(const FileSpec &platform_path, + FileSpec &resolved_platform_path) { + resolved_platform_path = platform_path; + FileSystem::Instance().Resolve(resolved_platform_path); + return true; +} + +const ArchSpec &Platform::GetSystemArchitecture() { + if (IsHost()) { + if (!m_system_arch.IsValid()) { + // We have a local host platform + m_system_arch = HostInfo::GetArchitecture(); + m_system_arch_set_while_connected = m_system_arch.IsValid(); + } + } else { + // We have a remote platform. We can only fetch the remote system + // architecture if we are connected, and we don't want to do it more than + // once. + + const bool is_connected = IsConnected(); + + bool fetch = false; + if (m_system_arch.IsValid()) { + // We have valid OS version info, check to make sure it wasn't manually + // set prior to connecting. If it was manually set prior to connecting, + // then lets fetch the actual OS version info if we are now connected. + if (is_connected && !m_system_arch_set_while_connected) + fetch = true; + } else { + // We don't have valid OS version info, fetch it if we are connected + fetch = is_connected; + } + + if (fetch) { + m_system_arch = GetRemoteSystemArchitecture(); + m_system_arch_set_while_connected = m_system_arch.IsValid(); + } + } + return m_system_arch; +} + +ArchSpec Platform::GetAugmentedArchSpec(llvm::StringRef triple) { + if (triple.empty()) + return ArchSpec(); + llvm::Triple normalized_triple(llvm::Triple::normalize(triple)); + if (!ArchSpec::ContainsOnlyArch(normalized_triple)) + return ArchSpec(triple); + + if (auto kind = HostInfo::ParseArchitectureKind(triple)) + return HostInfo::GetArchitecture(*kind); + + ArchSpec compatible_arch; + ArchSpec raw_arch(triple); + if (!IsCompatibleArchitecture(raw_arch, {}, ArchSpec::CompatibleMatch, + &compatible_arch)) + return raw_arch; + + if (!compatible_arch.IsValid()) + return ArchSpec(normalized_triple); + + const llvm::Triple &compatible_triple = compatible_arch.GetTriple(); + if (normalized_triple.getVendorName().empty()) + normalized_triple.setVendor(compatible_triple.getVendor()); + if (normalized_triple.getOSName().empty()) + normalized_triple.setOS(compatible_triple.getOS()); + if (normalized_triple.getEnvironmentName().empty()) + normalized_triple.setEnvironment(compatible_triple.getEnvironment()); + return ArchSpec(normalized_triple); +} + +Status Platform::ConnectRemote(Args &args) { + Status error; + if (IsHost()) + error.SetErrorStringWithFormatv( + "The currently selected platform ({0}) is " + "the host platform and is always connected.", + GetPluginName()); + else + error.SetErrorStringWithFormatv( + "Platform::ConnectRemote() is not supported by {0}", GetPluginName()); + return error; +} + +Status Platform::DisconnectRemote() { + Status error; + if (IsHost()) + error.SetErrorStringWithFormatv( + "The currently selected platform ({0}) is " + "the host platform and is always connected.", + GetPluginName()); + else + error.SetErrorStringWithFormatv( + "Platform::DisconnectRemote() is not supported by {0}", + GetPluginName()); + return error; +} + +bool Platform::GetProcessInfo(lldb::pid_t pid, + ProcessInstanceInfo &process_info) { + // Take care of the host case so that each subclass can just call this + // function to get the host functionality. + if (IsHost()) + return Host::GetProcessInfo(pid, process_info); + return false; +} + +uint32_t Platform::FindProcesses(const ProcessInstanceInfoMatch &match_info, + ProcessInstanceInfoList &process_infos) { + // Take care of the host case so that each subclass can just call this + // function to get the host functionality. + uint32_t match_count = 0; + if (IsHost()) + match_count = Host::FindProcesses(match_info, process_infos); + return match_count; +} + +ProcessInstanceInfoList Platform::GetAllProcesses() { + ProcessInstanceInfoList processes; + ProcessInstanceInfoMatch match; + assert(match.MatchAllProcesses()); + FindProcesses(match, processes); + return processes; +} + +Status Platform::LaunchProcess(ProcessLaunchInfo &launch_info) { + Status error; + Log *log = GetLog(LLDBLog::Platform); + LLDB_LOGF(log, "Platform::%s()", __FUNCTION__); + + // Take care of the host case so that each subclass can just call this + // function to get the host functionality. + if (IsHost()) { + if (::getenv("LLDB_LAUNCH_FLAG_LAUNCH_IN_TTY")) + launch_info.GetFlags().Set(eLaunchFlagLaunchInTTY); + + if (launch_info.GetFlags().Test(eLaunchFlagLaunchInShell)) { + const bool will_debug = launch_info.GetFlags().Test(eLaunchFlagDebug); + const bool first_arg_is_full_shell_command = false; + uint32_t num_resumes = GetResumeCountForLaunchInfo(launch_info); + if (log) { + const FileSpec &shell = launch_info.GetShell(); + std::string shell_str = (shell) ? shell.GetPath() : "<null>"; + LLDB_LOGF(log, + "Platform::%s GetResumeCountForLaunchInfo() returned %" PRIu32 + ", shell is '%s'", + __FUNCTION__, num_resumes, shell_str.c_str()); + } + + if (!launch_info.ConvertArgumentsForLaunchingInShell( + error, will_debug, first_arg_is_full_shell_command, num_resumes)) + return error; + } else if (launch_info.GetFlags().Test(eLaunchFlagShellExpandArguments)) { + error = ShellExpandArguments(launch_info); + if (error.Fail()) { + error.SetErrorStringWithFormat("shell expansion failed (reason: %s). " + "consider launching with 'process " + "launch'.", + error.AsCString("unknown")); + return error; + } + } + + LLDB_LOGF(log, "Platform::%s final launch_info resume count: %" PRIu32, + __FUNCTION__, launch_info.GetResumeCount()); + + error = Host::LaunchProcess(launch_info); + } else + error.SetErrorString( + "base lldb_private::Platform class can't launch remote processes"); + return error; +} + +Status Platform::ShellExpandArguments(ProcessLaunchInfo &launch_info) { + if (IsHost()) + return Host::ShellExpandArguments(launch_info); + return Status("base lldb_private::Platform class can't expand arguments"); +} + +Status Platform::KillProcess(const lldb::pid_t pid) { + Log *log = GetLog(LLDBLog::Platform); + LLDB_LOGF(log, "Platform::%s, pid %" PRIu64, __FUNCTION__, pid); + + if (!IsHost()) { + return Status( + "base lldb_private::Platform class can't kill remote processes"); + } + Host::Kill(pid, SIGKILL); + return Status(); +} + +lldb::ProcessSP Platform::DebugProcess(ProcessLaunchInfo &launch_info, + Debugger &debugger, Target &target, + Status &error) { + Log *log = GetLog(LLDBLog::Platform); + LLDB_LOG(log, "target = {0}", &target); + + ProcessSP process_sp; + // Make sure we stop at the entry point + launch_info.GetFlags().Set(eLaunchFlagDebug); + // We always launch the process we are going to debug in a separate process + // group, since then we can handle ^C interrupts ourselves w/o having to + // worry about the target getting them as well. + launch_info.SetLaunchInSeparateProcessGroup(true); + + // Allow any StructuredData process-bound plugins to adjust the launch info + // if needed + size_t i = 0; + bool iteration_complete = false; + // Note iteration can't simply go until a nullptr callback is returned, as it + // is valid for a plugin to not supply a filter. + auto get_filter_func = PluginManager::GetStructuredDataFilterCallbackAtIndex; + for (auto filter_callback = get_filter_func(i, iteration_complete); + !iteration_complete; + filter_callback = get_filter_func(++i, iteration_complete)) { + if (filter_callback) { + // Give this ProcessLaunchInfo filter a chance to adjust the launch info. + error = (*filter_callback)(launch_info, &target); + if (!error.Success()) { + LLDB_LOGF(log, + "Platform::%s() StructuredDataPlugin launch " + "filter failed.", + __FUNCTION__); + return process_sp; + } + } + } + + error = LaunchProcess(launch_info); + if (error.Success()) { + LLDB_LOGF(log, + "Platform::%s LaunchProcess() call succeeded (pid=%" PRIu64 ")", + __FUNCTION__, launch_info.GetProcessID()); + if (launch_info.GetProcessID() != LLDB_INVALID_PROCESS_ID) { + ProcessAttachInfo attach_info(launch_info); + process_sp = Attach(attach_info, debugger, &target, error); + if (process_sp) { + LLDB_LOG(log, "Attach() succeeded, Process plugin: {0}", + process_sp->GetPluginName()); + launch_info.SetHijackListener(attach_info.GetHijackListener()); + + // Since we attached to the process, it will think it needs to detach + // if the process object just goes away without an explicit call to + // Process::Kill() or Process::Detach(), so let it know to kill the + // process if this happens. + process_sp->SetShouldDetach(false); + + // If we didn't have any file actions, the pseudo terminal might have + // been used where the secondary side was given as the file to open for + // stdin/out/err after we have already opened the primary so we can + // read/write stdin/out/err. + int pty_fd = launch_info.GetPTY().ReleasePrimaryFileDescriptor(); + if (pty_fd != PseudoTerminal::invalid_fd) { + process_sp->SetSTDIOFileDescriptor(pty_fd); + } + } else { + LLDB_LOGF(log, "Platform::%s Attach() failed: %s", __FUNCTION__, + error.AsCString()); + } + } else { + LLDB_LOGF(log, + "Platform::%s LaunchProcess() returned launch_info with " + "invalid process id", + __FUNCTION__); + } + } else { + LLDB_LOGF(log, "Platform::%s LaunchProcess() failed: %s", __FUNCTION__, + error.AsCString()); + } + + return process_sp; +} + +std::vector<ArchSpec> +Platform::CreateArchList(llvm::ArrayRef<llvm::Triple::ArchType> archs, + llvm::Triple::OSType os) { + std::vector<ArchSpec> list; + for(auto arch : archs) { + llvm::Triple triple; + triple.setArch(arch); + triple.setOS(os); + list.push_back(ArchSpec(triple)); + } + return list; +} + +/// Lets a platform answer if it is compatible with a given +/// architecture and the target triple contained within. +bool Platform::IsCompatibleArchitecture(const ArchSpec &arch, + const ArchSpec &process_host_arch, + ArchSpec::MatchType match, + ArchSpec *compatible_arch_ptr) { + // If the architecture is invalid, we must answer true... + if (arch.IsValid()) { + ArchSpec platform_arch; + for (const ArchSpec &platform_arch : + GetSupportedArchitectures(process_host_arch)) { + if (arch.IsMatch(platform_arch, match)) { + if (compatible_arch_ptr) + *compatible_arch_ptr = platform_arch; + return true; + } + } + } + if (compatible_arch_ptr) + compatible_arch_ptr->Clear(); + return false; +} + +Status Platform::PutFile(const FileSpec &source, const FileSpec &destination, + uint32_t uid, uint32_t gid) { + Log *log = GetLog(LLDBLog::Platform); + LLDB_LOGF(log, "[PutFile] Using block by block transfer....\n"); + + auto source_open_options = + File::eOpenOptionReadOnly | File::eOpenOptionCloseOnExec; + namespace fs = llvm::sys::fs; + if (fs::is_symlink_file(source.GetPath())) + source_open_options |= File::eOpenOptionDontFollowSymlinks; + + auto source_file = FileSystem::Instance().Open(source, source_open_options, + lldb::eFilePermissionsUserRW); + if (!source_file) + return Status(source_file.takeError()); + Status error; + + bool requires_upload = true; + llvm::ErrorOr<llvm::MD5::MD5Result> remote_md5 = CalculateMD5(destination); + if (std::error_code ec = remote_md5.getError()) { + LLDB_LOG(log, "[PutFile] couldn't get md5 sum of destination: {0}", + ec.message()); + } else { + llvm::ErrorOr<llvm::MD5::MD5Result> local_md5 = + llvm::sys::fs::md5_contents(source.GetPath()); + if (std::error_code ec = local_md5.getError()) { + LLDB_LOG(log, "[PutFile] couldn't get md5 sum of source: {0}", + ec.message()); + } else { + LLDB_LOGF(log, "[PutFile] destination md5: %016" PRIx64 "%016" PRIx64, + remote_md5->high(), remote_md5->low()); + LLDB_LOGF(log, "[PutFile] local md5: %016" PRIx64 "%016" PRIx64, + local_md5->high(), local_md5->low()); + requires_upload = *remote_md5 != *local_md5; + } + } + + if (!requires_upload) { + LLDB_LOGF(log, "[PutFile] skipping PutFile because md5sums match"); + return error; + } + + uint32_t permissions = source_file.get()->GetPermissions(error); + if (permissions == 0) + permissions = lldb::eFilePermissionsUserRWX; + + lldb::user_id_t dest_file = OpenFile( + destination, File::eOpenOptionCanCreate | File::eOpenOptionWriteOnly | + File::eOpenOptionTruncate | File::eOpenOptionCloseOnExec, + permissions, error); + LLDB_LOGF(log, "dest_file = %" PRIu64 "\n", dest_file); + + if (error.Fail()) + return error; + if (dest_file == UINT64_MAX) + return Status("unable to open target file"); + lldb::WritableDataBufferSP buffer_sp(new DataBufferHeap(1024 * 16, 0)); + uint64_t offset = 0; + for (;;) { + size_t bytes_read = buffer_sp->GetByteSize(); + error = source_file.get()->Read(buffer_sp->GetBytes(), bytes_read); + if (error.Fail() || bytes_read == 0) + break; + + const uint64_t bytes_written = + WriteFile(dest_file, offset, buffer_sp->GetBytes(), bytes_read, error); + if (error.Fail()) + break; + + offset += bytes_written; + if (bytes_written != bytes_read) { + // We didn't write the correct number of bytes, so adjust the file + // position in the source file we are reading from... + source_file.get()->SeekFromStart(offset); + } + } + CloseFile(dest_file, error); + + if (uid == UINT32_MAX && gid == UINT32_MAX) + return error; + + // TODO: ChownFile? + + return error; +} + +Status Platform::GetFile(const FileSpec &source, const FileSpec &destination) { + Status error("unimplemented"); + return error; +} + +Status +Platform::CreateSymlink(const FileSpec &src, // The name of the link is in src + const FileSpec &dst) // The symlink points to dst +{ + if (IsHost()) + return FileSystem::Instance().Symlink(src, dst); + return Status("unimplemented"); +} + +bool Platform::GetFileExists(const lldb_private::FileSpec &file_spec) { + if (IsHost()) + return FileSystem::Instance().Exists(file_spec); + return false; +} + +Status Platform::Unlink(const FileSpec &path) { + if (IsHost()) + return llvm::sys::fs::remove(path.GetPath()); + return Status("unimplemented"); +} + +MmapArgList Platform::GetMmapArgumentList(const ArchSpec &arch, addr_t addr, + addr_t length, unsigned prot, + unsigned flags, addr_t fd, + addr_t offset) { + uint64_t flags_platform = 0; + if (flags & eMmapFlagsPrivate) + flags_platform |= MAP_PRIVATE; + if (flags & eMmapFlagsAnon) + flags_platform |= MAP_ANON; + + MmapArgList args({addr, length, prot, flags_platform, fd, offset}); + return args; +} + +lldb_private::Status Platform::RunShellCommand( + llvm::StringRef command, + const FileSpec & + working_dir, // Pass empty FileSpec to use the current working directory + int *status_ptr, // Pass nullptr if you don't want the process exit status + int *signo_ptr, // Pass nullptr if you don't want the signal that caused the + // process to exit + std::string + *command_output, // Pass nullptr if you don't want the command output + const Timeout<std::micro> &timeout) { + return RunShellCommand(llvm::StringRef(), command, working_dir, status_ptr, + signo_ptr, command_output, timeout); +} + +lldb_private::Status Platform::RunShellCommand( + llvm::StringRef shell, // Pass empty if you want to use the default + // shell interpreter + llvm::StringRef command, // Shouldn't be empty + const FileSpec & + working_dir, // Pass empty FileSpec to use the current working directory + int *status_ptr, // Pass nullptr if you don't want the process exit status + int *signo_ptr, // Pass nullptr if you don't want the signal that caused the + // process to exit + std::string + *command_output, // Pass nullptr if you don't want the command output + const Timeout<std::micro> &timeout) { + if (IsHost()) + return Host::RunShellCommand(shell, command, working_dir, status_ptr, + signo_ptr, command_output, timeout); + return Status("unable to run a remote command without a platform"); +} + +llvm::ErrorOr<llvm::MD5::MD5Result> +Platform::CalculateMD5(const FileSpec &file_spec) { + if (!IsHost()) + return std::make_error_code(std::errc::not_supported); + return llvm::sys::fs::md5_contents(file_spec.GetPath()); +} + +void Platform::SetLocalCacheDirectory(const char *local) { + m_local_cache_directory.assign(local); +} + +const char *Platform::GetLocalCacheDirectory() { + return m_local_cache_directory.c_str(); +} + +static constexpr OptionDefinition g_rsync_option_table[] = { + {LLDB_OPT_SET_ALL, false, "rsync", 'r', OptionParser::eNoArgument, nullptr, + {}, 0, eArgTypeNone, "Enable rsync."}, + {LLDB_OPT_SET_ALL, false, "rsync-opts", 'R', + OptionParser::eRequiredArgument, nullptr, {}, 0, eArgTypeCommandName, + "Platform-specific options required for rsync to work."}, + {LLDB_OPT_SET_ALL, false, "rsync-prefix", 'P', + OptionParser::eRequiredArgument, nullptr, {}, 0, eArgTypeCommandName, + "Platform-specific rsync prefix put before the remote path."}, + {LLDB_OPT_SET_ALL, false, "ignore-remote-hostname", 'i', + OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone, + "Do not automatically fill in the remote hostname when composing the " + "rsync command."}, +}; + +static constexpr OptionDefinition g_ssh_option_table[] = { + {LLDB_OPT_SET_ALL, false, "ssh", 's', OptionParser::eNoArgument, nullptr, + {}, 0, eArgTypeNone, "Enable SSH."}, + {LLDB_OPT_SET_ALL, false, "ssh-opts", 'S', OptionParser::eRequiredArgument, + nullptr, {}, 0, eArgTypeCommandName, + "Platform-specific options required for SSH to work."}, +}; + +static constexpr OptionDefinition g_caching_option_table[] = { + {LLDB_OPT_SET_ALL, false, "local-cache-dir", 'c', + OptionParser::eRequiredArgument, nullptr, {}, 0, eArgTypePath, + "Path in which to store local copies of files."}, +}; + +llvm::ArrayRef<OptionDefinition> OptionGroupPlatformRSync::GetDefinitions() { + return llvm::ArrayRef(g_rsync_option_table); +} + +void OptionGroupPlatformRSync::OptionParsingStarting( + ExecutionContext *execution_context) { + m_rsync = false; + m_rsync_opts.clear(); + m_rsync_prefix.clear(); + m_ignores_remote_hostname = false; +} + +lldb_private::Status +OptionGroupPlatformRSync::SetOptionValue(uint32_t option_idx, + llvm::StringRef option_arg, + ExecutionContext *execution_context) { + Status error; + char short_option = (char)GetDefinitions()[option_idx].short_option; + switch (short_option) { + case 'r': + m_rsync = true; + break; + + case 'R': + m_rsync_opts.assign(std::string(option_arg)); + break; + + case 'P': + m_rsync_prefix.assign(std::string(option_arg)); + break; + + case 'i': + m_ignores_remote_hostname = true; + break; + + default: + error.SetErrorStringWithFormat("unrecognized option '%c'", short_option); + break; + } + + return error; +} + +lldb::BreakpointSP +Platform::SetThreadCreationBreakpoint(lldb_private::Target &target) { + return lldb::BreakpointSP(); +} + +llvm::ArrayRef<OptionDefinition> OptionGroupPlatformSSH::GetDefinitions() { + return llvm::ArrayRef(g_ssh_option_table); +} + +void OptionGroupPlatformSSH::OptionParsingStarting( + ExecutionContext *execution_context) { + m_ssh = false; + m_ssh_opts.clear(); +} + +lldb_private::Status +OptionGroupPlatformSSH::SetOptionValue(uint32_t option_idx, + llvm::StringRef option_arg, + ExecutionContext *execution_context) { + Status error; + char short_option = (char)GetDefinitions()[option_idx].short_option; + switch (short_option) { + case 's': + m_ssh = true; + break; + + case 'S': + m_ssh_opts.assign(std::string(option_arg)); + break; + + default: + error.SetErrorStringWithFormat("unrecognized option '%c'", short_option); + break; + } + + return error; +} + +llvm::ArrayRef<OptionDefinition> OptionGroupPlatformCaching::GetDefinitions() { + return llvm::ArrayRef(g_caching_option_table); +} + +void OptionGroupPlatformCaching::OptionParsingStarting( + ExecutionContext *execution_context) { + m_cache_dir.clear(); +} + +lldb_private::Status OptionGroupPlatformCaching::SetOptionValue( + uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) { + Status error; + char short_option = (char)GetDefinitions()[option_idx].short_option; + switch (short_option) { + case 'c': + m_cache_dir.assign(std::string(option_arg)); + break; + + default: + error.SetErrorStringWithFormat("unrecognized option '%c'", short_option); + break; + } + + return error; +} + +Environment Platform::GetEnvironment() { + if (IsHost()) + return Host::GetEnvironment(); + return Environment(); +} + +const std::vector<ConstString> &Platform::GetTrapHandlerSymbolNames() { + if (!m_calculated_trap_handlers) { + std::lock_guard<std::mutex> guard(m_mutex); + if (!m_calculated_trap_handlers) { + CalculateTrapHandlerSymbolNames(); + m_calculated_trap_handlers = true; + } + } + return m_trap_handlers; +} + +Status +Platform::GetCachedExecutable(ModuleSpec &module_spec, + lldb::ModuleSP &module_sp, + const FileSpecList *module_search_paths_ptr) { + FileSpec platform_spec = module_spec.GetFileSpec(); + Status error = GetRemoteSharedModule( + module_spec, nullptr, module_sp, + [&](const ModuleSpec &spec) { + return Platform::ResolveExecutable(spec, module_sp, + module_search_paths_ptr); + }, + nullptr); + if (error.Success()) { + module_spec.GetFileSpec() = module_sp->GetFileSpec(); + module_spec.GetPlatformFileSpec() = platform_spec; + } + + return error; +} + +Status Platform::GetRemoteSharedModule(const ModuleSpec &module_spec, + Process *process, + lldb::ModuleSP &module_sp, + const ModuleResolver &module_resolver, + bool *did_create_ptr) { + // Get module information from a target. + ModuleSpec resolved_module_spec; + ArchSpec process_host_arch; + bool got_module_spec = false; + if (process) { + process_host_arch = process->GetSystemArchitecture(); + // Try to get module information from the process + if (process->GetModuleSpec(module_spec.GetFileSpec(), + module_spec.GetArchitecture(), + resolved_module_spec)) { + if (!module_spec.GetUUID().IsValid() || + module_spec.GetUUID() == resolved_module_spec.GetUUID()) { + got_module_spec = true; + } + } + } + + if (!module_spec.GetArchitecture().IsValid()) { + Status error; + // No valid architecture was specified, ask the platform for the + // architectures that we should be using (in the correct order) and see if + // we can find a match that way + ModuleSpec arch_module_spec(module_spec); + for (const ArchSpec &arch : GetSupportedArchitectures(process_host_arch)) { + arch_module_spec.GetArchitecture() = arch; + error = ModuleList::GetSharedModule(arch_module_spec, module_sp, nullptr, + nullptr, nullptr); + // Did we find an executable using one of the + if (error.Success() && module_sp) + break; + } + if (module_sp) { + resolved_module_spec = arch_module_spec; + got_module_spec = true; + } + } + + if (!got_module_spec) { + // Get module information from a target. + if (GetModuleSpec(module_spec.GetFileSpec(), module_spec.GetArchitecture(), + resolved_module_spec)) { + if (!module_spec.GetUUID().IsValid() || + module_spec.GetUUID() == resolved_module_spec.GetUUID()) { + got_module_spec = true; + } + } + } + + if (!got_module_spec) { + // Fall back to the given module resolver, which may have its own + // search logic. + return module_resolver(module_spec); + } + + // If we are looking for a specific UUID, make sure resolved_module_spec has + // the same one before we search. + if (module_spec.GetUUID().IsValid()) { + resolved_module_spec.GetUUID() = module_spec.GetUUID(); + } + + // Call locate module callback if set. This allows users to implement their + // own module cache system. For example, to leverage build system artifacts, + // to bypass pulling files from remote platform, or to search symbol files + // from symbol servers. + FileSpec symbol_file_spec; + CallLocateModuleCallbackIfSet(resolved_module_spec, module_sp, + symbol_file_spec, did_create_ptr); + if (module_sp) { + // The module is loaded. + if (symbol_file_spec) { + // 1. module_sp:loaded, symbol_file_spec:set + // The callback found a module file and a symbol file for this + // resolved_module_spec. Set the symbol file to the module. + module_sp->SetSymbolFileFileSpec(symbol_file_spec); + } else { + // 2. module_sp:loaded, symbol_file_spec:empty + // The callback only found a module file for this + // resolved_module_spec. + } + return Status(); + } + + // The module is not loaded by CallLocateModuleCallbackIfSet. + // 3. module_sp:empty, symbol_file_spec:set + // The callback only found a symbol file for the module. We continue to + // find a module file for this resolved_module_spec. and we will call + // module_sp->SetSymbolFileFileSpec with the symbol_file_spec later. + // 4. module_sp:empty, symbol_file_spec:empty + // The callback is not set. Or the callback did not find any module + // files nor any symbol files. Or the callback failed, or something + // went wrong. We continue to find a module file for this + // resolved_module_spec. + + // Trying to find a module by UUID on local file system. + const Status error = module_resolver(resolved_module_spec); + if (error.Success()) { + if (module_sp && symbol_file_spec) { + // Set the symbol file to the module if the locate modudle callback was + // called and returned only a symbol file. + module_sp->SetSymbolFileFileSpec(symbol_file_spec); + } + return error; + } + + // Fallback to call GetCachedSharedModule on failure. + if (GetCachedSharedModule(resolved_module_spec, module_sp, did_create_ptr)) { + if (module_sp && symbol_file_spec) { + // Set the symbol file to the module if the locate modudle callback was + // called and returned only a symbol file. + module_sp->SetSymbolFileFileSpec(symbol_file_spec); + } + return Status(); + } + + return Status("Failed to call GetCachedSharedModule"); +} + +void Platform::CallLocateModuleCallbackIfSet(const ModuleSpec &module_spec, + lldb::ModuleSP &module_sp, + FileSpec &symbol_file_spec, + bool *did_create_ptr) { + if (!m_locate_module_callback) { + // Locate module callback is not set. + return; + } + + FileSpec module_file_spec; + Status error = + m_locate_module_callback(module_spec, module_file_spec, symbol_file_spec); + + // Locate module callback is set and called. Check the error. + Log *log = GetLog(LLDBLog::Platform); + if (error.Fail()) { + LLDB_LOGF(log, "%s: locate module callback failed: %s", + LLVM_PRETTY_FUNCTION, error.AsCString()); + return; + } + + // The locate module callback was succeeded. + // Check the module_file_spec and symbol_file_spec values. + // 1. module:empty symbol:empty -> Failure + // - The callback did not return any files. + // 2. module:exists symbol:exists -> Success + // - The callback returned a module file and a symbol file. + // 3. module:exists symbol:empty -> Success + // - The callback returned only a module file. + // 4. module:empty symbol:exists -> Success + // - The callback returned only a symbol file. + // For example, a breakpad symbol text file. + if (!module_file_spec && !symbol_file_spec) { + // This is '1. module:empty symbol:empty -> Failure' + // The callback did not return any files. + LLDB_LOGF(log, + "%s: locate module callback did not set both " + "module_file_spec and symbol_file_spec", + LLVM_PRETTY_FUNCTION); + return; + } + + // If the callback returned a module file, it should exist. + if (module_file_spec && !FileSystem::Instance().Exists(module_file_spec)) { + LLDB_LOGF(log, + "%s: locate module callback set a non-existent file to " + "module_file_spec: %s", + LLVM_PRETTY_FUNCTION, module_file_spec.GetPath().c_str()); + // Clear symbol_file_spec for the error. + symbol_file_spec.Clear(); + return; + } + + // If the callback returned a symbol file, it should exist. + if (symbol_file_spec && !FileSystem::Instance().Exists(symbol_file_spec)) { + LLDB_LOGF(log, + "%s: locate module callback set a non-existent file to " + "symbol_file_spec: %s", + LLVM_PRETTY_FUNCTION, symbol_file_spec.GetPath().c_str()); + // Clear symbol_file_spec for the error. + symbol_file_spec.Clear(); + return; + } + + if (!module_file_spec && symbol_file_spec) { + // This is '4. module:empty symbol:exists -> Success' + // The locate module callback returned only a symbol file. For example, + // a breakpad symbol text file. GetRemoteSharedModule will use this returned + // symbol_file_spec. + LLDB_LOGF(log, "%s: locate module callback succeeded: symbol=%s", + LLVM_PRETTY_FUNCTION, symbol_file_spec.GetPath().c_str()); + return; + } + + // This is one of the following. + // - 2. module:exists symbol:exists -> Success + // - The callback returned a module file and a symbol file. + // - 3. module:exists symbol:empty -> Success + // - The callback returned Only a module file. + // Load the module file. + auto cached_module_spec(module_spec); + cached_module_spec.GetUUID().Clear(); // Clear UUID since it may contain md5 + // content hash instead of real UUID. + cached_module_spec.GetFileSpec() = module_file_spec; + cached_module_spec.GetPlatformFileSpec() = module_spec.GetFileSpec(); + cached_module_spec.SetObjectOffset(0); + + error = ModuleList::GetSharedModule(cached_module_spec, module_sp, nullptr, + nullptr, did_create_ptr, false); + if (error.Success() && module_sp) { + // Succeeded to load the module file. + LLDB_LOGF(log, "%s: locate module callback succeeded: module=%s symbol=%s", + LLVM_PRETTY_FUNCTION, module_file_spec.GetPath().c_str(), + symbol_file_spec.GetPath().c_str()); + } else { + LLDB_LOGF(log, + "%s: locate module callback succeeded but failed to load: " + "module=%s symbol=%s", + LLVM_PRETTY_FUNCTION, module_file_spec.GetPath().c_str(), + symbol_file_spec.GetPath().c_str()); + // Clear module_sp and symbol_file_spec for the error. + module_sp.reset(); + symbol_file_spec.Clear(); + } +} + +bool Platform::GetCachedSharedModule(const ModuleSpec &module_spec, + lldb::ModuleSP &module_sp, + bool *did_create_ptr) { + if (IsHost() || !GetGlobalPlatformProperties().GetUseModuleCache() || + !GetGlobalPlatformProperties().GetModuleCacheDirectory()) + return false; + + Log *log = GetLog(LLDBLog::Platform); + + // Check local cache for a module. + auto error = m_module_cache->GetAndPut( + GetModuleCacheRoot(), GetCacheHostname(), module_spec, + [this](const ModuleSpec &module_spec, + const FileSpec &tmp_download_file_spec) { + return DownloadModuleSlice( + module_spec.GetFileSpec(), module_spec.GetObjectOffset(), + module_spec.GetObjectSize(), tmp_download_file_spec); + + }, + [this](const ModuleSP &module_sp, + const FileSpec &tmp_download_file_spec) { + return DownloadSymbolFile(module_sp, tmp_download_file_spec); + }, + module_sp, did_create_ptr); + if (error.Success()) + return true; + + LLDB_LOGF(log, "Platform::%s - module %s not found in local cache: %s", + __FUNCTION__, module_spec.GetUUID().GetAsString().c_str(), + error.AsCString()); + return false; +} + +Status Platform::DownloadModuleSlice(const FileSpec &src_file_spec, + const uint64_t src_offset, + const uint64_t src_size, + const FileSpec &dst_file_spec) { + Status error; + + std::error_code EC; + llvm::raw_fd_ostream dst(dst_file_spec.GetPath(), EC, llvm::sys::fs::OF_None); + if (EC) { + error.SetErrorStringWithFormat("unable to open destination file: %s", + dst_file_spec.GetPath().c_str()); + return error; + } + + auto src_fd = OpenFile(src_file_spec, File::eOpenOptionReadOnly, + lldb::eFilePermissionsFileDefault, error); + + if (error.Fail()) { + error.SetErrorStringWithFormat("unable to open source file: %s", + error.AsCString()); + return error; + } + + std::vector<char> buffer(512 * 1024); + auto offset = src_offset; + uint64_t total_bytes_read = 0; + while (total_bytes_read < src_size) { + const auto to_read = std::min(static_cast<uint64_t>(buffer.size()), + src_size - total_bytes_read); + const uint64_t n_read = + ReadFile(src_fd, offset, &buffer[0], to_read, error); + if (error.Fail()) + break; + if (n_read == 0) { + error.SetErrorString("read 0 bytes"); + break; + } + offset += n_read; + total_bytes_read += n_read; + dst.write(&buffer[0], n_read); + } + + Status close_error; + CloseFile(src_fd, close_error); // Ignoring close error. + + return error; +} + +Status Platform::DownloadSymbolFile(const lldb::ModuleSP &module_sp, + const FileSpec &dst_file_spec) { + return Status( + "Symbol file downloading not supported by the default platform."); +} + +FileSpec Platform::GetModuleCacheRoot() { + auto dir_spec = GetGlobalPlatformProperties().GetModuleCacheDirectory(); + dir_spec.AppendPathComponent(GetPluginName()); + return dir_spec; +} + +const char *Platform::GetCacheHostname() { return GetHostname(); } + +const UnixSignalsSP &Platform::GetRemoteUnixSignals() { + static const auto s_default_unix_signals_sp = std::make_shared<UnixSignals>(); + return s_default_unix_signals_sp; +} + +UnixSignalsSP Platform::GetUnixSignals() { + if (IsHost()) + return UnixSignals::CreateForHost(); + return GetRemoteUnixSignals(); +} + +uint32_t Platform::LoadImage(lldb_private::Process *process, + const lldb_private::FileSpec &local_file, + const lldb_private::FileSpec &remote_file, + lldb_private::Status &error) { + if (local_file && remote_file) { + // Both local and remote file was specified. Install the local file to the + // given location. + if (IsRemote() || local_file != remote_file) { + error = Install(local_file, remote_file); + if (error.Fail()) + return LLDB_INVALID_IMAGE_TOKEN; + } + return DoLoadImage(process, remote_file, nullptr, error); + } + + if (local_file) { + // Only local file was specified. Install it to the current working + // directory. + FileSpec target_file = GetWorkingDirectory(); + target_file.AppendPathComponent(local_file.GetFilename().AsCString()); + if (IsRemote() || local_file != target_file) { + error = Install(local_file, target_file); + if (error.Fail()) + return LLDB_INVALID_IMAGE_TOKEN; + } + return DoLoadImage(process, target_file, nullptr, error); + } + + if (remote_file) { + // Only remote file was specified so we don't have to do any copying + return DoLoadImage(process, remote_file, nullptr, error); + } + + error.SetErrorString("Neither local nor remote file was specified"); + return LLDB_INVALID_IMAGE_TOKEN; +} + +uint32_t Platform::DoLoadImage(lldb_private::Process *process, + const lldb_private::FileSpec &remote_file, + const std::vector<std::string> *paths, + lldb_private::Status &error, + lldb_private::FileSpec *loaded_image) { + error.SetErrorString("LoadImage is not supported on the current platform"); + return LLDB_INVALID_IMAGE_TOKEN; +} + +uint32_t Platform::LoadImageUsingPaths(lldb_private::Process *process, + const lldb_private::FileSpec &remote_filename, + const std::vector<std::string> &paths, + lldb_private::Status &error, + lldb_private::FileSpec *loaded_path) +{ + FileSpec file_to_use; + if (remote_filename.IsAbsolute()) + file_to_use = FileSpec(remote_filename.GetFilename().GetStringRef(), + + remote_filename.GetPathStyle()); + else + file_to_use = remote_filename; + + return DoLoadImage(process, file_to_use, &paths, error, loaded_path); +} + +Status Platform::UnloadImage(lldb_private::Process *process, + uint32_t image_token) { + return Status("UnloadImage is not supported on the current platform"); +} + +lldb::ProcessSP Platform::ConnectProcess(llvm::StringRef connect_url, + llvm::StringRef plugin_name, + Debugger &debugger, Target *target, + Status &error) { + return DoConnectProcess(connect_url, plugin_name, debugger, nullptr, target, + error); +} + +lldb::ProcessSP Platform::ConnectProcessSynchronous( + llvm::StringRef connect_url, llvm::StringRef plugin_name, + Debugger &debugger, Stream &stream, Target *target, Status &error) { + return DoConnectProcess(connect_url, plugin_name, debugger, &stream, target, + error); +} + +lldb::ProcessSP Platform::DoConnectProcess(llvm::StringRef connect_url, + llvm::StringRef plugin_name, + Debugger &debugger, Stream *stream, + Target *target, Status &error) { + error.Clear(); + + if (!target) { + ArchSpec arch = Target::GetDefaultArchitecture(); + + const char *triple = + arch.IsValid() ? arch.GetTriple().getTriple().c_str() : ""; + + TargetSP new_target_sp; + error = debugger.GetTargetList().CreateTarget( + debugger, "", triple, eLoadDependentsNo, nullptr, new_target_sp); + + target = new_target_sp.get(); + if (!target || error.Fail()) { + return nullptr; + } + } + + lldb::ProcessSP process_sp = + target->CreateProcess(debugger.GetListener(), plugin_name, nullptr, true); + + if (!process_sp) + return nullptr; + + // If this private method is called with a stream we are synchronous. + const bool synchronous = stream != nullptr; + + ListenerSP listener_sp( + Listener::MakeListener("lldb.Process.ConnectProcess.hijack")); + if (synchronous) + process_sp->HijackProcessEvents(listener_sp); + + error = process_sp->ConnectRemote(connect_url); + if (error.Fail()) { + if (synchronous) + process_sp->RestoreProcessEvents(); + return nullptr; + } + + if (synchronous) { + EventSP event_sp; + process_sp->WaitForProcessToStop(std::nullopt, &event_sp, true, listener_sp, + nullptr); + process_sp->RestoreProcessEvents(); + bool pop_process_io_handler = false; + // This is a user-level stop, so we allow recognizers to select frames. + Process::HandleProcessStateChangedEvent( + event_sp, stream, SelectMostRelevantFrame, pop_process_io_handler); + } + + return process_sp; +} + +size_t Platform::ConnectToWaitingProcesses(lldb_private::Debugger &debugger, + lldb_private::Status &error) { + error.Clear(); + return 0; +} + +size_t Platform::GetSoftwareBreakpointTrapOpcode(Target &target, + BreakpointSite *bp_site) { + ArchSpec arch = target.GetArchitecture(); + assert(arch.IsValid()); + const uint8_t *trap_opcode = nullptr; + size_t trap_opcode_size = 0; + + switch (arch.GetMachine()) { + case llvm::Triple::aarch64_32: + case llvm::Triple::aarch64: { + static const uint8_t g_aarch64_opcode[] = {0x00, 0x00, 0x20, 0xd4}; + trap_opcode = g_aarch64_opcode; + trap_opcode_size = sizeof(g_aarch64_opcode); + } break; + + case llvm::Triple::arc: { + static const uint8_t g_hex_opcode[] = { 0xff, 0x7f }; + trap_opcode = g_hex_opcode; + trap_opcode_size = sizeof(g_hex_opcode); + } break; + + // TODO: support big-endian arm and thumb trap codes. + case llvm::Triple::arm: { + // The ARM reference recommends the use of 0xe7fddefe and 0xdefe but the + // linux kernel does otherwise. + static const uint8_t g_arm_breakpoint_opcode[] = {0xf0, 0x01, 0xf0, 0xe7}; + static const uint8_t g_thumb_breakpoint_opcode[] = {0x01, 0xde}; + + lldb::BreakpointLocationSP bp_loc_sp(bp_site->GetConstituentAtIndex(0)); + AddressClass addr_class = AddressClass::eUnknown; + + if (bp_loc_sp) { + addr_class = bp_loc_sp->GetAddress().GetAddressClass(); + if (addr_class == AddressClass::eUnknown && + (bp_loc_sp->GetAddress().GetFileAddress() & 1)) + addr_class = AddressClass::eCodeAlternateISA; + } + + if (addr_class == AddressClass::eCodeAlternateISA) { + trap_opcode = g_thumb_breakpoint_opcode; + trap_opcode_size = sizeof(g_thumb_breakpoint_opcode); + } else { + trap_opcode = g_arm_breakpoint_opcode; + trap_opcode_size = sizeof(g_arm_breakpoint_opcode); + } + } break; + + case llvm::Triple::avr: { + static const uint8_t g_hex_opcode[] = {0x98, 0x95}; + trap_opcode = g_hex_opcode; + trap_opcode_size = sizeof(g_hex_opcode); + } break; + + case llvm::Triple::mips: + case llvm::Triple::mips64: { + static const uint8_t g_hex_opcode[] = {0x00, 0x00, 0x00, 0x0d}; + trap_opcode = g_hex_opcode; + trap_opcode_size = sizeof(g_hex_opcode); + } break; + + case llvm::Triple::mipsel: + case llvm::Triple::mips64el: { + static const uint8_t g_hex_opcode[] = {0x0d, 0x00, 0x00, 0x00}; + trap_opcode = g_hex_opcode; + trap_opcode_size = sizeof(g_hex_opcode); + } break; + + case llvm::Triple::msp430: { + static const uint8_t g_msp430_opcode[] = {0x43, 0x43}; + trap_opcode = g_msp430_opcode; + trap_opcode_size = sizeof(g_msp430_opcode); + } break; + + case llvm::Triple::systemz: { + static const uint8_t g_hex_opcode[] = {0x00, 0x01}; + trap_opcode = g_hex_opcode; + trap_opcode_size = sizeof(g_hex_opcode); + } break; + + case llvm::Triple::hexagon: { + static const uint8_t g_hex_opcode[] = {0x0c, 0xdb, 0x00, 0x54}; + trap_opcode = g_hex_opcode; + trap_opcode_size = sizeof(g_hex_opcode); + } break; + + case llvm::Triple::ppc: + case llvm::Triple::ppc64: { + static const uint8_t g_ppc_opcode[] = {0x7f, 0xe0, 0x00, 0x08}; + trap_opcode = g_ppc_opcode; + trap_opcode_size = sizeof(g_ppc_opcode); + } break; + + case llvm::Triple::ppc64le: { + static const uint8_t g_ppc64le_opcode[] = {0x08, 0x00, 0xe0, 0x7f}; // trap + trap_opcode = g_ppc64le_opcode; + trap_opcode_size = sizeof(g_ppc64le_opcode); + } break; + + case llvm::Triple::x86: + case llvm::Triple::x86_64: { + static const uint8_t g_i386_opcode[] = {0xCC}; + trap_opcode = g_i386_opcode; + trap_opcode_size = sizeof(g_i386_opcode); + } break; + + case llvm::Triple::riscv32: + case llvm::Triple::riscv64: { + static const uint8_t g_riscv_opcode[] = {0x73, 0x00, 0x10, 0x00}; // ebreak + static const uint8_t g_riscv_opcode_c[] = {0x02, 0x90}; // c.ebreak + if (arch.GetFlags() & ArchSpec::eRISCV_rvc) { + trap_opcode = g_riscv_opcode_c; + trap_opcode_size = sizeof(g_riscv_opcode_c); + } else { + trap_opcode = g_riscv_opcode; + trap_opcode_size = sizeof(g_riscv_opcode); + } + } break; + + case llvm::Triple::loongarch32: + case llvm::Triple::loongarch64: { + static const uint8_t g_loongarch_opcode[] = {0x05, 0x00, 0x2a, + 0x00}; // break 0x5 + trap_opcode = g_loongarch_opcode; + trap_opcode_size = sizeof(g_loongarch_opcode); + } break; + + default: + return 0; + } + + assert(bp_site); + if (bp_site->SetTrapOpcode(trap_opcode, trap_opcode_size)) + return trap_opcode_size; + + return 0; +} + +CompilerType Platform::GetSiginfoType(const llvm::Triple& triple) { + return CompilerType(); +} + +Args Platform::GetExtraStartupCommands() { + return {}; +} + +void Platform::SetLocateModuleCallback(LocateModuleCallback callback) { + m_locate_module_callback = callback; +} + +Platform::LocateModuleCallback Platform::GetLocateModuleCallback() const { + return m_locate_module_callback; +} + +PlatformSP PlatformList::GetOrCreate(llvm::StringRef name) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + for (const PlatformSP &platform_sp : m_platforms) { + if (platform_sp->GetName() == name) + return platform_sp; + } + return Create(name); +} + +PlatformSP PlatformList::GetOrCreate(const ArchSpec &arch, + const ArchSpec &process_host_arch, + ArchSpec *platform_arch_ptr, + Status &error) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + // First try exact arch matches across all platforms already created + for (const auto &platform_sp : m_platforms) { + if (platform_sp->IsCompatibleArchitecture( + arch, process_host_arch, ArchSpec::ExactMatch, platform_arch_ptr)) + return platform_sp; + } + + // Next try compatible arch matches across all platforms already created + for (const auto &platform_sp : m_platforms) { + if (platform_sp->IsCompatibleArchitecture(arch, process_host_arch, + ArchSpec::CompatibleMatch, + platform_arch_ptr)) + return platform_sp; + } + + PlatformCreateInstance create_callback; + // First try exact arch matches across all platform plug-ins + uint32_t idx; + for (idx = 0; + (create_callback = PluginManager::GetPlatformCreateCallbackAtIndex(idx)); + ++idx) { + PlatformSP platform_sp = create_callback(false, &arch); + if (platform_sp && + platform_sp->IsCompatibleArchitecture( + arch, process_host_arch, ArchSpec::ExactMatch, platform_arch_ptr)) { + m_platforms.push_back(platform_sp); + return platform_sp; + } + } + // Next try compatible arch matches across all platform plug-ins + for (idx = 0; + (create_callback = PluginManager::GetPlatformCreateCallbackAtIndex(idx)); + ++idx) { + PlatformSP platform_sp = create_callback(false, &arch); + if (platform_sp && platform_sp->IsCompatibleArchitecture( + arch, process_host_arch, ArchSpec::CompatibleMatch, + platform_arch_ptr)) { + m_platforms.push_back(platform_sp); + return platform_sp; + } + } + if (platform_arch_ptr) + platform_arch_ptr->Clear(); + return nullptr; +} + +PlatformSP PlatformList::GetOrCreate(const ArchSpec &arch, + const ArchSpec &process_host_arch, + ArchSpec *platform_arch_ptr) { + Status error; + if (arch.IsValid()) + return GetOrCreate(arch, process_host_arch, platform_arch_ptr, error); + return nullptr; +} + +PlatformSP PlatformList::GetOrCreate(llvm::ArrayRef<ArchSpec> archs, + const ArchSpec &process_host_arch, + std::vector<PlatformSP> &candidates) { + candidates.clear(); + candidates.reserve(archs.size()); + + if (archs.empty()) + return nullptr; + + PlatformSP host_platform_sp = Platform::GetHostPlatform(); + + // Prefer the selected platform if it matches at least one architecture. + if (m_selected_platform_sp) { + for (const ArchSpec &arch : archs) { + if (m_selected_platform_sp->IsCompatibleArchitecture( + arch, process_host_arch, ArchSpec::CompatibleMatch, nullptr)) + return m_selected_platform_sp; + } + } + + // Prefer the host platform if it matches at least one architecture. + if (host_platform_sp) { + for (const ArchSpec &arch : archs) { + if (host_platform_sp->IsCompatibleArchitecture( + arch, process_host_arch, ArchSpec::CompatibleMatch, nullptr)) + return host_platform_sp; + } + } + + // Collect a list of candidate platforms for the architectures. + for (const ArchSpec &arch : archs) { + if (PlatformSP platform = GetOrCreate(arch, process_host_arch, nullptr)) + candidates.push_back(platform); + } + + // The selected or host platform didn't match any of the architectures. If + // the same platform supports all architectures then that's the obvious next + // best thing. + if (candidates.size() == archs.size()) { + if (llvm::all_of(candidates, [&](const PlatformSP &p) -> bool { + return p->GetName() == candidates.front()->GetName(); + })) { + return candidates.front(); + } + } + + // At this point we either have no platforms that match the given + // architectures or multiple platforms with no good way to disambiguate + // between them. + return nullptr; +} + +PlatformSP PlatformList::Create(llvm::StringRef name) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + PlatformSP platform_sp = Platform::Create(name); + m_platforms.push_back(platform_sp); + return platform_sp; +} + +bool PlatformList::LoadPlatformBinaryAndSetup(Process *process, + lldb::addr_t addr, bool notify) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + + PlatformCreateInstance create_callback; + for (int idx = 0; + (create_callback = PluginManager::GetPlatformCreateCallbackAtIndex(idx)); + ++idx) { + ArchSpec arch; + PlatformSP platform_sp = create_callback(true, &arch); + if (platform_sp) { + if (platform_sp->LoadPlatformBinaryAndSetup(process, addr, notify)) + return true; + } + } + return false; +} diff --git a/contrib/llvm-project/lldb/source/Target/Process.cpp b/contrib/llvm-project/lldb/source/Target/Process.cpp new file mode 100644 index 000000000000..d5a639d9beac --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/Process.cpp @@ -0,0 +1,6691 @@ +//===-- Process.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 <atomic> +#include <memory> +#include <mutex> +#include <optional> + +#include "llvm/ADT/ScopeExit.h" +#include "llvm/Support/ScopedPrinter.h" +#include "llvm/Support/Threading.h" + +#include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Breakpoint/StoppointCallbackContext.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/Progress.h" +#include "lldb/Expression/DiagnosticManager.h" +#include "lldb/Expression/DynamicCheckerFunctions.h" +#include "lldb/Expression/UserExpression.h" +#include "lldb/Expression/UtilityFunction.h" +#include "lldb/Host/ConnectionFileDescriptor.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/Host.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Host/Pipe.h" +#include "lldb/Host/Terminal.h" +#include "lldb/Host/ThreadLauncher.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Interpreter/OptionValueProperties.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Target/ABI.h" +#include "lldb/Target/AssertFrameRecognizer.h" +#include "lldb/Target/DynamicLoader.h" +#include "lldb/Target/InstrumentationRuntime.h" +#include "lldb/Target/JITLoader.h" +#include "lldb/Target/JITLoaderList.h" +#include "lldb/Target/Language.h" +#include "lldb/Target/LanguageRuntime.h" +#include "lldb/Target/MemoryHistory.h" +#include "lldb/Target/MemoryRegionInfo.h" +#include "lldb/Target/OperatingSystem.h" +#include "lldb/Target/Platform.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/StopInfo.h" +#include "lldb/Target/StructuredDataPlugin.h" +#include "lldb/Target/SystemRuntime.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/TargetList.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadPlan.h" +#include "lldb/Target/ThreadPlanBase.h" +#include "lldb/Target/ThreadPlanCallFunction.h" +#include "lldb/Target/ThreadPlanStack.h" +#include "lldb/Target/UnixSignals.h" +#include "lldb/Target/VerboseTrapFrameRecognizer.h" +#include "lldb/Utility/AddressableBits.h" +#include "lldb/Utility/Event.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/NameMatches.h" +#include "lldb/Utility/ProcessInfo.h" +#include "lldb/Utility/SelectHelper.h" +#include "lldb/Utility/State.h" +#include "lldb/Utility/Timer.h" + +using namespace lldb; +using namespace lldb_private; +using namespace std::chrono; + +// Comment out line below to disable memory caching, overriding the process +// setting target.process.disable-memory-cache +#define ENABLE_MEMORY_CACHING + +#ifdef ENABLE_MEMORY_CACHING +#define DISABLE_MEM_CACHE_DEFAULT false +#else +#define DISABLE_MEM_CACHE_DEFAULT true +#endif + +class ProcessOptionValueProperties + : public Cloneable<ProcessOptionValueProperties, OptionValueProperties> { +public: + ProcessOptionValueProperties(llvm::StringRef name) : Cloneable(name) {} + + const Property * + GetPropertyAtIndex(size_t idx, + const ExecutionContext *exe_ctx) const override { + // When getting the value for a key from the process options, we will + // always try and grab the setting from the current process if there is + // one. Else we just use the one from this instance. + if (exe_ctx) { + Process *process = exe_ctx->GetProcessPtr(); + if (process) { + ProcessOptionValueProperties *instance_properties = + static_cast<ProcessOptionValueProperties *>( + process->GetValueProperties().get()); + if (this != instance_properties) + return instance_properties->ProtectedGetPropertyAtIndex(idx); + } + } + return ProtectedGetPropertyAtIndex(idx); + } +}; + +class ProcessMemoryIterator { +public: + ProcessMemoryIterator(Process &process, lldb::addr_t base) + : m_process(process), m_base_addr(base) {} + + bool IsValid() { return m_is_valid; } + + uint8_t operator[](lldb::addr_t offset) { + if (!IsValid()) + return 0; + + uint8_t retval = 0; + Status error; + if (0 == m_process.ReadMemory(m_base_addr + offset, &retval, 1, error)) { + m_is_valid = false; + return 0; + } + + return retval; + } + +private: + Process &m_process; + const lldb::addr_t m_base_addr; + bool m_is_valid = true; +}; + +static constexpr OptionEnumValueElement g_follow_fork_mode_values[] = { + { + eFollowParent, + "parent", + "Continue tracing the parent process and detach the child.", + }, + { + eFollowChild, + "child", + "Trace the child process and detach the parent.", + }, +}; + +#define LLDB_PROPERTIES_process +#include "TargetProperties.inc" + +enum { +#define LLDB_PROPERTIES_process +#include "TargetPropertiesEnum.inc" + ePropertyExperimental, +}; + +#define LLDB_PROPERTIES_process_experimental +#include "TargetProperties.inc" + +enum { +#define LLDB_PROPERTIES_process_experimental +#include "TargetPropertiesEnum.inc" +}; + +class ProcessExperimentalOptionValueProperties + : public Cloneable<ProcessExperimentalOptionValueProperties, + OptionValueProperties> { +public: + ProcessExperimentalOptionValueProperties() + : Cloneable(Properties::GetExperimentalSettingsName()) {} +}; + +ProcessExperimentalProperties::ProcessExperimentalProperties() + : Properties(OptionValuePropertiesSP( + new ProcessExperimentalOptionValueProperties())) { + m_collection_sp->Initialize(g_process_experimental_properties); +} + +ProcessProperties::ProcessProperties(lldb_private::Process *process) + : Properties(), + m_process(process) // Can be nullptr for global ProcessProperties +{ + if (process == nullptr) { + // Global process properties, set them up one time + m_collection_sp = std::make_shared<ProcessOptionValueProperties>("process"); + m_collection_sp->Initialize(g_process_properties); + m_collection_sp->AppendProperty( + "thread", "Settings specific to threads.", true, + Thread::GetGlobalProperties().GetValueProperties()); + } else { + m_collection_sp = + OptionValueProperties::CreateLocalCopy(Process::GetGlobalProperties()); + m_collection_sp->SetValueChangedCallback( + ePropertyPythonOSPluginPath, + [this] { m_process->LoadOperatingSystemPlugin(true); }); + } + + m_experimental_properties_up = + std::make_unique<ProcessExperimentalProperties>(); + m_collection_sp->AppendProperty( + Properties::GetExperimentalSettingsName(), + "Experimental settings - setting these won't produce " + "errors if the setting is not present.", + true, m_experimental_properties_up->GetValueProperties()); +} + +ProcessProperties::~ProcessProperties() = default; + +bool ProcessProperties::GetDisableMemoryCache() const { + const uint32_t idx = ePropertyDisableMemCache; + return GetPropertyAtIndexAs<bool>( + idx, g_process_properties[idx].default_uint_value != 0); +} + +uint64_t ProcessProperties::GetMemoryCacheLineSize() const { + const uint32_t idx = ePropertyMemCacheLineSize; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_process_properties[idx].default_uint_value); +} + +Args ProcessProperties::GetExtraStartupCommands() const { + Args args; + const uint32_t idx = ePropertyExtraStartCommand; + m_collection_sp->GetPropertyAtIndexAsArgs(idx, args); + return args; +} + +void ProcessProperties::SetExtraStartupCommands(const Args &args) { + const uint32_t idx = ePropertyExtraStartCommand; + m_collection_sp->SetPropertyAtIndexFromArgs(idx, args); +} + +FileSpec ProcessProperties::GetPythonOSPluginPath() const { + const uint32_t idx = ePropertyPythonOSPluginPath; + return GetPropertyAtIndexAs<FileSpec>(idx, {}); +} + +uint32_t ProcessProperties::GetVirtualAddressableBits() const { + const uint32_t idx = ePropertyVirtualAddressableBits; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_process_properties[idx].default_uint_value); +} + +void ProcessProperties::SetVirtualAddressableBits(uint32_t bits) { + const uint32_t idx = ePropertyVirtualAddressableBits; + SetPropertyAtIndex(idx, static_cast<uint64_t>(bits)); +} + +uint32_t ProcessProperties::GetHighmemVirtualAddressableBits() const { + const uint32_t idx = ePropertyHighmemVirtualAddressableBits; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_process_properties[idx].default_uint_value); +} + +void ProcessProperties::SetHighmemVirtualAddressableBits(uint32_t bits) { + const uint32_t idx = ePropertyHighmemVirtualAddressableBits; + SetPropertyAtIndex(idx, static_cast<uint64_t>(bits)); +} + +void ProcessProperties::SetPythonOSPluginPath(const FileSpec &file) { + const uint32_t idx = ePropertyPythonOSPluginPath; + SetPropertyAtIndex(idx, file); +} + +bool ProcessProperties::GetIgnoreBreakpointsInExpressions() const { + const uint32_t idx = ePropertyIgnoreBreakpointsInExpressions; + return GetPropertyAtIndexAs<bool>( + idx, g_process_properties[idx].default_uint_value != 0); +} + +void ProcessProperties::SetIgnoreBreakpointsInExpressions(bool ignore) { + const uint32_t idx = ePropertyIgnoreBreakpointsInExpressions; + SetPropertyAtIndex(idx, ignore); +} + +bool ProcessProperties::GetUnwindOnErrorInExpressions() const { + const uint32_t idx = ePropertyUnwindOnErrorInExpressions; + return GetPropertyAtIndexAs<bool>( + idx, g_process_properties[idx].default_uint_value != 0); +} + +void ProcessProperties::SetUnwindOnErrorInExpressions(bool ignore) { + const uint32_t idx = ePropertyUnwindOnErrorInExpressions; + SetPropertyAtIndex(idx, ignore); +} + +bool ProcessProperties::GetStopOnSharedLibraryEvents() const { + const uint32_t idx = ePropertyStopOnSharedLibraryEvents; + return GetPropertyAtIndexAs<bool>( + idx, g_process_properties[idx].default_uint_value != 0); +} + +void ProcessProperties::SetStopOnSharedLibraryEvents(bool stop) { + const uint32_t idx = ePropertyStopOnSharedLibraryEvents; + SetPropertyAtIndex(idx, stop); +} + +bool ProcessProperties::GetDisableLangRuntimeUnwindPlans() const { + const uint32_t idx = ePropertyDisableLangRuntimeUnwindPlans; + return GetPropertyAtIndexAs<bool>( + idx, g_process_properties[idx].default_uint_value != 0); +} + +void ProcessProperties::SetDisableLangRuntimeUnwindPlans(bool disable) { + const uint32_t idx = ePropertyDisableLangRuntimeUnwindPlans; + SetPropertyAtIndex(idx, disable); + m_process->Flush(); +} + +bool ProcessProperties::GetDetachKeepsStopped() const { + const uint32_t idx = ePropertyDetachKeepsStopped; + return GetPropertyAtIndexAs<bool>( + idx, g_process_properties[idx].default_uint_value != 0); +} + +void ProcessProperties::SetDetachKeepsStopped(bool stop) { + const uint32_t idx = ePropertyDetachKeepsStopped; + SetPropertyAtIndex(idx, stop); +} + +bool ProcessProperties::GetWarningsOptimization() const { + const uint32_t idx = ePropertyWarningOptimization; + return GetPropertyAtIndexAs<bool>( + idx, g_process_properties[idx].default_uint_value != 0); +} + +bool ProcessProperties::GetWarningsUnsupportedLanguage() const { + const uint32_t idx = ePropertyWarningUnsupportedLanguage; + return GetPropertyAtIndexAs<bool>( + idx, g_process_properties[idx].default_uint_value != 0); +} + +bool ProcessProperties::GetStopOnExec() const { + const uint32_t idx = ePropertyStopOnExec; + return GetPropertyAtIndexAs<bool>( + idx, g_process_properties[idx].default_uint_value != 0); +} + +std::chrono::seconds ProcessProperties::GetUtilityExpressionTimeout() const { + const uint32_t idx = ePropertyUtilityExpressionTimeout; + uint64_t value = GetPropertyAtIndexAs<uint64_t>( + idx, g_process_properties[idx].default_uint_value); + return std::chrono::seconds(value); +} + +std::chrono::seconds ProcessProperties::GetInterruptTimeout() const { + const uint32_t idx = ePropertyInterruptTimeout; + uint64_t value = GetPropertyAtIndexAs<uint64_t>( + idx, g_process_properties[idx].default_uint_value); + return std::chrono::seconds(value); +} + +bool ProcessProperties::GetSteppingRunsAllThreads() const { + const uint32_t idx = ePropertySteppingRunsAllThreads; + return GetPropertyAtIndexAs<bool>( + idx, g_process_properties[idx].default_uint_value != 0); +} + +bool ProcessProperties::GetOSPluginReportsAllThreads() const { + const bool fail_value = true; + const Property *exp_property = + m_collection_sp->GetPropertyAtIndex(ePropertyExperimental); + OptionValueProperties *exp_values = + exp_property->GetValue()->GetAsProperties(); + if (!exp_values) + return fail_value; + + return exp_values + ->GetPropertyAtIndexAs<bool>(ePropertyOSPluginReportsAllThreads) + .value_or(fail_value); +} + +void ProcessProperties::SetOSPluginReportsAllThreads(bool does_report) { + const Property *exp_property = + m_collection_sp->GetPropertyAtIndex(ePropertyExperimental); + OptionValueProperties *exp_values = + exp_property->GetValue()->GetAsProperties(); + if (exp_values) + exp_values->SetPropertyAtIndex(ePropertyOSPluginReportsAllThreads, + does_report); +} + +FollowForkMode ProcessProperties::GetFollowForkMode() const { + const uint32_t idx = ePropertyFollowForkMode; + return GetPropertyAtIndexAs<FollowForkMode>( + idx, static_cast<FollowForkMode>( + g_process_properties[idx].default_uint_value)); +} + +ProcessSP Process::FindPlugin(lldb::TargetSP target_sp, + llvm::StringRef plugin_name, + ListenerSP listener_sp, + const FileSpec *crash_file_path, + bool can_connect) { + static uint32_t g_process_unique_id = 0; + + ProcessSP process_sp; + ProcessCreateInstance create_callback = nullptr; + if (!plugin_name.empty()) { + create_callback = + PluginManager::GetProcessCreateCallbackForPluginName(plugin_name); + if (create_callback) { + process_sp = create_callback(target_sp, listener_sp, crash_file_path, + can_connect); + if (process_sp) { + if (process_sp->CanDebug(target_sp, true)) { + process_sp->m_process_unique_id = ++g_process_unique_id; + } else + process_sp.reset(); + } + } + } else { + for (uint32_t idx = 0; + (create_callback = + PluginManager::GetProcessCreateCallbackAtIndex(idx)) != nullptr; + ++idx) { + process_sp = create_callback(target_sp, listener_sp, crash_file_path, + can_connect); + if (process_sp) { + if (process_sp->CanDebug(target_sp, false)) { + process_sp->m_process_unique_id = ++g_process_unique_id; + break; + } else + process_sp.reset(); + } + } + } + return process_sp; +} + +llvm::StringRef Process::GetStaticBroadcasterClass() { + static constexpr llvm::StringLiteral class_name("lldb.process"); + return class_name; +} + +Process::Process(lldb::TargetSP target_sp, ListenerSP listener_sp) + : Process(target_sp, listener_sp, UnixSignals::CreateForHost()) { + // This constructor just delegates to the full Process constructor, + // defaulting to using the Host's UnixSignals. +} + +Process::Process(lldb::TargetSP target_sp, ListenerSP listener_sp, + const UnixSignalsSP &unix_signals_sp) + : ProcessProperties(this), + Broadcaster((target_sp->GetDebugger().GetBroadcasterManager()), + Process::GetStaticBroadcasterClass().str()), + m_target_wp(target_sp), m_public_state(eStateUnloaded), + m_private_state(eStateUnloaded), + m_private_state_broadcaster(nullptr, + "lldb.process.internal_state_broadcaster"), + m_private_state_control_broadcaster( + nullptr, "lldb.process.internal_state_control_broadcaster"), + m_private_state_listener_sp( + Listener::MakeListener("lldb.process.internal_state_listener")), + m_mod_id(), m_process_unique_id(0), m_thread_index_id(0), + m_thread_id_to_index_id_map(), m_exit_status(-1), + m_thread_list_real(*this), m_thread_list(*this), m_thread_plans(*this), + m_extended_thread_list(*this), m_extended_thread_stop_id(0), + m_queue_list(this), m_queue_list_stop_id(0), + m_unix_signals_sp(unix_signals_sp), m_abi_sp(), m_process_input_reader(), + m_stdio_communication("process.stdio"), m_stdio_communication_mutex(), + m_stdin_forward(false), m_stdout_data(), m_stderr_data(), + m_profile_data_comm_mutex(), m_profile_data(), m_iohandler_sync(0), + m_memory_cache(*this), m_allocated_memory_cache(*this), + m_should_detach(false), m_next_event_action_up(), m_public_run_lock(), + m_private_run_lock(), m_currently_handling_do_on_removals(false), + m_resume_requested(false), m_finalizing(false), m_destructing(false), + m_clear_thread_plans_on_stop(false), m_force_next_event_delivery(false), + m_last_broadcast_state(eStateInvalid), m_destroy_in_process(false), + m_can_interpret_function_calls(false), m_run_thread_plan_lock(), + m_can_jit(eCanJITDontKnow) { + CheckInWithManager(); + + Log *log = GetLog(LLDBLog::Object); + LLDB_LOGF(log, "%p Process::Process()", static_cast<void *>(this)); + + if (!m_unix_signals_sp) + m_unix_signals_sp = std::make_shared<UnixSignals>(); + + SetEventName(eBroadcastBitStateChanged, "state-changed"); + SetEventName(eBroadcastBitInterrupt, "interrupt"); + SetEventName(eBroadcastBitSTDOUT, "stdout-available"); + SetEventName(eBroadcastBitSTDERR, "stderr-available"); + SetEventName(eBroadcastBitProfileData, "profile-data-available"); + SetEventName(eBroadcastBitStructuredData, "structured-data-available"); + + m_private_state_control_broadcaster.SetEventName( + eBroadcastInternalStateControlStop, "control-stop"); + m_private_state_control_broadcaster.SetEventName( + eBroadcastInternalStateControlPause, "control-pause"); + m_private_state_control_broadcaster.SetEventName( + eBroadcastInternalStateControlResume, "control-resume"); + + // The listener passed into process creation is the primary listener: + // It always listens for all the event bits for Process: + SetPrimaryListener(listener_sp); + + m_private_state_listener_sp->StartListeningForEvents( + &m_private_state_broadcaster, + eBroadcastBitStateChanged | eBroadcastBitInterrupt); + + m_private_state_listener_sp->StartListeningForEvents( + &m_private_state_control_broadcaster, + eBroadcastInternalStateControlStop | eBroadcastInternalStateControlPause | + eBroadcastInternalStateControlResume); + // We need something valid here, even if just the default UnixSignalsSP. + assert(m_unix_signals_sp && "null m_unix_signals_sp after initialization"); + + // Allow the platform to override the default cache line size + OptionValueSP value_sp = + m_collection_sp->GetPropertyAtIndex(ePropertyMemCacheLineSize) + ->GetValue(); + uint64_t platform_cache_line_size = + target_sp->GetPlatform()->GetDefaultMemoryCacheLineSize(); + if (!value_sp->OptionWasSet() && platform_cache_line_size != 0) + value_sp->SetValueAs(platform_cache_line_size); + + // FIXME: Frame recognizer registration should not be done in Target. + // We should have a plugin do the registration instead, for example, a + // common C LanguageRuntime plugin. + RegisterAssertFrameRecognizer(this); + RegisterVerboseTrapFrameRecognizer(*this); +} + +Process::~Process() { + Log *log = GetLog(LLDBLog::Object); + LLDB_LOGF(log, "%p Process::~Process()", static_cast<void *>(this)); + StopPrivateStateThread(); + + // ThreadList::Clear() will try to acquire this process's mutex, so + // explicitly clear the thread list here to ensure that the mutex is not + // destroyed before the thread list. + m_thread_list.Clear(); +} + +ProcessProperties &Process::GetGlobalProperties() { + // NOTE: intentional leak so we don't crash if global destructor chain gets + // called as other threads still use the result of this function + static ProcessProperties *g_settings_ptr = + new ProcessProperties(nullptr); + return *g_settings_ptr; +} + +void Process::Finalize(bool destructing) { + if (m_finalizing.exchange(true)) + return; + if (destructing) + m_destructing.exchange(true); + + // Destroy the process. This will call the virtual function DoDestroy under + // the hood, giving our derived class a chance to do the ncessary tear down. + DestroyImpl(false); + + // Clear our broadcaster before we proceed with destroying + Broadcaster::Clear(); + + // Do any cleanup needed prior to being destructed... Subclasses that + // override this method should call this superclass method as well. + + // We need to destroy the loader before the derived Process class gets + // destroyed since it is very likely that undoing the loader will require + // access to the real process. + m_dynamic_checkers_up.reset(); + m_abi_sp.reset(); + m_os_up.reset(); + m_system_runtime_up.reset(); + m_dyld_up.reset(); + m_jit_loaders_up.reset(); + m_thread_plans.Clear(); + m_thread_list_real.Destroy(); + m_thread_list.Destroy(); + m_extended_thread_list.Destroy(); + m_queue_list.Clear(); + m_queue_list_stop_id = 0; + m_watchpoint_resource_list.Clear(); + std::vector<Notifications> empty_notifications; + m_notifications.swap(empty_notifications); + m_image_tokens.clear(); + m_memory_cache.Clear(); + m_allocated_memory_cache.Clear(/*deallocate_memory=*/true); + { + std::lock_guard<std::recursive_mutex> guard(m_language_runtimes_mutex); + m_language_runtimes.clear(); + } + m_instrumentation_runtimes.clear(); + m_next_event_action_up.reset(); + // Clear the last natural stop ID since it has a strong reference to this + // process + m_mod_id.SetStopEventForLastNaturalStopID(EventSP()); + // We have to be very careful here as the m_private_state_listener might + // contain events that have ProcessSP values in them which can keep this + // process around forever. These events need to be cleared out. + m_private_state_listener_sp->Clear(); + m_public_run_lock.TrySetRunning(); // This will do nothing if already locked + m_public_run_lock.SetStopped(); + m_private_run_lock.TrySetRunning(); // This will do nothing if already locked + m_private_run_lock.SetStopped(); + m_structured_data_plugin_map.clear(); +} + +void Process::RegisterNotificationCallbacks(const Notifications &callbacks) { + m_notifications.push_back(callbacks); + if (callbacks.initialize != nullptr) + callbacks.initialize(callbacks.baton, this); +} + +bool Process::UnregisterNotificationCallbacks(const Notifications &callbacks) { + std::vector<Notifications>::iterator pos, end = m_notifications.end(); + for (pos = m_notifications.begin(); pos != end; ++pos) { + if (pos->baton == callbacks.baton && + pos->initialize == callbacks.initialize && + pos->process_state_changed == callbacks.process_state_changed) { + m_notifications.erase(pos); + return true; + } + } + return false; +} + +void Process::SynchronouslyNotifyStateChanged(StateType state) { + std::vector<Notifications>::iterator notification_pos, + notification_end = m_notifications.end(); + for (notification_pos = m_notifications.begin(); + notification_pos != notification_end; ++notification_pos) { + if (notification_pos->process_state_changed) + notification_pos->process_state_changed(notification_pos->baton, this, + state); + } +} + +// FIXME: We need to do some work on events before the general Listener sees +// them. +// For instance if we are continuing from a breakpoint, we need to ensure that +// we do the little "insert real insn, step & stop" trick. But we can't do +// that when the event is delivered by the broadcaster - since that is done on +// the thread that is waiting for new events, so if we needed more than one +// event for our handling, we would stall. So instead we do it when we fetch +// the event off of the queue. +// + +StateType Process::GetNextEvent(EventSP &event_sp) { + StateType state = eStateInvalid; + + if (GetPrimaryListener()->GetEventForBroadcaster(this, event_sp, + std::chrono::seconds(0)) && + event_sp) + state = Process::ProcessEventData::GetStateFromEvent(event_sp.get()); + + return state; +} + +void Process::SyncIOHandler(uint32_t iohandler_id, + const Timeout<std::micro> &timeout) { + // don't sync (potentially context switch) in case where there is no process + // IO + if (!ProcessIOHandlerExists()) + return; + + auto Result = m_iohandler_sync.WaitForValueNotEqualTo(iohandler_id, timeout); + + Log *log = GetLog(LLDBLog::Process); + if (Result) { + LLDB_LOG( + log, + "waited from m_iohandler_sync to change from {0}. New value is {1}.", + iohandler_id, *Result); + } else { + LLDB_LOG(log, "timed out waiting for m_iohandler_sync to change from {0}.", + iohandler_id); + } +} + +StateType Process::WaitForProcessToStop( + const Timeout<std::micro> &timeout, EventSP *event_sp_ptr, bool wait_always, + ListenerSP hijack_listener_sp, Stream *stream, bool use_run_lock, + SelectMostRelevant select_most_relevant) { + // We can't just wait for a "stopped" event, because the stopped event may + // have restarted the target. We have to actually check each event, and in + // the case of a stopped event check the restarted flag on the event. + if (event_sp_ptr) + event_sp_ptr->reset(); + StateType state = GetState(); + // If we are exited or detached, we won't ever get back to any other valid + // state... + if (state == eStateDetached || state == eStateExited) + return state; + + Log *log = GetLog(LLDBLog::Process); + LLDB_LOG(log, "timeout = {0}", timeout); + + if (!wait_always && StateIsStoppedState(state, true) && + StateIsStoppedState(GetPrivateState(), true)) { + LLDB_LOGF(log, + "Process::%s returning without waiting for events; process " + "private and public states are already 'stopped'.", + __FUNCTION__); + // We need to toggle the run lock as this won't get done in + // SetPublicState() if the process is hijacked. + if (hijack_listener_sp && use_run_lock) + m_public_run_lock.SetStopped(); + return state; + } + + while (state != eStateInvalid) { + EventSP event_sp; + state = GetStateChangedEvents(event_sp, timeout, hijack_listener_sp); + if (event_sp_ptr && event_sp) + *event_sp_ptr = event_sp; + + bool pop_process_io_handler = (hijack_listener_sp.get() != nullptr); + Process::HandleProcessStateChangedEvent( + event_sp, stream, select_most_relevant, pop_process_io_handler); + + switch (state) { + case eStateCrashed: + case eStateDetached: + case eStateExited: + case eStateUnloaded: + // We need to toggle the run lock as this won't get done in + // SetPublicState() if the process is hijacked. + if (hijack_listener_sp && use_run_lock) + m_public_run_lock.SetStopped(); + return state; + case eStateStopped: + if (Process::ProcessEventData::GetRestartedFromEvent(event_sp.get())) + continue; + else { + // We need to toggle the run lock as this won't get done in + // SetPublicState() if the process is hijacked. + if (hijack_listener_sp && use_run_lock) + m_public_run_lock.SetStopped(); + return state; + } + default: + continue; + } + } + return state; +} + +bool Process::HandleProcessStateChangedEvent( + const EventSP &event_sp, Stream *stream, + SelectMostRelevant select_most_relevant, + bool &pop_process_io_handler) { + const bool handle_pop = pop_process_io_handler; + + pop_process_io_handler = false; + ProcessSP process_sp = + Process::ProcessEventData::GetProcessFromEvent(event_sp.get()); + + if (!process_sp) + return false; + + StateType event_state = + Process::ProcessEventData::GetStateFromEvent(event_sp.get()); + if (event_state == eStateInvalid) + return false; + + switch (event_state) { + case eStateInvalid: + case eStateUnloaded: + case eStateAttaching: + case eStateLaunching: + case eStateStepping: + case eStateDetached: + if (stream) + stream->Printf("Process %" PRIu64 " %s\n", process_sp->GetID(), + StateAsCString(event_state)); + if (event_state == eStateDetached) + pop_process_io_handler = true; + break; + + case eStateConnected: + case eStateRunning: + // Don't be chatty when we run... + break; + + case eStateExited: + if (stream) + process_sp->GetStatus(*stream); + pop_process_io_handler = true; + break; + + case eStateStopped: + case eStateCrashed: + case eStateSuspended: + // Make sure the program hasn't been auto-restarted: + if (Process::ProcessEventData::GetRestartedFromEvent(event_sp.get())) { + if (stream) { + size_t num_reasons = + Process::ProcessEventData::GetNumRestartedReasons(event_sp.get()); + if (num_reasons > 0) { + // FIXME: Do we want to report this, or would that just be annoyingly + // chatty? + if (num_reasons == 1) { + const char *reason = + Process::ProcessEventData::GetRestartedReasonAtIndex( + event_sp.get(), 0); + stream->Printf("Process %" PRIu64 " stopped and restarted: %s\n", + process_sp->GetID(), + reason ? reason : "<UNKNOWN REASON>"); + } else { + stream->Printf("Process %" PRIu64 + " stopped and restarted, reasons:\n", + process_sp->GetID()); + + for (size_t i = 0; i < num_reasons; i++) { + const char *reason = + Process::ProcessEventData::GetRestartedReasonAtIndex( + event_sp.get(), i); + stream->Printf("\t%s\n", reason ? reason : "<UNKNOWN REASON>"); + } + } + } + } + } else { + StopInfoSP curr_thread_stop_info_sp; + // Lock the thread list so it doesn't change on us, this is the scope for + // the locker: + { + ThreadList &thread_list = process_sp->GetThreadList(); + std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex()); + + ThreadSP curr_thread(thread_list.GetSelectedThread()); + ThreadSP thread; + StopReason curr_thread_stop_reason = eStopReasonInvalid; + bool prefer_curr_thread = false; + if (curr_thread && curr_thread->IsValid()) { + curr_thread_stop_reason = curr_thread->GetStopReason(); + switch (curr_thread_stop_reason) { + case eStopReasonNone: + case eStopReasonInvalid: + // Don't prefer the current thread if it didn't stop for a reason. + break; + case eStopReasonSignal: { + // We need to do the same computation we do for other threads + // below in case the current thread happens to be the one that + // stopped for the no-stop signal. + uint64_t signo = curr_thread->GetStopInfo()->GetValue(); + if (process_sp->GetUnixSignals()->GetShouldStop(signo)) + prefer_curr_thread = true; + } break; + default: + prefer_curr_thread = true; + break; + } + curr_thread_stop_info_sp = curr_thread->GetStopInfo(); + } + + if (!prefer_curr_thread) { + // Prefer a thread that has just completed its plan over another + // thread as current thread. + ThreadSP plan_thread; + ThreadSP other_thread; + + const size_t num_threads = thread_list.GetSize(); + size_t i; + for (i = 0; i < num_threads; ++i) { + thread = thread_list.GetThreadAtIndex(i); + StopReason thread_stop_reason = thread->GetStopReason(); + switch (thread_stop_reason) { + case eStopReasonInvalid: + case eStopReasonNone: + break; + + case eStopReasonSignal: { + // Don't select a signal thread if we weren't going to stop at + // that signal. We have to have had another reason for stopping + // here, and the user doesn't want to see this thread. + uint64_t signo = thread->GetStopInfo()->GetValue(); + if (process_sp->GetUnixSignals()->GetShouldStop(signo)) { + if (!other_thread) + other_thread = thread; + } + break; + } + case eStopReasonTrace: + case eStopReasonBreakpoint: + case eStopReasonWatchpoint: + case eStopReasonException: + case eStopReasonExec: + case eStopReasonFork: + case eStopReasonVFork: + case eStopReasonVForkDone: + case eStopReasonThreadExiting: + case eStopReasonInstrumentation: + case eStopReasonProcessorTrace: + if (!other_thread) + other_thread = thread; + break; + case eStopReasonPlanComplete: + if (!plan_thread) + plan_thread = thread; + break; + } + } + if (plan_thread) + thread_list.SetSelectedThreadByID(plan_thread->GetID()); + else if (other_thread) + thread_list.SetSelectedThreadByID(other_thread->GetID()); + else { + if (curr_thread && curr_thread->IsValid()) + thread = curr_thread; + else + thread = thread_list.GetThreadAtIndex(0); + + if (thread) + thread_list.SetSelectedThreadByID(thread->GetID()); + } + } + } + // Drop the ThreadList mutex by here, since GetThreadStatus below might + // have to run code, e.g. for Data formatters, and if we hold the + // ThreadList mutex, then the process is going to have a hard time + // restarting the process. + if (stream) { + Debugger &debugger = process_sp->GetTarget().GetDebugger(); + if (debugger.GetTargetList().GetSelectedTarget().get() == + &process_sp->GetTarget()) { + ThreadSP thread_sp = process_sp->GetThreadList().GetSelectedThread(); + + if (!thread_sp || !thread_sp->IsValid()) + return false; + + const bool only_threads_with_stop_reason = true; + const uint32_t start_frame = + thread_sp->GetSelectedFrameIndex(select_most_relevant); + const uint32_t num_frames = 1; + const uint32_t num_frames_with_source = 1; + const bool stop_format = true; + + process_sp->GetStatus(*stream); + process_sp->GetThreadStatus(*stream, only_threads_with_stop_reason, + start_frame, num_frames, + num_frames_with_source, + stop_format); + if (curr_thread_stop_info_sp) { + lldb::addr_t crashing_address; + ValueObjectSP valobj_sp = StopInfo::GetCrashingDereference( + curr_thread_stop_info_sp, &crashing_address); + if (valobj_sp) { + const ValueObject::GetExpressionPathFormat format = + ValueObject::GetExpressionPathFormat:: + eGetExpressionPathFormatHonorPointers; + stream->PutCString("Likely cause: "); + valobj_sp->GetExpressionPath(*stream, format); + stream->Printf(" accessed 0x%" PRIx64 "\n", crashing_address); + } + } + } else { + uint32_t target_idx = debugger.GetTargetList().GetIndexOfTarget( + process_sp->GetTarget().shared_from_this()); + if (target_idx != UINT32_MAX) + stream->Printf("Target %d: (", target_idx); + else + stream->Printf("Target <unknown index>: ("); + process_sp->GetTarget().Dump(stream, eDescriptionLevelBrief); + stream->Printf(") stopped.\n"); + } + } + + // Pop the process IO handler + pop_process_io_handler = true; + } + break; + } + + if (handle_pop && pop_process_io_handler) + process_sp->PopProcessIOHandler(); + + return true; +} + +bool Process::HijackProcessEvents(ListenerSP listener_sp) { + if (listener_sp) { + return HijackBroadcaster(listener_sp, eBroadcastBitStateChanged | + eBroadcastBitInterrupt); + } else + return false; +} + +void Process::RestoreProcessEvents() { RestoreBroadcaster(); } + +StateType Process::GetStateChangedEvents(EventSP &event_sp, + const Timeout<std::micro> &timeout, + ListenerSP hijack_listener_sp) { + Log *log = GetLog(LLDBLog::Process); + LLDB_LOG(log, "timeout = {0}, event_sp)...", timeout); + + ListenerSP listener_sp = hijack_listener_sp; + if (!listener_sp) + listener_sp = GetPrimaryListener(); + + StateType state = eStateInvalid; + if (listener_sp->GetEventForBroadcasterWithType( + this, eBroadcastBitStateChanged | eBroadcastBitInterrupt, event_sp, + timeout)) { + if (event_sp && event_sp->GetType() == eBroadcastBitStateChanged) + state = Process::ProcessEventData::GetStateFromEvent(event_sp.get()); + else + LLDB_LOG(log, "got no event or was interrupted."); + } + + LLDB_LOG(log, "timeout = {0}, event_sp) => {1}", timeout, state); + return state; +} + +Event *Process::PeekAtStateChangedEvents() { + Log *log = GetLog(LLDBLog::Process); + + LLDB_LOGF(log, "Process::%s...", __FUNCTION__); + + Event *event_ptr; + event_ptr = GetPrimaryListener()->PeekAtNextEventForBroadcasterWithType( + this, eBroadcastBitStateChanged); + if (log) { + if (event_ptr) { + LLDB_LOGF(log, "Process::%s (event_ptr) => %s", __FUNCTION__, + StateAsCString(ProcessEventData::GetStateFromEvent(event_ptr))); + } else { + LLDB_LOGF(log, "Process::%s no events found", __FUNCTION__); + } + } + return event_ptr; +} + +StateType +Process::GetStateChangedEventsPrivate(EventSP &event_sp, + const Timeout<std::micro> &timeout) { + Log *log = GetLog(LLDBLog::Process); + LLDB_LOG(log, "timeout = {0}, event_sp)...", timeout); + + StateType state = eStateInvalid; + if (m_private_state_listener_sp->GetEventForBroadcasterWithType( + &m_private_state_broadcaster, + eBroadcastBitStateChanged | eBroadcastBitInterrupt, event_sp, + timeout)) + if (event_sp && event_sp->GetType() == eBroadcastBitStateChanged) + state = Process::ProcessEventData::GetStateFromEvent(event_sp.get()); + + LLDB_LOG(log, "timeout = {0}, event_sp) => {1}", timeout, + state == eStateInvalid ? "TIMEOUT" : StateAsCString(state)); + return state; +} + +bool Process::GetEventsPrivate(EventSP &event_sp, + const Timeout<std::micro> &timeout, + bool control_only) { + Log *log = GetLog(LLDBLog::Process); + LLDB_LOG(log, "timeout = {0}, event_sp)...", timeout); + + if (control_only) + return m_private_state_listener_sp->GetEventForBroadcaster( + &m_private_state_control_broadcaster, event_sp, timeout); + else + return m_private_state_listener_sp->GetEvent(event_sp, timeout); +} + +bool Process::IsRunning() const { + return StateIsRunningState(m_public_state.GetValue()); +} + +int Process::GetExitStatus() { + std::lock_guard<std::mutex> guard(m_exit_status_mutex); + + if (m_public_state.GetValue() == eStateExited) + return m_exit_status; + return -1; +} + +const char *Process::GetExitDescription() { + std::lock_guard<std::mutex> guard(m_exit_status_mutex); + + if (m_public_state.GetValue() == eStateExited && !m_exit_string.empty()) + return m_exit_string.c_str(); + return nullptr; +} + +bool Process::SetExitStatus(int status, llvm::StringRef exit_string) { + // Use a mutex to protect setting the exit status. + std::lock_guard<std::mutex> guard(m_exit_status_mutex); + + Log *log(GetLog(LLDBLog::State | LLDBLog::Process)); + LLDB_LOG(log, "(plugin = {0} status = {1} ({1:x8}), description=\"{2}\")", + GetPluginName(), status, exit_string); + + // We were already in the exited state + if (m_private_state.GetValue() == eStateExited) { + LLDB_LOG( + log, + "(plugin = {0}) ignoring exit status because state was already set " + "to eStateExited", + GetPluginName()); + return false; + } + + m_exit_status = status; + if (!exit_string.empty()) + m_exit_string = exit_string.str(); + else + m_exit_string.clear(); + + // Clear the last natural stop ID since it has a strong reference to this + // process + m_mod_id.SetStopEventForLastNaturalStopID(EventSP()); + + SetPrivateState(eStateExited); + + // Allow subclasses to do some cleanup + DidExit(); + + return true; +} + +bool Process::IsAlive() { + switch (m_private_state.GetValue()) { + case eStateConnected: + case eStateAttaching: + case eStateLaunching: + case eStateStopped: + case eStateRunning: + case eStateStepping: + case eStateCrashed: + case eStateSuspended: + return true; + default: + return false; + } +} + +// This static callback can be used to watch for local child processes on the +// current host. The child process exits, the process will be found in the +// global target list (we want to be completely sure that the +// lldb_private::Process doesn't go away before we can deliver the signal. +bool Process::SetProcessExitStatus( + lldb::pid_t pid, bool exited, + int signo, // Zero for no signal + int exit_status // Exit value of process if signal is zero + ) { + Log *log = GetLog(LLDBLog::Process); + LLDB_LOGF(log, + "Process::SetProcessExitStatus (pid=%" PRIu64 + ", exited=%i, signal=%i, exit_status=%i)\n", + pid, exited, signo, exit_status); + + if (exited) { + TargetSP target_sp(Debugger::FindTargetWithProcessID(pid)); + if (target_sp) { + ProcessSP process_sp(target_sp->GetProcessSP()); + if (process_sp) { + llvm::StringRef signal_str = + process_sp->GetUnixSignals()->GetSignalAsStringRef(signo); + process_sp->SetExitStatus(exit_status, signal_str); + } + } + return true; + } + return false; +} + +bool Process::UpdateThreadList(ThreadList &old_thread_list, + ThreadList &new_thread_list) { + m_thread_plans.ClearThreadCache(); + return DoUpdateThreadList(old_thread_list, new_thread_list); +} + +void Process::UpdateThreadListIfNeeded() { + const uint32_t stop_id = GetStopID(); + if (m_thread_list.GetSize(false) == 0 || + stop_id != m_thread_list.GetStopID()) { + bool clear_unused_threads = true; + const StateType state = GetPrivateState(); + if (StateIsStoppedState(state, true)) { + std::lock_guard<std::recursive_mutex> guard(m_thread_list.GetMutex()); + m_thread_list.SetStopID(stop_id); + + // m_thread_list does have its own mutex, but we need to hold onto the + // mutex between the call to UpdateThreadList(...) and the + // os->UpdateThreadList(...) so it doesn't change on us + ThreadList &old_thread_list = m_thread_list; + ThreadList real_thread_list(*this); + ThreadList new_thread_list(*this); + // Always update the thread list with the protocol specific thread list, + // but only update if "true" is returned + if (UpdateThreadList(m_thread_list_real, real_thread_list)) { + // Don't call into the OperatingSystem to update the thread list if we + // are shutting down, since that may call back into the SBAPI's, + // requiring the API lock which is already held by whoever is shutting + // us down, causing a deadlock. + OperatingSystem *os = GetOperatingSystem(); + if (os && !m_destroy_in_process) { + // Clear any old backing threads where memory threads might have been + // backed by actual threads from the lldb_private::Process subclass + size_t num_old_threads = old_thread_list.GetSize(false); + for (size_t i = 0; i < num_old_threads; ++i) + old_thread_list.GetThreadAtIndex(i, false)->ClearBackingThread(); + // See if the OS plugin reports all threads. If it does, then + // it is safe to clear unseen thread's plans here. Otherwise we + // should preserve them in case they show up again: + clear_unused_threads = GetOSPluginReportsAllThreads(); + + // Turn off dynamic types to ensure we don't run any expressions. + // Objective-C can run an expression to determine if a SBValue is a + // dynamic type or not and we need to avoid this. OperatingSystem + // plug-ins can't run expressions that require running code... + + Target &target = GetTarget(); + const lldb::DynamicValueType saved_prefer_dynamic = + target.GetPreferDynamicValue(); + if (saved_prefer_dynamic != lldb::eNoDynamicValues) + target.SetPreferDynamicValue(lldb::eNoDynamicValues); + + // Now let the OperatingSystem plug-in update the thread list + + os->UpdateThreadList( + old_thread_list, // Old list full of threads created by OS plug-in + real_thread_list, // The actual thread list full of threads + // created by each lldb_private::Process + // subclass + new_thread_list); // The new thread list that we will show to the + // user that gets filled in + + if (saved_prefer_dynamic != lldb::eNoDynamicValues) + target.SetPreferDynamicValue(saved_prefer_dynamic); + } else { + // No OS plug-in, the new thread list is the same as the real thread + // list. + new_thread_list = real_thread_list; + } + + m_thread_list_real.Update(real_thread_list); + m_thread_list.Update(new_thread_list); + m_thread_list.SetStopID(stop_id); + + if (GetLastNaturalStopID() != m_extended_thread_stop_id) { + // Clear any extended threads that we may have accumulated previously + m_extended_thread_list.Clear(); + m_extended_thread_stop_id = GetLastNaturalStopID(); + + m_queue_list.Clear(); + m_queue_list_stop_id = GetLastNaturalStopID(); + } + } + // Now update the plan stack map. + // If we do have an OS plugin, any absent real threads in the + // m_thread_list have already been removed from the ThreadPlanStackMap. + // So any remaining threads are OS Plugin threads, and those we want to + // preserve in case they show up again. + m_thread_plans.Update(m_thread_list, clear_unused_threads); + } + } +} + +ThreadPlanStack *Process::FindThreadPlans(lldb::tid_t tid) { + return m_thread_plans.Find(tid); +} + +bool Process::PruneThreadPlansForTID(lldb::tid_t tid) { + return m_thread_plans.PrunePlansForTID(tid); +} + +void Process::PruneThreadPlans() { + m_thread_plans.Update(GetThreadList(), true, false); +} + +bool Process::DumpThreadPlansForTID(Stream &strm, lldb::tid_t tid, + lldb::DescriptionLevel desc_level, + bool internal, bool condense_trivial, + bool skip_unreported_plans) { + return m_thread_plans.DumpPlansForTID( + strm, tid, desc_level, internal, condense_trivial, skip_unreported_plans); +} +void Process::DumpThreadPlans(Stream &strm, lldb::DescriptionLevel desc_level, + bool internal, bool condense_trivial, + bool skip_unreported_plans) { + m_thread_plans.DumpPlans(strm, desc_level, internal, condense_trivial, + skip_unreported_plans); +} + +void Process::UpdateQueueListIfNeeded() { + if (m_system_runtime_up) { + if (m_queue_list.GetSize() == 0 || + m_queue_list_stop_id != GetLastNaturalStopID()) { + const StateType state = GetPrivateState(); + if (StateIsStoppedState(state, true)) { + m_system_runtime_up->PopulateQueueList(m_queue_list); + m_queue_list_stop_id = GetLastNaturalStopID(); + } + } + } +} + +ThreadSP Process::CreateOSPluginThread(lldb::tid_t tid, lldb::addr_t context) { + OperatingSystem *os = GetOperatingSystem(); + if (os) + return os->CreateThread(tid, context); + return ThreadSP(); +} + +uint32_t Process::GetNextThreadIndexID(uint64_t thread_id) { + return AssignIndexIDToThread(thread_id); +} + +bool Process::HasAssignedIndexIDToThread(uint64_t thread_id) { + return (m_thread_id_to_index_id_map.find(thread_id) != + m_thread_id_to_index_id_map.end()); +} + +uint32_t Process::AssignIndexIDToThread(uint64_t thread_id) { + uint32_t result = 0; + std::map<uint64_t, uint32_t>::iterator iterator = + m_thread_id_to_index_id_map.find(thread_id); + if (iterator == m_thread_id_to_index_id_map.end()) { + result = ++m_thread_index_id; + m_thread_id_to_index_id_map[thread_id] = result; + } else { + result = iterator->second; + } + + return result; +} + +StateType Process::GetState() { + if (CurrentThreadIsPrivateStateThread()) + return m_private_state.GetValue(); + else + return m_public_state.GetValue(); +} + +void Process::SetPublicState(StateType new_state, bool restarted) { + const bool new_state_is_stopped = StateIsStoppedState(new_state, false); + if (new_state_is_stopped) { + // This will only set the time if the public stop time has no value, so + // it is ok to call this multiple times. With a public stop we can't look + // at the stop ID because many private stops might have happened, so we + // can't check for a stop ID of zero. This allows the "statistics" command + // to dump the time it takes to reach somewhere in your code, like a + // breakpoint you set. + GetTarget().GetStatistics().SetFirstPublicStopTime(); + } + + Log *log(GetLog(LLDBLog::State | LLDBLog::Process)); + LLDB_LOGF(log, "(plugin = %s, state = %s, restarted = %i)", + GetPluginName().data(), StateAsCString(new_state), restarted); + const StateType old_state = m_public_state.GetValue(); + m_public_state.SetValue(new_state); + + // On the transition from Run to Stopped, we unlock the writer end of the run + // lock. The lock gets locked in Resume, which is the public API to tell the + // program to run. + if (!StateChangedIsExternallyHijacked()) { + if (new_state == eStateDetached) { + LLDB_LOGF(log, + "(plugin = %s, state = %s) -- unlocking run lock for detach", + GetPluginName().data(), StateAsCString(new_state)); + m_public_run_lock.SetStopped(); + } else { + const bool old_state_is_stopped = StateIsStoppedState(old_state, false); + if ((old_state_is_stopped != new_state_is_stopped)) { + if (new_state_is_stopped && !restarted) { + LLDB_LOGF(log, "(plugin = %s, state = %s) -- unlocking run lock", + GetPluginName().data(), StateAsCString(new_state)); + m_public_run_lock.SetStopped(); + } + } + } + } +} + +Status Process::Resume() { + Log *log(GetLog(LLDBLog::State | LLDBLog::Process)); + LLDB_LOGF(log, "(plugin = %s) -- locking run lock", GetPluginName().data()); + if (!m_public_run_lock.TrySetRunning()) { + Status error("Resume request failed - process still running."); + LLDB_LOGF(log, "(plugin = %s) -- TrySetRunning failed, not resuming.", + GetPluginName().data()); + return error; + } + Status error = PrivateResume(); + if (!error.Success()) { + // Undo running state change + m_public_run_lock.SetStopped(); + } + return error; +} + +Status Process::ResumeSynchronous(Stream *stream) { + Log *log(GetLog(LLDBLog::State | LLDBLog::Process)); + LLDB_LOGF(log, "Process::ResumeSynchronous -- locking run lock"); + if (!m_public_run_lock.TrySetRunning()) { + Status error("Resume request failed - process still running."); + LLDB_LOGF(log, "Process::Resume: -- TrySetRunning failed, not resuming."); + return error; + } + + ListenerSP listener_sp( + Listener::MakeListener(ResumeSynchronousHijackListenerName.data())); + HijackProcessEvents(listener_sp); + + Status error = PrivateResume(); + if (error.Success()) { + StateType state = + WaitForProcessToStop(std::nullopt, nullptr, true, listener_sp, stream, + true /* use_run_lock */, SelectMostRelevantFrame); + const bool must_be_alive = + false; // eStateExited is ok, so this must be false + if (!StateIsStoppedState(state, must_be_alive)) + error.SetErrorStringWithFormat( + "process not in stopped state after synchronous resume: %s", + StateAsCString(state)); + } else { + // Undo running state change + m_public_run_lock.SetStopped(); + } + + // Undo the hijacking of process events... + RestoreProcessEvents(); + + return error; +} + +bool Process::StateChangedIsExternallyHijacked() { + if (IsHijackedForEvent(eBroadcastBitStateChanged)) { + llvm::StringRef hijacking_name = GetHijackingListenerName(); + if (!hijacking_name.starts_with("lldb.internal")) + return true; + } + return false; +} + +bool Process::StateChangedIsHijackedForSynchronousResume() { + if (IsHijackedForEvent(eBroadcastBitStateChanged)) { + llvm::StringRef hijacking_name = GetHijackingListenerName(); + if (hijacking_name == ResumeSynchronousHijackListenerName) + return true; + } + return false; +} + +StateType Process::GetPrivateState() { return m_private_state.GetValue(); } + +void Process::SetPrivateState(StateType new_state) { + // Use m_destructing not m_finalizing here. If we are finalizing a process + // that we haven't started tearing down, we'd like to be able to nicely + // detach if asked, but that requires the event system be live. That will + // not be true for an in-the-middle-of-being-destructed Process, since the + // event system relies on Process::shared_from_this, which may have already + // been destroyed. + if (m_destructing) + return; + + Log *log(GetLog(LLDBLog::State | LLDBLog::Process | LLDBLog::Unwind)); + bool state_changed = false; + + LLDB_LOGF(log, "(plugin = %s, state = %s)", GetPluginName().data(), + StateAsCString(new_state)); + + std::lock_guard<std::recursive_mutex> thread_guard(m_thread_list.GetMutex()); + std::lock_guard<std::recursive_mutex> guard(m_private_state.GetMutex()); + + const StateType old_state = m_private_state.GetValueNoLock(); + state_changed = old_state != new_state; + + const bool old_state_is_stopped = StateIsStoppedState(old_state, false); + const bool new_state_is_stopped = StateIsStoppedState(new_state, false); + if (old_state_is_stopped != new_state_is_stopped) { + if (new_state_is_stopped) + m_private_run_lock.SetStopped(); + else + m_private_run_lock.SetRunning(); + } + + if (state_changed) { + m_private_state.SetValueNoLock(new_state); + EventSP event_sp( + new Event(eBroadcastBitStateChanged, + new ProcessEventData(shared_from_this(), new_state))); + if (StateIsStoppedState(new_state, false)) { + // Note, this currently assumes that all threads in the list stop when + // the process stops. In the future we will want to support a debugging + // model where some threads continue to run while others are stopped. + // When that happens we will either need a way for the thread list to + // identify which threads are stopping or create a special thread list + // containing only threads which actually stopped. + // + // The process plugin is responsible for managing the actual behavior of + // the threads and should have stopped any threads that are going to stop + // before we get here. + m_thread_list.DidStop(); + + if (m_mod_id.BumpStopID() == 0) + GetTarget().GetStatistics().SetFirstPrivateStopTime(); + + if (!m_mod_id.IsLastResumeForUserExpression()) + m_mod_id.SetStopEventForLastNaturalStopID(event_sp); + m_memory_cache.Clear(); + LLDB_LOGF(log, "(plugin = %s, state = %s, stop_id = %u", + GetPluginName().data(), StateAsCString(new_state), + m_mod_id.GetStopID()); + } + + m_private_state_broadcaster.BroadcastEvent(event_sp); + } else { + LLDB_LOGF(log, "(plugin = %s, state = %s) state didn't change. Ignoring...", + GetPluginName().data(), StateAsCString(new_state)); + } +} + +void Process::SetRunningUserExpression(bool on) { + m_mod_id.SetRunningUserExpression(on); +} + +void Process::SetRunningUtilityFunction(bool on) { + m_mod_id.SetRunningUtilityFunction(on); +} + +addr_t Process::GetImageInfoAddress() { return LLDB_INVALID_ADDRESS; } + +const lldb::ABISP &Process::GetABI() { + if (!m_abi_sp) + m_abi_sp = ABI::FindPlugin(shared_from_this(), GetTarget().GetArchitecture()); + return m_abi_sp; +} + +std::vector<LanguageRuntime *> Process::GetLanguageRuntimes() { + std::vector<LanguageRuntime *> language_runtimes; + + if (m_finalizing) + return language_runtimes; + + std::lock_guard<std::recursive_mutex> guard(m_language_runtimes_mutex); + // Before we pass off a copy of the language runtimes, we must make sure that + // our collection is properly populated. It's possible that some of the + // language runtimes were not loaded yet, either because nobody requested it + // yet or the proper condition for loading wasn't yet met (e.g. libc++.so + // hadn't been loaded). + for (const lldb::LanguageType lang_type : Language::GetSupportedLanguages()) { + if (LanguageRuntime *runtime = GetLanguageRuntime(lang_type)) + language_runtimes.emplace_back(runtime); + } + + return language_runtimes; +} + +LanguageRuntime *Process::GetLanguageRuntime(lldb::LanguageType language) { + if (m_finalizing) + return nullptr; + + LanguageRuntime *runtime = nullptr; + + std::lock_guard<std::recursive_mutex> guard(m_language_runtimes_mutex); + LanguageRuntimeCollection::iterator pos; + pos = m_language_runtimes.find(language); + if (pos == m_language_runtimes.end() || !pos->second) { + lldb::LanguageRuntimeSP runtime_sp( + LanguageRuntime::FindPlugin(this, language)); + + m_language_runtimes[language] = runtime_sp; + runtime = runtime_sp.get(); + } else + runtime = pos->second.get(); + + if (runtime) + // It's possible that a language runtime can support multiple LanguageTypes, + // for example, CPPLanguageRuntime will support eLanguageTypeC_plus_plus, + // eLanguageTypeC_plus_plus_03, etc. Because of this, we should get the + // primary language type and make sure that our runtime supports it. + assert(runtime->GetLanguageType() == Language::GetPrimaryLanguage(language)); + + return runtime; +} + +bool Process::IsPossibleDynamicValue(ValueObject &in_value) { + if (m_finalizing) + return false; + + if (in_value.IsDynamic()) + return false; + LanguageType known_type = in_value.GetObjectRuntimeLanguage(); + + if (known_type != eLanguageTypeUnknown && known_type != eLanguageTypeC) { + LanguageRuntime *runtime = GetLanguageRuntime(known_type); + return runtime ? runtime->CouldHaveDynamicValue(in_value) : false; + } + + for (LanguageRuntime *runtime : GetLanguageRuntimes()) { + if (runtime->CouldHaveDynamicValue(in_value)) + return true; + } + + return false; +} + +void Process::SetDynamicCheckers(DynamicCheckerFunctions *dynamic_checkers) { + m_dynamic_checkers_up.reset(dynamic_checkers); +} + +StopPointSiteList<BreakpointSite> &Process::GetBreakpointSiteList() { + return m_breakpoint_site_list; +} + +const StopPointSiteList<BreakpointSite> & +Process::GetBreakpointSiteList() const { + return m_breakpoint_site_list; +} + +void Process::DisableAllBreakpointSites() { + m_breakpoint_site_list.ForEach([this](BreakpointSite *bp_site) -> void { + // bp_site->SetEnabled(true); + DisableBreakpointSite(bp_site); + }); +} + +Status Process::ClearBreakpointSiteByID(lldb::user_id_t break_id) { + Status error(DisableBreakpointSiteByID(break_id)); + + if (error.Success()) + m_breakpoint_site_list.Remove(break_id); + + return error; +} + +Status Process::DisableBreakpointSiteByID(lldb::user_id_t break_id) { + Status error; + BreakpointSiteSP bp_site_sp = m_breakpoint_site_list.FindByID(break_id); + if (bp_site_sp) { + if (bp_site_sp->IsEnabled()) + error = DisableBreakpointSite(bp_site_sp.get()); + } else { + error.SetErrorStringWithFormat("invalid breakpoint site ID: %" PRIu64, + break_id); + } + + return error; +} + +Status Process::EnableBreakpointSiteByID(lldb::user_id_t break_id) { + Status error; + BreakpointSiteSP bp_site_sp = m_breakpoint_site_list.FindByID(break_id); + if (bp_site_sp) { + if (!bp_site_sp->IsEnabled()) + error = EnableBreakpointSite(bp_site_sp.get()); + } else { + error.SetErrorStringWithFormat("invalid breakpoint site ID: %" PRIu64, + break_id); + } + return error; +} + +lldb::break_id_t +Process::CreateBreakpointSite(const BreakpointLocationSP &constituent, + bool use_hardware) { + addr_t load_addr = LLDB_INVALID_ADDRESS; + + bool show_error = true; + switch (GetState()) { + case eStateInvalid: + case eStateUnloaded: + case eStateConnected: + case eStateAttaching: + case eStateLaunching: + case eStateDetached: + case eStateExited: + show_error = false; + break; + + case eStateStopped: + case eStateRunning: + case eStateStepping: + case eStateCrashed: + case eStateSuspended: + show_error = IsAlive(); + break; + } + + // Reset the IsIndirect flag here, in case the location changes from pointing + // to a indirect symbol to a regular symbol. + constituent->SetIsIndirect(false); + + if (constituent->ShouldResolveIndirectFunctions()) { + Symbol *symbol = constituent->GetAddress().CalculateSymbolContextSymbol(); + if (symbol && symbol->IsIndirect()) { + Status error; + Address symbol_address = symbol->GetAddress(); + load_addr = ResolveIndirectFunction(&symbol_address, error); + if (!error.Success() && show_error) { + GetTarget().GetDebugger().GetErrorStream().Printf( + "warning: failed to resolve indirect function at 0x%" PRIx64 + " for breakpoint %i.%i: %s\n", + symbol->GetLoadAddress(&GetTarget()), + constituent->GetBreakpoint().GetID(), constituent->GetID(), + error.AsCString() ? error.AsCString() : "unknown error"); + return LLDB_INVALID_BREAK_ID; + } + Address resolved_address(load_addr); + load_addr = resolved_address.GetOpcodeLoadAddress(&GetTarget()); + constituent->SetIsIndirect(true); + } else + load_addr = constituent->GetAddress().GetOpcodeLoadAddress(&GetTarget()); + } else + load_addr = constituent->GetAddress().GetOpcodeLoadAddress(&GetTarget()); + + if (load_addr != LLDB_INVALID_ADDRESS) { + BreakpointSiteSP bp_site_sp; + + // Look up this breakpoint site. If it exists, then add this new + // constituent, otherwise create a new breakpoint site and add it. + + bp_site_sp = m_breakpoint_site_list.FindByAddress(load_addr); + + if (bp_site_sp) { + bp_site_sp->AddConstituent(constituent); + constituent->SetBreakpointSite(bp_site_sp); + return bp_site_sp->GetID(); + } else { + bp_site_sp.reset( + new BreakpointSite(constituent, load_addr, use_hardware)); + if (bp_site_sp) { + Status error = EnableBreakpointSite(bp_site_sp.get()); + if (error.Success()) { + constituent->SetBreakpointSite(bp_site_sp); + return m_breakpoint_site_list.Add(bp_site_sp); + } else { + if (show_error || use_hardware) { + // Report error for setting breakpoint... + GetTarget().GetDebugger().GetErrorStream().Printf( + "warning: failed to set breakpoint site at 0x%" PRIx64 + " for breakpoint %i.%i: %s\n", + load_addr, constituent->GetBreakpoint().GetID(), + constituent->GetID(), + error.AsCString() ? error.AsCString() : "unknown error"); + } + } + } + } + } + // We failed to enable the breakpoint + return LLDB_INVALID_BREAK_ID; +} + +void Process::RemoveConstituentFromBreakpointSite( + lldb::user_id_t constituent_id, lldb::user_id_t constituent_loc_id, + BreakpointSiteSP &bp_site_sp) { + uint32_t num_constituents = + bp_site_sp->RemoveConstituent(constituent_id, constituent_loc_id); + if (num_constituents == 0) { + // Don't try to disable the site if we don't have a live process anymore. + if (IsAlive()) + DisableBreakpointSite(bp_site_sp.get()); + m_breakpoint_site_list.RemoveByAddress(bp_site_sp->GetLoadAddress()); + } +} + +size_t Process::RemoveBreakpointOpcodesFromBuffer(addr_t bp_addr, size_t size, + uint8_t *buf) const { + size_t bytes_removed = 0; + StopPointSiteList<BreakpointSite> bp_sites_in_range; + + if (m_breakpoint_site_list.FindInRange(bp_addr, bp_addr + size, + bp_sites_in_range)) { + bp_sites_in_range.ForEach([bp_addr, size, + buf](BreakpointSite *bp_site) -> void { + if (bp_site->GetType() == BreakpointSite::eSoftware) { + addr_t intersect_addr; + size_t intersect_size; + size_t opcode_offset; + if (bp_site->IntersectsRange(bp_addr, size, &intersect_addr, + &intersect_size, &opcode_offset)) { + assert(bp_addr <= intersect_addr && intersect_addr < bp_addr + size); + assert(bp_addr < intersect_addr + intersect_size && + intersect_addr + intersect_size <= bp_addr + size); + assert(opcode_offset + intersect_size <= bp_site->GetByteSize()); + size_t buf_offset = intersect_addr - bp_addr; + ::memcpy(buf + buf_offset, + bp_site->GetSavedOpcodeBytes() + opcode_offset, + intersect_size); + } + } + }); + } + return bytes_removed; +} + +size_t Process::GetSoftwareBreakpointTrapOpcode(BreakpointSite *bp_site) { + PlatformSP platform_sp(GetTarget().GetPlatform()); + if (platform_sp) + return platform_sp->GetSoftwareBreakpointTrapOpcode(GetTarget(), bp_site); + return 0; +} + +Status Process::EnableSoftwareBreakpoint(BreakpointSite *bp_site) { + Status error; + assert(bp_site != nullptr); + Log *log = GetLog(LLDBLog::Breakpoints); + const addr_t bp_addr = bp_site->GetLoadAddress(); + LLDB_LOGF( + log, "Process::EnableSoftwareBreakpoint (site_id = %d) addr = 0x%" PRIx64, + bp_site->GetID(), (uint64_t)bp_addr); + if (bp_site->IsEnabled()) { + LLDB_LOGF( + log, + "Process::EnableSoftwareBreakpoint (site_id = %d) addr = 0x%" PRIx64 + " -- already enabled", + bp_site->GetID(), (uint64_t)bp_addr); + return error; + } + + if (bp_addr == LLDB_INVALID_ADDRESS) { + error.SetErrorString("BreakpointSite contains an invalid load address."); + return error; + } + // Ask the lldb::Process subclass to fill in the correct software breakpoint + // trap for the breakpoint site + const size_t bp_opcode_size = GetSoftwareBreakpointTrapOpcode(bp_site); + + if (bp_opcode_size == 0) { + error.SetErrorStringWithFormat("Process::GetSoftwareBreakpointTrapOpcode() " + "returned zero, unable to get breakpoint " + "trap for address 0x%" PRIx64, + bp_addr); + } else { + const uint8_t *const bp_opcode_bytes = bp_site->GetTrapOpcodeBytes(); + + if (bp_opcode_bytes == nullptr) { + error.SetErrorString( + "BreakpointSite doesn't contain a valid breakpoint trap opcode."); + return error; + } + + // Save the original opcode by reading it + if (DoReadMemory(bp_addr, bp_site->GetSavedOpcodeBytes(), bp_opcode_size, + error) == bp_opcode_size) { + // Write a software breakpoint in place of the original opcode + if (DoWriteMemory(bp_addr, bp_opcode_bytes, bp_opcode_size, error) == + bp_opcode_size) { + uint8_t verify_bp_opcode_bytes[64]; + if (DoReadMemory(bp_addr, verify_bp_opcode_bytes, bp_opcode_size, + error) == bp_opcode_size) { + if (::memcmp(bp_opcode_bytes, verify_bp_opcode_bytes, + bp_opcode_size) == 0) { + bp_site->SetEnabled(true); + bp_site->SetType(BreakpointSite::eSoftware); + LLDB_LOGF(log, + "Process::EnableSoftwareBreakpoint (site_id = %d) " + "addr = 0x%" PRIx64 " -- SUCCESS", + bp_site->GetID(), (uint64_t)bp_addr); + } else + error.SetErrorString( + "failed to verify the breakpoint trap in memory."); + } else + error.SetErrorString( + "Unable to read memory to verify breakpoint trap."); + } else + error.SetErrorString("Unable to write breakpoint trap to memory."); + } else + error.SetErrorString("Unable to read memory at breakpoint address."); + } + if (log && error.Fail()) + LLDB_LOGF( + log, + "Process::EnableSoftwareBreakpoint (site_id = %d) addr = 0x%" PRIx64 + " -- FAILED: %s", + bp_site->GetID(), (uint64_t)bp_addr, error.AsCString()); + return error; +} + +Status Process::DisableSoftwareBreakpoint(BreakpointSite *bp_site) { + Status error; + assert(bp_site != nullptr); + Log *log = GetLog(LLDBLog::Breakpoints); + addr_t bp_addr = bp_site->GetLoadAddress(); + lldb::user_id_t breakID = bp_site->GetID(); + LLDB_LOGF(log, + "Process::DisableSoftwareBreakpoint (breakID = %" PRIu64 + ") addr = 0x%" PRIx64, + breakID, (uint64_t)bp_addr); + + if (bp_site->IsHardware()) { + error.SetErrorString("Breakpoint site is a hardware breakpoint."); + } else if (bp_site->IsEnabled()) { + const size_t break_op_size = bp_site->GetByteSize(); + const uint8_t *const break_op = bp_site->GetTrapOpcodeBytes(); + if (break_op_size > 0) { + // Clear a software breakpoint instruction + uint8_t curr_break_op[8]; + assert(break_op_size <= sizeof(curr_break_op)); + bool break_op_found = false; + + // Read the breakpoint opcode + if (DoReadMemory(bp_addr, curr_break_op, break_op_size, error) == + break_op_size) { + bool verify = false; + // Make sure the breakpoint opcode exists at this address + if (::memcmp(curr_break_op, break_op, break_op_size) == 0) { + break_op_found = true; + // We found a valid breakpoint opcode at this address, now restore + // the saved opcode. + if (DoWriteMemory(bp_addr, bp_site->GetSavedOpcodeBytes(), + break_op_size, error) == break_op_size) { + verify = true; + } else + error.SetErrorString( + "Memory write failed when restoring original opcode."); + } else { + error.SetErrorString( + "Original breakpoint trap is no longer in memory."); + // Set verify to true and so we can check if the original opcode has + // already been restored + verify = true; + } + + if (verify) { + uint8_t verify_opcode[8]; + assert(break_op_size < sizeof(verify_opcode)); + // Verify that our original opcode made it back to the inferior + if (DoReadMemory(bp_addr, verify_opcode, break_op_size, error) == + break_op_size) { + // compare the memory we just read with the original opcode + if (::memcmp(bp_site->GetSavedOpcodeBytes(), verify_opcode, + break_op_size) == 0) { + // SUCCESS + bp_site->SetEnabled(false); + LLDB_LOGF(log, + "Process::DisableSoftwareBreakpoint (site_id = %d) " + "addr = 0x%" PRIx64 " -- SUCCESS", + bp_site->GetID(), (uint64_t)bp_addr); + return error; + } else { + if (break_op_found) + error.SetErrorString("Failed to restore original opcode."); + } + } else + error.SetErrorString("Failed to read memory to verify that " + "breakpoint trap was restored."); + } + } else + error.SetErrorString( + "Unable to read memory that should contain the breakpoint trap."); + } + } else { + LLDB_LOGF( + log, + "Process::DisableSoftwareBreakpoint (site_id = %d) addr = 0x%" PRIx64 + " -- already disabled", + bp_site->GetID(), (uint64_t)bp_addr); + return error; + } + + LLDB_LOGF( + log, + "Process::DisableSoftwareBreakpoint (site_id = %d) addr = 0x%" PRIx64 + " -- FAILED: %s", + bp_site->GetID(), (uint64_t)bp_addr, error.AsCString()); + return error; +} + +// Uncomment to verify memory caching works after making changes to caching +// code +//#define VERIFY_MEMORY_READS + +size_t Process::ReadMemory(addr_t addr, void *buf, size_t size, Status &error) { + if (ABISP abi_sp = GetABI()) + addr = abi_sp->FixAnyAddress(addr); + + error.Clear(); + if (!GetDisableMemoryCache()) { +#if defined(VERIFY_MEMORY_READS) + // Memory caching is enabled, with debug verification + + if (buf && size) { + // Uncomment the line below to make sure memory caching is working. + // I ran this through the test suite and got no assertions, so I am + // pretty confident this is working well. If any changes are made to + // memory caching, uncomment the line below and test your changes! + + // Verify all memory reads by using the cache first, then redundantly + // reading the same memory from the inferior and comparing to make sure + // everything is exactly the same. + std::string verify_buf(size, '\0'); + assert(verify_buf.size() == size); + const size_t cache_bytes_read = + m_memory_cache.Read(this, addr, buf, size, error); + Status verify_error; + const size_t verify_bytes_read = + ReadMemoryFromInferior(addr, const_cast<char *>(verify_buf.data()), + verify_buf.size(), verify_error); + assert(cache_bytes_read == verify_bytes_read); + assert(memcmp(buf, verify_buf.data(), verify_buf.size()) == 0); + assert(verify_error.Success() == error.Success()); + return cache_bytes_read; + } + return 0; +#else // !defined(VERIFY_MEMORY_READS) + // Memory caching is enabled, without debug verification + + return m_memory_cache.Read(addr, buf, size, error); +#endif // defined (VERIFY_MEMORY_READS) + } else { + // Memory caching is disabled + + return ReadMemoryFromInferior(addr, buf, size, error); + } +} + +void Process::DoFindInMemory(lldb::addr_t start_addr, lldb::addr_t end_addr, + const uint8_t *buf, size_t size, + AddressRanges &matches, size_t alignment, + size_t max_matches) { + // Inputs are already validated in FindInMemory() functions. + assert(buf != nullptr); + assert(size > 0); + assert(alignment > 0); + assert(max_matches > 0); + assert(start_addr != LLDB_INVALID_ADDRESS); + assert(end_addr != LLDB_INVALID_ADDRESS); + assert(start_addr < end_addr); + + lldb::addr_t start = llvm::alignTo(start_addr, alignment); + while (matches.size() < max_matches && (start + size) < end_addr) { + const lldb::addr_t found_addr = FindInMemory(start, end_addr, buf, size); + if (found_addr == LLDB_INVALID_ADDRESS) + break; + + if (found_addr % alignment) { + // We need to check the alignment because the FindInMemory uses a special + // algorithm to efficiently search mememory but doesn't support alignment. + start = llvm::alignTo(start + 1, alignment); + continue; + } + + matches.emplace_back(found_addr, size); + start = found_addr + alignment; + } +} + +AddressRanges Process::FindRangesInMemory(const uint8_t *buf, uint64_t size, + const AddressRanges &ranges, + size_t alignment, size_t max_matches, + Status &error) { + AddressRanges matches; + if (buf == nullptr) { + error.SetErrorString("buffer is null"); + return matches; + } + if (size == 0) { + error.SetErrorString("buffer size is zero"); + return matches; + } + if (ranges.empty()) { + error.SetErrorString("empty ranges"); + return matches; + } + if (alignment == 0) { + error.SetErrorString("alignment must be greater than zero"); + return matches; + } + if (max_matches == 0) { + error.SetErrorString("max_matches must be greater than zero"); + return matches; + } + + int resolved_ranges = 0; + Target &target = GetTarget(); + for (size_t i = 0; i < ranges.size(); ++i) { + if (matches.size() >= max_matches) + break; + const AddressRange &range = ranges[i]; + if (range.IsValid() == false) + continue; + + const lldb::addr_t start_addr = + range.GetBaseAddress().GetLoadAddress(&target); + if (start_addr == LLDB_INVALID_ADDRESS) + continue; + + ++resolved_ranges; + const lldb::addr_t end_addr = start_addr + range.GetByteSize(); + DoFindInMemory(start_addr, end_addr, buf, size, matches, alignment, + max_matches); + } + + if (resolved_ranges > 0) + error.Clear(); + else + error.SetErrorString("unable to resolve any ranges"); + + return matches; +} + +lldb::addr_t Process::FindInMemory(const uint8_t *buf, uint64_t size, + const AddressRange &range, size_t alignment, + Status &error) { + if (buf == nullptr) { + error.SetErrorString("buffer is null"); + return LLDB_INVALID_ADDRESS; + } + if (size == 0) { + error.SetErrorString("buffer size is zero"); + return LLDB_INVALID_ADDRESS; + } + if (!range.IsValid()) { + error.SetErrorString("range is invalid"); + return LLDB_INVALID_ADDRESS; + } + if (alignment == 0) { + error.SetErrorString("alignment must be greater than zero"); + return LLDB_INVALID_ADDRESS; + } + + Target &target = GetTarget(); + const lldb::addr_t start_addr = + range.GetBaseAddress().GetLoadAddress(&target); + if (start_addr == LLDB_INVALID_ADDRESS) { + error.SetErrorString("range load address is invalid"); + return LLDB_INVALID_ADDRESS; + } + const lldb::addr_t end_addr = start_addr + range.GetByteSize(); + + AddressRanges matches; + DoFindInMemory(start_addr, end_addr, buf, size, matches, alignment, 1); + if (matches.empty()) + return LLDB_INVALID_ADDRESS; + + error.Clear(); + return matches[0].GetBaseAddress().GetLoadAddress(&target); +} + +size_t Process::ReadCStringFromMemory(addr_t addr, std::string &out_str, + Status &error) { + char buf[256]; + out_str.clear(); + addr_t curr_addr = addr; + while (true) { + size_t length = ReadCStringFromMemory(curr_addr, buf, sizeof(buf), error); + if (length == 0) + break; + out_str.append(buf, length); + // If we got "length - 1" bytes, we didn't get the whole C string, we need + // to read some more characters + if (length == sizeof(buf) - 1) + curr_addr += length; + else + break; + } + return out_str.size(); +} + +// Deprecated in favor of ReadStringFromMemory which has wchar support and +// correct code to find null terminators. +size_t Process::ReadCStringFromMemory(addr_t addr, char *dst, + size_t dst_max_len, + Status &result_error) { + size_t total_cstr_len = 0; + if (dst && dst_max_len) { + result_error.Clear(); + // NULL out everything just to be safe + memset(dst, 0, dst_max_len); + Status error; + addr_t curr_addr = addr; + const size_t cache_line_size = m_memory_cache.GetMemoryCacheLineSize(); + size_t bytes_left = dst_max_len - 1; + char *curr_dst = dst; + + while (bytes_left > 0) { + addr_t cache_line_bytes_left = + cache_line_size - (curr_addr % cache_line_size); + addr_t bytes_to_read = + std::min<addr_t>(bytes_left, cache_line_bytes_left); + size_t bytes_read = ReadMemory(curr_addr, curr_dst, bytes_to_read, error); + + if (bytes_read == 0) { + result_error = error; + dst[total_cstr_len] = '\0'; + break; + } + const size_t len = strlen(curr_dst); + + total_cstr_len += len; + + if (len < bytes_to_read) + break; + + curr_dst += bytes_read; + curr_addr += bytes_read; + bytes_left -= bytes_read; + } + } else { + if (dst == nullptr) + result_error.SetErrorString("invalid arguments"); + else + result_error.Clear(); + } + return total_cstr_len; +} + +size_t Process::ReadMemoryFromInferior(addr_t addr, void *buf, size_t size, + Status &error) { + LLDB_SCOPED_TIMER(); + + if (ABISP abi_sp = GetABI()) + addr = abi_sp->FixAnyAddress(addr); + + if (buf == nullptr || size == 0) + return 0; + + size_t bytes_read = 0; + uint8_t *bytes = (uint8_t *)buf; + + while (bytes_read < size) { + const size_t curr_size = size - bytes_read; + const size_t curr_bytes_read = + DoReadMemory(addr + bytes_read, bytes + bytes_read, curr_size, error); + bytes_read += curr_bytes_read; + if (curr_bytes_read == curr_size || curr_bytes_read == 0) + break; + } + + // Replace any software breakpoint opcodes that fall into this range back + // into "buf" before we return + if (bytes_read > 0) + RemoveBreakpointOpcodesFromBuffer(addr, bytes_read, (uint8_t *)buf); + return bytes_read; +} + +uint64_t Process::ReadUnsignedIntegerFromMemory(lldb::addr_t vm_addr, + size_t integer_byte_size, + uint64_t fail_value, + Status &error) { + Scalar scalar; + if (ReadScalarIntegerFromMemory(vm_addr, integer_byte_size, false, scalar, + error)) + return scalar.ULongLong(fail_value); + return fail_value; +} + +int64_t Process::ReadSignedIntegerFromMemory(lldb::addr_t vm_addr, + size_t integer_byte_size, + int64_t fail_value, + Status &error) { + Scalar scalar; + if (ReadScalarIntegerFromMemory(vm_addr, integer_byte_size, true, scalar, + error)) + return scalar.SLongLong(fail_value); + return fail_value; +} + +addr_t Process::ReadPointerFromMemory(lldb::addr_t vm_addr, Status &error) { + Scalar scalar; + if (ReadScalarIntegerFromMemory(vm_addr, GetAddressByteSize(), false, scalar, + error)) + return scalar.ULongLong(LLDB_INVALID_ADDRESS); + return LLDB_INVALID_ADDRESS; +} + +bool Process::WritePointerToMemory(lldb::addr_t vm_addr, lldb::addr_t ptr_value, + Status &error) { + Scalar scalar; + const uint32_t addr_byte_size = GetAddressByteSize(); + if (addr_byte_size <= 4) + scalar = (uint32_t)ptr_value; + else + scalar = ptr_value; + return WriteScalarToMemory(vm_addr, scalar, addr_byte_size, error) == + addr_byte_size; +} + +size_t Process::WriteMemoryPrivate(addr_t addr, const void *buf, size_t size, + Status &error) { + size_t bytes_written = 0; + const uint8_t *bytes = (const uint8_t *)buf; + + while (bytes_written < size) { + const size_t curr_size = size - bytes_written; + const size_t curr_bytes_written = DoWriteMemory( + addr + bytes_written, bytes + bytes_written, curr_size, error); + bytes_written += curr_bytes_written; + if (curr_bytes_written == curr_size || curr_bytes_written == 0) + break; + } + return bytes_written; +} + +size_t Process::WriteMemory(addr_t addr, const void *buf, size_t size, + Status &error) { + if (ABISP abi_sp = GetABI()) + addr = abi_sp->FixAnyAddress(addr); + +#if defined(ENABLE_MEMORY_CACHING) + m_memory_cache.Flush(addr, size); +#endif + + if (buf == nullptr || size == 0) + return 0; + + m_mod_id.BumpMemoryID(); + + // We need to write any data that would go where any current software traps + // (enabled software breakpoints) any software traps (breakpoints) that we + // may have placed in our tasks memory. + + StopPointSiteList<BreakpointSite> bp_sites_in_range; + if (!m_breakpoint_site_list.FindInRange(addr, addr + size, bp_sites_in_range)) + return WriteMemoryPrivate(addr, buf, size, error); + + // No breakpoint sites overlap + if (bp_sites_in_range.IsEmpty()) + return WriteMemoryPrivate(addr, buf, size, error); + + const uint8_t *ubuf = (const uint8_t *)buf; + uint64_t bytes_written = 0; + + bp_sites_in_range.ForEach([this, addr, size, &bytes_written, &ubuf, + &error](BreakpointSite *bp) -> void { + if (error.Fail()) + return; + + if (bp->GetType() != BreakpointSite::eSoftware) + return; + + addr_t intersect_addr; + size_t intersect_size; + size_t opcode_offset; + const bool intersects = bp->IntersectsRange( + addr, size, &intersect_addr, &intersect_size, &opcode_offset); + UNUSED_IF_ASSERT_DISABLED(intersects); + assert(intersects); + assert(addr <= intersect_addr && intersect_addr < addr + size); + assert(addr < intersect_addr + intersect_size && + intersect_addr + intersect_size <= addr + size); + assert(opcode_offset + intersect_size <= bp->GetByteSize()); + + // Check for bytes before this breakpoint + const addr_t curr_addr = addr + bytes_written; + if (intersect_addr > curr_addr) { + // There are some bytes before this breakpoint that we need to just + // write to memory + size_t curr_size = intersect_addr - curr_addr; + size_t curr_bytes_written = + WriteMemoryPrivate(curr_addr, ubuf + bytes_written, curr_size, error); + bytes_written += curr_bytes_written; + if (curr_bytes_written != curr_size) { + // We weren't able to write all of the requested bytes, we are + // done looping and will return the number of bytes that we have + // written so far. + if (error.Success()) + error.SetErrorToGenericError(); + } + } + // Now write any bytes that would cover up any software breakpoints + // directly into the breakpoint opcode buffer + ::memcpy(bp->GetSavedOpcodeBytes() + opcode_offset, ubuf + bytes_written, + intersect_size); + bytes_written += intersect_size; + }); + + // Write any remaining bytes after the last breakpoint if we have any left + if (bytes_written < size) + bytes_written += + WriteMemoryPrivate(addr + bytes_written, ubuf + bytes_written, + size - bytes_written, error); + + return bytes_written; +} + +size_t Process::WriteScalarToMemory(addr_t addr, const Scalar &scalar, + size_t byte_size, Status &error) { + if (byte_size == UINT32_MAX) + byte_size = scalar.GetByteSize(); + if (byte_size > 0) { + uint8_t buf[32]; + const size_t mem_size = + scalar.GetAsMemoryData(buf, byte_size, GetByteOrder(), error); + if (mem_size > 0) + return WriteMemory(addr, buf, mem_size, error); + else + error.SetErrorString("failed to get scalar as memory data"); + } else { + error.SetErrorString("invalid scalar value"); + } + return 0; +} + +size_t Process::ReadScalarIntegerFromMemory(addr_t addr, uint32_t byte_size, + bool is_signed, Scalar &scalar, + Status &error) { + uint64_t uval = 0; + if (byte_size == 0) { + error.SetErrorString("byte size is zero"); + } else if (byte_size & (byte_size - 1)) { + error.SetErrorStringWithFormat("byte size %u is not a power of 2", + byte_size); + } else if (byte_size <= sizeof(uval)) { + const size_t bytes_read = ReadMemory(addr, &uval, byte_size, error); + if (bytes_read == byte_size) { + DataExtractor data(&uval, sizeof(uval), GetByteOrder(), + GetAddressByteSize()); + lldb::offset_t offset = 0; + if (byte_size <= 4) + scalar = data.GetMaxU32(&offset, byte_size); + else + scalar = data.GetMaxU64(&offset, byte_size); + if (is_signed) + scalar.SignExtend(byte_size * 8); + return bytes_read; + } + } else { + error.SetErrorStringWithFormat( + "byte size of %u is too large for integer scalar type", byte_size); + } + return 0; +} + +Status Process::WriteObjectFile(std::vector<ObjectFile::LoadableData> entries) { + Status error; + for (const auto &Entry : entries) { + WriteMemory(Entry.Dest, Entry.Contents.data(), Entry.Contents.size(), + error); + if (!error.Success()) + break; + } + return error; +} + +#define USE_ALLOCATE_MEMORY_CACHE 1 +addr_t Process::AllocateMemory(size_t size, uint32_t permissions, + Status &error) { + if (GetPrivateState() != eStateStopped) { + error.SetErrorToGenericError(); + return LLDB_INVALID_ADDRESS; + } + +#if defined(USE_ALLOCATE_MEMORY_CACHE) + return m_allocated_memory_cache.AllocateMemory(size, permissions, error); +#else + addr_t allocated_addr = DoAllocateMemory(size, permissions, error); + Log *log = GetLog(LLDBLog::Process); + LLDB_LOGF(log, + "Process::AllocateMemory(size=%" PRIu64 + ", permissions=%s) => 0x%16.16" PRIx64 + " (m_stop_id = %u m_memory_id = %u)", + (uint64_t)size, GetPermissionsAsCString(permissions), + (uint64_t)allocated_addr, m_mod_id.GetStopID(), + m_mod_id.GetMemoryID()); + return allocated_addr; +#endif +} + +addr_t Process::CallocateMemory(size_t size, uint32_t permissions, + Status &error) { + addr_t return_addr = AllocateMemory(size, permissions, error); + if (error.Success()) { + std::string buffer(size, 0); + WriteMemory(return_addr, buffer.c_str(), size, error); + } + return return_addr; +} + +bool Process::CanJIT() { + if (m_can_jit == eCanJITDontKnow) { + Log *log = GetLog(LLDBLog::Process); + Status err; + + uint64_t allocated_memory = AllocateMemory( + 8, ePermissionsReadable | ePermissionsWritable | ePermissionsExecutable, + err); + + if (err.Success()) { + m_can_jit = eCanJITYes; + LLDB_LOGF(log, + "Process::%s pid %" PRIu64 + " allocation test passed, CanJIT () is true", + __FUNCTION__, GetID()); + } else { + m_can_jit = eCanJITNo; + LLDB_LOGF(log, + "Process::%s pid %" PRIu64 + " allocation test failed, CanJIT () is false: %s", + __FUNCTION__, GetID(), err.AsCString()); + } + + DeallocateMemory(allocated_memory); + } + + return m_can_jit == eCanJITYes; +} + +void Process::SetCanJIT(bool can_jit) { + m_can_jit = (can_jit ? eCanJITYes : eCanJITNo); +} + +void Process::SetCanRunCode(bool can_run_code) { + SetCanJIT(can_run_code); + m_can_interpret_function_calls = can_run_code; +} + +Status Process::DeallocateMemory(addr_t ptr) { + Status error; +#if defined(USE_ALLOCATE_MEMORY_CACHE) + if (!m_allocated_memory_cache.DeallocateMemory(ptr)) { + error.SetErrorStringWithFormat( + "deallocation of memory at 0x%" PRIx64 " failed.", (uint64_t)ptr); + } +#else + error = DoDeallocateMemory(ptr); + + Log *log = GetLog(LLDBLog::Process); + LLDB_LOGF(log, + "Process::DeallocateMemory(addr=0x%16.16" PRIx64 + ") => err = %s (m_stop_id = %u, m_memory_id = %u)", + ptr, error.AsCString("SUCCESS"), m_mod_id.GetStopID(), + m_mod_id.GetMemoryID()); +#endif + return error; +} + +bool Process::GetWatchpointReportedAfter() { + if (std::optional<bool> subclass_override = DoGetWatchpointReportedAfter()) + return *subclass_override; + + bool reported_after = true; + const ArchSpec &arch = GetTarget().GetArchitecture(); + if (!arch.IsValid()) + return reported_after; + llvm::Triple triple = arch.GetTriple(); + + if (triple.isMIPS() || triple.isPPC64() || triple.isRISCV() || + triple.isAArch64() || triple.isArmMClass() || triple.isARM()) + reported_after = false; + + return reported_after; +} + +ModuleSP Process::ReadModuleFromMemory(const FileSpec &file_spec, + lldb::addr_t header_addr, + size_t size_to_read) { + Log *log = GetLog(LLDBLog::Host); + if (log) { + LLDB_LOGF(log, + "Process::ReadModuleFromMemory reading %s binary from memory", + file_spec.GetPath().c_str()); + } + ModuleSP module_sp(new Module(file_spec, ArchSpec())); + if (module_sp) { + Status error; + std::unique_ptr<Progress> progress_up; + // Reading an ObjectFile from a local corefile is very fast, + // only print a progress update if we're reading from a + // live session which might go over gdb remote serial protocol. + if (IsLiveDebugSession()) + progress_up = std::make_unique<Progress>( + "Reading binary from memory", file_spec.GetFilename().GetString()); + + ObjectFile *objfile = module_sp->GetMemoryObjectFile( + shared_from_this(), header_addr, error, size_to_read); + if (objfile) + return module_sp; + } + return ModuleSP(); +} + +bool Process::GetLoadAddressPermissions(lldb::addr_t load_addr, + uint32_t &permissions) { + MemoryRegionInfo range_info; + permissions = 0; + Status error(GetMemoryRegionInfo(load_addr, range_info)); + if (!error.Success()) + return false; + if (range_info.GetReadable() == MemoryRegionInfo::eDontKnow || + range_info.GetWritable() == MemoryRegionInfo::eDontKnow || + range_info.GetExecutable() == MemoryRegionInfo::eDontKnow) { + return false; + } + permissions = range_info.GetLLDBPermissions(); + return true; +} + +Status Process::EnableWatchpoint(WatchpointSP wp_sp, bool notify) { + Status error; + error.SetErrorString("watchpoints are not supported"); + return error; +} + +Status Process::DisableWatchpoint(WatchpointSP wp_sp, bool notify) { + Status error; + error.SetErrorString("watchpoints are not supported"); + return error; +} + +StateType +Process::WaitForProcessStopPrivate(EventSP &event_sp, + const Timeout<std::micro> &timeout) { + StateType state; + + while (true) { + event_sp.reset(); + state = GetStateChangedEventsPrivate(event_sp, timeout); + + if (StateIsStoppedState(state, false)) + break; + + // If state is invalid, then we timed out + if (state == eStateInvalid) + break; + + if (event_sp) + HandlePrivateEvent(event_sp); + } + return state; +} + +void Process::LoadOperatingSystemPlugin(bool flush) { + std::lock_guard<std::recursive_mutex> guard(m_thread_mutex); + if (flush) + m_thread_list.Clear(); + m_os_up.reset(OperatingSystem::FindPlugin(this, nullptr)); + if (flush) + Flush(); +} + +Status Process::Launch(ProcessLaunchInfo &launch_info) { + StateType state_after_launch = eStateInvalid; + EventSP first_stop_event_sp; + Status status = + LaunchPrivate(launch_info, state_after_launch, first_stop_event_sp); + if (status.Fail()) + return status; + + if (state_after_launch != eStateStopped && + state_after_launch != eStateCrashed) + return Status(); + + // Note, the stop event was consumed above, but not handled. This + // was done to give DidLaunch a chance to run. The target is either + // stopped or crashed. Directly set the state. This is done to + // prevent a stop message with a bunch of spurious output on thread + // status, as well as not pop a ProcessIOHandler. + SetPublicState(state_after_launch, false); + + if (PrivateStateThreadIsValid()) + ResumePrivateStateThread(); + else + StartPrivateStateThread(); + + // Target was stopped at entry as was intended. Need to notify the + // listeners about it. + if (launch_info.GetFlags().Test(eLaunchFlagStopAtEntry)) + HandlePrivateEvent(first_stop_event_sp); + + return Status(); +} + +Status Process::LaunchPrivate(ProcessLaunchInfo &launch_info, StateType &state, + EventSP &event_sp) { + Status error; + m_abi_sp.reset(); + m_dyld_up.reset(); + m_jit_loaders_up.reset(); + m_system_runtime_up.reset(); + m_os_up.reset(); + + { + std::lock_guard<std::mutex> guard(m_process_input_reader_mutex); + m_process_input_reader.reset(); + } + + Module *exe_module = GetTarget().GetExecutableModulePointer(); + + // The "remote executable path" is hooked up to the local Executable + // module. But we should be able to debug a remote process even if the + // executable module only exists on the remote. However, there needs to + // be a way to express this path, without actually having a module. + // The way to do that is to set the ExecutableFile in the LaunchInfo. + // Figure that out here: + + FileSpec exe_spec_to_use; + if (!exe_module) { + if (!launch_info.GetExecutableFile() && !launch_info.IsScriptedProcess()) { + error.SetErrorString("executable module does not exist"); + return error; + } + exe_spec_to_use = launch_info.GetExecutableFile(); + } else + exe_spec_to_use = exe_module->GetFileSpec(); + + if (exe_module && FileSystem::Instance().Exists(exe_module->GetFileSpec())) { + // Install anything that might need to be installed prior to launching. + // For host systems, this will do nothing, but if we are connected to a + // remote platform it will install any needed binaries + error = GetTarget().Install(&launch_info); + if (error.Fail()) + return error; + } + + // Listen and queue events that are broadcasted during the process launch. + ListenerSP listener_sp(Listener::MakeListener("LaunchEventHijack")); + HijackProcessEvents(listener_sp); + auto on_exit = llvm::make_scope_exit([this]() { RestoreProcessEvents(); }); + + if (PrivateStateThreadIsValid()) + PausePrivateStateThread(); + + error = WillLaunch(exe_module); + if (error.Fail()) { + std::string local_exec_file_path = exe_spec_to_use.GetPath(); + return Status("file doesn't exist: '%s'", local_exec_file_path.c_str()); + } + + const bool restarted = false; + SetPublicState(eStateLaunching, restarted); + m_should_detach = false; + + if (m_public_run_lock.TrySetRunning()) { + // Now launch using these arguments. + error = DoLaunch(exe_module, launch_info); + } else { + // This shouldn't happen + error.SetErrorString("failed to acquire process run lock"); + } + + if (error.Fail()) { + if (GetID() != LLDB_INVALID_PROCESS_ID) { + SetID(LLDB_INVALID_PROCESS_ID); + const char *error_string = error.AsCString(); + if (error_string == nullptr) + error_string = "launch failed"; + SetExitStatus(-1, error_string); + } + return error; + } + + // Now wait for the process to launch and return control to us, and then + // call DidLaunch: + state = WaitForProcessStopPrivate(event_sp, seconds(10)); + + if (state == eStateInvalid || !event_sp) { + // We were able to launch the process, but we failed to catch the + // initial stop. + error.SetErrorString("failed to catch stop after launch"); + SetExitStatus(0, error.AsCString()); + Destroy(false); + return error; + } + + if (state == eStateExited) { + // We exited while trying to launch somehow. Don't call DidLaunch + // as that's not likely to work, and return an invalid pid. + HandlePrivateEvent(event_sp); + return Status(); + } + + if (state == eStateStopped || state == eStateCrashed) { + DidLaunch(); + + // Now that we know the process type, update its signal responses from the + // ones stored in the Target: + if (m_unix_signals_sp) { + StreamSP warning_strm = GetTarget().GetDebugger().GetAsyncErrorStream(); + GetTarget().UpdateSignalsFromDummy(m_unix_signals_sp, warning_strm); + } + + DynamicLoader *dyld = GetDynamicLoader(); + if (dyld) + dyld->DidLaunch(); + + GetJITLoaders().DidLaunch(); + + SystemRuntime *system_runtime = GetSystemRuntime(); + if (system_runtime) + system_runtime->DidLaunch(); + + if (!m_os_up) + LoadOperatingSystemPlugin(false); + + // We successfully launched the process and stopped, now it the + // right time to set up signal filters before resuming. + UpdateAutomaticSignalFiltering(); + return Status(); + } + + return Status("Unexpected process state after the launch: %s, expected %s, " + "%s, %s or %s", + StateAsCString(state), StateAsCString(eStateInvalid), + StateAsCString(eStateExited), StateAsCString(eStateStopped), + StateAsCString(eStateCrashed)); +} + +Status Process::LoadCore() { + Status error = DoLoadCore(); + if (error.Success()) { + ListenerSP listener_sp( + Listener::MakeListener("lldb.process.load_core_listener")); + HijackProcessEvents(listener_sp); + + if (PrivateStateThreadIsValid()) + ResumePrivateStateThread(); + else + StartPrivateStateThread(); + + DynamicLoader *dyld = GetDynamicLoader(); + if (dyld) + dyld->DidAttach(); + + GetJITLoaders().DidAttach(); + + SystemRuntime *system_runtime = GetSystemRuntime(); + if (system_runtime) + system_runtime->DidAttach(); + + if (!m_os_up) + LoadOperatingSystemPlugin(false); + + // We successfully loaded a core file, now pretend we stopped so we can + // show all of the threads in the core file and explore the crashed state. + SetPrivateState(eStateStopped); + + // Wait for a stopped event since we just posted one above... + lldb::EventSP event_sp; + StateType state = + WaitForProcessToStop(std::nullopt, &event_sp, true, listener_sp, + nullptr, true, SelectMostRelevantFrame); + + if (!StateIsStoppedState(state, false)) { + Log *log = GetLog(LLDBLog::Process); + LLDB_LOGF(log, "Process::Halt() failed to stop, state is: %s", + StateAsCString(state)); + error.SetErrorString( + "Did not get stopped event after loading the core file."); + } + RestoreProcessEvents(); + } + return error; +} + +DynamicLoader *Process::GetDynamicLoader() { + if (!m_dyld_up) + m_dyld_up.reset(DynamicLoader::FindPlugin(this, "")); + return m_dyld_up.get(); +} + +void Process::SetDynamicLoader(DynamicLoaderUP dyld_up) { + m_dyld_up = std::move(dyld_up); +} + +DataExtractor Process::GetAuxvData() { return DataExtractor(); } + +llvm::Expected<bool> Process::SaveCore(llvm::StringRef outfile) { + return false; +} + +JITLoaderList &Process::GetJITLoaders() { + if (!m_jit_loaders_up) { + m_jit_loaders_up = std::make_unique<JITLoaderList>(); + JITLoader::LoadPlugins(this, *m_jit_loaders_up); + } + return *m_jit_loaders_up; +} + +SystemRuntime *Process::GetSystemRuntime() { + if (!m_system_runtime_up) + m_system_runtime_up.reset(SystemRuntime::FindPlugin(this)); + return m_system_runtime_up.get(); +} + +Process::AttachCompletionHandler::AttachCompletionHandler(Process *process, + uint32_t exec_count) + : NextEventAction(process), m_exec_count(exec_count) { + Log *log = GetLog(LLDBLog::Process); + LLDB_LOGF( + log, + "Process::AttachCompletionHandler::%s process=%p, exec_count=%" PRIu32, + __FUNCTION__, static_cast<void *>(process), exec_count); +} + +Process::NextEventAction::EventActionResult +Process::AttachCompletionHandler::PerformAction(lldb::EventSP &event_sp) { + Log *log = GetLog(LLDBLog::Process); + + StateType state = ProcessEventData::GetStateFromEvent(event_sp.get()); + LLDB_LOGF(log, + "Process::AttachCompletionHandler::%s called with state %s (%d)", + __FUNCTION__, StateAsCString(state), static_cast<int>(state)); + + switch (state) { + case eStateAttaching: + return eEventActionSuccess; + + case eStateRunning: + case eStateConnected: + return eEventActionRetry; + + case eStateStopped: + case eStateCrashed: + // During attach, prior to sending the eStateStopped event, + // lldb_private::Process subclasses must set the new process ID. + assert(m_process->GetID() != LLDB_INVALID_PROCESS_ID); + // We don't want these events to be reported, so go set the + // ShouldReportStop here: + m_process->GetThreadList().SetShouldReportStop(eVoteNo); + + if (m_exec_count > 0) { + --m_exec_count; + + LLDB_LOGF(log, + "Process::AttachCompletionHandler::%s state %s: reduced " + "remaining exec count to %" PRIu32 ", requesting resume", + __FUNCTION__, StateAsCString(state), m_exec_count); + + RequestResume(); + return eEventActionRetry; + } else { + LLDB_LOGF(log, + "Process::AttachCompletionHandler::%s state %s: no more " + "execs expected to start, continuing with attach", + __FUNCTION__, StateAsCString(state)); + + m_process->CompleteAttach(); + return eEventActionSuccess; + } + break; + + default: + case eStateExited: + case eStateInvalid: + break; + } + + m_exit_string.assign("No valid Process"); + return eEventActionExit; +} + +Process::NextEventAction::EventActionResult +Process::AttachCompletionHandler::HandleBeingInterrupted() { + return eEventActionSuccess; +} + +const char *Process::AttachCompletionHandler::GetExitString() { + return m_exit_string.c_str(); +} + +ListenerSP ProcessAttachInfo::GetListenerForProcess(Debugger &debugger) { + if (m_listener_sp) + return m_listener_sp; + else + return debugger.GetListener(); +} + +Status Process::WillLaunch(Module *module) { + return DoWillLaunch(module); +} + +Status Process::WillAttachToProcessWithID(lldb::pid_t pid) { + return DoWillAttachToProcessWithID(pid); +} + +Status Process::WillAttachToProcessWithName(const char *process_name, + bool wait_for_launch) { + return DoWillAttachToProcessWithName(process_name, wait_for_launch); +} + +Status Process::Attach(ProcessAttachInfo &attach_info) { + m_abi_sp.reset(); + { + std::lock_guard<std::mutex> guard(m_process_input_reader_mutex); + m_process_input_reader.reset(); + } + m_dyld_up.reset(); + m_jit_loaders_up.reset(); + m_system_runtime_up.reset(); + m_os_up.reset(); + + lldb::pid_t attach_pid = attach_info.GetProcessID(); + Status error; + if (attach_pid == LLDB_INVALID_PROCESS_ID) { + char process_name[PATH_MAX]; + + if (attach_info.GetExecutableFile().GetPath(process_name, + sizeof(process_name))) { + const bool wait_for_launch = attach_info.GetWaitForLaunch(); + + if (wait_for_launch) { + error = WillAttachToProcessWithName(process_name, wait_for_launch); + if (error.Success()) { + if (m_public_run_lock.TrySetRunning()) { + m_should_detach = true; + const bool restarted = false; + SetPublicState(eStateAttaching, restarted); + // Now attach using these arguments. + error = DoAttachToProcessWithName(process_name, attach_info); + } else { + // This shouldn't happen + error.SetErrorString("failed to acquire process run lock"); + } + + if (error.Fail()) { + if (GetID() != LLDB_INVALID_PROCESS_ID) { + SetID(LLDB_INVALID_PROCESS_ID); + if (error.AsCString() == nullptr) + error.SetErrorString("attach failed"); + + SetExitStatus(-1, error.AsCString()); + } + } else { + SetNextEventAction(new Process::AttachCompletionHandler( + this, attach_info.GetResumeCount())); + StartPrivateStateThread(); + } + return error; + } + } else { + ProcessInstanceInfoList process_infos; + PlatformSP platform_sp(GetTarget().GetPlatform()); + + if (platform_sp) { + ProcessInstanceInfoMatch match_info; + match_info.GetProcessInfo() = attach_info; + match_info.SetNameMatchType(NameMatch::Equals); + platform_sp->FindProcesses(match_info, process_infos); + const uint32_t num_matches = process_infos.size(); + if (num_matches == 1) { + attach_pid = process_infos[0].GetProcessID(); + // Fall through and attach using the above process ID + } else { + match_info.GetProcessInfo().GetExecutableFile().GetPath( + process_name, sizeof(process_name)); + if (num_matches > 1) { + StreamString s; + ProcessInstanceInfo::DumpTableHeader(s, true, false); + for (size_t i = 0; i < num_matches; i++) { + process_infos[i].DumpAsTableRow( + s, platform_sp->GetUserIDResolver(), true, false); + } + error.SetErrorStringWithFormat( + "more than one process named %s:\n%s", process_name, + s.GetData()); + } else + error.SetErrorStringWithFormat( + "could not find a process named %s", process_name); + } + } else { + error.SetErrorString( + "invalid platform, can't find processes by name"); + return error; + } + } + } else { + error.SetErrorString("invalid process name"); + } + } + + if (attach_pid != LLDB_INVALID_PROCESS_ID) { + error = WillAttachToProcessWithID(attach_pid); + if (error.Success()) { + + if (m_public_run_lock.TrySetRunning()) { + // Now attach using these arguments. + m_should_detach = true; + const bool restarted = false; + SetPublicState(eStateAttaching, restarted); + error = DoAttachToProcessWithID(attach_pid, attach_info); + } else { + // This shouldn't happen + error.SetErrorString("failed to acquire process run lock"); + } + + if (error.Success()) { + SetNextEventAction(new Process::AttachCompletionHandler( + this, attach_info.GetResumeCount())); + StartPrivateStateThread(); + } else { + if (GetID() != LLDB_INVALID_PROCESS_ID) + SetID(LLDB_INVALID_PROCESS_ID); + + const char *error_string = error.AsCString(); + if (error_string == nullptr) + error_string = "attach failed"; + + SetExitStatus(-1, error_string); + } + } + } + return error; +} + +void Process::CompleteAttach() { + Log *log(GetLog(LLDBLog::Process | LLDBLog::Target)); + LLDB_LOGF(log, "Process::%s()", __FUNCTION__); + + // Let the process subclass figure out at much as it can about the process + // before we go looking for a dynamic loader plug-in. + ArchSpec process_arch; + DidAttach(process_arch); + + if (process_arch.IsValid()) { + LLDB_LOG(log, + "Process::{0} replacing process architecture with DidAttach() " + "architecture: \"{1}\"", + __FUNCTION__, process_arch.GetTriple().getTriple()); + GetTarget().SetArchitecture(process_arch); + } + + // We just attached. If we have a platform, ask it for the process + // architecture, and if it isn't the same as the one we've already set, + // switch architectures. + PlatformSP platform_sp(GetTarget().GetPlatform()); + assert(platform_sp); + ArchSpec process_host_arch = GetSystemArchitecture(); + if (platform_sp) { + const ArchSpec &target_arch = GetTarget().GetArchitecture(); + if (target_arch.IsValid() && !platform_sp->IsCompatibleArchitecture( + target_arch, process_host_arch, + ArchSpec::CompatibleMatch, nullptr)) { + ArchSpec platform_arch; + platform_sp = GetTarget().GetDebugger().GetPlatformList().GetOrCreate( + target_arch, process_host_arch, &platform_arch); + if (platform_sp) { + GetTarget().SetPlatform(platform_sp); + GetTarget().SetArchitecture(platform_arch); + LLDB_LOG(log, + "switching platform to {0} and architecture to {1} based on " + "info from attach", + platform_sp->GetName(), platform_arch.GetTriple().getTriple()); + } + } else if (!process_arch.IsValid()) { + ProcessInstanceInfo process_info; + GetProcessInfo(process_info); + const ArchSpec &process_arch = process_info.GetArchitecture(); + const ArchSpec &target_arch = GetTarget().GetArchitecture(); + if (process_arch.IsValid() && + target_arch.IsCompatibleMatch(process_arch) && + !target_arch.IsExactMatch(process_arch)) { + GetTarget().SetArchitecture(process_arch); + LLDB_LOGF(log, + "Process::%s switching architecture to %s based on info " + "the platform retrieved for pid %" PRIu64, + __FUNCTION__, process_arch.GetTriple().getTriple().c_str(), + GetID()); + } + } + } + // Now that we know the process type, update its signal responses from the + // ones stored in the Target: + if (m_unix_signals_sp) { + StreamSP warning_strm = GetTarget().GetDebugger().GetAsyncErrorStream(); + GetTarget().UpdateSignalsFromDummy(m_unix_signals_sp, warning_strm); + } + + // We have completed the attach, now it is time to find the dynamic loader + // plug-in + DynamicLoader *dyld = GetDynamicLoader(); + if (dyld) { + dyld->DidAttach(); + if (log) { + ModuleSP exe_module_sp = GetTarget().GetExecutableModule(); + LLDB_LOG(log, + "after DynamicLoader::DidAttach(), target " + "executable is {0} (using {1} plugin)", + exe_module_sp ? exe_module_sp->GetFileSpec() : FileSpec(), + dyld->GetPluginName()); + } + } + + GetJITLoaders().DidAttach(); + + SystemRuntime *system_runtime = GetSystemRuntime(); + if (system_runtime) { + system_runtime->DidAttach(); + if (log) { + ModuleSP exe_module_sp = GetTarget().GetExecutableModule(); + LLDB_LOG(log, + "after SystemRuntime::DidAttach(), target " + "executable is {0} (using {1} plugin)", + exe_module_sp ? exe_module_sp->GetFileSpec() : FileSpec(), + system_runtime->GetPluginName()); + } + } + + if (!m_os_up) { + LoadOperatingSystemPlugin(false); + if (m_os_up) { + // Somebody might have gotten threads before now, but we need to force the + // update after we've loaded the OperatingSystem plugin or it won't get a + // chance to process the threads. + m_thread_list.Clear(); + UpdateThreadListIfNeeded(); + } + } + // Figure out which one is the executable, and set that in our target: + ModuleSP new_executable_module_sp; + for (ModuleSP module_sp : GetTarget().GetImages().Modules()) { + if (module_sp && module_sp->IsExecutable()) { + if (GetTarget().GetExecutableModulePointer() != module_sp.get()) + new_executable_module_sp = module_sp; + break; + } + } + if (new_executable_module_sp) { + GetTarget().SetExecutableModule(new_executable_module_sp, + eLoadDependentsNo); + if (log) { + ModuleSP exe_module_sp = GetTarget().GetExecutableModule(); + LLDB_LOGF( + log, + "Process::%s after looping through modules, target executable is %s", + __FUNCTION__, + exe_module_sp ? exe_module_sp->GetFileSpec().GetPath().c_str() + : "<none>"); + } + } +} + +Status Process::ConnectRemote(llvm::StringRef remote_url) { + m_abi_sp.reset(); + { + std::lock_guard<std::mutex> guard(m_process_input_reader_mutex); + m_process_input_reader.reset(); + } + + // Find the process and its architecture. Make sure it matches the + // architecture of the current Target, and if not adjust it. + + Status error(DoConnectRemote(remote_url)); + if (error.Success()) { + if (GetID() != LLDB_INVALID_PROCESS_ID) { + EventSP event_sp; + StateType state = WaitForProcessStopPrivate(event_sp, std::nullopt); + + if (state == eStateStopped || state == eStateCrashed) { + // If we attached and actually have a process on the other end, then + // this ended up being the equivalent of an attach. + CompleteAttach(); + + // This delays passing the stopped event to listeners till + // CompleteAttach gets a chance to complete... + HandlePrivateEvent(event_sp); + } + } + + if (PrivateStateThreadIsValid()) + ResumePrivateStateThread(); + else + StartPrivateStateThread(); + } + return error; +} + +Status Process::PrivateResume() { + Log *log(GetLog(LLDBLog::Process | LLDBLog::Step)); + LLDB_LOGF(log, + "Process::PrivateResume() m_stop_id = %u, public state: %s " + "private state: %s", + m_mod_id.GetStopID(), StateAsCString(m_public_state.GetValue()), + StateAsCString(m_private_state.GetValue())); + + // If signals handing status changed we might want to update our signal + // filters before resuming. + UpdateAutomaticSignalFiltering(); + + Status error(WillResume()); + // Tell the process it is about to resume before the thread list + if (error.Success()) { + // Now let the thread list know we are about to resume so it can let all of + // our threads know that they are about to be resumed. Threads will each be + // called with Thread::WillResume(StateType) where StateType contains the + // state that they are supposed to have when the process is resumed + // (suspended/running/stepping). Threads should also check their resume + // signal in lldb::Thread::GetResumeSignal() to see if they are supposed to + // start back up with a signal. + if (m_thread_list.WillResume()) { + // Last thing, do the PreResumeActions. + if (!RunPreResumeActions()) { + error.SetErrorString( + "Process::PrivateResume PreResumeActions failed, not resuming."); + } else { + m_mod_id.BumpResumeID(); + error = DoResume(); + if (error.Success()) { + DidResume(); + m_thread_list.DidResume(); + LLDB_LOGF(log, "Process thinks the process has resumed."); + } else { + LLDB_LOGF(log, "Process::PrivateResume() DoResume failed."); + return error; + } + } + } else { + // Somebody wanted to run without running (e.g. we were faking a step + // from one frame of a set of inlined frames that share the same PC to + // another.) So generate a continue & a stopped event, and let the world + // handle them. + LLDB_LOGF(log, + "Process::PrivateResume() asked to simulate a start & stop."); + + SetPrivateState(eStateRunning); + SetPrivateState(eStateStopped); + } + } else + LLDB_LOGF(log, "Process::PrivateResume() got an error \"%s\".", + error.AsCString("<unknown error>")); + return error; +} + +Status Process::Halt(bool clear_thread_plans, bool use_run_lock) { + if (!StateIsRunningState(m_public_state.GetValue())) + return Status("Process is not running."); + + // Don't clear the m_clear_thread_plans_on_stop, only set it to true if in + // case it was already set and some thread plan logic calls halt on its own. + m_clear_thread_plans_on_stop |= clear_thread_plans; + + ListenerSP halt_listener_sp( + Listener::MakeListener("lldb.process.halt_listener")); + HijackProcessEvents(halt_listener_sp); + + EventSP event_sp; + + SendAsyncInterrupt(); + + if (m_public_state.GetValue() == eStateAttaching) { + // Don't hijack and eat the eStateExited as the code that was doing the + // attach will be waiting for this event... + RestoreProcessEvents(); + Destroy(false); + SetExitStatus(SIGKILL, "Cancelled async attach."); + return Status(); + } + + // Wait for the process halt timeout seconds for the process to stop. + // If we are going to use the run lock, that means we're stopping out to the + // user, so we should also select the most relevant frame. + SelectMostRelevant select_most_relevant = + use_run_lock ? SelectMostRelevantFrame : DoNoSelectMostRelevantFrame; + StateType state = WaitForProcessToStop(GetInterruptTimeout(), &event_sp, true, + halt_listener_sp, nullptr, + use_run_lock, select_most_relevant); + RestoreProcessEvents(); + + if (state == eStateInvalid || !event_sp) { + // We timed out and didn't get a stop event... + return Status("Halt timed out. State = %s", StateAsCString(GetState())); + } + + BroadcastEvent(event_sp); + + return Status(); +} + +lldb::addr_t Process::FindInMemory(lldb::addr_t low, lldb::addr_t high, + const uint8_t *buf, size_t size) { + const size_t region_size = high - low; + + if (region_size < size) + return LLDB_INVALID_ADDRESS; + + std::vector<size_t> bad_char_heuristic(256, size); + ProcessMemoryIterator iterator(*this, low); + + for (size_t idx = 0; idx < size - 1; idx++) { + decltype(bad_char_heuristic)::size_type bcu_idx = buf[idx]; + bad_char_heuristic[bcu_idx] = size - idx - 1; + } + for (size_t s = 0; s <= (region_size - size);) { + int64_t j = size - 1; + while (j >= 0 && buf[j] == iterator[s + j]) + j--; + if (j < 0) + return low + s; + else + s += bad_char_heuristic[iterator[s + size - 1]]; + } + + return LLDB_INVALID_ADDRESS; +} + +Status Process::StopForDestroyOrDetach(lldb::EventSP &exit_event_sp) { + Status error; + + // Check both the public & private states here. If we're hung evaluating an + // expression, for instance, then the public state will be stopped, but we + // still need to interrupt. + if (m_public_state.GetValue() == eStateRunning || + m_private_state.GetValue() == eStateRunning) { + Log *log = GetLog(LLDBLog::Process); + LLDB_LOGF(log, "Process::%s() About to stop.", __FUNCTION__); + + ListenerSP listener_sp( + Listener::MakeListener("lldb.Process.StopForDestroyOrDetach.hijack")); + HijackProcessEvents(listener_sp); + + SendAsyncInterrupt(); + + // Consume the interrupt event. + StateType state = WaitForProcessToStop(GetInterruptTimeout(), + &exit_event_sp, true, listener_sp); + + RestoreProcessEvents(); + + // If the process exited while we were waiting for it to stop, put the + // exited event into the shared pointer passed in and return. Our caller + // doesn't need to do anything else, since they don't have a process + // anymore... + + if (state == eStateExited || m_private_state.GetValue() == eStateExited) { + LLDB_LOGF(log, "Process::%s() Process exited while waiting to stop.", + __FUNCTION__); + return error; + } else + exit_event_sp.reset(); // It is ok to consume any non-exit stop events + + if (state != eStateStopped) { + LLDB_LOGF(log, "Process::%s() failed to stop, state is: %s", __FUNCTION__, + StateAsCString(state)); + // If we really couldn't stop the process then we should just error out + // here, but if the lower levels just bobbled sending the event and we + // really are stopped, then continue on. + StateType private_state = m_private_state.GetValue(); + if (private_state != eStateStopped) { + return Status( + "Attempt to stop the target in order to detach timed out. " + "State = %s", + StateAsCString(GetState())); + } + } + } + return error; +} + +Status Process::Detach(bool keep_stopped) { + EventSP exit_event_sp; + Status error; + m_destroy_in_process = true; + + error = WillDetach(); + + if (error.Success()) { + if (DetachRequiresHalt()) { + error = StopForDestroyOrDetach(exit_event_sp); + if (!error.Success()) { + m_destroy_in_process = false; + return error; + } else if (exit_event_sp) { + // We shouldn't need to do anything else here. There's no process left + // to detach from... + StopPrivateStateThread(); + m_destroy_in_process = false; + return error; + } + } + + m_thread_list.DiscardThreadPlans(); + DisableAllBreakpointSites(); + + error = DoDetach(keep_stopped); + if (error.Success()) { + DidDetach(); + StopPrivateStateThread(); + } else { + return error; + } + } + m_destroy_in_process = false; + + // If we exited when we were waiting for a process to stop, then forward the + // event here so we don't lose the event + if (exit_event_sp) { + // Directly broadcast our exited event because we shut down our private + // state thread above + BroadcastEvent(exit_event_sp); + } + + // If we have been interrupted (to kill us) in the middle of running, we may + // not end up propagating the last events through the event system, in which + // case we might strand the write lock. Unlock it here so when we do to tear + // down the process we don't get an error destroying the lock. + + m_public_run_lock.SetStopped(); + return error; +} + +Status Process::Destroy(bool force_kill) { + // If we've already called Process::Finalize then there's nothing useful to + // be done here. Finalize has actually called Destroy already. + if (m_finalizing) + return {}; + return DestroyImpl(force_kill); +} + +Status Process::DestroyImpl(bool force_kill) { + // Tell ourselves we are in the process of destroying the process, so that we + // don't do any unnecessary work that might hinder the destruction. Remember + // to set this back to false when we are done. That way if the attempt + // failed and the process stays around for some reason it won't be in a + // confused state. + + if (force_kill) + m_should_detach = false; + + if (GetShouldDetach()) { + // FIXME: This will have to be a process setting: + bool keep_stopped = false; + Detach(keep_stopped); + } + + m_destroy_in_process = true; + + Status error(WillDestroy()); + if (error.Success()) { + EventSP exit_event_sp; + if (DestroyRequiresHalt()) { + error = StopForDestroyOrDetach(exit_event_sp); + } + + if (m_public_state.GetValue() == eStateStopped) { + // Ditch all thread plans, and remove all our breakpoints: in case we + // have to restart the target to kill it, we don't want it hitting a + // breakpoint... Only do this if we've stopped, however, since if we + // didn't manage to halt it above, then we're not going to have much luck + // doing this now. + m_thread_list.DiscardThreadPlans(); + DisableAllBreakpointSites(); + } + + error = DoDestroy(); + if (error.Success()) { + DidDestroy(); + StopPrivateStateThread(); + } + m_stdio_communication.StopReadThread(); + m_stdio_communication.Disconnect(); + m_stdin_forward = false; + + { + std::lock_guard<std::mutex> guard(m_process_input_reader_mutex); + if (m_process_input_reader) { + m_process_input_reader->SetIsDone(true); + m_process_input_reader->Cancel(); + m_process_input_reader.reset(); + } + } + + // If we exited when we were waiting for a process to stop, then forward + // the event here so we don't lose the event + if (exit_event_sp) { + // Directly broadcast our exited event because we shut down our private + // state thread above + BroadcastEvent(exit_event_sp); + } + + // If we have been interrupted (to kill us) in the middle of running, we + // may not end up propagating the last events through the event system, in + // which case we might strand the write lock. Unlock it here so when we do + // to tear down the process we don't get an error destroying the lock. + m_public_run_lock.SetStopped(); + } + + m_destroy_in_process = false; + + return error; +} + +Status Process::Signal(int signal) { + Status error(WillSignal()); + if (error.Success()) { + error = DoSignal(signal); + if (error.Success()) + DidSignal(); + } + return error; +} + +void Process::SetUnixSignals(UnixSignalsSP &&signals_sp) { + assert(signals_sp && "null signals_sp"); + m_unix_signals_sp = std::move(signals_sp); +} + +const lldb::UnixSignalsSP &Process::GetUnixSignals() { + assert(m_unix_signals_sp && "null m_unix_signals_sp"); + return m_unix_signals_sp; +} + +lldb::ByteOrder Process::GetByteOrder() const { + return GetTarget().GetArchitecture().GetByteOrder(); +} + +uint32_t Process::GetAddressByteSize() const { + return GetTarget().GetArchitecture().GetAddressByteSize(); +} + +bool Process::ShouldBroadcastEvent(Event *event_ptr) { + const StateType state = + Process::ProcessEventData::GetStateFromEvent(event_ptr); + bool return_value = true; + Log *log(GetLog(LLDBLog::Events | LLDBLog::Process)); + + switch (state) { + case eStateDetached: + case eStateExited: + case eStateUnloaded: + m_stdio_communication.SynchronizeWithReadThread(); + m_stdio_communication.StopReadThread(); + m_stdio_communication.Disconnect(); + m_stdin_forward = false; + + [[fallthrough]]; + case eStateConnected: + case eStateAttaching: + case eStateLaunching: + // These events indicate changes in the state of the debugging session, + // always report them. + return_value = true; + break; + case eStateInvalid: + // We stopped for no apparent reason, don't report it. + return_value = false; + break; + case eStateRunning: + case eStateStepping: + // If we've started the target running, we handle the cases where we are + // already running and where there is a transition from stopped to running + // differently. running -> running: Automatically suppress extra running + // events stopped -> running: Report except when there is one or more no + // votes + // and no yes votes. + SynchronouslyNotifyStateChanged(state); + if (m_force_next_event_delivery) + return_value = true; + else { + switch (m_last_broadcast_state) { + case eStateRunning: + case eStateStepping: + // We always suppress multiple runnings with no PUBLIC stop in between. + return_value = false; + break; + default: + // TODO: make this work correctly. For now always report + // run if we aren't running so we don't miss any running events. If I + // run the lldb/test/thread/a.out file and break at main.cpp:58, run + // and hit the breakpoints on multiple threads, then somehow during the + // stepping over of all breakpoints no run gets reported. + + // This is a transition from stop to run. + switch (m_thread_list.ShouldReportRun(event_ptr)) { + case eVoteYes: + case eVoteNoOpinion: + return_value = true; + break; + case eVoteNo: + return_value = false; + break; + } + break; + } + } + break; + case eStateStopped: + case eStateCrashed: + case eStateSuspended: + // We've stopped. First see if we're going to restart the target. If we + // are going to stop, then we always broadcast the event. If we aren't + // going to stop, let the thread plans decide if we're going to report this + // event. If no thread has an opinion, we don't report it. + + m_stdio_communication.SynchronizeWithReadThread(); + RefreshStateAfterStop(); + if (ProcessEventData::GetInterruptedFromEvent(event_ptr)) { + LLDB_LOGF(log, + "Process::ShouldBroadcastEvent (%p) stopped due to an " + "interrupt, state: %s", + static_cast<void *>(event_ptr), StateAsCString(state)); + // Even though we know we are going to stop, we should let the threads + // have a look at the stop, so they can properly set their state. + m_thread_list.ShouldStop(event_ptr); + return_value = true; + } else { + bool was_restarted = ProcessEventData::GetRestartedFromEvent(event_ptr); + bool should_resume = false; + + // It makes no sense to ask "ShouldStop" if we've already been + // restarted... Asking the thread list is also not likely to go well, + // since we are running again. So in that case just report the event. + + if (!was_restarted) + should_resume = !m_thread_list.ShouldStop(event_ptr); + + if (was_restarted || should_resume || m_resume_requested) { + Vote report_stop_vote = m_thread_list.ShouldReportStop(event_ptr); + LLDB_LOGF(log, + "Process::ShouldBroadcastEvent: should_resume: %i state: " + "%s was_restarted: %i report_stop_vote: %d.", + should_resume, StateAsCString(state), was_restarted, + report_stop_vote); + + switch (report_stop_vote) { + case eVoteYes: + return_value = true; + break; + case eVoteNoOpinion: + case eVoteNo: + return_value = false; + break; + } + + if (!was_restarted) { + LLDB_LOGF(log, + "Process::ShouldBroadcastEvent (%p) Restarting process " + "from state: %s", + static_cast<void *>(event_ptr), StateAsCString(state)); + ProcessEventData::SetRestartedInEvent(event_ptr, true); + PrivateResume(); + } + } else { + return_value = true; + SynchronouslyNotifyStateChanged(state); + } + } + break; + } + + // Forcing the next event delivery is a one shot deal. So reset it here. + m_force_next_event_delivery = false; + + // We do some coalescing of events (for instance two consecutive running + // events get coalesced.) But we only coalesce against events we actually + // broadcast. So we use m_last_broadcast_state to track that. NB - you + // can't use "m_public_state.GetValue()" for that purpose, as was originally + // done, because the PublicState reflects the last event pulled off the + // queue, and there may be several events stacked up on the queue unserviced. + // So the PublicState may not reflect the last broadcasted event yet. + // m_last_broadcast_state gets updated here. + + if (return_value) + m_last_broadcast_state = state; + + LLDB_LOGF(log, + "Process::ShouldBroadcastEvent (%p) => new state: %s, last " + "broadcast state: %s - %s", + static_cast<void *>(event_ptr), StateAsCString(state), + StateAsCString(m_last_broadcast_state), + return_value ? "YES" : "NO"); + return return_value; +} + +bool Process::StartPrivateStateThread(bool is_secondary_thread) { + Log *log = GetLog(LLDBLog::Events); + + bool already_running = PrivateStateThreadIsValid(); + LLDB_LOGF(log, "Process::%s()%s ", __FUNCTION__, + already_running ? " already running" + : " starting private state thread"); + + if (!is_secondary_thread && already_running) + return true; + + // Create a thread that watches our internal state and controls which events + // make it to clients (into the DCProcess event queue). + char thread_name[1024]; + uint32_t max_len = llvm::get_max_thread_name_length(); + if (max_len > 0 && max_len <= 30) { + // On platforms with abbreviated thread name lengths, choose thread names + // that fit within the limit. + if (already_running) + snprintf(thread_name, sizeof(thread_name), "intern-state-OV"); + else + snprintf(thread_name, sizeof(thread_name), "intern-state"); + } else { + if (already_running) + snprintf(thread_name, sizeof(thread_name), + "<lldb.process.internal-state-override(pid=%" PRIu64 ")>", + GetID()); + else + snprintf(thread_name, sizeof(thread_name), + "<lldb.process.internal-state(pid=%" PRIu64 ")>", GetID()); + } + + llvm::Expected<HostThread> private_state_thread = + ThreadLauncher::LaunchThread( + thread_name, + [this, is_secondary_thread] { + return RunPrivateStateThread(is_secondary_thread); + }, + 8 * 1024 * 1024); + if (!private_state_thread) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Host), private_state_thread.takeError(), + "failed to launch host thread: {0}"); + return false; + } + + assert(private_state_thread->IsJoinable()); + m_private_state_thread = *private_state_thread; + ResumePrivateStateThread(); + return true; +} + +void Process::PausePrivateStateThread() { + ControlPrivateStateThread(eBroadcastInternalStateControlPause); +} + +void Process::ResumePrivateStateThread() { + ControlPrivateStateThread(eBroadcastInternalStateControlResume); +} + +void Process::StopPrivateStateThread() { + if (m_private_state_thread.IsJoinable()) + ControlPrivateStateThread(eBroadcastInternalStateControlStop); + else { + Log *log = GetLog(LLDBLog::Process); + LLDB_LOGF( + log, + "Went to stop the private state thread, but it was already invalid."); + } +} + +void Process::ControlPrivateStateThread(uint32_t signal) { + Log *log = GetLog(LLDBLog::Process); + + assert(signal == eBroadcastInternalStateControlStop || + signal == eBroadcastInternalStateControlPause || + signal == eBroadcastInternalStateControlResume); + + LLDB_LOGF(log, "Process::%s (signal = %d)", __FUNCTION__, signal); + + // Signal the private state thread + if (m_private_state_thread.IsJoinable()) { + // Broadcast the event. + // It is important to do this outside of the if below, because it's + // possible that the thread state is invalid but that the thread is waiting + // on a control event instead of simply being on its way out (this should + // not happen, but it apparently can). + LLDB_LOGF(log, "Sending control event of type: %d.", signal); + std::shared_ptr<EventDataReceipt> event_receipt_sp(new EventDataReceipt()); + m_private_state_control_broadcaster.BroadcastEvent(signal, + event_receipt_sp); + + // Wait for the event receipt or for the private state thread to exit + bool receipt_received = false; + if (PrivateStateThreadIsValid()) { + while (!receipt_received) { + // Check for a receipt for n seconds and then check if the private + // state thread is still around. + receipt_received = + event_receipt_sp->WaitForEventReceived(GetUtilityExpressionTimeout()); + if (!receipt_received) { + // Check if the private state thread is still around. If it isn't + // then we are done waiting + if (!PrivateStateThreadIsValid()) + break; // Private state thread exited or is exiting, we are done + } + } + } + + if (signal == eBroadcastInternalStateControlStop) { + thread_result_t result = {}; + m_private_state_thread.Join(&result); + m_private_state_thread.Reset(); + } + } else { + LLDB_LOGF( + log, + "Private state thread already dead, no need to signal it to stop."); + } +} + +void Process::SendAsyncInterrupt() { + if (PrivateStateThreadIsValid()) + m_private_state_broadcaster.BroadcastEvent(Process::eBroadcastBitInterrupt, + nullptr); + else + BroadcastEvent(Process::eBroadcastBitInterrupt, nullptr); +} + +void Process::HandlePrivateEvent(EventSP &event_sp) { + Log *log = GetLog(LLDBLog::Process); + m_resume_requested = false; + + const StateType new_state = + Process::ProcessEventData::GetStateFromEvent(event_sp.get()); + + // First check to see if anybody wants a shot at this event: + if (m_next_event_action_up) { + NextEventAction::EventActionResult action_result = + m_next_event_action_up->PerformAction(event_sp); + LLDB_LOGF(log, "Ran next event action, result was %d.", action_result); + + switch (action_result) { + case NextEventAction::eEventActionSuccess: + SetNextEventAction(nullptr); + break; + + case NextEventAction::eEventActionRetry: + break; + + case NextEventAction::eEventActionExit: + // Handle Exiting Here. If we already got an exited event, we should + // just propagate it. Otherwise, swallow this event, and set our state + // to exit so the next event will kill us. + if (new_state != eStateExited) { + // FIXME: should cons up an exited event, and discard this one. + SetExitStatus(0, m_next_event_action_up->GetExitString()); + SetNextEventAction(nullptr); + return; + } + SetNextEventAction(nullptr); + break; + } + } + + // See if we should broadcast this state to external clients? + const bool should_broadcast = ShouldBroadcastEvent(event_sp.get()); + + if (should_broadcast) { + const bool is_hijacked = IsHijackedForEvent(eBroadcastBitStateChanged); + if (log) { + LLDB_LOGF(log, + "Process::%s (pid = %" PRIu64 + ") broadcasting new state %s (old state %s) to %s", + __FUNCTION__, GetID(), StateAsCString(new_state), + StateAsCString(GetState()), + is_hijacked ? "hijacked" : "public"); + } + Process::ProcessEventData::SetUpdateStateOnRemoval(event_sp.get()); + if (StateIsRunningState(new_state)) { + // Only push the input handler if we aren't fowarding events, as this + // means the curses GUI is in use... Or don't push it if we are launching + // since it will come up stopped. + if (!GetTarget().GetDebugger().IsForwardingEvents() && + new_state != eStateLaunching && new_state != eStateAttaching) { + PushProcessIOHandler(); + m_iohandler_sync.SetValue(m_iohandler_sync.GetValue() + 1, + eBroadcastAlways); + LLDB_LOGF(log, "Process::%s updated m_iohandler_sync to %d", + __FUNCTION__, m_iohandler_sync.GetValue()); + } + } else if (StateIsStoppedState(new_state, false)) { + if (!Process::ProcessEventData::GetRestartedFromEvent(event_sp.get())) { + // If the lldb_private::Debugger is handling the events, we don't want + // to pop the process IOHandler here, we want to do it when we receive + // the stopped event so we can carefully control when the process + // IOHandler is popped because when we stop we want to display some + // text stating how and why we stopped, then maybe some + // process/thread/frame info, and then we want the "(lldb) " prompt to + // show up. If we pop the process IOHandler here, then we will cause + // the command interpreter to become the top IOHandler after the + // process pops off and it will update its prompt right away... See the + // Debugger.cpp file where it calls the function as + // "process_sp->PopProcessIOHandler()" to see where I am talking about. + // Otherwise we end up getting overlapping "(lldb) " prompts and + // garbled output. + // + // If we aren't handling the events in the debugger (which is indicated + // by "m_target.GetDebugger().IsHandlingEvents()" returning false) or + // we are hijacked, then we always pop the process IO handler manually. + // Hijacking happens when the internal process state thread is running + // thread plans, or when commands want to run in synchronous mode and + // they call "process->WaitForProcessToStop()". An example of something + // that will hijack the events is a simple expression: + // + // (lldb) expr (int)puts("hello") + // + // This will cause the internal process state thread to resume and halt + // the process (and _it_ will hijack the eBroadcastBitStateChanged + // events) and we do need the IO handler to be pushed and popped + // correctly. + + if (is_hijacked || !GetTarget().GetDebugger().IsHandlingEvents()) + PopProcessIOHandler(); + } + } + + BroadcastEvent(event_sp); + } else { + if (log) { + LLDB_LOGF( + log, + "Process::%s (pid = %" PRIu64 + ") suppressing state %s (old state %s): should_broadcast == false", + __FUNCTION__, GetID(), StateAsCString(new_state), + StateAsCString(GetState())); + } + } +} + +Status Process::HaltPrivate() { + EventSP event_sp; + Status error(WillHalt()); + if (error.Fail()) + return error; + + // Ask the process subclass to actually halt our process + bool caused_stop; + error = DoHalt(caused_stop); + + DidHalt(); + return error; +} + +thread_result_t Process::RunPrivateStateThread(bool is_secondary_thread) { + bool control_only = true; + + Log *log = GetLog(LLDBLog::Process); + LLDB_LOGF(log, "Process::%s (arg = %p, pid = %" PRIu64 ") thread starting...", + __FUNCTION__, static_cast<void *>(this), GetID()); + + bool exit_now = false; + bool interrupt_requested = false; + while (!exit_now) { + EventSP event_sp; + GetEventsPrivate(event_sp, std::nullopt, control_only); + if (event_sp->BroadcasterIs(&m_private_state_control_broadcaster)) { + LLDB_LOGF(log, + "Process::%s (arg = %p, pid = %" PRIu64 + ") got a control event: %d", + __FUNCTION__, static_cast<void *>(this), GetID(), + event_sp->GetType()); + + switch (event_sp->GetType()) { + case eBroadcastInternalStateControlStop: + exit_now = true; + break; // doing any internal state management below + + case eBroadcastInternalStateControlPause: + control_only = true; + break; + + case eBroadcastInternalStateControlResume: + control_only = false; + break; + } + + continue; + } else if (event_sp->GetType() == eBroadcastBitInterrupt) { + if (m_public_state.GetValue() == eStateAttaching) { + LLDB_LOGF(log, + "Process::%s (arg = %p, pid = %" PRIu64 + ") woke up with an interrupt while attaching - " + "forwarding interrupt.", + __FUNCTION__, static_cast<void *>(this), GetID()); + // The server may be spinning waiting for a process to appear, in which + // case we should tell it to stop doing that. Normally, we don't NEED + // to do that because we will next close the communication to the stub + // and that will get it to shut down. But there are remote debugging + // cases where relying on that side-effect causes the shutdown to be + // flakey, so we should send a positive signal to interrupt the wait. + Status error = HaltPrivate(); + BroadcastEvent(eBroadcastBitInterrupt, nullptr); + } else if (StateIsRunningState(m_last_broadcast_state)) { + LLDB_LOGF(log, + "Process::%s (arg = %p, pid = %" PRIu64 + ") woke up with an interrupt - Halting.", + __FUNCTION__, static_cast<void *>(this), GetID()); + Status error = HaltPrivate(); + if (error.Fail() && log) + LLDB_LOGF(log, + "Process::%s (arg = %p, pid = %" PRIu64 + ") failed to halt the process: %s", + __FUNCTION__, static_cast<void *>(this), GetID(), + error.AsCString()); + // Halt should generate a stopped event. Make a note of the fact that + // we were doing the interrupt, so we can set the interrupted flag + // after we receive the event. We deliberately set this to true even if + // HaltPrivate failed, so that we can interrupt on the next natural + // stop. + interrupt_requested = true; + } else { + // This can happen when someone (e.g. Process::Halt) sees that we are + // running and sends an interrupt request, but the process actually + // stops before we receive it. In that case, we can just ignore the + // request. We use m_last_broadcast_state, because the Stopped event + // may not have been popped of the event queue yet, which is when the + // public state gets updated. + LLDB_LOGF(log, + "Process::%s ignoring interrupt as we have already stopped.", + __FUNCTION__); + } + continue; + } + + const StateType internal_state = + Process::ProcessEventData::GetStateFromEvent(event_sp.get()); + + if (internal_state != eStateInvalid) { + if (m_clear_thread_plans_on_stop && + StateIsStoppedState(internal_state, true)) { + m_clear_thread_plans_on_stop = false; + m_thread_list.DiscardThreadPlans(); + } + + if (interrupt_requested) { + if (StateIsStoppedState(internal_state, true)) { + // We requested the interrupt, so mark this as such in the stop event + // so clients can tell an interrupted process from a natural stop + ProcessEventData::SetInterruptedInEvent(event_sp.get(), true); + interrupt_requested = false; + } else if (log) { + LLDB_LOGF(log, + "Process::%s interrupt_requested, but a non-stopped " + "state '%s' received.", + __FUNCTION__, StateAsCString(internal_state)); + } + } + + HandlePrivateEvent(event_sp); + } + + if (internal_state == eStateInvalid || internal_state == eStateExited || + internal_state == eStateDetached) { + LLDB_LOGF(log, + "Process::%s (arg = %p, pid = %" PRIu64 + ") about to exit with internal state %s...", + __FUNCTION__, static_cast<void *>(this), GetID(), + StateAsCString(internal_state)); + + break; + } + } + + // Verify log is still enabled before attempting to write to it... + LLDB_LOGF(log, "Process::%s (arg = %p, pid = %" PRIu64 ") thread exiting...", + __FUNCTION__, static_cast<void *>(this), GetID()); + + // If we are a secondary thread, then the primary thread we are working for + // will have already acquired the public_run_lock, and isn't done with what + // it was doing yet, so don't try to change it on the way out. + if (!is_secondary_thread) + m_public_run_lock.SetStopped(); + return {}; +} + +// Process Event Data + +Process::ProcessEventData::ProcessEventData() : EventData(), m_process_wp() {} + +Process::ProcessEventData::ProcessEventData(const ProcessSP &process_sp, + StateType state) + : EventData(), m_process_wp(), m_state(state) { + if (process_sp) + m_process_wp = process_sp; +} + +Process::ProcessEventData::~ProcessEventData() = default; + +llvm::StringRef Process::ProcessEventData::GetFlavorString() { + return "Process::ProcessEventData"; +} + +llvm::StringRef Process::ProcessEventData::GetFlavor() const { + return ProcessEventData::GetFlavorString(); +} + +bool Process::ProcessEventData::ShouldStop(Event *event_ptr, + bool &found_valid_stopinfo) { + found_valid_stopinfo = false; + + ProcessSP process_sp(m_process_wp.lock()); + if (!process_sp) + return false; + + ThreadList &curr_thread_list = process_sp->GetThreadList(); + uint32_t num_threads = curr_thread_list.GetSize(); + + // The actions might change one of the thread's stop_info's opinions about + // whether we should stop the process, so we need to query that as we go. + + // One other complication here, is that we try to catch any case where the + // target has run (except for expressions) and immediately exit, but if we + // get that wrong (which is possible) then the thread list might have + // changed, and that would cause our iteration here to crash. We could + // make a copy of the thread list, but we'd really like to also know if it + // has changed at all, so we store the original thread ID's of all threads and + // check what we get back against this list & bag out if anything differs. + std::vector<std::pair<ThreadSP, size_t>> not_suspended_threads; + for (uint32_t idx = 0; idx < num_threads; ++idx) { + lldb::ThreadSP thread_sp = curr_thread_list.GetThreadAtIndex(idx); + + /* + Filter out all suspended threads, they could not be the reason + of stop and no need to perform any actions on them. + */ + if (thread_sp->GetResumeState() != eStateSuspended) + not_suspended_threads.emplace_back(thread_sp, thread_sp->GetIndexID()); + } + + // Use this to track whether we should continue from here. We will only + // continue the target running if no thread says we should stop. Of course + // if some thread's PerformAction actually sets the target running, then it + // doesn't matter what the other threads say... + + bool still_should_stop = false; + + // Sometimes - for instance if we have a bug in the stub we are talking to, + // we stop but no thread has a valid stop reason. In that case we should + // just stop, because we have no way of telling what the right thing to do + // is, and it's better to let the user decide than continue behind their + // backs. + + for (auto [thread_sp, thread_index] : not_suspended_threads) { + if (curr_thread_list.GetSize() != num_threads) { + Log *log(GetLog(LLDBLog::Step | LLDBLog::Process)); + LLDB_LOGF( + log, + "Number of threads changed from %u to %u while processing event.", + num_threads, curr_thread_list.GetSize()); + break; + } + + if (thread_sp->GetIndexID() != thread_index) { + Log *log(GetLog(LLDBLog::Step | LLDBLog::Process)); + LLDB_LOG(log, + "The thread {0} changed from {1} to {2} while processing event.", + thread_sp.get(), thread_index, thread_sp->GetIndexID()); + break; + } + + StopInfoSP stop_info_sp = thread_sp->GetStopInfo(); + if (stop_info_sp && stop_info_sp->IsValid()) { + found_valid_stopinfo = true; + bool this_thread_wants_to_stop; + if (stop_info_sp->GetOverrideShouldStop()) { + this_thread_wants_to_stop = + stop_info_sp->GetOverriddenShouldStopValue(); + } else { + stop_info_sp->PerformAction(event_ptr); + // The stop action might restart the target. If it does, then we + // want to mark that in the event so that whoever is receiving it + // will know to wait for the running event and reflect that state + // appropriately. We also need to stop processing actions, since they + // aren't expecting the target to be running. + + // FIXME: we might have run. + if (stop_info_sp->HasTargetRunSinceMe()) { + SetRestarted(true); + break; + } + + this_thread_wants_to_stop = stop_info_sp->ShouldStop(event_ptr); + } + + if (!still_should_stop) + still_should_stop = this_thread_wants_to_stop; + } + } + + return still_should_stop; +} + +bool Process::ProcessEventData::ForwardEventToPendingListeners( + Event *event_ptr) { + // STDIO and the other async event notifications should always be forwarded. + if (event_ptr->GetType() != Process::eBroadcastBitStateChanged) + return true; + + // For state changed events, if the update state is zero, we are handling + // this on the private state thread. We should wait for the public event. + return m_update_state == 1; +} + +void Process::ProcessEventData::DoOnRemoval(Event *event_ptr) { + // We only have work to do for state changed events: + if (event_ptr->GetType() != Process::eBroadcastBitStateChanged) + return; + + ProcessSP process_sp(m_process_wp.lock()); + + if (!process_sp) + return; + + // This function gets called twice for each event, once when the event gets + // pulled off of the private process event queue, and then any number of + // times, first when it gets pulled off of the public event queue, then other + // times when we're pretending that this is where we stopped at the end of + // expression evaluation. m_update_state is used to distinguish these three + // cases; it is 0 when we're just pulling it off for private handling, and > + // 1 for expression evaluation, and we don't want to do the breakpoint + // command handling then. + if (m_update_state != 1) + return; + + process_sp->SetPublicState( + m_state, Process::ProcessEventData::GetRestartedFromEvent(event_ptr)); + + if (m_state == eStateStopped && !m_restarted) { + // Let process subclasses know we are about to do a public stop and do + // anything they might need to in order to speed up register and memory + // accesses. + process_sp->WillPublicStop(); + } + + // If this is a halt event, even if the halt stopped with some reason other + // than a plain interrupt (e.g. we had already stopped for a breakpoint when + // the halt request came through) don't do the StopInfo actions, as they may + // end up restarting the process. + if (m_interrupted) + return; + + // If we're not stopped or have restarted, then skip the StopInfo actions: + if (m_state != eStateStopped || m_restarted) { + return; + } + + bool does_anybody_have_an_opinion = false; + bool still_should_stop = ShouldStop(event_ptr, does_anybody_have_an_opinion); + + if (GetRestarted()) { + return; + } + + if (!still_should_stop && does_anybody_have_an_opinion) { + // We've been asked to continue, so do that here. + SetRestarted(true); + // Use the private resume method here, since we aren't changing the run + // lock state. + process_sp->PrivateResume(); + } else { + bool hijacked = process_sp->IsHijackedForEvent(eBroadcastBitStateChanged) && + !process_sp->StateChangedIsHijackedForSynchronousResume(); + + if (!hijacked) { + // If we didn't restart, run the Stop Hooks here. + // Don't do that if state changed events aren't hooked up to the + // public (or SyncResume) broadcasters. StopHooks are just for + // real public stops. They might also restart the target, + // so watch for that. + if (process_sp->GetTarget().RunStopHooks()) + SetRestarted(true); + } + } +} + +void Process::ProcessEventData::Dump(Stream *s) const { + ProcessSP process_sp(m_process_wp.lock()); + + if (process_sp) + s->Printf(" process = %p (pid = %" PRIu64 "), ", + static_cast<void *>(process_sp.get()), process_sp->GetID()); + else + s->PutCString(" process = NULL, "); + + s->Printf("state = %s", StateAsCString(GetState())); +} + +const Process::ProcessEventData * +Process::ProcessEventData::GetEventDataFromEvent(const Event *event_ptr) { + if (event_ptr) { + const EventData *event_data = event_ptr->GetData(); + if (event_data && + event_data->GetFlavor() == ProcessEventData::GetFlavorString()) + return static_cast<const ProcessEventData *>(event_ptr->GetData()); + } + return nullptr; +} + +ProcessSP +Process::ProcessEventData::GetProcessFromEvent(const Event *event_ptr) { + ProcessSP process_sp; + const ProcessEventData *data = GetEventDataFromEvent(event_ptr); + if (data) + process_sp = data->GetProcessSP(); + return process_sp; +} + +StateType Process::ProcessEventData::GetStateFromEvent(const Event *event_ptr) { + const ProcessEventData *data = GetEventDataFromEvent(event_ptr); + if (data == nullptr) + return eStateInvalid; + else + return data->GetState(); +} + +bool Process::ProcessEventData::GetRestartedFromEvent(const Event *event_ptr) { + const ProcessEventData *data = GetEventDataFromEvent(event_ptr); + if (data == nullptr) + return false; + else + return data->GetRestarted(); +} + +void Process::ProcessEventData::SetRestartedInEvent(Event *event_ptr, + bool new_value) { + ProcessEventData *data = + const_cast<ProcessEventData *>(GetEventDataFromEvent(event_ptr)); + if (data != nullptr) + data->SetRestarted(new_value); +} + +size_t +Process::ProcessEventData::GetNumRestartedReasons(const Event *event_ptr) { + ProcessEventData *data = + const_cast<ProcessEventData *>(GetEventDataFromEvent(event_ptr)); + if (data != nullptr) + return data->GetNumRestartedReasons(); + else + return 0; +} + +const char * +Process::ProcessEventData::GetRestartedReasonAtIndex(const Event *event_ptr, + size_t idx) { + ProcessEventData *data = + const_cast<ProcessEventData *>(GetEventDataFromEvent(event_ptr)); + if (data != nullptr) + return data->GetRestartedReasonAtIndex(idx); + else + return nullptr; +} + +void Process::ProcessEventData::AddRestartedReason(Event *event_ptr, + const char *reason) { + ProcessEventData *data = + const_cast<ProcessEventData *>(GetEventDataFromEvent(event_ptr)); + if (data != nullptr) + data->AddRestartedReason(reason); +} + +bool Process::ProcessEventData::GetInterruptedFromEvent( + const Event *event_ptr) { + const ProcessEventData *data = GetEventDataFromEvent(event_ptr); + if (data == nullptr) + return false; + else + return data->GetInterrupted(); +} + +void Process::ProcessEventData::SetInterruptedInEvent(Event *event_ptr, + bool new_value) { + ProcessEventData *data = + const_cast<ProcessEventData *>(GetEventDataFromEvent(event_ptr)); + if (data != nullptr) + data->SetInterrupted(new_value); +} + +bool Process::ProcessEventData::SetUpdateStateOnRemoval(Event *event_ptr) { + ProcessEventData *data = + const_cast<ProcessEventData *>(GetEventDataFromEvent(event_ptr)); + if (data) { + data->SetUpdateStateOnRemoval(); + return true; + } + return false; +} + +lldb::TargetSP Process::CalculateTarget() { return m_target_wp.lock(); } + +void Process::CalculateExecutionContext(ExecutionContext &exe_ctx) { + exe_ctx.SetTargetPtr(&GetTarget()); + exe_ctx.SetProcessPtr(this); + exe_ctx.SetThreadPtr(nullptr); + exe_ctx.SetFramePtr(nullptr); +} + +// uint32_t +// Process::ListProcessesMatchingName (const char *name, StringList &matches, +// std::vector<lldb::pid_t> &pids) +//{ +// return 0; +//} +// +// ArchSpec +// Process::GetArchSpecForExistingProcess (lldb::pid_t pid) +//{ +// return Host::GetArchSpecForExistingProcess (pid); +//} +// +// ArchSpec +// Process::GetArchSpecForExistingProcess (const char *process_name) +//{ +// return Host::GetArchSpecForExistingProcess (process_name); +//} + +EventSP Process::CreateEventFromProcessState(uint32_t event_type) { + auto event_data_sp = + std::make_shared<ProcessEventData>(shared_from_this(), GetState()); + return std::make_shared<Event>(event_type, event_data_sp); +} + +void Process::AppendSTDOUT(const char *s, size_t len) { + std::lock_guard<std::recursive_mutex> guard(m_stdio_communication_mutex); + m_stdout_data.append(s, len); + auto event_sp = CreateEventFromProcessState(eBroadcastBitSTDOUT); + BroadcastEventIfUnique(event_sp); +} + +void Process::AppendSTDERR(const char *s, size_t len) { + std::lock_guard<std::recursive_mutex> guard(m_stdio_communication_mutex); + m_stderr_data.append(s, len); + auto event_sp = CreateEventFromProcessState(eBroadcastBitSTDERR); + BroadcastEventIfUnique(event_sp); +} + +void Process::BroadcastAsyncProfileData(const std::string &one_profile_data) { + std::lock_guard<std::recursive_mutex> guard(m_profile_data_comm_mutex); + m_profile_data.push_back(one_profile_data); + auto event_sp = CreateEventFromProcessState(eBroadcastBitProfileData); + BroadcastEventIfUnique(event_sp); +} + +void Process::BroadcastStructuredData(const StructuredData::ObjectSP &object_sp, + const StructuredDataPluginSP &plugin_sp) { + auto data_sp = std::make_shared<EventDataStructuredData>( + shared_from_this(), object_sp, plugin_sp); + BroadcastEvent(eBroadcastBitStructuredData, data_sp); +} + +StructuredDataPluginSP +Process::GetStructuredDataPlugin(llvm::StringRef type_name) const { + auto find_it = m_structured_data_plugin_map.find(type_name); + if (find_it != m_structured_data_plugin_map.end()) + return find_it->second; + else + return StructuredDataPluginSP(); +} + +size_t Process::GetAsyncProfileData(char *buf, size_t buf_size, Status &error) { + std::lock_guard<std::recursive_mutex> guard(m_profile_data_comm_mutex); + if (m_profile_data.empty()) + return 0; + + std::string &one_profile_data = m_profile_data.front(); + size_t bytes_available = one_profile_data.size(); + if (bytes_available > 0) { + Log *log = GetLog(LLDBLog::Process); + LLDB_LOGF(log, "Process::GetProfileData (buf = %p, size = %" PRIu64 ")", + static_cast<void *>(buf), static_cast<uint64_t>(buf_size)); + if (bytes_available > buf_size) { + memcpy(buf, one_profile_data.c_str(), buf_size); + one_profile_data.erase(0, buf_size); + bytes_available = buf_size; + } else { + memcpy(buf, one_profile_data.c_str(), bytes_available); + m_profile_data.erase(m_profile_data.begin()); + } + } + return bytes_available; +} + +// Process STDIO + +size_t Process::GetSTDOUT(char *buf, size_t buf_size, Status &error) { + std::lock_guard<std::recursive_mutex> guard(m_stdio_communication_mutex); + size_t bytes_available = m_stdout_data.size(); + if (bytes_available > 0) { + Log *log = GetLog(LLDBLog::Process); + LLDB_LOGF(log, "Process::GetSTDOUT (buf = %p, size = %" PRIu64 ")", + static_cast<void *>(buf), static_cast<uint64_t>(buf_size)); + if (bytes_available > buf_size) { + memcpy(buf, m_stdout_data.c_str(), buf_size); + m_stdout_data.erase(0, buf_size); + bytes_available = buf_size; + } else { + memcpy(buf, m_stdout_data.c_str(), bytes_available); + m_stdout_data.clear(); + } + } + return bytes_available; +} + +size_t Process::GetSTDERR(char *buf, size_t buf_size, Status &error) { + std::lock_guard<std::recursive_mutex> gaurd(m_stdio_communication_mutex); + size_t bytes_available = m_stderr_data.size(); + if (bytes_available > 0) { + Log *log = GetLog(LLDBLog::Process); + LLDB_LOGF(log, "Process::GetSTDERR (buf = %p, size = %" PRIu64 ")", + static_cast<void *>(buf), static_cast<uint64_t>(buf_size)); + if (bytes_available > buf_size) { + memcpy(buf, m_stderr_data.c_str(), buf_size); + m_stderr_data.erase(0, buf_size); + bytes_available = buf_size; + } else { + memcpy(buf, m_stderr_data.c_str(), bytes_available); + m_stderr_data.clear(); + } + } + return bytes_available; +} + +void Process::STDIOReadThreadBytesReceived(void *baton, const void *src, + size_t src_len) { + Process *process = (Process *)baton; + process->AppendSTDOUT(static_cast<const char *>(src), src_len); +} + +class IOHandlerProcessSTDIO : public IOHandler { +public: + IOHandlerProcessSTDIO(Process *process, int write_fd) + : IOHandler(process->GetTarget().GetDebugger(), + IOHandler::Type::ProcessIO), + m_process(process), + m_read_file(GetInputFD(), File::eOpenOptionReadOnly, false), + m_write_file(write_fd, File::eOpenOptionWriteOnly, false) { + m_pipe.CreateNew(false); + } + + ~IOHandlerProcessSTDIO() override = default; + + void SetIsRunning(bool running) { + std::lock_guard<std::mutex> guard(m_mutex); + SetIsDone(!running); + m_is_running = running; + } + + // Each IOHandler gets to run until it is done. It should read data from the + // "in" and place output into "out" and "err and return when done. + void Run() override { + if (!m_read_file.IsValid() || !m_write_file.IsValid() || + !m_pipe.CanRead() || !m_pipe.CanWrite()) { + SetIsDone(true); + return; + } + + SetIsDone(false); + const int read_fd = m_read_file.GetDescriptor(); + Terminal terminal(read_fd); + TerminalState terminal_state(terminal, false); + // FIXME: error handling? + llvm::consumeError(terminal.SetCanonical(false)); + llvm::consumeError(terminal.SetEcho(false)); +// FD_ZERO, FD_SET are not supported on windows +#ifndef _WIN32 + const int pipe_read_fd = m_pipe.GetReadFileDescriptor(); + SetIsRunning(true); + while (true) { + { + std::lock_guard<std::mutex> guard(m_mutex); + if (GetIsDone()) + break; + } + + SelectHelper select_helper; + select_helper.FDSetRead(read_fd); + select_helper.FDSetRead(pipe_read_fd); + Status error = select_helper.Select(); + + if (error.Fail()) + break; + + char ch = 0; + size_t n; + if (select_helper.FDIsSetRead(read_fd)) { + n = 1; + if (m_read_file.Read(&ch, n).Success() && n == 1) { + if (m_write_file.Write(&ch, n).Fail() || n != 1) + break; + } else + break; + } + + if (select_helper.FDIsSetRead(pipe_read_fd)) { + size_t bytes_read; + // Consume the interrupt byte + Status error = m_pipe.Read(&ch, 1, bytes_read); + if (error.Success()) { + if (ch == 'q') + break; + if (ch == 'i') + if (StateIsRunningState(m_process->GetState())) + m_process->SendAsyncInterrupt(); + } + } + } + SetIsRunning(false); +#endif + } + + void Cancel() override { + std::lock_guard<std::mutex> guard(m_mutex); + SetIsDone(true); + // Only write to our pipe to cancel if we are in + // IOHandlerProcessSTDIO::Run(). We can end up with a python command that + // is being run from the command interpreter: + // + // (lldb) step_process_thousands_of_times + // + // In this case the command interpreter will be in the middle of handling + // the command and if the process pushes and pops the IOHandler thousands + // of times, we can end up writing to m_pipe without ever consuming the + // bytes from the pipe in IOHandlerProcessSTDIO::Run() and end up + // deadlocking when the pipe gets fed up and blocks until data is consumed. + if (m_is_running) { + char ch = 'q'; // Send 'q' for quit + size_t bytes_written = 0; + m_pipe.Write(&ch, 1, bytes_written); + } + } + + bool Interrupt() override { + // Do only things that are safe to do in an interrupt context (like in a + // SIGINT handler), like write 1 byte to a file descriptor. This will + // interrupt the IOHandlerProcessSTDIO::Run() and we can look at the byte + // that was written to the pipe and then call + // m_process->SendAsyncInterrupt() from a much safer location in code. + if (m_active) { + char ch = 'i'; // Send 'i' for interrupt + size_t bytes_written = 0; + Status result = m_pipe.Write(&ch, 1, bytes_written); + return result.Success(); + } else { + // This IOHandler might be pushed on the stack, but not being run + // currently so do the right thing if we aren't actively watching for + // STDIN by sending the interrupt to the process. Otherwise the write to + // the pipe above would do nothing. This can happen when the command + // interpreter is running and gets a "expression ...". It will be on the + // IOHandler thread and sending the input is complete to the delegate + // which will cause the expression to run, which will push the process IO + // handler, but not run it. + + if (StateIsRunningState(m_process->GetState())) { + m_process->SendAsyncInterrupt(); + return true; + } + } + return false; + } + + void GotEOF() override {} + +protected: + Process *m_process; + NativeFile m_read_file; // Read from this file (usually actual STDIN for LLDB + NativeFile m_write_file; // Write to this file (usually the primary pty for + // getting io to debuggee) + Pipe m_pipe; + std::mutex m_mutex; + bool m_is_running = false; +}; + +void Process::SetSTDIOFileDescriptor(int fd) { + // First set up the Read Thread for reading/handling process I/O + m_stdio_communication.SetConnection( + std::make_unique<ConnectionFileDescriptor>(fd, true)); + if (m_stdio_communication.IsConnected()) { + m_stdio_communication.SetReadThreadBytesReceivedCallback( + STDIOReadThreadBytesReceived, this); + m_stdio_communication.StartReadThread(); + + // Now read thread is set up, set up input reader. + { + std::lock_guard<std::mutex> guard(m_process_input_reader_mutex); + if (!m_process_input_reader) + m_process_input_reader = + std::make_shared<IOHandlerProcessSTDIO>(this, fd); + } + } +} + +bool Process::ProcessIOHandlerIsActive() { + std::lock_guard<std::mutex> guard(m_process_input_reader_mutex); + IOHandlerSP io_handler_sp(m_process_input_reader); + if (io_handler_sp) + return GetTarget().GetDebugger().IsTopIOHandler(io_handler_sp); + return false; +} + +bool Process::PushProcessIOHandler() { + std::lock_guard<std::mutex> guard(m_process_input_reader_mutex); + IOHandlerSP io_handler_sp(m_process_input_reader); + if (io_handler_sp) { + Log *log = GetLog(LLDBLog::Process); + LLDB_LOGF(log, "Process::%s pushing IO handler", __FUNCTION__); + + io_handler_sp->SetIsDone(false); + // If we evaluate an utility function, then we don't cancel the current + // IOHandler. Our IOHandler is non-interactive and shouldn't disturb the + // existing IOHandler that potentially provides the user interface (e.g. + // the IOHandler for Editline). + bool cancel_top_handler = !m_mod_id.IsRunningUtilityFunction(); + GetTarget().GetDebugger().RunIOHandlerAsync(io_handler_sp, + cancel_top_handler); + return true; + } + return false; +} + +bool Process::PopProcessIOHandler() { + std::lock_guard<std::mutex> guard(m_process_input_reader_mutex); + IOHandlerSP io_handler_sp(m_process_input_reader); + if (io_handler_sp) + return GetTarget().GetDebugger().RemoveIOHandler(io_handler_sp); + return false; +} + +// The process needs to know about installed plug-ins +void Process::SettingsInitialize() { Thread::SettingsInitialize(); } + +void Process::SettingsTerminate() { Thread::SettingsTerminate(); } + +namespace { +// RestorePlanState is used to record the "is private", "is controlling" and +// "okay +// to discard" fields of the plan we are running, and reset it on Clean or on +// destruction. It will only reset the state once, so you can call Clean and +// then monkey with the state and it won't get reset on you again. + +class RestorePlanState { +public: + RestorePlanState(lldb::ThreadPlanSP thread_plan_sp) + : m_thread_plan_sp(thread_plan_sp) { + if (m_thread_plan_sp) { + m_private = m_thread_plan_sp->GetPrivate(); + m_is_controlling = m_thread_plan_sp->IsControllingPlan(); + m_okay_to_discard = m_thread_plan_sp->OkayToDiscard(); + } + } + + ~RestorePlanState() { Clean(); } + + void Clean() { + if (!m_already_reset && m_thread_plan_sp) { + m_already_reset = true; + m_thread_plan_sp->SetPrivate(m_private); + m_thread_plan_sp->SetIsControllingPlan(m_is_controlling); + m_thread_plan_sp->SetOkayToDiscard(m_okay_to_discard); + } + } + +private: + lldb::ThreadPlanSP m_thread_plan_sp; + bool m_already_reset = false; + bool m_private = false; + bool m_is_controlling = false; + bool m_okay_to_discard = false; +}; +} // anonymous namespace + +static microseconds +GetOneThreadExpressionTimeout(const EvaluateExpressionOptions &options) { + const milliseconds default_one_thread_timeout(250); + + // If the overall wait is forever, then we don't need to worry about it. + if (!options.GetTimeout()) { + return options.GetOneThreadTimeout() ? *options.GetOneThreadTimeout() + : default_one_thread_timeout; + } + + // If the one thread timeout is set, use it. + if (options.GetOneThreadTimeout()) + return *options.GetOneThreadTimeout(); + + // Otherwise use half the total timeout, bounded by the + // default_one_thread_timeout. + return std::min<microseconds>(default_one_thread_timeout, + *options.GetTimeout() / 2); +} + +static Timeout<std::micro> +GetExpressionTimeout(const EvaluateExpressionOptions &options, + bool before_first_timeout) { + // If we are going to run all threads the whole time, or if we are only going + // to run one thread, we can just return the overall timeout. + if (!options.GetStopOthers() || !options.GetTryAllThreads()) + return options.GetTimeout(); + + if (before_first_timeout) + return GetOneThreadExpressionTimeout(options); + + if (!options.GetTimeout()) + return std::nullopt; + else + return *options.GetTimeout() - GetOneThreadExpressionTimeout(options); +} + +static std::optional<ExpressionResults> +HandleStoppedEvent(lldb::tid_t thread_id, const ThreadPlanSP &thread_plan_sp, + RestorePlanState &restorer, const EventSP &event_sp, + EventSP &event_to_broadcast_sp, + const EvaluateExpressionOptions &options, + bool handle_interrupts) { + Log *log = GetLog(LLDBLog::Step | LLDBLog::Process); + + ThreadSP thread_sp = thread_plan_sp->GetTarget() + .GetProcessSP() + ->GetThreadList() + .FindThreadByID(thread_id); + if (!thread_sp) { + LLDB_LOG(log, + "The thread on which we were running the " + "expression: tid = {0}, exited while " + "the expression was running.", + thread_id); + return eExpressionThreadVanished; + } + + ThreadPlanSP plan = thread_sp->GetCompletedPlan(); + if (plan == thread_plan_sp && plan->PlanSucceeded()) { + LLDB_LOG(log, "execution completed successfully"); + + // Restore the plan state so it will get reported as intended when we are + // done. + restorer.Clean(); + return eExpressionCompleted; + } + + StopInfoSP stop_info_sp = thread_sp->GetStopInfo(); + if (stop_info_sp && stop_info_sp->GetStopReason() == eStopReasonBreakpoint && + stop_info_sp->ShouldNotify(event_sp.get())) { + LLDB_LOG(log, "stopped for breakpoint: {0}.", stop_info_sp->GetDescription()); + if (!options.DoesIgnoreBreakpoints()) { + // Restore the plan state and then force Private to false. We are going + // to stop because of this plan so we need it to become a public plan or + // it won't report correctly when we continue to its termination later + // on. + restorer.Clean(); + thread_plan_sp->SetPrivate(false); + event_to_broadcast_sp = event_sp; + } + return eExpressionHitBreakpoint; + } + + if (!handle_interrupts && + Process::ProcessEventData::GetInterruptedFromEvent(event_sp.get())) + return std::nullopt; + + LLDB_LOG(log, "thread plan did not successfully complete"); + if (!options.DoesUnwindOnError()) + event_to_broadcast_sp = event_sp; + return eExpressionInterrupted; +} + +ExpressionResults +Process::RunThreadPlan(ExecutionContext &exe_ctx, + lldb::ThreadPlanSP &thread_plan_sp, + const EvaluateExpressionOptions &options, + DiagnosticManager &diagnostic_manager) { + ExpressionResults return_value = eExpressionSetupError; + + std::lock_guard<std::mutex> run_thread_plan_locker(m_run_thread_plan_lock); + + if (!thread_plan_sp) { + diagnostic_manager.PutString( + lldb::eSeverityError, "RunThreadPlan called with empty thread plan."); + return eExpressionSetupError; + } + + if (!thread_plan_sp->ValidatePlan(nullptr)) { + diagnostic_manager.PutString( + lldb::eSeverityError, + "RunThreadPlan called with an invalid thread plan."); + return eExpressionSetupError; + } + + if (exe_ctx.GetProcessPtr() != this) { + diagnostic_manager.PutString(lldb::eSeverityError, + "RunThreadPlan called on wrong process."); + return eExpressionSetupError; + } + + Thread *thread = exe_ctx.GetThreadPtr(); + if (thread == nullptr) { + diagnostic_manager.PutString(lldb::eSeverityError, + "RunThreadPlan called with invalid thread."); + return eExpressionSetupError; + } + + // Record the thread's id so we can tell when a thread we were using + // to run the expression exits during the expression evaluation. + lldb::tid_t expr_thread_id = thread->GetID(); + + // We need to change some of the thread plan attributes for the thread plan + // runner. This will restore them when we are done: + + RestorePlanState thread_plan_restorer(thread_plan_sp); + + // We rely on the thread plan we are running returning "PlanCompleted" if + // when it successfully completes. For that to be true the plan can't be + // private - since private plans suppress themselves in the GetCompletedPlan + // call. + + thread_plan_sp->SetPrivate(false); + + // The plans run with RunThreadPlan also need to be terminal controlling plans + // or when they are done we will end up asking the plan above us whether we + // should stop, which may give the wrong answer. + + thread_plan_sp->SetIsControllingPlan(true); + thread_plan_sp->SetOkayToDiscard(false); + + // If we are running some utility expression for LLDB, we now have to mark + // this in the ProcesModID of this process. This RAII takes care of marking + // and reverting the mark it once we are done running the expression. + UtilityFunctionScope util_scope(options.IsForUtilityExpr() ? this : nullptr); + + if (m_private_state.GetValue() != eStateStopped) { + diagnostic_manager.PutString( + lldb::eSeverityError, + "RunThreadPlan called while the private state was not stopped."); + return eExpressionSetupError; + } + + // Save the thread & frame from the exe_ctx for restoration after we run + const uint32_t thread_idx_id = thread->GetIndexID(); + StackFrameSP selected_frame_sp = + thread->GetSelectedFrame(DoNoSelectMostRelevantFrame); + if (!selected_frame_sp) { + thread->SetSelectedFrame(nullptr); + selected_frame_sp = thread->GetSelectedFrame(DoNoSelectMostRelevantFrame); + if (!selected_frame_sp) { + diagnostic_manager.Printf( + lldb::eSeverityError, + "RunThreadPlan called without a selected frame on thread %d", + thread_idx_id); + return eExpressionSetupError; + } + } + + // Make sure the timeout values make sense. The one thread timeout needs to + // be smaller than the overall timeout. + if (options.GetOneThreadTimeout() && options.GetTimeout() && + *options.GetTimeout() < *options.GetOneThreadTimeout()) { + diagnostic_manager.PutString(lldb::eSeverityError, + "RunThreadPlan called with one thread " + "timeout greater than total timeout"); + return eExpressionSetupError; + } + + StackID ctx_frame_id = selected_frame_sp->GetStackID(); + + // N.B. Running the target may unset the currently selected thread and frame. + // We don't want to do that either, so we should arrange to reset them as + // well. + + lldb::ThreadSP selected_thread_sp = GetThreadList().GetSelectedThread(); + + uint32_t selected_tid; + StackID selected_stack_id; + if (selected_thread_sp) { + selected_tid = selected_thread_sp->GetIndexID(); + selected_stack_id = + selected_thread_sp->GetSelectedFrame(DoNoSelectMostRelevantFrame) + ->GetStackID(); + } else { + selected_tid = LLDB_INVALID_THREAD_ID; + } + + HostThread backup_private_state_thread; + lldb::StateType old_state = eStateInvalid; + lldb::ThreadPlanSP stopper_base_plan_sp; + + Log *log(GetLog(LLDBLog::Step | LLDBLog::Process)); + if (m_private_state_thread.EqualsThread(Host::GetCurrentThread())) { + // Yikes, we are running on the private state thread! So we can't wait for + // public events on this thread, since we are the thread that is generating + // public events. The simplest thing to do is to spin up a temporary thread + // to handle private state thread events while we are fielding public + // events here. + LLDB_LOGF(log, "Running thread plan on private state thread, spinning up " + "another state thread to handle the events."); + + backup_private_state_thread = m_private_state_thread; + + // One other bit of business: we want to run just this thread plan and + // anything it pushes, and then stop, returning control here. But in the + // normal course of things, the plan above us on the stack would be given a + // shot at the stop event before deciding to stop, and we don't want that. + // So we insert a "stopper" base plan on the stack before the plan we want + // to run. Since base plans always stop and return control to the user, + // that will do just what we want. + stopper_base_plan_sp.reset(new ThreadPlanBase(*thread)); + thread->QueueThreadPlan(stopper_base_plan_sp, false); + // Have to make sure our public state is stopped, since otherwise the + // reporting logic below doesn't work correctly. + old_state = m_public_state.GetValue(); + m_public_state.SetValueNoLock(eStateStopped); + + // Now spin up the private state thread: + StartPrivateStateThread(true); + } + + thread->QueueThreadPlan( + thread_plan_sp, false); // This used to pass "true" does that make sense? + + if (options.GetDebug()) { + // In this case, we aren't actually going to run, we just want to stop + // right away. Flush this thread so we will refetch the stacks and show the + // correct backtrace. + // FIXME: To make this prettier we should invent some stop reason for this, + // but that + // is only cosmetic, and this functionality is only of use to lldb + // developers who can live with not pretty... + thread->Flush(); + return eExpressionStoppedForDebug; + } + + ListenerSP listener_sp( + Listener::MakeListener("lldb.process.listener.run-thread-plan")); + + lldb::EventSP event_to_broadcast_sp; + + { + // This process event hijacker Hijacks the Public events and its destructor + // makes sure that the process events get restored on exit to the function. + // + // If the event needs to propagate beyond the hijacker (e.g., the process + // exits during execution), then the event is put into + // event_to_broadcast_sp for rebroadcasting. + + ProcessEventHijacker run_thread_plan_hijacker(*this, listener_sp); + + if (log) { + StreamString s; + thread_plan_sp->GetDescription(&s, lldb::eDescriptionLevelVerbose); + LLDB_LOGF(log, + "Process::RunThreadPlan(): Resuming thread %u - 0x%4.4" PRIx64 + " to run thread plan \"%s\".", + thread_idx_id, expr_thread_id, s.GetData()); + } + + bool got_event; + lldb::EventSP event_sp; + lldb::StateType stop_state = lldb::eStateInvalid; + + bool before_first_timeout = true; // This is set to false the first time + // that we have to halt the target. + bool do_resume = true; + bool handle_running_event = true; + + // This is just for accounting: + uint32_t num_resumes = 0; + + // If we are going to run all threads the whole time, or if we are only + // going to run one thread, then we don't need the first timeout. So we + // pretend we are after the first timeout already. + if (!options.GetStopOthers() || !options.GetTryAllThreads()) + before_first_timeout = false; + + LLDB_LOGF(log, "Stop others: %u, try all: %u, before_first: %u.\n", + options.GetStopOthers(), options.GetTryAllThreads(), + before_first_timeout); + + // This isn't going to work if there are unfetched events on the queue. Are + // there cases where we might want to run the remaining events here, and + // then try to call the function? That's probably being too tricky for our + // own good. + + Event *other_events = listener_sp->PeekAtNextEvent(); + if (other_events != nullptr) { + diagnostic_manager.PutString( + lldb::eSeverityError, + "RunThreadPlan called with pending events on the queue."); + return eExpressionSetupError; + } + + // We also need to make sure that the next event is delivered. We might be + // calling a function as part of a thread plan, in which case the last + // delivered event could be the running event, and we don't want event + // coalescing to cause us to lose OUR running event... + ForceNextEventDelivery(); + +// This while loop must exit out the bottom, there's cleanup that we need to do +// when we are done. So don't call return anywhere within it. + +#ifdef LLDB_RUN_THREAD_HALT_WITH_EVENT + // It's pretty much impossible to write test cases for things like: One + // thread timeout expires, I go to halt, but the process already stopped on + // the function call stop breakpoint. Turning on this define will make us + // not fetch the first event till after the halt. So if you run a quick + // function, it will have completed, and the completion event will be + // waiting, when you interrupt for halt. The expression evaluation should + // still succeed. + bool miss_first_event = true; +#endif + while (true) { + // We usually want to resume the process if we get to the top of the + // loop. The only exception is if we get two running events with no + // intervening stop, which can happen, we will just wait for then next + // stop event. + LLDB_LOGF(log, + "Top of while loop: do_resume: %i handle_running_event: %i " + "before_first_timeout: %i.", + do_resume, handle_running_event, before_first_timeout); + + if (do_resume || handle_running_event) { + // Do the initial resume and wait for the running event before going + // further. + + if (do_resume) { + num_resumes++; + Status resume_error = PrivateResume(); + if (!resume_error.Success()) { + diagnostic_manager.Printf( + lldb::eSeverityError, + "couldn't resume inferior the %d time: \"%s\".", num_resumes, + resume_error.AsCString()); + return_value = eExpressionSetupError; + break; + } + } + + got_event = + listener_sp->GetEvent(event_sp, GetUtilityExpressionTimeout()); + if (!got_event) { + LLDB_LOGF(log, + "Process::RunThreadPlan(): didn't get any event after " + "resume %" PRIu32 ", exiting.", + num_resumes); + + diagnostic_manager.Printf(lldb::eSeverityError, + "didn't get any event after resume %" PRIu32 + ", exiting.", + num_resumes); + return_value = eExpressionSetupError; + break; + } + + stop_state = + Process::ProcessEventData::GetStateFromEvent(event_sp.get()); + + if (stop_state != eStateRunning) { + bool restarted = false; + + if (stop_state == eStateStopped) { + restarted = Process::ProcessEventData::GetRestartedFromEvent( + event_sp.get()); + LLDB_LOGF( + log, + "Process::RunThreadPlan(): didn't get running event after " + "resume %d, got %s instead (restarted: %i, do_resume: %i, " + "handle_running_event: %i).", + num_resumes, StateAsCString(stop_state), restarted, do_resume, + handle_running_event); + } + + if (restarted) { + // This is probably an overabundance of caution, I don't think I + // should ever get a stopped & restarted event here. But if I do, + // the best thing is to Halt and then get out of here. + const bool clear_thread_plans = false; + const bool use_run_lock = false; + Halt(clear_thread_plans, use_run_lock); + } + + diagnostic_manager.Printf( + lldb::eSeverityError, + "didn't get running event after initial resume, got %s instead.", + StateAsCString(stop_state)); + return_value = eExpressionSetupError; + break; + } + + if (log) + log->PutCString("Process::RunThreadPlan(): resuming succeeded."); + // We need to call the function synchronously, so spin waiting for it + // to return. If we get interrupted while executing, we're going to + // lose our context, and won't be able to gather the result at this + // point. We set the timeout AFTER the resume, since the resume takes + // some time and we don't want to charge that to the timeout. + } else { + if (log) + log->PutCString("Process::RunThreadPlan(): waiting for next event."); + } + + do_resume = true; + handle_running_event = true; + + // Now wait for the process to stop again: + event_sp.reset(); + + Timeout<std::micro> timeout = + GetExpressionTimeout(options, before_first_timeout); + if (log) { + if (timeout) { + auto now = system_clock::now(); + LLDB_LOGF(log, + "Process::RunThreadPlan(): about to wait - now is %s - " + "endpoint is %s", + llvm::to_string(now).c_str(), + llvm::to_string(now + *timeout).c_str()); + } else { + LLDB_LOGF(log, "Process::RunThreadPlan(): about to wait forever."); + } + } + +#ifdef LLDB_RUN_THREAD_HALT_WITH_EVENT + // See comment above... + if (miss_first_event) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + miss_first_event = false; + got_event = false; + } else +#endif + got_event = listener_sp->GetEvent(event_sp, timeout); + + if (got_event) { + if (event_sp) { + bool keep_going = false; + if (event_sp->GetType() == eBroadcastBitInterrupt) { + const bool clear_thread_plans = false; + const bool use_run_lock = false; + Halt(clear_thread_plans, use_run_lock); + return_value = eExpressionInterrupted; + diagnostic_manager.PutString(lldb::eSeverityInfo, + "execution halted by user interrupt."); + LLDB_LOGF(log, "Process::RunThreadPlan(): Got interrupted by " + "eBroadcastBitInterrupted, exiting."); + break; + } else { + stop_state = + Process::ProcessEventData::GetStateFromEvent(event_sp.get()); + LLDB_LOGF(log, + "Process::RunThreadPlan(): in while loop, got event: %s.", + StateAsCString(stop_state)); + + switch (stop_state) { + case lldb::eStateStopped: { + if (Process::ProcessEventData::GetRestartedFromEvent( + event_sp.get())) { + // If we were restarted, we just need to go back up to fetch + // another event. + LLDB_LOGF(log, "Process::RunThreadPlan(): Got a stop and " + "restart, so we'll continue waiting."); + keep_going = true; + do_resume = false; + handle_running_event = true; + } else { + const bool handle_interrupts = true; + return_value = *HandleStoppedEvent( + expr_thread_id, thread_plan_sp, thread_plan_restorer, + event_sp, event_to_broadcast_sp, options, + handle_interrupts); + if (return_value == eExpressionThreadVanished) + keep_going = false; + } + } break; + + case lldb::eStateRunning: + // This shouldn't really happen, but sometimes we do get two + // running events without an intervening stop, and in that case + // we should just go back to waiting for the stop. + do_resume = false; + keep_going = true; + handle_running_event = false; + break; + + default: + LLDB_LOGF(log, + "Process::RunThreadPlan(): execution stopped with " + "unexpected state: %s.", + StateAsCString(stop_state)); + + if (stop_state == eStateExited) + event_to_broadcast_sp = event_sp; + + diagnostic_manager.PutString( + lldb::eSeverityError, + "execution stopped with unexpected state."); + return_value = eExpressionInterrupted; + break; + } + } + + if (keep_going) + continue; + else + break; + } else { + if (log) + log->PutCString("Process::RunThreadPlan(): got_event was true, but " + "the event pointer was null. How odd..."); + return_value = eExpressionInterrupted; + break; + } + } else { + // If we didn't get an event that means we've timed out... We will + // interrupt the process here. Depending on what we were asked to do + // we will either exit, or try with all threads running for the same + // timeout. + + if (log) { + if (options.GetTryAllThreads()) { + if (before_first_timeout) { + LLDB_LOG(log, + "Running function with one thread timeout timed out."); + } else + LLDB_LOG(log, "Restarting function with all threads enabled and " + "timeout: {0} timed out, abandoning execution.", + timeout); + } else + LLDB_LOG(log, "Running function with timeout: {0} timed out, " + "abandoning execution.", + timeout); + } + + // It is possible that between the time we issued the Halt, and we get + // around to calling Halt the target could have stopped. That's fine, + // Halt will figure that out and send the appropriate Stopped event. + // BUT it is also possible that we stopped & restarted (e.g. hit a + // signal with "stop" set to false.) In + // that case, we'll get the stopped & restarted event, and we should go + // back to waiting for the Halt's stopped event. That's what this + // while loop does. + + bool back_to_top = true; + uint32_t try_halt_again = 0; + bool do_halt = true; + const uint32_t num_retries = 5; + while (try_halt_again < num_retries) { + Status halt_error; + if (do_halt) { + LLDB_LOGF(log, "Process::RunThreadPlan(): Running Halt."); + const bool clear_thread_plans = false; + const bool use_run_lock = false; + Halt(clear_thread_plans, use_run_lock); + } + if (halt_error.Success()) { + if (log) + log->PutCString("Process::RunThreadPlan(): Halt succeeded."); + + got_event = + listener_sp->GetEvent(event_sp, GetUtilityExpressionTimeout()); + + if (got_event) { + stop_state = + Process::ProcessEventData::GetStateFromEvent(event_sp.get()); + if (log) { + LLDB_LOGF(log, + "Process::RunThreadPlan(): Stopped with event: %s", + StateAsCString(stop_state)); + if (stop_state == lldb::eStateStopped && + Process::ProcessEventData::GetInterruptedFromEvent( + event_sp.get())) + log->PutCString(" Event was the Halt interruption event."); + } + + if (stop_state == lldb::eStateStopped) { + if (Process::ProcessEventData::GetRestartedFromEvent( + event_sp.get())) { + if (log) + log->PutCString("Process::RunThreadPlan(): Went to halt " + "but got a restarted event, there must be " + "an un-restarted stopped event so try " + "again... " + "Exiting wait loop."); + try_halt_again++; + do_halt = false; + continue; + } + + // Between the time we initiated the Halt and the time we + // delivered it, the process could have already finished its + // job. Check that here: + const bool handle_interrupts = false; + if (auto result = HandleStoppedEvent( + expr_thread_id, thread_plan_sp, thread_plan_restorer, + event_sp, event_to_broadcast_sp, options, + handle_interrupts)) { + return_value = *result; + back_to_top = false; + break; + } + + if (!options.GetTryAllThreads()) { + if (log) + log->PutCString("Process::RunThreadPlan(): try_all_threads " + "was false, we stopped so now we're " + "quitting."); + return_value = eExpressionInterrupted; + back_to_top = false; + break; + } + + if (before_first_timeout) { + // Set all the other threads to run, and return to the top of + // the loop, which will continue; + before_first_timeout = false; + thread_plan_sp->SetStopOthers(false); + if (log) + log->PutCString( + "Process::RunThreadPlan(): about to resume."); + + back_to_top = true; + break; + } else { + // Running all threads failed, so return Interrupted. + if (log) + log->PutCString("Process::RunThreadPlan(): running all " + "threads timed out."); + return_value = eExpressionInterrupted; + back_to_top = false; + break; + } + } + } else { + if (log) + log->PutCString("Process::RunThreadPlan(): halt said it " + "succeeded, but I got no event. " + "I'm getting out of here passing Interrupted."); + return_value = eExpressionInterrupted; + back_to_top = false; + break; + } + } else { + try_halt_again++; + continue; + } + } + + if (!back_to_top || try_halt_again > num_retries) + break; + else + continue; + } + } // END WAIT LOOP + + // If we had to start up a temporary private state thread to run this + // thread plan, shut it down now. + if (backup_private_state_thread.IsJoinable()) { + StopPrivateStateThread(); + Status error; + m_private_state_thread = backup_private_state_thread; + if (stopper_base_plan_sp) { + thread->DiscardThreadPlansUpToPlan(stopper_base_plan_sp); + } + if (old_state != eStateInvalid) + m_public_state.SetValueNoLock(old_state); + } + + // If our thread went away on us, we need to get out of here without + // doing any more work. We don't have to clean up the thread plan, that + // will have happened when the Thread was destroyed. + if (return_value == eExpressionThreadVanished) { + return return_value; + } + + if (return_value != eExpressionCompleted && log) { + // Print a backtrace into the log so we can figure out where we are: + StreamString s; + s.PutCString("Thread state after unsuccessful completion: \n"); + thread->GetStackFrameStatus(s, 0, UINT32_MAX, true, UINT32_MAX); + log->PutString(s.GetString()); + } + // Restore the thread state if we are going to discard the plan execution. + // There are three cases where this could happen: 1) The execution + // successfully completed 2) We hit a breakpoint, and ignore_breakpoints + // was true 3) We got some other error, and discard_on_error was true + bool should_unwind = (return_value == eExpressionInterrupted && + options.DoesUnwindOnError()) || + (return_value == eExpressionHitBreakpoint && + options.DoesIgnoreBreakpoints()); + + if (return_value == eExpressionCompleted || should_unwind) { + thread_plan_sp->RestoreThreadState(); + } + + // Now do some processing on the results of the run: + if (return_value == eExpressionInterrupted || + return_value == eExpressionHitBreakpoint) { + if (log) { + StreamString s; + if (event_sp) + event_sp->Dump(&s); + else { + log->PutCString("Process::RunThreadPlan(): Stop event that " + "interrupted us is NULL."); + } + + StreamString ts; + + const char *event_explanation = nullptr; + + do { + if (!event_sp) { + event_explanation = "<no event>"; + break; + } else if (event_sp->GetType() == eBroadcastBitInterrupt) { + event_explanation = "<user interrupt>"; + break; + } else { + const Process::ProcessEventData *event_data = + Process::ProcessEventData::GetEventDataFromEvent( + event_sp.get()); + + if (!event_data) { + event_explanation = "<no event data>"; + break; + } + + Process *process = event_data->GetProcessSP().get(); + + if (!process) { + event_explanation = "<no process>"; + break; + } + + ThreadList &thread_list = process->GetThreadList(); + + uint32_t num_threads = thread_list.GetSize(); + uint32_t thread_index; + + ts.Printf("<%u threads> ", num_threads); + + for (thread_index = 0; thread_index < num_threads; ++thread_index) { + Thread *thread = thread_list.GetThreadAtIndex(thread_index).get(); + + if (!thread) { + ts.Printf("<?> "); + continue; + } + + ts.Printf("<0x%4.4" PRIx64 " ", thread->GetID()); + RegisterContext *register_context = + thread->GetRegisterContext().get(); + + if (register_context) + ts.Printf("[ip 0x%" PRIx64 "] ", register_context->GetPC()); + else + ts.Printf("[ip unknown] "); + + // Show the private stop info here, the public stop info will be + // from the last natural stop. + lldb::StopInfoSP stop_info_sp = thread->GetPrivateStopInfo(); + if (stop_info_sp) { + const char *stop_desc = stop_info_sp->GetDescription(); + if (stop_desc) + ts.PutCString(stop_desc); + } + ts.Printf(">"); + } + + event_explanation = ts.GetData(); + } + } while (false); + + if (event_explanation) + LLDB_LOGF(log, + "Process::RunThreadPlan(): execution interrupted: %s %s", + s.GetData(), event_explanation); + else + LLDB_LOGF(log, "Process::RunThreadPlan(): execution interrupted: %s", + s.GetData()); + } + + if (should_unwind) { + LLDB_LOGF(log, + "Process::RunThreadPlan: ExecutionInterrupted - " + "discarding thread plans up to %p.", + static_cast<void *>(thread_plan_sp.get())); + thread->DiscardThreadPlansUpToPlan(thread_plan_sp); + } else { + LLDB_LOGF(log, + "Process::RunThreadPlan: ExecutionInterrupted - for " + "plan: %p not discarding.", + static_cast<void *>(thread_plan_sp.get())); + } + } else if (return_value == eExpressionSetupError) { + if (log) + log->PutCString("Process::RunThreadPlan(): execution set up error."); + + if (options.DoesUnwindOnError()) { + thread->DiscardThreadPlansUpToPlan(thread_plan_sp); + } + } else { + if (thread->IsThreadPlanDone(thread_plan_sp.get())) { + if (log) + log->PutCString("Process::RunThreadPlan(): thread plan is done"); + return_value = eExpressionCompleted; + } else if (thread->WasThreadPlanDiscarded(thread_plan_sp.get())) { + if (log) + log->PutCString( + "Process::RunThreadPlan(): thread plan was discarded"); + return_value = eExpressionDiscarded; + } else { + if (log) + log->PutCString( + "Process::RunThreadPlan(): thread plan stopped in mid course"); + if (options.DoesUnwindOnError() && thread_plan_sp) { + if (log) + log->PutCString("Process::RunThreadPlan(): discarding thread plan " + "'cause unwind_on_error is set."); + thread->DiscardThreadPlansUpToPlan(thread_plan_sp); + } + } + } + + // Thread we ran the function in may have gone away because we ran the + // target Check that it's still there, and if it is put it back in the + // context. Also restore the frame in the context if it is still present. + thread = GetThreadList().FindThreadByIndexID(thread_idx_id, true).get(); + if (thread) { + exe_ctx.SetFrameSP(thread->GetFrameWithStackID(ctx_frame_id)); + } + + // Also restore the current process'es selected frame & thread, since this + // function calling may be done behind the user's back. + + if (selected_tid != LLDB_INVALID_THREAD_ID) { + if (GetThreadList().SetSelectedThreadByIndexID(selected_tid) && + selected_stack_id.IsValid()) { + // We were able to restore the selected thread, now restore the frame: + std::lock_guard<std::recursive_mutex> guard(GetThreadList().GetMutex()); + StackFrameSP old_frame_sp = + GetThreadList().GetSelectedThread()->GetFrameWithStackID( + selected_stack_id); + if (old_frame_sp) + GetThreadList().GetSelectedThread()->SetSelectedFrame( + old_frame_sp.get()); + } + } + } + + // If the process exited during the run of the thread plan, notify everyone. + + if (event_to_broadcast_sp) { + if (log) + log->PutCString("Process::RunThreadPlan(): rebroadcasting event."); + BroadcastEvent(event_to_broadcast_sp); + } + + return return_value; +} + +const char *Process::ExecutionResultAsCString(ExpressionResults result) { + const char *result_name = "<unknown>"; + + switch (result) { + case eExpressionCompleted: + result_name = "eExpressionCompleted"; + break; + case eExpressionDiscarded: + result_name = "eExpressionDiscarded"; + break; + case eExpressionInterrupted: + result_name = "eExpressionInterrupted"; + break; + case eExpressionHitBreakpoint: + result_name = "eExpressionHitBreakpoint"; + break; + case eExpressionSetupError: + result_name = "eExpressionSetupError"; + break; + case eExpressionParseError: + result_name = "eExpressionParseError"; + break; + case eExpressionResultUnavailable: + result_name = "eExpressionResultUnavailable"; + break; + case eExpressionTimedOut: + result_name = "eExpressionTimedOut"; + break; + case eExpressionStoppedForDebug: + result_name = "eExpressionStoppedForDebug"; + break; + case eExpressionThreadVanished: + result_name = "eExpressionThreadVanished"; + } + return result_name; +} + +void Process::GetStatus(Stream &strm) { + const StateType state = GetState(); + if (StateIsStoppedState(state, false)) { + if (state == eStateExited) { + int exit_status = GetExitStatus(); + const char *exit_description = GetExitDescription(); + strm.Printf("Process %" PRIu64 " exited with status = %i (0x%8.8x) %s\n", + GetID(), exit_status, exit_status, + exit_description ? exit_description : ""); + } else { + if (state == eStateConnected) + strm.Printf("Connected to remote target.\n"); + else + strm.Printf("Process %" PRIu64 " %s\n", GetID(), StateAsCString(state)); + } + } else { + strm.Printf("Process %" PRIu64 " is running.\n", GetID()); + } +} + +size_t Process::GetThreadStatus(Stream &strm, + bool only_threads_with_stop_reason, + uint32_t start_frame, uint32_t num_frames, + uint32_t num_frames_with_source, + bool stop_format) { + size_t num_thread_infos_dumped = 0; + + // You can't hold the thread list lock while calling Thread::GetStatus. That + // very well might run code (e.g. if we need it to get return values or + // arguments.) For that to work the process has to be able to acquire it. + // So instead copy the thread ID's, and look them up one by one: + + uint32_t num_threads; + std::vector<lldb::tid_t> thread_id_array; + // Scope for thread list locker; + { + std::lock_guard<std::recursive_mutex> guard(GetThreadList().GetMutex()); + ThreadList &curr_thread_list = GetThreadList(); + num_threads = curr_thread_list.GetSize(); + uint32_t idx; + thread_id_array.resize(num_threads); + for (idx = 0; idx < num_threads; ++idx) + thread_id_array[idx] = curr_thread_list.GetThreadAtIndex(idx)->GetID(); + } + + for (uint32_t i = 0; i < num_threads; i++) { + ThreadSP thread_sp(GetThreadList().FindThreadByID(thread_id_array[i])); + if (thread_sp) { + if (only_threads_with_stop_reason) { + StopInfoSP stop_info_sp = thread_sp->GetStopInfo(); + if (!stop_info_sp || !stop_info_sp->IsValid()) + continue; + } + thread_sp->GetStatus(strm, start_frame, num_frames, + num_frames_with_source, + stop_format); + ++num_thread_infos_dumped; + } else { + Log *log = GetLog(LLDBLog::Process); + LLDB_LOGF(log, "Process::GetThreadStatus - thread 0x" PRIu64 + " vanished while running Thread::GetStatus."); + } + } + return num_thread_infos_dumped; +} + +void Process::AddInvalidMemoryRegion(const LoadRange ®ion) { + m_memory_cache.AddInvalidRange(region.GetRangeBase(), region.GetByteSize()); +} + +bool Process::RemoveInvalidMemoryRange(const LoadRange ®ion) { + return m_memory_cache.RemoveInvalidRange(region.GetRangeBase(), + region.GetByteSize()); +} + +void Process::AddPreResumeAction(PreResumeActionCallback callback, + void *baton) { + m_pre_resume_actions.push_back(PreResumeCallbackAndBaton(callback, baton)); +} + +bool Process::RunPreResumeActions() { + bool result = true; + while (!m_pre_resume_actions.empty()) { + struct PreResumeCallbackAndBaton action = m_pre_resume_actions.back(); + m_pre_resume_actions.pop_back(); + bool this_result = action.callback(action.baton); + if (result) + result = this_result; + } + return result; +} + +void Process::ClearPreResumeActions() { m_pre_resume_actions.clear(); } + +void Process::ClearPreResumeAction(PreResumeActionCallback callback, void *baton) +{ + PreResumeCallbackAndBaton element(callback, baton); + auto found_iter = std::find(m_pre_resume_actions.begin(), m_pre_resume_actions.end(), element); + if (found_iter != m_pre_resume_actions.end()) + { + m_pre_resume_actions.erase(found_iter); + } +} + +ProcessRunLock &Process::GetRunLock() { + if (m_private_state_thread.EqualsThread(Host::GetCurrentThread())) + return m_private_run_lock; + else + return m_public_run_lock; +} + +bool Process::CurrentThreadIsPrivateStateThread() +{ + return m_private_state_thread.EqualsThread(Host::GetCurrentThread()); +} + + +void Process::Flush() { + m_thread_list.Flush(); + m_extended_thread_list.Flush(); + m_extended_thread_stop_id = 0; + m_queue_list.Clear(); + m_queue_list_stop_id = 0; +} + +lldb::addr_t Process::GetCodeAddressMask() { + if (uint32_t num_bits_setting = GetVirtualAddressableBits()) + return AddressableBits::AddressableBitToMask(num_bits_setting); + + return m_code_address_mask; +} + +lldb::addr_t Process::GetDataAddressMask() { + if (uint32_t num_bits_setting = GetVirtualAddressableBits()) + return AddressableBits::AddressableBitToMask(num_bits_setting); + + return m_data_address_mask; +} + +lldb::addr_t Process::GetHighmemCodeAddressMask() { + if (uint32_t num_bits_setting = GetHighmemVirtualAddressableBits()) + return AddressableBits::AddressableBitToMask(num_bits_setting); + + if (m_highmem_code_address_mask != LLDB_INVALID_ADDRESS_MASK) + return m_highmem_code_address_mask; + return GetCodeAddressMask(); +} + +lldb::addr_t Process::GetHighmemDataAddressMask() { + if (uint32_t num_bits_setting = GetHighmemVirtualAddressableBits()) + return AddressableBits::AddressableBitToMask(num_bits_setting); + + if (m_highmem_data_address_mask != LLDB_INVALID_ADDRESS_MASK) + return m_highmem_data_address_mask; + return GetDataAddressMask(); +} + +void Process::SetCodeAddressMask(lldb::addr_t code_address_mask) { + LLDB_LOG(GetLog(LLDBLog::Process), + "Setting Process code address mask to {0:x}", code_address_mask); + m_code_address_mask = code_address_mask; +} + +void Process::SetDataAddressMask(lldb::addr_t data_address_mask) { + LLDB_LOG(GetLog(LLDBLog::Process), + "Setting Process data address mask to {0:x}", data_address_mask); + m_data_address_mask = data_address_mask; +} + +void Process::SetHighmemCodeAddressMask(lldb::addr_t code_address_mask) { + LLDB_LOG(GetLog(LLDBLog::Process), + "Setting Process highmem code address mask to {0:x}", + code_address_mask); + m_highmem_code_address_mask = code_address_mask; +} + +void Process::SetHighmemDataAddressMask(lldb::addr_t data_address_mask) { + LLDB_LOG(GetLog(LLDBLog::Process), + "Setting Process highmem data address mask to {0:x}", + data_address_mask); + m_highmem_data_address_mask = data_address_mask; +} + +addr_t Process::FixCodeAddress(addr_t addr) { + if (ABISP abi_sp = GetABI()) + addr = abi_sp->FixCodeAddress(addr); + return addr; +} + +addr_t Process::FixDataAddress(addr_t addr) { + if (ABISP abi_sp = GetABI()) + addr = abi_sp->FixDataAddress(addr); + return addr; +} + +addr_t Process::FixAnyAddress(addr_t addr) { + if (ABISP abi_sp = GetABI()) + addr = abi_sp->FixAnyAddress(addr); + return addr; +} + +void Process::DidExec() { + Log *log = GetLog(LLDBLog::Process); + LLDB_LOGF(log, "Process::%s()", __FUNCTION__); + + Target &target = GetTarget(); + target.CleanupProcess(); + target.ClearModules(false); + m_dynamic_checkers_up.reset(); + m_abi_sp.reset(); + m_system_runtime_up.reset(); + m_os_up.reset(); + m_dyld_up.reset(); + m_jit_loaders_up.reset(); + m_image_tokens.clear(); + // After an exec, the inferior is a new process and these memory regions are + // no longer allocated. + m_allocated_memory_cache.Clear(/*deallocte_memory=*/false); + { + std::lock_guard<std::recursive_mutex> guard(m_language_runtimes_mutex); + m_language_runtimes.clear(); + } + m_instrumentation_runtimes.clear(); + m_thread_list.DiscardThreadPlans(); + m_memory_cache.Clear(true); + DoDidExec(); + CompleteAttach(); + // Flush the process (threads and all stack frames) after running + // CompleteAttach() in case the dynamic loader loaded things in new + // locations. + Flush(); + + // After we figure out what was loaded/unloaded in CompleteAttach, we need to + // let the target know so it can do any cleanup it needs to. + target.DidExec(); +} + +addr_t Process::ResolveIndirectFunction(const Address *address, Status &error) { + if (address == nullptr) { + error.SetErrorString("Invalid address argument"); + return LLDB_INVALID_ADDRESS; + } + + addr_t function_addr = LLDB_INVALID_ADDRESS; + + addr_t addr = address->GetLoadAddress(&GetTarget()); + std::map<addr_t, addr_t>::const_iterator iter = + m_resolved_indirect_addresses.find(addr); + if (iter != m_resolved_indirect_addresses.end()) { + function_addr = (*iter).second; + } else { + if (!CallVoidArgVoidPtrReturn(address, function_addr)) { + Symbol *symbol = address->CalculateSymbolContextSymbol(); + error.SetErrorStringWithFormat( + "Unable to call resolver for indirect function %s", + symbol ? symbol->GetName().AsCString() : "<UNKNOWN>"); + function_addr = LLDB_INVALID_ADDRESS; + } else { + if (ABISP abi_sp = GetABI()) + function_addr = abi_sp->FixCodeAddress(function_addr); + m_resolved_indirect_addresses.insert( + std::pair<addr_t, addr_t>(addr, function_addr)); + } + } + return function_addr; +} + +void Process::ModulesDidLoad(ModuleList &module_list) { + // Inform the system runtime of the modified modules. + SystemRuntime *sys_runtime = GetSystemRuntime(); + if (sys_runtime) + sys_runtime->ModulesDidLoad(module_list); + + GetJITLoaders().ModulesDidLoad(module_list); + + // Give the instrumentation runtimes a chance to be created before informing + // them of the modified modules. + InstrumentationRuntime::ModulesDidLoad(module_list, this, + m_instrumentation_runtimes); + for (auto &runtime : m_instrumentation_runtimes) + runtime.second->ModulesDidLoad(module_list); + + // Give the language runtimes a chance to be created before informing them of + // the modified modules. + for (const lldb::LanguageType lang_type : Language::GetSupportedLanguages()) { + if (LanguageRuntime *runtime = GetLanguageRuntime(lang_type)) + runtime->ModulesDidLoad(module_list); + } + + // If we don't have an operating system plug-in, try to load one since + // loading shared libraries might cause a new one to try and load + if (!m_os_up) + LoadOperatingSystemPlugin(false); + + // Inform the structured-data plugins of the modified modules. + for (auto &pair : m_structured_data_plugin_map) { + if (pair.second) + pair.second->ModulesDidLoad(*this, module_list); + } +} + +void Process::PrintWarningOptimization(const SymbolContext &sc) { + if (!GetWarningsOptimization()) + return; + if (!sc.module_sp || !sc.function || !sc.function->GetIsOptimized()) + return; + sc.module_sp->ReportWarningOptimization(GetTarget().GetDebugger().GetID()); +} + +void Process::PrintWarningUnsupportedLanguage(const SymbolContext &sc) { + if (!GetWarningsUnsupportedLanguage()) + return; + if (!sc.module_sp) + return; + LanguageType language = sc.GetLanguage(); + if (language == eLanguageTypeUnknown || + language == lldb::eLanguageTypeAssembly || + language == lldb::eLanguageTypeMipsAssembler) + return; + LanguageSet plugins = + PluginManager::GetAllTypeSystemSupportedLanguagesForTypes(); + if (plugins[language]) + return; + sc.module_sp->ReportWarningUnsupportedLanguage( + language, GetTarget().GetDebugger().GetID()); +} + +bool Process::GetProcessInfo(ProcessInstanceInfo &info) { + info.Clear(); + + PlatformSP platform_sp = GetTarget().GetPlatform(); + if (!platform_sp) + return false; + + return platform_sp->GetProcessInfo(GetID(), info); +} + +ThreadCollectionSP Process::GetHistoryThreads(lldb::addr_t addr) { + ThreadCollectionSP threads; + + const MemoryHistorySP &memory_history = + MemoryHistory::FindPlugin(shared_from_this()); + + if (!memory_history) { + return threads; + } + + threads = std::make_shared<ThreadCollection>( + memory_history->GetHistoryThreads(addr)); + + return threads; +} + +InstrumentationRuntimeSP +Process::GetInstrumentationRuntime(lldb::InstrumentationRuntimeType type) { + InstrumentationRuntimeCollection::iterator pos; + pos = m_instrumentation_runtimes.find(type); + if (pos == m_instrumentation_runtimes.end()) { + return InstrumentationRuntimeSP(); + } else + return (*pos).second; +} + +bool Process::GetModuleSpec(const FileSpec &module_file_spec, + const ArchSpec &arch, ModuleSpec &module_spec) { + module_spec.Clear(); + return false; +} + +size_t Process::AddImageToken(lldb::addr_t image_ptr) { + m_image_tokens.push_back(image_ptr); + return m_image_tokens.size() - 1; +} + +lldb::addr_t Process::GetImagePtrFromToken(size_t token) const { + if (token < m_image_tokens.size()) + return m_image_tokens[token]; + return LLDB_INVALID_IMAGE_TOKEN; +} + +void Process::ResetImageToken(size_t token) { + if (token < m_image_tokens.size()) + m_image_tokens[token] = LLDB_INVALID_IMAGE_TOKEN; +} + +Address +Process::AdvanceAddressToNextBranchInstruction(Address default_stop_addr, + AddressRange range_bounds) { + Target &target = GetTarget(); + DisassemblerSP disassembler_sp; + InstructionList *insn_list = nullptr; + + Address retval = default_stop_addr; + + if (!target.GetUseFastStepping()) + return retval; + if (!default_stop_addr.IsValid()) + return retval; + + const char *plugin_name = nullptr; + const char *flavor = nullptr; + disassembler_sp = Disassembler::DisassembleRange( + target.GetArchitecture(), plugin_name, flavor, GetTarget(), range_bounds); + if (disassembler_sp) + insn_list = &disassembler_sp->GetInstructionList(); + + if (insn_list == nullptr) { + return retval; + } + + size_t insn_offset = + insn_list->GetIndexOfInstructionAtAddress(default_stop_addr); + if (insn_offset == UINT32_MAX) { + return retval; + } + + uint32_t branch_index = insn_list->GetIndexOfNextBranchInstruction( + insn_offset, false /* ignore_calls*/, nullptr); + if (branch_index == UINT32_MAX) { + return retval; + } + + if (branch_index > insn_offset) { + Address next_branch_insn_address = + insn_list->GetInstructionAtIndex(branch_index)->GetAddress(); + if (next_branch_insn_address.IsValid() && + range_bounds.ContainsFileAddress(next_branch_insn_address)) { + retval = next_branch_insn_address; + } + } + + return retval; +} + +Status Process::GetMemoryRegionInfo(lldb::addr_t load_addr, + MemoryRegionInfo &range_info) { + if (const lldb::ABISP &abi = GetABI()) + load_addr = abi->FixAnyAddress(load_addr); + return DoGetMemoryRegionInfo(load_addr, range_info); +} + +Status Process::GetMemoryRegions(lldb_private::MemoryRegionInfos ®ion_list) { + Status error; + + lldb::addr_t range_end = 0; + const lldb::ABISP &abi = GetABI(); + + region_list.clear(); + do { + lldb_private::MemoryRegionInfo region_info; + error = GetMemoryRegionInfo(range_end, region_info); + // GetMemoryRegionInfo should only return an error if it is unimplemented. + if (error.Fail()) { + region_list.clear(); + break; + } + + // We only check the end address, not start and end, because we assume that + // the start will not have non-address bits until the first unmappable + // region. We will have exited the loop by that point because the previous + // region, the last mappable region, will have non-address bits in its end + // address. + range_end = region_info.GetRange().GetRangeEnd(); + if (region_info.GetMapped() == MemoryRegionInfo::eYes) { + region_list.push_back(std::move(region_info)); + } + } while ( + // For a process with no non-address bits, all address bits + // set means the end of memory. + range_end != LLDB_INVALID_ADDRESS && + // If we have non-address bits and some are set then the end + // is at or beyond the end of mappable memory. + !(abi && (abi->FixAnyAddress(range_end) != range_end))); + + return error; +} + +Status +Process::ConfigureStructuredData(llvm::StringRef type_name, + const StructuredData::ObjectSP &config_sp) { + // If you get this, the Process-derived class needs to implement a method to + // enable an already-reported asynchronous structured data feature. See + // ProcessGDBRemote for an example implementation over gdb-remote. + return Status("unimplemented"); +} + +void Process::MapSupportedStructuredDataPlugins( + const StructuredData::Array &supported_type_names) { + Log *log = GetLog(LLDBLog::Process); + + // Bail out early if there are no type names to map. + if (supported_type_names.GetSize() == 0) { + LLDB_LOG(log, "no structured data types supported"); + return; + } + + // These StringRefs are backed by the input parameter. + std::set<llvm::StringRef> type_names; + + LLDB_LOG(log, + "the process supports the following async structured data types:"); + + supported_type_names.ForEach( + [&type_names, &log](StructuredData::Object *object) { + // There shouldn't be null objects in the array. + if (!object) + return false; + + // All type names should be strings. + const llvm::StringRef type_name = object->GetStringValue(); + if (type_name.empty()) + return false; + + type_names.insert(type_name); + LLDB_LOG(log, "- {0}", type_name); + return true; + }); + + // For each StructuredDataPlugin, if the plugin handles any of the types in + // the supported_type_names, map that type name to that plugin. Stop when + // we've consumed all the type names. + // FIXME: should we return an error if there are type names nobody + // supports? + for (uint32_t plugin_index = 0; !type_names.empty(); plugin_index++) { + auto create_instance = + PluginManager::GetStructuredDataPluginCreateCallbackAtIndex( + plugin_index); + if (!create_instance) + break; + + // Create the plugin. + StructuredDataPluginSP plugin_sp = (*create_instance)(*this); + if (!plugin_sp) { + // This plugin doesn't think it can work with the process. Move on to the + // next. + continue; + } + + // For any of the remaining type names, map any that this plugin supports. + std::vector<llvm::StringRef> names_to_remove; + for (llvm::StringRef type_name : type_names) { + if (plugin_sp->SupportsStructuredDataType(type_name)) { + m_structured_data_plugin_map.insert( + std::make_pair(type_name, plugin_sp)); + names_to_remove.push_back(type_name); + LLDB_LOG(log, "using plugin {0} for type name {1}", + plugin_sp->GetPluginName(), type_name); + } + } + + // Remove the type names that were consumed by this plugin. + for (llvm::StringRef type_name : names_to_remove) + type_names.erase(type_name); + } +} + +bool Process::RouteAsyncStructuredData( + const StructuredData::ObjectSP object_sp) { + // Nothing to do if there's no data. + if (!object_sp) + return false; + + // The contract is this must be a dictionary, so we can look up the routing + // key via the top-level 'type' string value within the dictionary. + StructuredData::Dictionary *dictionary = object_sp->GetAsDictionary(); + if (!dictionary) + return false; + + // Grab the async structured type name (i.e. the feature/plugin name). + llvm::StringRef type_name; + if (!dictionary->GetValueForKeyAsString("type", type_name)) + return false; + + // Check if there's a plugin registered for this type name. + auto find_it = m_structured_data_plugin_map.find(type_name); + if (find_it == m_structured_data_plugin_map.end()) { + // We don't have a mapping for this structured data type. + return false; + } + + // Route the structured data to the plugin. + find_it->second->HandleArrivalOfStructuredData(*this, type_name, object_sp); + return true; +} + +Status Process::UpdateAutomaticSignalFiltering() { + // Default implementation does nothign. + // No automatic signal filtering to speak of. + return Status(); +} + +UtilityFunction *Process::GetLoadImageUtilityFunction( + Platform *platform, + llvm::function_ref<std::unique_ptr<UtilityFunction>()> factory) { + if (platform != GetTarget().GetPlatform().get()) + return nullptr; + llvm::call_once(m_dlopen_utility_func_flag_once, + [&] { m_dlopen_utility_func_up = factory(); }); + return m_dlopen_utility_func_up.get(); +} + +llvm::Expected<TraceSupportedResponse> Process::TraceSupported() { + if (!IsLiveDebugSession()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Can't trace a non-live process."); + return llvm::make_error<UnimplementedError>(); +} + +bool Process::CallVoidArgVoidPtrReturn(const Address *address, + addr_t &returned_func, + bool trap_exceptions) { + Thread *thread = GetThreadList().GetExpressionExecutionThread().get(); + if (thread == nullptr || address == nullptr) + return false; + + EvaluateExpressionOptions options; + options.SetStopOthers(true); + options.SetUnwindOnError(true); + options.SetIgnoreBreakpoints(true); + options.SetTryAllThreads(true); + options.SetDebug(false); + options.SetTimeout(GetUtilityExpressionTimeout()); + options.SetTrapExceptions(trap_exceptions); + + auto type_system_or_err = + GetTarget().GetScratchTypeSystemForLanguage(eLanguageTypeC); + if (!type_system_or_err) { + llvm::consumeError(type_system_or_err.takeError()); + return false; + } + auto ts = *type_system_or_err; + if (!ts) + return false; + CompilerType void_ptr_type = + ts->GetBasicTypeFromAST(eBasicTypeVoid).GetPointerType(); + lldb::ThreadPlanSP call_plan_sp(new ThreadPlanCallFunction( + *thread, *address, void_ptr_type, llvm::ArrayRef<addr_t>(), options)); + if (call_plan_sp) { + DiagnosticManager diagnostics; + + StackFrame *frame = thread->GetStackFrameAtIndex(0).get(); + if (frame) { + ExecutionContext exe_ctx; + frame->CalculateExecutionContext(exe_ctx); + ExpressionResults result = + RunThreadPlan(exe_ctx, call_plan_sp, options, diagnostics); + if (result == eExpressionCompleted) { + returned_func = + call_plan_sp->GetReturnValueObject()->GetValueAsUnsigned( + LLDB_INVALID_ADDRESS); + + if (GetAddressByteSize() == 4) { + if (returned_func == UINT32_MAX) + return false; + } else if (GetAddressByteSize() == 8) { + if (returned_func == UINT64_MAX) + return false; + } + return true; + } + } + } + + return false; +} + +llvm::Expected<const MemoryTagManager *> Process::GetMemoryTagManager() { + Architecture *arch = GetTarget().GetArchitecturePlugin(); + const MemoryTagManager *tag_manager = + arch ? arch->GetMemoryTagManager() : nullptr; + if (!arch || !tag_manager) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "This architecture does not support memory tagging"); + } + + if (!SupportsMemoryTagging()) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Process does not support memory tagging"); + } + + return tag_manager; +} + +llvm::Expected<std::vector<lldb::addr_t>> +Process::ReadMemoryTags(lldb::addr_t addr, size_t len) { + llvm::Expected<const MemoryTagManager *> tag_manager_or_err = + GetMemoryTagManager(); + if (!tag_manager_or_err) + return tag_manager_or_err.takeError(); + + const MemoryTagManager *tag_manager = *tag_manager_or_err; + llvm::Expected<std::vector<uint8_t>> tag_data = + DoReadMemoryTags(addr, len, tag_manager->GetAllocationTagType()); + if (!tag_data) + return tag_data.takeError(); + + return tag_manager->UnpackTagsData(*tag_data, + len / tag_manager->GetGranuleSize()); +} + +Status Process::WriteMemoryTags(lldb::addr_t addr, size_t len, + const std::vector<lldb::addr_t> &tags) { + llvm::Expected<const MemoryTagManager *> tag_manager_or_err = + GetMemoryTagManager(); + if (!tag_manager_or_err) + return Status(tag_manager_or_err.takeError()); + + const MemoryTagManager *tag_manager = *tag_manager_or_err; + llvm::Expected<std::vector<uint8_t>> packed_tags = + tag_manager->PackTags(tags); + if (!packed_tags) { + return Status(packed_tags.takeError()); + } + + return DoWriteMemoryTags(addr, len, tag_manager->GetAllocationTagType(), + *packed_tags); +} + +// Create a CoreFileMemoryRange from a MemoryRegionInfo +static Process::CoreFileMemoryRange +CreateCoreFileMemoryRange(const MemoryRegionInfo ®ion) { + const addr_t addr = region.GetRange().GetRangeBase(); + llvm::AddressRange range(addr, addr + region.GetRange().GetByteSize()); + return {range, region.GetLLDBPermissions()}; +} + +// Add dirty pages to the core file ranges and return true if dirty pages +// were added. Return false if the dirty page information is not valid or in +// the region. +static bool AddDirtyPages(const MemoryRegionInfo ®ion, + Process::CoreFileMemoryRanges &ranges) { + const auto &dirty_page_list = region.GetDirtyPageList(); + if (!dirty_page_list) + return false; + const uint32_t lldb_permissions = region.GetLLDBPermissions(); + const addr_t page_size = region.GetPageSize(); + if (page_size == 0) + return false; + llvm::AddressRange range(0, 0); + for (addr_t page_addr : *dirty_page_list) { + if (range.empty()) { + // No range yet, initialize the range with the current dirty page. + range = llvm::AddressRange(page_addr, page_addr + page_size); + } else { + if (range.end() == page_addr) { + // Combine consective ranges. + range = llvm::AddressRange(range.start(), page_addr + page_size); + } else { + // Add previous contiguous range and init the new range with the + // current dirty page. + ranges.push_back({range, lldb_permissions}); + range = llvm::AddressRange(page_addr, page_addr + page_size); + } + } + } + // The last range + if (!range.empty()) + ranges.push_back({range, lldb_permissions}); + return true; +} + +// Given a region, add the region to \a ranges. +// +// Only add the region if it isn't empty and if it has some permissions. +// If \a try_dirty_pages is true, then try to add only the dirty pages for a +// given region. If the region has dirty page information, only dirty pages +// will be added to \a ranges, else the entire range will be added to \a +// ranges. +static void AddRegion(const MemoryRegionInfo ®ion, bool try_dirty_pages, + Process::CoreFileMemoryRanges &ranges) { + // Don't add empty ranges. + if (region.GetRange().GetByteSize() == 0) + return; + // Don't add ranges with no read permissions. + if ((region.GetLLDBPermissions() & lldb::ePermissionsReadable) == 0) + return; + if (try_dirty_pages && AddDirtyPages(region, ranges)) + return; + ranges.push_back(CreateCoreFileMemoryRange(region)); +} + +static void SaveOffRegionsWithStackPointers( + Process &process, const MemoryRegionInfos ®ions, + Process::CoreFileMemoryRanges &ranges, std::set<addr_t> &stack_ends) { + const bool try_dirty_pages = true; + + // Before we take any dump, we want to save off the used portions of the + // stacks and mark those memory regions as saved. This prevents us from saving + // the unused portion of the stack below the stack pointer. Saving space on + // the dump. + for (lldb::ThreadSP thread_sp : process.GetThreadList().Threads()) { + if (!thread_sp) + continue; + StackFrameSP frame_sp = thread_sp->GetStackFrameAtIndex(0); + if (!frame_sp) + continue; + RegisterContextSP reg_ctx_sp = frame_sp->GetRegisterContext(); + if (!reg_ctx_sp) + continue; + const addr_t sp = reg_ctx_sp->GetSP(); + const size_t red_zone = process.GetABI()->GetRedZoneSize(); + lldb_private::MemoryRegionInfo sp_region; + if (process.GetMemoryRegionInfo(sp, sp_region).Success()) { + const size_t stack_head = (sp - red_zone); + const size_t stack_size = sp_region.GetRange().GetRangeEnd() - stack_head; + sp_region.GetRange().SetRangeBase(stack_head); + sp_region.GetRange().SetByteSize(stack_size); + stack_ends.insert(sp_region.GetRange().GetRangeEnd()); + AddRegion(sp_region, try_dirty_pages, ranges); + } + } +} + +// Save all memory regions that are not empty or have at least some permissions +// for a full core file style. +static void GetCoreFileSaveRangesFull(Process &process, + const MemoryRegionInfos ®ions, + Process::CoreFileMemoryRanges &ranges, + std::set<addr_t> &stack_ends) { + + // Don't add only dirty pages, add full regions. +const bool try_dirty_pages = false; + for (const auto ®ion : regions) + if (stack_ends.count(region.GetRange().GetRangeEnd()) == 0) + AddRegion(region, try_dirty_pages, ranges); +} + +// Save only the dirty pages to the core file. Make sure the process has at +// least some dirty pages, as some OS versions don't support reporting what +// pages are dirty within an memory region. If no memory regions have dirty +// page information fall back to saving out all ranges with write permissions. +static void GetCoreFileSaveRangesDirtyOnly( + Process &process, const MemoryRegionInfos ®ions, + Process::CoreFileMemoryRanges &ranges, std::set<addr_t> &stack_ends) { + + // Iterate over the regions and find all dirty pages. + bool have_dirty_page_info = false; + for (const auto ®ion : regions) { + if (stack_ends.count(region.GetRange().GetRangeEnd()) == 0 && + AddDirtyPages(region, ranges)) + have_dirty_page_info = true; + } + + if (!have_dirty_page_info) { + // We didn't find support for reporting dirty pages from the process + // plug-in so fall back to any region with write access permissions. + const bool try_dirty_pages = false; + for (const auto ®ion : regions) + if (stack_ends.count(region.GetRange().GetRangeEnd()) == 0 && + region.GetWritable() == MemoryRegionInfo::eYes) + AddRegion(region, try_dirty_pages, ranges); + } +} + +// Save all thread stacks to the core file. Some OS versions support reporting +// when a memory region is stack related. We check on this information, but we +// also use the stack pointers of each thread and add those in case the OS +// doesn't support reporting stack memory. This function also attempts to only +// emit dirty pages from the stack if the memory regions support reporting +// dirty regions as this will make the core file smaller. If the process +// doesn't support dirty regions, then it will fall back to adding the full +// stack region. +static void GetCoreFileSaveRangesStackOnly( + Process &process, const MemoryRegionInfos ®ions, + Process::CoreFileMemoryRanges &ranges, std::set<addr_t> &stack_ends) { + const bool try_dirty_pages = true; + // Some platforms support annotating the region information that tell us that + // it comes from a thread stack. So look for those regions first. + + for (const auto ®ion : regions) { + // Save all the stack memory ranges not associated with a stack pointer. + if (stack_ends.count(region.GetRange().GetRangeEnd()) == 0 && + region.IsStackMemory() == MemoryRegionInfo::eYes) + AddRegion(region, try_dirty_pages, ranges); + } +} + +Status Process::CalculateCoreFileSaveRanges(lldb::SaveCoreStyle core_style, + CoreFileMemoryRanges &ranges) { + lldb_private::MemoryRegionInfos regions; + Status err = GetMemoryRegions(regions); + if (err.Fail()) + return err; + if (regions.empty()) + return Status("failed to get any valid memory regions from the process"); + if (core_style == eSaveCoreUnspecified) + return Status("callers must set the core_style to something other than " + "eSaveCoreUnspecified"); + + std::set<addr_t> stack_ends; + SaveOffRegionsWithStackPointers(*this, regions, ranges, stack_ends); + + switch (core_style) { + case eSaveCoreUnspecified: + break; + + case eSaveCoreFull: + GetCoreFileSaveRangesFull(*this, regions, ranges, stack_ends); + break; + + case eSaveCoreDirtyOnly: + GetCoreFileSaveRangesDirtyOnly(*this, regions, ranges, stack_ends); + break; + + case eSaveCoreStackOnly: + GetCoreFileSaveRangesStackOnly(*this, regions, ranges, stack_ends); + break; + } + + if (err.Fail()) + return err; + + if (ranges.empty()) + return Status("no valid address ranges found for core style"); + + return Status(); // Success! +} + +void Process::SetAddressableBitMasks(AddressableBits bit_masks) { + uint32_t low_memory_addr_bits = bit_masks.GetLowmemAddressableBits(); + uint32_t high_memory_addr_bits = bit_masks.GetHighmemAddressableBits(); + + if (low_memory_addr_bits == 0 && high_memory_addr_bits == 0) + return; + + if (low_memory_addr_bits != 0) { + addr_t low_addr_mask = + AddressableBits::AddressableBitToMask(low_memory_addr_bits); + SetCodeAddressMask(low_addr_mask); + SetDataAddressMask(low_addr_mask); + } + + if (high_memory_addr_bits != 0) { + addr_t high_addr_mask = + AddressableBits::AddressableBitToMask(high_memory_addr_bits); + SetHighmemCodeAddressMask(high_addr_mask); + SetHighmemDataAddressMask(high_addr_mask); + } +} diff --git a/contrib/llvm-project/lldb/source/Target/ProcessTrace.cpp b/contrib/llvm-project/lldb/source/Target/ProcessTrace.cpp new file mode 100644 index 000000000000..4718a7ca50a7 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ProcessTrace.cpp @@ -0,0 +1,130 @@ +//===-- ProcessTrace.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 "lldb/Target/ProcessTrace.h" + +#include <memory> + +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/Section.h" +#include "lldb/Target/ABI.h" +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Target/Target.h" + +using namespace lldb; +using namespace lldb_private; + +LLDB_PLUGIN_DEFINE(ProcessTrace) + +llvm::StringRef ProcessTrace::GetPluginDescriptionStatic() { + return "Trace process plug-in."; +} + +void ProcessTrace::Terminate() { + PluginManager::UnregisterPlugin(ProcessTrace::CreateInstance); +} + +ProcessSP ProcessTrace::CreateInstance(TargetSP target_sp, + ListenerSP listener_sp, + const FileSpec *crash_file, + bool can_connect) { + if (can_connect) + return nullptr; + return std::make_shared<ProcessTrace>(target_sp, listener_sp, *crash_file); +} + +bool ProcessTrace::CanDebug(TargetSP target_sp, bool plugin_specified_by_name) { + return plugin_specified_by_name; +} + +ProcessTrace::ProcessTrace(TargetSP target_sp, ListenerSP listener_sp, + const FileSpec &core_file) + : PostMortemProcess(target_sp, listener_sp, core_file) {} + +ProcessTrace::~ProcessTrace() { + Clear(); + // We need to call finalize on the process before destroying ourselves to + // make sure all of the broadcaster cleanup goes as planned. If we destruct + // this class, then Process::~Process() might have problems trying to fully + // destroy the broadcaster. + Finalize(true /* destructing */); +} + +void ProcessTrace::DidAttach(ArchSpec &process_arch) { + ListenerSP listener_sp( + Listener::MakeListener("lldb.process_trace.did_attach_listener")); + HijackProcessEvents(listener_sp); + + SetCanJIT(false); + StartPrivateStateThread(); + SetPrivateState(eStateStopped); + + EventSP event_sp; + WaitForProcessToStop(std::nullopt, &event_sp, true, listener_sp); + + RestoreProcessEvents(); + + Process::DidAttach(process_arch); +} + +bool ProcessTrace::DoUpdateThreadList(ThreadList &old_thread_list, + ThreadList &new_thread_list) { + return false; +} + +void ProcessTrace::RefreshStateAfterStop() {} + +Status ProcessTrace::DoDestroy() { return Status(); } + +size_t ProcessTrace::ReadMemory(addr_t addr, void *buf, size_t size, + Status &error) { + if (const ABISP &abi = GetABI()) + addr = abi->FixAnyAddress(addr); + + // Don't allow the caching that lldb_private::Process::ReadMemory does since + // we have it all cached in the trace files. + return DoReadMemory(addr, buf, size, error); +} + +void ProcessTrace::Clear() { m_thread_list.Clear(); } + +void ProcessTrace::Initialize() { + static llvm::once_flag g_once_flag; + + llvm::call_once(g_once_flag, []() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), + GetPluginDescriptionStatic(), CreateInstance); + }); +} + +ArchSpec ProcessTrace::GetArchitecture() { + return GetTarget().GetArchitecture(); +} + +bool ProcessTrace::GetProcessInfo(ProcessInstanceInfo &info) { + info.Clear(); + info.SetProcessID(GetID()); + info.SetArchitecture(GetArchitecture()); + ModuleSP module_sp = GetTarget().GetExecutableModule(); + if (module_sp) { + const bool add_exe_file_as_first_arg = false; + info.SetExecutableFile(GetTarget().GetExecutableModule()->GetFileSpec(), + add_exe_file_as_first_arg); + } + return true; +} + +size_t ProcessTrace::DoReadMemory(addr_t addr, void *buf, size_t size, + Status &error) { + Address resolved_address; + GetTarget().GetSectionLoadList().ResolveLoadAddress(addr, resolved_address); + + return GetTarget().ReadMemoryFromFileCache(resolved_address, buf, size, + error); +} diff --git a/contrib/llvm-project/lldb/source/Target/Queue.cpp b/contrib/llvm-project/lldb/source/Target/Queue.cpp new file mode 100644 index 000000000000..67eae6327673 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/Queue.cpp @@ -0,0 +1,89 @@ +//===-- Queue.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 "lldb/Target/Queue.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/QueueList.h" +#include "lldb/Target/SystemRuntime.h" +#include "lldb/Target/Thread.h" + +using namespace lldb; +using namespace lldb_private; + +Queue::Queue(ProcessSP process_sp, lldb::queue_id_t queue_id, + const char *queue_name) + : m_process_wp(), m_queue_id(queue_id), m_queue_name(), + m_running_work_items_count(0), m_pending_work_items_count(0), + m_pending_items(), m_dispatch_queue_t_addr(LLDB_INVALID_ADDRESS), + m_kind(eQueueKindUnknown) { + if (queue_name) + m_queue_name = queue_name; + + m_process_wp = process_sp; +} + +Queue::~Queue() = default; + +queue_id_t Queue::GetID() { return m_queue_id; } + +const char *Queue::GetName() { + return (m_queue_name.empty() ? nullptr : m_queue_name.c_str()); +} + +uint32_t Queue::GetIndexID() { return m_queue_id; } + +std::vector<lldb::ThreadSP> Queue::GetThreads() { + std::vector<ThreadSP> result; + ProcessSP process_sp = m_process_wp.lock(); + if (process_sp) { + for (ThreadSP thread_sp : process_sp->Threads()) { + if (thread_sp->GetQueueID() == m_queue_id) { + result.push_back(thread_sp); + } + } + } + return result; +} + +void Queue::SetNumRunningWorkItems(uint32_t count) { + m_running_work_items_count = count; +} + +uint32_t Queue::GetNumRunningWorkItems() const { + return m_running_work_items_count; +} + +void Queue::SetNumPendingWorkItems(uint32_t count) { + m_pending_work_items_count = count; +} + +uint32_t Queue::GetNumPendingWorkItems() const { + return m_pending_work_items_count; +} + +void Queue::SetLibdispatchQueueAddress(addr_t dispatch_queue_t_addr) { + m_dispatch_queue_t_addr = dispatch_queue_t_addr; +} + +addr_t Queue::GetLibdispatchQueueAddress() const { + return m_dispatch_queue_t_addr; +} + +const std::vector<lldb::QueueItemSP> &Queue::GetPendingItems() { + if (m_pending_items.empty()) { + ProcessSP process_sp = m_process_wp.lock(); + if (process_sp && process_sp->GetSystemRuntime()) { + process_sp->GetSystemRuntime()->PopulatePendingItemsForQueue(this); + } + } + return m_pending_items; +} + +lldb::QueueKind Queue::GetKind() { return m_kind; } + +void Queue::SetKind(lldb::QueueKind kind) { m_kind = kind; } diff --git a/contrib/llvm-project/lldb/source/Target/QueueItem.cpp b/contrib/llvm-project/lldb/source/Target/QueueItem.cpp new file mode 100644 index 000000000000..7070812a10b0 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/QueueItem.cpp @@ -0,0 +1,106 @@ +//===-- QueueItem.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 "lldb/Target/Queue.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/QueueItem.h" +#include "lldb/Target/SystemRuntime.h" + +using namespace lldb; +using namespace lldb_private; + +QueueItem::QueueItem(QueueSP queue_sp, ProcessSP process_sp, + lldb::addr_t item_ref, lldb_private::Address address) + : m_queue_wp(), m_process_wp(), m_item_ref(item_ref), m_address(address), + m_have_fetched_entire_item(false), m_kind(eQueueItemKindUnknown), + m_item_that_enqueued_this_ref(LLDB_INVALID_ADDRESS), + m_enqueueing_thread_id(LLDB_INVALID_THREAD_ID), + m_enqueueing_queue_id(LLDB_INVALID_QUEUE_ID), + m_target_queue_id(LLDB_INVALID_QUEUE_ID), m_stop_id(0), m_backtrace(), + m_thread_label(), m_queue_label(), m_target_queue_label() { + m_queue_wp = queue_sp; + m_process_wp = process_sp; +} + +QueueItem::~QueueItem() = default; + +QueueItemKind QueueItem::GetKind() { + FetchEntireItem(); + return m_kind; +} + +void QueueItem::SetKind(QueueItemKind item_kind) { m_kind = item_kind; } + +Address &QueueItem::GetAddress() { return m_address; } + +void QueueItem::SetAddress(Address addr) { m_address = addr; } + +ThreadSP QueueItem::GetExtendedBacktraceThread(ConstString type) { + FetchEntireItem(); + ThreadSP return_thread; + QueueSP queue_sp = m_queue_wp.lock(); + if (queue_sp) { + ProcessSP process_sp = queue_sp->GetProcess(); + if (process_sp && process_sp->GetSystemRuntime()) { + return_thread = + process_sp->GetSystemRuntime()->GetExtendedBacktraceForQueueItem( + this->shared_from_this(), type); + } + } + return return_thread; +} + +lldb::addr_t QueueItem::GetItemThatEnqueuedThis() { + FetchEntireItem(); + return m_item_that_enqueued_this_ref; +} + +lldb::tid_t QueueItem::GetEnqueueingThreadID() { + FetchEntireItem(); + return m_enqueueing_thread_id; +} + +lldb::queue_id_t QueueItem::GetEnqueueingQueueID() { + FetchEntireItem(); + return m_enqueueing_queue_id; +} + +uint32_t QueueItem::GetStopID() { + FetchEntireItem(); + return m_stop_id; +} + +std::vector<lldb::addr_t> &QueueItem::GetEnqueueingBacktrace() { + FetchEntireItem(); + return m_backtrace; +} + +std::string QueueItem::GetThreadLabel() { + FetchEntireItem(); + return m_thread_label; +} + +std::string QueueItem::GetQueueLabel() { + FetchEntireItem(); + return m_queue_label; +} + +ProcessSP QueueItem::GetProcessSP() { return m_process_wp.lock(); } + +void QueueItem::FetchEntireItem() { + if (m_have_fetched_entire_item) + return; + ProcessSP process_sp = m_process_wp.lock(); + if (process_sp) { + SystemRuntime *runtime = process_sp->GetSystemRuntime(); + if (runtime) { + runtime->CompleteQueueItem(this, m_item_ref); + m_have_fetched_entire_item = true; + } + } +} diff --git a/contrib/llvm-project/lldb/source/Target/QueueList.cpp b/contrib/llvm-project/lldb/source/Target/QueueList.cpp new file mode 100644 index 000000000000..84ec24f03f51 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/QueueList.cpp @@ -0,0 +1,69 @@ +//===-- QueueList.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 "lldb/Target/Queue.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/QueueList.h" + +using namespace lldb; +using namespace lldb_private; + +QueueList::QueueList(Process *process) + : m_process(process), m_stop_id(0), m_queues(), m_mutex() {} + +QueueList::~QueueList() { Clear(); } + +uint32_t QueueList::GetSize() { + std::lock_guard<std::mutex> guard(m_mutex); + return m_queues.size(); +} + +lldb::QueueSP QueueList::GetQueueAtIndex(uint32_t idx) { + std::lock_guard<std::mutex> guard(m_mutex); + if (idx < m_queues.size()) { + return m_queues[idx]; + } else { + return QueueSP(); + } +} + +void QueueList::Clear() { + std::lock_guard<std::mutex> guard(m_mutex); + m_queues.clear(); +} + +void QueueList::AddQueue(QueueSP queue_sp) { + std::lock_guard<std::mutex> guard(m_mutex); + if (queue_sp.get()) { + m_queues.push_back(queue_sp); + } +} + +lldb::QueueSP QueueList::FindQueueByID(lldb::queue_id_t qid) { + QueueSP ret; + for (QueueSP queue_sp : Queues()) { + if (queue_sp->GetID() == qid) { + ret = queue_sp; + break; + } + } + return ret; +} + +lldb::QueueSP QueueList::FindQueueByIndexID(uint32_t index_id) { + QueueSP ret; + for (QueueSP queue_sp : Queues()) { + if (queue_sp->GetIndexID() == index_id) { + ret = queue_sp; + break; + } + } + return ret; +} + +std::mutex &QueueList::GetMutex() { return m_mutex; } diff --git a/contrib/llvm-project/lldb/source/Target/RegisterContext.cpp b/contrib/llvm-project/lldb/source/Target/RegisterContext.cpp new file mode 100644 index 000000000000..47c50b8a0154 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/RegisterContext.cpp @@ -0,0 +1,469 @@ +//===-- RegisterContext.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 "lldb/Target/RegisterContext.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/Value.h" +#include "lldb/Expression/DWARFExpression.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/DataExtractor.h" +#include "lldb/Utility/Endian.h" +#include "lldb/Utility/RegisterValue.h" +#include "lldb/Utility/Scalar.h" + +using namespace lldb; +using namespace lldb_private; + +RegisterContext::RegisterContext(Thread &thread, uint32_t concrete_frame_idx) + : m_thread(thread), m_concrete_frame_idx(concrete_frame_idx), + m_stop_id(thread.GetProcess()->GetStopID()) {} + +RegisterContext::~RegisterContext() = default; + +void RegisterContext::InvalidateIfNeeded(bool force) { + ProcessSP process_sp(m_thread.GetProcess()); + bool invalidate = force; + uint32_t process_stop_id = UINT32_MAX; + + if (process_sp) + process_stop_id = process_sp->GetStopID(); + else + invalidate = true; + + if (!invalidate) + invalidate = process_stop_id != GetStopID(); + + if (invalidate) { + InvalidateAllRegisters(); + SetStopID(process_stop_id); + } +} + +const RegisterInfo * +RegisterContext::GetRegisterInfoByName(llvm::StringRef reg_name, + uint32_t start_idx) { + if (reg_name.empty()) + return nullptr; + + // Generic register names take precedence over specific register names. + // For example, on x86 we want "sp" to refer to the complete RSP/ESP register + // rather than the 16-bit SP pseudo-register. + uint32_t generic_reg = Args::StringToGenericRegister(reg_name); + if (generic_reg != LLDB_INVALID_REGNUM) { + const RegisterInfo *reg_info = + GetRegisterInfo(eRegisterKindGeneric, generic_reg); + if (reg_info) + return reg_info; + } + + const uint32_t num_registers = GetRegisterCount(); + for (uint32_t reg = start_idx; reg < num_registers; ++reg) { + const RegisterInfo *reg_info = GetRegisterInfoAtIndex(reg); + + if (reg_name.equals_insensitive(reg_info->name) || + reg_name.equals_insensitive(reg_info->alt_name)) + return reg_info; + } + + return nullptr; +} + +const RegisterInfo *RegisterContext::GetRegisterInfo(lldb::RegisterKind kind, + uint32_t num) { + const uint32_t reg_num = ConvertRegisterKindToRegisterNumber(kind, num); + if (reg_num == LLDB_INVALID_REGNUM) + return nullptr; + return GetRegisterInfoAtIndex(reg_num); +} + +const char *RegisterContext::GetRegisterName(uint32_t reg) { + const RegisterInfo *reg_info = GetRegisterInfoAtIndex(reg); + if (reg_info) + return reg_info->name; + return nullptr; +} + +uint64_t RegisterContext::GetPC(uint64_t fail_value) { + uint32_t reg = ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_PC); + uint64_t pc = ReadRegisterAsUnsigned(reg, fail_value); + + if (pc != fail_value) { + TargetSP target_sp = m_thread.CalculateTarget(); + if (target_sp) { + Target *target = target_sp.get(); + if (target) + pc = target->GetOpcodeLoadAddress(pc, AddressClass::eCode); + } + } + + return pc; +} + +uint64_t RegisterContext::GetThreadPointer(uint64_t fail_value) { + uint32_t reg = ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_TP); + return ReadRegisterAsUnsigned(reg, fail_value); +} + +bool RegisterContext::SetPC(uint64_t pc) { + uint32_t reg = ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_PC); + bool success = WriteRegisterFromUnsigned(reg, pc); + if (success) { + StackFrameSP frame_sp( + m_thread.GetFrameWithConcreteFrameIndex(m_concrete_frame_idx)); + if (frame_sp) + frame_sp->ChangePC(pc); + else + m_thread.ClearStackFrames(); + } + return success; +} + +bool RegisterContext::GetPCForSymbolication(Address &address) { + addr_t pc = GetPC(LLDB_INVALID_ADDRESS); + if (pc == LLDB_INVALID_ADDRESS) + return false; + TargetSP target_sp = m_thread.CalculateTarget(); + if (!target_sp.get()) + return false; + + if (!BehavesLikeZerothFrame() && pc != 0) + pc--; + address.SetLoadAddress(pc, target_sp.get()); + return true; +} + +bool RegisterContext::SetPC(Address addr) { + TargetSP target_sp = m_thread.CalculateTarget(); + Target *target = target_sp.get(); + + lldb::addr_t callAddr = addr.GetCallableLoadAddress(target); + if (callAddr == LLDB_INVALID_ADDRESS) + return false; + + return SetPC(callAddr); +} + +uint64_t RegisterContext::GetSP(uint64_t fail_value) { + uint32_t reg = ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_SP); + return ReadRegisterAsUnsigned(reg, fail_value); +} + +bool RegisterContext::SetSP(uint64_t sp) { + uint32_t reg = ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_SP); + return WriteRegisterFromUnsigned(reg, sp); +} + +uint64_t RegisterContext::GetFP(uint64_t fail_value) { + uint32_t reg = ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_FP); + return ReadRegisterAsUnsigned(reg, fail_value); +} + +bool RegisterContext::SetFP(uint64_t fp) { + uint32_t reg = ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_FP); + return WriteRegisterFromUnsigned(reg, fp); +} + +uint64_t RegisterContext::GetReturnAddress(uint64_t fail_value) { + uint32_t reg = ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_RA); + return ReadRegisterAsUnsigned(reg, fail_value); +} + +uint64_t RegisterContext::GetFlags(uint64_t fail_value) { + uint32_t reg = ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_FLAGS); + return ReadRegisterAsUnsigned(reg, fail_value); +} + +uint64_t RegisterContext::ReadRegisterAsUnsigned(uint32_t reg, + uint64_t fail_value) { + if (reg != LLDB_INVALID_REGNUM) + return ReadRegisterAsUnsigned(GetRegisterInfoAtIndex(reg), fail_value); + return fail_value; +} + +uint64_t RegisterContext::ReadRegisterAsUnsigned(const RegisterInfo *reg_info, + uint64_t fail_value) { + if (reg_info) { + RegisterValue value; + if (ReadRegister(reg_info, value)) + return value.GetAsUInt64(); + } + return fail_value; +} + +bool RegisterContext::WriteRegisterFromUnsigned(uint32_t reg, uint64_t uval) { + if (reg == LLDB_INVALID_REGNUM) + return false; + return WriteRegisterFromUnsigned(GetRegisterInfoAtIndex(reg), uval); +} + +bool RegisterContext::WriteRegisterFromUnsigned(const RegisterInfo *reg_info, + uint64_t uval) { + if (reg_info) { + RegisterValue value; + if (value.SetUInt(uval, reg_info->byte_size)) + return WriteRegister(reg_info, value); + } + return false; +} + +bool RegisterContext::CopyFromRegisterContext(lldb::RegisterContextSP context) { + uint32_t num_register_sets = context->GetRegisterSetCount(); + // We don't know that two threads have the same register context, so require + // the threads to be the same. + if (context->GetThreadID() != GetThreadID()) + return false; + + if (num_register_sets != GetRegisterSetCount()) + return false; + + RegisterContextSP frame_zero_context = m_thread.GetRegisterContext(); + + for (uint32_t set_idx = 0; set_idx < num_register_sets; ++set_idx) { + const RegisterSet *const reg_set = GetRegisterSet(set_idx); + + const uint32_t num_registers = reg_set->num_registers; + for (uint32_t reg_idx = 0; reg_idx < num_registers; ++reg_idx) { + const uint32_t reg = reg_set->registers[reg_idx]; + const RegisterInfo *reg_info = GetRegisterInfoAtIndex(reg); + if (!reg_info || reg_info->value_regs) + continue; + RegisterValue reg_value; + + // If we can reconstruct the register from the frame we are copying from, + // then do so, otherwise use the value from frame 0. + if (context->ReadRegister(reg_info, reg_value)) { + WriteRegister(reg_info, reg_value); + } else if (frame_zero_context->ReadRegister(reg_info, reg_value)) { + WriteRegister(reg_info, reg_value); + } + } + } + return true; +} + +lldb::tid_t RegisterContext::GetThreadID() const { return m_thread.GetID(); } + +uint32_t RegisterContext::NumSupportedHardwareBreakpoints() { return 0; } + +uint32_t RegisterContext::SetHardwareBreakpoint(lldb::addr_t addr, + size_t size) { + return LLDB_INVALID_INDEX32; +} + +// Used when parsing DWARF and EH frame information and any other object file +// sections that contain register numbers in them. +uint32_t +RegisterContext::ConvertRegisterKindToRegisterNumber(lldb::RegisterKind kind, + uint32_t num) { + const uint32_t num_regs = GetRegisterCount(); + + assert(kind < kNumRegisterKinds); + for (uint32_t reg_idx = 0; reg_idx < num_regs; ++reg_idx) { + const RegisterInfo *reg_info = GetRegisterInfoAtIndex(reg_idx); + + if (reg_info->kinds[kind] == num) + return reg_idx; + } + + return LLDB_INVALID_REGNUM; +} + +bool RegisterContext::ClearHardwareBreakpoint(uint32_t hw_idx) { return false; } + +uint32_t RegisterContext::NumSupportedHardwareWatchpoints() { return 0; } + +uint32_t RegisterContext::SetHardwareWatchpoint(lldb::addr_t addr, size_t size, + bool read, bool write) { + return LLDB_INVALID_INDEX32; +} + +bool RegisterContext::ClearHardwareWatchpoint(uint32_t hw_index) { + return false; +} + +bool RegisterContext::HardwareSingleStep(bool enable) { return false; } + +Status RegisterContext::ReadRegisterValueFromMemory( + const RegisterInfo *reg_info, lldb::addr_t src_addr, uint32_t src_len, + RegisterValue ®_value) { + Status error; + if (reg_info == nullptr) { + error.SetErrorString("invalid register info argument."); + return error; + } + + // Moving from addr into a register + // + // Case 1: src_len == dst_len + // + // |AABBCCDD| Address contents + // |AABBCCDD| Register contents + // + // Case 2: src_len > dst_len + // + // Status! (The register should always be big enough to hold the data) + // + // Case 3: src_len < dst_len + // + // |AABB| Address contents + // |AABB0000| Register contents [on little-endian hardware] + // |0000AABB| Register contents [on big-endian hardware] + const uint32_t dst_len = reg_info->byte_size; + + if (src_len > dst_len) { + error.SetErrorStringWithFormat( + "%u bytes is too big to store in register %s (%u bytes)", src_len, + reg_info->name, dst_len); + return error; + } + + ProcessSP process_sp(m_thread.GetProcess()); + if (process_sp) { + RegisterValue::BytesContainer src(src_len); + + // Read the memory + const uint32_t bytes_read = + process_sp->ReadMemory(src_addr, src.data(), src_len, error); + + // Make sure the memory read succeeded... + if (bytes_read != src_len) { + if (error.Success()) { + // This might happen if we read _some_ bytes but not all + error.SetErrorStringWithFormat("read %u of %u bytes", bytes_read, + src_len); + } + return error; + } + + // We now have a memory buffer that contains the part or all of the + // register value. Set the register value using this memory data. + // TODO: we might need to add a parameter to this function in case the byte + // order of the memory data doesn't match the process. For now we are + // assuming they are the same. + reg_value.SetFromMemoryData(*reg_info, src.data(), src_len, + process_sp->GetByteOrder(), error); + } else + error.SetErrorString("invalid process"); + + return error; +} + +Status RegisterContext::WriteRegisterValueToMemory( + const RegisterInfo *reg_info, lldb::addr_t dst_addr, uint32_t dst_len, + const RegisterValue ®_value) { + Status error; + ProcessSP process_sp(m_thread.GetProcess()); + + if (!process_sp) { + error.SetErrorString("invalid process"); + return error; + } + + if (reg_info == nullptr) { + error.SetErrorString("Invalid register info argument."); + return error; + } + + // TODO: we might need to add a parameter to this function in case the byte + // order of the memory data doesn't match the process. For now we are + // assuming they are the same. + RegisterValue::BytesContainer dst(dst_len); + const uint32_t bytes_copied = reg_value.GetAsMemoryData( + *reg_info, dst.data(), dst_len, process_sp->GetByteOrder(), error); + + if (error.Success()) { + if (bytes_copied == 0) { + error.SetErrorString("byte copy failed."); + } else { + const uint32_t bytes_written = + process_sp->WriteMemory(dst_addr, dst.data(), bytes_copied, error); + if (bytes_written != bytes_copied) { + if (error.Success()) { + // This might happen if we read _some_ bytes but not all + error.SetErrorStringWithFormat("only wrote %u of %u bytes", + bytes_written, bytes_copied); + } + } + } + } + + return error; +} + +lldb::ByteOrder RegisterContext::GetByteOrder() { + // Get the target process whose privileged thread was used for the register + // read. + lldb::ByteOrder byte_order = lldb::eByteOrderInvalid; + lldb_private::Process *process = CalculateProcess().get(); + + if (process) + byte_order = process->GetByteOrder(); + return byte_order; +} + +bool RegisterContext::ReadAllRegisterValues( + lldb_private::RegisterCheckpoint ®_checkpoint) { + return ReadAllRegisterValues(reg_checkpoint.GetData()); +} + +bool RegisterContext::WriteAllRegisterValues( + const lldb_private::RegisterCheckpoint ®_checkpoint) { + return WriteAllRegisterValues(reg_checkpoint.GetData()); +} + +TargetSP RegisterContext::CalculateTarget() { + return m_thread.CalculateTarget(); +} + +ProcessSP RegisterContext::CalculateProcess() { + return m_thread.CalculateProcess(); +} + +ThreadSP RegisterContext::CalculateThread() { + return m_thread.shared_from_this(); +} + +StackFrameSP RegisterContext::CalculateStackFrame() { + // Register contexts might belong to many frames if we have inlined functions + // inside a frame since all inlined functions share the same registers, so we + // can't definitively say which frame we come from... + return StackFrameSP(); +} + +void RegisterContext::CalculateExecutionContext(ExecutionContext &exe_ctx) { + m_thread.CalculateExecutionContext(exe_ctx); +} + +bool RegisterContext::ConvertBetweenRegisterKinds(lldb::RegisterKind source_rk, + uint32_t source_regnum, + lldb::RegisterKind target_rk, + uint32_t &target_regnum) { + const uint32_t num_registers = GetRegisterCount(); + for (uint32_t reg = 0; reg < num_registers; ++reg) { + const RegisterInfo *reg_info = GetRegisterInfoAtIndex(reg); + + if (reg_info->kinds[source_rk] == source_regnum) { + target_regnum = reg_info->kinds[target_rk]; + return (target_regnum != LLDB_INVALID_REGNUM); + } + } + return false; +} diff --git a/contrib/llvm-project/lldb/source/Target/RegisterContextUnwind.cpp b/contrib/llvm-project/lldb/source/Target/RegisterContextUnwind.cpp new file mode 100644 index 000000000000..bc8081f4e3b3 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/RegisterContextUnwind.cpp @@ -0,0 +1,2394 @@ +//===-- RegisterContextUnwind.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 "lldb/Target/RegisterContextUnwind.h" +#include "lldb/Core/Address.h" +#include "lldb/Core/AddressRange.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/Value.h" +#include "lldb/Expression/DWARFExpressionList.h" +#include "lldb/Symbol/ArmUnwindInfo.h" +#include "lldb/Symbol/CallFrameInfo.h" +#include "lldb/Symbol/DWARFCallFrameInfo.h" +#include "lldb/Symbol/FuncUnwinders.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Symbol/SymbolFile.h" +#include "lldb/Target/ABI.h" +#include "lldb/Target/DynamicLoader.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/LanguageRuntime.h" +#include "lldb/Target/Platform.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/RegisterValue.h" +#include "lldb/Utility/VASPrintf.h" +#include "lldb/lldb-private.h" + +#include <cassert> +#include <memory> + +using namespace lldb; +using namespace lldb_private; + +static ConstString GetSymbolOrFunctionName(const SymbolContext &sym_ctx) { + if (sym_ctx.symbol) + return sym_ctx.symbol->GetName(); + else if (sym_ctx.function) + return sym_ctx.function->GetName(); + return ConstString(); +} + +RegisterContextUnwind::RegisterContextUnwind(Thread &thread, + const SharedPtr &next_frame, + SymbolContext &sym_ctx, + uint32_t frame_number, + UnwindLLDB &unwind_lldb) + : RegisterContext(thread, frame_number), m_thread(thread), + m_fast_unwind_plan_sp(), m_full_unwind_plan_sp(), + m_fallback_unwind_plan_sp(), m_all_registers_available(false), + m_frame_type(-1), m_cfa(LLDB_INVALID_ADDRESS), + m_afa(LLDB_INVALID_ADDRESS), m_start_pc(), m_current_pc(), + m_current_offset(0), m_current_offset_backed_up_one(0), + m_behaves_like_zeroth_frame(false), m_sym_ctx(sym_ctx), + m_sym_ctx_valid(false), m_frame_number(frame_number), m_registers(), + m_parent_unwind(unwind_lldb) { + m_sym_ctx.Clear(false); + m_sym_ctx_valid = false; + + if (IsFrameZero()) { + InitializeZerothFrame(); + } else { + InitializeNonZerothFrame(); + } + + // This same code exists over in the GetFullUnwindPlanForFrame() but it may + // not have been executed yet + if (IsFrameZero() || next_frame->m_frame_type == eTrapHandlerFrame || + next_frame->m_frame_type == eDebuggerFrame) { + m_all_registers_available = true; + } +} + +bool RegisterContextUnwind::IsUnwindPlanValidForCurrentPC( + lldb::UnwindPlanSP unwind_plan_sp) { + if (!unwind_plan_sp) + return false; + + // check if m_current_pc is valid + if (unwind_plan_sp->PlanValidAtAddress(m_current_pc)) { + // yes - current offset can be used as is + return true; + } + + // if m_current_offset <= 0, we've got nothing else to try + if (m_current_offset <= 0) + return false; + + // check pc - 1 to see if it's valid + Address pc_minus_one(m_current_pc); + pc_minus_one.SetOffset(m_current_pc.GetOffset() - 1); + if (unwind_plan_sp->PlanValidAtAddress(pc_minus_one)) { + return true; + } + + return false; +} + +// Initialize a RegisterContextUnwind which is the first frame of a stack -- the +// zeroth frame or currently executing frame. + +void RegisterContextUnwind::InitializeZerothFrame() { + Log *log = GetLog(LLDBLog::Unwind); + ExecutionContext exe_ctx(m_thread.shared_from_this()); + RegisterContextSP reg_ctx_sp = m_thread.GetRegisterContext(); + + if (reg_ctx_sp.get() == nullptr) { + m_frame_type = eNotAValidFrame; + UnwindLogMsg("frame does not have a register context"); + return; + } + + addr_t current_pc = reg_ctx_sp->GetPC(); + + if (current_pc == LLDB_INVALID_ADDRESS) { + m_frame_type = eNotAValidFrame; + UnwindLogMsg("frame does not have a pc"); + return; + } + + Process *process = exe_ctx.GetProcessPtr(); + + // Let ABIs fixup code addresses to make sure they are valid. In ARM ABIs + // this will strip bit zero in case we read a PC from memory or from the LR. + // (which would be a no-op in frame 0 where we get it from the register set, + // but still a good idea to make the call here for other ABIs that may + // exist.) + if (ABISP abi_sp = process->GetABI()) + current_pc = abi_sp->FixCodeAddress(current_pc); + + UnwindPlanSP lang_runtime_plan_sp = LanguageRuntime::GetRuntimeUnwindPlan( + m_thread, this, m_behaves_like_zeroth_frame); + if (lang_runtime_plan_sp.get()) { + UnwindLogMsg("This is an async frame"); + } + + // Initialize m_current_pc, an Address object, based on current_pc, an + // addr_t. + m_current_pc.SetLoadAddress(current_pc, &process->GetTarget()); + + // If we don't have a Module for some reason, we're not going to find + // symbol/function information - just stick in some reasonable defaults and + // hope we can unwind past this frame. + ModuleSP pc_module_sp(m_current_pc.GetModule()); + if (!m_current_pc.IsValid() || !pc_module_sp) { + UnwindLogMsg("using architectural default unwind method"); + } + + AddressRange addr_range; + m_sym_ctx_valid = m_current_pc.ResolveFunctionScope(m_sym_ctx, &addr_range); + + if (m_sym_ctx.symbol) { + UnwindLogMsg("with pc value of 0x%" PRIx64 ", symbol name is '%s'", + current_pc, GetSymbolOrFunctionName(m_sym_ctx).AsCString("")); + } else if (m_sym_ctx.function) { + UnwindLogMsg("with pc value of 0x%" PRIx64 ", function name is '%s'", + current_pc, GetSymbolOrFunctionName(m_sym_ctx).AsCString("")); + } else { + UnwindLogMsg("with pc value of 0x%" PRIx64 + ", no symbol/function name is known.", + current_pc); + } + + if (IsTrapHandlerSymbol(process, m_sym_ctx)) { + m_frame_type = eTrapHandlerFrame; + } else { + // FIXME: Detect eDebuggerFrame here. + m_frame_type = eNormalFrame; + } + + // If we were able to find a symbol/function, set addr_range to the bounds of + // that symbol/function. else treat the current pc value as the start_pc and + // record no offset. + if (addr_range.GetBaseAddress().IsValid()) { + m_start_pc = addr_range.GetBaseAddress(); + if (m_current_pc.GetSection() == m_start_pc.GetSection()) { + m_current_offset = m_current_pc.GetOffset() - m_start_pc.GetOffset(); + } else if (m_current_pc.GetModule() == m_start_pc.GetModule()) { + // This means that whatever symbol we kicked up isn't really correct --- + // we should not cross section boundaries ... We really should NULL out + // the function/symbol in this case unless there is a bad assumption here + // due to inlined functions? + m_current_offset = + m_current_pc.GetFileAddress() - m_start_pc.GetFileAddress(); + } + m_current_offset_backed_up_one = m_current_offset; + } else { + m_start_pc = m_current_pc; + m_current_offset = -1; + m_current_offset_backed_up_one = -1; + } + + // We've set m_frame_type and m_sym_ctx before these calls. + + m_fast_unwind_plan_sp = GetFastUnwindPlanForFrame(); + m_full_unwind_plan_sp = GetFullUnwindPlanForFrame(); + + UnwindPlan::RowSP active_row; + lldb::RegisterKind row_register_kind = eRegisterKindGeneric; + + // If we have LanguageRuntime UnwindPlan for this unwind, use those + // rules to find the caller frame instead of the function's normal + // UnwindPlans. The full unwind plan for this frame will be + // the LanguageRuntime-provided unwind plan, and there will not be a + // fast unwind plan. + if (lang_runtime_plan_sp.get()) { + active_row = + lang_runtime_plan_sp->GetRowForFunctionOffset(m_current_offset); + row_register_kind = lang_runtime_plan_sp->GetRegisterKind(); + if (!ReadFrameAddress(row_register_kind, active_row->GetCFAValue(), + m_cfa)) { + UnwindLogMsg("Cannot set cfa"); + } else { + m_full_unwind_plan_sp = lang_runtime_plan_sp; + if (log) { + StreamString active_row_strm; + active_row->Dump(active_row_strm, lang_runtime_plan_sp.get(), &m_thread, + m_start_pc.GetLoadAddress(exe_ctx.GetTargetPtr())); + UnwindLogMsg("async active row: %s", active_row_strm.GetData()); + } + UnwindLogMsg("m_cfa = 0x%" PRIx64 " m_afa = 0x%" PRIx64, m_cfa, m_afa); + UnwindLogMsg( + "initialized async frame current pc is 0x%" PRIx64 + " cfa is 0x%" PRIx64 " afa is 0x%" PRIx64, + (uint64_t)m_current_pc.GetLoadAddress(exe_ctx.GetTargetPtr()), + (uint64_t)m_cfa, (uint64_t)m_afa); + + return; + } + } + + if (m_full_unwind_plan_sp && + m_full_unwind_plan_sp->PlanValidAtAddress(m_current_pc)) { + active_row = + m_full_unwind_plan_sp->GetRowForFunctionOffset(m_current_offset); + row_register_kind = m_full_unwind_plan_sp->GetRegisterKind(); + if (active_row.get() && log) { + StreamString active_row_strm; + active_row->Dump(active_row_strm, m_full_unwind_plan_sp.get(), &m_thread, + m_start_pc.GetLoadAddress(exe_ctx.GetTargetPtr())); + UnwindLogMsg("%s", active_row_strm.GetData()); + } + } + + if (!active_row.get()) { + UnwindLogMsg("could not find an unwindplan row for this frame's pc"); + m_frame_type = eNotAValidFrame; + return; + } + + if (!ReadFrameAddress(row_register_kind, active_row->GetCFAValue(), m_cfa)) { + // Try the fall back unwind plan since the + // full unwind plan failed. + FuncUnwindersSP func_unwinders_sp; + UnwindPlanSP call_site_unwind_plan; + bool cfa_status = false; + + if (m_sym_ctx_valid) { + func_unwinders_sp = + pc_module_sp->GetUnwindTable().GetFuncUnwindersContainingAddress( + m_current_pc, m_sym_ctx); + } + + if (func_unwinders_sp.get() != nullptr) + call_site_unwind_plan = func_unwinders_sp->GetUnwindPlanAtCallSite( + process->GetTarget(), m_thread); + + if (call_site_unwind_plan.get() != nullptr) { + m_fallback_unwind_plan_sp = call_site_unwind_plan; + if (TryFallbackUnwindPlan()) + cfa_status = true; + } + if (!cfa_status) { + UnwindLogMsg("could not read CFA value for first frame."); + m_frame_type = eNotAValidFrame; + return; + } + } else + ReadFrameAddress(row_register_kind, active_row->GetAFAValue(), m_afa); + + if (m_cfa == LLDB_INVALID_ADDRESS && m_afa == LLDB_INVALID_ADDRESS) { + UnwindLogMsg( + "could not read CFA or AFA values for first frame, not valid."); + m_frame_type = eNotAValidFrame; + return; + } + + UnwindLogMsg("initialized frame current pc is 0x%" PRIx64 " cfa is 0x%" PRIx64 + " afa is 0x%" PRIx64 " using %s UnwindPlan", + (uint64_t)m_current_pc.GetLoadAddress(exe_ctx.GetTargetPtr()), + (uint64_t)m_cfa, + (uint64_t)m_afa, + m_full_unwind_plan_sp->GetSourceName().GetCString()); +} + +// Initialize a RegisterContextUnwind for the non-zeroth frame -- rely on the +// RegisterContextUnwind "below" it to provide things like its current pc value. + +void RegisterContextUnwind::InitializeNonZerothFrame() { + Log *log = GetLog(LLDBLog::Unwind); + if (IsFrameZero()) { + m_frame_type = eNotAValidFrame; + UnwindLogMsg("non-zeroth frame tests positive for IsFrameZero -- that " + "shouldn't happen."); + return; + } + + if (!GetNextFrame().get() || !GetNextFrame()->IsValid()) { + m_frame_type = eNotAValidFrame; + UnwindLogMsg("Could not get next frame, marking this frame as invalid."); + return; + } + if (!m_thread.GetRegisterContext()) { + m_frame_type = eNotAValidFrame; + UnwindLogMsg("Could not get register context for this thread, marking this " + "frame as invalid."); + return; + } + + ExecutionContext exe_ctx(m_thread.shared_from_this()); + Process *process = exe_ctx.GetProcessPtr(); + + // Some languages may have a logical parent stack frame which is + // not a real stack frame, but the programmer would consider it to + // be the caller of the frame, e.g. Swift asynchronous frames. + // + // A LanguageRuntime may provide an UnwindPlan that is used in this + // stack trace base on the RegisterContext contents, intsead + // of the normal UnwindPlans we would use for the return-pc. + UnwindPlanSP lang_runtime_plan_sp = LanguageRuntime::GetRuntimeUnwindPlan( + m_thread, this, m_behaves_like_zeroth_frame); + if (lang_runtime_plan_sp.get()) { + UnwindLogMsg("This is an async frame"); + } + + addr_t pc; + if (!ReadGPRValue(eRegisterKindGeneric, LLDB_REGNUM_GENERIC_PC, pc)) { + UnwindLogMsg("could not get pc value"); + m_frame_type = eNotAValidFrame; + return; + } + + // Let ABIs fixup code addresses to make sure they are valid. In ARM ABIs + // this will strip bit zero in case we read a PC from memory or from the LR. + ABISP abi_sp = process->GetABI(); + if (abi_sp) + pc = abi_sp->FixCodeAddress(pc); + + if (log) { + UnwindLogMsg("pc = 0x%" PRIx64, pc); + addr_t reg_val; + if (ReadGPRValue(eRegisterKindGeneric, LLDB_REGNUM_GENERIC_FP, reg_val)) { + if (abi_sp) + reg_val = abi_sp->FixDataAddress(reg_val); + UnwindLogMsg("fp = 0x%" PRIx64, reg_val); + } + if (ReadGPRValue(eRegisterKindGeneric, LLDB_REGNUM_GENERIC_SP, reg_val)) { + if (abi_sp) + reg_val = abi_sp->FixDataAddress(reg_val); + UnwindLogMsg("sp = 0x%" PRIx64, reg_val); + } + } + + // A pc of 0x0 means it's the end of the stack crawl unless we're above a trap + // handler function + bool above_trap_handler = false; + if (GetNextFrame().get() && GetNextFrame()->IsValid() && + GetNextFrame()->IsTrapHandlerFrame()) + above_trap_handler = true; + + if (pc == 0 || pc == 0x1) { + if (!above_trap_handler) { + m_frame_type = eNotAValidFrame; + UnwindLogMsg("this frame has a pc of 0x0"); + return; + } + } + + const bool allow_section_end = true; + m_current_pc.SetLoadAddress(pc, &process->GetTarget(), allow_section_end); + + // If we don't have a Module for some reason, we're not going to find + // symbol/function information - just stick in some reasonable defaults and + // hope we can unwind past this frame. If we're above a trap handler, + // we may be at a bogus address because we jumped through a bogus function + // pointer and trapped, so don't force the arch default unwind plan in that + // case. + ModuleSP pc_module_sp(m_current_pc.GetModule()); + if ((!m_current_pc.IsValid() || !pc_module_sp) && + above_trap_handler == false) { + UnwindLogMsg("using architectural default unwind method"); + + // Test the pc value to see if we know it's in an unmapped/non-executable + // region of memory. + uint32_t permissions; + if (process->GetLoadAddressPermissions(pc, permissions) && + (permissions & ePermissionsExecutable) == 0) { + // If this is the second frame off the stack, we may have unwound the + // first frame incorrectly. But using the architecture default unwind + // plan may get us back on track -- albeit possibly skipping a real + // frame. Give this frame a clearly-invalid pc and see if we can get any + // further. + if (GetNextFrame().get() && GetNextFrame()->IsValid() && + GetNextFrame()->IsFrameZero()) { + UnwindLogMsg("had a pc of 0x%" PRIx64 " which is not in executable " + "memory but on frame 1 -- " + "allowing it once.", + (uint64_t)pc); + m_frame_type = eSkipFrame; + } else { + // anywhere other than the second frame, a non-executable pc means + // we're off in the weeds -- stop now. + m_frame_type = eNotAValidFrame; + UnwindLogMsg("pc is in a non-executable section of memory and this " + "isn't the 2nd frame in the stack walk."); + return; + } + } + + if (abi_sp) { + m_fast_unwind_plan_sp.reset(); + m_full_unwind_plan_sp = + std::make_shared<UnwindPlan>(lldb::eRegisterKindGeneric); + abi_sp->CreateDefaultUnwindPlan(*m_full_unwind_plan_sp); + if (m_frame_type != eSkipFrame) // don't override eSkipFrame + { + m_frame_type = eNormalFrame; + } + m_all_registers_available = false; + m_current_offset = -1; + m_current_offset_backed_up_one = -1; + RegisterKind row_register_kind = m_full_unwind_plan_sp->GetRegisterKind(); + UnwindPlan::RowSP row = m_full_unwind_plan_sp->GetRowForFunctionOffset(0); + if (row.get()) { + if (!ReadFrameAddress(row_register_kind, row->GetCFAValue(), m_cfa)) { + UnwindLogMsg("failed to get cfa value"); + if (m_frame_type != eSkipFrame) // don't override eSkipFrame + { + m_frame_type = eNotAValidFrame; + } + return; + } + + ReadFrameAddress(row_register_kind, row->GetAFAValue(), m_afa); + + // A couple of sanity checks.. + if (m_cfa == LLDB_INVALID_ADDRESS || m_cfa == 0 || m_cfa == 1) { + UnwindLogMsg("could not find a valid cfa address"); + m_frame_type = eNotAValidFrame; + return; + } + + // m_cfa should point into the stack memory; if we can query memory + // region permissions, see if the memory is allocated & readable. + if (process->GetLoadAddressPermissions(m_cfa, permissions) && + (permissions & ePermissionsReadable) == 0) { + m_frame_type = eNotAValidFrame; + UnwindLogMsg( + "the CFA points to a region of memory that is not readable"); + return; + } + } else { + UnwindLogMsg("could not find a row for function offset zero"); + m_frame_type = eNotAValidFrame; + return; + } + + if (CheckIfLoopingStack()) { + TryFallbackUnwindPlan(); + if (CheckIfLoopingStack()) { + UnwindLogMsg("same CFA address as next frame, assuming the unwind is " + "looping - stopping"); + m_frame_type = eNotAValidFrame; + return; + } + } + + UnwindLogMsg("initialized frame cfa is 0x%" PRIx64 " afa is 0x%" PRIx64, + (uint64_t)m_cfa, (uint64_t)m_afa); + return; + } + m_frame_type = eNotAValidFrame; + UnwindLogMsg("could not find any symbol for this pc, or a default unwind " + "plan, to continue unwind."); + return; + } + + AddressRange addr_range; + m_sym_ctx_valid = m_current_pc.ResolveFunctionScope(m_sym_ctx, &addr_range); + + if (m_sym_ctx.symbol) { + UnwindLogMsg("with pc value of 0x%" PRIx64 ", symbol name is '%s'", pc, + GetSymbolOrFunctionName(m_sym_ctx).AsCString("")); + } else if (m_sym_ctx.function) { + UnwindLogMsg("with pc value of 0x%" PRIx64 ", function name is '%s'", pc, + GetSymbolOrFunctionName(m_sym_ctx).AsCString("")); + } else { + UnwindLogMsg("with pc value of 0x%" PRIx64 + ", no symbol/function name is known.", + pc); + } + + bool decr_pc_and_recompute_addr_range; + + if (!m_sym_ctx_valid) { + // Always decrement and recompute if the symbol lookup failed + decr_pc_and_recompute_addr_range = true; + } else if (GetNextFrame()->m_frame_type == eTrapHandlerFrame || + GetNextFrame()->m_frame_type == eDebuggerFrame) { + // Don't decrement if we're "above" an asynchronous event like + // sigtramp. + decr_pc_and_recompute_addr_range = false; + } else if (!addr_range.GetBaseAddress().IsValid() || + addr_range.GetBaseAddress().GetSection() != m_current_pc.GetSection() || + addr_range.GetBaseAddress().GetOffset() != m_current_pc.GetOffset()) { + // If our "current" pc isn't the start of a function, decrement the pc + // if we're up the stack. + if (m_behaves_like_zeroth_frame) + decr_pc_and_recompute_addr_range = false; + else + decr_pc_and_recompute_addr_range = true; + } else if (IsTrapHandlerSymbol(process, m_sym_ctx)) { + // Signal dispatch may set the return address of the handler it calls to + // point to the first byte of a return trampoline (like __kernel_rt_sigreturn), + // so do not decrement and recompute if the symbol we already found is a trap + // handler. + decr_pc_and_recompute_addr_range = false; + } else if (m_behaves_like_zeroth_frame) { + decr_pc_and_recompute_addr_range = false; + } else { + // Decrement to find the function containing the call. + decr_pc_and_recompute_addr_range = true; + } + + // We need to back up the pc by 1 byte and re-search for the Symbol to handle + // the case where the "saved pc" value is pointing to the next function, e.g. + // if a function ends with a CALL instruction. + // FIXME this may need to be an architectural-dependent behavior; if so we'll + // need to add a member function + // to the ABI plugin and consult that. + if (decr_pc_and_recompute_addr_range) { + UnwindLogMsg("Backing up the pc value of 0x%" PRIx64 + " by 1 and re-doing symbol lookup; old symbol was %s", + pc, GetSymbolOrFunctionName(m_sym_ctx).AsCString("")); + Address temporary_pc; + temporary_pc.SetLoadAddress(pc - 1, &process->GetTarget()); + m_sym_ctx.Clear(false); + m_sym_ctx_valid = temporary_pc.ResolveFunctionScope(m_sym_ctx, &addr_range); + + UnwindLogMsg("Symbol is now %s", + GetSymbolOrFunctionName(m_sym_ctx).AsCString("")); + } + + // If we were able to find a symbol/function, set addr_range_ptr to the + // bounds of that symbol/function. else treat the current pc value as the + // start_pc and record no offset. + if (addr_range.GetBaseAddress().IsValid()) { + m_start_pc = addr_range.GetBaseAddress(); + m_current_offset = pc - m_start_pc.GetLoadAddress(&process->GetTarget()); + m_current_offset_backed_up_one = m_current_offset; + if (decr_pc_and_recompute_addr_range && + m_current_offset_backed_up_one > 0) { + m_current_offset_backed_up_one--; + if (m_sym_ctx_valid) { + m_current_pc.SetLoadAddress(pc - 1, &process->GetTarget()); + } + } + } else { + m_start_pc = m_current_pc; + m_current_offset = -1; + m_current_offset_backed_up_one = -1; + } + + if (IsTrapHandlerSymbol(process, m_sym_ctx)) { + m_frame_type = eTrapHandlerFrame; + } else { + // FIXME: Detect eDebuggerFrame here. + if (m_frame_type != eSkipFrame) // don't override eSkipFrame + { + m_frame_type = eNormalFrame; + } + } + + UnwindPlan::RowSP active_row; + RegisterKind row_register_kind = eRegisterKindGeneric; + + // If we have LanguageRuntime UnwindPlan for this unwind, use those + // rules to find the caller frame instead of the function's normal + // UnwindPlans. The full unwind plan for this frame will be + // the LanguageRuntime-provided unwind plan, and there will not be a + // fast unwind plan. + if (lang_runtime_plan_sp.get()) { + active_row = + lang_runtime_plan_sp->GetRowForFunctionOffset(m_current_offset); + row_register_kind = lang_runtime_plan_sp->GetRegisterKind(); + if (!ReadFrameAddress(row_register_kind, active_row->GetCFAValue(), + m_cfa)) { + UnwindLogMsg("Cannot set cfa"); + } else { + m_full_unwind_plan_sp = lang_runtime_plan_sp; + if (log) { + StreamString active_row_strm; + active_row->Dump(active_row_strm, lang_runtime_plan_sp.get(), &m_thread, + m_start_pc.GetLoadAddress(exe_ctx.GetTargetPtr())); + UnwindLogMsg("async active row: %s", active_row_strm.GetData()); + } + UnwindLogMsg("m_cfa = 0x%" PRIx64 " m_afa = 0x%" PRIx64, m_cfa, m_afa); + UnwindLogMsg( + "initialized async frame current pc is 0x%" PRIx64 + " cfa is 0x%" PRIx64 " afa is 0x%" PRIx64, + (uint64_t)m_current_pc.GetLoadAddress(exe_ctx.GetTargetPtr()), + (uint64_t)m_cfa, (uint64_t)m_afa); + + return; + } + } + + // We've set m_frame_type and m_sym_ctx before this call. + m_fast_unwind_plan_sp = GetFastUnwindPlanForFrame(); + + // Try to get by with just the fast UnwindPlan if possible - the full + // UnwindPlan may be expensive to get (e.g. if we have to parse the entire + // eh_frame section of an ObjectFile for the first time.) + + if (m_fast_unwind_plan_sp && + m_fast_unwind_plan_sp->PlanValidAtAddress(m_current_pc)) { + active_row = + m_fast_unwind_plan_sp->GetRowForFunctionOffset(m_current_offset); + row_register_kind = m_fast_unwind_plan_sp->GetRegisterKind(); + PropagateTrapHandlerFlagFromUnwindPlan(m_fast_unwind_plan_sp); + if (active_row.get() && log) { + StreamString active_row_strm; + active_row->Dump(active_row_strm, m_fast_unwind_plan_sp.get(), &m_thread, + m_start_pc.GetLoadAddress(exe_ctx.GetTargetPtr())); + UnwindLogMsg("Using fast unwind plan '%s'", + m_fast_unwind_plan_sp->GetSourceName().AsCString()); + UnwindLogMsg("active row: %s", active_row_strm.GetData()); + } + } else { + m_full_unwind_plan_sp = GetFullUnwindPlanForFrame(); + if (IsUnwindPlanValidForCurrentPC(m_full_unwind_plan_sp)) { + active_row = m_full_unwind_plan_sp->GetRowForFunctionOffset( + m_current_offset_backed_up_one); + row_register_kind = m_full_unwind_plan_sp->GetRegisterKind(); + PropagateTrapHandlerFlagFromUnwindPlan(m_full_unwind_plan_sp); + if (active_row.get() && log) { + StreamString active_row_strm; + active_row->Dump(active_row_strm, m_full_unwind_plan_sp.get(), + &m_thread, + m_start_pc.GetLoadAddress(exe_ctx.GetTargetPtr())); + UnwindLogMsg("Using full unwind plan '%s'", + m_full_unwind_plan_sp->GetSourceName().AsCString()); + UnwindLogMsg("active row: %s", active_row_strm.GetData()); + } + } + } + + if (!active_row.get()) { + m_frame_type = eNotAValidFrame; + UnwindLogMsg("could not find unwind row for this pc"); + return; + } + + if (!ReadFrameAddress(row_register_kind, active_row->GetCFAValue(), m_cfa)) { + UnwindLogMsg("failed to get cfa"); + m_frame_type = eNotAValidFrame; + return; + } + + ReadFrameAddress(row_register_kind, active_row->GetAFAValue(), m_afa); + + UnwindLogMsg("m_cfa = 0x%" PRIx64 " m_afa = 0x%" PRIx64, m_cfa, m_afa); + + if (CheckIfLoopingStack()) { + TryFallbackUnwindPlan(); + if (CheckIfLoopingStack()) { + UnwindLogMsg("same CFA address as next frame, assuming the unwind is " + "looping - stopping"); + m_frame_type = eNotAValidFrame; + return; + } + } + + UnwindLogMsg("initialized frame current pc is 0x%" PRIx64 + " cfa is 0x%" PRIx64 " afa is 0x%" PRIx64, + (uint64_t)m_current_pc.GetLoadAddress(exe_ctx.GetTargetPtr()), + (uint64_t)m_cfa, + (uint64_t)m_afa); +} + +bool RegisterContextUnwind::CheckIfLoopingStack() { + // If we have a bad stack setup, we can get the same CFA value multiple times + // -- or even more devious, we can actually oscillate between two CFA values. + // Detect that here and break out to avoid a possible infinite loop in lldb + // trying to unwind the stack. To detect when we have the same CFA value + // multiple times, we compare the + // CFA of the current + // frame with the 2nd next frame because in some specail case (e.g. signal + // hanlders, hand written assembly without ABI compliance) we can have 2 + // frames with the same + // CFA (in theory we + // can have arbitrary number of frames with the same CFA, but more then 2 is + // very unlikely) + + RegisterContextUnwind::SharedPtr next_frame = GetNextFrame(); + if (next_frame) { + RegisterContextUnwind::SharedPtr next_next_frame = + next_frame->GetNextFrame(); + addr_t next_next_frame_cfa = LLDB_INVALID_ADDRESS; + if (next_next_frame && next_next_frame->GetCFA(next_next_frame_cfa)) { + if (next_next_frame_cfa == m_cfa) { + // We have a loop in the stack unwind + return true; + } + } + } + return false; +} + +bool RegisterContextUnwind::IsFrameZero() const { return m_frame_number == 0; } + +bool RegisterContextUnwind::BehavesLikeZerothFrame() const { + if (m_frame_number == 0) + return true; + if (m_behaves_like_zeroth_frame) + return true; + return false; +} + +// Find a fast unwind plan for this frame, if possible. +// +// On entry to this method, +// +// 1. m_frame_type should already be set to eTrapHandlerFrame/eDebuggerFrame +// if either of those are correct, +// 2. m_sym_ctx should already be filled in, and +// 3. m_current_pc should have the current pc value for this frame +// 4. m_current_offset_backed_up_one should have the current byte offset into +// the function, maybe backed up by 1, -1 if unknown + +UnwindPlanSP RegisterContextUnwind::GetFastUnwindPlanForFrame() { + UnwindPlanSP unwind_plan_sp; + ModuleSP pc_module_sp(m_current_pc.GetModule()); + + if (!m_current_pc.IsValid() || !pc_module_sp || + pc_module_sp->GetObjectFile() == nullptr) + return unwind_plan_sp; + + if (IsFrameZero()) + return unwind_plan_sp; + + FuncUnwindersSP func_unwinders_sp( + pc_module_sp->GetUnwindTable().GetFuncUnwindersContainingAddress( + m_current_pc, m_sym_ctx)); + if (!func_unwinders_sp) + return unwind_plan_sp; + + // If we're in _sigtramp(), unwinding past this frame requires special + // knowledge. + if (m_frame_type == eTrapHandlerFrame || m_frame_type == eDebuggerFrame) + return unwind_plan_sp; + + unwind_plan_sp = func_unwinders_sp->GetUnwindPlanFastUnwind( + *m_thread.CalculateTarget(), m_thread); + if (unwind_plan_sp) { + if (unwind_plan_sp->PlanValidAtAddress(m_current_pc)) { + m_frame_type = eNormalFrame; + return unwind_plan_sp; + } else { + unwind_plan_sp.reset(); + } + } + return unwind_plan_sp; +} + +// On entry to this method, +// +// 1. m_frame_type should already be set to eTrapHandlerFrame/eDebuggerFrame +// if either of those are correct, +// 2. m_sym_ctx should already be filled in, and +// 3. m_current_pc should have the current pc value for this frame +// 4. m_current_offset_backed_up_one should have the current byte offset into +// the function, maybe backed up by 1, -1 if unknown + +UnwindPlanSP RegisterContextUnwind::GetFullUnwindPlanForFrame() { + UnwindPlanSP unwind_plan_sp; + UnwindPlanSP arch_default_unwind_plan_sp; + ExecutionContext exe_ctx(m_thread.shared_from_this()); + Process *process = exe_ctx.GetProcessPtr(); + ABI *abi = process ? process->GetABI().get() : nullptr; + if (abi) { + arch_default_unwind_plan_sp = + std::make_shared<UnwindPlan>(lldb::eRegisterKindGeneric); + abi->CreateDefaultUnwindPlan(*arch_default_unwind_plan_sp); + } else { + UnwindLogMsg( + "unable to get architectural default UnwindPlan from ABI plugin"); + } + + if (IsFrameZero() || GetNextFrame()->m_frame_type == eTrapHandlerFrame || + GetNextFrame()->m_frame_type == eDebuggerFrame) { + m_behaves_like_zeroth_frame = true; + // If this frame behaves like a 0th frame (currently executing or + // interrupted asynchronously), all registers can be retrieved. + m_all_registers_available = true; + } + + // If we've done a jmp 0x0 / bl 0x0 (called through a null function pointer) + // so the pc is 0x0 in the zeroth frame, we need to use the "unwind at first + // instruction" arch default UnwindPlan Also, if this Process can report on + // memory region attributes, any non-executable region means we jumped + // through a bad function pointer - handle the same way as 0x0. Note, if we + // have a symbol context & a symbol, we don't want to follow this code path. + // This is for jumping to memory regions without any information available. + + if ((!m_sym_ctx_valid || + (m_sym_ctx.function == nullptr && m_sym_ctx.symbol == nullptr)) && + m_behaves_like_zeroth_frame && m_current_pc.IsValid()) { + uint32_t permissions; + addr_t current_pc_addr = + m_current_pc.GetLoadAddress(exe_ctx.GetTargetPtr()); + if (current_pc_addr == 0 || + (process && + process->GetLoadAddressPermissions(current_pc_addr, permissions) && + (permissions & ePermissionsExecutable) == 0)) { + if (abi) { + unwind_plan_sp = + std::make_shared<UnwindPlan>(lldb::eRegisterKindGeneric); + abi->CreateFunctionEntryUnwindPlan(*unwind_plan_sp); + m_frame_type = eNormalFrame; + return unwind_plan_sp; + } + } + } + + // No Module for the current pc, try using the architecture default unwind. + ModuleSP pc_module_sp(m_current_pc.GetModule()); + if (!m_current_pc.IsValid() || !pc_module_sp || + pc_module_sp->GetObjectFile() == nullptr) { + m_frame_type = eNormalFrame; + return arch_default_unwind_plan_sp; + } + + FuncUnwindersSP func_unwinders_sp; + if (m_sym_ctx_valid) { + func_unwinders_sp = + pc_module_sp->GetUnwindTable().GetFuncUnwindersContainingAddress( + m_current_pc, m_sym_ctx); + } + + // No FuncUnwinders available for this pc (stripped function symbols, lldb + // could not augment its function table with another source, like + // LC_FUNCTION_STARTS or eh_frame in ObjectFileMachO). See if eh_frame or the + // .ARM.exidx tables have unwind information for this address, else fall back + // to the architectural default unwind. + if (!func_unwinders_sp) { + m_frame_type = eNormalFrame; + + if (!pc_module_sp || !pc_module_sp->GetObjectFile() || + !m_current_pc.IsValid()) + return arch_default_unwind_plan_sp; + + // Even with -fomit-frame-pointer, we can try eh_frame to get back on + // track. + DWARFCallFrameInfo *eh_frame = + pc_module_sp->GetUnwindTable().GetEHFrameInfo(); + if (eh_frame) { + unwind_plan_sp = std::make_shared<UnwindPlan>(lldb::eRegisterKindGeneric); + if (eh_frame->GetUnwindPlan(m_current_pc, *unwind_plan_sp)) + return unwind_plan_sp; + else + unwind_plan_sp.reset(); + } + + ArmUnwindInfo *arm_exidx = + pc_module_sp->GetUnwindTable().GetArmUnwindInfo(); + if (arm_exidx) { + unwind_plan_sp = std::make_shared<UnwindPlan>(lldb::eRegisterKindGeneric); + if (arm_exidx->GetUnwindPlan(exe_ctx.GetTargetRef(), m_current_pc, + *unwind_plan_sp)) + return unwind_plan_sp; + else + unwind_plan_sp.reset(); + } + + CallFrameInfo *object_file_unwind = + pc_module_sp->GetUnwindTable().GetObjectFileUnwindInfo(); + if (object_file_unwind) { + unwind_plan_sp = std::make_shared<UnwindPlan>(lldb::eRegisterKindGeneric); + if (object_file_unwind->GetUnwindPlan(m_current_pc, *unwind_plan_sp)) + return unwind_plan_sp; + else + unwind_plan_sp.reset(); + } + + return arch_default_unwind_plan_sp; + } + + if (m_frame_type == eTrapHandlerFrame && process) { + m_fast_unwind_plan_sp.reset(); + + // On some platforms the unwind information for signal handlers is not + // present or correct. Give the platform plugins a chance to provide + // substitute plan. Otherwise, use eh_frame. + if (m_sym_ctx_valid) { + lldb::PlatformSP platform = process->GetTarget().GetPlatform(); + unwind_plan_sp = platform->GetTrapHandlerUnwindPlan( + process->GetTarget().GetArchitecture().GetTriple(), + GetSymbolOrFunctionName(m_sym_ctx)); + + if (unwind_plan_sp) + return unwind_plan_sp; + } + + unwind_plan_sp = + func_unwinders_sp->GetEHFrameUnwindPlan(process->GetTarget()); + if (!unwind_plan_sp) + unwind_plan_sp = + func_unwinders_sp->GetObjectFileUnwindPlan(process->GetTarget()); + if (unwind_plan_sp && unwind_plan_sp->PlanValidAtAddress(m_current_pc) && + unwind_plan_sp->GetSourcedFromCompiler() == eLazyBoolYes) { + return unwind_plan_sp; + } + } + + // Ask the DynamicLoader if the eh_frame CFI should be trusted in this frame + // even when it's frame zero This comes up if we have hand-written functions + // in a Module and hand-written eh_frame. The assembly instruction + // inspection may fail and the eh_frame CFI were probably written with some + // care to do the right thing. It'd be nice if there was a way to ask the + // eh_frame directly if it is asynchronous (can be trusted at every + // instruction point) or synchronous (the normal case - only at call sites). + // But there is not. + if (process && process->GetDynamicLoader() && + process->GetDynamicLoader()->AlwaysRelyOnEHUnwindInfo(m_sym_ctx)) { + // We must specifically call the GetEHFrameUnwindPlan() method here -- + // normally we would call GetUnwindPlanAtCallSite() -- because CallSite may + // return an unwind plan sourced from either eh_frame (that's what we + // intend) or compact unwind (this won't work) + unwind_plan_sp = + func_unwinders_sp->GetEHFrameUnwindPlan(process->GetTarget()); + if (!unwind_plan_sp) + unwind_plan_sp = + func_unwinders_sp->GetObjectFileUnwindPlan(process->GetTarget()); + if (unwind_plan_sp && unwind_plan_sp->PlanValidAtAddress(m_current_pc)) { + UnwindLogMsgVerbose("frame uses %s for full UnwindPlan because the " + "DynamicLoader suggested we prefer it", + unwind_plan_sp->GetSourceName().GetCString()); + return unwind_plan_sp; + } + } + + // Typically the NonCallSite UnwindPlan is the unwind created by inspecting + // the assembly language instructions + if (m_behaves_like_zeroth_frame && process) { + unwind_plan_sp = func_unwinders_sp->GetUnwindPlanAtNonCallSite( + process->GetTarget(), m_thread); + if (unwind_plan_sp && unwind_plan_sp->PlanValidAtAddress(m_current_pc)) { + if (unwind_plan_sp->GetSourcedFromCompiler() == eLazyBoolNo) { + // We probably have an UnwindPlan created by inspecting assembly + // instructions. The assembly profilers work really well with compiler- + // generated functions but hand- written assembly can be problematic. + // We set the eh_frame based unwind plan as our fallback unwind plan if + // instruction emulation doesn't work out even for non call sites if it + // is available and use the architecture default unwind plan if it is + // not available. The eh_frame unwind plan is more reliable even on non + // call sites then the architecture default plan and for hand written + // assembly code it is often written in a way that it valid at all + // location what helps in the most common cases when the instruction + // emulation fails. + UnwindPlanSP call_site_unwind_plan = + func_unwinders_sp->GetUnwindPlanAtCallSite(process->GetTarget(), + m_thread); + if (call_site_unwind_plan && + call_site_unwind_plan.get() != unwind_plan_sp.get() && + call_site_unwind_plan->GetSourceName() != + unwind_plan_sp->GetSourceName()) { + m_fallback_unwind_plan_sp = call_site_unwind_plan; + } else { + m_fallback_unwind_plan_sp = arch_default_unwind_plan_sp; + } + } + UnwindLogMsgVerbose("frame uses %s for full UnwindPlan because this " + "is the non-call site unwind plan and this is a " + "zeroth frame", + unwind_plan_sp->GetSourceName().GetCString()); + return unwind_plan_sp; + } + + // If we're on the first instruction of a function, and we have an + // architectural default UnwindPlan for the initial instruction of a + // function, use that. + if (m_current_offset == 0) { + unwind_plan_sp = + func_unwinders_sp->GetUnwindPlanArchitectureDefaultAtFunctionEntry( + m_thread); + if (unwind_plan_sp) { + UnwindLogMsgVerbose("frame uses %s for full UnwindPlan because we are at " + "the first instruction of a function", + unwind_plan_sp->GetSourceName().GetCString()); + return unwind_plan_sp; + } + } + } + + // Typically this is unwind info from an eh_frame section intended for + // exception handling; only valid at call sites + if (process) { + unwind_plan_sp = func_unwinders_sp->GetUnwindPlanAtCallSite( + process->GetTarget(), m_thread); + } + if (IsUnwindPlanValidForCurrentPC(unwind_plan_sp)) { + UnwindLogMsgVerbose("frame uses %s for full UnwindPlan because this " + "is the call-site unwind plan", + unwind_plan_sp->GetSourceName().GetCString()); + return unwind_plan_sp; + } + + // We'd prefer to use an UnwindPlan intended for call sites when we're at a + // call site but if we've struck out on that, fall back to using the non- + // call-site assembly inspection UnwindPlan if possible. + if (process) { + unwind_plan_sp = func_unwinders_sp->GetUnwindPlanAtNonCallSite( + process->GetTarget(), m_thread); + } + if (unwind_plan_sp && + unwind_plan_sp->GetSourcedFromCompiler() == eLazyBoolNo) { + // We probably have an UnwindPlan created by inspecting assembly + // instructions. The assembly profilers work really well with compiler- + // generated functions but hand- written assembly can be problematic. We + // set the eh_frame based unwind plan as our fallback unwind plan if + // instruction emulation doesn't work out even for non call sites if it is + // available and use the architecture default unwind plan if it is not + // available. The eh_frame unwind plan is more reliable even on non call + // sites then the architecture default plan and for hand written assembly + // code it is often written in a way that it valid at all location what + // helps in the most common cases when the instruction emulation fails. + UnwindPlanSP call_site_unwind_plan = + func_unwinders_sp->GetUnwindPlanAtCallSite(process->GetTarget(), + m_thread); + if (call_site_unwind_plan && + call_site_unwind_plan.get() != unwind_plan_sp.get() && + call_site_unwind_plan->GetSourceName() != + unwind_plan_sp->GetSourceName()) { + m_fallback_unwind_plan_sp = call_site_unwind_plan; + } else { + m_fallback_unwind_plan_sp = arch_default_unwind_plan_sp; + } + } + + if (IsUnwindPlanValidForCurrentPC(unwind_plan_sp)) { + UnwindLogMsgVerbose("frame uses %s for full UnwindPlan because we " + "failed to find a call-site unwind plan that would work", + unwind_plan_sp->GetSourceName().GetCString()); + return unwind_plan_sp; + } + + // If nothing else, use the architectural default UnwindPlan and hope that + // does the job. + if (arch_default_unwind_plan_sp) + UnwindLogMsgVerbose( + "frame uses %s for full UnwindPlan because we are falling back " + "to the arch default plan", + arch_default_unwind_plan_sp->GetSourceName().GetCString()); + else + UnwindLogMsg( + "Unable to find any UnwindPlan for full unwind of this frame."); + + return arch_default_unwind_plan_sp; +} + +void RegisterContextUnwind::InvalidateAllRegisters() { + m_frame_type = eNotAValidFrame; +} + +size_t RegisterContextUnwind::GetRegisterCount() { + return m_thread.GetRegisterContext()->GetRegisterCount(); +} + +const RegisterInfo *RegisterContextUnwind::GetRegisterInfoAtIndex(size_t reg) { + return m_thread.GetRegisterContext()->GetRegisterInfoAtIndex(reg); +} + +size_t RegisterContextUnwind::GetRegisterSetCount() { + return m_thread.GetRegisterContext()->GetRegisterSetCount(); +} + +const RegisterSet *RegisterContextUnwind::GetRegisterSet(size_t reg_set) { + return m_thread.GetRegisterContext()->GetRegisterSet(reg_set); +} + +uint32_t RegisterContextUnwind::ConvertRegisterKindToRegisterNumber( + lldb::RegisterKind kind, uint32_t num) { + return m_thread.GetRegisterContext()->ConvertRegisterKindToRegisterNumber( + kind, num); +} + +bool RegisterContextUnwind::ReadRegisterValueFromRegisterLocation( + lldb_private::UnwindLLDB::RegisterLocation regloc, + const RegisterInfo *reg_info, RegisterValue &value) { + if (!IsValid()) + return false; + bool success = false; + + switch (regloc.type) { + case UnwindLLDB::RegisterLocation::eRegisterInLiveRegisterContext: { + const RegisterInfo *other_reg_info = + GetRegisterInfoAtIndex(regloc.location.register_number); + + if (!other_reg_info) + return false; + + success = + m_thread.GetRegisterContext()->ReadRegister(other_reg_info, value); + } break; + case UnwindLLDB::RegisterLocation::eRegisterInRegister: { + const RegisterInfo *other_reg_info = + GetRegisterInfoAtIndex(regloc.location.register_number); + + if (!other_reg_info) + return false; + + if (IsFrameZero()) { + success = + m_thread.GetRegisterContext()->ReadRegister(other_reg_info, value); + } else { + success = GetNextFrame()->ReadRegister(other_reg_info, value); + } + } break; + case UnwindLLDB::RegisterLocation::eRegisterValueInferred: + success = + value.SetUInt(regloc.location.inferred_value, reg_info->byte_size); + break; + + case UnwindLLDB::RegisterLocation::eRegisterNotSaved: + break; + case UnwindLLDB::RegisterLocation::eRegisterSavedAtHostMemoryLocation: + llvm_unreachable("FIXME debugger inferior function call unwind"); + case UnwindLLDB::RegisterLocation::eRegisterSavedAtMemoryLocation: { + Status error(ReadRegisterValueFromMemory( + reg_info, regloc.location.target_memory_location, reg_info->byte_size, + value)); + success = error.Success(); + } break; + default: + llvm_unreachable("Unknown RegisterLocation type."); + } + return success; +} + +bool RegisterContextUnwind::WriteRegisterValueToRegisterLocation( + lldb_private::UnwindLLDB::RegisterLocation regloc, + const RegisterInfo *reg_info, const RegisterValue &value) { + if (!IsValid()) + return false; + + bool success = false; + + switch (regloc.type) { + case UnwindLLDB::RegisterLocation::eRegisterInLiveRegisterContext: { + const RegisterInfo *other_reg_info = + GetRegisterInfoAtIndex(regloc.location.register_number); + success = + m_thread.GetRegisterContext()->WriteRegister(other_reg_info, value); + } break; + case UnwindLLDB::RegisterLocation::eRegisterInRegister: { + const RegisterInfo *other_reg_info = + GetRegisterInfoAtIndex(regloc.location.register_number); + if (IsFrameZero()) { + success = + m_thread.GetRegisterContext()->WriteRegister(other_reg_info, value); + } else { + success = GetNextFrame()->WriteRegister(other_reg_info, value); + } + } break; + case UnwindLLDB::RegisterLocation::eRegisterValueInferred: + case UnwindLLDB::RegisterLocation::eRegisterNotSaved: + break; + case UnwindLLDB::RegisterLocation::eRegisterSavedAtHostMemoryLocation: + llvm_unreachable("FIXME debugger inferior function call unwind"); + case UnwindLLDB::RegisterLocation::eRegisterSavedAtMemoryLocation: { + Status error(WriteRegisterValueToMemory( + reg_info, regloc.location.target_memory_location, reg_info->byte_size, + value)); + success = error.Success(); + } break; + default: + llvm_unreachable("Unknown RegisterLocation type."); + } + return success; +} + +bool RegisterContextUnwind::IsValid() const { + return m_frame_type != eNotAValidFrame; +} + +// After the final stack frame in a stack walk we'll get one invalid +// (eNotAValidFrame) stack frame -- one past the end of the stack walk. But +// higher-level code will need to tell the difference between "the unwind plan +// below this frame failed" versus "we successfully completed the stack walk" +// so this method helps to disambiguate that. + +bool RegisterContextUnwind::IsTrapHandlerFrame() const { + return m_frame_type == eTrapHandlerFrame; +} + +// A skip frame is a bogus frame on the stack -- but one where we're likely to +// find a real frame farther +// up the stack if we keep looking. It's always the second frame in an unwind +// (i.e. the first frame after frame zero) where unwinding can be the +// trickiest. Ideally we'll mark up this frame in some way so the user knows +// we're displaying bad data and we may have skipped one frame of their real +// program in the process of getting back on track. + +bool RegisterContextUnwind::IsSkipFrame() const { + return m_frame_type == eSkipFrame; +} + +bool RegisterContextUnwind::IsTrapHandlerSymbol( + lldb_private::Process *process, + const lldb_private::SymbolContext &m_sym_ctx) const { + PlatformSP platform_sp(process->GetTarget().GetPlatform()); + if (platform_sp) { + const std::vector<ConstString> trap_handler_names( + platform_sp->GetTrapHandlerSymbolNames()); + for (ConstString name : trap_handler_names) { + if ((m_sym_ctx.function && m_sym_ctx.function->GetName() == name) || + (m_sym_ctx.symbol && m_sym_ctx.symbol->GetName() == name)) { + return true; + } + } + } + const std::vector<ConstString> user_specified_trap_handler_names( + m_parent_unwind.GetUserSpecifiedTrapHandlerFunctionNames()); + for (ConstString name : user_specified_trap_handler_names) { + if ((m_sym_ctx.function && m_sym_ctx.function->GetName() == name) || + (m_sym_ctx.symbol && m_sym_ctx.symbol->GetName() == name)) { + return true; + } + } + + return false; +} + +// Answer the question: Where did THIS frame save the CALLER frame ("previous" +// frame)'s register value? + +enum UnwindLLDB::RegisterSearchResult +RegisterContextUnwind::SavedLocationForRegister( + uint32_t lldb_regnum, lldb_private::UnwindLLDB::RegisterLocation ®loc) { + RegisterNumber regnum(m_thread, eRegisterKindLLDB, lldb_regnum); + Log *log = GetLog(LLDBLog::Unwind); + + // Have we already found this register location? + if (!m_registers.empty()) { + std::map<uint32_t, + lldb_private::UnwindLLDB::RegisterLocation>::const_iterator + iterator; + iterator = m_registers.find(regnum.GetAsKind(eRegisterKindLLDB)); + if (iterator != m_registers.end()) { + regloc = iterator->second; + UnwindLogMsg("supplying caller's saved %s (%d)'s location, cached", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB)); + return UnwindLLDB::RegisterSearchResult::eRegisterFound; + } + } + + // Look through the available UnwindPlans for the register location. + + UnwindPlan::Row::RegisterLocation unwindplan_regloc; + bool have_unwindplan_regloc = false; + RegisterKind unwindplan_registerkind = kNumRegisterKinds; + + if (m_fast_unwind_plan_sp) { + UnwindPlan::RowSP active_row = + m_fast_unwind_plan_sp->GetRowForFunctionOffset(m_current_offset); + unwindplan_registerkind = m_fast_unwind_plan_sp->GetRegisterKind(); + if (regnum.GetAsKind(unwindplan_registerkind) == LLDB_INVALID_REGNUM) { + UnwindLogMsg("could not convert lldb regnum %s (%d) into %d RegisterKind " + "reg numbering scheme", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB), + (int)unwindplan_registerkind); + return UnwindLLDB::RegisterSearchResult::eRegisterNotFound; + } + // The architecture default unwind plan marks unknown registers as + // Undefined so that we don't forward them up the stack when a + // jitted stack frame may have overwritten them. But when the + // arch default unwind plan is used as the Fast Unwind Plan, we + // need to recognize this & switch over to the Full Unwind Plan + // to see what unwind rule that (more knoweldgeable, probably) + // UnwindPlan has. If the full UnwindPlan says the register + // location is Undefined, then it really is. + if (active_row->GetRegisterInfo(regnum.GetAsKind(unwindplan_registerkind), + unwindplan_regloc) && + !unwindplan_regloc.IsUndefined()) { + UnwindLogMsg( + "supplying caller's saved %s (%d)'s location using FastUnwindPlan", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB)); + have_unwindplan_regloc = true; + } + } + + if (!have_unwindplan_regloc) { + // m_full_unwind_plan_sp being NULL means that we haven't tried to find a + // full UnwindPlan yet + bool got_new_full_unwindplan = false; + if (!m_full_unwind_plan_sp) { + m_full_unwind_plan_sp = GetFullUnwindPlanForFrame(); + got_new_full_unwindplan = true; + } + + if (m_full_unwind_plan_sp) { + RegisterNumber pc_regnum(m_thread, eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_PC); + + UnwindPlan::RowSP active_row = + m_full_unwind_plan_sp->GetRowForFunctionOffset( + m_current_offset_backed_up_one); + unwindplan_registerkind = m_full_unwind_plan_sp->GetRegisterKind(); + + if (got_new_full_unwindplan && active_row.get() && log) { + StreamString active_row_strm; + ExecutionContext exe_ctx(m_thread.shared_from_this()); + active_row->Dump(active_row_strm, m_full_unwind_plan_sp.get(), + &m_thread, + m_start_pc.GetLoadAddress(exe_ctx.GetTargetPtr())); + UnwindLogMsg("Using full unwind plan '%s'", + m_full_unwind_plan_sp->GetSourceName().AsCString()); + UnwindLogMsg("active row: %s", active_row_strm.GetData()); + } + RegisterNumber return_address_reg; + + // If we're fetching the saved pc and this UnwindPlan defines a + // ReturnAddress register (e.g. lr on arm), look for the return address + // register number in the UnwindPlan's row. + if (pc_regnum.IsValid() && pc_regnum == regnum && + m_full_unwind_plan_sp->GetReturnAddressRegister() != + LLDB_INVALID_REGNUM) { + // If this is a trap handler frame, we should have access to + // the complete register context when the interrupt/async + // signal was received, we should fetch the actual saved $pc + // value instead of the Return Address register. + // If $pc is not available, fall back to the RA reg. + UnwindPlan::Row::RegisterLocation scratch; + if (m_frame_type == eTrapHandlerFrame && + active_row->GetRegisterInfo + (pc_regnum.GetAsKind (unwindplan_registerkind), scratch)) { + UnwindLogMsg("Providing pc register instead of rewriting to " + "RA reg because this is a trap handler and there is " + "a location for the saved pc register value."); + } else { + return_address_reg.init( + m_thread, m_full_unwind_plan_sp->GetRegisterKind(), + m_full_unwind_plan_sp->GetReturnAddressRegister()); + regnum = return_address_reg; + UnwindLogMsg("requested caller's saved PC but this UnwindPlan uses a " + "RA reg; getting %s (%d) instead", + return_address_reg.GetName(), + return_address_reg.GetAsKind(eRegisterKindLLDB)); + } + } else { + if (regnum.GetAsKind(unwindplan_registerkind) == LLDB_INVALID_REGNUM) { + if (unwindplan_registerkind == eRegisterKindGeneric) { + UnwindLogMsg("could not convert lldb regnum %s (%d) into " + "eRegisterKindGeneric reg numbering scheme", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB)); + } else { + UnwindLogMsg("could not convert lldb regnum %s (%d) into %d " + "RegisterKind reg numbering scheme", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB), + (int)unwindplan_registerkind); + } + return UnwindLLDB::RegisterSearchResult::eRegisterNotFound; + } + } + + if (regnum.IsValid() && + active_row->GetRegisterInfo(regnum.GetAsKind(unwindplan_registerkind), + unwindplan_regloc)) { + have_unwindplan_regloc = true; + UnwindLogMsg( + "supplying caller's saved %s (%d)'s location using %s UnwindPlan", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB), + m_full_unwind_plan_sp->GetSourceName().GetCString()); + } + + // This is frame 0 and we're retrieving the PC and it's saved in a Return + // Address register and it hasn't been saved anywhere yet -- that is, + // it's still live in the actual register. Handle this specially. + + if (!have_unwindplan_regloc && return_address_reg.IsValid() && + BehavesLikeZerothFrame()) { + if (return_address_reg.GetAsKind(eRegisterKindLLDB) != + LLDB_INVALID_REGNUM) { + lldb_private::UnwindLLDB::RegisterLocation new_regloc; + new_regloc.type = + UnwindLLDB::RegisterLocation::eRegisterInLiveRegisterContext; + new_regloc.location.register_number = + return_address_reg.GetAsKind(eRegisterKindLLDB); + m_registers[regnum.GetAsKind(eRegisterKindLLDB)] = new_regloc; + regloc = new_regloc; + UnwindLogMsg("supplying caller's register %s (%d) from the live " + "RegisterContext at frame 0, saved in %d", + return_address_reg.GetName(), + return_address_reg.GetAsKind(eRegisterKindLLDB), + return_address_reg.GetAsKind(eRegisterKindLLDB)); + return UnwindLLDB::RegisterSearchResult::eRegisterFound; + } + } + + // If this architecture stores the return address in a register (it + // defines a Return Address register) and we're on a non-zero stack frame + // and the Full UnwindPlan says that the pc is stored in the + // RA registers (e.g. lr on arm), then we know that the full unwindplan is + // not trustworthy -- this + // is an impossible situation and the instruction emulation code has + // likely been misled. If this stack frame meets those criteria, we need + // to throw away the Full UnwindPlan that the instruction emulation came + // up with and fall back to the architecture's Default UnwindPlan so the + // stack walk can get past this point. + + // Special note: If the Full UnwindPlan was generated from the compiler, + // don't second-guess it when we're at a call site location. + + // arch_default_ra_regnum is the return address register # in the Full + // UnwindPlan register numbering + RegisterNumber arch_default_ra_regnum(m_thread, eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_RA); + + if (arch_default_ra_regnum.GetAsKind(unwindplan_registerkind) != + LLDB_INVALID_REGNUM && + pc_regnum == regnum && unwindplan_regloc.IsInOtherRegister() && + unwindplan_regloc.GetRegisterNumber() == + arch_default_ra_regnum.GetAsKind(unwindplan_registerkind) && + m_full_unwind_plan_sp->GetSourcedFromCompiler() != eLazyBoolYes && + !m_all_registers_available) { + UnwindLogMsg("%s UnwindPlan tried to restore the pc from the link " + "register but this is a non-zero frame", + m_full_unwind_plan_sp->GetSourceName().GetCString()); + + // Throw away the full unwindplan; install the arch default unwindplan + if (ForceSwitchToFallbackUnwindPlan()) { + // Update for the possibly new unwind plan + unwindplan_registerkind = m_full_unwind_plan_sp->GetRegisterKind(); + UnwindPlan::RowSP active_row = + m_full_unwind_plan_sp->GetRowForFunctionOffset(m_current_offset); + + // Sanity check: Verify that we can fetch a pc value and CFA value + // with this unwind plan + + RegisterNumber arch_default_pc_reg(m_thread, eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_PC); + bool can_fetch_pc_value = false; + bool can_fetch_cfa = false; + addr_t cfa_value; + if (active_row) { + if (arch_default_pc_reg.GetAsKind(unwindplan_registerkind) != + LLDB_INVALID_REGNUM && + active_row->GetRegisterInfo( + arch_default_pc_reg.GetAsKind(unwindplan_registerkind), + unwindplan_regloc)) { + can_fetch_pc_value = true; + } + if (ReadFrameAddress(unwindplan_registerkind, + active_row->GetCFAValue(), cfa_value)) { + can_fetch_cfa = true; + } + } + + have_unwindplan_regloc = can_fetch_pc_value && can_fetch_cfa; + } else { + // We were unable to fall back to another unwind plan + have_unwindplan_regloc = false; + } + } + } + } + + ExecutionContext exe_ctx(m_thread.shared_from_this()); + Process *process = exe_ctx.GetProcessPtr(); + if (!have_unwindplan_regloc) { + // If the UnwindPlan failed to give us an unwind location for this + // register, we may be able to fall back to some ABI-defined default. For + // example, some ABIs allow to determine the caller's SP via the CFA. Also, + // the ABI may set volatile registers to the undefined state. + ABI *abi = process ? process->GetABI().get() : nullptr; + if (abi) { + const RegisterInfo *reg_info = + GetRegisterInfoAtIndex(regnum.GetAsKind(eRegisterKindLLDB)); + if (reg_info && + abi->GetFallbackRegisterLocation(reg_info, unwindplan_regloc)) { + UnwindLogMsg( + "supplying caller's saved %s (%d)'s location using ABI default", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB)); + have_unwindplan_regloc = true; + } + } + } + + if (!have_unwindplan_regloc) { + if (IsFrameZero()) { + // This is frame 0 - we should return the actual live register context + // value + lldb_private::UnwindLLDB::RegisterLocation new_regloc; + new_regloc.type = + UnwindLLDB::RegisterLocation::eRegisterInLiveRegisterContext; + new_regloc.location.register_number = regnum.GetAsKind(eRegisterKindLLDB); + m_registers[regnum.GetAsKind(eRegisterKindLLDB)] = new_regloc; + regloc = new_regloc; + UnwindLogMsg("supplying caller's register %s (%d) from the live " + "RegisterContext at frame 0", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB)); + return UnwindLLDB::RegisterSearchResult::eRegisterFound; + } else { + std::string unwindplan_name; + if (m_full_unwind_plan_sp) { + unwindplan_name += "via '"; + unwindplan_name += m_full_unwind_plan_sp->GetSourceName().AsCString(); + unwindplan_name += "'"; + } + UnwindLogMsg("no save location for %s (%d) %s", regnum.GetName(), + regnum.GetAsKind(eRegisterKindLLDB), + unwindplan_name.c_str()); + } + return UnwindLLDB::RegisterSearchResult::eRegisterNotFound; + } + + // unwindplan_regloc has valid contents about where to retrieve the register + if (unwindplan_regloc.IsUnspecified()) { + lldb_private::UnwindLLDB::RegisterLocation new_regloc = {}; + new_regloc.type = UnwindLLDB::RegisterLocation::eRegisterNotSaved; + m_registers[regnum.GetAsKind(eRegisterKindLLDB)] = new_regloc; + UnwindLogMsg("save location for %s (%d) is unspecified, continue searching", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB)); + return UnwindLLDB::RegisterSearchResult::eRegisterNotFound; + } + + if (unwindplan_regloc.IsUndefined()) { + UnwindLogMsg( + "did not supply reg location for %s (%d) because it is volatile", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB)); + return UnwindLLDB::RegisterSearchResult::eRegisterIsVolatile; + } + + if (unwindplan_regloc.IsSame()) { + if (!m_all_registers_available && + (regnum.GetAsKind(eRegisterKindGeneric) == LLDB_REGNUM_GENERIC_PC || + regnum.GetAsKind(eRegisterKindGeneric) == LLDB_REGNUM_GENERIC_RA)) { + UnwindLogMsg("register %s (%d) is marked as 'IsSame' - it is a pc or " + "return address reg on a frame which does not have all " + "registers available -- treat as if we have no information", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB)); + return UnwindLLDB::RegisterSearchResult::eRegisterNotFound; + } else { + regloc.type = UnwindLLDB::RegisterLocation::eRegisterInRegister; + regloc.location.register_number = regnum.GetAsKind(eRegisterKindLLDB); + m_registers[regnum.GetAsKind(eRegisterKindLLDB)] = regloc; + UnwindLogMsg( + "supplying caller's register %s (%d), saved in register %s (%d)", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB), + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB)); + return UnwindLLDB::RegisterSearchResult::eRegisterFound; + } + } + + if (unwindplan_regloc.IsCFAPlusOffset()) { + int offset = unwindplan_regloc.GetOffset(); + regloc.type = UnwindLLDB::RegisterLocation::eRegisterValueInferred; + regloc.location.inferred_value = m_cfa + offset; + m_registers[regnum.GetAsKind(eRegisterKindLLDB)] = regloc; + UnwindLogMsg("supplying caller's register %s (%d), value is CFA plus " + "offset %d [value is 0x%" PRIx64 "]", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB), offset, + regloc.location.inferred_value); + return UnwindLLDB::RegisterSearchResult::eRegisterFound; + } + + if (unwindplan_regloc.IsAtCFAPlusOffset()) { + int offset = unwindplan_regloc.GetOffset(); + regloc.type = UnwindLLDB::RegisterLocation::eRegisterSavedAtMemoryLocation; + regloc.location.target_memory_location = m_cfa + offset; + m_registers[regnum.GetAsKind(eRegisterKindLLDB)] = regloc; + UnwindLogMsg("supplying caller's register %s (%d) from the stack, saved at " + "CFA plus offset %d [saved at 0x%" PRIx64 "]", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB), offset, + regloc.location.target_memory_location); + return UnwindLLDB::RegisterSearchResult::eRegisterFound; + } + + if (unwindplan_regloc.IsAFAPlusOffset()) { + if (m_afa == LLDB_INVALID_ADDRESS) + return UnwindLLDB::RegisterSearchResult::eRegisterNotFound; + + int offset = unwindplan_regloc.GetOffset(); + regloc.type = UnwindLLDB::RegisterLocation::eRegisterValueInferred; + regloc.location.inferred_value = m_afa + offset; + m_registers[regnum.GetAsKind(eRegisterKindLLDB)] = regloc; + UnwindLogMsg("supplying caller's register %s (%d), value is AFA plus " + "offset %d [value is 0x%" PRIx64 "]", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB), offset, + regloc.location.inferred_value); + return UnwindLLDB::RegisterSearchResult::eRegisterFound; + } + + if (unwindplan_regloc.IsAtAFAPlusOffset()) { + if (m_afa == LLDB_INVALID_ADDRESS) + return UnwindLLDB::RegisterSearchResult::eRegisterNotFound; + + int offset = unwindplan_regloc.GetOffset(); + regloc.type = UnwindLLDB::RegisterLocation::eRegisterSavedAtMemoryLocation; + regloc.location.target_memory_location = m_afa + offset; + m_registers[regnum.GetAsKind(eRegisterKindLLDB)] = regloc; + UnwindLogMsg("supplying caller's register %s (%d) from the stack, saved at " + "AFA plus offset %d [saved at 0x%" PRIx64 "]", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB), offset, + regloc.location.target_memory_location); + return UnwindLLDB::RegisterSearchResult::eRegisterFound; + } + + if (unwindplan_regloc.IsInOtherRegister()) { + uint32_t unwindplan_regnum = unwindplan_regloc.GetRegisterNumber(); + RegisterNumber row_regnum(m_thread, unwindplan_registerkind, + unwindplan_regnum); + if (row_regnum.GetAsKind(eRegisterKindLLDB) == LLDB_INVALID_REGNUM) { + UnwindLogMsg("could not supply caller's %s (%d) location - was saved in " + "another reg but couldn't convert that regnum", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB)); + return UnwindLLDB::RegisterSearchResult::eRegisterNotFound; + } + regloc.type = UnwindLLDB::RegisterLocation::eRegisterInRegister; + regloc.location.register_number = row_regnum.GetAsKind(eRegisterKindLLDB); + m_registers[regnum.GetAsKind(eRegisterKindLLDB)] = regloc; + UnwindLogMsg( + "supplying caller's register %s (%d), saved in register %s (%d)", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB), + row_regnum.GetName(), row_regnum.GetAsKind(eRegisterKindLLDB)); + return UnwindLLDB::RegisterSearchResult::eRegisterFound; + } + + if (unwindplan_regloc.IsDWARFExpression() || + unwindplan_regloc.IsAtDWARFExpression()) { + DataExtractor dwarfdata(unwindplan_regloc.GetDWARFExpressionBytes(), + unwindplan_regloc.GetDWARFExpressionLength(), + process->GetByteOrder(), + process->GetAddressByteSize()); + ModuleSP opcode_ctx; + DWARFExpressionList dwarfexpr(opcode_ctx, dwarfdata, nullptr); + dwarfexpr.GetMutableExpressionAtAddress()->SetRegisterKind( + unwindplan_registerkind); + Value cfa_val = Scalar(m_cfa); + cfa_val.SetValueType(Value::ValueType::LoadAddress); + llvm::Expected<Value> result = + dwarfexpr.Evaluate(&exe_ctx, this, 0, &cfa_val, nullptr); + if (!result) { + LLDB_LOG_ERROR(log, result.takeError(), + "DWARF expression failed to evaluate: {0}"); + } else { + addr_t val; + val = result->GetScalar().ULongLong(); + if (unwindplan_regloc.IsDWARFExpression()) { + regloc.type = UnwindLLDB::RegisterLocation::eRegisterValueInferred; + regloc.location.inferred_value = val; + m_registers[regnum.GetAsKind(eRegisterKindLLDB)] = regloc; + UnwindLogMsg("supplying caller's register %s (%d) via DWARF expression " + "(IsDWARFExpression)", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB)); + return UnwindLLDB::RegisterSearchResult::eRegisterFound; + } else { + regloc.type = + UnwindLLDB::RegisterLocation::eRegisterSavedAtMemoryLocation; + regloc.location.target_memory_location = val; + m_registers[regnum.GetAsKind(eRegisterKindLLDB)] = regloc; + UnwindLogMsg("supplying caller's register %s (%d) via DWARF expression " + "(IsAtDWARFExpression)", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB)); + return UnwindLLDB::RegisterSearchResult::eRegisterFound; + } + } + UnwindLogMsg("tried to use IsDWARFExpression or IsAtDWARFExpression for %s " + "(%d) but failed", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB)); + return UnwindLLDB::RegisterSearchResult::eRegisterNotFound; + } + + UnwindLogMsg("no save location for %s (%d) in this stack frame", + regnum.GetName(), regnum.GetAsKind(eRegisterKindLLDB)); + + // FIXME UnwindPlan::Row types atDWARFExpression and isDWARFExpression are + // unsupported. + + return UnwindLLDB::RegisterSearchResult::eRegisterNotFound; +} + +// TryFallbackUnwindPlan() -- this method is a little tricky. +// +// When this is called, the frame above -- the caller frame, the "previous" +// frame -- is invalid or bad. +// +// Instead of stopping the stack walk here, we'll try a different UnwindPlan +// and see if we can get a valid frame above us. +// +// This most often happens when an unwind plan based on assembly instruction +// inspection is not correct -- mostly with hand-written assembly functions or +// functions where the stack frame is set up "out of band", e.g. the kernel +// saved the register context and then called an asynchronous trap handler like +// _sigtramp. +// +// Often in these cases, if we just do a dumb stack walk we'll get past this +// tricky frame and our usual techniques can continue to be used. + +bool RegisterContextUnwind::TryFallbackUnwindPlan() { + if (m_fallback_unwind_plan_sp.get() == nullptr) + return false; + + if (m_full_unwind_plan_sp.get() == nullptr) + return false; + + if (m_full_unwind_plan_sp.get() == m_fallback_unwind_plan_sp.get() || + m_full_unwind_plan_sp->GetSourceName() == + m_fallback_unwind_plan_sp->GetSourceName()) { + return false; + } + + // If a compiler generated unwind plan failed, trying the arch default + // unwindplan isn't going to do any better. + if (m_full_unwind_plan_sp->GetSourcedFromCompiler() == eLazyBoolYes) + return false; + + // Get the caller's pc value and our own CFA value. Swap in the fallback + // unwind plan, re-fetch the caller's pc value and CFA value. If they're the + // same, then the fallback unwind plan provides no benefit. + + RegisterNumber pc_regnum(m_thread, eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_PC); + + addr_t old_caller_pc_value = LLDB_INVALID_ADDRESS; + addr_t new_caller_pc_value = LLDB_INVALID_ADDRESS; + UnwindLLDB::RegisterLocation regloc = {}; + if (SavedLocationForRegister(pc_regnum.GetAsKind(eRegisterKindLLDB), + regloc) == + UnwindLLDB::RegisterSearchResult::eRegisterFound) { + const RegisterInfo *reg_info = + GetRegisterInfoAtIndex(pc_regnum.GetAsKind(eRegisterKindLLDB)); + if (reg_info) { + RegisterValue reg_value; + if (ReadRegisterValueFromRegisterLocation(regloc, reg_info, reg_value)) { + old_caller_pc_value = reg_value.GetAsUInt64(); + if (ProcessSP process_sp = m_thread.GetProcess()) { + if (ABISP abi_sp = process_sp->GetABI()) + old_caller_pc_value = abi_sp->FixCodeAddress(old_caller_pc_value); + } + } + } + } + + // This is a tricky wrinkle! If SavedLocationForRegister() detects a really + // impossible register location for the full unwind plan, it may call + // ForceSwitchToFallbackUnwindPlan() which in turn replaces the full + // unwindplan with the fallback... in short, we're done, we're using the + // fallback UnwindPlan. We checked if m_fallback_unwind_plan_sp was nullptr + // at the top -- the only way it became nullptr since then is via + // SavedLocationForRegister(). + if (m_fallback_unwind_plan_sp.get() == nullptr) + return true; + + // Switch the full UnwindPlan to be the fallback UnwindPlan. If we decide + // this isn't working, we need to restore. We'll also need to save & restore + // the value of the m_cfa ivar. Save is down below a bit in 'old_cfa'. + UnwindPlanSP original_full_unwind_plan_sp = m_full_unwind_plan_sp; + addr_t old_cfa = m_cfa; + addr_t old_afa = m_afa; + + m_registers.clear(); + + m_full_unwind_plan_sp = m_fallback_unwind_plan_sp; + + UnwindPlan::RowSP active_row = + m_fallback_unwind_plan_sp->GetRowForFunctionOffset( + m_current_offset_backed_up_one); + + if (active_row && + active_row->GetCFAValue().GetValueType() != + UnwindPlan::Row::FAValue::unspecified) { + addr_t new_cfa; + if (!ReadFrameAddress(m_fallback_unwind_plan_sp->GetRegisterKind(), + active_row->GetCFAValue(), new_cfa) || + new_cfa == 0 || new_cfa == 1 || new_cfa == LLDB_INVALID_ADDRESS) { + UnwindLogMsg("failed to get cfa with fallback unwindplan"); + m_fallback_unwind_plan_sp.reset(); + m_full_unwind_plan_sp = original_full_unwind_plan_sp; + return false; + } + m_cfa = new_cfa; + + ReadFrameAddress(m_fallback_unwind_plan_sp->GetRegisterKind(), + active_row->GetAFAValue(), m_afa); + + if (SavedLocationForRegister(pc_regnum.GetAsKind(eRegisterKindLLDB), + regloc) == + UnwindLLDB::RegisterSearchResult::eRegisterFound) { + const RegisterInfo *reg_info = + GetRegisterInfoAtIndex(pc_regnum.GetAsKind(eRegisterKindLLDB)); + if (reg_info) { + RegisterValue reg_value; + if (ReadRegisterValueFromRegisterLocation(regloc, reg_info, + reg_value)) { + new_caller_pc_value = reg_value.GetAsUInt64(); + if (ProcessSP process_sp = m_thread.GetProcess()) { + if (ABISP abi_sp = process_sp->GetABI()) + new_caller_pc_value = abi_sp->FixCodeAddress(new_caller_pc_value); + } + } + } + } + + if (new_caller_pc_value == LLDB_INVALID_ADDRESS) { + UnwindLogMsg("failed to get a pc value for the caller frame with the " + "fallback unwind plan"); + m_fallback_unwind_plan_sp.reset(); + m_full_unwind_plan_sp = original_full_unwind_plan_sp; + m_cfa = old_cfa; + m_afa = old_afa; + return false; + } + + if (old_caller_pc_value == new_caller_pc_value && + m_cfa == old_cfa && + m_afa == old_afa) { + UnwindLogMsg("fallback unwind plan got the same values for this frame " + "CFA and caller frame pc, not using"); + m_fallback_unwind_plan_sp.reset(); + m_full_unwind_plan_sp = original_full_unwind_plan_sp; + return false; + } + + UnwindLogMsg("trying to unwind from this function with the UnwindPlan '%s' " + "because UnwindPlan '%s' failed.", + m_fallback_unwind_plan_sp->GetSourceName().GetCString(), + original_full_unwind_plan_sp->GetSourceName().GetCString()); + + // We've copied the fallback unwind plan into the full - now clear the + // fallback. + m_fallback_unwind_plan_sp.reset(); + PropagateTrapHandlerFlagFromUnwindPlan(m_full_unwind_plan_sp); + } + + return true; +} + +bool RegisterContextUnwind::ForceSwitchToFallbackUnwindPlan() { + if (m_fallback_unwind_plan_sp.get() == nullptr) + return false; + + if (m_full_unwind_plan_sp.get() == nullptr) + return false; + + if (m_full_unwind_plan_sp.get() == m_fallback_unwind_plan_sp.get() || + m_full_unwind_plan_sp->GetSourceName() == + m_fallback_unwind_plan_sp->GetSourceName()) { + return false; + } + + UnwindPlan::RowSP active_row = + m_fallback_unwind_plan_sp->GetRowForFunctionOffset(m_current_offset); + + if (active_row && + active_row->GetCFAValue().GetValueType() != + UnwindPlan::Row::FAValue::unspecified) { + addr_t new_cfa; + if (!ReadFrameAddress(m_fallback_unwind_plan_sp->GetRegisterKind(), + active_row->GetCFAValue(), new_cfa) || + new_cfa == 0 || new_cfa == 1 || new_cfa == LLDB_INVALID_ADDRESS) { + UnwindLogMsg("failed to get cfa with fallback unwindplan"); + m_fallback_unwind_plan_sp.reset(); + return false; + } + + ReadFrameAddress(m_fallback_unwind_plan_sp->GetRegisterKind(), + active_row->GetAFAValue(), m_afa); + + m_full_unwind_plan_sp = m_fallback_unwind_plan_sp; + m_fallback_unwind_plan_sp.reset(); + + m_registers.clear(); + + m_cfa = new_cfa; + + PropagateTrapHandlerFlagFromUnwindPlan(m_full_unwind_plan_sp); + + UnwindLogMsg("switched unconditionally to the fallback unwindplan %s", + m_full_unwind_plan_sp->GetSourceName().GetCString()); + return true; + } + return false; +} + +void RegisterContextUnwind::PropagateTrapHandlerFlagFromUnwindPlan( + lldb::UnwindPlanSP unwind_plan) { + if (unwind_plan->GetUnwindPlanForSignalTrap() != eLazyBoolYes) { + // Unwind plan does not indicate trap handler. Do nothing. We may + // already be flagged as trap handler flag due to the symbol being + // in the trap handler symbol list, and that should take precedence. + return; + } else if (m_frame_type != eNormalFrame) { + // If this is already a trap handler frame, nothing to do. + // If this is a skip or debug or invalid frame, don't override that. + return; + } + + m_frame_type = eTrapHandlerFrame; + + if (m_current_offset_backed_up_one != m_current_offset) { + // We backed up the pc by 1 to compute the symbol context, but + // now need to undo that because the pc of the trap handler + // frame may in fact be the first instruction of a signal return + // trampoline, rather than the instruction after a call. This + // happens on systems where the signal handler dispatch code, rather + // than calling the handler and being returned to, jumps to the + // handler after pushing the address of a return trampoline on the + // stack -- on these systems, when the handler returns, control will + // be transferred to the return trampoline, so that's the best + // symbol we can present in the callstack. + UnwindLogMsg("Resetting current offset and re-doing symbol lookup; " + "old symbol was %s", + GetSymbolOrFunctionName(m_sym_ctx).AsCString("")); + m_current_offset_backed_up_one = m_current_offset; + + AddressRange addr_range; + m_sym_ctx_valid = m_current_pc.ResolveFunctionScope(m_sym_ctx, &addr_range); + + UnwindLogMsg("Symbol is now %s", + GetSymbolOrFunctionName(m_sym_ctx).AsCString("")); + + ExecutionContext exe_ctx(m_thread.shared_from_this()); + Process *process = exe_ctx.GetProcessPtr(); + Target *target = &process->GetTarget(); + + m_start_pc = addr_range.GetBaseAddress(); + m_current_offset = + m_current_pc.GetLoadAddress(target) - m_start_pc.GetLoadAddress(target); + } +} + +bool RegisterContextUnwind::ReadFrameAddress( + lldb::RegisterKind row_register_kind, UnwindPlan::Row::FAValue &fa, + addr_t &address) { + RegisterValue reg_value; + + address = LLDB_INVALID_ADDRESS; + addr_t cfa_reg_contents; + ABISP abi_sp = m_thread.GetProcess()->GetABI(); + + switch (fa.GetValueType()) { + case UnwindPlan::Row::FAValue::isRegisterDereferenced: { + RegisterNumber cfa_reg(m_thread, row_register_kind, + fa.GetRegisterNumber()); + if (ReadGPRValue(cfa_reg, cfa_reg_contents)) { + const RegisterInfo *reg_info = + GetRegisterInfoAtIndex(cfa_reg.GetAsKind(eRegisterKindLLDB)); + RegisterValue reg_value; + if (reg_info) { + if (abi_sp) + cfa_reg_contents = abi_sp->FixDataAddress(cfa_reg_contents); + Status error = ReadRegisterValueFromMemory( + reg_info, cfa_reg_contents, reg_info->byte_size, reg_value); + if (error.Success()) { + address = reg_value.GetAsUInt64(); + if (abi_sp) + address = abi_sp->FixCodeAddress(address); + UnwindLogMsg( + "CFA value via dereferencing reg %s (%d): reg has val 0x%" PRIx64 + ", CFA value is 0x%" PRIx64, + cfa_reg.GetName(), cfa_reg.GetAsKind(eRegisterKindLLDB), + cfa_reg_contents, address); + return true; + } else { + UnwindLogMsg("Tried to deref reg %s (%d) [0x%" PRIx64 + "] but memory read failed.", + cfa_reg.GetName(), cfa_reg.GetAsKind(eRegisterKindLLDB), + cfa_reg_contents); + } + } + } + break; + } + case UnwindPlan::Row::FAValue::isRegisterPlusOffset: { + RegisterNumber cfa_reg(m_thread, row_register_kind, + fa.GetRegisterNumber()); + if (ReadGPRValue(cfa_reg, cfa_reg_contents)) { + if (abi_sp) + cfa_reg_contents = abi_sp->FixDataAddress(cfa_reg_contents); + if (cfa_reg_contents == LLDB_INVALID_ADDRESS || cfa_reg_contents == 0 || + cfa_reg_contents == 1) { + UnwindLogMsg( + "Got an invalid CFA register value - reg %s (%d), value 0x%" PRIx64, + cfa_reg.GetName(), cfa_reg.GetAsKind(eRegisterKindLLDB), + cfa_reg_contents); + cfa_reg_contents = LLDB_INVALID_ADDRESS; + return false; + } + address = cfa_reg_contents + fa.GetOffset(); + UnwindLogMsg( + "CFA is 0x%" PRIx64 ": Register %s (%d) contents are 0x%" PRIx64 + ", offset is %d", + address, cfa_reg.GetName(), cfa_reg.GetAsKind(eRegisterKindLLDB), + cfa_reg_contents, fa.GetOffset()); + return true; + } + break; + } + case UnwindPlan::Row::FAValue::isDWARFExpression: { + ExecutionContext exe_ctx(m_thread.shared_from_this()); + Process *process = exe_ctx.GetProcessPtr(); + DataExtractor dwarfdata(fa.GetDWARFExpressionBytes(), + fa.GetDWARFExpressionLength(), + process->GetByteOrder(), + process->GetAddressByteSize()); + ModuleSP opcode_ctx; + DWARFExpressionList dwarfexpr(opcode_ctx, dwarfdata, nullptr); + dwarfexpr.GetMutableExpressionAtAddress()->SetRegisterKind( + row_register_kind); + llvm::Expected<Value> result = + dwarfexpr.Evaluate(&exe_ctx, this, 0, nullptr, nullptr); + if (result) { + address = result->GetScalar().ULongLong(); + if (ABISP abi_sp = m_thread.GetProcess()->GetABI()) + address = abi_sp->FixCodeAddress(address); + + UnwindLogMsg("CFA value set by DWARF expression is 0x%" PRIx64, + address); + return true; + } + UnwindLogMsg("Failed to set CFA value via DWARF expression: %s", + llvm::toString(result.takeError()).c_str()); + break; + } + case UnwindPlan::Row::FAValue::isRaSearch: { + Process &process = *m_thread.GetProcess(); + lldb::addr_t return_address_hint = GetReturnAddressHint(fa.GetOffset()); + if (return_address_hint == LLDB_INVALID_ADDRESS) + return false; + const unsigned max_iterations = 256; + for (unsigned i = 0; i < max_iterations; ++i) { + Status st; + lldb::addr_t candidate_addr = + return_address_hint + i * process.GetAddressByteSize(); + lldb::addr_t candidate = + process.ReadPointerFromMemory(candidate_addr, st); + if (st.Fail()) { + UnwindLogMsg("Cannot read memory at 0x%" PRIx64 ": %s", candidate_addr, + st.AsCString()); + return false; + } + Address addr; + uint32_t permissions; + if (process.GetLoadAddressPermissions(candidate, permissions) && + permissions & lldb::ePermissionsExecutable) { + address = candidate_addr; + UnwindLogMsg("Heuristically found CFA: 0x%" PRIx64, address); + return true; + } + } + UnwindLogMsg("No suitable CFA found"); + break; + } + default: + return false; + } + return false; +} + +lldb::addr_t RegisterContextUnwind::GetReturnAddressHint(int32_t plan_offset) { + addr_t hint; + if (!ReadGPRValue(eRegisterKindGeneric, LLDB_REGNUM_GENERIC_SP, hint)) + return LLDB_INVALID_ADDRESS; + if (!m_sym_ctx.module_sp || !m_sym_ctx.symbol) + return LLDB_INVALID_ADDRESS; + if (ABISP abi_sp = m_thread.GetProcess()->GetABI()) + hint = abi_sp->FixCodeAddress(hint); + + hint += plan_offset; + + if (auto next = GetNextFrame()) { + if (!next->m_sym_ctx.module_sp || !next->m_sym_ctx.symbol) + return LLDB_INVALID_ADDRESS; + if (auto expected_size = + next->m_sym_ctx.module_sp->GetSymbolFile()->GetParameterStackSize( + *next->m_sym_ctx.symbol)) + hint += *expected_size; + else { + UnwindLogMsgVerbose("Could not retrieve parameter size: %s", + llvm::toString(expected_size.takeError()).c_str()); + return LLDB_INVALID_ADDRESS; + } + } + return hint; +} + +// Retrieve a general purpose register value for THIS frame, as saved by the +// NEXT frame, i.e. the frame that +// this frame called. e.g. +// +// foo () { } +// bar () { foo (); } +// main () { bar (); } +// +// stopped in foo() so +// frame 0 - foo +// frame 1 - bar +// frame 2 - main +// and this RegisterContext is for frame 1 (bar) - if we want to get the pc +// value for frame 1, we need to ask +// where frame 0 (the "next" frame) saved that and retrieve the value. + +bool RegisterContextUnwind::ReadGPRValue(lldb::RegisterKind register_kind, + uint32_t regnum, addr_t &value) { + if (!IsValid()) + return false; + + uint32_t lldb_regnum; + if (register_kind == eRegisterKindLLDB) { + lldb_regnum = regnum; + } else if (!m_thread.GetRegisterContext()->ConvertBetweenRegisterKinds( + register_kind, regnum, eRegisterKindLLDB, lldb_regnum)) { + return false; + } + + const RegisterInfo *reg_info = GetRegisterInfoAtIndex(lldb_regnum); + assert(reg_info); + if (!reg_info) { + UnwindLogMsg( + "Could not find RegisterInfo definition for lldb register number %d", + lldb_regnum); + return false; + } + + uint32_t generic_regnum = LLDB_INVALID_REGNUM; + if (register_kind == eRegisterKindGeneric) + generic_regnum = regnum; + else + m_thread.GetRegisterContext()->ConvertBetweenRegisterKinds( + register_kind, regnum, eRegisterKindGeneric, generic_regnum); + ABISP abi_sp = m_thread.GetProcess()->GetABI(); + + RegisterValue reg_value; + // if this is frame 0 (currently executing frame), get the requested reg + // contents from the actual thread registers + if (IsFrameZero()) { + if (m_thread.GetRegisterContext()->ReadRegister(reg_info, reg_value)) { + value = reg_value.GetAsUInt64(); + if (abi_sp && generic_regnum != LLDB_INVALID_REGNUM) { + if (generic_regnum == LLDB_REGNUM_GENERIC_PC || + generic_regnum == LLDB_REGNUM_GENERIC_RA) + value = abi_sp->FixCodeAddress(value); + if (generic_regnum == LLDB_REGNUM_GENERIC_SP || + generic_regnum == LLDB_REGNUM_GENERIC_FP) + value = abi_sp->FixDataAddress(value); + } + return true; + } + return false; + } + + bool pc_register = false; + if (generic_regnum != LLDB_INVALID_REGNUM && + (generic_regnum == LLDB_REGNUM_GENERIC_PC || + generic_regnum == LLDB_REGNUM_GENERIC_RA)) + pc_register = true; + + lldb_private::UnwindLLDB::RegisterLocation regloc; + if (!m_parent_unwind.SearchForSavedLocationForRegister( + lldb_regnum, regloc, m_frame_number - 1, pc_register)) { + return false; + } + if (ReadRegisterValueFromRegisterLocation(regloc, reg_info, reg_value)) { + value = reg_value.GetAsUInt64(); + if (pc_register) { + if (ABISP abi_sp = m_thread.GetProcess()->GetABI()) { + value = abi_sp->FixCodeAddress(value); + } + } + return true; + } + return false; +} + +bool RegisterContextUnwind::ReadGPRValue(const RegisterNumber ®num, + addr_t &value) { + return ReadGPRValue(regnum.GetRegisterKind(), regnum.GetRegisterNumber(), + value); +} + +// Find the value of a register in THIS frame + +bool RegisterContextUnwind::ReadRegister(const RegisterInfo *reg_info, + RegisterValue &value) { + if (!IsValid()) + return false; + + const uint32_t lldb_regnum = reg_info->kinds[eRegisterKindLLDB]; + UnwindLogMsgVerbose("looking for register saved location for reg %d", + lldb_regnum); + + // If this is the 0th frame, hand this over to the live register context + if (IsFrameZero()) { + UnwindLogMsgVerbose("passing along to the live register context for reg %d", + lldb_regnum); + return m_thread.GetRegisterContext()->ReadRegister(reg_info, value); + } + + bool is_pc_regnum = false; + if (reg_info->kinds[eRegisterKindGeneric] == LLDB_REGNUM_GENERIC_PC || + reg_info->kinds[eRegisterKindGeneric] == LLDB_REGNUM_GENERIC_RA) { + is_pc_regnum = true; + } + + lldb_private::UnwindLLDB::RegisterLocation regloc; + // Find out where the NEXT frame saved THIS frame's register contents + if (!m_parent_unwind.SearchForSavedLocationForRegister( + lldb_regnum, regloc, m_frame_number - 1, is_pc_regnum)) + return false; + + bool result = ReadRegisterValueFromRegisterLocation(regloc, reg_info, value); + if (result) { + if (is_pc_regnum && value.GetType() == RegisterValue::eTypeUInt64) { + addr_t reg_value = value.GetAsUInt64(LLDB_INVALID_ADDRESS); + if (reg_value != LLDB_INVALID_ADDRESS) { + if (ABISP abi_sp = m_thread.GetProcess()->GetABI()) + value = abi_sp->FixCodeAddress(reg_value); + } + } + } + return result; +} + +bool RegisterContextUnwind::WriteRegister(const RegisterInfo *reg_info, + const RegisterValue &value) { + if (!IsValid()) + return false; + + const uint32_t lldb_regnum = reg_info->kinds[eRegisterKindLLDB]; + UnwindLogMsgVerbose("looking for register saved location for reg %d", + lldb_regnum); + + // If this is the 0th frame, hand this over to the live register context + if (IsFrameZero()) { + UnwindLogMsgVerbose("passing along to the live register context for reg %d", + lldb_regnum); + return m_thread.GetRegisterContext()->WriteRegister(reg_info, value); + } + + lldb_private::UnwindLLDB::RegisterLocation regloc; + // Find out where the NEXT frame saved THIS frame's register contents + if (!m_parent_unwind.SearchForSavedLocationForRegister( + lldb_regnum, regloc, m_frame_number - 1, false)) + return false; + + return WriteRegisterValueToRegisterLocation(regloc, reg_info, value); +} + +// Don't need to implement this one +bool RegisterContextUnwind::ReadAllRegisterValues( + lldb::WritableDataBufferSP &data_sp) { + return false; +} + +// Don't need to implement this one +bool RegisterContextUnwind::WriteAllRegisterValues( + const lldb::DataBufferSP &data_sp) { + return false; +} + +// Retrieve the pc value for THIS from + +bool RegisterContextUnwind::GetCFA(addr_t &cfa) { + if (!IsValid()) { + return false; + } + if (m_cfa == LLDB_INVALID_ADDRESS) { + return false; + } + cfa = m_cfa; + return true; +} + +RegisterContextUnwind::SharedPtr RegisterContextUnwind::GetNextFrame() const { + RegisterContextUnwind::SharedPtr regctx; + if (m_frame_number == 0) + return regctx; + return m_parent_unwind.GetRegisterContextForFrameNum(m_frame_number - 1); +} + +RegisterContextUnwind::SharedPtr RegisterContextUnwind::GetPrevFrame() const { + RegisterContextUnwind::SharedPtr regctx; + return m_parent_unwind.GetRegisterContextForFrameNum(m_frame_number + 1); +} + +// Retrieve the address of the start of the function of THIS frame + +bool RegisterContextUnwind::GetStartPC(addr_t &start_pc) { + if (!IsValid()) + return false; + + if (!m_start_pc.IsValid()) { + bool read_successfully = ReadPC (start_pc); + if (read_successfully) + { + ProcessSP process_sp (m_thread.GetProcess()); + if (process_sp) + { + if (ABISP abi_sp = process_sp->GetABI()) + start_pc = abi_sp->FixCodeAddress(start_pc); + } + } + return read_successfully; + } + start_pc = m_start_pc.GetLoadAddress(CalculateTarget().get()); + return true; +} + +// Retrieve the current pc value for THIS frame, as saved by the NEXT frame. + +bool RegisterContextUnwind::ReadPC(addr_t &pc) { + if (!IsValid()) + return false; + + bool above_trap_handler = false; + if (GetNextFrame().get() && GetNextFrame()->IsValid() && + GetNextFrame()->IsTrapHandlerFrame()) + above_trap_handler = true; + + if (ReadGPRValue(eRegisterKindGeneric, LLDB_REGNUM_GENERIC_PC, pc)) { + // A pc value of 0 or 1 is impossible in the middle of the stack -- it + // indicates the end of a stack walk. + // On the currently executing frame (or such a frame interrupted + // asynchronously by sigtramp et al) this may occur if code has jumped + // through a NULL pointer -- we want to be able to unwind past that frame + // to help find the bug. + + if (ABISP abi_sp = m_thread.GetProcess()->GetABI()) + pc = abi_sp->FixCodeAddress(pc); + + return !(m_all_registers_available == false && + above_trap_handler == false && (pc == 0 || pc == 1)); + } else { + return false; + } +} + +void RegisterContextUnwind::UnwindLogMsg(const char *fmt, ...) { + Log *log = GetLog(LLDBLog::Unwind); + if (!log) + return; + + va_list args; + va_start(args, fmt); + + llvm::SmallString<0> logmsg; + if (VASprintf(logmsg, fmt, args)) { + LLDB_LOGF(log, "%*sth%d/fr%u %s", + m_frame_number < 100 ? m_frame_number : 100, "", + m_thread.GetIndexID(), m_frame_number, logmsg.c_str()); + } + va_end(args); +} + +void RegisterContextUnwind::UnwindLogMsgVerbose(const char *fmt, ...) { + Log *log = GetLog(LLDBLog::Unwind); + if (!log || !log->GetVerbose()) + return; + + va_list args; + va_start(args, fmt); + + llvm::SmallString<0> logmsg; + if (VASprintf(logmsg, fmt, args)) { + LLDB_LOGF(log, "%*sth%d/fr%u %s", + m_frame_number < 100 ? m_frame_number : 100, "", + m_thread.GetIndexID(), m_frame_number, logmsg.c_str()); + } + va_end(args); +} diff --git a/contrib/llvm-project/lldb/source/Target/RegisterFlags.cpp b/contrib/llvm-project/lldb/source/Target/RegisterFlags.cpp new file mode 100644 index 000000000000..976e03870ad9 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/RegisterFlags.cpp @@ -0,0 +1,429 @@ +//===-- RegisterFlags.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 "lldb/Target/RegisterFlags.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/StreamString.h" + +#include "llvm/ADT/StringExtras.h" + +#include <limits> +#include <numeric> +#include <optional> + +using namespace lldb_private; + +RegisterFlags::Field::Field(std::string name, unsigned start, unsigned end) + : m_name(std::move(name)), m_start(start), m_end(end), + m_enum_type(nullptr) { + assert(m_start <= m_end && "Start bit must be <= end bit."); +} + +RegisterFlags::Field::Field(std::string name, unsigned bit_position) + : m_name(std::move(name)), m_start(bit_position), m_end(bit_position), + m_enum_type(nullptr) {} + +RegisterFlags::Field::Field(std::string name, unsigned start, unsigned end, + const FieldEnum *enum_type) + : m_name(std::move(name)), m_start(start), m_end(end), + m_enum_type(enum_type) { + if (m_enum_type) { + // Check that all values fit into this field. The XML parser will also + // do this check so at runtime nothing should fail this check. + // We can also make enums in C++ at compile time, which might fail this + // check, so we catch them before it makes it into a release. + uint64_t max_value = GetMaxValue(); + UNUSED_IF_ASSERT_DISABLED(max_value); + for (const auto &enumerator : m_enum_type->GetEnumerators()) { + UNUSED_IF_ASSERT_DISABLED(enumerator); + assert(enumerator.m_value <= max_value && + "Enumerator value exceeds maximum value for this field"); + } + } +} + +void RegisterFlags::Field::DumpToLog(Log *log) const { + LLDB_LOG(log, " Name: \"{0}\" Start: {1} End: {2}", m_name.c_str(), m_start, + m_end); +} + +bool RegisterFlags::Field::Overlaps(const Field &other) const { + unsigned overlap_start = std::max(GetStart(), other.GetStart()); + unsigned overlap_end = std::min(GetEnd(), other.GetEnd()); + return overlap_start <= overlap_end; +} + +unsigned RegisterFlags::Field::PaddingDistance(const Field &other) const { + assert(!Overlaps(other) && + "Cannot get padding distance for overlapping fields."); + assert((other < (*this)) && "Expected fields in MSB to LSB order."); + + // If they don't overlap they are either next to each other or separated + // by some number of bits. + + // Where left will be the MSB and right will be the LSB. + unsigned lhs_start = GetStart(); + unsigned rhs_end = other.GetStart() + other.GetSizeInBits() - 1; + + if (*this < other) { + lhs_start = other.GetStart(); + rhs_end = GetStart() + GetSizeInBits() - 1; + } + + return lhs_start - rhs_end - 1; +} + +unsigned RegisterFlags::Field::GetSizeInBits(unsigned start, unsigned end) { + return end - start + 1; +} + +unsigned RegisterFlags::Field::GetSizeInBits() const { + return GetSizeInBits(m_start, m_end); +} + +uint64_t RegisterFlags::Field::GetMaxValue(unsigned start, unsigned end) { + uint64_t max = std::numeric_limits<uint64_t>::max(); + unsigned bits = GetSizeInBits(start, end); + // If the field is >= 64 bits the shift below would be undefined. + // We assume the GDB client has discarded any field that would fail this + // assert, it's only to check information we define directly in C++. + assert(bits <= 64 && "Cannot handle field with size > 64 bits"); + if (bits < 64) { + max = ((uint64_t)1 << bits) - 1; + } + return max; +} + +uint64_t RegisterFlags::Field::GetMaxValue() const { + return GetMaxValue(m_start, m_end); +} + +uint64_t RegisterFlags::Field::GetMask() const { + return GetMaxValue() << m_start; +} + +void RegisterFlags::SetFields(const std::vector<Field> &fields) { + // We expect that these are unsorted but do not overlap. + // They could fill the register but may have gaps. + std::vector<Field> provided_fields = fields; + + m_fields.clear(); + m_fields.reserve(provided_fields.size()); + + // ProcessGDBRemote should have sorted these in descending order already. + assert(std::is_sorted(provided_fields.rbegin(), provided_fields.rend())); + + // Build a new list of fields that includes anonymous (empty name) fields + // wherever there is a gap. This will simplify processing later. + std::optional<Field> previous_field; + unsigned register_msb = (m_size * 8) - 1; + for (auto field : provided_fields) { + if (previous_field) { + unsigned padding = previous_field->PaddingDistance(field); + if (padding) { + // -1 to end just before the previous field. + unsigned end = previous_field->GetStart() - 1; + // +1 because if you want to pad 1 bit you want to start and end + // on the same bit. + m_fields.push_back(Field("", field.GetEnd() + 1, end)); + } + } else { + // This is the first field. Check that it starts at the register's MSB. + if (field.GetEnd() != register_msb) + m_fields.push_back(Field("", field.GetEnd() + 1, register_msb)); + } + m_fields.push_back(field); + previous_field = field; + } + + // The last field may not extend all the way to bit 0. + if (previous_field && previous_field->GetStart() != 0) + m_fields.push_back(Field("", 0, previous_field->GetStart() - 1)); +} + +RegisterFlags::RegisterFlags(std::string id, unsigned size, + const std::vector<Field> &fields) + : m_id(std::move(id)), m_size(size) { + SetFields(fields); +} + +void RegisterFlags::DumpToLog(Log *log) const { + LLDB_LOG(log, "ID: \"{0}\" Size: {1}", m_id.c_str(), m_size); + for (const Field &field : m_fields) + field.DumpToLog(log); +} + +static StreamString FormatCell(const StreamString &content, + unsigned column_width) { + unsigned pad = column_width - content.GetString().size(); + std::string pad_l; + std::string pad_r; + if (pad) { + pad_l = std::string(pad / 2, ' '); + pad_r = std::string((pad / 2) + (pad % 2), ' '); + } + + StreamString aligned; + aligned.Printf("|%s%s%s", pad_l.c_str(), content.GetString().data(), + pad_r.c_str()); + return aligned; +} + +static void EmitTable(std::string &out, std::array<std::string, 3> &table) { + // Close the table. + for (std::string &line : table) + line += '|'; + + out += std::accumulate(table.begin() + 1, table.end(), table.front(), + [](std::string lhs, const auto &rhs) { + return std::move(lhs) + "\n" + rhs; + }); +} + +std::string RegisterFlags::AsTable(uint32_t max_width) const { + std::string table; + // position / gridline / name + std::array<std::string, 3> lines; + uint32_t current_width = 0; + + for (const RegisterFlags::Field &field : m_fields) { + StreamString position; + if (field.GetEnd() == field.GetStart()) + position.Printf(" %d ", field.GetEnd()); + else + position.Printf(" %d-%d ", field.GetEnd(), field.GetStart()); + + StreamString name; + name.Printf(" %s ", field.GetName().c_str()); + + unsigned column_width = position.GetString().size(); + unsigned name_width = name.GetString().size(); + if (name_width > column_width) + column_width = name_width; + + // If the next column would overflow and we have already formatted at least + // one column, put out what we have and move to a new table on the next line + // (+1 here because we need to cap the ends with '|'). If this is the first + // column, just let it overflow and we'll wrap next time around. There's not + // much we can do with a very small terminal. + if (current_width && ((current_width + column_width + 1) >= max_width)) { + EmitTable(table, lines); + // Blank line between each. + table += "\n\n"; + + for (std::string &line : lines) + line.clear(); + current_width = 0; + } + + StreamString aligned_position = FormatCell(position, column_width); + lines[0] += aligned_position.GetString(); + StreamString grid; + grid << '|' << std::string(column_width, '-'); + lines[1] += grid.GetString(); + StreamString aligned_name = FormatCell(name, column_width); + lines[2] += aligned_name.GetString(); + + // +1 for the left side '|'. + current_width += column_width + 1; + } + + // If we didn't overflow and still have table to print out. + if (lines[0].size()) + EmitTable(table, lines); + + return table; +} + +// Print enums as: +// value = name, value2 = name2 +// Subject to the limits of the terminal width. +static void DumpEnumerators(StreamString &strm, size_t indent, + size_t current_width, uint32_t max_width, + const FieldEnum::Enumerators &enumerators) { + for (auto it = enumerators.cbegin(); it != enumerators.cend(); ++it) { + StreamString enumerator_strm; + // The first enumerator of a line doesn't need to be separated. + if (current_width != indent) + enumerator_strm << ' '; + + enumerator_strm.Printf("%" PRIu64 " = %s", it->m_value, it->m_name.c_str()); + + // Don't put "," after the last enumerator. + if (std::next(it) != enumerators.cend()) + enumerator_strm << ","; + + llvm::StringRef enumerator_string = enumerator_strm.GetString(); + // If printing the next enumerator would take us over the width, start + // a new line. However, if we're printing the first enumerator of this + // line, don't start a new one. Resulting in there being at least one per + // line. + // + // This means for very small widths we get: + // A: 0 = foo, + // 1 = bar + // Instead of: + // A: + // 0 = foo, + // 1 = bar + if ((current_width + enumerator_string.size() > max_width) && + current_width != indent) { + current_width = indent; + strm << '\n' << std::string(indent, ' '); + // We're going to a new line so we don't need a space before the + // name of the enumerator. + enumerator_string = enumerator_string.drop_front(); + } + + current_width += enumerator_string.size(); + strm << enumerator_string; + } +} + +std::string RegisterFlags::DumpEnums(uint32_t max_width) const { + StreamString strm; + bool printed_enumerators_once = false; + + for (const auto &field : m_fields) { + const FieldEnum *enum_type = field.GetEnum(); + if (!enum_type) + continue; + + const FieldEnum::Enumerators &enumerators = enum_type->GetEnumerators(); + if (enumerators.empty()) + continue; + + // Break between enumerators of different fields. + if (printed_enumerators_once) + strm << "\n\n"; + else + printed_enumerators_once = true; + + std::string name_string = field.GetName() + ": "; + size_t indent = name_string.size(); + size_t current_width = indent; + + strm << name_string; + + DumpEnumerators(strm, indent, current_width, max_width, enumerators); + } + + return strm.GetString().str(); +} + +void RegisterFlags::EnumsToXML(Stream &strm, llvm::StringSet<> &seen) const { + for (const Field &field : m_fields) + if (const FieldEnum *enum_type = field.GetEnum()) { + const std::string &id = enum_type->GetID(); + if (!seen.contains(id)) { + enum_type->ToXML(strm, GetSize()); + seen.insert(id); + } + } +} + +void FieldEnum::ToXML(Stream &strm, unsigned size) const { + // Example XML: + // <enum id="foo" size="4"> + // <evalue name="bar" value="1"/> + // </enum> + // Note that "size" is only emitted for GDB compatibility, LLDB does not need + // it. + + strm.Indent(); + strm << "<enum id=\"" << GetID() << "\" "; + // This is the size of the underlying enum type if this were a C type. + // In other words, the size of the register in bytes. + strm.Printf("size=\"%d\"", size); + + const Enumerators &enumerators = GetEnumerators(); + if (enumerators.empty()) { + strm << "/>\n"; + return; + } + + strm << ">\n"; + strm.IndentMore(); + for (const auto &enumerator : enumerators) { + strm.Indent(); + enumerator.ToXML(strm); + strm.PutChar('\n'); + } + strm.IndentLess(); + strm.Indent("</enum>\n"); +} + +void FieldEnum::Enumerator::ToXML(Stream &strm) const { + std::string escaped_name; + llvm::raw_string_ostream escape_strm(escaped_name); + llvm::printHTMLEscaped(m_name, escape_strm); + strm.Printf("<evalue name=\"%s\" value=\"%" PRIu64 "\"/>", + escaped_name.c_str(), m_value); +} + +void FieldEnum::Enumerator::DumpToLog(Log *log) const { + LLDB_LOG(log, " Name: \"{0}\" Value: {1}", m_name.c_str(), m_value); +} + +void FieldEnum::DumpToLog(Log *log) const { + LLDB_LOG(log, "ID: \"{0}\"", m_id.c_str()); + for (const auto &enumerator : GetEnumerators()) + enumerator.DumpToLog(log); +} + +void RegisterFlags::ToXML(Stream &strm) const { + // Example XML: + // <flags id="cpsr_flags" size="4"> + // <field name="incorrect" start="0" end="0"/> + // </flags> + strm.Indent(); + strm << "<flags id=\"" << GetID() << "\" "; + strm.Printf("size=\"%d\"", GetSize()); + strm << ">"; + for (const Field &field : m_fields) { + // Skip padding fields. + if (field.GetName().empty()) + continue; + + strm << "\n"; + strm.IndentMore(); + field.ToXML(strm); + strm.IndentLess(); + } + strm.PutChar('\n'); + strm.Indent("</flags>\n"); +} + +void RegisterFlags::Field::ToXML(Stream &strm) const { + // Example XML with an enum: + // <field name="correct" start="0" end="0" type="some_enum"> + // Without: + // <field name="correct" start="0" end="0"/> + strm.Indent(); + strm << "<field name=\""; + + std::string escaped_name; + llvm::raw_string_ostream escape_strm(escaped_name); + llvm::printHTMLEscaped(GetName(), escape_strm); + strm << escaped_name << "\" "; + + strm.Printf("start=\"%d\" end=\"%d\"", GetStart(), GetEnd()); + + if (const FieldEnum *enum_type = GetEnum()) + strm << " type=\"" << enum_type->GetID() << "\""; + + strm << "/>"; +} + +FieldEnum::FieldEnum(std::string id, const Enumerators &enumerators) + : m_id(id), m_enumerators(enumerators) { + for (const auto &enumerator : m_enumerators) { + UNUSED_IF_ASSERT_DISABLED(enumerator); + assert(enumerator.m_name.size() && "Enumerator name cannot be empty"); + } +}
\ No newline at end of file diff --git a/contrib/llvm-project/lldb/source/Target/RegisterNumber.cpp b/contrib/llvm-project/lldb/source/Target/RegisterNumber.cpp new file mode 100644 index 000000000000..e5610bf58854 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/RegisterNumber.cpp @@ -0,0 +1,105 @@ +//===-- RegisterNumber.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 "lldb/Target/RegisterNumber.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/Thread.h" + +using namespace lldb_private; + +RegisterNumber::RegisterNumber(lldb_private::Thread &thread, + lldb::RegisterKind kind, uint32_t num) + : m_reg_ctx_sp(thread.GetRegisterContext()), m_regnum(num), m_kind(kind), + m_kind_regnum_map(), m_name("") { + if (m_reg_ctx_sp.get()) { + const lldb_private::RegisterInfo *reginfo = + m_reg_ctx_sp->GetRegisterInfoAtIndex( + GetAsKind(lldb::eRegisterKindLLDB)); + if (reginfo && reginfo->name) { + m_name = reginfo->name; + } + } +} + +RegisterNumber::RegisterNumber() : m_reg_ctx_sp(), m_kind_regnum_map() {} + +void RegisterNumber::init(lldb_private::Thread &thread, lldb::RegisterKind kind, + uint32_t num) { + m_reg_ctx_sp = thread.GetRegisterContext(); + m_regnum = num; + m_kind = kind; + if (m_reg_ctx_sp.get()) { + const lldb_private::RegisterInfo *reginfo = + m_reg_ctx_sp->GetRegisterInfoAtIndex( + GetAsKind(lldb::eRegisterKindLLDB)); + if (reginfo && reginfo->name) { + m_name = reginfo->name; + } + } +} + +const RegisterNumber &RegisterNumber::operator=(const RegisterNumber &rhs) { + m_reg_ctx_sp = rhs.m_reg_ctx_sp; + m_regnum = rhs.m_regnum; + m_kind = rhs.m_kind; + for (auto it : rhs.m_kind_regnum_map) + m_kind_regnum_map[it.first] = it.second; + m_name = rhs.m_name; + return *this; +} + +bool RegisterNumber::operator==(RegisterNumber &rhs) { + if (IsValid() != rhs.IsValid()) + return false; + + if (m_kind == rhs.m_kind) { + return m_regnum == rhs.m_regnum; + } + + uint32_t rhs_regnum = rhs.GetAsKind(m_kind); + if (rhs_regnum != LLDB_INVALID_REGNUM) { + return m_regnum == rhs_regnum; + } + uint32_t lhs_regnum = GetAsKind(rhs.m_kind); + { return lhs_regnum == rhs.m_regnum; } + return false; +} + +bool RegisterNumber::operator!=(RegisterNumber &rhs) { return !(*this == rhs); } + +bool RegisterNumber::IsValid() const { + return m_reg_ctx_sp.get() && m_kind != lldb::kNumRegisterKinds && + m_regnum != LLDB_INVALID_REGNUM; +} + +uint32_t RegisterNumber::GetAsKind(lldb::RegisterKind kind) { + if (m_regnum == LLDB_INVALID_REGNUM) + return LLDB_INVALID_REGNUM; + + if (kind == m_kind) + return m_regnum; + + Collection::iterator iter = m_kind_regnum_map.find(kind); + if (iter != m_kind_regnum_map.end()) { + return iter->second; + } + uint32_t output_regnum = LLDB_INVALID_REGNUM; + if (m_reg_ctx_sp && + m_reg_ctx_sp->ConvertBetweenRegisterKinds(m_kind, m_regnum, kind, + output_regnum) && + output_regnum != LLDB_INVALID_REGNUM) { + m_kind_regnum_map[kind] = output_regnum; + } + return output_regnum; +} + +uint32_t RegisterNumber::GetRegisterNumber() const { return m_regnum; } + +lldb::RegisterKind RegisterNumber::GetRegisterKind() const { return m_kind; } + +const char *RegisterNumber::GetName() { return m_name; } diff --git a/contrib/llvm-project/lldb/source/Target/RemoteAwarePlatform.cpp b/contrib/llvm-project/lldb/source/Target/RemoteAwarePlatform.cpp new file mode 100644 index 000000000000..cac738ea67b4 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/RemoteAwarePlatform.cpp @@ -0,0 +1,282 @@ +//===-- RemoteAwarePlatform.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 "lldb/Target/RemoteAwarePlatform.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleList.h" +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/Host.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Utility/StreamString.h" +#include <optional> + +using namespace lldb_private; +using namespace lldb; + +bool RemoteAwarePlatform::GetModuleSpec(const FileSpec &module_file_spec, + const ArchSpec &arch, + ModuleSpec &module_spec) { + if (m_remote_platform_sp) + return m_remote_platform_sp->GetModuleSpec(module_file_spec, arch, + module_spec); + + return false; +} + +Status RemoteAwarePlatform::ResolveExecutable( + const ModuleSpec &module_spec, lldb::ModuleSP &exe_module_sp, + const FileSpecList *module_search_paths_ptr) { + ModuleSpec resolved_module_spec(module_spec); + + // The host platform can resolve the path more aggressively. + if (IsHost()) { + FileSpec &resolved_file_spec = resolved_module_spec.GetFileSpec(); + + if (!FileSystem::Instance().Exists(resolved_file_spec)) { + resolved_module_spec.GetFileSpec().SetFile(resolved_file_spec.GetPath(), + FileSpec::Style::native); + FileSystem::Instance().Resolve(resolved_file_spec); + } + + if (!FileSystem::Instance().Exists(resolved_file_spec)) + FileSystem::Instance().ResolveExecutableLocation(resolved_file_spec); + } else if (m_remote_platform_sp) { + return GetCachedExecutable(resolved_module_spec, exe_module_sp, + module_search_paths_ptr); + } + + return Platform::ResolveExecutable(resolved_module_spec, exe_module_sp, + module_search_paths_ptr); +} + +Status RemoteAwarePlatform::RunShellCommand( + llvm::StringRef command, const FileSpec &working_dir, int *status_ptr, + int *signo_ptr, std::string *command_output, + const Timeout<std::micro> &timeout) { + return RunShellCommand(llvm::StringRef(), command, working_dir, status_ptr, + signo_ptr, command_output, timeout); +} + +Status RemoteAwarePlatform::RunShellCommand( + llvm::StringRef shell, llvm::StringRef command, const FileSpec &working_dir, + int *status_ptr, int *signo_ptr, std::string *command_output, + const Timeout<std::micro> &timeout) { + if (m_remote_platform_sp) + return m_remote_platform_sp->RunShellCommand(shell, command, working_dir, + status_ptr, signo_ptr, + command_output, timeout); + return Platform::RunShellCommand(shell, command, working_dir, status_ptr, + signo_ptr, command_output, timeout); +} + +Status RemoteAwarePlatform::MakeDirectory(const FileSpec &file_spec, + uint32_t file_permissions) { + if (m_remote_platform_sp) + return m_remote_platform_sp->MakeDirectory(file_spec, file_permissions); + return Platform::MakeDirectory(file_spec, file_permissions); +} + +Status RemoteAwarePlatform::GetFilePermissions(const FileSpec &file_spec, + uint32_t &file_permissions) { + if (m_remote_platform_sp) + return m_remote_platform_sp->GetFilePermissions(file_spec, + file_permissions); + return Platform::GetFilePermissions(file_spec, file_permissions); +} + +Status RemoteAwarePlatform::SetFilePermissions(const FileSpec &file_spec, + uint32_t file_permissions) { + if (m_remote_platform_sp) + return m_remote_platform_sp->SetFilePermissions(file_spec, + file_permissions); + return Platform::SetFilePermissions(file_spec, file_permissions); +} + +lldb::user_id_t RemoteAwarePlatform::OpenFile(const FileSpec &file_spec, + File::OpenOptions flags, + uint32_t mode, Status &error) { + if (m_remote_platform_sp) + return m_remote_platform_sp->OpenFile(file_spec, flags, mode, error); + return Platform::OpenFile(file_spec, flags, mode, error); +} + +bool RemoteAwarePlatform::CloseFile(lldb::user_id_t fd, Status &error) { + if (m_remote_platform_sp) + return m_remote_platform_sp->CloseFile(fd, error); + return Platform::CloseFile(fd, error); +} + +uint64_t RemoteAwarePlatform::ReadFile(lldb::user_id_t fd, uint64_t offset, + void *dst, uint64_t dst_len, + Status &error) { + if (m_remote_platform_sp) + return m_remote_platform_sp->ReadFile(fd, offset, dst, dst_len, error); + return Platform::ReadFile(fd, offset, dst, dst_len, error); +} + +uint64_t RemoteAwarePlatform::WriteFile(lldb::user_id_t fd, uint64_t offset, + const void *src, uint64_t src_len, + Status &error) { + if (m_remote_platform_sp) + return m_remote_platform_sp->WriteFile(fd, offset, src, src_len, error); + return Platform::WriteFile(fd, offset, src, src_len, error); +} + +lldb::user_id_t RemoteAwarePlatform::GetFileSize(const FileSpec &file_spec) { + if (m_remote_platform_sp) + return m_remote_platform_sp->GetFileSize(file_spec); + return Platform::GetFileSize(file_spec); +} + +Status RemoteAwarePlatform::CreateSymlink(const FileSpec &src, + const FileSpec &dst) { + if (m_remote_platform_sp) + return m_remote_platform_sp->CreateSymlink(src, dst); + return Platform::CreateSymlink(src, dst); +} + +bool RemoteAwarePlatform::GetFileExists(const FileSpec &file_spec) { + if (m_remote_platform_sp) + return m_remote_platform_sp->GetFileExists(file_spec); + return Platform::GetFileExists(file_spec); +} + +Status RemoteAwarePlatform::Unlink(const FileSpec &file_spec) { + if (m_remote_platform_sp) + return m_remote_platform_sp->Unlink(file_spec); + return Platform::Unlink(file_spec); +} + +llvm::ErrorOr<llvm::MD5::MD5Result> +RemoteAwarePlatform::CalculateMD5(const FileSpec &file_spec) { + if (m_remote_platform_sp) + return m_remote_platform_sp->CalculateMD5(file_spec); + return Platform::CalculateMD5(file_spec); +} + +FileSpec RemoteAwarePlatform::GetRemoteWorkingDirectory() { + if (IsRemote() && m_remote_platform_sp) + return m_remote_platform_sp->GetRemoteWorkingDirectory(); + return Platform::GetRemoteWorkingDirectory(); +} + +bool RemoteAwarePlatform::SetRemoteWorkingDirectory( + const FileSpec &working_dir) { + if (IsRemote() && m_remote_platform_sp) + return m_remote_platform_sp->SetRemoteWorkingDirectory(working_dir); + return Platform::SetRemoteWorkingDirectory(working_dir); +} + +Status RemoteAwarePlatform::GetFileWithUUID(const FileSpec &platform_file, + const UUID *uuid_ptr, + FileSpec &local_file) { + if (IsRemote() && m_remote_platform_sp) + return m_remote_platform_sp->GetFileWithUUID(platform_file, uuid_ptr, + local_file); + + // Default to the local case + local_file = platform_file; + return Status(); +} + +bool RemoteAwarePlatform::GetRemoteOSVersion() { + if (m_remote_platform_sp) { + m_os_version = m_remote_platform_sp->GetOSVersion(); + return !m_os_version.empty(); + } + return false; +} + +std::optional<std::string> RemoteAwarePlatform::GetRemoteOSBuildString() { + if (m_remote_platform_sp) + return m_remote_platform_sp->GetRemoteOSBuildString(); + return std::nullopt; +} + +std::optional<std::string> RemoteAwarePlatform::GetRemoteOSKernelDescription() { + if (m_remote_platform_sp) + return m_remote_platform_sp->GetRemoteOSKernelDescription(); + return std::nullopt; +} + +ArchSpec RemoteAwarePlatform::GetRemoteSystemArchitecture() { + if (m_remote_platform_sp) + return m_remote_platform_sp->GetRemoteSystemArchitecture(); + return ArchSpec(); +} + +const char *RemoteAwarePlatform::GetHostname() { + if (m_remote_platform_sp) + return m_remote_platform_sp->GetHostname(); + return Platform::GetHostname(); +} + +UserIDResolver &RemoteAwarePlatform::GetUserIDResolver() { + if (m_remote_platform_sp) + return m_remote_platform_sp->GetUserIDResolver(); + return Platform::GetUserIDResolver(); +} + +Environment RemoteAwarePlatform::GetEnvironment() { + if (m_remote_platform_sp) + return m_remote_platform_sp->GetEnvironment(); + return Platform::GetEnvironment(); +} + +bool RemoteAwarePlatform::IsConnected() const { + if (m_remote_platform_sp) + return m_remote_platform_sp->IsConnected(); + return Platform::IsConnected(); +} + +bool RemoteAwarePlatform::GetProcessInfo(lldb::pid_t pid, + ProcessInstanceInfo &process_info) { + if (m_remote_platform_sp) + return m_remote_platform_sp->GetProcessInfo(pid, process_info); + return Platform::GetProcessInfo(pid, process_info); +} + +uint32_t +RemoteAwarePlatform::FindProcesses(const ProcessInstanceInfoMatch &match_info, + ProcessInstanceInfoList &process_infos) { + if (m_remote_platform_sp) + return m_remote_platform_sp->FindProcesses(match_info, process_infos); + return Platform::FindProcesses(match_info, process_infos); +} + +lldb::ProcessSP RemoteAwarePlatform::ConnectProcess(llvm::StringRef connect_url, + llvm::StringRef plugin_name, + Debugger &debugger, + Target *target, + Status &error) { + if (m_remote_platform_sp) + return m_remote_platform_sp->ConnectProcess(connect_url, plugin_name, + debugger, target, error); + return Platform::ConnectProcess(connect_url, plugin_name, debugger, target, + error); +} + +Status RemoteAwarePlatform::LaunchProcess(ProcessLaunchInfo &launch_info) { + if (m_remote_platform_sp) + return m_remote_platform_sp->LaunchProcess(launch_info); + return Platform::LaunchProcess(launch_info); +} + +Status RemoteAwarePlatform::KillProcess(const lldb::pid_t pid) { + if (m_remote_platform_sp) + return m_remote_platform_sp->KillProcess(pid); + return Platform::KillProcess(pid); +} + +size_t RemoteAwarePlatform::ConnectToWaitingProcesses(Debugger &debugger, + Status &error) { + if (m_remote_platform_sp) + return m_remote_platform_sp->ConnectToWaitingProcesses(debugger, error); + return Platform::ConnectToWaitingProcesses(debugger, error); +} diff --git a/contrib/llvm-project/lldb/source/Target/SectionLoadHistory.cpp b/contrib/llvm-project/lldb/source/Target/SectionLoadHistory.cpp new file mode 100644 index 000000000000..f329d425e34b --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/SectionLoadHistory.cpp @@ -0,0 +1,164 @@ +//===-- SectionLoadHistory.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 "lldb/Target/SectionLoadHistory.h" + +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Utility/Stream.h" + +using namespace lldb; +using namespace lldb_private; + +bool SectionLoadHistory::IsEmpty() const { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + return m_stop_id_to_section_load_list.empty(); +} + +void SectionLoadHistory::Clear() { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + m_stop_id_to_section_load_list.clear(); +} + +uint32_t SectionLoadHistory::GetLastStopID() const { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + if (m_stop_id_to_section_load_list.empty()) + return 0; + else + return m_stop_id_to_section_load_list.rbegin()->first; +} + +SectionLoadList * +SectionLoadHistory::GetSectionLoadListForStopID(uint32_t stop_id, + bool read_only) { + if (!m_stop_id_to_section_load_list.empty()) { + if (read_only) { + // The section load list is for reading data only so we don't need to + // create a new SectionLoadList for the current stop ID, just return the + // section load list for the stop ID that is equal to or less than the + // current stop ID + if (stop_id == eStopIDNow) { + // If we are asking for the latest and greatest value, it is always at + // the end of our list because that will be the highest stop ID. + StopIDToSectionLoadList::reverse_iterator rpos = + m_stop_id_to_section_load_list.rbegin(); + return rpos->second.get(); + } else { + StopIDToSectionLoadList::iterator pos = + m_stop_id_to_section_load_list.lower_bound(stop_id); + if (pos != m_stop_id_to_section_load_list.end() && + pos->first == stop_id) + return pos->second.get(); + else if (pos != m_stop_id_to_section_load_list.begin()) { + --pos; + return pos->second.get(); + } + } + } else { + // You can only use "eStopIDNow" when reading from the section load + // history + assert(stop_id != eStopIDNow); + + // We are updating the section load list (not read only), so if the stop + // ID passed in isn't the same as the last stop ID in our collection, + // then create a new node using the current stop ID + StopIDToSectionLoadList::iterator pos = + m_stop_id_to_section_load_list.lower_bound(stop_id); + if (pos != m_stop_id_to_section_load_list.end() && + pos->first == stop_id) { + // We already have an entry for this value + return pos->second.get(); + } + + // We must make a new section load list that is based on the last valid + // section load list, so here we copy the last section load list and add + // a new node for the current stop ID. + StopIDToSectionLoadList::reverse_iterator rpos = + m_stop_id_to_section_load_list.rbegin(); + SectionLoadListSP section_load_list_sp( + new SectionLoadList(*rpos->second)); + m_stop_id_to_section_load_list[stop_id] = section_load_list_sp; + return section_load_list_sp.get(); + } + } + SectionLoadListSP section_load_list_sp(new SectionLoadList()); + if (stop_id == eStopIDNow) + stop_id = 0; + m_stop_id_to_section_load_list[stop_id] = section_load_list_sp; + return section_load_list_sp.get(); +} + +SectionLoadList &SectionLoadHistory::GetCurrentSectionLoadList() { + const bool read_only = true; + std::lock_guard<std::recursive_mutex> guard(m_mutex); + SectionLoadList *section_load_list = + GetSectionLoadListForStopID(eStopIDNow, read_only); + assert(section_load_list != nullptr); + return *section_load_list; +} + +addr_t +SectionLoadHistory::GetSectionLoadAddress(uint32_t stop_id, + const lldb::SectionSP §ion_sp) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + const bool read_only = true; + SectionLoadList *section_load_list = + GetSectionLoadListForStopID(stop_id, read_only); + return section_load_list->GetSectionLoadAddress(section_sp); +} + +bool SectionLoadHistory::ResolveLoadAddress(uint32_t stop_id, addr_t load_addr, + Address &so_addr) { + // First find the top level section that this load address exists in + std::lock_guard<std::recursive_mutex> guard(m_mutex); + const bool read_only = true; + SectionLoadList *section_load_list = + GetSectionLoadListForStopID(stop_id, read_only); + return section_load_list->ResolveLoadAddress(load_addr, so_addr); +} + +bool SectionLoadHistory::SetSectionLoadAddress( + uint32_t stop_id, const lldb::SectionSP §ion_sp, addr_t load_addr, + bool warn_multiple) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + const bool read_only = false; + SectionLoadList *section_load_list = + GetSectionLoadListForStopID(stop_id, read_only); + return section_load_list->SetSectionLoadAddress(section_sp, load_addr, + warn_multiple); +} + +size_t +SectionLoadHistory::SetSectionUnloaded(uint32_t stop_id, + const lldb::SectionSP §ion_sp) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + const bool read_only = false; + SectionLoadList *section_load_list = + GetSectionLoadListForStopID(stop_id, read_only); + return section_load_list->SetSectionUnloaded(section_sp); +} + +bool SectionLoadHistory::SetSectionUnloaded(uint32_t stop_id, + const lldb::SectionSP §ion_sp, + addr_t load_addr) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + const bool read_only = false; + SectionLoadList *section_load_list = + GetSectionLoadListForStopID(stop_id, read_only); + return section_load_list->SetSectionUnloaded(section_sp, load_addr); +} + +void SectionLoadHistory::Dump(Stream &s, Target *target) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + StopIDToSectionLoadList::iterator pos, + end = m_stop_id_to_section_load_list.end(); + for (pos = m_stop_id_to_section_load_list.begin(); pos != end; ++pos) { + s.Printf("StopID = %u:\n", pos->first); + pos->second->Dump(s, target); + s.EOL(); + } +} diff --git a/contrib/llvm-project/lldb/source/Target/SectionLoadList.cpp b/contrib/llvm-project/lldb/source/Target/SectionLoadList.cpp new file mode 100644 index 000000000000..d4bf0573b229 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/SectionLoadList.cpp @@ -0,0 +1,269 @@ +//===-- SectionLoadList.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 "lldb/Target/SectionLoadList.h" + +#include "lldb/Core/Module.h" +#include "lldb/Core/Section.h" +#include "lldb/Symbol/Block.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Stream.h" + +using namespace lldb; +using namespace lldb_private; + +SectionLoadList::SectionLoadList(const SectionLoadList &rhs) + : m_addr_to_sect(), m_sect_to_addr(), m_mutex() { + std::lock_guard<std::recursive_mutex> guard(rhs.m_mutex); + m_addr_to_sect = rhs.m_addr_to_sect; + m_sect_to_addr = rhs.m_sect_to_addr; +} + +void SectionLoadList::operator=(const SectionLoadList &rhs) { + std::lock(m_mutex, rhs.m_mutex); + std::lock_guard<std::recursive_mutex> lhs_guard(m_mutex, std::adopt_lock); + std::lock_guard<std::recursive_mutex> rhs_guard(rhs.m_mutex, std::adopt_lock); + m_addr_to_sect = rhs.m_addr_to_sect; + m_sect_to_addr = rhs.m_sect_to_addr; +} + +bool SectionLoadList::IsEmpty() const { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + return m_addr_to_sect.empty(); +} + +void SectionLoadList::Clear() { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + m_addr_to_sect.clear(); + m_sect_to_addr.clear(); +} + +addr_t +SectionLoadList::GetSectionLoadAddress(const lldb::SectionSP §ion) const { + // TODO: add support for the same section having multiple load addresses + addr_t section_load_addr = LLDB_INVALID_ADDRESS; + if (section) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + sect_to_addr_collection::const_iterator pos = + m_sect_to_addr.find(section.get()); + + if (pos != m_sect_to_addr.end()) + section_load_addr = pos->second; + } + return section_load_addr; +} + +bool SectionLoadList::SetSectionLoadAddress(const lldb::SectionSP §ion, + addr_t load_addr, + bool warn_multiple) { + Log *log = GetLog(LLDBLog::DynamicLoader); + ModuleSP module_sp(section->GetModule()); + + if (module_sp) { + LLDB_LOGV(log, "(section = {0} ({1}.{2}), load_addr = {3:x}) module = {4}", + section.get(), module_sp->GetFileSpec(), section->GetName(), + load_addr, module_sp.get()); + + if (section->GetByteSize() == 0) + return false; // No change + + // Fill in the section -> load_addr map + std::lock_guard<std::recursive_mutex> guard(m_mutex); + sect_to_addr_collection::iterator sta_pos = + m_sect_to_addr.find(section.get()); + if (sta_pos != m_sect_to_addr.end()) { + if (load_addr == sta_pos->second) + return false; // No change... + else + sta_pos->second = load_addr; + } else + m_sect_to_addr[section.get()] = load_addr; + + // Fill in the load_addr -> section map + addr_to_sect_collection::iterator ats_pos = m_addr_to_sect.find(load_addr); + if (ats_pos != m_addr_to_sect.end()) { + // Some sections are ok to overlap, and for others we should warn. When + // we have multiple load addresses that correspond to a section, we will + // always attribute the section to the be last section that claims it + // exists at that address. Sometimes it is ok for more that one section + // to be loaded at a specific load address, and other times it isn't. The + // "warn_multiple" parameter tells us if we should warn in this case or + // not. The DynamicLoader plug-in subclasses should know which sections + // should warn and which shouldn't (darwin shared cache modules all + // shared the same "__LINKEDIT" sections, so the dynamic loader can pass + // false for "warn_multiple"). + if (warn_multiple && section != ats_pos->second) { + ModuleSP module_sp(section->GetModule()); + if (module_sp) { + ModuleSP curr_module_sp(ats_pos->second->GetModule()); + if (curr_module_sp) { + module_sp->ReportWarning( + "address {0:x16} maps to more than one section: {1}.{2} and " + "{3}.{4}", + load_addr, module_sp->GetFileSpec().GetFilename().GetCString(), + section->GetName().GetCString(), + curr_module_sp->GetFileSpec().GetFilename().GetCString(), + ats_pos->second->GetName().GetCString()); + } + } + } + ats_pos->second = section; + } else { + // Remove the old address->section entry, if + // there is one. + for (const auto &entry : m_addr_to_sect) { + if (entry.second == section) { + const auto &it_pos = m_addr_to_sect.find(entry.first); + m_addr_to_sect.erase(it_pos); + break; + } + } + m_addr_to_sect[load_addr] = section; + } + return true; // Changed + + } else { + if (log) { + LLDB_LOGF( + log, + "SectionLoadList::%s (section = %p (%s), load_addr = 0x%16.16" PRIx64 + ") error: module has been deleted", + __FUNCTION__, static_cast<void *>(section.get()), + section->GetName().AsCString(), load_addr); + } + } + return false; +} + +size_t SectionLoadList::SetSectionUnloaded(const lldb::SectionSP §ion_sp) { + size_t unload_count = 0; + + if (section_sp) { + Log *log = GetLog(LLDBLog::DynamicLoader); + + if (log && log->GetVerbose()) { + ModuleSP module_sp = section_sp->GetModule(); + std::string module_name("<Unknown>"); + if (module_sp) { + const FileSpec &module_file_spec( + section_sp->GetModule()->GetFileSpec()); + module_name = module_file_spec.GetPath(); + } + LLDB_LOGF(log, "SectionLoadList::%s (section = %p (%s.%s))", __FUNCTION__, + static_cast<void *>(section_sp.get()), module_name.c_str(), + section_sp->GetName().AsCString()); + } + + std::lock_guard<std::recursive_mutex> guard(m_mutex); + + sect_to_addr_collection::iterator sta_pos = + m_sect_to_addr.find(section_sp.get()); + if (sta_pos != m_sect_to_addr.end()) { + ++unload_count; + addr_t load_addr = sta_pos->second; + m_sect_to_addr.erase(sta_pos); + + addr_to_sect_collection::iterator ats_pos = + m_addr_to_sect.find(load_addr); + if (ats_pos != m_addr_to_sect.end()) + m_addr_to_sect.erase(ats_pos); + } + } + return unload_count; +} + +bool SectionLoadList::SetSectionUnloaded(const lldb::SectionSP §ion_sp, + addr_t load_addr) { + Log *log = GetLog(LLDBLog::DynamicLoader); + + if (log && log->GetVerbose()) { + ModuleSP module_sp = section_sp->GetModule(); + std::string module_name("<Unknown>"); + if (module_sp) { + const FileSpec &module_file_spec(section_sp->GetModule()->GetFileSpec()); + module_name = module_file_spec.GetPath(); + } + LLDB_LOGF( + log, + "SectionLoadList::%s (section = %p (%s.%s), load_addr = 0x%16.16" PRIx64 + ")", + __FUNCTION__, static_cast<void *>(section_sp.get()), + module_name.c_str(), section_sp->GetName().AsCString(), load_addr); + } + bool erased = false; + std::lock_guard<std::recursive_mutex> guard(m_mutex); + sect_to_addr_collection::iterator sta_pos = + m_sect_to_addr.find(section_sp.get()); + if (sta_pos != m_sect_to_addr.end()) { + erased = true; + m_sect_to_addr.erase(sta_pos); + } + + addr_to_sect_collection::iterator ats_pos = m_addr_to_sect.find(load_addr); + if (ats_pos != m_addr_to_sect.end()) { + erased = true; + m_addr_to_sect.erase(ats_pos); + } + + return erased; +} + +bool SectionLoadList::ResolveLoadAddress(addr_t load_addr, Address &so_addr, + bool allow_section_end) const { + // First find the top level section that this load address exists in + std::lock_guard<std::recursive_mutex> guard(m_mutex); + if (!m_addr_to_sect.empty()) { + addr_to_sect_collection::const_iterator pos = + m_addr_to_sect.lower_bound(load_addr); + if (pos != m_addr_to_sect.end()) { + if (load_addr != pos->first && pos != m_addr_to_sect.begin()) + --pos; + const addr_t pos_load_addr = pos->first; + if (load_addr >= pos_load_addr) { + addr_t offset = load_addr - pos_load_addr; + if (offset < pos->second->GetByteSize() + (allow_section_end ? 1 : 0)) { + // We have found the top level section, now we need to find the + // deepest child section. + return pos->second->ResolveContainedAddress(offset, so_addr, + allow_section_end); + } + } + } else { + // There are no entries that have an address that is >= load_addr, so we + // need to check the last entry on our collection. + addr_to_sect_collection::const_reverse_iterator rpos = + m_addr_to_sect.rbegin(); + if (load_addr >= rpos->first) { + addr_t offset = load_addr - rpos->first; + if (offset < + rpos->second->GetByteSize() + (allow_section_end ? 1 : 0)) { + // We have found the top level section, now we need to find the + // deepest child section. + return rpos->second->ResolveContainedAddress(offset, so_addr, + allow_section_end); + } + } + } + } + so_addr.Clear(); + return false; +} + +void SectionLoadList::Dump(Stream &s, Target *target) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + addr_to_sect_collection::const_iterator pos, end; + for (pos = m_addr_to_sect.begin(), end = m_addr_to_sect.end(); pos != end; + ++pos) { + s.Printf("addr = 0x%16.16" PRIx64 ", section = %p: ", pos->first, + static_cast<void *>(pos->second.get())); + pos->second->Dump(s.AsRawOstream(), s.GetIndentLevel(), target, 0); + } +} diff --git a/contrib/llvm-project/lldb/source/Target/StackFrame.cpp b/contrib/llvm-project/lldb/source/Target/StackFrame.cpp new file mode 100644 index 000000000000..3a2b4d05b288 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/StackFrame.cpp @@ -0,0 +1,1982 @@ +//===-- StackFrame.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 "lldb/Target/StackFrame.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Disassembler.h" +#include "lldb/Core/FormatEntity.h" +#include "lldb/Core/Mangled.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/Value.h" +#include "lldb/Core/ValueObjectConstResult.h" +#include "lldb/Core/ValueObjectMemory.h" +#include "lldb/Core/ValueObjectVariable.h" +#include "lldb/Symbol/CompileUnit.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Symbol/SymbolContextScope.h" +#include "lldb/Symbol/SymbolFile.h" +#include "lldb/Symbol/Type.h" +#include "lldb/Symbol/VariableList.h" +#include "lldb/Target/ABI.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/StackFrameRecognizer.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/RegisterValue.h" + +#include "lldb/lldb-enumerations.h" + +#include <memory> + +using namespace lldb; +using namespace lldb_private; + +// The first bits in the flags are reserved for the SymbolContext::Scope bits +// so we know if we have tried to look up information in our internal symbol +// context (m_sc) already. +#define RESOLVED_FRAME_CODE_ADDR (uint32_t(eSymbolContextLastItem) << 1) +#define RESOLVED_FRAME_ID_SYMBOL_SCOPE (RESOLVED_FRAME_CODE_ADDR << 1) +#define GOT_FRAME_BASE (RESOLVED_FRAME_ID_SYMBOL_SCOPE << 1) +#define RESOLVED_VARIABLES (GOT_FRAME_BASE << 1) +#define RESOLVED_GLOBAL_VARIABLES (RESOLVED_VARIABLES << 1) + +StackFrame::StackFrame(const ThreadSP &thread_sp, user_id_t frame_idx, + user_id_t unwind_frame_index, addr_t cfa, + bool cfa_is_valid, addr_t pc, StackFrame::Kind kind, + bool behaves_like_zeroth_frame, + const SymbolContext *sc_ptr) + : m_thread_wp(thread_sp), m_frame_index(frame_idx), + m_concrete_frame_index(unwind_frame_index), m_reg_context_sp(), + m_id(pc, cfa, nullptr), m_frame_code_addr(pc), m_sc(), m_flags(), + m_frame_base(), m_frame_base_error(), m_cfa_is_valid(cfa_is_valid), + m_stack_frame_kind(kind), + m_behaves_like_zeroth_frame(behaves_like_zeroth_frame), + m_variable_list_sp(), m_variable_list_value_objects(), + m_recognized_frame_sp(), m_disassembly(), m_mutex() { + // If we don't have a CFA value, use the frame index for our StackID so that + // recursive functions properly aren't confused with one another on a history + // stack. + if (IsHistorical() && !m_cfa_is_valid) { + m_id.SetCFA(m_frame_index); + } + + if (sc_ptr != nullptr) { + m_sc = *sc_ptr; + m_flags.Set(m_sc.GetResolvedMask()); + } +} + +StackFrame::StackFrame(const ThreadSP &thread_sp, user_id_t frame_idx, + user_id_t unwind_frame_index, + const RegisterContextSP ®_context_sp, addr_t cfa, + addr_t pc, bool behaves_like_zeroth_frame, + const SymbolContext *sc_ptr) + : m_thread_wp(thread_sp), m_frame_index(frame_idx), + m_concrete_frame_index(unwind_frame_index), + m_reg_context_sp(reg_context_sp), m_id(pc, cfa, nullptr), + m_frame_code_addr(pc), m_sc(), m_flags(), m_frame_base(), + m_frame_base_error(), m_cfa_is_valid(true), + m_stack_frame_kind(StackFrame::Kind::Regular), + m_behaves_like_zeroth_frame(behaves_like_zeroth_frame), + m_variable_list_sp(), m_variable_list_value_objects(), + m_recognized_frame_sp(), m_disassembly(), m_mutex() { + if (sc_ptr != nullptr) { + m_sc = *sc_ptr; + m_flags.Set(m_sc.GetResolvedMask()); + } + + if (reg_context_sp && !m_sc.target_sp) { + m_sc.target_sp = reg_context_sp->CalculateTarget(); + if (m_sc.target_sp) + m_flags.Set(eSymbolContextTarget); + } +} + +StackFrame::StackFrame(const ThreadSP &thread_sp, user_id_t frame_idx, + user_id_t unwind_frame_index, + const RegisterContextSP ®_context_sp, addr_t cfa, + const Address &pc_addr, bool behaves_like_zeroth_frame, + const SymbolContext *sc_ptr) + : m_thread_wp(thread_sp), m_frame_index(frame_idx), + m_concrete_frame_index(unwind_frame_index), + m_reg_context_sp(reg_context_sp), + m_id(pc_addr.GetLoadAddress(thread_sp->CalculateTarget().get()), cfa, + nullptr), + m_frame_code_addr(pc_addr), m_sc(), m_flags(), m_frame_base(), + m_frame_base_error(), m_cfa_is_valid(true), + m_stack_frame_kind(StackFrame::Kind::Regular), + m_behaves_like_zeroth_frame(behaves_like_zeroth_frame), + m_variable_list_sp(), m_variable_list_value_objects(), + m_recognized_frame_sp(), m_disassembly(), m_mutex() { + if (sc_ptr != nullptr) { + m_sc = *sc_ptr; + m_flags.Set(m_sc.GetResolvedMask()); + } + + if (!m_sc.target_sp && reg_context_sp) { + m_sc.target_sp = reg_context_sp->CalculateTarget(); + if (m_sc.target_sp) + m_flags.Set(eSymbolContextTarget); + } + + ModuleSP pc_module_sp(pc_addr.GetModule()); + if (!m_sc.module_sp || m_sc.module_sp != pc_module_sp) { + if (pc_module_sp) { + m_sc.module_sp = pc_module_sp; + m_flags.Set(eSymbolContextModule); + } else { + m_sc.module_sp.reset(); + } + } +} + +StackFrame::~StackFrame() = default; + +StackID &StackFrame::GetStackID() { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + // Make sure we have resolved the StackID object's symbol context scope if we + // already haven't looked it up. + + if (m_flags.IsClear(RESOLVED_FRAME_ID_SYMBOL_SCOPE)) { + if (m_id.GetSymbolContextScope()) { + // We already have a symbol context scope, we just don't have our flag + // bit set. + m_flags.Set(RESOLVED_FRAME_ID_SYMBOL_SCOPE); + } else { + // Calculate the frame block and use this for the stack ID symbol context + // scope if we have one. + SymbolContextScope *scope = GetFrameBlock(); + if (scope == nullptr) { + // We don't have a block, so use the symbol + if (m_flags.IsClear(eSymbolContextSymbol)) + GetSymbolContext(eSymbolContextSymbol); + + // It is ok if m_sc.symbol is nullptr here + scope = m_sc.symbol; + } + // Set the symbol context scope (the accessor will set the + // RESOLVED_FRAME_ID_SYMBOL_SCOPE bit in m_flags). + SetSymbolContextScope(scope); + } + } + return m_id; +} + +uint32_t StackFrame::GetFrameIndex() const { + ThreadSP thread_sp = GetThread(); + if (thread_sp) + return thread_sp->GetStackFrameList()->GetVisibleStackFrameIndex( + m_frame_index); + else + return m_frame_index; +} + +void StackFrame::SetSymbolContextScope(SymbolContextScope *symbol_scope) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + m_flags.Set(RESOLVED_FRAME_ID_SYMBOL_SCOPE); + m_id.SetSymbolContextScope(symbol_scope); +} + +const Address &StackFrame::GetFrameCodeAddress() { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + if (m_flags.IsClear(RESOLVED_FRAME_CODE_ADDR) && + !m_frame_code_addr.IsSectionOffset()) { + m_flags.Set(RESOLVED_FRAME_CODE_ADDR); + + // Resolve the PC into a temporary address because if ResolveLoadAddress + // fails to resolve the address, it will clear the address object... + ThreadSP thread_sp(GetThread()); + if (thread_sp) { + TargetSP target_sp(thread_sp->CalculateTarget()); + if (target_sp) { + const bool allow_section_end = true; + if (m_frame_code_addr.SetOpcodeLoadAddress( + m_frame_code_addr.GetOffset(), target_sp.get(), + AddressClass::eCode, allow_section_end)) { + ModuleSP module_sp(m_frame_code_addr.GetModule()); + if (module_sp) { + m_sc.module_sp = module_sp; + m_flags.Set(eSymbolContextModule); + } + } + } + } + } + return m_frame_code_addr; +} + +// This can't be rewritten into a call to +// RegisterContext::GetPCForSymbolication because this +// StackFrame may have been constructed with a special pc, +// e.g. tail-call artificial frames. +Address StackFrame::GetFrameCodeAddressForSymbolication() { + Address lookup_addr(GetFrameCodeAddress()); + if (!lookup_addr.IsValid()) + return lookup_addr; + if (m_behaves_like_zeroth_frame) + return lookup_addr; + + addr_t offset = lookup_addr.GetOffset(); + if (offset > 0) { + lookup_addr.SetOffset(offset - 1); + } else { + // lookup_addr is the start of a section. We need do the math on the + // actual load address and re-compute the section. We're working with + // a 'noreturn' function at the end of a section. + TargetSP target_sp = CalculateTarget(); + if (target_sp) { + addr_t addr_minus_one = lookup_addr.GetOpcodeLoadAddress( + target_sp.get(), AddressClass::eCode) - + 1; + lookup_addr.SetOpcodeLoadAddress(addr_minus_one, target_sp.get()); + } + } + return lookup_addr; +} + +bool StackFrame::ChangePC(addr_t pc) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + // We can't change the pc value of a history stack frame - it is immutable. + if (IsHistorical()) + return false; + m_frame_code_addr.SetRawAddress(pc); + m_sc.Clear(false); + m_flags.Reset(0); + ThreadSP thread_sp(GetThread()); + if (thread_sp) + thread_sp->ClearStackFrames(); + return true; +} + +const char *StackFrame::Disassemble() { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + if (!m_disassembly.Empty()) + return m_disassembly.GetData(); + + ExecutionContext exe_ctx(shared_from_this()); + if (Target *target = exe_ctx.GetTargetPtr()) { + Disassembler::Disassemble(target->GetDebugger(), target->GetArchitecture(), + *this, m_disassembly); + } + + return m_disassembly.Empty() ? nullptr : m_disassembly.GetData(); +} + +Block *StackFrame::GetFrameBlock() { + if (m_sc.block == nullptr && m_flags.IsClear(eSymbolContextBlock)) + GetSymbolContext(eSymbolContextBlock); + + if (m_sc.block) { + Block *inline_block = m_sc.block->GetContainingInlinedBlock(); + if (inline_block) { + // Use the block with the inlined function info as the frame block we + // want this frame to have only the variables for the inlined function + // and its non-inlined block child blocks. + return inline_block; + } else { + // This block is not contained within any inlined function blocks with so + // we want to use the top most function block. + return &m_sc.function->GetBlock(false); + } + } + return nullptr; +} + +// Get the symbol context if we already haven't done so by resolving the +// PC address as much as possible. This way when we pass around a +// StackFrame object, everyone will have as much information as possible and no +// one will ever have to look things up manually. +const SymbolContext & +StackFrame::GetSymbolContext(SymbolContextItem resolve_scope) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + // Copy our internal symbol context into "sc". + if ((m_flags.Get() & resolve_scope) != resolve_scope) { + uint32_t resolved = 0; + + // If the target was requested add that: + if (!m_sc.target_sp) { + m_sc.target_sp = CalculateTarget(); + if (m_sc.target_sp) + resolved |= eSymbolContextTarget; + } + + // Resolve our PC to section offset if we haven't already done so and if we + // don't have a module. The resolved address section will contain the + // module to which it belongs + if (!m_sc.module_sp && m_flags.IsClear(RESOLVED_FRAME_CODE_ADDR)) + GetFrameCodeAddress(); + + // If this is not frame zero, then we need to subtract 1 from the PC value + // when doing address lookups since the PC will be on the instruction + // following the function call instruction... + Address lookup_addr(GetFrameCodeAddressForSymbolication()); + + if (m_sc.module_sp) { + // We have something in our stack frame symbol context, lets check if we + // haven't already tried to lookup one of those things. If we haven't + // then we will do the query. + + SymbolContextItem actual_resolve_scope = SymbolContextItem(0); + + if (resolve_scope & eSymbolContextCompUnit) { + if (m_flags.IsClear(eSymbolContextCompUnit)) { + if (m_sc.comp_unit) + resolved |= eSymbolContextCompUnit; + else + actual_resolve_scope |= eSymbolContextCompUnit; + } + } + + if (resolve_scope & eSymbolContextFunction) { + if (m_flags.IsClear(eSymbolContextFunction)) { + if (m_sc.function) + resolved |= eSymbolContextFunction; + else + actual_resolve_scope |= eSymbolContextFunction; + } + } + + if (resolve_scope & eSymbolContextBlock) { + if (m_flags.IsClear(eSymbolContextBlock)) { + if (m_sc.block) + resolved |= eSymbolContextBlock; + else + actual_resolve_scope |= eSymbolContextBlock; + } + } + + if (resolve_scope & eSymbolContextSymbol) { + if (m_flags.IsClear(eSymbolContextSymbol)) { + if (m_sc.symbol) + resolved |= eSymbolContextSymbol; + else + actual_resolve_scope |= eSymbolContextSymbol; + } + } + + if (resolve_scope & eSymbolContextLineEntry) { + if (m_flags.IsClear(eSymbolContextLineEntry)) { + if (m_sc.line_entry.IsValid()) + resolved |= eSymbolContextLineEntry; + else + actual_resolve_scope |= eSymbolContextLineEntry; + } + } + + if (actual_resolve_scope) { + // We might be resolving less information than what is already in our + // current symbol context so resolve into a temporary symbol context + // "sc" so we don't clear out data we have already found in "m_sc" + SymbolContext sc; + // Set flags that indicate what we have tried to resolve + resolved |= m_sc.module_sp->ResolveSymbolContextForAddress( + lookup_addr, actual_resolve_scope, sc); + // Only replace what we didn't already have as we may have information + // for an inlined function scope that won't match what a standard + // lookup by address would match + if ((resolved & eSymbolContextCompUnit) && m_sc.comp_unit == nullptr) + m_sc.comp_unit = sc.comp_unit; + if ((resolved & eSymbolContextFunction) && m_sc.function == nullptr) + m_sc.function = sc.function; + if ((resolved & eSymbolContextBlock) && m_sc.block == nullptr) + m_sc.block = sc.block; + if ((resolved & eSymbolContextSymbol) && m_sc.symbol == nullptr) + m_sc.symbol = sc.symbol; + if ((resolved & eSymbolContextLineEntry) && + !m_sc.line_entry.IsValid()) { + m_sc.line_entry = sc.line_entry; + m_sc.line_entry.ApplyFileMappings(m_sc.target_sp); + } + } + } else { + // If we don't have a module, then we can't have the compile unit, + // function, block, line entry or symbol, so we can safely call + // ResolveSymbolContextForAddress with our symbol context member m_sc. + if (m_sc.target_sp) { + resolved |= m_sc.target_sp->GetImages().ResolveSymbolContextForAddress( + lookup_addr, resolve_scope, m_sc); + } + } + + // Update our internal flags so we remember what we have tried to locate so + // we don't have to keep trying when more calls to this function are made. + // We might have dug up more information that was requested (for example if + // we were asked to only get the block, we will have gotten the compile + // unit, and function) so set any additional bits that we resolved + m_flags.Set(resolve_scope | resolved); + } + + // Return the symbol context with everything that was possible to resolve + // resolved. + return m_sc; +} + +VariableList *StackFrame::GetVariableList(bool get_file_globals, + Status *error_ptr) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + if (m_flags.IsClear(RESOLVED_VARIABLES)) { + m_flags.Set(RESOLVED_VARIABLES); + m_variable_list_sp = std::make_shared<VariableList>(); + + Block *frame_block = GetFrameBlock(); + + if (frame_block) { + const bool get_child_variables = true; + const bool can_create = true; + const bool stop_if_child_block_is_inlined_function = true; + frame_block->AppendBlockVariables(can_create, get_child_variables, + stop_if_child_block_is_inlined_function, + [](Variable *v) { return true; }, + m_variable_list_sp.get()); + } + } + + if (m_flags.IsClear(RESOLVED_GLOBAL_VARIABLES) && get_file_globals) { + m_flags.Set(RESOLVED_GLOBAL_VARIABLES); + + if (m_flags.IsClear(eSymbolContextCompUnit)) + GetSymbolContext(eSymbolContextCompUnit); + + if (m_sc.comp_unit) { + VariableListSP global_variable_list_sp( + m_sc.comp_unit->GetVariableList(true)); + if (m_variable_list_sp) + m_variable_list_sp->AddVariables(global_variable_list_sp.get()); + else + m_variable_list_sp = global_variable_list_sp; + } + } + + if (error_ptr && m_variable_list_sp->GetSize() == 0) { + // Check with the symbol file to check if there is an error for why we + // don't have variables that the user might need to know about. + GetSymbolContext(eSymbolContextEverything); + if (m_sc.module_sp) { + SymbolFile *sym_file = m_sc.module_sp->GetSymbolFile(); + if (sym_file) + *error_ptr = sym_file->GetFrameVariableError(*this); + } + } + + return m_variable_list_sp.get(); +} + +VariableListSP +StackFrame::GetInScopeVariableList(bool get_file_globals, + bool must_have_valid_location) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + // We can't fetch variable information for a history stack frame. + if (IsHistorical()) + return VariableListSP(); + + VariableListSP var_list_sp(new VariableList); + GetSymbolContext(eSymbolContextCompUnit | eSymbolContextBlock); + + if (m_sc.block) { + const bool can_create = true; + const bool get_parent_variables = true; + const bool stop_if_block_is_inlined_function = true; + m_sc.block->AppendVariables( + can_create, get_parent_variables, stop_if_block_is_inlined_function, + [this, must_have_valid_location](Variable *v) { + return v->IsInScope(this) && (!must_have_valid_location || + v->LocationIsValidForFrame(this)); + }, + var_list_sp.get()); + } + + if (m_sc.comp_unit && get_file_globals) { + VariableListSP global_variable_list_sp( + m_sc.comp_unit->GetVariableList(true)); + if (global_variable_list_sp) + var_list_sp->AddVariables(global_variable_list_sp.get()); + } + + return var_list_sp; +} + +ValueObjectSP StackFrame::GetValueForVariableExpressionPath( + llvm::StringRef var_expr, DynamicValueType use_dynamic, uint32_t options, + VariableSP &var_sp, Status &error) { + llvm::StringRef original_var_expr = var_expr; + // We can't fetch variable information for a history stack frame. + if (IsHistorical()) + return ValueObjectSP(); + + if (var_expr.empty()) { + error.SetErrorStringWithFormat("invalid variable path '%s'", + var_expr.str().c_str()); + return ValueObjectSP(); + } + + const bool check_ptr_vs_member = + (options & eExpressionPathOptionCheckPtrVsMember) != 0; + const bool no_fragile_ivar = + (options & eExpressionPathOptionsNoFragileObjcIvar) != 0; + const bool no_synth_child = + (options & eExpressionPathOptionsNoSyntheticChildren) != 0; + // const bool no_synth_array = (options & + // eExpressionPathOptionsNoSyntheticArrayRange) != 0; + error.Clear(); + bool deref = false; + bool address_of = false; + ValueObjectSP valobj_sp; + const bool get_file_globals = true; + // When looking up a variable for an expression, we need only consider the + // variables that are in scope. + VariableListSP var_list_sp(GetInScopeVariableList(get_file_globals)); + VariableList *variable_list = var_list_sp.get(); + + if (!variable_list) + return ValueObjectSP(); + + // If first character is a '*', then show pointer contents + std::string var_expr_storage; + if (var_expr[0] == '*') { + deref = true; + var_expr = var_expr.drop_front(); // Skip the '*' + } else if (var_expr[0] == '&') { + address_of = true; + var_expr = var_expr.drop_front(); // Skip the '&' + } + + size_t separator_idx = var_expr.find_first_of(".-[=+~|&^%#@!/?,<>{}"); + StreamString var_expr_path_strm; + + ConstString name_const_string(var_expr.substr(0, separator_idx)); + + var_sp = variable_list->FindVariable(name_const_string, false); + + bool synthetically_added_instance_object = false; + + if (var_sp) { + var_expr = var_expr.drop_front(name_const_string.GetLength()); + } + + if (!var_sp && (options & eExpressionPathOptionsAllowDirectIVarAccess)) { + // Check for direct ivars access which helps us with implicit access to + // ivars using "this" or "self". + GetSymbolContext(eSymbolContextFunction | eSymbolContextBlock); + llvm::StringRef instance_var_name = m_sc.GetInstanceVariableName(); + if (!instance_var_name.empty()) { + var_sp = variable_list->FindVariable(ConstString(instance_var_name)); + if (var_sp) { + separator_idx = 0; + if (Type *var_type = var_sp->GetType()) + if (auto compiler_type = var_type->GetForwardCompilerType()) + if (!compiler_type.IsPointerType()) + var_expr_storage = "."; + + if (var_expr_storage.empty()) + var_expr_storage = "->"; + var_expr_storage += var_expr; + var_expr = var_expr_storage; + synthetically_added_instance_object = true; + } + } + } + + if (!var_sp && (options & eExpressionPathOptionsInspectAnonymousUnions)) { + // Check if any anonymous unions are there which contain a variable with + // the name we need + for (const VariableSP &variable_sp : *variable_list) { + if (!variable_sp) + continue; + if (!variable_sp->GetName().IsEmpty()) + continue; + + Type *var_type = variable_sp->GetType(); + if (!var_type) + continue; + + if (!var_type->GetForwardCompilerType().IsAnonymousType()) + continue; + valobj_sp = GetValueObjectForFrameVariable(variable_sp, use_dynamic); + if (!valobj_sp) + return valobj_sp; + valobj_sp = valobj_sp->GetChildMemberWithName(name_const_string); + if (valobj_sp) + break; + } + } + + if (var_sp && !valobj_sp) { + valobj_sp = GetValueObjectForFrameVariable(var_sp, use_dynamic); + if (!valobj_sp) + return valobj_sp; + } + if (!valobj_sp) { + error.SetErrorStringWithFormat("no variable named '%s' found in this frame", + name_const_string.GetCString()); + return ValueObjectSP(); + } + + // We are dumping at least one child + while (!var_expr.empty()) { + // Calculate the next separator index ahead of time + ValueObjectSP child_valobj_sp; + const char separator_type = var_expr[0]; + bool expr_is_ptr = false; + switch (separator_type) { + case '-': + expr_is_ptr = true; + if (var_expr.size() >= 2 && var_expr[1] != '>') + return ValueObjectSP(); + + if (no_fragile_ivar) { + // Make sure we aren't trying to deref an objective + // C ivar if this is not allowed + const uint32_t pointer_type_flags = + valobj_sp->GetCompilerType().GetTypeInfo(nullptr); + if ((pointer_type_flags & eTypeIsObjC) && + (pointer_type_flags & eTypeIsPointer)) { + // This was an objective C object pointer and it was requested we + // skip any fragile ivars so return nothing here + return ValueObjectSP(); + } + } + + // If we have a non pointer type with a sythetic value then lets check if + // we have an sythetic dereference specified. + if (!valobj_sp->IsPointerType() && valobj_sp->HasSyntheticValue()) { + Status deref_error; + if (valobj_sp->GetCompilerType().IsReferenceType()) { + valobj_sp = valobj_sp->GetSyntheticValue()->Dereference(deref_error); + if (!valobj_sp || deref_error.Fail()) { + error.SetErrorStringWithFormatv( + "Failed to dereference reference type: %s", deref_error); + return ValueObjectSP(); + } + } + + valobj_sp = valobj_sp->Dereference(deref_error); + if (!valobj_sp || deref_error.Fail()) { + error.SetErrorStringWithFormatv( + "Failed to dereference sythetic value: {0}", deref_error); + return ValueObjectSP(); + } + // Some synthetic plug-ins fail to set the error in Dereference + if (!valobj_sp) { + error.SetErrorString("Failed to dereference sythetic value"); + return ValueObjectSP(); + } + expr_is_ptr = false; + } + + var_expr = var_expr.drop_front(); // Remove the '-' + [[fallthrough]]; + case '.': { + var_expr = var_expr.drop_front(); // Remove the '.' or '>' + separator_idx = var_expr.find_first_of(".-["); + ConstString child_name(var_expr.substr(0, var_expr.find_first_of(".-["))); + + if (check_ptr_vs_member) { + // We either have a pointer type and need to verify valobj_sp is a + // pointer, or we have a member of a class/union/struct being accessed + // with the . syntax and need to verify we don't have a pointer. + const bool actual_is_ptr = valobj_sp->IsPointerType(); + + if (actual_is_ptr != expr_is_ptr) { + // Incorrect use of "." with a pointer, or "->" with a + // class/union/struct instance or reference. + valobj_sp->GetExpressionPath(var_expr_path_strm); + if (actual_is_ptr) + error.SetErrorStringWithFormat( + "\"%s\" is a pointer and . was used to attempt to access " + "\"%s\". Did you mean \"%s->%s\"?", + var_expr_path_strm.GetData(), child_name.GetCString(), + var_expr_path_strm.GetData(), var_expr.str().c_str()); + else + error.SetErrorStringWithFormat( + "\"%s\" is not a pointer and -> was used to attempt to " + "access \"%s\". Did you mean \"%s.%s\"?", + var_expr_path_strm.GetData(), child_name.GetCString(), + var_expr_path_strm.GetData(), var_expr.str().c_str()); + return ValueObjectSP(); + } + } + child_valobj_sp = valobj_sp->GetChildMemberWithName(child_name); + if (!child_valobj_sp) { + if (!no_synth_child) { + child_valobj_sp = valobj_sp->GetSyntheticValue(); + if (child_valobj_sp) + child_valobj_sp = + child_valobj_sp->GetChildMemberWithName(child_name); + } + + if (no_synth_child || !child_valobj_sp) { + // No child member with name "child_name" + if (synthetically_added_instance_object) { + // We added a "this->" or "self->" to the beginning of the + // expression and this is the first pointer ivar access, so just + // return the normal error + error.SetErrorStringWithFormat( + "no variable or instance variable named '%s' found in " + "this frame", + name_const_string.GetCString()); + } else { + valobj_sp->GetExpressionPath(var_expr_path_strm); + if (child_name) { + error.SetErrorStringWithFormat( + "\"%s\" is not a member of \"(%s) %s\"", + child_name.GetCString(), + valobj_sp->GetTypeName().AsCString("<invalid type>"), + var_expr_path_strm.GetData()); + } else { + error.SetErrorStringWithFormat( + "incomplete expression path after \"%s\" in \"%s\"", + var_expr_path_strm.GetData(), + original_var_expr.str().c_str()); + } + } + return ValueObjectSP(); + } + } + synthetically_added_instance_object = false; + // Remove the child name from the path + var_expr = var_expr.drop_front(child_name.GetLength()); + if (use_dynamic != eNoDynamicValues) { + ValueObjectSP dynamic_value_sp( + child_valobj_sp->GetDynamicValue(use_dynamic)); + if (dynamic_value_sp) + child_valobj_sp = dynamic_value_sp; + } + } break; + + case '[': { + // Array member access, or treating pointer as an array Need at least two + // brackets and a number + if (var_expr.size() <= 2) { + error.SetErrorStringWithFormat( + "invalid square bracket encountered after \"%s\" in \"%s\"", + var_expr_path_strm.GetData(), var_expr.str().c_str()); + return ValueObjectSP(); + } + + // Drop the open brace. + var_expr = var_expr.drop_front(); + long child_index = 0; + + // If there's no closing brace, this is an invalid expression. + size_t end_pos = var_expr.find_first_of(']'); + if (end_pos == llvm::StringRef::npos) { + error.SetErrorStringWithFormat( + "missing closing square bracket in expression \"%s\"", + var_expr_path_strm.GetData()); + return ValueObjectSP(); + } + llvm::StringRef index_expr = var_expr.take_front(end_pos); + llvm::StringRef original_index_expr = index_expr; + // Drop all of "[index_expr]" + var_expr = var_expr.drop_front(end_pos + 1); + + if (index_expr.consumeInteger(0, child_index)) { + // If there was no integer anywhere in the index expression, this is + // erroneous expression. + error.SetErrorStringWithFormat("invalid index expression \"%s\"", + index_expr.str().c_str()); + return ValueObjectSP(); + } + + if (index_expr.empty()) { + // The entire index expression was a single integer. + + if (valobj_sp->GetCompilerType().IsPointerToScalarType() && deref) { + // what we have is *ptr[low]. the most similar C++ syntax is to deref + // ptr and extract bit low out of it. reading array item low would be + // done by saying ptr[low], without a deref * sign + Status deref_error; + ValueObjectSP temp(valobj_sp->Dereference(deref_error)); + if (!temp || deref_error.Fail()) { + valobj_sp->GetExpressionPath(var_expr_path_strm); + error.SetErrorStringWithFormat( + "could not dereference \"(%s) %s\"", + valobj_sp->GetTypeName().AsCString("<invalid type>"), + var_expr_path_strm.GetData()); + return ValueObjectSP(); + } + valobj_sp = temp; + deref = false; + } else if (valobj_sp->GetCompilerType().IsArrayOfScalarType() && + deref) { + // what we have is *arr[low]. the most similar C++ syntax is to get + // arr[0] (an operation that is equivalent to deref-ing arr) and + // extract bit low out of it. reading array item low would be done by + // saying arr[low], without a deref * sign + ValueObjectSP temp(valobj_sp->GetChildAtIndex(0)); + if (!temp) { + valobj_sp->GetExpressionPath(var_expr_path_strm); + error.SetErrorStringWithFormat( + "could not get item 0 for \"(%s) %s\"", + valobj_sp->GetTypeName().AsCString("<invalid type>"), + var_expr_path_strm.GetData()); + return ValueObjectSP(); + } + valobj_sp = temp; + deref = false; + } + + bool is_incomplete_array = false; + if (valobj_sp->IsPointerType()) { + bool is_objc_pointer = true; + + if (valobj_sp->GetCompilerType().GetMinimumLanguage() != + eLanguageTypeObjC) + is_objc_pointer = false; + else if (!valobj_sp->GetCompilerType().IsPointerType()) + is_objc_pointer = false; + + if (no_synth_child && is_objc_pointer) { + error.SetErrorStringWithFormat( + "\"(%s) %s\" is an Objective-C pointer, and cannot be " + "subscripted", + valobj_sp->GetTypeName().AsCString("<invalid type>"), + var_expr_path_strm.GetData()); + + return ValueObjectSP(); + } else if (is_objc_pointer) { + // dereferencing ObjC variables is not valid.. so let's try and + // recur to synthetic children + ValueObjectSP synthetic = valobj_sp->GetSyntheticValue(); + if (!synthetic /* no synthetic */ + || synthetic == valobj_sp) /* synthetic is the same as + the original object */ + { + valobj_sp->GetExpressionPath(var_expr_path_strm); + error.SetErrorStringWithFormat( + "\"(%s) %s\" is not an array type", + valobj_sp->GetTypeName().AsCString("<invalid type>"), + var_expr_path_strm.GetData()); + } else if (static_cast<uint32_t>(child_index) >= + synthetic + ->GetNumChildrenIgnoringErrors() /* synthetic does + not have that + many values */) { + valobj_sp->GetExpressionPath(var_expr_path_strm); + error.SetErrorStringWithFormat( + "array index %ld is not valid for \"(%s) %s\"", child_index, + valobj_sp->GetTypeName().AsCString("<invalid type>"), + var_expr_path_strm.GetData()); + } else { + child_valobj_sp = synthetic->GetChildAtIndex(child_index); + if (!child_valobj_sp) { + valobj_sp->GetExpressionPath(var_expr_path_strm); + error.SetErrorStringWithFormat( + "array index %ld is not valid for \"(%s) %s\"", child_index, + valobj_sp->GetTypeName().AsCString("<invalid type>"), + var_expr_path_strm.GetData()); + } + } + } else { + child_valobj_sp = + valobj_sp->GetSyntheticArrayMember(child_index, true); + if (!child_valobj_sp) { + valobj_sp->GetExpressionPath(var_expr_path_strm); + error.SetErrorStringWithFormat( + "failed to use pointer as array for index %ld for " + "\"(%s) %s\"", + child_index, + valobj_sp->GetTypeName().AsCString("<invalid type>"), + var_expr_path_strm.GetData()); + } + } + } else if (valobj_sp->GetCompilerType().IsArrayType( + nullptr, nullptr, &is_incomplete_array)) { + // Pass false to dynamic_value here so we can tell the difference + // between no dynamic value and no member of this type... + child_valobj_sp = valobj_sp->GetChildAtIndex(child_index); + if (!child_valobj_sp && (is_incomplete_array || !no_synth_child)) + child_valobj_sp = + valobj_sp->GetSyntheticArrayMember(child_index, true); + + if (!child_valobj_sp) { + valobj_sp->GetExpressionPath(var_expr_path_strm); + error.SetErrorStringWithFormat( + "array index %ld is not valid for \"(%s) %s\"", child_index, + valobj_sp->GetTypeName().AsCString("<invalid type>"), + var_expr_path_strm.GetData()); + } + } else if (valobj_sp->GetCompilerType().IsScalarType()) { + // this is a bitfield asking to display just one bit + child_valobj_sp = valobj_sp->GetSyntheticBitFieldChild( + child_index, child_index, true); + if (!child_valobj_sp) { + valobj_sp->GetExpressionPath(var_expr_path_strm); + error.SetErrorStringWithFormat( + "bitfield range %ld-%ld is not valid for \"(%s) %s\"", + child_index, child_index, + valobj_sp->GetTypeName().AsCString("<invalid type>"), + var_expr_path_strm.GetData()); + } + } else { + ValueObjectSP synthetic = valobj_sp->GetSyntheticValue(); + if (no_synth_child /* synthetic is forbidden */ || + !synthetic /* no synthetic */ + || synthetic == valobj_sp) /* synthetic is the same as the + original object */ + { + valobj_sp->GetExpressionPath(var_expr_path_strm); + error.SetErrorStringWithFormat( + "\"(%s) %s\" is not an array type", + valobj_sp->GetTypeName().AsCString("<invalid type>"), + var_expr_path_strm.GetData()); + } else if (static_cast<uint32_t>(child_index) >= + synthetic->GetNumChildrenIgnoringErrors() /* synthetic + does not have that many values */) { + valobj_sp->GetExpressionPath(var_expr_path_strm); + error.SetErrorStringWithFormat( + "array index %ld is not valid for \"(%s) %s\"", child_index, + valobj_sp->GetTypeName().AsCString("<invalid type>"), + var_expr_path_strm.GetData()); + } else { + child_valobj_sp = synthetic->GetChildAtIndex(child_index); + if (!child_valobj_sp) { + valobj_sp->GetExpressionPath(var_expr_path_strm); + error.SetErrorStringWithFormat( + "array index %ld is not valid for \"(%s) %s\"", child_index, + valobj_sp->GetTypeName().AsCString("<invalid type>"), + var_expr_path_strm.GetData()); + } + } + } + + if (!child_valobj_sp) { + // Invalid array index... + return ValueObjectSP(); + } + + if (use_dynamic != eNoDynamicValues) { + ValueObjectSP dynamic_value_sp( + child_valobj_sp->GetDynamicValue(use_dynamic)); + if (dynamic_value_sp) + child_valobj_sp = dynamic_value_sp; + } + // Break out early from the switch since we were able to find the child + // member + break; + } + + // this is most probably a BitField, let's take a look + if (index_expr.front() != '-') { + error.SetErrorStringWithFormat("invalid range expression \"'%s'\"", + original_index_expr.str().c_str()); + return ValueObjectSP(); + } + + index_expr = index_expr.drop_front(); + long final_index = 0; + if (index_expr.getAsInteger(0, final_index)) { + error.SetErrorStringWithFormat("invalid range expression \"'%s'\"", + original_index_expr.str().c_str()); + return ValueObjectSP(); + } + + // if the format given is [high-low], swap range + if (child_index > final_index) { + long temp = child_index; + child_index = final_index; + final_index = temp; + } + + if (valobj_sp->GetCompilerType().IsPointerToScalarType() && deref) { + // what we have is *ptr[low-high]. the most similar C++ syntax is to + // deref ptr and extract bits low thru high out of it. reading array + // items low thru high would be done by saying ptr[low-high], without a + // deref * sign + Status deref_error; + ValueObjectSP temp(valobj_sp->Dereference(deref_error)); + if (!temp || deref_error.Fail()) { + valobj_sp->GetExpressionPath(var_expr_path_strm); + error.SetErrorStringWithFormat( + "could not dereference \"(%s) %s\"", + valobj_sp->GetTypeName().AsCString("<invalid type>"), + var_expr_path_strm.GetData()); + return ValueObjectSP(); + } + valobj_sp = temp; + deref = false; + } else if (valobj_sp->GetCompilerType().IsArrayOfScalarType() && deref) { + // what we have is *arr[low-high]. the most similar C++ syntax is to + // get arr[0] (an operation that is equivalent to deref-ing arr) and + // extract bits low thru high out of it. reading array items low thru + // high would be done by saying arr[low-high], without a deref * sign + ValueObjectSP temp(valobj_sp->GetChildAtIndex(0)); + if (!temp) { + valobj_sp->GetExpressionPath(var_expr_path_strm); + error.SetErrorStringWithFormat( + "could not get item 0 for \"(%s) %s\"", + valobj_sp->GetTypeName().AsCString("<invalid type>"), + var_expr_path_strm.GetData()); + return ValueObjectSP(); + } + valobj_sp = temp; + deref = false; + } + + child_valobj_sp = + valobj_sp->GetSyntheticBitFieldChild(child_index, final_index, true); + if (!child_valobj_sp) { + valobj_sp->GetExpressionPath(var_expr_path_strm); + error.SetErrorStringWithFormat( + "bitfield range %ld-%ld is not valid for \"(%s) %s\"", child_index, + final_index, valobj_sp->GetTypeName().AsCString("<invalid type>"), + var_expr_path_strm.GetData()); + } + + if (!child_valobj_sp) { + // Invalid bitfield range... + return ValueObjectSP(); + } + + if (use_dynamic != eNoDynamicValues) { + ValueObjectSP dynamic_value_sp( + child_valobj_sp->GetDynamicValue(use_dynamic)); + if (dynamic_value_sp) + child_valobj_sp = dynamic_value_sp; + } + // Break out early from the switch since we were able to find the child + // member + break; + } + default: + // Failure... + { + valobj_sp->GetExpressionPath(var_expr_path_strm); + error.SetErrorStringWithFormat( + "unexpected char '%c' encountered after \"%s\" in \"%s\"", + separator_type, var_expr_path_strm.GetData(), + var_expr.str().c_str()); + + return ValueObjectSP(); + } + } + + if (child_valobj_sp) + valobj_sp = child_valobj_sp; + } + if (valobj_sp) { + if (deref) { + ValueObjectSP deref_valobj_sp(valobj_sp->Dereference(error)); + valobj_sp = deref_valobj_sp; + } else if (address_of) { + ValueObjectSP address_of_valobj_sp(valobj_sp->AddressOf(error)); + valobj_sp = address_of_valobj_sp; + } + } + return valobj_sp; +} + +bool StackFrame::GetFrameBaseValue(Scalar &frame_base, Status *error_ptr) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + if (!m_cfa_is_valid) { + m_frame_base_error.SetErrorString( + "No frame base available for this historical stack frame."); + return false; + } + + if (m_flags.IsClear(GOT_FRAME_BASE)) { + if (m_sc.function) { + m_frame_base.Clear(); + m_frame_base_error.Clear(); + + m_flags.Set(GOT_FRAME_BASE); + ExecutionContext exe_ctx(shared_from_this()); + addr_t loclist_base_addr = LLDB_INVALID_ADDRESS; + if (!m_sc.function->GetFrameBaseExpression().IsAlwaysValidSingleExpr()) + loclist_base_addr = + m_sc.function->GetAddressRange().GetBaseAddress().GetLoadAddress( + exe_ctx.GetTargetPtr()); + + llvm::Expected<Value> expr_value = + m_sc.function->GetFrameBaseExpression().Evaluate( + &exe_ctx, nullptr, loclist_base_addr, nullptr, nullptr); + if (!expr_value) + m_frame_base_error = expr_value.takeError(); + else + m_frame_base = expr_value->ResolveValue(&exe_ctx); + } else { + m_frame_base_error.SetErrorString("No function in symbol context."); + } + } + + if (m_frame_base_error.Success()) + frame_base = m_frame_base; + + if (error_ptr) + *error_ptr = m_frame_base_error; + return m_frame_base_error.Success(); +} + +DWARFExpressionList *StackFrame::GetFrameBaseExpression(Status *error_ptr) { + if (!m_sc.function) { + if (error_ptr) { + error_ptr->SetErrorString("No function in symbol context."); + } + return nullptr; + } + + return &m_sc.function->GetFrameBaseExpression(); +} + +RegisterContextSP StackFrame::GetRegisterContext() { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + if (!m_reg_context_sp) { + ThreadSP thread_sp(GetThread()); + if (thread_sp) + m_reg_context_sp = thread_sp->CreateRegisterContextForFrame(this); + } + return m_reg_context_sp; +} + +bool StackFrame::HasDebugInformation() { + GetSymbolContext(eSymbolContextLineEntry); + return m_sc.line_entry.IsValid(); +} + +ValueObjectSP +StackFrame::GetValueObjectForFrameVariable(const VariableSP &variable_sp, + DynamicValueType use_dynamic) { + ValueObjectSP valobj_sp; + { // Scope for stack frame mutex. We need to drop this mutex before we figure + // out the dynamic value. That will require converting the StackID in the + // VO back to a StackFrame, which will in turn require locking the + // StackFrameList. If we still hold the StackFrame mutex, we could suffer + // lock inversion against the pattern of getting the StackFrameList and + // then the stack frame, which is fairly common. + std::lock_guard<std::recursive_mutex> guard(m_mutex); + if (IsHistorical()) { + return valobj_sp; + } + VariableList *var_list = GetVariableList(true, nullptr); + if (var_list) { + // Make sure the variable is a frame variable + const uint32_t var_idx = var_list->FindIndexForVariable(variable_sp.get()); + const uint32_t num_variables = var_list->GetSize(); + if (var_idx < num_variables) { + valobj_sp = m_variable_list_value_objects.GetValueObjectAtIndex(var_idx); + if (!valobj_sp) { + if (m_variable_list_value_objects.GetSize() < num_variables) + m_variable_list_value_objects.Resize(num_variables); + valobj_sp = ValueObjectVariable::Create(this, variable_sp); + m_variable_list_value_objects.SetValueObjectAtIndex(var_idx, + valobj_sp); + } + } + } + } // End of StackFrame mutex scope. + if (use_dynamic != eNoDynamicValues && valobj_sp) { + ValueObjectSP dynamic_sp = valobj_sp->GetDynamicValue(use_dynamic); + if (dynamic_sp) + return dynamic_sp; + } + return valobj_sp; +} + +bool StackFrame::IsInlined() { + if (m_sc.block == nullptr) + GetSymbolContext(eSymbolContextBlock); + if (m_sc.block) + return m_sc.block->GetContainingInlinedBlock() != nullptr; + return false; +} + +bool StackFrame::IsHistorical() const { + return m_stack_frame_kind == StackFrame::Kind::History; +} + +bool StackFrame::IsArtificial() const { + return m_stack_frame_kind == StackFrame::Kind::Artificial; +} + +SourceLanguage StackFrame::GetLanguage() { + CompileUnit *cu = GetSymbolContext(eSymbolContextCompUnit).comp_unit; + if (cu) + return cu->GetLanguage(); + return {}; +} + +SourceLanguage StackFrame::GuessLanguage() { + SourceLanguage lang_type = GetLanguage(); + + if (lang_type == eLanguageTypeUnknown) { + SymbolContext sc = + GetSymbolContext(eSymbolContextFunction | eSymbolContextSymbol); + if (sc.function) + lang_type = LanguageType(sc.function->GetMangled().GuessLanguage()); + else if (sc.symbol) + lang_type = SourceLanguage(sc.symbol->GetMangled().GuessLanguage()); + } + + return lang_type; +} + +namespace { +std::pair<const Instruction::Operand *, int64_t> +GetBaseExplainingValue(const Instruction::Operand &operand, + RegisterContext ®ister_context, lldb::addr_t value) { + switch (operand.m_type) { + case Instruction::Operand::Type::Dereference: + case Instruction::Operand::Type::Immediate: + case Instruction::Operand::Type::Invalid: + case Instruction::Operand::Type::Product: + // These are not currently interesting + return std::make_pair(nullptr, 0); + case Instruction::Operand::Type::Sum: { + const Instruction::Operand *immediate_child = nullptr; + const Instruction::Operand *variable_child = nullptr; + if (operand.m_children[0].m_type == Instruction::Operand::Type::Immediate) { + immediate_child = &operand.m_children[0]; + variable_child = &operand.m_children[1]; + } else if (operand.m_children[1].m_type == + Instruction::Operand::Type::Immediate) { + immediate_child = &operand.m_children[1]; + variable_child = &operand.m_children[0]; + } + if (!immediate_child) { + return std::make_pair(nullptr, 0); + } + lldb::addr_t adjusted_value = value; + if (immediate_child->m_negative) { + adjusted_value += immediate_child->m_immediate; + } else { + adjusted_value -= immediate_child->m_immediate; + } + std::pair<const Instruction::Operand *, int64_t> base_and_offset = + GetBaseExplainingValue(*variable_child, register_context, + adjusted_value); + if (!base_and_offset.first) { + return std::make_pair(nullptr, 0); + } + if (immediate_child->m_negative) { + base_and_offset.second -= immediate_child->m_immediate; + } else { + base_and_offset.second += immediate_child->m_immediate; + } + return base_and_offset; + } + case Instruction::Operand::Type::Register: { + const RegisterInfo *info = + register_context.GetRegisterInfoByName(operand.m_register.AsCString()); + if (!info) { + return std::make_pair(nullptr, 0); + } + RegisterValue reg_value; + if (!register_context.ReadRegister(info, reg_value)) { + return std::make_pair(nullptr, 0); + } + if (reg_value.GetAsUInt64() == value) { + return std::make_pair(&operand, 0); + } else { + return std::make_pair(nullptr, 0); + } + } + } + return std::make_pair(nullptr, 0); +} + +std::pair<const Instruction::Operand *, int64_t> +GetBaseExplainingDereference(const Instruction::Operand &operand, + RegisterContext ®ister_context, + lldb::addr_t addr) { + if (operand.m_type == Instruction::Operand::Type::Dereference) { + return GetBaseExplainingValue(operand.m_children[0], register_context, + addr); + } + return std::make_pair(nullptr, 0); +} +} // namespace + +lldb::ValueObjectSP StackFrame::GuessValueForAddress(lldb::addr_t addr) { + TargetSP target_sp = CalculateTarget(); + + const ArchSpec &target_arch = target_sp->GetArchitecture(); + + AddressRange pc_range; + pc_range.GetBaseAddress() = GetFrameCodeAddress(); + pc_range.SetByteSize(target_arch.GetMaximumOpcodeByteSize()); + + const char *plugin_name = nullptr; + const char *flavor = nullptr; + const bool force_live_memory = true; + + DisassemblerSP disassembler_sp = + Disassembler::DisassembleRange(target_arch, plugin_name, flavor, + *target_sp, pc_range, force_live_memory); + + if (!disassembler_sp || !disassembler_sp->GetInstructionList().GetSize()) { + return ValueObjectSP(); + } + + InstructionSP instruction_sp = + disassembler_sp->GetInstructionList().GetInstructionAtIndex(0); + + llvm::SmallVector<Instruction::Operand, 3> operands; + + if (!instruction_sp->ParseOperands(operands)) { + return ValueObjectSP(); + } + + RegisterContextSP register_context_sp = GetRegisterContext(); + + if (!register_context_sp) { + return ValueObjectSP(); + } + + for (const Instruction::Operand &operand : operands) { + std::pair<const Instruction::Operand *, int64_t> base_and_offset = + GetBaseExplainingDereference(operand, *register_context_sp, addr); + + if (!base_and_offset.first) { + continue; + } + + switch (base_and_offset.first->m_type) { + case Instruction::Operand::Type::Immediate: { + lldb_private::Address addr; + if (target_sp->ResolveLoadAddress(base_and_offset.first->m_immediate + + base_and_offset.second, + addr)) { + auto c_type_system_or_err = + target_sp->GetScratchTypeSystemForLanguage(eLanguageTypeC); + if (auto err = c_type_system_or_err.takeError()) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), std::move(err), + "Unable to guess value for given address: {0}"); + return ValueObjectSP(); + } else { + auto ts = *c_type_system_or_err; + if (!ts) + return {}; + CompilerType void_ptr_type = + ts->GetBasicTypeFromAST(lldb::BasicType::eBasicTypeChar) + .GetPointerType(); + return ValueObjectMemory::Create(this, "", addr, void_ptr_type); + } + } else { + return ValueObjectSP(); + } + break; + } + case Instruction::Operand::Type::Register: { + return GuessValueForRegisterAndOffset(base_and_offset.first->m_register, + base_and_offset.second); + } + default: + return ValueObjectSP(); + } + } + + return ValueObjectSP(); +} + +namespace { +ValueObjectSP GetValueForOffset(StackFrame &frame, ValueObjectSP &parent, + int64_t offset) { + if (offset < 0 || uint64_t(offset) >= parent->GetByteSize()) { + return ValueObjectSP(); + } + + if (parent->IsPointerOrReferenceType()) { + return parent; + } + + for (int ci = 0, ce = parent->GetNumChildrenIgnoringErrors(); ci != ce; + ++ci) { + ValueObjectSP child_sp = parent->GetChildAtIndex(ci); + + if (!child_sp) { + return ValueObjectSP(); + } + + int64_t child_offset = child_sp->GetByteOffset(); + int64_t child_size = child_sp->GetByteSize().value_or(0); + + if (offset >= child_offset && offset < (child_offset + child_size)) { + return GetValueForOffset(frame, child_sp, offset - child_offset); + } + } + + if (offset == 0) { + return parent; + } else { + return ValueObjectSP(); + } +} + +ValueObjectSP GetValueForDereferincingOffset(StackFrame &frame, + ValueObjectSP &base, + int64_t offset) { + // base is a pointer to something + // offset is the thing to add to the pointer We return the most sensible + // ValueObject for the result of *(base+offset) + + if (!base->IsPointerOrReferenceType()) { + return ValueObjectSP(); + } + + Status error; + ValueObjectSP pointee = base->Dereference(error); + + if (!pointee) { + return ValueObjectSP(); + } + + if (offset >= 0 && uint64_t(offset) >= pointee->GetByteSize()) { + int64_t index = offset / pointee->GetByteSize().value_or(1); + offset = offset % pointee->GetByteSize().value_or(1); + const bool can_create = true; + pointee = base->GetSyntheticArrayMember(index, can_create); + } + + if (!pointee || error.Fail()) { + return ValueObjectSP(); + } + + return GetValueForOffset(frame, pointee, offset); +} + +/// Attempt to reconstruct the ValueObject for the address contained in a +/// given register plus an offset. +/// +/// \param [in] frame +/// The current stack frame. +/// +/// \param [in] reg +/// The register. +/// +/// \param [in] offset +/// The offset from the register. +/// +/// \param [in] disassembler +/// A disassembler containing instructions valid up to the current PC. +/// +/// \param [in] variables +/// The variable list from the current frame, +/// +/// \param [in] pc +/// The program counter for the instruction considered the 'user'. +/// +/// \return +/// A string describing the base for the ExpressionPath. This could be a +/// variable, a register value, an argument, or a function return value. +/// The ValueObject if found. If valid, it has a valid ExpressionPath. +lldb::ValueObjectSP DoGuessValueAt(StackFrame &frame, ConstString reg, + int64_t offset, Disassembler &disassembler, + VariableList &variables, const Address &pc) { + // Example of operation for Intel: + // + // +14: movq -0x8(%rbp), %rdi + // +18: movq 0x8(%rdi), %rdi + // +22: addl 0x4(%rdi), %eax + // + // f, a pointer to a struct, is known to be at -0x8(%rbp). + // + // DoGuessValueAt(frame, rdi, 4, dis, vars, 0x22) finds the instruction at + // +18 that assigns to rdi, and calls itself recursively for that dereference + // DoGuessValueAt(frame, rdi, 8, dis, vars, 0x18) finds the instruction at + // +14 that assigns to rdi, and calls itself recursively for that + // dereference + // DoGuessValueAt(frame, rbp, -8, dis, vars, 0x14) finds "f" in the + // variable list. + // Returns a ValueObject for f. (That's what was stored at rbp-8 at +14) + // Returns a ValueObject for *(f+8) or f->b (That's what was stored at rdi+8 + // at +18) + // Returns a ValueObject for *(f->b+4) or f->b->a (That's what was stored at + // rdi+4 at +22) + + // First, check the variable list to see if anything is at the specified + // location. + + using namespace OperandMatchers; + + const RegisterInfo *reg_info = + frame.GetRegisterContext()->GetRegisterInfoByName(reg.AsCString()); + if (!reg_info) { + return ValueObjectSP(); + } + + Instruction::Operand op = + offset ? Instruction::Operand::BuildDereference( + Instruction::Operand::BuildSum( + Instruction::Operand::BuildRegister(reg), + Instruction::Operand::BuildImmediate(offset))) + : Instruction::Operand::BuildDereference( + Instruction::Operand::BuildRegister(reg)); + + for (VariableSP var_sp : variables) { + if (var_sp->LocationExpressionList().MatchesOperand(frame, op)) + return frame.GetValueObjectForFrameVariable(var_sp, eNoDynamicValues); + } + + const uint32_t current_inst = + disassembler.GetInstructionList().GetIndexOfInstructionAtAddress(pc); + if (current_inst == UINT32_MAX) { + return ValueObjectSP(); + } + + for (uint32_t ii = current_inst - 1; ii != (uint32_t)-1; --ii) { + // This is not an exact algorithm, and it sacrifices accuracy for + // generality. Recognizing "mov" and "ld" instructions –– and which + // are their source and destination operands -- is something the + // disassembler should do for us. + InstructionSP instruction_sp = + disassembler.GetInstructionList().GetInstructionAtIndex(ii); + + if (instruction_sp->IsCall()) { + ABISP abi_sp = frame.CalculateProcess()->GetABI(); + if (!abi_sp) { + continue; + } + + const char *return_register_name; + if (!abi_sp->GetPointerReturnRegister(return_register_name)) { + continue; + } + + const RegisterInfo *return_register_info = + frame.GetRegisterContext()->GetRegisterInfoByName( + return_register_name); + if (!return_register_info) { + continue; + } + + int64_t offset = 0; + + if (!MatchUnaryOp(MatchOpType(Instruction::Operand::Type::Dereference), + MatchRegOp(*return_register_info))(op) && + !MatchUnaryOp( + MatchOpType(Instruction::Operand::Type::Dereference), + MatchBinaryOp(MatchOpType(Instruction::Operand::Type::Sum), + MatchRegOp(*return_register_info), + FetchImmOp(offset)))(op)) { + continue; + } + + llvm::SmallVector<Instruction::Operand, 1> operands; + if (!instruction_sp->ParseOperands(operands) || operands.size() != 1) { + continue; + } + + switch (operands[0].m_type) { + default: + break; + case Instruction::Operand::Type::Immediate: { + SymbolContext sc; + Address load_address; + if (!frame.CalculateTarget()->ResolveLoadAddress( + operands[0].m_immediate, load_address)) { + break; + } + frame.CalculateTarget()->GetImages().ResolveSymbolContextForAddress( + load_address, eSymbolContextFunction, sc); + if (!sc.function) { + break; + } + CompilerType function_type = sc.function->GetCompilerType(); + if (!function_type.IsFunctionType()) { + break; + } + CompilerType return_type = function_type.GetFunctionReturnType(); + RegisterValue return_value; + if (!frame.GetRegisterContext()->ReadRegister(return_register_info, + return_value)) { + break; + } + std::string name_str( + sc.function->GetName().AsCString("<unknown function>")); + name_str.append("()"); + Address return_value_address(return_value.GetAsUInt64()); + ValueObjectSP return_value_sp = ValueObjectMemory::Create( + &frame, name_str, return_value_address, return_type); + return GetValueForDereferincingOffset(frame, return_value_sp, offset); + } + } + + continue; + } + + llvm::SmallVector<Instruction::Operand, 2> operands; + if (!instruction_sp->ParseOperands(operands) || operands.size() != 2) { + continue; + } + + Instruction::Operand *origin_operand = nullptr; + auto clobbered_reg_matcher = [reg_info](const Instruction::Operand &op) { + return MatchRegOp(*reg_info)(op) && op.m_clobbered; + }; + + if (clobbered_reg_matcher(operands[0])) { + origin_operand = &operands[1]; + } + else if (clobbered_reg_matcher(operands[1])) { + origin_operand = &operands[0]; + } + else { + continue; + } + + // We have an origin operand. Can we track its value down? + ValueObjectSP source_path; + ConstString origin_register; + int64_t origin_offset = 0; + + if (FetchRegOp(origin_register)(*origin_operand)) { + source_path = DoGuessValueAt(frame, origin_register, 0, disassembler, + variables, instruction_sp->GetAddress()); + } else if (MatchUnaryOp( + MatchOpType(Instruction::Operand::Type::Dereference), + FetchRegOp(origin_register))(*origin_operand) || + MatchUnaryOp( + MatchOpType(Instruction::Operand::Type::Dereference), + MatchBinaryOp(MatchOpType(Instruction::Operand::Type::Sum), + FetchRegOp(origin_register), + FetchImmOp(origin_offset)))(*origin_operand)) { + source_path = + DoGuessValueAt(frame, origin_register, origin_offset, disassembler, + variables, instruction_sp->GetAddress()); + if (!source_path) { + continue; + } + source_path = + GetValueForDereferincingOffset(frame, source_path, offset); + } + + if (source_path) { + return source_path; + } + } + + return ValueObjectSP(); +} +} + +lldb::ValueObjectSP StackFrame::GuessValueForRegisterAndOffset(ConstString reg, + int64_t offset) { + TargetSP target_sp = CalculateTarget(); + + const ArchSpec &target_arch = target_sp->GetArchitecture(); + + Block *frame_block = GetFrameBlock(); + + if (!frame_block) { + return ValueObjectSP(); + } + + Function *function = frame_block->CalculateSymbolContextFunction(); + if (!function) { + return ValueObjectSP(); + } + + AddressRange pc_range = function->GetAddressRange(); + + if (GetFrameCodeAddress().GetFileAddress() < + pc_range.GetBaseAddress().GetFileAddress() || + GetFrameCodeAddress().GetFileAddress() - + pc_range.GetBaseAddress().GetFileAddress() >= + pc_range.GetByteSize()) { + return ValueObjectSP(); + } + + const char *plugin_name = nullptr; + const char *flavor = nullptr; + const bool force_live_memory = true; + DisassemblerSP disassembler_sp = + Disassembler::DisassembleRange(target_arch, plugin_name, flavor, + *target_sp, pc_range, force_live_memory); + + if (!disassembler_sp || !disassembler_sp->GetInstructionList().GetSize()) { + return ValueObjectSP(); + } + + const bool get_file_globals = false; + VariableList *variables = GetVariableList(get_file_globals, nullptr); + + if (!variables) { + return ValueObjectSP(); + } + + return DoGuessValueAt(*this, reg, offset, *disassembler_sp, *variables, + GetFrameCodeAddress()); +} + +lldb::ValueObjectSP StackFrame::FindVariable(ConstString name) { + ValueObjectSP value_sp; + + if (!name) + return value_sp; + + TargetSP target_sp = CalculateTarget(); + ProcessSP process_sp = CalculateProcess(); + + if (!target_sp && !process_sp) + return value_sp; + + VariableList variable_list; + VariableSP var_sp; + SymbolContext sc(GetSymbolContext(eSymbolContextBlock)); + + if (sc.block) { + const bool can_create = true; + const bool get_parent_variables = true; + const bool stop_if_block_is_inlined_function = true; + + if (sc.block->AppendVariables( + can_create, get_parent_variables, stop_if_block_is_inlined_function, + [this](Variable *v) { return v->IsInScope(this); }, + &variable_list)) { + var_sp = variable_list.FindVariable(name); + } + + if (var_sp) + value_sp = GetValueObjectForFrameVariable(var_sp, eNoDynamicValues); + } + + return value_sp; +} + +TargetSP StackFrame::CalculateTarget() { + TargetSP target_sp; + ThreadSP thread_sp(GetThread()); + if (thread_sp) { + ProcessSP process_sp(thread_sp->CalculateProcess()); + if (process_sp) + target_sp = process_sp->CalculateTarget(); + } + return target_sp; +} + +ProcessSP StackFrame::CalculateProcess() { + ProcessSP process_sp; + ThreadSP thread_sp(GetThread()); + if (thread_sp) + process_sp = thread_sp->CalculateProcess(); + return process_sp; +} + +ThreadSP StackFrame::CalculateThread() { return GetThread(); } + +StackFrameSP StackFrame::CalculateStackFrame() { return shared_from_this(); } + +void StackFrame::CalculateExecutionContext(ExecutionContext &exe_ctx) { + exe_ctx.SetContext(shared_from_this()); +} + +bool StackFrame::DumpUsingFormat(Stream &strm, + const FormatEntity::Entry *format, + llvm::StringRef frame_marker) { + GetSymbolContext(eSymbolContextEverything); + ExecutionContext exe_ctx(shared_from_this()); + StreamString s; + s.PutCString(frame_marker); + + if (format && FormatEntity::Format(*format, s, &m_sc, &exe_ctx, nullptr, + nullptr, false, false)) { + strm.PutCString(s.GetString()); + return true; + } + return false; +} + +void StackFrame::DumpUsingSettingsFormat(Stream *strm, bool show_unique, + const char *frame_marker) { + if (strm == nullptr) + return; + + ExecutionContext exe_ctx(shared_from_this()); + + const FormatEntity::Entry *frame_format = nullptr; + Target *target = exe_ctx.GetTargetPtr(); + if (target) { + if (show_unique) { + frame_format = target->GetDebugger().GetFrameFormatUnique(); + } else { + frame_format = target->GetDebugger().GetFrameFormat(); + } + } + if (!DumpUsingFormat(*strm, frame_format, frame_marker)) { + Dump(strm, true, false); + strm->EOL(); + } +} + +void StackFrame::Dump(Stream *strm, bool show_frame_index, + bool show_fullpaths) { + if (strm == nullptr) + return; + + if (show_frame_index) + strm->Printf("frame #%u: ", m_frame_index); + ExecutionContext exe_ctx(shared_from_this()); + Target *target = exe_ctx.GetTargetPtr(); + strm->Printf("0x%0*" PRIx64 " ", + target ? (target->GetArchitecture().GetAddressByteSize() * 2) + : 16, + GetFrameCodeAddress().GetLoadAddress(target)); + GetSymbolContext(eSymbolContextEverything); + const bool show_module = true; + const bool show_inline = true; + const bool show_function_arguments = true; + const bool show_function_name = true; + m_sc.DumpStopContext(strm, exe_ctx.GetBestExecutionContextScope(), + GetFrameCodeAddress(), show_fullpaths, show_module, + show_inline, show_function_arguments, + show_function_name); +} + +void StackFrame::UpdateCurrentFrameFromPreviousFrame(StackFrame &prev_frame) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + assert(GetStackID() == + prev_frame.GetStackID()); // TODO: remove this after some testing + m_variable_list_sp = prev_frame.m_variable_list_sp; + m_variable_list_value_objects.Swap(prev_frame.m_variable_list_value_objects); + if (!m_disassembly.GetString().empty()) { + m_disassembly.Clear(); + m_disassembly.PutCString(prev_frame.m_disassembly.GetString()); + } +} + +void StackFrame::UpdatePreviousFrameFromCurrentFrame(StackFrame &curr_frame) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + assert(GetStackID() == + curr_frame.GetStackID()); // TODO: remove this after some testing + m_id.SetPC(curr_frame.m_id.GetPC()); // Update the Stack ID PC value + assert(GetThread() == curr_frame.GetThread()); + m_frame_index = curr_frame.m_frame_index; + m_concrete_frame_index = curr_frame.m_concrete_frame_index; + m_reg_context_sp = curr_frame.m_reg_context_sp; + m_frame_code_addr = curr_frame.m_frame_code_addr; + m_behaves_like_zeroth_frame = curr_frame.m_behaves_like_zeroth_frame; + assert(!m_sc.target_sp || !curr_frame.m_sc.target_sp || + m_sc.target_sp.get() == curr_frame.m_sc.target_sp.get()); + assert(!m_sc.module_sp || !curr_frame.m_sc.module_sp || + m_sc.module_sp.get() == curr_frame.m_sc.module_sp.get()); + assert(m_sc.comp_unit == nullptr || curr_frame.m_sc.comp_unit == nullptr || + m_sc.comp_unit == curr_frame.m_sc.comp_unit); + assert(m_sc.function == nullptr || curr_frame.m_sc.function == nullptr || + m_sc.function == curr_frame.m_sc.function); + m_sc = curr_frame.m_sc; + m_flags.Clear(GOT_FRAME_BASE | eSymbolContextEverything); + m_flags.Set(m_sc.GetResolvedMask()); + m_frame_base.Clear(); + m_frame_base_error.Clear(); +} + +bool StackFrame::HasCachedData() const { + if (m_variable_list_sp) + return true; + if (m_variable_list_value_objects.GetSize() > 0) + return true; + if (!m_disassembly.GetString().empty()) + return true; + return false; +} + +bool StackFrame::GetStatus(Stream &strm, bool show_frame_info, bool show_source, + bool show_unique, const char *frame_marker) { + if (show_frame_info) { + strm.Indent(); + DumpUsingSettingsFormat(&strm, show_unique, frame_marker); + } + + if (show_source) { + ExecutionContext exe_ctx(shared_from_this()); + bool have_source = false, have_debuginfo = false; + Debugger::StopDisassemblyType disasm_display = + Debugger::eStopDisassemblyTypeNever; + Target *target = exe_ctx.GetTargetPtr(); + if (target) { + Debugger &debugger = target->GetDebugger(); + const uint32_t source_lines_before = + debugger.GetStopSourceLineCount(true); + const uint32_t source_lines_after = + debugger.GetStopSourceLineCount(false); + disasm_display = debugger.GetStopDisassemblyDisplay(); + + GetSymbolContext(eSymbolContextCompUnit | eSymbolContextLineEntry); + if (m_sc.comp_unit && m_sc.line_entry.IsValid()) { + have_debuginfo = true; + if (source_lines_before > 0 || source_lines_after > 0) { + uint32_t start_line = m_sc.line_entry.line; + if (!start_line && m_sc.function) { + FileSpec source_file; + m_sc.function->GetStartLineSourceInfo(source_file, start_line); + } + + size_t num_lines = + target->GetSourceManager().DisplaySourceLinesWithLineNumbers( + m_sc.line_entry.GetFile(), start_line, m_sc.line_entry.column, + source_lines_before, source_lines_after, "->", &strm); + if (num_lines != 0) + have_source = true; + // TODO: Give here a one time warning if source file is missing. + if (!m_sc.line_entry.line) { + ConstString fn_name = m_sc.GetFunctionName(); + + if (!fn_name.IsEmpty()) + strm.Printf( + "Note: this address is compiler-generated code in function " + "%s that has no source code associated with it.", + fn_name.AsCString()); + else + strm.Printf("Note: this address is compiler-generated code that " + "has no source code associated with it."); + strm.EOL(); + } + } + } + switch (disasm_display) { + case Debugger::eStopDisassemblyTypeNever: + break; + + case Debugger::eStopDisassemblyTypeNoDebugInfo: + if (have_debuginfo) + break; + [[fallthrough]]; + + case Debugger::eStopDisassemblyTypeNoSource: + if (have_source) + break; + [[fallthrough]]; + + case Debugger::eStopDisassemblyTypeAlways: + if (target) { + const uint32_t disasm_lines = debugger.GetDisassemblyLineCount(); + if (disasm_lines > 0) { + const ArchSpec &target_arch = target->GetArchitecture(); + const char *plugin_name = nullptr; + const char *flavor = nullptr; + const bool mixed_source_and_assembly = false; + Disassembler::Disassemble( + target->GetDebugger(), target_arch, plugin_name, flavor, + exe_ctx, GetFrameCodeAddress(), + {Disassembler::Limit::Instructions, disasm_lines}, + mixed_source_and_assembly, 0, + Disassembler::eOptionMarkPCAddress, strm); + } + } + break; + } + } + } + return true; +} + +RecognizedStackFrameSP StackFrame::GetRecognizedFrame() { + if (!m_recognized_frame_sp) { + m_recognized_frame_sp = GetThread() + ->GetProcess() + ->GetTarget() + .GetFrameRecognizerManager() + .RecognizeFrame(CalculateStackFrame()); + } + return m_recognized_frame_sp; +} diff --git a/contrib/llvm-project/lldb/source/Target/StackFrameList.cpp b/contrib/llvm-project/lldb/source/Target/StackFrameList.cpp new file mode 100644 index 000000000000..0cf9ce1bf043 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/StackFrameList.cpp @@ -0,0 +1,984 @@ +//===-- StackFrameList.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 "lldb/Target/StackFrameList.h" +#include "lldb/Breakpoint/Breakpoint.h" +#include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/SourceManager.h" +#include "lldb/Host/StreamFile.h" +#include "lldb/Symbol/Block.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Target/StackFrameRecognizer.h" +#include "lldb/Target/StopInfo.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/Unwind.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "llvm/ADT/SmallPtrSet.h" + +#include <memory> + +//#define DEBUG_STACK_FRAMES 1 + +using namespace lldb; +using namespace lldb_private; + +// StackFrameList constructor +StackFrameList::StackFrameList(Thread &thread, + const lldb::StackFrameListSP &prev_frames_sp, + bool show_inline_frames) + : m_thread(thread), m_prev_frames_sp(prev_frames_sp), m_mutex(), m_frames(), + m_selected_frame_idx(), m_concrete_frames_fetched(0), + m_current_inlined_depth(UINT32_MAX), + m_current_inlined_pc(LLDB_INVALID_ADDRESS), + m_show_inlined_frames(show_inline_frames) { + if (prev_frames_sp) { + m_current_inlined_depth = prev_frames_sp->m_current_inlined_depth; + m_current_inlined_pc = prev_frames_sp->m_current_inlined_pc; + } +} + +StackFrameList::~StackFrameList() { + // Call clear since this takes a lock and clears the stack frame list in case + // another thread is currently using this stack frame list + Clear(); +} + +void StackFrameList::CalculateCurrentInlinedDepth() { + uint32_t cur_inlined_depth = GetCurrentInlinedDepth(); + if (cur_inlined_depth == UINT32_MAX) { + ResetCurrentInlinedDepth(); + } +} + +uint32_t StackFrameList::GetCurrentInlinedDepth() { + if (m_show_inlined_frames && m_current_inlined_pc != LLDB_INVALID_ADDRESS) { + lldb::addr_t cur_pc = m_thread.GetRegisterContext()->GetPC(); + if (cur_pc != m_current_inlined_pc) { + m_current_inlined_pc = LLDB_INVALID_ADDRESS; + m_current_inlined_depth = UINT32_MAX; + Log *log = GetLog(LLDBLog::Step); + if (log && log->GetVerbose()) + LLDB_LOGF( + log, + "GetCurrentInlinedDepth: invalidating current inlined depth.\n"); + } + return m_current_inlined_depth; + } else { + return UINT32_MAX; + } +} + +void StackFrameList::ResetCurrentInlinedDepth() { + if (!m_show_inlined_frames) + return; + + std::lock_guard<std::recursive_mutex> guard(m_mutex); + + GetFramesUpTo(0, DoNotAllowInterruption); + if (m_frames.empty()) + return; + if (!m_frames[0]->IsInlined()) { + m_current_inlined_depth = UINT32_MAX; + m_current_inlined_pc = LLDB_INVALID_ADDRESS; + Log *log = GetLog(LLDBLog::Step); + if (log && log->GetVerbose()) + LLDB_LOGF( + log, + "ResetCurrentInlinedDepth: Invalidating current inlined depth.\n"); + return; + } + + // We only need to do something special about inlined blocks when we are + // at the beginning of an inlined function: + // FIXME: We probably also have to do something special if the PC is at + // the END of an inlined function, which coincides with the end of either + // its containing function or another inlined function. + + Block *block_ptr = m_frames[0]->GetFrameBlock(); + if (!block_ptr) + return; + + Address pc_as_address; + lldb::addr_t curr_pc = m_thread.GetRegisterContext()->GetPC(); + pc_as_address.SetLoadAddress(curr_pc, &(m_thread.GetProcess()->GetTarget())); + AddressRange containing_range; + if (!block_ptr->GetRangeContainingAddress(pc_as_address, containing_range) || + pc_as_address != containing_range.GetBaseAddress()) + return; + + // If we got here because of a breakpoint hit, then set the inlined depth + // depending on where the breakpoint was set. If we got here because of a + // crash, then set the inlined depth to the deepest most block. Otherwise, + // we stopped here naturally as the result of a step, so set ourselves in the + // containing frame of the whole set of nested inlines, so the user can then + // "virtually" step into the frames one by one, or next over the whole mess. + // Note: We don't have to handle being somewhere in the middle of the stack + // here, since ResetCurrentInlinedDepth doesn't get called if there is a + // valid inlined depth set. + StopInfoSP stop_info_sp = m_thread.GetStopInfo(); + if (!stop_info_sp) + return; + switch (stop_info_sp->GetStopReason()) { + case eStopReasonWatchpoint: + case eStopReasonException: + case eStopReasonExec: + case eStopReasonFork: + case eStopReasonVFork: + case eStopReasonVForkDone: + case eStopReasonSignal: + // In all these cases we want to stop in the deepest frame. + m_current_inlined_pc = curr_pc; + m_current_inlined_depth = 0; + break; + case eStopReasonBreakpoint: { + // FIXME: Figure out what this break point is doing, and set the inline + // depth appropriately. Be careful to take into account breakpoints that + // implement step over prologue, since that should do the default + // calculation. For now, if the breakpoints corresponding to this hit are + // all internal, I set the stop location to the top of the inlined stack, + // since that will make things like stepping over prologues work right. + // But if there are any non-internal breakpoints I do to the bottom of the + // stack, since that was the old behavior. + uint32_t bp_site_id = stop_info_sp->GetValue(); + BreakpointSiteSP bp_site_sp( + m_thread.GetProcess()->GetBreakpointSiteList().FindByID(bp_site_id)); + bool all_internal = true; + if (bp_site_sp) { + uint32_t num_owners = bp_site_sp->GetNumberOfConstituents(); + for (uint32_t i = 0; i < num_owners; i++) { + Breakpoint &bp_ref = + bp_site_sp->GetConstituentAtIndex(i)->GetBreakpoint(); + if (!bp_ref.IsInternal()) { + all_internal = false; + } + } + } + if (!all_internal) { + m_current_inlined_pc = curr_pc; + m_current_inlined_depth = 0; + break; + } + } + [[fallthrough]]; + default: { + // Otherwise, we should set ourselves at the container of the inlining, so + // that the user can descend into them. So first we check whether we have + // more than one inlined block sharing this PC: + int num_inlined_functions = 0; + + for (Block *container_ptr = block_ptr->GetInlinedParent(); + container_ptr != nullptr; + container_ptr = container_ptr->GetInlinedParent()) { + if (!container_ptr->GetRangeContainingAddress(pc_as_address, + containing_range)) + break; + if (pc_as_address != containing_range.GetBaseAddress()) + break; + + num_inlined_functions++; + } + m_current_inlined_pc = curr_pc; + m_current_inlined_depth = num_inlined_functions + 1; + Log *log = GetLog(LLDBLog::Step); + if (log && log->GetVerbose()) + LLDB_LOGF(log, + "ResetCurrentInlinedDepth: setting inlined " + "depth: %d 0x%" PRIx64 ".\n", + m_current_inlined_depth, curr_pc); + + break; + } + } +} + +bool StackFrameList::DecrementCurrentInlinedDepth() { + if (m_show_inlined_frames) { + uint32_t current_inlined_depth = GetCurrentInlinedDepth(); + if (current_inlined_depth != UINT32_MAX) { + if (current_inlined_depth > 0) { + m_current_inlined_depth--; + return true; + } + } + } + return false; +} + +void StackFrameList::SetCurrentInlinedDepth(uint32_t new_depth) { + m_current_inlined_depth = new_depth; + if (new_depth == UINT32_MAX) + m_current_inlined_pc = LLDB_INVALID_ADDRESS; + else + m_current_inlined_pc = m_thread.GetRegisterContext()->GetPC(); +} + +void StackFrameList::GetOnlyConcreteFramesUpTo(uint32_t end_idx, + Unwind &unwinder) { + assert(m_thread.IsValid() && "Expected valid thread"); + assert(m_frames.size() <= end_idx && "Expected there to be frames to fill"); + + if (end_idx < m_concrete_frames_fetched) + return; + + uint32_t num_frames = unwinder.GetFramesUpTo(end_idx); + if (num_frames <= end_idx + 1) { + // Done unwinding. + m_concrete_frames_fetched = UINT32_MAX; + } + + // Don't create the frames eagerly. Defer this work to GetFrameAtIndex, + // which can lazily query the unwinder to create frames. + m_frames.resize(num_frames); +} + +/// A sequence of calls that comprise some portion of a backtrace. Each frame +/// is represented as a pair of a callee (Function *) and an address within the +/// callee. +struct CallDescriptor { + Function *func; + CallEdge::AddrType address_type = CallEdge::AddrType::Call; + addr_t address = LLDB_INVALID_ADDRESS; +}; +using CallSequence = std::vector<CallDescriptor>; + +/// Find the unique path through the call graph from \p begin (with return PC +/// \p return_pc) to \p end. On success this path is stored into \p path, and +/// on failure \p path is unchanged. +static void FindInterveningFrames(Function &begin, Function &end, + ExecutionContext &exe_ctx, Target &target, + addr_t return_pc, CallSequence &path, + ModuleList &images, Log *log) { + LLDB_LOG(log, "Finding frames between {0} and {1}, retn-pc={2:x}", + begin.GetDisplayName(), end.GetDisplayName(), return_pc); + + // Find a non-tail calling edge with the correct return PC. + if (log) + for (const auto &edge : begin.GetCallEdges()) + LLDB_LOG(log, "FindInterveningFrames: found call with retn-PC = {0:x}", + edge->GetReturnPCAddress(begin, target)); + CallEdge *first_edge = begin.GetCallEdgeForReturnAddress(return_pc, target); + if (!first_edge) { + LLDB_LOG(log, "No call edge outgoing from {0} with retn-PC == {1:x}", + begin.GetDisplayName(), return_pc); + return; + } + + // The first callee may not be resolved, or there may be nothing to fill in. + Function *first_callee = first_edge->GetCallee(images, exe_ctx); + if (!first_callee) { + LLDB_LOG(log, "Could not resolve callee"); + return; + } + if (first_callee == &end) { + LLDB_LOG(log, "Not searching further, first callee is {0} (retn-PC: {1:x})", + end.GetDisplayName(), return_pc); + return; + } + + // Run DFS on the tail-calling edges out of the first callee to find \p end. + // Fully explore the set of functions reachable from the first edge via tail + // calls in order to detect ambiguous executions. + struct DFS { + CallSequence active_path = {}; + CallSequence solution_path = {}; + llvm::SmallPtrSet<Function *, 2> visited_nodes = {}; + bool ambiguous = false; + Function *end; + ModuleList &images; + Target ⌖ + ExecutionContext &context; + + DFS(Function *end, ModuleList &images, Target &target, + ExecutionContext &context) + : end(end), images(images), target(target), context(context) {} + + void search(CallEdge &first_edge, Function &first_callee, + CallSequence &path) { + dfs(first_edge, first_callee); + if (!ambiguous) + path = std::move(solution_path); + } + + void dfs(CallEdge ¤t_edge, Function &callee) { + // Found a path to the target function. + if (&callee == end) { + if (solution_path.empty()) + solution_path = active_path; + else + ambiguous = true; + return; + } + + // Terminate the search if tail recursion is found, or more generally if + // there's more than one way to reach a target. This errs on the side of + // caution: it conservatively stops searching when some solutions are + // still possible to save time in the average case. + if (!visited_nodes.insert(&callee).second) { + ambiguous = true; + return; + } + + // Search the calls made from this callee. + active_path.push_back(CallDescriptor{&callee}); + for (const auto &edge : callee.GetTailCallingEdges()) { + Function *next_callee = edge->GetCallee(images, context); + if (!next_callee) + continue; + + std::tie(active_path.back().address_type, active_path.back().address) = + edge->GetCallerAddress(callee, target); + + dfs(*edge, *next_callee); + if (ambiguous) + return; + } + active_path.pop_back(); + } + }; + + DFS(&end, images, target, exe_ctx).search(*first_edge, *first_callee, path); +} + +/// Given that \p next_frame will be appended to the frame list, synthesize +/// tail call frames between the current end of the list and \p next_frame. +/// If any frames are added, adjust the frame index of \p next_frame. +/// +/// -------------- +/// | ... | <- Completed frames. +/// -------------- +/// | prev_frame | +/// -------------- +/// | ... | <- Artificial frames inserted here. +/// -------------- +/// | next_frame | +/// -------------- +/// | ... | <- Not-yet-visited frames. +/// -------------- +void StackFrameList::SynthesizeTailCallFrames(StackFrame &next_frame) { + // Cannot synthesize tail call frames when the stack is empty (there is no + // "previous" frame). + if (m_frames.empty()) + return; + + TargetSP target_sp = next_frame.CalculateTarget(); + if (!target_sp) + return; + + lldb::RegisterContextSP next_reg_ctx_sp = next_frame.GetRegisterContext(); + if (!next_reg_ctx_sp) + return; + + Log *log = GetLog(LLDBLog::Step); + + StackFrame &prev_frame = *m_frames.back().get(); + + // Find the functions prev_frame and next_frame are stopped in. The function + // objects are needed to search the lazy call graph for intervening frames. + Function *prev_func = + prev_frame.GetSymbolContext(eSymbolContextFunction).function; + if (!prev_func) { + LLDB_LOG(log, "SynthesizeTailCallFrames: can't find previous function"); + return; + } + Function *next_func = + next_frame.GetSymbolContext(eSymbolContextFunction).function; + if (!next_func) { + LLDB_LOG(log, "SynthesizeTailCallFrames: can't find next function"); + return; + } + + // Try to find the unique sequence of (tail) calls which led from next_frame + // to prev_frame. + CallSequence path; + addr_t return_pc = next_reg_ctx_sp->GetPC(); + Target &target = *target_sp.get(); + ModuleList &images = next_frame.CalculateTarget()->GetImages(); + ExecutionContext exe_ctx(target_sp, /*get_process=*/true); + exe_ctx.SetFramePtr(&next_frame); + FindInterveningFrames(*next_func, *prev_func, exe_ctx, target, return_pc, + path, images, log); + + // Push synthetic tail call frames. + for (auto calleeInfo : llvm::reverse(path)) { + Function *callee = calleeInfo.func; + uint32_t frame_idx = m_frames.size(); + uint32_t concrete_frame_idx = next_frame.GetConcreteFrameIndex(); + addr_t cfa = LLDB_INVALID_ADDRESS; + bool cfa_is_valid = false; + addr_t pc = calleeInfo.address; + // If the callee address refers to the call instruction, we do not want to + // subtract 1 from this value. + const bool behaves_like_zeroth_frame = + calleeInfo.address_type == CallEdge::AddrType::Call; + SymbolContext sc; + callee->CalculateSymbolContext(&sc); + auto synth_frame = std::make_shared<StackFrame>( + m_thread.shared_from_this(), frame_idx, concrete_frame_idx, cfa, + cfa_is_valid, pc, StackFrame::Kind::Artificial, + behaves_like_zeroth_frame, &sc); + m_frames.push_back(synth_frame); + LLDB_LOG(log, "Pushed frame {0} at {1:x}", callee->GetDisplayName(), pc); + } + + // If any frames were created, adjust next_frame's index. + if (!path.empty()) + next_frame.SetFrameIndex(m_frames.size()); +} + +bool StackFrameList::GetFramesUpTo(uint32_t end_idx, + InterruptionControl allow_interrupt) { + // Do not fetch frames for an invalid thread. + bool was_interrupted = false; + if (!m_thread.IsValid()) + return false; + + // We've already gotten more frames than asked for, or we've already finished + // unwinding, return. + if (m_frames.size() > end_idx || GetAllFramesFetched()) + return false; + + Unwind &unwinder = m_thread.GetUnwinder(); + + if (!m_show_inlined_frames) { + GetOnlyConcreteFramesUpTo(end_idx, unwinder); + return false; + } + +#if defined(DEBUG_STACK_FRAMES) + StreamFile s(stdout, false); +#endif + // If we are hiding some frames from the outside world, we need to add + // those onto the total count of frames to fetch. However, we don't need + // to do that if end_idx is 0 since in that case we always get the first + // concrete frame and all the inlined frames below it... And of course, if + // end_idx is UINT32_MAX that means get all, so just do that... + + uint32_t inlined_depth = 0; + if (end_idx > 0 && end_idx != UINT32_MAX) { + inlined_depth = GetCurrentInlinedDepth(); + if (inlined_depth != UINT32_MAX) { + if (end_idx > 0) + end_idx += inlined_depth; + } + } + + StackFrameSP unwind_frame_sp; + Debugger &dbg = m_thread.GetProcess()->GetTarget().GetDebugger(); + do { + uint32_t idx = m_concrete_frames_fetched++; + lldb::addr_t pc = LLDB_INVALID_ADDRESS; + lldb::addr_t cfa = LLDB_INVALID_ADDRESS; + bool behaves_like_zeroth_frame = (idx == 0); + if (idx == 0) { + // We might have already created frame zero, only create it if we need + // to. + if (m_frames.empty()) { + RegisterContextSP reg_ctx_sp(m_thread.GetRegisterContext()); + + if (reg_ctx_sp) { + const bool success = unwinder.GetFrameInfoAtIndex( + idx, cfa, pc, behaves_like_zeroth_frame); + // There shouldn't be any way not to get the frame info for frame + // 0. But if the unwinder can't make one, lets make one by hand + // with the SP as the CFA and see if that gets any further. + if (!success) { + cfa = reg_ctx_sp->GetSP(); + pc = reg_ctx_sp->GetPC(); + } + + unwind_frame_sp = std::make_shared<StackFrame>( + m_thread.shared_from_this(), m_frames.size(), idx, reg_ctx_sp, + cfa, pc, behaves_like_zeroth_frame, nullptr); + m_frames.push_back(unwind_frame_sp); + } + } else { + unwind_frame_sp = m_frames.front(); + cfa = unwind_frame_sp->m_id.GetCallFrameAddress(); + } + } else { + // Check for interruption when building the frames. + // Do the check in idx > 0 so that we'll always create a 0th frame. + if (allow_interrupt + && INTERRUPT_REQUESTED(dbg, "Interrupted having fetched {0} frames", + m_frames.size())) { + was_interrupted = true; + break; + } + + const bool success = + unwinder.GetFrameInfoAtIndex(idx, cfa, pc, behaves_like_zeroth_frame); + if (!success) { + // We've gotten to the end of the stack. + SetAllFramesFetched(); + break; + } + const bool cfa_is_valid = true; + unwind_frame_sp = std::make_shared<StackFrame>( + m_thread.shared_from_this(), m_frames.size(), idx, cfa, cfa_is_valid, + pc, StackFrame::Kind::Regular, behaves_like_zeroth_frame, nullptr); + + // Create synthetic tail call frames between the previous frame and the + // newly-found frame. The new frame's index may change after this call, + // although its concrete index will stay the same. + SynthesizeTailCallFrames(*unwind_frame_sp.get()); + + m_frames.push_back(unwind_frame_sp); + } + + assert(unwind_frame_sp); + SymbolContext unwind_sc = unwind_frame_sp->GetSymbolContext( + eSymbolContextBlock | eSymbolContextFunction); + Block *unwind_block = unwind_sc.block; + TargetSP target_sp = m_thread.CalculateTarget(); + if (unwind_block) { + Address curr_frame_address( + unwind_frame_sp->GetFrameCodeAddressForSymbolication()); + + SymbolContext next_frame_sc; + Address next_frame_address; + + while (unwind_sc.GetParentOfInlinedScope( + curr_frame_address, next_frame_sc, next_frame_address)) { + next_frame_sc.line_entry.ApplyFileMappings(target_sp); + behaves_like_zeroth_frame = false; + StackFrameSP frame_sp(new StackFrame( + m_thread.shared_from_this(), m_frames.size(), idx, + unwind_frame_sp->GetRegisterContextSP(), cfa, next_frame_address, + behaves_like_zeroth_frame, &next_frame_sc)); + + m_frames.push_back(frame_sp); + unwind_sc = next_frame_sc; + curr_frame_address = next_frame_address; + } + } + } while (m_frames.size() - 1 < end_idx); + + // Don't try to merge till you've calculated all the frames in this stack. + if (GetAllFramesFetched() && m_prev_frames_sp) { + StackFrameList *prev_frames = m_prev_frames_sp.get(); + StackFrameList *curr_frames = this; + +#if defined(DEBUG_STACK_FRAMES) + s.PutCString("\nprev_frames:\n"); + prev_frames->Dump(&s); + s.PutCString("\ncurr_frames:\n"); + curr_frames->Dump(&s); + s.EOL(); +#endif + size_t curr_frame_num, prev_frame_num; + + for (curr_frame_num = curr_frames->m_frames.size(), + prev_frame_num = prev_frames->m_frames.size(); + curr_frame_num > 0 && prev_frame_num > 0; + --curr_frame_num, --prev_frame_num) { + const size_t curr_frame_idx = curr_frame_num - 1; + const size_t prev_frame_idx = prev_frame_num - 1; + StackFrameSP curr_frame_sp(curr_frames->m_frames[curr_frame_idx]); + StackFrameSP prev_frame_sp(prev_frames->m_frames[prev_frame_idx]); + +#if defined(DEBUG_STACK_FRAMES) + s.Printf("\n\nCurr frame #%u ", curr_frame_idx); + if (curr_frame_sp) + curr_frame_sp->Dump(&s, true, false); + else + s.PutCString("NULL"); + s.Printf("\nPrev frame #%u ", prev_frame_idx); + if (prev_frame_sp) + prev_frame_sp->Dump(&s, true, false); + else + s.PutCString("NULL"); +#endif + + StackFrame *curr_frame = curr_frame_sp.get(); + StackFrame *prev_frame = prev_frame_sp.get(); + + if (curr_frame == nullptr || prev_frame == nullptr) + break; + + // Check the stack ID to make sure they are equal. + if (curr_frame->GetStackID() != prev_frame->GetStackID()) + break; + + prev_frame->UpdatePreviousFrameFromCurrentFrame(*curr_frame); + // Now copy the fixed up previous frame into the current frames so the + // pointer doesn't change. + m_frames[curr_frame_idx] = prev_frame_sp; + +#if defined(DEBUG_STACK_FRAMES) + s.Printf("\n Copying previous frame to current frame"); +#endif + } + // We are done with the old stack frame list, we can release it now. + m_prev_frames_sp.reset(); + } + +#if defined(DEBUG_STACK_FRAMES) + s.PutCString("\n\nNew frames:\n"); + Dump(&s); + s.EOL(); +#endif + // Don't report interrupted if we happen to have gotten all the frames: + if (!GetAllFramesFetched()) + return was_interrupted; + return false; +} + +uint32_t StackFrameList::GetNumFrames(bool can_create) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + + if (can_create) { + // Don't allow interrupt or we might not return the correct count + GetFramesUpTo(UINT32_MAX, DoNotAllowInterruption); + } + return GetVisibleStackFrameIndex(m_frames.size()); +} + +void StackFrameList::Dump(Stream *s) { + if (s == nullptr) + return; + + std::lock_guard<std::recursive_mutex> guard(m_mutex); + + const_iterator pos, begin = m_frames.begin(), end = m_frames.end(); + for (pos = begin; pos != end; ++pos) { + StackFrame *frame = (*pos).get(); + s->Printf("%p: ", static_cast<void *>(frame)); + if (frame) { + frame->GetStackID().Dump(s); + frame->DumpUsingSettingsFormat(s); + } else + s->Printf("frame #%u", (uint32_t)std::distance(begin, pos)); + s->EOL(); + } + s->EOL(); +} + +StackFrameSP StackFrameList::GetFrameAtIndex(uint32_t idx) { + StackFrameSP frame_sp; + std::lock_guard<std::recursive_mutex> guard(m_mutex); + uint32_t original_idx = idx; + + uint32_t inlined_depth = GetCurrentInlinedDepth(); + if (inlined_depth != UINT32_MAX) + idx += inlined_depth; + + if (idx < m_frames.size()) + frame_sp = m_frames[idx]; + + if (frame_sp) + return frame_sp; + + // GetFramesUpTo will fill m_frames with as many frames as you asked for, if + // there are that many. If there weren't then you asked for too many frames. + // GetFramesUpTo returns true if interrupted: + if (GetFramesUpTo(idx)) { + Log *log = GetLog(LLDBLog::Thread); + LLDB_LOG(log, "GetFrameAtIndex was interrupted"); + return {}; + } + + if (idx < m_frames.size()) { + if (m_show_inlined_frames) { + // When inline frames are enabled we actually create all the frames in + // GetFramesUpTo. + frame_sp = m_frames[idx]; + } else { + addr_t pc, cfa; + bool behaves_like_zeroth_frame = (idx == 0); + if (m_thread.GetUnwinder().GetFrameInfoAtIndex( + idx, cfa, pc, behaves_like_zeroth_frame)) { + const bool cfa_is_valid = true; + frame_sp = std::make_shared<StackFrame>( + m_thread.shared_from_this(), idx, idx, cfa, cfa_is_valid, pc, + StackFrame::Kind::Regular, behaves_like_zeroth_frame, nullptr); + + Function *function = + frame_sp->GetSymbolContext(eSymbolContextFunction).function; + if (function) { + // When we aren't showing inline functions we always use the top + // most function block as the scope. + frame_sp->SetSymbolContextScope(&function->GetBlock(false)); + } else { + // Set the symbol scope from the symbol regardless if it is nullptr + // or valid. + frame_sp->SetSymbolContextScope( + frame_sp->GetSymbolContext(eSymbolContextSymbol).symbol); + } + SetFrameAtIndex(idx, frame_sp); + } + } + } else if (original_idx == 0) { + // There should ALWAYS be a frame at index 0. If something went wrong with + // the CurrentInlinedDepth such that there weren't as many frames as we + // thought taking that into account, then reset the current inlined depth + // and return the real zeroth frame. + if (m_frames.empty()) { + // Why do we have a thread with zero frames, that should not ever + // happen... + assert(!m_thread.IsValid() && "A valid thread has no frames."); + } else { + ResetCurrentInlinedDepth(); + frame_sp = m_frames[original_idx]; + } + } + + return frame_sp; +} + +StackFrameSP +StackFrameList::GetFrameWithConcreteFrameIndex(uint32_t unwind_idx) { + // First try assuming the unwind index is the same as the frame index. The + // unwind index is always greater than or equal to the frame index, so it is + // a good place to start. If we have inlined frames we might have 5 concrete + // frames (frame unwind indexes go from 0-4), but we might have 15 frames + // after we make all the inlined frames. Most of the time the unwind frame + // index (or the concrete frame index) is the same as the frame index. + uint32_t frame_idx = unwind_idx; + StackFrameSP frame_sp(GetFrameAtIndex(frame_idx)); + while (frame_sp) { + if (frame_sp->GetFrameIndex() == unwind_idx) + break; + frame_sp = GetFrameAtIndex(++frame_idx); + } + return frame_sp; +} + +static bool CompareStackID(const StackFrameSP &stack_sp, + const StackID &stack_id) { + return stack_sp->GetStackID() < stack_id; +} + +StackFrameSP StackFrameList::GetFrameWithStackID(const StackID &stack_id) { + StackFrameSP frame_sp; + + if (stack_id.IsValid()) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + uint32_t frame_idx = 0; + // Do a binary search in case the stack frame is already in our cache + collection::const_iterator begin = m_frames.begin(); + collection::const_iterator end = m_frames.end(); + if (begin != end) { + collection::const_iterator pos = + std::lower_bound(begin, end, stack_id, CompareStackID); + if (pos != end) { + if ((*pos)->GetStackID() == stack_id) + return *pos; + } + } + do { + frame_sp = GetFrameAtIndex(frame_idx); + if (frame_sp && frame_sp->GetStackID() == stack_id) + break; + frame_idx++; + } while (frame_sp); + } + return frame_sp; +} + +bool StackFrameList::SetFrameAtIndex(uint32_t idx, StackFrameSP &frame_sp) { + if (idx >= m_frames.size()) + m_frames.resize(idx + 1); + // Make sure allocation succeeded by checking bounds again + if (idx < m_frames.size()) { + m_frames[idx] = frame_sp; + return true; + } + return false; // resize failed, out of memory? +} + +void StackFrameList::SelectMostRelevantFrame() { + // Don't call into the frame recognizers on the private state thread as + // they can cause code to run in the target, and that can cause deadlocks + // when fetching stop events for the expression. + if (m_thread.GetProcess()->CurrentThreadIsPrivateStateThread()) + return; + + Log *log = GetLog(LLDBLog::Thread); + + // Only the top frame should be recognized. + StackFrameSP frame_sp = GetFrameAtIndex(0); + if (!frame_sp) { + LLDB_LOG(log, "Failed to construct Frame #0"); + return; + } + + RecognizedStackFrameSP recognized_frame_sp = frame_sp->GetRecognizedFrame(); + + if (!recognized_frame_sp) { + LLDB_LOG(log, "Frame #0 not recognized"); + return; + } + + if (StackFrameSP most_relevant_frame_sp = + recognized_frame_sp->GetMostRelevantFrame()) { + LLDB_LOG(log, "Found most relevant frame at index {0}", + most_relevant_frame_sp->GetFrameIndex()); + SetSelectedFrame(most_relevant_frame_sp.get()); + } else { + LLDB_LOG(log, "No relevant frame!"); + } +} + +uint32_t StackFrameList::GetSelectedFrameIndex( + SelectMostRelevant select_most_relevant) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + if (!m_selected_frame_idx && select_most_relevant) + SelectMostRelevantFrame(); + if (!m_selected_frame_idx) { + // If we aren't selecting the most relevant frame, and the selected frame + // isn't set, then don't force a selection here, just return 0. + if (!select_most_relevant) + return 0; + m_selected_frame_idx = 0; + } + return *m_selected_frame_idx; +} + +uint32_t StackFrameList::SetSelectedFrame(lldb_private::StackFrame *frame) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + const_iterator pos; + const_iterator begin = m_frames.begin(); + const_iterator end = m_frames.end(); + m_selected_frame_idx = 0; + + for (pos = begin; pos != end; ++pos) { + if (pos->get() == frame) { + m_selected_frame_idx = std::distance(begin, pos); + uint32_t inlined_depth = GetCurrentInlinedDepth(); + if (inlined_depth != UINT32_MAX) + m_selected_frame_idx = *m_selected_frame_idx - inlined_depth; + break; + } + } + + SetDefaultFileAndLineToSelectedFrame(); + return *m_selected_frame_idx; +} + +bool StackFrameList::SetSelectedFrameByIndex(uint32_t idx) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + StackFrameSP frame_sp(GetFrameAtIndex(idx)); + if (frame_sp) { + SetSelectedFrame(frame_sp.get()); + return true; + } else + return false; +} + +void StackFrameList::SetDefaultFileAndLineToSelectedFrame() { + if (m_thread.GetID() == + m_thread.GetProcess()->GetThreadList().GetSelectedThread()->GetID()) { + StackFrameSP frame_sp( + GetFrameAtIndex(GetSelectedFrameIndex(DoNoSelectMostRelevantFrame))); + if (frame_sp) { + SymbolContext sc = frame_sp->GetSymbolContext(eSymbolContextLineEntry); + if (sc.line_entry.GetFile()) + m_thread.CalculateTarget()->GetSourceManager().SetDefaultFileAndLine( + sc.line_entry.GetFile(), sc.line_entry.line); + } + } +} + +// The thread has been run, reset the number stack frames to zero so we can +// determine how many frames we have lazily. +// Note, we don't actually re-use StackFrameLists, we always make a new +// StackFrameList every time we stop, and then copy frame information frame +// by frame from the old to the new StackFrameList. So the comment above, +// does not describe how StackFrameLists are currently used. +// Clear is currently only used to clear the list in the destructor. +void StackFrameList::Clear() { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + m_frames.clear(); + m_concrete_frames_fetched = 0; + m_selected_frame_idx.reset(); +} + +lldb::StackFrameSP +StackFrameList::GetStackFrameSPForStackFramePtr(StackFrame *stack_frame_ptr) { + const_iterator pos; + const_iterator begin = m_frames.begin(); + const_iterator end = m_frames.end(); + lldb::StackFrameSP ret_sp; + + for (pos = begin; pos != end; ++pos) { + if (pos->get() == stack_frame_ptr) { + ret_sp = (*pos); + break; + } + } + return ret_sp; +} + +size_t StackFrameList::GetStatus(Stream &strm, uint32_t first_frame, + uint32_t num_frames, bool show_frame_info, + uint32_t num_frames_with_source, + bool show_unique, + const char *selected_frame_marker) { + size_t num_frames_displayed = 0; + + if (num_frames == 0) + return 0; + + StackFrameSP frame_sp; + uint32_t frame_idx = 0; + uint32_t last_frame; + + // Don't let the last frame wrap around... + if (num_frames == UINT32_MAX) + last_frame = UINT32_MAX; + else + last_frame = first_frame + num_frames; + + StackFrameSP selected_frame_sp = + m_thread.GetSelectedFrame(DoNoSelectMostRelevantFrame); + const char *unselected_marker = nullptr; + std::string buffer; + if (selected_frame_marker) { + size_t len = strlen(selected_frame_marker); + buffer.insert(buffer.begin(), len, ' '); + unselected_marker = buffer.c_str(); + } + const char *marker = nullptr; + + for (frame_idx = first_frame; frame_idx < last_frame; ++frame_idx) { + frame_sp = GetFrameAtIndex(frame_idx); + if (!frame_sp) + break; + + if (selected_frame_marker != nullptr) { + if (frame_sp == selected_frame_sp) + marker = selected_frame_marker; + else + marker = unselected_marker; + } + // Check for interruption here. If we're fetching arguments, this loop + // can go slowly: + Debugger &dbg = m_thread.GetProcess()->GetTarget().GetDebugger(); + if (INTERRUPT_REQUESTED( + dbg, "Interrupted dumping stack for thread {0:x} with {1} shown.", + m_thread.GetID(), num_frames_displayed)) + break; + + + if (!frame_sp->GetStatus(strm, show_frame_info, + num_frames_with_source > (first_frame - frame_idx), + show_unique, marker)) + break; + ++num_frames_displayed; + } + + strm.IndentLess(); + return num_frames_displayed; +} diff --git a/contrib/llvm-project/lldb/source/Target/StackFrameRecognizer.cpp b/contrib/llvm-project/lldb/source/Target/StackFrameRecognizer.cpp new file mode 100644 index 000000000000..0ccb1ae9c031 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/StackFrameRecognizer.cpp @@ -0,0 +1,154 @@ +//===-- StackFrameRecognizer.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 "lldb/Target/StackFrameRecognizer.h" +#include "lldb/Core/Module.h" +#include "lldb/Interpreter/ScriptInterpreter.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Utility/RegularExpression.h" + +using namespace lldb; +using namespace lldb_private; + +class ScriptedRecognizedStackFrame : public RecognizedStackFrame { +public: + ScriptedRecognizedStackFrame(ValueObjectListSP args) { + m_arguments = args; + } +}; + +ScriptedStackFrameRecognizer::ScriptedStackFrameRecognizer( + ScriptInterpreter *interpreter, const char *pclass) + : m_interpreter(interpreter), m_python_class(pclass) { + m_python_object_sp = + m_interpreter->CreateFrameRecognizer(m_python_class.c_str()); +} + +RecognizedStackFrameSP +ScriptedStackFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame) { + if (!m_python_object_sp || !m_interpreter) + return RecognizedStackFrameSP(); + + ValueObjectListSP args = + m_interpreter->GetRecognizedArguments(m_python_object_sp, frame); + auto args_synthesized = ValueObjectListSP(new ValueObjectList()); + for (const auto &o : args->GetObjects()) { + args_synthesized->Append(ValueObjectRecognizerSynthesizedValue::Create( + *o, eValueTypeVariableArgument)); + } + + return RecognizedStackFrameSP( + new ScriptedRecognizedStackFrame(args_synthesized)); +} + +void StackFrameRecognizerManager::AddRecognizer( + StackFrameRecognizerSP recognizer, ConstString module, + llvm::ArrayRef<ConstString> symbols, bool first_instruction_only) { + m_recognizers.push_front({(uint32_t)m_recognizers.size(), recognizer, false, + module, RegularExpressionSP(), symbols, + RegularExpressionSP(), first_instruction_only}); +} + +void StackFrameRecognizerManager::AddRecognizer( + StackFrameRecognizerSP recognizer, RegularExpressionSP module, + RegularExpressionSP symbol, bool first_instruction_only) { + m_recognizers.push_front({(uint32_t)m_recognizers.size(), recognizer, true, + ConstString(), module, std::vector<ConstString>(), + symbol, first_instruction_only}); +} + +void StackFrameRecognizerManager::ForEach( + const std::function<void(uint32_t, std::string, std::string, + llvm::ArrayRef<ConstString>, bool)> &callback) { + for (auto entry : m_recognizers) { + if (entry.is_regexp) { + std::string module_name; + std::string symbol_name; + + if (entry.module_regexp) + module_name = entry.module_regexp->GetText().str(); + if (entry.symbol_regexp) + symbol_name = entry.symbol_regexp->GetText().str(); + + callback(entry.recognizer_id, entry.recognizer->GetName(), module_name, + llvm::ArrayRef(ConstString(symbol_name)), true); + + } else { + callback(entry.recognizer_id, entry.recognizer->GetName(), + entry.module.GetCString(), entry.symbols, false); + } + } +} + +bool StackFrameRecognizerManager::RemoveRecognizerWithID( + uint32_t recognizer_id) { + if (recognizer_id >= m_recognizers.size()) + return false; + auto found = + llvm::find_if(m_recognizers, [recognizer_id](const RegisteredEntry &e) { + return e.recognizer_id == recognizer_id; + }); + if (found == m_recognizers.end()) + return false; + m_recognizers.erase(found); + return true; +} + +void StackFrameRecognizerManager::RemoveAllRecognizers() { + m_recognizers.clear(); +} + +StackFrameRecognizerSP +StackFrameRecognizerManager::GetRecognizerForFrame(StackFrameSP frame) { + const SymbolContext &symctx = frame->GetSymbolContext( + eSymbolContextModule | eSymbolContextFunction | eSymbolContextSymbol); + ConstString function_name = symctx.GetFunctionName(); + ModuleSP module_sp = symctx.module_sp; + if (!module_sp) + return StackFrameRecognizerSP(); + ConstString module_name = module_sp->GetFileSpec().GetFilename(); + Symbol *symbol = symctx.symbol; + if (!symbol) + return StackFrameRecognizerSP(); + Address start_addr = symbol->GetAddress(); + Address current_addr = frame->GetFrameCodeAddress(); + + for (auto entry : m_recognizers) { + if (entry.module) + if (entry.module != module_name) + continue; + + if (entry.module_regexp) + if (!entry.module_regexp->Execute(module_name.GetStringRef())) + continue; + + if (!entry.symbols.empty()) + if (!llvm::is_contained(entry.symbols, function_name)) + continue; + + if (entry.symbol_regexp) + if (!entry.symbol_regexp->Execute(function_name.GetStringRef())) + continue; + + if (entry.first_instruction_only) + if (start_addr != current_addr) + continue; + + return entry.recognizer; + } + return StackFrameRecognizerSP(); +} + +RecognizedStackFrameSP +StackFrameRecognizerManager::RecognizeFrame(StackFrameSP frame) { + auto recognizer = GetRecognizerForFrame(frame); + if (!recognizer) + return RecognizedStackFrameSP(); + return recognizer->RecognizeFrame(frame); +} diff --git a/contrib/llvm-project/lldb/source/Target/StackID.cpp b/contrib/llvm-project/lldb/source/Target/StackID.cpp new file mode 100644 index 000000000000..410d5b7e820a --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/StackID.cpp @@ -0,0 +1,97 @@ +//===-- StackID.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 "lldb/Target/StackID.h" +#include "lldb/Symbol/Block.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Utility/Stream.h" + +using namespace lldb_private; + +void StackID::Dump(Stream *s) { + s->Printf("StackID (pc = 0x%16.16" PRIx64 ", cfa = 0x%16.16" PRIx64 + ", symbol_scope = %p", + m_pc, m_cfa, static_cast<void *>(m_symbol_scope)); + if (m_symbol_scope) { + SymbolContext sc; + + m_symbol_scope->CalculateSymbolContext(&sc); + if (sc.block) + s->Printf(" (Block {0x%8.8" PRIx64 "})", sc.block->GetID()); + else if (sc.symbol) + s->Printf(" (Symbol{0x%8.8x})", sc.symbol->GetID()); + } + s->PutCString(") "); +} + +bool lldb_private::operator==(const StackID &lhs, const StackID &rhs) { + if (lhs.GetCallFrameAddress() != rhs.GetCallFrameAddress()) + return false; + + SymbolContextScope *lhs_scope = lhs.GetSymbolContextScope(); + SymbolContextScope *rhs_scope = rhs.GetSymbolContextScope(); + + // Only compare the PC values if both symbol context scopes are nullptr + if (lhs_scope == nullptr && rhs_scope == nullptr) + return lhs.GetPC() == rhs.GetPC(); + + return lhs_scope == rhs_scope; +} + +bool lldb_private::operator!=(const StackID &lhs, const StackID &rhs) { + if (lhs.GetCallFrameAddress() != rhs.GetCallFrameAddress()) + return true; + + SymbolContextScope *lhs_scope = lhs.GetSymbolContextScope(); + SymbolContextScope *rhs_scope = rhs.GetSymbolContextScope(); + + if (lhs_scope == nullptr && rhs_scope == nullptr) + return lhs.GetPC() != rhs.GetPC(); + + return lhs_scope != rhs_scope; +} + +bool lldb_private::operator<(const StackID &lhs, const StackID &rhs) { + const lldb::addr_t lhs_cfa = lhs.GetCallFrameAddress(); + const lldb::addr_t rhs_cfa = rhs.GetCallFrameAddress(); + + // FIXME: We are assuming that the stacks grow downward in memory. That's not + // necessary, but true on + // all the machines we care about at present. If this changes, we'll have to + // deal with that. The ABI is the agent who knows this ordering, but the + // StackID has no access to the ABI. The most straightforward way to handle + // this is to add a "m_grows_downward" bool to the StackID, and set it in the + // constructor. But I'm not going to waste a bool per StackID on this till we + // need it. + + if (lhs_cfa != rhs_cfa) + return lhs_cfa < rhs_cfa; + + SymbolContextScope *lhs_scope = lhs.GetSymbolContextScope(); + SymbolContextScope *rhs_scope = rhs.GetSymbolContextScope(); + + if (lhs_scope != nullptr && rhs_scope != nullptr) { + // Same exact scope, lhs is not less than (younger than rhs) + if (lhs_scope == rhs_scope) + return false; + + SymbolContext lhs_sc; + SymbolContext rhs_sc; + lhs_scope->CalculateSymbolContext(&lhs_sc); + rhs_scope->CalculateSymbolContext(&rhs_sc); + + // Items with the same function can only be compared + if (lhs_sc.function == rhs_sc.function && lhs_sc.function != nullptr && + lhs_sc.block != nullptr && rhs_sc.function != nullptr && + rhs_sc.block != nullptr) { + return rhs_sc.block->Contains(lhs_sc.block); + } + } + return false; +} diff --git a/contrib/llvm-project/lldb/source/Target/Statistics.cpp b/contrib/llvm-project/lldb/source/Target/Statistics.cpp new file mode 100644 index 000000000000..583d1524881f --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/Statistics.cpp @@ -0,0 +1,410 @@ +//===-- Statistics.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 "lldb/Target/Statistics.h" + +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Symbol/SymbolFile.h" +#include "lldb/Target/DynamicLoader.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/UnixSignals.h" +#include "lldb/Utility/StructuredData.h" + +using namespace lldb; +using namespace lldb_private; +using namespace llvm; + +static void EmplaceSafeString(llvm::json::Object &obj, llvm::StringRef key, + const std::string &str) { + if (str.empty()) + return; + if (LLVM_LIKELY(llvm::json::isUTF8(str))) + obj.try_emplace(key, str); + else + obj.try_emplace(key, llvm::json::fixUTF8(str)); +} + +json::Value StatsSuccessFail::ToJSON() const { + return json::Object{{"successes", successes}, {"failures", failures}}; +} + +static double elapsed(const StatsTimepoint &start, const StatsTimepoint &end) { + StatsDuration::Duration elapsed = + end.time_since_epoch() - start.time_since_epoch(); + return elapsed.count(); +} + +void TargetStats::CollectStats(Target &target) { + m_module_identifiers.clear(); + for (ModuleSP module_sp : target.GetImages().Modules()) + m_module_identifiers.emplace_back((intptr_t)module_sp.get()); +} + +json::Value ModuleStats::ToJSON() const { + json::Object module; + EmplaceSafeString(module, "path", path); + EmplaceSafeString(module, "uuid", uuid); + EmplaceSafeString(module, "triple", triple); + module.try_emplace("identifier", identifier); + module.try_emplace("symbolTableParseTime", symtab_parse_time); + module.try_emplace("symbolTableIndexTime", symtab_index_time); + module.try_emplace("symbolTableLoadedFromCache", symtab_loaded_from_cache); + module.try_emplace("symbolTableSavedToCache", symtab_saved_to_cache); + module.try_emplace("debugInfoParseTime", debug_parse_time); + module.try_emplace("debugInfoIndexTime", debug_index_time); + module.try_emplace("debugInfoByteSize", (int64_t)debug_info_size); + module.try_emplace("debugInfoIndexLoadedFromCache", + debug_info_index_loaded_from_cache); + module.try_emplace("debugInfoIndexSavedToCache", + debug_info_index_saved_to_cache); + module.try_emplace("debugInfoEnabled", debug_info_enabled); + module.try_emplace("debugInfoHadVariableErrors", + debug_info_had_variable_errors); + module.try_emplace("debugInfoHadIncompleteTypes", + debug_info_had_incomplete_types); + module.try_emplace("symbolTableStripped", symtab_stripped); + if (!symfile_path.empty()) + module.try_emplace("symbolFilePath", symfile_path); + + if (!symfile_modules.empty()) { + json::Array symfile_ids; + for (const auto symfile_id: symfile_modules) + symfile_ids.emplace_back(symfile_id); + module.try_emplace("symbolFileModuleIdentifiers", std::move(symfile_ids)); + } + + if (!type_system_stats.empty()) { + json::Array type_systems; + for (const auto &entry : type_system_stats) { + json::Object obj; + obj.try_emplace(entry.first().str(), entry.second); + type_systems.emplace_back(std::move(obj)); + } + module.try_emplace("typeSystemInfo", std::move(type_systems)); + } + + return module; +} + +llvm::json::Value ConstStringStats::ToJSON() const { + json::Object obj; + obj.try_emplace<int64_t>("bytesTotal", stats.GetBytesTotal()); + obj.try_emplace<int64_t>("bytesUsed", stats.GetBytesUsed()); + obj.try_emplace<int64_t>("bytesUnused", stats.GetBytesUnused()); + return obj; +} + +json::Value +TargetStats::ToJSON(Target &target, + const lldb_private::StatisticsOptions &options) { + json::Object target_metrics_json; + ProcessSP process_sp = target.GetProcessSP(); + const bool summary_only = options.GetSummaryOnly(); + const bool include_modules = options.GetIncludeModules(); + if (!summary_only) { + CollectStats(target); + + json::Array json_module_uuid_array; + for (auto module_identifier : m_module_identifiers) + json_module_uuid_array.emplace_back(module_identifier); + + target_metrics_json.try_emplace(m_expr_eval.name, m_expr_eval.ToJSON()); + target_metrics_json.try_emplace(m_frame_var.name, m_frame_var.ToJSON()); + if (include_modules) + target_metrics_json.try_emplace("moduleIdentifiers", + std::move(json_module_uuid_array)); + + if (m_launch_or_attach_time && m_first_private_stop_time) { + double elapsed_time = + elapsed(*m_launch_or_attach_time, *m_first_private_stop_time); + target_metrics_json.try_emplace("launchOrAttachTime", elapsed_time); + } + if (m_launch_or_attach_time && m_first_public_stop_time) { + double elapsed_time = + elapsed(*m_launch_or_attach_time, *m_first_public_stop_time); + target_metrics_json.try_emplace("firstStopTime", elapsed_time); + } + target_metrics_json.try_emplace("targetCreateTime", + m_create_time.get().count()); + + json::Array breakpoints_array; + double totalBreakpointResolveTime = 0.0; + // Report both the normal breakpoint list and the internal breakpoint list. + for (int i = 0; i < 2; ++i) { + BreakpointList &breakpoints = target.GetBreakpointList(i == 1); + std::unique_lock<std::recursive_mutex> lock; + breakpoints.GetListMutex(lock); + size_t num_breakpoints = breakpoints.GetSize(); + for (size_t i = 0; i < num_breakpoints; i++) { + Breakpoint *bp = breakpoints.GetBreakpointAtIndex(i).get(); + breakpoints_array.push_back(bp->GetStatistics()); + totalBreakpointResolveTime += bp->GetResolveTime().count(); + } + } + target_metrics_json.try_emplace("breakpoints", + std::move(breakpoints_array)); + target_metrics_json.try_emplace("totalBreakpointResolveTime", + totalBreakpointResolveTime); + + if (process_sp) { + UnixSignalsSP unix_signals_sp = process_sp->GetUnixSignals(); + if (unix_signals_sp) + target_metrics_json.try_emplace( + "signals", unix_signals_sp->GetHitCountStatistics()); + } + } + + // Counting "totalSharedLibraryEventHitCount" from breakpoints of kind + // "shared-library-event". + { + uint32_t shared_library_event_breakpoint_hit_count = 0; + // The "shared-library-event" is only found in the internal breakpoint list. + BreakpointList &breakpoints = target.GetBreakpointList(/* internal */ true); + std::unique_lock<std::recursive_mutex> lock; + breakpoints.GetListMutex(lock); + size_t num_breakpoints = breakpoints.GetSize(); + for (size_t i = 0; i < num_breakpoints; i++) { + Breakpoint *bp = breakpoints.GetBreakpointAtIndex(i).get(); + if (strcmp(bp->GetBreakpointKind(), "shared-library-event") == 0) + shared_library_event_breakpoint_hit_count += bp->GetHitCount(); + } + + target_metrics_json.try_emplace("totalSharedLibraryEventHitCount", + shared_library_event_breakpoint_hit_count); + } + + if (process_sp) { + uint32_t stop_id = process_sp->GetStopID(); + target_metrics_json.try_emplace("stopCount", stop_id); + + llvm::StringRef dyld_plugin_name; + if (process_sp->GetDynamicLoader()) + dyld_plugin_name = process_sp->GetDynamicLoader()->GetPluginName(); + target_metrics_json.try_emplace("dyldPluginName", dyld_plugin_name); + } + target_metrics_json.try_emplace("sourceMapDeduceCount", + m_source_map_deduce_count); + return target_metrics_json; +} + +void TargetStats::SetLaunchOrAttachTime() { + m_launch_or_attach_time = StatsClock::now(); + m_first_private_stop_time = std::nullopt; +} + +void TargetStats::SetFirstPrivateStopTime() { + // Launching and attaching has many paths depending on if synchronous mode + // was used or if we are stopping at the entry point or not. Only set the + // first stop time if it hasn't already been set. + if (!m_first_private_stop_time) + m_first_private_stop_time = StatsClock::now(); +} + +void TargetStats::SetFirstPublicStopTime() { + // Launching and attaching has many paths depending on if synchronous mode + // was used or if we are stopping at the entry point or not. Only set the + // first stop time if it hasn't already been set. + if (!m_first_public_stop_time) + m_first_public_stop_time = StatsClock::now(); +} + +void TargetStats::IncreaseSourceMapDeduceCount() { + ++m_source_map_deduce_count; +} + +bool DebuggerStats::g_collecting_stats = false; + +llvm::json::Value DebuggerStats::ReportStatistics( + Debugger &debugger, Target *target, + const lldb_private::StatisticsOptions &options) { + + const bool summary_only = options.GetSummaryOnly(); + const bool load_all_debug_info = options.GetLoadAllDebugInfo(); + const bool include_targets = options.GetIncludeTargets(); + const bool include_modules = options.GetIncludeModules(); + const bool include_transcript = options.GetIncludeTranscript(); + + json::Array json_targets; + json::Array json_modules; + double symtab_parse_time = 0.0; + double symtab_index_time = 0.0; + double debug_parse_time = 0.0; + double debug_index_time = 0.0; + uint32_t symtabs_loaded = 0; + uint32_t symtabs_saved = 0; + uint32_t debug_index_loaded = 0; + uint32_t debug_index_saved = 0; + uint64_t debug_info_size = 0; + + std::vector<ModuleStats> modules; + std::lock_guard<std::recursive_mutex> guard( + Module::GetAllocationModuleCollectionMutex()); + const uint64_t num_modules = Module::GetNumberAllocatedModules(); + uint32_t num_debug_info_enabled_modules = 0; + uint32_t num_modules_has_debug_info = 0; + uint32_t num_modules_with_variable_errors = 0; + uint32_t num_modules_with_incomplete_types = 0; + uint32_t num_stripped_modules = 0; + for (size_t image_idx = 0; image_idx < num_modules; ++image_idx) { + Module *module = Module::GetAllocatedModuleAtIndex(image_idx); + ModuleStats module_stat; + module_stat.symtab_parse_time = module->GetSymtabParseTime().get().count(); + module_stat.symtab_index_time = module->GetSymtabIndexTime().get().count(); + Symtab *symtab = module->GetSymtab(); + if (symtab) { + module_stat.symtab_loaded_from_cache = symtab->GetWasLoadedFromCache(); + if (module_stat.symtab_loaded_from_cache) + ++symtabs_loaded; + module_stat.symtab_saved_to_cache = symtab->GetWasSavedToCache(); + if (module_stat.symtab_saved_to_cache) + ++symtabs_saved; + } + SymbolFile *sym_file = module->GetSymbolFile(); + if (sym_file) { + if (!summary_only) { + if (sym_file->GetObjectFile() != module->GetObjectFile()) + module_stat.symfile_path = + sym_file->GetObjectFile()->GetFileSpec().GetPath(); + ModuleList symbol_modules = sym_file->GetDebugInfoModules(); + for (const auto &symbol_module : symbol_modules.Modules()) + module_stat.symfile_modules.push_back((intptr_t)symbol_module.get()); + } + module_stat.debug_info_index_loaded_from_cache = + sym_file->GetDebugInfoIndexWasLoadedFromCache(); + if (module_stat.debug_info_index_loaded_from_cache) + ++debug_index_loaded; + module_stat.debug_info_index_saved_to_cache = + sym_file->GetDebugInfoIndexWasSavedToCache(); + if (module_stat.debug_info_index_saved_to_cache) + ++debug_index_saved; + module_stat.debug_index_time = sym_file->GetDebugInfoIndexTime().count(); + module_stat.debug_parse_time = sym_file->GetDebugInfoParseTime().count(); + module_stat.debug_info_size = + sym_file->GetDebugInfoSize(load_all_debug_info); + module_stat.symtab_stripped = module->GetObjectFile()->IsStripped(); + if (module_stat.symtab_stripped) + ++num_stripped_modules; + module_stat.debug_info_enabled = sym_file->GetLoadDebugInfoEnabled() && + module_stat.debug_info_size > 0; + module_stat.debug_info_had_variable_errors = + sym_file->GetDebugInfoHadFrameVariableErrors(); + if (module_stat.debug_info_enabled) + ++num_debug_info_enabled_modules; + if (module_stat.debug_info_size > 0) + ++num_modules_has_debug_info; + if (module_stat.debug_info_had_variable_errors) + ++num_modules_with_variable_errors; + } + symtab_parse_time += module_stat.symtab_parse_time; + symtab_index_time += module_stat.symtab_index_time; + debug_parse_time += module_stat.debug_parse_time; + debug_index_time += module_stat.debug_index_time; + debug_info_size += module_stat.debug_info_size; + module->ForEachTypeSystem([&](lldb::TypeSystemSP ts) { + if (auto stats = ts->ReportStatistics()) + module_stat.type_system_stats.insert({ts->GetPluginName(), *stats}); + if (ts->GetHasForcefullyCompletedTypes()) + module_stat.debug_info_had_incomplete_types = true; + return true; + }); + if (module_stat.debug_info_had_incomplete_types) + ++num_modules_with_incomplete_types; + + if (include_modules) { + module_stat.identifier = (intptr_t)module; + module_stat.path = module->GetFileSpec().GetPath(); + if (ConstString object_name = module->GetObjectName()) { + module_stat.path.append(1, '('); + module_stat.path.append(object_name.GetStringRef().str()); + module_stat.path.append(1, ')'); + } + module_stat.uuid = module->GetUUID().GetAsString(); + module_stat.triple = module->GetArchitecture().GetTriple().str(); + json_modules.emplace_back(module_stat.ToJSON()); + } + } + + json::Object global_stats{ + {"totalSymbolTableParseTime", symtab_parse_time}, + {"totalSymbolTableIndexTime", symtab_index_time}, + {"totalSymbolTablesLoadedFromCache", symtabs_loaded}, + {"totalSymbolTablesSavedToCache", symtabs_saved}, + {"totalDebugInfoParseTime", debug_parse_time}, + {"totalDebugInfoIndexTime", debug_index_time}, + {"totalDebugInfoIndexLoadedFromCache", debug_index_loaded}, + {"totalDebugInfoIndexSavedToCache", debug_index_saved}, + {"totalDebugInfoByteSize", debug_info_size}, + {"totalModuleCount", num_modules}, + {"totalModuleCountHasDebugInfo", num_modules_has_debug_info}, + {"totalModuleCountWithVariableErrors", num_modules_with_variable_errors}, + {"totalModuleCountWithIncompleteTypes", + num_modules_with_incomplete_types}, + {"totalDebugInfoEnabled", num_debug_info_enabled_modules}, + {"totalSymbolTableStripped", num_stripped_modules}, + }; + + if (include_targets) { + if (target) { + json_targets.emplace_back(target->ReportStatistics(options)); + } else { + for (const auto &target : debugger.GetTargetList().Targets()) + json_targets.emplace_back(target->ReportStatistics(options)); + } + global_stats.try_emplace("targets", std::move(json_targets)); + } + + ConstStringStats const_string_stats; + json::Object json_memory{ + {"strings", const_string_stats.ToJSON()}, + }; + global_stats.try_emplace("memory", std::move(json_memory)); + if (!summary_only) { + json::Value cmd_stats = debugger.GetCommandInterpreter().GetStatistics(); + global_stats.try_emplace("commands", std::move(cmd_stats)); + } + + if (include_modules) { + global_stats.try_emplace("modules", std::move(json_modules)); + } + + if (include_transcript) { + // When transcript is available, add it to the to-be-returned statistics. + // + // NOTE: + // When the statistics is polled by an LLDB command: + // - The transcript in the returned statistics *will NOT* contain the + // returned statistics itself (otherwise infinite recursion). + // - The returned statistics *will* be written to the internal transcript + // buffer. It *will* appear in the next statistcs or transcript poll. + // + // For example, let's say the following commands are run in order: + // - "version" + // - "statistics dump" <- call it "A" + // - "statistics dump" <- call it "B" + // The output of "A" will contain the transcript of "version" and + // "statistics dump" (A), with the latter having empty output. The output + // of B will contain the trascnript of "version", "statistics dump" (A), + // "statistics dump" (B), with A's output populated and B's output empty. + const StructuredData::Array &transcript = + debugger.GetCommandInterpreter().GetTranscript(); + if (transcript.GetSize() != 0) { + std::string buffer; + llvm::raw_string_ostream ss(buffer); + json::OStream json_os(ss); + transcript.Serialize(json_os); + if (auto json_transcript = llvm::json::parse(ss.str())) + global_stats.try_emplace("transcript", + std::move(json_transcript.get())); + } + } + + return std::move(global_stats); +} diff --git a/contrib/llvm-project/lldb/source/Target/StopInfo.cpp b/contrib/llvm-project/lldb/source/Target/StopInfo.cpp new file mode 100644 index 000000000000..95f78056b164 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/StopInfo.cpp @@ -0,0 +1,1494 @@ +//===-- StopInfo.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> + +#include "lldb/Breakpoint/Breakpoint.h" +#include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Breakpoint/StoppointCallbackContext.h" +#include "lldb/Breakpoint/Watchpoint.h" +#include "lldb/Breakpoint/WatchpointResource.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/ValueObject.h" +#include "lldb/Expression/UserExpression.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/StopInfo.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadPlan.h" +#include "lldb/Target/ThreadPlanStepInstruction.h" +#include "lldb/Target/UnixSignals.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/StreamString.h" + +using namespace lldb; +using namespace lldb_private; + +StopInfo::StopInfo(Thread &thread, uint64_t value) + : m_thread_wp(thread.shared_from_this()), + m_stop_id(thread.GetProcess()->GetStopID()), + m_resume_id(thread.GetProcess()->GetResumeID()), m_value(value), + m_description(), m_override_should_notify(eLazyBoolCalculate), + m_override_should_stop(eLazyBoolCalculate), m_extended_info() {} + +bool StopInfo::IsValid() const { + ThreadSP thread_sp(m_thread_wp.lock()); + if (thread_sp) + return thread_sp->GetProcess()->GetStopID() == m_stop_id; + return false; +} + +void StopInfo::MakeStopInfoValid() { + ThreadSP thread_sp(m_thread_wp.lock()); + if (thread_sp) { + m_stop_id = thread_sp->GetProcess()->GetStopID(); + m_resume_id = thread_sp->GetProcess()->GetResumeID(); + } +} + +bool StopInfo::HasTargetRunSinceMe() { + ThreadSP thread_sp(m_thread_wp.lock()); + + if (thread_sp) { + lldb::StateType ret_type = thread_sp->GetProcess()->GetPrivateState(); + if (ret_type == eStateRunning) { + return true; + } else if (ret_type == eStateStopped) { + // This is a little tricky. We want to count "run and stopped again + // before you could ask this question as a "TRUE" answer to + // HasTargetRunSinceMe. But we don't want to include any running of the + // target done for expressions. So we track both resumes, and resumes + // caused by expressions, and check if there are any resumes + // NOT caused + // by expressions. + + uint32_t curr_resume_id = thread_sp->GetProcess()->GetResumeID(); + uint32_t last_user_expression_id = + thread_sp->GetProcess()->GetLastUserExpressionResumeID(); + if (curr_resume_id == m_resume_id) { + return false; + } else if (curr_resume_id > last_user_expression_id) { + return true; + } + } + } + return false; +} + +// StopInfoBreakpoint + +namespace lldb_private { +class StopInfoBreakpoint : public StopInfo { +public: + StopInfoBreakpoint(Thread &thread, break_id_t break_id) + : StopInfo(thread, break_id), m_should_stop(false), + m_should_stop_is_valid(false), m_should_perform_action(true), + m_address(LLDB_INVALID_ADDRESS), m_break_id(LLDB_INVALID_BREAK_ID), + m_was_all_internal(false), m_was_one_shot(false) { + StoreBPInfo(); + } + + StopInfoBreakpoint(Thread &thread, break_id_t break_id, bool should_stop) + : StopInfo(thread, break_id), m_should_stop(should_stop), + m_should_stop_is_valid(true), m_should_perform_action(true), + m_address(LLDB_INVALID_ADDRESS), m_break_id(LLDB_INVALID_BREAK_ID), + m_was_all_internal(false), m_was_one_shot(false) { + StoreBPInfo(); + } + + ~StopInfoBreakpoint() override = default; + + void StoreBPInfo() { + ThreadSP thread_sp(m_thread_wp.lock()); + if (thread_sp) { + BreakpointSiteSP bp_site_sp( + thread_sp->GetProcess()->GetBreakpointSiteList().FindByID(m_value)); + if (bp_site_sp) { + uint32_t num_constituents = bp_site_sp->GetNumberOfConstituents(); + if (num_constituents == 1) { + BreakpointLocationSP bp_loc_sp = bp_site_sp->GetConstituentAtIndex(0); + if (bp_loc_sp) { + Breakpoint & bkpt = bp_loc_sp->GetBreakpoint(); + m_break_id = bkpt.GetID(); + m_was_one_shot = bkpt.IsOneShot(); + m_was_all_internal = bkpt.IsInternal(); + } + } else { + m_was_all_internal = true; + for (uint32_t i = 0; i < num_constituents; i++) { + if (!bp_site_sp->GetConstituentAtIndex(i) + ->GetBreakpoint() + .IsInternal()) { + m_was_all_internal = false; + break; + } + } + } + m_address = bp_site_sp->GetLoadAddress(); + } + } + } + + bool IsValidForOperatingSystemThread(Thread &thread) override { + ProcessSP process_sp(thread.GetProcess()); + if (process_sp) { + BreakpointSiteSP bp_site_sp( + process_sp->GetBreakpointSiteList().FindByID(m_value)); + if (bp_site_sp) + return bp_site_sp->ValidForThisThread(thread); + } + return false; + } + + StopReason GetStopReason() const override { return eStopReasonBreakpoint; } + + bool ShouldStopSynchronous(Event *event_ptr) override { + ThreadSP thread_sp(m_thread_wp.lock()); + if (thread_sp) { + if (!m_should_stop_is_valid) { + // Only check once if we should stop at a breakpoint + BreakpointSiteSP bp_site_sp( + thread_sp->GetProcess()->GetBreakpointSiteList().FindByID(m_value)); + if (bp_site_sp) { + ExecutionContext exe_ctx(thread_sp->GetStackFrameAtIndex(0)); + StoppointCallbackContext context(event_ptr, exe_ctx, true); + bp_site_sp->BumpHitCounts(); + m_should_stop = bp_site_sp->ShouldStop(&context); + } else { + Log *log = GetLog(LLDBLog::Process); + + LLDB_LOGF(log, + "Process::%s could not find breakpoint site id: %" PRId64 + "...", + __FUNCTION__, m_value); + + m_should_stop = true; + } + m_should_stop_is_valid = true; + } + return m_should_stop; + } + return false; + } + + bool DoShouldNotify(Event *event_ptr) override { + return !m_was_all_internal; + } + + const char *GetDescription() override { + if (m_description.empty()) { + ThreadSP thread_sp(m_thread_wp.lock()); + if (thread_sp) { + BreakpointSiteSP bp_site_sp( + thread_sp->GetProcess()->GetBreakpointSiteList().FindByID(m_value)); + if (bp_site_sp) { + StreamString strm; + // If we have just hit an internal breakpoint, and it has a kind + // description, print that instead of the full breakpoint printing: + if (bp_site_sp->IsInternal()) { + size_t num_constituents = bp_site_sp->GetNumberOfConstituents(); + for (size_t idx = 0; idx < num_constituents; idx++) { + const char *kind = bp_site_sp->GetConstituentAtIndex(idx) + ->GetBreakpoint() + .GetBreakpointKind(); + if (kind != nullptr) { + m_description.assign(kind); + return kind; + } + } + } + + strm.Printf("breakpoint "); + bp_site_sp->GetDescription(&strm, eDescriptionLevelBrief); + m_description = std::string(strm.GetString()); + } else { + StreamString strm; + if (m_break_id != LLDB_INVALID_BREAK_ID) { + BreakpointSP break_sp = + thread_sp->GetProcess()->GetTarget().GetBreakpointByID( + m_break_id); + if (break_sp) { + if (break_sp->IsInternal()) { + const char *kind = break_sp->GetBreakpointKind(); + if (kind) + strm.Printf("internal %s breakpoint(%d).", kind, m_break_id); + else + strm.Printf("internal breakpoint(%d).", m_break_id); + } else { + strm.Printf("breakpoint %d.", m_break_id); + } + } else { + if (m_was_one_shot) + strm.Printf("one-shot breakpoint %d", m_break_id); + else + strm.Printf("breakpoint %d which has been deleted.", + m_break_id); + } + } else if (m_address == LLDB_INVALID_ADDRESS) + strm.Printf("breakpoint site %" PRIi64 + " which has been deleted - unknown address", + m_value); + else + strm.Printf("breakpoint site %" PRIi64 + " which has been deleted - was at 0x%" PRIx64, + m_value, m_address); + + m_description = std::string(strm.GetString()); + } + } + } + return m_description.c_str(); + } + +protected: + bool ShouldStop(Event *event_ptr) override { + // This just reports the work done by PerformAction or the synchronous + // stop. It should only ever get called after they have had a chance to + // run. + assert(m_should_stop_is_valid); + return m_should_stop; + } + + void PerformAction(Event *event_ptr) override { + if (!m_should_perform_action) + return; + m_should_perform_action = false; + bool all_stopping_locs_internal = true; + + ThreadSP thread_sp(m_thread_wp.lock()); + + if (thread_sp) { + Log *log = GetLog(LLDBLog::Breakpoints | LLDBLog::Step); + + if (!thread_sp->IsValid()) { + // This shouldn't ever happen, but just in case, don't do more harm. + if (log) { + LLDB_LOGF(log, "PerformAction got called with an invalid thread."); + } + m_should_stop = true; + m_should_stop_is_valid = true; + return; + } + + BreakpointSiteSP bp_site_sp( + thread_sp->GetProcess()->GetBreakpointSiteList().FindByID(m_value)); + std::unordered_set<break_id_t> precondition_breakpoints; + // Breakpoints that fail their condition check are not considered to + // have been hit. If the only locations at this site have failed their + // conditions, we should change the stop-info to none. Otherwise, if we + // hit another breakpoint on a different thread which does stop, users + // will see a breakpont hit with a failed condition, which is wrong. + // Use this variable to tell us if that is true. + bool actually_hit_any_locations = false; + if (bp_site_sp) { + // Let's copy the constituents list out of the site and store them in a + // local list. That way if one of the breakpoint actions changes the + // site, then we won't be operating on a bad list. + BreakpointLocationCollection site_locations; + size_t num_constituents = + bp_site_sp->CopyConstituentsList(site_locations); + + if (num_constituents == 0) { + m_should_stop = true; + actually_hit_any_locations = true; // We're going to stop, don't + // change the stop info. + } else { + // We go through each location, and test first its precondition - + // this overrides everything. Note, we only do this once per + // breakpoint - not once per location... Then check the condition. + // If the condition says to stop, then we run the callback for that + // location. If that callback says to stop as well, then we set + // m_should_stop to true; we are going to stop. But we still want to + // give all the breakpoints whose conditions say we are going to stop + // a chance to run their callbacks. Of course if any callback + // restarts the target by putting "continue" in the callback, then + // we're going to restart, without running the rest of the callbacks. + // And in this case we will end up not stopping even if another + // location said we should stop. But that's better than not running + // all the callbacks. + + // There's one other complication here. We may have run an async + // breakpoint callback that said we should stop. We only want to + // override that if another breakpoint action says we shouldn't + // stop. If nobody else has an opinion, then we should stop if the + // async callback says we should. An example of this is the async + // shared library load notification breakpoint and the setting + // stop-on-sharedlibrary-events. + // We'll keep the async value in async_should_stop, and track whether + // anyone said we should NOT stop in actually_said_continue. + bool async_should_stop = false; + if (m_should_stop_is_valid) + async_should_stop = m_should_stop; + bool actually_said_continue = false; + + m_should_stop = false; + + // We don't select threads as we go through them testing breakpoint + // conditions and running commands. So we need to set the thread for + // expression evaluation here: + ThreadList::ExpressionExecutionThreadPusher thread_pusher(thread_sp); + + ExecutionContext exe_ctx(thread_sp->GetStackFrameAtIndex(0)); + Process *process = exe_ctx.GetProcessPtr(); + if (process->GetModIDRef().IsRunningExpression()) { + // If we are in the middle of evaluating an expression, don't run + // asynchronous breakpoint commands or expressions. That could + // lead to infinite recursion if the command or condition re-calls + // the function with this breakpoint. + // TODO: We can keep a list of the breakpoints we've seen while + // running expressions in the nested + // PerformAction calls that can arise when the action runs a + // function that hits another breakpoint, and only stop running + // commands when we see the same breakpoint hit a second time. + + m_should_stop_is_valid = true; + + // It is possible that the user has a breakpoint at the same site + // as the completed plan had (e.g. user has a breakpoint + // on a module entry point, and `ThreadPlanCallFunction` ends + // also there). We can't find an internal breakpoint in the loop + // later because it was already removed on the plan completion. + // So check if the plan was completed, and stop if so. + if (thread_sp->CompletedPlanOverridesBreakpoint()) { + m_should_stop = true; + thread_sp->ResetStopInfo(); + return; + } + + LLDB_LOGF(log, "StopInfoBreakpoint::PerformAction - Hit a " + "breakpoint while running an expression," + " not running commands to avoid recursion."); + bool ignoring_breakpoints = + process->GetIgnoreBreakpointsInExpressions(); + // Internal breakpoints should be allowed to do their job, we + // can make sure they don't do anything that would cause recursive + // command execution: + if (!m_was_all_internal) { + m_should_stop = !ignoring_breakpoints; + LLDB_LOGF(log, + "StopInfoBreakpoint::PerformAction - in expression, " + "continuing: %s.", + m_should_stop ? "true" : "false"); + Debugger::ReportWarning( + "hit breakpoint while running function, skipping commands " + "and conditions to prevent recursion", + process->GetTarget().GetDebugger().GetID()); + return; + } + } + + StoppointCallbackContext context(event_ptr, exe_ctx, false); + + // For safety's sake let's also grab an extra reference to the + // breakpoint constituents of the locations we're going to examine, + // since the locations are going to have to get back to their + // breakpoints, and the locations don't keep their constituents alive. + // I'm just sticking the BreakpointSP's in a vector since I'm only + // using it to locally increment their retain counts. + + std::vector<lldb::BreakpointSP> location_constituents; + + for (size_t j = 0; j < num_constituents; j++) { + BreakpointLocationSP loc(site_locations.GetByIndex(j)); + location_constituents.push_back( + loc->GetBreakpoint().shared_from_this()); + } + + for (size_t j = 0; j < num_constituents; j++) { + lldb::BreakpointLocationSP bp_loc_sp = site_locations.GetByIndex(j); + StreamString loc_desc; + if (log) { + bp_loc_sp->GetDescription(&loc_desc, eDescriptionLevelBrief); + } + // If another action disabled this breakpoint or its location, then + // don't run the actions. + if (!bp_loc_sp->IsEnabled() || + !bp_loc_sp->GetBreakpoint().IsEnabled()) + continue; + + // The breakpoint site may have many locations associated with it, + // not all of them valid for this thread. Skip the ones that + // aren't: + if (!bp_loc_sp->ValidForThisThread(*thread_sp)) { + if (log) { + LLDB_LOGF(log, + "Breakpoint %s hit on thread 0x%llx but it was not " + "for this thread, continuing.", + loc_desc.GetData(), + static_cast<unsigned long long>(thread_sp->GetID())); + } + continue; + } + + // First run the precondition, but since the precondition is per + // breakpoint, only run it once per breakpoint. + std::pair<std::unordered_set<break_id_t>::iterator, bool> result = + precondition_breakpoints.insert( + bp_loc_sp->GetBreakpoint().GetID()); + if (!result.second) + continue; + + bool precondition_result = + bp_loc_sp->GetBreakpoint().EvaluatePrecondition(context); + if (!precondition_result) { + actually_said_continue = true; + continue; + } + // Next run the condition for the breakpoint. If that says we + // should stop, then we'll run the callback for the breakpoint. If + // the callback says we shouldn't stop that will win. + + if (bp_loc_sp->GetConditionText() == nullptr) + actually_hit_any_locations = true; + else { + Status condition_error; + bool condition_says_stop = + bp_loc_sp->ConditionSaysStop(exe_ctx, condition_error); + + if (!condition_error.Success()) { + // If the condition fails to evaluate, we are going to stop + // at it, so the location was hit. + actually_hit_any_locations = true; + const char *err_str = + condition_error.AsCString("<unknown error>"); + LLDB_LOGF(log, "Error evaluating condition: \"%s\"\n", err_str); + + StreamString strm; + strm << "stopped due to an error evaluating condition of " + "breakpoint "; + bp_loc_sp->GetDescription(&strm, eDescriptionLevelBrief); + strm << ": \"" << bp_loc_sp->GetConditionText() << "\"\n"; + strm << err_str; + + Debugger::ReportError( + strm.GetString().str(), + exe_ctx.GetTargetRef().GetDebugger().GetID()); + } else { + LLDB_LOGF(log, + "Condition evaluated for breakpoint %s on thread " + "0x%llx condition_says_stop: %i.", + loc_desc.GetData(), + static_cast<unsigned long long>(thread_sp->GetID()), + condition_says_stop); + if (condition_says_stop) + actually_hit_any_locations = true; + else { + // We don't want to increment the hit count of breakpoints if + // the condition fails. We've already bumped it by the time + // we get here, so undo the bump: + bp_loc_sp->UndoBumpHitCount(); + actually_said_continue = true; + continue; + } + } + } + + // We've done all the checks whose failure means "we consider lldb + // not to have hit the breakpoint". Now we're going to check for + // conditions that might continue after hitting. Start with the + // ignore count: + if (!bp_loc_sp->IgnoreCountShouldStop()) { + actually_said_continue = true; + continue; + } + + // Check the auto-continue bit on the location, do this before the + // callback since it may change this, but that would be for the + // NEXT hit. Note, you might think you could check auto-continue + // before the condition, and not evaluate the condition if it says + // to continue. But failing the condition means the breakpoint was + // effectively NOT HIT. So these two states are different. + bool auto_continue_says_stop = true; + if (bp_loc_sp->IsAutoContinue()) + { + LLDB_LOGF(log, + "Continuing breakpoint %s as AutoContinue was set.", + loc_desc.GetData()); + // We want this stop reported, so you will know we auto-continued + // but only for external breakpoints: + if (!bp_loc_sp->GetBreakpoint().IsInternal()) + thread_sp->SetShouldReportStop(eVoteYes); + auto_continue_says_stop = false; + } + + bool callback_says_stop = true; + + // FIXME: For now the callbacks have to run in async mode - the + // first time we restart we need + // to get out of there. So set it here. + // When we figure out how to nest breakpoint hits then this will + // change. + + // Don't run async callbacks in PerformAction. They have already + // been taken into account with async_should_stop. + if (!bp_loc_sp->IsCallbackSynchronous()) { + Debugger &debugger = thread_sp->CalculateTarget()->GetDebugger(); + bool old_async = debugger.GetAsyncExecution(); + debugger.SetAsyncExecution(true); + + callback_says_stop = bp_loc_sp->InvokeCallback(&context); + + debugger.SetAsyncExecution(old_async); + + if (callback_says_stop && auto_continue_says_stop) + m_should_stop = true; + else + actually_said_continue = true; + } + + if (m_should_stop && !bp_loc_sp->GetBreakpoint().IsInternal()) + all_stopping_locs_internal = false; + + // If we are going to stop for this breakpoint, then remove the + // breakpoint. + if (callback_says_stop && bp_loc_sp && + bp_loc_sp->GetBreakpoint().IsOneShot()) { + thread_sp->GetProcess()->GetTarget().RemoveBreakpointByID( + bp_loc_sp->GetBreakpoint().GetID()); + } + // Also make sure that the callback hasn't continued the target. If + // it did, when we'll set m_should_start to false and get out of + // here. + if (HasTargetRunSinceMe()) { + m_should_stop = false; + actually_said_continue = true; + break; + } + } + // At this point if nobody actually told us to continue, we should + // give the async breakpoint callback a chance to weigh in: + if (!actually_said_continue && !m_should_stop) { + m_should_stop = async_should_stop; + } + } + // We've figured out what this stop wants to do, so mark it as valid so + // we don't compute it again. + m_should_stop_is_valid = true; + } else { + m_should_stop = true; + m_should_stop_is_valid = true; + actually_hit_any_locations = true; + Log *log_process(GetLog(LLDBLog::Process)); + + LLDB_LOGF(log_process, + "Process::%s could not find breakpoint site id: %" PRId64 + "...", + __FUNCTION__, m_value); + } + + if ((!m_should_stop || all_stopping_locs_internal) && + thread_sp->CompletedPlanOverridesBreakpoint()) { + + // Override should_stop decision when we have completed step plan + // additionally to the breakpoint + m_should_stop = true; + + // We know we're stopping for a completed plan and we don't want to + // show the breakpoint stop, so compute the public stop info immediately + // here. + thread_sp->CalculatePublicStopInfo(); + } else if (!actually_hit_any_locations) { + // In the end, we didn't actually have any locations that passed their + // "was I hit" checks. So say we aren't stopped. + GetThread()->ResetStopInfo(); + LLDB_LOGF(log, "Process::%s all locations failed condition checks.", + __FUNCTION__); + } + + LLDB_LOGF(log, + "Process::%s returning from action with m_should_stop: %d.", + __FUNCTION__, m_should_stop); + } + } + +private: + bool m_should_stop; + bool m_should_stop_is_valid; + bool m_should_perform_action; // Since we are trying to preserve the "state" + // of the system even if we run functions + // etc. behind the users backs, we need to make sure we only REALLY perform + // the action once. + lldb::addr_t m_address; // We use this to capture the breakpoint site address + // when we create the StopInfo, + // in case somebody deletes it between the time the StopInfo is made and the + // description is asked for. + lldb::break_id_t m_break_id; + bool m_was_all_internal; + bool m_was_one_shot; +}; + +// StopInfoWatchpoint + +class StopInfoWatchpoint : public StopInfo { +public: + // Make sure watchpoint is properly disabled and subsequently enabled while + // performing watchpoint actions. + class WatchpointSentry { + public: + WatchpointSentry(ProcessSP p_sp, WatchpointSP w_sp) : process_sp(p_sp), + watchpoint_sp(w_sp) { + if (process_sp && watchpoint_sp) { + const bool notify = false; + watchpoint_sp->TurnOnEphemeralMode(); + process_sp->DisableWatchpoint(watchpoint_sp, notify); + process_sp->AddPreResumeAction(SentryPreResumeAction, this); + } + } + + void DoReenable() { + if (process_sp && watchpoint_sp) { + bool was_disabled = watchpoint_sp->IsDisabledDuringEphemeralMode(); + watchpoint_sp->TurnOffEphemeralMode(); + const bool notify = false; + if (was_disabled) { + process_sp->DisableWatchpoint(watchpoint_sp, notify); + } else { + process_sp->EnableWatchpoint(watchpoint_sp, notify); + } + } + } + + ~WatchpointSentry() { + DoReenable(); + if (process_sp) + process_sp->ClearPreResumeAction(SentryPreResumeAction, this); + } + + static bool SentryPreResumeAction(void *sentry_void) { + WatchpointSentry *sentry = (WatchpointSentry *) sentry_void; + sentry->DoReenable(); + return true; + } + + private: + ProcessSP process_sp; + WatchpointSP watchpoint_sp; + }; + + StopInfoWatchpoint(Thread &thread, break_id_t watch_id, bool silently_skip_wp) + : StopInfo(thread, watch_id), m_silently_skip_wp(silently_skip_wp) {} + + ~StopInfoWatchpoint() override = default; + + StopReason GetStopReason() const override { return eStopReasonWatchpoint; } + + const char *GetDescription() override { + if (m_description.empty()) { + StreamString strm; + strm.Printf("watchpoint %" PRIi64, m_value); + m_description = std::string(strm.GetString()); + } + return m_description.c_str(); + } + +protected: + using StopInfoWatchpointSP = std::shared_ptr<StopInfoWatchpoint>; + // This plan is used to orchestrate stepping over the watchpoint for + // architectures (e.g. ARM) that report the watch before running the watched + // access. This is the sort of job you have to defer to the thread plans, + // if you try to do it directly in the stop info and there are other threads + // that needed to process this stop you will have yanked control away from + // them and they won't behave correctly. + class ThreadPlanStepOverWatchpoint : public ThreadPlanStepInstruction { + public: + ThreadPlanStepOverWatchpoint(Thread &thread, + StopInfoWatchpointSP stop_info_sp, + WatchpointSP watch_sp) + : ThreadPlanStepInstruction(thread, false, true, eVoteNoOpinion, + eVoteNoOpinion), + m_stop_info_sp(stop_info_sp), m_watch_sp(watch_sp) { + assert(watch_sp); + } + + bool DoWillResume(lldb::StateType resume_state, + bool current_plan) override { + if (resume_state == eStateSuspended) + return true; + + if (!m_did_disable_wp) { + GetThread().GetProcess()->DisableWatchpoint(m_watch_sp, false); + m_did_disable_wp = true; + } + return true; + } + + bool DoPlanExplainsStop(Event *event_ptr) override { + if (ThreadPlanStepInstruction::DoPlanExplainsStop(event_ptr)) + return true; + StopInfoSP stop_info_sp = GetThread().GetPrivateStopInfo(); + // lldb-server resets the stop info for threads that didn't get to run, + // so we might have not gotten to run, but still have a watchpoint stop + // reason, in which case this will indeed be for us. + if (stop_info_sp + && stop_info_sp->GetStopReason() == eStopReasonWatchpoint) + return true; + return false; + } + + void DidPop() override { + // Don't artifically keep the watchpoint alive. + m_watch_sp.reset(); + } + + bool ShouldStop(Event *event_ptr) override { + bool should_stop = ThreadPlanStepInstruction::ShouldStop(event_ptr); + bool plan_done = MischiefManaged(); + if (plan_done) { + m_stop_info_sp->SetStepOverPlanComplete(); + GetThread().SetStopInfo(m_stop_info_sp); + ResetWatchpoint(); + } + return should_stop; + } + + bool ShouldRunBeforePublicStop() override { + return true; + } + + protected: + void ResetWatchpoint() { + if (!m_did_disable_wp) + return; + m_did_disable_wp = true; + GetThread().GetProcess()->EnableWatchpoint(m_watch_sp, true); + } + + private: + StopInfoWatchpointSP m_stop_info_sp; + WatchpointSP m_watch_sp; + bool m_did_disable_wp = false; + }; + + bool ShouldStopSynchronous(Event *event_ptr) override { + // If we are running our step-over the watchpoint plan, stop if it's done + // and continue if it's not: + if (m_should_stop_is_valid) + return m_should_stop; + + // If we are running our step over plan, then stop here and let the regular + // ShouldStop figure out what we should do: Otherwise, give our plan + // more time to get run: + if (m_using_step_over_plan) + return m_step_over_plan_complete; + + Log *log = GetLog(LLDBLog::Process); + ThreadSP thread_sp(m_thread_wp.lock()); + assert(thread_sp); + + if (thread_sp->GetTemporaryResumeState() == eStateSuspended) { + // This is the second firing of a watchpoint so don't process it again. + LLDB_LOG(log, "We didn't run but stopped with a StopInfoWatchpoint, we " + "have already handled this one, don't do it again."); + m_should_stop = false; + m_should_stop_is_valid = true; + return m_should_stop; + } + + WatchpointSP wp_sp( + thread_sp->CalculateTarget()->GetWatchpointList().FindByID(GetValue())); + // If we can no longer find the watchpoint, we just have to stop: + if (!wp_sp) { + + LLDB_LOGF(log, + "Process::%s could not find watchpoint location id: %" PRId64 + "...", + __FUNCTION__, GetValue()); + + m_should_stop = true; + m_should_stop_is_valid = true; + return true; + } + + ExecutionContext exe_ctx(thread_sp->GetStackFrameAtIndex(0)); + StoppointCallbackContext context(event_ptr, exe_ctx, true); + m_should_stop = wp_sp->ShouldStop(&context); + if (!m_should_stop) { + // This won't happen at present because we only allow one watchpoint per + // watched range. So we won't stop at a watched address with a disabled + // watchpoint. If we start allowing overlapping watchpoints, then we + // will have to make watchpoints be real "WatchpointSite" and delegate to + // all the watchpoints sharing the site. In that case, the code below + // would be the right thing to do. + m_should_stop_is_valid = true; + return m_should_stop; + } + // If this is a system where we need to execute the watchpoint by hand + // after the hit, queue a thread plan to do that, and then say not to stop. + // Otherwise, let the async action figure out whether the watchpoint should + // stop + + ProcessSP process_sp = exe_ctx.GetProcessSP(); + bool wp_triggers_after = process_sp->GetWatchpointReportedAfter(); + + if (!wp_triggers_after) { + // We have to step over the watchpoint before we know what to do: + StopInfoWatchpointSP me_as_siwp_sp + = std::static_pointer_cast<StopInfoWatchpoint>(shared_from_this()); + ThreadPlanSP step_over_wp_sp(new ThreadPlanStepOverWatchpoint( + *(thread_sp.get()), me_as_siwp_sp, wp_sp)); + // When this plan is done we want to stop, so set this as a Controlling + // plan. + step_over_wp_sp->SetIsControllingPlan(true); + step_over_wp_sp->SetOkayToDiscard(false); + + Status error; + error = thread_sp->QueueThreadPlan(step_over_wp_sp, false); + // If we couldn't push the thread plan, just stop here: + if (!error.Success()) { + LLDB_LOGF(log, "Could not push our step over watchpoint plan: %s", + error.AsCString()); + + m_should_stop = true; + m_should_stop_is_valid = true; + return true; + } else { + // Otherwise, don't set m_should_stop, we don't know that yet. Just + // say we should continue, and tell the thread we really should do so: + thread_sp->SetShouldRunBeforePublicStop(true); + m_using_step_over_plan = true; + return false; + } + } else { + // We didn't have to do anything special + m_should_stop_is_valid = true; + return m_should_stop; + } + + return m_should_stop; + } + + bool ShouldStop(Event *event_ptr) override { + // This just reports the work done by PerformAction or the synchronous + // stop. It should only ever get called after they have had a chance to + // run. + assert(m_should_stop_is_valid); + return m_should_stop; + } + + void PerformAction(Event *event_ptr) override { + Log *log = GetLog(LLDBLog::Watchpoints); + // We're going to calculate if we should stop or not in some way during the + // course of this code. Also by default we're going to stop, so set that + // here. + m_should_stop = true; + + + ThreadSP thread_sp(m_thread_wp.lock()); + if (thread_sp) { + + WatchpointSP wp_sp( + thread_sp->CalculateTarget()->GetWatchpointList().FindByID( + GetValue())); + if (wp_sp) { + // This sentry object makes sure the current watchpoint is disabled + // while performing watchpoint actions, and it is then enabled after we + // are finished. + ExecutionContext exe_ctx(thread_sp->GetStackFrameAtIndex(0)); + ProcessSP process_sp = exe_ctx.GetProcessSP(); + + WatchpointSentry sentry(process_sp, wp_sp); + + if (m_silently_skip_wp) { + m_should_stop = false; + wp_sp->UndoHitCount(); + } + + if (wp_sp->GetHitCount() <= wp_sp->GetIgnoreCount()) { + m_should_stop = false; + m_should_stop_is_valid = true; + } + + Debugger &debugger = exe_ctx.GetTargetRef().GetDebugger(); + + if (m_should_stop && wp_sp->GetConditionText() != nullptr) { + // We need to make sure the user sees any parse errors in their + // condition, so we'll hook the constructor errors up to the + // debugger's Async I/O. + ExpressionResults result_code; + EvaluateExpressionOptions expr_options; + expr_options.SetUnwindOnError(true); + expr_options.SetIgnoreBreakpoints(true); + ValueObjectSP result_value_sp; + Status error; + result_code = UserExpression::Evaluate( + exe_ctx, expr_options, wp_sp->GetConditionText(), + llvm::StringRef(), result_value_sp, error); + + if (result_code == eExpressionCompleted) { + if (result_value_sp) { + Scalar scalar_value; + if (result_value_sp->ResolveValue(scalar_value)) { + if (scalar_value.ULongLong(1) == 0) { + // The condition failed, which we consider "not having hit + // the watchpoint" so undo the hit count here. + wp_sp->UndoHitCount(); + m_should_stop = false; + } else + m_should_stop = true; + LLDB_LOGF(log, + "Condition successfully evaluated, result is %s.\n", + m_should_stop ? "true" : "false"); + } else { + m_should_stop = true; + LLDB_LOGF( + log, + "Failed to get an integer result from the expression."); + } + } + } else { + const char *err_str = error.AsCString("<unknown error>"); + LLDB_LOGF(log, "Error evaluating condition: \"%s\"\n", err_str); + + StreamString strm; + strm << "stopped due to an error evaluating condition of " + "watchpoint "; + wp_sp->GetDescription(&strm, eDescriptionLevelBrief); + strm << ": \"" << wp_sp->GetConditionText() << "\"\n"; + strm << err_str; + + Debugger::ReportError(strm.GetString().str(), + exe_ctx.GetTargetRef().GetDebugger().GetID()); + } + } + + // If the condition says to stop, we run the callback to further decide + // whether to stop. + if (m_should_stop) { + // FIXME: For now the callbacks have to run in async mode - the + // first time we restart we need + // to get out of there. So set it here. + // When we figure out how to nest watchpoint hits then this will + // change. + + bool old_async = debugger.GetAsyncExecution(); + debugger.SetAsyncExecution(true); + + StoppointCallbackContext context(event_ptr, exe_ctx, false); + bool stop_requested = wp_sp->InvokeCallback(&context); + + debugger.SetAsyncExecution(old_async); + + // Also make sure that the callback hasn't continued the target. If + // it did, when we'll set m_should_stop to false and get out of here. + if (HasTargetRunSinceMe()) + m_should_stop = false; + + if (m_should_stop && !stop_requested) { + // We have been vetoed by the callback mechanism. + m_should_stop = false; + } + } + + // Don't stop if the watched region value is unmodified, and + // this is a Modify-type watchpoint. + if (m_should_stop && !wp_sp->WatchedValueReportable(exe_ctx)) { + wp_sp->UndoHitCount(); + m_should_stop = false; + } + + // Finally, if we are going to stop, print out the new & old values: + if (m_should_stop) { + wp_sp->CaptureWatchedValue(exe_ctx); + + Debugger &debugger = exe_ctx.GetTargetRef().GetDebugger(); + StreamSP output_sp = debugger.GetAsyncOutputStream(); + if (wp_sp->DumpSnapshots(output_sp.get())) { + output_sp->EOL(); + output_sp->Flush(); + } + } + + } else { + Log *log_process(GetLog(LLDBLog::Process)); + + LLDB_LOGF(log_process, + "Process::%s could not find watchpoint id: %" PRId64 "...", + __FUNCTION__, m_value); + } + LLDB_LOGF(log, + "Process::%s returning from action with m_should_stop: %d.", + __FUNCTION__, m_should_stop); + + m_should_stop_is_valid = true; + } + } + +private: + void SetStepOverPlanComplete() { + assert(m_using_step_over_plan); + m_step_over_plan_complete = true; + } + + bool m_should_stop = false; + bool m_should_stop_is_valid = false; + // A false watchpoint hit has happened - + // the thread stopped with a watchpoint + // hit notification, but the watched region + // was not actually accessed (as determined + // by the gdb stub we're talking to). + // Continue past this watchpoint without + // notifying the user; on some targets this + // may mean disable wp, instruction step, + // re-enable wp, continue. + // On others, just continue. + bool m_silently_skip_wp = false; + bool m_step_over_plan_complete = false; + bool m_using_step_over_plan = false; +}; + +// StopInfoUnixSignal + +class StopInfoUnixSignal : public StopInfo { +public: + StopInfoUnixSignal(Thread &thread, int signo, const char *description, + std::optional<int> code) + : StopInfo(thread, signo), m_code(code) { + SetDescription(description); + } + + ~StopInfoUnixSignal() override = default; + + StopReason GetStopReason() const override { return eStopReasonSignal; } + + bool ShouldStopSynchronous(Event *event_ptr) override { + ThreadSP thread_sp(m_thread_wp.lock()); + if (thread_sp) + return thread_sp->GetProcess()->GetUnixSignals()->GetShouldStop(m_value); + return false; + } + + bool ShouldStop(Event *event_ptr) override { + ThreadSP thread_sp(m_thread_wp.lock()); + if (thread_sp) + return thread_sp->GetProcess()->GetUnixSignals()->GetShouldStop(m_value); + return false; + } + + // If should stop returns false, check if we should notify of this event + bool DoShouldNotify(Event *event_ptr) override { + ThreadSP thread_sp(m_thread_wp.lock()); + if (thread_sp) { + bool should_notify = + thread_sp->GetProcess()->GetUnixSignals()->GetShouldNotify(m_value); + if (should_notify) { + StreamString strm; + strm.Format( + "thread {0:d} received signal: {1}", thread_sp->GetIndexID(), + thread_sp->GetProcess()->GetUnixSignals()->GetSignalAsStringRef( + m_value)); + Process::ProcessEventData::AddRestartedReason(event_ptr, + strm.GetData()); + } + return should_notify; + } + return true; + } + + void WillResume(lldb::StateType resume_state) override { + ThreadSP thread_sp(m_thread_wp.lock()); + if (thread_sp) { + if (!thread_sp->GetProcess()->GetUnixSignals()->GetShouldSuppress( + m_value)) + thread_sp->SetResumeSignal(m_value); + } + } + + const char *GetDescription() override { + if (m_description.empty()) { + ThreadSP thread_sp(m_thread_wp.lock()); + if (thread_sp) { + UnixSignalsSP unix_signals = thread_sp->GetProcess()->GetUnixSignals(); + StreamString strm; + strm << "signal "; + + std::string signal_name = + unix_signals->GetSignalDescription(m_value, m_code); + if (signal_name.size()) + strm << signal_name; + else + strm.Printf("%" PRIi64, m_value); + + m_description = std::string(strm.GetString()); + } + } + return m_description.c_str(); + } + +private: + // In siginfo_t terms, if m_value is si_signo, m_code is si_code. + std::optional<int> m_code; +}; + +// StopInfoTrace + +class StopInfoTrace : public StopInfo { +public: + StopInfoTrace(Thread &thread) : StopInfo(thread, LLDB_INVALID_UID) {} + + ~StopInfoTrace() override = default; + + StopReason GetStopReason() const override { return eStopReasonTrace; } + + const char *GetDescription() override { + if (m_description.empty()) + return "trace"; + else + return m_description.c_str(); + } +}; + +// StopInfoException + +class StopInfoException : public StopInfo { +public: + StopInfoException(Thread &thread, const char *description) + : StopInfo(thread, LLDB_INVALID_UID) { + if (description) + SetDescription(description); + } + + ~StopInfoException() override = default; + + StopReason GetStopReason() const override { return eStopReasonException; } + + const char *GetDescription() override { + if (m_description.empty()) + return "exception"; + else + return m_description.c_str(); + } +}; + +// StopInfoProcessorTrace + +class StopInfoProcessorTrace : public StopInfo { +public: + StopInfoProcessorTrace(Thread &thread, const char *description) + : StopInfo(thread, LLDB_INVALID_UID) { + if (description) + SetDescription(description); + } + + ~StopInfoProcessorTrace() override = default; + + StopReason GetStopReason() const override { + return eStopReasonProcessorTrace; + } + + const char *GetDescription() override { + if (m_description.empty()) + return "processor trace event"; + else + return m_description.c_str(); + } +}; + +// StopInfoThreadPlan + +class StopInfoThreadPlan : public StopInfo { +public: + StopInfoThreadPlan(ThreadPlanSP &plan_sp, ValueObjectSP &return_valobj_sp, + ExpressionVariableSP &expression_variable_sp) + : StopInfo(plan_sp->GetThread(), LLDB_INVALID_UID), m_plan_sp(plan_sp), + m_return_valobj_sp(return_valobj_sp), + m_expression_variable_sp(expression_variable_sp) {} + + ~StopInfoThreadPlan() override = default; + + StopReason GetStopReason() const override { return eStopReasonPlanComplete; } + + const char *GetDescription() override { + if (m_description.empty()) { + StreamString strm; + m_plan_sp->GetDescription(&strm, eDescriptionLevelBrief); + m_description = std::string(strm.GetString()); + } + return m_description.c_str(); + } + + ValueObjectSP GetReturnValueObject() { return m_return_valobj_sp; } + + ExpressionVariableSP GetExpressionVariable() { + return m_expression_variable_sp; + } + +protected: + bool ShouldStop(Event *event_ptr) override { + if (m_plan_sp) + return m_plan_sp->ShouldStop(event_ptr); + else + return StopInfo::ShouldStop(event_ptr); + } + +private: + ThreadPlanSP m_plan_sp; + ValueObjectSP m_return_valobj_sp; + ExpressionVariableSP m_expression_variable_sp; +}; + +// StopInfoExec + +class StopInfoExec : public StopInfo { +public: + StopInfoExec(Thread &thread) : StopInfo(thread, LLDB_INVALID_UID) {} + + ~StopInfoExec() override = default; + + bool ShouldStop(Event *event_ptr) override { + ThreadSP thread_sp(m_thread_wp.lock()); + if (thread_sp) + return thread_sp->GetProcess()->GetStopOnExec(); + return false; + } + + StopReason GetStopReason() const override { return eStopReasonExec; } + + const char *GetDescription() override { return "exec"; } + +protected: + void PerformAction(Event *event_ptr) override { + // Only perform the action once + if (m_performed_action) + return; + m_performed_action = true; + ThreadSP thread_sp(m_thread_wp.lock()); + if (thread_sp) + thread_sp->GetProcess()->DidExec(); + } + + bool m_performed_action = false; +}; + +// StopInfoFork + +class StopInfoFork : public StopInfo { +public: + StopInfoFork(Thread &thread, lldb::pid_t child_pid, lldb::tid_t child_tid) + : StopInfo(thread, child_pid), m_child_pid(child_pid), + m_child_tid(child_tid) {} + + ~StopInfoFork() override = default; + + bool ShouldStop(Event *event_ptr) override { return false; } + + StopReason GetStopReason() const override { return eStopReasonFork; } + + const char *GetDescription() override { return "fork"; } + +protected: + void PerformAction(Event *event_ptr) override { + // Only perform the action once + if (m_performed_action) + return; + m_performed_action = true; + ThreadSP thread_sp(m_thread_wp.lock()); + if (thread_sp) + thread_sp->GetProcess()->DidFork(m_child_pid, m_child_tid); + } + + bool m_performed_action = false; + +private: + lldb::pid_t m_child_pid; + lldb::tid_t m_child_tid; +}; + +// StopInfoVFork + +class StopInfoVFork : public StopInfo { +public: + StopInfoVFork(Thread &thread, lldb::pid_t child_pid, lldb::tid_t child_tid) + : StopInfo(thread, child_pid), m_child_pid(child_pid), + m_child_tid(child_tid) {} + + ~StopInfoVFork() override = default; + + bool ShouldStop(Event *event_ptr) override { return false; } + + StopReason GetStopReason() const override { return eStopReasonVFork; } + + const char *GetDescription() override { return "vfork"; } + +protected: + void PerformAction(Event *event_ptr) override { + // Only perform the action once + if (m_performed_action) + return; + m_performed_action = true; + ThreadSP thread_sp(m_thread_wp.lock()); + if (thread_sp) + thread_sp->GetProcess()->DidVFork(m_child_pid, m_child_tid); + } + + bool m_performed_action = false; + +private: + lldb::pid_t m_child_pid; + lldb::tid_t m_child_tid; +}; + +// StopInfoVForkDone + +class StopInfoVForkDone : public StopInfo { +public: + StopInfoVForkDone(Thread &thread) : StopInfo(thread, 0) {} + + ~StopInfoVForkDone() override = default; + + bool ShouldStop(Event *event_ptr) override { return false; } + + StopReason GetStopReason() const override { return eStopReasonVForkDone; } + + const char *GetDescription() override { return "vforkdone"; } + +protected: + void PerformAction(Event *event_ptr) override { + // Only perform the action once + if (m_performed_action) + return; + m_performed_action = true; + ThreadSP thread_sp(m_thread_wp.lock()); + if (thread_sp) + thread_sp->GetProcess()->DidVForkDone(); + } + + bool m_performed_action = false; +}; + +} // namespace lldb_private + +StopInfoSP StopInfo::CreateStopReasonWithBreakpointSiteID(Thread &thread, + break_id_t break_id) { + return StopInfoSP(new StopInfoBreakpoint(thread, break_id)); +} + +StopInfoSP StopInfo::CreateStopReasonWithBreakpointSiteID(Thread &thread, + break_id_t break_id, + bool should_stop) { + return StopInfoSP(new StopInfoBreakpoint(thread, break_id, should_stop)); +} + +// LWP_TODO: We'll need a CreateStopReasonWithWatchpointResourceID akin +// to CreateStopReasonWithBreakpointSiteID +StopInfoSP StopInfo::CreateStopReasonWithWatchpointID(Thread &thread, + break_id_t watch_id, + bool silently_continue) { + return StopInfoSP( + new StopInfoWatchpoint(thread, watch_id, silently_continue)); +} + +StopInfoSP StopInfo::CreateStopReasonWithSignal(Thread &thread, int signo, + const char *description, + std::optional<int> code) { + thread.GetProcess()->GetUnixSignals()->IncrementSignalHitCount(signo); + return StopInfoSP(new StopInfoUnixSignal(thread, signo, description, code)); +} + +StopInfoSP StopInfo::CreateStopReasonToTrace(Thread &thread) { + return StopInfoSP(new StopInfoTrace(thread)); +} + +StopInfoSP StopInfo::CreateStopReasonWithPlan( + ThreadPlanSP &plan_sp, ValueObjectSP return_valobj_sp, + ExpressionVariableSP expression_variable_sp) { + return StopInfoSP(new StopInfoThreadPlan(plan_sp, return_valobj_sp, + expression_variable_sp)); +} + +StopInfoSP StopInfo::CreateStopReasonWithException(Thread &thread, + const char *description) { + return StopInfoSP(new StopInfoException(thread, description)); +} + +StopInfoSP StopInfo::CreateStopReasonProcessorTrace(Thread &thread, + const char *description) { + return StopInfoSP(new StopInfoProcessorTrace(thread, description)); +} + +StopInfoSP StopInfo::CreateStopReasonWithExec(Thread &thread) { + return StopInfoSP(new StopInfoExec(thread)); +} + +StopInfoSP StopInfo::CreateStopReasonFork(Thread &thread, + lldb::pid_t child_pid, + lldb::tid_t child_tid) { + return StopInfoSP(new StopInfoFork(thread, child_pid, child_tid)); +} + + +StopInfoSP StopInfo::CreateStopReasonVFork(Thread &thread, + lldb::pid_t child_pid, + lldb::tid_t child_tid) { + return StopInfoSP(new StopInfoVFork(thread, child_pid, child_tid)); +} + +StopInfoSP StopInfo::CreateStopReasonVForkDone(Thread &thread) { + return StopInfoSP(new StopInfoVForkDone(thread)); +} + +ValueObjectSP StopInfo::GetReturnValueObject(StopInfoSP &stop_info_sp) { + if (stop_info_sp && + stop_info_sp->GetStopReason() == eStopReasonPlanComplete) { + StopInfoThreadPlan *plan_stop_info = + static_cast<StopInfoThreadPlan *>(stop_info_sp.get()); + return plan_stop_info->GetReturnValueObject(); + } else + return ValueObjectSP(); +} + +ExpressionVariableSP StopInfo::GetExpressionVariable(StopInfoSP &stop_info_sp) { + if (stop_info_sp && + stop_info_sp->GetStopReason() == eStopReasonPlanComplete) { + StopInfoThreadPlan *plan_stop_info = + static_cast<StopInfoThreadPlan *>(stop_info_sp.get()); + return plan_stop_info->GetExpressionVariable(); + } else + return ExpressionVariableSP(); +} + +lldb::ValueObjectSP +StopInfo::GetCrashingDereference(StopInfoSP &stop_info_sp, + lldb::addr_t *crashing_address) { + if (!stop_info_sp) { + return ValueObjectSP(); + } + + const char *description = stop_info_sp->GetDescription(); + if (!description) { + return ValueObjectSP(); + } + + ThreadSP thread_sp = stop_info_sp->GetThread(); + if (!thread_sp) { + return ValueObjectSP(); + } + + StackFrameSP frame_sp = + thread_sp->GetSelectedFrame(DoNoSelectMostRelevantFrame); + + if (!frame_sp) { + return ValueObjectSP(); + } + + const char address_string[] = "address="; + + const char *address_loc = strstr(description, address_string); + if (!address_loc) { + return ValueObjectSP(); + } + + address_loc += (sizeof(address_string) - 1); + + uint64_t address = strtoull(address_loc, nullptr, 0); + if (crashing_address) { + *crashing_address = address; + } + + return frame_sp->GuessValueForAddress(address); +} diff --git a/contrib/llvm-project/lldb/source/Target/StructuredDataPlugin.cpp b/contrib/llvm-project/lldb/source/Target/StructuredDataPlugin.cpp new file mode 100644 index 000000000000..1b5894b5df4b --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/StructuredDataPlugin.cpp @@ -0,0 +1,67 @@ +//===-- StructuredDataPlugin.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 "lldb/Target/StructuredDataPlugin.h" + +#include "lldb/Core/Debugger.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandObjectMultiword.h" + +using namespace lldb; +using namespace lldb_private; + +namespace { +class CommandStructuredData : public CommandObjectMultiword { +public: + CommandStructuredData(CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "structured-data", + "Parent for per-plugin structured data commands", + "plugin structured-data <plugin>") {} + + ~CommandStructuredData() override = default; +}; +} + +StructuredDataPlugin::StructuredDataPlugin(const ProcessWP &process_wp) + : PluginInterface(), m_process_wp(process_wp) {} + +StructuredDataPlugin::~StructuredDataPlugin() = default; + +bool StructuredDataPlugin::GetEnabled(llvm::StringRef type_name) const { + // By default, plugins are always enabled. Plugin authors should override + // this if there is an enabled/disabled state for their plugin. + return true; +} + +ProcessSP StructuredDataPlugin::GetProcess() const { + return m_process_wp.lock(); +} + +void StructuredDataPlugin::InitializeBasePluginForDebugger(Debugger &debugger) { + // Create our mutliword command anchor if it doesn't already exist. + auto &interpreter = debugger.GetCommandInterpreter(); + if (!interpreter.GetCommandObject("plugin structured-data")) { + // Find the parent command. + auto parent_command = + debugger.GetCommandInterpreter().GetCommandObject("plugin"); + if (!parent_command) + return; + + // Create the structured-data ommand object. + auto command_name = "structured-data"; + auto command_sp = CommandObjectSP(new CommandStructuredData(interpreter)); + + // Hook it up under the top-level plugin command. + parent_command->LoadSubCommand(command_name, command_sp); + } +} + +void StructuredDataPlugin::ModulesDidLoad(Process &process, + ModuleList &module_list) { + // Default implementation does nothing. +} diff --git a/contrib/llvm-project/lldb/source/Target/SystemRuntime.cpp b/contrib/llvm-project/lldb/source/Target/SystemRuntime.cpp new file mode 100644 index 000000000000..6d8a2ef55225 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/SystemRuntime.cpp @@ -0,0 +1,49 @@ +//===-- SystemRuntime.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 "lldb/Target/SystemRuntime.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Target/Process.h" +#include "lldb/lldb-private.h" + +using namespace lldb; +using namespace lldb_private; + +SystemRuntime *SystemRuntime::FindPlugin(Process *process) { + SystemRuntimeCreateInstance create_callback = nullptr; + for (uint32_t idx = 0; + (create_callback = PluginManager::GetSystemRuntimeCreateCallbackAtIndex( + idx)) != nullptr; + ++idx) { + std::unique_ptr<SystemRuntime> instance_up(create_callback(process)); + if (instance_up) + return instance_up.release(); + } + return nullptr; +} + +SystemRuntime::SystemRuntime(Process *process) : Runtime(process), m_types() {} + +SystemRuntime::~SystemRuntime() = default; + +void SystemRuntime::DidAttach() {} + +void SystemRuntime::DidLaunch() {} + +void SystemRuntime::Detach() {} + +void SystemRuntime::ModulesDidLoad(const ModuleList &module_list) {} + +const std::vector<ConstString> &SystemRuntime::GetExtendedBacktraceTypes() { + return m_types; +} + +ThreadSP SystemRuntime::GetExtendedBacktraceThread(ThreadSP thread, + ConstString type) { + return ThreadSP(); +} diff --git a/contrib/llvm-project/lldb/source/Target/Target.cpp b/contrib/llvm-project/lldb/source/Target/Target.cpp new file mode 100644 index 000000000000..ec0da8a1378a --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/Target.cpp @@ -0,0 +1,4949 @@ +//===-- Target.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 "lldb/Target/Target.h" +#include "lldb/Breakpoint/BreakpointIDList.h" +#include "lldb/Breakpoint/BreakpointPrecondition.h" +#include "lldb/Breakpoint/BreakpointResolver.h" +#include "lldb/Breakpoint/BreakpointResolverAddress.h" +#include "lldb/Breakpoint/BreakpointResolverFileLine.h" +#include "lldb/Breakpoint/BreakpointResolverFileRegex.h" +#include "lldb/Breakpoint/BreakpointResolverName.h" +#include "lldb/Breakpoint/BreakpointResolverScripted.h" +#include "lldb/Breakpoint/Watchpoint.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/SearchFilter.h" +#include "lldb/Core/Section.h" +#include "lldb/Core/SourceManager.h" +#include "lldb/Core/StructuredDataImpl.h" +#include "lldb/Core/ValueObject.h" +#include "lldb/Core/ValueObjectConstResult.h" +#include "lldb/Expression/DiagnosticManager.h" +#include "lldb/Expression/ExpressionVariable.h" +#include "lldb/Expression/REPL.h" +#include "lldb/Expression/UserExpression.h" +#include "lldb/Expression/UtilityFunction.h" +#include "lldb/Host/Host.h" +#include "lldb/Host/PosixApi.h" +#include "lldb/Host/StreamFile.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionGroupWatchpoint.h" +#include "lldb/Interpreter/OptionValues.h" +#include "lldb/Interpreter/Property.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Target/ABI.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Language.h" +#include "lldb/Target/LanguageRuntime.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterTypeBuilder.h" +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Target/StackFrameRecognizer.h" +#include "lldb/Target/SystemRuntime.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadSpec.h" +#include "lldb/Target/UnixSignals.h" +#include "lldb/Utility/Event.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/LLDBAssert.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/State.h" +#include "lldb/Utility/StreamString.h" +#include "lldb/Utility/Timer.h" + +#include "llvm/ADT/ScopeExit.h" +#include "llvm/ADT/SetVector.h" + +#include <memory> +#include <mutex> +#include <optional> +#include <sstream> + +using namespace lldb; +using namespace lldb_private; + +constexpr std::chrono::milliseconds EvaluateExpressionOptions::default_timeout; + +Target::Arch::Arch(const ArchSpec &spec) + : m_spec(spec), + m_plugin_up(PluginManager::CreateArchitectureInstance(spec)) {} + +const Target::Arch &Target::Arch::operator=(const ArchSpec &spec) { + m_spec = spec; + m_plugin_up = PluginManager::CreateArchitectureInstance(spec); + return *this; +} + +llvm::StringRef Target::GetStaticBroadcasterClass() { + static constexpr llvm::StringLiteral class_name("lldb.target"); + return class_name; +} + +Target::Target(Debugger &debugger, const ArchSpec &target_arch, + const lldb::PlatformSP &platform_sp, bool is_dummy_target) + : TargetProperties(this), + Broadcaster(debugger.GetBroadcasterManager(), + Target::GetStaticBroadcasterClass().str()), + ExecutionContextScope(), m_debugger(debugger), m_platform_sp(platform_sp), + m_mutex(), m_arch(target_arch), m_images(this), m_section_load_history(), + m_breakpoint_list(false), m_internal_breakpoint_list(true), + m_watchpoint_list(), m_process_sp(), m_search_filter_sp(), + m_image_search_paths(ImageSearchPathsChanged, this), + m_source_manager_up(), m_stop_hooks(), m_stop_hook_next_id(0), + m_latest_stop_hook_id(0), m_valid(true), m_suppress_stop_hooks(false), + m_is_dummy_target(is_dummy_target), + m_frame_recognizer_manager_up( + std::make_unique<StackFrameRecognizerManager>()) { + SetEventName(eBroadcastBitBreakpointChanged, "breakpoint-changed"); + SetEventName(eBroadcastBitModulesLoaded, "modules-loaded"); + SetEventName(eBroadcastBitModulesUnloaded, "modules-unloaded"); + SetEventName(eBroadcastBitWatchpointChanged, "watchpoint-changed"); + SetEventName(eBroadcastBitSymbolsLoaded, "symbols-loaded"); + + CheckInWithManager(); + + LLDB_LOG(GetLog(LLDBLog::Object), "{0} Target::Target()", + static_cast<void *>(this)); + if (target_arch.IsValid()) { + LLDB_LOG(GetLog(LLDBLog::Target), + "Target::Target created with architecture {0} ({1})", + target_arch.GetArchitectureName(), + target_arch.GetTriple().getTriple().c_str()); + } + + UpdateLaunchInfoFromProperties(); +} + +Target::~Target() { + Log *log = GetLog(LLDBLog::Object); + LLDB_LOG(log, "{0} Target::~Target()", static_cast<void *>(this)); + DeleteCurrentProcess(); +} + +void Target::PrimeFromDummyTarget(Target &target) { + m_stop_hooks = target.m_stop_hooks; + + for (const auto &breakpoint_sp : target.m_breakpoint_list.Breakpoints()) { + if (breakpoint_sp->IsInternal()) + continue; + + BreakpointSP new_bp( + Breakpoint::CopyFromBreakpoint(shared_from_this(), *breakpoint_sp)); + AddBreakpoint(std::move(new_bp), false); + } + + for (const auto &bp_name_entry : target.m_breakpoint_names) { + AddBreakpointName(std::make_unique<BreakpointName>(*bp_name_entry.second)); + } + + m_frame_recognizer_manager_up = std::make_unique<StackFrameRecognizerManager>( + *target.m_frame_recognizer_manager_up); + + m_dummy_signals = target.m_dummy_signals; +} + +void Target::Dump(Stream *s, lldb::DescriptionLevel description_level) { + // s->Printf("%.*p: ", (int)sizeof(void*) * 2, this); + if (description_level != lldb::eDescriptionLevelBrief) { + s->Indent(); + s->PutCString("Target\n"); + s->IndentMore(); + m_images.Dump(s); + m_breakpoint_list.Dump(s); + m_internal_breakpoint_list.Dump(s); + s->IndentLess(); + } else { + Module *exe_module = GetExecutableModulePointer(); + if (exe_module) + s->PutCString(exe_module->GetFileSpec().GetFilename().GetCString()); + else + s->PutCString("No executable module."); + } +} + +void Target::CleanupProcess() { + // Do any cleanup of the target we need to do between process instances. + // NB It is better to do this before destroying the process in case the + // clean up needs some help from the process. + m_breakpoint_list.ClearAllBreakpointSites(); + m_internal_breakpoint_list.ClearAllBreakpointSites(); + ResetBreakpointHitCounts(); + // Disable watchpoints just on the debugger side. + std::unique_lock<std::recursive_mutex> lock; + this->GetWatchpointList().GetListMutex(lock); + DisableAllWatchpoints(false); + ClearAllWatchpointHitCounts(); + ClearAllWatchpointHistoricValues(); + m_latest_stop_hook_id = 0; +} + +void Target::DeleteCurrentProcess() { + if (m_process_sp) { + // We dispose any active tracing sessions on the current process + m_trace_sp.reset(); + m_section_load_history.Clear(); + if (m_process_sp->IsAlive()) + m_process_sp->Destroy(false); + + m_process_sp->Finalize(false /* not destructing */); + + CleanupProcess(); + + m_process_sp.reset(); + } +} + +const lldb::ProcessSP &Target::CreateProcess(ListenerSP listener_sp, + llvm::StringRef plugin_name, + const FileSpec *crash_file, + bool can_connect) { + if (!listener_sp) + listener_sp = GetDebugger().GetListener(); + DeleteCurrentProcess(); + m_process_sp = Process::FindPlugin(shared_from_this(), plugin_name, + listener_sp, crash_file, can_connect); + return m_process_sp; +} + +const lldb::ProcessSP &Target::GetProcessSP() const { return m_process_sp; } + +lldb::REPLSP Target::GetREPL(Status &err, lldb::LanguageType language, + const char *repl_options, bool can_create) { + if (language == eLanguageTypeUnknown) + language = m_debugger.GetREPLLanguage(); + + if (language == eLanguageTypeUnknown) { + LanguageSet repl_languages = Language::GetLanguagesSupportingREPLs(); + + if (auto single_lang = repl_languages.GetSingularLanguage()) { + language = *single_lang; + } else if (repl_languages.Empty()) { + err.SetErrorString( + "LLDB isn't configured with REPL support for any languages."); + return REPLSP(); + } else { + err.SetErrorString( + "Multiple possible REPL languages. Please specify a language."); + return REPLSP(); + } + } + + REPLMap::iterator pos = m_repl_map.find(language); + + if (pos != m_repl_map.end()) { + return pos->second; + } + + if (!can_create) { + err.SetErrorStringWithFormat( + "Couldn't find an existing REPL for %s, and can't create a new one", + Language::GetNameForLanguageType(language)); + return lldb::REPLSP(); + } + + Debugger *const debugger = nullptr; + lldb::REPLSP ret = REPL::Create(err, language, debugger, this, repl_options); + + if (ret) { + m_repl_map[language] = ret; + return m_repl_map[language]; + } + + if (err.Success()) { + err.SetErrorStringWithFormat("Couldn't create a REPL for %s", + Language::GetNameForLanguageType(language)); + } + + return lldb::REPLSP(); +} + +void Target::SetREPL(lldb::LanguageType language, lldb::REPLSP repl_sp) { + lldbassert(!m_repl_map.count(language)); + + m_repl_map[language] = repl_sp; +} + +void Target::Destroy() { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + m_valid = false; + DeleteCurrentProcess(); + m_platform_sp.reset(); + m_arch = ArchSpec(); + ClearModules(true); + m_section_load_history.Clear(); + const bool notify = false; + m_breakpoint_list.RemoveAll(notify); + m_internal_breakpoint_list.RemoveAll(notify); + m_last_created_breakpoint.reset(); + m_watchpoint_list.RemoveAll(notify); + m_last_created_watchpoint.reset(); + m_search_filter_sp.reset(); + m_image_search_paths.Clear(notify); + m_stop_hooks.clear(); + m_stop_hook_next_id = 0; + m_suppress_stop_hooks = false; + m_repl_map.clear(); + Args signal_args; + ClearDummySignals(signal_args); +} + +llvm::StringRef Target::GetABIName() const { + lldb::ABISP abi_sp; + if (m_process_sp) + abi_sp = m_process_sp->GetABI(); + if (!abi_sp) + abi_sp = ABI::FindPlugin(ProcessSP(), GetArchitecture()); + if (abi_sp) + return abi_sp->GetPluginName(); + return {}; +} + +BreakpointList &Target::GetBreakpointList(bool internal) { + if (internal) + return m_internal_breakpoint_list; + else + return m_breakpoint_list; +} + +const BreakpointList &Target::GetBreakpointList(bool internal) const { + if (internal) + return m_internal_breakpoint_list; + else + return m_breakpoint_list; +} + +BreakpointSP Target::GetBreakpointByID(break_id_t break_id) { + BreakpointSP bp_sp; + + if (LLDB_BREAK_ID_IS_INTERNAL(break_id)) + bp_sp = m_internal_breakpoint_list.FindBreakpointByID(break_id); + else + bp_sp = m_breakpoint_list.FindBreakpointByID(break_id); + + return bp_sp; +} + +lldb::BreakpointSP +lldb_private::Target::CreateBreakpointAtUserEntry(Status &error) { + ModuleSP main_module_sp = GetExecutableModule(); + FileSpecList shared_lib_filter; + shared_lib_filter.Append(main_module_sp->GetFileSpec()); + llvm::SetVector<std::string, std::vector<std::string>, + std::unordered_set<std::string>> + entryPointNamesSet; + for (LanguageType lang_type : Language::GetSupportedLanguages()) { + Language *lang = Language::FindPlugin(lang_type); + if (!lang) { + error.SetErrorString("Language not found\n"); + return lldb::BreakpointSP(); + } + std::string entryPointName = lang->GetUserEntryPointName().str(); + if (!entryPointName.empty()) + entryPointNamesSet.insert(entryPointName); + } + if (entryPointNamesSet.empty()) { + error.SetErrorString("No entry point name found\n"); + return lldb::BreakpointSP(); + } + BreakpointSP bp_sp = CreateBreakpoint( + &shared_lib_filter, + /*containingSourceFiles=*/nullptr, entryPointNamesSet.takeVector(), + /*func_name_type_mask=*/eFunctionNameTypeFull, + /*language=*/eLanguageTypeUnknown, + /*offset=*/0, + /*skip_prologue=*/eLazyBoolNo, + /*internal=*/false, + /*hardware=*/false); + if (!bp_sp) { + error.SetErrorString("Breakpoint creation failed.\n"); + return lldb::BreakpointSP(); + } + bp_sp->SetOneShot(true); + return bp_sp; +} + +BreakpointSP Target::CreateSourceRegexBreakpoint( + const FileSpecList *containingModules, + const FileSpecList *source_file_spec_list, + const std::unordered_set<std::string> &function_names, + RegularExpression source_regex, bool internal, bool hardware, + LazyBool move_to_nearest_code) { + SearchFilterSP filter_sp(GetSearchFilterForModuleAndCUList( + containingModules, source_file_spec_list)); + if (move_to_nearest_code == eLazyBoolCalculate) + move_to_nearest_code = GetMoveToNearestCode() ? eLazyBoolYes : eLazyBoolNo; + BreakpointResolverSP resolver_sp(new BreakpointResolverFileRegex( + nullptr, std::move(source_regex), function_names, + !static_cast<bool>(move_to_nearest_code))); + + return CreateBreakpoint(filter_sp, resolver_sp, internal, hardware, true); +} + +BreakpointSP Target::CreateBreakpoint(const FileSpecList *containingModules, + const FileSpec &file, uint32_t line_no, + uint32_t column, lldb::addr_t offset, + LazyBool check_inlines, + LazyBool skip_prologue, bool internal, + bool hardware, + LazyBool move_to_nearest_code) { + FileSpec remapped_file; + std::optional<llvm::StringRef> removed_prefix_opt = + GetSourcePathMap().ReverseRemapPath(file, remapped_file); + if (!removed_prefix_opt) + remapped_file = file; + + if (check_inlines == eLazyBoolCalculate) { + const InlineStrategy inline_strategy = GetInlineStrategy(); + switch (inline_strategy) { + case eInlineBreakpointsNever: + check_inlines = eLazyBoolNo; + break; + + case eInlineBreakpointsHeaders: + if (remapped_file.IsSourceImplementationFile()) + check_inlines = eLazyBoolNo; + else + check_inlines = eLazyBoolYes; + break; + + case eInlineBreakpointsAlways: + check_inlines = eLazyBoolYes; + break; + } + } + SearchFilterSP filter_sp; + if (check_inlines == eLazyBoolNo) { + // Not checking for inlines, we are looking only for matching compile units + FileSpecList compile_unit_list; + compile_unit_list.Append(remapped_file); + filter_sp = GetSearchFilterForModuleAndCUList(containingModules, + &compile_unit_list); + } else { + filter_sp = GetSearchFilterForModuleList(containingModules); + } + if (skip_prologue == eLazyBoolCalculate) + skip_prologue = GetSkipPrologue() ? eLazyBoolYes : eLazyBoolNo; + if (move_to_nearest_code == eLazyBoolCalculate) + move_to_nearest_code = GetMoveToNearestCode() ? eLazyBoolYes : eLazyBoolNo; + + SourceLocationSpec location_spec(remapped_file, line_no, column, + check_inlines, + !static_cast<bool>(move_to_nearest_code)); + if (!location_spec) + return nullptr; + + BreakpointResolverSP resolver_sp(new BreakpointResolverFileLine( + nullptr, offset, skip_prologue, location_spec, removed_prefix_opt)); + return CreateBreakpoint(filter_sp, resolver_sp, internal, hardware, true); +} + +BreakpointSP Target::CreateBreakpoint(lldb::addr_t addr, bool internal, + bool hardware) { + Address so_addr; + + // Check for any reason we want to move this breakpoint to other address. + addr = GetBreakableLoadAddress(addr); + + // Attempt to resolve our load address if possible, though it is ok if it + // doesn't resolve to section/offset. + + // Try and resolve as a load address if possible + GetSectionLoadList().ResolveLoadAddress(addr, so_addr); + if (!so_addr.IsValid()) { + // The address didn't resolve, so just set this as an absolute address + so_addr.SetOffset(addr); + } + BreakpointSP bp_sp(CreateBreakpoint(so_addr, internal, hardware)); + return bp_sp; +} + +BreakpointSP Target::CreateBreakpoint(const Address &addr, bool internal, + bool hardware) { + SearchFilterSP filter_sp( + new SearchFilterForUnconstrainedSearches(shared_from_this())); + BreakpointResolverSP resolver_sp( + new BreakpointResolverAddress(nullptr, addr)); + return CreateBreakpoint(filter_sp, resolver_sp, internal, hardware, false); +} + +lldb::BreakpointSP +Target::CreateAddressInModuleBreakpoint(lldb::addr_t file_addr, bool internal, + const FileSpec &file_spec, + bool request_hardware) { + SearchFilterSP filter_sp( + new SearchFilterForUnconstrainedSearches(shared_from_this())); + BreakpointResolverSP resolver_sp(new BreakpointResolverAddress( + nullptr, file_addr, file_spec)); + return CreateBreakpoint(filter_sp, resolver_sp, internal, request_hardware, + false); +} + +BreakpointSP Target::CreateBreakpoint( + const FileSpecList *containingModules, + const FileSpecList *containingSourceFiles, const char *func_name, + FunctionNameType func_name_type_mask, LanguageType language, + lldb::addr_t offset, LazyBool skip_prologue, bool internal, bool hardware) { + BreakpointSP bp_sp; + if (func_name) { + SearchFilterSP filter_sp(GetSearchFilterForModuleAndCUList( + containingModules, containingSourceFiles)); + + if (skip_prologue == eLazyBoolCalculate) + skip_prologue = GetSkipPrologue() ? eLazyBoolYes : eLazyBoolNo; + if (language == lldb::eLanguageTypeUnknown) + language = GetLanguage().AsLanguageType(); + + BreakpointResolverSP resolver_sp(new BreakpointResolverName( + nullptr, func_name, func_name_type_mask, language, Breakpoint::Exact, + offset, skip_prologue)); + bp_sp = CreateBreakpoint(filter_sp, resolver_sp, internal, hardware, true); + } + return bp_sp; +} + +lldb::BreakpointSP +Target::CreateBreakpoint(const FileSpecList *containingModules, + const FileSpecList *containingSourceFiles, + const std::vector<std::string> &func_names, + FunctionNameType func_name_type_mask, + LanguageType language, lldb::addr_t offset, + LazyBool skip_prologue, bool internal, bool hardware) { + BreakpointSP bp_sp; + size_t num_names = func_names.size(); + if (num_names > 0) { + SearchFilterSP filter_sp(GetSearchFilterForModuleAndCUList( + containingModules, containingSourceFiles)); + + if (skip_prologue == eLazyBoolCalculate) + skip_prologue = GetSkipPrologue() ? eLazyBoolYes : eLazyBoolNo; + if (language == lldb::eLanguageTypeUnknown) + language = GetLanguage().AsLanguageType(); + + BreakpointResolverSP resolver_sp( + new BreakpointResolverName(nullptr, func_names, func_name_type_mask, + language, offset, skip_prologue)); + bp_sp = CreateBreakpoint(filter_sp, resolver_sp, internal, hardware, true); + } + return bp_sp; +} + +BreakpointSP +Target::CreateBreakpoint(const FileSpecList *containingModules, + const FileSpecList *containingSourceFiles, + const char *func_names[], size_t num_names, + FunctionNameType func_name_type_mask, + LanguageType language, lldb::addr_t offset, + LazyBool skip_prologue, bool internal, bool hardware) { + BreakpointSP bp_sp; + if (num_names > 0) { + SearchFilterSP filter_sp(GetSearchFilterForModuleAndCUList( + containingModules, containingSourceFiles)); + + if (skip_prologue == eLazyBoolCalculate) { + if (offset == 0) + skip_prologue = GetSkipPrologue() ? eLazyBoolYes : eLazyBoolNo; + else + skip_prologue = eLazyBoolNo; + } + if (language == lldb::eLanguageTypeUnknown) + language = GetLanguage().AsLanguageType(); + + BreakpointResolverSP resolver_sp(new BreakpointResolverName( + nullptr, func_names, num_names, func_name_type_mask, language, offset, + skip_prologue)); + resolver_sp->SetOffset(offset); + bp_sp = CreateBreakpoint(filter_sp, resolver_sp, internal, hardware, true); + } + return bp_sp; +} + +SearchFilterSP +Target::GetSearchFilterForModule(const FileSpec *containingModule) { + SearchFilterSP filter_sp; + if (containingModule != nullptr) { + // TODO: We should look into sharing module based search filters + // across many breakpoints like we do for the simple target based one + filter_sp = std::make_shared<SearchFilterByModule>(shared_from_this(), + *containingModule); + } else { + if (!m_search_filter_sp) + m_search_filter_sp = + std::make_shared<SearchFilterForUnconstrainedSearches>( + shared_from_this()); + filter_sp = m_search_filter_sp; + } + return filter_sp; +} + +SearchFilterSP +Target::GetSearchFilterForModuleList(const FileSpecList *containingModules) { + SearchFilterSP filter_sp; + if (containingModules && containingModules->GetSize() != 0) { + // TODO: We should look into sharing module based search filters + // across many breakpoints like we do for the simple target based one + filter_sp = std::make_shared<SearchFilterByModuleList>(shared_from_this(), + *containingModules); + } else { + if (!m_search_filter_sp) + m_search_filter_sp = + std::make_shared<SearchFilterForUnconstrainedSearches>( + shared_from_this()); + filter_sp = m_search_filter_sp; + } + return filter_sp; +} + +SearchFilterSP Target::GetSearchFilterForModuleAndCUList( + const FileSpecList *containingModules, + const FileSpecList *containingSourceFiles) { + if (containingSourceFiles == nullptr || containingSourceFiles->GetSize() == 0) + return GetSearchFilterForModuleList(containingModules); + + SearchFilterSP filter_sp; + if (containingModules == nullptr) { + // We could make a special "CU List only SearchFilter". Better yet was if + // these could be composable, but that will take a little reworking. + + filter_sp = std::make_shared<SearchFilterByModuleListAndCU>( + shared_from_this(), FileSpecList(), *containingSourceFiles); + } else { + filter_sp = std::make_shared<SearchFilterByModuleListAndCU>( + shared_from_this(), *containingModules, *containingSourceFiles); + } + return filter_sp; +} + +BreakpointSP Target::CreateFuncRegexBreakpoint( + const FileSpecList *containingModules, + const FileSpecList *containingSourceFiles, RegularExpression func_regex, + lldb::LanguageType requested_language, LazyBool skip_prologue, + bool internal, bool hardware) { + SearchFilterSP filter_sp(GetSearchFilterForModuleAndCUList( + containingModules, containingSourceFiles)); + bool skip = (skip_prologue == eLazyBoolCalculate) + ? GetSkipPrologue() + : static_cast<bool>(skip_prologue); + BreakpointResolverSP resolver_sp(new BreakpointResolverName( + nullptr, std::move(func_regex), requested_language, 0, skip)); + + return CreateBreakpoint(filter_sp, resolver_sp, internal, hardware, true); +} + +lldb::BreakpointSP +Target::CreateExceptionBreakpoint(enum lldb::LanguageType language, + bool catch_bp, bool throw_bp, bool internal, + Args *additional_args, Status *error) { + BreakpointSP exc_bkpt_sp = LanguageRuntime::CreateExceptionBreakpoint( + *this, language, catch_bp, throw_bp, internal); + if (exc_bkpt_sp && additional_args) { + BreakpointPreconditionSP precondition_sp = exc_bkpt_sp->GetPrecondition(); + if (precondition_sp && additional_args) { + if (error) + *error = precondition_sp->ConfigurePrecondition(*additional_args); + else + precondition_sp->ConfigurePrecondition(*additional_args); + } + } + return exc_bkpt_sp; +} + +lldb::BreakpointSP Target::CreateScriptedBreakpoint( + const llvm::StringRef class_name, const FileSpecList *containingModules, + const FileSpecList *containingSourceFiles, bool internal, + bool request_hardware, StructuredData::ObjectSP extra_args_sp, + Status *creation_error) { + SearchFilterSP filter_sp; + + lldb::SearchDepth depth = lldb::eSearchDepthTarget; + bool has_files = + containingSourceFiles && containingSourceFiles->GetSize() > 0; + bool has_modules = containingModules && containingModules->GetSize() > 0; + + if (has_files && has_modules) { + filter_sp = GetSearchFilterForModuleAndCUList(containingModules, + containingSourceFiles); + } else if (has_files) { + filter_sp = + GetSearchFilterForModuleAndCUList(nullptr, containingSourceFiles); + } else if (has_modules) { + filter_sp = GetSearchFilterForModuleList(containingModules); + } else { + filter_sp = std::make_shared<SearchFilterForUnconstrainedSearches>( + shared_from_this()); + } + + BreakpointResolverSP resolver_sp(new BreakpointResolverScripted( + nullptr, class_name, depth, StructuredDataImpl(extra_args_sp))); + return CreateBreakpoint(filter_sp, resolver_sp, internal, false, true); +} + +BreakpointSP Target::CreateBreakpoint(SearchFilterSP &filter_sp, + BreakpointResolverSP &resolver_sp, + bool internal, bool request_hardware, + bool resolve_indirect_symbols) { + BreakpointSP bp_sp; + if (filter_sp && resolver_sp) { + const bool hardware = request_hardware || GetRequireHardwareBreakpoints(); + bp_sp.reset(new Breakpoint(*this, filter_sp, resolver_sp, hardware, + resolve_indirect_symbols)); + resolver_sp->SetBreakpoint(bp_sp); + AddBreakpoint(bp_sp, internal); + } + return bp_sp; +} + +void Target::AddBreakpoint(lldb::BreakpointSP bp_sp, bool internal) { + if (!bp_sp) + return; + if (internal) + m_internal_breakpoint_list.Add(bp_sp, false); + else + m_breakpoint_list.Add(bp_sp, true); + + Log *log = GetLog(LLDBLog::Breakpoints); + if (log) { + StreamString s; + bp_sp->GetDescription(&s, lldb::eDescriptionLevelVerbose); + LLDB_LOGF(log, "Target::%s (internal = %s) => break_id = %s\n", + __FUNCTION__, bp_sp->IsInternal() ? "yes" : "no", s.GetData()); + } + + bp_sp->ResolveBreakpoint(); + + if (!internal) { + m_last_created_breakpoint = bp_sp; + } +} + +void Target::AddNameToBreakpoint(BreakpointID &id, llvm::StringRef name, + Status &error) { + BreakpointSP bp_sp = + m_breakpoint_list.FindBreakpointByID(id.GetBreakpointID()); + if (!bp_sp) { + StreamString s; + id.GetDescription(&s, eDescriptionLevelBrief); + error.SetErrorStringWithFormat("Could not find breakpoint %s", s.GetData()); + return; + } + AddNameToBreakpoint(bp_sp, name, error); +} + +void Target::AddNameToBreakpoint(BreakpointSP &bp_sp, llvm::StringRef name, + Status &error) { + if (!bp_sp) + return; + + BreakpointName *bp_name = FindBreakpointName(ConstString(name), true, error); + if (!bp_name) + return; + + bp_name->ConfigureBreakpoint(bp_sp); + bp_sp->AddName(name); +} + +void Target::AddBreakpointName(std::unique_ptr<BreakpointName> bp_name) { + m_breakpoint_names.insert( + std::make_pair(bp_name->GetName(), std::move(bp_name))); +} + +BreakpointName *Target::FindBreakpointName(ConstString name, bool can_create, + Status &error) { + BreakpointID::StringIsBreakpointName(name.GetStringRef(), error); + if (!error.Success()) + return nullptr; + + BreakpointNameList::iterator iter = m_breakpoint_names.find(name); + if (iter != m_breakpoint_names.end()) { + return iter->second.get(); + } + + if (!can_create) { + error.SetErrorStringWithFormat("Breakpoint name \"%s\" doesn't exist and " + "can_create is false.", + name.AsCString()); + return nullptr; + } + + return m_breakpoint_names + .insert(std::make_pair(name, std::make_unique<BreakpointName>(name))) + .first->second.get(); +} + +void Target::DeleteBreakpointName(ConstString name) { + BreakpointNameList::iterator iter = m_breakpoint_names.find(name); + + if (iter != m_breakpoint_names.end()) { + const char *name_cstr = name.AsCString(); + m_breakpoint_names.erase(iter); + for (auto bp_sp : m_breakpoint_list.Breakpoints()) + bp_sp->RemoveName(name_cstr); + } +} + +void Target::RemoveNameFromBreakpoint(lldb::BreakpointSP &bp_sp, + ConstString name) { + bp_sp->RemoveName(name.AsCString()); +} + +void Target::ConfigureBreakpointName( + BreakpointName &bp_name, const BreakpointOptions &new_options, + const BreakpointName::Permissions &new_permissions) { + bp_name.GetOptions().CopyOverSetOptions(new_options); + bp_name.GetPermissions().MergeInto(new_permissions); + ApplyNameToBreakpoints(bp_name); +} + +void Target::ApplyNameToBreakpoints(BreakpointName &bp_name) { + llvm::Expected<std::vector<BreakpointSP>> expected_vector = + m_breakpoint_list.FindBreakpointsByName(bp_name.GetName().AsCString()); + + if (!expected_vector) { + LLDB_LOG(GetLog(LLDBLog::Breakpoints), "invalid breakpoint name: {}", + llvm::toString(expected_vector.takeError())); + return; + } + + for (auto bp_sp : *expected_vector) + bp_name.ConfigureBreakpoint(bp_sp); +} + +void Target::GetBreakpointNames(std::vector<std::string> &names) { + names.clear(); + for (const auto& bp_name_entry : m_breakpoint_names) { + names.push_back(bp_name_entry.first.AsCString()); + } + llvm::sort(names); +} + +bool Target::ProcessIsValid() { + return (m_process_sp && m_process_sp->IsAlive()); +} + +static bool CheckIfWatchpointsSupported(Target *target, Status &error) { + std::optional<uint32_t> num_supported_hardware_watchpoints = + target->GetProcessSP()->GetWatchpointSlotCount(); + + // If unable to determine the # of watchpoints available, + // assume they are supported. + if (!num_supported_hardware_watchpoints) + return true; + + if (*num_supported_hardware_watchpoints == 0) { + error.SetErrorStringWithFormat( + "Target supports (%u) hardware watchpoint slots.\n", + *num_supported_hardware_watchpoints); + return false; + } + return true; +} + +// See also Watchpoint::SetWatchpointType(uint32_t type) and the +// OptionGroupWatchpoint::WatchType enum type. +WatchpointSP Target::CreateWatchpoint(lldb::addr_t addr, size_t size, + const CompilerType *type, uint32_t kind, + Status &error) { + Log *log = GetLog(LLDBLog::Watchpoints); + LLDB_LOGF(log, + "Target::%s (addr = 0x%8.8" PRIx64 " size = %" PRIu64 + " type = %u)\n", + __FUNCTION__, addr, (uint64_t)size, kind); + + WatchpointSP wp_sp; + if (!ProcessIsValid()) { + error.SetErrorString("process is not alive"); + return wp_sp; + } + + if (addr == LLDB_INVALID_ADDRESS || size == 0) { + if (size == 0) + error.SetErrorString("cannot set a watchpoint with watch_size of 0"); + else + error.SetErrorStringWithFormat("invalid watch address: %" PRIu64, addr); + return wp_sp; + } + + if (!LLDB_WATCH_TYPE_IS_VALID(kind)) { + error.SetErrorStringWithFormat("invalid watchpoint type: %d", kind); + } + + if (!CheckIfWatchpointsSupported(this, error)) + return wp_sp; + + // Currently we only support one watchpoint per address, with total number of + // watchpoints limited by the hardware which the inferior is running on. + + // Grab the list mutex while doing operations. + const bool notify = false; // Don't notify about all the state changes we do + // on creating the watchpoint. + + // Mask off ignored bits from watchpoint address. + if (ABISP abi = m_process_sp->GetABI()) + addr = abi->FixDataAddress(addr); + + // LWP_TODO this sequence is looking for an existing watchpoint + // at the exact same user-specified address, disables the new one + // if addr/size/type match. If type/size differ, disable old one. + // This isn't correct, we need both watchpoints to use a shared + // WatchpointResource in the target, and expand the WatchpointResource + // to handle the needs of both Watchpoints. + // Also, even if the addresses don't match, they may need to be + // supported by the same WatchpointResource, e.g. a watchpoint + // watching 1 byte at 0x102 and a watchpoint watching 1 byte at 0x103. + // They're in the same word and must be watched by a single hardware + // watchpoint register. + + std::unique_lock<std::recursive_mutex> lock; + this->GetWatchpointList().GetListMutex(lock); + WatchpointSP matched_sp = m_watchpoint_list.FindByAddress(addr); + if (matched_sp) { + size_t old_size = matched_sp->GetByteSize(); + uint32_t old_type = + (matched_sp->WatchpointRead() ? LLDB_WATCH_TYPE_READ : 0) | + (matched_sp->WatchpointWrite() ? LLDB_WATCH_TYPE_WRITE : 0) | + (matched_sp->WatchpointModify() ? LLDB_WATCH_TYPE_MODIFY : 0); + // Return the existing watchpoint if both size and type match. + if (size == old_size && kind == old_type) { + wp_sp = matched_sp; + wp_sp->SetEnabled(false, notify); + } else { + // Nil the matched watchpoint; we will be creating a new one. + m_process_sp->DisableWatchpoint(matched_sp, notify); + m_watchpoint_list.Remove(matched_sp->GetID(), true); + } + } + + if (!wp_sp) { + wp_sp = std::make_shared<Watchpoint>(*this, addr, size, type); + wp_sp->SetWatchpointType(kind, notify); + m_watchpoint_list.Add(wp_sp, true); + } + + error = m_process_sp->EnableWatchpoint(wp_sp, notify); + LLDB_LOGF(log, "Target::%s (creation of watchpoint %s with id = %u)\n", + __FUNCTION__, error.Success() ? "succeeded" : "failed", + wp_sp->GetID()); + + if (error.Fail()) { + // Enabling the watchpoint on the device side failed. Remove the said + // watchpoint from the list maintained by the target instance. + m_watchpoint_list.Remove(wp_sp->GetID(), true); + wp_sp.reset(); + } else + m_last_created_watchpoint = wp_sp; + return wp_sp; +} + +void Target::RemoveAllowedBreakpoints() { + Log *log = GetLog(LLDBLog::Breakpoints); + LLDB_LOGF(log, "Target::%s \n", __FUNCTION__); + + m_breakpoint_list.RemoveAllowed(true); + + m_last_created_breakpoint.reset(); +} + +void Target::RemoveAllBreakpoints(bool internal_also) { + Log *log = GetLog(LLDBLog::Breakpoints); + LLDB_LOGF(log, "Target::%s (internal_also = %s)\n", __FUNCTION__, + internal_also ? "yes" : "no"); + + m_breakpoint_list.RemoveAll(true); + if (internal_also) + m_internal_breakpoint_list.RemoveAll(false); + + m_last_created_breakpoint.reset(); +} + +void Target::DisableAllBreakpoints(bool internal_also) { + Log *log = GetLog(LLDBLog::Breakpoints); + LLDB_LOGF(log, "Target::%s (internal_also = %s)\n", __FUNCTION__, + internal_also ? "yes" : "no"); + + m_breakpoint_list.SetEnabledAll(false); + if (internal_also) + m_internal_breakpoint_list.SetEnabledAll(false); +} + +void Target::DisableAllowedBreakpoints() { + Log *log = GetLog(LLDBLog::Breakpoints); + LLDB_LOGF(log, "Target::%s", __FUNCTION__); + + m_breakpoint_list.SetEnabledAllowed(false); +} + +void Target::EnableAllBreakpoints(bool internal_also) { + Log *log = GetLog(LLDBLog::Breakpoints); + LLDB_LOGF(log, "Target::%s (internal_also = %s)\n", __FUNCTION__, + internal_also ? "yes" : "no"); + + m_breakpoint_list.SetEnabledAll(true); + if (internal_also) + m_internal_breakpoint_list.SetEnabledAll(true); +} + +void Target::EnableAllowedBreakpoints() { + Log *log = GetLog(LLDBLog::Breakpoints); + LLDB_LOGF(log, "Target::%s", __FUNCTION__); + + m_breakpoint_list.SetEnabledAllowed(true); +} + +bool Target::RemoveBreakpointByID(break_id_t break_id) { + Log *log = GetLog(LLDBLog::Breakpoints); + LLDB_LOGF(log, "Target::%s (break_id = %i, internal = %s)\n", __FUNCTION__, + break_id, LLDB_BREAK_ID_IS_INTERNAL(break_id) ? "yes" : "no"); + + if (DisableBreakpointByID(break_id)) { + if (LLDB_BREAK_ID_IS_INTERNAL(break_id)) + m_internal_breakpoint_list.Remove(break_id, false); + else { + if (m_last_created_breakpoint) { + if (m_last_created_breakpoint->GetID() == break_id) + m_last_created_breakpoint.reset(); + } + m_breakpoint_list.Remove(break_id, true); + } + return true; + } + return false; +} + +bool Target::DisableBreakpointByID(break_id_t break_id) { + Log *log = GetLog(LLDBLog::Breakpoints); + LLDB_LOGF(log, "Target::%s (break_id = %i, internal = %s)\n", __FUNCTION__, + break_id, LLDB_BREAK_ID_IS_INTERNAL(break_id) ? "yes" : "no"); + + BreakpointSP bp_sp; + + if (LLDB_BREAK_ID_IS_INTERNAL(break_id)) + bp_sp = m_internal_breakpoint_list.FindBreakpointByID(break_id); + else + bp_sp = m_breakpoint_list.FindBreakpointByID(break_id); + if (bp_sp) { + bp_sp->SetEnabled(false); + return true; + } + return false; +} + +bool Target::EnableBreakpointByID(break_id_t break_id) { + Log *log = GetLog(LLDBLog::Breakpoints); + LLDB_LOGF(log, "Target::%s (break_id = %i, internal = %s)\n", __FUNCTION__, + break_id, LLDB_BREAK_ID_IS_INTERNAL(break_id) ? "yes" : "no"); + + BreakpointSP bp_sp; + + if (LLDB_BREAK_ID_IS_INTERNAL(break_id)) + bp_sp = m_internal_breakpoint_list.FindBreakpointByID(break_id); + else + bp_sp = m_breakpoint_list.FindBreakpointByID(break_id); + + if (bp_sp) { + bp_sp->SetEnabled(true); + return true; + } + return false; +} + +void Target::ResetBreakpointHitCounts() { + GetBreakpointList().ResetHitCounts(); +} + +Status Target::SerializeBreakpointsToFile(const FileSpec &file, + const BreakpointIDList &bp_ids, + bool append) { + Status error; + + if (!file) { + error.SetErrorString("Invalid FileSpec."); + return error; + } + + std::string path(file.GetPath()); + StructuredData::ObjectSP input_data_sp; + + StructuredData::ArraySP break_store_sp; + StructuredData::Array *break_store_ptr = nullptr; + + if (append) { + input_data_sp = StructuredData::ParseJSONFromFile(file, error); + if (error.Success()) { + break_store_ptr = input_data_sp->GetAsArray(); + if (!break_store_ptr) { + error.SetErrorStringWithFormat( + "Tried to append to invalid input file %s", path.c_str()); + return error; + } + } + } + + if (!break_store_ptr) { + break_store_sp = std::make_shared<StructuredData::Array>(); + break_store_ptr = break_store_sp.get(); + } + + StreamFile out_file(path.c_str(), + File::eOpenOptionTruncate | File::eOpenOptionWriteOnly | + File::eOpenOptionCanCreate | + File::eOpenOptionCloseOnExec, + lldb::eFilePermissionsFileDefault); + if (!out_file.GetFile().IsValid()) { + error.SetErrorStringWithFormat("Unable to open output file: %s.", + path.c_str()); + return error; + } + + std::unique_lock<std::recursive_mutex> lock; + GetBreakpointList().GetListMutex(lock); + + if (bp_ids.GetSize() == 0) { + const BreakpointList &breakpoints = GetBreakpointList(); + + size_t num_breakpoints = breakpoints.GetSize(); + for (size_t i = 0; i < num_breakpoints; i++) { + Breakpoint *bp = breakpoints.GetBreakpointAtIndex(i).get(); + StructuredData::ObjectSP bkpt_save_sp = bp->SerializeToStructuredData(); + // If a breakpoint can't serialize it, just ignore it for now: + if (bkpt_save_sp) + break_store_ptr->AddItem(bkpt_save_sp); + } + } else { + + std::unordered_set<lldb::break_id_t> processed_bkpts; + const size_t count = bp_ids.GetSize(); + for (size_t i = 0; i < count; ++i) { + BreakpointID cur_bp_id = bp_ids.GetBreakpointIDAtIndex(i); + lldb::break_id_t bp_id = cur_bp_id.GetBreakpointID(); + + if (bp_id != LLDB_INVALID_BREAK_ID) { + // Only do each breakpoint once: + std::pair<std::unordered_set<lldb::break_id_t>::iterator, bool> + insert_result = processed_bkpts.insert(bp_id); + if (!insert_result.second) + continue; + + Breakpoint *bp = GetBreakpointByID(bp_id).get(); + StructuredData::ObjectSP bkpt_save_sp = bp->SerializeToStructuredData(); + // If the user explicitly asked to serialize a breakpoint, and we + // can't, then raise an error: + if (!bkpt_save_sp) { + error.SetErrorStringWithFormat("Unable to serialize breakpoint %d", + bp_id); + return error; + } + break_store_ptr->AddItem(bkpt_save_sp); + } + } + } + + break_store_ptr->Dump(out_file, false); + out_file.PutChar('\n'); + return error; +} + +Status Target::CreateBreakpointsFromFile(const FileSpec &file, + BreakpointIDList &new_bps) { + std::vector<std::string> no_names; + return CreateBreakpointsFromFile(file, no_names, new_bps); +} + +Status Target::CreateBreakpointsFromFile(const FileSpec &file, + std::vector<std::string> &names, + BreakpointIDList &new_bps) { + std::unique_lock<std::recursive_mutex> lock; + GetBreakpointList().GetListMutex(lock); + + Status error; + StructuredData::ObjectSP input_data_sp = + StructuredData::ParseJSONFromFile(file, error); + if (!error.Success()) { + return error; + } else if (!input_data_sp || !input_data_sp->IsValid()) { + error.SetErrorStringWithFormat("Invalid JSON from input file: %s.", + file.GetPath().c_str()); + return error; + } + + StructuredData::Array *bkpt_array = input_data_sp->GetAsArray(); + if (!bkpt_array) { + error.SetErrorStringWithFormat( + "Invalid breakpoint data from input file: %s.", file.GetPath().c_str()); + return error; + } + + size_t num_bkpts = bkpt_array->GetSize(); + size_t num_names = names.size(); + + for (size_t i = 0; i < num_bkpts; i++) { + StructuredData::ObjectSP bkpt_object_sp = bkpt_array->GetItemAtIndex(i); + // Peel off the breakpoint key, and feed the rest to the Breakpoint: + StructuredData::Dictionary *bkpt_dict = bkpt_object_sp->GetAsDictionary(); + if (!bkpt_dict) { + error.SetErrorStringWithFormat( + "Invalid breakpoint data for element %zu from input file: %s.", i, + file.GetPath().c_str()); + return error; + } + StructuredData::ObjectSP bkpt_data_sp = + bkpt_dict->GetValueForKey(Breakpoint::GetSerializationKey()); + if (num_names && + !Breakpoint::SerializedBreakpointMatchesNames(bkpt_data_sp, names)) + continue; + + BreakpointSP bkpt_sp = Breakpoint::CreateFromStructuredData( + shared_from_this(), bkpt_data_sp, error); + if (!error.Success()) { + error.SetErrorStringWithFormat( + "Error restoring breakpoint %zu from %s: %s.", i, + file.GetPath().c_str(), error.AsCString()); + return error; + } + new_bps.AddBreakpointID(BreakpointID(bkpt_sp->GetID())); + } + return error; +} + +// The flag 'end_to_end', default to true, signifies that the operation is +// performed end to end, for both the debugger and the debuggee. + +// Assumption: Caller holds the list mutex lock for m_watchpoint_list for end +// to end operations. +bool Target::RemoveAllWatchpoints(bool end_to_end) { + Log *log = GetLog(LLDBLog::Watchpoints); + LLDB_LOGF(log, "Target::%s\n", __FUNCTION__); + + if (!end_to_end) { + m_watchpoint_list.RemoveAll(true); + return true; + } + + // Otherwise, it's an end to end operation. + + if (!ProcessIsValid()) + return false; + + for (WatchpointSP wp_sp : m_watchpoint_list.Watchpoints()) { + if (!wp_sp) + return false; + + Status rc = m_process_sp->DisableWatchpoint(wp_sp); + if (rc.Fail()) + return false; + } + m_watchpoint_list.RemoveAll(true); + m_last_created_watchpoint.reset(); + return true; // Success! +} + +// Assumption: Caller holds the list mutex lock for m_watchpoint_list for end +// to end operations. +bool Target::DisableAllWatchpoints(bool end_to_end) { + Log *log = GetLog(LLDBLog::Watchpoints); + LLDB_LOGF(log, "Target::%s\n", __FUNCTION__); + + if (!end_to_end) { + m_watchpoint_list.SetEnabledAll(false); + return true; + } + + // Otherwise, it's an end to end operation. + + if (!ProcessIsValid()) + return false; + + for (WatchpointSP wp_sp : m_watchpoint_list.Watchpoints()) { + if (!wp_sp) + return false; + + Status rc = m_process_sp->DisableWatchpoint(wp_sp); + if (rc.Fail()) + return false; + } + return true; // Success! +} + +// Assumption: Caller holds the list mutex lock for m_watchpoint_list for end +// to end operations. +bool Target::EnableAllWatchpoints(bool end_to_end) { + Log *log = GetLog(LLDBLog::Watchpoints); + LLDB_LOGF(log, "Target::%s\n", __FUNCTION__); + + if (!end_to_end) { + m_watchpoint_list.SetEnabledAll(true); + return true; + } + + // Otherwise, it's an end to end operation. + + if (!ProcessIsValid()) + return false; + + for (WatchpointSP wp_sp : m_watchpoint_list.Watchpoints()) { + if (!wp_sp) + return false; + + Status rc = m_process_sp->EnableWatchpoint(wp_sp); + if (rc.Fail()) + return false; + } + return true; // Success! +} + +// Assumption: Caller holds the list mutex lock for m_watchpoint_list. +bool Target::ClearAllWatchpointHitCounts() { + Log *log = GetLog(LLDBLog::Watchpoints); + LLDB_LOGF(log, "Target::%s\n", __FUNCTION__); + + for (WatchpointSP wp_sp : m_watchpoint_list.Watchpoints()) { + if (!wp_sp) + return false; + + wp_sp->ResetHitCount(); + } + return true; // Success! +} + +// Assumption: Caller holds the list mutex lock for m_watchpoint_list. +bool Target::ClearAllWatchpointHistoricValues() { + Log *log = GetLog(LLDBLog::Watchpoints); + LLDB_LOGF(log, "Target::%s\n", __FUNCTION__); + + for (WatchpointSP wp_sp : m_watchpoint_list.Watchpoints()) { + if (!wp_sp) + return false; + + wp_sp->ResetHistoricValues(); + } + return true; // Success! +} + +// Assumption: Caller holds the list mutex lock for m_watchpoint_list during +// these operations. +bool Target::IgnoreAllWatchpoints(uint32_t ignore_count) { + Log *log = GetLog(LLDBLog::Watchpoints); + LLDB_LOGF(log, "Target::%s\n", __FUNCTION__); + + if (!ProcessIsValid()) + return false; + + for (WatchpointSP wp_sp : m_watchpoint_list.Watchpoints()) { + if (!wp_sp) + return false; + + wp_sp->SetIgnoreCount(ignore_count); + } + return true; // Success! +} + +// Assumption: Caller holds the list mutex lock for m_watchpoint_list. +bool Target::DisableWatchpointByID(lldb::watch_id_t watch_id) { + Log *log = GetLog(LLDBLog::Watchpoints); + LLDB_LOGF(log, "Target::%s (watch_id = %i)\n", __FUNCTION__, watch_id); + + if (!ProcessIsValid()) + return false; + + WatchpointSP wp_sp = m_watchpoint_list.FindByID(watch_id); + if (wp_sp) { + Status rc = m_process_sp->DisableWatchpoint(wp_sp); + if (rc.Success()) + return true; + + // Else, fallthrough. + } + return false; +} + +// Assumption: Caller holds the list mutex lock for m_watchpoint_list. +bool Target::EnableWatchpointByID(lldb::watch_id_t watch_id) { + Log *log = GetLog(LLDBLog::Watchpoints); + LLDB_LOGF(log, "Target::%s (watch_id = %i)\n", __FUNCTION__, watch_id); + + if (!ProcessIsValid()) + return false; + + WatchpointSP wp_sp = m_watchpoint_list.FindByID(watch_id); + if (wp_sp) { + Status rc = m_process_sp->EnableWatchpoint(wp_sp); + if (rc.Success()) + return true; + + // Else, fallthrough. + } + return false; +} + +// Assumption: Caller holds the list mutex lock for m_watchpoint_list. +bool Target::RemoveWatchpointByID(lldb::watch_id_t watch_id) { + Log *log = GetLog(LLDBLog::Watchpoints); + LLDB_LOGF(log, "Target::%s (watch_id = %i)\n", __FUNCTION__, watch_id); + + WatchpointSP watch_to_remove_sp = m_watchpoint_list.FindByID(watch_id); + if (watch_to_remove_sp == m_last_created_watchpoint) + m_last_created_watchpoint.reset(); + + if (DisableWatchpointByID(watch_id)) { + m_watchpoint_list.Remove(watch_id, true); + return true; + } + return false; +} + +// Assumption: Caller holds the list mutex lock for m_watchpoint_list. +bool Target::IgnoreWatchpointByID(lldb::watch_id_t watch_id, + uint32_t ignore_count) { + Log *log = GetLog(LLDBLog::Watchpoints); + LLDB_LOGF(log, "Target::%s (watch_id = %i)\n", __FUNCTION__, watch_id); + + if (!ProcessIsValid()) + return false; + + WatchpointSP wp_sp = m_watchpoint_list.FindByID(watch_id); + if (wp_sp) { + wp_sp->SetIgnoreCount(ignore_count); + return true; + } + return false; +} + +ModuleSP Target::GetExecutableModule() { + // search for the first executable in the module list + for (size_t i = 0; i < m_images.GetSize(); ++i) { + ModuleSP module_sp = m_images.GetModuleAtIndex(i); + lldb_private::ObjectFile *obj = module_sp->GetObjectFile(); + if (obj == nullptr) + continue; + if (obj->GetType() == ObjectFile::Type::eTypeExecutable) + return module_sp; + } + // as fall back return the first module loaded + return m_images.GetModuleAtIndex(0); +} + +Module *Target::GetExecutableModulePointer() { + return GetExecutableModule().get(); +} + +static void LoadScriptingResourceForModule(const ModuleSP &module_sp, + Target *target) { + Status error; + StreamString feedback_stream; + if (module_sp && !module_sp->LoadScriptingResourceInTarget(target, error, + feedback_stream)) { + if (error.AsCString()) + target->GetDebugger().GetErrorStream().Printf( + "unable to load scripting data for module %s - error reported was " + "%s\n", + module_sp->GetFileSpec().GetFileNameStrippingExtension().GetCString(), + error.AsCString()); + } + if (feedback_stream.GetSize()) + target->GetDebugger().GetErrorStream().Printf("%s\n", + feedback_stream.GetData()); +} + +void Target::ClearModules(bool delete_locations) { + ModulesDidUnload(m_images, delete_locations); + m_section_load_history.Clear(); + m_images.Clear(); + m_scratch_type_system_map.Clear(); +} + +void Target::DidExec() { + // When a process exec's we need to know about it so we can do some cleanup. + m_breakpoint_list.RemoveInvalidLocations(m_arch.GetSpec()); + m_internal_breakpoint_list.RemoveInvalidLocations(m_arch.GetSpec()); +} + +void Target::SetExecutableModule(ModuleSP &executable_sp, + LoadDependentFiles load_dependent_files) { + Log *log = GetLog(LLDBLog::Target); + ClearModules(false); + + if (executable_sp) { + ElapsedTime elapsed(m_stats.GetCreateTime()); + LLDB_SCOPED_TIMERF("Target::SetExecutableModule (executable = '%s')", + executable_sp->GetFileSpec().GetPath().c_str()); + + const bool notify = true; + m_images.Append(executable_sp, + notify); // The first image is our executable file + + // If we haven't set an architecture yet, reset our architecture based on + // what we found in the executable module. + if (!m_arch.GetSpec().IsValid()) { + m_arch = executable_sp->GetArchitecture(); + LLDB_LOG(log, + "Target::SetExecutableModule setting architecture to {0} ({1}) " + "based on executable file", + m_arch.GetSpec().GetArchitectureName(), + m_arch.GetSpec().GetTriple().getTriple()); + } + + FileSpecList dependent_files; + ObjectFile *executable_objfile = executable_sp->GetObjectFile(); + bool load_dependents = true; + switch (load_dependent_files) { + case eLoadDependentsDefault: + load_dependents = executable_sp->IsExecutable(); + break; + case eLoadDependentsYes: + load_dependents = true; + break; + case eLoadDependentsNo: + load_dependents = false; + break; + } + + if (executable_objfile && load_dependents) { + ModuleList added_modules; + executable_objfile->GetDependentModules(dependent_files); + for (uint32_t i = 0; i < dependent_files.GetSize(); i++) { + FileSpec dependent_file_spec(dependent_files.GetFileSpecAtIndex(i)); + FileSpec platform_dependent_file_spec; + if (m_platform_sp) + m_platform_sp->GetFileWithUUID(dependent_file_spec, nullptr, + platform_dependent_file_spec); + else + platform_dependent_file_spec = dependent_file_spec; + + ModuleSpec module_spec(platform_dependent_file_spec, m_arch.GetSpec()); + ModuleSP image_module_sp( + GetOrCreateModule(module_spec, false /* notify */)); + if (image_module_sp) { + added_modules.AppendIfNeeded(image_module_sp, false); + ObjectFile *objfile = image_module_sp->GetObjectFile(); + if (objfile) + objfile->GetDependentModules(dependent_files); + } + } + ModulesDidLoad(added_modules); + } + } +} + +bool Target::SetArchitecture(const ArchSpec &arch_spec, bool set_platform, + bool merge) { + Log *log = GetLog(LLDBLog::Target); + bool missing_local_arch = !m_arch.GetSpec().IsValid(); + bool replace_local_arch = true; + bool compatible_local_arch = false; + ArchSpec other(arch_spec); + + // Changing the architecture might mean that the currently selected platform + // isn't compatible. Set the platform correctly if we are asked to do so, + // otherwise assume the user will set the platform manually. + if (set_platform) { + if (other.IsValid()) { + auto platform_sp = GetPlatform(); + if (!platform_sp || !platform_sp->IsCompatibleArchitecture( + other, {}, ArchSpec::CompatibleMatch, nullptr)) { + ArchSpec platform_arch; + if (PlatformSP arch_platform_sp = + GetDebugger().GetPlatformList().GetOrCreate(other, {}, + &platform_arch)) { + SetPlatform(arch_platform_sp); + if (platform_arch.IsValid()) + other = platform_arch; + } + } + } + } + + if (!missing_local_arch) { + if (merge && m_arch.GetSpec().IsCompatibleMatch(arch_spec)) { + other.MergeFrom(m_arch.GetSpec()); + + if (m_arch.GetSpec().IsCompatibleMatch(other)) { + compatible_local_arch = true; + + if (m_arch.GetSpec().GetTriple() == other.GetTriple()) + replace_local_arch = false; + } + } + } + + if (compatible_local_arch || missing_local_arch) { + // If we haven't got a valid arch spec, or the architectures are compatible + // update the architecture, unless the one we already have is more + // specified + if (replace_local_arch) + m_arch = other; + LLDB_LOG(log, + "Target::SetArchitecture merging compatible arch; arch " + "is now {0} ({1})", + m_arch.GetSpec().GetArchitectureName(), + m_arch.GetSpec().GetTriple().getTriple()); + return true; + } + + // If we have an executable file, try to reset the executable to the desired + // architecture + LLDB_LOGF( + log, + "Target::SetArchitecture changing architecture to %s (%s) from %s (%s)", + arch_spec.GetArchitectureName(), + arch_spec.GetTriple().getTriple().c_str(), + m_arch.GetSpec().GetArchitectureName(), + m_arch.GetSpec().GetTriple().getTriple().c_str()); + m_arch = other; + ModuleSP executable_sp = GetExecutableModule(); + + ClearModules(true); + // Need to do something about unsetting breakpoints. + + if (executable_sp) { + LLDB_LOGF(log, + "Target::SetArchitecture Trying to select executable file " + "architecture %s (%s)", + arch_spec.GetArchitectureName(), + arch_spec.GetTriple().getTriple().c_str()); + ModuleSpec module_spec(executable_sp->GetFileSpec(), other); + FileSpecList search_paths = GetExecutableSearchPaths(); + Status error = ModuleList::GetSharedModule(module_spec, executable_sp, + &search_paths, nullptr, nullptr); + + if (!error.Fail() && executable_sp) { + SetExecutableModule(executable_sp, eLoadDependentsYes); + return true; + } + } + return false; +} + +bool Target::MergeArchitecture(const ArchSpec &arch_spec) { + Log *log = GetLog(LLDBLog::Target); + if (arch_spec.IsValid()) { + if (m_arch.GetSpec().IsCompatibleMatch(arch_spec)) { + // The current target arch is compatible with "arch_spec", see if we can + // improve our current architecture using bits from "arch_spec" + + LLDB_LOGF(log, + "Target::MergeArchitecture target has arch %s, merging with " + "arch %s", + m_arch.GetSpec().GetTriple().getTriple().c_str(), + arch_spec.GetTriple().getTriple().c_str()); + + // Merge bits from arch_spec into "merged_arch" and set our architecture + ArchSpec merged_arch(m_arch.GetSpec()); + merged_arch.MergeFrom(arch_spec); + return SetArchitecture(merged_arch); + } else { + // The new architecture is different, we just need to replace it + return SetArchitecture(arch_spec); + } + } + return false; +} + +void Target::NotifyWillClearList(const ModuleList &module_list) {} + +void Target::NotifyModuleAdded(const ModuleList &module_list, + const ModuleSP &module_sp) { + // A module is being added to this target for the first time + if (m_valid) { + ModuleList my_module_list; + my_module_list.Append(module_sp); + ModulesDidLoad(my_module_list); + } +} + +void Target::NotifyModuleRemoved(const ModuleList &module_list, + const ModuleSP &module_sp) { + // A module is being removed from this target. + if (m_valid) { + ModuleList my_module_list; + my_module_list.Append(module_sp); + ModulesDidUnload(my_module_list, false); + } +} + +void Target::NotifyModuleUpdated(const ModuleList &module_list, + const ModuleSP &old_module_sp, + const ModuleSP &new_module_sp) { + // A module is replacing an already added module + if (m_valid) { + m_breakpoint_list.UpdateBreakpointsWhenModuleIsReplaced(old_module_sp, + new_module_sp); + m_internal_breakpoint_list.UpdateBreakpointsWhenModuleIsReplaced( + old_module_sp, new_module_sp); + } +} + +void Target::NotifyModulesRemoved(lldb_private::ModuleList &module_list) { + ModulesDidUnload(module_list, false); +} + +void Target::ModulesDidLoad(ModuleList &module_list) { + const size_t num_images = module_list.GetSize(); + if (m_valid && num_images) { + for (size_t idx = 0; idx < num_images; ++idx) { + ModuleSP module_sp(module_list.GetModuleAtIndex(idx)); + LoadScriptingResourceForModule(module_sp, this); + } + m_breakpoint_list.UpdateBreakpoints(module_list, true, false); + m_internal_breakpoint_list.UpdateBreakpoints(module_list, true, false); + if (m_process_sp) { + m_process_sp->ModulesDidLoad(module_list); + } + auto data_sp = + std::make_shared<TargetEventData>(shared_from_this(), module_list); + BroadcastEvent(eBroadcastBitModulesLoaded, data_sp); + } +} + +void Target::SymbolsDidLoad(ModuleList &module_list) { + if (m_valid && module_list.GetSize()) { + if (m_process_sp) { + for (LanguageRuntime *runtime : m_process_sp->GetLanguageRuntimes()) { + runtime->SymbolsDidLoad(module_list); + } + } + + m_breakpoint_list.UpdateBreakpoints(module_list, true, false); + m_internal_breakpoint_list.UpdateBreakpoints(module_list, true, false); + auto data_sp = + std::make_shared<TargetEventData>(shared_from_this(), module_list); + BroadcastEvent(eBroadcastBitSymbolsLoaded, data_sp); + } +} + +void Target::ModulesDidUnload(ModuleList &module_list, bool delete_locations) { + if (m_valid && module_list.GetSize()) { + UnloadModuleSections(module_list); + auto data_sp = + std::make_shared<TargetEventData>(shared_from_this(), module_list); + BroadcastEvent(eBroadcastBitModulesUnloaded, data_sp); + m_breakpoint_list.UpdateBreakpoints(module_list, false, delete_locations); + m_internal_breakpoint_list.UpdateBreakpoints(module_list, false, + delete_locations); + + // If a module was torn down it will have torn down the 'TypeSystemClang's + // that we used as source 'ASTContext's for the persistent variables in + // the current target. Those would now be unsafe to access because the + // 'DeclOrigin' are now possibly stale. Thus clear all persistent + // variables. We only want to flush 'TypeSystem's if the module being + // unloaded was capable of describing a source type. JITted module unloads + // happen frequently for Objective-C utility functions or the REPL and rely + // on the persistent variables to stick around. + const bool should_flush_type_systems = + module_list.AnyOf([](lldb_private::Module &module) { + auto *object_file = module.GetObjectFile(); + + if (!object_file) + return false; + + auto type = object_file->GetType(); + + // eTypeExecutable: when debugged binary was rebuilt + // eTypeSharedLibrary: if dylib was re-loaded + return module.FileHasChanged() && + (type == ObjectFile::eTypeObjectFile || + type == ObjectFile::eTypeExecutable || + type == ObjectFile::eTypeSharedLibrary); + }); + + if (should_flush_type_systems) + m_scratch_type_system_map.Clear(); + } +} + +bool Target::ModuleIsExcludedForUnconstrainedSearches( + const FileSpec &module_file_spec) { + if (GetBreakpointsConsultPlatformAvoidList()) { + ModuleList matchingModules; + ModuleSpec module_spec(module_file_spec); + GetImages().FindModules(module_spec, matchingModules); + size_t num_modules = matchingModules.GetSize(); + + // If there is more than one module for this file spec, only + // return true if ALL the modules are on the black list. + if (num_modules > 0) { + for (size_t i = 0; i < num_modules; i++) { + if (!ModuleIsExcludedForUnconstrainedSearches( + matchingModules.GetModuleAtIndex(i))) + return false; + } + return true; + } + } + return false; +} + +bool Target::ModuleIsExcludedForUnconstrainedSearches( + const lldb::ModuleSP &module_sp) { + if (GetBreakpointsConsultPlatformAvoidList()) { + if (m_platform_sp) + return m_platform_sp->ModuleIsExcludedForUnconstrainedSearches(*this, + module_sp); + } + return false; +} + +size_t Target::ReadMemoryFromFileCache(const Address &addr, void *dst, + size_t dst_len, Status &error) { + SectionSP section_sp(addr.GetSection()); + if (section_sp) { + // If the contents of this section are encrypted, the on-disk file is + // unusable. Read only from live memory. + if (section_sp->IsEncrypted()) { + error.SetErrorString("section is encrypted"); + return 0; + } + ModuleSP module_sp(section_sp->GetModule()); + if (module_sp) { + ObjectFile *objfile = section_sp->GetModule()->GetObjectFile(); + if (objfile) { + size_t bytes_read = objfile->ReadSectionData( + section_sp.get(), addr.GetOffset(), dst, dst_len); + if (bytes_read > 0) + return bytes_read; + else + error.SetErrorStringWithFormat("error reading data from section %s", + section_sp->GetName().GetCString()); + } else + error.SetErrorString("address isn't from a object file"); + } else + error.SetErrorString("address isn't in a module"); + } else + error.SetErrorString("address doesn't contain a section that points to a " + "section in a object file"); + + return 0; +} + +size_t Target::ReadMemory(const Address &addr, void *dst, size_t dst_len, + Status &error, bool force_live_memory, + lldb::addr_t *load_addr_ptr) { + error.Clear(); + + Address fixed_addr = addr; + if (ProcessIsValid()) + if (const ABISP &abi = m_process_sp->GetABI()) + fixed_addr.SetLoadAddress(abi->FixAnyAddress(addr.GetLoadAddress(this)), + this); + + // if we end up reading this from process memory, we will fill this with the + // actual load address + if (load_addr_ptr) + *load_addr_ptr = LLDB_INVALID_ADDRESS; + + size_t bytes_read = 0; + + addr_t load_addr = LLDB_INVALID_ADDRESS; + addr_t file_addr = LLDB_INVALID_ADDRESS; + Address resolved_addr; + if (!fixed_addr.IsSectionOffset()) { + SectionLoadList §ion_load_list = GetSectionLoadList(); + if (section_load_list.IsEmpty()) { + // No sections are loaded, so we must assume we are not running yet and + // anything we are given is a file address. + file_addr = + fixed_addr.GetOffset(); // "fixed_addr" doesn't have a section, so + // its offset is the file address + m_images.ResolveFileAddress(file_addr, resolved_addr); + } else { + // We have at least one section loaded. This can be because we have + // manually loaded some sections with "target modules load ..." or + // because we have a live process that has sections loaded through + // the dynamic loader + load_addr = + fixed_addr.GetOffset(); // "fixed_addr" doesn't have a section, so + // its offset is the load address + section_load_list.ResolveLoadAddress(load_addr, resolved_addr); + } + } + if (!resolved_addr.IsValid()) + resolved_addr = fixed_addr; + + // If we read from the file cache but can't get as many bytes as requested, + // we keep the result around in this buffer, in case this result is the + // best we can do. + std::unique_ptr<uint8_t[]> file_cache_read_buffer; + size_t file_cache_bytes_read = 0; + + // Read from file cache if read-only section. + if (!force_live_memory && resolved_addr.IsSectionOffset()) { + SectionSP section_sp(resolved_addr.GetSection()); + if (section_sp) { + auto permissions = Flags(section_sp->GetPermissions()); + bool is_readonly = !permissions.Test(ePermissionsWritable) && + permissions.Test(ePermissionsReadable); + if (is_readonly) { + file_cache_bytes_read = + ReadMemoryFromFileCache(resolved_addr, dst, dst_len, error); + if (file_cache_bytes_read == dst_len) + return file_cache_bytes_read; + else if (file_cache_bytes_read > 0) { + file_cache_read_buffer = + std::make_unique<uint8_t[]>(file_cache_bytes_read); + std::memcpy(file_cache_read_buffer.get(), dst, file_cache_bytes_read); + } + } + } + } + + if (ProcessIsValid()) { + if (load_addr == LLDB_INVALID_ADDRESS) + load_addr = resolved_addr.GetLoadAddress(this); + + if (load_addr == LLDB_INVALID_ADDRESS) { + ModuleSP addr_module_sp(resolved_addr.GetModule()); + if (addr_module_sp && addr_module_sp->GetFileSpec()) + error.SetErrorStringWithFormatv( + "{0:F}[{1:x+}] can't be resolved, {0:F} is not currently loaded", + addr_module_sp->GetFileSpec(), resolved_addr.GetFileAddress()); + else + error.SetErrorStringWithFormat("0x%" PRIx64 " can't be resolved", + resolved_addr.GetFileAddress()); + } else { + bytes_read = m_process_sp->ReadMemory(load_addr, dst, dst_len, error); + if (bytes_read != dst_len) { + if (error.Success()) { + if (bytes_read == 0) + error.SetErrorStringWithFormat( + "read memory from 0x%" PRIx64 " failed", load_addr); + else + error.SetErrorStringWithFormat( + "only %" PRIu64 " of %" PRIu64 + " bytes were read from memory at 0x%" PRIx64, + (uint64_t)bytes_read, (uint64_t)dst_len, load_addr); + } + } + if (bytes_read) { + if (load_addr_ptr) + *load_addr_ptr = load_addr; + return bytes_read; + } + } + } + + if (file_cache_read_buffer && file_cache_bytes_read > 0) { + // Reading from the process failed. If we've previously succeeded in reading + // something from the file cache, then copy that over and return that. + std::memcpy(dst, file_cache_read_buffer.get(), file_cache_bytes_read); + return file_cache_bytes_read; + } + + if (!file_cache_read_buffer && resolved_addr.IsSectionOffset()) { + // If we didn't already try and read from the object file cache, then try + // it after failing to read from the process. + return ReadMemoryFromFileCache(resolved_addr, dst, dst_len, error); + } + return 0; +} + +size_t Target::ReadCStringFromMemory(const Address &addr, std::string &out_str, + Status &error, bool force_live_memory) { + char buf[256]; + out_str.clear(); + addr_t curr_addr = addr.GetLoadAddress(this); + Address address(addr); + while (true) { + size_t length = ReadCStringFromMemory(address, buf, sizeof(buf), error, + force_live_memory); + if (length == 0) + break; + out_str.append(buf, length); + // If we got "length - 1" bytes, we didn't get the whole C string, we need + // to read some more characters + if (length == sizeof(buf) - 1) + curr_addr += length; + else + break; + address = Address(curr_addr); + } + return out_str.size(); +} + +size_t Target::ReadCStringFromMemory(const Address &addr, char *dst, + size_t dst_max_len, Status &result_error, + bool force_live_memory) { + size_t total_cstr_len = 0; + if (dst && dst_max_len) { + result_error.Clear(); + // NULL out everything just to be safe + memset(dst, 0, dst_max_len); + Status error; + addr_t curr_addr = addr.GetLoadAddress(this); + Address address(addr); + + // We could call m_process_sp->GetMemoryCacheLineSize() but I don't think + // this really needs to be tied to the memory cache subsystem's cache line + // size, so leave this as a fixed constant. + const size_t cache_line_size = 512; + + size_t bytes_left = dst_max_len - 1; + char *curr_dst = dst; + + while (bytes_left > 0) { + addr_t cache_line_bytes_left = + cache_line_size - (curr_addr % cache_line_size); + addr_t bytes_to_read = + std::min<addr_t>(bytes_left, cache_line_bytes_left); + size_t bytes_read = ReadMemory(address, curr_dst, bytes_to_read, error, + force_live_memory); + + if (bytes_read == 0) { + result_error = error; + dst[total_cstr_len] = '\0'; + break; + } + const size_t len = strlen(curr_dst); + + total_cstr_len += len; + + if (len < bytes_to_read) + break; + + curr_dst += bytes_read; + curr_addr += bytes_read; + bytes_left -= bytes_read; + address = Address(curr_addr); + } + } else { + if (dst == nullptr) + result_error.SetErrorString("invalid arguments"); + else + result_error.Clear(); + } + return total_cstr_len; +} + +addr_t Target::GetReasonableReadSize(const Address &addr) { + addr_t load_addr = addr.GetLoadAddress(this); + if (load_addr != LLDB_INVALID_ADDRESS && m_process_sp) { + // Avoid crossing cache line boundaries. + addr_t cache_line_size = m_process_sp->GetMemoryCacheLineSize(); + return cache_line_size - (load_addr % cache_line_size); + } + + // The read is going to go to the file cache, so we can just pick a largish + // value. + return 0x1000; +} + +size_t Target::ReadStringFromMemory(const Address &addr, char *dst, + size_t max_bytes, Status &error, + size_t type_width, bool force_live_memory) { + if (!dst || !max_bytes || !type_width || max_bytes < type_width) + return 0; + + size_t total_bytes_read = 0; + + // Ensure a null terminator independent of the number of bytes that is + // read. + memset(dst, 0, max_bytes); + size_t bytes_left = max_bytes - type_width; + + const char terminator[4] = {'\0', '\0', '\0', '\0'}; + assert(sizeof(terminator) >= type_width && "Attempting to validate a " + "string with more than 4 bytes " + "per character!"); + + Address address = addr; + char *curr_dst = dst; + + error.Clear(); + while (bytes_left > 0 && error.Success()) { + addr_t bytes_to_read = + std::min<addr_t>(bytes_left, GetReasonableReadSize(address)); + size_t bytes_read = + ReadMemory(address, curr_dst, bytes_to_read, error, force_live_memory); + + if (bytes_read == 0) + break; + + // Search for a null terminator of correct size and alignment in + // bytes_read + size_t aligned_start = total_bytes_read - total_bytes_read % type_width; + for (size_t i = aligned_start; + i + type_width <= total_bytes_read + bytes_read; i += type_width) + if (::memcmp(&dst[i], terminator, type_width) == 0) { + error.Clear(); + return i; + } + + total_bytes_read += bytes_read; + curr_dst += bytes_read; + address.Slide(bytes_read); + bytes_left -= bytes_read; + } + return total_bytes_read; +} + +size_t Target::ReadScalarIntegerFromMemory(const Address &addr, uint32_t byte_size, + bool is_signed, Scalar &scalar, + Status &error, + bool force_live_memory) { + uint64_t uval; + + if (byte_size <= sizeof(uval)) { + size_t bytes_read = + ReadMemory(addr, &uval, byte_size, error, force_live_memory); + if (bytes_read == byte_size) { + DataExtractor data(&uval, sizeof(uval), m_arch.GetSpec().GetByteOrder(), + m_arch.GetSpec().GetAddressByteSize()); + lldb::offset_t offset = 0; + if (byte_size <= 4) + scalar = data.GetMaxU32(&offset, byte_size); + else + scalar = data.GetMaxU64(&offset, byte_size); + + if (is_signed) + scalar.SignExtend(byte_size * 8); + return bytes_read; + } + } else { + error.SetErrorStringWithFormat( + "byte size of %u is too large for integer scalar type", byte_size); + } + return 0; +} + +uint64_t Target::ReadUnsignedIntegerFromMemory(const Address &addr, + size_t integer_byte_size, + uint64_t fail_value, Status &error, + bool force_live_memory) { + Scalar scalar; + if (ReadScalarIntegerFromMemory(addr, integer_byte_size, false, scalar, error, + force_live_memory)) + return scalar.ULongLong(fail_value); + return fail_value; +} + +bool Target::ReadPointerFromMemory(const Address &addr, Status &error, + Address &pointer_addr, + bool force_live_memory) { + Scalar scalar; + if (ReadScalarIntegerFromMemory(addr, m_arch.GetSpec().GetAddressByteSize(), + false, scalar, error, force_live_memory)) { + addr_t pointer_vm_addr = scalar.ULongLong(LLDB_INVALID_ADDRESS); + if (pointer_vm_addr != LLDB_INVALID_ADDRESS) { + SectionLoadList §ion_load_list = GetSectionLoadList(); + if (section_load_list.IsEmpty()) { + // No sections are loaded, so we must assume we are not running yet and + // anything we are given is a file address. + m_images.ResolveFileAddress(pointer_vm_addr, pointer_addr); + } else { + // We have at least one section loaded. This can be because we have + // manually loaded some sections with "target modules load ..." or + // because we have a live process that has sections loaded through + // the dynamic loader + section_load_list.ResolveLoadAddress(pointer_vm_addr, pointer_addr); + } + // We weren't able to resolve the pointer value, so just return an + // address with no section + if (!pointer_addr.IsValid()) + pointer_addr.SetOffset(pointer_vm_addr); + return true; + } + } + return false; +} + +ModuleSP Target::GetOrCreateModule(const ModuleSpec &module_spec, bool notify, + Status *error_ptr) { + ModuleSP module_sp; + + Status error; + + // First see if we already have this module in our module list. If we do, + // then we're done, we don't need to consult the shared modules list. But + // only do this if we are passed a UUID. + + if (module_spec.GetUUID().IsValid()) + module_sp = m_images.FindFirstModule(module_spec); + + if (!module_sp) { + llvm::SmallVector<ModuleSP, 1> + old_modules; // This will get filled in if we have a new version + // of the library + bool did_create_module = false; + FileSpecList search_paths = GetExecutableSearchPaths(); + FileSpec symbol_file_spec; + + // Call locate module callback if set. This allows users to implement their + // own module cache system. For example, to leverage build system artifacts, + // to bypass pulling files from remote platform, or to search symbol files + // from symbol servers. + if (m_platform_sp) + m_platform_sp->CallLocateModuleCallbackIfSet( + module_spec, module_sp, symbol_file_spec, &did_create_module); + + // The result of this CallLocateModuleCallbackIfSet is one of the following. + // 1. module_sp:loaded, symbol_file_spec:set + // The callback found a module file and a symbol file for the + // module_spec. We will call module_sp->SetSymbolFileFileSpec with + // the symbol_file_spec later. + // 2. module_sp:loaded, symbol_file_spec:empty + // The callback only found a module file for the module_spec. + // 3. module_sp:empty, symbol_file_spec:set + // The callback only found a symbol file for the module. We continue + // to find a module file for this module_spec and we will call + // module_sp->SetSymbolFileFileSpec with the symbol_file_spec later. + // 4. module_sp:empty, symbol_file_spec:empty + // Platform does not exist, the callback is not set, the callback did + // not find any module files nor any symbol files, the callback failed, + // or something went wrong. We continue to find a module file for this + // module_spec. + + if (!module_sp) { + // If there are image search path entries, try to use them to acquire a + // suitable image. + if (m_image_search_paths.GetSize()) { + ModuleSpec transformed_spec(module_spec); + ConstString transformed_dir; + if (m_image_search_paths.RemapPath( + module_spec.GetFileSpec().GetDirectory(), transformed_dir)) { + transformed_spec.GetFileSpec().SetDirectory(transformed_dir); + transformed_spec.GetFileSpec().SetFilename( + module_spec.GetFileSpec().GetFilename()); + error = ModuleList::GetSharedModule(transformed_spec, module_sp, + &search_paths, &old_modules, + &did_create_module); + } + } + } + + if (!module_sp) { + // If we have a UUID, we can check our global shared module list in case + // we already have it. If we don't have a valid UUID, then we can't since + // the path in "module_spec" will be a platform path, and we will need to + // let the platform find that file. For example, we could be asking for + // "/usr/lib/dyld" and if we do not have a UUID, we don't want to pick + // the local copy of "/usr/lib/dyld" since our platform could be a remote + // platform that has its own "/usr/lib/dyld" in an SDK or in a local file + // cache. + if (module_spec.GetUUID().IsValid()) { + // We have a UUID, it is OK to check the global module list... + error = + ModuleList::GetSharedModule(module_spec, module_sp, &search_paths, + &old_modules, &did_create_module); + } + + if (!module_sp) { + // The platform is responsible for finding and caching an appropriate + // module in the shared module cache. + if (m_platform_sp) { + error = m_platform_sp->GetSharedModule( + module_spec, m_process_sp.get(), module_sp, &search_paths, + &old_modules, &did_create_module); + } else { + error.SetErrorString("no platform is currently set"); + } + } + } + + // We found a module that wasn't in our target list. Let's make sure that + // there wasn't an equivalent module in the list already, and if there was, + // let's remove it. + if (module_sp) { + ObjectFile *objfile = module_sp->GetObjectFile(); + if (objfile) { + switch (objfile->GetType()) { + case ObjectFile::eTypeCoreFile: /// A core file that has a checkpoint of + /// a program's execution state + case ObjectFile::eTypeExecutable: /// A normal executable + case ObjectFile::eTypeDynamicLinker: /// The platform's dynamic linker + /// executable + case ObjectFile::eTypeObjectFile: /// An intermediate object file + case ObjectFile::eTypeSharedLibrary: /// A shared library that can be + /// used during execution + break; + case ObjectFile::eTypeDebugInfo: /// An object file that contains only + /// debug information + if (error_ptr) + error_ptr->SetErrorString("debug info files aren't valid target " + "modules, please specify an executable"); + return ModuleSP(); + case ObjectFile::eTypeStubLibrary: /// A library that can be linked + /// against but not used for + /// execution + if (error_ptr) + error_ptr->SetErrorString("stub libraries aren't valid target " + "modules, please specify an executable"); + return ModuleSP(); + default: + if (error_ptr) + error_ptr->SetErrorString( + "unsupported file type, please specify an executable"); + return ModuleSP(); + } + // GetSharedModule is not guaranteed to find the old shared module, for + // instance in the common case where you pass in the UUID, it is only + // going to find the one module matching the UUID. In fact, it has no + // good way to know what the "old module" relevant to this target is, + // since there might be many copies of a module with this file spec in + // various running debug sessions, but only one of them will belong to + // this target. So let's remove the UUID from the module list, and look + // in the target's module list. Only do this if there is SOMETHING else + // in the module spec... + if (module_spec.GetUUID().IsValid() && + !module_spec.GetFileSpec().GetFilename().IsEmpty() && + !module_spec.GetFileSpec().GetDirectory().IsEmpty()) { + ModuleSpec module_spec_copy(module_spec.GetFileSpec()); + module_spec_copy.GetUUID().Clear(); + + ModuleList found_modules; + m_images.FindModules(module_spec_copy, found_modules); + found_modules.ForEach([&](const ModuleSP &found_module) -> bool { + old_modules.push_back(found_module); + return true; + }); + } + + // If the locate module callback had found a symbol file, set it to the + // module_sp before preloading symbols. + if (symbol_file_spec) + module_sp->SetSymbolFileFileSpec(symbol_file_spec); + + // Preload symbols outside of any lock, so hopefully we can do this for + // each library in parallel. + if (GetPreloadSymbols()) + module_sp->PreloadSymbols(); + llvm::SmallVector<ModuleSP, 1> replaced_modules; + for (ModuleSP &old_module_sp : old_modules) { + if (m_images.GetIndexForModule(old_module_sp.get()) != + LLDB_INVALID_INDEX32) { + if (replaced_modules.empty()) + m_images.ReplaceModule(old_module_sp, module_sp); + else + m_images.Remove(old_module_sp); + + replaced_modules.push_back(std::move(old_module_sp)); + } + } + + if (replaced_modules.size() > 1) { + // The same new module replaced multiple old modules + // simultaneously. It's not clear this should ever + // happen (if we always replace old modules as we add + // new ones, presumably we should never have more than + // one old one). If there are legitimate cases where + // this happens, then the ModuleList::Notifier interface + // may need to be adjusted to allow reporting this. + // In the meantime, just log that this has happened; just + // above we called ReplaceModule on the first one, and Remove + // on the rest. + if (Log *log = GetLog(LLDBLog::Target | LLDBLog::Modules)) { + StreamString message; + auto dump = [&message](Module &dump_module) -> void { + UUID dump_uuid = dump_module.GetUUID(); + + message << '['; + dump_module.GetDescription(message.AsRawOstream()); + message << " (uuid "; + + if (dump_uuid.IsValid()) + dump_uuid.Dump(message); + else + message << "not specified"; + + message << ")]"; + }; + + message << "New module "; + dump(*module_sp); + message.AsRawOstream() + << llvm::formatv(" simultaneously replaced {0} old modules: ", + replaced_modules.size()); + for (ModuleSP &replaced_module_sp : replaced_modules) + dump(*replaced_module_sp); + + log->PutString(message.GetString()); + } + } + + if (replaced_modules.empty()) + m_images.Append(module_sp, notify); + + for (ModuleSP &old_module_sp : replaced_modules) { + Module *old_module_ptr = old_module_sp.get(); + old_module_sp.reset(); + ModuleList::RemoveSharedModuleIfOrphaned(old_module_ptr); + } + } else + module_sp.reset(); + } + } + if (error_ptr) + *error_ptr = error; + return module_sp; +} + +TargetSP Target::CalculateTarget() { return shared_from_this(); } + +ProcessSP Target::CalculateProcess() { return m_process_sp; } + +ThreadSP Target::CalculateThread() { return ThreadSP(); } + +StackFrameSP Target::CalculateStackFrame() { return StackFrameSP(); } + +void Target::CalculateExecutionContext(ExecutionContext &exe_ctx) { + exe_ctx.Clear(); + exe_ctx.SetTargetPtr(this); +} + +PathMappingList &Target::GetImageSearchPathList() { + return m_image_search_paths; +} + +void Target::ImageSearchPathsChanged(const PathMappingList &path_list, + void *baton) { + Target *target = (Target *)baton; + ModuleSP exe_module_sp(target->GetExecutableModule()); + if (exe_module_sp) + target->SetExecutableModule(exe_module_sp, eLoadDependentsYes); +} + +llvm::Expected<lldb::TypeSystemSP> +Target::GetScratchTypeSystemForLanguage(lldb::LanguageType language, + bool create_on_demand) { + if (!m_valid) + return llvm::createStringError("Invalid Target"); + + if (language == eLanguageTypeMipsAssembler // GNU AS and LLVM use it for all + // assembly code + || language == eLanguageTypeUnknown) { + LanguageSet languages_for_expressions = + Language::GetLanguagesSupportingTypeSystemsForExpressions(); + + if (languages_for_expressions[eLanguageTypeC]) { + language = eLanguageTypeC; // LLDB's default. Override by setting the + // target language. + } else { + if (languages_for_expressions.Empty()) + return llvm::createStringError( + "No expression support for any languages"); + language = (LanguageType)languages_for_expressions.bitvector.find_first(); + } + } + + return m_scratch_type_system_map.GetTypeSystemForLanguage(language, this, + create_on_demand); +} + +CompilerType Target::GetRegisterType(const std::string &name, + const lldb_private::RegisterFlags &flags, + uint32_t byte_size) { + RegisterTypeBuilderSP provider = PluginManager::GetRegisterTypeBuilder(*this); + assert(provider); + return provider->GetRegisterType(name, flags, byte_size); +} + +std::vector<lldb::TypeSystemSP> +Target::GetScratchTypeSystems(bool create_on_demand) { + if (!m_valid) + return {}; + + // Some TypeSystem instances are associated with several LanguageTypes so + // they will show up several times in the loop below. The SetVector filters + // out all duplicates as they serve no use for the caller. + std::vector<lldb::TypeSystemSP> scratch_type_systems; + + LanguageSet languages_for_expressions = + Language::GetLanguagesSupportingTypeSystemsForExpressions(); + + for (auto bit : languages_for_expressions.bitvector.set_bits()) { + auto language = (LanguageType)bit; + auto type_system_or_err = + GetScratchTypeSystemForLanguage(language, create_on_demand); + if (!type_system_or_err) + LLDB_LOG_ERROR( + GetLog(LLDBLog::Target), type_system_or_err.takeError(), + "Language '{1}' has expression support but no scratch type " + "system available: {0}", + Language::GetNameForLanguageType(language)); + else + if (auto ts = *type_system_or_err) + scratch_type_systems.push_back(ts); + } + + std::sort(scratch_type_systems.begin(), scratch_type_systems.end()); + scratch_type_systems.erase( + std::unique(scratch_type_systems.begin(), scratch_type_systems.end()), + scratch_type_systems.end()); + return scratch_type_systems; +} + +PersistentExpressionState * +Target::GetPersistentExpressionStateForLanguage(lldb::LanguageType language) { + auto type_system_or_err = GetScratchTypeSystemForLanguage(language, true); + + if (auto err = type_system_or_err.takeError()) { + LLDB_LOG_ERROR( + GetLog(LLDBLog::Target), std::move(err), + "Unable to get persistent expression state for language {1}: {0}", + Language::GetNameForLanguageType(language)); + return nullptr; + } + + if (auto ts = *type_system_or_err) + return ts->GetPersistentExpressionState(); + + LLDB_LOG(GetLog(LLDBLog::Target), + "Unable to get persistent expression state for language {1}: {0}", + Language::GetNameForLanguageType(language)); + return nullptr; +} + +UserExpression *Target::GetUserExpressionForLanguage( + llvm::StringRef expr, llvm::StringRef prefix, SourceLanguage language, + Expression::ResultType desired_type, + const EvaluateExpressionOptions &options, ValueObject *ctx_obj, + Status &error) { + auto type_system_or_err = + GetScratchTypeSystemForLanguage(language.AsLanguageType()); + if (auto err = type_system_or_err.takeError()) { + error.SetErrorStringWithFormat( + "Could not find type system for language %s: %s", + Language::GetNameForLanguageType(language.AsLanguageType()), + llvm::toString(std::move(err)).c_str()); + return nullptr; + } + + auto ts = *type_system_or_err; + if (!ts) { + error.SetErrorStringWithFormat( + "Type system for language %s is no longer live", + language.GetDescription().data()); + return nullptr; + } + + auto *user_expr = ts->GetUserExpression(expr, prefix, language, desired_type, + options, ctx_obj); + if (!user_expr) + error.SetErrorStringWithFormat( + "Could not create an expression for language %s", + language.GetDescription().data()); + + return user_expr; +} + +FunctionCaller *Target::GetFunctionCallerForLanguage( + lldb::LanguageType language, const CompilerType &return_type, + const Address &function_address, const ValueList &arg_value_list, + const char *name, Status &error) { + auto type_system_or_err = GetScratchTypeSystemForLanguage(language); + if (auto err = type_system_or_err.takeError()) { + error.SetErrorStringWithFormat( + "Could not find type system for language %s: %s", + Language::GetNameForLanguageType(language), + llvm::toString(std::move(err)).c_str()); + return nullptr; + } + auto ts = *type_system_or_err; + if (!ts) { + error.SetErrorStringWithFormat( + "Type system for language %s is no longer live", + Language::GetNameForLanguageType(language)); + return nullptr; + } + auto *persistent_fn = ts->GetFunctionCaller(return_type, function_address, + arg_value_list, name); + if (!persistent_fn) + error.SetErrorStringWithFormat( + "Could not create an expression for language %s", + Language::GetNameForLanguageType(language)); + + return persistent_fn; +} + +llvm::Expected<std::unique_ptr<UtilityFunction>> +Target::CreateUtilityFunction(std::string expression, std::string name, + lldb::LanguageType language, + ExecutionContext &exe_ctx) { + auto type_system_or_err = GetScratchTypeSystemForLanguage(language); + if (!type_system_or_err) + return type_system_or_err.takeError(); + auto ts = *type_system_or_err; + if (!ts) + return llvm::createStringError( + llvm::StringRef("Type system for language ") + + Language::GetNameForLanguageType(language) + + llvm::StringRef(" is no longer live")); + std::unique_ptr<UtilityFunction> utility_fn = + ts->CreateUtilityFunction(std::move(expression), std::move(name)); + if (!utility_fn) + return llvm::createStringError( + llvm::StringRef("Could not create an expression for language") + + Language::GetNameForLanguageType(language)); + + DiagnosticManager diagnostics; + if (!utility_fn->Install(diagnostics, exe_ctx)) + return llvm::createStringError(diagnostics.GetString()); + + return std::move(utility_fn); +} + +void Target::SettingsInitialize() { Process::SettingsInitialize(); } + +void Target::SettingsTerminate() { Process::SettingsTerminate(); } + +FileSpecList Target::GetDefaultExecutableSearchPaths() { + return Target::GetGlobalProperties().GetExecutableSearchPaths(); +} + +FileSpecList Target::GetDefaultDebugFileSearchPaths() { + return Target::GetGlobalProperties().GetDebugFileSearchPaths(); +} + +ArchSpec Target::GetDefaultArchitecture() { + return Target::GetGlobalProperties().GetDefaultArchitecture(); +} + +void Target::SetDefaultArchitecture(const ArchSpec &arch) { + LLDB_LOG(GetLog(LLDBLog::Target), + "setting target's default architecture to {0} ({1})", + arch.GetArchitectureName(), arch.GetTriple().getTriple()); + Target::GetGlobalProperties().SetDefaultArchitecture(arch); +} + +llvm::Error Target::SetLabel(llvm::StringRef label) { + size_t n = LLDB_INVALID_INDEX32; + if (llvm::to_integer(label, n)) + return llvm::createStringError("Cannot use integer as target label."); + TargetList &targets = GetDebugger().GetTargetList(); + for (size_t i = 0; i < targets.GetNumTargets(); i++) { + TargetSP target_sp = targets.GetTargetAtIndex(i); + if (target_sp && target_sp->GetLabel() == label) { + return llvm::make_error<llvm::StringError>( + llvm::formatv( + "Cannot use label '{0}' since it's set in target #{1}.", label, + i), + llvm::inconvertibleErrorCode()); + } + } + + m_label = label.str(); + return llvm::Error::success(); +} + +Target *Target::GetTargetFromContexts(const ExecutionContext *exe_ctx_ptr, + const SymbolContext *sc_ptr) { + // The target can either exist in the "process" of ExecutionContext, or in + // the "target_sp" member of SymbolContext. This accessor helper function + // will get the target from one of these locations. + + Target *target = nullptr; + if (sc_ptr != nullptr) + target = sc_ptr->target_sp.get(); + if (target == nullptr && exe_ctx_ptr) + target = exe_ctx_ptr->GetTargetPtr(); + return target; +} + +ExpressionResults Target::EvaluateExpression( + llvm::StringRef expr, ExecutionContextScope *exe_scope, + lldb::ValueObjectSP &result_valobj_sp, + const EvaluateExpressionOptions &options, std::string *fixed_expression, + ValueObject *ctx_obj) { + result_valobj_sp.reset(); + + ExpressionResults execution_results = eExpressionSetupError; + + if (expr.empty()) { + m_stats.GetExpressionStats().NotifyFailure(); + return execution_results; + } + + // We shouldn't run stop hooks in expressions. + bool old_suppress_value = m_suppress_stop_hooks; + m_suppress_stop_hooks = true; + auto on_exit = llvm::make_scope_exit([this, old_suppress_value]() { + m_suppress_stop_hooks = old_suppress_value; + }); + + ExecutionContext exe_ctx; + + if (exe_scope) { + exe_scope->CalculateExecutionContext(exe_ctx); + } else if (m_process_sp) { + m_process_sp->CalculateExecutionContext(exe_ctx); + } else { + CalculateExecutionContext(exe_ctx); + } + + // Make sure we aren't just trying to see the value of a persistent variable + // (something like "$0") + // Only check for persistent variables the expression starts with a '$' + lldb::ExpressionVariableSP persistent_var_sp; + if (expr[0] == '$') { + auto type_system_or_err = + GetScratchTypeSystemForLanguage(eLanguageTypeC); + if (auto err = type_system_or_err.takeError()) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Target), std::move(err), + "Unable to get scratch type system"); + } else { + auto ts = *type_system_or_err; + if (!ts) + LLDB_LOG_ERROR(GetLog(LLDBLog::Target), std::move(err), + "Scratch type system is no longer live: {0}"); + else + persistent_var_sp = + ts->GetPersistentExpressionState()->GetVariable(expr); + } + } + if (persistent_var_sp) { + result_valobj_sp = persistent_var_sp->GetValueObject(); + execution_results = eExpressionCompleted; + } else { + llvm::StringRef prefix = GetExpressionPrefixContents(); + Status error; + execution_results = UserExpression::Evaluate(exe_ctx, options, expr, prefix, + result_valobj_sp, error, + fixed_expression, ctx_obj); + // Pass up the error by wrapping it inside an error result. + if (error.Fail() && !result_valobj_sp) + result_valobj_sp = ValueObjectConstResult::Create( + exe_ctx.GetBestExecutionContextScope(), error); + } + + if (execution_results == eExpressionCompleted) + m_stats.GetExpressionStats().NotifySuccess(); + else + m_stats.GetExpressionStats().NotifyFailure(); + return execution_results; +} + +lldb::ExpressionVariableSP Target::GetPersistentVariable(ConstString name) { + lldb::ExpressionVariableSP variable_sp; + m_scratch_type_system_map.ForEach( + [name, &variable_sp](TypeSystemSP type_system) -> bool { + auto ts = type_system.get(); + if (!ts) + return true; + if (PersistentExpressionState *persistent_state = + ts->GetPersistentExpressionState()) { + variable_sp = persistent_state->GetVariable(name); + + if (variable_sp) + return false; // Stop iterating the ForEach + } + return true; // Keep iterating the ForEach + }); + return variable_sp; +} + +lldb::addr_t Target::GetPersistentSymbol(ConstString name) { + lldb::addr_t address = LLDB_INVALID_ADDRESS; + + m_scratch_type_system_map.ForEach( + [name, &address](lldb::TypeSystemSP type_system) -> bool { + auto ts = type_system.get(); + if (!ts) + return true; + + if (PersistentExpressionState *persistent_state = + ts->GetPersistentExpressionState()) { + address = persistent_state->LookupSymbol(name); + if (address != LLDB_INVALID_ADDRESS) + return false; // Stop iterating the ForEach + } + return true; // Keep iterating the ForEach + }); + return address; +} + +llvm::Expected<lldb_private::Address> Target::GetEntryPointAddress() { + Module *exe_module = GetExecutableModulePointer(); + + // Try to find the entry point address in the primary executable. + const bool has_primary_executable = exe_module && exe_module->GetObjectFile(); + if (has_primary_executable) { + Address entry_addr = exe_module->GetObjectFile()->GetEntryPointAddress(); + if (entry_addr.IsValid()) + return entry_addr; + } + + const ModuleList &modules = GetImages(); + const size_t num_images = modules.GetSize(); + for (size_t idx = 0; idx < num_images; ++idx) { + ModuleSP module_sp(modules.GetModuleAtIndex(idx)); + if (!module_sp || !module_sp->GetObjectFile()) + continue; + + Address entry_addr = module_sp->GetObjectFile()->GetEntryPointAddress(); + if (entry_addr.IsValid()) + return entry_addr; + } + + // We haven't found the entry point address. Return an appropriate error. + if (!has_primary_executable) + return llvm::createStringError( + "No primary executable found and could not find entry point address in " + "any executable module"); + + return llvm::createStringError( + "Could not find entry point address for primary executable module \"" + + exe_module->GetFileSpec().GetFilename().GetStringRef() + "\""); +} + +lldb::addr_t Target::GetCallableLoadAddress(lldb::addr_t load_addr, + AddressClass addr_class) const { + auto arch_plugin = GetArchitecturePlugin(); + return arch_plugin + ? arch_plugin->GetCallableLoadAddress(load_addr, addr_class) + : load_addr; +} + +lldb::addr_t Target::GetOpcodeLoadAddress(lldb::addr_t load_addr, + AddressClass addr_class) const { + auto arch_plugin = GetArchitecturePlugin(); + return arch_plugin ? arch_plugin->GetOpcodeLoadAddress(load_addr, addr_class) + : load_addr; +} + +lldb::addr_t Target::GetBreakableLoadAddress(lldb::addr_t addr) { + auto arch_plugin = GetArchitecturePlugin(); + return arch_plugin ? arch_plugin->GetBreakableLoadAddress(addr, *this) : addr; +} + +SourceManager &Target::GetSourceManager() { + if (!m_source_manager_up) + m_source_manager_up = std::make_unique<SourceManager>(shared_from_this()); + return *m_source_manager_up; +} + +Target::StopHookSP Target::CreateStopHook(StopHook::StopHookKind kind) { + lldb::user_id_t new_uid = ++m_stop_hook_next_id; + Target::StopHookSP stop_hook_sp; + switch (kind) { + case StopHook::StopHookKind::CommandBased: + stop_hook_sp.reset(new StopHookCommandLine(shared_from_this(), new_uid)); + break; + case StopHook::StopHookKind::ScriptBased: + stop_hook_sp.reset(new StopHookScripted(shared_from_this(), new_uid)); + break; + } + m_stop_hooks[new_uid] = stop_hook_sp; + return stop_hook_sp; +} + +void Target::UndoCreateStopHook(lldb::user_id_t user_id) { + if (!RemoveStopHookByID(user_id)) + return; + if (user_id == m_stop_hook_next_id) + m_stop_hook_next_id--; +} + +bool Target::RemoveStopHookByID(lldb::user_id_t user_id) { + size_t num_removed = m_stop_hooks.erase(user_id); + return (num_removed != 0); +} + +void Target::RemoveAllStopHooks() { m_stop_hooks.clear(); } + +Target::StopHookSP Target::GetStopHookByID(lldb::user_id_t user_id) { + StopHookSP found_hook; + + StopHookCollection::iterator specified_hook_iter; + specified_hook_iter = m_stop_hooks.find(user_id); + if (specified_hook_iter != m_stop_hooks.end()) + found_hook = (*specified_hook_iter).second; + return found_hook; +} + +bool Target::SetStopHookActiveStateByID(lldb::user_id_t user_id, + bool active_state) { + StopHookCollection::iterator specified_hook_iter; + specified_hook_iter = m_stop_hooks.find(user_id); + if (specified_hook_iter == m_stop_hooks.end()) + return false; + + (*specified_hook_iter).second->SetIsActive(active_state); + return true; +} + +void Target::SetAllStopHooksActiveState(bool active_state) { + StopHookCollection::iterator pos, end = m_stop_hooks.end(); + for (pos = m_stop_hooks.begin(); pos != end; pos++) { + (*pos).second->SetIsActive(active_state); + } +} + +bool Target::RunStopHooks() { + if (m_suppress_stop_hooks) + return false; + + if (!m_process_sp) + return false; + + // Somebody might have restarted the process: + // Still return false, the return value is about US restarting the target. + if (m_process_sp->GetState() != eStateStopped) + return false; + + if (m_stop_hooks.empty()) + return false; + + // If there aren't any active stop hooks, don't bother either. + bool any_active_hooks = false; + for (auto hook : m_stop_hooks) { + if (hook.second->IsActive()) { + any_active_hooks = true; + break; + } + } + if (!any_active_hooks) + return false; + + // Make sure we check that we are not stopped because of us running a user + // expression since in that case we do not want to run the stop-hooks. Note, + // you can't just check whether the last stop was for a User Expression, + // because breakpoint commands get run before stop hooks, and one of them + // might have run an expression. You have to ensure you run the stop hooks + // once per natural stop. + uint32_t last_natural_stop = m_process_sp->GetModIDRef().GetLastNaturalStopID(); + if (last_natural_stop != 0 && m_latest_stop_hook_id == last_natural_stop) + return false; + + m_latest_stop_hook_id = last_natural_stop; + + std::vector<ExecutionContext> exc_ctx_with_reasons; + + ThreadList &cur_threadlist = m_process_sp->GetThreadList(); + size_t num_threads = cur_threadlist.GetSize(); + for (size_t i = 0; i < num_threads; i++) { + lldb::ThreadSP cur_thread_sp = cur_threadlist.GetThreadAtIndex(i); + if (cur_thread_sp->ThreadStoppedForAReason()) { + lldb::StackFrameSP cur_frame_sp = cur_thread_sp->GetStackFrameAtIndex(0); + exc_ctx_with_reasons.emplace_back(m_process_sp.get(), cur_thread_sp.get(), + cur_frame_sp.get()); + } + } + + // If no threads stopped for a reason, don't run the stop-hooks. + size_t num_exe_ctx = exc_ctx_with_reasons.size(); + if (num_exe_ctx == 0) + return false; + + StreamSP output_sp = m_debugger.GetAsyncOutputStream(); + + bool auto_continue = false; + bool hooks_ran = false; + bool print_hook_header = (m_stop_hooks.size() != 1); + bool print_thread_header = (num_exe_ctx != 1); + bool should_stop = false; + bool somebody_restarted = false; + + for (auto stop_entry : m_stop_hooks) { + StopHookSP cur_hook_sp = stop_entry.second; + if (!cur_hook_sp->IsActive()) + continue; + + bool any_thread_matched = false; + for (auto exc_ctx : exc_ctx_with_reasons) { + // We detect somebody restarted in the stop-hook loop, and broke out of + // that loop back to here. So break out of here too. + if (somebody_restarted) + break; + + if (!cur_hook_sp->ExecutionContextPasses(exc_ctx)) + continue; + + // We only consult the auto-continue for a stop hook if it matched the + // specifier. + auto_continue |= cur_hook_sp->GetAutoContinue(); + + if (!hooks_ran) + hooks_ran = true; + + if (print_hook_header && !any_thread_matched) { + StreamString s; + cur_hook_sp->GetDescription(s, eDescriptionLevelBrief); + if (s.GetSize() != 0) + output_sp->Printf("\n- Hook %" PRIu64 " (%s)\n", cur_hook_sp->GetID(), + s.GetData()); + else + output_sp->Printf("\n- Hook %" PRIu64 "\n", cur_hook_sp->GetID()); + any_thread_matched = true; + } + + if (print_thread_header) + output_sp->Printf("-- Thread %d\n", + exc_ctx.GetThreadPtr()->GetIndexID()); + + StopHook::StopHookResult this_result = + cur_hook_sp->HandleStop(exc_ctx, output_sp); + bool this_should_stop = true; + + switch (this_result) { + case StopHook::StopHookResult::KeepStopped: + // If this hook is set to auto-continue that should override the + // HandleStop result... + if (cur_hook_sp->GetAutoContinue()) + this_should_stop = false; + else + this_should_stop = true; + + break; + case StopHook::StopHookResult::RequestContinue: + this_should_stop = false; + break; + case StopHook::StopHookResult::AlreadyContinued: + // We don't have a good way to prohibit people from restarting the + // target willy nilly in a stop hook. If the hook did so, give a + // gentle suggestion here and bag out if the hook processing. + output_sp->Printf("\nAborting stop hooks, hook %" PRIu64 + " set the program running.\n" + " Consider using '-G true' to make " + "stop hooks auto-continue.\n", + cur_hook_sp->GetID()); + somebody_restarted = true; + break; + } + // If we're already restarted, stop processing stop hooks. + // FIXME: if we are doing non-stop mode for real, we would have to + // check that OUR thread was restarted, otherwise we should keep + // processing stop hooks. + if (somebody_restarted) + break; + + // If anybody wanted to stop, we should all stop. + if (!should_stop) + should_stop = this_should_stop; + } + } + + output_sp->Flush(); + + // If one of the commands in the stop hook already restarted the target, + // report that fact. + if (somebody_restarted) + return true; + + // Finally, if auto-continue was requested, do it now: + // We only compute should_stop against the hook results if a hook got to run + // which is why we have to do this conjoint test. + if ((hooks_ran && !should_stop) || auto_continue) { + Log *log = GetLog(LLDBLog::Process); + Status error = m_process_sp->PrivateResume(); + if (error.Success()) { + LLDB_LOG(log, "Resuming from RunStopHooks"); + return true; + } else { + LLDB_LOG(log, "Resuming from RunStopHooks failed: {0}", error); + return false; + } + } + + return false; +} + +TargetProperties &Target::GetGlobalProperties() { + // NOTE: intentional leak so we don't crash if global destructor chain gets + // called as other threads still use the result of this function + static TargetProperties *g_settings_ptr = + new TargetProperties(nullptr); + return *g_settings_ptr; +} + +Status Target::Install(ProcessLaunchInfo *launch_info) { + Status error; + PlatformSP platform_sp(GetPlatform()); + if (platform_sp) { + if (platform_sp->IsRemote()) { + if (platform_sp->IsConnected()) { + // Install all files that have an install path when connected to a + // remote platform. If target.auto-install-main-executable is set then + // also install the main executable even if it does not have an explicit + // install path specified. + const ModuleList &modules = GetImages(); + const size_t num_images = modules.GetSize(); + for (size_t idx = 0; idx < num_images; ++idx) { + ModuleSP module_sp(modules.GetModuleAtIndex(idx)); + if (module_sp) { + const bool is_main_executable = module_sp == GetExecutableModule(); + FileSpec local_file(module_sp->GetFileSpec()); + if (local_file) { + FileSpec remote_file(module_sp->GetRemoteInstallFileSpec()); + if (!remote_file) { + if (is_main_executable && GetAutoInstallMainExecutable()) { + // Automatically install the main executable. + remote_file = platform_sp->GetRemoteWorkingDirectory(); + remote_file.AppendPathComponent( + module_sp->GetFileSpec().GetFilename().GetCString()); + } + } + if (remote_file) { + error = platform_sp->Install(local_file, remote_file); + if (error.Success()) { + module_sp->SetPlatformFileSpec(remote_file); + if (is_main_executable) { + platform_sp->SetFilePermissions(remote_file, 0700); + if (launch_info) + launch_info->SetExecutableFile(remote_file, false); + } + } else + break; + } + } + } + } + } + } + } + return error; +} + +bool Target::ResolveLoadAddress(addr_t load_addr, Address &so_addr, + uint32_t stop_id) { + return m_section_load_history.ResolveLoadAddress(stop_id, load_addr, so_addr); +} + +bool Target::ResolveFileAddress(lldb::addr_t file_addr, + Address &resolved_addr) { + return m_images.ResolveFileAddress(file_addr, resolved_addr); +} + +bool Target::SetSectionLoadAddress(const SectionSP §ion_sp, + addr_t new_section_load_addr, + bool warn_multiple) { + const addr_t old_section_load_addr = + m_section_load_history.GetSectionLoadAddress( + SectionLoadHistory::eStopIDNow, section_sp); + if (old_section_load_addr != new_section_load_addr) { + uint32_t stop_id = 0; + ProcessSP process_sp(GetProcessSP()); + if (process_sp) + stop_id = process_sp->GetStopID(); + else + stop_id = m_section_load_history.GetLastStopID(); + if (m_section_load_history.SetSectionLoadAddress( + stop_id, section_sp, new_section_load_addr, warn_multiple)) + return true; // Return true if the section load address was changed... + } + return false; // Return false to indicate nothing changed +} + +size_t Target::UnloadModuleSections(const ModuleList &module_list) { + size_t section_unload_count = 0; + size_t num_modules = module_list.GetSize(); + for (size_t i = 0; i < num_modules; ++i) { + section_unload_count += + UnloadModuleSections(module_list.GetModuleAtIndex(i)); + } + return section_unload_count; +} + +size_t Target::UnloadModuleSections(const lldb::ModuleSP &module_sp) { + uint32_t stop_id = 0; + ProcessSP process_sp(GetProcessSP()); + if (process_sp) + stop_id = process_sp->GetStopID(); + else + stop_id = m_section_load_history.GetLastStopID(); + SectionList *sections = module_sp->GetSectionList(); + size_t section_unload_count = 0; + if (sections) { + const uint32_t num_sections = sections->GetNumSections(0); + for (uint32_t i = 0; i < num_sections; ++i) { + section_unload_count += m_section_load_history.SetSectionUnloaded( + stop_id, sections->GetSectionAtIndex(i)); + } + } + return section_unload_count; +} + +bool Target::SetSectionUnloaded(const lldb::SectionSP §ion_sp) { + uint32_t stop_id = 0; + ProcessSP process_sp(GetProcessSP()); + if (process_sp) + stop_id = process_sp->GetStopID(); + else + stop_id = m_section_load_history.GetLastStopID(); + return m_section_load_history.SetSectionUnloaded(stop_id, section_sp); +} + +bool Target::SetSectionUnloaded(const lldb::SectionSP §ion_sp, + addr_t load_addr) { + uint32_t stop_id = 0; + ProcessSP process_sp(GetProcessSP()); + if (process_sp) + stop_id = process_sp->GetStopID(); + else + stop_id = m_section_load_history.GetLastStopID(); + return m_section_load_history.SetSectionUnloaded(stop_id, section_sp, + load_addr); +} + +void Target::ClearAllLoadedSections() { m_section_load_history.Clear(); } + +void Target::SaveScriptedLaunchInfo(lldb_private::ProcessInfo &process_info) { + if (process_info.IsScriptedProcess()) { + // Only copy scripted process launch options. + ProcessLaunchInfo &default_launch_info = const_cast<ProcessLaunchInfo &>( + GetGlobalProperties().GetProcessLaunchInfo()); + default_launch_info.SetProcessPluginName("ScriptedProcess"); + default_launch_info.SetScriptedMetadata(process_info.GetScriptedMetadata()); + SetProcessLaunchInfo(default_launch_info); + } +} + +Status Target::Launch(ProcessLaunchInfo &launch_info, Stream *stream) { + m_stats.SetLaunchOrAttachTime(); + Status error; + Log *log = GetLog(LLDBLog::Target); + + LLDB_LOGF(log, "Target::%s() called for %s", __FUNCTION__, + launch_info.GetExecutableFile().GetPath().c_str()); + + StateType state = eStateInvalid; + + // Scope to temporarily get the process state in case someone has manually + // remotely connected already to a process and we can skip the platform + // launching. + { + ProcessSP process_sp(GetProcessSP()); + + if (process_sp) { + state = process_sp->GetState(); + LLDB_LOGF(log, + "Target::%s the process exists, and its current state is %s", + __FUNCTION__, StateAsCString(state)); + } else { + LLDB_LOGF(log, "Target::%s the process instance doesn't currently exist.", + __FUNCTION__); + } + } + + launch_info.GetFlags().Set(eLaunchFlagDebug); + + SaveScriptedLaunchInfo(launch_info); + + // Get the value of synchronous execution here. If you wait till after you + // have started to run, then you could have hit a breakpoint, whose command + // might switch the value, and then you'll pick up that incorrect value. + Debugger &debugger = GetDebugger(); + const bool synchronous_execution = + debugger.GetCommandInterpreter().GetSynchronous(); + + PlatformSP platform_sp(GetPlatform()); + + FinalizeFileActions(launch_info); + + if (state == eStateConnected) { + if (launch_info.GetFlags().Test(eLaunchFlagLaunchInTTY)) { + error.SetErrorString( + "can't launch in tty when launching through a remote connection"); + return error; + } + } + + if (!launch_info.GetArchitecture().IsValid()) + launch_info.GetArchitecture() = GetArchitecture(); + + // Hijacking events of the process to be created to be sure that all events + // until the first stop are intercepted (in case if platform doesn't define + // its own hijacking listener or if the process is created by the target + // manually, without the platform). + if (!launch_info.GetHijackListener()) + launch_info.SetHijackListener(Listener::MakeListener( + Process::LaunchSynchronousHijackListenerName.data())); + + // If we're not already connected to the process, and if we have a platform + // that can launch a process for debugging, go ahead and do that here. + if (state != eStateConnected && platform_sp && + platform_sp->CanDebugProcess() && !launch_info.IsScriptedProcess()) { + LLDB_LOGF(log, "Target::%s asking the platform to debug the process", + __FUNCTION__); + + // If there was a previous process, delete it before we make the new one. + // One subtle point, we delete the process before we release the reference + // to m_process_sp. That way even if we are the last owner, the process + // will get Finalized before it gets destroyed. + DeleteCurrentProcess(); + + m_process_sp = + GetPlatform()->DebugProcess(launch_info, debugger, *this, error); + + } else { + LLDB_LOGF(log, + "Target::%s the platform doesn't know how to debug a " + "process, getting a process plugin to do this for us.", + __FUNCTION__); + + if (state == eStateConnected) { + assert(m_process_sp); + } else { + // Use a Process plugin to construct the process. + CreateProcess(launch_info.GetListener(), + launch_info.GetProcessPluginName(), nullptr, false); + } + + // Since we didn't have a platform launch the process, launch it here. + if (m_process_sp) { + m_process_sp->HijackProcessEvents(launch_info.GetHijackListener()); + m_process_sp->SetShadowListener(launch_info.GetShadowListener()); + error = m_process_sp->Launch(launch_info); + } + } + + if (!m_process_sp && error.Success()) + error.SetErrorString("failed to launch or debug process"); + + if (!error.Success()) + return error; + + bool rebroadcast_first_stop = + !synchronous_execution && + launch_info.GetFlags().Test(eLaunchFlagStopAtEntry); + + assert(launch_info.GetHijackListener()); + + EventSP first_stop_event_sp; + state = m_process_sp->WaitForProcessToStop(std::nullopt, &first_stop_event_sp, + rebroadcast_first_stop, + launch_info.GetHijackListener()); + m_process_sp->RestoreProcessEvents(); + + if (rebroadcast_first_stop) { + assert(first_stop_event_sp); + m_process_sp->BroadcastEvent(first_stop_event_sp); + return error; + } + + switch (state) { + case eStateStopped: { + if (launch_info.GetFlags().Test(eLaunchFlagStopAtEntry)) + break; + if (synchronous_execution) + // Now we have handled the stop-from-attach, and we are just + // switching to a synchronous resume. So we should switch to the + // SyncResume hijacker. + m_process_sp->ResumeSynchronous(stream); + else + error = m_process_sp->Resume(); + if (!error.Success()) { + Status error2; + error2.SetErrorStringWithFormat( + "process resume at entry point failed: %s", error.AsCString()); + error = error2; + } + } break; + case eStateExited: { + bool with_shell = !!launch_info.GetShell(); + const int exit_status = m_process_sp->GetExitStatus(); + const char *exit_desc = m_process_sp->GetExitDescription(); + std::string desc; + if (exit_desc && exit_desc[0]) + desc = " (" + std::string(exit_desc) + ')'; + if (with_shell) + error.SetErrorStringWithFormat( + "process exited with status %i%s\n" + "'r' and 'run' are aliases that default to launching through a " + "shell.\n" + "Try launching without going through a shell by using " + "'process launch'.", + exit_status, desc.c_str()); + else + error.SetErrorStringWithFormat("process exited with status %i%s", + exit_status, desc.c_str()); + } break; + default: + error.SetErrorStringWithFormat("initial process state wasn't stopped: %s", + StateAsCString(state)); + break; + } + return error; +} + +void Target::SetTrace(const TraceSP &trace_sp) { m_trace_sp = trace_sp; } + +TraceSP Target::GetTrace() { return m_trace_sp; } + +llvm::Expected<TraceSP> Target::CreateTrace() { + if (!m_process_sp) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "A process is required for tracing"); + if (m_trace_sp) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "A trace already exists for the target"); + + llvm::Expected<TraceSupportedResponse> trace_type = + m_process_sp->TraceSupported(); + if (!trace_type) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), "Tracing is not supported. %s", + llvm::toString(trace_type.takeError()).c_str()); + if (llvm::Expected<TraceSP> trace_sp = + Trace::FindPluginForLiveProcess(trace_type->name, *m_process_sp)) + m_trace_sp = *trace_sp; + else + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Couldn't create a Trace object for the process. %s", + llvm::toString(trace_sp.takeError()).c_str()); + return m_trace_sp; +} + +llvm::Expected<TraceSP> Target::GetTraceOrCreate() { + if (m_trace_sp) + return m_trace_sp; + return CreateTrace(); +} + +Status Target::Attach(ProcessAttachInfo &attach_info, Stream *stream) { + m_stats.SetLaunchOrAttachTime(); + auto state = eStateInvalid; + auto process_sp = GetProcessSP(); + if (process_sp) { + state = process_sp->GetState(); + if (process_sp->IsAlive() && state != eStateConnected) { + if (state == eStateAttaching) + return Status("process attach is in progress"); + return Status("a process is already being debugged"); + } + } + + const ModuleSP old_exec_module_sp = GetExecutableModule(); + + // If no process info was specified, then use the target executable name as + // the process to attach to by default + if (!attach_info.ProcessInfoSpecified()) { + if (old_exec_module_sp) + attach_info.GetExecutableFile().SetFilename( + old_exec_module_sp->GetPlatformFileSpec().GetFilename()); + + if (!attach_info.ProcessInfoSpecified()) { + return Status("no process specified, create a target with a file, or " + "specify the --pid or --name"); + } + } + + const auto platform_sp = + GetDebugger().GetPlatformList().GetSelectedPlatform(); + ListenerSP hijack_listener_sp; + const bool async = attach_info.GetAsync(); + if (!async) { + hijack_listener_sp = Listener::MakeListener( + Process::AttachSynchronousHijackListenerName.data()); + attach_info.SetHijackListener(hijack_listener_sp); + } + + Status error; + if (state != eStateConnected && platform_sp != nullptr && + platform_sp->CanDebugProcess() && !attach_info.IsScriptedProcess()) { + SetPlatform(platform_sp); + process_sp = platform_sp->Attach(attach_info, GetDebugger(), this, error); + } else { + if (state != eStateConnected) { + SaveScriptedLaunchInfo(attach_info); + llvm::StringRef plugin_name = attach_info.GetProcessPluginName(); + process_sp = + CreateProcess(attach_info.GetListenerForProcess(GetDebugger()), + plugin_name, nullptr, false); + if (!process_sp) { + error.SetErrorStringWithFormatv( + "failed to create process using plugin '{0}'", + plugin_name.empty() ? "<empty>" : plugin_name); + return error; + } + } + if (hijack_listener_sp) + process_sp->HijackProcessEvents(hijack_listener_sp); + error = process_sp->Attach(attach_info); + } + + if (error.Success() && process_sp) { + if (async) { + process_sp->RestoreProcessEvents(); + } else { + // We are stopping all the way out to the user, so update selected frames. + state = process_sp->WaitForProcessToStop( + std::nullopt, nullptr, false, attach_info.GetHijackListener(), stream, + true, SelectMostRelevantFrame); + process_sp->RestoreProcessEvents(); + + if (state != eStateStopped) { + const char *exit_desc = process_sp->GetExitDescription(); + if (exit_desc) + error.SetErrorStringWithFormat("%s", exit_desc); + else + error.SetErrorString( + "process did not stop (no such process or permission problem?)"); + process_sp->Destroy(false); + } + } + } + return error; +} + +void Target::FinalizeFileActions(ProcessLaunchInfo &info) { + Log *log = GetLog(LLDBLog::Process); + + // Finalize the file actions, and if none were given, default to opening up a + // pseudo terminal + PlatformSP platform_sp = GetPlatform(); + const bool default_to_use_pty = + m_platform_sp ? m_platform_sp->IsHost() : false; + LLDB_LOG( + log, + "have platform={0}, platform_sp->IsHost()={1}, default_to_use_pty={2}", + bool(platform_sp), + platform_sp ? (platform_sp->IsHost() ? "true" : "false") : "n/a", + default_to_use_pty); + + // If nothing for stdin or stdout or stderr was specified, then check the + // process for any default settings that were set with "settings set" + if (info.GetFileActionForFD(STDIN_FILENO) == nullptr || + info.GetFileActionForFD(STDOUT_FILENO) == nullptr || + info.GetFileActionForFD(STDERR_FILENO) == nullptr) { + LLDB_LOG(log, "at least one of stdin/stdout/stderr was not set, evaluating " + "default handling"); + + if (info.GetFlags().Test(eLaunchFlagLaunchInTTY)) { + // Do nothing, if we are launching in a remote terminal no file actions + // should be done at all. + return; + } + + if (info.GetFlags().Test(eLaunchFlagDisableSTDIO)) { + LLDB_LOG(log, "eLaunchFlagDisableSTDIO set, adding suppression action " + "for stdin, stdout and stderr"); + info.AppendSuppressFileAction(STDIN_FILENO, true, false); + info.AppendSuppressFileAction(STDOUT_FILENO, false, true); + info.AppendSuppressFileAction(STDERR_FILENO, false, true); + } else { + // Check for any values that might have gotten set with any of: (lldb) + // settings set target.input-path (lldb) settings set target.output-path + // (lldb) settings set target.error-path + FileSpec in_file_spec; + FileSpec out_file_spec; + FileSpec err_file_spec; + // Only override with the target settings if we don't already have an + // action for in, out or error + if (info.GetFileActionForFD(STDIN_FILENO) == nullptr) + in_file_spec = GetStandardInputPath(); + if (info.GetFileActionForFD(STDOUT_FILENO) == nullptr) + out_file_spec = GetStandardOutputPath(); + if (info.GetFileActionForFD(STDERR_FILENO) == nullptr) + err_file_spec = GetStandardErrorPath(); + + LLDB_LOG(log, "target stdin='{0}', target stdout='{1}', stderr='{1}'", + in_file_spec, out_file_spec, err_file_spec); + + if (in_file_spec) { + info.AppendOpenFileAction(STDIN_FILENO, in_file_spec, true, false); + LLDB_LOG(log, "appended stdin open file action for {0}", in_file_spec); + } + + if (out_file_spec) { + info.AppendOpenFileAction(STDOUT_FILENO, out_file_spec, false, true); + LLDB_LOG(log, "appended stdout open file action for {0}", + out_file_spec); + } + + if (err_file_spec) { + info.AppendOpenFileAction(STDERR_FILENO, err_file_spec, false, true); + LLDB_LOG(log, "appended stderr open file action for {0}", + err_file_spec); + } + + if (default_to_use_pty) { + llvm::Error Err = info.SetUpPtyRedirection(); + LLDB_LOG_ERROR(log, std::move(Err), "SetUpPtyRedirection failed: {0}"); + } + } + } +} + +void Target::AddDummySignal(llvm::StringRef name, LazyBool pass, LazyBool notify, + LazyBool stop) { + if (name.empty()) + return; + // Don't add a signal if all the actions are trivial: + if (pass == eLazyBoolCalculate && notify == eLazyBoolCalculate + && stop == eLazyBoolCalculate) + return; + + auto& elem = m_dummy_signals[name]; + elem.pass = pass; + elem.notify = notify; + elem.stop = stop; +} + +bool Target::UpdateSignalFromDummy(UnixSignalsSP signals_sp, + const DummySignalElement &elem) { + if (!signals_sp) + return false; + + int32_t signo + = signals_sp->GetSignalNumberFromName(elem.first().str().c_str()); + if (signo == LLDB_INVALID_SIGNAL_NUMBER) + return false; + + if (elem.second.pass == eLazyBoolYes) + signals_sp->SetShouldSuppress(signo, false); + else if (elem.second.pass == eLazyBoolNo) + signals_sp->SetShouldSuppress(signo, true); + + if (elem.second.notify == eLazyBoolYes) + signals_sp->SetShouldNotify(signo, true); + else if (elem.second.notify == eLazyBoolNo) + signals_sp->SetShouldNotify(signo, false); + + if (elem.second.stop == eLazyBoolYes) + signals_sp->SetShouldStop(signo, true); + else if (elem.second.stop == eLazyBoolNo) + signals_sp->SetShouldStop(signo, false); + return true; +} + +bool Target::ResetSignalFromDummy(UnixSignalsSP signals_sp, + const DummySignalElement &elem) { + if (!signals_sp) + return false; + int32_t signo + = signals_sp->GetSignalNumberFromName(elem.first().str().c_str()); + if (signo == LLDB_INVALID_SIGNAL_NUMBER) + return false; + bool do_pass = elem.second.pass != eLazyBoolCalculate; + bool do_stop = elem.second.stop != eLazyBoolCalculate; + bool do_notify = elem.second.notify != eLazyBoolCalculate; + signals_sp->ResetSignal(signo, do_stop, do_notify, do_pass); + return true; +} + +void Target::UpdateSignalsFromDummy(UnixSignalsSP signals_sp, + StreamSP warning_stream_sp) { + if (!signals_sp) + return; + + for (const auto &elem : m_dummy_signals) { + if (!UpdateSignalFromDummy(signals_sp, elem)) + warning_stream_sp->Printf("Target signal '%s' not found in process\n", + elem.first().str().c_str()); + } +} + +void Target::ClearDummySignals(Args &signal_names) { + ProcessSP process_sp = GetProcessSP(); + // The simplest case, delete them all with no process to update. + if (signal_names.GetArgumentCount() == 0 && !process_sp) { + m_dummy_signals.clear(); + return; + } + UnixSignalsSP signals_sp; + if (process_sp) + signals_sp = process_sp->GetUnixSignals(); + + for (const Args::ArgEntry &entry : signal_names) { + const char *signal_name = entry.c_str(); + auto elem = m_dummy_signals.find(signal_name); + // If we didn't find it go on. + // FIXME: Should I pipe error handling through here? + if (elem == m_dummy_signals.end()) { + continue; + } + if (signals_sp) + ResetSignalFromDummy(signals_sp, *elem); + m_dummy_signals.erase(elem); + } +} + +void Target::PrintDummySignals(Stream &strm, Args &signal_args) { + strm.Printf("NAME PASS STOP NOTIFY\n"); + strm.Printf("=========== ======= ======= =======\n"); + + auto str_for_lazy = [] (LazyBool lazy) -> const char * { + switch (lazy) { + case eLazyBoolCalculate: return "not set"; + case eLazyBoolYes: return "true "; + case eLazyBoolNo: return "false "; + } + llvm_unreachable("Fully covered switch above!"); + }; + size_t num_args = signal_args.GetArgumentCount(); + for (const auto &elem : m_dummy_signals) { + bool print_it = false; + for (size_t idx = 0; idx < num_args; idx++) { + if (elem.first() == signal_args.GetArgumentAtIndex(idx)) { + print_it = true; + break; + } + } + if (print_it) { + strm.Printf("%-11s ", elem.first().str().c_str()); + strm.Printf("%s %s %s\n", str_for_lazy(elem.second.pass), + str_for_lazy(elem.second.stop), + str_for_lazy(elem.second.notify)); + } + } +} + +// Target::StopHook +Target::StopHook::StopHook(lldb::TargetSP target_sp, lldb::user_id_t uid) + : UserID(uid), m_target_sp(target_sp), m_specifier_sp(), + m_thread_spec_up() {} + +Target::StopHook::StopHook(const StopHook &rhs) + : UserID(rhs.GetID()), m_target_sp(rhs.m_target_sp), + m_specifier_sp(rhs.m_specifier_sp), m_thread_spec_up(), + m_active(rhs.m_active), m_auto_continue(rhs.m_auto_continue) { + if (rhs.m_thread_spec_up) + m_thread_spec_up = std::make_unique<ThreadSpec>(*rhs.m_thread_spec_up); +} + +void Target::StopHook::SetSpecifier(SymbolContextSpecifier *specifier) { + m_specifier_sp.reset(specifier); +} + +void Target::StopHook::SetThreadSpecifier(ThreadSpec *specifier) { + m_thread_spec_up.reset(specifier); +} + +bool Target::StopHook::ExecutionContextPasses(const ExecutionContext &exc_ctx) { + SymbolContextSpecifier *specifier = GetSpecifier(); + if (!specifier) + return true; + + bool will_run = true; + if (exc_ctx.GetFramePtr()) + will_run = GetSpecifier()->SymbolContextMatches( + exc_ctx.GetFramePtr()->GetSymbolContext(eSymbolContextEverything)); + if (will_run && GetThreadSpecifier() != nullptr) + will_run = + GetThreadSpecifier()->ThreadPassesBasicTests(exc_ctx.GetThreadRef()); + + return will_run; +} + +void Target::StopHook::GetDescription(Stream &s, + lldb::DescriptionLevel level) const { + + // For brief descriptions, only print the subclass description: + if (level == eDescriptionLevelBrief) { + GetSubclassDescription(s, level); + return; + } + + unsigned indent_level = s.GetIndentLevel(); + + s.SetIndentLevel(indent_level + 2); + + s.Printf("Hook: %" PRIu64 "\n", GetID()); + if (m_active) + s.Indent("State: enabled\n"); + else + s.Indent("State: disabled\n"); + + if (m_auto_continue) + s.Indent("AutoContinue on\n"); + + if (m_specifier_sp) { + s.Indent(); + s.PutCString("Specifier:\n"); + s.SetIndentLevel(indent_level + 4); + m_specifier_sp->GetDescription(&s, level); + s.SetIndentLevel(indent_level + 2); + } + + if (m_thread_spec_up) { + StreamString tmp; + s.Indent("Thread:\n"); + m_thread_spec_up->GetDescription(&tmp, level); + s.SetIndentLevel(indent_level + 4); + s.Indent(tmp.GetString()); + s.PutCString("\n"); + s.SetIndentLevel(indent_level + 2); + } + GetSubclassDescription(s, level); +} + +void Target::StopHookCommandLine::GetSubclassDescription( + Stream &s, lldb::DescriptionLevel level) const { + // The brief description just prints the first command. + if (level == eDescriptionLevelBrief) { + if (m_commands.GetSize() == 1) + s.PutCString(m_commands.GetStringAtIndex(0)); + return; + } + s.Indent("Commands: \n"); + s.SetIndentLevel(s.GetIndentLevel() + 4); + uint32_t num_commands = m_commands.GetSize(); + for (uint32_t i = 0; i < num_commands; i++) { + s.Indent(m_commands.GetStringAtIndex(i)); + s.PutCString("\n"); + } + s.SetIndentLevel(s.GetIndentLevel() - 4); +} + +// Target::StopHookCommandLine +void Target::StopHookCommandLine::SetActionFromString(const std::string &string) { + GetCommands().SplitIntoLines(string); +} + +void Target::StopHookCommandLine::SetActionFromStrings( + const std::vector<std::string> &strings) { + for (auto string : strings) + GetCommands().AppendString(string.c_str()); +} + +Target::StopHook::StopHookResult +Target::StopHookCommandLine::HandleStop(ExecutionContext &exc_ctx, + StreamSP output_sp) { + assert(exc_ctx.GetTargetPtr() && "Can't call PerformAction on a context " + "with no target"); + + if (!m_commands.GetSize()) + return StopHookResult::KeepStopped; + + CommandReturnObject result(false); + result.SetImmediateOutputStream(output_sp); + result.SetInteractive(false); + Debugger &debugger = exc_ctx.GetTargetPtr()->GetDebugger(); + CommandInterpreterRunOptions options; + options.SetStopOnContinue(true); + options.SetStopOnError(true); + options.SetEchoCommands(false); + options.SetPrintResults(true); + options.SetPrintErrors(true); + options.SetAddToHistory(false); + + // Force Async: + bool old_async = debugger.GetAsyncExecution(); + debugger.SetAsyncExecution(true); + debugger.GetCommandInterpreter().HandleCommands(GetCommands(), exc_ctx, + options, result); + debugger.SetAsyncExecution(old_async); + lldb::ReturnStatus status = result.GetStatus(); + if (status == eReturnStatusSuccessContinuingNoResult || + status == eReturnStatusSuccessContinuingResult) + return StopHookResult::AlreadyContinued; + return StopHookResult::KeepStopped; +} + +// Target::StopHookScripted +Status Target::StopHookScripted::SetScriptCallback( + std::string class_name, StructuredData::ObjectSP extra_args_sp) { + Status error; + + ScriptInterpreter *script_interp = + GetTarget()->GetDebugger().GetScriptInterpreter(); + if (!script_interp) { + error.SetErrorString("No script interpreter installed."); + return error; + } + + m_class_name = class_name; + m_extra_args.SetObjectSP(extra_args_sp); + + m_implementation_sp = script_interp->CreateScriptedStopHook( + GetTarget(), m_class_name.c_str(), m_extra_args, error); + + return error; +} + +Target::StopHook::StopHookResult +Target::StopHookScripted::HandleStop(ExecutionContext &exc_ctx, + StreamSP output_sp) { + assert(exc_ctx.GetTargetPtr() && "Can't call HandleStop on a context " + "with no target"); + + ScriptInterpreter *script_interp = + GetTarget()->GetDebugger().GetScriptInterpreter(); + if (!script_interp) + return StopHookResult::KeepStopped; + + bool should_stop = script_interp->ScriptedStopHookHandleStop( + m_implementation_sp, exc_ctx, output_sp); + + return should_stop ? StopHookResult::KeepStopped + : StopHookResult::RequestContinue; +} + +void Target::StopHookScripted::GetSubclassDescription( + Stream &s, lldb::DescriptionLevel level) const { + if (level == eDescriptionLevelBrief) { + s.PutCString(m_class_name); + return; + } + s.Indent("Class:"); + s.Printf("%s\n", m_class_name.c_str()); + + // Now print the extra args: + // FIXME: We should use StructuredData.GetDescription on the m_extra_args + // but that seems to rely on some printing plugin that doesn't exist. + if (!m_extra_args.IsValid()) + return; + StructuredData::ObjectSP object_sp = m_extra_args.GetObjectSP(); + if (!object_sp || !object_sp->IsValid()) + return; + + StructuredData::Dictionary *as_dict = object_sp->GetAsDictionary(); + if (!as_dict || !as_dict->IsValid()) + return; + + uint32_t num_keys = as_dict->GetSize(); + if (num_keys == 0) + return; + + s.Indent("Args:\n"); + s.SetIndentLevel(s.GetIndentLevel() + 4); + + auto print_one_element = [&s](llvm::StringRef key, + StructuredData::Object *object) { + s.Indent(); + s.Format("{0} : {1}\n", key, object->GetStringValue()); + return true; + }; + + as_dict->ForEach(print_one_element); + + s.SetIndentLevel(s.GetIndentLevel() - 4); +} + +static constexpr OptionEnumValueElement g_dynamic_value_types[] = { + { + eNoDynamicValues, + "no-dynamic-values", + "Don't calculate the dynamic type of values", + }, + { + eDynamicCanRunTarget, + "run-target", + "Calculate the dynamic type of values " + "even if you have to run the target.", + }, + { + eDynamicDontRunTarget, + "no-run-target", + "Calculate the dynamic type of values, but don't run the target.", + }, +}; + +OptionEnumValues lldb_private::GetDynamicValueTypes() { + return OptionEnumValues(g_dynamic_value_types); +} + +static constexpr OptionEnumValueElement g_inline_breakpoint_enums[] = { + { + eInlineBreakpointsNever, + "never", + "Never look for inline breakpoint locations (fastest). This setting " + "should only be used if you know that no inlining occurs in your" + "programs.", + }, + { + eInlineBreakpointsHeaders, + "headers", + "Only check for inline breakpoint locations when setting breakpoints " + "in header files, but not when setting breakpoint in implementation " + "source files (default).", + }, + { + eInlineBreakpointsAlways, + "always", + "Always look for inline breakpoint locations when setting file and " + "line breakpoints (slower but most accurate).", + }, +}; + +enum x86DisassemblyFlavor { + eX86DisFlavorDefault, + eX86DisFlavorIntel, + eX86DisFlavorATT +}; + +static constexpr OptionEnumValueElement g_x86_dis_flavor_value_types[] = { + { + eX86DisFlavorDefault, + "default", + "Disassembler default (currently att).", + }, + { + eX86DisFlavorIntel, + "intel", + "Intel disassembler flavor.", + }, + { + eX86DisFlavorATT, + "att", + "AT&T disassembler flavor.", + }, +}; + +static constexpr OptionEnumValueElement g_import_std_module_value_types[] = { + { + eImportStdModuleFalse, + "false", + "Never import the 'std' C++ module in the expression parser.", + }, + { + eImportStdModuleFallback, + "fallback", + "Retry evaluating expressions with an imported 'std' C++ module if they" + " failed to parse without the module. This allows evaluating more " + "complex expressions involving C++ standard library types." + }, + { + eImportStdModuleTrue, + "true", + "Always import the 'std' C++ module. This allows evaluating more " + "complex expressions involving C++ standard library types. This feature" + " is experimental." + }, +}; + +static constexpr OptionEnumValueElement + g_dynamic_class_info_helper_value_types[] = { + { + eDynamicClassInfoHelperAuto, + "auto", + "Automatically determine the most appropriate method for the " + "target OS.", + }, + {eDynamicClassInfoHelperRealizedClassesStruct, "RealizedClassesStruct", + "Prefer using the realized classes struct."}, + {eDynamicClassInfoHelperCopyRealizedClassList, "CopyRealizedClassList", + "Prefer using the CopyRealizedClassList API."}, + {eDynamicClassInfoHelperGetRealizedClassList, "GetRealizedClassList", + "Prefer using the GetRealizedClassList API."}, +}; + +static constexpr OptionEnumValueElement g_hex_immediate_style_values[] = { + { + Disassembler::eHexStyleC, + "c", + "C-style (0xffff).", + }, + { + Disassembler::eHexStyleAsm, + "asm", + "Asm-style (0ffffh).", + }, +}; + +static constexpr OptionEnumValueElement g_load_script_from_sym_file_values[] = { + { + eLoadScriptFromSymFileTrue, + "true", + "Load debug scripts inside symbol files", + }, + { + eLoadScriptFromSymFileFalse, + "false", + "Do not load debug scripts inside symbol files.", + }, + { + eLoadScriptFromSymFileWarn, + "warn", + "Warn about debug scripts inside symbol files but do not load them.", + }, +}; + +static constexpr OptionEnumValueElement g_load_cwd_lldbinit_values[] = { + { + eLoadCWDlldbinitTrue, + "true", + "Load .lldbinit files from current directory", + }, + { + eLoadCWDlldbinitFalse, + "false", + "Do not load .lldbinit files from current directory", + }, + { + eLoadCWDlldbinitWarn, + "warn", + "Warn about loading .lldbinit files from current directory", + }, +}; + +static constexpr OptionEnumValueElement g_memory_module_load_level_values[] = { + { + eMemoryModuleLoadLevelMinimal, + "minimal", + "Load minimal information when loading modules from memory. Currently " + "this setting loads sections only.", + }, + { + eMemoryModuleLoadLevelPartial, + "partial", + "Load partial information when loading modules from memory. Currently " + "this setting loads sections and function bounds.", + }, + { + eMemoryModuleLoadLevelComplete, + "complete", + "Load complete information when loading modules from memory. Currently " + "this setting loads sections and all symbols.", + }, +}; + +#define LLDB_PROPERTIES_target +#include "TargetProperties.inc" + +enum { +#define LLDB_PROPERTIES_target +#include "TargetPropertiesEnum.inc" + ePropertyExperimental, +}; + +class TargetOptionValueProperties + : public Cloneable<TargetOptionValueProperties, OptionValueProperties> { +public: + TargetOptionValueProperties(llvm::StringRef name) : Cloneable(name) {} + + const Property * + GetPropertyAtIndex(size_t idx, + const ExecutionContext *exe_ctx = nullptr) const override { + // When getting the value for a key from the target options, we will always + // try and grab the setting from the current target if there is one. Else + // we just use the one from this instance. + if (exe_ctx) { + Target *target = exe_ctx->GetTargetPtr(); + if (target) { + TargetOptionValueProperties *target_properties = + static_cast<TargetOptionValueProperties *>( + target->GetValueProperties().get()); + if (this != target_properties) + return target_properties->ProtectedGetPropertyAtIndex(idx); + } + } + return ProtectedGetPropertyAtIndex(idx); + } +}; + +// TargetProperties +#define LLDB_PROPERTIES_target_experimental +#include "TargetProperties.inc" + +enum { +#define LLDB_PROPERTIES_target_experimental +#include "TargetPropertiesEnum.inc" +}; + +class TargetExperimentalOptionValueProperties + : public Cloneable<TargetExperimentalOptionValueProperties, + OptionValueProperties> { +public: + TargetExperimentalOptionValueProperties() + : Cloneable(Properties::GetExperimentalSettingsName()) {} +}; + +TargetExperimentalProperties::TargetExperimentalProperties() + : Properties(OptionValuePropertiesSP( + new TargetExperimentalOptionValueProperties())) { + m_collection_sp->Initialize(g_target_experimental_properties); +} + +// TargetProperties +TargetProperties::TargetProperties(Target *target) + : Properties(), m_launch_info(), m_target(target) { + if (target) { + m_collection_sp = + OptionValueProperties::CreateLocalCopy(Target::GetGlobalProperties()); + + // Set callbacks to update launch_info whenever "settins set" updated any + // of these properties + m_collection_sp->SetValueChangedCallback( + ePropertyArg0, [this] { Arg0ValueChangedCallback(); }); + m_collection_sp->SetValueChangedCallback( + ePropertyRunArgs, [this] { RunArgsValueChangedCallback(); }); + m_collection_sp->SetValueChangedCallback( + ePropertyEnvVars, [this] { EnvVarsValueChangedCallback(); }); + m_collection_sp->SetValueChangedCallback( + ePropertyUnsetEnvVars, [this] { EnvVarsValueChangedCallback(); }); + m_collection_sp->SetValueChangedCallback( + ePropertyInheritEnv, [this] { EnvVarsValueChangedCallback(); }); + m_collection_sp->SetValueChangedCallback( + ePropertyInputPath, [this] { InputPathValueChangedCallback(); }); + m_collection_sp->SetValueChangedCallback( + ePropertyOutputPath, [this] { OutputPathValueChangedCallback(); }); + m_collection_sp->SetValueChangedCallback( + ePropertyErrorPath, [this] { ErrorPathValueChangedCallback(); }); + m_collection_sp->SetValueChangedCallback(ePropertyDetachOnError, [this] { + DetachOnErrorValueChangedCallback(); + }); + m_collection_sp->SetValueChangedCallback( + ePropertyDisableASLR, [this] { DisableASLRValueChangedCallback(); }); + m_collection_sp->SetValueChangedCallback( + ePropertyInheritTCC, [this] { InheritTCCValueChangedCallback(); }); + m_collection_sp->SetValueChangedCallback( + ePropertyDisableSTDIO, [this] { DisableSTDIOValueChangedCallback(); }); + + m_collection_sp->SetValueChangedCallback( + ePropertySaveObjectsDir, [this] { CheckJITObjectsDir(); }); + m_experimental_properties_up = + std::make_unique<TargetExperimentalProperties>(); + m_collection_sp->AppendProperty( + Properties::GetExperimentalSettingsName(), + "Experimental settings - setting these won't produce " + "errors if the setting is not present.", + true, m_experimental_properties_up->GetValueProperties()); + } else { + m_collection_sp = std::make_shared<TargetOptionValueProperties>("target"); + m_collection_sp->Initialize(g_target_properties); + m_experimental_properties_up = + std::make_unique<TargetExperimentalProperties>(); + m_collection_sp->AppendProperty( + Properties::GetExperimentalSettingsName(), + "Experimental settings - setting these won't produce " + "errors if the setting is not present.", + true, m_experimental_properties_up->GetValueProperties()); + m_collection_sp->AppendProperty( + "process", "Settings specific to processes.", true, + Process::GetGlobalProperties().GetValueProperties()); + m_collection_sp->SetValueChangedCallback( + ePropertySaveObjectsDir, [this] { CheckJITObjectsDir(); }); + } +} + +TargetProperties::~TargetProperties() = default; + +void TargetProperties::UpdateLaunchInfoFromProperties() { + Arg0ValueChangedCallback(); + RunArgsValueChangedCallback(); + EnvVarsValueChangedCallback(); + InputPathValueChangedCallback(); + OutputPathValueChangedCallback(); + ErrorPathValueChangedCallback(); + DetachOnErrorValueChangedCallback(); + DisableASLRValueChangedCallback(); + InheritTCCValueChangedCallback(); + DisableSTDIOValueChangedCallback(); +} + +std::optional<bool> TargetProperties::GetExperimentalPropertyValue( + size_t prop_idx, ExecutionContext *exe_ctx) const { + const Property *exp_property = + m_collection_sp->GetPropertyAtIndex(ePropertyExperimental, exe_ctx); + OptionValueProperties *exp_values = + exp_property->GetValue()->GetAsProperties(); + if (exp_values) + return exp_values->GetPropertyAtIndexAs<bool>(prop_idx, exe_ctx); + return std::nullopt; +} + +bool TargetProperties::GetInjectLocalVariables( + ExecutionContext *exe_ctx) const { + return GetExperimentalPropertyValue(ePropertyInjectLocalVars, exe_ctx) + .value_or(true); +} + +ArchSpec TargetProperties::GetDefaultArchitecture() const { + const uint32_t idx = ePropertyDefaultArch; + return GetPropertyAtIndexAs<ArchSpec>(idx, {}); +} + +void TargetProperties::SetDefaultArchitecture(const ArchSpec &arch) { + const uint32_t idx = ePropertyDefaultArch; + SetPropertyAtIndex(idx, arch); +} + +bool TargetProperties::GetMoveToNearestCode() const { + const uint32_t idx = ePropertyMoveToNearestCode; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +lldb::DynamicValueType TargetProperties::GetPreferDynamicValue() const { + const uint32_t idx = ePropertyPreferDynamic; + return GetPropertyAtIndexAs<lldb::DynamicValueType>( + idx, static_cast<lldb::DynamicValueType>( + g_target_properties[idx].default_uint_value)); +} + +bool TargetProperties::SetPreferDynamicValue(lldb::DynamicValueType d) { + const uint32_t idx = ePropertyPreferDynamic; + return SetPropertyAtIndex(idx, d); +} + +bool TargetProperties::GetPreloadSymbols() const { + if (INTERRUPT_REQUESTED(m_target->GetDebugger(), + "Interrupted checking preload symbols")) { + return false; + } + const uint32_t idx = ePropertyPreloadSymbols; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +void TargetProperties::SetPreloadSymbols(bool b) { + const uint32_t idx = ePropertyPreloadSymbols; + SetPropertyAtIndex(idx, b); +} + +bool TargetProperties::GetDisableASLR() const { + const uint32_t idx = ePropertyDisableASLR; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +void TargetProperties::SetDisableASLR(bool b) { + const uint32_t idx = ePropertyDisableASLR; + SetPropertyAtIndex(idx, b); +} + +bool TargetProperties::GetInheritTCC() const { + const uint32_t idx = ePropertyInheritTCC; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +void TargetProperties::SetInheritTCC(bool b) { + const uint32_t idx = ePropertyInheritTCC; + SetPropertyAtIndex(idx, b); +} + +bool TargetProperties::GetDetachOnError() const { + const uint32_t idx = ePropertyDetachOnError; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +void TargetProperties::SetDetachOnError(bool b) { + const uint32_t idx = ePropertyDetachOnError; + SetPropertyAtIndex(idx, b); +} + +bool TargetProperties::GetDisableSTDIO() const { + const uint32_t idx = ePropertyDisableSTDIO; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +void TargetProperties::SetDisableSTDIO(bool b) { + const uint32_t idx = ePropertyDisableSTDIO; + SetPropertyAtIndex(idx, b); +} + +const char *TargetProperties::GetDisassemblyFlavor() const { + const uint32_t idx = ePropertyDisassemblyFlavor; + const char *return_value; + + x86DisassemblyFlavor flavor_value = + GetPropertyAtIndexAs<x86DisassemblyFlavor>( + idx, static_cast<x86DisassemblyFlavor>( + g_target_properties[idx].default_uint_value)); + + return_value = g_x86_dis_flavor_value_types[flavor_value].string_value; + return return_value; +} + +InlineStrategy TargetProperties::GetInlineStrategy() const { + const uint32_t idx = ePropertyInlineStrategy; + return GetPropertyAtIndexAs<InlineStrategy>( + idx, + static_cast<InlineStrategy>(g_target_properties[idx].default_uint_value)); +} + +llvm::StringRef TargetProperties::GetArg0() const { + const uint32_t idx = ePropertyArg0; + return GetPropertyAtIndexAs<llvm::StringRef>( + idx, g_target_properties[idx].default_cstr_value); +} + +void TargetProperties::SetArg0(llvm::StringRef arg) { + const uint32_t idx = ePropertyArg0; + SetPropertyAtIndex(idx, arg); + m_launch_info.SetArg0(arg); +} + +bool TargetProperties::GetRunArguments(Args &args) const { + const uint32_t idx = ePropertyRunArgs; + return m_collection_sp->GetPropertyAtIndexAsArgs(idx, args); +} + +void TargetProperties::SetRunArguments(const Args &args) { + const uint32_t idx = ePropertyRunArgs; + m_collection_sp->SetPropertyAtIndexFromArgs(idx, args); + m_launch_info.GetArguments() = args; +} + +Environment TargetProperties::ComputeEnvironment() const { + Environment env; + + if (m_target && + GetPropertyAtIndexAs<bool>( + ePropertyInheritEnv, + g_target_properties[ePropertyInheritEnv].default_uint_value != 0)) { + if (auto platform_sp = m_target->GetPlatform()) { + Environment platform_env = platform_sp->GetEnvironment(); + for (const auto &KV : platform_env) + env[KV.first()] = KV.second; + } + } + + Args property_unset_env; + m_collection_sp->GetPropertyAtIndexAsArgs(ePropertyUnsetEnvVars, + property_unset_env); + for (const auto &var : property_unset_env) + env.erase(var.ref()); + + Args property_env; + m_collection_sp->GetPropertyAtIndexAsArgs(ePropertyEnvVars, property_env); + for (const auto &KV : Environment(property_env)) + env[KV.first()] = KV.second; + + return env; +} + +Environment TargetProperties::GetEnvironment() const { + return ComputeEnvironment(); +} + +Environment TargetProperties::GetInheritedEnvironment() const { + Environment environment; + + if (m_target == nullptr) + return environment; + + if (!GetPropertyAtIndexAs<bool>( + ePropertyInheritEnv, + g_target_properties[ePropertyInheritEnv].default_uint_value != 0)) + return environment; + + PlatformSP platform_sp = m_target->GetPlatform(); + if (platform_sp == nullptr) + return environment; + + Environment platform_environment = platform_sp->GetEnvironment(); + for (const auto &KV : platform_environment) + environment[KV.first()] = KV.second; + + Args property_unset_environment; + m_collection_sp->GetPropertyAtIndexAsArgs(ePropertyUnsetEnvVars, + property_unset_environment); + for (const auto &var : property_unset_environment) + environment.erase(var.ref()); + + return environment; +} + +Environment TargetProperties::GetTargetEnvironment() const { + Args property_environment; + m_collection_sp->GetPropertyAtIndexAsArgs(ePropertyEnvVars, + property_environment); + Environment environment; + for (const auto &KV : Environment(property_environment)) + environment[KV.first()] = KV.second; + + return environment; +} + +void TargetProperties::SetEnvironment(Environment env) { + // TODO: Get rid of the Args intermediate step + const uint32_t idx = ePropertyEnvVars; + m_collection_sp->SetPropertyAtIndexFromArgs(idx, Args(env)); +} + +bool TargetProperties::GetSkipPrologue() const { + const uint32_t idx = ePropertySkipPrologue; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +PathMappingList &TargetProperties::GetSourcePathMap() const { + const uint32_t idx = ePropertySourceMap; + OptionValuePathMappings *option_value = + m_collection_sp->GetPropertyAtIndexAsOptionValuePathMappings(idx); + assert(option_value); + return option_value->GetCurrentValue(); +} + +bool TargetProperties::GetAutoSourceMapRelative() const { + const uint32_t idx = ePropertyAutoSourceMapRelative; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +void TargetProperties::AppendExecutableSearchPaths(const FileSpec &dir) { + const uint32_t idx = ePropertyExecutableSearchPaths; + OptionValueFileSpecList *option_value = + m_collection_sp->GetPropertyAtIndexAsOptionValueFileSpecList(idx); + assert(option_value); + option_value->AppendCurrentValue(dir); +} + +FileSpecList TargetProperties::GetExecutableSearchPaths() { + const uint32_t idx = ePropertyExecutableSearchPaths; + return GetPropertyAtIndexAs<FileSpecList>(idx, {}); +} + +FileSpecList TargetProperties::GetDebugFileSearchPaths() { + const uint32_t idx = ePropertyDebugFileSearchPaths; + return GetPropertyAtIndexAs<FileSpecList>(idx, {}); +} + +FileSpecList TargetProperties::GetClangModuleSearchPaths() { + const uint32_t idx = ePropertyClangModuleSearchPaths; + return GetPropertyAtIndexAs<FileSpecList>(idx, {}); +} + +bool TargetProperties::GetEnableAutoImportClangModules() const { + const uint32_t idx = ePropertyAutoImportClangModules; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +ImportStdModule TargetProperties::GetImportStdModule() const { + const uint32_t idx = ePropertyImportStdModule; + return GetPropertyAtIndexAs<ImportStdModule>( + idx, static_cast<ImportStdModule>( + g_target_properties[idx].default_uint_value)); +} + +DynamicClassInfoHelper TargetProperties::GetDynamicClassInfoHelper() const { + const uint32_t idx = ePropertyDynamicClassInfoHelper; + return GetPropertyAtIndexAs<DynamicClassInfoHelper>( + idx, static_cast<DynamicClassInfoHelper>( + g_target_properties[idx].default_uint_value)); +} + +bool TargetProperties::GetEnableAutoApplyFixIts() const { + const uint32_t idx = ePropertyAutoApplyFixIts; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +uint64_t TargetProperties::GetNumberOfRetriesWithFixits() const { + const uint32_t idx = ePropertyRetriesWithFixIts; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_target_properties[idx].default_uint_value); +} + +bool TargetProperties::GetEnableNotifyAboutFixIts() const { + const uint32_t idx = ePropertyNotifyAboutFixIts; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +FileSpec TargetProperties::GetSaveJITObjectsDir() const { + const uint32_t idx = ePropertySaveObjectsDir; + return GetPropertyAtIndexAs<FileSpec>(idx, {}); +} + +void TargetProperties::CheckJITObjectsDir() { + FileSpec new_dir = GetSaveJITObjectsDir(); + if (!new_dir) + return; + + const FileSystem &instance = FileSystem::Instance(); + bool exists = instance.Exists(new_dir); + bool is_directory = instance.IsDirectory(new_dir); + std::string path = new_dir.GetPath(true); + bool writable = llvm::sys::fs::can_write(path); + if (exists && is_directory && writable) + return; + + m_collection_sp->GetPropertyAtIndex(ePropertySaveObjectsDir) + ->GetValue() + ->Clear(); + + std::string buffer; + llvm::raw_string_ostream os(buffer); + os << "JIT object dir '" << path << "' "; + if (!exists) + os << "does not exist"; + else if (!is_directory) + os << "is not a directory"; + else if (!writable) + os << "is not writable"; + + std::optional<lldb::user_id_t> debugger_id; + if (m_target) + debugger_id = m_target->GetDebugger().GetID(); + Debugger::ReportError(os.str(), debugger_id); +} + +bool TargetProperties::GetEnableSyntheticValue() const { + const uint32_t idx = ePropertyEnableSynthetic; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +bool TargetProperties::ShowHexVariableValuesWithLeadingZeroes() const { + const uint32_t idx = ePropertyShowHexVariableValuesWithLeadingZeroes; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +uint32_t TargetProperties::GetMaxZeroPaddingInFloatFormat() const { + const uint32_t idx = ePropertyMaxZeroPaddingInFloatFormat; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_target_properties[idx].default_uint_value); +} + +uint32_t TargetProperties::GetMaximumNumberOfChildrenToDisplay() const { + const uint32_t idx = ePropertyMaxChildrenCount; + return GetPropertyAtIndexAs<int64_t>( + idx, g_target_properties[idx].default_uint_value); +} + +std::pair<uint32_t, bool> +TargetProperties::GetMaximumDepthOfChildrenToDisplay() const { + const uint32_t idx = ePropertyMaxChildrenDepth; + auto *option_value = + m_collection_sp->GetPropertyAtIndexAsOptionValueUInt64(idx); + bool is_default = !option_value->OptionWasSet(); + return {option_value->GetCurrentValue(), is_default}; +} + +uint32_t TargetProperties::GetMaximumSizeOfStringSummary() const { + const uint32_t idx = ePropertyMaxSummaryLength; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_target_properties[idx].default_uint_value); +} + +uint32_t TargetProperties::GetMaximumMemReadSize() const { + const uint32_t idx = ePropertyMaxMemReadSize; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_target_properties[idx].default_uint_value); +} + +FileSpec TargetProperties::GetStandardInputPath() const { + const uint32_t idx = ePropertyInputPath; + return GetPropertyAtIndexAs<FileSpec>(idx, {}); +} + +void TargetProperties::SetStandardInputPath(llvm::StringRef path) { + const uint32_t idx = ePropertyInputPath; + SetPropertyAtIndex(idx, path); +} + +FileSpec TargetProperties::GetStandardOutputPath() const { + const uint32_t idx = ePropertyOutputPath; + return GetPropertyAtIndexAs<FileSpec>(idx, {}); +} + +void TargetProperties::SetStandardOutputPath(llvm::StringRef path) { + const uint32_t idx = ePropertyOutputPath; + SetPropertyAtIndex(idx, path); +} + +FileSpec TargetProperties::GetStandardErrorPath() const { + const uint32_t idx = ePropertyErrorPath; + return GetPropertyAtIndexAs<FileSpec>(idx, {}); +} + +void TargetProperties::SetStandardErrorPath(llvm::StringRef path) { + const uint32_t idx = ePropertyErrorPath; + SetPropertyAtIndex(idx, path); +} + +SourceLanguage TargetProperties::GetLanguage() const { + const uint32_t idx = ePropertyLanguage; + return {GetPropertyAtIndexAs<LanguageType>(idx, {})}; +} + +llvm::StringRef TargetProperties::GetExpressionPrefixContents() { + const uint32_t idx = ePropertyExprPrefix; + OptionValueFileSpec *file = + m_collection_sp->GetPropertyAtIndexAsOptionValueFileSpec(idx); + if (file) { + DataBufferSP data_sp(file->GetFileContents()); + if (data_sp) + return llvm::StringRef( + reinterpret_cast<const char *>(data_sp->GetBytes()), + data_sp->GetByteSize()); + } + return ""; +} + +uint64_t TargetProperties::GetExprErrorLimit() const { + const uint32_t idx = ePropertyExprErrorLimit; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_target_properties[idx].default_uint_value); +} + +uint64_t TargetProperties::GetExprAllocAddress() const { + const uint32_t idx = ePropertyExprAllocAddress; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_target_properties[idx].default_uint_value); +} + +uint64_t TargetProperties::GetExprAllocSize() const { + const uint32_t idx = ePropertyExprAllocSize; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_target_properties[idx].default_uint_value); +} + +uint64_t TargetProperties::GetExprAllocAlign() const { + const uint32_t idx = ePropertyExprAllocAlign; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_target_properties[idx].default_uint_value); +} + +bool TargetProperties::GetBreakpointsConsultPlatformAvoidList() { + const uint32_t idx = ePropertyBreakpointUseAvoidList; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +bool TargetProperties::GetUseHexImmediates() const { + const uint32_t idx = ePropertyUseHexImmediates; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +bool TargetProperties::GetUseFastStepping() const { + const uint32_t idx = ePropertyUseFastStepping; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +bool TargetProperties::GetDisplayExpressionsInCrashlogs() const { + const uint32_t idx = ePropertyDisplayExpressionsInCrashlogs; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +LoadScriptFromSymFile TargetProperties::GetLoadScriptFromSymbolFile() const { + const uint32_t idx = ePropertyLoadScriptFromSymbolFile; + return GetPropertyAtIndexAs<LoadScriptFromSymFile>( + idx, static_cast<LoadScriptFromSymFile>( + g_target_properties[idx].default_uint_value)); +} + +LoadCWDlldbinitFile TargetProperties::GetLoadCWDlldbinitFile() const { + const uint32_t idx = ePropertyLoadCWDlldbinitFile; + return GetPropertyAtIndexAs<LoadCWDlldbinitFile>( + idx, static_cast<LoadCWDlldbinitFile>( + g_target_properties[idx].default_uint_value)); +} + +Disassembler::HexImmediateStyle TargetProperties::GetHexImmediateStyle() const { + const uint32_t idx = ePropertyHexImmediateStyle; + return GetPropertyAtIndexAs<Disassembler::HexImmediateStyle>( + idx, static_cast<Disassembler::HexImmediateStyle>( + g_target_properties[idx].default_uint_value)); +} + +MemoryModuleLoadLevel TargetProperties::GetMemoryModuleLoadLevel() const { + const uint32_t idx = ePropertyMemoryModuleLoadLevel; + return GetPropertyAtIndexAs<MemoryModuleLoadLevel>( + idx, static_cast<MemoryModuleLoadLevel>( + g_target_properties[idx].default_uint_value)); +} + +bool TargetProperties::GetUserSpecifiedTrapHandlerNames(Args &args) const { + const uint32_t idx = ePropertyTrapHandlerNames; + return m_collection_sp->GetPropertyAtIndexAsArgs(idx, args); +} + +void TargetProperties::SetUserSpecifiedTrapHandlerNames(const Args &args) { + const uint32_t idx = ePropertyTrapHandlerNames; + m_collection_sp->SetPropertyAtIndexFromArgs(idx, args); +} + +bool TargetProperties::GetDisplayRuntimeSupportValues() const { + const uint32_t idx = ePropertyDisplayRuntimeSupportValues; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +void TargetProperties::SetDisplayRuntimeSupportValues(bool b) { + const uint32_t idx = ePropertyDisplayRuntimeSupportValues; + SetPropertyAtIndex(idx, b); +} + +bool TargetProperties::GetDisplayRecognizedArguments() const { + const uint32_t idx = ePropertyDisplayRecognizedArguments; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +void TargetProperties::SetDisplayRecognizedArguments(bool b) { + const uint32_t idx = ePropertyDisplayRecognizedArguments; + SetPropertyAtIndex(idx, b); +} + +const ProcessLaunchInfo &TargetProperties::GetProcessLaunchInfo() const { + return m_launch_info; +} + +void TargetProperties::SetProcessLaunchInfo( + const ProcessLaunchInfo &launch_info) { + m_launch_info = launch_info; + SetArg0(launch_info.GetArg0()); + SetRunArguments(launch_info.GetArguments()); + SetEnvironment(launch_info.GetEnvironment()); + const FileAction *input_file_action = + launch_info.GetFileActionForFD(STDIN_FILENO); + if (input_file_action) { + SetStandardInputPath(input_file_action->GetPath()); + } + const FileAction *output_file_action = + launch_info.GetFileActionForFD(STDOUT_FILENO); + if (output_file_action) { + SetStandardOutputPath(output_file_action->GetPath()); + } + const FileAction *error_file_action = + launch_info.GetFileActionForFD(STDERR_FILENO); + if (error_file_action) { + SetStandardErrorPath(error_file_action->GetPath()); + } + SetDetachOnError(launch_info.GetFlags().Test(lldb::eLaunchFlagDetachOnError)); + SetDisableASLR(launch_info.GetFlags().Test(lldb::eLaunchFlagDisableASLR)); + SetInheritTCC( + launch_info.GetFlags().Test(lldb::eLaunchFlagInheritTCCFromParent)); + SetDisableSTDIO(launch_info.GetFlags().Test(lldb::eLaunchFlagDisableSTDIO)); +} + +bool TargetProperties::GetRequireHardwareBreakpoints() const { + const uint32_t idx = ePropertyRequireHardwareBreakpoints; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +void TargetProperties::SetRequireHardwareBreakpoints(bool b) { + const uint32_t idx = ePropertyRequireHardwareBreakpoints; + m_collection_sp->SetPropertyAtIndex(idx, b); +} + +bool TargetProperties::GetAutoInstallMainExecutable() const { + const uint32_t idx = ePropertyAutoInstallMainExecutable; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +void TargetProperties::Arg0ValueChangedCallback() { + m_launch_info.SetArg0(GetArg0()); +} + +void TargetProperties::RunArgsValueChangedCallback() { + Args args; + if (GetRunArguments(args)) + m_launch_info.GetArguments() = args; +} + +void TargetProperties::EnvVarsValueChangedCallback() { + m_launch_info.GetEnvironment() = ComputeEnvironment(); +} + +void TargetProperties::InputPathValueChangedCallback() { + m_launch_info.AppendOpenFileAction(STDIN_FILENO, GetStandardInputPath(), true, + false); +} + +void TargetProperties::OutputPathValueChangedCallback() { + m_launch_info.AppendOpenFileAction(STDOUT_FILENO, GetStandardOutputPath(), + false, true); +} + +void TargetProperties::ErrorPathValueChangedCallback() { + m_launch_info.AppendOpenFileAction(STDERR_FILENO, GetStandardErrorPath(), + false, true); +} + +void TargetProperties::DetachOnErrorValueChangedCallback() { + if (GetDetachOnError()) + m_launch_info.GetFlags().Set(lldb::eLaunchFlagDetachOnError); + else + m_launch_info.GetFlags().Clear(lldb::eLaunchFlagDetachOnError); +} + +void TargetProperties::DisableASLRValueChangedCallback() { + if (GetDisableASLR()) + m_launch_info.GetFlags().Set(lldb::eLaunchFlagDisableASLR); + else + m_launch_info.GetFlags().Clear(lldb::eLaunchFlagDisableASLR); +} + +void TargetProperties::InheritTCCValueChangedCallback() { + if (GetInheritTCC()) + m_launch_info.GetFlags().Set(lldb::eLaunchFlagInheritTCCFromParent); + else + m_launch_info.GetFlags().Clear(lldb::eLaunchFlagInheritTCCFromParent); +} + +void TargetProperties::DisableSTDIOValueChangedCallback() { + if (GetDisableSTDIO()) + m_launch_info.GetFlags().Set(lldb::eLaunchFlagDisableSTDIO); + else + m_launch_info.GetFlags().Clear(lldb::eLaunchFlagDisableSTDIO); +} + +bool TargetProperties::GetDebugUtilityExpression() const { + const uint32_t idx = ePropertyDebugUtilityExpression; + return GetPropertyAtIndexAs<bool>( + idx, g_target_properties[idx].default_uint_value != 0); +} + +void TargetProperties::SetDebugUtilityExpression(bool debug) { + const uint32_t idx = ePropertyDebugUtilityExpression; + SetPropertyAtIndex(idx, debug); +} + +// Target::TargetEventData + +Target::TargetEventData::TargetEventData(const lldb::TargetSP &target_sp) + : EventData(), m_target_sp(target_sp), m_module_list() {} + +Target::TargetEventData::TargetEventData(const lldb::TargetSP &target_sp, + const ModuleList &module_list) + : EventData(), m_target_sp(target_sp), m_module_list(module_list) {} + +Target::TargetEventData::~TargetEventData() = default; + +llvm::StringRef Target::TargetEventData::GetFlavorString() { + return "Target::TargetEventData"; +} + +void Target::TargetEventData::Dump(Stream *s) const { + for (size_t i = 0; i < m_module_list.GetSize(); ++i) { + if (i != 0) + *s << ", "; + m_module_list.GetModuleAtIndex(i)->GetDescription( + s->AsRawOstream(), lldb::eDescriptionLevelBrief); + } +} + +const Target::TargetEventData * +Target::TargetEventData::GetEventDataFromEvent(const Event *event_ptr) { + if (event_ptr) { + const EventData *event_data = event_ptr->GetData(); + if (event_data && + event_data->GetFlavor() == TargetEventData::GetFlavorString()) + return static_cast<const TargetEventData *>(event_ptr->GetData()); + } + return nullptr; +} + +TargetSP Target::TargetEventData::GetTargetFromEvent(const Event *event_ptr) { + TargetSP target_sp; + const TargetEventData *event_data = GetEventDataFromEvent(event_ptr); + if (event_data) + target_sp = event_data->m_target_sp; + return target_sp; +} + +ModuleList +Target::TargetEventData::GetModuleListFromEvent(const Event *event_ptr) { + ModuleList module_list; + const TargetEventData *event_data = GetEventDataFromEvent(event_ptr); + if (event_data) + module_list = event_data->m_module_list; + return module_list; +} + +std::recursive_mutex &Target::GetAPIMutex() { + if (GetProcessSP() && GetProcessSP()->CurrentThreadIsPrivateStateThread()) + return m_private_mutex; + else + return m_mutex; +} + +/// Get metrics associated with this target in JSON format. +llvm::json::Value +Target::ReportStatistics(const lldb_private::StatisticsOptions &options) { + return m_stats.ToJSON(*this, options); +} diff --git a/contrib/llvm-project/lldb/source/Target/TargetList.cpp b/contrib/llvm-project/lldb/source/Target/TargetList.cpp new file mode 100644 index 000000000000..10467753666f --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/TargetList.cpp @@ -0,0 +1,582 @@ +//===-- TargetList.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 "lldb/Target/TargetList.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Host/Host.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/OptionGroupPlatform.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Target/Platform.h" +#include "lldb/Target/Process.h" +#include "lldb/Utility/Broadcaster.h" +#include "lldb/Utility/Event.h" +#include "lldb/Utility/State.h" +#include "lldb/Utility/TildeExpressionResolver.h" +#include "lldb/Utility/Timer.h" + +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/FileSystem.h" + +using namespace lldb; +using namespace lldb_private; + +llvm::StringRef TargetList::GetStaticBroadcasterClass() { + static constexpr llvm::StringLiteral class_name("lldb.targetList"); + return class_name; +} + +// TargetList constructor +TargetList::TargetList(Debugger &debugger) + : Broadcaster(debugger.GetBroadcasterManager(), + TargetList::GetStaticBroadcasterClass().str()), + m_target_list(), m_target_list_mutex(), m_selected_target_idx(0) { + CheckInWithManager(); +} + +Status TargetList::CreateTarget(Debugger &debugger, + llvm::StringRef user_exe_path, + llvm::StringRef triple_str, + LoadDependentFiles load_dependent_files, + const OptionGroupPlatform *platform_options, + TargetSP &target_sp) { + std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex); + auto result = TargetList::CreateTargetInternal( + debugger, user_exe_path, triple_str, load_dependent_files, + platform_options, target_sp); + + if (target_sp && result.Success()) + AddTargetInternal(target_sp, /*do_select*/ true); + return result; +} + +Status TargetList::CreateTarget(Debugger &debugger, + llvm::StringRef user_exe_path, + const ArchSpec &specified_arch, + LoadDependentFiles load_dependent_files, + PlatformSP &platform_sp, TargetSP &target_sp) { + std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex); + auto result = TargetList::CreateTargetInternal( + debugger, user_exe_path, specified_arch, load_dependent_files, + platform_sp, target_sp); + + if (target_sp && result.Success()) + AddTargetInternal(target_sp, /*do_select*/ true); + return result; +} + +Status TargetList::CreateTargetInternal( + Debugger &debugger, llvm::StringRef user_exe_path, + llvm::StringRef triple_str, LoadDependentFiles load_dependent_files, + const OptionGroupPlatform *platform_options, TargetSP &target_sp) { + Status error; + + PlatformList &platform_list = debugger.GetPlatformList(); + // Let's start by looking at the selected platform. + PlatformSP platform_sp = platform_list.GetSelectedPlatform(); + + // This variable corresponds to the architecture specified by the triple + // string. If that string was empty the currently selected platform will + // determine the architecture. + const ArchSpec arch(triple_str); + if (!triple_str.empty() && !arch.IsValid()) { + error.SetErrorStringWithFormat("invalid triple '%s'", + triple_str.str().c_str()); + return error; + } + + ArchSpec platform_arch(arch); + + // Create a new platform if a platform was specified in the platform options + // and doesn't match the selected platform. + if (platform_options && platform_options->PlatformWasSpecified() && + !platform_options->PlatformMatches(platform_sp)) { + const bool select_platform = true; + platform_sp = platform_options->CreatePlatformWithOptions( + debugger.GetCommandInterpreter(), arch, select_platform, error, + platform_arch); + if (!platform_sp) + return error; + } + + bool prefer_platform_arch = false; + auto update_platform_arch = [&](const ArchSpec &module_arch) { + // If the OS or vendor weren't specified, then adopt the module's + // architecture so that the platform matching can be more accurate. + if (!platform_arch.TripleOSWasSpecified() || + !platform_arch.TripleVendorWasSpecified()) { + prefer_platform_arch = true; + platform_arch = module_arch; + } + }; + + if (!user_exe_path.empty()) { + ModuleSpec module_spec(FileSpec(user_exe_path, FileSpec::Style::native)); + FileSystem::Instance().Resolve(module_spec.GetFileSpec()); + + // Try to resolve the exe based on PATH and/or platform-specific suffixes, + // but only if using the host platform. + if (platform_sp->IsHost() && + !FileSystem::Instance().Exists(module_spec.GetFileSpec())) + FileSystem::Instance().ResolveExecutableLocation( + module_spec.GetFileSpec()); + + // Resolve the executable in case we are given a path to a application + // bundle like a .app bundle on MacOSX. + Host::ResolveExecutableInBundle(module_spec.GetFileSpec()); + + lldb::offset_t file_offset = 0; + lldb::offset_t file_size = 0; + ModuleSpecList module_specs; + const size_t num_specs = ObjectFile::GetModuleSpecifications( + module_spec.GetFileSpec(), file_offset, file_size, module_specs); + + if (num_specs > 0) { + ModuleSpec matching_module_spec; + + if (num_specs == 1) { + if (module_specs.GetModuleSpecAtIndex(0, matching_module_spec)) { + if (platform_arch.IsValid()) { + if (platform_arch.IsCompatibleMatch( + matching_module_spec.GetArchitecture())) { + // If the OS or vendor weren't specified, then adopt the module's + // architecture so that the platform matching can be more + // accurate. + update_platform_arch(matching_module_spec.GetArchitecture()); + } else { + StreamString platform_arch_strm; + StreamString module_arch_strm; + + platform_arch.DumpTriple(platform_arch_strm.AsRawOstream()); + matching_module_spec.GetArchitecture().DumpTriple( + module_arch_strm.AsRawOstream()); + error.SetErrorStringWithFormat( + "the specified architecture '%s' is not compatible with '%s' " + "in '%s'", + platform_arch_strm.GetData(), module_arch_strm.GetData(), + module_spec.GetFileSpec().GetPath().c_str()); + return error; + } + } else { + // Only one arch and none was specified. + prefer_platform_arch = true; + platform_arch = matching_module_spec.GetArchitecture(); + } + } + } else if (arch.IsValid()) { + // Fat binary. A (valid) architecture was specified. + module_spec.GetArchitecture() = arch; + if (module_specs.FindMatchingModuleSpec(module_spec, + matching_module_spec)) + update_platform_arch(matching_module_spec.GetArchitecture()); + } else { + // Fat binary. No architecture specified, check if there is + // only one platform for all of the architectures. + std::vector<PlatformSP> candidates; + std::vector<ArchSpec> archs; + for (const ModuleSpec &spec : module_specs.ModuleSpecs()) + archs.push_back(spec.GetArchitecture()); + if (PlatformSP platform_for_archs_sp = + platform_list.GetOrCreate(archs, {}, candidates)) { + platform_sp = platform_for_archs_sp; + } else if (candidates.empty()) { + error.SetErrorString("no matching platforms found for this file"); + return error; + } else { + // More than one platform claims to support this file. + StreamString error_strm; + std::set<llvm::StringRef> platform_set; + error_strm.Printf( + "more than one platform supports this executable ("); + for (const auto &candidate : candidates) { + llvm::StringRef platform_name = candidate->GetName(); + if (platform_set.count(platform_name)) + continue; + if (!platform_set.empty()) + error_strm.PutCString(", "); + error_strm.PutCString(platform_name); + platform_set.insert(platform_name); + } + error_strm.Printf("), specify an architecture to disambiguate"); + error.SetErrorString(error_strm.GetString()); + return error; + } + } + } + } + + // If we have a valid architecture, make sure the current platform is + // compatible with that architecture. + if (!prefer_platform_arch && arch.IsValid()) { + if (!platform_sp->IsCompatibleArchitecture( + arch, {}, ArchSpec::CompatibleMatch, nullptr)) { + platform_sp = platform_list.GetOrCreate(arch, {}, &platform_arch); + if (platform_sp) + platform_list.SetSelectedPlatform(platform_sp); + } + } else if (platform_arch.IsValid()) { + // If "arch" isn't valid, yet "platform_arch" is, it means we have an + // executable file with a single architecture which should be used. + ArchSpec fixed_platform_arch; + if (!platform_sp->IsCompatibleArchitecture( + platform_arch, {}, ArchSpec::CompatibleMatch, nullptr)) { + platform_sp = + platform_list.GetOrCreate(platform_arch, {}, &fixed_platform_arch); + if (platform_sp) + platform_list.SetSelectedPlatform(platform_sp); + } + } + + if (!platform_arch.IsValid()) + platform_arch = arch; + + return TargetList::CreateTargetInternal(debugger, user_exe_path, + platform_arch, load_dependent_files, + platform_sp, target_sp); +} + +Status TargetList::CreateTargetInternal(Debugger &debugger, + llvm::StringRef user_exe_path, + const ArchSpec &specified_arch, + LoadDependentFiles load_dependent_files, + lldb::PlatformSP &platform_sp, + lldb::TargetSP &target_sp) { + LLDB_SCOPED_TIMERF("TargetList::CreateTarget (file = '%s', arch = '%s')", + user_exe_path.str().c_str(), + specified_arch.GetArchitectureName()); + Status error; + const bool is_dummy_target = false; + + ArchSpec arch(specified_arch); + + if (arch.IsValid()) { + if (!platform_sp || !platform_sp->IsCompatibleArchitecture( + arch, {}, ArchSpec::CompatibleMatch, nullptr)) + platform_sp = + debugger.GetPlatformList().GetOrCreate(specified_arch, {}, &arch); + } + + if (!platform_sp) + platform_sp = debugger.GetPlatformList().GetSelectedPlatform(); + + if (!arch.IsValid()) + arch = specified_arch; + + FileSpec file(user_exe_path); + if (!FileSystem::Instance().Exists(file) && user_exe_path.starts_with("~")) { + // we want to expand the tilde but we don't want to resolve any symbolic + // links so we can't use the FileSpec constructor's resolve flag + llvm::SmallString<64> unglobbed_path; + StandardTildeExpressionResolver Resolver; + Resolver.ResolveFullPath(user_exe_path, unglobbed_path); + + if (unglobbed_path.empty()) + file = FileSpec(user_exe_path); + else + file = FileSpec(unglobbed_path.c_str()); + } + + bool user_exe_path_is_bundle = false; + char resolved_bundle_exe_path[PATH_MAX]; + resolved_bundle_exe_path[0] = '\0'; + if (file) { + if (FileSystem::Instance().IsDirectory(file)) + user_exe_path_is_bundle = true; + + if (file.IsRelative() && !user_exe_path.empty()) { + llvm::SmallString<64> cwd; + if (! llvm::sys::fs::current_path(cwd)) { + FileSpec cwd_file(cwd.c_str()); + cwd_file.AppendPathComponent(file); + if (FileSystem::Instance().Exists(cwd_file)) + file = cwd_file; + } + } + + ModuleSP exe_module_sp; + if (platform_sp) { + FileSpecList executable_search_paths( + Target::GetDefaultExecutableSearchPaths()); + ModuleSpec module_spec(file, arch); + error = platform_sp->ResolveExecutable(module_spec, exe_module_sp, + executable_search_paths.GetSize() + ? &executable_search_paths + : nullptr); + } + + if (error.Success() && exe_module_sp) { + if (exe_module_sp->GetObjectFile() == nullptr) { + if (arch.IsValid()) { + error.SetErrorStringWithFormat( + "\"%s\" doesn't contain architecture %s", file.GetPath().c_str(), + arch.GetArchitectureName()); + } else { + error.SetErrorStringWithFormat("unsupported file type \"%s\"", + file.GetPath().c_str()); + } + return error; + } + target_sp.reset(new Target(debugger, arch, platform_sp, is_dummy_target)); + debugger.GetTargetList().RegisterInProcessTarget(target_sp); + target_sp->SetExecutableModule(exe_module_sp, load_dependent_files); + if (user_exe_path_is_bundle) + exe_module_sp->GetFileSpec().GetPath(resolved_bundle_exe_path, + sizeof(resolved_bundle_exe_path)); + if (target_sp->GetPreloadSymbols()) + exe_module_sp->PreloadSymbols(); + } + } else { + // No file was specified, just create an empty target with any arch if a + // valid arch was specified + target_sp.reset(new Target(debugger, arch, platform_sp, is_dummy_target)); + debugger.GetTargetList().RegisterInProcessTarget(target_sp); + } + + if (!target_sp) + return error; + + // Set argv0 with what the user typed, unless the user specified a + // directory. If the user specified a directory, then it is probably a + // bundle that was resolved and we need to use the resolved bundle path + if (!user_exe_path.empty()) { + // Use exactly what the user typed as the first argument when we exec or + // posix_spawn + if (user_exe_path_is_bundle && resolved_bundle_exe_path[0]) { + target_sp->SetArg0(resolved_bundle_exe_path); + } else { + // Use resolved path + target_sp->SetArg0(file.GetPath().c_str()); + } + } + if (file.GetDirectory()) { + FileSpec file_dir; + file_dir.SetDirectory(file.GetDirectory()); + target_sp->AppendExecutableSearchPaths(file_dir); + } + + // Now prime this from the dummy target: + target_sp->PrimeFromDummyTarget(debugger.GetDummyTarget()); + + return error; +} + +bool TargetList::DeleteTarget(TargetSP &target_sp) { + std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex); + auto it = llvm::find(m_target_list, target_sp); + if (it == m_target_list.end()) + return false; + + m_target_list.erase(it); + return true; +} + +TargetSP TargetList::FindTargetWithExecutableAndArchitecture( + const FileSpec &exe_file_spec, const ArchSpec *exe_arch_ptr) const { + std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex); + auto it = std::find_if(m_target_list.begin(), m_target_list.end(), + [&exe_file_spec, exe_arch_ptr](const TargetSP &item) { + Module *exe_module = item->GetExecutableModulePointer(); + if (!exe_module || + !FileSpec::Match(exe_file_spec, exe_module->GetFileSpec())) + return false; + + return !exe_arch_ptr || + exe_arch_ptr->IsCompatibleMatch(exe_module->GetArchitecture()); + }); + + if (it != m_target_list.end()) + return *it; + + return TargetSP(); +} + +TargetSP TargetList::FindTargetWithProcessID(lldb::pid_t pid) const { + std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex); + auto it = std::find_if(m_target_list.begin(), m_target_list.end(), + [pid](const TargetSP &item) { + auto *process_ptr = item->GetProcessSP().get(); + return process_ptr && (process_ptr->GetID() == pid); + }); + + if (it != m_target_list.end()) + return *it; + + return TargetSP(); +} + +TargetSP TargetList::FindTargetWithProcess(Process *process) const { + TargetSP target_sp; + if (!process) + return target_sp; + + std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex); + auto it = std::find_if(m_target_list.begin(), m_target_list.end(), + [process](const TargetSP &item) { + return item->GetProcessSP().get() == process; + }); + + if (it != m_target_list.end()) + target_sp = *it; + + return target_sp; +} + +TargetSP TargetList::GetTargetSP(Target *target) const { + TargetSP target_sp; + if (!target) + return target_sp; + + std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex); + auto it = std::find_if(m_target_list.begin(), m_target_list.end(), + [target](const TargetSP &item) { return item.get() == target; }); + if (it != m_target_list.end()) + target_sp = *it; + + return target_sp; +} + +uint32_t TargetList::SendAsyncInterrupt(lldb::pid_t pid) { + uint32_t num_async_interrupts_sent = 0; + + if (pid != LLDB_INVALID_PROCESS_ID) { + TargetSP target_sp(FindTargetWithProcessID(pid)); + if (target_sp) { + Process *process = target_sp->GetProcessSP().get(); + if (process) { + process->SendAsyncInterrupt(); + ++num_async_interrupts_sent; + } + } + } else { + // We don't have a valid pid to broadcast to, so broadcast to the target + // list's async broadcaster... + BroadcastEvent(Process::eBroadcastBitInterrupt, nullptr); + } + + return num_async_interrupts_sent; +} + +uint32_t TargetList::SignalIfRunning(lldb::pid_t pid, int signo) { + uint32_t num_signals_sent = 0; + Process *process = nullptr; + if (pid == LLDB_INVALID_PROCESS_ID) { + // Signal all processes with signal + std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex); + for (const auto &target_sp : m_target_list) { + process = target_sp->GetProcessSP().get(); + if (process && process->IsAlive()) { + ++num_signals_sent; + process->Signal(signo); + } + } + } else { + // Signal a specific process with signal + TargetSP target_sp(FindTargetWithProcessID(pid)); + if (target_sp) { + process = target_sp->GetProcessSP().get(); + if (process && process->IsAlive()) { + ++num_signals_sent; + process->Signal(signo); + } + } + } + return num_signals_sent; +} + +size_t TargetList::GetNumTargets() const { + std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex); + return m_target_list.size(); +} + +lldb::TargetSP TargetList::GetTargetAtIndex(uint32_t idx) const { + TargetSP target_sp; + std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex); + if (idx < m_target_list.size()) + target_sp = m_target_list[idx]; + return target_sp; +} + +uint32_t TargetList::GetIndexOfTarget(lldb::TargetSP target_sp) const { + std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex); + auto it = llvm::find(m_target_list, target_sp); + if (it != m_target_list.end()) + return std::distance(m_target_list.begin(), it); + return UINT32_MAX; +} + +void TargetList::AddTargetInternal(TargetSP target_sp, bool do_select) { + lldbassert(!llvm::is_contained(m_target_list, target_sp) && + "target already exists it the list"); + UnregisterInProcessTarget(target_sp); + m_target_list.push_back(std::move(target_sp)); + if (do_select) + SetSelectedTargetInternal(m_target_list.size() - 1); +} + +void TargetList::SetSelectedTargetInternal(uint32_t index) { + lldbassert(!m_target_list.empty()); + m_selected_target_idx = index < m_target_list.size() ? index : 0; +} + +void TargetList::SetSelectedTarget(uint32_t index) { + std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex); + SetSelectedTargetInternal(index); +} + +void TargetList::SetSelectedTarget(const TargetSP &target_sp) { + // Don't allow an invalid target shared pointer or a target that has been + // destroyed to become the selected target. + if (target_sp && target_sp->IsValid()) { + std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex); + auto it = llvm::find(m_target_list, target_sp); + SetSelectedTargetInternal(std::distance(m_target_list.begin(), it)); + } +} + +lldb::TargetSP TargetList::GetSelectedTarget() { + std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex); + if (m_selected_target_idx >= m_target_list.size()) + m_selected_target_idx = 0; + return GetTargetAtIndex(m_selected_target_idx); +} + +bool TargetList::AnyTargetContainsModule(Module &module) { + std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex); + for (const auto &target_sp : m_target_list) { + if (target_sp->GetImages().FindModule(&module)) + return true; + } + for (const auto &target_sp: m_in_process_target_list) { + if (target_sp->GetImages().FindModule(&module)) + return true; + } + return false; +} + + void TargetList::RegisterInProcessTarget(TargetSP target_sp) { + std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex); + [[maybe_unused]] bool was_added; + std::tie(std::ignore, was_added) = + m_in_process_target_list.insert(target_sp); + assert(was_added && "Target pointer was left in the in-process map"); + } + + void TargetList::UnregisterInProcessTarget(TargetSP target_sp) { + std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex); + [[maybe_unused]] bool was_present = + m_in_process_target_list.erase(target_sp); + assert(was_present && "Target pointer being removed was not registered"); + } + + bool TargetList::IsTargetInProcess(TargetSP target_sp) { + std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex); + return m_in_process_target_list.count(target_sp) == 1; + } diff --git a/contrib/llvm-project/lldb/source/Target/TargetProperties.td b/contrib/llvm-project/lldb/source/Target/TargetProperties.td new file mode 100644 index 000000000000..7f79218e0a6a --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/TargetProperties.td @@ -0,0 +1,319 @@ +include "../../include/lldb/Core/PropertiesBase.td" + +let Definition = "target_experimental" in { + def InjectLocalVars : Property<"inject-local-vars", "Boolean">, + Global, DefaultTrue, + Desc<"If true, inject local variables explicitly into the expression text. This will fix symbol resolution when there are name collisions between ivars and local variables. But it can make expressions run much more slowly.">; +} + +let Definition = "target" in { + def DefaultArch: Property<"default-arch", "Arch">, + Global, + DefaultStringValue<"">, + Desc<"Default architecture to choose, when there's a choice.">; + def MoveToNearestCode: Property<"move-to-nearest-code", "Boolean">, + DefaultTrue, + Desc<"Move breakpoints to nearest code.">; + def Language: Property<"language", "Language">, + DefaultEnumValue<"eLanguageTypeUnknown">, + Desc<"The language to use when interpreting expressions entered in commands.">; + def ExprPrefix: Property<"expr-prefix", "FileSpec">, + DefaultStringValue<"">, + Desc<"Path to a file containing expressions to be prepended to all expressions.">; + def ExprErrorLimit: Property<"expr-error-limit", "UInt64">, + DefaultUnsignedValue<5>, + Desc<"The maximum amount of errors to emit while parsing an expression. " + "A value of 0 means to always continue parsing if possible.">; + def ExprAllocAddress: Property<"expr-alloc-address", "UInt64">, + DefaultUnsignedValue<0>, + Desc<"Start address within the process address space of memory allocation for expression evaluation.">; + def ExprAllocSize: Property<"expr-alloc-size", "UInt64">, + DefaultUnsignedValue<0>, + Desc<"Amount of memory in bytes to allocate for expression evaluation.">; + def ExprAllocAlign: Property<"expr-alloc-align", "UInt64">, + DefaultUnsignedValue<0>, + Desc<"Alignment for each memory allocation for expression evaluation.">; + def PreferDynamic: Property<"prefer-dynamic-value", "Enum">, + DefaultEnumValue<"eDynamicDontRunTarget">, + EnumValues<"OptionEnumValues(g_dynamic_value_types)">, + Desc<"Should printed values be shown as their dynamic value.">; + def EnableSynthetic: Property<"enable-synthetic-value", "Boolean">, + DefaultTrue, + Desc<"Should synthetic values be used by default whenever available.">; + def SkipPrologue: Property<"skip-prologue", "Boolean">, + DefaultTrue, + Desc<"Skip function prologues when setting breakpoints by name.">; + def SourceMap: Property<"source-map", "PathMap">, + DefaultStringValue<"">, + Desc<"Source path remappings apply substitutions to the paths of source files, typically needed to debug from a different host than the one that built the target. The source-map property consists of an array of pairs, the first element is a path prefix, and the second is its replacement. The syntax is `prefix1 replacement1 prefix2 replacement2...`. The pairs are checked in order, the first prefix that matches is used, and that prefix is substituted with the replacement. A common pattern is to use source-map in conjunction with the clang -fdebug-prefix-map flag. In the build, use `-fdebug-prefix-map=/path/to/build_dir=.` to rewrite the host specific build directory to `.`. Then for debugging, use `settings set target.source-map . /path/to/local_dir` to convert `.` to a valid local path.">; + def AutoSourceMapRelative: Property<"auto-source-map-relative", "Boolean">, + DefaultTrue, + Desc<"Automatically deduce source path mappings based on source file breakpoint resolution. It only deduces source mapping if source file breakpoint request is using full path and if the debug info contains relative paths.">; + def ExecutableSearchPaths: Property<"exec-search-paths", "FileSpecList">, + DefaultStringValue<"">, + Desc<"Executable search paths to use when locating executable files whose paths don't match the local file system.">; + def DebugFileSearchPaths: Property<"debug-file-search-paths", "FileSpecList">, + DefaultStringValue<"">, + Desc<"List of directories to be searched when locating debug symbol files. See also symbols.enable-external-lookup.">; + def ClangModuleSearchPaths: Property<"clang-module-search-paths", "FileSpecList">, + DefaultStringValue<"">, + Desc<"List of directories to be searched when locating modules for Clang.">; + def AutoImportClangModules: Property<"auto-import-clang-modules", "Boolean">, + DefaultTrue, + Desc<"Automatically load Clang modules referred to by the program.">; + def ImportStdModule: Property<"import-std-module", "Enum">, + DefaultEnumValue<"eImportStdModuleFalse">, + EnumValues<"OptionEnumValues(g_import_std_module_value_types)">, + Desc<"Import the 'std' C++ module to improve expression parsing involving " + " C++ standard library types.">; + def DynamicClassInfoHelper: Property<"objc-dynamic-class-extractor", "Enum">, + DefaultEnumValue<"eDynamicClassInfoHelperAuto">, + EnumValues<"OptionEnumValues(g_dynamic_class_info_helper_value_types)">, + Desc<"Configure how LLDB parses dynamic Objective-C class metadata. By default LLDB will choose the most appropriate method for the target OS.">; + def AutoApplyFixIts: Property<"auto-apply-fixits", "Boolean">, + DefaultTrue, + Desc<"Automatically apply fix-it hints to expressions.">; + def RetriesWithFixIts: Property<"retries-with-fixits", "UInt64">, + DefaultUnsignedValue<1>, + Desc<"Maximum number of attempts to fix an expression with Fix-Its">; + def NotifyAboutFixIts: Property<"notify-about-fixits", "Boolean">, + DefaultTrue, + Desc<"Print the fixed expression text.">; + def SaveObjectsDir: Property<"save-jit-objects-dir", "FileSpec">, + DefaultStringValue<"">, + Desc<"If specified, the directory to save intermediate object files generated by the LLVM JIT">; + def ShowHexVariableValuesWithLeadingZeroes: Property<"show-hex-variable-values-with-leading-zeroes", "Boolean">, + Global, + DefaultTrue, + Desc<"Whether to display leading zeroes when printing variable values in hex format.">; + def MaxZeroPaddingInFloatFormat: Property<"max-zero-padding-in-float-format", "UInt64">, + DefaultUnsignedValue<6>, + Desc<"The maximum number of zeroes to insert when displaying a very small float before falling back to scientific notation.">; + def MaxChildrenCount: Property<"max-children-count", "SInt64">, + DefaultUnsignedValue<256>, + Desc<"Maximum number of children to expand in any level of depth.">; + def MaxChildrenDepth: Property<"max-children-depth", "UInt64">, + DefaultUnsignedValue<0xFFFFFFFF>, + Desc<"Maximum depth to expand children.">; + def MaxSummaryLength: Property<"max-string-summary-length", "UInt64">, + DefaultUnsignedValue<1024>, + Desc<"Maximum number of characters to show when using %s in summary strings.">; + def MaxMemReadSize: Property<"max-memory-read-size", "SInt64">, + DefaultUnsignedValue<1024>, + Desc<"Maximum number of bytes that 'memory read' will fetch before --force must be specified.">; + def BreakpointUseAvoidList: Property<"breakpoints-use-platform-avoid-list", "Boolean">, + DefaultTrue, + Desc<"Consult the platform module avoid list when setting non-module specific breakpoints.">; + def Arg0: Property<"arg0", "String">, + DefaultStringValue<"">, + Desc<"The first argument passed to the program in the argument array which can be different from the executable itself.">; + def RunArgs: Property<"run-args", "Args">, + DefaultStringValue<"">, + Desc<"A list containing all the arguments to be passed to the executable when it is run. Note that this does NOT include the argv[0] which is in target.arg0.">; + def EnvVars: Property<"env-vars", "Dictionary">, + ElementType<"String">, + Desc<"A list of user provided environment variables to be passed to the executable's environment, and their values.">; + def UnsetEnvVars: Property<"unset-env-vars", "Array">, + ElementType<"String">, + Desc<"A list of environment variable names to be unset in the inferior's environment. This is most useful to unset some host environment variables when target.inherit-env is true. target.env-vars takes precedence over target.unset-env-vars.">; + def InheritEnv: Property<"inherit-env", "Boolean">, + DefaultTrue, + Desc<"Inherit the environment from the process that is running LLDB.">; + def InputPath: Property<"input-path", "FileSpec">, + DefaultStringValue<"">, + Desc<"The file/path to be used by the executable program for reading its standard input.">; + def OutputPath: Property<"output-path", "FileSpec">, + DefaultStringValue<"">, + Desc<"The file/path to be used by the executable program for writing its standard output.">; + def ErrorPath: Property<"error-path", "FileSpec">, + DefaultStringValue<"">, + Desc<"The file/path to be used by the executable program for writing its standard error.">; + def DetachOnError: Property<"detach-on-error", "Boolean">, + DefaultTrue, + Desc<"debugserver will detach (rather than killing) a process if it loses connection with lldb.">; + def PreloadSymbols: Property<"preload-symbols", "Boolean">, + DefaultTrue, + Desc<"Enable loading of symbol tables before they are needed.">; + def DisableASLR: Property<"disable-aslr", "Boolean">, + DefaultTrue, + Desc<"Disable Address Space Layout Randomization (ASLR)">; + def DisableSTDIO: Property<"disable-stdio", "Boolean">, + DefaultFalse, + Desc<"Disable stdin/stdout for process (e.g. for a GUI application)">; + def InheritTCC: Property<"inherit-tcc", "Boolean">, + DefaultFalse, + Desc<"Inherit the TCC permissions from the inferior's parent instead of making the process itself responsible.">; + def InlineStrategy: Property<"inline-breakpoint-strategy", "Enum">, + DefaultEnumValue<"eInlineBreakpointsAlways">, + EnumValues<"OptionEnumValues(g_inline_breakpoint_enums)">, + Desc<"The strategy to use when settings breakpoints by file and line. Breakpoint locations can end up being inlined by the compiler, so that a compile unit 'a.c' might contain an inlined function from another source file. Usually this is limited to breakpoint locations from inlined functions from header or other include files, or more accurately non-implementation source files. Sometimes code might #include implementation files and cause inlined breakpoint locations in inlined implementation files. Always checking for inlined breakpoint locations can be expensive (memory and time), so if you have a project with many headers and find that setting breakpoints is slow, then you can change this setting to headers. This setting allows you to control exactly which strategy is used when setting file and line breakpoints.">; + def DisassemblyFlavor: Property<"x86-disassembly-flavor", "Enum">, + DefaultEnumValue<"eX86DisFlavorDefault">, + EnumValues<"OptionEnumValues(g_x86_dis_flavor_value_types)">, + Desc<"The default disassembly flavor to use for x86 or x86-64 targets.">; + def UseHexImmediates: Property<"use-hex-immediates", "Boolean">, + DefaultTrue, + Desc<"Show immediates in disassembly as hexadecimal.">; + def HexImmediateStyle: Property<"hex-immediate-style", "Enum">, + DefaultEnumValue<"Disassembler::eHexStyleC">, + EnumValues<"OptionEnumValues(g_hex_immediate_style_values)">, + Desc<"Which style to use for printing hexadecimal disassembly values.">; + def UseFastStepping: Property<"use-fast-stepping", "Boolean">, + DefaultTrue, + Desc<"Use a fast stepping algorithm based on running from branch to branch rather than instruction single-stepping.">; + def LoadScriptFromSymbolFile: Property<"load-script-from-symbol-file", "Enum">, + DefaultEnumValue<"eLoadScriptFromSymFileWarn">, + EnumValues<"OptionEnumValues(g_load_script_from_sym_file_values)">, + Desc<"Allow LLDB to load scripting resources embedded in symbol files when available.">; + def LoadCWDlldbinitFile: Property<"load-cwd-lldbinit", "Enum">, + DefaultEnumValue<"eLoadCWDlldbinitWarn">, + EnumValues<"OptionEnumValues(g_load_cwd_lldbinit_values)">, + Desc<"Allow LLDB to .lldbinit files from the current directory automatically.">; + def MemoryModuleLoadLevel: Property<"memory-module-load-level", "Enum">, + DefaultEnumValue<"eMemoryModuleLoadLevelComplete">, + EnumValues<"OptionEnumValues(g_memory_module_load_level_values)">, + Desc<"Loading modules from memory can be slow as reading the symbol tables and other data can take a long time depending on your connection to the debug target. This setting helps users control how much information gets loaded when loading modules from memory.'complete' is the default value for this setting which will load all sections and symbols by reading them from memory (slowest, most accurate). 'partial' will load sections and attempt to find function bounds without downloading the symbol table (faster, still accurate, missing symbol names). 'minimal' is the fastest setting and will load section data with no symbols, but should rarely be used as stack frames in these memory regions will be inaccurate and not provide any context (fastest). ">; + def DisplayExpressionsInCrashlogs: Property<"display-expression-in-crashlogs", "Boolean">, + DefaultFalse, + Desc<"Expressions that crash will show up in crash logs if the host system supports executable specific crash log strings and this setting is set to true.">; + def TrapHandlerNames: Property<"trap-handler-names", "Array">, + Global, + ElementType<"String">, + Desc<"A list of trap handler function names, e.g. a common Unix user process one is _sigtramp.">; + def DisplayRuntimeSupportValues: Property<"display-runtime-support-values", "Boolean">, + DefaultFalse, + Desc<"If true, LLDB will show variables that are meant to support the operation of a language's runtime support.">; + def DisplayRecognizedArguments: Property<"display-recognized-arguments", "Boolean">, + DefaultFalse, + Desc<"Show recognized arguments in variable listings by default.">; + def RequireHardwareBreakpoints: Property<"require-hardware-breakpoint", "Boolean">, + DefaultFalse, + Desc<"Require all breakpoints to be hardware breakpoints.">; + def AutoInstallMainExecutable: Property<"auto-install-main-executable", "Boolean">, + DefaultTrue, + Desc<"Always install the main executable when connected to a remote platform.">; + def DebugUtilityExpression: Property<"debug-utility-expression", "Boolean">, + DefaultFalse, + Desc<"Enable debugging of LLDB-internal utility expressions.">; +} + +let Definition = "process_experimental" in { + def OSPluginReportsAllThreads: Property<"os-plugin-reports-all-threads", "Boolean">, + Global, + DefaultTrue, + Desc<"Set to False if your OS Plugins doesn't report all threads on each stop.">; +} + +let Definition = "process" in { + def DisableMemCache: Property<"disable-memory-cache", "Boolean">, + DefaultFalse, + Desc<"Disable reading and caching of memory in fixed-size units.">; + def ExtraStartCommand: Property<"extra-startup-command", "Array">, + ElementType<"String">, + Desc<"A list containing extra commands understood by the particular process plugin used. For instance, to turn on debugserver logging set this to 'QSetLogging:bitmask=LOG_DEFAULT;'">; + def IgnoreBreakpointsInExpressions: Property<"ignore-breakpoints-in-expressions", "Boolean">, + Global, + DefaultTrue, + Desc<"If true, breakpoints will be ignored during expression evaluation.">; + def UnwindOnErrorInExpressions: Property<"unwind-on-error-in-expressions", "Boolean">, + Global, + DefaultTrue, + Desc<"If true, errors in expression evaluation will unwind the stack back to the state before the call.">; + def PythonOSPluginPath: Property<"python-os-plugin-path", "FileSpec">, + DefaultUnsignedValue<1>, + Desc<"A path to a python OS plug-in module file that contains a OperatingSystemPlugIn class.">; + def StopOnSharedLibraryEvents: Property<"stop-on-sharedlibrary-events", "Boolean">, + Global, + DefaultFalse, + Desc<"If true, stop when a shared library is loaded or unloaded.">; + def DisableLangRuntimeUnwindPlans: Property<"disable-language-runtime-unwindplans", "Boolean">, + Global, + DefaultFalse, + Desc<"If true, language runtime augmented/overidden backtraces will not be used when printing a stack trace.">; + def DetachKeepsStopped: Property<"detach-keeps-stopped", "Boolean">, + Global, + DefaultFalse, + Desc<"If true, detach will attempt to keep the process stopped.">; + def MemCacheLineSize: Property<"memory-cache-line-size", "UInt64">, + DefaultUnsignedValue<512>, + Desc<"The memory cache line size">; + def WarningOptimization: Property<"optimization-warnings", "Boolean">, + DefaultTrue, + Desc<"If true, warn when stopped in code that is optimized where stepping and variable availability may not behave as expected.">; + def WarningUnsupportedLanguage: Property<"unsupported-language-warnings", "Boolean">, + DefaultTrue, + Desc<"If true, warn when stopped in code that is written in a source language that LLDB does not support.">; + def StopOnExec: Property<"stop-on-exec", "Boolean">, + Global, + DefaultTrue, + Desc<"If true, stop when the inferior exec's.">; + def UtilityExpressionTimeout: Property<"utility-expression-timeout", "UInt64">, +#ifdef LLDB_SANITIZED + DefaultUnsignedValue<60>, +#else + DefaultUnsignedValue<15>, +#endif + Desc<"The time in seconds to wait for LLDB-internal utility expressions.">; + def InterruptTimeout: Property<"interrupt-timeout", "UInt64">, +#ifdef LLDB_SANITIZED + DefaultUnsignedValue<60>, +#else + DefaultUnsignedValue<20>, +#endif + Desc<"The time in seconds to wait for an interrupt succeed in stopping the target.">; + def SteppingRunsAllThreads: Property<"run-all-threads", "Boolean">, + DefaultFalse, + Desc<"If true, stepping operations will run all threads. This is equivalent to setting the run-mode option to 'all-threads'.">; + def VirtualAddressableBits: Property<"virtual-addressable-bits", "UInt64">, + DefaultUnsignedValue<0>, + Desc<"The number of bits used for addressing. If the value is 39, then bits 0..38 are used for addressing. The default value of 0 means unspecified.">; + def HighmemVirtualAddressableBits: Property<"highmem-virtual-addressable-bits", "UInt64">, + DefaultUnsignedValue<0>, + Desc<"The number of bits used for addressing high memory, when it differs from low memory in the same Process. When this is non-zero, target.process.virtual-addressable-bits will be the value for low memory (0x000... addresses) and this setting will be the value for high memory (0xfff... addresses). When this is zero, target.process.virtual-addressable-bits applies to all addresses. It is very uncommon to use this setting.">; + def FollowForkMode: Property<"follow-fork-mode", "Enum">, + DefaultEnumValue<"eFollowParent">, + EnumValues<"OptionEnumValues(g_follow_fork_mode_values)">, + Desc<"Debugger's behavior upon fork or vfork.">; +} + +let Definition = "platform" in { + def UseModuleCache: Property<"use-module-cache", "Boolean">, + Global, + DefaultTrue, + Desc<"Use module cache.">; + def ModuleCacheDirectory: Property<"module-cache-directory", "FileSpec">, + Global, + DefaultStringValue<"">, + Desc<"Root directory for cached modules.">; +} + +let Definition = "thread" in { + def StepInAvoidsNoDebug: Property<"step-in-avoid-nodebug", "Boolean">, + Global, + DefaultTrue, + Desc<"If true, step-in will not stop in functions with no debug information.">; + def StepOutAvoidsNoDebug: Property<"step-out-avoid-nodebug", "Boolean">, + Global, + DefaultFalse, + Desc<"If true, when step-in/step-out/step-over leave the current frame, they will continue to step out till they come to a function with debug information. Passing a frame argument to step-out will override this option.">; + def StepAvoidRegex: Property<"step-avoid-regexp", "Regex">, + Global, + DefaultStringValue<"^std::">, + Desc<"A regular expression defining functions step-in won't stop in.">; + def StepAvoidLibraries: Property<"step-avoid-libraries", "FileSpecList">, + Global, + DefaultStringValue<"">, + Desc<"A list of libraries that source stepping won't stop in.">; + def EnableThreadTrace: Property<"trace-thread", "Boolean">, + DefaultFalse, + Desc<"If true, this thread will single-step and log execution.">; + def MaxBacktraceDepth: Property<"max-backtrace-depth", "UInt64">, + DefaultUnsignedValue<600000>, + Desc<"Maximum number of frames to backtrace.">; +} + +let Definition = "language" in { + def EnableFilterForLineBreakpoints: Property<"enable-filter-for-line-breakpoints", "Boolean">, + DefaultTrue, + Desc<"If true, allow Language plugins to filter locations when setting breakpoints by line number or regex.">; +} diff --git a/contrib/llvm-project/lldb/source/Target/Thread.cpp b/contrib/llvm-project/lldb/source/Target/Thread.cpp new file mode 100644 index 000000000000..e75f5a356cec --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/Thread.cpp @@ -0,0 +1,2066 @@ +//===-- Thread.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 "lldb/Target/Thread.h" +#include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/FormatEntity.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/StructuredDataImpl.h" +#include "lldb/Core/ValueObject.h" +#include "lldb/Core/ValueObjectConstResult.h" +#include "lldb/Host/Host.h" +#include "lldb/Interpreter/OptionValueFileSpecList.h" +#include "lldb/Interpreter/OptionValueProperties.h" +#include "lldb/Interpreter/Property.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Target/ABI.h" +#include "lldb/Target/DynamicLoader.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/LanguageRuntime.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/StackFrameRecognizer.h" +#include "lldb/Target/StopInfo.h" +#include "lldb/Target/SystemRuntime.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/ThreadPlan.h" +#include "lldb/Target/ThreadPlanBase.h" +#include "lldb/Target/ThreadPlanCallFunction.h" +#include "lldb/Target/ThreadPlanPython.h" +#include "lldb/Target/ThreadPlanRunToAddress.h" +#include "lldb/Target/ThreadPlanStack.h" +#include "lldb/Target/ThreadPlanStepInRange.h" +#include "lldb/Target/ThreadPlanStepInstruction.h" +#include "lldb/Target/ThreadPlanStepOut.h" +#include "lldb/Target/ThreadPlanStepOverBreakpoint.h" +#include "lldb/Target/ThreadPlanStepOverRange.h" +#include "lldb/Target/ThreadPlanStepThrough.h" +#include "lldb/Target/ThreadPlanStepUntil.h" +#include "lldb/Target/ThreadSpec.h" +#include "lldb/Target/UnwindLLDB.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/RegularExpression.h" +#include "lldb/Utility/State.h" +#include "lldb/Utility/Stream.h" +#include "lldb/Utility/StreamString.h" +#include "lldb/lldb-enumerations.h" + +#include <memory> +#include <optional> + +using namespace lldb; +using namespace lldb_private; + +ThreadProperties &Thread::GetGlobalProperties() { + // NOTE: intentional leak so we don't crash if global destructor chain gets + // called as other threads still use the result of this function + static ThreadProperties *g_settings_ptr = new ThreadProperties(true); + return *g_settings_ptr; +} + +#define LLDB_PROPERTIES_thread +#include "TargetProperties.inc" + +enum { +#define LLDB_PROPERTIES_thread +#include "TargetPropertiesEnum.inc" +}; + +class ThreadOptionValueProperties + : public Cloneable<ThreadOptionValueProperties, OptionValueProperties> { +public: + ThreadOptionValueProperties(llvm::StringRef name) : Cloneable(name) {} + + const Property * + GetPropertyAtIndex(size_t idx, + const ExecutionContext *exe_ctx) const override { + // When getting the value for a key from the thread options, we will always + // try and grab the setting from the current thread if there is one. Else + // we just use the one from this instance. + if (exe_ctx) { + Thread *thread = exe_ctx->GetThreadPtr(); + if (thread) { + ThreadOptionValueProperties *instance_properties = + static_cast<ThreadOptionValueProperties *>( + thread->GetValueProperties().get()); + if (this != instance_properties) + return instance_properties->ProtectedGetPropertyAtIndex(idx); + } + } + return ProtectedGetPropertyAtIndex(idx); + } +}; + +ThreadProperties::ThreadProperties(bool is_global) : Properties() { + if (is_global) { + m_collection_sp = std::make_shared<ThreadOptionValueProperties>("thread"); + m_collection_sp->Initialize(g_thread_properties); + } else + m_collection_sp = + OptionValueProperties::CreateLocalCopy(Thread::GetGlobalProperties()); +} + +ThreadProperties::~ThreadProperties() = default; + +const RegularExpression *ThreadProperties::GetSymbolsToAvoidRegexp() { + const uint32_t idx = ePropertyStepAvoidRegex; + return GetPropertyAtIndexAs<const RegularExpression *>(idx); +} + +FileSpecList ThreadProperties::GetLibrariesToAvoid() const { + const uint32_t idx = ePropertyStepAvoidLibraries; + return GetPropertyAtIndexAs<FileSpecList>(idx, {}); +} + +bool ThreadProperties::GetTraceEnabledState() const { + const uint32_t idx = ePropertyEnableThreadTrace; + return GetPropertyAtIndexAs<bool>( + idx, g_thread_properties[idx].default_uint_value != 0); +} + +bool ThreadProperties::GetStepInAvoidsNoDebug() const { + const uint32_t idx = ePropertyStepInAvoidsNoDebug; + return GetPropertyAtIndexAs<bool>( + idx, g_thread_properties[idx].default_uint_value != 0); +} + +bool ThreadProperties::GetStepOutAvoidsNoDebug() const { + const uint32_t idx = ePropertyStepOutAvoidsNoDebug; + return GetPropertyAtIndexAs<bool>( + idx, g_thread_properties[idx].default_uint_value != 0); +} + +uint64_t ThreadProperties::GetMaxBacktraceDepth() const { + const uint32_t idx = ePropertyMaxBacktraceDepth; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_thread_properties[idx].default_uint_value); +} + +// Thread Event Data + +llvm::StringRef Thread::ThreadEventData::GetFlavorString() { + return "Thread::ThreadEventData"; +} + +Thread::ThreadEventData::ThreadEventData(const lldb::ThreadSP thread_sp) + : m_thread_sp(thread_sp), m_stack_id() {} + +Thread::ThreadEventData::ThreadEventData(const lldb::ThreadSP thread_sp, + const StackID &stack_id) + : m_thread_sp(thread_sp), m_stack_id(stack_id) {} + +Thread::ThreadEventData::ThreadEventData() : m_thread_sp(), m_stack_id() {} + +Thread::ThreadEventData::~ThreadEventData() = default; + +void Thread::ThreadEventData::Dump(Stream *s) const {} + +const Thread::ThreadEventData * +Thread::ThreadEventData::GetEventDataFromEvent(const Event *event_ptr) { + if (event_ptr) { + const EventData *event_data = event_ptr->GetData(); + if (event_data && + event_data->GetFlavor() == ThreadEventData::GetFlavorString()) + return static_cast<const ThreadEventData *>(event_ptr->GetData()); + } + return nullptr; +} + +ThreadSP Thread::ThreadEventData::GetThreadFromEvent(const Event *event_ptr) { + ThreadSP thread_sp; + const ThreadEventData *event_data = GetEventDataFromEvent(event_ptr); + if (event_data) + thread_sp = event_data->GetThread(); + return thread_sp; +} + +StackID Thread::ThreadEventData::GetStackIDFromEvent(const Event *event_ptr) { + StackID stack_id; + const ThreadEventData *event_data = GetEventDataFromEvent(event_ptr); + if (event_data) + stack_id = event_data->GetStackID(); + return stack_id; +} + +StackFrameSP +Thread::ThreadEventData::GetStackFrameFromEvent(const Event *event_ptr) { + const ThreadEventData *event_data = GetEventDataFromEvent(event_ptr); + StackFrameSP frame_sp; + if (event_data) { + ThreadSP thread_sp = event_data->GetThread(); + if (thread_sp) { + frame_sp = thread_sp->GetStackFrameList()->GetFrameWithStackID( + event_data->GetStackID()); + } + } + return frame_sp; +} + +// Thread class + +llvm::StringRef Thread::GetStaticBroadcasterClass() { + static constexpr llvm::StringLiteral class_name("lldb.thread"); + return class_name; +} + +Thread::Thread(Process &process, lldb::tid_t tid, bool use_invalid_index_id) + : ThreadProperties(false), UserID(tid), + Broadcaster(process.GetTarget().GetDebugger().GetBroadcasterManager(), + Thread::GetStaticBroadcasterClass().str()), + m_process_wp(process.shared_from_this()), m_stop_info_sp(), + m_stop_info_stop_id(0), m_stop_info_override_stop_id(0), + m_should_run_before_public_stop(false), + m_index_id(use_invalid_index_id ? LLDB_INVALID_INDEX32 + : process.GetNextThreadIndexID(tid)), + m_reg_context_sp(), m_state(eStateUnloaded), m_state_mutex(), + m_frame_mutex(), m_curr_frames_sp(), m_prev_frames_sp(), + m_prev_framezero_pc(), m_resume_signal(LLDB_INVALID_SIGNAL_NUMBER), + m_resume_state(eStateRunning), m_temporary_resume_state(eStateRunning), + m_unwinder_up(), m_destroy_called(false), + m_override_should_notify(eLazyBoolCalculate), + m_extended_info_fetched(false), m_extended_info() { + Log *log = GetLog(LLDBLog::Object); + LLDB_LOGF(log, "%p Thread::Thread(tid = 0x%4.4" PRIx64 ")", + static_cast<void *>(this), GetID()); + + CheckInWithManager(); +} + +Thread::~Thread() { + Log *log = GetLog(LLDBLog::Object); + LLDB_LOGF(log, "%p Thread::~Thread(tid = 0x%4.4" PRIx64 ")", + static_cast<void *>(this), GetID()); + /// If you hit this assert, it means your derived class forgot to call + /// DoDestroy in its destructor. + assert(m_destroy_called); +} + +void Thread::DestroyThread() { + m_destroy_called = true; + m_stop_info_sp.reset(); + m_reg_context_sp.reset(); + m_unwinder_up.reset(); + std::lock_guard<std::recursive_mutex> guard(m_frame_mutex); + m_curr_frames_sp.reset(); + m_prev_frames_sp.reset(); + m_prev_framezero_pc.reset(); +} + +void Thread::BroadcastSelectedFrameChange(StackID &new_frame_id) { + if (EventTypeHasListeners(eBroadcastBitSelectedFrameChanged)) { + auto data_sp = + std::make_shared<ThreadEventData>(shared_from_this(), new_frame_id); + BroadcastEvent(eBroadcastBitSelectedFrameChanged, data_sp); + } +} + +lldb::StackFrameSP +Thread::GetSelectedFrame(SelectMostRelevant select_most_relevant) { + StackFrameListSP stack_frame_list_sp(GetStackFrameList()); + StackFrameSP frame_sp = stack_frame_list_sp->GetFrameAtIndex( + stack_frame_list_sp->GetSelectedFrameIndex(select_most_relevant)); + FrameSelectedCallback(frame_sp.get()); + return frame_sp; +} + +uint32_t Thread::SetSelectedFrame(lldb_private::StackFrame *frame, + bool broadcast) { + uint32_t ret_value = GetStackFrameList()->SetSelectedFrame(frame); + if (broadcast) + BroadcastSelectedFrameChange(frame->GetStackID()); + FrameSelectedCallback(frame); + return ret_value; +} + +bool Thread::SetSelectedFrameByIndex(uint32_t frame_idx, bool broadcast) { + StackFrameSP frame_sp(GetStackFrameList()->GetFrameAtIndex(frame_idx)); + if (frame_sp) { + GetStackFrameList()->SetSelectedFrame(frame_sp.get()); + if (broadcast) + BroadcastSelectedFrameChange(frame_sp->GetStackID()); + FrameSelectedCallback(frame_sp.get()); + return true; + } else + return false; +} + +bool Thread::SetSelectedFrameByIndexNoisily(uint32_t frame_idx, + Stream &output_stream) { + const bool broadcast = true; + bool success = SetSelectedFrameByIndex(frame_idx, broadcast); + if (success) { + StackFrameSP frame_sp = GetSelectedFrame(DoNoSelectMostRelevantFrame); + if (frame_sp) { + bool already_shown = false; + SymbolContext frame_sc( + frame_sp->GetSymbolContext(eSymbolContextLineEntry)); + const Debugger &debugger = GetProcess()->GetTarget().GetDebugger(); + if (debugger.GetUseExternalEditor() && frame_sc.line_entry.GetFile() && + frame_sc.line_entry.line != 0) { + if (llvm::Error e = Host::OpenFileInExternalEditor( + debugger.GetExternalEditor(), frame_sc.line_entry.GetFile(), + frame_sc.line_entry.line)) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Host), std::move(e), + "OpenFileInExternalEditor failed: {0}"); + } else { + already_shown = true; + } + } + + bool show_frame_info = true; + bool show_source = !already_shown; + FrameSelectedCallback(frame_sp.get()); + return frame_sp->GetStatus(output_stream, show_frame_info, show_source); + } + return false; + } else + return false; +} + +void Thread::FrameSelectedCallback(StackFrame *frame) { + if (!frame) + return; + + if (frame->HasDebugInformation() && + (GetProcess()->GetWarningsOptimization() || + GetProcess()->GetWarningsUnsupportedLanguage())) { + SymbolContext sc = + frame->GetSymbolContext(eSymbolContextFunction | eSymbolContextModule); + GetProcess()->PrintWarningOptimization(sc); + GetProcess()->PrintWarningUnsupportedLanguage(sc); + } +} + +lldb::StopInfoSP Thread::GetStopInfo() { + if (m_destroy_called) + return m_stop_info_sp; + + ThreadPlanSP completed_plan_sp(GetCompletedPlan()); + ProcessSP process_sp(GetProcess()); + const uint32_t stop_id = process_sp ? process_sp->GetStopID() : UINT32_MAX; + + // Here we select the stop info according to priorirty: - m_stop_info_sp (if + // not trace) - preset value - completed plan stop info - new value with plan + // from completed plan stack - m_stop_info_sp (trace stop reason is OK now) - + // ask GetPrivateStopInfo to set stop info + + bool have_valid_stop_info = m_stop_info_sp && + m_stop_info_sp ->IsValid() && + m_stop_info_stop_id == stop_id; + bool have_valid_completed_plan = completed_plan_sp && completed_plan_sp->PlanSucceeded(); + bool plan_failed = completed_plan_sp && !completed_plan_sp->PlanSucceeded(); + bool plan_overrides_trace = + have_valid_stop_info && have_valid_completed_plan + && (m_stop_info_sp->GetStopReason() == eStopReasonTrace); + + if (have_valid_stop_info && !plan_overrides_trace && !plan_failed) { + return m_stop_info_sp; + } else if (completed_plan_sp) { + return StopInfo::CreateStopReasonWithPlan( + completed_plan_sp, GetReturnValueObject(), GetExpressionVariable()); + } else { + GetPrivateStopInfo(); + return m_stop_info_sp; + } +} + +void Thread::CalculatePublicStopInfo() { + ResetStopInfo(); + SetStopInfo(GetStopInfo()); +} + +lldb::StopInfoSP Thread::GetPrivateStopInfo(bool calculate) { + if (!calculate) + return m_stop_info_sp; + + if (m_destroy_called) + return m_stop_info_sp; + + ProcessSP process_sp(GetProcess()); + if (process_sp) { + const uint32_t process_stop_id = process_sp->GetStopID(); + if (m_stop_info_stop_id != process_stop_id) { + // We preserve the old stop info for a variety of reasons: + // 1) Someone has already updated it by the time we get here + // 2) We didn't get to execute the breakpoint instruction we stopped at + // 3) This is a virtual step so we didn't actually run + // 4) If this thread wasn't allowed to run the last time round. + if (m_stop_info_sp) { + if (m_stop_info_sp->IsValid() || IsStillAtLastBreakpointHit() || + GetCurrentPlan()->IsVirtualStep() + || GetTemporaryResumeState() == eStateSuspended) + SetStopInfo(m_stop_info_sp); + else + m_stop_info_sp.reset(); + } + + if (!m_stop_info_sp) { + if (!CalculateStopInfo()) + SetStopInfo(StopInfoSP()); + } + } + + // The stop info can be manually set by calling Thread::SetStopInfo() prior + // to this function ever getting called, so we can't rely on + // "m_stop_info_stop_id != process_stop_id" as the condition for the if + // statement below, we must also check the stop info to see if we need to + // override it. See the header documentation in + // Architecture::OverrideStopInfo() for more information on the stop + // info override callback. + if (m_stop_info_override_stop_id != process_stop_id) { + m_stop_info_override_stop_id = process_stop_id; + if (m_stop_info_sp) { + if (const Architecture *arch = + process_sp->GetTarget().GetArchitecturePlugin()) + arch->OverrideStopInfo(*this); + } + } + } + + // If we were resuming the process and it was interrupted, + // return no stop reason. This thread would like to resume. + if (m_stop_info_sp && m_stop_info_sp->WasContinueInterrupted(*this)) + return {}; + + return m_stop_info_sp; +} + +lldb::StopReason Thread::GetStopReason() { + lldb::StopInfoSP stop_info_sp(GetStopInfo()); + if (stop_info_sp) + return stop_info_sp->GetStopReason(); + return eStopReasonNone; +} + +bool Thread::StopInfoIsUpToDate() const { + ProcessSP process_sp(GetProcess()); + if (process_sp) + return m_stop_info_stop_id == process_sp->GetStopID(); + else + return true; // Process is no longer around so stop info is always up to + // date... +} + +void Thread::ResetStopInfo() { + if (m_stop_info_sp) { + m_stop_info_sp.reset(); + } +} + +void Thread::SetStopInfo(const lldb::StopInfoSP &stop_info_sp) { + m_stop_info_sp = stop_info_sp; + if (m_stop_info_sp) { + m_stop_info_sp->MakeStopInfoValid(); + // If we are overriding the ShouldReportStop, do that here: + if (m_override_should_notify != eLazyBoolCalculate) + m_stop_info_sp->OverrideShouldNotify(m_override_should_notify == + eLazyBoolYes); + } + + ProcessSP process_sp(GetProcess()); + if (process_sp) + m_stop_info_stop_id = process_sp->GetStopID(); + else + m_stop_info_stop_id = UINT32_MAX; + Log *log = GetLog(LLDBLog::Thread); + LLDB_LOGF(log, "%p: tid = 0x%" PRIx64 ": stop info = %s (stop_id = %u)", + static_cast<void *>(this), GetID(), + stop_info_sp ? stop_info_sp->GetDescription() : "<NULL>", + m_stop_info_stop_id); +} + +void Thread::SetShouldReportStop(Vote vote) { + if (vote == eVoteNoOpinion) + return; + else { + m_override_should_notify = (vote == eVoteYes ? eLazyBoolYes : eLazyBoolNo); + if (m_stop_info_sp) + m_stop_info_sp->OverrideShouldNotify(m_override_should_notify == + eLazyBoolYes); + } +} + +void Thread::SetStopInfoToNothing() { + // Note, we can't just NULL out the private reason, or the native thread + // implementation will try to go calculate it again. For now, just set it to + // a Unix Signal with an invalid signal number. + SetStopInfo( + StopInfo::CreateStopReasonWithSignal(*this, LLDB_INVALID_SIGNAL_NUMBER)); +} + +bool Thread::ThreadStoppedForAReason() { return (bool)GetPrivateStopInfo(); } + +bool Thread::CheckpointThreadState(ThreadStateCheckpoint &saved_state) { + saved_state.register_backup_sp.reset(); + lldb::StackFrameSP frame_sp(GetStackFrameAtIndex(0)); + if (frame_sp) { + lldb::RegisterCheckpointSP reg_checkpoint_sp( + new RegisterCheckpoint(RegisterCheckpoint::Reason::eExpression)); + if (reg_checkpoint_sp) { + lldb::RegisterContextSP reg_ctx_sp(frame_sp->GetRegisterContext()); + if (reg_ctx_sp && reg_ctx_sp->ReadAllRegisterValues(*reg_checkpoint_sp)) + saved_state.register_backup_sp = reg_checkpoint_sp; + } + } + if (!saved_state.register_backup_sp) + return false; + + saved_state.stop_info_sp = GetStopInfo(); + ProcessSP process_sp(GetProcess()); + if (process_sp) + saved_state.orig_stop_id = process_sp->GetStopID(); + saved_state.current_inlined_depth = GetCurrentInlinedDepth(); + saved_state.m_completed_plan_checkpoint = + GetPlans().CheckpointCompletedPlans(); + + return true; +} + +bool Thread::RestoreRegisterStateFromCheckpoint( + ThreadStateCheckpoint &saved_state) { + if (saved_state.register_backup_sp) { + lldb::StackFrameSP frame_sp(GetStackFrameAtIndex(0)); + if (frame_sp) { + lldb::RegisterContextSP reg_ctx_sp(frame_sp->GetRegisterContext()); + if (reg_ctx_sp) { + bool ret = + reg_ctx_sp->WriteAllRegisterValues(*saved_state.register_backup_sp); + + // Clear out all stack frames as our world just changed. + ClearStackFrames(); + reg_ctx_sp->InvalidateIfNeeded(true); + if (m_unwinder_up) + m_unwinder_up->Clear(); + return ret; + } + } + } + return false; +} + +void Thread::RestoreThreadStateFromCheckpoint( + ThreadStateCheckpoint &saved_state) { + if (saved_state.stop_info_sp) + saved_state.stop_info_sp->MakeStopInfoValid(); + SetStopInfo(saved_state.stop_info_sp); + GetStackFrameList()->SetCurrentInlinedDepth( + saved_state.current_inlined_depth); + GetPlans().RestoreCompletedPlanCheckpoint( + saved_state.m_completed_plan_checkpoint); +} + +StateType Thread::GetState() const { + // If any other threads access this we will need a mutex for it + std::lock_guard<std::recursive_mutex> guard(m_state_mutex); + return m_state; +} + +void Thread::SetState(StateType state) { + std::lock_guard<std::recursive_mutex> guard(m_state_mutex); + m_state = state; +} + +std::string Thread::GetStopDescription() { + StackFrameSP frame_sp = GetStackFrameAtIndex(0); + + if (!frame_sp) + return GetStopDescriptionRaw(); + + auto recognized_frame_sp = frame_sp->GetRecognizedFrame(); + + if (!recognized_frame_sp) + return GetStopDescriptionRaw(); + + std::string recognized_stop_description = + recognized_frame_sp->GetStopDescription(); + + if (!recognized_stop_description.empty()) + return recognized_stop_description; + + return GetStopDescriptionRaw(); +} + +std::string Thread::GetStopDescriptionRaw() { + StopInfoSP stop_info_sp = GetStopInfo(); + std::string raw_stop_description; + if (stop_info_sp && stop_info_sp->IsValid()) { + raw_stop_description = stop_info_sp->GetDescription(); + assert((!raw_stop_description.empty() || + stop_info_sp->GetStopReason() == eStopReasonNone) && + "StopInfo returned an empty description."); + } + return raw_stop_description; +} + +void Thread::WillStop() { + ThreadPlan *current_plan = GetCurrentPlan(); + + // FIXME: I may decide to disallow threads with no plans. In which + // case this should go to an assert. + + if (!current_plan) + return; + + current_plan->WillStop(); +} + +void Thread::SetupForResume() { + if (GetResumeState() != eStateSuspended) { + // If we're at a breakpoint push the step-over breakpoint plan. Do this + // before telling the current plan it will resume, since we might change + // what the current plan is. + + lldb::RegisterContextSP reg_ctx_sp(GetRegisterContext()); + if (reg_ctx_sp) { + const addr_t thread_pc = reg_ctx_sp->GetPC(); + BreakpointSiteSP bp_site_sp = + GetProcess()->GetBreakpointSiteList().FindByAddress(thread_pc); + if (bp_site_sp) { + // Note, don't assume there's a ThreadPlanStepOverBreakpoint, the + // target may not require anything special to step over a breakpoint. + + ThreadPlan *cur_plan = GetCurrentPlan(); + + bool push_step_over_bp_plan = false; + if (cur_plan->GetKind() == ThreadPlan::eKindStepOverBreakpoint) { + ThreadPlanStepOverBreakpoint *bp_plan = + (ThreadPlanStepOverBreakpoint *)cur_plan; + if (bp_plan->GetBreakpointLoadAddress() != thread_pc) + push_step_over_bp_plan = true; + } else + push_step_over_bp_plan = true; + + if (push_step_over_bp_plan) { + ThreadPlanSP step_bp_plan_sp(new ThreadPlanStepOverBreakpoint(*this)); + if (step_bp_plan_sp) { + step_bp_plan_sp->SetPrivate(true); + + if (GetCurrentPlan()->RunState() != eStateStepping) { + ThreadPlanStepOverBreakpoint *step_bp_plan = + static_cast<ThreadPlanStepOverBreakpoint *>( + step_bp_plan_sp.get()); + step_bp_plan->SetAutoContinue(true); + } + QueueThreadPlan(step_bp_plan_sp, false); + } + } + } + } + } +} + +bool Thread::ShouldResume(StateType resume_state) { + // At this point clear the completed plan stack. + GetPlans().WillResume(); + m_override_should_notify = eLazyBoolCalculate; + + StateType prev_resume_state = GetTemporaryResumeState(); + + SetTemporaryResumeState(resume_state); + + lldb::ThreadSP backing_thread_sp(GetBackingThread()); + if (backing_thread_sp) + backing_thread_sp->SetTemporaryResumeState(resume_state); + + // Make sure m_stop_info_sp is valid. Don't do this for threads we suspended + // in the previous run. + if (prev_resume_state != eStateSuspended) + GetPrivateStopInfo(); + + // This is a little dubious, but we are trying to limit how often we actually + // fetch stop info from the target, 'cause that slows down single stepping. + // So assume that if we got to the point where we're about to resume, and we + // haven't yet had to fetch the stop reason, then it doesn't need to know + // about the fact that we are resuming... + const uint32_t process_stop_id = GetProcess()->GetStopID(); + if (m_stop_info_stop_id == process_stop_id && + (m_stop_info_sp && m_stop_info_sp->IsValid())) { + StopInfo *stop_info = GetPrivateStopInfo().get(); + if (stop_info) + stop_info->WillResume(resume_state); + } + + // Tell all the plans that we are about to resume in case they need to clear + // any state. We distinguish between the plan on the top of the stack and the + // lower plans in case a plan needs to do any special business before it + // runs. + + bool need_to_resume = false; + ThreadPlan *plan_ptr = GetCurrentPlan(); + if (plan_ptr) { + need_to_resume = plan_ptr->WillResume(resume_state, true); + + while ((plan_ptr = GetPreviousPlan(plan_ptr)) != nullptr) { + plan_ptr->WillResume(resume_state, false); + } + + // If the WillResume for the plan says we are faking a resume, then it will + // have set an appropriate stop info. In that case, don't reset it here. + + if (need_to_resume && resume_state != eStateSuspended) { + m_stop_info_sp.reset(); + } + } + + if (need_to_resume) { + ClearStackFrames(); + // Let Thread subclasses do any special work they need to prior to resuming + WillResume(resume_state); + } + + return need_to_resume; +} + +void Thread::DidResume() { + SetResumeSignal(LLDB_INVALID_SIGNAL_NUMBER); + // This will get recomputed each time when we stop. + SetShouldRunBeforePublicStop(false); +} + +void Thread::DidStop() { SetState(eStateStopped); } + +bool Thread::ShouldStop(Event *event_ptr) { + ThreadPlan *current_plan = GetCurrentPlan(); + + bool should_stop = true; + + Log *log = GetLog(LLDBLog::Step); + + if (GetResumeState() == eStateSuspended) { + LLDB_LOGF(log, + "Thread::%s for tid = 0x%4.4" PRIx64 " 0x%4.4" PRIx64 + ", should_stop = 0 (ignore since thread was suspended)", + __FUNCTION__, GetID(), GetProtocolID()); + return false; + } + + if (GetTemporaryResumeState() == eStateSuspended) { + LLDB_LOGF(log, + "Thread::%s for tid = 0x%4.4" PRIx64 " 0x%4.4" PRIx64 + ", should_stop = 0 (ignore since thread was suspended)", + __FUNCTION__, GetID(), GetProtocolID()); + return false; + } + + // Based on the current thread plan and process stop info, check if this + // thread caused the process to stop. NOTE: this must take place before the + // plan is moved from the current plan stack to the completed plan stack. + if (!ThreadStoppedForAReason()) { + LLDB_LOGF(log, + "Thread::%s for tid = 0x%4.4" PRIx64 " 0x%4.4" PRIx64 + ", pc = 0x%16.16" PRIx64 + ", should_stop = 0 (ignore since no stop reason)", + __FUNCTION__, GetID(), GetProtocolID(), + GetRegisterContext() ? GetRegisterContext()->GetPC() + : LLDB_INVALID_ADDRESS); + return false; + } + + // Clear the "must run me before stop" if it was set: + SetShouldRunBeforePublicStop(false); + + if (log) { + LLDB_LOGF(log, + "Thread::%s(%p) for tid = 0x%4.4" PRIx64 " 0x%4.4" PRIx64 + ", pc = 0x%16.16" PRIx64, + __FUNCTION__, static_cast<void *>(this), GetID(), GetProtocolID(), + GetRegisterContext() ? GetRegisterContext()->GetPC() + : LLDB_INVALID_ADDRESS); + LLDB_LOGF(log, "^^^^^^^^ Thread::ShouldStop Begin ^^^^^^^^"); + StreamString s; + s.IndentMore(); + GetProcess()->DumpThreadPlansForTID( + s, GetID(), eDescriptionLevelVerbose, true /* internal */, + false /* condense_trivial */, true /* skip_unreported */); + LLDB_LOGF(log, "Plan stack initial state:\n%s", s.GetData()); + } + + // The top most plan always gets to do the trace log... + current_plan->DoTraceLog(); + + // First query the stop info's ShouldStopSynchronous. This handles + // "synchronous" stop reasons, for example the breakpoint command on internal + // breakpoints. If a synchronous stop reason says we should not stop, then + // we don't have to do any more work on this stop. + StopInfoSP private_stop_info(GetPrivateStopInfo()); + if (private_stop_info && + !private_stop_info->ShouldStopSynchronous(event_ptr)) { + LLDB_LOGF(log, "StopInfo::ShouldStop async callback says we should not " + "stop, returning ShouldStop of false."); + return false; + } + + // If we've already been restarted, don't query the plans since the state + // they would examine is not current. + if (Process::ProcessEventData::GetRestartedFromEvent(event_ptr)) + return false; + + // Before the plans see the state of the world, calculate the current inlined + // depth. + GetStackFrameList()->CalculateCurrentInlinedDepth(); + + // If the base plan doesn't understand why we stopped, then we have to find a + // plan that does. If that plan is still working, then we don't need to do + // any more work. If the plan that explains the stop is done, then we should + // pop all the plans below it, and pop it, and then let the plans above it + // decide whether they still need to do more work. + + bool done_processing_current_plan = false; + + if (!current_plan->PlanExplainsStop(event_ptr)) { + if (current_plan->TracerExplainsStop()) { + done_processing_current_plan = true; + should_stop = false; + } else { + // If the current plan doesn't explain the stop, then find one that does + // and let it handle the situation. + ThreadPlan *plan_ptr = current_plan; + while ((plan_ptr = GetPreviousPlan(plan_ptr)) != nullptr) { + if (plan_ptr->PlanExplainsStop(event_ptr)) { + LLDB_LOGF(log, "Plan %s explains stop.", plan_ptr->GetName()); + + should_stop = plan_ptr->ShouldStop(event_ptr); + + // plan_ptr explains the stop, next check whether plan_ptr is done, + // if so, then we should take it and all the plans below it off the + // stack. + + if (plan_ptr->MischiefManaged()) { + // We're going to pop the plans up to and including the plan that + // explains the stop. + ThreadPlan *prev_plan_ptr = GetPreviousPlan(plan_ptr); + + do { + if (should_stop) + current_plan->WillStop(); + PopPlan(); + } while ((current_plan = GetCurrentPlan()) != prev_plan_ptr); + // Now, if the responsible plan was not "Okay to discard" then + // we're done, otherwise we forward this to the next plan in the + // stack below. + done_processing_current_plan = + (plan_ptr->IsControllingPlan() && !plan_ptr->OkayToDiscard()); + } else { + bool should_force_run = plan_ptr->ShouldRunBeforePublicStop(); + if (should_force_run) { + SetShouldRunBeforePublicStop(true); + should_stop = false; + } + done_processing_current_plan = true; + } + break; + } + } + } + } + + if (!done_processing_current_plan) { + bool override_stop = false; + + // We're starting from the base plan, so just let it decide; + if (current_plan->IsBasePlan()) { + should_stop = current_plan->ShouldStop(event_ptr); + LLDB_LOGF(log, "Base plan says should stop: %i.", should_stop); + } else { + // Otherwise, don't let the base plan override what the other plans say + // to do, since presumably if there were other plans they would know what + // to do... + while (true) { + if (current_plan->IsBasePlan()) + break; + + should_stop = current_plan->ShouldStop(event_ptr); + LLDB_LOGF(log, "Plan %s should stop: %d.", current_plan->GetName(), + should_stop); + if (current_plan->MischiefManaged()) { + if (should_stop) + current_plan->WillStop(); + + if (current_plan->ShouldAutoContinue(event_ptr)) { + override_stop = true; + LLDB_LOGF(log, "Plan %s auto-continue: true.", + current_plan->GetName()); + } + + // If a Controlling Plan wants to stop, we let it. Otherwise, see if + // the plan's parent wants to stop. + + PopPlan(); + if (should_stop && current_plan->IsControllingPlan() && + !current_plan->OkayToDiscard()) { + break; + } + + current_plan = GetCurrentPlan(); + if (current_plan == nullptr) { + break; + } + } else { + break; + } + } + } + + if (override_stop) + should_stop = false; + } + + // One other potential problem is that we set up a controlling plan, then stop + // in before it is complete - for instance by hitting a breakpoint during a + // step-over - then do some step/finish/etc operations that wind up past the + // end point condition of the initial plan. We don't want to strand the + // original plan on the stack, This code clears stale plans off the stack. + + if (should_stop) { + ThreadPlan *plan_ptr = GetCurrentPlan(); + + // Discard the stale plans and all plans below them in the stack, plus move + // the completed plans to the completed plan stack + while (!plan_ptr->IsBasePlan()) { + bool stale = plan_ptr->IsPlanStale(); + ThreadPlan *examined_plan = plan_ptr; + plan_ptr = GetPreviousPlan(examined_plan); + + if (stale) { + LLDB_LOGF( + log, + "Plan %s being discarded in cleanup, it says it is already done.", + examined_plan->GetName()); + while (GetCurrentPlan() != examined_plan) { + DiscardPlan(); + } + if (examined_plan->IsPlanComplete()) { + // plan is complete but does not explain the stop (example: step to a + // line with breakpoint), let us move the plan to + // completed_plan_stack anyway + PopPlan(); + } else + DiscardPlan(); + } + } + } + + if (log) { + StreamString s; + s.IndentMore(); + GetProcess()->DumpThreadPlansForTID( + s, GetID(), eDescriptionLevelVerbose, true /* internal */, + false /* condense_trivial */, true /* skip_unreported */); + LLDB_LOGF(log, "Plan stack final state:\n%s", s.GetData()); + LLDB_LOGF(log, "vvvvvvvv Thread::ShouldStop End (returning %i) vvvvvvvv", + should_stop); + } + return should_stop; +} + +Vote Thread::ShouldReportStop(Event *event_ptr) { + StateType thread_state = GetResumeState(); + StateType temp_thread_state = GetTemporaryResumeState(); + + Log *log = GetLog(LLDBLog::Step); + + if (thread_state == eStateSuspended || thread_state == eStateInvalid) { + LLDB_LOGF(log, + "Thread::ShouldReportStop() tid = 0x%4.4" PRIx64 + ": returning vote %i (state was suspended or invalid)", + GetID(), eVoteNoOpinion); + return eVoteNoOpinion; + } + + if (temp_thread_state == eStateSuspended || + temp_thread_state == eStateInvalid) { + LLDB_LOGF(log, + "Thread::ShouldReportStop() tid = 0x%4.4" PRIx64 + ": returning vote %i (temporary state was suspended or invalid)", + GetID(), eVoteNoOpinion); + return eVoteNoOpinion; + } + + if (!ThreadStoppedForAReason()) { + LLDB_LOGF(log, + "Thread::ShouldReportStop() tid = 0x%4.4" PRIx64 + ": returning vote %i (thread didn't stop for a reason.)", + GetID(), eVoteNoOpinion); + return eVoteNoOpinion; + } + + if (GetPlans().AnyCompletedPlans()) { + // Pass skip_private = false to GetCompletedPlan, since we want to ask + // the last plan, regardless of whether it is private or not. + LLDB_LOGF(log, + "Thread::ShouldReportStop() tid = 0x%4.4" PRIx64 + ": returning vote for complete stack's back plan", + GetID()); + return GetPlans().GetCompletedPlan(false)->ShouldReportStop(event_ptr); + } else { + Vote thread_vote = eVoteNoOpinion; + ThreadPlan *plan_ptr = GetCurrentPlan(); + while (true) { + if (plan_ptr->PlanExplainsStop(event_ptr)) { + thread_vote = plan_ptr->ShouldReportStop(event_ptr); + break; + } + if (plan_ptr->IsBasePlan()) + break; + else + plan_ptr = GetPreviousPlan(plan_ptr); + } + LLDB_LOGF(log, + "Thread::ShouldReportStop() tid = 0x%4.4" PRIx64 + ": returning vote %i for current plan", + GetID(), thread_vote); + + return thread_vote; + } +} + +Vote Thread::ShouldReportRun(Event *event_ptr) { + StateType thread_state = GetResumeState(); + + if (thread_state == eStateSuspended || thread_state == eStateInvalid) { + return eVoteNoOpinion; + } + + Log *log = GetLog(LLDBLog::Step); + if (GetPlans().AnyCompletedPlans()) { + // Pass skip_private = false to GetCompletedPlan, since we want to ask + // the last plan, regardless of whether it is private or not. + LLDB_LOGF(log, + "Current Plan for thread %d(%p) (0x%4.4" PRIx64 + ", %s): %s being asked whether we should report run.", + GetIndexID(), static_cast<void *>(this), GetID(), + StateAsCString(GetTemporaryResumeState()), + GetCompletedPlan()->GetName()); + + return GetPlans().GetCompletedPlan(false)->ShouldReportRun(event_ptr); + } else { + LLDB_LOGF(log, + "Current Plan for thread %d(%p) (0x%4.4" PRIx64 + ", %s): %s being asked whether we should report run.", + GetIndexID(), static_cast<void *>(this), GetID(), + StateAsCString(GetTemporaryResumeState()), + GetCurrentPlan()->GetName()); + + return GetCurrentPlan()->ShouldReportRun(event_ptr); + } +} + +bool Thread::MatchesSpec(const ThreadSpec *spec) { + return (spec == nullptr) ? true : spec->ThreadPassesBasicTests(*this); +} + +ThreadPlanStack &Thread::GetPlans() const { + ThreadPlanStack *plans = GetProcess()->FindThreadPlans(GetID()); + if (plans) + return *plans; + + // History threads don't have a thread plan, but they do ask get asked to + // describe themselves, which usually involves pulling out the stop reason. + // That in turn will check for a completed plan on the ThreadPlanStack. + // Instead of special-casing at that point, we return a Stack with a + // ThreadPlanNull as its base plan. That will give the right answers to the + // queries GetDescription makes, and only assert if you try to run the thread. + if (!m_null_plan_stack_up) + m_null_plan_stack_up = std::make_unique<ThreadPlanStack>(*this, true); + return *m_null_plan_stack_up; +} + +void Thread::PushPlan(ThreadPlanSP thread_plan_sp) { + assert(thread_plan_sp && "Don't push an empty thread plan."); + + Log *log = GetLog(LLDBLog::Step); + if (log) { + StreamString s; + thread_plan_sp->GetDescription(&s, lldb::eDescriptionLevelFull); + LLDB_LOGF(log, "Thread::PushPlan(0x%p): \"%s\", tid = 0x%4.4" PRIx64 ".", + static_cast<void *>(this), s.GetData(), + thread_plan_sp->GetThread().GetID()); + } + + GetPlans().PushPlan(std::move(thread_plan_sp)); +} + +void Thread::PopPlan() { + Log *log = GetLog(LLDBLog::Step); + ThreadPlanSP popped_plan_sp = GetPlans().PopPlan(); + if (log) { + LLDB_LOGF(log, "Popping plan: \"%s\", tid = 0x%4.4" PRIx64 ".", + popped_plan_sp->GetName(), popped_plan_sp->GetThread().GetID()); + } +} + +void Thread::DiscardPlan() { + Log *log = GetLog(LLDBLog::Step); + ThreadPlanSP discarded_plan_sp = GetPlans().DiscardPlan(); + + LLDB_LOGF(log, "Discarding plan: \"%s\", tid = 0x%4.4" PRIx64 ".", + discarded_plan_sp->GetName(), + discarded_plan_sp->GetThread().GetID()); +} + +void Thread::AutoCompleteThreadPlans(CompletionRequest &request) const { + const ThreadPlanStack &plans = GetPlans(); + if (!plans.AnyPlans()) + return; + + // Iterate from the second plan (index: 1) to skip the base plan. + ThreadPlanSP p; + uint32_t i = 1; + while ((p = plans.GetPlanByIndex(i, false))) { + StreamString strm; + p->GetDescription(&strm, eDescriptionLevelInitial); + request.TryCompleteCurrentArg(std::to_string(i), strm.GetString()); + i++; + } +} + +ThreadPlan *Thread::GetCurrentPlan() const { + return GetPlans().GetCurrentPlan().get(); +} + +ThreadPlanSP Thread::GetCompletedPlan() const { + return GetPlans().GetCompletedPlan(); +} + +ValueObjectSP Thread::GetReturnValueObject() const { + return GetPlans().GetReturnValueObject(); +} + +ExpressionVariableSP Thread::GetExpressionVariable() const { + return GetPlans().GetExpressionVariable(); +} + +bool Thread::IsThreadPlanDone(ThreadPlan *plan) const { + return GetPlans().IsPlanDone(plan); +} + +bool Thread::WasThreadPlanDiscarded(ThreadPlan *plan) const { + return GetPlans().WasPlanDiscarded(plan); +} + +bool Thread::CompletedPlanOverridesBreakpoint() const { + return GetPlans().AnyCompletedPlans(); +} + +ThreadPlan *Thread::GetPreviousPlan(ThreadPlan *current_plan) const{ + return GetPlans().GetPreviousPlan(current_plan); +} + +Status Thread::QueueThreadPlan(ThreadPlanSP &thread_plan_sp, + bool abort_other_plans) { + Status status; + StreamString s; + if (!thread_plan_sp->ValidatePlan(&s)) { + DiscardThreadPlansUpToPlan(thread_plan_sp); + thread_plan_sp.reset(); + status.SetErrorString(s.GetString()); + return status; + } + + if (abort_other_plans) + DiscardThreadPlans(true); + + PushPlan(thread_plan_sp); + + // This seems a little funny, but I don't want to have to split up the + // constructor and the DidPush in the scripted plan, that seems annoying. + // That means the constructor has to be in DidPush. So I have to validate the + // plan AFTER pushing it, and then take it off again... + if (!thread_plan_sp->ValidatePlan(&s)) { + DiscardThreadPlansUpToPlan(thread_plan_sp); + thread_plan_sp.reset(); + status.SetErrorString(s.GetString()); + return status; + } + + return status; +} + +bool Thread::DiscardUserThreadPlansUpToIndex(uint32_t plan_index) { + // Count the user thread plans from the back end to get the number of the one + // we want to discard: + + ThreadPlan *up_to_plan_ptr = GetPlans().GetPlanByIndex(plan_index).get(); + if (up_to_plan_ptr == nullptr) + return false; + + DiscardThreadPlansUpToPlan(up_to_plan_ptr); + return true; +} + +void Thread::DiscardThreadPlansUpToPlan(lldb::ThreadPlanSP &up_to_plan_sp) { + DiscardThreadPlansUpToPlan(up_to_plan_sp.get()); +} + +void Thread::DiscardThreadPlansUpToPlan(ThreadPlan *up_to_plan_ptr) { + Log *log = GetLog(LLDBLog::Step); + LLDB_LOGF(log, + "Discarding thread plans for thread tid = 0x%4.4" PRIx64 + ", up to %p", + GetID(), static_cast<void *>(up_to_plan_ptr)); + GetPlans().DiscardPlansUpToPlan(up_to_plan_ptr); +} + +void Thread::DiscardThreadPlans(bool force) { + Log *log = GetLog(LLDBLog::Step); + if (log) { + LLDB_LOGF(log, + "Discarding thread plans for thread (tid = 0x%4.4" PRIx64 + ", force %d)", + GetID(), force); + } + + if (force) { + GetPlans().DiscardAllPlans(); + return; + } + GetPlans().DiscardConsultingControllingPlans(); +} + +Status Thread::UnwindInnermostExpression() { + Status error; + ThreadPlan *innermost_expr_plan = GetPlans().GetInnermostExpression(); + if (!innermost_expr_plan) { + error.SetErrorString("No expressions currently active on this thread"); + return error; + } + DiscardThreadPlansUpToPlan(innermost_expr_plan); + return error; +} + +ThreadPlanSP Thread::QueueBasePlan(bool abort_other_plans) { + ThreadPlanSP thread_plan_sp(new ThreadPlanBase(*this)); + QueueThreadPlan(thread_plan_sp, abort_other_plans); + return thread_plan_sp; +} + +ThreadPlanSP Thread::QueueThreadPlanForStepSingleInstruction( + bool step_over, bool abort_other_plans, bool stop_other_threads, + Status &status) { + ThreadPlanSP thread_plan_sp(new ThreadPlanStepInstruction( + *this, step_over, stop_other_threads, eVoteNoOpinion, eVoteNoOpinion)); + status = QueueThreadPlan(thread_plan_sp, abort_other_plans); + return thread_plan_sp; +} + +ThreadPlanSP Thread::QueueThreadPlanForStepOverRange( + bool abort_other_plans, const AddressRange &range, + const SymbolContext &addr_context, lldb::RunMode stop_other_threads, + Status &status, LazyBool step_out_avoids_code_withoug_debug_info) { + ThreadPlanSP thread_plan_sp; + thread_plan_sp = std::make_shared<ThreadPlanStepOverRange>( + *this, range, addr_context, stop_other_threads, + step_out_avoids_code_withoug_debug_info); + + status = QueueThreadPlan(thread_plan_sp, abort_other_plans); + return thread_plan_sp; +} + +// Call the QueueThreadPlanForStepOverRange method which takes an address +// range. +ThreadPlanSP Thread::QueueThreadPlanForStepOverRange( + bool abort_other_plans, const LineEntry &line_entry, + const SymbolContext &addr_context, lldb::RunMode stop_other_threads, + Status &status, LazyBool step_out_avoids_code_withoug_debug_info) { + const bool include_inlined_functions = true; + auto address_range = + line_entry.GetSameLineContiguousAddressRange(include_inlined_functions); + return QueueThreadPlanForStepOverRange( + abort_other_plans, address_range, addr_context, stop_other_threads, + status, step_out_avoids_code_withoug_debug_info); +} + +ThreadPlanSP Thread::QueueThreadPlanForStepInRange( + bool abort_other_plans, const AddressRange &range, + const SymbolContext &addr_context, const char *step_in_target, + lldb::RunMode stop_other_threads, Status &status, + LazyBool step_in_avoids_code_without_debug_info, + LazyBool step_out_avoids_code_without_debug_info) { + ThreadPlanSP thread_plan_sp(new ThreadPlanStepInRange( + *this, range, addr_context, step_in_target, stop_other_threads, + step_in_avoids_code_without_debug_info, + step_out_avoids_code_without_debug_info)); + status = QueueThreadPlan(thread_plan_sp, abort_other_plans); + return thread_plan_sp; +} + +// Call the QueueThreadPlanForStepInRange method which takes an address range. +ThreadPlanSP Thread::QueueThreadPlanForStepInRange( + bool abort_other_plans, const LineEntry &line_entry, + const SymbolContext &addr_context, const char *step_in_target, + lldb::RunMode stop_other_threads, Status &status, + LazyBool step_in_avoids_code_without_debug_info, + LazyBool step_out_avoids_code_without_debug_info) { + const bool include_inlined_functions = false; + return QueueThreadPlanForStepInRange( + abort_other_plans, + line_entry.GetSameLineContiguousAddressRange(include_inlined_functions), + addr_context, step_in_target, stop_other_threads, status, + step_in_avoids_code_without_debug_info, + step_out_avoids_code_without_debug_info); +} + +ThreadPlanSP Thread::QueueThreadPlanForStepOut( + bool abort_other_plans, SymbolContext *addr_context, bool first_insn, + bool stop_other_threads, Vote report_stop_vote, Vote report_run_vote, + uint32_t frame_idx, Status &status, + LazyBool step_out_avoids_code_without_debug_info) { + ThreadPlanSP thread_plan_sp(new ThreadPlanStepOut( + *this, addr_context, first_insn, stop_other_threads, report_stop_vote, + report_run_vote, frame_idx, step_out_avoids_code_without_debug_info)); + + status = QueueThreadPlan(thread_plan_sp, abort_other_plans); + return thread_plan_sp; +} + +ThreadPlanSP Thread::QueueThreadPlanForStepOutNoShouldStop( + bool abort_other_plans, SymbolContext *addr_context, bool first_insn, + bool stop_other_threads, Vote report_stop_vote, Vote report_run_vote, + uint32_t frame_idx, Status &status, bool continue_to_next_branch) { + const bool calculate_return_value = + false; // No need to calculate the return value here. + ThreadPlanSP thread_plan_sp(new ThreadPlanStepOut( + *this, addr_context, first_insn, stop_other_threads, report_stop_vote, + report_run_vote, frame_idx, eLazyBoolNo, continue_to_next_branch, + calculate_return_value)); + + ThreadPlanStepOut *new_plan = + static_cast<ThreadPlanStepOut *>(thread_plan_sp.get()); + new_plan->ClearShouldStopHereCallbacks(); + + status = QueueThreadPlan(thread_plan_sp, abort_other_plans); + return thread_plan_sp; +} + +ThreadPlanSP Thread::QueueThreadPlanForStepThrough(StackID &return_stack_id, + bool abort_other_plans, + bool stop_other_threads, + Status &status) { + ThreadPlanSP thread_plan_sp( + new ThreadPlanStepThrough(*this, return_stack_id, stop_other_threads)); + if (!thread_plan_sp || !thread_plan_sp->ValidatePlan(nullptr)) + return ThreadPlanSP(); + + status = QueueThreadPlan(thread_plan_sp, abort_other_plans); + return thread_plan_sp; +} + +ThreadPlanSP Thread::QueueThreadPlanForRunToAddress(bool abort_other_plans, + Address &target_addr, + bool stop_other_threads, + Status &status) { + ThreadPlanSP thread_plan_sp( + new ThreadPlanRunToAddress(*this, target_addr, stop_other_threads)); + + status = QueueThreadPlan(thread_plan_sp, abort_other_plans); + return thread_plan_sp; +} + +ThreadPlanSP Thread::QueueThreadPlanForStepUntil( + bool abort_other_plans, lldb::addr_t *address_list, size_t num_addresses, + bool stop_other_threads, uint32_t frame_idx, Status &status) { + ThreadPlanSP thread_plan_sp(new ThreadPlanStepUntil( + *this, address_list, num_addresses, stop_other_threads, frame_idx)); + + status = QueueThreadPlan(thread_plan_sp, abort_other_plans); + return thread_plan_sp; +} + +lldb::ThreadPlanSP Thread::QueueThreadPlanForStepScripted( + bool abort_other_plans, const char *class_name, + StructuredData::ObjectSP extra_args_sp, bool stop_other_threads, + Status &status) { + + ThreadPlanSP thread_plan_sp(new ThreadPlanPython( + *this, class_name, StructuredDataImpl(extra_args_sp))); + thread_plan_sp->SetStopOthers(stop_other_threads); + status = QueueThreadPlan(thread_plan_sp, abort_other_plans); + return thread_plan_sp; +} + +uint32_t Thread::GetIndexID() const { return m_index_id; } + +TargetSP Thread::CalculateTarget() { + TargetSP target_sp; + ProcessSP process_sp(GetProcess()); + if (process_sp) + target_sp = process_sp->CalculateTarget(); + return target_sp; +} + +ProcessSP Thread::CalculateProcess() { return GetProcess(); } + +ThreadSP Thread::CalculateThread() { return shared_from_this(); } + +StackFrameSP Thread::CalculateStackFrame() { return StackFrameSP(); } + +void Thread::CalculateExecutionContext(ExecutionContext &exe_ctx) { + exe_ctx.SetContext(shared_from_this()); +} + +StackFrameListSP Thread::GetStackFrameList() { + std::lock_guard<std::recursive_mutex> guard(m_frame_mutex); + + if (!m_curr_frames_sp) + m_curr_frames_sp = + std::make_shared<StackFrameList>(*this, m_prev_frames_sp, true); + + return m_curr_frames_sp; +} + +std::optional<addr_t> Thread::GetPreviousFrameZeroPC() { + return m_prev_framezero_pc; +} + +void Thread::ClearStackFrames() { + std::lock_guard<std::recursive_mutex> guard(m_frame_mutex); + + GetUnwinder().Clear(); + m_prev_framezero_pc.reset(); + if (RegisterContextSP reg_ctx_sp = GetRegisterContext()) + m_prev_framezero_pc = reg_ctx_sp->GetPC(); + + // Only store away the old "reference" StackFrameList if we got all its + // frames: + // FIXME: At some point we can try to splice in the frames we have fetched + // into the new frame as we make it, but let's not try that now. + if (m_curr_frames_sp && m_curr_frames_sp->GetAllFramesFetched()) + m_prev_frames_sp.swap(m_curr_frames_sp); + m_curr_frames_sp.reset(); + + m_extended_info.reset(); + m_extended_info_fetched = false; +} + +lldb::StackFrameSP Thread::GetFrameWithConcreteFrameIndex(uint32_t unwind_idx) { + return GetStackFrameList()->GetFrameWithConcreteFrameIndex(unwind_idx); +} + +Status Thread::ReturnFromFrameWithIndex(uint32_t frame_idx, + lldb::ValueObjectSP return_value_sp, + bool broadcast) { + StackFrameSP frame_sp = GetStackFrameAtIndex(frame_idx); + Status return_error; + + if (!frame_sp) { + return_error.SetErrorStringWithFormat( + "Could not find frame with index %d in thread 0x%" PRIx64 ".", + frame_idx, GetID()); + } + + return ReturnFromFrame(frame_sp, return_value_sp, broadcast); +} + +Status Thread::ReturnFromFrame(lldb::StackFrameSP frame_sp, + lldb::ValueObjectSP return_value_sp, + bool broadcast) { + Status return_error; + + if (!frame_sp) { + return_error.SetErrorString("Can't return to a null frame."); + return return_error; + } + + Thread *thread = frame_sp->GetThread().get(); + uint32_t older_frame_idx = frame_sp->GetFrameIndex() + 1; + StackFrameSP older_frame_sp = thread->GetStackFrameAtIndex(older_frame_idx); + if (!older_frame_sp) { + return_error.SetErrorString("No older frame to return to."); + return return_error; + } + + if (return_value_sp) { + lldb::ABISP abi = thread->GetProcess()->GetABI(); + if (!abi) { + return_error.SetErrorString("Could not find ABI to set return value."); + return return_error; + } + SymbolContext sc = frame_sp->GetSymbolContext(eSymbolContextFunction); + + // FIXME: ValueObject::Cast doesn't currently work correctly, at least not + // for scalars. + // Turn that back on when that works. + if (/* DISABLES CODE */ (false) && sc.function != nullptr) { + Type *function_type = sc.function->GetType(); + if (function_type) { + CompilerType return_type = + sc.function->GetCompilerType().GetFunctionReturnType(); + if (return_type) { + StreamString s; + return_type.DumpTypeDescription(&s); + ValueObjectSP cast_value_sp = return_value_sp->Cast(return_type); + if (cast_value_sp) { + cast_value_sp->SetFormat(eFormatHex); + return_value_sp = cast_value_sp; + } + } + } + } + + return_error = abi->SetReturnValueObject(older_frame_sp, return_value_sp); + if (!return_error.Success()) + return return_error; + } + + // Now write the return registers for the chosen frame: Note, we can't use + // ReadAllRegisterValues->WriteAllRegisterValues, since the read & write cook + // their data + + StackFrameSP youngest_frame_sp = thread->GetStackFrameAtIndex(0); + if (youngest_frame_sp) { + lldb::RegisterContextSP reg_ctx_sp(youngest_frame_sp->GetRegisterContext()); + if (reg_ctx_sp) { + bool copy_success = reg_ctx_sp->CopyFromRegisterContext( + older_frame_sp->GetRegisterContext()); + if (copy_success) { + thread->DiscardThreadPlans(true); + thread->ClearStackFrames(); + if (broadcast && EventTypeHasListeners(eBroadcastBitStackChanged)) { + auto data_sp = std::make_shared<ThreadEventData>(shared_from_this()); + BroadcastEvent(eBroadcastBitStackChanged, data_sp); + } + } else { + return_error.SetErrorString("Could not reset register values."); + } + } else { + return_error.SetErrorString("Frame has no register context."); + } + } else { + return_error.SetErrorString("Returned past top frame."); + } + return return_error; +} + +static void DumpAddressList(Stream &s, const std::vector<Address> &list, + ExecutionContextScope *exe_scope) { + for (size_t n = 0; n < list.size(); n++) { + s << "\t"; + list[n].Dump(&s, exe_scope, Address::DumpStyleResolvedDescription, + Address::DumpStyleSectionNameOffset); + s << "\n"; + } +} + +Status Thread::JumpToLine(const FileSpec &file, uint32_t line, + bool can_leave_function, std::string *warnings) { + ExecutionContext exe_ctx(GetStackFrameAtIndex(0)); + Target *target = exe_ctx.GetTargetPtr(); + TargetSP target_sp = exe_ctx.GetTargetSP(); + RegisterContext *reg_ctx = exe_ctx.GetRegisterContext(); + StackFrame *frame = exe_ctx.GetFramePtr(); + const SymbolContext &sc = frame->GetSymbolContext(eSymbolContextFunction); + + // Find candidate locations. + std::vector<Address> candidates, within_function, outside_function; + target->GetImages().FindAddressesForLine(target_sp, file, line, sc.function, + within_function, outside_function); + + // If possible, we try and stay within the current function. Within a + // function, we accept multiple locations (optimized code may do this, + // there's no solution here so we do the best we can). However if we're + // trying to leave the function, we don't know how to pick the right + // location, so if there's more than one then we bail. + if (!within_function.empty()) + candidates = within_function; + else if (outside_function.size() == 1 && can_leave_function) + candidates = outside_function; + + // Check if we got anything. + if (candidates.empty()) { + if (outside_function.empty()) { + return Status("Cannot locate an address for %s:%i.", + file.GetFilename().AsCString(), line); + } else if (outside_function.size() == 1) { + return Status("%s:%i is outside the current function.", + file.GetFilename().AsCString(), line); + } else { + StreamString sstr; + DumpAddressList(sstr, outside_function, target); + return Status("%s:%i has multiple candidate locations:\n%s", + file.GetFilename().AsCString(), line, sstr.GetData()); + } + } + + // Accept the first location, warn about any others. + Address dest = candidates[0]; + if (warnings && candidates.size() > 1) { + StreamString sstr; + sstr.Printf("%s:%i appears multiple times in this function, selecting the " + "first location:\n", + file.GetFilename().AsCString(), line); + DumpAddressList(sstr, candidates, target); + *warnings = std::string(sstr.GetString()); + } + + if (!reg_ctx->SetPC(dest)) + return Status("Cannot change PC to target address."); + + return Status(); +} + +bool Thread::DumpUsingFormat(Stream &strm, uint32_t frame_idx, + const FormatEntity::Entry *format) { + ExecutionContext exe_ctx(shared_from_this()); + Process *process = exe_ctx.GetProcessPtr(); + if (!process || !format) + return false; + + StackFrameSP frame_sp; + SymbolContext frame_sc; + if (frame_idx != LLDB_INVALID_FRAME_ID) { + frame_sp = GetStackFrameAtIndex(frame_idx); + if (frame_sp) { + exe_ctx.SetFrameSP(frame_sp); + frame_sc = frame_sp->GetSymbolContext(eSymbolContextEverything); + } + } + + return FormatEntity::Format(*format, strm, frame_sp ? &frame_sc : nullptr, + &exe_ctx, nullptr, nullptr, false, false); +} + +void Thread::DumpUsingSettingsFormat(Stream &strm, uint32_t frame_idx, + bool stop_format) { + ExecutionContext exe_ctx(shared_from_this()); + + const FormatEntity::Entry *thread_format; + if (stop_format) + thread_format = exe_ctx.GetTargetRef().GetDebugger().GetThreadStopFormat(); + else + thread_format = exe_ctx.GetTargetRef().GetDebugger().GetThreadFormat(); + + assert(thread_format); + + DumpUsingFormat(strm, frame_idx, thread_format); +} + +void Thread::SettingsInitialize() {} + +void Thread::SettingsTerminate() {} + +lldb::addr_t Thread::GetThreadPointer() { + if (m_reg_context_sp) + return m_reg_context_sp->GetThreadPointer(); + return LLDB_INVALID_ADDRESS; +} + +addr_t Thread::GetThreadLocalData(const ModuleSP module, + lldb::addr_t tls_file_addr) { + // The default implementation is to ask the dynamic loader for it. This can + // be overridden for specific platforms. + DynamicLoader *loader = GetProcess()->GetDynamicLoader(); + if (loader) + return loader->GetThreadLocalData(module, shared_from_this(), + tls_file_addr); + else + return LLDB_INVALID_ADDRESS; +} + +bool Thread::SafeToCallFunctions() { + Process *process = GetProcess().get(); + if (process) { + DynamicLoader *loader = GetProcess()->GetDynamicLoader(); + if (loader && loader->IsFullyInitialized() == false) + return false; + + SystemRuntime *runtime = process->GetSystemRuntime(); + if (runtime) { + return runtime->SafeToCallFunctionsOnThisThread(shared_from_this()); + } + } + return true; +} + +lldb::StackFrameSP +Thread::GetStackFrameSPForStackFramePtr(StackFrame *stack_frame_ptr) { + return GetStackFrameList()->GetStackFrameSPForStackFramePtr(stack_frame_ptr); +} + +std::string Thread::StopReasonAsString(lldb::StopReason reason) { + switch (reason) { + case eStopReasonInvalid: + return "invalid"; + case eStopReasonNone: + return "none"; + case eStopReasonTrace: + return "trace"; + case eStopReasonBreakpoint: + return "breakpoint"; + case eStopReasonWatchpoint: + return "watchpoint"; + case eStopReasonSignal: + return "signal"; + case eStopReasonException: + return "exception"; + case eStopReasonExec: + return "exec"; + case eStopReasonFork: + return "fork"; + case eStopReasonVFork: + return "vfork"; + case eStopReasonVForkDone: + return "vfork done"; + case eStopReasonPlanComplete: + return "plan complete"; + case eStopReasonThreadExiting: + return "thread exiting"; + case eStopReasonInstrumentation: + return "instrumentation break"; + case eStopReasonProcessorTrace: + return "processor trace"; + } + + return "StopReason = " + std::to_string(reason); +} + +std::string Thread::RunModeAsString(lldb::RunMode mode) { + switch (mode) { + case eOnlyThisThread: + return "only this thread"; + case eAllThreads: + return "all threads"; + case eOnlyDuringStepping: + return "only during stepping"; + } + + return "RunMode = " + std::to_string(mode); +} + +size_t Thread::GetStatus(Stream &strm, uint32_t start_frame, + uint32_t num_frames, uint32_t num_frames_with_source, + bool stop_format, bool only_stacks) { + + if (!only_stacks) { + ExecutionContext exe_ctx(shared_from_this()); + Target *target = exe_ctx.GetTargetPtr(); + Process *process = exe_ctx.GetProcessPtr(); + strm.Indent(); + bool is_selected = false; + if (process) { + if (process->GetThreadList().GetSelectedThread().get() == this) + is_selected = true; + } + strm.Printf("%c ", is_selected ? '*' : ' '); + if (target && target->GetDebugger().GetUseExternalEditor()) { + StackFrameSP frame_sp = GetStackFrameAtIndex(start_frame); + if (frame_sp) { + SymbolContext frame_sc( + frame_sp->GetSymbolContext(eSymbolContextLineEntry)); + if (frame_sc.line_entry.line != 0 && frame_sc.line_entry.GetFile()) { + if (llvm::Error e = Host::OpenFileInExternalEditor( + target->GetDebugger().GetExternalEditor(), + frame_sc.line_entry.GetFile(), frame_sc.line_entry.line)) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Host), std::move(e), + "OpenFileInExternalEditor failed: {0}"); + } + } + } + } + + DumpUsingSettingsFormat(strm, start_frame, stop_format); + } + + size_t num_frames_shown = 0; + if (num_frames > 0) { + strm.IndentMore(); + + const bool show_frame_info = true; + const bool show_frame_unique = only_stacks; + const char *selected_frame_marker = nullptr; + if (num_frames == 1 || only_stacks || + (GetID() != GetProcess()->GetThreadList().GetSelectedThread()->GetID())) + strm.IndentMore(); + else + selected_frame_marker = "* "; + + num_frames_shown = GetStackFrameList()->GetStatus( + strm, start_frame, num_frames, show_frame_info, num_frames_with_source, + show_frame_unique, selected_frame_marker); + if (num_frames == 1) + strm.IndentLess(); + strm.IndentLess(); + } + return num_frames_shown; +} + +bool Thread::GetDescription(Stream &strm, lldb::DescriptionLevel level, + bool print_json_thread, bool print_json_stopinfo) { + const bool stop_format = false; + DumpUsingSettingsFormat(strm, 0, stop_format); + strm.Printf("\n"); + + StructuredData::ObjectSP thread_info = GetExtendedInfo(); + + if (print_json_thread || print_json_stopinfo) { + if (thread_info && print_json_thread) { + thread_info->Dump(strm); + strm.Printf("\n"); + } + + if (print_json_stopinfo && m_stop_info_sp) { + StructuredData::ObjectSP stop_info = m_stop_info_sp->GetExtendedInfo(); + if (stop_info) { + stop_info->Dump(strm); + strm.Printf("\n"); + } + } + + return true; + } + + if (thread_info) { + StructuredData::ObjectSP activity = + thread_info->GetObjectForDotSeparatedPath("activity"); + StructuredData::ObjectSP breadcrumb = + thread_info->GetObjectForDotSeparatedPath("breadcrumb"); + StructuredData::ObjectSP messages = + thread_info->GetObjectForDotSeparatedPath("trace_messages"); + + bool printed_activity = false; + if (activity && activity->GetType() == eStructuredDataTypeDictionary) { + StructuredData::Dictionary *activity_dict = activity->GetAsDictionary(); + StructuredData::ObjectSP id = activity_dict->GetValueForKey("id"); + StructuredData::ObjectSP name = activity_dict->GetValueForKey("name"); + if (name && name->GetType() == eStructuredDataTypeString && id && + id->GetType() == eStructuredDataTypeInteger) { + strm.Format(" Activity '{0}', {1:x}\n", + name->GetAsString()->GetValue(), + id->GetUnsignedIntegerValue()); + } + printed_activity = true; + } + bool printed_breadcrumb = false; + if (breadcrumb && breadcrumb->GetType() == eStructuredDataTypeDictionary) { + if (printed_activity) + strm.Printf("\n"); + StructuredData::Dictionary *breadcrumb_dict = + breadcrumb->GetAsDictionary(); + StructuredData::ObjectSP breadcrumb_text = + breadcrumb_dict->GetValueForKey("name"); + if (breadcrumb_text && + breadcrumb_text->GetType() == eStructuredDataTypeString) { + strm.Format(" Current Breadcrumb: {0}\n", + breadcrumb_text->GetAsString()->GetValue()); + } + printed_breadcrumb = true; + } + if (messages && messages->GetType() == eStructuredDataTypeArray) { + if (printed_breadcrumb) + strm.Printf("\n"); + StructuredData::Array *messages_array = messages->GetAsArray(); + const size_t msg_count = messages_array->GetSize(); + if (msg_count > 0) { + strm.Printf(" %zu trace messages:\n", msg_count); + for (size_t i = 0; i < msg_count; i++) { + StructuredData::ObjectSP message = messages_array->GetItemAtIndex(i); + if (message && message->GetType() == eStructuredDataTypeDictionary) { + StructuredData::Dictionary *message_dict = + message->GetAsDictionary(); + StructuredData::ObjectSP message_text = + message_dict->GetValueForKey("message"); + if (message_text && + message_text->GetType() == eStructuredDataTypeString) { + strm.Format(" {0}\n", message_text->GetAsString()->GetValue()); + } + } + } + } + } + } + + return true; +} + +size_t Thread::GetStackFrameStatus(Stream &strm, uint32_t first_frame, + uint32_t num_frames, bool show_frame_info, + uint32_t num_frames_with_source) { + return GetStackFrameList()->GetStatus( + strm, first_frame, num_frames, show_frame_info, num_frames_with_source); +} + +Unwind &Thread::GetUnwinder() { + if (!m_unwinder_up) + m_unwinder_up = std::make_unique<UnwindLLDB>(*this); + return *m_unwinder_up; +} + +void Thread::Flush() { + ClearStackFrames(); + m_reg_context_sp.reset(); +} + +bool Thread::IsStillAtLastBreakpointHit() { + // If we are currently stopped at a breakpoint, always return that stopinfo + // and don't reset it. This allows threads to maintain their breakpoint + // stopinfo, such as when thread-stepping in multithreaded programs. + if (m_stop_info_sp) { + StopReason stop_reason = m_stop_info_sp->GetStopReason(); + if (stop_reason == lldb::eStopReasonBreakpoint) { + uint64_t value = m_stop_info_sp->GetValue(); + lldb::RegisterContextSP reg_ctx_sp(GetRegisterContext()); + if (reg_ctx_sp) { + lldb::addr_t pc = reg_ctx_sp->GetPC(); + BreakpointSiteSP bp_site_sp = + GetProcess()->GetBreakpointSiteList().FindByAddress(pc); + if (bp_site_sp && static_cast<break_id_t>(value) == bp_site_sp->GetID()) + return true; + } + } + } + return false; +} + +Status Thread::StepIn(bool source_step, + LazyBool step_in_avoids_code_without_debug_info, + LazyBool step_out_avoids_code_without_debug_info) + +{ + Status error; + Process *process = GetProcess().get(); + if (StateIsStoppedState(process->GetState(), true)) { + StackFrameSP frame_sp = GetStackFrameAtIndex(0); + ThreadPlanSP new_plan_sp; + const lldb::RunMode run_mode = eOnlyThisThread; + const bool abort_other_plans = false; + + if (source_step && frame_sp && frame_sp->HasDebugInformation()) { + SymbolContext sc(frame_sp->GetSymbolContext(eSymbolContextEverything)); + new_plan_sp = QueueThreadPlanForStepInRange( + abort_other_plans, sc.line_entry, sc, nullptr, run_mode, error, + step_in_avoids_code_without_debug_info, + step_out_avoids_code_without_debug_info); + } else { + new_plan_sp = QueueThreadPlanForStepSingleInstruction( + false, abort_other_plans, run_mode, error); + } + + new_plan_sp->SetIsControllingPlan(true); + new_plan_sp->SetOkayToDiscard(false); + + // Why do we need to set the current thread by ID here??? + process->GetThreadList().SetSelectedThreadByID(GetID()); + error = process->Resume(); + } else { + error.SetErrorString("process not stopped"); + } + return error; +} + +Status Thread::StepOver(bool source_step, + LazyBool step_out_avoids_code_without_debug_info) { + Status error; + Process *process = GetProcess().get(); + if (StateIsStoppedState(process->GetState(), true)) { + StackFrameSP frame_sp = GetStackFrameAtIndex(0); + ThreadPlanSP new_plan_sp; + + const lldb::RunMode run_mode = eOnlyThisThread; + const bool abort_other_plans = false; + + if (source_step && frame_sp && frame_sp->HasDebugInformation()) { + SymbolContext sc(frame_sp->GetSymbolContext(eSymbolContextEverything)); + new_plan_sp = QueueThreadPlanForStepOverRange( + abort_other_plans, sc.line_entry, sc, run_mode, error, + step_out_avoids_code_without_debug_info); + } else { + new_plan_sp = QueueThreadPlanForStepSingleInstruction( + true, abort_other_plans, run_mode, error); + } + + new_plan_sp->SetIsControllingPlan(true); + new_plan_sp->SetOkayToDiscard(false); + + // Why do we need to set the current thread by ID here??? + process->GetThreadList().SetSelectedThreadByID(GetID()); + error = process->Resume(); + } else { + error.SetErrorString("process not stopped"); + } + return error; +} + +Status Thread::StepOut(uint32_t frame_idx) { + Status error; + Process *process = GetProcess().get(); + if (StateIsStoppedState(process->GetState(), true)) { + const bool first_instruction = false; + const bool stop_other_threads = false; + const bool abort_other_plans = false; + + ThreadPlanSP new_plan_sp(QueueThreadPlanForStepOut( + abort_other_plans, nullptr, first_instruction, stop_other_threads, + eVoteYes, eVoteNoOpinion, frame_idx, error)); + + new_plan_sp->SetIsControllingPlan(true); + new_plan_sp->SetOkayToDiscard(false); + + // Why do we need to set the current thread by ID here??? + process->GetThreadList().SetSelectedThreadByID(GetID()); + error = process->Resume(); + } else { + error.SetErrorString("process not stopped"); + } + return error; +} + +ValueObjectSP Thread::GetCurrentException() { + if (auto frame_sp = GetStackFrameAtIndex(0)) + if (auto recognized_frame = frame_sp->GetRecognizedFrame()) + if (auto e = recognized_frame->GetExceptionObject()) + return e; + + // NOTE: Even though this behavior is generalized, only ObjC is actually + // supported at the moment. + for (LanguageRuntime *runtime : GetProcess()->GetLanguageRuntimes()) { + if (auto e = runtime->GetExceptionObjectForThread(shared_from_this())) + return e; + } + + return ValueObjectSP(); +} + +ThreadSP Thread::GetCurrentExceptionBacktrace() { + ValueObjectSP exception = GetCurrentException(); + if (!exception) + return ThreadSP(); + + // NOTE: Even though this behavior is generalized, only ObjC is actually + // supported at the moment. + for (LanguageRuntime *runtime : GetProcess()->GetLanguageRuntimes()) { + if (auto bt = runtime->GetBacktraceThreadFromException(exception)) + return bt; + } + + return ThreadSP(); +} + +lldb::ValueObjectSP Thread::GetSiginfoValue() { + ProcessSP process_sp = GetProcess(); + assert(process_sp); + Target &target = process_sp->GetTarget(); + PlatformSP platform_sp = target.GetPlatform(); + assert(platform_sp); + ArchSpec arch = target.GetArchitecture(); + + CompilerType type = platform_sp->GetSiginfoType(arch.GetTriple()); + if (!type.IsValid()) + return ValueObjectConstResult::Create(&target, Status("no siginfo_t for the platform")); + + std::optional<uint64_t> type_size = type.GetByteSize(nullptr); + assert(type_size); + llvm::Expected<std::unique_ptr<llvm::MemoryBuffer>> data = + GetSiginfo(*type_size); + if (!data) + return ValueObjectConstResult::Create(&target, Status(data.takeError())); + + DataExtractor data_extractor{data.get()->getBufferStart(), data.get()->getBufferSize(), + process_sp->GetByteOrder(), arch.GetAddressByteSize()}; + return ValueObjectConstResult::Create(&target, type, ConstString("__lldb_siginfo"), data_extractor); +} diff --git a/contrib/llvm-project/lldb/source/Target/ThreadCollection.cpp b/contrib/llvm-project/lldb/source/Target/ThreadCollection.cpp new file mode 100644 index 000000000000..45ce2fd318b7 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadCollection.cpp @@ -0,0 +1,65 @@ +//===-- ThreadCollection.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 <cstdlib> + +#include <algorithm> +#include <mutex> + +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadCollection.h" + +using namespace lldb; +using namespace lldb_private; + +ThreadCollection::ThreadCollection() : m_threads(), m_mutex() {} + +ThreadCollection::ThreadCollection(collection threads) + : m_threads(threads), m_mutex() {} + +void ThreadCollection::AddThread(const ThreadSP &thread_sp) { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + m_threads.push_back(thread_sp); +} + +void ThreadCollection::AddThreadSortedByIndexID(const ThreadSP &thread_sp) { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + // Make sure we always keep the threads sorted by thread index ID + const uint32_t thread_index_id = thread_sp->GetIndexID(); + if (m_threads.empty() || m_threads.back()->GetIndexID() < thread_index_id) + m_threads.push_back(thread_sp); + else { + m_threads.insert( + llvm::upper_bound(m_threads, thread_sp, + [](const ThreadSP &lhs, const ThreadSP &rhs) -> bool { + return lhs->GetIndexID() < rhs->GetIndexID(); + }), + thread_sp); + } +} + +void ThreadCollection::InsertThread(const lldb::ThreadSP &thread_sp, + uint32_t idx) { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + if (idx < m_threads.size()) + m_threads.insert(m_threads.begin() + idx, thread_sp); + else + m_threads.push_back(thread_sp); +} + +uint32_t ThreadCollection::GetSize() { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + return m_threads.size(); +} + +ThreadSP ThreadCollection::GetThreadAtIndex(uint32_t idx) { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + ThreadSP thread_sp; + if (idx < m_threads.size()) + thread_sp = m_threads[idx]; + return thread_sp; +} diff --git a/contrib/llvm-project/lldb/source/Target/ThreadList.cpp b/contrib/llvm-project/lldb/source/Target/ThreadList.cpp new file mode 100644 index 000000000000..1a2d7dd61c77 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadList.cpp @@ -0,0 +1,799 @@ +//===-- ThreadList.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 <cstdlib> + +#include <algorithm> + +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadList.h" +#include "lldb/Target/ThreadPlan.h" +#include "lldb/Utility/LLDBAssert.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/State.h" + +using namespace lldb; +using namespace lldb_private; + +ThreadList::ThreadList(Process &process) + : ThreadCollection(), m_process(process), m_stop_id(0), + m_selected_tid(LLDB_INVALID_THREAD_ID) {} + +ThreadList::ThreadList(const ThreadList &rhs) + : ThreadCollection(), m_process(rhs.m_process), m_stop_id(rhs.m_stop_id), + m_selected_tid() { + // Use the assignment operator since it uses the mutex + *this = rhs; +} + +const ThreadList &ThreadList::operator=(const ThreadList &rhs) { + if (this != &rhs) { + // We only allow assignments between thread lists describing the same + // process. Same process implies same mutex, which means it's enough to lock + // just the current object. + assert(&m_process == &rhs.m_process); + assert(&GetMutex() == &rhs.GetMutex()); + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + + m_stop_id = rhs.m_stop_id; + m_threads = rhs.m_threads; + m_selected_tid = rhs.m_selected_tid; + } + return *this; +} + +ThreadList::~ThreadList() { + // Clear the thread list. Clear will take the mutex lock which will ensure + // that if anyone is using the list they won't get it removed while using it. + Clear(); +} + +lldb::ThreadSP ThreadList::GetExpressionExecutionThread() { + if (m_expression_tid_stack.empty()) + return GetSelectedThread(); + ThreadSP expr_thread_sp = FindThreadByID(m_expression_tid_stack.back()); + if (expr_thread_sp) + return expr_thread_sp; + else + return GetSelectedThread(); +} + +void ThreadList::PushExpressionExecutionThread(lldb::tid_t tid) { + m_expression_tid_stack.push_back(tid); +} + +void ThreadList::PopExpressionExecutionThread(lldb::tid_t tid) { + assert(m_expression_tid_stack.back() == tid); + m_expression_tid_stack.pop_back(); +} + +uint32_t ThreadList::GetStopID() const { return m_stop_id; } + +void ThreadList::SetStopID(uint32_t stop_id) { m_stop_id = stop_id; } + +uint32_t ThreadList::GetSize(bool can_update) { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + + if (can_update) + m_process.UpdateThreadListIfNeeded(); + return m_threads.size(); +} + +ThreadSP ThreadList::GetThreadAtIndex(uint32_t idx, bool can_update) { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + + if (can_update) + m_process.UpdateThreadListIfNeeded(); + + ThreadSP thread_sp; + if (idx < m_threads.size()) + thread_sp = m_threads[idx]; + return thread_sp; +} + +ThreadSP ThreadList::FindThreadByID(lldb::tid_t tid, bool can_update) { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + + if (can_update) + m_process.UpdateThreadListIfNeeded(); + + ThreadSP thread_sp; + uint32_t idx = 0; + const uint32_t num_threads = m_threads.size(); + for (idx = 0; idx < num_threads; ++idx) { + if (m_threads[idx]->GetID() == tid) { + thread_sp = m_threads[idx]; + break; + } + } + return thread_sp; +} + +ThreadSP ThreadList::FindThreadByProtocolID(lldb::tid_t tid, bool can_update) { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + + if (can_update) + m_process.UpdateThreadListIfNeeded(); + + ThreadSP thread_sp; + uint32_t idx = 0; + const uint32_t num_threads = m_threads.size(); + for (idx = 0; idx < num_threads; ++idx) { + if (m_threads[idx]->GetProtocolID() == tid) { + thread_sp = m_threads[idx]; + break; + } + } + return thread_sp; +} + +ThreadSP ThreadList::RemoveThreadByID(lldb::tid_t tid, bool can_update) { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + + if (can_update) + m_process.UpdateThreadListIfNeeded(); + + ThreadSP thread_sp; + uint32_t idx = 0; + const uint32_t num_threads = m_threads.size(); + for (idx = 0; idx < num_threads; ++idx) { + if (m_threads[idx]->GetID() == tid) { + thread_sp = m_threads[idx]; + m_threads.erase(m_threads.begin() + idx); + break; + } + } + return thread_sp; +} + +ThreadSP ThreadList::RemoveThreadByProtocolID(lldb::tid_t tid, + bool can_update) { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + + if (can_update) + m_process.UpdateThreadListIfNeeded(); + + ThreadSP thread_sp; + uint32_t idx = 0; + const uint32_t num_threads = m_threads.size(); + for (idx = 0; idx < num_threads; ++idx) { + if (m_threads[idx]->GetProtocolID() == tid) { + thread_sp = m_threads[idx]; + m_threads.erase(m_threads.begin() + idx); + break; + } + } + return thread_sp; +} + +ThreadSP ThreadList::GetThreadSPForThreadPtr(Thread *thread_ptr) { + ThreadSP thread_sp; + if (thread_ptr) { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + + uint32_t idx = 0; + const uint32_t num_threads = m_threads.size(); + for (idx = 0; idx < num_threads; ++idx) { + if (m_threads[idx].get() == thread_ptr) { + thread_sp = m_threads[idx]; + break; + } + } + } + return thread_sp; +} + +ThreadSP ThreadList::GetBackingThread(const ThreadSP &real_thread) { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + + ThreadSP thread_sp; + const uint32_t num_threads = m_threads.size(); + for (uint32_t idx = 0; idx < num_threads; ++idx) { + if (m_threads[idx]->GetBackingThread() == real_thread) { + thread_sp = m_threads[idx]; + break; + } + } + return thread_sp; +} + +ThreadSP ThreadList::FindThreadByIndexID(uint32_t index_id, bool can_update) { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + + if (can_update) + m_process.UpdateThreadListIfNeeded(); + + ThreadSP thread_sp; + const uint32_t num_threads = m_threads.size(); + for (uint32_t idx = 0; idx < num_threads; ++idx) { + if (m_threads[idx]->GetIndexID() == index_id) { + thread_sp = m_threads[idx]; + break; + } + } + return thread_sp; +} + +bool ThreadList::ShouldStop(Event *event_ptr) { + // Running events should never stop, obviously... + + Log *log = GetLog(LLDBLog::Step); + + // The ShouldStop method of the threads can do a whole lot of work, figuring + // out whether the thread plan conditions are met. So we don't want to keep + // the ThreadList locked the whole time we are doing this. + // FIXME: It is possible that running code could cause new threads + // to be created. If that happens, we will miss asking them whether they + // should stop. This is not a big deal since we haven't had a chance to hang + // any interesting operations on those threads yet. + + collection threads_copy; + { + // Scope for locker + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + + m_process.UpdateThreadListIfNeeded(); + for (lldb::ThreadSP thread_sp : m_threads) { + // This is an optimization... If we didn't let a thread run in between + // the previous stop and this one, we shouldn't have to consult it for + // ShouldStop. So just leave it off the list we are going to inspect. + // If the thread didn't run but had work to do before declaring a public + // stop, then also include it. + // On Linux, if a thread-specific conditional breakpoint was hit, it won't + // necessarily be the thread that hit the breakpoint itself that + // evaluates the conditional expression, so the thread that hit the + // breakpoint could still be asked to stop, even though it hasn't been + // allowed to run since the previous stop. + if (thread_sp->GetTemporaryResumeState() != eStateSuspended || + thread_sp->IsStillAtLastBreakpointHit() + || thread_sp->ShouldRunBeforePublicStop()) + threads_copy.push_back(thread_sp); + } + + // It is possible the threads we were allowing to run all exited and then + // maybe the user interrupted or something, then fall back on looking at + // all threads: + + if (threads_copy.size() == 0) + threads_copy = m_threads; + } + + collection::iterator pos, end = threads_copy.end(); + + if (log) { + log->PutCString(""); + LLDB_LOGF(log, + "ThreadList::%s: %" PRIu64 " threads, %" PRIu64 + " unsuspended threads", + __FUNCTION__, (uint64_t)m_threads.size(), + (uint64_t)threads_copy.size()); + } + + bool did_anybody_stop_for_a_reason = false; + + // If the event is an Interrupt event, then we're going to stop no matter + // what. Otherwise, presume we won't stop. + bool should_stop = false; + if (Process::ProcessEventData::GetInterruptedFromEvent(event_ptr)) { + LLDB_LOGF( + log, "ThreadList::%s handling interrupt event, should stop set to true", + __FUNCTION__); + + should_stop = true; + } + + // Now we run through all the threads and get their stop info's. We want to + // make sure to do this first before we start running the ShouldStop, because + // one thread's ShouldStop could destroy information (like deleting a thread + // specific breakpoint another thread had stopped at) which could lead us to + // compute the StopInfo incorrectly. We don't need to use it here, we just + // want to make sure it gets computed. + + for (pos = threads_copy.begin(); pos != end; ++pos) { + ThreadSP thread_sp(*pos); + thread_sp->GetStopInfo(); + } + + // If a thread needs to finish some job that can be done just on this thread + // before broadcastion the stop, it will signal that by returning true for + // ShouldRunBeforePublicStop. This variable gathers the results from that. + bool a_thread_needs_to_run = false; + for (pos = threads_copy.begin(); pos != end; ++pos) { + ThreadSP thread_sp(*pos); + + // We should never get a stop for which no thread had a stop reason, but + // sometimes we do see this - for instance when we first connect to a + // remote stub. In that case we should stop, since we can't figure out the + // right thing to do and stopping gives the user control over what to do in + // this instance. + // + // Note, this causes a problem when you have a thread specific breakpoint, + // and a bunch of threads hit the breakpoint, but not the thread which we + // are waiting for. All the threads that are not "supposed" to hit the + // breakpoint are marked as having no stop reason, which is right, they + // should not show a stop reason. But that triggers this code and causes + // us to stop seemingly for no reason. + // + // Since the only way we ever saw this error was on first attach, I'm only + // going to trigger set did_anybody_stop_for_a_reason to true unless this + // is the first stop. + // + // If this becomes a problem, we'll have to have another StopReason like + // "StopInfoHidden" which will look invalid everywhere but at this check. + + if (thread_sp->GetProcess()->GetStopID() > 1) + did_anybody_stop_for_a_reason = true; + else + did_anybody_stop_for_a_reason |= thread_sp->ThreadStoppedForAReason(); + + const bool thread_should_stop = thread_sp->ShouldStop(event_ptr); + + if (thread_should_stop) + should_stop |= true; + else { + bool this_thread_forces_run = thread_sp->ShouldRunBeforePublicStop(); + a_thread_needs_to_run |= this_thread_forces_run; + if (this_thread_forces_run) + LLDB_LOG(log, + "ThreadList::{0} thread: {1:x}, " + "says it needs to run before public stop.", + __FUNCTION__, thread_sp->GetID()); + } + } + + if (a_thread_needs_to_run) { + should_stop = false; + } else if (!should_stop && !did_anybody_stop_for_a_reason) { + should_stop = true; + LLDB_LOGF(log, + "ThreadList::%s we stopped but no threads had a stop reason, " + "overriding should_stop and stopping.", + __FUNCTION__); + } + + LLDB_LOGF(log, "ThreadList::%s overall should_stop = %i", __FUNCTION__, + should_stop); + + if (should_stop) { + for (pos = threads_copy.begin(); pos != end; ++pos) { + ThreadSP thread_sp(*pos); + thread_sp->WillStop(); + } + } + + return should_stop; +} + +Vote ThreadList::ShouldReportStop(Event *event_ptr) { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + + Vote result = eVoteNoOpinion; + m_process.UpdateThreadListIfNeeded(); + collection::iterator pos, end = m_threads.end(); + + Log *log = GetLog(LLDBLog::Step); + + LLDB_LOGF(log, "ThreadList::%s %" PRIu64 " threads", __FUNCTION__, + (uint64_t)m_threads.size()); + + // Run through the threads and ask whether we should report this event. For + // stopping, a YES vote wins over everything. A NO vote wins over NO + // opinion. The exception is if a thread has work it needs to force before + // a public stop, which overrides everyone else's opinion: + for (pos = m_threads.begin(); pos != end; ++pos) { + ThreadSP thread_sp(*pos); + if (thread_sp->ShouldRunBeforePublicStop()) { + LLDB_LOG(log, "Thread {0:x} has private business to complete, overrode " + "the should report stop.", thread_sp->GetID()); + result = eVoteNo; + break; + } + + const Vote vote = thread_sp->ShouldReportStop(event_ptr); + switch (vote) { + case eVoteNoOpinion: + continue; + + case eVoteYes: + result = eVoteYes; + break; + + case eVoteNo: + if (result == eVoteNoOpinion) { + result = eVoteNo; + } else { + LLDB_LOG(log, + "Thread {0:x} voted {1}, but lost out because result was {2}", + thread_sp->GetID(), vote, result); + } + break; + } + } + LLDB_LOG(log, "Returning {0}", result); + return result; +} + +void ThreadList::SetShouldReportStop(Vote vote) { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + + m_process.UpdateThreadListIfNeeded(); + collection::iterator pos, end = m_threads.end(); + for (pos = m_threads.begin(); pos != end; ++pos) { + ThreadSP thread_sp(*pos); + thread_sp->SetShouldReportStop(vote); + } +} + +Vote ThreadList::ShouldReportRun(Event *event_ptr) { + + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + + Vote result = eVoteNoOpinion; + m_process.UpdateThreadListIfNeeded(); + collection::iterator pos, end = m_threads.end(); + + // Run through the threads and ask whether we should report this event. The + // rule is NO vote wins over everything, a YES vote wins over no opinion. + + Log *log = GetLog(LLDBLog::Step); + + for (pos = m_threads.begin(); pos != end; ++pos) { + if ((*pos)->GetResumeState() != eStateSuspended) { + switch ((*pos)->ShouldReportRun(event_ptr)) { + case eVoteNoOpinion: + continue; + case eVoteYes: + if (result == eVoteNoOpinion) + result = eVoteYes; + break; + case eVoteNo: + LLDB_LOGF(log, + "ThreadList::ShouldReportRun() thread %d (0x%4.4" PRIx64 + ") says don't report.", + (*pos)->GetIndexID(), (*pos)->GetID()); + result = eVoteNo; + break; + } + } + } + return result; +} + +void ThreadList::Clear() { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + m_stop_id = 0; + m_threads.clear(); + m_selected_tid = LLDB_INVALID_THREAD_ID; +} + +void ThreadList::Destroy() { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + const uint32_t num_threads = m_threads.size(); + for (uint32_t idx = 0; idx < num_threads; ++idx) { + m_threads[idx]->DestroyThread(); + } +} + +void ThreadList::RefreshStateAfterStop() { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + + m_process.UpdateThreadListIfNeeded(); + + Log *log = GetLog(LLDBLog::Step); + if (log && log->GetVerbose()) + LLDB_LOGF(log, + "Turning off notification of new threads while single stepping " + "a thread."); + + collection::iterator pos, end = m_threads.end(); + for (pos = m_threads.begin(); pos != end; ++pos) + (*pos)->RefreshStateAfterStop(); +} + +void ThreadList::DiscardThreadPlans() { + // You don't need to update the thread list here, because only threads that + // you currently know about have any thread plans. + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + + collection::iterator pos, end = m_threads.end(); + for (pos = m_threads.begin(); pos != end; ++pos) + (*pos)->DiscardThreadPlans(true); +} + +bool ThreadList::WillResume() { + // Run through the threads and perform their momentary actions. But we only + // do this for threads that are running, user suspended threads stay where + // they are. + + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + m_process.UpdateThreadListIfNeeded(); + + collection::iterator pos, end = m_threads.end(); + + // See if any thread wants to run stopping others. If it does, then we won't + // setup the other threads for resume, since they aren't going to get a + // chance to run. This is necessary because the SetupForResume might add + // "StopOthers" plans which would then get to be part of the who-gets-to-run + // negotiation, but they're coming in after the fact, and the threads that + // are already set up should take priority. + + bool wants_solo_run = false; + + for (pos = m_threads.begin(); pos != end; ++pos) { + lldbassert((*pos)->GetCurrentPlan() && + "thread should not have null thread plan"); + if ((*pos)->GetResumeState() != eStateSuspended && + (*pos)->GetCurrentPlan()->StopOthers()) { + if ((*pos)->IsOperatingSystemPluginThread() && + !(*pos)->GetBackingThread()) + continue; + wants_solo_run = true; + break; + } + } + + if (wants_solo_run) { + Log *log = GetLog(LLDBLog::Step); + if (log && log->GetVerbose()) + LLDB_LOGF(log, "Turning on notification of new threads while single " + "stepping a thread."); + m_process.StartNoticingNewThreads(); + } else { + Log *log = GetLog(LLDBLog::Step); + if (log && log->GetVerbose()) + LLDB_LOGF(log, "Turning off notification of new threads while single " + "stepping a thread."); + m_process.StopNoticingNewThreads(); + } + + // Give all the threads that are likely to run a last chance to set up their + // state before we negotiate who is actually going to get a chance to run... + // Don't set to resume suspended threads, and if any thread wanted to stop + // others, only call setup on the threads that request StopOthers... + + for (pos = m_threads.begin(); pos != end; ++pos) { + if ((*pos)->GetResumeState() != eStateSuspended && + (!wants_solo_run || (*pos)->GetCurrentPlan()->StopOthers())) { + if ((*pos)->IsOperatingSystemPluginThread() && + !(*pos)->GetBackingThread()) + continue; + (*pos)->SetupForResume(); + } + } + + // Now go through the threads and see if any thread wants to run just itself. + // if so then pick one and run it. + + ThreadList run_me_only_list(m_process); + + run_me_only_list.SetStopID(m_process.GetStopID()); + + // One or more threads might want to "Stop Others". We want to handle all + // those requests first. And if there is a thread that wanted to "resume + // before a public stop", let it get the first crack: + // There are two special kinds of thread that have priority for "StopOthers": + // a "ShouldRunBeforePublicStop thread, or the currently selected thread. If + // we find one satisfying that critereon, put it here. + ThreadSP stop_others_thread_sp; + + for (pos = m_threads.begin(); pos != end; ++pos) { + ThreadSP thread_sp(*pos); + if (thread_sp->GetResumeState() != eStateSuspended && + thread_sp->GetCurrentPlan()->StopOthers()) { + if ((*pos)->IsOperatingSystemPluginThread() && + !(*pos)->GetBackingThread()) + continue; + + // You can't say "stop others" and also want yourself to be suspended. + assert(thread_sp->GetCurrentPlan()->RunState() != eStateSuspended); + run_me_only_list.AddThread(thread_sp); + + if (thread_sp == GetSelectedThread()) + stop_others_thread_sp = thread_sp; + + if (thread_sp->ShouldRunBeforePublicStop()) { + // This takes precedence, so if we find one of these, service it: + stop_others_thread_sp = thread_sp; + break; + } + } + } + + bool need_to_resume = true; + + if (run_me_only_list.GetSize(false) == 0) { + // Everybody runs as they wish: + for (pos = m_threads.begin(); pos != end; ++pos) { + ThreadSP thread_sp(*pos); + StateType run_state; + if (thread_sp->GetResumeState() != eStateSuspended) + run_state = thread_sp->GetCurrentPlan()->RunState(); + else + run_state = eStateSuspended; + if (!thread_sp->ShouldResume(run_state)) + need_to_resume = false; + } + } else { + ThreadSP thread_to_run; + + if (stop_others_thread_sp) { + thread_to_run = stop_others_thread_sp; + } else if (run_me_only_list.GetSize(false) == 1) { + thread_to_run = run_me_only_list.GetThreadAtIndex(0); + } else { + int random_thread = + (int)((run_me_only_list.GetSize(false) * (double)rand()) / + (RAND_MAX + 1.0)); + thread_to_run = run_me_only_list.GetThreadAtIndex(random_thread); + } + + for (pos = m_threads.begin(); pos != end; ++pos) { + ThreadSP thread_sp(*pos); + if (thread_sp == thread_to_run) { + // Note, a thread might be able to fulfil it's plan w/o actually + // resuming. An example of this is a step that changes the current + // inlined function depth w/o moving the PC. Check that here: + if (!thread_sp->ShouldResume(thread_sp->GetCurrentPlan()->RunState())) + need_to_resume = false; + } else + thread_sp->ShouldResume(eStateSuspended); + } + } + + return need_to_resume; +} + +void ThreadList::DidResume() { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + collection::iterator pos, end = m_threads.end(); + for (pos = m_threads.begin(); pos != end; ++pos) { + // Don't clear out threads that aren't going to get a chance to run, rather + // leave their state for the next time around. + ThreadSP thread_sp(*pos); + if (thread_sp->GetTemporaryResumeState() != eStateSuspended) + thread_sp->DidResume(); + } +} + +void ThreadList::DidStop() { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + collection::iterator pos, end = m_threads.end(); + for (pos = m_threads.begin(); pos != end; ++pos) { + // Notify threads that the process just stopped. Note, this currently + // assumes that all threads in the list stop when the process stops. In + // the future we will want to support a debugging model where some threads + // continue to run while others are stopped. We either need to handle that + // somehow here or create a special thread list containing only threads + // which will stop in the code that calls this method (currently + // Process::SetPrivateState). + ThreadSP thread_sp(*pos); + if (StateIsRunningState(thread_sp->GetState())) + thread_sp->DidStop(); + } +} + +ThreadSP ThreadList::GetSelectedThread() { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + ThreadSP thread_sp = FindThreadByID(m_selected_tid); + if (!thread_sp.get()) { + if (m_threads.size() == 0) + return thread_sp; + m_selected_tid = m_threads[0]->GetID(); + thread_sp = m_threads[0]; + } + return thread_sp; +} + +bool ThreadList::SetSelectedThreadByID(lldb::tid_t tid, bool notify) { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + ThreadSP selected_thread_sp(FindThreadByID(tid)); + if (selected_thread_sp) { + m_selected_tid = tid; + selected_thread_sp->SetDefaultFileAndLineToSelectedFrame(); + } else + m_selected_tid = LLDB_INVALID_THREAD_ID; + + if (notify) + NotifySelectedThreadChanged(m_selected_tid); + + return m_selected_tid != LLDB_INVALID_THREAD_ID; +} + +bool ThreadList::SetSelectedThreadByIndexID(uint32_t index_id, bool notify) { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + ThreadSP selected_thread_sp(FindThreadByIndexID(index_id)); + if (selected_thread_sp.get()) { + m_selected_tid = selected_thread_sp->GetID(); + selected_thread_sp->SetDefaultFileAndLineToSelectedFrame(); + } else + m_selected_tid = LLDB_INVALID_THREAD_ID; + + if (notify) + NotifySelectedThreadChanged(m_selected_tid); + + return m_selected_tid != LLDB_INVALID_THREAD_ID; +} + +void ThreadList::NotifySelectedThreadChanged(lldb::tid_t tid) { + ThreadSP selected_thread_sp(FindThreadByID(tid)); + if (selected_thread_sp->EventTypeHasListeners( + Thread::eBroadcastBitThreadSelected)) { + auto data_sp = + std::make_shared<Thread::ThreadEventData>(selected_thread_sp); + selected_thread_sp->BroadcastEvent(Thread::eBroadcastBitThreadSelected, + data_sp); + } +} + +void ThreadList::Update(ThreadList &rhs) { + if (this != &rhs) { + // We only allow assignments between thread lists describing the same + // process. Same process implies same mutex, which means it's enough to lock + // just the current object. + assert(&m_process == &rhs.m_process); + assert(&GetMutex() == &rhs.GetMutex()); + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + + m_stop_id = rhs.m_stop_id; + m_threads.swap(rhs.m_threads); + m_selected_tid = rhs.m_selected_tid; + + // Now we look for threads that we are done with and make sure to clear + // them up as much as possible so anyone with a shared pointer will still + // have a reference, but the thread won't be of much use. Using + // std::weak_ptr for all backward references (such as a thread to a + // process) will eventually solve this issue for us, but for now, we need + // to work around the issue + collection::iterator rhs_pos, rhs_end = rhs.m_threads.end(); + for (rhs_pos = rhs.m_threads.begin(); rhs_pos != rhs_end; ++rhs_pos) { + // If this thread has already been destroyed, we don't need to look for + // it to destroy it again. + if (!(*rhs_pos)->IsValid()) + continue; + + const lldb::tid_t tid = (*rhs_pos)->GetID(); + bool thread_is_alive = false; + const uint32_t num_threads = m_threads.size(); + for (uint32_t idx = 0; idx < num_threads; ++idx) { + ThreadSP backing_thread = m_threads[idx]->GetBackingThread(); + if (m_threads[idx]->GetID() == tid || + (backing_thread && backing_thread->GetID() == tid)) { + thread_is_alive = true; + break; + } + } + if (!thread_is_alive) { + (*rhs_pos)->DestroyThread(); + } + } + } +} + +void ThreadList::Flush() { + std::lock_guard<std::recursive_mutex> guard(GetMutex()); + collection::iterator pos, end = m_threads.end(); + for (pos = m_threads.begin(); pos != end; ++pos) + (*pos)->Flush(); +} + +std::recursive_mutex &ThreadList::GetMutex() const { + return m_process.m_thread_mutex; +} + +ThreadList::ExpressionExecutionThreadPusher::ExpressionExecutionThreadPusher( + lldb::ThreadSP thread_sp) + : m_thread_list(nullptr), m_tid(LLDB_INVALID_THREAD_ID) { + if (thread_sp) { + m_tid = thread_sp->GetID(); + m_thread_list = &thread_sp->GetProcess()->GetThreadList(); + m_thread_list->PushExpressionExecutionThread(m_tid); + } +} diff --git a/contrib/llvm-project/lldb/source/Target/ThreadPlan.cpp b/contrib/llvm-project/lldb/source/Target/ThreadPlan.cpp new file mode 100644 index 000000000000..7927fc314014 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadPlan.cpp @@ -0,0 +1,292 @@ +//===-- ThreadPlan.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 "lldb/Target/ThreadPlan.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/State.h" + +using namespace lldb; +using namespace lldb_private; + +// ThreadPlan constructor +ThreadPlan::ThreadPlan(ThreadPlanKind kind, const char *name, Thread &thread, + Vote report_stop_vote, Vote report_run_vote) + : m_process(*thread.GetProcess().get()), m_tid(thread.GetID()), + m_report_stop_vote(report_stop_vote), m_report_run_vote(report_run_vote), + m_takes_iteration_count(false), m_could_not_resolve_hw_bp(false), + m_thread(&thread), m_kind(kind), m_name(name), m_plan_complete_mutex(), + m_cached_plan_explains_stop(eLazyBoolCalculate), m_plan_complete(false), + m_plan_private(false), m_okay_to_discard(true), + m_is_controlling_plan(false), m_plan_succeeded(true) { + SetID(GetNextID()); +} + +// Destructor +ThreadPlan::~ThreadPlan() = default; + +Target &ThreadPlan::GetTarget() { return m_process.GetTarget(); } + +const Target &ThreadPlan::GetTarget() const { return m_process.GetTarget(); } + +Thread &ThreadPlan::GetThread() { + if (m_thread) + return *m_thread; + + ThreadSP thread_sp = m_process.GetThreadList().FindThreadByID(m_tid); + m_thread = thread_sp.get(); + return *m_thread; +} + +bool ThreadPlan::PlanExplainsStop(Event *event_ptr) { + if (m_cached_plan_explains_stop == eLazyBoolCalculate) { + bool actual_value = DoPlanExplainsStop(event_ptr); + CachePlanExplainsStop(actual_value); + return actual_value; + } else { + return m_cached_plan_explains_stop == eLazyBoolYes; + } +} + +bool ThreadPlan::IsPlanComplete() { + std::lock_guard<std::recursive_mutex> guard(m_plan_complete_mutex); + return m_plan_complete; +} + +void ThreadPlan::SetPlanComplete(bool success) { + std::lock_guard<std::recursive_mutex> guard(m_plan_complete_mutex); + m_plan_complete = true; + m_plan_succeeded = success; +} + +bool ThreadPlan::MischiefManaged() { + std::lock_guard<std::recursive_mutex> guard(m_plan_complete_mutex); + // Mark the plan is complete, but don't override the success flag. + m_plan_complete = true; + return true; +} + +Vote ThreadPlan::ShouldReportStop(Event *event_ptr) { + Log *log = GetLog(LLDBLog::Step); + + if (m_report_stop_vote == eVoteNoOpinion) { + ThreadPlan *prev_plan = GetPreviousPlan(); + if (prev_plan) { + Vote prev_vote = prev_plan->ShouldReportStop(event_ptr); + LLDB_LOG(log, "returning previous thread plan vote: {0}", prev_vote); + return prev_vote; + } + } + LLDB_LOG(log, "Returning vote: {0}", m_report_stop_vote); + return m_report_stop_vote; +} + +Vote ThreadPlan::ShouldReportRun(Event *event_ptr) { + if (m_report_run_vote == eVoteNoOpinion) { + ThreadPlan *prev_plan = GetPreviousPlan(); + if (prev_plan) + return prev_plan->ShouldReportRun(event_ptr); + } + return m_report_run_vote; +} + +void ThreadPlan::ClearThreadCache() { m_thread = nullptr; } + +bool ThreadPlan::StopOthers() { + ThreadPlan *prev_plan; + prev_plan = GetPreviousPlan(); + return (prev_plan == nullptr) ? false : prev_plan->StopOthers(); +} + +void ThreadPlan::SetStopOthers(bool new_value) { + // SetStopOthers doesn't work up the hierarchy. You have to set the explicit + // ThreadPlan you want to affect. +} + +bool ThreadPlan::WillResume(StateType resume_state, bool current_plan) { + m_cached_plan_explains_stop = eLazyBoolCalculate; + + if (current_plan) { + Log *log = GetLog(LLDBLog::Step); + + if (log) { + RegisterContext *reg_ctx = GetThread().GetRegisterContext().get(); + assert(reg_ctx); + addr_t pc = reg_ctx->GetPC(); + addr_t sp = reg_ctx->GetSP(); + addr_t fp = reg_ctx->GetFP(); + LLDB_LOGF( + log, + "%s Thread #%u (0x%p): tid = 0x%4.4" PRIx64 ", pc = 0x%8.8" PRIx64 + ", sp = 0x%8.8" PRIx64 ", fp = 0x%8.8" PRIx64 ", " + "plan = '%s', state = %s, stop others = %d", + __FUNCTION__, GetThread().GetIndexID(), + static_cast<void *>(&GetThread()), m_tid, static_cast<uint64_t>(pc), + static_cast<uint64_t>(sp), static_cast<uint64_t>(fp), m_name.c_str(), + StateAsCString(resume_state), StopOthers()); + } + } + bool success = DoWillResume(resume_state, current_plan); + ClearThreadCache(); // We don't cache the thread pointer over resumes. This + // Thread might go away, and another Thread represent + // the same underlying object on a later stop. + return success; +} + +lldb::user_id_t ThreadPlan::GetNextID() { + static uint32_t g_nextPlanID = 0; + return ++g_nextPlanID; +} + +void ThreadPlan::DidPush() {} + +void ThreadPlan::DidPop() {} + +bool ThreadPlan::OkayToDiscard() { + return IsControllingPlan() ? m_okay_to_discard : true; +} + +lldb::StateType ThreadPlan::RunState() { + if (m_tracer_sp && m_tracer_sp->TracingEnabled()) + return eStateStepping; + else + return GetPlanRunState(); +} + +bool ThreadPlan::IsUsuallyUnexplainedStopReason(lldb::StopReason reason) { + switch (reason) { + case eStopReasonWatchpoint: + case eStopReasonSignal: + case eStopReasonException: + case eStopReasonExec: + case eStopReasonThreadExiting: + case eStopReasonInstrumentation: + case eStopReasonFork: + case eStopReasonVFork: + case eStopReasonVForkDone: + return true; + default: + return false; + } +} + +// ThreadPlanNull + +ThreadPlanNull::ThreadPlanNull(Thread &thread) + : ThreadPlan(ThreadPlan::eKindNull, "Null Thread Plan", thread, + eVoteNoOpinion, eVoteNoOpinion) {} + +ThreadPlanNull::~ThreadPlanNull() = default; + +void ThreadPlanNull::GetDescription(Stream *s, lldb::DescriptionLevel level) { + s->PutCString("Null thread plan - thread has been destroyed."); +} + +bool ThreadPlanNull::ValidatePlan(Stream *error) { +#ifdef LLDB_CONFIGURATION_DEBUG + fprintf(stderr, + "error: %s called on thread that has been destroyed (tid = 0x%" PRIx64 + ", ptid = 0x%" PRIx64 ")", + LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); +#else + Log *log = GetLog(LLDBLog::Thread); + if (log) + log->Error("%s called on thread that has been destroyed (tid = 0x%" PRIx64 + ", ptid = 0x%" PRIx64 ")", + LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); +#endif + return true; +} + +bool ThreadPlanNull::ShouldStop(Event *event_ptr) { +#ifdef LLDB_CONFIGURATION_DEBUG + fprintf(stderr, + "error: %s called on thread that has been destroyed (tid = 0x%" PRIx64 + ", ptid = 0x%" PRIx64 ")", + LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); +#else + Log *log = GetLog(LLDBLog::Thread); + if (log) + log->Error("%s called on thread that has been destroyed (tid = 0x%" PRIx64 + ", ptid = 0x%" PRIx64 ")", + LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); +#endif + return true; +} + +bool ThreadPlanNull::WillStop() { +#ifdef LLDB_CONFIGURATION_DEBUG + fprintf(stderr, + "error: %s called on thread that has been destroyed (tid = 0x%" PRIx64 + ", ptid = 0x%" PRIx64 ")", + LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); +#else + Log *log = GetLog(LLDBLog::Thread); + if (log) + log->Error("%s called on thread that has been destroyed (tid = 0x%" PRIx64 + ", ptid = 0x%" PRIx64 ")", + LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); +#endif + return true; +} + +bool ThreadPlanNull::DoPlanExplainsStop(Event *event_ptr) { +#ifdef LLDB_CONFIGURATION_DEBUG + fprintf(stderr, + "error: %s called on thread that has been destroyed (tid = 0x%" PRIx64 + ", ptid = 0x%" PRIx64 ")", + LLVM_PRETTY_FUNCTION, GetThread().GetID(), GetThread().GetProtocolID()); +#else + Log *log = GetLog(LLDBLog::Thread); + if (log) + log->Error("%s called on thread that has been destroyed (tid = 0x%" PRIx64 + ", ptid = 0x%" PRIx64 ")", + LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); +#endif + return true; +} + +// The null plan is never done. +bool ThreadPlanNull::MischiefManaged() { +// The null plan is never done. +#ifdef LLDB_CONFIGURATION_DEBUG + fprintf(stderr, + "error: %s called on thread that has been destroyed (tid = 0x%" PRIx64 + ", ptid = 0x%" PRIx64 ")", + LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); +#else + Log *log = GetLog(LLDBLog::Thread); + if (log) + log->Error("%s called on thread that has been destroyed (tid = 0x%" PRIx64 + ", ptid = 0x%" PRIx64 ")", + LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); +#endif + return false; +} + +lldb::StateType ThreadPlanNull::GetPlanRunState() { +// Not sure what to return here. This is a dead thread. +#ifdef LLDB_CONFIGURATION_DEBUG + fprintf(stderr, + "error: %s called on thread that has been destroyed (tid = 0x%" PRIx64 + ", ptid = 0x%" PRIx64 ")", + LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); +#else + Log *log = GetLog(LLDBLog::Thread); + if (log) + log->Error("%s called on thread that has been destroyed (tid = 0x%" PRIx64 + ", ptid = 0x%" PRIx64 ")", + LLVM_PRETTY_FUNCTION, m_tid, GetThread().GetProtocolID()); +#endif + return eStateRunning; +} diff --git a/contrib/llvm-project/lldb/source/Target/ThreadPlanBase.cpp b/contrib/llvm-project/lldb/source/Target/ThreadPlanBase.cpp new file mode 100644 index 000000000000..dfd2157e70d4 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadPlanBase.cpp @@ -0,0 +1,198 @@ +//===-- ThreadPlanBase.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 "lldb/Target/ThreadPlanBase.h" + +// +#include "lldb/Breakpoint/Breakpoint.h" +#include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Breakpoint/BreakpointSite.h" +#include "lldb/Breakpoint/StoppointCallbackContext.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/StopInfo.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Stream.h" + +using namespace lldb; +using namespace lldb_private; + +// ThreadPlanBase: This one always stops, and never has anything particular to +// do. +// FIXME: The "signal handling" policies should probably go here. + +ThreadPlanBase::ThreadPlanBase(Thread &thread) + : ThreadPlan(ThreadPlan::eKindBase, "base plan", thread, eVoteYes, + eVoteNoOpinion) { +// Set the tracer to a default tracer. +// FIXME: need to add a thread settings variable to pix various tracers... +#define THREAD_PLAN_USE_ASSEMBLY_TRACER 1 + +#ifdef THREAD_PLAN_USE_ASSEMBLY_TRACER + ThreadPlanTracerSP new_tracer_sp(new ThreadPlanAssemblyTracer(thread)); +#else + ThreadPlanTracerSP new_tracer_sp(new ThreadPlanTracer(m_thread)); +#endif + new_tracer_sp->EnableTracing(thread.GetTraceEnabledState()); + SetThreadPlanTracer(new_tracer_sp); + SetIsControllingPlan(true); +} + +ThreadPlanBase::~ThreadPlanBase() = default; + +void ThreadPlanBase::GetDescription(Stream *s, lldb::DescriptionLevel level) { + s->Printf("Base thread plan."); +} + +bool ThreadPlanBase::ValidatePlan(Stream *error) { return true; } + +bool ThreadPlanBase::DoPlanExplainsStop(Event *event_ptr) { + // The base plan should defer to its tracer, since by default it always + // handles the stop. + return !TracerExplainsStop(); +} + +Vote ThreadPlanBase::ShouldReportStop(Event *event_ptr) { + StopInfoSP stop_info_sp = GetThread().GetStopInfo(); + if (stop_info_sp) { + bool should_notify = stop_info_sp->ShouldNotify(event_ptr); + if (should_notify) + return eVoteYes; + else + return eVoteNoOpinion; + } else + return eVoteNoOpinion; +} + +bool ThreadPlanBase::ShouldStop(Event *event_ptr) { + m_report_stop_vote = eVoteYes; + m_report_run_vote = eVoteYes; + + Log *log = GetLog(LLDBLog::Step); + + StopInfoSP stop_info_sp = GetPrivateStopInfo(); + if (stop_info_sp) { + StopReason reason = stop_info_sp->GetStopReason(); + switch (reason) { + case eStopReasonInvalid: + case eStopReasonNone: + // This + m_report_run_vote = eVoteNoOpinion; + m_report_stop_vote = eVoteNo; + return false; + + case eStopReasonBreakpoint: + case eStopReasonWatchpoint: + if (stop_info_sp->ShouldStopSynchronous(event_ptr)) { + // If we are going to stop for a breakpoint, then unship the other + // plans at this point. Don't force the discard, however, so + // Controlling plans can stay in place if they want to. + LLDB_LOGF( + log, + "Base plan discarding thread plans for thread tid = 0x%4.4" PRIx64 + " (breakpoint hit.)", + m_tid); + GetThread().DiscardThreadPlans(false); + return true; + } + // If we aren't going to stop at this breakpoint, and it is internal, + // don't report this stop or the subsequent running event. Otherwise we + // will post the stopped & running, but the stopped event will get marked + // with "restarted" so the UI will know to wait and expect the consequent + // "running". + if (stop_info_sp->ShouldNotify(event_ptr)) { + m_report_stop_vote = eVoteYes; + m_report_run_vote = eVoteYes; + } else { + m_report_stop_vote = eVoteNo; + m_report_run_vote = eVoteNo; + } + return false; + + // TODO: the break below was missing, was this intentional??? If so + // please mention it + break; + + case eStopReasonException: + // If we crashed, discard thread plans and stop. Don't force the + // discard, however, since on rerun the target may clean up this + // exception and continue normally from there. + LLDB_LOGF( + log, + "Base plan discarding thread plans for thread tid = 0x%4.4" PRIx64 + " (exception: %s)", + m_tid, stop_info_sp->GetDescription()); + GetThread().DiscardThreadPlans(false); + return true; + + case eStopReasonExec: + // If we crashed, discard thread plans and stop. Don't force the + // discard, however, since on rerun the target may clean up this + // exception and continue normally from there. + LLDB_LOGF( + log, + "Base plan discarding thread plans for thread tid = 0x%4.4" PRIx64 + " (exec.)", + m_tid); + GetThread().DiscardThreadPlans(false); + return true; + + case eStopReasonThreadExiting: + case eStopReasonSignal: + if (stop_info_sp->ShouldStop(event_ptr)) { + LLDB_LOGF( + log, + "Base plan discarding thread plans for thread tid = 0x%4.4" PRIx64 + " (signal: %s)", + m_tid, stop_info_sp->GetDescription()); + GetThread().DiscardThreadPlans(false); + return true; + } else { + // We're not going to stop, but while we are here, let's figure out + // whether to report this. + if (stop_info_sp->ShouldNotify(event_ptr)) + m_report_stop_vote = eVoteYes; + else + m_report_stop_vote = eVoteNo; + } + return false; + + default: + return true; + } + + } else { + m_report_run_vote = eVoteNoOpinion; + m_report_stop_vote = eVoteNo; + } + + // If there's no explicit reason to stop, then we will continue. + return false; +} + +bool ThreadPlanBase::StopOthers() { return false; } + +StateType ThreadPlanBase::GetPlanRunState() { return eStateRunning; } + +bool ThreadPlanBase::WillStop() { return true; } + +bool ThreadPlanBase::DoWillResume(lldb::StateType resume_state, + bool current_plan) { + // Reset these to the default values so we don't set them wrong, then not get + // asked for a while, then return the wrong answer. + m_report_run_vote = eVoteNoOpinion; + m_report_stop_vote = eVoteNo; + return true; +} + +// The base plan is never done. +bool ThreadPlanBase::MischiefManaged() { + // The base plan is never done. + return false; +} diff --git a/contrib/llvm-project/lldb/source/Target/ThreadPlanCallFunction.cpp b/contrib/llvm-project/lldb/source/Target/ThreadPlanCallFunction.cpp new file mode 100644 index 000000000000..50dcb66b9719 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadPlanCallFunction.cpp @@ -0,0 +1,474 @@ +//===-- ThreadPlanCallFunction.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 "lldb/Target/ThreadPlanCallFunction.h" +#include "lldb/Breakpoint/Breakpoint.h" +#include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Core/Address.h" +#include "lldb/Core/DumpRegisterValue.h" +#include "lldb/Core/Module.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Target/ABI.h" +#include "lldb/Target/LanguageRuntime.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/StopInfo.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadPlanRunToAddress.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Stream.h" + +#include <memory> + +using namespace lldb; +using namespace lldb_private; + +// ThreadPlanCallFunction: Plan to call a single function +bool ThreadPlanCallFunction::ConstructorSetup( + Thread &thread, ABI *&abi, lldb::addr_t &start_load_addr, + lldb::addr_t &function_load_addr) { + SetIsControllingPlan(true); + SetOkayToDiscard(false); + SetPrivate(true); + + ProcessSP process_sp(thread.GetProcess()); + if (!process_sp) + return false; + + abi = process_sp->GetABI().get(); + + if (!abi) + return false; + + Log *log = GetLog(LLDBLog::Step); + + SetBreakpoints(); + + m_function_sp = thread.GetRegisterContext()->GetSP() - abi->GetRedZoneSize(); + // If we can't read memory at the point of the process where we are planning + // to put our function, we're not going to get any further... + Status error; + process_sp->ReadUnsignedIntegerFromMemory(m_function_sp, 4, 0, error); + if (!error.Success()) { + m_constructor_errors.Printf( + "Trying to put the stack in unreadable memory at: 0x%" PRIx64 ".", + m_function_sp); + LLDB_LOGF(log, "ThreadPlanCallFunction(%p): %s.", static_cast<void *>(this), + m_constructor_errors.GetData()); + return false; + } + + llvm::Expected<Address> start_address = GetTarget().GetEntryPointAddress(); + if (!start_address) { + m_constructor_errors.Printf( + "%s", llvm::toString(start_address.takeError()).c_str()); + LLDB_LOGF(log, "ThreadPlanCallFunction(%p): %s.", static_cast<void *>(this), + m_constructor_errors.GetData()); + return false; + } + + m_start_addr = *start_address; + start_load_addr = m_start_addr.GetLoadAddress(&GetTarget()); + + // Checkpoint the thread state so we can restore it later. + if (log && log->GetVerbose()) + ReportRegisterState("About to checkpoint thread before function call. " + "Original register state was:"); + + if (!thread.CheckpointThreadState(m_stored_thread_state)) { + m_constructor_errors.Printf("Setting up ThreadPlanCallFunction, failed to " + "checkpoint thread state."); + LLDB_LOGF(log, "ThreadPlanCallFunction(%p): %s.", static_cast<void *>(this), + m_constructor_errors.GetData()); + return false; + } + function_load_addr = m_function_addr.GetLoadAddress(&GetTarget()); + + return true; +} + +ThreadPlanCallFunction::ThreadPlanCallFunction( + Thread &thread, const Address &function, const CompilerType &return_type, + llvm::ArrayRef<addr_t> args, const EvaluateExpressionOptions &options) + : ThreadPlan(ThreadPlan::eKindCallFunction, "Call function plan", thread, + eVoteNoOpinion, eVoteNoOpinion), + m_valid(false), m_stop_other_threads(options.GetStopOthers()), + m_unwind_on_error(options.DoesUnwindOnError()), + m_ignore_breakpoints(options.DoesIgnoreBreakpoints()), + m_debug_execution(options.GetDebug()), + m_trap_exceptions(options.GetTrapExceptions()), m_function_addr(function), + m_start_addr(), m_function_sp(0), m_subplan_sp(), + m_cxx_language_runtime(nullptr), m_objc_language_runtime(nullptr), + m_stored_thread_state(), m_real_stop_info_sp(), m_constructor_errors(), + m_return_valobj_sp(), m_takedown_done(false), + m_should_clear_objc_exception_bp(false), + m_should_clear_cxx_exception_bp(false), + m_stop_address(LLDB_INVALID_ADDRESS), m_return_type(return_type) { + lldb::addr_t start_load_addr = LLDB_INVALID_ADDRESS; + lldb::addr_t function_load_addr = LLDB_INVALID_ADDRESS; + ABI *abi = nullptr; + + if (!ConstructorSetup(thread, abi, start_load_addr, function_load_addr)) + return; + + if (!abi->PrepareTrivialCall(thread, m_function_sp, function_load_addr, + start_load_addr, args)) + return; + + ReportRegisterState("Function call was set up. Register state was:"); + + m_valid = true; +} + +ThreadPlanCallFunction::ThreadPlanCallFunction( + Thread &thread, const Address &function, + const EvaluateExpressionOptions &options) + : ThreadPlan(ThreadPlan::eKindCallFunction, "Call function plan", thread, + eVoteNoOpinion, eVoteNoOpinion), + m_valid(false), m_stop_other_threads(options.GetStopOthers()), + m_unwind_on_error(options.DoesUnwindOnError()), + m_ignore_breakpoints(options.DoesIgnoreBreakpoints()), + m_debug_execution(options.GetDebug()), + m_trap_exceptions(options.GetTrapExceptions()), m_function_addr(function), + m_start_addr(), m_function_sp(0), m_subplan_sp(), + m_cxx_language_runtime(nullptr), m_objc_language_runtime(nullptr), + m_stored_thread_state(), m_real_stop_info_sp(), m_constructor_errors(), + m_return_valobj_sp(), m_takedown_done(false), + m_should_clear_objc_exception_bp(false), + m_should_clear_cxx_exception_bp(false), + m_stop_address(LLDB_INVALID_ADDRESS), m_return_type(CompilerType()) {} + +ThreadPlanCallFunction::~ThreadPlanCallFunction() { + DoTakedown(PlanSucceeded()); +} + +void ThreadPlanCallFunction::ReportRegisterState(const char *message) { + Log *log = GetLog(LLDBLog::Step); + if (log && log->GetVerbose()) { + StreamString strm; + RegisterContext *reg_ctx = GetThread().GetRegisterContext().get(); + + log->PutCString(message); + + RegisterValue reg_value; + + for (uint32_t reg_idx = 0, num_registers = reg_ctx->GetRegisterCount(); + reg_idx < num_registers; ++reg_idx) { + const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoAtIndex(reg_idx); + if (reg_ctx->ReadRegister(reg_info, reg_value)) { + DumpRegisterValue(reg_value, strm, *reg_info, true, false, + eFormatDefault); + strm.EOL(); + } + } + log->PutString(strm.GetString()); + } +} + +void ThreadPlanCallFunction::DoTakedown(bool success) { + Log *log = GetLog(LLDBLog::Step); + + if (!m_valid) { + // Don't call DoTakedown if we were never valid to begin with. + LLDB_LOGF(log, + "ThreadPlanCallFunction(%p): Log called on " + "ThreadPlanCallFunction that was never valid.", + static_cast<void *>(this)); + return; + } + + if (!m_takedown_done) { + Thread &thread = GetThread(); + if (success) { + SetReturnValue(); + } + LLDB_LOGF(log, + "ThreadPlanCallFunction(%p): DoTakedown called for thread " + "0x%4.4" PRIx64 ", m_valid: %d complete: %d.\n", + static_cast<void *>(this), m_tid, m_valid, IsPlanComplete()); + m_takedown_done = true; + m_stop_address = + thread.GetStackFrameAtIndex(0)->GetRegisterContext()->GetPC(); + m_real_stop_info_sp = GetPrivateStopInfo(); + if (!thread.RestoreRegisterStateFromCheckpoint(m_stored_thread_state)) { + LLDB_LOGF(log, + "ThreadPlanCallFunction(%p): DoTakedown failed to restore " + "register state", + static_cast<void *>(this)); + } + SetPlanComplete(success); + ClearBreakpoints(); + if (log && log->GetVerbose()) + ReportRegisterState("Restoring thread state after function call. " + "Restored register state:"); + } else { + LLDB_LOGF(log, + "ThreadPlanCallFunction(%p): DoTakedown called as no-op for " + "thread 0x%4.4" PRIx64 ", m_valid: %d complete: %d.\n", + static_cast<void *>(this), m_tid, m_valid, IsPlanComplete()); + } +} + +void ThreadPlanCallFunction::DidPop() { DoTakedown(PlanSucceeded()); } + +void ThreadPlanCallFunction::GetDescription(Stream *s, DescriptionLevel level) { + if (level == eDescriptionLevelBrief) { + s->Printf("Function call thread plan"); + } else { + s->Printf("Thread plan to call 0x%" PRIx64, + m_function_addr.GetLoadAddress(&GetTarget())); + } +} + +bool ThreadPlanCallFunction::ValidatePlan(Stream *error) { + if (!m_valid) { + if (error) { + if (m_constructor_errors.GetSize() > 0) + error->PutCString(m_constructor_errors.GetString()); + else + error->PutCString("Unknown error"); + } + return false; + } + + return true; +} + +Vote ThreadPlanCallFunction::ShouldReportStop(Event *event_ptr) { + if (m_takedown_done || IsPlanComplete()) + return eVoteYes; + else + return ThreadPlan::ShouldReportStop(event_ptr); +} + +bool ThreadPlanCallFunction::DoPlanExplainsStop(Event *event_ptr) { + Log *log(GetLog(LLDBLog::Step | LLDBLog::Process)); + m_real_stop_info_sp = GetPrivateStopInfo(); + + // If our subplan knows why we stopped, even if it's done (which would + // forward the question to us) we answer yes. + if (m_subplan_sp && m_subplan_sp->PlanExplainsStop(event_ptr)) { + SetPlanComplete(); + return true; + } + + // Check if the breakpoint is one of ours. + + StopReason stop_reason; + if (!m_real_stop_info_sp) + stop_reason = eStopReasonNone; + else + stop_reason = m_real_stop_info_sp->GetStopReason(); + LLDB_LOG(log, + "ThreadPlanCallFunction::PlanExplainsStop: Got stop reason - {0}.", + Thread::StopReasonAsString(stop_reason)); + + if (stop_reason == eStopReasonBreakpoint && BreakpointsExplainStop()) + return true; + + // One more quirk here. If this event was from Halt interrupting the target, + // then we should not consider ourselves complete. Return true to + // acknowledge the stop. + if (Process::ProcessEventData::GetInterruptedFromEvent(event_ptr)) { + LLDB_LOGF(log, "ThreadPlanCallFunction::PlanExplainsStop: The event is an " + "Interrupt, returning true."); + return true; + } + // We control breakpoints separately from other "stop reasons." So first, + // check the case where we stopped for an internal breakpoint, in that case, + // continue on. If it is not an internal breakpoint, consult + // m_ignore_breakpoints. + + if (stop_reason == eStopReasonBreakpoint) { + uint64_t break_site_id = m_real_stop_info_sp->GetValue(); + BreakpointSiteSP bp_site_sp; + bp_site_sp = m_process.GetBreakpointSiteList().FindByID(break_site_id); + if (bp_site_sp) { + uint32_t num_owners = bp_site_sp->GetNumberOfConstituents(); + bool is_internal = true; + for (uint32_t i = 0; i < num_owners; i++) { + Breakpoint &bp = bp_site_sp->GetConstituentAtIndex(i)->GetBreakpoint(); + LLDB_LOGF(log, + "ThreadPlanCallFunction::PlanExplainsStop: hit " + "breakpoint %d while calling function", + bp.GetID()); + + if (!bp.IsInternal()) { + is_internal = false; + break; + } + } + if (is_internal) { + LLDB_LOGF(log, "ThreadPlanCallFunction::PlanExplainsStop hit an " + "internal breakpoint, not stopping."); + return false; + } + } + + if (m_ignore_breakpoints) { + LLDB_LOGF(log, + "ThreadPlanCallFunction::PlanExplainsStop: we are ignoring " + "breakpoints, overriding breakpoint stop info ShouldStop, " + "returning true"); + m_real_stop_info_sp->OverrideShouldStop(false); + return true; + } else { + LLDB_LOGF(log, "ThreadPlanCallFunction::PlanExplainsStop: we are not " + "ignoring breakpoints, overriding breakpoint stop info " + "ShouldStop, returning true"); + m_real_stop_info_sp->OverrideShouldStop(true); + return false; + } + } else if (!m_unwind_on_error) { + // If we don't want to discard this plan, than any stop we don't understand + // should be propagated up the stack. + return false; + } else { + // If the subplan is running, any crashes are attributable to us. If we + // want to discard the plan, then we say we explain the stop but if we are + // going to be discarded, let whoever is above us explain the stop. But + // don't discard the plan if the stop would restart itself (for instance if + // it is a signal that is set not to stop. Check that here first. We just + // say we explain the stop but aren't done and everything will continue on + // from there. + + if (m_real_stop_info_sp && + m_real_stop_info_sp->ShouldStopSynchronous(event_ptr)) { + SetPlanComplete(false); + return m_subplan_sp ? m_unwind_on_error : false; + } else + return true; + } +} + +bool ThreadPlanCallFunction::ShouldStop(Event *event_ptr) { + // We do some computation in DoPlanExplainsStop that may or may not set the + // plan as complete. We need to do that here to make sure our state is + // correct. + DoPlanExplainsStop(event_ptr); + + if (IsPlanComplete()) { + ReportRegisterState("Function completed. Register state was:"); + return true; + } else { + return false; + } +} + +bool ThreadPlanCallFunction::StopOthers() { return m_stop_other_threads; } + +StateType ThreadPlanCallFunction::GetPlanRunState() { return eStateRunning; } + +void ThreadPlanCallFunction::DidPush() { + //#define SINGLE_STEP_EXPRESSIONS + + // Now set the thread state to "no reason" so we don't run with whatever + // signal was outstanding... Wait till the plan is pushed so we aren't + // changing the stop info till we're about to run. + + GetThread().SetStopInfoToNothing(); + +#ifndef SINGLE_STEP_EXPRESSIONS + Thread &thread = GetThread(); + m_subplan_sp = std::make_shared<ThreadPlanRunToAddress>(thread, m_start_addr, + m_stop_other_threads); + + thread.QueueThreadPlan(m_subplan_sp, false); + m_subplan_sp->SetPrivate(true); +#endif +} + +bool ThreadPlanCallFunction::WillStop() { return true; } + +bool ThreadPlanCallFunction::MischiefManaged() { + Log *log = GetLog(LLDBLog::Step); + + if (IsPlanComplete()) { + LLDB_LOGF(log, "ThreadPlanCallFunction(%p): Completed call function plan.", + static_cast<void *>(this)); + + ThreadPlan::MischiefManaged(); + return true; + } else { + return false; + } +} + +void ThreadPlanCallFunction::SetBreakpoints() { + if (m_trap_exceptions) { + m_cxx_language_runtime = + m_process.GetLanguageRuntime(eLanguageTypeC_plus_plus); + m_objc_language_runtime = m_process.GetLanguageRuntime(eLanguageTypeObjC); + + if (m_cxx_language_runtime) { + m_should_clear_cxx_exception_bp = + !m_cxx_language_runtime->ExceptionBreakpointsAreSet(); + m_cxx_language_runtime->SetExceptionBreakpoints(); + } + if (m_objc_language_runtime) { + m_should_clear_objc_exception_bp = + !m_objc_language_runtime->ExceptionBreakpointsAreSet(); + m_objc_language_runtime->SetExceptionBreakpoints(); + } + } +} + +void ThreadPlanCallFunction::ClearBreakpoints() { + if (m_trap_exceptions) { + if (m_cxx_language_runtime && m_should_clear_cxx_exception_bp) + m_cxx_language_runtime->ClearExceptionBreakpoints(); + if (m_objc_language_runtime && m_should_clear_objc_exception_bp) + m_objc_language_runtime->ClearExceptionBreakpoints(); + } +} + +bool ThreadPlanCallFunction::BreakpointsExplainStop() { + StopInfoSP stop_info_sp = GetPrivateStopInfo(); + + if (m_trap_exceptions) { + if ((m_cxx_language_runtime && + m_cxx_language_runtime->ExceptionBreakpointsExplainStop( + stop_info_sp)) || + (m_objc_language_runtime && + m_objc_language_runtime->ExceptionBreakpointsExplainStop( + stop_info_sp))) { + Log *log = GetLog(LLDBLog::Step); + LLDB_LOGF(log, "ThreadPlanCallFunction::BreakpointsExplainStop - Hit an " + "exception breakpoint, setting plan complete."); + + SetPlanComplete(false); + + // If the user has set the ObjC language breakpoint, it would normally + // get priority over our internal catcher breakpoint, but in this case we + // can't let that happen, so force the ShouldStop here. + stop_info_sp->OverrideShouldStop(true); + return true; + } + } + + return false; +} + +void ThreadPlanCallFunction::SetStopOthers(bool new_value) { + m_subplan_sp->SetStopOthers(new_value); +} + +void ThreadPlanCallFunction::RestoreThreadState() { + GetThread().RestoreThreadStateFromCheckpoint(m_stored_thread_state); +} + +void ThreadPlanCallFunction::SetReturnValue() { + const ABI *abi = m_process.GetABI().get(); + if (abi && m_return_type.IsValid()) { + const bool persistent = false; + m_return_valobj_sp = + abi->GetReturnValueObject(GetThread(), m_return_type, persistent); + } +} diff --git a/contrib/llvm-project/lldb/source/Target/ThreadPlanCallFunctionUsingABI.cpp b/contrib/llvm-project/lldb/source/Target/ThreadPlanCallFunctionUsingABI.cpp new file mode 100644 index 000000000000..52b27309e912 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadPlanCallFunctionUsingABI.cpp @@ -0,0 +1,66 @@ +//===-- ThreadPlanCallFunctionUsingABI.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 "lldb/Target/ThreadPlanCallFunctionUsingABI.h" +#include "lldb/Core/Address.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Stream.h" + +using namespace lldb; +using namespace lldb_private; + +// ThreadPlanCallFunctionUsingABI: Plan to call a single function using the ABI +// instead of JIT +ThreadPlanCallFunctionUsingABI::ThreadPlanCallFunctionUsingABI( + Thread &thread, const Address &function, llvm::Type &prototype, + llvm::Type &return_type, llvm::ArrayRef<ABI::CallArgument> args, + const EvaluateExpressionOptions &options) + : ThreadPlanCallFunction(thread, function, options), + m_return_type(return_type) { + lldb::addr_t start_load_addr = LLDB_INVALID_ADDRESS; + lldb::addr_t function_load_addr = LLDB_INVALID_ADDRESS; + ABI *abi = nullptr; + + if (!ConstructorSetup(thread, abi, start_load_addr, function_load_addr)) + return; + + if (!abi->PrepareTrivialCall(thread, m_function_sp, function_load_addr, + start_load_addr, prototype, args)) + return; + + ReportRegisterState("ABI Function call was set up. Register state was:"); + + m_valid = true; +} + +ThreadPlanCallFunctionUsingABI::~ThreadPlanCallFunctionUsingABI() = default; + +void ThreadPlanCallFunctionUsingABI::GetDescription(Stream *s, + DescriptionLevel level) { + if (level == eDescriptionLevelBrief) { + s->Printf("Function call thread plan using ABI instead of JIT"); + } else { + s->Printf("Thread plan to call 0x%" PRIx64 " using ABI instead of JIT", + m_function_addr.GetLoadAddress(&GetTarget())); + } +} + +void ThreadPlanCallFunctionUsingABI::SetReturnValue() { + const ABI *abi = m_process.GetABI().get(); + + // Ask the abi for the return value + if (abi) { + const bool persistent = false; + m_return_valobj_sp = + abi->GetReturnValueObject(GetThread(), m_return_type, persistent); + } +} diff --git a/contrib/llvm-project/lldb/source/Target/ThreadPlanCallOnFunctionExit.cpp b/contrib/llvm-project/lldb/source/Target/ThreadPlanCallOnFunctionExit.cpp new file mode 100644 index 000000000000..4bccf96d721b --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadPlanCallOnFunctionExit.cpp @@ -0,0 +1,97 @@ +//===-- ThreadPlanCallOnFunctionExit.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 "lldb/Target/ThreadPlanCallOnFunctionExit.h" + +using namespace lldb; +using namespace lldb_private; + +ThreadPlanCallOnFunctionExit::ThreadPlanCallOnFunctionExit( + Thread &thread, const Callback &callback) + : ThreadPlan(ThreadPlanKind::eKindGeneric, "CallOnFunctionExit", thread, + eVoteNoOpinion, eVoteNoOpinion // TODO check with Jim on these + ), + m_callback(callback) { + // We are not a user-generated plan. + SetIsControllingPlan(false); +} + +void ThreadPlanCallOnFunctionExit::DidPush() { + // We now want to queue the "step out" thread plan so it executes and + // completes. + + // Set stop vote to eVoteNo. + Status status; + m_step_out_threadplan_sp = GetThread().QueueThreadPlanForStepOut( + false, // abort other plans + nullptr, // addr_context + true, // first instruction + true, // stop other threads + eVoteNo, // do not say "we're stopping" + eVoteNoOpinion, // don't care about run state broadcasting + 0, // frame_idx + status, // status + eLazyBoolCalculate // avoid code w/o debinfo + ); +} + +// ThreadPlan API + +void ThreadPlanCallOnFunctionExit::GetDescription( + Stream *s, lldb::DescriptionLevel level) { + if (!s) + return; + s->Printf("Running until completion of current function, then making " + "callback."); +} + +bool ThreadPlanCallOnFunctionExit::ValidatePlan(Stream *error) { + // We'll say we're always good since I don't know what would make this + // invalid. + return true; +} + +bool ThreadPlanCallOnFunctionExit::ShouldStop(Event *event_ptr) { + // If this is where we find out that an internal stop came in, then: Check if + // the step-out plan completed. If it did, then we want to run the callback + // here (our reason for living...) + if (m_step_out_threadplan_sp && m_step_out_threadplan_sp->IsPlanComplete()) { + m_callback(); + + // We no longer need the pointer to the step-out thread plan. + m_step_out_threadplan_sp.reset(); + + // Indicate that this plan is done and can be discarded. + SetPlanComplete(); + + // We're done now, but we want to return false so that we don't cause the + // thread to really stop. + } + + return false; +} + +bool ThreadPlanCallOnFunctionExit::WillStop() { + // The code looks like the return value is ignored via ThreadList:: + // ShouldStop(). This is called when we really are going to stop. We don't + // care and don't need to do anything here. + return false; +} + +bool ThreadPlanCallOnFunctionExit::DoPlanExplainsStop(Event *event_ptr) { + // We don't ever explain a stop. The only stop that is relevant to us + // directly is the step_out plan we added to do the heavy lifting of getting + // us past the current method. + return false; +} + +lldb::StateType ThreadPlanCallOnFunctionExit::GetPlanRunState() { + // This value doesn't matter - we'll never be the top thread plan, so nobody + // will ask us this question. + return eStateRunning; +} diff --git a/contrib/llvm-project/lldb/source/Target/ThreadPlanCallUserExpression.cpp b/contrib/llvm-project/lldb/source/Target/ThreadPlanCallUserExpression.cpp new file mode 100644 index 000000000000..65758599dcee --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadPlanCallUserExpression.cpp @@ -0,0 +1,117 @@ +//===-- ThreadPlanCallUserExpression.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 "lldb/Target/ThreadPlanCallUserExpression.h" + +#include "lldb/Breakpoint/Breakpoint.h" +#include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Core/Address.h" +#include "lldb/Expression/DiagnosticManager.h" +#include "lldb/Expression/DynamicCheckerFunctions.h" +#include "lldb/Expression/UserExpression.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Target/LanguageRuntime.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/StopInfo.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadPlanRunToAddress.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Stream.h" + +using namespace lldb; +using namespace lldb_private; + +// ThreadPlanCallUserExpression: Plan to call a single function + +ThreadPlanCallUserExpression::ThreadPlanCallUserExpression( + Thread &thread, Address &function, llvm::ArrayRef<lldb::addr_t> args, + const EvaluateExpressionOptions &options, + lldb::UserExpressionSP &user_expression_sp) + : ThreadPlanCallFunction(thread, function, CompilerType(), args, options), + m_user_expression_sp(user_expression_sp) { + // User expressions are generally "User generated" so we should set them up + // to stop when done. + SetIsControllingPlan(true); + SetOkayToDiscard(false); +} + +ThreadPlanCallUserExpression::~ThreadPlanCallUserExpression() = default; + +void ThreadPlanCallUserExpression::GetDescription( + Stream *s, lldb::DescriptionLevel level) { + if (level == eDescriptionLevelBrief) + s->Printf("User Expression thread plan"); + else + ThreadPlanCallFunction::GetDescription(s, level); +} + +void ThreadPlanCallUserExpression::DidPush() { + ThreadPlanCallFunction::DidPush(); + if (m_user_expression_sp) + m_user_expression_sp->WillStartExecuting(); +} + +void ThreadPlanCallUserExpression::DidPop() { + ThreadPlanCallFunction::DidPop(); + if (m_user_expression_sp) + m_user_expression_sp.reset(); +} + +bool ThreadPlanCallUserExpression::MischiefManaged() { + Log *log = GetLog(LLDBLog::Step); + + if (IsPlanComplete()) { + LLDB_LOGF(log, "ThreadPlanCallFunction(%p): Completed call function plan.", + static_cast<void *>(this)); + + if (m_manage_materialization && PlanSucceeded() && m_user_expression_sp) { + lldb::addr_t function_stack_top; + lldb::addr_t function_stack_bottom; + lldb::addr_t function_stack_pointer = GetFunctionStackPointer(); + + function_stack_bottom = function_stack_pointer - HostInfo::GetPageSize(); + function_stack_top = function_stack_pointer; + + DiagnosticManager diagnostics; + + ExecutionContext exe_ctx(GetThread()); + + m_user_expression_sp->FinalizeJITExecution( + diagnostics, exe_ctx, m_result_var_sp, function_stack_bottom, + function_stack_top); + } + + ThreadPlan::MischiefManaged(); + return true; + } else { + return false; + } +} + +StopInfoSP ThreadPlanCallUserExpression::GetRealStopInfo() { + StopInfoSP stop_info_sp = ThreadPlanCallFunction::GetRealStopInfo(); + + if (stop_info_sp) { + lldb::addr_t addr = GetStopAddress(); + DynamicCheckerFunctions *checkers = m_process.GetDynamicCheckers(); + StreamString s; + + if (checkers && checkers->DoCheckersExplainStop(addr, s)) + stop_info_sp->SetDescription(s.GetData()); + } + + return stop_info_sp; +} + +void ThreadPlanCallUserExpression::DoTakedown(bool success) { + ThreadPlanCallFunction::DoTakedown(success); + m_user_expression_sp->DidFinishExecuting(); +} diff --git a/contrib/llvm-project/lldb/source/Target/ThreadPlanPython.cpp b/contrib/llvm-project/lldb/source/Target/ThreadPlanPython.cpp new file mode 100644 index 000000000000..dfcb67eb1902 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadPlanPython.cpp @@ -0,0 +1,218 @@ +//===-- ThreadPlanPython.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 "lldb/Target/ThreadPlan.h" + +#include "lldb/Core/Debugger.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/Interfaces/ScriptedThreadPlanInterface.h" +#include "lldb/Interpreter/ScriptInterpreter.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadPlan.h" +#include "lldb/Target/ThreadPlanPython.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/State.h" + +using namespace lldb; +using namespace lldb_private; + +// ThreadPlanPython + +ThreadPlanPython::ThreadPlanPython(Thread &thread, const char *class_name, + const StructuredDataImpl &args_data) + : ThreadPlan(ThreadPlan::eKindPython, "Python based Thread Plan", thread, + eVoteNoOpinion, eVoteNoOpinion), + m_class_name(class_name), m_args_data(args_data), m_did_push(false), + m_stop_others(false) { + ScriptInterpreter *interpreter = GetScriptInterpreter(); + if (!interpreter) { + SetPlanComplete(false); + // FIXME: error handling + // error.SetErrorStringWithFormat( + // "ThreadPlanPython::%s () - ERROR: %s", __FUNCTION__, + // "Couldn't get script interpreter"); + return; + } + + m_interface = interpreter->CreateScriptedThreadPlanInterface(); + if (!m_interface) { + SetPlanComplete(false); + // FIXME: error handling + // error.SetErrorStringWithFormat( + // "ThreadPlanPython::%s () - ERROR: %s", __FUNCTION__, + // "Script interpreter couldn't create Scripted Thread Plan Interface"); + return; + } + + SetIsControllingPlan(true); + SetOkayToDiscard(true); + SetPrivate(false); +} + +bool ThreadPlanPython::ValidatePlan(Stream *error) { + if (!m_did_push) + return true; + + if (!m_implementation_sp) { + if (error) + error->Printf("Error constructing Python ThreadPlan: %s", + m_error_str.empty() ? "<unknown error>" + : m_error_str.c_str()); + return false; + } + + return true; +} + +ScriptInterpreter *ThreadPlanPython::GetScriptInterpreter() { + return m_process.GetTarget().GetDebugger().GetScriptInterpreter(); +} + +void ThreadPlanPython::DidPush() { + // We set up the script side in DidPush, so that it can push other plans in + // the constructor, and doesn't have to care about the details of DidPush. + m_did_push = true; + if (m_interface) { + auto obj_or_err = m_interface->CreatePluginObject( + m_class_name, this->shared_from_this(), m_args_data); + if (!obj_or_err) { + m_error_str = llvm::toString(obj_or_err.takeError()); + SetPlanComplete(false); + } else + m_implementation_sp = *obj_or_err; + } +} + +bool ThreadPlanPython::ShouldStop(Event *event_ptr) { + Log *log = GetLog(LLDBLog::Thread); + LLDB_LOGF(log, "%s called on Python Thread Plan: %s )", LLVM_PRETTY_FUNCTION, + m_class_name.c_str()); + + bool should_stop = true; + if (m_implementation_sp) { + auto should_stop_or_err = m_interface->ShouldStop(event_ptr); + if (!should_stop_or_err) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), should_stop_or_err.takeError(), + "Can't call ScriptedThreadPlan::ShouldStop."); + SetPlanComplete(false); + } else + should_stop = *should_stop_or_err; + } + return should_stop; +} + +bool ThreadPlanPython::IsPlanStale() { + Log *log = GetLog(LLDBLog::Thread); + LLDB_LOGF(log, "%s called on Python Thread Plan: %s )", LLVM_PRETTY_FUNCTION, + m_class_name.c_str()); + + bool is_stale = true; + if (m_implementation_sp) { + auto is_stale_or_err = m_interface->IsStale(); + if (!is_stale_or_err) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), is_stale_or_err.takeError(), + "Can't call ScriptedThreadPlan::IsStale."); + SetPlanComplete(false); + } else + is_stale = *is_stale_or_err; + } + return is_stale; +} + +bool ThreadPlanPython::DoPlanExplainsStop(Event *event_ptr) { + Log *log = GetLog(LLDBLog::Thread); + LLDB_LOGF(log, "%s called on Python Thread Plan: %s )", LLVM_PRETTY_FUNCTION, + m_class_name.c_str()); + + bool explains_stop = true; + if (m_implementation_sp) { + auto explains_stop_or_error = m_interface->ExplainsStop(event_ptr); + if (!explains_stop_or_error) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), + explains_stop_or_error.takeError(), + "Can't call ScriptedThreadPlan::ExplainsStop."); + SetPlanComplete(false); + } else + explains_stop = *explains_stop_or_error; + } + return explains_stop; +} + +bool ThreadPlanPython::MischiefManaged() { + Log *log = GetLog(LLDBLog::Thread); + LLDB_LOGF(log, "%s called on Python Thread Plan: %s )", LLVM_PRETTY_FUNCTION, + m_class_name.c_str()); + bool mischief_managed = true; + if (m_implementation_sp) { + // I don't really need mischief_managed, since it's simpler to just call + // SetPlanComplete in should_stop. + mischief_managed = IsPlanComplete(); + if (mischief_managed) { + // We need to cache the stop reason here we'll need it in GetDescription. + GetDescription(&m_stop_description, eDescriptionLevelBrief); + m_implementation_sp.reset(); + } + } + return mischief_managed; +} + +lldb::StateType ThreadPlanPython::GetPlanRunState() { + Log *log = GetLog(LLDBLog::Thread); + LLDB_LOGF(log, "%s called on Python Thread Plan: %s )", LLVM_PRETTY_FUNCTION, + m_class_name.c_str()); + lldb::StateType run_state = eStateRunning; + if (m_implementation_sp) + run_state = m_interface->GetRunState(); + return run_state; +} + +void ThreadPlanPython::GetDescription(Stream *s, lldb::DescriptionLevel level) { + Log *log = GetLog(LLDBLog::Thread); + LLDB_LOGF(log, "%s called on Python Thread Plan: %s )", LLVM_PRETTY_FUNCTION, + m_class_name.c_str()); + if (m_implementation_sp) { + ScriptInterpreter *script_interp = GetScriptInterpreter(); + if (script_interp) { + lldb::StreamSP stream = std::make_shared<lldb_private::StreamString>(); + llvm::Error err = m_interface->GetStopDescription(stream); + if (err) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), std::move(err), + "Can't call ScriptedThreadPlan::GetStopDescription."); + s->Printf("Python thread plan implemented by class %s.", + m_class_name.c_str()); + } else + s->PutCString( + reinterpret_cast<StreamString *>(stream.get())->GetData()); + } + return; + } + // It's an error not to have a description, so if we get here, we should + // add something. + if (m_stop_description.Empty()) + s->Printf("Python thread plan implemented by class %s.", + m_class_name.c_str()); + s->PutCString(m_stop_description.GetData()); +} + +// The ones below are not currently exported to Python. +bool ThreadPlanPython::WillStop() { + Log *log = GetLog(LLDBLog::Thread); + LLDB_LOGF(log, "%s called on Python Thread Plan: %s )", LLVM_PRETTY_FUNCTION, + m_class_name.c_str()); + return true; +} + +bool ThreadPlanPython::DoWillResume(lldb::StateType resume_state, + bool current_plan) { + m_stop_description.Clear(); + return true; +} diff --git a/contrib/llvm-project/lldb/source/Target/ThreadPlanRunToAddress.cpp b/contrib/llvm-project/lldb/source/Target/ThreadPlanRunToAddress.cpp new file mode 100644 index 000000000000..a2ac8c3d0966 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadPlanRunToAddress.cpp @@ -0,0 +1,203 @@ +//===-- ThreadPlanRunToAddress.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 "lldb/Target/ThreadPlanRunToAddress.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Stream.h" + +using namespace lldb; +using namespace lldb_private; + +// ThreadPlanRunToAddress: Continue plan + +ThreadPlanRunToAddress::ThreadPlanRunToAddress(Thread &thread, Address &address, + bool stop_others) + : ThreadPlan(ThreadPlan::eKindRunToAddress, "Run to address plan", thread, + eVoteNoOpinion, eVoteNoOpinion), + m_stop_others(stop_others), m_addresses(), m_break_ids() { + m_addresses.push_back( + address.GetOpcodeLoadAddress(thread.CalculateTarget().get())); + SetInitialBreakpoints(); +} + +ThreadPlanRunToAddress::ThreadPlanRunToAddress(Thread &thread, + lldb::addr_t address, + bool stop_others) + : ThreadPlan(ThreadPlan::eKindRunToAddress, "Run to address plan", thread, + eVoteNoOpinion, eVoteNoOpinion), + m_stop_others(stop_others), m_addresses(), m_break_ids() { + m_addresses.push_back( + thread.CalculateTarget()->GetOpcodeLoadAddress(address)); + SetInitialBreakpoints(); +} + +ThreadPlanRunToAddress::ThreadPlanRunToAddress( + Thread &thread, const std::vector<lldb::addr_t> &addresses, + bool stop_others) + : ThreadPlan(ThreadPlan::eKindRunToAddress, "Run to address plan", thread, + eVoteNoOpinion, eVoteNoOpinion), + m_stop_others(stop_others), m_addresses(addresses), m_break_ids() { + // Convert all addresses into opcode addresses to make sure we set + // breakpoints at the correct address. + Target &target = thread.GetProcess()->GetTarget(); + std::vector<lldb::addr_t>::iterator pos, end = m_addresses.end(); + for (pos = m_addresses.begin(); pos != end; ++pos) + *pos = target.GetOpcodeLoadAddress(*pos); + + SetInitialBreakpoints(); +} + +void ThreadPlanRunToAddress::SetInitialBreakpoints() { + size_t num_addresses = m_addresses.size(); + m_break_ids.resize(num_addresses); + + for (size_t i = 0; i < num_addresses; i++) { + Breakpoint *breakpoint; + breakpoint = + GetTarget().CreateBreakpoint(m_addresses[i], true, false).get(); + if (breakpoint != nullptr) { + if (breakpoint->IsHardware() && !breakpoint->HasResolvedLocations()) + m_could_not_resolve_hw_bp = true; + m_break_ids[i] = breakpoint->GetID(); + breakpoint->SetThreadID(m_tid); + breakpoint->SetBreakpointKind("run-to-address"); + } + } +} + +ThreadPlanRunToAddress::~ThreadPlanRunToAddress() { + size_t num_break_ids = m_break_ids.size(); + for (size_t i = 0; i < num_break_ids; i++) { + GetTarget().RemoveBreakpointByID(m_break_ids[i]); + } + m_could_not_resolve_hw_bp = false; +} + +void ThreadPlanRunToAddress::GetDescription(Stream *s, + lldb::DescriptionLevel level) { + size_t num_addresses = m_addresses.size(); + + if (level == lldb::eDescriptionLevelBrief) { + if (num_addresses == 0) { + s->Printf("run to address with no addresses given."); + return; + } else if (num_addresses == 1) + s->Printf("run to address: "); + else + s->Printf("run to addresses: "); + + for (size_t i = 0; i < num_addresses; i++) { + DumpAddress(s->AsRawOstream(), m_addresses[i], sizeof(addr_t)); + s->Printf(" "); + } + } else { + if (num_addresses == 0) { + s->Printf("run to address with no addresses given."); + return; + } else if (num_addresses == 1) + s->Printf("Run to address: "); + else { + s->Printf("Run to addresses: "); + } + + for (size_t i = 0; i < num_addresses; i++) { + if (num_addresses > 1) { + s->Printf("\n"); + s->Indent(); + } + + DumpAddress(s->AsRawOstream(), m_addresses[i], sizeof(addr_t)); + s->Printf(" using breakpoint: %d - ", m_break_ids[i]); + Breakpoint *breakpoint = + GetTarget().GetBreakpointByID(m_break_ids[i]).get(); + if (breakpoint) + breakpoint->Dump(s); + else + s->Printf("but the breakpoint has been deleted."); + } + } +} + +bool ThreadPlanRunToAddress::ValidatePlan(Stream *error) { + if (m_could_not_resolve_hw_bp) { + if (error) + error->Printf("Could not set hardware breakpoint(s)"); + return false; + } + + // If we couldn't set the breakpoint for some reason, then this won't work. + bool all_bps_good = true; + size_t num_break_ids = m_break_ids.size(); + for (size_t i = 0; i < num_break_ids; i++) { + if (m_break_ids[i] == LLDB_INVALID_BREAK_ID) { + all_bps_good = false; + if (error) { + error->Printf("Could not set breakpoint for address: "); + DumpAddress(error->AsRawOstream(), m_addresses[i], sizeof(addr_t)); + error->Printf("\n"); + } + } + } + return all_bps_good; +} + +bool ThreadPlanRunToAddress::DoPlanExplainsStop(Event *event_ptr) { + return AtOurAddress(); +} + +bool ThreadPlanRunToAddress::ShouldStop(Event *event_ptr) { + return AtOurAddress(); +} + +bool ThreadPlanRunToAddress::StopOthers() { return m_stop_others; } + +void ThreadPlanRunToAddress::SetStopOthers(bool new_value) { + m_stop_others = new_value; +} + +StateType ThreadPlanRunToAddress::GetPlanRunState() { return eStateRunning; } + +bool ThreadPlanRunToAddress::WillStop() { return true; } + +bool ThreadPlanRunToAddress::MischiefManaged() { + Log *log = GetLog(LLDBLog::Step); + + if (AtOurAddress()) { + // Remove the breakpoint + size_t num_break_ids = m_break_ids.size(); + + for (size_t i = 0; i < num_break_ids; i++) { + if (m_break_ids[i] != LLDB_INVALID_BREAK_ID) { + GetTarget().RemoveBreakpointByID(m_break_ids[i]); + m_break_ids[i] = LLDB_INVALID_BREAK_ID; + } + } + LLDB_LOGF(log, "Completed run to address plan."); + ThreadPlan::MischiefManaged(); + return true; + } else + return false; +} + +bool ThreadPlanRunToAddress::AtOurAddress() { + lldb::addr_t current_address = GetThread().GetRegisterContext()->GetPC(); + bool found_it = false; + size_t num_addresses = m_addresses.size(); + for (size_t i = 0; i < num_addresses; i++) { + if (m_addresses[i] == current_address) { + found_it = true; + break; + } + } + return found_it; +} diff --git a/contrib/llvm-project/lldb/source/Target/ThreadPlanShouldStopHere.cpp b/contrib/llvm-project/lldb/source/Target/ThreadPlanShouldStopHere.cpp new file mode 100644 index 000000000000..e72f8d8f51a2 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadPlanShouldStopHere.cpp @@ -0,0 +1,161 @@ +//===-- ThreadPlanShouldStopHere.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 "lldb/Target/ThreadPlanShouldStopHere.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" + +using namespace lldb; +using namespace lldb_private; + +// ThreadPlanShouldStopHere constructor +ThreadPlanShouldStopHere::ThreadPlanShouldStopHere(ThreadPlan *owner) + : m_callbacks(), m_baton(nullptr), m_owner(owner), + m_flags(ThreadPlanShouldStopHere::eNone) { + m_callbacks.should_stop_here_callback = + ThreadPlanShouldStopHere::DefaultShouldStopHereCallback; + m_callbacks.step_from_here_callback = + ThreadPlanShouldStopHere::DefaultStepFromHereCallback; +} + +ThreadPlanShouldStopHere::ThreadPlanShouldStopHere( + ThreadPlan *owner, const ThreadPlanShouldStopHereCallbacks *callbacks, + void *baton) + : m_callbacks(), m_baton(), m_owner(owner), + m_flags(ThreadPlanShouldStopHere::eNone) { + SetShouldStopHereCallbacks(callbacks, baton); +} + +ThreadPlanShouldStopHere::~ThreadPlanShouldStopHere() = default; + +bool ThreadPlanShouldStopHere::InvokeShouldStopHereCallback( + FrameComparison operation, Status &status) { + bool should_stop_here = true; + if (m_callbacks.should_stop_here_callback) { + should_stop_here = m_callbacks.should_stop_here_callback( + m_owner, m_flags, operation, status, m_baton); + Log *log = GetLog(LLDBLog::Step); + if (log) { + lldb::addr_t current_addr = + m_owner->GetThread().GetRegisterContext()->GetPC(0); + + LLDB_LOGF(log, "ShouldStopHere callback returned %u from 0x%" PRIx64 ".", + should_stop_here, current_addr); + } + } + + return should_stop_here; +} + +bool ThreadPlanShouldStopHere::DefaultShouldStopHereCallback( + ThreadPlan *current_plan, Flags &flags, FrameComparison operation, + Status &status, void *baton) { + bool should_stop_here = true; + StackFrame *frame = current_plan->GetThread().GetStackFrameAtIndex(0).get(); + if (!frame) + return true; + + Log *log = GetLog(LLDBLog::Step); + + if ((operation == eFrameCompareOlder && flags.Test(eStepOutAvoidNoDebug)) || + (operation == eFrameCompareYounger && flags.Test(eStepInAvoidNoDebug)) || + (operation == eFrameCompareSameParent && + flags.Test(eStepInAvoidNoDebug))) { + if (!frame->HasDebugInformation()) { + LLDB_LOGF(log, "Stepping out of frame with no debug info"); + + should_stop_here = false; + } + } + + // Always avoid code with line number 0. + // FIXME: At present the ShouldStop and the StepFromHere calculate this + // independently. If this ever + // becomes expensive (this one isn't) we can try to have this set a state + // that the StepFromHere can use. + if (frame) { + SymbolContext sc; + sc = frame->GetSymbolContext(eSymbolContextLineEntry); + if (sc.line_entry.line == 0) + should_stop_here = false; + } + + return should_stop_here; +} + +ThreadPlanSP ThreadPlanShouldStopHere::DefaultStepFromHereCallback( + ThreadPlan *current_plan, Flags &flags, FrameComparison operation, + Status &status, void *baton) { + const bool stop_others = false; + const size_t frame_index = 0; + ThreadPlanSP return_plan_sp; + // If we are stepping through code at line number 0, then we need to step + // over this range. Otherwise we will step out. + Log *log = GetLog(LLDBLog::Step); + + StackFrame *frame = current_plan->GetThread().GetStackFrameAtIndex(0).get(); + if (!frame) + return return_plan_sp; + SymbolContext sc; + sc = frame->GetSymbolContext(eSymbolContextLineEntry | eSymbolContextSymbol); + + if (sc.line_entry.line == 0) { + AddressRange range = sc.line_entry.range; + + // If the whole function is marked line 0 just step out, that's easier & + // faster than continuing to step through it. + bool just_step_out = false; + if (sc.symbol && sc.symbol->ValueIsAddress()) { + Address symbol_end = sc.symbol->GetAddress(); + symbol_end.Slide(sc.symbol->GetByteSize() - 1); + if (range.ContainsFileAddress(sc.symbol->GetAddress()) && + range.ContainsFileAddress(symbol_end)) { + LLDB_LOGF(log, "Stopped in a function with only line 0 lines, just " + "stepping out."); + just_step_out = true; + } + } + if (!just_step_out) { + LLDB_LOGF(log, "ThreadPlanShouldStopHere::DefaultStepFromHereCallback " + "Queueing StepInRange plan to step through line 0 code."); + + return_plan_sp = current_plan->GetThread().QueueThreadPlanForStepInRange( + false, range, sc, nullptr, eOnlyDuringStepping, status, + eLazyBoolCalculate, eLazyBoolNo); + } + } + + if (!return_plan_sp) + return_plan_sp = + current_plan->GetThread().QueueThreadPlanForStepOutNoShouldStop( + false, nullptr, true, stop_others, eVoteNo, eVoteNoOpinion, + frame_index, status, true); + return return_plan_sp; +} + +ThreadPlanSP ThreadPlanShouldStopHere::QueueStepOutFromHerePlan( + lldb_private::Flags &flags, lldb::FrameComparison operation, + Status &status) { + ThreadPlanSP return_plan_sp; + if (m_callbacks.step_from_here_callback) { + return_plan_sp = m_callbacks.step_from_here_callback( + m_owner, flags, operation, status, m_baton); + } + return return_plan_sp; +} + +lldb::ThreadPlanSP ThreadPlanShouldStopHere::CheckShouldStopHereAndQueueStepOut( + lldb::FrameComparison operation, Status &status) { + if (!InvokeShouldStopHereCallback(operation, status)) + return QueueStepOutFromHerePlan(m_flags, operation, status); + else + return ThreadPlanSP(); +} diff --git a/contrib/llvm-project/lldb/source/Target/ThreadPlanStack.cpp b/contrib/llvm-project/lldb/source/Target/ThreadPlanStack.cpp new file mode 100644 index 000000000000..157293142907 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadPlanStack.cpp @@ -0,0 +1,521 @@ +//===-- ThreadPlanStack.cpp -------------------------------------*- C++ -*-===// +// +// 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 "lldb/Target/ThreadPlanStack.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadPlan.h" +#include "lldb/Utility/Log.h" + +using namespace lldb; +using namespace lldb_private; + +static void PrintPlanElement(Stream &s, const ThreadPlanSP &plan, + lldb::DescriptionLevel desc_level, + int32_t elem_idx) { + s.IndentMore(); + s.Indent(); + s.Printf("Element %d: ", elem_idx); + plan->GetDescription(&s, desc_level); + s.EOL(); + s.IndentLess(); +} + +ThreadPlanStack::ThreadPlanStack(const Thread &thread, bool make_null) { + if (make_null) { + // The ThreadPlanNull doesn't do anything to the Thread, so this is actually + // still a const operation. + m_plans.push_back( + ThreadPlanSP(new ThreadPlanNull(const_cast<Thread &>(thread)))); + } +} + +void ThreadPlanStack::DumpThreadPlans(Stream &s, + lldb::DescriptionLevel desc_level, + bool include_internal) const { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + s.IndentMore(); + PrintOneStack(s, "Active plan stack", m_plans, desc_level, include_internal); + PrintOneStack(s, "Completed plan stack", m_completed_plans, desc_level, + include_internal); + PrintOneStack(s, "Discarded plan stack", m_discarded_plans, desc_level, + include_internal); + s.IndentLess(); +} + +void ThreadPlanStack::PrintOneStack(Stream &s, llvm::StringRef stack_name, + const PlanStack &stack, + lldb::DescriptionLevel desc_level, + bool include_internal) const { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + // If the stack is empty, just exit: + if (stack.empty()) + return; + + // Make sure there are public completed plans: + bool any_public = false; + if (!include_internal) { + for (auto plan : stack) { + if (!plan->GetPrivate()) { + any_public = true; + break; + } + } + } + + if (include_internal || any_public) { + int print_idx = 0; + s.Indent(); + s << stack_name << ":\n"; + for (auto plan : stack) { + if (!include_internal && plan->GetPrivate()) + continue; + PrintPlanElement(s, plan, desc_level, print_idx++); + } + } +} + +size_t ThreadPlanStack::CheckpointCompletedPlans() { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + m_completed_plan_checkpoint++; + m_completed_plan_store.insert( + std::make_pair(m_completed_plan_checkpoint, m_completed_plans)); + return m_completed_plan_checkpoint; +} + +void ThreadPlanStack::RestoreCompletedPlanCheckpoint(size_t checkpoint) { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + auto result = m_completed_plan_store.find(checkpoint); + assert(result != m_completed_plan_store.end() && + "Asked for a checkpoint that didn't exist"); + m_completed_plans.swap((*result).second); + m_completed_plan_store.erase(result); +} + +void ThreadPlanStack::DiscardCompletedPlanCheckpoint(size_t checkpoint) { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + m_completed_plan_store.erase(checkpoint); +} + +void ThreadPlanStack::ThreadDestroyed(Thread *thread) { + // Tell the plan stacks that this thread is going away: + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + for (ThreadPlanSP plan : m_plans) + plan->ThreadDestroyed(); + + for (ThreadPlanSP plan : m_discarded_plans) + plan->ThreadDestroyed(); + + for (ThreadPlanSP plan : m_completed_plans) + plan->ThreadDestroyed(); + + // Now clear the current plan stacks: + m_plans.clear(); + m_discarded_plans.clear(); + m_completed_plans.clear(); + + // Push a ThreadPlanNull on the plan stack. That way we can continue + // assuming that the plan stack is never empty, but if somebody errantly asks + // questions of a destroyed thread without checking first whether it is + // destroyed, they won't crash. + if (thread != nullptr) { + lldb::ThreadPlanSP null_plan_sp(new ThreadPlanNull(*thread)); + m_plans.push_back(null_plan_sp); + } +} + +void ThreadPlanStack::PushPlan(lldb::ThreadPlanSP new_plan_sp) { + // If the thread plan doesn't already have a tracer, give it its parent's + // tracer: + // The first plan has to be a base plan: + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + assert((m_plans.size() > 0 || new_plan_sp->IsBasePlan()) && + "Zeroth plan must be a base plan"); + + if (!new_plan_sp->GetThreadPlanTracer()) { + assert(!m_plans.empty()); + new_plan_sp->SetThreadPlanTracer(m_plans.back()->GetThreadPlanTracer()); + } + m_plans.push_back(new_plan_sp); + new_plan_sp->DidPush(); +} + +lldb::ThreadPlanSP ThreadPlanStack::PopPlan() { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + assert(m_plans.size() > 1 && "Can't pop the base thread plan"); + + // Note that moving the top element of the vector would leave it in an + // undefined state, and break the guarantee that the stack's thread plans are + // all valid. + lldb::ThreadPlanSP plan_sp = m_plans.back(); + m_plans.pop_back(); + m_completed_plans.push_back(plan_sp); + plan_sp->DidPop(); + return plan_sp; +} + +lldb::ThreadPlanSP ThreadPlanStack::DiscardPlan() { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + assert(m_plans.size() > 1 && "Can't discard the base thread plan"); + + // Note that moving the top element of the vector would leave it in an + // undefined state, and break the guarantee that the stack's thread plans are + // all valid. + lldb::ThreadPlanSP plan_sp = m_plans.back(); + m_plans.pop_back(); + m_discarded_plans.push_back(plan_sp); + plan_sp->DidPop(); + return plan_sp; +} + +// If the input plan is nullptr, discard all plans. Otherwise make sure this +// plan is in the stack, and if so discard up to and including it. +void ThreadPlanStack::DiscardPlansUpToPlan(ThreadPlan *up_to_plan_ptr) { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + int stack_size = m_plans.size(); + + if (up_to_plan_ptr == nullptr) { + for (int i = stack_size - 1; i > 0; i--) + DiscardPlan(); + return; + } + + bool found_it = false; + for (int i = stack_size - 1; i > 0; i--) { + if (m_plans[i].get() == up_to_plan_ptr) { + found_it = true; + break; + } + } + + if (found_it) { + bool last_one = false; + for (int i = stack_size - 1; i > 0 && !last_one; i--) { + if (GetCurrentPlan().get() == up_to_plan_ptr) + last_one = true; + DiscardPlan(); + } + } +} + +void ThreadPlanStack::DiscardAllPlans() { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + int stack_size = m_plans.size(); + for (int i = stack_size - 1; i > 0; i--) { + DiscardPlan(); + } +} + +void ThreadPlanStack::DiscardConsultingControllingPlans() { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + while (true) { + int controlling_plan_idx; + bool discard = true; + + // Find the first controlling plan, see if it wants discarding, and if yes + // discard up to it. + for (controlling_plan_idx = m_plans.size() - 1; controlling_plan_idx >= 0; + controlling_plan_idx--) { + if (m_plans[controlling_plan_idx]->IsControllingPlan()) { + discard = m_plans[controlling_plan_idx]->OkayToDiscard(); + break; + } + } + + // If the controlling plan doesn't want to get discarded, then we're done. + if (!discard) + return; + + // First pop all the dependent plans: + for (int i = m_plans.size() - 1; i > controlling_plan_idx; i--) { + DiscardPlan(); + } + + // Now discard the controlling plan itself. + // The bottom-most plan never gets discarded. "OkayToDiscard" for it + // means discard it's dependent plans, but not it... + if (controlling_plan_idx > 0) { + DiscardPlan(); + } + } +} + +lldb::ThreadPlanSP ThreadPlanStack::GetCurrentPlan() const { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + assert(m_plans.size() != 0 && "There will always be a base plan."); + return m_plans.back(); +} + +lldb::ThreadPlanSP ThreadPlanStack::GetCompletedPlan(bool skip_private) const { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + if (m_completed_plans.empty()) + return {}; + + if (!skip_private) + return m_completed_plans.back(); + + for (int i = m_completed_plans.size() - 1; i >= 0; i--) { + lldb::ThreadPlanSP completed_plan_sp; + completed_plan_sp = m_completed_plans[i]; + if (!completed_plan_sp->GetPrivate()) + return completed_plan_sp; + } + return {}; +} + +lldb::ThreadPlanSP ThreadPlanStack::GetPlanByIndex(uint32_t plan_idx, + bool skip_private) const { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + uint32_t idx = 0; + + for (lldb::ThreadPlanSP plan_sp : m_plans) { + if (skip_private && plan_sp->GetPrivate()) + continue; + if (idx == plan_idx) + return plan_sp; + idx++; + } + return {}; +} + +lldb::ValueObjectSP ThreadPlanStack::GetReturnValueObject() const { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + if (m_completed_plans.empty()) + return {}; + + for (int i = m_completed_plans.size() - 1; i >= 0; i--) { + lldb::ValueObjectSP return_valobj_sp; + return_valobj_sp = m_completed_plans[i]->GetReturnValueObject(); + if (return_valobj_sp) + return return_valobj_sp; + } + return {}; +} + +lldb::ExpressionVariableSP ThreadPlanStack::GetExpressionVariable() const { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + if (m_completed_plans.empty()) + return {}; + + for (int i = m_completed_plans.size() - 1; i >= 0; i--) { + lldb::ExpressionVariableSP expression_variable_sp; + expression_variable_sp = m_completed_plans[i]->GetExpressionVariable(); + if (expression_variable_sp) + return expression_variable_sp; + } + return {}; +} +bool ThreadPlanStack::AnyPlans() const { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + // There is always a base plan... + return m_plans.size() > 1; +} + +bool ThreadPlanStack::AnyCompletedPlans() const { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + return !m_completed_plans.empty(); +} + +bool ThreadPlanStack::AnyDiscardedPlans() const { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + return !m_discarded_plans.empty(); +} + +bool ThreadPlanStack::IsPlanDone(ThreadPlan *in_plan) const { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + for (auto plan : m_completed_plans) { + if (plan.get() == in_plan) + return true; + } + return false; +} + +bool ThreadPlanStack::WasPlanDiscarded(ThreadPlan *in_plan) const { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + for (auto plan : m_discarded_plans) { + if (plan.get() == in_plan) + return true; + } + return false; +} + +ThreadPlan *ThreadPlanStack::GetPreviousPlan(ThreadPlan *current_plan) const { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + if (current_plan == nullptr) + return nullptr; + + // Look first in the completed plans, if the plan is here and there is + // a completed plan above it, return that. + int stack_size = m_completed_plans.size(); + for (int i = stack_size - 1; i > 0; i--) { + if (current_plan == m_completed_plans[i].get()) + return m_completed_plans[i - 1].get(); + } + + // If this is the first completed plan, the previous one is the + // bottom of the regular plan stack. + if (stack_size > 0 && m_completed_plans[0].get() == current_plan) { + return GetCurrentPlan().get(); + } + + // Otherwise look for it in the regular plans. + stack_size = m_plans.size(); + for (int i = stack_size - 1; i > 0; i--) { + if (current_plan == m_plans[i].get()) + return m_plans[i - 1].get(); + } + return nullptr; +} + +ThreadPlan *ThreadPlanStack::GetInnermostExpression() const { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + int stack_size = m_plans.size(); + + for (int i = stack_size - 1; i > 0; i--) { + if (m_plans[i]->GetKind() == ThreadPlan::eKindCallFunction) + return m_plans[i].get(); + } + return nullptr; +} + +void ThreadPlanStack::ClearThreadCache() { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + for (lldb::ThreadPlanSP thread_plan_sp : m_plans) + thread_plan_sp->ClearThreadCache(); +} + +void ThreadPlanStack::WillResume() { + std::lock_guard<std::recursive_mutex> guard(m_stack_mutex); + m_completed_plans.clear(); + m_discarded_plans.clear(); +} + +void ThreadPlanStackMap::Update(ThreadList ¤t_threads, + bool delete_missing, + bool check_for_new) { + + std::lock_guard<std::recursive_mutex> guard(m_stack_map_mutex); + // Now find all the new threads and add them to the map: + if (check_for_new) { + for (auto thread : current_threads.Threads()) { + lldb::tid_t cur_tid = thread->GetID(); + if (!Find(cur_tid)) { + AddThread(*thread); + thread->QueueBasePlan(true); + } + } + } + + // If we aren't reaping missing threads at this point, + // we are done. + if (!delete_missing) + return; + // Otherwise scan for absent TID's. + std::vector<lldb::tid_t> missing_threads; + // If we are going to delete plans from the plan stack, + // then scan for absent TID's: + for (auto &thread_plans : m_plans_list) { + lldb::tid_t cur_tid = thread_plans.first; + ThreadSP thread_sp = current_threads.FindThreadByID(cur_tid); + if (!thread_sp) + missing_threads.push_back(cur_tid); + } + for (lldb::tid_t tid : missing_threads) { + RemoveTID(tid); + } +} + +void ThreadPlanStackMap::DumpPlans(Stream &strm, + lldb::DescriptionLevel desc_level, + bool internal, bool condense_if_trivial, + bool skip_unreported) { + std::lock_guard<std::recursive_mutex> guard(m_stack_map_mutex); + for (auto &elem : m_plans_list) { + lldb::tid_t tid = elem.first; + uint32_t index_id = 0; + ThreadSP thread_sp = m_process.GetThreadList().FindThreadByID(tid); + + if (skip_unreported) { + if (!thread_sp) + continue; + } + if (thread_sp) + index_id = thread_sp->GetIndexID(); + + if (condense_if_trivial) { + if (!elem.second.AnyPlans() && !elem.second.AnyCompletedPlans() && + !elem.second.AnyDiscardedPlans()) { + strm.Printf("thread #%u: tid = 0x%4.4" PRIx64 "\n", index_id, tid); + strm.IndentMore(); + strm.Indent(); + strm.Printf("No active thread plans\n"); + strm.IndentLess(); + return; + } + } + + strm.Indent(); + strm.Printf("thread #%u: tid = 0x%4.4" PRIx64 ":\n", index_id, tid); + + elem.second.DumpThreadPlans(strm, desc_level, internal); + } +} + +bool ThreadPlanStackMap::DumpPlansForTID(Stream &strm, lldb::tid_t tid, + lldb::DescriptionLevel desc_level, + bool internal, + bool condense_if_trivial, + bool skip_unreported) { + std::lock_guard<std::recursive_mutex> guard(m_stack_map_mutex); + uint32_t index_id = 0; + ThreadSP thread_sp = m_process.GetThreadList().FindThreadByID(tid); + + if (skip_unreported) { + if (!thread_sp) { + strm.Format("Unknown TID: {0}", tid); + return false; + } + } + + if (thread_sp) + index_id = thread_sp->GetIndexID(); + ThreadPlanStack *stack = Find(tid); + if (!stack) { + strm.Format("Unknown TID: {0}\n", tid); + return false; + } + + if (condense_if_trivial) { + if (!stack->AnyPlans() && !stack->AnyCompletedPlans() && + !stack->AnyDiscardedPlans()) { + strm.Printf("thread #%u: tid = 0x%4.4" PRIx64 "\n", index_id, tid); + strm.IndentMore(); + strm.Indent(); + strm.Printf("No active thread plans\n"); + strm.IndentLess(); + return true; + } + } + + strm.Indent(); + strm.Printf("thread #%u: tid = 0x%4.4" PRIx64 ":\n", index_id, tid); + + stack->DumpThreadPlans(strm, desc_level, internal); + return true; +} + +bool ThreadPlanStackMap::PrunePlansForTID(lldb::tid_t tid) { + // We only remove the plans for unreported TID's. + std::lock_guard<std::recursive_mutex> guard(m_stack_map_mutex); + ThreadSP thread_sp = m_process.GetThreadList().FindThreadByID(tid); + if (thread_sp) + return false; + + return RemoveTID(tid); +} diff --git a/contrib/llvm-project/lldb/source/Target/ThreadPlanStepInRange.cpp b/contrib/llvm-project/lldb/source/Target/ThreadPlanStepInRange.cpp new file mode 100644 index 000000000000..17f2100b804f --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadPlanStepInRange.cpp @@ -0,0 +1,485 @@ +//===-- ThreadPlanStepInRange.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 "lldb/Target/ThreadPlanStepInRange.h" +#include "lldb/Core/Architecture.h" +#include "lldb/Core/Module.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadPlanStepOut.h" +#include "lldb/Target/ThreadPlanStepThrough.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/RegularExpression.h" +#include "lldb/Utility/Stream.h" + +using namespace lldb; +using namespace lldb_private; + +uint32_t ThreadPlanStepInRange::s_default_flag_values = + ThreadPlanShouldStopHere::eStepInAvoidNoDebug; + +// ThreadPlanStepInRange: Step through a stack range, either stepping over or +// into based on the value of \a type. + +ThreadPlanStepInRange::ThreadPlanStepInRange( + Thread &thread, const AddressRange &range, + const SymbolContext &addr_context, const char *step_into_target, + lldb::RunMode stop_others, LazyBool step_in_avoids_code_without_debug_info, + LazyBool step_out_avoids_code_without_debug_info) + : ThreadPlanStepRange(ThreadPlan::eKindStepInRange, + "Step Range stepping in", thread, range, addr_context, + stop_others), + ThreadPlanShouldStopHere(this), m_step_past_prologue(true), + m_virtual_step(false), m_step_into_target(step_into_target) { + SetCallbacks(); + SetFlagsToDefault(); + SetupAvoidNoDebug(step_in_avoids_code_without_debug_info, + step_out_avoids_code_without_debug_info); +} + +ThreadPlanStepInRange::~ThreadPlanStepInRange() = default; + +void ThreadPlanStepInRange::SetupAvoidNoDebug( + LazyBool step_in_avoids_code_without_debug_info, + LazyBool step_out_avoids_code_without_debug_info) { + bool avoid_nodebug = true; + Thread &thread = GetThread(); + switch (step_in_avoids_code_without_debug_info) { + case eLazyBoolYes: + avoid_nodebug = true; + break; + case eLazyBoolNo: + avoid_nodebug = false; + break; + case eLazyBoolCalculate: + avoid_nodebug = thread.GetStepInAvoidsNoDebug(); + break; + } + if (avoid_nodebug) + GetFlags().Set(ThreadPlanShouldStopHere::eStepInAvoidNoDebug); + else + GetFlags().Clear(ThreadPlanShouldStopHere::eStepInAvoidNoDebug); + + switch (step_out_avoids_code_without_debug_info) { + case eLazyBoolYes: + avoid_nodebug = true; + break; + case eLazyBoolNo: + avoid_nodebug = false; + break; + case eLazyBoolCalculate: + avoid_nodebug = thread.GetStepOutAvoidsNoDebug(); + break; + } + if (avoid_nodebug) + GetFlags().Set(ThreadPlanShouldStopHere::eStepOutAvoidNoDebug); + else + GetFlags().Clear(ThreadPlanShouldStopHere::eStepOutAvoidNoDebug); +} + +void ThreadPlanStepInRange::GetDescription(Stream *s, + lldb::DescriptionLevel level) { + + auto PrintFailureIfAny = [&]() { + if (m_status.Success()) + return; + s->Printf(" failed (%s)", m_status.AsCString()); + }; + + if (level == lldb::eDescriptionLevelBrief) { + s->Printf("step in"); + PrintFailureIfAny(); + return; + } + + s->Printf("Stepping in"); + bool printed_line_info = false; + if (m_addr_context.line_entry.IsValid()) { + s->Printf(" through line "); + m_addr_context.line_entry.DumpStopContext(s, false); + printed_line_info = true; + } + + const char *step_into_target = m_step_into_target.AsCString(); + if (step_into_target && step_into_target[0] != '\0') + s->Printf(" targeting %s", m_step_into_target.AsCString()); + + if (!printed_line_info || level == eDescriptionLevelVerbose) { + s->Printf(" using ranges:"); + DumpRanges(s); + } + + PrintFailureIfAny(); + + s->PutChar('.'); +} + +bool ThreadPlanStepInRange::ShouldStop(Event *event_ptr) { + Log *log = GetLog(LLDBLog::Step); + + if (log) { + StreamString s; + DumpAddress(s.AsRawOstream(), GetThread().GetRegisterContext()->GetPC(), + GetTarget().GetArchitecture().GetAddressByteSize()); + LLDB_LOGF(log, "ThreadPlanStepInRange reached %s.", s.GetData()); + } + + if (IsPlanComplete()) + return true; + + m_no_more_plans = false; + if (m_sub_plan_sp && m_sub_plan_sp->IsPlanComplete()) { + if (!m_sub_plan_sp->PlanSucceeded()) { + SetPlanComplete(); + m_no_more_plans = true; + return true; + } else + m_sub_plan_sp.reset(); + } + + if (m_virtual_step) { + // If we've just completed a virtual step, all we need to do is check for a + // ShouldStopHere plan, and otherwise we're done. + // FIXME - This can be both a step in and a step out. Probably should + // record which in the m_virtual_step. + m_sub_plan_sp = + CheckShouldStopHereAndQueueStepOut(eFrameCompareYounger, m_status); + } else { + // Stepping through should be done running other threads in general, since + // we're setting a breakpoint and continuing. So only stop others if we + // are explicitly told to do so. + + bool stop_others = (m_stop_others == lldb::eOnlyThisThread); + + FrameComparison frame_order = CompareCurrentFrameToStartFrame(); + + Thread &thread = GetThread(); + if (frame_order == eFrameCompareOlder || + frame_order == eFrameCompareSameParent) { + // If we're in an older frame then we should stop. + // + // A caveat to this is if we think the frame is older but we're actually + // in a trampoline. + // I'm going to make the assumption that you wouldn't RETURN to a + // trampoline. So if we are in a trampoline we think the frame is older + // because the trampoline confused the backtracer. + m_sub_plan_sp = thread.QueueThreadPlanForStepThrough( + m_stack_id, false, stop_others, m_status); + if (!m_sub_plan_sp) { + // Otherwise check the ShouldStopHere for step out: + m_sub_plan_sp = + CheckShouldStopHereAndQueueStepOut(frame_order, m_status); + if (log) { + if (m_sub_plan_sp) + LLDB_LOGF(log, + "ShouldStopHere found plan to step out of this frame."); + else + LLDB_LOGF(log, "ShouldStopHere no plan to step out of this frame."); + } + } else if (log) { + LLDB_LOGF( + log, "Thought I stepped out, but in fact arrived at a trampoline."); + } + } else if (frame_order == eFrameCompareEqual && InSymbol()) { + // If we are not in a place we should step through, we're done. One + // tricky bit here is that some stubs don't push a frame, so we have to + // check both the case of a frame that is younger, or the same as this + // frame. However, if the frame is the same, and we are still in the + // symbol we started in, the we don't need to do this. This first check + // isn't strictly necessary, but it is more efficient. + + // If we're still in the range, keep going, either by running to the next + // branch breakpoint, or by stepping. + if (InRange()) { + SetNextBranchBreakpoint(); + return false; + } + + SetPlanComplete(); + m_no_more_plans = true; + return true; + } + + // If we get to this point, we're not going to use a previously set "next + // branch" breakpoint, so delete it: + ClearNextBranchBreakpoint(); + + // We may have set the plan up above in the FrameIsOlder section: + + if (!m_sub_plan_sp) + m_sub_plan_sp = thread.QueueThreadPlanForStepThrough( + m_stack_id, false, stop_others, m_status); + + if (log) { + if (m_sub_plan_sp) + LLDB_LOGF(log, "Found a step through plan: %s", + m_sub_plan_sp->GetName()); + else + LLDB_LOGF(log, "No step through plan found."); + } + + // If not, give the "should_stop" callback a chance to push a plan to get + // us out of here. But only do that if we actually have stepped in. + if (!m_sub_plan_sp && frame_order == eFrameCompareYounger) + m_sub_plan_sp = CheckShouldStopHereAndQueueStepOut(frame_order, m_status); + + // If we've stepped in and we are going to stop here, check to see if we + // were asked to run past the prologue, and if so do that. + + if (!m_sub_plan_sp && frame_order == eFrameCompareYounger && + m_step_past_prologue) { + lldb::StackFrameSP curr_frame = thread.GetStackFrameAtIndex(0); + if (curr_frame) { + size_t bytes_to_skip = 0; + lldb::addr_t curr_addr = thread.GetRegisterContext()->GetPC(); + Address func_start_address; + + SymbolContext sc = curr_frame->GetSymbolContext(eSymbolContextFunction | + eSymbolContextSymbol); + + if (sc.function) { + func_start_address = sc.function->GetAddressRange().GetBaseAddress(); + if (curr_addr == func_start_address.GetLoadAddress(&GetTarget())) + bytes_to_skip = sc.function->GetPrologueByteSize(); + } else if (sc.symbol) { + func_start_address = sc.symbol->GetAddress(); + if (curr_addr == func_start_address.GetLoadAddress(&GetTarget())) + bytes_to_skip = sc.symbol->GetPrologueByteSize(); + } + + if (bytes_to_skip == 0 && sc.symbol) { + const Architecture *arch = GetTarget().GetArchitecturePlugin(); + if (arch) { + Address curr_sec_addr; + GetTarget().GetSectionLoadList().ResolveLoadAddress(curr_addr, + curr_sec_addr); + bytes_to_skip = arch->GetBytesToSkip(*sc.symbol, curr_sec_addr); + } + } + + if (bytes_to_skip != 0) { + func_start_address.Slide(bytes_to_skip); + log = GetLog(LLDBLog::Step); + LLDB_LOGF(log, "Pushing past prologue "); + + m_sub_plan_sp = thread.QueueThreadPlanForRunToAddress( + false, func_start_address, true, m_status); + } + } + } + } + + if (!m_sub_plan_sp) { + m_no_more_plans = true; + SetPlanComplete(); + return true; + } else { + m_no_more_plans = false; + m_sub_plan_sp->SetPrivate(true); + return false; + } +} + +void ThreadPlanStepInRange::SetAvoidRegexp(const char *name) { + if (m_avoid_regexp_up) + *m_avoid_regexp_up = RegularExpression(name); + else + m_avoid_regexp_up = std::make_unique<RegularExpression>(name); +} + +void ThreadPlanStepInRange::SetDefaultFlagValue(uint32_t new_value) { + // TODO: Should we test this for sanity? + ThreadPlanStepInRange::s_default_flag_values = new_value; +} + +bool ThreadPlanStepInRange::FrameMatchesAvoidCriteria() { + StackFrame *frame = GetThread().GetStackFrameAtIndex(0).get(); + + // Check the library list first, as that's cheapest: + bool libraries_say_avoid = false; + + FileSpecList libraries_to_avoid(GetThread().GetLibrariesToAvoid()); + size_t num_libraries = libraries_to_avoid.GetSize(); + if (num_libraries > 0) { + SymbolContext sc(frame->GetSymbolContext(eSymbolContextModule)); + FileSpec frame_library(sc.module_sp->GetFileSpec()); + + if (frame_library) { + for (size_t i = 0; i < num_libraries; i++) { + const FileSpec &file_spec(libraries_to_avoid.GetFileSpecAtIndex(i)); + if (FileSpec::Match(file_spec, frame_library)) { + libraries_say_avoid = true; + break; + } + } + } + } + if (libraries_say_avoid) + return true; + + const RegularExpression *avoid_regexp_to_use = m_avoid_regexp_up.get(); + if (avoid_regexp_to_use == nullptr) + avoid_regexp_to_use = GetThread().GetSymbolsToAvoidRegexp(); + + if (avoid_regexp_to_use != nullptr) { + SymbolContext sc = frame->GetSymbolContext( + eSymbolContextFunction | eSymbolContextBlock | eSymbolContextSymbol); + if (sc.symbol != nullptr) { + const char *frame_function_name = + sc.GetFunctionName(Mangled::ePreferDemangledWithoutArguments) + .GetCString(); + if (frame_function_name) { + bool return_value = avoid_regexp_to_use->Execute(frame_function_name); + if (return_value) { + LLDB_LOGF(GetLog(LLDBLog::Step), + "Stepping out of function \"%s\" because it matches the " + "avoid regexp \"%s\".", + frame_function_name, + avoid_regexp_to_use->GetText().str().c_str()); + } + return return_value; + } + } + } + return false; +} + +bool ThreadPlanStepInRange::DefaultShouldStopHereCallback( + ThreadPlan *current_plan, Flags &flags, FrameComparison operation, + Status &status, void *baton) { + bool should_stop_here = true; + StackFrame *frame = current_plan->GetThread().GetStackFrameAtIndex(0).get(); + Log *log = GetLog(LLDBLog::Step); + + // First see if the ThreadPlanShouldStopHere default implementation thinks we + // should get out of here: + should_stop_here = ThreadPlanShouldStopHere::DefaultShouldStopHereCallback( + current_plan, flags, operation, status, baton); + if (!should_stop_here) + return false; + + if (should_stop_here && current_plan->GetKind() == eKindStepInRange && + operation == eFrameCompareYounger) { + ThreadPlanStepInRange *step_in_range_plan = + static_cast<ThreadPlanStepInRange *>(current_plan); + if (step_in_range_plan->m_step_into_target) { + SymbolContext sc = frame->GetSymbolContext( + eSymbolContextFunction | eSymbolContextBlock | eSymbolContextSymbol); + if (sc.symbol != nullptr) { + // First try an exact match, since that's cheap with ConstStrings. + // Then do a strstr compare. + if (step_in_range_plan->m_step_into_target == sc.GetFunctionName()) { + should_stop_here = true; + } else { + const char *target_name = + step_in_range_plan->m_step_into_target.AsCString(); + const char *function_name = sc.GetFunctionName().AsCString(); + + if (function_name == nullptr) + should_stop_here = false; + else if (strstr(function_name, target_name) == nullptr) + should_stop_here = false; + } + if (log && !should_stop_here) + LLDB_LOGF(log, + "Stepping out of frame %s which did not match step into " + "target %s.", + sc.GetFunctionName().AsCString(), + step_in_range_plan->m_step_into_target.AsCString()); + } + } + + if (should_stop_here) { + ThreadPlanStepInRange *step_in_range_plan = + static_cast<ThreadPlanStepInRange *>(current_plan); + // Don't log the should_step_out here, it's easier to do it in + // FrameMatchesAvoidCriteria. + should_stop_here = !step_in_range_plan->FrameMatchesAvoidCriteria(); + } + } + + return should_stop_here; +} + +bool ThreadPlanStepInRange::DoPlanExplainsStop(Event *event_ptr) { + // We always explain a stop. Either we've just done a single step, in which + // case we'll do our ordinary processing, or we stopped for some reason that + // isn't handled by our sub-plans, in which case we want to just stop right + // away. In general, we don't want to mark the plan as complete for + // unexplained stops. For instance, if you step in to some code with no debug + // info, so you step out and in the course of that hit a breakpoint, then you + // want to stop & show the user the breakpoint, but not unship the step in + // plan, since you still may want to complete that plan when you continue. + // This is particularly true when doing "step in to target function." + // stepping. + // + // The only variation is that if we are doing "step by running to next + // branch" in which case if we hit our branch breakpoint we don't set the + // plan to complete. + + bool return_value = false; + + if (m_virtual_step) { + return_value = true; + } else { + StopInfoSP stop_info_sp = GetPrivateStopInfo(); + if (stop_info_sp) { + StopReason reason = stop_info_sp->GetStopReason(); + + if (reason == eStopReasonBreakpoint) { + if (NextRangeBreakpointExplainsStop(stop_info_sp)) { + return_value = true; + } + } else if (IsUsuallyUnexplainedStopReason(reason)) { + Log *log = GetLog(LLDBLog::Step); + if (log) + log->PutCString("ThreadPlanStepInRange got asked if it explains the " + "stop for some reason other than step."); + return_value = false; + } else { + return_value = true; + } + } else + return_value = true; + } + + return return_value; +} + +bool ThreadPlanStepInRange::DoWillResume(lldb::StateType resume_state, + bool current_plan) { + m_virtual_step = false; + if (resume_state == eStateStepping && current_plan) { + Thread &thread = GetThread(); + // See if we are about to step over a virtual inlined call. + bool step_without_resume = thread.DecrementCurrentInlinedDepth(); + if (step_without_resume) { + Log *log = GetLog(LLDBLog::Step); + LLDB_LOGF(log, + "ThreadPlanStepInRange::DoWillResume: returning false, " + "inline_depth: %d", + thread.GetCurrentInlinedDepth()); + SetStopInfo(StopInfo::CreateStopReasonToTrace(thread)); + + // FIXME: Maybe it would be better to create a InlineStep stop reason, but + // then + // the whole rest of the world would have to handle that stop reason. + m_virtual_step = true; + } + return !step_without_resume; + } + return true; +} + +bool ThreadPlanStepInRange::IsVirtualStep() { return m_virtual_step; } diff --git a/contrib/llvm-project/lldb/source/Target/ThreadPlanStepInstruction.cpp b/contrib/llvm-project/lldb/source/Target/ThreadPlanStepInstruction.cpp new file mode 100644 index 000000000000..42bee79c42bb --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadPlanStepInstruction.cpp @@ -0,0 +1,254 @@ +//===-- ThreadPlanStepInstruction.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 "lldb/Target/ThreadPlanStepInstruction.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/StopInfo.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Stream.h" + +using namespace lldb; +using namespace lldb_private; + +// ThreadPlanStepInstruction: Step over the current instruction + +ThreadPlanStepInstruction::ThreadPlanStepInstruction(Thread &thread, + bool step_over, + bool stop_other_threads, + Vote report_stop_vote, + Vote report_run_vote) + : ThreadPlan(ThreadPlan::eKindStepInstruction, + "Step over single instruction", thread, report_stop_vote, + report_run_vote), + m_instruction_addr(0), m_stop_other_threads(stop_other_threads), + m_step_over(step_over) { + m_takes_iteration_count = true; + SetUpState(); +} + +ThreadPlanStepInstruction::~ThreadPlanStepInstruction() = default; + +void ThreadPlanStepInstruction::SetUpState() { + Thread &thread = GetThread(); + m_instruction_addr = thread.GetRegisterContext()->GetPC(0); + StackFrameSP start_frame_sp(thread.GetStackFrameAtIndex(0)); + m_stack_id = start_frame_sp->GetStackID(); + + m_start_has_symbol = + start_frame_sp->GetSymbolContext(eSymbolContextSymbol).symbol != nullptr; + + StackFrameSP parent_frame_sp = thread.GetStackFrameAtIndex(1); + if (parent_frame_sp) + m_parent_frame_id = parent_frame_sp->GetStackID(); +} + +void ThreadPlanStepInstruction::GetDescription(Stream *s, + lldb::DescriptionLevel level) { + auto PrintFailureIfAny = [&]() { + if (m_status.Success()) + return; + s->Printf(" failed (%s)", m_status.AsCString()); + }; + + if (level == lldb::eDescriptionLevelBrief) { + if (m_step_over) + s->Printf("instruction step over"); + else + s->Printf("instruction step into"); + + PrintFailureIfAny(); + } else { + s->Printf("Stepping one instruction past "); + DumpAddress(s->AsRawOstream(), m_instruction_addr, sizeof(addr_t)); + if (!m_start_has_symbol) + s->Printf(" which has no symbol"); + + if (m_step_over) + s->Printf(" stepping over calls"); + else + s->Printf(" stepping into calls"); + + PrintFailureIfAny(); + } +} + +bool ThreadPlanStepInstruction::ValidatePlan(Stream *error) { + // Since we read the instruction we're stepping over from the thread, this + // plan will always work. + return true; +} + +bool ThreadPlanStepInstruction::DoPlanExplainsStop(Event *event_ptr) { + StopInfoSP stop_info_sp = GetPrivateStopInfo(); + if (stop_info_sp) { + StopReason reason = stop_info_sp->GetStopReason(); + return (reason == eStopReasonTrace || reason == eStopReasonNone); + } + return false; +} + +bool ThreadPlanStepInstruction::IsPlanStale() { + Log *log = GetLog(LLDBLog::Step); + Thread &thread = GetThread(); + StackID cur_frame_id = thread.GetStackFrameAtIndex(0)->GetStackID(); + if (cur_frame_id == m_stack_id) { + // Set plan Complete when we reach next instruction + uint64_t pc = thread.GetRegisterContext()->GetPC(0); + uint32_t max_opcode_size = + GetTarget().GetArchitecture().GetMaximumOpcodeByteSize(); + bool next_instruction_reached = (pc > m_instruction_addr) && + (pc <= m_instruction_addr + max_opcode_size); + if (next_instruction_reached) { + SetPlanComplete(); + } + return (thread.GetRegisterContext()->GetPC(0) != m_instruction_addr); + } else if (cur_frame_id < m_stack_id) { + // If the current frame is younger than the start frame and we are stepping + // over, then we need to continue, but if we are doing just one step, we're + // done. + return !m_step_over; + } else { + if (log) { + LLDB_LOGF(log, + "ThreadPlanStepInstruction::IsPlanStale - Current frame is " + "older than start frame, plan is stale."); + } + return true; + } +} + +bool ThreadPlanStepInstruction::ShouldStop(Event *event_ptr) { + Thread &thread = GetThread(); + if (m_step_over) { + Log *log = GetLog(LLDBLog::Step); + StackFrameSP cur_frame_sp = thread.GetStackFrameAtIndex(0); + if (!cur_frame_sp) { + LLDB_LOGF( + log, + "ThreadPlanStepInstruction couldn't get the 0th frame, stopping."); + SetPlanComplete(); + return true; + } + + StackID cur_frame_zero_id = cur_frame_sp->GetStackID(); + + if (cur_frame_zero_id == m_stack_id || m_stack_id < cur_frame_zero_id) { + if (thread.GetRegisterContext()->GetPC(0) != m_instruction_addr) { + if (--m_iteration_count <= 0) { + SetPlanComplete(); + return true; + } else { + // We are still stepping, reset the start pc, and in case we've + // stepped out, reset the current stack id. + SetUpState(); + return false; + } + } else + return false; + } else { + // We've stepped in, step back out again: + StackFrame *return_frame = thread.GetStackFrameAtIndex(1).get(); + if (return_frame) { + if (return_frame->GetStackID() != m_parent_frame_id || + m_start_has_symbol) { + // next-instruction shouldn't step out of inlined functions. But we + // may have stepped into a real function that starts with an inlined + // function, and we do want to step out of that... + + if (cur_frame_sp->IsInlined()) { + StackFrameSP parent_frame_sp = + thread.GetFrameWithStackID(m_stack_id); + + if (parent_frame_sp && + parent_frame_sp->GetConcreteFrameIndex() == + cur_frame_sp->GetConcreteFrameIndex()) { + SetPlanComplete(); + if (log) { + LLDB_LOGF(log, + "Frame we stepped into is inlined into the frame " + "we were stepping from, stopping."); + } + return true; + } + } + + if (log) { + StreamString s; + s.PutCString("Stepped in to: "); + addr_t stop_addr = + thread.GetStackFrameAtIndex(0)->GetRegisterContext()->GetPC(); + DumpAddress(s.AsRawOstream(), stop_addr, + GetTarget().GetArchitecture().GetAddressByteSize()); + s.PutCString(" stepping out to: "); + addr_t return_addr = return_frame->GetRegisterContext()->GetPC(); + DumpAddress(s.AsRawOstream(), return_addr, + GetTarget().GetArchitecture().GetAddressByteSize()); + LLDB_LOGF(log, "%s.", s.GetData()); + } + + // StepInstruction should probably have the tri-state RunMode, but + // for now it is safer to run others. + const bool stop_others = false; + thread.QueueThreadPlanForStepOutNoShouldStop( + false, nullptr, true, stop_others, eVoteNo, eVoteNoOpinion, 0, + m_status); + return false; + } else { + if (log) { + log->PutCString( + "The stack id we are stepping in changed, but our parent frame " + "did not when stepping from code with no symbols. " + "We are probably just confused about where we are, stopping."); + } + SetPlanComplete(); + return true; + } + } else { + LLDB_LOGF(log, "Could not find previous frame, stopping."); + SetPlanComplete(); + return true; + } + } + } else { + lldb::addr_t pc_addr = thread.GetRegisterContext()->GetPC(0); + if (pc_addr != m_instruction_addr) { + if (--m_iteration_count <= 0) { + SetPlanComplete(); + return true; + } else { + // We are still stepping, reset the start pc, and in case we've stepped + // in or out, reset the current stack id. + SetUpState(); + return false; + } + } else + return false; + } +} + +bool ThreadPlanStepInstruction::StopOthers() { return m_stop_other_threads; } + +StateType ThreadPlanStepInstruction::GetPlanRunState() { + return eStateStepping; +} + +bool ThreadPlanStepInstruction::WillStop() { return true; } + +bool ThreadPlanStepInstruction::MischiefManaged() { + if (IsPlanComplete()) { + Log *log = GetLog(LLDBLog::Step); + LLDB_LOGF(log, "Completed single instruction step plan."); + ThreadPlan::MischiefManaged(); + return true; + } else { + return false; + } +} diff --git a/contrib/llvm-project/lldb/source/Target/ThreadPlanStepOut.cpp b/contrib/llvm-project/lldb/source/Target/ThreadPlanStepOut.cpp new file mode 100644 index 000000000000..0a1e2ae605ef --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadPlanStepOut.cpp @@ -0,0 +1,529 @@ +//===-- ThreadPlanStepOut.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 "lldb/Target/ThreadPlanStepOut.h" +#include "lldb/Breakpoint/Breakpoint.h" +#include "lldb/Core/Value.h" +#include "lldb/Core/ValueObjectConstResult.h" +#include "lldb/Symbol/Block.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Symbol/Type.h" +#include "lldb/Target/ABI.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/StopInfo.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/ThreadPlanStepOverRange.h" +#include "lldb/Target/ThreadPlanStepThrough.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" + +#include <memory> + +using namespace lldb; +using namespace lldb_private; + +uint32_t ThreadPlanStepOut::s_default_flag_values = 0; + +// ThreadPlanStepOut: Step out of the current frame +ThreadPlanStepOut::ThreadPlanStepOut( + Thread &thread, SymbolContext *context, bool first_insn, bool stop_others, + Vote report_stop_vote, Vote report_run_vote, uint32_t frame_idx, + LazyBool step_out_avoids_code_without_debug_info, + bool continue_to_next_branch, bool gather_return_value) + : ThreadPlan(ThreadPlan::eKindStepOut, "Step out", thread, report_stop_vote, + report_run_vote), + ThreadPlanShouldStopHere(this), m_step_from_insn(LLDB_INVALID_ADDRESS), + m_return_bp_id(LLDB_INVALID_BREAK_ID), + m_return_addr(LLDB_INVALID_ADDRESS), m_stop_others(stop_others), + m_immediate_step_from_function(nullptr), + m_calculate_return_value(gather_return_value) { + Log *log = GetLog(LLDBLog::Step); + SetFlagsToDefault(); + SetupAvoidNoDebug(step_out_avoids_code_without_debug_info); + + m_step_from_insn = thread.GetRegisterContext()->GetPC(0); + + uint32_t return_frame_index = frame_idx + 1; + StackFrameSP return_frame_sp(thread.GetStackFrameAtIndex(return_frame_index)); + StackFrameSP immediate_return_from_sp(thread.GetStackFrameAtIndex(frame_idx)); + + if (!return_frame_sp || !immediate_return_from_sp) + return; // we can't do anything here. ValidatePlan() will return false. + + // While stepping out, behave as-if artificial frames are not present. + while (return_frame_sp->IsArtificial()) { + m_stepped_past_frames.push_back(return_frame_sp); + + ++return_frame_index; + return_frame_sp = thread.GetStackFrameAtIndex(return_frame_index); + + // We never expect to see an artificial frame without a regular ancestor. + // If this happens, log the issue and defensively refuse to step out. + if (!return_frame_sp) { + LLDB_LOG(log, "Can't step out of frame with artificial ancestors"); + return; + } + } + + m_step_out_to_id = return_frame_sp->GetStackID(); + m_immediate_step_from_id = immediate_return_from_sp->GetStackID(); + + // If the frame directly below the one we are returning to is inlined, we + // have to be a little more careful. It is non-trivial to determine the real + // "return code address" for an inlined frame, so we have to work our way to + // that frame and then step out. + if (immediate_return_from_sp->IsInlined()) { + if (frame_idx > 0) { + // First queue a plan that gets us to this inlined frame, and when we get + // there we'll queue a second plan that walks us out of this frame. + m_step_out_to_inline_plan_sp = std::make_shared<ThreadPlanStepOut>( + thread, nullptr, false, stop_others, eVoteNoOpinion, eVoteNoOpinion, + frame_idx - 1, eLazyBoolNo, continue_to_next_branch); + static_cast<ThreadPlanStepOut *>(m_step_out_to_inline_plan_sp.get()) + ->SetShouldStopHereCallbacks(nullptr, nullptr); + m_step_out_to_inline_plan_sp->SetPrivate(true); + } else { + // If we're already at the inlined frame we're stepping through, then + // just do that now. + QueueInlinedStepPlan(false); + } + } else { + // Find the return address and set a breakpoint there: + // FIXME - can we do this more securely if we know first_insn? + + Address return_address(return_frame_sp->GetFrameCodeAddress()); + if (continue_to_next_branch) { + SymbolContext return_address_sc; + AddressRange range; + Address return_address_decr_pc = return_address; + if (return_address_decr_pc.GetOffset() > 0) + return_address_decr_pc.Slide(-1); + + return_address_decr_pc.CalculateSymbolContext( + &return_address_sc, lldb::eSymbolContextLineEntry); + if (return_address_sc.line_entry.IsValid()) { + const bool include_inlined_functions = false; + range = return_address_sc.line_entry.GetSameLineContiguousAddressRange( + include_inlined_functions); + if (range.GetByteSize() > 0) { + return_address = m_process.AdvanceAddressToNextBranchInstruction( + return_address, range); + } + } + } + m_return_addr = return_address.GetLoadAddress(&m_process.GetTarget()); + + if (m_return_addr == LLDB_INVALID_ADDRESS) + return; + + // Perform some additional validation on the return address. + uint32_t permissions = 0; + if (!m_process.GetLoadAddressPermissions(m_return_addr, permissions)) { + LLDB_LOGF(log, "ThreadPlanStepOut(%p): Return address (0x%" PRIx64 + ") permissions not found.", static_cast<void *>(this), + m_return_addr); + } else if (!(permissions & ePermissionsExecutable)) { + m_constructor_errors.Printf("Return address (0x%" PRIx64 + ") did not point to executable memory.", + m_return_addr); + LLDB_LOGF(log, "ThreadPlanStepOut(%p): %s", static_cast<void *>(this), + m_constructor_errors.GetData()); + return; + } + + Breakpoint *return_bp = + GetTarget().CreateBreakpoint(m_return_addr, true, false).get(); + + if (return_bp != nullptr) { + if (return_bp->IsHardware() && !return_bp->HasResolvedLocations()) + m_could_not_resolve_hw_bp = true; + return_bp->SetThreadID(m_tid); + m_return_bp_id = return_bp->GetID(); + return_bp->SetBreakpointKind("step-out"); + } + + if (immediate_return_from_sp) { + const SymbolContext &sc = + immediate_return_from_sp->GetSymbolContext(eSymbolContextFunction); + if (sc.function) { + m_immediate_step_from_function = sc.function; + } + } + } +} + +void ThreadPlanStepOut::SetupAvoidNoDebug( + LazyBool step_out_avoids_code_without_debug_info) { + bool avoid_nodebug = true; + switch (step_out_avoids_code_without_debug_info) { + case eLazyBoolYes: + avoid_nodebug = true; + break; + case eLazyBoolNo: + avoid_nodebug = false; + break; + case eLazyBoolCalculate: + avoid_nodebug = GetThread().GetStepOutAvoidsNoDebug(); + break; + } + if (avoid_nodebug) + GetFlags().Set(ThreadPlanShouldStopHere::eStepOutAvoidNoDebug); + else + GetFlags().Clear(ThreadPlanShouldStopHere::eStepOutAvoidNoDebug); +} + +void ThreadPlanStepOut::DidPush() { + Thread &thread = GetThread(); + if (m_step_out_to_inline_plan_sp) + thread.QueueThreadPlan(m_step_out_to_inline_plan_sp, false); + else if (m_step_through_inline_plan_sp) + thread.QueueThreadPlan(m_step_through_inline_plan_sp, false); +} + +ThreadPlanStepOut::~ThreadPlanStepOut() { + if (m_return_bp_id != LLDB_INVALID_BREAK_ID) + GetTarget().RemoveBreakpointByID(m_return_bp_id); +} + +void ThreadPlanStepOut::GetDescription(Stream *s, + lldb::DescriptionLevel level) { + if (level == lldb::eDescriptionLevelBrief) + s->Printf("step out"); + else { + if (m_step_out_to_inline_plan_sp) + s->Printf("Stepping out to inlined frame so we can walk through it."); + else if (m_step_through_inline_plan_sp) + s->Printf("Stepping out by stepping through inlined function."); + else { + s->Printf("Stepping out from "); + Address tmp_address; + if (tmp_address.SetLoadAddress(m_step_from_insn, &GetTarget())) { + tmp_address.Dump(s, &m_process, Address::DumpStyleResolvedDescription, + Address::DumpStyleLoadAddress); + } else { + s->Printf("address 0x%" PRIx64 "", (uint64_t)m_step_from_insn); + } + + // FIXME: find some useful way to present the m_return_id, since there may + // be multiple copies of the + // same function on the stack. + + s->Printf(" returning to frame at "); + if (tmp_address.SetLoadAddress(m_return_addr, &GetTarget())) { + tmp_address.Dump(s, &m_process, Address::DumpStyleResolvedDescription, + Address::DumpStyleLoadAddress); + } else { + s->Printf("address 0x%" PRIx64 "", (uint64_t)m_return_addr); + } + + if (level == eDescriptionLevelVerbose) + s->Printf(" using breakpoint site %d", m_return_bp_id); + } + } + + if (m_stepped_past_frames.empty()) + return; + + s->Printf("\n"); + for (StackFrameSP frame_sp : m_stepped_past_frames) { + s->Printf("Stepped out past: "); + frame_sp->DumpUsingSettingsFormat(s); + } +} + +bool ThreadPlanStepOut::ValidatePlan(Stream *error) { + if (m_step_out_to_inline_plan_sp) + return m_step_out_to_inline_plan_sp->ValidatePlan(error); + + if (m_step_through_inline_plan_sp) + return m_step_through_inline_plan_sp->ValidatePlan(error); + + if (m_could_not_resolve_hw_bp) { + if (error) + error->PutCString( + "Could not create hardware breakpoint for thread plan."); + return false; + } + + if (m_return_bp_id == LLDB_INVALID_BREAK_ID) { + if (error) { + error->PutCString("Could not create return address breakpoint."); + if (m_constructor_errors.GetSize() > 0) { + error->PutCString(" "); + error->PutCString(m_constructor_errors.GetString()); + } + } + return false; + } + + return true; +} + +bool ThreadPlanStepOut::DoPlanExplainsStop(Event *event_ptr) { + // If the step out plan is done, then we just need to step through the + // inlined frame. + if (m_step_out_to_inline_plan_sp) { + return m_step_out_to_inline_plan_sp->MischiefManaged(); + } else if (m_step_through_inline_plan_sp) { + if (m_step_through_inline_plan_sp->MischiefManaged()) { + CalculateReturnValue(); + SetPlanComplete(); + return true; + } else + return false; + } else if (m_step_out_further_plan_sp) { + return m_step_out_further_plan_sp->MischiefManaged(); + } + + // We don't explain signals or breakpoints (breakpoints that handle stepping + // in or out will be handled by a child plan. + + StopInfoSP stop_info_sp = GetPrivateStopInfo(); + if (stop_info_sp) { + StopReason reason = stop_info_sp->GetStopReason(); + if (reason == eStopReasonBreakpoint) { + // If this is OUR breakpoint, we're fine, otherwise we don't know why + // this happened... + BreakpointSiteSP site_sp( + m_process.GetBreakpointSiteList().FindByID(stop_info_sp->GetValue())); + if (site_sp && site_sp->IsBreakpointAtThisSite(m_return_bp_id)) { + bool done; + + StackID frame_zero_id = + GetThread().GetStackFrameAtIndex(0)->GetStackID(); + + if (m_step_out_to_id == frame_zero_id) + done = true; + else if (m_step_out_to_id < frame_zero_id) { + // Either we stepped past the breakpoint, or the stack ID calculation + // was incorrect and we should probably stop. + done = true; + } else { + done = (m_immediate_step_from_id < frame_zero_id); + } + + if (done) { + if (InvokeShouldStopHereCallback(eFrameCompareOlder, m_status)) { + CalculateReturnValue(); + SetPlanComplete(); + } + } + + // If there was only one owner, then we're done. But if we also hit + // some user breakpoint on our way out, we should mark ourselves as + // done, but also not claim to explain the stop, since it is more + // important to report the user breakpoint than the step out + // completion. + + if (site_sp->GetNumberOfConstituents() == 1) + return true; + } + return false; + } else if (IsUsuallyUnexplainedStopReason(reason)) + return false; + else + return true; + } + return true; +} + +bool ThreadPlanStepOut::ShouldStop(Event *event_ptr) { + if (IsPlanComplete()) + return true; + + bool done = false; + if (m_step_out_to_inline_plan_sp) { + if (m_step_out_to_inline_plan_sp->MischiefManaged()) { + // Now step through the inlined stack we are in: + if (QueueInlinedStepPlan(true)) { + // If we can't queue a plan to do this, then just call ourselves done. + m_step_out_to_inline_plan_sp.reset(); + SetPlanComplete(false); + return true; + } else + done = true; + } else + return m_step_out_to_inline_plan_sp->ShouldStop(event_ptr); + } else if (m_step_through_inline_plan_sp) { + if (m_step_through_inline_plan_sp->MischiefManaged()) + done = true; + else + return m_step_through_inline_plan_sp->ShouldStop(event_ptr); + } else if (m_step_out_further_plan_sp) { + if (m_step_out_further_plan_sp->MischiefManaged()) + m_step_out_further_plan_sp.reset(); + else + return m_step_out_further_plan_sp->ShouldStop(event_ptr); + } + + if (!done) { + StackID frame_zero_id = GetThread().GetStackFrameAtIndex(0)->GetStackID(); + done = !(frame_zero_id < m_step_out_to_id); + } + + // The normal step out computations think we are done, so all we need to do + // is consult the ShouldStopHere, and we are done. + + if (done) { + if (InvokeShouldStopHereCallback(eFrameCompareOlder, m_status)) { + CalculateReturnValue(); + SetPlanComplete(); + } else { + m_step_out_further_plan_sp = + QueueStepOutFromHerePlan(m_flags, eFrameCompareOlder, m_status); + done = false; + } + } + + return done; +} + +bool ThreadPlanStepOut::StopOthers() { return m_stop_others; } + +StateType ThreadPlanStepOut::GetPlanRunState() { return eStateRunning; } + +bool ThreadPlanStepOut::DoWillResume(StateType resume_state, + bool current_plan) { + if (m_step_out_to_inline_plan_sp || m_step_through_inline_plan_sp) + return true; + + if (m_return_bp_id == LLDB_INVALID_BREAK_ID) + return false; + + if (current_plan) { + Breakpoint *return_bp = GetTarget().GetBreakpointByID(m_return_bp_id).get(); + if (return_bp != nullptr) + return_bp->SetEnabled(true); + } + return true; +} + +bool ThreadPlanStepOut::WillStop() { + if (m_return_bp_id != LLDB_INVALID_BREAK_ID) { + Breakpoint *return_bp = GetTarget().GetBreakpointByID(m_return_bp_id).get(); + if (return_bp != nullptr) + return_bp->SetEnabled(false); + } + + return true; +} + +bool ThreadPlanStepOut::MischiefManaged() { + if (IsPlanComplete()) { + // Did I reach my breakpoint? If so I'm done. + // + // I also check the stack depth, since if we've blown past the breakpoint + // for some + // reason and we're now stopping for some other reason altogether, then + // we're done with this step out operation. + + Log *log = GetLog(LLDBLog::Step); + if (log) + LLDB_LOGF(log, "Completed step out plan."); + if (m_return_bp_id != LLDB_INVALID_BREAK_ID) { + GetTarget().RemoveBreakpointByID(m_return_bp_id); + m_return_bp_id = LLDB_INVALID_BREAK_ID; + } + + ThreadPlan::MischiefManaged(); + return true; + } else { + return false; + } +} + +bool ThreadPlanStepOut::QueueInlinedStepPlan(bool queue_now) { + // Now figure out the range of this inlined block, and set up a "step through + // range" plan for that. If we've been provided with a context, then use the + // block in that context. + Thread &thread = GetThread(); + StackFrameSP immediate_return_from_sp(thread.GetStackFrameAtIndex(0)); + if (!immediate_return_from_sp) + return false; + + Log *log = GetLog(LLDBLog::Step); + if (log) { + StreamString s; + immediate_return_from_sp->Dump(&s, true, false); + LLDB_LOGF(log, "Queuing inlined frame to step past: %s.", s.GetData()); + } + + Block *from_block = immediate_return_from_sp->GetFrameBlock(); + if (from_block) { + Block *inlined_block = from_block->GetContainingInlinedBlock(); + if (inlined_block) { + size_t num_ranges = inlined_block->GetNumRanges(); + AddressRange inline_range; + if (inlined_block->GetRangeAtIndex(0, inline_range)) { + SymbolContext inlined_sc; + inlined_block->CalculateSymbolContext(&inlined_sc); + inlined_sc.target_sp = GetTarget().shared_from_this(); + RunMode run_mode = + m_stop_others ? lldb::eOnlyThisThread : lldb::eAllThreads; + const LazyBool avoid_no_debug = eLazyBoolNo; + + m_step_through_inline_plan_sp = + std::make_shared<ThreadPlanStepOverRange>( + thread, inline_range, inlined_sc, run_mode, avoid_no_debug); + ThreadPlanStepOverRange *step_through_inline_plan_ptr = + static_cast<ThreadPlanStepOverRange *>( + m_step_through_inline_plan_sp.get()); + m_step_through_inline_plan_sp->SetPrivate(true); + + step_through_inline_plan_ptr->SetOkayToDiscard(true); + StreamString errors; + if (!step_through_inline_plan_ptr->ValidatePlan(&errors)) { + // FIXME: Log this failure. + delete step_through_inline_plan_ptr; + return false; + } + + for (size_t i = 1; i < num_ranges; i++) { + if (inlined_block->GetRangeAtIndex(i, inline_range)) + step_through_inline_plan_ptr->AddRange(inline_range); + } + + if (queue_now) + thread.QueueThreadPlan(m_step_through_inline_plan_sp, false); + return true; + } + } + } + + return false; +} + +void ThreadPlanStepOut::CalculateReturnValue() { + if (m_return_valobj_sp) + return; + + if (!m_calculate_return_value) + return; + + if (m_immediate_step_from_function != nullptr) { + CompilerType return_compiler_type = + m_immediate_step_from_function->GetCompilerType() + .GetFunctionReturnType(); + if (return_compiler_type) { + lldb::ABISP abi_sp = m_process.GetABI(); + if (abi_sp) + m_return_valobj_sp = + abi_sp->GetReturnValueObject(GetThread(), return_compiler_type); + } + } +} + +bool ThreadPlanStepOut::IsPlanStale() { + // If we are still lower on the stack than the frame we are returning to, + // then there's something for us to do. Otherwise, we're stale. + + StackID frame_zero_id = GetThread().GetStackFrameAtIndex(0)->GetStackID(); + return !(frame_zero_id < m_step_out_to_id); +} diff --git a/contrib/llvm-project/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp b/contrib/llvm-project/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp new file mode 100644 index 000000000000..f88a2b895931 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp @@ -0,0 +1,172 @@ +//===-- ThreadPlanStepOverBreakpoint.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 "lldb/Target/ThreadPlanStepOverBreakpoint.h" + +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Stream.h" + +using namespace lldb; +using namespace lldb_private; + +// ThreadPlanStepOverBreakpoint: Single steps over a breakpoint bp_site_sp at +// the pc. + +ThreadPlanStepOverBreakpoint::ThreadPlanStepOverBreakpoint(Thread &thread) + : ThreadPlan( + ThreadPlan::eKindStepOverBreakpoint, "Step over breakpoint trap", + thread, eVoteNo, + eVoteNoOpinion), // We need to report the run since this happens + // first in the thread plan stack when stepping over + // a breakpoint + m_breakpoint_addr(LLDB_INVALID_ADDRESS), + m_auto_continue(false), m_reenabled_breakpoint_site(false) + +{ + m_breakpoint_addr = thread.GetRegisterContext()->GetPC(); + m_breakpoint_site_id = + thread.GetProcess()->GetBreakpointSiteList().FindIDByAddress( + m_breakpoint_addr); +} + +ThreadPlanStepOverBreakpoint::~ThreadPlanStepOverBreakpoint() = default; + +void ThreadPlanStepOverBreakpoint::GetDescription( + Stream *s, lldb::DescriptionLevel level) { + s->Printf("Single stepping past breakpoint site %" PRIu64 " at 0x%" PRIx64, + m_breakpoint_site_id, (uint64_t)m_breakpoint_addr); +} + +bool ThreadPlanStepOverBreakpoint::ValidatePlan(Stream *error) { return true; } + +bool ThreadPlanStepOverBreakpoint::DoPlanExplainsStop(Event *event_ptr) { + StopInfoSP stop_info_sp = GetPrivateStopInfo(); + if (stop_info_sp) { + StopReason reason = stop_info_sp->GetStopReason(); + + Log *log = GetLog(LLDBLog::Step); + LLDB_LOG(log, "Step over breakpoint stopped for reason: {0}.", + Thread::StopReasonAsString(reason)); + + switch (reason) { + case eStopReasonTrace: + case eStopReasonNone: + return true; + case eStopReasonBreakpoint: + { + // It's a little surprising that we stop here for a breakpoint hit. + // However, when you single step ONTO a breakpoint we still want to call + // that a breakpoint hit, and trigger the actions, etc. Otherwise you + // would see the PC at the breakpoint without having triggered the + // actions, then you'd continue, the PC wouldn't change, and you'd see + // the breakpoint hit, which would be odd. So the lower levels fake + // "step onto breakpoint address" and return that as a breakpoint hit. + // So our trace step COULD appear as a breakpoint hit if the next + // instruction also contained a breakpoint. We don't want to handle + // that, since we really don't know what to do with breakpoint hits. + // But make sure we don't set ourselves to auto-continue or we'll wrench + // control away from the plans that can deal with this. + // Be careful, however, as we may have "seen a breakpoint under the PC + // because we stopped without changing the PC, in which case we do want + // to re-claim this stop so we'll try again. + lldb::addr_t pc_addr = GetThread().GetRegisterContext()->GetPC(); + + if (pc_addr == m_breakpoint_addr) { + LLDB_LOGF(log, + "Got breakpoint stop reason but pc: 0x%" PRIx64 + "hasn't changed.", + pc_addr); + return true; + } + + SetAutoContinue(false); + return false; + } + default: + return false; + } + } + return false; +} + +bool ThreadPlanStepOverBreakpoint::ShouldStop(Event *event_ptr) { + return !ShouldAutoContinue(event_ptr); +} + +bool ThreadPlanStepOverBreakpoint::StopOthers() { return true; } + +StateType ThreadPlanStepOverBreakpoint::GetPlanRunState() { + return eStateStepping; +} + +bool ThreadPlanStepOverBreakpoint::DoWillResume(StateType resume_state, + bool current_plan) { + if (current_plan) { + BreakpointSiteSP bp_site_sp( + m_process.GetBreakpointSiteList().FindByAddress(m_breakpoint_addr)); + if (bp_site_sp && bp_site_sp->IsEnabled()) { + m_process.DisableBreakpointSite(bp_site_sp.get()); + m_reenabled_breakpoint_site = false; + } + } + return true; +} + +bool ThreadPlanStepOverBreakpoint::WillStop() { + ReenableBreakpointSite(); + return true; +} + +void ThreadPlanStepOverBreakpoint::DidPop() { ReenableBreakpointSite(); } + +bool ThreadPlanStepOverBreakpoint::MischiefManaged() { + lldb::addr_t pc_addr = GetThread().GetRegisterContext()->GetPC(); + + if (pc_addr == m_breakpoint_addr) { + // If we are still at the PC of our breakpoint, then for some reason we + // didn't get a chance to run. + return false; + } else { + Log *log = GetLog(LLDBLog::Step); + LLDB_LOGF(log, "Completed step over breakpoint plan."); + // Otherwise, re-enable the breakpoint we were stepping over, and we're + // done. + ReenableBreakpointSite(); + ThreadPlan::MischiefManaged(); + return true; + } +} + +void ThreadPlanStepOverBreakpoint::ReenableBreakpointSite() { + if (!m_reenabled_breakpoint_site) { + m_reenabled_breakpoint_site = true; + BreakpointSiteSP bp_site_sp( + m_process.GetBreakpointSiteList().FindByAddress(m_breakpoint_addr)); + if (bp_site_sp) { + m_process.EnableBreakpointSite(bp_site_sp.get()); + } + } +} +void ThreadPlanStepOverBreakpoint::ThreadDestroyed() { + ReenableBreakpointSite(); +} + +void ThreadPlanStepOverBreakpoint::SetAutoContinue(bool do_it) { + m_auto_continue = do_it; +} + +bool ThreadPlanStepOverBreakpoint::ShouldAutoContinue(Event *event_ptr) { + return m_auto_continue; +} + +bool ThreadPlanStepOverBreakpoint::IsPlanStale() { + return GetThread().GetRegisterContext()->GetPC() != m_breakpoint_addr; +} diff --git a/contrib/llvm-project/lldb/source/Target/ThreadPlanStepOverRange.cpp b/contrib/llvm-project/lldb/source/Target/ThreadPlanStepOverRange.cpp new file mode 100644 index 000000000000..abe4d34bd32c --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadPlanStepOverRange.cpp @@ -0,0 +1,419 @@ +//===-- ThreadPlanStepOverRange.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 "lldb/Target/ThreadPlanStepOverRange.h" +#include "lldb/Symbol/Block.h" +#include "lldb/Symbol/CompileUnit.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/LineTable.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadPlanStepOut.h" +#include "lldb/Target/ThreadPlanStepThrough.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Stream.h" + +using namespace lldb_private; +using namespace lldb; + +uint32_t ThreadPlanStepOverRange::s_default_flag_values = 0; + +// ThreadPlanStepOverRange: Step through a stack range, either stepping over or +// into based on the value of \a type. + +ThreadPlanStepOverRange::ThreadPlanStepOverRange( + Thread &thread, const AddressRange &range, + const SymbolContext &addr_context, lldb::RunMode stop_others, + LazyBool step_out_avoids_code_without_debug_info) + : ThreadPlanStepRange(ThreadPlan::eKindStepOverRange, + "Step range stepping over", thread, range, + addr_context, stop_others), + ThreadPlanShouldStopHere(this), m_first_resume(true) { + SetFlagsToDefault(); + SetupAvoidNoDebug(step_out_avoids_code_without_debug_info); +} + +ThreadPlanStepOverRange::~ThreadPlanStepOverRange() = default; + +void ThreadPlanStepOverRange::GetDescription(Stream *s, + lldb::DescriptionLevel level) { + auto PrintFailureIfAny = [&]() { + if (m_status.Success()) + return; + s->Printf(" failed (%s)", m_status.AsCString()); + }; + + if (level == lldb::eDescriptionLevelBrief) { + s->Printf("step over"); + PrintFailureIfAny(); + return; + } + + s->Printf("Stepping over"); + bool printed_line_info = false; + if (m_addr_context.line_entry.IsValid()) { + s->Printf(" line "); + m_addr_context.line_entry.DumpStopContext(s, false); + printed_line_info = true; + } + + if (!printed_line_info || level == eDescriptionLevelVerbose) { + s->Printf(" using ranges: "); + DumpRanges(s); + } + + PrintFailureIfAny(); + + s->PutChar('.'); +} + +void ThreadPlanStepOverRange::SetupAvoidNoDebug( + LazyBool step_out_avoids_code_without_debug_info) { + bool avoid_nodebug = true; + switch (step_out_avoids_code_without_debug_info) { + case eLazyBoolYes: + avoid_nodebug = true; + break; + case eLazyBoolNo: + avoid_nodebug = false; + break; + case eLazyBoolCalculate: + avoid_nodebug = GetThread().GetStepOutAvoidsNoDebug(); + break; + } + if (avoid_nodebug) + GetFlags().Set(ThreadPlanShouldStopHere::eStepOutAvoidNoDebug); + else + GetFlags().Clear(ThreadPlanShouldStopHere::eStepOutAvoidNoDebug); + // Step Over plans should always avoid no-debug on step in. Seems like you + // shouldn't have to say this, but a tail call looks more like a step in that + // a step out, so we want to catch this case. + GetFlags().Set(ThreadPlanShouldStopHere::eStepInAvoidNoDebug); +} + +bool ThreadPlanStepOverRange::IsEquivalentContext( + const SymbolContext &context) { + // Match as much as is specified in the m_addr_context: This is a fairly + // loose sanity check. Note, sometimes the target doesn't get filled in so I + // left out the target check. And sometimes the module comes in as the .o + // file from the inlined range, so I left that out too... + if (m_addr_context.comp_unit) { + if (m_addr_context.comp_unit != context.comp_unit) + return false; + if (m_addr_context.function) { + if (m_addr_context.function != context.function) + return false; + // It is okay to return to a different block of a straight function, we + // only have to be more careful if returning from one inlined block to + // another. + if (m_addr_context.block->GetInlinedFunctionInfo() == nullptr && + context.block->GetInlinedFunctionInfo() == nullptr) + return true; + return m_addr_context.block == context.block; + } + } + // Fall back to symbol if we have no decision from comp_unit/function/block. + return m_addr_context.symbol && m_addr_context.symbol == context.symbol; +} + +bool ThreadPlanStepOverRange::ShouldStop(Event *event_ptr) { + Log *log = GetLog(LLDBLog::Step); + Thread &thread = GetThread(); + + if (log) { + StreamString s; + DumpAddress(s.AsRawOstream(), thread.GetRegisterContext()->GetPC(), + GetTarget().GetArchitecture().GetAddressByteSize()); + LLDB_LOGF(log, "ThreadPlanStepOverRange reached %s.", s.GetData()); + } + + // If we're out of the range but in the same frame or in our caller's frame + // then we should stop. When stepping out we only stop others if we are + // forcing running one thread. + bool stop_others = (m_stop_others == lldb::eOnlyThisThread); + ThreadPlanSP new_plan_sp; + FrameComparison frame_order = CompareCurrentFrameToStartFrame(); + + if (frame_order == eFrameCompareOlder) { + // If we're in an older frame then we should stop. + // + // A caveat to this is if we think the frame is older but we're actually in + // a trampoline. + // I'm going to make the assumption that you wouldn't RETURN to a + // trampoline. So if we are in a trampoline we think the frame is older + // because the trampoline confused the backtracer. As below, we step + // through first, and then try to figure out how to get back out again. + + new_plan_sp = thread.QueueThreadPlanForStepThrough(m_stack_id, false, + stop_others, m_status); + + if (new_plan_sp && log) + LLDB_LOGF(log, + "Thought I stepped out, but in fact arrived at a trampoline."); + } else if (frame_order == eFrameCompareYounger) { + // Make sure we really are in a new frame. Do that by unwinding and seeing + // if the start function really is our start function... + for (uint32_t i = 1;; ++i) { + StackFrameSP older_frame_sp = thread.GetStackFrameAtIndex(i); + if (!older_frame_sp) { + // We can't unwind the next frame we should just get out of here & + // stop... + break; + } + + const SymbolContext &older_context = + older_frame_sp->GetSymbolContext(eSymbolContextEverything); + if (IsEquivalentContext(older_context)) { + // If we have the next-branch-breakpoint in the range, we can just + // rely on that breakpoint to trigger once we return to the range. + if (m_next_branch_bp_sp) + return false; + new_plan_sp = thread.QueueThreadPlanForStepOutNoShouldStop( + false, nullptr, true, stop_others, eVoteNo, eVoteNoOpinion, 0, + m_status, true); + break; + } else { + new_plan_sp = thread.QueueThreadPlanForStepThrough( + m_stack_id, false, stop_others, m_status); + // If we found a way through, then we should stop recursing. + if (new_plan_sp) + break; + } + } + } else { + // If we're still in the range, keep going. + if (InRange()) { + SetNextBranchBreakpoint(); + return false; + } + + if (!InSymbol()) { + // This one is a little tricky. Sometimes we may be in a stub or + // something similar, in which case we need to get out of there. But if + // we are in a stub then it's likely going to be hard to get out from + // here. It is probably easiest to step into the stub, and then it will + // be straight-forward to step out. + new_plan_sp = thread.QueueThreadPlanForStepThrough(m_stack_id, false, + stop_others, m_status); + } else { + // The current clang (at least through 424) doesn't always get the + // address range for the DW_TAG_inlined_subroutines right, so that when + // you leave the inlined range the line table says you are still in the + // source file of the inlining function. This is bad, because now you + // are missing the stack frame for the function containing the inlining, + // and if you sensibly do "finish" to get out of this function you will + // instead exit the containing function. To work around this, we check + // whether we are still in the source file we started in, and if not + // assume it is an error, and push a plan to get us out of this line and + // back to the containing file. + + if (m_addr_context.line_entry.IsValid()) { + SymbolContext sc; + StackFrameSP frame_sp = thread.GetStackFrameAtIndex(0); + sc = frame_sp->GetSymbolContext(eSymbolContextEverything); + if (sc.line_entry.IsValid()) { + if (!sc.line_entry.original_file_sp->Equal( + *m_addr_context.line_entry.original_file_sp, + SupportFile::eEqualFileSpecAndChecksumIfSet) && + sc.comp_unit == m_addr_context.comp_unit && + sc.function == m_addr_context.function) { + // Okay, find the next occurrence of this file in the line table: + LineTable *line_table = m_addr_context.comp_unit->GetLineTable(); + if (line_table) { + Address cur_address = frame_sp->GetFrameCodeAddress(); + uint32_t entry_idx; + LineEntry line_entry; + if (line_table->FindLineEntryByAddress(cur_address, line_entry, + &entry_idx)) { + LineEntry next_line_entry; + bool step_past_remaining_inline = false; + if (entry_idx > 0) { + // We require the previous line entry and the current line + // entry come from the same file. The other requirement is + // that the previous line table entry be part of an inlined + // block, we don't want to step past cases where people have + // inlined some code fragment by using #include <source- + // fragment.c> directly. + LineEntry prev_line_entry; + if (line_table->GetLineEntryAtIndex(entry_idx - 1, + prev_line_entry) && + prev_line_entry.original_file_sp->Equal( + *line_entry.original_file_sp, + SupportFile::eEqualFileSpecAndChecksumIfSet)) { + SymbolContext prev_sc; + Address prev_address = + prev_line_entry.range.GetBaseAddress(); + prev_address.CalculateSymbolContext(&prev_sc); + if (prev_sc.block) { + Block *inlined_block = + prev_sc.block->GetContainingInlinedBlock(); + if (inlined_block) { + AddressRange inline_range; + inlined_block->GetRangeContainingAddress(prev_address, + inline_range); + if (!inline_range.ContainsFileAddress(cur_address)) { + + step_past_remaining_inline = true; + } + } + } + } + } + + if (step_past_remaining_inline) { + uint32_t look_ahead_step = 1; + while (line_table->GetLineEntryAtIndex( + entry_idx + look_ahead_step, next_line_entry)) { + // Make sure we haven't wandered out of the function we + // started from... + Address next_line_address = + next_line_entry.range.GetBaseAddress(); + Function *next_line_function = + next_line_address.CalculateSymbolContextFunction(); + if (next_line_function != m_addr_context.function) + break; + + if (next_line_entry.original_file_sp->Equal( + *m_addr_context.line_entry.original_file_sp, + SupportFile::eEqualFileSpecAndChecksumIfSet)) { + const bool abort_other_plans = false; + const RunMode stop_other_threads = RunMode::eAllThreads; + lldb::addr_t cur_pc = thread.GetStackFrameAtIndex(0) + ->GetRegisterContext() + ->GetPC(); + AddressRange step_range( + cur_pc, + next_line_address.GetLoadAddress(&GetTarget()) - + cur_pc); + + new_plan_sp = thread.QueueThreadPlanForStepOverRange( + abort_other_plans, step_range, sc, stop_other_threads, + m_status); + break; + } + look_ahead_step++; + } + } + } + } + } + } + } + } + } + + // If we get to this point, we're not going to use a previously set "next + // branch" breakpoint, so delete it: + ClearNextBranchBreakpoint(); + + // If we haven't figured out something to do yet, then ask the ShouldStopHere + // callback: + if (!new_plan_sp) { + new_plan_sp = CheckShouldStopHereAndQueueStepOut(frame_order, m_status); + } + + if (!new_plan_sp) + m_no_more_plans = true; + else { + // Any new plan will be an implementation plan, so mark it private: + new_plan_sp->SetPrivate(true); + m_no_more_plans = false; + } + + if (!new_plan_sp) { + // For efficiencies sake, we know we're done here so we don't have to do + // this calculation again in MischiefManaged. + SetPlanComplete(m_status.Success()); + return true; + } else + return false; +} + +bool ThreadPlanStepOverRange::DoPlanExplainsStop(Event *event_ptr) { + // For crashes, breakpoint hits, signals, etc, let the base plan (or some + // plan above us) handle the stop. That way the user can see the stop, step + // around, and then when they are done, continue and have their step + // complete. The exception is if we've hit our "run to next branch" + // breakpoint. Note, unlike the step in range plan, we don't mark ourselves + // complete if we hit an unexplained breakpoint/crash. + + Log *log = GetLog(LLDBLog::Step); + StopInfoSP stop_info_sp = GetPrivateStopInfo(); + bool return_value; + + if (stop_info_sp) { + StopReason reason = stop_info_sp->GetStopReason(); + + if (reason == eStopReasonTrace) { + return_value = true; + } else if (reason == eStopReasonBreakpoint) { + return_value = NextRangeBreakpointExplainsStop(stop_info_sp); + } else { + if (log) + log->PutCString("ThreadPlanStepOverRange got asked if it explains the " + "stop for some reason other than step."); + return_value = false; + } + } else + return_value = true; + + return return_value; +} + +bool ThreadPlanStepOverRange::DoWillResume(lldb::StateType resume_state, + bool current_plan) { + if (resume_state != eStateSuspended && m_first_resume) { + m_first_resume = false; + if (resume_state == eStateStepping && current_plan) { + Thread &thread = GetThread(); + // See if we are about to step over an inlined call in the middle of the + // inlined stack, if so figure out its extents and reset our range to + // step over that. + bool in_inlined_stack = thread.DecrementCurrentInlinedDepth(); + if (in_inlined_stack) { + Log *log = GetLog(LLDBLog::Step); + LLDB_LOGF(log, + "ThreadPlanStepInRange::DoWillResume: adjusting range to " + "the frame at inlined depth %d.", + thread.GetCurrentInlinedDepth()); + StackFrameSP stack_sp = thread.GetStackFrameAtIndex(0); + if (stack_sp) { + Block *frame_block = stack_sp->GetFrameBlock(); + lldb::addr_t curr_pc = thread.GetRegisterContext()->GetPC(); + AddressRange my_range; + if (frame_block->GetRangeContainingLoadAddress( + curr_pc, m_process.GetTarget(), my_range)) { + m_address_ranges.clear(); + m_address_ranges.push_back(my_range); + if (log) { + StreamString s; + const InlineFunctionInfo *inline_info = + frame_block->GetInlinedFunctionInfo(); + const char *name; + if (inline_info) + name = inline_info->GetName().AsCString(); + else + name = "<unknown-notinlined>"; + + s.Printf( + "Stepping over inlined function \"%s\" in inlined stack: ", + name); + DumpRanges(&s); + log->PutString(s.GetString()); + } + } + } + } + } + } + + return true; +} diff --git a/contrib/llvm-project/lldb/source/Target/ThreadPlanStepRange.cpp b/contrib/llvm-project/lldb/source/Target/ThreadPlanStepRange.cpp new file mode 100644 index 000000000000..801856bd5419 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadPlanStepRange.cpp @@ -0,0 +1,497 @@ +//===-- ThreadPlanStepRange.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 "lldb/Target/ThreadPlanStepRange.h" +#include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Breakpoint/BreakpointSite.h" +#include "lldb/Core/Disassembler.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/StopInfo.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadPlanRunToAddress.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Stream.h" + +using namespace lldb; +using namespace lldb_private; + +// ThreadPlanStepRange: Step through a stack range, either stepping over or +// into based on the value of \a type. + +ThreadPlanStepRange::ThreadPlanStepRange(ThreadPlanKind kind, const char *name, + Thread &thread, + const AddressRange &range, + const SymbolContext &addr_context, + lldb::RunMode stop_others, + bool given_ranges_only) + : ThreadPlan(kind, name, thread, eVoteNoOpinion, eVoteNoOpinion), + m_addr_context(addr_context), m_address_ranges(), + m_stop_others(stop_others), m_stack_id(), m_parent_stack_id(), + m_no_more_plans(false), m_first_run_event(true), m_use_fast_step(false), + m_given_ranges_only(given_ranges_only) { + m_use_fast_step = GetTarget().GetUseFastStepping(); + AddRange(range); + m_stack_id = thread.GetStackFrameAtIndex(0)->GetStackID(); + StackFrameSP parent_stack = thread.GetStackFrameAtIndex(1); + if (parent_stack) + m_parent_stack_id = parent_stack->GetStackID(); +} + +ThreadPlanStepRange::~ThreadPlanStepRange() { ClearNextBranchBreakpoint(); } + +void ThreadPlanStepRange::DidPush() { + // See if we can find a "next range" breakpoint: + SetNextBranchBreakpoint(); +} + +bool ThreadPlanStepRange::ValidatePlan(Stream *error) { + if (m_could_not_resolve_hw_bp) { + if (error) + error->PutCString( + "Could not create hardware breakpoint for thread plan."); + return false; + } + return true; +} + +Vote ThreadPlanStepRange::ShouldReportStop(Event *event_ptr) { + Log *log = GetLog(LLDBLog::Step); + + const Vote vote = IsPlanComplete() ? eVoteYes : eVoteNo; + LLDB_LOGF(log, "ThreadPlanStepRange::ShouldReportStop() returning vote %i\n", + vote); + return vote; +} + +void ThreadPlanStepRange::AddRange(const AddressRange &new_range) { + // For now I'm just adding the ranges. At some point we may want to condense + // the ranges if they overlap, though I don't think it is likely to be very + // important. + m_address_ranges.push_back(new_range); + + // Fill the slot for this address range with an empty DisassemblerSP in the + // instruction ranges. I want the indices to match, but I don't want to do + // the work to disassemble this range if I don't step into it. + m_instruction_ranges.push_back(DisassemblerSP()); +} + +void ThreadPlanStepRange::DumpRanges(Stream *s) { + size_t num_ranges = m_address_ranges.size(); + if (num_ranges == 1) { + m_address_ranges[0].Dump(s, &GetTarget(), Address::DumpStyleLoadAddress); + } else { + for (size_t i = 0; i < num_ranges; i++) { + s->Printf(" %" PRIu64 ": ", uint64_t(i)); + m_address_ranges[i].Dump(s, &GetTarget(), Address::DumpStyleLoadAddress); + } + } +} + +bool ThreadPlanStepRange::InRange() { + Log *log = GetLog(LLDBLog::Step); + bool ret_value = false; + Thread &thread = GetThread(); + lldb::addr_t pc_load_addr = thread.GetRegisterContext()->GetPC(); + + size_t num_ranges = m_address_ranges.size(); + for (size_t i = 0; i < num_ranges; i++) { + ret_value = + m_address_ranges[i].ContainsLoadAddress(pc_load_addr, &GetTarget()); + if (ret_value) + break; + } + + if (!ret_value && !m_given_ranges_only) { + // See if we've just stepped to another part of the same line number... + StackFrame *frame = thread.GetStackFrameAtIndex(0).get(); + + SymbolContext new_context( + frame->GetSymbolContext(eSymbolContextEverything)); + if (m_addr_context.line_entry.IsValid() && + new_context.line_entry.IsValid()) { + if (m_addr_context.line_entry.original_file_sp->Equal( + *new_context.line_entry.original_file_sp, + SupportFile::eEqualFileSpecAndChecksumIfSet)) { + if (m_addr_context.line_entry.line == new_context.line_entry.line) { + m_addr_context = new_context; + const bool include_inlined_functions = + GetKind() == eKindStepOverRange; + AddRange(m_addr_context.line_entry.GetSameLineContiguousAddressRange( + include_inlined_functions)); + ret_value = true; + if (log) { + StreamString s; + m_addr_context.line_entry.Dump(&s, &GetTarget(), true, + Address::DumpStyleLoadAddress, + Address::DumpStyleLoadAddress, true); + + LLDB_LOGF( + log, + "Step range plan stepped to another range of same line: %s", + s.GetData()); + } + } else if (new_context.line_entry.line == 0) { + new_context.line_entry.line = m_addr_context.line_entry.line; + m_addr_context = new_context; + const bool include_inlined_functions = + GetKind() == eKindStepOverRange; + AddRange(m_addr_context.line_entry.GetSameLineContiguousAddressRange( + include_inlined_functions)); + ret_value = true; + if (log) { + StreamString s; + m_addr_context.line_entry.Dump(&s, &GetTarget(), true, + Address::DumpStyleLoadAddress, + Address::DumpStyleLoadAddress, true); + + LLDB_LOGF(log, + "Step range plan stepped to a range at linenumber 0 " + "stepping through that range: %s", + s.GetData()); + } + } else if (new_context.line_entry.range.GetBaseAddress().GetLoadAddress( + &GetTarget()) != pc_load_addr) { + // Another thing that sometimes happens here is that we step out of + // one line into the MIDDLE of another line. So far I mostly see + // this due to bugs in the debug information. But we probably don't + // want to be in the middle of a line range, so in that case reset + // the stepping range to the line we've stepped into the middle of + // and continue. + m_addr_context = new_context; + m_address_ranges.clear(); + AddRange(m_addr_context.line_entry.range); + ret_value = true; + if (log) { + StreamString s; + m_addr_context.line_entry.Dump(&s, &GetTarget(), true, + Address::DumpStyleLoadAddress, + Address::DumpStyleLoadAddress, true); + + LLDB_LOGF(log, + "Step range plan stepped to the middle of new " + "line(%d): %s, continuing to clear this line.", + new_context.line_entry.line, s.GetData()); + } + } + } + } + } + + if (!ret_value && log) + LLDB_LOGF(log, "Step range plan out of range to 0x%" PRIx64, pc_load_addr); + + return ret_value; +} + +bool ThreadPlanStepRange::InSymbol() { + lldb::addr_t cur_pc = GetThread().GetRegisterContext()->GetPC(); + if (m_addr_context.function != nullptr) { + return m_addr_context.function->GetAddressRange().ContainsLoadAddress( + cur_pc, &GetTarget()); + } else if (m_addr_context.symbol && m_addr_context.symbol->ValueIsAddress()) { + AddressRange range(m_addr_context.symbol->GetAddressRef(), + m_addr_context.symbol->GetByteSize()); + return range.ContainsLoadAddress(cur_pc, &GetTarget()); + } + return false; +} + +// FIXME: This should also handle inlining if we aren't going to do inlining in +// the +// main stack. +// +// Ideally we should remember the whole stack frame list, and then compare that +// to the current list. + +lldb::FrameComparison ThreadPlanStepRange::CompareCurrentFrameToStartFrame() { + FrameComparison frame_order; + Thread &thread = GetThread(); + StackID cur_frame_id = thread.GetStackFrameAtIndex(0)->GetStackID(); + + if (cur_frame_id == m_stack_id) { + frame_order = eFrameCompareEqual; + } else if (cur_frame_id < m_stack_id) { + frame_order = eFrameCompareYounger; + } else { + StackFrameSP cur_parent_frame = thread.GetStackFrameAtIndex(1); + StackID cur_parent_id; + if (cur_parent_frame) + cur_parent_id = cur_parent_frame->GetStackID(); + if (m_parent_stack_id.IsValid() && cur_parent_id.IsValid() && + m_parent_stack_id == cur_parent_id) + frame_order = eFrameCompareSameParent; + else + frame_order = eFrameCompareOlder; + } + return frame_order; +} + +bool ThreadPlanStepRange::StopOthers() { + switch (m_stop_others) { + case lldb::eOnlyThisThread: + return true; + case lldb::eOnlyDuringStepping: + // If there is a call in the range of the next branch breakpoint, + // then we should always run all threads, since a call can execute + // arbitrary code which might for instance take a lock that's held + // by another thread. + return !m_found_calls; + case lldb::eAllThreads: + return false; + } + llvm_unreachable("Unhandled run mode!"); +} + +InstructionList *ThreadPlanStepRange::GetInstructionsForAddress( + lldb::addr_t addr, size_t &range_index, size_t &insn_offset) { + size_t num_ranges = m_address_ranges.size(); + for (size_t i = 0; i < num_ranges; i++) { + if (m_address_ranges[i].ContainsLoadAddress(addr, &GetTarget())) { + // Some joker added a zero size range to the stepping range... + if (m_address_ranges[i].GetByteSize() == 0) + return nullptr; + + if (!m_instruction_ranges[i]) { + // Disassemble the address range given: + const char *plugin_name = nullptr; + const char *flavor = nullptr; + m_instruction_ranges[i] = Disassembler::DisassembleRange( + GetTarget().GetArchitecture(), plugin_name, flavor, GetTarget(), + m_address_ranges[i]); + } + if (!m_instruction_ranges[i]) + return nullptr; + else { + // Find where we are in the instruction list as well. If we aren't at + // an instruction, return nullptr. In this case, we're probably lost, + // and shouldn't try to do anything fancy. + + insn_offset = + m_instruction_ranges[i] + ->GetInstructionList() + .GetIndexOfInstructionAtLoadAddress(addr, GetTarget()); + if (insn_offset == UINT32_MAX) + return nullptr; + else { + range_index = i; + return &m_instruction_ranges[i]->GetInstructionList(); + } + } + } + } + return nullptr; +} + +void ThreadPlanStepRange::ClearNextBranchBreakpoint() { + if (m_next_branch_bp_sp) { + Log *log = GetLog(LLDBLog::Step); + LLDB_LOGF(log, "Removing next branch breakpoint: %d.", + m_next_branch_bp_sp->GetID()); + GetTarget().RemoveBreakpointByID(m_next_branch_bp_sp->GetID()); + m_next_branch_bp_sp.reset(); + m_could_not_resolve_hw_bp = false; + m_found_calls = false; + } +} + +bool ThreadPlanStepRange::SetNextBranchBreakpoint() { + if (m_next_branch_bp_sp) + return true; + + Log *log = GetLog(LLDBLog::Step); + // Stepping through ranges using breakpoints doesn't work yet, but with this + // off we fall back to instruction single stepping. + if (!m_use_fast_step) + return false; + + // clear the m_found_calls, we'll rediscover it for this range. + m_found_calls = false; + + lldb::addr_t cur_addr = GetThread().GetRegisterContext()->GetPC(); + // Find the current address in our address ranges, and fetch the disassembly + // if we haven't already: + size_t pc_index; + size_t range_index; + InstructionList *instructions = + GetInstructionsForAddress(cur_addr, range_index, pc_index); + if (instructions == nullptr) + return false; + else { + const bool ignore_calls = GetKind() == eKindStepOverRange; + uint32_t branch_index = instructions->GetIndexOfNextBranchInstruction( + pc_index, ignore_calls, &m_found_calls); + Address run_to_address; + + // If we didn't find a branch, run to the end of the range. + if (branch_index == UINT32_MAX) { + uint32_t last_index = instructions->GetSize() - 1; + if (last_index - pc_index > 1) { + InstructionSP last_inst = + instructions->GetInstructionAtIndex(last_index); + size_t last_inst_size = last_inst->GetOpcode().GetByteSize(); + run_to_address = last_inst->GetAddress(); + run_to_address.Slide(last_inst_size); + } + } else if (branch_index - pc_index > 1) { + run_to_address = + instructions->GetInstructionAtIndex(branch_index)->GetAddress(); + } + + if (run_to_address.IsValid()) { + const bool is_internal = true; + m_next_branch_bp_sp = + GetTarget().CreateBreakpoint(run_to_address, is_internal, false); + if (m_next_branch_bp_sp) { + + if (m_next_branch_bp_sp->IsHardware() && + !m_next_branch_bp_sp->HasResolvedLocations()) + m_could_not_resolve_hw_bp = true; + + if (log) { + lldb::break_id_t bp_site_id = LLDB_INVALID_BREAK_ID; + BreakpointLocationSP bp_loc = + m_next_branch_bp_sp->GetLocationAtIndex(0); + if (bp_loc) { + BreakpointSiteSP bp_site = bp_loc->GetBreakpointSite(); + if (bp_site) { + bp_site_id = bp_site->GetID(); + } + } + LLDB_LOGF(log, + "ThreadPlanStepRange::SetNextBranchBreakpoint - Setting " + "breakpoint %d (site %d) to run to address 0x%" PRIx64, + m_next_branch_bp_sp->GetID(), bp_site_id, + run_to_address.GetLoadAddress(&m_process.GetTarget())); + } + + m_next_branch_bp_sp->SetThreadID(m_tid); + m_next_branch_bp_sp->SetBreakpointKind("next-branch-location"); + + return true; + } else + return false; + } + } + return false; +} + +bool ThreadPlanStepRange::NextRangeBreakpointExplainsStop( + lldb::StopInfoSP stop_info_sp) { + Log *log = GetLog(LLDBLog::Step); + if (!m_next_branch_bp_sp) + return false; + + break_id_t bp_site_id = stop_info_sp->GetValue(); + BreakpointSiteSP bp_site_sp = + m_process.GetBreakpointSiteList().FindByID(bp_site_id); + if (!bp_site_sp) + return false; + else if (!bp_site_sp->IsBreakpointAtThisSite(m_next_branch_bp_sp->GetID())) + return false; + else { + // If we've hit the next branch breakpoint, then clear it. + size_t num_constituents = bp_site_sp->GetNumberOfConstituents(); + bool explains_stop = true; + // If all the constituents are internal, then we are probably just stepping + // over this range from multiple threads, or multiple frames, so we want to + // continue. If one is not internal, then we should not explain the stop, + // and let the user breakpoint handle the stop. + for (size_t i = 0; i < num_constituents; i++) { + if (!bp_site_sp->GetConstituentAtIndex(i)->GetBreakpoint().IsInternal()) { + explains_stop = false; + break; + } + } + LLDB_LOGF(log, + "ThreadPlanStepRange::NextRangeBreakpointExplainsStop - Hit " + "next range breakpoint which has %" PRIu64 + " constituents - explains stop: %u.", + (uint64_t)num_constituents, explains_stop); + ClearNextBranchBreakpoint(); + return explains_stop; + } +} + +bool ThreadPlanStepRange::WillStop() { return true; } + +StateType ThreadPlanStepRange::GetPlanRunState() { + if (m_next_branch_bp_sp) + return eStateRunning; + else + return eStateStepping; +} + +bool ThreadPlanStepRange::MischiefManaged() { + // If we have pushed some plans between ShouldStop & MischiefManaged, then + // we're not done... + // I do this check first because we might have stepped somewhere that will + // fool InRange into + // thinking it needs to step past the end of that line. This happens, for + // instance, when stepping over inlined code that is in the middle of the + // current line. + + if (!m_no_more_plans) + return false; + + bool done = true; + if (!IsPlanComplete()) { + if (InRange()) { + done = false; + } else { + FrameComparison frame_order = CompareCurrentFrameToStartFrame(); + done = (frame_order != eFrameCompareOlder) ? m_no_more_plans : true; + } + } + + if (done) { + Log *log = GetLog(LLDBLog::Step); + LLDB_LOGF(log, "Completed step through range plan."); + ClearNextBranchBreakpoint(); + ThreadPlan::MischiefManaged(); + return true; + } else { + return false; + } +} + +bool ThreadPlanStepRange::IsPlanStale() { + Log *log = GetLog(LLDBLog::Step); + FrameComparison frame_order = CompareCurrentFrameToStartFrame(); + + if (frame_order == eFrameCompareOlder) { + if (log) { + LLDB_LOGF(log, "ThreadPlanStepRange::IsPlanStale returning true, we've " + "stepped out."); + } + return true; + } else if (frame_order == eFrameCompareEqual && InSymbol()) { + // If we are not in a place we should step through, we've gotten stale. One + // tricky bit here is that some stubs don't push a frame, so we should. + // check that we are in the same symbol. + if (!InRange()) { + // Set plan Complete when we reach next instruction just after the range + lldb::addr_t addr = GetThread().GetRegisterContext()->GetPC() - 1; + size_t num_ranges = m_address_ranges.size(); + for (size_t i = 0; i < num_ranges; i++) { + bool in_range = + m_address_ranges[i].ContainsLoadAddress(addr, &GetTarget()); + if (in_range) { + SetPlanComplete(); + } + } + return true; + } + } + return false; +} diff --git a/contrib/llvm-project/lldb/source/Target/ThreadPlanStepThrough.cpp b/contrib/llvm-project/lldb/source/Target/ThreadPlanStepThrough.cpp new file mode 100644 index 000000000000..e15635e40700 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadPlanStepThrough.cpp @@ -0,0 +1,264 @@ +//===-- ThreadPlanStepThrough.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 "lldb/Target/ThreadPlanStepThrough.h" +#include "lldb/Breakpoint/Breakpoint.h" +#include "lldb/Target/DynamicLoader.h" +#include "lldb/Target/LanguageRuntime.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Stream.h" + +using namespace lldb; +using namespace lldb_private; + +// ThreadPlanStepThrough: If the current instruction is a trampoline, step +// through it If it is the beginning of the prologue of a function, step +// through that as well. + +ThreadPlanStepThrough::ThreadPlanStepThrough(Thread &thread, + StackID &m_stack_id, + bool stop_others) + : ThreadPlan(ThreadPlan::eKindStepThrough, + "Step through trampolines and prologues", thread, + eVoteNoOpinion, eVoteNoOpinion), + m_start_address(0), m_backstop_bkpt_id(LLDB_INVALID_BREAK_ID), + m_backstop_addr(LLDB_INVALID_ADDRESS), m_return_stack_id(m_stack_id), + m_stop_others(stop_others) { + LookForPlanToStepThroughFromCurrentPC(); + + // If we don't get a valid step through plan, don't bother to set up a + // backstop. + if (m_sub_plan_sp) { + m_start_address = GetThread().GetRegisterContext()->GetPC(0); + + // We are going to return back to the concrete frame 1, we might pass by + // some inlined code that we're in the middle of by doing this, but it's + // easier than trying to figure out where the inlined code might return to. + + StackFrameSP return_frame_sp = thread.GetFrameWithStackID(m_stack_id); + + if (return_frame_sp) { + m_backstop_addr = return_frame_sp->GetFrameCodeAddress().GetLoadAddress( + thread.CalculateTarget().get()); + Breakpoint *return_bp = + m_process.GetTarget() + .CreateBreakpoint(m_backstop_addr, true, false) + .get(); + + if (return_bp != nullptr) { + if (return_bp->IsHardware() && !return_bp->HasResolvedLocations()) + m_could_not_resolve_hw_bp = true; + return_bp->SetThreadID(m_tid); + m_backstop_bkpt_id = return_bp->GetID(); + return_bp->SetBreakpointKind("step-through-backstop"); + } + Log *log = GetLog(LLDBLog::Step); + if (log) { + LLDB_LOGF(log, "Setting backstop breakpoint %d at address: 0x%" PRIx64, + m_backstop_bkpt_id, m_backstop_addr); + } + } + } +} + +ThreadPlanStepThrough::~ThreadPlanStepThrough() { ClearBackstopBreakpoint(); } + +void ThreadPlanStepThrough::DidPush() { + if (m_sub_plan_sp) + PushPlan(m_sub_plan_sp); +} + +void ThreadPlanStepThrough::LookForPlanToStepThroughFromCurrentPC() { + Thread &thread = GetThread(); + DynamicLoader *loader = thread.GetProcess()->GetDynamicLoader(); + if (loader) + m_sub_plan_sp = loader->GetStepThroughTrampolinePlan(thread, m_stop_others); + + // If the DynamicLoader was unable to provide us with a ThreadPlan, then we + // try the LanguageRuntimes. + if (!m_sub_plan_sp) { + for (LanguageRuntime *runtime : m_process.GetLanguageRuntimes()) { + m_sub_plan_sp = + runtime->GetStepThroughTrampolinePlan(thread, m_stop_others); + + if (m_sub_plan_sp) + break; + } + } + + Log *log = GetLog(LLDBLog::Step); + if (log) { + lldb::addr_t current_address = GetThread().GetRegisterContext()->GetPC(0); + if (m_sub_plan_sp) { + StreamString s; + m_sub_plan_sp->GetDescription(&s, lldb::eDescriptionLevelFull); + LLDB_LOGF(log, "Found step through plan from 0x%" PRIx64 ": %s", + current_address, s.GetData()); + } else { + LLDB_LOGF(log, + "Couldn't find step through plan from address 0x%" PRIx64 ".", + current_address); + } + } +} + +void ThreadPlanStepThrough::GetDescription(Stream *s, + lldb::DescriptionLevel level) { + if (level == lldb::eDescriptionLevelBrief) + s->Printf("Step through"); + else { + s->PutCString("Stepping through trampoline code from: "); + DumpAddress(s->AsRawOstream(), m_start_address, sizeof(addr_t)); + if (m_backstop_bkpt_id != LLDB_INVALID_BREAK_ID) { + s->Printf(" with backstop breakpoint ID: %d at address: ", + m_backstop_bkpt_id); + DumpAddress(s->AsRawOstream(), m_backstop_addr, sizeof(addr_t)); + } else + s->PutCString(" unable to set a backstop breakpoint."); + } +} + +bool ThreadPlanStepThrough::ValidatePlan(Stream *error) { + if (m_could_not_resolve_hw_bp) { + if (error) + error->PutCString( + "Could not create hardware breakpoint for thread plan."); + return false; + } + + if (m_backstop_bkpt_id == LLDB_INVALID_BREAK_ID) { + if (error) + error->PutCString("Could not create backstop breakpoint."); + return false; + } + + if (!m_sub_plan_sp.get()) { + if (error) + error->PutCString("Does not have a subplan."); + return false; + } + + return true; +} + +bool ThreadPlanStepThrough::DoPlanExplainsStop(Event *event_ptr) { + // If we have a sub-plan, it will have been asked first if we explain the + // stop, and we won't get asked. The only time we would be the one directly + // asked this question is if we hit our backstop breakpoint. + + return HitOurBackstopBreakpoint(); +} + +bool ThreadPlanStepThrough::ShouldStop(Event *event_ptr) { + // If we've already marked ourselves done, then we're done... + if (IsPlanComplete()) + return true; + + // First, did we hit the backstop breakpoint? + if (HitOurBackstopBreakpoint()) { + SetPlanComplete(true); + return true; + } + + // If we don't have a sub-plan, then we're also done (can't see how we would + // ever get here without a plan, but just in case. + + if (!m_sub_plan_sp) { + SetPlanComplete(); + return true; + } + + // If the current sub plan is not done, we don't want to stop. Actually, we + // probably won't ever get here in this state, since we generally won't get + // asked any questions if out current sub-plan is not done... + if (!m_sub_plan_sp->IsPlanComplete()) + return false; + + // If our current sub plan failed, then let's just run to our backstop. If + // we can't do that then just stop. + if (!m_sub_plan_sp->PlanSucceeded()) { + if (m_backstop_bkpt_id != LLDB_INVALID_BREAK_ID) { + m_sub_plan_sp.reset(); + return false; + } else { + SetPlanComplete(false); + return true; + } + } + + // Next see if there is a specific step through plan at our current pc (these + // might chain, for instance stepping through a dylib trampoline to the objc + // dispatch function...) + LookForPlanToStepThroughFromCurrentPC(); + if (m_sub_plan_sp) { + PushPlan(m_sub_plan_sp); + return false; + } else { + SetPlanComplete(); + return true; + } +} + +bool ThreadPlanStepThrough::StopOthers() { return m_stop_others; } + +StateType ThreadPlanStepThrough::GetPlanRunState() { return eStateRunning; } + +bool ThreadPlanStepThrough::DoWillResume(StateType resume_state, + bool current_plan) { + return true; +} + +bool ThreadPlanStepThrough::WillStop() { return true; } + +void ThreadPlanStepThrough::ClearBackstopBreakpoint() { + if (m_backstop_bkpt_id != LLDB_INVALID_BREAK_ID) { + m_process.GetTarget().RemoveBreakpointByID(m_backstop_bkpt_id); + m_backstop_bkpt_id = LLDB_INVALID_BREAK_ID; + m_could_not_resolve_hw_bp = false; + } +} + +bool ThreadPlanStepThrough::MischiefManaged() { + Log *log = GetLog(LLDBLog::Step); + + if (!IsPlanComplete()) { + return false; + } else { + LLDB_LOGF(log, "Completed step through step plan."); + + ClearBackstopBreakpoint(); + ThreadPlan::MischiefManaged(); + return true; + } +} + +bool ThreadPlanStepThrough::HitOurBackstopBreakpoint() { + Thread &thread = GetThread(); + StopInfoSP stop_info_sp(thread.GetStopInfo()); + if (stop_info_sp && stop_info_sp->GetStopReason() == eStopReasonBreakpoint) { + break_id_t stop_value = (break_id_t)stop_info_sp->GetValue(); + BreakpointSiteSP cur_site_sp = + m_process.GetBreakpointSiteList().FindByID(stop_value); + if (cur_site_sp && + cur_site_sp->IsBreakpointAtThisSite(m_backstop_bkpt_id)) { + StackID cur_frame_zero_id = thread.GetStackFrameAtIndex(0)->GetStackID(); + + if (cur_frame_zero_id == m_return_stack_id) { + Log *log = GetLog(LLDBLog::Step); + if (log) + log->PutCString("ThreadPlanStepThrough hit backstop breakpoint."); + return true; + } + } + } + return false; +} diff --git a/contrib/llvm-project/lldb/source/Target/ThreadPlanStepUntil.cpp b/contrib/llvm-project/lldb/source/Target/ThreadPlanStepUntil.cpp new file mode 100644 index 000000000000..ee0f803510e6 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadPlanStepUntil.cpp @@ -0,0 +1,329 @@ +//===-- ThreadPlanStepUntil.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 "lldb/Target/ThreadPlanStepUntil.h" + +#include "lldb/Breakpoint/Breakpoint.h" +#include "lldb/Symbol/SymbolContextScope.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/StopInfo.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" + +using namespace lldb; +using namespace lldb_private; + +// ThreadPlanStepUntil: Run until we reach a given line number or step out of +// the current frame + +ThreadPlanStepUntil::ThreadPlanStepUntil(Thread &thread, + lldb::addr_t *address_list, + size_t num_addresses, bool stop_others, + uint32_t frame_idx) + : ThreadPlan(ThreadPlan::eKindStepUntil, "Step until", thread, + eVoteNoOpinion, eVoteNoOpinion), + m_step_from_insn(LLDB_INVALID_ADDRESS), + m_return_bp_id(LLDB_INVALID_BREAK_ID), + m_return_addr(LLDB_INVALID_ADDRESS), m_stepped_out(false), + m_should_stop(false), m_ran_analyze(false), m_explains_stop(false), + m_until_points(), m_stop_others(stop_others) { + // Stash away our "until" addresses: + TargetSP target_sp(thread.CalculateTarget()); + + StackFrameSP frame_sp(thread.GetStackFrameAtIndex(frame_idx)); + if (frame_sp) { + m_step_from_insn = frame_sp->GetStackID().GetPC(); + + // Find the return address and set a breakpoint there: + // FIXME - can we do this more securely if we know first_insn? + + StackFrameSP return_frame_sp(thread.GetStackFrameAtIndex(frame_idx + 1)); + if (return_frame_sp) { + // TODO: add inline functionality + m_return_addr = return_frame_sp->GetStackID().GetPC(); + Breakpoint *return_bp = + target_sp->CreateBreakpoint(m_return_addr, true, false).get(); + + if (return_bp != nullptr) { + if (return_bp->IsHardware() && !return_bp->HasResolvedLocations()) + m_could_not_resolve_hw_bp = true; + return_bp->SetThreadID(m_tid); + m_return_bp_id = return_bp->GetID(); + return_bp->SetBreakpointKind("until-return-backstop"); + } + } + + m_stack_id = frame_sp->GetStackID(); + + // Now set breakpoints on all our return addresses: + for (size_t i = 0; i < num_addresses; i++) { + Breakpoint *until_bp = + target_sp->CreateBreakpoint(address_list[i], true, false).get(); + if (until_bp != nullptr) { + until_bp->SetThreadID(m_tid); + m_until_points[address_list[i]] = until_bp->GetID(); + until_bp->SetBreakpointKind("until-target"); + } else { + m_until_points[address_list[i]] = LLDB_INVALID_BREAK_ID; + } + } + } +} + +ThreadPlanStepUntil::~ThreadPlanStepUntil() { Clear(); } + +void ThreadPlanStepUntil::Clear() { + Target &target = GetTarget(); + if (m_return_bp_id != LLDB_INVALID_BREAK_ID) { + target.RemoveBreakpointByID(m_return_bp_id); + m_return_bp_id = LLDB_INVALID_BREAK_ID; + } + + until_collection::iterator pos, end = m_until_points.end(); + for (pos = m_until_points.begin(); pos != end; pos++) { + target.RemoveBreakpointByID((*pos).second); + } + m_until_points.clear(); + m_could_not_resolve_hw_bp = false; +} + +void ThreadPlanStepUntil::GetDescription(Stream *s, + lldb::DescriptionLevel level) { + if (level == lldb::eDescriptionLevelBrief) { + s->Printf("step until"); + if (m_stepped_out) + s->Printf(" - stepped out"); + } else { + if (m_until_points.size() == 1) + s->Printf("Stepping from address 0x%" PRIx64 " until we reach 0x%" PRIx64 + " using breakpoint %d", + (uint64_t)m_step_from_insn, + (uint64_t)(*m_until_points.begin()).first, + (*m_until_points.begin()).second); + else { + until_collection::iterator pos, end = m_until_points.end(); + s->Printf("Stepping from address 0x%" PRIx64 " until we reach one of:", + (uint64_t)m_step_from_insn); + for (pos = m_until_points.begin(); pos != end; pos++) { + s->Printf("\n\t0x%" PRIx64 " (bp: %d)", (uint64_t)(*pos).first, + (*pos).second); + } + } + s->Printf(" stepped out address is 0x%" PRIx64 ".", + (uint64_t)m_return_addr); + } +} + +bool ThreadPlanStepUntil::ValidatePlan(Stream *error) { + if (m_could_not_resolve_hw_bp) { + if (error) + error->PutCString( + "Could not create hardware breakpoint for thread plan."); + return false; + } else if (m_return_bp_id == LLDB_INVALID_BREAK_ID) { + if (error) + error->PutCString("Could not create return breakpoint."); + return false; + } else { + until_collection::iterator pos, end = m_until_points.end(); + for (pos = m_until_points.begin(); pos != end; pos++) { + if (!LLDB_BREAK_ID_IS_VALID((*pos).second)) + return false; + } + return true; + } +} + +void ThreadPlanStepUntil::AnalyzeStop() { + if (m_ran_analyze) + return; + + StopInfoSP stop_info_sp = GetPrivateStopInfo(); + m_should_stop = true; + m_explains_stop = false; + + if (stop_info_sp) { + StopReason reason = stop_info_sp->GetStopReason(); + + if (reason == eStopReasonBreakpoint) { + // If this is OUR breakpoint, we're fine, otherwise we don't know why + // this happened... + BreakpointSiteSP this_site = + m_process.GetBreakpointSiteList().FindByID(stop_info_sp->GetValue()); + if (!this_site) { + m_explains_stop = false; + return; + } + + if (this_site->IsBreakpointAtThisSite(m_return_bp_id)) { + // If we are at our "step out" breakpoint, and the stack depth has + // shrunk, then this is indeed our stop. If the stack depth has grown, + // then we've hit our step out breakpoint recursively. If we are the + // only breakpoint at that location, then we do explain the stop, and + // we'll just continue. If there was another breakpoint here, then we + // don't explain the stop, but we won't mark ourselves Completed, + // because maybe that breakpoint will continue, and then we'll finish + // the "until". + bool done; + StackID cur_frame_zero_id; + + done = (m_stack_id < cur_frame_zero_id); + + if (done) { + m_stepped_out = true; + SetPlanComplete(); + } else + m_should_stop = false; + + if (this_site->GetNumberOfConstituents() == 1) + m_explains_stop = true; + else + m_explains_stop = false; + return; + } else { + // Check if we've hit one of our "until" breakpoints. + until_collection::iterator pos, end = m_until_points.end(); + for (pos = m_until_points.begin(); pos != end; pos++) { + if (this_site->IsBreakpointAtThisSite((*pos).second)) { + // If we're at the right stack depth, then we're done. + Thread &thread = GetThread(); + bool done; + StackID frame_zero_id = + thread.GetStackFrameAtIndex(0)->GetStackID(); + + if (frame_zero_id == m_stack_id) + done = true; + else if (frame_zero_id < m_stack_id) + done = false; + else { + StackFrameSP older_frame_sp = thread.GetStackFrameAtIndex(1); + + // But if we can't even unwind one frame we should just get out + // of here & stop... + if (older_frame_sp) { + const SymbolContext &older_context = + older_frame_sp->GetSymbolContext(eSymbolContextEverything); + SymbolContext stack_context; + m_stack_id.GetSymbolContextScope()->CalculateSymbolContext( + &stack_context); + + done = (older_context == stack_context); + } else + done = false; + } + + if (done) + SetPlanComplete(); + else + m_should_stop = false; + + // Otherwise we've hit this breakpoint recursively. If we're the + // only breakpoint here, then we do explain the stop, and we'll + // continue. If not then we should let higher plans handle this + // stop. + if (this_site->GetNumberOfConstituents() == 1) + m_explains_stop = true; + else { + m_should_stop = true; + m_explains_stop = false; + } + return; + } + } + } + // If we get here we haven't hit any of our breakpoints, so let the + // higher plans take care of the stop. + m_explains_stop = false; + return; + } else if (IsUsuallyUnexplainedStopReason(reason)) { + m_explains_stop = false; + } else { + m_explains_stop = true; + } + } +} + +bool ThreadPlanStepUntil::DoPlanExplainsStop(Event *event_ptr) { + // We don't explain signals or breakpoints (breakpoints that handle stepping + // in or out will be handled by a child plan. + AnalyzeStop(); + return m_explains_stop; +} + +bool ThreadPlanStepUntil::ShouldStop(Event *event_ptr) { + // If we've told our self in ExplainsStop that we plan to continue, then do + // so here. Otherwise, as long as this thread has stopped for a reason, we + // will stop. + + StopInfoSP stop_info_sp = GetPrivateStopInfo(); + if (!stop_info_sp || stop_info_sp->GetStopReason() == eStopReasonNone) + return false; + + AnalyzeStop(); + return m_should_stop; +} + +bool ThreadPlanStepUntil::StopOthers() { return m_stop_others; } + +StateType ThreadPlanStepUntil::GetPlanRunState() { return eStateRunning; } + +bool ThreadPlanStepUntil::DoWillResume(StateType resume_state, + bool current_plan) { + if (current_plan) { + Target &target = GetTarget(); + Breakpoint *return_bp = target.GetBreakpointByID(m_return_bp_id).get(); + if (return_bp != nullptr) + return_bp->SetEnabled(true); + + until_collection::iterator pos, end = m_until_points.end(); + for (pos = m_until_points.begin(); pos != end; pos++) { + Breakpoint *until_bp = target.GetBreakpointByID((*pos).second).get(); + if (until_bp != nullptr) + until_bp->SetEnabled(true); + } + } + + m_should_stop = true; + m_ran_analyze = false; + m_explains_stop = false; + return true; +} + +bool ThreadPlanStepUntil::WillStop() { + Target &target = GetTarget(); + Breakpoint *return_bp = target.GetBreakpointByID(m_return_bp_id).get(); + if (return_bp != nullptr) + return_bp->SetEnabled(false); + + until_collection::iterator pos, end = m_until_points.end(); + for (pos = m_until_points.begin(); pos != end; pos++) { + Breakpoint *until_bp = target.GetBreakpointByID((*pos).second).get(); + if (until_bp != nullptr) + until_bp->SetEnabled(false); + } + return true; +} + +bool ThreadPlanStepUntil::MischiefManaged() { + // I'm letting "PlanExplainsStop" do all the work, and just reporting that + // here. + bool done = false; + if (IsPlanComplete()) { + Log *log = GetLog(LLDBLog::Step); + LLDB_LOGF(log, "Completed step until plan."); + + Clear(); + done = true; + } + if (done) + ThreadPlan::MischiefManaged(); + + return done; +} diff --git a/contrib/llvm-project/lldb/source/Target/ThreadPlanTracer.cpp b/contrib/llvm-project/lldb/source/Target/ThreadPlanTracer.cpp new file mode 100644 index 000000000000..acadda8f582f --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadPlanTracer.cpp @@ -0,0 +1,235 @@ +//===-- ThreadPlanTracer.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 "lldb/Core/Debugger.h" +#include "lldb/Core/Disassembler.h" +#include "lldb/Core/DumpRegisterValue.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/Value.h" +#include "lldb/Symbol/TypeList.h" +#include "lldb/Symbol/TypeSystem.h" +#include "lldb/Target/ABI.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadPlan.h" +#include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/DataExtractor.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/State.h" + +using namespace lldb; +using namespace lldb_private; + +#pragma mark ThreadPlanTracer + +ThreadPlanTracer::ThreadPlanTracer(Thread &thread, lldb::StreamSP &stream_sp) + : m_process(*thread.GetProcess().get()), m_tid(thread.GetID()), + m_enabled(false), m_stream_sp(stream_sp), m_thread(nullptr) {} + +ThreadPlanTracer::ThreadPlanTracer(Thread &thread) + : m_process(*thread.GetProcess().get()), m_tid(thread.GetID()), + m_enabled(false), m_stream_sp(), m_thread(nullptr) {} + +Stream *ThreadPlanTracer::GetLogStream() { + if (m_stream_sp) + return m_stream_sp.get(); + else { + TargetSP target_sp(GetThread().CalculateTarget()); + if (target_sp) + return &(target_sp->GetDebugger().GetOutputStream()); + } + return nullptr; +} + +Thread &ThreadPlanTracer::GetThread() { + if (m_thread) + return *m_thread; + + ThreadSP thread_sp = m_process.GetThreadList().FindThreadByID(m_tid); + m_thread = thread_sp.get(); + return *m_thread; +} +void ThreadPlanTracer::Log() { + SymbolContext sc; + bool show_frame_index = false; + bool show_fullpaths = false; + + Stream *stream = GetLogStream(); + if (stream) { + GetThread().GetStackFrameAtIndex(0)->Dump(stream, show_frame_index, + show_fullpaths); + stream->Printf("\n"); + stream->Flush(); + } +} + +bool ThreadPlanTracer::TracerExplainsStop() { + if (m_enabled) { + lldb::StopInfoSP stop_info = GetThread().GetStopInfo(); + return (stop_info->GetStopReason() == eStopReasonTrace); + } else + return false; +} + +#pragma mark ThreadPlanAssemblyTracer + +ThreadPlanAssemblyTracer::ThreadPlanAssemblyTracer(Thread &thread, + lldb::StreamSP &stream_sp) + : ThreadPlanTracer(thread, stream_sp), m_disassembler_sp(), m_intptr_type(), + m_register_values() {} + +ThreadPlanAssemblyTracer::ThreadPlanAssemblyTracer(Thread &thread) + : ThreadPlanTracer(thread), m_disassembler_sp(), m_intptr_type(), + m_register_values() {} + +Disassembler *ThreadPlanAssemblyTracer::GetDisassembler() { + if (!m_disassembler_sp) + m_disassembler_sp = Disassembler::FindPlugin( + m_process.GetTarget().GetArchitecture(), nullptr, nullptr); + return m_disassembler_sp.get(); +} + +TypeFromUser ThreadPlanAssemblyTracer::GetIntPointerType() { + if (!m_intptr_type.IsValid()) { + if (auto target_sp = m_process.CalculateTarget()) { + auto type_system_or_err = + target_sp->GetScratchTypeSystemForLanguage(eLanguageTypeC); + if (auto err = type_system_or_err.takeError()) { + LLDB_LOG_ERROR( + GetLog(LLDBLog::Types), std::move(err), + "Unable to get integer pointer type from TypeSystem: {0}"); + } else { + if (auto ts = *type_system_or_err) + m_intptr_type = TypeFromUser(ts->GetBuiltinTypeForEncodingAndBitSize( + eEncodingUint, + target_sp->GetArchitecture().GetAddressByteSize() * 8)); + } + } + } + return m_intptr_type; +} + +ThreadPlanAssemblyTracer::~ThreadPlanAssemblyTracer() = default; + +void ThreadPlanAssemblyTracer::TracingStarted() { +} + +void ThreadPlanAssemblyTracer::TracingEnded() { m_register_values.clear(); } + +void ThreadPlanAssemblyTracer::Log() { + Stream *stream = GetLogStream(); + + if (!stream) + return; + + RegisterContext *reg_ctx = GetThread().GetRegisterContext().get(); + + lldb::addr_t pc = reg_ctx->GetPC(); + Address pc_addr; + bool addr_valid = false; + uint8_t buffer[16] = {0}; // Must be big enough for any single instruction + addr_valid = m_process.GetTarget().GetSectionLoadList().ResolveLoadAddress( + pc, pc_addr); + + pc_addr.Dump(stream, &GetThread(), Address::DumpStyleResolvedDescription, + Address::DumpStyleModuleWithFileAddress); + stream->PutCString(" "); + + Disassembler *disassembler = GetDisassembler(); + if (disassembler) { + Status err; + m_process.ReadMemory(pc, buffer, sizeof(buffer), err); + + if (err.Success()) { + DataExtractor extractor(buffer, sizeof(buffer), m_process.GetByteOrder(), + m_process.GetAddressByteSize()); + + bool data_from_file = false; + if (addr_valid) + disassembler->DecodeInstructions(pc_addr, extractor, 0, 1, false, + data_from_file); + else + disassembler->DecodeInstructions(Address(pc), extractor, 0, 1, false, + data_from_file); + + InstructionList &instruction_list = disassembler->GetInstructionList(); + const uint32_t max_opcode_byte_size = + instruction_list.GetMaxOpcocdeByteSize(); + + if (instruction_list.GetSize()) { + const bool show_bytes = true; + const bool show_address = true; + const bool show_control_flow_kind = true; + Instruction *instruction = + instruction_list.GetInstructionAtIndex(0).get(); + const FormatEntity::Entry *disassemble_format = + m_process.GetTarget().GetDebugger().GetDisassemblyFormat(); + instruction->Dump(stream, max_opcode_byte_size, show_address, + show_bytes, show_control_flow_kind, nullptr, nullptr, + nullptr, disassemble_format, 0); + } + } + } + + const ABI *abi = m_process.GetABI().get(); + TypeFromUser intptr_type = GetIntPointerType(); + + if (abi && intptr_type.IsValid()) { + ValueList value_list; + const int num_args = 1; + + for (int arg_index = 0; arg_index < num_args; ++arg_index) { + Value value; + value.SetValueType(Value::ValueType::Scalar); + value.SetCompilerType(intptr_type); + value_list.PushValue(value); + } + + if (abi->GetArgumentValues(GetThread(), value_list)) { + for (int arg_index = 0; arg_index < num_args; ++arg_index) { + stream->Printf( + "\n\targ[%d]=%llx", arg_index, + value_list.GetValueAtIndex(arg_index)->GetScalar().ULongLong()); + + if (arg_index + 1 < num_args) + stream->PutCString(", "); + } + } + } + + if (m_register_values.empty()) { + RegisterContext *reg_ctx = GetThread().GetRegisterContext().get(); + m_register_values.resize(reg_ctx->GetRegisterCount()); + } + + RegisterValue reg_value; + for (uint32_t reg_num = 0, num_registers = reg_ctx->GetRegisterCount(); + reg_num < num_registers; ++reg_num) { + const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoAtIndex(reg_num); + if (reg_ctx->ReadRegister(reg_info, reg_value)) { + assert(reg_num < m_register_values.size()); + if (m_register_values[reg_num].GetType() == RegisterValue::eTypeInvalid || + reg_value != m_register_values[reg_num]) { + if (reg_value.GetType() != RegisterValue::eTypeInvalid) { + stream->PutCString("\n\t"); + DumpRegisterValue(reg_value, *stream, *reg_info, true, false, + eFormatDefault); + } + } + m_register_values[reg_num] = reg_value; + } + } + stream->EOL(); + stream->Flush(); +} diff --git a/contrib/llvm-project/lldb/source/Target/ThreadSpec.cpp b/contrib/llvm-project/lldb/source/Target/ThreadSpec.cpp new file mode 100644 index 000000000000..ba4c3aa89455 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/ThreadSpec.cpp @@ -0,0 +1,155 @@ +//===-- ThreadSpec.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 "lldb/Target/ThreadSpec.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/StructuredData.h" + +using namespace lldb; +using namespace lldb_private; + +const char *ThreadSpec::g_option_names[static_cast<uint32_t>( + ThreadSpec::OptionNames::LastOptionName)]{"Index", "ID", "Name", + "QueueName"}; + +ThreadSpec::ThreadSpec() : m_name(), m_queue_name() {} + +std::unique_ptr<ThreadSpec> ThreadSpec::CreateFromStructuredData( + const StructuredData::Dictionary &spec_dict, Status &error) { + uint32_t index = UINT32_MAX; + lldb::tid_t tid = LLDB_INVALID_THREAD_ID; + llvm::StringRef name; + llvm::StringRef queue_name; + + std::unique_ptr<ThreadSpec> thread_spec_up(new ThreadSpec()); + bool success = spec_dict.GetValueForKeyAsInteger( + GetKey(OptionNames::ThreadIndex), index); + if (success) + thread_spec_up->SetIndex(index); + + success = + spec_dict.GetValueForKeyAsInteger(GetKey(OptionNames::ThreadID), tid); + if (success) + thread_spec_up->SetTID(tid); + + success = + spec_dict.GetValueForKeyAsString(GetKey(OptionNames::ThreadName), name); + if (success) + thread_spec_up->SetName(name); + + success = spec_dict.GetValueForKeyAsString(GetKey(OptionNames::ThreadName), + queue_name); + if (success) + thread_spec_up->SetQueueName(queue_name); + + return thread_spec_up; +} + +StructuredData::ObjectSP ThreadSpec::SerializeToStructuredData() { + StructuredData::DictionarySP data_dict_sp(new StructuredData::Dictionary()); + + if (m_index != UINT32_MAX) + data_dict_sp->AddIntegerItem(GetKey(OptionNames::ThreadIndex), m_index); + if (m_tid != LLDB_INVALID_THREAD_ID) + data_dict_sp->AddIntegerItem(GetKey(OptionNames::ThreadID), m_tid); + if (!m_name.empty()) + data_dict_sp->AddStringItem(GetKey(OptionNames::ThreadName), m_name); + if (!m_queue_name.empty()) + data_dict_sp->AddStringItem(GetKey(OptionNames::QueueName), m_queue_name); + + return data_dict_sp; +} + +const char *ThreadSpec::GetName() const { + return m_name.empty() ? nullptr : m_name.c_str(); +} + +const char *ThreadSpec::GetQueueName() const { + return m_queue_name.empty() ? nullptr : m_queue_name.c_str(); +} + +bool ThreadSpec::TIDMatches(Thread &thread) const { + if (m_tid == LLDB_INVALID_THREAD_ID) + return true; + + lldb::tid_t thread_id = thread.GetID(); + return TIDMatches(thread_id); +} + +bool ThreadSpec::IndexMatches(Thread &thread) const { + if (m_index == UINT32_MAX) + return true; + uint32_t index = thread.GetIndexID(); + return IndexMatches(index); +} + +bool ThreadSpec::NameMatches(Thread &thread) const { + if (m_name.empty()) + return true; + + const char *name = thread.GetName(); + return NameMatches(name); +} + +bool ThreadSpec::QueueNameMatches(Thread &thread) const { + if (m_queue_name.empty()) + return true; + + const char *queue_name = thread.GetQueueName(); + return QueueNameMatches(queue_name); +} + +bool ThreadSpec::ThreadPassesBasicTests(Thread &thread) const { + if (!HasSpecification()) + return true; + + if (!TIDMatches(thread)) + return false; + + if (!IndexMatches(thread)) + return false; + + if (!NameMatches(thread)) + return false; + + if (!QueueNameMatches(thread)) + return false; + + return true; +} + +bool ThreadSpec::HasSpecification() const { + return (m_index != UINT32_MAX || m_tid != LLDB_INVALID_THREAD_ID || + !m_name.empty() || !m_queue_name.empty()); +} + +void ThreadSpec::GetDescription(Stream *s, lldb::DescriptionLevel level) const { + if (!HasSpecification()) { + if (level == eDescriptionLevelBrief) { + s->PutCString("thread spec: no "); + } + } else { + if (level == eDescriptionLevelBrief) { + s->PutCString("thread spec: yes "); + } else { + if (GetTID() != LLDB_INVALID_THREAD_ID) + s->Printf("tid: 0x%" PRIx64 " ", GetTID()); + + if (GetIndex() != UINT32_MAX) + s->Printf("index: %d ", GetIndex()); + + const char *name = GetName(); + if (name) + s->Printf("thread name: \"%s\" ", name); + + const char *queue_name = GetQueueName(); + if (queue_name) + s->Printf("queue name: \"%s\" ", queue_name); + } + } +} diff --git a/contrib/llvm-project/lldb/source/Target/Trace.cpp b/contrib/llvm-project/lldb/source/Target/Trace.cpp new file mode 100644 index 000000000000..1ffd617a80fc --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/Trace.cpp @@ -0,0 +1,530 @@ +//===-- Trace.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 "lldb/Target/Trace.h" + +#include "llvm/Support/Format.h" + +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Stream.h" +#include <optional> + +using namespace lldb; +using namespace lldb_private; +using namespace llvm; + +// Helper structs used to extract the type of a JSON trace bundle description +// object without having to parse the entire object. + +struct JSONSimpleTraceBundleDescription { + std::string type; +}; + +namespace llvm { +namespace json { + +bool fromJSON(const Value &value, JSONSimpleTraceBundleDescription &bundle, + Path path) { + json::ObjectMapper o(value, path); + return o && o.map("type", bundle.type); +} + +} // namespace json +} // namespace llvm + +/// Helper functions for fetching data in maps and returning Optionals or +/// pointers instead of iterators for simplicity. It's worth mentioning that the +/// Optionals version can't return the inner data by reference because of +/// limitations in move constructors. +/// \{ +template <typename K, typename V> +static std::optional<V> Lookup(DenseMap<K, V> &map, K k) { + auto it = map.find(k); + if (it == map.end()) + return std::nullopt; + return it->second; +} + +template <typename K, typename V> +static V *LookupAsPtr(DenseMap<K, V> &map, K k) { + auto it = map.find(k); + if (it == map.end()) + return nullptr; + return &it->second; +} + +/// Similar to the methods above but it looks for an item in a map of maps. +template <typename K1, typename K2, typename V> +static std::optional<V> Lookup(DenseMap<K1, DenseMap<K2, V>> &map, K1 k1, + K2 k2) { + auto it = map.find(k1); + if (it == map.end()) + return std::nullopt; + return Lookup(it->second, k2); +} + +/// Similar to the methods above but it looks for an item in a map of maps. +template <typename K1, typename K2, typename V> +static V *LookupAsPtr(DenseMap<K1, DenseMap<K2, V>> &map, K1 k1, K2 k2) { + auto it = map.find(k1); + if (it == map.end()) + return nullptr; + return LookupAsPtr(it->second, k2); +} +/// \} + +static Error createInvalidPlugInError(StringRef plugin_name) { + return createStringError( + std::errc::invalid_argument, + "no trace plug-in matches the specified type: \"%s\"", + plugin_name.data()); +} + +Expected<lldb::TraceSP> +Trace::LoadPostMortemTraceFromFile(Debugger &debugger, + const FileSpec &trace_description_file) { + + auto buffer_or_error = + MemoryBuffer::getFile(trace_description_file.GetPath()); + if (!buffer_or_error) { + return createStringError(std::errc::invalid_argument, + "could not open input file: %s - %s.", + trace_description_file.GetPath().c_str(), + buffer_or_error.getError().message().c_str()); + } + + Expected<json::Value> session_file = + json::parse(buffer_or_error.get()->getBuffer().str()); + if (!session_file) { + return session_file.takeError(); + } + + return Trace::FindPluginForPostMortemProcess( + debugger, *session_file, + trace_description_file.GetDirectory().AsCString()); +} + +Expected<lldb::TraceSP> Trace::FindPluginForPostMortemProcess( + Debugger &debugger, const json::Value &trace_bundle_description, + StringRef bundle_dir) { + JSONSimpleTraceBundleDescription json_bundle; + json::Path::Root root("traceBundle"); + if (!json::fromJSON(trace_bundle_description, json_bundle, root)) + return root.getError(); + + if (auto create_callback = + PluginManager::GetTraceCreateCallback(json_bundle.type)) + return create_callback(trace_bundle_description, bundle_dir, debugger); + + return createInvalidPlugInError(json_bundle.type); +} + +Expected<lldb::TraceSP> Trace::FindPluginForLiveProcess(llvm::StringRef name, + Process &process) { + if (!process.IsLiveDebugSession()) + return createStringError(inconvertibleErrorCode(), + "Can't trace non-live processes"); + + if (auto create_callback = + PluginManager::GetTraceCreateCallbackForLiveProcess(name)) + return create_callback(process); + + return createInvalidPlugInError(name); +} + +Expected<StringRef> Trace::FindPluginSchema(StringRef name) { + StringRef schema = PluginManager::GetTraceSchema(name); + if (!schema.empty()) + return schema; + + return createInvalidPlugInError(name); +} + +Error Trace::Start(const llvm::json::Value &request) { + if (!m_live_process) + return createStringError( + inconvertibleErrorCode(), + "Attempted to start tracing without a live process."); + return m_live_process->TraceStart(request); +} + +Error Trace::Stop() { + if (!m_live_process) + return createStringError( + inconvertibleErrorCode(), + "Attempted to stop tracing without a live process."); + return m_live_process->TraceStop(TraceStopRequest(GetPluginName())); +} + +Error Trace::Stop(llvm::ArrayRef<lldb::tid_t> tids) { + if (!m_live_process) + return createStringError( + inconvertibleErrorCode(), + "Attempted to stop tracing without a live process."); + return m_live_process->TraceStop(TraceStopRequest(GetPluginName(), tids)); +} + +Expected<std::string> Trace::GetLiveProcessState() { + if (!m_live_process) + return createStringError( + inconvertibleErrorCode(), + "Attempted to fetch live trace information without a live process."); + return m_live_process->TraceGetState(GetPluginName()); +} + +std::optional<uint64_t> +Trace::GetLiveThreadBinaryDataSize(lldb::tid_t tid, llvm::StringRef kind) { + Storage &storage = GetUpdatedStorage(); + return Lookup(storage.live_thread_data, tid, ConstString(kind)); +} + +std::optional<uint64_t> Trace::GetLiveCpuBinaryDataSize(lldb::cpu_id_t cpu_id, + llvm::StringRef kind) { + Storage &storage = GetUpdatedStorage(); + return Lookup(storage.live_cpu_data_sizes, cpu_id, ConstString(kind)); +} + +std::optional<uint64_t> +Trace::GetLiveProcessBinaryDataSize(llvm::StringRef kind) { + Storage &storage = GetUpdatedStorage(); + return Lookup(storage.live_process_data, ConstString(kind)); +} + +Expected<std::vector<uint8_t>> +Trace::GetLiveTraceBinaryData(const TraceGetBinaryDataRequest &request, + uint64_t expected_size) { + if (!m_live_process) + return createStringError( + inconvertibleErrorCode(), + formatv("Attempted to fetch live trace data without a live process. " + "Data kind = {0}, tid = {1}, cpu id = {2}.", + request.kind, request.tid, request.cpu_id)); + + Expected<std::vector<uint8_t>> data = + m_live_process->TraceGetBinaryData(request); + + if (!data) + return data.takeError(); + + if (data->size() != expected_size) + return createStringError( + inconvertibleErrorCode(), + formatv("Got incomplete live trace data. Data kind = {0}, expected " + "size = {1}, actual size = {2}, tid = {3}, cpu id = {4}", + request.kind, expected_size, data->size(), request.tid, + request.cpu_id)); + + return data; +} + +Expected<std::vector<uint8_t>> +Trace::GetLiveThreadBinaryData(lldb::tid_t tid, llvm::StringRef kind) { + std::optional<uint64_t> size = GetLiveThreadBinaryDataSize(tid, kind); + if (!size) + return createStringError( + inconvertibleErrorCode(), + "Tracing data \"%s\" is not available for thread %" PRIu64 ".", + kind.data(), tid); + + TraceGetBinaryDataRequest request{GetPluginName().str(), kind.str(), tid, + /*cpu_id=*/std::nullopt}; + return GetLiveTraceBinaryData(request, *size); +} + +Expected<std::vector<uint8_t>> +Trace::GetLiveCpuBinaryData(lldb::cpu_id_t cpu_id, llvm::StringRef kind) { + if (!m_live_process) + return createStringError( + inconvertibleErrorCode(), + "Attempted to fetch live cpu data without a live process."); + std::optional<uint64_t> size = GetLiveCpuBinaryDataSize(cpu_id, kind); + if (!size) + return createStringError( + inconvertibleErrorCode(), + "Tracing data \"%s\" is not available for cpu_id %" PRIu64 ".", + kind.data(), cpu_id); + + TraceGetBinaryDataRequest request{GetPluginName().str(), kind.str(), + /*tid=*/std::nullopt, cpu_id}; + return m_live_process->TraceGetBinaryData(request); +} + +Expected<std::vector<uint8_t>> +Trace::GetLiveProcessBinaryData(llvm::StringRef kind) { + std::optional<uint64_t> size = GetLiveProcessBinaryDataSize(kind); + if (!size) + return createStringError( + inconvertibleErrorCode(), + "Tracing data \"%s\" is not available for the process.", kind.data()); + + TraceGetBinaryDataRequest request{GetPluginName().str(), kind.str(), + /*tid=*/std::nullopt, + /*cpu_id*/ std::nullopt}; + return GetLiveTraceBinaryData(request, *size); +} + +Trace::Storage &Trace::GetUpdatedStorage() { + RefreshLiveProcessState(); + return m_storage; +} + +const char *Trace::RefreshLiveProcessState() { + if (!m_live_process) + return nullptr; + + uint32_t new_stop_id = m_live_process->GetStopID(); + if (new_stop_id == m_stop_id) + return nullptr; + + Log *log = GetLog(LLDBLog::Target); + LLDB_LOG(log, "Trace::RefreshLiveProcessState invoked"); + + m_stop_id = new_stop_id; + m_storage = Trace::Storage(); + + auto do_refresh = [&]() -> Error { + Expected<std::string> json_string = GetLiveProcessState(); + if (!json_string) + return json_string.takeError(); + + Expected<TraceGetStateResponse> live_process_state = + json::parse<TraceGetStateResponse>(*json_string, + "TraceGetStateResponse"); + if (!live_process_state) + return live_process_state.takeError(); + + if (live_process_state->warnings) { + for (std::string &warning : *live_process_state->warnings) + LLDB_LOG(log, "== Warning when fetching the trace state: {0}", warning); + } + + for (const TraceThreadState &thread_state : + live_process_state->traced_threads) { + for (const TraceBinaryData &item : thread_state.binary_data) + m_storage.live_thread_data[thread_state.tid].insert( + {ConstString(item.kind), item.size}); + } + + LLDB_LOG(log, "== Found {0} threads being traced", + live_process_state->traced_threads.size()); + + if (live_process_state->cpus) { + m_storage.cpus.emplace(); + for (const TraceCpuState &cpu_state : *live_process_state->cpus) { + m_storage.cpus->push_back(cpu_state.id); + for (const TraceBinaryData &item : cpu_state.binary_data) + m_storage.live_cpu_data_sizes[cpu_state.id].insert( + {ConstString(item.kind), item.size}); + } + LLDB_LOG(log, "== Found {0} cpu cpus being traced", + live_process_state->cpus->size()); + } + + for (const TraceBinaryData &item : live_process_state->process_binary_data) + m_storage.live_process_data.insert({ConstString(item.kind), item.size}); + + return DoRefreshLiveProcessState(std::move(*live_process_state), + *json_string); + }; + + if (Error err = do_refresh()) { + m_storage.live_refresh_error = toString(std::move(err)); + return m_storage.live_refresh_error->c_str(); + } + + return nullptr; +} + +Trace::Trace(ArrayRef<ProcessSP> postmortem_processes, + std::optional<std::vector<lldb::cpu_id_t>> postmortem_cpus) { + for (ProcessSP process_sp : postmortem_processes) + m_storage.postmortem_processes.push_back(process_sp.get()); + m_storage.cpus = postmortem_cpus; +} + +Process *Trace::GetLiveProcess() { return m_live_process; } + +ArrayRef<Process *> Trace::GetPostMortemProcesses() { + return m_storage.postmortem_processes; +} + +std::vector<Process *> Trace::GetAllProcesses() { + if (Process *proc = GetLiveProcess()) + return {proc}; + return GetPostMortemProcesses(); +} + +uint32_t Trace::GetStopID() { + RefreshLiveProcessState(); + return m_stop_id; +} + +llvm::Expected<FileSpec> +Trace::GetPostMortemThreadDataFile(lldb::tid_t tid, llvm::StringRef kind) { + Storage &storage = GetUpdatedStorage(); + if (std::optional<FileSpec> file = + Lookup(storage.postmortem_thread_data, tid, ConstString(kind))) + return *file; + else + return createStringError( + inconvertibleErrorCode(), + formatv("The thread with tid={0} doesn't have the tracing data {1}", + tid, kind)); +} + +llvm::Expected<FileSpec> Trace::GetPostMortemCpuDataFile(lldb::cpu_id_t cpu_id, + llvm::StringRef kind) { + Storage &storage = GetUpdatedStorage(); + if (std::optional<FileSpec> file = + Lookup(storage.postmortem_cpu_data, cpu_id, ConstString(kind))) + return *file; + else + return createStringError( + inconvertibleErrorCode(), + formatv("The cpu with id={0} doesn't have the tracing data {1}", cpu_id, + kind)); +} + +void Trace::SetPostMortemThreadDataFile(lldb::tid_t tid, llvm::StringRef kind, + FileSpec file_spec) { + Storage &storage = GetUpdatedStorage(); + storage.postmortem_thread_data[tid].insert({ConstString(kind), file_spec}); +} + +void Trace::SetPostMortemCpuDataFile(lldb::cpu_id_t cpu_id, + llvm::StringRef kind, FileSpec file_spec) { + Storage &storage = GetUpdatedStorage(); + storage.postmortem_cpu_data[cpu_id].insert({ConstString(kind), file_spec}); +} + +llvm::Error +Trace::OnLiveThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind, + OnBinaryDataReadCallback callback) { + Expected<std::vector<uint8_t>> data = GetLiveThreadBinaryData(tid, kind); + if (!data) + return data.takeError(); + return callback(*data); +} + +llvm::Error Trace::OnLiveCpuBinaryDataRead(lldb::cpu_id_t cpu_id, + llvm::StringRef kind, + OnBinaryDataReadCallback callback) { + Storage &storage = GetUpdatedStorage(); + if (std::vector<uint8_t> *cpu_data = + LookupAsPtr(storage.live_cpu_data, cpu_id, ConstString(kind))) + return callback(*cpu_data); + + Expected<std::vector<uint8_t>> data = GetLiveCpuBinaryData(cpu_id, kind); + if (!data) + return data.takeError(); + auto it = storage.live_cpu_data[cpu_id].insert( + {ConstString(kind), std::move(*data)}); + return callback(it.first->second); +} + +llvm::Error Trace::OnDataFileRead(FileSpec file, + OnBinaryDataReadCallback callback) { + ErrorOr<std::unique_ptr<MemoryBuffer>> trace_or_error = + MemoryBuffer::getFile(file.GetPath()); + if (std::error_code err = trace_or_error.getError()) + return createStringError( + inconvertibleErrorCode(), "Failed fetching trace-related file %s. %s", + file.GetPath().c_str(), toString(errorCodeToError(err)).c_str()); + + MemoryBuffer &data = **trace_or_error; + ArrayRef<uint8_t> array_ref( + reinterpret_cast<const uint8_t *>(data.getBufferStart()), + data.getBufferSize()); + return callback(array_ref); +} + +llvm::Error +Trace::OnPostMortemThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind, + OnBinaryDataReadCallback callback) { + if (Expected<FileSpec> file = GetPostMortemThreadDataFile(tid, kind)) + return OnDataFileRead(*file, callback); + else + return file.takeError(); +} + +llvm::Error +Trace::OnPostMortemCpuBinaryDataRead(lldb::cpu_id_t cpu_id, + llvm::StringRef kind, + OnBinaryDataReadCallback callback) { + if (Expected<FileSpec> file = GetPostMortemCpuDataFile(cpu_id, kind)) + return OnDataFileRead(*file, callback); + else + return file.takeError(); +} + +llvm::Error Trace::OnThreadBinaryDataRead(lldb::tid_t tid, llvm::StringRef kind, + OnBinaryDataReadCallback callback) { + if (m_live_process) + return OnLiveThreadBinaryDataRead(tid, kind, callback); + else + return OnPostMortemThreadBinaryDataRead(tid, kind, callback); +} + +llvm::Error +Trace::OnAllCpusBinaryDataRead(llvm::StringRef kind, + OnCpusBinaryDataReadCallback callback) { + DenseMap<cpu_id_t, ArrayRef<uint8_t>> buffers; + Storage &storage = GetUpdatedStorage(); + if (!storage.cpus) + return Error::success(); + + std::function<Error(std::vector<cpu_id_t>::iterator)> process_cpu = + [&](std::vector<cpu_id_t>::iterator cpu_id) -> Error { + if (cpu_id == storage.cpus->end()) + return callback(buffers); + + return OnCpuBinaryDataRead(*cpu_id, kind, + [&](ArrayRef<uint8_t> data) -> Error { + buffers.try_emplace(*cpu_id, data); + auto next_id = cpu_id; + next_id++; + return process_cpu(next_id); + }); + }; + return process_cpu(storage.cpus->begin()); +} + +llvm::Error Trace::OnCpuBinaryDataRead(lldb::cpu_id_t cpu_id, + llvm::StringRef kind, + OnBinaryDataReadCallback callback) { + if (m_live_process) + return OnLiveCpuBinaryDataRead(cpu_id, kind, callback); + else + return OnPostMortemCpuBinaryDataRead(cpu_id, kind, callback); +} + +ArrayRef<lldb::cpu_id_t> Trace::GetTracedCpus() { + Storage &storage = GetUpdatedStorage(); + if (storage.cpus) + return *storage.cpus; + return {}; +} + +std::vector<Process *> Trace::GetTracedProcesses() { + std::vector<Process *> processes; + Storage &storage = GetUpdatedStorage(); + + for (Process *proc : storage.postmortem_processes) + processes.push_back(proc); + + if (m_live_process) + processes.push_back(m_live_process); + return processes; +} diff --git a/contrib/llvm-project/lldb/source/Target/TraceCursor.cpp b/contrib/llvm-project/lldb/source/Target/TraceCursor.cpp new file mode 100644 index 000000000000..b85cc750a8a4 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/TraceCursor.cpp @@ -0,0 +1,59 @@ +//===-- TraceCursor.cpp -----------------------------------------*- C++ -*-===// +// +// 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 "lldb/Target/TraceCursor.h" + +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Trace.h" + +using namespace lldb; +using namespace lldb_private; +using namespace llvm; + +TraceCursor::TraceCursor(lldb::ThreadSP thread_sp) + : m_exe_ctx_ref(ExecutionContext(thread_sp)) {} + +ExecutionContextRef &TraceCursor::GetExecutionContextRef() { + return m_exe_ctx_ref; +} + +void TraceCursor::SetForwards(bool forwards) { m_forwards = forwards; } + +bool TraceCursor::IsForwards() const { return m_forwards; } + +bool TraceCursor::IsError() const { + return GetItemKind() == lldb::eTraceItemKindError; +} + +bool TraceCursor::IsEvent() const { + return GetItemKind() == lldb::eTraceItemKindEvent; +} + +bool TraceCursor::IsInstruction() const { + return GetItemKind() == lldb::eTraceItemKindInstruction; +} + +const char *TraceCursor::GetEventTypeAsString() const { + return EventKindToString(GetEventType()); +} + +const char *TraceCursor::EventKindToString(lldb::TraceEvent event_kind) { + switch (event_kind) { + case lldb::eTraceEventDisabledHW: + return "hardware disabled tracing"; + case lldb::eTraceEventDisabledSW: + return "software disabled tracing"; + case lldb::eTraceEventCPUChanged: + return "CPU core changed"; + case lldb::eTraceEventHWClockTick: + return "HW clock tick"; + case lldb::eTraceEventSyncPoint: + return "trace synchronization point"; + } + llvm_unreachable("Fully covered switch above"); +} diff --git a/contrib/llvm-project/lldb/source/Target/TraceDumper.cpp b/contrib/llvm-project/lldb/source/Target/TraceDumper.cpp new file mode 100644 index 000000000000..4ef8efc1a676 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/TraceDumper.cpp @@ -0,0 +1,914 @@ +//===-- TraceDumper.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 "lldb/Target/TraceDumper.h" +#include "lldb/Core/Module.h" +#include "lldb/Symbol/CompileUnit.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/SectionLoadList.h" +#include <optional> + +using namespace lldb; +using namespace lldb_private; +using namespace llvm; + +/// \return +/// The given string or \b std::nullopt if it's empty. +static std::optional<const char *> ToOptionalString(const char *s) { + if (!s) + return std::nullopt; + return s; +} + +static const char *GetModuleName(const SymbolContext &sc) { + if (!sc.module_sp) + return nullptr; + return sc.module_sp->GetFileSpec().GetFilename().AsCString(); +} + +/// \return +/// The module name (basename if the module is a file, or the actual name if +/// it's a virtual module), or \b nullptr if no name nor module was found. +static const char *GetModuleName(const TraceDumper::TraceItem &item) { + if (!item.symbol_info) + return nullptr; + return GetModuleName(item.symbol_info->sc); +} + +// This custom LineEntry validator is neded because some line_entries have +// 0 as line, which is meaningless. Notice that LineEntry::IsValid only +// checks that line is not LLDB_INVALID_LINE_NUMBER, i.e. UINT32_MAX. +static bool IsLineEntryValid(const LineEntry &line_entry) { + return line_entry.IsValid() && line_entry.line > 0; +} + +/// \return +/// \b true if the provided line entries match line, column and source file. +/// This function assumes that the line entries are valid. +static bool FileLineAndColumnMatches(const LineEntry &a, const LineEntry &b) { + if (a.line != b.line) + return false; + if (a.column != b.column) + return false; + return a.GetFile() == b.GetFile(); +} + +/// Compare the symbol contexts of the provided \a SymbolInfo +/// objects. +/// +/// \return +/// \a true if both instructions belong to the same scope level analized +/// in the following order: +/// - module +/// - symbol +/// - function +/// - inlined function +/// - source line info +static bool +IsSameInstructionSymbolContext(const TraceDumper::SymbolInfo &prev_insn, + const TraceDumper::SymbolInfo &insn, + bool check_source_line_info = true) { + // module checks + if (insn.sc.module_sp != prev_insn.sc.module_sp) + return false; + + // symbol checks + if (insn.sc.symbol != prev_insn.sc.symbol) + return false; + + // function checks + if (!insn.sc.function && !prev_insn.sc.function) + return true; // This means two dangling instruction in the same module. We + // can assume they are part of the same unnamed symbol + else if (insn.sc.function != prev_insn.sc.function) + return false; + + Block *inline_block_a = + insn.sc.block ? insn.sc.block->GetContainingInlinedBlock() : nullptr; + Block *inline_block_b = prev_insn.sc.block + ? prev_insn.sc.block->GetContainingInlinedBlock() + : nullptr; + if (inline_block_a != inline_block_b) + return false; + + // line entry checks + if (!check_source_line_info) + return true; + + const bool curr_line_valid = IsLineEntryValid(insn.sc.line_entry); + const bool prev_line_valid = IsLineEntryValid(prev_insn.sc.line_entry); + if (curr_line_valid && prev_line_valid) + return FileLineAndColumnMatches(insn.sc.line_entry, + prev_insn.sc.line_entry); + return curr_line_valid == prev_line_valid; +} + +class OutputWriterCLI : public TraceDumper::OutputWriter { +public: + OutputWriterCLI(Stream &s, const TraceDumperOptions &options, Thread &thread) + : m_s(s), m_options(options) { + m_s.Format("thread #{0}: tid = {1}\n", thread.GetIndexID(), thread.GetID()); + }; + + void NoMoreData() override { m_s << " no more data\n"; } + + void FunctionCallForest( + const std::vector<TraceDumper::FunctionCallUP> &forest) override { + for (size_t i = 0; i < forest.size(); i++) { + m_s.Format("\n[call tree #{0}]\n", i); + DumpFunctionCallTree(*forest[i]); + } + } + + void TraceItem(const TraceDumper::TraceItem &item) override { + if (item.symbol_info) { + if (!item.prev_symbol_info || + !IsSameInstructionSymbolContext(*item.prev_symbol_info, + *item.symbol_info)) { + m_s << " "; + const char *module_name = GetModuleName(item); + if (!module_name) + m_s << "(none)"; + else if (!item.symbol_info->sc.function && !item.symbol_info->sc.symbol) + m_s.Format("{0}`(none)", module_name); + else + item.symbol_info->sc.DumpStopContext( + &m_s, item.symbol_info->exe_ctx.GetTargetPtr(), + item.symbol_info->address, + /*show_fullpaths=*/false, + /*show_module=*/true, /*show_inlined_frames=*/false, + /*show_function_arguments=*/true, + /*show_function_name=*/true); + m_s << "\n"; + } + } + + if (item.error && !m_was_prev_instruction_an_error) + m_s << " ...missing instructions\n"; + + m_s.Format(" {0}: ", item.id); + + if (m_options.show_timestamps) { + m_s.Format("[{0}] ", item.timestamp + ? formatv("{0:3} ns", *item.timestamp).str() + : "unavailable"); + } + + if (item.event) { + m_s << "(event) " << TraceCursor::EventKindToString(*item.event); + switch (*item.event) { + case eTraceEventCPUChanged: + m_s.Format(" [new CPU={0}]", + item.cpu_id ? std::to_string(*item.cpu_id) : "unavailable"); + break; + case eTraceEventHWClockTick: + m_s.Format(" [{0}]", item.hw_clock ? std::to_string(*item.hw_clock) + : "unavailable"); + break; + case eTraceEventDisabledHW: + case eTraceEventDisabledSW: + break; + case eTraceEventSyncPoint: + m_s.Format(" [{0}]", item.sync_point_metadata); + break; + } + } else if (item.error) { + m_s << "(error) " << *item.error; + } else { + m_s.Format("{0:x+16}", item.load_address); + if (item.symbol_info && item.symbol_info->instruction) { + m_s << " "; + item.symbol_info->instruction->Dump( + &m_s, /*max_opcode_byte_size=*/0, + /*show_address=*/false, + /*show_bytes=*/false, m_options.show_control_flow_kind, + &item.symbol_info->exe_ctx, &item.symbol_info->sc, + /*prev_sym_ctx=*/nullptr, + /*disassembly_addr_format=*/nullptr, + /*max_address_text_size=*/0); + } + } + + m_was_prev_instruction_an_error = (bool)item.error; + m_s << "\n"; + } + +private: + void + DumpSegmentContext(const TraceDumper::FunctionCall::TracedSegment &segment) { + if (segment.GetOwningCall().IsError()) { + m_s << "<tracing errors>"; + return; + } + + const SymbolContext &first_sc = segment.GetFirstInstructionSymbolInfo().sc; + first_sc.DumpStopContext( + &m_s, segment.GetFirstInstructionSymbolInfo().exe_ctx.GetTargetPtr(), + segment.GetFirstInstructionSymbolInfo().address, + /*show_fullpaths=*/false, + /*show_module=*/true, /*show_inlined_frames=*/false, + /*show_function_arguments=*/true, + /*show_function_name=*/true); + m_s << " to "; + const SymbolContext &last_sc = segment.GetLastInstructionSymbolInfo().sc; + if (IsLineEntryValid(first_sc.line_entry) && + IsLineEntryValid(last_sc.line_entry)) { + m_s.Format("{0}:{1}", last_sc.line_entry.line, last_sc.line_entry.column); + } else { + last_sc.DumpStopContext( + &m_s, segment.GetFirstInstructionSymbolInfo().exe_ctx.GetTargetPtr(), + segment.GetLastInstructionSymbolInfo().address, + /*show_fullpaths=*/false, + /*show_module=*/false, /*show_inlined_frames=*/false, + /*show_function_arguments=*/false, + /*show_function_name=*/false); + } + } + + void DumpUntracedContext(const TraceDumper::FunctionCall &function_call) { + if (function_call.IsError()) { + m_s << "tracing error"; + } + const SymbolContext &sc = function_call.GetSymbolInfo().sc; + + const char *module_name = GetModuleName(sc); + if (!module_name) + m_s << "(none)"; + else if (!sc.function && !sc.symbol) + m_s << module_name << "`(none)"; + else + m_s << module_name << "`" << sc.GetFunctionName().AsCString(); + } + + void DumpFunctionCallTree(const TraceDumper::FunctionCall &function_call) { + if (function_call.GetUntracedPrefixSegment()) { + m_s.Indent(); + DumpUntracedContext(function_call); + m_s << "\n"; + + m_s.IndentMore(); + DumpFunctionCallTree(function_call.GetUntracedPrefixSegment()->GetNestedCall()); + m_s.IndentLess(); + } + + for (const TraceDumper::FunctionCall::TracedSegment &segment : + function_call.GetTracedSegments()) { + m_s.Indent(); + DumpSegmentContext(segment); + m_s.Format(" [{0}, {1}]\n", segment.GetFirstInstructionID(), + segment.GetLastInstructionID()); + + segment.IfNestedCall([&](const TraceDumper::FunctionCall &nested_call) { + m_s.IndentMore(); + DumpFunctionCallTree(nested_call); + m_s.IndentLess(); + }); + } + } + + Stream &m_s; + TraceDumperOptions m_options; + bool m_was_prev_instruction_an_error = false; +}; + +class OutputWriterJSON : public TraceDumper::OutputWriter { + /* schema: + error_message: string + | { + "event": string, + "id": decimal, + "tsc"?: string decimal, + "cpuId"? decimal, + } | { + "error": string, + "id": decimal, + "tsc"?: string decimal, + | { + "loadAddress": string decimal, + "id": decimal, + "hwClock"?: string decimal, + "syncPointMetadata"?: string, + "timestamp_ns"?: string decimal, + "module"?: string, + "symbol"?: string, + "line"?: decimal, + "column"?: decimal, + "source"?: string, + "mnemonic"?: string, + "controlFlowKind"?: string, + } + */ +public: + OutputWriterJSON(Stream &s, const TraceDumperOptions &options) + : m_s(s), m_options(options), + m_j(m_s.AsRawOstream(), + /*IndentSize=*/options.pretty_print_json ? 2 : 0) { + m_j.arrayBegin(); + }; + + ~OutputWriterJSON() { m_j.arrayEnd(); } + + void FunctionCallForest( + const std::vector<TraceDumper::FunctionCallUP> &forest) override { + for (size_t i = 0; i < forest.size(); i++) { + m_j.object([&] { DumpFunctionCallTree(*forest[i]); }); + } + } + + void DumpFunctionCallTree(const TraceDumper::FunctionCall &function_call) { + if (function_call.GetUntracedPrefixSegment()) { + m_j.attributeObject("untracedPrefixSegment", [&] { + m_j.attributeObject("nestedCall", [&] { + DumpFunctionCallTree( + function_call.GetUntracedPrefixSegment()->GetNestedCall()); + }); + }); + } + + if (!function_call.GetTracedSegments().empty()) { + m_j.attributeArray("tracedSegments", [&] { + for (const TraceDumper::FunctionCall::TracedSegment &segment : + function_call.GetTracedSegments()) { + m_j.object([&] { + m_j.attribute("firstInstructionId", + std::to_string(segment.GetFirstInstructionID())); + m_j.attribute("lastInstructionId", + std::to_string(segment.GetLastInstructionID())); + segment.IfNestedCall( + [&](const TraceDumper::FunctionCall &nested_call) { + m_j.attributeObject( + "nestedCall", [&] { DumpFunctionCallTree(nested_call); }); + }); + }); + } + }); + } + } + + void DumpEvent(const TraceDumper::TraceItem &item) { + m_j.attribute("event", TraceCursor::EventKindToString(*item.event)); + switch (*item.event) { + case eTraceEventCPUChanged: + m_j.attribute("cpuId", item.cpu_id); + break; + case eTraceEventHWClockTick: + m_j.attribute("hwClock", item.hw_clock); + break; + case eTraceEventDisabledHW: + case eTraceEventDisabledSW: + break; + case eTraceEventSyncPoint: + m_j.attribute("syncPointMetadata", item.sync_point_metadata); + break; + } + } + + void DumpInstruction(const TraceDumper::TraceItem &item) { + m_j.attribute("loadAddress", formatv("{0:x}", item.load_address)); + if (item.symbol_info) { + m_j.attribute("module", ToOptionalString(GetModuleName(item))); + m_j.attribute( + "symbol", + ToOptionalString(item.symbol_info->sc.GetFunctionName().AsCString())); + + if (lldb::InstructionSP instruction = item.symbol_info->instruction) { + ExecutionContext exe_ctx = item.symbol_info->exe_ctx; + m_j.attribute("mnemonic", + ToOptionalString(instruction->GetMnemonic(&exe_ctx))); + if (m_options.show_control_flow_kind) { + lldb::InstructionControlFlowKind instruction_control_flow_kind = + instruction->GetControlFlowKind(&exe_ctx); + m_j.attribute("controlFlowKind", + ToOptionalString( + Instruction::GetNameForInstructionControlFlowKind( + instruction_control_flow_kind))); + } + } + + if (IsLineEntryValid(item.symbol_info->sc.line_entry)) { + m_j.attribute( + "source", + ToOptionalString( + item.symbol_info->sc.line_entry.GetFile().GetPath().c_str())); + m_j.attribute("line", item.symbol_info->sc.line_entry.line); + m_j.attribute("column", item.symbol_info->sc.line_entry.column); + } + } + } + + void TraceItem(const TraceDumper::TraceItem &item) override { + m_j.object([&] { + m_j.attribute("id", item.id); + if (m_options.show_timestamps) + m_j.attribute("timestamp_ns", item.timestamp + ? std::optional<std::string>( + std::to_string(*item.timestamp)) + : std::nullopt); + + if (item.event) { + DumpEvent(item); + } else if (item.error) { + m_j.attribute("error", *item.error); + } else { + DumpInstruction(item); + } + }); + } + +private: + Stream &m_s; + TraceDumperOptions m_options; + json::OStream m_j; +}; + +static std::unique_ptr<TraceDumper::OutputWriter> +CreateWriter(Stream &s, const TraceDumperOptions &options, Thread &thread) { + if (options.json) + return std::unique_ptr<TraceDumper::OutputWriter>( + new OutputWriterJSON(s, options)); + else + return std::unique_ptr<TraceDumper::OutputWriter>( + new OutputWriterCLI(s, options, thread)); +} + +TraceDumper::TraceDumper(lldb::TraceCursorSP cursor_sp, Stream &s, + const TraceDumperOptions &options) + : m_cursor_sp(std::move(cursor_sp)), m_options(options), + m_writer_up(CreateWriter( + s, m_options, *m_cursor_sp->GetExecutionContextRef().GetThreadSP())) { + + if (m_options.id) + m_cursor_sp->GoToId(*m_options.id); + else if (m_options.forwards) + m_cursor_sp->Seek(0, lldb::eTraceCursorSeekTypeBeginning); + else + m_cursor_sp->Seek(0, lldb::eTraceCursorSeekTypeEnd); + + m_cursor_sp->SetForwards(m_options.forwards); + if (m_options.skip) { + m_cursor_sp->Seek((m_options.forwards ? 1 : -1) * *m_options.skip, + lldb::eTraceCursorSeekTypeCurrent); + } +} + +TraceDumper::TraceItem TraceDumper::CreatRawTraceItem() { + TraceItem item = {}; + item.id = m_cursor_sp->GetId(); + + if (m_options.show_timestamps) + item.timestamp = m_cursor_sp->GetWallClockTime(); + return item; +} + +/// Find the symbol context for the given address reusing the previous +/// instruction's symbol context when possible. +static SymbolContext +CalculateSymbolContext(const Address &address, + const SymbolContext &prev_symbol_context) { + lldb_private::AddressRange range; + if (prev_symbol_context.GetAddressRange(eSymbolContextEverything, 0, + /*inline_block_range*/ true, range) && + range.Contains(address)) + return prev_symbol_context; + + SymbolContext sc; + address.CalculateSymbolContext(&sc, eSymbolContextEverything); + return sc; +} + +/// Find the disassembler for the given address reusing the previous +/// instruction's disassembler when possible. +static std::tuple<DisassemblerSP, InstructionSP> +CalculateDisass(const TraceDumper::SymbolInfo &symbol_info, + const TraceDumper::SymbolInfo &prev_symbol_info, + const ExecutionContext &exe_ctx) { + if (prev_symbol_info.disassembler) { + if (InstructionSP instruction = + prev_symbol_info.disassembler->GetInstructionList() + .GetInstructionAtAddress(symbol_info.address)) + return std::make_tuple(prev_symbol_info.disassembler, instruction); + } + + if (symbol_info.sc.function) { + if (DisassemblerSP disassembler = + symbol_info.sc.function->GetInstructions(exe_ctx, nullptr)) { + if (InstructionSP instruction = + disassembler->GetInstructionList().GetInstructionAtAddress( + symbol_info.address)) + return std::make_tuple(disassembler, instruction); + } + } + // We fallback to a single instruction disassembler + Target &target = exe_ctx.GetTargetRef(); + const ArchSpec arch = target.GetArchitecture(); + lldb_private::AddressRange range(symbol_info.address, + arch.GetMaximumOpcodeByteSize()); + DisassemblerSP disassembler = + Disassembler::DisassembleRange(arch, /*plugin_name*/ nullptr, + /*flavor*/ nullptr, target, range); + return std::make_tuple( + disassembler, + disassembler ? disassembler->GetInstructionList().GetInstructionAtAddress( + symbol_info.address) + : InstructionSP()); +} + +static TraceDumper::SymbolInfo +CalculateSymbolInfo(const ExecutionContext &exe_ctx, lldb::addr_t load_address, + const TraceDumper::SymbolInfo &prev_symbol_info) { + TraceDumper::SymbolInfo symbol_info; + symbol_info.exe_ctx = exe_ctx; + symbol_info.address.SetLoadAddress(load_address, exe_ctx.GetTargetPtr()); + symbol_info.sc = + CalculateSymbolContext(symbol_info.address, prev_symbol_info.sc); + std::tie(symbol_info.disassembler, symbol_info.instruction) = + CalculateDisass(symbol_info, prev_symbol_info, exe_ctx); + return symbol_info; +} + +std::optional<lldb::user_id_t> TraceDumper::DumpInstructions(size_t count) { + ThreadSP thread_sp = m_cursor_sp->GetExecutionContextRef().GetThreadSP(); + + SymbolInfo prev_symbol_info; + std::optional<lldb::user_id_t> last_id; + + ExecutionContext exe_ctx; + thread_sp->GetProcess()->GetTarget().CalculateExecutionContext(exe_ctx); + + for (size_t insn_seen = 0; insn_seen < count && m_cursor_sp->HasValue(); + m_cursor_sp->Next()) { + + last_id = m_cursor_sp->GetId(); + TraceItem item = CreatRawTraceItem(); + + if (m_cursor_sp->IsEvent() && m_options.show_events) { + item.event = m_cursor_sp->GetEventType(); + switch (*item.event) { + case eTraceEventCPUChanged: + item.cpu_id = m_cursor_sp->GetCPU(); + break; + case eTraceEventHWClockTick: + item.hw_clock = m_cursor_sp->GetHWClock(); + break; + case eTraceEventDisabledHW: + case eTraceEventDisabledSW: + break; + case eTraceEventSyncPoint: + item.sync_point_metadata = m_cursor_sp->GetSyncPointMetadata(); + break; + } + m_writer_up->TraceItem(item); + } else if (m_cursor_sp->IsError()) { + item.error = m_cursor_sp->GetError(); + m_writer_up->TraceItem(item); + } else if (m_cursor_sp->IsInstruction() && !m_options.only_events) { + insn_seen++; + item.load_address = m_cursor_sp->GetLoadAddress(); + + if (!m_options.raw) { + SymbolInfo symbol_info = + CalculateSymbolInfo(exe_ctx, item.load_address, prev_symbol_info); + item.prev_symbol_info = prev_symbol_info; + item.symbol_info = symbol_info; + prev_symbol_info = symbol_info; + } + m_writer_up->TraceItem(item); + } + } + if (!m_cursor_sp->HasValue()) + m_writer_up->NoMoreData(); + return last_id; +} + +void TraceDumper::FunctionCall::TracedSegment::AppendInsn( + const TraceCursorSP &cursor_sp, + const TraceDumper::SymbolInfo &symbol_info) { + m_last_insn_id = cursor_sp->GetId(); + m_last_symbol_info = symbol_info; +} + +lldb::user_id_t +TraceDumper::FunctionCall::TracedSegment::GetFirstInstructionID() const { + return m_first_insn_id; +} + +lldb::user_id_t +TraceDumper::FunctionCall::TracedSegment::GetLastInstructionID() const { + return m_last_insn_id; +} + +void TraceDumper::FunctionCall::TracedSegment::IfNestedCall( + std::function<void(const FunctionCall &function_call)> callback) const { + if (m_nested_call) + callback(*m_nested_call); +} + +const TraceDumper::FunctionCall & +TraceDumper::FunctionCall::TracedSegment::GetOwningCall() const { + return m_owning_call; +} + +TraceDumper::FunctionCall & +TraceDumper::FunctionCall::TracedSegment::CreateNestedCall( + const TraceCursorSP &cursor_sp, + const TraceDumper::SymbolInfo &symbol_info) { + m_nested_call = std::make_unique<FunctionCall>(cursor_sp, symbol_info); + m_nested_call->SetParentCall(m_owning_call); + return *m_nested_call; +} + +const TraceDumper::SymbolInfo & +TraceDumper::FunctionCall::TracedSegment::GetFirstInstructionSymbolInfo() + const { + return m_first_symbol_info; +} + +const TraceDumper::SymbolInfo & +TraceDumper::FunctionCall::TracedSegment::GetLastInstructionSymbolInfo() const { + return m_last_symbol_info; +} + +const TraceDumper::FunctionCall & +TraceDumper::FunctionCall::UntracedPrefixSegment::GetNestedCall() const { + return *m_nested_call; +} + +TraceDumper::FunctionCall::FunctionCall( + const TraceCursorSP &cursor_sp, + const TraceDumper::SymbolInfo &symbol_info) { + m_is_error = cursor_sp->IsError(); + AppendSegment(cursor_sp, symbol_info); +} + +void TraceDumper::FunctionCall::AppendSegment( + const TraceCursorSP &cursor_sp, + const TraceDumper::SymbolInfo &symbol_info) { + m_traced_segments.emplace_back(cursor_sp, symbol_info, *this); +} + +const TraceDumper::SymbolInfo & +TraceDumper::FunctionCall::GetSymbolInfo() const { + return m_traced_segments.back().GetLastInstructionSymbolInfo(); +} + +bool TraceDumper::FunctionCall::IsError() const { return m_is_error; } + +const std::deque<TraceDumper::FunctionCall::TracedSegment> & +TraceDumper::FunctionCall::GetTracedSegments() const { + return m_traced_segments; +} + +TraceDumper::FunctionCall::TracedSegment & +TraceDumper::FunctionCall::GetLastTracedSegment() { + return m_traced_segments.back(); +} + +const std::optional<TraceDumper::FunctionCall::UntracedPrefixSegment> & +TraceDumper::FunctionCall::GetUntracedPrefixSegment() const { + return m_untraced_prefix_segment; +} + +void TraceDumper::FunctionCall::SetUntracedPrefixSegment( + TraceDumper::FunctionCallUP &&nested_call) { + m_untraced_prefix_segment.emplace(std::move(nested_call)); +} + +TraceDumper::FunctionCall *TraceDumper::FunctionCall::GetParentCall() const { + return m_parent_call; +} + +void TraceDumper::FunctionCall::SetParentCall( + TraceDumper::FunctionCall &parent_call) { + m_parent_call = &parent_call; +} + +/// Given an instruction that happens after a return, find the ancestor function +/// call that owns it. If this ancestor doesn't exist, create a new ancestor and +/// make it the root of the tree. +/// +/// \param[in] last_function_call +/// The function call that performs the return. +/// +/// \param[in] symbol_info +/// The symbol information of the instruction after the return. +/// +/// \param[in] cursor_sp +/// The cursor pointing to the instruction after the return. +/// +/// \param[in,out] roots +/// The object owning the roots. It might be modified if a new root needs to +/// be created. +/// +/// \return +/// A reference to the function call that owns the new instruction +static TraceDumper::FunctionCall &AppendReturnedInstructionToFunctionCallForest( + TraceDumper::FunctionCall &last_function_call, + const TraceDumper::SymbolInfo &symbol_info, const TraceCursorSP &cursor_sp, + std::vector<TraceDumper::FunctionCallUP> &roots) { + + // We omit the current node because we can't return to itself. + TraceDumper::FunctionCall *ancestor = last_function_call.GetParentCall(); + + for (; ancestor; ancestor = ancestor->GetParentCall()) { + // This loop traverses the tree until it finds a call that we can return to. + if (IsSameInstructionSymbolContext(ancestor->GetSymbolInfo(), symbol_info, + /*check_source_line_info=*/false)) { + // We returned to this symbol, so we are assuming we are returning there + // Note: If this is not robust enough, we should actually check if we + // returning to the instruction that follows the last instruction from + // that call, as that's the behavior of CALL instructions. + ancestor->AppendSegment(cursor_sp, symbol_info); + return *ancestor; + } + } + + // We didn't find the call we were looking for, so we now create a synthetic + // one that will contain the new instruction in its first traced segment. + TraceDumper::FunctionCallUP new_root = + std::make_unique<TraceDumper::FunctionCall>(cursor_sp, symbol_info); + // This new root will own the previous root through an untraced prefix segment. + new_root->SetUntracedPrefixSegment(std::move(roots.back())); + roots.pop_back(); + // We update the roots container to point to the new root + roots.emplace_back(std::move(new_root)); + return *roots.back(); +} + +/// Append an instruction to a function call forest. The new instruction might +/// be appended to the current segment, to a new nest call, or return to an +/// ancestor call. +/// +/// \param[in] exe_ctx +/// The exeuction context of the traced thread. +/// +/// \param[in] last_function_call +/// The chronologically most recent function call before the new instruction. +/// +/// \param[in] prev_symbol_info +/// The symbol information of the previous instruction in the trace. +/// +/// \param[in] symbol_info +/// The symbol information of the new instruction. +/// +/// \param[in] cursor_sp +/// The cursor pointing to the new instruction. +/// +/// \param[in,out] roots +/// The object owning the roots. It might be modified if a new root needs to +/// be created. +/// +/// \return +/// A reference to the function call that owns the new instruction. +static TraceDumper::FunctionCall &AppendInstructionToFunctionCallForest( + const ExecutionContext &exe_ctx, + TraceDumper::FunctionCall *last_function_call, + const TraceDumper::SymbolInfo &prev_symbol_info, + const TraceDumper::SymbolInfo &symbol_info, const TraceCursorSP &cursor_sp, + std::vector<TraceDumper::FunctionCallUP> &roots) { + if (!last_function_call || last_function_call->IsError()) { + // We create a brand new root + roots.emplace_back( + std::make_unique<TraceDumper::FunctionCall>(cursor_sp, symbol_info)); + return *roots.back(); + } + + lldb_private::AddressRange range; + if (symbol_info.sc.GetAddressRange( + eSymbolContextBlock | eSymbolContextFunction | eSymbolContextSymbol, + 0, /*inline_block_range*/ true, range)) { + if (range.GetBaseAddress() == symbol_info.address) { + // Our instruction is the first instruction of a function. This has + // to be a call. This should also identify if a trampoline or the linker + // is making a call using a non-CALL instruction. + return last_function_call->GetLastTracedSegment().CreateNestedCall( + cursor_sp, symbol_info); + } + } + if (IsSameInstructionSymbolContext(prev_symbol_info, symbol_info, + /*check_source_line_info=*/false)) { + // We are still in the same function. This can't be a call because otherwise + // we would be in the first instruction of the symbol. + last_function_call->GetLastTracedSegment().AppendInsn(cursor_sp, + symbol_info); + return *last_function_call; + } + // Now we are in a different symbol. Let's see if this is a return or a + // call + const InstructionSP &insn = last_function_call->GetLastTracedSegment() + .GetLastInstructionSymbolInfo() + .instruction; + InstructionControlFlowKind insn_kind = + insn ? insn->GetControlFlowKind(&exe_ctx) + : eInstructionControlFlowKindOther; + + switch (insn_kind) { + case lldb::eInstructionControlFlowKindCall: + case lldb::eInstructionControlFlowKindFarCall: { + // This is a regular call + return last_function_call->GetLastTracedSegment().CreateNestedCall( + cursor_sp, symbol_info); + } + case lldb::eInstructionControlFlowKindFarReturn: + case lldb::eInstructionControlFlowKindReturn: { + // We should have caught most trampolines and linker functions earlier, so + // let's assume this is a regular return. + return AppendReturnedInstructionToFunctionCallForest( + *last_function_call, symbol_info, cursor_sp, roots); + } + default: + // we changed symbols not using a call or return and we are not in the + // beginning of a symbol, so this should be something very artificial + // or maybe a jump to some label in the middle of it section. + + // We first check if it's a return from an inline method + if (prev_symbol_info.sc.block && + prev_symbol_info.sc.block->GetContainingInlinedBlock()) { + return AppendReturnedInstructionToFunctionCallForest( + *last_function_call, symbol_info, cursor_sp, roots); + } + // Now We assume it's a call. We should revisit this in the future. + // Ideally we should be able to decide whether to create a new tree, + // or go deeper or higher in the stack. + return last_function_call->GetLastTracedSegment().CreateNestedCall( + cursor_sp, symbol_info); + } +} + +/// Append an error to a function call forest. The new error might be appended +/// to the current segment if it contains errors or will create a new root. +/// +/// \param[in] last_function_call +/// The chronologically most recent function call before the new error. +/// +/// \param[in] cursor_sp +/// The cursor pointing to the new error. +/// +/// \param[in,out] roots +/// The object owning the roots. It might be modified if a new root needs to +/// be created. +/// +/// \return +/// A reference to the function call that owns the new error. +TraceDumper::FunctionCall &AppendErrorToFunctionCallForest( + TraceDumper::FunctionCall *last_function_call, TraceCursorSP &cursor_sp, + std::vector<TraceDumper::FunctionCallUP> &roots) { + if (last_function_call && last_function_call->IsError()) { + last_function_call->GetLastTracedSegment().AppendInsn( + cursor_sp, TraceDumper::SymbolInfo{}); + return *last_function_call; + } else { + roots.emplace_back(std::make_unique<TraceDumper::FunctionCall>( + cursor_sp, TraceDumper::SymbolInfo{})); + return *roots.back(); + } +} + +static std::vector<TraceDumper::FunctionCallUP> +CreateFunctionCallForest(TraceCursorSP &cursor_sp, + const ExecutionContext &exe_ctx) { + + std::vector<TraceDumper::FunctionCallUP> roots; + TraceDumper::SymbolInfo prev_symbol_info; + + TraceDumper::FunctionCall *last_function_call = nullptr; + + for (; cursor_sp->HasValue(); cursor_sp->Next()) { + if (cursor_sp->IsError()) { + last_function_call = &AppendErrorToFunctionCallForest(last_function_call, + cursor_sp, roots); + prev_symbol_info = {}; + } else if (cursor_sp->IsInstruction()) { + TraceDumper::SymbolInfo symbol_info = CalculateSymbolInfo( + exe_ctx, cursor_sp->GetLoadAddress(), prev_symbol_info); + + last_function_call = &AppendInstructionToFunctionCallForest( + exe_ctx, last_function_call, prev_symbol_info, symbol_info, cursor_sp, + roots); + prev_symbol_info = symbol_info; + } else if (cursor_sp->GetEventType() == eTraceEventCPUChanged) { + // TODO: In case of a CPU change, we create a new root because we haven't + // investigated yet if a call tree can safely continue or if interrupts + // could have polluted the original call tree. + last_function_call = nullptr; + prev_symbol_info = {}; + } + } + + return roots; +} + +void TraceDumper::DumpFunctionCalls() { + ThreadSP thread_sp = m_cursor_sp->GetExecutionContextRef().GetThreadSP(); + ExecutionContext exe_ctx; + thread_sp->GetProcess()->GetTarget().CalculateExecutionContext(exe_ctx); + + m_writer_up->FunctionCallForest( + CreateFunctionCallForest(m_cursor_sp, exe_ctx)); +} diff --git a/contrib/llvm-project/lldb/source/Target/TraceExporter.cpp b/contrib/llvm-project/lldb/source/Target/TraceExporter.cpp new file mode 100644 index 000000000000..8c925aa495b0 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/TraceExporter.cpp @@ -0,0 +1,31 @@ +//===-- TraceExporter.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 "lldb/Target/TraceExporter.h" + +#include "lldb/Core/PluginManager.h" + +using namespace lldb; +using namespace lldb_private; +using namespace llvm; + +static Error createInvalidPlugInError(StringRef plugin_name) { + return createStringError( + std::errc::invalid_argument, + "no trace expoter plug-in matches the specified type: \"%s\"", + plugin_name.data()); +} + +Expected<lldb::TraceExporterUP> +TraceExporter::FindPlugin(llvm::StringRef name) { + if (auto create_callback = + PluginManager::GetTraceExporterCreateCallback(name)) + return create_callback(); + + return createInvalidPlugInError(name); +} diff --git a/contrib/llvm-project/lldb/source/Target/UnixSignals.cpp b/contrib/llvm-project/lldb/source/Target/UnixSignals.cpp new file mode 100644 index 000000000000..e3c7a83ece07 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/UnixSignals.cpp @@ -0,0 +1,398 @@ +//===-- UnixSignals.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 "lldb/Target/UnixSignals.h" +#include "Plugins/Process/Utility/FreeBSDSignals.h" +#include "Plugins/Process/Utility/LinuxSignals.h" +#include "Plugins/Process/Utility/NetBSDSignals.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Utility/ArchSpec.h" +#include <optional> +#include <sstream> + +using namespace lldb_private; +using namespace llvm; + +UnixSignals::Signal::Signal(llvm::StringRef name, bool default_suppress, + bool default_stop, bool default_notify, + llvm::StringRef description, llvm::StringRef alias) + : m_name(name), m_alias(alias), m_description(description), + m_suppress(default_suppress), m_stop(default_stop), + m_notify(default_notify), m_default_suppress(default_suppress), + m_default_stop(default_stop), m_default_notify(default_notify) {} + +lldb::UnixSignalsSP UnixSignals::Create(const ArchSpec &arch) { + const auto &triple = arch.GetTriple(); + switch (triple.getOS()) { + case llvm::Triple::Linux: + return std::make_shared<LinuxSignals>(); + case llvm::Triple::FreeBSD: + case llvm::Triple::OpenBSD: + return std::make_shared<FreeBSDSignals>(); + case llvm::Triple::NetBSD: + return std::make_shared<NetBSDSignals>(); + default: + return std::make_shared<UnixSignals>(); + } +} + +lldb::UnixSignalsSP UnixSignals::CreateForHost() { + static lldb::UnixSignalsSP s_unix_signals_sp = + Create(HostInfo::GetArchitecture()); + return s_unix_signals_sp; +} + +// UnixSignals constructor +UnixSignals::UnixSignals() { Reset(); } + +UnixSignals::UnixSignals(const UnixSignals &rhs) : m_signals(rhs.m_signals) {} + +UnixSignals::~UnixSignals() = default; + +void UnixSignals::Reset() { + // This builds one standard set of Unix Signals. If yours aren't quite in + // this order, you can either subclass this class, and use Add & Remove to + // change them or you can subclass and build them afresh in your constructor. + // + // Note: the signals below are the Darwin signals. Do not change these! + + m_signals.clear(); + + // clang-format off + // SIGNO NAME SUPPRESS STOP NOTIFY DESCRIPTION + // ====== ============== ======== ====== ====== =================================================== + AddSignal(1, "SIGHUP", false, true, true, "hangup"); + AddSignal(2, "SIGINT", true, true, true, "interrupt"); + AddSignal(3, "SIGQUIT", false, true, true, "quit"); + AddSignal(4, "SIGILL", false, true, true, "illegal instruction"); + AddSignal(5, "SIGTRAP", true, true, true, "trace trap (not reset when caught)"); + AddSignal(6, "SIGABRT", false, true, true, "abort()"); + AddSignal(7, "SIGEMT", false, true, true, "pollable event"); + AddSignal(8, "SIGFPE", false, true, true, "floating point exception"); + AddSignal(9, "SIGKILL", false, true, true, "kill"); + AddSignal(10, "SIGBUS", false, true, true, "bus error"); + AddSignal(11, "SIGSEGV", false, true, true, "segmentation violation"); + AddSignal(12, "SIGSYS", false, true, true, "bad argument to system call"); + AddSignal(13, "SIGPIPE", false, false, false, "write on a pipe with no one to read it"); + AddSignal(14, "SIGALRM", false, false, false, "alarm clock"); + AddSignal(15, "SIGTERM", false, true, true, "software termination signal from kill"); + AddSignal(16, "SIGURG", false, false, false, "urgent condition on IO channel"); + AddSignal(17, "SIGSTOP", true, true, true, "sendable stop signal not from tty"); + AddSignal(18, "SIGTSTP", false, true, true, "stop signal from tty"); + AddSignal(19, "SIGCONT", false, false, true, "continue a stopped process"); + AddSignal(20, "SIGCHLD", false, false, false, "to parent on child stop or exit"); + AddSignal(21, "SIGTTIN", false, true, true, "to readers process group upon background tty read"); + AddSignal(22, "SIGTTOU", false, true, true, "to readers process group upon background tty write"); + AddSignal(23, "SIGIO", false, false, false, "input/output possible signal"); + AddSignal(24, "SIGXCPU", false, true, true, "exceeded CPU time limit"); + AddSignal(25, "SIGXFSZ", false, true, true, "exceeded file size limit"); + AddSignal(26, "SIGVTALRM", false, false, false, "virtual time alarm"); + AddSignal(27, "SIGPROF", false, false, false, "profiling time alarm"); + AddSignal(28, "SIGWINCH", false, false, false, "window size changes"); + AddSignal(29, "SIGINFO", false, true, true, "information request"); + AddSignal(30, "SIGUSR1", false, true, true, "user defined signal 1"); + AddSignal(31, "SIGUSR2", false, true, true, "user defined signal 2"); + // clang-format on +} + +void UnixSignals::AddSignal(int signo, llvm::StringRef name, + bool default_suppress, bool default_stop, + bool default_notify, llvm::StringRef description, + llvm::StringRef alias) { + Signal new_signal(name, default_suppress, default_stop, default_notify, + description, alias); + m_signals.insert(std::make_pair(signo, new_signal)); + ++m_version; +} + +void UnixSignals::AddSignalCode(int signo, int code, + const llvm::StringLiteral description, + SignalCodePrintOption print_option) { + collection::iterator signal = m_signals.find(signo); + assert(signal != m_signals.end() && + "Tried to add code to signal that does not exist."); + signal->second.m_codes.insert( + std::pair{code, SignalCode{description, print_option}}); + ++m_version; +} + +void UnixSignals::RemoveSignal(int signo) { + collection::iterator pos = m_signals.find(signo); + if (pos != m_signals.end()) + m_signals.erase(pos); + ++m_version; +} + +llvm::StringRef UnixSignals::GetSignalAsStringRef(int32_t signo) const { + const auto pos = m_signals.find(signo); + if (pos == m_signals.end()) + return {}; + return pos->second.m_name; +} + +std::string +UnixSignals::GetSignalDescription(int32_t signo, std::optional<int32_t> code, + std::optional<lldb::addr_t> addr, + std::optional<lldb::addr_t> lower, + std::optional<lldb::addr_t> upper) const { + std::string str; + + collection::const_iterator pos = m_signals.find(signo); + if (pos != m_signals.end()) { + str = pos->second.m_name.str(); + + if (code) { + std::map<int32_t, SignalCode>::const_iterator cpos = + pos->second.m_codes.find(*code); + if (cpos != pos->second.m_codes.end()) { + const SignalCode &sc = cpos->second; + str += ": "; + if (sc.m_print_option != SignalCodePrintOption::Bounds) + str += sc.m_description.str(); + + std::stringstream strm; + switch (sc.m_print_option) { + case SignalCodePrintOption::None: + break; + case SignalCodePrintOption::Address: + if (addr) + strm << " (fault address: 0x" << std::hex << *addr << ")"; + break; + case SignalCodePrintOption::Bounds: + if (lower && upper && addr) { + if ((unsigned long)(*addr) < *lower) + strm << "lower bound violation "; + else + strm << "upper bound violation "; + + strm << "(fault address: 0x" << std::hex << *addr; + strm << ", lower bound: 0x" << std::hex << *lower; + strm << ", upper bound: 0x" << std::hex << *upper; + strm << ")"; + } else + strm << sc.m_description.str(); + + break; + } + str += strm.str(); + } + } + } + + return str; +} + +bool UnixSignals::SignalIsValid(int32_t signo) const { + return m_signals.find(signo) != m_signals.end(); +} + +llvm::StringRef UnixSignals::GetShortName(llvm::StringRef name) const { + return name.substr(3); // Remove "SIG" from name +} + +int32_t UnixSignals::GetSignalNumberFromName(const char *name) const { + llvm::StringRef name_ref(name); + + collection::const_iterator pos, end = m_signals.end(); + for (pos = m_signals.begin(); pos != end; pos++) { + if ((name_ref == pos->second.m_name) || (name_ref == pos->second.m_alias) || + (name_ref == GetShortName(pos->second.m_name)) || + (name_ref == GetShortName(pos->second.m_alias))) + return pos->first; + } + + int32_t signo; + if (llvm::to_integer(name, signo)) + return signo; + return LLDB_INVALID_SIGNAL_NUMBER; +} + +int32_t UnixSignals::GetFirstSignalNumber() const { + if (m_signals.empty()) + return LLDB_INVALID_SIGNAL_NUMBER; + + return (*m_signals.begin()).first; +} + +int32_t UnixSignals::GetNextSignalNumber(int32_t current_signal) const { + collection::const_iterator pos = m_signals.find(current_signal); + collection::const_iterator end = m_signals.end(); + if (pos == end) + return LLDB_INVALID_SIGNAL_NUMBER; + else { + pos++; + if (pos == end) + return LLDB_INVALID_SIGNAL_NUMBER; + else + return pos->first; + } +} + +bool UnixSignals::GetSignalInfo(int32_t signo, bool &should_suppress, + bool &should_stop, bool &should_notify) const { + const auto pos = m_signals.find(signo); + if (pos == m_signals.end()) + return false; + + const Signal &signal = pos->second; + should_suppress = signal.m_suppress; + should_stop = signal.m_stop; + should_notify = signal.m_notify; + return true; +} + +bool UnixSignals::GetShouldSuppress(int signo) const { + collection::const_iterator pos = m_signals.find(signo); + if (pos != m_signals.end()) + return pos->second.m_suppress; + return false; +} + +bool UnixSignals::SetShouldSuppress(int signo, bool value) { + collection::iterator pos = m_signals.find(signo); + if (pos != m_signals.end()) { + pos->second.m_suppress = value; + ++m_version; + return true; + } + return false; +} + +bool UnixSignals::SetShouldSuppress(const char *signal_name, bool value) { + const int32_t signo = GetSignalNumberFromName(signal_name); + if (signo != LLDB_INVALID_SIGNAL_NUMBER) + return SetShouldSuppress(signo, value); + return false; +} + +bool UnixSignals::GetShouldStop(int signo) const { + collection::const_iterator pos = m_signals.find(signo); + if (pos != m_signals.end()) + return pos->second.m_stop; + return false; +} + +bool UnixSignals::SetShouldStop(int signo, bool value) { + collection::iterator pos = m_signals.find(signo); + if (pos != m_signals.end()) { + pos->second.m_stop = value; + ++m_version; + return true; + } + return false; +} + +bool UnixSignals::SetShouldStop(const char *signal_name, bool value) { + const int32_t signo = GetSignalNumberFromName(signal_name); + if (signo != LLDB_INVALID_SIGNAL_NUMBER) + return SetShouldStop(signo, value); + return false; +} + +bool UnixSignals::GetShouldNotify(int signo) const { + collection::const_iterator pos = m_signals.find(signo); + if (pos != m_signals.end()) + return pos->second.m_notify; + return false; +} + +bool UnixSignals::SetShouldNotify(int signo, bool value) { + collection::iterator pos = m_signals.find(signo); + if (pos != m_signals.end()) { + pos->second.m_notify = value; + ++m_version; + return true; + } + return false; +} + +bool UnixSignals::SetShouldNotify(const char *signal_name, bool value) { + const int32_t signo = GetSignalNumberFromName(signal_name); + if (signo != LLDB_INVALID_SIGNAL_NUMBER) + return SetShouldNotify(signo, value); + return false; +} + +int32_t UnixSignals::GetNumSignals() const { return m_signals.size(); } + +int32_t UnixSignals::GetSignalAtIndex(int32_t index) const { + if (index < 0 || m_signals.size() <= static_cast<size_t>(index)) + return LLDB_INVALID_SIGNAL_NUMBER; + auto it = m_signals.begin(); + std::advance(it, index); + return it->first; +} + +uint64_t UnixSignals::GetVersion() const { return m_version; } + +std::vector<int32_t> +UnixSignals::GetFilteredSignals(std::optional<bool> should_suppress, + std::optional<bool> should_stop, + std::optional<bool> should_notify) { + std::vector<int32_t> result; + for (int32_t signo = GetFirstSignalNumber(); + signo != LLDB_INVALID_SIGNAL_NUMBER; + signo = GetNextSignalNumber(signo)) { + + bool signal_suppress = false; + bool signal_stop = false; + bool signal_notify = false; + GetSignalInfo(signo, signal_suppress, signal_stop, signal_notify); + + // If any of filtering conditions are not met, we move on to the next + // signal. + if (should_suppress && signal_suppress != *should_suppress) + continue; + + if (should_stop && signal_stop != *should_stop) + continue; + + if (should_notify && signal_notify != *should_notify) + continue; + + result.push_back(signo); + } + + return result; +} + +void UnixSignals::IncrementSignalHitCount(int signo) { + collection::iterator pos = m_signals.find(signo); + if (pos != m_signals.end()) + pos->second.m_hit_count += 1; +} + +json::Value UnixSignals::GetHitCountStatistics() const { + json::Array json_signals; + for (const auto &pair : m_signals) { + if (pair.second.m_hit_count > 0) + json_signals.emplace_back( + json::Object{{pair.second.m_name, pair.second.m_hit_count}}); + } + return std::move(json_signals); +} + +void UnixSignals::Signal::Reset(bool reset_stop, bool reset_notify, + bool reset_suppress) { + if (reset_stop) + m_stop = m_default_stop; + if (reset_notify) + m_notify = m_default_notify; + if (reset_suppress) + m_suppress = m_default_suppress; +} + +bool UnixSignals::ResetSignal(int32_t signo, bool reset_stop, + bool reset_notify, bool reset_suppress) { + auto elem = m_signals.find(signo); + if (elem == m_signals.end()) + return false; + (*elem).second.Reset(reset_stop, reset_notify, reset_suppress); + return true; +} + diff --git a/contrib/llvm-project/lldb/source/Target/UnwindAssembly.cpp b/contrib/llvm-project/lldb/source/Target/UnwindAssembly.cpp new file mode 100644 index 000000000000..75f2b328cef8 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/UnwindAssembly.cpp @@ -0,0 +1,31 @@ +//===-- UnwindAssembly.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 "lldb/Target/UnwindAssembly.h" +#include "lldb/Core/PluginInterface.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/lldb-private.h" + +using namespace lldb; +using namespace lldb_private; + +UnwindAssemblySP UnwindAssembly::FindPlugin(const ArchSpec &arch) { + UnwindAssemblyCreateInstance create_callback; + + for (uint32_t idx = 0; + (create_callback = PluginManager::GetUnwindAssemblyCreateCallbackAtIndex( + idx)) != nullptr; + ++idx) { + UnwindAssemblySP assembly_profiler_up(create_callback(arch)); + if (assembly_profiler_up) + return assembly_profiler_up; + } + return nullptr; +} + +UnwindAssembly::UnwindAssembly(const ArchSpec &arch) : m_arch(arch) {} diff --git a/contrib/llvm-project/lldb/source/Target/UnwindLLDB.cpp b/contrib/llvm-project/lldb/source/Target/UnwindLLDB.cpp new file mode 100644 index 000000000000..f43e940492b0 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/UnwindLLDB.cpp @@ -0,0 +1,525 @@ +//===-- UnwindLLDB.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 "lldb/Target/UnwindLLDB.h" +#include "lldb/Core/Module.h" +#include "lldb/Symbol/FuncUnwinders.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/UnwindPlan.h" +#include "lldb/Target/ABI.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/RegisterContextUnwind.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" + +using namespace lldb; +using namespace lldb_private; + +UnwindLLDB::UnwindLLDB(Thread &thread) + : Unwind(thread), m_frames(), m_unwind_complete(false), + m_user_supplied_trap_handler_functions() { + ProcessSP process_sp(thread.GetProcess()); + if (process_sp) { + Args args; + process_sp->GetTarget().GetUserSpecifiedTrapHandlerNames(args); + size_t count = args.GetArgumentCount(); + for (size_t i = 0; i < count; i++) { + const char *func_name = args.GetArgumentAtIndex(i); + m_user_supplied_trap_handler_functions.push_back(ConstString(func_name)); + } + } +} + +uint32_t UnwindLLDB::DoGetFrameCount() { + if (!m_unwind_complete) { +//#define DEBUG_FRAME_SPEED 1 +#if DEBUG_FRAME_SPEED +#define FRAME_COUNT 10000 + using namespace std::chrono; + auto time_value = steady_clock::now(); +#endif + if (!AddFirstFrame()) + return 0; + + ProcessSP process_sp(m_thread.GetProcess()); + ABI *abi = process_sp ? process_sp->GetABI().get() : nullptr; + + while (AddOneMoreFrame(abi)) { +#if DEBUG_FRAME_SPEED + if ((m_frames.size() % FRAME_COUNT) == 0) { + const auto now = steady_clock::now(); + const auto delta_t = now - time_value; + printf("%u frames in %.9f ms (%g frames/sec)\n", FRAME_COUNT, + duration<double, std::milli>(delta_t).count(), + (float)FRAME_COUNT / duration<double>(delta_t).count()); + time_value = now; + } +#endif + } + } + return m_frames.size(); +} + +bool UnwindLLDB::AddFirstFrame() { + if (m_frames.size() > 0) + return true; + + ProcessSP process_sp(m_thread.GetProcess()); + ABI *abi = process_sp ? process_sp->GetABI().get() : nullptr; + + // First, set up the 0th (initial) frame + CursorSP first_cursor_sp(new Cursor()); + RegisterContextLLDBSP reg_ctx_sp(new RegisterContextUnwind( + m_thread, RegisterContextLLDBSP(), first_cursor_sp->sctx, 0, *this)); + if (reg_ctx_sp.get() == nullptr) + goto unwind_done; + + if (!reg_ctx_sp->IsValid()) + goto unwind_done; + + if (!reg_ctx_sp->GetCFA(first_cursor_sp->cfa)) + goto unwind_done; + + if (!reg_ctx_sp->ReadPC(first_cursor_sp->start_pc)) + goto unwind_done; + + // Everything checks out, so release the auto pointer value and let the + // cursor own it in its shared pointer + first_cursor_sp->reg_ctx_lldb_sp = reg_ctx_sp; + m_frames.push_back(first_cursor_sp); + + // Update the Full Unwind Plan for this frame if not valid + UpdateUnwindPlanForFirstFrameIfInvalid(abi); + + return true; + +unwind_done: + Log *log = GetLog(LLDBLog::Unwind); + if (log) { + LLDB_LOGF(log, "th%d Unwind of this thread is complete.", + m_thread.GetIndexID()); + } + m_unwind_complete = true; + return false; +} + +UnwindLLDB::CursorSP UnwindLLDB::GetOneMoreFrame(ABI *abi) { + assert(m_frames.size() != 0 && + "Get one more frame called with empty frame list"); + + // If we've already gotten to the end of the stack, don't bother to try + // again... + if (m_unwind_complete) + return nullptr; + + Log *log = GetLog(LLDBLog::Unwind); + + CursorSP prev_frame = m_frames.back(); + uint32_t cur_idx = m_frames.size(); + + CursorSP cursor_sp(new Cursor()); + RegisterContextLLDBSP reg_ctx_sp(new RegisterContextUnwind( + m_thread, prev_frame->reg_ctx_lldb_sp, cursor_sp->sctx, cur_idx, *this)); + + uint64_t max_stack_depth = m_thread.GetMaxBacktraceDepth(); + + // We want to detect an unwind that cycles erroneously and stop backtracing. + // Don't want this maximum unwind limit to be too low -- if you have a + // backtrace with an "infinitely recursing" bug, it will crash when the stack + // blows out and the first 35,000 frames are uninteresting - it's the top + // most 5 frames that you actually care about. So you can't just cap the + // unwind at 10,000 or something. Realistically anything over around 200,000 + // is going to blow out the stack space. If we're still unwinding at that + // point, we're probably never going to finish. + if (cur_idx >= max_stack_depth) { + LLDB_LOGF(log, + "%*sFrame %d unwound too many frames, assuming unwind has " + "gone astray, stopping.", + cur_idx < 100 ? cur_idx : 100, "", cur_idx); + return nullptr; + } + + if (reg_ctx_sp.get() == nullptr) { + // If the RegisterContextUnwind has a fallback UnwindPlan, it will switch to + // that and return true. Subsequent calls to TryFallbackUnwindPlan() will + // return false. + if (prev_frame->reg_ctx_lldb_sp->TryFallbackUnwindPlan()) { + // TryFallbackUnwindPlan for prev_frame succeeded and updated + // reg_ctx_lldb_sp field of prev_frame. However, cfa field of prev_frame + // still needs to be updated. Hence updating it. + if (!(prev_frame->reg_ctx_lldb_sp->GetCFA(prev_frame->cfa))) + return nullptr; + + return GetOneMoreFrame(abi); + } + + LLDB_LOGF(log, "%*sFrame %d did not get a RegisterContext, stopping.", + cur_idx < 100 ? cur_idx : 100, "", cur_idx); + return nullptr; + } + + if (!reg_ctx_sp->IsValid()) { + // We failed to get a valid RegisterContext. See if the regctx below this + // on the stack has a fallback unwind plan it can use. Subsequent calls to + // TryFallbackUnwindPlan() will return false. + if (prev_frame->reg_ctx_lldb_sp->TryFallbackUnwindPlan()) { + // TryFallbackUnwindPlan for prev_frame succeeded and updated + // reg_ctx_lldb_sp field of prev_frame. However, cfa field of prev_frame + // still needs to be updated. Hence updating it. + if (!(prev_frame->reg_ctx_lldb_sp->GetCFA(prev_frame->cfa))) + return nullptr; + + return GetOneMoreFrame(abi); + } + + LLDB_LOGF(log, + "%*sFrame %d invalid RegisterContext for this frame, " + "stopping stack walk", + cur_idx < 100 ? cur_idx : 100, "", cur_idx); + return nullptr; + } + if (!reg_ctx_sp->GetCFA(cursor_sp->cfa)) { + // If the RegisterContextUnwind has a fallback UnwindPlan, it will switch to + // that and return true. Subsequent calls to TryFallbackUnwindPlan() will + // return false. + if (prev_frame->reg_ctx_lldb_sp->TryFallbackUnwindPlan()) { + // TryFallbackUnwindPlan for prev_frame succeeded and updated + // reg_ctx_lldb_sp field of prev_frame. However, cfa field of prev_frame + // still needs to be updated. Hence updating it. + if (!(prev_frame->reg_ctx_lldb_sp->GetCFA(prev_frame->cfa))) + return nullptr; + + return GetOneMoreFrame(abi); + } + + LLDB_LOGF(log, + "%*sFrame %d did not get CFA for this frame, stopping stack walk", + cur_idx < 100 ? cur_idx : 100, "", cur_idx); + return nullptr; + } + if (abi && !abi->CallFrameAddressIsValid(cursor_sp->cfa)) { + // On Mac OS X, the _sigtramp asynchronous signal trampoline frame may not + // have its (constructed) CFA aligned correctly -- don't do the abi + // alignment check for these. + if (!reg_ctx_sp->IsTrapHandlerFrame()) { + // See if we can find a fallback unwind plan for THIS frame. It may be + // that the UnwindPlan we're using for THIS frame was bad and gave us a + // bad CFA. If that's not it, then see if we can change the UnwindPlan + // for the frame below us ("NEXT") -- see if using that other UnwindPlan + // gets us a better unwind state. + if (!reg_ctx_sp->TryFallbackUnwindPlan() || + !reg_ctx_sp->GetCFA(cursor_sp->cfa) || + !abi->CallFrameAddressIsValid(cursor_sp->cfa)) { + if (prev_frame->reg_ctx_lldb_sp->TryFallbackUnwindPlan()) { + // TryFallbackUnwindPlan for prev_frame succeeded and updated + // reg_ctx_lldb_sp field of prev_frame. However, cfa field of + // prev_frame still needs to be updated. Hence updating it. + if (!(prev_frame->reg_ctx_lldb_sp->GetCFA(prev_frame->cfa))) + return nullptr; + + return GetOneMoreFrame(abi); + } + + LLDB_LOGF(log, + "%*sFrame %d did not get a valid CFA for this frame, " + "stopping stack walk", + cur_idx < 100 ? cur_idx : 100, "", cur_idx); + return nullptr; + } else { + LLDB_LOGF(log, + "%*sFrame %d had a bad CFA value but we switched the " + "UnwindPlan being used and got one that looks more " + "realistic.", + cur_idx < 100 ? cur_idx : 100, "", cur_idx); + } + } + } + if (!reg_ctx_sp->ReadPC(cursor_sp->start_pc)) { + // If the RegisterContextUnwind has a fallback UnwindPlan, it will switch to + // that and return true. Subsequent calls to TryFallbackUnwindPlan() will + // return false. + if (prev_frame->reg_ctx_lldb_sp->TryFallbackUnwindPlan()) { + // TryFallbackUnwindPlan for prev_frame succeeded and updated + // reg_ctx_lldb_sp field of prev_frame. However, cfa field of prev_frame + // still needs to be updated. Hence updating it. + if (!(prev_frame->reg_ctx_lldb_sp->GetCFA(prev_frame->cfa))) + return nullptr; + + return GetOneMoreFrame(abi); + } + + LLDB_LOGF(log, + "%*sFrame %d did not get PC for this frame, stopping stack walk", + cur_idx < 100 ? cur_idx : 100, "", cur_idx); + return nullptr; + } + + // Invalid code addresses should not appear on the stack *unless* we're + // directly below a trap handler frame (in this case, the invalid address is + // likely the cause of the trap). + if (abi && !abi->CodeAddressIsValid(cursor_sp->start_pc) && + !prev_frame->reg_ctx_lldb_sp->IsTrapHandlerFrame()) { + // If the RegisterContextUnwind has a fallback UnwindPlan, it will switch to + // that and return true. Subsequent calls to TryFallbackUnwindPlan() will + // return false. + if (prev_frame->reg_ctx_lldb_sp->TryFallbackUnwindPlan()) { + // TryFallbackUnwindPlan for prev_frame succeeded and updated + // reg_ctx_lldb_sp field of prev_frame. However, cfa field of prev_frame + // still needs to be updated. Hence updating it. + if (!(prev_frame->reg_ctx_lldb_sp->GetCFA(prev_frame->cfa))) + return nullptr; + + return GetOneMoreFrame(abi); + } + + LLDB_LOGF(log, "%*sFrame %d did not get a valid PC, stopping stack walk", + cur_idx < 100 ? cur_idx : 100, "", cur_idx); + return nullptr; + } + // Infinite loop where the current cursor is the same as the previous one... + if (prev_frame->start_pc == cursor_sp->start_pc && + prev_frame->cfa == cursor_sp->cfa) { + LLDB_LOGF(log, + "th%d pc of this frame is the same as the previous frame and " + "CFAs for both frames are identical -- stopping unwind", + m_thread.GetIndexID()); + return nullptr; + } + + cursor_sp->reg_ctx_lldb_sp = reg_ctx_sp; + return cursor_sp; +} + +void UnwindLLDB::UpdateUnwindPlanForFirstFrameIfInvalid(ABI *abi) { + // This function is called for First Frame only. + assert(m_frames.size() == 1 && "No. of cursor frames are not 1"); + + bool old_m_unwind_complete = m_unwind_complete; + CursorSP old_m_candidate_frame = m_candidate_frame; + + // Try to unwind 2 more frames using the Unwinder. It uses Full UnwindPlan + // and if Full UnwindPlan fails, then uses FallBack UnwindPlan. Also update + // the cfa of Frame 0 (if required). + AddOneMoreFrame(abi); + + // Remove all the frames added by above function as the purpose of using + // above function was just to check whether Unwinder of Frame 0 works or not. + for (uint32_t i = 1; i < m_frames.size(); i++) + m_frames.pop_back(); + + // Restore status after calling AddOneMoreFrame + m_unwind_complete = old_m_unwind_complete; + m_candidate_frame = old_m_candidate_frame; +} + +bool UnwindLLDB::AddOneMoreFrame(ABI *abi) { + Log *log = GetLog(LLDBLog::Unwind); + + // Frame zero is a little different + if (m_frames.empty()) + return false; + + // If we've already gotten to the end of the stack, don't bother to try + // again... + if (m_unwind_complete) + return false; + + CursorSP new_frame = m_candidate_frame; + if (new_frame == nullptr) + new_frame = GetOneMoreFrame(abi); + + if (new_frame == nullptr) { + LLDB_LOGF(log, "th%d Unwind of this thread is complete.", + m_thread.GetIndexID()); + m_unwind_complete = true; + return false; + } + + m_frames.push_back(new_frame); + + // If we can get one more frame further then accept that we get back a + // correct frame. + m_candidate_frame = GetOneMoreFrame(abi); + if (m_candidate_frame) + return true; + + // We can't go further from the frame returned by GetOneMore frame. Lets try + // to get a different frame with using the fallback unwind plan. + if (!m_frames[m_frames.size() - 2] + ->reg_ctx_lldb_sp->TryFallbackUnwindPlan()) { + // We don't have a valid fallback unwind plan. Accept the frame as it is. + // This is a valid situation when we are at the bottom of the stack. + return true; + } + + // Remove the possibly incorrect frame from the frame list and try to add a + // different one with the newly selected fallback unwind plan. + m_frames.pop_back(); + CursorSP new_frame_v2 = GetOneMoreFrame(abi); + if (new_frame_v2 == nullptr) { + // We haven't got a new frame from the fallback unwind plan. Accept the + // frame from the original unwind plan. This is a valid situation when we + // are at the bottom of the stack. + m_frames.push_back(new_frame); + return true; + } + + // Push the new frame to the list and try to continue from this frame. If we + // can get a new frame then accept it as the correct one. + m_frames.push_back(new_frame_v2); + m_candidate_frame = GetOneMoreFrame(abi); + if (m_candidate_frame) { + // If control reached here then TryFallbackUnwindPlan had succeeded for + // Cursor::m_frames[m_frames.size() - 2]. It also succeeded to Unwind next + // 2 frames i.e. m_frames[m_frames.size() - 1] and a frame after that. For + // Cursor::m_frames[m_frames.size() - 2], reg_ctx_lldb_sp field was already + // updated during TryFallbackUnwindPlan call above. However, cfa field + // still needs to be updated. Hence updating it here and then returning. + return m_frames[m_frames.size() - 2]->reg_ctx_lldb_sp->GetCFA( + m_frames[m_frames.size() - 2]->cfa); + } + + // The new frame hasn't helped in unwinding. Fall back to the original one as + // the default unwind plan is usually more reliable then the fallback one. + m_frames.pop_back(); + m_frames.push_back(new_frame); + return true; +} + +bool UnwindLLDB::DoGetFrameInfoAtIndex(uint32_t idx, addr_t &cfa, addr_t &pc, + bool &behaves_like_zeroth_frame) { + if (m_frames.size() == 0) { + if (!AddFirstFrame()) + return false; + } + + ProcessSP process_sp(m_thread.GetProcess()); + ABI *abi = process_sp ? process_sp->GetABI().get() : nullptr; + + while (idx >= m_frames.size() && AddOneMoreFrame(abi)) + ; + + if (idx < m_frames.size()) { + cfa = m_frames[idx]->cfa; + pc = m_frames[idx]->start_pc; + if (idx == 0) { + // Frame zero always behaves like it. + behaves_like_zeroth_frame = true; + } else if (m_frames[idx - 1]->reg_ctx_lldb_sp->IsTrapHandlerFrame()) { + // This could be an asynchronous signal, thus the + // pc might point to the interrupted instruction rather + // than a post-call instruction + behaves_like_zeroth_frame = true; + } else if (m_frames[idx]->reg_ctx_lldb_sp->IsTrapHandlerFrame()) { + // This frame may result from signal processing installing + // a pointer to the first byte of a signal-return trampoline + // in the return address slot of the frame below, so this + // too behaves like the zeroth frame (i.e. the pc might not + // be pointing just past a call in it) + behaves_like_zeroth_frame = true; + } else if (m_frames[idx]->reg_ctx_lldb_sp->BehavesLikeZerothFrame()) { + behaves_like_zeroth_frame = true; + } else { + behaves_like_zeroth_frame = false; + } + return true; + } + return false; +} + +lldb::RegisterContextSP +UnwindLLDB::DoCreateRegisterContextForFrame(StackFrame *frame) { + lldb::RegisterContextSP reg_ctx_sp; + uint32_t idx = frame->GetConcreteFrameIndex(); + + if (idx == 0) { + return m_thread.GetRegisterContext(); + } + + if (m_frames.size() == 0) { + if (!AddFirstFrame()) + return reg_ctx_sp; + } + + ProcessSP process_sp(m_thread.GetProcess()); + ABI *abi = process_sp ? process_sp->GetABI().get() : nullptr; + + while (idx >= m_frames.size()) { + if (!AddOneMoreFrame(abi)) + break; + } + + const uint32_t num_frames = m_frames.size(); + if (idx < num_frames) { + Cursor *frame_cursor = m_frames[idx].get(); + reg_ctx_sp = frame_cursor->reg_ctx_lldb_sp; + } + return reg_ctx_sp; +} + +UnwindLLDB::RegisterContextLLDBSP +UnwindLLDB::GetRegisterContextForFrameNum(uint32_t frame_num) { + RegisterContextLLDBSP reg_ctx_sp; + if (frame_num < m_frames.size()) + reg_ctx_sp = m_frames[frame_num]->reg_ctx_lldb_sp; + return reg_ctx_sp; +} + +bool UnwindLLDB::SearchForSavedLocationForRegister( + uint32_t lldb_regnum, lldb_private::UnwindLLDB::RegisterLocation ®loc, + uint32_t starting_frame_num, bool pc_reg) { + int64_t frame_num = starting_frame_num; + if (static_cast<size_t>(frame_num) >= m_frames.size()) + return false; + + // Never interrogate more than one level while looking for the saved pc + // value. If the value isn't saved by frame_num, none of the frames lower on + // the stack will have a useful value. + if (pc_reg) { + UnwindLLDB::RegisterSearchResult result; + result = m_frames[frame_num]->reg_ctx_lldb_sp->SavedLocationForRegister( + lldb_regnum, regloc); + return result == UnwindLLDB::RegisterSearchResult::eRegisterFound; + } + while (frame_num >= 0) { + UnwindLLDB::RegisterSearchResult result; + result = m_frames[frame_num]->reg_ctx_lldb_sp->SavedLocationForRegister( + lldb_regnum, regloc); + + // We descended down to the live register context aka stack frame 0 and are + // reading the value out of a live register. + if (result == UnwindLLDB::RegisterSearchResult::eRegisterFound && + regloc.type == + UnwindLLDB::RegisterLocation::eRegisterInLiveRegisterContext) { + return true; + } + + // If we have unwind instructions saying that register N is saved in + // register M in the middle of the stack (and N can equal M here, meaning + // the register was not used in this function), then change the register + // number we're looking for to M and keep looking for a concrete location + // down the stack, or an actual value from a live RegisterContext at frame + // 0. + if (result == UnwindLLDB::RegisterSearchResult::eRegisterFound && + regloc.type == UnwindLLDB::RegisterLocation::eRegisterInRegister && + frame_num > 0) { + result = UnwindLLDB::RegisterSearchResult::eRegisterNotFound; + lldb_regnum = regloc.location.register_number; + } + + if (result == UnwindLLDB::RegisterSearchResult::eRegisterFound) + return true; + if (result == UnwindLLDB::RegisterSearchResult::eRegisterIsVolatile) + return false; + frame_num--; + } + return false; +} diff --git a/contrib/llvm-project/lldb/source/Target/VerboseTrapFrameRecognizer.cpp b/contrib/llvm-project/lldb/source/Target/VerboseTrapFrameRecognizer.cpp new file mode 100644 index 000000000000..fe72c8aec570 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Target/VerboseTrapFrameRecognizer.cpp @@ -0,0 +1,122 @@ +#include "lldb/Target/VerboseTrapFrameRecognizer.h" + +#include "lldb/Core/Module.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/StackFrameRecognizer.h" +#include "lldb/Target/Target.h" + +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" + +#include "clang/CodeGen/ModuleBuilder.h" + +using namespace llvm; +using namespace lldb; +using namespace lldb_private; + +VerboseTrapRecognizedStackFrame::VerboseTrapRecognizedStackFrame( + StackFrameSP most_relevant_frame_sp, std::string stop_desc) + : m_most_relevant_frame(most_relevant_frame_sp) { + m_stop_desc = std::move(stop_desc); +} + +lldb::RecognizedStackFrameSP +VerboseTrapFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame_sp) { + if (frame_sp->GetFrameIndex()) + return {}; + + ThreadSP thread_sp = frame_sp->GetThread(); + ProcessSP process_sp = thread_sp->GetProcess(); + + StackFrameSP most_relevant_frame_sp = thread_sp->GetStackFrameAtIndex(1); + + if (!most_relevant_frame_sp) { + Log *log = GetLog(LLDBLog::Unwind); + LLDB_LOG( + log, + "Failed to find most relevant frame: Hit unwinding bound (1 frame)!"); + return {}; + } + + SymbolContext sc = frame_sp->GetSymbolContext(eSymbolContextEverything); + + if (!sc.block) + return {}; + + // The runtime error is set as the function name in the inlined function info + // of frame #0 by the compiler + const InlineFunctionInfo *inline_info = nullptr; + Block *inline_block = sc.block->GetContainingInlinedBlock(); + + if (!inline_block) + return {}; + + inline_info = sc.block->GetInlinedFunctionInfo(); + + if (!inline_info) + return {}; + + auto func_name = inline_info->GetName().GetStringRef(); + if (func_name.empty()) + return {}; + + static auto trap_regex = + llvm::Regex(llvm::formatv("^{0}\\$(.*)\\$(.*)$", ClangTrapPrefix).str()); + SmallVector<llvm::StringRef, 3> matches; + std::string regex_err_msg; + if (!trap_regex.match(func_name, &matches, ®ex_err_msg)) { + LLDB_LOGF(GetLog(LLDBLog::Unwind), + "Failed to parse match trap regex for '%s': %s", func_name.data(), + regex_err_msg.c_str()); + + return {}; + } + + // For `__clang_trap_msg$category$message$` we expect 3 matches: + // 1. entire string + // 2. category + // 3. message + if (matches.size() != 3) { + LLDB_LOGF(GetLog(LLDBLog::Unwind), + "Unexpected function name format. Expected '<trap prefix>$<trap " + "category>$<trap message>'$ but got: '%s'.", + func_name.data()); + + return {}; + } + + auto category = matches[1]; + auto message = matches[2]; + + std::string stop_reason = + category.empty() ? "<empty category>" : category.str(); + if (!message.empty()) { + stop_reason += ": "; + stop_reason += message.str(); + } + + return std::make_shared<VerboseTrapRecognizedStackFrame>( + most_relevant_frame_sp, std::move(stop_reason)); +} + +lldb::StackFrameSP VerboseTrapRecognizedStackFrame::GetMostRelevantFrame() { + return m_most_relevant_frame; +} + +namespace lldb_private { + +void RegisterVerboseTrapFrameRecognizer(Process &process) { + RegularExpressionSP module_regex_sp = nullptr; + auto symbol_regex_sp = std::make_shared<RegularExpression>( + llvm::formatv("^{0}", ClangTrapPrefix).str()); + + StackFrameRecognizerSP srf_recognizer_sp = + std::make_shared<VerboseTrapFrameRecognizer>(); + + process.GetTarget().GetFrameRecognizerManager().AddRecognizer( + srf_recognizer_sp, module_regex_sp, symbol_regex_sp, false); +} + +} // namespace lldb_private |