diff options
Diffstat (limited to 'contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC')
20 files changed, 10741 insertions, 0 deletions
diff --git a/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp new file mode 100644 index 000000000000..39969520b745 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp @@ -0,0 +1,747 @@ +//===-- AppleObjCClassDescriptorV2.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 "AppleObjCClassDescriptorV2.h" + +#include "lldb/Expression/FunctionCaller.h" +#include "lldb/Target/ABI.h" +#include "lldb/Target/Language.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/lldb-enumerations.h" + +using namespace lldb; +using namespace lldb_private; + +bool ClassDescriptorV2::Read_objc_class( + Process *process, std::unique_ptr<objc_class_t> &objc_class) const { + objc_class = std::make_unique<objc_class_t>(); + + bool ret = objc_class->Read(process, m_objc_class_ptr); + + if (!ret) + objc_class.reset(); + + return ret; +} + +static lldb::addr_t GetClassDataMask(Process *process) { + switch (process->GetAddressByteSize()) { + case 4: + return 0xfffffffcUL; + case 8: + return 0x00007ffffffffff8UL; + default: + break; + } + + return LLDB_INVALID_ADDRESS; +} + +bool ClassDescriptorV2::objc_class_t::Read(Process *process, + lldb::addr_t addr) { + size_t ptr_size = process->GetAddressByteSize(); + + size_t objc_class_size = ptr_size // uintptr_t isa; + + ptr_size // Class superclass; + + ptr_size // void *cache; + + ptr_size // IMP *vtable; + + ptr_size; // uintptr_t data_NEVER_USE; + + DataBufferHeap objc_class_buf(objc_class_size, '\0'); + Status error; + + process->ReadMemory(addr, objc_class_buf.GetBytes(), objc_class_size, error); + if (error.Fail()) { + return false; + } + + DataExtractor extractor(objc_class_buf.GetBytes(), objc_class_size, + process->GetByteOrder(), + process->GetAddressByteSize()); + + lldb::offset_t cursor = 0; + + m_isa = extractor.GetAddress_unchecked(&cursor); // uintptr_t isa; + m_superclass = extractor.GetAddress_unchecked(&cursor); // Class superclass; + m_cache_ptr = extractor.GetAddress_unchecked(&cursor); // void *cache; + m_vtable_ptr = extractor.GetAddress_unchecked(&cursor); // IMP *vtable; + lldb::addr_t data_NEVER_USE = + extractor.GetAddress_unchecked(&cursor); // uintptr_t data_NEVER_USE; + + m_flags = (uint8_t)(data_NEVER_USE & (lldb::addr_t)3); + m_data_ptr = data_NEVER_USE & GetClassDataMask(process); + + if (ABISP abi_sp = process->GetABI()) { + m_isa = abi_sp->FixCodeAddress(m_isa); + m_superclass = abi_sp->FixCodeAddress(m_superclass); + m_data_ptr = abi_sp->FixCodeAddress(m_data_ptr); + } + return true; +} + +bool ClassDescriptorV2::class_rw_t::Read(Process *process, lldb::addr_t addr) { + size_t ptr_size = process->GetAddressByteSize(); + + size_t size = sizeof(uint32_t) // uint32_t flags; + + sizeof(uint32_t) // uint32_t version; + + ptr_size // const class_ro_t *ro; + + ptr_size // union { method_list_t **method_lists; + // method_list_t *method_list; }; + + ptr_size // struct chained_property_list *properties; + + ptr_size // const protocol_list_t **protocols; + + ptr_size // Class firstSubclass; + + ptr_size; // Class nextSiblingClass; + + DataBufferHeap buffer(size, '\0'); + Status error; + + process->ReadMemory(addr, buffer.GetBytes(), size, error); + if (error.Fail()) { + return false; + } + + DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(), + process->GetAddressByteSize()); + + lldb::offset_t cursor = 0; + + m_flags = extractor.GetU32_unchecked(&cursor); + m_version = extractor.GetU32_unchecked(&cursor); + m_ro_ptr = extractor.GetAddress_unchecked(&cursor); + if (ABISP abi_sp = process->GetABI()) + m_ro_ptr = abi_sp->FixCodeAddress(m_ro_ptr); + m_method_list_ptr = extractor.GetAddress_unchecked(&cursor); + m_properties_ptr = extractor.GetAddress_unchecked(&cursor); + m_firstSubclass = extractor.GetAddress_unchecked(&cursor); + m_nextSiblingClass = extractor.GetAddress_unchecked(&cursor); + + if (m_ro_ptr & 1) { + DataBufferHeap buffer(ptr_size, '\0'); + process->ReadMemory(m_ro_ptr ^ 1, buffer.GetBytes(), ptr_size, error); + if (error.Fail()) + return false; + cursor = 0; + DataExtractor extractor(buffer.GetBytes(), ptr_size, + process->GetByteOrder(), + process->GetAddressByteSize()); + m_ro_ptr = extractor.GetAddress_unchecked(&cursor); + if (ABISP abi_sp = process->GetABI()) + m_ro_ptr = abi_sp->FixCodeAddress(m_ro_ptr); + } + + return true; +} + +bool ClassDescriptorV2::class_ro_t::Read(Process *process, lldb::addr_t addr) { + size_t ptr_size = process->GetAddressByteSize(); + + size_t size = sizeof(uint32_t) // uint32_t flags; + + sizeof(uint32_t) // uint32_t instanceStart; + + sizeof(uint32_t) // uint32_t instanceSize; + + (ptr_size == 8 ? sizeof(uint32_t) + : 0) // uint32_t reserved; // __LP64__ only + + ptr_size // const uint8_t *ivarLayout; + + ptr_size // const char *name; + + ptr_size // const method_list_t *baseMethods; + + ptr_size // const protocol_list_t *baseProtocols; + + ptr_size // const ivar_list_t *ivars; + + ptr_size // const uint8_t *weakIvarLayout; + + ptr_size; // const property_list_t *baseProperties; + + DataBufferHeap buffer(size, '\0'); + Status error; + + process->ReadMemory(addr, buffer.GetBytes(), size, error); + if (error.Fail()) { + return false; + } + + DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(), + process->GetAddressByteSize()); + + lldb::offset_t cursor = 0; + + m_flags = extractor.GetU32_unchecked(&cursor); + m_instanceStart = extractor.GetU32_unchecked(&cursor); + m_instanceSize = extractor.GetU32_unchecked(&cursor); + if (ptr_size == 8) + m_reserved = extractor.GetU32_unchecked(&cursor); + else + m_reserved = 0; + m_ivarLayout_ptr = extractor.GetAddress_unchecked(&cursor); + m_name_ptr = extractor.GetAddress_unchecked(&cursor); + m_baseMethods_ptr = extractor.GetAddress_unchecked(&cursor); + m_baseProtocols_ptr = extractor.GetAddress_unchecked(&cursor); + m_ivars_ptr = extractor.GetAddress_unchecked(&cursor); + m_weakIvarLayout_ptr = extractor.GetAddress_unchecked(&cursor); + m_baseProperties_ptr = extractor.GetAddress_unchecked(&cursor); + + DataBufferHeap name_buf(1024, '\0'); + + process->ReadCStringFromMemory(m_name_ptr, (char *)name_buf.GetBytes(), + name_buf.GetByteSize(), error); + + if (error.Fail()) { + return false; + } + + m_name.assign((char *)name_buf.GetBytes()); + + return true; +} + +bool ClassDescriptorV2::Read_class_row( + Process *process, const objc_class_t &objc_class, + std::unique_ptr<class_ro_t> &class_ro, + std::unique_ptr<class_rw_t> &class_rw) const { + class_ro.reset(); + class_rw.reset(); + + Status error; + uint32_t class_row_t_flags = process->ReadUnsignedIntegerFromMemory( + objc_class.m_data_ptr, sizeof(uint32_t), 0, error); + if (!error.Success()) + return false; + + if (class_row_t_flags & RW_REALIZED) { + class_rw = std::make_unique<class_rw_t>(); + + if (!class_rw->Read(process, objc_class.m_data_ptr)) { + class_rw.reset(); + return false; + } + + class_ro = std::make_unique<class_ro_t>(); + + if (!class_ro->Read(process, class_rw->m_ro_ptr)) { + class_rw.reset(); + class_ro.reset(); + return false; + } + } else { + class_ro = std::make_unique<class_ro_t>(); + + if (!class_ro->Read(process, objc_class.m_data_ptr)) { + class_ro.reset(); + return false; + } + } + + return true; +} + +bool ClassDescriptorV2::method_list_t::Read(Process *process, + lldb::addr_t addr) { + size_t size = sizeof(uint32_t) // uint32_t entsize_NEVER_USE; + + sizeof(uint32_t); // uint32_t count; + + DataBufferHeap buffer(size, '\0'); + Status error; + + if (ABISP abi_sp = process->GetABI()) + addr = abi_sp->FixCodeAddress(addr); + process->ReadMemory(addr, buffer.GetBytes(), size, error); + if (error.Fail()) { + return false; + } + + DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(), + process->GetAddressByteSize()); + + lldb::offset_t cursor = 0; + + uint32_t entsize = extractor.GetU32_unchecked(&cursor); + m_is_small = (entsize & 0x80000000) != 0; + m_has_direct_selector = (entsize & 0x40000000) != 0; + m_entsize = entsize & 0xfffc; + m_count = extractor.GetU32_unchecked(&cursor); + m_first_ptr = addr + cursor; + + return true; +} + +bool ClassDescriptorV2::method_t::Read(Process *process, lldb::addr_t addr, + lldb::addr_t relative_selector_base_addr, + bool is_small, bool has_direct_sel) { + size_t ptr_size = process->GetAddressByteSize(); + size_t size = GetSize(process, is_small); + + DataBufferHeap buffer(size, '\0'); + Status error; + + process->ReadMemory(addr, buffer.GetBytes(), size, error); + if (error.Fail()) { + return false; + } + + DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(), + ptr_size); + lldb::offset_t cursor = 0; + + if (is_small) { + uint32_t nameref_offset = extractor.GetU32_unchecked(&cursor); + uint32_t types_offset = extractor.GetU32_unchecked(&cursor); + uint32_t imp_offset = extractor.GetU32_unchecked(&cursor); + + m_name_ptr = addr + nameref_offset; + + if (!has_direct_sel) { + // The SEL offset points to a SELRef. We need to dereference twice. + m_name_ptr = process->ReadUnsignedIntegerFromMemory(m_name_ptr, ptr_size, + 0, error); + if (!error.Success()) + return false; + } else if (relative_selector_base_addr != LLDB_INVALID_ADDRESS) { + m_name_ptr = relative_selector_base_addr + nameref_offset; + } + m_types_ptr = addr + 4 + types_offset; + m_imp_ptr = addr + 8 + imp_offset; + } else { + m_name_ptr = extractor.GetAddress_unchecked(&cursor); + m_types_ptr = extractor.GetAddress_unchecked(&cursor); + m_imp_ptr = extractor.GetAddress_unchecked(&cursor); + } + + process->ReadCStringFromMemory(m_name_ptr, m_name, error); + if (error.Fail()) { + return false; + } + + process->ReadCStringFromMemory(m_types_ptr, m_types, error); + return !error.Fail(); +} + +bool ClassDescriptorV2::ivar_list_t::Read(Process *process, lldb::addr_t addr) { + size_t size = sizeof(uint32_t) // uint32_t entsize; + + sizeof(uint32_t); // uint32_t count; + + DataBufferHeap buffer(size, '\0'); + Status error; + + process->ReadMemory(addr, buffer.GetBytes(), size, error); + if (error.Fail()) { + return false; + } + + DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(), + process->GetAddressByteSize()); + + lldb::offset_t cursor = 0; + + m_entsize = extractor.GetU32_unchecked(&cursor); + m_count = extractor.GetU32_unchecked(&cursor); + m_first_ptr = addr + cursor; + + return true; +} + +bool ClassDescriptorV2::ivar_t::Read(Process *process, lldb::addr_t addr) { + size_t size = GetSize(process); + + DataBufferHeap buffer(size, '\0'); + Status error; + + process->ReadMemory(addr, buffer.GetBytes(), size, error); + if (error.Fail()) { + return false; + } + + DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(), + process->GetAddressByteSize()); + + lldb::offset_t cursor = 0; + + m_offset_ptr = extractor.GetAddress_unchecked(&cursor); + m_name_ptr = extractor.GetAddress_unchecked(&cursor); + m_type_ptr = extractor.GetAddress_unchecked(&cursor); + m_alignment = extractor.GetU32_unchecked(&cursor); + m_size = extractor.GetU32_unchecked(&cursor); + + process->ReadCStringFromMemory(m_name_ptr, m_name, error); + if (error.Fail()) { + return false; + } + + process->ReadCStringFromMemory(m_type_ptr, m_type, error); + return !error.Fail(); +} + +bool ClassDescriptorV2::relative_list_entry_t::Read(Process *process, + lldb::addr_t addr) { + Log *log = GetLog(LLDBLog::Types); + size_t size = sizeof(uint64_t); // m_image_index : 16 + // m_list_offset : 48 + + DataBufferHeap buffer(size, '\0'); + Status error; + + process->ReadMemory(addr, buffer.GetBytes(), size, error); + // FIXME: Propagate this error up + if (error.Fail()) { + LLDB_LOG(log, "Failed to read relative_list_entry_t at address {0:x}", + addr); + return false; + } + + DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(), + process->GetAddressByteSize()); + lldb::offset_t cursor = 0; + uint64_t raw_entry = extractor.GetU64_unchecked(&cursor); + m_image_index = raw_entry & 0xFFFF; + m_list_offset = (int64_t)(raw_entry >> 16); + return true; +} + +bool ClassDescriptorV2::relative_list_list_t::Read(Process *process, + lldb::addr_t addr) { + Log *log = GetLog(LLDBLog::Types); + size_t size = sizeof(uint32_t) // m_entsize + + sizeof(uint32_t); // m_count + + DataBufferHeap buffer(size, '\0'); + Status error; + + // FIXME: Propagate this error up + process->ReadMemory(addr, buffer.GetBytes(), size, error); + if (error.Fail()) { + LLDB_LOG(log, "Failed to read relative_list_list_t at address 0x" PRIx64, + addr); + return false; + } + + DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(), + process->GetAddressByteSize()); + lldb::offset_t cursor = 0; + m_entsize = extractor.GetU32_unchecked(&cursor); + m_count = extractor.GetU32_unchecked(&cursor); + m_first_ptr = addr + cursor; + return true; +} + +std::optional<ClassDescriptorV2::method_list_t> +ClassDescriptorV2::GetMethodList(Process *process, + lldb::addr_t method_list_ptr) const { + Log *log = GetLog(LLDBLog::Types); + ClassDescriptorV2::method_list_t method_list; + if (!method_list.Read(process, method_list_ptr)) + return std::nullopt; + + const size_t method_size = method_t::GetSize(process, method_list.m_is_small); + if (method_list.m_entsize != method_size) { + LLDB_LOG(log, + "method_list_t at address 0x" PRIx64 " has an entsize of " PRIu16 + " but method size should be " PRIu64, + method_list_ptr, method_list.m_entsize, method_size); + return std::nullopt; + } + + return method_list; +} + +bool ClassDescriptorV2::ProcessMethodList( + std::function<bool(const char *, const char *)> const &instance_method_func, + ClassDescriptorV2::method_list_t &method_list) const { + lldb_private::Process *process = m_runtime.GetProcess(); + auto method = std::make_unique<method_t>(); + lldb::addr_t relative_selector_base_addr = + m_runtime.GetRelativeSelectorBaseAddr(); + for (uint32_t i = 0, e = method_list.m_count; i < e; ++i) { + method->Read(process, method_list.m_first_ptr + (i * method_list.m_entsize), + relative_selector_base_addr, method_list.m_is_small, + method_list.m_has_direct_selector); + if (instance_method_func(method->m_name.c_str(), method->m_types.c_str())) + break; + } + return true; +} + +// The relevant data structures: +// - relative_list_list_t +// - uint32_t count +// - uint32_t entsize +// - Followed by <count> number of relative_list_entry_t of size <entsize> +// +// - relative_list_entry_t +// - uint64_t image_index : 16 +// - int64_t list_offset : 48 +// - Note: The above 2 fit into 8 bytes always +// +// image_index corresponds to an image in the shared cache +// list_offset is used to calculate the address of the method_list_t we want +bool ClassDescriptorV2::ProcessRelativeMethodLists( + std::function<bool(const char *, const char *)> const &instance_method_func, + lldb::addr_t relative_method_list_ptr) const { + lldb_private::Process *process = m_runtime.GetProcess(); + auto relative_method_lists = std::make_unique<relative_list_list_t>(); + + // 1. Process the count and entsize of the relative_list_list_t + if (!relative_method_lists->Read(process, relative_method_list_ptr)) + return false; + + auto entry = std::make_unique<relative_list_entry_t>(); + for (uint32_t i = 0; i < relative_method_lists->m_count; i++) { + // 2. Extract the image index and the list offset from the + // relative_list_entry_t + const lldb::addr_t entry_addr = relative_method_lists->m_first_ptr + + (i * relative_method_lists->m_entsize); + if (!entry->Read(process, entry_addr)) + return false; + + // 3. Calculate the pointer to the method_list_t from the + // relative_list_entry_t + const lldb::addr_t method_list_addr = entry_addr + entry->m_list_offset; + + // 4. Get the method_list_t from the pointer + std::optional<method_list_t> method_list = + GetMethodList(process, method_list_addr); + if (!method_list) + return false; + + // 5. Cache the result so we don't need to reconstruct it later. + m_image_to_method_lists[entry->m_image_index].emplace_back(*method_list); + + // 6. If the relevant image is loaded, add the methods to the Decl + if (!m_runtime.IsSharedCacheImageLoaded(entry->m_image_index)) + continue; + + if (!ProcessMethodList(instance_method_func, *method_list)) + return false; + } + + // We need to keep track of the last time we updated so we can re-update the + // type information in the future + m_last_version_updated = m_runtime.GetSharedCacheImageHeaderVersion(); + + return true; +} + +bool ClassDescriptorV2::Describe( + std::function<void(ObjCLanguageRuntime::ObjCISA)> const &superclass_func, + std::function<bool(const char *, const char *)> const &instance_method_func, + std::function<bool(const char *, const char *)> const &class_method_func, + std::function<bool(const char *, const char *, lldb::addr_t, + uint64_t)> const &ivar_func) const { + lldb_private::Process *process = m_runtime.GetProcess(); + + std::unique_ptr<objc_class_t> objc_class; + std::unique_ptr<class_ro_t> class_ro; + std::unique_ptr<class_rw_t> class_rw; + + if (!Read_objc_class(process, objc_class)) + return false; + if (!Read_class_row(process, *objc_class, class_ro, class_rw)) + return false; + + static ConstString NSObject_name("NSObject"); + + if (m_name != NSObject_name && superclass_func) + superclass_func(objc_class->m_superclass); + + if (instance_method_func) { + // This is a relative list of lists + if (class_ro->m_baseMethods_ptr & 1) { + if (!ProcessRelativeMethodLists(instance_method_func, + class_ro->m_baseMethods_ptr ^ 1)) + return false; + } else { + std::optional<method_list_t> base_method_list = + GetMethodList(process, class_ro->m_baseMethods_ptr); + if (!base_method_list) + return false; + if (!ProcessMethodList(instance_method_func, *base_method_list)) + return false; + } + } + + if (class_method_func) { + AppleObjCRuntime::ClassDescriptorSP metaclass(GetMetaclass()); + + // We don't care about the metaclass's superclass, or its class methods. + // Its instance methods are our class methods. + + if (metaclass) { + metaclass->Describe( + std::function<void(ObjCLanguageRuntime::ObjCISA)>(nullptr), + class_method_func, + std::function<bool(const char *, const char *)>(nullptr), + std::function<bool(const char *, const char *, lldb::addr_t, + uint64_t)>(nullptr)); + } + } + + if (ivar_func) { + if (class_ro->m_ivars_ptr != 0) { + ivar_list_t ivar_list; + if (!ivar_list.Read(process, class_ro->m_ivars_ptr)) + return false; + + if (ivar_list.m_entsize != ivar_t::GetSize(process)) + return false; + + ivar_t ivar; + + for (uint32_t i = 0, e = ivar_list.m_count; i < e; ++i) { + ivar.Read(process, ivar_list.m_first_ptr + (i * ivar_list.m_entsize)); + + if (ivar_func(ivar.m_name.c_str(), ivar.m_type.c_str(), + ivar.m_offset_ptr, ivar.m_size)) + break; + } + } + } + + return true; +} + +ConstString ClassDescriptorV2::GetClassName() { + if (!m_name) { + lldb_private::Process *process = m_runtime.GetProcess(); + + if (process) { + std::unique_ptr<objc_class_t> objc_class; + std::unique_ptr<class_ro_t> class_ro; + std::unique_ptr<class_rw_t> class_rw; + + if (!Read_objc_class(process, objc_class)) + return m_name; + if (!Read_class_row(process, *objc_class, class_ro, class_rw)) + return m_name; + + m_name = ConstString(class_ro->m_name.c_str()); + } + } + return m_name; +} + +ObjCLanguageRuntime::ClassDescriptorSP ClassDescriptorV2::GetSuperclass() { + lldb_private::Process *process = m_runtime.GetProcess(); + + if (!process) + return ObjCLanguageRuntime::ClassDescriptorSP(); + + std::unique_ptr<objc_class_t> objc_class; + + if (!Read_objc_class(process, objc_class)) + return ObjCLanguageRuntime::ClassDescriptorSP(); + + return m_runtime.ObjCLanguageRuntime::GetClassDescriptorFromISA( + objc_class->m_superclass); +} + +ObjCLanguageRuntime::ClassDescriptorSP ClassDescriptorV2::GetMetaclass() const { + lldb_private::Process *process = m_runtime.GetProcess(); + + if (!process) + return ObjCLanguageRuntime::ClassDescriptorSP(); + + std::unique_ptr<objc_class_t> objc_class; + + if (!Read_objc_class(process, objc_class)) + return ObjCLanguageRuntime::ClassDescriptorSP(); + + lldb::addr_t candidate_isa = m_runtime.GetPointerISA(objc_class->m_isa); + + return ObjCLanguageRuntime::ClassDescriptorSP( + new ClassDescriptorV2(m_runtime, candidate_isa, nullptr)); +} + +uint64_t ClassDescriptorV2::GetInstanceSize() { + lldb_private::Process *process = m_runtime.GetProcess(); + + if (process) { + std::unique_ptr<objc_class_t> objc_class; + std::unique_ptr<class_ro_t> class_ro; + std::unique_ptr<class_rw_t> class_rw; + + if (!Read_objc_class(process, objc_class)) + return 0; + if (!Read_class_row(process, *objc_class, class_ro, class_rw)) + return 0; + + return class_ro->m_instanceSize; + } + + return 0; +} + +// From the ObjC runtime. +static uint8_t IS_SWIFT_STABLE = 1U << 1; + +LanguageType ClassDescriptorV2::GetImplementationLanguage() const { + std::unique_ptr<objc_class_t> objc_class; + if (auto *process = m_runtime.GetProcess()) + if (Read_objc_class(process, objc_class)) + if (objc_class->m_flags & IS_SWIFT_STABLE) + return lldb::eLanguageTypeSwift; + + return lldb::eLanguageTypeObjC; +} + +ClassDescriptorV2::iVarsStorage::iVarsStorage() : m_ivars(), m_mutex() {} + +size_t ClassDescriptorV2::iVarsStorage::size() { return m_ivars.size(); } + +ClassDescriptorV2::iVarDescriptor &ClassDescriptorV2::iVarsStorage:: +operator[](size_t idx) { + return m_ivars[idx]; +} + +void ClassDescriptorV2::iVarsStorage::fill(AppleObjCRuntimeV2 &runtime, + ClassDescriptorV2 &descriptor) { + if (m_filled) + return; + std::lock_guard<std::recursive_mutex> guard(m_mutex); + Log *log = GetLog(LLDBLog::Types); + LLDB_LOGV(log, "class_name = {0}", descriptor.GetClassName()); + m_filled = true; + ObjCLanguageRuntime::EncodingToTypeSP encoding_to_type_sp( + runtime.GetEncodingToType()); + Process *process(runtime.GetProcess()); + if (!encoding_to_type_sp) + return; + descriptor.Describe(nullptr, nullptr, nullptr, [this, process, + encoding_to_type_sp, + log](const char *name, + const char *type, + lldb::addr_t offset_ptr, + uint64_t size) -> bool { + const bool for_expression = false; + const bool stop_loop = false; + LLDB_LOGV(log, "name = {0}, encoding = {1}, offset_ptr = {2:x}, size = {3}", + name, type, offset_ptr, size); + CompilerType ivar_type = + encoding_to_type_sp->RealizeType(type, for_expression); + if (ivar_type) { + LLDB_LOGV(log, + "name = {0}, encoding = {1}, offset_ptr = {2:x}, size = " + "{3}, type_size = {4}", + name, type, offset_ptr, size, + ivar_type.GetByteSize(nullptr).value_or(0)); + Scalar offset_scalar; + Status error; + const int offset_ptr_size = 4; + const bool is_signed = false; + size_t read = process->ReadScalarIntegerFromMemory( + offset_ptr, offset_ptr_size, is_signed, offset_scalar, error); + if (error.Success() && 4 == read) { + LLDB_LOGV(log, "offset_ptr = {0:x} --> {1}", offset_ptr, + offset_scalar.SInt()); + m_ivars.push_back( + {ConstString(name), ivar_type, size, offset_scalar.SInt()}); + } else + LLDB_LOGV(log, "offset_ptr = {0:x} --> read fail, read = %{1}", + offset_ptr, read); + } + return stop_loop; + }); +} + +void ClassDescriptorV2::GetIVarInformation() { + m_ivars_storage.fill(m_runtime, *this); +} diff --git a/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.h b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.h new file mode 100644 index 000000000000..920a5eba20ab --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.h @@ -0,0 +1,396 @@ +//===-- AppleObjCClassDescriptorV2.h ----------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLEOBJCCLASSDESCRIPTORV2_H +#define LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLEOBJCCLASSDESCRIPTORV2_H + +#include <mutex> + +#include "AppleObjCRuntimeV2.h" +#include "lldb/lldb-enumerations.h" +#include "lldb/lldb-private.h" + +#include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h" + +namespace lldb_private { + +class ClassDescriptorV2 : public ObjCLanguageRuntime::ClassDescriptor { +public: + friend class lldb_private::AppleObjCRuntimeV2; + + ~ClassDescriptorV2() override = default; + + ConstString GetClassName() override; + + ObjCLanguageRuntime::ClassDescriptorSP GetSuperclass() override; + + ObjCLanguageRuntime::ClassDescriptorSP GetMetaclass() const override; + + bool IsValid() override { + return true; // any Objective-C v2 runtime class descriptor we vend is valid + } + + lldb::LanguageType GetImplementationLanguage() const override; + + // a custom descriptor is used for tagged pointers + bool GetTaggedPointerInfo(uint64_t *info_bits = nullptr, + uint64_t *value_bits = nullptr, + uint64_t *payload = nullptr) override { + return false; + } + + bool GetTaggedPointerInfoSigned(uint64_t *info_bits = nullptr, + int64_t *value_bits = nullptr, + uint64_t *payload = nullptr) override { + return false; + } + + uint64_t GetInstanceSize() override; + + ObjCLanguageRuntime::ObjCISA GetISA() override { return m_objc_class_ptr; } + + bool Describe( + std::function<void(ObjCLanguageRuntime::ObjCISA)> const &superclass_func, + std::function<bool(const char *, const char *)> const + &instance_method_func, + std::function<bool(const char *, const char *)> const &class_method_func, + std::function<bool(const char *, const char *, lldb::addr_t, + uint64_t)> const &ivar_func) const override; + + size_t GetNumIVars() override { + GetIVarInformation(); + return m_ivars_storage.size(); + } + + iVarDescriptor GetIVarAtIndex(size_t idx) override { + if (idx >= GetNumIVars()) + return iVarDescriptor(); + return m_ivars_storage[idx]; + } + +protected: + void GetIVarInformation(); + +private: + static const uint32_t RW_REALIZED = (1u << 31); + + struct objc_class_t { + ObjCLanguageRuntime::ObjCISA m_isa = 0; // The class's metaclass. + ObjCLanguageRuntime::ObjCISA m_superclass = 0; + lldb::addr_t m_cache_ptr = 0; + lldb::addr_t m_vtable_ptr = 0; + lldb::addr_t m_data_ptr = 0; + uint8_t m_flags = 0; + + objc_class_t() = default; + + void Clear() { + m_isa = 0; + m_superclass = 0; + m_cache_ptr = 0; + m_vtable_ptr = 0; + m_data_ptr = 0; + m_flags = 0; + } + + bool Read(Process *process, lldb::addr_t addr); + }; + + struct class_ro_t { + uint32_t m_flags; + uint32_t m_instanceStart; + uint32_t m_instanceSize; + uint32_t m_reserved; + + lldb::addr_t m_ivarLayout_ptr; + lldb::addr_t m_name_ptr; + lldb::addr_t m_baseMethods_ptr; + lldb::addr_t m_baseProtocols_ptr; + lldb::addr_t m_ivars_ptr; + + lldb::addr_t m_weakIvarLayout_ptr; + lldb::addr_t m_baseProperties_ptr; + + std::string m_name; + + bool Read(Process *process, lldb::addr_t addr); + }; + + struct class_rw_t { + uint32_t m_flags; + uint32_t m_version; + + lldb::addr_t m_ro_ptr; + union { + lldb::addr_t m_method_list_ptr; + lldb::addr_t m_method_lists_ptr; + }; + lldb::addr_t m_properties_ptr; + lldb::addr_t m_protocols_ptr; + + ObjCLanguageRuntime::ObjCISA m_firstSubclass; + ObjCLanguageRuntime::ObjCISA m_nextSiblingClass; + + bool Read(Process *process, lldb::addr_t addr); + }; + + struct method_list_t { + uint16_t m_entsize; + bool m_is_small; + bool m_has_direct_selector; + uint32_t m_count; + lldb::addr_t m_first_ptr; + + bool Read(Process *process, lldb::addr_t addr); + }; + + std::optional<method_list_t> + GetMethodList(Process *process, lldb::addr_t method_list_ptr) const; + + struct method_t { + lldb::addr_t m_name_ptr; + lldb::addr_t m_types_ptr; + lldb::addr_t m_imp_ptr; + + std::string m_name; + std::string m_types; + + static size_t GetSize(Process *process, bool is_small) { + size_t field_size; + if (is_small) + field_size = 4; // uint32_t relative indirect fields + else + field_size = process->GetAddressByteSize(); + + return field_size // SEL name; + + field_size // const char *types; + + field_size; // IMP imp; + } + + bool Read(Process *process, lldb::addr_t addr, + lldb::addr_t relative_selector_base_addr, bool is_small, + bool has_direct_sel); + }; + + struct ivar_list_t { + uint32_t m_entsize; + uint32_t m_count; + lldb::addr_t m_first_ptr; + + bool Read(Process *process, lldb::addr_t addr); + }; + + struct ivar_t { + lldb::addr_t m_offset_ptr; + lldb::addr_t m_name_ptr; + lldb::addr_t m_type_ptr; + uint32_t m_alignment; + uint32_t m_size; + + std::string m_name; + std::string m_type; + + static size_t GetSize(Process *process) { + size_t ptr_size = process->GetAddressByteSize(); + + return ptr_size // uintptr_t *offset; + + ptr_size // const char *name; + + ptr_size // const char *type; + + sizeof(uint32_t) // uint32_t alignment; + + sizeof(uint32_t); // uint32_t size; + } + + bool Read(Process *process, lldb::addr_t addr); + }; + + struct relative_list_entry_t { + uint16_t m_image_index; + int64_t m_list_offset; + + bool Read(Process *process, lldb::addr_t addr); + }; + + struct relative_list_list_t { + uint32_t m_entsize; + uint32_t m_count; + lldb::addr_t m_first_ptr; + + bool Read(Process *process, lldb::addr_t addr); + }; + + class iVarsStorage { + public: + iVarsStorage(); + + size_t size(); + + iVarDescriptor &operator[](size_t idx); + + void fill(AppleObjCRuntimeV2 &runtime, ClassDescriptorV2 &descriptor); + + private: + bool m_filled = false; + std::vector<iVarDescriptor> m_ivars; + std::recursive_mutex m_mutex; + }; + + // The constructor should only be invoked by the runtime as it builds its + // caches + // or populates them. A ClassDescriptorV2 should only ever exist in a cache. + ClassDescriptorV2(AppleObjCRuntimeV2 &runtime, + ObjCLanguageRuntime::ObjCISA isa, const char *name) + : m_runtime(runtime), m_objc_class_ptr(isa), m_name(name), + m_ivars_storage(), m_image_to_method_lists(), m_last_version_updated() { + } + + bool Read_objc_class(Process *process, + std::unique_ptr<objc_class_t> &objc_class) const; + + bool Read_class_row(Process *process, const objc_class_t &objc_class, + std::unique_ptr<class_ro_t> &class_ro, + std::unique_ptr<class_rw_t> &class_rw) const; + + bool ProcessMethodList(std::function<bool(const char *, const char *)> const + &instance_method_func, + method_list_t &method_list) const; + + bool ProcessRelativeMethodLists( + std::function<bool(const char *, const char *)> const + &instance_method_func, + lldb::addr_t relative_method_list_ptr) const; + + AppleObjCRuntimeV2 + &m_runtime; // The runtime, so we can read information lazily. + lldb::addr_t m_objc_class_ptr; // The address of the objc_class_t. (I.e., + // objects of this class type have this as + // their ISA) + ConstString m_name; // May be NULL + iVarsStorage m_ivars_storage; + + mutable std::map<uint16_t, std::vector<method_list_t>> + m_image_to_method_lists; + mutable std::optional<uint64_t> m_last_version_updated; +}; + +// tagged pointer descriptor +class ClassDescriptorV2Tagged : public ObjCLanguageRuntime::ClassDescriptor { +public: + ClassDescriptorV2Tagged(ConstString class_name, uint64_t payload) { + m_name = class_name; + if (!m_name) { + m_valid = false; + return; + } + m_valid = true; + m_payload = payload; + m_info_bits = (m_payload & 0xF0ULL) >> 4; + m_value_bits = (m_payload & ~0x0000000000000000FFULL) >> 8; + } + + ClassDescriptorV2Tagged( + ObjCLanguageRuntime::ClassDescriptorSP actual_class_sp, + uint64_t u_payload, int64_t s_payload) { + if (!actual_class_sp) { + m_valid = false; + return; + } + m_name = actual_class_sp->GetClassName(); + if (!m_name) { + m_valid = false; + return; + } + m_valid = true; + m_payload = u_payload; + m_info_bits = (m_payload & 0x0FULL); + m_value_bits = (m_payload & ~0x0FULL) >> 4; + m_value_bits_signed = (s_payload & ~0x0FLL) >> 4; + } + + ~ClassDescriptorV2Tagged() override = default; + + ConstString GetClassName() override { return m_name; } + + ObjCLanguageRuntime::ClassDescriptorSP GetSuperclass() override { + // tagged pointers can represent a class that has a superclass, but since + // that information is not + // stored in the object itself, we would have to query the runtime to + // discover the hierarchy + // for the time being, we skip this step in the interest of static discovery + return ObjCLanguageRuntime::ClassDescriptorSP(); + } + + ObjCLanguageRuntime::ClassDescriptorSP GetMetaclass() const override { + return ObjCLanguageRuntime::ClassDescriptorSP(); + } + + bool IsValid() override { return m_valid; } + + bool IsKVO() override { + return false; // tagged pointers are not KVO'ed + } + + bool IsCFType() override { + return false; // tagged pointers are not CF objects + } + + bool GetTaggedPointerInfo(uint64_t *info_bits = nullptr, + uint64_t *value_bits = nullptr, + uint64_t *payload = nullptr) override { + if (info_bits) + *info_bits = GetInfoBits(); + if (value_bits) + *value_bits = GetValueBits(); + if (payload) + *payload = GetPayload(); + return true; + } + + bool GetTaggedPointerInfoSigned(uint64_t *info_bits = nullptr, + int64_t *value_bits = nullptr, + uint64_t *payload = nullptr) override { + if (info_bits) + *info_bits = GetInfoBits(); + if (value_bits) + *value_bits = GetValueBitsSigned(); + if (payload) + *payload = GetPayload(); + return true; + } + + uint64_t GetInstanceSize() override { + return (IsValid() ? m_pointer_size : 0); + } + + ObjCLanguageRuntime::ObjCISA GetISA() override { + return 0; // tagged pointers have no ISA + } + + // these calls are not part of any formal tagged pointers specification + virtual uint64_t GetValueBits() { return (IsValid() ? m_value_bits : 0); } + + virtual int64_t GetValueBitsSigned() { + return (IsValid() ? m_value_bits_signed : 0); + } + + virtual uint64_t GetInfoBits() { return (IsValid() ? m_info_bits : 0); } + + virtual uint64_t GetPayload() { return (IsValid() ? m_payload : 0); } + +private: + ConstString m_name; + uint8_t m_pointer_size = 0; + bool m_valid = false; + uint64_t m_info_bits = 0; + uint64_t m_value_bits = 0; + int64_t m_value_bits_signed = 0; + uint64_t m_payload = 0; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLEOBJCCLASSDESCRIPTORV2_H diff --git a/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCDeclVendor.cpp b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCDeclVendor.cpp new file mode 100644 index 000000000000..6894cdccaf95 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCDeclVendor.cpp @@ -0,0 +1,618 @@ +//===-- AppleObjCDeclVendor.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 "AppleObjCDeclVendor.h" + +#include "Plugins/ExpressionParser/Clang/ClangASTMetadata.h" +#include "Plugins/ExpressionParser/Clang/ClangUtil.h" +#include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h" +#include "lldb/Core/Module.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" + +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclObjC.h" +#include "clang/AST/ExternalASTSource.h" + +using namespace lldb_private; + +class lldb_private::AppleObjCExternalASTSource + : public clang::ExternalASTSource { +public: + AppleObjCExternalASTSource(AppleObjCDeclVendor &decl_vendor) + : m_decl_vendor(decl_vendor) {} + + bool FindExternalVisibleDeclsByName(const clang::DeclContext *decl_ctx, + clang::DeclarationName name) override { + + Log *log(GetLog( + LLDBLog::Expressions)); // FIXME - a more appropriate log channel? + + if (log) { + LLDB_LOGF(log, + "AppleObjCExternalASTSource::FindExternalVisibleDeclsByName" + " on (ASTContext*)%p Looking for %s in (%sDecl*)%p", + static_cast<void *>(&decl_ctx->getParentASTContext()), + name.getAsString().c_str(), decl_ctx->getDeclKindName(), + static_cast<const void *>(decl_ctx)); + } + + do { + const clang::ObjCInterfaceDecl *interface_decl = + llvm::dyn_cast<clang::ObjCInterfaceDecl>(decl_ctx); + + if (!interface_decl) + break; + + clang::ObjCInterfaceDecl *non_const_interface_decl = + const_cast<clang::ObjCInterfaceDecl *>(interface_decl); + + if (!m_decl_vendor.FinishDecl(non_const_interface_decl)) + break; + + clang::DeclContext::lookup_result result = + non_const_interface_decl->lookup(name); + + return (!result.empty()); + } while (false); + + SetNoExternalVisibleDeclsForName(decl_ctx, name); + return false; + } + + void CompleteType(clang::TagDecl *tag_decl) override { + + Log *log(GetLog( + LLDBLog::Expressions)); // FIXME - a more appropriate log channel? + + LLDB_LOGF(log, + "AppleObjCExternalASTSource::CompleteType on " + "(ASTContext*)%p Completing (TagDecl*)%p named %s", + static_cast<void *>(&tag_decl->getASTContext()), + static_cast<void *>(tag_decl), tag_decl->getName().str().c_str()); + + LLDB_LOG(log, " AOEAS::CT Before:\n{1}", ClangUtil::DumpDecl(tag_decl)); + + LLDB_LOG(log, " AOEAS::CT After:{1}", ClangUtil::DumpDecl(tag_decl)); + } + + void CompleteType(clang::ObjCInterfaceDecl *interface_decl) override { + + Log *log(GetLog( + LLDBLog::Expressions)); // FIXME - a more appropriate log channel? + + if (log) { + LLDB_LOGF(log, + "AppleObjCExternalASTSource::CompleteType on " + "(ASTContext*)%p Completing (ObjCInterfaceDecl*)%p named %s", + static_cast<void *>(&interface_decl->getASTContext()), + static_cast<void *>(interface_decl), + interface_decl->getName().str().c_str()); + + LLDB_LOGF(log, " AOEAS::CT Before:"); + LLDB_LOG(log, " [CT] {0}", ClangUtil::DumpDecl(interface_decl)); + } + + m_decl_vendor.FinishDecl(interface_decl); + + if (log) { + LLDB_LOGF(log, " [CT] After:"); + LLDB_LOG(log, " [CT] {0}", ClangUtil::DumpDecl(interface_decl)); + } + } + + bool layoutRecordType( + const clang::RecordDecl *Record, uint64_t &Size, uint64_t &Alignment, + llvm::DenseMap<const clang::FieldDecl *, uint64_t> &FieldOffsets, + llvm::DenseMap<const clang::CXXRecordDecl *, clang::CharUnits> + &BaseOffsets, + llvm::DenseMap<const clang::CXXRecordDecl *, clang::CharUnits> + &VirtualBaseOffsets) override { + return false; + } + + void StartTranslationUnit(clang::ASTConsumer *Consumer) override { + clang::TranslationUnitDecl *translation_unit_decl = + m_decl_vendor.m_ast_ctx->getASTContext().getTranslationUnitDecl(); + translation_unit_decl->setHasExternalVisibleStorage(); + translation_unit_decl->setHasExternalLexicalStorage(); + } + +private: + AppleObjCDeclVendor &m_decl_vendor; +}; + +AppleObjCDeclVendor::AppleObjCDeclVendor(ObjCLanguageRuntime &runtime) + : ClangDeclVendor(eAppleObjCDeclVendor), m_runtime(runtime), + m_type_realizer_sp(m_runtime.GetEncodingToType()) { + m_ast_ctx = std::make_shared<TypeSystemClang>( + "AppleObjCDeclVendor AST", + runtime.GetProcess()->GetTarget().GetArchitecture().GetTriple()); + m_external_source = new AppleObjCExternalASTSource(*this); + llvm::IntrusiveRefCntPtr<clang::ExternalASTSource> external_source_owning_ptr( + m_external_source); + m_ast_ctx->getASTContext().setExternalSource(external_source_owning_ptr); +} + +clang::ObjCInterfaceDecl * +AppleObjCDeclVendor::GetDeclForISA(ObjCLanguageRuntime::ObjCISA isa) { + ISAToInterfaceMap::const_iterator iter = m_isa_to_interface.find(isa); + + if (iter != m_isa_to_interface.end()) + return iter->second; + + clang::ASTContext &ast_ctx = m_ast_ctx->getASTContext(); + + ObjCLanguageRuntime::ClassDescriptorSP descriptor = + m_runtime.GetClassDescriptorFromISA(isa); + + if (!descriptor) + return nullptr; + + ConstString name(descriptor->GetClassName()); + + clang::IdentifierInfo &identifier_info = + ast_ctx.Idents.get(name.GetStringRef()); + + clang::ObjCInterfaceDecl *new_iface_decl = clang::ObjCInterfaceDecl::Create( + ast_ctx, ast_ctx.getTranslationUnitDecl(), clang::SourceLocation(), + &identifier_info, nullptr, nullptr); + + ClangASTMetadata meta_data; + meta_data.SetISAPtr(isa); + m_ast_ctx->SetMetadata(new_iface_decl, meta_data); + + new_iface_decl->setHasExternalVisibleStorage(); + new_iface_decl->setHasExternalLexicalStorage(); + + ast_ctx.getTranslationUnitDecl()->addDecl(new_iface_decl); + + m_isa_to_interface[isa] = new_iface_decl; + + return new_iface_decl; +} + +class ObjCRuntimeMethodType { +public: + ObjCRuntimeMethodType(const char *types) { + const char *cursor = types; + enum ParserState { Start = 0, InType, InPos } state = Start; + const char *type = nullptr; + int brace_depth = 0; + + uint32_t stepsLeft = 256; + + while (true) { + if (--stepsLeft == 0) { + m_is_valid = false; + return; + } + + switch (state) { + case Start: { + switch (*cursor) { + default: + state = InType; + type = cursor; + break; + case '\0': + m_is_valid = true; + return; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + m_is_valid = false; + return; + } + } break; + case InType: { + switch (*cursor) { + default: + ++cursor; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (!brace_depth) { + state = InPos; + if (type) { + m_type_vector.push_back(std::string(type, (cursor - type))); + } else { + m_is_valid = false; + return; + } + type = nullptr; + } else { + ++cursor; + } + break; + case '[': + case '{': + case '(': + ++brace_depth; + ++cursor; + break; + case ']': + case '}': + case ')': + if (!brace_depth) { + m_is_valid = false; + return; + } + --brace_depth; + ++cursor; + break; + case '\0': + m_is_valid = false; + return; + } + } break; + case InPos: { + switch (*cursor) { + default: + state = InType; + type = cursor; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + ++cursor; + break; + case '\0': + m_is_valid = true; + return; + } + } break; + } + } + } + + clang::ObjCMethodDecl * + BuildMethod(TypeSystemClang &clang_ast_ctxt, + clang::ObjCInterfaceDecl *interface_decl, const char *name, + bool instance, + ObjCLanguageRuntime::EncodingToTypeSP type_realizer_sp) { + if (!m_is_valid || m_type_vector.size() < 3) + return nullptr; + + clang::ASTContext &ast_ctx(interface_decl->getASTContext()); + + const bool isInstance = instance; + const bool isVariadic = false; + const bool isPropertyAccessor = false; + const bool isSynthesizedAccessorStub = false; + const bool isImplicitlyDeclared = true; + const bool isDefined = false; + const clang::ObjCImplementationControl impControl = + clang::ObjCImplementationControl::None; + const bool HasRelatedResultType = false; + const bool for_expression = true; + + std::vector<const clang::IdentifierInfo *> selector_components; + + const char *name_cursor = name; + bool is_zero_argument = true; + + while (*name_cursor != '\0') { + const char *colon_loc = strchr(name_cursor, ':'); + if (!colon_loc) { + selector_components.push_back( + &ast_ctx.Idents.get(llvm::StringRef(name_cursor))); + break; + } else { + is_zero_argument = false; + selector_components.push_back(&ast_ctx.Idents.get( + llvm::StringRef(name_cursor, colon_loc - name_cursor))); + name_cursor = colon_loc + 1; + } + } + + const clang::IdentifierInfo **identifier_infos = selector_components.data(); + if (!identifier_infos) { + return nullptr; + } + + clang::Selector sel = ast_ctx.Selectors.getSelector( + is_zero_argument ? 0 : selector_components.size(), + identifier_infos); + + clang::QualType ret_type = + ClangUtil::GetQualType(type_realizer_sp->RealizeType( + clang_ast_ctxt, m_type_vector[0].c_str(), for_expression)); + + if (ret_type.isNull()) + return nullptr; + + clang::ObjCMethodDecl *ret = clang::ObjCMethodDecl::Create( + ast_ctx, clang::SourceLocation(), clang::SourceLocation(), sel, + ret_type, nullptr, interface_decl, isInstance, isVariadic, + isPropertyAccessor, isSynthesizedAccessorStub, isImplicitlyDeclared, + isDefined, impControl, HasRelatedResultType); + + std::vector<clang::ParmVarDecl *> parm_vars; + + for (size_t ai = 3, ae = m_type_vector.size(); ai != ae; ++ai) { + const bool for_expression = true; + clang::QualType arg_type = + ClangUtil::GetQualType(type_realizer_sp->RealizeType( + clang_ast_ctxt, m_type_vector[ai].c_str(), for_expression)); + + if (arg_type.isNull()) + return nullptr; // well, we just wasted a bunch of time. Wish we could + // delete the stuff we'd just made! + + parm_vars.push_back(clang::ParmVarDecl::Create( + ast_ctx, ret, clang::SourceLocation(), clang::SourceLocation(), + nullptr, arg_type, nullptr, clang::SC_None, nullptr)); + } + + ret->setMethodParams(ast_ctx, + llvm::ArrayRef<clang::ParmVarDecl *>(parm_vars), + llvm::ArrayRef<clang::SourceLocation>()); + + return ret; + } + + explicit operator bool() { return m_is_valid; } + + size_t GetNumTypes() { return m_type_vector.size(); } + + const char *GetTypeAtIndex(size_t idx) { return m_type_vector[idx].c_str(); } + +private: + typedef std::vector<std::string> TypeVector; + + TypeVector m_type_vector; + bool m_is_valid = false; +}; + +bool AppleObjCDeclVendor::FinishDecl(clang::ObjCInterfaceDecl *interface_decl) { + Log *log( + GetLog(LLDBLog::Expressions)); // FIXME - a more appropriate log channel? + + ClangASTMetadata *metadata = m_ast_ctx->GetMetadata(interface_decl); + ObjCLanguageRuntime::ObjCISA objc_isa = 0; + if (metadata) + objc_isa = metadata->GetISAPtr(); + + if (!objc_isa) + return false; + + if (!interface_decl->hasExternalVisibleStorage()) + return true; + + interface_decl->startDefinition(); + + interface_decl->setHasExternalVisibleStorage(false); + interface_decl->setHasExternalLexicalStorage(false); + + ObjCLanguageRuntime::ClassDescriptorSP descriptor = + m_runtime.GetClassDescriptorFromISA(objc_isa); + + if (!descriptor) + return false; + + auto superclass_func = [interface_decl, + this](ObjCLanguageRuntime::ObjCISA isa) { + clang::ObjCInterfaceDecl *superclass_decl = GetDeclForISA(isa); + + if (!superclass_decl) + return; + + FinishDecl(superclass_decl); + clang::ASTContext &context = m_ast_ctx->getASTContext(); + interface_decl->setSuperClass(context.getTrivialTypeSourceInfo( + context.getObjCInterfaceType(superclass_decl))); + }; + + auto instance_method_func = + [log, interface_decl, this](const char *name, const char *types) -> bool { + if (!name || !types) + return false; // skip this one + + ObjCRuntimeMethodType method_type(types); + + clang::ObjCMethodDecl *method_decl = method_type.BuildMethod( + *m_ast_ctx, interface_decl, name, true, m_type_realizer_sp); + + LLDB_LOGF(log, "[ AOTV::FD] Instance method [%s] [%s]", name, types); + + if (method_decl) + interface_decl->addDecl(method_decl); + + return false; + }; + + auto class_method_func = [log, interface_decl, + this](const char *name, const char *types) -> bool { + if (!name || !types) + return false; // skip this one + + ObjCRuntimeMethodType method_type(types); + + clang::ObjCMethodDecl *method_decl = method_type.BuildMethod( + *m_ast_ctx, interface_decl, name, false, m_type_realizer_sp); + + LLDB_LOGF(log, "[ AOTV::FD] Class method [%s] [%s]", name, types); + + if (method_decl) + interface_decl->addDecl(method_decl); + + return false; + }; + + auto ivar_func = [log, interface_decl, + this](const char *name, const char *type, + lldb::addr_t offset_ptr, uint64_t size) -> bool { + if (!name || !type) + return false; + + const bool for_expression = false; + + LLDB_LOGF(log, + "[ AOTV::FD] Instance variable [%s] [%s], offset at %" PRIx64, + name, type, offset_ptr); + + CompilerType ivar_type = m_runtime.GetEncodingToType()->RealizeType( + *m_ast_ctx, type, for_expression); + + if (ivar_type.IsValid()) { + clang::TypeSourceInfo *const type_source_info = nullptr; + const bool is_synthesized = false; + clang::ObjCIvarDecl *ivar_decl = clang::ObjCIvarDecl::Create( + m_ast_ctx->getASTContext(), interface_decl, clang::SourceLocation(), + clang::SourceLocation(), &m_ast_ctx->getASTContext().Idents.get(name), + ClangUtil::GetQualType(ivar_type), + type_source_info, // TypeSourceInfo * + clang::ObjCIvarDecl::Public, nullptr, is_synthesized); + + if (ivar_decl) { + interface_decl->addDecl(ivar_decl); + } + } + + return false; + }; + + LLDB_LOGF(log, + "[AppleObjCDeclVendor::FinishDecl] Finishing Objective-C " + "interface for %s", + descriptor->GetClassName().AsCString()); + + if (!descriptor->Describe(superclass_func, instance_method_func, + class_method_func, ivar_func)) + return false; + + if (log) { + LLDB_LOGF( + log, + "[AppleObjCDeclVendor::FinishDecl] Finished Objective-C interface"); + + LLDB_LOG(log, " [AOTV::FD] {0}", ClangUtil::DumpDecl(interface_decl)); + } + + return true; +} + +uint32_t AppleObjCDeclVendor::FindDecls(ConstString name, bool append, + uint32_t max_matches, + std::vector<CompilerDecl> &decls) { + + Log *log( + GetLog(LLDBLog::Expressions)); // FIXME - a more appropriate log channel? + + LLDB_LOGF(log, "AppleObjCDeclVendor::FindDecls ('%s', %s, %u, )", + (const char *)name.AsCString(), append ? "true" : "false", + max_matches); + + if (!append) + decls.clear(); + + uint32_t ret = 0; + + do { + // See if the type is already in our ASTContext. + + clang::ASTContext &ast_ctx = m_ast_ctx->getASTContext(); + + clang::IdentifierInfo &identifier_info = + ast_ctx.Idents.get(name.GetStringRef()); + clang::DeclarationName decl_name = + ast_ctx.DeclarationNames.getIdentifier(&identifier_info); + + clang::DeclContext::lookup_result lookup_result = + ast_ctx.getTranslationUnitDecl()->lookup(decl_name); + + if (!lookup_result.empty()) { + if (clang::ObjCInterfaceDecl *result_iface_decl = + llvm::dyn_cast<clang::ObjCInterfaceDecl>(*lookup_result.begin())) { + if (log) { + clang::QualType result_iface_type = + ast_ctx.getObjCInterfaceType(result_iface_decl); + + uint64_t isa_value = LLDB_INVALID_ADDRESS; + ClangASTMetadata *metadata = m_ast_ctx->GetMetadata(result_iface_decl); + if (metadata) + isa_value = metadata->GetISAPtr(); + + LLDB_LOGF(log, + "AOCTV::FT Found %s (isa 0x%" PRIx64 ") in the ASTContext", + result_iface_type.getAsString().data(), isa_value); + } + + decls.push_back(m_ast_ctx->GetCompilerDecl(result_iface_decl)); + ret++; + break; + } else { + LLDB_LOGF(log, "AOCTV::FT There's something in the ASTContext, but " + "it's not something we know about"); + break; + } + } else if (log) { + LLDB_LOGF(log, "AOCTV::FT Couldn't find %s in the ASTContext", + name.AsCString()); + } + + // It's not. If it exists, we have to put it into our ASTContext. + + ObjCLanguageRuntime::ObjCISA isa = m_runtime.GetISA(name); + + if (!isa) { + LLDB_LOGF(log, "AOCTV::FT Couldn't find the isa"); + + break; + } + + clang::ObjCInterfaceDecl *iface_decl = GetDeclForISA(isa); + + if (!iface_decl) { + LLDB_LOGF(log, + "AOCTV::FT Couldn't get the Objective-C interface for " + "isa 0x%" PRIx64, + (uint64_t)isa); + + break; + } + + if (log) { + clang::QualType new_iface_type = ast_ctx.getObjCInterfaceType(iface_decl); + + LLDB_LOG(log, "AOCTV::FT Created {1} (isa 0x{2:x})", + new_iface_type.getAsString(), (uint64_t)isa); + } + + decls.push_back(m_ast_ctx->GetCompilerDecl(iface_decl)); + ret++; + break; + } while (false); + + return ret; +} diff --git a/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCDeclVendor.h b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCDeclVendor.h new file mode 100644 index 000000000000..3bb0f77f6bde --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCDeclVendor.h @@ -0,0 +1,53 @@ +//===-- AppleObjCDeclVendor.h -----------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLEOBJCDECLVENDOR_H +#define LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLEOBJCDECLVENDOR_H + +#include "lldb/lldb-private.h" + +#include "Plugins/ExpressionParser/Clang/ClangDeclVendor.h" +#include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h" +#include "Plugins/TypeSystem/Clang/TypeSystemClang.h" + +namespace lldb_private { + +class AppleObjCExternalASTSource; + +class AppleObjCDeclVendor : public ClangDeclVendor { +public: + AppleObjCDeclVendor(ObjCLanguageRuntime &runtime); + + static bool classof(const DeclVendor *vendor) { + return vendor->GetKind() == eAppleObjCDeclVendor; + } + + uint32_t FindDecls(ConstString name, bool append, uint32_t max_matches, + std::vector<CompilerDecl> &decls) override; + + friend class AppleObjCExternalASTSource; + +private: + clang::ObjCInterfaceDecl *GetDeclForISA(ObjCLanguageRuntime::ObjCISA isa); + bool FinishDecl(clang::ObjCInterfaceDecl *decl); + + ObjCLanguageRuntime &m_runtime; + std::shared_ptr<TypeSystemClang> m_ast_ctx; + ObjCLanguageRuntime::EncodingToTypeSP m_type_realizer_sp; + AppleObjCExternalASTSource *m_external_source; + + typedef llvm::DenseMap<ObjCLanguageRuntime::ObjCISA, + clang::ObjCInterfaceDecl *> + ISAToInterfaceMap; + + ISAToInterfaceMap m_isa_to_interface; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLEOBJCDECLVENDOR_H diff --git a/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.cpp b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.cpp new file mode 100644 index 000000000000..5ff267720629 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.cpp @@ -0,0 +1,630 @@ +//===-- AppleObjCRuntime.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 "AppleObjCRuntime.h" +#include "AppleObjCRuntimeV1.h" +#include "AppleObjCRuntimeV2.h" +#include "AppleObjCTrampolineHandler.h" +#include "Plugins/Language/ObjC/NSString.h" +#include "Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.h" +#include "Plugins/Process/Utility/HistoryThread.h" +#include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleList.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/Section.h" +#include "lldb/Core/ValueObject.h" +#include "lldb/Core/ValueObjectConstResult.h" +#include "lldb/DataFormatters/FormattersHelpers.h" +#include "lldb/Expression/DiagnosticManager.h" +#include "lldb/Expression/FunctionCaller.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/StopInfo.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/ErrorMessages.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Scalar.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/StreamString.h" +#include "clang/AST/Type.h" + +#include "Plugins/TypeSystem/Clang/TypeSystemClang.h" + +#include <vector> + +using namespace lldb; +using namespace lldb_private; + +LLDB_PLUGIN_DEFINE(AppleObjCRuntime) + +char AppleObjCRuntime::ID = 0; + +AppleObjCRuntime::~AppleObjCRuntime() = default; + +AppleObjCRuntime::AppleObjCRuntime(Process *process) + : ObjCLanguageRuntime(process), m_read_objc_library(false), + m_objc_trampoline_handler_up(), m_Foundation_major() { + ReadObjCLibraryIfNeeded(process->GetTarget().GetImages()); +} + +void AppleObjCRuntime::Initialize() { + AppleObjCRuntimeV2::Initialize(); + AppleObjCRuntimeV1::Initialize(); +} + +void AppleObjCRuntime::Terminate() { + AppleObjCRuntimeV2::Terminate(); + AppleObjCRuntimeV1::Terminate(); +} + +llvm::Error AppleObjCRuntime::GetObjectDescription(Stream &str, + ValueObject &valobj) { + CompilerType compiler_type(valobj.GetCompilerType()); + bool is_signed; + // ObjC objects can only be pointers (or numbers that actually represents + // pointers but haven't been typecast, because reasons..) + if (!compiler_type.IsIntegerType(is_signed) && !compiler_type.IsPointerType()) + return llvm::createStringError("not a pointer type"); + + // Make the argument list: we pass one arg, the address of our pointer, to + // the print function. + Value val; + + if (!valobj.ResolveValue(val.GetScalar())) + return llvm::createStringError("pointer value could not be resolved"); + + // Value Objects may not have a process in their ExecutionContextRef. But we + // need to have one in the ref we pass down to eventually call description. + // Get it from the target if it isn't present. + ExecutionContext exe_ctx; + if (valobj.GetProcessSP()) { + exe_ctx = ExecutionContext(valobj.GetExecutionContextRef()); + } else { + exe_ctx.SetContext(valobj.GetTargetSP(), true); + if (!exe_ctx.HasProcessScope()) + return llvm::createStringError("no process"); + } + return GetObjectDescription(str, val, exe_ctx.GetBestExecutionContextScope()); +} + +llvm::Error +AppleObjCRuntime::GetObjectDescription(Stream &strm, Value &value, + ExecutionContextScope *exe_scope) { + if (!m_read_objc_library) + return llvm::createStringError("Objective-C runtime not loaded"); + + ExecutionContext exe_ctx; + exe_scope->CalculateExecutionContext(exe_ctx); + Process *process = exe_ctx.GetProcessPtr(); + if (!process) + return llvm::createStringError("no process"); + + // We need other parts of the exe_ctx, but the processes have to match. + assert(m_process == process); + + // Get the function address for the print function. + const Address *function_address = GetPrintForDebuggerAddr(); + if (!function_address) + return llvm::createStringError("no print function"); + + Target *target = exe_ctx.GetTargetPtr(); + CompilerType compiler_type = value.GetCompilerType(); + if (compiler_type) { + if (!TypeSystemClang::IsObjCObjectPointerType(compiler_type)) + return llvm::createStringError( + "Value doesn't point to an ObjC object.\n"); + } else { + // If it is not a pointer, see if we can make it into a pointer. + TypeSystemClangSP scratch_ts_sp = + ScratchTypeSystemClang::GetForTarget(*target); + if (!scratch_ts_sp) + return llvm::createStringError("no scratch type system"); + + CompilerType opaque_type = scratch_ts_sp->GetBasicType(eBasicTypeObjCID); + if (!opaque_type) + opaque_type = + scratch_ts_sp->GetBasicType(eBasicTypeVoid).GetPointerType(); + // value.SetContext(Value::eContextTypeClangType, opaque_type_ptr); + value.SetCompilerType(opaque_type); + } + + ValueList arg_value_list; + arg_value_list.PushValue(value); + + // This is the return value: + TypeSystemClangSP scratch_ts_sp = + ScratchTypeSystemClang::GetForTarget(*target); + if (!scratch_ts_sp) + return llvm::createStringError("no scratch type system"); + + CompilerType return_compiler_type = scratch_ts_sp->GetCStringType(true); + Value ret; + // ret.SetContext(Value::eContextTypeClangType, return_compiler_type); + ret.SetCompilerType(return_compiler_type); + + if (!exe_ctx.GetFramePtr()) { + Thread *thread = exe_ctx.GetThreadPtr(); + if (thread == nullptr) { + exe_ctx.SetThreadSP(process->GetThreadList().GetSelectedThread()); + thread = exe_ctx.GetThreadPtr(); + } + if (thread) { + exe_ctx.SetFrameSP(thread->GetSelectedFrame(DoNoSelectMostRelevantFrame)); + } + } + + // Now we're ready to call the function: + + DiagnosticManager diagnostics; + lldb::addr_t wrapper_struct_addr = LLDB_INVALID_ADDRESS; + + if (!m_print_object_caller_up) { + Status error; + m_print_object_caller_up.reset( + exe_scope->CalculateTarget()->GetFunctionCallerForLanguage( + eLanguageTypeObjC, return_compiler_type, *function_address, + arg_value_list, "objc-object-description", error)); + if (error.Fail()) { + m_print_object_caller_up.reset(); + return llvm::createStringError( + llvm::Twine( + "could not get function runner to call print for debugger " + "function: ") + + error.AsCString()); + } + m_print_object_caller_up->InsertFunction(exe_ctx, wrapper_struct_addr, + diagnostics); + } else { + m_print_object_caller_up->WriteFunctionArguments( + exe_ctx, wrapper_struct_addr, arg_value_list, diagnostics); + } + + EvaluateExpressionOptions options; + options.SetUnwindOnError(true); + options.SetTryAllThreads(true); + options.SetStopOthers(true); + options.SetIgnoreBreakpoints(true); + options.SetTimeout(process->GetUtilityExpressionTimeout()); + options.SetIsForUtilityExpr(true); + + ExpressionResults results = m_print_object_caller_up->ExecuteFunction( + exe_ctx, &wrapper_struct_addr, options, diagnostics, ret); + if (results != eExpressionCompleted) + return llvm::createStringError( + "could not evaluate print object function: " + toString(results)); + + addr_t result_ptr = ret.GetScalar().ULongLong(LLDB_INVALID_ADDRESS); + + char buf[512]; + size_t cstr_len = 0; + size_t full_buffer_len = sizeof(buf) - 1; + size_t curr_len = full_buffer_len; + while (curr_len == full_buffer_len) { + Status error; + curr_len = process->ReadCStringFromMemory(result_ptr + cstr_len, buf, + sizeof(buf), error); + strm.Write(buf, curr_len); + cstr_len += curr_len; + } + if (cstr_len > 0) + return llvm::Error::success(); + return llvm::createStringError("empty object description"); +} + +lldb::ModuleSP AppleObjCRuntime::GetObjCModule() { + ModuleSP module_sp(m_objc_module_wp.lock()); + if (module_sp) + return module_sp; + + Process *process = GetProcess(); + if (process) { + const ModuleList &modules = process->GetTarget().GetImages(); + for (uint32_t idx = 0; idx < modules.GetSize(); idx++) { + module_sp = modules.GetModuleAtIndex(idx); + if (AppleObjCRuntime::AppleIsModuleObjCLibrary(module_sp)) { + m_objc_module_wp = module_sp; + return module_sp; + } + } + } + return ModuleSP(); +} + +Address *AppleObjCRuntime::GetPrintForDebuggerAddr() { + if (!m_PrintForDebugger_addr) { + const ModuleList &modules = m_process->GetTarget().GetImages(); + + SymbolContextList contexts; + SymbolContext context; + + modules.FindSymbolsWithNameAndType(ConstString("_NSPrintForDebugger"), + eSymbolTypeCode, contexts); + if (contexts.IsEmpty()) { + modules.FindSymbolsWithNameAndType(ConstString("_CFPrintForDebugger"), + eSymbolTypeCode, contexts); + if (contexts.IsEmpty()) + return nullptr; + } + + contexts.GetContextAtIndex(0, context); + + m_PrintForDebugger_addr = + std::make_unique<Address>(context.symbol->GetAddress()); + } + + return m_PrintForDebugger_addr.get(); +} + +bool AppleObjCRuntime::CouldHaveDynamicValue(ValueObject &in_value) { + return in_value.GetCompilerType().IsPossibleDynamicType( + nullptr, + false, // do not check C++ + true); // check ObjC +} + +bool AppleObjCRuntime::GetDynamicTypeAndAddress( + ValueObject &in_value, lldb::DynamicValueType use_dynamic, + TypeAndOrName &class_type_or_name, Address &address, + Value::ValueType &value_type) { + return false; +} + +TypeAndOrName +AppleObjCRuntime::FixUpDynamicType(const TypeAndOrName &type_and_or_name, + ValueObject &static_value) { + CompilerType static_type(static_value.GetCompilerType()); + Flags static_type_flags(static_type.GetTypeInfo()); + + TypeAndOrName ret(type_and_or_name); + if (type_and_or_name.HasType()) { + // The type will always be the type of the dynamic object. If our parent's + // type was a pointer, then our type should be a pointer to the type of the + // dynamic object. If a reference, then the original type should be + // okay... + CompilerType orig_type = type_and_or_name.GetCompilerType(); + CompilerType corrected_type = orig_type; + if (static_type_flags.AllSet(eTypeIsPointer)) + corrected_type = orig_type.GetPointerType(); + ret.SetCompilerType(corrected_type); + } else { + // If we are here we need to adjust our dynamic type name to include the + // correct & or * symbol + std::string corrected_name(type_and_or_name.GetName().GetCString()); + if (static_type_flags.AllSet(eTypeIsPointer)) + corrected_name.append(" *"); + // the parent type should be a correctly pointer'ed or referenc'ed type + ret.SetCompilerType(static_type); + ret.SetName(corrected_name.c_str()); + } + return ret; +} + +bool AppleObjCRuntime::AppleIsModuleObjCLibrary(const ModuleSP &module_sp) { + if (module_sp) { + const FileSpec &module_file_spec = module_sp->GetFileSpec(); + static ConstString ObjCName("libobjc.A.dylib"); + + if (module_file_spec) { + if (module_file_spec.GetFilename() == ObjCName) + return true; + } + } + return false; +} + +// we use the version of Foundation to make assumptions about the ObjC runtime +// on a target +uint32_t AppleObjCRuntime::GetFoundationVersion() { + if (!m_Foundation_major) { + const ModuleList &modules = m_process->GetTarget().GetImages(); + for (uint32_t idx = 0; idx < modules.GetSize(); idx++) { + lldb::ModuleSP module_sp = modules.GetModuleAtIndex(idx); + if (!module_sp) + continue; + if (strcmp(module_sp->GetFileSpec().GetFilename().AsCString(""), + "Foundation") == 0) { + m_Foundation_major = module_sp->GetVersion().getMajor(); + return *m_Foundation_major; + } + } + return LLDB_INVALID_MODULE_VERSION; + } else + return *m_Foundation_major; +} + +void AppleObjCRuntime::GetValuesForGlobalCFBooleans(lldb::addr_t &cf_true, + lldb::addr_t &cf_false) { + cf_true = cf_false = LLDB_INVALID_ADDRESS; +} + +bool AppleObjCRuntime::IsModuleObjCLibrary(const ModuleSP &module_sp) { + return AppleIsModuleObjCLibrary(module_sp); +} + +bool AppleObjCRuntime::ReadObjCLibrary(const ModuleSP &module_sp) { + // Maybe check here and if we have a handler already, and the UUID of this + // module is the same as the one in the current module, then we don't have to + // reread it? + m_objc_trampoline_handler_up = std::make_unique<AppleObjCTrampolineHandler>( + m_process->shared_from_this(), module_sp); + if (m_objc_trampoline_handler_up != nullptr) { + m_read_objc_library = true; + return true; + } else + return false; +} + +ThreadPlanSP AppleObjCRuntime::GetStepThroughTrampolinePlan(Thread &thread, + bool stop_others) { + ThreadPlanSP thread_plan_sp; + if (m_objc_trampoline_handler_up) + thread_plan_sp = m_objc_trampoline_handler_up->GetStepThroughDispatchPlan( + thread, stop_others); + return thread_plan_sp; +} + +// Static Functions +ObjCLanguageRuntime::ObjCRuntimeVersions +AppleObjCRuntime::GetObjCVersion(Process *process, ModuleSP &objc_module_sp) { + if (!process) + return ObjCRuntimeVersions::eObjC_VersionUnknown; + + Target &target = process->GetTarget(); + if (target.GetArchitecture().GetTriple().getVendor() != + llvm::Triple::VendorType::Apple) + return ObjCRuntimeVersions::eObjC_VersionUnknown; + + for (ModuleSP module_sp : target.GetImages().Modules()) { + // One tricky bit here is that we might get called as part of the initial + // module loading, but before all the pre-run libraries get winnowed from + // the module list. So there might actually be an old and incorrect ObjC + // library sitting around in the list, and we don't want to look at that. + // That's why we call IsLoadedInTarget. + + if (AppleIsModuleObjCLibrary(module_sp) && + module_sp->IsLoadedInTarget(&target)) { + objc_module_sp = module_sp; + ObjectFile *ofile = module_sp->GetObjectFile(); + if (!ofile) + return ObjCRuntimeVersions::eObjC_VersionUnknown; + + SectionList *sections = module_sp->GetSectionList(); + if (!sections) + return ObjCRuntimeVersions::eObjC_VersionUnknown; + SectionSP v1_telltale_section_sp = + sections->FindSectionByName(ConstString("__OBJC")); + if (v1_telltale_section_sp) { + return ObjCRuntimeVersions::eAppleObjC_V1; + } + return ObjCRuntimeVersions::eAppleObjC_V2; + } + } + + return ObjCRuntimeVersions::eObjC_VersionUnknown; +} + +void AppleObjCRuntime::SetExceptionBreakpoints() { + const bool catch_bp = false; + const bool throw_bp = true; + const bool is_internal = true; + + if (!m_objc_exception_bp_sp) { + m_objc_exception_bp_sp = LanguageRuntime::CreateExceptionBreakpoint( + m_process->GetTarget(), GetLanguageType(), catch_bp, throw_bp, + is_internal); + if (m_objc_exception_bp_sp) + m_objc_exception_bp_sp->SetBreakpointKind("ObjC exception"); + } else + m_objc_exception_bp_sp->SetEnabled(true); +} + +void AppleObjCRuntime::ClearExceptionBreakpoints() { + if (!m_process) + return; + + if (m_objc_exception_bp_sp.get()) { + m_objc_exception_bp_sp->SetEnabled(false); + } +} + +bool AppleObjCRuntime::ExceptionBreakpointsAreSet() { + return m_objc_exception_bp_sp && m_objc_exception_bp_sp->IsEnabled(); +} + +bool AppleObjCRuntime::ExceptionBreakpointsExplainStop( + lldb::StopInfoSP stop_reason) { + if (!m_process) + return false; + + if (!stop_reason || stop_reason->GetStopReason() != eStopReasonBreakpoint) + return false; + + uint64_t break_site_id = stop_reason->GetValue(); + return m_process->GetBreakpointSiteList().StopPointSiteContainsBreakpoint( + break_site_id, m_objc_exception_bp_sp->GetID()); +} + +bool AppleObjCRuntime::CalculateHasNewLiteralsAndIndexing() { + if (!m_process) + return false; + + Target &target(m_process->GetTarget()); + + static ConstString s_method_signature( + "-[NSDictionary objectForKeyedSubscript:]"); + static ConstString s_arclite_method_signature( + "__arclite_objectForKeyedSubscript"); + + SymbolContextList sc_list; + + target.GetImages().FindSymbolsWithNameAndType(s_method_signature, + eSymbolTypeCode, sc_list); + if (sc_list.IsEmpty()) + target.GetImages().FindSymbolsWithNameAndType(s_arclite_method_signature, + eSymbolTypeCode, sc_list); + return !sc_list.IsEmpty(); +} + +lldb::SearchFilterSP AppleObjCRuntime::CreateExceptionSearchFilter() { + Target &target = m_process->GetTarget(); + + FileSpecList filter_modules; + if (target.GetArchitecture().GetTriple().getVendor() == llvm::Triple::Apple) { + filter_modules.Append(std::get<0>(GetExceptionThrowLocation())); + } + return target.GetSearchFilterForModuleList(&filter_modules); +} + +ValueObjectSP AppleObjCRuntime::GetExceptionObjectForThread( + ThreadSP thread_sp) { + auto *cpp_runtime = m_process->GetLanguageRuntime(eLanguageTypeC_plus_plus); + if (!cpp_runtime) return ValueObjectSP(); + auto cpp_exception = cpp_runtime->GetExceptionObjectForThread(thread_sp); + if (!cpp_exception) return ValueObjectSP(); + + auto descriptor = GetClassDescriptor(*cpp_exception); + if (!descriptor || !descriptor->IsValid()) return ValueObjectSP(); + + while (descriptor) { + ConstString class_name(descriptor->GetClassName()); + if (class_name == "NSException") + return cpp_exception; + descriptor = descriptor->GetSuperclass(); + } + + return ValueObjectSP(); +} + +/// Utility method for error handling in GetBacktraceThreadFromException. +/// \param msg The message to add to the log. +/// \return An invalid ThreadSP to be returned from +/// GetBacktraceThreadFromException. +[[nodiscard]] +static ThreadSP FailExceptionParsing(llvm::StringRef msg) { + Log *log = GetLog(LLDBLog::Language); + LLDB_LOG(log, "Failed getting backtrace from exception: {0}", msg); + return ThreadSP(); +} + +ThreadSP AppleObjCRuntime::GetBacktraceThreadFromException( + lldb::ValueObjectSP exception_sp) { + ValueObjectSP reserved_dict = + exception_sp->GetChildMemberWithName("reserved"); + if (!reserved_dict) + return FailExceptionParsing("Failed to get 'reserved' member."); + + reserved_dict = reserved_dict->GetSyntheticValue(); + if (!reserved_dict) + return FailExceptionParsing("Failed to get synthetic value."); + + TypeSystemClangSP scratch_ts_sp = + ScratchTypeSystemClang::GetForTarget(*exception_sp->GetTargetSP()); + if (!scratch_ts_sp) + return FailExceptionParsing("Failed to get scratch AST."); + CompilerType objc_id = scratch_ts_sp->GetBasicType(lldb::eBasicTypeObjCID); + ValueObjectSP return_addresses; + + auto objc_object_from_address = [&exception_sp, &objc_id](uint64_t addr, + const char *name) { + Value value(addr); + value.SetCompilerType(objc_id); + auto object = ValueObjectConstResult::Create( + exception_sp->GetTargetSP().get(), value, ConstString(name)); + object = object->GetDynamicValue(eDynamicDontRunTarget); + return object; + }; + + for (size_t idx = 0; idx < reserved_dict->GetNumChildrenIgnoringErrors(); + idx++) { + ValueObjectSP dict_entry = reserved_dict->GetChildAtIndex(idx); + + DataExtractor data; + data.SetAddressByteSize(dict_entry->GetProcessSP()->GetAddressByteSize()); + Status error; + dict_entry->GetData(data, error); + if (error.Fail()) return ThreadSP(); + + lldb::offset_t data_offset = 0; + auto dict_entry_key = data.GetAddress(&data_offset); + auto dict_entry_value = data.GetAddress(&data_offset); + + auto key_nsstring = objc_object_from_address(dict_entry_key, "key"); + StreamString key_summary; + if (lldb_private::formatters::NSStringSummaryProvider( + *key_nsstring, key_summary, TypeSummaryOptions()) && + !key_summary.Empty()) { + if (key_summary.GetString() == "\"callStackReturnAddresses\"") { + return_addresses = objc_object_from_address(dict_entry_value, + "callStackReturnAddresses"); + break; + } + } + } + + if (!return_addresses) + return FailExceptionParsing("Failed to get return addresses."); + auto frames_value = return_addresses->GetChildMemberWithName("_frames"); + if (!frames_value) + return FailExceptionParsing("Failed to get frames_value."); + addr_t frames_addr = frames_value->GetValueAsUnsigned(0); + auto count_value = return_addresses->GetChildMemberWithName("_cnt"); + if (!count_value) + return FailExceptionParsing("Failed to get count_value."); + size_t count = count_value->GetValueAsUnsigned(0); + auto ignore_value = return_addresses->GetChildMemberWithName("_ignore"); + if (!ignore_value) + return FailExceptionParsing("Failed to get ignore_value."); + size_t ignore = ignore_value->GetValueAsUnsigned(0); + + size_t ptr_size = m_process->GetAddressByteSize(); + std::vector<lldb::addr_t> pcs; + for (size_t idx = 0; idx < count; idx++) { + Status error; + addr_t pc = m_process->ReadPointerFromMemory( + frames_addr + (ignore + idx) * ptr_size, error); + pcs.push_back(pc); + } + + if (pcs.empty()) + return FailExceptionParsing("Failed to get PC list."); + + ThreadSP new_thread_sp(new HistoryThread(*m_process, 0, pcs)); + m_process->GetExtendedThreadList().AddThread(new_thread_sp); + return new_thread_sp; +} + +std::tuple<FileSpec, ConstString> +AppleObjCRuntime::GetExceptionThrowLocation() { + return std::make_tuple( + FileSpec("libobjc.A.dylib"), ConstString("objc_exception_throw")); +} + +void AppleObjCRuntime::ReadObjCLibraryIfNeeded(const ModuleList &module_list) { + if (!HasReadObjCLibrary()) { + std::lock_guard<std::recursive_mutex> guard(module_list.GetMutex()); + + size_t num_modules = module_list.GetSize(); + for (size_t i = 0; i < num_modules; i++) { + auto mod = module_list.GetModuleAtIndex(i); + if (IsModuleObjCLibrary(mod)) { + ReadObjCLibrary(mod); + break; + } + } + } +} + +void AppleObjCRuntime::ModulesDidLoad(const ModuleList &module_list) { + ReadObjCLibraryIfNeeded(module_list); +} diff --git a/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.h b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.h new file mode 100644 index 000000000000..da58d44db19a --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.h @@ -0,0 +1,135 @@ +//===-- AppleObjCRuntime.h --------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLEOBJCRUNTIME_H +#define LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLEOBJCRUNTIME_H + + +#include "AppleObjCTrampolineHandler.h" +#include "AppleThreadPlanStepThroughObjCTrampoline.h" +#include "lldb/Target/LanguageRuntime.h" +#include "lldb/lldb-private.h" + +#include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h" +#include <optional> + +namespace lldb_private { + +class AppleObjCRuntime : public lldb_private::ObjCLanguageRuntime { +public: + ~AppleObjCRuntime() override; + + // Static Functions + // Note there is no CreateInstance, Initialize & Terminate functions here, + // because + // you can't make an instance of this generic runtime. + + static char ID; + + static void Initialize(); + + static void Terminate(); + + bool isA(const void *ClassID) const override { + return ClassID == &ID || ObjCLanguageRuntime::isA(ClassID); + } + + static bool classof(const LanguageRuntime *runtime) { + return runtime->isA(&ID); + } + + // These are generic runtime functions: + llvm::Error GetObjectDescription(Stream &str, Value &value, + ExecutionContextScope *exe_scope) override; + + llvm::Error GetObjectDescription(Stream &str, ValueObject &object) override; + + bool CouldHaveDynamicValue(ValueObject &in_value) override; + + bool GetDynamicTypeAndAddress(ValueObject &in_value, + lldb::DynamicValueType use_dynamic, + TypeAndOrName &class_type_or_name, + Address &address, + Value::ValueType &value_type) override; + + TypeAndOrName FixUpDynamicType(const TypeAndOrName &type_and_or_name, + ValueObject &static_value) override; + + // These are the ObjC specific functions. + + bool IsModuleObjCLibrary(const lldb::ModuleSP &module_sp) override; + + bool ReadObjCLibrary(const lldb::ModuleSP &module_sp) override; + + bool HasReadObjCLibrary() override { return m_read_objc_library; } + + lldb::ThreadPlanSP GetStepThroughTrampolinePlan(Thread &thread, + bool stop_others) override; + + // Get the "libobjc.A.dylib" module from the current target if we can find + // it, also cache it once it is found to ensure quick lookups. + lldb::ModuleSP GetObjCModule(); + + // Sync up with the target + + void ModulesDidLoad(const ModuleList &module_list) override; + + void SetExceptionBreakpoints() override; + + void ClearExceptionBreakpoints() override; + + bool ExceptionBreakpointsAreSet() override; + + bool ExceptionBreakpointsExplainStop(lldb::StopInfoSP stop_reason) override; + + lldb::SearchFilterSP CreateExceptionSearchFilter() override; + + static std::tuple<FileSpec, ConstString> GetExceptionThrowLocation(); + + lldb::ValueObjectSP GetExceptionObjectForThread( + lldb::ThreadSP thread_sp) override; + + lldb::ThreadSP GetBacktraceThreadFromException( + lldb::ValueObjectSP thread_sp) override; + + uint32_t GetFoundationVersion(); + + virtual void GetValuesForGlobalCFBooleans(lldb::addr_t &cf_true, + lldb::addr_t &cf_false); + + virtual bool IsTaggedPointer (lldb::addr_t addr) { return false; } + +protected: + // Call CreateInstance instead. + AppleObjCRuntime(Process *process); + + bool CalculateHasNewLiteralsAndIndexing() override; + + static bool AppleIsModuleObjCLibrary(const lldb::ModuleSP &module_sp); + + static ObjCRuntimeVersions GetObjCVersion(Process *process, + lldb::ModuleSP &objc_module_sp); + + void ReadObjCLibraryIfNeeded(const ModuleList &module_list); + + Address *GetPrintForDebuggerAddr(); + + std::unique_ptr<Address> m_PrintForDebugger_addr; + bool m_read_objc_library; + std::unique_ptr<lldb_private::AppleObjCTrampolineHandler> + m_objc_trampoline_handler_up; + lldb::BreakpointSP m_objc_exception_bp_sp; + lldb::ModuleWP m_objc_module_wp; + std::unique_ptr<FunctionCaller> m_print_object_caller_up; + + std::optional<uint32_t> m_Foundation_major; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLEOBJCRUNTIME_H diff --git a/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV1.cpp b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV1.cpp new file mode 100644 index 000000000000..93168c23f354 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV1.cpp @@ -0,0 +1,431 @@ +//===-- AppleObjCRuntimeV1.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 "AppleObjCRuntimeV1.h" +#include "AppleObjCDeclVendor.h" +#include "AppleObjCTrampolineHandler.h" + +#include "clang/AST/Type.h" + +#include "Plugins/TypeSystem/Clang/TypeSystemClang.h" +#include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Expression/FunctionCaller.h" +#include "lldb/Expression/UtilityFunction.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Scalar.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/StreamString.h" + +#include <memory> +#include <vector> + +using namespace lldb; +using namespace lldb_private; + +char AppleObjCRuntimeV1::ID = 0; + +AppleObjCRuntimeV1::AppleObjCRuntimeV1(Process *process) + : AppleObjCRuntime(process), m_hash_signature(), + m_isa_hash_table_ptr(LLDB_INVALID_ADDRESS) {} + +// for V1 runtime we just try to return a class name as that is the minimum +// level of support required for the data formatters to work +bool AppleObjCRuntimeV1::GetDynamicTypeAndAddress( + ValueObject &in_value, lldb::DynamicValueType use_dynamic, + TypeAndOrName &class_type_or_name, Address &address, + Value::ValueType &value_type) { + class_type_or_name.Clear(); + value_type = Value::ValueType::Scalar; + if (CouldHaveDynamicValue(in_value)) { + auto class_descriptor(GetClassDescriptor(in_value)); + if (class_descriptor && class_descriptor->IsValid() && + class_descriptor->GetClassName()) { + const addr_t object_ptr = in_value.GetPointerValue(); + address.SetRawAddress(object_ptr); + class_type_or_name.SetName(class_descriptor->GetClassName()); + } + } + return !class_type_or_name.IsEmpty(); +} + +// Static Functions +lldb_private::LanguageRuntime * +AppleObjCRuntimeV1::CreateInstance(Process *process, + lldb::LanguageType language) { + // FIXME: This should be a MacOS or iOS process, and we need to look for the + // OBJC section to make + // sure we aren't using the V1 runtime. + if (language == eLanguageTypeObjC) { + ModuleSP objc_module_sp; + + if (AppleObjCRuntime::GetObjCVersion(process, objc_module_sp) == + ObjCRuntimeVersions::eAppleObjC_V1) + return new AppleObjCRuntimeV1(process); + else + return nullptr; + } else + return nullptr; +} + +void AppleObjCRuntimeV1::Initialize() { + PluginManager::RegisterPlugin( + GetPluginNameStatic(), "Apple Objective-C Language Runtime - Version 1", + CreateInstance, + /*command_callback = */ nullptr, GetBreakpointExceptionPrecondition); +} + +void AppleObjCRuntimeV1::Terminate() { + PluginManager::UnregisterPlugin(CreateInstance); +} + +BreakpointResolverSP +AppleObjCRuntimeV1::CreateExceptionResolver(const BreakpointSP &bkpt, + bool catch_bp, bool throw_bp) { + BreakpointResolverSP resolver_sp; + + if (throw_bp) + resolver_sp = std::make_shared<BreakpointResolverName>( + bkpt, std::get<1>(GetExceptionThrowLocation()).AsCString(), + eFunctionNameTypeBase, eLanguageTypeUnknown, Breakpoint::Exact, 0, + eLazyBoolNo); + // FIXME: don't do catch yet. + return resolver_sp; +} + +struct BufStruct { + char contents[2048]; +}; + +llvm::Expected<std::unique_ptr<UtilityFunction>> +AppleObjCRuntimeV1::CreateObjectChecker(std::string name, + ExecutionContext &exe_ctx) { + std::unique_ptr<BufStruct> buf(new BufStruct); + + int strformatsize = + snprintf(&buf->contents[0], sizeof(buf->contents), + "struct __objc_class " + " \n" + "{ " + " \n" + " struct __objc_class *isa; " + " \n" + " struct __objc_class *super_class; " + " \n" + " const char *name; " + " \n" + " // rest of struct elided because unused " + " \n" + "}; " + " \n" + " " + " \n" + "struct __objc_object " + " \n" + "{ " + " \n" + " struct __objc_class *isa; " + " \n" + "}; " + " \n" + " " + " \n" + "extern \"C\" void " + " \n" + "%s(void *$__lldb_arg_obj, void *$__lldb_arg_selector) " + " \n" + "{ " + " \n" + " struct __objc_object *obj = (struct " + "__objc_object*)$__lldb_arg_obj; \n" + " if ($__lldb_arg_obj == (void *)0) " + " \n" + " return; // nil is ok " + " (int)strlen(obj->isa->name); " + " \n" + "} " + " \n", + name.c_str()); + assert(strformatsize < (int)sizeof(buf->contents)); + UNUSED_IF_ASSERT_DISABLED(strformatsize); + + return GetTargetRef().CreateUtilityFunction(buf->contents, std::move(name), + eLanguageTypeC, exe_ctx); +} + +AppleObjCRuntimeV1::ClassDescriptorV1::ClassDescriptorV1( + ValueObject &isa_pointer) { + Initialize(isa_pointer.GetValueAsUnsigned(0), isa_pointer.GetProcessSP()); +} + +AppleObjCRuntimeV1::ClassDescriptorV1::ClassDescriptorV1( + ObjCISA isa, lldb::ProcessSP process_sp) { + Initialize(isa, process_sp); +} + +void AppleObjCRuntimeV1::ClassDescriptorV1::Initialize( + ObjCISA isa, lldb::ProcessSP process_sp) { + if (!isa || !process_sp) { + m_valid = false; + return; + } + + m_valid = true; + + Status error; + + m_isa = process_sp->ReadPointerFromMemory(isa, error); + + if (error.Fail()) { + m_valid = false; + return; + } + + uint32_t ptr_size = process_sp->GetAddressByteSize(); + + if (!IsPointerValid(m_isa, ptr_size)) { + m_valid = false; + return; + } + + m_parent_isa = process_sp->ReadPointerFromMemory(m_isa + ptr_size, error); + + if (error.Fail()) { + m_valid = false; + return; + } + + if (!IsPointerValid(m_parent_isa, ptr_size, true)) { + m_valid = false; + return; + } + + lldb::addr_t name_ptr = + process_sp->ReadPointerFromMemory(m_isa + 2 * ptr_size, error); + + if (error.Fail()) { + m_valid = false; + return; + } + + lldb::WritableDataBufferSP buffer_sp(new DataBufferHeap(1024, 0)); + + size_t count = process_sp->ReadCStringFromMemory( + name_ptr, (char *)buffer_sp->GetBytes(), 1024, error); + + if (error.Fail()) { + m_valid = false; + return; + } + + if (count) + m_name = ConstString(reinterpret_cast<const char *>(buffer_sp->GetBytes())); + else + m_name = ConstString(); + + m_instance_size = process_sp->ReadUnsignedIntegerFromMemory( + m_isa + 5 * ptr_size, ptr_size, 0, error); + + if (error.Fail()) { + m_valid = false; + return; + } + + m_process_wp = lldb::ProcessWP(process_sp); +} + +AppleObjCRuntime::ClassDescriptorSP +AppleObjCRuntimeV1::ClassDescriptorV1::GetSuperclass() { + if (!m_valid) + return AppleObjCRuntime::ClassDescriptorSP(); + ProcessSP process_sp = m_process_wp.lock(); + if (!process_sp) + return AppleObjCRuntime::ClassDescriptorSP(); + return ObjCLanguageRuntime::ClassDescriptorSP( + new AppleObjCRuntimeV1::ClassDescriptorV1(m_parent_isa, process_sp)); +} + +AppleObjCRuntime::ClassDescriptorSP +AppleObjCRuntimeV1::ClassDescriptorV1::GetMetaclass() const { + return ClassDescriptorSP(); +} + +bool AppleObjCRuntimeV1::ClassDescriptorV1::Describe( + std::function<void(ObjCLanguageRuntime::ObjCISA)> const &superclass_func, + std::function<bool(const char *, const char *)> const &instance_method_func, + std::function<bool(const char *, const char *)> const &class_method_func, + std::function<bool(const char *, const char *, lldb::addr_t, + uint64_t)> const &ivar_func) const { + return false; +} + +lldb::addr_t AppleObjCRuntimeV1::GetTaggedPointerObfuscator() { + return 0; +} + +lldb::addr_t AppleObjCRuntimeV1::GetISAHashTablePointer() { + if (m_isa_hash_table_ptr == LLDB_INVALID_ADDRESS) { + ModuleSP objc_module_sp(GetObjCModule()); + + if (!objc_module_sp) + return LLDB_INVALID_ADDRESS; + + static ConstString g_objc_debug_class_hash("_objc_debug_class_hash"); + + const Symbol *symbol = objc_module_sp->FindFirstSymbolWithNameAndType( + g_objc_debug_class_hash, lldb::eSymbolTypeData); + if (symbol && symbol->ValueIsAddress()) { + Process *process = GetProcess(); + if (process) { + + lldb::addr_t objc_debug_class_hash_addr = + symbol->GetAddressRef().GetLoadAddress(&process->GetTarget()); + + if (objc_debug_class_hash_addr != LLDB_INVALID_ADDRESS) { + Status error; + lldb::addr_t objc_debug_class_hash_ptr = + process->ReadPointerFromMemory(objc_debug_class_hash_addr, error); + if (objc_debug_class_hash_ptr != 0 && + objc_debug_class_hash_ptr != LLDB_INVALID_ADDRESS) { + m_isa_hash_table_ptr = objc_debug_class_hash_ptr; + } + } + } + } + } + return m_isa_hash_table_ptr; +} + +void AppleObjCRuntimeV1::UpdateISAToDescriptorMapIfNeeded() { + // TODO: implement HashTableSignature... + Process *process = GetProcess(); + + if (process) { + // Update the process stop ID that indicates the last time we updated the + // map, whether it was successful or not. + m_isa_to_descriptor_stop_id = process->GetStopID(); + + Log *log = GetLog(LLDBLog::Process); + + ProcessSP process_sp = process->shared_from_this(); + + ModuleSP objc_module_sp(GetObjCModule()); + + if (!objc_module_sp) + return; + + lldb::addr_t hash_table_ptr = GetISAHashTablePointer(); + if (hash_table_ptr != LLDB_INVALID_ADDRESS) { + // Read the NXHashTable struct: + // + // typedef struct { + // const NXHashTablePrototype *prototype; + // unsigned count; + // unsigned nbBuckets; + // void *buckets; + // const void *info; + // } NXHashTable; + + Status error; + DataBufferHeap buffer(1024, 0); + if (process->ReadMemory(hash_table_ptr, buffer.GetBytes(), 20, error) == + 20) { + const uint32_t addr_size = m_process->GetAddressByteSize(); + const ByteOrder byte_order = m_process->GetByteOrder(); + DataExtractor data(buffer.GetBytes(), buffer.GetByteSize(), byte_order, + addr_size); + lldb::offset_t offset = addr_size; // Skip prototype + const uint32_t count = data.GetU32(&offset); + const uint32_t num_buckets = data.GetU32(&offset); + const addr_t buckets_ptr = data.GetAddress(&offset); + if (m_hash_signature.NeedsUpdate(count, num_buckets, buckets_ptr)) { + m_hash_signature.UpdateSignature(count, num_buckets, buckets_ptr); + + const uint32_t data_size = num_buckets * 2 * sizeof(uint32_t); + buffer.SetByteSize(data_size); + + if (process->ReadMemory(buckets_ptr, buffer.GetBytes(), data_size, + error) == data_size) { + data.SetData(buffer.GetBytes(), buffer.GetByteSize(), byte_order); + offset = 0; + for (uint32_t bucket_idx = 0; bucket_idx < num_buckets; + ++bucket_idx) { + const uint32_t bucket_isa_count = data.GetU32(&offset); + const lldb::addr_t bucket_data = data.GetU32(&offset); + + if (bucket_isa_count == 0) + continue; + + ObjCISA isa; + if (bucket_isa_count == 1) { + // When we only have one entry in the bucket, the bucket data + // is the "isa" + isa = bucket_data; + if (isa) { + if (!ISAIsCached(isa)) { + ClassDescriptorSP descriptor_sp( + new ClassDescriptorV1(isa, process_sp)); + + if (log && log->GetVerbose()) + LLDB_LOGF(log, + "AppleObjCRuntimeV1 added (ObjCISA)0x%" PRIx64 + " from _objc_debug_class_hash to " + "isa->descriptor cache", + isa); + + AddClass(isa, descriptor_sp); + } + } + } else { + // When we have more than one entry in the bucket, the bucket + // data is a pointer to an array of "isa" values + addr_t isa_addr = bucket_data; + for (uint32_t isa_idx = 0; isa_idx < bucket_isa_count; + ++isa_idx, isa_addr += addr_size) { + isa = m_process->ReadPointerFromMemory(isa_addr, error); + + if (isa && isa != LLDB_INVALID_ADDRESS) { + if (!ISAIsCached(isa)) { + ClassDescriptorSP descriptor_sp( + new ClassDescriptorV1(isa, process_sp)); + + if (log && log->GetVerbose()) + LLDB_LOGF( + log, + "AppleObjCRuntimeV1 added (ObjCISA)0x%" PRIx64 + " from _objc_debug_class_hash to isa->descriptor " + "cache", + isa); + + AddClass(isa, descriptor_sp); + } + } + } + } + } + } + } + } + } + } else { + m_isa_to_descriptor_stop_id = UINT32_MAX; + } +} + +DeclVendor *AppleObjCRuntimeV1::GetDeclVendor() { + return nullptr; +} diff --git a/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV1.h b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV1.h new file mode 100644 index 000000000000..46d8e89c906e --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV1.h @@ -0,0 +1,160 @@ +//===-- AppleObjCRuntimeV1.h ------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLEOBJCRUNTIMEV1_H +#define LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLEOBJCRUNTIMEV1_H + +#include "AppleObjCRuntime.h" +#include "lldb/lldb-private.h" + +#include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h" + +namespace lldb_private { + +class AppleObjCRuntimeV1 : public AppleObjCRuntime { +public: + ~AppleObjCRuntimeV1() override = default; + + // Static Functions + static void Initialize(); + + static void Terminate(); + + static lldb_private::LanguageRuntime * + CreateInstance(Process *process, lldb::LanguageType language); + + static llvm::StringRef GetPluginNameStatic() { return "apple-objc-v1"; } + + static char ID; + + bool isA(const void *ClassID) const override { + return ClassID == &ID || AppleObjCRuntime::isA(ClassID); + } + + static bool classof(const LanguageRuntime *runtime) { + return runtime->isA(&ID); + } + + lldb::addr_t GetTaggedPointerObfuscator(); + + class ClassDescriptorV1 : public ObjCLanguageRuntime::ClassDescriptor { + public: + ClassDescriptorV1(ValueObject &isa_pointer); + ClassDescriptorV1(ObjCISA isa, lldb::ProcessSP process_sp); + + ~ClassDescriptorV1() override = default; + + ConstString GetClassName() override { return m_name; } + + ClassDescriptorSP GetSuperclass() override; + + ClassDescriptorSP GetMetaclass() const override; + + bool IsValid() override { return m_valid; } + + // v1 does not support tagged pointers + bool GetTaggedPointerInfo(uint64_t *info_bits = nullptr, + uint64_t *value_bits = nullptr, + uint64_t *payload = nullptr) override { + return false; + } + + bool GetTaggedPointerInfoSigned(uint64_t *info_bits = nullptr, + int64_t *value_bits = nullptr, + uint64_t *payload = nullptr) override { + return false; + } + + uint64_t GetInstanceSize() override { return m_instance_size; } + + ObjCISA GetISA() override { return m_isa; } + + bool + Describe(std::function<void(ObjCLanguageRuntime::ObjCISA)> const + &superclass_func, + std::function<bool(const char *, const char *)> const + &instance_method_func, + std::function<bool(const char *, const char *)> const + &class_method_func, + std::function<bool(const char *, const char *, lldb::addr_t, + uint64_t)> const &ivar_func) const override; + + protected: + void Initialize(ObjCISA isa, lldb::ProcessSP process_sp); + + private: + ConstString m_name; + ObjCISA m_isa; + ObjCISA m_parent_isa; + bool m_valid; + lldb::ProcessWP m_process_wp; + uint64_t m_instance_size; + }; + + // These are generic runtime functions: + bool GetDynamicTypeAndAddress(ValueObject &in_value, + lldb::DynamicValueType use_dynamic, + TypeAndOrName &class_type_or_name, + Address &address, + Value::ValueType &value_type) override; + + llvm::Expected<std::unique_ptr<UtilityFunction>> + CreateObjectChecker(std::string, ExecutionContext &exe_ctx) override; + + // PluginInterface protocol + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } + + ObjCRuntimeVersions GetRuntimeVersion() const override { + return ObjCRuntimeVersions::eAppleObjC_V1; + } + + void UpdateISAToDescriptorMapIfNeeded() override; + + DeclVendor *GetDeclVendor() override; + +protected: + lldb::BreakpointResolverSP + CreateExceptionResolver(const lldb::BreakpointSP &bkpt, + bool catch_bp, bool throw_bp) override; + + class HashTableSignature { + public: + HashTableSignature() = default; + + bool NeedsUpdate(uint32_t count, uint32_t num_buckets, + lldb::addr_t buckets_ptr) { + return m_count != count || m_num_buckets != num_buckets || + m_buckets_ptr != buckets_ptr; + } + + void UpdateSignature(uint32_t count, uint32_t num_buckets, + lldb::addr_t buckets_ptr) { + m_count = count; + m_num_buckets = num_buckets; + m_buckets_ptr = buckets_ptr; + } + + protected: + uint32_t m_count = 0; + uint32_t m_num_buckets = 0; + lldb::addr_t m_buckets_ptr = LLDB_INVALID_ADDRESS; + }; + + lldb::addr_t GetISAHashTablePointer(); + + HashTableSignature m_hash_signature; + lldb::addr_t m_isa_hash_table_ptr; + std::unique_ptr<DeclVendor> m_decl_vendor_up; + +private: + AppleObjCRuntimeV1(Process *process); +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLEOBJCRUNTIMEV1_H diff --git a/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp new file mode 100644 index 000000000000..9409497f1c81 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp @@ -0,0 +1,3470 @@ +//===-- AppleObjCRuntimeV2.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 "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h" +#include "Plugins/TypeSystem/Clang/TypeSystemClang.h" + +#include "lldb/Core/Debugger.h" +#include "lldb/Core/DebuggerEvents.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/Section.h" +#include "lldb/Core/ValueObjectConstResult.h" +#include "lldb/Core/ValueObjectVariable.h" +#include "lldb/Expression/DiagnosticManager.h" +#include "lldb/Expression/FunctionCaller.h" +#include "lldb/Expression/UtilityFunction.h" +#include "lldb/Host/OptionParser.h" +#include "lldb/Interpreter/CommandObject.h" +#include "lldb/Interpreter/CommandObjectMultiword.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionArgParser.h" +#include "lldb/Interpreter/OptionValueBoolean.h" +#include "lldb/Symbol/CompilerType.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Symbol/TypeList.h" +#include "lldb/Symbol/VariableList.h" +#include "lldb/Target/ABI.h" +#include "lldb/Target/DynamicLoader.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/LanguageRuntime.h" +#include "lldb/Target/Platform.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/StackFrameRecognizer.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/ConstString.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 "lldb/Utility/StreamString.h" +#include "lldb/Utility/Timer.h" +#include "lldb/lldb-enumerations.h" + +#include "AppleObjCClassDescriptorV2.h" +#include "AppleObjCDeclVendor.h" +#include "AppleObjCRuntimeV2.h" +#include "AppleObjCTrampolineHandler.h" +#include "AppleObjCTypeEncodingParser.h" + +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclObjC.h" +#include "clang/Basic/TargetInfo.h" +#include "llvm/ADT/ScopeExit.h" + +#include <cstdint> +#include <memory> +#include <string> +#include <vector> + +using namespace lldb; +using namespace lldb_private; + +char AppleObjCRuntimeV2::ID = 0; + +static const char *g_get_dynamic_class_info_name = + "__lldb_apple_objc_v2_get_dynamic_class_info"; + +static const char *g_get_dynamic_class_info_body = R"( + +extern "C" +{ + size_t strlen(const char *); + char *strncpy (char * s1, const char * s2, size_t n); + int printf(const char * format, ...); +} +#define DEBUG_PRINTF(fmt, ...) if (should_log) printf(fmt, ## __VA_ARGS__) + +typedef struct _NXMapTable { + void *prototype; + unsigned num_classes; + unsigned num_buckets_minus_one; + void *buckets; +} NXMapTable; + +#define NX_MAPNOTAKEY ((void *)(-1)) + +typedef struct BucketInfo +{ + const char *name_ptr; + Class isa; +} BucketInfo; + +struct ClassInfo +{ + Class isa; + uint32_t hash; +} __attribute__((__packed__)); + +uint32_t +__lldb_apple_objc_v2_get_dynamic_class_info (void *gdb_objc_realized_classes_ptr, + void *class_infos_ptr, + uint32_t class_infos_byte_size, + uint32_t should_log) +{ + DEBUG_PRINTF ("gdb_objc_realized_classes_ptr = %p\n", gdb_objc_realized_classes_ptr); + DEBUG_PRINTF ("class_infos_ptr = %p\n", class_infos_ptr); + DEBUG_PRINTF ("class_infos_byte_size = %u\n", class_infos_byte_size); + const NXMapTable *grc = (const NXMapTable *)gdb_objc_realized_classes_ptr; + if (grc) + { + const unsigned num_classes = grc->num_classes; + DEBUG_PRINTF ("num_classes = %u\n", grc->num_classes); + if (class_infos_ptr) + { + const unsigned num_buckets_minus_one = grc->num_buckets_minus_one; + DEBUG_PRINTF ("num_buckets_minus_one = %u\n", num_buckets_minus_one); + + const size_t max_class_infos = class_infos_byte_size/sizeof(ClassInfo); + DEBUG_PRINTF ("max_class_infos = %u\n", max_class_infos); + + ClassInfo *class_infos = (ClassInfo *)class_infos_ptr; + BucketInfo *buckets = (BucketInfo *)grc->buckets; + + uint32_t idx = 0; + for (unsigned i=0; i<=num_buckets_minus_one; ++i) + { + if (buckets[i].name_ptr != NX_MAPNOTAKEY) + { + if (idx < max_class_infos) + { + const char *s = buckets[i].name_ptr; + uint32_t h = 5381; + for (unsigned char c = *s; c; c = *++s) + h = ((h << 5) + h) + c; + class_infos[idx].hash = h; + class_infos[idx].isa = buckets[i].isa; + DEBUG_PRINTF ("[%u] isa = %8p %s\n", idx, class_infos[idx].isa, buckets[i].name_ptr); + } + ++idx; + } + } + if (idx < max_class_infos) + { + class_infos[idx].isa = NULL; + class_infos[idx].hash = 0; + } + } + return num_classes; + } + return 0; +} + +)"; + +static const char *g_get_dynamic_class_info2_name = + "__lldb_apple_objc_v2_get_dynamic_class_info2"; + +static const char *g_get_dynamic_class_info2_body = R"( + +extern "C" { + int printf(const char * format, ...); + void free(void *ptr); + Class* objc_copyRealizedClassList_nolock(unsigned int *outCount); + const char* objc_debug_class_getNameRaw(Class cls); +} + +#define DEBUG_PRINTF(fmt, ...) if (should_log) printf(fmt, ## __VA_ARGS__) + +struct ClassInfo +{ + Class isa; + uint32_t hash; +} __attribute__((__packed__)); + +uint32_t +__lldb_apple_objc_v2_get_dynamic_class_info2(void *gdb_objc_realized_classes_ptr, + void *class_infos_ptr, + uint32_t class_infos_byte_size, + uint32_t should_log) +{ + DEBUG_PRINTF ("class_infos_ptr = %p\n", class_infos_ptr); + DEBUG_PRINTF ("class_infos_byte_size = %u\n", class_infos_byte_size); + + const size_t max_class_infos = class_infos_byte_size/sizeof(ClassInfo); + DEBUG_PRINTF ("max_class_infos = %u\n", max_class_infos); + + ClassInfo *class_infos = (ClassInfo *)class_infos_ptr; + + uint32_t count = 0; + Class* realized_class_list = objc_copyRealizedClassList_nolock(&count); + DEBUG_PRINTF ("count = %u\n", count); + + uint32_t idx = 0; + for (uint32_t i=0; i<count; ++i) + { + if (idx < max_class_infos) + { + Class isa = realized_class_list[i]; + const char *name_ptr = objc_debug_class_getNameRaw(isa); + if (!name_ptr) + continue; + const char *s = name_ptr; + uint32_t h = 5381; + for (unsigned char c = *s; c; c = *++s) + h = ((h << 5) + h) + c; + class_infos[idx].hash = h; + class_infos[idx].isa = isa; + DEBUG_PRINTF ("[%u] isa = %8p %s\n", idx, class_infos[idx].isa, name_ptr); + } + idx++; + } + + if (idx < max_class_infos) + { + class_infos[idx].isa = NULL; + class_infos[idx].hash = 0; + } + + free(realized_class_list); + return count; +} +)"; + +static const char *g_get_dynamic_class_info3_name = + "__lldb_apple_objc_v2_get_dynamic_class_info3"; + +static const char *g_get_dynamic_class_info3_body = R"( + +extern "C" { + int printf(const char * format, ...); + void free(void *ptr); + size_t objc_getRealizedClassList_trylock(Class *buffer, size_t len); + const char* objc_debug_class_getNameRaw(Class cls); + const char* class_getName(Class cls); +} + +#define DEBUG_PRINTF(fmt, ...) if (should_log) printf(fmt, ## __VA_ARGS__) + +struct ClassInfo +{ + Class isa; + uint32_t hash; +} __attribute__((__packed__)); + +uint32_t +__lldb_apple_objc_v2_get_dynamic_class_info3(void *gdb_objc_realized_classes_ptr, + void *class_infos_ptr, + uint32_t class_infos_byte_size, + void *class_buffer, + uint32_t class_buffer_len, + uint32_t should_log) +{ + DEBUG_PRINTF ("class_infos_ptr = %p\n", class_infos_ptr); + DEBUG_PRINTF ("class_infos_byte_size = %u\n", class_infos_byte_size); + + const size_t max_class_infos = class_infos_byte_size/sizeof(ClassInfo); + DEBUG_PRINTF ("max_class_infos = %u\n", max_class_infos); + + ClassInfo *class_infos = (ClassInfo *)class_infos_ptr; + + Class *realized_class_list = (Class*)class_buffer; + + uint32_t count = objc_getRealizedClassList_trylock(realized_class_list, + class_buffer_len); + DEBUG_PRINTF ("count = %u\n", count); + + uint32_t idx = 0; + for (uint32_t i=0; i<count; ++i) + { + if (idx < max_class_infos) + { + Class isa = realized_class_list[i]; + const char *name_ptr = objc_debug_class_getNameRaw(isa); + if (!name_ptr) { + class_getName(isa); // Realize name of lazy classes. + name_ptr = objc_debug_class_getNameRaw(isa); + } + if (!name_ptr) + continue; + const char *s = name_ptr; + uint32_t h = 5381; + for (unsigned char c = *s; c; c = *++s) + h = ((h << 5) + h) + c; + class_infos[idx].hash = h; + class_infos[idx].isa = isa; + DEBUG_PRINTF ("[%u] isa = %8p %s\n", idx, class_infos[idx].isa, name_ptr); + } + idx++; + } + + if (idx < max_class_infos) + { + class_infos[idx].isa = NULL; + class_infos[idx].hash = 0; + } + + return count; +} +)"; + +// We'll substitute in class_getName or class_getNameRaw depending +// on which is present. +static const char *g_shared_cache_class_name_funcptr = R"( +extern "C" +{ + const char *%s(void *objc_class); + const char *(*class_name_lookup_func)(void *) = %s; +} +)"; + +static const char *g_get_shared_cache_class_info_name = + "__lldb_apple_objc_v2_get_shared_cache_class_info"; + +static const char *g_get_shared_cache_class_info_body = R"( + +extern "C" +{ + size_t strlen(const char *); + char *strncpy (char * s1, const char * s2, size_t n); + int printf(const char * format, ...); +} + +#define DEBUG_PRINTF(fmt, ...) if (should_log) printf(fmt, ## __VA_ARGS__) + + +struct objc_classheader_t { + int32_t clsOffset; + int32_t hiOffset; +}; + +struct objc_classheader_v16_t { + uint64_t isDuplicate : 1, + objectCacheOffset : 47, // Offset from the shared cache base + dylibObjCIndex : 16; +}; + +struct objc_clsopt_t { + uint32_t capacity; + uint32_t occupied; + uint32_t shift; + uint32_t mask; + uint32_t zero; + uint32_t unused; + uint64_t salt; + uint32_t scramble[256]; + uint8_t tab[0]; // tab[mask+1] + // uint8_t checkbytes[capacity]; + // int32_t offset[capacity]; + // objc_classheader_t clsOffsets[capacity]; + // uint32_t duplicateCount; + // objc_classheader_t duplicateOffsets[duplicateCount]; +}; + +struct objc_clsopt_v16_t { + uint32_t version; + uint32_t capacity; + uint32_t occupied; + uint32_t shift; + uint32_t mask; + uint32_t zero; + uint64_t salt; + uint32_t scramble[256]; + uint8_t tab[0]; // tab[mask+1] + // uint8_t checkbytes[capacity]; + // int32_t offset[capacity]; + // objc_classheader_t clsOffsets[capacity]; + // uint32_t duplicateCount; + // objc_classheader_t duplicateOffsets[duplicateCount]; +}; + +struct objc_opt_t { + uint32_t version; + int32_t selopt_offset; + int32_t headeropt_offset; + int32_t clsopt_offset; +}; + +struct objc_opt_v14_t { + uint32_t version; + uint32_t flags; + int32_t selopt_offset; + int32_t headeropt_offset; + int32_t clsopt_offset; +}; + +struct objc_opt_v16_t { + uint32_t version; + uint32_t flags; + int32_t selopt_offset; + int32_t headeropt_ro_offset; + int32_t unused_clsopt_offset; + int32_t unused_protocolopt_offset; + int32_t headeropt_rw_offset; + int32_t unused_protocolopt2_offset; + int32_t largeSharedCachesClassOffset; + int32_t largeSharedCachesProtocolOffset; + uint64_t relativeMethodSelectorBaseAddressCacheOffset; +}; + +struct ClassInfo +{ + Class isa; + uint32_t hash; +} __attribute__((__packed__)); + +uint32_t +__lldb_apple_objc_v2_get_shared_cache_class_info (void *objc_opt_ro_ptr, + void *shared_cache_base_ptr, + void *class_infos_ptr, + uint64_t *relative_selector_offset, + uint32_t class_infos_byte_size, + uint32_t should_log) +{ + *relative_selector_offset = 0; + uint32_t idx = 0; + DEBUG_PRINTF ("objc_opt_ro_ptr = %p\n", objc_opt_ro_ptr); + DEBUG_PRINTF ("shared_cache_base_ptr = %p\n", shared_cache_base_ptr); + DEBUG_PRINTF ("class_infos_ptr = %p\n", class_infos_ptr); + DEBUG_PRINTF ("class_infos_byte_size = %u (%llu class infos)\n", class_infos_byte_size, (uint64_t)(class_infos_byte_size/sizeof(ClassInfo))); + if (objc_opt_ro_ptr) + { + const objc_opt_t *objc_opt = (objc_opt_t *)objc_opt_ro_ptr; + const objc_opt_v14_t* objc_opt_v14 = (objc_opt_v14_t*)objc_opt_ro_ptr; + const objc_opt_v16_t* objc_opt_v16 = (objc_opt_v16_t*)objc_opt_ro_ptr; + if (objc_opt->version >= 16) + { + *relative_selector_offset = objc_opt_v16->relativeMethodSelectorBaseAddressCacheOffset; + DEBUG_PRINTF ("objc_opt->version = %u\n", objc_opt_v16->version); + DEBUG_PRINTF ("objc_opt->flags = %u\n", objc_opt_v16->flags); + DEBUG_PRINTF ("objc_opt->selopt_offset = %d\n", objc_opt_v16->selopt_offset); + DEBUG_PRINTF ("objc_opt->headeropt_ro_offset = %d\n", objc_opt_v16->headeropt_ro_offset); + DEBUG_PRINTF ("objc_opt->relativeMethodSelectorBaseAddressCacheOffset = %d\n", *relative_selector_offset); + } + else if (objc_opt->version >= 14) + { + DEBUG_PRINTF ("objc_opt->version = %u\n", objc_opt_v14->version); + DEBUG_PRINTF ("objc_opt->flags = %u\n", objc_opt_v14->flags); + DEBUG_PRINTF ("objc_opt->selopt_offset = %d\n", objc_opt_v14->selopt_offset); + DEBUG_PRINTF ("objc_opt->headeropt_offset = %d\n", objc_opt_v14->headeropt_offset); + DEBUG_PRINTF ("objc_opt->clsopt_offset = %d\n", objc_opt_v14->clsopt_offset); + } + else + { + DEBUG_PRINTF ("objc_opt->version = %u\n", objc_opt->version); + DEBUG_PRINTF ("objc_opt->selopt_offset = %d\n", objc_opt->selopt_offset); + DEBUG_PRINTF ("objc_opt->headeropt_offset = %d\n", objc_opt->headeropt_offset); + DEBUG_PRINTF ("objc_opt->clsopt_offset = %d\n", objc_opt->clsopt_offset); + } + + if (objc_opt->version == 16) + { + const objc_clsopt_v16_t* clsopt = (const objc_clsopt_v16_t*)((uint8_t *)objc_opt + objc_opt_v16->largeSharedCachesClassOffset); + const size_t max_class_infos = class_infos_byte_size/sizeof(ClassInfo); + + DEBUG_PRINTF("max_class_infos = %llu\n", (uint64_t)max_class_infos); + + ClassInfo *class_infos = (ClassInfo *)class_infos_ptr; + + const uint8_t *checkbytes = &clsopt->tab[clsopt->mask+1]; + const int32_t *offsets = (const int32_t *)(checkbytes + clsopt->capacity); + const objc_classheader_v16_t *classOffsets = (const objc_classheader_v16_t *)(offsets + clsopt->capacity); + + DEBUG_PRINTF ("clsopt->capacity = %u\n", clsopt->capacity); + DEBUG_PRINTF ("clsopt->mask = 0x%8.8x\n", clsopt->mask); + DEBUG_PRINTF ("classOffsets = %p\n", classOffsets); + + for (uint32_t i=0; i<clsopt->capacity; ++i) + { + const uint64_t objectCacheOffset = classOffsets[i].objectCacheOffset; + DEBUG_PRINTF("objectCacheOffset[%u] = %u\n", i, objectCacheOffset); + + if (classOffsets[i].isDuplicate) { + DEBUG_PRINTF("isDuplicate = true\n"); + continue; // duplicate + } + + if (objectCacheOffset == 0) { + DEBUG_PRINTF("objectCacheOffset == invalidEntryOffset\n"); + continue; // invalid offset + } + + if (class_infos && idx < max_class_infos) + { + class_infos[idx].isa = (Class)((uint8_t *)shared_cache_base_ptr + objectCacheOffset); + + // Lookup the class name. + const char *name = class_name_lookup_func(class_infos[idx].isa); + DEBUG_PRINTF("[%u] isa = %8p %s\n", idx, class_infos[idx].isa, name); + + // Hash the class name so we don't have to read it. + const char *s = name; + uint32_t h = 5381; + for (unsigned char c = *s; c; c = *++s) + { + // class_getName demangles swift names and the hash must + // be calculated on the mangled name. hash==0 means lldb + // will fetch the mangled name and compute the hash in + // ParseClassInfoArray. + if (c == '.') + { + h = 0; + break; + } + h = ((h << 5) + h) + c; + } + class_infos[idx].hash = h; + } + else + { + DEBUG_PRINTF("not(class_infos && idx < max_class_infos)\n"); + } + ++idx; + } + + const uint32_t *duplicate_count_ptr = (uint32_t *)&classOffsets[clsopt->capacity]; + const uint32_t duplicate_count = *duplicate_count_ptr; + const objc_classheader_v16_t *duplicateClassOffsets = (const objc_classheader_v16_t *)(&duplicate_count_ptr[1]); + + DEBUG_PRINTF ("duplicate_count = %u\n", duplicate_count); + DEBUG_PRINTF ("duplicateClassOffsets = %p\n", duplicateClassOffsets); + + for (uint32_t i=0; i<duplicate_count; ++i) + { + const uint64_t objectCacheOffset = classOffsets[i].objectCacheOffset; + DEBUG_PRINTF("objectCacheOffset[%u] = %u\n", i, objectCacheOffset); + + if (classOffsets[i].isDuplicate) { + DEBUG_PRINTF("isDuplicate = true\n"); + continue; // duplicate + } + + if (objectCacheOffset == 0) { + DEBUG_PRINTF("objectCacheOffset == invalidEntryOffset\n"); + continue; // invalid offset + } + + if (class_infos && idx < max_class_infos) + { + class_infos[idx].isa = (Class)((uint8_t *)shared_cache_base_ptr + objectCacheOffset); + + // Lookup the class name. + const char *name = class_name_lookup_func(class_infos[idx].isa); + DEBUG_PRINTF("[%u] isa = %8p %s\n", idx, class_infos[idx].isa, name); + + // Hash the class name so we don't have to read it. + const char *s = name; + uint32_t h = 5381; + for (unsigned char c = *s; c; c = *++s) + { + // class_getName demangles swift names and the hash must + // be calculated on the mangled name. hash==0 means lldb + // will fetch the mangled name and compute the hash in + // ParseClassInfoArray. + if (c == '.') + { + h = 0; + break; + } + h = ((h << 5) + h) + c; + } + class_infos[idx].hash = h; + } + } + } + else if (objc_opt->version >= 12 && objc_opt->version <= 15) + { + const objc_clsopt_t* clsopt = NULL; + if (objc_opt->version >= 14) + clsopt = (const objc_clsopt_t*)((uint8_t *)objc_opt_v14 + objc_opt_v14->clsopt_offset); + else + clsopt = (const objc_clsopt_t*)((uint8_t *)objc_opt + objc_opt->clsopt_offset); + const size_t max_class_infos = class_infos_byte_size/sizeof(ClassInfo); + DEBUG_PRINTF("max_class_infos = %llu\n", (uint64_t)max_class_infos); + ClassInfo *class_infos = (ClassInfo *)class_infos_ptr; + int32_t invalidEntryOffset = 0; + // this is safe to do because the version field order is invariant + if (objc_opt->version == 12) + invalidEntryOffset = 16; + const uint8_t *checkbytes = &clsopt->tab[clsopt->mask+1]; + const int32_t *offsets = (const int32_t *)(checkbytes + clsopt->capacity); + const objc_classheader_t *classOffsets = (const objc_classheader_t *)(offsets + clsopt->capacity); + DEBUG_PRINTF ("clsopt->capacity = %u\n", clsopt->capacity); + DEBUG_PRINTF ("clsopt->mask = 0x%8.8x\n", clsopt->mask); + DEBUG_PRINTF ("classOffsets = %p\n", classOffsets); + DEBUG_PRINTF("invalidEntryOffset = %d\n", invalidEntryOffset); + for (uint32_t i=0; i<clsopt->capacity; ++i) + { + const int32_t clsOffset = classOffsets[i].clsOffset; + DEBUG_PRINTF("clsOffset[%u] = %u\n", i, clsOffset); + if (clsOffset & 1) + { + DEBUG_PRINTF("clsOffset & 1\n"); + continue; // duplicate + } + else if (clsOffset == invalidEntryOffset) + { + DEBUG_PRINTF("clsOffset == invalidEntryOffset\n"); + continue; // invalid offset + } + + if (class_infos && idx < max_class_infos) + { + class_infos[idx].isa = (Class)((uint8_t *)clsopt + clsOffset); + const char *name = class_name_lookup_func (class_infos[idx].isa); + DEBUG_PRINTF ("[%u] isa = %8p %s\n", idx, class_infos[idx].isa, name); + // Hash the class name so we don't have to read it + const char *s = name; + uint32_t h = 5381; + for (unsigned char c = *s; c; c = *++s) + { + // class_getName demangles swift names and the hash must + // be calculated on the mangled name. hash==0 means lldb + // will fetch the mangled name and compute the hash in + // ParseClassInfoArray. + if (c == '.') + { + h = 0; + break; + } + h = ((h << 5) + h) + c; + } + class_infos[idx].hash = h; + } + else + { + DEBUG_PRINTF("not(class_infos && idx < max_class_infos)\n"); + } + ++idx; + } + + const uint32_t *duplicate_count_ptr = (uint32_t *)&classOffsets[clsopt->capacity]; + const uint32_t duplicate_count = *duplicate_count_ptr; + const objc_classheader_t *duplicateClassOffsets = (const objc_classheader_t *)(&duplicate_count_ptr[1]); + DEBUG_PRINTF ("duplicate_count = %u\n", duplicate_count); + DEBUG_PRINTF ("duplicateClassOffsets = %p\n", duplicateClassOffsets); + for (uint32_t i=0; i<duplicate_count; ++i) + { + const int32_t clsOffset = duplicateClassOffsets[i].clsOffset; + if (clsOffset & 1) + continue; // duplicate + else if (clsOffset == invalidEntryOffset) + continue; // invalid offset + + if (class_infos && idx < max_class_infos) + { + class_infos[idx].isa = (Class)((uint8_t *)clsopt + clsOffset); + const char *name = class_name_lookup_func (class_infos[idx].isa); + DEBUG_PRINTF ("[%u] isa = %8p %s\n", idx, class_infos[idx].isa, name); + // Hash the class name so we don't have to read it + const char *s = name; + uint32_t h = 5381; + for (unsigned char c = *s; c; c = *++s) + { + // class_getName demangles swift names and the hash must + // be calculated on the mangled name. hash==0 means lldb + // will fetch the mangled name and compute the hash in + // ParseClassInfoArray. + if (c == '.') + { + h = 0; + break; + } + h = ((h << 5) + h) + c; + } + class_infos[idx].hash = h; + } + ++idx; + } + } + DEBUG_PRINTF ("%u class_infos\n", idx); + DEBUG_PRINTF ("done\n"); + } + return idx; +} + + +)"; + +static uint64_t +ExtractRuntimeGlobalSymbol(Process *process, ConstString name, + const ModuleSP &module_sp, Status &error, + bool read_value = true, uint8_t byte_size = 0, + uint64_t default_value = LLDB_INVALID_ADDRESS, + SymbolType sym_type = lldb::eSymbolTypeData) { + if (!process) { + error.SetErrorString("no process"); + return default_value; + } + + if (!module_sp) { + error.SetErrorString("no module"); + return default_value; + } + + if (!byte_size) + byte_size = process->GetAddressByteSize(); + const Symbol *symbol = + module_sp->FindFirstSymbolWithNameAndType(name, lldb::eSymbolTypeData); + + if (!symbol || !symbol->ValueIsAddress()) { + error.SetErrorString("no symbol"); + return default_value; + } + + lldb::addr_t symbol_load_addr = + symbol->GetAddressRef().GetLoadAddress(&process->GetTarget()); + if (symbol_load_addr == LLDB_INVALID_ADDRESS) { + error.SetErrorString("symbol address invalid"); + return default_value; + } + + if (read_value) + return process->ReadUnsignedIntegerFromMemory(symbol_load_addr, byte_size, + default_value, error); + return symbol_load_addr; +} + +static void RegisterObjCExceptionRecognizer(Process *process); + +AppleObjCRuntimeV2::AppleObjCRuntimeV2(Process *process, + const ModuleSP &objc_module_sp) + : AppleObjCRuntime(process), m_objc_module_sp(objc_module_sp), + m_dynamic_class_info_extractor(*this), + m_shared_cache_class_info_extractor(*this), m_decl_vendor_up(), + m_tagged_pointer_obfuscator(LLDB_INVALID_ADDRESS), + m_isa_hash_table_ptr(LLDB_INVALID_ADDRESS), + m_relative_selector_base(LLDB_INVALID_ADDRESS), m_hash_signature(), + m_has_object_getClass(false), m_has_objc_copyRealizedClassList(false), + m_has_objc_getRealizedClassList_trylock(false), m_loaded_objc_opt(false), + m_non_pointer_isa_cache_up(), + m_tagged_pointer_vendor_up( + TaggedPointerVendorV2::CreateInstance(*this, objc_module_sp)), + m_encoding_to_type_sp(), m_CFBoolean_values(), + m_realized_class_generation_count(0) { + static const ConstString g_gdb_object_getClass("gdb_object_getClass"); + m_has_object_getClass = HasSymbol(g_gdb_object_getClass); + static const ConstString g_objc_copyRealizedClassList( + "_ZL33objc_copyRealizedClassList_nolockPj"); + static const ConstString g_objc_getRealizedClassList_trylock( + "_objc_getRealizedClassList_trylock"); + m_has_objc_copyRealizedClassList = HasSymbol(g_objc_copyRealizedClassList); + m_has_objc_getRealizedClassList_trylock = + HasSymbol(g_objc_getRealizedClassList_trylock); + WarnIfNoExpandedSharedCache(); + RegisterObjCExceptionRecognizer(process); +} + +LanguageRuntime * +AppleObjCRuntimeV2::GetPreferredLanguageRuntime(ValueObject &in_value) { + if (auto process_sp = in_value.GetProcessSP()) { + assert(process_sp.get() == m_process); + if (auto descriptor_sp = GetNonKVOClassDescriptor(in_value)) { + LanguageType impl_lang = descriptor_sp->GetImplementationLanguage(); + if (impl_lang != eLanguageTypeUnknown) + return process_sp->GetLanguageRuntime(impl_lang); + } + } + return nullptr; +} + +bool AppleObjCRuntimeV2::GetDynamicTypeAndAddress( + ValueObject &in_value, lldb::DynamicValueType use_dynamic, + TypeAndOrName &class_type_or_name, Address &address, + Value::ValueType &value_type) { + // We should never get here with a null process... + assert(m_process != nullptr); + + // The Runtime is attached to a particular process, you shouldn't pass in a + // value from another process. Note, however, the process might be NULL (e.g. + // if the value was made with SBTarget::EvaluateExpression...) in which case + // it is sufficient if the target's match: + + Process *process = in_value.GetProcessSP().get(); + if (process) + assert(process == m_process); + else + assert(in_value.GetTargetSP().get() == m_process->CalculateTarget().get()); + + class_type_or_name.Clear(); + value_type = Value::ValueType::Scalar; + + // Make sure we can have a dynamic value before starting... + if (CouldHaveDynamicValue(in_value)) { + // First job, pull out the address at 0 offset from the object That will + // be the ISA pointer. + ClassDescriptorSP objc_class_sp(GetNonKVOClassDescriptor(in_value)); + if (objc_class_sp) { + const addr_t object_ptr = in_value.GetPointerValue(); + address.SetRawAddress(object_ptr); + + ConstString class_name(objc_class_sp->GetClassName()); + class_type_or_name.SetName(class_name); + TypeSP type_sp(objc_class_sp->GetType()); + if (type_sp) + class_type_or_name.SetTypeSP(type_sp); + else { + type_sp = LookupInCompleteClassCache(class_name); + if (type_sp) { + objc_class_sp->SetType(type_sp); + class_type_or_name.SetTypeSP(type_sp); + } else { + // try to go for a CompilerType at least + if (auto *vendor = GetDeclVendor()) { + auto types = vendor->FindTypes(class_name, /*max_matches*/ 1); + if (!types.empty()) + class_type_or_name.SetCompilerType(types.front()); + } + } + } + } + } + return !class_type_or_name.IsEmpty(); +} + +// Static Functions +LanguageRuntime *AppleObjCRuntimeV2::CreateInstance(Process *process, + LanguageType language) { + // FIXME: This should be a MacOS or iOS process, and we need to look for the + // OBJC section to make + // sure we aren't using the V1 runtime. + if (language == eLanguageTypeObjC) { + ModuleSP objc_module_sp; + + if (AppleObjCRuntime::GetObjCVersion(process, objc_module_sp) == + ObjCRuntimeVersions::eAppleObjC_V2) + return new AppleObjCRuntimeV2(process, objc_module_sp); + return nullptr; + } + return nullptr; +} + +static constexpr OptionDefinition g_objc_classtable_dump_options[] = { + {LLDB_OPT_SET_ALL, + false, + "verbose", + 'v', + OptionParser::eNoArgument, + nullptr, + {}, + 0, + eArgTypeNone, + "Print ivar and method information in detail"}}; + +class CommandObjectObjC_ClassTable_Dump : public CommandObjectParsed { +public: + class CommandOptions : public Options { + public: + CommandOptions() : Options(), m_verbose(false, false) {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + switch (short_option) { + case 'v': + m_verbose.SetCurrentValue(true); + m_verbose.SetOptionWasSet(); + break; + + default: + error.SetErrorStringWithFormat("unrecognized short option '%c'", + short_option); + break; + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_verbose.Clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::ArrayRef(g_objc_classtable_dump_options); + } + + OptionValueBoolean m_verbose; + }; + + CommandObjectObjC_ClassTable_Dump(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "dump", + "Dump information on Objective-C classes " + "known to the current process.", + "language objc class-table dump", + eCommandRequiresProcess | + eCommandProcessMustBeLaunched | + eCommandProcessMustBePaused), + m_options() { + AddSimpleArgumentList(eArgTypeRegularExpression, eArgRepeatOptional); + } + + ~CommandObjectObjC_ClassTable_Dump() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + std::unique_ptr<RegularExpression> regex_up; + switch (command.GetArgumentCount()) { + case 0: + break; + case 1: { + regex_up = + std::make_unique<RegularExpression>(command.GetArgumentAtIndex(0)); + if (!regex_up->IsValid()) { + result.AppendError( + "invalid argument - please provide a valid regular expression"); + result.SetStatus(lldb::eReturnStatusFailed); + return; + } + break; + } + default: { + result.AppendError("please provide 0 or 1 arguments"); + result.SetStatus(lldb::eReturnStatusFailed); + return; + } + } + + Process *process = m_exe_ctx.GetProcessPtr(); + ObjCLanguageRuntime *objc_runtime = ObjCLanguageRuntime::Get(*process); + if (objc_runtime) { + auto iterators_pair = objc_runtime->GetDescriptorIteratorPair(); + auto iterator = iterators_pair.first; + auto &std_out = result.GetOutputStream(); + for (; iterator != iterators_pair.second; iterator++) { + if (iterator->second) { + const char *class_name = + iterator->second->GetClassName().AsCString("<unknown>"); + if (regex_up && class_name && + !regex_up->Execute(llvm::StringRef(class_name))) + continue; + std_out.Printf("isa = 0x%" PRIx64, iterator->first); + std_out.Printf(" name = %s", class_name); + std_out.Printf(" instance size = %" PRIu64, + iterator->second->GetInstanceSize()); + std_out.Printf(" num ivars = %" PRIuPTR, + (uintptr_t)iterator->second->GetNumIVars()); + if (auto superclass = iterator->second->GetSuperclass()) { + std_out.Printf(" superclass = %s", + superclass->GetClassName().AsCString("<unknown>")); + } + std_out.Printf("\n"); + if (m_options.m_verbose) { + for (size_t i = 0; i < iterator->second->GetNumIVars(); i++) { + auto ivar = iterator->second->GetIVarAtIndex(i); + std_out.Printf( + " ivar name = %s type = %s size = %" PRIu64 + " offset = %" PRId32 "\n", + ivar.m_name.AsCString("<unknown>"), + ivar.m_type.GetDisplayTypeName().AsCString("<unknown>"), + ivar.m_size, ivar.m_offset); + } + + iterator->second->Describe( + nullptr, + [&std_out](const char *name, const char *type) -> bool { + std_out.Printf(" instance method name = %s type = %s\n", + name, type); + return false; + }, + [&std_out](const char *name, const char *type) -> bool { + std_out.Printf(" class method name = %s type = %s\n", name, + type); + return false; + }, + nullptr); + } + } else { + if (regex_up && !regex_up->Execute(llvm::StringRef())) + continue; + std_out.Printf("isa = 0x%" PRIx64 " has no associated class.\n", + iterator->first); + } + } + result.SetStatus(lldb::eReturnStatusSuccessFinishResult); + return; + } + result.AppendError("current process has no Objective-C runtime loaded"); + result.SetStatus(lldb::eReturnStatusFailed); + } + + CommandOptions m_options; +}; + +class CommandObjectMultiwordObjC_TaggedPointer_Info + : public CommandObjectParsed { +public: + CommandObjectMultiwordObjC_TaggedPointer_Info(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "info", "Dump information on a tagged pointer.", + "language objc tagged-pointer info", + eCommandRequiresProcess | eCommandProcessMustBeLaunched | + eCommandProcessMustBePaused) { + AddSimpleArgumentList(eArgTypeAddress, eArgRepeatPlus); + } + + ~CommandObjectMultiwordObjC_TaggedPointer_Info() override = default; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + if (command.GetArgumentCount() == 0) { + result.AppendError("this command requires arguments"); + result.SetStatus(lldb::eReturnStatusFailed); + return; + } + + Process *process = m_exe_ctx.GetProcessPtr(); + ExecutionContext exe_ctx(process); + + ObjCLanguageRuntime *objc_runtime = ObjCLanguageRuntime::Get(*process); + if (!objc_runtime) { + result.AppendError("current process has no Objective-C runtime loaded"); + result.SetStatus(lldb::eReturnStatusFailed); + return; + } + + ObjCLanguageRuntime::TaggedPointerVendor *tagged_ptr_vendor = + objc_runtime->GetTaggedPointerVendor(); + if (!tagged_ptr_vendor) { + result.AppendError("current process has no tagged pointer support"); + result.SetStatus(lldb::eReturnStatusFailed); + return; + } + + for (size_t i = 0; i < command.GetArgumentCount(); i++) { + const char *arg_str = command.GetArgumentAtIndex(i); + if (!arg_str) + continue; + + Status error; + lldb::addr_t arg_addr = OptionArgParser::ToAddress( + &exe_ctx, arg_str, LLDB_INVALID_ADDRESS, &error); + if (arg_addr == 0 || arg_addr == LLDB_INVALID_ADDRESS || error.Fail()) { + result.AppendErrorWithFormatv( + "could not convert '{0}' to a valid address\n", arg_str); + result.SetStatus(lldb::eReturnStatusFailed); + return; + } + + if (!tagged_ptr_vendor->IsPossibleTaggedPointer(arg_addr)) { + result.GetOutputStream().Format("{0:x16} is not tagged\n", arg_addr); + continue; + } + + auto descriptor_sp = tagged_ptr_vendor->GetClassDescriptor(arg_addr); + if (!descriptor_sp) { + result.AppendErrorWithFormatv( + "could not get class descriptor for {0:x16}\n", arg_addr); + result.SetStatus(lldb::eReturnStatusFailed); + return; + } + + uint64_t info_bits = 0; + uint64_t value_bits = 0; + uint64_t payload = 0; + if (descriptor_sp->GetTaggedPointerInfo(&info_bits, &value_bits, + &payload)) { + result.GetOutputStream().Format( + "{0:x} is tagged\n" + "\tpayload = {1:x16}\n" + "\tvalue = {2:x16}\n" + "\tinfo bits = {3:x16}\n" + "\tclass = {4}\n", + arg_addr, payload, value_bits, info_bits, + descriptor_sp->GetClassName().AsCString("<unknown>")); + } else { + result.GetOutputStream().Format("{0:x16} is not tagged\n", arg_addr); + } + } + + result.SetStatus(lldb::eReturnStatusSuccessFinishResult); + } +}; + +class CommandObjectMultiwordObjC_ClassTable : public CommandObjectMultiword { +public: + CommandObjectMultiwordObjC_ClassTable(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "class-table", + "Commands for operating on the Objective-C class table.", + "class-table <subcommand> [<subcommand-options>]") { + LoadSubCommand( + "dump", + CommandObjectSP(new CommandObjectObjC_ClassTable_Dump(interpreter))); + } + + ~CommandObjectMultiwordObjC_ClassTable() override = default; +}; + +class CommandObjectMultiwordObjC_TaggedPointer : public CommandObjectMultiword { +public: + CommandObjectMultiwordObjC_TaggedPointer(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "tagged-pointer", + "Commands for operating on Objective-C tagged pointers.", + "class-table <subcommand> [<subcommand-options>]") { + LoadSubCommand( + "info", + CommandObjectSP( + new CommandObjectMultiwordObjC_TaggedPointer_Info(interpreter))); + } + + ~CommandObjectMultiwordObjC_TaggedPointer() override = default; +}; + +class CommandObjectMultiwordObjC : public CommandObjectMultiword { +public: + CommandObjectMultiwordObjC(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "objc", + "Commands for operating on the Objective-C language runtime.", + "objc <subcommand> [<subcommand-options>]") { + LoadSubCommand("class-table", + CommandObjectSP( + new CommandObjectMultiwordObjC_ClassTable(interpreter))); + LoadSubCommand("tagged-pointer", + CommandObjectSP(new CommandObjectMultiwordObjC_TaggedPointer( + interpreter))); + } + + ~CommandObjectMultiwordObjC() override = default; +}; + +void AppleObjCRuntimeV2::Initialize() { + PluginManager::RegisterPlugin( + GetPluginNameStatic(), "Apple Objective-C Language Runtime - Version 2", + CreateInstance, + [](CommandInterpreter &interpreter) -> lldb::CommandObjectSP { + return CommandObjectSP(new CommandObjectMultiwordObjC(interpreter)); + }, + GetBreakpointExceptionPrecondition); +} + +void AppleObjCRuntimeV2::Terminate() { + PluginManager::UnregisterPlugin(CreateInstance); +} + +BreakpointResolverSP +AppleObjCRuntimeV2::CreateExceptionResolver(const BreakpointSP &bkpt, + bool catch_bp, bool throw_bp) { + BreakpointResolverSP resolver_sp; + + if (throw_bp) + resolver_sp = std::make_shared<BreakpointResolverName>( + bkpt, std::get<1>(GetExceptionThrowLocation()).AsCString(), + eFunctionNameTypeBase, eLanguageTypeUnknown, Breakpoint::Exact, 0, + eLazyBoolNo); + // FIXME: We don't do catch breakpoints for ObjC yet. + // Should there be some way for the runtime to specify what it can do in this + // regard? + return resolver_sp; +} + +llvm::Expected<std::unique_ptr<UtilityFunction>> +AppleObjCRuntimeV2::CreateObjectChecker(std::string name, + ExecutionContext &exe_ctx) { + char check_function_code[2048]; + + int len = 0; + if (m_has_object_getClass) { + len = ::snprintf(check_function_code, sizeof(check_function_code), R"( + extern "C" void *gdb_object_getClass(void *); + extern "C" int printf(const char *format, ...); + extern "C" void + %s(void *$__lldb_arg_obj, void *$__lldb_arg_selector) { + if ($__lldb_arg_obj == (void *)0) + return; // nil is ok + if (!gdb_object_getClass($__lldb_arg_obj)) { + *((volatile int *)0) = 'ocgc'; + } else if ($__lldb_arg_selector != (void *)0) { + signed char $responds = (signed char) + [(id)$__lldb_arg_obj respondsToSelector: + (void *) $__lldb_arg_selector]; + if ($responds == (signed char) 0) + *((volatile int *)0) = 'ocgc'; + } + })", + name.c_str()); + } else { + len = ::snprintf(check_function_code, sizeof(check_function_code), R"( + extern "C" void *gdb_class_getClass(void *); + extern "C" int printf(const char *format, ...); + extern "C" void + %s(void *$__lldb_arg_obj, void *$__lldb_arg_selector) { + if ($__lldb_arg_obj == (void *)0) + return; // nil is ok + void **$isa_ptr = (void **)$__lldb_arg_obj; + if (*$isa_ptr == (void *)0 || + !gdb_class_getClass(*$isa_ptr)) + *((volatile int *)0) = 'ocgc'; + else if ($__lldb_arg_selector != (void *)0) { + signed char $responds = (signed char) + [(id)$__lldb_arg_obj respondsToSelector: + (void *) $__lldb_arg_selector]; + if ($responds == (signed char) 0) + *((volatile int *)0) = 'ocgc'; + } + })", + name.c_str()); + } + + assert(len < (int)sizeof(check_function_code)); + UNUSED_IF_ASSERT_DISABLED(len); + + return GetTargetRef().CreateUtilityFunction(check_function_code, name, + eLanguageTypeC, exe_ctx); +} + +size_t AppleObjCRuntimeV2::GetByteOffsetForIvar(CompilerType &parent_ast_type, + const char *ivar_name) { + uint32_t ivar_offset = LLDB_INVALID_IVAR_OFFSET; + + ConstString class_name = parent_ast_type.GetTypeName(); + if (!class_name.IsEmpty() && ivar_name && ivar_name[0]) { + // Make the objective C V2 mangled name for the ivar offset from the class + // name and ivar name + std::string buffer("OBJC_IVAR_$_"); + buffer.append(class_name.AsCString()); + buffer.push_back('.'); + buffer.append(ivar_name); + ConstString ivar_const_str(buffer.c_str()); + + // Try to get the ivar offset address from the symbol table first using the + // name we created above + SymbolContextList sc_list; + Target &target = m_process->GetTarget(); + target.GetImages().FindSymbolsWithNameAndType(ivar_const_str, + eSymbolTypeObjCIVar, sc_list); + + addr_t ivar_offset_address = LLDB_INVALID_ADDRESS; + + Status error; + SymbolContext ivar_offset_symbol; + if (sc_list.GetSize() == 1 && + sc_list.GetContextAtIndex(0, ivar_offset_symbol)) { + if (ivar_offset_symbol.symbol) + ivar_offset_address = + ivar_offset_symbol.symbol->GetLoadAddress(&target); + } + + // If we didn't get the ivar offset address from the symbol table, fall + // back to getting it from the runtime + if (ivar_offset_address == LLDB_INVALID_ADDRESS) + ivar_offset_address = LookupRuntimeSymbol(ivar_const_str); + + if (ivar_offset_address != LLDB_INVALID_ADDRESS) + ivar_offset = m_process->ReadUnsignedIntegerFromMemory( + ivar_offset_address, 4, LLDB_INVALID_IVAR_OFFSET, error); + } + return ivar_offset; +} + +// tagged pointers are special not-a-real-pointer values that contain both type +// and value information this routine attempts to check with as little +// computational effort as possible whether something could possibly be a +// tagged pointer - false positives are possible but false negatives shouldn't +bool AppleObjCRuntimeV2::IsTaggedPointer(addr_t ptr) { + if (!m_tagged_pointer_vendor_up) + return false; + return m_tagged_pointer_vendor_up->IsPossibleTaggedPointer(ptr); +} + +class RemoteNXMapTable { +public: + RemoteNXMapTable() : m_end_iterator(*this, -1) {} + + void Dump() { + printf("RemoteNXMapTable.m_load_addr = 0x%" PRIx64 "\n", m_load_addr); + printf("RemoteNXMapTable.m_count = %u\n", m_count); + printf("RemoteNXMapTable.m_num_buckets_minus_one = %u\n", + m_num_buckets_minus_one); + printf("RemoteNXMapTable.m_buckets_ptr = 0x%" PRIX64 "\n", m_buckets_ptr); + } + + bool ParseHeader(Process *process, lldb::addr_t load_addr) { + m_process = process; + m_load_addr = load_addr; + m_map_pair_size = m_process->GetAddressByteSize() * 2; + m_invalid_key = + m_process->GetAddressByteSize() == 8 ? UINT64_MAX : UINT32_MAX; + Status err; + + // This currently holds true for all platforms we support, but we might + // need to change this to use get the actually byte size of "unsigned" from + // the target AST... + const uint32_t unsigned_byte_size = sizeof(uint32_t); + // Skip the prototype as we don't need it (const struct + // +NXMapTablePrototype *prototype) + + bool success = true; + if (load_addr == LLDB_INVALID_ADDRESS) + success = false; + else { + lldb::addr_t cursor = load_addr + m_process->GetAddressByteSize(); + + // unsigned count; + m_count = m_process->ReadUnsignedIntegerFromMemory( + cursor, unsigned_byte_size, 0, err); + if (m_count) { + cursor += unsigned_byte_size; + + // unsigned nbBucketsMinusOne; + m_num_buckets_minus_one = m_process->ReadUnsignedIntegerFromMemory( + cursor, unsigned_byte_size, 0, err); + cursor += unsigned_byte_size; + + // void *buckets; + m_buckets_ptr = m_process->ReadPointerFromMemory(cursor, err); + + success = m_count > 0 && m_buckets_ptr != LLDB_INVALID_ADDRESS; + } + } + + if (!success) { + m_count = 0; + m_num_buckets_minus_one = 0; + m_buckets_ptr = LLDB_INVALID_ADDRESS; + } + return success; + } + + // const_iterator mimics NXMapState and its code comes from NXInitMapState + // and NXNextMapState. + typedef std::pair<ConstString, ObjCLanguageRuntime::ObjCISA> element; + + friend class const_iterator; + class const_iterator { + public: + const_iterator(RemoteNXMapTable &parent, int index) + : m_parent(parent), m_index(index) { + AdvanceToValidIndex(); + } + + const_iterator(const const_iterator &rhs) + : m_parent(rhs.m_parent), m_index(rhs.m_index) { + // AdvanceToValidIndex() has been called by rhs already. + } + + const_iterator &operator=(const const_iterator &rhs) { + // AdvanceToValidIndex() has been called by rhs already. + assert(&m_parent == &rhs.m_parent); + m_index = rhs.m_index; + return *this; + } + + bool operator==(const const_iterator &rhs) const { + if (&m_parent != &rhs.m_parent) + return false; + if (m_index != rhs.m_index) + return false; + + return true; + } + + bool operator!=(const const_iterator &rhs) const { + return !(operator==(rhs)); + } + + const_iterator &operator++() { + AdvanceToValidIndex(); + return *this; + } + + element operator*() const { + if (m_index == -1) { + // TODO find a way to make this an error, but not an assert + return element(); + } + + lldb::addr_t pairs_ptr = m_parent.m_buckets_ptr; + size_t map_pair_size = m_parent.m_map_pair_size; + lldb::addr_t pair_ptr = pairs_ptr + (m_index * map_pair_size); + + Status err; + + lldb::addr_t key = + m_parent.m_process->ReadPointerFromMemory(pair_ptr, err); + if (!err.Success()) + return element(); + lldb::addr_t value = m_parent.m_process->ReadPointerFromMemory( + pair_ptr + m_parent.m_process->GetAddressByteSize(), err); + if (!err.Success()) + return element(); + + std::string key_string; + + m_parent.m_process->ReadCStringFromMemory(key, key_string, err); + if (!err.Success()) + return element(); + + return element(ConstString(key_string.c_str()), + (ObjCLanguageRuntime::ObjCISA)value); + } + + private: + void AdvanceToValidIndex() { + if (m_index == -1) + return; + + const lldb::addr_t pairs_ptr = m_parent.m_buckets_ptr; + const size_t map_pair_size = m_parent.m_map_pair_size; + const lldb::addr_t invalid_key = m_parent.m_invalid_key; + Status err; + + while (m_index--) { + lldb::addr_t pair_ptr = pairs_ptr + (m_index * map_pair_size); + lldb::addr_t key = + m_parent.m_process->ReadPointerFromMemory(pair_ptr, err); + + if (!err.Success()) { + m_index = -1; + return; + } + + if (key != invalid_key) + return; + } + } + RemoteNXMapTable &m_parent; + int m_index; + }; + + const_iterator begin() { + return const_iterator(*this, m_num_buckets_minus_one + 1); + } + + const_iterator end() { return m_end_iterator; } + + uint32_t GetCount() const { return m_count; } + + uint32_t GetBucketCount() const { return m_num_buckets_minus_one; } + + lldb::addr_t GetBucketDataPointer() const { return m_buckets_ptr; } + + lldb::addr_t GetTableLoadAddress() const { return m_load_addr; } + +private: + // contents of _NXMapTable struct + uint32_t m_count = 0; + uint32_t m_num_buckets_minus_one = 0; + lldb::addr_t m_buckets_ptr = LLDB_INVALID_ADDRESS; + lldb_private::Process *m_process = nullptr; + const_iterator m_end_iterator; + lldb::addr_t m_load_addr = LLDB_INVALID_ADDRESS; + size_t m_map_pair_size = 0; + lldb::addr_t m_invalid_key = 0; +}; + +AppleObjCRuntimeV2::HashTableSignature::HashTableSignature() = default; + +void AppleObjCRuntimeV2::HashTableSignature::UpdateSignature( + const RemoteNXMapTable &hash_table) { + m_count = hash_table.GetCount(); + m_num_buckets = hash_table.GetBucketCount(); + m_buckets_ptr = hash_table.GetBucketDataPointer(); +} + +bool AppleObjCRuntimeV2::HashTableSignature::NeedsUpdate( + Process *process, AppleObjCRuntimeV2 *runtime, + RemoteNXMapTable &hash_table) { + if (!hash_table.ParseHeader(process, runtime->GetISAHashTablePointer())) { + return false; // Failed to parse the header, no need to update anything + } + + // Check with out current signature and return true if the count, number of + // buckets or the hash table address changes. + if (m_count == hash_table.GetCount() && + m_num_buckets == hash_table.GetBucketCount() && + m_buckets_ptr == hash_table.GetBucketDataPointer()) { + // Hash table hasn't changed + return false; + } + // Hash table data has changed, we need to update + return true; +} + +ObjCLanguageRuntime::ClassDescriptorSP +AppleObjCRuntimeV2::GetClassDescriptorFromISA(ObjCISA isa) { + ObjCLanguageRuntime::ClassDescriptorSP class_descriptor_sp; + if (auto *non_pointer_isa_cache = GetNonPointerIsaCache()) + class_descriptor_sp = non_pointer_isa_cache->GetClassDescriptor(isa); + if (!class_descriptor_sp) + class_descriptor_sp = ObjCLanguageRuntime::GetClassDescriptorFromISA(isa); + return class_descriptor_sp; +} + +ObjCLanguageRuntime::ClassDescriptorSP +AppleObjCRuntimeV2::GetClassDescriptor(ValueObject &valobj) { + ClassDescriptorSP objc_class_sp; + if (valobj.IsBaseClass()) { + ValueObject *parent = valobj.GetParent(); + // if I am my own parent, bail out of here fast.. + if (parent && parent != &valobj) { + ClassDescriptorSP parent_descriptor_sp = GetClassDescriptor(*parent); + if (parent_descriptor_sp) + return parent_descriptor_sp->GetSuperclass(); + } + return nullptr; + } + // if we get an invalid VO (which might still happen when playing around with + // pointers returned by the expression parser, don't consider this a valid + // ObjC object) + if (!valobj.GetCompilerType().IsValid()) + return objc_class_sp; + addr_t isa_pointer = valobj.GetPointerValue(); + + // tagged pointer + if (IsTaggedPointer(isa_pointer)) + return m_tagged_pointer_vendor_up->GetClassDescriptor(isa_pointer); + ExecutionContext exe_ctx(valobj.GetExecutionContextRef()); + + Process *process = exe_ctx.GetProcessPtr(); + if (!process) + return objc_class_sp; + + Status error; + ObjCISA isa = process->ReadPointerFromMemory(isa_pointer, error); + if (isa == LLDB_INVALID_ADDRESS) + return objc_class_sp; + + objc_class_sp = GetClassDescriptorFromISA(isa); + if (!objc_class_sp) { + if (ABISP abi_sp = process->GetABI()) + isa = abi_sp->FixCodeAddress(isa); + objc_class_sp = GetClassDescriptorFromISA(isa); + } + + if (isa && !objc_class_sp) { + Log *log = GetLog(LLDBLog::Process | LLDBLog::Types); + LLDB_LOGF(log, + "0x%" PRIx64 ": AppleObjCRuntimeV2::GetClassDescriptor() ISA was " + "not in class descriptor cache 0x%" PRIx64, + isa_pointer, isa); + } + return objc_class_sp; +} + +lldb::addr_t AppleObjCRuntimeV2::GetTaggedPointerObfuscator() { + if (m_tagged_pointer_obfuscator != LLDB_INVALID_ADDRESS) + return m_tagged_pointer_obfuscator; + + Process *process = GetProcess(); + ModuleSP objc_module_sp(GetObjCModule()); + + if (!objc_module_sp) + return LLDB_INVALID_ADDRESS; + + static ConstString g_gdb_objc_obfuscator( + "objc_debug_taggedpointer_obfuscator"); + + const Symbol *symbol = objc_module_sp->FindFirstSymbolWithNameAndType( + g_gdb_objc_obfuscator, lldb::eSymbolTypeAny); + if (symbol) { + lldb::addr_t g_gdb_obj_obfuscator_ptr = + symbol->GetLoadAddress(&process->GetTarget()); + + if (g_gdb_obj_obfuscator_ptr != LLDB_INVALID_ADDRESS) { + Status error; + m_tagged_pointer_obfuscator = + process->ReadPointerFromMemory(g_gdb_obj_obfuscator_ptr, error); + } + } + // If we don't have a correct value at this point, there must be no + // obfuscation. + if (m_tagged_pointer_obfuscator == LLDB_INVALID_ADDRESS) + m_tagged_pointer_obfuscator = 0; + + return m_tagged_pointer_obfuscator; +} + +lldb::addr_t AppleObjCRuntimeV2::GetISAHashTablePointer() { + if (m_isa_hash_table_ptr == LLDB_INVALID_ADDRESS) { + Process *process = GetProcess(); + + ModuleSP objc_module_sp(GetObjCModule()); + + if (!objc_module_sp) + return LLDB_INVALID_ADDRESS; + + static ConstString g_gdb_objc_realized_classes("gdb_objc_realized_classes"); + + const Symbol *symbol = objc_module_sp->FindFirstSymbolWithNameAndType( + g_gdb_objc_realized_classes, lldb::eSymbolTypeAny); + if (symbol) { + lldb::addr_t gdb_objc_realized_classes_ptr = + symbol->GetLoadAddress(&process->GetTarget()); + + if (gdb_objc_realized_classes_ptr != LLDB_INVALID_ADDRESS) { + Status error; + m_isa_hash_table_ptr = process->ReadPointerFromMemory( + gdb_objc_realized_classes_ptr, error); + } + } + } + return m_isa_hash_table_ptr; +} + +std::unique_ptr<AppleObjCRuntimeV2::SharedCacheImageHeaders> +AppleObjCRuntimeV2::SharedCacheImageHeaders::CreateSharedCacheImageHeaders( + AppleObjCRuntimeV2 &runtime) { + Log *log = GetLog(LLDBLog::Process | LLDBLog::Types); + Process *process = runtime.GetProcess(); + ModuleSP objc_module_sp(runtime.GetObjCModule()); + if (!objc_module_sp || !process) + return nullptr; + + const Symbol *symbol = objc_module_sp->FindFirstSymbolWithNameAndType( + ConstString("objc_debug_headerInfoRWs"), lldb::eSymbolTypeAny); + if (!symbol) { + LLDB_LOG(log, "Symbol 'objc_debug_headerInfoRWs' unavailable. Some " + "information concerning the shared cache may be unavailable"); + return nullptr; + } + + lldb::addr_t objc_debug_headerInfoRWs_addr = + symbol->GetLoadAddress(&process->GetTarget()); + if (objc_debug_headerInfoRWs_addr == LLDB_INVALID_ADDRESS) { + LLDB_LOG(log, "Symbol 'objc_debug_headerInfoRWs' was found but we were " + "unable to get its load address"); + return nullptr; + } + + Status error; + lldb::addr_t objc_debug_headerInfoRWs_ptr = + process->ReadPointerFromMemory(objc_debug_headerInfoRWs_addr, error); + if (error.Fail()) { + LLDB_LOG(log, + "Failed to read address of 'objc_debug_headerInfoRWs' at {0:x}", + objc_debug_headerInfoRWs_addr); + return nullptr; + } + + const size_t metadata_size = + sizeof(uint32_t) + sizeof(uint32_t); // count + entsize + DataBufferHeap metadata_buffer(metadata_size, '\0'); + process->ReadMemory(objc_debug_headerInfoRWs_ptr, metadata_buffer.GetBytes(), + metadata_size, error); + if (error.Fail()) { + LLDB_LOG(log, + "Unable to read metadata for 'objc_debug_headerInfoRWs' at {0:x}", + objc_debug_headerInfoRWs_ptr); + return nullptr; + } + + DataExtractor metadata_extractor(metadata_buffer.GetBytes(), metadata_size, + process->GetByteOrder(), + process->GetAddressByteSize()); + lldb::offset_t cursor = 0; + uint32_t count = metadata_extractor.GetU32_unchecked(&cursor); + uint32_t entsize = metadata_extractor.GetU32_unchecked(&cursor); + if (count == 0 || entsize == 0) { + LLDB_LOG(log, + "'objc_debug_headerInfoRWs' had count {0} with entsize {1}. These " + "should both be non-zero.", + count, entsize); + return nullptr; + } + + std::unique_ptr<SharedCacheImageHeaders> shared_cache_image_headers( + new SharedCacheImageHeaders(runtime, objc_debug_headerInfoRWs_ptr, count, + entsize)); + if (auto Err = shared_cache_image_headers->UpdateIfNeeded()) { + LLDB_LOG_ERROR(log, std::move(Err), + "Failed to update SharedCacheImageHeaders: {0}"); + return nullptr; + } + + return shared_cache_image_headers; +} + +llvm::Error AppleObjCRuntimeV2::SharedCacheImageHeaders::UpdateIfNeeded() { + if (!m_needs_update) + return llvm::Error::success(); + + Process *process = m_runtime.GetProcess(); + constexpr lldb::addr_t metadata_size = + sizeof(uint32_t) + sizeof(uint32_t); // count + entsize + + Status error; + const lldb::addr_t first_header_addr = m_headerInfoRWs_ptr + metadata_size; + DataBufferHeap header_buffer(m_entsize, '\0'); + lldb::offset_t cursor = 0; + for (uint32_t i = 0; i < m_count; i++) { + const lldb::addr_t header_addr = first_header_addr + (i * m_entsize); + process->ReadMemory(header_addr, header_buffer.GetBytes(), m_entsize, + error); + if (error.Fail()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Failed to read memory from inferior when " + "populating SharedCacheImageHeaders"); + + DataExtractor header_extractor(header_buffer.GetBytes(), m_entsize, + process->GetByteOrder(), + process->GetAddressByteSize()); + cursor = 0; + bool is_loaded = false; + if (m_entsize == 4) { + uint32_t header = header_extractor.GetU32_unchecked(&cursor); + if (header & 1) + is_loaded = true; + } else { + uint64_t header = header_extractor.GetU64_unchecked(&cursor); + if (header & 1) + is_loaded = true; + } + + if (is_loaded) + m_loaded_images.set(i); + else + m_loaded_images.reset(i); + } + m_needs_update = false; + m_version++; + return llvm::Error::success(); +} + +bool AppleObjCRuntimeV2::SharedCacheImageHeaders::IsImageLoaded( + uint16_t image_index) { + if (image_index >= m_count) + return false; + if (auto Err = UpdateIfNeeded()) { + Log *log = GetLog(LLDBLog::Process | LLDBLog::Types); + LLDB_LOG_ERROR(log, std::move(Err), + "Failed to update SharedCacheImageHeaders: {0}"); + } + return m_loaded_images.test(image_index); +} + +uint64_t AppleObjCRuntimeV2::SharedCacheImageHeaders::GetVersion() { + if (auto Err = UpdateIfNeeded()) { + Log *log = GetLog(LLDBLog::Process | LLDBLog::Types); + LLDB_LOG_ERROR(log, std::move(Err), + "Failed to update SharedCacheImageHeaders: {0}"); + } + return m_version; +} + +std::unique_ptr<UtilityFunction> +AppleObjCRuntimeV2::DynamicClassInfoExtractor::GetClassInfoUtilityFunctionImpl( + ExecutionContext &exe_ctx, Helper helper, std::string code, + std::string name) { + Log *log = GetLog(LLDBLog::Process | LLDBLog::Types); + + LLDB_LOG(log, "Creating utility function {0}", name); + + TypeSystemClangSP scratch_ts_sp = + ScratchTypeSystemClang::GetForTarget(exe_ctx.GetTargetRef()); + if (!scratch_ts_sp) + return {}; + + auto utility_fn_or_error = exe_ctx.GetTargetRef().CreateUtilityFunction( + std::move(code), std::move(name), eLanguageTypeC, exe_ctx); + if (!utility_fn_or_error) { + LLDB_LOG_ERROR( + log, utility_fn_or_error.takeError(), + "Failed to get utility function for dynamic info extractor: {0}"); + return {}; + } + + // Make some types for our arguments. + CompilerType clang_uint32_t_type = + scratch_ts_sp->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 32); + CompilerType clang_void_pointer_type = + scratch_ts_sp->GetBasicType(eBasicTypeVoid).GetPointerType(); + + // Make the runner function for our implementation utility function. + ValueList arguments; + Value value; + value.SetValueType(Value::ValueType::Scalar); + value.SetCompilerType(clang_void_pointer_type); + arguments.PushValue(value); + arguments.PushValue(value); + value.SetValueType(Value::ValueType::Scalar); + value.SetCompilerType(clang_uint32_t_type); + arguments.PushValue(value); + + // objc_getRealizedClassList_trylock takes an additional buffer and length. + if (helper == Helper::objc_getRealizedClassList_trylock) { + value.SetCompilerType(clang_void_pointer_type); + arguments.PushValue(value); + value.SetCompilerType(clang_uint32_t_type); + arguments.PushValue(value); + } + + arguments.PushValue(value); + + std::unique_ptr<UtilityFunction> utility_fn = std::move(*utility_fn_or_error); + + Status error; + utility_fn->MakeFunctionCaller(clang_uint32_t_type, arguments, + exe_ctx.GetThreadSP(), error); + + if (error.Fail()) { + LLDB_LOG(log, + "Failed to make function caller for implementation lookup: {0}.", + error.AsCString()); + return {}; + } + + return utility_fn; +} + +UtilityFunction * +AppleObjCRuntimeV2::DynamicClassInfoExtractor::GetClassInfoUtilityFunction( + ExecutionContext &exe_ctx, Helper helper) { + switch (helper) { + case gdb_objc_realized_classes: { + if (!m_gdb_objc_realized_classes_helper.utility_function) + m_gdb_objc_realized_classes_helper.utility_function = + GetClassInfoUtilityFunctionImpl(exe_ctx, helper, + g_get_dynamic_class_info_body, + g_get_dynamic_class_info_name); + return m_gdb_objc_realized_classes_helper.utility_function.get(); + } + case objc_copyRealizedClassList: { + if (!m_objc_copyRealizedClassList_helper.utility_function) + m_objc_copyRealizedClassList_helper.utility_function = + GetClassInfoUtilityFunctionImpl(exe_ctx, helper, + g_get_dynamic_class_info2_body, + g_get_dynamic_class_info2_name); + return m_objc_copyRealizedClassList_helper.utility_function.get(); + } + case objc_getRealizedClassList_trylock: { + if (!m_objc_getRealizedClassList_trylock_helper.utility_function) + m_objc_getRealizedClassList_trylock_helper.utility_function = + GetClassInfoUtilityFunctionImpl(exe_ctx, helper, + g_get_dynamic_class_info3_body, + g_get_dynamic_class_info3_name); + return m_objc_getRealizedClassList_trylock_helper.utility_function.get(); + } + } + llvm_unreachable("Unexpected helper"); +} + +lldb::addr_t & +AppleObjCRuntimeV2::DynamicClassInfoExtractor::GetClassInfoArgs(Helper helper) { + switch (helper) { + case gdb_objc_realized_classes: + return m_gdb_objc_realized_classes_helper.args; + case objc_copyRealizedClassList: + return m_objc_copyRealizedClassList_helper.args; + case objc_getRealizedClassList_trylock: + return m_objc_getRealizedClassList_trylock_helper.args; + } + llvm_unreachable("Unexpected helper"); +} + +AppleObjCRuntimeV2::DynamicClassInfoExtractor::Helper +AppleObjCRuntimeV2::DynamicClassInfoExtractor::ComputeHelper( + ExecutionContext &exe_ctx) const { + if (!m_runtime.m_has_objc_copyRealizedClassList && + !m_runtime.m_has_objc_getRealizedClassList_trylock) + return DynamicClassInfoExtractor::gdb_objc_realized_classes; + + if (Process *process = m_runtime.GetProcess()) { + if (DynamicLoader *loader = process->GetDynamicLoader()) { + if (loader->IsFullyInitialized()) { + switch (exe_ctx.GetTargetRef().GetDynamicClassInfoHelper()) { + case eDynamicClassInfoHelperAuto: + [[fallthrough]]; + case eDynamicClassInfoHelperGetRealizedClassList: + if (m_runtime.m_has_objc_getRealizedClassList_trylock) + return DynamicClassInfoExtractor::objc_getRealizedClassList_trylock; + [[fallthrough]]; + case eDynamicClassInfoHelperCopyRealizedClassList: + if (m_runtime.m_has_objc_copyRealizedClassList) + return DynamicClassInfoExtractor::objc_copyRealizedClassList; + [[fallthrough]]; + case eDynamicClassInfoHelperRealizedClassesStruct: + return DynamicClassInfoExtractor::gdb_objc_realized_classes; + } + } + } + } + + return DynamicClassInfoExtractor::gdb_objc_realized_classes; +} + +std::unique_ptr<UtilityFunction> +AppleObjCRuntimeV2::SharedCacheClassInfoExtractor:: + GetClassInfoUtilityFunctionImpl(ExecutionContext &exe_ctx) { + Log *log = GetLog(LLDBLog::Process | LLDBLog::Types); + + LLDB_LOG(log, "Creating utility function {0}", + g_get_shared_cache_class_info_name); + + TypeSystemClangSP scratch_ts_sp = + ScratchTypeSystemClang::GetForTarget(exe_ctx.GetTargetRef()); + if (!scratch_ts_sp) + return {}; + + // If the inferior objc.dylib has the class_getNameRaw function, use that in + // our jitted expression. Else fall back to the old class_getName. + static ConstString g_class_getName_symbol_name("class_getName"); + static ConstString g_class_getNameRaw_symbol_name( + "objc_debug_class_getNameRaw"); + + ConstString class_name_getter_function_name = + m_runtime.HasSymbol(g_class_getNameRaw_symbol_name) + ? g_class_getNameRaw_symbol_name + : g_class_getName_symbol_name; + + // Substitute in the correct class_getName / class_getNameRaw function name, + // concatenate the two parts of our expression text. The format string has + // two %s's, so provide the name twice. + std::string shared_class_expression; + llvm::raw_string_ostream(shared_class_expression) + << llvm::format(g_shared_cache_class_name_funcptr, + class_name_getter_function_name.AsCString(), + class_name_getter_function_name.AsCString()); + + shared_class_expression += g_get_shared_cache_class_info_body; + + auto utility_fn_or_error = exe_ctx.GetTargetRef().CreateUtilityFunction( + std::move(shared_class_expression), g_get_shared_cache_class_info_name, + eLanguageTypeC, exe_ctx); + + if (!utility_fn_or_error) { + LLDB_LOG_ERROR( + log, utility_fn_or_error.takeError(), + "Failed to get utility function for shared class info extractor: {0}"); + return nullptr; + } + + // Make some types for our arguments. + CompilerType clang_uint32_t_type = + scratch_ts_sp->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 32); + CompilerType clang_void_pointer_type = + scratch_ts_sp->GetBasicType(eBasicTypeVoid).GetPointerType(); + CompilerType clang_uint64_t_pointer_type = + scratch_ts_sp->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 64) + .GetPointerType(); + + // Next make the function caller for our implementation utility function. + ValueList arguments; + Value value; + value.SetValueType(Value::ValueType::Scalar); + value.SetCompilerType(clang_void_pointer_type); + arguments.PushValue(value); + arguments.PushValue(value); + arguments.PushValue(value); + + value.SetValueType(Value::ValueType::Scalar); + value.SetCompilerType(clang_uint64_t_pointer_type); + arguments.PushValue(value); + + value.SetValueType(Value::ValueType::Scalar); + value.SetCompilerType(clang_uint32_t_type); + arguments.PushValue(value); + arguments.PushValue(value); + + std::unique_ptr<UtilityFunction> utility_fn = std::move(*utility_fn_or_error); + + Status error; + utility_fn->MakeFunctionCaller(clang_uint32_t_type, arguments, + exe_ctx.GetThreadSP(), error); + + if (error.Fail()) { + LLDB_LOG(log, + "Failed to make function caller for implementation lookup: {0}.", + error.AsCString()); + return {}; + } + + return utility_fn; +} + +UtilityFunction * +AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::GetClassInfoUtilityFunction( + ExecutionContext &exe_ctx) { + if (!m_utility_function) + m_utility_function = GetClassInfoUtilityFunctionImpl(exe_ctx); + return m_utility_function.get(); +} + +AppleObjCRuntimeV2::DescriptorMapUpdateResult +AppleObjCRuntimeV2::DynamicClassInfoExtractor::UpdateISAToDescriptorMap( + RemoteNXMapTable &hash_table) { + Process *process = m_runtime.GetProcess(); + if (process == nullptr) + return DescriptorMapUpdateResult::Fail(); + + uint32_t num_class_infos = 0; + + Log *log = GetLog(LLDBLog::Process | LLDBLog::Types); + + ExecutionContext exe_ctx; + + ThreadSP thread_sp = process->GetThreadList().GetExpressionExecutionThread(); + + if (!thread_sp) + return DescriptorMapUpdateResult::Fail(); + + if (!thread_sp->SafeToCallFunctions()) + return DescriptorMapUpdateResult::Retry(); + + thread_sp->CalculateExecutionContext(exe_ctx); + TypeSystemClangSP scratch_ts_sp = + ScratchTypeSystemClang::GetForTarget(process->GetTarget()); + + if (!scratch_ts_sp) + return DescriptorMapUpdateResult::Fail(); + + Address function_address; + + const uint32_t addr_size = process->GetAddressByteSize(); + + Status err; + + // Compute which helper we're going to use for this update. + const DynamicClassInfoExtractor::Helper helper = ComputeHelper(exe_ctx); + + // Read the total number of classes from the hash table + const uint32_t num_classes = + helper == DynamicClassInfoExtractor::gdb_objc_realized_classes + ? hash_table.GetCount() + : m_runtime.m_realized_class_generation_count; + if (num_classes == 0) { + LLDB_LOGF(log, "No dynamic classes found."); + return DescriptorMapUpdateResult::Success(0); + } + + UtilityFunction *get_class_info_code = + GetClassInfoUtilityFunction(exe_ctx, helper); + if (!get_class_info_code) { + // The callee will have already logged a useful error message. + return DescriptorMapUpdateResult::Fail(); + } + + FunctionCaller *get_class_info_function = + get_class_info_code->GetFunctionCaller(); + + if (!get_class_info_function) { + LLDB_LOGF(log, "Failed to get implementation lookup function caller."); + return DescriptorMapUpdateResult::Fail(); + } + + ValueList arguments = get_class_info_function->GetArgumentValues(); + + DiagnosticManager diagnostics; + + const uint32_t class_info_byte_size = addr_size + 4; + const uint32_t class_infos_byte_size = num_classes * class_info_byte_size; + lldb::addr_t class_infos_addr = process->AllocateMemory( + class_infos_byte_size, ePermissionsReadable | ePermissionsWritable, err); + + if (class_infos_addr == LLDB_INVALID_ADDRESS) { + LLDB_LOGF(log, + "unable to allocate %" PRIu32 + " bytes in process for shared cache read", + class_infos_byte_size); + return DescriptorMapUpdateResult::Fail(); + } + + auto deallocate_class_infos = llvm::make_scope_exit([&] { + // Deallocate the memory we allocated for the ClassInfo array + if (class_infos_addr != LLDB_INVALID_ADDRESS) + process->DeallocateMemory(class_infos_addr); + }); + + lldb::addr_t class_buffer_addr = LLDB_INVALID_ADDRESS; + const uint32_t class_byte_size = addr_size; + const uint32_t class_buffer_len = num_classes; + const uint32_t class_buffer_byte_size = class_buffer_len * class_byte_size; + if (helper == Helper::objc_getRealizedClassList_trylock) { + class_buffer_addr = process->AllocateMemory( + class_buffer_byte_size, ePermissionsReadable | ePermissionsWritable, + err); + if (class_buffer_addr == LLDB_INVALID_ADDRESS) { + LLDB_LOGF(log, + "unable to allocate %" PRIu32 + " bytes in process for shared cache read", + class_buffer_byte_size); + return DescriptorMapUpdateResult::Fail(); + } + } + + auto deallocate_class_buffer = llvm::make_scope_exit([&] { + // Deallocate the memory we allocated for the Class array + if (class_buffer_addr != LLDB_INVALID_ADDRESS) + process->DeallocateMemory(class_buffer_addr); + }); + + std::lock_guard<std::mutex> guard(m_mutex); + + // Fill in our function argument values + uint32_t index = 0; + arguments.GetValueAtIndex(index++)->GetScalar() = + hash_table.GetTableLoadAddress(); + arguments.GetValueAtIndex(index++)->GetScalar() = class_infos_addr; + arguments.GetValueAtIndex(index++)->GetScalar() = class_infos_byte_size; + + if (class_buffer_addr != LLDB_INVALID_ADDRESS) { + arguments.GetValueAtIndex(index++)->GetScalar() = class_buffer_addr; + arguments.GetValueAtIndex(index++)->GetScalar() = class_buffer_byte_size; + } + + // Only dump the runtime classes from the expression evaluation if the log is + // verbose: + Log *type_log = GetLog(LLDBLog::Types); + bool dump_log = type_log && type_log->GetVerbose(); + + arguments.GetValueAtIndex(index++)->GetScalar() = dump_log ? 1 : 0; + + bool success = false; + + diagnostics.Clear(); + + // Write our function arguments into the process so we can run our function + if (get_class_info_function->WriteFunctionArguments( + exe_ctx, GetClassInfoArgs(helper), arguments, diagnostics)) { + EvaluateExpressionOptions options; + options.SetUnwindOnError(true); + options.SetTryAllThreads(false); + options.SetStopOthers(true); + options.SetIgnoreBreakpoints(true); + options.SetTimeout(process->GetUtilityExpressionTimeout()); + options.SetIsForUtilityExpr(true); + + CompilerType clang_uint32_t_type = + scratch_ts_sp->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 32); + + Value return_value; + return_value.SetValueType(Value::ValueType::Scalar); + return_value.SetCompilerType(clang_uint32_t_type); + return_value.GetScalar() = 0; + + diagnostics.Clear(); + + // Run the function + ExpressionResults results = get_class_info_function->ExecuteFunction( + exe_ctx, &GetClassInfoArgs(helper), options, diagnostics, return_value); + + if (results == eExpressionCompleted) { + // The result is the number of ClassInfo structures that were filled in + num_class_infos = return_value.GetScalar().ULong(); + LLDB_LOG(log, "Discovered {0} Objective-C classes", num_class_infos); + if (num_class_infos > 0) { + // Read the ClassInfo structures + DataBufferHeap buffer(num_class_infos * class_info_byte_size, 0); + if (process->ReadMemory(class_infos_addr, buffer.GetBytes(), + buffer.GetByteSize(), + err) == buffer.GetByteSize()) { + DataExtractor class_infos_data(buffer.GetBytes(), + buffer.GetByteSize(), + process->GetByteOrder(), addr_size); + m_runtime.ParseClassInfoArray(class_infos_data, num_class_infos); + } + } + success = true; + } else { + if (log) { + LLDB_LOGF(log, "Error evaluating our find class name function."); + diagnostics.Dump(log); + } + } + } else { + if (log) { + LLDB_LOGF(log, "Error writing function arguments."); + diagnostics.Dump(log); + } + } + + return DescriptorMapUpdateResult(success, false, num_class_infos); +} + +uint32_t AppleObjCRuntimeV2::ParseClassInfoArray(const DataExtractor &data, + uint32_t num_class_infos) { + // Parses an array of "num_class_infos" packed ClassInfo structures: + // + // struct ClassInfo + // { + // Class isa; + // uint32_t hash; + // } __attribute__((__packed__)); + + Log *log = GetLog(LLDBLog::Types); + bool should_log = log && log->GetVerbose(); + + uint32_t num_parsed = 0; + + // Iterate through all ClassInfo structures + lldb::offset_t offset = 0; + for (uint32_t i = 0; i < num_class_infos; ++i) { + ObjCISA isa = data.GetAddress(&offset); + + if (isa == 0) { + if (should_log) + LLDB_LOGF( + log, "AppleObjCRuntimeV2 found NULL isa, ignoring this class info"); + continue; + } + // Check if we already know about this ISA, if we do, the info will never + // change, so we can just skip it. + if (ISAIsCached(isa)) { + if (should_log) + LLDB_LOGF(log, + "AppleObjCRuntimeV2 found cached isa=0x%" PRIx64 + ", ignoring this class info", + isa); + offset += 4; + } else { + // Read the 32 bit hash for the class name + const uint32_t name_hash = data.GetU32(&offset); + ClassDescriptorSP descriptor_sp( + new ClassDescriptorV2(*this, isa, nullptr)); + + // The code in g_get_shared_cache_class_info_body sets the value of the + // hash to 0 to signal a demangled symbol. We use class_getName() in that + // code to find the class name, but this returns a demangled name for + // Swift symbols. For those symbols, recompute the hash here by reading + // their name from the runtime. + if (name_hash) + AddClass(isa, descriptor_sp, name_hash); + else + AddClass(isa, descriptor_sp, + descriptor_sp->GetClassName().AsCString(nullptr)); + num_parsed++; + if (should_log) + LLDB_LOGF(log, + "AppleObjCRuntimeV2 added isa=0x%" PRIx64 + ", hash=0x%8.8x, name=%s", + isa, name_hash, + descriptor_sp->GetClassName().AsCString("<unknown>")); + } + } + if (should_log) + LLDB_LOGF(log, "AppleObjCRuntimeV2 parsed %" PRIu32 " class infos", + num_parsed); + return num_parsed; +} + +bool AppleObjCRuntimeV2::HasSymbol(ConstString Name) { + if (!m_objc_module_sp) + return false; + if (const Symbol *symbol = m_objc_module_sp->FindFirstSymbolWithNameAndType( + Name, lldb::eSymbolTypeCode)) { + if (symbol->ValueIsAddress() || symbol->GetAddressRef().IsValid()) + return true; + } + return false; +} + +AppleObjCRuntimeV2::DescriptorMapUpdateResult +AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::UpdateISAToDescriptorMap() { + Process *process = m_runtime.GetProcess(); + if (process == nullptr) + return DescriptorMapUpdateResult::Fail(); + + Log *log = GetLog(LLDBLog::Process | LLDBLog::Types); + + ExecutionContext exe_ctx; + + ThreadSP thread_sp = process->GetThreadList().GetExpressionExecutionThread(); + + if (!thread_sp) + return DescriptorMapUpdateResult::Fail(); + + if (!thread_sp->SafeToCallFunctions()) + return DescriptorMapUpdateResult::Retry(); + + thread_sp->CalculateExecutionContext(exe_ctx); + TypeSystemClangSP scratch_ts_sp = + ScratchTypeSystemClang::GetForTarget(process->GetTarget()); + + if (!scratch_ts_sp) + return DescriptorMapUpdateResult::Fail(); + + Address function_address; + + const uint32_t addr_size = process->GetAddressByteSize(); + + Status err; + + uint32_t num_class_infos = 0; + + const lldb::addr_t objc_opt_ptr = m_runtime.GetSharedCacheReadOnlyAddress(); + const lldb::addr_t shared_cache_base_addr = + m_runtime.GetSharedCacheBaseAddress(); + + if (objc_opt_ptr == LLDB_INVALID_ADDRESS || + shared_cache_base_addr == LLDB_INVALID_ADDRESS) + return DescriptorMapUpdateResult::Fail(); + + // The number of entries to pre-allocate room for. + // Each entry is (addrsize + 4) bytes + // FIXME: It is not sustainable to continue incrementing this value every time + // the shared cache grows. This is because it requires allocating memory in + // the inferior process and some inferior processes have small memory limits. + const uint32_t max_num_classes = 212992; + + UtilityFunction *get_class_info_code = GetClassInfoUtilityFunction(exe_ctx); + if (!get_class_info_code) { + // The callee will have already logged a useful error message. + return DescriptorMapUpdateResult::Fail(); + } + + FunctionCaller *get_shared_cache_class_info_function = + get_class_info_code->GetFunctionCaller(); + + if (!get_shared_cache_class_info_function) { + LLDB_LOGF(log, "Failed to get implementation lookup function caller."); + return DescriptorMapUpdateResult::Fail(); + } + + ValueList arguments = + get_shared_cache_class_info_function->GetArgumentValues(); + + DiagnosticManager diagnostics; + + const uint32_t class_info_byte_size = addr_size + 4; + const uint32_t class_infos_byte_size = max_num_classes * class_info_byte_size; + lldb::addr_t class_infos_addr = process->AllocateMemory( + class_infos_byte_size, ePermissionsReadable | ePermissionsWritable, err); + const uint32_t relative_selector_offset_addr_size = 64; + lldb::addr_t relative_selector_offset_addr = + process->AllocateMemory(relative_selector_offset_addr_size, + ePermissionsReadable | ePermissionsWritable, err); + + if (class_infos_addr == LLDB_INVALID_ADDRESS) { + LLDB_LOGF(log, + "unable to allocate %" PRIu32 + " bytes in process for shared cache read", + class_infos_byte_size); + return DescriptorMapUpdateResult::Fail(); + } + + std::lock_guard<std::mutex> guard(m_mutex); + + // Fill in our function argument values + arguments.GetValueAtIndex(0)->GetScalar() = objc_opt_ptr; + arguments.GetValueAtIndex(1)->GetScalar() = shared_cache_base_addr; + arguments.GetValueAtIndex(2)->GetScalar() = class_infos_addr; + arguments.GetValueAtIndex(3)->GetScalar() = relative_selector_offset_addr; + arguments.GetValueAtIndex(4)->GetScalar() = class_infos_byte_size; + // Only dump the runtime classes from the expression evaluation if the log is + // verbose: + Log *type_log = GetLog(LLDBLog::Types); + bool dump_log = type_log && type_log->GetVerbose(); + + arguments.GetValueAtIndex(5)->GetScalar() = dump_log ? 1 : 0; + + bool success = false; + + diagnostics.Clear(); + + // Write our function arguments into the process so we can run our function + if (get_shared_cache_class_info_function->WriteFunctionArguments( + exe_ctx, m_args, arguments, diagnostics)) { + EvaluateExpressionOptions options; + options.SetUnwindOnError(true); + options.SetTryAllThreads(false); + options.SetStopOthers(true); + options.SetIgnoreBreakpoints(true); + options.SetTimeout(process->GetUtilityExpressionTimeout()); + options.SetIsForUtilityExpr(true); + + CompilerType clang_uint32_t_type = + scratch_ts_sp->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 32); + + Value return_value; + return_value.SetValueType(Value::ValueType::Scalar); + return_value.SetCompilerType(clang_uint32_t_type); + return_value.GetScalar() = 0; + + diagnostics.Clear(); + + // Run the function + ExpressionResults results = + get_shared_cache_class_info_function->ExecuteFunction( + exe_ctx, &m_args, options, diagnostics, return_value); + + if (results == eExpressionCompleted) { + // The result is the number of ClassInfo structures that were filled in + num_class_infos = return_value.GetScalar().ULong(); + LLDB_LOG(log, "Discovered {0} Objective-C classes in the shared cache", + num_class_infos); + // Assert if there were more classes than we pre-allocated + // room for. + assert(num_class_infos <= max_num_classes); + if (num_class_infos > 0) { + if (num_class_infos > max_num_classes) { + num_class_infos = max_num_classes; + + success = false; + } else { + success = true; + } + + // Read the relative selector offset. + DataBufferHeap relative_selector_offset_buffer(64, 0); + if (process->ReadMemory(relative_selector_offset_addr, + relative_selector_offset_buffer.GetBytes(), + relative_selector_offset_buffer.GetByteSize(), + err) == + relative_selector_offset_buffer.GetByteSize()) { + DataExtractor relative_selector_offset_data( + relative_selector_offset_buffer.GetBytes(), + relative_selector_offset_buffer.GetByteSize(), + process->GetByteOrder(), addr_size); + lldb::offset_t offset = 0; + uint64_t relative_selector_offset = + relative_selector_offset_data.GetU64(&offset); + if (relative_selector_offset > 0) { + // The offset is relative to the objc_opt struct. + m_runtime.SetRelativeSelectorBaseAddr(objc_opt_ptr + + relative_selector_offset); + } + } + + // Read the ClassInfo structures + DataBufferHeap class_infos_buffer( + num_class_infos * class_info_byte_size, 0); + if (process->ReadMemory(class_infos_addr, class_infos_buffer.GetBytes(), + class_infos_buffer.GetByteSize(), + err) == class_infos_buffer.GetByteSize()) { + DataExtractor class_infos_data(class_infos_buffer.GetBytes(), + class_infos_buffer.GetByteSize(), + process->GetByteOrder(), addr_size); + + m_runtime.ParseClassInfoArray(class_infos_data, num_class_infos); + } + } else { + success = true; + } + } else { + if (log) { + LLDB_LOGF(log, "Error evaluating our find class name function."); + diagnostics.Dump(log); + } + } + } else { + if (log) { + LLDB_LOGF(log, "Error writing function arguments."); + diagnostics.Dump(log); + } + } + + // Deallocate the memory we allocated for the ClassInfo array + process->DeallocateMemory(class_infos_addr); + + return DescriptorMapUpdateResult(success, false, num_class_infos); +} + +lldb::addr_t AppleObjCRuntimeV2::GetSharedCacheReadOnlyAddress() { + Process *process = GetProcess(); + + if (process) { + ModuleSP objc_module_sp(GetObjCModule()); + + if (objc_module_sp) { + ObjectFile *objc_object = objc_module_sp->GetObjectFile(); + + if (objc_object) { + SectionList *section_list = objc_module_sp->GetSectionList(); + + if (section_list) { + SectionSP text_segment_sp( + section_list->FindSectionByName(ConstString("__TEXT"))); + + if (text_segment_sp) { + SectionSP objc_opt_section_sp( + text_segment_sp->GetChildren().FindSectionByName( + ConstString("__objc_opt_ro"))); + + if (objc_opt_section_sp) { + return objc_opt_section_sp->GetLoadBaseAddress( + &process->GetTarget()); + } + } + } + } + } + } + return LLDB_INVALID_ADDRESS; +} + +lldb::addr_t AppleObjCRuntimeV2::GetSharedCacheBaseAddress() { + StructuredData::ObjectSP info = m_process->GetSharedCacheInfo(); + if (!info) + return LLDB_INVALID_ADDRESS; + + StructuredData::Dictionary *info_dict = info->GetAsDictionary(); + if (!info_dict) + return LLDB_INVALID_ADDRESS; + + StructuredData::ObjectSP value = + info_dict->GetValueForKey("shared_cache_base_address"); + if (!value) + return LLDB_INVALID_ADDRESS; + + return value->GetUnsignedIntegerValue(LLDB_INVALID_ADDRESS); +} + +void AppleObjCRuntimeV2::UpdateISAToDescriptorMapIfNeeded() { + LLDB_SCOPED_TIMER(); + + Log *log = GetLog(LLDBLog::Process | LLDBLog::Types); + + // Else we need to check with our process to see when the map was updated. + Process *process = GetProcess(); + + if (process) { + RemoteNXMapTable hash_table; + + // Update the process stop ID that indicates the last time we updated the + // map, whether it was successful or not. + m_isa_to_descriptor_stop_id = process->GetStopID(); + + // Ask the runtime is the realized class generation count changed. Unlike + // the hash table, this accounts for lazily named classes. + const bool class_count_changed = RealizedClassGenerationCountChanged(); + + if (!m_hash_signature.NeedsUpdate(process, this, hash_table) && + !class_count_changed) + return; + + m_hash_signature.UpdateSignature(hash_table); + + // Grab the dynamically loaded Objective-C classes from memory. + DescriptorMapUpdateResult dynamic_update_result = + m_dynamic_class_info_extractor.UpdateISAToDescriptorMap(hash_table); + + // Now get the objc classes that are baked into the Objective-C runtime in + // the shared cache, but only once per process as this data never changes + if (!m_loaded_objc_opt) { + // it is legitimately possible for the shared cache to be empty - in that + // case, the dynamic hash table will contain all the class information we + // need; the situation we're trying to detect is one where we aren't + // seeing class information from the runtime - in order to detect that + // vs. just the shared cache being empty or sparsely populated, we set an + // arbitrary (very low) threshold for the number of classes that we want + // to see in a "good" scenario - anything below that is suspicious + // (Foundation alone has thousands of classes) + const uint32_t num_classes_to_warn_at = 500; + + DescriptorMapUpdateResult shared_cache_update_result = + m_shared_cache_class_info_extractor.UpdateISAToDescriptorMap(); + + LLDB_LOGF(log, + "attempted to read objc class data - results: " + "[dynamic_update]: ran: %s, retry: %s, count: %" PRIu32 + " [shared_cache_update]: ran: %s, retry: %s, count: %" PRIu32, + dynamic_update_result.m_update_ran ? "yes" : "no", + dynamic_update_result.m_retry_update ? "yes" : "no", + dynamic_update_result.m_num_found, + shared_cache_update_result.m_update_ran ? "yes" : "no", + shared_cache_update_result.m_retry_update ? "yes" : "no", + shared_cache_update_result.m_num_found); + + // warn if: + // - we could not run either expression + // - we found fewer than num_classes_to_warn_at classes total + if (dynamic_update_result.m_retry_update || + shared_cache_update_result.m_retry_update) + WarnIfNoClassesCached(SharedCacheWarningReason::eExpressionUnableToRun); + else if ((!shared_cache_update_result.m_update_ran) || + (!dynamic_update_result.m_update_ran)) + WarnIfNoClassesCached( + SharedCacheWarningReason::eExpressionExecutionFailure); + else if (dynamic_update_result.m_num_found + + shared_cache_update_result.m_num_found < + num_classes_to_warn_at) + WarnIfNoClassesCached(SharedCacheWarningReason::eNotEnoughClassesRead); + else + m_loaded_objc_opt = true; + } + } else { + m_isa_to_descriptor_stop_id = UINT32_MAX; + } +} + +bool AppleObjCRuntimeV2::RealizedClassGenerationCountChanged() { + Process *process = GetProcess(); + if (!process) + return false; + + Status error; + uint64_t objc_debug_realized_class_generation_count = + ExtractRuntimeGlobalSymbol( + process, ConstString("objc_debug_realized_class_generation_count"), + GetObjCModule(), error); + if (error.Fail()) + return false; + + if (m_realized_class_generation_count == + objc_debug_realized_class_generation_count) + return false; + + Log *log = GetLog(LLDBLog::Process | LLDBLog::Types); + LLDB_LOG(log, + "objc_debug_realized_class_generation_count changed from {0} to {1}", + m_realized_class_generation_count, + objc_debug_realized_class_generation_count); + + m_realized_class_generation_count = + objc_debug_realized_class_generation_count; + + return true; +} + +static bool DoesProcessHaveSharedCache(Process &process) { + PlatformSP platform_sp = process.GetTarget().GetPlatform(); + if (!platform_sp) + return true; // this should not happen + + llvm::StringRef platform_plugin_name_sr = platform_sp->GetPluginName(); + if (platform_plugin_name_sr.ends_with("-simulator")) + return false; + + return true; +} + +void AppleObjCRuntimeV2::WarnIfNoClassesCached( + SharedCacheWarningReason reason) { + if (GetProcess() && !DoesProcessHaveSharedCache(*GetProcess())) { + // Simulators do not have the objc_opt_ro class table so don't actually + // complain to the user + return; + } + + Debugger &debugger(GetProcess()->GetTarget().GetDebugger()); + switch (reason) { + case SharedCacheWarningReason::eNotEnoughClassesRead: + Debugger::ReportWarning("could not find Objective-C class data in " + "the process. This may reduce the quality of type " + "information available.\n", + debugger.GetID(), &m_no_classes_cached_warning); + break; + case SharedCacheWarningReason::eExpressionExecutionFailure: + Debugger::ReportWarning( + "could not execute support code to read " + "Objective-C class data in the process. This may " + "reduce the quality of type information available.\n", + debugger.GetID(), &m_no_classes_cached_warning); + break; + case SharedCacheWarningReason::eExpressionUnableToRun: + Debugger::ReportWarning( + "could not execute support code to read Objective-C class data because " + "it's not yet safe to do so, and will be retried later.\n", + debugger.GetID(), nullptr); + break; + } +} + +void AppleObjCRuntimeV2::WarnIfNoExpandedSharedCache() { + if (!m_objc_module_sp) + return; + + ObjectFile *object_file = m_objc_module_sp->GetObjectFile(); + if (!object_file) + return; + + if (!object_file->IsInMemory()) + return; + + Target &target = GetProcess()->GetTarget(); + Debugger &debugger = target.GetDebugger(); + + std::string buffer; + llvm::raw_string_ostream os(buffer); + + os << "libobjc.A.dylib is being read from process memory. This " + "indicates that LLDB could not "; + if (PlatformSP platform_sp = target.GetPlatform()) { + if (platform_sp->IsHost()) { + os << "read from the host's in-memory shared cache"; + } else { + os << "find the on-disk shared cache for this device"; + } + } else { + os << "read from the shared cache"; + } + os << ". This will likely reduce debugging performance.\n"; + + Debugger::ReportWarning(os.str(), debugger.GetID(), + &m_no_expanded_cache_warning); +} + +DeclVendor *AppleObjCRuntimeV2::GetDeclVendor() { + if (!m_decl_vendor_up) + m_decl_vendor_up = std::make_unique<AppleObjCDeclVendor>(*this); + + return m_decl_vendor_up.get(); +} + +lldb::addr_t AppleObjCRuntimeV2::LookupRuntimeSymbol(ConstString name) { + lldb::addr_t ret = LLDB_INVALID_ADDRESS; + + const char *name_cstr = name.AsCString(); + + if (name_cstr) { + llvm::StringRef name_strref(name_cstr); + + llvm::StringRef ivar_prefix("OBJC_IVAR_$_"); + llvm::StringRef class_prefix("OBJC_CLASS_$_"); + + if (name_strref.starts_with(ivar_prefix)) { + llvm::StringRef ivar_skipped_prefix = + name_strref.substr(ivar_prefix.size()); + std::pair<llvm::StringRef, llvm::StringRef> class_and_ivar = + ivar_skipped_prefix.split('.'); + + if (!class_and_ivar.first.empty() && !class_and_ivar.second.empty()) { + const ConstString class_name_cs(class_and_ivar.first); + ClassDescriptorSP descriptor = + ObjCLanguageRuntime::GetClassDescriptorFromClassName(class_name_cs); + + if (descriptor) { + const ConstString ivar_name_cs(class_and_ivar.second); + const char *ivar_name_cstr = ivar_name_cs.AsCString(); + + auto ivar_func = [&ret, + ivar_name_cstr](const char *name, const char *type, + lldb::addr_t offset_addr, + uint64_t size) -> lldb::addr_t { + if (!strcmp(name, ivar_name_cstr)) { + ret = offset_addr; + return true; + } + return false; + }; + + descriptor->Describe( + std::function<void(ObjCISA)>(nullptr), + std::function<bool(const char *, const char *)>(nullptr), + std::function<bool(const char *, const char *)>(nullptr), + ivar_func); + } + } + } else if (name_strref.starts_with(class_prefix)) { + llvm::StringRef class_skipped_prefix = + name_strref.substr(class_prefix.size()); + const ConstString class_name_cs(class_skipped_prefix); + ClassDescriptorSP descriptor = + GetClassDescriptorFromClassName(class_name_cs); + + if (descriptor) + ret = descriptor->GetISA(); + } + } + + return ret; +} + +AppleObjCRuntimeV2::NonPointerISACache * +AppleObjCRuntimeV2::NonPointerISACache::CreateInstance( + AppleObjCRuntimeV2 &runtime, const lldb::ModuleSP &objc_module_sp) { + Process *process(runtime.GetProcess()); + + Status error; + + Log *log = GetLog(LLDBLog::Types); + + auto objc_debug_isa_magic_mask = ExtractRuntimeGlobalSymbol( + process, ConstString("objc_debug_isa_magic_mask"), objc_module_sp, error); + if (error.Fail()) + return nullptr; + + auto objc_debug_isa_magic_value = ExtractRuntimeGlobalSymbol( + process, ConstString("objc_debug_isa_magic_value"), objc_module_sp, + error); + if (error.Fail()) + return nullptr; + + auto objc_debug_isa_class_mask = ExtractRuntimeGlobalSymbol( + process, ConstString("objc_debug_isa_class_mask"), objc_module_sp, error); + if (error.Fail()) + return nullptr; + + if (log) + log->PutCString("AOCRT::NPI: Found all the non-indexed ISA masks"); + + bool foundError = false; + auto objc_debug_indexed_isa_magic_mask = ExtractRuntimeGlobalSymbol( + process, ConstString("objc_debug_indexed_isa_magic_mask"), objc_module_sp, + error); + foundError |= error.Fail(); + + auto objc_debug_indexed_isa_magic_value = ExtractRuntimeGlobalSymbol( + process, ConstString("objc_debug_indexed_isa_magic_value"), + objc_module_sp, error); + foundError |= error.Fail(); + + auto objc_debug_indexed_isa_index_mask = ExtractRuntimeGlobalSymbol( + process, ConstString("objc_debug_indexed_isa_index_mask"), objc_module_sp, + error); + foundError |= error.Fail(); + + auto objc_debug_indexed_isa_index_shift = ExtractRuntimeGlobalSymbol( + process, ConstString("objc_debug_indexed_isa_index_shift"), + objc_module_sp, error); + foundError |= error.Fail(); + + auto objc_indexed_classes = + ExtractRuntimeGlobalSymbol(process, ConstString("objc_indexed_classes"), + objc_module_sp, error, false); + foundError |= error.Fail(); + + if (log) + log->PutCString("AOCRT::NPI: Found all the indexed ISA masks"); + + // we might want to have some rules to outlaw these other values (e.g if the + // mask is zero but the value is non-zero, ...) + + return new NonPointerISACache( + runtime, objc_module_sp, objc_debug_isa_class_mask, + objc_debug_isa_magic_mask, objc_debug_isa_magic_value, + objc_debug_indexed_isa_magic_mask, objc_debug_indexed_isa_magic_value, + objc_debug_indexed_isa_index_mask, objc_debug_indexed_isa_index_shift, + foundError ? 0 : objc_indexed_classes); +} + +AppleObjCRuntimeV2::TaggedPointerVendorV2 * +AppleObjCRuntimeV2::TaggedPointerVendorV2::CreateInstance( + AppleObjCRuntimeV2 &runtime, const lldb::ModuleSP &objc_module_sp) { + Process *process(runtime.GetProcess()); + + Status error; + + auto objc_debug_taggedpointer_mask = ExtractRuntimeGlobalSymbol( + process, ConstString("objc_debug_taggedpointer_mask"), objc_module_sp, + error); + if (error.Fail()) + return new TaggedPointerVendorLegacy(runtime); + + auto objc_debug_taggedpointer_slot_shift = ExtractRuntimeGlobalSymbol( + process, ConstString("objc_debug_taggedpointer_slot_shift"), + objc_module_sp, error, true, 4); + if (error.Fail()) + return new TaggedPointerVendorLegacy(runtime); + + auto objc_debug_taggedpointer_slot_mask = ExtractRuntimeGlobalSymbol( + process, ConstString("objc_debug_taggedpointer_slot_mask"), + objc_module_sp, error, true, 4); + if (error.Fail()) + return new TaggedPointerVendorLegacy(runtime); + + auto objc_debug_taggedpointer_payload_lshift = ExtractRuntimeGlobalSymbol( + process, ConstString("objc_debug_taggedpointer_payload_lshift"), + objc_module_sp, error, true, 4); + if (error.Fail()) + return new TaggedPointerVendorLegacy(runtime); + + auto objc_debug_taggedpointer_payload_rshift = ExtractRuntimeGlobalSymbol( + process, ConstString("objc_debug_taggedpointer_payload_rshift"), + objc_module_sp, error, true, 4); + if (error.Fail()) + return new TaggedPointerVendorLegacy(runtime); + + auto objc_debug_taggedpointer_classes = ExtractRuntimeGlobalSymbol( + process, ConstString("objc_debug_taggedpointer_classes"), objc_module_sp, + error, false); + if (error.Fail()) + return new TaggedPointerVendorLegacy(runtime); + + // try to detect the "extended tagged pointer" variables - if any are + // missing, use the non-extended vendor + do { + auto objc_debug_taggedpointer_ext_mask = ExtractRuntimeGlobalSymbol( + process, ConstString("objc_debug_taggedpointer_ext_mask"), + objc_module_sp, error); + if (error.Fail()) + break; + + auto objc_debug_taggedpointer_ext_slot_shift = ExtractRuntimeGlobalSymbol( + process, ConstString("objc_debug_taggedpointer_ext_slot_shift"), + objc_module_sp, error, true, 4); + if (error.Fail()) + break; + + auto objc_debug_taggedpointer_ext_slot_mask = ExtractRuntimeGlobalSymbol( + process, ConstString("objc_debug_taggedpointer_ext_slot_mask"), + objc_module_sp, error, true, 4); + if (error.Fail()) + break; + + auto objc_debug_taggedpointer_ext_classes = ExtractRuntimeGlobalSymbol( + process, ConstString("objc_debug_taggedpointer_ext_classes"), + objc_module_sp, error, false); + if (error.Fail()) + break; + + auto objc_debug_taggedpointer_ext_payload_lshift = + ExtractRuntimeGlobalSymbol( + process, ConstString("objc_debug_taggedpointer_ext_payload_lshift"), + objc_module_sp, error, true, 4); + if (error.Fail()) + break; + + auto objc_debug_taggedpointer_ext_payload_rshift = + ExtractRuntimeGlobalSymbol( + process, ConstString("objc_debug_taggedpointer_ext_payload_rshift"), + objc_module_sp, error, true, 4); + if (error.Fail()) + break; + + return new TaggedPointerVendorExtended( + runtime, objc_debug_taggedpointer_mask, + objc_debug_taggedpointer_ext_mask, objc_debug_taggedpointer_slot_shift, + objc_debug_taggedpointer_ext_slot_shift, + objc_debug_taggedpointer_slot_mask, + objc_debug_taggedpointer_ext_slot_mask, + objc_debug_taggedpointer_payload_lshift, + objc_debug_taggedpointer_payload_rshift, + objc_debug_taggedpointer_ext_payload_lshift, + objc_debug_taggedpointer_ext_payload_rshift, + objc_debug_taggedpointer_classes, objc_debug_taggedpointer_ext_classes); + } while (false); + + // we might want to have some rules to outlaw these values (e.g if the + // table's address is zero) + + return new TaggedPointerVendorRuntimeAssisted( + runtime, objc_debug_taggedpointer_mask, + objc_debug_taggedpointer_slot_shift, objc_debug_taggedpointer_slot_mask, + objc_debug_taggedpointer_payload_lshift, + objc_debug_taggedpointer_payload_rshift, + objc_debug_taggedpointer_classes); +} + +bool AppleObjCRuntimeV2::TaggedPointerVendorLegacy::IsPossibleTaggedPointer( + lldb::addr_t ptr) { + return (ptr & 1); +} + +ObjCLanguageRuntime::ClassDescriptorSP +AppleObjCRuntimeV2::TaggedPointerVendorLegacy::GetClassDescriptor( + lldb::addr_t ptr) { + if (!IsPossibleTaggedPointer(ptr)) + return ObjCLanguageRuntime::ClassDescriptorSP(); + + uint32_t foundation_version = m_runtime.GetFoundationVersion(); + + if (foundation_version == LLDB_INVALID_MODULE_VERSION) + return ObjCLanguageRuntime::ClassDescriptorSP(); + + uint64_t class_bits = (ptr & 0xE) >> 1; + ConstString name; + + static ConstString g_NSAtom("NSAtom"); + static ConstString g_NSNumber("NSNumber"); + static ConstString g_NSDateTS("NSDateTS"); + static ConstString g_NSManagedObject("NSManagedObject"); + static ConstString g_NSDate("NSDate"); + + if (foundation_version >= 900) { + switch (class_bits) { + case 0: + name = g_NSAtom; + break; + case 3: + name = g_NSNumber; + break; + case 4: + name = g_NSDateTS; + break; + case 5: + name = g_NSManagedObject; + break; + case 6: + name = g_NSDate; + break; + default: + return ObjCLanguageRuntime::ClassDescriptorSP(); + } + } else { + switch (class_bits) { + case 1: + name = g_NSNumber; + break; + case 5: + name = g_NSManagedObject; + break; + case 6: + name = g_NSDate; + break; + case 7: + name = g_NSDateTS; + break; + default: + return ObjCLanguageRuntime::ClassDescriptorSP(); + } + } + + lldb::addr_t unobfuscated = ptr ^ m_runtime.GetTaggedPointerObfuscator(); + return ClassDescriptorSP(new ClassDescriptorV2Tagged(name, unobfuscated)); +} + +AppleObjCRuntimeV2::TaggedPointerVendorRuntimeAssisted:: + TaggedPointerVendorRuntimeAssisted( + AppleObjCRuntimeV2 &runtime, uint64_t objc_debug_taggedpointer_mask, + uint32_t objc_debug_taggedpointer_slot_shift, + uint32_t objc_debug_taggedpointer_slot_mask, + uint32_t objc_debug_taggedpointer_payload_lshift, + uint32_t objc_debug_taggedpointer_payload_rshift, + lldb::addr_t objc_debug_taggedpointer_classes) + : TaggedPointerVendorV2(runtime), m_cache(), + m_objc_debug_taggedpointer_mask(objc_debug_taggedpointer_mask), + m_objc_debug_taggedpointer_slot_shift( + objc_debug_taggedpointer_slot_shift), + m_objc_debug_taggedpointer_slot_mask(objc_debug_taggedpointer_slot_mask), + m_objc_debug_taggedpointer_payload_lshift( + objc_debug_taggedpointer_payload_lshift), + m_objc_debug_taggedpointer_payload_rshift( + objc_debug_taggedpointer_payload_rshift), + m_objc_debug_taggedpointer_classes(objc_debug_taggedpointer_classes) {} + +bool AppleObjCRuntimeV2::TaggedPointerVendorRuntimeAssisted:: + IsPossibleTaggedPointer(lldb::addr_t ptr) { + return (ptr & m_objc_debug_taggedpointer_mask) != 0; +} + +ObjCLanguageRuntime::ClassDescriptorSP +AppleObjCRuntimeV2::TaggedPointerVendorRuntimeAssisted::GetClassDescriptor( + lldb::addr_t ptr) { + ClassDescriptorSP actual_class_descriptor_sp; + uint64_t unobfuscated = (ptr) ^ m_runtime.GetTaggedPointerObfuscator(); + + if (!IsPossibleTaggedPointer(unobfuscated)) + return ObjCLanguageRuntime::ClassDescriptorSP(); + + uintptr_t slot = (ptr >> m_objc_debug_taggedpointer_slot_shift) & + m_objc_debug_taggedpointer_slot_mask; + + CacheIterator iterator = m_cache.find(slot), end = m_cache.end(); + if (iterator != end) { + actual_class_descriptor_sp = iterator->second; + } else { + Process *process(m_runtime.GetProcess()); + uintptr_t slot_ptr = slot * process->GetAddressByteSize() + + m_objc_debug_taggedpointer_classes; + Status error; + uintptr_t slot_data = process->ReadPointerFromMemory(slot_ptr, error); + if (error.Fail() || slot_data == 0 || + slot_data == uintptr_t(LLDB_INVALID_ADDRESS)) + return nullptr; + actual_class_descriptor_sp = + m_runtime.GetClassDescriptorFromISA((ObjCISA)slot_data); + if (!actual_class_descriptor_sp) { + if (ABISP abi_sp = process->GetABI()) { + ObjCISA fixed_isa = abi_sp->FixCodeAddress((ObjCISA)slot_data); + actual_class_descriptor_sp = + m_runtime.GetClassDescriptorFromISA(fixed_isa); + } + } + if (!actual_class_descriptor_sp) + return ObjCLanguageRuntime::ClassDescriptorSP(); + m_cache[slot] = actual_class_descriptor_sp; + } + + uint64_t data_payload = + ((unobfuscated << m_objc_debug_taggedpointer_payload_lshift) >> + m_objc_debug_taggedpointer_payload_rshift); + int64_t data_payload_signed = + ((int64_t)(unobfuscated << m_objc_debug_taggedpointer_payload_lshift) >> + m_objc_debug_taggedpointer_payload_rshift); + return ClassDescriptorSP(new ClassDescriptorV2Tagged( + actual_class_descriptor_sp, data_payload, data_payload_signed)); +} + +AppleObjCRuntimeV2::TaggedPointerVendorExtended::TaggedPointerVendorExtended( + AppleObjCRuntimeV2 &runtime, uint64_t objc_debug_taggedpointer_mask, + uint64_t objc_debug_taggedpointer_ext_mask, + uint32_t objc_debug_taggedpointer_slot_shift, + uint32_t objc_debug_taggedpointer_ext_slot_shift, + uint32_t objc_debug_taggedpointer_slot_mask, + uint32_t objc_debug_taggedpointer_ext_slot_mask, + uint32_t objc_debug_taggedpointer_payload_lshift, + uint32_t objc_debug_taggedpointer_payload_rshift, + uint32_t objc_debug_taggedpointer_ext_payload_lshift, + uint32_t objc_debug_taggedpointer_ext_payload_rshift, + lldb::addr_t objc_debug_taggedpointer_classes, + lldb::addr_t objc_debug_taggedpointer_ext_classes) + : TaggedPointerVendorRuntimeAssisted( + runtime, objc_debug_taggedpointer_mask, + objc_debug_taggedpointer_slot_shift, + objc_debug_taggedpointer_slot_mask, + objc_debug_taggedpointer_payload_lshift, + objc_debug_taggedpointer_payload_rshift, + objc_debug_taggedpointer_classes), + m_ext_cache(), + m_objc_debug_taggedpointer_ext_mask(objc_debug_taggedpointer_ext_mask), + m_objc_debug_taggedpointer_ext_slot_shift( + objc_debug_taggedpointer_ext_slot_shift), + m_objc_debug_taggedpointer_ext_slot_mask( + objc_debug_taggedpointer_ext_slot_mask), + m_objc_debug_taggedpointer_ext_payload_lshift( + objc_debug_taggedpointer_ext_payload_lshift), + m_objc_debug_taggedpointer_ext_payload_rshift( + objc_debug_taggedpointer_ext_payload_rshift), + m_objc_debug_taggedpointer_ext_classes( + objc_debug_taggedpointer_ext_classes) {} + +bool AppleObjCRuntimeV2::TaggedPointerVendorExtended:: + IsPossibleExtendedTaggedPointer(lldb::addr_t ptr) { + if (!IsPossibleTaggedPointer(ptr)) + return false; + + if (m_objc_debug_taggedpointer_ext_mask == 0) + return false; + + return ((ptr & m_objc_debug_taggedpointer_ext_mask) == + m_objc_debug_taggedpointer_ext_mask); +} + +ObjCLanguageRuntime::ClassDescriptorSP +AppleObjCRuntimeV2::TaggedPointerVendorExtended::GetClassDescriptor( + lldb::addr_t ptr) { + ClassDescriptorSP actual_class_descriptor_sp; + uint64_t unobfuscated = (ptr) ^ m_runtime.GetTaggedPointerObfuscator(); + + if (!IsPossibleTaggedPointer(unobfuscated)) + return ObjCLanguageRuntime::ClassDescriptorSP(); + + if (!IsPossibleExtendedTaggedPointer(unobfuscated)) + return this->TaggedPointerVendorRuntimeAssisted::GetClassDescriptor(ptr); + + uintptr_t slot = (ptr >> m_objc_debug_taggedpointer_ext_slot_shift) & + m_objc_debug_taggedpointer_ext_slot_mask; + + CacheIterator iterator = m_ext_cache.find(slot), end = m_ext_cache.end(); + if (iterator != end) { + actual_class_descriptor_sp = iterator->second; + } else { + Process *process(m_runtime.GetProcess()); + uintptr_t slot_ptr = slot * process->GetAddressByteSize() + + m_objc_debug_taggedpointer_ext_classes; + Status error; + uintptr_t slot_data = process->ReadPointerFromMemory(slot_ptr, error); + if (error.Fail() || slot_data == 0 || + slot_data == uintptr_t(LLDB_INVALID_ADDRESS)) + return nullptr; + actual_class_descriptor_sp = + m_runtime.GetClassDescriptorFromISA((ObjCISA)slot_data); + if (!actual_class_descriptor_sp) + return ObjCLanguageRuntime::ClassDescriptorSP(); + m_ext_cache[slot] = actual_class_descriptor_sp; + } + + uint64_t data_payload = (((uint64_t)unobfuscated + << m_objc_debug_taggedpointer_ext_payload_lshift) >> + m_objc_debug_taggedpointer_ext_payload_rshift); + int64_t data_payload_signed = + ((int64_t)((uint64_t)unobfuscated + << m_objc_debug_taggedpointer_ext_payload_lshift) >> + m_objc_debug_taggedpointer_ext_payload_rshift); + + return ClassDescriptorSP(new ClassDescriptorV2Tagged( + actual_class_descriptor_sp, data_payload, data_payload_signed)); +} + +AppleObjCRuntimeV2::NonPointerISACache::NonPointerISACache( + AppleObjCRuntimeV2 &runtime, const ModuleSP &objc_module_sp, + uint64_t objc_debug_isa_class_mask, uint64_t objc_debug_isa_magic_mask, + uint64_t objc_debug_isa_magic_value, + uint64_t objc_debug_indexed_isa_magic_mask, + uint64_t objc_debug_indexed_isa_magic_value, + uint64_t objc_debug_indexed_isa_index_mask, + uint64_t objc_debug_indexed_isa_index_shift, + lldb::addr_t objc_indexed_classes) + : m_runtime(runtime), m_cache(), m_objc_module_wp(objc_module_sp), + m_objc_debug_isa_class_mask(objc_debug_isa_class_mask), + m_objc_debug_isa_magic_mask(objc_debug_isa_magic_mask), + m_objc_debug_isa_magic_value(objc_debug_isa_magic_value), + m_objc_debug_indexed_isa_magic_mask(objc_debug_indexed_isa_magic_mask), + m_objc_debug_indexed_isa_magic_value(objc_debug_indexed_isa_magic_value), + m_objc_debug_indexed_isa_index_mask(objc_debug_indexed_isa_index_mask), + m_objc_debug_indexed_isa_index_shift(objc_debug_indexed_isa_index_shift), + m_objc_indexed_classes(objc_indexed_classes), m_indexed_isa_cache() {} + +ObjCLanguageRuntime::ClassDescriptorSP +AppleObjCRuntimeV2::NonPointerISACache::GetClassDescriptor(ObjCISA isa) { + ObjCISA real_isa = 0; + if (!EvaluateNonPointerISA(isa, real_isa)) + return ObjCLanguageRuntime::ClassDescriptorSP(); + auto cache_iter = m_cache.find(real_isa); + if (cache_iter != m_cache.end()) + return cache_iter->second; + auto descriptor_sp = + m_runtime.ObjCLanguageRuntime::GetClassDescriptorFromISA(real_isa); + if (descriptor_sp) // cache only positive matches since the table might grow + m_cache[real_isa] = descriptor_sp; + return descriptor_sp; +} + +bool AppleObjCRuntimeV2::NonPointerISACache::EvaluateNonPointerISA( + ObjCISA isa, ObjCISA &ret_isa) { + Log *log = GetLog(LLDBLog::Types); + + LLDB_LOGF(log, "AOCRT::NPI Evaluate(isa = 0x%" PRIx64 ")", (uint64_t)isa); + + if ((isa & ~m_objc_debug_isa_class_mask) == 0) + return false; + + // If all of the indexed ISA variables are set, then its possible that this + // ISA is indexed, and we should first try to get its value using the index. + // Note, we check these variables first as the ObjC runtime will set at least + // one of their values to 0 if they aren't needed. + if (m_objc_debug_indexed_isa_magic_mask && + m_objc_debug_indexed_isa_magic_value && + m_objc_debug_indexed_isa_index_mask && + m_objc_debug_indexed_isa_index_shift && m_objc_indexed_classes) { + if ((isa & ~m_objc_debug_indexed_isa_index_mask) == 0) + return false; + + if ((isa & m_objc_debug_indexed_isa_magic_mask) == + m_objc_debug_indexed_isa_magic_value) { + // Magic bits are correct, so try extract the index. + uintptr_t index = (isa & m_objc_debug_indexed_isa_index_mask) >> + m_objc_debug_indexed_isa_index_shift; + // If the index is out of bounds of the length of the array then check if + // the array has been updated. If that is the case then we should try + // read the count again, and update the cache if the count has been + // updated. + if (index > m_indexed_isa_cache.size()) { + LLDB_LOGF(log, + "AOCRT::NPI (index = %" PRIu64 + ") exceeds cache (size = %" PRIu64 ")", + (uint64_t)index, (uint64_t)m_indexed_isa_cache.size()); + + Process *process(m_runtime.GetProcess()); + + ModuleSP objc_module_sp(m_objc_module_wp.lock()); + if (!objc_module_sp) + return false; + + Status error; + auto objc_indexed_classes_count = ExtractRuntimeGlobalSymbol( + process, ConstString("objc_indexed_classes_count"), objc_module_sp, + error); + if (error.Fail()) + return false; + + LLDB_LOGF(log, "AOCRT::NPI (new class count = %" PRIu64 ")", + (uint64_t)objc_indexed_classes_count); + + if (objc_indexed_classes_count > m_indexed_isa_cache.size()) { + // Read the class entries we don't have. We should just read all of + // them instead of just the one we need as then we can cache those we + // may need later. + auto num_new_classes = + objc_indexed_classes_count - m_indexed_isa_cache.size(); + const uint32_t addr_size = process->GetAddressByteSize(); + DataBufferHeap buffer(num_new_classes * addr_size, 0); + + lldb::addr_t last_read_class = + m_objc_indexed_classes + (m_indexed_isa_cache.size() * addr_size); + size_t bytes_read = process->ReadMemory( + last_read_class, buffer.GetBytes(), buffer.GetByteSize(), error); + if (error.Fail() || bytes_read != buffer.GetByteSize()) + return false; + + LLDB_LOGF(log, "AOCRT::NPI (read new classes count = %" PRIu64 ")", + (uint64_t)num_new_classes); + + // Append the new entries to the existing cache. + DataExtractor data(buffer.GetBytes(), buffer.GetByteSize(), + process->GetByteOrder(), + process->GetAddressByteSize()); + + lldb::offset_t offset = 0; + for (unsigned i = 0; i != num_new_classes; ++i) + m_indexed_isa_cache.push_back(data.GetAddress(&offset)); + } + } + + // If the index is still out of range then this isn't a pointer. + if (index > m_indexed_isa_cache.size()) + return false; + + LLDB_LOGF(log, "AOCRT::NPI Evaluate(ret_isa = 0x%" PRIx64 ")", + (uint64_t)m_indexed_isa_cache[index]); + + ret_isa = m_indexed_isa_cache[index]; + return (ret_isa != 0); // this is a pointer so 0 is not a valid value + } + + return false; + } + + // Definitely not an indexed ISA, so try to use a mask to extract the pointer + // from the ISA. + if ((isa & m_objc_debug_isa_magic_mask) == m_objc_debug_isa_magic_value) { + ret_isa = isa & m_objc_debug_isa_class_mask; + return (ret_isa != 0); // this is a pointer so 0 is not a valid value + } + return false; +} + +ObjCLanguageRuntime::EncodingToTypeSP AppleObjCRuntimeV2::GetEncodingToType() { + if (!m_encoding_to_type_sp) + m_encoding_to_type_sp = + std::make_shared<AppleObjCTypeEncodingParser>(*this); + return m_encoding_to_type_sp; +} + +lldb_private::AppleObjCRuntime::ObjCISA +AppleObjCRuntimeV2::GetPointerISA(ObjCISA isa) { + ObjCISA ret = isa; + + if (auto *non_pointer_isa_cache = GetNonPointerIsaCache()) + non_pointer_isa_cache->EvaluateNonPointerISA(isa, ret); + + return ret; +} + +bool AppleObjCRuntimeV2::GetCFBooleanValuesIfNeeded() { + if (m_CFBoolean_values) + return true; + + static ConstString g_dunder_kCFBooleanFalse("__kCFBooleanFalse"); + static ConstString g_dunder_kCFBooleanTrue("__kCFBooleanTrue"); + static ConstString g_kCFBooleanFalse("kCFBooleanFalse"); + static ConstString g_kCFBooleanTrue("kCFBooleanTrue"); + + std::function<lldb::addr_t(ConstString, ConstString)> get_symbol = + [this](ConstString sym, ConstString real_sym) -> lldb::addr_t { + SymbolContextList sc_list; + GetProcess()->GetTarget().GetImages().FindSymbolsWithNameAndType( + sym, lldb::eSymbolTypeData, sc_list); + if (sc_list.GetSize() == 1) { + SymbolContext sc; + sc_list.GetContextAtIndex(0, sc); + if (sc.symbol) + return sc.symbol->GetLoadAddress(&GetProcess()->GetTarget()); + } + GetProcess()->GetTarget().GetImages().FindSymbolsWithNameAndType( + real_sym, lldb::eSymbolTypeData, sc_list); + if (sc_list.GetSize() != 1) + return LLDB_INVALID_ADDRESS; + + SymbolContext sc; + sc_list.GetContextAtIndex(0, sc); + if (!sc.symbol) + return LLDB_INVALID_ADDRESS; + + lldb::addr_t addr = sc.symbol->GetLoadAddress(&GetProcess()->GetTarget()); + Status error; + addr = GetProcess()->ReadPointerFromMemory(addr, error); + if (error.Fail()) + return LLDB_INVALID_ADDRESS; + return addr; + }; + + lldb::addr_t false_addr = get_symbol(g_dunder_kCFBooleanFalse, g_kCFBooleanFalse); + lldb::addr_t true_addr = get_symbol(g_dunder_kCFBooleanTrue, g_kCFBooleanTrue); + + return (m_CFBoolean_values = {false_addr, true_addr}).operator bool(); +} + +void AppleObjCRuntimeV2::GetValuesForGlobalCFBooleans(lldb::addr_t &cf_true, + lldb::addr_t &cf_false) { + if (GetCFBooleanValuesIfNeeded()) { + cf_true = m_CFBoolean_values->second; + cf_false = m_CFBoolean_values->first; + } else + this->AppleObjCRuntime::GetValuesForGlobalCFBooleans(cf_true, cf_false); +} + +void AppleObjCRuntimeV2::ModulesDidLoad(const ModuleList &module_list) { + AppleObjCRuntime::ModulesDidLoad(module_list); + if (HasReadObjCLibrary() && m_shared_cache_image_headers_up) + m_shared_cache_image_headers_up->SetNeedsUpdate(); +} + +bool AppleObjCRuntimeV2::IsSharedCacheImageLoaded(uint16_t image_index) { + if (!m_shared_cache_image_headers_up) { + m_shared_cache_image_headers_up = + SharedCacheImageHeaders::CreateSharedCacheImageHeaders(*this); + } + if (m_shared_cache_image_headers_up) + return m_shared_cache_image_headers_up->IsImageLoaded(image_index); + + return false; +} + +std::optional<uint64_t> AppleObjCRuntimeV2::GetSharedCacheImageHeaderVersion() { + if (!m_shared_cache_image_headers_up) { + m_shared_cache_image_headers_up = + SharedCacheImageHeaders::CreateSharedCacheImageHeaders(*this); + } + if (m_shared_cache_image_headers_up) + return m_shared_cache_image_headers_up->GetVersion(); + + return std::nullopt; +} + +#pragma mark Frame recognizers + +class ObjCExceptionRecognizedStackFrame : public RecognizedStackFrame { +public: + ObjCExceptionRecognizedStackFrame(StackFrameSP frame_sp) { + ThreadSP thread_sp = frame_sp->GetThread(); + ProcessSP process_sp = thread_sp->GetProcess(); + + const lldb::ABISP &abi = process_sp->GetABI(); + if (!abi) + return; + + TypeSystemClangSP scratch_ts_sp = + ScratchTypeSystemClang::GetForTarget(process_sp->GetTarget()); + if (!scratch_ts_sp) + return; + CompilerType voidstar = + scratch_ts_sp->GetBasicType(lldb::eBasicTypeVoid).GetPointerType(); + + ValueList args; + Value input_value; + input_value.SetCompilerType(voidstar); + args.PushValue(input_value); + + if (!abi->GetArgumentValues(*thread_sp, args)) + return; + + addr_t exception_addr = args.GetValueAtIndex(0)->GetScalar().ULongLong(); + + Value value(exception_addr); + value.SetCompilerType(voidstar); + exception = ValueObjectConstResult::Create(frame_sp.get(), value, + ConstString("exception")); + exception = ValueObjectRecognizerSynthesizedValue::Create( + *exception, eValueTypeVariableArgument); + exception = exception->GetDynamicValue(eDynamicDontRunTarget); + + m_arguments = ValueObjectListSP(new ValueObjectList()); + m_arguments->Append(exception); + + m_stop_desc = "hit Objective-C exception"; + } + + ValueObjectSP exception; + + lldb::ValueObjectSP GetExceptionObject() override { return exception; } +}; + +class ObjCExceptionThrowFrameRecognizer : public StackFrameRecognizer { + lldb::RecognizedStackFrameSP + RecognizeFrame(lldb::StackFrameSP frame) override { + return lldb::RecognizedStackFrameSP( + new ObjCExceptionRecognizedStackFrame(frame)); + }; + std::string GetName() override { + return "ObjC Exception Throw StackFrame Recognizer"; + } +}; + +static void RegisterObjCExceptionRecognizer(Process *process) { + FileSpec module; + ConstString function; + std::tie(module, function) = AppleObjCRuntime::GetExceptionThrowLocation(); + std::vector<ConstString> symbols = {function}; + + process->GetTarget().GetFrameRecognizerManager().AddRecognizer( + StackFrameRecognizerSP(new ObjCExceptionThrowFrameRecognizer()), + module.GetFilename(), symbols, + /*first_instruction_only*/ true); +} diff --git a/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h new file mode 100644 index 000000000000..c9d0b3a907b5 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h @@ -0,0 +1,482 @@ +//===-- AppleObjCRuntimeV2.h ------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLEOBJCRUNTIMEV2_H +#define LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLEOBJCRUNTIMEV2_H + +#include <map> +#include <memory> +#include <mutex> +#include <optional> + +#include "AppleObjCRuntime.h" +#include "lldb/lldb-private.h" + +#include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h" + +#include "llvm/ADT/BitVector.h" + +class RemoteNXMapTable; + +namespace lldb_private { + +class AppleObjCRuntimeV2 : public AppleObjCRuntime { +public: + ~AppleObjCRuntimeV2() override = default; + + static void Initialize(); + + static void Terminate(); + + static lldb_private::LanguageRuntime * + CreateInstance(Process *process, lldb::LanguageType language); + + static llvm::StringRef GetPluginNameStatic() { return "apple-objc-v2"; } + + LanguageRuntime *GetPreferredLanguageRuntime(ValueObject &in_value) override; + + static char ID; + + bool isA(const void *ClassID) const override { + return ClassID == &ID || AppleObjCRuntime::isA(ClassID); + } + + static bool classof(const LanguageRuntime *runtime) { + return runtime->isA(&ID); + } + + bool GetDynamicTypeAndAddress(ValueObject &in_value, + lldb::DynamicValueType use_dynamic, + TypeAndOrName &class_type_or_name, + Address &address, + Value::ValueType &value_type) override; + + llvm::Expected<std::unique_ptr<UtilityFunction>> + CreateObjectChecker(std::string name, ExecutionContext &exe_ctx) override; + + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } + + ObjCRuntimeVersions GetRuntimeVersion() const override { + return ObjCRuntimeVersions::eAppleObjC_V2; + } + + size_t GetByteOffsetForIvar(CompilerType &parent_ast_type, + const char *ivar_name) override; + + void UpdateISAToDescriptorMapIfNeeded() override; + + ClassDescriptorSP GetClassDescriptor(ValueObject &valobj) override; + + ClassDescriptorSP GetClassDescriptorFromISA(ObjCISA isa) override; + + DeclVendor *GetDeclVendor() override; + + lldb::addr_t LookupRuntimeSymbol(ConstString name) override; + + EncodingToTypeSP GetEncodingToType() override; + + bool IsTaggedPointer(lldb::addr_t ptr) override; + + TaggedPointerVendor *GetTaggedPointerVendor() override { + return m_tagged_pointer_vendor_up.get(); + } + + lldb::addr_t GetTaggedPointerObfuscator(); + + /// Returns the base address for relative method list selector strings. + lldb::addr_t GetRelativeSelectorBaseAddr() { + return m_relative_selector_base; + } + + void SetRelativeSelectorBaseAddr(lldb::addr_t relative_selector_base) { + m_relative_selector_base = relative_selector_base; + } + + void GetValuesForGlobalCFBooleans(lldb::addr_t &cf_true, + lldb::addr_t &cf_false) override; + + void ModulesDidLoad(const ModuleList &module_list) override; + + bool IsSharedCacheImageLoaded(uint16_t image_index); + + std::optional<uint64_t> GetSharedCacheImageHeaderVersion(); + +protected: + lldb::BreakpointResolverSP + CreateExceptionResolver(const lldb::BreakpointSP &bkpt, bool catch_bp, + bool throw_bp) override; + +private: + class HashTableSignature { + public: + HashTableSignature(); + + bool NeedsUpdate(Process *process, AppleObjCRuntimeV2 *runtime, + RemoteNXMapTable &hash_table); + + void UpdateSignature(const RemoteNXMapTable &hash_table); + + protected: + uint32_t m_count = 0; + uint32_t m_num_buckets = 0; + lldb::addr_t m_buckets_ptr = 0; + }; + + class NonPointerISACache { + public: + static NonPointerISACache * + CreateInstance(AppleObjCRuntimeV2 &runtime, + const lldb::ModuleSP &objc_module_sp); + + ObjCLanguageRuntime::ClassDescriptorSP GetClassDescriptor(ObjCISA isa); + + private: + NonPointerISACache(AppleObjCRuntimeV2 &runtime, + const lldb::ModuleSP &objc_module_sp, + uint64_t objc_debug_isa_class_mask, + uint64_t objc_debug_isa_magic_mask, + uint64_t objc_debug_isa_magic_value, + uint64_t objc_debug_indexed_isa_magic_mask, + uint64_t objc_debug_indexed_isa_magic_value, + uint64_t objc_debug_indexed_isa_index_mask, + uint64_t objc_debug_indexed_isa_index_shift, + lldb::addr_t objc_indexed_classes); + + bool EvaluateNonPointerISA(ObjCISA isa, ObjCISA &ret_isa); + + AppleObjCRuntimeV2 &m_runtime; + std::map<ObjCISA, ObjCLanguageRuntime::ClassDescriptorSP> m_cache; + lldb::ModuleWP m_objc_module_wp; + uint64_t m_objc_debug_isa_class_mask; + uint64_t m_objc_debug_isa_magic_mask; + uint64_t m_objc_debug_isa_magic_value; + + uint64_t m_objc_debug_indexed_isa_magic_mask; + uint64_t m_objc_debug_indexed_isa_magic_value; + uint64_t m_objc_debug_indexed_isa_index_mask; + uint64_t m_objc_debug_indexed_isa_index_shift; + lldb::addr_t m_objc_indexed_classes; + + std::vector<lldb::addr_t> m_indexed_isa_cache; + + friend class AppleObjCRuntimeV2; + + NonPointerISACache(const NonPointerISACache &) = delete; + const NonPointerISACache &operator=(const NonPointerISACache &) = delete; + }; + + class TaggedPointerVendorV2 + : public ObjCLanguageRuntime::TaggedPointerVendor { + public: + ~TaggedPointerVendorV2() override = default; + + static TaggedPointerVendorV2 * + CreateInstance(AppleObjCRuntimeV2 &runtime, + const lldb::ModuleSP &objc_module_sp); + + protected: + AppleObjCRuntimeV2 &m_runtime; + + TaggedPointerVendorV2(AppleObjCRuntimeV2 &runtime) + : TaggedPointerVendor(), m_runtime(runtime) {} + + private: + TaggedPointerVendorV2(const TaggedPointerVendorV2 &) = delete; + const TaggedPointerVendorV2 & + operator=(const TaggedPointerVendorV2 &) = delete; + }; + + class TaggedPointerVendorRuntimeAssisted : public TaggedPointerVendorV2 { + public: + bool IsPossibleTaggedPointer(lldb::addr_t ptr) override; + + ObjCLanguageRuntime::ClassDescriptorSP + GetClassDescriptor(lldb::addr_t ptr) override; + + protected: + TaggedPointerVendorRuntimeAssisted( + AppleObjCRuntimeV2 &runtime, uint64_t objc_debug_taggedpointer_mask, + uint32_t objc_debug_taggedpointer_slot_shift, + uint32_t objc_debug_taggedpointer_slot_mask, + uint32_t objc_debug_taggedpointer_payload_lshift, + uint32_t objc_debug_taggedpointer_payload_rshift, + lldb::addr_t objc_debug_taggedpointer_classes); + + typedef std::map<uint8_t, ObjCLanguageRuntime::ClassDescriptorSP> Cache; + typedef Cache::iterator CacheIterator; + Cache m_cache; + uint64_t m_objc_debug_taggedpointer_mask; + uint32_t m_objc_debug_taggedpointer_slot_shift; + uint32_t m_objc_debug_taggedpointer_slot_mask; + uint32_t m_objc_debug_taggedpointer_payload_lshift; + uint32_t m_objc_debug_taggedpointer_payload_rshift; + lldb::addr_t m_objc_debug_taggedpointer_classes; + + friend class AppleObjCRuntimeV2::TaggedPointerVendorV2; + + TaggedPointerVendorRuntimeAssisted( + const TaggedPointerVendorRuntimeAssisted &) = delete; + const TaggedPointerVendorRuntimeAssisted & + operator=(const TaggedPointerVendorRuntimeAssisted &) = delete; + }; + + class TaggedPointerVendorExtended + : public TaggedPointerVendorRuntimeAssisted { + public: + ObjCLanguageRuntime::ClassDescriptorSP + GetClassDescriptor(lldb::addr_t ptr) override; + + protected: + TaggedPointerVendorExtended( + AppleObjCRuntimeV2 &runtime, uint64_t objc_debug_taggedpointer_mask, + uint64_t objc_debug_taggedpointer_ext_mask, + uint32_t objc_debug_taggedpointer_slot_shift, + uint32_t objc_debug_taggedpointer_ext_slot_shift, + uint32_t objc_debug_taggedpointer_slot_mask, + uint32_t objc_debug_taggedpointer_ext_slot_mask, + uint32_t objc_debug_taggedpointer_payload_lshift, + uint32_t objc_debug_taggedpointer_payload_rshift, + uint32_t objc_debug_taggedpointer_ext_payload_lshift, + uint32_t objc_debug_taggedpointer_ext_payload_rshift, + lldb::addr_t objc_debug_taggedpointer_classes, + lldb::addr_t objc_debug_taggedpointer_ext_classes); + + bool IsPossibleExtendedTaggedPointer(lldb::addr_t ptr); + + typedef std::map<uint8_t, ObjCLanguageRuntime::ClassDescriptorSP> Cache; + typedef Cache::iterator CacheIterator; + Cache m_ext_cache; + uint64_t m_objc_debug_taggedpointer_ext_mask; + uint32_t m_objc_debug_taggedpointer_ext_slot_shift; + uint32_t m_objc_debug_taggedpointer_ext_slot_mask; + uint32_t m_objc_debug_taggedpointer_ext_payload_lshift; + uint32_t m_objc_debug_taggedpointer_ext_payload_rshift; + lldb::addr_t m_objc_debug_taggedpointer_ext_classes; + + friend class AppleObjCRuntimeV2::TaggedPointerVendorV2; + + TaggedPointerVendorExtended(const TaggedPointerVendorExtended &) = delete; + const TaggedPointerVendorExtended & + operator=(const TaggedPointerVendorExtended &) = delete; + }; + + class TaggedPointerVendorLegacy : public TaggedPointerVendorV2 { + public: + bool IsPossibleTaggedPointer(lldb::addr_t ptr) override; + + ObjCLanguageRuntime::ClassDescriptorSP + GetClassDescriptor(lldb::addr_t ptr) override; + + protected: + TaggedPointerVendorLegacy(AppleObjCRuntimeV2 &runtime) + : TaggedPointerVendorV2(runtime) {} + + friend class AppleObjCRuntimeV2::TaggedPointerVendorV2; + + TaggedPointerVendorLegacy(const TaggedPointerVendorLegacy &) = delete; + const TaggedPointerVendorLegacy & + operator=(const TaggedPointerVendorLegacy &) = delete; + }; + + struct DescriptorMapUpdateResult { + bool m_update_ran; + bool m_retry_update; + uint32_t m_num_found; + + DescriptorMapUpdateResult(bool ran, bool retry, uint32_t found) { + m_update_ran = ran; + + m_retry_update = retry; + + m_num_found = found; + } + + static DescriptorMapUpdateResult Fail() { return {false, false, 0}; } + + static DescriptorMapUpdateResult Success(uint32_t found) { + return {true, false, found}; + } + + static DescriptorMapUpdateResult Retry() { return {false, true, 0}; } + }; + + /// Abstraction to read the Objective-C class info. + class ClassInfoExtractor { + public: + ClassInfoExtractor(AppleObjCRuntimeV2 &runtime) : m_runtime(runtime) {} + std::mutex &GetMutex() { return m_mutex; } + + protected: + /// The lifetime of this object is tied to that of the runtime. + AppleObjCRuntimeV2 &m_runtime; + std::mutex m_mutex; + }; + + /// We can read the class info from the Objective-C runtime using + /// gdb_objc_realized_classes, objc_copyRealizedClassList or + /// objc_getRealizedClassList_trylock. The RealizedClassList variants are + /// preferred because they include lazily named classes, but they are not + /// always available or safe to call. + /// + /// We potentially need more than one helper for the same process, because we + /// may need to use gdb_objc_realized_classes until dyld is initialized and + /// then switch over to objc_copyRealizedClassList or + /// objc_getRealizedClassList_trylock for lazily named classes. + class DynamicClassInfoExtractor : public ClassInfoExtractor { + public: + DynamicClassInfoExtractor(AppleObjCRuntimeV2 &runtime) + : ClassInfoExtractor(runtime) {} + + DescriptorMapUpdateResult + UpdateISAToDescriptorMap(RemoteNXMapTable &hash_table); + + private: + enum Helper { + gdb_objc_realized_classes, + objc_copyRealizedClassList, + objc_getRealizedClassList_trylock + }; + + /// Compute which helper to use. If dyld is not yet fully initialized we + /// must use gdb_objc_realized_classes. Otherwise, we prefer + /// objc_getRealizedClassList_trylock and objc_copyRealizedClassList + /// respectively, depending on availability. + Helper ComputeHelper(ExecutionContext &exe_ctx) const; + + UtilityFunction *GetClassInfoUtilityFunction(ExecutionContext &exe_ctx, + Helper helper); + lldb::addr_t &GetClassInfoArgs(Helper helper); + + std::unique_ptr<UtilityFunction> + GetClassInfoUtilityFunctionImpl(ExecutionContext &exe_ctx, Helper helper, + std::string code, std::string name); + + struct UtilityFunctionHelper { + std::unique_ptr<UtilityFunction> utility_function; + lldb::addr_t args = LLDB_INVALID_ADDRESS; + }; + + UtilityFunctionHelper m_gdb_objc_realized_classes_helper; + UtilityFunctionHelper m_objc_copyRealizedClassList_helper; + UtilityFunctionHelper m_objc_getRealizedClassList_trylock_helper; + }; + + /// Abstraction to read the Objective-C class info from the shared cache. + class SharedCacheClassInfoExtractor : public ClassInfoExtractor { + public: + SharedCacheClassInfoExtractor(AppleObjCRuntimeV2 &runtime) + : ClassInfoExtractor(runtime) {} + + DescriptorMapUpdateResult UpdateISAToDescriptorMap(); + + private: + UtilityFunction *GetClassInfoUtilityFunction(ExecutionContext &exe_ctx); + + std::unique_ptr<UtilityFunction> + GetClassInfoUtilityFunctionImpl(ExecutionContext &exe_ctx); + + std::unique_ptr<UtilityFunction> m_utility_function; + lldb::addr_t m_args = LLDB_INVALID_ADDRESS; + }; + + class SharedCacheImageHeaders { + public: + static std::unique_ptr<SharedCacheImageHeaders> + CreateSharedCacheImageHeaders(AppleObjCRuntimeV2 &runtime); + + void SetNeedsUpdate() { m_needs_update = true; } + + bool IsImageLoaded(uint16_t image_index); + + uint64_t GetVersion(); + + private: + SharedCacheImageHeaders(AppleObjCRuntimeV2 &runtime, + lldb::addr_t headerInfoRWs_ptr, uint32_t count, + uint32_t entsize) + : m_runtime(runtime), m_headerInfoRWs_ptr(headerInfoRWs_ptr), + m_loaded_images(count, false), m_version(0), m_count(count), + m_entsize(entsize), m_needs_update(true) {} + llvm::Error UpdateIfNeeded(); + + AppleObjCRuntimeV2 &m_runtime; + lldb::addr_t m_headerInfoRWs_ptr; + llvm::BitVector m_loaded_images; + uint64_t m_version; + uint32_t m_count; + uint32_t m_entsize; + bool m_needs_update; + }; + + AppleObjCRuntimeV2(Process *process, const lldb::ModuleSP &objc_module_sp); + + ObjCISA GetPointerISA(ObjCISA isa); + + lldb::addr_t GetISAHashTablePointer(); + + /// Update the generation count of realized classes. This is not an exact + /// count but rather a value that is incremented when new classes are realized + /// or destroyed. Unlike the count in gdb_objc_realized_classes, it will + /// change when lazily named classes get realized. + bool RealizedClassGenerationCountChanged(); + + uint32_t ParseClassInfoArray(const lldb_private::DataExtractor &data, + uint32_t num_class_infos); + + enum class SharedCacheWarningReason { + eExpressionUnableToRun, + eExpressionExecutionFailure, + eNotEnoughClassesRead + }; + + void WarnIfNoClassesCached(SharedCacheWarningReason reason); + void WarnIfNoExpandedSharedCache(); + + lldb::addr_t GetSharedCacheReadOnlyAddress(); + lldb::addr_t GetSharedCacheBaseAddress(); + + bool GetCFBooleanValuesIfNeeded(); + + bool HasSymbol(ConstString Name); + + NonPointerISACache *GetNonPointerIsaCache() { + if (!m_non_pointer_isa_cache_up) + m_non_pointer_isa_cache_up.reset( + NonPointerISACache::CreateInstance(*this, m_objc_module_sp)); + return m_non_pointer_isa_cache_up.get(); + } + + friend class ClassDescriptorV2; + + lldb::ModuleSP m_objc_module_sp; + + DynamicClassInfoExtractor m_dynamic_class_info_extractor; + SharedCacheClassInfoExtractor m_shared_cache_class_info_extractor; + + std::unique_ptr<DeclVendor> m_decl_vendor_up; + lldb::addr_t m_tagged_pointer_obfuscator; + lldb::addr_t m_isa_hash_table_ptr; + lldb::addr_t m_relative_selector_base; + HashTableSignature m_hash_signature; + bool m_has_object_getClass; + bool m_has_objc_copyRealizedClassList; + bool m_has_objc_getRealizedClassList_trylock; + bool m_loaded_objc_opt; + std::unique_ptr<NonPointerISACache> m_non_pointer_isa_cache_up; + std::unique_ptr<TaggedPointerVendor> m_tagged_pointer_vendor_up; + EncodingToTypeSP m_encoding_to_type_sp; + std::once_flag m_no_classes_cached_warning; + std::once_flag m_no_expanded_cache_warning; + std::optional<std::pair<lldb::addr_t, lldb::addr_t>> m_CFBoolean_values; + uint64_t m_realized_class_generation_count; + std::unique_ptr<SharedCacheImageHeaders> m_shared_cache_image_headers_up; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLEOBJCRUNTIMEV2_H diff --git a/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp new file mode 100644 index 000000000000..2b8adeae10d1 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp @@ -0,0 +1,1179 @@ +//===-- AppleObjCTrampolineHandler.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 "AppleObjCTrampolineHandler.h" +#include "AppleThreadPlanStepThroughObjCTrampoline.h" + +#include "Plugins/TypeSystem/Clang/TypeSystemClang.h" +#include "lldb/Breakpoint/StoppointCallbackContext.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/Value.h" +#include "lldb/Expression/DiagnosticManager.h" +#include "lldb/Expression/FunctionCaller.h" +#include "lldb/Expression/UserExpression.h" +#include "lldb/Expression/UtilityFunction.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Target/ABI.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadPlanRunToAddress.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/ScopeExit.h" + +#include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h" + +#include <memory> + +using namespace lldb; +using namespace lldb_private; + +const char *AppleObjCTrampolineHandler::g_lookup_implementation_function_name = + "__lldb_objc_find_implementation_for_selector"; +const char *AppleObjCTrampolineHandler:: + g_lookup_implementation_with_stret_function_code = + R"( + if (is_stret) { + return_struct.impl_addr = + class_getMethodImplementation_stret (return_struct.class_addr, + return_struct.sel_addr); + } else { + return_struct.impl_addr = + class_getMethodImplementation (return_struct.class_addr, + return_struct.sel_addr); + } + if (debug) + printf ("\n*** Returning implementation: %p.\n", + return_struct.impl_addr); + + return return_struct.impl_addr; +} +)"; +const char * + AppleObjCTrampolineHandler::g_lookup_implementation_no_stret_function_code = + R"( + return_struct.impl_addr = + class_getMethodImplementation (return_struct.class_addr, + return_struct.sel_addr); + if (debug) + printf ("\n*** getMethodImpletation for addr: 0x%p sel: 0x%p result: 0x%p.\n", + return_struct.class_addr, return_struct.sel_addr, return_struct.impl_addr); + + return return_struct.impl_addr; +} +)"; + +const char + *AppleObjCTrampolineHandler::g_lookup_implementation_function_common_code = + R"( +extern "C" +{ + extern void *class_getMethodImplementation(void *objc_class, void *sel); + extern void *class_getMethodImplementation_stret(void *objc_class, void *sel); + extern void * object_getClass (id object); + extern void * sel_getUid(char *name); + extern int printf(const char *format, ...); +} +extern "C" void * +__lldb_objc_find_implementation_for_selector (void *object, + void *sel, + int is_str_ptr, + int is_stret, + int is_super, + int is_super2, + int is_fixup, + int is_fixed, + int debug) +{ + struct __lldb_imp_return_struct { + void *class_addr; + void *sel_addr; + void *impl_addr; + }; + + struct __lldb_objc_class { + void *isa; + void *super_ptr; + }; + struct __lldb_objc_super { + void *receiver; + struct __lldb_objc_class *class_ptr; + }; + struct __lldb_msg_ref { + void *dont_know; + void *sel; + }; + + struct __lldb_imp_return_struct return_struct; + + if (debug) + printf ("\n*** Called with obj: %p sel: %p is_str_ptr: %d " + "is_stret: %d is_super: %d, " + "is_super2: %d, is_fixup: %d, is_fixed: %d\n", + object, sel, is_str_ptr, is_stret, + is_super, is_super2, is_fixup, is_fixed); + + if (is_str_ptr) { + if (debug) + printf("*** Turning string: '%s'", sel); + sel = sel_getUid((char *)sel); + if (debug) + printf("*** into sel to %p", sel); + } + if (is_super) { + if (is_super2) { + return_struct.class_addr + = ((__lldb_objc_super *) object)->class_ptr->super_ptr; + } else { + return_struct.class_addr = ((__lldb_objc_super *) object)->class_ptr; + } + if (debug) + printf("*** Super, class addr: %p\n", return_struct.class_addr); + } else { + // This code seems a little funny, but has its reasons... + // The call to [object class] is here because if this is a class, and has + // not been called into yet, we need to do something to force the class to + // initialize itself. + // Then the call to object_getClass will actually return the correct class, + // either the class if object is a class instance, or the meta-class if it + // is a class pointer. + void *class_ptr = (void *) [(id) object class]; + return_struct.class_addr = (id) object_getClass((id) object); + if (debug) { + if (class_ptr == object) { + printf ("Found a class object, need to return the meta class %p -> %p\n", + class_ptr, return_struct.class_addr); + } else { + printf ("[object class] returned: %p object_getClass: %p.\n", + class_ptr, return_struct.class_addr); + } + } + } + + if (is_fixup) { + if (is_fixed) { + return_struct.sel_addr = ((__lldb_msg_ref *) sel)->sel; + } else { + char *sel_name = (char *) ((__lldb_msg_ref *) sel)->sel; + return_struct.sel_addr = sel_getUid (sel_name); + if (debug) + printf ("\n*** Got fixed up selector: %p for name %s.\n", + return_struct.sel_addr, sel_name); + } + } else { + return_struct.sel_addr = sel; + } +)"; + +AppleObjCTrampolineHandler::AppleObjCVTables::VTableRegion::VTableRegion( + AppleObjCVTables *owner, lldb::addr_t header_addr) + : m_valid(true), m_owner(owner), m_header_addr(header_addr) { + SetUpRegion(); +} + +AppleObjCTrampolineHandler::~AppleObjCTrampolineHandler() = default; + +void AppleObjCTrampolineHandler::AppleObjCVTables::VTableRegion::SetUpRegion() { + // The header looks like: + // + // uint16_t headerSize + // uint16_t descSize + // uint32_t descCount + // void * next + // + // First read in the header: + + char memory_buffer[16]; + ProcessSP process_sp = m_owner->GetProcessSP(); + if (!process_sp) + return; + DataExtractor data(memory_buffer, sizeof(memory_buffer), + process_sp->GetByteOrder(), + process_sp->GetAddressByteSize()); + size_t actual_size = 8 + process_sp->GetAddressByteSize(); + Status error; + size_t bytes_read = + process_sp->ReadMemory(m_header_addr, memory_buffer, actual_size, error); + if (bytes_read != actual_size) { + m_valid = false; + return; + } + + lldb::offset_t offset = 0; + const uint16_t header_size = data.GetU16(&offset); + const uint16_t descriptor_size = data.GetU16(&offset); + const size_t num_descriptors = data.GetU32(&offset); + + m_next_region = data.GetAddress(&offset); + + // If the header size is 0, that means we've come in too early before this + // data is set up. + // Set ourselves as not valid, and continue. + if (header_size == 0 || num_descriptors == 0) { + m_valid = false; + return; + } + + // Now read in all the descriptors: + // The descriptor looks like: + // + // uint32_t offset + // uint32_t flags + // + // Where offset is either 0 - in which case it is unused, or it is + // the offset of the vtable code from the beginning of the + // descriptor record. Below, we'll convert that into an absolute + // code address, since I don't want to have to compute it over and + // over. + + // Ingest the whole descriptor array: + const lldb::addr_t desc_ptr = m_header_addr + header_size; + const size_t desc_array_size = num_descriptors * descriptor_size; + WritableDataBufferSP data_sp(new DataBufferHeap(desc_array_size, '\0')); + uint8_t *dst = (uint8_t *)data_sp->GetBytes(); + + DataExtractor desc_extractor(dst, desc_array_size, process_sp->GetByteOrder(), + process_sp->GetAddressByteSize()); + bytes_read = process_sp->ReadMemory(desc_ptr, dst, desc_array_size, error); + if (bytes_read != desc_array_size) { + m_valid = false; + return; + } + + // The actual code for the vtables will be laid out consecutively, so I also + // compute the start and end of the whole code block. + + offset = 0; + m_code_start_addr = 0; + m_code_end_addr = 0; + + for (size_t i = 0; i < num_descriptors; i++) { + lldb::addr_t start_offset = offset; + uint32_t voffset = desc_extractor.GetU32(&offset); + uint32_t flags = desc_extractor.GetU32(&offset); + lldb::addr_t code_addr = desc_ptr + start_offset + voffset; + m_descriptors.push_back(VTableDescriptor(flags, code_addr)); + + if (m_code_start_addr == 0 || code_addr < m_code_start_addr) + m_code_start_addr = code_addr; + if (code_addr > m_code_end_addr) + m_code_end_addr = code_addr; + + offset = start_offset + descriptor_size; + } + // Finally, a little bird told me that all the vtable code blocks + // are the same size. Let's compute the blocks and if they are all + // the same add the size to the code end address: + lldb::addr_t code_size = 0; + bool all_the_same = true; + for (size_t i = 0; i < num_descriptors - 1; i++) { + lldb::addr_t this_size = + m_descriptors[i + 1].code_start - m_descriptors[i].code_start; + if (code_size == 0) + code_size = this_size; + else { + if (this_size != code_size) + all_the_same = false; + if (this_size > code_size) + code_size = this_size; + } + } + if (all_the_same) + m_code_end_addr += code_size; +} + +bool AppleObjCTrampolineHandler::AppleObjCVTables::VTableRegion:: + AddressInRegion(lldb::addr_t addr, uint32_t &flags) { + if (!IsValid()) + return false; + + if (addr < m_code_start_addr || addr > m_code_end_addr) + return false; + + std::vector<VTableDescriptor>::iterator pos, end = m_descriptors.end(); + for (pos = m_descriptors.begin(); pos != end; pos++) { + if (addr <= (*pos).code_start) { + flags = (*pos).flags; + return true; + } + } + return false; +} + +void AppleObjCTrampolineHandler::AppleObjCVTables::VTableRegion::Dump( + Stream &s) { + s.Printf("Header addr: 0x%" PRIx64 " Code start: 0x%" PRIx64 + " Code End: 0x%" PRIx64 " Next: 0x%" PRIx64 "\n", + m_header_addr, m_code_start_addr, m_code_end_addr, m_next_region); + size_t num_elements = m_descriptors.size(); + for (size_t i = 0; i < num_elements; i++) { + s.Indent(); + s.Printf("Code start: 0x%" PRIx64 " Flags: %d\n", + m_descriptors[i].code_start, m_descriptors[i].flags); + } +} + +AppleObjCTrampolineHandler::AppleObjCVTables::AppleObjCVTables( + const ProcessSP &process_sp, const ModuleSP &objc_module_sp) + : m_process_wp(), m_trampoline_header(LLDB_INVALID_ADDRESS), + m_trampolines_changed_bp_id(LLDB_INVALID_BREAK_ID), + m_objc_module_sp(objc_module_sp) { + if (process_sp) + m_process_wp = process_sp; +} + +AppleObjCTrampolineHandler::AppleObjCVTables::~AppleObjCVTables() { + ProcessSP process_sp = GetProcessSP(); + if (process_sp) { + if (m_trampolines_changed_bp_id != LLDB_INVALID_BREAK_ID) + process_sp->GetTarget().RemoveBreakpointByID(m_trampolines_changed_bp_id); + } +} + +bool AppleObjCTrampolineHandler::AppleObjCVTables::InitializeVTableSymbols() { + if (m_trampoline_header != LLDB_INVALID_ADDRESS) + return true; + + ProcessSP process_sp = GetProcessSP(); + if (process_sp) { + Target &target = process_sp->GetTarget(); + + if (!m_objc_module_sp) { + for (ModuleSP module_sp : target.GetImages().Modules()) { + if (ObjCLanguageRuntime::Get(*process_sp) + ->IsModuleObjCLibrary(module_sp)) { + m_objc_module_sp = module_sp; + break; + } + } + } + + if (m_objc_module_sp) { + ConstString trampoline_name("gdb_objc_trampolines"); + const Symbol *trampoline_symbol = + m_objc_module_sp->FindFirstSymbolWithNameAndType(trampoline_name, + eSymbolTypeData); + if (trampoline_symbol != nullptr) { + m_trampoline_header = trampoline_symbol->GetLoadAddress(&target); + if (m_trampoline_header == LLDB_INVALID_ADDRESS) + return false; + + // Next look up the "changed" symbol and set a breakpoint on that... + ConstString changed_name("gdb_objc_trampolines_changed"); + const Symbol *changed_symbol = + m_objc_module_sp->FindFirstSymbolWithNameAndType(changed_name, + eSymbolTypeCode); + if (changed_symbol != nullptr) { + const Address changed_symbol_addr = changed_symbol->GetAddress(); + if (!changed_symbol_addr.IsValid()) + return false; + + lldb::addr_t changed_addr = + changed_symbol_addr.GetOpcodeLoadAddress(&target); + if (changed_addr != LLDB_INVALID_ADDRESS) { + BreakpointSP trampolines_changed_bp_sp = + target.CreateBreakpoint(changed_addr, true, false); + if (trampolines_changed_bp_sp) { + m_trampolines_changed_bp_id = trampolines_changed_bp_sp->GetID(); + trampolines_changed_bp_sp->SetCallback(RefreshTrampolines, this, + true); + trampolines_changed_bp_sp->SetBreakpointKind( + "objc-trampolines-changed"); + return true; + } + } + } + } + } + } + return false; +} + +bool AppleObjCTrampolineHandler::AppleObjCVTables::RefreshTrampolines( + void *baton, StoppointCallbackContext *context, lldb::user_id_t break_id, + lldb::user_id_t break_loc_id) { + AppleObjCVTables *vtable_handler = (AppleObjCVTables *)baton; + if (vtable_handler->InitializeVTableSymbols()) { + // The Update function is called with the address of an added region. So we + // grab that address, and + // feed it into ReadRegions. Of course, our friend the ABI will get the + // values for us. + ExecutionContext exe_ctx(context->exe_ctx_ref); + Process *process = exe_ctx.GetProcessPtr(); + const ABI *abi = process->GetABI().get(); + + TypeSystemClangSP scratch_ts_sp = + ScratchTypeSystemClang::GetForTarget(process->GetTarget()); + if (!scratch_ts_sp) + return false; + + ValueList argument_values; + Value input_value; + CompilerType clang_void_ptr_type = + scratch_ts_sp->GetBasicType(eBasicTypeVoid).GetPointerType(); + + input_value.SetValueType(Value::ValueType::Scalar); + // input_value.SetContext (Value::eContextTypeClangType, + // clang_void_ptr_type); + input_value.SetCompilerType(clang_void_ptr_type); + argument_values.PushValue(input_value); + + bool success = + abi->GetArgumentValues(exe_ctx.GetThreadRef(), argument_values); + if (!success) + return false; + + // Now get a pointer value from the zeroth argument. + Status error; + DataExtractor data; + error = argument_values.GetValueAtIndex(0)->GetValueAsData(&exe_ctx, data, + nullptr); + lldb::offset_t offset = 0; + lldb::addr_t region_addr = data.GetAddress(&offset); + + if (region_addr != 0) + vtable_handler->ReadRegions(region_addr); + } + return false; +} + +bool AppleObjCTrampolineHandler::AppleObjCVTables::ReadRegions() { + // The no argument version reads the start region from the value of + // the gdb_regions_header, and gets started from there. + + m_regions.clear(); + if (!InitializeVTableSymbols()) + return false; + Status error; + ProcessSP process_sp = GetProcessSP(); + if (process_sp) { + lldb::addr_t region_addr = + process_sp->ReadPointerFromMemory(m_trampoline_header, error); + if (error.Success()) + return ReadRegions(region_addr); + } + return false; +} + +bool AppleObjCTrampolineHandler::AppleObjCVTables::ReadRegions( + lldb::addr_t region_addr) { + ProcessSP process_sp = GetProcessSP(); + if (!process_sp) + return false; + + Log *log = GetLog(LLDBLog::Step); + + // We aren't starting at the trampoline symbol. + InitializeVTableSymbols(); + lldb::addr_t next_region = region_addr; + + // Read in the sizes of the headers. + while (next_region != 0) { + m_regions.push_back(VTableRegion(this, next_region)); + if (!m_regions.back().IsValid()) { + m_regions.clear(); + return false; + } + if (log) { + StreamString s; + m_regions.back().Dump(s); + LLDB_LOGF(log, "Read vtable region: \n%s", s.GetData()); + } + + next_region = m_regions.back().GetNextRegionAddr(); + } + + return true; +} + +bool AppleObjCTrampolineHandler::AppleObjCVTables::IsAddressInVTables( + lldb::addr_t addr, uint32_t &flags) { + region_collection::iterator pos, end = m_regions.end(); + for (pos = m_regions.begin(); pos != end; pos++) { + if ((*pos).AddressInRegion(addr, flags)) + return true; + } + return false; +} + +const AppleObjCTrampolineHandler::DispatchFunction + AppleObjCTrampolineHandler::g_dispatch_functions[] = { + // NAME STRET SUPER SUPER2 FIXUP TYPE + {"objc_msgSend", false, false, false, DispatchFunction::eFixUpNone}, + {"objc_msgSend_fixup", false, false, false, + DispatchFunction::eFixUpToFix}, + {"objc_msgSend_fixedup", false, false, false, + DispatchFunction::eFixUpFixed}, + {"objc_msgSend_stret", true, false, false, + DispatchFunction::eFixUpNone}, + {"objc_msgSend_stret_fixup", true, false, false, + DispatchFunction::eFixUpToFix}, + {"objc_msgSend_stret_fixedup", true, false, false, + DispatchFunction::eFixUpFixed}, + {"objc_msgSend_fpret", false, false, false, + DispatchFunction::eFixUpNone}, + {"objc_msgSend_fpret_fixup", false, false, false, + DispatchFunction::eFixUpToFix}, + {"objc_msgSend_fpret_fixedup", false, false, false, + DispatchFunction::eFixUpFixed}, + {"objc_msgSend_fp2ret", false, false, true, + DispatchFunction::eFixUpNone}, + {"objc_msgSend_fp2ret_fixup", false, false, true, + DispatchFunction::eFixUpToFix}, + {"objc_msgSend_fp2ret_fixedup", false, false, true, + DispatchFunction::eFixUpFixed}, + {"objc_msgSendSuper", false, true, false, DispatchFunction::eFixUpNone}, + {"objc_msgSendSuper_stret", true, true, false, + DispatchFunction::eFixUpNone}, + {"objc_msgSendSuper2", false, true, true, DispatchFunction::eFixUpNone}, + {"objc_msgSendSuper2_fixup", false, true, true, + DispatchFunction::eFixUpToFix}, + {"objc_msgSendSuper2_fixedup", false, true, true, + DispatchFunction::eFixUpFixed}, + {"objc_msgSendSuper2_stret", true, true, true, + DispatchFunction::eFixUpNone}, + {"objc_msgSendSuper2_stret_fixup", true, true, true, + DispatchFunction::eFixUpToFix}, + {"objc_msgSendSuper2_stret_fixedup", true, true, true, + DispatchFunction::eFixUpFixed}, +}; + +// This is the table of ObjC "accelerated dispatch" functions. They are a set +// of objc methods that are "seldom overridden" and so the compiler replaces the +// objc_msgSend with a call to one of the dispatch functions. That will check +// whether the method has been overridden, and directly call the Foundation +// implementation if not. +// This table is supposed to be complete. If ones get added in the future, we +// will have to add them to the table. +const char *AppleObjCTrampolineHandler::g_opt_dispatch_names[] = { + "objc_alloc", + "objc_autorelease", + "objc_release", + "objc_retain", + "objc_alloc_init", + "objc_allocWithZone", + "objc_opt_class", + "objc_opt_isKindOfClass", + "objc_opt_new", + "objc_opt_respondsToSelector", + "objc_opt_self", +}; + +AppleObjCTrampolineHandler::AppleObjCTrampolineHandler( + const ProcessSP &process_sp, const ModuleSP &objc_module_sp) + : m_process_wp(), m_objc_module_sp(objc_module_sp), + m_impl_fn_addr(LLDB_INVALID_ADDRESS), + m_impl_stret_fn_addr(LLDB_INVALID_ADDRESS), + m_msg_forward_addr(LLDB_INVALID_ADDRESS), + m_msg_forward_stret_addr(LLDB_INVALID_ADDRESS) { + if (process_sp) + m_process_wp = process_sp; + // Look up the known resolution functions: + + ConstString get_impl_name("class_getMethodImplementation"); + ConstString get_impl_stret_name("class_getMethodImplementation_stret"); + ConstString msg_forward_name("_objc_msgForward"); + ConstString msg_forward_stret_name("_objc_msgForward_stret"); + + Target *target = process_sp ? &process_sp->GetTarget() : nullptr; + const Symbol *class_getMethodImplementation = + m_objc_module_sp->FindFirstSymbolWithNameAndType(get_impl_name, + eSymbolTypeCode); + const Symbol *class_getMethodImplementation_stret = + m_objc_module_sp->FindFirstSymbolWithNameAndType(get_impl_stret_name, + eSymbolTypeCode); + const Symbol *msg_forward = m_objc_module_sp->FindFirstSymbolWithNameAndType( + msg_forward_name, eSymbolTypeCode); + const Symbol *msg_forward_stret = + m_objc_module_sp->FindFirstSymbolWithNameAndType(msg_forward_stret_name, + eSymbolTypeCode); + + if (class_getMethodImplementation) + m_impl_fn_addr = + class_getMethodImplementation->GetAddress().GetOpcodeLoadAddress( + target); + if (class_getMethodImplementation_stret) + m_impl_stret_fn_addr = + class_getMethodImplementation_stret->GetAddress().GetOpcodeLoadAddress( + target); + if (msg_forward) + m_msg_forward_addr = msg_forward->GetAddress().GetOpcodeLoadAddress(target); + if (msg_forward_stret) + m_msg_forward_stret_addr = + msg_forward_stret->GetAddress().GetOpcodeLoadAddress(target); + + // FIXME: Do some kind of logging here. + if (m_impl_fn_addr == LLDB_INVALID_ADDRESS) { + // If we can't even find the ordinary get method implementation function, + // then we aren't going to be able to + // step through any method dispatches. Warn to that effect and get out of + // here. + if (process_sp->CanJIT()) { + process_sp->GetTarget().GetDebugger().GetErrorStream().Printf( + "Could not find implementation lookup function \"%s\"" + " step in through ObjC method dispatch will not work.\n", + get_impl_name.AsCString()); + } + return; + } + + // We will either set the implementation to the _stret or non_stret version, + // so either way it's safe to start filling the m_lookup_..._code here. + m_lookup_implementation_function_code.assign( + g_lookup_implementation_function_common_code); + + if (m_impl_stret_fn_addr == LLDB_INVALID_ADDRESS) { + // It there is no stret return lookup function, assume that it is the same + // as the straight lookup: + m_impl_stret_fn_addr = m_impl_fn_addr; + // Also we will use the version of the lookup code that doesn't rely on the + // stret version of the function. + m_lookup_implementation_function_code.append( + g_lookup_implementation_no_stret_function_code); + } else { + m_lookup_implementation_function_code.append( + g_lookup_implementation_with_stret_function_code); + } + + // Look up the addresses for the objc dispatch functions and cache + // them. For now I'm inspecting the symbol names dynamically to + // figure out how to dispatch to them. If it becomes more + // complicated than this we can turn the g_dispatch_functions char * + // array into a template table, and populate the DispatchFunction + // map from there. + + for (size_t i = 0; i != std::size(g_dispatch_functions); i++) { + ConstString name_const_str(g_dispatch_functions[i].name); + const Symbol *msgSend_symbol = + m_objc_module_sp->FindFirstSymbolWithNameAndType(name_const_str, + eSymbolTypeCode); + if (msgSend_symbol && msgSend_symbol->ValueIsAddress()) { + // FIXME: Make g_dispatch_functions static table of + // DispatchFunctions, and have the map be address->index. + // Problem is we also need to lookup the dispatch function. For + // now we could have a side table of stret & non-stret dispatch + // functions. If that's as complex as it gets, we're fine. + + lldb::addr_t sym_addr = + msgSend_symbol->GetAddressRef().GetOpcodeLoadAddress(target); + + m_msgSend_map.insert(std::pair<lldb::addr_t, int>(sym_addr, i)); + } + } + + // Similarly, cache the addresses of the "optimized dispatch" function. + for (size_t i = 0; i != std::size(g_opt_dispatch_names); i++) { + ConstString name_const_str(g_opt_dispatch_names[i]); + const Symbol *msgSend_symbol = + m_objc_module_sp->FindFirstSymbolWithNameAndType(name_const_str, + eSymbolTypeCode); + if (msgSend_symbol && msgSend_symbol->ValueIsAddress()) { + lldb::addr_t sym_addr = + msgSend_symbol->GetAddressRef().GetOpcodeLoadAddress(target); + + m_opt_dispatch_map.emplace(sym_addr, i); + } + } + + // Build our vtable dispatch handler here: + m_vtables_up = + std::make_unique<AppleObjCVTables>(process_sp, m_objc_module_sp); + if (m_vtables_up) + m_vtables_up->ReadRegions(); +} + +lldb::addr_t +AppleObjCTrampolineHandler::SetupDispatchFunction(Thread &thread, + ValueList &dispatch_values) { + ThreadSP thread_sp(thread.shared_from_this()); + ExecutionContext exe_ctx(thread_sp); + Log *log = GetLog(LLDBLog::Step); + + lldb::addr_t args_addr = LLDB_INVALID_ADDRESS; + FunctionCaller *impl_function_caller = nullptr; + + // Scope for mutex locker: + { + std::lock_guard<std::mutex> guard(m_impl_function_mutex); + + // First stage is to make the ClangUtility to hold our injected function: + + if (!m_impl_code) { + if (!m_lookup_implementation_function_code.empty()) { + auto utility_fn_or_error = exe_ctx.GetTargetRef().CreateUtilityFunction( + m_lookup_implementation_function_code, + g_lookup_implementation_function_name, eLanguageTypeC, exe_ctx); + if (!utility_fn_or_error) { + LLDB_LOG_ERROR( + log, utility_fn_or_error.takeError(), + "Failed to get Utility Function for implementation lookup: {0}."); + return args_addr; + } + m_impl_code = std::move(*utility_fn_or_error); + } else { + LLDB_LOGF(log, "No method lookup implementation code."); + return LLDB_INVALID_ADDRESS; + } + + // Next make the runner function for our implementation utility function. + TypeSystemClangSP scratch_ts_sp = ScratchTypeSystemClang::GetForTarget( + thread.GetProcess()->GetTarget()); + if (!scratch_ts_sp) + return LLDB_INVALID_ADDRESS; + + CompilerType clang_void_ptr_type = + scratch_ts_sp->GetBasicType(eBasicTypeVoid).GetPointerType(); + Status error; + + impl_function_caller = m_impl_code->MakeFunctionCaller( + clang_void_ptr_type, dispatch_values, thread_sp, error); + if (error.Fail()) { + LLDB_LOGF(log, + "Error getting function caller for dispatch lookup: \"%s\".", + error.AsCString()); + return args_addr; + } + } else { + impl_function_caller = m_impl_code->GetFunctionCaller(); + } + } + + // Now write down the argument values for this particular call. + // This looks like it might be a race condition if other threads + // were calling into here, but actually it isn't because we allocate + // a new args structure for this call by passing args_addr = + // LLDB_INVALID_ADDRESS... + + DiagnosticManager diagnostics; + if (!impl_function_caller->WriteFunctionArguments( + exe_ctx, args_addr, dispatch_values, diagnostics)) { + if (log) { + LLDB_LOGF(log, "Error writing function arguments."); + diagnostics.Dump(log); + } + return args_addr; + } + + return args_addr; +} + +const AppleObjCTrampolineHandler::DispatchFunction * +AppleObjCTrampolineHandler::FindDispatchFunction(lldb::addr_t addr) { + MsgsendMap::iterator pos; + pos = m_msgSend_map.find(addr); + if (pos != m_msgSend_map.end()) { + return &g_dispatch_functions[(*pos).second]; + } + return nullptr; +} + +void AppleObjCTrampolineHandler::ForEachDispatchFunction( + std::function<void(lldb::addr_t, const DispatchFunction &)> callback) { + for (auto elem : m_msgSend_map) { + callback(elem.first, g_dispatch_functions[elem.second]); + } +} + +ThreadPlanSP +AppleObjCTrampolineHandler::GetStepThroughDispatchPlan(Thread &thread, + bool stop_others) { + ThreadPlanSP ret_plan_sp; + lldb::addr_t curr_pc = thread.GetRegisterContext()->GetPC(); + + DispatchFunction vtable_dispatch = {"vtable", false, false, false, + DispatchFunction::eFixUpFixed}; + // The selector specific stubs are a wrapper for objc_msgSend. They don't get + // passed a SEL, but instead the selector string is encoded in the stub + // name, in the form: + // objc_msgSend$SelectorName + // and the stub figures out the uniqued selector. If we find ourselves in + // one of these stubs, we strip off the selector string and pass that to the + // implementation finder function, which looks up the SEL (you have to do this + // in process) and passes that to the runtime lookup function. + DispatchFunction sel_stub_dispatch = {"sel-specific-stub", false, false, + false, DispatchFunction::eFixUpNone}; + + // First step is to see if we're in a selector-specific dispatch stub. + // Those are of the form _objc_msgSend$<SELECTOR>, so see if the current + // function has that name: + Address func_addr; + Target &target = thread.GetProcess()->GetTarget(); + llvm::StringRef sym_name; + const DispatchFunction *this_dispatch = nullptr; + + if (target.ResolveLoadAddress(curr_pc, func_addr)) { + Symbol *curr_sym = func_addr.CalculateSymbolContextSymbol(); + if (curr_sym) + sym_name = curr_sym->GetName().GetStringRef(); + + if (!sym_name.empty() && !sym_name.consume_front("objc_msgSend$")) + sym_name = {}; + else + this_dispatch = &sel_stub_dispatch; + } + bool in_selector_stub = !sym_name.empty(); + // Second step is to look and see if we are in one of the known ObjC + // dispatch functions. We've already compiled a table of same, so + // consult it. + + if (!in_selector_stub) + this_dispatch = FindDispatchFunction(curr_pc); + + // Next check to see if we are in a vtable region: + + if (!this_dispatch && m_vtables_up) { + uint32_t flags; + if (m_vtables_up->IsAddressInVTables(curr_pc, flags)) { + vtable_dispatch.stret_return = + (flags & AppleObjCVTables::eOBJC_TRAMPOLINE_STRET) == + AppleObjCVTables::eOBJC_TRAMPOLINE_STRET; + this_dispatch = &vtable_dispatch; + } + } + + // Since we set this_dispatch in both the vtable & sel specific stub cases + // this if will be used for all three of those cases. + if (this_dispatch) { + Log *log = GetLog(LLDBLog::Step); + + // We are decoding a method dispatch. First job is to pull the + // arguments out. If we are in a regular stub, we get self & selector, + // but if we are in a selector-specific stub, we'll have to get that from + // the string sym_name. + + lldb::StackFrameSP thread_cur_frame = thread.GetStackFrameAtIndex(0); + + const ABI *abi = nullptr; + ProcessSP process_sp(thread.CalculateProcess()); + if (process_sp) + abi = process_sp->GetABI().get(); + if (abi == nullptr) + return ret_plan_sp; + + TargetSP target_sp(thread.CalculateTarget()); + + TypeSystemClangSP scratch_ts_sp = + ScratchTypeSystemClang::GetForTarget(*target_sp); + if (!scratch_ts_sp) + return ret_plan_sp; + + ValueList argument_values; + Value void_ptr_value; + CompilerType clang_void_ptr_type = + scratch_ts_sp->GetBasicType(eBasicTypeVoid).GetPointerType(); + void_ptr_value.SetValueType(Value::ValueType::Scalar); + // void_ptr_value.SetContext (Value::eContextTypeClangType, + // clang_void_ptr_type); + void_ptr_value.SetCompilerType(clang_void_ptr_type); + + int obj_index; + int sel_index; + + // If this is a selector-specific stub then just push one value, 'cause + // we only get the object. + // If this is a struct return dispatch, then the first argument is + // the return struct pointer, and the object is the second, and + // the selector is the third. + // Otherwise the object is the first and the selector the second. + if (in_selector_stub) { + obj_index = 0; + sel_index = 1; + argument_values.PushValue(void_ptr_value); + } else if (this_dispatch->stret_return) { + obj_index = 1; + sel_index = 2; + argument_values.PushValue(void_ptr_value); + argument_values.PushValue(void_ptr_value); + argument_values.PushValue(void_ptr_value); + } else { + obj_index = 0; + sel_index = 1; + argument_values.PushValue(void_ptr_value); + argument_values.PushValue(void_ptr_value); + } + + bool success = abi->GetArgumentValues(thread, argument_values); + if (!success) + return ret_plan_sp; + + lldb::addr_t obj_addr = + argument_values.GetValueAtIndex(obj_index)->GetScalar().ULongLong(); + if (obj_addr == 0x0) { + LLDB_LOGF( + log, + "Asked to step to dispatch to nil object, returning empty plan."); + return ret_plan_sp; + } + + ExecutionContext exe_ctx(thread.shared_from_this()); + // isa_addr will store the class pointer that the method is being + // dispatched to - so either the class directly or the super class + // if this is one of the objc_msgSendSuper flavors. That's mostly + // used to look up the class/selector pair in our cache. + + lldb::addr_t isa_addr = LLDB_INVALID_ADDRESS; + lldb::addr_t sel_addr = LLDB_INVALID_ADDRESS; + // If we are not in a selector stub, get the sel address from the arguments. + if (!in_selector_stub) + sel_addr = + argument_values.GetValueAtIndex(sel_index)->GetScalar().ULongLong(); + + // Figure out the class this is being dispatched to and see if + // we've already cached this method call, If so we can push a + // run-to-address plan directly. Otherwise we have to figure out + // where the implementation lives. + + if (this_dispatch->is_super) { + if (this_dispatch->is_super2) { + // In the objc_msgSendSuper2 case, we don't get the object + // directly, we get a structure containing the object and the + // class to which the super message is being sent. So we need + // to dig the super out of the class and use that. + + Value super_value(*(argument_values.GetValueAtIndex(obj_index))); + super_value.GetScalar() += process_sp->GetAddressByteSize(); + super_value.ResolveValue(&exe_ctx); + + if (super_value.GetScalar().IsValid()) { + + // isa_value now holds the class pointer. The second word of the + // class pointer is the super-class pointer: + super_value.GetScalar() += process_sp->GetAddressByteSize(); + super_value.ResolveValue(&exe_ctx); + if (super_value.GetScalar().IsValid()) + isa_addr = super_value.GetScalar().ULongLong(); + else { + LLDB_LOGF(log, "Failed to extract the super class value from the " + "class in objc_super."); + } + } else { + LLDB_LOGF(log, "Failed to extract the class value from objc_super."); + } + } else { + // In the objc_msgSendSuper case, we don't get the object + // directly, we get a two element structure containing the + // object and the super class to which the super message is + // being sent. So the class we want is the second element of + // this structure. + + Value super_value(*(argument_values.GetValueAtIndex(obj_index))); + super_value.GetScalar() += process_sp->GetAddressByteSize(); + super_value.ResolveValue(&exe_ctx); + + if (super_value.GetScalar().IsValid()) { + isa_addr = super_value.GetScalar().ULongLong(); + } else { + LLDB_LOGF(log, "Failed to extract the class value from objc_super."); + } + } + } else { + // In the direct dispatch case, the object->isa is the class pointer we + // want. + + // This is a little cheesy, but since object->isa is the first field, + // making the object value a load address value and resolving it will get + // the pointer sized data pointed to by that value... + + // Note, it isn't a fatal error not to be able to get the + // address from the object, since this might be a "tagged + // pointer" which isn't a real object, but rather some word + // length encoded dingus. + + Value isa_value(*(argument_values.GetValueAtIndex(obj_index))); + + isa_value.SetValueType(Value::ValueType::LoadAddress); + isa_value.ResolveValue(&exe_ctx); + if (isa_value.GetScalar().IsValid()) { + isa_addr = isa_value.GetScalar().ULongLong(); + } else { + LLDB_LOGF(log, "Failed to extract the isa value from object."); + } + } + + // Okay, we've got the address of the class for which we're resolving this, + // let's see if it's in our cache: + lldb::addr_t impl_addr = LLDB_INVALID_ADDRESS; + // If this is a regular dispatch, look up the sel in our addr to sel cache: + if (isa_addr != LLDB_INVALID_ADDRESS) { + ObjCLanguageRuntime *objc_runtime = + ObjCLanguageRuntime::Get(*thread.GetProcess()); + assert(objc_runtime != nullptr); + if (!in_selector_stub) { + LLDB_LOG(log, "Resolving call for class - {0} and selector - {1}", + isa_addr, sel_addr); + impl_addr = objc_runtime->LookupInMethodCache(isa_addr, sel_addr); + } else { + LLDB_LOG(log, "Resolving call for class - {0} and selector - {1}", + isa_addr, sym_name); + impl_addr = objc_runtime->LookupInMethodCache(isa_addr, sym_name); + } + } + // If it is a selector-specific stub dispatch, look in the string cache: + + if (impl_addr != LLDB_INVALID_ADDRESS) { + // Yup, it was in the cache, so we can run to that address directly. + + LLDB_LOGF(log, "Found implementation address in cache: 0x%" PRIx64, + impl_addr); + + ret_plan_sp = std::make_shared<ThreadPlanRunToAddress>(thread, impl_addr, + stop_others); + } else { + // We haven't seen this class/selector pair yet. Look it up. + StreamString errors; + Address impl_code_address; + + ValueList dispatch_values; + + // We've will inject a little function in the target that takes the + // object, selector/selector string and some flags, + // and figures out the implementation. Looks like: + // void *__lldb_objc_find_implementation_for_selector (void *object, + // void *sel, + // int + // is_str_ptr, + // int is_stret, + // int is_super, + // int is_super2, + // int is_fixup, + // int is_fixed, + // int debug) + // If we don't have an actual SEL, but rather a string version of the + // selector WE injected, set is_str_ptr to true, and sel to the address + // of the string. + // So set up the arguments for that call. + + dispatch_values.PushValue(*(argument_values.GetValueAtIndex(obj_index))); + lldb::addr_t sel_str_addr = LLDB_INVALID_ADDRESS; + if (!in_selector_stub) { + // If we don't have a selector string, push the selector from arguments. + dispatch_values.PushValue( + *(argument_values.GetValueAtIndex(sel_index))); + } else { + // Otherwise, inject the string into the target, and push that value for + // the sel argument. + Status error; + sel_str_addr = process_sp->AllocateMemory( + sym_name.size() + 1, ePermissionsReadable | ePermissionsWritable, + error); + if (sel_str_addr == LLDB_INVALID_ADDRESS || error.Fail()) { + LLDB_LOG(log, + "Could not allocate memory for selector string {0}: {1}", + sym_name, error); + return ret_plan_sp; + } + process_sp->WriteMemory(sel_str_addr, sym_name.str().c_str(), + sym_name.size() + 1, error); + if (error.Fail()) { + LLDB_LOG(log, "Could not write string to address {0}", sel_str_addr); + return ret_plan_sp; + } + Value sel_ptr_value(void_ptr_value); + sel_ptr_value.GetScalar() = sel_str_addr; + dispatch_values.PushValue(sel_ptr_value); + } + + Value flag_value; + CompilerType clang_int_type = + scratch_ts_sp->GetBuiltinTypeForEncodingAndBitSize( + lldb::eEncodingSint, 32); + flag_value.SetValueType(Value::ValueType::Scalar); + // flag_value.SetContext (Value::eContextTypeClangType, clang_int_type); + flag_value.SetCompilerType(clang_int_type); + + if (in_selector_stub) + flag_value.GetScalar() = 1; + else + flag_value.GetScalar() = 0; + dispatch_values.PushValue(flag_value); + + if (this_dispatch->stret_return) + flag_value.GetScalar() = 1; + else + flag_value.GetScalar() = 0; + dispatch_values.PushValue(flag_value); + + if (this_dispatch->is_super) + flag_value.GetScalar() = 1; + else + flag_value.GetScalar() = 0; + dispatch_values.PushValue(flag_value); + + if (this_dispatch->is_super2) + flag_value.GetScalar() = 1; + else + flag_value.GetScalar() = 0; + dispatch_values.PushValue(flag_value); + + switch (this_dispatch->fixedup) { + case DispatchFunction::eFixUpNone: + flag_value.GetScalar() = 0; + dispatch_values.PushValue(flag_value); + dispatch_values.PushValue(flag_value); + break; + case DispatchFunction::eFixUpFixed: + flag_value.GetScalar() = 1; + dispatch_values.PushValue(flag_value); + flag_value.GetScalar() = 1; + dispatch_values.PushValue(flag_value); + break; + case DispatchFunction::eFixUpToFix: + flag_value.GetScalar() = 1; + dispatch_values.PushValue(flag_value); + flag_value.GetScalar() = 0; + dispatch_values.PushValue(flag_value); + break; + } + if (log && log->GetVerbose()) + flag_value.GetScalar() = 1; + else + flag_value.GetScalar() = 0; // FIXME - Set to 0 when debugging is done. + dispatch_values.PushValue(flag_value); + + ret_plan_sp = std::make_shared<AppleThreadPlanStepThroughObjCTrampoline>( + thread, *this, dispatch_values, isa_addr, sel_addr, sel_str_addr, + sym_name); + if (log) { + StreamString s; + ret_plan_sp->GetDescription(&s, eDescriptionLevelFull); + LLDB_LOGF(log, "Using ObjC step plan: %s.\n", s.GetData()); + } + } + } + + // Finally, check if we have hit an "optimized dispatch" function. This will + // either directly call the base implementation or dispatch an objc_msgSend + // if the method has been overridden. So we just do a "step in/step out", + // setting a breakpoint on objc_msgSend, and if we hit the msgSend, we + // will automatically step in again. That's the job of the + // AppleThreadPlanStepThroughDirectDispatch. + if (!this_dispatch && !ret_plan_sp) { + MsgsendMap::iterator pos; + pos = m_opt_dispatch_map.find(curr_pc); + if (pos != m_opt_dispatch_map.end()) { + const char *opt_name = g_opt_dispatch_names[(*pos).second]; + ret_plan_sp = std::make_shared<AppleThreadPlanStepThroughDirectDispatch>( + thread, *this, opt_name); + } + } + + return ret_plan_sp; +} + +FunctionCaller * +AppleObjCTrampolineHandler::GetLookupImplementationFunctionCaller() { + return m_impl_code->GetFunctionCaller(); +} diff --git a/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.h b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.h new file mode 100644 index 000000000000..a6e1e16d1ee0 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.h @@ -0,0 +1,169 @@ +//===-- AppleObjCTrampolineHandler.h ----------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLEOBJCTRAMPOLINEHANDLER_H +#define LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLEOBJCTRAMPOLINEHANDLER_H + +#include <map> +#include <mutex> +#include <vector> + +#include "lldb/Expression/UtilityFunction.h" +#include "lldb/lldb-public.h" + +namespace lldb_private { + +class AppleObjCTrampolineHandler { +public: + AppleObjCTrampolineHandler(const lldb::ProcessSP &process_sp, + const lldb::ModuleSP &objc_module_sp); + + ~AppleObjCTrampolineHandler(); + + lldb::ThreadPlanSP GetStepThroughDispatchPlan(Thread &thread, + bool stop_others); + + FunctionCaller *GetLookupImplementationFunctionCaller(); + + bool AddrIsMsgForward(lldb::addr_t addr) const { + return (addr == m_msg_forward_addr || addr == m_msg_forward_stret_addr); + } + + struct DispatchFunction { + public: + enum FixUpState { eFixUpNone, eFixUpFixed, eFixUpToFix }; + + const char *name = nullptr; + bool stret_return = false; + bool is_super = false; + bool is_super2 = false; + FixUpState fixedup = eFixUpNone; + }; + + lldb::addr_t SetupDispatchFunction(Thread &thread, + ValueList &dispatch_values); + const DispatchFunction *FindDispatchFunction(lldb::addr_t addr); + void ForEachDispatchFunction(std::function<void(lldb::addr_t, + const DispatchFunction &)>); + +private: + /// These hold the code for the function that finds the implementation of + /// an ObjC message send given the class & selector and the kind of dispatch. + /// There are two variants depending on whether the platform uses a separate + /// _stret passing convention (e.g. Intel) or not (e.g. ARM). The difference + /// is only at the very end of the function, so the code is broken into the + /// common prefix and the suffix, which get composed appropriately before + /// the function gets compiled. + /// \{ + static const char *g_lookup_implementation_function_name; + static const char *g_lookup_implementation_function_common_code; + static const char *g_lookup_implementation_with_stret_function_code; + static const char *g_lookup_implementation_no_stret_function_code; + /// \} + + class AppleObjCVTables { + public: + // These come from objc-gdb.h. + enum VTableFlags { + eOBJC_TRAMPOLINE_MESSAGE = (1 << 0), // trampoline acts like objc_msgSend + eOBJC_TRAMPOLINE_STRET = (1 << 1), // trampoline is struct-returning + eOBJC_TRAMPOLINE_VTABLE = (1 << 2) // trampoline is vtable dispatcher + }; + + private: + struct VTableDescriptor { + VTableDescriptor(uint32_t in_flags, lldb::addr_t in_code_start) + : flags(in_flags), code_start(in_code_start) {} + + uint32_t flags; + lldb::addr_t code_start; + }; + + class VTableRegion { + public: + VTableRegion() = default; + + VTableRegion(AppleObjCVTables *owner, lldb::addr_t header_addr); + + void SetUpRegion(); + + lldb::addr_t GetNextRegionAddr() { return m_next_region; } + + lldb::addr_t GetCodeStart() { return m_code_start_addr; } + + lldb::addr_t GetCodeEnd() { return m_code_end_addr; } + + uint32_t GetFlagsForVTableAtAddress(lldb::addr_t address) { return 0; } + + bool IsValid() { return m_valid; } + + bool AddressInRegion(lldb::addr_t addr, uint32_t &flags); + + void Dump(Stream &s); + + bool m_valid = false; + AppleObjCVTables *m_owner = nullptr; + lldb::addr_t m_header_addr = LLDB_INVALID_ADDRESS; + lldb::addr_t m_code_start_addr = 0; + lldb::addr_t m_code_end_addr = 0; + std::vector<VTableDescriptor> m_descriptors; + lldb::addr_t m_next_region = 0; + }; + + public: + AppleObjCVTables(const lldb::ProcessSP &process_sp, + const lldb::ModuleSP &objc_module_sp); + + ~AppleObjCVTables(); + + bool InitializeVTableSymbols(); + + static bool RefreshTrampolines(void *baton, + StoppointCallbackContext *context, + lldb::user_id_t break_id, + lldb::user_id_t break_loc_id); + bool ReadRegions(); + + bool ReadRegions(lldb::addr_t region_addr); + + bool IsAddressInVTables(lldb::addr_t addr, uint32_t &flags); + + lldb::ProcessSP GetProcessSP() { return m_process_wp.lock(); } + + private: + lldb::ProcessWP m_process_wp; + typedef std::vector<VTableRegion> region_collection; + lldb::addr_t m_trampoline_header; + lldb::break_id_t m_trampolines_changed_bp_id; + region_collection m_regions; + lldb::ModuleSP m_objc_module_sp; + }; + + static const DispatchFunction g_dispatch_functions[]; + static const char *g_opt_dispatch_names[]; + + using MsgsendMap = std::map<lldb::addr_t, int>; // This table maps an dispatch + // fn address to the index in + // g_dispatch_functions + MsgsendMap m_msgSend_map; + MsgsendMap m_opt_dispatch_map; + lldb::ProcessWP m_process_wp; + lldb::ModuleSP m_objc_module_sp; + std::string m_lookup_implementation_function_code; + std::unique_ptr<UtilityFunction> m_impl_code; + std::mutex m_impl_function_mutex; + lldb::addr_t m_impl_fn_addr; + lldb::addr_t m_impl_stret_fn_addr; + lldb::addr_t m_msg_forward_addr; + lldb::addr_t m_msg_forward_stret_addr; + std::unique_ptr<AppleObjCVTables> m_vtables_up; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLEOBJCTRAMPOLINEHANDLER_H diff --git a/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTypeEncodingParser.cpp b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTypeEncodingParser.cpp new file mode 100644 index 000000000000..ddaa7a8a597b --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTypeEncodingParser.cpp @@ -0,0 +1,372 @@ +//===-- AppleObjCTypeEncodingParser.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 "AppleObjCTypeEncodingParser.h" + +#include "Plugins/ExpressionParser/Clang/ClangUtil.h" +#include "Plugins/TypeSystem/Clang/TypeSystemClang.h" +#include "lldb/Symbol/CompilerType.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/StringLexer.h" + +#include "clang/Basic/TargetInfo.h" + +#include <vector> + +using namespace lldb_private; + +AppleObjCTypeEncodingParser::AppleObjCTypeEncodingParser( + ObjCLanguageRuntime &runtime) + : ObjCLanguageRuntime::EncodingToType(), m_runtime(runtime) { + if (m_scratch_ast_ctx_sp) + return; + + m_scratch_ast_ctx_sp = std::make_shared<TypeSystemClang>( + "AppleObjCTypeEncodingParser ASTContext", + runtime.GetProcess()->GetTarget().GetArchitecture().GetTriple()); +} + +std::string AppleObjCTypeEncodingParser::ReadStructName(StringLexer &type) { + StreamString buffer; + while (type.HasAtLeast(1) && type.Peek() != '=') + buffer.Printf("%c", type.Next()); + return std::string(buffer.GetString()); +} + +std::string AppleObjCTypeEncodingParser::ReadQuotedString(StringLexer &type) { + StreamString buffer; + while (type.HasAtLeast(1) && type.Peek() != '"') + buffer.Printf("%c", type.Next()); + StringLexer::Character next = type.Next(); + UNUSED_IF_ASSERT_DISABLED(next); + assert(next == '"'); + return std::string(buffer.GetString()); +} + +uint32_t AppleObjCTypeEncodingParser::ReadNumber(StringLexer &type) { + uint32_t total = 0; + while (type.HasAtLeast(1) && isdigit(type.Peek())) + total = 10 * total + (type.Next() - '0'); + return total; +} + +// as an extension to the published grammar recent runtimes emit structs like +// this: +// "{CGRect=\"origin\"{CGPoint=\"x\"d\"y\"d}\"size\"{CGSize=\"width\"d\"height\"d}}" + +AppleObjCTypeEncodingParser::StructElement::StructElement() + : type(clang::QualType()) {} + +AppleObjCTypeEncodingParser::StructElement +AppleObjCTypeEncodingParser::ReadStructElement(TypeSystemClang &ast_ctx, + StringLexer &type, + bool for_expression) { + StructElement retval; + if (type.NextIf('"')) + retval.name = ReadQuotedString(type); + if (!type.NextIf('"')) + return retval; + uint32_t bitfield_size = 0; + retval.type = BuildType(ast_ctx, type, for_expression, &bitfield_size); + retval.bitfield = bitfield_size; + return retval; +} + +clang::QualType AppleObjCTypeEncodingParser::BuildStruct( + TypeSystemClang &ast_ctx, StringLexer &type, bool for_expression) { + return BuildAggregate(ast_ctx, type, for_expression, _C_STRUCT_B, _C_STRUCT_E, + llvm::to_underlying(clang::TagTypeKind::Struct)); +} + +clang::QualType AppleObjCTypeEncodingParser::BuildUnion( + TypeSystemClang &ast_ctx, StringLexer &type, bool for_expression) { + return BuildAggregate(ast_ctx, type, for_expression, _C_UNION_B, _C_UNION_E, + llvm::to_underlying(clang::TagTypeKind::Union)); +} + +clang::QualType AppleObjCTypeEncodingParser::BuildAggregate( + TypeSystemClang &ast_ctx, StringLexer &type, bool for_expression, + char opener, char closer, uint32_t kind) { + if (!type.NextIf(opener)) + return clang::QualType(); + std::string name(ReadStructName(type)); + + // We do not handle templated classes/structs at the moment. If the name has + // a < in it, we are going to abandon this. We're still obliged to parse it, + // so we just set a flag that means "Don't actually build anything." + + const bool is_templated = name.find('<') != std::string::npos; + + if (!type.NextIf('=')) + return clang::QualType(); + bool in_union = true; + std::vector<StructElement> elements; + while (in_union && type.HasAtLeast(1)) { + if (type.NextIf(closer)) { + in_union = false; + break; + } else { + auto element = ReadStructElement(ast_ctx, type, for_expression); + if (element.type.isNull()) + break; + else + elements.push_back(element); + } + } + if (in_union) + return clang::QualType(); + + if (is_templated) + return clang::QualType(); // This is where we bail out. Sorry! + + CompilerType union_type(ast_ctx.CreateRecordType( + nullptr, OptionalClangModuleID(), lldb::eAccessPublic, name, kind, + lldb::eLanguageTypeC)); + if (union_type) { + TypeSystemClang::StartTagDeclarationDefinition(union_type); + + unsigned int count = 0; + for (auto element : elements) { + if (element.name.empty()) { + StreamString elem_name; + elem_name.Printf("__unnamed_%u", count); + element.name = std::string(elem_name.GetString()); + } + TypeSystemClang::AddFieldToRecordType( + union_type, element.name.c_str(), ast_ctx.GetType(element.type), + lldb::eAccessPublic, element.bitfield); + ++count; + } + TypeSystemClang::CompleteTagDeclarationDefinition(union_type); + } + return ClangUtil::GetQualType(union_type); +} + +clang::QualType AppleObjCTypeEncodingParser::BuildArray( + TypeSystemClang &ast_ctx, StringLexer &type, bool for_expression) { + if (!type.NextIf(_C_ARY_B)) + return clang::QualType(); + uint32_t size = ReadNumber(type); + clang::QualType element_type(BuildType(ast_ctx, type, for_expression)); + if (!type.NextIf(_C_ARY_E)) + return clang::QualType(); + CompilerType array_type(ast_ctx.CreateArrayType( + CompilerType(ast_ctx.weak_from_this(), element_type.getAsOpaquePtr()), + size, false)); + return ClangUtil::GetQualType(array_type); +} + +// the runtime can emit these in the form of @"SomeType", giving more specifics +// this would be interesting for expression parser interop, but since we +// actually try to avoid exposing the ivar info to the expression evaluator, +// consume but ignore the type info and always return an 'id'; if anything, +// dynamic typing will resolve things for us anyway +clang::QualType AppleObjCTypeEncodingParser::BuildObjCObjectPointerType( + TypeSystemClang &clang_ast_ctx, StringLexer &type, bool for_expression) { + if (!type.NextIf(_C_ID)) + return clang::QualType(); + + clang::ASTContext &ast_ctx = clang_ast_ctx.getASTContext(); + + std::string name; + + if (type.NextIf('"')) { + // We have to be careful here. We're used to seeing + // @"NSString" + // but in records it is possible that the string following an @ is the name + // of the next field and @ means "id". This is the case if anything + // unquoted except for "}", the end of the type, or another name follows + // the quoted string. + // + // E.g. + // - @"NSString"@ means "id, followed by a field named NSString of type id" + // - @"NSString"} means "a pointer to NSString and the end of the struct" - + // @"NSString""nextField" means "a pointer to NSString and a field named + // nextField" - @"NSString" followed by the end of the string means "a + // pointer to NSString" + // + // As a result, the rule is: If we see @ followed by a quoted string, we + // peek. - If we see }, ), ], the end of the string, or a quote ("), the + // quoted string is a class name. - If we see anything else, the quoted + // string is a field name and we push it back onto type. + + name = ReadQuotedString(type); + + if (type.HasAtLeast(1)) { + switch (type.Peek()) { + default: + // roll back + type.PutBack(name.length() + + 2); // undo our consumption of the string and of the quotes + name.clear(); + break; + case _C_STRUCT_E: + case _C_UNION_E: + case _C_ARY_E: + case '"': + // the quoted string is a class name – see the rule + break; + } + } else { + // the quoted string is a class name – see the rule + } + } + + if (for_expression && !name.empty()) { + size_t less_than_pos = name.find('<'); + + if (less_than_pos != std::string::npos) { + if (less_than_pos == 0) + return ast_ctx.getObjCIdType(); + else + name.erase(less_than_pos); + } + + DeclVendor *decl_vendor = m_runtime.GetDeclVendor(); + if (!decl_vendor) + return clang::QualType(); + + auto types = decl_vendor->FindTypes(ConstString(name), /*max_matches*/ 1); + + if (types.empty()) { + // The user can forward-declare something that has no definition. The + // runtime doesn't prohibit this at all. This is a rare and very weird + // case. Assert assert in debug builds so we catch other weird cases. + assert(false && "forward declaration without definition"); + LLDB_LOG(GetLog(LLDBLog::Types), + "forward declaration without definition: {0}", name); + return ast_ctx.getObjCIdType(); + } + + return ClangUtil::GetQualType(types.front().GetPointerType()); + } else { + // We're going to resolve this dynamically anyway, so just smile and wave. + return ast_ctx.getObjCIdType(); + } +} + +clang::QualType +AppleObjCTypeEncodingParser::BuildType(TypeSystemClang &clang_ast_ctx, + StringLexer &type, bool for_expression, + uint32_t *bitfield_bit_size) { + if (!type.HasAtLeast(1)) + return clang::QualType(); + + clang::ASTContext &ast_ctx = clang_ast_ctx.getASTContext(); + + switch (type.Peek()) { + default: + break; + case _C_STRUCT_B: + return BuildStruct(clang_ast_ctx, type, for_expression); + case _C_ARY_B: + return BuildArray(clang_ast_ctx, type, for_expression); + case _C_UNION_B: + return BuildUnion(clang_ast_ctx, type, for_expression); + case _C_ID: + return BuildObjCObjectPointerType(clang_ast_ctx, type, for_expression); + } + + switch (type.Next()) { + default: + type.PutBack(1); + return clang::QualType(); + case _C_CHR: + return ast_ctx.CharTy; + case _C_INT: + return ast_ctx.IntTy; + case _C_SHT: + return ast_ctx.ShortTy; + case _C_LNG: + return ast_ctx.getIntTypeForBitwidth(32, true); + // this used to be done like this: + // return clang_ast_ctx->GetIntTypeFromBitSize(32, true).GetQualType(); + // which uses one of the constants if one is available, but we don't think + // all this work is necessary. + case _C_LNG_LNG: + return ast_ctx.LongLongTy; + case _C_UCHR: + return ast_ctx.UnsignedCharTy; + case _C_UINT: + return ast_ctx.UnsignedIntTy; + case _C_USHT: + return ast_ctx.UnsignedShortTy; + case _C_ULNG: + return ast_ctx.getIntTypeForBitwidth(32, false); + // see note for _C_LNG + case _C_ULNG_LNG: + return ast_ctx.UnsignedLongLongTy; + case _C_FLT: + return ast_ctx.FloatTy; + case _C_DBL: + return ast_ctx.DoubleTy; + case _C_BOOL: + return ast_ctx.BoolTy; + case _C_VOID: + return ast_ctx.VoidTy; + case _C_CHARPTR: + return ast_ctx.getPointerType(ast_ctx.CharTy); + case _C_CLASS: + return ast_ctx.getObjCClassType(); + case _C_SEL: + return ast_ctx.getObjCSelType(); + case _C_BFLD: { + uint32_t size = ReadNumber(type); + if (bitfield_bit_size) { + *bitfield_bit_size = size; + return ast_ctx.UnsignedIntTy; // FIXME: the spec is fairly vague here. + } else + return clang::QualType(); + } + case _C_CONST: { + clang::QualType target_type = + BuildType(clang_ast_ctx, type, for_expression); + if (target_type.isNull()) + return clang::QualType(); + else if (target_type == ast_ctx.UnknownAnyTy) + return ast_ctx.UnknownAnyTy; + else + return ast_ctx.getConstType(target_type); + } + case _C_PTR: { + if (!for_expression && type.NextIf(_C_UNDEF)) { + // if we are not supporting the concept of unknownAny, but what is being + // created here is an unknownAny*, then we can just get away with a void* + // this is theoretically wrong (in the same sense as 'theoretically + // nothing exists') but is way better than outright failure in many + // practical cases + return ast_ctx.VoidPtrTy; + } else { + clang::QualType target_type = + BuildType(clang_ast_ctx, type, for_expression); + if (target_type.isNull()) + return clang::QualType(); + else if (target_type == ast_ctx.UnknownAnyTy) + return ast_ctx.UnknownAnyTy; + else + return ast_ctx.getPointerType(target_type); + } + } + case _C_UNDEF: + return for_expression ? ast_ctx.UnknownAnyTy : clang::QualType(); + } +} + +CompilerType AppleObjCTypeEncodingParser::RealizeType(TypeSystemClang &ast_ctx, + const char *name, + bool for_expression) { + if (name && name[0]) { + StringLexer lexer(name); + clang::QualType qual_type = BuildType(ast_ctx, lexer, for_expression); + return ast_ctx.GetType(qual_type); + } + return CompilerType(); +} diff --git a/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTypeEncodingParser.h b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTypeEncodingParser.h new file mode 100644 index 000000000000..57ed9c21faba --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTypeEncodingParser.h @@ -0,0 +1,73 @@ +//===-- AppleObjCTypeEncodingParser.h ---------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLEOBJCTYPEENCODINGPARSER_H +#define LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLEOBJCTYPEENCODINGPARSER_H + +#include "Plugins/Language/ObjC/ObjCConstants.h" +#include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h" +#include "lldb/lldb-private.h" + +#include "clang/AST/ASTContext.h" + +namespace lldb_private { +class StringLexer; +class AppleObjCTypeEncodingParser : public ObjCLanguageRuntime::EncodingToType { +public: + AppleObjCTypeEncodingParser(ObjCLanguageRuntime &runtime); + ~AppleObjCTypeEncodingParser() override = default; + + CompilerType RealizeType(TypeSystemClang &ast_ctx, const char *name, + bool for_expression) override; + +private: + struct StructElement { + std::string name; + clang::QualType type; + uint32_t bitfield = 0; + + StructElement(); + ~StructElement() = default; + }; + + clang::QualType BuildType(TypeSystemClang &clang_ast_ctx, StringLexer &type, + bool for_expression, + uint32_t *bitfield_bit_size = nullptr); + + clang::QualType BuildStruct(TypeSystemClang &ast_ctx, StringLexer &type, + bool for_expression); + + clang::QualType BuildAggregate(TypeSystemClang &clang_ast_ctx, + StringLexer &type, bool for_expression, + char opener, char closer, uint32_t kind); + + clang::QualType BuildUnion(TypeSystemClang &ast_ctx, StringLexer &type, + bool for_expression); + + clang::QualType BuildArray(TypeSystemClang &ast_ctx, StringLexer &type, + bool for_expression); + + std::string ReadStructName(StringLexer &type); + + StructElement ReadStructElement(TypeSystemClang &ast_ctx, StringLexer &type, + bool for_expression); + + clang::QualType BuildObjCObjectPointerType(TypeSystemClang &clang_ast_ctx, + StringLexer &type, + bool for_expression); + + uint32_t ReadNumber(StringLexer &type); + + std::string ReadQuotedString(StringLexer &type); + + ObjCLanguageRuntime &m_runtime; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLEOBJCTYPEENCODINGPARSER_H diff --git a/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp new file mode 100644 index 000000000000..4093cbdd955f --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp @@ -0,0 +1,428 @@ +//===-- AppleThreadPlanStepThroughObjCTrampoline.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 "AppleThreadPlanStepThroughObjCTrampoline.h" + +#include "AppleObjCTrampolineHandler.h" +#include "lldb/Expression/DiagnosticManager.h" +#include "lldb/Expression/FunctionCaller.h" +#include "lldb/Expression/UtilityFunction.h" +#include "lldb/Target/ABI.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadPlanRunToAddress.h" +#include "lldb/Target/ThreadPlanStepOut.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" + +#include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h" + +#include <memory> + +using namespace lldb; +using namespace lldb_private; + +// ThreadPlanStepThroughObjCTrampoline constructor +AppleThreadPlanStepThroughObjCTrampoline:: + AppleThreadPlanStepThroughObjCTrampoline( + Thread &thread, AppleObjCTrampolineHandler &trampoline_handler, + ValueList &input_values, lldb::addr_t isa_addr, lldb::addr_t sel_addr, + lldb::addr_t sel_str_addr, llvm::StringRef sel_str) + : ThreadPlan(ThreadPlan::eKindGeneric, + "MacOSX Step through ObjC Trampoline", thread, eVoteNoOpinion, + eVoteNoOpinion), + m_trampoline_handler(trampoline_handler), + m_args_addr(LLDB_INVALID_ADDRESS), m_input_values(input_values), + m_isa_addr(isa_addr), m_sel_addr(sel_addr), m_impl_function(nullptr), + m_sel_str_addr(sel_str_addr), m_sel_str(sel_str) {} + +// Destructor +AppleThreadPlanStepThroughObjCTrampoline:: + ~AppleThreadPlanStepThroughObjCTrampoline() = default; + +void AppleThreadPlanStepThroughObjCTrampoline::DidPush() { + // Setting up the memory space for the called function text might require + // allocations, i.e. a nested function call. This needs to be done as a + // PreResumeAction. + m_process.AddPreResumeAction(PreResumeInitializeFunctionCaller, (void *)this); +} + +bool AppleThreadPlanStepThroughObjCTrampoline::InitializeFunctionCaller() { + if (!m_func_sp) { + DiagnosticManager diagnostics; + m_args_addr = + m_trampoline_handler.SetupDispatchFunction(GetThread(), m_input_values); + + if (m_args_addr == LLDB_INVALID_ADDRESS) { + return false; + } + m_impl_function = + m_trampoline_handler.GetLookupImplementationFunctionCaller(); + ExecutionContext exc_ctx; + EvaluateExpressionOptions options; + options.SetUnwindOnError(true); + options.SetIgnoreBreakpoints(true); + options.SetStopOthers(false); + GetThread().CalculateExecutionContext(exc_ctx); + m_func_sp = m_impl_function->GetThreadPlanToCallFunction( + exc_ctx, m_args_addr, options, diagnostics); + m_func_sp->SetOkayToDiscard(true); + PushPlan(m_func_sp); + } + return true; +} + +bool AppleThreadPlanStepThroughObjCTrampoline:: + PreResumeInitializeFunctionCaller(void *void_myself) { + AppleThreadPlanStepThroughObjCTrampoline *myself = + static_cast<AppleThreadPlanStepThroughObjCTrampoline *>(void_myself); + return myself->InitializeFunctionCaller(); +} + +void AppleThreadPlanStepThroughObjCTrampoline::GetDescription( + Stream *s, lldb::DescriptionLevel level) { + if (level == lldb::eDescriptionLevelBrief) + s->Printf("Step through ObjC trampoline"); + else { + s->Printf("Stepping to implementation of ObjC method - obj: 0x%llx, isa: " + "0x%" PRIx64 ", sel: 0x%" PRIx64, + m_input_values.GetValueAtIndex(0)->GetScalar().ULongLong(), + m_isa_addr, m_sel_addr); + } +} + +bool AppleThreadPlanStepThroughObjCTrampoline::ValidatePlan(Stream *error) { + return true; +} + +bool AppleThreadPlanStepThroughObjCTrampoline::DoPlanExplainsStop( + Event *event_ptr) { + // If we get asked to explain the stop it will be because something went + // wrong (like the implementation for selector function crashed... We're + // going to figure out what to do about that, so we do explain the stop. + return true; +} + +lldb::StateType AppleThreadPlanStepThroughObjCTrampoline::GetPlanRunState() { + return eStateRunning; +} + +bool AppleThreadPlanStepThroughObjCTrampoline::ShouldStop(Event *event_ptr) { + // First stage: we are still handling the "call a function to get the target + // of the dispatch" + if (m_func_sp) { + if (!m_func_sp->IsPlanComplete()) { + return false; + } else { + if (!m_func_sp->PlanSucceeded()) { + SetPlanComplete(false); + return true; + } + m_func_sp.reset(); + } + } + + // Second stage, if all went well with the function calling, get the + // implementation function address, and queue up a "run to that address" plan. + Log *log = GetLog(LLDBLog::Step); + + if (!m_run_to_sp) { + Value target_addr_value; + ExecutionContext exc_ctx; + GetThread().CalculateExecutionContext(exc_ctx); + m_impl_function->FetchFunctionResults(exc_ctx, m_args_addr, + target_addr_value); + m_impl_function->DeallocateFunctionResults(exc_ctx, m_args_addr); + lldb::addr_t target_addr = target_addr_value.GetScalar().ULongLong(); + + if (ABISP abi_sp = GetThread().GetProcess()->GetABI()) { + target_addr = abi_sp->FixCodeAddress(target_addr); + } + Address target_so_addr; + target_so_addr.SetOpcodeLoadAddress(target_addr, exc_ctx.GetTargetPtr()); + if (target_addr == 0) { + LLDB_LOGF(log, "Got target implementation of 0x0, stopping."); + SetPlanComplete(); + return true; + } + if (m_trampoline_handler.AddrIsMsgForward(target_addr)) { + LLDB_LOGF(log, + "Implementation lookup returned msgForward function: 0x%" PRIx64 + ", stopping.", + target_addr); + + SymbolContext sc = GetThread().GetStackFrameAtIndex(0)->GetSymbolContext( + eSymbolContextEverything); + Status status; + const bool abort_other_plans = false; + const bool first_insn = true; + const uint32_t frame_idx = 0; + m_run_to_sp = GetThread().QueueThreadPlanForStepOutNoShouldStop( + abort_other_plans, &sc, first_insn, false, eVoteNoOpinion, + eVoteNoOpinion, frame_idx, status); + if (m_run_to_sp && status.Success()) + m_run_to_sp->SetPrivate(true); + return false; + } + + LLDB_LOGF(log, "Running to ObjC method implementation: 0x%" PRIx64, + target_addr); + + ObjCLanguageRuntime *objc_runtime = + ObjCLanguageRuntime::Get(*GetThread().GetProcess()); + assert(objc_runtime != nullptr); + if (m_sel_str_addr != LLDB_INVALID_ADDRESS) { + // Cache the string -> implementation and free the string in the target. + Status dealloc_error = + GetThread().GetProcess()->DeallocateMemory(m_sel_str_addr); + // For now just log this: + if (dealloc_error.Fail()) + LLDB_LOG(log, "Failed to deallocate the sel str at {0} - error: {1}", + m_sel_str_addr, dealloc_error); + objc_runtime->AddToMethodCache(m_isa_addr, m_sel_str, target_addr); + LLDB_LOG(log, + "Adding \\{isa-addr={0}, sel-addr={1}\\} = addr={2} to cache.", + m_isa_addr, m_sel_str, target_addr); + } else { + objc_runtime->AddToMethodCache(m_isa_addr, m_sel_addr, target_addr); + LLDB_LOGF(log, + "Adding {isa-addr=0x%" PRIx64 ", sel-addr=0x%" PRIx64 + "} = addr=0x%" PRIx64 " to cache.", + m_isa_addr, m_sel_addr, target_addr); + } + + m_run_to_sp = std::make_shared<ThreadPlanRunToAddress>( + GetThread(), target_so_addr, false); + PushPlan(m_run_to_sp); + return false; + } else if (GetThread().IsThreadPlanDone(m_run_to_sp.get())) { + // Third stage, work the run to target plan. + SetPlanComplete(); + return true; + } + return false; +} + +// The base class MischiefManaged does some cleanup - so you have to call it in +// your MischiefManaged derived class. +bool AppleThreadPlanStepThroughObjCTrampoline::MischiefManaged() { + return IsPlanComplete(); +} + +bool AppleThreadPlanStepThroughObjCTrampoline::WillStop() { return true; } + +// Objective-C uses optimized dispatch functions for some common and seldom +// overridden methods. For instance +// [object respondsToSelector:]; +// will get compiled to: +// objc_opt_respondsToSelector(object); +// This checks whether the selector has been overridden, directly calling the +// implementation if it hasn't and calling objc_msgSend if it has. +// +// We need to get into the overridden implementation. We'll do that by +// setting a breakpoint on objc_msgSend, and doing a "step out". If we stop +// at objc_msgSend, we can step through to the target of the send, and see if +// that's a place we want to stop. +// +// A couple of complexities. The checking code might call some other method, +// so we might see objc_msgSend more than once. Also, these optimized dispatch +// functions might dispatch more than one message at a time (e.g. alloc followed +// by init.) So we can't give up at the first objc_msgSend. +// That means among other things that we have to handle the "ShouldStopHere" - +// since we can't just return control to the plan that's controlling us on the +// first step. + +AppleThreadPlanStepThroughDirectDispatch :: + AppleThreadPlanStepThroughDirectDispatch( + Thread &thread, AppleObjCTrampolineHandler &handler, + llvm::StringRef dispatch_func_name) + : ThreadPlanStepOut(thread, nullptr, true /* first instruction */, false, + eVoteNoOpinion, eVoteNoOpinion, + 0 /* Step out of zeroth frame */, + eLazyBoolNo /* Our parent plan will decide this + when we are done */ + , + true /* Run to branch for inline step out */, + false /* Don't gather the return value */), + m_trampoline_handler(handler), + m_dispatch_func_name(std::string(dispatch_func_name)), + m_at_msg_send(false) { + // Set breakpoints on the dispatch functions: + auto bkpt_callback = [&] (lldb::addr_t addr, + const AppleObjCTrampolineHandler + ::DispatchFunction &dispatch) { + m_msgSend_bkpts.push_back(GetTarget().CreateBreakpoint(addr, + true /* internal */, + false /* hard */)); + m_msgSend_bkpts.back()->SetThreadID(GetThread().GetID()); + }; + handler.ForEachDispatchFunction(bkpt_callback); + + // We'll set the step-out plan in the DidPush so it gets queued in the right + // order. + + if (GetThread().GetStepInAvoidsNoDebug()) + GetFlags().Set(ThreadPlanShouldStopHere::eStepInAvoidNoDebug); + else + GetFlags().Clear(ThreadPlanShouldStopHere::eStepInAvoidNoDebug); + // We only care about step in. Our parent plan will figure out what to + // do when we've stepped out again. + GetFlags().Clear(ThreadPlanShouldStopHere::eStepOutAvoidNoDebug); +} + +AppleThreadPlanStepThroughDirectDispatch:: + ~AppleThreadPlanStepThroughDirectDispatch() { + for (BreakpointSP bkpt_sp : m_msgSend_bkpts) { + GetTarget().RemoveBreakpointByID(bkpt_sp->GetID()); + } +} + +void AppleThreadPlanStepThroughDirectDispatch::GetDescription( + Stream *s, lldb::DescriptionLevel level) { + switch (level) { + case lldb::eDescriptionLevelBrief: + s->PutCString("Step through ObjC direct dispatch function."); + break; + default: + s->Printf("Step through ObjC direct dispatch '%s' using breakpoints: ", + m_dispatch_func_name.c_str()); + bool first = true; + for (auto bkpt_sp : m_msgSend_bkpts) { + if (!first) { + s->PutCString(", "); + } + first = false; + s->Printf("%d", bkpt_sp->GetID()); + } + (*s) << "."; + break; + } +} + +bool +AppleThreadPlanStepThroughDirectDispatch::DoPlanExplainsStop(Event *event_ptr) { + if (ThreadPlanStepOut::DoPlanExplainsStop(event_ptr)) + return true; + + StopInfoSP stop_info_sp = GetPrivateStopInfo(); + + // Check if the breakpoint is one of ours msgSend dispatch breakpoints. + + StopReason stop_reason = eStopReasonNone; + if (stop_info_sp) + stop_reason = stop_info_sp->GetStopReason(); + + // See if this is one of our msgSend breakpoints: + if (stop_reason == eStopReasonBreakpoint) { + ProcessSP process_sp = GetThread().GetProcess(); + uint64_t break_site_id = stop_info_sp->GetValue(); + BreakpointSiteSP site_sp + = process_sp->GetBreakpointSiteList().FindByID(break_site_id); + // Some other plan might have deleted the site's last owner before this + // got to us. In which case, it wasn't our breakpoint... + if (!site_sp) + return false; + + for (BreakpointSP break_sp : m_msgSend_bkpts) { + if (site_sp->IsBreakpointAtThisSite(break_sp->GetID())) { + // If we aren't the only one with a breakpoint on this site, then we + // should just stop and return control to the user. + if (site_sp->GetNumberOfConstituents() > 1) { + SetPlanComplete(true); + return false; + } + m_at_msg_send = true; + return true; + } + } + } + + // We're done here. If one of our sub-plans explained the stop, they + // would have already answered true to PlanExplainsStop, and if they were + // done, we'll get called to figure out what to do in ShouldStop... + return false; +} + +bool AppleThreadPlanStepThroughDirectDispatch + ::DoWillResume(lldb::StateType resume_state, bool current_plan) { + ThreadPlanStepOut::DoWillResume(resume_state, current_plan); + m_at_msg_send = false; + return true; +} + +bool AppleThreadPlanStepThroughDirectDispatch::ShouldStop(Event *event_ptr) { + // If step out plan finished, that means we didn't find our way into a method + // implementation. Either we went directly to the default implementation, + // of the overridden implementation didn't have debug info. + // So we should mark ourselves as done. + const bool step_out_should_stop = ThreadPlanStepOut::ShouldStop(event_ptr); + if (step_out_should_stop) { + SetPlanComplete(true); + return true; + } + + // If we have a step through plan, then w're in the process of getting + // through an ObjC msgSend. If we arrived at the target function, then + // check whether we have debug info, and if we do, stop. + Log *log = GetLog(LLDBLog::Step); + + if (m_objc_step_through_sp && m_objc_step_through_sp->IsPlanComplete()) { + // If the plan failed for some reason, we should probably just let the + // step over plan get us out of here... We don't need to do anything about + // the step through plan, it is done and will get popped when we continue. + if (!m_objc_step_through_sp->PlanSucceeded()) { + LLDB_LOGF(log, "ObjC Step through plan failed. Stepping out."); + } + Status error; + if (InvokeShouldStopHereCallback(eFrameCompareYounger, error)) { + SetPlanComplete(true); + return true; + } + // If we didn't want to stop at this msgSend, there might be another so + // we should just continue on with the step out and see if our breakpoint + // triggers again. + m_objc_step_through_sp.reset(); + for (BreakpointSP bkpt_sp : m_msgSend_bkpts) { + bkpt_sp->SetEnabled(true); + } + return false; + } + + // If we hit an msgSend breakpoint, then we should queue the step through + // plan: + + if (m_at_msg_send) { + LanguageRuntime *objc_runtime + = GetThread().GetProcess()->GetLanguageRuntime(eLanguageTypeObjC); + // There's no way we could have gotten here without an ObjC language + // runtime. + assert(objc_runtime); + m_objc_step_through_sp = + objc_runtime->GetStepThroughTrampolinePlan(GetThread(), false); + // If we failed to find the target for this dispatch, just keep going and + // let the step out complete. + if (!m_objc_step_through_sp) { + LLDB_LOG(log, "Couldn't find target for message dispatch, continuing."); + return false; + } + // Otherwise push the step through plan and continue. + GetThread().QueueThreadPlan(m_objc_step_through_sp, false); + for (BreakpointSP bkpt_sp : m_msgSend_bkpts) { + bkpt_sp->SetEnabled(false); + } + return false; + } + return true; +} + +bool AppleThreadPlanStepThroughDirectDispatch::MischiefManaged() { + if (IsPlanComplete()) + return true; + return ThreadPlanStepOut::MischiefManaged(); +} diff --git a/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h new file mode 100644 index 000000000000..ba4fb5ace1ed --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h @@ -0,0 +1,122 @@ +//===-- AppleThreadPlanStepThroughObjCTrampoline.h --------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLETHREADPLANSTEPTHROUGHOBJCTRAMPOLINE_H +#define LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLETHREADPLANSTEPTHROUGHOBJCTRAMPOLINE_H + +#include "AppleObjCTrampolineHandler.h" +#include "lldb/Core/Value.h" +#include "lldb/Target/ThreadPlan.h" +#include "lldb/Target/ThreadPlanStepInRange.h" +#include "lldb/Target/ThreadPlanStepOut.h" +#include "lldb/Target/ThreadPlanShouldStopHere.h" +#include "lldb/lldb-enumerations.h" +#include "lldb/lldb-types.h" + +namespace lldb_private { + +class AppleThreadPlanStepThroughObjCTrampoline : public ThreadPlan { +public: + AppleThreadPlanStepThroughObjCTrampoline( + Thread &thread, AppleObjCTrampolineHandler &trampoline_handler, + ValueList &values, lldb::addr_t isa_addr, lldb::addr_t sel_addr, + lldb::addr_t sel_str_addr, llvm::StringRef sel_str); + + ~AppleThreadPlanStepThroughObjCTrampoline() override; + + static bool PreResumeInitializeFunctionCaller(void *myself); + + void GetDescription(Stream *s, lldb::DescriptionLevel level) override; + + bool ValidatePlan(Stream *error) override; + + lldb::StateType GetPlanRunState() override; + + bool ShouldStop(Event *event_ptr) override; + + // The step through code might have to fill in the cache, so it is not safe + // to run only one thread. + bool StopOthers() override { return false; } + + // The base class MischiefManaged does some cleanup - so you have to call it + // in your MischiefManaged derived class. + bool MischiefManaged() override; + + void DidPush() override; + + bool WillStop() override; + +protected: + bool DoPlanExplainsStop(Event *event_ptr) override; + +private: + bool InitializeFunctionCaller(); + + AppleObjCTrampolineHandler &m_trampoline_handler; /// The handler itself. + lldb::addr_t m_args_addr; /// Stores the address for our step through function + /// result structure. + ValueList m_input_values; + lldb::addr_t m_isa_addr; /// isa_addr and sel_addr are the keys we will use to + /// cache the implementation. + lldb::addr_t m_sel_addr; + lldb::ThreadPlanSP m_func_sp; /// This is the function call plan. We fill it + /// at start, then set it to NULL when this plan + /// is done. That way we know to go on to: + lldb::ThreadPlanSP m_run_to_sp; /// The plan that runs to the target. + FunctionCaller *m_impl_function; /// This is a pointer to a impl function that + /// is owned by the client that pushes this + /// plan. + lldb::addr_t m_sel_str_addr; /// If this is not LLDB_INVALID_ADDRESS then it + /// is the address we wrote the selector string + /// to. We need to deallocate it when the + /// function call is done. + std::string m_sel_str; /// This is the string we wrote to memory - we + /// use it for caching, but only if + /// m_sel_str_addr is non-null. +}; + +class AppleThreadPlanStepThroughDirectDispatch: public ThreadPlanStepOut { +public: + AppleThreadPlanStepThroughDirectDispatch(Thread &thread, + AppleObjCTrampolineHandler &handler, + llvm::StringRef dispatch_func_name); + + ~AppleThreadPlanStepThroughDirectDispatch() override; + + void GetDescription(Stream *s, lldb::DescriptionLevel level) override; + + bool ShouldStop(Event *event_ptr) override; + + bool StopOthers() override { return false; } + + bool MischiefManaged() override; + + bool DoWillResume(lldb::StateType resume_state, bool current_plan) override; + + void SetFlagsToDefault() override { + GetFlags().Set(ThreadPlanStepInRange::GetDefaultFlagsValue()); + } + +protected: + bool DoPlanExplainsStop(Event *event_ptr) override; + + AppleObjCTrampolineHandler &m_trampoline_handler; + std::string m_dispatch_func_name; /// Which dispatch function we're stepping + /// through. + lldb::ThreadPlanSP m_objc_step_through_sp; /// When we hit an objc_msgSend, + /// we'll use this plan to get to + /// its target. + std::vector<lldb::BreakpointSP> m_msgSend_bkpts; /// Breakpoints on the objc + /// dispatch functions. + bool m_at_msg_send; /// Are we currently handling an msg_send + +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_APPLEOBJCRUNTIME_APPLETHREADPLANSTEPTHROUGHOBJCTRAMPOLINE_H diff --git a/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/GNUstepObjCRuntime/GNUstepObjCRuntime.cpp b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/GNUstepObjCRuntime/GNUstepObjCRuntime.cpp new file mode 100644 index 000000000000..58b838752be5 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/GNUstepObjCRuntime/GNUstepObjCRuntime.cpp @@ -0,0 +1,226 @@ +//===-- GNUstepObjCRuntime.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 "GNUstepObjCRuntime.h" + +#include "Plugins/TypeSystem/Clang/TypeSystemClang.h" + +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/ValueObject.h" +#include "lldb/Expression/UtilityFunction.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/ArchSpec.h" +#include "lldb/Utility/ConstString.h" + +using namespace lldb; +using namespace lldb_private; + +LLDB_PLUGIN_DEFINE(GNUstepObjCRuntime) + +char GNUstepObjCRuntime::ID = 0; + +void GNUstepObjCRuntime::Initialize() { + PluginManager::RegisterPlugin( + GetPluginNameStatic(), "GNUstep Objective-C Language Runtime - libobjc2", + CreateInstance); +} + +void GNUstepObjCRuntime::Terminate() { + PluginManager::UnregisterPlugin(CreateInstance); +} + +static bool CanModuleBeGNUstepObjCLibrary(const ModuleSP &module_sp, + const llvm::Triple &TT) { + if (!module_sp) + return false; + const FileSpec &module_file_spec = module_sp->GetFileSpec(); + if (!module_file_spec) + return false; + llvm::StringRef filename = module_file_spec.GetFilename().GetStringRef(); + if (TT.isOSBinFormatELF()) + return filename.starts_with("libobjc.so"); + if (TT.isOSWindows()) + return filename == "objc.dll"; + return false; +} + +static bool ScanForGNUstepObjCLibraryCandidate(const ModuleList &modules, + const llvm::Triple &TT) { + std::lock_guard<std::recursive_mutex> guard(modules.GetMutex()); + size_t num_modules = modules.GetSize(); + for (size_t i = 0; i < num_modules; i++) { + auto mod = modules.GetModuleAtIndex(i); + if (CanModuleBeGNUstepObjCLibrary(mod, TT)) + return true; + } + return false; +} + +LanguageRuntime *GNUstepObjCRuntime::CreateInstance(Process *process, + LanguageType language) { + if (language != eLanguageTypeObjC) + return nullptr; + if (!process) + return nullptr; + + Target &target = process->GetTarget(); + const llvm::Triple &TT = target.GetArchitecture().GetTriple(); + if (TT.getVendor() == llvm::Triple::VendorType::Apple) + return nullptr; + + const ModuleList &images = target.GetImages(); + if (!ScanForGNUstepObjCLibraryCandidate(images, TT)) + return nullptr; + + if (TT.isOSBinFormatELF()) { + SymbolContextList eh_pers; + RegularExpression regex("__gnustep_objc[x]*_personality_v[0-9]+"); + images.FindSymbolsMatchingRegExAndType(regex, eSymbolTypeCode, eh_pers); + if (eh_pers.GetSize() == 0) + return nullptr; + } else if (TT.isOSWindows()) { + SymbolContextList objc_mandatory; + images.FindSymbolsWithNameAndType(ConstString("__objc_load"), + eSymbolTypeCode, objc_mandatory); + if (objc_mandatory.GetSize() == 0) + return nullptr; + } + + return new GNUstepObjCRuntime(process); +} + +GNUstepObjCRuntime::~GNUstepObjCRuntime() = default; + +GNUstepObjCRuntime::GNUstepObjCRuntime(Process *process) + : ObjCLanguageRuntime(process), m_objc_module_sp(nullptr) { + ReadObjCLibraryIfNeeded(process->GetTarget().GetImages()); +} + +llvm::Error GNUstepObjCRuntime::GetObjectDescription(Stream &str, + ValueObject &valobj) { + return llvm::createStringError( + "LLDB's GNUStep runtime does not support object description"); +} + +llvm::Error +GNUstepObjCRuntime::GetObjectDescription(Stream &strm, Value &value, + ExecutionContextScope *exe_scope) { + return llvm::createStringError( + "LLDB's GNUStep runtime does not support object description"); +} + +bool GNUstepObjCRuntime::CouldHaveDynamicValue(ValueObject &in_value) { + static constexpr bool check_cxx = false; + static constexpr bool check_objc = true; + return in_value.GetCompilerType().IsPossibleDynamicType(nullptr, check_cxx, + check_objc); +} + +bool GNUstepObjCRuntime::GetDynamicTypeAndAddress( + ValueObject &in_value, DynamicValueType use_dynamic, + TypeAndOrName &class_type_or_name, Address &address, + Value::ValueType &value_type) { + return false; +} + +TypeAndOrName +GNUstepObjCRuntime::FixUpDynamicType(const TypeAndOrName &type_and_or_name, + ValueObject &static_value) { + CompilerType static_type(static_value.GetCompilerType()); + Flags static_type_flags(static_type.GetTypeInfo()); + + TypeAndOrName ret(type_and_or_name); + if (type_and_or_name.HasType()) { + // The type will always be the type of the dynamic object. If our parent's + // type was a pointer, then our type should be a pointer to the type of the + // dynamic object. If a reference, then the original type should be + // okay... + CompilerType orig_type = type_and_or_name.GetCompilerType(); + CompilerType corrected_type = orig_type; + if (static_type_flags.AllSet(eTypeIsPointer)) + corrected_type = orig_type.GetPointerType(); + ret.SetCompilerType(corrected_type); + } else { + // If we are here we need to adjust our dynamic type name to include the + // correct & or * symbol + std::string corrected_name(type_and_or_name.GetName().GetCString()); + if (static_type_flags.AllSet(eTypeIsPointer)) + corrected_name.append(" *"); + // the parent type should be a correctly pointer'ed or referenc'ed type + ret.SetCompilerType(static_type); + ret.SetName(corrected_name.c_str()); + } + return ret; +} + +BreakpointResolverSP +GNUstepObjCRuntime::CreateExceptionResolver(const BreakpointSP &bkpt, + bool catch_bp, bool throw_bp) { + BreakpointResolverSP resolver_sp; + + if (throw_bp) + resolver_sp = std::make_shared<BreakpointResolverName>( + bkpt, "objc_exception_throw", eFunctionNameTypeBase, + eLanguageTypeUnknown, Breakpoint::Exact, 0, eLazyBoolNo); + + return resolver_sp; +} + +llvm::Expected<std::unique_ptr<UtilityFunction>> +GNUstepObjCRuntime::CreateObjectChecker(std::string name, + ExecutionContext &exe_ctx) { + // TODO: This function is supposed to check whether an ObjC selector is + // present for an object. Might be implemented similar as in the Apple V2 + // runtime. + const char *function_template = R"( + extern "C" void + %s(void *$__lldb_arg_obj, void *$__lldb_arg_selector) {} + )"; + + char empty_function_code[2048]; + int len = ::snprintf(empty_function_code, sizeof(empty_function_code), + function_template, name.c_str()); + + assert(len < (int)sizeof(empty_function_code)); + UNUSED_IF_ASSERT_DISABLED(len); + + return GetTargetRef().CreateUtilityFunction(empty_function_code, name, + eLanguageTypeC, exe_ctx); +} + +ThreadPlanSP +GNUstepObjCRuntime::GetStepThroughTrampolinePlan(Thread &thread, + bool stop_others) { + // TODO: Implement this properly to avoid stepping into things like PLT stubs + return nullptr; +} + +void GNUstepObjCRuntime::UpdateISAToDescriptorMapIfNeeded() { + // TODO: Support lazily named and dynamically loaded Objective-C classes +} + +bool GNUstepObjCRuntime::IsModuleObjCLibrary(const ModuleSP &module_sp) { + const llvm::Triple &TT = GetTargetRef().GetArchitecture().GetTriple(); + return CanModuleBeGNUstepObjCLibrary(module_sp, TT); +} + +bool GNUstepObjCRuntime::ReadObjCLibrary(const ModuleSP &module_sp) { + assert(m_objc_module_sp == nullptr && "Check HasReadObjCLibrary() first"); + m_objc_module_sp = module_sp; + + // Right now we don't use this, but we might want to check for debugger + // runtime support symbols like 'gdb_object_getClass' in the future. + return true; +} + +void GNUstepObjCRuntime::ModulesDidLoad(const ModuleList &module_list) { + ReadObjCLibraryIfNeeded(module_list); +} diff --git a/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/GNUstepObjCRuntime/GNUstepObjCRuntime.h b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/GNUstepObjCRuntime/GNUstepObjCRuntime.h new file mode 100644 index 000000000000..de24466ebb00 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/GNUstepObjCRuntime/GNUstepObjCRuntime.h @@ -0,0 +1,111 @@ +//===-- GNUstepObjCRuntime.h ------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_GNUSTEPOBJCRUNTIME_GNUSTEPOBJCRUNTIME_H +#define LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_GNUSTEPOBJCRUNTIME_GNUSTEPOBJCRUNTIME_H + +#include "lldb/Target/LanguageRuntime.h" +#include "lldb/lldb-private.h" + +#include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h" + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" + +#include <optional> + +namespace lldb_private { + +class GNUstepObjCRuntime : public lldb_private::ObjCLanguageRuntime { +public: + ~GNUstepObjCRuntime() override; + + // + // PluginManager, PluginInterface and LLVM RTTI implementation + // + + static char ID; + + static void Initialize(); + + static void Terminate(); + + static lldb_private::LanguageRuntime * + CreateInstance(Process *process, lldb::LanguageType language); + + static llvm::StringRef GetPluginNameStatic() { + return "gnustep-objc-libobjc2"; + } + + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } + + void ModulesDidLoad(const ModuleList &module_list) override; + + bool isA(const void *ClassID) const override { + return ClassID == &ID || ObjCLanguageRuntime::isA(ClassID); + } + + static bool classof(const LanguageRuntime *runtime) { + return runtime->isA(&ID); + } + + // + // LanguageRuntime implementation + // + llvm::Error GetObjectDescription(Stream &str, Value &value, + ExecutionContextScope *exe_scope) override; + + llvm::Error GetObjectDescription(Stream &str, ValueObject &object) override; + + bool CouldHaveDynamicValue(ValueObject &in_value) override; + + bool GetDynamicTypeAndAddress(ValueObject &in_value, + lldb::DynamicValueType use_dynamic, + TypeAndOrName &class_type_or_name, + Address &address, + Value::ValueType &value_type) override; + + TypeAndOrName FixUpDynamicType(const TypeAndOrName &type_and_or_name, + ValueObject &static_value) override; + + lldb::BreakpointResolverSP + CreateExceptionResolver(const lldb::BreakpointSP &bkpt, bool catch_bp, + bool throw_bp) override; + + lldb::ThreadPlanSP GetStepThroughTrampolinePlan(Thread &thread, + bool stop_others) override; + + // + // ObjCLanguageRuntime implementation + // + + bool IsModuleObjCLibrary(const lldb::ModuleSP &module_sp) override; + + bool ReadObjCLibrary(const lldb::ModuleSP &module_sp) override; + + bool HasReadObjCLibrary() override { return m_objc_module_sp != nullptr; } + + llvm::Expected<std::unique_ptr<UtilityFunction>> + CreateObjectChecker(std::string name, ExecutionContext &exe_ctx) override; + + ObjCRuntimeVersions GetRuntimeVersion() const override { + return ObjCRuntimeVersions::eGNUstep_libobjc2; + } + + void UpdateISAToDescriptorMapIfNeeded() override; + +protected: + // Call CreateInstance instead. + GNUstepObjCRuntime(Process *process); + + lldb::ModuleSP m_objc_module_sp; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_GNUSTEPOBJCRUNTIME_GNUSTEPOBJCRUNTIME_H diff --git a/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.cpp b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.cpp new file mode 100644 index 000000000000..a812ffba7a64 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.cpp @@ -0,0 +1,459 @@ +//===-- ObjCLanguageRuntime.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 "clang/AST/Type.h" + +#include "ObjCLanguageRuntime.h" + +#include "Plugins/TypeSystem/Clang/TypeSystemClang.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/ValueObject.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Symbol/SymbolFile.h" +#include "lldb/Symbol/Type.h" +#include "lldb/Symbol/TypeList.h" +#include "lldb/Symbol/Variable.h" +#include "lldb/Target/ABI.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Timer.h" + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/DJB.h" +#include <optional> + +using namespace lldb; +using namespace lldb_private; + +char ObjCLanguageRuntime::ID = 0; + +// Destructor +ObjCLanguageRuntime::~ObjCLanguageRuntime() = default; + +ObjCLanguageRuntime::ObjCLanguageRuntime(Process *process) + : LanguageRuntime(process), m_impl_cache(), m_impl_str_cache(), + m_has_new_literals_and_indexing(eLazyBoolCalculate), + m_isa_to_descriptor(), m_hash_to_isa_map(), m_type_size_cache(), + m_isa_to_descriptor_stop_id(UINT32_MAX), m_complete_class_cache(), + m_negative_complete_class_cache() {} + +bool ObjCLanguageRuntime::IsAllowedRuntimeValue(ConstString name) { + static ConstString g_self = ConstString("self"); + static ConstString g_cmd = ConstString("_cmd"); + return name == g_self || name == g_cmd; +} + +bool ObjCLanguageRuntime::AddClass(ObjCISA isa, + const ClassDescriptorSP &descriptor_sp, + const char *class_name) { + if (isa != 0) { + m_isa_to_descriptor[isa] = descriptor_sp; + // class_name is assumed to be valid + m_hash_to_isa_map.insert(std::make_pair(llvm::djbHash(class_name), isa)); + return true; + } + return false; +} + +void ObjCLanguageRuntime::AddToMethodCache(lldb::addr_t class_addr, + lldb::addr_t selector, + lldb::addr_t impl_addr) { + Log *log = GetLog(LLDBLog::Step); + if (log) { + LLDB_LOGF(log, + "Caching: class 0x%" PRIx64 " selector 0x%" PRIx64 + " implementation 0x%" PRIx64 ".", + class_addr, selector, impl_addr); + } + m_impl_cache.insert(std::pair<ClassAndSel, lldb::addr_t>( + ClassAndSel(class_addr, selector), impl_addr)); +} + +void ObjCLanguageRuntime::AddToMethodCache(lldb::addr_t class_addr, + llvm::StringRef sel_str, + lldb::addr_t impl_addr) { + Log *log = GetLog(LLDBLog::Step); + + LLDB_LOG(log, "Caching: class {0} selector {1} implementation {2}.", + class_addr, sel_str, impl_addr); + + m_impl_str_cache.insert(std::pair<ClassAndSelStr, lldb::addr_t>( + ClassAndSelStr(class_addr, sel_str), impl_addr)); +} + +lldb::addr_t ObjCLanguageRuntime::LookupInMethodCache(lldb::addr_t class_addr, + lldb::addr_t selector) { + MsgImplMap::iterator pos, end = m_impl_cache.end(); + pos = m_impl_cache.find(ClassAndSel(class_addr, selector)); + if (pos != end) + return (*pos).second; + return LLDB_INVALID_ADDRESS; +} + +lldb::addr_t ObjCLanguageRuntime::LookupInMethodCache(lldb::addr_t class_addr, + llvm::StringRef sel_str) { + MsgImplStrMap::iterator pos, end = m_impl_str_cache.end(); + pos = m_impl_str_cache.find(ClassAndSelStr(class_addr, sel_str)); + if (pos != end) + return (*pos).second; + return LLDB_INVALID_ADDRESS; +} + +lldb::TypeSP +ObjCLanguageRuntime::LookupInCompleteClassCache(ConstString &name) { + CompleteClassMap::iterator complete_class_iter = + m_complete_class_cache.find(name); + + if (complete_class_iter != m_complete_class_cache.end()) { + // Check the weak pointer to make sure the type hasn't been unloaded + TypeSP complete_type_sp(complete_class_iter->second.lock()); + + if (complete_type_sp) + return complete_type_sp; + else + m_complete_class_cache.erase(name); + } + + if (m_negative_complete_class_cache.count(name) > 0) + return TypeSP(); + + const ModuleList &modules = m_process->GetTarget().GetImages(); + + SymbolContextList sc_list; + modules.FindSymbolsWithNameAndType(name, eSymbolTypeObjCClass, sc_list); + const size_t matching_symbols = sc_list.GetSize(); + + if (matching_symbols) { + SymbolContext sc; + + sc_list.GetContextAtIndex(0, sc); + + ModuleSP module_sp(sc.module_sp); + + if (!module_sp) + return TypeSP(); + + TypeQuery query(name.GetStringRef(), TypeQueryOptions::e_exact_match); + TypeResults results; + module_sp->FindTypes(query, results); + for (const TypeSP &type_sp : results.GetTypeMap().Types()) { + if (TypeSystemClang::IsObjCObjectOrInterfaceType( + type_sp->GetForwardCompilerType())) { + if (TypePayloadClang(type_sp->GetPayload()).IsCompleteObjCClass()) { + m_complete_class_cache[name] = type_sp; + return type_sp; + } + } + } + } + m_negative_complete_class_cache.insert(name); + return TypeSP(); +} + +size_t ObjCLanguageRuntime::GetByteOffsetForIvar(CompilerType &parent_qual_type, + const char *ivar_name) { + return LLDB_INVALID_IVAR_OFFSET; +} + +bool ObjCLanguageRuntime::ClassDescriptor::IsPointerValid( + lldb::addr_t value, uint32_t ptr_size, bool allow_NULLs, bool allow_tagged, + bool check_version_specific) const { + if (!value) + return allow_NULLs; + if ((value % 2) == 1 && allow_tagged) + return true; + if ((value % ptr_size) == 0) + return (check_version_specific ? CheckPointer(value, ptr_size) : true); + else + return false; +} + +ObjCLanguageRuntime::ObjCISA +ObjCLanguageRuntime::GetISA(ConstString name) { + ISAToDescriptorIterator pos = GetDescriptorIterator(name); + if (pos != m_isa_to_descriptor.end()) + return pos->first; + return 0; +} + +ObjCLanguageRuntime::ISAToDescriptorIterator +ObjCLanguageRuntime::GetDescriptorIterator(ConstString name) { + ISAToDescriptorIterator end = m_isa_to_descriptor.end(); + + if (name) { + UpdateISAToDescriptorMap(); + if (m_hash_to_isa_map.empty()) { + // No name hashes were provided, we need to just linearly power through + // the names and find a match + for (ISAToDescriptorIterator pos = m_isa_to_descriptor.begin(); + pos != end; ++pos) { + if (pos->second->GetClassName() == name) + return pos; + } + } else { + // Name hashes were provided, so use them to efficiently lookup name to + // isa/descriptor + const uint32_t name_hash = llvm::djbHash(name.GetStringRef()); + std::pair<HashToISAIterator, HashToISAIterator> range = + m_hash_to_isa_map.equal_range(name_hash); + for (HashToISAIterator range_pos = range.first; range_pos != range.second; + ++range_pos) { + ISAToDescriptorIterator pos = + m_isa_to_descriptor.find(range_pos->second); + if (pos != m_isa_to_descriptor.end()) { + if (pos->second->GetClassName() == name) + return pos; + } + } + } + } + return end; +} + +std::pair<ObjCLanguageRuntime::ISAToDescriptorIterator, + ObjCLanguageRuntime::ISAToDescriptorIterator> +ObjCLanguageRuntime::GetDescriptorIteratorPair(bool update_if_needed) { + if (update_if_needed) + UpdateISAToDescriptorMapIfNeeded(); + + return std::pair<ObjCLanguageRuntime::ISAToDescriptorIterator, + ObjCLanguageRuntime::ISAToDescriptorIterator>( + m_isa_to_descriptor.begin(), m_isa_to_descriptor.end()); +} + +void ObjCLanguageRuntime::ReadObjCLibraryIfNeeded( + const ModuleList &module_list) { + if (!HasReadObjCLibrary()) { + std::lock_guard<std::recursive_mutex> guard(module_list.GetMutex()); + + size_t num_modules = module_list.GetSize(); + for (size_t i = 0; i < num_modules; i++) { + auto mod = module_list.GetModuleAtIndex(i); + if (IsModuleObjCLibrary(mod)) { + ReadObjCLibrary(mod); + break; + } + } + } +} + +ObjCLanguageRuntime::ObjCISA +ObjCLanguageRuntime::GetParentClass(ObjCLanguageRuntime::ObjCISA isa) { + ClassDescriptorSP objc_class_sp(GetClassDescriptorFromISA(isa)); + if (objc_class_sp) { + ClassDescriptorSP objc_super_class_sp(objc_class_sp->GetSuperclass()); + if (objc_super_class_sp) + return objc_super_class_sp->GetISA(); + } + return 0; +} + +ObjCLanguageRuntime::ClassDescriptorSP +ObjCLanguageRuntime::GetClassDescriptorFromClassName( + ConstString class_name) { + ISAToDescriptorIterator pos = GetDescriptorIterator(class_name); + if (pos != m_isa_to_descriptor.end()) + return pos->second; + return ClassDescriptorSP(); +} + +ObjCLanguageRuntime::ClassDescriptorSP +ObjCLanguageRuntime::GetClassDescriptor(ValueObject &valobj) { + ClassDescriptorSP objc_class_sp; + // if we get an invalid VO (which might still happen when playing around with + // pointers returned by the expression parser, don't consider this a valid + // ObjC object) + if (valobj.GetCompilerType().IsValid()) { + addr_t isa_pointer = valobj.GetPointerValue(); + if (isa_pointer != LLDB_INVALID_ADDRESS) { + ExecutionContext exe_ctx(valobj.GetExecutionContextRef()); + + Process *process = exe_ctx.GetProcessPtr(); + if (process) { + Status error; + ObjCISA isa = process->ReadPointerFromMemory(isa_pointer, error); + if (isa != LLDB_INVALID_ADDRESS) + objc_class_sp = GetClassDescriptorFromISA(isa); + } + } + } + return objc_class_sp; +} + +ObjCLanguageRuntime::ClassDescriptorSP +ObjCLanguageRuntime::GetNonKVOClassDescriptor(ValueObject &valobj) { + ObjCLanguageRuntime::ClassDescriptorSP objc_class_sp( + GetClassDescriptor(valobj)); + if (objc_class_sp) { + if (!objc_class_sp->IsKVO()) + return objc_class_sp; + + ClassDescriptorSP non_kvo_objc_class_sp(objc_class_sp->GetSuperclass()); + if (non_kvo_objc_class_sp && non_kvo_objc_class_sp->IsValid()) + return non_kvo_objc_class_sp; + } + return ClassDescriptorSP(); +} + +ObjCLanguageRuntime::ClassDescriptorSP +ObjCLanguageRuntime::GetClassDescriptorFromISA(ObjCISA isa) { + if (isa) { + UpdateISAToDescriptorMap(); + + ObjCLanguageRuntime::ISAToDescriptorIterator pos = + m_isa_to_descriptor.find(isa); + if (pos != m_isa_to_descriptor.end()) + return pos->second; + + if (ABISP abi_sp = m_process->GetABI()) { + pos = m_isa_to_descriptor.find(abi_sp->FixCodeAddress(isa)); + if (pos != m_isa_to_descriptor.end()) + return pos->second; + } + } + return ClassDescriptorSP(); +} + +ObjCLanguageRuntime::ClassDescriptorSP +ObjCLanguageRuntime::GetNonKVOClassDescriptor(ObjCISA isa) { + if (isa) { + ClassDescriptorSP objc_class_sp = GetClassDescriptorFromISA(isa); + if (objc_class_sp && objc_class_sp->IsValid()) { + if (!objc_class_sp->IsKVO()) + return objc_class_sp; + + ClassDescriptorSP non_kvo_objc_class_sp(objc_class_sp->GetSuperclass()); + if (non_kvo_objc_class_sp && non_kvo_objc_class_sp->IsValid()) + return non_kvo_objc_class_sp; + } + } + return ClassDescriptorSP(); +} + +CompilerType +ObjCLanguageRuntime::EncodingToType::RealizeType(const char *name, + bool for_expression) { + if (m_scratch_ast_ctx_sp) + return RealizeType(*m_scratch_ast_ctx_sp, name, for_expression); + return CompilerType(); +} + +ObjCLanguageRuntime::EncodingToType::~EncodingToType() = default; + +ObjCLanguageRuntime::EncodingToTypeSP ObjCLanguageRuntime::GetEncodingToType() { + return nullptr; +} + +std::optional<uint64_t> +ObjCLanguageRuntime::GetTypeBitSize(const CompilerType &compiler_type) { + void *opaque_ptr = compiler_type.GetOpaqueQualType(); + uint64_t cached_size = m_type_size_cache.Lookup(opaque_ptr); + if (cached_size > 0) + return cached_size; + + ClassDescriptorSP class_descriptor_sp = + GetClassDescriptorFromClassName(compiler_type.GetTypeName()); + if (!class_descriptor_sp) + return {}; + + int32_t max_offset = INT32_MIN; + uint64_t sizeof_max = 0; + bool found = false; + + for (size_t idx = 0; idx < class_descriptor_sp->GetNumIVars(); idx++) { + const auto &ivar = class_descriptor_sp->GetIVarAtIndex(idx); + int32_t cur_offset = ivar.m_offset; + if (cur_offset > max_offset) { + max_offset = cur_offset; + sizeof_max = ivar.m_size; + found = true; + } + } + + uint64_t size = 8 * (max_offset + sizeof_max); + if (found && size > 0) { + m_type_size_cache.Insert(opaque_ptr, size); + return size; + } + + return {}; +} + +lldb::BreakpointPreconditionSP +ObjCLanguageRuntime::GetBreakpointExceptionPrecondition(LanguageType language, + bool throw_bp) { + if (language != eLanguageTypeObjC) + return lldb::BreakpointPreconditionSP(); + if (!throw_bp) + return lldb::BreakpointPreconditionSP(); + BreakpointPreconditionSP precondition_sp( + new ObjCLanguageRuntime::ObjCExceptionPrecondition()); + return precondition_sp; +} + +// Exception breakpoint Precondition class for ObjC: +void ObjCLanguageRuntime::ObjCExceptionPrecondition::AddClassName( + const char *class_name) { + m_class_names.insert(class_name); +} + +ObjCLanguageRuntime::ObjCExceptionPrecondition::ObjCExceptionPrecondition() = + default; + +bool ObjCLanguageRuntime::ObjCExceptionPrecondition::EvaluatePrecondition( + StoppointCallbackContext &context) { + return true; +} + +void ObjCLanguageRuntime::ObjCExceptionPrecondition::GetDescription( + Stream &stream, lldb::DescriptionLevel level) {} + +Status ObjCLanguageRuntime::ObjCExceptionPrecondition::ConfigurePrecondition( + Args &args) { + Status error; + if (args.GetArgumentCount() > 0) + error.SetErrorString( + "The ObjC Exception breakpoint doesn't support extra options."); + return error; +} + +std::optional<CompilerType> +ObjCLanguageRuntime::GetRuntimeType(CompilerType base_type) { + CompilerType class_type; + bool is_pointer_type = false; + + if (TypeSystemClang::IsObjCObjectPointerType(base_type, &class_type)) + is_pointer_type = true; + else if (TypeSystemClang::IsObjCObjectOrInterfaceType(base_type)) + class_type = base_type; + else + return std::nullopt; + + if (!class_type) + return std::nullopt; + + ConstString class_name(class_type.GetTypeName()); + if (!class_name) + return std::nullopt; + + TypeSP complete_objc_class_type_sp = LookupInCompleteClassCache(class_name); + if (!complete_objc_class_type_sp) + return std::nullopt; + + CompilerType complete_class( + complete_objc_class_type_sp->GetFullCompilerType()); + if (complete_class.GetCompleteType()) { + if (is_pointer_type) + return complete_class.GetPointerType(); + else + return complete_class; + } + + return std::nullopt; +} diff --git a/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h new file mode 100644 index 000000000000..ffe9725fa682 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h @@ -0,0 +1,480 @@ +//===-- ObjCLanguageRuntime.h -----------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_OBJCLANGUAGERUNTIME_H +#define LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_OBJCLANGUAGERUNTIME_H + +#include <functional> +#include <map> +#include <memory> +#include <optional> +#include <unordered_set> + +#include "llvm/Support/Casting.h" + +#include "lldb/Breakpoint/BreakpointPrecondition.h" +#include "lldb/Core/PluginInterface.h" +#include "lldb/Symbol/CompilerType.h" +#include "lldb/Symbol/Type.h" +#include "lldb/Target/LanguageRuntime.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/ThreadSafeDenseMap.h" +#include "lldb/lldb-enumerations.h" +#include "lldb/lldb-private.h" + +class CommandObjectObjC_ClassTable_Dump; + +namespace lldb_private { + +class TypeSystemClang; +class UtilityFunction; + +class ObjCLanguageRuntime : public LanguageRuntime { +public: + enum class ObjCRuntimeVersions { + eObjC_VersionUnknown = 0, + eAppleObjC_V1 = 1, + eAppleObjC_V2 = 2, + eGNUstep_libobjc2 = 3, + }; + + typedef lldb::addr_t ObjCISA; + + class ClassDescriptor; + typedef std::shared_ptr<ClassDescriptor> ClassDescriptorSP; + + // the information that we want to support retrieving from an ObjC class this + // needs to be pure virtual since there are at least 2 different + // implementations of the runtime, and more might come + class ClassDescriptor { + public: + ClassDescriptor() : m_type_wp() {} + + virtual ~ClassDescriptor() = default; + + virtual ConstString GetClassName() = 0; + + virtual ClassDescriptorSP GetSuperclass() = 0; + + virtual ClassDescriptorSP GetMetaclass() const = 0; + + // virtual if any implementation has some other version-specific rules but + // for the known v1/v2 this is all that needs to be done + virtual bool IsKVO() { + if (m_is_kvo == eLazyBoolCalculate) { + const char *class_name = GetClassName().AsCString(); + if (class_name && *class_name) + m_is_kvo = + (LazyBool)(strstr(class_name, "NSKVONotifying_") == class_name); + } + return (m_is_kvo == eLazyBoolYes); + } + + // virtual if any implementation has some other version-specific rules but + // for the known v1/v2 this is all that needs to be done + virtual bool IsCFType() { + if (m_is_cf == eLazyBoolCalculate) { + const char *class_name = GetClassName().AsCString(); + if (class_name && *class_name) + m_is_cf = (LazyBool)(strcmp(class_name, "__NSCFType") == 0 || + strcmp(class_name, "NSCFType") == 0); + } + return (m_is_cf == eLazyBoolYes); + } + + /// Determine whether this class is implemented in Swift. + virtual lldb::LanguageType GetImplementationLanguage() const { + return lldb::eLanguageTypeObjC; + } + + virtual bool IsValid() = 0; + + /// There are two routines in the ObjC runtime that tagged pointer clients + /// can call to get the value from their tagged pointer, one that retrieves + /// it as an unsigned value and one a signed value. These two + /// GetTaggedPointerInfo methods mirror those two ObjC runtime calls. + /// @{ + virtual bool GetTaggedPointerInfo(uint64_t *info_bits = nullptr, + uint64_t *value_bits = nullptr, + uint64_t *payload = nullptr) = 0; + + virtual bool GetTaggedPointerInfoSigned(uint64_t *info_bits = nullptr, + int64_t *value_bits = nullptr, + uint64_t *payload = nullptr) = 0; + /// @} + + virtual uint64_t GetInstanceSize() = 0; + + // use to implement version-specific additional constraints on pointers + virtual bool CheckPointer(lldb::addr_t value, uint32_t ptr_size) const { + return true; + } + + virtual ObjCISA GetISA() = 0; + + // This should return true iff the interface could be completed + virtual bool + Describe(std::function<void(ObjCISA)> const &superclass_func, + std::function<bool(const char *, const char *)> const + &instance_method_func, + std::function<bool(const char *, const char *)> const + &class_method_func, + std::function<bool(const char *, const char *, lldb::addr_t, + uint64_t)> const &ivar_func) const { + return false; + } + + lldb::TypeSP GetType() { return m_type_wp.lock(); } + + void SetType(const lldb::TypeSP &type_sp) { m_type_wp = type_sp; } + + struct iVarDescriptor { + ConstString m_name; + CompilerType m_type; + uint64_t m_size; + int32_t m_offset; + }; + + virtual size_t GetNumIVars() { return 0; } + + virtual iVarDescriptor GetIVarAtIndex(size_t idx) { + return iVarDescriptor(); + } + + protected: + bool IsPointerValid(lldb::addr_t value, uint32_t ptr_size, + bool allow_NULLs = false, bool allow_tagged = false, + bool check_version_specific = false) const; + + private: + LazyBool m_is_kvo = eLazyBoolCalculate; + LazyBool m_is_cf = eLazyBoolCalculate; + lldb::TypeWP m_type_wp; + }; + + class EncodingToType { + public: + virtual ~EncodingToType(); + + virtual CompilerType RealizeType(TypeSystemClang &ast_ctx, const char *name, + bool for_expression) = 0; + virtual CompilerType RealizeType(const char *name, bool for_expression); + + protected: + std::shared_ptr<TypeSystemClang> m_scratch_ast_ctx_sp; + }; + + class ObjCExceptionPrecondition : public BreakpointPrecondition { + public: + ObjCExceptionPrecondition(); + + ~ObjCExceptionPrecondition() override = default; + + bool EvaluatePrecondition(StoppointCallbackContext &context) override; + void GetDescription(Stream &stream, lldb::DescriptionLevel level) override; + Status ConfigurePrecondition(Args &args) override; + + protected: + void AddClassName(const char *class_name); + + private: + std::unordered_set<std::string> m_class_names; + }; + + static lldb::BreakpointPreconditionSP + GetBreakpointExceptionPrecondition(lldb::LanguageType language, + bool throw_bp); + + class TaggedPointerVendor { + public: + virtual ~TaggedPointerVendor() = default; + + virtual bool IsPossibleTaggedPointer(lldb::addr_t ptr) = 0; + + virtual ObjCLanguageRuntime::ClassDescriptorSP + GetClassDescriptor(lldb::addr_t ptr) = 0; + + protected: + TaggedPointerVendor() = default; + + private: + TaggedPointerVendor(const TaggedPointerVendor &) = delete; + const TaggedPointerVendor &operator=(const TaggedPointerVendor &) = delete; + }; + + ~ObjCLanguageRuntime() override; + + static char ID; + + bool isA(const void *ClassID) const override { + return ClassID == &ID || LanguageRuntime::isA(ClassID); + } + + static bool classof(const LanguageRuntime *runtime) { + return runtime->isA(&ID); + } + + static ObjCLanguageRuntime *Get(Process &process) { + return llvm::cast_or_null<ObjCLanguageRuntime>( + process.GetLanguageRuntime(lldb::eLanguageTypeObjC)); + } + + virtual TaggedPointerVendor *GetTaggedPointerVendor() { return nullptr; } + + typedef std::shared_ptr<EncodingToType> EncodingToTypeSP; + + virtual EncodingToTypeSP GetEncodingToType(); + + virtual ClassDescriptorSP GetClassDescriptor(ValueObject &in_value); + + ClassDescriptorSP GetNonKVOClassDescriptor(ValueObject &in_value); + + virtual ClassDescriptorSP + GetClassDescriptorFromClassName(ConstString class_name); + + virtual ClassDescriptorSP GetClassDescriptorFromISA(ObjCISA isa); + + ClassDescriptorSP GetNonKVOClassDescriptor(ObjCISA isa); + + lldb::LanguageType GetLanguageType() const override { + return lldb::eLanguageTypeObjC; + } + + virtual bool IsModuleObjCLibrary(const lldb::ModuleSP &module_sp) = 0; + + virtual bool ReadObjCLibrary(const lldb::ModuleSP &module_sp) = 0; + + virtual bool HasReadObjCLibrary() = 0; + + // These two methods actually use different caches. The only time we'll + // cache a sel_str is if we found a "selector specific stub" for the selector + // and conversely we only add to the SEL cache if we saw a regular dispatch. + lldb::addr_t LookupInMethodCache(lldb::addr_t class_addr, lldb::addr_t sel); + lldb::addr_t LookupInMethodCache(lldb::addr_t class_addr, + llvm::StringRef sel_str); + + void AddToMethodCache(lldb::addr_t class_addr, lldb::addr_t sel, + lldb::addr_t impl_addr); + + void AddToMethodCache(lldb::addr_t class_addr, llvm::StringRef sel_str, + lldb::addr_t impl_addr); + + TypeAndOrName LookupInClassNameCache(lldb::addr_t class_addr); + + void AddToClassNameCache(lldb::addr_t class_addr, const char *name, + lldb::TypeSP type_sp); + + void AddToClassNameCache(lldb::addr_t class_addr, + const TypeAndOrName &class_or_type_name); + + lldb::TypeSP LookupInCompleteClassCache(ConstString &name); + + std::optional<CompilerType> GetRuntimeType(CompilerType base_type) override; + + virtual llvm::Expected<std::unique_ptr<UtilityFunction>> + CreateObjectChecker(std::string name, ExecutionContext &exe_ctx) = 0; + + virtual ObjCRuntimeVersions GetRuntimeVersion() const { + return ObjCRuntimeVersions::eObjC_VersionUnknown; + } + + bool IsValidISA(ObjCISA isa) { + UpdateISAToDescriptorMap(); + return m_isa_to_descriptor.count(isa) > 0; + } + + virtual void UpdateISAToDescriptorMapIfNeeded() = 0; + + void UpdateISAToDescriptorMap() { + if (m_process && m_process->GetStopID() != m_isa_to_descriptor_stop_id) { + UpdateISAToDescriptorMapIfNeeded(); + } + } + + virtual ObjCISA GetISA(ConstString name); + + virtual ObjCISA GetParentClass(ObjCISA isa); + + // Finds the byte offset of the child_type ivar in parent_type. If it can't + // find the offset, returns LLDB_INVALID_IVAR_OFFSET. + + virtual size_t GetByteOffsetForIvar(CompilerType &parent_qual_type, + const char *ivar_name); + + bool HasNewLiteralsAndIndexing() { + if (m_has_new_literals_and_indexing == eLazyBoolCalculate) { + if (CalculateHasNewLiteralsAndIndexing()) + m_has_new_literals_and_indexing = eLazyBoolYes; + else + m_has_new_literals_and_indexing = eLazyBoolNo; + } + + return (m_has_new_literals_and_indexing == eLazyBoolYes); + } + + void SymbolsDidLoad(const ModuleList &module_list) override { + m_negative_complete_class_cache.clear(); + } + + std::optional<uint64_t> + GetTypeBitSize(const CompilerType &compiler_type) override; + + /// Check whether the name is "self" or "_cmd" and should show up in + /// "frame variable". + bool IsAllowedRuntimeValue(ConstString name) override; + +protected: + // Classes that inherit from ObjCLanguageRuntime can see and modify these + ObjCLanguageRuntime(Process *process); + + virtual bool CalculateHasNewLiteralsAndIndexing() { return false; } + + bool ISAIsCached(ObjCISA isa) const { + return m_isa_to_descriptor.find(isa) != m_isa_to_descriptor.end(); + } + + bool AddClass(ObjCISA isa, const ClassDescriptorSP &descriptor_sp) { + if (isa != 0) { + m_isa_to_descriptor[isa] = descriptor_sp; + return true; + } + return false; + } + + bool AddClass(ObjCISA isa, const ClassDescriptorSP &descriptor_sp, + const char *class_name); + + bool AddClass(ObjCISA isa, const ClassDescriptorSP &descriptor_sp, + uint32_t class_name_hash) { + if (isa != 0) { + m_isa_to_descriptor[isa] = descriptor_sp; + m_hash_to_isa_map.insert(std::make_pair(class_name_hash, isa)); + return true; + } + return false; + } + +private: + // We keep two maps of <Class,Selector>->Implementation so we don't have + // to call the resolver function over and over. + // The first comes from regular obj_msgSend type dispatch, and maps the + // class + uniqued SEL value to an implementation. + // The second comes from the "selector-specific stubs", which are always + // of the form _objc_msgSend$SelectorName, so we don't know the uniqued + // selector, only the string name. + + // FIXME: We need to watch for the loading of Protocols, and flush the cache + // for any + // class that we see so changed. + + struct ClassAndSel { + ClassAndSel() = default; + + ClassAndSel(lldb::addr_t in_class_addr, lldb::addr_t in_sel_addr) + : class_addr(in_class_addr), sel_addr(in_sel_addr) {} + + bool operator==(const ClassAndSel &rhs) { + if (class_addr == rhs.class_addr && sel_addr == rhs.sel_addr) + return true; + else + return false; + } + + bool operator<(const ClassAndSel &rhs) const { + if (class_addr < rhs.class_addr) + return true; + else if (class_addr > rhs.class_addr) + return false; + else { + if (sel_addr < rhs.sel_addr) + return true; + else + return false; + } + } + + lldb::addr_t class_addr = LLDB_INVALID_ADDRESS; + lldb::addr_t sel_addr = LLDB_INVALID_ADDRESS; + }; + + struct ClassAndSelStr { + ClassAndSelStr() = default; + + ClassAndSelStr(lldb::addr_t in_class_addr, llvm::StringRef in_sel_name) + : class_addr(in_class_addr), sel_name(in_sel_name) {} + + bool operator==(const ClassAndSelStr &rhs) { + return class_addr == rhs.class_addr && sel_name == rhs.sel_name; + } + + bool operator<(const ClassAndSelStr &rhs) const { + if (class_addr < rhs.class_addr) + return true; + else if (class_addr > rhs.class_addr) + return false; + else + return ConstString::Compare(sel_name, rhs.sel_name); + } + + lldb::addr_t class_addr = LLDB_INVALID_ADDRESS; + ConstString sel_name; + }; + + typedef std::map<ClassAndSel, lldb::addr_t> MsgImplMap; + typedef std::map<ClassAndSelStr, lldb::addr_t> MsgImplStrMap; + typedef std::map<ObjCISA, ClassDescriptorSP> ISAToDescriptorMap; + typedef std::multimap<uint32_t, ObjCISA> HashToISAMap; + typedef ISAToDescriptorMap::iterator ISAToDescriptorIterator; + typedef HashToISAMap::iterator HashToISAIterator; + typedef ThreadSafeDenseMap<void *, uint64_t> TypeSizeCache; + + MsgImplMap m_impl_cache; + MsgImplStrMap m_impl_str_cache; + LazyBool m_has_new_literals_and_indexing; + ISAToDescriptorMap m_isa_to_descriptor; + HashToISAMap m_hash_to_isa_map; + TypeSizeCache m_type_size_cache; + +protected: + uint32_t m_isa_to_descriptor_stop_id; + + typedef std::map<ConstString, lldb::TypeWP> CompleteClassMap; + CompleteClassMap m_complete_class_cache; + + struct ConstStringSetHelpers { + size_t operator()(ConstString arg) const // for hashing + { + return (size_t)arg.GetCString(); + } + bool operator()(ConstString arg1, + ConstString arg2) const // for equality + { + return arg1.operator==(arg2); + } + }; + typedef std::unordered_set<ConstString, ConstStringSetHelpers, + ConstStringSetHelpers> + CompleteClassSet; + CompleteClassSet m_negative_complete_class_cache; + + ISAToDescriptorIterator GetDescriptorIterator(ConstString name); + + friend class ::CommandObjectObjC_ClassTable_Dump; + + std::pair<ISAToDescriptorIterator, ISAToDescriptorIterator> + GetDescriptorIteratorPair(bool update_if_needed = true); + + void ReadObjCLibraryIfNeeded(const ModuleList &module_list); + + ObjCLanguageRuntime(const ObjCLanguageRuntime &) = delete; + const ObjCLanguageRuntime &operator=(const ObjCLanguageRuntime &) = delete; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_OBJCLANGUAGERUNTIME_H |