diff options
Diffstat (limited to 'contrib/llvm-project/lldb/source/Core')
53 files changed, 35579 insertions, 0 deletions
diff --git a/contrib/llvm-project/lldb/source/Core/Address.cpp b/contrib/llvm-project/lldb/source/Core/Address.cpp new file mode 100644 index 000000000000..5a4751bd5256 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/Address.cpp @@ -0,0 +1,1055 @@ +//===-- Address.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/Core/Address.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Declaration.h" +#include "lldb/Core/DumpDataExtractor.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleList.h" +#include "lldb/Core/Section.h" +#include "lldb/Symbol/Block.h" +#include "lldb/Symbol/LineEntry.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Symbol/SymbolVendor.h" +#include "lldb/Symbol/Symtab.h" +#include "lldb/Symbol/Type.h" +#include "lldb/Symbol/Variable.h" +#include "lldb/Symbol/VariableList.h" +#include "lldb/Target/ABI.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/ExecutionContextScope.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/AnsiTerminal.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/DataExtractor.h" +#include "lldb/Utility/Endian.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/Stream.h" +#include "lldb/Utility/StreamString.h" + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Compiler.h" +#include "llvm/TargetParser/Triple.h" + +#include <cstdint> +#include <memory> +#include <vector> + +#include <cassert> +#include <cinttypes> +#include <cstring> + +namespace lldb_private { +class CompileUnit; +} +namespace lldb_private { +class Function; +} + +using namespace lldb; +using namespace lldb_private; + +static size_t ReadBytes(ExecutionContextScope *exe_scope, + const Address &address, void *dst, size_t dst_len) { + if (exe_scope == nullptr) + return 0; + + TargetSP target_sp(exe_scope->CalculateTarget()); + if (target_sp) { + Status error; + bool force_live_memory = true; + return target_sp->ReadMemory(address, dst, dst_len, error, + force_live_memory); + } + return 0; +} + +static bool GetByteOrderAndAddressSize(ExecutionContextScope *exe_scope, + const Address &address, + ByteOrder &byte_order, + uint32_t &addr_size) { + byte_order = eByteOrderInvalid; + addr_size = 0; + if (exe_scope == nullptr) + return false; + + TargetSP target_sp(exe_scope->CalculateTarget()); + if (target_sp) { + byte_order = target_sp->GetArchitecture().GetByteOrder(); + addr_size = target_sp->GetArchitecture().GetAddressByteSize(); + } + + if (byte_order == eByteOrderInvalid || addr_size == 0) { + ModuleSP module_sp(address.GetModule()); + if (module_sp) { + byte_order = module_sp->GetArchitecture().GetByteOrder(); + addr_size = module_sp->GetArchitecture().GetAddressByteSize(); + } + } + return byte_order != eByteOrderInvalid && addr_size != 0; +} + +static uint64_t ReadUIntMax64(ExecutionContextScope *exe_scope, + const Address &address, uint32_t byte_size, + bool &success) { + uint64_t uval64 = 0; + if (exe_scope == nullptr || byte_size > sizeof(uint64_t)) { + success = false; + return 0; + } + uint64_t buf = 0; + + success = ReadBytes(exe_scope, address, &buf, byte_size) == byte_size; + if (success) { + ByteOrder byte_order = eByteOrderInvalid; + uint32_t addr_size = 0; + if (GetByteOrderAndAddressSize(exe_scope, address, byte_order, addr_size)) { + DataExtractor data(&buf, sizeof(buf), byte_order, addr_size); + lldb::offset_t offset = 0; + uval64 = data.GetU64(&offset); + } else + success = false; + } + return uval64; +} + +static bool ReadAddress(ExecutionContextScope *exe_scope, + const Address &address, uint32_t pointer_size, + Address &deref_so_addr) { + if (exe_scope == nullptr) + return false; + + bool success = false; + addr_t deref_addr = ReadUIntMax64(exe_scope, address, pointer_size, success); + if (success) { + ExecutionContext exe_ctx; + exe_scope->CalculateExecutionContext(exe_ctx); + // If we have any sections that are loaded, try and resolve using the + // section load list + Target *target = exe_ctx.GetTargetPtr(); + if (target && !target->GetSectionLoadList().IsEmpty()) { + if (target->GetSectionLoadList().ResolveLoadAddress(deref_addr, + deref_so_addr)) + return true; + } else { + // If we were not running, yet able to read an integer, we must have a + // module + ModuleSP module_sp(address.GetModule()); + + assert(module_sp); + if (module_sp->ResolveFileAddress(deref_addr, deref_so_addr)) + return true; + } + + // We couldn't make "deref_addr" into a section offset value, but we were + // able to read the address, so we return a section offset address with no + // section and "deref_addr" as the offset (address). + deref_so_addr.SetRawAddress(deref_addr); + return true; + } + return false; +} + +static bool DumpUInt(ExecutionContextScope *exe_scope, const Address &address, + uint32_t byte_size, Stream *strm) { + if (exe_scope == nullptr || byte_size == 0) + return false; + std::vector<uint8_t> buf(byte_size, 0); + + if (ReadBytes(exe_scope, address, &buf[0], buf.size()) == buf.size()) { + ByteOrder byte_order = eByteOrderInvalid; + uint32_t addr_size = 0; + if (GetByteOrderAndAddressSize(exe_scope, address, byte_order, addr_size)) { + DataExtractor data(&buf.front(), buf.size(), byte_order, addr_size); + + DumpDataExtractor(data, strm, + 0, // Start offset in "data" + eFormatHex, // Print as characters + buf.size(), // Size of item + 1, // Items count + UINT32_MAX, // num per line + LLDB_INVALID_ADDRESS, // base address + 0, // bitfield bit size + 0); // bitfield bit offset + + return true; + } + } + return false; +} + +static size_t ReadCStringFromMemory(ExecutionContextScope *exe_scope, + const Address &address, Stream *strm) { + if (exe_scope == nullptr) + return 0; + const size_t k_buf_len = 256; + char buf[k_buf_len + 1]; + buf[k_buf_len] = '\0'; // NULL terminate + + // Byte order and address size don't matter for C string dumping.. + DataExtractor data(buf, sizeof(buf), endian::InlHostByteOrder(), 4); + size_t total_len = 0; + size_t bytes_read; + Address curr_address(address); + strm->PutChar('"'); + while ((bytes_read = ReadBytes(exe_scope, curr_address, buf, k_buf_len)) > + 0) { + size_t len = strlen(buf); + if (len == 0) + break; + if (len > bytes_read) + len = bytes_read; + + DumpDataExtractor(data, strm, + 0, // Start offset in "data" + eFormatChar, // Print as characters + 1, // Size of item (1 byte for a char!) + len, // How many bytes to print? + UINT32_MAX, // num per line + LLDB_INVALID_ADDRESS, // base address + 0, // bitfield bit size + + 0); // bitfield bit offset + + total_len += bytes_read; + + if (len < k_buf_len) + break; + curr_address.SetOffset(curr_address.GetOffset() + bytes_read); + } + strm->PutChar('"'); + return total_len; +} + +Address::Address(lldb::addr_t abs_addr) : m_section_wp(), m_offset(abs_addr) {} + +Address::Address(addr_t address, const SectionList *section_list) + : m_section_wp() { + ResolveAddressUsingFileSections(address, section_list); +} + +const Address &Address::operator=(const Address &rhs) { + if (this != &rhs) { + m_section_wp = rhs.m_section_wp; + m_offset = rhs.m_offset; + } + return *this; +} + +bool Address::ResolveAddressUsingFileSections(addr_t file_addr, + const SectionList *section_list) { + if (section_list) { + SectionSP section_sp( + section_list->FindSectionContainingFileAddress(file_addr)); + m_section_wp = section_sp; + if (section_sp) { + assert(section_sp->ContainsFileAddress(file_addr)); + m_offset = file_addr - section_sp->GetFileAddress(); + return true; // Successfully transformed addr into a section offset + // address + } + } + m_offset = file_addr; + return false; // Failed to resolve this address to a section offset value +} + +/// if "addr_range_ptr" is not NULL, then fill in with the address range of the function. +bool Address::ResolveFunctionScope(SymbolContext &sym_ctx, + AddressRange *addr_range_ptr) { + constexpr SymbolContextItem resolve_scope = + eSymbolContextFunction | eSymbolContextSymbol; + + if (!(CalculateSymbolContext(&sym_ctx, resolve_scope) & resolve_scope)) { + if (addr_range_ptr) + addr_range_ptr->Clear(); + return false; + } + + if (!addr_range_ptr) + return true; + + return sym_ctx.GetAddressRange(resolve_scope, 0, false, *addr_range_ptr); +} + +ModuleSP Address::GetModule() const { + lldb::ModuleSP module_sp; + SectionSP section_sp(GetSection()); + if (section_sp) + module_sp = section_sp->GetModule(); + return module_sp; +} + +addr_t Address::GetFileAddress() const { + SectionSP section_sp(GetSection()); + if (section_sp) { + addr_t sect_file_addr = section_sp->GetFileAddress(); + if (sect_file_addr == LLDB_INVALID_ADDRESS) { + // Section isn't resolved, we can't return a valid file address + return LLDB_INVALID_ADDRESS; + } + // We have a valid file range, so we can return the file based address by + // adding the file base address to our offset + return sect_file_addr + m_offset; + } else if (SectionWasDeletedPrivate()) { + // Used to have a valid section but it got deleted so the offset doesn't + // mean anything without the section + return LLDB_INVALID_ADDRESS; + } + // No section, we just return the offset since it is the value in this case + return m_offset; +} + +addr_t Address::GetLoadAddress(Target *target) const { + SectionSP section_sp(GetSection()); + if (section_sp) { + if (target) { + addr_t sect_load_addr = section_sp->GetLoadBaseAddress(target); + + if (sect_load_addr != LLDB_INVALID_ADDRESS) { + // We have a valid file range, so we can return the file based address + // by adding the file base address to our offset + return sect_load_addr + m_offset; + } + } + } else if (SectionWasDeletedPrivate()) { + // Used to have a valid section but it got deleted so the offset doesn't + // mean anything without the section + return LLDB_INVALID_ADDRESS; + } else { + // We don't have a section so the offset is the load address + return m_offset; + } + // The section isn't resolved or an invalid target was passed in so we can't + // return a valid load address. + return LLDB_INVALID_ADDRESS; +} + +addr_t Address::GetCallableLoadAddress(Target *target, bool is_indirect) const { + addr_t code_addr = LLDB_INVALID_ADDRESS; + + if (is_indirect && target) { + ProcessSP processSP = target->GetProcessSP(); + Status error; + if (processSP) { + code_addr = processSP->ResolveIndirectFunction(this, error); + if (!error.Success()) + code_addr = LLDB_INVALID_ADDRESS; + } + } else { + code_addr = GetLoadAddress(target); + } + + if (code_addr == LLDB_INVALID_ADDRESS) + return code_addr; + + if (target) + return target->GetCallableLoadAddress(code_addr, GetAddressClass()); + return code_addr; +} + +bool Address::SetCallableLoadAddress(lldb::addr_t load_addr, Target *target) { + if (SetLoadAddress(load_addr, target)) { + if (target) + m_offset = target->GetCallableLoadAddress(m_offset, GetAddressClass()); + return true; + } + return false; +} + +addr_t Address::GetOpcodeLoadAddress(Target *target, + AddressClass addr_class) const { + addr_t code_addr = GetLoadAddress(target); + if (code_addr != LLDB_INVALID_ADDRESS) { + if (addr_class == AddressClass::eInvalid) + addr_class = GetAddressClass(); + code_addr = target->GetOpcodeLoadAddress(code_addr, addr_class); + } + return code_addr; +} + +bool Address::SetOpcodeLoadAddress(lldb::addr_t load_addr, Target *target, + AddressClass addr_class, + bool allow_section_end) { + if (SetLoadAddress(load_addr, target, allow_section_end)) { + if (target) { + if (addr_class == AddressClass::eInvalid) + addr_class = GetAddressClass(); + m_offset = target->GetOpcodeLoadAddress(m_offset, addr_class); + } + return true; + } + return false; +} + +bool Address::GetDescription(Stream &s, Target &target, + DescriptionLevel level) const { + assert(level == eDescriptionLevelBrief && + "Non-brief descriptions not implemented"); + LineEntry line_entry; + if (CalculateSymbolContextLineEntry(line_entry)) { + s.Printf(" (%s:%u:%u)", line_entry.GetFile().GetFilename().GetCString(), + line_entry.line, line_entry.column); + return true; + } + return false; +} + +bool Address::Dump(Stream *s, ExecutionContextScope *exe_scope, DumpStyle style, + DumpStyle fallback_style, uint32_t addr_size, + bool all_ranges, + std::optional<Stream::HighlightSettings> settings) const { + // If the section was nullptr, only load address is going to work unless we + // are trying to deref a pointer + SectionSP section_sp(GetSection()); + if (!section_sp && style != DumpStyleResolvedPointerDescription) + style = DumpStyleLoadAddress; + + ExecutionContext exe_ctx(exe_scope); + Target *target = exe_ctx.GetTargetPtr(); + // If addr_byte_size is UINT32_MAX, then determine the correct address byte + // size for the process or default to the size of addr_t + if (addr_size == UINT32_MAX) { + if (target) + addr_size = target->GetArchitecture().GetAddressByteSize(); + else + addr_size = sizeof(addr_t); + } + + Address so_addr; + switch (style) { + case DumpStyleInvalid: + return false; + + case DumpStyleSectionNameOffset: + if (section_sp) { + section_sp->DumpName(s->AsRawOstream()); + s->Printf(" + %" PRIu64, m_offset); + } else { + DumpAddress(s->AsRawOstream(), m_offset, addr_size); + } + break; + + case DumpStyleSectionPointerOffset: + s->Printf("(Section *)%p + ", static_cast<void *>(section_sp.get())); + DumpAddress(s->AsRawOstream(), m_offset, addr_size); + break; + + case DumpStyleModuleWithFileAddress: + if (section_sp) { + ModuleSP module_sp = section_sp->GetModule(); + if (module_sp) + s->Printf("%s[", module_sp->GetFileSpec().GetFilename().AsCString( + "<Unknown>")); + else + s->Printf("%s[", "<Unknown>"); + } + [[fallthrough]]; + case DumpStyleFileAddress: { + addr_t file_addr = GetFileAddress(); + if (file_addr == LLDB_INVALID_ADDRESS) { + if (fallback_style != DumpStyleInvalid) + return Dump(s, exe_scope, fallback_style, DumpStyleInvalid, addr_size); + return false; + } + DumpAddress(s->AsRawOstream(), file_addr, addr_size); + if (style == DumpStyleModuleWithFileAddress && section_sp) + s->PutChar(']'); + } break; + + case DumpStyleLoadAddress: { + addr_t load_addr = GetLoadAddress(target); + + /* + * MIPS: + * Display address in compressed form for MIPS16 or microMIPS + * if the address belongs to AddressClass::eCodeAlternateISA. + */ + if (target) { + const llvm::Triple::ArchType llvm_arch = + target->GetArchitecture().GetMachine(); + if (llvm_arch == llvm::Triple::mips || + llvm_arch == llvm::Triple::mipsel || + llvm_arch == llvm::Triple::mips64 || + llvm_arch == llvm::Triple::mips64el) + load_addr = GetCallableLoadAddress(target); + } + + if (load_addr == LLDB_INVALID_ADDRESS) { + if (fallback_style != DumpStyleInvalid) + return Dump(s, exe_scope, fallback_style, DumpStyleInvalid, addr_size); + return false; + } + DumpAddress(s->AsRawOstream(), load_addr, addr_size); + } break; + + case DumpStyleResolvedDescription: + case DumpStyleResolvedDescriptionNoModule: + case DumpStyleResolvedDescriptionNoFunctionArguments: + case DumpStyleNoFunctionName: + if (IsSectionOffset()) { + uint32_t pointer_size = 4; + ModuleSP module_sp(GetModule()); + if (target) + pointer_size = target->GetArchitecture().GetAddressByteSize(); + else if (module_sp) + pointer_size = module_sp->GetArchitecture().GetAddressByteSize(); + bool showed_info = false; + if (section_sp) { + SectionType sect_type = section_sp->GetType(); + switch (sect_type) { + case eSectionTypeData: + if (module_sp) { + if (Symtab *symtab = module_sp->GetSymtab()) { + const addr_t file_Addr = GetFileAddress(); + Symbol *symbol = + symtab->FindSymbolContainingFileAddress(file_Addr); + if (symbol) { + const char *symbol_name = symbol->GetName().AsCString(); + if (symbol_name) { + s->PutCStringColorHighlighted(symbol_name, settings); + addr_t delta = + file_Addr - symbol->GetAddressRef().GetFileAddress(); + if (delta) + s->Printf(" + %" PRIu64, delta); + showed_info = true; + } + } + } + } + break; + + case eSectionTypeDataCString: + // Read the C string from memory and display it + showed_info = true; + ReadCStringFromMemory(exe_scope, *this, s); + break; + + case eSectionTypeDataCStringPointers: + if (ReadAddress(exe_scope, *this, pointer_size, so_addr)) { +#if VERBOSE_OUTPUT + s->PutCString("(char *)"); + so_addr.Dump(s, exe_scope, DumpStyleLoadAddress, + DumpStyleFileAddress); + s->PutCString(": "); +#endif + showed_info = true; + ReadCStringFromMemory(exe_scope, so_addr, s); + } + break; + + case eSectionTypeDataObjCMessageRefs: + if (ReadAddress(exe_scope, *this, pointer_size, so_addr)) { + if (target && so_addr.IsSectionOffset()) { + SymbolContext func_sc; + target->GetImages().ResolveSymbolContextForAddress( + so_addr, eSymbolContextEverything, func_sc); + if (func_sc.function != nullptr || func_sc.symbol != nullptr) { + showed_info = true; +#if VERBOSE_OUTPUT + s->PutCString("(objc_msgref *) -> { (func*)"); + so_addr.Dump(s, exe_scope, DumpStyleLoadAddress, + DumpStyleFileAddress); +#else + s->PutCString("{ "); +#endif + Address cstr_addr(*this); + cstr_addr.SetOffset(cstr_addr.GetOffset() + pointer_size); + func_sc.DumpStopContext(s, exe_scope, so_addr, true, true, + false, true, true); + if (ReadAddress(exe_scope, cstr_addr, pointer_size, so_addr)) { +#if VERBOSE_OUTPUT + s->PutCString("), (char *)"); + so_addr.Dump(s, exe_scope, DumpStyleLoadAddress, + DumpStyleFileAddress); + s->PutCString(" ("); +#else + s->PutCString(", "); +#endif + ReadCStringFromMemory(exe_scope, so_addr, s); + } +#if VERBOSE_OUTPUT + s->PutCString(") }"); +#else + s->PutCString(" }"); +#endif + } + } + } + break; + + case eSectionTypeDataObjCCFStrings: { + Address cfstring_data_addr(*this); + cfstring_data_addr.SetOffset(cfstring_data_addr.GetOffset() + + (2 * pointer_size)); + if (ReadAddress(exe_scope, cfstring_data_addr, pointer_size, + so_addr)) { +#if VERBOSE_OUTPUT + s->PutCString("(CFString *) "); + cfstring_data_addr.Dump(s, exe_scope, DumpStyleLoadAddress, + DumpStyleFileAddress); + s->PutCString(" -> @"); +#else + s->PutChar('@'); +#endif + if (so_addr.Dump(s, exe_scope, DumpStyleResolvedDescription)) + showed_info = true; + } + } break; + + case eSectionTypeData4: + // Read the 4 byte data and display it + showed_info = true; + s->PutCString("(uint32_t) "); + DumpUInt(exe_scope, *this, 4, s); + break; + + case eSectionTypeData8: + // Read the 8 byte data and display it + showed_info = true; + s->PutCString("(uint64_t) "); + DumpUInt(exe_scope, *this, 8, s); + break; + + case eSectionTypeData16: + // Read the 16 byte data and display it + showed_info = true; + s->PutCString("(uint128_t) "); + DumpUInt(exe_scope, *this, 16, s); + break; + + case eSectionTypeDataPointers: + // Read the pointer data and display it + if (ReadAddress(exe_scope, *this, pointer_size, so_addr)) { + s->PutCString("(void *)"); + so_addr.Dump(s, exe_scope, DumpStyleLoadAddress, + DumpStyleFileAddress); + + showed_info = true; + if (so_addr.IsSectionOffset()) { + SymbolContext pointer_sc; + if (target) { + target->GetImages().ResolveSymbolContextForAddress( + so_addr, eSymbolContextEverything, pointer_sc); + if (pointer_sc.function != nullptr || + pointer_sc.symbol != nullptr) { + s->PutCString(": "); + pointer_sc.DumpStopContext(s, exe_scope, so_addr, true, false, + false, true, true, false, + settings); + } + } + } + } + break; + + default: + break; + } + } + + if (!showed_info) { + if (module_sp) { + SymbolContext sc; + module_sp->ResolveSymbolContextForAddress( + *this, eSymbolContextEverything, sc); + if (sc.function || sc.symbol) { + bool show_stop_context = true; + const bool show_module = (style == DumpStyleResolvedDescription); + const bool show_fullpaths = false; + const bool show_inlined_frames = true; + const bool show_function_arguments = + (style != DumpStyleResolvedDescriptionNoFunctionArguments); + const bool show_function_name = (style != DumpStyleNoFunctionName); + if (sc.function == nullptr && sc.symbol != nullptr) { + // If we have just a symbol make sure it is in the right section + if (sc.symbol->ValueIsAddress()) { + if (sc.symbol->GetAddressRef().GetSection() != GetSection()) { + // don't show the module if the symbol is a trampoline symbol + show_stop_context = false; + } + } + } + if (show_stop_context) { + // We have a function or a symbol from the same sections as this + // address. + sc.DumpStopContext(s, exe_scope, *this, show_fullpaths, + show_module, show_inlined_frames, + show_function_arguments, show_function_name, + false, settings); + } else { + // We found a symbol but it was in a different section so it + // isn't the symbol we should be showing, just show the section + // name + offset + Dump(s, exe_scope, DumpStyleSectionNameOffset, DumpStyleInvalid, + UINT32_MAX, false, settings); + } + } + } + } + } else { + if (fallback_style != DumpStyleInvalid) + return Dump(s, exe_scope, fallback_style, DumpStyleInvalid, addr_size, + false, settings); + return false; + } + break; + + case DumpStyleDetailedSymbolContext: + if (IsSectionOffset()) { + ModuleSP module_sp(GetModule()); + if (module_sp) { + SymbolContext sc; + module_sp->ResolveSymbolContextForAddress( + *this, eSymbolContextEverything | eSymbolContextVariable, sc); + if (sc.symbol) { + // If we have just a symbol make sure it is in the same section as + // our address. If it isn't, then we might have just found the last + // symbol that came before the address that we are looking up that + // has nothing to do with our address lookup. + if (sc.symbol->ValueIsAddress() && + sc.symbol->GetAddressRef().GetSection() != GetSection()) + sc.symbol = nullptr; + } + sc.GetDescription(s, eDescriptionLevelBrief, target, settings); + + if (sc.block) { + bool can_create = true; + bool get_parent_variables = true; + bool stop_if_block_is_inlined_function = false; + VariableList variable_list; + addr_t file_addr = GetFileAddress(); + sc.block->AppendVariables( + can_create, get_parent_variables, + stop_if_block_is_inlined_function, + [&](Variable *var) { + return var && var->LocationIsValidForAddress(*this); + }, + &variable_list); + ABISP abi = + ABI::FindPlugin(ProcessSP(), module_sp->GetArchitecture()); + for (const VariableSP &var_sp : variable_list) { + s->Indent(); + s->Printf(" Variable: id = {0x%8.8" PRIx64 "}, name = \"%s\"", + var_sp->GetID(), var_sp->GetName().GetCString()); + Type *type = var_sp->GetType(); + if (type) + s->Printf(", type = \"%s\"", type->GetName().GetCString()); + else + s->PutCString(", type = <unknown>"); + s->PutCString(", valid ranges = "); + if (var_sp->GetScopeRange().IsEmpty()) + s->PutCString("<block>"); + else if (all_ranges) { + for (auto range : var_sp->GetScopeRange()) + DumpAddressRange(s->AsRawOstream(), range.GetRangeBase(), + range.GetRangeEnd(), addr_size); + } else if (auto *range = + var_sp->GetScopeRange().FindEntryThatContains( + file_addr)) + DumpAddressRange(s->AsRawOstream(), range->GetRangeBase(), + range->GetRangeEnd(), addr_size); + s->PutCString(", location = "); + var_sp->DumpLocations(s, all_ranges ? LLDB_INVALID_ADDRESS : *this); + s->PutCString(", decl = "); + var_sp->GetDeclaration().DumpStopContext(s, false); + s->EOL(); + } + } + } + } else { + if (fallback_style != DumpStyleInvalid) + return Dump(s, exe_scope, fallback_style, DumpStyleInvalid, addr_size, + false, settings); + return false; + } + break; + + case DumpStyleResolvedPointerDescription: { + Process *process = exe_ctx.GetProcessPtr(); + if (process) { + addr_t load_addr = GetLoadAddress(target); + if (load_addr != LLDB_INVALID_ADDRESS) { + Status memory_error; + addr_t dereferenced_load_addr = + process->ReadPointerFromMemory(load_addr, memory_error); + if (dereferenced_load_addr != LLDB_INVALID_ADDRESS) { + Address dereferenced_addr; + if (dereferenced_addr.SetLoadAddress(dereferenced_load_addr, + target)) { + StreamString strm; + if (dereferenced_addr.Dump(&strm, exe_scope, + DumpStyleResolvedDescription, + DumpStyleInvalid, addr_size)) { + DumpAddress(s->AsRawOstream(), dereferenced_load_addr, addr_size, + " -> ", " "); + s->Write(strm.GetString().data(), strm.GetSize()); + return true; + } + } + } + } + } + if (fallback_style != DumpStyleInvalid) + return Dump(s, exe_scope, fallback_style, DumpStyleInvalid, addr_size); + return false; + } break; + } + + return true; +} + +bool Address::SectionWasDeleted() const { + if (GetSection()) + return false; + return SectionWasDeletedPrivate(); +} + +bool Address::SectionWasDeletedPrivate() const { + lldb::SectionWP empty_section_wp; + + // If either call to "std::weak_ptr::owner_before(...) value returns true, + // this indicates that m_section_wp once contained (possibly still does) a + // reference to a valid shared pointer. This helps us know if we had a valid + // reference to a section which is now invalid because the module it was in + // was unloaded/deleted, or if the address doesn't have a valid reference to + // a section. + return empty_section_wp.owner_before(m_section_wp) || + m_section_wp.owner_before(empty_section_wp); +} + +uint32_t +Address::CalculateSymbolContext(SymbolContext *sc, + SymbolContextItem resolve_scope) const { + sc->Clear(false); + // Absolute addresses don't have enough information to reconstruct even their + // target. + + SectionSP section_sp(GetSection()); + if (section_sp) { + ModuleSP module_sp(section_sp->GetModule()); + if (module_sp) { + sc->module_sp = module_sp; + if (sc->module_sp) + return sc->module_sp->ResolveSymbolContextForAddress( + *this, resolve_scope, *sc); + } + } + return 0; +} + +ModuleSP Address::CalculateSymbolContextModule() const { + SectionSP section_sp(GetSection()); + if (section_sp) + return section_sp->GetModule(); + return ModuleSP(); +} + +CompileUnit *Address::CalculateSymbolContextCompileUnit() const { + SectionSP section_sp(GetSection()); + if (section_sp) { + SymbolContext sc; + sc.module_sp = section_sp->GetModule(); + if (sc.module_sp) { + sc.module_sp->ResolveSymbolContextForAddress(*this, + eSymbolContextCompUnit, sc); + return sc.comp_unit; + } + } + return nullptr; +} + +Function *Address::CalculateSymbolContextFunction() const { + SectionSP section_sp(GetSection()); + if (section_sp) { + SymbolContext sc; + sc.module_sp = section_sp->GetModule(); + if (sc.module_sp) { + sc.module_sp->ResolveSymbolContextForAddress(*this, + eSymbolContextFunction, sc); + return sc.function; + } + } + return nullptr; +} + +Block *Address::CalculateSymbolContextBlock() const { + SectionSP section_sp(GetSection()); + if (section_sp) { + SymbolContext sc; + sc.module_sp = section_sp->GetModule(); + if (sc.module_sp) { + sc.module_sp->ResolveSymbolContextForAddress(*this, eSymbolContextBlock, + sc); + return sc.block; + } + } + return nullptr; +} + +Symbol *Address::CalculateSymbolContextSymbol() const { + SectionSP section_sp(GetSection()); + if (section_sp) { + SymbolContext sc; + sc.module_sp = section_sp->GetModule(); + if (sc.module_sp) { + sc.module_sp->ResolveSymbolContextForAddress(*this, eSymbolContextSymbol, + sc); + return sc.symbol; + } + } + return nullptr; +} + +bool Address::CalculateSymbolContextLineEntry(LineEntry &line_entry) const { + SectionSP section_sp(GetSection()); + if (section_sp) { + SymbolContext sc; + sc.module_sp = section_sp->GetModule(); + if (sc.module_sp) { + sc.module_sp->ResolveSymbolContextForAddress(*this, + eSymbolContextLineEntry, sc); + if (sc.line_entry.IsValid()) { + line_entry = sc.line_entry; + return true; + } + } + } + line_entry.Clear(); + return false; +} + +int Address::CompareFileAddress(const Address &a, const Address &b) { + addr_t a_file_addr = a.GetFileAddress(); + addr_t b_file_addr = b.GetFileAddress(); + if (a_file_addr < b_file_addr) + return -1; + if (a_file_addr > b_file_addr) + return +1; + return 0; +} + +int Address::CompareLoadAddress(const Address &a, const Address &b, + Target *target) { + assert(target != nullptr); + addr_t a_load_addr = a.GetLoadAddress(target); + addr_t b_load_addr = b.GetLoadAddress(target); + if (a_load_addr < b_load_addr) + return -1; + if (a_load_addr > b_load_addr) + return +1; + return 0; +} + +int Address::CompareModulePointerAndOffset(const Address &a, const Address &b) { + ModuleSP a_module_sp(a.GetModule()); + ModuleSP b_module_sp(b.GetModule()); + Module *a_module = a_module_sp.get(); + Module *b_module = b_module_sp.get(); + if (a_module < b_module) + return -1; + if (a_module > b_module) + return +1; + // Modules are the same, just compare the file address since they should be + // unique + addr_t a_file_addr = a.GetFileAddress(); + addr_t b_file_addr = b.GetFileAddress(); + if (a_file_addr < b_file_addr) + return -1; + if (a_file_addr > b_file_addr) + return +1; + return 0; +} + +size_t Address::MemorySize() const { + // Noting special for the memory size of a single Address object, it is just + // the size of itself. + return sizeof(Address); +} + +// NOTE: Be careful using this operator. It can correctly compare two +// addresses from the same Module correctly. It can't compare two addresses +// from different modules in any meaningful way, but it will compare the module +// pointers. +// +// To sum things up: +// - works great for addresses within the same module - it works for addresses +// across multiple modules, but don't expect the +// address results to make much sense +// +// This basically lets Address objects be used in ordered collection classes. + +bool lldb_private::operator<(const Address &lhs, const Address &rhs) { + ModuleSP lhs_module_sp(lhs.GetModule()); + ModuleSP rhs_module_sp(rhs.GetModule()); + Module *lhs_module = lhs_module_sp.get(); + Module *rhs_module = rhs_module_sp.get(); + if (lhs_module == rhs_module) { + // Addresses are in the same module, just compare the file addresses + return lhs.GetFileAddress() < rhs.GetFileAddress(); + } else { + // The addresses are from different modules, just use the module pointer + // value to get consistent ordering + return lhs_module < rhs_module; + } +} + +bool lldb_private::operator>(const Address &lhs, const Address &rhs) { + ModuleSP lhs_module_sp(lhs.GetModule()); + ModuleSP rhs_module_sp(rhs.GetModule()); + Module *lhs_module = lhs_module_sp.get(); + Module *rhs_module = rhs_module_sp.get(); + if (lhs_module == rhs_module) { + // Addresses are in the same module, just compare the file addresses + return lhs.GetFileAddress() > rhs.GetFileAddress(); + } else { + // The addresses are from different modules, just use the module pointer + // value to get consistent ordering + return lhs_module > rhs_module; + } +} + +// The operator == checks for exact equality only (same section, same offset) +bool lldb_private::operator==(const Address &a, const Address &rhs) { + return a.GetOffset() == rhs.GetOffset() && a.GetSection() == rhs.GetSection(); +} + +// The operator != checks for exact inequality only (differing section, or +// different offset) +bool lldb_private::operator!=(const Address &a, const Address &rhs) { + return a.GetOffset() != rhs.GetOffset() || a.GetSection() != rhs.GetSection(); +} + +AddressClass Address::GetAddressClass() const { + ModuleSP module_sp(GetModule()); + if (module_sp) { + ObjectFile *obj_file = module_sp->GetObjectFile(); + if (obj_file) { + // Give the symbol file a chance to add to the unified section list + // and to the symtab. + module_sp->GetSymtab(); + return obj_file->GetAddressClass(GetFileAddress()); + } + } + return AddressClass::eUnknown; +} + +bool Address::SetLoadAddress(lldb::addr_t load_addr, Target *target, + bool allow_section_end) { + if (target && target->GetSectionLoadList().ResolveLoadAddress( + load_addr, *this, allow_section_end)) + return true; + m_section_wp.reset(); + m_offset = load_addr; + return false; +} diff --git a/contrib/llvm-project/lldb/source/Core/AddressRange.cpp b/contrib/llvm-project/lldb/source/Core/AddressRange.cpp new file mode 100644 index 000000000000..6cef7e149cd2 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/AddressRange.cpp @@ -0,0 +1,248 @@ +//===-- AddressRange.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/Core/AddressRange.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/Section.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/Stream.h" +#include "lldb/lldb-defines.h" +#include "lldb/lldb-types.h" + +#include "llvm/Support/Compiler.h" + +#include <memory> + +#include <cinttypes> + +namespace lldb_private { +class SectionList; +} + +using namespace lldb; +using namespace lldb_private; + +AddressRange::AddressRange() : m_base_addr() {} + +AddressRange::AddressRange(addr_t file_addr, addr_t byte_size, + const SectionList *section_list) + : m_base_addr(file_addr, section_list), m_byte_size(byte_size) {} + +AddressRange::AddressRange(const lldb::SectionSP §ion, addr_t offset, + addr_t byte_size) + : m_base_addr(section, offset), m_byte_size(byte_size) {} + +AddressRange::AddressRange(const Address &so_addr, addr_t byte_size) + : m_base_addr(so_addr), m_byte_size(byte_size) {} + +AddressRange::~AddressRange() = default; + +bool AddressRange::Contains(const Address &addr) const { + SectionSP range_sect_sp = GetBaseAddress().GetSection(); + SectionSP addr_sect_sp = addr.GetSection(); + if (range_sect_sp) { + if (!addr_sect_sp || + range_sect_sp->GetModule() != addr_sect_sp->GetModule()) + return false; // Modules do not match. + } else if (addr_sect_sp) { + return false; // Range has no module but "addr" does because addr has a + // section + } + // Either the modules match, or both have no module, so it is ok to compare + // the file addresses in this case only. + return ContainsFileAddress(addr); +} + +bool AddressRange::ContainsFileAddress(const Address &addr) const { + if (addr.GetSection() == m_base_addr.GetSection()) + return (addr.GetOffset() - m_base_addr.GetOffset()) < GetByteSize(); + addr_t file_base_addr = GetBaseAddress().GetFileAddress(); + if (file_base_addr == LLDB_INVALID_ADDRESS) + return false; + + addr_t file_addr = addr.GetFileAddress(); + if (file_addr == LLDB_INVALID_ADDRESS) + return false; + + if (file_base_addr <= file_addr) + return (file_addr - file_base_addr) < GetByteSize(); + + return false; +} + +bool AddressRange::ContainsFileAddress(addr_t file_addr) const { + if (file_addr == LLDB_INVALID_ADDRESS) + return false; + + addr_t file_base_addr = GetBaseAddress().GetFileAddress(); + if (file_base_addr == LLDB_INVALID_ADDRESS) + return false; + + if (file_base_addr <= file_addr) + return (file_addr - file_base_addr) < GetByteSize(); + + return false; +} + +bool AddressRange::ContainsLoadAddress(const Address &addr, + Target *target) const { + if (addr.GetSection() == m_base_addr.GetSection()) + return (addr.GetOffset() - m_base_addr.GetOffset()) < GetByteSize(); + addr_t load_base_addr = GetBaseAddress().GetLoadAddress(target); + if (load_base_addr == LLDB_INVALID_ADDRESS) + return false; + + addr_t load_addr = addr.GetLoadAddress(target); + if (load_addr == LLDB_INVALID_ADDRESS) + return false; + + if (load_base_addr <= load_addr) + return (load_addr - load_base_addr) < GetByteSize(); + + return false; +} + +bool AddressRange::ContainsLoadAddress(addr_t load_addr, Target *target) const { + if (load_addr == LLDB_INVALID_ADDRESS) + return false; + + addr_t load_base_addr = GetBaseAddress().GetLoadAddress(target); + if (load_base_addr == LLDB_INVALID_ADDRESS) + return false; + + if (load_base_addr <= load_addr) + return (load_addr - load_base_addr) < GetByteSize(); + + return false; +} + +bool AddressRange::Extend(const AddressRange &rhs_range) { + addr_t lhs_end_addr = GetBaseAddress().GetFileAddress() + GetByteSize(); + addr_t rhs_base_addr = rhs_range.GetBaseAddress().GetFileAddress(); + + if (!ContainsFileAddress(rhs_range.GetBaseAddress()) && + lhs_end_addr != rhs_base_addr) + // The ranges don't intersect at all on the right side of this range. + return false; + + addr_t rhs_end_addr = rhs_base_addr + rhs_range.GetByteSize(); + if (lhs_end_addr >= rhs_end_addr) + // The rhs range totally overlaps this one, nothing to add. + return false; + + m_byte_size += rhs_end_addr - lhs_end_addr; + return true; +} + +void AddressRange::Clear() { + m_base_addr.Clear(); + m_byte_size = 0; +} + +bool AddressRange::IsValid() const { + return m_base_addr.IsValid() && (m_byte_size > 0); +} + +bool AddressRange::Dump(Stream *s, Target *target, Address::DumpStyle style, + Address::DumpStyle fallback_style) const { + addr_t vmaddr = LLDB_INVALID_ADDRESS; + int addr_size = sizeof(addr_t); + if (target) + addr_size = target->GetArchitecture().GetAddressByteSize(); + + bool show_module = false; + switch (style) { + default: + break; + case Address::DumpStyleSectionNameOffset: + case Address::DumpStyleSectionPointerOffset: + s->PutChar('['); + m_base_addr.Dump(s, target, style, fallback_style); + s->PutChar('-'); + DumpAddress(s->AsRawOstream(), m_base_addr.GetOffset() + GetByteSize(), + addr_size); + s->PutChar(')'); + return true; + break; + + case Address::DumpStyleModuleWithFileAddress: + show_module = true; + [[fallthrough]]; + case Address::DumpStyleFileAddress: + vmaddr = m_base_addr.GetFileAddress(); + break; + + case Address::DumpStyleLoadAddress: + vmaddr = m_base_addr.GetLoadAddress(target); + break; + } + + if (vmaddr != LLDB_INVALID_ADDRESS) { + if (show_module) { + ModuleSP module_sp(GetBaseAddress().GetModule()); + if (module_sp) + s->Printf("%s", module_sp->GetFileSpec().GetFilename().AsCString( + "<Unknown>")); + } + DumpAddressRange(s->AsRawOstream(), vmaddr, vmaddr + GetByteSize(), + addr_size); + return true; + } else if (fallback_style != Address::DumpStyleInvalid) { + return Dump(s, target, fallback_style, Address::DumpStyleInvalid); + } + + return false; +} + +void AddressRange::DumpDebug(Stream *s) const { + s->Printf("%p: AddressRange section = %p, offset = 0x%16.16" PRIx64 + ", byte_size = 0x%16.16" PRIx64 "\n", + static_cast<const void *>(this), + static_cast<void *>(m_base_addr.GetSection().get()), + m_base_addr.GetOffset(), GetByteSize()); +} + +bool AddressRange::GetDescription(Stream *s, Target *target) const { + addr_t start_addr = m_base_addr.GetLoadAddress(target); + if (start_addr != LLDB_INVALID_ADDRESS) { + // We have a valid target and the address was resolved, or we have a base + // address with no section. Just print out a raw address range: [<addr>, + // <addr>) + s->Printf("[0x%" PRIx64 "-0x%" PRIx64 ")", start_addr, + start_addr + GetByteSize()); + return true; + } + + // Either no target or the address wasn't resolved, print as + // <module>[<file-addr>-<file-addr>) + const char *file_name = ""; + const auto section_sp = m_base_addr.GetSection(); + if (section_sp) { + if (const auto object_file = section_sp->GetObjectFile()) + file_name = object_file->GetFileSpec().GetFilename().AsCString(); + } + start_addr = m_base_addr.GetFileAddress(); + const addr_t end_addr = (start_addr == LLDB_INVALID_ADDRESS) + ? LLDB_INVALID_ADDRESS + : start_addr + GetByteSize(); + s->Printf("%s[0x%" PRIx64 "-0x%" PRIx64 ")", file_name, start_addr, end_addr); + return true; +} + +bool AddressRange::operator==(const AddressRange &rhs) { + if (!IsValid() || !rhs.IsValid()) + return false; + return m_base_addr == rhs.GetBaseAddress() && + m_byte_size == rhs.GetByteSize(); +} + +bool AddressRange::operator!=(const AddressRange &rhs) { + return !(*this == rhs); +} diff --git a/contrib/llvm-project/lldb/source/Core/AddressRangeListImpl.cpp b/contrib/llvm-project/lldb/source/Core/AddressRangeListImpl.cpp new file mode 100644 index 000000000000..d405cf0fa3ec --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/AddressRangeListImpl.cpp @@ -0,0 +1,50 @@ +//===-- AddressRangeListImpl.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/Core/AddressRangeListImpl.h" + +using namespace lldb; +using namespace lldb_private; + +AddressRangeListImpl::AddressRangeListImpl() : m_ranges() {} + +AddressRangeListImpl & +AddressRangeListImpl::operator=(const AddressRangeListImpl &rhs) { + if (this == &rhs) + return *this; + m_ranges = rhs.m_ranges; + return *this; +} + +size_t AddressRangeListImpl::GetSize() const { return m_ranges.size(); } + +void AddressRangeListImpl::Reserve(size_t capacity) { + m_ranges.reserve(capacity); +} + +void AddressRangeListImpl::Append(const AddressRange &sb_region) { + m_ranges.emplace_back(sb_region); +} + +void AddressRangeListImpl::Append(const AddressRangeListImpl &list) { + Reserve(GetSize() + list.GetSize()); + + for (const auto &range : list.m_ranges) + Append(range); +} + +void AddressRangeListImpl::Clear() { m_ranges.clear(); } + +lldb_private::AddressRange +AddressRangeListImpl::GetAddressRangeAtIndex(size_t index) { + if (index >= GetSize()) + return AddressRange(); + return m_ranges[index]; +} + +AddressRanges &AddressRangeListImpl::ref() { return m_ranges; } diff --git a/contrib/llvm-project/lldb/source/Core/AddressResolver.cpp b/contrib/llvm-project/lldb/source/Core/AddressResolver.cpp new file mode 100644 index 000000000000..87b0abd34e52 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/AddressResolver.cpp @@ -0,0 +1,43 @@ +//===-- AddressResolver.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/Core/AddressResolver.h" + +#include "lldb/Core/SearchFilter.h" + +namespace lldb_private { +class ModuleList; +} + +using namespace lldb_private; + +// AddressResolver: +AddressResolver::AddressResolver() = default; + +AddressResolver::~AddressResolver() = default; + +void AddressResolver::ResolveAddressInModules(SearchFilter &filter, + ModuleList &modules) { + filter.SearchInModuleList(*this, modules); +} + +void AddressResolver::ResolveAddress(SearchFilter &filter) { + filter.Search(*this); +} + +std::vector<AddressRange> &AddressResolver::GetAddressRanges() { + return m_address_ranges; +} + +size_t AddressResolver::GetNumberOfAddresses() { + return m_address_ranges.size(); +} + +AddressRange &AddressResolver::GetAddressRangeAtIndex(size_t idx) { + return m_address_ranges[idx]; +} diff --git a/contrib/llvm-project/lldb/source/Core/AddressResolverFileLine.cpp b/contrib/llvm-project/lldb/source/Core/AddressResolverFileLine.cpp new file mode 100644 index 000000000000..6ab3b8fcee15 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/AddressResolverFileLine.cpp @@ -0,0 +1,76 @@ +//===-- AddressResolverFileLine.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/Core/AddressResolverFileLine.h" + +#include "lldb/Core/Address.h" +#include "lldb/Core/AddressRange.h" +#include "lldb/Symbol/CompileUnit.h" +#include "lldb/Symbol/LineEntry.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Stream.h" +#include "lldb/Utility/StreamString.h" +#include "lldb/lldb-enumerations.h" +#include "lldb/lldb-types.h" + +#include <cinttypes> +#include <vector> + +using namespace lldb; +using namespace lldb_private; + +// AddressResolverFileLine: +AddressResolverFileLine::AddressResolverFileLine( + SourceLocationSpec location_spec) + : AddressResolver(), m_src_location_spec(location_spec) {} + +AddressResolverFileLine::~AddressResolverFileLine() = default; + +Searcher::CallbackReturn +AddressResolverFileLine::SearchCallback(SearchFilter &filter, + SymbolContext &context, Address *addr) { + SymbolContextList sc_list; + CompileUnit *cu = context.comp_unit; + + Log *log = GetLog(LLDBLog::Breakpoints); + + // TODO: Handle SourceLocationSpec column information + cu->ResolveSymbolContext(m_src_location_spec, eSymbolContextEverything, + sc_list); + for (const SymbolContext &sc : sc_list) { + Address line_start = sc.line_entry.range.GetBaseAddress(); + addr_t byte_size = sc.line_entry.range.GetByteSize(); + if (line_start.IsValid()) { + AddressRange new_range(line_start, byte_size); + m_address_ranges.push_back(new_range); + } else { + LLDB_LOGF(log, + "error: Unable to resolve address at file address 0x%" PRIx64 + " for %s:%d\n", + line_start.GetFileAddress(), + m_src_location_spec.GetFileSpec().GetFilename().AsCString( + "<Unknown>"), + m_src_location_spec.GetLine().value_or(0)); + } + } + return Searcher::eCallbackReturnContinue; +} + +lldb::SearchDepth AddressResolverFileLine::GetDepth() { + return lldb::eSearchDepthCompUnit; +} + +void AddressResolverFileLine::GetDescription(Stream *s) { + s->Printf( + "File and line address - file: \"%s\" line: %u", + m_src_location_spec.GetFileSpec().GetFilename().AsCString("<Unknown>"), + m_src_location_spec.GetLine().value_or(0)); +} diff --git a/contrib/llvm-project/lldb/source/Core/Communication.cpp b/contrib/llvm-project/lldb/source/Core/Communication.cpp new file mode 100644 index 000000000000..5d890632ccc6 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/Communication.cpp @@ -0,0 +1,166 @@ +//===-- Communication.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/Core/Communication.h" + +#include "lldb/Utility/Connection.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Status.h" + +#include "llvm/Support/Compiler.h" + +#include <algorithm> +#include <cstring> +#include <memory> + +#include <cerrno> +#include <cinttypes> +#include <cstdio> + +using namespace lldb; +using namespace lldb_private; + +Communication::Communication() + : m_connection_sp(), m_write_mutex(), m_close_on_eof(true) { +} + +Communication::~Communication() { + Clear(); +} + +void Communication::Clear() { + Disconnect(nullptr); +} + +ConnectionStatus Communication::Connect(const char *url, Status *error_ptr) { + Clear(); + + LLDB_LOG(GetLog(LLDBLog::Communication), + "{0} Communication::Connect (url = {1})", this, url); + + lldb::ConnectionSP connection_sp(m_connection_sp); + if (connection_sp) + return connection_sp->Connect(url, error_ptr); + if (error_ptr) + error_ptr->SetErrorString("Invalid connection."); + return eConnectionStatusNoConnection; +} + +ConnectionStatus Communication::Disconnect(Status *error_ptr) { + LLDB_LOG(GetLog(LLDBLog::Communication), "{0} Communication::Disconnect ()", + this); + + lldb::ConnectionSP connection_sp(m_connection_sp); + if (connection_sp) { + ConnectionStatus status = connection_sp->Disconnect(error_ptr); + // We currently don't protect connection_sp with any mutex for multi- + // threaded environments. So lets not nuke our connection class without + // putting some multi-threaded protections in. We also probably don't want + // to pay for the overhead it might cause if every time we access the + // connection we have to take a lock. + // + // This unique pointer will cleanup after itself when this object goes + // away, so there is no need to currently have it destroy itself + // immediately upon disconnect. + // connection_sp.reset(); + return status; + } + return eConnectionStatusNoConnection; +} + +bool Communication::IsConnected() const { + lldb::ConnectionSP connection_sp(m_connection_sp); + return (connection_sp ? connection_sp->IsConnected() : false); +} + +bool Communication::HasConnection() const { + return m_connection_sp.get() != nullptr; +} + +size_t Communication::Read(void *dst, size_t dst_len, + const Timeout<std::micro> &timeout, + ConnectionStatus &status, Status *error_ptr) { + Log *log = GetLog(LLDBLog::Communication); + LLDB_LOG( + log, + "this = {0}, dst = {1}, dst_len = {2}, timeout = {3}, connection = {4}", + this, dst, dst_len, timeout, m_connection_sp.get()); + + return ReadFromConnection(dst, dst_len, timeout, status, error_ptr); +} + +size_t Communication::Write(const void *src, size_t src_len, + ConnectionStatus &status, Status *error_ptr) { + lldb::ConnectionSP connection_sp(m_connection_sp); + + std::lock_guard<std::mutex> guard(m_write_mutex); + LLDB_LOG(GetLog(LLDBLog::Communication), + "{0} Communication::Write (src = {1}, src_len = {2}" + ") connection = {3}", + this, src, (uint64_t)src_len, connection_sp.get()); + + if (connection_sp) + return connection_sp->Write(src, src_len, status, error_ptr); + + if (error_ptr) + error_ptr->SetErrorString("Invalid connection."); + status = eConnectionStatusNoConnection; + return 0; +} + +size_t Communication::WriteAll(const void *src, size_t src_len, + ConnectionStatus &status, Status *error_ptr) { + size_t total_written = 0; + do + total_written += Write(static_cast<const char *>(src) + total_written, + src_len - total_written, status, error_ptr); + while (status == eConnectionStatusSuccess && total_written < src_len); + return total_written; +} + +size_t Communication::ReadFromConnection(void *dst, size_t dst_len, + const Timeout<std::micro> &timeout, + ConnectionStatus &status, + Status *error_ptr) { + lldb::ConnectionSP connection_sp(m_connection_sp); + if (connection_sp) + return connection_sp->Read(dst, dst_len, timeout, status, error_ptr); + + if (error_ptr) + error_ptr->SetErrorString("Invalid connection."); + status = eConnectionStatusNoConnection; + return 0; +} + +void Communication::SetConnection(std::unique_ptr<Connection> connection) { + Disconnect(nullptr); + m_connection_sp = std::move(connection); +} + +std::string +Communication::ConnectionStatusAsString(lldb::ConnectionStatus status) { + switch (status) { + case eConnectionStatusSuccess: + return "success"; + case eConnectionStatusError: + return "error"; + case eConnectionStatusTimedOut: + return "timed out"; + case eConnectionStatusNoConnection: + return "no connection"; + case eConnectionStatusLostConnection: + return "lost connection"; + case eConnectionStatusEndOfFile: + return "end of file"; + case eConnectionStatusInterrupted: + return "interrupted"; + } + + return "@" + std::to_string(status); +} diff --git a/contrib/llvm-project/lldb/source/Core/CoreProperties.td b/contrib/llvm-project/lldb/source/Core/CoreProperties.td new file mode 100644 index 000000000000..30f715364a41 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/CoreProperties.td @@ -0,0 +1,228 @@ +include "../../include/lldb/Core/PropertiesBase.td" + +let Definition = "modulelist" in { + def EnableExternalLookup: Property<"enable-external-lookup", "Boolean">, + Global, + DefaultTrue, + Desc<"Control the use of external tools and repositories to locate symbol files. Directories listed in target.debug-file-search-paths and directory of the executable are always checked first for separate debug info files. Then depending on this setting: On macOS, Spotlight would be also used to locate a matching .dSYM bundle based on the UUID of the executable. On NetBSD, directory /usr/libdata/debug would be also searched. On platforms other than NetBSD directory /usr/lib/debug would be also searched. If all other methods fail there may be symbol-locator plugins that, if configured properly, will also attempt to acquire symbols. The debuginfod plugin defaults to the DEGUFINFOD_URLS environment variable which is configurable through the 'plugin.symbol-locator.debuginfod.server_urls' setting.">; + def EnableBackgroundLookup: Property<"enable-background-lookup", "Boolean">, + Global, + DefaultFalse, + Desc<"Alias for backward compatibility: when enabled this is the equivalent to 'symbols.auto-download background'.">; + def AutoDownload: Property<"auto-download", "Enum">, + Global, + DefaultEnumValue<"eSymbolDownloadOff">, + EnumValues<"OptionEnumValues(g_auto_download_enum_values)">, + Desc<"On macOS, automatically download symbols with dsymForUUID (or an equivalent script/binary) for relevant images in the debug session.">; + def ClangModulesCachePath: Property<"clang-modules-cache-path", "FileSpec">, + Global, + DefaultStringValue<"">, + Desc<"The path to the clang modules cache directory (-fmodules-cache-path).">; + def SymLinkPaths: Property<"debug-info-symlink-paths", "FileSpecList">, + Global, + DefaultStringValue<"">, + Desc<"Debug info path which should be resolved while parsing, relative to the host filesystem.">; + def EnableLLDBIndexCache: Property<"enable-lldb-index-cache", "Boolean">, + Global, + DefaultFalse, + Desc<"Enable caching for debug sessions in LLDB. LLDB can cache data for each module for improved performance in subsequent debug sessions.">; + def LLDBIndexCachePath: Property<"lldb-index-cache-path", "FileSpec">, + Global, + DefaultStringValue<"">, + Desc<"The path to the LLDB index cache directory.">; + def LLDBIndexCacheMaxByteSize: Property<"lldb-index-cache-max-byte-size", "UInt64">, + Global, + DefaultUnsignedValue<0>, + Desc<"The maximum size for the LLDB index cache directory in bytes. A value over the amount of available space on the disk will be reduced to the amount of available space. A value of 0 disables the absolute size-based pruning.">; + def LLDBIndexCacheMaxPercent: Property<"lldb-index-cache-max-percent", "UInt64">, + Global, + DefaultUnsignedValue<0>, + Desc<"The maximum size for the cache directory in terms of percentage of the available space on the disk. Set to 100 to indicate no limit, 50 to indicate that the cache size will not be left over half the available disk space. A value over 100 will be reduced to 100. A value of 0 disables the percentage size-based pruning.">; + def LLDBIndexCacheExpirationDays: Property<"lldb-index-cache-expiration-days", "UInt64">, + Global, + DefaultUnsignedValue<7>, + Desc<"The expiration time in days for a file. When a file hasn't been accessed for the specified amount of days, it is removed from the cache. A value of 0 disables the expiration-based pruning.">; + def LoadSymbolOnDemand: Property<"load-on-demand", "Boolean">, + Global, + DefaultFalse, + Desc<"Enable on demand symbol loading in LLDB. LLDB will load debug info on demand for each module based on various conditions (e.g. matched breakpoint, resolved stack frame addresses and matched global variables/function symbols in symbol table) to improve performance. Please refer to docs/use/ondemand.rst for details.">; +} + +let Definition = "debugger" in { + def AutoConfirm: Property<"auto-confirm", "Boolean">, + Global, + DefaultFalse, + Desc<"If true all confirmation prompts will receive their default reply.">; + def DisassemblyFormat: Property<"disassembly-format", "FormatEntity">, + Global, + DefaultStringValue<"{${function.initial-function}{${module.file.basename}`}{${function.name-without-args}}:\\\\n}{${function.changed}\\\\n{${module.file.basename}`}{${function.name-without-args}}:\\\\n}{${ansi.fg.yellow}${current-pc-arrow}${ansi.normal} }${addr-file-or-load}{ <${function.concrete-only-addr-offset-no-padding}>}: ">, + Desc<"The default disassembly format string to use when disassembling instruction sequences.">; + def FrameFormat: Property<"frame-format", "FormatEntity">, + Global, + DefaultStringValue<"frame #${frame.index}: ${ansi.fg.yellow}${frame.pc}${ansi.normal}{ ${module.file.basename}{`${function.name-with-args}{${frame.no-debug}${function.pc-offset}}}}{ at ${ansi.fg.cyan}${line.file.basename}${ansi.normal}:${ansi.fg.yellow}${line.number}${ansi.normal}{:${ansi.fg.yellow}${line.column}${ansi.normal}}}{${function.is-optimized} [opt]}{${frame.is-artificial} [artificial]}\\\\n">, + Desc<"The default frame format string to use when displaying stack frame information for threads.">; + def NotiftVoid: Property<"notify-void", "Boolean">, + Global, + DefaultFalse, + Desc<"Notify the user explicitly if an expression returns void (default: false).">; + def Prompt: Property<"prompt", "String">, + Global, + DefaultEnumValue<"OptionValueString::eOptionEncodeCharacterEscapeSequences">, + DefaultStringValue<"(lldb) ">, + Desc<"The debugger command line prompt displayed for the user.">; + def PromptAnsiPrefix: Property<"prompt-ansi-prefix", "String">, + Global, + DefaultStringValue<"${ansi.faint}">, + Desc<"When in a color-enabled terminal, use the ANSI terminal code specified in this format immediately before the prompt.">; + def PromptAnsiSuffix: Property<"prompt-ansi-suffix", "String">, + Global, + DefaultStringValue<"${ansi.normal}">, + Desc<"When in a color-enabled terminal, use the ANSI terminal code specified in this format immediately after the prompt.">; + def ScriptLanguage: Property<"script-lang", "Enum">, + Global, + DefaultEnumValue<"eScriptLanguageLua">, + EnumValues<"OptionEnumValues(g_language_enumerators)">, + Desc<"The script language to be used for evaluating user-written scripts.">; + def REPLLanguage: Property<"repl-lang", "Language">, + Global, + DefaultEnumValue<"eLanguageTypeUnknown">, + Desc<"The language to use for the REPL.">; + def StopDisassemblyCount: Property<"stop-disassembly-count", "UInt64">, + Global, + DefaultUnsignedValue<4>, + Desc<"The number of disassembly lines to show when displaying a stopped context.">; + def StopDisassemblyDisplay: Property<"stop-disassembly-display", "Enum">, + Global, + DefaultEnumValue<"Debugger::eStopDisassemblyTypeNoDebugInfo">, + EnumValues<"OptionEnumValues(g_show_disassembly_enum_values)">, + Desc<"Control when to display disassembly when displaying a stopped context.">; + def StopDisassemblyMaxSize: Property<"stop-disassembly-max-size", "UInt64">, + Global, + DefaultUnsignedValue<32000>, + Desc<"The size limit to use when disassembling large functions (default: 32KB).">; + def StopLineCountAfter: Property<"stop-line-count-after", "UInt64">, + Global, + DefaultUnsignedValue<3>, + Desc<"The number of sources lines to display that come after the current source line when displaying a stopped context.">; + def StopLineCountBefore: Property<"stop-line-count-before", "UInt64">, + Global, + DefaultUnsignedValue<3>, + Desc<"The number of sources lines to display that come before the current source line when displaying a stopped context.">; + def HighlightSource: Property<"highlight-source", "Boolean">, + Global, + DefaultTrue, + Desc<"If true, LLDB will highlight the displayed source code.">; + def StopShowColumn: Property<"stop-show-column", "Enum">, + DefaultEnumValue<"eStopShowColumnAnsiOrCaret">, + EnumValues<"OptionEnumValues(s_stop_show_column_values)">, + Desc<"If true, LLDB will use the column information from the debug info to mark the current position when displaying a stopped context.">; + def StopShowColumnAnsiPrefix: Property<"stop-show-column-ansi-prefix", "String">, + Global, + DefaultStringValue<"${ansi.underline}">, + Desc<"When displaying the column marker in a color-enabled terminal, use the ANSI terminal code specified in this format at the immediately before the column to be marked.">; + def StopShowColumnAnsiSuffix: Property<"stop-show-column-ansi-suffix", "String">, + Global, + DefaultStringValue<"${ansi.normal}">, + Desc<"When displaying the column marker in a color-enabled terminal, use the ANSI terminal code specified in this format immediately after the column to be marked.">; + def StopShowLineMarkerAnsiPrefix: Property<"stop-show-line-ansi-prefix", "String">, + Global, + DefaultStringValue<"${ansi.fg.yellow}">, + Desc<"When displaying the line marker in a color-enabled terminal, use the ANSI terminal code specified in this format at the immediately before the line to be marked.">; + def StopShowLineMarkerAnsiSuffix: Property<"stop-show-line-ansi-suffix", "String">, + Global, + DefaultStringValue<"${ansi.normal}">, + Desc<"When displaying the line marker in a color-enabled terminal, use the ANSI terminal code specified in this format immediately after the line to be marked.">; + def TerminalWidth: Property<"term-width", "UInt64">, + Global, + DefaultUnsignedValue<80>, + Desc<"The maximum number of columns to use for displaying text.">; + def ThreadFormat: Property<"thread-format", "FormatEntity">, + Global, + DefaultStringValue<"thread #${thread.index}: tid = ${thread.id%tid}{, ${frame.pc}}{ ${module.file.basename}{`${function.name-with-args}{${frame.no-debug}${function.pc-offset}}}}{ at ${ansi.fg.cyan}${line.file.basename}${ansi.normal}:${ansi.fg.yellow}${line.number}${ansi.normal}{:${ansi.fg.yellow}${line.column}${ansi.normal}}}{, name = ${ansi.fg.green}'${thread.name}'${ansi.normal}}{, queue = ${ansi.fg.green}'${thread.queue}'${ansi.normal}}{, activity = ${ansi.fg.green}'${thread.info.activity.name}'${ansi.normal}}{, ${thread.info.trace_messages} messages}{, stop reason = ${ansi.fg.red}${thread.stop-reason}${ansi.normal}}{\\\\nReturn value: ${thread.return-value}}{\\\\nCompleted expression: ${thread.completed-expression}}\\\\n">, + Desc<"The default thread format string to use when displaying thread information.">; + def ThreadStopFormat: Property<"thread-stop-format", "FormatEntity">, + Global, + DefaultStringValue<"thread #${thread.index}{, name = '${thread.name}'}{, queue = ${ansi.fg.green}'${thread.queue}'${ansi.normal}}{, activity = ${ansi.fg.green}'${thread.info.activity.name}'${ansi.normal}}{, ${thread.info.trace_messages} messages}{, stop reason = ${ansi.fg.red}${thread.stop-reason}${ansi.normal}}{\\\\nReturn value: ${thread.return-value}}{\\\\nCompleted expression: ${thread.completed-expression}}\\\\n">, + Desc<"The default thread format string to use when displaying thread information as part of the stop display.">; + def UseExternalEditor: Property<"use-external-editor", "Boolean">, + Global, + DefaultFalse, + Desc<"Whether to use an external editor or not.">; + def ExternalEditor: Property<"external-editor", "String">, + Global, + DefaultStringValue<"">, + Desc<"External editor to use when use-external-editor is enabled.">; + def UseColor: Property<"use-color", "Boolean">, + Global, + DefaultTrue, + Desc<"Whether to use Ansi color codes or not.">; + def ShowProgress: Property<"show-progress", "Boolean">, + Global, + DefaultTrue, + Desc<"Whether to show progress or not if the debugger's output is an interactive color-enabled terminal.">; + def ShowProgressAnsiPrefix: Property<"show-progress-ansi-prefix", "String">, + Global, + DefaultStringValue<"${ansi.faint}">, + Desc<"When displaying progress in a color-enabled terminal, use the ANSI terminal code specified in this format immediately before the progress message.">; + def ShowProgressAnsiSuffix: Property<"show-progress-ansi-suffix", "String">, + Global, + DefaultStringValue<"${ansi.normal}">, + Desc<"When displaying progress in a color-enabled terminal, use the ANSI terminal code specified in this format immediately after the progress message.">; + def UseSourceCache: Property<"use-source-cache", "Boolean">, + Global, + DefaultTrue, + Desc<"Whether to cache source files in memory or not.">; + def AutoOneLineSummaries: Property<"auto-one-line-summaries", "Boolean">, + Global, + DefaultTrue, + Desc<"If true, LLDB will automatically display small structs in one-liner format (default: true).">; + def AutoIndent: Property<"auto-indent", "Boolean">, + Global, + DefaultTrue, + Desc<"If true, LLDB will auto indent/outdent code. Currently only supported in the REPL (default: true).">; + def PrintDecls: Property<"print-decls", "Boolean">, + Global, + DefaultTrue, + Desc<"If true, LLDB will print the values of variables declared in an expression. Currently only supported in the REPL (default: true).">; + def TabSize: Property<"tab-size", "UInt64">, + Global, + DefaultUnsignedValue<2>, + Desc<"The tab size to use when indenting code in multi-line input mode (default: 2).">; + def EscapeNonPrintables: Property<"escape-non-printables", "Boolean">, + Global, + DefaultTrue, + Desc<"If true, LLDB will automatically escape non-printable and escape characters when formatting strings.">; + def FrameFormatUnique: Property<"frame-format-unique", "FormatEntity">, + Global, + DefaultStringValue<"frame #${frame.index}: ${ansi.fg.yellow}${frame.pc}${ansi.normal}{ ${module.file.basename}{`${function.name-without-args}{${frame.no-debug}${function.pc-offset}}}}{ at ${ansi.fg.cyan}${line.file.basename}${ansi.normal}:${ansi.fg.yellow}${line.number}${ansi.normal}{:${ansi.fg.yellow}${line.column}${ansi.normal}}}{${function.is-optimized} [opt]}{${frame.is-artificial} [artificial]}\\\\n">, + Desc<"The default frame format string to use when displaying stack frame information for threads from thread backtrace unique.">; + def ShowAutosuggestion: Property<"show-autosuggestion", "Boolean">, + Global, + DefaultFalse, + Desc<"If true, LLDB will show suggestions to complete the command the user typed. Suggestions may be accepted using Ctrl-F.">; + def ShowAutosuggestionAnsiPrefix: Property<"show-autosuggestion-ansi-prefix", "String">, + Global, + DefaultStringValue<"${ansi.faint}">, + Desc<"When displaying suggestion in a color-enabled terminal, use the ANSI terminal code specified in this format immediately before the suggestion.">; + def ShowAutosuggestionAnsiSuffix: Property<"show-autosuggestion-ansi-suffix", "String">, + Global, + DefaultStringValue<"${ansi.normal}">, + Desc<"When displaying suggestion in a color-enabled terminal, use the ANSI terminal code specified in this format immediately after the suggestion.">; + def ShowRegexMatchAnsiPrefix: Property<"show-regex-match-ansi-prefix", "String">, + Global, + DefaultStringValue<"${ansi.fg.red}">, + Desc<"When displaying a regex match in a color-enabled terminal, use the ANSI terminal code specified in this format immediately before the match.">; + def ShowRegexMatchAnsiSuffix: Property<"show-regex-match-ansi-suffix", "String">, + Global, + DefaultStringValue<"${ansi.normal}">, + Desc<"When displaying a regex match in a color-enabled terminal, use the ANSI terminal code specified in this format immediately after the match.">; + def ShowDontUsePoHint: Property<"show-dont-use-po-hint", "Boolean">, + Global, + DefaultTrue, + Desc<"If true, and object description was requested for a type that does not implement it, LLDB will print a hint telling the user to consider using p instead.">; + def DWIMPrintVerbosity: Property<"dwim-print-verbosity", "Enum">, + Global, + DefaultEnumValue<"eDWIMPrintVerbosityNone">, + EnumValues<"OptionEnumValues(g_dwim_print_verbosities)">, + Desc<"The verbosity level used by dwim-print.">; +} diff --git a/contrib/llvm-project/lldb/source/Core/DataFileCache.cpp b/contrib/llvm-project/lldb/source/Core/DataFileCache.cpp new file mode 100644 index 000000000000..a8127efc1df0 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/DataFileCache.cpp @@ -0,0 +1,322 @@ +//===-- DataFileCache.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/Core/DataFileCache.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleList.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Utility/DataEncoder.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "llvm/Support/CachePruning.h" + +using namespace lldb_private; + + +llvm::CachePruningPolicy DataFileCache::GetLLDBIndexCachePolicy() { + static llvm::CachePruningPolicy policy; + static llvm::once_flag once_flag; + + llvm::call_once(once_flag, []() { + // Prune the cache based off of the LLDB settings each time we create a + // cache object. + ModuleListProperties &properties = + ModuleList::GetGlobalModuleListProperties(); + // Only scan once an hour. If we have lots of debug sessions we don't want + // to scan this directory too often. A timestamp file is written to the + // directory to ensure different processes don't scan the directory too + // often. This setting doesn't mean that a thread will continually scan the + // cache directory within this process. + policy.Interval = std::chrono::hours(1); + // Get the user settings for pruning. + policy.MaxSizeBytes = properties.GetLLDBIndexCacheMaxByteSize(); + policy.MaxSizePercentageOfAvailableSpace = + properties.GetLLDBIndexCacheMaxPercent(); + policy.Expiration = + std::chrono::hours(properties.GetLLDBIndexCacheExpirationDays() * 24); + }); + return policy; +} + +DataFileCache::DataFileCache(llvm::StringRef path, llvm::CachePruningPolicy policy) { + m_cache_dir.SetPath(path); + pruneCache(path, policy); + + // This lambda will get called when the data is gotten from the cache and + // also after the data was set for a given key. We only need to take + // ownership of the data if we are geting the data, so we use the + // m_take_ownership member variable to indicate if we need to take + // ownership. + + auto add_buffer = [this](unsigned task, const llvm::Twine &moduleName, + std::unique_ptr<llvm::MemoryBuffer> m) { + if (m_take_ownership) + m_mem_buff_up = std::move(m); + }; + llvm::Expected<llvm::FileCache> cache_or_err = + llvm::localCache("LLDBModuleCache", "lldb-module", path, add_buffer); + if (cache_or_err) + m_cache_callback = std::move(*cache_or_err); + else { + Log *log = GetLog(LLDBLog::Modules); + LLDB_LOG_ERROR(log, cache_or_err.takeError(), + "failed to create lldb index cache directory: {0}"); + } +} + +std::unique_ptr<llvm::MemoryBuffer> +DataFileCache::GetCachedData(llvm::StringRef key) { + std::lock_guard<std::mutex> guard(m_mutex); + + const unsigned task = 1; + m_take_ownership = true; + // If we call the "m_cache_callback" function and the data is cached, it will + // call the "add_buffer" lambda function from the constructor which will in + // turn take ownership of the member buffer that is passed to the callback and + // put it into a member variable. + llvm::Expected<llvm::AddStreamFn> add_stream_or_err = + m_cache_callback(task, key, ""); + m_take_ownership = false; + // At this point we either already called the "add_buffer" lambda with + // the data or we haven't. We can tell if we got the cached data by checking + // the add_stream function pointer value below. + if (add_stream_or_err) { + llvm::AddStreamFn &add_stream = *add_stream_or_err; + // If the "add_stream" is nullptr, then the data was cached and we already + // called the "add_buffer" lambda. If it is valid, then if we were to call + // the add_stream function it would cause a cache file to get generated + // and we would be expected to fill in the data. In this function we only + // want to check if the data was cached, so we don't want to call + // "add_stream" in this function. + if (!add_stream) + return std::move(m_mem_buff_up); + } else { + Log *log = GetLog(LLDBLog::Modules); + LLDB_LOG_ERROR(log, add_stream_or_err.takeError(), + "failed to get the cache add stream callback for key: {0}"); + } + // Data was not cached. + return std::unique_ptr<llvm::MemoryBuffer>(); +} + +bool DataFileCache::SetCachedData(llvm::StringRef key, + llvm::ArrayRef<uint8_t> data) { + std::lock_guard<std::mutex> guard(m_mutex); + const unsigned task = 2; + // If we call this function and the data is cached, it will call the + // add_buffer lambda function from the constructor which will ignore the + // data. + llvm::Expected<llvm::AddStreamFn> add_stream_or_err = + m_cache_callback(task, key, ""); + // If we reach this code then we either already called the callback with + // the data or we haven't. We can tell if we had the cached data by checking + // the CacheAddStream function pointer value below. + if (add_stream_or_err) { + llvm::AddStreamFn &add_stream = *add_stream_or_err; + // If the "add_stream" is nullptr, then the data was cached. If it is + // valid, then if we call the add_stream function with a task it will + // cause the file to get generated, but we only want to check if the data + // is cached here, so we don't want to call it here. Note that the + // add_buffer will also get called in this case after the data has been + // provided, but we won't take ownership of the memory buffer as we just + // want to write the data. + if (add_stream) { + llvm::Expected<std::unique_ptr<llvm::CachedFileStream>> file_or_err = + add_stream(task, ""); + if (file_or_err) { + llvm::CachedFileStream *cfs = file_or_err->get(); + cfs->OS->write((const char *)data.data(), data.size()); + return true; + } else { + Log *log = GetLog(LLDBLog::Modules); + LLDB_LOG_ERROR(log, file_or_err.takeError(), + "failed to get the cache file stream for key: {0}"); + } + } + } else { + Log *log = GetLog(LLDBLog::Modules); + LLDB_LOG_ERROR(log, add_stream_or_err.takeError(), + "failed to get the cache add stream callback for key: {0}"); + } + return false; +} + +FileSpec DataFileCache::GetCacheFilePath(llvm::StringRef key) { + FileSpec cache_file(m_cache_dir); + std::string filename("llvmcache-"); + filename += key.str(); + cache_file.AppendPathComponent(filename); + return cache_file; +} + +Status DataFileCache::RemoveCacheFile(llvm::StringRef key) { + FileSpec cache_file = GetCacheFilePath(key); + FileSystem &fs = FileSystem::Instance(); + if (!fs.Exists(cache_file)) + return Status(); + return fs.RemoveFile(cache_file); +} + +CacheSignature::CacheSignature(lldb_private::Module *module) { + Clear(); + UUID uuid = module->GetUUID(); + if (uuid.IsValid()) + m_uuid = uuid; + + std::time_t mod_time = 0; + mod_time = llvm::sys::toTimeT(module->GetModificationTime()); + if (mod_time != 0) + m_mod_time = mod_time; + + mod_time = llvm::sys::toTimeT(module->GetObjectModificationTime()); + if (mod_time != 0) + m_obj_mod_time = mod_time; +} + +CacheSignature::CacheSignature(lldb_private::ObjectFile *objfile) { + Clear(); + UUID uuid = objfile->GetUUID(); + if (uuid.IsValid()) + m_uuid = uuid; + + std::time_t mod_time = 0; + // Grab the modification time of the object file's file. It isn't always the + // same as the module's file when you have a executable file as the main + // executable, and you have a object file for a symbol file. + FileSystem &fs = FileSystem::Instance(); + mod_time = llvm::sys::toTimeT(fs.GetModificationTime(objfile->GetFileSpec())); + if (mod_time != 0) + m_mod_time = mod_time; + + mod_time = + llvm::sys::toTimeT(objfile->GetModule()->GetObjectModificationTime()); + if (mod_time != 0) + m_obj_mod_time = mod_time; +} + +enum SignatureEncoding { + eSignatureUUID = 1u, + eSignatureModTime = 2u, + eSignatureObjectModTime = 3u, + eSignatureEnd = 255u, +}; + +bool CacheSignature::Encode(DataEncoder &encoder) const { + if (!IsValid()) + return false; // Invalid signature, return false! + + if (m_uuid) { + llvm::ArrayRef<uint8_t> uuid_bytes = m_uuid->GetBytes(); + encoder.AppendU8(eSignatureUUID); + encoder.AppendU8(uuid_bytes.size()); + encoder.AppendData(uuid_bytes); + } + if (m_mod_time) { + encoder.AppendU8(eSignatureModTime); + encoder.AppendU32(*m_mod_time); + } + if (m_obj_mod_time) { + encoder.AppendU8(eSignatureObjectModTime); + encoder.AppendU32(*m_obj_mod_time); + } + encoder.AppendU8(eSignatureEnd); + return true; +} + +bool CacheSignature::Decode(const lldb_private::DataExtractor &data, + lldb::offset_t *offset_ptr) { + Clear(); + while (uint8_t sig_encoding = data.GetU8(offset_ptr)) { + switch (sig_encoding) { + case eSignatureUUID: { + const uint8_t length = data.GetU8(offset_ptr); + const uint8_t *bytes = (const uint8_t *)data.GetData(offset_ptr, length); + if (bytes != nullptr && length > 0) + m_uuid = UUID(llvm::ArrayRef<uint8_t>(bytes, length)); + } break; + case eSignatureModTime: { + uint32_t mod_time = data.GetU32(offset_ptr); + if (mod_time > 0) + m_mod_time = mod_time; + } break; + case eSignatureObjectModTime: { + uint32_t mod_time = data.GetU32(offset_ptr); + if (mod_time > 0) + m_obj_mod_time = mod_time; + } break; + case eSignatureEnd: + // The definition of is valid changed to only be valid if the UUID is + // valid so make sure that if we attempt to decode an old cache file + // that we will fail to decode the cache file if the signature isn't + // considered valid. + return IsValid(); + default: + break; + } + } + return false; +} + +uint32_t ConstStringTable::Add(ConstString s) { + auto pos = m_string_to_offset.find(s); + if (pos != m_string_to_offset.end()) + return pos->second; + const uint32_t offset = m_next_offset; + m_strings.push_back(s); + m_string_to_offset[s] = offset; + m_next_offset += s.GetLength() + 1; + return offset; +} + +static const llvm::StringRef kStringTableIdentifier("STAB"); + +bool ConstStringTable::Encode(DataEncoder &encoder) { + // Write an 4 character code into the stream. This will help us when decoding + // to make sure we find this identifier when decoding the string table to make + // sure we have the rigth data. It also helps to identify the string table + // when dumping the hex bytes in a cache file. + encoder.AppendData(kStringTableIdentifier); + size_t length_offset = encoder.GetByteSize(); + encoder.AppendU32(0); // Total length of all strings which will be fixed up. + size_t strtab_offset = encoder.GetByteSize(); + encoder.AppendU8(0); // Start the string table with an empty string. + for (auto s: m_strings) { + // Make sure all of the offsets match up with what we handed out! + assert(m_string_to_offset.find(s)->second == + encoder.GetByteSize() - strtab_offset); + // Append the C string into the encoder + encoder.AppendCString(s.GetStringRef()); + } + // Fixup the string table length. + encoder.PutU32(length_offset, encoder.GetByteSize() - strtab_offset); + return true; +} + +bool StringTableReader::Decode(const lldb_private::DataExtractor &data, + lldb::offset_t *offset_ptr) { + llvm::StringRef identifier((const char *)data.GetData(offset_ptr, 4), 4); + if (identifier != kStringTableIdentifier) + return false; + const uint32_t length = data.GetU32(offset_ptr); + // We always have at least one byte for the empty string at offset zero. + if (length == 0) + return false; + const char *bytes = (const char *)data.GetData(offset_ptr, length); + if (bytes == nullptr) + return false; + m_data = llvm::StringRef(bytes, length); + return true; +} + +llvm::StringRef StringTableReader::Get(uint32_t offset) const { + if (offset >= m_data.size()) + return llvm::StringRef(); + return llvm::StringRef(m_data.data() + offset); +} + diff --git a/contrib/llvm-project/lldb/source/Core/Debugger.cpp b/contrib/llvm-project/lldb/source/Core/Debugger.cpp new file mode 100644 index 000000000000..309e01e45658 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/Debugger.cpp @@ -0,0 +1,2238 @@ +//===-- Debugger.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/Core/Debugger.h" + +#include "lldb/Breakpoint/Breakpoint.h" +#include "lldb/Core/DebuggerEvents.h" +#include "lldb/Core/FormatEntity.h" +#include "lldb/Core/Mangled.h" +#include "lldb/Core/ModuleList.h" +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/Progress.h" +#include "lldb/Core/StreamAsynchronousIO.h" +#include "lldb/DataFormatters/DataVisualization.h" +#include "lldb/Expression/REPL.h" +#include "lldb/Host/File.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Host/StreamFile.h" +#include "lldb/Host/Terminal.h" +#include "lldb/Host/ThreadLauncher.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionValue.h" +#include "lldb/Interpreter/OptionValueLanguage.h" +#include "lldb/Interpreter/OptionValueProperties.h" +#include "lldb/Interpreter/OptionValueSInt64.h" +#include "lldb/Interpreter/OptionValueString.h" +#include "lldb/Interpreter/Property.h" +#include "lldb/Interpreter/ScriptInterpreter.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Target/Language.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/StructuredDataPlugin.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/TargetList.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadList.h" +#include "lldb/Utility/AnsiTerminal.h" +#include "lldb/Utility/Event.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Listener.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/State.h" +#include "lldb/Utility/Stream.h" +#include "lldb/Utility/StreamString.h" +#include "lldb/lldb-enumerations.h" + +#if defined(_WIN32) +#include "lldb/Host/windows/PosixApi.h" +#include "lldb/Host/windows/windows.h" +#endif + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/iterator.h" +#include "llvm/Support/DynamicLibrary.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/ThreadPool.h" +#include "llvm/Support/Threading.h" +#include "llvm/Support/raw_ostream.h" + +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <list> +#include <memory> +#include <mutex> +#include <optional> +#include <set> +#include <string> +#include <system_error> + +// Includes for pipe() +#if defined(_WIN32) +#include <fcntl.h> +#include <io.h> +#else +#include <unistd.h> +#endif + +namespace lldb_private { +class Address; +} + +using namespace lldb; +using namespace lldb_private; + +static lldb::user_id_t g_unique_id = 1; +static size_t g_debugger_event_thread_stack_bytes = 8 * 1024 * 1024; + +#pragma mark Static Functions + +static std::recursive_mutex *g_debugger_list_mutex_ptr = + nullptr; // NOTE: intentional leak to avoid issues with C++ destructor chain +static Debugger::DebuggerList *g_debugger_list_ptr = + nullptr; // NOTE: intentional leak to avoid issues with C++ destructor chain +static llvm::DefaultThreadPool *g_thread_pool = nullptr; + +static constexpr OptionEnumValueElement g_show_disassembly_enum_values[] = { + { + Debugger::eStopDisassemblyTypeNever, + "never", + "Never show disassembly when displaying a stop context.", + }, + { + Debugger::eStopDisassemblyTypeNoDebugInfo, + "no-debuginfo", + "Show disassembly when there is no debug information.", + }, + { + Debugger::eStopDisassemblyTypeNoSource, + "no-source", + "Show disassembly when there is no source information, or the source " + "file " + "is missing when displaying a stop context.", + }, + { + Debugger::eStopDisassemblyTypeAlways, + "always", + "Always show disassembly when displaying a stop context.", + }, +}; + +static constexpr OptionEnumValueElement g_language_enumerators[] = { + { + eScriptLanguageNone, + "none", + "Disable scripting languages.", + }, + { + eScriptLanguagePython, + "python", + "Select python as the default scripting language.", + }, + { + eScriptLanguageDefault, + "default", + "Select the lldb default as the default scripting language.", + }, +}; + +static constexpr OptionEnumValueElement g_dwim_print_verbosities[] = { + {eDWIMPrintVerbosityNone, "none", + "Use no verbosity when running dwim-print."}, + {eDWIMPrintVerbosityExpression, "expression", + "Use partial verbosity when running dwim-print - display a message when " + "`expression` evaluation is used."}, + {eDWIMPrintVerbosityFull, "full", + "Use full verbosity when running dwim-print."}, +}; + +static constexpr OptionEnumValueElement s_stop_show_column_values[] = { + { + eStopShowColumnAnsiOrCaret, + "ansi-or-caret", + "Highlight the stop column with ANSI terminal codes when color/ANSI " + "mode is enabled; otherwise, fall back to using a text-only caret (^) " + "as if \"caret-only\" mode was selected.", + }, + { + eStopShowColumnAnsi, + "ansi", + "Highlight the stop column with ANSI terminal codes when running LLDB " + "with color/ANSI enabled.", + }, + { + eStopShowColumnCaret, + "caret", + "Highlight the stop column with a caret character (^) underneath the " + "stop column. This method introduces a new line in source listings " + "that display thread stop locations.", + }, + { + eStopShowColumnNone, + "none", + "Do not highlight the stop column.", + }, +}; + +#define LLDB_PROPERTIES_debugger +#include "CoreProperties.inc" + +enum { +#define LLDB_PROPERTIES_debugger +#include "CorePropertiesEnum.inc" +}; + +LoadPluginCallbackType Debugger::g_load_plugin_callback = nullptr; + +Status Debugger::SetPropertyValue(const ExecutionContext *exe_ctx, + VarSetOperationType op, + llvm::StringRef property_path, + llvm::StringRef value) { + bool is_load_script = + (property_path == "target.load-script-from-symbol-file"); + // These properties might change how we visualize data. + bool invalidate_data_vis = (property_path == "escape-non-printables"); + invalidate_data_vis |= + (property_path == "target.max-zero-padding-in-float-format"); + if (invalidate_data_vis) { + DataVisualization::ForceUpdate(); + } + + TargetSP target_sp; + LoadScriptFromSymFile load_script_old_value = eLoadScriptFromSymFileFalse; + if (is_load_script && exe_ctx && exe_ctx->GetTargetSP()) { + target_sp = exe_ctx->GetTargetSP(); + load_script_old_value = + target_sp->TargetProperties::GetLoadScriptFromSymbolFile(); + } + Status error(Properties::SetPropertyValue(exe_ctx, op, property_path, value)); + if (error.Success()) { + // FIXME it would be nice to have "on-change" callbacks for properties + if (property_path == g_debugger_properties[ePropertyPrompt].name) { + llvm::StringRef new_prompt = GetPrompt(); + std::string str = lldb_private::ansi::FormatAnsiTerminalCodes( + new_prompt, GetUseColor()); + if (str.length()) + new_prompt = str; + GetCommandInterpreter().UpdatePrompt(new_prompt); + auto bytes = std::make_unique<EventDataBytes>(new_prompt); + auto prompt_change_event_sp = std::make_shared<Event>( + CommandInterpreter::eBroadcastBitResetPrompt, bytes.release()); + GetCommandInterpreter().BroadcastEvent(prompt_change_event_sp); + } else if (property_path == g_debugger_properties[ePropertyUseColor].name) { + // use-color changed. Ping the prompt so it can reset the ansi terminal + // codes. + SetPrompt(GetPrompt()); + } else if (property_path == + g_debugger_properties[ePropertyPromptAnsiPrefix].name || + property_path == + g_debugger_properties[ePropertyPromptAnsiSuffix].name) { + // Prompt colors changed. Ping the prompt so it can reset the ansi + // terminal codes. + SetPrompt(GetPrompt()); + } else if (property_path == + g_debugger_properties[ePropertyUseSourceCache].name) { + // use-source-cache changed. Wipe out the cache contents if it was + // disabled. + if (!GetUseSourceCache()) { + m_source_file_cache.Clear(); + } + } else if (is_load_script && target_sp && + load_script_old_value == eLoadScriptFromSymFileWarn) { + if (target_sp->TargetProperties::GetLoadScriptFromSymbolFile() == + eLoadScriptFromSymFileTrue) { + std::list<Status> errors; + StreamString feedback_stream; + if (!target_sp->LoadScriptingResources(errors, feedback_stream)) { + Stream &s = GetErrorStream(); + for (auto error : errors) { + s.Printf("%s\n", error.AsCString()); + } + if (feedback_stream.GetSize()) + s.PutCString(feedback_stream.GetString()); + } + } + } + } + return error; +} + +bool Debugger::GetAutoConfirm() const { + constexpr uint32_t idx = ePropertyAutoConfirm; + return GetPropertyAtIndexAs<bool>( + idx, g_debugger_properties[idx].default_uint_value != 0); +} + +const FormatEntity::Entry *Debugger::GetDisassemblyFormat() const { + constexpr uint32_t idx = ePropertyDisassemblyFormat; + return GetPropertyAtIndexAs<const FormatEntity::Entry *>(idx); +} + +const FormatEntity::Entry *Debugger::GetFrameFormat() const { + constexpr uint32_t idx = ePropertyFrameFormat; + return GetPropertyAtIndexAs<const FormatEntity::Entry *>(idx); +} + +const FormatEntity::Entry *Debugger::GetFrameFormatUnique() const { + constexpr uint32_t idx = ePropertyFrameFormatUnique; + return GetPropertyAtIndexAs<const FormatEntity::Entry *>(idx); +} + +uint64_t Debugger::GetStopDisassemblyMaxSize() const { + constexpr uint32_t idx = ePropertyStopDisassemblyMaxSize; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_debugger_properties[idx].default_uint_value); +} + +bool Debugger::GetNotifyVoid() const { + constexpr uint32_t idx = ePropertyNotiftVoid; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_debugger_properties[idx].default_uint_value != 0); +} + +llvm::StringRef Debugger::GetPrompt() const { + constexpr uint32_t idx = ePropertyPrompt; + return GetPropertyAtIndexAs<llvm::StringRef>( + idx, g_debugger_properties[idx].default_cstr_value); +} + +llvm::StringRef Debugger::GetPromptAnsiPrefix() const { + const uint32_t idx = ePropertyPromptAnsiPrefix; + return GetPropertyAtIndexAs<llvm::StringRef>( + idx, g_debugger_properties[idx].default_cstr_value); +} + +llvm::StringRef Debugger::GetPromptAnsiSuffix() const { + const uint32_t idx = ePropertyPromptAnsiSuffix; + return GetPropertyAtIndexAs<llvm::StringRef>( + idx, g_debugger_properties[idx].default_cstr_value); +} + +void Debugger::SetPrompt(llvm::StringRef p) { + constexpr uint32_t idx = ePropertyPrompt; + SetPropertyAtIndex(idx, p); + llvm::StringRef new_prompt = GetPrompt(); + std::string str = + lldb_private::ansi::FormatAnsiTerminalCodes(new_prompt, GetUseColor()); + if (str.length()) + new_prompt = str; + GetCommandInterpreter().UpdatePrompt(new_prompt); +} + +const FormatEntity::Entry *Debugger::GetThreadFormat() const { + constexpr uint32_t idx = ePropertyThreadFormat; + return GetPropertyAtIndexAs<const FormatEntity::Entry *>(idx); +} + +const FormatEntity::Entry *Debugger::GetThreadStopFormat() const { + constexpr uint32_t idx = ePropertyThreadStopFormat; + return GetPropertyAtIndexAs<const FormatEntity::Entry *>(idx); +} + +lldb::ScriptLanguage Debugger::GetScriptLanguage() const { + const uint32_t idx = ePropertyScriptLanguage; + return GetPropertyAtIndexAs<lldb::ScriptLanguage>( + idx, static_cast<lldb::ScriptLanguage>( + g_debugger_properties[idx].default_uint_value)); +} + +bool Debugger::SetScriptLanguage(lldb::ScriptLanguage script_lang) { + const uint32_t idx = ePropertyScriptLanguage; + return SetPropertyAtIndex(idx, script_lang); +} + +lldb::LanguageType Debugger::GetREPLLanguage() const { + const uint32_t idx = ePropertyREPLLanguage; + return GetPropertyAtIndexAs<LanguageType>(idx, {}); +} + +bool Debugger::SetREPLLanguage(lldb::LanguageType repl_lang) { + const uint32_t idx = ePropertyREPLLanguage; + return SetPropertyAtIndex(idx, repl_lang); +} + +uint64_t Debugger::GetTerminalWidth() const { + const uint32_t idx = ePropertyTerminalWidth; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_debugger_properties[idx].default_uint_value); +} + +bool Debugger::SetTerminalWidth(uint64_t term_width) { + if (auto handler_sp = m_io_handler_stack.Top()) + handler_sp->TerminalSizeChanged(); + + const uint32_t idx = ePropertyTerminalWidth; + return SetPropertyAtIndex(idx, term_width); +} + +bool Debugger::GetUseExternalEditor() const { + const uint32_t idx = ePropertyUseExternalEditor; + return GetPropertyAtIndexAs<bool>( + idx, g_debugger_properties[idx].default_uint_value != 0); +} + +bool Debugger::SetUseExternalEditor(bool b) { + const uint32_t idx = ePropertyUseExternalEditor; + return SetPropertyAtIndex(idx, b); +} + +llvm::StringRef Debugger::GetExternalEditor() const { + const uint32_t idx = ePropertyExternalEditor; + return GetPropertyAtIndexAs<llvm::StringRef>( + idx, g_debugger_properties[idx].default_cstr_value); +} + +bool Debugger::SetExternalEditor(llvm::StringRef editor) { + const uint32_t idx = ePropertyExternalEditor; + return SetPropertyAtIndex(idx, editor); +} + +bool Debugger::GetUseColor() const { + const uint32_t idx = ePropertyUseColor; + return GetPropertyAtIndexAs<bool>( + idx, g_debugger_properties[idx].default_uint_value != 0); +} + +bool Debugger::SetUseColor(bool b) { + const uint32_t idx = ePropertyUseColor; + bool ret = SetPropertyAtIndex(idx, b); + SetPrompt(GetPrompt()); + return ret; +} + +bool Debugger::GetShowProgress() const { + const uint32_t idx = ePropertyShowProgress; + return GetPropertyAtIndexAs<bool>( + idx, g_debugger_properties[idx].default_uint_value != 0); +} + +bool Debugger::SetShowProgress(bool show_progress) { + const uint32_t idx = ePropertyShowProgress; + return SetPropertyAtIndex(idx, show_progress); +} + +llvm::StringRef Debugger::GetShowProgressAnsiPrefix() const { + const uint32_t idx = ePropertyShowProgressAnsiPrefix; + return GetPropertyAtIndexAs<llvm::StringRef>( + idx, g_debugger_properties[idx].default_cstr_value); +} + +llvm::StringRef Debugger::GetShowProgressAnsiSuffix() const { + const uint32_t idx = ePropertyShowProgressAnsiSuffix; + return GetPropertyAtIndexAs<llvm::StringRef>( + idx, g_debugger_properties[idx].default_cstr_value); +} + +bool Debugger::GetUseAutosuggestion() const { + const uint32_t idx = ePropertyShowAutosuggestion; + return GetPropertyAtIndexAs<bool>( + idx, g_debugger_properties[idx].default_uint_value != 0); +} + +llvm::StringRef Debugger::GetAutosuggestionAnsiPrefix() const { + const uint32_t idx = ePropertyShowAutosuggestionAnsiPrefix; + return GetPropertyAtIndexAs<llvm::StringRef>( + idx, g_debugger_properties[idx].default_cstr_value); +} + +llvm::StringRef Debugger::GetAutosuggestionAnsiSuffix() const { + const uint32_t idx = ePropertyShowAutosuggestionAnsiSuffix; + return GetPropertyAtIndexAs<llvm::StringRef>( + idx, g_debugger_properties[idx].default_cstr_value); +} + +llvm::StringRef Debugger::GetRegexMatchAnsiPrefix() const { + const uint32_t idx = ePropertyShowRegexMatchAnsiPrefix; + return GetPropertyAtIndexAs<llvm::StringRef>( + idx, g_debugger_properties[idx].default_cstr_value); +} + +llvm::StringRef Debugger::GetRegexMatchAnsiSuffix() const { + const uint32_t idx = ePropertyShowRegexMatchAnsiSuffix; + return GetPropertyAtIndexAs<llvm::StringRef>( + idx, g_debugger_properties[idx].default_cstr_value); +} + +bool Debugger::GetShowDontUsePoHint() const { + const uint32_t idx = ePropertyShowDontUsePoHint; + return GetPropertyAtIndexAs<bool>( + idx, g_debugger_properties[idx].default_uint_value != 0); +} + +bool Debugger::GetUseSourceCache() const { + const uint32_t idx = ePropertyUseSourceCache; + return GetPropertyAtIndexAs<bool>( + idx, g_debugger_properties[idx].default_uint_value != 0); +} + +bool Debugger::SetUseSourceCache(bool b) { + const uint32_t idx = ePropertyUseSourceCache; + bool ret = SetPropertyAtIndex(idx, b); + if (!ret) { + m_source_file_cache.Clear(); + } + return ret; +} +bool Debugger::GetHighlightSource() const { + const uint32_t idx = ePropertyHighlightSource; + return GetPropertyAtIndexAs<bool>( + idx, g_debugger_properties[idx].default_uint_value != 0); +} + +StopShowColumn Debugger::GetStopShowColumn() const { + const uint32_t idx = ePropertyStopShowColumn; + return GetPropertyAtIndexAs<lldb::StopShowColumn>( + idx, static_cast<lldb::StopShowColumn>( + g_debugger_properties[idx].default_uint_value)); +} + +llvm::StringRef Debugger::GetStopShowColumnAnsiPrefix() const { + const uint32_t idx = ePropertyStopShowColumnAnsiPrefix; + return GetPropertyAtIndexAs<llvm::StringRef>( + idx, g_debugger_properties[idx].default_cstr_value); +} + +llvm::StringRef Debugger::GetStopShowColumnAnsiSuffix() const { + const uint32_t idx = ePropertyStopShowColumnAnsiSuffix; + return GetPropertyAtIndexAs<llvm::StringRef>( + idx, g_debugger_properties[idx].default_cstr_value); +} + +llvm::StringRef Debugger::GetStopShowLineMarkerAnsiPrefix() const { + const uint32_t idx = ePropertyStopShowLineMarkerAnsiPrefix; + return GetPropertyAtIndexAs<llvm::StringRef>( + idx, g_debugger_properties[idx].default_cstr_value); +} + +llvm::StringRef Debugger::GetStopShowLineMarkerAnsiSuffix() const { + const uint32_t idx = ePropertyStopShowLineMarkerAnsiSuffix; + return GetPropertyAtIndexAs<llvm::StringRef>( + idx, g_debugger_properties[idx].default_cstr_value); +} + +uint64_t Debugger::GetStopSourceLineCount(bool before) const { + const uint32_t idx = + before ? ePropertyStopLineCountBefore : ePropertyStopLineCountAfter; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_debugger_properties[idx].default_uint_value); +} + +Debugger::StopDisassemblyType Debugger::GetStopDisassemblyDisplay() const { + const uint32_t idx = ePropertyStopDisassemblyDisplay; + return GetPropertyAtIndexAs<Debugger::StopDisassemblyType>( + idx, static_cast<Debugger::StopDisassemblyType>( + g_debugger_properties[idx].default_uint_value)); +} + +uint64_t Debugger::GetDisassemblyLineCount() const { + const uint32_t idx = ePropertyStopDisassemblyCount; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_debugger_properties[idx].default_uint_value); +} + +bool Debugger::GetAutoOneLineSummaries() const { + const uint32_t idx = ePropertyAutoOneLineSummaries; + return GetPropertyAtIndexAs<bool>( + idx, g_debugger_properties[idx].default_uint_value != 0); +} + +bool Debugger::GetEscapeNonPrintables() const { + const uint32_t idx = ePropertyEscapeNonPrintables; + return GetPropertyAtIndexAs<bool>( + idx, g_debugger_properties[idx].default_uint_value != 0); +} + +bool Debugger::GetAutoIndent() const { + const uint32_t idx = ePropertyAutoIndent; + return GetPropertyAtIndexAs<bool>( + idx, g_debugger_properties[idx].default_uint_value != 0); +} + +bool Debugger::SetAutoIndent(bool b) { + const uint32_t idx = ePropertyAutoIndent; + return SetPropertyAtIndex(idx, b); +} + +bool Debugger::GetPrintDecls() const { + const uint32_t idx = ePropertyPrintDecls; + return GetPropertyAtIndexAs<bool>( + idx, g_debugger_properties[idx].default_uint_value != 0); +} + +bool Debugger::SetPrintDecls(bool b) { + const uint32_t idx = ePropertyPrintDecls; + return SetPropertyAtIndex(idx, b); +} + +uint64_t Debugger::GetTabSize() const { + const uint32_t idx = ePropertyTabSize; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_debugger_properties[idx].default_uint_value); +} + +bool Debugger::SetTabSize(uint64_t tab_size) { + const uint32_t idx = ePropertyTabSize; + return SetPropertyAtIndex(idx, tab_size); +} + +lldb::DWIMPrintVerbosity Debugger::GetDWIMPrintVerbosity() const { + const uint32_t idx = ePropertyDWIMPrintVerbosity; + return GetPropertyAtIndexAs<lldb::DWIMPrintVerbosity>( + idx, static_cast<lldb::DWIMPrintVerbosity>( + g_debugger_properties[idx].default_uint_value)); +} + +#pragma mark Debugger + +// const DebuggerPropertiesSP & +// Debugger::GetSettings() const +//{ +// return m_properties_sp; +//} +// + +void Debugger::Initialize(LoadPluginCallbackType load_plugin_callback) { + assert(g_debugger_list_ptr == nullptr && + "Debugger::Initialize called more than once!"); + g_debugger_list_mutex_ptr = new std::recursive_mutex(); + g_debugger_list_ptr = new DebuggerList(); + g_thread_pool = new llvm::DefaultThreadPool(llvm::optimal_concurrency()); + g_load_plugin_callback = load_plugin_callback; +} + +void Debugger::Terminate() { + assert(g_debugger_list_ptr && + "Debugger::Terminate called without a matching Debugger::Initialize!"); + + if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { + std::lock_guard<std::recursive_mutex> guard(*g_debugger_list_mutex_ptr); + for (const auto &debugger : *g_debugger_list_ptr) + debugger->HandleDestroyCallback(); + } + + if (g_thread_pool) { + // The destructor will wait for all the threads to complete. + delete g_thread_pool; + } + + if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { + // Clear our global list of debugger objects + { + std::lock_guard<std::recursive_mutex> guard(*g_debugger_list_mutex_ptr); + for (const auto &debugger : *g_debugger_list_ptr) + debugger->Clear(); + g_debugger_list_ptr->clear(); + } + } +} + +void Debugger::SettingsInitialize() { Target::SettingsInitialize(); } + +void Debugger::SettingsTerminate() { Target::SettingsTerminate(); } + +bool Debugger::LoadPlugin(const FileSpec &spec, Status &error) { + if (g_load_plugin_callback) { + llvm::sys::DynamicLibrary dynlib = + g_load_plugin_callback(shared_from_this(), spec, error); + if (dynlib.isValid()) { + m_loaded_plugins.push_back(dynlib); + return true; + } + } else { + // The g_load_plugin_callback is registered in SBDebugger::Initialize() and + // if the public API layer isn't available (code is linking against all of + // the internal LLDB static libraries), then we can't load plugins + error.SetErrorString("Public API layer is not available"); + } + return false; +} + +static FileSystem::EnumerateDirectoryResult +LoadPluginCallback(void *baton, llvm::sys::fs::file_type ft, + llvm::StringRef path) { + Status error; + + static constexpr llvm::StringLiteral g_dylibext(".dylib"); + static constexpr llvm::StringLiteral g_solibext(".so"); + + if (!baton) + return FileSystem::eEnumerateDirectoryResultQuit; + + Debugger *debugger = (Debugger *)baton; + + namespace fs = llvm::sys::fs; + // If we have a regular file, a symbolic link or unknown file type, try and + // process the file. We must handle unknown as sometimes the directory + // enumeration might be enumerating a file system that doesn't have correct + // file type information. + if (ft == fs::file_type::regular_file || ft == fs::file_type::symlink_file || + ft == fs::file_type::type_unknown) { + FileSpec plugin_file_spec(path); + FileSystem::Instance().Resolve(plugin_file_spec); + + if (plugin_file_spec.GetFileNameExtension() != g_dylibext && + plugin_file_spec.GetFileNameExtension() != g_solibext) { + return FileSystem::eEnumerateDirectoryResultNext; + } + + Status plugin_load_error; + debugger->LoadPlugin(plugin_file_spec, plugin_load_error); + + return FileSystem::eEnumerateDirectoryResultNext; + } else if (ft == fs::file_type::directory_file || + ft == fs::file_type::symlink_file || + ft == fs::file_type::type_unknown) { + // Try and recurse into anything that a directory or symbolic link. We must + // also do this for unknown as sometimes the directory enumeration might be + // enumerating a file system that doesn't have correct file type + // information. + return FileSystem::eEnumerateDirectoryResultEnter; + } + + return FileSystem::eEnumerateDirectoryResultNext; +} + +void Debugger::InstanceInitialize() { + const bool find_directories = true; + const bool find_files = true; + const bool find_other = true; + char dir_path[PATH_MAX]; + if (FileSpec dir_spec = HostInfo::GetSystemPluginDir()) { + if (FileSystem::Instance().Exists(dir_spec) && + dir_spec.GetPath(dir_path, sizeof(dir_path))) { + FileSystem::Instance().EnumerateDirectory(dir_path, find_directories, + find_files, find_other, + LoadPluginCallback, this); + } + } + + if (FileSpec dir_spec = HostInfo::GetUserPluginDir()) { + if (FileSystem::Instance().Exists(dir_spec) && + dir_spec.GetPath(dir_path, sizeof(dir_path))) { + FileSystem::Instance().EnumerateDirectory(dir_path, find_directories, + find_files, find_other, + LoadPluginCallback, this); + } + } + + PluginManager::DebuggerInitialize(*this); +} + +DebuggerSP Debugger::CreateInstance(lldb::LogOutputCallback log_callback, + void *baton) { + DebuggerSP debugger_sp(new Debugger(log_callback, baton)); + if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { + std::lock_guard<std::recursive_mutex> guard(*g_debugger_list_mutex_ptr); + g_debugger_list_ptr->push_back(debugger_sp); + } + debugger_sp->InstanceInitialize(); + return debugger_sp; +} + +void Debugger::HandleDestroyCallback() { + const lldb::user_id_t user_id = GetID(); + // Invoke and remove all the callbacks in an FIFO order. Callbacks which are + // added during this loop will be appended, invoked and then removed last. + // Callbacks which are removed during this loop will not be invoked. + while (true) { + DestroyCallbackInfo callback_info; + { + std::lock_guard<std::mutex> guard(m_destroy_callback_mutex); + if (m_destroy_callbacks.empty()) + break; + // Pop the first item in the list + callback_info = m_destroy_callbacks.front(); + m_destroy_callbacks.erase(m_destroy_callbacks.begin()); + } + // Call the destroy callback with user id and baton + callback_info.callback(user_id, callback_info.baton); + } +} + +void Debugger::Destroy(DebuggerSP &debugger_sp) { + if (!debugger_sp) + return; + + debugger_sp->HandleDestroyCallback(); + CommandInterpreter &cmd_interpreter = debugger_sp->GetCommandInterpreter(); + + if (cmd_interpreter.GetSaveSessionOnQuit()) { + CommandReturnObject result(debugger_sp->GetUseColor()); + cmd_interpreter.SaveTranscript(result); + if (result.Succeeded()) + (*debugger_sp->GetAsyncOutputStream()) << result.GetOutputData() << '\n'; + else + (*debugger_sp->GetAsyncErrorStream()) << result.GetErrorData() << '\n'; + } + + debugger_sp->Clear(); + + if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { + std::lock_guard<std::recursive_mutex> guard(*g_debugger_list_mutex_ptr); + DebuggerList::iterator pos, end = g_debugger_list_ptr->end(); + for (pos = g_debugger_list_ptr->begin(); pos != end; ++pos) { + if ((*pos).get() == debugger_sp.get()) { + g_debugger_list_ptr->erase(pos); + return; + } + } + } +} + +DebuggerSP +Debugger::FindDebuggerWithInstanceName(llvm::StringRef instance_name) { + if (!g_debugger_list_ptr || !g_debugger_list_mutex_ptr) + return DebuggerSP(); + + std::lock_guard<std::recursive_mutex> guard(*g_debugger_list_mutex_ptr); + for (const DebuggerSP &debugger_sp : *g_debugger_list_ptr) { + if (!debugger_sp) + continue; + + if (llvm::StringRef(debugger_sp->GetInstanceName()) == instance_name) + return debugger_sp; + } + return DebuggerSP(); +} + +TargetSP Debugger::FindTargetWithProcessID(lldb::pid_t pid) { + TargetSP target_sp; + if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { + std::lock_guard<std::recursive_mutex> guard(*g_debugger_list_mutex_ptr); + DebuggerList::iterator pos, end = g_debugger_list_ptr->end(); + for (pos = g_debugger_list_ptr->begin(); pos != end; ++pos) { + target_sp = (*pos)->GetTargetList().FindTargetWithProcessID(pid); + if (target_sp) + break; + } + } + return target_sp; +} + +TargetSP Debugger::FindTargetWithProcess(Process *process) { + TargetSP target_sp; + if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { + std::lock_guard<std::recursive_mutex> guard(*g_debugger_list_mutex_ptr); + DebuggerList::iterator pos, end = g_debugger_list_ptr->end(); + for (pos = g_debugger_list_ptr->begin(); pos != end; ++pos) { + target_sp = (*pos)->GetTargetList().FindTargetWithProcess(process); + if (target_sp) + break; + } + } + return target_sp; +} + +llvm::StringRef Debugger::GetStaticBroadcasterClass() { + static constexpr llvm::StringLiteral class_name("lldb.debugger"); + return class_name; +} + +Debugger::Debugger(lldb::LogOutputCallback log_callback, void *baton) + : UserID(g_unique_id++), + Properties(std::make_shared<OptionValueProperties>()), + m_input_file_sp(std::make_shared<NativeFile>(stdin, false)), + m_output_stream_sp(std::make_shared<StreamFile>(stdout, false)), + m_error_stream_sp(std::make_shared<StreamFile>(stderr, false)), + m_input_recorder(nullptr), + m_broadcaster_manager_sp(BroadcasterManager::MakeBroadcasterManager()), + m_terminal_state(), m_target_list(*this), m_platform_list(), + m_listener_sp(Listener::MakeListener("lldb.Debugger")), + m_source_manager_up(), m_source_file_cache(), + m_command_interpreter_up( + std::make_unique<CommandInterpreter>(*this, false)), + m_io_handler_stack(), + m_instance_name(llvm::formatv("debugger_{0}", GetID()).str()), + m_loaded_plugins(), m_event_handler_thread(), m_io_handler_thread(), + m_sync_broadcaster(nullptr, "lldb.debugger.sync"), + m_broadcaster(m_broadcaster_manager_sp, + GetStaticBroadcasterClass().str()), + m_forward_listener_sp(), m_clear_once() { + // Initialize the debugger properties as early as possible as other parts of + // LLDB will start querying them during construction. + m_collection_sp->Initialize(g_debugger_properties); + m_collection_sp->AppendProperty( + "target", "Settings specify to debugging targets.", true, + Target::GetGlobalProperties().GetValueProperties()); + m_collection_sp->AppendProperty( + "platform", "Platform settings.", true, + Platform::GetGlobalPlatformProperties().GetValueProperties()); + m_collection_sp->AppendProperty( + "symbols", "Symbol lookup and cache settings.", true, + ModuleList::GetGlobalModuleListProperties().GetValueProperties()); + m_collection_sp->AppendProperty( + LanguageProperties::GetSettingName(), "Language settings.", true, + Language::GetGlobalLanguageProperties().GetValueProperties()); + if (m_command_interpreter_up) { + m_collection_sp->AppendProperty( + "interpreter", + "Settings specify to the debugger's command interpreter.", true, + m_command_interpreter_up->GetValueProperties()); + } + if (log_callback) + m_callback_handler_sp = + std::make_shared<CallbackLogHandler>(log_callback, baton); + m_command_interpreter_up->Initialize(); + // Always add our default platform to the platform list + PlatformSP default_platform_sp(Platform::GetHostPlatform()); + assert(default_platform_sp); + m_platform_list.Append(default_platform_sp, true); + + // Create the dummy target. + { + ArchSpec arch(Target::GetDefaultArchitecture()); + if (!arch.IsValid()) + arch = HostInfo::GetArchitecture(); + assert(arch.IsValid() && "No valid default or host archspec"); + const bool is_dummy_target = true; + m_dummy_target_sp.reset( + new Target(*this, arch, default_platform_sp, is_dummy_target)); + } + assert(m_dummy_target_sp.get() && "Couldn't construct dummy target?"); + + OptionValueUInt64 *term_width = + m_collection_sp->GetPropertyAtIndexAsOptionValueUInt64( + ePropertyTerminalWidth); + term_width->SetMinimumValue(10); + term_width->SetMaximumValue(1024); + + // Turn off use-color if this is a dumb terminal. + const char *term = getenv("TERM"); + if (term && !strcmp(term, "dumb")) + SetUseColor(false); + // Turn off use-color if we don't write to a terminal with color support. + if (!GetOutputFile().GetIsTerminalWithColors()) + SetUseColor(false); + + if (Diagnostics::Enabled()) { + m_diagnostics_callback_id = Diagnostics::Instance().AddCallback( + [this](const FileSpec &dir) -> llvm::Error { + for (auto &entry : m_stream_handlers) { + llvm::StringRef log_path = entry.first(); + llvm::StringRef file_name = llvm::sys::path::filename(log_path); + FileSpec destination = dir.CopyByAppendingPathComponent(file_name); + std::error_code ec = + llvm::sys::fs::copy_file(log_path, destination.GetPath()); + if (ec) + return llvm::errorCodeToError(ec); + } + return llvm::Error::success(); + }); + } + +#if defined(_WIN32) && defined(ENABLE_VIRTUAL_TERMINAL_PROCESSING) + // Enabling use of ANSI color codes because LLDB is using them to highlight + // text. + llvm::sys::Process::UseANSIEscapeCodes(true); +#endif +} + +Debugger::~Debugger() { Clear(); } + +void Debugger::Clear() { + // Make sure we call this function only once. With the C++ global destructor + // chain having a list of debuggers and with code that can be running on + // other threads, we need to ensure this doesn't happen multiple times. + // + // The following functions call Debugger::Clear(): + // Debugger::~Debugger(); + // static void Debugger::Destroy(lldb::DebuggerSP &debugger_sp); + // static void Debugger::Terminate(); + llvm::call_once(m_clear_once, [this]() { + ClearIOHandlers(); + StopIOHandlerThread(); + StopEventHandlerThread(); + m_listener_sp->Clear(); + for (TargetSP target_sp : m_target_list.Targets()) { + if (target_sp) { + if (ProcessSP process_sp = target_sp->GetProcessSP()) + process_sp->Finalize(false /* not destructing */); + target_sp->Destroy(); + } + } + m_broadcaster_manager_sp->Clear(); + + // Close the input file _before_ we close the input read communications + // class as it does NOT own the input file, our m_input_file does. + m_terminal_state.Clear(); + GetInputFile().Close(); + + m_command_interpreter_up->Clear(); + + if (Diagnostics::Enabled()) + Diagnostics::Instance().RemoveCallback(m_diagnostics_callback_id); + }); +} + +bool Debugger::GetAsyncExecution() { + return !m_command_interpreter_up->GetSynchronous(); +} + +void Debugger::SetAsyncExecution(bool async_execution) { + m_command_interpreter_up->SetSynchronous(!async_execution); +} + +repro::DataRecorder *Debugger::GetInputRecorder() { return m_input_recorder; } + +static inline int OpenPipe(int fds[2], std::size_t size) { +#ifdef _WIN32 + return _pipe(fds, size, O_BINARY); +#else + (void)size; + return pipe(fds); +#endif +} + +Status Debugger::SetInputString(const char *data) { + Status result; + enum PIPES { READ, WRITE }; // Indexes for the read and write fds + int fds[2] = {-1, -1}; + + if (data == nullptr) { + result.SetErrorString("String data is null"); + return result; + } + + size_t size = strlen(data); + if (size == 0) { + result.SetErrorString("String data is empty"); + return result; + } + + if (OpenPipe(fds, size) != 0) { + result.SetErrorString( + "can't create pipe file descriptors for LLDB commands"); + return result; + } + + int r = write(fds[WRITE], data, size); + (void)r; + // Close the write end of the pipe, so that the command interpreter will exit + // when it consumes all the data. + llvm::sys::Process::SafelyCloseFileDescriptor(fds[WRITE]); + + // Open the read file descriptor as a FILE * that we can return as an input + // handle. + FILE *commands_file = fdopen(fds[READ], "rb"); + if (commands_file == nullptr) { + result.SetErrorStringWithFormat("fdopen(%i, \"rb\") failed (errno = %i) " + "when trying to open LLDB commands pipe", + fds[READ], errno); + llvm::sys::Process::SafelyCloseFileDescriptor(fds[READ]); + return result; + } + + SetInputFile((FileSP)std::make_shared<NativeFile>(commands_file, true)); + return result; +} + +void Debugger::SetInputFile(FileSP file_sp) { + assert(file_sp && file_sp->IsValid()); + m_input_file_sp = std::move(file_sp); + // Save away the terminal state if that is relevant, so that we can restore + // it in RestoreInputState. + SaveInputTerminalState(); +} + +void Debugger::SetOutputFile(FileSP file_sp) { + assert(file_sp && file_sp->IsValid()); + m_output_stream_sp = std::make_shared<StreamFile>(file_sp); +} + +void Debugger::SetErrorFile(FileSP file_sp) { + assert(file_sp && file_sp->IsValid()); + m_error_stream_sp = std::make_shared<StreamFile>(file_sp); +} + +void Debugger::SaveInputTerminalState() { + int fd = GetInputFile().GetDescriptor(); + if (fd != File::kInvalidDescriptor) + m_terminal_state.Save(fd, true); +} + +void Debugger::RestoreInputTerminalState() { m_terminal_state.Restore(); } + +ExecutionContext Debugger::GetSelectedExecutionContext() { + bool adopt_selected = true; + ExecutionContextRef exe_ctx_ref(GetSelectedTarget().get(), adopt_selected); + return ExecutionContext(exe_ctx_ref); +} + +void Debugger::DispatchInputInterrupt() { + std::lock_guard<std::recursive_mutex> guard(m_io_handler_stack.GetMutex()); + IOHandlerSP reader_sp(m_io_handler_stack.Top()); + if (reader_sp) + reader_sp->Interrupt(); +} + +void Debugger::DispatchInputEndOfFile() { + std::lock_guard<std::recursive_mutex> guard(m_io_handler_stack.GetMutex()); + IOHandlerSP reader_sp(m_io_handler_stack.Top()); + if (reader_sp) + reader_sp->GotEOF(); +} + +void Debugger::ClearIOHandlers() { + // The bottom input reader should be the main debugger input reader. We do + // not want to close that one here. + std::lock_guard<std::recursive_mutex> guard(m_io_handler_stack.GetMutex()); + while (m_io_handler_stack.GetSize() > 1) { + IOHandlerSP reader_sp(m_io_handler_stack.Top()); + if (reader_sp) + PopIOHandler(reader_sp); + } +} + +void Debugger::RunIOHandlers() { + IOHandlerSP reader_sp = m_io_handler_stack.Top(); + while (true) { + if (!reader_sp) + break; + + reader_sp->Run(); + { + std::lock_guard<std::recursive_mutex> guard( + m_io_handler_synchronous_mutex); + + // Remove all input readers that are done from the top of the stack + while (true) { + IOHandlerSP top_reader_sp = m_io_handler_stack.Top(); + if (top_reader_sp && top_reader_sp->GetIsDone()) + PopIOHandler(top_reader_sp); + else + break; + } + reader_sp = m_io_handler_stack.Top(); + } + } + ClearIOHandlers(); +} + +void Debugger::RunIOHandlerSync(const IOHandlerSP &reader_sp) { + std::lock_guard<std::recursive_mutex> guard(m_io_handler_synchronous_mutex); + + PushIOHandler(reader_sp); + IOHandlerSP top_reader_sp = reader_sp; + + while (top_reader_sp) { + top_reader_sp->Run(); + + // Don't unwind past the starting point. + if (top_reader_sp.get() == reader_sp.get()) { + if (PopIOHandler(reader_sp)) + break; + } + + // If we pushed new IO handlers, pop them if they're done or restart the + // loop to run them if they're not. + while (true) { + top_reader_sp = m_io_handler_stack.Top(); + if (top_reader_sp && top_reader_sp->GetIsDone()) { + PopIOHandler(top_reader_sp); + // Don't unwind past the starting point. + if (top_reader_sp.get() == reader_sp.get()) + return; + } else { + break; + } + } + } +} + +bool Debugger::IsTopIOHandler(const lldb::IOHandlerSP &reader_sp) { + return m_io_handler_stack.IsTop(reader_sp); +} + +bool Debugger::CheckTopIOHandlerTypes(IOHandler::Type top_type, + IOHandler::Type second_top_type) { + return m_io_handler_stack.CheckTopIOHandlerTypes(top_type, second_top_type); +} + +void Debugger::PrintAsync(const char *s, size_t len, bool is_stdout) { + bool printed = m_io_handler_stack.PrintAsync(s, len, is_stdout); + if (!printed) { + lldb::StreamFileSP stream = + is_stdout ? m_output_stream_sp : m_error_stream_sp; + stream->Write(s, len); + } +} + +llvm::StringRef Debugger::GetTopIOHandlerControlSequence(char ch) { + return m_io_handler_stack.GetTopIOHandlerControlSequence(ch); +} + +const char *Debugger::GetIOHandlerCommandPrefix() { + return m_io_handler_stack.GetTopIOHandlerCommandPrefix(); +} + +const char *Debugger::GetIOHandlerHelpPrologue() { + return m_io_handler_stack.GetTopIOHandlerHelpPrologue(); +} + +bool Debugger::RemoveIOHandler(const IOHandlerSP &reader_sp) { + return PopIOHandler(reader_sp); +} + +void Debugger::RunIOHandlerAsync(const IOHandlerSP &reader_sp, + bool cancel_top_handler) { + PushIOHandler(reader_sp, cancel_top_handler); +} + +void Debugger::AdoptTopIOHandlerFilesIfInvalid(FileSP &in, StreamFileSP &out, + StreamFileSP &err) { + // Before an IOHandler runs, it must have in/out/err streams. This function + // is called when one ore more of the streams are nullptr. We use the top + // input reader's in/out/err streams, or fall back to the debugger file + // handles, or we fall back onto stdin/stdout/stderr as a last resort. + + std::lock_guard<std::recursive_mutex> guard(m_io_handler_stack.GetMutex()); + IOHandlerSP top_reader_sp(m_io_handler_stack.Top()); + // If no STDIN has been set, then set it appropriately + if (!in || !in->IsValid()) { + if (top_reader_sp) + in = top_reader_sp->GetInputFileSP(); + else + in = GetInputFileSP(); + // If there is nothing, use stdin + if (!in) + in = std::make_shared<NativeFile>(stdin, false); + } + // If no STDOUT has been set, then set it appropriately + if (!out || !out->GetFile().IsValid()) { + if (top_reader_sp) + out = top_reader_sp->GetOutputStreamFileSP(); + else + out = GetOutputStreamSP(); + // If there is nothing, use stdout + if (!out) + out = std::make_shared<StreamFile>(stdout, false); + } + // If no STDERR has been set, then set it appropriately + if (!err || !err->GetFile().IsValid()) { + if (top_reader_sp) + err = top_reader_sp->GetErrorStreamFileSP(); + else + err = GetErrorStreamSP(); + // If there is nothing, use stderr + if (!err) + err = std::make_shared<StreamFile>(stderr, false); + } +} + +void Debugger::PushIOHandler(const IOHandlerSP &reader_sp, + bool cancel_top_handler) { + if (!reader_sp) + return; + + std::lock_guard<std::recursive_mutex> guard(m_io_handler_stack.GetMutex()); + + // Get the current top input reader... + IOHandlerSP top_reader_sp(m_io_handler_stack.Top()); + + // Don't push the same IO handler twice... + if (reader_sp == top_reader_sp) + return; + + // Push our new input reader + m_io_handler_stack.Push(reader_sp); + reader_sp->Activate(); + + // Interrupt the top input reader to it will exit its Run() function and let + // this new input reader take over + if (top_reader_sp) { + top_reader_sp->Deactivate(); + if (cancel_top_handler) + top_reader_sp->Cancel(); + } +} + +bool Debugger::PopIOHandler(const IOHandlerSP &pop_reader_sp) { + if (!pop_reader_sp) + return false; + + std::lock_guard<std::recursive_mutex> guard(m_io_handler_stack.GetMutex()); + + // The reader on the stop of the stack is done, so let the next read on the + // stack refresh its prompt and if there is one... + if (m_io_handler_stack.IsEmpty()) + return false; + + IOHandlerSP reader_sp(m_io_handler_stack.Top()); + + if (pop_reader_sp != reader_sp) + return false; + + reader_sp->Deactivate(); + reader_sp->Cancel(); + m_io_handler_stack.Pop(); + + reader_sp = m_io_handler_stack.Top(); + if (reader_sp) + reader_sp->Activate(); + + return true; +} + +StreamSP Debugger::GetAsyncOutputStream() { + return std::make_shared<StreamAsynchronousIO>(*this, true, GetUseColor()); +} + +StreamSP Debugger::GetAsyncErrorStream() { + return std::make_shared<StreamAsynchronousIO>(*this, false, GetUseColor()); +} + +void Debugger::RequestInterrupt() { + std::lock_guard<std::mutex> guard(m_interrupt_mutex); + m_interrupt_requested++; +} + +void Debugger::CancelInterruptRequest() { + std::lock_guard<std::mutex> guard(m_interrupt_mutex); + if (m_interrupt_requested > 0) + m_interrupt_requested--; +} + +bool Debugger::InterruptRequested() { + // This is the one we should call internally. This will return true either + // if there's a debugger interrupt and we aren't on the IOHandler thread, + // or if we are on the IOHandler thread and there's a CommandInterpreter + // interrupt. + if (!IsIOHandlerThreadCurrentThread()) { + std::lock_guard<std::mutex> guard(m_interrupt_mutex); + return m_interrupt_requested != 0; + } + return GetCommandInterpreter().WasInterrupted(); +} + +Debugger::InterruptionReport::InterruptionReport( + std::string function_name, const llvm::formatv_object_base &payload) + : m_function_name(std::move(function_name)), + m_interrupt_time(std::chrono::system_clock::now()), + m_thread_id(llvm::get_threadid()) { + llvm::raw_string_ostream desc(m_description); + desc << payload << "\n"; +} + +void Debugger::ReportInterruption(const InterruptionReport &report) { + // For now, just log the description: + Log *log = GetLog(LLDBLog::Host); + LLDB_LOG(log, "Interruption: {0}", report.m_description); +} + +Debugger::DebuggerList Debugger::DebuggersRequestingInterruption() { + DebuggerList result; + if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { + std::lock_guard<std::recursive_mutex> guard(*g_debugger_list_mutex_ptr); + for (auto debugger_sp : *g_debugger_list_ptr) { + if (debugger_sp->InterruptRequested()) + result.push_back(debugger_sp); + } + } + return result; +} + +size_t Debugger::GetNumDebuggers() { + if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { + std::lock_guard<std::recursive_mutex> guard(*g_debugger_list_mutex_ptr); + return g_debugger_list_ptr->size(); + } + return 0; +} + +lldb::DebuggerSP Debugger::GetDebuggerAtIndex(size_t index) { + DebuggerSP debugger_sp; + + if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { + std::lock_guard<std::recursive_mutex> guard(*g_debugger_list_mutex_ptr); + if (index < g_debugger_list_ptr->size()) + debugger_sp = g_debugger_list_ptr->at(index); + } + + return debugger_sp; +} + +DebuggerSP Debugger::FindDebuggerWithID(lldb::user_id_t id) { + DebuggerSP debugger_sp; + + if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { + std::lock_guard<std::recursive_mutex> guard(*g_debugger_list_mutex_ptr); + DebuggerList::iterator pos, end = g_debugger_list_ptr->end(); + for (pos = g_debugger_list_ptr->begin(); pos != end; ++pos) { + if ((*pos)->GetID() == id) { + debugger_sp = *pos; + break; + } + } + } + return debugger_sp; +} + +bool Debugger::FormatDisassemblerAddress(const FormatEntity::Entry *format, + const SymbolContext *sc, + const SymbolContext *prev_sc, + const ExecutionContext *exe_ctx, + const Address *addr, Stream &s) { + FormatEntity::Entry format_entry; + + if (format == nullptr) { + if (exe_ctx != nullptr && exe_ctx->HasTargetScope()) + format = exe_ctx->GetTargetRef().GetDebugger().GetDisassemblyFormat(); + if (format == nullptr) { + FormatEntity::Parse("${addr}: ", format_entry); + format = &format_entry; + } + } + bool function_changed = false; + bool initial_function = false; + if (prev_sc && (prev_sc->function || prev_sc->symbol)) { + if (sc && (sc->function || sc->symbol)) { + if (prev_sc->symbol && sc->symbol) { + if (!sc->symbol->Compare(prev_sc->symbol->GetName(), + prev_sc->symbol->GetType())) { + function_changed = true; + } + } else if (prev_sc->function && sc->function) { + if (prev_sc->function->GetMangled() != sc->function->GetMangled()) { + function_changed = true; + } + } + } + } + // The first context on a list of instructions will have a prev_sc that has + // no Function or Symbol -- if SymbolContext had an IsValid() method, it + // would return false. But we do get a prev_sc pointer. + if ((sc && (sc->function || sc->symbol)) && prev_sc && + (prev_sc->function == nullptr && prev_sc->symbol == nullptr)) { + initial_function = true; + } + return FormatEntity::Format(*format, s, sc, exe_ctx, addr, nullptr, + function_changed, initial_function); +} + +void Debugger::AssertCallback(llvm::StringRef message, + llvm::StringRef backtrace, + llvm::StringRef prompt) { + Debugger::ReportError( + llvm::formatv("{0}\n{1}{2}", message, backtrace, prompt).str()); +} + +void Debugger::SetLoggingCallback(lldb::LogOutputCallback log_callback, + void *baton) { + // For simplicity's sake, I am not going to deal with how to close down any + // open logging streams, I just redirect everything from here on out to the + // callback. + m_callback_handler_sp = + std::make_shared<CallbackLogHandler>(log_callback, baton); +} + +void Debugger::SetDestroyCallback( + lldb_private::DebuggerDestroyCallback destroy_callback, void *baton) { + std::lock_guard<std::mutex> guard(m_destroy_callback_mutex); + m_destroy_callbacks.clear(); + const lldb::callback_token_t token = m_destroy_callback_next_token++; + m_destroy_callbacks.emplace_back(token, destroy_callback, baton); +} + +lldb::callback_token_t Debugger::AddDestroyCallback( + lldb_private::DebuggerDestroyCallback destroy_callback, void *baton) { + std::lock_guard<std::mutex> guard(m_destroy_callback_mutex); + const lldb::callback_token_t token = m_destroy_callback_next_token++; + m_destroy_callbacks.emplace_back(token, destroy_callback, baton); + return token; +} + +bool Debugger::RemoveDestroyCallback(lldb::callback_token_t token) { + std::lock_guard<std::mutex> guard(m_destroy_callback_mutex); + for (auto it = m_destroy_callbacks.begin(); it != m_destroy_callbacks.end(); + ++it) { + if (it->token == token) { + m_destroy_callbacks.erase(it); + return true; + } + } + return false; +} + +static void PrivateReportProgress(Debugger &debugger, uint64_t progress_id, + std::string title, std::string details, + uint64_t completed, uint64_t total, + bool is_debugger_specific, + uint32_t progress_broadcast_bit) { + // Only deliver progress events if we have any progress listeners. + if (!debugger.GetBroadcaster().EventTypeHasListeners(progress_broadcast_bit)) + return; + + EventSP event_sp(new Event( + progress_broadcast_bit, + new ProgressEventData(progress_id, std::move(title), std::move(details), + completed, total, is_debugger_specific))); + debugger.GetBroadcaster().BroadcastEvent(event_sp); +} + +void Debugger::ReportProgress(uint64_t progress_id, std::string title, + std::string details, uint64_t completed, + uint64_t total, + std::optional<lldb::user_id_t> debugger_id, + uint32_t progress_category_bit) { + // Check if this progress is for a specific debugger. + if (debugger_id) { + // It is debugger specific, grab it and deliver the event if the debugger + // still exists. + DebuggerSP debugger_sp = FindDebuggerWithID(*debugger_id); + if (debugger_sp) + PrivateReportProgress(*debugger_sp, progress_id, std::move(title), + std::move(details), completed, total, + /*is_debugger_specific*/ true, + progress_category_bit); + return; + } + // The progress event is not debugger specific, iterate over all debuggers + // and deliver a progress event to each one. + if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { + std::lock_guard<std::recursive_mutex> guard(*g_debugger_list_mutex_ptr); + DebuggerList::iterator pos, end = g_debugger_list_ptr->end(); + for (pos = g_debugger_list_ptr->begin(); pos != end; ++pos) + PrivateReportProgress(*(*pos), progress_id, title, details, completed, + total, /*is_debugger_specific*/ false, + progress_category_bit); + } +} + +static void PrivateReportDiagnostic(Debugger &debugger, Severity severity, + std::string message, + bool debugger_specific) { + uint32_t event_type = 0; + switch (severity) { + case eSeverityInfo: + assert(false && "eSeverityInfo should not be broadcast"); + return; + case eSeverityWarning: + event_type = lldb::eBroadcastBitWarning; + break; + case eSeverityError: + event_type = lldb::eBroadcastBitError; + break; + } + + Broadcaster &broadcaster = debugger.GetBroadcaster(); + if (!broadcaster.EventTypeHasListeners(event_type)) { + // Diagnostics are too important to drop. If nobody is listening, print the + // diagnostic directly to the debugger's error stream. + DiagnosticEventData event_data(severity, std::move(message), + debugger_specific); + StreamSP stream = debugger.GetAsyncErrorStream(); + event_data.Dump(stream.get()); + return; + } + EventSP event_sp = std::make_shared<Event>( + event_type, + new DiagnosticEventData(severity, std::move(message), debugger_specific)); + broadcaster.BroadcastEvent(event_sp); +} + +void Debugger::ReportDiagnosticImpl(Severity severity, std::string message, + std::optional<lldb::user_id_t> debugger_id, + std::once_flag *once) { + auto ReportDiagnosticLambda = [&]() { + // Always log diagnostics to the system log. + Host::SystemLog(severity, message); + + // The diagnostic subsystem is optional but we still want to broadcast + // events when it's disabled. + if (Diagnostics::Enabled()) + Diagnostics::Instance().Report(message); + + // We don't broadcast info events. + if (severity == lldb::eSeverityInfo) + return; + + // Check if this diagnostic is for a specific debugger. + if (debugger_id) { + // It is debugger specific, grab it and deliver the event if the debugger + // still exists. + DebuggerSP debugger_sp = FindDebuggerWithID(*debugger_id); + if (debugger_sp) + PrivateReportDiagnostic(*debugger_sp, severity, std::move(message), + true); + return; + } + // The diagnostic event is not debugger specific, iterate over all debuggers + // and deliver a diagnostic event to each one. + if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { + std::lock_guard<std::recursive_mutex> guard(*g_debugger_list_mutex_ptr); + for (const auto &debugger : *g_debugger_list_ptr) + PrivateReportDiagnostic(*debugger, severity, message, false); + } + }; + + if (once) + std::call_once(*once, ReportDiagnosticLambda); + else + ReportDiagnosticLambda(); +} + +void Debugger::ReportWarning(std::string message, + std::optional<lldb::user_id_t> debugger_id, + std::once_flag *once) { + ReportDiagnosticImpl(eSeverityWarning, std::move(message), debugger_id, once); +} + +void Debugger::ReportError(std::string message, + std::optional<lldb::user_id_t> debugger_id, + std::once_flag *once) { + ReportDiagnosticImpl(eSeverityError, std::move(message), debugger_id, once); +} + +void Debugger::ReportInfo(std::string message, + std::optional<lldb::user_id_t> debugger_id, + std::once_flag *once) { + ReportDiagnosticImpl(eSeverityInfo, std::move(message), debugger_id, once); +} + +void Debugger::ReportSymbolChange(const ModuleSpec &module_spec) { + if (g_debugger_list_ptr && g_debugger_list_mutex_ptr) { + std::lock_guard<std::recursive_mutex> guard(*g_debugger_list_mutex_ptr); + for (DebuggerSP debugger_sp : *g_debugger_list_ptr) { + EventSP event_sp = std::make_shared<Event>( + lldb::eBroadcastSymbolChange, + new SymbolChangeEventData(debugger_sp, module_spec)); + debugger_sp->GetBroadcaster().BroadcastEvent(event_sp); + } + } +} + +static std::shared_ptr<LogHandler> +CreateLogHandler(LogHandlerKind log_handler_kind, int fd, bool should_close, + size_t buffer_size) { + switch (log_handler_kind) { + case eLogHandlerStream: + return std::make_shared<StreamLogHandler>(fd, should_close, buffer_size); + case eLogHandlerCircular: + return std::make_shared<RotatingLogHandler>(buffer_size); + case eLogHandlerSystem: + return std::make_shared<SystemLogHandler>(); + case eLogHandlerCallback: + return {}; + } + return {}; +} + +bool Debugger::EnableLog(llvm::StringRef channel, + llvm::ArrayRef<const char *> categories, + llvm::StringRef log_file, uint32_t log_options, + size_t buffer_size, LogHandlerKind log_handler_kind, + llvm::raw_ostream &error_stream) { + + std::shared_ptr<LogHandler> log_handler_sp; + if (m_callback_handler_sp) { + log_handler_sp = m_callback_handler_sp; + // For now when using the callback mode you always get thread & timestamp. + log_options |= + LLDB_LOG_OPTION_PREPEND_TIMESTAMP | LLDB_LOG_OPTION_PREPEND_THREAD_NAME; + } else if (log_file.empty()) { + log_handler_sp = + CreateLogHandler(log_handler_kind, GetOutputFile().GetDescriptor(), + /*should_close=*/false, buffer_size); + } else { + auto pos = m_stream_handlers.find(log_file); + if (pos != m_stream_handlers.end()) + log_handler_sp = pos->second.lock(); + if (!log_handler_sp) { + File::OpenOptions flags = + File::eOpenOptionWriteOnly | File::eOpenOptionCanCreate; + if (log_options & LLDB_LOG_OPTION_APPEND) + flags |= File::eOpenOptionAppend; + else + flags |= File::eOpenOptionTruncate; + llvm::Expected<FileUP> file = FileSystem::Instance().Open( + FileSpec(log_file), flags, lldb::eFilePermissionsFileDefault, false); + if (!file) { + error_stream << "Unable to open log file '" << log_file + << "': " << llvm::toString(file.takeError()) << "\n"; + return false; + } + + log_handler_sp = + CreateLogHandler(log_handler_kind, (*file)->GetDescriptor(), + /*should_close=*/true, buffer_size); + m_stream_handlers[log_file] = log_handler_sp; + } + } + assert(log_handler_sp); + + if (log_options == 0) + log_options = LLDB_LOG_OPTION_PREPEND_THREAD_NAME; + + return Log::EnableLogChannel(log_handler_sp, log_options, channel, categories, + error_stream); +} + +ScriptInterpreter * +Debugger::GetScriptInterpreter(bool can_create, + std::optional<lldb::ScriptLanguage> language) { + std::lock_guard<std::recursive_mutex> locker(m_script_interpreter_mutex); + lldb::ScriptLanguage script_language = + language ? *language : GetScriptLanguage(); + + if (!m_script_interpreters[script_language]) { + if (!can_create) + return nullptr; + m_script_interpreters[script_language] = + PluginManager::GetScriptInterpreterForLanguage(script_language, *this); + } + + return m_script_interpreters[script_language].get(); +} + +SourceManager &Debugger::GetSourceManager() { + if (!m_source_manager_up) + m_source_manager_up = std::make_unique<SourceManager>(shared_from_this()); + return *m_source_manager_up; +} + +// This function handles events that were broadcast by the process. +void Debugger::HandleBreakpointEvent(const EventSP &event_sp) { + using namespace lldb; + const uint32_t event_type = + Breakpoint::BreakpointEventData::GetBreakpointEventTypeFromEvent( + event_sp); + + // if (event_type & eBreakpointEventTypeAdded + // || event_type & eBreakpointEventTypeRemoved + // || event_type & eBreakpointEventTypeEnabled + // || event_type & eBreakpointEventTypeDisabled + // || event_type & eBreakpointEventTypeCommandChanged + // || event_type & eBreakpointEventTypeConditionChanged + // || event_type & eBreakpointEventTypeIgnoreChanged + // || event_type & eBreakpointEventTypeLocationsResolved) + // { + // // Don't do anything about these events, since the breakpoint + // commands already echo these actions. + // } + // + if (event_type & eBreakpointEventTypeLocationsAdded) { + uint32_t num_new_locations = + Breakpoint::BreakpointEventData::GetNumBreakpointLocationsFromEvent( + event_sp); + if (num_new_locations > 0) { + BreakpointSP breakpoint = + Breakpoint::BreakpointEventData::GetBreakpointFromEvent(event_sp); + StreamSP output_sp(GetAsyncOutputStream()); + if (output_sp) { + output_sp->Printf("%d location%s added to breakpoint %d\n", + num_new_locations, num_new_locations == 1 ? "" : "s", + breakpoint->GetID()); + output_sp->Flush(); + } + } + } + // else if (event_type & eBreakpointEventTypeLocationsRemoved) + // { + // // These locations just get disabled, not sure it is worth spamming + // folks about this on the command line. + // } + // else if (event_type & eBreakpointEventTypeLocationsResolved) + // { + // // This might be an interesting thing to note, but I'm going to + // leave it quiet for now, it just looked noisy. + // } +} + +void Debugger::FlushProcessOutput(Process &process, bool flush_stdout, + bool flush_stderr) { + const auto &flush = [&](Stream &stream, + size_t (Process::*get)(char *, size_t, Status &)) { + Status error; + size_t len; + char buffer[1024]; + while ((len = (process.*get)(buffer, sizeof(buffer), error)) > 0) + stream.Write(buffer, len); + stream.Flush(); + }; + + std::lock_guard<std::mutex> guard(m_output_flush_mutex); + if (flush_stdout) + flush(*GetAsyncOutputStream(), &Process::GetSTDOUT); + if (flush_stderr) + flush(*GetAsyncErrorStream(), &Process::GetSTDERR); +} + +// This function handles events that were broadcast by the process. +void Debugger::HandleProcessEvent(const EventSP &event_sp) { + using namespace lldb; + const uint32_t event_type = event_sp->GetType(); + ProcessSP process_sp = + (event_type == Process::eBroadcastBitStructuredData) + ? EventDataStructuredData::GetProcessFromEvent(event_sp.get()) + : Process::ProcessEventData::GetProcessFromEvent(event_sp.get()); + + StreamSP output_stream_sp = GetAsyncOutputStream(); + StreamSP error_stream_sp = GetAsyncErrorStream(); + const bool gui_enabled = IsForwardingEvents(); + + if (!gui_enabled) { + bool pop_process_io_handler = false; + assert(process_sp); + + bool state_is_stopped = false; + const bool got_state_changed = + (event_type & Process::eBroadcastBitStateChanged) != 0; + const bool got_stdout = (event_type & Process::eBroadcastBitSTDOUT) != 0; + const bool got_stderr = (event_type & Process::eBroadcastBitSTDERR) != 0; + const bool got_structured_data = + (event_type & Process::eBroadcastBitStructuredData) != 0; + + if (got_state_changed) { + StateType event_state = + Process::ProcessEventData::GetStateFromEvent(event_sp.get()); + state_is_stopped = StateIsStoppedState(event_state, false); + } + + // Display running state changes first before any STDIO + if (got_state_changed && !state_is_stopped) { + // This is a public stop which we are going to announce to the user, so + // we should force the most relevant frame selection here. + Process::HandleProcessStateChangedEvent(event_sp, output_stream_sp.get(), + SelectMostRelevantFrame, + pop_process_io_handler); + } + + // Now display STDOUT and STDERR + FlushProcessOutput(*process_sp, got_stdout || got_state_changed, + got_stderr || got_state_changed); + + // Give structured data events an opportunity to display. + if (got_structured_data) { + StructuredDataPluginSP plugin_sp = + EventDataStructuredData::GetPluginFromEvent(event_sp.get()); + if (plugin_sp) { + auto structured_data_sp = + EventDataStructuredData::GetObjectFromEvent(event_sp.get()); + if (output_stream_sp) { + StreamString content_stream; + Status error = + plugin_sp->GetDescription(structured_data_sp, content_stream); + if (error.Success()) { + if (!content_stream.GetString().empty()) { + // Add newline. + content_stream.PutChar('\n'); + content_stream.Flush(); + + // Print it. + output_stream_sp->PutCString(content_stream.GetString()); + } + } else { + error_stream_sp->Format("Failed to print structured " + "data with plugin {0}: {1}", + plugin_sp->GetPluginName(), error); + } + } + } + } + + // Now display any stopped state changes after any STDIO + if (got_state_changed && state_is_stopped) { + Process::HandleProcessStateChangedEvent(event_sp, output_stream_sp.get(), + SelectMostRelevantFrame, + pop_process_io_handler); + } + + output_stream_sp->Flush(); + error_stream_sp->Flush(); + + if (pop_process_io_handler) + process_sp->PopProcessIOHandler(); + } +} + +void Debugger::HandleThreadEvent(const EventSP &event_sp) { + // At present the only thread event we handle is the Frame Changed event, and + // all we do for that is just reprint the thread status for that thread. + using namespace lldb; + const uint32_t event_type = event_sp->GetType(); + const bool stop_format = true; + if (event_type == Thread::eBroadcastBitStackChanged || + event_type == Thread::eBroadcastBitThreadSelected) { + ThreadSP thread_sp( + Thread::ThreadEventData::GetThreadFromEvent(event_sp.get())); + if (thread_sp) { + thread_sp->GetStatus(*GetAsyncOutputStream(), 0, 1, 1, stop_format); + } + } +} + +bool Debugger::IsForwardingEvents() { return (bool)m_forward_listener_sp; } + +void Debugger::EnableForwardEvents(const ListenerSP &listener_sp) { + m_forward_listener_sp = listener_sp; +} + +void Debugger::CancelForwardEvents(const ListenerSP &listener_sp) { + m_forward_listener_sp.reset(); +} + +lldb::thread_result_t Debugger::DefaultEventHandler() { + ListenerSP listener_sp(GetListener()); + ConstString broadcaster_class_target(Target::GetStaticBroadcasterClass()); + ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass()); + ConstString broadcaster_class_thread(Thread::GetStaticBroadcasterClass()); + BroadcastEventSpec target_event_spec(broadcaster_class_target, + Target::eBroadcastBitBreakpointChanged); + + BroadcastEventSpec process_event_spec( + broadcaster_class_process, + Process::eBroadcastBitStateChanged | Process::eBroadcastBitSTDOUT | + Process::eBroadcastBitSTDERR | Process::eBroadcastBitStructuredData); + + BroadcastEventSpec thread_event_spec(broadcaster_class_thread, + Thread::eBroadcastBitStackChanged | + Thread::eBroadcastBitThreadSelected); + + listener_sp->StartListeningForEventSpec(m_broadcaster_manager_sp, + target_event_spec); + listener_sp->StartListeningForEventSpec(m_broadcaster_manager_sp, + process_event_spec); + listener_sp->StartListeningForEventSpec(m_broadcaster_manager_sp, + thread_event_spec); + listener_sp->StartListeningForEvents( + m_command_interpreter_up.get(), + CommandInterpreter::eBroadcastBitQuitCommandReceived | + CommandInterpreter::eBroadcastBitAsynchronousOutputData | + CommandInterpreter::eBroadcastBitAsynchronousErrorData); + + listener_sp->StartListeningForEvents( + &m_broadcaster, lldb::eBroadcastBitProgress | lldb::eBroadcastBitWarning | + lldb::eBroadcastBitError | + lldb::eBroadcastSymbolChange); + + // Let the thread that spawned us know that we have started up and that we + // are now listening to all required events so no events get missed + m_sync_broadcaster.BroadcastEvent(eBroadcastBitEventThreadIsListening); + + bool done = false; + while (!done) { + EventSP event_sp; + if (listener_sp->GetEvent(event_sp, std::nullopt)) { + if (event_sp) { + Broadcaster *broadcaster = event_sp->GetBroadcaster(); + if (broadcaster) { + uint32_t event_type = event_sp->GetType(); + ConstString broadcaster_class(broadcaster->GetBroadcasterClass()); + if (broadcaster_class == broadcaster_class_process) { + HandleProcessEvent(event_sp); + } else if (broadcaster_class == broadcaster_class_target) { + if (Breakpoint::BreakpointEventData::GetEventDataFromEvent( + event_sp.get())) { + HandleBreakpointEvent(event_sp); + } + } else if (broadcaster_class == broadcaster_class_thread) { + HandleThreadEvent(event_sp); + } else if (broadcaster == m_command_interpreter_up.get()) { + if (event_type & + CommandInterpreter::eBroadcastBitQuitCommandReceived) { + done = true; + } else if (event_type & + CommandInterpreter::eBroadcastBitAsynchronousErrorData) { + const char *data = static_cast<const char *>( + EventDataBytes::GetBytesFromEvent(event_sp.get())); + if (data && data[0]) { + StreamSP error_sp(GetAsyncErrorStream()); + if (error_sp) { + error_sp->PutCString(data); + error_sp->Flush(); + } + } + } else if (event_type & CommandInterpreter:: + eBroadcastBitAsynchronousOutputData) { + const char *data = static_cast<const char *>( + EventDataBytes::GetBytesFromEvent(event_sp.get())); + if (data && data[0]) { + StreamSP output_sp(GetAsyncOutputStream()); + if (output_sp) { + output_sp->PutCString(data); + output_sp->Flush(); + } + } + } + } else if (broadcaster == &m_broadcaster) { + if (event_type & lldb::eBroadcastBitProgress) + HandleProgressEvent(event_sp); + else if (event_type & lldb::eBroadcastBitWarning) + HandleDiagnosticEvent(event_sp); + else if (event_type & lldb::eBroadcastBitError) + HandleDiagnosticEvent(event_sp); + } + } + + if (m_forward_listener_sp) + m_forward_listener_sp->AddEvent(event_sp); + } + } + } + return {}; +} + +bool Debugger::StartEventHandlerThread() { + if (!m_event_handler_thread.IsJoinable()) { + // We must synchronize with the DefaultEventHandler() thread to ensure it + // is up and running and listening to events before we return from this + // function. We do this by listening to events for the + // eBroadcastBitEventThreadIsListening from the m_sync_broadcaster + ConstString full_name("lldb.debugger.event-handler"); + ListenerSP listener_sp(Listener::MakeListener(full_name.AsCString())); + listener_sp->StartListeningForEvents(&m_sync_broadcaster, + eBroadcastBitEventThreadIsListening); + + llvm::StringRef thread_name = + full_name.GetLength() < llvm::get_max_thread_name_length() + ? full_name.GetStringRef() + : "dbg.evt-handler"; + + // Use larger 8MB stack for this thread + llvm::Expected<HostThread> event_handler_thread = + ThreadLauncher::LaunchThread( + thread_name, [this] { return DefaultEventHandler(); }, + g_debugger_event_thread_stack_bytes); + + if (event_handler_thread) { + m_event_handler_thread = *event_handler_thread; + } else { + LLDB_LOG_ERROR(GetLog(LLDBLog::Host), event_handler_thread.takeError(), + "failed to launch host thread: {0}"); + } + + // Make sure DefaultEventHandler() is running and listening to events + // before we return from this function. We are only listening for events of + // type eBroadcastBitEventThreadIsListening so we don't need to check the + // event, we just need to wait an infinite amount of time for it (nullptr + // timeout as the first parameter) + lldb::EventSP event_sp; + listener_sp->GetEvent(event_sp, std::nullopt); + } + return m_event_handler_thread.IsJoinable(); +} + +void Debugger::StopEventHandlerThread() { + if (m_event_handler_thread.IsJoinable()) { + GetCommandInterpreter().BroadcastEvent( + CommandInterpreter::eBroadcastBitQuitCommandReceived); + m_event_handler_thread.Join(nullptr); + } +} + +lldb::thread_result_t Debugger::IOHandlerThread() { + RunIOHandlers(); + StopEventHandlerThread(); + return {}; +} + +void Debugger::HandleProgressEvent(const lldb::EventSP &event_sp) { + auto *data = ProgressEventData::GetEventDataFromEvent(event_sp.get()); + if (!data) + return; + + // Do some bookkeeping for the current event, regardless of whether we're + // going to show the progress. + const uint64_t id = data->GetID(); + if (m_current_event_id) { + Log *log = GetLog(LLDBLog::Events); + if (log && log->GetVerbose()) { + StreamString log_stream; + log_stream.AsRawOstream() + << static_cast<void *>(this) << " Debugger(" << GetID() + << ")::HandleProgressEvent( m_current_event_id = " + << *m_current_event_id << ", data = { "; + data->Dump(&log_stream); + log_stream << " } )"; + log->PutString(log_stream.GetString()); + } + if (id != *m_current_event_id) + return; + if (data->GetCompleted() == data->GetTotal()) + m_current_event_id.reset(); + } else { + m_current_event_id = id; + } + + // Decide whether we actually are going to show the progress. This decision + // can change between iterations so check it inside the loop. + if (!GetShowProgress()) + return; + + // Determine whether the current output file is an interactive terminal with + // color support. We assume that if we support ANSI escape codes we support + // vt100 escape codes. + File &file = GetOutputFile(); + if (!file.GetIsInteractive() || !file.GetIsTerminalWithColors()) + return; + + StreamSP output = GetAsyncOutputStream(); + + // Print over previous line, if any. + output->Printf("\r"); + + if (data->GetCompleted() == data->GetTotal()) { + // Clear the current line. + output->Printf("\x1B[2K"); + output->Flush(); + return; + } + + // Trim the progress message if it exceeds the window's width and print it. + std::string message = data->GetMessage(); + if (data->IsFinite()) + message = llvm::formatv("[{0}/{1}] {2}", data->GetCompleted(), + data->GetTotal(), message) + .str(); + + // Trim the progress message if it exceeds the window's width and print it. + const uint32_t term_width = GetTerminalWidth(); + const uint32_t ellipsis = 3; + if (message.size() + ellipsis >= term_width) + message = message.substr(0, term_width - ellipsis); + + const bool use_color = GetUseColor(); + llvm::StringRef ansi_prefix = GetShowProgressAnsiPrefix(); + if (!ansi_prefix.empty()) + output->Printf( + "%s", ansi::FormatAnsiTerminalCodes(ansi_prefix, use_color).c_str()); + + output->Printf("%s...", message.c_str()); + + llvm::StringRef ansi_suffix = GetShowProgressAnsiSuffix(); + if (!ansi_suffix.empty()) + output->Printf( + "%s", ansi::FormatAnsiTerminalCodes(ansi_suffix, use_color).c_str()); + + // Clear until the end of the line. + output->Printf("\x1B[K\r"); + + // Flush the output. + output->Flush(); +} + +void Debugger::HandleDiagnosticEvent(const lldb::EventSP &event_sp) { + auto *data = DiagnosticEventData::GetEventDataFromEvent(event_sp.get()); + if (!data) + return; + + StreamSP stream = GetAsyncErrorStream(); + data->Dump(stream.get()); +} + +bool Debugger::HasIOHandlerThread() const { + return m_io_handler_thread.IsJoinable(); +} + +HostThread Debugger::SetIOHandlerThread(HostThread &new_thread) { + HostThread old_host = m_io_handler_thread; + m_io_handler_thread = new_thread; + return old_host; +} + +bool Debugger::StartIOHandlerThread() { + if (!m_io_handler_thread.IsJoinable()) { + llvm::Expected<HostThread> io_handler_thread = ThreadLauncher::LaunchThread( + "lldb.debugger.io-handler", [this] { return IOHandlerThread(); }, + 8 * 1024 * 1024); // Use larger 8MB stack for this thread + if (io_handler_thread) { + m_io_handler_thread = *io_handler_thread; + } else { + LLDB_LOG_ERROR(GetLog(LLDBLog::Host), io_handler_thread.takeError(), + "failed to launch host thread: {0}"); + } + } + return m_io_handler_thread.IsJoinable(); +} + +void Debugger::StopIOHandlerThread() { + if (m_io_handler_thread.IsJoinable()) { + GetInputFile().Close(); + m_io_handler_thread.Join(nullptr); + } +} + +void Debugger::JoinIOHandlerThread() { + if (HasIOHandlerThread()) { + thread_result_t result; + m_io_handler_thread.Join(&result); + m_io_handler_thread = LLDB_INVALID_HOST_THREAD; + } +} + +bool Debugger::IsIOHandlerThreadCurrentThread() const { + if (!HasIOHandlerThread()) + return false; + return m_io_handler_thread.EqualsThread(Host::GetCurrentThread()); +} + +Target &Debugger::GetSelectedOrDummyTarget(bool prefer_dummy) { + if (!prefer_dummy) { + if (TargetSP target = m_target_list.GetSelectedTarget()) + return *target; + } + return GetDummyTarget(); +} + +Status Debugger::RunREPL(LanguageType language, const char *repl_options) { + Status err; + FileSpec repl_executable; + + if (language == eLanguageTypeUnknown) + language = 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 err; + } else { + err.SetErrorString( + "Multiple possible REPL languages. Please specify a language."); + return err; + } + } + + Target *const target = + nullptr; // passing in an empty target means the REPL must create one + + REPLSP repl_sp(REPL::Create(err, language, this, target, repl_options)); + + if (!err.Success()) { + return err; + } + + if (!repl_sp) { + err.SetErrorStringWithFormat("couldn't find a REPL for %s", + Language::GetNameForLanguageType(language)); + return err; + } + + repl_sp->SetCompilerOptions(repl_options); + repl_sp->RunLoop(); + + return err; +} + +llvm::ThreadPoolInterface &Debugger::GetThreadPool() { + assert(g_thread_pool && + "Debugger::GetThreadPool called before Debugger::Initialize"); + return *g_thread_pool; +} diff --git a/contrib/llvm-project/lldb/source/Core/DebuggerEvents.cpp b/contrib/llvm-project/lldb/source/Core/DebuggerEvents.cpp new file mode 100644 index 000000000000..2fa6efd155af --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/DebuggerEvents.cpp @@ -0,0 +1,157 @@ +//===-- DebuggerEvents.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/Core/DebuggerEvents.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/Progress.h" +#include "llvm/Support/WithColor.h" + +using namespace lldb_private; +using namespace lldb; + +template <typename T> +static const T *GetEventDataFromEventImpl(const Event *event_ptr) { + if (event_ptr) + if (const EventData *event_data = event_ptr->GetData()) + if (event_data->GetFlavor() == T::GetFlavorString()) + return static_cast<const T *>(event_ptr->GetData()); + return nullptr; +} + +llvm::StringRef ProgressEventData::GetFlavorString() { + return "ProgressEventData"; +} + +llvm::StringRef ProgressEventData::GetFlavor() const { + return ProgressEventData::GetFlavorString(); +} + +void ProgressEventData::Dump(Stream *s) const { + s->Printf(" id = %" PRIu64 ", title = \"%s\"", m_id, m_title.c_str()); + if (!m_details.empty()) + s->Printf(", details = \"%s\"", m_details.c_str()); + if (m_completed == 0 || m_completed == m_total) + s->Printf(", type = %s", m_completed == 0 ? "start" : "end"); + else + s->PutCString(", type = update"); + // If m_total is UINT64_MAX, there is no progress to report, just "start" + // and "end". If it isn't we will show the completed and total amounts. + if (m_total != Progress::kNonDeterministicTotal) + s->Printf(", progress = %" PRIu64 " of %" PRIu64, m_completed, m_total); +} + +const ProgressEventData * +ProgressEventData::GetEventDataFromEvent(const Event *event_ptr) { + return GetEventDataFromEventImpl<ProgressEventData>(event_ptr); +} + +StructuredData::DictionarySP +ProgressEventData::GetAsStructuredData(const Event *event_ptr) { + const ProgressEventData *progress_data = + ProgressEventData::GetEventDataFromEvent(event_ptr); + + if (!progress_data) + return {}; + + auto dictionary_sp = std::make_shared<StructuredData::Dictionary>(); + dictionary_sp->AddStringItem("title", progress_data->GetTitle()); + dictionary_sp->AddStringItem("details", progress_data->GetDetails()); + dictionary_sp->AddStringItem("message", progress_data->GetMessage()); + dictionary_sp->AddIntegerItem("progress_id", progress_data->GetID()); + dictionary_sp->AddIntegerItem("completed", progress_data->GetCompleted()); + dictionary_sp->AddIntegerItem("total", progress_data->GetTotal()); + dictionary_sp->AddBooleanItem("debugger_specific", + progress_data->IsDebuggerSpecific()); + + return dictionary_sp; +} + +llvm::StringRef DiagnosticEventData::GetPrefix() const { + switch (m_severity) { + case Severity::eSeverityInfo: + return "info"; + case Severity::eSeverityWarning: + return "warning"; + case Severity::eSeverityError: + return "error"; + } + llvm_unreachable("Fully covered switch above!"); +} + +void DiagnosticEventData::Dump(Stream *s) const { + llvm::HighlightColor color = m_severity == lldb::eSeverityWarning + ? llvm::HighlightColor::Warning + : llvm::HighlightColor::Error; + llvm::WithColor(s->AsRawOstream(), color, llvm::ColorMode::Enable) + << GetPrefix(); + *s << ": " << GetMessage() << '\n'; + s->Flush(); +} + +llvm::StringRef DiagnosticEventData::GetFlavorString() { + return "DiagnosticEventData"; +} + +llvm::StringRef DiagnosticEventData::GetFlavor() const { + return DiagnosticEventData::GetFlavorString(); +} + +const DiagnosticEventData * +DiagnosticEventData::GetEventDataFromEvent(const Event *event_ptr) { + return GetEventDataFromEventImpl<DiagnosticEventData>(event_ptr); +} + +StructuredData::DictionarySP +DiagnosticEventData::GetAsStructuredData(const Event *event_ptr) { + const DiagnosticEventData *diagnostic_data = + DiagnosticEventData::GetEventDataFromEvent(event_ptr); + + if (!diagnostic_data) + return {}; + + auto dictionary_sp = std::make_shared<StructuredData::Dictionary>(); + dictionary_sp->AddStringItem("message", diagnostic_data->GetMessage()); + dictionary_sp->AddStringItem("type", diagnostic_data->GetPrefix()); + dictionary_sp->AddBooleanItem("debugger_specific", + diagnostic_data->IsDebuggerSpecific()); + return dictionary_sp; +} + +llvm::StringRef SymbolChangeEventData::GetFlavorString() { + return "SymbolChangeEventData"; +} + +llvm::StringRef SymbolChangeEventData::GetFlavor() const { + return SymbolChangeEventData::GetFlavorString(); +} + +const SymbolChangeEventData * +SymbolChangeEventData::GetEventDataFromEvent(const Event *event_ptr) { + return GetEventDataFromEventImpl<SymbolChangeEventData>(event_ptr); +} + +void SymbolChangeEventData::DoOnRemoval(Event *event_ptr) { + DebuggerSP debugger_sp(m_debugger_wp.lock()); + if (!debugger_sp) + return; + + for (TargetSP target_sp : debugger_sp->GetTargetList().Targets()) { + if (ModuleSP module_sp = + target_sp->GetImages().FindModule(m_module_spec.GetUUID())) { + { + std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); + if (!module_sp->GetSymbolFileFileSpec()) + module_sp->SetSymbolFileFileSpec(m_module_spec.GetSymbolFileSpec()); + } + ModuleList module_list; + module_list.Append(module_sp); + target_sp->SymbolsDidLoad(module_list); + } + } +} diff --git a/contrib/llvm-project/lldb/source/Core/Declaration.cpp b/contrib/llvm-project/lldb/source/Core/Declaration.cpp new file mode 100644 index 000000000000..579a3999d14e --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/Declaration.cpp @@ -0,0 +1,83 @@ +//===-- Declaration.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/Core/Declaration.h" +#include "lldb/Utility/Stream.h" + +using namespace lldb_private; + +void Declaration::Dump(Stream *s, bool show_fullpaths) const { + if (m_file) { + *s << ", decl = "; + if (show_fullpaths) + *s << m_file; + else + *s << m_file.GetFilename(); + if (m_line > 0) + s->Printf(":%u", m_line); + if (m_column != LLDB_INVALID_COLUMN_NUMBER) + s->Printf(":%u", m_column); + } else { + if (m_line > 0) { + s->Printf(", line = %u", m_line); + if (m_column != LLDB_INVALID_COLUMN_NUMBER) + s->Printf(":%u", m_column); + } else if (m_column != LLDB_INVALID_COLUMN_NUMBER) + s->Printf(", column = %u", m_column); + } +} + +bool Declaration::DumpStopContext(Stream *s, bool show_fullpaths) const { + if (m_file) { + if (show_fullpaths) + *s << m_file; + else + m_file.GetFilename().Dump(s); + + if (m_line > 0) + s->Printf(":%u", m_line); + if (m_column != LLDB_INVALID_COLUMN_NUMBER) + s->Printf(":%u", m_column); + return true; + } else if (m_line > 0) { + s->Printf(" line %u", m_line); + if (m_column != LLDB_INVALID_COLUMN_NUMBER) + s->Printf(":%u", m_column); + return true; + } + return false; +} + +size_t Declaration::MemorySize() const { return sizeof(Declaration); } + +int Declaration::Compare(const Declaration &a, const Declaration &b) { + int result = FileSpec::Compare(a.m_file, b.m_file, true); + if (result) + return result; + if (a.m_line < b.m_line) + return -1; + else if (a.m_line > b.m_line) + return 1; + if (a.m_column < b.m_column) + return -1; + else if (a.m_column > b.m_column) + return 1; + return 0; +} + +bool Declaration::FileAndLineEqual(const Declaration &declaration) const { + int file_compare = FileSpec::Compare(this->m_file, declaration.m_file, true); + return file_compare == 0 && this->m_line == declaration.m_line; +} + +bool lldb_private::operator==(const Declaration &lhs, const Declaration &rhs) { + if (lhs.GetColumn() != rhs.GetColumn()) + return false; + + return lhs.GetLine() == rhs.GetLine() && lhs.GetFile() == rhs.GetFile(); +} diff --git a/contrib/llvm-project/lldb/source/Core/Disassembler.cpp b/contrib/llvm-project/lldb/source/Core/Disassembler.cpp new file mode 100644 index 000000000000..9286f62058bc --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/Disassembler.cpp @@ -0,0 +1,1331 @@ +//===-- Disassembler.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/Core/Disassembler.h" + +#include "lldb/Core/AddressRange.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/EmulateInstruction.h" +#include "lldb/Core/Mangled.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleList.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/SourceManager.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Interpreter/OptionValue.h" +#include "lldb/Interpreter/OptionValueArray.h" +#include "lldb/Interpreter/OptionValueDictionary.h" +#include "lldb/Interpreter/OptionValueRegex.h" +#include "lldb/Interpreter/OptionValueString.h" +#include "lldb/Interpreter/OptionValueUInt64.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Target/ExecutionContext.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/DataExtractor.h" +#include "lldb/Utility/RegularExpression.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/Stream.h" +#include "lldb/Utility/StreamString.h" +#include "lldb/Utility/Timer.h" +#include "lldb/lldb-private-enumerations.h" +#include "lldb/lldb-private-interfaces.h" +#include "lldb/lldb-private-types.h" +#include "llvm/Support/Compiler.h" +#include "llvm/TargetParser/Triple.h" + +#include <cstdint> +#include <cstring> +#include <utility> + +#include <cassert> + +#define DEFAULT_DISASM_BYTE_SIZE 32 + +using namespace lldb; +using namespace lldb_private; + +DisassemblerSP Disassembler::FindPlugin(const ArchSpec &arch, + const char *flavor, + const char *plugin_name) { + LLDB_SCOPED_TIMERF("Disassembler::FindPlugin (arch = %s, plugin_name = %s)", + arch.GetArchitectureName(), plugin_name); + + DisassemblerCreateInstance create_callback = nullptr; + + if (plugin_name) { + create_callback = + PluginManager::GetDisassemblerCreateCallbackForPluginName(plugin_name); + if (create_callback) { + if (auto disasm_sp = create_callback(arch, flavor)) + return disasm_sp; + } + } else { + for (uint32_t idx = 0; + (create_callback = PluginManager::GetDisassemblerCreateCallbackAtIndex( + idx)) != nullptr; + ++idx) { + if (auto disasm_sp = create_callback(arch, flavor)) + return disasm_sp; + } + } + return DisassemblerSP(); +} + +DisassemblerSP Disassembler::FindPluginForTarget(const Target &target, + const ArchSpec &arch, + const char *flavor, + const char *plugin_name) { + if (flavor == nullptr) { + // FIXME - we don't have the mechanism in place to do per-architecture + // settings. But since we know that for now we only support flavors on x86 + // & x86_64, + if (arch.GetTriple().getArch() == llvm::Triple::x86 || + arch.GetTriple().getArch() == llvm::Triple::x86_64) + flavor = target.GetDisassemblyFlavor(); + } + return FindPlugin(arch, flavor, plugin_name); +} + +static Address ResolveAddress(Target &target, const Address &addr) { + if (!addr.IsSectionOffset()) { + Address resolved_addr; + // If we weren't passed in a section offset address range, try and resolve + // it to something + bool is_resolved = target.GetSectionLoadList().IsEmpty() + ? target.GetImages().ResolveFileAddress( + addr.GetOffset(), resolved_addr) + : target.GetSectionLoadList().ResolveLoadAddress( + addr.GetOffset(), resolved_addr); + + // We weren't able to resolve the address, just treat it as a raw address + if (is_resolved && resolved_addr.IsValid()) + return resolved_addr; + } + return addr; +} + +lldb::DisassemblerSP Disassembler::DisassembleRange( + const ArchSpec &arch, const char *plugin_name, const char *flavor, + Target &target, const AddressRange &range, bool force_live_memory) { + if (range.GetByteSize() <= 0) + return {}; + + if (!range.GetBaseAddress().IsValid()) + return {}; + + lldb::DisassemblerSP disasm_sp = + Disassembler::FindPluginForTarget(target, arch, flavor, plugin_name); + + if (!disasm_sp) + return {}; + + const size_t bytes_disassembled = disasm_sp->ParseInstructions( + target, range.GetBaseAddress(), {Limit::Bytes, range.GetByteSize()}, + nullptr, force_live_memory); + if (bytes_disassembled == 0) + return {}; + + return disasm_sp; +} + +lldb::DisassemblerSP +Disassembler::DisassembleBytes(const ArchSpec &arch, const char *plugin_name, + const char *flavor, const Address &start, + const void *src, size_t src_len, + uint32_t num_instructions, bool data_from_file) { + if (!src) + return {}; + + lldb::DisassemblerSP disasm_sp = + Disassembler::FindPlugin(arch, flavor, plugin_name); + + if (!disasm_sp) + return {}; + + DataExtractor data(src, src_len, arch.GetByteOrder(), + arch.GetAddressByteSize()); + + (void)disasm_sp->DecodeInstructions(start, data, 0, num_instructions, false, + data_from_file); + return disasm_sp; +} + +bool Disassembler::Disassemble(Debugger &debugger, const ArchSpec &arch, + const char *plugin_name, const char *flavor, + const ExecutionContext &exe_ctx, + const Address &address, Limit limit, + bool mixed_source_and_assembly, + uint32_t num_mixed_context_lines, + uint32_t options, Stream &strm) { + if (!exe_ctx.GetTargetPtr()) + return false; + + lldb::DisassemblerSP disasm_sp(Disassembler::FindPluginForTarget( + exe_ctx.GetTargetRef(), arch, flavor, plugin_name)); + if (!disasm_sp) + return false; + + const bool force_live_memory = true; + size_t bytes_disassembled = disasm_sp->ParseInstructions( + exe_ctx.GetTargetRef(), address, limit, &strm, force_live_memory); + if (bytes_disassembled == 0) + return false; + + disasm_sp->PrintInstructions(debugger, arch, exe_ctx, + mixed_source_and_assembly, + num_mixed_context_lines, options, strm); + return true; +} + +Disassembler::SourceLine +Disassembler::GetFunctionDeclLineEntry(const SymbolContext &sc) { + if (!sc.function) + return {}; + + if (!sc.line_entry.IsValid()) + return {}; + + LineEntry prologue_end_line = sc.line_entry; + FileSpec func_decl_file; + uint32_t func_decl_line; + sc.function->GetStartLineSourceInfo(func_decl_file, func_decl_line); + + if (func_decl_file != prologue_end_line.GetFile() && + func_decl_file != prologue_end_line.original_file_sp->GetSpecOnly()) + return {}; + + SourceLine decl_line; + decl_line.file = func_decl_file; + decl_line.line = func_decl_line; + // TODO: Do we care about column on these entries? If so, we need to plumb + // that through GetStartLineSourceInfo. + decl_line.column = 0; + return decl_line; +} + +void Disassembler::AddLineToSourceLineTables( + SourceLine &line, + std::map<FileSpec, std::set<uint32_t>> &source_lines_seen) { + if (line.IsValid()) { + auto source_lines_seen_pos = source_lines_seen.find(line.file); + if (source_lines_seen_pos == source_lines_seen.end()) { + std::set<uint32_t> lines; + lines.insert(line.line); + source_lines_seen.emplace(line.file, lines); + } else { + source_lines_seen_pos->second.insert(line.line); + } + } +} + +bool Disassembler::ElideMixedSourceAndDisassemblyLine( + const ExecutionContext &exe_ctx, const SymbolContext &sc, + SourceLine &line) { + + // TODO: should we also check target.process.thread.step-avoid-libraries ? + + const RegularExpression *avoid_regex = nullptr; + + // Skip any line #0 entries - they are implementation details + if (line.line == 0) + return true; + + ThreadSP thread_sp = exe_ctx.GetThreadSP(); + if (thread_sp) { + avoid_regex = thread_sp->GetSymbolsToAvoidRegexp(); + } else { + TargetSP target_sp = exe_ctx.GetTargetSP(); + if (target_sp) { + Status error; + OptionValueSP value_sp = target_sp->GetDebugger().GetPropertyValue( + &exe_ctx, "target.process.thread.step-avoid-regexp", error); + if (value_sp && value_sp->GetType() == OptionValue::eTypeRegex) { + OptionValueRegex *re = value_sp->GetAsRegex(); + if (re) { + avoid_regex = re->GetCurrentValue(); + } + } + } + } + if (avoid_regex && sc.symbol != nullptr) { + const char *function_name = + sc.GetFunctionName(Mangled::ePreferDemangledWithoutArguments) + .GetCString(); + if (function_name && avoid_regex->Execute(function_name)) { + // skip this source line + return true; + } + } + // don't skip this source line + return false; +} + +void Disassembler::PrintInstructions(Debugger &debugger, const ArchSpec &arch, + const ExecutionContext &exe_ctx, + bool mixed_source_and_assembly, + uint32_t num_mixed_context_lines, + uint32_t options, Stream &strm) { + // We got some things disassembled... + size_t num_instructions_found = GetInstructionList().GetSize(); + + const uint32_t max_opcode_byte_size = + GetInstructionList().GetMaxOpcocdeByteSize(); + SymbolContext sc; + SymbolContext prev_sc; + AddressRange current_source_line_range; + const Address *pc_addr_ptr = nullptr; + StackFrame *frame = exe_ctx.GetFramePtr(); + + TargetSP target_sp(exe_ctx.GetTargetSP()); + SourceManager &source_manager = + target_sp ? target_sp->GetSourceManager() : debugger.GetSourceManager(); + + if (frame) { + pc_addr_ptr = &frame->GetFrameCodeAddress(); + } + const uint32_t scope = + eSymbolContextLineEntry | eSymbolContextFunction | eSymbolContextSymbol; + const bool use_inline_block_range = false; + + const FormatEntity::Entry *disassembly_format = nullptr; + FormatEntity::Entry format; + if (exe_ctx.HasTargetScope()) { + disassembly_format = + exe_ctx.GetTargetRef().GetDebugger().GetDisassemblyFormat(); + } else { + FormatEntity::Parse("${addr}: ", format); + disassembly_format = &format; + } + + // First pass: step through the list of instructions, find how long the + // initial addresses strings are, insert padding in the second pass so the + // opcodes all line up nicely. + + // Also build up the source line mapping if this is mixed source & assembly + // mode. Calculate the source line for each assembly instruction (eliding + // inlined functions which the user wants to skip). + + std::map<FileSpec, std::set<uint32_t>> source_lines_seen; + Symbol *previous_symbol = nullptr; + + size_t address_text_size = 0; + for (size_t i = 0; i < num_instructions_found; ++i) { + Instruction *inst = GetInstructionList().GetInstructionAtIndex(i).get(); + if (inst) { + const Address &addr = inst->GetAddress(); + ModuleSP module_sp(addr.GetModule()); + if (module_sp) { + const SymbolContextItem resolve_mask = eSymbolContextFunction | + eSymbolContextSymbol | + eSymbolContextLineEntry; + uint32_t resolved_mask = + module_sp->ResolveSymbolContextForAddress(addr, resolve_mask, sc); + if (resolved_mask) { + StreamString strmstr; + Debugger::FormatDisassemblerAddress(disassembly_format, &sc, nullptr, + &exe_ctx, &addr, strmstr); + size_t cur_line = strmstr.GetSizeOfLastLine(); + if (cur_line > address_text_size) + address_text_size = cur_line; + + // Add entries to our "source_lines_seen" map+set which list which + // sources lines occur in this disassembly session. We will print + // lines of context around a source line, but we don't want to print + // a source line that has a line table entry of its own - we'll leave + // that source line to be printed when it actually occurs in the + // disassembly. + + if (mixed_source_and_assembly && sc.line_entry.IsValid()) { + if (sc.symbol != previous_symbol) { + SourceLine decl_line = GetFunctionDeclLineEntry(sc); + if (!ElideMixedSourceAndDisassemblyLine(exe_ctx, sc, decl_line)) + AddLineToSourceLineTables(decl_line, source_lines_seen); + } + if (sc.line_entry.IsValid()) { + SourceLine this_line; + this_line.file = sc.line_entry.GetFile(); + this_line.line = sc.line_entry.line; + this_line.column = sc.line_entry.column; + if (!ElideMixedSourceAndDisassemblyLine(exe_ctx, sc, this_line)) + AddLineToSourceLineTables(this_line, source_lines_seen); + } + } + } + sc.Clear(false); + } + } + } + + previous_symbol = nullptr; + SourceLine previous_line; + for (size_t i = 0; i < num_instructions_found; ++i) { + Instruction *inst = GetInstructionList().GetInstructionAtIndex(i).get(); + + if (inst) { + const Address &addr = inst->GetAddress(); + const bool inst_is_at_pc = pc_addr_ptr && addr == *pc_addr_ptr; + SourceLinesToDisplay source_lines_to_display; + + prev_sc = sc; + + ModuleSP module_sp(addr.GetModule()); + if (module_sp) { + uint32_t resolved_mask = module_sp->ResolveSymbolContextForAddress( + addr, eSymbolContextEverything, sc); + if (resolved_mask) { + if (mixed_source_and_assembly) { + + // If we've started a new function (non-inlined), print all of the + // source lines from the function declaration until the first line + // table entry - typically the opening curly brace of the function. + if (previous_symbol != sc.symbol) { + // The default disassembly format puts an extra blank line + // between functions - so when we're displaying the source + // context for a function, we don't want to add a blank line + // after the source context or we'll end up with two of them. + if (previous_symbol != nullptr) + source_lines_to_display.print_source_context_end_eol = false; + + previous_symbol = sc.symbol; + if (sc.function && sc.line_entry.IsValid()) { + LineEntry prologue_end_line = sc.line_entry; + if (!ElideMixedSourceAndDisassemblyLine(exe_ctx, sc, + prologue_end_line)) { + FileSpec func_decl_file; + uint32_t func_decl_line; + sc.function->GetStartLineSourceInfo(func_decl_file, + func_decl_line); + if (func_decl_file == prologue_end_line.GetFile() || + func_decl_file == + prologue_end_line.original_file_sp->GetSpecOnly()) { + // Add all the lines between the function declaration and + // the first non-prologue source line to the list of lines + // to print. + for (uint32_t lineno = func_decl_line; + lineno <= prologue_end_line.line; lineno++) { + SourceLine this_line; + this_line.file = func_decl_file; + this_line.line = lineno; + source_lines_to_display.lines.push_back(this_line); + } + // Mark the last line as the "current" one. Usually this + // is the open curly brace. + if (source_lines_to_display.lines.size() > 0) + source_lines_to_display.current_source_line = + source_lines_to_display.lines.size() - 1; + } + } + } + sc.GetAddressRange(scope, 0, use_inline_block_range, + current_source_line_range); + } + + // If we've left a previous source line's address range, print a + // new source line + if (!current_source_line_range.ContainsFileAddress(addr)) { + sc.GetAddressRange(scope, 0, use_inline_block_range, + current_source_line_range); + + if (sc != prev_sc && sc.comp_unit && sc.line_entry.IsValid()) { + SourceLine this_line; + this_line.file = sc.line_entry.GetFile(); + this_line.line = sc.line_entry.line; + + if (!ElideMixedSourceAndDisassemblyLine(exe_ctx, sc, + this_line)) { + // Only print this source line if it is different from the + // last source line we printed. There may have been inlined + // functions between these lines that we elided, resulting in + // the same line being printed twice in a row for a + // contiguous block of assembly instructions. + if (this_line != previous_line) { + + std::vector<uint32_t> previous_lines; + for (uint32_t i = 0; + i < num_mixed_context_lines && + (this_line.line - num_mixed_context_lines) > 0; + i++) { + uint32_t line = + this_line.line - num_mixed_context_lines + i; + auto pos = source_lines_seen.find(this_line.file); + if (pos != source_lines_seen.end()) { + if (pos->second.count(line) == 1) { + previous_lines.clear(); + } else { + previous_lines.push_back(line); + } + } + } + for (size_t i = 0; i < previous_lines.size(); i++) { + SourceLine previous_line; + previous_line.file = this_line.file; + previous_line.line = previous_lines[i]; + auto pos = source_lines_seen.find(previous_line.file); + if (pos != source_lines_seen.end()) { + pos->second.insert(previous_line.line); + } + source_lines_to_display.lines.push_back(previous_line); + } + + source_lines_to_display.lines.push_back(this_line); + source_lines_to_display.current_source_line = + source_lines_to_display.lines.size() - 1; + + for (uint32_t i = 0; i < num_mixed_context_lines; i++) { + SourceLine next_line; + next_line.file = this_line.file; + next_line.line = this_line.line + i + 1; + auto pos = source_lines_seen.find(next_line.file); + if (pos != source_lines_seen.end()) { + if (pos->second.count(next_line.line) == 1) + break; + pos->second.insert(next_line.line); + } + source_lines_to_display.lines.push_back(next_line); + } + } + previous_line = this_line; + } + } + } + } + } else { + sc.Clear(true); + } + } + + if (source_lines_to_display.lines.size() > 0) { + strm.EOL(); + for (size_t idx = 0; idx < source_lines_to_display.lines.size(); + idx++) { + SourceLine ln = source_lines_to_display.lines[idx]; + const char *line_highlight = ""; + if (inst_is_at_pc && (options & eOptionMarkPCSourceLine)) { + line_highlight = "->"; + } else if (idx == source_lines_to_display.current_source_line) { + line_highlight = "**"; + } + source_manager.DisplaySourceLinesWithLineNumbers( + ln.file, ln.line, ln.column, 0, 0, line_highlight, &strm); + } + if (source_lines_to_display.print_source_context_end_eol) + strm.EOL(); + } + + const bool show_bytes = (options & eOptionShowBytes) != 0; + const bool show_control_flow_kind = + (options & eOptionShowControlFlowKind) != 0; + inst->Dump(&strm, max_opcode_byte_size, true, show_bytes, + show_control_flow_kind, &exe_ctx, &sc, &prev_sc, nullptr, + address_text_size); + strm.EOL(); + } else { + break; + } + } +} + +bool Disassembler::Disassemble(Debugger &debugger, const ArchSpec &arch, + StackFrame &frame, Stream &strm) { + AddressRange range; + SymbolContext sc( + frame.GetSymbolContext(eSymbolContextFunction | eSymbolContextSymbol)); + if (sc.function) { + range = sc.function->GetAddressRange(); + } else if (sc.symbol && sc.symbol->ValueIsAddress()) { + range.GetBaseAddress() = sc.symbol->GetAddressRef(); + range.SetByteSize(sc.symbol->GetByteSize()); + } else { + range.GetBaseAddress() = frame.GetFrameCodeAddress(); + } + + if (range.GetBaseAddress().IsValid() && range.GetByteSize() == 0) + range.SetByteSize(DEFAULT_DISASM_BYTE_SIZE); + + Disassembler::Limit limit = {Disassembler::Limit::Bytes, + range.GetByteSize()}; + if (limit.value == 0) + limit.value = DEFAULT_DISASM_BYTE_SIZE; + + return Disassemble(debugger, arch, nullptr, nullptr, frame, + range.GetBaseAddress(), limit, false, 0, 0, strm); +} + +Instruction::Instruction(const Address &address, AddressClass addr_class) + : m_address(address), m_address_class(addr_class), m_opcode(), + m_calculated_strings(false) {} + +Instruction::~Instruction() = default; + +AddressClass Instruction::GetAddressClass() { + if (m_address_class == AddressClass::eInvalid) + m_address_class = m_address.GetAddressClass(); + return m_address_class; +} + +const char *Instruction::GetNameForInstructionControlFlowKind( + lldb::InstructionControlFlowKind instruction_control_flow_kind) { + switch (instruction_control_flow_kind) { + case eInstructionControlFlowKindUnknown: + return "unknown"; + case eInstructionControlFlowKindOther: + return "other"; + case eInstructionControlFlowKindCall: + return "call"; + case eInstructionControlFlowKindReturn: + return "return"; + case eInstructionControlFlowKindJump: + return "jump"; + case eInstructionControlFlowKindCondJump: + return "cond jump"; + case eInstructionControlFlowKindFarCall: + return "far call"; + case eInstructionControlFlowKindFarReturn: + return "far return"; + case eInstructionControlFlowKindFarJump: + return "far jump"; + } + llvm_unreachable("Fully covered switch above!"); +} + +void Instruction::Dump(lldb_private::Stream *s, uint32_t max_opcode_byte_size, + bool show_address, bool show_bytes, + bool show_control_flow_kind, + const ExecutionContext *exe_ctx, + const SymbolContext *sym_ctx, + const SymbolContext *prev_sym_ctx, + const FormatEntity::Entry *disassembly_addr_format, + size_t max_address_text_size) { + size_t opcode_column_width = 7; + const size_t operand_column_width = 25; + + CalculateMnemonicOperandsAndCommentIfNeeded(exe_ctx); + + StreamString ss; + + if (show_address) { + Debugger::FormatDisassemblerAddress(disassembly_addr_format, sym_ctx, + prev_sym_ctx, exe_ctx, &m_address, ss); + ss.FillLastLineToColumn(max_address_text_size, ' '); + } + + if (show_bytes) { + if (m_opcode.GetType() == Opcode::eTypeBytes) { + // x86_64 and i386 are the only ones that use bytes right now so pad out + // the byte dump to be able to always show 15 bytes (3 chars each) plus a + // space + if (max_opcode_byte_size > 0) + m_opcode.Dump(&ss, max_opcode_byte_size * 3 + 1); + else + m_opcode.Dump(&ss, 15 * 3 + 1); + } else { + // Else, we have ARM or MIPS which can show up to a uint32_t 0x00000000 + // (10 spaces) plus two for padding... + if (max_opcode_byte_size > 0) + m_opcode.Dump(&ss, max_opcode_byte_size * 3 + 1); + else + m_opcode.Dump(&ss, 12); + } + } + + if (show_control_flow_kind) { + lldb::InstructionControlFlowKind instruction_control_flow_kind = + GetControlFlowKind(exe_ctx); + ss.Printf("%-12s", GetNameForInstructionControlFlowKind( + instruction_control_flow_kind)); + } + + bool show_color = false; + if (exe_ctx) { + if (TargetSP target_sp = exe_ctx->GetTargetSP()) { + show_color = target_sp->GetDebugger().GetUseColor(); + } + } + const size_t opcode_pos = ss.GetSizeOfLastLine(); + const std::string &opcode_name = + show_color ? m_markup_opcode_name : m_opcode_name; + const std::string &mnemonics = show_color ? m_markup_mnemonics : m_mnemonics; + + // The default opcode size of 7 characters is plenty for most architectures + // but some like arm can pull out the occasional vqrshrun.s16. We won't get + // consistent column spacing in these cases, unfortunately. Also note that we + // need to directly use m_opcode_name here (instead of opcode_name) so we + // don't include color codes as characters. + if (m_opcode_name.length() >= opcode_column_width) { + opcode_column_width = m_opcode_name.length() + 1; + } + + ss.PutCString(opcode_name); + ss.FillLastLineToColumn(opcode_pos + opcode_column_width, ' '); + ss.PutCString(mnemonics); + + if (!m_comment.empty()) { + ss.FillLastLineToColumn( + opcode_pos + opcode_column_width + operand_column_width, ' '); + ss.PutCString(" ; "); + ss.PutCString(m_comment); + } + s->PutCString(ss.GetString()); +} + +bool Instruction::DumpEmulation(const ArchSpec &arch) { + std::unique_ptr<EmulateInstruction> insn_emulator_up( + EmulateInstruction::FindPlugin(arch, eInstructionTypeAny, nullptr)); + if (insn_emulator_up) { + insn_emulator_up->SetInstruction(GetOpcode(), GetAddress(), nullptr); + return insn_emulator_up->EvaluateInstruction(0); + } + + return false; +} + +bool Instruction::CanSetBreakpoint () { + return !HasDelaySlot(); +} + +bool Instruction::HasDelaySlot() { + // Default is false. + return false; +} + +OptionValueSP Instruction::ReadArray(FILE *in_file, Stream &out_stream, + OptionValue::Type data_type) { + bool done = false; + char buffer[1024]; + + auto option_value_sp = std::make_shared<OptionValueArray>(1u << data_type); + + int idx = 0; + while (!done) { + if (!fgets(buffer, 1023, in_file)) { + out_stream.Printf( + "Instruction::ReadArray: Error reading file (fgets).\n"); + option_value_sp.reset(); + return option_value_sp; + } + + std::string line(buffer); + + size_t len = line.size(); + if (line[len - 1] == '\n') { + line[len - 1] = '\0'; + line.resize(len - 1); + } + + if ((line.size() == 1) && line[0] == ']') { + done = true; + line.clear(); + } + + if (!line.empty()) { + std::string value; + static RegularExpression g_reg_exp( + llvm::StringRef("^[ \t]*([^ \t]+)[ \t]*$")); + llvm::SmallVector<llvm::StringRef, 2> matches; + if (g_reg_exp.Execute(line, &matches)) + value = matches[1].str(); + else + value = line; + + OptionValueSP data_value_sp; + switch (data_type) { + case OptionValue::eTypeUInt64: + data_value_sp = std::make_shared<OptionValueUInt64>(0, 0); + data_value_sp->SetValueFromString(value); + break; + // Other types can be added later as needed. + default: + data_value_sp = std::make_shared<OptionValueString>(value.c_str(), ""); + break; + } + + option_value_sp->GetAsArray()->InsertValue(idx, data_value_sp); + ++idx; + } + } + + return option_value_sp; +} + +OptionValueSP Instruction::ReadDictionary(FILE *in_file, Stream &out_stream) { + bool done = false; + char buffer[1024]; + + auto option_value_sp = std::make_shared<OptionValueDictionary>(); + static constexpr llvm::StringLiteral encoding_key("data_encoding"); + OptionValue::Type data_type = OptionValue::eTypeInvalid; + + while (!done) { + // Read the next line in the file + if (!fgets(buffer, 1023, in_file)) { + out_stream.Printf( + "Instruction::ReadDictionary: Error reading file (fgets).\n"); + option_value_sp.reset(); + return option_value_sp; + } + + // Check to see if the line contains the end-of-dictionary marker ("}") + std::string line(buffer); + + size_t len = line.size(); + if (line[len - 1] == '\n') { + line[len - 1] = '\0'; + line.resize(len - 1); + } + + if ((line.size() == 1) && (line[0] == '}')) { + done = true; + line.clear(); + } + + // Try to find a key-value pair in the current line and add it to the + // dictionary. + if (!line.empty()) { + static RegularExpression g_reg_exp(llvm::StringRef( + "^[ \t]*([a-zA-Z_][a-zA-Z0-9_]*)[ \t]*=[ \t]*(.*)[ \t]*$")); + + llvm::SmallVector<llvm::StringRef, 3> matches; + + bool reg_exp_success = g_reg_exp.Execute(line, &matches); + std::string key; + std::string value; + if (reg_exp_success) { + key = matches[1].str(); + value = matches[2].str(); + } else { + out_stream.Printf("Instruction::ReadDictionary: Failure executing " + "regular expression.\n"); + option_value_sp.reset(); + return option_value_sp; + } + + // Check value to see if it's the start of an array or dictionary. + + lldb::OptionValueSP value_sp; + assert(value.empty() == false); + assert(key.empty() == false); + + if (value[0] == '{') { + assert(value.size() == 1); + // value is a dictionary + value_sp = ReadDictionary(in_file, out_stream); + if (!value_sp) { + option_value_sp.reset(); + return option_value_sp; + } + } else if (value[0] == '[') { + assert(value.size() == 1); + // value is an array + value_sp = ReadArray(in_file, out_stream, data_type); + if (!value_sp) { + option_value_sp.reset(); + return option_value_sp; + } + // We've used the data_type to read an array; re-set the type to + // Invalid + data_type = OptionValue::eTypeInvalid; + } else if ((value[0] == '0') && (value[1] == 'x')) { + value_sp = std::make_shared<OptionValueUInt64>(0, 0); + value_sp->SetValueFromString(value); + } else { + size_t len = value.size(); + if ((value[0] == '"') && (value[len - 1] == '"')) + value = value.substr(1, len - 2); + value_sp = std::make_shared<OptionValueString>(value.c_str(), ""); + } + + if (key == encoding_key) { + // A 'data_encoding=..." is NOT a normal key-value pair; it is meta-data + // indicating the data type of an upcoming array (usually the next bit + // of data to be read in). + if (llvm::StringRef(value) == "uint32_t") + data_type = OptionValue::eTypeUInt64; + } else + option_value_sp->GetAsDictionary()->SetValueForKey(key, value_sp, + false); + } + } + + return option_value_sp; +} + +bool Instruction::TestEmulation(Stream &out_stream, const char *file_name) { + if (!file_name) { + out_stream.Printf("Instruction::TestEmulation: Missing file_name."); + return false; + } + FILE *test_file = FileSystem::Instance().Fopen(file_name, "r"); + if (!test_file) { + out_stream.Printf( + "Instruction::TestEmulation: Attempt to open test file failed."); + return false; + } + + char buffer[256]; + if (!fgets(buffer, 255, test_file)) { + out_stream.Printf( + "Instruction::TestEmulation: Error reading first line of test file.\n"); + fclose(test_file); + return false; + } + + if (strncmp(buffer, "InstructionEmulationState={", 27) != 0) { + out_stream.Printf("Instructin::TestEmulation: Test file does not contain " + "emulation state dictionary\n"); + fclose(test_file); + return false; + } + + // Read all the test information from the test file into an + // OptionValueDictionary. + + OptionValueSP data_dictionary_sp(ReadDictionary(test_file, out_stream)); + if (!data_dictionary_sp) { + out_stream.Printf( + "Instruction::TestEmulation: Error reading Dictionary Object.\n"); + fclose(test_file); + return false; + } + + fclose(test_file); + + OptionValueDictionary *data_dictionary = + data_dictionary_sp->GetAsDictionary(); + static constexpr llvm::StringLiteral description_key("assembly_string"); + static constexpr llvm::StringLiteral triple_key("triple"); + + OptionValueSP value_sp = data_dictionary->GetValueForKey(description_key); + + if (!value_sp) { + out_stream.Printf("Instruction::TestEmulation: Test file does not " + "contain description string.\n"); + return false; + } + + SetDescription(value_sp->GetValueAs<llvm::StringRef>().value_or("")); + + value_sp = data_dictionary->GetValueForKey(triple_key); + if (!value_sp) { + out_stream.Printf( + "Instruction::TestEmulation: Test file does not contain triple.\n"); + return false; + } + + ArchSpec arch; + arch.SetTriple( + llvm::Triple(value_sp->GetValueAs<llvm::StringRef>().value_or(""))); + + bool success = false; + std::unique_ptr<EmulateInstruction> insn_emulator_up( + EmulateInstruction::FindPlugin(arch, eInstructionTypeAny, nullptr)); + if (insn_emulator_up) + success = + insn_emulator_up->TestEmulation(out_stream, arch, data_dictionary); + + if (success) + out_stream.Printf("Emulation test succeeded."); + else + out_stream.Printf("Emulation test failed."); + + return success; +} + +bool Instruction::Emulate( + const ArchSpec &arch, uint32_t evaluate_options, void *baton, + EmulateInstruction::ReadMemoryCallback read_mem_callback, + EmulateInstruction::WriteMemoryCallback write_mem_callback, + EmulateInstruction::ReadRegisterCallback read_reg_callback, + EmulateInstruction::WriteRegisterCallback write_reg_callback) { + std::unique_ptr<EmulateInstruction> insn_emulator_up( + EmulateInstruction::FindPlugin(arch, eInstructionTypeAny, nullptr)); + if (insn_emulator_up) { + insn_emulator_up->SetBaton(baton); + insn_emulator_up->SetCallbacks(read_mem_callback, write_mem_callback, + read_reg_callback, write_reg_callback); + insn_emulator_up->SetInstruction(GetOpcode(), GetAddress(), nullptr); + return insn_emulator_up->EvaluateInstruction(evaluate_options); + } + + return false; +} + +uint32_t Instruction::GetData(DataExtractor &data) { + return m_opcode.GetData(data); +} + +InstructionList::InstructionList() : m_instructions() {} + +InstructionList::~InstructionList() = default; + +size_t InstructionList::GetSize() const { return m_instructions.size(); } + +uint32_t InstructionList::GetMaxOpcocdeByteSize() const { + uint32_t max_inst_size = 0; + collection::const_iterator pos, end; + for (pos = m_instructions.begin(), end = m_instructions.end(); pos != end; + ++pos) { + uint32_t inst_size = (*pos)->GetOpcode().GetByteSize(); + if (max_inst_size < inst_size) + max_inst_size = inst_size; + } + return max_inst_size; +} + +InstructionSP InstructionList::GetInstructionAtIndex(size_t idx) const { + InstructionSP inst_sp; + if (idx < m_instructions.size()) + inst_sp = m_instructions[idx]; + return inst_sp; +} + +InstructionSP InstructionList::GetInstructionAtAddress(const Address &address) { + uint32_t index = GetIndexOfInstructionAtAddress(address); + if (index != UINT32_MAX) + return GetInstructionAtIndex(index); + return nullptr; +} + +void InstructionList::Dump(Stream *s, bool show_address, bool show_bytes, + bool show_control_flow_kind, + const ExecutionContext *exe_ctx) { + const uint32_t max_opcode_byte_size = GetMaxOpcocdeByteSize(); + collection::const_iterator pos, begin, end; + + const FormatEntity::Entry *disassembly_format = nullptr; + FormatEntity::Entry format; + if (exe_ctx && exe_ctx->HasTargetScope()) { + disassembly_format = + exe_ctx->GetTargetRef().GetDebugger().GetDisassemblyFormat(); + } else { + FormatEntity::Parse("${addr}: ", format); + disassembly_format = &format; + } + + for (begin = m_instructions.begin(), end = m_instructions.end(), pos = begin; + pos != end; ++pos) { + if (pos != begin) + s->EOL(); + (*pos)->Dump(s, max_opcode_byte_size, show_address, show_bytes, + show_control_flow_kind, exe_ctx, nullptr, nullptr, + disassembly_format, 0); + } +} + +void InstructionList::Clear() { m_instructions.clear(); } + +void InstructionList::Append(lldb::InstructionSP &inst_sp) { + if (inst_sp) + m_instructions.push_back(inst_sp); +} + +uint32_t +InstructionList::GetIndexOfNextBranchInstruction(uint32_t start, + bool ignore_calls, + bool *found_calls) const { + size_t num_instructions = m_instructions.size(); + + uint32_t next_branch = UINT32_MAX; + + if (found_calls) + *found_calls = false; + for (size_t i = start; i < num_instructions; i++) { + if (m_instructions[i]->DoesBranch()) { + if (ignore_calls && m_instructions[i]->IsCall()) { + if (found_calls) + *found_calls = true; + continue; + } + next_branch = i; + break; + } + } + + return next_branch; +} + +uint32_t +InstructionList::GetIndexOfInstructionAtAddress(const Address &address) { + size_t num_instructions = m_instructions.size(); + uint32_t index = UINT32_MAX; + for (size_t i = 0; i < num_instructions; i++) { + if (m_instructions[i]->GetAddress() == address) { + index = i; + break; + } + } + return index; +} + +uint32_t +InstructionList::GetIndexOfInstructionAtLoadAddress(lldb::addr_t load_addr, + Target &target) { + Address address; + address.SetLoadAddress(load_addr, &target); + return GetIndexOfInstructionAtAddress(address); +} + +size_t Disassembler::ParseInstructions(Target &target, Address start, + Limit limit, Stream *error_strm_ptr, + bool force_live_memory) { + m_instruction_list.Clear(); + + if (!start.IsValid()) + return 0; + + start = ResolveAddress(target, start); + + addr_t byte_size = limit.value; + if (limit.kind == Limit::Instructions) + byte_size *= m_arch.GetMaximumOpcodeByteSize(); + auto data_sp = std::make_shared<DataBufferHeap>(byte_size, '\0'); + + Status error; + lldb::addr_t load_addr = LLDB_INVALID_ADDRESS; + const size_t bytes_read = + target.ReadMemory(start, data_sp->GetBytes(), data_sp->GetByteSize(), + error, force_live_memory, &load_addr); + const bool data_from_file = load_addr == LLDB_INVALID_ADDRESS; + + if (bytes_read == 0) { + if (error_strm_ptr) { + if (const char *error_cstr = error.AsCString()) + error_strm_ptr->Printf("error: %s\n", error_cstr); + } + return 0; + } + + if (bytes_read != data_sp->GetByteSize()) + data_sp->SetByteSize(bytes_read); + DataExtractor data(data_sp, m_arch.GetByteOrder(), + m_arch.GetAddressByteSize()); + return DecodeInstructions(start, data, 0, + limit.kind == Limit::Instructions ? limit.value + : UINT32_MAX, + false, data_from_file); +} + +// Disassembler copy constructor +Disassembler::Disassembler(const ArchSpec &arch, const char *flavor) + : m_arch(arch), m_instruction_list(), m_flavor() { + if (flavor == nullptr) + m_flavor.assign("default"); + else + m_flavor.assign(flavor); + + // If this is an arm variant that can only include thumb (T16, T32) + // instructions, force the arch triple to be "thumbv.." instead of "armv..." + if (arch.IsAlwaysThumbInstructions()) { + std::string thumb_arch_name(arch.GetTriple().getArchName().str()); + // Replace "arm" with "thumb" so we get all thumb variants correct + if (thumb_arch_name.size() > 3) { + thumb_arch_name.erase(0, 3); + thumb_arch_name.insert(0, "thumb"); + } + m_arch.SetTriple(thumb_arch_name.c_str()); + } +} + +Disassembler::~Disassembler() = default; + +InstructionList &Disassembler::GetInstructionList() { + return m_instruction_list; +} + +const InstructionList &Disassembler::GetInstructionList() const { + return m_instruction_list; +} + +// Class PseudoInstruction + +PseudoInstruction::PseudoInstruction() + : Instruction(Address(), AddressClass::eUnknown), m_description() {} + +PseudoInstruction::~PseudoInstruction() = default; + +bool PseudoInstruction::DoesBranch() { + // This is NOT a valid question for a pseudo instruction. + return false; +} + +bool PseudoInstruction::HasDelaySlot() { + // This is NOT a valid question for a pseudo instruction. + return false; +} + +bool PseudoInstruction::IsLoad() { return false; } + +bool PseudoInstruction::IsAuthenticated() { return false; } + +size_t PseudoInstruction::Decode(const lldb_private::Disassembler &disassembler, + const lldb_private::DataExtractor &data, + lldb::offset_t data_offset) { + return m_opcode.GetByteSize(); +} + +void PseudoInstruction::SetOpcode(size_t opcode_size, void *opcode_data) { + if (!opcode_data) + return; + + switch (opcode_size) { + case 8: { + uint8_t value8 = *((uint8_t *)opcode_data); + m_opcode.SetOpcode8(value8, eByteOrderInvalid); + break; + } + case 16: { + uint16_t value16 = *((uint16_t *)opcode_data); + m_opcode.SetOpcode16(value16, eByteOrderInvalid); + break; + } + case 32: { + uint32_t value32 = *((uint32_t *)opcode_data); + m_opcode.SetOpcode32(value32, eByteOrderInvalid); + break; + } + case 64: { + uint64_t value64 = *((uint64_t *)opcode_data); + m_opcode.SetOpcode64(value64, eByteOrderInvalid); + break; + } + default: + break; + } +} + +void PseudoInstruction::SetDescription(llvm::StringRef description) { + m_description = std::string(description); +} + +Instruction::Operand Instruction::Operand::BuildRegister(ConstString &r) { + Operand ret; + ret.m_type = Type::Register; + ret.m_register = r; + return ret; +} + +Instruction::Operand Instruction::Operand::BuildImmediate(lldb::addr_t imm, + bool neg) { + Operand ret; + ret.m_type = Type::Immediate; + ret.m_immediate = imm; + ret.m_negative = neg; + return ret; +} + +Instruction::Operand Instruction::Operand::BuildImmediate(int64_t imm) { + Operand ret; + ret.m_type = Type::Immediate; + if (imm < 0) { + ret.m_immediate = -imm; + ret.m_negative = true; + } else { + ret.m_immediate = imm; + ret.m_negative = false; + } + return ret; +} + +Instruction::Operand +Instruction::Operand::BuildDereference(const Operand &ref) { + Operand ret; + ret.m_type = Type::Dereference; + ret.m_children = {ref}; + return ret; +} + +Instruction::Operand Instruction::Operand::BuildSum(const Operand &lhs, + const Operand &rhs) { + Operand ret; + ret.m_type = Type::Sum; + ret.m_children = {lhs, rhs}; + return ret; +} + +Instruction::Operand Instruction::Operand::BuildProduct(const Operand &lhs, + const Operand &rhs) { + Operand ret; + ret.m_type = Type::Product; + ret.m_children = {lhs, rhs}; + return ret; +} + +std::function<bool(const Instruction::Operand &)> +lldb_private::OperandMatchers::MatchBinaryOp( + std::function<bool(const Instruction::Operand &)> base, + std::function<bool(const Instruction::Operand &)> left, + std::function<bool(const Instruction::Operand &)> right) { + return [base, left, right](const Instruction::Operand &op) -> bool { + return (base(op) && op.m_children.size() == 2 && + ((left(op.m_children[0]) && right(op.m_children[1])) || + (left(op.m_children[1]) && right(op.m_children[0])))); + }; +} + +std::function<bool(const Instruction::Operand &)> +lldb_private::OperandMatchers::MatchUnaryOp( + std::function<bool(const Instruction::Operand &)> base, + std::function<bool(const Instruction::Operand &)> child) { + return [base, child](const Instruction::Operand &op) -> bool { + return (base(op) && op.m_children.size() == 1 && child(op.m_children[0])); + }; +} + +std::function<bool(const Instruction::Operand &)> +lldb_private::OperandMatchers::MatchRegOp(const RegisterInfo &info) { + return [&info](const Instruction::Operand &op) { + return (op.m_type == Instruction::Operand::Type::Register && + (op.m_register == ConstString(info.name) || + op.m_register == ConstString(info.alt_name))); + }; +} + +std::function<bool(const Instruction::Operand &)> +lldb_private::OperandMatchers::FetchRegOp(ConstString ®) { + return [®](const Instruction::Operand &op) { + if (op.m_type != Instruction::Operand::Type::Register) { + return false; + } + reg = op.m_register; + return true; + }; +} + +std::function<bool(const Instruction::Operand &)> +lldb_private::OperandMatchers::MatchImmOp(int64_t imm) { + return [imm](const Instruction::Operand &op) { + return (op.m_type == Instruction::Operand::Type::Immediate && + ((op.m_negative && op.m_immediate == (uint64_t)-imm) || + (!op.m_negative && op.m_immediate == (uint64_t)imm))); + }; +} + +std::function<bool(const Instruction::Operand &)> +lldb_private::OperandMatchers::FetchImmOp(int64_t &imm) { + return [&imm](const Instruction::Operand &op) { + if (op.m_type != Instruction::Operand::Type::Immediate) { + return false; + } + if (op.m_negative) { + imm = -((int64_t)op.m_immediate); + } else { + imm = ((int64_t)op.m_immediate); + } + return true; + }; +} + +std::function<bool(const Instruction::Operand &)> +lldb_private::OperandMatchers::MatchOpType(Instruction::Operand::Type type) { + return [type](const Instruction::Operand &op) { return op.m_type == type; }; +} diff --git a/contrib/llvm-project/lldb/source/Core/DumpDataExtractor.cpp b/contrib/llvm-project/lldb/source/Core/DumpDataExtractor.cpp new file mode 100644 index 000000000000..826edd7bab04 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/DumpDataExtractor.cpp @@ -0,0 +1,913 @@ +//===-- DumpDataExtractor.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/Core/DumpDataExtractor.h" + +#include "lldb/lldb-defines.h" +#include "lldb/lldb-forward.h" + +#include "lldb/Core/Address.h" +#include "lldb/Core/Disassembler.h" +#include "lldb/Core/ModuleList.h" +#include "lldb/Target/ABI.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/ExecutionContextScope.h" +#include "lldb/Target/MemoryRegionInfo.h" +#include "lldb/Target/MemoryTagManager.h" +#include "lldb/Target/MemoryTagMap.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/DataExtractor.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Stream.h" + +#include "llvm/ADT/APFloat.h" +#include "llvm/ADT/APInt.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallVector.h" + +#include <limits> +#include <memory> +#include <string> + +#include <cassert> +#include <cctype> +#include <cinttypes> +#include <cmath> + +#include <bitset> +#include <optional> +#include <sstream> + +using namespace lldb_private; +using namespace lldb; + +#define NON_PRINTABLE_CHAR '.' + +static std::optional<llvm::APInt> GetAPInt(const DataExtractor &data, + lldb::offset_t *offset_ptr, + lldb::offset_t byte_size) { + if (byte_size == 0) + return std::nullopt; + + llvm::SmallVector<uint64_t, 2> uint64_array; + lldb::offset_t bytes_left = byte_size; + uint64_t u64; + const lldb::ByteOrder byte_order = data.GetByteOrder(); + if (byte_order == lldb::eByteOrderLittle) { + while (bytes_left > 0) { + if (bytes_left >= 8) { + u64 = data.GetU64(offset_ptr); + bytes_left -= 8; + } else { + u64 = data.GetMaxU64(offset_ptr, (uint32_t)bytes_left); + bytes_left = 0; + } + uint64_array.push_back(u64); + } + return llvm::APInt(byte_size * 8, llvm::ArrayRef<uint64_t>(uint64_array)); + } else if (byte_order == lldb::eByteOrderBig) { + lldb::offset_t be_offset = *offset_ptr + byte_size; + lldb::offset_t temp_offset; + while (bytes_left > 0) { + if (bytes_left >= 8) { + be_offset -= 8; + temp_offset = be_offset; + u64 = data.GetU64(&temp_offset); + bytes_left -= 8; + } else { + be_offset -= bytes_left; + temp_offset = be_offset; + u64 = data.GetMaxU64(&temp_offset, (uint32_t)bytes_left); + bytes_left = 0; + } + uint64_array.push_back(u64); + } + *offset_ptr += byte_size; + return llvm::APInt(byte_size * 8, llvm::ArrayRef<uint64_t>(uint64_array)); + } + return std::nullopt; +} + +static lldb::offset_t DumpAPInt(Stream *s, const DataExtractor &data, + lldb::offset_t offset, lldb::offset_t byte_size, + bool is_signed, unsigned radix) { + std::optional<llvm::APInt> apint = GetAPInt(data, &offset, byte_size); + if (apint) { + std::string apint_str = toString(*apint, radix, is_signed); + switch (radix) { + case 2: + s->Write("0b", 2); + break; + case 8: + s->Write("0", 1); + break; + case 10: + break; + } + s->Write(apint_str.c_str(), apint_str.size()); + } + return offset; +} + +/// Dumps decoded instructions to a stream. +static lldb::offset_t DumpInstructions(const DataExtractor &DE, Stream *s, + ExecutionContextScope *exe_scope, + offset_t start_offset, + uint64_t base_addr, + size_t number_of_instructions) { + offset_t offset = start_offset; + + TargetSP target_sp; + if (exe_scope) + target_sp = exe_scope->CalculateTarget(); + if (target_sp) { + DisassemblerSP disassembler_sp( + Disassembler::FindPlugin(target_sp->GetArchitecture(), + target_sp->GetDisassemblyFlavor(), nullptr)); + if (disassembler_sp) { + lldb::addr_t addr = base_addr + start_offset; + lldb_private::Address so_addr; + bool data_from_file = true; + if (target_sp->GetSectionLoadList().ResolveLoadAddress(addr, so_addr)) { + data_from_file = false; + } else { + if (target_sp->GetSectionLoadList().IsEmpty() || + !target_sp->GetImages().ResolveFileAddress(addr, so_addr)) + so_addr.SetRawAddress(addr); + } + + size_t bytes_consumed = disassembler_sp->DecodeInstructions( + so_addr, DE, start_offset, number_of_instructions, false, + data_from_file); + + if (bytes_consumed) { + offset += bytes_consumed; + const bool show_address = base_addr != LLDB_INVALID_ADDRESS; + const bool show_bytes = false; + const bool show_control_flow_kind = false; + ExecutionContext exe_ctx; + exe_scope->CalculateExecutionContext(exe_ctx); + disassembler_sp->GetInstructionList().Dump( + s, show_address, show_bytes, show_control_flow_kind, &exe_ctx); + } + } + } else + s->Printf("invalid target"); + + return offset; +} + +/// Prints the specific escape sequence of the given character to the stream. +/// If the character doesn't have a known specific escape sequence (e.g., '\a', +/// '\n' but not generic escape sequences such as'\x12'), this function will +/// not modify the stream and return false. +static bool TryDumpSpecialEscapedChar(Stream &s, const char c) { + switch (c) { + case '\033': + // Common non-standard escape code for 'escape'. + s.Printf("\\e"); + return true; + case '\a': + s.Printf("\\a"); + return true; + case '\b': + s.Printf("\\b"); + return true; + case '\f': + s.Printf("\\f"); + return true; + case '\n': + s.Printf("\\n"); + return true; + case '\r': + s.Printf("\\r"); + return true; + case '\t': + s.Printf("\\t"); + return true; + case '\v': + s.Printf("\\v"); + return true; + case '\0': + s.Printf("\\0"); + return true; + default: + return false; + } +} + +/// Dump the character to a stream. A character that is not printable will be +/// represented by its escape sequence. +static void DumpCharacter(Stream &s, const char c) { + if (TryDumpSpecialEscapedChar(s, c)) + return; + if (llvm::isPrint(c)) { + s.PutChar(c); + return; + } + s.Printf("\\x%2.2hhx", c); +} + +/// Dump a floating point type. +template <typename FloatT> +void DumpFloatingPoint(std::ostringstream &ss, FloatT f) { + static_assert(std::is_floating_point<FloatT>::value, + "Only floating point types can be dumped."); + // NaN and Inf are potentially implementation defined and on Darwin it + // seems NaNs are printed without their sign. Manually implement dumping them + // here to avoid having to deal with platform differences. + if (std::isnan(f)) { + if (std::signbit(f)) + ss << '-'; + ss << "nan"; + return; + } + if (std::isinf(f)) { + if (std::signbit(f)) + ss << '-'; + ss << "inf"; + return; + } + ss << f; +} + +static std::optional<MemoryTagMap> +GetMemoryTags(lldb::addr_t addr, size_t length, + ExecutionContextScope *exe_scope) { + assert(addr != LLDB_INVALID_ADDRESS); + + if (!exe_scope) + return std::nullopt; + + TargetSP target_sp = exe_scope->CalculateTarget(); + if (!target_sp) + return std::nullopt; + + ProcessSP process_sp = target_sp->CalculateProcess(); + if (!process_sp) + return std::nullopt; + + llvm::Expected<const MemoryTagManager *> tag_manager_or_err = + process_sp->GetMemoryTagManager(); + if (!tag_manager_or_err) { + llvm::consumeError(tag_manager_or_err.takeError()); + return std::nullopt; + } + + MemoryRegionInfos memory_regions; + // Don't check return status, list will be just empty if an error happened. + process_sp->GetMemoryRegions(memory_regions); + + llvm::Expected<std::vector<MemoryTagManager::TagRange>> tagged_ranges_or_err = + (*tag_manager_or_err) + ->MakeTaggedRanges(addr, addr + length, memory_regions); + // Here we know that our range will not be inverted but we must still check + // for an error. + if (!tagged_ranges_or_err) { + llvm::consumeError(tagged_ranges_or_err.takeError()); + return std::nullopt; + } + if (tagged_ranges_or_err->empty()) + return std::nullopt; + + MemoryTagMap memory_tag_map(*tag_manager_or_err); + for (const MemoryTagManager::TagRange &range : *tagged_ranges_or_err) { + llvm::Expected<std::vector<lldb::addr_t>> tags_or_err = + process_sp->ReadMemoryTags(range.GetRangeBase(), range.GetByteSize()); + + if (tags_or_err) + memory_tag_map.InsertTags(range.GetRangeBase(), *tags_or_err); + else + llvm::consumeError(tags_or_err.takeError()); + } + + if (memory_tag_map.Empty()) + return std::nullopt; + + return memory_tag_map; +} + +static void printMemoryTags(const DataExtractor &DE, Stream *s, + lldb::addr_t addr, size_t len, + const std::optional<MemoryTagMap> &memory_tag_map) { + std::vector<std::optional<lldb::addr_t>> tags = + memory_tag_map->GetTags(addr, len); + + // Only print if there is at least one tag for this line + if (tags.empty()) + return; + + s->Printf(" (tag%s:", tags.size() > 1 ? "s" : ""); + // Some granules may not be tagged but print something for them + // so that the ordering remains intact. + for (auto tag : tags) { + if (tag) + s->Printf(" 0x%" PRIx64, *tag); + else + s->PutCString(" <no tag>"); + } + s->PutCString(")"); +} + +static const llvm::fltSemantics &GetFloatSemantics(const TargetSP &target_sp, + size_t byte_size) { + if (target_sp) { + auto type_system_or_err = + target_sp->GetScratchTypeSystemForLanguage(eLanguageTypeC); + if (!type_system_or_err) + llvm::consumeError(type_system_or_err.takeError()); + else if (auto ts = *type_system_or_err) + return ts->GetFloatTypeSemantics(byte_size); + } + // No target, just make a reasonable guess + switch(byte_size) { + case 2: + return llvm::APFloat::IEEEhalf(); + case 4: + return llvm::APFloat::IEEEsingle(); + case 8: + return llvm::APFloat::IEEEdouble(); + } + return llvm::APFloat::Bogus(); +} + +lldb::offset_t lldb_private::DumpDataExtractor( + const DataExtractor &DE, Stream *s, offset_t start_offset, + lldb::Format item_format, size_t item_byte_size, size_t item_count, + size_t num_per_line, uint64_t base_addr, + uint32_t item_bit_size, // If zero, this is not a bitfield value, if + // non-zero, the value is a bitfield + uint32_t item_bit_offset, // If "item_bit_size" is non-zero, this is the + // shift amount to apply to a bitfield + ExecutionContextScope *exe_scope, bool show_memory_tags) { + if (s == nullptr) + return start_offset; + + if (item_format == eFormatPointer) { + if (item_byte_size != 4 && item_byte_size != 8) + item_byte_size = s->GetAddressByteSize(); + } + + offset_t offset = start_offset; + + std::optional<MemoryTagMap> memory_tag_map; + if (show_memory_tags && base_addr != LLDB_INVALID_ADDRESS) + memory_tag_map = + GetMemoryTags(base_addr, DE.GetByteSize() - offset, exe_scope); + + if (item_format == eFormatInstruction) + return DumpInstructions(DE, s, exe_scope, start_offset, base_addr, + item_count); + + if ((item_format == eFormatOSType || item_format == eFormatAddressInfo) && + item_byte_size > 8) + item_format = eFormatHex; + + lldb::offset_t line_start_offset = start_offset; + for (uint32_t count = 0; DE.ValidOffset(offset) && count < item_count; + ++count) { + // If we are at the beginning or end of a line + // Note that the last line is handled outside this for loop. + if ((count % num_per_line) == 0) { + // If we are at the end of a line + if (count > 0) { + if (item_format == eFormatBytesWithASCII && + offset > line_start_offset) { + s->Printf("%*s", + static_cast<int>( + (num_per_line - (offset - line_start_offset)) * 3 + 2), + ""); + DumpDataExtractor(DE, s, line_start_offset, eFormatCharPrintable, 1, + offset - line_start_offset, SIZE_MAX, + LLDB_INVALID_ADDRESS, 0, 0); + } + + if (base_addr != LLDB_INVALID_ADDRESS && memory_tag_map) { + size_t line_len = offset - line_start_offset; + lldb::addr_t line_base = + base_addr + + (offset - start_offset - line_len) / DE.getTargetByteSize(); + printMemoryTags(DE, s, line_base, line_len, memory_tag_map); + } + + s->EOL(); + } + if (base_addr != LLDB_INVALID_ADDRESS) + s->Printf("0x%8.8" PRIx64 ": ", + (uint64_t)(base_addr + + (offset - start_offset) / DE.getTargetByteSize())); + + line_start_offset = offset; + } else if (item_format != eFormatChar && + item_format != eFormatCharPrintable && + item_format != eFormatCharArray && count > 0) { + s->PutChar(' '); + } + + switch (item_format) { + case eFormatBoolean: + if (item_byte_size <= 8) + s->Printf("%s", DE.GetMaxU64Bitfield(&offset, item_byte_size, + item_bit_size, item_bit_offset) + ? "true" + : "false"); + else { + s->Printf("error: unsupported byte size (%" PRIu64 + ") for boolean format", + (uint64_t)item_byte_size); + return offset; + } + break; + + case eFormatBinary: + if (item_byte_size <= 8) { + uint64_t uval64 = DE.GetMaxU64Bitfield(&offset, item_byte_size, + item_bit_size, item_bit_offset); + // Avoid std::bitset<64>::to_string() since it is missing in earlier + // C++ libraries + std::string binary_value(64, '0'); + std::bitset<64> bits(uval64); + for (uint32_t i = 0; i < 64; ++i) + if (bits[i]) + binary_value[64 - 1 - i] = '1'; + if (item_bit_size > 0) + s->Printf("0b%s", binary_value.c_str() + 64 - item_bit_size); + else if (item_byte_size > 0 && item_byte_size <= 8) + s->Printf("0b%s", binary_value.c_str() + 64 - item_byte_size * 8); + } else { + const bool is_signed = false; + const unsigned radix = 2; + offset = DumpAPInt(s, DE, offset, item_byte_size, is_signed, radix); + } + break; + + case eFormatBytes: + case eFormatBytesWithASCII: + for (uint32_t i = 0; i < item_byte_size; ++i) { + s->Printf("%2.2x", DE.GetU8(&offset)); + } + + // Put an extra space between the groups of bytes if more than one is + // being dumped in a group (item_byte_size is more than 1). + if (item_byte_size > 1) + s->PutChar(' '); + break; + + case eFormatChar: + case eFormatCharPrintable: + case eFormatCharArray: { + // Reject invalid item_byte_size. + if (item_byte_size > 8) { + s->Printf("error: unsupported byte size (%" PRIu64 ") for char format", + (uint64_t)item_byte_size); + return offset; + } + + // If we are only printing one character surround it with single quotes + if (item_count == 1 && item_format == eFormatChar) + s->PutChar('\''); + + const uint64_t ch = DE.GetMaxU64Bitfield(&offset, item_byte_size, + item_bit_size, item_bit_offset); + if (llvm::isPrint(ch)) + s->Printf("%c", (char)ch); + else if (item_format != eFormatCharPrintable) { + if (!TryDumpSpecialEscapedChar(*s, ch)) { + if (item_byte_size == 1) + s->Printf("\\x%2.2x", (uint8_t)ch); + else + s->Printf("%" PRIu64, ch); + } + } else { + s->PutChar(NON_PRINTABLE_CHAR); + } + + // If we are only printing one character surround it with single quotes + if (item_count == 1 && item_format == eFormatChar) + s->PutChar('\''); + } break; + + case eFormatEnum: // Print enum value as a signed integer when we don't get + // the enum type + case eFormatDecimal: + if (item_byte_size <= 8) + s->Printf("%" PRId64, + DE.GetMaxS64Bitfield(&offset, item_byte_size, item_bit_size, + item_bit_offset)); + else { + const bool is_signed = true; + const unsigned radix = 10; + offset = DumpAPInt(s, DE, offset, item_byte_size, is_signed, radix); + } + break; + + case eFormatUnsigned: + if (item_byte_size <= 8) + s->Printf("%" PRIu64, + DE.GetMaxU64Bitfield(&offset, item_byte_size, item_bit_size, + item_bit_offset)); + else { + const bool is_signed = false; + const unsigned radix = 10; + offset = DumpAPInt(s, DE, offset, item_byte_size, is_signed, radix); + } + break; + + case eFormatOctal: + if (item_byte_size <= 8) + s->Printf("0%" PRIo64, + DE.GetMaxS64Bitfield(&offset, item_byte_size, item_bit_size, + item_bit_offset)); + else { + const bool is_signed = false; + const unsigned radix = 8; + offset = DumpAPInt(s, DE, offset, item_byte_size, is_signed, radix); + } + break; + + case eFormatOSType: { + uint64_t uval64 = DE.GetMaxU64Bitfield(&offset, item_byte_size, + item_bit_size, item_bit_offset); + s->PutChar('\''); + for (uint32_t i = 0; i < item_byte_size; ++i) { + uint8_t ch = (uint8_t)(uval64 >> ((item_byte_size - i - 1) * 8)); + DumpCharacter(*s, ch); + } + s->PutChar('\''); + } break; + + case eFormatCString: { + const char *cstr = DE.GetCStr(&offset); + + if (!cstr) { + s->Printf("NULL"); + offset = LLDB_INVALID_OFFSET; + } else { + s->PutChar('\"'); + + while (const char c = *cstr) { + DumpCharacter(*s, c); + ++cstr; + } + + s->PutChar('\"'); + } + } break; + + case eFormatPointer: + DumpAddress(s->AsRawOstream(), + DE.GetMaxU64Bitfield(&offset, item_byte_size, item_bit_size, + item_bit_offset), + sizeof(addr_t)); + break; + + case eFormatComplexInteger: { + size_t complex_int_byte_size = item_byte_size / 2; + + if (complex_int_byte_size > 0 && complex_int_byte_size <= 8) { + s->Printf("%" PRIu64, + DE.GetMaxU64Bitfield(&offset, complex_int_byte_size, 0, 0)); + s->Printf(" + %" PRIu64 "i", + DE.GetMaxU64Bitfield(&offset, complex_int_byte_size, 0, 0)); + } else { + s->Printf("error: unsupported byte size (%" PRIu64 + ") for complex integer format", + (uint64_t)item_byte_size); + return offset; + } + } break; + + case eFormatComplex: + if (sizeof(float) * 2 == item_byte_size) { + float f32_1 = DE.GetFloat(&offset); + float f32_2 = DE.GetFloat(&offset); + + s->Printf("%g + %gi", f32_1, f32_2); + break; + } else if (sizeof(double) * 2 == item_byte_size) { + double d64_1 = DE.GetDouble(&offset); + double d64_2 = DE.GetDouble(&offset); + + s->Printf("%lg + %lgi", d64_1, d64_2); + break; + } else if (sizeof(long double) * 2 == item_byte_size) { + long double ld64_1 = DE.GetLongDouble(&offset); + long double ld64_2 = DE.GetLongDouble(&offset); + s->Printf("%Lg + %Lgi", ld64_1, ld64_2); + break; + } else { + s->Printf("error: unsupported byte size (%" PRIu64 + ") for complex float format", + (uint64_t)item_byte_size); + return offset; + } + break; + + default: + case eFormatDefault: + case eFormatHex: + case eFormatHexUppercase: { + bool wantsuppercase = (item_format == eFormatHexUppercase); + switch (item_byte_size) { + case 1: + case 2: + case 4: + case 8: + if (Target::GetGlobalProperties() + .ShowHexVariableValuesWithLeadingZeroes()) { + s->Printf(wantsuppercase ? "0x%*.*" PRIX64 : "0x%*.*" PRIx64, + (int)(2 * item_byte_size), (int)(2 * item_byte_size), + DE.GetMaxU64Bitfield(&offset, item_byte_size, item_bit_size, + item_bit_offset)); + } else { + s->Printf(wantsuppercase ? "0x%" PRIX64 : "0x%" PRIx64, + DE.GetMaxU64Bitfield(&offset, item_byte_size, item_bit_size, + item_bit_offset)); + } + break; + default: { + assert(item_bit_size == 0 && item_bit_offset == 0); + const uint8_t *bytes = + (const uint8_t *)DE.GetData(&offset, item_byte_size); + if (bytes) { + s->PutCString("0x"); + uint32_t idx; + if (DE.GetByteOrder() == eByteOrderBig) { + for (idx = 0; idx < item_byte_size; ++idx) + s->Printf(wantsuppercase ? "%2.2X" : "%2.2x", bytes[idx]); + } else { + for (idx = 0; idx < item_byte_size; ++idx) + s->Printf(wantsuppercase ? "%2.2X" : "%2.2x", + bytes[item_byte_size - 1 - idx]); + } + } + } break; + } + } break; + + case eFormatFloat: { + TargetSP target_sp; + if (exe_scope) + target_sp = exe_scope->CalculateTarget(); + + std::optional<unsigned> format_max_padding; + if (target_sp) + format_max_padding = target_sp->GetMaxZeroPaddingInFloatFormat(); + + // Show full precision when printing float values + const unsigned format_precision = 0; + + const llvm::fltSemantics &semantics = + GetFloatSemantics(target_sp, item_byte_size); + + // Recalculate the byte size in case of a difference. This is possible + // when item_byte_size is 16 (128-bit), because you could get back the + // x87DoubleExtended semantics which has a byte size of 10 (80-bit). + const size_t semantics_byte_size = + (llvm::APFloat::getSizeInBits(semantics) + 7) / 8; + std::optional<llvm::APInt> apint = + GetAPInt(DE, &offset, semantics_byte_size); + if (apint) { + llvm::APFloat apfloat(semantics, *apint); + llvm::SmallVector<char, 256> sv; + if (format_max_padding) + apfloat.toString(sv, format_precision, *format_max_padding); + else + apfloat.toString(sv, format_precision); + s->AsRawOstream() << sv; + } else { + s->Format("error: unsupported byte size ({0}) for float format", + item_byte_size); + return offset; + } + } break; + + case eFormatUnicode16: + s->Printf("U+%4.4x", DE.GetU16(&offset)); + break; + + case eFormatUnicode32: + s->Printf("U+0x%8.8x", DE.GetU32(&offset)); + break; + + case eFormatAddressInfo: { + addr_t addr = DE.GetMaxU64Bitfield(&offset, item_byte_size, item_bit_size, + item_bit_offset); + s->Printf("0x%*.*" PRIx64, (int)(2 * item_byte_size), + (int)(2 * item_byte_size), addr); + if (exe_scope) { + TargetSP target_sp(exe_scope->CalculateTarget()); + lldb_private::Address so_addr; + if (target_sp) { + if (target_sp->GetSectionLoadList().ResolveLoadAddress(addr, + so_addr)) { + s->PutChar(' '); + so_addr.Dump(s, exe_scope, Address::DumpStyleResolvedDescription, + Address::DumpStyleModuleWithFileAddress); + } else { + so_addr.SetOffset(addr); + so_addr.Dump(s, exe_scope, + Address::DumpStyleResolvedPointerDescription); + if (ProcessSP process_sp = exe_scope->CalculateProcess()) { + if (ABISP abi_sp = process_sp->GetABI()) { + addr_t addr_fixed = abi_sp->FixCodeAddress(addr); + if (target_sp->GetSectionLoadList().ResolveLoadAddress( + addr_fixed, so_addr)) { + s->PutChar(' '); + s->Printf("(0x%*.*" PRIx64 ")", (int)(2 * item_byte_size), + (int)(2 * item_byte_size), addr_fixed); + s->PutChar(' '); + so_addr.Dump(s, exe_scope, + Address::DumpStyleResolvedDescription, + Address::DumpStyleModuleWithFileAddress); + } + } + } + } + } + } + } break; + + case eFormatHexFloat: + if (sizeof(float) == item_byte_size) { + char float_cstr[256]; + llvm::APFloat ap_float(DE.GetFloat(&offset)); + ap_float.convertToHexString(float_cstr, 0, false, + llvm::APFloat::rmNearestTiesToEven); + s->Printf("%s", float_cstr); + break; + } else if (sizeof(double) == item_byte_size) { + char float_cstr[256]; + llvm::APFloat ap_float(DE.GetDouble(&offset)); + ap_float.convertToHexString(float_cstr, 0, false, + llvm::APFloat::rmNearestTiesToEven); + s->Printf("%s", float_cstr); + break; + } else { + s->Printf("error: unsupported byte size (%" PRIu64 + ") for hex float format", + (uint64_t)item_byte_size); + return offset; + } + break; + + // please keep the single-item formats below in sync with + // FormatManager::GetSingleItemFormat if you fail to do so, users will + // start getting different outputs depending on internal implementation + // details they should not care about || + case eFormatVectorOfChar: // || + s->PutChar('{'); // \/ + offset = + DumpDataExtractor(DE, s, offset, eFormatCharArray, 1, item_byte_size, + item_byte_size, LLDB_INVALID_ADDRESS, 0, 0); + s->PutChar('}'); + break; + + case eFormatVectorOfSInt8: + s->PutChar('{'); + offset = + DumpDataExtractor(DE, s, offset, eFormatDecimal, 1, item_byte_size, + item_byte_size, LLDB_INVALID_ADDRESS, 0, 0); + s->PutChar('}'); + break; + + case eFormatVectorOfUInt8: + s->PutChar('{'); + offset = DumpDataExtractor(DE, s, offset, eFormatHex, 1, item_byte_size, + item_byte_size, LLDB_INVALID_ADDRESS, 0, 0); + s->PutChar('}'); + break; + + case eFormatVectorOfSInt16: + s->PutChar('{'); + offset = DumpDataExtractor( + DE, s, offset, eFormatDecimal, sizeof(uint16_t), + item_byte_size / sizeof(uint16_t), item_byte_size / sizeof(uint16_t), + LLDB_INVALID_ADDRESS, 0, 0); + s->PutChar('}'); + break; + + case eFormatVectorOfUInt16: + s->PutChar('{'); + offset = DumpDataExtractor(DE, s, offset, eFormatHex, sizeof(uint16_t), + item_byte_size / sizeof(uint16_t), + item_byte_size / sizeof(uint16_t), + LLDB_INVALID_ADDRESS, 0, 0); + s->PutChar('}'); + break; + + case eFormatVectorOfSInt32: + s->PutChar('{'); + offset = DumpDataExtractor( + DE, s, offset, eFormatDecimal, sizeof(uint32_t), + item_byte_size / sizeof(uint32_t), item_byte_size / sizeof(uint32_t), + LLDB_INVALID_ADDRESS, 0, 0); + s->PutChar('}'); + break; + + case eFormatVectorOfUInt32: + s->PutChar('{'); + offset = DumpDataExtractor(DE, s, offset, eFormatHex, sizeof(uint32_t), + item_byte_size / sizeof(uint32_t), + item_byte_size / sizeof(uint32_t), + LLDB_INVALID_ADDRESS, 0, 0); + s->PutChar('}'); + break; + + case eFormatVectorOfSInt64: + s->PutChar('{'); + offset = DumpDataExtractor( + DE, s, offset, eFormatDecimal, sizeof(uint64_t), + item_byte_size / sizeof(uint64_t), item_byte_size / sizeof(uint64_t), + LLDB_INVALID_ADDRESS, 0, 0); + s->PutChar('}'); + break; + + case eFormatVectorOfUInt64: + s->PutChar('{'); + offset = DumpDataExtractor(DE, s, offset, eFormatHex, sizeof(uint64_t), + item_byte_size / sizeof(uint64_t), + item_byte_size / sizeof(uint64_t), + LLDB_INVALID_ADDRESS, 0, 0); + s->PutChar('}'); + break; + + case eFormatVectorOfFloat16: + s->PutChar('{'); + offset = + DumpDataExtractor(DE, s, offset, eFormatFloat, 2, item_byte_size / 2, + item_byte_size / 2, LLDB_INVALID_ADDRESS, 0, 0); + s->PutChar('}'); + break; + + case eFormatVectorOfFloat32: + s->PutChar('{'); + offset = + DumpDataExtractor(DE, s, offset, eFormatFloat, 4, item_byte_size / 4, + item_byte_size / 4, LLDB_INVALID_ADDRESS, 0, 0); + s->PutChar('}'); + break; + + case eFormatVectorOfFloat64: + s->PutChar('{'); + offset = + DumpDataExtractor(DE, s, offset, eFormatFloat, 8, item_byte_size / 8, + item_byte_size / 8, LLDB_INVALID_ADDRESS, 0, 0); + s->PutChar('}'); + break; + + case eFormatVectorOfUInt128: + s->PutChar('{'); + offset = + DumpDataExtractor(DE, s, offset, eFormatHex, 16, item_byte_size / 16, + item_byte_size / 16, LLDB_INVALID_ADDRESS, 0, 0); + s->PutChar('}'); + break; + } + } + + // If anything was printed we want to catch the end of the last line. + // Since we will exit the for loop above before we get a chance to append to + // it normally. + if (offset > line_start_offset) { + if (item_format == eFormatBytesWithASCII) { + s->Printf("%*s", + static_cast<int>( + (num_per_line - (offset - line_start_offset)) * 3 + 2), + ""); + DumpDataExtractor(DE, s, line_start_offset, eFormatCharPrintable, 1, + offset - line_start_offset, SIZE_MAX, + LLDB_INVALID_ADDRESS, 0, 0); + } + + if (base_addr != LLDB_INVALID_ADDRESS && memory_tag_map) { + size_t line_len = offset - line_start_offset; + lldb::addr_t line_base = base_addr + (offset - start_offset - line_len) / + DE.getTargetByteSize(); + printMemoryTags(DE, s, line_base, line_len, memory_tag_map); + } + } + + return offset; // Return the offset at which we ended up +} + +void lldb_private::DumpHexBytes(Stream *s, const void *src, size_t src_len, + uint32_t bytes_per_line, + lldb::addr_t base_addr) { + DataExtractor data(src, src_len, lldb::eByteOrderLittle, 4); + DumpDataExtractor(data, s, + 0, // Offset into "src" + lldb::eFormatBytes, // Dump as hex bytes + 1, // Size of each item is 1 for single bytes + src_len, // Number of bytes + bytes_per_line, // Num bytes per line + base_addr, // Base address + 0, 0); // Bitfield info +} diff --git a/contrib/llvm-project/lldb/source/Core/DumpRegisterInfo.cpp b/contrib/llvm-project/lldb/source/Core/DumpRegisterInfo.cpp new file mode 100644 index 000000000000..eccc6784cd49 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/DumpRegisterInfo.cpp @@ -0,0 +1,121 @@ +//===-- DumpRegisterInfo.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/Core/DumpRegisterInfo.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/RegisterFlags.h" +#include "lldb/Utility/Stream.h" + +using namespace lldb; +using namespace lldb_private; + +using SetInfo = std::pair<const char *, uint32_t>; + +void lldb_private::DumpRegisterInfo(Stream &strm, RegisterContext &ctx, + const RegisterInfo &info, + uint32_t terminal_width) { + std::vector<const char *> invalidates; + if (info.invalidate_regs) { + for (uint32_t *inv_regs = info.invalidate_regs; + *inv_regs != LLDB_INVALID_REGNUM; ++inv_regs) { + const RegisterInfo *inv_info = + ctx.GetRegisterInfo(lldb::eRegisterKindLLDB, *inv_regs); + assert( + inv_info && + "Register invalidate list refers to a register that does not exist."); + invalidates.push_back(inv_info->name); + } + } + + // We include the index here so that you can use it with "register read -s". + std::vector<SetInfo> in_sets; + for (uint32_t set_idx = 0; set_idx < ctx.GetRegisterSetCount(); ++set_idx) { + const RegisterSet *set = ctx.GetRegisterSet(set_idx); + assert(set && "Register set should be valid."); + for (uint32_t reg_idx = 0; reg_idx < set->num_registers; ++reg_idx) { + const RegisterInfo *set_reg_info = + ctx.GetRegisterInfoAtIndex(set->registers[reg_idx]); + assert(set_reg_info && "Register info should be valid."); + + if (set_reg_info == &info) { + in_sets.push_back({set->name, set_idx}); + break; + } + } + } + + std::vector<const char *> read_from; + if (info.value_regs) { + for (uint32_t *read_regs = info.value_regs; + *read_regs != LLDB_INVALID_REGNUM; ++read_regs) { + const RegisterInfo *read_info = + ctx.GetRegisterInfo(lldb::eRegisterKindLLDB, *read_regs); + assert(read_info && "Register value registers list refers to a register " + "that does not exist."); + read_from.push_back(read_info->name); + } + } + + DoDumpRegisterInfo(strm, info.name, info.alt_name, info.byte_size, + invalidates, read_from, in_sets, info.flags_type, + terminal_width); +} + +template <typename ElementType> +static void DumpList(Stream &strm, const char *title, + const std::vector<ElementType> &list, + std::function<void(Stream &, ElementType)> emitter) { + if (list.empty()) + return; + + strm.EOL(); + strm << title; + bool first = true; + for (ElementType elem : list) { + if (!first) + strm << ", "; + first = false; + emitter(strm, elem); + } +} + +void lldb_private::DoDumpRegisterInfo( + Stream &strm, const char *name, const char *alt_name, uint32_t byte_size, + const std::vector<const char *> &invalidates, + const std::vector<const char *> &read_from, + const std::vector<SetInfo> &in_sets, const RegisterFlags *flags_type, + uint32_t terminal_width) { + strm << " Name: " << name; + if (alt_name) + strm << " (" << alt_name << ")"; + strm.EOL(); + + // Size in bits may seem obvious for the usual 32 or 64 bit registers. + // When we get to vector registers, then scalable vector registers, it is very + // useful to know without the user doing extra work. + strm.Printf(" Size: %d bytes (%d bits)", byte_size, byte_size * 8); + + std::function<void(Stream &, const char *)> emit_str = + [](Stream &strm, const char *s) { strm << s; }; + DumpList(strm, "Invalidates: ", invalidates, emit_str); + DumpList(strm, " Read from: ", read_from, emit_str); + + std::function<void(Stream &, SetInfo)> emit_set = [](Stream &strm, + SetInfo info) { + strm.Printf("%s (index %d)", info.first, info.second); + }; + DumpList(strm, " In sets: ", in_sets, emit_set); + + if (flags_type) { + strm.Printf("\n\n%s", flags_type->AsTable(terminal_width).c_str()); + + std::string enumerators = flags_type->DumpEnums(terminal_width); + if (enumerators.size()) + strm << "\n\n" << enumerators; + } +} diff --git a/contrib/llvm-project/lldb/source/Core/DumpRegisterValue.cpp b/contrib/llvm-project/lldb/source/Core/DumpRegisterValue.cpp new file mode 100644 index 000000000000..90b31fd0e865 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/DumpRegisterValue.cpp @@ -0,0 +1,174 @@ +//===-- DumpRegisterValue.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/Core/DumpRegisterValue.h" +#include "lldb/Core/DumpDataExtractor.h" +#include "lldb/Core/ValueObject.h" +#include "lldb/Core/ValueObjectConstResult.h" +#include "lldb/DataFormatters/DumpValueObjectOptions.h" +#include "lldb/Target/RegisterFlags.h" +#include "lldb/Utility/DataExtractor.h" +#include "lldb/Utility/Endian.h" +#include "lldb/Utility/RegisterValue.h" +#include "lldb/Utility/StreamString.h" +#include "lldb/lldb-private-types.h" +#include "llvm/ADT/bit.h" + +using namespace lldb; + +template <typename T> +static void dump_type_value(lldb_private::CompilerType &fields_type, T value, + lldb_private::ExecutionContextScope *exe_scope, + const lldb_private::RegisterInfo ®_info, + lldb_private::Stream &strm) { + lldb::ByteOrder target_order = exe_scope->CalculateProcess()->GetByteOrder(); + + // For the bitfield types we generate, it is expected that the fields are + // in what is usually a big endian order. Most significant field first. + // This is also clang's internal ordering and the order we want to print + // them. On a big endian host this all matches up, for a little endian + // host we have to swap the order of the fields before display. + if (target_order == lldb::ByteOrder::eByteOrderLittle) { + value = reg_info.flags_type->ReverseFieldOrder(value); + } + + // Then we need to match the target's endian on a byte level as well. + if (lldb_private::endian::InlHostByteOrder() != target_order) + value = llvm::byteswap(value); + + lldb_private::DataExtractor data_extractor{ + &value, sizeof(T), lldb_private::endian::InlHostByteOrder(), 8}; + + lldb::ValueObjectSP vobj_sp = lldb_private::ValueObjectConstResult::Create( + exe_scope, fields_type, lldb_private::ConstString(), data_extractor); + lldb_private::DumpValueObjectOptions dump_options; + lldb_private::DumpValueObjectOptions::ChildPrintingDecider decider = + [](lldb_private::ConstString varname) { + // Unnamed bit-fields are padding that we don't want to show. + return varname.GetLength(); + }; + dump_options.SetChildPrintingDecider(decider).SetHideRootType(true); + + if (llvm::Error error = vobj_sp->Dump(strm, dump_options)) + strm << "error: " << toString(std::move(error)); +} + +void lldb_private::DumpRegisterValue(const RegisterValue ®_val, Stream &s, + const RegisterInfo ®_info, + bool prefix_with_name, + bool prefix_with_alt_name, Format format, + uint32_t reg_name_right_align_at, + ExecutionContextScope *exe_scope, + bool print_flags, TargetSP target_sp) { + DataExtractor data; + if (!reg_val.GetData(data)) + return; + + bool name_printed = false; + // For simplicity, alignment of the register name printing applies only in + // the most common case where: + // + // prefix_with_name^prefix_with_alt_name is true + // + StreamString format_string; + if (reg_name_right_align_at && (prefix_with_name ^ prefix_with_alt_name)) + format_string.Printf("%%%us", reg_name_right_align_at); + else + format_string.Printf("%%s"); + std::string fmt = std::string(format_string.GetString()); + if (prefix_with_name) { + if (reg_info.name) { + s.Printf(fmt.c_str(), reg_info.name); + name_printed = true; + } else if (reg_info.alt_name) { + s.Printf(fmt.c_str(), reg_info.alt_name); + prefix_with_alt_name = false; + name_printed = true; + } + } + if (prefix_with_alt_name) { + if (name_printed) + s.PutChar('/'); + if (reg_info.alt_name) { + s.Printf(fmt.c_str(), reg_info.alt_name); + name_printed = true; + } else if (!name_printed) { + // No alternate name but we were asked to display a name, so show the + // main name + s.Printf(fmt.c_str(), reg_info.name); + name_printed = true; + } + } + if (name_printed) + s.PutCString(" = "); + + if (format == eFormatDefault) + format = reg_info.format; + + DumpDataExtractor(data, &s, + 0, // Offset in "data" + format, // Format to use when dumping + reg_info.byte_size, // item_byte_size + 1, // item_count + UINT32_MAX, // num_per_line + LLDB_INVALID_ADDRESS, // base_addr + 0, // item_bit_size + 0, // item_bit_offset + exe_scope); + + if (!print_flags || !reg_info.flags_type || !exe_scope || !target_sp || + (reg_info.byte_size != 4 && reg_info.byte_size != 8)) + return; + + CompilerType fields_type = target_sp->GetRegisterType( + reg_info.name, *reg_info.flags_type, reg_info.byte_size); + + // Use a new stream so we can remove a trailing newline later. + StreamString fields_stream; + + if (reg_info.byte_size == 4) { + dump_type_value(fields_type, reg_val.GetAsUInt32(), exe_scope, reg_info, + fields_stream); + } else { + dump_type_value(fields_type, reg_val.GetAsUInt64(), exe_scope, reg_info, + fields_stream); + } + + // Registers are indented like: + // (lldb) register read foo + // foo = 0x12345678 + // So we need to indent to match that. + + // First drop the extra newline that the value printer added. The register + // command will add one itself. + llvm::StringRef fields_str = fields_stream.GetString().drop_back(); + + // End the line that contains " foo = 0x12345678". + s.EOL(); + + // Then split the value lines and indent each one. + bool first = true; + while (fields_str.size()) { + std::pair<llvm::StringRef, llvm::StringRef> split = fields_str.split('\n'); + fields_str = split.second; + // Indent as far as the register name did. + s.Printf(fmt.c_str(), ""); + + // Lines after the first won't have " = " so compensate for that. + if (!first) + s << " "; + first = false; + + s << split.first; + + // On the last line we don't want a newline because the command will add + // one too. + if (fields_str.size()) + s.EOL(); + } +} diff --git a/contrib/llvm-project/lldb/source/Core/DynamicLoader.cpp b/contrib/llvm-project/lldb/source/Core/DynamicLoader.cpp new file mode 100644 index 000000000000..7758a87403b5 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/DynamicLoader.cpp @@ -0,0 +1,372 @@ +//===-- DynamicLoader.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/DynamicLoader.h" + +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleList.h" +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/Progress.h" +#include "lldb/Core/Section.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Target/MemoryRegionInfo.h" +#include "lldb/Target/Platform.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/lldb-private-interfaces.h" + +#include "llvm/ADT/StringRef.h" + +#include <memory> + +#include <cassert> + +using namespace lldb; +using namespace lldb_private; + +DynamicLoader *DynamicLoader::FindPlugin(Process *process, + llvm::StringRef plugin_name) { + DynamicLoaderCreateInstance create_callback = nullptr; + if (!plugin_name.empty()) { + create_callback = + PluginManager::GetDynamicLoaderCreateCallbackForPluginName(plugin_name); + if (create_callback) { + std::unique_ptr<DynamicLoader> instance_up( + create_callback(process, true)); + if (instance_up) + return instance_up.release(); + } + } else { + for (uint32_t idx = 0; + (create_callback = + PluginManager::GetDynamicLoaderCreateCallbackAtIndex(idx)) != + nullptr; + ++idx) { + std::unique_ptr<DynamicLoader> instance_up( + create_callback(process, false)); + if (instance_up) + return instance_up.release(); + } + } + return nullptr; +} + +DynamicLoader::DynamicLoader(Process *process) : m_process(process) {} + +// Accessors to the global setting as to whether to stop at image (shared +// library) loading/unloading. + +bool DynamicLoader::GetStopWhenImagesChange() const { + return m_process->GetStopOnSharedLibraryEvents(); +} + +void DynamicLoader::SetStopWhenImagesChange(bool stop) { + m_process->SetStopOnSharedLibraryEvents(stop); +} + +ModuleSP DynamicLoader::GetTargetExecutable() { + Target &target = m_process->GetTarget(); + ModuleSP executable = target.GetExecutableModule(); + + if (executable) { + if (FileSystem::Instance().Exists(executable->GetFileSpec())) { + ModuleSpec module_spec(executable->GetFileSpec(), + executable->GetArchitecture()); + auto module_sp = std::make_shared<Module>(module_spec); + + // Check if the executable has changed and set it to the target + // executable if they differ. + if (module_sp && module_sp->GetUUID().IsValid() && + executable->GetUUID().IsValid()) { + if (module_sp->GetUUID() != executable->GetUUID()) + executable.reset(); + } else if (executable->FileHasChanged()) { + executable.reset(); + } + + if (!executable) { + executable = target.GetOrCreateModule(module_spec, true /* notify */); + if (executable.get() != target.GetExecutableModulePointer()) { + // Don't load dependent images since we are in dyld where we will + // know and find out about all images that are loaded + target.SetExecutableModule(executable, eLoadDependentsNo); + } + } + } + } + return executable; +} + +void DynamicLoader::UpdateLoadedSections(ModuleSP module, addr_t link_map_addr, + addr_t base_addr, + bool base_addr_is_offset) { + UpdateLoadedSectionsCommon(module, base_addr, base_addr_is_offset); +} + +void DynamicLoader::UpdateLoadedSectionsCommon(ModuleSP module, + addr_t base_addr, + bool base_addr_is_offset) { + bool changed; + module->SetLoadAddress(m_process->GetTarget(), base_addr, base_addr_is_offset, + changed); +} + +void DynamicLoader::UnloadSections(const ModuleSP module) { + UnloadSectionsCommon(module); +} + +void DynamicLoader::UnloadSectionsCommon(const ModuleSP module) { + Target &target = m_process->GetTarget(); + const SectionList *sections = GetSectionListFromModule(module); + + assert(sections && "SectionList missing from unloaded module."); + + const size_t num_sections = sections->GetSize(); + for (size_t i = 0; i < num_sections; ++i) { + SectionSP section_sp(sections->GetSectionAtIndex(i)); + target.SetSectionUnloaded(section_sp); + } +} + +const SectionList * +DynamicLoader::GetSectionListFromModule(const ModuleSP module) const { + SectionList *sections = nullptr; + if (module) { + ObjectFile *obj_file = module->GetObjectFile(); + if (obj_file != nullptr) { + sections = obj_file->GetSectionList(); + } + } + return sections; +} + +ModuleSP DynamicLoader::FindModuleViaTarget(const FileSpec &file) { + Target &target = m_process->GetTarget(); + ModuleSpec module_spec(file, target.GetArchitecture()); + + if (ModuleSP module_sp = target.GetImages().FindFirstModule(module_spec)) + return module_sp; + + if (ModuleSP module_sp = target.GetOrCreateModule(module_spec, false)) + return module_sp; + + return nullptr; +} + +ModuleSP DynamicLoader::LoadModuleAtAddress(const FileSpec &file, + addr_t link_map_addr, + addr_t base_addr, + bool base_addr_is_offset) { + if (ModuleSP module_sp = FindModuleViaTarget(file)) { + UpdateLoadedSections(module_sp, link_map_addr, base_addr, + base_addr_is_offset); + return module_sp; + } + + return nullptr; +} + +static ModuleSP ReadUnnamedMemoryModule(Process *process, addr_t addr, + llvm::StringRef name) { + char namebuf[80]; + if (name.empty()) { + snprintf(namebuf, sizeof(namebuf), "memory-image-0x%" PRIx64, addr); + name = namebuf; + } + return process->ReadModuleFromMemory(FileSpec(name), addr); +} + +ModuleSP DynamicLoader::LoadBinaryWithUUIDAndAddress( + Process *process, llvm::StringRef name, UUID uuid, addr_t value, + bool value_is_offset, bool force_symbol_search, bool notify, + bool set_address_in_target, bool allow_memory_image_last_resort) { + ModuleSP memory_module_sp; + ModuleSP module_sp; + PlatformSP platform_sp = process->GetTarget().GetPlatform(); + Target &target = process->GetTarget(); + Status error; + + StreamString prog_str; + if (!name.empty()) { + prog_str << name.str() << " "; + } + if (uuid.IsValid()) + prog_str << uuid.GetAsString(); + if (value_is_offset == 0 && value != LLDB_INVALID_ADDRESS) { + prog_str << "at 0x"; + prog_str.PutHex64(value); + } + + if (!uuid.IsValid() && !value_is_offset) { + memory_module_sp = ReadUnnamedMemoryModule(process, value, name); + + if (memory_module_sp) { + uuid = memory_module_sp->GetUUID(); + if (uuid.IsValid()) { + prog_str << " "; + prog_str << uuid.GetAsString(); + } + } + } + ModuleSpec module_spec; + module_spec.GetUUID() = uuid; + FileSpec name_filespec(name); + + if (uuid.IsValid()) { + Progress progress("Locating binary", prog_str.GetString().str()); + + // Has lldb already seen a module with this UUID? + // Or have external lookup enabled in DebugSymbols on macOS. + if (!module_sp) + error = ModuleList::GetSharedModule(module_spec, module_sp, nullptr, + nullptr, nullptr); + + // Can lldb's symbol/executable location schemes + // find an executable and symbol file. + if (!module_sp) { + FileSpecList search_paths = Target::GetDefaultDebugFileSearchPaths(); + module_spec.GetSymbolFileSpec() = + PluginManager::LocateExecutableSymbolFile(module_spec, search_paths); + ModuleSpec objfile_module_spec = + PluginManager::LocateExecutableObjectFile(module_spec); + module_spec.GetFileSpec() = objfile_module_spec.GetFileSpec(); + if (FileSystem::Instance().Exists(module_spec.GetFileSpec()) && + FileSystem::Instance().Exists(module_spec.GetSymbolFileSpec())) { + module_sp = std::make_shared<Module>(module_spec); + } + } + + // If we haven't found a binary, or we don't have a SymbolFile, see + // if there is an external search tool that can find it. + if (!module_sp || !module_sp->GetSymbolFileFileSpec()) { + PluginManager::DownloadObjectAndSymbolFile(module_spec, error, + force_symbol_search); + if (FileSystem::Instance().Exists(module_spec.GetFileSpec())) { + module_sp = std::make_shared<Module>(module_spec); + } else if (force_symbol_search && error.AsCString("") && + error.AsCString("")[0] != '\0') { + target.GetDebugger().GetErrorStream() << error.AsCString(); + } + } + + // If we only found the executable, create a Module based on that. + if (!module_sp && FileSystem::Instance().Exists(module_spec.GetFileSpec())) + module_sp = std::make_shared<Module>(module_spec); + } + + // If we couldn't find the binary anywhere else, as a last resort, + // read it out of memory. + if (allow_memory_image_last_resort && !module_sp.get() && + value != LLDB_INVALID_ADDRESS && !value_is_offset) { + if (!memory_module_sp) + memory_module_sp = ReadUnnamedMemoryModule(process, value, name); + if (memory_module_sp) + module_sp = memory_module_sp; + } + + Log *log = GetLog(LLDBLog::DynamicLoader); + if (module_sp.get()) { + // Ensure the Target has an architecture set in case + // we need it while processing this binary/eh_frame/debug info. + if (!target.GetArchitecture().IsValid()) + target.SetArchitecture(module_sp->GetArchitecture()); + target.GetImages().AppendIfNeeded(module_sp, false); + + bool changed = false; + if (set_address_in_target) { + if (module_sp->GetObjectFile()) { + if (value != LLDB_INVALID_ADDRESS) { + LLDB_LOGF(log, + "DynamicLoader::LoadBinaryWithUUIDAndAddress Loading " + "binary %s UUID %s at %s 0x%" PRIx64, + name.str().c_str(), uuid.GetAsString().c_str(), + value_is_offset ? "offset" : "address", value); + module_sp->SetLoadAddress(target, value, value_is_offset, changed); + } else { + // No address/offset/slide, load the binary at file address, + // offset 0. + LLDB_LOGF(log, + "DynamicLoader::LoadBinaryWithUUIDAndAddress Loading " + "binary %s UUID %s at file address", + name.str().c_str(), uuid.GetAsString().c_str()); + module_sp->SetLoadAddress(target, 0, true /* value_is_slide */, + changed); + } + } else { + // In-memory image, load at its true address, offset 0. + LLDB_LOGF(log, + "DynamicLoader::LoadBinaryWithUUIDAndAddress Loading binary " + "%s UUID %s from memory at address 0x%" PRIx64, + name.str().c_str(), uuid.GetAsString().c_str(), value); + module_sp->SetLoadAddress(target, 0, true /* value_is_slide */, + changed); + } + } + + if (notify) { + ModuleList added_module; + added_module.Append(module_sp, false); + target.ModulesDidLoad(added_module); + } + } else { + if (force_symbol_search) { + Stream &s = target.GetDebugger().GetErrorStream(); + s.Printf("Unable to find file"); + if (!name.empty()) + s.Printf(" %s", name.str().c_str()); + if (uuid.IsValid()) + s.Printf(" with UUID %s", uuid.GetAsString().c_str()); + if (value != LLDB_INVALID_ADDRESS) { + if (value_is_offset) + s.Printf(" with slide 0x%" PRIx64, value); + else + s.Printf(" at address 0x%" PRIx64, value); + } + s.Printf("\n"); + } + LLDB_LOGF(log, + "Unable to find binary %s with UUID %s and load it at " + "%s 0x%" PRIx64, + name.str().c_str(), uuid.GetAsString().c_str(), + value_is_offset ? "offset" : "address", value); + } + + return module_sp; +} + +int64_t DynamicLoader::ReadUnsignedIntWithSizeInBytes(addr_t addr, + int size_in_bytes) { + Status error; + uint64_t value = + m_process->ReadUnsignedIntegerFromMemory(addr, size_in_bytes, 0, error); + if (error.Fail()) + return -1; + else + return (int64_t)value; +} + +addr_t DynamicLoader::ReadPointer(addr_t addr) { + Status error; + addr_t value = m_process->ReadPointerFromMemory(addr, error); + if (error.Fail()) + return LLDB_INVALID_ADDRESS; + else + return value; +} + +void DynamicLoader::LoadOperatingSystemPlugin(bool flush) +{ + if (m_process) + m_process->LoadOperatingSystemPlugin(flush); +} + diff --git a/contrib/llvm-project/lldb/source/Core/EmulateInstruction.cpp b/contrib/llvm-project/lldb/source/Core/EmulateInstruction.cpp new file mode 100644 index 000000000000..d240b4d3b331 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/EmulateInstruction.cpp @@ -0,0 +1,594 @@ +//===-- EmulateInstruction.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/Core/EmulateInstruction.h" + +#include "lldb/Core/Address.h" +#include "lldb/Core/DumpRegisterValue.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Host/StreamFile.h" +#include "lldb/Symbol/UnwindPlan.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/DataExtractor.h" +#include "lldb/Utility/RegisterValue.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/Stream.h" +#include "lldb/Utility/StreamString.h" +#include "lldb/lldb-forward.h" +#include "lldb/lldb-private-interfaces.h" + +#include "llvm/ADT/StringRef.h" + +#include <cstring> +#include <memory> +#include <optional> + +#include <cinttypes> +#include <cstdio> + +namespace lldb_private { +class Target; +} + +using namespace lldb; +using namespace lldb_private; + +EmulateInstruction * +EmulateInstruction::FindPlugin(const ArchSpec &arch, + InstructionType supported_inst_type, + const char *plugin_name) { + EmulateInstructionCreateInstance create_callback = nullptr; + if (plugin_name) { + create_callback = + PluginManager::GetEmulateInstructionCreateCallbackForPluginName( + plugin_name); + if (create_callback) { + EmulateInstruction *emulate_insn_ptr = + create_callback(arch, supported_inst_type); + if (emulate_insn_ptr) + return emulate_insn_ptr; + } + } else { + for (uint32_t idx = 0; + (create_callback = + PluginManager::GetEmulateInstructionCreateCallbackAtIndex(idx)) != + nullptr; + ++idx) { + EmulateInstruction *emulate_insn_ptr = + create_callback(arch, supported_inst_type); + if (emulate_insn_ptr) + return emulate_insn_ptr; + } + } + return nullptr; +} + +EmulateInstruction::EmulateInstruction(const ArchSpec &arch) : m_arch(arch) {} + +std::optional<RegisterValue> +EmulateInstruction::ReadRegister(const RegisterInfo ®_info) { + if (m_read_reg_callback == nullptr) + return {}; + + RegisterValue reg_value; + bool success = m_read_reg_callback(this, m_baton, ®_info, reg_value); + if (success) + return reg_value; + return {}; +} + +bool EmulateInstruction::ReadRegister(lldb::RegisterKind reg_kind, + uint32_t reg_num, + RegisterValue ®_value) { + std::optional<RegisterInfo> reg_info = GetRegisterInfo(reg_kind, reg_num); + if (!reg_info) + return false; + + std::optional<RegisterValue> value = ReadRegister(*reg_info); + if (value) + reg_value = *value; + return value.has_value(); +} + +uint64_t EmulateInstruction::ReadRegisterUnsigned(lldb::RegisterKind reg_kind, + uint32_t reg_num, + uint64_t fail_value, + bool *success_ptr) { + RegisterValue reg_value; + if (ReadRegister(reg_kind, reg_num, reg_value)) + return reg_value.GetAsUInt64(fail_value, success_ptr); + if (success_ptr) + *success_ptr = false; + return fail_value; +} + +uint64_t EmulateInstruction::ReadRegisterUnsigned(const RegisterInfo ®_info, + uint64_t fail_value, + bool *success_ptr) { + std::optional<RegisterValue> reg_value = ReadRegister(reg_info); + if (!reg_value) { + if (success_ptr) + *success_ptr = false; + return fail_value; + } + + return reg_value->GetAsUInt64(fail_value, success_ptr); +} + +bool EmulateInstruction::WriteRegister(const Context &context, + const RegisterInfo ®_info, + const RegisterValue ®_value) { + if (m_write_reg_callback != nullptr) + return m_write_reg_callback(this, m_baton, context, ®_info, reg_value); + return false; +} + +bool EmulateInstruction::WriteRegister(const Context &context, + lldb::RegisterKind reg_kind, + uint32_t reg_num, + const RegisterValue ®_value) { + std::optional<RegisterInfo> reg_info = GetRegisterInfo(reg_kind, reg_num); + if (reg_info) + return WriteRegister(context, *reg_info, reg_value); + return false; +} + +bool EmulateInstruction::WriteRegisterUnsigned(const Context &context, + lldb::RegisterKind reg_kind, + uint32_t reg_num, + uint64_t uint_value) { + std::optional<RegisterInfo> reg_info = GetRegisterInfo(reg_kind, reg_num); + if (reg_info) { + RegisterValue reg_value; + if (reg_value.SetUInt(uint_value, reg_info->byte_size)) + return WriteRegister(context, *reg_info, reg_value); + } + return false; +} + +bool EmulateInstruction::WriteRegisterUnsigned(const Context &context, + const RegisterInfo ®_info, + uint64_t uint_value) { + RegisterValue reg_value; + if (reg_value.SetUInt(uint_value, reg_info.byte_size)) + return WriteRegister(context, reg_info, reg_value); + return false; +} + +size_t EmulateInstruction::ReadMemory(const Context &context, lldb::addr_t addr, + void *dst, size_t dst_len) { + if (m_read_mem_callback != nullptr) + return m_read_mem_callback(this, m_baton, context, addr, dst, dst_len) == + dst_len; + return false; +} + +uint64_t EmulateInstruction::ReadMemoryUnsigned(const Context &context, + lldb::addr_t addr, + size_t byte_size, + uint64_t fail_value, + bool *success_ptr) { + uint64_t uval64 = 0; + bool success = false; + if (byte_size <= 8) { + uint8_t buf[sizeof(uint64_t)]; + size_t bytes_read = + m_read_mem_callback(this, m_baton, context, addr, buf, byte_size); + if (bytes_read == byte_size) { + lldb::offset_t offset = 0; + DataExtractor data(buf, byte_size, GetByteOrder(), GetAddressByteSize()); + uval64 = data.GetMaxU64(&offset, byte_size); + success = true; + } + } + + if (success_ptr) + *success_ptr = success; + + if (!success) + uval64 = fail_value; + return uval64; +} + +bool EmulateInstruction::WriteMemoryUnsigned(const Context &context, + lldb::addr_t addr, uint64_t uval, + size_t uval_byte_size) { + StreamString strm(Stream::eBinary, GetAddressByteSize(), GetByteOrder()); + strm.PutMaxHex64(uval, uval_byte_size); + + size_t bytes_written = m_write_mem_callback( + this, m_baton, context, addr, strm.GetString().data(), uval_byte_size); + return (bytes_written == uval_byte_size); +} + +bool EmulateInstruction::WriteMemory(const Context &context, lldb::addr_t addr, + const void *src, size_t src_len) { + if (m_write_mem_callback != nullptr) + return m_write_mem_callback(this, m_baton, context, addr, src, src_len) == + src_len; + return false; +} + +void EmulateInstruction::SetBaton(void *baton) { m_baton = baton; } + +void EmulateInstruction::SetCallbacks( + ReadMemoryCallback read_mem_callback, + WriteMemoryCallback write_mem_callback, + ReadRegisterCallback read_reg_callback, + WriteRegisterCallback write_reg_callback) { + m_read_mem_callback = read_mem_callback; + m_write_mem_callback = write_mem_callback; + m_read_reg_callback = read_reg_callback; + m_write_reg_callback = write_reg_callback; +} + +void EmulateInstruction::SetReadMemCallback( + ReadMemoryCallback read_mem_callback) { + m_read_mem_callback = read_mem_callback; +} + +void EmulateInstruction::SetWriteMemCallback( + WriteMemoryCallback write_mem_callback) { + m_write_mem_callback = write_mem_callback; +} + +void EmulateInstruction::SetReadRegCallback( + ReadRegisterCallback read_reg_callback) { + m_read_reg_callback = read_reg_callback; +} + +void EmulateInstruction::SetWriteRegCallback( + WriteRegisterCallback write_reg_callback) { + m_write_reg_callback = write_reg_callback; +} + +// +// Read & Write Memory and Registers callback functions. +// + +size_t EmulateInstruction::ReadMemoryFrame(EmulateInstruction *instruction, + void *baton, const Context &context, + lldb::addr_t addr, void *dst, + size_t dst_len) { + if (baton == nullptr || dst == nullptr || dst_len == 0) + return 0; + + StackFrame *frame = (StackFrame *)baton; + + ProcessSP process_sp(frame->CalculateProcess()); + if (process_sp) { + Status error; + return process_sp->ReadMemory(addr, dst, dst_len, error); + } + return 0; +} + +size_t EmulateInstruction::WriteMemoryFrame(EmulateInstruction *instruction, + void *baton, const Context &context, + lldb::addr_t addr, const void *src, + size_t src_len) { + if (baton == nullptr || src == nullptr || src_len == 0) + return 0; + + StackFrame *frame = (StackFrame *)baton; + + ProcessSP process_sp(frame->CalculateProcess()); + if (process_sp) { + Status error; + return process_sp->WriteMemory(addr, src, src_len, error); + } + + return 0; +} + +bool EmulateInstruction::ReadRegisterFrame(EmulateInstruction *instruction, + void *baton, + const RegisterInfo *reg_info, + RegisterValue ®_value) { + if (baton == nullptr) + return false; + + StackFrame *frame = (StackFrame *)baton; + return frame->GetRegisterContext()->ReadRegister(reg_info, reg_value); +} + +bool EmulateInstruction::WriteRegisterFrame(EmulateInstruction *instruction, + void *baton, const Context &context, + const RegisterInfo *reg_info, + const RegisterValue ®_value) { + if (baton == nullptr) + return false; + + StackFrame *frame = (StackFrame *)baton; + return frame->GetRegisterContext()->WriteRegister(reg_info, reg_value); +} + +size_t EmulateInstruction::ReadMemoryDefault(EmulateInstruction *instruction, + void *baton, + const Context &context, + lldb::addr_t addr, void *dst, + size_t length) { + StreamFile strm(stdout, false); + strm.Printf(" Read from Memory (address = 0x%" PRIx64 ", length = %" PRIu64 + ", context = ", + addr, (uint64_t)length); + context.Dump(strm, instruction); + strm.EOL(); + *((uint64_t *)dst) = 0xdeadbeef; + return length; +} + +size_t EmulateInstruction::WriteMemoryDefault(EmulateInstruction *instruction, + void *baton, + const Context &context, + lldb::addr_t addr, + const void *dst, size_t length) { + StreamFile strm(stdout, false); + strm.Printf(" Write to Memory (address = 0x%" PRIx64 ", length = %" PRIu64 + ", context = ", + addr, (uint64_t)length); + context.Dump(strm, instruction); + strm.EOL(); + return length; +} + +bool EmulateInstruction::ReadRegisterDefault(EmulateInstruction *instruction, + void *baton, + const RegisterInfo *reg_info, + RegisterValue ®_value) { + StreamFile strm(stdout, false); + strm.Printf(" Read Register (%s)\n", reg_info->name); + lldb::RegisterKind reg_kind; + uint32_t reg_num; + if (GetBestRegisterKindAndNumber(reg_info, reg_kind, reg_num)) + reg_value.SetUInt64((uint64_t)reg_kind << 24 | reg_num); + else + reg_value.SetUInt64(0); + + return true; +} + +bool EmulateInstruction::WriteRegisterDefault(EmulateInstruction *instruction, + void *baton, + const Context &context, + const RegisterInfo *reg_info, + const RegisterValue ®_value) { + StreamFile strm(stdout, false); + strm.Printf(" Write to Register (name = %s, value = ", reg_info->name); + DumpRegisterValue(reg_value, strm, *reg_info, false, false, eFormatDefault); + strm.PutCString(", context = "); + context.Dump(strm, instruction); + strm.EOL(); + return true; +} + +void EmulateInstruction::Context::Dump(Stream &strm, + EmulateInstruction *instruction) const { + switch (type) { + case eContextReadOpcode: + strm.PutCString("reading opcode"); + break; + + case eContextImmediate: + strm.PutCString("immediate"); + break; + + case eContextPushRegisterOnStack: + strm.PutCString("push register"); + break; + + case eContextPopRegisterOffStack: + strm.PutCString("pop register"); + break; + + case eContextAdjustStackPointer: + strm.PutCString("adjust sp"); + break; + + case eContextSetFramePointer: + strm.PutCString("set frame pointer"); + break; + + case eContextAdjustBaseRegister: + strm.PutCString("adjusting (writing value back to) a base register"); + break; + + case eContextRegisterPlusOffset: + strm.PutCString("register + offset"); + break; + + case eContextRegisterStore: + strm.PutCString("store register"); + break; + + case eContextRegisterLoad: + strm.PutCString("load register"); + break; + + case eContextRelativeBranchImmediate: + strm.PutCString("relative branch immediate"); + break; + + case eContextAbsoluteBranchRegister: + strm.PutCString("absolute branch register"); + break; + + case eContextSupervisorCall: + strm.PutCString("supervisor call"); + break; + + case eContextTableBranchReadMemory: + strm.PutCString("table branch read memory"); + break; + + case eContextWriteRegisterRandomBits: + strm.PutCString("write random bits to a register"); + break; + + case eContextWriteMemoryRandomBits: + strm.PutCString("write random bits to a memory address"); + break; + + case eContextArithmetic: + strm.PutCString("arithmetic"); + break; + + case eContextReturnFromException: + strm.PutCString("return from exception"); + break; + + default: + strm.PutCString("unrecognized context."); + break; + } + + switch (GetInfoType()) { + case eInfoTypeRegisterPlusOffset: + strm.Printf(" (reg_plus_offset = %s%+" PRId64 ")", + info.RegisterPlusOffset.reg.name, + info.RegisterPlusOffset.signed_offset); + break; + + case eInfoTypeRegisterPlusIndirectOffset: + strm.Printf(" (reg_plus_reg = %s + %s)", + info.RegisterPlusIndirectOffset.base_reg.name, + info.RegisterPlusIndirectOffset.offset_reg.name); + break; + + case eInfoTypeRegisterToRegisterPlusOffset: + strm.Printf(" (base_and_imm_offset = %s%+" PRId64 ", data_reg = %s)", + info.RegisterToRegisterPlusOffset.base_reg.name, + info.RegisterToRegisterPlusOffset.offset, + info.RegisterToRegisterPlusOffset.data_reg.name); + break; + + case eInfoTypeRegisterToRegisterPlusIndirectOffset: + strm.Printf(" (base_and_reg_offset = %s + %s, data_reg = %s)", + info.RegisterToRegisterPlusIndirectOffset.base_reg.name, + info.RegisterToRegisterPlusIndirectOffset.offset_reg.name, + info.RegisterToRegisterPlusIndirectOffset.data_reg.name); + break; + + case eInfoTypeRegisterRegisterOperands: + strm.Printf(" (register to register binary op: %s and %s)", + info.RegisterRegisterOperands.operand1.name, + info.RegisterRegisterOperands.operand2.name); + break; + + case eInfoTypeOffset: + strm.Printf(" (signed_offset = %+" PRId64 ")", info.signed_offset); + break; + + case eInfoTypeRegister: + strm.Printf(" (reg = %s)", info.reg.name); + break; + + case eInfoTypeImmediate: + strm.Printf(" (unsigned_immediate = %" PRIu64 " (0x%16.16" PRIx64 "))", + info.unsigned_immediate, info.unsigned_immediate); + break; + + case eInfoTypeImmediateSigned: + strm.Printf(" (signed_immediate = %+" PRId64 " (0x%16.16" PRIx64 "))", + info.signed_immediate, info.signed_immediate); + break; + + case eInfoTypeAddress: + strm.Printf(" (address = 0x%" PRIx64 ")", info.address); + break; + + case eInfoTypeISAAndImmediate: + strm.Printf(" (isa = %u, unsigned_immediate = %u (0x%8.8x))", + info.ISAAndImmediate.isa, info.ISAAndImmediate.unsigned_data32, + info.ISAAndImmediate.unsigned_data32); + break; + + case eInfoTypeISAAndImmediateSigned: + strm.Printf(" (isa = %u, signed_immediate = %i (0x%8.8x))", + info.ISAAndImmediateSigned.isa, + info.ISAAndImmediateSigned.signed_data32, + info.ISAAndImmediateSigned.signed_data32); + break; + + case eInfoTypeISA: + strm.Printf(" (isa = %u)", info.isa); + break; + + case eInfoTypeNoArgs: + break; + } +} + +bool EmulateInstruction::SetInstruction(const Opcode &opcode, + const Address &inst_addr, + Target *target) { + m_opcode = opcode; + m_addr = LLDB_INVALID_ADDRESS; + if (inst_addr.IsValid()) { + if (target != nullptr) + m_addr = inst_addr.GetLoadAddress(target); + if (m_addr == LLDB_INVALID_ADDRESS) + m_addr = inst_addr.GetFileAddress(); + } + return true; +} + +bool EmulateInstruction::GetBestRegisterKindAndNumber( + const RegisterInfo *reg_info, lldb::RegisterKind ®_kind, + uint32_t ®_num) { + // Generic and DWARF should be the two most popular register kinds when + // emulating instructions since they are the most platform agnostic... + reg_num = reg_info->kinds[eRegisterKindGeneric]; + if (reg_num != LLDB_INVALID_REGNUM) { + reg_kind = eRegisterKindGeneric; + return true; + } + + reg_num = reg_info->kinds[eRegisterKindDWARF]; + if (reg_num != LLDB_INVALID_REGNUM) { + reg_kind = eRegisterKindDWARF; + return true; + } + + reg_num = reg_info->kinds[eRegisterKindLLDB]; + if (reg_num != LLDB_INVALID_REGNUM) { + reg_kind = eRegisterKindLLDB; + return true; + } + + reg_num = reg_info->kinds[eRegisterKindEHFrame]; + if (reg_num != LLDB_INVALID_REGNUM) { + reg_kind = eRegisterKindEHFrame; + return true; + } + + reg_num = reg_info->kinds[eRegisterKindProcessPlugin]; + if (reg_num != LLDB_INVALID_REGNUM) { + reg_kind = eRegisterKindProcessPlugin; + return true; + } + return false; +} + +uint32_t +EmulateInstruction::GetInternalRegisterNumber(RegisterContext *reg_ctx, + const RegisterInfo ®_info) { + lldb::RegisterKind reg_kind; + uint32_t reg_num; + if (reg_ctx && GetBestRegisterKindAndNumber(®_info, reg_kind, reg_num)) + return reg_ctx->ConvertRegisterKindToRegisterNumber(reg_kind, reg_num); + return LLDB_INVALID_REGNUM; +} + +bool EmulateInstruction::CreateFunctionEntryUnwind(UnwindPlan &unwind_plan) { + unwind_plan.Clear(); + return false; +} diff --git a/contrib/llvm-project/lldb/source/Core/FileLineResolver.cpp b/contrib/llvm-project/lldb/source/Core/FileLineResolver.cpp new file mode 100644 index 000000000000..7c3cf70572ee --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/FileLineResolver.cpp @@ -0,0 +1,89 @@ +//===-- FileLineResolver.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/Core/FileLineResolver.h" + +#include "lldb/Symbol/CompileUnit.h" +#include "lldb/Symbol/LineTable.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/FileSpecList.h" +#include "lldb/Utility/Stream.h" + +#include <string> + +namespace lldb_private { +class Address; +} + +using namespace lldb; +using namespace lldb_private; + +// FileLineResolver: +FileLineResolver::FileLineResolver(const FileSpec &file_spec, uint32_t line_no, + bool check_inlines) + : Searcher(), m_file_spec(file_spec), m_line_number(line_no), + m_inlines(check_inlines) {} + +FileLineResolver::~FileLineResolver() = default; + +Searcher::CallbackReturn +FileLineResolver::SearchCallback(SearchFilter &filter, SymbolContext &context, + Address *addr) { + CompileUnit *cu = context.comp_unit; + + if (m_inlines || m_file_spec.Compare(cu->GetPrimaryFile(), m_file_spec, + (bool)m_file_spec.GetDirectory())) { + uint32_t start_file_idx = 0; + uint32_t file_idx = + cu->GetSupportFiles().FindFileIndex(start_file_idx, m_file_spec, false); + if (file_idx != UINT32_MAX) { + LineTable *line_table = cu->GetLineTable(); + if (line_table) { + if (m_line_number == 0) { + // Match all lines in a file... + const bool append = true; + while (file_idx != UINT32_MAX) { + line_table->FindLineEntriesForFileIndex(file_idx, append, + m_sc_list); + // Get the next file index in case we have multiple file entries + // for the same file + file_idx = cu->GetSupportFiles().FindFileIndex(file_idx + 1, + m_file_spec, false); + } + } else { + // Match a specific line in a file... + } + } + } + } + return Searcher::eCallbackReturnContinue; +} + +lldb::SearchDepth FileLineResolver::GetDepth() { + return lldb::eSearchDepthCompUnit; +} + +void FileLineResolver::GetDescription(Stream *s) { + s->Printf("File and line resolver for file: \"%s\" line: %u", + m_file_spec.GetPath().c_str(), m_line_number); +} + +void FileLineResolver::Clear() { + m_file_spec.Clear(); + m_line_number = UINT32_MAX; + m_sc_list.Clear(); + m_inlines = true; +} + +void FileLineResolver::Reset(const FileSpec &file_spec, uint32_t line, + bool check_inlines) { + m_file_spec = file_spec; + m_line_number = line; + m_sc_list.Clear(); + m_inlines = check_inlines; +} diff --git a/contrib/llvm-project/lldb/source/Core/FormatEntity.cpp b/contrib/llvm-project/lldb/source/Core/FormatEntity.cpp new file mode 100644 index 000000000000..4e1f37099148 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/FormatEntity.cpp @@ -0,0 +1,2515 @@ +//===-- FormatEntity.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/Core/FormatEntity.h" + +#include "lldb/Core/Address.h" +#include "lldb/Core/AddressRange.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/DumpRegisterValue.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ValueObject.h" +#include "lldb/Core/ValueObjectVariable.h" +#include "lldb/DataFormatters/DataVisualization.h" +#include "lldb/DataFormatters/FormatClasses.h" +#include "lldb/DataFormatters/FormatManager.h" +#include "lldb/DataFormatters/TypeSummary.h" +#include "lldb/Expression/ExpressionVariable.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Symbol/Block.h" +#include "lldb/Symbol/CompileUnit.h" +#include "lldb/Symbol/CompilerType.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/LineEntry.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Symbol/VariableList.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/ExecutionContextScope.h" +#include "lldb/Target/Language.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Target/StopInfo.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/AnsiTerminal.h" +#include "lldb/Utility/ArchSpec.h" +#include "lldb/Utility/CompletionRequest.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/RegisterValue.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/Stream.h" +#include "lldb/Utility/StreamString.h" +#include "lldb/Utility/StringList.h" +#include "lldb/Utility/StructuredData.h" +#include "lldb/lldb-defines.h" +#include "lldb/lldb-forward.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/Regex.h" +#include "llvm/TargetParser/Triple.h" + +#include <cctype> +#include <cinttypes> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <memory> +#include <type_traits> +#include <utility> + +namespace lldb_private { +class ScriptInterpreter; +} +namespace lldb_private { +struct RegisterInfo; +} + +using namespace lldb; +using namespace lldb_private; + +using Definition = lldb_private::FormatEntity::Entry::Definition; +using Entry = FormatEntity::Entry; +using EntryType = FormatEntity::Entry::Type; + +enum FileKind { FileError = 0, Basename, Dirname, Fullpath }; + +constexpr Definition g_string_entry[] = { + Definition("*", EntryType::ParentString)}; + +constexpr Definition g_addr_entries[] = { + Definition("load", EntryType::AddressLoad), + Definition("file", EntryType::AddressFile)}; + +constexpr Definition g_file_child_entries[] = { + Definition("basename", EntryType::ParentNumber, FileKind::Basename), + Definition("dirname", EntryType::ParentNumber, FileKind::Dirname), + Definition("fullpath", EntryType::ParentNumber, FileKind::Fullpath)}; + +constexpr Definition g_frame_child_entries[] = { + Definition("index", EntryType::FrameIndex), + Definition("pc", EntryType::FrameRegisterPC), + Definition("fp", EntryType::FrameRegisterFP), + Definition("sp", EntryType::FrameRegisterSP), + Definition("flags", EntryType::FrameRegisterFlags), + Definition("no-debug", EntryType::FrameNoDebug), + Entry::DefinitionWithChildren("reg", EntryType::FrameRegisterByName, + g_string_entry), + Definition("is-artificial", EntryType::FrameIsArtificial), +}; + +constexpr Definition g_function_child_entries[] = { + Definition("id", EntryType::FunctionID), + Definition("name", EntryType::FunctionName), + Definition("name-without-args", EntryType::FunctionNameNoArgs), + Definition("name-with-args", EntryType::FunctionNameWithArgs), + Definition("mangled-name", EntryType::FunctionMangledName), + Definition("addr-offset", EntryType::FunctionAddrOffset), + Definition("concrete-only-addr-offset-no-padding", + EntryType::FunctionAddrOffsetConcrete), + Definition("line-offset", EntryType::FunctionLineOffset), + Definition("pc-offset", EntryType::FunctionPCOffset), + Definition("initial-function", EntryType::FunctionInitial), + Definition("changed", EntryType::FunctionChanged), + Definition("is-optimized", EntryType::FunctionIsOptimized)}; + +constexpr Definition g_line_child_entries[] = { + Entry::DefinitionWithChildren("file", EntryType::LineEntryFile, + g_file_child_entries), + Definition("number", EntryType::LineEntryLineNumber), + Definition("column", EntryType::LineEntryColumn), + Definition("start-addr", EntryType::LineEntryStartAddress), + Definition("end-addr", EntryType::LineEntryEndAddress), +}; + +constexpr Definition g_module_child_entries[] = {Entry::DefinitionWithChildren( + "file", EntryType::ModuleFile, g_file_child_entries)}; + +constexpr Definition g_process_child_entries[] = { + Definition("id", EntryType::ProcessID), + Definition("name", EntryType::ProcessFile, FileKind::Basename), + Entry::DefinitionWithChildren("file", EntryType::ProcessFile, + g_file_child_entries)}; + +constexpr Definition g_svar_child_entries[] = { + Definition("*", EntryType::ParentString)}; + +constexpr Definition g_var_child_entries[] = { + Definition("*", EntryType::ParentString)}; + +constexpr Definition g_thread_child_entries[] = { + Definition("id", EntryType::ThreadID), + Definition("protocol_id", EntryType::ThreadProtocolID), + Definition("index", EntryType::ThreadIndexID), + Entry::DefinitionWithChildren("info", EntryType::ThreadInfo, + g_string_entry), + Definition("queue", EntryType::ThreadQueue), + Definition("name", EntryType::ThreadName), + Definition("stop-reason", EntryType::ThreadStopReason), + Definition("stop-reason-raw", EntryType::ThreadStopReasonRaw), + Definition("return-value", EntryType::ThreadReturnValue), + Definition("completed-expression", EntryType::ThreadCompletedExpression)}; + +constexpr Definition g_target_child_entries[] = { + Definition("arch", EntryType::TargetArch)}; + +#define _TO_STR2(_val) #_val +#define _TO_STR(_val) _TO_STR2(_val) + +constexpr Definition g_ansi_fg_entries[] = { + Definition("black", + ANSI_ESC_START _TO_STR(ANSI_FG_COLOR_BLACK) ANSI_ESC_END), + Definition("red", ANSI_ESC_START _TO_STR(ANSI_FG_COLOR_RED) ANSI_ESC_END), + Definition("green", + ANSI_ESC_START _TO_STR(ANSI_FG_COLOR_GREEN) ANSI_ESC_END), + Definition("yellow", + ANSI_ESC_START _TO_STR(ANSI_FG_COLOR_YELLOW) ANSI_ESC_END), + Definition("blue", ANSI_ESC_START _TO_STR(ANSI_FG_COLOR_BLUE) ANSI_ESC_END), + Definition("purple", + ANSI_ESC_START _TO_STR(ANSI_FG_COLOR_PURPLE) ANSI_ESC_END), + Definition("cyan", ANSI_ESC_START _TO_STR(ANSI_FG_COLOR_CYAN) ANSI_ESC_END), + Definition("white", + ANSI_ESC_START _TO_STR(ANSI_FG_COLOR_WHITE) ANSI_ESC_END), +}; + +constexpr Definition g_ansi_bg_entries[] = { + Definition("black", + ANSI_ESC_START _TO_STR(ANSI_BG_COLOR_BLACK) ANSI_ESC_END), + Definition("red", ANSI_ESC_START _TO_STR(ANSI_BG_COLOR_RED) ANSI_ESC_END), + Definition("green", + ANSI_ESC_START _TO_STR(ANSI_BG_COLOR_GREEN) ANSI_ESC_END), + Definition("yellow", + ANSI_ESC_START _TO_STR(ANSI_BG_COLOR_YELLOW) ANSI_ESC_END), + Definition("blue", ANSI_ESC_START _TO_STR(ANSI_BG_COLOR_BLUE) ANSI_ESC_END), + Definition("purple", + ANSI_ESC_START _TO_STR(ANSI_BG_COLOR_PURPLE) ANSI_ESC_END), + Definition("cyan", ANSI_ESC_START _TO_STR(ANSI_BG_COLOR_CYAN) ANSI_ESC_END), + Definition("white", + ANSI_ESC_START _TO_STR(ANSI_BG_COLOR_WHITE) ANSI_ESC_END), +}; + +constexpr Definition g_ansi_entries[] = { + Entry::DefinitionWithChildren("fg", EntryType::Invalid, g_ansi_fg_entries), + Entry::DefinitionWithChildren("bg", EntryType::Invalid, g_ansi_bg_entries), + Definition("normal", ANSI_ESC_START _TO_STR(ANSI_CTRL_NORMAL) ANSI_ESC_END), + Definition("bold", ANSI_ESC_START _TO_STR(ANSI_CTRL_BOLD) ANSI_ESC_END), + Definition("faint", ANSI_ESC_START _TO_STR(ANSI_CTRL_FAINT) ANSI_ESC_END), + Definition("italic", ANSI_ESC_START _TO_STR(ANSI_CTRL_ITALIC) ANSI_ESC_END), + Definition("underline", + ANSI_ESC_START _TO_STR(ANSI_CTRL_UNDERLINE) ANSI_ESC_END), + Definition("slow-blink", + ANSI_ESC_START _TO_STR(ANSI_CTRL_SLOW_BLINK) ANSI_ESC_END), + Definition("fast-blink", + ANSI_ESC_START _TO_STR(ANSI_CTRL_FAST_BLINK) ANSI_ESC_END), + Definition("negative", + ANSI_ESC_START _TO_STR(ANSI_CTRL_IMAGE_NEGATIVE) ANSI_ESC_END), + Definition("conceal", + ANSI_ESC_START _TO_STR(ANSI_CTRL_CONCEAL) ANSI_ESC_END), + Definition("crossed-out", + ANSI_ESC_START _TO_STR(ANSI_CTRL_CROSSED_OUT) ANSI_ESC_END), +}; + +constexpr Definition g_script_child_entries[] = { + Definition("frame", EntryType::ScriptFrame), + Definition("process", EntryType::ScriptProcess), + Definition("target", EntryType::ScriptTarget), + Definition("thread", EntryType::ScriptThread), + Definition("var", EntryType::ScriptVariable), + Definition("svar", EntryType::ScriptVariableSynthetic), + Definition("thread", EntryType::ScriptThread)}; + +constexpr Definition g_top_level_entries[] = { + Entry::DefinitionWithChildren("addr", EntryType::AddressLoadOrFile, + g_addr_entries), + Definition("addr-file-or-load", EntryType::AddressLoadOrFile), + Entry::DefinitionWithChildren("ansi", EntryType::Invalid, g_ansi_entries), + Definition("current-pc-arrow", EntryType::CurrentPCArrow), + Entry::DefinitionWithChildren("file", EntryType::File, + g_file_child_entries), + Definition("language", EntryType::Lang), + Entry::DefinitionWithChildren("frame", EntryType::Invalid, + g_frame_child_entries), + Entry::DefinitionWithChildren("function", EntryType::Invalid, + g_function_child_entries), + Entry::DefinitionWithChildren("line", EntryType::Invalid, + g_line_child_entries), + Entry::DefinitionWithChildren("module", EntryType::Invalid, + g_module_child_entries), + Entry::DefinitionWithChildren("process", EntryType::Invalid, + g_process_child_entries), + Entry::DefinitionWithChildren("script", EntryType::Invalid, + g_script_child_entries), + Entry::DefinitionWithChildren("svar", EntryType::VariableSynthetic, + g_svar_child_entries, true), + Entry::DefinitionWithChildren("thread", EntryType::Invalid, + g_thread_child_entries), + Entry::DefinitionWithChildren("target", EntryType::Invalid, + g_target_child_entries), + Entry::DefinitionWithChildren("var", EntryType::Variable, + g_var_child_entries, true)}; + +constexpr Definition g_root = Entry::DefinitionWithChildren( + "<root>", EntryType::Root, g_top_level_entries); + +FormatEntity::Entry::Entry(llvm::StringRef s) + : string(s.data(), s.size()), printf_format(), children(), + type(Type::String) {} + +FormatEntity::Entry::Entry(char ch) + : string(1, ch), printf_format(), children(), type(Type::String) {} + +void FormatEntity::Entry::AppendChar(char ch) { + if (children.empty() || children.back().type != Entry::Type::String) + children.push_back(Entry(ch)); + else + children.back().string.append(1, ch); +} + +void FormatEntity::Entry::AppendText(const llvm::StringRef &s) { + if (children.empty() || children.back().type != Entry::Type::String) + children.push_back(Entry(s)); + else + children.back().string.append(s.data(), s.size()); +} + +void FormatEntity::Entry::AppendText(const char *cstr) { + return AppendText(llvm::StringRef(cstr)); +} + +#define ENUM_TO_CSTR(eee) \ + case FormatEntity::Entry::Type::eee: \ + return #eee + +const char *FormatEntity::Entry::TypeToCString(Type t) { + switch (t) { + ENUM_TO_CSTR(Invalid); + ENUM_TO_CSTR(ParentNumber); + ENUM_TO_CSTR(ParentString); + ENUM_TO_CSTR(EscapeCode); + ENUM_TO_CSTR(Root); + ENUM_TO_CSTR(String); + ENUM_TO_CSTR(Scope); + ENUM_TO_CSTR(Variable); + ENUM_TO_CSTR(VariableSynthetic); + ENUM_TO_CSTR(ScriptVariable); + ENUM_TO_CSTR(ScriptVariableSynthetic); + ENUM_TO_CSTR(AddressLoad); + ENUM_TO_CSTR(AddressFile); + ENUM_TO_CSTR(AddressLoadOrFile); + ENUM_TO_CSTR(ProcessID); + ENUM_TO_CSTR(ProcessFile); + ENUM_TO_CSTR(ScriptProcess); + ENUM_TO_CSTR(ThreadID); + ENUM_TO_CSTR(ThreadProtocolID); + ENUM_TO_CSTR(ThreadIndexID); + ENUM_TO_CSTR(ThreadName); + ENUM_TO_CSTR(ThreadQueue); + ENUM_TO_CSTR(ThreadStopReason); + ENUM_TO_CSTR(ThreadStopReasonRaw); + ENUM_TO_CSTR(ThreadReturnValue); + ENUM_TO_CSTR(ThreadCompletedExpression); + ENUM_TO_CSTR(ScriptThread); + ENUM_TO_CSTR(ThreadInfo); + ENUM_TO_CSTR(TargetArch); + ENUM_TO_CSTR(ScriptTarget); + ENUM_TO_CSTR(ModuleFile); + ENUM_TO_CSTR(File); + ENUM_TO_CSTR(Lang); + ENUM_TO_CSTR(FrameIndex); + ENUM_TO_CSTR(FrameNoDebug); + ENUM_TO_CSTR(FrameRegisterPC); + ENUM_TO_CSTR(FrameRegisterSP); + ENUM_TO_CSTR(FrameRegisterFP); + ENUM_TO_CSTR(FrameRegisterFlags); + ENUM_TO_CSTR(FrameRegisterByName); + ENUM_TO_CSTR(FrameIsArtificial); + ENUM_TO_CSTR(ScriptFrame); + ENUM_TO_CSTR(FunctionID); + ENUM_TO_CSTR(FunctionDidChange); + ENUM_TO_CSTR(FunctionInitialFunction); + ENUM_TO_CSTR(FunctionName); + ENUM_TO_CSTR(FunctionNameWithArgs); + ENUM_TO_CSTR(FunctionNameNoArgs); + ENUM_TO_CSTR(FunctionMangledName); + ENUM_TO_CSTR(FunctionAddrOffset); + ENUM_TO_CSTR(FunctionAddrOffsetConcrete); + ENUM_TO_CSTR(FunctionLineOffset); + ENUM_TO_CSTR(FunctionPCOffset); + ENUM_TO_CSTR(FunctionInitial); + ENUM_TO_CSTR(FunctionChanged); + ENUM_TO_CSTR(FunctionIsOptimized); + ENUM_TO_CSTR(LineEntryFile); + ENUM_TO_CSTR(LineEntryLineNumber); + ENUM_TO_CSTR(LineEntryColumn); + ENUM_TO_CSTR(LineEntryStartAddress); + ENUM_TO_CSTR(LineEntryEndAddress); + ENUM_TO_CSTR(CurrentPCArrow); + } + return "???"; +} + +#undef ENUM_TO_CSTR + +void FormatEntity::Entry::Dump(Stream &s, int depth) const { + s.Printf("%*.*s%-20s: ", depth * 2, depth * 2, "", TypeToCString(type)); + if (fmt != eFormatDefault) + s.Printf("lldb-format = %s, ", FormatManager::GetFormatAsCString(fmt)); + if (!string.empty()) + s.Printf("string = \"%s\"", string.c_str()); + if (!printf_format.empty()) + s.Printf("printf_format = \"%s\"", printf_format.c_str()); + if (number != 0) + s.Printf("number = %" PRIu64 " (0x%" PRIx64 "), ", number, number); + if (deref) + s.Printf("deref = true, "); + s.EOL(); + for (const auto &child : children) { + child.Dump(s, depth + 1); + } +} + +template <typename T> +static bool RunScriptFormatKeyword(Stream &s, const SymbolContext *sc, + const ExecutionContext *exe_ctx, T t, + const char *script_function_name) { + Target *target = Target::GetTargetFromContexts(exe_ctx, sc); + + if (target) { + ScriptInterpreter *script_interpreter = + target->GetDebugger().GetScriptInterpreter(); + if (script_interpreter) { + Status error; + std::string script_output; + + if (script_interpreter->RunScriptFormatKeyword(script_function_name, t, + script_output, error) && + error.Success()) { + s.Printf("%s", script_output.c_str()); + return true; + } else { + s.Printf("<error: %s>", error.AsCString()); + } + } + } + return false; +} + +static bool DumpAddressAndContent(Stream &s, const SymbolContext *sc, + const ExecutionContext *exe_ctx, + const Address &addr, + bool print_file_addr_or_load_addr) { + Target *target = Target::GetTargetFromContexts(exe_ctx, sc); + addr_t vaddr = LLDB_INVALID_ADDRESS; + if (exe_ctx && !target->GetSectionLoadList().IsEmpty()) + vaddr = addr.GetLoadAddress(target); + if (vaddr == LLDB_INVALID_ADDRESS) + vaddr = addr.GetFileAddress(); + + if (vaddr != LLDB_INVALID_ADDRESS) { + int addr_width = 0; + if (exe_ctx && target) { + addr_width = target->GetArchitecture().GetAddressByteSize() * 2; + } + if (addr_width == 0) + addr_width = 16; + if (print_file_addr_or_load_addr) { + ExecutionContextScope *exe_scope = nullptr; + if (exe_ctx) + exe_scope = exe_ctx->GetBestExecutionContextScope(); + addr.Dump(&s, exe_scope, Address::DumpStyleLoadAddress, + Address::DumpStyleModuleWithFileAddress, 0); + } else { + s.Printf("0x%*.*" PRIx64, addr_width, addr_width, vaddr); + } + return true; + } + return false; +} + +static bool DumpAddressOffsetFromFunction(Stream &s, const SymbolContext *sc, + const ExecutionContext *exe_ctx, + const Address &format_addr, + bool concrete_only, bool no_padding, + bool print_zero_offsets) { + if (format_addr.IsValid()) { + Address func_addr; + + if (sc) { + if (sc->function) { + func_addr = sc->function->GetAddressRange().GetBaseAddress(); + if (sc->block && !concrete_only) { + // Check to make sure we aren't in an inline function. If we are, use + // the inline block range that contains "format_addr" since blocks + // can be discontiguous. + Block *inline_block = sc->block->GetContainingInlinedBlock(); + AddressRange inline_range; + if (inline_block && inline_block->GetRangeContainingAddress( + format_addr, inline_range)) + func_addr = inline_range.GetBaseAddress(); + } + } else if (sc->symbol && sc->symbol->ValueIsAddress()) + func_addr = sc->symbol->GetAddressRef(); + } + + if (func_addr.IsValid()) { + const char *addr_offset_padding = no_padding ? "" : " "; + + if (func_addr.GetSection() == format_addr.GetSection()) { + addr_t func_file_addr = func_addr.GetFileAddress(); + addr_t addr_file_addr = format_addr.GetFileAddress(); + if (addr_file_addr > func_file_addr || + (addr_file_addr == func_file_addr && print_zero_offsets)) { + s.Printf("%s+%s%" PRIu64, addr_offset_padding, addr_offset_padding, + addr_file_addr - func_file_addr); + } else if (addr_file_addr < func_file_addr) { + s.Printf("%s-%s%" PRIu64, addr_offset_padding, addr_offset_padding, + func_file_addr - addr_file_addr); + } + return true; + } else { + Target *target = Target::GetTargetFromContexts(exe_ctx, sc); + if (target) { + addr_t func_load_addr = func_addr.GetLoadAddress(target); + addr_t addr_load_addr = format_addr.GetLoadAddress(target); + if (addr_load_addr > func_load_addr || + (addr_load_addr == func_load_addr && print_zero_offsets)) { + s.Printf("%s+%s%" PRIu64, addr_offset_padding, addr_offset_padding, + addr_load_addr - func_load_addr); + } else if (addr_load_addr < func_load_addr) { + s.Printf("%s-%s%" PRIu64, addr_offset_padding, addr_offset_padding, + func_load_addr - addr_load_addr); + } + return true; + } + } + } + } + return false; +} + +static bool ScanBracketedRange(llvm::StringRef subpath, + size_t &close_bracket_index, + const char *&var_name_final_if_array_range, + int64_t &index_lower, int64_t &index_higher) { + Log *log = GetLog(LLDBLog::DataFormatters); + close_bracket_index = llvm::StringRef::npos; + const size_t open_bracket_index = subpath.find('['); + if (open_bracket_index == llvm::StringRef::npos) { + LLDB_LOGF(log, + "[ScanBracketedRange] no bracketed range, skipping entirely"); + return false; + } + + close_bracket_index = subpath.find(']', open_bracket_index + 1); + + if (close_bracket_index == llvm::StringRef::npos) { + LLDB_LOGF(log, + "[ScanBracketedRange] no bracketed range, skipping entirely"); + return false; + } else { + var_name_final_if_array_range = subpath.data() + open_bracket_index; + + if (close_bracket_index - open_bracket_index == 1) { + LLDB_LOGF( + log, + "[ScanBracketedRange] '[]' detected.. going from 0 to end of data"); + index_lower = 0; + } else { + const size_t separator_index = subpath.find('-', open_bracket_index + 1); + + if (separator_index == llvm::StringRef::npos) { + const char *index_lower_cstr = subpath.data() + open_bracket_index + 1; + index_lower = ::strtoul(index_lower_cstr, nullptr, 0); + index_higher = index_lower; + LLDB_LOGF(log, + "[ScanBracketedRange] [%" PRId64 + "] detected, high index is same", + index_lower); + } else { + const char *index_lower_cstr = subpath.data() + open_bracket_index + 1; + const char *index_higher_cstr = subpath.data() + separator_index + 1; + index_lower = ::strtoul(index_lower_cstr, nullptr, 0); + index_higher = ::strtoul(index_higher_cstr, nullptr, 0); + LLDB_LOGF(log, + "[ScanBracketedRange] [%" PRId64 "-%" PRId64 "] detected", + index_lower, index_higher); + } + if (index_lower > index_higher && index_higher > 0) { + LLDB_LOGF(log, "[ScanBracketedRange] swapping indices"); + const int64_t temp = index_lower; + index_lower = index_higher; + index_higher = temp; + } + } + } + return true; +} + +static bool DumpFile(Stream &s, const FileSpec &file, FileKind file_kind) { + switch (file_kind) { + case FileKind::FileError: + break; + + case FileKind::Basename: + if (file.GetFilename()) { + s << file.GetFilename(); + return true; + } + break; + + case FileKind::Dirname: + if (file.GetDirectory()) { + s << file.GetDirectory(); + return true; + } + break; + + case FileKind::Fullpath: + if (file) { + s << file; + return true; + } + break; + } + return false; +} + +static bool DumpRegister(Stream &s, StackFrame *frame, RegisterKind reg_kind, + uint32_t reg_num, Format format) { + if (frame) { + RegisterContext *reg_ctx = frame->GetRegisterContext().get(); + + if (reg_ctx) { + const uint32_t lldb_reg_num = + reg_ctx->ConvertRegisterKindToRegisterNumber(reg_kind, reg_num); + if (lldb_reg_num != LLDB_INVALID_REGNUM) { + const RegisterInfo *reg_info = + reg_ctx->GetRegisterInfoAtIndex(lldb_reg_num); + if (reg_info) { + RegisterValue reg_value; + if (reg_ctx->ReadRegister(reg_info, reg_value)) { + DumpRegisterValue(reg_value, s, *reg_info, false, false, format); + return true; + } + } + } + } + } + return false; +} + +static ValueObjectSP ExpandIndexedExpression(ValueObject *valobj, size_t index, + bool deref_pointer) { + Log *log = GetLog(LLDBLog::DataFormatters); + std::string name_to_deref = llvm::formatv("[{0}]", index); + LLDB_LOG(log, "[ExpandIndexedExpression] name to deref: {0}", name_to_deref); + ValueObject::GetValueForExpressionPathOptions options; + ValueObject::ExpressionPathEndResultType final_value_type; + ValueObject::ExpressionPathScanEndReason reason_to_stop; + ValueObject::ExpressionPathAftermath what_next = + (deref_pointer ? ValueObject::eExpressionPathAftermathDereference + : ValueObject::eExpressionPathAftermathNothing); + ValueObjectSP item = valobj->GetValueForExpressionPath( + name_to_deref, &reason_to_stop, &final_value_type, options, &what_next); + if (!item) { + LLDB_LOGF(log, + "[ExpandIndexedExpression] ERROR: why stopping = %d," + " final_value_type %d", + reason_to_stop, final_value_type); + } else { + LLDB_LOGF(log, + "[ExpandIndexedExpression] ALL RIGHT: why stopping = %d," + " final_value_type %d", + reason_to_stop, final_value_type); + } + return item; +} + +static char ConvertValueObjectStyleToChar( + ValueObject::ValueObjectRepresentationStyle style) { + switch (style) { + case ValueObject::eValueObjectRepresentationStyleLanguageSpecific: + return '@'; + case ValueObject::eValueObjectRepresentationStyleValue: + return 'V'; + case ValueObject::eValueObjectRepresentationStyleLocation: + return 'L'; + case ValueObject::eValueObjectRepresentationStyleSummary: + return 'S'; + case ValueObject::eValueObjectRepresentationStyleChildrenCount: + return '#'; + case ValueObject::eValueObjectRepresentationStyleType: + return 'T'; + case ValueObject::eValueObjectRepresentationStyleName: + return 'N'; + case ValueObject::eValueObjectRepresentationStyleExpressionPath: + return '>'; + } + return '\0'; +} + +/// Options supported by format_provider<T> for integral arithmetic types. +/// See table in FormatProviders.h. +static llvm::Regex LLVMFormatPattern{"x[-+]?\\d*|n|d", llvm::Regex::IgnoreCase}; + +static bool DumpValueWithLLVMFormat(Stream &s, llvm::StringRef options, + ValueObject &valobj) { + std::string formatted; + std::string llvm_format = ("{0:" + options + "}").str(); + + auto type_info = valobj.GetTypeInfo(); + if ((type_info & eTypeIsInteger) && LLVMFormatPattern.match(options)) { + if (type_info & eTypeIsSigned) { + bool success = false; + int64_t integer = valobj.GetValueAsSigned(0, &success); + if (success) + formatted = llvm::formatv(llvm_format.data(), integer); + } else { + bool success = false; + uint64_t integer = valobj.GetValueAsUnsigned(0, &success); + if (success) + formatted = llvm::formatv(llvm_format.data(), integer); + } + } + + if (formatted.empty()) + return false; + + s.Write(formatted.data(), formatted.size()); + return true; +} + +static bool DumpValue(Stream &s, const SymbolContext *sc, + const ExecutionContext *exe_ctx, + const FormatEntity::Entry &entry, ValueObject *valobj) { + if (valobj == nullptr) + return false; + + Log *log = GetLog(LLDBLog::DataFormatters); + Format custom_format = eFormatInvalid; + ValueObject::ValueObjectRepresentationStyle val_obj_display = + entry.string.empty() + ? ValueObject::eValueObjectRepresentationStyleValue + : ValueObject::eValueObjectRepresentationStyleSummary; + + bool do_deref_pointer = entry.deref; + bool is_script = false; + switch (entry.type) { + case FormatEntity::Entry::Type::ScriptVariable: + is_script = true; + break; + + case FormatEntity::Entry::Type::Variable: + custom_format = entry.fmt; + val_obj_display = (ValueObject::ValueObjectRepresentationStyle)entry.number; + break; + + case FormatEntity::Entry::Type::ScriptVariableSynthetic: + is_script = true; + [[fallthrough]]; + case FormatEntity::Entry::Type::VariableSynthetic: + custom_format = entry.fmt; + val_obj_display = (ValueObject::ValueObjectRepresentationStyle)entry.number; + if (!valobj->IsSynthetic()) { + valobj = valobj->GetSyntheticValue().get(); + if (valobj == nullptr) + return false; + } + break; + + default: + return false; + } + + ValueObject::ExpressionPathAftermath what_next = + (do_deref_pointer ? ValueObject::eExpressionPathAftermathDereference + : ValueObject::eExpressionPathAftermathNothing); + ValueObject::GetValueForExpressionPathOptions options; + options.DontCheckDotVsArrowSyntax() + .DoAllowBitfieldSyntax() + .DoAllowFragileIVar() + .SetSyntheticChildrenTraversal( + ValueObject::GetValueForExpressionPathOptions:: + SyntheticChildrenTraversal::Both); + ValueObject *target = nullptr; + const char *var_name_final_if_array_range = nullptr; + size_t close_bracket_index = llvm::StringRef::npos; + int64_t index_lower = -1; + int64_t index_higher = -1; + bool is_array_range = false; + bool was_plain_var = false; + bool was_var_format = false; + bool was_var_indexed = false; + ValueObject::ExpressionPathScanEndReason reason_to_stop = + ValueObject::eExpressionPathScanEndReasonEndOfString; + ValueObject::ExpressionPathEndResultType final_value_type = + ValueObject::eExpressionPathEndResultTypePlain; + + if (is_script) { + return RunScriptFormatKeyword(s, sc, exe_ctx, valobj, entry.string.c_str()); + } + + auto split = llvm::StringRef(entry.string).split(':'); + auto subpath = split.first; + auto llvm_format = split.second; + + // simplest case ${var}, just print valobj's value + if (subpath.empty()) { + if (entry.printf_format.empty() && entry.fmt == eFormatDefault && + entry.number == ValueObject::eValueObjectRepresentationStyleValue) + was_plain_var = true; + else + was_var_format = true; + target = valobj; + } else // this is ${var.something} or multiple .something nested + { + if (subpath[0] == '[') + was_var_indexed = true; + ScanBracketedRange(subpath, close_bracket_index, + var_name_final_if_array_range, index_lower, + index_higher); + + Status error; + + LLDB_LOG(log, "[Debugger::FormatPrompt] symbol to expand: {0}", subpath); + + target = + valobj + ->GetValueForExpressionPath(subpath, &reason_to_stop, + &final_value_type, options, &what_next) + .get(); + + if (!target) { + LLDB_LOGF(log, + "[Debugger::FormatPrompt] ERROR: why stopping = %d," + " final_value_type %d", + reason_to_stop, final_value_type); + return false; + } else { + LLDB_LOGF(log, + "[Debugger::FormatPrompt] ALL RIGHT: why stopping = %d," + " final_value_type %d", + reason_to_stop, final_value_type); + target = target + ->GetQualifiedRepresentationIfAvailable( + target->GetDynamicValueType(), true) + .get(); + } + } + + is_array_range = + (final_value_type == + ValueObject::eExpressionPathEndResultTypeBoundedRange || + final_value_type == + ValueObject::eExpressionPathEndResultTypeUnboundedRange); + + do_deref_pointer = + (what_next == ValueObject::eExpressionPathAftermathDereference); + + if (do_deref_pointer && !is_array_range) { + // I have not deref-ed yet, let's do it + // this happens when we are not going through + // GetValueForVariableExpressionPath to get to the target ValueObject + Status error; + target = target->Dereference(error).get(); + if (error.Fail()) { + LLDB_LOGF(log, "[Debugger::FormatPrompt] ERROR: %s\n", + error.AsCString("unknown")); + return false; + } + do_deref_pointer = false; + } + + if (!target) { + LLDB_LOGF(log, "[Debugger::FormatPrompt] could not calculate target for " + "prompt expression"); + return false; + } + + // we do not want to use the summary for a bitfield of type T:n if we were + // originally dealing with just a T - that would get us into an endless + // recursion + if (target->IsBitfield() && was_var_indexed) { + // TODO: check for a (T:n)-specific summary - we should still obey that + StreamString bitfield_name; + bitfield_name.Printf("%s:%d", target->GetTypeName().AsCString(), + target->GetBitfieldBitSize()); + auto type_sp = std::make_shared<TypeNameSpecifierImpl>( + bitfield_name.GetString(), lldb::eFormatterMatchExact); + if (val_obj_display == + ValueObject::eValueObjectRepresentationStyleSummary && + !DataVisualization::GetSummaryForType(type_sp)) + val_obj_display = ValueObject::eValueObjectRepresentationStyleValue; + } + + // TODO use flags for these + const uint32_t type_info_flags = + target->GetCompilerType().GetTypeInfo(nullptr); + bool is_array = (type_info_flags & eTypeIsArray) != 0; + bool is_pointer = (type_info_flags & eTypeIsPointer) != 0; + bool is_aggregate = target->GetCompilerType().IsAggregateType(); + + if ((is_array || is_pointer) && (!is_array_range) && + val_obj_display == + ValueObject::eValueObjectRepresentationStyleValue) // this should be + // wrong, but there + // are some + // exceptions + { + StreamString str_temp; + LLDB_LOGF(log, + "[Debugger::FormatPrompt] I am into array || pointer && !range"); + + if (target->HasSpecialPrintableRepresentation(val_obj_display, + custom_format)) { + // try to use the special cases + bool success = target->DumpPrintableRepresentation( + str_temp, val_obj_display, custom_format); + LLDB_LOGF(log, "[Debugger::FormatPrompt] special cases did%s match", + success ? "" : "n't"); + + // should not happen + if (success) + s << str_temp.GetString(); + return true; + } else { + if (was_plain_var) // if ${var} + { + s << target->GetTypeName() << " @ " << target->GetLocationAsCString(); + } else if (is_pointer) // if pointer, value is the address stored + { + target->DumpPrintableRepresentation( + s, val_obj_display, custom_format, + ValueObject::PrintableRepresentationSpecialCases::eDisable); + } + return true; + } + } + + // if directly trying to print ${var}, and this is an aggregate, display a + // nice type @ location message + if (is_aggregate && was_plain_var) { + s << target->GetTypeName() << " @ " << target->GetLocationAsCString(); + return true; + } + + // if directly trying to print ${var%V}, and this is an aggregate, do not let + // the user do it + if (is_aggregate && + ((was_var_format && + val_obj_display == + ValueObject::eValueObjectRepresentationStyleValue))) { + s << "<invalid use of aggregate type>"; + return true; + } + + if (!is_array_range) { + if (!llvm_format.empty()) { + if (DumpValueWithLLVMFormat(s, llvm_format, *target)) { + LLDB_LOGF(log, "dumping using llvm format"); + return true; + } else { + LLDB_LOG( + log, + "empty output using llvm format '{0}' - with type info flags {1}", + entry.printf_format, target->GetTypeInfo()); + } + } + LLDB_LOGF(log, "dumping ordinary printable output"); + return target->DumpPrintableRepresentation(s, val_obj_display, + custom_format); + } else { + LLDB_LOGF(log, + "[Debugger::FormatPrompt] checking if I can handle as array"); + if (!is_array && !is_pointer) + return false; + LLDB_LOGF(log, "[Debugger::FormatPrompt] handle as array"); + StreamString special_directions_stream; + llvm::StringRef special_directions; + if (close_bracket_index != llvm::StringRef::npos && + subpath.size() > close_bracket_index) { + ConstString additional_data(subpath.drop_front(close_bracket_index + 1)); + special_directions_stream.Printf("${%svar%s", do_deref_pointer ? "*" : "", + additional_data.GetCString()); + + if (entry.fmt != eFormatDefault) { + const char format_char = + FormatManager::GetFormatAsFormatChar(entry.fmt); + if (format_char != '\0') + special_directions_stream.Printf("%%%c", format_char); + else { + const char *format_cstr = + FormatManager::GetFormatAsCString(entry.fmt); + special_directions_stream.Printf("%%%s", format_cstr); + } + } else if (entry.number != 0) { + const char style_char = ConvertValueObjectStyleToChar( + (ValueObject::ValueObjectRepresentationStyle)entry.number); + if (style_char) + special_directions_stream.Printf("%%%c", style_char); + } + special_directions_stream.PutChar('}'); + special_directions = + llvm::StringRef(special_directions_stream.GetString()); + } + + // let us display items index_lower thru index_higher of this array + s.PutChar('['); + + if (index_higher < 0) + index_higher = valobj->GetNumChildrenIgnoringErrors() - 1; + + uint32_t max_num_children = + target->GetTargetSP()->GetMaximumNumberOfChildrenToDisplay(); + + bool success = true; + for (int64_t index = index_lower; index <= index_higher; ++index) { + ValueObject *item = ExpandIndexedExpression(target, index, false).get(); + + if (!item) { + LLDB_LOGF(log, + "[Debugger::FormatPrompt] ERROR in getting child item at " + "index %" PRId64, + index); + } else { + LLDB_LOGF( + log, + "[Debugger::FormatPrompt] special_directions for child item: %s", + special_directions.data() ? special_directions.data() : ""); + } + + if (special_directions.empty()) { + success &= item->DumpPrintableRepresentation(s, val_obj_display, + custom_format); + } else { + success &= FormatEntity::FormatStringRef( + special_directions, s, sc, exe_ctx, nullptr, item, false, false); + } + + if (--max_num_children == 0) { + s.PutCString(", ..."); + break; + } + + if (index < index_higher) + s.PutChar(','); + } + s.PutChar(']'); + return success; + } +} + +static bool DumpRegister(Stream &s, StackFrame *frame, const char *reg_name, + Format format) { + if (frame) { + RegisterContext *reg_ctx = frame->GetRegisterContext().get(); + + if (reg_ctx) { + const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoByName(reg_name); + if (reg_info) { + RegisterValue reg_value; + if (reg_ctx->ReadRegister(reg_info, reg_value)) { + DumpRegisterValue(reg_value, s, *reg_info, false, false, format); + return true; + } + } + } + } + return false; +} + +static bool FormatThreadExtendedInfoRecurse( + const FormatEntity::Entry &entry, + const StructuredData::ObjectSP &thread_info_dictionary, + const SymbolContext *sc, const ExecutionContext *exe_ctx, Stream &s) { + llvm::StringRef path(entry.string); + + StructuredData::ObjectSP value = + thread_info_dictionary->GetObjectForDotSeparatedPath(path); + + if (value) { + if (value->GetType() == eStructuredDataTypeInteger) { + const char *token_format = "0x%4.4" PRIx64; + if (!entry.printf_format.empty()) + token_format = entry.printf_format.c_str(); + s.Printf(token_format, value->GetUnsignedIntegerValue()); + return true; + } else if (value->GetType() == eStructuredDataTypeFloat) { + s.Printf("%f", value->GetAsFloat()->GetValue()); + return true; + } else if (value->GetType() == eStructuredDataTypeString) { + s.Format("{0}", value->GetAsString()->GetValue()); + return true; + } else if (value->GetType() == eStructuredDataTypeArray) { + if (value->GetAsArray()->GetSize() > 0) { + s.Printf("%zu", value->GetAsArray()->GetSize()); + return true; + } + } else if (value->GetType() == eStructuredDataTypeDictionary) { + s.Printf("%zu", + value->GetAsDictionary()->GetKeys()->GetAsArray()->GetSize()); + return true; + } + } + + return false; +} + +static inline bool IsToken(const char *var_name_begin, const char *var) { + return (::strncmp(var_name_begin, var, strlen(var)) == 0); +} + +/// Parses the basename out of a demangled function name +/// that may include function arguments. Supports +/// template functions. +/// +/// Returns pointers to the opening and closing parenthesis of +/// `full_name`. Can return nullptr for either parenthesis if +/// none is exists. +static std::pair<char const *, char const *> +ParseBaseName(char const *full_name) { + const char *open_paren = strchr(full_name, '('); + const char *close_paren = nullptr; + const char *generic = strchr(full_name, '<'); + // if before the arguments list begins there is a template sign + // then scan to the end of the generic args before you try to find + // the arguments list + if (generic && open_paren && generic < open_paren) { + int generic_depth = 1; + ++generic; + for (; *generic && generic_depth > 0; generic++) { + if (*generic == '<') + generic_depth++; + if (*generic == '>') + generic_depth--; + } + if (*generic) + open_paren = strchr(generic, '('); + else + open_paren = nullptr; + } + + if (open_paren) { + if (IsToken(open_paren, "(anonymous namespace)")) { + open_paren = strchr(open_paren + strlen("(anonymous namespace)"), '('); + if (open_paren) + close_paren = strchr(open_paren, ')'); + } else + close_paren = strchr(open_paren, ')'); + } + + return {open_paren, close_paren}; +} + +/// Writes out the function name in 'full_name' to 'out_stream' +/// but replaces each argument type with the variable name +/// and the corresponding pretty-printed value +static void PrettyPrintFunctionNameWithArgs(Stream &out_stream, + char const *full_name, + ExecutionContextScope *exe_scope, + VariableList const &args) { + auto [open_paren, close_paren] = ParseBaseName(full_name); + if (open_paren) + out_stream.Write(full_name, open_paren - full_name + 1); + else { + out_stream.PutCString(full_name); + out_stream.PutChar('('); + } + + FormatEntity::PrettyPrintFunctionArguments(out_stream, args, exe_scope); + + if (close_paren) + out_stream.PutCString(close_paren); + else + out_stream.PutChar(')'); +} + +static void FormatInlinedBlock(Stream &out_stream, Block *block) { + if (!block) + return; + Block *inline_block = block->GetContainingInlinedBlock(); + if (inline_block) { + if (const InlineFunctionInfo *inline_info = + inline_block->GetInlinedFunctionInfo()) { + out_stream.PutCString(" [inlined] "); + inline_info->GetName().Dump(&out_stream); + } + } +} + +bool FormatEntity::FormatStringRef(const llvm::StringRef &format_str, Stream &s, + const SymbolContext *sc, + const ExecutionContext *exe_ctx, + const Address *addr, ValueObject *valobj, + bool function_changed, + bool initial_function) { + if (!format_str.empty()) { + FormatEntity::Entry root; + Status error = FormatEntity::Parse(format_str, root); + if (error.Success()) { + return FormatEntity::Format(root, s, sc, exe_ctx, addr, valobj, + function_changed, initial_function); + } + } + return false; +} + +bool FormatEntity::FormatCString(const char *format, Stream &s, + const SymbolContext *sc, + const ExecutionContext *exe_ctx, + const Address *addr, ValueObject *valobj, + bool function_changed, bool initial_function) { + if (format && format[0]) { + FormatEntity::Entry root; + llvm::StringRef format_str(format); + Status error = FormatEntity::Parse(format_str, root); + if (error.Success()) { + return FormatEntity::Format(root, s, sc, exe_ctx, addr, valobj, + function_changed, initial_function); + } + } + return false; +} + +bool FormatEntity::Format(const Entry &entry, Stream &s, + const SymbolContext *sc, + const ExecutionContext *exe_ctx, const Address *addr, + ValueObject *valobj, bool function_changed, + bool initial_function) { + switch (entry.type) { + case Entry::Type::Invalid: + case Entry::Type::ParentNumber: // Only used for + // FormatEntity::Entry::Definition encoding + case Entry::Type::ParentString: // Only used for + // FormatEntity::Entry::Definition encoding + return false; + case Entry::Type::EscapeCode: + if (exe_ctx) { + if (Target *target = exe_ctx->GetTargetPtr()) { + Debugger &debugger = target->GetDebugger(); + if (debugger.GetUseColor()) { + s.PutCString(entry.string); + } + } + } + // Always return true, so colors being disabled is transparent. + return true; + + case Entry::Type::Root: + for (const auto &child : entry.children) { + if (!Format(child, s, sc, exe_ctx, addr, valobj, function_changed, + initial_function)) { + return false; // If any item of root fails, then the formatting fails + } + } + return true; // Only return true if all items succeeded + + case Entry::Type::String: + s.PutCString(entry.string); + return true; + + case Entry::Type::Scope: { + StreamString scope_stream; + bool success = false; + for (const auto &child : entry.children) { + success = Format(child, scope_stream, sc, exe_ctx, addr, valobj, + function_changed, initial_function); + if (!success) + break; + } + // Only if all items in a scope succeed, then do we print the output into + // the main stream + if (success) + s.Write(scope_stream.GetString().data(), scope_stream.GetString().size()); + } + return true; // Scopes always successfully print themselves + + case Entry::Type::Variable: + case Entry::Type::VariableSynthetic: + case Entry::Type::ScriptVariable: + case Entry::Type::ScriptVariableSynthetic: + return DumpValue(s, sc, exe_ctx, entry, valobj); + + case Entry::Type::AddressFile: + case Entry::Type::AddressLoad: + case Entry::Type::AddressLoadOrFile: + return ( + addr != nullptr && addr->IsValid() && + DumpAddressAndContent(s, sc, exe_ctx, *addr, + entry.type == Entry::Type::AddressLoadOrFile)); + + case Entry::Type::ProcessID: + if (exe_ctx) { + Process *process = exe_ctx->GetProcessPtr(); + if (process) { + const char *format = "%" PRIu64; + if (!entry.printf_format.empty()) + format = entry.printf_format.c_str(); + s.Printf(format, process->GetID()); + return true; + } + } + return false; + + case Entry::Type::ProcessFile: + if (exe_ctx) { + Process *process = exe_ctx->GetProcessPtr(); + if (process) { + Module *exe_module = process->GetTarget().GetExecutableModulePointer(); + if (exe_module) { + if (DumpFile(s, exe_module->GetFileSpec(), (FileKind)entry.number)) + return true; + } + } + } + return false; + + case Entry::Type::ScriptProcess: + if (exe_ctx) { + Process *process = exe_ctx->GetProcessPtr(); + if (process) + return RunScriptFormatKeyword(s, sc, exe_ctx, process, + entry.string.c_str()); + } + return false; + + case Entry::Type::ThreadID: + if (exe_ctx) { + Thread *thread = exe_ctx->GetThreadPtr(); + if (thread) { + const char *format = "0x%4.4" PRIx64; + if (!entry.printf_format.empty()) { + // Watch for the special "tid" format... + if (entry.printf_format == "tid") { + // TODO(zturner): Rather than hardcoding this to be platform + // specific, it should be controlled by a setting and the default + // value of the setting can be different depending on the platform. + Target &target = thread->GetProcess()->GetTarget(); + ArchSpec arch(target.GetArchitecture()); + llvm::Triple::OSType ostype = arch.IsValid() + ? arch.GetTriple().getOS() + : llvm::Triple::UnknownOS; + if (ostype == llvm::Triple::FreeBSD || + ostype == llvm::Triple::Linux || + ostype == llvm::Triple::NetBSD || + ostype == llvm::Triple::OpenBSD) { + format = "%" PRIu64; + } + } else { + format = entry.printf_format.c_str(); + } + } + s.Printf(format, thread->GetID()); + return true; + } + } + return false; + + case Entry::Type::ThreadProtocolID: + if (exe_ctx) { + Thread *thread = exe_ctx->GetThreadPtr(); + if (thread) { + const char *format = "0x%4.4" PRIx64; + if (!entry.printf_format.empty()) + format = entry.printf_format.c_str(); + s.Printf(format, thread->GetProtocolID()); + return true; + } + } + return false; + + case Entry::Type::ThreadIndexID: + if (exe_ctx) { + Thread *thread = exe_ctx->GetThreadPtr(); + if (thread) { + const char *format = "%" PRIu32; + if (!entry.printf_format.empty()) + format = entry.printf_format.c_str(); + s.Printf(format, thread->GetIndexID()); + return true; + } + } + return false; + + case Entry::Type::ThreadName: + if (exe_ctx) { + Thread *thread = exe_ctx->GetThreadPtr(); + if (thread) { + const char *cstr = thread->GetName(); + if (cstr && cstr[0]) { + s.PutCString(cstr); + return true; + } + } + } + return false; + + case Entry::Type::ThreadQueue: + if (exe_ctx) { + Thread *thread = exe_ctx->GetThreadPtr(); + if (thread) { + const char *cstr = thread->GetQueueName(); + if (cstr && cstr[0]) { + s.PutCString(cstr); + return true; + } + } + } + return false; + + case Entry::Type::ThreadStopReason: + if (exe_ctx) { + if (Thread *thread = exe_ctx->GetThreadPtr()) { + std::string stop_description = thread->GetStopDescription(); + if (!stop_description.empty()) { + s.PutCString(stop_description); + return true; + } + } + } + return false; + + case Entry::Type::ThreadStopReasonRaw: + if (exe_ctx) { + if (Thread *thread = exe_ctx->GetThreadPtr()) { + std::string stop_description = thread->GetStopDescriptionRaw(); + if (!stop_description.empty()) { + s.PutCString(stop_description); + return true; + } + } + } + return false; + + case Entry::Type::ThreadReturnValue: + if (exe_ctx) { + Thread *thread = exe_ctx->GetThreadPtr(); + if (thread) { + StopInfoSP stop_info_sp = thread->GetStopInfo(); + if (stop_info_sp && stop_info_sp->IsValid()) { + ValueObjectSP return_valobj_sp = + StopInfo::GetReturnValueObject(stop_info_sp); + if (return_valobj_sp) { + if (llvm::Error error = return_valobj_sp->Dump(s)) { + s << "error: " << toString(std::move(error)); + return false; + } + return true; + } + } + } + } + return false; + + case Entry::Type::ThreadCompletedExpression: + if (exe_ctx) { + Thread *thread = exe_ctx->GetThreadPtr(); + if (thread) { + StopInfoSP stop_info_sp = thread->GetStopInfo(); + if (stop_info_sp && stop_info_sp->IsValid()) { + ExpressionVariableSP expression_var_sp = + StopInfo::GetExpressionVariable(stop_info_sp); + if (expression_var_sp && expression_var_sp->GetValueObject()) { + if (llvm::Error error = + expression_var_sp->GetValueObject()->Dump(s)) { + s << "error: " << toString(std::move(error)); + return false; + } + return true; + } + } + } + } + return false; + + case Entry::Type::ScriptThread: + if (exe_ctx) { + Thread *thread = exe_ctx->GetThreadPtr(); + if (thread) + return RunScriptFormatKeyword(s, sc, exe_ctx, thread, + entry.string.c_str()); + } + return false; + + case Entry::Type::ThreadInfo: + if (exe_ctx) { + Thread *thread = exe_ctx->GetThreadPtr(); + if (thread) { + StructuredData::ObjectSP object_sp = thread->GetExtendedInfo(); + if (object_sp && + object_sp->GetType() == eStructuredDataTypeDictionary) { + if (FormatThreadExtendedInfoRecurse(entry, object_sp, sc, exe_ctx, s)) + return true; + } + } + } + return false; + + case Entry::Type::TargetArch: + if (exe_ctx) { + Target *target = exe_ctx->GetTargetPtr(); + if (target) { + const ArchSpec &arch = target->GetArchitecture(); + if (arch.IsValid()) { + s.PutCString(arch.GetArchitectureName()); + return true; + } + } + } + return false; + + case Entry::Type::ScriptTarget: + if (exe_ctx) { + Target *target = exe_ctx->GetTargetPtr(); + if (target) + return RunScriptFormatKeyword(s, sc, exe_ctx, target, + entry.string.c_str()); + } + return false; + + case Entry::Type::ModuleFile: + if (sc) { + Module *module = sc->module_sp.get(); + if (module) { + if (DumpFile(s, module->GetFileSpec(), (FileKind)entry.number)) + return true; + } + } + return false; + + case Entry::Type::File: + if (sc) { + CompileUnit *cu = sc->comp_unit; + if (cu) { + if (DumpFile(s, cu->GetPrimaryFile(), (FileKind)entry.number)) + return true; + } + } + return false; + + case Entry::Type::Lang: + if (sc) { + CompileUnit *cu = sc->comp_unit; + if (cu) { + const char *lang_name = + Language::GetNameForLanguageType(cu->GetLanguage()); + if (lang_name) { + s.PutCString(lang_name); + return true; + } + } + } + return false; + + case Entry::Type::FrameIndex: + if (exe_ctx) { + StackFrame *frame = exe_ctx->GetFramePtr(); + if (frame) { + const char *format = "%" PRIu32; + if (!entry.printf_format.empty()) + format = entry.printf_format.c_str(); + s.Printf(format, frame->GetFrameIndex()); + return true; + } + } + return false; + + case Entry::Type::FrameRegisterPC: + if (exe_ctx) { + StackFrame *frame = exe_ctx->GetFramePtr(); + if (frame) { + const Address &pc_addr = frame->GetFrameCodeAddress(); + if (pc_addr.IsValid()) { + if (DumpAddressAndContent(s, sc, exe_ctx, pc_addr, false)) + return true; + } + } + } + return false; + + case Entry::Type::FrameRegisterSP: + if (exe_ctx) { + StackFrame *frame = exe_ctx->GetFramePtr(); + if (frame) { + if (DumpRegister(s, frame, eRegisterKindGeneric, LLDB_REGNUM_GENERIC_SP, + (lldb::Format)entry.number)) + return true; + } + } + return false; + + case Entry::Type::FrameRegisterFP: + if (exe_ctx) { + StackFrame *frame = exe_ctx->GetFramePtr(); + if (frame) { + if (DumpRegister(s, frame, eRegisterKindGeneric, LLDB_REGNUM_GENERIC_FP, + (lldb::Format)entry.number)) + return true; + } + } + return false; + + case Entry::Type::FrameRegisterFlags: + if (exe_ctx) { + StackFrame *frame = exe_ctx->GetFramePtr(); + if (frame) { + if (DumpRegister(s, frame, eRegisterKindGeneric, + LLDB_REGNUM_GENERIC_FLAGS, (lldb::Format)entry.number)) + return true; + } + } + return false; + + case Entry::Type::FrameNoDebug: + if (exe_ctx) { + StackFrame *frame = exe_ctx->GetFramePtr(); + if (frame) { + return !frame->HasDebugInformation(); + } + } + return true; + + case Entry::Type::FrameRegisterByName: + if (exe_ctx) { + StackFrame *frame = exe_ctx->GetFramePtr(); + if (frame) { + if (DumpRegister(s, frame, entry.string.c_str(), + (lldb::Format)entry.number)) + return true; + } + } + return false; + + case Entry::Type::FrameIsArtificial: { + if (exe_ctx) + if (StackFrame *frame = exe_ctx->GetFramePtr()) + return frame->IsArtificial(); + return false; + } + + case Entry::Type::ScriptFrame: + if (exe_ctx) { + StackFrame *frame = exe_ctx->GetFramePtr(); + if (frame) + return RunScriptFormatKeyword(s, sc, exe_ctx, frame, + entry.string.c_str()); + } + return false; + + case Entry::Type::FunctionID: + if (sc) { + if (sc->function) { + s.Printf("function{0x%8.8" PRIx64 "}", sc->function->GetID()); + return true; + } else if (sc->symbol) { + s.Printf("symbol[%u]", sc->symbol->GetID()); + return true; + } + } + return false; + + case Entry::Type::FunctionDidChange: + return function_changed; + + case Entry::Type::FunctionInitialFunction: + return initial_function; + + case Entry::Type::FunctionName: { + if (!sc) + return false; + + Language *language_plugin = nullptr; + bool language_plugin_handled = false; + StreamString ss; + + if (sc->function) + language_plugin = Language::FindPlugin(sc->function->GetLanguage()); + else if (sc->symbol) + language_plugin = Language::FindPlugin(sc->symbol->GetLanguage()); + + if (language_plugin) + language_plugin_handled = language_plugin->GetFunctionDisplayName( + sc, exe_ctx, Language::FunctionNameRepresentation::eName, ss); + + if (language_plugin_handled) { + s << ss.GetString(); + return true; + } else { + const char *name = nullptr; + if (sc->function) + name = sc->function->GetName().AsCString(nullptr); + else if (sc->symbol) + name = sc->symbol->GetName().AsCString(nullptr); + + if (name) { + s.PutCString(name); + FormatInlinedBlock(s, sc->block); + return true; + } + } + } + return false; + + case Entry::Type::FunctionNameNoArgs: { + if (!sc) + return false; + + Language *language_plugin = nullptr; + bool language_plugin_handled = false; + StreamString ss; + if (sc->function) + language_plugin = Language::FindPlugin(sc->function->GetLanguage()); + else if (sc->symbol) + language_plugin = Language::FindPlugin(sc->symbol->GetLanguage()); + + if (language_plugin) + language_plugin_handled = language_plugin->GetFunctionDisplayName( + sc, exe_ctx, Language::FunctionNameRepresentation::eNameWithNoArgs, + ss); + + if (language_plugin_handled) { + s << ss.GetString(); + return true; + } else { + ConstString name; + if (sc->function) + name = sc->function->GetNameNoArguments(); + else if (sc->symbol) + name = sc->symbol->GetNameNoArguments(); + if (name) { + s.PutCString(name.GetCString()); + FormatInlinedBlock(s, sc->block); + return true; + } + } + } + return false; + + case Entry::Type::FunctionNameWithArgs: { + if (!sc) + return false; + + Language *language_plugin = nullptr; + bool language_plugin_handled = false; + StreamString ss; + if (sc->function) + language_plugin = Language::FindPlugin(sc->function->GetLanguage()); + else if (sc->symbol) + language_plugin = Language::FindPlugin(sc->symbol->GetLanguage()); + + if (language_plugin) + language_plugin_handled = language_plugin->GetFunctionDisplayName( + sc, exe_ctx, Language::FunctionNameRepresentation::eNameWithArgs, ss); + + if (language_plugin_handled) { + s << ss.GetString(); + return true; + } else { + // Print the function name with arguments in it + if (sc->function) { + ExecutionContextScope *exe_scope = + exe_ctx ? exe_ctx->GetBestExecutionContextScope() : nullptr; + const char *cstr = sc->function->GetName().AsCString(nullptr); + if (cstr) { + const InlineFunctionInfo *inline_info = nullptr; + VariableListSP variable_list_sp; + bool get_function_vars = true; + if (sc->block) { + Block *inline_block = sc->block->GetContainingInlinedBlock(); + + if (inline_block) { + get_function_vars = false; + inline_info = inline_block->GetInlinedFunctionInfo(); + if (inline_info) + variable_list_sp = inline_block->GetBlockVariableList(true); + } + } + + if (get_function_vars) { + variable_list_sp = + sc->function->GetBlock(true).GetBlockVariableList(true); + } + + if (inline_info) { + s.PutCString(cstr); + s.PutCString(" [inlined] "); + cstr = inline_info->GetName().GetCString(); + } + + VariableList args; + if (variable_list_sp) + variable_list_sp->AppendVariablesWithScope( + eValueTypeVariableArgument, args); + if (args.GetSize() > 0) { + PrettyPrintFunctionNameWithArgs(s, cstr, exe_scope, args); + } else { + s.PutCString(cstr); + } + return true; + } + } else if (sc->symbol) { + const char *cstr = sc->symbol->GetName().AsCString(nullptr); + if (cstr) { + s.PutCString(cstr); + return true; + } + } + } + } + return false; + + case Entry::Type::FunctionMangledName: { + if (!sc) + return false; + + const char *name = nullptr; + if (sc->symbol) + name = + sc->symbol->GetMangled().GetName(Mangled::ePreferMangled).AsCString(); + else if (sc->function) + name = sc->function->GetMangled() + .GetName(Mangled::ePreferMangled) + .AsCString(); + + if (!name) + return false; + s.PutCString(name); + FormatInlinedBlock(s, sc->block); + return true; + } + case Entry::Type::FunctionAddrOffset: + if (addr) { + if (DumpAddressOffsetFromFunction(s, sc, exe_ctx, *addr, false, false, + false)) + return true; + } + return false; + + case Entry::Type::FunctionAddrOffsetConcrete: + if (addr) { + if (DumpAddressOffsetFromFunction(s, sc, exe_ctx, *addr, true, true, + true)) + return true; + } + return false; + + case Entry::Type::FunctionLineOffset: + if (sc) + return (DumpAddressOffsetFromFunction( + s, sc, exe_ctx, sc->line_entry.range.GetBaseAddress(), false, false, + false)); + return false; + + case Entry::Type::FunctionPCOffset: + if (exe_ctx) { + StackFrame *frame = exe_ctx->GetFramePtr(); + if (frame) { + if (DumpAddressOffsetFromFunction(s, sc, exe_ctx, + frame->GetFrameCodeAddress(), false, + false, false)) + return true; + } + } + return false; + + case Entry::Type::FunctionChanged: + return function_changed; + + case Entry::Type::FunctionIsOptimized: { + bool is_optimized = false; + if (sc && sc->function && sc->function->GetIsOptimized()) { + is_optimized = true; + } + return is_optimized; + } + + case Entry::Type::FunctionInitial: + return initial_function; + + case Entry::Type::LineEntryFile: + if (sc && sc->line_entry.IsValid()) { + Module *module = sc->module_sp.get(); + if (module) { + if (DumpFile(s, sc->line_entry.GetFile(), (FileKind)entry.number)) + return true; + } + } + return false; + + case Entry::Type::LineEntryLineNumber: + if (sc && sc->line_entry.IsValid()) { + const char *format = "%" PRIu32; + if (!entry.printf_format.empty()) + format = entry.printf_format.c_str(); + s.Printf(format, sc->line_entry.line); + return true; + } + return false; + + case Entry::Type::LineEntryColumn: + if (sc && sc->line_entry.IsValid() && sc->line_entry.column) { + const char *format = "%" PRIu32; + if (!entry.printf_format.empty()) + format = entry.printf_format.c_str(); + s.Printf(format, sc->line_entry.column); + return true; + } + return false; + + case Entry::Type::LineEntryStartAddress: + case Entry::Type::LineEntryEndAddress: + if (sc && sc->line_entry.range.GetBaseAddress().IsValid()) { + Address addr = sc->line_entry.range.GetBaseAddress(); + + if (entry.type == Entry::Type::LineEntryEndAddress) + addr.Slide(sc->line_entry.range.GetByteSize()); + if (DumpAddressAndContent(s, sc, exe_ctx, addr, false)) + return true; + } + return false; + + case Entry::Type::CurrentPCArrow: + if (addr && exe_ctx && exe_ctx->GetFramePtr()) { + RegisterContextSP reg_ctx = + exe_ctx->GetFramePtr()->GetRegisterContextSP(); + if (reg_ctx) { + addr_t pc_loadaddr = reg_ctx->GetPC(); + if (pc_loadaddr != LLDB_INVALID_ADDRESS) { + Address pc; + pc.SetLoadAddress(pc_loadaddr, exe_ctx->GetTargetPtr()); + if (pc == *addr) { + s.Printf("-> "); + return true; + } + } + } + s.Printf(" "); + return true; + } + return false; + } + return false; +} + +static bool DumpCommaSeparatedChildEntryNames(Stream &s, + const Definition *parent) { + if (parent->children) { + const size_t n = parent->num_children; + for (size_t i = 0; i < n; ++i) { + if (i > 0) + s.PutCString(", "); + s.Printf("\"%s\"", parent->children[i].name); + } + return true; + } + return false; +} + +static Status ParseEntry(const llvm::StringRef &format_str, + const Definition *parent, FormatEntity::Entry &entry) { + Status error; + + const size_t sep_pos = format_str.find_first_of(".[:"); + const char sep_char = + (sep_pos == llvm::StringRef::npos) ? '\0' : format_str[sep_pos]; + llvm::StringRef key = format_str.substr(0, sep_pos); + + const size_t n = parent->num_children; + for (size_t i = 0; i < n; ++i) { + const Definition *entry_def = parent->children + i; + if (key == entry_def->name || entry_def->name[0] == '*') { + llvm::StringRef value; + if (sep_char) + value = + format_str.substr(sep_pos + (entry_def->keep_separator ? 0 : 1)); + switch (entry_def->type) { + case FormatEntity::Entry::Type::ParentString: + entry.string = format_str.str(); + return error; // Success + + case FormatEntity::Entry::Type::ParentNumber: + entry.number = entry_def->data; + return error; // Success + + case FormatEntity::Entry::Type::EscapeCode: + entry.type = entry_def->type; + entry.string = entry_def->string; + return error; // Success + + default: + entry.type = entry_def->type; + break; + } + + if (value.empty()) { + if (entry_def->type == FormatEntity::Entry::Type::Invalid) { + if (entry_def->children) { + StreamString error_strm; + error_strm.Printf("'%s' can't be specified on its own, you must " + "access one of its children: ", + entry_def->name); + DumpCommaSeparatedChildEntryNames(error_strm, entry_def); + error.SetErrorStringWithFormat("%s", error_strm.GetData()); + } else if (sep_char == ':') { + // Any value whose separator is a with a ':' means this value has a + // string argument that needs to be stored in the entry (like + // "${script.var:}"). In this case the string value is the empty + // string which is ok. + } else { + error.SetErrorStringWithFormat("%s", "invalid entry definitions"); + } + } + } else { + if (entry_def->children) { + error = ParseEntry(value, entry_def, entry); + } else if (sep_char == ':') { + // Any value whose separator is a with a ':' means this value has a + // string argument that needs to be stored in the entry (like + // "${script.var:modulename.function}") + entry.string = value.str(); + } else { + error.SetErrorStringWithFormat( + "'%s' followed by '%s' but it has no children", key.str().c_str(), + value.str().c_str()); + } + } + return error; + } + } + StreamString error_strm; + if (parent->type == FormatEntity::Entry::Type::Root) + error_strm.Printf( + "invalid top level item '%s'. Valid top level items are: ", + key.str().c_str()); + else + error_strm.Printf("invalid member '%s' in '%s'. Valid members are: ", + key.str().c_str(), parent->name); + DumpCommaSeparatedChildEntryNames(error_strm, parent); + error.SetErrorStringWithFormat("%s", error_strm.GetData()); + return error; +} + +static const Definition *FindEntry(const llvm::StringRef &format_str, + const Definition *parent, + llvm::StringRef &remainder) { + Status error; + + std::pair<llvm::StringRef, llvm::StringRef> p = format_str.split('.'); + const size_t n = parent->num_children; + for (size_t i = 0; i < n; ++i) { + const Definition *entry_def = parent->children + i; + if (p.first == entry_def->name || entry_def->name[0] == '*') { + if (p.second.empty()) { + if (format_str.back() == '.') + remainder = format_str.drop_front(format_str.size() - 1); + else + remainder = llvm::StringRef(); // Exact match + return entry_def; + } else { + if (entry_def->children) { + return FindEntry(p.second, entry_def, remainder); + } else { + remainder = p.second; + return entry_def; + } + } + } + } + remainder = format_str; + return parent; +} + +static Status ParseInternal(llvm::StringRef &format, Entry &parent_entry, + uint32_t depth) { + Status error; + while (!format.empty() && error.Success()) { + const size_t non_special_chars = format.find_first_of("${}\\"); + + if (non_special_chars == llvm::StringRef::npos) { + // No special characters, just string bytes so add them and we are done + parent_entry.AppendText(format); + return error; + } + + if (non_special_chars > 0) { + // We have a special character, so add all characters before these as a + // plain string + parent_entry.AppendText(format.substr(0, non_special_chars)); + format = format.drop_front(non_special_chars); + } + + switch (format[0]) { + case '\0': + return error; + + case '{': { + format = format.drop_front(); // Skip the '{' + Entry scope_entry(Entry::Type::Scope); + error = ParseInternal(format, scope_entry, depth + 1); + if (error.Fail()) + return error; + parent_entry.AppendEntry(std::move(scope_entry)); + } break; + + case '}': + if (depth == 0) + error.SetErrorString("unmatched '}' character"); + else + format = + format + .drop_front(); // Skip the '}' as we are at the end of the scope + return error; + + case '\\': { + format = format.drop_front(); // Skip the '\' character + if (format.empty()) { + error.SetErrorString( + "'\\' character was not followed by another character"); + return error; + } + + const char desens_char = format[0]; + format = format.drop_front(); // Skip the desensitized char character + switch (desens_char) { + case 'a': + parent_entry.AppendChar('\a'); + break; + case 'b': + parent_entry.AppendChar('\b'); + break; + case 'f': + parent_entry.AppendChar('\f'); + break; + case 'n': + parent_entry.AppendChar('\n'); + break; + case 'r': + parent_entry.AppendChar('\r'); + break; + case 't': + parent_entry.AppendChar('\t'); + break; + case 'v': + parent_entry.AppendChar('\v'); + break; + case '\'': + parent_entry.AppendChar('\''); + break; + case '\\': + parent_entry.AppendChar('\\'); + break; + case '0': + // 1 to 3 octal chars + { + // Make a string that can hold onto the initial zero char, up to 3 + // octal digits, and a terminating NULL. + char oct_str[5] = {0, 0, 0, 0, 0}; + + int i; + for (i = 0; (format[i] >= '0' && format[i] <= '7') && i < 4; ++i) + oct_str[i] = format[i]; + + // We don't want to consume the last octal character since the main + // for loop will do this for us, so we advance p by one less than i + // (even if i is zero) + format = format.drop_front(i); + unsigned long octal_value = ::strtoul(oct_str, nullptr, 8); + if (octal_value <= UINT8_MAX) { + parent_entry.AppendChar((char)octal_value); + } else { + error.SetErrorString("octal number is larger than a single byte"); + return error; + } + } + break; + + case 'x': + // hex number in the format + if (isxdigit(format[0])) { + // Make a string that can hold onto two hex chars plus a + // NULL terminator + char hex_str[3] = {0, 0, 0}; + hex_str[0] = format[0]; + + format = format.drop_front(); + + if (isxdigit(format[0])) { + hex_str[1] = format[0]; + format = format.drop_front(); + } + + unsigned long hex_value = strtoul(hex_str, nullptr, 16); + if (hex_value <= UINT8_MAX) { + parent_entry.AppendChar((char)hex_value); + } else { + error.SetErrorString("hex number is larger than a single byte"); + return error; + } + } else { + parent_entry.AppendChar(desens_char); + } + break; + + default: + // Just desensitize any other character by just printing what came + // after the '\' + parent_entry.AppendChar(desens_char); + break; + } + } break; + + case '$': + format = format.drop_front(); // Skip the '$' + if (format.empty() || format.front() != '{') { + // Print '$' when not followed by '{'. + parent_entry.AppendText("$"); + } else { + format = format.drop_front(); // Skip the '{' + + llvm::StringRef variable, variable_format; + error = FormatEntity::ExtractVariableInfo(format, variable, + variable_format); + if (error.Fail()) + return error; + bool verify_is_thread_id = false; + Entry entry; + if (!variable_format.empty()) { + entry.printf_format = variable_format.str(); + + // If the format contains a '%' we are going to assume this is a + // printf style format. So if you want to format your thread ID + // using "0x%llx" you can use: ${thread.id%0x%llx} + // + // If there is no '%' in the format, then it is assumed to be a + // LLDB format name, or one of the extended formats specified in + // the switch statement below. + + if (entry.printf_format.find('%') == std::string::npos) { + bool clear_printf = false; + + if (entry.printf_format.size() == 1) { + switch (entry.printf_format[0]) { + case '@': // if this is an @ sign, print ObjC description + entry.number = ValueObject:: + eValueObjectRepresentationStyleLanguageSpecific; + clear_printf = true; + break; + case 'V': // if this is a V, print the value using the default + // format + entry.number = + ValueObject::eValueObjectRepresentationStyleValue; + clear_printf = true; + break; + case 'L': // if this is an L, print the location of the value + entry.number = + ValueObject::eValueObjectRepresentationStyleLocation; + clear_printf = true; + break; + case 'S': // if this is an S, print the summary after all + entry.number = + ValueObject::eValueObjectRepresentationStyleSummary; + clear_printf = true; + break; + case '#': // if this is a '#', print the number of children + entry.number = + ValueObject::eValueObjectRepresentationStyleChildrenCount; + clear_printf = true; + break; + case 'T': // if this is a 'T', print the type + entry.number = ValueObject::eValueObjectRepresentationStyleType; + clear_printf = true; + break; + case 'N': // if this is a 'N', print the name + entry.number = ValueObject::eValueObjectRepresentationStyleName; + clear_printf = true; + break; + case '>': // if this is a '>', print the expression path + entry.number = + ValueObject::eValueObjectRepresentationStyleExpressionPath; + clear_printf = true; + break; + } + } + + if (entry.number == 0) { + if (FormatManager::GetFormatFromCString( + entry.printf_format.c_str(), entry.fmt)) { + clear_printf = true; + } else if (entry.printf_format == "tid") { + verify_is_thread_id = true; + } else { + error.SetErrorStringWithFormat("invalid format: '%s'", + entry.printf_format.c_str()); + return error; + } + } + + // Our format string turned out to not be a printf style format + // so lets clear the string + if (clear_printf) + entry.printf_format.clear(); + } + } + + // Check for dereferences + if (variable[0] == '*') { + entry.deref = true; + variable = variable.drop_front(); + } + + error = ParseEntry(variable, &g_root, entry); + if (error.Fail()) + return error; + + llvm::StringRef entry_string(entry.string); + if (entry_string.contains(':')) { + auto [_, llvm_format] = entry_string.split(':'); + if (!llvm_format.empty() && !LLVMFormatPattern.match(llvm_format)) { + error.SetErrorStringWithFormat("invalid llvm format: '%s'", + llvm_format.data()); + return error; + } + } + + if (verify_is_thread_id) { + if (entry.type != Entry::Type::ThreadID && + entry.type != Entry::Type::ThreadProtocolID) { + error.SetErrorString("the 'tid' format can only be used on " + "${thread.id} and ${thread.protocol_id}"); + } + } + + switch (entry.type) { + case Entry::Type::Variable: + case Entry::Type::VariableSynthetic: + if (entry.number == 0) { + if (entry.string.empty()) + entry.number = ValueObject::eValueObjectRepresentationStyleValue; + else + entry.number = + ValueObject::eValueObjectRepresentationStyleSummary; + } + break; + default: + // Make sure someone didn't try to dereference anything but ${var} + // or ${svar} + if (entry.deref) { + error.SetErrorStringWithFormat( + "${%s} can't be dereferenced, only ${var} and ${svar} can.", + variable.str().c_str()); + return error; + } + } + parent_entry.AppendEntry(std::move(entry)); + } + break; + } + } + return error; +} + +Status FormatEntity::ExtractVariableInfo(llvm::StringRef &format_str, + llvm::StringRef &variable_name, + llvm::StringRef &variable_format) { + Status error; + variable_name = llvm::StringRef(); + variable_format = llvm::StringRef(); + + const size_t paren_pos = format_str.find('}'); + if (paren_pos != llvm::StringRef::npos) { + const size_t percent_pos = format_str.find('%'); + if (percent_pos < paren_pos) { + if (percent_pos > 0) { + if (percent_pos > 1) + variable_name = format_str.substr(0, percent_pos); + variable_format = + format_str.substr(percent_pos + 1, paren_pos - (percent_pos + 1)); + } + } else { + variable_name = format_str.substr(0, paren_pos); + } + // Strip off elements and the formatting and the trailing '}' + format_str = format_str.substr(paren_pos + 1); + } else { + error.SetErrorStringWithFormat( + "missing terminating '}' character for '${%s'", + format_str.str().c_str()); + } + return error; +} + +bool FormatEntity::FormatFileSpec(const FileSpec &file_spec, Stream &s, + llvm::StringRef variable_name, + llvm::StringRef variable_format) { + if (variable_name.empty() || variable_name == ".fullpath") { + file_spec.Dump(s.AsRawOstream()); + return true; + } else if (variable_name == ".basename") { + s.PutCString(file_spec.GetFilename().GetStringRef()); + return true; + } else if (variable_name == ".dirname") { + s.PutCString(file_spec.GetFilename().GetStringRef()); + return true; + } + return false; +} + +static std::string MakeMatch(const llvm::StringRef &prefix, + const char *suffix) { + std::string match(prefix.str()); + match.append(suffix); + return match; +} + +static void AddMatches(const Definition *def, const llvm::StringRef &prefix, + const llvm::StringRef &match_prefix, + StringList &matches) { + const size_t n = def->num_children; + if (n > 0) { + for (size_t i = 0; i < n; ++i) { + std::string match = prefix.str(); + if (match_prefix.empty()) + matches.AppendString(MakeMatch(prefix, def->children[i].name)); + else if (strncmp(def->children[i].name, match_prefix.data(), + match_prefix.size()) == 0) + matches.AppendString( + MakeMatch(prefix, def->children[i].name + match_prefix.size())); + } + } +} + +void FormatEntity::AutoComplete(CompletionRequest &request) { + llvm::StringRef str = request.GetCursorArgumentPrefix(); + + const size_t dollar_pos = str.rfind('$'); + if (dollar_pos == llvm::StringRef::npos) + return; + + // Hitting TAB after $ at the end of the string add a "{" + if (dollar_pos == str.size() - 1) { + std::string match = str.str(); + match.append("{"); + request.AddCompletion(match); + return; + } + + if (str[dollar_pos + 1] != '{') + return; + + const size_t close_pos = str.find('}', dollar_pos + 2); + if (close_pos != llvm::StringRef::npos) + return; + + const size_t format_pos = str.find('%', dollar_pos + 2); + if (format_pos != llvm::StringRef::npos) + return; + + llvm::StringRef partial_variable(str.substr(dollar_pos + 2)); + if (partial_variable.empty()) { + // Suggest all top level entities as we are just past "${" + StringList new_matches; + AddMatches(&g_root, str, llvm::StringRef(), new_matches); + request.AddCompletions(new_matches); + return; + } + + // We have a partially specified variable, find it + llvm::StringRef remainder; + const Definition *entry_def = FindEntry(partial_variable, &g_root, remainder); + if (!entry_def) + return; + + const size_t n = entry_def->num_children; + + if (remainder.empty()) { + // Exact match + if (n > 0) { + // "${thread.info" <TAB> + request.AddCompletion(MakeMatch(str, ".")); + } else { + // "${thread.id" <TAB> + request.AddCompletion(MakeMatch(str, "}")); + } + } else if (remainder == ".") { + // "${thread." <TAB> + StringList new_matches; + AddMatches(entry_def, str, llvm::StringRef(), new_matches); + request.AddCompletions(new_matches); + } else { + // We have a partial match + // "${thre" <TAB> + StringList new_matches; + AddMatches(entry_def, str, remainder, new_matches); + request.AddCompletions(new_matches); + } +} + +void FormatEntity::PrettyPrintFunctionArguments( + Stream &out_stream, VariableList const &args, + ExecutionContextScope *exe_scope) { + const size_t num_args = args.GetSize(); + for (size_t arg_idx = 0; arg_idx < num_args; ++arg_idx) { + std::string buffer; + + VariableSP var_sp(args.GetVariableAtIndex(arg_idx)); + ValueObjectSP var_value_sp(ValueObjectVariable::Create(exe_scope, var_sp)); + StreamString ss; + llvm::StringRef var_representation; + const char *var_name = var_value_sp->GetName().GetCString(); + if (var_value_sp->GetCompilerType().IsValid()) { + if (exe_scope && exe_scope->CalculateTarget()) + var_value_sp = var_value_sp->GetQualifiedRepresentationIfAvailable( + exe_scope->CalculateTarget() + ->TargetProperties::GetPreferDynamicValue(), + exe_scope->CalculateTarget() + ->TargetProperties::GetEnableSyntheticValue()); + if (var_value_sp->GetCompilerType().IsAggregateType() && + DataVisualization::ShouldPrintAsOneLiner(*var_value_sp)) { + static StringSummaryFormat format(TypeSummaryImpl::Flags() + .SetHideItemNames(false) + .SetShowMembersOneLiner(true), + ""); + format.FormatObject(var_value_sp.get(), buffer, TypeSummaryOptions()); + var_representation = buffer; + } else + var_value_sp->DumpPrintableRepresentation( + ss, + ValueObject::ValueObjectRepresentationStyle:: + eValueObjectRepresentationStyleSummary, + eFormatDefault, + ValueObject::PrintableRepresentationSpecialCases::eAllow, false); + } + + if (!ss.GetString().empty()) + var_representation = ss.GetString(); + if (arg_idx > 0) + out_stream.PutCString(", "); + if (var_value_sp->GetError().Success()) { + if (!var_representation.empty()) + out_stream.Printf("%s=%s", var_name, var_representation.str().c_str()); + else + out_stream.Printf("%s=%s at %s", var_name, + var_value_sp->GetTypeName().GetCString(), + var_value_sp->GetLocationAsCString()); + } else + out_stream.Printf("%s=<unavailable>", var_name); + } +} + +Status FormatEntity::Parse(const llvm::StringRef &format_str, Entry &entry) { + entry.Clear(); + entry.type = Entry::Type::Root; + llvm::StringRef modifiable_format(format_str); + return ParseInternal(modifiable_format, entry, 0); +} diff --git a/contrib/llvm-project/lldb/source/Core/Highlighter.cpp b/contrib/llvm-project/lldb/source/Core/Highlighter.cpp new file mode 100644 index 000000000000..f49b778baba8 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/Highlighter.cpp @@ -0,0 +1,82 @@ +//===-- Highlighter.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/Core/Highlighter.h" + +#include "lldb/Target/Language.h" +#include "lldb/Utility/AnsiTerminal.h" +#include "lldb/Utility/StreamString.h" +#include <optional> + +using namespace lldb_private; +using namespace lldb_private::ansi; + +void HighlightStyle::ColorStyle::Apply(Stream &s, llvm::StringRef value) const { + s << m_prefix << value << m_suffix; +} + +void HighlightStyle::ColorStyle::Set(llvm::StringRef prefix, + llvm::StringRef suffix) { + m_prefix = FormatAnsiTerminalCodes(prefix); + m_suffix = FormatAnsiTerminalCodes(suffix); +} + +void DefaultHighlighter::Highlight(const HighlightStyle &options, + llvm::StringRef line, + std::optional<size_t> cursor_pos, + llvm::StringRef previous_lines, + Stream &s) const { + // If we don't have a valid cursor, then we just print the line as-is. + if (!cursor_pos || *cursor_pos >= line.size()) { + s << line; + return; + } + + // If we have a valid cursor, we have to apply the 'selected' style around + // the character below the cursor. + + // Split the line around the character which is below the cursor. + size_t column = *cursor_pos; + // Print the characters before the cursor. + s << line.substr(0, column); + // Print the selected character with the defined color codes. + options.selected.Apply(s, line.substr(column, 1)); + // Print the rest of the line. + s << line.substr(column + 1U); +} + +static HighlightStyle::ColorStyle GetColor(const char *c) { + return HighlightStyle::ColorStyle(c, "${ansi.normal}"); +} + +HighlightStyle HighlightStyle::MakeVimStyle() { + HighlightStyle result; + result.comment = GetColor("${ansi.fg.purple}"); + result.scalar_literal = GetColor("${ansi.fg.red}"); + result.keyword = GetColor("${ansi.fg.green}"); + return result; +} + +const Highlighter & +HighlighterManager::getHighlighterFor(lldb::LanguageType language_type, + llvm::StringRef path) const { + Language *language = lldb_private::Language::FindPlugin(language_type, path); + if (language && language->GetHighlighter()) + return *language->GetHighlighter(); + return m_default; +} + +std::string Highlighter::Highlight(const HighlightStyle &options, + llvm::StringRef line, + std::optional<size_t> cursor_pos, + llvm::StringRef previous_lines) const { + StreamString s; + Highlight(options, line, cursor_pos, previous_lines, s); + s.Flush(); + return s.GetString().str(); +} diff --git a/contrib/llvm-project/lldb/source/Core/IOHandler.cpp b/contrib/llvm-project/lldb/source/Core/IOHandler.cpp new file mode 100644 index 000000000000..695c2481e353 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/IOHandler.cpp @@ -0,0 +1,663 @@ +//===-- IOHandler.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/Core/IOHandler.h" + +#if defined(__APPLE__) +#include <deque> +#endif +#include <string> + +#include "lldb/Core/Debugger.h" +#include "lldb/Host/Config.h" +#include "lldb/Host/File.h" +#include "lldb/Host/StreamFile.h" +#include "lldb/Utility/AnsiTerminal.h" +#include "lldb/Utility/Predicate.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/StreamString.h" +#include "lldb/Utility/StringList.h" +#include "lldb/lldb-forward.h" + +#if LLDB_ENABLE_LIBEDIT +#include "lldb/Host/Editline.h" +#endif +#include "lldb/Interpreter/CommandCompletions.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "llvm/ADT/StringRef.h" + +#ifdef _WIN32 +#include "lldb/Host/windows/windows.h" +#endif + +#include <memory> +#include <mutex> +#include <optional> + +#include <cassert> +#include <cctype> +#include <cerrno> +#include <clocale> +#include <cstdint> +#include <cstdio> +#include <cstring> +#include <type_traits> + +using namespace lldb; +using namespace lldb_private; +using llvm::StringRef; + +IOHandler::IOHandler(Debugger &debugger, IOHandler::Type type) + : IOHandler(debugger, type, + FileSP(), // Adopt STDIN from top input reader + StreamFileSP(), // Adopt STDOUT from top input reader + StreamFileSP(), // Adopt STDERR from top input reader + 0 // Flags + + ) {} + +IOHandler::IOHandler(Debugger &debugger, IOHandler::Type type, + const lldb::FileSP &input_sp, + const lldb::StreamFileSP &output_sp, + const lldb::StreamFileSP &error_sp, uint32_t flags) + : m_debugger(debugger), m_input_sp(input_sp), m_output_sp(output_sp), + m_error_sp(error_sp), m_popped(false), m_flags(flags), m_type(type), + m_user_data(nullptr), m_done(false), m_active(false) { + // If any files are not specified, then adopt them from the top input reader. + if (!m_input_sp || !m_output_sp || !m_error_sp) + debugger.AdoptTopIOHandlerFilesIfInvalid(m_input_sp, m_output_sp, + m_error_sp); +} + +IOHandler::~IOHandler() = default; + +int IOHandler::GetInputFD() { + return (m_input_sp ? m_input_sp->GetDescriptor() : -1); +} + +int IOHandler::GetOutputFD() { + return (m_output_sp ? m_output_sp->GetFile().GetDescriptor() : -1); +} + +int IOHandler::GetErrorFD() { + return (m_error_sp ? m_error_sp->GetFile().GetDescriptor() : -1); +} + +FILE *IOHandler::GetInputFILE() { + return (m_input_sp ? m_input_sp->GetStream() : nullptr); +} + +FILE *IOHandler::GetOutputFILE() { + return (m_output_sp ? m_output_sp->GetFile().GetStream() : nullptr); +} + +FILE *IOHandler::GetErrorFILE() { + return (m_error_sp ? m_error_sp->GetFile().GetStream() : nullptr); +} + +FileSP IOHandler::GetInputFileSP() { return m_input_sp; } + +StreamFileSP IOHandler::GetOutputStreamFileSP() { return m_output_sp; } + +StreamFileSP IOHandler::GetErrorStreamFileSP() { return m_error_sp; } + +bool IOHandler::GetIsInteractive() { + return GetInputFileSP() ? GetInputFileSP()->GetIsInteractive() : false; +} + +bool IOHandler::GetIsRealTerminal() { + return GetInputFileSP() ? GetInputFileSP()->GetIsRealTerminal() : false; +} + +void IOHandler::SetPopped(bool b) { m_popped.SetValue(b, eBroadcastOnChange); } + +void IOHandler::WaitForPop() { m_popped.WaitForValueEqualTo(true); } + +void IOHandler::PrintAsync(const char *s, size_t len, bool is_stdout) { + std::lock_guard<std::recursive_mutex> guard(m_output_mutex); + lldb::StreamFileSP stream = is_stdout ? m_output_sp : m_error_sp; + stream->Write(s, len); + stream->Flush(); +} + +bool IOHandlerStack::PrintAsync(const char *s, size_t len, bool is_stdout) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + if (!m_top) + return false; + m_top->PrintAsync(s, len, is_stdout); + return true; +} + +IOHandlerConfirm::IOHandlerConfirm(Debugger &debugger, llvm::StringRef prompt, + bool default_response) + : IOHandlerEditline( + debugger, IOHandler::Type::Confirm, + nullptr, // nullptr editline_name means no history loaded/saved + llvm::StringRef(), // No prompt + llvm::StringRef(), // No continuation prompt + false, // Multi-line + false, // Don't colorize the prompt (i.e. the confirm message.) + 0, *this), + m_default_response(default_response), m_user_response(default_response) { + StreamString prompt_stream; + prompt_stream.PutCString(prompt); + if (m_default_response) + prompt_stream.Printf(": [Y/n] "); + else + prompt_stream.Printf(": [y/N] "); + + SetPrompt(prompt_stream.GetString()); +} + +IOHandlerConfirm::~IOHandlerConfirm() = default; + +void IOHandlerConfirm::IOHandlerComplete(IOHandler &io_handler, + CompletionRequest &request) { + if (request.GetRawCursorPos() != 0) + return; + request.AddCompletion(m_default_response ? "y" : "n"); +} + +void IOHandlerConfirm::IOHandlerInputComplete(IOHandler &io_handler, + std::string &line) { + if (line.empty()) { + // User just hit enter, set the response to the default + m_user_response = m_default_response; + io_handler.SetIsDone(true); + return; + } + + if (line.size() == 1) { + switch (line[0]) { + case 'y': + case 'Y': + m_user_response = true; + io_handler.SetIsDone(true); + return; + case 'n': + case 'N': + m_user_response = false; + io_handler.SetIsDone(true); + return; + default: + break; + } + } + + if (line == "yes" || line == "YES" || line == "Yes") { + m_user_response = true; + io_handler.SetIsDone(true); + } else if (line == "no" || line == "NO" || line == "No") { + m_user_response = false; + io_handler.SetIsDone(true); + } +} + +std::optional<std::string> +IOHandlerDelegate::IOHandlerSuggestion(IOHandler &io_handler, + llvm::StringRef line) { + return io_handler.GetDebugger() + .GetCommandInterpreter() + .GetAutoSuggestionForCommand(line); +} + +void IOHandlerDelegate::IOHandlerComplete(IOHandler &io_handler, + CompletionRequest &request) { + switch (m_completion) { + case Completion::None: + break; + case Completion::LLDBCommand: + io_handler.GetDebugger().GetCommandInterpreter().HandleCompletion(request); + break; + case Completion::Expression: + lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks( + io_handler.GetDebugger().GetCommandInterpreter(), + lldb::eVariablePathCompletion, request, nullptr); + break; + } +} + +IOHandlerEditline::IOHandlerEditline( + Debugger &debugger, IOHandler::Type type, + const char *editline_name, // Used for saving history files + llvm::StringRef prompt, llvm::StringRef continuation_prompt, + bool multi_line, bool color, uint32_t line_number_start, + IOHandlerDelegate &delegate) + : IOHandlerEditline(debugger, type, + FileSP(), // Inherit input from top input reader + StreamFileSP(), // Inherit output from top input reader + StreamFileSP(), // Inherit error from top input reader + 0, // Flags + editline_name, // Used for saving history files + prompt, continuation_prompt, multi_line, color, + line_number_start, delegate) {} + +IOHandlerEditline::IOHandlerEditline( + Debugger &debugger, IOHandler::Type type, const lldb::FileSP &input_sp, + const lldb::StreamFileSP &output_sp, const lldb::StreamFileSP &error_sp, + uint32_t flags, + const char *editline_name, // Used for saving history files + llvm::StringRef prompt, llvm::StringRef continuation_prompt, + bool multi_line, bool color, uint32_t line_number_start, + IOHandlerDelegate &delegate) + : IOHandler(debugger, type, input_sp, output_sp, error_sp, flags), +#if LLDB_ENABLE_LIBEDIT + m_editline_up(), +#endif + m_delegate(delegate), m_prompt(), m_continuation_prompt(), + m_current_lines_ptr(nullptr), m_base_line_number(line_number_start), + m_curr_line_idx(UINT32_MAX), m_multi_line(multi_line), m_color(color), + m_interrupt_exits(true) { + SetPrompt(prompt); + +#if LLDB_ENABLE_LIBEDIT + bool use_editline = false; + + use_editline = GetInputFILE() && GetOutputFILE() && GetErrorFILE() && + m_input_sp && m_input_sp->GetIsRealTerminal(); + + if (use_editline) { + m_editline_up = std::make_unique<Editline>(editline_name, GetInputFILE(), + GetOutputFILE(), GetErrorFILE(), + GetOutputMutex()); + m_editline_up->SetIsInputCompleteCallback( + [this](Editline *editline, StringList &lines) { + return this->IsInputCompleteCallback(editline, lines); + }); + + m_editline_up->SetAutoCompleteCallback([this](CompletionRequest &request) { + this->AutoCompleteCallback(request); + }); + + if (debugger.GetUseAutosuggestion()) { + m_editline_up->SetSuggestionCallback([this](llvm::StringRef line) { + return this->SuggestionCallback(line); + }); + if (m_color) { + m_editline_up->SetSuggestionAnsiPrefix(ansi::FormatAnsiTerminalCodes( + debugger.GetAutosuggestionAnsiPrefix())); + m_editline_up->SetSuggestionAnsiSuffix(ansi::FormatAnsiTerminalCodes( + debugger.GetAutosuggestionAnsiSuffix())); + } + } + // See if the delegate supports fixing indentation + const char *indent_chars = delegate.IOHandlerGetFixIndentationCharacters(); + if (indent_chars) { + // The delegate does support indentation, hook it up so when any + // indentation character is typed, the delegate gets a chance to fix it + FixIndentationCallbackType f = [this](Editline *editline, + const StringList &lines, + int cursor_position) { + return this->FixIndentationCallback(editline, lines, cursor_position); + }; + m_editline_up->SetFixIndentationCallback(std::move(f), indent_chars); + } + } +#endif + SetBaseLineNumber(m_base_line_number); + SetPrompt(prompt); + SetContinuationPrompt(continuation_prompt); +} + +IOHandlerEditline::~IOHandlerEditline() { +#if LLDB_ENABLE_LIBEDIT + m_editline_up.reset(); +#endif +} + +void IOHandlerEditline::Activate() { + IOHandler::Activate(); + m_delegate.IOHandlerActivated(*this, GetIsInteractive()); +} + +void IOHandlerEditline::Deactivate() { + IOHandler::Deactivate(); + m_delegate.IOHandlerDeactivated(*this); +} + +void IOHandlerEditline::TerminalSizeChanged() { +#if LLDB_ENABLE_LIBEDIT + if (m_editline_up) + m_editline_up->TerminalSizeChanged(); +#endif +} + +// Split out a line from the buffer, if there is a full one to get. +static std::optional<std::string> SplitLine(std::string &line_buffer) { + size_t pos = line_buffer.find('\n'); + if (pos == std::string::npos) + return std::nullopt; + std::string line = + std::string(StringRef(line_buffer.c_str(), pos).rtrim("\n\r")); + line_buffer = line_buffer.substr(pos + 1); + return line; +} + +// If the final line of the file ends without a end-of-line, return +// it as a line anyway. +static std::optional<std::string> SplitLineEOF(std::string &line_buffer) { + if (llvm::all_of(line_buffer, llvm::isSpace)) + return std::nullopt; + std::string line = std::move(line_buffer); + line_buffer.clear(); + return line; +} + +bool IOHandlerEditline::GetLine(std::string &line, bool &interrupted) { +#if LLDB_ENABLE_LIBEDIT + if (m_editline_up) { + return m_editline_up->GetLine(line, interrupted); + } +#endif + + line.clear(); + + if (GetIsInteractive()) { + const char *prompt = nullptr; + + if (m_multi_line && m_curr_line_idx > 0) + prompt = GetContinuationPrompt(); + + if (prompt == nullptr) + prompt = GetPrompt(); + + if (prompt && prompt[0]) { + if (m_output_sp) { + m_output_sp->Printf("%s", prompt); + m_output_sp->Flush(); + } + } + } + + std::optional<std::string> got_line = SplitLine(m_line_buffer); + + if (!got_line && !m_input_sp) { + // No more input file, we are done... + SetIsDone(true); + return false; + } + + FILE *in = GetInputFILE(); + char buffer[256]; + + if (!got_line && !in && m_input_sp) { + // there is no FILE*, fall back on just reading bytes from the stream. + while (!got_line) { + size_t bytes_read = sizeof(buffer); + Status error = m_input_sp->Read((void *)buffer, bytes_read); + if (error.Success() && !bytes_read) { + got_line = SplitLineEOF(m_line_buffer); + break; + } + if (error.Fail()) + break; + m_line_buffer += StringRef(buffer, bytes_read); + got_line = SplitLine(m_line_buffer); + } + } + + if (!got_line && in) { + while (!got_line) { + char *r = fgets(buffer, sizeof(buffer), in); +#ifdef _WIN32 + // ReadFile on Windows is supposed to set ERROR_OPERATION_ABORTED + // according to the docs on MSDN. However, this has evidently been a + // known bug since Windows 8. Therefore, we can't detect if a signal + // interrupted in the fgets. So pressing ctrl-c causes the repl to end + // and the process to exit. A temporary workaround is just to attempt to + // fgets twice until this bug is fixed. + if (r == nullptr) + r = fgets(buffer, sizeof(buffer), in); + // this is the equivalent of EINTR for Windows + if (r == nullptr && GetLastError() == ERROR_OPERATION_ABORTED) + continue; +#endif + if (r == nullptr) { + if (ferror(in) && errno == EINTR) + continue; + if (feof(in)) + got_line = SplitLineEOF(m_line_buffer); + break; + } + m_line_buffer += buffer; + got_line = SplitLine(m_line_buffer); + } + } + + if (got_line) { + line = *got_line; + } + + return (bool)got_line; +} + +#if LLDB_ENABLE_LIBEDIT +bool IOHandlerEditline::IsInputCompleteCallback(Editline *editline, + StringList &lines) { + return m_delegate.IOHandlerIsInputComplete(*this, lines); +} + +int IOHandlerEditline::FixIndentationCallback(Editline *editline, + const StringList &lines, + int cursor_position) { + return m_delegate.IOHandlerFixIndentation(*this, lines, cursor_position); +} + +std::optional<std::string> +IOHandlerEditline::SuggestionCallback(llvm::StringRef line) { + return m_delegate.IOHandlerSuggestion(*this, line); +} + +void IOHandlerEditline::AutoCompleteCallback(CompletionRequest &request) { + m_delegate.IOHandlerComplete(*this, request); +} +#endif + +const char *IOHandlerEditline::GetPrompt() { +#if LLDB_ENABLE_LIBEDIT + if (m_editline_up) { + return m_editline_up->GetPrompt(); + } else { +#endif + if (m_prompt.empty()) + return nullptr; +#if LLDB_ENABLE_LIBEDIT + } +#endif + return m_prompt.c_str(); +} + +bool IOHandlerEditline::SetPrompt(llvm::StringRef prompt) { + m_prompt = std::string(prompt); + +#if LLDB_ENABLE_LIBEDIT + if (m_editline_up) { + m_editline_up->SetPrompt(m_prompt.empty() ? nullptr : m_prompt.c_str()); + if (m_color) { + m_editline_up->SetPromptAnsiPrefix( + ansi::FormatAnsiTerminalCodes(m_debugger.GetPromptAnsiPrefix())); + m_editline_up->SetPromptAnsiSuffix( + ansi::FormatAnsiTerminalCodes(m_debugger.GetPromptAnsiSuffix())); + } + } +#endif + return true; +} + +const char *IOHandlerEditline::GetContinuationPrompt() { + return (m_continuation_prompt.empty() ? nullptr + : m_continuation_prompt.c_str()); +} + +void IOHandlerEditline::SetContinuationPrompt(llvm::StringRef prompt) { + m_continuation_prompt = std::string(prompt); + +#if LLDB_ENABLE_LIBEDIT + if (m_editline_up) + m_editline_up->SetContinuationPrompt(m_continuation_prompt.empty() + ? nullptr + : m_continuation_prompt.c_str()); +#endif +} + +void IOHandlerEditline::SetBaseLineNumber(uint32_t line) { + m_base_line_number = line; +} + +uint32_t IOHandlerEditline::GetCurrentLineIndex() const { +#if LLDB_ENABLE_LIBEDIT + if (m_editline_up) + return m_editline_up->GetCurrentLine(); +#endif + return m_curr_line_idx; +} + +StringList IOHandlerEditline::GetCurrentLines() const { +#if LLDB_ENABLE_LIBEDIT + if (m_editline_up) + return m_editline_up->GetInputAsStringList(); +#endif + // When libedit is not used, the current lines can be gotten from + // `m_current_lines_ptr`, which is updated whenever a new line is processed. + // This doesn't happen when libedit is used, in which case + // `m_current_lines_ptr` is only updated when the full input is terminated. + + if (m_current_lines_ptr) + return *m_current_lines_ptr; + return StringList(); +} + +bool IOHandlerEditline::GetLines(StringList &lines, bool &interrupted) { + m_current_lines_ptr = &lines; + + bool success = false; +#if LLDB_ENABLE_LIBEDIT + if (m_editline_up) { + return m_editline_up->GetLines(m_base_line_number, lines, interrupted); + } else { +#endif + bool done = false; + Status error; + + while (!done) { + // Show line numbers if we are asked to + std::string line; + if (m_base_line_number > 0 && GetIsInteractive()) { + if (m_output_sp) { + m_output_sp->Printf("%u%s", + m_base_line_number + (uint32_t)lines.GetSize(), + GetPrompt() == nullptr ? " " : ""); + } + } + + m_curr_line_idx = lines.GetSize(); + + bool interrupted = false; + if (GetLine(line, interrupted) && !interrupted) { + lines.AppendString(line); + done = m_delegate.IOHandlerIsInputComplete(*this, lines); + } else { + done = true; + } + } + success = lines.GetSize() > 0; +#if LLDB_ENABLE_LIBEDIT + } +#endif + return success; +} + +// 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 IOHandlerEditline::Run() { + std::string line; + while (IsActive()) { + bool interrupted = false; + if (m_multi_line) { + StringList lines; + if (GetLines(lines, interrupted)) { + if (interrupted) { + m_done = m_interrupt_exits; + m_delegate.IOHandlerInputInterrupted(*this, line); + + } else { + line = lines.CopyList(); + m_delegate.IOHandlerInputComplete(*this, line); + } + } else { + m_done = true; + } + } else { + if (GetLine(line, interrupted)) { + if (interrupted) + m_delegate.IOHandlerInputInterrupted(*this, line); + else + m_delegate.IOHandlerInputComplete(*this, line); + } else { + m_done = true; + } + } + } +} + +void IOHandlerEditline::Cancel() { +#if LLDB_ENABLE_LIBEDIT + if (m_editline_up) + m_editline_up->Cancel(); +#endif +} + +bool IOHandlerEditline::Interrupt() { + // Let the delgate handle it first + if (m_delegate.IOHandlerInterrupt(*this)) + return true; + +#if LLDB_ENABLE_LIBEDIT + if (m_editline_up) + return m_editline_up->Interrupt(); +#endif + return false; +} + +void IOHandlerEditline::GotEOF() { +#if LLDB_ENABLE_LIBEDIT + if (m_editline_up) + m_editline_up->Interrupt(); +#endif +} + +void IOHandlerEditline::PrintAsync(const char *s, size_t len, bool is_stdout) { +#if LLDB_ENABLE_LIBEDIT + if (m_editline_up) { + std::lock_guard<std::recursive_mutex> guard(m_output_mutex); + lldb::StreamFileSP stream = is_stdout ? m_output_sp : m_error_sp; + m_editline_up->PrintAsync(stream.get(), s, len); + } else +#endif + { +#ifdef _WIN32 + const char *prompt = GetPrompt(); + if (prompt) { + // Back up over previous prompt using Windows API + CONSOLE_SCREEN_BUFFER_INFO screen_buffer_info; + HANDLE console_handle = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleScreenBufferInfo(console_handle, &screen_buffer_info); + COORD coord = screen_buffer_info.dwCursorPosition; + coord.X -= strlen(prompt); + if (coord.X < 0) + coord.X = 0; + SetConsoleCursorPosition(console_handle, coord); + } +#endif + IOHandler::PrintAsync(s, len, is_stdout); +#ifdef _WIN32 + if (prompt) + IOHandler::PrintAsync(prompt, strlen(prompt), is_stdout); +#endif + } +} diff --git a/contrib/llvm-project/lldb/source/Core/IOHandlerCursesGUI.cpp b/contrib/llvm-project/lldb/source/Core/IOHandlerCursesGUI.cpp new file mode 100644 index 000000000000..d922d32f9105 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/IOHandlerCursesGUI.cpp @@ -0,0 +1,7756 @@ +//===-- IOHandlerCursesGUI.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/Core/IOHandlerCursesGUI.h" +#include "lldb/Host/Config.h" + +#if LLDB_ENABLE_CURSES +#if CURSES_HAVE_NCURSES_CURSES_H +#include <ncurses/curses.h> +#include <ncurses/panel.h> +#else +#include <curses.h> +#include <panel.h> +#endif +#endif + +#if defined(__APPLE__) +#include <deque> +#endif +#include <string> + +#include "lldb/Core/Debugger.h" +#include "lldb/Core/ValueObjectUpdater.h" +#include "lldb/Host/File.h" +#include "lldb/Utility/AnsiTerminal.h" +#include "lldb/Utility/Predicate.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/StreamString.h" +#include "lldb/Utility/StringList.h" +#include "lldb/lldb-forward.h" + +#include "lldb/Interpreter/CommandCompletions.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/OptionGroupPlatform.h" + +#if LLDB_ENABLE_CURSES +#include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/ValueObject.h" +#include "lldb/Core/ValueObjectRegister.h" +#include "lldb/Symbol/Block.h" +#include "lldb/Symbol/CompileUnit.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Symbol/VariableList.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Target/StopInfo.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/State.h" +#endif + +#include "llvm/ADT/StringRef.h" + +#ifdef _WIN32 +#include "lldb/Host/windows/windows.h" +#endif + +#include <memory> +#include <mutex> + +#include <cassert> +#include <cctype> +#include <cerrno> +#include <cstdint> +#include <cstdio> +#include <cstring> +#include <functional> +#include <optional> +#include <type_traits> + +using namespace lldb; +using namespace lldb_private; +using llvm::StringRef; + +// we may want curses to be disabled for some builds for instance, windows +#if LLDB_ENABLE_CURSES + +#define KEY_CTRL_A 1 +#define KEY_CTRL_E 5 +#define KEY_CTRL_K 11 +#define KEY_RETURN 10 +#define KEY_ESCAPE 27 +#define KEY_DELETE 127 + +#define KEY_SHIFT_TAB (KEY_MAX + 1) +#define KEY_ALT_ENTER (KEY_MAX + 2) + +namespace curses { +class Menu; +class MenuDelegate; +class Window; +class WindowDelegate; +typedef std::shared_ptr<Menu> MenuSP; +typedef std::shared_ptr<MenuDelegate> MenuDelegateSP; +typedef std::shared_ptr<Window> WindowSP; +typedef std::shared_ptr<WindowDelegate> WindowDelegateSP; +typedef std::vector<MenuSP> Menus; +typedef std::vector<WindowSP> Windows; +typedef std::vector<WindowDelegateSP> WindowDelegates; + +#if 0 +type summary add -s "x=${var.x}, y=${var.y}" curses::Point +type summary add -s "w=${var.width}, h=${var.height}" curses::Size +type summary add -s "${var.origin%S} ${var.size%S}" curses::Rect +#endif + +struct Point { + int x; + int y; + + Point(int _x = 0, int _y = 0) : x(_x), y(_y) {} + + void Clear() { + x = 0; + y = 0; + } + + Point &operator+=(const Point &rhs) { + x += rhs.x; + y += rhs.y; + return *this; + } + + void Dump() { printf("(x=%i, y=%i)\n", x, y); } +}; + +bool operator==(const Point &lhs, const Point &rhs) { + return lhs.x == rhs.x && lhs.y == rhs.y; +} + +bool operator!=(const Point &lhs, const Point &rhs) { + return lhs.x != rhs.x || lhs.y != rhs.y; +} + +struct Size { + int width; + int height; + Size(int w = 0, int h = 0) : width(w), height(h) {} + + void Clear() { + width = 0; + height = 0; + } + + void Dump() { printf("(w=%i, h=%i)\n", width, height); } +}; + +bool operator==(const Size &lhs, const Size &rhs) { + return lhs.width == rhs.width && lhs.height == rhs.height; +} + +bool operator!=(const Size &lhs, const Size &rhs) { + return lhs.width != rhs.width || lhs.height != rhs.height; +} + +struct Rect { + Point origin; + Size size; + + Rect() : origin(), size() {} + + Rect(const Point &p, const Size &s) : origin(p), size(s) {} + + void Clear() { + origin.Clear(); + size.Clear(); + } + + void Dump() { + printf("(x=%i, y=%i), w=%i, h=%i)\n", origin.x, origin.y, size.width, + size.height); + } + + void Inset(int w, int h) { + if (size.width > w * 2) + size.width -= w * 2; + origin.x += w; + + if (size.height > h * 2) + size.height -= h * 2; + origin.y += h; + } + + // Return a status bar rectangle which is the last line of this rectangle. + // This rectangle will be modified to not include the status bar area. + Rect MakeStatusBar() { + Rect status_bar; + if (size.height > 1) { + status_bar.origin.x = origin.x; + status_bar.origin.y = size.height; + status_bar.size.width = size.width; + status_bar.size.height = 1; + --size.height; + } + return status_bar; + } + + // Return a menubar rectangle which is the first line of this rectangle. This + // rectangle will be modified to not include the menubar area. + Rect MakeMenuBar() { + Rect menubar; + if (size.height > 1) { + menubar.origin.x = origin.x; + menubar.origin.y = origin.y; + menubar.size.width = size.width; + menubar.size.height = 1; + ++origin.y; + --size.height; + } + return menubar; + } + + void HorizontalSplitPercentage(float top_percentage, Rect &top, + Rect &bottom) const { + float top_height = top_percentage * size.height; + HorizontalSplit(top_height, top, bottom); + } + + void HorizontalSplit(int top_height, Rect &top, Rect &bottom) const { + top = *this; + if (top_height < size.height) { + top.size.height = top_height; + bottom.origin.x = origin.x; + bottom.origin.y = origin.y + top.size.height; + bottom.size.width = size.width; + bottom.size.height = size.height - top.size.height; + } else { + bottom.Clear(); + } + } + + void VerticalSplitPercentage(float left_percentage, Rect &left, + Rect &right) const { + float left_width = left_percentage * size.width; + VerticalSplit(left_width, left, right); + } + + void VerticalSplit(int left_width, Rect &left, Rect &right) const { + left = *this; + if (left_width < size.width) { + left.size.width = left_width; + right.origin.x = origin.x + left.size.width; + right.origin.y = origin.y; + right.size.width = size.width - left.size.width; + right.size.height = size.height; + } else { + right.Clear(); + } + } +}; + +bool operator==(const Rect &lhs, const Rect &rhs) { + return lhs.origin == rhs.origin && lhs.size == rhs.size; +} + +bool operator!=(const Rect &lhs, const Rect &rhs) { + return lhs.origin != rhs.origin || lhs.size != rhs.size; +} + +enum HandleCharResult { + eKeyNotHandled = 0, + eKeyHandled = 1, + eQuitApplication = 2 +}; + +enum class MenuActionResult { + Handled, + NotHandled, + Quit // Exit all menus and quit +}; + +struct KeyHelp { + int ch; + const char *description; +}; + +// COLOR_PAIR index names +enum { + // First 16 colors are 8 black background and 8 blue background colors, + // needed by OutputColoredStringTruncated(). + BlackOnBlack = 1, + RedOnBlack, + GreenOnBlack, + YellowOnBlack, + BlueOnBlack, + MagentaOnBlack, + CyanOnBlack, + WhiteOnBlack, + BlackOnBlue, + RedOnBlue, + GreenOnBlue, + YellowOnBlue, + BlueOnBlue, + MagentaOnBlue, + CyanOnBlue, + WhiteOnBlue, + // Other colors, as needed. + BlackOnWhite, + MagentaOnWhite, + LastColorPairIndex = MagentaOnWhite +}; + +class WindowDelegate { +public: + virtual ~WindowDelegate() = default; + + virtual bool WindowDelegateDraw(Window &window, bool force) { + return false; // Drawing not handled + } + + virtual HandleCharResult WindowDelegateHandleChar(Window &window, int key) { + return eKeyNotHandled; + } + + virtual const char *WindowDelegateGetHelpText() { return nullptr; } + + virtual KeyHelp *WindowDelegateGetKeyHelp() { return nullptr; } +}; + +class HelpDialogDelegate : public WindowDelegate { +public: + HelpDialogDelegate(const char *text, KeyHelp *key_help_array); + + ~HelpDialogDelegate() override; + + bool WindowDelegateDraw(Window &window, bool force) override; + + HandleCharResult WindowDelegateHandleChar(Window &window, int key) override; + + size_t GetNumLines() const { return m_text.GetSize(); } + + size_t GetMaxLineLength() const { return m_text.GetMaxStringLength(); } + +protected: + StringList m_text; + int m_first_visible_line = 0; +}; + +// A surface is an abstraction for something than can be drawn on. The surface +// have a width, a height, a cursor position, and a multitude of drawing +// operations. This type should be sub-classed to get an actually useful ncurses +// object, such as a Window or a Pad. +class Surface { +public: + enum class Type { Window, Pad }; + + Surface(Surface::Type type) : m_type(type) {} + + WINDOW *get() { return m_window; } + + operator WINDOW *() { return m_window; } + + Surface SubSurface(Rect bounds) { + Surface subSurface(m_type); + if (m_type == Type::Pad) + subSurface.m_window = + ::subpad(m_window, bounds.size.height, bounds.size.width, + bounds.origin.y, bounds.origin.x); + else + subSurface.m_window = + ::derwin(m_window, bounds.size.height, bounds.size.width, + bounds.origin.y, bounds.origin.x); + return subSurface; + } + + // Copy a region of the surface to another surface. + void CopyToSurface(Surface &target, Point source_origin, Point target_origin, + Size size) { + ::copywin(m_window, target.get(), source_origin.y, source_origin.x, + target_origin.y, target_origin.x, + target_origin.y + size.height - 1, + target_origin.x + size.width - 1, false); + } + + int GetCursorX() const { return getcurx(m_window); } + int GetCursorY() const { return getcury(m_window); } + void MoveCursor(int x, int y) { ::wmove(m_window, y, x); } + + void AttributeOn(attr_t attr) { ::wattron(m_window, attr); } + void AttributeOff(attr_t attr) { ::wattroff(m_window, attr); } + + int GetMaxX() const { return getmaxx(m_window); } + int GetMaxY() const { return getmaxy(m_window); } + int GetWidth() const { return GetMaxX(); } + int GetHeight() const { return GetMaxY(); } + Size GetSize() const { return Size(GetWidth(), GetHeight()); } + // Get a zero origin rectangle width the surface size. + Rect GetFrame() const { return Rect(Point(), GetSize()); } + + void Clear() { ::wclear(m_window); } + void Erase() { ::werase(m_window); } + + void SetBackground(int color_pair_idx) { + ::wbkgd(m_window, COLOR_PAIR(color_pair_idx)); + } + + void PutChar(int ch) { ::waddch(m_window, ch); } + void PutCString(const char *s, int len = -1) { ::waddnstr(m_window, s, len); } + + void PutCStringTruncated(int right_pad, const char *s, int len = -1) { + int bytes_left = GetWidth() - GetCursorX(); + if (bytes_left > right_pad) { + bytes_left -= right_pad; + ::waddnstr(m_window, s, len < 0 ? bytes_left : std::min(bytes_left, len)); + } + } + + void Printf(const char *format, ...) __attribute__((format(printf, 2, 3))) { + va_list args; + va_start(args, format); + vw_printw(m_window, format, args); + va_end(args); + } + + void PrintfTruncated(int right_pad, const char *format, ...) + __attribute__((format(printf, 3, 4))) { + va_list args; + va_start(args, format); + StreamString strm; + strm.PrintfVarArg(format, args); + va_end(args); + PutCStringTruncated(right_pad, strm.GetData()); + } + + void VerticalLine(int n, chtype v_char = ACS_VLINE) { + ::wvline(m_window, v_char, n); + } + void HorizontalLine(int n, chtype h_char = ACS_HLINE) { + ::whline(m_window, h_char, n); + } + void Box(chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) { + ::box(m_window, v_char, h_char); + } + + void TitledBox(const char *title, chtype v_char = ACS_VLINE, + chtype h_char = ACS_HLINE) { + Box(v_char, h_char); + int title_offset = 2; + MoveCursor(title_offset, 0); + PutChar('['); + PutCString(title, GetWidth() - title_offset); + PutChar(']'); + } + + void Box(const Rect &bounds, chtype v_char = ACS_VLINE, + chtype h_char = ACS_HLINE) { + MoveCursor(bounds.origin.x, bounds.origin.y); + VerticalLine(bounds.size.height); + HorizontalLine(bounds.size.width); + PutChar(ACS_ULCORNER); + + MoveCursor(bounds.origin.x + bounds.size.width - 1, bounds.origin.y); + VerticalLine(bounds.size.height); + PutChar(ACS_URCORNER); + + MoveCursor(bounds.origin.x, bounds.origin.y + bounds.size.height - 1); + HorizontalLine(bounds.size.width); + PutChar(ACS_LLCORNER); + + MoveCursor(bounds.origin.x + bounds.size.width - 1, + bounds.origin.y + bounds.size.height - 1); + PutChar(ACS_LRCORNER); + } + + void TitledBox(const Rect &bounds, const char *title, + chtype v_char = ACS_VLINE, chtype h_char = ACS_HLINE) { + Box(bounds, v_char, h_char); + int title_offset = 2; + MoveCursor(bounds.origin.x + title_offset, bounds.origin.y); + PutChar('['); + PutCString(title, bounds.size.width - title_offset); + PutChar(']'); + } + + // Curses doesn't allow direct output of color escape sequences, but that's + // how we get source lines from the Highligher class. Read the line and + // convert color escape sequences to curses color attributes. Use + // first_skip_count to skip leading visible characters. Returns false if all + // visible characters were skipped due to first_skip_count. + bool OutputColoredStringTruncated(int right_pad, StringRef string, + size_t skip_first_count, + bool use_blue_background) { + attr_t saved_attr; + short saved_pair; + bool result = false; + wattr_get(m_window, &saved_attr, &saved_pair, nullptr); + if (use_blue_background) + ::wattron(m_window, COLOR_PAIR(WhiteOnBlue)); + while (!string.empty()) { + size_t esc_pos = string.find(ANSI_ESC_START); + if (esc_pos == StringRef::npos) { + string = string.substr(skip_first_count); + if (!string.empty()) { + PutCStringTruncated(right_pad, string.data(), string.size()); + result = true; + } + break; + } + if (esc_pos > 0) { + if (skip_first_count > 0) { + int skip = std::min(esc_pos, skip_first_count); + string = string.substr(skip); + skip_first_count -= skip; + esc_pos -= skip; + } + if (esc_pos > 0) { + PutCStringTruncated(right_pad, string.data(), esc_pos); + result = true; + string = string.drop_front(esc_pos); + } + } + bool consumed = string.consume_front(ANSI_ESC_START); + assert(consumed); + UNUSED_IF_ASSERT_DISABLED(consumed); + // This is written to match our Highlighter classes, which seem to + // generate only foreground color escape sequences. If necessary, this + // will need to be extended. + // Only 8 basic foreground colors, underline and reset, our Highlighter + // doesn't use anything else. + int value; + if (!!string.consumeInteger(10, value) || // Returns false on success. + !(value == 0 || value == ANSI_CTRL_UNDERLINE || + (value >= ANSI_FG_COLOR_BLACK && value <= ANSI_FG_COLOR_WHITE))) { + llvm::errs() << "No valid color code in color escape sequence.\n"; + continue; + } + if (!string.consume_front(ANSI_ESC_END)) { + llvm::errs() << "Missing '" << ANSI_ESC_END + << "' in color escape sequence.\n"; + continue; + } + if (value == 0) { // Reset. + wattr_set(m_window, saved_attr, saved_pair, nullptr); + if (use_blue_background) + ::wattron(m_window, COLOR_PAIR(WhiteOnBlue)); + } else if (value == ANSI_CTRL_UNDERLINE) { + ::wattron(m_window, A_UNDERLINE); + } else { + // Mapped directly to first 16 color pairs (black/blue background). + ::wattron(m_window, COLOR_PAIR(value - ANSI_FG_COLOR_BLACK + 1 + + (use_blue_background ? 8 : 0))); + } + } + wattr_set(m_window, saved_attr, saved_pair, nullptr); + return result; + } + +protected: + Type m_type; + WINDOW *m_window = nullptr; +}; + +class Pad : public Surface { +public: + Pad(Size size) : Surface(Surface::Type::Pad) { + m_window = ::newpad(size.height, size.width); + } + + ~Pad() { ::delwin(m_window); } +}; + +class Window : public Surface { +public: + Window(const char *name) + : Surface(Surface::Type::Window), m_name(name), m_panel(nullptr), + m_parent(nullptr), m_subwindows(), m_delegate_sp(), + m_curr_active_window_idx(UINT32_MAX), + m_prev_active_window_idx(UINT32_MAX), m_delete(false), + m_needs_update(true), m_can_activate(true), m_is_subwin(false) {} + + Window(const char *name, WINDOW *w, bool del = true) + : Surface(Surface::Type::Window), m_name(name), m_panel(nullptr), + m_parent(nullptr), m_subwindows(), m_delegate_sp(), + m_curr_active_window_idx(UINT32_MAX), + m_prev_active_window_idx(UINT32_MAX), m_delete(del), + m_needs_update(true), m_can_activate(true), m_is_subwin(false) { + if (w) + Reset(w); + } + + Window(const char *name, const Rect &bounds) + : Surface(Surface::Type::Window), m_name(name), m_panel(nullptr), + m_parent(nullptr), m_subwindows(), m_delegate_sp(), + m_curr_active_window_idx(UINT32_MAX), + m_prev_active_window_idx(UINT32_MAX), m_delete(false), + m_needs_update(true), m_can_activate(true), m_is_subwin(false) { + Reset(::newwin(bounds.size.height, bounds.size.width, bounds.origin.y, + bounds.origin.y)); + } + + virtual ~Window() { + RemoveSubWindows(); + Reset(); + } + + void Reset(WINDOW *w = nullptr, bool del = true) { + if (m_window == w) + return; + + if (m_panel) { + ::del_panel(m_panel); + m_panel = nullptr; + } + if (m_window && m_delete) { + ::delwin(m_window); + m_window = nullptr; + m_delete = false; + } + if (w) { + m_window = w; + m_panel = ::new_panel(m_window); + m_delete = del; + } + } + + // Get the rectangle in our parent window + Rect GetBounds() const { return Rect(GetParentOrigin(), GetSize()); } + + Rect GetCenteredRect(int width, int height) { + Size size = GetSize(); + width = std::min(size.width, width); + height = std::min(size.height, height); + int x = (size.width - width) / 2; + int y = (size.height - height) / 2; + return Rect(Point(x, y), Size(width, height)); + } + + int GetChar() { return ::wgetch(m_window); } + Point GetParentOrigin() const { return Point(GetParentX(), GetParentY()); } + int GetParentX() const { return getparx(m_window); } + int GetParentY() const { return getpary(m_window); } + void MoveWindow(int x, int y) { MoveWindow(Point(x, y)); } + void Resize(int w, int h) { ::wresize(m_window, h, w); } + void Resize(const Size &size) { + ::wresize(m_window, size.height, size.width); + } + void MoveWindow(const Point &origin) { + const bool moving_window = origin != GetParentOrigin(); + if (m_is_subwin && moving_window) { + // Can't move subwindows, must delete and re-create + Size size = GetSize(); + Reset(::subwin(m_parent->m_window, size.height, size.width, origin.y, + origin.x), + true); + } else { + ::mvwin(m_window, origin.y, origin.x); + } + } + + void SetBounds(const Rect &bounds) { + const bool moving_window = bounds.origin != GetParentOrigin(); + if (m_is_subwin && moving_window) { + // Can't move subwindows, must delete and re-create + Reset(::subwin(m_parent->m_window, bounds.size.height, bounds.size.width, + bounds.origin.y, bounds.origin.x), + true); + } else { + if (moving_window) + MoveWindow(bounds.origin); + Resize(bounds.size); + } + } + + void Touch() { + ::touchwin(m_window); + if (m_parent) + m_parent->Touch(); + } + + WindowSP CreateSubWindow(const char *name, const Rect &bounds, + bool make_active) { + auto get_window = [this, &bounds]() { + return m_window + ? ::subwin(m_window, bounds.size.height, bounds.size.width, + bounds.origin.y, bounds.origin.x) + : ::newwin(bounds.size.height, bounds.size.width, + bounds.origin.y, bounds.origin.x); + }; + WindowSP subwindow_sp = std::make_shared<Window>(name, get_window(), true); + subwindow_sp->m_is_subwin = subwindow_sp.operator bool(); + subwindow_sp->m_parent = this; + if (make_active) { + m_prev_active_window_idx = m_curr_active_window_idx; + m_curr_active_window_idx = m_subwindows.size(); + } + m_subwindows.push_back(subwindow_sp); + ::top_panel(subwindow_sp->m_panel); + m_needs_update = true; + return subwindow_sp; + } + + bool RemoveSubWindow(Window *window) { + Windows::iterator pos, end = m_subwindows.end(); + size_t i = 0; + for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) { + if ((*pos).get() == window) { + if (m_prev_active_window_idx == i) + m_prev_active_window_idx = UINT32_MAX; + else if (m_prev_active_window_idx != UINT32_MAX && + m_prev_active_window_idx > i) + --m_prev_active_window_idx; + + if (m_curr_active_window_idx == i) + m_curr_active_window_idx = UINT32_MAX; + else if (m_curr_active_window_idx != UINT32_MAX && + m_curr_active_window_idx > i) + --m_curr_active_window_idx; + window->Erase(); + m_subwindows.erase(pos); + m_needs_update = true; + if (m_parent) + m_parent->Touch(); + else + ::touchwin(stdscr); + return true; + } + } + return false; + } + + WindowSP FindSubWindow(const char *name) { + Windows::iterator pos, end = m_subwindows.end(); + size_t i = 0; + for (pos = m_subwindows.begin(); pos != end; ++pos, ++i) { + if ((*pos)->m_name == name) + return *pos; + } + return WindowSP(); + } + + void RemoveSubWindows() { + m_curr_active_window_idx = UINT32_MAX; + m_prev_active_window_idx = UINT32_MAX; + for (Windows::iterator pos = m_subwindows.begin(); + pos != m_subwindows.end(); pos = m_subwindows.erase(pos)) { + (*pos)->Erase(); + } + if (m_parent) + m_parent->Touch(); + else + ::touchwin(stdscr); + } + + // Window drawing utilities + void DrawTitleBox(const char *title, const char *bottom_message = nullptr) { + attr_t attr = 0; + if (IsActive()) + attr = A_BOLD | COLOR_PAIR(BlackOnWhite); + else + attr = 0; + if (attr) + AttributeOn(attr); + + Box(); + MoveCursor(3, 0); + + if (title && title[0]) { + PutChar('<'); + PutCString(title); + PutChar('>'); + } + + if (bottom_message && bottom_message[0]) { + int bottom_message_length = strlen(bottom_message); + int x = GetWidth() - 3 - (bottom_message_length + 2); + + if (x > 0) { + MoveCursor(x, GetHeight() - 1); + PutChar('['); + PutCString(bottom_message); + PutChar(']'); + } else { + MoveCursor(1, GetHeight() - 1); + PutChar('['); + PutCStringTruncated(1, bottom_message); + } + } + if (attr) + AttributeOff(attr); + } + + virtual void Draw(bool force) { + if (m_delegate_sp && m_delegate_sp->WindowDelegateDraw(*this, force)) + return; + + for (auto &subwindow_sp : m_subwindows) + subwindow_sp->Draw(force); + } + + bool CreateHelpSubwindow() { + if (m_delegate_sp) { + const char *text = m_delegate_sp->WindowDelegateGetHelpText(); + KeyHelp *key_help = m_delegate_sp->WindowDelegateGetKeyHelp(); + if ((text && text[0]) || key_help) { + std::unique_ptr<HelpDialogDelegate> help_delegate_up( + new HelpDialogDelegate(text, key_help)); + const size_t num_lines = help_delegate_up->GetNumLines(); + const size_t max_length = help_delegate_up->GetMaxLineLength(); + Rect bounds = GetBounds(); + bounds.Inset(1, 1); + if (max_length + 4 < static_cast<size_t>(bounds.size.width)) { + bounds.origin.x += (bounds.size.width - max_length + 4) / 2; + bounds.size.width = max_length + 4; + } else { + if (bounds.size.width > 100) { + const int inset_w = bounds.size.width / 4; + bounds.origin.x += inset_w; + bounds.size.width -= 2 * inset_w; + } + } + + if (num_lines + 2 < static_cast<size_t>(bounds.size.height)) { + bounds.origin.y += (bounds.size.height - num_lines + 2) / 2; + bounds.size.height = num_lines + 2; + } else { + if (bounds.size.height > 100) { + const int inset_h = bounds.size.height / 4; + bounds.origin.y += inset_h; + bounds.size.height -= 2 * inset_h; + } + } + WindowSP help_window_sp; + Window *parent_window = GetParent(); + if (parent_window) + help_window_sp = parent_window->CreateSubWindow("Help", bounds, true); + else + help_window_sp = CreateSubWindow("Help", bounds, true); + help_window_sp->SetDelegate( + WindowDelegateSP(help_delegate_up.release())); + return true; + } + } + return false; + } + + virtual HandleCharResult HandleChar(int key) { + // Always check the active window first + HandleCharResult result = eKeyNotHandled; + WindowSP active_window_sp = GetActiveWindow(); + if (active_window_sp) { + result = active_window_sp->HandleChar(key); + if (result != eKeyNotHandled) + return result; + } + + if (m_delegate_sp) { + result = m_delegate_sp->WindowDelegateHandleChar(*this, key); + if (result != eKeyNotHandled) + return result; + } + + // Then check for any windows that want any keys that weren't handled. This + // is typically only for a menubar. Make a copy of the subwindows in case + // any HandleChar() functions muck with the subwindows. If we don't do + // this, we can crash when iterating over the subwindows. + Windows subwindows(m_subwindows); + for (auto subwindow_sp : subwindows) { + if (!subwindow_sp->m_can_activate) { + HandleCharResult result = subwindow_sp->HandleChar(key); + if (result != eKeyNotHandled) + return result; + } + } + + return eKeyNotHandled; + } + + WindowSP GetActiveWindow() { + if (!m_subwindows.empty()) { + if (m_curr_active_window_idx >= m_subwindows.size()) { + if (m_prev_active_window_idx < m_subwindows.size()) { + m_curr_active_window_idx = m_prev_active_window_idx; + m_prev_active_window_idx = UINT32_MAX; + } else if (IsActive()) { + m_prev_active_window_idx = UINT32_MAX; + m_curr_active_window_idx = UINT32_MAX; + + // Find first window that wants to be active if this window is active + const size_t num_subwindows = m_subwindows.size(); + for (size_t i = 0; i < num_subwindows; ++i) { + if (m_subwindows[i]->GetCanBeActive()) { + m_curr_active_window_idx = i; + break; + } + } + } + } + + if (m_curr_active_window_idx < m_subwindows.size()) + return m_subwindows[m_curr_active_window_idx]; + } + return WindowSP(); + } + + bool GetCanBeActive() const { return m_can_activate; } + + void SetCanBeActive(bool b) { m_can_activate = b; } + + void SetDelegate(const WindowDelegateSP &delegate_sp) { + m_delegate_sp = delegate_sp; + } + + Window *GetParent() const { return m_parent; } + + bool IsActive() const { + if (m_parent) + return m_parent->GetActiveWindow().get() == this; + else + return true; // Top level window is always active + } + + void SelectNextWindowAsActive() { + // Move active focus to next window + const int num_subwindows = m_subwindows.size(); + int start_idx = 0; + if (m_curr_active_window_idx != UINT32_MAX) { + m_prev_active_window_idx = m_curr_active_window_idx; + start_idx = m_curr_active_window_idx + 1; + } + for (int idx = start_idx; idx < num_subwindows; ++idx) { + if (m_subwindows[idx]->GetCanBeActive()) { + m_curr_active_window_idx = idx; + return; + } + } + for (int idx = 0; idx < start_idx; ++idx) { + if (m_subwindows[idx]->GetCanBeActive()) { + m_curr_active_window_idx = idx; + break; + } + } + } + + void SelectPreviousWindowAsActive() { + // Move active focus to previous window + const int num_subwindows = m_subwindows.size(); + int start_idx = num_subwindows - 1; + if (m_curr_active_window_idx != UINT32_MAX) { + m_prev_active_window_idx = m_curr_active_window_idx; + start_idx = m_curr_active_window_idx - 1; + } + for (int idx = start_idx; idx >= 0; --idx) { + if (m_subwindows[idx]->GetCanBeActive()) { + m_curr_active_window_idx = idx; + return; + } + } + for (int idx = num_subwindows - 1; idx > start_idx; --idx) { + if (m_subwindows[idx]->GetCanBeActive()) { + m_curr_active_window_idx = idx; + break; + } + } + } + + const char *GetName() const { return m_name.c_str(); } + +protected: + std::string m_name; + PANEL *m_panel; + Window *m_parent; + Windows m_subwindows; + WindowDelegateSP m_delegate_sp; + uint32_t m_curr_active_window_idx; + uint32_t m_prev_active_window_idx; + bool m_delete; + bool m_needs_update; + bool m_can_activate; + bool m_is_subwin; + +private: + Window(const Window &) = delete; + const Window &operator=(const Window &) = delete; +}; + +///////// +// Forms +///////// + +// A scroll context defines a vertical region that needs to be visible in a +// scrolling area. The region is defined by the index of the start and end lines +// of the region. The start and end lines may be equal, in which case, the +// region is a single line. +struct ScrollContext { + int start; + int end; + + ScrollContext(int line) : start(line), end(line) {} + ScrollContext(int _start, int _end) : start(_start), end(_end) {} + + void Offset(int offset) { + start += offset; + end += offset; + } +}; + +class FieldDelegate { +public: + virtual ~FieldDelegate() = default; + + // Returns the number of lines needed to draw the field. The draw method will + // be given a surface that have exactly this number of lines. + virtual int FieldDelegateGetHeight() = 0; + + // Returns the scroll context in the local coordinates of the field. By + // default, the scroll context spans the whole field. Bigger fields with + // internal navigation should override this method to provide a finer context. + // Typical override methods would first get the scroll context of the internal + // element then add the offset of the element in the field. + virtual ScrollContext FieldDelegateGetScrollContext() { + return ScrollContext(0, FieldDelegateGetHeight() - 1); + } + + // Draw the field in the given subpad surface. The surface have a height that + // is equal to the height returned by FieldDelegateGetHeight(). If the field + // is selected in the form window, then is_selected will be true. + virtual void FieldDelegateDraw(Surface &surface, bool is_selected) = 0; + + // Handle the key that wasn't handled by the form window or a container field. + virtual HandleCharResult FieldDelegateHandleChar(int key) { + return eKeyNotHandled; + } + + // This is executed once the user exists the field, that is, once the user + // navigates to the next or the previous field. This is particularly useful to + // do in-field validation and error setting. Fields with internal navigation + // should call this method on their fields. + virtual void FieldDelegateExitCallback() {} + + // Fields may have internal navigation, for instance, a List Field have + // multiple internal elements, which needs to be navigated. To allow for this + // mechanism, the window shouldn't handle the navigation keys all the time, + // and instead call the key handing method of the selected field. It should + // only handle the navigation keys when the field contains a single element or + // have the last or first element selected depending on if the user is + // navigating forward or backward. Additionally, once a field is selected in + // the forward or backward direction, its first or last internal element + // should be selected. The following methods implements those mechanisms. + + // Returns true if the first element in the field is selected or if the field + // contains a single element. + virtual bool FieldDelegateOnFirstOrOnlyElement() { return true; } + + // Returns true if the last element in the field is selected or if the field + // contains a single element. + virtual bool FieldDelegateOnLastOrOnlyElement() { return true; } + + // Select the first element in the field if multiple elements exists. + virtual void FieldDelegateSelectFirstElement() {} + + // Select the last element in the field if multiple elements exists. + virtual void FieldDelegateSelectLastElement() {} + + // Returns true if the field has an error, false otherwise. + virtual bool FieldDelegateHasError() { return false; } + + bool FieldDelegateIsVisible() { return m_is_visible; } + + void FieldDelegateHide() { m_is_visible = false; } + + void FieldDelegateShow() { m_is_visible = true; } + +protected: + bool m_is_visible = true; +}; + +typedef std::unique_ptr<FieldDelegate> FieldDelegateUP; + +class TextFieldDelegate : public FieldDelegate { +public: + TextFieldDelegate(const char *label, const char *content, bool required) + : m_label(label), m_required(required) { + if (content) + m_content = content; + } + + // Text fields are drawn as titled boxes of a single line, with a possible + // error messages at the end. + // + // __[Label]___________ + // | | + // |__________________| + // - Error message if it exists. + + // The text field has a height of 3 lines. 2 lines for borders and 1 line for + // the content. + int GetFieldHeight() { return 3; } + + // The text field has a full height of 3 or 4 lines. 3 lines for the actual + // field and an optional line for an error if it exists. + int FieldDelegateGetHeight() override { + int height = GetFieldHeight(); + if (FieldDelegateHasError()) + height++; + return height; + } + + // Get the cursor X position in the surface coordinate. + int GetCursorXPosition() { return m_cursor_position - m_first_visibile_char; } + + int GetContentLength() { return m_content.length(); } + + void DrawContent(Surface &surface, bool is_selected) { + UpdateScrolling(surface.GetWidth()); + + surface.MoveCursor(0, 0); + const char *text = m_content.c_str() + m_first_visibile_char; + surface.PutCString(text, surface.GetWidth()); + + // Highlight the cursor. + surface.MoveCursor(GetCursorXPosition(), 0); + if (is_selected) + surface.AttributeOn(A_REVERSE); + if (m_cursor_position == GetContentLength()) + // Cursor is past the last character. Highlight an empty space. + surface.PutChar(' '); + else + surface.PutChar(m_content[m_cursor_position]); + if (is_selected) + surface.AttributeOff(A_REVERSE); + } + + void DrawField(Surface &surface, bool is_selected) { + surface.TitledBox(m_label.c_str()); + + Rect content_bounds = surface.GetFrame(); + content_bounds.Inset(1, 1); + Surface content_surface = surface.SubSurface(content_bounds); + + DrawContent(content_surface, is_selected); + } + + void DrawError(Surface &surface) { + if (!FieldDelegateHasError()) + return; + surface.MoveCursor(0, 0); + surface.AttributeOn(COLOR_PAIR(RedOnBlack)); + surface.PutChar(ACS_DIAMOND); + surface.PutChar(' '); + surface.PutCStringTruncated(1, GetError().c_str()); + surface.AttributeOff(COLOR_PAIR(RedOnBlack)); + } + + void FieldDelegateDraw(Surface &surface, bool is_selected) override { + Rect frame = surface.GetFrame(); + Rect field_bounds, error_bounds; + frame.HorizontalSplit(GetFieldHeight(), field_bounds, error_bounds); + Surface field_surface = surface.SubSurface(field_bounds); + Surface error_surface = surface.SubSurface(error_bounds); + + DrawField(field_surface, is_selected); + DrawError(error_surface); + } + + // Get the position of the last visible character. + int GetLastVisibleCharPosition(int width) { + int position = m_first_visibile_char + width - 1; + return std::min(position, GetContentLength()); + } + + void UpdateScrolling(int width) { + if (m_cursor_position < m_first_visibile_char) { + m_first_visibile_char = m_cursor_position; + return; + } + + if (m_cursor_position > GetLastVisibleCharPosition(width)) + m_first_visibile_char = m_cursor_position - (width - 1); + } + + // The cursor is allowed to move one character past the string. + // m_cursor_position is in range [0, GetContentLength()]. + void MoveCursorRight() { + if (m_cursor_position < GetContentLength()) + m_cursor_position++; + } + + void MoveCursorLeft() { + if (m_cursor_position > 0) + m_cursor_position--; + } + + void MoveCursorToStart() { m_cursor_position = 0; } + + void MoveCursorToEnd() { m_cursor_position = GetContentLength(); } + + void ScrollLeft() { + if (m_first_visibile_char > 0) + m_first_visibile_char--; + } + + // Insert a character at the current cursor position and advance the cursor + // position. + void InsertChar(char character) { + m_content.insert(m_cursor_position, 1, character); + m_cursor_position++; + ClearError(); + } + + // Remove the character before the cursor position, retreat the cursor + // position, and scroll left. + void RemovePreviousChar() { + if (m_cursor_position == 0) + return; + + m_content.erase(m_cursor_position - 1, 1); + m_cursor_position--; + ScrollLeft(); + ClearError(); + } + + // Remove the character after the cursor position. + void RemoveNextChar() { + if (m_cursor_position == GetContentLength()) + return; + + m_content.erase(m_cursor_position, 1); + ClearError(); + } + + // Clear characters from the current cursor position to the end. + void ClearToEnd() { + m_content.erase(m_cursor_position); + ClearError(); + } + + void Clear() { + m_content.clear(); + m_cursor_position = 0; + ClearError(); + } + + // True if the key represents a char that can be inserted in the field + // content, false otherwise. + virtual bool IsAcceptableChar(int key) { + // The behavior of isprint is undefined when the value is not representable + // as an unsigned char. So explicitly check for non-ascii key codes. + if (key > 127) + return false; + return isprint(key); + } + + HandleCharResult FieldDelegateHandleChar(int key) override { + if (IsAcceptableChar(key)) { + ClearError(); + InsertChar((char)key); + return eKeyHandled; + } + + switch (key) { + case KEY_HOME: + case KEY_CTRL_A: + MoveCursorToStart(); + return eKeyHandled; + case KEY_END: + case KEY_CTRL_E: + MoveCursorToEnd(); + return eKeyHandled; + case KEY_RIGHT: + case KEY_SF: + MoveCursorRight(); + return eKeyHandled; + case KEY_LEFT: + case KEY_SR: + MoveCursorLeft(); + return eKeyHandled; + case KEY_BACKSPACE: + case KEY_DELETE: + RemovePreviousChar(); + return eKeyHandled; + case KEY_DC: + RemoveNextChar(); + return eKeyHandled; + case KEY_EOL: + case KEY_CTRL_K: + ClearToEnd(); + return eKeyHandled; + case KEY_DL: + case KEY_CLEAR: + Clear(); + return eKeyHandled; + default: + break; + } + return eKeyNotHandled; + } + + bool FieldDelegateHasError() override { return !m_error.empty(); } + + void FieldDelegateExitCallback() override { + if (!IsSpecified() && m_required) + SetError("This field is required!"); + } + + bool IsSpecified() { return !m_content.empty(); } + + void ClearError() { m_error.clear(); } + + const std::string &GetError() { return m_error; } + + void SetError(const char *error) { m_error = error; } + + const std::string &GetText() { return m_content; } + + void SetText(const char *text) { + if (text == nullptr) { + m_content.clear(); + return; + } + m_content = text; + } + +protected: + std::string m_label; + bool m_required; + // The position of the top left corner character of the border. + std::string m_content; + // The cursor position in the content string itself. Can be in the range + // [0, GetContentLength()]. + int m_cursor_position = 0; + // The index of the first visible character in the content. + int m_first_visibile_char = 0; + // Optional error message. If empty, field is considered to have no error. + std::string m_error; +}; + +class IntegerFieldDelegate : public TextFieldDelegate { +public: + IntegerFieldDelegate(const char *label, int content, bool required) + : TextFieldDelegate(label, std::to_string(content).c_str(), required) {} + + // Only accept digits. + bool IsAcceptableChar(int key) override { return isdigit(key); } + + // Returns the integer content of the field. + int GetInteger() { return std::stoi(m_content); } +}; + +class FileFieldDelegate : public TextFieldDelegate { +public: + FileFieldDelegate(const char *label, const char *content, bool need_to_exist, + bool required) + : TextFieldDelegate(label, content, required), + m_need_to_exist(need_to_exist) {} + + void FieldDelegateExitCallback() override { + TextFieldDelegate::FieldDelegateExitCallback(); + if (!IsSpecified()) + return; + + if (!m_need_to_exist) + return; + + FileSpec file = GetResolvedFileSpec(); + if (!FileSystem::Instance().Exists(file)) { + SetError("File doesn't exist!"); + return; + } + if (FileSystem::Instance().IsDirectory(file)) { + SetError("Not a file!"); + return; + } + } + + FileSpec GetFileSpec() { + FileSpec file_spec(GetPath()); + return file_spec; + } + + FileSpec GetResolvedFileSpec() { + FileSpec file_spec(GetPath()); + FileSystem::Instance().Resolve(file_spec); + return file_spec; + } + + const std::string &GetPath() { return m_content; } + +protected: + bool m_need_to_exist; +}; + +class DirectoryFieldDelegate : public TextFieldDelegate { +public: + DirectoryFieldDelegate(const char *label, const char *content, + bool need_to_exist, bool required) + : TextFieldDelegate(label, content, required), + m_need_to_exist(need_to_exist) {} + + void FieldDelegateExitCallback() override { + TextFieldDelegate::FieldDelegateExitCallback(); + if (!IsSpecified()) + return; + + if (!m_need_to_exist) + return; + + FileSpec file = GetResolvedFileSpec(); + if (!FileSystem::Instance().Exists(file)) { + SetError("Directory doesn't exist!"); + return; + } + if (!FileSystem::Instance().IsDirectory(file)) { + SetError("Not a directory!"); + return; + } + } + + FileSpec GetFileSpec() { + FileSpec file_spec(GetPath()); + return file_spec; + } + + FileSpec GetResolvedFileSpec() { + FileSpec file_spec(GetPath()); + FileSystem::Instance().Resolve(file_spec); + return file_spec; + } + + const std::string &GetPath() { return m_content; } + +protected: + bool m_need_to_exist; +}; + +class ArchFieldDelegate : public TextFieldDelegate { +public: + ArchFieldDelegate(const char *label, const char *content, bool required) + : TextFieldDelegate(label, content, required) {} + + void FieldDelegateExitCallback() override { + TextFieldDelegate::FieldDelegateExitCallback(); + if (!IsSpecified()) + return; + + if (!GetArchSpec().IsValid()) + SetError("Not a valid arch!"); + } + + const std::string &GetArchString() { return m_content; } + + ArchSpec GetArchSpec() { return ArchSpec(GetArchString()); } +}; + +class BooleanFieldDelegate : public FieldDelegate { +public: + BooleanFieldDelegate(const char *label, bool content) + : m_label(label), m_content(content) {} + + // Boolean fields are drawn as checkboxes. + // + // [X] Label or [ ] Label + + // Boolean fields are have a single line. + int FieldDelegateGetHeight() override { return 1; } + + void FieldDelegateDraw(Surface &surface, bool is_selected) override { + surface.MoveCursor(0, 0); + surface.PutChar('['); + if (is_selected) + surface.AttributeOn(A_REVERSE); + surface.PutChar(m_content ? ACS_DIAMOND : ' '); + if (is_selected) + surface.AttributeOff(A_REVERSE); + surface.PutChar(']'); + surface.PutChar(' '); + surface.PutCString(m_label.c_str()); + } + + void ToggleContent() { m_content = !m_content; } + + void SetContentToTrue() { m_content = true; } + + void SetContentToFalse() { m_content = false; } + + HandleCharResult FieldDelegateHandleChar(int key) override { + switch (key) { + case 't': + case '1': + SetContentToTrue(); + return eKeyHandled; + case 'f': + case '0': + SetContentToFalse(); + return eKeyHandled; + case ' ': + case '\r': + case '\n': + case KEY_ENTER: + ToggleContent(); + return eKeyHandled; + default: + break; + } + return eKeyNotHandled; + } + + // Returns the boolean content of the field. + bool GetBoolean() { return m_content; } + +protected: + std::string m_label; + bool m_content; +}; + +class ChoicesFieldDelegate : public FieldDelegate { +public: + ChoicesFieldDelegate(const char *label, int number_of_visible_choices, + std::vector<std::string> choices) + : m_label(label), m_number_of_visible_choices(number_of_visible_choices), + m_choices(choices) {} + + // Choices fields are drawn as titles boxses of a number of visible choices. + // The rest of the choices become visible as the user scroll. The selected + // choice is denoted by a diamond as the first character. + // + // __[Label]___________ + // |-Choice 1 | + // | Choice 2 | + // | Choice 3 | + // |__________________| + + // Choices field have two border characters plus the number of visible + // choices. + int FieldDelegateGetHeight() override { + return m_number_of_visible_choices + 2; + } + + int GetNumberOfChoices() { return m_choices.size(); } + + // Get the index of the last visible choice. + int GetLastVisibleChoice() { + int index = m_first_visibile_choice + m_number_of_visible_choices; + return std::min(index, GetNumberOfChoices()) - 1; + } + + void DrawContent(Surface &surface, bool is_selected) { + int choices_to_draw = GetLastVisibleChoice() - m_first_visibile_choice + 1; + for (int i = 0; i < choices_to_draw; i++) { + surface.MoveCursor(0, i); + int current_choice = m_first_visibile_choice + i; + const char *text = m_choices[current_choice].c_str(); + bool highlight = is_selected && current_choice == m_choice; + if (highlight) + surface.AttributeOn(A_REVERSE); + surface.PutChar(current_choice == m_choice ? ACS_DIAMOND : ' '); + surface.PutCString(text); + if (highlight) + surface.AttributeOff(A_REVERSE); + } + } + + void FieldDelegateDraw(Surface &surface, bool is_selected) override { + UpdateScrolling(); + + surface.TitledBox(m_label.c_str()); + + Rect content_bounds = surface.GetFrame(); + content_bounds.Inset(1, 1); + Surface content_surface = surface.SubSurface(content_bounds); + + DrawContent(content_surface, is_selected); + } + + void SelectPrevious() { + if (m_choice > 0) + m_choice--; + } + + void SelectNext() { + if (m_choice < GetNumberOfChoices() - 1) + m_choice++; + } + + void UpdateScrolling() { + if (m_choice > GetLastVisibleChoice()) { + m_first_visibile_choice = m_choice - (m_number_of_visible_choices - 1); + return; + } + + if (m_choice < m_first_visibile_choice) + m_first_visibile_choice = m_choice; + } + + HandleCharResult FieldDelegateHandleChar(int key) override { + switch (key) { + case KEY_UP: + SelectPrevious(); + return eKeyHandled; + case KEY_DOWN: + SelectNext(); + return eKeyHandled; + default: + break; + } + return eKeyNotHandled; + } + + // Returns the content of the choice as a string. + std::string GetChoiceContent() { return m_choices[m_choice]; } + + // Returns the index of the choice. + int GetChoice() { return m_choice; } + + void SetChoice(llvm::StringRef choice) { + for (int i = 0; i < GetNumberOfChoices(); i++) { + if (choice == m_choices[i]) { + m_choice = i; + return; + } + } + } + +protected: + std::string m_label; + int m_number_of_visible_choices; + std::vector<std::string> m_choices; + // The index of the selected choice. + int m_choice = 0; + // The index of the first visible choice in the field. + int m_first_visibile_choice = 0; +}; + +class PlatformPluginFieldDelegate : public ChoicesFieldDelegate { +public: + PlatformPluginFieldDelegate(Debugger &debugger) + : ChoicesFieldDelegate("Platform Plugin", 3, GetPossiblePluginNames()) { + PlatformSP platform_sp = debugger.GetPlatformList().GetSelectedPlatform(); + if (platform_sp) + SetChoice(platform_sp->GetPluginName()); + } + + std::vector<std::string> GetPossiblePluginNames() { + std::vector<std::string> names; + size_t i = 0; + for (llvm::StringRef name = + PluginManager::GetPlatformPluginNameAtIndex(i++); + !name.empty(); name = PluginManager::GetProcessPluginNameAtIndex(i++)) + names.push_back(name.str()); + return names; + } + + std::string GetPluginName() { + std::string plugin_name = GetChoiceContent(); + return plugin_name; + } +}; + +class ProcessPluginFieldDelegate : public ChoicesFieldDelegate { +public: + ProcessPluginFieldDelegate() + : ChoicesFieldDelegate("Process Plugin", 3, GetPossiblePluginNames()) {} + + std::vector<std::string> GetPossiblePluginNames() { + std::vector<std::string> names; + names.push_back("<default>"); + + size_t i = 0; + for (llvm::StringRef name = PluginManager::GetProcessPluginNameAtIndex(i++); + !name.empty(); name = PluginManager::GetProcessPluginNameAtIndex(i++)) + names.push_back(name.str()); + return names; + } + + std::string GetPluginName() { + std::string plugin_name = GetChoiceContent(); + if (plugin_name == "<default>") + return ""; + return plugin_name; + } +}; + +class LazyBooleanFieldDelegate : public ChoicesFieldDelegate { +public: + LazyBooleanFieldDelegate(const char *label, const char *calculate_label) + : ChoicesFieldDelegate(label, 3, GetPossibleOptions(calculate_label)) {} + + static constexpr const char *kNo = "No"; + static constexpr const char *kYes = "Yes"; + + std::vector<std::string> GetPossibleOptions(const char *calculate_label) { + std::vector<std::string> options; + options.push_back(calculate_label); + options.push_back(kYes); + options.push_back(kNo); + return options; + } + + LazyBool GetLazyBoolean() { + std::string choice = GetChoiceContent(); + if (choice == kNo) + return eLazyBoolNo; + else if (choice == kYes) + return eLazyBoolYes; + else + return eLazyBoolCalculate; + } +}; + +template <class T> class ListFieldDelegate : public FieldDelegate { +public: + ListFieldDelegate(const char *label, T default_field) + : m_label(label), m_default_field(default_field), + m_selection_type(SelectionType::NewButton) {} + + // Signify which element is selected. If a field or a remove button is + // selected, then m_selection_index signifies the particular field that + // is selected or the field that the remove button belongs to. + enum class SelectionType { Field, RemoveButton, NewButton }; + + // A List field is drawn as a titled box of a number of other fields of the + // same type. Each field has a Remove button next to it that removes the + // corresponding field. Finally, the last line contains a New button to add a + // new field. + // + // __[Label]___________ + // | Field 0 [Remove] | + // | Field 1 [Remove] | + // | Field 2 [Remove] | + // | [New] | + // |__________________| + + // List fields have two lines for border characters, 1 line for the New + // button, and the total height of the available fields. + int FieldDelegateGetHeight() override { + // 2 border characters. + int height = 2; + // Total height of the fields. + for (int i = 0; i < GetNumberOfFields(); i++) { + height += m_fields[i].FieldDelegateGetHeight(); + } + // A line for the New button. + height++; + return height; + } + + ScrollContext FieldDelegateGetScrollContext() override { + int height = FieldDelegateGetHeight(); + if (m_selection_type == SelectionType::NewButton) + return ScrollContext(height - 2, height - 1); + + FieldDelegate &field = m_fields[m_selection_index]; + ScrollContext context = field.FieldDelegateGetScrollContext(); + + // Start at 1 because of the top border. + int offset = 1; + for (int i = 0; i < m_selection_index; i++) { + offset += m_fields[i].FieldDelegateGetHeight(); + } + context.Offset(offset); + + // If the scroll context is touching the top border, include it in the + // context to show the label. + if (context.start == 1) + context.start--; + + // If the scroll context is touching the new button, include it as well as + // the bottom border in the context. + if (context.end == height - 3) + context.end += 2; + + return context; + } + + void DrawRemoveButton(Surface &surface, int highlight) { + surface.MoveCursor(1, surface.GetHeight() / 2); + if (highlight) + surface.AttributeOn(A_REVERSE); + surface.PutCString("[Remove]"); + if (highlight) + surface.AttributeOff(A_REVERSE); + } + + void DrawFields(Surface &surface, bool is_selected) { + int line = 0; + int width = surface.GetWidth(); + for (int i = 0; i < GetNumberOfFields(); i++) { + int height = m_fields[i].FieldDelegateGetHeight(); + Rect bounds = Rect(Point(0, line), Size(width, height)); + Rect field_bounds, remove_button_bounds; + bounds.VerticalSplit(bounds.size.width - sizeof(" [Remove]"), + field_bounds, remove_button_bounds); + Surface field_surface = surface.SubSurface(field_bounds); + Surface remove_button_surface = surface.SubSurface(remove_button_bounds); + + bool is_element_selected = m_selection_index == i && is_selected; + bool is_field_selected = + is_element_selected && m_selection_type == SelectionType::Field; + bool is_remove_button_selected = + is_element_selected && + m_selection_type == SelectionType::RemoveButton; + m_fields[i].FieldDelegateDraw(field_surface, is_field_selected); + DrawRemoveButton(remove_button_surface, is_remove_button_selected); + + line += height; + } + } + + void DrawNewButton(Surface &surface, bool is_selected) { + const char *button_text = "[New]"; + int x = (surface.GetWidth() - sizeof(button_text) - 1) / 2; + surface.MoveCursor(x, 0); + bool highlight = + is_selected && m_selection_type == SelectionType::NewButton; + if (highlight) + surface.AttributeOn(A_REVERSE); + surface.PutCString(button_text); + if (highlight) + surface.AttributeOff(A_REVERSE); + } + + void FieldDelegateDraw(Surface &surface, bool is_selected) override { + surface.TitledBox(m_label.c_str()); + + Rect content_bounds = surface.GetFrame(); + content_bounds.Inset(1, 1); + Rect fields_bounds, new_button_bounds; + content_bounds.HorizontalSplit(content_bounds.size.height - 1, + fields_bounds, new_button_bounds); + Surface fields_surface = surface.SubSurface(fields_bounds); + Surface new_button_surface = surface.SubSurface(new_button_bounds); + + DrawFields(fields_surface, is_selected); + DrawNewButton(new_button_surface, is_selected); + } + + void AddNewField() { + m_fields.push_back(m_default_field); + m_selection_index = GetNumberOfFields() - 1; + m_selection_type = SelectionType::Field; + FieldDelegate &field = m_fields[m_selection_index]; + field.FieldDelegateSelectFirstElement(); + } + + void RemoveField() { + m_fields.erase(m_fields.begin() + m_selection_index); + if (m_selection_index != 0) + m_selection_index--; + + if (GetNumberOfFields() > 0) { + m_selection_type = SelectionType::Field; + FieldDelegate &field = m_fields[m_selection_index]; + field.FieldDelegateSelectFirstElement(); + } else + m_selection_type = SelectionType::NewButton; + } + + HandleCharResult SelectNext(int key) { + if (m_selection_type == SelectionType::NewButton) + return eKeyNotHandled; + + if (m_selection_type == SelectionType::RemoveButton) { + if (m_selection_index == GetNumberOfFields() - 1) { + m_selection_type = SelectionType::NewButton; + return eKeyHandled; + } + m_selection_index++; + m_selection_type = SelectionType::Field; + FieldDelegate &next_field = m_fields[m_selection_index]; + next_field.FieldDelegateSelectFirstElement(); + return eKeyHandled; + } + + FieldDelegate &field = m_fields[m_selection_index]; + if (!field.FieldDelegateOnLastOrOnlyElement()) { + return field.FieldDelegateHandleChar(key); + } + + field.FieldDelegateExitCallback(); + + m_selection_type = SelectionType::RemoveButton; + return eKeyHandled; + } + + HandleCharResult SelectPrevious(int key) { + if (FieldDelegateOnFirstOrOnlyElement()) + return eKeyNotHandled; + + if (m_selection_type == SelectionType::RemoveButton) { + m_selection_type = SelectionType::Field; + FieldDelegate &field = m_fields[m_selection_index]; + field.FieldDelegateSelectLastElement(); + return eKeyHandled; + } + + if (m_selection_type == SelectionType::NewButton) { + m_selection_type = SelectionType::RemoveButton; + m_selection_index = GetNumberOfFields() - 1; + return eKeyHandled; + } + + FieldDelegate &field = m_fields[m_selection_index]; + if (!field.FieldDelegateOnFirstOrOnlyElement()) { + return field.FieldDelegateHandleChar(key); + } + + field.FieldDelegateExitCallback(); + + m_selection_type = SelectionType::RemoveButton; + m_selection_index--; + return eKeyHandled; + } + + // If the last element of the field is selected and it didn't handle the key. + // Select the next field or new button if the selected field is the last one. + HandleCharResult SelectNextInList(int key) { + assert(m_selection_type == SelectionType::Field); + + FieldDelegate &field = m_fields[m_selection_index]; + if (field.FieldDelegateHandleChar(key) == eKeyHandled) + return eKeyHandled; + + if (!field.FieldDelegateOnLastOrOnlyElement()) + return eKeyNotHandled; + + field.FieldDelegateExitCallback(); + + if (m_selection_index == GetNumberOfFields() - 1) { + m_selection_type = SelectionType::NewButton; + return eKeyHandled; + } + + m_selection_index++; + FieldDelegate &next_field = m_fields[m_selection_index]; + next_field.FieldDelegateSelectFirstElement(); + return eKeyHandled; + } + + HandleCharResult FieldDelegateHandleChar(int key) override { + switch (key) { + case '\r': + case '\n': + case KEY_ENTER: + switch (m_selection_type) { + case SelectionType::NewButton: + AddNewField(); + return eKeyHandled; + case SelectionType::RemoveButton: + RemoveField(); + return eKeyHandled; + case SelectionType::Field: + return SelectNextInList(key); + } + break; + case '\t': + return SelectNext(key); + case KEY_SHIFT_TAB: + return SelectPrevious(key); + default: + break; + } + + // If the key wasn't handled and one of the fields is selected, pass the key + // to that field. + if (m_selection_type == SelectionType::Field) { + return m_fields[m_selection_index].FieldDelegateHandleChar(key); + } + + return eKeyNotHandled; + } + + bool FieldDelegateOnLastOrOnlyElement() override { + if (m_selection_type == SelectionType::NewButton) { + return true; + } + return false; + } + + bool FieldDelegateOnFirstOrOnlyElement() override { + if (m_selection_type == SelectionType::NewButton && + GetNumberOfFields() == 0) + return true; + + if (m_selection_type == SelectionType::Field && m_selection_index == 0) { + FieldDelegate &field = m_fields[m_selection_index]; + return field.FieldDelegateOnFirstOrOnlyElement(); + } + + return false; + } + + void FieldDelegateSelectFirstElement() override { + if (GetNumberOfFields() == 0) { + m_selection_type = SelectionType::NewButton; + return; + } + + m_selection_type = SelectionType::Field; + m_selection_index = 0; + } + + void FieldDelegateSelectLastElement() override { + m_selection_type = SelectionType::NewButton; + } + + int GetNumberOfFields() { return m_fields.size(); } + + // Returns the form delegate at the current index. + T &GetField(int index) { return m_fields[index]; } + +protected: + std::string m_label; + // The default field delegate instance from which new field delegates will be + // created though a copy. + T m_default_field; + std::vector<T> m_fields; + int m_selection_index = 0; + // See SelectionType class enum. + SelectionType m_selection_type; +}; + +class ArgumentsFieldDelegate : public ListFieldDelegate<TextFieldDelegate> { +public: + ArgumentsFieldDelegate() + : ListFieldDelegate("Arguments", + TextFieldDelegate("Argument", "", false)) {} + + Args GetArguments() { + Args arguments; + for (int i = 0; i < GetNumberOfFields(); i++) { + arguments.AppendArgument(GetField(i).GetText()); + } + return arguments; + } + + void AddArguments(const Args &arguments) { + for (size_t i = 0; i < arguments.GetArgumentCount(); i++) { + AddNewField(); + TextFieldDelegate &field = GetField(GetNumberOfFields() - 1); + field.SetText(arguments.GetArgumentAtIndex(i)); + } + } +}; + +template <class KeyFieldDelegateType, class ValueFieldDelegateType> +class MappingFieldDelegate : public FieldDelegate { +public: + MappingFieldDelegate(KeyFieldDelegateType key_field, + ValueFieldDelegateType value_field) + : m_key_field(key_field), m_value_field(value_field), + m_selection_type(SelectionType::Key) {} + + // Signify which element is selected. The key field or its value field. + enum class SelectionType { Key, Value }; + + // A mapping field is drawn as two text fields with a right arrow in between. + // The first field stores the key of the mapping and the second stores the + // value if the mapping. + // + // __[Key]_____________ __[Value]___________ + // | | > | | + // |__________________| |__________________| + // - Error message if it exists. + + // The mapping field has a height that is equal to the maximum height between + // the key and value fields. + int FieldDelegateGetHeight() override { + return std::max(m_key_field.FieldDelegateGetHeight(), + m_value_field.FieldDelegateGetHeight()); + } + + void DrawArrow(Surface &surface) { + surface.MoveCursor(0, 1); + surface.PutChar(ACS_RARROW); + } + + void FieldDelegateDraw(Surface &surface, bool is_selected) override { + Rect bounds = surface.GetFrame(); + Rect key_field_bounds, arrow_and_value_field_bounds; + bounds.VerticalSplit(bounds.size.width / 2, key_field_bounds, + arrow_and_value_field_bounds); + Rect arrow_bounds, value_field_bounds; + arrow_and_value_field_bounds.VerticalSplit(1, arrow_bounds, + value_field_bounds); + + Surface key_field_surface = surface.SubSurface(key_field_bounds); + Surface arrow_surface = surface.SubSurface(arrow_bounds); + Surface value_field_surface = surface.SubSurface(value_field_bounds); + + bool key_is_selected = + m_selection_type == SelectionType::Key && is_selected; + m_key_field.FieldDelegateDraw(key_field_surface, key_is_selected); + DrawArrow(arrow_surface); + bool value_is_selected = + m_selection_type == SelectionType::Value && is_selected; + m_value_field.FieldDelegateDraw(value_field_surface, value_is_selected); + } + + HandleCharResult SelectNext(int key) { + if (FieldDelegateOnLastOrOnlyElement()) + return eKeyNotHandled; + + if (!m_key_field.FieldDelegateOnLastOrOnlyElement()) { + return m_key_field.FieldDelegateHandleChar(key); + } + + m_key_field.FieldDelegateExitCallback(); + m_selection_type = SelectionType::Value; + m_value_field.FieldDelegateSelectFirstElement(); + return eKeyHandled; + } + + HandleCharResult SelectPrevious(int key) { + if (FieldDelegateOnFirstOrOnlyElement()) + return eKeyNotHandled; + + if (!m_value_field.FieldDelegateOnFirstOrOnlyElement()) { + return m_value_field.FieldDelegateHandleChar(key); + } + + m_value_field.FieldDelegateExitCallback(); + m_selection_type = SelectionType::Key; + m_key_field.FieldDelegateSelectLastElement(); + return eKeyHandled; + } + + // If the value field is selected, pass the key to it. If the key field is + // selected, its last element is selected, and it didn't handle the key, then + // select its corresponding value field. + HandleCharResult SelectNextField(int key) { + if (m_selection_type == SelectionType::Value) { + return m_value_field.FieldDelegateHandleChar(key); + } + + if (m_key_field.FieldDelegateHandleChar(key) == eKeyHandled) + return eKeyHandled; + + if (!m_key_field.FieldDelegateOnLastOrOnlyElement()) + return eKeyNotHandled; + + m_key_field.FieldDelegateExitCallback(); + m_selection_type = SelectionType::Value; + m_value_field.FieldDelegateSelectFirstElement(); + return eKeyHandled; + } + + HandleCharResult FieldDelegateHandleChar(int key) override { + switch (key) { + case KEY_RETURN: + return SelectNextField(key); + case '\t': + return SelectNext(key); + case KEY_SHIFT_TAB: + return SelectPrevious(key); + default: + break; + } + + // If the key wasn't handled, pass the key to the selected field. + if (m_selection_type == SelectionType::Key) + return m_key_field.FieldDelegateHandleChar(key); + else + return m_value_field.FieldDelegateHandleChar(key); + + return eKeyNotHandled; + } + + bool FieldDelegateOnFirstOrOnlyElement() override { + return m_selection_type == SelectionType::Key; + } + + bool FieldDelegateOnLastOrOnlyElement() override { + return m_selection_type == SelectionType::Value; + } + + void FieldDelegateSelectFirstElement() override { + m_selection_type = SelectionType::Key; + } + + void FieldDelegateSelectLastElement() override { + m_selection_type = SelectionType::Value; + } + + bool FieldDelegateHasError() override { + return m_key_field.FieldDelegateHasError() || + m_value_field.FieldDelegateHasError(); + } + + KeyFieldDelegateType &GetKeyField() { return m_key_field; } + + ValueFieldDelegateType &GetValueField() { return m_value_field; } + +protected: + KeyFieldDelegateType m_key_field; + ValueFieldDelegateType m_value_field; + // See SelectionType class enum. + SelectionType m_selection_type; +}; + +class EnvironmentVariableNameFieldDelegate : public TextFieldDelegate { +public: + EnvironmentVariableNameFieldDelegate(const char *content) + : TextFieldDelegate("Name", content, true) {} + + // Environment variable names can't contain an equal sign. + bool IsAcceptableChar(int key) override { + return TextFieldDelegate::IsAcceptableChar(key) && key != '='; + } + + const std::string &GetName() { return m_content; } +}; + +class EnvironmentVariableFieldDelegate + : public MappingFieldDelegate<EnvironmentVariableNameFieldDelegate, + TextFieldDelegate> { +public: + EnvironmentVariableFieldDelegate() + : MappingFieldDelegate( + EnvironmentVariableNameFieldDelegate(""), + TextFieldDelegate("Value", "", /*required=*/false)) {} + + const std::string &GetName() { return GetKeyField().GetName(); } + + const std::string &GetValue() { return GetValueField().GetText(); } + + void SetName(const char *name) { return GetKeyField().SetText(name); } + + void SetValue(const char *value) { return GetValueField().SetText(value); } +}; + +class EnvironmentVariableListFieldDelegate + : public ListFieldDelegate<EnvironmentVariableFieldDelegate> { +public: + EnvironmentVariableListFieldDelegate(const char *label) + : ListFieldDelegate(label, EnvironmentVariableFieldDelegate()) {} + + Environment GetEnvironment() { + Environment environment; + for (int i = 0; i < GetNumberOfFields(); i++) { + environment.insert( + std::make_pair(GetField(i).GetName(), GetField(i).GetValue())); + } + return environment; + } + + void AddEnvironmentVariables(const Environment &environment) { + for (auto &variable : environment) { + AddNewField(); + EnvironmentVariableFieldDelegate &field = + GetField(GetNumberOfFields() - 1); + field.SetName(variable.getKey().str().c_str()); + field.SetValue(variable.getValue().c_str()); + } + } +}; + +class FormAction { +public: + FormAction(const char *label, std::function<void(Window &)> action) + : m_action(action) { + if (label) + m_label = label; + } + + // Draw a centered [Label]. + void Draw(Surface &surface, bool is_selected) { + int x = (surface.GetWidth() - m_label.length()) / 2; + surface.MoveCursor(x, 0); + if (is_selected) + surface.AttributeOn(A_REVERSE); + surface.PutChar('['); + surface.PutCString(m_label.c_str()); + surface.PutChar(']'); + if (is_selected) + surface.AttributeOff(A_REVERSE); + } + + void Execute(Window &window) { m_action(window); } + + const std::string &GetLabel() { return m_label; } + +protected: + std::string m_label; + std::function<void(Window &)> m_action; +}; + +class FormDelegate { +public: + FormDelegate() = default; + + virtual ~FormDelegate() = default; + + virtual std::string GetName() = 0; + + virtual void UpdateFieldsVisibility() {} + + FieldDelegate *GetField(uint32_t field_index) { + if (field_index < m_fields.size()) + return m_fields[field_index].get(); + return nullptr; + } + + FormAction &GetAction(int action_index) { return m_actions[action_index]; } + + int GetNumberOfFields() { return m_fields.size(); } + + int GetNumberOfActions() { return m_actions.size(); } + + bool HasError() { return !m_error.empty(); } + + void ClearError() { m_error.clear(); } + + const std::string &GetError() { return m_error; } + + void SetError(const char *error) { m_error = error; } + + // If all fields are valid, true is returned. Otherwise, an error message is + // set and false is returned. This method is usually called at the start of an + // action that requires valid fields. + bool CheckFieldsValidity() { + for (int i = 0; i < GetNumberOfFields(); i++) { + GetField(i)->FieldDelegateExitCallback(); + if (GetField(i)->FieldDelegateHasError()) { + SetError("Some fields are invalid!"); + return false; + } + } + return true; + } + + // Factory methods to create and add fields of specific types. + + TextFieldDelegate *AddTextField(const char *label, const char *content, + bool required) { + TextFieldDelegate *delegate = + new TextFieldDelegate(label, content, required); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + FileFieldDelegate *AddFileField(const char *label, const char *content, + bool need_to_exist, bool required) { + FileFieldDelegate *delegate = + new FileFieldDelegate(label, content, need_to_exist, required); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + DirectoryFieldDelegate *AddDirectoryField(const char *label, + const char *content, + bool need_to_exist, bool required) { + DirectoryFieldDelegate *delegate = + new DirectoryFieldDelegate(label, content, need_to_exist, required); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + ArchFieldDelegate *AddArchField(const char *label, const char *content, + bool required) { + ArchFieldDelegate *delegate = + new ArchFieldDelegate(label, content, required); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + IntegerFieldDelegate *AddIntegerField(const char *label, int content, + bool required) { + IntegerFieldDelegate *delegate = + new IntegerFieldDelegate(label, content, required); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + BooleanFieldDelegate *AddBooleanField(const char *label, bool content) { + BooleanFieldDelegate *delegate = new BooleanFieldDelegate(label, content); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + LazyBooleanFieldDelegate *AddLazyBooleanField(const char *label, + const char *calculate_label) { + LazyBooleanFieldDelegate *delegate = + new LazyBooleanFieldDelegate(label, calculate_label); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + ChoicesFieldDelegate *AddChoicesField(const char *label, int height, + std::vector<std::string> choices) { + ChoicesFieldDelegate *delegate = + new ChoicesFieldDelegate(label, height, choices); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + PlatformPluginFieldDelegate *AddPlatformPluginField(Debugger &debugger) { + PlatformPluginFieldDelegate *delegate = + new PlatformPluginFieldDelegate(debugger); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + ProcessPluginFieldDelegate *AddProcessPluginField() { + ProcessPluginFieldDelegate *delegate = new ProcessPluginFieldDelegate(); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + template <class T> + ListFieldDelegate<T> *AddListField(const char *label, T default_field) { + ListFieldDelegate<T> *delegate = + new ListFieldDelegate<T>(label, default_field); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + ArgumentsFieldDelegate *AddArgumentsField() { + ArgumentsFieldDelegate *delegate = new ArgumentsFieldDelegate(); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + template <class K, class V> + MappingFieldDelegate<K, V> *AddMappingField(K key_field, V value_field) { + MappingFieldDelegate<K, V> *delegate = + new MappingFieldDelegate<K, V>(key_field, value_field); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + EnvironmentVariableNameFieldDelegate * + AddEnvironmentVariableNameField(const char *content) { + EnvironmentVariableNameFieldDelegate *delegate = + new EnvironmentVariableNameFieldDelegate(content); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + EnvironmentVariableFieldDelegate *AddEnvironmentVariableField() { + EnvironmentVariableFieldDelegate *delegate = + new EnvironmentVariableFieldDelegate(); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + EnvironmentVariableListFieldDelegate * + AddEnvironmentVariableListField(const char *label) { + EnvironmentVariableListFieldDelegate *delegate = + new EnvironmentVariableListFieldDelegate(label); + m_fields.push_back(FieldDelegateUP(delegate)); + return delegate; + } + + // Factory methods for adding actions. + + void AddAction(const char *label, std::function<void(Window &)> action) { + m_actions.push_back(FormAction(label, action)); + } + +protected: + std::vector<FieldDelegateUP> m_fields; + std::vector<FormAction> m_actions; + // Optional error message. If empty, form is considered to have no error. + std::string m_error; +}; + +typedef std::shared_ptr<FormDelegate> FormDelegateSP; + +class FormWindowDelegate : public WindowDelegate { +public: + FormWindowDelegate(FormDelegateSP &delegate_sp) : m_delegate_sp(delegate_sp) { + assert(m_delegate_sp->GetNumberOfActions() > 0); + if (m_delegate_sp->GetNumberOfFields() > 0) + m_selection_type = SelectionType::Field; + else + m_selection_type = SelectionType::Action; + } + + // Signify which element is selected. If a field or an action is selected, + // then m_selection_index signifies the particular field or action that is + // selected. + enum class SelectionType { Field, Action }; + + // A form window is padded by one character from all sides. First, if an error + // message exists, it is drawn followed by a separator. Then one or more + // fields are drawn. Finally, all available actions are drawn on a single + // line. + // + // ___<Form Name>_________________________________________________ + // | | + // | - Error message if it exists. | + // |-------------------------------------------------------------| + // | Form elements here. | + // | Form actions here. | + // | | + // |______________________________________[Press Esc to cancel]__| + // + + // One line for the error and another for the horizontal line. + int GetErrorHeight() { + if (m_delegate_sp->HasError()) + return 2; + return 0; + } + + // Actions span a single line. + int GetActionsHeight() { + if (m_delegate_sp->GetNumberOfActions() > 0) + return 1; + return 0; + } + + // Get the total number of needed lines to draw the contents. + int GetContentHeight() { + int height = 0; + height += GetErrorHeight(); + for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) { + if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible()) + continue; + height += m_delegate_sp->GetField(i)->FieldDelegateGetHeight(); + } + height += GetActionsHeight(); + return height; + } + + ScrollContext GetScrollContext() { + if (m_selection_type == SelectionType::Action) + return ScrollContext(GetContentHeight() - 1); + + FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); + ScrollContext context = field->FieldDelegateGetScrollContext(); + + int offset = GetErrorHeight(); + for (int i = 0; i < m_selection_index; i++) { + if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible()) + continue; + offset += m_delegate_sp->GetField(i)->FieldDelegateGetHeight(); + } + context.Offset(offset); + + // If the context is touching the error, include the error in the context as + // well. + if (context.start == GetErrorHeight()) + context.start = 0; + + return context; + } + + void UpdateScrolling(Surface &surface) { + ScrollContext context = GetScrollContext(); + int content_height = GetContentHeight(); + int surface_height = surface.GetHeight(); + int visible_height = std::min(content_height, surface_height); + int last_visible_line = m_first_visible_line + visible_height - 1; + + // If the last visible line is bigger than the content, then it is invalid + // and needs to be set to the last line in the content. This can happen when + // a field has shrunk in height. + if (last_visible_line > content_height - 1) { + m_first_visible_line = content_height - visible_height; + } + + if (context.start < m_first_visible_line) { + m_first_visible_line = context.start; + return; + } + + if (context.end > last_visible_line) { + m_first_visible_line = context.end - visible_height + 1; + } + } + + void DrawError(Surface &surface) { + if (!m_delegate_sp->HasError()) + return; + surface.MoveCursor(0, 0); + surface.AttributeOn(COLOR_PAIR(RedOnBlack)); + surface.PutChar(ACS_DIAMOND); + surface.PutChar(' '); + surface.PutCStringTruncated(1, m_delegate_sp->GetError().c_str()); + surface.AttributeOff(COLOR_PAIR(RedOnBlack)); + + surface.MoveCursor(0, 1); + surface.HorizontalLine(surface.GetWidth()); + } + + void DrawFields(Surface &surface) { + int line = 0; + int width = surface.GetWidth(); + bool a_field_is_selected = m_selection_type == SelectionType::Field; + for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) { + FieldDelegate *field = m_delegate_sp->GetField(i); + if (!field->FieldDelegateIsVisible()) + continue; + bool is_field_selected = a_field_is_selected && m_selection_index == i; + int height = field->FieldDelegateGetHeight(); + Rect bounds = Rect(Point(0, line), Size(width, height)); + Surface field_surface = surface.SubSurface(bounds); + field->FieldDelegateDraw(field_surface, is_field_selected); + line += height; + } + } + + void DrawActions(Surface &surface) { + int number_of_actions = m_delegate_sp->GetNumberOfActions(); + int width = surface.GetWidth() / number_of_actions; + bool an_action_is_selected = m_selection_type == SelectionType::Action; + int x = 0; + for (int i = 0; i < number_of_actions; i++) { + bool is_action_selected = an_action_is_selected && m_selection_index == i; + FormAction &action = m_delegate_sp->GetAction(i); + Rect bounds = Rect(Point(x, 0), Size(width, 1)); + Surface action_surface = surface.SubSurface(bounds); + action.Draw(action_surface, is_action_selected); + x += width; + } + } + + void DrawElements(Surface &surface) { + Rect frame = surface.GetFrame(); + Rect fields_bounds, actions_bounds; + frame.HorizontalSplit(surface.GetHeight() - GetActionsHeight(), + fields_bounds, actions_bounds); + Surface fields_surface = surface.SubSurface(fields_bounds); + Surface actions_surface = surface.SubSurface(actions_bounds); + + DrawFields(fields_surface); + DrawActions(actions_surface); + } + + // Contents are first drawn on a pad. Then a subset of that pad is copied to + // the derived window starting at the first visible line. This essentially + // provides scrolling functionality. + void DrawContent(Surface &surface) { + UpdateScrolling(surface); + + int width = surface.GetWidth(); + int height = GetContentHeight(); + Pad pad = Pad(Size(width, height)); + + Rect frame = pad.GetFrame(); + Rect error_bounds, elements_bounds; + frame.HorizontalSplit(GetErrorHeight(), error_bounds, elements_bounds); + Surface error_surface = pad.SubSurface(error_bounds); + Surface elements_surface = pad.SubSurface(elements_bounds); + + DrawError(error_surface); + DrawElements(elements_surface); + + int copy_height = std::min(surface.GetHeight(), pad.GetHeight()); + pad.CopyToSurface(surface, Point(0, m_first_visible_line), Point(), + Size(width, copy_height)); + } + + void DrawSubmitHint(Surface &surface, bool is_active) { + surface.MoveCursor(2, surface.GetHeight() - 1); + if (is_active) + surface.AttributeOn(A_BOLD | COLOR_PAIR(BlackOnWhite)); + surface.Printf("[Press Alt+Enter to %s]", + m_delegate_sp->GetAction(0).GetLabel().c_str()); + if (is_active) + surface.AttributeOff(A_BOLD | COLOR_PAIR(BlackOnWhite)); + } + + bool WindowDelegateDraw(Window &window, bool force) override { + m_delegate_sp->UpdateFieldsVisibility(); + + window.Erase(); + + window.DrawTitleBox(m_delegate_sp->GetName().c_str(), + "Press Esc to Cancel"); + DrawSubmitHint(window, window.IsActive()); + + Rect content_bounds = window.GetFrame(); + content_bounds.Inset(2, 2); + Surface content_surface = window.SubSurface(content_bounds); + + DrawContent(content_surface); + return true; + } + + void SkipNextHiddenFields() { + while (true) { + if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible()) + return; + + if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) { + m_selection_type = SelectionType::Action; + m_selection_index = 0; + return; + } + + m_selection_index++; + } + } + + HandleCharResult SelectNext(int key) { + if (m_selection_type == SelectionType::Action) { + if (m_selection_index < m_delegate_sp->GetNumberOfActions() - 1) { + m_selection_index++; + return eKeyHandled; + } + + m_selection_index = 0; + m_selection_type = SelectionType::Field; + SkipNextHiddenFields(); + if (m_selection_type == SelectionType::Field) { + FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index); + next_field->FieldDelegateSelectFirstElement(); + } + return eKeyHandled; + } + + FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); + if (!field->FieldDelegateOnLastOrOnlyElement()) { + return field->FieldDelegateHandleChar(key); + } + + field->FieldDelegateExitCallback(); + + if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) { + m_selection_type = SelectionType::Action; + m_selection_index = 0; + return eKeyHandled; + } + + m_selection_index++; + SkipNextHiddenFields(); + + if (m_selection_type == SelectionType::Field) { + FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index); + next_field->FieldDelegateSelectFirstElement(); + } + + return eKeyHandled; + } + + void SkipPreviousHiddenFields() { + while (true) { + if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible()) + return; + + if (m_selection_index == 0) { + m_selection_type = SelectionType::Action; + m_selection_index = 0; + return; + } + + m_selection_index--; + } + } + + HandleCharResult SelectPrevious(int key) { + if (m_selection_type == SelectionType::Action) { + if (m_selection_index > 0) { + m_selection_index--; + return eKeyHandled; + } + m_selection_index = m_delegate_sp->GetNumberOfFields() - 1; + m_selection_type = SelectionType::Field; + SkipPreviousHiddenFields(); + if (m_selection_type == SelectionType::Field) { + FieldDelegate *previous_field = + m_delegate_sp->GetField(m_selection_index); + previous_field->FieldDelegateSelectLastElement(); + } + return eKeyHandled; + } + + FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); + if (!field->FieldDelegateOnFirstOrOnlyElement()) { + return field->FieldDelegateHandleChar(key); + } + + field->FieldDelegateExitCallback(); + + if (m_selection_index == 0) { + m_selection_type = SelectionType::Action; + m_selection_index = m_delegate_sp->GetNumberOfActions() - 1; + return eKeyHandled; + } + + m_selection_index--; + SkipPreviousHiddenFields(); + + if (m_selection_type == SelectionType::Field) { + FieldDelegate *previous_field = + m_delegate_sp->GetField(m_selection_index); + previous_field->FieldDelegateSelectLastElement(); + } + + return eKeyHandled; + } + + void ExecuteAction(Window &window, int index) { + FormAction &action = m_delegate_sp->GetAction(index); + action.Execute(window); + if (m_delegate_sp->HasError()) { + m_first_visible_line = 0; + m_selection_index = 0; + m_selection_type = SelectionType::Field; + } + } + + // Always return eKeyHandled to absorb all events since forms are always + // added as pop-ups that should take full control until canceled or submitted. + HandleCharResult WindowDelegateHandleChar(Window &window, int key) override { + switch (key) { + case '\r': + case '\n': + case KEY_ENTER: + if (m_selection_type == SelectionType::Action) { + ExecuteAction(window, m_selection_index); + return eKeyHandled; + } + break; + case KEY_ALT_ENTER: + ExecuteAction(window, 0); + return eKeyHandled; + case '\t': + SelectNext(key); + return eKeyHandled; + case KEY_SHIFT_TAB: + SelectPrevious(key); + return eKeyHandled; + case KEY_ESCAPE: + window.GetParent()->RemoveSubWindow(&window); + return eKeyHandled; + default: + break; + } + + // If the key wasn't handled and one of the fields is selected, pass the key + // to that field. + if (m_selection_type == SelectionType::Field) { + FieldDelegate *field = m_delegate_sp->GetField(m_selection_index); + if (field->FieldDelegateHandleChar(key) == eKeyHandled) + return eKeyHandled; + } + + // If the key wasn't handled by the possibly selected field, handle some + // extra keys for navigation. + switch (key) { + case KEY_DOWN: + SelectNext(key); + return eKeyHandled; + case KEY_UP: + SelectPrevious(key); + return eKeyHandled; + default: + break; + } + + return eKeyHandled; + } + +protected: + FormDelegateSP m_delegate_sp; + // The index of the currently selected SelectionType. + int m_selection_index = 0; + // See SelectionType class enum. + SelectionType m_selection_type; + // The first visible line from the pad. + int m_first_visible_line = 0; +}; + +/////////////////////////// +// Form Delegate Instances +/////////////////////////// + +class DetachOrKillProcessFormDelegate : public FormDelegate { +public: + DetachOrKillProcessFormDelegate(Process *process) : m_process(process) { + SetError("There is a running process, either detach or kill it."); + + m_keep_stopped_field = + AddBooleanField("Keep process stopped when detaching.", false); + + AddAction("Detach", [this](Window &window) { Detach(window); }); + AddAction("Kill", [this](Window &window) { Kill(window); }); + } + + std::string GetName() override { return "Detach/Kill Process"; } + + void Kill(Window &window) { + Status destroy_status(m_process->Destroy(false)); + if (destroy_status.Fail()) { + SetError("Failed to kill process."); + return; + } + window.GetParent()->RemoveSubWindow(&window); + } + + void Detach(Window &window) { + Status detach_status(m_process->Detach(m_keep_stopped_field->GetBoolean())); + if (detach_status.Fail()) { + SetError("Failed to detach from process."); + return; + } + window.GetParent()->RemoveSubWindow(&window); + } + +protected: + Process *m_process; + BooleanFieldDelegate *m_keep_stopped_field; +}; + +class ProcessAttachFormDelegate : public FormDelegate { +public: + ProcessAttachFormDelegate(Debugger &debugger, WindowSP main_window_sp) + : m_debugger(debugger), m_main_window_sp(main_window_sp) { + std::vector<std::string> types; + types.push_back(std::string("Name")); + types.push_back(std::string("PID")); + m_type_field = AddChoicesField("Attach By", 2, types); + m_pid_field = AddIntegerField("PID", 0, true); + m_name_field = + AddTextField("Process Name", GetDefaultProcessName().c_str(), true); + m_continue_field = AddBooleanField("Continue once attached.", false); + m_wait_for_field = AddBooleanField("Wait for process to launch.", false); + m_include_existing_field = + AddBooleanField("Include existing processes.", false); + m_show_advanced_field = AddBooleanField("Show advanced settings.", false); + m_plugin_field = AddProcessPluginField(); + + AddAction("Attach", [this](Window &window) { Attach(window); }); + } + + std::string GetName() override { return "Attach Process"; } + + void UpdateFieldsVisibility() override { + if (m_type_field->GetChoiceContent() == "Name") { + m_pid_field->FieldDelegateHide(); + m_name_field->FieldDelegateShow(); + m_wait_for_field->FieldDelegateShow(); + if (m_wait_for_field->GetBoolean()) + m_include_existing_field->FieldDelegateShow(); + else + m_include_existing_field->FieldDelegateHide(); + } else { + m_pid_field->FieldDelegateShow(); + m_name_field->FieldDelegateHide(); + m_wait_for_field->FieldDelegateHide(); + m_include_existing_field->FieldDelegateHide(); + } + if (m_show_advanced_field->GetBoolean()) + m_plugin_field->FieldDelegateShow(); + else + m_plugin_field->FieldDelegateHide(); + } + + // Get the basename of the target's main executable if available, empty string + // otherwise. + std::string GetDefaultProcessName() { + Target *target = m_debugger.GetSelectedTarget().get(); + if (target == nullptr) + return ""; + + ModuleSP module_sp = target->GetExecutableModule(); + if (!module_sp->IsExecutable()) + return ""; + + return module_sp->GetFileSpec().GetFilename().AsCString(); + } + + bool StopRunningProcess() { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + + if (!exe_ctx.HasProcessScope()) + return false; + + Process *process = exe_ctx.GetProcessPtr(); + if (!(process && process->IsAlive())) + return false; + + FormDelegateSP form_delegate_sp = + FormDelegateSP(new DetachOrKillProcessFormDelegate(process)); + Rect bounds = m_main_window_sp->GetCenteredRect(85, 8); + WindowSP form_window_sp = m_main_window_sp->CreateSubWindow( + form_delegate_sp->GetName().c_str(), bounds, true); + WindowDelegateSP window_delegate_sp = + WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); + form_window_sp->SetDelegate(window_delegate_sp); + + return true; + } + + Target *GetTarget() { + Target *target = m_debugger.GetSelectedTarget().get(); + + if (target != nullptr) + return target; + + TargetSP new_target_sp; + m_debugger.GetTargetList().CreateTarget( + m_debugger, "", "", eLoadDependentsNo, nullptr, new_target_sp); + + target = new_target_sp.get(); + + if (target == nullptr) + SetError("Failed to create target."); + + m_debugger.GetTargetList().SetSelectedTarget(new_target_sp); + + return target; + } + + ProcessAttachInfo GetAttachInfo() { + ProcessAttachInfo attach_info; + attach_info.SetContinueOnceAttached(m_continue_field->GetBoolean()); + if (m_type_field->GetChoiceContent() == "Name") { + attach_info.GetExecutableFile().SetFile(m_name_field->GetText(), + FileSpec::Style::native); + attach_info.SetWaitForLaunch(m_wait_for_field->GetBoolean()); + if (m_wait_for_field->GetBoolean()) + attach_info.SetIgnoreExisting(!m_include_existing_field->GetBoolean()); + } else { + attach_info.SetProcessID(m_pid_field->GetInteger()); + } + attach_info.SetProcessPluginName(m_plugin_field->GetPluginName()); + + return attach_info; + } + + void Attach(Window &window) { + ClearError(); + + bool all_fields_are_valid = CheckFieldsValidity(); + if (!all_fields_are_valid) + return; + + bool process_is_running = StopRunningProcess(); + if (process_is_running) + return; + + Target *target = GetTarget(); + if (HasError()) + return; + + StreamString stream; + ProcessAttachInfo attach_info = GetAttachInfo(); + Status status = target->Attach(attach_info, &stream); + + if (status.Fail()) { + SetError(status.AsCString()); + return; + } + + ProcessSP process_sp(target->GetProcessSP()); + if (!process_sp) { + SetError("Attached sucessfully but target has no process."); + return; + } + + if (attach_info.GetContinueOnceAttached()) + process_sp->Resume(); + + window.GetParent()->RemoveSubWindow(&window); + } + +protected: + Debugger &m_debugger; + WindowSP m_main_window_sp; + + ChoicesFieldDelegate *m_type_field; + IntegerFieldDelegate *m_pid_field; + TextFieldDelegate *m_name_field; + BooleanFieldDelegate *m_continue_field; + BooleanFieldDelegate *m_wait_for_field; + BooleanFieldDelegate *m_include_existing_field; + BooleanFieldDelegate *m_show_advanced_field; + ProcessPluginFieldDelegate *m_plugin_field; +}; + +class TargetCreateFormDelegate : public FormDelegate { +public: + TargetCreateFormDelegate(Debugger &debugger) : m_debugger(debugger) { + m_executable_field = AddFileField("Executable", "", /*need_to_exist=*/true, + /*required=*/true); + m_core_file_field = AddFileField("Core File", "", /*need_to_exist=*/true, + /*required=*/false); + m_symbol_file_field = AddFileField( + "Symbol File", "", /*need_to_exist=*/true, /*required=*/false); + m_show_advanced_field = AddBooleanField("Show advanced settings.", false); + m_remote_file_field = AddFileField( + "Remote File", "", /*need_to_exist=*/false, /*required=*/false); + m_arch_field = AddArchField("Architecture", "", /*required=*/false); + m_platform_field = AddPlatformPluginField(debugger); + m_load_dependent_files_field = + AddChoicesField("Load Dependents", 3, GetLoadDependentFilesChoices()); + + AddAction("Create", [this](Window &window) { CreateTarget(window); }); + } + + std::string GetName() override { return "Create Target"; } + + void UpdateFieldsVisibility() override { + if (m_show_advanced_field->GetBoolean()) { + m_remote_file_field->FieldDelegateShow(); + m_arch_field->FieldDelegateShow(); + m_platform_field->FieldDelegateShow(); + m_load_dependent_files_field->FieldDelegateShow(); + } else { + m_remote_file_field->FieldDelegateHide(); + m_arch_field->FieldDelegateHide(); + m_platform_field->FieldDelegateHide(); + m_load_dependent_files_field->FieldDelegateHide(); + } + } + + static constexpr const char *kLoadDependentFilesNo = "No"; + static constexpr const char *kLoadDependentFilesYes = "Yes"; + static constexpr const char *kLoadDependentFilesExecOnly = "Executable only"; + + std::vector<std::string> GetLoadDependentFilesChoices() { + std::vector<std::string> load_dependents_options; + load_dependents_options.push_back(kLoadDependentFilesExecOnly); + load_dependents_options.push_back(kLoadDependentFilesYes); + load_dependents_options.push_back(kLoadDependentFilesNo); + return load_dependents_options; + } + + LoadDependentFiles GetLoadDependentFiles() { + std::string choice = m_load_dependent_files_field->GetChoiceContent(); + if (choice == kLoadDependentFilesNo) + return eLoadDependentsNo; + if (choice == kLoadDependentFilesYes) + return eLoadDependentsYes; + return eLoadDependentsDefault; + } + + OptionGroupPlatform GetPlatformOptions() { + OptionGroupPlatform platform_options(false); + platform_options.SetPlatformName(m_platform_field->GetPluginName().c_str()); + return platform_options; + } + + TargetSP GetTarget() { + OptionGroupPlatform platform_options = GetPlatformOptions(); + TargetSP target_sp; + Status status = m_debugger.GetTargetList().CreateTarget( + m_debugger, m_executable_field->GetPath(), + m_arch_field->GetArchString(), GetLoadDependentFiles(), + &platform_options, target_sp); + + if (status.Fail()) { + SetError(status.AsCString()); + return nullptr; + } + + m_debugger.GetTargetList().SetSelectedTarget(target_sp); + + return target_sp; + } + + void SetSymbolFile(TargetSP target_sp) { + if (!m_symbol_file_field->IsSpecified()) + return; + + ModuleSP module_sp(target_sp->GetExecutableModule()); + if (!module_sp) + return; + + module_sp->SetSymbolFileFileSpec( + m_symbol_file_field->GetResolvedFileSpec()); + } + + void SetCoreFile(TargetSP target_sp) { + if (!m_core_file_field->IsSpecified()) + return; + + FileSpec core_file_spec = m_core_file_field->GetResolvedFileSpec(); + + FileSpec core_file_directory_spec; + core_file_directory_spec.SetDirectory(core_file_spec.GetDirectory()); + target_sp->AppendExecutableSearchPaths(core_file_directory_spec); + + ProcessSP process_sp(target_sp->CreateProcess( + m_debugger.GetListener(), llvm::StringRef(), &core_file_spec, false)); + + if (!process_sp) { + SetError("Unknown core file format!"); + return; + } + + Status status = process_sp->LoadCore(); + if (status.Fail()) { + SetError("Unknown core file format!"); + return; + } + } + + void SetRemoteFile(TargetSP target_sp) { + if (!m_remote_file_field->IsSpecified()) + return; + + ModuleSP module_sp(target_sp->GetExecutableModule()); + if (!module_sp) + return; + + FileSpec remote_file_spec = m_remote_file_field->GetFileSpec(); + module_sp->SetPlatformFileSpec(remote_file_spec); + } + + void RemoveTarget(TargetSP target_sp) { + m_debugger.GetTargetList().DeleteTarget(target_sp); + } + + void CreateTarget(Window &window) { + ClearError(); + + bool all_fields_are_valid = CheckFieldsValidity(); + if (!all_fields_are_valid) + return; + + TargetSP target_sp = GetTarget(); + if (HasError()) + return; + + SetSymbolFile(target_sp); + if (HasError()) { + RemoveTarget(target_sp); + return; + } + + SetCoreFile(target_sp); + if (HasError()) { + RemoveTarget(target_sp); + return; + } + + SetRemoteFile(target_sp); + if (HasError()) { + RemoveTarget(target_sp); + return; + } + + window.GetParent()->RemoveSubWindow(&window); + } + +protected: + Debugger &m_debugger; + + FileFieldDelegate *m_executable_field; + FileFieldDelegate *m_core_file_field; + FileFieldDelegate *m_symbol_file_field; + BooleanFieldDelegate *m_show_advanced_field; + FileFieldDelegate *m_remote_file_field; + ArchFieldDelegate *m_arch_field; + PlatformPluginFieldDelegate *m_platform_field; + ChoicesFieldDelegate *m_load_dependent_files_field; +}; + +class ProcessLaunchFormDelegate : public FormDelegate { +public: + ProcessLaunchFormDelegate(Debugger &debugger, WindowSP main_window_sp) + : m_debugger(debugger), m_main_window_sp(main_window_sp) { + + m_arguments_field = AddArgumentsField(); + SetArgumentsFieldDefaultValue(); + m_target_environment_field = + AddEnvironmentVariableListField("Target Environment Variables"); + SetTargetEnvironmentFieldDefaultValue(); + m_working_directory_field = AddDirectoryField( + "Working Directory", GetDefaultWorkingDirectory().c_str(), true, false); + + m_show_advanced_field = AddBooleanField("Show advanced settings.", false); + + m_stop_at_entry_field = AddBooleanField("Stop at entry point.", false); + m_detach_on_error_field = + AddBooleanField("Detach on error.", GetDefaultDetachOnError()); + m_disable_aslr_field = + AddBooleanField("Disable ASLR", GetDefaultDisableASLR()); + m_plugin_field = AddProcessPluginField(); + m_arch_field = AddArchField("Architecture", "", false); + m_shell_field = AddFileField("Shell", "", true, false); + m_expand_shell_arguments_field = + AddBooleanField("Expand shell arguments.", false); + + m_disable_standard_io_field = + AddBooleanField("Disable Standard IO", GetDefaultDisableStandardIO()); + m_standard_output_field = + AddFileField("Standard Output File", "", /*need_to_exist=*/false, + /*required=*/false); + m_standard_error_field = + AddFileField("Standard Error File", "", /*need_to_exist=*/false, + /*required=*/false); + m_standard_input_field = + AddFileField("Standard Input File", "", /*need_to_exist=*/false, + /*required=*/false); + + m_show_inherited_environment_field = + AddBooleanField("Show inherited environment variables.", false); + m_inherited_environment_field = + AddEnvironmentVariableListField("Inherited Environment Variables"); + SetInheritedEnvironmentFieldDefaultValue(); + + AddAction("Launch", [this](Window &window) { Launch(window); }); + } + + std::string GetName() override { return "Launch Process"; } + + void UpdateFieldsVisibility() override { + if (m_show_advanced_field->GetBoolean()) { + m_stop_at_entry_field->FieldDelegateShow(); + m_detach_on_error_field->FieldDelegateShow(); + m_disable_aslr_field->FieldDelegateShow(); + m_plugin_field->FieldDelegateShow(); + m_arch_field->FieldDelegateShow(); + m_shell_field->FieldDelegateShow(); + m_expand_shell_arguments_field->FieldDelegateShow(); + m_disable_standard_io_field->FieldDelegateShow(); + if (m_disable_standard_io_field->GetBoolean()) { + m_standard_input_field->FieldDelegateHide(); + m_standard_output_field->FieldDelegateHide(); + m_standard_error_field->FieldDelegateHide(); + } else { + m_standard_input_field->FieldDelegateShow(); + m_standard_output_field->FieldDelegateShow(); + m_standard_error_field->FieldDelegateShow(); + } + m_show_inherited_environment_field->FieldDelegateShow(); + if (m_show_inherited_environment_field->GetBoolean()) + m_inherited_environment_field->FieldDelegateShow(); + else + m_inherited_environment_field->FieldDelegateHide(); + } else { + m_stop_at_entry_field->FieldDelegateHide(); + m_detach_on_error_field->FieldDelegateHide(); + m_disable_aslr_field->FieldDelegateHide(); + m_plugin_field->FieldDelegateHide(); + m_arch_field->FieldDelegateHide(); + m_shell_field->FieldDelegateHide(); + m_expand_shell_arguments_field->FieldDelegateHide(); + m_disable_standard_io_field->FieldDelegateHide(); + m_standard_input_field->FieldDelegateHide(); + m_standard_output_field->FieldDelegateHide(); + m_standard_error_field->FieldDelegateHide(); + m_show_inherited_environment_field->FieldDelegateHide(); + m_inherited_environment_field->FieldDelegateHide(); + } + } + + // Methods for setting the default value of the fields. + + void SetArgumentsFieldDefaultValue() { + TargetSP target = m_debugger.GetSelectedTarget(); + if (target == nullptr) + return; + + const Args &target_arguments = + target->GetProcessLaunchInfo().GetArguments(); + m_arguments_field->AddArguments(target_arguments); + } + + void SetTargetEnvironmentFieldDefaultValue() { + TargetSP target = m_debugger.GetSelectedTarget(); + if (target == nullptr) + return; + + const Environment &target_environment = target->GetTargetEnvironment(); + m_target_environment_field->AddEnvironmentVariables(target_environment); + } + + void SetInheritedEnvironmentFieldDefaultValue() { + TargetSP target = m_debugger.GetSelectedTarget(); + if (target == nullptr) + return; + + const Environment &inherited_environment = + target->GetInheritedEnvironment(); + m_inherited_environment_field->AddEnvironmentVariables( + inherited_environment); + } + + std::string GetDefaultWorkingDirectory() { + TargetSP target = m_debugger.GetSelectedTarget(); + if (target == nullptr) + return ""; + + PlatformSP platform = target->GetPlatform(); + return platform->GetWorkingDirectory().GetPath(); + } + + bool GetDefaultDisableASLR() { + TargetSP target = m_debugger.GetSelectedTarget(); + if (target == nullptr) + return false; + + return target->GetDisableASLR(); + } + + bool GetDefaultDisableStandardIO() { + TargetSP target = m_debugger.GetSelectedTarget(); + if (target == nullptr) + return true; + + return target->GetDisableSTDIO(); + } + + bool GetDefaultDetachOnError() { + TargetSP target = m_debugger.GetSelectedTarget(); + if (target == nullptr) + return true; + + return target->GetDetachOnError(); + } + + // Methods for getting the necessary information and setting them to the + // ProcessLaunchInfo. + + void GetExecutableSettings(ProcessLaunchInfo &launch_info) { + TargetSP target = m_debugger.GetSelectedTarget(); + ModuleSP executable_module = target->GetExecutableModule(); + llvm::StringRef target_settings_argv0 = target->GetArg0(); + + if (!target_settings_argv0.empty()) { + launch_info.GetArguments().AppendArgument(target_settings_argv0); + launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(), + false); + return; + } + + launch_info.SetExecutableFile(executable_module->GetPlatformFileSpec(), + true); + } + + void GetArguments(ProcessLaunchInfo &launch_info) { + TargetSP target = m_debugger.GetSelectedTarget(); + Args arguments = m_arguments_field->GetArguments(); + launch_info.GetArguments().AppendArguments(arguments); + } + + void GetEnvironment(ProcessLaunchInfo &launch_info) { + Environment target_environment = + m_target_environment_field->GetEnvironment(); + Environment inherited_environment = + m_inherited_environment_field->GetEnvironment(); + launch_info.GetEnvironment().insert(target_environment.begin(), + target_environment.end()); + launch_info.GetEnvironment().insert(inherited_environment.begin(), + inherited_environment.end()); + } + + void GetWorkingDirectory(ProcessLaunchInfo &launch_info) { + if (m_working_directory_field->IsSpecified()) + launch_info.SetWorkingDirectory( + m_working_directory_field->GetResolvedFileSpec()); + } + + void GetStopAtEntry(ProcessLaunchInfo &launch_info) { + if (m_stop_at_entry_field->GetBoolean()) + launch_info.GetFlags().Set(eLaunchFlagStopAtEntry); + else + launch_info.GetFlags().Clear(eLaunchFlagStopAtEntry); + } + + void GetDetachOnError(ProcessLaunchInfo &launch_info) { + if (m_detach_on_error_field->GetBoolean()) + launch_info.GetFlags().Set(eLaunchFlagDetachOnError); + else + launch_info.GetFlags().Clear(eLaunchFlagDetachOnError); + } + + void GetDisableASLR(ProcessLaunchInfo &launch_info) { + if (m_disable_aslr_field->GetBoolean()) + launch_info.GetFlags().Set(eLaunchFlagDisableASLR); + else + launch_info.GetFlags().Clear(eLaunchFlagDisableASLR); + } + + void GetPlugin(ProcessLaunchInfo &launch_info) { + launch_info.SetProcessPluginName(m_plugin_field->GetPluginName()); + } + + void GetArch(ProcessLaunchInfo &launch_info) { + if (!m_arch_field->IsSpecified()) + return; + + TargetSP target_sp = m_debugger.GetSelectedTarget(); + PlatformSP platform_sp = + target_sp ? target_sp->GetPlatform() : PlatformSP(); + launch_info.GetArchitecture() = Platform::GetAugmentedArchSpec( + platform_sp.get(), m_arch_field->GetArchString()); + } + + void GetShell(ProcessLaunchInfo &launch_info) { + if (!m_shell_field->IsSpecified()) + return; + + launch_info.SetShell(m_shell_field->GetResolvedFileSpec()); + launch_info.SetShellExpandArguments( + m_expand_shell_arguments_field->GetBoolean()); + } + + void GetStandardIO(ProcessLaunchInfo &launch_info) { + if (m_disable_standard_io_field->GetBoolean()) { + launch_info.GetFlags().Set(eLaunchFlagDisableSTDIO); + return; + } + + FileAction action; + if (m_standard_input_field->IsSpecified()) { + if (action.Open(STDIN_FILENO, m_standard_input_field->GetFileSpec(), true, + false)) + launch_info.AppendFileAction(action); + } + if (m_standard_output_field->IsSpecified()) { + if (action.Open(STDOUT_FILENO, m_standard_output_field->GetFileSpec(), + false, true)) + launch_info.AppendFileAction(action); + } + if (m_standard_error_field->IsSpecified()) { + if (action.Open(STDERR_FILENO, m_standard_error_field->GetFileSpec(), + false, true)) + launch_info.AppendFileAction(action); + } + } + + void GetInheritTCC(ProcessLaunchInfo &launch_info) { + if (m_debugger.GetSelectedTarget()->GetInheritTCC()) + launch_info.GetFlags().Set(eLaunchFlagInheritTCCFromParent); + } + + ProcessLaunchInfo GetLaunchInfo() { + ProcessLaunchInfo launch_info; + + GetExecutableSettings(launch_info); + GetArguments(launch_info); + GetEnvironment(launch_info); + GetWorkingDirectory(launch_info); + GetStopAtEntry(launch_info); + GetDetachOnError(launch_info); + GetDisableASLR(launch_info); + GetPlugin(launch_info); + GetArch(launch_info); + GetShell(launch_info); + GetStandardIO(launch_info); + GetInheritTCC(launch_info); + + return launch_info; + } + + bool StopRunningProcess() { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + + if (!exe_ctx.HasProcessScope()) + return false; + + Process *process = exe_ctx.GetProcessPtr(); + if (!(process && process->IsAlive())) + return false; + + FormDelegateSP form_delegate_sp = + FormDelegateSP(new DetachOrKillProcessFormDelegate(process)); + Rect bounds = m_main_window_sp->GetCenteredRect(85, 8); + WindowSP form_window_sp = m_main_window_sp->CreateSubWindow( + form_delegate_sp->GetName().c_str(), bounds, true); + WindowDelegateSP window_delegate_sp = + WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); + form_window_sp->SetDelegate(window_delegate_sp); + + return true; + } + + Target *GetTarget() { + Target *target = m_debugger.GetSelectedTarget().get(); + + if (target == nullptr) { + SetError("No target exists!"); + return nullptr; + } + + ModuleSP exe_module_sp = target->GetExecutableModule(); + + if (exe_module_sp == nullptr) { + SetError("No executable in target!"); + return nullptr; + } + + return target; + } + + void Launch(Window &window) { + ClearError(); + + bool all_fields_are_valid = CheckFieldsValidity(); + if (!all_fields_are_valid) + return; + + bool process_is_running = StopRunningProcess(); + if (process_is_running) + return; + + Target *target = GetTarget(); + if (HasError()) + return; + + StreamString stream; + ProcessLaunchInfo launch_info = GetLaunchInfo(); + Status status = target->Launch(launch_info, &stream); + + if (status.Fail()) { + SetError(status.AsCString()); + return; + } + + ProcessSP process_sp(target->GetProcessSP()); + if (!process_sp) { + SetError("Launched successfully but target has no process!"); + return; + } + + window.GetParent()->RemoveSubWindow(&window); + } + +protected: + Debugger &m_debugger; + WindowSP m_main_window_sp; + + ArgumentsFieldDelegate *m_arguments_field; + EnvironmentVariableListFieldDelegate *m_target_environment_field; + DirectoryFieldDelegate *m_working_directory_field; + + BooleanFieldDelegate *m_show_advanced_field; + + BooleanFieldDelegate *m_stop_at_entry_field; + BooleanFieldDelegate *m_detach_on_error_field; + BooleanFieldDelegate *m_disable_aslr_field; + ProcessPluginFieldDelegate *m_plugin_field; + ArchFieldDelegate *m_arch_field; + FileFieldDelegate *m_shell_field; + BooleanFieldDelegate *m_expand_shell_arguments_field; + BooleanFieldDelegate *m_disable_standard_io_field; + FileFieldDelegate *m_standard_input_field; + FileFieldDelegate *m_standard_output_field; + FileFieldDelegate *m_standard_error_field; + + BooleanFieldDelegate *m_show_inherited_environment_field; + EnvironmentVariableListFieldDelegate *m_inherited_environment_field; +}; + +//////////// +// Searchers +//////////// + +class SearcherDelegate { +public: + SearcherDelegate() = default; + + virtual ~SearcherDelegate() = default; + + virtual int GetNumberOfMatches() = 0; + + // Get the string that will be displayed for the match at the input index. + virtual const std::string &GetMatchTextAtIndex(int index) = 0; + + // Update the matches of the search. This is executed every time the text + // field handles an event. + virtual void UpdateMatches(const std::string &text) = 0; + + // Execute the user callback given the index of some match. This is executed + // once the user selects a match. + virtual void ExecuteCallback(int match_index) = 0; +}; + +typedef std::shared_ptr<SearcherDelegate> SearcherDelegateSP; + +class SearcherWindowDelegate : public WindowDelegate { +public: + SearcherWindowDelegate(SearcherDelegateSP &delegate_sp) + : m_delegate_sp(delegate_sp), m_text_field("Search", "", false) { + ; + } + + // A completion window is padded by one character from all sides. A text field + // is first drawn for inputting the searcher request, then a list of matches + // are displayed in a scrollable list. + // + // ___<Searcher Window Name>____________________________ + // | | + // | __[Search]_______________________________________ | + // | | | | + // | |_______________________________________________| | + // | - Match 1. | + // | - Match 2. | + // | - ... | + // | | + // |____________________________[Press Esc to Cancel]__| + // + + // Get the index of the last visible match. Assuming at least one match + // exists. + int GetLastVisibleMatch(int height) { + int index = m_first_visible_match + height; + return std::min(index, m_delegate_sp->GetNumberOfMatches()) - 1; + } + + int GetNumberOfVisibleMatches(int height) { + return GetLastVisibleMatch(height) - m_first_visible_match + 1; + } + + void UpdateScrolling(Surface &surface) { + if (m_selected_match < m_first_visible_match) { + m_first_visible_match = m_selected_match; + return; + } + + int height = surface.GetHeight(); + int last_visible_match = GetLastVisibleMatch(height); + if (m_selected_match > last_visible_match) { + m_first_visible_match = m_selected_match - height + 1; + } + } + + void DrawMatches(Surface &surface) { + if (m_delegate_sp->GetNumberOfMatches() == 0) + return; + + UpdateScrolling(surface); + + int count = GetNumberOfVisibleMatches(surface.GetHeight()); + for (int i = 0; i < count; i++) { + surface.MoveCursor(1, i); + int current_match = m_first_visible_match + i; + if (current_match == m_selected_match) + surface.AttributeOn(A_REVERSE); + surface.PutCString( + m_delegate_sp->GetMatchTextAtIndex(current_match).c_str()); + if (current_match == m_selected_match) + surface.AttributeOff(A_REVERSE); + } + } + + void DrawContent(Surface &surface) { + Rect content_bounds = surface.GetFrame(); + Rect text_field_bounds, matchs_bounds; + content_bounds.HorizontalSplit(m_text_field.FieldDelegateGetHeight(), + text_field_bounds, matchs_bounds); + Surface text_field_surface = surface.SubSurface(text_field_bounds); + Surface matches_surface = surface.SubSurface(matchs_bounds); + + m_text_field.FieldDelegateDraw(text_field_surface, true); + DrawMatches(matches_surface); + } + + bool WindowDelegateDraw(Window &window, bool force) override { + window.Erase(); + + window.DrawTitleBox(window.GetName(), "Press Esc to Cancel"); + + Rect content_bounds = window.GetFrame(); + content_bounds.Inset(2, 2); + Surface content_surface = window.SubSurface(content_bounds); + + DrawContent(content_surface); + return true; + } + + void SelectNext() { + if (m_selected_match != m_delegate_sp->GetNumberOfMatches() - 1) + m_selected_match++; + } + + void SelectPrevious() { + if (m_selected_match != 0) + m_selected_match--; + } + + void ExecuteCallback(Window &window) { + m_delegate_sp->ExecuteCallback(m_selected_match); + window.GetParent()->RemoveSubWindow(&window); + } + + void UpdateMatches() { + m_delegate_sp->UpdateMatches(m_text_field.GetText()); + m_selected_match = 0; + } + + HandleCharResult WindowDelegateHandleChar(Window &window, int key) override { + switch (key) { + case '\r': + case '\n': + case KEY_ENTER: + ExecuteCallback(window); + return eKeyHandled; + case '\t': + case KEY_DOWN: + SelectNext(); + return eKeyHandled; + case KEY_SHIFT_TAB: + case KEY_UP: + SelectPrevious(); + return eKeyHandled; + case KEY_ESCAPE: + window.GetParent()->RemoveSubWindow(&window); + return eKeyHandled; + default: + break; + } + + if (m_text_field.FieldDelegateHandleChar(key) == eKeyHandled) + UpdateMatches(); + + return eKeyHandled; + } + +protected: + SearcherDelegateSP m_delegate_sp; + TextFieldDelegate m_text_field; + // The index of the currently selected match. + int m_selected_match = 0; + // The index of the first visible match. + int m_first_visible_match = 0; +}; + +////////////////////////////// +// Searcher Delegate Instances +////////////////////////////// + +// This is a searcher delegate wrapper around CommandCompletions common +// callbacks. The callbacks are only given the match string. The completion_mask +// can be a combination of lldb::CompletionType. +class CommonCompletionSearcherDelegate : public SearcherDelegate { +public: + typedef std::function<void(const std::string &)> CallbackType; + + CommonCompletionSearcherDelegate(Debugger &debugger, uint32_t completion_mask, + CallbackType callback) + : m_debugger(debugger), m_completion_mask(completion_mask), + m_callback(callback) {} + + int GetNumberOfMatches() override { return m_matches.GetSize(); } + + const std::string &GetMatchTextAtIndex(int index) override { + return m_matches[index]; + } + + void UpdateMatches(const std::string &text) override { + CompletionResult result; + CompletionRequest request(text.c_str(), text.size(), result); + lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks( + m_debugger.GetCommandInterpreter(), m_completion_mask, request, + nullptr); + result.GetMatches(m_matches); + } + + void ExecuteCallback(int match_index) override { + m_callback(m_matches[match_index]); + } + +protected: + Debugger &m_debugger; + // A compound mask from lldb::CompletionType. + uint32_t m_completion_mask; + // A callback to execute once the user selects a match. The match is passed to + // the callback as a string. + CallbackType m_callback; + StringList m_matches; +}; + +//////// +// Menus +//////// + +class MenuDelegate { +public: + virtual ~MenuDelegate() = default; + + virtual MenuActionResult MenuDelegateAction(Menu &menu) = 0; +}; + +class Menu : public WindowDelegate { +public: + enum class Type { Invalid, Bar, Item, Separator }; + + // Menubar or separator constructor + Menu(Type type); + + // Menuitem constructor + Menu(const char *name, const char *key_name, int key_value, + uint64_t identifier); + + ~Menu() override = default; + + const MenuDelegateSP &GetDelegate() const { return m_delegate_sp; } + + void SetDelegate(const MenuDelegateSP &delegate_sp) { + m_delegate_sp = delegate_sp; + } + + void RecalculateNameLengths(); + + void AddSubmenu(const MenuSP &menu_sp); + + int DrawAndRunMenu(Window &window); + + void DrawMenuTitle(Window &window, bool highlight); + + bool WindowDelegateDraw(Window &window, bool force) override; + + HandleCharResult WindowDelegateHandleChar(Window &window, int key) override; + + MenuActionResult ActionPrivate(Menu &menu) { + MenuActionResult result = MenuActionResult::NotHandled; + if (m_delegate_sp) { + result = m_delegate_sp->MenuDelegateAction(menu); + if (result != MenuActionResult::NotHandled) + return result; + } else if (m_parent) { + result = m_parent->ActionPrivate(menu); + if (result != MenuActionResult::NotHandled) + return result; + } + return m_canned_result; + } + + MenuActionResult Action() { + // Call the recursive action so it can try to handle it with the menu + // delegate, and if not, try our parent menu + return ActionPrivate(*this); + } + + void SetCannedResult(MenuActionResult result) { m_canned_result = result; } + + Menus &GetSubmenus() { return m_submenus; } + + const Menus &GetSubmenus() const { return m_submenus; } + + int GetSelectedSubmenuIndex() const { return m_selected; } + + void SetSelectedSubmenuIndex(int idx) { m_selected = idx; } + + Type GetType() const { return m_type; } + + int GetStartingColumn() const { return m_start_col; } + + void SetStartingColumn(int col) { m_start_col = col; } + + int GetKeyValue() const { return m_key_value; } + + std::string &GetName() { return m_name; } + + int GetDrawWidth() const { + return m_max_submenu_name_length + m_max_submenu_key_name_length + 8; + } + + uint64_t GetIdentifier() const { return m_identifier; } + + void SetIdentifier(uint64_t identifier) { m_identifier = identifier; } + +protected: + std::string m_name; + std::string m_key_name; + uint64_t m_identifier; + Type m_type; + int m_key_value; + int m_start_col; + int m_max_submenu_name_length; + int m_max_submenu_key_name_length; + int m_selected; + Menu *m_parent; + Menus m_submenus; + WindowSP m_menu_window_sp; + MenuActionResult m_canned_result; + MenuDelegateSP m_delegate_sp; +}; + +// Menubar or separator constructor +Menu::Menu(Type type) + : m_name(), m_key_name(), m_identifier(0), m_type(type), m_key_value(0), + m_start_col(0), m_max_submenu_name_length(0), + m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr), + m_submenus(), m_canned_result(MenuActionResult::NotHandled), + m_delegate_sp() {} + +// Menuitem constructor +Menu::Menu(const char *name, const char *key_name, int key_value, + uint64_t identifier) + : m_name(), m_key_name(), m_identifier(identifier), m_type(Type::Invalid), + m_key_value(key_value), m_start_col(0), m_max_submenu_name_length(0), + m_max_submenu_key_name_length(0), m_selected(0), m_parent(nullptr), + m_submenus(), m_canned_result(MenuActionResult::NotHandled), + m_delegate_sp() { + if (name && name[0]) { + m_name = name; + m_type = Type::Item; + if (key_name && key_name[0]) + m_key_name = key_name; + } else { + m_type = Type::Separator; + } +} + +void Menu::RecalculateNameLengths() { + m_max_submenu_name_length = 0; + m_max_submenu_key_name_length = 0; + Menus &submenus = GetSubmenus(); + const size_t num_submenus = submenus.size(); + for (size_t i = 0; i < num_submenus; ++i) { + Menu *submenu = submenus[i].get(); + if (static_cast<size_t>(m_max_submenu_name_length) < submenu->m_name.size()) + m_max_submenu_name_length = submenu->m_name.size(); + if (static_cast<size_t>(m_max_submenu_key_name_length) < + submenu->m_key_name.size()) + m_max_submenu_key_name_length = submenu->m_key_name.size(); + } +} + +void Menu::AddSubmenu(const MenuSP &menu_sp) { + menu_sp->m_parent = this; + if (static_cast<size_t>(m_max_submenu_name_length) < menu_sp->m_name.size()) + m_max_submenu_name_length = menu_sp->m_name.size(); + if (static_cast<size_t>(m_max_submenu_key_name_length) < + menu_sp->m_key_name.size()) + m_max_submenu_key_name_length = menu_sp->m_key_name.size(); + m_submenus.push_back(menu_sp); +} + +void Menu::DrawMenuTitle(Window &window, bool highlight) { + if (m_type == Type::Separator) { + window.MoveCursor(0, window.GetCursorY()); + window.PutChar(ACS_LTEE); + int width = window.GetWidth(); + if (width > 2) { + width -= 2; + for (int i = 0; i < width; ++i) + window.PutChar(ACS_HLINE); + } + window.PutChar(ACS_RTEE); + } else { + const int shortcut_key = m_key_value; + bool underlined_shortcut = false; + const attr_t highlight_attr = A_REVERSE; + if (highlight) + window.AttributeOn(highlight_attr); + if (llvm::isPrint(shortcut_key)) { + size_t lower_pos = m_name.find(tolower(shortcut_key)); + size_t upper_pos = m_name.find(toupper(shortcut_key)); + const char *name = m_name.c_str(); + size_t pos = std::min<size_t>(lower_pos, upper_pos); + if (pos != std::string::npos) { + underlined_shortcut = true; + if (pos > 0) { + window.PutCString(name, pos); + name += pos; + } + const attr_t shortcut_attr = A_UNDERLINE | A_BOLD; + window.AttributeOn(shortcut_attr); + window.PutChar(name[0]); + window.AttributeOff(shortcut_attr); + name++; + if (name[0]) + window.PutCString(name); + } + } + + if (!underlined_shortcut) { + window.PutCString(m_name.c_str()); + } + + if (highlight) + window.AttributeOff(highlight_attr); + + if (m_key_name.empty()) { + if (!underlined_shortcut && llvm::isPrint(m_key_value)) { + window.AttributeOn(COLOR_PAIR(MagentaOnWhite)); + window.Printf(" (%c)", m_key_value); + window.AttributeOff(COLOR_PAIR(MagentaOnWhite)); + } + } else { + window.AttributeOn(COLOR_PAIR(MagentaOnWhite)); + window.Printf(" (%s)", m_key_name.c_str()); + window.AttributeOff(COLOR_PAIR(MagentaOnWhite)); + } + } +} + +bool Menu::WindowDelegateDraw(Window &window, bool force) { + Menus &submenus = GetSubmenus(); + const size_t num_submenus = submenus.size(); + const int selected_idx = GetSelectedSubmenuIndex(); + Menu::Type menu_type = GetType(); + switch (menu_type) { + case Menu::Type::Bar: { + window.SetBackground(BlackOnWhite); + window.MoveCursor(0, 0); + for (size_t i = 0; i < num_submenus; ++i) { + Menu *menu = submenus[i].get(); + if (i > 0) + window.PutChar(' '); + menu->SetStartingColumn(window.GetCursorX()); + window.PutCString("| "); + menu->DrawMenuTitle(window, false); + } + window.PutCString(" |"); + } break; + + case Menu::Type::Item: { + int y = 1; + int x = 3; + // Draw the menu + int cursor_x = 0; + int cursor_y = 0; + window.Erase(); + window.SetBackground(BlackOnWhite); + window.Box(); + for (size_t i = 0; i < num_submenus; ++i) { + const bool is_selected = (i == static_cast<size_t>(selected_idx)); + window.MoveCursor(x, y + i); + if (is_selected) { + // Remember where we want the cursor to be + cursor_x = x - 1; + cursor_y = y + i; + } + submenus[i]->DrawMenuTitle(window, is_selected); + } + window.MoveCursor(cursor_x, cursor_y); + } break; + + default: + case Menu::Type::Separator: + break; + } + return true; // Drawing handled... +} + +HandleCharResult Menu::WindowDelegateHandleChar(Window &window, int key) { + HandleCharResult result = eKeyNotHandled; + + Menus &submenus = GetSubmenus(); + const size_t num_submenus = submenus.size(); + const int selected_idx = GetSelectedSubmenuIndex(); + Menu::Type menu_type = GetType(); + if (menu_type == Menu::Type::Bar) { + MenuSP run_menu_sp; + switch (key) { + case KEY_DOWN: + case KEY_UP: + // Show last menu or first menu + if (selected_idx < static_cast<int>(num_submenus)) + run_menu_sp = submenus[selected_idx]; + else if (!submenus.empty()) + run_menu_sp = submenus.front(); + result = eKeyHandled; + break; + + case KEY_RIGHT: + ++m_selected; + if (m_selected >= static_cast<int>(num_submenus)) + m_selected = 0; + if (m_selected < static_cast<int>(num_submenus)) + run_menu_sp = submenus[m_selected]; + else if (!submenus.empty()) + run_menu_sp = submenus.front(); + result = eKeyHandled; + break; + + case KEY_LEFT: + --m_selected; + if (m_selected < 0) + m_selected = num_submenus - 1; + if (m_selected < static_cast<int>(num_submenus)) + run_menu_sp = submenus[m_selected]; + else if (!submenus.empty()) + run_menu_sp = submenus.front(); + result = eKeyHandled; + break; + + default: + for (size_t i = 0; i < num_submenus; ++i) { + if (submenus[i]->GetKeyValue() == key) { + SetSelectedSubmenuIndex(i); + run_menu_sp = submenus[i]; + result = eKeyHandled; + break; + } + } + break; + } + + if (run_menu_sp) { + // Run the action on this menu in case we need to populate the menu with + // dynamic content and also in case check marks, and any other menu + // decorations need to be calculated + if (run_menu_sp->Action() == MenuActionResult::Quit) + return eQuitApplication; + + Rect menu_bounds; + menu_bounds.origin.x = run_menu_sp->GetStartingColumn(); + menu_bounds.origin.y = 1; + menu_bounds.size.width = run_menu_sp->GetDrawWidth(); + menu_bounds.size.height = run_menu_sp->GetSubmenus().size() + 2; + if (m_menu_window_sp) + window.GetParent()->RemoveSubWindow(m_menu_window_sp.get()); + + m_menu_window_sp = window.GetParent()->CreateSubWindow( + run_menu_sp->GetName().c_str(), menu_bounds, true); + m_menu_window_sp->SetDelegate(run_menu_sp); + } + } else if (menu_type == Menu::Type::Item) { + switch (key) { + case KEY_DOWN: + if (m_submenus.size() > 1) { + const int start_select = m_selected; + while (++m_selected != start_select) { + if (static_cast<size_t>(m_selected) >= num_submenus) + m_selected = 0; + if (m_submenus[m_selected]->GetType() == Type::Separator) + continue; + else + break; + } + return eKeyHandled; + } + break; + + case KEY_UP: + if (m_submenus.size() > 1) { + const int start_select = m_selected; + while (--m_selected != start_select) { + if (m_selected < static_cast<int>(0)) + m_selected = num_submenus - 1; + if (m_submenus[m_selected]->GetType() == Type::Separator) + continue; + else + break; + } + return eKeyHandled; + } + break; + + case KEY_RETURN: + if (static_cast<size_t>(selected_idx) < num_submenus) { + if (submenus[selected_idx]->Action() == MenuActionResult::Quit) + return eQuitApplication; + window.GetParent()->RemoveSubWindow(&window); + return eKeyHandled; + } + break; + + case KEY_ESCAPE: // Beware: pressing escape key has 1 to 2 second delay in + // case other chars are entered for escaped sequences + window.GetParent()->RemoveSubWindow(&window); + return eKeyHandled; + + default: + for (size_t i = 0; i < num_submenus; ++i) { + Menu *menu = submenus[i].get(); + if (menu->GetKeyValue() == key) { + SetSelectedSubmenuIndex(i); + window.GetParent()->RemoveSubWindow(&window); + if (menu->Action() == MenuActionResult::Quit) + return eQuitApplication; + return eKeyHandled; + } + } + break; + } + } else if (menu_type == Menu::Type::Separator) { + } + return result; +} + +class Application { +public: + Application(FILE *in, FILE *out) : m_window_sp(), m_in(in), m_out(out) {} + + ~Application() { + m_window_delegates.clear(); + m_window_sp.reset(); + if (m_screen) { + ::delscreen(m_screen); + m_screen = nullptr; + } + } + + void Initialize() { + m_screen = ::newterm(nullptr, m_out, m_in); + ::start_color(); + ::curs_set(0); + ::noecho(); + ::keypad(stdscr, TRUE); + } + + void Terminate() { ::endwin(); } + + void Run(Debugger &debugger) { + bool done = false; + int delay_in_tenths_of_a_second = 1; + + // Alas the threading model in curses is a bit lame so we need to resort + // to polling every 0.5 seconds. We could poll for stdin ourselves and + // then pass the keys down but then we need to translate all of the escape + // sequences ourselves. So we resort to polling for input because we need + // to receive async process events while in this loop. + + halfdelay(delay_in_tenths_of_a_second); // Poll using some number of + // tenths of seconds seconds when + // calling Window::GetChar() + + ListenerSP listener_sp( + Listener::MakeListener("lldb.IOHandler.curses.Application")); + ConstString broadcaster_class_process(Process::GetStaticBroadcasterClass()); + debugger.EnableForwardEvents(listener_sp); + + m_update_screen = true; +#if defined(__APPLE__) + std::deque<int> escape_chars; +#endif + + while (!done) { + if (m_update_screen) { + m_window_sp->Draw(false); + // All windows should be calling Window::DeferredRefresh() instead of + // Window::Refresh() so we can do a single update and avoid any screen + // blinking + update_panels(); + + // Cursor hiding isn't working on MacOSX, so hide it in the top left + // corner + m_window_sp->MoveCursor(0, 0); + + doupdate(); + m_update_screen = false; + } + +#if defined(__APPLE__) + // Terminal.app doesn't map its function keys correctly, F1-F4 default + // to: \033OP, \033OQ, \033OR, \033OS, so lets take care of this here if + // possible + int ch; + if (escape_chars.empty()) + ch = m_window_sp->GetChar(); + else { + ch = escape_chars.front(); + escape_chars.pop_front(); + } + if (ch == KEY_ESCAPE) { + int ch2 = m_window_sp->GetChar(); + if (ch2 == 'O') { + int ch3 = m_window_sp->GetChar(); + switch (ch3) { + case 'P': + ch = KEY_F(1); + break; + case 'Q': + ch = KEY_F(2); + break; + case 'R': + ch = KEY_F(3); + break; + case 'S': + ch = KEY_F(4); + break; + default: + escape_chars.push_back(ch2); + if (ch3 != -1) + escape_chars.push_back(ch3); + break; + } + } else if (ch2 != -1) + escape_chars.push_back(ch2); + } +#else + int ch = m_window_sp->GetChar(); + +#endif + if (ch == -1) { + if (feof(m_in) || ferror(m_in)) { + done = true; + } else { + // Just a timeout from using halfdelay(), check for events + EventSP event_sp; + while (listener_sp->PeekAtNextEvent()) { + listener_sp->GetEvent(event_sp, std::chrono::seconds(0)); + + if (event_sp) { + Broadcaster *broadcaster = event_sp->GetBroadcaster(); + if (broadcaster) { + // uint32_t event_type = event_sp->GetType(); + ConstString broadcaster_class( + broadcaster->GetBroadcasterClass()); + if (broadcaster_class == broadcaster_class_process) { + m_update_screen = true; + continue; // Don't get any key, just update our view + } + } + } + } + } + } else { + HandleCharResult key_result = m_window_sp->HandleChar(ch); + switch (key_result) { + case eKeyHandled: + m_update_screen = true; + break; + case eKeyNotHandled: + if (ch == 12) { // Ctrl+L, force full redraw + redrawwin(m_window_sp->get()); + m_update_screen = true; + } + break; + case eQuitApplication: + done = true; + break; + } + } + } + + debugger.CancelForwardEvents(listener_sp); + } + + WindowSP &GetMainWindow() { + if (!m_window_sp) + m_window_sp = std::make_shared<Window>("main", stdscr, false); + return m_window_sp; + } + + void TerminalSizeChanged() { + ::endwin(); + ::refresh(); + Rect content_bounds = m_window_sp->GetFrame(); + m_window_sp->SetBounds(content_bounds); + if (WindowSP menubar_window_sp = m_window_sp->FindSubWindow("Menubar")) + menubar_window_sp->SetBounds(content_bounds.MakeMenuBar()); + if (WindowSP status_window_sp = m_window_sp->FindSubWindow("Status")) + status_window_sp->SetBounds(content_bounds.MakeStatusBar()); + + WindowSP source_window_sp = m_window_sp->FindSubWindow("Source"); + WindowSP variables_window_sp = m_window_sp->FindSubWindow("Variables"); + WindowSP registers_window_sp = m_window_sp->FindSubWindow("Registers"); + WindowSP threads_window_sp = m_window_sp->FindSubWindow("Threads"); + + Rect threads_bounds; + Rect source_variables_bounds; + content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, + threads_bounds); + if (threads_window_sp) + threads_window_sp->SetBounds(threads_bounds); + else + source_variables_bounds = content_bounds; + + Rect source_bounds; + Rect variables_registers_bounds; + source_variables_bounds.HorizontalSplitPercentage( + 0.70, source_bounds, variables_registers_bounds); + if (variables_window_sp || registers_window_sp) { + if (variables_window_sp && registers_window_sp) { + Rect variables_bounds; + Rect registers_bounds; + variables_registers_bounds.VerticalSplitPercentage( + 0.50, variables_bounds, registers_bounds); + variables_window_sp->SetBounds(variables_bounds); + registers_window_sp->SetBounds(registers_bounds); + } else if (variables_window_sp) { + variables_window_sp->SetBounds(variables_registers_bounds); + } else { + registers_window_sp->SetBounds(variables_registers_bounds); + } + } else { + source_bounds = source_variables_bounds; + } + + source_window_sp->SetBounds(source_bounds); + + touchwin(stdscr); + redrawwin(m_window_sp->get()); + m_update_screen = true; + } + +protected: + WindowSP m_window_sp; + WindowDelegates m_window_delegates; + SCREEN *m_screen = nullptr; + FILE *m_in; + FILE *m_out; + bool m_update_screen = false; +}; + +} // namespace curses + +using namespace curses; + +struct Row { + ValueObjectUpdater value; + Row *parent; + // The process stop ID when the children were calculated. + uint32_t children_stop_id = 0; + int row_idx = 0; + int x = 1; + int y = 1; + bool might_have_children; + bool expanded = false; + bool calculated_children = false; + std::vector<Row> children; + + Row(const ValueObjectSP &v, Row *p) + : value(v), parent(p), + might_have_children(v ? v->MightHaveChildren() : false) {} + + size_t GetDepth() const { + if (parent) + return 1 + parent->GetDepth(); + return 0; + } + + void Expand() { expanded = true; } + + std::vector<Row> &GetChildren() { + ProcessSP process_sp = value.GetProcessSP(); + auto stop_id = process_sp->GetStopID(); + if (process_sp && stop_id != children_stop_id) { + children_stop_id = stop_id; + calculated_children = false; + } + if (!calculated_children) { + children.clear(); + calculated_children = true; + ValueObjectSP valobj = value.GetSP(); + if (valobj) { + const uint32_t num_children = valobj->GetNumChildrenIgnoringErrors(); + for (size_t i = 0; i < num_children; ++i) { + children.push_back(Row(valobj->GetChildAtIndex(i), this)); + } + } + } + return children; + } + + void Unexpand() { + expanded = false; + calculated_children = false; + children.clear(); + } + + void DrawTree(Window &window) { + if (parent) + parent->DrawTreeForChild(window, this, 0); + + if (might_have_children && + (!calculated_children || !GetChildren().empty())) { + // It we can get UTF8 characters to work we should try to use the + // "symbol" UTF8 string below + // const char *symbol = ""; + // if (row.expanded) + // symbol = "\xe2\x96\xbd "; + // else + // symbol = "\xe2\x96\xb7 "; + // window.PutCString (symbol); + + // The ACS_DARROW and ACS_RARROW don't look very nice they are just a 'v' + // or '>' character... + // if (expanded) + // window.PutChar (ACS_DARROW); + // else + // window.PutChar (ACS_RARROW); + // Since we can't find any good looking right arrow/down arrow symbols, + // just use a diamond... + window.PutChar(ACS_DIAMOND); + window.PutChar(ACS_HLINE); + } + } + + void DrawTreeForChild(Window &window, Row *child, uint32_t reverse_depth) { + if (parent) + parent->DrawTreeForChild(window, this, reverse_depth + 1); + + if (&GetChildren().back() == child) { + // Last child + if (reverse_depth == 0) { + window.PutChar(ACS_LLCORNER); + window.PutChar(ACS_HLINE); + } else { + window.PutChar(' '); + window.PutChar(' '); + } + } else { + if (reverse_depth == 0) { + window.PutChar(ACS_LTEE); + window.PutChar(ACS_HLINE); + } else { + window.PutChar(ACS_VLINE); + window.PutChar(' '); + } + } + } +}; + +struct DisplayOptions { + bool show_types; +}; + +class TreeItem; + +class TreeDelegate { +public: + TreeDelegate() = default; + virtual ~TreeDelegate() = default; + + virtual void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) = 0; + virtual void TreeDelegateGenerateChildren(TreeItem &item) = 0; + virtual void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index, + TreeItem *&selected_item) {} + // This is invoked when a tree item is selected. If true is returned, the + // views are updated. + virtual bool TreeDelegateItemSelected(TreeItem &item) = 0; + virtual bool TreeDelegateExpandRootByDefault() { return false; } + // This is mostly useful for root tree delegates. If false is returned, + // drawing will be skipped completely. This is needed, for instance, in + // skipping drawing of the threads tree if there is no running process. + virtual bool TreeDelegateShouldDraw() { return true; } +}; + +typedef std::shared_ptr<TreeDelegate> TreeDelegateSP; + +struct TreeItemData { + TreeItemData(TreeItem *parent, TreeDelegate &delegate, + bool might_have_children, bool is_expanded) + : m_parent(parent), m_delegate(&delegate), + m_might_have_children(might_have_children), m_is_expanded(is_expanded) { + } + +protected: + TreeItem *m_parent; + TreeDelegate *m_delegate; + void *m_user_data = nullptr; + uint64_t m_identifier = 0; + std::string m_text; + int m_row_idx = -1; // Zero based visible row index, -1 if not visible or for + // the root item + bool m_might_have_children; + bool m_is_expanded = false; +}; + +class TreeItem : public TreeItemData { +public: + TreeItem(TreeItem *parent, TreeDelegate &delegate, bool might_have_children) + : TreeItemData(parent, delegate, might_have_children, + parent == nullptr + ? delegate.TreeDelegateExpandRootByDefault() + : false), + m_children() {} + + TreeItem(const TreeItem &) = delete; + TreeItem &operator=(const TreeItem &rhs) = delete; + + TreeItem &operator=(TreeItem &&rhs) { + if (this != &rhs) { + TreeItemData::operator=(std::move(rhs)); + AdoptChildren(rhs.m_children); + } + return *this; + } + + TreeItem(TreeItem &&rhs) : TreeItemData(std::move(rhs)) { + AdoptChildren(rhs.m_children); + } + + size_t GetDepth() const { + if (m_parent) + return 1 + m_parent->GetDepth(); + return 0; + } + + int GetRowIndex() const { return m_row_idx; } + + void ClearChildren() { m_children.clear(); } + + void Resize(size_t n, TreeDelegate &delegate, bool might_have_children) { + if (m_children.size() >= n) { + m_children.erase(m_children.begin() + n, m_children.end()); + return; + } + m_children.reserve(n); + std::generate_n(std::back_inserter(m_children), n - m_children.size(), + [&, parent = this]() { + return TreeItem(parent, delegate, might_have_children); + }); + } + + TreeItem &operator[](size_t i) { return m_children[i]; } + + void SetRowIndex(int row_idx) { m_row_idx = row_idx; } + + size_t GetNumChildren() { + m_delegate->TreeDelegateGenerateChildren(*this); + return m_children.size(); + } + + void ItemWasSelected() { m_delegate->TreeDelegateItemSelected(*this); } + + void CalculateRowIndexes(int &row_idx) { + SetRowIndex(row_idx); + ++row_idx; + + const bool expanded = IsExpanded(); + + // The root item must calculate its children, or we must calculate the + // number of children if the item is expanded + if (m_parent == nullptr || expanded) + GetNumChildren(); + + for (auto &item : m_children) { + if (expanded) + item.CalculateRowIndexes(row_idx); + else + item.SetRowIndex(-1); + } + } + + TreeItem *GetParent() { return m_parent; } + + bool IsExpanded() const { return m_is_expanded; } + + void Expand() { m_is_expanded = true; } + + void Unexpand() { m_is_expanded = false; } + + bool Draw(Window &window, const int first_visible_row, + const uint32_t selected_row_idx, int &row_idx, int &num_rows_left) { + if (num_rows_left <= 0) + return false; + + if (m_row_idx >= first_visible_row) { + window.MoveCursor(2, row_idx + 1); + + if (m_parent) + m_parent->DrawTreeForChild(window, this, 0); + + if (m_might_have_children) { + // It we can get UTF8 characters to work we should try to use the + // "symbol" UTF8 string below + // const char *symbol = ""; + // if (row.expanded) + // symbol = "\xe2\x96\xbd "; + // else + // symbol = "\xe2\x96\xb7 "; + // window.PutCString (symbol); + + // The ACS_DARROW and ACS_RARROW don't look very nice they are just a + // 'v' or '>' character... + // if (expanded) + // window.PutChar (ACS_DARROW); + // else + // window.PutChar (ACS_RARROW); + // Since we can't find any good looking right arrow/down arrow symbols, + // just use a diamond... + window.PutChar(ACS_DIAMOND); + window.PutChar(ACS_HLINE); + } + bool highlight = (selected_row_idx == static_cast<size_t>(m_row_idx)) && + window.IsActive(); + + if (highlight) + window.AttributeOn(A_REVERSE); + + m_delegate->TreeDelegateDrawTreeItem(*this, window); + + if (highlight) + window.AttributeOff(A_REVERSE); + ++row_idx; + --num_rows_left; + } + + if (num_rows_left <= 0) + return false; // We are done drawing... + + if (IsExpanded()) { + for (auto &item : m_children) { + // If we displayed all the rows and item.Draw() returns false we are + // done drawing and can exit this for loop + if (!item.Draw(window, first_visible_row, selected_row_idx, row_idx, + num_rows_left)) + break; + } + } + return num_rows_left >= 0; // Return true if not done drawing yet + } + + void DrawTreeForChild(Window &window, TreeItem *child, + uint32_t reverse_depth) { + if (m_parent) + m_parent->DrawTreeForChild(window, this, reverse_depth + 1); + + if (&m_children.back() == child) { + // Last child + if (reverse_depth == 0) { + window.PutChar(ACS_LLCORNER); + window.PutChar(ACS_HLINE); + } else { + window.PutChar(' '); + window.PutChar(' '); + } + } else { + if (reverse_depth == 0) { + window.PutChar(ACS_LTEE); + window.PutChar(ACS_HLINE); + } else { + window.PutChar(ACS_VLINE); + window.PutChar(' '); + } + } + } + + TreeItem *GetItemForRowIndex(uint32_t row_idx) { + if (static_cast<uint32_t>(m_row_idx) == row_idx) + return this; + if (m_children.empty()) + return nullptr; + if (IsExpanded()) { + for (auto &item : m_children) { + TreeItem *selected_item_ptr = item.GetItemForRowIndex(row_idx); + if (selected_item_ptr) + return selected_item_ptr; + } + } + return nullptr; + } + + void *GetUserData() const { return m_user_data; } + + void SetUserData(void *user_data) { m_user_data = user_data; } + + uint64_t GetIdentifier() const { return m_identifier; } + + void SetIdentifier(uint64_t identifier) { m_identifier = identifier; } + + const std::string &GetText() const { return m_text; } + + void SetText(const char *text) { + if (text == nullptr) { + m_text.clear(); + return; + } + m_text = text; + } + + void SetMightHaveChildren(bool b) { m_might_have_children = b; } + +protected: + void AdoptChildren(std::vector<TreeItem> &children) { + m_children = std::move(children); + for (auto &child : m_children) + child.m_parent = this; + } + + std::vector<TreeItem> m_children; +}; + +class TreeWindowDelegate : public WindowDelegate { +public: + TreeWindowDelegate(Debugger &debugger, const TreeDelegateSP &delegate_sp) + : m_debugger(debugger), m_delegate_sp(delegate_sp), + m_root(nullptr, *delegate_sp, true) {} + + int NumVisibleRows() const { return m_max_y - m_min_y; } + + bool WindowDelegateDraw(Window &window, bool force) override { + m_min_x = 2; + m_min_y = 1; + m_max_x = window.GetWidth() - 1; + m_max_y = window.GetHeight() - 1; + + window.Erase(); + window.DrawTitleBox(window.GetName()); + + if (!m_delegate_sp->TreeDelegateShouldDraw()) { + m_selected_item = nullptr; + return true; + } + + const int num_visible_rows = NumVisibleRows(); + m_num_rows = 0; + m_root.CalculateRowIndexes(m_num_rows); + m_delegate_sp->TreeDelegateUpdateSelection(m_root, m_selected_row_idx, + m_selected_item); + + // If we unexpanded while having something selected our total number of + // rows is less than the num visible rows, then make sure we show all the + // rows by setting the first visible row accordingly. + if (m_first_visible_row > 0 && m_num_rows < num_visible_rows) + m_first_visible_row = 0; + + // Make sure the selected row is always visible + if (m_selected_row_idx < m_first_visible_row) + m_first_visible_row = m_selected_row_idx; + else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) + m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; + + int row_idx = 0; + int num_rows_left = num_visible_rows; + m_root.Draw(window, m_first_visible_row, m_selected_row_idx, row_idx, + num_rows_left); + // Get the selected row + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + + return true; // Drawing handled + } + + const char *WindowDelegateGetHelpText() override { + return "Thread window keyboard shortcuts:"; + } + + KeyHelp *WindowDelegateGetKeyHelp() override { + static curses::KeyHelp g_source_view_key_help[] = { + {KEY_UP, "Select previous item"}, + {KEY_DOWN, "Select next item"}, + {KEY_RIGHT, "Expand the selected item"}, + {KEY_LEFT, + "Unexpand the selected item or select parent if not expanded"}, + {KEY_PPAGE, "Page up"}, + {KEY_NPAGE, "Page down"}, + {'h', "Show help dialog"}, + {' ', "Toggle item expansion"}, + {',', "Page up"}, + {'.', "Page down"}, + {'\0', nullptr}}; + return g_source_view_key_help; + } + + HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { + switch (c) { + case ',': + case KEY_PPAGE: + // Page up key + if (m_first_visible_row > 0) { + if (m_first_visible_row > m_max_y) + m_first_visible_row -= m_max_y; + else + m_first_visible_row = 0; + m_selected_row_idx = m_first_visible_row; + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + if (m_selected_item) + m_selected_item->ItemWasSelected(); + } + return eKeyHandled; + + case '.': + case KEY_NPAGE: + // Page down key + if (m_num_rows > m_max_y) { + if (m_first_visible_row + m_max_y < m_num_rows) { + m_first_visible_row += m_max_y; + m_selected_row_idx = m_first_visible_row; + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + if (m_selected_item) + m_selected_item->ItemWasSelected(); + } + } + return eKeyHandled; + + case KEY_UP: + if (m_selected_row_idx > 0) { + --m_selected_row_idx; + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + if (m_selected_item) + m_selected_item->ItemWasSelected(); + } + return eKeyHandled; + + case KEY_DOWN: + if (m_selected_row_idx + 1 < m_num_rows) { + ++m_selected_row_idx; + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + if (m_selected_item) + m_selected_item->ItemWasSelected(); + } + return eKeyHandled; + + case KEY_RIGHT: + if (m_selected_item) { + if (!m_selected_item->IsExpanded()) + m_selected_item->Expand(); + } + return eKeyHandled; + + case KEY_LEFT: + if (m_selected_item) { + if (m_selected_item->IsExpanded()) + m_selected_item->Unexpand(); + else if (m_selected_item->GetParent()) { + m_selected_row_idx = m_selected_item->GetParent()->GetRowIndex(); + m_selected_item = m_root.GetItemForRowIndex(m_selected_row_idx); + if (m_selected_item) + m_selected_item->ItemWasSelected(); + } + } + return eKeyHandled; + + case ' ': + // Toggle expansion state when SPACE is pressed + if (m_selected_item) { + if (m_selected_item->IsExpanded()) + m_selected_item->Unexpand(); + else + m_selected_item->Expand(); + } + return eKeyHandled; + + case 'h': + window.CreateHelpSubwindow(); + return eKeyHandled; + + default: + break; + } + return eKeyNotHandled; + } + +protected: + Debugger &m_debugger; + TreeDelegateSP m_delegate_sp; + TreeItem m_root; + TreeItem *m_selected_item = nullptr; + int m_num_rows = 0; + int m_selected_row_idx = 0; + int m_first_visible_row = 0; + int m_min_x = 0; + int m_min_y = 0; + int m_max_x = 0; + int m_max_y = 0; +}; + +// A tree delegate that just draws the text member of the tree item, it doesn't +// have any children or actions. +class TextTreeDelegate : public TreeDelegate { +public: + TextTreeDelegate() : TreeDelegate() {} + + ~TextTreeDelegate() override = default; + + void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { + window.PutCStringTruncated(1, item.GetText().c_str()); + } + + void TreeDelegateGenerateChildren(TreeItem &item) override {} + + bool TreeDelegateItemSelected(TreeItem &item) override { return false; } +}; + +class FrameTreeDelegate : public TreeDelegate { +public: + FrameTreeDelegate() : TreeDelegate() { + FormatEntity::Parse( + "#${frame.index}: {${function.name}${function.pc-offset}}}", m_format); + } + + ~FrameTreeDelegate() override = default; + + void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { + Thread *thread = (Thread *)item.GetUserData(); + if (thread) { + const uint64_t frame_idx = item.GetIdentifier(); + StackFrameSP frame_sp = thread->GetStackFrameAtIndex(frame_idx); + if (frame_sp) { + StreamString strm; + const SymbolContext &sc = + frame_sp->GetSymbolContext(eSymbolContextEverything); + ExecutionContext exe_ctx(frame_sp); + if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr, + nullptr, false, false)) { + int right_pad = 1; + window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); + } + } + } + } + + void TreeDelegateGenerateChildren(TreeItem &item) override { + // No children for frames yet... + } + + bool TreeDelegateItemSelected(TreeItem &item) override { + Thread *thread = (Thread *)item.GetUserData(); + if (thread) { + thread->GetProcess()->GetThreadList().SetSelectedThreadByID( + thread->GetID()); + const uint64_t frame_idx = item.GetIdentifier(); + thread->SetSelectedFrameByIndex(frame_idx); + return true; + } + return false; + } + +protected: + FormatEntity::Entry m_format; +}; + +class ThreadTreeDelegate : public TreeDelegate { +public: + ThreadTreeDelegate(Debugger &debugger) + : TreeDelegate(), m_debugger(debugger) { + FormatEntity::Parse("thread #${thread.index}: tid = ${thread.id}{, stop " + "reason = ${thread.stop-reason}}", + m_format); + } + + ~ThreadTreeDelegate() override = default; + + ProcessSP GetProcess() { + return m_debugger.GetCommandInterpreter() + .GetExecutionContext() + .GetProcessSP(); + } + + ThreadSP GetThread(const TreeItem &item) { + ProcessSP process_sp = GetProcess(); + if (process_sp) + return process_sp->GetThreadList().FindThreadByID(item.GetIdentifier()); + return ThreadSP(); + } + + void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { + ThreadSP thread_sp = GetThread(item); + if (thread_sp) { + StreamString strm; + ExecutionContext exe_ctx(thread_sp); + if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, + nullptr, false, false)) { + int right_pad = 1; + window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); + } + } + } + + void TreeDelegateGenerateChildren(TreeItem &item) override { + ProcessSP process_sp = GetProcess(); + if (process_sp && process_sp->IsAlive()) { + StateType state = process_sp->GetState(); + if (StateIsStoppedState(state, true)) { + ThreadSP thread_sp = GetThread(item); + if (thread_sp) { + if (m_stop_id == process_sp->GetStopID() && + thread_sp->GetID() == m_tid) + return; // Children are already up to date + if (!m_frame_delegate_sp) { + // Always expand the thread item the first time we show it + m_frame_delegate_sp = std::make_shared<FrameTreeDelegate>(); + } + + m_stop_id = process_sp->GetStopID(); + m_tid = thread_sp->GetID(); + + size_t num_frames = thread_sp->GetStackFrameCount(); + item.Resize(num_frames, *m_frame_delegate_sp, false); + for (size_t i = 0; i < num_frames; ++i) { + item[i].SetUserData(thread_sp.get()); + item[i].SetIdentifier(i); + } + } + return; + } + } + item.ClearChildren(); + } + + bool TreeDelegateItemSelected(TreeItem &item) override { + ProcessSP process_sp = GetProcess(); + if (process_sp && process_sp->IsAlive()) { + StateType state = process_sp->GetState(); + if (StateIsStoppedState(state, true)) { + ThreadSP thread_sp = GetThread(item); + if (thread_sp) { + ThreadList &thread_list = thread_sp->GetProcess()->GetThreadList(); + std::lock_guard<std::recursive_mutex> guard(thread_list.GetMutex()); + ThreadSP selected_thread_sp = thread_list.GetSelectedThread(); + if (selected_thread_sp->GetID() != thread_sp->GetID()) { + thread_list.SetSelectedThreadByID(thread_sp->GetID()); + return true; + } + } + } + } + return false; + } + +protected: + Debugger &m_debugger; + std::shared_ptr<FrameTreeDelegate> m_frame_delegate_sp; + lldb::user_id_t m_tid = LLDB_INVALID_THREAD_ID; + uint32_t m_stop_id = UINT32_MAX; + FormatEntity::Entry m_format; +}; + +class ThreadsTreeDelegate : public TreeDelegate { +public: + ThreadsTreeDelegate(Debugger &debugger) + : TreeDelegate(), m_thread_delegate_sp(), m_debugger(debugger) { + FormatEntity::Parse("process ${process.id}{, name = ${process.name}}", + m_format); + } + + ~ThreadsTreeDelegate() override = default; + + ProcessSP GetProcess() { + return m_debugger.GetCommandInterpreter() + .GetExecutionContext() + .GetProcessSP(); + } + + bool TreeDelegateShouldDraw() override { + ProcessSP process = GetProcess(); + if (!process) + return false; + + if (StateIsRunningState(process->GetState())) + return false; + + return true; + } + + void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { + ProcessSP process_sp = GetProcess(); + if (process_sp && process_sp->IsAlive()) { + StreamString strm; + ExecutionContext exe_ctx(process_sp); + if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr, + nullptr, false, false)) { + int right_pad = 1; + window.PutCStringTruncated(right_pad, strm.GetString().str().c_str()); + } + } + } + + void TreeDelegateGenerateChildren(TreeItem &item) override { + ProcessSP process_sp = GetProcess(); + m_update_selection = false; + if (process_sp && process_sp->IsAlive()) { + StateType state = process_sp->GetState(); + if (StateIsStoppedState(state, true)) { + const uint32_t stop_id = process_sp->GetStopID(); + if (m_stop_id == stop_id) + return; // Children are already up to date + + m_stop_id = stop_id; + m_update_selection = true; + + if (!m_thread_delegate_sp) { + // Always expand the thread item the first time we show it + // item.Expand(); + m_thread_delegate_sp = + std::make_shared<ThreadTreeDelegate>(m_debugger); + } + + ThreadList &threads = process_sp->GetThreadList(); + std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); + ThreadSP selected_thread = threads.GetSelectedThread(); + size_t num_threads = threads.GetSize(); + item.Resize(num_threads, *m_thread_delegate_sp, false); + for (size_t i = 0; i < num_threads; ++i) { + ThreadSP thread = threads.GetThreadAtIndex(i); + item[i].SetIdentifier(thread->GetID()); + item[i].SetMightHaveChildren(true); + if (selected_thread->GetID() == thread->GetID()) + item[i].Expand(); + } + return; + } + } + item.ClearChildren(); + } + + void TreeDelegateUpdateSelection(TreeItem &root, int &selection_index, + TreeItem *&selected_item) override { + if (!m_update_selection) + return; + + ProcessSP process_sp = GetProcess(); + if (!(process_sp && process_sp->IsAlive())) + return; + + StateType state = process_sp->GetState(); + if (!StateIsStoppedState(state, true)) + return; + + ThreadList &threads = process_sp->GetThreadList(); + std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); + ThreadSP selected_thread = threads.GetSelectedThread(); + size_t num_threads = threads.GetSize(); + for (size_t i = 0; i < num_threads; ++i) { + ThreadSP thread = threads.GetThreadAtIndex(i); + if (selected_thread->GetID() == thread->GetID()) { + selected_item = + &root[i][thread->GetSelectedFrameIndex(SelectMostRelevantFrame)]; + selection_index = selected_item->GetRowIndex(); + return; + } + } + } + + bool TreeDelegateItemSelected(TreeItem &item) override { return false; } + + bool TreeDelegateExpandRootByDefault() override { return true; } + +protected: + std::shared_ptr<ThreadTreeDelegate> m_thread_delegate_sp; + Debugger &m_debugger; + uint32_t m_stop_id = UINT32_MAX; + bool m_update_selection = false; + FormatEntity::Entry m_format; +}; + +class BreakpointLocationTreeDelegate : public TreeDelegate { +public: + BreakpointLocationTreeDelegate(Debugger &debugger) + : TreeDelegate(), m_debugger(debugger) {} + + ~BreakpointLocationTreeDelegate() override = default; + + Process *GetProcess() { + ExecutionContext exe_ctx( + m_debugger.GetCommandInterpreter().GetExecutionContext()); + return exe_ctx.GetProcessPtr(); + } + + BreakpointLocationSP GetBreakpointLocation(const TreeItem &item) { + Breakpoint *breakpoint = (Breakpoint *)item.GetUserData(); + return breakpoint->GetLocationAtIndex(item.GetIdentifier()); + } + + void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { + BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item); + Process *process = GetProcess(); + StreamString stream; + stream.Printf("%i.%i: ", breakpoint_location->GetBreakpoint().GetID(), + breakpoint_location->GetID()); + Address address = breakpoint_location->GetAddress(); + address.Dump(&stream, process, Address::DumpStyleResolvedDescription, + Address::DumpStyleInvalid); + window.PutCStringTruncated(1, stream.GetString().str().c_str()); + } + + StringList ComputeDetailsList(BreakpointLocationSP breakpoint_location) { + StringList details; + + Address address = breakpoint_location->GetAddress(); + SymbolContext symbol_context; + address.CalculateSymbolContext(&symbol_context); + + if (symbol_context.module_sp) { + StreamString module_stream; + module_stream.PutCString("module = "); + symbol_context.module_sp->GetFileSpec().Dump( + module_stream.AsRawOstream()); + details.AppendString(module_stream.GetString()); + } + + if (symbol_context.comp_unit != nullptr) { + StreamString compile_unit_stream; + compile_unit_stream.PutCString("compile unit = "); + symbol_context.comp_unit->GetPrimaryFile().GetFilename().Dump( + &compile_unit_stream); + details.AppendString(compile_unit_stream.GetString()); + + if (symbol_context.function != nullptr) { + StreamString function_stream; + function_stream.PutCString("function = "); + function_stream.PutCString( + symbol_context.function->GetName().AsCString("<unknown>")); + details.AppendString(function_stream.GetString()); + } + + if (symbol_context.line_entry.line > 0) { + StreamString location_stream; + location_stream.PutCString("location = "); + symbol_context.line_entry.DumpStopContext(&location_stream, true); + details.AppendString(location_stream.GetString()); + } + + } else { + if (symbol_context.symbol) { + StreamString symbol_stream; + if (breakpoint_location->IsReExported()) + symbol_stream.PutCString("re-exported target = "); + else + symbol_stream.PutCString("symbol = "); + symbol_stream.PutCString( + symbol_context.symbol->GetName().AsCString("<unknown>")); + details.AppendString(symbol_stream.GetString()); + } + } + + Process *process = GetProcess(); + + StreamString address_stream; + address.Dump(&address_stream, process, Address::DumpStyleLoadAddress, + Address::DumpStyleModuleWithFileAddress); + details.AppendString(address_stream.GetString()); + + BreakpointSiteSP breakpoint_site = breakpoint_location->GetBreakpointSite(); + if (breakpoint_location->IsIndirect() && breakpoint_site) { + Address resolved_address; + resolved_address.SetLoadAddress(breakpoint_site->GetLoadAddress(), + &breakpoint_location->GetTarget()); + Symbol *resolved_symbol = resolved_address.CalculateSymbolContextSymbol(); + if (resolved_symbol) { + StreamString indirect_target_stream; + indirect_target_stream.PutCString("indirect target = "); + indirect_target_stream.PutCString( + resolved_symbol->GetName().GetCString()); + details.AppendString(indirect_target_stream.GetString()); + } + } + + bool is_resolved = breakpoint_location->IsResolved(); + StreamString resolved_stream; + resolved_stream.Printf("resolved = %s", is_resolved ? "true" : "false"); + details.AppendString(resolved_stream.GetString()); + + bool is_hardware = is_resolved && breakpoint_site->IsHardware(); + StreamString hardware_stream; + hardware_stream.Printf("hardware = %s", is_hardware ? "true" : "false"); + details.AppendString(hardware_stream.GetString()); + + StreamString hit_count_stream; + hit_count_stream.Printf("hit count = %-4u", + breakpoint_location->GetHitCount()); + details.AppendString(hit_count_stream.GetString()); + + return details; + } + + void TreeDelegateGenerateChildren(TreeItem &item) override { + BreakpointLocationSP breakpoint_location = GetBreakpointLocation(item); + StringList details = ComputeDetailsList(breakpoint_location); + + if (!m_string_delegate_sp) + m_string_delegate_sp = std::make_shared<TextTreeDelegate>(); + + item.Resize(details.GetSize(), *m_string_delegate_sp, false); + for (size_t i = 0; i < details.GetSize(); i++) { + item[i].SetText(details.GetStringAtIndex(i)); + } + } + + bool TreeDelegateItemSelected(TreeItem &item) override { return false; } + +protected: + Debugger &m_debugger; + std::shared_ptr<TextTreeDelegate> m_string_delegate_sp; +}; + +class BreakpointTreeDelegate : public TreeDelegate { +public: + BreakpointTreeDelegate(Debugger &debugger) + : TreeDelegate(), m_debugger(debugger), + m_breakpoint_location_delegate_sp() {} + + ~BreakpointTreeDelegate() override = default; + + BreakpointSP GetBreakpoint(const TreeItem &item) { + TargetSP target = m_debugger.GetSelectedTarget(); + BreakpointList &breakpoints = target->GetBreakpointList(false); + return breakpoints.GetBreakpointAtIndex(item.GetIdentifier()); + } + + void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { + BreakpointSP breakpoint = GetBreakpoint(item); + StreamString stream; + stream.Format("{0}: ", breakpoint->GetID()); + breakpoint->GetResolverDescription(&stream); + breakpoint->GetFilterDescription(&stream); + window.PutCStringTruncated(1, stream.GetString().str().c_str()); + } + + void TreeDelegateGenerateChildren(TreeItem &item) override { + BreakpointSP breakpoint = GetBreakpoint(item); + + if (!m_breakpoint_location_delegate_sp) + m_breakpoint_location_delegate_sp = + std::make_shared<BreakpointLocationTreeDelegate>(m_debugger); + + item.Resize(breakpoint->GetNumLocations(), + *m_breakpoint_location_delegate_sp, true); + for (size_t i = 0; i < breakpoint->GetNumLocations(); i++) { + item[i].SetIdentifier(i); + item[i].SetUserData(breakpoint.get()); + } + } + + bool TreeDelegateItemSelected(TreeItem &item) override { return false; } + +protected: + Debugger &m_debugger; + std::shared_ptr<BreakpointLocationTreeDelegate> + m_breakpoint_location_delegate_sp; +}; + +class BreakpointsTreeDelegate : public TreeDelegate { +public: + BreakpointsTreeDelegate(Debugger &debugger) + : TreeDelegate(), m_debugger(debugger), m_breakpoint_delegate_sp() {} + + ~BreakpointsTreeDelegate() override = default; + + bool TreeDelegateShouldDraw() override { + TargetSP target = m_debugger.GetSelectedTarget(); + if (!target) + return false; + + return true; + } + + void TreeDelegateDrawTreeItem(TreeItem &item, Window &window) override { + window.PutCString("Breakpoints"); + } + + void TreeDelegateGenerateChildren(TreeItem &item) override { + TargetSP target = m_debugger.GetSelectedTarget(); + + BreakpointList &breakpoints = target->GetBreakpointList(false); + std::unique_lock<std::recursive_mutex> lock; + breakpoints.GetListMutex(lock); + + if (!m_breakpoint_delegate_sp) + m_breakpoint_delegate_sp = + std::make_shared<BreakpointTreeDelegate>(m_debugger); + + item.Resize(breakpoints.GetSize(), *m_breakpoint_delegate_sp, true); + for (size_t i = 0; i < breakpoints.GetSize(); i++) { + item[i].SetIdentifier(i); + } + } + + bool TreeDelegateItemSelected(TreeItem &item) override { return false; } + + bool TreeDelegateExpandRootByDefault() override { return true; } + +protected: + Debugger &m_debugger; + std::shared_ptr<BreakpointTreeDelegate> m_breakpoint_delegate_sp; +}; + +class ValueObjectListDelegate : public WindowDelegate { +public: + ValueObjectListDelegate() : m_rows() {} + + ValueObjectListDelegate(ValueObjectList &valobj_list) : m_rows() { + SetValues(valobj_list); + } + + ~ValueObjectListDelegate() override = default; + + void SetValues(ValueObjectList &valobj_list) { + m_selected_row = nullptr; + m_selected_row_idx = 0; + m_first_visible_row = 0; + m_num_rows = 0; + m_rows.clear(); + for (auto &valobj_sp : valobj_list.GetObjects()) + m_rows.push_back(Row(valobj_sp, nullptr)); + } + + bool WindowDelegateDraw(Window &window, bool force) override { + m_num_rows = 0; + m_min_x = 2; + m_min_y = 1; + m_max_x = window.GetWidth() - 1; + m_max_y = window.GetHeight() - 1; + + window.Erase(); + window.DrawTitleBox(window.GetName()); + + const int num_visible_rows = NumVisibleRows(); + const int num_rows = CalculateTotalNumberRows(m_rows); + + // If we unexpanded while having something selected our total number of + // rows is less than the num visible rows, then make sure we show all the + // rows by setting the first visible row accordingly. + if (m_first_visible_row > 0 && num_rows < num_visible_rows) + m_first_visible_row = 0; + + // Make sure the selected row is always visible + if (m_selected_row_idx < m_first_visible_row) + m_first_visible_row = m_selected_row_idx; + else if (m_first_visible_row + num_visible_rows <= m_selected_row_idx) + m_first_visible_row = m_selected_row_idx - num_visible_rows + 1; + + DisplayRows(window, m_rows, g_options); + + // Get the selected row + m_selected_row = GetRowForRowIndex(m_selected_row_idx); + // Keep the cursor on the selected row so the highlight and the cursor are + // always on the same line + if (m_selected_row) + window.MoveCursor(m_selected_row->x, m_selected_row->y); + + return true; // Drawing handled + } + + KeyHelp *WindowDelegateGetKeyHelp() override { + static curses::KeyHelp g_source_view_key_help[] = { + {KEY_UP, "Select previous item"}, + {KEY_DOWN, "Select next item"}, + {KEY_RIGHT, "Expand selected item"}, + {KEY_LEFT, "Unexpand selected item or select parent if not expanded"}, + {KEY_PPAGE, "Page up"}, + {KEY_NPAGE, "Page down"}, + {'A', "Format as annotated address"}, + {'b', "Format as binary"}, + {'B', "Format as hex bytes with ASCII"}, + {'c', "Format as character"}, + {'d', "Format as a signed integer"}, + {'D', "Format selected value using the default format for the type"}, + {'f', "Format as float"}, + {'h', "Show help dialog"}, + {'i', "Format as instructions"}, + {'o', "Format as octal"}, + {'p', "Format as pointer"}, + {'s', "Format as C string"}, + {'t', "Toggle showing/hiding type names"}, + {'u', "Format as an unsigned integer"}, + {'x', "Format as hex"}, + {'X', "Format as uppercase hex"}, + {' ', "Toggle item expansion"}, + {',', "Page up"}, + {'.', "Page down"}, + {'\0', nullptr}}; + return g_source_view_key_help; + } + + HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { + switch (c) { + case 'x': + case 'X': + case 'o': + case 's': + case 'u': + case 'd': + case 'D': + case 'i': + case 'A': + case 'p': + case 'c': + case 'b': + case 'B': + case 'f': + // Change the format for the currently selected item + if (m_selected_row) { + auto valobj_sp = m_selected_row->value.GetSP(); + if (valobj_sp) + valobj_sp->SetFormat(FormatForChar(c)); + } + return eKeyHandled; + + case 't': + // Toggle showing type names + g_options.show_types = !g_options.show_types; + return eKeyHandled; + + case ',': + case KEY_PPAGE: + // Page up key + if (m_first_visible_row > 0) { + if (static_cast<int>(m_first_visible_row) > m_max_y) + m_first_visible_row -= m_max_y; + else + m_first_visible_row = 0; + m_selected_row_idx = m_first_visible_row; + } + return eKeyHandled; + + case '.': + case KEY_NPAGE: + // Page down key + if (m_num_rows > static_cast<size_t>(m_max_y)) { + if (m_first_visible_row + m_max_y < m_num_rows) { + m_first_visible_row += m_max_y; + m_selected_row_idx = m_first_visible_row; + } + } + return eKeyHandled; + + case KEY_UP: + if (m_selected_row_idx > 0) + --m_selected_row_idx; + return eKeyHandled; + + case KEY_DOWN: + if (m_selected_row_idx + 1 < m_num_rows) + ++m_selected_row_idx; + return eKeyHandled; + + case KEY_RIGHT: + if (m_selected_row) { + if (!m_selected_row->expanded) + m_selected_row->Expand(); + } + return eKeyHandled; + + case KEY_LEFT: + if (m_selected_row) { + if (m_selected_row->expanded) + m_selected_row->Unexpand(); + else if (m_selected_row->parent) + m_selected_row_idx = m_selected_row->parent->row_idx; + } + return eKeyHandled; + + case ' ': + // Toggle expansion state when SPACE is pressed + if (m_selected_row) { + if (m_selected_row->expanded) + m_selected_row->Unexpand(); + else + m_selected_row->Expand(); + } + return eKeyHandled; + + case 'h': + window.CreateHelpSubwindow(); + return eKeyHandled; + + default: + break; + } + return eKeyNotHandled; + } + +protected: + std::vector<Row> m_rows; + Row *m_selected_row = nullptr; + uint32_t m_selected_row_idx = 0; + uint32_t m_first_visible_row = 0; + uint32_t m_num_rows = 0; + int m_min_x = 0; + int m_min_y = 0; + int m_max_x = 0; + int m_max_y = 0; + + static Format FormatForChar(int c) { + switch (c) { + case 'x': + return eFormatHex; + case 'X': + return eFormatHexUppercase; + case 'o': + return eFormatOctal; + case 's': + return eFormatCString; + case 'u': + return eFormatUnsigned; + case 'd': + return eFormatDecimal; + case 'D': + return eFormatDefault; + case 'i': + return eFormatInstruction; + case 'A': + return eFormatAddressInfo; + case 'p': + return eFormatPointer; + case 'c': + return eFormatChar; + case 'b': + return eFormatBinary; + case 'B': + return eFormatBytesWithASCII; + case 'f': + return eFormatFloat; + } + return eFormatDefault; + } + + bool DisplayRowObject(Window &window, Row &row, DisplayOptions &options, + bool highlight, bool last_child) { + ValueObject *valobj = row.value.GetSP().get(); + + if (valobj == nullptr) + return false; + + const char *type_name = + options.show_types ? valobj->GetTypeName().GetCString() : nullptr; + const char *name = valobj->GetName().GetCString(); + const char *value = valobj->GetValueAsCString(); + const char *summary = valobj->GetSummaryAsCString(); + + window.MoveCursor(row.x, row.y); + + row.DrawTree(window); + + if (highlight) + window.AttributeOn(A_REVERSE); + + if (type_name && type_name[0]) + window.PrintfTruncated(1, "(%s) ", type_name); + + if (name && name[0]) + window.PutCStringTruncated(1, name); + + attr_t changd_attr = 0; + if (valobj->GetValueDidChange()) + changd_attr = COLOR_PAIR(RedOnBlack) | A_BOLD; + + if (value && value[0]) { + window.PutCStringTruncated(1, " = "); + if (changd_attr) + window.AttributeOn(changd_attr); + window.PutCStringTruncated(1, value); + if (changd_attr) + window.AttributeOff(changd_attr); + } + + if (summary && summary[0]) { + window.PutCStringTruncated(1, " "); + if (changd_attr) + window.AttributeOn(changd_attr); + window.PutCStringTruncated(1, summary); + if (changd_attr) + window.AttributeOff(changd_attr); + } + + if (highlight) + window.AttributeOff(A_REVERSE); + + return true; + } + + void DisplayRows(Window &window, std::vector<Row> &rows, + DisplayOptions &options) { + // > 0x25B7 + // \/ 0x25BD + + bool window_is_active = window.IsActive(); + for (auto &row : rows) { + const bool last_child = row.parent && &rows[rows.size() - 1] == &row; + // Save the row index in each Row structure + row.row_idx = m_num_rows; + if ((m_num_rows >= m_first_visible_row) && + ((m_num_rows - m_first_visible_row) < + static_cast<size_t>(NumVisibleRows()))) { + row.x = m_min_x; + row.y = m_num_rows - m_first_visible_row + 1; + if (DisplayRowObject(window, row, options, + window_is_active && + m_num_rows == m_selected_row_idx, + last_child)) { + ++m_num_rows; + } else { + row.x = 0; + row.y = 0; + } + } else { + row.x = 0; + row.y = 0; + ++m_num_rows; + } + + if (row.expanded) { + auto &children = row.GetChildren(); + if (!children.empty()) { + DisplayRows(window, children, options); + } + } + } + } + + int CalculateTotalNumberRows(std::vector<Row> &rows) { + int row_count = 0; + for (auto &row : rows) { + ++row_count; + if (row.expanded) + row_count += CalculateTotalNumberRows(row.GetChildren()); + } + return row_count; + } + + static Row *GetRowForRowIndexImpl(std::vector<Row> &rows, size_t &row_index) { + for (auto &row : rows) { + if (row_index == 0) + return &row; + else { + --row_index; + if (row.expanded) { + auto &children = row.GetChildren(); + if (!children.empty()) { + Row *result = GetRowForRowIndexImpl(children, row_index); + if (result) + return result; + } + } + } + } + return nullptr; + } + + Row *GetRowForRowIndex(size_t row_index) { + return GetRowForRowIndexImpl(m_rows, row_index); + } + + int NumVisibleRows() const { return m_max_y - m_min_y; } + + static DisplayOptions g_options; +}; + +class FrameVariablesWindowDelegate : public ValueObjectListDelegate { +public: + FrameVariablesWindowDelegate(Debugger &debugger) + : ValueObjectListDelegate(), m_debugger(debugger) {} + + ~FrameVariablesWindowDelegate() override = default; + + const char *WindowDelegateGetHelpText() override { + return "Frame variable window keyboard shortcuts:"; + } + + bool WindowDelegateDraw(Window &window, bool force) override { + ExecutionContext exe_ctx( + m_debugger.GetCommandInterpreter().GetExecutionContext()); + Process *process = exe_ctx.GetProcessPtr(); + Block *frame_block = nullptr; + StackFrame *frame = nullptr; + + if (process) { + StateType state = process->GetState(); + if (StateIsStoppedState(state, true)) { + frame = exe_ctx.GetFramePtr(); + if (frame) + frame_block = frame->GetFrameBlock(); + } else if (StateIsRunningState(state)) { + return true; // Don't do any updating when we are running + } + } + + ValueObjectList local_values; + if (frame_block) { + // Only update the variables if they have changed + if (m_frame_block != frame_block) { + m_frame_block = frame_block; + + VariableList *locals = frame->GetVariableList(true, nullptr); + if (locals) { + const DynamicValueType use_dynamic = eDynamicDontRunTarget; + for (const VariableSP &local_sp : *locals) { + ValueObjectSP value_sp = + frame->GetValueObjectForFrameVariable(local_sp, use_dynamic); + if (value_sp) { + ValueObjectSP synthetic_value_sp = value_sp->GetSyntheticValue(); + if (synthetic_value_sp) + local_values.Append(synthetic_value_sp); + else + local_values.Append(value_sp); + } + } + // Update the values + SetValues(local_values); + } + } + } else { + m_frame_block = nullptr; + // Update the values with an empty list if there is no frame + SetValues(local_values); + } + + return ValueObjectListDelegate::WindowDelegateDraw(window, force); + } + +protected: + Debugger &m_debugger; + Block *m_frame_block = nullptr; +}; + +class RegistersWindowDelegate : public ValueObjectListDelegate { +public: + RegistersWindowDelegate(Debugger &debugger) + : ValueObjectListDelegate(), m_debugger(debugger) {} + + ~RegistersWindowDelegate() override = default; + + const char *WindowDelegateGetHelpText() override { + return "Register window keyboard shortcuts:"; + } + + bool WindowDelegateDraw(Window &window, bool force) override { + ExecutionContext exe_ctx( + m_debugger.GetCommandInterpreter().GetExecutionContext()); + StackFrame *frame = exe_ctx.GetFramePtr(); + + ValueObjectList value_list; + if (frame) { + if (frame->GetStackID() != m_stack_id) { + m_stack_id = frame->GetStackID(); + RegisterContextSP reg_ctx(frame->GetRegisterContext()); + if (reg_ctx) { + const uint32_t num_sets = reg_ctx->GetRegisterSetCount(); + for (uint32_t set_idx = 0; set_idx < num_sets; ++set_idx) { + value_list.Append( + ValueObjectRegisterSet::Create(frame, reg_ctx, set_idx)); + } + } + SetValues(value_list); + } + } else { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive()) + return true; // Don't do any updating if we are running + else { + // Update the values with an empty list if there is no process or the + // process isn't alive anymore + SetValues(value_list); + } + } + return ValueObjectListDelegate::WindowDelegateDraw(window, force); + } + +protected: + Debugger &m_debugger; + StackID m_stack_id; +}; + +static const char *CursesKeyToCString(int ch) { + static char g_desc[32]; + if (ch >= KEY_F0 && ch < KEY_F0 + 64) { + snprintf(g_desc, sizeof(g_desc), "F%u", ch - KEY_F0); + return g_desc; + } + switch (ch) { + case KEY_DOWN: + return "down"; + case KEY_UP: + return "up"; + case KEY_LEFT: + return "left"; + case KEY_RIGHT: + return "right"; + case KEY_HOME: + return "home"; + case KEY_BACKSPACE: + return "backspace"; + case KEY_DL: + return "delete-line"; + case KEY_IL: + return "insert-line"; + case KEY_DC: + return "delete-char"; + case KEY_IC: + return "insert-char"; + case KEY_CLEAR: + return "clear"; + case KEY_EOS: + return "clear-to-eos"; + case KEY_EOL: + return "clear-to-eol"; + case KEY_SF: + return "scroll-forward"; + case KEY_SR: + return "scroll-backward"; + case KEY_NPAGE: + return "page-down"; + case KEY_PPAGE: + return "page-up"; + case KEY_STAB: + return "set-tab"; + case KEY_CTAB: + return "clear-tab"; + case KEY_CATAB: + return "clear-all-tabs"; + case KEY_ENTER: + return "enter"; + case KEY_PRINT: + return "print"; + case KEY_LL: + return "lower-left key"; + case KEY_A1: + return "upper left of keypad"; + case KEY_A3: + return "upper right of keypad"; + case KEY_B2: + return "center of keypad"; + case KEY_C1: + return "lower left of keypad"; + case KEY_C3: + return "lower right of keypad"; + case KEY_BTAB: + return "back-tab key"; + case KEY_BEG: + return "begin key"; + case KEY_CANCEL: + return "cancel key"; + case KEY_CLOSE: + return "close key"; + case KEY_COMMAND: + return "command key"; + case KEY_COPY: + return "copy key"; + case KEY_CREATE: + return "create key"; + case KEY_END: + return "end key"; + case KEY_EXIT: + return "exit key"; + case KEY_FIND: + return "find key"; + case KEY_HELP: + return "help key"; + case KEY_MARK: + return "mark key"; + case KEY_MESSAGE: + return "message key"; + case KEY_MOVE: + return "move key"; + case KEY_NEXT: + return "next key"; + case KEY_OPEN: + return "open key"; + case KEY_OPTIONS: + return "options key"; + case KEY_PREVIOUS: + return "previous key"; + case KEY_REDO: + return "redo key"; + case KEY_REFERENCE: + return "reference key"; + case KEY_REFRESH: + return "refresh key"; + case KEY_REPLACE: + return "replace key"; + case KEY_RESTART: + return "restart key"; + case KEY_RESUME: + return "resume key"; + case KEY_SAVE: + return "save key"; + case KEY_SBEG: + return "shifted begin key"; + case KEY_SCANCEL: + return "shifted cancel key"; + case KEY_SCOMMAND: + return "shifted command key"; + case KEY_SCOPY: + return "shifted copy key"; + case KEY_SCREATE: + return "shifted create key"; + case KEY_SDC: + return "shifted delete-character key"; + case KEY_SDL: + return "shifted delete-line key"; + case KEY_SELECT: + return "select key"; + case KEY_SEND: + return "shifted end key"; + case KEY_SEOL: + return "shifted clear-to-end-of-line key"; + case KEY_SEXIT: + return "shifted exit key"; + case KEY_SFIND: + return "shifted find key"; + case KEY_SHELP: + return "shifted help key"; + case KEY_SHOME: + return "shifted home key"; + case KEY_SIC: + return "shifted insert-character key"; + case KEY_SLEFT: + return "shifted left-arrow key"; + case KEY_SMESSAGE: + return "shifted message key"; + case KEY_SMOVE: + return "shifted move key"; + case KEY_SNEXT: + return "shifted next key"; + case KEY_SOPTIONS: + return "shifted options key"; + case KEY_SPREVIOUS: + return "shifted previous key"; + case KEY_SPRINT: + return "shifted print key"; + case KEY_SREDO: + return "shifted redo key"; + case KEY_SREPLACE: + return "shifted replace key"; + case KEY_SRIGHT: + return "shifted right-arrow key"; + case KEY_SRSUME: + return "shifted resume key"; + case KEY_SSAVE: + return "shifted save key"; + case KEY_SSUSPEND: + return "shifted suspend key"; + case KEY_SUNDO: + return "shifted undo key"; + case KEY_SUSPEND: + return "suspend key"; + case KEY_UNDO: + return "undo key"; + case KEY_MOUSE: + return "Mouse event has occurred"; + case KEY_RESIZE: + return "Terminal resize event"; +#ifdef KEY_EVENT + case KEY_EVENT: + return "We were interrupted by an event"; +#endif + case KEY_RETURN: + return "return"; + case ' ': + return "space"; + case '\t': + return "tab"; + case KEY_ESCAPE: + return "escape"; + default: + if (llvm::isPrint(ch)) + snprintf(g_desc, sizeof(g_desc), "%c", ch); + else + snprintf(g_desc, sizeof(g_desc), "\\x%2.2x", ch); + return g_desc; + } + return nullptr; +} + +HelpDialogDelegate::HelpDialogDelegate(const char *text, + KeyHelp *key_help_array) + : m_text() { + if (text && text[0]) { + m_text.SplitIntoLines(text); + m_text.AppendString(""); + } + if (key_help_array) { + for (KeyHelp *key = key_help_array; key->ch; ++key) { + StreamString key_description; + key_description.Printf("%10s - %s", CursesKeyToCString(key->ch), + key->description); + m_text.AppendString(key_description.GetString()); + } + } +} + +HelpDialogDelegate::~HelpDialogDelegate() = default; + +bool HelpDialogDelegate::WindowDelegateDraw(Window &window, bool force) { + window.Erase(); + const int window_height = window.GetHeight(); + int x = 2; + int y = 1; + const int min_y = y; + const int max_y = window_height - 1 - y; + const size_t num_visible_lines = max_y - min_y + 1; + const size_t num_lines = m_text.GetSize(); + const char *bottom_message; + if (num_lines <= num_visible_lines) + bottom_message = "Press any key to exit"; + else + bottom_message = "Use arrows to scroll, any other key to exit"; + window.DrawTitleBox(window.GetName(), bottom_message); + while (y <= max_y) { + window.MoveCursor(x, y); + window.PutCStringTruncated( + 1, m_text.GetStringAtIndex(m_first_visible_line + y - min_y)); + ++y; + } + return true; +} + +HandleCharResult HelpDialogDelegate::WindowDelegateHandleChar(Window &window, + int key) { + bool done = false; + const size_t num_lines = m_text.GetSize(); + const size_t num_visible_lines = window.GetHeight() - 2; + + if (num_lines <= num_visible_lines) { + done = true; + // If we have all lines visible and don't need scrolling, then any key + // press will cause us to exit + } else { + switch (key) { + case KEY_UP: + if (m_first_visible_line > 0) + --m_first_visible_line; + break; + + case KEY_DOWN: + if (m_first_visible_line + num_visible_lines < num_lines) + ++m_first_visible_line; + break; + + case KEY_PPAGE: + case ',': + if (m_first_visible_line > 0) { + if (static_cast<size_t>(m_first_visible_line) >= num_visible_lines) + m_first_visible_line -= num_visible_lines; + else + m_first_visible_line = 0; + } + break; + + case KEY_NPAGE: + case '.': + if (m_first_visible_line + num_visible_lines < num_lines) { + m_first_visible_line += num_visible_lines; + if (static_cast<size_t>(m_first_visible_line) > num_lines) + m_first_visible_line = num_lines - num_visible_lines; + } + break; + + default: + done = true; + break; + } + } + if (done) + window.GetParent()->RemoveSubWindow(&window); + return eKeyHandled; +} + +class ApplicationDelegate : public WindowDelegate, public MenuDelegate { +public: + enum { + eMenuID_LLDB = 1, + eMenuID_LLDBAbout, + eMenuID_LLDBExit, + + eMenuID_Target, + eMenuID_TargetCreate, + eMenuID_TargetDelete, + + eMenuID_Process, + eMenuID_ProcessAttach, + eMenuID_ProcessDetachResume, + eMenuID_ProcessDetachSuspended, + eMenuID_ProcessLaunch, + eMenuID_ProcessContinue, + eMenuID_ProcessHalt, + eMenuID_ProcessKill, + + eMenuID_Thread, + eMenuID_ThreadStepIn, + eMenuID_ThreadStepOver, + eMenuID_ThreadStepOut, + + eMenuID_View, + eMenuID_ViewBacktrace, + eMenuID_ViewRegisters, + eMenuID_ViewSource, + eMenuID_ViewVariables, + eMenuID_ViewBreakpoints, + + eMenuID_Help, + eMenuID_HelpGUIHelp + }; + + ApplicationDelegate(Application &app, Debugger &debugger) + : WindowDelegate(), MenuDelegate(), m_app(app), m_debugger(debugger) {} + + ~ApplicationDelegate() override = default; + + bool WindowDelegateDraw(Window &window, bool force) override { + return false; // Drawing not handled, let standard window drawing happen + } + + HandleCharResult WindowDelegateHandleChar(Window &window, int key) override { + switch (key) { + case '\t': + window.SelectNextWindowAsActive(); + return eKeyHandled; + + case KEY_SHIFT_TAB: + window.SelectPreviousWindowAsActive(); + return eKeyHandled; + + case 'h': + window.CreateHelpSubwindow(); + return eKeyHandled; + + case KEY_ESCAPE: + return eQuitApplication; + + default: + break; + } + return eKeyNotHandled; + } + + const char *WindowDelegateGetHelpText() override { + return "Welcome to the LLDB curses GUI.\n\n" + "Press the TAB key to change the selected view.\n" + "Each view has its own keyboard shortcuts, press 'h' to open a " + "dialog to display them.\n\n" + "Common key bindings for all views:"; + } + + KeyHelp *WindowDelegateGetKeyHelp() override { + static curses::KeyHelp g_source_view_key_help[] = { + {'\t', "Select next view"}, + {KEY_BTAB, "Select previous view"}, + {'h', "Show help dialog with view specific key bindings"}, + {',', "Page up"}, + {'.', "Page down"}, + {KEY_UP, "Select previous"}, + {KEY_DOWN, "Select next"}, + {KEY_LEFT, "Unexpand or select parent"}, + {KEY_RIGHT, "Expand"}, + {KEY_PPAGE, "Page up"}, + {KEY_NPAGE, "Page down"}, + {'\0', nullptr}}; + return g_source_view_key_help; + } + + MenuActionResult MenuDelegateAction(Menu &menu) override { + switch (menu.GetIdentifier()) { + case eMenuID_TargetCreate: { + WindowSP main_window_sp = m_app.GetMainWindow(); + FormDelegateSP form_delegate_sp = + FormDelegateSP(new TargetCreateFormDelegate(m_debugger)); + Rect bounds = main_window_sp->GetCenteredRect(80, 19); + WindowSP form_window_sp = main_window_sp->CreateSubWindow( + form_delegate_sp->GetName().c_str(), bounds, true); + WindowDelegateSP window_delegate_sp = + WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); + form_window_sp->SetDelegate(window_delegate_sp); + return MenuActionResult::Handled; + } + case eMenuID_ThreadStepIn: { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope()) { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive() && + StateIsStoppedState(process->GetState(), true)) + exe_ctx.GetThreadRef().StepIn(true); + } + } + return MenuActionResult::Handled; + + case eMenuID_ThreadStepOut: { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope()) { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive() && + StateIsStoppedState(process->GetState(), true)) { + Thread *thread = exe_ctx.GetThreadPtr(); + uint32_t frame_idx = + thread->GetSelectedFrameIndex(SelectMostRelevantFrame); + exe_ctx.GetThreadRef().StepOut(frame_idx); + } + } + } + return MenuActionResult::Handled; + + case eMenuID_ThreadStepOver: { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope()) { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive() && + StateIsStoppedState(process->GetState(), true)) + exe_ctx.GetThreadRef().StepOver(true); + } + } + return MenuActionResult::Handled; + + case eMenuID_ProcessAttach: { + WindowSP main_window_sp = m_app.GetMainWindow(); + FormDelegateSP form_delegate_sp = FormDelegateSP( + new ProcessAttachFormDelegate(m_debugger, main_window_sp)); + Rect bounds = main_window_sp->GetCenteredRect(80, 22); + WindowSP form_window_sp = main_window_sp->CreateSubWindow( + form_delegate_sp->GetName().c_str(), bounds, true); + WindowDelegateSP window_delegate_sp = + WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); + form_window_sp->SetDelegate(window_delegate_sp); + return MenuActionResult::Handled; + } + case eMenuID_ProcessLaunch: { + WindowSP main_window_sp = m_app.GetMainWindow(); + FormDelegateSP form_delegate_sp = FormDelegateSP( + new ProcessLaunchFormDelegate(m_debugger, main_window_sp)); + Rect bounds = main_window_sp->GetCenteredRect(80, 22); + WindowSP form_window_sp = main_window_sp->CreateSubWindow( + form_delegate_sp->GetName().c_str(), bounds, true); + WindowDelegateSP window_delegate_sp = + WindowDelegateSP(new FormWindowDelegate(form_delegate_sp)); + form_window_sp->SetDelegate(window_delegate_sp); + return MenuActionResult::Handled; + } + + case eMenuID_ProcessContinue: { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive() && + StateIsStoppedState(process->GetState(), true)) + process->Resume(); + } + } + return MenuActionResult::Handled; + + case eMenuID_ProcessKill: { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive()) + process->Destroy(false); + } + } + return MenuActionResult::Handled; + + case eMenuID_ProcessHalt: { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive()) + process->Halt(); + } + } + return MenuActionResult::Handled; + + case eMenuID_ProcessDetachResume: + case eMenuID_ProcessDetachSuspended: { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) { + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive()) + process->Detach(menu.GetIdentifier() == + eMenuID_ProcessDetachSuspended); + } + } + return MenuActionResult::Handled; + + case eMenuID_Process: { + // Populate the menu with all of the threads if the process is stopped + // when the Process menu gets selected and is about to display its + // submenu. + Menus &submenus = menu.GetSubmenus(); + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsAlive() && + StateIsStoppedState(process->GetState(), true)) { + if (submenus.size() == 7) + menu.AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); + else if (submenus.size() > 8) + submenus.erase(submenus.begin() + 8, submenus.end()); + + ThreadList &threads = process->GetThreadList(); + std::lock_guard<std::recursive_mutex> guard(threads.GetMutex()); + size_t num_threads = threads.GetSize(); + for (size_t i = 0; i < num_threads; ++i) { + ThreadSP thread_sp = threads.GetThreadAtIndex(i); + char menu_char = '\0'; + if (i < 9) + menu_char = '1' + i; + StreamString thread_menu_title; + thread_menu_title.Printf("Thread %u", thread_sp->GetIndexID()); + const char *thread_name = thread_sp->GetName(); + if (thread_name && thread_name[0]) + thread_menu_title.Printf(" %s", thread_name); + else { + const char *queue_name = thread_sp->GetQueueName(); + if (queue_name && queue_name[0]) + thread_menu_title.Printf(" %s", queue_name); + } + menu.AddSubmenu( + MenuSP(new Menu(thread_menu_title.GetString().str().c_str(), + nullptr, menu_char, thread_sp->GetID()))); + } + } else if (submenus.size() > 7) { + // Remove the separator and any other thread submenu items that were + // previously added + submenus.erase(submenus.begin() + 7, submenus.end()); + } + // Since we are adding and removing items we need to recalculate the + // name lengths + menu.RecalculateNameLengths(); + } + return MenuActionResult::Handled; + + case eMenuID_ViewVariables: { + WindowSP main_window_sp = m_app.GetMainWindow(); + WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); + WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); + WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); + const Rect source_bounds = source_window_sp->GetBounds(); + + if (variables_window_sp) { + const Rect variables_bounds = variables_window_sp->GetBounds(); + + main_window_sp->RemoveSubWindow(variables_window_sp.get()); + + if (registers_window_sp) { + // We have a registers window, so give all the area back to the + // registers window + Rect registers_bounds = variables_bounds; + registers_bounds.size.width = source_bounds.size.width; + registers_window_sp->SetBounds(registers_bounds); + } else { + // We have no registers window showing so give the bottom area back + // to the source view + source_window_sp->Resize(source_bounds.size.width, + source_bounds.size.height + + variables_bounds.size.height); + } + } else { + Rect new_variables_rect; + if (registers_window_sp) { + // We have a registers window so split the area of the registers + // window into two columns where the left hand side will be the + // variables and the right hand side will be the registers + const Rect variables_bounds = registers_window_sp->GetBounds(); + Rect new_registers_rect; + variables_bounds.VerticalSplitPercentage(0.50, new_variables_rect, + new_registers_rect); + registers_window_sp->SetBounds(new_registers_rect); + } else { + // No registers window, grab the bottom part of the source window + Rect new_source_rect; + source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, + new_variables_rect); + source_window_sp->SetBounds(new_source_rect); + } + WindowSP new_window_sp = main_window_sp->CreateSubWindow( + "Variables", new_variables_rect, false); + new_window_sp->SetDelegate( + WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); + } + touchwin(stdscr); + } + return MenuActionResult::Handled; + + case eMenuID_ViewRegisters: { + WindowSP main_window_sp = m_app.GetMainWindow(); + WindowSP source_window_sp = main_window_sp->FindSubWindow("Source"); + WindowSP variables_window_sp = main_window_sp->FindSubWindow("Variables"); + WindowSP registers_window_sp = main_window_sp->FindSubWindow("Registers"); + const Rect source_bounds = source_window_sp->GetBounds(); + + if (registers_window_sp) { + if (variables_window_sp) { + const Rect variables_bounds = variables_window_sp->GetBounds(); + + // We have a variables window, so give all the area back to the + // variables window + variables_window_sp->Resize(variables_bounds.size.width + + registers_window_sp->GetWidth(), + variables_bounds.size.height); + } else { + // We have no variables window showing so give the bottom area back + // to the source view + source_window_sp->Resize(source_bounds.size.width, + source_bounds.size.height + + registers_window_sp->GetHeight()); + } + main_window_sp->RemoveSubWindow(registers_window_sp.get()); + } else { + Rect new_regs_rect; + if (variables_window_sp) { + // We have a variables window, split it into two columns where the + // left hand side will be the variables and the right hand side will + // be the registers + const Rect variables_bounds = variables_window_sp->GetBounds(); + Rect new_vars_rect; + variables_bounds.VerticalSplitPercentage(0.50, new_vars_rect, + new_regs_rect); + variables_window_sp->SetBounds(new_vars_rect); + } else { + // No variables window, grab the bottom part of the source window + Rect new_source_rect; + source_bounds.HorizontalSplitPercentage(0.70, new_source_rect, + new_regs_rect); + source_window_sp->SetBounds(new_source_rect); + } + WindowSP new_window_sp = + main_window_sp->CreateSubWindow("Registers", new_regs_rect, false); + new_window_sp->SetDelegate( + WindowDelegateSP(new RegistersWindowDelegate(m_debugger))); + } + touchwin(stdscr); + } + return MenuActionResult::Handled; + + case eMenuID_ViewBreakpoints: { + WindowSP main_window_sp = m_app.GetMainWindow(); + WindowSP threads_window_sp = main_window_sp->FindSubWindow("Threads"); + WindowSP breakpoints_window_sp = + main_window_sp->FindSubWindow("Breakpoints"); + const Rect threads_bounds = threads_window_sp->GetBounds(); + + // If a breakpoints window already exists, remove it and give the area + // it used to occupy to the threads window. If it doesn't exist, split + // the threads window horizontally into two windows where the top window + // is the threads window and the bottom window is a newly added + // breakpoints window. + if (breakpoints_window_sp) { + threads_window_sp->Resize(threads_bounds.size.width, + threads_bounds.size.height + + breakpoints_window_sp->GetHeight()); + main_window_sp->RemoveSubWindow(breakpoints_window_sp.get()); + } else { + Rect new_threads_bounds, breakpoints_bounds; + threads_bounds.HorizontalSplitPercentage(0.70, new_threads_bounds, + breakpoints_bounds); + threads_window_sp->SetBounds(new_threads_bounds); + breakpoints_window_sp = main_window_sp->CreateSubWindow( + "Breakpoints", breakpoints_bounds, false); + TreeDelegateSP breakpoints_delegate_sp( + new BreakpointsTreeDelegate(m_debugger)); + breakpoints_window_sp->SetDelegate(WindowDelegateSP( + new TreeWindowDelegate(m_debugger, breakpoints_delegate_sp))); + } + touchwin(stdscr); + return MenuActionResult::Handled; + } + + case eMenuID_HelpGUIHelp: + m_app.GetMainWindow()->CreateHelpSubwindow(); + return MenuActionResult::Handled; + + default: + break; + } + + return MenuActionResult::NotHandled; + } + +protected: + Application &m_app; + Debugger &m_debugger; +}; + +class StatusBarWindowDelegate : public WindowDelegate { +public: + StatusBarWindowDelegate(Debugger &debugger) : m_debugger(debugger) { + FormatEntity::Parse("Thread: ${thread.id%tid}", m_format); + } + + ~StatusBarWindowDelegate() override = default; + + bool WindowDelegateDraw(Window &window, bool force) override { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + Process *process = exe_ctx.GetProcessPtr(); + Thread *thread = exe_ctx.GetThreadPtr(); + StackFrame *frame = exe_ctx.GetFramePtr(); + window.Erase(); + window.SetBackground(BlackOnWhite); + window.MoveCursor(0, 0); + if (process) { + const StateType state = process->GetState(); + window.Printf("Process: %5" PRIu64 " %10s", process->GetID(), + StateAsCString(state)); + + if (StateIsStoppedState(state, true)) { + StreamString strm; + if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, + nullptr, nullptr, false, false)) { + window.MoveCursor(40, 0); + window.PutCStringTruncated(1, strm.GetString().str().c_str()); + } + + window.MoveCursor(60, 0); + if (frame) + window.Printf("Frame: %3u PC = 0x%16.16" PRIx64, + frame->GetFrameIndex(), + frame->GetFrameCodeAddress().GetOpcodeLoadAddress( + exe_ctx.GetTargetPtr())); + } else if (state == eStateExited) { + const char *exit_desc = process->GetExitDescription(); + const int exit_status = process->GetExitStatus(); + if (exit_desc && exit_desc[0]) + window.Printf(" with status = %i (%s)", exit_status, exit_desc); + else + window.Printf(" with status = %i", exit_status); + } + } + return true; + } + +protected: + Debugger &m_debugger; + FormatEntity::Entry m_format; +}; + +class SourceFileWindowDelegate : public WindowDelegate { +public: + SourceFileWindowDelegate(Debugger &debugger) + : WindowDelegate(), m_debugger(debugger), m_sc(), m_file_sp(), + m_disassembly_sp(), m_disassembly_range(), m_title() {} + + ~SourceFileWindowDelegate() override = default; + + void Update(const SymbolContext &sc) { m_sc = sc; } + + uint32_t NumVisibleLines() const { return m_max_y - m_min_y; } + + const char *WindowDelegateGetHelpText() override { + return "Source/Disassembly window keyboard shortcuts:"; + } + + KeyHelp *WindowDelegateGetKeyHelp() override { + static curses::KeyHelp g_source_view_key_help[] = { + {KEY_RETURN, "Run to selected line with one shot breakpoint"}, + {KEY_UP, "Select previous source line"}, + {KEY_DOWN, "Select next source line"}, + {KEY_LEFT, "Scroll to the left"}, + {KEY_RIGHT, "Scroll to the right"}, + {KEY_PPAGE, "Page up"}, + {KEY_NPAGE, "Page down"}, + {'b', "Set breakpoint on selected source/disassembly line"}, + {'c', "Continue process"}, + {'D', "Detach with process suspended"}, + {'h', "Show help dialog"}, + {'n', "Step over (source line)"}, + {'N', "Step over (single instruction)"}, + {'f', "Step out (finish)"}, + {'s', "Step in (source line)"}, + {'S', "Step in (single instruction)"}, + {'u', "Frame up"}, + {'d', "Frame down"}, + {',', "Page up"}, + {'.', "Page down"}, + {'\0', nullptr}}; + return g_source_view_key_help; + } + + bool WindowDelegateDraw(Window &window, bool force) override { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + Process *process = exe_ctx.GetProcessPtr(); + Thread *thread = nullptr; + + bool update_location = false; + if (process) { + StateType state = process->GetState(); + if (StateIsStoppedState(state, true)) { + // We are stopped, so it is ok to + update_location = true; + } + } + + m_min_x = 1; + m_min_y = 2; + m_max_x = window.GetMaxX() - 1; + m_max_y = window.GetMaxY() - 1; + + const uint32_t num_visible_lines = NumVisibleLines(); + StackFrameSP frame_sp; + bool set_selected_line_to_pc = false; + + if (update_location) { + const bool process_alive = process->IsAlive(); + bool thread_changed = false; + if (process_alive) { + thread = exe_ctx.GetThreadPtr(); + if (thread) { + frame_sp = thread->GetSelectedFrame(SelectMostRelevantFrame); + auto tid = thread->GetID(); + thread_changed = tid != m_tid; + m_tid = tid; + } else { + if (m_tid != LLDB_INVALID_THREAD_ID) { + thread_changed = true; + m_tid = LLDB_INVALID_THREAD_ID; + } + } + } + const uint32_t stop_id = process ? process->GetStopID() : 0; + const bool stop_id_changed = stop_id != m_stop_id; + bool frame_changed = false; + m_stop_id = stop_id; + m_title.Clear(); + if (frame_sp) { + m_sc = frame_sp->GetSymbolContext(eSymbolContextEverything); + if (m_sc.module_sp) { + m_title.Printf( + "%s", m_sc.module_sp->GetFileSpec().GetFilename().GetCString()); + ConstString func_name = m_sc.GetFunctionName(); + if (func_name) + m_title.Printf("`%s", func_name.GetCString()); + } + const uint32_t frame_idx = frame_sp->GetFrameIndex(); + frame_changed = frame_idx != m_frame_idx; + m_frame_idx = frame_idx; + } else { + m_sc.Clear(true); + frame_changed = m_frame_idx != UINT32_MAX; + m_frame_idx = UINT32_MAX; + } + + const bool context_changed = + thread_changed || frame_changed || stop_id_changed; + + if (process_alive) { + if (m_sc.line_entry.IsValid()) { + m_pc_line = m_sc.line_entry.line; + if (m_pc_line != UINT32_MAX) + --m_pc_line; // Convert to zero based line number... + // Update the selected line if the stop ID changed... + if (context_changed) + m_selected_line = m_pc_line; + + if (m_file_sp && + m_file_sp->GetFileSpec() == m_sc.line_entry.GetFile()) { + // Same file, nothing to do, we should either have the lines or + // not (source file missing) + if (m_selected_line >= static_cast<size_t>(m_first_visible_line)) { + if (m_selected_line >= m_first_visible_line + num_visible_lines) + m_first_visible_line = m_selected_line - 10; + } else { + if (m_selected_line > 10) + m_first_visible_line = m_selected_line - 10; + else + m_first_visible_line = 0; + } + } else { + // File changed, set selected line to the line with the PC + m_selected_line = m_pc_line; + m_file_sp = m_debugger.GetSourceManager().GetFile( + m_sc.line_entry.GetFile()); + if (m_file_sp) { + const size_t num_lines = m_file_sp->GetNumLines(); + m_line_width = 1; + for (size_t n = num_lines; n >= 10; n = n / 10) + ++m_line_width; + + if (num_lines < num_visible_lines || + m_selected_line < num_visible_lines) + m_first_visible_line = 0; + else + m_first_visible_line = m_selected_line - 10; + } + } + } else { + m_file_sp.reset(); + } + + if (!m_file_sp || m_file_sp->GetNumLines() == 0) { + // Show disassembly + bool prefer_file_cache = false; + if (m_sc.function) { + if (m_disassembly_scope != m_sc.function) { + m_disassembly_scope = m_sc.function; + m_disassembly_sp = m_sc.function->GetInstructions( + exe_ctx, nullptr, !prefer_file_cache); + if (m_disassembly_sp) { + set_selected_line_to_pc = true; + m_disassembly_range = m_sc.function->GetAddressRange(); + } else { + m_disassembly_range.Clear(); + } + } else { + set_selected_line_to_pc = context_changed; + } + } else if (m_sc.symbol) { + if (m_disassembly_scope != m_sc.symbol) { + m_disassembly_scope = m_sc.symbol; + m_disassembly_sp = m_sc.symbol->GetInstructions( + exe_ctx, nullptr, prefer_file_cache); + if (m_disassembly_sp) { + set_selected_line_to_pc = true; + m_disassembly_range.GetBaseAddress() = + m_sc.symbol->GetAddress(); + m_disassembly_range.SetByteSize(m_sc.symbol->GetByteSize()); + } else { + m_disassembly_range.Clear(); + } + } else { + set_selected_line_to_pc = context_changed; + } + } + } + } else { + m_pc_line = UINT32_MAX; + } + } + + const int window_width = window.GetWidth(); + window.Erase(); + window.DrawTitleBox("Sources"); + if (!m_title.GetString().empty()) { + window.AttributeOn(A_REVERSE); + window.MoveCursor(1, 1); + window.PutChar(' '); + window.PutCStringTruncated(1, m_title.GetString().str().c_str()); + int x = window.GetCursorX(); + if (x < window_width - 1) { + window.Printf("%*s", window_width - x - 1, ""); + } + window.AttributeOff(A_REVERSE); + } + + Target *target = exe_ctx.GetTargetPtr(); + const size_t num_source_lines = GetNumSourceLines(); + if (num_source_lines > 0) { + // Display source + BreakpointLines bp_lines; + if (target) { + BreakpointList &bp_list = target->GetBreakpointList(); + const size_t num_bps = bp_list.GetSize(); + for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { + BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); + const size_t num_bps_locs = bp_sp->GetNumLocations(); + for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { + BreakpointLocationSP bp_loc_sp = + bp_sp->GetLocationAtIndex(bp_loc_idx); + LineEntry bp_loc_line_entry; + if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry( + bp_loc_line_entry)) { + if (m_file_sp->GetFileSpec() == bp_loc_line_entry.GetFile()) { + bp_lines.insert(bp_loc_line_entry.line); + } + } + } + } + } + + for (size_t i = 0; i < num_visible_lines; ++i) { + const uint32_t curr_line = m_first_visible_line + i; + if (curr_line < num_source_lines) { + const int line_y = m_min_y + i; + window.MoveCursor(1, line_y); + const bool is_pc_line = curr_line == m_pc_line; + const bool line_is_selected = m_selected_line == curr_line; + // Highlight the line as the PC line first (done by passing + // argument to OutputColoredStringTruncated()), then if the selected + // line isn't the same as the PC line, highlight it differently. + attr_t highlight_attr = 0; + attr_t bp_attr = 0; + if (line_is_selected && !is_pc_line) + highlight_attr = A_REVERSE; + + if (bp_lines.find(curr_line + 1) != bp_lines.end()) + bp_attr = COLOR_PAIR(BlackOnWhite); + + if (bp_attr) + window.AttributeOn(bp_attr); + + window.Printf(" %*u ", m_line_width, curr_line + 1); + + if (bp_attr) + window.AttributeOff(bp_attr); + + window.PutChar(ACS_VLINE); + // Mark the line with the PC with a diamond + if (is_pc_line) + window.PutChar(ACS_DIAMOND); + else + window.PutChar(' '); + + if (highlight_attr) + window.AttributeOn(highlight_attr); + + StreamString lineStream; + + std::optional<size_t> column; + if (is_pc_line && m_sc.line_entry.IsValid() && m_sc.line_entry.column) + column = m_sc.line_entry.column - 1; + m_file_sp->DisplaySourceLines(curr_line + 1, column, 0, 0, + &lineStream); + StringRef line = lineStream.GetString(); + if (line.ends_with("\n")) + line = line.drop_back(); + bool wasWritten = window.OutputColoredStringTruncated( + 1, line, m_first_visible_column, is_pc_line); + if (!wasWritten && (line_is_selected || is_pc_line)) { + // Draw an empty space to show the selected/PC line if empty, + // or draw '<' if nothing is visible because of scrolling too much + // to the right. + window.PutCStringTruncated( + 1, line.empty() && m_first_visible_column == 0 ? " " : "<"); + } + + if (is_pc_line && frame_sp && + frame_sp->GetConcreteFrameIndex() == 0) { + StopInfoSP stop_info_sp; + if (thread) + stop_info_sp = thread->GetStopInfo(); + if (stop_info_sp) { + const char *stop_description = stop_info_sp->GetDescription(); + if (stop_description && stop_description[0]) { + size_t stop_description_len = strlen(stop_description); + int desc_x = window_width - stop_description_len - 16; + if (desc_x - window.GetCursorX() > 0) + window.Printf("%*s", desc_x - window.GetCursorX(), ""); + window.MoveCursor(window_width - stop_description_len - 16, + line_y); + const attr_t stop_reason_attr = COLOR_PAIR(WhiteOnBlue); + window.AttributeOn(stop_reason_attr); + window.PrintfTruncated(1, " <<< Thread %u: %s ", + thread->GetIndexID(), stop_description); + window.AttributeOff(stop_reason_attr); + } + } else { + window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); + } + } + if (highlight_attr) + window.AttributeOff(highlight_attr); + } else { + break; + } + } + } else { + size_t num_disassembly_lines = GetNumDisassemblyLines(); + if (num_disassembly_lines > 0) { + // Display disassembly + BreakpointAddrs bp_file_addrs; + Target *target = exe_ctx.GetTargetPtr(); + if (target) { + BreakpointList &bp_list = target->GetBreakpointList(); + const size_t num_bps = bp_list.GetSize(); + for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { + BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); + const size_t num_bps_locs = bp_sp->GetNumLocations(); + for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; + ++bp_loc_idx) { + BreakpointLocationSP bp_loc_sp = + bp_sp->GetLocationAtIndex(bp_loc_idx); + LineEntry bp_loc_line_entry; + const lldb::addr_t file_addr = + bp_loc_sp->GetAddress().GetFileAddress(); + if (file_addr != LLDB_INVALID_ADDRESS) { + if (m_disassembly_range.ContainsFileAddress(file_addr)) + bp_file_addrs.insert(file_addr); + } + } + } + } + + const attr_t selected_highlight_attr = A_REVERSE; + const attr_t pc_highlight_attr = COLOR_PAIR(WhiteOnBlue); + + StreamString strm; + + InstructionList &insts = m_disassembly_sp->GetInstructionList(); + Address pc_address; + + if (frame_sp) + pc_address = frame_sp->GetFrameCodeAddress(); + const uint32_t pc_idx = + pc_address.IsValid() + ? insts.GetIndexOfInstructionAtAddress(pc_address) + : UINT32_MAX; + if (set_selected_line_to_pc) { + m_selected_line = pc_idx; + } + + const uint32_t non_visible_pc_offset = (num_visible_lines / 5); + if (static_cast<size_t>(m_first_visible_line) >= num_disassembly_lines) + m_first_visible_line = 0; + + if (pc_idx < num_disassembly_lines) { + if (pc_idx < static_cast<uint32_t>(m_first_visible_line) || + pc_idx >= m_first_visible_line + num_visible_lines) + m_first_visible_line = pc_idx - non_visible_pc_offset; + } + + for (size_t i = 0; i < num_visible_lines; ++i) { + const uint32_t inst_idx = m_first_visible_line + i; + Instruction *inst = insts.GetInstructionAtIndex(inst_idx).get(); + if (!inst) + break; + + const int line_y = m_min_y + i; + window.MoveCursor(1, line_y); + const bool is_pc_line = frame_sp && inst_idx == pc_idx; + const bool line_is_selected = m_selected_line == inst_idx; + // Highlight the line as the PC line first, then if the selected + // line isn't the same as the PC line, highlight it differently + attr_t highlight_attr = 0; + attr_t bp_attr = 0; + if (is_pc_line) + highlight_attr = pc_highlight_attr; + else if (line_is_selected) + highlight_attr = selected_highlight_attr; + + if (bp_file_addrs.find(inst->GetAddress().GetFileAddress()) != + bp_file_addrs.end()) + bp_attr = COLOR_PAIR(BlackOnWhite); + + if (bp_attr) + window.AttributeOn(bp_attr); + + window.Printf(" 0x%16.16llx ", + static_cast<unsigned long long>( + inst->GetAddress().GetLoadAddress(target))); + + if (bp_attr) + window.AttributeOff(bp_attr); + + window.PutChar(ACS_VLINE); + // Mark the line with the PC with a diamond + if (is_pc_line) + window.PutChar(ACS_DIAMOND); + else + window.PutChar(' '); + + if (highlight_attr) + window.AttributeOn(highlight_attr); + + const char *mnemonic = inst->GetMnemonic(&exe_ctx); + const char *operands = inst->GetOperands(&exe_ctx); + const char *comment = inst->GetComment(&exe_ctx); + + if (mnemonic != nullptr && mnemonic[0] == '\0') + mnemonic = nullptr; + if (operands != nullptr && operands[0] == '\0') + operands = nullptr; + if (comment != nullptr && comment[0] == '\0') + comment = nullptr; + + strm.Clear(); + + if (mnemonic != nullptr && operands != nullptr && comment != nullptr) + strm.Printf("%-8s %-25s ; %s", mnemonic, operands, comment); + else if (mnemonic != nullptr && operands != nullptr) + strm.Printf("%-8s %s", mnemonic, operands); + else if (mnemonic != nullptr) + strm.Printf("%s", mnemonic); + + int right_pad = 1; + window.PutCStringTruncated( + right_pad, + strm.GetString().substr(m_first_visible_column).data()); + + if (is_pc_line && frame_sp && + frame_sp->GetConcreteFrameIndex() == 0) { + StopInfoSP stop_info_sp; + if (thread) + stop_info_sp = thread->GetStopInfo(); + if (stop_info_sp) { + const char *stop_description = stop_info_sp->GetDescription(); + if (stop_description && stop_description[0]) { + size_t stop_description_len = strlen(stop_description); + int desc_x = window_width - stop_description_len - 16; + if (desc_x - window.GetCursorX() > 0) + window.Printf("%*s", desc_x - window.GetCursorX(), ""); + window.MoveCursor(window_width - stop_description_len - 15, + line_y); + if (thread) + window.PrintfTruncated(1, "<<< Thread %u: %s ", + thread->GetIndexID(), + stop_description); + } + } else { + window.Printf("%*s", window_width - window.GetCursorX() - 1, ""); + } + } + if (highlight_attr) + window.AttributeOff(highlight_attr); + } + } + } + return true; // Drawing handled + } + + size_t GetNumLines() { + size_t num_lines = GetNumSourceLines(); + if (num_lines == 0) + num_lines = GetNumDisassemblyLines(); + return num_lines; + } + + size_t GetNumSourceLines() const { + if (m_file_sp) + return m_file_sp->GetNumLines(); + return 0; + } + + size_t GetNumDisassemblyLines() const { + if (m_disassembly_sp) + return m_disassembly_sp->GetInstructionList().GetSize(); + return 0; + } + + HandleCharResult WindowDelegateHandleChar(Window &window, int c) override { + const uint32_t num_visible_lines = NumVisibleLines(); + const size_t num_lines = GetNumLines(); + + switch (c) { + case ',': + case KEY_PPAGE: + // Page up key + if (static_cast<uint32_t>(m_first_visible_line) > num_visible_lines) + m_first_visible_line -= num_visible_lines; + else + m_first_visible_line = 0; + m_selected_line = m_first_visible_line; + return eKeyHandled; + + case '.': + case KEY_NPAGE: + // Page down key + { + if (m_first_visible_line + num_visible_lines < num_lines) + m_first_visible_line += num_visible_lines; + else if (num_lines < num_visible_lines) + m_first_visible_line = 0; + else + m_first_visible_line = num_lines - num_visible_lines; + m_selected_line = m_first_visible_line; + } + return eKeyHandled; + + case KEY_UP: + if (m_selected_line > 0) { + m_selected_line--; + if (static_cast<size_t>(m_first_visible_line) > m_selected_line) + m_first_visible_line = m_selected_line; + } + return eKeyHandled; + + case KEY_DOWN: + if (m_selected_line + 1 < num_lines) { + m_selected_line++; + if (m_first_visible_line + num_visible_lines < m_selected_line) + m_first_visible_line++; + } + return eKeyHandled; + + case KEY_LEFT: + if (m_first_visible_column > 0) + --m_first_visible_column; + return eKeyHandled; + + case KEY_RIGHT: + ++m_first_visible_column; + return eKeyHandled; + + case '\r': + case '\n': + case KEY_ENTER: + // Set a breakpoint and run to the line using a one shot breakpoint + if (GetNumSourceLines() > 0) { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope() && exe_ctx.GetProcessRef().IsAlive()) { + BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( + nullptr, // Don't limit the breakpoint to certain modules + m_file_sp->GetFileSpec(), // Source file + m_selected_line + + 1, // Source line number (m_selected_line is zero based) + 0, // Unspecified column. + 0, // No offset + eLazyBoolCalculate, // Check inlines using global setting + eLazyBoolCalculate, // Skip prologue using global setting, + false, // internal + false, // request_hardware + eLazyBoolCalculate); // move_to_nearest_code + // Make breakpoint one shot + bp_sp->GetOptions().SetOneShot(true); + exe_ctx.GetProcessRef().Resume(); + } + } else if (m_selected_line < GetNumDisassemblyLines()) { + const Instruction *inst = m_disassembly_sp->GetInstructionList() + .GetInstructionAtIndex(m_selected_line) + .get(); + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasTargetScope()) { + Address addr = inst->GetAddress(); + BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( + addr, // lldb_private::Address + false, // internal + false); // request_hardware + // Make breakpoint one shot + bp_sp->GetOptions().SetOneShot(true); + exe_ctx.GetProcessRef().Resume(); + } + } + return eKeyHandled; + + case 'b': // 'b' == toggle breakpoint on currently selected line + ToggleBreakpointOnSelectedLine(); + return eKeyHandled; + + case 'D': // 'D' == detach and keep stopped + { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) + exe_ctx.GetProcessRef().Detach(true); + } + return eKeyHandled; + + case 'c': + // 'c' == continue + { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasProcessScope()) + exe_ctx.GetProcessRef().Resume(); + } + return eKeyHandled; + + case 'f': + // 'f' == step out (finish) + { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope() && + StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { + Thread *thread = exe_ctx.GetThreadPtr(); + uint32_t frame_idx = + thread->GetSelectedFrameIndex(SelectMostRelevantFrame); + exe_ctx.GetThreadRef().StepOut(frame_idx); + } + } + return eKeyHandled; + + case 'n': // 'n' == step over + case 'N': // 'N' == step over instruction + { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope() && + StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { + bool source_step = (c == 'n'); + exe_ctx.GetThreadRef().StepOver(source_step); + } + } + return eKeyHandled; + + case 's': // 's' == step into + case 'S': // 'S' == step into instruction + { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope() && + StateIsStoppedState(exe_ctx.GetProcessRef().GetState(), true)) { + bool source_step = (c == 's'); + exe_ctx.GetThreadRef().StepIn(source_step); + } + } + return eKeyHandled; + + case 'u': // 'u' == frame up + case 'd': // 'd' == frame down + { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (exe_ctx.HasThreadScope()) { + Thread *thread = exe_ctx.GetThreadPtr(); + uint32_t frame_idx = + thread->GetSelectedFrameIndex(SelectMostRelevantFrame); + if (frame_idx == UINT32_MAX) + frame_idx = 0; + if (c == 'u' && frame_idx + 1 < thread->GetStackFrameCount()) + ++frame_idx; + else if (c == 'd' && frame_idx > 0) + --frame_idx; + if (thread->SetSelectedFrameByIndex(frame_idx, true)) + exe_ctx.SetFrameSP(thread->GetSelectedFrame(SelectMostRelevantFrame)); + } + } + return eKeyHandled; + + case 'h': + window.CreateHelpSubwindow(); + return eKeyHandled; + + default: + break; + } + return eKeyNotHandled; + } + + void ToggleBreakpointOnSelectedLine() { + ExecutionContext exe_ctx = + m_debugger.GetCommandInterpreter().GetExecutionContext(); + if (!exe_ctx.HasTargetScope()) + return; + if (GetNumSourceLines() > 0) { + // Source file breakpoint. + BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList(); + const size_t num_bps = bp_list.GetSize(); + for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { + BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); + const size_t num_bps_locs = bp_sp->GetNumLocations(); + for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { + BreakpointLocationSP bp_loc_sp = + bp_sp->GetLocationAtIndex(bp_loc_idx); + LineEntry bp_loc_line_entry; + if (bp_loc_sp->GetAddress().CalculateSymbolContextLineEntry( + bp_loc_line_entry)) { + if (m_file_sp->GetFileSpec() == bp_loc_line_entry.GetFile() && + m_selected_line + 1 == bp_loc_line_entry.line) { + bool removed = + exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID()); + assert(removed); + UNUSED_IF_ASSERT_DISABLED(removed); + return; // Existing breakpoint removed. + } + } + } + } + // No breakpoint found on the location, add it. + BreakpointSP bp_sp = exe_ctx.GetTargetRef().CreateBreakpoint( + nullptr, // Don't limit the breakpoint to certain modules + m_file_sp->GetFileSpec(), // Source file + m_selected_line + + 1, // Source line number (m_selected_line is zero based) + 0, // No column specified. + 0, // No offset + eLazyBoolCalculate, // Check inlines using global setting + eLazyBoolCalculate, // Skip prologue using global setting, + false, // internal + false, // request_hardware + eLazyBoolCalculate); // move_to_nearest_code + } else { + // Disassembly breakpoint. + assert(GetNumDisassemblyLines() > 0); + assert(m_selected_line < GetNumDisassemblyLines()); + const Instruction *inst = m_disassembly_sp->GetInstructionList() + .GetInstructionAtIndex(m_selected_line) + .get(); + Address addr = inst->GetAddress(); + // Try to find it. + BreakpointList &bp_list = exe_ctx.GetTargetRef().GetBreakpointList(); + const size_t num_bps = bp_list.GetSize(); + for (size_t bp_idx = 0; bp_idx < num_bps; ++bp_idx) { + BreakpointSP bp_sp = bp_list.GetBreakpointAtIndex(bp_idx); + const size_t num_bps_locs = bp_sp->GetNumLocations(); + for (size_t bp_loc_idx = 0; bp_loc_idx < num_bps_locs; ++bp_loc_idx) { + BreakpointLocationSP bp_loc_sp = + bp_sp->GetLocationAtIndex(bp_loc_idx); + LineEntry bp_loc_line_entry; + const lldb::addr_t file_addr = + bp_loc_sp->GetAddress().GetFileAddress(); + if (file_addr == addr.GetFileAddress()) { + bool removed = + exe_ctx.GetTargetRef().RemoveBreakpointByID(bp_sp->GetID()); + assert(removed); + UNUSED_IF_ASSERT_DISABLED(removed); + return; // Existing breakpoint removed. + } + } + } + // No breakpoint found on the address, add it. + BreakpointSP bp_sp = + exe_ctx.GetTargetRef().CreateBreakpoint(addr, // lldb_private::Address + false, // internal + false); // request_hardware + } + } + +protected: + typedef std::set<uint32_t> BreakpointLines; + typedef std::set<lldb::addr_t> BreakpointAddrs; + + Debugger &m_debugger; + SymbolContext m_sc; + SourceManager::FileSP m_file_sp; + SymbolContextScope *m_disassembly_scope = nullptr; + lldb::DisassemblerSP m_disassembly_sp; + AddressRange m_disassembly_range; + StreamString m_title; + lldb::user_id_t m_tid = LLDB_INVALID_THREAD_ID; + int m_line_width = 4; + uint32_t m_selected_line = 0; // The selected line + uint32_t m_pc_line = 0; // The line with the PC + uint32_t m_stop_id = 0; + uint32_t m_frame_idx = UINT32_MAX; + int m_first_visible_line = 0; + int m_first_visible_column = 0; + int m_min_x = 0; + int m_min_y = 0; + int m_max_x = 0; + int m_max_y = 0; +}; + +DisplayOptions ValueObjectListDelegate::g_options = {true}; + +IOHandlerCursesGUI::IOHandlerCursesGUI(Debugger &debugger) + : IOHandler(debugger, IOHandler::Type::Curses) {} + +void IOHandlerCursesGUI::Activate() { + IOHandler::Activate(); + if (!m_app_ap) { + m_app_ap = std::make_unique<Application>(GetInputFILE(), GetOutputFILE()); + + // This is both a window and a menu delegate + std::shared_ptr<ApplicationDelegate> app_delegate_sp( + new ApplicationDelegate(*m_app_ap, m_debugger)); + + MenuDelegateSP app_menu_delegate_sp = + std::static_pointer_cast<MenuDelegate>(app_delegate_sp); + MenuSP lldb_menu_sp( + new Menu("LLDB", "F1", KEY_F(1), ApplicationDelegate::eMenuID_LLDB)); + MenuSP exit_menuitem_sp( + new Menu("Exit", nullptr, 'x', ApplicationDelegate::eMenuID_LLDBExit)); + exit_menuitem_sp->SetCannedResult(MenuActionResult::Quit); + lldb_menu_sp->AddSubmenu(MenuSP(new Menu( + "About LLDB", nullptr, 'a', ApplicationDelegate::eMenuID_LLDBAbout))); + lldb_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); + lldb_menu_sp->AddSubmenu(exit_menuitem_sp); + + MenuSP target_menu_sp(new Menu("Target", "F2", KEY_F(2), + ApplicationDelegate::eMenuID_Target)); + target_menu_sp->AddSubmenu(MenuSP(new Menu( + "Create", nullptr, 'c', ApplicationDelegate::eMenuID_TargetCreate))); + target_menu_sp->AddSubmenu(MenuSP(new Menu( + "Delete", nullptr, 'd', ApplicationDelegate::eMenuID_TargetDelete))); + + MenuSP process_menu_sp(new Menu("Process", "F3", KEY_F(3), + ApplicationDelegate::eMenuID_Process)); + process_menu_sp->AddSubmenu(MenuSP(new Menu( + "Attach", nullptr, 'a', ApplicationDelegate::eMenuID_ProcessAttach))); + process_menu_sp->AddSubmenu( + MenuSP(new Menu("Detach and resume", nullptr, 'd', + ApplicationDelegate::eMenuID_ProcessDetachResume))); + process_menu_sp->AddSubmenu( + MenuSP(new Menu("Detach suspended", nullptr, 's', + ApplicationDelegate::eMenuID_ProcessDetachSuspended))); + process_menu_sp->AddSubmenu(MenuSP(new Menu( + "Launch", nullptr, 'l', ApplicationDelegate::eMenuID_ProcessLaunch))); + process_menu_sp->AddSubmenu(MenuSP(new Menu(Menu::Type::Separator))); + process_menu_sp->AddSubmenu( + MenuSP(new Menu("Continue", nullptr, 'c', + ApplicationDelegate::eMenuID_ProcessContinue))); + process_menu_sp->AddSubmenu(MenuSP(new Menu( + "Halt", nullptr, 'h', ApplicationDelegate::eMenuID_ProcessHalt))); + process_menu_sp->AddSubmenu(MenuSP(new Menu( + "Kill", nullptr, 'k', ApplicationDelegate::eMenuID_ProcessKill))); + + MenuSP thread_menu_sp(new Menu("Thread", "F4", KEY_F(4), + ApplicationDelegate::eMenuID_Thread)); + thread_menu_sp->AddSubmenu(MenuSP(new Menu( + "Step In", nullptr, 'i', ApplicationDelegate::eMenuID_ThreadStepIn))); + thread_menu_sp->AddSubmenu( + MenuSP(new Menu("Step Over", nullptr, 'v', + ApplicationDelegate::eMenuID_ThreadStepOver))); + thread_menu_sp->AddSubmenu(MenuSP(new Menu( + "Step Out", nullptr, 'o', ApplicationDelegate::eMenuID_ThreadStepOut))); + + MenuSP view_menu_sp( + new Menu("View", "F5", KEY_F(5), ApplicationDelegate::eMenuID_View)); + view_menu_sp->AddSubmenu( + MenuSP(new Menu("Backtrace", nullptr, 't', + ApplicationDelegate::eMenuID_ViewBacktrace))); + view_menu_sp->AddSubmenu( + MenuSP(new Menu("Registers", nullptr, 'r', + ApplicationDelegate::eMenuID_ViewRegisters))); + view_menu_sp->AddSubmenu(MenuSP(new Menu( + "Source", nullptr, 's', ApplicationDelegate::eMenuID_ViewSource))); + view_menu_sp->AddSubmenu( + MenuSP(new Menu("Variables", nullptr, 'v', + ApplicationDelegate::eMenuID_ViewVariables))); + view_menu_sp->AddSubmenu( + MenuSP(new Menu("Breakpoints", nullptr, 'b', + ApplicationDelegate::eMenuID_ViewBreakpoints))); + + MenuSP help_menu_sp( + new Menu("Help", "F6", KEY_F(6), ApplicationDelegate::eMenuID_Help)); + help_menu_sp->AddSubmenu(MenuSP(new Menu( + "GUI Help", nullptr, 'g', ApplicationDelegate::eMenuID_HelpGUIHelp))); + + m_app_ap->Initialize(); + WindowSP &main_window_sp = m_app_ap->GetMainWindow(); + + MenuSP menubar_sp(new Menu(Menu::Type::Bar)); + menubar_sp->AddSubmenu(lldb_menu_sp); + menubar_sp->AddSubmenu(target_menu_sp); + menubar_sp->AddSubmenu(process_menu_sp); + menubar_sp->AddSubmenu(thread_menu_sp); + menubar_sp->AddSubmenu(view_menu_sp); + menubar_sp->AddSubmenu(help_menu_sp); + menubar_sp->SetDelegate(app_menu_delegate_sp); + + Rect content_bounds = main_window_sp->GetFrame(); + Rect menubar_bounds = content_bounds.MakeMenuBar(); + Rect status_bounds = content_bounds.MakeStatusBar(); + Rect source_bounds; + Rect variables_bounds; + Rect threads_bounds; + Rect source_variables_bounds; + content_bounds.VerticalSplitPercentage(0.80, source_variables_bounds, + threads_bounds); + source_variables_bounds.HorizontalSplitPercentage(0.70, source_bounds, + variables_bounds); + + WindowSP menubar_window_sp = + main_window_sp->CreateSubWindow("Menubar", menubar_bounds, false); + // Let the menubar get keys if the active window doesn't handle the keys + // that are typed so it can respond to menubar key presses. + menubar_window_sp->SetCanBeActive( + false); // Don't let the menubar become the active window + menubar_window_sp->SetDelegate(menubar_sp); + + WindowSP source_window_sp( + main_window_sp->CreateSubWindow("Source", source_bounds, true)); + WindowSP variables_window_sp( + main_window_sp->CreateSubWindow("Variables", variables_bounds, false)); + WindowSP threads_window_sp( + main_window_sp->CreateSubWindow("Threads", threads_bounds, false)); + WindowSP status_window_sp( + main_window_sp->CreateSubWindow("Status", status_bounds, false)); + status_window_sp->SetCanBeActive( + false); // Don't let the status bar become the active window + main_window_sp->SetDelegate( + std::static_pointer_cast<WindowDelegate>(app_delegate_sp)); + source_window_sp->SetDelegate( + WindowDelegateSP(new SourceFileWindowDelegate(m_debugger))); + variables_window_sp->SetDelegate( + WindowDelegateSP(new FrameVariablesWindowDelegate(m_debugger))); + TreeDelegateSP thread_delegate_sp(new ThreadsTreeDelegate(m_debugger)); + threads_window_sp->SetDelegate(WindowDelegateSP( + new TreeWindowDelegate(m_debugger, thread_delegate_sp))); + status_window_sp->SetDelegate( + WindowDelegateSP(new StatusBarWindowDelegate(m_debugger))); + + // All colors with black background. + init_pair(1, COLOR_BLACK, COLOR_BLACK); + init_pair(2, COLOR_RED, COLOR_BLACK); + init_pair(3, COLOR_GREEN, COLOR_BLACK); + init_pair(4, COLOR_YELLOW, COLOR_BLACK); + init_pair(5, COLOR_BLUE, COLOR_BLACK); + init_pair(6, COLOR_MAGENTA, COLOR_BLACK); + init_pair(7, COLOR_CYAN, COLOR_BLACK); + init_pair(8, COLOR_WHITE, COLOR_BLACK); + // All colors with blue background. + init_pair(9, COLOR_BLACK, COLOR_BLUE); + init_pair(10, COLOR_RED, COLOR_BLUE); + init_pair(11, COLOR_GREEN, COLOR_BLUE); + init_pair(12, COLOR_YELLOW, COLOR_BLUE); + init_pair(13, COLOR_BLUE, COLOR_BLUE); + init_pair(14, COLOR_MAGENTA, COLOR_BLUE); + init_pair(15, COLOR_CYAN, COLOR_BLUE); + init_pair(16, COLOR_WHITE, COLOR_BLUE); + // These must match the order in the color indexes enum. + init_pair(17, COLOR_BLACK, COLOR_WHITE); + init_pair(18, COLOR_MAGENTA, COLOR_WHITE); + static_assert(LastColorPairIndex == 18, "Color indexes do not match."); + + define_key("\033[Z", KEY_SHIFT_TAB); + define_key("\033\015", KEY_ALT_ENTER); + } +} + +void IOHandlerCursesGUI::Deactivate() { m_app_ap->Terminate(); } + +void IOHandlerCursesGUI::Run() { + m_app_ap->Run(m_debugger); + SetIsDone(true); +} + +IOHandlerCursesGUI::~IOHandlerCursesGUI() = default; + +void IOHandlerCursesGUI::Cancel() {} + +bool IOHandlerCursesGUI::Interrupt() { + return m_debugger.GetCommandInterpreter().IOHandlerInterrupt(*this); +} + +void IOHandlerCursesGUI::GotEOF() {} + +void IOHandlerCursesGUI::TerminalSizeChanged() { + m_app_ap->TerminalSizeChanged(); +} + +#endif // LLDB_ENABLE_CURSES diff --git a/contrib/llvm-project/lldb/source/Core/Mangled.cpp b/contrib/llvm-project/lldb/source/Core/Mangled.cpp new file mode 100644 index 000000000000..387c4fac6b0f --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/Mangled.cpp @@ -0,0 +1,523 @@ +//===-- Mangled.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/Core/Mangled.h" + +#include "lldb/Core/DataFileCache.h" +#include "lldb/Core/RichManglingContext.h" +#include "lldb/Target/Language.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/DataEncoder.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/RegularExpression.h" +#include "lldb/Utility/Stream.h" +#include "lldb/lldb-enumerations.h" + +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Demangle/Demangle.h" +#include "llvm/Support/Compiler.h" + +#include <mutex> +#include <string> +#include <string_view> +#include <utility> + +#include <cstdlib> +#include <cstring> +using namespace lldb_private; + +static inline bool cstring_is_mangled(llvm::StringRef s) { + return Mangled::GetManglingScheme(s) != Mangled::eManglingSchemeNone; +} + +#pragma mark Mangled + +Mangled::ManglingScheme Mangled::GetManglingScheme(llvm::StringRef const name) { + if (name.empty()) + return Mangled::eManglingSchemeNone; + + if (name.starts_with("?")) + return Mangled::eManglingSchemeMSVC; + + if (name.starts_with("_R")) + return Mangled::eManglingSchemeRustV0; + + if (name.starts_with("_D")) { + // A dlang mangled name begins with `_D`, followed by a numeric length. One + // known exception is the symbol `_Dmain`. + // See `SymbolName` and `LName` in + // https://dlang.org/spec/abi.html#name_mangling + llvm::StringRef buf = name.drop_front(2); + if (!buf.empty() && (llvm::isDigit(buf.front()) || name == "_Dmain")) + return Mangled::eManglingSchemeD; + } + + if (name.starts_with("_Z")) + return Mangled::eManglingSchemeItanium; + + // ___Z is a clang extension of block invocations + if (name.starts_with("___Z")) + return Mangled::eManglingSchemeItanium; + + // Swift's older style of mangling used "_T" as a mangling prefix. This can + // lead to false positives with other symbols that just so happen to start + // with "_T". To minimize the chance of that happening, we only return true + // for select old-style swift mangled names. The known cases are ObjC classes + // and protocols. Classes are either prefixed with "_TtC" or "_TtGC". + // Protocols are prefixed with "_TtP". + if (name.starts_with("_TtC") || name.starts_with("_TtGC") || + name.starts_with("_TtP")) + return Mangled::eManglingSchemeSwift; + + // Swift 4.2 used "$S" and "_$S". + // Swift 5 and onward uses "$s" and "_$s". + // Swift also uses "@__swiftmacro_" as a prefix for mangling filenames. + if (name.starts_with("$S") || name.starts_with("_$S") || + name.starts_with("$s") || name.starts_with("_$s") || + name.starts_with("@__swiftmacro_")) + return Mangled::eManglingSchemeSwift; + + return Mangled::eManglingSchemeNone; +} + +Mangled::Mangled(ConstString s) : m_mangled(), m_demangled() { + if (s) + SetValue(s); +} + +Mangled::Mangled(llvm::StringRef name) { + if (!name.empty()) + SetValue(ConstString(name)); +} + +// Convert to bool operator. This allows code to check any Mangled objects +// to see if they contain anything valid using code such as: +// +// Mangled mangled(...); +// if (mangled) +// { ... +Mangled::operator bool() const { return m_mangled || m_demangled; } + +// Clear the mangled and demangled values. +void Mangled::Clear() { + m_mangled.Clear(); + m_demangled.Clear(); +} + +// Compare the string values. +int Mangled::Compare(const Mangled &a, const Mangled &b) { + return ConstString::Compare(a.GetName(ePreferMangled), + b.GetName(ePreferMangled)); +} + +void Mangled::SetValue(ConstString name) { + if (name) { + if (cstring_is_mangled(name.GetStringRef())) { + m_demangled.Clear(); + m_mangled = name; + } else { + m_demangled = name; + m_mangled.Clear(); + } + } else { + m_demangled.Clear(); + m_mangled.Clear(); + } +} + +// Local helpers for different demangling implementations. +static char *GetMSVCDemangledStr(llvm::StringRef M) { + char *demangled_cstr = llvm::microsoftDemangle( + M, nullptr, nullptr, + llvm::MSDemangleFlags( + llvm::MSDF_NoAccessSpecifier | llvm::MSDF_NoCallingConvention | + llvm::MSDF_NoMemberType | llvm::MSDF_NoVariableType)); + + if (Log *log = GetLog(LLDBLog::Demangle)) { + if (demangled_cstr && demangled_cstr[0]) + LLDB_LOGF(log, "demangled msvc: %s -> \"%s\"", M.data(), demangled_cstr); + else + LLDB_LOGF(log, "demangled msvc: %s -> error", M.data()); + } + + return demangled_cstr; +} + +static char *GetItaniumDemangledStr(const char *M) { + char *demangled_cstr = nullptr; + + llvm::ItaniumPartialDemangler ipd; + bool err = ipd.partialDemangle(M); + if (!err) { + // Default buffer and size (will realloc in case it's too small). + size_t demangled_size = 80; + demangled_cstr = static_cast<char *>(std::malloc(demangled_size)); + demangled_cstr = ipd.finishDemangle(demangled_cstr, &demangled_size); + + assert(demangled_cstr && + "finishDemangle must always succeed if partialDemangle did"); + assert(demangled_cstr[demangled_size - 1] == '\0' && + "Expected demangled_size to return length including trailing null"); + } + + if (Log *log = GetLog(LLDBLog::Demangle)) { + if (demangled_cstr) + LLDB_LOGF(log, "demangled itanium: %s -> \"%s\"", M, demangled_cstr); + else + LLDB_LOGF(log, "demangled itanium: %s -> error: failed to demangle", M); + } + + return demangled_cstr; +} + +static char *GetRustV0DemangledStr(llvm::StringRef M) { + char *demangled_cstr = llvm::rustDemangle(M); + + if (Log *log = GetLog(LLDBLog::Demangle)) { + if (demangled_cstr && demangled_cstr[0]) + LLDB_LOG(log, "demangled rustv0: {0} -> \"{1}\"", M, demangled_cstr); + else + LLDB_LOG(log, "demangled rustv0: {0} -> error: failed to demangle", + static_cast<std::string_view>(M)); + } + + return demangled_cstr; +} + +static char *GetDLangDemangledStr(llvm::StringRef M) { + char *demangled_cstr = llvm::dlangDemangle(M); + + if (Log *log = GetLog(LLDBLog::Demangle)) { + if (demangled_cstr && demangled_cstr[0]) + LLDB_LOG(log, "demangled dlang: {0} -> \"{1}\"", M, demangled_cstr); + else + LLDB_LOG(log, "demangled dlang: {0} -> error: failed to demangle", + static_cast<std::string_view>(M)); + } + + return demangled_cstr; +} + +// Explicit demangling for scheduled requests during batch processing. This +// makes use of ItaniumPartialDemangler's rich demangle info +bool Mangled::GetRichManglingInfo(RichManglingContext &context, + SkipMangledNameFn *skip_mangled_name) { + // Others are not meant to arrive here. ObjC names or C's main() for example + // have their names stored in m_demangled, while m_mangled is empty. + assert(m_mangled); + + // Check whether or not we are interested in this name at all. + ManglingScheme scheme = GetManglingScheme(m_mangled.GetStringRef()); + if (skip_mangled_name && skip_mangled_name(m_mangled.GetStringRef(), scheme)) + return false; + + switch (scheme) { + case eManglingSchemeNone: + // The current mangled_name_filter would allow llvm_unreachable here. + return false; + + case eManglingSchemeItanium: + // We want the rich mangling info here, so we don't care whether or not + // there is a demangled string in the pool already. + return context.FromItaniumName(m_mangled); + + case eManglingSchemeMSVC: { + // We have no rich mangling for MSVC-mangled names yet, so first try to + // demangle it if necessary. + if (!m_demangled && !m_mangled.GetMangledCounterpart(m_demangled)) { + if (char *d = GetMSVCDemangledStr(m_mangled)) { + // Without the rich mangling info we have to demangle the full name. + // Copy it to string pool and connect the counterparts to accelerate + // later access in GetDemangledName(). + m_demangled.SetStringWithMangledCounterpart(llvm::StringRef(d), + m_mangled); + ::free(d); + } else { + m_demangled.SetCString(""); + } + } + + if (m_demangled.IsEmpty()) { + // Cannot demangle it, so don't try parsing. + return false; + } else { + // Demangled successfully, we can try and parse it with + // CPlusPlusLanguage::MethodName. + return context.FromCxxMethodName(m_demangled); + } + } + + case eManglingSchemeRustV0: + case eManglingSchemeD: + case eManglingSchemeSwift: + // Rich demangling scheme is not supported + return false; + } + llvm_unreachable("Fully covered switch above!"); +} + +// Generate the demangled name on demand using this accessor. Code in this +// class will need to use this accessor if it wishes to decode the demangled +// name. The result is cached and will be kept until a new string value is +// supplied to this object, or until the end of the object's lifetime. +ConstString Mangled::GetDemangledName() const { + // Check to make sure we have a valid mangled name and that we haven't + // already decoded our mangled name. + if (m_mangled && m_demangled.IsNull()) { + // Don't bother running anything that isn't mangled + const char *mangled_name = m_mangled.GetCString(); + ManglingScheme mangling_scheme = + GetManglingScheme(m_mangled.GetStringRef()); + if (mangling_scheme != eManglingSchemeNone && + !m_mangled.GetMangledCounterpart(m_demangled)) { + // We didn't already mangle this name, demangle it and if all goes well + // add it to our map. + char *demangled_name = nullptr; + switch (mangling_scheme) { + case eManglingSchemeMSVC: + demangled_name = GetMSVCDemangledStr(mangled_name); + break; + case eManglingSchemeItanium: { + demangled_name = GetItaniumDemangledStr(mangled_name); + break; + } + case eManglingSchemeRustV0: + demangled_name = GetRustV0DemangledStr(m_mangled); + break; + case eManglingSchemeD: + demangled_name = GetDLangDemangledStr(m_mangled); + break; + case eManglingSchemeSwift: + // Demangling a swift name requires the swift compiler. This is + // explicitly unsupported on llvm.org. + break; + case eManglingSchemeNone: + llvm_unreachable("eManglingSchemeNone was handled already"); + } + if (demangled_name) { + m_demangled.SetStringWithMangledCounterpart( + llvm::StringRef(demangled_name), m_mangled); + free(demangled_name); + } + } + if (m_demangled.IsNull()) { + // Set the demangled string to the empty string to indicate we tried to + // parse it once and failed. + m_demangled.SetCString(""); + } + } + + return m_demangled; +} + +ConstString Mangled::GetDisplayDemangledName() const { + if (Language *lang = Language::FindPlugin(GuessLanguage())) + return lang->GetDisplayDemangledName(*this); + return GetDemangledName(); +} + +bool Mangled::NameMatches(const RegularExpression ®ex) const { + if (m_mangled && regex.Execute(m_mangled.GetStringRef())) + return true; + + ConstString demangled = GetDemangledName(); + return demangled && regex.Execute(demangled.GetStringRef()); +} + +// Get the demangled name if there is one, else return the mangled name. +ConstString Mangled::GetName(Mangled::NamePreference preference) const { + if (preference == ePreferMangled && m_mangled) + return m_mangled; + + // Call the accessor to make sure we get a demangled name in case it hasn't + // been demangled yet... + ConstString demangled = GetDemangledName(); + + if (preference == ePreferDemangledWithoutArguments) { + if (Language *lang = Language::FindPlugin(GuessLanguage())) { + return lang->GetDemangledFunctionNameWithoutArguments(*this); + } + } + if (preference == ePreferDemangled) { + if (demangled) + return demangled; + return m_mangled; + } + return demangled; +} + +// Dump a Mangled object to stream "s". We don't force our demangled name to be +// computed currently (we don't use the accessor). +void Mangled::Dump(Stream *s) const { + if (m_mangled) { + *s << ", mangled = " << m_mangled; + } + if (m_demangled) { + const char *demangled = m_demangled.AsCString(); + s->Printf(", demangled = %s", demangled[0] ? demangled : "<error>"); + } +} + +// Dumps a debug version of this string with extra object and state information +// to stream "s". +void Mangled::DumpDebug(Stream *s) const { + s->Printf("%*p: Mangled mangled = ", static_cast<int>(sizeof(void *) * 2), + static_cast<const void *>(this)); + m_mangled.DumpDebug(s); + s->Printf(", demangled = "); + m_demangled.DumpDebug(s); +} + +// Return the size in byte that this object takes in memory. The size includes +// the size of the objects it owns, and not the strings that it references +// because they are shared strings. +size_t Mangled::MemorySize() const { + return m_mangled.MemorySize() + m_demangled.MemorySize(); +} + +// We "guess" the language because we can't determine a symbol's language from +// it's name. For example, a Pascal symbol can be mangled using the C++ +// Itanium scheme, and defined in a compilation unit within the same module as +// other C++ units. In addition, different targets could have different ways +// of mangling names from a given language, likewise the compilation units +// within those targets. +lldb::LanguageType Mangled::GuessLanguage() const { + lldb::LanguageType result = lldb::eLanguageTypeUnknown; + // Ask each language plugin to check if the mangled name belongs to it. + Language::ForEach([this, &result](Language *l) { + if (l->SymbolNameFitsToLanguage(*this)) { + result = l->GetLanguageType(); + return false; + } + return true; + }); + return result; +} + +// Dump OBJ to the supplied stream S. +Stream &operator<<(Stream &s, const Mangled &obj) { + if (obj.GetMangledName()) + s << "mangled = '" << obj.GetMangledName() << "'"; + + ConstString demangled = obj.GetDemangledName(); + if (demangled) + s << ", demangled = '" << demangled << '\''; + else + s << ", demangled = <error>"; + return s; +} + +// When encoding Mangled objects we can get away with encoding as little +// information as is required. The enumeration below helps us to efficiently +// encode Mangled objects. +enum MangledEncoding { + /// If the Mangled object has neither a mangled name or demangled name we can + /// encode the object with one zero byte using the Empty enumeration. + Empty = 0u, + /// If the Mangled object has only a demangled name and no mangled named, we + /// can encode only the demangled name. + DemangledOnly = 1u, + /// If the mangle name can calculate the demangled name (it is the + /// mangled/demangled counterpart), then we only need to encode the mangled + /// name as the demangled name can be recomputed. + MangledOnly = 2u, + /// If we have a Mangled object with two different names that are not related + /// then we need to save both strings. This can happen if we have a name that + /// isn't a true mangled name, but we want to be able to lookup a symbol by + /// name and type in the symbol table. We do this for Objective C symbols like + /// "OBJC_CLASS_$_NSValue" where the mangled named will be set to + /// "OBJC_CLASS_$_NSValue" and the demangled name will be manually set to + /// "NSValue". If we tried to demangled the name "OBJC_CLASS_$_NSValue" it + /// would fail, but in these cases we want these unrelated names to be + /// preserved. + MangledAndDemangled = 3u +}; + +bool Mangled::Decode(const DataExtractor &data, lldb::offset_t *offset_ptr, + const StringTableReader &strtab) { + m_mangled.Clear(); + m_demangled.Clear(); + MangledEncoding encoding = (MangledEncoding)data.GetU8(offset_ptr); + switch (encoding) { + case Empty: + return true; + + case DemangledOnly: + m_demangled.SetString(strtab.Get(data.GetU32(offset_ptr))); + return true; + + case MangledOnly: + m_mangled.SetString(strtab.Get(data.GetU32(offset_ptr))); + return true; + + case MangledAndDemangled: + m_mangled.SetString(strtab.Get(data.GetU32(offset_ptr))); + m_demangled.SetString(strtab.Get(data.GetU32(offset_ptr))); + return true; + } + return false; +} +/// The encoding format for the Mangled object is as follows: +/// +/// uint8_t encoding; +/// char str1[]; (only if DemangledOnly, MangledOnly) +/// char str2[]; (only if MangledAndDemangled) +/// +/// The strings are stored as NULL terminated UTF8 strings and str1 and str2 +/// are only saved if we need them based on the encoding. +/// +/// Some mangled names have a mangled name that can be demangled by the built +/// in demanglers. These kinds of mangled objects know when the mangled and +/// demangled names are the counterparts for each other. This is done because +/// demangling is very expensive and avoiding demangling the same name twice +/// saves us a lot of compute time. For these kinds of names we only need to +/// save the mangled name and have the encoding set to "MangledOnly". +/// +/// If a mangled obejct has only a demangled name, then we save only that string +/// and have the encoding set to "DemangledOnly". +/// +/// Some mangled objects have both mangled and demangled names, but the +/// demangled name can not be computed from the mangled name. This is often used +/// for runtime named, like Objective C runtime V2 and V3 names. Both these +/// names must be saved and the encoding is set to "MangledAndDemangled". +/// +/// For a Mangled object with no names, we only need to set the encoding to +/// "Empty" and not store any string values. +void Mangled::Encode(DataEncoder &file, ConstStringTable &strtab) const { + MangledEncoding encoding = Empty; + if (m_mangled) { + encoding = MangledOnly; + if (m_demangled) { + // We have both mangled and demangled names. If the demangled name is the + // counterpart of the mangled name, then we only need to save the mangled + // named. If they are different, we need to save both. + ConstString s; + if (!(m_mangled.GetMangledCounterpart(s) && s == m_demangled)) + encoding = MangledAndDemangled; + } + } else if (m_demangled) { + encoding = DemangledOnly; + } + file.AppendU8(encoding); + switch (encoding) { + case Empty: + break; + case DemangledOnly: + file.AppendU32(strtab.Add(m_demangled)); + break; + case MangledOnly: + file.AppendU32(strtab.Add(m_mangled)); + break; + case MangledAndDemangled: + file.AppendU32(strtab.Add(m_mangled)); + file.AppendU32(strtab.Add(m_demangled)); + break; + } +} diff --git a/contrib/llvm-project/lldb/source/Core/Module.cpp b/contrib/llvm-project/lldb/source/Core/Module.cpp new file mode 100644 index 000000000000..9c105b3f0e57 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/Module.cpp @@ -0,0 +1,1647 @@ +//===-- Module.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/Core/Module.h" + +#include "lldb/Core/AddressRange.h" +#include "lldb/Core/AddressResolverFileLine.h" +#include "lldb/Core/DataFileCache.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Mangled.h" +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Core/SearchFilter.h" +#include "lldb/Core/Section.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/Host.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/ScriptInterpreter.h" +#include "lldb/Symbol/CompileUnit.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/Symbol/SymbolLocator.h" +#include "lldb/Symbol/SymbolVendor.h" +#include "lldb/Symbol/Symtab.h" +#include "lldb/Symbol/Type.h" +#include "lldb/Symbol/TypeList.h" +#include "lldb/Symbol/TypeMap.h" +#include "lldb/Symbol/TypeSystem.h" +#include "lldb/Target/Language.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/FileSpecList.h" +#include "lldb/Utility/LLDBAssert.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/RegularExpression.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/Stream.h" +#include "lldb/Utility/StreamString.h" +#include "lldb/Utility/Timer.h" + +#if defined(_WIN32) +#include "lldb/Host/windows/PosixApi.h" +#endif + +#include "Plugins/Language/CPlusPlus/CPlusPlusLanguage.h" +#include "Plugins/Language/ObjC/ObjCLanguage.h" + +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/DJB.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/raw_ostream.h" + +#include <cassert> +#include <cinttypes> +#include <cstdarg> +#include <cstdint> +#include <cstring> +#include <map> +#include <optional> +#include <type_traits> +#include <utility> + +namespace lldb_private { +class CompilerDeclContext; +} +namespace lldb_private { +class VariableList; +} + +using namespace lldb; +using namespace lldb_private; + +// Shared pointers to modules track module lifetimes in targets and in the +// global module, but this collection will track all module objects that are +// still alive +typedef std::vector<Module *> ModuleCollection; + +static ModuleCollection &GetModuleCollection() { + // This module collection needs to live past any module, so we could either + // make it a shared pointer in each module or just leak is. Since it is only + // an empty vector by the time all the modules have gone away, we just leak + // it for now. If we decide this is a big problem we can introduce a + // Finalize method that will tear everything down in a predictable order. + + static ModuleCollection *g_module_collection = nullptr; + if (g_module_collection == nullptr) + g_module_collection = new ModuleCollection(); + + return *g_module_collection; +} + +std::recursive_mutex &Module::GetAllocationModuleCollectionMutex() { + // NOTE: The mutex below must be leaked since the global module list in + // the ModuleList class will get torn at some point, and we can't know if it + // will tear itself down before the "g_module_collection_mutex" below will. + // So we leak a Mutex object below to safeguard against that + + static std::recursive_mutex *g_module_collection_mutex = nullptr; + if (g_module_collection_mutex == nullptr) + g_module_collection_mutex = new std::recursive_mutex; // NOTE: known leak + return *g_module_collection_mutex; +} + +size_t Module::GetNumberAllocatedModules() { + std::lock_guard<std::recursive_mutex> guard( + GetAllocationModuleCollectionMutex()); + return GetModuleCollection().size(); +} + +Module *Module::GetAllocatedModuleAtIndex(size_t idx) { + std::lock_guard<std::recursive_mutex> guard( + GetAllocationModuleCollectionMutex()); + ModuleCollection &modules = GetModuleCollection(); + if (idx < modules.size()) + return modules[idx]; + return nullptr; +} + +Module::Module(const ModuleSpec &module_spec) + : m_file_has_changed(false), m_first_file_changed_log(false) { + // Scope for locker below... + { + std::lock_guard<std::recursive_mutex> guard( + GetAllocationModuleCollectionMutex()); + GetModuleCollection().push_back(this); + } + + Log *log(GetLog(LLDBLog::Object | LLDBLog::Modules)); + if (log != nullptr) + LLDB_LOGF(log, "%p Module::Module((%s) '%s%s%s%s')", + static_cast<void *>(this), + module_spec.GetArchitecture().GetArchitectureName(), + module_spec.GetFileSpec().GetPath().c_str(), + module_spec.GetObjectName().IsEmpty() ? "" : "(", + module_spec.GetObjectName().AsCString(""), + module_spec.GetObjectName().IsEmpty() ? "" : ")"); + + auto data_sp = module_spec.GetData(); + lldb::offset_t file_size = 0; + if (data_sp) + file_size = data_sp->GetByteSize(); + + // First extract all module specifications from the file using the local file + // path. If there are no specifications, then don't fill anything in + ModuleSpecList modules_specs; + if (ObjectFile::GetModuleSpecifications( + module_spec.GetFileSpec(), 0, file_size, modules_specs, data_sp) == 0) + return; + + // Now make sure that one of the module specifications matches what we just + // extract. We might have a module specification that specifies a file + // "/usr/lib/dyld" with UUID XXX, but we might have a local version of + // "/usr/lib/dyld" that has + // UUID YYY and we don't want those to match. If they don't match, just don't + // fill any ivars in so we don't accidentally grab the wrong file later since + // they don't match... + ModuleSpec matching_module_spec; + if (!modules_specs.FindMatchingModuleSpec(module_spec, + matching_module_spec)) { + if (log) { + LLDB_LOGF(log, "Found local object file but the specs didn't match"); + } + return; + } + + // Set m_data_sp if it was initially provided in the ModuleSpec. Note that + // we cannot use the data_sp variable here, because it will have been + // modified by GetModuleSpecifications(). + if (auto module_spec_data_sp = module_spec.GetData()) { + m_data_sp = module_spec_data_sp; + m_mod_time = {}; + } else { + if (module_spec.GetFileSpec()) + m_mod_time = + FileSystem::Instance().GetModificationTime(module_spec.GetFileSpec()); + else if (matching_module_spec.GetFileSpec()) + m_mod_time = FileSystem::Instance().GetModificationTime( + matching_module_spec.GetFileSpec()); + } + + // Copy the architecture from the actual spec if we got one back, else use + // the one that was specified + if (matching_module_spec.GetArchitecture().IsValid()) + m_arch = matching_module_spec.GetArchitecture(); + else if (module_spec.GetArchitecture().IsValid()) + m_arch = module_spec.GetArchitecture(); + + // Copy the file spec over and use the specified one (if there was one) so we + // don't use a path that might have gotten resolved a path in + // 'matching_module_spec' + if (module_spec.GetFileSpec()) + m_file = module_spec.GetFileSpec(); + else if (matching_module_spec.GetFileSpec()) + m_file = matching_module_spec.GetFileSpec(); + + // Copy the platform file spec over + if (module_spec.GetPlatformFileSpec()) + m_platform_file = module_spec.GetPlatformFileSpec(); + else if (matching_module_spec.GetPlatformFileSpec()) + m_platform_file = matching_module_spec.GetPlatformFileSpec(); + + // Copy the symbol file spec over + if (module_spec.GetSymbolFileSpec()) + m_symfile_spec = module_spec.GetSymbolFileSpec(); + else if (matching_module_spec.GetSymbolFileSpec()) + m_symfile_spec = matching_module_spec.GetSymbolFileSpec(); + + // Copy the object name over + if (matching_module_spec.GetObjectName()) + m_object_name = matching_module_spec.GetObjectName(); + else + m_object_name = module_spec.GetObjectName(); + + // Always trust the object offset (file offset) and object modification time + // (for mod time in a BSD static archive) of from the matching module + // specification + m_object_offset = matching_module_spec.GetObjectOffset(); + m_object_mod_time = matching_module_spec.GetObjectModificationTime(); +} + +Module::Module(const FileSpec &file_spec, const ArchSpec &arch, + ConstString object_name, lldb::offset_t object_offset, + const llvm::sys::TimePoint<> &object_mod_time) + : m_mod_time(FileSystem::Instance().GetModificationTime(file_spec)), + m_arch(arch), m_file(file_spec), m_object_name(object_name), + m_object_offset(object_offset), m_object_mod_time(object_mod_time), + m_file_has_changed(false), m_first_file_changed_log(false) { + // Scope for locker below... + { + std::lock_guard<std::recursive_mutex> guard( + GetAllocationModuleCollectionMutex()); + GetModuleCollection().push_back(this); + } + + Log *log(GetLog(LLDBLog::Object | LLDBLog::Modules)); + if (log != nullptr) + LLDB_LOGF(log, "%p Module::Module((%s) '%s%s%s%s')", + static_cast<void *>(this), m_arch.GetArchitectureName(), + m_file.GetPath().c_str(), m_object_name.IsEmpty() ? "" : "(", + m_object_name.AsCString(""), m_object_name.IsEmpty() ? "" : ")"); +} + +Module::Module() : m_file_has_changed(false), m_first_file_changed_log(false) { + std::lock_guard<std::recursive_mutex> guard( + GetAllocationModuleCollectionMutex()); + GetModuleCollection().push_back(this); +} + +Module::~Module() { + // Lock our module down while we tear everything down to make sure we don't + // get any access to the module while it is being destroyed + std::lock_guard<std::recursive_mutex> guard(m_mutex); + // Scope for locker below... + { + std::lock_guard<std::recursive_mutex> guard( + GetAllocationModuleCollectionMutex()); + ModuleCollection &modules = GetModuleCollection(); + ModuleCollection::iterator end = modules.end(); + ModuleCollection::iterator pos = std::find(modules.begin(), end, this); + assert(pos != end); + modules.erase(pos); + } + Log *log(GetLog(LLDBLog::Object | LLDBLog::Modules)); + if (log != nullptr) + LLDB_LOGF(log, "%p Module::~Module((%s) '%s%s%s%s')", + static_cast<void *>(this), m_arch.GetArchitectureName(), + m_file.GetPath().c_str(), m_object_name.IsEmpty() ? "" : "(", + m_object_name.AsCString(""), m_object_name.IsEmpty() ? "" : ")"); + // Release any auto pointers before we start tearing down our member + // variables since the object file and symbol files might need to make + // function calls back into this module object. The ordering is important + // here because symbol files can require the module object file. So we tear + // down the symbol file first, then the object file. + m_sections_up.reset(); + m_symfile_up.reset(); + m_objfile_sp.reset(); +} + +ObjectFile *Module::GetMemoryObjectFile(const lldb::ProcessSP &process_sp, + lldb::addr_t header_addr, Status &error, + size_t size_to_read) { + if (m_objfile_sp) { + error.SetErrorString("object file already exists"); + } else { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + if (process_sp) { + m_did_load_objfile = true; + std::shared_ptr<DataBufferHeap> data_sp = + std::make_shared<DataBufferHeap>(size_to_read, 0); + Status readmem_error; + const size_t bytes_read = + process_sp->ReadMemory(header_addr, data_sp->GetBytes(), + data_sp->GetByteSize(), readmem_error); + if (bytes_read < size_to_read) + data_sp->SetByteSize(bytes_read); + if (data_sp->GetByteSize() > 0) { + m_objfile_sp = ObjectFile::FindPlugin(shared_from_this(), process_sp, + header_addr, data_sp); + if (m_objfile_sp) { + StreamString s; + s.Printf("0x%16.16" PRIx64, header_addr); + m_object_name.SetString(s.GetString()); + + // Once we get the object file, update our module with the object + // file's architecture since it might differ in vendor/os if some + // parts were unknown. + m_arch = m_objfile_sp->GetArchitecture(); + + // Augment the arch with the target's information in case + // we are unable to extract the os/environment from memory. + m_arch.MergeFrom(process_sp->GetTarget().GetArchitecture()); + } else { + error.SetErrorString("unable to find suitable object file plug-in"); + } + } else { + error.SetErrorStringWithFormat("unable to read header from memory: %s", + readmem_error.AsCString()); + } + } else { + error.SetErrorString("invalid process"); + } + } + return m_objfile_sp.get(); +} + +const lldb_private::UUID &Module::GetUUID() { + if (!m_did_set_uuid.load()) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + if (!m_did_set_uuid.load()) { + ObjectFile *obj_file = GetObjectFile(); + + if (obj_file != nullptr) { + m_uuid = obj_file->GetUUID(); + m_did_set_uuid = true; + } + } + } + return m_uuid; +} + +void Module::SetUUID(const lldb_private::UUID &uuid) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + if (!m_did_set_uuid) { + m_uuid = uuid; + m_did_set_uuid = true; + } else { + lldbassert(0 && "Attempting to overwrite the existing module UUID"); + } +} + +llvm::Expected<TypeSystemSP> +Module::GetTypeSystemForLanguage(LanguageType language) { + return m_type_system_map.GetTypeSystemForLanguage(language, this, true); +} + +void Module::ForEachTypeSystem( + llvm::function_ref<bool(lldb::TypeSystemSP)> callback) { + m_type_system_map.ForEach(callback); +} + +void Module::ParseAllDebugSymbols() { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + size_t num_comp_units = GetNumCompileUnits(); + if (num_comp_units == 0) + return; + + SymbolFile *symbols = GetSymbolFile(); + + for (size_t cu_idx = 0; cu_idx < num_comp_units; cu_idx++) { + SymbolContext sc; + sc.module_sp = shared_from_this(); + sc.comp_unit = symbols->GetCompileUnitAtIndex(cu_idx).get(); + if (!sc.comp_unit) + continue; + + symbols->ParseVariablesForContext(sc); + + symbols->ParseFunctions(*sc.comp_unit); + + sc.comp_unit->ForeachFunction([&sc, &symbols](const FunctionSP &f) { + symbols->ParseBlocksRecursive(*f); + + // Parse the variables for this function and all its blocks + sc.function = f.get(); + symbols->ParseVariablesForContext(sc); + return false; + }); + + // Parse all types for this compile unit + symbols->ParseTypes(*sc.comp_unit); + } +} + +void Module::CalculateSymbolContext(SymbolContext *sc) { + sc->module_sp = shared_from_this(); +} + +ModuleSP Module::CalculateSymbolContextModule() { return shared_from_this(); } + +void Module::DumpSymbolContext(Stream *s) { + s->Printf(", Module{%p}", static_cast<void *>(this)); +} + +size_t Module::GetNumCompileUnits() { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + if (SymbolFile *symbols = GetSymbolFile()) + return symbols->GetNumCompileUnits(); + return 0; +} + +CompUnitSP Module::GetCompileUnitAtIndex(size_t index) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + size_t num_comp_units = GetNumCompileUnits(); + CompUnitSP cu_sp; + + if (index < num_comp_units) { + if (SymbolFile *symbols = GetSymbolFile()) + cu_sp = symbols->GetCompileUnitAtIndex(index); + } + return cu_sp; +} + +bool Module::ResolveFileAddress(lldb::addr_t vm_addr, Address &so_addr) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + SectionList *section_list = GetSectionList(); + if (section_list) + return so_addr.ResolveAddressUsingFileSections(vm_addr, section_list); + return false; +} + +uint32_t Module::ResolveSymbolContextForAddress( + const Address &so_addr, lldb::SymbolContextItem resolve_scope, + SymbolContext &sc, bool resolve_tail_call_address) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + uint32_t resolved_flags = 0; + + // Clear the result symbol context in case we don't find anything, but don't + // clear the target + sc.Clear(false); + + // Get the section from the section/offset address. + SectionSP section_sp(so_addr.GetSection()); + + // Make sure the section matches this module before we try and match anything + if (section_sp && section_sp->GetModule().get() == this) { + // If the section offset based address resolved itself, then this is the + // right module. + sc.module_sp = shared_from_this(); + resolved_flags |= eSymbolContextModule; + + SymbolFile *symfile = GetSymbolFile(); + if (!symfile) + return resolved_flags; + + // Resolve the compile unit, function, block, line table or line entry if + // requested. + if (resolve_scope & eSymbolContextCompUnit || + resolve_scope & eSymbolContextFunction || + resolve_scope & eSymbolContextBlock || + resolve_scope & eSymbolContextLineEntry || + resolve_scope & eSymbolContextVariable) { + symfile->SetLoadDebugInfoEnabled(); + resolved_flags |= + symfile->ResolveSymbolContext(so_addr, resolve_scope, sc); + } + + // Resolve the symbol if requested, but don't re-look it up if we've + // already found it. + if (resolve_scope & eSymbolContextSymbol && + !(resolved_flags & eSymbolContextSymbol)) { + Symtab *symtab = symfile->GetSymtab(); + if (symtab && so_addr.IsSectionOffset()) { + Symbol *matching_symbol = nullptr; + + symtab->ForEachSymbolContainingFileAddress( + so_addr.GetFileAddress(), + [&matching_symbol](Symbol *symbol) -> bool { + if (symbol->GetType() != eSymbolTypeInvalid) { + matching_symbol = symbol; + return false; // Stop iterating + } + return true; // Keep iterating + }); + sc.symbol = matching_symbol; + if (!sc.symbol && resolve_scope & eSymbolContextFunction && + !(resolved_flags & eSymbolContextFunction)) { + bool verify_unique = false; // No need to check again since + // ResolveSymbolContext failed to find a + // symbol at this address. + if (ObjectFile *obj_file = sc.module_sp->GetObjectFile()) + sc.symbol = + obj_file->ResolveSymbolForAddress(so_addr, verify_unique); + } + + if (sc.symbol) { + if (sc.symbol->IsSynthetic()) { + // We have a synthetic symbol so lets check if the object file from + // the symbol file in the symbol vendor is different than the + // object file for the module, and if so search its symbol table to + // see if we can come up with a better symbol. For example dSYM + // files on MacOSX have an unstripped symbol table inside of them. + ObjectFile *symtab_objfile = symtab->GetObjectFile(); + if (symtab_objfile && symtab_objfile->IsStripped()) { + ObjectFile *symfile_objfile = symfile->GetObjectFile(); + if (symfile_objfile != symtab_objfile) { + Symtab *symfile_symtab = symfile_objfile->GetSymtab(); + if (symfile_symtab) { + Symbol *symbol = + symfile_symtab->FindSymbolContainingFileAddress( + so_addr.GetFileAddress()); + if (symbol && !symbol->IsSynthetic()) { + sc.symbol = symbol; + } + } + } + } + } + resolved_flags |= eSymbolContextSymbol; + } + } + } + + // For function symbols, so_addr may be off by one. This is a convention + // consistent with FDE row indices in eh_frame sections, but requires extra + // logic here to permit symbol lookup for disassembly and unwind. + if (resolve_scope & eSymbolContextSymbol && + !(resolved_flags & eSymbolContextSymbol) && resolve_tail_call_address && + so_addr.IsSectionOffset()) { + Address previous_addr = so_addr; + previous_addr.Slide(-1); + + bool do_resolve_tail_call_address = false; // prevent recursion + const uint32_t flags = ResolveSymbolContextForAddress( + previous_addr, resolve_scope, sc, do_resolve_tail_call_address); + if (flags & eSymbolContextSymbol) { + AddressRange addr_range; + if (sc.GetAddressRange(eSymbolContextFunction | eSymbolContextSymbol, 0, + false, addr_range)) { + if (addr_range.GetBaseAddress().GetSection() == + so_addr.GetSection()) { + // If the requested address is one past the address range of a + // function (i.e. a tail call), or the decremented address is the + // start of a function (i.e. some forms of trampoline), indicate + // that the symbol has been resolved. + if (so_addr.GetOffset() == + addr_range.GetBaseAddress().GetOffset() || + so_addr.GetOffset() == addr_range.GetBaseAddress().GetOffset() + + addr_range.GetByteSize()) { + resolved_flags |= flags; + } + } else { + sc.symbol = + nullptr; // Don't trust the symbol if the sections didn't match. + } + } + } + } + } + return resolved_flags; +} + +uint32_t Module::ResolveSymbolContextForFilePath( + const char *file_path, uint32_t line, bool check_inlines, + lldb::SymbolContextItem resolve_scope, SymbolContextList &sc_list) { + FileSpec file_spec(file_path); + return ResolveSymbolContextsForFileSpec(file_spec, line, check_inlines, + resolve_scope, sc_list); +} + +uint32_t Module::ResolveSymbolContextsForFileSpec( + const FileSpec &file_spec, uint32_t line, bool check_inlines, + lldb::SymbolContextItem resolve_scope, SymbolContextList &sc_list) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + LLDB_SCOPED_TIMERF("Module::ResolveSymbolContextForFilePath (%s:%u, " + "check_inlines = %s, resolve_scope = 0x%8.8x)", + file_spec.GetPath().c_str(), line, + check_inlines ? "yes" : "no", resolve_scope); + + const uint32_t initial_count = sc_list.GetSize(); + + if (SymbolFile *symbols = GetSymbolFile()) { + // TODO: Handle SourceLocationSpec column information + SourceLocationSpec location_spec(file_spec, line, /*column=*/std::nullopt, + check_inlines, /*exact_match=*/false); + + symbols->ResolveSymbolContext(location_spec, resolve_scope, sc_list); + } + + return sc_list.GetSize() - initial_count; +} + +void Module::FindGlobalVariables(ConstString name, + const CompilerDeclContext &parent_decl_ctx, + size_t max_matches, VariableList &variables) { + if (SymbolFile *symbols = GetSymbolFile()) + symbols->FindGlobalVariables(name, parent_decl_ctx, max_matches, variables); +} + +void Module::FindGlobalVariables(const RegularExpression ®ex, + size_t max_matches, VariableList &variables) { + SymbolFile *symbols = GetSymbolFile(); + if (symbols) + symbols->FindGlobalVariables(regex, max_matches, variables); +} + +void Module::FindCompileUnits(const FileSpec &path, + SymbolContextList &sc_list) { + const size_t num_compile_units = GetNumCompileUnits(); + SymbolContext sc; + sc.module_sp = shared_from_this(); + for (size_t i = 0; i < num_compile_units; ++i) { + sc.comp_unit = GetCompileUnitAtIndex(i).get(); + if (sc.comp_unit) { + if (FileSpec::Match(path, sc.comp_unit->GetPrimaryFile())) + sc_list.Append(sc); + } + } +} + +Module::LookupInfo::LookupInfo(ConstString name, + FunctionNameType name_type_mask, + LanguageType language) + : m_name(name), m_lookup_name(), m_language(language) { + const char *name_cstr = name.GetCString(); + llvm::StringRef basename; + llvm::StringRef context; + + if (name_type_mask & eFunctionNameTypeAuto) { + if (CPlusPlusLanguage::IsCPPMangledName(name_cstr)) + m_name_type_mask = eFunctionNameTypeFull; + else if ((language == eLanguageTypeUnknown || + Language::LanguageIsObjC(language)) && + ObjCLanguage::IsPossibleObjCMethodName(name_cstr)) + m_name_type_mask = eFunctionNameTypeFull; + else if (Language::LanguageIsC(language)) { + m_name_type_mask = eFunctionNameTypeFull; + } else { + if ((language == eLanguageTypeUnknown || + Language::LanguageIsObjC(language)) && + ObjCLanguage::IsPossibleObjCSelector(name_cstr)) + m_name_type_mask |= eFunctionNameTypeSelector; + + CPlusPlusLanguage::MethodName cpp_method(name); + basename = cpp_method.GetBasename(); + if (basename.empty()) { + if (CPlusPlusLanguage::ExtractContextAndIdentifier(name_cstr, context, + basename)) + m_name_type_mask |= (eFunctionNameTypeMethod | eFunctionNameTypeBase); + else + m_name_type_mask |= eFunctionNameTypeFull; + } else { + m_name_type_mask |= (eFunctionNameTypeMethod | eFunctionNameTypeBase); + } + } + } else { + m_name_type_mask = name_type_mask; + if (name_type_mask & eFunctionNameTypeMethod || + name_type_mask & eFunctionNameTypeBase) { + // If they've asked for a CPP method or function name and it can't be + // that, we don't even need to search for CPP methods or names. + CPlusPlusLanguage::MethodName cpp_method(name); + if (cpp_method.IsValid()) { + basename = cpp_method.GetBasename(); + + if (!cpp_method.GetQualifiers().empty()) { + // There is a "const" or other qualifier following the end of the + // function parens, this can't be a eFunctionNameTypeBase + m_name_type_mask &= ~(eFunctionNameTypeBase); + if (m_name_type_mask == eFunctionNameTypeNone) + return; + } + } else { + // If the CPP method parser didn't manage to chop this up, try to fill + // in the base name if we can. If a::b::c is passed in, we need to just + // look up "c", and then we'll filter the result later. + CPlusPlusLanguage::ExtractContextAndIdentifier(name_cstr, context, + basename); + } + } + + if (name_type_mask & eFunctionNameTypeSelector) { + if (!ObjCLanguage::IsPossibleObjCSelector(name_cstr)) { + m_name_type_mask &= ~(eFunctionNameTypeSelector); + if (m_name_type_mask == eFunctionNameTypeNone) + return; + } + } + + // Still try and get a basename in case someone specifies a name type mask + // of eFunctionNameTypeFull and a name like "A::func" + if (basename.empty()) { + if (name_type_mask & eFunctionNameTypeFull && + !CPlusPlusLanguage::IsCPPMangledName(name_cstr)) { + CPlusPlusLanguage::MethodName cpp_method(name); + basename = cpp_method.GetBasename(); + if (basename.empty()) + CPlusPlusLanguage::ExtractContextAndIdentifier(name_cstr, context, + basename); + } + } + } + + if (!basename.empty()) { + // The name supplied was a partial C++ path like "a::count". In this case + // we want to do a lookup on the basename "count" and then make sure any + // matching results contain "a::count" so that it would match "b::a::count" + // and "a::count". This is why we set "match_name_after_lookup" to true + m_lookup_name.SetString(basename); + m_match_name_after_lookup = true; + } else { + // The name is already correct, just use the exact name as supplied, and we + // won't need to check if any matches contain "name" + m_lookup_name = name; + m_match_name_after_lookup = false; + } +} + +bool Module::LookupInfo::NameMatchesLookupInfo( + ConstString function_name, LanguageType language_type) const { + // We always keep unnamed symbols + if (!function_name) + return true; + + // If we match exactly, we can return early + if (m_name == function_name) + return true; + + // If function_name is mangled, we'll need to demangle it. + // In the pathologial case where the function name "looks" mangled but is + // actually demangled (e.g. a method named _Zonk), this operation should be + // relatively inexpensive since no demangling is actually occuring. See + // Mangled::SetValue for more context. + const bool function_name_may_be_mangled = + Mangled::GetManglingScheme(function_name) != Mangled::eManglingSchemeNone; + ConstString demangled_function_name = function_name; + if (function_name_may_be_mangled) { + Mangled mangled_function_name(function_name); + demangled_function_name = mangled_function_name.GetDemangledName(); + } + + // If the symbol has a language, then let the language make the match. + // Otherwise just check that the demangled function name contains the + // demangled user-provided name. + if (Language *language = Language::FindPlugin(language_type)) + return language->DemangledNameContainsPath(m_name, demangled_function_name); + + llvm::StringRef function_name_ref = demangled_function_name; + return function_name_ref.contains(m_name); +} + +void Module::LookupInfo::Prune(SymbolContextList &sc_list, + size_t start_idx) const { + if (m_match_name_after_lookup && m_name) { + SymbolContext sc; + size_t i = start_idx; + while (i < sc_list.GetSize()) { + if (!sc_list.GetContextAtIndex(i, sc)) + break; + + bool keep_it = + NameMatchesLookupInfo(sc.GetFunctionName(), sc.GetLanguage()); + if (keep_it) + ++i; + else + sc_list.RemoveContextAtIndex(i); + } + } + + // If we have only full name matches we might have tried to set breakpoint on + // "func" and specified eFunctionNameTypeFull, but we might have found + // "a::func()", "a::b::func()", "c::func()", "func()" and "func". Only + // "func()" and "func" should end up matching. + if (m_name_type_mask == eFunctionNameTypeFull) { + SymbolContext sc; + size_t i = start_idx; + while (i < sc_list.GetSize()) { + if (!sc_list.GetContextAtIndex(i, sc)) + break; + // Make sure the mangled and demangled names don't match before we try to + // pull anything out + ConstString mangled_name(sc.GetFunctionName(Mangled::ePreferMangled)); + ConstString full_name(sc.GetFunctionName()); + if (mangled_name != m_name && full_name != m_name) { + CPlusPlusLanguage::MethodName cpp_method(full_name); + if (cpp_method.IsValid()) { + if (cpp_method.GetContext().empty()) { + if (cpp_method.GetBasename().compare(m_name) != 0) { + sc_list.RemoveContextAtIndex(i); + continue; + } + } else { + std::string qualified_name; + llvm::StringRef anon_prefix("(anonymous namespace)"); + if (cpp_method.GetContext() == anon_prefix) + qualified_name = cpp_method.GetBasename().str(); + else + qualified_name = cpp_method.GetScopeQualifiedName(); + if (qualified_name != m_name.GetCString()) { + sc_list.RemoveContextAtIndex(i); + continue; + } + } + } + } + ++i; + } + } +} + +void Module::FindFunctions(const Module::LookupInfo &lookup_info, + const CompilerDeclContext &parent_decl_ctx, + const ModuleFunctionSearchOptions &options, + SymbolContextList &sc_list) { + // Find all the functions (not symbols, but debug information functions... + if (SymbolFile *symbols = GetSymbolFile()) { + symbols->FindFunctions(lookup_info, parent_decl_ctx, + options.include_inlines, sc_list); + // Now check our symbol table for symbols that are code symbols if + // requested + if (options.include_symbols) { + if (Symtab *symtab = symbols->GetSymtab()) { + symtab->FindFunctionSymbols(lookup_info.GetLookupName(), + lookup_info.GetNameTypeMask(), sc_list); + } + } + } +} + +void Module::FindFunctions(ConstString name, + const CompilerDeclContext &parent_decl_ctx, + FunctionNameType name_type_mask, + const ModuleFunctionSearchOptions &options, + SymbolContextList &sc_list) { + const size_t old_size = sc_list.GetSize(); + LookupInfo lookup_info(name, name_type_mask, eLanguageTypeUnknown); + FindFunctions(lookup_info, parent_decl_ctx, options, sc_list); + if (name_type_mask & eFunctionNameTypeAuto) { + const size_t new_size = sc_list.GetSize(); + if (old_size < new_size) + lookup_info.Prune(sc_list, old_size); + } +} + +void Module::FindFunctions(llvm::ArrayRef<CompilerContext> compiler_ctx, + FunctionNameType name_type_mask, + const ModuleFunctionSearchOptions &options, + SymbolContextList &sc_list) { + if (compiler_ctx.empty() || + compiler_ctx.back().kind != CompilerContextKind::Function) + return; + ConstString name = compiler_ctx.back().name; + SymbolContextList unfiltered; + FindFunctions(name, CompilerDeclContext(), name_type_mask, options, + unfiltered); + // Filter by context. + for (auto &sc : unfiltered) + if (sc.function && compiler_ctx.equals(sc.function->GetCompilerContext())) + sc_list.Append(sc); +} + +void Module::FindFunctions(const RegularExpression ®ex, + const ModuleFunctionSearchOptions &options, + SymbolContextList &sc_list) { + const size_t start_size = sc_list.GetSize(); + + if (SymbolFile *symbols = GetSymbolFile()) { + symbols->FindFunctions(regex, options.include_inlines, sc_list); + + // Now check our symbol table for symbols that are code symbols if + // requested + if (options.include_symbols) { + Symtab *symtab = symbols->GetSymtab(); + if (symtab) { + std::vector<uint32_t> symbol_indexes; + symtab->AppendSymbolIndexesMatchingRegExAndType( + regex, eSymbolTypeAny, Symtab::eDebugAny, Symtab::eVisibilityAny, + symbol_indexes); + const size_t num_matches = symbol_indexes.size(); + if (num_matches) { + SymbolContext sc(this); + const size_t end_functions_added_index = sc_list.GetSize(); + size_t num_functions_added_to_sc_list = + end_functions_added_index - start_size; + if (num_functions_added_to_sc_list == 0) { + // No functions were added, just symbols, so we can just append + // them + for (size_t i = 0; i < num_matches; ++i) { + sc.symbol = symtab->SymbolAtIndex(symbol_indexes[i]); + SymbolType sym_type = sc.symbol->GetType(); + if (sc.symbol && (sym_type == eSymbolTypeCode || + sym_type == eSymbolTypeResolver)) + sc_list.Append(sc); + } + } else { + typedef std::map<lldb::addr_t, uint32_t> FileAddrToIndexMap; + FileAddrToIndexMap file_addr_to_index; + for (size_t i = start_size; i < end_functions_added_index; ++i) { + const SymbolContext &sc = sc_list[i]; + if (sc.block) + continue; + file_addr_to_index[sc.function->GetAddressRange() + .GetBaseAddress() + .GetFileAddress()] = i; + } + + FileAddrToIndexMap::const_iterator end = file_addr_to_index.end(); + // Functions were added so we need to merge symbols into any + // existing function symbol contexts + for (size_t i = start_size; i < num_matches; ++i) { + sc.symbol = symtab->SymbolAtIndex(symbol_indexes[i]); + SymbolType sym_type = sc.symbol->GetType(); + if (sc.symbol && sc.symbol->ValueIsAddress() && + (sym_type == eSymbolTypeCode || + sym_type == eSymbolTypeResolver)) { + FileAddrToIndexMap::const_iterator pos = + file_addr_to_index.find( + sc.symbol->GetAddressRef().GetFileAddress()); + if (pos == end) + sc_list.Append(sc); + else + sc_list[pos->second].symbol = sc.symbol; + } + } + } + } + } + } + } +} + +void Module::FindAddressesForLine(const lldb::TargetSP target_sp, + const FileSpec &file, uint32_t line, + Function *function, + std::vector<Address> &output_local, + std::vector<Address> &output_extern) { + SearchFilterByModule filter(target_sp, m_file); + + // TODO: Handle SourceLocationSpec column information + SourceLocationSpec location_spec(file, line, /*column=*/std::nullopt, + /*check_inlines=*/true, + /*exact_match=*/false); + AddressResolverFileLine resolver(location_spec); + resolver.ResolveAddress(filter); + + for (size_t n = 0; n < resolver.GetNumberOfAddresses(); n++) { + Address addr = resolver.GetAddressRangeAtIndex(n).GetBaseAddress(); + Function *f = addr.CalculateSymbolContextFunction(); + if (f && f == function) + output_local.push_back(addr); + else + output_extern.push_back(addr); + } +} + +void Module::FindTypes(const TypeQuery &query, TypeResults &results) { + if (SymbolFile *symbols = GetSymbolFile()) + symbols->FindTypes(query, results); +} + +static Debugger::DebuggerList +DebuggersOwningModuleRequestingInterruption(Module &module) { + Debugger::DebuggerList requestors = + Debugger::DebuggersRequestingInterruption(); + Debugger::DebuggerList interruptors; + if (requestors.empty()) + return interruptors; + + for (auto debugger_sp : requestors) { + if (!debugger_sp->InterruptRequested()) + continue; + if (debugger_sp->GetTargetList() + .AnyTargetContainsModule(module)) + interruptors.push_back(debugger_sp); + } + return interruptors; +} + +SymbolFile *Module::GetSymbolFile(bool can_create, Stream *feedback_strm) { + if (!m_did_load_symfile.load()) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + if (!m_did_load_symfile.load() && can_create) { + Debugger::DebuggerList interruptors = + DebuggersOwningModuleRequestingInterruption(*this); + if (!interruptors.empty()) { + for (auto debugger_sp : interruptors) { + REPORT_INTERRUPTION(*(debugger_sp.get()), + "Interrupted fetching symbols for module {0}", + this->GetFileSpec()); + } + return nullptr; + } + ObjectFile *obj_file = GetObjectFile(); + if (obj_file != nullptr) { + LLDB_SCOPED_TIMER(); + m_symfile_up.reset( + SymbolVendor::FindPlugin(shared_from_this(), feedback_strm)); + m_did_load_symfile = true; + if (m_unwind_table) + m_unwind_table->Update(); + } + } + } + return m_symfile_up ? m_symfile_up->GetSymbolFile() : nullptr; +} + +Symtab *Module::GetSymtab() { + if (SymbolFile *symbols = GetSymbolFile()) + return symbols->GetSymtab(); + return nullptr; +} + +void Module::SetFileSpecAndObjectName(const FileSpec &file, + ConstString object_name) { + // Container objects whose paths do not specify a file directly can call this + // function to correct the file and object names. + m_file = file; + m_mod_time = FileSystem::Instance().GetModificationTime(file); + m_object_name = object_name; +} + +const ArchSpec &Module::GetArchitecture() const { return m_arch; } + +std::string Module::GetSpecificationDescription() const { + std::string spec(GetFileSpec().GetPath()); + if (m_object_name) { + spec += '('; + spec += m_object_name.GetCString(); + spec += ')'; + } + return spec; +} + +void Module::GetDescription(llvm::raw_ostream &s, + lldb::DescriptionLevel level) { + if (level >= eDescriptionLevelFull) { + if (m_arch.IsValid()) + s << llvm::formatv("({0}) ", m_arch.GetArchitectureName()); + } + + if (level == eDescriptionLevelBrief) { + const char *filename = m_file.GetFilename().GetCString(); + if (filename) + s << filename; + } else { + char path[PATH_MAX]; + if (m_file.GetPath(path, sizeof(path))) + s << path; + } + + const char *object_name = m_object_name.GetCString(); + if (object_name) + s << llvm::formatv("({0})", object_name); +} + +bool Module::FileHasChanged() const { + // We have provided the DataBuffer for this module to avoid accessing the + // filesystem. We never want to reload those files. + if (m_data_sp) + return false; + if (!m_file_has_changed) + m_file_has_changed = + (FileSystem::Instance().GetModificationTime(m_file) != m_mod_time); + return m_file_has_changed; +} + +void Module::ReportWarningOptimization( + std::optional<lldb::user_id_t> debugger_id) { + ConstString file_name = GetFileSpec().GetFilename(); + if (file_name.IsEmpty()) + return; + + StreamString ss; + ss << file_name + << " was compiled with optimization - stepping may behave " + "oddly; variables may not be available."; + Debugger::ReportWarning(std::string(ss.GetString()), debugger_id, + &m_optimization_warning); +} + +void Module::ReportWarningUnsupportedLanguage( + LanguageType language, std::optional<lldb::user_id_t> debugger_id) { + StreamString ss; + ss << "This version of LLDB has no plugin for the language \"" + << Language::GetNameForLanguageType(language) + << "\". " + "Inspection of frame variables will be limited."; + Debugger::ReportWarning(std::string(ss.GetString()), debugger_id, + &m_language_warning); +} + +void Module::ReportErrorIfModifyDetected( + const llvm::formatv_object_base &payload) { + if (!m_first_file_changed_log) { + if (FileHasChanged()) { + m_first_file_changed_log = true; + StreamString strm; + strm.PutCString("the object file "); + GetDescription(strm.AsRawOstream(), lldb::eDescriptionLevelFull); + strm.PutCString(" has been modified\n"); + strm.PutCString(payload.str()); + strm.PutCString("The debug session should be aborted as the original " + "debug information has been overwritten."); + Debugger::ReportError(std::string(strm.GetString())); + } + } +} + +void Module::ReportError(const llvm::formatv_object_base &payload) { + StreamString strm; + GetDescription(strm.AsRawOstream(), lldb::eDescriptionLevelBrief); + strm.PutChar(' '); + strm.PutCString(payload.str()); + Debugger::ReportError(strm.GetString().str()); +} + +void Module::ReportWarning(const llvm::formatv_object_base &payload) { + StreamString strm; + GetDescription(strm.AsRawOstream(), lldb::eDescriptionLevelFull); + strm.PutChar(' '); + strm.PutCString(payload.str()); + Debugger::ReportWarning(std::string(strm.GetString())); +} + +void Module::LogMessage(Log *log, const llvm::formatv_object_base &payload) { + StreamString log_message; + GetDescription(log_message.AsRawOstream(), lldb::eDescriptionLevelFull); + log_message.PutCString(": "); + log_message.PutCString(payload.str()); + log->PutCString(log_message.GetData()); +} + +void Module::LogMessageVerboseBacktrace( + Log *log, const llvm::formatv_object_base &payload) { + StreamString log_message; + GetDescription(log_message.AsRawOstream(), lldb::eDescriptionLevelFull); + log_message.PutCString(": "); + log_message.PutCString(payload.str()); + if (log->GetVerbose()) { + std::string back_trace; + llvm::raw_string_ostream stream(back_trace); + llvm::sys::PrintStackTrace(stream); + log_message.PutCString(back_trace); + } + log->PutCString(log_message.GetData()); +} + +void Module::Dump(Stream *s) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + // s->Printf("%.*p: ", (int)sizeof(void*) * 2, this); + s->Indent(); + s->Printf("Module %s%s%s%s\n", m_file.GetPath().c_str(), + m_object_name ? "(" : "", + m_object_name ? m_object_name.GetCString() : "", + m_object_name ? ")" : ""); + + s->IndentMore(); + + ObjectFile *objfile = GetObjectFile(); + if (objfile) + objfile->Dump(s); + + if (SymbolFile *symbols = GetSymbolFile()) + symbols->Dump(*s); + + s->IndentLess(); +} + +ConstString Module::GetObjectName() const { return m_object_name; } + +ObjectFile *Module::GetObjectFile() { + if (!m_did_load_objfile.load()) { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + if (!m_did_load_objfile.load()) { + LLDB_SCOPED_TIMERF("Module::GetObjectFile () module = %s", + GetFileSpec().GetFilename().AsCString("")); + lldb::offset_t data_offset = 0; + lldb::offset_t file_size = 0; + + if (m_data_sp) + file_size = m_data_sp->GetByteSize(); + else if (m_file) + file_size = FileSystem::Instance().GetByteSize(m_file); + + if (file_size > m_object_offset) { + m_did_load_objfile = true; + // FindPlugin will modify its data_sp argument. Do not let it + // modify our m_data_sp member. + auto data_sp = m_data_sp; + m_objfile_sp = ObjectFile::FindPlugin( + shared_from_this(), &m_file, m_object_offset, + file_size - m_object_offset, data_sp, data_offset); + if (m_objfile_sp) { + // Once we get the object file, update our module with the object + // file's architecture since it might differ in vendor/os if some + // parts were unknown. But since the matching arch might already be + // more specific than the generic COFF architecture, only merge in + // those values that overwrite unspecified unknown values. + m_arch.MergeFrom(m_objfile_sp->GetArchitecture()); + } else { + ReportError("failed to load objfile for {0}\nDebugging will be " + "degraded for this module.", + GetFileSpec().GetPath().c_str()); + } + } + } + } + return m_objfile_sp.get(); +} + +SectionList *Module::GetSectionList() { + // Populate m_sections_up with sections from objfile. + if (!m_sections_up) { + ObjectFile *obj_file = GetObjectFile(); + if (obj_file != nullptr) + obj_file->CreateSections(*GetUnifiedSectionList()); + } + return m_sections_up.get(); +} + +void Module::SectionFileAddressesChanged() { + ObjectFile *obj_file = GetObjectFile(); + if (obj_file) + obj_file->SectionFileAddressesChanged(); + if (SymbolFile *symbols = GetSymbolFile()) + symbols->SectionFileAddressesChanged(); +} + +UnwindTable &Module::GetUnwindTable() { + if (!m_unwind_table) { + if (!m_symfile_spec) + SymbolLocator::DownloadSymbolFileAsync(GetUUID()); + m_unwind_table.emplace(*this); + } + return *m_unwind_table; +} + +SectionList *Module::GetUnifiedSectionList() { + if (!m_sections_up) + m_sections_up = std::make_unique<SectionList>(); + return m_sections_up.get(); +} + +const Symbol *Module::FindFirstSymbolWithNameAndType(ConstString name, + SymbolType symbol_type) { + LLDB_SCOPED_TIMERF( + "Module::FindFirstSymbolWithNameAndType (name = %s, type = %i)", + name.AsCString(), symbol_type); + if (Symtab *symtab = GetSymtab()) + return symtab->FindFirstSymbolWithNameAndType( + name, symbol_type, Symtab::eDebugAny, Symtab::eVisibilityAny); + return nullptr; +} +void Module::SymbolIndicesToSymbolContextList( + Symtab *symtab, std::vector<uint32_t> &symbol_indexes, + SymbolContextList &sc_list) { + // No need to protect this call using m_mutex all other method calls are + // already thread safe. + + size_t num_indices = symbol_indexes.size(); + if (num_indices > 0) { + SymbolContext sc; + CalculateSymbolContext(&sc); + for (size_t i = 0; i < num_indices; i++) { + sc.symbol = symtab->SymbolAtIndex(symbol_indexes[i]); + if (sc.symbol) + sc_list.Append(sc); + } + } +} + +void Module::FindFunctionSymbols(ConstString name, uint32_t name_type_mask, + SymbolContextList &sc_list) { + LLDB_SCOPED_TIMERF("Module::FindSymbolsFunctions (name = %s, mask = 0x%8.8x)", + name.AsCString(), name_type_mask); + if (Symtab *symtab = GetSymtab()) + symtab->FindFunctionSymbols(name, name_type_mask, sc_list); +} + +void Module::FindSymbolsWithNameAndType(ConstString name, + SymbolType symbol_type, + SymbolContextList &sc_list) { + // No need to protect this call using m_mutex all other method calls are + // already thread safe. + if (Symtab *symtab = GetSymtab()) { + std::vector<uint32_t> symbol_indexes; + symtab->FindAllSymbolsWithNameAndType(name, symbol_type, symbol_indexes); + SymbolIndicesToSymbolContextList(symtab, symbol_indexes, sc_list); + } +} + +void Module::FindSymbolsMatchingRegExAndType( + const RegularExpression ®ex, SymbolType symbol_type, + SymbolContextList &sc_list, Mangled::NamePreference mangling_preference) { + // No need to protect this call using m_mutex all other method calls are + // already thread safe. + LLDB_SCOPED_TIMERF( + "Module::FindSymbolsMatchingRegExAndType (regex = %s, type = %i)", + regex.GetText().str().c_str(), symbol_type); + if (Symtab *symtab = GetSymtab()) { + std::vector<uint32_t> symbol_indexes; + symtab->FindAllSymbolsMatchingRexExAndType( + regex, symbol_type, Symtab::eDebugAny, Symtab::eVisibilityAny, + symbol_indexes, mangling_preference); + SymbolIndicesToSymbolContextList(symtab, symbol_indexes, sc_list); + } +} + +void Module::PreloadSymbols() { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + SymbolFile *sym_file = GetSymbolFile(); + if (!sym_file) + return; + + // Load the object file symbol table and any symbols from the SymbolFile that + // get appended using SymbolFile::AddSymbols(...). + if (Symtab *symtab = sym_file->GetSymtab()) + symtab->PreloadSymbols(); + + // Now let the symbol file preload its data and the symbol table will be + // available without needing to take the module lock. + sym_file->PreloadSymbols(); +} + +void Module::SetSymbolFileFileSpec(const FileSpec &file) { + if (!FileSystem::Instance().Exists(file)) + return; + if (m_symfile_up) { + // Remove any sections in the unified section list that come from the + // current symbol vendor. + SectionList *section_list = GetSectionList(); + SymbolFile *symbol_file = GetSymbolFile(); + if (section_list && symbol_file) { + ObjectFile *obj_file = symbol_file->GetObjectFile(); + // Make sure we have an object file and that the symbol vendor's objfile + // isn't the same as the module's objfile before we remove any sections + // for it... + if (obj_file) { + // Check to make sure we aren't trying to specify the file we already + // have + if (obj_file->GetFileSpec() == file) { + // We are being told to add the exact same file that we already have + // we don't have to do anything. + return; + } + + // Cleare the current symtab as we are going to replace it with a new + // one + obj_file->ClearSymtab(); + + // The symbol file might be a directory bundle ("/tmp/a.out.dSYM") + // instead of a full path to the symbol file within the bundle + // ("/tmp/a.out.dSYM/Contents/Resources/DWARF/a.out"). So we need to + // check this + if (FileSystem::Instance().IsDirectory(file)) { + std::string new_path(file.GetPath()); + std::string old_path(obj_file->GetFileSpec().GetPath()); + if (llvm::StringRef(old_path).starts_with(new_path)) { + // We specified the same bundle as the symbol file that we already + // have + return; + } + } + + if (obj_file != m_objfile_sp.get()) { + size_t num_sections = section_list->GetNumSections(0); + for (size_t idx = num_sections; idx > 0; --idx) { + lldb::SectionSP section_sp( + section_list->GetSectionAtIndex(idx - 1)); + if (section_sp->GetObjectFile() == obj_file) { + section_list->DeleteSection(idx - 1); + } + } + } + } + } + // Keep all old symbol files around in case there are any lingering type + // references in any SBValue objects that might have been handed out. + m_old_symfiles.push_back(std::move(m_symfile_up)); + } + m_symfile_spec = file; + m_symfile_up.reset(); + m_did_load_symfile = false; +} + +bool Module::IsExecutable() { + if (GetObjectFile() == nullptr) + return false; + else + return GetObjectFile()->IsExecutable(); +} + +bool Module::IsLoadedInTarget(Target *target) { + ObjectFile *obj_file = GetObjectFile(); + if (obj_file) { + SectionList *sections = GetSectionList(); + if (sections != nullptr) { + size_t num_sections = sections->GetSize(); + for (size_t sect_idx = 0; sect_idx < num_sections; sect_idx++) { + SectionSP section_sp = sections->GetSectionAtIndex(sect_idx); + if (section_sp->GetLoadBaseAddress(target) != LLDB_INVALID_ADDRESS) { + return true; + } + } + } + } + return false; +} + +bool Module::LoadScriptingResourceInTarget(Target *target, Status &error, + Stream &feedback_stream) { + if (!target) { + error.SetErrorString("invalid destination Target"); + return false; + } + + LoadScriptFromSymFile should_load = + target->TargetProperties::GetLoadScriptFromSymbolFile(); + + if (should_load == eLoadScriptFromSymFileFalse) + return false; + + Debugger &debugger = target->GetDebugger(); + const ScriptLanguage script_language = debugger.GetScriptLanguage(); + if (script_language != eScriptLanguageNone) { + + PlatformSP platform_sp(target->GetPlatform()); + + if (!platform_sp) { + error.SetErrorString("invalid Platform"); + return false; + } + + FileSpecList file_specs = platform_sp->LocateExecutableScriptingResources( + target, *this, feedback_stream); + + const uint32_t num_specs = file_specs.GetSize(); + if (num_specs) { + ScriptInterpreter *script_interpreter = debugger.GetScriptInterpreter(); + if (script_interpreter) { + for (uint32_t i = 0; i < num_specs; ++i) { + FileSpec scripting_fspec(file_specs.GetFileSpecAtIndex(i)); + if (scripting_fspec && + FileSystem::Instance().Exists(scripting_fspec)) { + if (should_load == eLoadScriptFromSymFileWarn) { + feedback_stream.Printf( + "warning: '%s' contains a debug script. To run this script " + "in " + "this debug session:\n\n command script import " + "\"%s\"\n\n" + "To run all discovered debug scripts in this session:\n\n" + " settings set target.load-script-from-symbol-file " + "true\n", + GetFileSpec().GetFileNameStrippingExtension().GetCString(), + scripting_fspec.GetPath().c_str()); + return false; + } + StreamString scripting_stream; + scripting_fspec.Dump(scripting_stream.AsRawOstream()); + LoadScriptOptions options; + bool did_load = script_interpreter->LoadScriptingModule( + scripting_stream.GetData(), options, error); + if (!did_load) + return false; + } + } + } else { + error.SetErrorString("invalid ScriptInterpreter"); + return false; + } + } + } + return true; +} + +bool Module::SetArchitecture(const ArchSpec &new_arch) { + if (!m_arch.IsValid()) { + m_arch = new_arch; + return true; + } + return m_arch.IsCompatibleMatch(new_arch); +} + +bool Module::SetLoadAddress(Target &target, lldb::addr_t value, + bool value_is_offset, bool &changed) { + ObjectFile *object_file = GetObjectFile(); + if (object_file != nullptr) { + changed = object_file->SetLoadAddress(target, value, value_is_offset); + return true; + } else { + changed = false; + } + return false; +} + +bool Module::MatchesModuleSpec(const ModuleSpec &module_ref) { + const UUID &uuid = module_ref.GetUUID(); + + if (uuid.IsValid()) { + // If the UUID matches, then nothing more needs to match... + return (uuid == GetUUID()); + } + + const FileSpec &file_spec = module_ref.GetFileSpec(); + if (!FileSpec::Match(file_spec, m_file) && + !FileSpec::Match(file_spec, m_platform_file)) + return false; + + const FileSpec &platform_file_spec = module_ref.GetPlatformFileSpec(); + if (!FileSpec::Match(platform_file_spec, GetPlatformFileSpec())) + return false; + + const ArchSpec &arch = module_ref.GetArchitecture(); + if (arch.IsValid()) { + if (!m_arch.IsCompatibleMatch(arch)) + return false; + } + + ConstString object_name = module_ref.GetObjectName(); + if (object_name) { + if (object_name != GetObjectName()) + return false; + } + return true; +} + +bool Module::FindSourceFile(const FileSpec &orig_spec, + FileSpec &new_spec) const { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + if (auto remapped = m_source_mappings.FindFile(orig_spec)) { + new_spec = *remapped; + return true; + } + return false; +} + +std::optional<std::string> Module::RemapSourceFile(llvm::StringRef path) const { + std::lock_guard<std::recursive_mutex> guard(m_mutex); + if (auto remapped = m_source_mappings.RemapPath(path)) + return remapped->GetPath(); + return {}; +} + +void Module::RegisterXcodeSDK(llvm::StringRef sdk_name, + llvm::StringRef sysroot) { + auto sdk_path_or_err = + HostInfo::GetSDKRoot(HostInfo::SDKOptions{sdk_name.str()}); + + if (!sdk_path_or_err) { + Debugger::ReportError("Error while searching for Xcode SDK: " + + toString(sdk_path_or_err.takeError())); + return; + } + + auto sdk_path = *sdk_path_or_err; + if (sdk_path.empty()) + return; + // If the SDK changed for a previously registered source path, update it. + // This could happend with -fdebug-prefix-map, otherwise it's unlikely. + if (!m_source_mappings.Replace(sysroot, sdk_path, true)) + // In the general case, however, append it to the list. + m_source_mappings.Append(sysroot, sdk_path, false); +} + +bool Module::MergeArchitecture(const ArchSpec &arch_spec) { + if (!arch_spec.IsValid()) + return false; + LLDB_LOGF(GetLog(LLDBLog::Object | LLDBLog::Modules), + "module has arch %s, merging/replacing with arch %s", + m_arch.GetTriple().getTriple().c_str(), + arch_spec.GetTriple().getTriple().c_str()); + if (!m_arch.IsCompatibleMatch(arch_spec)) { + // The new architecture is different, we just need to replace it. + return SetArchitecture(arch_spec); + } + + // Merge bits from arch_spec into "merged_arch" and set our architecture. + ArchSpec merged_arch(m_arch); + merged_arch.MergeFrom(arch_spec); + // SetArchitecture() is a no-op if m_arch is already valid. + m_arch = ArchSpec(); + return SetArchitecture(merged_arch); +} + +llvm::VersionTuple Module::GetVersion() { + if (ObjectFile *obj_file = GetObjectFile()) + return obj_file->GetVersion(); + return llvm::VersionTuple(); +} + +bool Module::GetIsDynamicLinkEditor() { + ObjectFile *obj_file = GetObjectFile(); + + if (obj_file) + return obj_file->GetIsDynamicLinkEditor(); + + return false; +} + +uint32_t Module::Hash() { + std::string identifier; + llvm::raw_string_ostream id_strm(identifier); + id_strm << m_arch.GetTriple().str() << '-' << m_file.GetPath(); + if (m_object_name) + id_strm << '(' << m_object_name << ')'; + if (m_object_offset > 0) + id_strm << m_object_offset; + const auto mtime = llvm::sys::toTimeT(m_object_mod_time); + if (mtime > 0) + id_strm << mtime; + return llvm::djbHash(id_strm.str()); +} + +std::string Module::GetCacheKey() { + std::string key; + llvm::raw_string_ostream strm(key); + strm << m_arch.GetTriple().str() << '-' << m_file.GetFilename(); + if (m_object_name) + strm << '(' << m_object_name << ')'; + strm << '-' << llvm::format_hex(Hash(), 10); + return strm.str(); +} + +DataFileCache *Module::GetIndexCache() { + if (!ModuleList::GetGlobalModuleListProperties().GetEnableLLDBIndexCache()) + return nullptr; + // 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 DataFileCache *g_data_file_cache = + new DataFileCache(ModuleList::GetGlobalModuleListProperties() + .GetLLDBIndexCachePath() + .GetPath()); + return g_data_file_cache; +} diff --git a/contrib/llvm-project/lldb/source/Core/ModuleChild.cpp b/contrib/llvm-project/lldb/source/Core/ModuleChild.cpp new file mode 100644 index 000000000000..7aaa8f2ac250 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/ModuleChild.cpp @@ -0,0 +1,28 @@ +//===-- ModuleChild.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/Core/ModuleChild.h" + +using namespace lldb_private; + +ModuleChild::ModuleChild(const lldb::ModuleSP &module_sp) + : m_module_wp(module_sp) {} + +ModuleChild::~ModuleChild() = default; + +const ModuleChild &ModuleChild::operator=(const ModuleChild &rhs) { + if (this != &rhs) + m_module_wp = rhs.m_module_wp; + return *this; +} + +lldb::ModuleSP ModuleChild::GetModule() const { return m_module_wp.lock(); } + +void ModuleChild::SetModule(const lldb::ModuleSP &module_sp) { + m_module_wp = module_sp; +} diff --git a/contrib/llvm-project/lldb/source/Core/ModuleList.cpp b/contrib/llvm-project/lldb/source/Core/ModuleList.cpp new file mode 100644 index 000000000000..b03490bacf95 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/ModuleList.cpp @@ -0,0 +1,1100 @@ +//===-- ModuleList.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/Core/ModuleList.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Interpreter/OptionValueFileSpec.h" +#include "lldb/Interpreter/OptionValueFileSpecList.h" +#include "lldb/Interpreter/OptionValueProperties.h" +#include "lldb/Interpreter/Property.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Symbol/TypeList.h" +#include "lldb/Symbol/VariableList.h" +#include "lldb/Utility/ArchSpec.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/FileSpecList.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/UUID.h" +#include "lldb/lldb-defines.h" + +#if defined(_WIN32) +#include "lldb/Host/windows/PosixApi.h" +#endif + +#include "clang/Driver/Driver.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Threading.h" +#include "llvm/Support/raw_ostream.h" + +#include <chrono> +#include <memory> +#include <mutex> +#include <string> +#include <utility> + +namespace lldb_private { +class Function; +} +namespace lldb_private { +class RegularExpression; +} +namespace lldb_private { +class Stream; +} +namespace lldb_private { +class SymbolFile; +} +namespace lldb_private { +class Target; +} + +using namespace lldb; +using namespace lldb_private; + +namespace { + +#define LLDB_PROPERTIES_modulelist +#include "CoreProperties.inc" + +enum { +#define LLDB_PROPERTIES_modulelist +#include "CorePropertiesEnum.inc" +}; + +} // namespace + +ModuleListProperties::ModuleListProperties() { + m_collection_sp = std::make_shared<OptionValueProperties>("symbols"); + m_collection_sp->Initialize(g_modulelist_properties); + m_collection_sp->SetValueChangedCallback(ePropertySymLinkPaths, + [this] { UpdateSymlinkMappings(); }); + + llvm::SmallString<128> path; + if (clang::driver::Driver::getDefaultModuleCachePath(path)) { + lldbassert(SetClangModulesCachePath(FileSpec(path))); + } + + path.clear(); + if (llvm::sys::path::cache_directory(path)) { + llvm::sys::path::append(path, "lldb"); + llvm::sys::path::append(path, "IndexCache"); + lldbassert(SetLLDBIndexCachePath(FileSpec(path))); + } + +} + +bool ModuleListProperties::GetEnableExternalLookup() const { + const uint32_t idx = ePropertyEnableExternalLookup; + return GetPropertyAtIndexAs<bool>( + idx, g_modulelist_properties[idx].default_uint_value != 0); +} + +bool ModuleListProperties::SetEnableExternalLookup(bool new_value) { + return SetPropertyAtIndex(ePropertyEnableExternalLookup, new_value); +} + +SymbolDownload ModuleListProperties::GetSymbolAutoDownload() const { + // Backward compatibility alias. + if (GetPropertyAtIndexAs<bool>(ePropertyEnableBackgroundLookup, false)) + return eSymbolDownloadBackground; + + const uint32_t idx = ePropertyAutoDownload; + return GetPropertyAtIndexAs<lldb::SymbolDownload>( + idx, static_cast<lldb::SymbolDownload>( + g_modulelist_properties[idx].default_uint_value)); +} + +FileSpec ModuleListProperties::GetClangModulesCachePath() const { + const uint32_t idx = ePropertyClangModulesCachePath; + return GetPropertyAtIndexAs<FileSpec>(idx, {}); +} + +bool ModuleListProperties::SetClangModulesCachePath(const FileSpec &path) { + const uint32_t idx = ePropertyClangModulesCachePath; + return SetPropertyAtIndex(idx, path); +} + +FileSpec ModuleListProperties::GetLLDBIndexCachePath() const { + const uint32_t idx = ePropertyLLDBIndexCachePath; + return GetPropertyAtIndexAs<FileSpec>(idx, {}); +} + +bool ModuleListProperties::SetLLDBIndexCachePath(const FileSpec &path) { + const uint32_t idx = ePropertyLLDBIndexCachePath; + return SetPropertyAtIndex(idx, path); +} + +bool ModuleListProperties::GetEnableLLDBIndexCache() const { + const uint32_t idx = ePropertyEnableLLDBIndexCache; + return GetPropertyAtIndexAs<bool>( + idx, g_modulelist_properties[idx].default_uint_value != 0); +} + +bool ModuleListProperties::SetEnableLLDBIndexCache(bool new_value) { + return SetPropertyAtIndex(ePropertyEnableLLDBIndexCache, new_value); +} + +uint64_t ModuleListProperties::GetLLDBIndexCacheMaxByteSize() { + const uint32_t idx = ePropertyLLDBIndexCacheMaxByteSize; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_modulelist_properties[idx].default_uint_value); +} + +uint64_t ModuleListProperties::GetLLDBIndexCacheMaxPercent() { + const uint32_t idx = ePropertyLLDBIndexCacheMaxPercent; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_modulelist_properties[idx].default_uint_value); +} + +uint64_t ModuleListProperties::GetLLDBIndexCacheExpirationDays() { + const uint32_t idx = ePropertyLLDBIndexCacheExpirationDays; + return GetPropertyAtIndexAs<uint64_t>( + idx, g_modulelist_properties[idx].default_uint_value); +} + +void ModuleListProperties::UpdateSymlinkMappings() { + FileSpecList list = + GetPropertyAtIndexAs<FileSpecList>(ePropertySymLinkPaths, {}); + llvm::sys::ScopedWriter lock(m_symlink_paths_mutex); + const bool notify = false; + m_symlink_paths.Clear(notify); + for (auto symlink : list) { + FileSpec resolved; + Status status = FileSystem::Instance().Readlink(symlink, resolved); + if (status.Success()) + m_symlink_paths.Append(symlink.GetPath(), resolved.GetPath(), notify); + } +} + +PathMappingList ModuleListProperties::GetSymlinkMappings() const { + llvm::sys::ScopedReader lock(m_symlink_paths_mutex); + return m_symlink_paths; +} + +bool ModuleListProperties::GetLoadSymbolOnDemand() { + const uint32_t idx = ePropertyLoadSymbolOnDemand; + return GetPropertyAtIndexAs<bool>( + idx, g_modulelist_properties[idx].default_uint_value != 0); +} + +ModuleList::ModuleList() : m_modules(), m_modules_mutex() {} + +ModuleList::ModuleList(const ModuleList &rhs) : m_modules(), m_modules_mutex() { + std::lock_guard<std::recursive_mutex> lhs_guard(m_modules_mutex); + std::lock_guard<std::recursive_mutex> rhs_guard(rhs.m_modules_mutex); + m_modules = rhs.m_modules; +} + +ModuleList::ModuleList(ModuleList::Notifier *notifier) + : m_modules(), m_modules_mutex(), m_notifier(notifier) {} + +const ModuleList &ModuleList::operator=(const ModuleList &rhs) { + if (this != &rhs) { + std::lock(m_modules_mutex, rhs.m_modules_mutex); + std::lock_guard<std::recursive_mutex> lhs_guard(m_modules_mutex, + std::adopt_lock); + std::lock_guard<std::recursive_mutex> rhs_guard(rhs.m_modules_mutex, + std::adopt_lock); + m_modules = rhs.m_modules; + } + return *this; +} + +ModuleList::~ModuleList() = default; + +void ModuleList::AppendImpl(const ModuleSP &module_sp, bool use_notifier) { + if (module_sp) { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + // We are required to keep the first element of the Module List as the + // executable module. So check here and if the first module is NOT an + // but the new one is, we insert this module at the beginning, rather than + // at the end. + // We don't need to do any of this if the list is empty: + if (m_modules.empty()) { + m_modules.push_back(module_sp); + } else { + // Since producing the ObjectFile may take some work, first check the 0th + // element, and only if that's NOT an executable look at the incoming + // ObjectFile. That way in the normal case we only look at the element + // 0 ObjectFile. + const bool elem_zero_is_executable + = m_modules[0]->GetObjectFile()->GetType() + == ObjectFile::Type::eTypeExecutable; + lldb_private::ObjectFile *obj = module_sp->GetObjectFile(); + if (!elem_zero_is_executable && obj + && obj->GetType() == ObjectFile::Type::eTypeExecutable) { + m_modules.insert(m_modules.begin(), module_sp); + } else { + m_modules.push_back(module_sp); + } + } + if (use_notifier && m_notifier) + m_notifier->NotifyModuleAdded(*this, module_sp); + } +} + +void ModuleList::Append(const ModuleSP &module_sp, bool notify) { + AppendImpl(module_sp, notify); +} + +void ModuleList::ReplaceEquivalent( + const ModuleSP &module_sp, + llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules) { + if (module_sp) { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + + // First remove any equivalent modules. Equivalent modules are modules + // whose path, platform path and architecture match. + ModuleSpec equivalent_module_spec(module_sp->GetFileSpec(), + module_sp->GetArchitecture()); + equivalent_module_spec.GetPlatformFileSpec() = + module_sp->GetPlatformFileSpec(); + + size_t idx = 0; + while (idx < m_modules.size()) { + ModuleSP test_module_sp(m_modules[idx]); + if (test_module_sp->MatchesModuleSpec(equivalent_module_spec)) { + if (old_modules) + old_modules->push_back(test_module_sp); + RemoveImpl(m_modules.begin() + idx); + } else { + ++idx; + } + } + // Now add the new module to the list + Append(module_sp); + } +} + +bool ModuleList::AppendIfNeeded(const ModuleSP &new_module, bool notify) { + if (new_module) { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + for (const ModuleSP &module_sp : m_modules) { + if (module_sp.get() == new_module.get()) + return false; // Already in the list + } + // Only push module_sp on the list if it wasn't already in there. + Append(new_module, notify); + return true; + } + return false; +} + +void ModuleList::Append(const ModuleList &module_list) { + for (auto pos : module_list.m_modules) + Append(pos); +} + +bool ModuleList::AppendIfNeeded(const ModuleList &module_list) { + bool any_in = false; + for (auto pos : module_list.m_modules) { + if (AppendIfNeeded(pos)) + any_in = true; + } + return any_in; +} + +bool ModuleList::RemoveImpl(const ModuleSP &module_sp, bool use_notifier) { + if (module_sp) { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + collection::iterator pos, end = m_modules.end(); + for (pos = m_modules.begin(); pos != end; ++pos) { + if (pos->get() == module_sp.get()) { + m_modules.erase(pos); + if (use_notifier && m_notifier) + m_notifier->NotifyModuleRemoved(*this, module_sp); + return true; + } + } + } + return false; +} + +ModuleList::collection::iterator +ModuleList::RemoveImpl(ModuleList::collection::iterator pos, + bool use_notifier) { + ModuleSP module_sp(*pos); + collection::iterator retval = m_modules.erase(pos); + if (use_notifier && m_notifier) + m_notifier->NotifyModuleRemoved(*this, module_sp); + return retval; +} + +bool ModuleList::Remove(const ModuleSP &module_sp, bool notify) { + return RemoveImpl(module_sp, notify); +} + +bool ModuleList::ReplaceModule(const lldb::ModuleSP &old_module_sp, + const lldb::ModuleSP &new_module_sp) { + if (!RemoveImpl(old_module_sp, false)) + return false; + AppendImpl(new_module_sp, false); + if (m_notifier) + m_notifier->NotifyModuleUpdated(*this, old_module_sp, new_module_sp); + return true; +} + +bool ModuleList::RemoveIfOrphaned(const Module *module_ptr) { + if (module_ptr) { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + collection::iterator pos, end = m_modules.end(); + for (pos = m_modules.begin(); pos != end; ++pos) { + if (pos->get() == module_ptr) { + if (pos->use_count() == 1) { + pos = RemoveImpl(pos); + return true; + } else + return false; + } + } + } + return false; +} + +size_t ModuleList::RemoveOrphans(bool mandatory) { + std::unique_lock<std::recursive_mutex> lock(m_modules_mutex, std::defer_lock); + + if (mandatory) { + lock.lock(); + } else { + // Not mandatory, remove orphans if we can get the mutex + if (!lock.try_lock()) + return 0; + } + size_t remove_count = 0; + // Modules might hold shared pointers to other modules, so removing one + // module might make other modules orphans. Keep removing modules until + // there are no further modules that can be removed. + bool made_progress = true; + while (made_progress) { + // Keep track if we make progress this iteration. + made_progress = false; + collection::iterator pos = m_modules.begin(); + while (pos != m_modules.end()) { + if (pos->use_count() == 1) { + pos = RemoveImpl(pos); + ++remove_count; + // We did make progress. + made_progress = true; + } else { + ++pos; + } + } + } + return remove_count; +} + +size_t ModuleList::Remove(ModuleList &module_list) { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + size_t num_removed = 0; + collection::iterator pos, end = module_list.m_modules.end(); + for (pos = module_list.m_modules.begin(); pos != end; ++pos) { + if (Remove(*pos, false /* notify */)) + ++num_removed; + } + if (m_notifier) + m_notifier->NotifyModulesRemoved(module_list); + return num_removed; +} + +void ModuleList::Clear() { ClearImpl(); } + +void ModuleList::Destroy() { ClearImpl(); } + +void ModuleList::ClearImpl(bool use_notifier) { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + if (use_notifier && m_notifier) + m_notifier->NotifyWillClearList(*this); + m_modules.clear(); +} + +Module *ModuleList::GetModulePointerAtIndex(size_t idx) const { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + if (idx < m_modules.size()) + return m_modules[idx].get(); + return nullptr; +} + +ModuleSP ModuleList::GetModuleAtIndex(size_t idx) const { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + return GetModuleAtIndexUnlocked(idx); +} + +ModuleSP ModuleList::GetModuleAtIndexUnlocked(size_t idx) const { + ModuleSP module_sp; + if (idx < m_modules.size()) + module_sp = m_modules[idx]; + return module_sp; +} + +void ModuleList::FindFunctions(ConstString name, + FunctionNameType name_type_mask, + const ModuleFunctionSearchOptions &options, + SymbolContextList &sc_list) const { + const size_t old_size = sc_list.GetSize(); + + if (name_type_mask & eFunctionNameTypeAuto) { + Module::LookupInfo lookup_info(name, name_type_mask, eLanguageTypeUnknown); + + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + for (const ModuleSP &module_sp : m_modules) { + module_sp->FindFunctions(lookup_info, CompilerDeclContext(), options, + sc_list); + } + + const size_t new_size = sc_list.GetSize(); + + if (old_size < new_size) + lookup_info.Prune(sc_list, old_size); + } else { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + for (const ModuleSP &module_sp : m_modules) { + module_sp->FindFunctions(name, CompilerDeclContext(), name_type_mask, + options, sc_list); + } + } +} + +void ModuleList::FindFunctionSymbols(ConstString name, + lldb::FunctionNameType name_type_mask, + SymbolContextList &sc_list) { + const size_t old_size = sc_list.GetSize(); + + if (name_type_mask & eFunctionNameTypeAuto) { + Module::LookupInfo lookup_info(name, name_type_mask, eLanguageTypeUnknown); + + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + for (const ModuleSP &module_sp : m_modules) { + module_sp->FindFunctionSymbols(lookup_info.GetLookupName(), + lookup_info.GetNameTypeMask(), sc_list); + } + + const size_t new_size = sc_list.GetSize(); + + if (old_size < new_size) + lookup_info.Prune(sc_list, old_size); + } else { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + for (const ModuleSP &module_sp : m_modules) { + module_sp->FindFunctionSymbols(name, name_type_mask, sc_list); + } + } +} + +void ModuleList::FindFunctions(const RegularExpression &name, + const ModuleFunctionSearchOptions &options, + SymbolContextList &sc_list) { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + for (const ModuleSP &module_sp : m_modules) + module_sp->FindFunctions(name, options, sc_list); +} + +void ModuleList::FindCompileUnits(const FileSpec &path, + SymbolContextList &sc_list) const { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + for (const ModuleSP &module_sp : m_modules) + module_sp->FindCompileUnits(path, sc_list); +} + +void ModuleList::FindGlobalVariables(ConstString name, size_t max_matches, + VariableList &variable_list) const { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + for (const ModuleSP &module_sp : m_modules) { + module_sp->FindGlobalVariables(name, CompilerDeclContext(), max_matches, + variable_list); + } +} + +void ModuleList::FindGlobalVariables(const RegularExpression ®ex, + size_t max_matches, + VariableList &variable_list) const { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + for (const ModuleSP &module_sp : m_modules) + module_sp->FindGlobalVariables(regex, max_matches, variable_list); +} + +void ModuleList::FindSymbolsWithNameAndType(ConstString name, + SymbolType symbol_type, + SymbolContextList &sc_list) const { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + for (const ModuleSP &module_sp : m_modules) + module_sp->FindSymbolsWithNameAndType(name, symbol_type, sc_list); +} + +void ModuleList::FindSymbolsMatchingRegExAndType( + const RegularExpression ®ex, lldb::SymbolType symbol_type, + SymbolContextList &sc_list) const { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + for (const ModuleSP &module_sp : m_modules) + module_sp->FindSymbolsMatchingRegExAndType(regex, symbol_type, sc_list); +} + +void ModuleList::FindModules(const ModuleSpec &module_spec, + ModuleList &matching_module_list) const { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + for (const ModuleSP &module_sp : m_modules) { + if (module_sp->MatchesModuleSpec(module_spec)) + matching_module_list.Append(module_sp); + } +} + +ModuleSP ModuleList::FindModule(const Module *module_ptr) const { + ModuleSP module_sp; + + // Scope for "locker" + { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + collection::const_iterator pos, end = m_modules.end(); + + for (pos = m_modules.begin(); pos != end; ++pos) { + if ((*pos).get() == module_ptr) { + module_sp = (*pos); + break; + } + } + } + return module_sp; +} + +ModuleSP ModuleList::FindModule(const UUID &uuid) const { + ModuleSP module_sp; + + if (uuid.IsValid()) { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + collection::const_iterator pos, end = m_modules.end(); + + for (pos = m_modules.begin(); pos != end; ++pos) { + if ((*pos)->GetUUID() == uuid) { + module_sp = (*pos); + break; + } + } + } + return module_sp; +} + +void ModuleList::FindTypes(Module *search_first, const TypeQuery &query, + TypeResults &results) const { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + if (search_first) { + search_first->FindTypes(query, results); + if (results.Done(query)) + return; + } + for (const auto &module_sp : m_modules) { + if (search_first != module_sp.get()) { + module_sp->FindTypes(query, results); + if (results.Done(query)) + return; + } + } +} + +bool ModuleList::FindSourceFile(const FileSpec &orig_spec, + FileSpec &new_spec) const { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + for (const ModuleSP &module_sp : m_modules) { + if (module_sp->FindSourceFile(orig_spec, new_spec)) + return true; + } + return false; +} + +void ModuleList::FindAddressesForLine(const lldb::TargetSP target_sp, + const FileSpec &file, uint32_t line, + Function *function, + std::vector<Address> &output_local, + std::vector<Address> &output_extern) { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + for (const ModuleSP &module_sp : m_modules) { + module_sp->FindAddressesForLine(target_sp, file, line, function, + output_local, output_extern); + } +} + +ModuleSP ModuleList::FindFirstModule(const ModuleSpec &module_spec) const { + ModuleSP module_sp; + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + collection::const_iterator pos, end = m_modules.end(); + for (pos = m_modules.begin(); pos != end; ++pos) { + ModuleSP module_sp(*pos); + if (module_sp->MatchesModuleSpec(module_spec)) + return module_sp; + } + return module_sp; +} + +size_t ModuleList::GetSize() const { + size_t size = 0; + { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + size = m_modules.size(); + } + return size; +} + +void ModuleList::Dump(Stream *s) const { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + for (const ModuleSP &module_sp : m_modules) + module_sp->Dump(s); +} + +void ModuleList::LogUUIDAndPaths(Log *log, const char *prefix_cstr) { + if (log != nullptr) { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + collection::const_iterator pos, begin = m_modules.begin(), + end = m_modules.end(); + for (pos = begin; pos != end; ++pos) { + Module *module = pos->get(); + const FileSpec &module_file_spec = module->GetFileSpec(); + LLDB_LOGF(log, "%s[%u] %s (%s) \"%s\"", prefix_cstr ? prefix_cstr : "", + (uint32_t)std::distance(begin, pos), + module->GetUUID().GetAsString().c_str(), + module->GetArchitecture().GetArchitectureName(), + module_file_spec.GetPath().c_str()); + } + } +} + +bool ModuleList::ResolveFileAddress(lldb::addr_t vm_addr, + Address &so_addr) const { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + for (const ModuleSP &module_sp : m_modules) { + if (module_sp->ResolveFileAddress(vm_addr, so_addr)) + return true; + } + + return false; +} + +uint32_t +ModuleList::ResolveSymbolContextForAddress(const Address &so_addr, + SymbolContextItem resolve_scope, + SymbolContext &sc) const { + // The address is already section offset so it has a module + uint32_t resolved_flags = 0; + ModuleSP module_sp(so_addr.GetModule()); + if (module_sp) { + resolved_flags = + module_sp->ResolveSymbolContextForAddress(so_addr, resolve_scope, sc); + } else { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + collection::const_iterator pos, end = m_modules.end(); + for (pos = m_modules.begin(); pos != end; ++pos) { + resolved_flags = + (*pos)->ResolveSymbolContextForAddress(so_addr, resolve_scope, sc); + if (resolved_flags != 0) + break; + } + } + + return resolved_flags; +} + +uint32_t ModuleList::ResolveSymbolContextForFilePath( + const char *file_path, uint32_t line, bool check_inlines, + SymbolContextItem resolve_scope, SymbolContextList &sc_list) const { + FileSpec file_spec(file_path); + return ResolveSymbolContextsForFileSpec(file_spec, line, check_inlines, + resolve_scope, sc_list); +} + +uint32_t ModuleList::ResolveSymbolContextsForFileSpec( + const FileSpec &file_spec, uint32_t line, bool check_inlines, + SymbolContextItem resolve_scope, SymbolContextList &sc_list) const { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + for (const ModuleSP &module_sp : m_modules) { + module_sp->ResolveSymbolContextsForFileSpec(file_spec, line, check_inlines, + resolve_scope, sc_list); + } + + return sc_list.GetSize(); +} + +size_t ModuleList::GetIndexForModule(const Module *module) const { + if (module) { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + collection::const_iterator pos; + collection::const_iterator begin = m_modules.begin(); + collection::const_iterator end = m_modules.end(); + for (pos = begin; pos != end; ++pos) { + if ((*pos).get() == module) + return std::distance(begin, pos); + } + } + return LLDB_INVALID_INDEX32; +} + +namespace { +struct SharedModuleListInfo { + ModuleList module_list; + ModuleListProperties module_list_properties; +}; +} +static SharedModuleListInfo &GetSharedModuleListInfo() +{ + static SharedModuleListInfo *g_shared_module_list_info = nullptr; + static llvm::once_flag g_once_flag; + llvm::call_once(g_once_flag, []() { + // NOTE: Intentionally leak the module list so a program doesn't have to + // cleanup all modules and object files as it exits. This just wastes time + // doing a bunch of cleanup that isn't required. + if (g_shared_module_list_info == nullptr) + g_shared_module_list_info = new SharedModuleListInfo(); + }); + return *g_shared_module_list_info; +} + +static ModuleList &GetSharedModuleList() { + return GetSharedModuleListInfo().module_list; +} + +ModuleListProperties &ModuleList::GetGlobalModuleListProperties() { + return GetSharedModuleListInfo().module_list_properties; +} + +bool ModuleList::ModuleIsInCache(const Module *module_ptr) { + if (module_ptr) { + ModuleList &shared_module_list = GetSharedModuleList(); + return shared_module_list.FindModule(module_ptr).get() != nullptr; + } + return false; +} + +void ModuleList::FindSharedModules(const ModuleSpec &module_spec, + ModuleList &matching_module_list) { + GetSharedModuleList().FindModules(module_spec, matching_module_list); +} + +lldb::ModuleSP ModuleList::FindSharedModule(const UUID &uuid) { + return GetSharedModuleList().FindModule(uuid); +} + +size_t ModuleList::RemoveOrphanSharedModules(bool mandatory) { + return GetSharedModuleList().RemoveOrphans(mandatory); +} + +Status +ModuleList::GetSharedModule(const ModuleSpec &module_spec, ModuleSP &module_sp, + const FileSpecList *module_search_paths_ptr, + llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, + bool *did_create_ptr, bool always_create) { + ModuleList &shared_module_list = GetSharedModuleList(); + std::lock_guard<std::recursive_mutex> guard( + shared_module_list.m_modules_mutex); + char path[PATH_MAX]; + + Status error; + + module_sp.reset(); + + if (did_create_ptr) + *did_create_ptr = false; + + const UUID *uuid_ptr = module_spec.GetUUIDPtr(); + const FileSpec &module_file_spec = module_spec.GetFileSpec(); + const ArchSpec &arch = module_spec.GetArchitecture(); + + // Make sure no one else can try and get or create a module while this + // function is actively working on it by doing an extra lock on the global + // mutex list. + if (!always_create) { + ModuleList matching_module_list; + shared_module_list.FindModules(module_spec, matching_module_list); + const size_t num_matching_modules = matching_module_list.GetSize(); + + if (num_matching_modules > 0) { + for (size_t module_idx = 0; module_idx < num_matching_modules; + ++module_idx) { + module_sp = matching_module_list.GetModuleAtIndex(module_idx); + + // Make sure the file for the module hasn't been modified + if (module_sp->FileHasChanged()) { + if (old_modules) + old_modules->push_back(module_sp); + + Log *log = GetLog(LLDBLog::Modules); + if (log != nullptr) + LLDB_LOGF( + log, "%p '%s' module changed: removing from global module list", + static_cast<void *>(module_sp.get()), + module_sp->GetFileSpec().GetFilename().GetCString()); + + shared_module_list.Remove(module_sp); + module_sp.reset(); + } else { + // The module matches and the module was not modified from when it + // was last loaded. + return error; + } + } + } + } + + if (module_sp) + return error; + + module_sp = std::make_shared<Module>(module_spec); + // Make sure there are a module and an object file since we can specify a + // valid file path with an architecture that might not be in that file. By + // getting the object file we can guarantee that the architecture matches + if (module_sp->GetObjectFile()) { + // If we get in here we got the correct arch, now we just need to verify + // the UUID if one was given + if (uuid_ptr && *uuid_ptr != module_sp->GetUUID()) { + module_sp.reset(); + } else { + if (module_sp->GetObjectFile() && + module_sp->GetObjectFile()->GetType() == + ObjectFile::eTypeStubLibrary) { + module_sp.reset(); + } else { + if (did_create_ptr) { + *did_create_ptr = true; + } + + shared_module_list.ReplaceEquivalent(module_sp, old_modules); + return error; + } + } + } else { + module_sp.reset(); + } + + if (module_search_paths_ptr) { + const auto num_directories = module_search_paths_ptr->GetSize(); + for (size_t idx = 0; idx < num_directories; ++idx) { + auto search_path_spec = module_search_paths_ptr->GetFileSpecAtIndex(idx); + FileSystem::Instance().Resolve(search_path_spec); + namespace fs = llvm::sys::fs; + if (!FileSystem::Instance().IsDirectory(search_path_spec)) + continue; + search_path_spec.AppendPathComponent( + module_spec.GetFileSpec().GetFilename().GetStringRef()); + if (!FileSystem::Instance().Exists(search_path_spec)) + continue; + + auto resolved_module_spec(module_spec); + resolved_module_spec.GetFileSpec() = search_path_spec; + module_sp = std::make_shared<Module>(resolved_module_spec); + if (module_sp->GetObjectFile()) { + // If we get in here we got the correct arch, now we just need to + // verify the UUID if one was given + if (uuid_ptr && *uuid_ptr != module_sp->GetUUID()) { + module_sp.reset(); + } else { + if (module_sp->GetObjectFile()->GetType() == + ObjectFile::eTypeStubLibrary) { + module_sp.reset(); + } else { + if (did_create_ptr) + *did_create_ptr = true; + + shared_module_list.ReplaceEquivalent(module_sp, old_modules); + return Status(); + } + } + } else { + module_sp.reset(); + } + } + } + + // Either the file didn't exist where at the path, or no path was given, so + // we now have to use more extreme measures to try and find the appropriate + // module. + + // Fixup the incoming path in case the path points to a valid file, yet the + // arch or UUID (if one was passed in) don't match. + ModuleSpec located_binary_modulespec = + PluginManager::LocateExecutableObjectFile(module_spec); + + // Don't look for the file if it appears to be the same one we already + // checked for above... + if (located_binary_modulespec.GetFileSpec() != module_file_spec) { + if (!FileSystem::Instance().Exists( + located_binary_modulespec.GetFileSpec())) { + located_binary_modulespec.GetFileSpec().GetPath(path, sizeof(path)); + if (path[0] == '\0') + module_file_spec.GetPath(path, sizeof(path)); + // How can this check ever be true? This branch it is false, and we + // haven't modified file_spec. + if (FileSystem::Instance().Exists( + located_binary_modulespec.GetFileSpec())) { + std::string uuid_str; + if (uuid_ptr && uuid_ptr->IsValid()) + uuid_str = uuid_ptr->GetAsString(); + + if (arch.IsValid()) { + if (!uuid_str.empty()) + error.SetErrorStringWithFormat( + "'%s' does not contain the %s architecture and UUID %s", path, + arch.GetArchitectureName(), uuid_str.c_str()); + else + error.SetErrorStringWithFormat( + "'%s' does not contain the %s architecture.", path, + arch.GetArchitectureName()); + } + } else { + error.SetErrorStringWithFormat("'%s' does not exist", path); + } + if (error.Fail()) + module_sp.reset(); + return error; + } + + // Make sure no one else can try and get or create a module while this + // function is actively working on it by doing an extra lock on the global + // mutex list. + ModuleSpec platform_module_spec(module_spec); + platform_module_spec.GetFileSpec() = + located_binary_modulespec.GetFileSpec(); + platform_module_spec.GetPlatformFileSpec() = + located_binary_modulespec.GetFileSpec(); + platform_module_spec.GetSymbolFileSpec() = + located_binary_modulespec.GetSymbolFileSpec(); + ModuleList matching_module_list; + shared_module_list.FindModules(platform_module_spec, matching_module_list); + if (!matching_module_list.IsEmpty()) { + module_sp = matching_module_list.GetModuleAtIndex(0); + + // If we didn't have a UUID in mind when looking for the object file, + // then we should make sure the modification time hasn't changed! + if (platform_module_spec.GetUUIDPtr() == nullptr) { + auto file_spec_mod_time = FileSystem::Instance().GetModificationTime( + located_binary_modulespec.GetFileSpec()); + if (file_spec_mod_time != llvm::sys::TimePoint<>()) { + if (file_spec_mod_time != module_sp->GetModificationTime()) { + if (old_modules) + old_modules->push_back(module_sp); + shared_module_list.Remove(module_sp); + module_sp.reset(); + } + } + } + } + + if (!module_sp) { + module_sp = std::make_shared<Module>(platform_module_spec); + // Make sure there are a module and an object file since we can specify a + // valid file path with an architecture that might not be in that file. + // By getting the object file we can guarantee that the architecture + // matches + if (module_sp && module_sp->GetObjectFile()) { + if (module_sp->GetObjectFile()->GetType() == + ObjectFile::eTypeStubLibrary) { + module_sp.reset(); + } else { + if (did_create_ptr) + *did_create_ptr = true; + + shared_module_list.ReplaceEquivalent(module_sp, old_modules); + } + } else { + located_binary_modulespec.GetFileSpec().GetPath(path, sizeof(path)); + + if (located_binary_modulespec.GetFileSpec()) { + if (arch.IsValid()) + error.SetErrorStringWithFormat( + "unable to open %s architecture in '%s'", + arch.GetArchitectureName(), path); + else + error.SetErrorStringWithFormat("unable to open '%s'", path); + } else { + std::string uuid_str; + if (uuid_ptr && uuid_ptr->IsValid()) + uuid_str = uuid_ptr->GetAsString(); + + if (!uuid_str.empty()) + error.SetErrorStringWithFormat( + "cannot locate a module for UUID '%s'", uuid_str.c_str()); + else + error.SetErrorString("cannot locate a module"); + } + } + } + } + + return error; +} + +bool ModuleList::RemoveSharedModule(lldb::ModuleSP &module_sp) { + return GetSharedModuleList().Remove(module_sp); +} + +bool ModuleList::RemoveSharedModuleIfOrphaned(const Module *module_ptr) { + return GetSharedModuleList().RemoveIfOrphaned(module_ptr); +} + +bool ModuleList::LoadScriptingResourcesInTarget(Target *target, + std::list<Status> &errors, + Stream &feedback_stream, + bool continue_on_error) { + if (!target) + return false; + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + for (auto module : m_modules) { + Status error; + if (module) { + if (!module->LoadScriptingResourceInTarget(target, error, + feedback_stream)) { + if (error.Fail() && error.AsCString()) { + error.SetErrorStringWithFormat("unable to load scripting data for " + "module %s - error reported was %s", + module->GetFileSpec() + .GetFileNameStrippingExtension() + .GetCString(), + error.AsCString()); + errors.push_back(error); + + if (!continue_on_error) + return false; + } + } + } + } + return errors.empty(); +} + +void ModuleList::ForEach( + std::function<bool(const ModuleSP &module_sp)> const &callback) const { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + for (const auto &module_sp : m_modules) { + assert(module_sp != nullptr); + // If the callback returns false, then stop iterating and break out + if (!callback(module_sp)) + break; + } +} + +bool ModuleList::AnyOf( + std::function<bool(lldb_private::Module &module_sp)> const &callback) + const { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + for (const auto &module_sp : m_modules) { + assert(module_sp != nullptr); + if (callback(*module_sp)) + return true; + } + + return false; +} + + +void ModuleList::Swap(ModuleList &other) { + // scoped_lock locks both mutexes at once. + std::scoped_lock<std::recursive_mutex, std::recursive_mutex> lock( + m_modules_mutex, other.m_modules_mutex); + m_modules.swap(other.m_modules); +} diff --git a/contrib/llvm-project/lldb/source/Core/Opcode.cpp b/contrib/llvm-project/lldb/source/Core/Opcode.cpp new file mode 100644 index 000000000000..3e30d98975d8 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/Opcode.cpp @@ -0,0 +1,140 @@ +//===-- Opcode.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/Core/Opcode.h" + +#include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/DataExtractor.h" +#include "lldb/Utility/Endian.h" +#include "lldb/Utility/Stream.h" +#include "lldb/lldb-forward.h" + +#include <memory> + +#include <cinttypes> + +using namespace lldb; +using namespace lldb_private; + +int Opcode::Dump(Stream *s, uint32_t min_byte_width) { + const uint32_t previous_bytes = s->GetWrittenBytes(); + switch (m_type) { + case Opcode::eTypeInvalid: + s->PutCString("<invalid>"); + break; + case Opcode::eType8: + s->Printf("0x%2.2x", m_data.inst8); + break; + case Opcode::eType16: + s->Printf("0x%4.4x", m_data.inst16); + break; + case Opcode::eType16_2: + case Opcode::eType32: + s->Printf("0x%8.8x", m_data.inst32); + break; + + case Opcode::eType64: + s->Printf("0x%16.16" PRIx64, m_data.inst64); + break; + + case Opcode::eTypeBytes: + for (uint32_t i = 0; i < m_data.inst.length; ++i) { + if (i > 0) + s->PutChar(' '); + s->Printf("%2.2x", m_data.inst.bytes[i]); + } + break; + } + + uint32_t bytes_written_so_far = s->GetWrittenBytes() - previous_bytes; + // Add spaces to make sure bytes display comes out even in case opcodes aren't + // all the same size. + if (bytes_written_so_far < min_byte_width) + s->Printf("%*s", min_byte_width - bytes_written_so_far, ""); + return s->GetWrittenBytes() - previous_bytes; +} + +lldb::ByteOrder Opcode::GetDataByteOrder() const { + if (m_byte_order != eByteOrderInvalid) { + return m_byte_order; + } + switch (m_type) { + case Opcode::eTypeInvalid: + break; + case Opcode::eType8: + case Opcode::eType16: + case Opcode::eType16_2: + case Opcode::eType32: + case Opcode::eType64: + return endian::InlHostByteOrder(); + case Opcode::eTypeBytes: + break; + } + return eByteOrderInvalid; +} + +uint32_t Opcode::GetData(DataExtractor &data) const { + uint32_t byte_size = GetByteSize(); + uint8_t swap_buf[8]; + const void *buf = nullptr; + + if (byte_size > 0) { + if (!GetEndianSwap()) { + if (m_type == Opcode::eType16_2) { + // 32 bit thumb instruction, we need to sizzle this a bit + swap_buf[0] = m_data.inst.bytes[2]; + swap_buf[1] = m_data.inst.bytes[3]; + swap_buf[2] = m_data.inst.bytes[0]; + swap_buf[3] = m_data.inst.bytes[1]; + buf = swap_buf; + } else { + buf = GetOpcodeDataBytes(); + } + } else { + switch (m_type) { + case Opcode::eTypeInvalid: + break; + case Opcode::eType8: + buf = GetOpcodeDataBytes(); + break; + case Opcode::eType16: + *(uint16_t *)swap_buf = llvm::byteswap<uint16_t>(m_data.inst16); + buf = swap_buf; + break; + case Opcode::eType16_2: + swap_buf[0] = m_data.inst.bytes[1]; + swap_buf[1] = m_data.inst.bytes[0]; + swap_buf[2] = m_data.inst.bytes[3]; + swap_buf[3] = m_data.inst.bytes[2]; + buf = swap_buf; + break; + case Opcode::eType32: + *(uint32_t *)swap_buf = llvm::byteswap<uint32_t>(m_data.inst32); + buf = swap_buf; + break; + case Opcode::eType64: + *(uint32_t *)swap_buf = llvm::byteswap<uint64_t>(m_data.inst64); + buf = swap_buf; + break; + case Opcode::eTypeBytes: + buf = GetOpcodeDataBytes(); + break; + } + } + } + if (buf != nullptr) { + DataBufferSP buffer_sp; + + buffer_sp = std::make_shared<DataBufferHeap>(buf, byte_size); + data.SetByteOrder(GetDataByteOrder()); + data.SetData(buffer_sp); + return byte_size; + } + data.Clear(); + return 0; +} diff --git a/contrib/llvm-project/lldb/source/Core/PluginManager.cpp b/contrib/llvm-project/lldb/source/Core/PluginManager.cpp new file mode 100644 index 000000000000..759ef3a8afe0 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/PluginManager.cpp @@ -0,0 +1,1852 @@ +//===-- PluginManager.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/Core/PluginManager.h" + +#include "lldb/Core/Debugger.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Interpreter/OptionValueProperties.h" +#include "lldb/Symbol/SaveCoreOptions.h" +#include "lldb/Target/Process.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/StringList.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/DynamicLibrary.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/raw_ostream.h" +#include <cassert> +#include <map> +#include <memory> +#include <mutex> +#include <string> +#include <utility> +#include <vector> +#if defined(_WIN32) +#include "lldb/Host/windows/PosixApi.h" +#endif + +using namespace lldb; +using namespace lldb_private; + +typedef bool (*PluginInitCallback)(); +typedef void (*PluginTermCallback)(); + +struct PluginInfo { + PluginInfo() = default; + + llvm::sys::DynamicLibrary library; + PluginInitCallback plugin_init_callback = nullptr; + PluginTermCallback plugin_term_callback = nullptr; +}; + +typedef std::map<FileSpec, PluginInfo> PluginTerminateMap; + +static std::recursive_mutex &GetPluginMapMutex() { + static std::recursive_mutex g_plugin_map_mutex; + return g_plugin_map_mutex; +} + +static PluginTerminateMap &GetPluginMap() { + static PluginTerminateMap g_plugin_map; + return g_plugin_map; +} + +static bool PluginIsLoaded(const FileSpec &plugin_file_spec) { + std::lock_guard<std::recursive_mutex> guard(GetPluginMapMutex()); + PluginTerminateMap &plugin_map = GetPluginMap(); + return plugin_map.find(plugin_file_spec) != plugin_map.end(); +} + +static void SetPluginInfo(const FileSpec &plugin_file_spec, + const PluginInfo &plugin_info) { + std::lock_guard<std::recursive_mutex> guard(GetPluginMapMutex()); + PluginTerminateMap &plugin_map = GetPluginMap(); + assert(plugin_map.find(plugin_file_spec) == plugin_map.end()); + plugin_map[plugin_file_spec] = plugin_info; +} + +template <typename FPtrTy> static FPtrTy CastToFPtr(void *VPtr) { + return reinterpret_cast<FPtrTy>(VPtr); +} + +static FileSystem::EnumerateDirectoryResult +LoadPluginCallback(void *baton, llvm::sys::fs::file_type ft, + llvm::StringRef path) { + Status error; + + namespace fs = llvm::sys::fs; + // If we have a regular file, a symbolic link or unknown file type, try and + // process the file. We must handle unknown as sometimes the directory + // enumeration might be enumerating a file system that doesn't have correct + // file type information. + if (ft == fs::file_type::regular_file || ft == fs::file_type::symlink_file || + ft == fs::file_type::type_unknown) { + FileSpec plugin_file_spec(path); + FileSystem::Instance().Resolve(plugin_file_spec); + + if (PluginIsLoaded(plugin_file_spec)) + return FileSystem::eEnumerateDirectoryResultNext; + else { + PluginInfo plugin_info; + + std::string pluginLoadError; + plugin_info.library = llvm::sys::DynamicLibrary::getPermanentLibrary( + plugin_file_spec.GetPath().c_str(), &pluginLoadError); + if (plugin_info.library.isValid()) { + bool success = false; + plugin_info.plugin_init_callback = CastToFPtr<PluginInitCallback>( + plugin_info.library.getAddressOfSymbol("LLDBPluginInitialize")); + if (plugin_info.plugin_init_callback) { + // Call the plug-in "bool LLDBPluginInitialize(void)" function + success = plugin_info.plugin_init_callback(); + } + + if (success) { + // It is ok for the "LLDBPluginTerminate" symbol to be nullptr + plugin_info.plugin_term_callback = CastToFPtr<PluginTermCallback>( + plugin_info.library.getAddressOfSymbol("LLDBPluginTerminate")); + } else { + // The initialize function returned FALSE which means the plug-in + // might not be compatible, or might be too new or too old, or might + // not want to run on this machine. Set it to a default-constructed + // instance to invalidate it. + plugin_info = PluginInfo(); + } + + // Regardless of success or failure, cache the plug-in load in our + // plug-in info so we don't try to load it again and again. + SetPluginInfo(plugin_file_spec, plugin_info); + + return FileSystem::eEnumerateDirectoryResultNext; + } + } + } + + if (ft == fs::file_type::directory_file || + ft == fs::file_type::symlink_file || ft == fs::file_type::type_unknown) { + // Try and recurse into anything that a directory or symbolic link. We must + // also do this for unknown as sometimes the directory enumeration might be + // enumerating a file system that doesn't have correct file type + // information. + return FileSystem::eEnumerateDirectoryResultEnter; + } + + return FileSystem::eEnumerateDirectoryResultNext; +} + +void PluginManager::Initialize() { + const bool find_directories = true; + const bool find_files = true; + const bool find_other = true; + char dir_path[PATH_MAX]; + if (FileSpec dir_spec = HostInfo::GetSystemPluginDir()) { + if (FileSystem::Instance().Exists(dir_spec) && + dir_spec.GetPath(dir_path, sizeof(dir_path))) { + FileSystem::Instance().EnumerateDirectory(dir_path, find_directories, + find_files, find_other, + LoadPluginCallback, nullptr); + } + } + + if (FileSpec dir_spec = HostInfo::GetUserPluginDir()) { + if (FileSystem::Instance().Exists(dir_spec) && + dir_spec.GetPath(dir_path, sizeof(dir_path))) { + FileSystem::Instance().EnumerateDirectory(dir_path, find_directories, + find_files, find_other, + LoadPluginCallback, nullptr); + } + } +} + +void PluginManager::Terminate() { + std::lock_guard<std::recursive_mutex> guard(GetPluginMapMutex()); + PluginTerminateMap &plugin_map = GetPluginMap(); + + PluginTerminateMap::const_iterator pos, end = plugin_map.end(); + for (pos = plugin_map.begin(); pos != end; ++pos) { + // Call the plug-in "void LLDBPluginTerminate (void)" function if there is + // one (if the symbol was not nullptr). + if (pos->second.library.isValid()) { + if (pos->second.plugin_term_callback) + pos->second.plugin_term_callback(); + } + } + plugin_map.clear(); +} + +template <typename Callback> struct PluginInstance { + typedef Callback CallbackType; + + PluginInstance() = default; + PluginInstance(llvm::StringRef name, llvm::StringRef description, + Callback create_callback, + DebuggerInitializeCallback debugger_init_callback = nullptr) + : name(name), description(description), create_callback(create_callback), + debugger_init_callback(debugger_init_callback) {} + + llvm::StringRef name; + llvm::StringRef description; + Callback create_callback; + DebuggerInitializeCallback debugger_init_callback; +}; + +template <typename Instance> class PluginInstances { +public: + template <typename... Args> + bool RegisterPlugin(llvm::StringRef name, llvm::StringRef description, + typename Instance::CallbackType callback, + Args &&...args) { + if (!callback) + return false; + assert(!name.empty()); + Instance instance = + Instance(name, description, callback, std::forward<Args>(args)...); + m_instances.push_back(instance); + return false; + } + + bool UnregisterPlugin(typename Instance::CallbackType callback) { + if (!callback) + return false; + auto pos = m_instances.begin(); + auto end = m_instances.end(); + for (; pos != end; ++pos) { + if (pos->create_callback == callback) { + m_instances.erase(pos); + return true; + } + } + return false; + } + + typename Instance::CallbackType GetCallbackAtIndex(uint32_t idx) { + if (Instance *instance = GetInstanceAtIndex(idx)) + return instance->create_callback; + return nullptr; + } + + llvm::StringRef GetDescriptionAtIndex(uint32_t idx) { + if (Instance *instance = GetInstanceAtIndex(idx)) + return instance->description; + return ""; + } + + llvm::StringRef GetNameAtIndex(uint32_t idx) { + if (Instance *instance = GetInstanceAtIndex(idx)) + return instance->name; + return ""; + } + + typename Instance::CallbackType GetCallbackForName(llvm::StringRef name) { + if (name.empty()) + return nullptr; + for (auto &instance : m_instances) { + if (name == instance.name) + return instance.create_callback; + } + return nullptr; + } + + void PerformDebuggerCallback(Debugger &debugger) { + for (auto &instance : m_instances) { + if (instance.debugger_init_callback) + instance.debugger_init_callback(debugger); + } + } + + const std::vector<Instance> &GetInstances() const { return m_instances; } + std::vector<Instance> &GetInstances() { return m_instances; } + + Instance *GetInstanceAtIndex(uint32_t idx) { + if (idx < m_instances.size()) + return &m_instances[idx]; + return nullptr; + } + +private: + std::vector<Instance> m_instances; +}; + +#pragma mark ABI + +typedef PluginInstance<ABICreateInstance> ABIInstance; +typedef PluginInstances<ABIInstance> ABIInstances; + +static ABIInstances &GetABIInstances() { + static ABIInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin(llvm::StringRef name, + llvm::StringRef description, + ABICreateInstance create_callback) { + return GetABIInstances().RegisterPlugin(name, description, create_callback); +} + +bool PluginManager::UnregisterPlugin(ABICreateInstance create_callback) { + return GetABIInstances().UnregisterPlugin(create_callback); +} + +ABICreateInstance PluginManager::GetABICreateCallbackAtIndex(uint32_t idx) { + return GetABIInstances().GetCallbackAtIndex(idx); +} + +#pragma mark Architecture + +typedef PluginInstance<ArchitectureCreateInstance> ArchitectureInstance; +typedef std::vector<ArchitectureInstance> ArchitectureInstances; + +static ArchitectureInstances &GetArchitectureInstances() { + static ArchitectureInstances g_instances; + return g_instances; +} + +void PluginManager::RegisterPlugin(llvm::StringRef name, + llvm::StringRef description, + ArchitectureCreateInstance create_callback) { + GetArchitectureInstances().push_back({name, description, create_callback}); +} + +void PluginManager::UnregisterPlugin( + ArchitectureCreateInstance create_callback) { + auto &instances = GetArchitectureInstances(); + + for (auto pos = instances.begin(), end = instances.end(); pos != end; ++pos) { + if (pos->create_callback == create_callback) { + instances.erase(pos); + return; + } + } + llvm_unreachable("Plugin not found"); +} + +std::unique_ptr<Architecture> +PluginManager::CreateArchitectureInstance(const ArchSpec &arch) { + for (const auto &instances : GetArchitectureInstances()) { + if (auto plugin_up = instances.create_callback(arch)) + return plugin_up; + } + return nullptr; +} + +#pragma mark Disassembler + +typedef PluginInstance<DisassemblerCreateInstance> DisassemblerInstance; +typedef PluginInstances<DisassemblerInstance> DisassemblerInstances; + +static DisassemblerInstances &GetDisassemblerInstances() { + static DisassemblerInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin(llvm::StringRef name, + llvm::StringRef description, + DisassemblerCreateInstance create_callback) { + return GetDisassemblerInstances().RegisterPlugin(name, description, + create_callback); +} + +bool PluginManager::UnregisterPlugin( + DisassemblerCreateInstance create_callback) { + return GetDisassemblerInstances().UnregisterPlugin(create_callback); +} + +DisassemblerCreateInstance +PluginManager::GetDisassemblerCreateCallbackAtIndex(uint32_t idx) { + return GetDisassemblerInstances().GetCallbackAtIndex(idx); +} + +DisassemblerCreateInstance +PluginManager::GetDisassemblerCreateCallbackForPluginName( + llvm::StringRef name) { + return GetDisassemblerInstances().GetCallbackForName(name); +} + +#pragma mark DynamicLoader + +typedef PluginInstance<DynamicLoaderCreateInstance> DynamicLoaderInstance; +typedef PluginInstances<DynamicLoaderInstance> DynamicLoaderInstances; + +static DynamicLoaderInstances &GetDynamicLoaderInstances() { + static DynamicLoaderInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + DynamicLoaderCreateInstance create_callback, + DebuggerInitializeCallback debugger_init_callback) { + return GetDynamicLoaderInstances().RegisterPlugin( + name, description, create_callback, debugger_init_callback); +} + +bool PluginManager::UnregisterPlugin( + DynamicLoaderCreateInstance create_callback) { + return GetDynamicLoaderInstances().UnregisterPlugin(create_callback); +} + +DynamicLoaderCreateInstance +PluginManager::GetDynamicLoaderCreateCallbackAtIndex(uint32_t idx) { + return GetDynamicLoaderInstances().GetCallbackAtIndex(idx); +} + +DynamicLoaderCreateInstance +PluginManager::GetDynamicLoaderCreateCallbackForPluginName( + llvm::StringRef name) { + return GetDynamicLoaderInstances().GetCallbackForName(name); +} + +#pragma mark JITLoader + +typedef PluginInstance<JITLoaderCreateInstance> JITLoaderInstance; +typedef PluginInstances<JITLoaderInstance> JITLoaderInstances; + +static JITLoaderInstances &GetJITLoaderInstances() { + static JITLoaderInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + JITLoaderCreateInstance create_callback, + DebuggerInitializeCallback debugger_init_callback) { + return GetJITLoaderInstances().RegisterPlugin( + name, description, create_callback, debugger_init_callback); +} + +bool PluginManager::UnregisterPlugin(JITLoaderCreateInstance create_callback) { + return GetJITLoaderInstances().UnregisterPlugin(create_callback); +} + +JITLoaderCreateInstance +PluginManager::GetJITLoaderCreateCallbackAtIndex(uint32_t idx) { + return GetJITLoaderInstances().GetCallbackAtIndex(idx); +} + +#pragma mark EmulateInstruction + +typedef PluginInstance<EmulateInstructionCreateInstance> + EmulateInstructionInstance; +typedef PluginInstances<EmulateInstructionInstance> EmulateInstructionInstances; + +static EmulateInstructionInstances &GetEmulateInstructionInstances() { + static EmulateInstructionInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + EmulateInstructionCreateInstance create_callback) { + return GetEmulateInstructionInstances().RegisterPlugin(name, description, + create_callback); +} + +bool PluginManager::UnregisterPlugin( + EmulateInstructionCreateInstance create_callback) { + return GetEmulateInstructionInstances().UnregisterPlugin(create_callback); +} + +EmulateInstructionCreateInstance +PluginManager::GetEmulateInstructionCreateCallbackAtIndex(uint32_t idx) { + return GetEmulateInstructionInstances().GetCallbackAtIndex(idx); +} + +EmulateInstructionCreateInstance +PluginManager::GetEmulateInstructionCreateCallbackForPluginName( + llvm::StringRef name) { + return GetEmulateInstructionInstances().GetCallbackForName(name); +} + +#pragma mark OperatingSystem + +typedef PluginInstance<OperatingSystemCreateInstance> OperatingSystemInstance; +typedef PluginInstances<OperatingSystemInstance> OperatingSystemInstances; + +static OperatingSystemInstances &GetOperatingSystemInstances() { + static OperatingSystemInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + OperatingSystemCreateInstance create_callback, + DebuggerInitializeCallback debugger_init_callback) { + return GetOperatingSystemInstances().RegisterPlugin( + name, description, create_callback, debugger_init_callback); +} + +bool PluginManager::UnregisterPlugin( + OperatingSystemCreateInstance create_callback) { + return GetOperatingSystemInstances().UnregisterPlugin(create_callback); +} + +OperatingSystemCreateInstance +PluginManager::GetOperatingSystemCreateCallbackAtIndex(uint32_t idx) { + return GetOperatingSystemInstances().GetCallbackAtIndex(idx); +} + +OperatingSystemCreateInstance +PluginManager::GetOperatingSystemCreateCallbackForPluginName( + llvm::StringRef name) { + return GetOperatingSystemInstances().GetCallbackForName(name); +} + +#pragma mark Language + +typedef PluginInstance<LanguageCreateInstance> LanguageInstance; +typedef PluginInstances<LanguageInstance> LanguageInstances; + +static LanguageInstances &GetLanguageInstances() { + static LanguageInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin(llvm::StringRef name, + llvm::StringRef description, + LanguageCreateInstance create_callback) { + return GetLanguageInstances().RegisterPlugin(name, description, + create_callback); +} + +bool PluginManager::UnregisterPlugin(LanguageCreateInstance create_callback) { + return GetLanguageInstances().UnregisterPlugin(create_callback); +} + +LanguageCreateInstance +PluginManager::GetLanguageCreateCallbackAtIndex(uint32_t idx) { + return GetLanguageInstances().GetCallbackAtIndex(idx); +} + +#pragma mark LanguageRuntime + +struct LanguageRuntimeInstance + : public PluginInstance<LanguageRuntimeCreateInstance> { + LanguageRuntimeInstance( + llvm::StringRef name, llvm::StringRef description, + CallbackType create_callback, + DebuggerInitializeCallback debugger_init_callback, + LanguageRuntimeGetCommandObject command_callback, + LanguageRuntimeGetExceptionPrecondition precondition_callback) + : PluginInstance<LanguageRuntimeCreateInstance>( + name, description, create_callback, debugger_init_callback), + command_callback(command_callback), + precondition_callback(precondition_callback) {} + + LanguageRuntimeGetCommandObject command_callback; + LanguageRuntimeGetExceptionPrecondition precondition_callback; +}; + +typedef PluginInstances<LanguageRuntimeInstance> LanguageRuntimeInstances; + +static LanguageRuntimeInstances &GetLanguageRuntimeInstances() { + static LanguageRuntimeInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + LanguageRuntimeCreateInstance create_callback, + LanguageRuntimeGetCommandObject command_callback, + LanguageRuntimeGetExceptionPrecondition precondition_callback) { + return GetLanguageRuntimeInstances().RegisterPlugin( + name, description, create_callback, nullptr, command_callback, + precondition_callback); +} + +bool PluginManager::UnregisterPlugin( + LanguageRuntimeCreateInstance create_callback) { + return GetLanguageRuntimeInstances().UnregisterPlugin(create_callback); +} + +LanguageRuntimeCreateInstance +PluginManager::GetLanguageRuntimeCreateCallbackAtIndex(uint32_t idx) { + return GetLanguageRuntimeInstances().GetCallbackAtIndex(idx); +} + +LanguageRuntimeGetCommandObject +PluginManager::GetLanguageRuntimeGetCommandObjectAtIndex(uint32_t idx) { + const auto &instances = GetLanguageRuntimeInstances().GetInstances(); + if (idx < instances.size()) + return instances[idx].command_callback; + return nullptr; +} + +LanguageRuntimeGetExceptionPrecondition +PluginManager::GetLanguageRuntimeGetExceptionPreconditionAtIndex(uint32_t idx) { + const auto &instances = GetLanguageRuntimeInstances().GetInstances(); + if (idx < instances.size()) + return instances[idx].precondition_callback; + return nullptr; +} + +#pragma mark SystemRuntime + +typedef PluginInstance<SystemRuntimeCreateInstance> SystemRuntimeInstance; +typedef PluginInstances<SystemRuntimeInstance> SystemRuntimeInstances; + +static SystemRuntimeInstances &GetSystemRuntimeInstances() { + static SystemRuntimeInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + SystemRuntimeCreateInstance create_callback) { + return GetSystemRuntimeInstances().RegisterPlugin(name, description, + create_callback); +} + +bool PluginManager::UnregisterPlugin( + SystemRuntimeCreateInstance create_callback) { + return GetSystemRuntimeInstances().UnregisterPlugin(create_callback); +} + +SystemRuntimeCreateInstance +PluginManager::GetSystemRuntimeCreateCallbackAtIndex(uint32_t idx) { + return GetSystemRuntimeInstances().GetCallbackAtIndex(idx); +} + +#pragma mark ObjectFile + +struct ObjectFileInstance : public PluginInstance<ObjectFileCreateInstance> { + ObjectFileInstance( + llvm::StringRef name, llvm::StringRef description, + CallbackType create_callback, + ObjectFileCreateMemoryInstance create_memory_callback, + ObjectFileGetModuleSpecifications get_module_specifications, + ObjectFileSaveCore save_core, + DebuggerInitializeCallback debugger_init_callback) + : PluginInstance<ObjectFileCreateInstance>( + name, description, create_callback, debugger_init_callback), + create_memory_callback(create_memory_callback), + get_module_specifications(get_module_specifications), + save_core(save_core) {} + + ObjectFileCreateMemoryInstance create_memory_callback; + ObjectFileGetModuleSpecifications get_module_specifications; + ObjectFileSaveCore save_core; +}; +typedef PluginInstances<ObjectFileInstance> ObjectFileInstances; + +static ObjectFileInstances &GetObjectFileInstances() { + static ObjectFileInstances g_instances; + return g_instances; +} + +bool PluginManager::IsRegisteredObjectFilePluginName(llvm::StringRef name) { + if (name.empty()) + return false; + + const auto &instances = GetObjectFileInstances().GetInstances(); + for (auto &instance : instances) { + if (instance.name == name) + return true; + } + return false; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + ObjectFileCreateInstance create_callback, + ObjectFileCreateMemoryInstance create_memory_callback, + ObjectFileGetModuleSpecifications get_module_specifications, + ObjectFileSaveCore save_core, + DebuggerInitializeCallback debugger_init_callback) { + return GetObjectFileInstances().RegisterPlugin( + name, description, create_callback, create_memory_callback, + get_module_specifications, save_core, debugger_init_callback); +} + +bool PluginManager::UnregisterPlugin(ObjectFileCreateInstance create_callback) { + return GetObjectFileInstances().UnregisterPlugin(create_callback); +} + +ObjectFileCreateInstance +PluginManager::GetObjectFileCreateCallbackAtIndex(uint32_t idx) { + return GetObjectFileInstances().GetCallbackAtIndex(idx); +} + +ObjectFileCreateMemoryInstance +PluginManager::GetObjectFileCreateMemoryCallbackAtIndex(uint32_t idx) { + const auto &instances = GetObjectFileInstances().GetInstances(); + if (idx < instances.size()) + return instances[idx].create_memory_callback; + return nullptr; +} + +ObjectFileGetModuleSpecifications +PluginManager::GetObjectFileGetModuleSpecificationsCallbackAtIndex( + uint32_t idx) { + const auto &instances = GetObjectFileInstances().GetInstances(); + if (idx < instances.size()) + return instances[idx].get_module_specifications; + return nullptr; +} + +ObjectFileCreateMemoryInstance +PluginManager::GetObjectFileCreateMemoryCallbackForPluginName( + llvm::StringRef name) { + const auto &instances = GetObjectFileInstances().GetInstances(); + for (auto &instance : instances) { + if (instance.name == name) + return instance.create_memory_callback; + } + return nullptr; +} + +Status PluginManager::SaveCore(const lldb::ProcessSP &process_sp, + const lldb_private::SaveCoreOptions &options) { + Status error; + if (!options.GetOutputFile()) { + error.SetErrorString("No output file specified"); + return error; + } + + if (!process_sp) { + error.SetErrorString("Invalid process"); + return error; + } + + if (!options.GetPluginName().has_value()) { + // Try saving core directly from the process plugin first. + llvm::Expected<bool> ret = + process_sp->SaveCore(options.GetOutputFile()->GetPath()); + if (!ret) + return Status(ret.takeError()); + if (ret.get()) + return Status(); + } + + // Fall back to object plugins. + const auto &plugin_name = options.GetPluginName().value_or(""); + auto &instances = GetObjectFileInstances().GetInstances(); + for (auto &instance : instances) { + if (plugin_name.empty() || instance.name == plugin_name) { + if (instance.save_core && instance.save_core(process_sp, options, error)) + return error; + } + } + + // Check to see if any of the object file plugins tried and failed to save. + // If none ran, set the error message. + if (error.Success()) + error.SetErrorString( + "no ObjectFile plugins were able to save a core for this process"); + return error; +} + +#pragma mark ObjectContainer + +struct ObjectContainerInstance + : public PluginInstance<ObjectContainerCreateInstance> { + ObjectContainerInstance( + llvm::StringRef name, llvm::StringRef description, + CallbackType create_callback, + ObjectContainerCreateMemoryInstance create_memory_callback, + ObjectFileGetModuleSpecifications get_module_specifications) + : PluginInstance<ObjectContainerCreateInstance>(name, description, + create_callback), + create_memory_callback(create_memory_callback), + get_module_specifications(get_module_specifications) {} + + ObjectContainerCreateMemoryInstance create_memory_callback; + ObjectFileGetModuleSpecifications get_module_specifications; +}; +typedef PluginInstances<ObjectContainerInstance> ObjectContainerInstances; + +static ObjectContainerInstances &GetObjectContainerInstances() { + static ObjectContainerInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + ObjectContainerCreateInstance create_callback, + ObjectFileGetModuleSpecifications get_module_specifications, + ObjectContainerCreateMemoryInstance create_memory_callback) { + return GetObjectContainerInstances().RegisterPlugin( + name, description, create_callback, create_memory_callback, + get_module_specifications); +} + +bool PluginManager::UnregisterPlugin( + ObjectContainerCreateInstance create_callback) { + return GetObjectContainerInstances().UnregisterPlugin(create_callback); +} + +ObjectContainerCreateInstance +PluginManager::GetObjectContainerCreateCallbackAtIndex(uint32_t idx) { + return GetObjectContainerInstances().GetCallbackAtIndex(idx); +} + +ObjectContainerCreateMemoryInstance +PluginManager::GetObjectContainerCreateMemoryCallbackAtIndex(uint32_t idx) { + const auto &instances = GetObjectContainerInstances().GetInstances(); + if (idx < instances.size()) + return instances[idx].create_memory_callback; + return nullptr; +} + +ObjectFileGetModuleSpecifications +PluginManager::GetObjectContainerGetModuleSpecificationsCallbackAtIndex( + uint32_t idx) { + const auto &instances = GetObjectContainerInstances().GetInstances(); + if (idx < instances.size()) + return instances[idx].get_module_specifications; + return nullptr; +} + +#pragma mark Platform + +typedef PluginInstance<PlatformCreateInstance> PlatformInstance; +typedef PluginInstances<PlatformInstance> PlatformInstances; + +static PlatformInstances &GetPlatformInstances() { + static PlatformInstances g_platform_instances; + return g_platform_instances; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + PlatformCreateInstance create_callback, + DebuggerInitializeCallback debugger_init_callback) { + return GetPlatformInstances().RegisterPlugin( + name, description, create_callback, debugger_init_callback); +} + +bool PluginManager::UnregisterPlugin(PlatformCreateInstance create_callback) { + return GetPlatformInstances().UnregisterPlugin(create_callback); +} + +llvm::StringRef PluginManager::GetPlatformPluginNameAtIndex(uint32_t idx) { + return GetPlatformInstances().GetNameAtIndex(idx); +} + +llvm::StringRef +PluginManager::GetPlatformPluginDescriptionAtIndex(uint32_t idx) { + return GetPlatformInstances().GetDescriptionAtIndex(idx); +} + +PlatformCreateInstance +PluginManager::GetPlatformCreateCallbackAtIndex(uint32_t idx) { + return GetPlatformInstances().GetCallbackAtIndex(idx); +} + +PlatformCreateInstance +PluginManager::GetPlatformCreateCallbackForPluginName(llvm::StringRef name) { + return GetPlatformInstances().GetCallbackForName(name); +} + +void PluginManager::AutoCompletePlatformName(llvm::StringRef name, + CompletionRequest &request) { + for (const auto &instance : GetPlatformInstances().GetInstances()) { + if (instance.name.starts_with(name)) + request.AddCompletion(instance.name); + } +} + +#pragma mark Process + +typedef PluginInstance<ProcessCreateInstance> ProcessInstance; +typedef PluginInstances<ProcessInstance> ProcessInstances; + +static ProcessInstances &GetProcessInstances() { + static ProcessInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + ProcessCreateInstance create_callback, + DebuggerInitializeCallback debugger_init_callback) { + return GetProcessInstances().RegisterPlugin( + name, description, create_callback, debugger_init_callback); +} + +bool PluginManager::UnregisterPlugin(ProcessCreateInstance create_callback) { + return GetProcessInstances().UnregisterPlugin(create_callback); +} + +llvm::StringRef PluginManager::GetProcessPluginNameAtIndex(uint32_t idx) { + return GetProcessInstances().GetNameAtIndex(idx); +} + +llvm::StringRef PluginManager::GetProcessPluginDescriptionAtIndex(uint32_t idx) { + return GetProcessInstances().GetDescriptionAtIndex(idx); +} + +ProcessCreateInstance +PluginManager::GetProcessCreateCallbackAtIndex(uint32_t idx) { + return GetProcessInstances().GetCallbackAtIndex(idx); +} + +ProcessCreateInstance +PluginManager::GetProcessCreateCallbackForPluginName(llvm::StringRef name) { + return GetProcessInstances().GetCallbackForName(name); +} + +void PluginManager::AutoCompleteProcessName(llvm::StringRef name, + CompletionRequest &request) { + for (const auto &instance : GetProcessInstances().GetInstances()) { + if (instance.name.starts_with(name)) + request.AddCompletion(instance.name, instance.description); + } +} + +#pragma mark RegisterTypeBuilder + +struct RegisterTypeBuilderInstance + : public PluginInstance<RegisterTypeBuilderCreateInstance> { + RegisterTypeBuilderInstance(llvm::StringRef name, llvm::StringRef description, + CallbackType create_callback) + : PluginInstance<RegisterTypeBuilderCreateInstance>(name, description, + create_callback) {} +}; + +typedef PluginInstances<RegisterTypeBuilderInstance> + RegisterTypeBuilderInstances; + +static RegisterTypeBuilderInstances &GetRegisterTypeBuilderInstances() { + static RegisterTypeBuilderInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + RegisterTypeBuilderCreateInstance create_callback) { + return GetRegisterTypeBuilderInstances().RegisterPlugin(name, description, + create_callback); +} + +bool PluginManager::UnregisterPlugin( + RegisterTypeBuilderCreateInstance create_callback) { + return GetRegisterTypeBuilderInstances().UnregisterPlugin(create_callback); +} + +lldb::RegisterTypeBuilderSP +PluginManager::GetRegisterTypeBuilder(Target &target) { + const auto &instances = GetRegisterTypeBuilderInstances().GetInstances(); + // We assume that RegisterTypeBuilderClang is the only instance of this plugin + // type and is always present. + assert(instances.size()); + return instances[0].create_callback(target); +} + +#pragma mark ScriptInterpreter + +struct ScriptInterpreterInstance + : public PluginInstance<ScriptInterpreterCreateInstance> { + ScriptInterpreterInstance(llvm::StringRef name, llvm::StringRef description, + CallbackType create_callback, + lldb::ScriptLanguage language) + : PluginInstance<ScriptInterpreterCreateInstance>(name, description, + create_callback), + language(language) {} + + lldb::ScriptLanguage language = lldb::eScriptLanguageNone; +}; + +typedef PluginInstances<ScriptInterpreterInstance> ScriptInterpreterInstances; + +static ScriptInterpreterInstances &GetScriptInterpreterInstances() { + static ScriptInterpreterInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + lldb::ScriptLanguage script_language, + ScriptInterpreterCreateInstance create_callback) { + return GetScriptInterpreterInstances().RegisterPlugin( + name, description, create_callback, script_language); +} + +bool PluginManager::UnregisterPlugin( + ScriptInterpreterCreateInstance create_callback) { + return GetScriptInterpreterInstances().UnregisterPlugin(create_callback); +} + +ScriptInterpreterCreateInstance +PluginManager::GetScriptInterpreterCreateCallbackAtIndex(uint32_t idx) { + return GetScriptInterpreterInstances().GetCallbackAtIndex(idx); +} + +lldb::ScriptInterpreterSP +PluginManager::GetScriptInterpreterForLanguage(lldb::ScriptLanguage script_lang, + Debugger &debugger) { + const auto &instances = GetScriptInterpreterInstances().GetInstances(); + ScriptInterpreterCreateInstance none_instance = nullptr; + for (const auto &instance : instances) { + if (instance.language == lldb::eScriptLanguageNone) + none_instance = instance.create_callback; + + if (script_lang == instance.language) + return instance.create_callback(debugger); + } + + // If we didn't find one, return the ScriptInterpreter for the null language. + assert(none_instance != nullptr); + return none_instance(debugger); +} + +#pragma mark StructuredDataPlugin + +struct StructuredDataPluginInstance + : public PluginInstance<StructuredDataPluginCreateInstance> { + StructuredDataPluginInstance( + llvm::StringRef name, llvm::StringRef description, + CallbackType create_callback, + DebuggerInitializeCallback debugger_init_callback, + StructuredDataFilterLaunchInfo filter_callback) + : PluginInstance<StructuredDataPluginCreateInstance>( + name, description, create_callback, debugger_init_callback), + filter_callback(filter_callback) {} + + StructuredDataFilterLaunchInfo filter_callback = nullptr; +}; + +typedef PluginInstances<StructuredDataPluginInstance> + StructuredDataPluginInstances; + +static StructuredDataPluginInstances &GetStructuredDataPluginInstances() { + static StructuredDataPluginInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + StructuredDataPluginCreateInstance create_callback, + DebuggerInitializeCallback debugger_init_callback, + StructuredDataFilterLaunchInfo filter_callback) { + return GetStructuredDataPluginInstances().RegisterPlugin( + name, description, create_callback, debugger_init_callback, + filter_callback); +} + +bool PluginManager::UnregisterPlugin( + StructuredDataPluginCreateInstance create_callback) { + return GetStructuredDataPluginInstances().UnregisterPlugin(create_callback); +} + +StructuredDataPluginCreateInstance +PluginManager::GetStructuredDataPluginCreateCallbackAtIndex(uint32_t idx) { + return GetStructuredDataPluginInstances().GetCallbackAtIndex(idx); +} + +StructuredDataFilterLaunchInfo +PluginManager::GetStructuredDataFilterCallbackAtIndex( + uint32_t idx, bool &iteration_complete) { + const auto &instances = GetStructuredDataPluginInstances().GetInstances(); + if (idx < instances.size()) { + iteration_complete = false; + return instances[idx].filter_callback; + } else { + iteration_complete = true; + } + return nullptr; +} + +#pragma mark SymbolFile + +typedef PluginInstance<SymbolFileCreateInstance> SymbolFileInstance; +typedef PluginInstances<SymbolFileInstance> SymbolFileInstances; + +static SymbolFileInstances &GetSymbolFileInstances() { + static SymbolFileInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + SymbolFileCreateInstance create_callback, + DebuggerInitializeCallback debugger_init_callback) { + return GetSymbolFileInstances().RegisterPlugin( + name, description, create_callback, debugger_init_callback); +} + +bool PluginManager::UnregisterPlugin(SymbolFileCreateInstance create_callback) { + return GetSymbolFileInstances().UnregisterPlugin(create_callback); +} + +SymbolFileCreateInstance +PluginManager::GetSymbolFileCreateCallbackAtIndex(uint32_t idx) { + return GetSymbolFileInstances().GetCallbackAtIndex(idx); +} + +#pragma mark SymbolVendor + +typedef PluginInstance<SymbolVendorCreateInstance> SymbolVendorInstance; +typedef PluginInstances<SymbolVendorInstance> SymbolVendorInstances; + +static SymbolVendorInstances &GetSymbolVendorInstances() { + static SymbolVendorInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin(llvm::StringRef name, + llvm::StringRef description, + SymbolVendorCreateInstance create_callback) { + return GetSymbolVendorInstances().RegisterPlugin(name, description, + create_callback); +} + +bool PluginManager::UnregisterPlugin( + SymbolVendorCreateInstance create_callback) { + return GetSymbolVendorInstances().UnregisterPlugin(create_callback); +} + +SymbolVendorCreateInstance +PluginManager::GetSymbolVendorCreateCallbackAtIndex(uint32_t idx) { + return GetSymbolVendorInstances().GetCallbackAtIndex(idx); +} + +#pragma mark SymbolLocator + +struct SymbolLocatorInstance + : public PluginInstance<SymbolLocatorCreateInstance> { + SymbolLocatorInstance( + llvm::StringRef name, llvm::StringRef description, + CallbackType create_callback, + SymbolLocatorLocateExecutableObjectFile locate_executable_object_file, + SymbolLocatorLocateExecutableSymbolFile locate_executable_symbol_file, + SymbolLocatorDownloadObjectAndSymbolFile download_object_symbol_file, + SymbolLocatorFindSymbolFileInBundle find_symbol_file_in_bundle, + DebuggerInitializeCallback debugger_init_callback) + : PluginInstance<SymbolLocatorCreateInstance>( + name, description, create_callback, debugger_init_callback), + locate_executable_object_file(locate_executable_object_file), + locate_executable_symbol_file(locate_executable_symbol_file), + download_object_symbol_file(download_object_symbol_file), + find_symbol_file_in_bundle(find_symbol_file_in_bundle) {} + + SymbolLocatorLocateExecutableObjectFile locate_executable_object_file; + SymbolLocatorLocateExecutableSymbolFile locate_executable_symbol_file; + SymbolLocatorDownloadObjectAndSymbolFile download_object_symbol_file; + SymbolLocatorFindSymbolFileInBundle find_symbol_file_in_bundle; +}; +typedef PluginInstances<SymbolLocatorInstance> SymbolLocatorInstances; + +static SymbolLocatorInstances &GetSymbolLocatorInstances() { + static SymbolLocatorInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + SymbolLocatorCreateInstance create_callback, + SymbolLocatorLocateExecutableObjectFile locate_executable_object_file, + SymbolLocatorLocateExecutableSymbolFile locate_executable_symbol_file, + SymbolLocatorDownloadObjectAndSymbolFile download_object_symbol_file, + SymbolLocatorFindSymbolFileInBundle find_symbol_file_in_bundle, + DebuggerInitializeCallback debugger_init_callback) { + return GetSymbolLocatorInstances().RegisterPlugin( + name, description, create_callback, locate_executable_object_file, + locate_executable_symbol_file, download_object_symbol_file, + find_symbol_file_in_bundle, debugger_init_callback); +} + +bool PluginManager::UnregisterPlugin( + SymbolLocatorCreateInstance create_callback) { + return GetSymbolLocatorInstances().UnregisterPlugin(create_callback); +} + +SymbolLocatorCreateInstance +PluginManager::GetSymbolLocatorCreateCallbackAtIndex(uint32_t idx) { + return GetSymbolLocatorInstances().GetCallbackAtIndex(idx); +} + +ModuleSpec +PluginManager::LocateExecutableObjectFile(const ModuleSpec &module_spec) { + auto &instances = GetSymbolLocatorInstances().GetInstances(); + for (auto &instance : instances) { + if (instance.locate_executable_object_file) { + std::optional<ModuleSpec> result = + instance.locate_executable_object_file(module_spec); + if (result) + return *result; + } + } + return {}; +} + +FileSpec PluginManager::LocateExecutableSymbolFile( + const ModuleSpec &module_spec, const FileSpecList &default_search_paths) { + auto &instances = GetSymbolLocatorInstances().GetInstances(); + for (auto &instance : instances) { + if (instance.locate_executable_symbol_file) { + std::optional<FileSpec> result = instance.locate_executable_symbol_file( + module_spec, default_search_paths); + if (result) + return *result; + } + } + return {}; +} + +bool PluginManager::DownloadObjectAndSymbolFile(ModuleSpec &module_spec, + Status &error, + bool force_lookup, + bool copy_executable) { + auto &instances = GetSymbolLocatorInstances().GetInstances(); + for (auto &instance : instances) { + if (instance.download_object_symbol_file) { + if (instance.download_object_symbol_file(module_spec, error, force_lookup, + copy_executable)) + return true; + } + } + return false; +} + +FileSpec PluginManager::FindSymbolFileInBundle(const FileSpec &symfile_bundle, + const UUID *uuid, + const ArchSpec *arch) { + auto &instances = GetSymbolLocatorInstances().GetInstances(); + for (auto &instance : instances) { + if (instance.find_symbol_file_in_bundle) { + std::optional<FileSpec> result = + instance.find_symbol_file_in_bundle(symfile_bundle, uuid, arch); + if (result) + return *result; + } + } + return {}; +} + +#pragma mark Trace + +struct TraceInstance + : public PluginInstance<TraceCreateInstanceFromBundle> { + TraceInstance( + llvm::StringRef name, llvm::StringRef description, + CallbackType create_callback_from_bundle, + TraceCreateInstanceForLiveProcess create_callback_for_live_process, + llvm::StringRef schema, DebuggerInitializeCallback debugger_init_callback) + : PluginInstance<TraceCreateInstanceFromBundle>( + name, description, create_callback_from_bundle, + debugger_init_callback), + schema(schema), + create_callback_for_live_process(create_callback_for_live_process) {} + + llvm::StringRef schema; + TraceCreateInstanceForLiveProcess create_callback_for_live_process; +}; + +typedef PluginInstances<TraceInstance> TraceInstances; + +static TraceInstances &GetTracePluginInstances() { + static TraceInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + TraceCreateInstanceFromBundle create_callback_from_bundle, + TraceCreateInstanceForLiveProcess create_callback_for_live_process, + llvm::StringRef schema, DebuggerInitializeCallback debugger_init_callback) { + return GetTracePluginInstances().RegisterPlugin( + name, description, create_callback_from_bundle, + create_callback_for_live_process, schema, debugger_init_callback); +} + +bool PluginManager::UnregisterPlugin( + TraceCreateInstanceFromBundle create_callback_from_bundle) { + return GetTracePluginInstances().UnregisterPlugin( + create_callback_from_bundle); +} + +TraceCreateInstanceFromBundle +PluginManager::GetTraceCreateCallback(llvm::StringRef plugin_name) { + return GetTracePluginInstances().GetCallbackForName(plugin_name); +} + +TraceCreateInstanceForLiveProcess +PluginManager::GetTraceCreateCallbackForLiveProcess(llvm::StringRef plugin_name) { + for (const TraceInstance &instance : GetTracePluginInstances().GetInstances()) + if (instance.name == plugin_name) + return instance.create_callback_for_live_process; + return nullptr; +} + +llvm::StringRef PluginManager::GetTraceSchema(llvm::StringRef plugin_name) { + for (const TraceInstance &instance : GetTracePluginInstances().GetInstances()) + if (instance.name == plugin_name) + return instance.schema; + return llvm::StringRef(); +} + +llvm::StringRef PluginManager::GetTraceSchema(size_t index) { + if (TraceInstance *instance = + GetTracePluginInstances().GetInstanceAtIndex(index)) + return instance->schema; + return llvm::StringRef(); +} + +#pragma mark TraceExporter + +struct TraceExporterInstance + : public PluginInstance<TraceExporterCreateInstance> { + TraceExporterInstance( + llvm::StringRef name, llvm::StringRef description, + TraceExporterCreateInstance create_instance, + ThreadTraceExportCommandCreator create_thread_trace_export_command) + : PluginInstance<TraceExporterCreateInstance>(name, description, + create_instance), + create_thread_trace_export_command(create_thread_trace_export_command) { + } + + ThreadTraceExportCommandCreator create_thread_trace_export_command; +}; + +typedef PluginInstances<TraceExporterInstance> TraceExporterInstances; + +static TraceExporterInstances &GetTraceExporterInstances() { + static TraceExporterInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + TraceExporterCreateInstance create_callback, + ThreadTraceExportCommandCreator create_thread_trace_export_command) { + return GetTraceExporterInstances().RegisterPlugin( + name, description, create_callback, create_thread_trace_export_command); +} + +TraceExporterCreateInstance +PluginManager::GetTraceExporterCreateCallback(llvm::StringRef plugin_name) { + return GetTraceExporterInstances().GetCallbackForName(plugin_name); +} + +bool PluginManager::UnregisterPlugin( + TraceExporterCreateInstance create_callback) { + return GetTraceExporterInstances().UnregisterPlugin(create_callback); +} + +ThreadTraceExportCommandCreator +PluginManager::GetThreadTraceExportCommandCreatorAtIndex(uint32_t index) { + if (TraceExporterInstance *instance = + GetTraceExporterInstances().GetInstanceAtIndex(index)) + return instance->create_thread_trace_export_command; + return nullptr; +} + +llvm::StringRef +PluginManager::GetTraceExporterPluginNameAtIndex(uint32_t index) { + return GetTraceExporterInstances().GetNameAtIndex(index); +} + +#pragma mark UnwindAssembly + +typedef PluginInstance<UnwindAssemblyCreateInstance> UnwindAssemblyInstance; +typedef PluginInstances<UnwindAssemblyInstance> UnwindAssemblyInstances; + +static UnwindAssemblyInstances &GetUnwindAssemblyInstances() { + static UnwindAssemblyInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + UnwindAssemblyCreateInstance create_callback) { + return GetUnwindAssemblyInstances().RegisterPlugin(name, description, + create_callback); +} + +bool PluginManager::UnregisterPlugin( + UnwindAssemblyCreateInstance create_callback) { + return GetUnwindAssemblyInstances().UnregisterPlugin(create_callback); +} + +UnwindAssemblyCreateInstance +PluginManager::GetUnwindAssemblyCreateCallbackAtIndex(uint32_t idx) { + return GetUnwindAssemblyInstances().GetCallbackAtIndex(idx); +} + +#pragma mark MemoryHistory + +typedef PluginInstance<MemoryHistoryCreateInstance> MemoryHistoryInstance; +typedef PluginInstances<MemoryHistoryInstance> MemoryHistoryInstances; + +static MemoryHistoryInstances &GetMemoryHistoryInstances() { + static MemoryHistoryInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + MemoryHistoryCreateInstance create_callback) { + return GetMemoryHistoryInstances().RegisterPlugin(name, description, + create_callback); +} + +bool PluginManager::UnregisterPlugin( + MemoryHistoryCreateInstance create_callback) { + return GetMemoryHistoryInstances().UnregisterPlugin(create_callback); +} + +MemoryHistoryCreateInstance +PluginManager::GetMemoryHistoryCreateCallbackAtIndex(uint32_t idx) { + return GetMemoryHistoryInstances().GetCallbackAtIndex(idx); +} + +#pragma mark InstrumentationRuntime + +struct InstrumentationRuntimeInstance + : public PluginInstance<InstrumentationRuntimeCreateInstance> { + InstrumentationRuntimeInstance( + llvm::StringRef name, llvm::StringRef description, + CallbackType create_callback, + InstrumentationRuntimeGetType get_type_callback) + : PluginInstance<InstrumentationRuntimeCreateInstance>(name, description, + create_callback), + get_type_callback(get_type_callback) {} + + InstrumentationRuntimeGetType get_type_callback = nullptr; +}; + +typedef PluginInstances<InstrumentationRuntimeInstance> + InstrumentationRuntimeInstances; + +static InstrumentationRuntimeInstances &GetInstrumentationRuntimeInstances() { + static InstrumentationRuntimeInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + InstrumentationRuntimeCreateInstance create_callback, + InstrumentationRuntimeGetType get_type_callback) { + return GetInstrumentationRuntimeInstances().RegisterPlugin( + name, description, create_callback, get_type_callback); +} + +bool PluginManager::UnregisterPlugin( + InstrumentationRuntimeCreateInstance create_callback) { + return GetInstrumentationRuntimeInstances().UnregisterPlugin(create_callback); +} + +InstrumentationRuntimeGetType +PluginManager::GetInstrumentationRuntimeGetTypeCallbackAtIndex(uint32_t idx) { + const auto &instances = GetInstrumentationRuntimeInstances().GetInstances(); + if (idx < instances.size()) + return instances[idx].get_type_callback; + return nullptr; +} + +InstrumentationRuntimeCreateInstance +PluginManager::GetInstrumentationRuntimeCreateCallbackAtIndex(uint32_t idx) { + return GetInstrumentationRuntimeInstances().GetCallbackAtIndex(idx); +} + +#pragma mark TypeSystem + +struct TypeSystemInstance : public PluginInstance<TypeSystemCreateInstance> { + TypeSystemInstance(llvm::StringRef name, llvm::StringRef description, + CallbackType create_callback, + LanguageSet supported_languages_for_types, + LanguageSet supported_languages_for_expressions) + : PluginInstance<TypeSystemCreateInstance>(name, description, + create_callback), + supported_languages_for_types(supported_languages_for_types), + supported_languages_for_expressions( + supported_languages_for_expressions) {} + + LanguageSet supported_languages_for_types; + LanguageSet supported_languages_for_expressions; +}; + +typedef PluginInstances<TypeSystemInstance> TypeSystemInstances; + +static TypeSystemInstances &GetTypeSystemInstances() { + static TypeSystemInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + TypeSystemCreateInstance create_callback, + LanguageSet supported_languages_for_types, + LanguageSet supported_languages_for_expressions) { + return GetTypeSystemInstances().RegisterPlugin( + name, description, create_callback, supported_languages_for_types, + supported_languages_for_expressions); +} + +bool PluginManager::UnregisterPlugin(TypeSystemCreateInstance create_callback) { + return GetTypeSystemInstances().UnregisterPlugin(create_callback); +} + +TypeSystemCreateInstance +PluginManager::GetTypeSystemCreateCallbackAtIndex(uint32_t idx) { + return GetTypeSystemInstances().GetCallbackAtIndex(idx); +} + +LanguageSet PluginManager::GetAllTypeSystemSupportedLanguagesForTypes() { + const auto &instances = GetTypeSystemInstances().GetInstances(); + LanguageSet all; + for (unsigned i = 0; i < instances.size(); ++i) + all.bitvector |= instances[i].supported_languages_for_types.bitvector; + return all; +} + +LanguageSet PluginManager::GetAllTypeSystemSupportedLanguagesForExpressions() { + const auto &instances = GetTypeSystemInstances().GetInstances(); + LanguageSet all; + for (unsigned i = 0; i < instances.size(); ++i) + all.bitvector |= instances[i].supported_languages_for_expressions.bitvector; + return all; +} + +#pragma mark REPL + +struct REPLInstance : public PluginInstance<REPLCreateInstance> { + REPLInstance(llvm::StringRef name, llvm::StringRef description, + CallbackType create_callback, LanguageSet supported_languages) + : PluginInstance<REPLCreateInstance>(name, description, create_callback), + supported_languages(supported_languages) {} + + LanguageSet supported_languages; +}; + +typedef PluginInstances<REPLInstance> REPLInstances; + +static REPLInstances &GetREPLInstances() { + static REPLInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin(llvm::StringRef name, llvm::StringRef description, + REPLCreateInstance create_callback, + LanguageSet supported_languages) { + return GetREPLInstances().RegisterPlugin(name, description, create_callback, + supported_languages); +} + +bool PluginManager::UnregisterPlugin(REPLCreateInstance create_callback) { + return GetREPLInstances().UnregisterPlugin(create_callback); +} + +REPLCreateInstance PluginManager::GetREPLCreateCallbackAtIndex(uint32_t idx) { + return GetREPLInstances().GetCallbackAtIndex(idx); +} + +LanguageSet PluginManager::GetREPLSupportedLanguagesAtIndex(uint32_t idx) { + const auto &instances = GetREPLInstances().GetInstances(); + return idx < instances.size() ? instances[idx].supported_languages + : LanguageSet(); +} + +LanguageSet PluginManager::GetREPLAllTypeSystemSupportedLanguages() { + const auto &instances = GetREPLInstances().GetInstances(); + LanguageSet all; + for (unsigned i = 0; i < instances.size(); ++i) + all.bitvector |= instances[i].supported_languages.bitvector; + return all; +} + +#pragma mark PluginManager + +void PluginManager::DebuggerInitialize(Debugger &debugger) { + GetDynamicLoaderInstances().PerformDebuggerCallback(debugger); + GetJITLoaderInstances().PerformDebuggerCallback(debugger); + GetObjectFileInstances().PerformDebuggerCallback(debugger); + GetPlatformInstances().PerformDebuggerCallback(debugger); + GetProcessInstances().PerformDebuggerCallback(debugger); + GetSymbolFileInstances().PerformDebuggerCallback(debugger); + GetSymbolLocatorInstances().PerformDebuggerCallback(debugger); + GetOperatingSystemInstances().PerformDebuggerCallback(debugger); + GetStructuredDataPluginInstances().PerformDebuggerCallback(debugger); + GetTracePluginInstances().PerformDebuggerCallback(debugger); +} + +// This is the preferred new way to register plugin specific settings. e.g. +// This will put a plugin's settings under e.g. +// "plugin.<plugin_type_name>.<plugin_type_desc>.SETTINGNAME". +static lldb::OptionValuePropertiesSP +GetDebuggerPropertyForPlugins(Debugger &debugger, llvm::StringRef plugin_type_name, + llvm::StringRef plugin_type_desc, + bool can_create) { + lldb::OptionValuePropertiesSP parent_properties_sp( + debugger.GetValueProperties()); + if (parent_properties_sp) { + static constexpr llvm::StringLiteral g_property_name("plugin"); + + OptionValuePropertiesSP plugin_properties_sp = + parent_properties_sp->GetSubProperty(nullptr, g_property_name); + if (!plugin_properties_sp && can_create) { + plugin_properties_sp = + std::make_shared<OptionValueProperties>(g_property_name); + parent_properties_sp->AppendProperty(g_property_name, + "Settings specify to plugins.", true, + plugin_properties_sp); + } + + if (plugin_properties_sp) { + lldb::OptionValuePropertiesSP plugin_type_properties_sp = + plugin_properties_sp->GetSubProperty(nullptr, plugin_type_name); + if (!plugin_type_properties_sp && can_create) { + plugin_type_properties_sp = + std::make_shared<OptionValueProperties>(plugin_type_name); + plugin_properties_sp->AppendProperty(plugin_type_name, plugin_type_desc, + true, plugin_type_properties_sp); + } + return plugin_type_properties_sp; + } + } + return lldb::OptionValuePropertiesSP(); +} + +// This is deprecated way to register plugin specific settings. e.g. +// "<plugin_type_name>.plugin.<plugin_type_desc>.SETTINGNAME" and Platform +// generic settings would be under "platform.SETTINGNAME". +static lldb::OptionValuePropertiesSP GetDebuggerPropertyForPluginsOldStyle( + Debugger &debugger, llvm::StringRef plugin_type_name, + llvm::StringRef plugin_type_desc, bool can_create) { + static constexpr llvm::StringLiteral g_property_name("plugin"); + lldb::OptionValuePropertiesSP parent_properties_sp( + debugger.GetValueProperties()); + if (parent_properties_sp) { + OptionValuePropertiesSP plugin_properties_sp = + parent_properties_sp->GetSubProperty(nullptr, plugin_type_name); + if (!plugin_properties_sp && can_create) { + plugin_properties_sp = + std::make_shared<OptionValueProperties>(plugin_type_name); + parent_properties_sp->AppendProperty(plugin_type_name, plugin_type_desc, + true, plugin_properties_sp); + } + + if (plugin_properties_sp) { + lldb::OptionValuePropertiesSP plugin_type_properties_sp = + plugin_properties_sp->GetSubProperty(nullptr, g_property_name); + if (!plugin_type_properties_sp && can_create) { + plugin_type_properties_sp = + std::make_shared<OptionValueProperties>(g_property_name); + plugin_properties_sp->AppendProperty(g_property_name, + "Settings specific to plugins", + true, plugin_type_properties_sp); + } + return plugin_type_properties_sp; + } + } + return lldb::OptionValuePropertiesSP(); +} + +namespace { + +typedef lldb::OptionValuePropertiesSP +GetDebuggerPropertyForPluginsPtr(Debugger &, llvm::StringRef, llvm::StringRef, + bool can_create); +} + +static lldb::OptionValuePropertiesSP +GetSettingForPlugin(Debugger &debugger, llvm::StringRef setting_name, + llvm::StringRef plugin_type_name, + GetDebuggerPropertyForPluginsPtr get_debugger_property = + GetDebuggerPropertyForPlugins) { + lldb::OptionValuePropertiesSP properties_sp; + lldb::OptionValuePropertiesSP plugin_type_properties_sp(get_debugger_property( + debugger, plugin_type_name, + "", // not creating to so we don't need the description + false)); + if (plugin_type_properties_sp) + properties_sp = + plugin_type_properties_sp->GetSubProperty(nullptr, setting_name); + return properties_sp; +} + +static bool +CreateSettingForPlugin(Debugger &debugger, llvm::StringRef plugin_type_name, + llvm::StringRef plugin_type_desc, + const lldb::OptionValuePropertiesSP &properties_sp, + llvm::StringRef description, bool is_global_property, + GetDebuggerPropertyForPluginsPtr get_debugger_property = + GetDebuggerPropertyForPlugins) { + if (properties_sp) { + lldb::OptionValuePropertiesSP plugin_type_properties_sp( + get_debugger_property(debugger, plugin_type_name, plugin_type_desc, + true)); + if (plugin_type_properties_sp) { + plugin_type_properties_sp->AppendProperty(properties_sp->GetName(), + description, is_global_property, + properties_sp); + return true; + } + } + return false; +} + +static constexpr llvm::StringLiteral kDynamicLoaderPluginName("dynamic-loader"); +static constexpr llvm::StringLiteral kPlatformPluginName("platform"); +static constexpr llvm::StringLiteral kProcessPluginName("process"); +static constexpr llvm::StringLiteral kTracePluginName("trace"); +static constexpr llvm::StringLiteral kObjectFilePluginName("object-file"); +static constexpr llvm::StringLiteral kSymbolFilePluginName("symbol-file"); +static constexpr llvm::StringLiteral kSymbolLocatorPluginName("symbol-locator"); +static constexpr llvm::StringLiteral kJITLoaderPluginName("jit-loader"); +static constexpr llvm::StringLiteral + kStructuredDataPluginName("structured-data"); + +lldb::OptionValuePropertiesSP +PluginManager::GetSettingForDynamicLoaderPlugin(Debugger &debugger, + llvm::StringRef setting_name) { + return GetSettingForPlugin(debugger, setting_name, kDynamicLoaderPluginName); +} + +bool PluginManager::CreateSettingForDynamicLoaderPlugin( + Debugger &debugger, const lldb::OptionValuePropertiesSP &properties_sp, + llvm::StringRef description, bool is_global_property) { + return CreateSettingForPlugin(debugger, kDynamicLoaderPluginName, + "Settings for dynamic loader plug-ins", + properties_sp, description, is_global_property); +} + +lldb::OptionValuePropertiesSP +PluginManager::GetSettingForPlatformPlugin(Debugger &debugger, + llvm::StringRef setting_name) { + return GetSettingForPlugin(debugger, setting_name, kPlatformPluginName, + GetDebuggerPropertyForPluginsOldStyle); +} + +bool PluginManager::CreateSettingForPlatformPlugin( + Debugger &debugger, const lldb::OptionValuePropertiesSP &properties_sp, + llvm::StringRef description, bool is_global_property) { + return CreateSettingForPlugin(debugger, kPlatformPluginName, + "Settings for platform plug-ins", properties_sp, + description, is_global_property, + GetDebuggerPropertyForPluginsOldStyle); +} + +lldb::OptionValuePropertiesSP +PluginManager::GetSettingForProcessPlugin(Debugger &debugger, + llvm::StringRef setting_name) { + return GetSettingForPlugin(debugger, setting_name, kProcessPluginName); +} + +bool PluginManager::CreateSettingForProcessPlugin( + Debugger &debugger, const lldb::OptionValuePropertiesSP &properties_sp, + llvm::StringRef description, bool is_global_property) { + return CreateSettingForPlugin(debugger, kProcessPluginName, + "Settings for process plug-ins", properties_sp, + description, is_global_property); +} + +lldb::OptionValuePropertiesSP +PluginManager::GetSettingForSymbolLocatorPlugin(Debugger &debugger, + llvm::StringRef setting_name) { + return GetSettingForPlugin(debugger, setting_name, kSymbolLocatorPluginName); +} + +bool PluginManager::CreateSettingForSymbolLocatorPlugin( + Debugger &debugger, const lldb::OptionValuePropertiesSP &properties_sp, + llvm::StringRef description, bool is_global_property) { + return CreateSettingForPlugin(debugger, kSymbolLocatorPluginName, + "Settings for symbol locator plug-ins", + properties_sp, description, is_global_property); +} + +bool PluginManager::CreateSettingForTracePlugin( + Debugger &debugger, const lldb::OptionValuePropertiesSP &properties_sp, + llvm::StringRef description, bool is_global_property) { + return CreateSettingForPlugin(debugger, kTracePluginName, + "Settings for trace plug-ins", properties_sp, + description, is_global_property); +} + +lldb::OptionValuePropertiesSP +PluginManager::GetSettingForObjectFilePlugin(Debugger &debugger, + llvm::StringRef setting_name) { + return GetSettingForPlugin(debugger, setting_name, kObjectFilePluginName); +} + +bool PluginManager::CreateSettingForObjectFilePlugin( + Debugger &debugger, const lldb::OptionValuePropertiesSP &properties_sp, + llvm::StringRef description, bool is_global_property) { + return CreateSettingForPlugin(debugger, kObjectFilePluginName, + "Settings for object file plug-ins", + properties_sp, description, is_global_property); +} + +lldb::OptionValuePropertiesSP +PluginManager::GetSettingForSymbolFilePlugin(Debugger &debugger, + llvm::StringRef setting_name) { + return GetSettingForPlugin(debugger, setting_name, kSymbolFilePluginName); +} + +bool PluginManager::CreateSettingForSymbolFilePlugin( + Debugger &debugger, const lldb::OptionValuePropertiesSP &properties_sp, + llvm::StringRef description, bool is_global_property) { + return CreateSettingForPlugin(debugger, kSymbolFilePluginName, + "Settings for symbol file plug-ins", + properties_sp, description, is_global_property); +} + +lldb::OptionValuePropertiesSP +PluginManager::GetSettingForJITLoaderPlugin(Debugger &debugger, + llvm::StringRef setting_name) { + return GetSettingForPlugin(debugger, setting_name, kJITLoaderPluginName); +} + +bool PluginManager::CreateSettingForJITLoaderPlugin( + Debugger &debugger, const lldb::OptionValuePropertiesSP &properties_sp, + llvm::StringRef description, bool is_global_property) { + return CreateSettingForPlugin(debugger, kJITLoaderPluginName, + "Settings for JIT loader plug-ins", + properties_sp, description, is_global_property); +} + +static const char *kOperatingSystemPluginName("os"); + +lldb::OptionValuePropertiesSP +PluginManager::GetSettingForOperatingSystemPlugin(Debugger &debugger, + llvm::StringRef setting_name) { + lldb::OptionValuePropertiesSP properties_sp; + lldb::OptionValuePropertiesSP plugin_type_properties_sp( + GetDebuggerPropertyForPlugins( + debugger, kOperatingSystemPluginName, + "", // not creating to so we don't need the description + false)); + if (plugin_type_properties_sp) + properties_sp = + plugin_type_properties_sp->GetSubProperty(nullptr, setting_name); + return properties_sp; +} + +bool PluginManager::CreateSettingForOperatingSystemPlugin( + Debugger &debugger, const lldb::OptionValuePropertiesSP &properties_sp, + llvm::StringRef description, bool is_global_property) { + if (properties_sp) { + lldb::OptionValuePropertiesSP plugin_type_properties_sp( + GetDebuggerPropertyForPlugins(debugger, kOperatingSystemPluginName, + "Settings for operating system plug-ins", + true)); + if (plugin_type_properties_sp) { + plugin_type_properties_sp->AppendProperty(properties_sp->GetName(), + description, is_global_property, + properties_sp); + return true; + } + } + return false; +} + +lldb::OptionValuePropertiesSP +PluginManager::GetSettingForStructuredDataPlugin(Debugger &debugger, + llvm::StringRef setting_name) { + return GetSettingForPlugin(debugger, setting_name, kStructuredDataPluginName); +} + +bool PluginManager::CreateSettingForStructuredDataPlugin( + Debugger &debugger, const lldb::OptionValuePropertiesSP &properties_sp, + llvm::StringRef description, bool is_global_property) { + return CreateSettingForPlugin(debugger, kStructuredDataPluginName, + "Settings for structured data plug-ins", + properties_sp, description, is_global_property); +} diff --git a/contrib/llvm-project/lldb/source/Core/Progress.cpp b/contrib/llvm-project/lldb/source/Core/Progress.cpp new file mode 100644 index 000000000000..1a779e2ddf92 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/Progress.cpp @@ -0,0 +1,193 @@ +//===-- Progress.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/Core/Progress.h" + +#include "lldb/Core/Debugger.h" +#include "lldb/Utility/StreamString.h" + +#include <cstdint> +#include <mutex> +#include <optional> + +using namespace lldb; +using namespace lldb_private; + +std::atomic<uint64_t> Progress::g_id(0); + +Progress::Progress(std::string title, std::string details, + std::optional<uint64_t> total, + lldb_private::Debugger *debugger) + : m_details(details), m_completed(0), + m_total(Progress::kNonDeterministicTotal), + m_progress_data{title, ++g_id, + /*m_progress_data.debugger_id=*/std::nullopt} { + if (total) + m_total = *total; + + if (debugger) + m_progress_data.debugger_id = debugger->GetID(); + + std::lock_guard<std::mutex> guard(m_mutex); + ReportProgress(); + + // Report to the ProgressManager if that subsystem is enabled. + if (ProgressManager::Enabled()) + ProgressManager::Instance().Increment(m_progress_data); +} + +Progress::~Progress() { + // Make sure to always report progress completed when this object is + // destructed so it indicates the progress dialog/activity should go away. + std::lock_guard<std::mutex> guard(m_mutex); + if (!m_completed) + m_completed = m_total; + ReportProgress(); + + // Report to the ProgressManager if that subsystem is enabled. + if (ProgressManager::Enabled()) + ProgressManager::Instance().Decrement(m_progress_data); +} + +void Progress::Increment(uint64_t amount, + std::optional<std::string> updated_detail) { + if (amount > 0) { + std::lock_guard<std::mutex> guard(m_mutex); + if (updated_detail) + m_details = std::move(updated_detail.value()); + // Watch out for unsigned overflow and make sure we don't increment too + // much and exceed the total. + if (m_total && (amount > (m_total - m_completed))) + m_completed = m_total; + else + m_completed += amount; + ReportProgress(); + } +} + +void Progress::ReportProgress() { + if (!m_complete) { + // Make sure we only send one notification that indicates the progress is + // complete + m_complete = m_completed == m_total; + Debugger::ReportProgress(m_progress_data.progress_id, m_progress_data.title, + m_details, m_completed, m_total, + m_progress_data.debugger_id); + } +} + +ProgressManager::ProgressManager() + : m_entries(), m_alarm(std::chrono::milliseconds(100)) {} + +ProgressManager::~ProgressManager() {} + +void ProgressManager::Initialize() { + assert(!InstanceImpl() && "Already initialized."); + InstanceImpl().emplace(); +} + +void ProgressManager::Terminate() { + assert(InstanceImpl() && "Already terminated."); + InstanceImpl().reset(); +} + +bool ProgressManager::Enabled() { return InstanceImpl().operator bool(); } + +ProgressManager &ProgressManager::Instance() { + assert(InstanceImpl() && "ProgressManager must be initialized"); + return *InstanceImpl(); +} + +std::optional<ProgressManager> &ProgressManager::InstanceImpl() { + static std::optional<ProgressManager> g_progress_manager; + return g_progress_manager; +} + +void ProgressManager::Increment(const Progress::ProgressData &progress_data) { + std::lock_guard<std::mutex> lock(m_entries_mutex); + + llvm::StringRef key = progress_data.title; + bool new_entry = !m_entries.contains(key); + Entry &entry = m_entries[progress_data.title]; + + if (new_entry) { + // This is a new progress event. Report progress and store the progress + // data. + ReportProgress(progress_data, EventType::Begin); + entry.data = progress_data; + } else if (entry.refcount == 0) { + // This is an existing entry that was scheduled to be deleted but a new one + // came in before the timer expired. + assert(entry.handle != Alarm::INVALID_HANDLE); + + if (!m_alarm.Cancel(entry.handle)) { + // The timer expired before we had a chance to cancel it. We have to treat + // this as an entirely new progress event. + ReportProgress(progress_data, EventType::Begin); + } + // Clear the alarm handle. + entry.handle = Alarm::INVALID_HANDLE; + } + + // Regardless of how we got here, we need to bump the reference count. + entry.refcount++; +} + +void ProgressManager::Decrement(const Progress::ProgressData &progress_data) { + std::lock_guard<std::mutex> lock(m_entries_mutex); + llvm::StringRef key = progress_data.title; + + if (!m_entries.contains(key)) + return; + + Entry &entry = m_entries[key]; + entry.refcount--; + + if (entry.refcount == 0) { + assert(entry.handle == Alarm::INVALID_HANDLE); + + // Copy the key to a std::string so we can pass it by value to the lambda. + // The underlying StringRef will not exist by the time the callback is + // called. + std::string key_str = std::string(key); + + // Start a timer. If it expires before we see another progress event, it + // will be reported. + entry.handle = m_alarm.Create([=]() { Expire(key_str); }); + } +} + +void ProgressManager::ReportProgress( + const Progress::ProgressData &progress_data, EventType type) { + // The category bit only keeps track of when progress report categories have + // started and ended, so clear the details and reset other fields when + // broadcasting to it since that bit doesn't need that information. + const uint64_t completed = + (type == EventType::Begin) ? 0 : Progress::kNonDeterministicTotal; + Debugger::ReportProgress(progress_data.progress_id, progress_data.title, "", + completed, Progress::kNonDeterministicTotal, + progress_data.debugger_id, + lldb::eBroadcastBitProgressCategory); +} + +void ProgressManager::Expire(llvm::StringRef key) { + std::lock_guard<std::mutex> lock(m_entries_mutex); + + // This shouldn't happen but be resilient anyway. + if (!m_entries.contains(key)) + return; + + // A new event came in and the alarm fired before we had a chance to restart + // it. + if (m_entries[key].refcount != 0) + return; + + // We're done with this entry. + ReportProgress(m_entries[key].data, EventType::End); + m_entries.erase(key); +} diff --git a/contrib/llvm-project/lldb/source/Core/RichManglingContext.cpp b/contrib/llvm-project/lldb/source/Core/RichManglingContext.cpp new file mode 100644 index 000000000000..b68c9e11581b --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/RichManglingContext.cpp @@ -0,0 +1,162 @@ +//===-- RichManglingContext.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/Core/RichManglingContext.h" +#include "Plugins/Language/CPlusPlus/CPlusPlusLanguage.h" +#include "lldb/Utility/LLDBLog.h" + +#include "llvm/ADT/StringRef.h" + +using namespace lldb; +using namespace lldb_private; + +// RichManglingContext +RichManglingContext::~RichManglingContext() { + std::free(m_ipd_buf); + ResetCxxMethodParser(); +} + +void RichManglingContext::ResetCxxMethodParser() { + // If we want to support parsers for other languages some day, we need a + // switch here to delete the correct parser type. + if (m_cxx_method_parser.has_value()) { + assert(m_provider == PluginCxxLanguage); + delete get<CPlusPlusLanguage::MethodName>(m_cxx_method_parser); + m_cxx_method_parser.reset(); + } +} + +void RichManglingContext::ResetProvider(InfoProvider new_provider) { + ResetCxxMethodParser(); + + assert(new_provider != None && "Only reset to a valid provider"); + m_provider = new_provider; +} + +bool RichManglingContext::FromItaniumName(ConstString mangled) { + bool err = m_ipd.partialDemangle(mangled.GetCString()); + if (!err) { + ResetProvider(ItaniumPartialDemangler); + } + + if (Log *log = GetLog(LLDBLog::Demangle)) { + if (!err) { + ParseFullName(); + LLDB_LOG(log, "demangled itanium: {0} -> \"{1}\"", mangled, m_ipd_buf); + } else { + LLDB_LOG(log, "demangled itanium: {0} -> error: failed to demangle", + mangled); + } + } + + return !err; // true == success +} + +bool RichManglingContext::FromCxxMethodName(ConstString demangled) { + ResetProvider(PluginCxxLanguage); + m_cxx_method_parser = new CPlusPlusLanguage::MethodName(demangled); + return true; +} + +bool RichManglingContext::IsCtorOrDtor() const { + assert(m_provider != None && "Initialize a provider first"); + switch (m_provider) { + case ItaniumPartialDemangler: + return m_ipd.isCtorOrDtor(); + case PluginCxxLanguage: { + // We can only check for destructors here. + auto base_name = + get<CPlusPlusLanguage::MethodName>(m_cxx_method_parser)->GetBasename(); + return base_name.starts_with("~"); + } + case None: + return false; + } + llvm_unreachable("Fully covered switch above!"); +} + +llvm::StringRef RichManglingContext::processIPDStrResult(char *ipd_res, + size_t res_size) { + // Error case: Clear the buffer. + if (LLVM_UNLIKELY(ipd_res == nullptr)) { + assert(res_size == m_ipd_buf_size && + "Failed IPD queries keep the original size in the N parameter"); + + m_ipd_buf[0] = '\0'; + return llvm::StringRef(m_ipd_buf, 0); + } + + // IPD's res_size includes null terminator. + assert(ipd_res[res_size - 1] == '\0' && + "IPD returns null-terminated strings and we rely on that"); + + // Update buffer/size on realloc. + if (LLVM_UNLIKELY(ipd_res != m_ipd_buf || res_size > m_ipd_buf_size)) { + m_ipd_buf = ipd_res; // std::realloc freed or reused the old buffer. + m_ipd_buf_size = res_size; // May actually be bigger, but we can't know. + + if (Log *log = GetLog(LLDBLog::Demangle)) + LLDB_LOG(log, "ItaniumPartialDemangler Realloc: new buffer size is {0}", + m_ipd_buf_size); + } + + // 99% case: Just remember the string length. + return llvm::StringRef(m_ipd_buf, res_size - 1); +} + +llvm::StringRef RichManglingContext::ParseFunctionBaseName() { + assert(m_provider != None && "Initialize a provider first"); + switch (m_provider) { + case ItaniumPartialDemangler: { + auto n = m_ipd_buf_size; + auto buf = m_ipd.getFunctionBaseName(m_ipd_buf, &n); + return processIPDStrResult(buf, n); + } + case PluginCxxLanguage: + return get<CPlusPlusLanguage::MethodName>(m_cxx_method_parser) + ->GetBasename(); + case None: + return {}; + } + llvm_unreachable("Fully covered switch above!"); +} + +llvm::StringRef RichManglingContext::ParseFunctionDeclContextName() { + assert(m_provider != None && "Initialize a provider first"); + switch (m_provider) { + case ItaniumPartialDemangler: { + auto n = m_ipd_buf_size; + auto buf = m_ipd.getFunctionDeclContextName(m_ipd_buf, &n); + return processIPDStrResult(buf, n); + } + case PluginCxxLanguage: + return get<CPlusPlusLanguage::MethodName>(m_cxx_method_parser) + ->GetContext(); + case None: + return {}; + } + llvm_unreachable("Fully covered switch above!"); +} + +llvm::StringRef RichManglingContext::ParseFullName() { + assert(m_provider != None && "Initialize a provider first"); + switch (m_provider) { + case ItaniumPartialDemangler: { + auto n = m_ipd_buf_size; + auto buf = m_ipd.finishDemangle(m_ipd_buf, &n); + return processIPDStrResult(buf, n); + } + case PluginCxxLanguage: + return get<CPlusPlusLanguage::MethodName>(m_cxx_method_parser) + ->GetFullName() + .GetStringRef(); + case None: + return {}; + } + llvm_unreachable("Fully covered switch above!"); +} diff --git a/contrib/llvm-project/lldb/source/Core/SearchFilter.cpp b/contrib/llvm-project/lldb/source/Core/SearchFilter.cpp new file mode 100644 index 000000000000..b3ff73bbf58f --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/SearchFilter.cpp @@ -0,0 +1,800 @@ +//===-- SearchFilter.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/Core/SearchFilter.h" + +#include "lldb/Breakpoint/Breakpoint.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleList.h" +#include "lldb/Symbol/CompileUnit.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Symbol/SymbolFile.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/Stream.h" +#include "lldb/lldb-enumerations.h" + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/ErrorHandling.h" + +#include <memory> +#include <mutex> +#include <string> + +#include <cinttypes> +#include <cstring> + +namespace lldb_private { +class Address; +} +namespace lldb_private { +class Function; +} + +using namespace lldb; +using namespace lldb_private; + +const char *SearchFilter::g_ty_to_name[] = {"Unconstrained", "Exception", + "Module", "Modules", + "ModulesAndCU", "Unknown"}; + +const char + *SearchFilter::g_option_names[SearchFilter::OptionNames::LastOptionName] = { + "ModuleList", "CUList"}; + +const char *SearchFilter::FilterTyToName(enum FilterTy type) { + if (type > LastKnownFilterType) + return g_ty_to_name[UnknownFilter]; + + return g_ty_to_name[type]; +} + +SearchFilter::FilterTy SearchFilter::NameToFilterTy(llvm::StringRef name) { + for (size_t i = 0; i <= LastKnownFilterType; i++) { + if (name == g_ty_to_name[i]) + return (FilterTy)i; + } + return UnknownFilter; +} + +Searcher::Searcher() = default; + +Searcher::~Searcher() = default; + +void Searcher::GetDescription(Stream *s) {} + +SearchFilter::SearchFilter(const TargetSP &target_sp, unsigned char filterType) + : m_target_sp(target_sp), SubclassID(filterType) {} + +SearchFilter::~SearchFilter() = default; + +SearchFilterSP SearchFilter::CreateFromStructuredData( + const lldb::TargetSP& target_sp, + const StructuredData::Dictionary &filter_dict, + Status &error) { + SearchFilterSP result_sp; + if (!filter_dict.IsValid()) { + error.SetErrorString("Can't deserialize from an invalid data object."); + return result_sp; + } + + llvm::StringRef subclass_name; + + bool success = filter_dict.GetValueForKeyAsString( + GetSerializationSubclassKey(), subclass_name); + if (!success) { + error.SetErrorString("Filter data missing subclass key"); + return result_sp; + } + + FilterTy filter_type = NameToFilterTy(subclass_name); + if (filter_type == UnknownFilter) { + error.SetErrorStringWithFormatv("Unknown filter type: {0}.", subclass_name); + return result_sp; + } + + StructuredData::Dictionary *subclass_options = nullptr; + success = filter_dict.GetValueForKeyAsDictionary( + GetSerializationSubclassOptionsKey(), subclass_options); + if (!success || !subclass_options || !subclass_options->IsValid()) { + error.SetErrorString("Filter data missing subclass options key."); + return result_sp; + } + + switch (filter_type) { + case Unconstrained: + result_sp = SearchFilterForUnconstrainedSearches::CreateFromStructuredData( + target_sp, *subclass_options, error); + break; + case ByModule: + result_sp = SearchFilterByModule::CreateFromStructuredData( + target_sp, *subclass_options, error); + break; + case ByModules: + result_sp = SearchFilterByModuleList::CreateFromStructuredData( + target_sp, *subclass_options, error); + break; + case ByModulesAndCU: + result_sp = SearchFilterByModuleListAndCU::CreateFromStructuredData( + target_sp, *subclass_options, error); + break; + case Exception: + error.SetErrorString("Can't serialize exception breakpoints yet."); + break; + default: + llvm_unreachable("Should never get an uresolvable filter type."); + } + + return result_sp; +} + +bool SearchFilter::ModulePasses(const FileSpec &spec) { return true; } + +bool SearchFilter::ModulePasses(const ModuleSP &module_sp) { return true; } + +bool SearchFilter::AddressPasses(Address &address) { return true; } + +bool SearchFilter::CompUnitPasses(FileSpec &fileSpec) { return true; } + +bool SearchFilter::CompUnitPasses(CompileUnit &compUnit) { return true; } + +bool SearchFilter::FunctionPasses(Function &function) { + // This is a slightly cheesy job, but since we don't have finer grained + // filters yet, just checking that the start address passes is probably + // good enough for the base class behavior. + Address addr = function.GetAddressRange().GetBaseAddress(); + return AddressPasses(addr); +} + + +uint32_t SearchFilter::GetFilterRequiredItems() { + return (lldb::SymbolContextItem)0; +} + +void SearchFilter::GetDescription(Stream *s) {} + +void SearchFilter::Dump(Stream *s) const {} + +lldb::SearchFilterSP SearchFilter::CreateCopy(lldb::TargetSP& target_sp) { + SearchFilterSP ret_sp = DoCreateCopy(); + ret_sp->SetTarget(target_sp); + return ret_sp; +} + +// Helper functions for serialization. + +StructuredData::DictionarySP +SearchFilter::WrapOptionsDict(StructuredData::DictionarySP options_dict_sp) { + if (!options_dict_sp || !options_dict_sp->IsValid()) + return StructuredData::DictionarySP(); + + auto type_dict_sp = std::make_shared<StructuredData::Dictionary>(); + type_dict_sp->AddStringItem(GetSerializationSubclassKey(), GetFilterName()); + type_dict_sp->AddItem(GetSerializationSubclassOptionsKey(), options_dict_sp); + + return type_dict_sp; +} + +void SearchFilter::SerializeFileSpecList( + StructuredData::DictionarySP &options_dict_sp, OptionNames name, + FileSpecList &file_list) { + size_t num_modules = file_list.GetSize(); + + // Don't serialize empty lists. + if (num_modules == 0) + return; + + auto module_array_sp = std::make_shared<StructuredData::Array>(); + for (size_t i = 0; i < num_modules; i++) { + module_array_sp->AddItem(std::make_shared<StructuredData::String>( + file_list.GetFileSpecAtIndex(i).GetPath())); + } + options_dict_sp->AddItem(GetKey(name), module_array_sp); +} + +// UTILITY Functions to help iterate down through the elements of the +// SymbolContext. + +void SearchFilter::Search(Searcher &searcher) { + SymbolContext empty_sc; + + if (!m_target_sp) + return; + empty_sc.target_sp = m_target_sp; + + if (searcher.GetDepth() == lldb::eSearchDepthTarget) { + searcher.SearchCallback(*this, empty_sc, nullptr); + return; + } + + DoModuleIteration(empty_sc, searcher); +} + +void SearchFilter::SearchInModuleList(Searcher &searcher, ModuleList &modules) { + SymbolContext empty_sc; + + if (!m_target_sp) + return; + empty_sc.target_sp = m_target_sp; + + if (searcher.GetDepth() == lldb::eSearchDepthTarget) { + searcher.SearchCallback(*this, empty_sc, nullptr); + return; + } + + for (ModuleSP module_sp : modules.Modules()) { + if (!ModulePasses(module_sp)) + continue; + if (DoModuleIteration(module_sp, searcher) == Searcher::eCallbackReturnStop) + return; + } +} + +Searcher::CallbackReturn +SearchFilter::DoModuleIteration(const lldb::ModuleSP &module_sp, + Searcher &searcher) { + SymbolContext matchingContext(m_target_sp, module_sp); + return DoModuleIteration(matchingContext, searcher); +} + +Searcher::CallbackReturn +SearchFilter::DoModuleIteration(const SymbolContext &context, + Searcher &searcher) { + if (searcher.GetDepth() < lldb::eSearchDepthModule) + return Searcher::eCallbackReturnContinue; + + if (context.module_sp) { + if (searcher.GetDepth() != lldb::eSearchDepthModule) + return DoCUIteration(context.module_sp, context, searcher); + + SymbolContext matchingContext(context.module_sp.get()); + searcher.SearchCallback(*this, matchingContext, nullptr); + return Searcher::eCallbackReturnContinue; + } + + for (ModuleSP module_sp : m_target_sp->GetImages().Modules()) { + // If this is the last level supplied, then call the callback directly, + // otherwise descend. + if (!ModulePasses(module_sp)) + continue; + + if (searcher.GetDepth() == lldb::eSearchDepthModule) { + SymbolContext matchingContext(m_target_sp, module_sp); + + Searcher::CallbackReturn shouldContinue = + searcher.SearchCallback(*this, matchingContext, nullptr); + if (shouldContinue == Searcher::eCallbackReturnStop || + shouldContinue == Searcher::eCallbackReturnPop) + return shouldContinue; + } else { + Searcher::CallbackReturn shouldContinue = + DoCUIteration(module_sp, context, searcher); + if (shouldContinue == Searcher::eCallbackReturnStop) + return shouldContinue; + else if (shouldContinue == Searcher::eCallbackReturnPop) + continue; + } + } + + return Searcher::eCallbackReturnContinue; +} + +Searcher::CallbackReturn +SearchFilter::DoCUIteration(const ModuleSP &module_sp, + const SymbolContext &context, Searcher &searcher) { + Searcher::CallbackReturn shouldContinue; + if (context.comp_unit != nullptr) { + if (CompUnitPasses(*context.comp_unit)) { + SymbolContext matchingContext(m_target_sp, module_sp, context.comp_unit); + return searcher.SearchCallback(*this, matchingContext, nullptr); + } + return Searcher::eCallbackReturnContinue; + } + + const size_t num_comp_units = module_sp->GetNumCompileUnits(); + for (size_t i = 0; i < num_comp_units; i++) { + CompUnitSP cu_sp(module_sp->GetCompileUnitAtIndex(i)); + if (!cu_sp) + continue; + if (!CompUnitPasses(*(cu_sp.get()))) + continue; + + if (searcher.GetDepth() == lldb::eSearchDepthCompUnit) { + SymbolContext matchingContext(m_target_sp, module_sp, cu_sp.get()); + + shouldContinue = searcher.SearchCallback(*this, matchingContext, nullptr); + + if (shouldContinue == Searcher::eCallbackReturnPop) + return Searcher::eCallbackReturnContinue; + else if (shouldContinue == Searcher::eCallbackReturnStop) + return shouldContinue; + continue; + } + + // First make sure this compile unit's functions are parsed + // since CompUnit::ForeachFunction only iterates over already + // parsed functions. + SymbolFile *sym_file = module_sp->GetSymbolFile(); + if (!sym_file) + continue; + if (!sym_file->ParseFunctions(*cu_sp)) + continue; + // If we got any functions, use ForeachFunction to do the iteration. + cu_sp->ForeachFunction([&](const FunctionSP &func_sp) { + if (!FunctionPasses(*func_sp.get())) + return false; // Didn't pass the filter, just keep going. + if (searcher.GetDepth() == lldb::eSearchDepthFunction) { + SymbolContext matchingContext(m_target_sp, module_sp, cu_sp.get(), + func_sp.get()); + shouldContinue = + searcher.SearchCallback(*this, matchingContext, nullptr); + } else { + shouldContinue = DoFunctionIteration(func_sp.get(), context, searcher); + } + return shouldContinue != Searcher::eCallbackReturnContinue; + }); + } + return Searcher::eCallbackReturnContinue; +} + +Searcher::CallbackReturn SearchFilter::DoFunctionIteration( + Function *function, const SymbolContext &context, Searcher &searcher) { + // FIXME: Implement... + return Searcher::eCallbackReturnContinue; +} + +// SearchFilterForUnconstrainedSearches: +// Selects a shared library matching a given file spec, consulting the targets +// "black list". +SearchFilterSP SearchFilterForUnconstrainedSearches::CreateFromStructuredData( + const lldb::TargetSP& target_sp, + const StructuredData::Dictionary &data_dict, + Status &error) { + // No options for an unconstrained search. + return std::make_shared<SearchFilterForUnconstrainedSearches>(target_sp); +} + +StructuredData::ObjectSP +SearchFilterForUnconstrainedSearches::SerializeToStructuredData() { + // The options dictionary is an empty dictionary: + auto result_sp = std::make_shared<StructuredData::Dictionary>(); + return WrapOptionsDict(result_sp); +} + +bool SearchFilterForUnconstrainedSearches::ModulePasses( + const FileSpec &module_spec) { + return !m_target_sp->ModuleIsExcludedForUnconstrainedSearches(module_spec); +} + +bool SearchFilterForUnconstrainedSearches::ModulePasses( + const lldb::ModuleSP &module_sp) { + if (!module_sp) + return true; + else if (m_target_sp->ModuleIsExcludedForUnconstrainedSearches(module_sp)) + return false; + return true; +} + +SearchFilterSP SearchFilterForUnconstrainedSearches::DoCreateCopy() { + return std::make_shared<SearchFilterForUnconstrainedSearches>(*this); +} + +// SearchFilterByModule: +// Selects a shared library matching a given file spec + +SearchFilterByModule::SearchFilterByModule(const lldb::TargetSP &target_sp, + const FileSpec &module) + : SearchFilter(target_sp, FilterTy::ByModule), m_module_spec(module) {} + +SearchFilterByModule::~SearchFilterByModule() = default; + +bool SearchFilterByModule::ModulePasses(const ModuleSP &module_sp) { + return (module_sp && + FileSpec::Match(m_module_spec, module_sp->GetFileSpec())); +} + +bool SearchFilterByModule::ModulePasses(const FileSpec &spec) { + return FileSpec::Match(m_module_spec, spec); +} + +bool SearchFilterByModule::AddressPasses(Address &address) { + // FIXME: Not yet implemented + return true; +} + +void SearchFilterByModule::Search(Searcher &searcher) { + if (!m_target_sp) + return; + + if (searcher.GetDepth() == lldb::eSearchDepthTarget) { + SymbolContext empty_sc; + empty_sc.target_sp = m_target_sp; + searcher.SearchCallback(*this, empty_sc, nullptr); + } + + // If the module file spec is a full path, then we can just find the one + // filespec that passes. Otherwise, we need to go through all modules and + // find the ones that match the file name. + + const ModuleList &target_modules = m_target_sp->GetImages(); + std::lock_guard<std::recursive_mutex> guard(target_modules.GetMutex()); + + for (ModuleSP module_sp : m_target_sp->GetImages().Modules()) { + if (FileSpec::Match(m_module_spec, module_sp->GetFileSpec())) { + SymbolContext matchingContext(m_target_sp, module_sp); + Searcher::CallbackReturn shouldContinue; + + shouldContinue = DoModuleIteration(matchingContext, searcher); + if (shouldContinue == Searcher::eCallbackReturnStop) + return; + } + } +} + +void SearchFilterByModule::GetDescription(Stream *s) { + s->PutCString(", module = "); + s->PutCString(m_module_spec.GetFilename().AsCString("<Unknown>")); +} + +uint32_t SearchFilterByModule::GetFilterRequiredItems() { + return eSymbolContextModule; +} + +void SearchFilterByModule::Dump(Stream *s) const {} + +SearchFilterSP SearchFilterByModule::DoCreateCopy() { + return std::make_shared<SearchFilterByModule>(*this); +} + +SearchFilterSP SearchFilterByModule::CreateFromStructuredData( + const lldb::TargetSP& target_sp, + const StructuredData::Dictionary &data_dict, + Status &error) { + StructuredData::Array *modules_array; + bool success = data_dict.GetValueForKeyAsArray(GetKey(OptionNames::ModList), + modules_array); + if (!success) { + error.SetErrorString("SFBM::CFSD: Could not find the module list key."); + return nullptr; + } + + size_t num_modules = modules_array->GetSize(); + if (num_modules > 1) { + error.SetErrorString( + "SFBM::CFSD: Only one modules allowed for SearchFilterByModule."); + return nullptr; + } + + std::optional<llvm::StringRef> maybe_module = + modules_array->GetItemAtIndexAsString(0); + if (!maybe_module) { + error.SetErrorString("SFBM::CFSD: filter module item not a string."); + return nullptr; + } + FileSpec module_spec(*maybe_module); + + return std::make_shared<SearchFilterByModule>(target_sp, module_spec); +} + +StructuredData::ObjectSP SearchFilterByModule::SerializeToStructuredData() { + auto options_dict_sp = std::make_shared<StructuredData::Dictionary>(); + auto module_array_sp = std::make_shared<StructuredData::Array>(); + module_array_sp->AddItem( + std::make_shared<StructuredData::String>(m_module_spec.GetPath())); + options_dict_sp->AddItem(GetKey(OptionNames::ModList), module_array_sp); + return WrapOptionsDict(options_dict_sp); +} + +// SearchFilterByModuleList: +// Selects a shared library matching a given file spec + +SearchFilterByModuleList::SearchFilterByModuleList( + const lldb::TargetSP &target_sp, const FileSpecList &module_list) + : SearchFilter(target_sp, FilterTy::ByModules), + m_module_spec_list(module_list) {} + +SearchFilterByModuleList::SearchFilterByModuleList( + const lldb::TargetSP &target_sp, const FileSpecList &module_list, + enum FilterTy filter_ty) + : SearchFilter(target_sp, filter_ty), m_module_spec_list(module_list) {} + +SearchFilterByModuleList::~SearchFilterByModuleList() = default; + +bool SearchFilterByModuleList::ModulePasses(const ModuleSP &module_sp) { + if (m_module_spec_list.GetSize() == 0) + return true; + + return module_sp && m_module_spec_list.FindFileIndex( + 0, module_sp->GetFileSpec(), false) != UINT32_MAX; +} + +bool SearchFilterByModuleList::ModulePasses(const FileSpec &spec) { + if (m_module_spec_list.GetSize() == 0) + return true; + + return m_module_spec_list.FindFileIndex(0, spec, true) != UINT32_MAX; +} + +bool SearchFilterByModuleList::AddressPasses(Address &address) { + // FIXME: Not yet implemented + return true; +} + +void SearchFilterByModuleList::Search(Searcher &searcher) { + if (!m_target_sp) + return; + + if (searcher.GetDepth() == lldb::eSearchDepthTarget) { + SymbolContext empty_sc; + empty_sc.target_sp = m_target_sp; + searcher.SearchCallback(*this, empty_sc, nullptr); + } + + // If the module file spec is a full path, then we can just find the one + // filespec that passes. Otherwise, we need to go through all modules and + // find the ones that match the file name. + for (ModuleSP module_sp : m_target_sp->GetImages().Modules()) { + if (m_module_spec_list.FindFileIndex(0, module_sp->GetFileSpec(), false) == + UINT32_MAX) + continue; + SymbolContext matchingContext(m_target_sp, module_sp); + Searcher::CallbackReturn shouldContinue; + + shouldContinue = DoModuleIteration(matchingContext, searcher); + if (shouldContinue == Searcher::eCallbackReturnStop) + return; + } +} + +void SearchFilterByModuleList::GetDescription(Stream *s) { + size_t num_modules = m_module_spec_list.GetSize(); + if (num_modules == 1) { + s->Printf(", module = "); + s->PutCString( + m_module_spec_list.GetFileSpecAtIndex(0).GetFilename().AsCString( + "<Unknown>")); + return; + } + + s->Printf(", modules(%" PRIu64 ") = ", (uint64_t)num_modules); + for (size_t i = 0; i < num_modules; i++) { + s->PutCString( + m_module_spec_list.GetFileSpecAtIndex(i).GetFilename().AsCString( + "<Unknown>")); + if (i != num_modules - 1) + s->PutCString(", "); + } +} + +uint32_t SearchFilterByModuleList::GetFilterRequiredItems() { + return eSymbolContextModule; +} + +void SearchFilterByModuleList::Dump(Stream *s) const {} + +lldb::SearchFilterSP SearchFilterByModuleList::DoCreateCopy() { + return std::make_shared<SearchFilterByModuleList>(*this); +} + +SearchFilterSP SearchFilterByModuleList::CreateFromStructuredData( + const lldb::TargetSP& target_sp, + const StructuredData::Dictionary &data_dict, + Status &error) { + StructuredData::Array *modules_array; + bool success = data_dict.GetValueForKeyAsArray(GetKey(OptionNames::ModList), + modules_array); + + if (!success) + return std::make_shared<SearchFilterByModuleList>(target_sp, + FileSpecList{}); + FileSpecList modules; + size_t num_modules = modules_array->GetSize(); + for (size_t i = 0; i < num_modules; i++) { + std::optional<llvm::StringRef> maybe_module = + modules_array->GetItemAtIndexAsString(i); + if (!maybe_module) { + error.SetErrorStringWithFormat( + "SFBM::CFSD: filter module item %zu not a string.", i); + return nullptr; + } + modules.EmplaceBack(*maybe_module); + } + return std::make_shared<SearchFilterByModuleList>(target_sp, modules); +} + +void SearchFilterByModuleList::SerializeUnwrapped( + StructuredData::DictionarySP &options_dict_sp) { + SerializeFileSpecList(options_dict_sp, OptionNames::ModList, + m_module_spec_list); +} + +StructuredData::ObjectSP SearchFilterByModuleList::SerializeToStructuredData() { + auto options_dict_sp = std::make_shared<StructuredData::Dictionary>(); + SerializeUnwrapped(options_dict_sp); + return WrapOptionsDict(options_dict_sp); +} + +// SearchFilterByModuleListAndCU: +// Selects a shared library matching a given file spec + +SearchFilterByModuleListAndCU::SearchFilterByModuleListAndCU( + const lldb::TargetSP &target_sp, const FileSpecList &module_list, + const FileSpecList &cu_list) + : SearchFilterByModuleList(target_sp, module_list, + FilterTy::ByModulesAndCU), + m_cu_spec_list(cu_list) {} + +SearchFilterByModuleListAndCU::~SearchFilterByModuleListAndCU() = default; + +lldb::SearchFilterSP SearchFilterByModuleListAndCU::CreateFromStructuredData( + const lldb::TargetSP& target_sp, + const StructuredData::Dictionary &data_dict, + Status &error) { + StructuredData::Array *modules_array = nullptr; + SearchFilterSP result_sp; + bool success = data_dict.GetValueForKeyAsArray(GetKey(OptionNames::ModList), + modules_array); + FileSpecList modules; + if (success) { + size_t num_modules = modules_array->GetSize(); + for (size_t i = 0; i < num_modules; i++) { + std::optional<llvm::StringRef> maybe_module = + modules_array->GetItemAtIndexAsString(i); + if (!maybe_module) { + error.SetErrorStringWithFormat( + "SFBM::CFSD: filter module item %zu not a string.", i); + return result_sp; + } + modules.EmplaceBack(*maybe_module); + } + } + + StructuredData::Array *cus_array = nullptr; + success = + data_dict.GetValueForKeyAsArray(GetKey(OptionNames::CUList), cus_array); + if (!success) { + error.SetErrorString("SFBM::CFSD: Could not find the CU list key."); + return result_sp; + } + + size_t num_cus = cus_array->GetSize(); + FileSpecList cus; + for (size_t i = 0; i < num_cus; i++) { + std::optional<llvm::StringRef> maybe_cu = + cus_array->GetItemAtIndexAsString(i); + if (!maybe_cu) { + error.SetErrorStringWithFormat( + "SFBM::CFSD: filter CU item %zu not a string.", i); + return nullptr; + } + cus.EmplaceBack(*maybe_cu); + } + + return std::make_shared<SearchFilterByModuleListAndCU>( + target_sp, modules, cus); +} + +StructuredData::ObjectSP +SearchFilterByModuleListAndCU::SerializeToStructuredData() { + auto options_dict_sp = std::make_shared<StructuredData::Dictionary>(); + SearchFilterByModuleList::SerializeUnwrapped(options_dict_sp); + SerializeFileSpecList(options_dict_sp, OptionNames::CUList, m_cu_spec_list); + return WrapOptionsDict(options_dict_sp); +} + +bool SearchFilterByModuleListAndCU::AddressPasses(Address &address) { + SymbolContext sym_ctx; + address.CalculateSymbolContext(&sym_ctx, eSymbolContextEverything); + if (!sym_ctx.comp_unit) { + if (m_cu_spec_list.GetSize() != 0) + return false; // Has no comp_unit so can't pass the file check. + } + FileSpec cu_spec; + if (sym_ctx.comp_unit) + cu_spec = sym_ctx.comp_unit->GetPrimaryFile(); + if (m_cu_spec_list.FindFileIndex(0, cu_spec, false) == UINT32_MAX) + return false; // Fails the file check + return SearchFilterByModuleList::ModulePasses(sym_ctx.module_sp); +} + +bool SearchFilterByModuleListAndCU::CompUnitPasses(FileSpec &fileSpec) { + return m_cu_spec_list.FindFileIndex(0, fileSpec, false) != UINT32_MAX; +} + +bool SearchFilterByModuleListAndCU::CompUnitPasses(CompileUnit &compUnit) { + bool in_cu_list = m_cu_spec_list.FindFileIndex(0, compUnit.GetPrimaryFile(), + false) != UINT32_MAX; + if (!in_cu_list) + return false; + + ModuleSP module_sp(compUnit.GetModule()); + if (!module_sp) + return true; + + return SearchFilterByModuleList::ModulePasses(module_sp); +} + +void SearchFilterByModuleListAndCU::Search(Searcher &searcher) { + if (!m_target_sp) + return; + + if (searcher.GetDepth() == lldb::eSearchDepthTarget) { + SymbolContext empty_sc; + empty_sc.target_sp = m_target_sp; + searcher.SearchCallback(*this, empty_sc, nullptr); + } + + // If the module file spec is a full path, then we can just find the one + // filespec that passes. Otherwise, we need to go through all modules and + // find the ones that match the file name. + + ModuleList matching_modules; + + bool no_modules_in_filter = m_module_spec_list.GetSize() == 0; + for (ModuleSP module_sp : m_target_sp->GetImages().Modules()) { + if (!no_modules_in_filter && + m_module_spec_list.FindFileIndex(0, module_sp->GetFileSpec(), false) == + UINT32_MAX) + continue; + + SymbolContext matchingContext(m_target_sp, module_sp); + Searcher::CallbackReturn shouldContinue; + + if (searcher.GetDepth() == lldb::eSearchDepthModule) { + shouldContinue = DoModuleIteration(matchingContext, searcher); + if (shouldContinue == Searcher::eCallbackReturnStop) + return; + continue; + } + + const size_t num_cu = module_sp->GetNumCompileUnits(); + for (size_t cu_idx = 0; cu_idx < num_cu; cu_idx++) { + CompUnitSP cu_sp = module_sp->GetCompileUnitAtIndex(cu_idx); + matchingContext.comp_unit = cu_sp.get(); + if (!matchingContext.comp_unit) + continue; + if (m_cu_spec_list.FindFileIndex( + 0, matchingContext.comp_unit->GetPrimaryFile(), false) == + UINT32_MAX) + continue; + shouldContinue = DoCUIteration(module_sp, matchingContext, searcher); + if (shouldContinue == Searcher::eCallbackReturnStop) + return; + } + } +} + +void SearchFilterByModuleListAndCU::GetDescription(Stream *s) { + size_t num_modules = m_module_spec_list.GetSize(); + if (num_modules == 1) { + s->Printf(", module = "); + s->PutCString( + m_module_spec_list.GetFileSpecAtIndex(0).GetFilename().AsCString( + "<Unknown>")); + } else if (num_modules > 0) { + s->Printf(", modules(%" PRIu64 ") = ", static_cast<uint64_t>(num_modules)); + for (size_t i = 0; i < num_modules; i++) { + s->PutCString( + m_module_spec_list.GetFileSpecAtIndex(i).GetFilename().AsCString( + "<Unknown>")); + if (i != num_modules - 1) + s->PutCString(", "); + } + } +} + +uint32_t SearchFilterByModuleListAndCU::GetFilterRequiredItems() { + return eSymbolContextModule | eSymbolContextCompUnit; +} + +void SearchFilterByModuleListAndCU::Dump(Stream *s) const {} + +SearchFilterSP SearchFilterByModuleListAndCU::DoCreateCopy() { + return std::make_shared<SearchFilterByModuleListAndCU>(*this); +} diff --git a/contrib/llvm-project/lldb/source/Core/Section.cpp b/contrib/llvm-project/lldb/source/Core/Section.cpp new file mode 100644 index 000000000000..0763e88d4608 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/Section.cpp @@ -0,0 +1,712 @@ +//===-- Section.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/Core/Section.h" +#include "lldb/Core/Address.h" +#include "lldb/Core/Module.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/VMRange.h" + +#include <cinttypes> +#include <limits> +#include <utility> + +namespace lldb_private { +class DataExtractor; +} +using namespace lldb; +using namespace lldb_private; + +const char *Section::GetTypeAsCString() const { + switch (m_type) { + case eSectionTypeInvalid: + return "invalid"; + case eSectionTypeCode: + return "code"; + case eSectionTypeContainer: + return "container"; + case eSectionTypeData: + return "data"; + case eSectionTypeDataCString: + return "data-cstr"; + case eSectionTypeDataCStringPointers: + return "data-cstr-ptr"; + case eSectionTypeDataSymbolAddress: + return "data-symbol-addr"; + case eSectionTypeData4: + return "data-4-byte"; + case eSectionTypeData8: + return "data-8-byte"; + case eSectionTypeData16: + return "data-16-byte"; + case eSectionTypeDataPointers: + return "data-ptrs"; + case eSectionTypeDebug: + return "debug"; + case eSectionTypeZeroFill: + return "zero-fill"; + case eSectionTypeDataObjCMessageRefs: + return "objc-message-refs"; + case eSectionTypeDataObjCCFStrings: + return "objc-cfstrings"; + case eSectionTypeDWARFDebugAbbrev: + return "dwarf-abbrev"; + case eSectionTypeDWARFDebugAbbrevDwo: + return "dwarf-abbrev-dwo"; + case eSectionTypeDWARFDebugAddr: + return "dwarf-addr"; + case eSectionTypeDWARFDebugAranges: + return "dwarf-aranges"; + case eSectionTypeDWARFDebugCuIndex: + return "dwarf-cu-index"; + case eSectionTypeDWARFDebugTuIndex: + return "dwarf-tu-index"; + case eSectionTypeDWARFDebugFrame: + return "dwarf-frame"; + case eSectionTypeDWARFDebugInfo: + return "dwarf-info"; + case eSectionTypeDWARFDebugInfoDwo: + return "dwarf-info-dwo"; + case eSectionTypeDWARFDebugLine: + return "dwarf-line"; + case eSectionTypeDWARFDebugLineStr: + return "dwarf-line-str"; + case eSectionTypeDWARFDebugLoc: + return "dwarf-loc"; + case eSectionTypeDWARFDebugLocDwo: + return "dwarf-loc-dwo"; + case eSectionTypeDWARFDebugLocLists: + return "dwarf-loclists"; + case eSectionTypeDWARFDebugLocListsDwo: + return "dwarf-loclists-dwo"; + case eSectionTypeDWARFDebugMacInfo: + return "dwarf-macinfo"; + case eSectionTypeDWARFDebugMacro: + return "dwarf-macro"; + case eSectionTypeDWARFDebugPubNames: + return "dwarf-pubnames"; + case eSectionTypeDWARFDebugPubTypes: + return "dwarf-pubtypes"; + case eSectionTypeDWARFDebugRanges: + return "dwarf-ranges"; + case eSectionTypeDWARFDebugRngLists: + return "dwarf-rnglists"; + case eSectionTypeDWARFDebugRngListsDwo: + return "dwarf-rnglists-dwo"; + case eSectionTypeDWARFDebugStr: + return "dwarf-str"; + case eSectionTypeDWARFDebugStrDwo: + return "dwarf-str-dwo"; + case eSectionTypeDWARFDebugStrOffsets: + return "dwarf-str-offsets"; + case eSectionTypeDWARFDebugStrOffsetsDwo: + return "dwarf-str-offsets-dwo"; + case eSectionTypeDWARFDebugTypes: + return "dwarf-types"; + case eSectionTypeDWARFDebugTypesDwo: + return "dwarf-types-dwo"; + case eSectionTypeDWARFDebugNames: + return "dwarf-names"; + case eSectionTypeELFSymbolTable: + return "elf-symbol-table"; + case eSectionTypeELFDynamicSymbols: + return "elf-dynamic-symbols"; + case eSectionTypeELFRelocationEntries: + return "elf-relocation-entries"; + case eSectionTypeELFDynamicLinkInfo: + return "elf-dynamic-link-info"; + case eSectionTypeDWARFAppleNames: + return "apple-names"; + case eSectionTypeDWARFAppleTypes: + return "apple-types"; + case eSectionTypeDWARFAppleNamespaces: + return "apple-namespaces"; + case eSectionTypeDWARFAppleObjC: + return "apple-objc"; + case eSectionTypeEHFrame: + return "eh-frame"; + case eSectionTypeARMexidx: + return "ARM.exidx"; + case eSectionTypeARMextab: + return "ARM.extab"; + case eSectionTypeCompactUnwind: + return "compact-unwind"; + case eSectionTypeGoSymtab: + return "go-symtab"; + case eSectionTypeAbsoluteAddress: + return "absolute"; + case eSectionTypeDWARFGNUDebugAltLink: + return "dwarf-gnu-debugaltlink"; + case eSectionTypeCTF: + return "ctf"; + case eSectionTypeOther: + return "regular"; + case eSectionTypeSwiftModules: + return "swift-modules"; + } + return "unknown"; +} + +Section::Section(const ModuleSP &module_sp, ObjectFile *obj_file, + user_id_t sect_id, ConstString name, + SectionType sect_type, addr_t file_addr, addr_t byte_size, + lldb::offset_t file_offset, lldb::offset_t file_size, + uint32_t log2align, uint32_t flags, + uint32_t target_byte_size /*=1*/) + : ModuleChild(module_sp), UserID(sect_id), Flags(flags), + m_obj_file(obj_file), m_type(sect_type), m_parent_wp(), m_name(name), + m_file_addr(file_addr), m_byte_size(byte_size), + m_file_offset(file_offset), m_file_size(file_size), + m_log2align(log2align), m_children(), m_fake(false), m_encrypted(false), + m_thread_specific(false), m_readable(false), m_writable(false), + m_executable(false), m_relocated(false), m_target_byte_size(target_byte_size) { +} + +Section::Section(const lldb::SectionSP &parent_section_sp, + const ModuleSP &module_sp, ObjectFile *obj_file, + user_id_t sect_id, ConstString name, + SectionType sect_type, addr_t file_addr, addr_t byte_size, + lldb::offset_t file_offset, lldb::offset_t file_size, + uint32_t log2align, uint32_t flags, + uint32_t target_byte_size /*=1*/) + : ModuleChild(module_sp), UserID(sect_id), Flags(flags), + m_obj_file(obj_file), m_type(sect_type), m_parent_wp(), m_name(name), + m_file_addr(file_addr), m_byte_size(byte_size), + m_file_offset(file_offset), m_file_size(file_size), + m_log2align(log2align), m_children(), m_fake(false), m_encrypted(false), + m_thread_specific(false), m_readable(false), m_writable(false), + m_executable(false), m_relocated(false), m_target_byte_size(target_byte_size) { + if (parent_section_sp) + m_parent_wp = parent_section_sp; +} + +Section::~Section() = default; + +addr_t Section::GetFileAddress() const { + SectionSP parent_sp(GetParent()); + if (parent_sp) { + // This section has a parent which means m_file_addr is an offset into the + // parent section, so the file address for this section is the file address + // of the parent plus the offset + return parent_sp->GetFileAddress() + m_file_addr; + } + // This section has no parent, so m_file_addr is the file base address + return m_file_addr; +} + +bool Section::SetFileAddress(lldb::addr_t file_addr) { + SectionSP parent_sp(GetParent()); + if (parent_sp) { + if (m_file_addr >= file_addr) + return parent_sp->SetFileAddress(m_file_addr - file_addr); + return false; + } else { + // This section has no parent, so m_file_addr is the file base address + m_file_addr = file_addr; + return true; + } +} + +lldb::addr_t Section::GetOffset() const { + // This section has a parent which means m_file_addr is an offset. + SectionSP parent_sp(GetParent()); + if (parent_sp) + return m_file_addr; + + // This section has no parent, so there is no offset to be had + return 0; +} + +addr_t Section::GetLoadBaseAddress(Target *target) const { + addr_t load_base_addr = LLDB_INVALID_ADDRESS; + SectionSP parent_sp(GetParent()); + if (parent_sp) { + load_base_addr = parent_sp->GetLoadBaseAddress(target); + if (load_base_addr != LLDB_INVALID_ADDRESS) + load_base_addr += GetOffset(); + } + if (load_base_addr == LLDB_INVALID_ADDRESS) { + load_base_addr = target->GetSectionLoadList().GetSectionLoadAddress( + const_cast<Section *>(this)->shared_from_this()); + } + return load_base_addr; +} + +bool Section::ResolveContainedAddress(addr_t offset, Address &so_addr, + bool allow_section_end) const { + const size_t num_children = m_children.GetSize(); + for (size_t i = 0; i < num_children; i++) { + Section *child_section = m_children.GetSectionAtIndex(i).get(); + + addr_t child_offset = child_section->GetOffset(); + if (child_offset <= offset && + offset - child_offset < + child_section->GetByteSize() + (allow_section_end ? 1 : 0)) + return child_section->ResolveContainedAddress(offset - child_offset, + so_addr, allow_section_end); + } + so_addr.SetOffset(offset); + so_addr.SetSection(const_cast<Section *>(this)->shared_from_this()); + + // Ensure that there are no orphaned (i.e., moduleless) sections. + assert(GetModule().get()); + return true; +} + +bool Section::ContainsFileAddress(addr_t vm_addr) const { + const addr_t file_addr = GetFileAddress(); + if (file_addr != LLDB_INVALID_ADDRESS && !IsThreadSpecific()) { + if (file_addr <= vm_addr) { + const addr_t offset = (vm_addr - file_addr) * m_target_byte_size; + return offset < GetByteSize(); + } + } + return false; +} + +void Section::Dump(llvm::raw_ostream &s, unsigned indent, Target *target, + uint32_t depth) const { + s.indent(indent); + s << llvm::format("0x%16.16" PRIx64 " %-22s ", GetID(), GetTypeAsCString()); + bool resolved = true; + addr_t addr = LLDB_INVALID_ADDRESS; + + if (GetByteSize() == 0) + s.indent(39); + else { + if (target) + addr = GetLoadBaseAddress(target); + + if (addr == LLDB_INVALID_ADDRESS) { + if (target) + resolved = false; + addr = GetFileAddress(); + } + + VMRange range(addr, addr + m_byte_size); + range.Dump(s, 0); + } + + s << llvm::format("%c %c%c%c 0x%8.8" PRIx64 " 0x%8.8" PRIx64 " 0x%8.8x ", + resolved ? ' ' : '*', m_readable ? 'r' : '-', + m_writable ? 'w' : '-', m_executable ? 'x' : '-', + m_file_offset, m_file_size, Get()); + + DumpName(s); + + s << "\n"; + + if (depth > 0) + m_children.Dump(s, indent, target, false, depth - 1); +} + +void Section::DumpName(llvm::raw_ostream &s) const { + SectionSP parent_sp(GetParent()); + if (parent_sp) { + parent_sp->DumpName(s); + s << '.'; + } else { + // The top most section prints the module basename + const char *name = nullptr; + ModuleSP module_sp(GetModule()); + + if (m_obj_file) { + const FileSpec &file_spec = m_obj_file->GetFileSpec(); + name = file_spec.GetFilename().AsCString(); + } + if ((!name || !name[0]) && module_sp) + name = module_sp->GetFileSpec().GetFilename().AsCString(); + if (name && name[0]) + s << name << '.'; + } + s << m_name; +} + +bool Section::IsDescendant(const Section *section) { + if (this == section) + return true; + SectionSP parent_sp(GetParent()); + if (parent_sp) + return parent_sp->IsDescendant(section); + return false; +} + +bool Section::Slide(addr_t slide_amount, bool slide_children) { + if (m_file_addr != LLDB_INVALID_ADDRESS) { + if (slide_amount == 0) + return true; + + m_file_addr += slide_amount; + + if (slide_children) + m_children.Slide(slide_amount, slide_children); + + return true; + } + return false; +} + +/// Get the permissions as OR'ed bits from lldb::Permissions +uint32_t Section::GetPermissions() const { + uint32_t permissions = 0; + if (m_readable) + permissions |= ePermissionsReadable; + if (m_writable) + permissions |= ePermissionsWritable; + if (m_executable) + permissions |= ePermissionsExecutable; + return permissions; +} + +/// Set the permissions using bits OR'ed from lldb::Permissions +void Section::SetPermissions(uint32_t permissions) { + m_readable = (permissions & ePermissionsReadable) != 0; + m_writable = (permissions & ePermissionsWritable) != 0; + m_executable = (permissions & ePermissionsExecutable) != 0; +} + +lldb::offset_t Section::GetSectionData(void *dst, lldb::offset_t dst_len, + lldb::offset_t offset) { + if (m_obj_file) + return m_obj_file->ReadSectionData(this, offset, dst, dst_len); + return 0; +} + +lldb::offset_t Section::GetSectionData(DataExtractor §ion_data) { + if (m_obj_file) + return m_obj_file->ReadSectionData(this, section_data); + return 0; +} + +bool Section::ContainsOnlyDebugInfo() const { + switch (m_type) { + case eSectionTypeInvalid: + case eSectionTypeCode: + case eSectionTypeContainer: + case eSectionTypeData: + case eSectionTypeDataCString: + case eSectionTypeDataCStringPointers: + case eSectionTypeDataSymbolAddress: + case eSectionTypeData4: + case eSectionTypeData8: + case eSectionTypeData16: + case eSectionTypeDataPointers: + case eSectionTypeZeroFill: + case eSectionTypeDataObjCMessageRefs: + case eSectionTypeDataObjCCFStrings: + case eSectionTypeELFSymbolTable: + case eSectionTypeELFDynamicSymbols: + case eSectionTypeELFRelocationEntries: + case eSectionTypeELFDynamicLinkInfo: + case eSectionTypeEHFrame: + case eSectionTypeARMexidx: + case eSectionTypeARMextab: + case eSectionTypeCompactUnwind: + case eSectionTypeGoSymtab: + case eSectionTypeAbsoluteAddress: + case eSectionTypeOther: + // Used for "__dof_cache" in mach-o or ".debug" for COFF which isn't debug + // information that we parse at all. This was causing system files with no + // debug info to show debug info byte sizes in the "statistics dump" output + // for each module. New "eSectionType" enums should be created for dedicated + // debug info that has a predefined format if we wish for these sections to + // show up as debug info. + case eSectionTypeDebug: + return false; + + case eSectionTypeDWARFDebugAbbrev: + case eSectionTypeDWARFDebugAbbrevDwo: + case eSectionTypeDWARFDebugAddr: + case eSectionTypeDWARFDebugAranges: + case eSectionTypeDWARFDebugCuIndex: + case eSectionTypeDWARFDebugTuIndex: + case eSectionTypeDWARFDebugFrame: + case eSectionTypeDWARFDebugInfo: + case eSectionTypeDWARFDebugInfoDwo: + case eSectionTypeDWARFDebugLine: + case eSectionTypeDWARFDebugLineStr: + case eSectionTypeDWARFDebugLoc: + case eSectionTypeDWARFDebugLocDwo: + case eSectionTypeDWARFDebugLocLists: + case eSectionTypeDWARFDebugLocListsDwo: + case eSectionTypeDWARFDebugMacInfo: + case eSectionTypeDWARFDebugMacro: + case eSectionTypeDWARFDebugPubNames: + case eSectionTypeDWARFDebugPubTypes: + case eSectionTypeDWARFDebugRanges: + case eSectionTypeDWARFDebugRngLists: + case eSectionTypeDWARFDebugRngListsDwo: + case eSectionTypeDWARFDebugStr: + case eSectionTypeDWARFDebugStrDwo: + case eSectionTypeDWARFDebugStrOffsets: + case eSectionTypeDWARFDebugStrOffsetsDwo: + case eSectionTypeDWARFDebugTypes: + case eSectionTypeDWARFDebugTypesDwo: + case eSectionTypeDWARFDebugNames: + case eSectionTypeDWARFAppleNames: + case eSectionTypeDWARFAppleTypes: + case eSectionTypeDWARFAppleNamespaces: + case eSectionTypeDWARFAppleObjC: + case eSectionTypeDWARFGNUDebugAltLink: + case eSectionTypeCTF: + case eSectionTypeSwiftModules: + return true; + } + return false; +} + + +#pragma mark SectionList + +SectionList &SectionList::operator=(const SectionList &rhs) { + if (this != &rhs) + m_sections = rhs.m_sections; + return *this; +} + +size_t SectionList::AddSection(const lldb::SectionSP §ion_sp) { + if (section_sp) { + size_t section_index = m_sections.size(); + m_sections.push_back(section_sp); + return section_index; + } + + return std::numeric_limits<size_t>::max(); +} + +// Warning, this can be slow as it's removing items from a std::vector. +bool SectionList::DeleteSection(size_t idx) { + if (idx < m_sections.size()) { + m_sections.erase(m_sections.begin() + idx); + return true; + } + return false; +} + +size_t SectionList::FindSectionIndex(const Section *sect) { + iterator sect_iter; + iterator begin = m_sections.begin(); + iterator end = m_sections.end(); + for (sect_iter = begin; sect_iter != end; ++sect_iter) { + if (sect_iter->get() == sect) { + // The secton was already in this section list + return std::distance(begin, sect_iter); + } + } + return UINT32_MAX; +} + +size_t SectionList::AddUniqueSection(const lldb::SectionSP §_sp) { + size_t sect_idx = FindSectionIndex(sect_sp.get()); + if (sect_idx == UINT32_MAX) { + sect_idx = AddSection(sect_sp); + } + return sect_idx; +} + +bool SectionList::ReplaceSection(user_id_t sect_id, + const lldb::SectionSP §_sp, + uint32_t depth) { + iterator sect_iter, end = m_sections.end(); + for (sect_iter = m_sections.begin(); sect_iter != end; ++sect_iter) { + if ((*sect_iter)->GetID() == sect_id) { + *sect_iter = sect_sp; + return true; + } else if (depth > 0) { + if ((*sect_iter) + ->GetChildren() + .ReplaceSection(sect_id, sect_sp, depth - 1)) + return true; + } + } + return false; +} + +size_t SectionList::GetNumSections(uint32_t depth) const { + size_t count = m_sections.size(); + if (depth > 0) { + const_iterator sect_iter, end = m_sections.end(); + for (sect_iter = m_sections.begin(); sect_iter != end; ++sect_iter) { + count += (*sect_iter)->GetChildren().GetNumSections(depth - 1); + } + } + return count; +} + +SectionSP SectionList::GetSectionAtIndex(size_t idx) const { + SectionSP sect_sp; + if (idx < m_sections.size()) + sect_sp = m_sections[idx]; + return sect_sp; +} + +SectionSP +SectionList::FindSectionByName(ConstString section_dstr) const { + SectionSP sect_sp; + // Check if we have a valid section string + if (section_dstr && !m_sections.empty()) { + const_iterator sect_iter; + const_iterator end = m_sections.end(); + for (sect_iter = m_sections.begin(); + sect_iter != end && sect_sp.get() == nullptr; ++sect_iter) { + Section *child_section = sect_iter->get(); + if (child_section) { + if (child_section->GetName() == section_dstr) { + sect_sp = *sect_iter; + } else { + sect_sp = + child_section->GetChildren().FindSectionByName(section_dstr); + } + } + } + } + return sect_sp; +} + +SectionSP SectionList::FindSectionByID(user_id_t sect_id) const { + SectionSP sect_sp; + if (sect_id) { + const_iterator sect_iter; + const_iterator end = m_sections.end(); + for (sect_iter = m_sections.begin(); + sect_iter != end && sect_sp.get() == nullptr; ++sect_iter) { + if ((*sect_iter)->GetID() == sect_id) { + sect_sp = *sect_iter; + break; + } else { + sect_sp = (*sect_iter)->GetChildren().FindSectionByID(sect_id); + } + } + } + return sect_sp; +} + +SectionSP SectionList::FindSectionByType(SectionType sect_type, + bool check_children, + size_t start_idx) const { + SectionSP sect_sp; + size_t num_sections = m_sections.size(); + for (size_t idx = start_idx; idx < num_sections; ++idx) { + if (m_sections[idx]->GetType() == sect_type) { + sect_sp = m_sections[idx]; + break; + } else if (check_children) { + sect_sp = m_sections[idx]->GetChildren().FindSectionByType( + sect_type, check_children, 0); + if (sect_sp) + break; + } + } + return sect_sp; +} + +SectionSP SectionList::FindSectionContainingFileAddress(addr_t vm_addr, + uint32_t depth) const { + SectionSP sect_sp; + const_iterator sect_iter; + const_iterator end = m_sections.end(); + for (sect_iter = m_sections.begin(); + sect_iter != end && sect_sp.get() == nullptr; ++sect_iter) { + Section *sect = sect_iter->get(); + if (sect->ContainsFileAddress(vm_addr)) { + // The file address is in this section. We need to make sure one of our + // child sections doesn't contain this address as well as obeying the + // depth limit that was passed in. + if (depth > 0) + sect_sp = sect->GetChildren().FindSectionContainingFileAddress( + vm_addr, depth - 1); + + if (sect_sp.get() == nullptr && !sect->IsFake()) + sect_sp = *sect_iter; + } + } + return sect_sp; +} + +bool SectionList::ContainsSection(user_id_t sect_id) const { + return FindSectionByID(sect_id).get() != nullptr; +} + +void SectionList::Dump(llvm::raw_ostream &s, unsigned indent, Target *target, + bool show_header, uint32_t depth) const { + bool target_has_loaded_sections = + target && !target->GetSectionLoadList().IsEmpty(); + if (show_header && !m_sections.empty()) { + s.indent(indent); + s << llvm::formatv( + "SectID Type {0} Address " + " Perm File Off. File Size Flags Section Name\n", + target_has_loaded_sections ? "Load" : "File"); + s.indent(indent); + s << "------------------ ---------------------- " + "--------------------------------------- ---- ---------- ---------- " + "---------- ----------------------------\n"; + } + + for (const auto §ion_sp : m_sections) + section_sp->Dump(s, indent, target_has_loaded_sections ? target : nullptr, + depth); +} + +size_t SectionList::Slide(addr_t slide_amount, bool slide_children) { + size_t count = 0; + const_iterator pos, end = m_sections.end(); + for (pos = m_sections.begin(); pos != end; ++pos) { + if ((*pos)->Slide(slide_amount, slide_children)) + ++count; + } + return count; +} + +uint64_t SectionList::GetDebugInfoSize() const { + uint64_t debug_info_size = 0; + for (const auto §ion : m_sections) { + const SectionList &sub_sections = section->GetChildren(); + if (sub_sections.GetSize() > 0) + debug_info_size += sub_sections.GetDebugInfoSize(); + else if (section->ContainsOnlyDebugInfo()) + debug_info_size += section->GetFileSize(); + } + return debug_info_size; +} + +namespace llvm { +namespace json { + +bool fromJSON(const llvm::json::Value &value, + lldb_private::JSONSection §ion, llvm::json::Path path) { + llvm::json::ObjectMapper o(value, path); + return o && o.map("name", section.name) && o.map("type", section.type) && + o.map("size", section.address) && o.map("size", section.size); +} + +bool fromJSON(const llvm::json::Value &value, lldb::SectionType &type, + llvm::json::Path path) { + if (auto str = value.getAsString()) { + type = llvm::StringSwitch<lldb::SectionType>(*str) + .Case("code", eSectionTypeCode) + .Case("container", eSectionTypeContainer) + .Case("data", eSectionTypeData) + .Case("debug", eSectionTypeDebug) + .Default(eSectionTypeInvalid); + + if (type == eSectionTypeInvalid) { + path.report("invalid section type"); + return false; + } + + return true; + } + path.report("expected string"); + return false; +} +} // namespace json +} // namespace llvm diff --git a/contrib/llvm-project/lldb/source/Core/SourceLocationSpec.cpp b/contrib/llvm-project/lldb/source/Core/SourceLocationSpec.cpp new file mode 100644 index 000000000000..7165d04955d6 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/SourceLocationSpec.cpp @@ -0,0 +1,83 @@ +//===-- SourceLocationSpec.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/Core/SourceLocationSpec.h" +#include "lldb/Utility/StreamString.h" +#include "llvm/ADT/StringExtras.h" +#include <optional> + +using namespace lldb; +using namespace lldb_private; + +SourceLocationSpec::SourceLocationSpec(FileSpec file_spec, uint32_t line, + std::optional<uint16_t> column, + bool check_inlines, bool exact_match) + : m_declaration(file_spec, line, + column.value_or(LLDB_INVALID_COLUMN_NUMBER)), + m_check_inlines(check_inlines), m_exact_match(exact_match) {} + +SourceLocationSpec::operator bool() const { return m_declaration.IsValid(); } + +bool SourceLocationSpec::operator!() const { return !operator bool(); } + +bool SourceLocationSpec::operator==(const SourceLocationSpec &rhs) const { + return m_declaration == rhs.m_declaration && + m_check_inlines == rhs.GetCheckInlines() && + m_exact_match == rhs.GetExactMatch(); +} + +bool SourceLocationSpec::operator!=(const SourceLocationSpec &rhs) const { + return !(*this == rhs); +} + +bool SourceLocationSpec::operator<(const SourceLocationSpec &rhs) const { + return SourceLocationSpec::Compare(*this, rhs) < 0; +} + +Stream &lldb_private::operator<<(Stream &s, const SourceLocationSpec &loc) { + loc.Dump(s); + return s; +} + +int SourceLocationSpec::Compare(const SourceLocationSpec &lhs, + const SourceLocationSpec &rhs) { + return Declaration::Compare(lhs.m_declaration, rhs.m_declaration); +} + +bool SourceLocationSpec::Equal(const SourceLocationSpec &lhs, + const SourceLocationSpec &rhs, bool full) { + return full ? lhs == rhs + : (lhs.GetFileSpec() == rhs.GetFileSpec() && + lhs.GetLine() == rhs.GetLine()); +} + +void SourceLocationSpec::Dump(Stream &s) const { + s << "check inlines = " << llvm::toStringRef(m_check_inlines); + s << ", exact match = " << llvm::toStringRef(m_exact_match); + m_declaration.Dump(&s, true); +} + +std::string SourceLocationSpec::GetString() const { + StreamString ss; + Dump(ss); + return ss.GetString().str(); +} + +std::optional<uint32_t> SourceLocationSpec::GetLine() const { + uint32_t line = m_declaration.GetLine(); + if (line == 0 || line == LLDB_INVALID_LINE_NUMBER) + return std::nullopt; + return line; +} + +std::optional<uint16_t> SourceLocationSpec::GetColumn() const { + uint16_t column = m_declaration.GetColumn(); + if (column == LLDB_INVALID_COLUMN_NUMBER) + return std::nullopt; + return column; +} diff --git a/contrib/llvm-project/lldb/source/Core/SourceManager.cpp b/contrib/llvm-project/lldb/source/Core/SourceManager.cpp new file mode 100644 index 000000000000..0d70c554e534 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/SourceManager.cpp @@ -0,0 +1,833 @@ +//===-- SourceManager.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/Core/SourceManager.h" + +#include "lldb/Core/Address.h" +#include "lldb/Core/AddressRange.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/FormatEntity.h" +#include "lldb/Core/Highlighter.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleList.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Symbol/CompileUnit.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/LineEntry.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Target/PathMappingList.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/AnsiTerminal.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/DataBuffer.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/RegularExpression.h" +#include "lldb/Utility/Stream.h" +#include "lldb/lldb-enumerations.h" + +#include "llvm/ADT/Twine.h" + +#include <memory> +#include <optional> +#include <utility> + +#include <cassert> +#include <cstdio> + +namespace lldb_private { +class ExecutionContext; +} +namespace lldb_private { +class ValueObject; +} + +using namespace lldb; +using namespace lldb_private; + +static inline bool is_newline_char(char ch) { return ch == '\n' || ch == '\r'; } + +static void resolve_tilde(FileSpec &file_spec) { + if (!FileSystem::Instance().Exists(file_spec) && + file_spec.GetDirectory() && + file_spec.GetDirectory().GetCString()[0] == '~') { + FileSystem::Instance().Resolve(file_spec); + } +} + +// SourceManager constructor +SourceManager::SourceManager(const TargetSP &target_sp) + : m_last_line(0), m_last_count(0), m_default_set(false), + m_target_wp(target_sp), + m_debugger_wp(target_sp->GetDebugger().shared_from_this()) {} + +SourceManager::SourceManager(const DebuggerSP &debugger_sp) + : m_last_line(0), m_last_count(0), m_default_set(false), m_target_wp(), + m_debugger_wp(debugger_sp) {} + +// Destructor +SourceManager::~SourceManager() = default; + +SourceManager::FileSP SourceManager::GetFile(const FileSpec &file_spec) { + if (!file_spec) + return {}; + + Log *log = GetLog(LLDBLog::Source); + + DebuggerSP debugger_sp(m_debugger_wp.lock()); + TargetSP target_sp(m_target_wp.lock()); + + if (!debugger_sp || !debugger_sp->GetUseSourceCache()) { + LLDB_LOG(log, "Source file caching disabled: creating new source file: {0}", + file_spec); + if (target_sp) + return std::make_shared<File>(file_spec, target_sp); + return std::make_shared<File>(file_spec, debugger_sp); + } + + ProcessSP process_sp = target_sp ? target_sp->GetProcessSP() : ProcessSP(); + + // Check the process source cache first. This is the fast path which avoids + // touching the file system unless the path remapping has changed. + if (process_sp) { + if (FileSP file_sp = + process_sp->GetSourceFileCache().FindSourceFile(file_spec)) { + LLDB_LOG(log, "Found source file in the process cache: {0}", file_spec); + if (file_sp->PathRemappingIsStale()) { + LLDB_LOG(log, "Path remapping is stale: removing file from caches: {0}", + file_spec); + + // Remove the file from the debugger and process cache. Otherwise we'll + // hit the same issue again below when querying the debugger cache. + debugger_sp->GetSourceFileCache().RemoveSourceFile(file_sp); + process_sp->GetSourceFileCache().RemoveSourceFile(file_sp); + + file_sp.reset(); + } else { + return file_sp; + } + } + } + + // Cache miss in the process cache. Check the debugger source cache. + FileSP file_sp = debugger_sp->GetSourceFileCache().FindSourceFile(file_spec); + + // We found the file in the debugger cache. Check if anything invalidated our + // cache result. + if (file_sp) + LLDB_LOG(log, "Found source file in the debugger cache: {0}", file_spec); + + // Check if the path remapping has changed. + if (file_sp && file_sp->PathRemappingIsStale()) { + LLDB_LOG(log, "Path remapping is stale: {0}", file_spec); + file_sp.reset(); + } + + // Check if the modification time has changed. + if (file_sp && file_sp->ModificationTimeIsStale()) { + LLDB_LOG(log, "Modification time is stale: {0}", file_spec); + file_sp.reset(); + } + + // Check if the file exists on disk. + if (file_sp && !FileSystem::Instance().Exists(file_sp->GetFileSpec())) { + LLDB_LOG(log, "File doesn't exist on disk: {0}", file_spec); + file_sp.reset(); + } + + // If at this point we don't have a valid file, it means we either didn't find + // it in the debugger cache or something caused it to be invalidated. + if (!file_sp) { + LLDB_LOG(log, "Creating and caching new source file: {0}", file_spec); + + // (Re)create the file. + if (target_sp) + file_sp = std::make_shared<File>(file_spec, target_sp); + else + file_sp = std::make_shared<File>(file_spec, debugger_sp); + + // Add the file to the debugger and process cache. If the file was + // invalidated, this will overwrite it. + debugger_sp->GetSourceFileCache().AddSourceFile(file_spec, file_sp); + if (process_sp) + process_sp->GetSourceFileCache().AddSourceFile(file_spec, file_sp); + } + + return file_sp; +} + +static bool should_highlight_source(DebuggerSP debugger_sp) { + if (!debugger_sp) + return false; + + // We don't use ANSI stop column formatting if the debugger doesn't think it + // should be using color. + if (!debugger_sp->GetUseColor()) + return false; + + return debugger_sp->GetHighlightSource(); +} + +static bool should_show_stop_column_with_ansi(DebuggerSP debugger_sp) { + // We don't use ANSI stop column formatting if we can't lookup values from + // the debugger. + if (!debugger_sp) + return false; + + // We don't use ANSI stop column formatting if the debugger doesn't think it + // should be using color. + if (!debugger_sp->GetUseColor()) + return false; + + // We only use ANSI stop column formatting if we're either supposed to show + // ANSI where available (which we know we have when we get to this point), or + // if we're only supposed to use ANSI. + const auto value = debugger_sp->GetStopShowColumn(); + return ((value == eStopShowColumnAnsiOrCaret) || + (value == eStopShowColumnAnsi)); +} + +static bool should_show_stop_column_with_caret(DebuggerSP debugger_sp) { + // We don't use text-based stop column formatting if we can't lookup values + // from the debugger. + if (!debugger_sp) + return false; + + // If we're asked to show the first available of ANSI or caret, then we do + // show the caret when ANSI is not available. + const auto value = debugger_sp->GetStopShowColumn(); + if ((value == eStopShowColumnAnsiOrCaret) && !debugger_sp->GetUseColor()) + return true; + + // The only other time we use caret is if we're explicitly asked to show + // caret. + return value == eStopShowColumnCaret; +} + +static bool should_show_stop_line_with_ansi(DebuggerSP debugger_sp) { + return debugger_sp && debugger_sp->GetUseColor(); +} + +size_t SourceManager::DisplaySourceLinesWithLineNumbersUsingLastFile( + uint32_t start_line, uint32_t count, uint32_t curr_line, uint32_t column, + const char *current_line_cstr, Stream *s, + const SymbolContextList *bp_locs) { + if (count == 0) + return 0; + + Stream::ByteDelta delta(*s); + + if (start_line == 0) { + if (m_last_line != 0 && m_last_line != UINT32_MAX) + start_line = m_last_line + m_last_count; + else + start_line = 1; + } + + if (!m_default_set) { + FileSpec tmp_spec; + uint32_t tmp_line; + GetDefaultFileAndLine(tmp_spec, tmp_line); + } + + m_last_line = start_line; + m_last_count = count; + + if (FileSP last_file_sp = GetLastFile()) { + const uint32_t end_line = start_line + count - 1; + for (uint32_t line = start_line; line <= end_line; ++line) { + if (!last_file_sp->LineIsValid(line)) { + m_last_line = UINT32_MAX; + break; + } + + std::string prefix; + if (bp_locs) { + uint32_t bp_count = bp_locs->NumLineEntriesWithLine(line); + + if (bp_count > 0) + prefix = llvm::formatv("[{0}]", bp_count); + else + prefix = " "; + } + + char buffer[3]; + snprintf(buffer, sizeof(buffer), "%2.2s", + (line == curr_line) ? current_line_cstr : ""); + std::string current_line_highlight(buffer); + + auto debugger_sp = m_debugger_wp.lock(); + if (should_show_stop_line_with_ansi(debugger_sp)) { + current_line_highlight = ansi::FormatAnsiTerminalCodes( + (debugger_sp->GetStopShowLineMarkerAnsiPrefix() + + current_line_highlight + + debugger_sp->GetStopShowLineMarkerAnsiSuffix()) + .str()); + } + + s->Printf("%s%s %-4u\t", prefix.c_str(), current_line_highlight.c_str(), + line); + + // So far we treated column 0 as a special 'no column value', but + // DisplaySourceLines starts counting columns from 0 (and no column is + // expressed by passing an empty optional). + std::optional<size_t> columnToHighlight; + if (line == curr_line && column) + columnToHighlight = column - 1; + + size_t this_line_size = + last_file_sp->DisplaySourceLines(line, columnToHighlight, 0, 0, s); + if (column != 0 && line == curr_line && + should_show_stop_column_with_caret(debugger_sp)) { + // Display caret cursor. + std::string src_line; + last_file_sp->GetLine(line, src_line); + s->Printf(" \t"); + // Insert a space for every non-tab character in the source line. + for (size_t i = 0; i + 1 < column && i < src_line.length(); ++i) + s->PutChar(src_line[i] == '\t' ? '\t' : ' '); + // Now add the caret. + s->Printf("^\n"); + } + if (this_line_size == 0) { + m_last_line = UINT32_MAX; + break; + } + } + } + return *delta; +} + +size_t SourceManager::DisplaySourceLinesWithLineNumbers( + const FileSpec &file_spec, uint32_t line, uint32_t column, + uint32_t context_before, uint32_t context_after, + const char *current_line_cstr, Stream *s, + const SymbolContextList *bp_locs) { + FileSP file_sp(GetFile(file_spec)); + + uint32_t start_line; + uint32_t count = context_before + context_after + 1; + if (line > context_before) + start_line = line - context_before; + else + start_line = 1; + + FileSP last_file_sp(GetLastFile()); + if (last_file_sp.get() != file_sp.get()) { + if (line == 0) + m_last_line = 0; + m_last_file_spec = file_spec; + } + return DisplaySourceLinesWithLineNumbersUsingLastFile( + start_line, count, line, column, current_line_cstr, s, bp_locs); +} + +size_t SourceManager::DisplayMoreWithLineNumbers( + Stream *s, uint32_t count, bool reverse, const SymbolContextList *bp_locs) { + // If we get called before anybody has set a default file and line, then try + // to figure it out here. + FileSP last_file_sp(GetLastFile()); + const bool have_default_file_line = last_file_sp && m_last_line > 0; + if (!m_default_set) { + FileSpec tmp_spec; + uint32_t tmp_line; + GetDefaultFileAndLine(tmp_spec, tmp_line); + } + + if (last_file_sp) { + if (m_last_line == UINT32_MAX) + return 0; + + if (reverse && m_last_line == 1) + return 0; + + if (count > 0) + m_last_count = count; + else if (m_last_count == 0) + m_last_count = 10; + + if (m_last_line > 0) { + if (reverse) { + // If this is the first time we've done a reverse, then back up one + // more time so we end up showing the chunk before the last one we've + // shown: + if (m_last_line > m_last_count) + m_last_line -= m_last_count; + else + m_last_line = 1; + } else if (have_default_file_line) + m_last_line += m_last_count; + } else + m_last_line = 1; + + const uint32_t column = 0; + return DisplaySourceLinesWithLineNumbersUsingLastFile( + m_last_line, m_last_count, UINT32_MAX, column, "", s, bp_locs); + } + return 0; +} + +bool SourceManager::SetDefaultFileAndLine(const FileSpec &file_spec, + uint32_t line) { + m_default_set = true; + FileSP file_sp(GetFile(file_spec)); + + if (file_sp) { + m_last_line = line; + m_last_file_spec = file_spec; + return true; + } else { + return false; + } +} + +bool SourceManager::GetDefaultFileAndLine(FileSpec &file_spec, uint32_t &line) { + if (FileSP last_file_sp = GetLastFile()) { + file_spec = m_last_file_spec; + line = m_last_line; + return true; + } else if (!m_default_set) { + TargetSP target_sp(m_target_wp.lock()); + + if (target_sp) { + // If nobody has set the default file and line then try here. If there's + // no executable, then we will try again later when there is one. + // Otherwise, if we can't find it we won't look again, somebody will have + // to set it (for instance when we stop somewhere...) + Module *executable_ptr = target_sp->GetExecutableModulePointer(); + if (executable_ptr) { + SymbolContextList sc_list; + ConstString main_name("main"); + + ModuleFunctionSearchOptions function_options; + function_options.include_symbols = + false; // Force it to be a debug symbol. + function_options.include_inlines = true; + executable_ptr->FindFunctions(main_name, CompilerDeclContext(), + lldb::eFunctionNameTypeBase, + function_options, sc_list); + for (const SymbolContext &sc : sc_list) { + if (sc.function) { + lldb_private::LineEntry line_entry; + if (sc.function->GetAddressRange() + .GetBaseAddress() + .CalculateSymbolContextLineEntry(line_entry)) { + SetDefaultFileAndLine(line_entry.GetFile(), line_entry.line); + file_spec = m_last_file_spec; + line = m_last_line; + return true; + } + } + } + } + } + } + return false; +} + +void SourceManager::FindLinesMatchingRegex(FileSpec &file_spec, + RegularExpression ®ex, + uint32_t start_line, + uint32_t end_line, + std::vector<uint32_t> &match_lines) { + match_lines.clear(); + FileSP file_sp = GetFile(file_spec); + if (!file_sp) + return; + return file_sp->FindLinesMatchingRegex(regex, start_line, end_line, + match_lines); +} + +SourceManager::File::File(const FileSpec &file_spec, + lldb::DebuggerSP debugger_sp) + : m_file_spec_orig(file_spec), m_file_spec(), m_mod_time(), + m_debugger_wp(debugger_sp), m_target_wp(TargetSP()) { + CommonInitializer(file_spec, {}); +} + +SourceManager::File::File(const FileSpec &file_spec, TargetSP target_sp) + : m_file_spec_orig(file_spec), m_file_spec(), m_mod_time(), + m_debugger_wp(target_sp ? target_sp->GetDebugger().shared_from_this() + : DebuggerSP()), + m_target_wp(target_sp) { + CommonInitializer(file_spec, target_sp); +} + +void SourceManager::File::CommonInitializer(const FileSpec &file_spec, + TargetSP target_sp) { + // Set the file and update the modification time. + SetFileSpec(file_spec); + + // Always update the source map modification ID if we have a target. + if (target_sp) + m_source_map_mod_id = target_sp->GetSourcePathMap().GetModificationID(); + + // File doesn't exist. + if (m_mod_time == llvm::sys::TimePoint<>()) { + if (target_sp) { + // If this is just a file name, try finding it in the target. + if (!file_spec.GetDirectory() && file_spec.GetFilename()) { + bool check_inlines = false; + SymbolContextList sc_list; + size_t num_matches = + target_sp->GetImages().ResolveSymbolContextForFilePath( + file_spec.GetFilename().AsCString(), 0, check_inlines, + SymbolContextItem(eSymbolContextModule | + eSymbolContextCompUnit), + sc_list); + bool got_multiple = false; + if (num_matches != 0) { + if (num_matches > 1) { + CompileUnit *test_cu = nullptr; + for (const SymbolContext &sc : sc_list) { + if (sc.comp_unit) { + if (test_cu) { + if (test_cu != sc.comp_unit) + got_multiple = true; + break; + } else + test_cu = sc.comp_unit; + } + } + } + if (!got_multiple) { + SymbolContext sc; + sc_list.GetContextAtIndex(0, sc); + if (sc.comp_unit) + SetFileSpec(sc.comp_unit->GetPrimaryFile()); + } + } + } + + // Try remapping the file if it doesn't exist. + if (!FileSystem::Instance().Exists(m_file_spec)) { + // Check target specific source remappings (i.e., the + // target.source-map setting), then fall back to the module + // specific remapping (i.e., the .dSYM remapping dictionary). + auto remapped = target_sp->GetSourcePathMap().FindFile(m_file_spec); + if (!remapped) { + FileSpec new_spec; + if (target_sp->GetImages().FindSourceFile(m_file_spec, new_spec)) + remapped = new_spec; + } + if (remapped) + SetFileSpec(*remapped); + } + } + } + + // If the file exists, read in the data. + if (m_mod_time != llvm::sys::TimePoint<>()) + m_data_sp = FileSystem::Instance().CreateDataBuffer(m_file_spec); +} + +void SourceManager::File::SetFileSpec(FileSpec file_spec) { + resolve_tilde(file_spec); + m_file_spec = std::move(file_spec); + m_mod_time = FileSystem::Instance().GetModificationTime(m_file_spec); +} + +uint32_t SourceManager::File::GetLineOffset(uint32_t line) { + if (line == 0) + return UINT32_MAX; + + if (line == 1) + return 0; + + if (CalculateLineOffsets(line)) { + if (line < m_offsets.size()) + return m_offsets[line - 1]; // yes we want "line - 1" in the index + } + return UINT32_MAX; +} + +uint32_t SourceManager::File::GetNumLines() { + CalculateLineOffsets(); + return m_offsets.size(); +} + +const char *SourceManager::File::PeekLineData(uint32_t line) { + if (!LineIsValid(line)) + return nullptr; + + size_t line_offset = GetLineOffset(line); + if (line_offset < m_data_sp->GetByteSize()) + return (const char *)m_data_sp->GetBytes() + line_offset; + return nullptr; +} + +uint32_t SourceManager::File::GetLineLength(uint32_t line, + bool include_newline_chars) { + if (!LineIsValid(line)) + return false; + + size_t start_offset = GetLineOffset(line); + size_t end_offset = GetLineOffset(line + 1); + if (end_offset == UINT32_MAX) + end_offset = m_data_sp->GetByteSize(); + + if (end_offset > start_offset) { + uint32_t length = end_offset - start_offset; + if (!include_newline_chars) { + const char *line_start = + (const char *)m_data_sp->GetBytes() + start_offset; + while (length > 0) { + const char last_char = line_start[length - 1]; + if ((last_char == '\r') || (last_char == '\n')) + --length; + else + break; + } + } + return length; + } + return 0; +} + +bool SourceManager::File::LineIsValid(uint32_t line) { + if (line == 0) + return false; + + if (CalculateLineOffsets(line)) + return line < m_offsets.size(); + return false; +} + +bool SourceManager::File::ModificationTimeIsStale() const { + // TODO: use host API to sign up for file modifications to anything in our + // source cache and only update when we determine a file has been updated. + // For now we check each time we want to display info for the file. + auto curr_mod_time = FileSystem::Instance().GetModificationTime(m_file_spec); + return curr_mod_time != llvm::sys::TimePoint<>() && + m_mod_time != curr_mod_time; +} + +bool SourceManager::File::PathRemappingIsStale() const { + if (TargetSP target_sp = m_target_wp.lock()) + return GetSourceMapModificationID() != + target_sp->GetSourcePathMap().GetModificationID(); + return false; +} + +size_t SourceManager::File::DisplaySourceLines(uint32_t line, + std::optional<size_t> column, + uint32_t context_before, + uint32_t context_after, + Stream *s) { + // Nothing to write if there's no stream. + if (!s) + return 0; + + // Sanity check m_data_sp before proceeding. + if (!m_data_sp) + return 0; + + size_t bytes_written = s->GetWrittenBytes(); + + auto debugger_sp = m_debugger_wp.lock(); + + HighlightStyle style; + // Use the default Vim style if source highlighting is enabled. + if (should_highlight_source(debugger_sp)) + style = HighlightStyle::MakeVimStyle(); + + // If we should mark the stop column with color codes, then copy the prefix + // and suffix to our color style. + if (should_show_stop_column_with_ansi(debugger_sp)) + style.selected.Set(debugger_sp->GetStopShowColumnAnsiPrefix(), + debugger_sp->GetStopShowColumnAnsiSuffix()); + + HighlighterManager mgr; + std::string path = GetFileSpec().GetPath(/*denormalize*/ false); + // FIXME: Find a way to get the definitive language this file was written in + // and pass it to the highlighter. + const auto &h = mgr.getHighlighterFor(lldb::eLanguageTypeUnknown, path); + + const uint32_t start_line = + line <= context_before ? 1 : line - context_before; + const uint32_t start_line_offset = GetLineOffset(start_line); + if (start_line_offset != UINT32_MAX) { + const uint32_t end_line = line + context_after; + uint32_t end_line_offset = GetLineOffset(end_line + 1); + if (end_line_offset == UINT32_MAX) + end_line_offset = m_data_sp->GetByteSize(); + + assert(start_line_offset <= end_line_offset); + if (start_line_offset < end_line_offset) { + size_t count = end_line_offset - start_line_offset; + const uint8_t *cstr = m_data_sp->GetBytes() + start_line_offset; + + auto ref = llvm::StringRef(reinterpret_cast<const char *>(cstr), count); + + h.Highlight(style, ref, column, "", *s); + + // Ensure we get an end of line character one way or another. + if (!is_newline_char(ref.back())) + s->EOL(); + } + } + return s->GetWrittenBytes() - bytes_written; +} + +void SourceManager::File::FindLinesMatchingRegex( + RegularExpression ®ex, uint32_t start_line, uint32_t end_line, + std::vector<uint32_t> &match_lines) { + match_lines.clear(); + + if (!LineIsValid(start_line) || + (end_line != UINT32_MAX && !LineIsValid(end_line))) + return; + if (start_line > end_line) + return; + + for (uint32_t line_no = start_line; line_no < end_line; line_no++) { + std::string buffer; + if (!GetLine(line_no, buffer)) + break; + if (regex.Execute(buffer)) { + match_lines.push_back(line_no); + } + } +} + +bool lldb_private::operator==(const SourceManager::File &lhs, + const SourceManager::File &rhs) { + if (lhs.m_file_spec != rhs.m_file_spec) + return false; + return lhs.m_mod_time == rhs.m_mod_time; +} + +bool SourceManager::File::CalculateLineOffsets(uint32_t line) { + line = + UINT32_MAX; // TODO: take this line out when we support partial indexing + if (line == UINT32_MAX) { + // Already done? + if (!m_offsets.empty() && m_offsets[0] == UINT32_MAX) + return true; + + if (m_offsets.empty()) { + if (m_data_sp.get() == nullptr) + return false; + + const char *start = (const char *)m_data_sp->GetBytes(); + if (start) { + const char *end = start + m_data_sp->GetByteSize(); + + // Calculate all line offsets from scratch + + // Push a 1 at index zero to indicate the file has been completely + // indexed. + m_offsets.push_back(UINT32_MAX); + const char *s; + for (s = start; s < end; ++s) { + char curr_ch = *s; + if (is_newline_char(curr_ch)) { + if (s + 1 < end) { + char next_ch = s[1]; + if (is_newline_char(next_ch)) { + if (curr_ch != next_ch) + ++s; + } + } + m_offsets.push_back(s + 1 - start); + } + } + if (!m_offsets.empty()) { + if (m_offsets.back() < size_t(end - start)) + m_offsets.push_back(end - start); + } + return true; + } + } else { + // Some lines have been populated, start where we last left off + assert("Not implemented yet" && false); + } + + } else { + // Calculate all line offsets up to "line" + assert("Not implemented yet" && false); + } + return false; +} + +bool SourceManager::File::GetLine(uint32_t line_no, std::string &buffer) { + if (!LineIsValid(line_no)) + return false; + + size_t start_offset = GetLineOffset(line_no); + size_t end_offset = GetLineOffset(line_no + 1); + if (end_offset == UINT32_MAX) { + end_offset = m_data_sp->GetByteSize(); + } + buffer.assign((const char *)m_data_sp->GetBytes() + start_offset, + end_offset - start_offset); + + return true; +} + +void SourceManager::SourceFileCache::AddSourceFile(const FileSpec &file_spec, + FileSP file_sp) { + llvm::sys::ScopedWriter guard(m_mutex); + + assert(file_sp && "invalid FileSP"); + + AddSourceFileImpl(file_spec, file_sp); + const FileSpec &resolved_file_spec = file_sp->GetFileSpec(); + if (file_spec != resolved_file_spec) + AddSourceFileImpl(file_sp->GetFileSpec(), file_sp); +} + +void SourceManager::SourceFileCache::RemoveSourceFile(const FileSP &file_sp) { + llvm::sys::ScopedWriter guard(m_mutex); + + assert(file_sp && "invalid FileSP"); + + // Iterate over all the elements in the cache. + // This is expensive but a relatively uncommon operation. + auto it = m_file_cache.begin(); + while (it != m_file_cache.end()) { + if (it->second == file_sp) + it = m_file_cache.erase(it); + else + it++; + } +} + +void SourceManager::SourceFileCache::AddSourceFileImpl( + const FileSpec &file_spec, FileSP file_sp) { + FileCache::iterator pos = m_file_cache.find(file_spec); + if (pos == m_file_cache.end()) { + m_file_cache[file_spec] = file_sp; + } else { + if (file_sp != pos->second) + m_file_cache[file_spec] = file_sp; + } +} + +SourceManager::FileSP SourceManager::SourceFileCache::FindSourceFile( + const FileSpec &file_spec) const { + llvm::sys::ScopedReader guard(m_mutex); + + FileCache::const_iterator pos = m_file_cache.find(file_spec); + if (pos != m_file_cache.end()) + return pos->second; + return {}; +} + +void SourceManager::SourceFileCache::Dump(Stream &stream) const { + stream << "Modification time Lines Path\n"; + stream << "------------------- -------- --------------------------------\n"; + for (auto &entry : m_file_cache) { + if (!entry.second) + continue; + FileSP file = entry.second; + stream.Format("{0:%Y-%m-%d %H:%M:%S} {1,8:d} {2}\n", file->GetTimestamp(), + file->GetNumLines(), entry.first.GetPath()); + } +} diff --git a/contrib/llvm-project/lldb/source/Core/StreamAsynchronousIO.cpp b/contrib/llvm-project/lldb/source/Core/StreamAsynchronousIO.cpp new file mode 100644 index 000000000000..c2c64b61ab72 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/StreamAsynchronousIO.cpp @@ -0,0 +1,37 @@ +//===-- StreamAsynchronousIO.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/Core/StreamAsynchronousIO.h" + +#include "lldb/Core/Debugger.h" +#include "lldb/lldb-enumerations.h" + +using namespace lldb; +using namespace lldb_private; + +StreamAsynchronousIO::StreamAsynchronousIO(Debugger &debugger, bool for_stdout, + bool colors) + : Stream(0, 4, eByteOrderBig, colors), m_debugger(debugger), m_data(), + m_for_stdout(for_stdout) {} + +StreamAsynchronousIO::~StreamAsynchronousIO() { + // Flush when we destroy to make sure we display the data + Flush(); +} + +void StreamAsynchronousIO::Flush() { + if (!m_data.empty()) { + m_debugger.PrintAsync(m_data.data(), m_data.size(), m_for_stdout); + m_data = std::string(); + } +} + +size_t StreamAsynchronousIO::WriteImpl(const void *s, size_t length) { + m_data.append((const char *)s, length); + return length; +} diff --git a/contrib/llvm-project/lldb/source/Core/ThreadedCommunication.cpp b/contrib/llvm-project/lldb/source/Core/ThreadedCommunication.cpp new file mode 100644 index 000000000000..2f3dada3ac93 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/ThreadedCommunication.cpp @@ -0,0 +1,376 @@ +//===-- ThreadedCommunication.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/Core/ThreadedCommunication.h" + +#include "lldb/Host/ThreadLauncher.h" +#include "lldb/Utility/Connection.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/Event.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Listener.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Status.h" + +#include "llvm/Support/Compiler.h" + +#include <algorithm> +#include <chrono> +#include <cstring> +#include <memory> +#include <shared_mutex> + +#include <cerrno> +#include <cinttypes> +#include <cstdio> + +using namespace lldb; +using namespace lldb_private; + +llvm::StringRef ThreadedCommunication::GetStaticBroadcasterClass() { + static constexpr llvm::StringLiteral class_name("lldb.communication"); + return class_name; +} + +ThreadedCommunication::ThreadedCommunication(const char *name) + : Communication(), Broadcaster(nullptr, name), m_read_thread_enabled(false), + m_read_thread_did_exit(false), m_bytes(), m_bytes_mutex(), + m_synchronize_mutex(), m_callback(nullptr), m_callback_baton(nullptr) { + LLDB_LOG(GetLog(LLDBLog::Object | LLDBLog::Communication), + "{0} ThreadedCommunication::ThreadedCommunication (name = {1})", + this, name); + + SetEventName(eBroadcastBitDisconnected, "disconnected"); + SetEventName(eBroadcastBitReadThreadGotBytes, "got bytes"); + SetEventName(eBroadcastBitReadThreadDidExit, "read thread did exit"); + SetEventName(eBroadcastBitReadThreadShouldExit, "read thread should exit"); + SetEventName(eBroadcastBitPacketAvailable, "packet available"); + SetEventName(eBroadcastBitNoMorePendingInput, "no more pending input"); + + CheckInWithManager(); +} + +ThreadedCommunication::~ThreadedCommunication() { + LLDB_LOG(GetLog(LLDBLog::Object | LLDBLog::Communication), + "{0} ThreadedCommunication::~ThreadedCommunication (name = {1})", + this, GetBroadcasterName()); +} + +void ThreadedCommunication::Clear() { + SetReadThreadBytesReceivedCallback(nullptr, nullptr); + StopReadThread(nullptr); + Communication::Clear(); +} + +ConnectionStatus ThreadedCommunication::Disconnect(Status *error_ptr) { + assert((!m_read_thread_enabled || m_read_thread_did_exit) && + "Disconnecting while the read thread is running is racy!"); + return Communication::Disconnect(error_ptr); +} + +size_t ThreadedCommunication::Read(void *dst, size_t dst_len, + const Timeout<std::micro> &timeout, + ConnectionStatus &status, + Status *error_ptr) { + Log *log = GetLog(LLDBLog::Communication); + LLDB_LOG( + log, + "this = {0}, dst = {1}, dst_len = {2}, timeout = {3}, connection = {4}", + this, dst, dst_len, timeout, m_connection_sp.get()); + + if (m_read_thread_enabled) { + // We have a dedicated read thread that is getting data for us + size_t cached_bytes = GetCachedBytes(dst, dst_len); + if (cached_bytes > 0) { + status = eConnectionStatusSuccess; + return cached_bytes; + } + if (timeout && timeout->count() == 0) { + if (error_ptr) + error_ptr->SetErrorString("Timed out."); + status = eConnectionStatusTimedOut; + return 0; + } + + if (!m_connection_sp) { + if (error_ptr) + error_ptr->SetErrorString("Invalid connection."); + status = eConnectionStatusNoConnection; + return 0; + } + + // No data yet, we have to start listening. + ListenerSP listener_sp( + Listener::MakeListener("ThreadedCommunication::Read")); + listener_sp->StartListeningForEvents( + this, eBroadcastBitReadThreadGotBytes | eBroadcastBitReadThreadDidExit); + + // Re-check for data, as it might have arrived while we were setting up our + // listener. + cached_bytes = GetCachedBytes(dst, dst_len); + if (cached_bytes > 0) { + status = eConnectionStatusSuccess; + return cached_bytes; + } + + EventSP event_sp; + // Explicitly check for the thread exit, for the same reason. + if (m_read_thread_did_exit) { + // We've missed the event, lets just conjure one up. + event_sp = std::make_shared<Event>(eBroadcastBitReadThreadDidExit); + } else { + if (!listener_sp->GetEvent(event_sp, timeout)) { + if (error_ptr) + error_ptr->SetErrorString("Timed out."); + status = eConnectionStatusTimedOut; + return 0; + } + } + const uint32_t event_type = event_sp->GetType(); + if (event_type & eBroadcastBitReadThreadGotBytes) { + return GetCachedBytes(dst, dst_len); + } + + if (event_type & eBroadcastBitReadThreadDidExit) { + // If the thread exited of its own accord, it either means it + // hit an end-of-file condition or an error. + status = m_pass_status; + if (error_ptr) + *error_ptr = std::move(m_pass_error); + + if (GetCloseOnEOF()) + Disconnect(nullptr); + return 0; + } + llvm_unreachable("Got unexpected event type!"); + } + + // We aren't using a read thread, just read the data synchronously in this + // thread. + return Communication::Read(dst, dst_len, timeout, status, error_ptr); +} + +bool ThreadedCommunication::StartReadThread(Status *error_ptr) { + std::lock_guard<std::mutex> lock(m_read_thread_mutex); + + if (error_ptr) + error_ptr->Clear(); + + if (m_read_thread.IsJoinable()) + return true; + + LLDB_LOG(GetLog(LLDBLog::Communication), + "{0} ThreadedCommunication::StartReadThread ()", this); + + const std::string thread_name = + llvm::formatv("<lldb.comm.{0}>", GetBroadcasterName()); + + m_read_thread_enabled = true; + m_read_thread_did_exit = false; + auto maybe_thread = ThreadLauncher::LaunchThread( + thread_name, [this] { return ReadThread(); }); + if (maybe_thread) { + m_read_thread = *maybe_thread; + } else { + if (error_ptr) + *error_ptr = Status(maybe_thread.takeError()); + else { + LLDB_LOG_ERROR(GetLog(LLDBLog::Host), maybe_thread.takeError(), + "failed to launch host thread: {0}"); + } + } + + if (!m_read_thread.IsJoinable()) + m_read_thread_enabled = false; + + return m_read_thread_enabled; +} + +bool ThreadedCommunication::StopReadThread(Status *error_ptr) { + std::lock_guard<std::mutex> lock(m_read_thread_mutex); + + if (!m_read_thread.IsJoinable()) + return true; + + LLDB_LOG(GetLog(LLDBLog::Communication), + "{0} ThreadedCommunication::StopReadThread ()", this); + + m_read_thread_enabled = false; + + BroadcastEvent(eBroadcastBitReadThreadShouldExit, nullptr); + + Status error = m_read_thread.Join(nullptr); + return error.Success(); +} + +bool ThreadedCommunication::JoinReadThread(Status *error_ptr) { + std::lock_guard<std::mutex> lock(m_read_thread_mutex); + + if (!m_read_thread.IsJoinable()) + return true; + + Status error = m_read_thread.Join(nullptr); + return error.Success(); +} + +size_t ThreadedCommunication::GetCachedBytes(void *dst, size_t dst_len) { + std::lock_guard<std::recursive_mutex> guard(m_bytes_mutex); + if (!m_bytes.empty()) { + // If DST is nullptr and we have a thread, then return the number of bytes + // that are available so the caller can call again + if (dst == nullptr) + return m_bytes.size(); + + const size_t len = std::min<size_t>(dst_len, m_bytes.size()); + + ::memcpy(dst, m_bytes.c_str(), len); + m_bytes.erase(m_bytes.begin(), m_bytes.begin() + len); + + return len; + } + return 0; +} + +void ThreadedCommunication::AppendBytesToCache(const uint8_t *bytes, size_t len, + bool broadcast, + ConnectionStatus status) { + LLDB_LOG(GetLog(LLDBLog::Communication), + "{0} ThreadedCommunication::AppendBytesToCache (src = {1}, src_len " + "= {2}, " + "broadcast = {3})", + this, bytes, (uint64_t)len, broadcast); + if ((bytes == nullptr || len == 0) && + (status != lldb::eConnectionStatusEndOfFile)) + return; + if (m_callback) { + // If the user registered a callback, then call it and do not broadcast + m_callback(m_callback_baton, bytes, len); + } else if (bytes != nullptr && len > 0) { + std::lock_guard<std::recursive_mutex> guard(m_bytes_mutex); + m_bytes.append((const char *)bytes, len); + if (broadcast) + BroadcastEventIfUnique(eBroadcastBitReadThreadGotBytes); + } +} + +bool ThreadedCommunication::ReadThreadIsRunning() { + return m_read_thread_enabled; +} + +lldb::thread_result_t ThreadedCommunication::ReadThread() { + Log *log = GetLog(LLDBLog::Communication); + + LLDB_LOG(log, "Communication({0}) thread starting...", this); + + uint8_t buf[1024]; + + Status error; + ConnectionStatus status = eConnectionStatusSuccess; + bool done = false; + bool disconnect = false; + while (!done && m_read_thread_enabled) { + size_t bytes_read = ReadFromConnection( + buf, sizeof(buf), std::chrono::seconds(5), status, &error); + if (bytes_read > 0 || status == eConnectionStatusEndOfFile) + AppendBytesToCache(buf, bytes_read, true, status); + + switch (status) { + case eConnectionStatusSuccess: + break; + + case eConnectionStatusEndOfFile: + done = true; + disconnect = GetCloseOnEOF(); + break; + case eConnectionStatusError: // Check GetError() for details + if (error.GetType() == eErrorTypePOSIX && error.GetError() == EIO) { + // EIO on a pipe is usually caused by remote shutdown + disconnect = GetCloseOnEOF(); + done = true; + } + if (error.Fail()) + LLDB_LOG(log, "error: {0}, status = {1}", error, + ThreadedCommunication::ConnectionStatusAsString(status)); + break; + case eConnectionStatusInterrupted: // Synchronization signal from + // SynchronizeWithReadThread() + // The connection returns eConnectionStatusInterrupted only when there is + // no input pending to be read, so we can signal that. + BroadcastEvent(eBroadcastBitNoMorePendingInput); + break; + case eConnectionStatusNoConnection: // No connection + case eConnectionStatusLostConnection: // Lost connection while connected to + // a valid connection + done = true; + [[fallthrough]]; + case eConnectionStatusTimedOut: // Request timed out + if (error.Fail()) + LLDB_LOG(log, "error: {0}, status = {1}", error, + ThreadedCommunication::ConnectionStatusAsString(status)); + break; + } + } + m_pass_status = status; + m_pass_error = std::move(error); + LLDB_LOG(log, "Communication({0}) thread exiting...", this); + + // Start shutting down. We need to do this in a very specific order to ensure + // we don't race with threads wanting to read/synchronize with us. + + // First, we signal our intent to exit. This ensures no new thread start + // waiting on events from us. + m_read_thread_did_exit = true; + + // Unblock any existing thread waiting for the synchronization signal. + BroadcastEvent(eBroadcastBitNoMorePendingInput); + + { + // Wait for the synchronization thread to finish... + std::lock_guard<std::mutex> guard(m_synchronize_mutex); + // ... and disconnect. + if (disconnect) + Disconnect(); + } + + // Finally, unblock any readers waiting for us to exit. + BroadcastEvent(eBroadcastBitReadThreadDidExit); + return {}; +} + +void ThreadedCommunication::SetReadThreadBytesReceivedCallback( + ReadThreadBytesReceived callback, void *callback_baton) { + m_callback = callback; + m_callback_baton = callback_baton; +} + +void ThreadedCommunication::SynchronizeWithReadThread() { + // Only one thread can do the synchronization dance at a time. + std::lock_guard<std::mutex> guard(m_synchronize_mutex); + + // First start listening for the synchronization event. + ListenerSP listener_sp(Listener::MakeListener( + "ThreadedCommunication::SyncronizeWithReadThread")); + listener_sp->StartListeningForEvents(this, eBroadcastBitNoMorePendingInput); + + // If the thread is not running, there is no point in synchronizing. + if (!m_read_thread_enabled || m_read_thread_did_exit) + return; + + // Notify the read thread. + m_connection_sp->InterruptRead(); + + // Wait for the synchronization event. + EventSP event_sp; + listener_sp->GetEvent(event_sp, std::nullopt); +} + +void ThreadedCommunication::SetConnection( + std::unique_ptr<Connection> connection) { + StopReadThread(nullptr); + Communication::SetConnection(std::move(connection)); +} diff --git a/contrib/llvm-project/lldb/source/Core/UserSettingsController.cpp b/contrib/llvm-project/lldb/source/Core/UserSettingsController.cpp new file mode 100644 index 000000000000..72217117557f --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/UserSettingsController.cpp @@ -0,0 +1,120 @@ +//===-- UserSettingsController.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/Core/UserSettingsController.h" + +#include "lldb/Interpreter/OptionValueProperties.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/Stream.h" + +#include <memory> + +namespace lldb_private { +class CommandInterpreter; +} +namespace lldb_private { +class ConstString; +} +namespace lldb_private { +class ExecutionContext; +} +namespace lldb_private { +class Property; +} + +using namespace lldb; +using namespace lldb_private; + +Properties::Properties() = default; + +Properties::Properties(const lldb::OptionValuePropertiesSP &collection_sp) + : m_collection_sp(collection_sp) {} + +Properties::~Properties() = default; + +lldb::OptionValueSP +Properties::GetPropertyValue(const ExecutionContext *exe_ctx, + llvm::StringRef path, Status &error) const { + OptionValuePropertiesSP properties_sp(GetValueProperties()); + if (properties_sp) + return properties_sp->GetSubValue(exe_ctx, path, error); + return lldb::OptionValueSP(); +} + +Status Properties::SetPropertyValue(const ExecutionContext *exe_ctx, + VarSetOperationType op, + llvm::StringRef path, + llvm::StringRef value) { + OptionValuePropertiesSP properties_sp(GetValueProperties()); + if (properties_sp) + return properties_sp->SetSubValue(exe_ctx, op, path, value); + Status error; + error.SetErrorString("no properties"); + return error; +} + +void Properties::DumpAllPropertyValues(const ExecutionContext *exe_ctx, + Stream &strm, uint32_t dump_mask, + bool is_json) { + OptionValuePropertiesSP properties_sp(GetValueProperties()); + if (!properties_sp) + return; + + if (is_json) { + llvm::json::Value json = properties_sp->ToJSON(exe_ctx); + strm.Printf("%s", llvm::formatv("{0:2}", json).str().c_str()); + } else + properties_sp->DumpValue(exe_ctx, strm, dump_mask); +} + +void Properties::DumpAllDescriptions(CommandInterpreter &interpreter, + Stream &strm) const { + strm.PutCString("Top level variables:\n\n"); + + OptionValuePropertiesSP properties_sp(GetValueProperties()); + if (properties_sp) + return properties_sp->DumpAllDescriptions(interpreter, strm); +} + +Status Properties::DumpPropertyValue(const ExecutionContext *exe_ctx, + Stream &strm, + llvm::StringRef property_path, + uint32_t dump_mask, bool is_json) { + OptionValuePropertiesSP properties_sp(GetValueProperties()); + if (properties_sp) { + return properties_sp->DumpPropertyValue(exe_ctx, strm, property_path, + dump_mask, is_json); + } + Status error; + error.SetErrorString("empty property list"); + return error; +} + +size_t +Properties::Apropos(llvm::StringRef keyword, + std::vector<const Property *> &matching_properties) const { + OptionValuePropertiesSP properties_sp(GetValueProperties()); + if (properties_sp) { + properties_sp->Apropos(keyword, matching_properties); + } + return matching_properties.size(); +} + +llvm::StringRef Properties::GetExperimentalSettingsName() { + static constexpr llvm::StringLiteral g_experimental("experimental"); + return g_experimental; +} + +bool Properties::IsSettingExperimental(llvm::StringRef setting) { + if (setting.empty()) + return false; + + llvm::StringRef experimental = GetExperimentalSettingsName(); + size_t dot_pos = setting.find_first_of('.'); + return setting.take_front(dot_pos) == experimental; +} diff --git a/contrib/llvm-project/lldb/source/Core/Value.cpp b/contrib/llvm-project/lldb/source/Core/Value.cpp new file mode 100644 index 000000000000..995cc934c820 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/Value.cpp @@ -0,0 +1,693 @@ +//===-- Value.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/Core/Value.h" + +#include "lldb/Core/Address.h" +#include "lldb/Core/Module.h" +#include "lldb/Symbol/CompilerType.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Symbol/Type.h" +#include "lldb/Symbol/Variable.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/DataExtractor.h" +#include "lldb/Utility/Endian.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/State.h" +#include "lldb/Utility/Stream.h" +#include "lldb/lldb-defines.h" +#include "lldb/lldb-forward.h" +#include "lldb/lldb-types.h" + +#include <memory> +#include <optional> +#include <string> + +#include <cinttypes> + +using namespace lldb; +using namespace lldb_private; + +Value::Value() : m_value(), m_compiler_type(), m_data_buffer() {} + +Value::Value(const Scalar &scalar) + : m_value(scalar), m_compiler_type(), m_data_buffer() {} + +Value::Value(const void *bytes, int len) + : m_value(), m_compiler_type(), m_value_type(ValueType::HostAddress), + m_data_buffer() { + SetBytes(bytes, len); +} + +Value::Value(const Value &v) + : m_value(v.m_value), m_compiler_type(v.m_compiler_type), + m_context(v.m_context), m_value_type(v.m_value_type), + m_context_type(v.m_context_type), m_data_buffer() { + const uintptr_t rhs_value = + (uintptr_t)v.m_value.ULongLong(LLDB_INVALID_ADDRESS); + if ((rhs_value != 0) && + (rhs_value == (uintptr_t)v.m_data_buffer.GetBytes())) { + m_data_buffer.CopyData(v.m_data_buffer.GetBytes(), + v.m_data_buffer.GetByteSize()); + + m_value = (uintptr_t)m_data_buffer.GetBytes(); + } +} + +Value &Value::operator=(const Value &rhs) { + if (this != &rhs) { + m_value = rhs.m_value; + m_compiler_type = rhs.m_compiler_type; + m_context = rhs.m_context; + m_value_type = rhs.m_value_type; + m_context_type = rhs.m_context_type; + const uintptr_t rhs_value = + (uintptr_t)rhs.m_value.ULongLong(LLDB_INVALID_ADDRESS); + if ((rhs_value != 0) && + (rhs_value == (uintptr_t)rhs.m_data_buffer.GetBytes())) { + m_data_buffer.CopyData(rhs.m_data_buffer.GetBytes(), + rhs.m_data_buffer.GetByteSize()); + + m_value = (uintptr_t)m_data_buffer.GetBytes(); + } + } + return *this; +} + +void Value::SetBytes(const void *bytes, int len) { + m_value_type = ValueType::HostAddress; + m_data_buffer.CopyData(bytes, len); + m_value = (uintptr_t)m_data_buffer.GetBytes(); +} + +void Value::AppendBytes(const void *bytes, int len) { + m_value_type = ValueType::HostAddress; + m_data_buffer.AppendData(bytes, len); + m_value = (uintptr_t)m_data_buffer.GetBytes(); +} + +void Value::Dump(Stream *strm) { + if (!strm) + return; + m_value.GetValue(*strm, true); + strm->Printf(", value_type = %s, context = %p, context_type = %s", + Value::GetValueTypeAsCString(m_value_type), m_context, + Value::GetContextTypeAsCString(m_context_type)); +} + +Value::ValueType Value::GetValueType() const { return m_value_type; } + +AddressType Value::GetValueAddressType() const { + switch (m_value_type) { + case ValueType::Invalid: + case ValueType::Scalar: + break; + case ValueType::LoadAddress: + return eAddressTypeLoad; + case ValueType::FileAddress: + return eAddressTypeFile; + case ValueType::HostAddress: + return eAddressTypeHost; + } + return eAddressTypeInvalid; +} + +Value::ValueType Value::GetValueTypeFromAddressType(AddressType address_type) { + switch (address_type) { + case eAddressTypeFile: + return Value::ValueType::FileAddress; + case eAddressTypeLoad: + return Value::ValueType::LoadAddress; + case eAddressTypeHost: + return Value::ValueType::HostAddress; + case eAddressTypeInvalid: + return Value::ValueType::Invalid; + } + llvm_unreachable("Unexpected address type!"); +} + +RegisterInfo *Value::GetRegisterInfo() const { + if (m_context_type == ContextType::RegisterInfo) + return static_cast<RegisterInfo *>(m_context); + return nullptr; +} + +Type *Value::GetType() { + if (m_context_type == ContextType::LLDBType) + return static_cast<Type *>(m_context); + return nullptr; +} + +size_t Value::AppendDataToHostBuffer(const Value &rhs) { + if (this == &rhs) + return 0; + + size_t curr_size = m_data_buffer.GetByteSize(); + Status error; + switch (rhs.GetValueType()) { + case ValueType::Invalid: + return 0; + case ValueType::Scalar: { + const size_t scalar_size = rhs.m_value.GetByteSize(); + if (scalar_size > 0) { + const size_t new_size = curr_size + scalar_size; + if (ResizeData(new_size) == new_size) { + rhs.m_value.GetAsMemoryData(m_data_buffer.GetBytes() + curr_size, + scalar_size, endian::InlHostByteOrder(), + error); + return scalar_size; + } + } + } break; + case ValueType::FileAddress: + case ValueType::LoadAddress: + case ValueType::HostAddress: { + const uint8_t *src = rhs.GetBuffer().GetBytes(); + const size_t src_len = rhs.GetBuffer().GetByteSize(); + if (src && src_len > 0) { + const size_t new_size = curr_size + src_len; + if (ResizeData(new_size) == new_size) { + ::memcpy(m_data_buffer.GetBytes() + curr_size, src, src_len); + return src_len; + } + } + } break; + } + return 0; +} + +size_t Value::ResizeData(size_t len) { + m_value_type = ValueType::HostAddress; + m_data_buffer.SetByteSize(len); + m_value = (uintptr_t)m_data_buffer.GetBytes(); + return m_data_buffer.GetByteSize(); +} + +bool Value::ValueOf(ExecutionContext *exe_ctx) { + switch (m_context_type) { + case ContextType::Invalid: + case ContextType::RegisterInfo: // RegisterInfo * + case ContextType::LLDBType: // Type * + break; + + case ContextType::Variable: // Variable * + ResolveValue(exe_ctx); + return true; + } + return false; +} + +uint64_t Value::GetValueByteSize(Status *error_ptr, ExecutionContext *exe_ctx) { + switch (m_context_type) { + case ContextType::RegisterInfo: // RegisterInfo * + if (GetRegisterInfo()) { + if (error_ptr) + error_ptr->Clear(); + return GetRegisterInfo()->byte_size; + } + break; + + case ContextType::Invalid: + case ContextType::LLDBType: // Type * + case ContextType::Variable: // Variable * + { + auto *scope = exe_ctx ? exe_ctx->GetBestExecutionContextScope() : nullptr; + if (std::optional<uint64_t> size = GetCompilerType().GetByteSize(scope)) { + if (error_ptr) + error_ptr->Clear(); + return *size; + } + break; + } + } + if (error_ptr && error_ptr->Success()) + error_ptr->SetErrorString("Unable to determine byte size."); + return 0; +} + +const CompilerType &Value::GetCompilerType() { + if (!m_compiler_type.IsValid()) { + switch (m_context_type) { + case ContextType::Invalid: + break; + + case ContextType::RegisterInfo: + break; // TODO: Eventually convert into a compiler type? + + case ContextType::LLDBType: { + Type *lldb_type = GetType(); + if (lldb_type) + m_compiler_type = lldb_type->GetForwardCompilerType(); + } break; + + case ContextType::Variable: { + Variable *variable = GetVariable(); + if (variable) { + Type *variable_type = variable->GetType(); + if (variable_type) + m_compiler_type = variable_type->GetForwardCompilerType(); + } + } break; + } + } + + return m_compiler_type; +} + +void Value::SetCompilerType(const CompilerType &compiler_type) { + m_compiler_type = compiler_type; +} + +lldb::Format Value::GetValueDefaultFormat() { + switch (m_context_type) { + case ContextType::RegisterInfo: + if (GetRegisterInfo()) + return GetRegisterInfo()->format; + break; + + case ContextType::Invalid: + case ContextType::LLDBType: + case ContextType::Variable: { + const CompilerType &ast_type = GetCompilerType(); + if (ast_type.IsValid()) + return ast_type.GetFormat(); + } break; + } + + // Return a good default in case we can't figure anything out + return eFormatHex; +} + +bool Value::GetData(DataExtractor &data) { + switch (m_value_type) { + case ValueType::Invalid: + return false; + case ValueType::Scalar: + if (m_value.GetData(data)) + return true; + break; + + case ValueType::LoadAddress: + case ValueType::FileAddress: + case ValueType::HostAddress: + if (m_data_buffer.GetByteSize()) { + data.SetData(m_data_buffer.GetBytes(), m_data_buffer.GetByteSize(), + data.GetByteOrder()); + return true; + } + break; + } + + return false; +} + +Status Value::GetValueAsData(ExecutionContext *exe_ctx, DataExtractor &data, + Module *module) { + data.Clear(); + + Status error; + lldb::addr_t address = LLDB_INVALID_ADDRESS; + AddressType address_type = eAddressTypeFile; + Address file_so_addr; + const CompilerType &ast_type = GetCompilerType(); + std::optional<uint64_t> type_size = ast_type.GetByteSize( + exe_ctx ? exe_ctx->GetBestExecutionContextScope() : nullptr); + // Nothing to be done for a zero-sized type. + if (type_size && *type_size == 0) + return error; + + switch (m_value_type) { + case ValueType::Invalid: + error.SetErrorString("invalid value"); + break; + case ValueType::Scalar: { + data.SetByteOrder(endian::InlHostByteOrder()); + if (ast_type.IsValid()) + data.SetAddressByteSize(ast_type.GetPointerByteSize()); + else + data.SetAddressByteSize(sizeof(void *)); + + uint32_t limit_byte_size = UINT32_MAX; + + if (type_size) + limit_byte_size = *type_size; + + if (limit_byte_size <= m_value.GetByteSize()) { + if (m_value.GetData(data, limit_byte_size)) + return error; // Success; + } + + error.SetErrorString("extracting data from value failed"); + break; + } + case ValueType::LoadAddress: + if (exe_ctx == nullptr) { + error.SetErrorString("can't read load address (no execution context)"); + } else { + Process *process = exe_ctx->GetProcessPtr(); + if (process == nullptr || !process->IsAlive()) { + Target *target = exe_ctx->GetTargetPtr(); + if (target) { + // Allow expressions to run and evaluate things when the target has + // memory sections loaded. This allows you to use "target modules + // load" to load your executable and any shared libraries, then + // execute commands where you can look at types in data sections. + const SectionLoadList &target_sections = target->GetSectionLoadList(); + if (!target_sections.IsEmpty()) { + address = m_value.ULongLong(LLDB_INVALID_ADDRESS); + if (target_sections.ResolveLoadAddress(address, file_so_addr)) { + address_type = eAddressTypeLoad; + data.SetByteOrder(target->GetArchitecture().GetByteOrder()); + data.SetAddressByteSize( + target->GetArchitecture().GetAddressByteSize()); + } else + address = LLDB_INVALID_ADDRESS; + } + } else { + error.SetErrorString("can't read load address (invalid process)"); + } + } else { + address = m_value.ULongLong(LLDB_INVALID_ADDRESS); + address_type = eAddressTypeLoad; + data.SetByteOrder( + process->GetTarget().GetArchitecture().GetByteOrder()); + data.SetAddressByteSize( + process->GetTarget().GetArchitecture().GetAddressByteSize()); + } + } + break; + + case ValueType::FileAddress: + if (exe_ctx == nullptr) { + error.SetErrorString("can't read file address (no execution context)"); + } else if (exe_ctx->GetTargetPtr() == nullptr) { + error.SetErrorString("can't read file address (invalid target)"); + } else { + address = m_value.ULongLong(LLDB_INVALID_ADDRESS); + if (address == LLDB_INVALID_ADDRESS) { + error.SetErrorString("invalid file address"); + } else { + if (module == nullptr) { + // The only thing we can currently lock down to a module so that we + // can resolve a file address, is a variable. + Variable *variable = GetVariable(); + if (variable) { + SymbolContext var_sc; + variable->CalculateSymbolContext(&var_sc); + module = var_sc.module_sp.get(); + } + } + + if (module) { + bool resolved = false; + ObjectFile *objfile = module->GetObjectFile(); + if (objfile) { + Address so_addr(address, objfile->GetSectionList()); + addr_t load_address = + so_addr.GetLoadAddress(exe_ctx->GetTargetPtr()); + bool process_launched_and_stopped = + exe_ctx->GetProcessPtr() + ? StateIsStoppedState(exe_ctx->GetProcessPtr()->GetState(), + true /* must_exist */) + : false; + // Don't use the load address if the process has exited. + if (load_address != LLDB_INVALID_ADDRESS && + process_launched_and_stopped) { + resolved = true; + address = load_address; + address_type = eAddressTypeLoad; + data.SetByteOrder( + exe_ctx->GetTargetRef().GetArchitecture().GetByteOrder()); + data.SetAddressByteSize(exe_ctx->GetTargetRef() + .GetArchitecture() + .GetAddressByteSize()); + } else { + if (so_addr.IsSectionOffset()) { + resolved = true; + file_so_addr = so_addr; + data.SetByteOrder(objfile->GetByteOrder()); + data.SetAddressByteSize(objfile->GetAddressByteSize()); + } + } + } + if (!resolved) { + Variable *variable = GetVariable(); + + if (module) { + if (variable) + error.SetErrorStringWithFormat( + "unable to resolve the module for file address 0x%" PRIx64 + " for variable '%s' in %s", + address, variable->GetName().AsCString(""), + module->GetFileSpec().GetPath().c_str()); + else + error.SetErrorStringWithFormat( + "unable to resolve the module for file address 0x%" PRIx64 + " in %s", + address, module->GetFileSpec().GetPath().c_str()); + } else { + if (variable) + error.SetErrorStringWithFormat( + "unable to resolve the module for file address 0x%" PRIx64 + " for variable '%s'", + address, variable->GetName().AsCString("")); + else + error.SetErrorStringWithFormat( + "unable to resolve the module for file address 0x%" PRIx64, + address); + } + } + } else { + // Can't convert a file address to anything valid without more + // context (which Module it came from) + error.SetErrorString( + "can't read memory from file address without more context"); + } + } + } + break; + + case ValueType::HostAddress: + address = m_value.ULongLong(LLDB_INVALID_ADDRESS); + address_type = eAddressTypeHost; + if (exe_ctx) { + Target *target = exe_ctx->GetTargetPtr(); + if (target) { + data.SetByteOrder(target->GetArchitecture().GetByteOrder()); + data.SetAddressByteSize(target->GetArchitecture().GetAddressByteSize()); + break; + } + } + // fallback to host settings + data.SetByteOrder(endian::InlHostByteOrder()); + data.SetAddressByteSize(sizeof(void *)); + break; + } + + // Bail if we encountered any errors + if (error.Fail()) + return error; + + if (address == LLDB_INVALID_ADDRESS) { + error.SetErrorStringWithFormat("invalid %s address", + address_type == eAddressTypeHost ? "host" + : "load"); + return error; + } + + // If we got here, we need to read the value from memory. + size_t byte_size = GetValueByteSize(&error, exe_ctx); + + // Bail if we encountered any errors getting the byte size. + if (error.Fail()) + return error; + + // No memory to read for zero-sized types. + if (byte_size == 0) + return error; + + // Make sure we have enough room within "data", and if we don't make + // something large enough that does + if (!data.ValidOffsetForDataOfSize(0, byte_size)) { + auto data_sp = std::make_shared<DataBufferHeap>(byte_size, '\0'); + data.SetData(data_sp); + } + + uint8_t *dst = const_cast<uint8_t *>(data.PeekData(0, byte_size)); + if (dst != nullptr) { + if (address_type == eAddressTypeHost) { + // The address is an address in this process, so just copy it. + if (address == 0) { + error.SetErrorString("trying to read from host address of 0."); + return error; + } + memcpy(dst, reinterpret_cast<uint8_t *>(address), byte_size); + } else if ((address_type == eAddressTypeLoad) || + (address_type == eAddressTypeFile)) { + if (file_so_addr.IsValid()) { + const bool force_live_memory = true; + if (exe_ctx->GetTargetRef().ReadMemory(file_so_addr, dst, byte_size, + error, force_live_memory) != + byte_size) { + error.SetErrorStringWithFormat( + "read memory from 0x%" PRIx64 " failed", (uint64_t)address); + } + } else { + // The execution context might have a NULL process, but it might have a + // valid process in the exe_ctx->target, so use the + // ExecutionContext::GetProcess accessor to ensure we get the process + // if there is one. + Process *process = exe_ctx->GetProcessPtr(); + + if (process) { + const size_t bytes_read = + process->ReadMemory(address, dst, byte_size, error); + if (bytes_read != byte_size) + error.SetErrorStringWithFormat( + "read memory from 0x%" PRIx64 " failed (%u of %u bytes read)", + (uint64_t)address, (uint32_t)bytes_read, (uint32_t)byte_size); + } else { + error.SetErrorStringWithFormat("read memory from 0x%" PRIx64 + " failed (invalid process)", + (uint64_t)address); + } + } + } else { + error.SetErrorStringWithFormat("unsupported AddressType value (%i)", + address_type); + } + } else { + error.SetErrorString("out of memory"); + } + + return error; +} + +Scalar &Value::ResolveValue(ExecutionContext *exe_ctx, Module *module) { + const CompilerType &compiler_type = GetCompilerType(); + if (compiler_type.IsValid()) { + switch (m_value_type) { + case ValueType::Invalid: + case ValueType::Scalar: // raw scalar value + break; + + case ValueType::FileAddress: + case ValueType::LoadAddress: // load address value + case ValueType::HostAddress: // host address value (for memory in the process + // that is using liblldb) + { + DataExtractor data; + lldb::addr_t addr = m_value.ULongLong(LLDB_INVALID_ADDRESS); + Status error(GetValueAsData(exe_ctx, data, module)); + if (error.Success()) { + Scalar scalar; + if (compiler_type.GetValueAsScalar( + data, 0, data.GetByteSize(), scalar, + exe_ctx ? exe_ctx->GetBestExecutionContextScope() : nullptr)) { + m_value = scalar; + m_value_type = ValueType::Scalar; + } else { + if ((uintptr_t)addr != (uintptr_t)m_data_buffer.GetBytes()) { + m_value.Clear(); + m_value_type = ValueType::Scalar; + } + } + } else { + if ((uintptr_t)addr != (uintptr_t)m_data_buffer.GetBytes()) { + m_value.Clear(); + m_value_type = ValueType::Scalar; + } + } + } break; + } + } + return m_value; +} + +Variable *Value::GetVariable() { + if (m_context_type == ContextType::Variable) + return static_cast<Variable *>(m_context); + return nullptr; +} + +void Value::Clear() { + m_value.Clear(); + m_compiler_type.Clear(); + m_value_type = ValueType::Scalar; + m_context = nullptr; + m_context_type = ContextType::Invalid; + m_data_buffer.Clear(); +} + +const char *Value::GetValueTypeAsCString(ValueType value_type) { + switch (value_type) { + case ValueType::Invalid: + return "invalid"; + case ValueType::Scalar: + return "scalar"; + case ValueType::FileAddress: + return "file address"; + case ValueType::LoadAddress: + return "load address"; + case ValueType::HostAddress: + return "host address"; + }; + llvm_unreachable("enum cases exhausted."); +} + +const char *Value::GetContextTypeAsCString(ContextType context_type) { + switch (context_type) { + case ContextType::Invalid: + return "invalid"; + case ContextType::RegisterInfo: + return "RegisterInfo *"; + case ContextType::LLDBType: + return "Type *"; + case ContextType::Variable: + return "Variable *"; + }; + llvm_unreachable("enum cases exhausted."); +} + +void Value::ConvertToLoadAddress(Module *module, Target *target) { + if (!module || !target || (GetValueType() != ValueType::FileAddress)) + return; + + lldb::addr_t file_addr = GetScalar().ULongLong(LLDB_INVALID_ADDRESS); + if (file_addr == LLDB_INVALID_ADDRESS) + return; + + Address so_addr; + if (!module->ResolveFileAddress(file_addr, so_addr)) + return; + lldb::addr_t load_addr = so_addr.GetLoadAddress(target); + if (load_addr == LLDB_INVALID_ADDRESS) + return; + + SetValueType(Value::ValueType::LoadAddress); + GetScalar() = load_addr; +} + +void ValueList::PushValue(const Value &value) { m_values.push_back(value); } + +size_t ValueList::GetSize() { return m_values.size(); } + +Value *ValueList::GetValueAtIndex(size_t idx) { + if (idx < GetSize()) { + return &(m_values[idx]); + } else + return nullptr; +} + +void ValueList::Clear() { m_values.clear(); } diff --git a/contrib/llvm-project/lldb/source/Core/ValueObject.cpp b/contrib/llvm-project/lldb/source/Core/ValueObject.cpp new file mode 100644 index 000000000000..8f72efc2299b --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/ValueObject.cpp @@ -0,0 +1,3781 @@ +//===-- ValueObject.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/Core/ValueObject.h" + +#include "lldb/Core/Address.h" +#include "lldb/Core/Declaration.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ValueObjectCast.h" +#include "lldb/Core/ValueObjectChild.h" +#include "lldb/Core/ValueObjectConstResult.h" +#include "lldb/Core/ValueObjectDynamicValue.h" +#include "lldb/Core/ValueObjectMemory.h" +#include "lldb/Core/ValueObjectSyntheticFilter.h" +#include "lldb/Core/ValueObjectVTable.h" +#include "lldb/DataFormatters/DataVisualization.h" +#include "lldb/DataFormatters/DumpValueObjectOptions.h" +#include "lldb/DataFormatters/FormatManager.h" +#include "lldb/DataFormatters/StringPrinter.h" +#include "lldb/DataFormatters/TypeFormat.h" +#include "lldb/DataFormatters/TypeSummary.h" +#include "lldb/DataFormatters/ValueObjectPrinter.h" +#include "lldb/Expression/ExpressionVariable.h" +#include "lldb/Host/Config.h" +#include "lldb/Symbol/CompileUnit.h" +#include "lldb/Symbol/CompilerType.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Symbol/Type.h" +#include "lldb/Symbol/Variable.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Language.h" +#include "lldb/Target/LanguageRuntime.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadList.h" +#include "lldb/Utility/DataBuffer.h" +#include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/Flags.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Scalar.h" +#include "lldb/Utility/Stream.h" +#include "lldb/Utility/StreamString.h" +#include "lldb/lldb-private-types.h" + +#include "llvm/Support/Compiler.h" + +#include <algorithm> +#include <cstdint> +#include <cstdlib> +#include <memory> +#include <optional> +#include <tuple> + +#include <cassert> +#include <cinttypes> +#include <cstdio> +#include <cstring> + +#include <lldb/Core/ValueObject.h> + +namespace lldb_private { +class ExecutionContextScope; +} +namespace lldb_private { +class SymbolContextScope; +} + +using namespace lldb; +using namespace lldb_private; + +static user_id_t g_value_obj_uid = 0; + +// ValueObject constructor +ValueObject::ValueObject(ValueObject &parent) + : m_parent(&parent), m_update_point(parent.GetUpdatePoint()), + m_manager(parent.GetManager()), m_id(++g_value_obj_uid) { + m_flags.m_is_synthetic_children_generated = + parent.m_flags.m_is_synthetic_children_generated; + m_data.SetByteOrder(parent.GetDataExtractor().GetByteOrder()); + m_data.SetAddressByteSize(parent.GetDataExtractor().GetAddressByteSize()); + m_manager->ManageObject(this); +} + +// ValueObject constructor +ValueObject::ValueObject(ExecutionContextScope *exe_scope, + ValueObjectManager &manager, + AddressType child_ptr_or_ref_addr_type) + : m_update_point(exe_scope), m_manager(&manager), + m_address_type_of_ptr_or_ref_children(child_ptr_or_ref_addr_type), + m_id(++g_value_obj_uid) { + if (exe_scope) { + TargetSP target_sp(exe_scope->CalculateTarget()); + if (target_sp) { + const ArchSpec &arch = target_sp->GetArchitecture(); + m_data.SetByteOrder(arch.GetByteOrder()); + m_data.SetAddressByteSize(arch.GetAddressByteSize()); + } + } + m_manager->ManageObject(this); +} + +// Destructor +ValueObject::~ValueObject() = default; + +bool ValueObject::UpdateValueIfNeeded(bool update_format) { + + bool did_change_formats = false; + + if (update_format) + did_change_formats = UpdateFormatsIfNeeded(); + + // If this is a constant value, then our success is predicated on whether we + // have an error or not + if (GetIsConstant()) { + // if you are constant, things might still have changed behind your back + // (e.g. you are a frozen object and things have changed deeper than you + // cared to freeze-dry yourself) in this case, your value has not changed, + // but "computed" entries might have, so you might now have a different + // summary, or a different object description. clear these so we will + // recompute them + if (update_format && !did_change_formats) + ClearUserVisibleData(eClearUserVisibleDataItemsSummary | + eClearUserVisibleDataItemsDescription); + return m_error.Success(); + } + + bool first_update = IsChecksumEmpty(); + + if (NeedsUpdating()) { + m_update_point.SetUpdated(); + + // Save the old value using swap to avoid a string copy which also will + // clear our m_value_str + if (m_value_str.empty()) { + m_flags.m_old_value_valid = false; + } else { + m_flags.m_old_value_valid = true; + m_old_value_str.swap(m_value_str); + ClearUserVisibleData(eClearUserVisibleDataItemsValue); + } + + ClearUserVisibleData(); + + if (IsInScope()) { + const bool value_was_valid = GetValueIsValid(); + SetValueDidChange(false); + + m_error.Clear(); + + // Call the pure virtual function to update the value + + bool need_compare_checksums = false; + llvm::SmallVector<uint8_t, 16> old_checksum; + + if (!first_update && CanProvideValue()) { + need_compare_checksums = true; + old_checksum.resize(m_value_checksum.size()); + std::copy(m_value_checksum.begin(), m_value_checksum.end(), + old_checksum.begin()); + } + + bool success = UpdateValue(); + + SetValueIsValid(success); + + if (success) { + UpdateChildrenAddressType(); + const uint64_t max_checksum_size = 128; + m_data.Checksum(m_value_checksum, max_checksum_size); + } else { + need_compare_checksums = false; + m_value_checksum.clear(); + } + + assert(!need_compare_checksums || + (!old_checksum.empty() && !m_value_checksum.empty())); + + if (first_update) + SetValueDidChange(false); + else if (!m_flags.m_value_did_change && !success) { + // The value wasn't gotten successfully, so we mark this as changed if + // the value used to be valid and now isn't + SetValueDidChange(value_was_valid); + } else if (need_compare_checksums) { + SetValueDidChange(memcmp(&old_checksum[0], &m_value_checksum[0], + m_value_checksum.size())); + } + + } else { + m_error.SetErrorString("out of scope"); + } + } + return m_error.Success(); +} + +bool ValueObject::UpdateFormatsIfNeeded() { + Log *log = GetLog(LLDBLog::DataFormatters); + LLDB_LOGF(log, + "[%s %p] checking for FormatManager revisions. ValueObject " + "rev: %d - Global rev: %d", + GetName().GetCString(), static_cast<void *>(this), + m_last_format_mgr_revision, + DataVisualization::GetCurrentRevision()); + + bool any_change = false; + + if ((m_last_format_mgr_revision != DataVisualization::GetCurrentRevision())) { + m_last_format_mgr_revision = DataVisualization::GetCurrentRevision(); + any_change = true; + + SetValueFormat(DataVisualization::GetFormat(*this, GetDynamicValueType())); + SetSummaryFormat( + DataVisualization::GetSummaryFormat(*this, GetDynamicValueType())); + SetSyntheticChildren( + DataVisualization::GetSyntheticChildren(*this, GetDynamicValueType())); + } + + return any_change; +} + +void ValueObject::SetNeedsUpdate() { + m_update_point.SetNeedsUpdate(); + // We have to clear the value string here so ConstResult children will notice + // if their values are changed by hand (i.e. with SetValueAsCString). + ClearUserVisibleData(eClearUserVisibleDataItemsValue); +} + +void ValueObject::ClearDynamicTypeInformation() { + m_flags.m_children_count_valid = false; + m_flags.m_did_calculate_complete_objc_class_type = false; + m_last_format_mgr_revision = 0; + m_override_type = CompilerType(); + SetValueFormat(lldb::TypeFormatImplSP()); + SetSummaryFormat(lldb::TypeSummaryImplSP()); + SetSyntheticChildren(lldb::SyntheticChildrenSP()); +} + +CompilerType ValueObject::MaybeCalculateCompleteType() { + CompilerType compiler_type(GetCompilerTypeImpl()); + + if (m_flags.m_did_calculate_complete_objc_class_type) { + if (m_override_type.IsValid()) + return m_override_type; + else + return compiler_type; + } + + m_flags.m_did_calculate_complete_objc_class_type = true; + + ProcessSP process_sp( + GetUpdatePoint().GetExecutionContextRef().GetProcessSP()); + + if (!process_sp) + return compiler_type; + + if (auto *runtime = + process_sp->GetLanguageRuntime(GetObjectRuntimeLanguage())) { + if (std::optional<CompilerType> complete_type = + runtime->GetRuntimeType(compiler_type)) { + m_override_type = *complete_type; + if (m_override_type.IsValid()) + return m_override_type; + } + } + return compiler_type; +} + + + +DataExtractor &ValueObject::GetDataExtractor() { + UpdateValueIfNeeded(false); + return m_data; +} + +const Status &ValueObject::GetError() { + UpdateValueIfNeeded(false); + return m_error; +} + +const char *ValueObject::GetLocationAsCStringImpl(const Value &value, + const DataExtractor &data) { + if (UpdateValueIfNeeded(false)) { + if (m_location_str.empty()) { + StreamString sstr; + + Value::ValueType value_type = value.GetValueType(); + + switch (value_type) { + case Value::ValueType::Invalid: + m_location_str = "invalid"; + break; + case Value::ValueType::Scalar: + if (value.GetContextType() == Value::ContextType::RegisterInfo) { + RegisterInfo *reg_info = value.GetRegisterInfo(); + if (reg_info) { + if (reg_info->name) + m_location_str = reg_info->name; + else if (reg_info->alt_name) + m_location_str = reg_info->alt_name; + if (m_location_str.empty()) + m_location_str = (reg_info->encoding == lldb::eEncodingVector) + ? "vector" + : "scalar"; + } + } + if (m_location_str.empty()) + m_location_str = "scalar"; + break; + + case Value::ValueType::LoadAddress: + case Value::ValueType::FileAddress: + case Value::ValueType::HostAddress: { + uint32_t addr_nibble_size = data.GetAddressByteSize() * 2; + sstr.Printf("0x%*.*llx", addr_nibble_size, addr_nibble_size, + value.GetScalar().ULongLong(LLDB_INVALID_ADDRESS)); + m_location_str = std::string(sstr.GetString()); + } break; + } + } + } + return m_location_str.c_str(); +} + +bool ValueObject::ResolveValue(Scalar &scalar) { + if (UpdateValueIfNeeded( + false)) // make sure that you are up to date before returning anything + { + ExecutionContext exe_ctx(GetExecutionContextRef()); + Value tmp_value(m_value); + scalar = tmp_value.ResolveValue(&exe_ctx, GetModule().get()); + if (scalar.IsValid()) { + const uint32_t bitfield_bit_size = GetBitfieldBitSize(); + if (bitfield_bit_size) + return scalar.ExtractBitfield(bitfield_bit_size, + GetBitfieldBitOffset()); + return true; + } + } + return false; +} + +bool ValueObject::IsLogicalTrue(Status &error) { + if (Language *language = Language::FindPlugin(GetObjectRuntimeLanguage())) { + LazyBool is_logical_true = language->IsLogicalTrue(*this, error); + switch (is_logical_true) { + case eLazyBoolYes: + case eLazyBoolNo: + return (is_logical_true == true); + case eLazyBoolCalculate: + break; + } + } + + Scalar scalar_value; + + if (!ResolveValue(scalar_value)) { + error.SetErrorString("failed to get a scalar result"); + return false; + } + + bool ret; + ret = scalar_value.ULongLong(1) != 0; + error.Clear(); + return ret; +} + +ValueObjectSP ValueObject::GetChildAtIndex(uint32_t idx, bool can_create) { + ValueObjectSP child_sp; + // We may need to update our value if we are dynamic + if (IsPossibleDynamicType()) + UpdateValueIfNeeded(false); + if (idx < GetNumChildrenIgnoringErrors()) { + // Check if we have already made the child value object? + if (can_create && !m_children.HasChildAtIndex(idx)) { + // No we haven't created the child at this index, so lets have our + // subclass do it and cache the result for quick future access. + m_children.SetChildAtIndex(idx, CreateChildAtIndex(idx)); + } + + ValueObject *child = m_children.GetChildAtIndex(idx); + if (child != nullptr) + return child->GetSP(); + } + return child_sp; +} + +lldb::ValueObjectSP +ValueObject::GetChildAtNamePath(llvm::ArrayRef<llvm::StringRef> names) { + if (names.size() == 0) + return GetSP(); + ValueObjectSP root(GetSP()); + for (llvm::StringRef name : names) { + root = root->GetChildMemberWithName(name); + if (!root) { + return root; + } + } + return root; +} + +size_t ValueObject::GetIndexOfChildWithName(llvm::StringRef name) { + bool omit_empty_base_classes = true; + return GetCompilerType().GetIndexOfChildWithName(name, + omit_empty_base_classes); +} + +ValueObjectSP ValueObject::GetChildMemberWithName(llvm::StringRef name, + bool can_create) { + // We may need to update our value if we are dynamic. + if (IsPossibleDynamicType()) + UpdateValueIfNeeded(false); + + // When getting a child by name, it could be buried inside some base classes + // (which really aren't part of the expression path), so we need a vector of + // indexes that can get us down to the correct child. + std::vector<uint32_t> child_indexes; + bool omit_empty_base_classes = true; + + if (!GetCompilerType().IsValid()) + return ValueObjectSP(); + + const size_t num_child_indexes = + GetCompilerType().GetIndexOfChildMemberWithName( + name, omit_empty_base_classes, child_indexes); + if (num_child_indexes == 0) + return nullptr; + + ValueObjectSP child_sp = GetSP(); + for (uint32_t idx : child_indexes) + if (child_sp) + child_sp = child_sp->GetChildAtIndex(idx, can_create); + return child_sp; +} + +llvm::Expected<uint32_t> ValueObject::GetNumChildren(uint32_t max) { + UpdateValueIfNeeded(); + + if (max < UINT32_MAX) { + if (m_flags.m_children_count_valid) { + size_t children_count = m_children.GetChildrenCount(); + return children_count <= max ? children_count : max; + } else + return CalculateNumChildren(max); + } + + if (!m_flags.m_children_count_valid) { + auto num_children_or_err = CalculateNumChildren(); + if (num_children_or_err) + SetNumChildren(*num_children_or_err); + else + return num_children_or_err; + } + return m_children.GetChildrenCount(); +} + +uint32_t ValueObject::GetNumChildrenIgnoringErrors(uint32_t max) { + auto value_or_err = GetNumChildren(max); + if (value_or_err) + return *value_or_err; + LLDB_LOG_ERRORV(GetLog(LLDBLog::DataFormatters), value_or_err.takeError(), + "{0}"); + return 0; +} + +bool ValueObject::MightHaveChildren() { + bool has_children = false; + const uint32_t type_info = GetTypeInfo(); + if (type_info) { + if (type_info & (eTypeHasChildren | eTypeIsPointer | eTypeIsReference)) + has_children = true; + } else { + has_children = GetNumChildrenIgnoringErrors() > 0; + } + return has_children; +} + +// Should only be called by ValueObject::GetNumChildren() +void ValueObject::SetNumChildren(uint32_t num_children) { + m_flags.m_children_count_valid = true; + m_children.SetChildrenCount(num_children); +} + +ValueObject *ValueObject::CreateChildAtIndex(size_t idx) { + bool omit_empty_base_classes = true; + bool ignore_array_bounds = false; + std::string child_name; + uint32_t child_byte_size = 0; + int32_t child_byte_offset = 0; + uint32_t child_bitfield_bit_size = 0; + uint32_t child_bitfield_bit_offset = 0; + bool child_is_base_class = false; + bool child_is_deref_of_parent = false; + uint64_t language_flags = 0; + const bool transparent_pointers = true; + + ExecutionContext exe_ctx(GetExecutionContextRef()); + + auto child_compiler_type_or_err = + GetCompilerType().GetChildCompilerTypeAtIndex( + &exe_ctx, idx, transparent_pointers, omit_empty_base_classes, + ignore_array_bounds, child_name, child_byte_size, child_byte_offset, + child_bitfield_bit_size, child_bitfield_bit_offset, + child_is_base_class, child_is_deref_of_parent, this, language_flags); + if (!child_compiler_type_or_err || !child_compiler_type_or_err->IsValid()) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Types), + child_compiler_type_or_err.takeError(), + "could not find child: {0}"); + return nullptr; + } + + return new ValueObjectChild( + *this, *child_compiler_type_or_err, ConstString(child_name), + child_byte_size, child_byte_offset, child_bitfield_bit_size, + child_bitfield_bit_offset, child_is_base_class, child_is_deref_of_parent, + eAddressTypeInvalid, language_flags); +} + +ValueObject *ValueObject::CreateSyntheticArrayMember(size_t idx) { + bool omit_empty_base_classes = true; + bool ignore_array_bounds = true; + std::string child_name; + uint32_t child_byte_size = 0; + int32_t child_byte_offset = 0; + uint32_t child_bitfield_bit_size = 0; + uint32_t child_bitfield_bit_offset = 0; + bool child_is_base_class = false; + bool child_is_deref_of_parent = false; + uint64_t language_flags = 0; + const bool transparent_pointers = false; + + ExecutionContext exe_ctx(GetExecutionContextRef()); + + auto child_compiler_type_or_err = + GetCompilerType().GetChildCompilerTypeAtIndex( + &exe_ctx, 0, transparent_pointers, omit_empty_base_classes, + ignore_array_bounds, child_name, child_byte_size, child_byte_offset, + child_bitfield_bit_size, child_bitfield_bit_offset, + child_is_base_class, child_is_deref_of_parent, this, language_flags); + if (!child_compiler_type_or_err) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Types), + child_compiler_type_or_err.takeError(), + "could not find child: {0}"); + return nullptr; + } + + if (child_compiler_type_or_err->IsValid()) { + child_byte_offset += child_byte_size * idx; + + return new ValueObjectChild( + *this, *child_compiler_type_or_err, ConstString(child_name), + child_byte_size, child_byte_offset, child_bitfield_bit_size, + child_bitfield_bit_offset, child_is_base_class, + child_is_deref_of_parent, eAddressTypeInvalid, language_flags); + } + + // In case of an incomplete type, try to use the ValueObject's + // synthetic value to create the child ValueObject. + if (ValueObjectSP synth_valobj_sp = GetSyntheticValue()) + return synth_valobj_sp->GetChildAtIndex(idx, /*can_create=*/true).get(); + + return nullptr; +} + +bool ValueObject::GetSummaryAsCString(TypeSummaryImpl *summary_ptr, + std::string &destination, + lldb::LanguageType lang) { + return GetSummaryAsCString(summary_ptr, destination, + TypeSummaryOptions().SetLanguage(lang)); +} + +bool ValueObject::GetSummaryAsCString(TypeSummaryImpl *summary_ptr, + std::string &destination, + const TypeSummaryOptions &options) { + destination.clear(); + + // If we have a forcefully completed type, don't try and show a summary from + // a valid summary string or function because the type is not complete and + // no member variables or member functions will be available. + if (GetCompilerType().IsForcefullyCompleted()) { + destination = "<incomplete type>"; + return true; + } + + // ideally we would like to bail out if passing NULL, but if we do so we end + // up not providing the summary for function pointers anymore + if (/*summary_ptr == NULL ||*/ m_flags.m_is_getting_summary) + return false; + + m_flags.m_is_getting_summary = true; + + TypeSummaryOptions actual_options(options); + + if (actual_options.GetLanguage() == lldb::eLanguageTypeUnknown) + actual_options.SetLanguage(GetPreferredDisplayLanguage()); + + // this is a hot path in code and we prefer to avoid setting this string all + // too often also clearing out other information that we might care to see in + // a crash log. might be useful in very specific situations though. + /*Host::SetCrashDescriptionWithFormat("Trying to fetch a summary for %s %s. + Summary provider's description is %s", + GetTypeName().GetCString(), + GetName().GetCString(), + summary_ptr->GetDescription().c_str());*/ + + if (UpdateValueIfNeeded(false) && summary_ptr) { + if (HasSyntheticValue()) + m_synthetic_value->UpdateValueIfNeeded(); // the summary might depend on + // the synthetic children being + // up-to-date (e.g. ${svar%#}) + summary_ptr->FormatObject(this, destination, actual_options); + } + m_flags.m_is_getting_summary = false; + return !destination.empty(); +} + +const char *ValueObject::GetSummaryAsCString(lldb::LanguageType lang) { + if (UpdateValueIfNeeded(true) && m_summary_str.empty()) { + TypeSummaryOptions summary_options; + summary_options.SetLanguage(lang); + GetSummaryAsCString(GetSummaryFormat().get(), m_summary_str, + summary_options); + } + if (m_summary_str.empty()) + return nullptr; + return m_summary_str.c_str(); +} + +bool ValueObject::GetSummaryAsCString(std::string &destination, + const TypeSummaryOptions &options) { + return GetSummaryAsCString(GetSummaryFormat().get(), destination, options); +} + +bool ValueObject::IsCStringContainer(bool check_pointer) { + CompilerType pointee_or_element_compiler_type; + const Flags type_flags(GetTypeInfo(&pointee_or_element_compiler_type)); + bool is_char_arr_ptr(type_flags.AnySet(eTypeIsArray | eTypeIsPointer) && + pointee_or_element_compiler_type.IsCharType()); + if (!is_char_arr_ptr) + return false; + if (!check_pointer) + return true; + if (type_flags.Test(eTypeIsArray)) + return true; + addr_t cstr_address = LLDB_INVALID_ADDRESS; + AddressType cstr_address_type = eAddressTypeInvalid; + cstr_address = GetPointerValue(&cstr_address_type); + return (cstr_address != LLDB_INVALID_ADDRESS); +} + +size_t ValueObject::GetPointeeData(DataExtractor &data, uint32_t item_idx, + uint32_t item_count) { + CompilerType pointee_or_element_compiler_type; + const uint32_t type_info = GetTypeInfo(&pointee_or_element_compiler_type); + const bool is_pointer_type = type_info & eTypeIsPointer; + const bool is_array_type = type_info & eTypeIsArray; + if (!(is_pointer_type || is_array_type)) + return 0; + + if (item_count == 0) + return 0; + + ExecutionContext exe_ctx(GetExecutionContextRef()); + + std::optional<uint64_t> item_type_size = + pointee_or_element_compiler_type.GetByteSize( + exe_ctx.GetBestExecutionContextScope()); + if (!item_type_size) + return 0; + const uint64_t bytes = item_count * *item_type_size; + const uint64_t offset = item_idx * *item_type_size; + + if (item_idx == 0 && item_count == 1) // simply a deref + { + if (is_pointer_type) { + Status error; + ValueObjectSP pointee_sp = Dereference(error); + if (error.Fail() || pointee_sp.get() == nullptr) + return 0; + return pointee_sp->GetData(data, error); + } else { + ValueObjectSP child_sp = GetChildAtIndex(0); + if (child_sp.get() == nullptr) + return 0; + Status error; + return child_sp->GetData(data, error); + } + return true; + } else /* (items > 1) */ + { + Status error; + lldb_private::DataBufferHeap *heap_buf_ptr = nullptr; + lldb::DataBufferSP data_sp(heap_buf_ptr = + new lldb_private::DataBufferHeap()); + + AddressType addr_type; + lldb::addr_t addr = is_pointer_type ? GetPointerValue(&addr_type) + : GetAddressOf(true, &addr_type); + + switch (addr_type) { + case eAddressTypeFile: { + ModuleSP module_sp(GetModule()); + if (module_sp) { + addr = addr + offset; + Address so_addr; + module_sp->ResolveFileAddress(addr, so_addr); + ExecutionContext exe_ctx(GetExecutionContextRef()); + Target *target = exe_ctx.GetTargetPtr(); + if (target) { + heap_buf_ptr->SetByteSize(bytes); + size_t bytes_read = target->ReadMemory( + so_addr, heap_buf_ptr->GetBytes(), bytes, error, true); + if (error.Success()) { + data.SetData(data_sp); + return bytes_read; + } + } + } + } break; + case eAddressTypeLoad: { + ExecutionContext exe_ctx(GetExecutionContextRef()); + Process *process = exe_ctx.GetProcessPtr(); + if (process) { + heap_buf_ptr->SetByteSize(bytes); + size_t bytes_read = process->ReadMemory( + addr + offset, heap_buf_ptr->GetBytes(), bytes, error); + if (error.Success() || bytes_read > 0) { + data.SetData(data_sp); + return bytes_read; + } + } + } break; + case eAddressTypeHost: { + auto max_bytes = + GetCompilerType().GetByteSize(exe_ctx.GetBestExecutionContextScope()); + if (max_bytes && *max_bytes > offset) { + size_t bytes_read = std::min<uint64_t>(*max_bytes - offset, bytes); + addr = m_value.GetScalar().ULongLong(LLDB_INVALID_ADDRESS); + if (addr == 0 || addr == LLDB_INVALID_ADDRESS) + break; + heap_buf_ptr->CopyData((uint8_t *)(addr + offset), bytes_read); + data.SetData(data_sp); + return bytes_read; + } + } break; + case eAddressTypeInvalid: + break; + } + } + return 0; +} + +uint64_t ValueObject::GetData(DataExtractor &data, Status &error) { + UpdateValueIfNeeded(false); + ExecutionContext exe_ctx(GetExecutionContextRef()); + error = m_value.GetValueAsData(&exe_ctx, data, GetModule().get()); + if (error.Fail()) { + if (m_data.GetByteSize()) { + data = m_data; + error.Clear(); + return data.GetByteSize(); + } else { + return 0; + } + } + data.SetAddressByteSize(m_data.GetAddressByteSize()); + data.SetByteOrder(m_data.GetByteOrder()); + return data.GetByteSize(); +} + +bool ValueObject::SetData(DataExtractor &data, Status &error) { + error.Clear(); + // Make sure our value is up to date first so that our location and location + // type is valid. + if (!UpdateValueIfNeeded(false)) { + error.SetErrorString("unable to read value"); + return false; + } + + uint64_t count = 0; + const Encoding encoding = GetCompilerType().GetEncoding(count); + + const size_t byte_size = GetByteSize().value_or(0); + + Value::ValueType value_type = m_value.GetValueType(); + + switch (value_type) { + case Value::ValueType::Invalid: + error.SetErrorString("invalid location"); + return false; + case Value::ValueType::Scalar: { + Status set_error = + m_value.GetScalar().SetValueFromData(data, encoding, byte_size); + + if (!set_error.Success()) { + error.SetErrorStringWithFormat("unable to set scalar value: %s", + set_error.AsCString()); + return false; + } + } break; + case Value::ValueType::LoadAddress: { + // If it is a load address, then the scalar value is the storage location + // of the data, and we have to shove this value down to that load location. + ExecutionContext exe_ctx(GetExecutionContextRef()); + Process *process = exe_ctx.GetProcessPtr(); + if (process) { + addr_t target_addr = m_value.GetScalar().ULongLong(LLDB_INVALID_ADDRESS); + size_t bytes_written = process->WriteMemory( + target_addr, data.GetDataStart(), byte_size, error); + if (!error.Success()) + return false; + if (bytes_written != byte_size) { + error.SetErrorString("unable to write value to memory"); + return false; + } + } + } break; + case Value::ValueType::HostAddress: { + // If it is a host address, then we stuff the scalar as a DataBuffer into + // the Value's data. + DataBufferSP buffer_sp(new DataBufferHeap(byte_size, 0)); + m_data.SetData(buffer_sp, 0); + data.CopyByteOrderedData(0, byte_size, + const_cast<uint8_t *>(m_data.GetDataStart()), + byte_size, m_data.GetByteOrder()); + m_value.GetScalar() = (uintptr_t)m_data.GetDataStart(); + } break; + case Value::ValueType::FileAddress: + break; + } + + // If we have reached this point, then we have successfully changed the + // value. + SetNeedsUpdate(); + return true; +} + +static bool CopyStringDataToBufferSP(const StreamString &source, + lldb::WritableDataBufferSP &destination) { + llvm::StringRef src = source.GetString(); + src = src.rtrim('\0'); + destination = std::make_shared<DataBufferHeap>(src.size(), 0); + memcpy(destination->GetBytes(), src.data(), src.size()); + return true; +} + +std::pair<size_t, bool> +ValueObject::ReadPointedString(lldb::WritableDataBufferSP &buffer_sp, + Status &error, bool honor_array) { + bool was_capped = false; + StreamString s; + ExecutionContext exe_ctx(GetExecutionContextRef()); + Target *target = exe_ctx.GetTargetPtr(); + + if (!target) { + s << "<no target to read from>"; + error.SetErrorString("no target to read from"); + CopyStringDataToBufferSP(s, buffer_sp); + return {0, was_capped}; + } + + const auto max_length = target->GetMaximumSizeOfStringSummary(); + + size_t bytes_read = 0; + size_t total_bytes_read = 0; + + CompilerType compiler_type = GetCompilerType(); + CompilerType elem_or_pointee_compiler_type; + const Flags type_flags(GetTypeInfo(&elem_or_pointee_compiler_type)); + if (type_flags.AnySet(eTypeIsArray | eTypeIsPointer) && + elem_or_pointee_compiler_type.IsCharType()) { + addr_t cstr_address = LLDB_INVALID_ADDRESS; + AddressType cstr_address_type = eAddressTypeInvalid; + + size_t cstr_len = 0; + bool capped_data = false; + const bool is_array = type_flags.Test(eTypeIsArray); + if (is_array) { + // We have an array + uint64_t array_size = 0; + if (compiler_type.IsArrayType(nullptr, &array_size)) { + cstr_len = array_size; + if (cstr_len > max_length) { + capped_data = true; + cstr_len = max_length; + } + } + cstr_address = GetAddressOf(true, &cstr_address_type); + } else { + // We have a pointer + cstr_address = GetPointerValue(&cstr_address_type); + } + + if (cstr_address == 0 || cstr_address == LLDB_INVALID_ADDRESS) { + if (cstr_address_type == eAddressTypeHost && is_array) { + const char *cstr = GetDataExtractor().PeekCStr(0); + if (cstr == nullptr) { + s << "<invalid address>"; + error.SetErrorString("invalid address"); + CopyStringDataToBufferSP(s, buffer_sp); + return {0, was_capped}; + } + s << llvm::StringRef(cstr, cstr_len); + CopyStringDataToBufferSP(s, buffer_sp); + return {cstr_len, was_capped}; + } else { + s << "<invalid address>"; + error.SetErrorString("invalid address"); + CopyStringDataToBufferSP(s, buffer_sp); + return {0, was_capped}; + } + } + + Address cstr_so_addr(cstr_address); + DataExtractor data; + if (cstr_len > 0 && honor_array) { + // I am using GetPointeeData() here to abstract the fact that some + // ValueObjects are actually frozen pointers in the host but the pointed- + // to data lives in the debuggee, and GetPointeeData() automatically + // takes care of this + GetPointeeData(data, 0, cstr_len); + + if ((bytes_read = data.GetByteSize()) > 0) { + total_bytes_read = bytes_read; + for (size_t offset = 0; offset < bytes_read; offset++) + s.Printf("%c", *data.PeekData(offset, 1)); + if (capped_data) + was_capped = true; + } + } else { + cstr_len = max_length; + const size_t k_max_buf_size = 64; + + size_t offset = 0; + + int cstr_len_displayed = -1; + bool capped_cstr = false; + // I am using GetPointeeData() here to abstract the fact that some + // ValueObjects are actually frozen pointers in the host but the pointed- + // to data lives in the debuggee, and GetPointeeData() automatically + // takes care of this + while ((bytes_read = GetPointeeData(data, offset, k_max_buf_size)) > 0) { + total_bytes_read += bytes_read; + const char *cstr = data.PeekCStr(0); + size_t len = strnlen(cstr, k_max_buf_size); + if (cstr_len_displayed < 0) + cstr_len_displayed = len; + + if (len == 0) + break; + cstr_len_displayed += len; + if (len > bytes_read) + len = bytes_read; + if (len > cstr_len) + len = cstr_len; + + for (size_t offset = 0; offset < bytes_read; offset++) + s.Printf("%c", *data.PeekData(offset, 1)); + + if (len < k_max_buf_size) + break; + + if (len >= cstr_len) { + capped_cstr = true; + break; + } + + cstr_len -= len; + offset += len; + } + + if (cstr_len_displayed >= 0) { + if (capped_cstr) + was_capped = true; + } + } + } else { + error.SetErrorString("not a string object"); + s << "<not a string object>"; + } + CopyStringDataToBufferSP(s, buffer_sp); + return {total_bytes_read, was_capped}; +} + +llvm::Expected<std::string> ValueObject::GetObjectDescription() { + if (!UpdateValueIfNeeded(true)) + return llvm::createStringError("could not update value"); + + // Return cached value. + if (!m_object_desc_str.empty()) + return m_object_desc_str; + + ExecutionContext exe_ctx(GetExecutionContextRef()); + Process *process = exe_ctx.GetProcessPtr(); + if (!process) + return llvm::createStringError("no process"); + + // Returns the object description produced by one language runtime. + auto get_object_description = + [&](LanguageType language) -> llvm::Expected<std::string> { + if (LanguageRuntime *runtime = process->GetLanguageRuntime(language)) { + StreamString s; + if (llvm::Error error = runtime->GetObjectDescription(s, *this)) + return error; + m_object_desc_str = s.GetString(); + return m_object_desc_str; + } + return llvm::createStringError("no native language runtime"); + }; + + // Try the native language runtime first. + LanguageType native_language = GetObjectRuntimeLanguage(); + llvm::Expected<std::string> desc = get_object_description(native_language); + if (desc) + return desc; + + // Try the Objective-C language runtime. This fallback is necessary + // for Objective-C++ and mixed Objective-C / C++ programs. + if (Language::LanguageIsCFamily(native_language)) { + // We're going to try again, so let's drop the first error. + llvm::consumeError(desc.takeError()); + return get_object_description(eLanguageTypeObjC); + } + return desc; +} + +bool ValueObject::GetValueAsCString(const lldb_private::TypeFormatImpl &format, + std::string &destination) { + if (UpdateValueIfNeeded(false)) + return format.FormatObject(this, destination); + else + return false; +} + +bool ValueObject::GetValueAsCString(lldb::Format format, + std::string &destination) { + return GetValueAsCString(TypeFormatImpl_Format(format), destination); +} + +const char *ValueObject::GetValueAsCString() { + if (UpdateValueIfNeeded(true)) { + lldb::TypeFormatImplSP format_sp; + lldb::Format my_format = GetFormat(); + if (my_format == lldb::eFormatDefault) { + if (m_type_format_sp) + format_sp = m_type_format_sp; + else { + if (m_flags.m_is_bitfield_for_scalar) + my_format = eFormatUnsigned; + else { + if (m_value.GetContextType() == Value::ContextType::RegisterInfo) { + const RegisterInfo *reg_info = m_value.GetRegisterInfo(); + if (reg_info) + my_format = reg_info->format; + } else { + my_format = GetValue().GetCompilerType().GetFormat(); + } + } + } + } + if (my_format != m_last_format || m_value_str.empty()) { + m_last_format = my_format; + if (!format_sp) + format_sp = std::make_shared<TypeFormatImpl_Format>(my_format); + if (GetValueAsCString(*format_sp.get(), m_value_str)) { + if (!m_flags.m_value_did_change && m_flags.m_old_value_valid) { + // The value was gotten successfully, so we consider the value as + // changed if the value string differs + SetValueDidChange(m_old_value_str != m_value_str); + } + } + } + } + if (m_value_str.empty()) + return nullptr; + return m_value_str.c_str(); +} + +// if > 8bytes, 0 is returned. this method should mostly be used to read +// address values out of pointers +uint64_t ValueObject::GetValueAsUnsigned(uint64_t fail_value, bool *success) { + // If our byte size is zero this is an aggregate type that has children + if (CanProvideValue()) { + Scalar scalar; + if (ResolveValue(scalar)) { + if (success) + *success = true; + scalar.MakeUnsigned(); + return scalar.ULongLong(fail_value); + } + // fallthrough, otherwise... + } + + if (success) + *success = false; + return fail_value; +} + +int64_t ValueObject::GetValueAsSigned(int64_t fail_value, bool *success) { + // If our byte size is zero this is an aggregate type that has children + if (CanProvideValue()) { + Scalar scalar; + if (ResolveValue(scalar)) { + if (success) + *success = true; + scalar.MakeSigned(); + return scalar.SLongLong(fail_value); + } + // fallthrough, otherwise... + } + + if (success) + *success = false; + return fail_value; +} + +llvm::Expected<llvm::APSInt> ValueObject::GetValueAsAPSInt() { + // Make sure the type can be converted to an APSInt. + if (!GetCompilerType().IsInteger() && + !GetCompilerType().IsScopedEnumerationType() && + !GetCompilerType().IsEnumerationType() && + !GetCompilerType().IsPointerType() && + !GetCompilerType().IsNullPtrType() && + !GetCompilerType().IsReferenceType() && !GetCompilerType().IsBoolean()) + return llvm::make_error<llvm::StringError>( + "type cannot be converted to APSInt", llvm::inconvertibleErrorCode()); + + if (CanProvideValue()) { + Scalar scalar; + if (ResolveValue(scalar)) + return scalar.GetAPSInt(); + } + + return llvm::make_error<llvm::StringError>( + "error occurred; unable to convert to APSInt", + llvm::inconvertibleErrorCode()); +} + +llvm::Expected<llvm::APFloat> ValueObject::GetValueAsAPFloat() { + if (!GetCompilerType().IsFloat()) + return llvm::make_error<llvm::StringError>( + "type cannot be converted to APFloat", llvm::inconvertibleErrorCode()); + + if (CanProvideValue()) { + Scalar scalar; + if (ResolveValue(scalar)) + return scalar.GetAPFloat(); + } + + return llvm::make_error<llvm::StringError>( + "error occurred; unable to convert to APFloat", + llvm::inconvertibleErrorCode()); +} + +llvm::Expected<bool> ValueObject::GetValueAsBool() { + CompilerType val_type = GetCompilerType(); + if (val_type.IsInteger() || val_type.IsUnscopedEnumerationType() || + val_type.IsPointerType()) { + auto value_or_err = GetValueAsAPSInt(); + if (value_or_err) + return value_or_err->getBoolValue(); + } + if (val_type.IsFloat()) { + auto value_or_err = GetValueAsAPFloat(); + if (value_or_err) + return value_or_err->isNonZero(); + } + if (val_type.IsArrayType()) + return GetAddressOf() != 0; + + return llvm::make_error<llvm::StringError>("type cannot be converted to bool", + llvm::inconvertibleErrorCode()); +} + +void ValueObject::SetValueFromInteger(const llvm::APInt &value, Status &error) { + // Verify the current object is an integer object + CompilerType val_type = GetCompilerType(); + if (!val_type.IsInteger() && !val_type.IsUnscopedEnumerationType() && + !val_type.IsFloat() && !val_type.IsPointerType() && + !val_type.IsScalarType()) { + error.SetErrorString("current value object is not an integer objet"); + return; + } + + // Verify the current object is not actually associated with any program + // variable. + if (GetVariable()) { + error.SetErrorString("current value object is not a temporary object"); + return; + } + + // Verify the proposed new value is the right size. + lldb::TargetSP target = GetTargetSP(); + uint64_t byte_size = 0; + if (auto temp = GetCompilerType().GetByteSize(target.get())) + byte_size = temp.value(); + if (value.getBitWidth() != byte_size * CHAR_BIT) { + error.SetErrorString( + "illegal argument: new value should be of the same size"); + return; + } + + lldb::DataExtractorSP data_sp; + data_sp->SetData(value.getRawData(), byte_size, + target->GetArchitecture().GetByteOrder()); + data_sp->SetAddressByteSize( + static_cast<uint8_t>(target->GetArchitecture().GetAddressByteSize())); + SetData(*data_sp, error); +} + +void ValueObject::SetValueFromInteger(lldb::ValueObjectSP new_val_sp, + Status &error) { + // Verify the current object is an integer object + CompilerType val_type = GetCompilerType(); + if (!val_type.IsInteger() && !val_type.IsUnscopedEnumerationType() && + !val_type.IsFloat() && !val_type.IsPointerType() && + !val_type.IsScalarType()) { + error.SetErrorString("current value object is not an integer objet"); + return; + } + + // Verify the current object is not actually associated with any program + // variable. + if (GetVariable()) { + error.SetErrorString("current value object is not a temporary object"); + return; + } + + // Verify the proposed new value is the right type. + CompilerType new_val_type = new_val_sp->GetCompilerType(); + if (!new_val_type.IsInteger() && !new_val_type.IsFloat() && + !new_val_type.IsPointerType()) { + error.SetErrorString( + "illegal argument: new value should be of the same size"); + return; + } + + if (new_val_type.IsInteger()) { + auto value_or_err = new_val_sp->GetValueAsAPSInt(); + if (value_or_err) + SetValueFromInteger(*value_or_err, error); + else + error.SetErrorString("error getting APSInt from new_val_sp"); + } else if (new_val_type.IsFloat()) { + auto value_or_err = new_val_sp->GetValueAsAPFloat(); + if (value_or_err) + SetValueFromInteger(value_or_err->bitcastToAPInt(), error); + else + error.SetErrorString("error getting APFloat from new_val_sp"); + } else if (new_val_type.IsPointerType()) { + bool success = true; + uint64_t int_val = new_val_sp->GetValueAsUnsigned(0, &success); + if (success) { + lldb::TargetSP target = GetTargetSP(); + uint64_t num_bits = 0; + if (auto temp = new_val_sp->GetCompilerType().GetBitSize(target.get())) + num_bits = temp.value(); + SetValueFromInteger(llvm::APInt(num_bits, int_val), error); + } else + error.SetErrorString("error converting new_val_sp to integer"); + } +} + +// if any more "special cases" are added to +// ValueObject::DumpPrintableRepresentation() please keep this call up to date +// by returning true for your new special cases. We will eventually move to +// checking this call result before trying to display special cases +bool ValueObject::HasSpecialPrintableRepresentation( + ValueObjectRepresentationStyle val_obj_display, Format custom_format) { + Flags flags(GetTypeInfo()); + if (flags.AnySet(eTypeIsArray | eTypeIsPointer) && + val_obj_display == ValueObject::eValueObjectRepresentationStyleValue) { + if (IsCStringContainer(true) && + (custom_format == eFormatCString || custom_format == eFormatCharArray || + custom_format == eFormatChar || custom_format == eFormatVectorOfChar)) + return true; + + if (flags.Test(eTypeIsArray)) { + if ((custom_format == eFormatBytes) || + (custom_format == eFormatBytesWithASCII)) + return true; + + if ((custom_format == eFormatVectorOfChar) || + (custom_format == eFormatVectorOfFloat32) || + (custom_format == eFormatVectorOfFloat64) || + (custom_format == eFormatVectorOfSInt16) || + (custom_format == eFormatVectorOfSInt32) || + (custom_format == eFormatVectorOfSInt64) || + (custom_format == eFormatVectorOfSInt8) || + (custom_format == eFormatVectorOfUInt128) || + (custom_format == eFormatVectorOfUInt16) || + (custom_format == eFormatVectorOfUInt32) || + (custom_format == eFormatVectorOfUInt64) || + (custom_format == eFormatVectorOfUInt8)) + return true; + } + } + return false; +} + +bool ValueObject::DumpPrintableRepresentation( + Stream &s, ValueObjectRepresentationStyle val_obj_display, + Format custom_format, PrintableRepresentationSpecialCases special, + bool do_dump_error) { + + // If the ValueObject has an error, we might end up dumping the type, which + // is useful, but if we don't even have a type, then don't examine the object + // further as that's not meaningful, only the error is. + if (m_error.Fail() && !GetCompilerType().IsValid()) { + if (do_dump_error) + s.Printf("<%s>", m_error.AsCString()); + return false; + } + + Flags flags(GetTypeInfo()); + + bool allow_special = + (special == ValueObject::PrintableRepresentationSpecialCases::eAllow); + const bool only_special = false; + + if (allow_special) { + if (flags.AnySet(eTypeIsArray | eTypeIsPointer) && + val_obj_display == ValueObject::eValueObjectRepresentationStyleValue) { + // when being asked to get a printable display an array or pointer type + // directly, try to "do the right thing" + + if (IsCStringContainer(true) && + (custom_format == eFormatCString || + custom_format == eFormatCharArray || custom_format == eFormatChar || + custom_format == + eFormatVectorOfChar)) // print char[] & char* directly + { + Status error; + lldb::WritableDataBufferSP buffer_sp; + std::pair<size_t, bool> read_string = + ReadPointedString(buffer_sp, error, + (custom_format == eFormatVectorOfChar) || + (custom_format == eFormatCharArray)); + lldb_private::formatters::StringPrinter:: + ReadBufferAndDumpToStreamOptions options(*this); + options.SetData(DataExtractor( + buffer_sp, lldb::eByteOrderInvalid, + 8)); // none of this matters for a string - pass some defaults + options.SetStream(&s); + options.SetPrefixToken(nullptr); + options.SetQuote('"'); + options.SetSourceSize(buffer_sp->GetByteSize()); + options.SetIsTruncated(read_string.second); + options.SetBinaryZeroIsTerminator(custom_format != eFormatVectorOfChar); + formatters::StringPrinter::ReadBufferAndDumpToStream< + lldb_private::formatters::StringPrinter::StringElementType::ASCII>( + options); + return !error.Fail(); + } + + if (custom_format == eFormatEnum) + return false; + + // this only works for arrays, because I have no way to know when the + // pointed memory ends, and no special \0 end of data marker + if (flags.Test(eTypeIsArray)) { + if ((custom_format == eFormatBytes) || + (custom_format == eFormatBytesWithASCII)) { + const size_t count = GetNumChildrenIgnoringErrors(); + + s << '['; + for (size_t low = 0; low < count; low++) { + + if (low) + s << ','; + + ValueObjectSP child = GetChildAtIndex(low); + if (!child.get()) { + s << "<invalid child>"; + continue; + } + child->DumpPrintableRepresentation( + s, ValueObject::eValueObjectRepresentationStyleValue, + custom_format); + } + + s << ']'; + + return true; + } + + if ((custom_format == eFormatVectorOfChar) || + (custom_format == eFormatVectorOfFloat32) || + (custom_format == eFormatVectorOfFloat64) || + (custom_format == eFormatVectorOfSInt16) || + (custom_format == eFormatVectorOfSInt32) || + (custom_format == eFormatVectorOfSInt64) || + (custom_format == eFormatVectorOfSInt8) || + (custom_format == eFormatVectorOfUInt128) || + (custom_format == eFormatVectorOfUInt16) || + (custom_format == eFormatVectorOfUInt32) || + (custom_format == eFormatVectorOfUInt64) || + (custom_format == eFormatVectorOfUInt8)) // arrays of bytes, bytes + // with ASCII or any vector + // format should be printed + // directly + { + const size_t count = GetNumChildrenIgnoringErrors(); + + Format format = FormatManager::GetSingleItemFormat(custom_format); + + s << '['; + for (size_t low = 0; low < count; low++) { + + if (low) + s << ','; + + ValueObjectSP child = GetChildAtIndex(low); + if (!child.get()) { + s << "<invalid child>"; + continue; + } + child->DumpPrintableRepresentation( + s, ValueObject::eValueObjectRepresentationStyleValue, format); + } + + s << ']'; + + return true; + } + } + + if ((custom_format == eFormatBoolean) || + (custom_format == eFormatBinary) || (custom_format == eFormatChar) || + (custom_format == eFormatCharPrintable) || + (custom_format == eFormatComplexFloat) || + (custom_format == eFormatDecimal) || (custom_format == eFormatHex) || + (custom_format == eFormatHexUppercase) || + (custom_format == eFormatFloat) || (custom_format == eFormatOctal) || + (custom_format == eFormatOSType) || + (custom_format == eFormatUnicode16) || + (custom_format == eFormatUnicode32) || + (custom_format == eFormatUnsigned) || + (custom_format == eFormatPointer) || + (custom_format == eFormatComplexInteger) || + (custom_format == eFormatComplex) || + (custom_format == eFormatDefault)) // use the [] operator + return false; + } + } + + if (only_special) + return false; + + bool var_success = false; + + { + llvm::StringRef str; + + // this is a local stream that we are using to ensure that the data pointed + // to by cstr survives long enough for us to copy it to its destination - + // it is necessary to have this temporary storage area for cases where our + // desired output is not backed by some other longer-term storage + StreamString strm; + + if (custom_format != eFormatInvalid) + SetFormat(custom_format); + + switch (val_obj_display) { + case eValueObjectRepresentationStyleValue: + str = GetValueAsCString(); + break; + + case eValueObjectRepresentationStyleSummary: + str = GetSummaryAsCString(); + break; + + case eValueObjectRepresentationStyleLanguageSpecific: { + llvm::Expected<std::string> desc = GetObjectDescription(); + if (!desc) { + strm << "error: " << toString(desc.takeError()); + str = strm.GetString(); + } else { + strm << *desc; + str = strm.GetString(); + } + } break; + + case eValueObjectRepresentationStyleLocation: + str = GetLocationAsCString(); + break; + + case eValueObjectRepresentationStyleChildrenCount: + strm.Printf("%" PRIu64 "", (uint64_t)GetNumChildrenIgnoringErrors()); + str = strm.GetString(); + break; + + case eValueObjectRepresentationStyleType: + str = GetTypeName().GetStringRef(); + break; + + case eValueObjectRepresentationStyleName: + str = GetName().GetStringRef(); + break; + + case eValueObjectRepresentationStyleExpressionPath: + GetExpressionPath(strm); + str = strm.GetString(); + break; + } + + // If the requested display style produced no output, try falling back to + // alternative presentations. + if (str.empty()) { + if (val_obj_display == eValueObjectRepresentationStyleValue) + str = GetSummaryAsCString(); + else if (val_obj_display == eValueObjectRepresentationStyleSummary) { + if (!CanProvideValue()) { + strm.Printf("%s @ %s", GetTypeName().AsCString(), + GetLocationAsCString()); + str = strm.GetString(); + } else + str = GetValueAsCString(); + } + } + + if (!str.empty()) + s << str; + else { + // We checked for errors at the start, but do it again here in case + // realizing the value for dumping produced an error. + if (m_error.Fail()) { + if (do_dump_error) + s.Printf("<%s>", m_error.AsCString()); + else + return false; + } else if (val_obj_display == eValueObjectRepresentationStyleSummary) + s.PutCString("<no summary available>"); + else if (val_obj_display == eValueObjectRepresentationStyleValue) + s.PutCString("<no value available>"); + else if (val_obj_display == + eValueObjectRepresentationStyleLanguageSpecific) + s.PutCString("<not a valid Objective-C object>"); // edit this if we + // have other runtimes + // that support a + // description + else + s.PutCString("<no printable representation>"); + } + + // we should only return false here if we could not do *anything* even if + // we have an error message as output, that's a success from our callers' + // perspective, so return true + var_success = true; + + if (custom_format != eFormatInvalid) + SetFormat(eFormatDefault); + } + + return var_success; +} + +addr_t ValueObject::GetAddressOf(bool scalar_is_load_address, + AddressType *address_type) { + // Can't take address of a bitfield + if (IsBitfield()) + return LLDB_INVALID_ADDRESS; + + if (!UpdateValueIfNeeded(false)) + return LLDB_INVALID_ADDRESS; + + switch (m_value.GetValueType()) { + case Value::ValueType::Invalid: + return LLDB_INVALID_ADDRESS; + case Value::ValueType::Scalar: + if (scalar_is_load_address) { + if (address_type) + *address_type = eAddressTypeLoad; + return m_value.GetScalar().ULongLong(LLDB_INVALID_ADDRESS); + } + break; + + case Value::ValueType::LoadAddress: + case Value::ValueType::FileAddress: { + if (address_type) + *address_type = m_value.GetValueAddressType(); + return m_value.GetScalar().ULongLong(LLDB_INVALID_ADDRESS); + } break; + case Value::ValueType::HostAddress: { + if (address_type) + *address_type = m_value.GetValueAddressType(); + return LLDB_INVALID_ADDRESS; + } break; + } + if (address_type) + *address_type = eAddressTypeInvalid; + return LLDB_INVALID_ADDRESS; +} + +addr_t ValueObject::GetPointerValue(AddressType *address_type) { + addr_t address = LLDB_INVALID_ADDRESS; + if (address_type) + *address_type = eAddressTypeInvalid; + + if (!UpdateValueIfNeeded(false)) + return address; + + switch (m_value.GetValueType()) { + case Value::ValueType::Invalid: + return LLDB_INVALID_ADDRESS; + case Value::ValueType::Scalar: + address = m_value.GetScalar().ULongLong(LLDB_INVALID_ADDRESS); + break; + + case Value::ValueType::HostAddress: + case Value::ValueType::LoadAddress: + case Value::ValueType::FileAddress: { + lldb::offset_t data_offset = 0; + address = m_data.GetAddress(&data_offset); + } break; + } + + if (address_type) + *address_type = GetAddressTypeOfChildren(); + + return address; +} + +bool ValueObject::SetValueFromCString(const char *value_str, Status &error) { + error.Clear(); + // Make sure our value is up to date first so that our location and location + // type is valid. + if (!UpdateValueIfNeeded(false)) { + error.SetErrorString("unable to read value"); + return false; + } + + uint64_t count = 0; + const Encoding encoding = GetCompilerType().GetEncoding(count); + + const size_t byte_size = GetByteSize().value_or(0); + + Value::ValueType value_type = m_value.GetValueType(); + + if (value_type == Value::ValueType::Scalar) { + // If the value is already a scalar, then let the scalar change itself: + m_value.GetScalar().SetValueFromCString(value_str, encoding, byte_size); + } else if (byte_size <= 16) { + // If the value fits in a scalar, then make a new scalar and again let the + // scalar code do the conversion, then figure out where to put the new + // value. + Scalar new_scalar; + error = new_scalar.SetValueFromCString(value_str, encoding, byte_size); + if (error.Success()) { + switch (value_type) { + case Value::ValueType::LoadAddress: { + // If it is a load address, then the scalar value is the storage + // location of the data, and we have to shove this value down to that + // load location. + ExecutionContext exe_ctx(GetExecutionContextRef()); + Process *process = exe_ctx.GetProcessPtr(); + if (process) { + addr_t target_addr = + m_value.GetScalar().ULongLong(LLDB_INVALID_ADDRESS); + size_t bytes_written = process->WriteScalarToMemory( + target_addr, new_scalar, byte_size, error); + if (!error.Success()) + return false; + if (bytes_written != byte_size) { + error.SetErrorString("unable to write value to memory"); + return false; + } + } + } break; + case Value::ValueType::HostAddress: { + // If it is a host address, then we stuff the scalar as a DataBuffer + // into the Value's data. + DataExtractor new_data; + new_data.SetByteOrder(m_data.GetByteOrder()); + + DataBufferSP buffer_sp(new DataBufferHeap(byte_size, 0)); + m_data.SetData(buffer_sp, 0); + bool success = new_scalar.GetData(new_data); + if (success) { + new_data.CopyByteOrderedData( + 0, byte_size, const_cast<uint8_t *>(m_data.GetDataStart()), + byte_size, m_data.GetByteOrder()); + } + m_value.GetScalar() = (uintptr_t)m_data.GetDataStart(); + + } break; + case Value::ValueType::Invalid: + error.SetErrorString("invalid location"); + return false; + case Value::ValueType::FileAddress: + case Value::ValueType::Scalar: + break; + } + } else { + return false; + } + } else { + // We don't support setting things bigger than a scalar at present. + error.SetErrorString("unable to write aggregate data type"); + return false; + } + + // If we have reached this point, then we have successfully changed the + // value. + SetNeedsUpdate(); + return true; +} + +bool ValueObject::GetDeclaration(Declaration &decl) { + decl.Clear(); + return false; +} + +void ValueObject::AddSyntheticChild(ConstString key, + ValueObject *valobj) { + m_synthetic_children[key] = valobj; +} + +ValueObjectSP ValueObject::GetSyntheticChild(ConstString key) const { + ValueObjectSP synthetic_child_sp; + std::map<ConstString, ValueObject *>::const_iterator pos = + m_synthetic_children.find(key); + if (pos != m_synthetic_children.end()) + synthetic_child_sp = pos->second->GetSP(); + return synthetic_child_sp; +} + +bool ValueObject::IsPossibleDynamicType() { + ExecutionContext exe_ctx(GetExecutionContextRef()); + Process *process = exe_ctx.GetProcessPtr(); + if (process) + return process->IsPossibleDynamicValue(*this); + else + return GetCompilerType().IsPossibleDynamicType(nullptr, true, true); +} + +bool ValueObject::IsRuntimeSupportValue() { + Process *process(GetProcessSP().get()); + if (!process) + return false; + + // We trust that the compiler did the right thing and marked runtime support + // values as artificial. + if (!GetVariable() || !GetVariable()->IsArtificial()) + return false; + + if (auto *runtime = process->GetLanguageRuntime(GetVariable()->GetLanguage())) + if (runtime->IsAllowedRuntimeValue(GetName())) + return false; + + return true; +} + +bool ValueObject::IsNilReference() { + if (Language *language = Language::FindPlugin(GetObjectRuntimeLanguage())) { + return language->IsNilReference(*this); + } + return false; +} + +bool ValueObject::IsUninitializedReference() { + if (Language *language = Language::FindPlugin(GetObjectRuntimeLanguage())) { + return language->IsUninitializedReference(*this); + } + return false; +} + +// This allows you to create an array member using and index that doesn't not +// fall in the normal bounds of the array. Many times structure can be defined +// as: struct Collection { +// uint32_t item_count; +// Item item_array[0]; +// }; +// The size of the "item_array" is 1, but many times in practice there are more +// items in "item_array". + +ValueObjectSP ValueObject::GetSyntheticArrayMember(size_t index, + bool can_create) { + ValueObjectSP synthetic_child_sp; + if (IsPointerType() || IsArrayType()) { + std::string index_str = llvm::formatv("[{0}]", index); + ConstString index_const_str(index_str); + // Check if we have already created a synthetic array member in this valid + // object. If we have we will re-use it. + synthetic_child_sp = GetSyntheticChild(index_const_str); + if (!synthetic_child_sp) { + ValueObject *synthetic_child; + // We haven't made a synthetic array member for INDEX yet, so lets make + // one and cache it for any future reference. + synthetic_child = CreateSyntheticArrayMember(index); + + // Cache the value if we got one back... + if (synthetic_child) { + AddSyntheticChild(index_const_str, synthetic_child); + synthetic_child_sp = synthetic_child->GetSP(); + synthetic_child_sp->SetName(ConstString(index_str)); + synthetic_child_sp->m_flags.m_is_array_item_for_pointer = true; + } + } + } + return synthetic_child_sp; +} + +ValueObjectSP ValueObject::GetSyntheticBitFieldChild(uint32_t from, uint32_t to, + bool can_create) { + ValueObjectSP synthetic_child_sp; + if (IsScalarType()) { + std::string index_str = llvm::formatv("[{0}-{1}]", from, to); + ConstString index_const_str(index_str); + // Check if we have already created a synthetic array member in this valid + // object. If we have we will re-use it. + synthetic_child_sp = GetSyntheticChild(index_const_str); + if (!synthetic_child_sp) { + uint32_t bit_field_size = to - from + 1; + uint32_t bit_field_offset = from; + if (GetDataExtractor().GetByteOrder() == eByteOrderBig) + bit_field_offset = + GetByteSize().value_or(0) * 8 - bit_field_size - bit_field_offset; + // We haven't made a synthetic array member for INDEX yet, so lets make + // one and cache it for any future reference. + ValueObjectChild *synthetic_child = new ValueObjectChild( + *this, GetCompilerType(), index_const_str, GetByteSize().value_or(0), + 0, bit_field_size, bit_field_offset, false, false, + eAddressTypeInvalid, 0); + + // Cache the value if we got one back... + if (synthetic_child) { + AddSyntheticChild(index_const_str, synthetic_child); + synthetic_child_sp = synthetic_child->GetSP(); + synthetic_child_sp->SetName(ConstString(index_str)); + synthetic_child_sp->m_flags.m_is_bitfield_for_scalar = true; + } + } + } + return synthetic_child_sp; +} + +ValueObjectSP ValueObject::GetSyntheticChildAtOffset( + uint32_t offset, const CompilerType &type, bool can_create, + ConstString name_const_str) { + + ValueObjectSP synthetic_child_sp; + + if (name_const_str.IsEmpty()) { + name_const_str.SetString("@" + std::to_string(offset)); + } + + // Check if we have already created a synthetic array member in this valid + // object. If we have we will re-use it. + synthetic_child_sp = GetSyntheticChild(name_const_str); + + if (synthetic_child_sp.get()) + return synthetic_child_sp; + + if (!can_create) + return {}; + + ExecutionContext exe_ctx(GetExecutionContextRef()); + std::optional<uint64_t> size = + type.GetByteSize(exe_ctx.GetBestExecutionContextScope()); + if (!size) + return {}; + ValueObjectChild *synthetic_child = + new ValueObjectChild(*this, type, name_const_str, *size, offset, 0, 0, + false, false, eAddressTypeInvalid, 0); + if (synthetic_child) { + AddSyntheticChild(name_const_str, synthetic_child); + synthetic_child_sp = synthetic_child->GetSP(); + synthetic_child_sp->SetName(name_const_str); + synthetic_child_sp->m_flags.m_is_child_at_offset = true; + } + return synthetic_child_sp; +} + +ValueObjectSP ValueObject::GetSyntheticBase(uint32_t offset, + const CompilerType &type, + bool can_create, + ConstString name_const_str) { + ValueObjectSP synthetic_child_sp; + + if (name_const_str.IsEmpty()) { + char name_str[128]; + snprintf(name_str, sizeof(name_str), "base%s@%i", + type.GetTypeName().AsCString("<unknown>"), offset); + name_const_str.SetCString(name_str); + } + + // Check if we have already created a synthetic array member in this valid + // object. If we have we will re-use it. + synthetic_child_sp = GetSyntheticChild(name_const_str); + + if (synthetic_child_sp.get()) + return synthetic_child_sp; + + if (!can_create) + return {}; + + const bool is_base_class = true; + + ExecutionContext exe_ctx(GetExecutionContextRef()); + std::optional<uint64_t> size = + type.GetByteSize(exe_ctx.GetBestExecutionContextScope()); + if (!size) + return {}; + ValueObjectChild *synthetic_child = + new ValueObjectChild(*this, type, name_const_str, *size, offset, 0, 0, + is_base_class, false, eAddressTypeInvalid, 0); + if (synthetic_child) { + AddSyntheticChild(name_const_str, synthetic_child); + synthetic_child_sp = synthetic_child->GetSP(); + synthetic_child_sp->SetName(name_const_str); + } + return synthetic_child_sp; +} + +// your expression path needs to have a leading . or -> (unless it somehow +// "looks like" an array, in which case it has a leading [ symbol). while the [ +// is meaningful and should be shown to the user, . and -> are just parser +// design, but by no means added information for the user.. strip them off +static const char *SkipLeadingExpressionPathSeparators(const char *expression) { + if (!expression || !expression[0]) + return expression; + if (expression[0] == '.') + return expression + 1; + if (expression[0] == '-' && expression[1] == '>') + return expression + 2; + return expression; +} + +ValueObjectSP +ValueObject::GetSyntheticExpressionPathChild(const char *expression, + bool can_create) { + ValueObjectSP synthetic_child_sp; + ConstString name_const_string(expression); + // Check if we have already created a synthetic array member in this valid + // object. If we have we will re-use it. + synthetic_child_sp = GetSyntheticChild(name_const_string); + if (!synthetic_child_sp) { + // We haven't made a synthetic array member for expression yet, so lets + // make one and cache it for any future reference. + synthetic_child_sp = GetValueForExpressionPath( + expression, nullptr, nullptr, + GetValueForExpressionPathOptions().SetSyntheticChildrenTraversal( + GetValueForExpressionPathOptions::SyntheticChildrenTraversal:: + None)); + + // Cache the value if we got one back... + if (synthetic_child_sp.get()) { + // FIXME: this causes a "real" child to end up with its name changed to + // the contents of expression + AddSyntheticChild(name_const_string, synthetic_child_sp.get()); + synthetic_child_sp->SetName( + ConstString(SkipLeadingExpressionPathSeparators(expression))); + } + } + return synthetic_child_sp; +} + +void ValueObject::CalculateSyntheticValue() { + TargetSP target_sp(GetTargetSP()); + if (target_sp && !target_sp->GetEnableSyntheticValue()) { + m_synthetic_value = nullptr; + return; + } + + lldb::SyntheticChildrenSP current_synth_sp(m_synthetic_children_sp); + + if (!UpdateFormatsIfNeeded() && m_synthetic_value) + return; + + if (m_synthetic_children_sp.get() == nullptr) + return; + + if (current_synth_sp == m_synthetic_children_sp && m_synthetic_value) + return; + + m_synthetic_value = new ValueObjectSynthetic(*this, m_synthetic_children_sp); +} + +void ValueObject::CalculateDynamicValue(DynamicValueType use_dynamic) { + if (use_dynamic == eNoDynamicValues) + return; + + if (!m_dynamic_value && !IsDynamic()) { + ExecutionContext exe_ctx(GetExecutionContextRef()); + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsPossibleDynamicValue(*this)) { + ClearDynamicTypeInformation(); + m_dynamic_value = new ValueObjectDynamicValue(*this, use_dynamic); + } + } +} + +ValueObjectSP ValueObject::GetDynamicValue(DynamicValueType use_dynamic) { + if (use_dynamic == eNoDynamicValues) + return ValueObjectSP(); + + if (!IsDynamic() && m_dynamic_value == nullptr) { + CalculateDynamicValue(use_dynamic); + } + if (m_dynamic_value && m_dynamic_value->GetError().Success()) + return m_dynamic_value->GetSP(); + else + return ValueObjectSP(); +} + +ValueObjectSP ValueObject::GetSyntheticValue() { + CalculateSyntheticValue(); + + if (m_synthetic_value) + return m_synthetic_value->GetSP(); + else + return ValueObjectSP(); +} + +bool ValueObject::HasSyntheticValue() { + UpdateFormatsIfNeeded(); + + if (m_synthetic_children_sp.get() == nullptr) + return false; + + CalculateSyntheticValue(); + + return m_synthetic_value != nullptr; +} + +ValueObject *ValueObject::GetNonBaseClassParent() { + if (GetParent()) { + if (GetParent()->IsBaseClass()) + return GetParent()->GetNonBaseClassParent(); + else + return GetParent(); + } + return nullptr; +} + +bool ValueObject::IsBaseClass(uint32_t &depth) { + if (!IsBaseClass()) { + depth = 0; + return false; + } + if (GetParent()) { + GetParent()->IsBaseClass(depth); + depth = depth + 1; + return true; + } + // TODO: a base of no parent? weird.. + depth = 1; + return true; +} + +void ValueObject::GetExpressionPath(Stream &s, + GetExpressionPathFormat epformat) { + // synthetic children do not actually "exist" as part of the hierarchy, and + // sometimes they are consed up in ways that don't make sense from an + // underlying language/API standpoint. So, use a special code path here to + // return something that can hopefully be used in expression + if (m_flags.m_is_synthetic_children_generated) { + UpdateValueIfNeeded(); + + if (m_value.GetValueType() == Value::ValueType::LoadAddress) { + if (IsPointerOrReferenceType()) { + s.Printf("((%s)0x%" PRIx64 ")", GetTypeName().AsCString("void"), + GetValueAsUnsigned(0)); + return; + } else { + uint64_t load_addr = + m_value.GetScalar().ULongLong(LLDB_INVALID_ADDRESS); + if (load_addr != LLDB_INVALID_ADDRESS) { + s.Printf("(*( (%s *)0x%" PRIx64 "))", GetTypeName().AsCString("void"), + load_addr); + return; + } + } + } + + if (CanProvideValue()) { + s.Printf("((%s)%s)", GetTypeName().AsCString("void"), + GetValueAsCString()); + return; + } + + return; + } + + const bool is_deref_of_parent = IsDereferenceOfParent(); + + if (is_deref_of_parent && + epformat == eGetExpressionPathFormatDereferencePointers) { + // this is the original format of GetExpressionPath() producing code like + // *(a_ptr).memberName, which is entirely fine, until you put this into + // StackFrame::GetValueForVariableExpressionPath() which prefers to see + // a_ptr->memberName. the eHonorPointers mode is meant to produce strings + // in this latter format + s.PutCString("*("); + } + + ValueObject *parent = GetParent(); + + if (parent) + parent->GetExpressionPath(s, epformat); + + // if we are a deref_of_parent just because we are synthetic array members + // made up to allow ptr[%d] syntax to work in variable printing, then add our + // name ([%d]) to the expression path + if (m_flags.m_is_array_item_for_pointer && + epformat == eGetExpressionPathFormatHonorPointers) + s.PutCString(m_name.GetStringRef()); + + if (!IsBaseClass()) { + if (!is_deref_of_parent) { + ValueObject *non_base_class_parent = GetNonBaseClassParent(); + if (non_base_class_parent && + !non_base_class_parent->GetName().IsEmpty()) { + CompilerType non_base_class_parent_compiler_type = + non_base_class_parent->GetCompilerType(); + if (non_base_class_parent_compiler_type) { + if (parent && parent->IsDereferenceOfParent() && + epformat == eGetExpressionPathFormatHonorPointers) { + s.PutCString("->"); + } else { + const uint32_t non_base_class_parent_type_info = + non_base_class_parent_compiler_type.GetTypeInfo(); + + if (non_base_class_parent_type_info & eTypeIsPointer) { + s.PutCString("->"); + } else if ((non_base_class_parent_type_info & eTypeHasChildren) && + !(non_base_class_parent_type_info & eTypeIsArray)) { + s.PutChar('.'); + } + } + } + } + + const char *name = GetName().GetCString(); + if (name) + s.PutCString(name); + } + } + + if (is_deref_of_parent && + epformat == eGetExpressionPathFormatDereferencePointers) { + s.PutChar(')'); + } +} + +ValueObjectSP ValueObject::GetValueForExpressionPath( + llvm::StringRef expression, ExpressionPathScanEndReason *reason_to_stop, + ExpressionPathEndResultType *final_value_type, + const GetValueForExpressionPathOptions &options, + ExpressionPathAftermath *final_task_on_target) { + + ExpressionPathScanEndReason dummy_reason_to_stop = + ValueObject::eExpressionPathScanEndReasonUnknown; + ExpressionPathEndResultType dummy_final_value_type = + ValueObject::eExpressionPathEndResultTypeInvalid; + ExpressionPathAftermath dummy_final_task_on_target = + ValueObject::eExpressionPathAftermathNothing; + + ValueObjectSP ret_val = GetValueForExpressionPath_Impl( + expression, reason_to_stop ? reason_to_stop : &dummy_reason_to_stop, + final_value_type ? final_value_type : &dummy_final_value_type, options, + final_task_on_target ? final_task_on_target + : &dummy_final_task_on_target); + + if (!final_task_on_target || + *final_task_on_target == ValueObject::eExpressionPathAftermathNothing) + return ret_val; + + if (ret_val.get() && + ((final_value_type ? *final_value_type : dummy_final_value_type) == + eExpressionPathEndResultTypePlain)) // I can only deref and takeaddress + // of plain objects + { + if ((final_task_on_target ? *final_task_on_target + : dummy_final_task_on_target) == + ValueObject::eExpressionPathAftermathDereference) { + Status error; + ValueObjectSP final_value = ret_val->Dereference(error); + if (error.Fail() || !final_value.get()) { + if (reason_to_stop) + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonDereferencingFailed; + if (final_value_type) + *final_value_type = ValueObject::eExpressionPathEndResultTypeInvalid; + return ValueObjectSP(); + } else { + if (final_task_on_target) + *final_task_on_target = ValueObject::eExpressionPathAftermathNothing; + return final_value; + } + } + if (*final_task_on_target == + ValueObject::eExpressionPathAftermathTakeAddress) { + Status error; + ValueObjectSP final_value = ret_val->AddressOf(error); + if (error.Fail() || !final_value.get()) { + if (reason_to_stop) + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonTakingAddressFailed; + if (final_value_type) + *final_value_type = ValueObject::eExpressionPathEndResultTypeInvalid; + return ValueObjectSP(); + } else { + if (final_task_on_target) + *final_task_on_target = ValueObject::eExpressionPathAftermathNothing; + return final_value; + } + } + } + return ret_val; // final_task_on_target will still have its original value, so + // you know I did not do it +} + +ValueObjectSP ValueObject::GetValueForExpressionPath_Impl( + llvm::StringRef expression, ExpressionPathScanEndReason *reason_to_stop, + ExpressionPathEndResultType *final_result, + const GetValueForExpressionPathOptions &options, + ExpressionPathAftermath *what_next) { + ValueObjectSP root = GetSP(); + + if (!root) + return nullptr; + + llvm::StringRef remainder = expression; + + while (true) { + llvm::StringRef temp_expression = remainder; + + CompilerType root_compiler_type = root->GetCompilerType(); + CompilerType pointee_compiler_type; + Flags pointee_compiler_type_info; + + Flags root_compiler_type_info( + root_compiler_type.GetTypeInfo(&pointee_compiler_type)); + if (pointee_compiler_type) + pointee_compiler_type_info.Reset(pointee_compiler_type.GetTypeInfo()); + + if (temp_expression.empty()) { + *reason_to_stop = ValueObject::eExpressionPathScanEndReasonEndOfString; + return root; + } + + switch (temp_expression.front()) { + case '-': { + temp_expression = temp_expression.drop_front(); + if (options.m_check_dot_vs_arrow_syntax && + root_compiler_type_info.Test(eTypeIsPointer)) // if you are trying to + // use -> on a + // non-pointer and I + // must catch the error + { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonArrowInsteadOfDot; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return ValueObjectSP(); + } + if (root_compiler_type_info.Test(eTypeIsObjC) && // if yo are trying to + // extract an ObjC IVar + // when this is forbidden + root_compiler_type_info.Test(eTypeIsPointer) && + options.m_no_fragile_ivar) { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonFragileIVarNotAllowed; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return ValueObjectSP(); + } + if (!temp_expression.starts_with(">")) { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonUnexpectedSymbol; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return ValueObjectSP(); + } + } + [[fallthrough]]; + case '.': // or fallthrough from -> + { + if (options.m_check_dot_vs_arrow_syntax && + temp_expression.front() == '.' && + root_compiler_type_info.Test(eTypeIsPointer)) // if you are trying to + // use . on a pointer + // and I must catch the + // error + { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonDotInsteadOfArrow; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return nullptr; + } + temp_expression = temp_expression.drop_front(); // skip . or > + + size_t next_sep_pos = temp_expression.find_first_of("-.[", 1); + if (next_sep_pos == llvm::StringRef::npos) // if no other separator just + // expand this last layer + { + llvm::StringRef child_name = temp_expression; + ValueObjectSP child_valobj_sp = + root->GetChildMemberWithName(child_name); + + if (child_valobj_sp.get()) // we know we are done, so just return + { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonEndOfString; + *final_result = ValueObject::eExpressionPathEndResultTypePlain; + return child_valobj_sp; + } else { + switch (options.m_synthetic_children_traversal) { + case GetValueForExpressionPathOptions::SyntheticChildrenTraversal:: + None: + break; + case GetValueForExpressionPathOptions::SyntheticChildrenTraversal:: + FromSynthetic: + if (root->IsSynthetic()) { + child_valobj_sp = root->GetNonSyntheticValue(); + if (child_valobj_sp.get()) + child_valobj_sp = + child_valobj_sp->GetChildMemberWithName(child_name); + } + break; + case GetValueForExpressionPathOptions::SyntheticChildrenTraversal:: + ToSynthetic: + if (!root->IsSynthetic()) { + child_valobj_sp = root->GetSyntheticValue(); + if (child_valobj_sp.get()) + child_valobj_sp = + child_valobj_sp->GetChildMemberWithName(child_name); + } + break; + case GetValueForExpressionPathOptions::SyntheticChildrenTraversal:: + Both: + if (root->IsSynthetic()) { + child_valobj_sp = root->GetNonSyntheticValue(); + if (child_valobj_sp.get()) + child_valobj_sp = + child_valobj_sp->GetChildMemberWithName(child_name); + } else { + child_valobj_sp = root->GetSyntheticValue(); + if (child_valobj_sp.get()) + child_valobj_sp = + child_valobj_sp->GetChildMemberWithName(child_name); + } + break; + } + } + + // if we are here and options.m_no_synthetic_children is true, + // child_valobj_sp is going to be a NULL SP, so we hit the "else" + // branch, and return an error + if (child_valobj_sp.get()) // if it worked, just return + { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonEndOfString; + *final_result = ValueObject::eExpressionPathEndResultTypePlain; + return child_valobj_sp; + } else { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonNoSuchChild; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return nullptr; + } + } else // other layers do expand + { + llvm::StringRef next_separator = temp_expression.substr(next_sep_pos); + llvm::StringRef child_name = temp_expression.slice(0, next_sep_pos); + + ValueObjectSP child_valobj_sp = + root->GetChildMemberWithName(child_name); + if (child_valobj_sp.get()) // store the new root and move on + { + root = child_valobj_sp; + remainder = next_separator; + *final_result = ValueObject::eExpressionPathEndResultTypePlain; + continue; + } else { + switch (options.m_synthetic_children_traversal) { + case GetValueForExpressionPathOptions::SyntheticChildrenTraversal:: + None: + break; + case GetValueForExpressionPathOptions::SyntheticChildrenTraversal:: + FromSynthetic: + if (root->IsSynthetic()) { + child_valobj_sp = root->GetNonSyntheticValue(); + if (child_valobj_sp.get()) + child_valobj_sp = + child_valobj_sp->GetChildMemberWithName(child_name); + } + break; + case GetValueForExpressionPathOptions::SyntheticChildrenTraversal:: + ToSynthetic: + if (!root->IsSynthetic()) { + child_valobj_sp = root->GetSyntheticValue(); + if (child_valobj_sp.get()) + child_valobj_sp = + child_valobj_sp->GetChildMemberWithName(child_name); + } + break; + case GetValueForExpressionPathOptions::SyntheticChildrenTraversal:: + Both: + if (root->IsSynthetic()) { + child_valobj_sp = root->GetNonSyntheticValue(); + if (child_valobj_sp.get()) + child_valobj_sp = + child_valobj_sp->GetChildMemberWithName(child_name); + } else { + child_valobj_sp = root->GetSyntheticValue(); + if (child_valobj_sp.get()) + child_valobj_sp = + child_valobj_sp->GetChildMemberWithName(child_name); + } + break; + } + } + + // if we are here and options.m_no_synthetic_children is true, + // child_valobj_sp is going to be a NULL SP, so we hit the "else" + // branch, and return an error + if (child_valobj_sp.get()) // if it worked, move on + { + root = child_valobj_sp; + remainder = next_separator; + *final_result = ValueObject::eExpressionPathEndResultTypePlain; + continue; + } else { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonNoSuchChild; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return nullptr; + } + } + break; + } + case '[': { + if (!root_compiler_type_info.Test(eTypeIsArray) && + !root_compiler_type_info.Test(eTypeIsPointer) && + !root_compiler_type_info.Test( + eTypeIsVector)) // if this is not a T[] nor a T* + { + if (!root_compiler_type_info.Test( + eTypeIsScalar)) // if this is not even a scalar... + { + if (options.m_synthetic_children_traversal == + GetValueForExpressionPathOptions::SyntheticChildrenTraversal:: + None) // ...only chance left is synthetic + { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonRangeOperatorInvalid; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return ValueObjectSP(); + } + } else if (!options.m_allow_bitfields_syntax) // if this is a scalar, + // check that we can + // expand bitfields + { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonRangeOperatorNotAllowed; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return ValueObjectSP(); + } + } + if (temp_expression[1] == + ']') // if this is an unbounded range it only works for arrays + { + if (!root_compiler_type_info.Test(eTypeIsArray)) { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonEmptyRangeNotAllowed; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return nullptr; + } else // even if something follows, we cannot expand unbounded ranges, + // just let the caller do it + { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonArrayRangeOperatorMet; + *final_result = + ValueObject::eExpressionPathEndResultTypeUnboundedRange; + return root; + } + } + + size_t close_bracket_position = temp_expression.find(']', 1); + if (close_bracket_position == + llvm::StringRef::npos) // if there is no ], this is a syntax error + { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonUnexpectedSymbol; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return nullptr; + } + + llvm::StringRef bracket_expr = + temp_expression.slice(1, close_bracket_position); + + // If this was an empty expression it would have been caught by the if + // above. + assert(!bracket_expr.empty()); + + if (!bracket_expr.contains('-')) { + // if no separator, this is of the form [N]. Note that this cannot be + // an unbounded range of the form [], because that case was handled + // above with an unconditional return. + unsigned long index = 0; + if (bracket_expr.getAsInteger(0, index)) { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonUnexpectedSymbol; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return nullptr; + } + + // from here on we do have a valid index + if (root_compiler_type_info.Test(eTypeIsArray)) { + ValueObjectSP child_valobj_sp = root->GetChildAtIndex(index); + if (!child_valobj_sp) + child_valobj_sp = root->GetSyntheticArrayMember(index, true); + if (!child_valobj_sp) + if (root->HasSyntheticValue() && + llvm::expectedToStdOptional( + root->GetSyntheticValue()->GetNumChildren()) + .value_or(0) > index) + child_valobj_sp = + root->GetSyntheticValue()->GetChildAtIndex(index); + if (child_valobj_sp) { + root = child_valobj_sp; + remainder = + temp_expression.substr(close_bracket_position + 1); // skip ] + *final_result = ValueObject::eExpressionPathEndResultTypePlain; + continue; + } else { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonNoSuchChild; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return nullptr; + } + } else if (root_compiler_type_info.Test(eTypeIsPointer)) { + if (*what_next == + ValueObject:: + eExpressionPathAftermathDereference && // if this is a + // ptr-to-scalar, I + // am accessing it + // by index and I + // would have + // deref'ed anyway, + // then do it now + // and use this as + // a bitfield + pointee_compiler_type_info.Test(eTypeIsScalar)) { + Status error; + root = root->Dereference(error); + if (error.Fail() || !root) { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonDereferencingFailed; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return nullptr; + } else { + *what_next = eExpressionPathAftermathNothing; + continue; + } + } else { + if (root->GetCompilerType().GetMinimumLanguage() == + eLanguageTypeObjC && + pointee_compiler_type_info.AllClear(eTypeIsPointer) && + root->HasSyntheticValue() && + (options.m_synthetic_children_traversal == + GetValueForExpressionPathOptions:: + SyntheticChildrenTraversal::ToSynthetic || + options.m_synthetic_children_traversal == + GetValueForExpressionPathOptions:: + SyntheticChildrenTraversal::Both)) { + root = root->GetSyntheticValue()->GetChildAtIndex(index); + } else + root = root->GetSyntheticArrayMember(index, true); + if (!root) { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonNoSuchChild; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return nullptr; + } else { + remainder = + temp_expression.substr(close_bracket_position + 1); // skip ] + *final_result = ValueObject::eExpressionPathEndResultTypePlain; + continue; + } + } + } else if (root_compiler_type_info.Test(eTypeIsScalar)) { + root = root->GetSyntheticBitFieldChild(index, index, true); + if (!root) { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonNoSuchChild; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return nullptr; + } else // we do not know how to expand members of bitfields, so we + // just return and let the caller do any further processing + { + *reason_to_stop = ValueObject:: + eExpressionPathScanEndReasonBitfieldRangeOperatorMet; + *final_result = ValueObject::eExpressionPathEndResultTypeBitfield; + return root; + } + } else if (root_compiler_type_info.Test(eTypeIsVector)) { + root = root->GetChildAtIndex(index); + if (!root) { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonNoSuchChild; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return ValueObjectSP(); + } else { + remainder = + temp_expression.substr(close_bracket_position + 1); // skip ] + *final_result = ValueObject::eExpressionPathEndResultTypePlain; + continue; + } + } else if (options.m_synthetic_children_traversal == + GetValueForExpressionPathOptions:: + SyntheticChildrenTraversal::ToSynthetic || + options.m_synthetic_children_traversal == + GetValueForExpressionPathOptions:: + SyntheticChildrenTraversal::Both) { + if (root->HasSyntheticValue()) + root = root->GetSyntheticValue(); + else if (!root->IsSynthetic()) { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonSyntheticValueMissing; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return nullptr; + } + // if we are here, then root itself is a synthetic VO.. should be + // good to go + + if (!root) { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonSyntheticValueMissing; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return nullptr; + } + root = root->GetChildAtIndex(index); + if (!root) { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonNoSuchChild; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return nullptr; + } else { + remainder = + temp_expression.substr(close_bracket_position + 1); // skip ] + *final_result = ValueObject::eExpressionPathEndResultTypePlain; + continue; + } + } else { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonNoSuchChild; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return nullptr; + } + } else { + // we have a low and a high index + llvm::StringRef sleft, sright; + unsigned long low_index, high_index; + std::tie(sleft, sright) = bracket_expr.split('-'); + if (sleft.getAsInteger(0, low_index) || + sright.getAsInteger(0, high_index)) { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonUnexpectedSymbol; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return nullptr; + } + + if (low_index > high_index) // swap indices if required + std::swap(low_index, high_index); + + if (root_compiler_type_info.Test( + eTypeIsScalar)) // expansion only works for scalars + { + root = root->GetSyntheticBitFieldChild(low_index, high_index, true); + if (!root) { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonNoSuchChild; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return nullptr; + } else { + *reason_to_stop = ValueObject:: + eExpressionPathScanEndReasonBitfieldRangeOperatorMet; + *final_result = ValueObject::eExpressionPathEndResultTypeBitfield; + return root; + } + } else if (root_compiler_type_info.Test( + eTypeIsPointer) && // if this is a ptr-to-scalar, I am + // accessing it by index and I would + // have deref'ed anyway, then do it + // now and use this as a bitfield + *what_next == + ValueObject::eExpressionPathAftermathDereference && + pointee_compiler_type_info.Test(eTypeIsScalar)) { + Status error; + root = root->Dereference(error); + if (error.Fail() || !root) { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonDereferencingFailed; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return nullptr; + } else { + *what_next = ValueObject::eExpressionPathAftermathNothing; + continue; + } + } else { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonArrayRangeOperatorMet; + *final_result = ValueObject::eExpressionPathEndResultTypeBoundedRange; + return root; + } + } + break; + } + default: // some non-separator is in the way + { + *reason_to_stop = + ValueObject::eExpressionPathScanEndReasonUnexpectedSymbol; + *final_result = ValueObject::eExpressionPathEndResultTypeInvalid; + return nullptr; + } + } + } +} + +llvm::Error ValueObject::Dump(Stream &s) { + return Dump(s, DumpValueObjectOptions(*this)); +} + +llvm::Error ValueObject::Dump(Stream &s, + const DumpValueObjectOptions &options) { + ValueObjectPrinter printer(*this, &s, options); + return printer.PrintValueObject(); +} + +ValueObjectSP ValueObject::CreateConstantValue(ConstString name) { + ValueObjectSP valobj_sp; + + if (UpdateValueIfNeeded(false) && m_error.Success()) { + ExecutionContext exe_ctx(GetExecutionContextRef()); + + DataExtractor data; + data.SetByteOrder(m_data.GetByteOrder()); + data.SetAddressByteSize(m_data.GetAddressByteSize()); + + if (IsBitfield()) { + Value v(Scalar(GetValueAsUnsigned(UINT64_MAX))); + m_error = v.GetValueAsData(&exe_ctx, data, GetModule().get()); + } else + m_error = m_value.GetValueAsData(&exe_ctx, data, GetModule().get()); + + valobj_sp = ValueObjectConstResult::Create( + exe_ctx.GetBestExecutionContextScope(), GetCompilerType(), name, data, + GetAddressOf()); + } + + if (!valobj_sp) { + ExecutionContext exe_ctx(GetExecutionContextRef()); + valobj_sp = ValueObjectConstResult::Create( + exe_ctx.GetBestExecutionContextScope(), m_error); + } + return valobj_sp; +} + +ValueObjectSP ValueObject::GetQualifiedRepresentationIfAvailable( + lldb::DynamicValueType dynValue, bool synthValue) { + ValueObjectSP result_sp; + switch (dynValue) { + case lldb::eDynamicCanRunTarget: + case lldb::eDynamicDontRunTarget: { + if (!IsDynamic()) + result_sp = GetDynamicValue(dynValue); + } break; + case lldb::eNoDynamicValues: { + if (IsDynamic()) + result_sp = GetStaticValue(); + } break; + } + if (!result_sp) + result_sp = GetSP(); + assert(result_sp); + + bool is_synthetic = result_sp->IsSynthetic(); + if (synthValue && !is_synthetic) { + if (auto synth_sp = result_sp->GetSyntheticValue()) + return synth_sp; + } + if (!synthValue && is_synthetic) { + if (auto non_synth_sp = result_sp->GetNonSyntheticValue()) + return non_synth_sp; + } + + return result_sp; +} + +ValueObjectSP ValueObject::Dereference(Status &error) { + if (m_deref_valobj) + return m_deref_valobj->GetSP(); + + const bool is_pointer_or_reference_type = IsPointerOrReferenceType(); + if (is_pointer_or_reference_type) { + bool omit_empty_base_classes = true; + bool ignore_array_bounds = false; + + std::string child_name_str; + uint32_t child_byte_size = 0; + int32_t child_byte_offset = 0; + uint32_t child_bitfield_bit_size = 0; + uint32_t child_bitfield_bit_offset = 0; + bool child_is_base_class = false; + bool child_is_deref_of_parent = false; + const bool transparent_pointers = false; + CompilerType compiler_type = GetCompilerType(); + uint64_t language_flags = 0; + + ExecutionContext exe_ctx(GetExecutionContextRef()); + + CompilerType child_compiler_type; + auto child_compiler_type_or_err = compiler_type.GetChildCompilerTypeAtIndex( + &exe_ctx, 0, transparent_pointers, omit_empty_base_classes, + ignore_array_bounds, child_name_str, child_byte_size, child_byte_offset, + child_bitfield_bit_size, child_bitfield_bit_offset, child_is_base_class, + child_is_deref_of_parent, this, language_flags); + if (!child_compiler_type_or_err) + LLDB_LOG_ERROR(GetLog(LLDBLog::Types), + child_compiler_type_or_err.takeError(), + "could not find child: {0}"); + else + child_compiler_type = *child_compiler_type_or_err; + + if (child_compiler_type && child_byte_size) { + ConstString child_name; + if (!child_name_str.empty()) + child_name.SetCString(child_name_str.c_str()); + + m_deref_valobj = new ValueObjectChild( + *this, child_compiler_type, child_name, child_byte_size, + child_byte_offset, child_bitfield_bit_size, child_bitfield_bit_offset, + child_is_base_class, child_is_deref_of_parent, eAddressTypeInvalid, + language_flags); + } + + // In case of incomplete child compiler type, use the pointee type and try + // to recreate a new ValueObjectChild using it. + if (!m_deref_valobj) { + // FIXME(#59012): C++ stdlib formatters break with incomplete types (e.g. + // `std::vector<int> &`). Remove ObjC restriction once that's resolved. + if (Language::LanguageIsObjC(GetPreferredDisplayLanguage()) && + HasSyntheticValue()) { + child_compiler_type = compiler_type.GetPointeeType(); + + if (child_compiler_type) { + ConstString child_name; + if (!child_name_str.empty()) + child_name.SetCString(child_name_str.c_str()); + + m_deref_valobj = new ValueObjectChild( + *this, child_compiler_type, child_name, child_byte_size, + child_byte_offset, child_bitfield_bit_size, + child_bitfield_bit_offset, child_is_base_class, + child_is_deref_of_parent, eAddressTypeInvalid, language_flags); + } + } + } + + } else if (HasSyntheticValue()) { + m_deref_valobj = + GetSyntheticValue()->GetChildMemberWithName("$$dereference$$").get(); + } else if (IsSynthetic()) { + m_deref_valobj = GetChildMemberWithName("$$dereference$$").get(); + } + + if (m_deref_valobj) { + error.Clear(); + return m_deref_valobj->GetSP(); + } else { + StreamString strm; + GetExpressionPath(strm); + + if (is_pointer_or_reference_type) + error.SetErrorStringWithFormat("dereference failed: (%s) %s", + GetTypeName().AsCString("<invalid type>"), + strm.GetData()); + else + error.SetErrorStringWithFormat("not a pointer or reference type: (%s) %s", + GetTypeName().AsCString("<invalid type>"), + strm.GetData()); + return ValueObjectSP(); + } +} + +ValueObjectSP ValueObject::AddressOf(Status &error) { + if (m_addr_of_valobj_sp) + return m_addr_of_valobj_sp; + + AddressType address_type = eAddressTypeInvalid; + const bool scalar_is_load_address = false; + addr_t addr = GetAddressOf(scalar_is_load_address, &address_type); + error.Clear(); + if (addr != LLDB_INVALID_ADDRESS && address_type != eAddressTypeHost) { + switch (address_type) { + case eAddressTypeInvalid: { + StreamString expr_path_strm; + GetExpressionPath(expr_path_strm); + error.SetErrorStringWithFormat("'%s' is not in memory", + expr_path_strm.GetData()); + } break; + + case eAddressTypeFile: + case eAddressTypeLoad: { + CompilerType compiler_type = GetCompilerType(); + if (compiler_type) { + std::string name(1, '&'); + name.append(m_name.AsCString("")); + ExecutionContext exe_ctx(GetExecutionContextRef()); + m_addr_of_valobj_sp = ValueObjectConstResult::Create( + exe_ctx.GetBestExecutionContextScope(), + compiler_type.GetPointerType(), ConstString(name.c_str()), addr, + eAddressTypeInvalid, m_data.GetAddressByteSize()); + } + } break; + default: + break; + } + } else { + StreamString expr_path_strm; + GetExpressionPath(expr_path_strm); + error.SetErrorStringWithFormat("'%s' doesn't have a valid address", + expr_path_strm.GetData()); + } + + return m_addr_of_valobj_sp; +} + +ValueObjectSP ValueObject::DoCast(const CompilerType &compiler_type) { + return ValueObjectCast::Create(*this, GetName(), compiler_type); +} + +ValueObjectSP ValueObject::Cast(const CompilerType &compiler_type) { + // Only allow casts if the original type is equal or larger than the cast + // type, unless we know this is a load address. Getting the size wrong for + // a host side storage could leak lldb memory, so we absolutely want to + // prevent that. We may not always get the right value, for instance if we + // have an expression result value that's copied into a storage location in + // the target may not have copied enough memory. I'm not trying to fix that + // here, I'm just making Cast from a smaller to a larger possible in all the + // cases where that doesn't risk making a Value out of random lldb memory. + // You have to check the ValueObject's Value for the address types, since + // ValueObjects that use live addresses will tell you they fetch data from the + // live address, but once they are made, they actually don't. + // FIXME: Can we make ValueObject's with a live address fetch "more data" from + // the live address if it is still valid? + + Status error; + CompilerType my_type = GetCompilerType(); + + ExecutionContextScope *exe_scope + = ExecutionContext(GetExecutionContextRef()) + .GetBestExecutionContextScope(); + if (compiler_type.GetByteSize(exe_scope) + <= GetCompilerType().GetByteSize(exe_scope) + || m_value.GetValueType() == Value::ValueType::LoadAddress) + return DoCast(compiler_type); + + error.SetErrorString("Can only cast to a type that is equal to or smaller " + "than the orignal type."); + + return ValueObjectConstResult::Create( + ExecutionContext(GetExecutionContextRef()).GetBestExecutionContextScope(), + error); +} + +lldb::ValueObjectSP ValueObject::Clone(ConstString new_name) { + return ValueObjectCast::Create(*this, new_name, GetCompilerType()); +} + +ValueObjectSP ValueObject::CastPointerType(const char *name, + CompilerType &compiler_type) { + ValueObjectSP valobj_sp; + AddressType address_type; + addr_t ptr_value = GetPointerValue(&address_type); + + if (ptr_value != LLDB_INVALID_ADDRESS) { + Address ptr_addr(ptr_value); + ExecutionContext exe_ctx(GetExecutionContextRef()); + valobj_sp = ValueObjectMemory::Create( + exe_ctx.GetBestExecutionContextScope(), name, ptr_addr, compiler_type); + } + return valobj_sp; +} + +ValueObjectSP ValueObject::CastPointerType(const char *name, TypeSP &type_sp) { + ValueObjectSP valobj_sp; + AddressType address_type; + addr_t ptr_value = GetPointerValue(&address_type); + + if (ptr_value != LLDB_INVALID_ADDRESS) { + Address ptr_addr(ptr_value); + ExecutionContext exe_ctx(GetExecutionContextRef()); + valobj_sp = ValueObjectMemory::Create( + exe_ctx.GetBestExecutionContextScope(), name, ptr_addr, type_sp); + } + return valobj_sp; +} + +lldb::addr_t ValueObject::GetLoadAddress() { + lldb::addr_t addr_value = LLDB_INVALID_ADDRESS; + if (auto target_sp = GetTargetSP()) { + const bool scalar_is_load_address = true; + AddressType addr_type; + addr_value = GetAddressOf(scalar_is_load_address, &addr_type); + if (addr_type == eAddressTypeFile) { + lldb::ModuleSP module_sp(GetModule()); + if (!module_sp) + addr_value = LLDB_INVALID_ADDRESS; + else { + Address tmp_addr; + module_sp->ResolveFileAddress(addr_value, tmp_addr); + addr_value = tmp_addr.GetLoadAddress(target_sp.get()); + } + } else if (addr_type == eAddressTypeHost || + addr_type == eAddressTypeInvalid) + addr_value = LLDB_INVALID_ADDRESS; + } + return addr_value; +} + +llvm::Expected<lldb::ValueObjectSP> ValueObject::CastDerivedToBaseType( + CompilerType type, const llvm::ArrayRef<uint32_t> &base_type_indices) { + // Make sure the starting type and the target type are both valid for this + // type of cast; otherwise return the shared pointer to the original + // (unchanged) ValueObject. + if (!type.IsPointerType() && !type.IsReferenceType()) + return llvm::make_error<llvm::StringError>( + "Invalid target type: should be a pointer or a reference", + llvm::inconvertibleErrorCode()); + + CompilerType start_type = GetCompilerType(); + if (start_type.IsReferenceType()) + start_type = start_type.GetNonReferenceType(); + + auto target_record_type = + type.IsPointerType() ? type.GetPointeeType() : type.GetNonReferenceType(); + auto start_record_type = + start_type.IsPointerType() ? start_type.GetPointeeType() : start_type; + + if (!target_record_type.IsRecordType() || !start_record_type.IsRecordType()) + return llvm::make_error<llvm::StringError>( + "Underlying start & target types should be record types", + llvm::inconvertibleErrorCode()); + + if (target_record_type.CompareTypes(start_record_type)) + return llvm::make_error<llvm::StringError>( + "Underlying start & target types should be different", + llvm::inconvertibleErrorCode()); + + if (base_type_indices.empty()) + return llvm::make_error<llvm::StringError>( + "Children sequence must be non-empty", llvm::inconvertibleErrorCode()); + + // Both the starting & target types are valid for the cast, and the list of + // base class indices is non-empty, so we can proceed with the cast. + + lldb::TargetSP target = GetTargetSP(); + // The `value` can be a pointer, but GetChildAtIndex works for pointers too. + lldb::ValueObjectSP inner_value = GetSP(); + + for (const uint32_t i : base_type_indices) + // Create synthetic value if needed. + inner_value = + inner_value->GetChildAtIndex(i, /*can_create_synthetic*/ true); + + // At this point type of `inner_value` should be the dereferenced target + // type. + CompilerType inner_value_type = inner_value->GetCompilerType(); + if (type.IsPointerType()) { + if (!inner_value_type.CompareTypes(type.GetPointeeType())) + return llvm::make_error<llvm::StringError>( + "casted value doesn't match the desired type", + llvm::inconvertibleErrorCode()); + + uintptr_t addr = inner_value->GetLoadAddress(); + llvm::StringRef name = ""; + ExecutionContext exe_ctx(target.get(), false); + return ValueObject::CreateValueObjectFromAddress(name, addr, exe_ctx, type, + /* do deref */ false); + } + + // At this point the target type should be a reference. + if (!inner_value_type.CompareTypes(type.GetNonReferenceType())) + return llvm::make_error<llvm::StringError>( + "casted value doesn't match the desired type", + llvm::inconvertibleErrorCode()); + + return lldb::ValueObjectSP(inner_value->Cast(type.GetNonReferenceType())); +} + +llvm::Expected<lldb::ValueObjectSP> +ValueObject::CastBaseToDerivedType(CompilerType type, uint64_t offset) { + // Make sure the starting type and the target type are both valid for this + // type of cast; otherwise return the shared pointer to the original + // (unchanged) ValueObject. + if (!type.IsPointerType() && !type.IsReferenceType()) + return llvm::make_error<llvm::StringError>( + "Invalid target type: should be a pointer or a reference", + llvm::inconvertibleErrorCode()); + + CompilerType start_type = GetCompilerType(); + if (start_type.IsReferenceType()) + start_type = start_type.GetNonReferenceType(); + + auto target_record_type = + type.IsPointerType() ? type.GetPointeeType() : type.GetNonReferenceType(); + auto start_record_type = + start_type.IsPointerType() ? start_type.GetPointeeType() : start_type; + + if (!target_record_type.IsRecordType() || !start_record_type.IsRecordType()) + return llvm::make_error<llvm::StringError>( + "Underlying start & target types should be record types", + llvm::inconvertibleErrorCode()); + + if (target_record_type.CompareTypes(start_record_type)) + return llvm::make_error<llvm::StringError>( + "Underlying start & target types should be different", + llvm::inconvertibleErrorCode()); + + CompilerType virtual_base; + if (target_record_type.IsVirtualBase(start_record_type, &virtual_base)) { + if (!virtual_base.IsValid()) + return llvm::make_error<llvm::StringError>( + "virtual base should be valid", llvm::inconvertibleErrorCode()); + return llvm::make_error<llvm::StringError>( + llvm::Twine("cannot cast " + start_type.TypeDescription() + " to " + + type.TypeDescription() + " via virtual base " + + virtual_base.TypeDescription()), + llvm::inconvertibleErrorCode()); + } + + // Both the starting & target types are valid for the cast, so we can + // proceed with the cast. + + lldb::TargetSP target = GetTargetSP(); + auto pointer_type = + type.IsPointerType() ? type : type.GetNonReferenceType().GetPointerType(); + + uintptr_t addr = + type.IsPointerType() ? GetValueAsUnsigned(0) : GetLoadAddress(); + + llvm::StringRef name = ""; + ExecutionContext exe_ctx(target.get(), false); + lldb::ValueObjectSP value = ValueObject::CreateValueObjectFromAddress( + name, addr - offset, exe_ctx, pointer_type, /* do_deref */ false); + + if (type.IsPointerType()) + return value; + + // At this point the target type is a reference. Since `value` is a pointer, + // it has to be dereferenced. + Status error; + return value->Dereference(error); +} + +lldb::ValueObjectSP ValueObject::CastToBasicType(CompilerType type) { + bool is_scalar = GetCompilerType().IsScalarType(); + bool is_enum = GetCompilerType().IsEnumerationType(); + bool is_pointer = + GetCompilerType().IsPointerType() || GetCompilerType().IsNullPtrType(); + bool is_float = GetCompilerType().IsFloat(); + bool is_integer = GetCompilerType().IsInteger(); + + if (!type.IsScalarType()) { + m_error.SetErrorString("target type must be a scalar"); + return GetSP(); + } + + if (!is_scalar && !is_enum && !is_pointer) { + m_error.SetErrorString("argument must be a scalar, enum, or pointer"); + return GetSP(); + } + + lldb::TargetSP target = GetTargetSP(); + uint64_t type_byte_size = 0; + uint64_t val_byte_size = 0; + if (auto temp = type.GetByteSize(target.get())) + type_byte_size = temp.value(); + if (auto temp = GetCompilerType().GetByteSize(target.get())) + val_byte_size = temp.value(); + + if (is_pointer) { + if (!type.IsInteger() && !type.IsBoolean()) { + m_error.SetErrorString("target type must be an integer or boolean"); + return GetSP(); + } + if (!type.IsBoolean() && type_byte_size < val_byte_size) { + m_error.SetErrorString( + "target type cannot be smaller than the pointer type"); + return GetSP(); + } + } + + if (type.IsBoolean()) { + if (!is_scalar || is_integer) + return ValueObject::CreateValueObjectFromBool( + target, GetValueAsUnsigned(0) != 0, "result"); + else if (is_scalar && is_float) { + auto float_value_or_err = GetValueAsAPFloat(); + if (float_value_or_err) + return ValueObject::CreateValueObjectFromBool( + target, !float_value_or_err->isZero(), "result"); + else { + m_error.SetErrorStringWithFormat( + "cannot get value as APFloat: %s", + llvm::toString(float_value_or_err.takeError()).c_str()); + return GetSP(); + } + } + } + + if (type.IsInteger()) { + if (!is_scalar || is_integer) { + auto int_value_or_err = GetValueAsAPSInt(); + if (int_value_or_err) { + // Get the value as APSInt and extend or truncate it to the requested + // size. + llvm::APSInt ext = + int_value_or_err->extOrTrunc(type_byte_size * CHAR_BIT); + return ValueObject::CreateValueObjectFromAPInt(target, ext, type, + "result"); + } else { + m_error.SetErrorStringWithFormat( + "cannot get value as APSInt: %s", + llvm::toString(int_value_or_err.takeError()).c_str()); + ; + return GetSP(); + } + } else if (is_scalar && is_float) { + llvm::APSInt integer(type_byte_size * CHAR_BIT, !type.IsSigned()); + bool is_exact; + auto float_value_or_err = GetValueAsAPFloat(); + if (float_value_or_err) { + llvm::APFloatBase::opStatus status = + float_value_or_err->convertToInteger( + integer, llvm::APFloat::rmTowardZero, &is_exact); + + // Casting floating point values that are out of bounds of the target + // type is undefined behaviour. + if (status & llvm::APFloatBase::opInvalidOp) { + m_error.SetErrorStringWithFormat( + "invalid type cast detected: %s", + llvm::toString(float_value_or_err.takeError()).c_str()); + return GetSP(); + } + return ValueObject::CreateValueObjectFromAPInt(target, integer, type, + "result"); + } + } + } + + if (type.IsFloat()) { + if (!is_scalar) { + auto int_value_or_err = GetValueAsAPSInt(); + if (int_value_or_err) { + llvm::APSInt ext = + int_value_or_err->extOrTrunc(type_byte_size * CHAR_BIT); + Scalar scalar_int(ext); + llvm::APFloat f = scalar_int.CreateAPFloatFromAPSInt( + type.GetCanonicalType().GetBasicTypeEnumeration()); + return ValueObject::CreateValueObjectFromAPFloat(target, f, type, + "result"); + } else { + m_error.SetErrorStringWithFormat( + "cannot get value as APSInt: %s", + llvm::toString(int_value_or_err.takeError()).c_str()); + return GetSP(); + } + } else { + if (is_integer) { + auto int_value_or_err = GetValueAsAPSInt(); + if (int_value_or_err) { + Scalar scalar_int(*int_value_or_err); + llvm::APFloat f = scalar_int.CreateAPFloatFromAPSInt( + type.GetCanonicalType().GetBasicTypeEnumeration()); + return ValueObject::CreateValueObjectFromAPFloat(target, f, type, + "result"); + } else { + m_error.SetErrorStringWithFormat( + "cannot get value as APSInt: %s", + llvm::toString(int_value_or_err.takeError()).c_str()); + return GetSP(); + } + } + if (is_float) { + auto float_value_or_err = GetValueAsAPFloat(); + if (float_value_or_err) { + Scalar scalar_float(*float_value_or_err); + llvm::APFloat f = scalar_float.CreateAPFloatFromAPFloat( + type.GetCanonicalType().GetBasicTypeEnumeration()); + return ValueObject::CreateValueObjectFromAPFloat(target, f, type, + "result"); + } else { + m_error.SetErrorStringWithFormat( + "cannot get value as APFloat: %s", + llvm::toString(float_value_or_err.takeError()).c_str()); + return GetSP(); + } + } + } + } + + m_error.SetErrorString("Unable to perform requested cast"); + return GetSP(); +} + +lldb::ValueObjectSP ValueObject::CastToEnumType(CompilerType type) { + bool is_enum = GetCompilerType().IsEnumerationType(); + bool is_integer = GetCompilerType().IsInteger(); + bool is_float = GetCompilerType().IsFloat(); + + if (!is_enum && !is_integer && !is_float) { + m_error.SetErrorString("argument must be an integer, a float, or an enum"); + return GetSP(); + } + + if (!type.IsEnumerationType()) { + m_error.SetErrorString("target type must be an enum"); + return GetSP(); + } + + lldb::TargetSP target = GetTargetSP(); + uint64_t byte_size = 0; + if (auto temp = type.GetByteSize(target.get())) + byte_size = temp.value(); + + if (is_float) { + llvm::APSInt integer(byte_size * CHAR_BIT, !type.IsSigned()); + bool is_exact; + auto value_or_err = GetValueAsAPFloat(); + if (value_or_err) { + llvm::APFloatBase::opStatus status = value_or_err->convertToInteger( + integer, llvm::APFloat::rmTowardZero, &is_exact); + + // Casting floating point values that are out of bounds of the target + // type is undefined behaviour. + if (status & llvm::APFloatBase::opInvalidOp) { + m_error.SetErrorStringWithFormat( + "invalid type cast detected: %s", + llvm::toString(value_or_err.takeError()).c_str()); + return GetSP(); + } + return ValueObject::CreateValueObjectFromAPInt(target, integer, type, + "result"); + } else { + m_error.SetErrorString("cannot get value as APFloat"); + return GetSP(); + } + } else { + // Get the value as APSInt and extend or truncate it to the requested size. + auto value_or_err = GetValueAsAPSInt(); + if (value_or_err) { + llvm::APSInt ext = value_or_err->extOrTrunc(byte_size * CHAR_BIT); + return ValueObject::CreateValueObjectFromAPInt(target, ext, type, + "result"); + } else { + m_error.SetErrorStringWithFormat( + "cannot get value as APSInt: %s", + llvm::toString(value_or_err.takeError()).c_str()); + return GetSP(); + } + } + m_error.SetErrorString("Cannot perform requested cast"); + return GetSP(); +} + +ValueObject::EvaluationPoint::EvaluationPoint() : m_mod_id(), m_exe_ctx_ref() {} + +ValueObject::EvaluationPoint::EvaluationPoint(ExecutionContextScope *exe_scope, + bool use_selected) + : m_mod_id(), m_exe_ctx_ref() { + ExecutionContext exe_ctx(exe_scope); + TargetSP target_sp(exe_ctx.GetTargetSP()); + if (target_sp) { + m_exe_ctx_ref.SetTargetSP(target_sp); + ProcessSP process_sp(exe_ctx.GetProcessSP()); + if (!process_sp) + process_sp = target_sp->GetProcessSP(); + + if (process_sp) { + m_mod_id = process_sp->GetModID(); + m_exe_ctx_ref.SetProcessSP(process_sp); + + ThreadSP thread_sp(exe_ctx.GetThreadSP()); + + if (!thread_sp) { + if (use_selected) + thread_sp = process_sp->GetThreadList().GetSelectedThread(); + } + + if (thread_sp) { + m_exe_ctx_ref.SetThreadSP(thread_sp); + + StackFrameSP frame_sp(exe_ctx.GetFrameSP()); + if (!frame_sp) { + if (use_selected) + frame_sp = thread_sp->GetSelectedFrame(DoNoSelectMostRelevantFrame); + } + if (frame_sp) + m_exe_ctx_ref.SetFrameSP(frame_sp); + } + } + } +} + +ValueObject::EvaluationPoint::EvaluationPoint( + const ValueObject::EvaluationPoint &rhs) + : m_mod_id(), m_exe_ctx_ref(rhs.m_exe_ctx_ref) {} + +ValueObject::EvaluationPoint::~EvaluationPoint() = default; + +// This function checks the EvaluationPoint against the current process state. +// If the current state matches the evaluation point, or the evaluation point +// is already invalid, then we return false, meaning "no change". If the +// current state is different, we update our state, and return true meaning +// "yes, change". If we did see a change, we also set m_needs_update to true, +// so future calls to NeedsUpdate will return true. exe_scope will be set to +// the current execution context scope. + +bool ValueObject::EvaluationPoint::SyncWithProcessState( + bool accept_invalid_exe_ctx) { + // Start with the target, if it is NULL, then we're obviously not going to + // get any further: + const bool thread_and_frame_only_if_stopped = true; + ExecutionContext exe_ctx( + m_exe_ctx_ref.Lock(thread_and_frame_only_if_stopped)); + + if (exe_ctx.GetTargetPtr() == nullptr) + return false; + + // If we don't have a process nothing can change. + Process *process = exe_ctx.GetProcessPtr(); + if (process == nullptr) + return false; + + // If our stop id is the current stop ID, nothing has changed: + ProcessModID current_mod_id = process->GetModID(); + + // If the current stop id is 0, either we haven't run yet, or the process + // state has been cleared. In either case, we aren't going to be able to sync + // with the process state. + if (current_mod_id.GetStopID() == 0) + return false; + + bool changed = false; + const bool was_valid = m_mod_id.IsValid(); + if (was_valid) { + if (m_mod_id == current_mod_id) { + // Everything is already up to date in this object, no need to update the + // execution context scope. + changed = false; + } else { + m_mod_id = current_mod_id; + m_needs_update = true; + changed = true; + } + } + + // Now re-look up the thread and frame in case the underlying objects have + // gone away & been recreated. That way we'll be sure to return a valid + // exe_scope. If we used to have a thread or a frame but can't find it + // anymore, then mark ourselves as invalid. + + if (!accept_invalid_exe_ctx) { + if (m_exe_ctx_ref.HasThreadRef()) { + ThreadSP thread_sp(m_exe_ctx_ref.GetThreadSP()); + if (thread_sp) { + if (m_exe_ctx_ref.HasFrameRef()) { + StackFrameSP frame_sp(m_exe_ctx_ref.GetFrameSP()); + if (!frame_sp) { + // We used to have a frame, but now it is gone + SetInvalid(); + changed = was_valid; + } + } + } else { + // We used to have a thread, but now it is gone + SetInvalid(); + changed = was_valid; + } + } + } + + return changed; +} + +void ValueObject::EvaluationPoint::SetUpdated() { + ProcessSP process_sp(m_exe_ctx_ref.GetProcessSP()); + if (process_sp) + m_mod_id = process_sp->GetModID(); + m_needs_update = false; +} + +void ValueObject::ClearUserVisibleData(uint32_t clear_mask) { + if ((clear_mask & eClearUserVisibleDataItemsValue) == + eClearUserVisibleDataItemsValue) + m_value_str.clear(); + + if ((clear_mask & eClearUserVisibleDataItemsLocation) == + eClearUserVisibleDataItemsLocation) + m_location_str.clear(); + + if ((clear_mask & eClearUserVisibleDataItemsSummary) == + eClearUserVisibleDataItemsSummary) + m_summary_str.clear(); + + if ((clear_mask & eClearUserVisibleDataItemsDescription) == + eClearUserVisibleDataItemsDescription) + m_object_desc_str.clear(); + + if ((clear_mask & eClearUserVisibleDataItemsSyntheticChildren) == + eClearUserVisibleDataItemsSyntheticChildren) { + if (m_synthetic_value) + m_synthetic_value = nullptr; + } +} + +SymbolContextScope *ValueObject::GetSymbolContextScope() { + if (m_parent) { + if (!m_parent->IsPointerOrReferenceType()) + return m_parent->GetSymbolContextScope(); + } + return nullptr; +} + +lldb::ValueObjectSP +ValueObject::CreateValueObjectFromExpression(llvm::StringRef name, + llvm::StringRef expression, + const ExecutionContext &exe_ctx) { + return CreateValueObjectFromExpression(name, expression, exe_ctx, + EvaluateExpressionOptions()); +} + +lldb::ValueObjectSP ValueObject::CreateValueObjectFromExpression( + llvm::StringRef name, llvm::StringRef expression, + const ExecutionContext &exe_ctx, const EvaluateExpressionOptions &options) { + lldb::ValueObjectSP retval_sp; + lldb::TargetSP target_sp(exe_ctx.GetTargetSP()); + if (!target_sp) + return retval_sp; + if (expression.empty()) + return retval_sp; + target_sp->EvaluateExpression(expression, exe_ctx.GetFrameSP().get(), + retval_sp, options); + if (retval_sp && !name.empty()) + retval_sp->SetName(ConstString(name)); + return retval_sp; +} + +lldb::ValueObjectSP ValueObject::CreateValueObjectFromAddress( + llvm::StringRef name, uint64_t address, const ExecutionContext &exe_ctx, + CompilerType type, bool do_deref) { + if (type) { + CompilerType pointer_type(type.GetPointerType()); + if (!do_deref) + pointer_type = type; + if (pointer_type) { + lldb::DataBufferSP buffer( + new lldb_private::DataBufferHeap(&address, sizeof(lldb::addr_t))); + lldb::ValueObjectSP ptr_result_valobj_sp(ValueObjectConstResult::Create( + exe_ctx.GetBestExecutionContextScope(), pointer_type, + ConstString(name), buffer, exe_ctx.GetByteOrder(), + exe_ctx.GetAddressByteSize())); + if (ptr_result_valobj_sp) { + if (do_deref) + ptr_result_valobj_sp->GetValue().SetValueType( + Value::ValueType::LoadAddress); + Status err; + if (do_deref) + ptr_result_valobj_sp = ptr_result_valobj_sp->Dereference(err); + if (ptr_result_valobj_sp && !name.empty()) + ptr_result_valobj_sp->SetName(ConstString(name)); + } + return ptr_result_valobj_sp; + } + } + return lldb::ValueObjectSP(); +} + +lldb::ValueObjectSP ValueObject::CreateValueObjectFromData( + llvm::StringRef name, const DataExtractor &data, + const ExecutionContext &exe_ctx, CompilerType type) { + lldb::ValueObjectSP new_value_sp; + new_value_sp = ValueObjectConstResult::Create( + exe_ctx.GetBestExecutionContextScope(), type, ConstString(name), data, + LLDB_INVALID_ADDRESS); + new_value_sp->SetAddressTypeOfChildren(eAddressTypeLoad); + if (new_value_sp && !name.empty()) + new_value_sp->SetName(ConstString(name)); + return new_value_sp; +} + +lldb::ValueObjectSP +ValueObject::CreateValueObjectFromAPInt(lldb::TargetSP target, + const llvm::APInt &v, CompilerType type, + llvm::StringRef name) { + ExecutionContext exe_ctx(target.get(), false); + uint64_t byte_size = 0; + if (auto temp = type.GetByteSize(target.get())) + byte_size = temp.value(); + lldb::DataExtractorSP data_sp = std::make_shared<DataExtractor>( + reinterpret_cast<const void *>(v.getRawData()), byte_size, + exe_ctx.GetByteOrder(), exe_ctx.GetAddressByteSize()); + return ValueObject::CreateValueObjectFromData(name, *data_sp, exe_ctx, type); +} + +lldb::ValueObjectSP ValueObject::CreateValueObjectFromAPFloat( + lldb::TargetSP target, const llvm::APFloat &v, CompilerType type, + llvm::StringRef name) { + return CreateValueObjectFromAPInt(target, v.bitcastToAPInt(), type, name); +} + +lldb::ValueObjectSP +ValueObject::CreateValueObjectFromBool(lldb::TargetSP target, bool value, + llvm::StringRef name) { + CompilerType target_type; + if (target) { + for (auto type_system_sp : target->GetScratchTypeSystems()) + if (auto compiler_type = + type_system_sp->GetBasicTypeFromAST(lldb::eBasicTypeBool)) { + target_type = compiler_type; + break; + } + } + ExecutionContext exe_ctx(target.get(), false); + uint64_t byte_size = 0; + if (auto temp = target_type.GetByteSize(target.get())) + byte_size = temp.value(); + lldb::DataExtractorSP data_sp = std::make_shared<DataExtractor>( + reinterpret_cast<const void *>(&value), byte_size, exe_ctx.GetByteOrder(), + exe_ctx.GetAddressByteSize()); + return ValueObject::CreateValueObjectFromData(name, *data_sp, exe_ctx, + target_type); +} + +lldb::ValueObjectSP ValueObject::CreateValueObjectFromNullptr( + lldb::TargetSP target, CompilerType type, llvm::StringRef name) { + if (!type.IsNullPtrType()) { + lldb::ValueObjectSP ret_val; + return ret_val; + } + uintptr_t zero = 0; + ExecutionContext exe_ctx(target.get(), false); + uint64_t byte_size = 0; + if (auto temp = type.GetByteSize(target.get())) + byte_size = temp.value(); + lldb::DataExtractorSP data_sp = std::make_shared<DataExtractor>( + reinterpret_cast<const void *>(zero), byte_size, exe_ctx.GetByteOrder(), + exe_ctx.GetAddressByteSize()); + return ValueObject::CreateValueObjectFromData(name, *data_sp, exe_ctx, type); +} + +ModuleSP ValueObject::GetModule() { + ValueObject *root(GetRoot()); + if (root != this) + return root->GetModule(); + return lldb::ModuleSP(); +} + +ValueObject *ValueObject::GetRoot() { + if (m_root) + return m_root; + return (m_root = FollowParentChain([](ValueObject *vo) -> bool { + return (vo->m_parent != nullptr); + })); +} + +ValueObject * +ValueObject::FollowParentChain(std::function<bool(ValueObject *)> f) { + ValueObject *vo = this; + while (vo) { + if (!f(vo)) + break; + vo = vo->m_parent; + } + return vo; +} + +AddressType ValueObject::GetAddressTypeOfChildren() { + if (m_address_type_of_ptr_or_ref_children == eAddressTypeInvalid) { + ValueObject *root(GetRoot()); + if (root != this) + return root->GetAddressTypeOfChildren(); + } + return m_address_type_of_ptr_or_ref_children; +} + +lldb::DynamicValueType ValueObject::GetDynamicValueType() { + ValueObject *with_dv_info = this; + while (with_dv_info) { + if (with_dv_info->HasDynamicValueTypeInfo()) + return with_dv_info->GetDynamicValueTypeImpl(); + with_dv_info = with_dv_info->m_parent; + } + return lldb::eNoDynamicValues; +} + +lldb::Format ValueObject::GetFormat() const { + const ValueObject *with_fmt_info = this; + while (with_fmt_info) { + if (with_fmt_info->m_format != lldb::eFormatDefault) + return with_fmt_info->m_format; + with_fmt_info = with_fmt_info->m_parent; + } + return m_format; +} + +lldb::LanguageType ValueObject::GetPreferredDisplayLanguage() { + lldb::LanguageType type = m_preferred_display_language; + if (m_preferred_display_language == lldb::eLanguageTypeUnknown) { + if (GetRoot()) { + if (GetRoot() == this) { + if (StackFrameSP frame_sp = GetFrameSP()) { + const SymbolContext &sc( + frame_sp->GetSymbolContext(eSymbolContextCompUnit)); + if (CompileUnit *cu = sc.comp_unit) + type = cu->GetLanguage(); + } + } else { + type = GetRoot()->GetPreferredDisplayLanguage(); + } + } + } + return (m_preferred_display_language = type); // only compute it once +} + +void ValueObject::SetPreferredDisplayLanguageIfNeeded(lldb::LanguageType lt) { + if (m_preferred_display_language == lldb::eLanguageTypeUnknown) + SetPreferredDisplayLanguage(lt); +} + +bool ValueObject::CanProvideValue() { + // we need to support invalid types as providers of values because some bare- + // board debugging scenarios have no notion of types, but still manage to + // have raw numeric values for things like registers. sigh. + CompilerType type = GetCompilerType(); + return (!type.IsValid()) || (0 != (type.GetTypeInfo() & eTypeHasValue)); +} + + + +ValueObjectSP ValueObject::Persist() { + if (!UpdateValueIfNeeded()) + return nullptr; + + TargetSP target_sp(GetTargetSP()); + if (!target_sp) + return nullptr; + + PersistentExpressionState *persistent_state = + target_sp->GetPersistentExpressionStateForLanguage( + GetPreferredDisplayLanguage()); + + if (!persistent_state) + return nullptr; + + ConstString name = persistent_state->GetNextPersistentVariableName(); + + ValueObjectSP const_result_sp = + ValueObjectConstResult::Create(target_sp.get(), GetValue(), name); + + ExpressionVariableSP persistent_var_sp = + persistent_state->CreatePersistentVariable(const_result_sp); + persistent_var_sp->m_live_sp = persistent_var_sp->m_frozen_sp; + persistent_var_sp->m_flags |= ExpressionVariable::EVIsProgramReference; + + return persistent_var_sp->GetValueObject(); +} + +lldb::ValueObjectSP ValueObject::GetVTable() { + return ValueObjectVTable::Create(*this); +} diff --git a/contrib/llvm-project/lldb/source/Core/ValueObjectCast.cpp b/contrib/llvm-project/lldb/source/Core/ValueObjectCast.cpp new file mode 100644 index 000000000000..c8e316415141 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/ValueObjectCast.cpp @@ -0,0 +1,94 @@ +//===-- ValueObjectCast.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/Core/ValueObjectCast.h" + +#include "lldb/Core/Value.h" +#include "lldb/Core/ValueObject.h" +#include "lldb/Symbol/CompilerType.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Utility/Scalar.h" +#include "lldb/Utility/Status.h" +#include <optional> + +namespace lldb_private { +class ConstString; +} + +using namespace lldb_private; + +lldb::ValueObjectSP ValueObjectCast::Create(ValueObject &parent, + ConstString name, + const CompilerType &cast_type) { + ValueObjectCast *cast_valobj_ptr = + new ValueObjectCast(parent, name, cast_type); + return cast_valobj_ptr->GetSP(); +} + +ValueObjectCast::ValueObjectCast(ValueObject &parent, ConstString name, + const CompilerType &cast_type) + : ValueObject(parent), m_cast_type(cast_type) { + SetName(name); + m_value.SetCompilerType(cast_type); +} + +ValueObjectCast::~ValueObjectCast() = default; + +CompilerType ValueObjectCast::GetCompilerTypeImpl() { return m_cast_type; } + +llvm::Expected<uint32_t> ValueObjectCast::CalculateNumChildren(uint32_t max) { + ExecutionContext exe_ctx(GetExecutionContextRef()); + auto children_count = GetCompilerType().GetNumChildren( + true, &exe_ctx); + if (!children_count) + return children_count; + return *children_count <= max ? *children_count : max; +} + +std::optional<uint64_t> ValueObjectCast::GetByteSize() { + ExecutionContext exe_ctx(GetExecutionContextRef()); + return m_value.GetValueByteSize(nullptr, &exe_ctx); +} + +lldb::ValueType ValueObjectCast::GetValueType() const { + // Let our parent answer global, local, argument, etc... + return m_parent->GetValueType(); +} + +bool ValueObjectCast::UpdateValue() { + SetValueIsValid(false); + m_error.Clear(); + + if (m_parent->UpdateValueIfNeeded(false)) { + Value old_value(m_value); + m_update_point.SetUpdated(); + m_value = m_parent->GetValue(); + CompilerType compiler_type(GetCompilerType()); + m_value.SetCompilerType(compiler_type); + SetAddressTypeOfChildren(m_parent->GetAddressTypeOfChildren()); + if (!CanProvideValue()) { + // this value object represents an aggregate type whose children have + // values, but this object does not. So we say we are changed if our + // location has changed. + SetValueDidChange(m_value.GetValueType() != old_value.GetValueType() || + m_value.GetScalar() != old_value.GetScalar()); + } + ExecutionContext exe_ctx(GetExecutionContextRef()); + m_error = m_value.GetValueAsData(&exe_ctx, m_data, GetModule().get()); + SetValueDidChange(m_parent->GetValueDidChange()); + return true; + } + + // The dynamic value failed to get an error, pass the error along + if (m_error.Success() && m_parent->GetError().Fail()) + m_error = m_parent->GetError(); + SetValueIsValid(false); + return false; +} + +bool ValueObjectCast::IsInScope() { return m_parent->IsInScope(); } diff --git a/contrib/llvm-project/lldb/source/Core/ValueObjectChild.cpp b/contrib/llvm-project/lldb/source/Core/ValueObjectChild.cpp new file mode 100644 index 000000000000..c6a97dd1a5cd --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/ValueObjectChild.cpp @@ -0,0 +1,226 @@ +//===-- ValueObjectChild.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/Core/ValueObjectChild.h" + +#include "lldb/Core/Value.h" +#include "lldb/Symbol/CompilerType.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Process.h" +#include "lldb/Utility/Flags.h" +#include "lldb/Utility/Scalar.h" +#include "lldb/Utility/Status.h" +#include "lldb/lldb-forward.h" + +#include <functional> +#include <memory> +#include <vector> + +#include <cstdio> +#include <cstring> + +using namespace lldb_private; + +ValueObjectChild::ValueObjectChild( + ValueObject &parent, const CompilerType &compiler_type, + ConstString name, uint64_t byte_size, int32_t byte_offset, + uint32_t bitfield_bit_size, uint32_t bitfield_bit_offset, + bool is_base_class, bool is_deref_of_parent, + AddressType child_ptr_or_ref_addr_type, uint64_t language_flags) + : ValueObject(parent), m_compiler_type(compiler_type), + m_byte_size(byte_size), m_byte_offset(byte_offset), + m_bitfield_bit_size(bitfield_bit_size), + m_bitfield_bit_offset(bitfield_bit_offset), + m_is_base_class(is_base_class), m_is_deref_of_parent(is_deref_of_parent), + m_can_update_with_invalid_exe_ctx() { + m_name = name; + SetAddressTypeOfChildren(child_ptr_or_ref_addr_type); + SetLanguageFlags(language_flags); +} + +ValueObjectChild::~ValueObjectChild() = default; + +lldb::ValueType ValueObjectChild::GetValueType() const { + return m_parent->GetValueType(); +} + +llvm::Expected<uint32_t> ValueObjectChild::CalculateNumChildren(uint32_t max) { + ExecutionContext exe_ctx(GetExecutionContextRef()); + auto children_count = GetCompilerType().GetNumChildren(true, &exe_ctx); + if (!children_count) + return children_count; + return *children_count <= max ? *children_count : max; +} + +static void AdjustForBitfieldness(ConstString &name, + uint8_t bitfield_bit_size) { + if (name && bitfield_bit_size) + name.SetString(llvm::formatv("{0}:{1}", name, bitfield_bit_size).str()); +} + +ConstString ValueObjectChild::GetTypeName() { + if (m_type_name.IsEmpty()) { + m_type_name = GetCompilerType().GetTypeName(); + AdjustForBitfieldness(m_type_name, m_bitfield_bit_size); + } + return m_type_name; +} + +ConstString ValueObjectChild::GetQualifiedTypeName() { + ConstString qualified_name = GetCompilerType().GetTypeName(); + AdjustForBitfieldness(qualified_name, m_bitfield_bit_size); + return qualified_name; +} + +ConstString ValueObjectChild::GetDisplayTypeName() { + ConstString display_name = GetCompilerType().GetDisplayTypeName(); + AdjustForBitfieldness(display_name, m_bitfield_bit_size); + return display_name; +} + +LazyBool ValueObjectChild::CanUpdateWithInvalidExecutionContext() { + if (m_can_update_with_invalid_exe_ctx) + return *m_can_update_with_invalid_exe_ctx; + if (m_parent) { + ValueObject *opinionated_parent = + m_parent->FollowParentChain([](ValueObject *valobj) -> bool { + return (valobj->CanUpdateWithInvalidExecutionContext() == + eLazyBoolCalculate); + }); + if (opinionated_parent) + return *(m_can_update_with_invalid_exe_ctx = + opinionated_parent->CanUpdateWithInvalidExecutionContext()); + } + return *(m_can_update_with_invalid_exe_ctx = + this->ValueObject::CanUpdateWithInvalidExecutionContext()); +} + +bool ValueObjectChild::UpdateValue() { + m_error.Clear(); + SetValueIsValid(false); + ValueObject *parent = m_parent; + if (parent) { + if (parent->UpdateValueIfNeeded(false)) { + m_value.SetCompilerType(GetCompilerType()); + + CompilerType parent_type(parent->GetCompilerType()); + // Copy the parent scalar value and the scalar value type + m_value.GetScalar() = parent->GetValue().GetScalar(); + m_value.SetValueType(parent->GetValue().GetValueType()); + + Flags parent_type_flags(parent_type.GetTypeInfo()); + const bool is_instance_ptr_base = + ((m_is_base_class) && + (parent_type_flags.AnySet(lldb::eTypeInstanceIsPointer))); + + if (parent->GetCompilerType().ShouldTreatScalarValueAsAddress()) { + m_value.GetScalar() = parent->GetPointerValue(); + + switch (parent->GetAddressTypeOfChildren()) { + case eAddressTypeFile: { + lldb::ProcessSP process_sp(GetProcessSP()); + if (process_sp && process_sp->IsAlive()) + m_value.SetValueType(Value::ValueType::LoadAddress); + else + m_value.SetValueType(Value::ValueType::FileAddress); + } break; + case eAddressTypeLoad: + m_value.SetValueType(is_instance_ptr_base + ? Value::ValueType::Scalar + : Value::ValueType::LoadAddress); + break; + case eAddressTypeHost: + m_value.SetValueType(Value::ValueType::HostAddress); + break; + case eAddressTypeInvalid: + // TODO: does this make sense? + m_value.SetValueType(Value::ValueType::Scalar); + break; + } + } + switch (m_value.GetValueType()) { + case Value::ValueType::Invalid: + break; + case Value::ValueType::LoadAddress: + case Value::ValueType::FileAddress: + case Value::ValueType::HostAddress: { + lldb::addr_t addr = m_value.GetScalar().ULongLong(LLDB_INVALID_ADDRESS); + if (addr == LLDB_INVALID_ADDRESS) { + m_error.SetErrorString("parent address is invalid."); + } else if (addr == 0) { + m_error.SetErrorString("parent is NULL"); + } else { + // If a bitfield doesn't fit into the child_byte_size'd window at + // child_byte_offset, move the window forward until it fits. The + // problem here is that Value has no notion of bitfields and thus the + // Value's DataExtractor is sized like the bitfields CompilerType; a + // sequence of bitfields, however, can be larger than their underlying + // type. + if (m_bitfield_bit_offset) { + const bool thread_and_frame_only_if_stopped = true; + ExecutionContext exe_ctx(GetExecutionContextRef().Lock( + thread_and_frame_only_if_stopped)); + if (auto type_bit_size = GetCompilerType().GetBitSize( + exe_ctx.GetBestExecutionContextScope())) { + uint64_t bitfield_end = + m_bitfield_bit_size + m_bitfield_bit_offset; + if (bitfield_end > *type_bit_size) { + uint64_t overhang_bytes = + (bitfield_end - *type_bit_size + 7) / 8; + m_byte_offset += overhang_bytes; + m_bitfield_bit_offset -= overhang_bytes * 8; + } + } + } + + // Set this object's scalar value to the address of its value by + // adding its byte offset to the parent address + m_value.GetScalar() += m_byte_offset; + } + } break; + + case Value::ValueType::Scalar: + // try to extract the child value from the parent's scalar value + { + Scalar scalar(m_value.GetScalar()); + scalar.ExtractBitfield(8 * m_byte_size, 8 * m_byte_offset); + m_value.GetScalar() = scalar; + } + break; + } + + if (m_error.Success()) { + const bool thread_and_frame_only_if_stopped = true; + ExecutionContext exe_ctx( + GetExecutionContextRef().Lock(thread_and_frame_only_if_stopped)); + if (GetCompilerType().GetTypeInfo() & lldb::eTypeHasValue) { + Value &value = is_instance_ptr_base ? m_parent->GetValue() : m_value; + m_error = + value.GetValueAsData(&exe_ctx, m_data, GetModule().get()); + } else { + m_error.Clear(); // No value so nothing to read... + } + } + + } else { + m_error.SetErrorStringWithFormat("parent failed to evaluate: %s", + parent->GetError().AsCString()); + } + } else { + m_error.SetErrorString("ValueObjectChild has a NULL parent ValueObject."); + } + + return m_error.Success(); +} + +bool ValueObjectChild::IsInScope() { + ValueObject *root(GetRoot()); + if (root) + return root->IsInScope(); + return false; +} diff --git a/contrib/llvm-project/lldb/source/Core/ValueObjectConstResult.cpp b/contrib/llvm-project/lldb/source/Core/ValueObjectConstResult.cpp new file mode 100644 index 000000000000..879d3c3f6b03 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/ValueObjectConstResult.cpp @@ -0,0 +1,302 @@ +//===-- ValueObjectConstResult.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/Core/ValueObjectConstResult.h" + +#include "lldb/Core/ValueObjectDynamicValue.h" +#include "lldb/Symbol/CompilerType.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/ExecutionContextScope.h" +#include "lldb/Target/Process.h" +#include "lldb/Utility/DataBuffer.h" +#include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/DataExtractor.h" +#include "lldb/Utility/Scalar.h" +#include <optional> + +namespace lldb_private { +class Module; +} + +using namespace lldb; +using namespace lldb_private; + +ValueObjectSP ValueObjectConstResult::Create(ExecutionContextScope *exe_scope, + ByteOrder byte_order, + uint32_t addr_byte_size, + lldb::addr_t address) { + auto manager_sp = ValueObjectManager::Create(); + return (new ValueObjectConstResult(exe_scope, *manager_sp, byte_order, + addr_byte_size, address)) + ->GetSP(); +} + +ValueObjectConstResult::ValueObjectConstResult(ExecutionContextScope *exe_scope, + ValueObjectManager &manager, + ByteOrder byte_order, + uint32_t addr_byte_size, + lldb::addr_t address) + : ValueObject(exe_scope, manager), m_impl(this, address) { + SetIsConstant(); + SetValueIsValid(true); + m_data.SetByteOrder(byte_order); + m_data.SetAddressByteSize(addr_byte_size); + SetAddressTypeOfChildren(eAddressTypeLoad); +} + +ValueObjectSP ValueObjectConstResult::Create(ExecutionContextScope *exe_scope, + const CompilerType &compiler_type, + ConstString name, + const DataExtractor &data, + lldb::addr_t address) { + auto manager_sp = ValueObjectManager::Create(); + return (new ValueObjectConstResult(exe_scope, *manager_sp, compiler_type, + name, data, address)) + ->GetSP(); +} + +ValueObjectConstResult::ValueObjectConstResult( + ExecutionContextScope *exe_scope, ValueObjectManager &manager, + const CompilerType &compiler_type, ConstString name, + const DataExtractor &data, lldb::addr_t address) + : ValueObject(exe_scope, manager), m_impl(this, address) { + m_data = data; + + if (!m_data.GetSharedDataBuffer()) { + DataBufferSP shared_data_buffer( + new DataBufferHeap(data.GetDataStart(), data.GetByteSize())); + m_data.SetData(shared_data_buffer); + } + + m_value.GetScalar() = (uintptr_t)m_data.GetDataStart(); + m_value.SetValueType(Value::ValueType::HostAddress); + m_value.SetCompilerType(compiler_type); + m_name = name; + SetIsConstant(); + SetValueIsValid(true); + SetAddressTypeOfChildren(eAddressTypeLoad); +} + +ValueObjectSP ValueObjectConstResult::Create(ExecutionContextScope *exe_scope, + const CompilerType &compiler_type, + ConstString name, + const lldb::DataBufferSP &data_sp, + lldb::ByteOrder data_byte_order, + uint32_t data_addr_size, + lldb::addr_t address) { + auto manager_sp = ValueObjectManager::Create(); + return (new ValueObjectConstResult(exe_scope, *manager_sp, compiler_type, + name, data_sp, data_byte_order, + data_addr_size, address)) + ->GetSP(); +} + +ValueObjectSP ValueObjectConstResult::Create(ExecutionContextScope *exe_scope, + Value &value, + ConstString name, + Module *module) { + auto manager_sp = ValueObjectManager::Create(); + return (new ValueObjectConstResult(exe_scope, *manager_sp, value, name, + module)) + ->GetSP(); +} + +ValueObjectConstResult::ValueObjectConstResult( + ExecutionContextScope *exe_scope, ValueObjectManager &manager, + const CompilerType &compiler_type, ConstString name, + const lldb::DataBufferSP &data_sp, lldb::ByteOrder data_byte_order, + uint32_t data_addr_size, lldb::addr_t address) + : ValueObject(exe_scope, manager), m_impl(this, address) { + m_data.SetByteOrder(data_byte_order); + m_data.SetAddressByteSize(data_addr_size); + m_data.SetData(data_sp); + m_value.GetScalar() = (uintptr_t)data_sp->GetBytes(); + m_value.SetValueType(Value::ValueType::HostAddress); + m_value.SetCompilerType(compiler_type); + m_name = name; + SetIsConstant(); + SetValueIsValid(true); + SetAddressTypeOfChildren(eAddressTypeLoad); +} + +ValueObjectSP ValueObjectConstResult::Create(ExecutionContextScope *exe_scope, + const CompilerType &compiler_type, + ConstString name, + lldb::addr_t address, + AddressType address_type, + uint32_t addr_byte_size) { + auto manager_sp = ValueObjectManager::Create(); + return (new ValueObjectConstResult(exe_scope, *manager_sp, compiler_type, + name, address, address_type, + addr_byte_size)) + ->GetSP(); +} + +ValueObjectConstResult::ValueObjectConstResult( + ExecutionContextScope *exe_scope, ValueObjectManager &manager, + const CompilerType &compiler_type, ConstString name, lldb::addr_t address, + AddressType address_type, uint32_t addr_byte_size) + : ValueObject(exe_scope, manager), m_type_name(), + m_impl(this, address) { + m_value.GetScalar() = address; + m_data.SetAddressByteSize(addr_byte_size); + m_value.GetScalar().GetData(m_data, addr_byte_size); + // m_value.SetValueType(Value::ValueType::HostAddress); + switch (address_type) { + case eAddressTypeInvalid: + m_value.SetValueType(Value::ValueType::Scalar); + break; + case eAddressTypeFile: + m_value.SetValueType(Value::ValueType::FileAddress); + break; + case eAddressTypeLoad: + m_value.SetValueType(Value::ValueType::LoadAddress); + break; + case eAddressTypeHost: + m_value.SetValueType(Value::ValueType::HostAddress); + break; + } + m_value.SetCompilerType(compiler_type); + m_name = name; + SetIsConstant(); + SetValueIsValid(true); + SetAddressTypeOfChildren(eAddressTypeLoad); +} + +ValueObjectSP ValueObjectConstResult::Create(ExecutionContextScope *exe_scope, + const Status &error) { + auto manager_sp = ValueObjectManager::Create(); + return (new ValueObjectConstResult(exe_scope, *manager_sp, error))->GetSP(); +} + +ValueObjectConstResult::ValueObjectConstResult(ExecutionContextScope *exe_scope, + ValueObjectManager &manager, + const Status &error) + : ValueObject(exe_scope, manager), m_impl(this) { + m_error = error; + SetIsConstant(); +} + +ValueObjectConstResult::ValueObjectConstResult(ExecutionContextScope *exe_scope, + ValueObjectManager &manager, + const Value &value, + ConstString name, Module *module) + : ValueObject(exe_scope, manager), m_impl(this) { + m_value = value; + m_name = name; + ExecutionContext exe_ctx; + exe_scope->CalculateExecutionContext(exe_ctx); + m_error = m_value.GetValueAsData(&exe_ctx, m_data, module); +} + +ValueObjectConstResult::~ValueObjectConstResult() = default; + +CompilerType ValueObjectConstResult::GetCompilerTypeImpl() { + return m_value.GetCompilerType(); +} + +lldb::ValueType ValueObjectConstResult::GetValueType() const { + return eValueTypeConstResult; +} + +std::optional<uint64_t> ValueObjectConstResult::GetByteSize() { + ExecutionContext exe_ctx(GetExecutionContextRef()); + if (!m_byte_size) { + if (auto size = + GetCompilerType().GetByteSize(exe_ctx.GetBestExecutionContextScope())) + SetByteSize(*size); + } + return m_byte_size; +} + +void ValueObjectConstResult::SetByteSize(size_t size) { m_byte_size = size; } + +llvm::Expected<uint32_t> +ValueObjectConstResult::CalculateNumChildren(uint32_t max) { + ExecutionContext exe_ctx(GetExecutionContextRef()); + auto children_count = GetCompilerType().GetNumChildren(true, &exe_ctx); + if (!children_count) + return children_count; + return *children_count <= max ? *children_count : max; +} + +ConstString ValueObjectConstResult::GetTypeName() { + if (m_type_name.IsEmpty()) + m_type_name = GetCompilerType().GetTypeName(); + return m_type_name; +} + +ConstString ValueObjectConstResult::GetDisplayTypeName() { + return GetCompilerType().GetDisplayTypeName(); +} + +bool ValueObjectConstResult::UpdateValue() { + // Const value is always valid + SetValueIsValid(true); + return true; +} + +bool ValueObjectConstResult::IsInScope() { + // A const result value is always in scope since it serializes all + // information needed to contain the constant value. + return true; +} + +lldb::ValueObjectSP ValueObjectConstResult::Dereference(Status &error) { + return m_impl.Dereference(error); +} + +lldb::ValueObjectSP ValueObjectConstResult::GetSyntheticChildAtOffset( + uint32_t offset, const CompilerType &type, bool can_create, + ConstString name_const_str) { + return m_impl.GetSyntheticChildAtOffset(offset, type, can_create, + name_const_str); +} + +lldb::ValueObjectSP ValueObjectConstResult::AddressOf(Status &error) { + return m_impl.AddressOf(error); +} + +lldb::addr_t ValueObjectConstResult::GetAddressOf(bool scalar_is_load_address, + AddressType *address_type) { + return m_impl.GetAddressOf(scalar_is_load_address, address_type); +} + +size_t ValueObjectConstResult::GetPointeeData(DataExtractor &data, + uint32_t item_idx, + uint32_t item_count) { + return m_impl.GetPointeeData(data, item_idx, item_count); +} + +lldb::ValueObjectSP +ValueObjectConstResult::GetDynamicValue(lldb::DynamicValueType use_dynamic) { + // Always recalculate dynamic values for const results as the memory that + // they might point to might have changed at any time. + if (use_dynamic != eNoDynamicValues) { + if (!IsDynamic()) { + ExecutionContext exe_ctx(GetExecutionContextRef()); + Process *process = exe_ctx.GetProcessPtr(); + if (process && process->IsPossibleDynamicValue(*this)) + m_dynamic_value = new ValueObjectDynamicValue(*this, use_dynamic); + } + if (m_dynamic_value && m_dynamic_value->GetError().Success()) + return m_dynamic_value->GetSP(); + } + return ValueObjectSP(); +} + +lldb::ValueObjectSP +ValueObjectConstResult::DoCast(const CompilerType &compiler_type) { + return m_impl.Cast(compiler_type); +} + +lldb::LanguageType ValueObjectConstResult::GetPreferredDisplayLanguage() { + if (m_preferred_display_language != lldb::eLanguageTypeUnknown) + return m_preferred_display_language; + return GetCompilerTypeImpl().GetMinimumLanguage(); +} diff --git a/contrib/llvm-project/lldb/source/Core/ValueObjectConstResultCast.cpp b/contrib/llvm-project/lldb/source/Core/ValueObjectConstResultCast.cpp new file mode 100644 index 000000000000..bf7a12dc6823 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/ValueObjectConstResultCast.cpp @@ -0,0 +1,56 @@ +//===-- ValueObjectConstResultCast.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/Core/ValueObjectConstResultCast.h" + +namespace lldb_private { +class DataExtractor; +} +namespace lldb_private { +class Status; +} +namespace lldb_private { +class ValueObject; +} + +using namespace lldb_private; + +ValueObjectConstResultCast::ValueObjectConstResultCast( + ValueObject &parent, ConstString name, const CompilerType &cast_type, + lldb::addr_t live_address) + : ValueObjectCast(parent, name, cast_type), m_impl(this, live_address) { + m_name = name; +} + +ValueObjectConstResultCast::~ValueObjectConstResultCast() = default; + +lldb::ValueObjectSP ValueObjectConstResultCast::Dereference(Status &error) { + return m_impl.Dereference(error); +} + +lldb::ValueObjectSP ValueObjectConstResultCast::GetSyntheticChildAtOffset( + uint32_t offset, const CompilerType &type, bool can_create, + ConstString name_const_str) { + return m_impl.GetSyntheticChildAtOffset(offset, type, can_create, + name_const_str); +} + +lldb::ValueObjectSP ValueObjectConstResultCast::AddressOf(Status &error) { + return m_impl.AddressOf(error); +} + +size_t ValueObjectConstResultCast::GetPointeeData(DataExtractor &data, + uint32_t item_idx, + uint32_t item_count) { + return m_impl.GetPointeeData(data, item_idx, item_count); +} + +lldb::ValueObjectSP +ValueObjectConstResultCast::DoCast(const CompilerType &compiler_type) { + return m_impl.Cast(compiler_type); +} diff --git a/contrib/llvm-project/lldb/source/Core/ValueObjectConstResultChild.cpp b/contrib/llvm-project/lldb/source/Core/ValueObjectConstResultChild.cpp new file mode 100644 index 000000000000..39fc0c9fbb35 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/ValueObjectConstResultChild.cpp @@ -0,0 +1,68 @@ +//===-- ValueObjectConstResultChild.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/Core/ValueObjectConstResultChild.h" + +#include "lldb/lldb-private-enumerations.h" +namespace lldb_private { +class DataExtractor; +} +namespace lldb_private { +class Status; +} +namespace lldb_private { +class ValueObject; +} + +using namespace lldb_private; + +ValueObjectConstResultChild::ValueObjectConstResultChild( + ValueObject &parent, const CompilerType &compiler_type, + ConstString name, uint32_t byte_size, int32_t byte_offset, + uint32_t bitfield_bit_size, uint32_t bitfield_bit_offset, + bool is_base_class, bool is_deref_of_parent, lldb::addr_t live_address, + uint64_t language_flags) + : ValueObjectChild(parent, compiler_type, name, byte_size, byte_offset, + bitfield_bit_size, bitfield_bit_offset, is_base_class, + is_deref_of_parent, eAddressTypeLoad, language_flags), + m_impl(this, live_address) { + m_name = name; +} + +ValueObjectConstResultChild::~ValueObjectConstResultChild() = default; + +lldb::ValueObjectSP ValueObjectConstResultChild::Dereference(Status &error) { + return m_impl.Dereference(error); +} + +lldb::ValueObjectSP ValueObjectConstResultChild::GetSyntheticChildAtOffset( + uint32_t offset, const CompilerType &type, bool can_create, + ConstString name_const_str) { + return m_impl.GetSyntheticChildAtOffset(offset, type, can_create, + name_const_str); +} + +lldb::ValueObjectSP ValueObjectConstResultChild::AddressOf(Status &error) { + return m_impl.AddressOf(error); +} + +lldb::addr_t ValueObjectConstResultChild::GetAddressOf( + bool scalar_is_load_address, AddressType* address_type) { + return m_impl.GetAddressOf(scalar_is_load_address, address_type); +} + +size_t ValueObjectConstResultChild::GetPointeeData(DataExtractor &data, + uint32_t item_idx, + uint32_t item_count) { + return m_impl.GetPointeeData(data, item_idx, item_count); +} + +lldb::ValueObjectSP +ValueObjectConstResultChild::DoCast(const CompilerType &compiler_type) { + return m_impl.Cast(compiler_type); +} diff --git a/contrib/llvm-project/lldb/source/Core/ValueObjectConstResultImpl.cpp b/contrib/llvm-project/lldb/source/Core/ValueObjectConstResultImpl.cpp new file mode 100644 index 000000000000..2a7c90770076 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/ValueObjectConstResultImpl.cpp @@ -0,0 +1,235 @@ +//===-- ValueObjectConstResultImpl.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/Core/ValueObjectConstResultImpl.h" + +#include "lldb/Core/Value.h" +#include "lldb/Core/ValueObject.h" +#include "lldb/Core/ValueObjectConstResult.h" +#include "lldb/Core/ValueObjectConstResultCast.h" +#include "lldb/Core/ValueObjectConstResultChild.h" +#include "lldb/Symbol/CompilerType.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/Endian.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Scalar.h" + +#include <string> + +namespace lldb_private { +class DataExtractor; +} +namespace lldb_private { +class Status; +} + +using namespace lldb; +using namespace lldb_private; + +ValueObjectConstResultImpl::ValueObjectConstResultImpl( + ValueObject *valobj, lldb::addr_t live_address) + : m_impl_backend(valobj), m_live_address(live_address), + m_live_address_type(eAddressTypeLoad), + m_address_of_backend() {} + +lldb::ValueObjectSP ValueObjectConstResultImpl::Dereference(Status &error) { + if (m_impl_backend == nullptr) + return lldb::ValueObjectSP(); + + return m_impl_backend->ValueObject::Dereference(error); +} + +ValueObject *ValueObjectConstResultImpl::CreateChildAtIndex(size_t idx) { + if (m_impl_backend == nullptr) + return nullptr; + + m_impl_backend->UpdateValueIfNeeded(false); + + bool omit_empty_base_classes = true; + bool ignore_array_bounds = false; + std::string child_name; + uint32_t child_byte_size = 0; + int32_t child_byte_offset = 0; + uint32_t child_bitfield_bit_size = 0; + uint32_t child_bitfield_bit_offset = 0; + bool child_is_base_class = false; + bool child_is_deref_of_parent = false; + uint64_t language_flags; + const bool transparent_pointers = true; + CompilerType compiler_type = m_impl_backend->GetCompilerType(); + + ExecutionContext exe_ctx(m_impl_backend->GetExecutionContextRef()); + + auto child_compiler_type_or_err = compiler_type.GetChildCompilerTypeAtIndex( + &exe_ctx, idx, transparent_pointers, omit_empty_base_classes, + ignore_array_bounds, child_name, child_byte_size, child_byte_offset, + child_bitfield_bit_size, child_bitfield_bit_offset, child_is_base_class, + child_is_deref_of_parent, m_impl_backend, language_flags); + + // One might think we should check that the size of the children + // is always strictly positive, hence we could avoid creating a + // ValueObject if that's not the case, but it turns out there + // are languages out there which allow zero-size types with + // children (e.g. Swift). + if (!child_compiler_type_or_err || !child_compiler_type_or_err->IsValid()) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Types), + child_compiler_type_or_err.takeError(), + "could not find child: {0}"); + return nullptr; + } + + lldb::addr_t child_live_addr = LLDB_INVALID_ADDRESS; + // Transfer the live address (with offset) to the child. But if + // the parent is a pointer, the live address is where that pointer + // value lives in memory, so the children live addresses aren't + // offsets from that value, they are just other load addresses that + // are recorded in the Value of the child ValueObjects. + if (m_live_address != LLDB_INVALID_ADDRESS && !compiler_type.IsPointerType()) + child_live_addr = m_live_address + child_byte_offset; + + return new ValueObjectConstResultChild( + *m_impl_backend, *child_compiler_type_or_err, ConstString(child_name), + child_byte_size, child_byte_offset, child_bitfield_bit_size, + child_bitfield_bit_offset, child_is_base_class, child_is_deref_of_parent, + child_live_addr, language_flags); +} + +ValueObject * +ValueObjectConstResultImpl::CreateSyntheticArrayMember(size_t idx) { + if (m_impl_backend == nullptr) + return nullptr; + + m_impl_backend->UpdateValueIfNeeded(false); + + bool omit_empty_base_classes = true; + bool ignore_array_bounds = true; + std::string child_name; + uint32_t child_byte_size = 0; + int32_t child_byte_offset = 0; + uint32_t child_bitfield_bit_size = 0; + uint32_t child_bitfield_bit_offset = 0; + bool child_is_base_class = false; + bool child_is_deref_of_parent = false; + uint64_t language_flags; + + const bool transparent_pointers = false; + CompilerType compiler_type = m_impl_backend->GetCompilerType(); + + ExecutionContext exe_ctx(m_impl_backend->GetExecutionContextRef()); + + auto child_compiler_type_or_err = compiler_type.GetChildCompilerTypeAtIndex( + &exe_ctx, 0, transparent_pointers, omit_empty_base_classes, + ignore_array_bounds, child_name, child_byte_size, child_byte_offset, + child_bitfield_bit_size, child_bitfield_bit_offset, child_is_base_class, + child_is_deref_of_parent, m_impl_backend, language_flags); + // One might think we should check that the size of the children + // is always strictly positive, hence we could avoid creating a + // ValueObject if that's not the case, but it turns out there + // are languages out there which allow zero-size types with + // children (e.g. Swift). + if (!child_compiler_type_or_err || !child_compiler_type_or_err->IsValid()) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Types), + child_compiler_type_or_err.takeError(), + "could not find child: {0}"); + return nullptr; + } + + child_byte_offset += child_byte_size * idx; + + lldb::addr_t child_live_addr = LLDB_INVALID_ADDRESS; + // Transfer the live address (with offset) to the child. But if + // the parent is a pointer, the live address is where that pointer + // value lives in memory, so the children live addresses aren't + // offsets from that value, they are just other load addresses that + // are recorded in the Value of the child ValueObjects. + if (m_live_address != LLDB_INVALID_ADDRESS && !compiler_type.IsPointerType()) + child_live_addr = m_live_address + child_byte_offset; + return new ValueObjectConstResultChild( + *m_impl_backend, *child_compiler_type_or_err, ConstString(child_name), + child_byte_size, child_byte_offset, child_bitfield_bit_size, + child_bitfield_bit_offset, child_is_base_class, child_is_deref_of_parent, + child_live_addr, language_flags); +} + +lldb::ValueObjectSP ValueObjectConstResultImpl::GetSyntheticChildAtOffset( + uint32_t offset, const CompilerType &type, bool can_create, + ConstString name_const_str) { + if (m_impl_backend == nullptr) + return lldb::ValueObjectSP(); + + return m_impl_backend->ValueObject::GetSyntheticChildAtOffset( + offset, type, can_create, name_const_str); +} + +lldb::ValueObjectSP ValueObjectConstResultImpl::AddressOf(Status &error) { + if (m_address_of_backend.get() != nullptr) + return m_address_of_backend; + + if (m_impl_backend == nullptr) + return lldb::ValueObjectSP(); + if (m_live_address != LLDB_INVALID_ADDRESS) { + CompilerType compiler_type(m_impl_backend->GetCompilerType()); + + lldb::DataBufferSP buffer(new lldb_private::DataBufferHeap( + &m_live_address, sizeof(lldb::addr_t))); + + std::string new_name("&"); + new_name.append(m_impl_backend->GetName().AsCString("")); + ExecutionContext exe_ctx(m_impl_backend->GetExecutionContextRef()); + m_address_of_backend = ValueObjectConstResult::Create( + exe_ctx.GetBestExecutionContextScope(), compiler_type.GetPointerType(), + ConstString(new_name.c_str()), buffer, endian::InlHostByteOrder(), + exe_ctx.GetAddressByteSize()); + + m_address_of_backend->GetValue().SetValueType(Value::ValueType::Scalar); + m_address_of_backend->GetValue().GetScalar() = m_live_address; + + return m_address_of_backend; + } else + return m_impl_backend->ValueObject::AddressOf(error); +} + +lldb::ValueObjectSP +ValueObjectConstResultImpl::Cast(const CompilerType &compiler_type) { + if (m_impl_backend == nullptr) + return lldb::ValueObjectSP(); + + ValueObjectConstResultCast *result_cast = + new ValueObjectConstResultCast(*m_impl_backend, m_impl_backend->GetName(), + compiler_type, m_live_address); + return result_cast->GetSP(); +} + +lldb::addr_t +ValueObjectConstResultImpl::GetAddressOf(bool scalar_is_load_address, + AddressType *address_type) { + + if (m_impl_backend == nullptr) + return 0; + + if (m_live_address == LLDB_INVALID_ADDRESS) { + return m_impl_backend->ValueObject::GetAddressOf(scalar_is_load_address, + address_type); + } + + if (address_type) + *address_type = m_live_address_type; + + return m_live_address; +} + +size_t ValueObjectConstResultImpl::GetPointeeData(DataExtractor &data, + uint32_t item_idx, + uint32_t item_count) { + if (m_impl_backend == nullptr) + return 0; + return m_impl_backend->ValueObject::GetPointeeData(data, item_idx, + item_count); +} diff --git a/contrib/llvm-project/lldb/source/Core/ValueObjectDynamicValue.cpp b/contrib/llvm-project/lldb/source/Core/ValueObjectDynamicValue.cpp new file mode 100644 index 000000000000..4695febdf8ca --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/ValueObjectDynamicValue.cpp @@ -0,0 +1,401 @@ +//===-- ValueObjectDynamicValue.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/Core/ValueObjectDynamicValue.h" +#include "lldb/Core/Value.h" +#include "lldb/Core/ValueObject.h" +#include "lldb/Symbol/CompilerType.h" +#include "lldb/Symbol/Type.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/LanguageRuntime.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/DataExtractor.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Scalar.h" +#include "lldb/Utility/Status.h" +#include "lldb/lldb-types.h" + +#include <cstring> +#include <optional> +namespace lldb_private { +class Declaration; +} + +using namespace lldb_private; + +ValueObjectDynamicValue::ValueObjectDynamicValue( + ValueObject &parent, lldb::DynamicValueType use_dynamic) + : ValueObject(parent), m_address(), m_dynamic_type_info(), + m_use_dynamic(use_dynamic) { + SetName(parent.GetName()); +} + +CompilerType ValueObjectDynamicValue::GetCompilerTypeImpl() { + const bool success = UpdateValueIfNeeded(false); + if (success) { + if (m_dynamic_type_info.HasType()) + return m_value.GetCompilerType(); + else + return m_parent->GetCompilerType(); + } + return m_parent->GetCompilerType(); +} + +ConstString ValueObjectDynamicValue::GetTypeName() { + const bool success = UpdateValueIfNeeded(false); + if (success) { + if (m_dynamic_type_info.HasName()) + return m_dynamic_type_info.GetName(); + } + return m_parent->GetTypeName(); +} + +TypeImpl ValueObjectDynamicValue::GetTypeImpl() { + const bool success = UpdateValueIfNeeded(false); + if (success && m_type_impl.IsValid()) { + return m_type_impl; + } + return m_parent->GetTypeImpl(); +} + +ConstString ValueObjectDynamicValue::GetQualifiedTypeName() { + const bool success = UpdateValueIfNeeded(false); + if (success) { + if (m_dynamic_type_info.HasName()) + return m_dynamic_type_info.GetName(); + } + return m_parent->GetQualifiedTypeName(); +} + +ConstString ValueObjectDynamicValue::GetDisplayTypeName() { + const bool success = UpdateValueIfNeeded(false); + if (success) { + if (m_dynamic_type_info.HasType()) + return GetCompilerType().GetDisplayTypeName(); + if (m_dynamic_type_info.HasName()) + return m_dynamic_type_info.GetName(); + } + return m_parent->GetDisplayTypeName(); +} + +llvm::Expected<uint32_t> +ValueObjectDynamicValue::CalculateNumChildren(uint32_t max) { + const bool success = UpdateValueIfNeeded(false); + if (success && m_dynamic_type_info.HasType()) { + ExecutionContext exe_ctx(GetExecutionContextRef()); + auto children_count = GetCompilerType().GetNumChildren(true, &exe_ctx); + if (!children_count) + return children_count; + return *children_count <= max ? *children_count : max; + } else + return m_parent->GetNumChildren(max); +} + +std::optional<uint64_t> ValueObjectDynamicValue::GetByteSize() { + const bool success = UpdateValueIfNeeded(false); + if (success && m_dynamic_type_info.HasType()) { + ExecutionContext exe_ctx(GetExecutionContextRef()); + return m_value.GetValueByteSize(nullptr, &exe_ctx); + } else + return m_parent->GetByteSize(); +} + +lldb::ValueType ValueObjectDynamicValue::GetValueType() const { + return m_parent->GetValueType(); +} + +bool ValueObjectDynamicValue::UpdateValue() { + SetValueIsValid(false); + m_error.Clear(); + + if (!m_parent->UpdateValueIfNeeded(false)) { + // The dynamic value failed to get an error, pass the error along + if (m_error.Success() && m_parent->GetError().Fail()) + m_error = m_parent->GetError(); + return false; + } + + // Setting our type_sp to NULL will route everything back through our parent + // which is equivalent to not using dynamic values. + if (m_use_dynamic == lldb::eNoDynamicValues) { + m_dynamic_type_info.Clear(); + return true; + } + + ExecutionContext exe_ctx(GetExecutionContextRef()); + Target *target = exe_ctx.GetTargetPtr(); + if (target) { + m_data.SetByteOrder(target->GetArchitecture().GetByteOrder()); + m_data.SetAddressByteSize(target->GetArchitecture().GetAddressByteSize()); + } + + // First make sure our Type and/or Address haven't changed: + Process *process = exe_ctx.GetProcessPtr(); + if (!process) + return false; + + TypeAndOrName class_type_or_name; + Address dynamic_address; + bool found_dynamic_type = false; + Value::ValueType value_type; + + LanguageRuntime *runtime = nullptr; + + lldb::LanguageType known_type = m_parent->GetObjectRuntimeLanguage(); + if (known_type != lldb::eLanguageTypeUnknown && + known_type != lldb::eLanguageTypeC) { + runtime = process->GetLanguageRuntime(known_type); + if (auto *preferred_runtime = + runtime->GetPreferredLanguageRuntime(*m_parent)) { + // Try the preferred runtime first. + found_dynamic_type = preferred_runtime->GetDynamicTypeAndAddress( + *m_parent, m_use_dynamic, class_type_or_name, dynamic_address, + value_type); + if (found_dynamic_type) + // Set the operative `runtime` for later use in this function. + runtime = preferred_runtime; + } + if (!found_dynamic_type) + // Fallback to the runtime for `known_type`. + found_dynamic_type = runtime->GetDynamicTypeAndAddress( + *m_parent, m_use_dynamic, class_type_or_name, dynamic_address, + value_type); + } else { + runtime = process->GetLanguageRuntime(lldb::eLanguageTypeC_plus_plus); + if (runtime) + found_dynamic_type = runtime->GetDynamicTypeAndAddress( + *m_parent, m_use_dynamic, class_type_or_name, dynamic_address, + value_type); + + if (!found_dynamic_type) { + runtime = process->GetLanguageRuntime(lldb::eLanguageTypeObjC); + if (runtime) + found_dynamic_type = runtime->GetDynamicTypeAndAddress( + *m_parent, m_use_dynamic, class_type_or_name, dynamic_address, + value_type); + } + } + + // Getting the dynamic value may have run the program a bit, and so marked us + // as needing updating, but we really don't... + + m_update_point.SetUpdated(); + + if (runtime && found_dynamic_type) { + if (class_type_or_name.HasType()) { + m_type_impl = + TypeImpl(m_parent->GetCompilerType(), + runtime->FixUpDynamicType(class_type_or_name, *m_parent) + .GetCompilerType()); + } else { + m_type_impl.Clear(); + } + } else { + m_type_impl.Clear(); + } + + // If we don't have a dynamic type, set ourselves to be invalid and return + // false. We used to try to produce a dynamic ValueObject that behaved "like" + // its parent, but that failed for ValueObjectConstResult, which is too + // complex a beast to try to emulate. If we return an invalid ValueObject, + // clients will end up getting the static value instead, which behaves + // correctly. + if (!found_dynamic_type) { + if (m_dynamic_type_info) + SetValueDidChange(true); + ClearDynamicTypeInformation(); + m_dynamic_type_info.Clear(); + m_error.SetErrorString("no dynamic type found"); + return false; + } + + Value old_value(m_value); + + Log *log = GetLog(LLDBLog::Types); + + bool has_changed_type = false; + + if (!m_dynamic_type_info) { + m_dynamic_type_info = class_type_or_name; + has_changed_type = true; + } else if (class_type_or_name != m_dynamic_type_info) { + // We are another type, we need to tear down our children... + m_dynamic_type_info = class_type_or_name; + SetValueDidChange(true); + has_changed_type = true; + } + + if (has_changed_type) + ClearDynamicTypeInformation(); + + if (!m_address.IsValid() || m_address != dynamic_address) { + if (m_address.IsValid()) + SetValueDidChange(true); + + // We've moved, so we should be fine... + m_address = dynamic_address; + lldb::TargetSP target_sp(GetTargetSP()); + lldb::addr_t load_address = m_address.GetLoadAddress(target_sp.get()); + m_value.GetScalar() = load_address; + } + + if (runtime) + m_dynamic_type_info = + runtime->FixUpDynamicType(m_dynamic_type_info, *m_parent); + + m_value.SetCompilerType(m_dynamic_type_info.GetCompilerType()); + + m_value.SetValueType(value_type); + + if (has_changed_type && log) + LLDB_LOGF(log, "[%s %p] has a new dynamic type %s", GetName().GetCString(), + static_cast<void *>(this), GetTypeName().GetCString()); + + if (m_address.IsValid() && m_dynamic_type_info) { + // The variable value is in the Scalar value inside the m_value. We can + // point our m_data right to it. + m_error = m_value.GetValueAsData(&exe_ctx, m_data, GetModule().get()); + if (m_error.Success()) { + if (!CanProvideValue()) { + // this value object represents an aggregate type whose children have + // values, but this object does not. So we say we are changed if our + // location has changed. + SetValueDidChange(m_value.GetValueType() != old_value.GetValueType() || + m_value.GetScalar() != old_value.GetScalar()); + } + + SetValueIsValid(true); + return true; + } + } + + // We get here if we've failed above... + SetValueIsValid(false); + return false; +} + +bool ValueObjectDynamicValue::IsInScope() { return m_parent->IsInScope(); } + +bool ValueObjectDynamicValue::SetValueFromCString(const char *value_str, + Status &error) { + if (!UpdateValueIfNeeded(false)) { + error.SetErrorString("unable to read value"); + return false; + } + + uint64_t my_value = GetValueAsUnsigned(UINT64_MAX); + uint64_t parent_value = m_parent->GetValueAsUnsigned(UINT64_MAX); + + if (my_value == UINT64_MAX || parent_value == UINT64_MAX) { + error.SetErrorString("unable to read value"); + return false; + } + + // if we are at an offset from our parent, in order to set ourselves + // correctly we would need to change the new value so that it refers to the + // correct dynamic type. we choose not to deal with that - if anything more + // than a value overwrite is required, you should be using the expression + // parser instead of the value editing facility + if (my_value != parent_value) { + // but NULL'ing out a value should always be allowed + if (strcmp(value_str, "0")) { + error.SetErrorString( + "unable to modify dynamic value, use 'expression' command"); + return false; + } + } + + bool ret_val = m_parent->SetValueFromCString(value_str, error); + SetNeedsUpdate(); + return ret_val; +} + +bool ValueObjectDynamicValue::SetData(DataExtractor &data, Status &error) { + if (!UpdateValueIfNeeded(false)) { + error.SetErrorString("unable to read value"); + return false; + } + + uint64_t my_value = GetValueAsUnsigned(UINT64_MAX); + uint64_t parent_value = m_parent->GetValueAsUnsigned(UINT64_MAX); + + if (my_value == UINT64_MAX || parent_value == UINT64_MAX) { + error.SetErrorString("unable to read value"); + return false; + } + + // if we are at an offset from our parent, in order to set ourselves + // correctly we would need to change the new value so that it refers to the + // correct dynamic type. we choose not to deal with that - if anything more + // than a value overwrite is required, you should be using the expression + // parser instead of the value editing facility + if (my_value != parent_value) { + // but NULL'ing out a value should always be allowed + lldb::offset_t offset = 0; + + if (data.GetAddress(&offset) != 0) { + error.SetErrorString( + "unable to modify dynamic value, use 'expression' command"); + return false; + } + } + + bool ret_val = m_parent->SetData(data, error); + SetNeedsUpdate(); + return ret_val; +} + +void ValueObjectDynamicValue::SetPreferredDisplayLanguage( + lldb::LanguageType lang) { + this->ValueObject::SetPreferredDisplayLanguage(lang); + if (m_parent) + m_parent->SetPreferredDisplayLanguage(lang); +} + +lldb::LanguageType ValueObjectDynamicValue::GetPreferredDisplayLanguage() { + if (m_preferred_display_language == lldb::eLanguageTypeUnknown) { + if (m_parent) + return m_parent->GetPreferredDisplayLanguage(); + return lldb::eLanguageTypeUnknown; + } else + return m_preferred_display_language; +} + +bool ValueObjectDynamicValue::IsSyntheticChildrenGenerated() { + if (m_parent) + return m_parent->IsSyntheticChildrenGenerated(); + return false; +} + +void ValueObjectDynamicValue::SetSyntheticChildrenGenerated(bool b) { + if (m_parent) + m_parent->SetSyntheticChildrenGenerated(b); + this->ValueObject::SetSyntheticChildrenGenerated(b); +} + +bool ValueObjectDynamicValue::GetDeclaration(Declaration &decl) { + if (m_parent) + return m_parent->GetDeclaration(decl); + + return ValueObject::GetDeclaration(decl); +} + +uint64_t ValueObjectDynamicValue::GetLanguageFlags() { + if (m_parent) + return m_parent->GetLanguageFlags(); + return this->ValueObject::GetLanguageFlags(); +} + +void ValueObjectDynamicValue::SetLanguageFlags(uint64_t flags) { + if (m_parent) + m_parent->SetLanguageFlags(flags); + else + this->ValueObject::SetLanguageFlags(flags); +} diff --git a/contrib/llvm-project/lldb/source/Core/ValueObjectList.cpp b/contrib/llvm-project/lldb/source/Core/ValueObjectList.cpp new file mode 100644 index 000000000000..28907261f0a6 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/ValueObjectList.cpp @@ -0,0 +1,109 @@ +//===-- ValueObjectList.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/Core/ValueObjectList.h" + +#include "lldb/Core/ValueObject.h" +#include "lldb/Utility/ConstString.h" + +#include <utility> + +using namespace lldb; +using namespace lldb_private; + +const ValueObjectList &ValueObjectList::operator=(const ValueObjectList &rhs) { + if (this != &rhs) + m_value_objects = rhs.m_value_objects; + return *this; +} + +void ValueObjectList::Append(const ValueObjectSP &val_obj_sp) { + m_value_objects.push_back(val_obj_sp); +} + +void ValueObjectList::Append(const ValueObjectList &valobj_list) { + std::copy(valobj_list.m_value_objects.begin(), // source begin + valobj_list.m_value_objects.end(), // source end + back_inserter(m_value_objects)); // destination +} + +size_t ValueObjectList::GetSize() const { return m_value_objects.size(); } + +void ValueObjectList::Resize(size_t size) { m_value_objects.resize(size); } + +lldb::ValueObjectSP ValueObjectList::GetValueObjectAtIndex(size_t idx) { + lldb::ValueObjectSP valobj_sp; + if (idx < m_value_objects.size()) + valobj_sp = m_value_objects[idx]; + return valobj_sp; +} + +lldb::ValueObjectSP ValueObjectList::RemoveValueObjectAtIndex(size_t idx) { + lldb::ValueObjectSP valobj_sp; + if (idx < m_value_objects.size()) { + valobj_sp = m_value_objects[idx]; + m_value_objects.erase(m_value_objects.begin() + idx); + } + return valobj_sp; +} + +void ValueObjectList::SetValueObjectAtIndex(size_t idx, + const ValueObjectSP &valobj_sp) { + if (idx >= m_value_objects.size()) + m_value_objects.resize(idx + 1); + m_value_objects[idx] = valobj_sp; +} + +ValueObjectSP ValueObjectList::FindValueObjectByValueName(const char *name) { + ConstString name_const_str(name); + ValueObjectSP val_obj_sp; + collection::iterator pos, end = m_value_objects.end(); + for (pos = m_value_objects.begin(); pos != end; ++pos) { + ValueObject *valobj = (*pos).get(); + if (valobj && valobj->GetName() == name_const_str) { + val_obj_sp = *pos; + break; + } + } + return val_obj_sp; +} + +ValueObjectSP ValueObjectList::FindValueObjectByUID(lldb::user_id_t uid) { + ValueObjectSP valobj_sp; + collection::iterator pos, end = m_value_objects.end(); + + for (pos = m_value_objects.begin(); pos != end; ++pos) { + // Watch out for NULL objects in our list as the list might get resized to + // a specific size and lazily filled in + ValueObject *valobj = (*pos).get(); + if (valobj && valobj->GetID() == uid) { + valobj_sp = *pos; + break; + } + } + return valobj_sp; +} + +ValueObjectSP +ValueObjectList::FindValueObjectByPointer(ValueObject *find_valobj) { + ValueObjectSP valobj_sp; + collection::iterator pos, end = m_value_objects.end(); + + for (pos = m_value_objects.begin(); pos != end; ++pos) { + ValueObject *valobj = (*pos).get(); + if (valobj && valobj == find_valobj) { + valobj_sp = *pos; + break; + } + } + return valobj_sp; +} + +void ValueObjectList::Swap(ValueObjectList &value_object_list) { + m_value_objects.swap(value_object_list.m_value_objects); +} diff --git a/contrib/llvm-project/lldb/source/Core/ValueObjectMemory.cpp b/contrib/llvm-project/lldb/source/Core/ValueObjectMemory.cpp new file mode 100644 index 000000000000..f555ab82f441 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/ValueObjectMemory.cpp @@ -0,0 +1,236 @@ +//===-- ValueObjectMemory.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/Core/ValueObjectMemory.h" +#include "lldb/Core/Value.h" +#include "lldb/Core/ValueObject.h" +#include "lldb/Symbol/Type.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/DataExtractor.h" +#include "lldb/Utility/Scalar.h" +#include "lldb/Utility/Status.h" +#include "lldb/lldb-types.h" +#include "llvm/Support/ErrorHandling.h" + +#include <cassert> +#include <memory> +#include <optional> + +namespace lldb_private { +class ExecutionContextScope; +} + +using namespace lldb; +using namespace lldb_private; + +ValueObjectSP ValueObjectMemory::Create(ExecutionContextScope *exe_scope, + llvm::StringRef name, + const Address &address, + lldb::TypeSP &type_sp) { + auto manager_sp = ValueObjectManager::Create(); + return (new ValueObjectMemory(exe_scope, *manager_sp, name, address, type_sp)) + ->GetSP(); +} + +ValueObjectSP ValueObjectMemory::Create(ExecutionContextScope *exe_scope, + llvm::StringRef name, + const Address &address, + const CompilerType &ast_type) { + auto manager_sp = ValueObjectManager::Create(); + return (new ValueObjectMemory(exe_scope, *manager_sp, name, address, + ast_type)) + ->GetSP(); +} + +ValueObjectMemory::ValueObjectMemory(ExecutionContextScope *exe_scope, + ValueObjectManager &manager, + llvm::StringRef name, + const Address &address, + lldb::TypeSP &type_sp) + : ValueObject(exe_scope, manager), m_address(address), m_type_sp(type_sp), + m_compiler_type() { + // Do not attempt to construct one of these objects with no variable! + assert(m_type_sp.get() != nullptr); + SetName(ConstString(name)); + m_value.SetContext(Value::ContextType::LLDBType, m_type_sp.get()); + TargetSP target_sp(GetTargetSP()); + lldb::addr_t load_address = m_address.GetLoadAddress(target_sp.get()); + if (load_address != LLDB_INVALID_ADDRESS) { + m_value.SetValueType(Value::ValueType::LoadAddress); + m_value.GetScalar() = load_address; + } else { + lldb::addr_t file_address = m_address.GetFileAddress(); + if (file_address != LLDB_INVALID_ADDRESS) { + m_value.SetValueType(Value::ValueType::FileAddress); + m_value.GetScalar() = file_address; + } else { + m_value.GetScalar() = m_address.GetOffset(); + m_value.SetValueType(Value::ValueType::Scalar); + } + } +} + +ValueObjectMemory::ValueObjectMemory(ExecutionContextScope *exe_scope, + ValueObjectManager &manager, + llvm::StringRef name, + const Address &address, + const CompilerType &ast_type) + : ValueObject(exe_scope, manager), m_address(address), m_type_sp(), + m_compiler_type(ast_type) { + // Do not attempt to construct one of these objects with no variable! + assert(m_compiler_type.IsValid()); + + TargetSP target_sp(GetTargetSP()); + + SetName(ConstString(name)); + m_value.SetCompilerType(m_compiler_type); + lldb::addr_t load_address = m_address.GetLoadAddress(target_sp.get()); + if (load_address != LLDB_INVALID_ADDRESS) { + m_value.SetValueType(Value::ValueType::LoadAddress); + m_value.GetScalar() = load_address; + } else { + lldb::addr_t file_address = m_address.GetFileAddress(); + if (file_address != LLDB_INVALID_ADDRESS) { + m_value.SetValueType(Value::ValueType::FileAddress); + m_value.GetScalar() = file_address; + } else { + m_value.GetScalar() = m_address.GetOffset(); + m_value.SetValueType(Value::ValueType::Scalar); + } + } +} + +ValueObjectMemory::~ValueObjectMemory() = default; + +CompilerType ValueObjectMemory::GetCompilerTypeImpl() { + if (m_type_sp) + return m_type_sp->GetForwardCompilerType(); + return m_compiler_type; +} + +ConstString ValueObjectMemory::GetTypeName() { + if (m_type_sp) + return m_type_sp->GetName(); + return m_compiler_type.GetTypeName(); +} + +ConstString ValueObjectMemory::GetDisplayTypeName() { + if (m_type_sp) + return m_type_sp->GetForwardCompilerType().GetDisplayTypeName(); + return m_compiler_type.GetDisplayTypeName(); +} + +llvm::Expected<uint32_t> ValueObjectMemory::CalculateNumChildren(uint32_t max) { + if (m_type_sp) { + auto child_count = m_type_sp->GetNumChildren(true); + if (!child_count) + return child_count; + return *child_count <= max ? *child_count : max; + } + + ExecutionContext exe_ctx(GetExecutionContextRef()); + const bool omit_empty_base_classes = true; + auto child_count = + m_compiler_type.GetNumChildren(omit_empty_base_classes, &exe_ctx); + if (!child_count) + return child_count; + return *child_count <= max ? *child_count : max; +} + +std::optional<uint64_t> ValueObjectMemory::GetByteSize() { + ExecutionContext exe_ctx(GetExecutionContextRef()); + if (m_type_sp) + return m_type_sp->GetByteSize(exe_ctx.GetBestExecutionContextScope()); + return m_compiler_type.GetByteSize(exe_ctx.GetBestExecutionContextScope()); +} + +lldb::ValueType ValueObjectMemory::GetValueType() const { + // RETHINK: Should this be inherited from somewhere? + return lldb::eValueTypeVariableGlobal; +} + +bool ValueObjectMemory::UpdateValue() { + SetValueIsValid(false); + m_error.Clear(); + + ExecutionContext exe_ctx(GetExecutionContextRef()); + + Target *target = exe_ctx.GetTargetPtr(); + if (target) { + m_data.SetByteOrder(target->GetArchitecture().GetByteOrder()); + m_data.SetAddressByteSize(target->GetArchitecture().GetAddressByteSize()); + } + + Value old_value(m_value); + if (m_address.IsValid()) { + Value::ValueType value_type = m_value.GetValueType(); + + switch (value_type) { + case Value::ValueType::Invalid: + m_error.SetErrorString("Invalid value"); + return false; + case Value::ValueType::Scalar: + // The variable value is in the Scalar value inside the m_value. We can + // point our m_data right to it. + m_error = m_value.GetValueAsData(&exe_ctx, m_data, GetModule().get()); + break; + + case Value::ValueType::FileAddress: + case Value::ValueType::LoadAddress: + case Value::ValueType::HostAddress: + // The DWARF expression result was an address in the inferior process. If + // this variable is an aggregate type, we just need the address as the + // main value as all child variable objects will rely upon this location + // and add an offset and then read their own values as needed. If this + // variable is a simple type, we read all data for it into m_data. Make + // sure this type has a value before we try and read it + + // If we have a file address, convert it to a load address if we can. + if (value_type == Value::ValueType::FileAddress && + exe_ctx.GetProcessPtr()) { + lldb::addr_t load_addr = m_address.GetLoadAddress(target); + if (load_addr != LLDB_INVALID_ADDRESS) { + m_value.SetValueType(Value::ValueType::LoadAddress); + m_value.GetScalar() = load_addr; + } + } + + if (!CanProvideValue()) { + // this value object represents an aggregate type whose children have + // values, but this object does not. So we say we are changed if our + // location has changed. + SetValueDidChange(value_type != old_value.GetValueType() || + m_value.GetScalar() != old_value.GetScalar()); + } else { + // Copy the Value and set the context to use our Variable so it can + // extract read its value into m_data appropriately + Value value(m_value); + if (m_type_sp) + value.SetContext(Value::ContextType::LLDBType, m_type_sp.get()); + else { + value.SetCompilerType(m_compiler_type); + } + + m_error = value.GetValueAsData(&exe_ctx, m_data, GetModule().get()); + } + break; + } + + SetValueIsValid(m_error.Success()); + } + return m_error.Success(); +} + +bool ValueObjectMemory::IsInScope() { + // FIXME: Maybe try to read the memory address, and if that works, then + // we are in scope? + return true; +} + +lldb::ModuleSP ValueObjectMemory::GetModule() { return m_address.GetModule(); } diff --git a/contrib/llvm-project/lldb/source/Core/ValueObjectRegister.cpp b/contrib/llvm-project/lldb/source/Core/ValueObjectRegister.cpp new file mode 100644 index 000000000000..8b5557a0178b --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/ValueObjectRegister.cpp @@ -0,0 +1,306 @@ +//===-- ValueObjectRegister.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/Core/ValueObjectRegister.h" + +#include "lldb/Core/Module.h" +#include "lldb/Core/Value.h" +#include "lldb/Symbol/CompilerType.h" +#include "lldb/Symbol/TypeSystem.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/DataExtractor.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Scalar.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/Stream.h" + +#include "llvm/ADT/StringRef.h" + +#include <cassert> +#include <memory> +#include <optional> + +namespace lldb_private { +class ExecutionContextScope; +} + +using namespace lldb; +using namespace lldb_private; + +#pragma mark ValueObjectRegisterSet + +ValueObjectSP +ValueObjectRegisterSet::Create(ExecutionContextScope *exe_scope, + lldb::RegisterContextSP ®_ctx_sp, + uint32_t set_idx) { + auto manager_sp = ValueObjectManager::Create(); + return (new ValueObjectRegisterSet(exe_scope, *manager_sp, reg_ctx_sp, + set_idx)) + ->GetSP(); +} + +ValueObjectRegisterSet::ValueObjectRegisterSet(ExecutionContextScope *exe_scope, + ValueObjectManager &manager, + lldb::RegisterContextSP ®_ctx, + uint32_t reg_set_idx) + : ValueObject(exe_scope, manager), m_reg_ctx_sp(reg_ctx), + m_reg_set(nullptr), m_reg_set_idx(reg_set_idx) { + assert(reg_ctx); + m_reg_set = reg_ctx->GetRegisterSet(m_reg_set_idx); + if (m_reg_set) { + m_name.SetCString(m_reg_set->name); + } +} + +ValueObjectRegisterSet::~ValueObjectRegisterSet() = default; + +CompilerType ValueObjectRegisterSet::GetCompilerTypeImpl() { + return CompilerType(); +} + +ConstString ValueObjectRegisterSet::GetTypeName() { return ConstString(); } + +ConstString ValueObjectRegisterSet::GetQualifiedTypeName() { + return ConstString(); +} + +llvm::Expected<uint32_t> +ValueObjectRegisterSet::CalculateNumChildren(uint32_t max) { + const RegisterSet *reg_set = m_reg_ctx_sp->GetRegisterSet(m_reg_set_idx); + if (reg_set) { + auto reg_count = reg_set->num_registers; + return reg_count <= max ? reg_count : max; + } + return 0; +} + +std::optional<uint64_t> ValueObjectRegisterSet::GetByteSize() { return 0; } + +bool ValueObjectRegisterSet::UpdateValue() { + m_error.Clear(); + SetValueDidChange(false); + ExecutionContext exe_ctx(GetExecutionContextRef()); + StackFrame *frame = exe_ctx.GetFramePtr(); + if (frame == nullptr) + m_reg_ctx_sp.reset(); + else { + m_reg_ctx_sp = frame->GetRegisterContext(); + if (m_reg_ctx_sp) { + const RegisterSet *reg_set = m_reg_ctx_sp->GetRegisterSet(m_reg_set_idx); + if (reg_set == nullptr) + m_reg_ctx_sp.reset(); + else if (m_reg_set != reg_set) { + SetValueDidChange(true); + m_name.SetCString(reg_set->name); + } + } + } + if (m_reg_ctx_sp) { + SetValueIsValid(true); + } else { + SetValueIsValid(false); + m_error.SetErrorToGenericError(); + m_children.Clear(); + } + return m_error.Success(); +} + +ValueObject *ValueObjectRegisterSet::CreateChildAtIndex(size_t idx) { + if (m_reg_ctx_sp && m_reg_set) { + return new ValueObjectRegister( + *this, m_reg_ctx_sp, + m_reg_ctx_sp->GetRegisterInfoAtIndex(m_reg_set->registers[idx])); + } + return nullptr; +} + +lldb::ValueObjectSP +ValueObjectRegisterSet::GetChildMemberWithName(llvm::StringRef name, + bool can_create) { + ValueObject *valobj = nullptr; + if (m_reg_ctx_sp && m_reg_set) { + const RegisterInfo *reg_info = m_reg_ctx_sp->GetRegisterInfoByName(name); + if (reg_info != nullptr) + valobj = new ValueObjectRegister(*this, m_reg_ctx_sp, reg_info); + } + if (valobj) + return valobj->GetSP(); + else + return ValueObjectSP(); +} + +size_t ValueObjectRegisterSet::GetIndexOfChildWithName(llvm::StringRef name) { + if (m_reg_ctx_sp && m_reg_set) { + const RegisterInfo *reg_info = m_reg_ctx_sp->GetRegisterInfoByName(name); + if (reg_info != nullptr) + return reg_info->kinds[eRegisterKindLLDB]; + } + return UINT32_MAX; +} + +#pragma mark - +#pragma mark ValueObjectRegister + +void ValueObjectRegister::ConstructObject(const RegisterInfo *reg_info) { + if (reg_info) { + m_reg_info = *reg_info; + if (reg_info->name) + m_name.SetCString(reg_info->name); + else if (reg_info->alt_name) + m_name.SetCString(reg_info->alt_name); + } +} + +ValueObjectRegister::ValueObjectRegister(ValueObject &parent, + lldb::RegisterContextSP ®_ctx_sp, + const RegisterInfo *reg_info) + : ValueObject(parent), m_reg_ctx_sp(reg_ctx_sp), m_reg_info(), + m_reg_value(), m_type_name(), m_compiler_type() { + assert(reg_ctx_sp.get()); + ConstructObject(reg_info); +} + +ValueObjectSP ValueObjectRegister::Create(ExecutionContextScope *exe_scope, + lldb::RegisterContextSP ®_ctx_sp, + const RegisterInfo *reg_info) { + auto manager_sp = ValueObjectManager::Create(); + return (new ValueObjectRegister(exe_scope, *manager_sp, reg_ctx_sp, reg_info)) + ->GetSP(); +} + +ValueObjectRegister::ValueObjectRegister(ExecutionContextScope *exe_scope, + ValueObjectManager &manager, + lldb::RegisterContextSP ®_ctx, + const RegisterInfo *reg_info) + : ValueObject(exe_scope, manager), m_reg_ctx_sp(reg_ctx), m_reg_info(), + m_reg_value(), m_type_name(), m_compiler_type() { + assert(reg_ctx); + ConstructObject(reg_info); +} + +ValueObjectRegister::~ValueObjectRegister() = default; + +CompilerType ValueObjectRegister::GetCompilerTypeImpl() { + if (!m_compiler_type.IsValid()) { + ExecutionContext exe_ctx(GetExecutionContextRef()); + if (auto *target = exe_ctx.GetTargetPtr()) { + if (auto *exe_module = target->GetExecutableModulePointer()) { + auto type_system_or_err = + exe_module->GetTypeSystemForLanguage(eLanguageTypeC); + if (auto err = type_system_or_err.takeError()) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Types), std::move(err), + "Unable to get CompilerType from TypeSystem: {0}"); + } else { + if (auto ts = *type_system_or_err) + m_compiler_type = ts->GetBuiltinTypeForEncodingAndBitSize( + m_reg_info.encoding, m_reg_info.byte_size * 8); + } + } + } + } + return m_compiler_type; +} + +ConstString ValueObjectRegister::GetTypeName() { + if (m_type_name.IsEmpty()) + m_type_name = GetCompilerType().GetTypeName(); + return m_type_name; +} + +llvm::Expected<uint32_t> +ValueObjectRegister::CalculateNumChildren(uint32_t max) { + ExecutionContext exe_ctx(GetExecutionContextRef()); + auto children_count = GetCompilerType().GetNumChildren(true, &exe_ctx); + if (!children_count) + return children_count; + return *children_count <= max ? *children_count : max; +} + +std::optional<uint64_t> ValueObjectRegister::GetByteSize() { + return m_reg_info.byte_size; +} + +bool ValueObjectRegister::UpdateValue() { + m_error.Clear(); + ExecutionContext exe_ctx(GetExecutionContextRef()); + StackFrame *frame = exe_ctx.GetFramePtr(); + if (frame == nullptr) { + m_reg_ctx_sp.reset(); + m_reg_value.Clear(); + } + + if (m_reg_ctx_sp) { + RegisterValue m_old_reg_value(m_reg_value); + if (m_reg_ctx_sp->ReadRegister(&m_reg_info, m_reg_value)) { + if (m_reg_value.GetData(m_data)) { + Process *process = exe_ctx.GetProcessPtr(); + if (process) + m_data.SetAddressByteSize(process->GetAddressByteSize()); + m_value.SetContext(Value::ContextType::RegisterInfo, + (void *)&m_reg_info); + m_value.SetValueType(Value::ValueType::HostAddress); + m_value.GetScalar() = (uintptr_t)m_data.GetDataStart(); + SetValueIsValid(true); + SetValueDidChange(!(m_old_reg_value == m_reg_value)); + return true; + } + } + } + + SetValueIsValid(false); + m_error.SetErrorToGenericError(); + return false; +} + +bool ValueObjectRegister::SetValueFromCString(const char *value_str, + Status &error) { + // The new value will be in the m_data. Copy that into our register value. + error = + m_reg_value.SetValueFromString(&m_reg_info, llvm::StringRef(value_str)); + if (!error.Success()) + return false; + + if (!m_reg_ctx_sp->WriteRegister(&m_reg_info, m_reg_value)) { + error.SetErrorString("unable to write back to register"); + return false; + } + + SetNeedsUpdate(); + return true; +} + +bool ValueObjectRegister::SetData(DataExtractor &data, Status &error) { + error = m_reg_value.SetValueFromData(m_reg_info, data, 0, false); + if (!error.Success()) + return false; + + if (!m_reg_ctx_sp->WriteRegister(&m_reg_info, m_reg_value)) { + error.SetErrorString("unable to write back to register"); + return false; + } + + SetNeedsUpdate(); + return true; +} + +bool ValueObjectRegister::ResolveValue(Scalar &scalar) { + if (UpdateValueIfNeeded( + false)) // make sure that you are up to date before returning anything + return m_reg_value.GetScalarValue(scalar); + return false; +} + +void ValueObjectRegister::GetExpressionPath(Stream &s, + GetExpressionPathFormat epformat) { + s.Printf("$%s", m_reg_info.name); +} diff --git a/contrib/llvm-project/lldb/source/Core/ValueObjectSyntheticFilter.cpp b/contrib/llvm-project/lldb/source/Core/ValueObjectSyntheticFilter.cpp new file mode 100644 index 000000000000..adac1b400705 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/ValueObjectSyntheticFilter.cpp @@ -0,0 +1,440 @@ +//===-- ValueObjectSyntheticFilter.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/Core/ValueObjectSyntheticFilter.h" + +#include "lldb/Core/Value.h" +#include "lldb/Core/ValueObject.h" +#include "lldb/DataFormatters/TypeSynthetic.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Status.h" + +#include "llvm/ADT/STLExtras.h" +#include <optional> + +namespace lldb_private { +class Declaration; +} + +using namespace lldb_private; + +class DummySyntheticFrontEnd : public SyntheticChildrenFrontEnd { +public: + DummySyntheticFrontEnd(ValueObject &backend) + : SyntheticChildrenFrontEnd(backend) {} + + llvm::Expected<uint32_t> CalculateNumChildren() override { + return m_backend.GetNumChildren(); + } + + lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override { + return m_backend.GetChildAtIndex(idx); + } + + size_t GetIndexOfChildWithName(ConstString name) override { + return m_backend.GetIndexOfChildWithName(name); + } + + bool MightHaveChildren() override { return m_backend.MightHaveChildren(); } + + lldb::ChildCacheState Update() override { + return lldb::ChildCacheState::eRefetch; + } +}; + +ValueObjectSynthetic::ValueObjectSynthetic(ValueObject &parent, + lldb::SyntheticChildrenSP filter) + : ValueObject(parent), m_synth_sp(std::move(filter)), m_children_byindex(), + m_name_toindex(), m_synthetic_children_cache(), + m_synthetic_children_count(UINT32_MAX), + m_parent_type_name(parent.GetTypeName()), + m_might_have_children(eLazyBoolCalculate), + m_provides_value(eLazyBoolCalculate) { + SetName(parent.GetName()); + // Copying the data of an incomplete type won't work as it has no byte size. + if (m_parent->GetCompilerType().IsCompleteType()) + CopyValueData(m_parent); + CreateSynthFilter(); +} + +ValueObjectSynthetic::~ValueObjectSynthetic() = default; + +CompilerType ValueObjectSynthetic::GetCompilerTypeImpl() { + return m_parent->GetCompilerType(); +} + +ConstString ValueObjectSynthetic::GetTypeName() { + return m_parent->GetTypeName(); +} + +ConstString ValueObjectSynthetic::GetQualifiedTypeName() { + return m_parent->GetQualifiedTypeName(); +} + +ConstString ValueObjectSynthetic::GetDisplayTypeName() { + if (ConstString synth_name = m_synth_filter_up->GetSyntheticTypeName()) + return synth_name; + + return m_parent->GetDisplayTypeName(); +} + +llvm::Expected<uint32_t> +ValueObjectSynthetic::CalculateNumChildren(uint32_t max) { + Log *log = GetLog(LLDBLog::DataFormatters); + + UpdateValueIfNeeded(); + if (m_synthetic_children_count < UINT32_MAX) + return m_synthetic_children_count <= max ? m_synthetic_children_count : max; + + if (max < UINT32_MAX) { + auto num_children = m_synth_filter_up->CalculateNumChildren(max); + LLDB_LOGF(log, + "[ValueObjectSynthetic::CalculateNumChildren] for VO of name " + "%s and type %s, the filter returned %u child values", + GetName().AsCString(), GetTypeName().AsCString(), + num_children ? *num_children : 0); + return num_children; + } else { + auto num_children_or_err = m_synth_filter_up->CalculateNumChildren(max); + if (!num_children_or_err) { + m_synthetic_children_count = 0; + return num_children_or_err; + } + auto num_children = (m_synthetic_children_count = *num_children_or_err); + LLDB_LOGF(log, + "[ValueObjectSynthetic::CalculateNumChildren] for VO of name " + "%s and type %s, the filter returned %u child values", + GetName().AsCString(), GetTypeName().AsCString(), num_children); + return num_children; + } +} + +lldb::ValueObjectSP +ValueObjectSynthetic::GetDynamicValue(lldb::DynamicValueType valueType) { + if (!m_parent) + return lldb::ValueObjectSP(); + if (IsDynamic() && GetDynamicValueType() == valueType) + return GetSP(); + return m_parent->GetDynamicValue(valueType); +} + +bool ValueObjectSynthetic::MightHaveChildren() { + if (m_might_have_children == eLazyBoolCalculate) + m_might_have_children = + (m_synth_filter_up->MightHaveChildren() ? eLazyBoolYes : eLazyBoolNo); + return (m_might_have_children != eLazyBoolNo); +} + +std::optional<uint64_t> ValueObjectSynthetic::GetByteSize() { + return m_parent->GetByteSize(); +} + +lldb::ValueType ValueObjectSynthetic::GetValueType() const { + return m_parent->GetValueType(); +} + +void ValueObjectSynthetic::CreateSynthFilter() { + ValueObject *valobj_for_frontend = m_parent; + if (m_synth_sp->WantsDereference()) + { + CompilerType type = m_parent->GetCompilerType(); + if (type.IsValid() && type.IsPointerOrReferenceType()) + { + Status error; + lldb::ValueObjectSP deref_sp = m_parent->Dereference(error); + if (error.Success()) + valobj_for_frontend = deref_sp.get(); + } + } + m_synth_filter_up = (m_synth_sp->GetFrontEnd(*valobj_for_frontend)); + if (!m_synth_filter_up) + m_synth_filter_up = std::make_unique<DummySyntheticFrontEnd>(*m_parent); +} + +bool ValueObjectSynthetic::UpdateValue() { + Log *log = GetLog(LLDBLog::DataFormatters); + + SetValueIsValid(false); + m_error.Clear(); + + if (!m_parent->UpdateValueIfNeeded(false)) { + // our parent could not update.. as we are meaningless without a parent, + // just stop + if (m_parent->GetError().Fail()) + m_error = m_parent->GetError(); + return false; + } + + // Regenerate the synthetic filter if our typename changes. When the (dynamic) + // type of an object changes, so does their synthetic filter of choice. + ConstString new_parent_type_name = m_parent->GetTypeName(); + if (new_parent_type_name != m_parent_type_name) { + LLDB_LOGF(log, + "[ValueObjectSynthetic::UpdateValue] name=%s, type changed " + "from %s to %s, recomputing synthetic filter", + GetName().AsCString(), m_parent_type_name.AsCString(), + new_parent_type_name.AsCString()); + m_parent_type_name = new_parent_type_name; + CreateSynthFilter(); + } + + // let our backend do its update + if (m_synth_filter_up->Update() == lldb::ChildCacheState::eRefetch) { + LLDB_LOGF(log, + "[ValueObjectSynthetic::UpdateValue] name=%s, synthetic " + "filter said caches are stale - clearing", + GetName().AsCString()); + // filter said that cached values are stale + { + std::lock_guard<std::mutex> guard(m_child_mutex); + m_children_byindex.clear(); + m_name_toindex.clear(); + } + // usually, an object's value can change but this does not alter its + // children count for a synthetic VO that might indeed happen, so we need + // to tell the upper echelons that they need to come back to us asking for + // children + m_flags.m_children_count_valid = false; + { + std::lock_guard<std::mutex> guard(m_child_mutex); + m_synthetic_children_cache.clear(); + } + m_synthetic_children_count = UINT32_MAX; + m_might_have_children = eLazyBoolCalculate; + } else { + LLDB_LOGF(log, + "[ValueObjectSynthetic::UpdateValue] name=%s, synthetic " + "filter said caches are still valid", + GetName().AsCString()); + } + + m_provides_value = eLazyBoolCalculate; + + lldb::ValueObjectSP synth_val(m_synth_filter_up->GetSyntheticValue()); + + if (synth_val && synth_val->CanProvideValue()) { + LLDB_LOGF(log, + "[ValueObjectSynthetic::UpdateValue] name=%s, synthetic " + "filter said it can provide a value", + GetName().AsCString()); + + m_provides_value = eLazyBoolYes; + CopyValueData(synth_val.get()); + } else { + LLDB_LOGF(log, + "[ValueObjectSynthetic::UpdateValue] name=%s, synthetic " + "filter said it will not provide a value", + GetName().AsCString()); + + m_provides_value = eLazyBoolNo; + // Copying the data of an incomplete type won't work as it has no byte size. + if (m_parent->GetCompilerType().IsCompleteType()) + CopyValueData(m_parent); + } + + SetValueIsValid(true); + return true; +} + +lldb::ValueObjectSP ValueObjectSynthetic::GetChildAtIndex(uint32_t idx, + bool can_create) { + Log *log = GetLog(LLDBLog::DataFormatters); + + LLDB_LOGF(log, + "[ValueObjectSynthetic::GetChildAtIndex] name=%s, retrieving " + "child at index %u", + GetName().AsCString(), idx); + + UpdateValueIfNeeded(); + + ValueObject *valobj; + bool child_is_cached; + { + std::lock_guard<std::mutex> guard(m_child_mutex); + auto cached_child_it = m_children_byindex.find(idx); + child_is_cached = cached_child_it != m_children_byindex.end(); + if (child_is_cached) + valobj = cached_child_it->second; + } + + if (!child_is_cached) { + if (can_create && m_synth_filter_up != nullptr) { + LLDB_LOGF(log, + "[ValueObjectSynthetic::GetChildAtIndex] name=%s, child at " + "index %u not cached and will be created", + GetName().AsCString(), idx); + + lldb::ValueObjectSP synth_guy = m_synth_filter_up->GetChildAtIndex(idx); + + LLDB_LOGF( + log, + "[ValueObjectSynthetic::GetChildAtIndex] name=%s, child at index " + "%u created as %p (is " + "synthetic: %s)", + GetName().AsCString(), idx, static_cast<void *>(synth_guy.get()), + synth_guy.get() + ? (synth_guy->IsSyntheticChildrenGenerated() ? "yes" : "no") + : "no"); + + if (!synth_guy) + return synth_guy; + + { + std::lock_guard<std::mutex> guard(m_child_mutex); + if (synth_guy->IsSyntheticChildrenGenerated()) + m_synthetic_children_cache.push_back(synth_guy); + m_children_byindex[idx] = synth_guy.get(); + } + synth_guy->SetPreferredDisplayLanguageIfNeeded( + GetPreferredDisplayLanguage()); + return synth_guy; + } else { + LLDB_LOGF(log, + "[ValueObjectSynthetic::GetChildAtIndex] name=%s, child at " + "index %u not cached and cannot " + "be created (can_create = %s, synth_filter = %p)", + GetName().AsCString(), idx, can_create ? "yes" : "no", + static_cast<void *>(m_synth_filter_up.get())); + + return lldb::ValueObjectSP(); + } + } else { + LLDB_LOGF(log, + "[ValueObjectSynthetic::GetChildAtIndex] name=%s, child at " + "index %u cached as %p", + GetName().AsCString(), idx, static_cast<void *>(valobj)); + + return valobj->GetSP(); + } +} + +lldb::ValueObjectSP +ValueObjectSynthetic::GetChildMemberWithName(llvm::StringRef name, + bool can_create) { + UpdateValueIfNeeded(); + + uint32_t index = GetIndexOfChildWithName(name); + + if (index == UINT32_MAX) + return lldb::ValueObjectSP(); + + return GetChildAtIndex(index, can_create); +} + +size_t ValueObjectSynthetic::GetIndexOfChildWithName(llvm::StringRef name_ref) { + UpdateValueIfNeeded(); + + ConstString name(name_ref); + + uint32_t found_index = UINT32_MAX; + bool did_find; + { + std::lock_guard<std::mutex> guard(m_child_mutex); + auto name_to_index = m_name_toindex.find(name.GetCString()); + did_find = name_to_index != m_name_toindex.end(); + if (did_find) + found_index = name_to_index->second; + } + + if (!did_find && m_synth_filter_up != nullptr) { + uint32_t index = m_synth_filter_up->GetIndexOfChildWithName(name); + if (index == UINT32_MAX) + return index; + std::lock_guard<std::mutex> guard(m_child_mutex); + m_name_toindex[name.GetCString()] = index; + return index; + } else if (!did_find && m_synth_filter_up == nullptr) + return UINT32_MAX; + else /*if (iter != m_name_toindex.end())*/ + return found_index; +} + +bool ValueObjectSynthetic::IsInScope() { return m_parent->IsInScope(); } + +lldb::ValueObjectSP ValueObjectSynthetic::GetNonSyntheticValue() { + return m_parent->GetSP(); +} + +void ValueObjectSynthetic::CopyValueData(ValueObject *source) { + m_value = (source->UpdateValueIfNeeded(), source->GetValue()); + ExecutionContext exe_ctx(GetExecutionContextRef()); + m_error = m_value.GetValueAsData(&exe_ctx, m_data, GetModule().get()); +} + +bool ValueObjectSynthetic::CanProvideValue() { + if (!UpdateValueIfNeeded()) + return false; + if (m_provides_value == eLazyBoolYes) + return true; + return m_parent->CanProvideValue(); +} + +bool ValueObjectSynthetic::SetValueFromCString(const char *value_str, + Status &error) { + return m_parent->SetValueFromCString(value_str, error); +} + +void ValueObjectSynthetic::SetFormat(lldb::Format format) { + if (m_parent) { + m_parent->ClearUserVisibleData(eClearUserVisibleDataItemsAll); + m_parent->SetFormat(format); + } + this->ValueObject::SetFormat(format); + this->ClearUserVisibleData(eClearUserVisibleDataItemsAll); +} + +void ValueObjectSynthetic::SetPreferredDisplayLanguage( + lldb::LanguageType lang) { + this->ValueObject::SetPreferredDisplayLanguage(lang); + if (m_parent) + m_parent->SetPreferredDisplayLanguage(lang); +} + +lldb::LanguageType ValueObjectSynthetic::GetPreferredDisplayLanguage() { + if (m_preferred_display_language == lldb::eLanguageTypeUnknown) { + if (m_parent) + return m_parent->GetPreferredDisplayLanguage(); + return lldb::eLanguageTypeUnknown; + } else + return m_preferred_display_language; +} + +bool ValueObjectSynthetic::IsSyntheticChildrenGenerated() { + if (m_parent) + return m_parent->IsSyntheticChildrenGenerated(); + return false; +} + +void ValueObjectSynthetic::SetSyntheticChildrenGenerated(bool b) { + if (m_parent) + m_parent->SetSyntheticChildrenGenerated(b); + this->ValueObject::SetSyntheticChildrenGenerated(b); +} + +bool ValueObjectSynthetic::GetDeclaration(Declaration &decl) { + if (m_parent) + return m_parent->GetDeclaration(decl); + + return ValueObject::GetDeclaration(decl); +} + +uint64_t ValueObjectSynthetic::GetLanguageFlags() { + if (m_parent) + return m_parent->GetLanguageFlags(); + return this->ValueObject::GetLanguageFlags(); +} + +void ValueObjectSynthetic::SetLanguageFlags(uint64_t flags) { + if (m_parent) + m_parent->SetLanguageFlags(flags); + else + this->ValueObject::SetLanguageFlags(flags); +} diff --git a/contrib/llvm-project/lldb/source/Core/ValueObjectUpdater.cpp b/contrib/llvm-project/lldb/source/Core/ValueObjectUpdater.cpp new file mode 100644 index 000000000000..af7f976a6d27 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/ValueObjectUpdater.cpp @@ -0,0 +1,56 @@ +//===-- ValueObjectUpdater.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/Core/ValueObjectUpdater.h" + +using namespace lldb_private; + +ValueObjectUpdater::ValueObjectUpdater(lldb::ValueObjectSP in_valobj_sp) { + if (!in_valobj_sp) + return; + // If the user passes in a value object that is dynamic or synthetic, then + // water it down to the static type. + m_root_valobj_sp = in_valobj_sp->GetQualifiedRepresentationIfAvailable( + lldb::eNoDynamicValues, false); +} + +lldb::ValueObjectSP ValueObjectUpdater::GetSP() { + lldb::ProcessSP process_sp = GetProcessSP(); + if (!process_sp) + return lldb::ValueObjectSP(); + + const uint32_t current_stop_id = process_sp->GetLastNaturalStopID(); + if (current_stop_id == m_stop_id) + return m_user_valobj_sp; + + m_stop_id = current_stop_id; + + if (!m_root_valobj_sp) { + m_user_valobj_sp.reset(); + return m_root_valobj_sp; + } + + m_user_valobj_sp = m_root_valobj_sp; + + lldb::ValueObjectSP dynamic_sp = + m_user_valobj_sp->GetDynamicValue(lldb::eDynamicDontRunTarget); + if (dynamic_sp) + m_user_valobj_sp = dynamic_sp; + + lldb::ValueObjectSP synthetic_sp = m_user_valobj_sp->GetSyntheticValue(); + if (synthetic_sp) + m_user_valobj_sp = synthetic_sp; + + return m_user_valobj_sp; +} + +lldb::ProcessSP ValueObjectUpdater::GetProcessSP() const { + if (m_root_valobj_sp) + return m_root_valobj_sp->GetProcessSP(); + return lldb::ProcessSP(); +} diff --git a/contrib/llvm-project/lldb/source/Core/ValueObjectVTable.cpp b/contrib/llvm-project/lldb/source/Core/ValueObjectVTable.cpp new file mode 100644 index 000000000000..72e198d08c9c --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/ValueObjectVTable.cpp @@ -0,0 +1,272 @@ +//===-- ValueObjectVTable.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/Core/ValueObjectVTable.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ValueObjectChild.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Target/Language.h" +#include "lldb/Target/LanguageRuntime.h" +#include "lldb/lldb-defines.h" +#include "lldb/lldb-enumerations.h" +#include "lldb/lldb-forward.h" +#include "lldb/lldb-private-enumerations.h" + +using namespace lldb; +using namespace lldb_private; + +class ValueObjectVTableChild : public ValueObject { +public: + ValueObjectVTableChild(ValueObject &parent, uint32_t func_idx, + uint64_t addr_size) + : ValueObject(parent), m_func_idx(func_idx), m_addr_size(addr_size) { + SetFormat(eFormatPointer); + SetName(ConstString(llvm::formatv("[{0}]", func_idx).str())); + } + + ~ValueObjectVTableChild() override = default; + + std::optional<uint64_t> GetByteSize() override { return m_addr_size; }; + + llvm::Expected<uint32_t> CalculateNumChildren(uint32_t max) override { + return 0; + }; + + ValueType GetValueType() const override { return eValueTypeVTableEntry; }; + + bool IsInScope() override { + if (ValueObject *parent = GetParent()) + return parent->IsInScope(); + return false; + }; + +protected: + bool UpdateValue() override { + SetValueIsValid(false); + m_value.Clear(); + ValueObject *parent = GetParent(); + if (!parent) { + m_error.SetErrorString("owning vtable object not valid"); + return false; + } + + addr_t parent_addr = parent->GetValueAsUnsigned(LLDB_INVALID_ADDRESS); + if (parent_addr == LLDB_INVALID_ADDRESS) { + m_error.SetErrorString("invalid vtable address"); + return false; + } + + ProcessSP process_sp = GetProcessSP(); + if (!process_sp) { + m_error.SetErrorString("no process"); + return false; + } + + TargetSP target_sp = GetTargetSP(); + if (!target_sp) { + m_error.SetErrorString("no target"); + return false; + } + + // Each `vtable_entry_addr` points to the function pointer. + addr_t vtable_entry_addr = parent_addr + m_func_idx * m_addr_size; + addr_t vfunc_ptr = + process_sp->ReadPointerFromMemory(vtable_entry_addr, m_error); + if (m_error.Fail()) { + m_error.SetErrorStringWithFormat( + "failed to read virtual function entry 0x%16.16" PRIx64, + vtable_entry_addr); + return false; + } + + + // Set our value to be the load address of the function pointer in memory + // and our type to be the function pointer type. + m_value.SetValueType(Value::ValueType::LoadAddress); + m_value.GetScalar() = vtable_entry_addr; + + // See if our resolved address points to a function in the debug info. If + // it does, then we can report the type as a function prototype for this + // function. + Function *function = nullptr; + Address resolved_vfunc_ptr_address; + target_sp->ResolveLoadAddress(vfunc_ptr, resolved_vfunc_ptr_address); + if (resolved_vfunc_ptr_address.IsValid()) + function = resolved_vfunc_ptr_address.CalculateSymbolContextFunction(); + if (function) { + m_value.SetCompilerType(function->GetCompilerType().GetPointerType()); + } else { + // Set our value's compiler type to a generic function protoype so that + // it displays as a hex function pointer for the value and the summary + // will display the address description. + + // Get the original type that this vtable is based off of so we can get + // the language from it correctly. + ValueObject *val = parent->GetParent(); + auto type_system = target_sp->GetScratchTypeSystemForLanguage( + val ? val->GetObjectRuntimeLanguage() : eLanguageTypeC_plus_plus); + if (type_system) { + m_value.SetCompilerType( + (*type_system)->CreateGenericFunctionPrototype().GetPointerType()); + } else { + consumeError(type_system.takeError()); + } + } + + // Now read our value into m_data so that our we can use the default + // summary provider for C++ for function pointers which will get the + // address description for our function pointer. + if (m_error.Success()) { + const bool thread_and_frame_only_if_stopped = true; + ExecutionContext exe_ctx( + GetExecutionContextRef().Lock(thread_and_frame_only_if_stopped)); + m_error = m_value.GetValueAsData(&exe_ctx, m_data, GetModule().get()); + } + SetValueDidChange(true); + SetValueIsValid(true); + return true; + }; + + CompilerType GetCompilerTypeImpl() override { + return m_value.GetCompilerType(); + }; + + const uint32_t m_func_idx; + const uint64_t m_addr_size; + +private: + // For ValueObject only + ValueObjectVTableChild(const ValueObjectVTableChild &) = delete; + const ValueObjectVTableChild & + operator=(const ValueObjectVTableChild &) = delete; +}; + +ValueObjectSP ValueObjectVTable::Create(ValueObject &parent) { + return (new ValueObjectVTable(parent))->GetSP(); +} + +ValueObjectVTable::ValueObjectVTable(ValueObject &parent) + : ValueObject(parent) { + SetFormat(eFormatPointer); +} + +std::optional<uint64_t> ValueObjectVTable::GetByteSize() { + if (m_vtable_symbol) + return m_vtable_symbol->GetByteSize(); + return std::nullopt; +} + +llvm::Expected<uint32_t> ValueObjectVTable::CalculateNumChildren(uint32_t max) { + if (UpdateValueIfNeeded(false)) + return m_num_vtable_entries <= max ? m_num_vtable_entries : max; + return 0; +} + +ValueType ValueObjectVTable::GetValueType() const { return eValueTypeVTable; } + +ConstString ValueObjectVTable::GetTypeName() { + if (m_vtable_symbol) + return m_vtable_symbol->GetName(); + return ConstString(); +} + +ConstString ValueObjectVTable::GetQualifiedTypeName() { return GetTypeName(); } + +ConstString ValueObjectVTable::GetDisplayTypeName() { + if (m_vtable_symbol) + return m_vtable_symbol->GetDisplayName(); + return ConstString(); +} + +bool ValueObjectVTable::IsInScope() { return GetParent()->IsInScope(); } + +ValueObject *ValueObjectVTable::CreateChildAtIndex(size_t idx) { + return new ValueObjectVTableChild(*this, idx, m_addr_size); +} + +bool ValueObjectVTable::UpdateValue() { + m_error.Clear(); + m_flags.m_children_count_valid = false; + SetValueIsValid(false); + m_num_vtable_entries = 0; + ValueObject *parent = GetParent(); + if (!parent) { + m_error.SetErrorString("no parent object"); + return false; + } + + ProcessSP process_sp = GetProcessSP(); + if (!process_sp) { + m_error.SetErrorString("no process"); + return false; + } + + const LanguageType language = parent->GetObjectRuntimeLanguage(); + LanguageRuntime *language_runtime = process_sp->GetLanguageRuntime(language); + + if (language_runtime == nullptr) { + m_error.SetErrorStringWithFormat( + "no language runtime support for the language \"%s\"", + Language::GetNameForLanguageType(language)); + return false; + } + + // Get the vtable information from the language runtime. + llvm::Expected<LanguageRuntime::VTableInfo> vtable_info_or_err = + language_runtime->GetVTableInfo(*parent, /*check_type=*/true); + if (!vtable_info_or_err) { + m_error = vtable_info_or_err.takeError(); + return false; + } + + TargetSP target_sp = GetTargetSP(); + const addr_t vtable_start_addr = + vtable_info_or_err->addr.GetLoadAddress(target_sp.get()); + + m_vtable_symbol = vtable_info_or_err->symbol; + if (!m_vtable_symbol) { + m_error.SetErrorStringWithFormat( + "no vtable symbol found containing 0x%" PRIx64, vtable_start_addr); + return false; + } + + // Now that we know it's a vtable, we update the object's state. + SetName(GetTypeName()); + + // Calculate the number of entries + if (!m_vtable_symbol->GetByteSizeIsValid()) { + m_error.SetErrorStringWithFormat( + "vtable symbol \"%s\" doesn't have a valid size", + m_vtable_symbol->GetMangled().GetDemangledName().GetCString()); + return false; + } + + m_addr_size = process_sp->GetAddressByteSize(); + const addr_t vtable_end_addr = + m_vtable_symbol->GetLoadAddress(target_sp.get()) + + m_vtable_symbol->GetByteSize(); + m_num_vtable_entries = (vtable_end_addr - vtable_start_addr) / m_addr_size; + + m_value.SetValueType(Value::ValueType::LoadAddress); + m_value.GetScalar() = parent->GetAddressOf(); + auto type_system_or_err = + target_sp->GetScratchTypeSystemForLanguage(eLanguageTypeC_plus_plus); + if (type_system_or_err) { + m_value.SetCompilerType( + (*type_system_or_err)->GetBasicTypeFromAST(eBasicTypeUnsignedLong)); + } else { + consumeError(type_system_or_err.takeError()); + } + SetValueDidChange(true); + SetValueIsValid(true); + return true; +} + +CompilerType ValueObjectVTable::GetCompilerTypeImpl() { return CompilerType(); } + +ValueObjectVTable::~ValueObjectVTable() = default; diff --git a/contrib/llvm-project/lldb/source/Core/ValueObjectVariable.cpp b/contrib/llvm-project/lldb/source/Core/ValueObjectVariable.cpp new file mode 100644 index 000000000000..51eb11d3a189 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Core/ValueObjectVariable.cpp @@ -0,0 +1,422 @@ +//===-- ValueObjectVariable.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/Core/ValueObjectVariable.h" + +#include "lldb/Core/Address.h" +#include "lldb/Core/AddressRange.h" +#include "lldb/Core/Declaration.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/Value.h" +#include "lldb/Expression/DWARFExpressionList.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Symbol/SymbolContextScope.h" +#include "lldb/Symbol/Type.h" +#include "lldb/Symbol/Variable.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/DataExtractor.h" +#include "lldb/Utility/RegisterValue.h" +#include "lldb/Utility/Scalar.h" +#include "lldb/Utility/Status.h" +#include "lldb/lldb-private-enumerations.h" +#include "lldb/lldb-types.h" + +#include "llvm/ADT/StringRef.h" + +#include <cassert> +#include <memory> +#include <optional> + +namespace lldb_private { +class ExecutionContextScope; +} +namespace lldb_private { +class StackFrame; +} +namespace lldb_private { +struct RegisterInfo; +} +using namespace lldb_private; + +lldb::ValueObjectSP +ValueObjectVariable::Create(ExecutionContextScope *exe_scope, + const lldb::VariableSP &var_sp) { + auto manager_sp = ValueObjectManager::Create(); + return (new ValueObjectVariable(exe_scope, *manager_sp, var_sp))->GetSP(); +} + +ValueObjectVariable::ValueObjectVariable(ExecutionContextScope *exe_scope, + ValueObjectManager &manager, + const lldb::VariableSP &var_sp) + : ValueObject(exe_scope, manager), m_variable_sp(var_sp) { + // Do not attempt to construct one of these objects with no variable! + assert(m_variable_sp.get() != nullptr); + m_name = var_sp->GetName(); +} + +ValueObjectVariable::~ValueObjectVariable() = default; + +CompilerType ValueObjectVariable::GetCompilerTypeImpl() { + Type *var_type = m_variable_sp->GetType(); + if (var_type) + return var_type->GetForwardCompilerType(); + return CompilerType(); +} + +ConstString ValueObjectVariable::GetTypeName() { + Type *var_type = m_variable_sp->GetType(); + if (var_type) + return var_type->GetName(); + return ConstString(); +} + +ConstString ValueObjectVariable::GetDisplayTypeName() { + Type *var_type = m_variable_sp->GetType(); + if (var_type) + return var_type->GetForwardCompilerType().GetDisplayTypeName(); + return ConstString(); +} + +ConstString ValueObjectVariable::GetQualifiedTypeName() { + Type *var_type = m_variable_sp->GetType(); + if (var_type) + return var_type->GetQualifiedName(); + return ConstString(); +} + +llvm::Expected<uint32_t> +ValueObjectVariable::CalculateNumChildren(uint32_t max) { + CompilerType type(GetCompilerType()); + + if (!type.IsValid()) + return llvm::make_error<llvm::StringError>("invalid type", + llvm::inconvertibleErrorCode()); + + ExecutionContext exe_ctx(GetExecutionContextRef()); + const bool omit_empty_base_classes = true; + auto child_count = type.GetNumChildren(omit_empty_base_classes, &exe_ctx); + if (!child_count) + return child_count; + return *child_count <= max ? *child_count : max; +} + +std::optional<uint64_t> ValueObjectVariable::GetByteSize() { + ExecutionContext exe_ctx(GetExecutionContextRef()); + + CompilerType type(GetCompilerType()); + + if (!type.IsValid()) + return {}; + + return type.GetByteSize(exe_ctx.GetBestExecutionContextScope()); +} + +lldb::ValueType ValueObjectVariable::GetValueType() const { + if (m_variable_sp) + return m_variable_sp->GetScope(); + return lldb::eValueTypeInvalid; +} + +bool ValueObjectVariable::UpdateValue() { + SetValueIsValid(false); + m_error.Clear(); + + Variable *variable = m_variable_sp.get(); + DWARFExpressionList &expr_list = variable->LocationExpressionList(); + + if (variable->GetLocationIsConstantValueData()) { + // expr doesn't contain DWARF bytes, it contains the constant variable + // value bytes themselves... + if (expr_list.GetExpressionData(m_data)) { + if (m_data.GetDataStart() && m_data.GetByteSize()) + m_value.SetBytes(m_data.GetDataStart(), m_data.GetByteSize()); + m_value.SetContext(Value::ContextType::Variable, variable); + } else + m_error.SetErrorString("empty constant data"); + // constant bytes can't be edited - sorry + m_resolved_value.SetContext(Value::ContextType::Invalid, nullptr); + } else { + lldb::addr_t loclist_base_load_addr = LLDB_INVALID_ADDRESS; + ExecutionContext exe_ctx(GetExecutionContextRef()); + + Target *target = exe_ctx.GetTargetPtr(); + if (target) { + m_data.SetByteOrder(target->GetArchitecture().GetByteOrder()); + m_data.SetAddressByteSize(target->GetArchitecture().GetAddressByteSize()); + } + + if (!expr_list.IsAlwaysValidSingleExpr()) { + SymbolContext sc; + variable->CalculateSymbolContext(&sc); + if (sc.function) + loclist_base_load_addr = + sc.function->GetAddressRange().GetBaseAddress().GetLoadAddress( + target); + } + Value old_value(m_value); + llvm::Expected<Value> maybe_value = expr_list.Evaluate( + &exe_ctx, nullptr, loclist_base_load_addr, nullptr, nullptr); + + if (maybe_value) { + m_value = *maybe_value; + m_resolved_value = m_value; + m_value.SetContext(Value::ContextType::Variable, variable); + + CompilerType compiler_type = GetCompilerType(); + if (compiler_type.IsValid()) + m_value.SetCompilerType(compiler_type); + + Value::ValueType value_type = m_value.GetValueType(); + + // The size of the buffer within m_value can be less than the size + // prescribed by its type. E.g. this can happen when an expression only + // partially describes an object (say, because it contains DW_OP_piece). + // + // In this case, grow m_value to the expected size. An alternative way to + // handle this is to teach Value::GetValueAsData() and ValueObjectChild + // not to read past the end of a host buffer, but this gets impractically + // complicated as a Value's host buffer may be shared with a distant + // ancestor or sibling in the ValueObject hierarchy. + // + // FIXME: When we grow m_value, we should represent the added bits as + // undefined somehow instead of as 0's. + if (value_type == Value::ValueType::HostAddress && + compiler_type.IsValid()) { + if (size_t value_buf_size = m_value.GetBuffer().GetByteSize()) { + size_t value_size = m_value.GetValueByteSize(&m_error, &exe_ctx); + if (m_error.Success() && value_buf_size < value_size) + m_value.ResizeData(value_size); + } + } + + Process *process = exe_ctx.GetProcessPtr(); + const bool process_is_alive = process && process->IsAlive(); + + switch (value_type) { + case Value::ValueType::Invalid: + m_error.SetErrorString("invalid value"); + break; + case Value::ValueType::Scalar: + // The variable value is in the Scalar value inside the m_value. We can + // point our m_data right to it. + m_error = + m_value.GetValueAsData(&exe_ctx, m_data, GetModule().get()); + break; + + case Value::ValueType::FileAddress: + case Value::ValueType::LoadAddress: + case Value::ValueType::HostAddress: + // The DWARF expression result was an address in the inferior process. + // If this variable is an aggregate type, we just need the address as + // the main value as all child variable objects will rely upon this + // location and add an offset and then read their own values as needed. + // If this variable is a simple type, we read all data for it into + // m_data. Make sure this type has a value before we try and read it + + // If we have a file address, convert it to a load address if we can. + if (value_type == Value::ValueType::FileAddress && process_is_alive) + m_value.ConvertToLoadAddress(GetModule().get(), target); + + if (!CanProvideValue()) { + // this value object represents an aggregate type whose children have + // values, but this object does not. So we say we are changed if our + // location has changed. + SetValueDidChange(value_type != old_value.GetValueType() || + m_value.GetScalar() != old_value.GetScalar()); + } else { + // Copy the Value and set the context to use our Variable so it can + // extract read its value into m_data appropriately + Value value(m_value); + value.SetContext(Value::ContextType::Variable, variable); + m_error = + value.GetValueAsData(&exe_ctx, m_data, GetModule().get()); + + SetValueDidChange(value_type != old_value.GetValueType() || + m_value.GetScalar() != old_value.GetScalar()); + } + break; + } + + SetValueIsValid(m_error.Success()); + } else { + m_error = maybe_value.takeError(); + // could not find location, won't allow editing + m_resolved_value.SetContext(Value::ContextType::Invalid, nullptr); + } + } + + return m_error.Success(); +} + +void ValueObjectVariable::DoUpdateChildrenAddressType(ValueObject &valobj) { + Value::ValueType value_type = valobj.GetValue().GetValueType(); + ExecutionContext exe_ctx(GetExecutionContextRef()); + Process *process = exe_ctx.GetProcessPtr(); + const bool process_is_alive = process && process->IsAlive(); + const uint32_t type_info = valobj.GetCompilerType().GetTypeInfo(); + const bool is_pointer_or_ref = + (type_info & (lldb::eTypeIsPointer | lldb::eTypeIsReference)) != 0; + + switch (value_type) { + case Value::ValueType::Invalid: + break; + case Value::ValueType::FileAddress: + // If this type is a pointer, then its children will be considered load + // addresses if the pointer or reference is dereferenced, but only if + // the process is alive. + // + // There could be global variables like in the following code: + // struct LinkedListNode { Foo* foo; LinkedListNode* next; }; + // Foo g_foo1; + // Foo g_foo2; + // LinkedListNode g_second_node = { &g_foo2, NULL }; + // LinkedListNode g_first_node = { &g_foo1, &g_second_node }; + // + // When we aren't running, we should be able to look at these variables + // using the "target variable" command. Children of the "g_first_node" + // always will be of the same address type as the parent. But children + // of the "next" member of LinkedListNode will become load addresses if + // we have a live process, or remain a file address if it was a file + // address. + if (process_is_alive && is_pointer_or_ref) + valobj.SetAddressTypeOfChildren(eAddressTypeLoad); + else + valobj.SetAddressTypeOfChildren(eAddressTypeFile); + break; + case Value::ValueType::HostAddress: + // Same as above for load addresses, except children of pointer or refs + // are always load addresses. Host addresses are used to store freeze + // dried variables. If this type is a struct, the entire struct + // contents will be copied into the heap of the + // LLDB process, but we do not currently follow any pointers. + if (is_pointer_or_ref) + valobj.SetAddressTypeOfChildren(eAddressTypeLoad); + else + valobj.SetAddressTypeOfChildren(eAddressTypeHost); + break; + case Value::ValueType::LoadAddress: + case Value::ValueType::Scalar: + valobj.SetAddressTypeOfChildren(eAddressTypeLoad); + break; + } +} + + + +bool ValueObjectVariable::IsInScope() { + const ExecutionContextRef &exe_ctx_ref = GetExecutionContextRef(); + if (exe_ctx_ref.HasFrameRef()) { + ExecutionContext exe_ctx(exe_ctx_ref); + StackFrame *frame = exe_ctx.GetFramePtr(); + if (frame) { + return m_variable_sp->IsInScope(frame); + } else { + // This ValueObject had a frame at one time, but now we can't locate it, + // so return false since we probably aren't in scope. + return false; + } + } + // We have a variable that wasn't tied to a frame, which means it is a global + // and is always in scope. + return true; +} + +lldb::ModuleSP ValueObjectVariable::GetModule() { + if (m_variable_sp) { + SymbolContextScope *sc_scope = m_variable_sp->GetSymbolContextScope(); + if (sc_scope) { + return sc_scope->CalculateSymbolContextModule(); + } + } + return lldb::ModuleSP(); +} + +SymbolContextScope *ValueObjectVariable::GetSymbolContextScope() { + if (m_variable_sp) + return m_variable_sp->GetSymbolContextScope(); + return nullptr; +} + +bool ValueObjectVariable::GetDeclaration(Declaration &decl) { + if (m_variable_sp) { + decl = m_variable_sp->GetDeclaration(); + return true; + } + return false; +} + +const char *ValueObjectVariable::GetLocationAsCString() { + if (m_resolved_value.GetContextType() == Value::ContextType::RegisterInfo) + return GetLocationAsCStringImpl(m_resolved_value, m_data); + else + return ValueObject::GetLocationAsCString(); +} + +bool ValueObjectVariable::SetValueFromCString(const char *value_str, + Status &error) { + if (!UpdateValueIfNeeded()) { + error.SetErrorString("unable to update value before writing"); + return false; + } + + if (m_resolved_value.GetContextType() == Value::ContextType::RegisterInfo) { + RegisterInfo *reg_info = m_resolved_value.GetRegisterInfo(); + ExecutionContext exe_ctx(GetExecutionContextRef()); + RegisterContext *reg_ctx = exe_ctx.GetRegisterContext(); + RegisterValue reg_value; + if (!reg_info || !reg_ctx) { + error.SetErrorString("unable to retrieve register info"); + return false; + } + error = reg_value.SetValueFromString(reg_info, llvm::StringRef(value_str)); + if (error.Fail()) + return false; + if (reg_ctx->WriteRegister(reg_info, reg_value)) { + SetNeedsUpdate(); + return true; + } else { + error.SetErrorString("unable to write back to register"); + return false; + } + } else + return ValueObject::SetValueFromCString(value_str, error); +} + +bool ValueObjectVariable::SetData(DataExtractor &data, Status &error) { + if (!UpdateValueIfNeeded()) { + error.SetErrorString("unable to update value before writing"); + return false; + } + + if (m_resolved_value.GetContextType() == Value::ContextType::RegisterInfo) { + RegisterInfo *reg_info = m_resolved_value.GetRegisterInfo(); + ExecutionContext exe_ctx(GetExecutionContextRef()); + RegisterContext *reg_ctx = exe_ctx.GetRegisterContext(); + RegisterValue reg_value; + if (!reg_info || !reg_ctx) { + error.SetErrorString("unable to retrieve register info"); + return false; + } + error = reg_value.SetValueFromData(*reg_info, data, 0, true); + if (error.Fail()) + return false; + if (reg_ctx->WriteRegister(reg_info, reg_value)) { + SetNeedsUpdate(); + return true; + } else { + error.SetErrorString("unable to write back to register"); + return false; + } + } else + return ValueObject::SetData(data, error); +} |