diff options
author | Ed Maste <emaste@FreeBSD.org> | 2016-01-03 20:36:46 +0000 |
---|---|---|
committer | Ed Maste <emaste@FreeBSD.org> | 2016-01-03 20:36:46 +0000 |
commit | 3bd2e91faeb9eeec1aae82c64a3253afff551cfd (patch) | |
tree | 5a3e741e28a6d673fdec1bfcc97391459e65b9d0 | |
parent | e81d9d49145e432d917eea3a70d2ae74dcad1d89 (diff) | |
download | src-test2-3bd2e91faeb9eeec1aae82c64a3253afff551cfd.tar.gz src-test2-3bd2e91faeb9eeec1aae82c64a3253afff551cfd.zip |
Notes
16 files changed, 7781 insertions, 0 deletions
diff --git a/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp new file mode 100644 index 000000000000..711d324d8aa7 --- /dev/null +++ b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp @@ -0,0 +1,573 @@ +//===-- AppleObjCClassDescriptorV2.cpp -----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "AppleObjCClassDescriptorV2.h" + +#include "lldb/Core/Log.h" +#include "lldb/Expression/FunctionCaller.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.reset(new objc_class_t); + + bool ret = objc_class->Read (process, m_objc_class_ptr); + + if (!ret) + objc_class.reset(); + + return ret; +} + +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'); + Error 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 & ~(lldb::addr_t)3; + + 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'); + Error 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); + 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); + + 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'); + Error 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(); + + Error 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.reset(new class_rw_t); + + if (!class_rw->Read(process, objc_class.m_data_ptr)) + { + class_rw.reset(); + return false; + } + + class_ro.reset(new class_ro_t); + + if (!class_ro->Read(process, class_rw->m_ro_ptr)) + { + class_rw.reset(); + class_ro.reset(); + return false; + } + } + else + { + class_ro.reset(new 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'); + Error 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) & ~(uint32_t)3; + m_count = extractor.GetU32_unchecked(&cursor); + m_first_ptr = addr + cursor; + + return true; +} + +bool +ClassDescriptorV2::method_t::Read(Process *process, lldb::addr_t addr) +{ + size_t size = GetSize(process); + + DataBufferHeap buffer (size, '\0'); + Error 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_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); + if (error.Fail()) + { + return false; + } + + return true; +} + +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'); + Error 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'); + Error 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); + if (error.Fail()) + { + return false; + } + + 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 0; + if (!Read_class_row(process, *objc_class, class_ro, class_rw)) + return 0; + + static ConstString NSObject_name("NSObject"); + + if (m_name != NSObject_name && superclass_func) + superclass_func(objc_class->m_superclass); + + if (instance_method_func) + { + std::unique_ptr<method_list_t> base_method_list; + + base_method_list.reset(new method_list_t); + if (!base_method_list->Read(process, class_ro->m_baseMethods_ptr)) + return false; + + if (base_method_list->m_entsize != method_t::GetSize(process)) + return false; + + std::unique_ptr<method_t> method; + method.reset(new method_t); + + for (uint32_t i = 0, e = base_method_list->m_count; i < e; ++i) + { + method->Read(process, base_method_list->m_first_ptr + (i * base_method_list->m_entsize)); + + if (instance_method_func(method->m_name.c_str(), method->m_types.c_str())) + break; + } + } + + 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; +} + +ClassDescriptorV2::iVarsStorage::iVarsStorage (): +m_filled(false), +m_ivars(), +m_mutex(Mutex::eMutexTypeRecursive) +{} + +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; + Mutex::Locker lock(m_mutex); + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_TYPES | LIBLLDB_LOG_VERBOSE)); + if (log) + log->Printf("[ClassDescriptorV2::iVarsStorage::fill] class_name = %s", descriptor.GetClassName().AsCString("<unknown")); + 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; + if (log) + log->Printf("[ClassDescriptorV2::iVarsStorage::fill] name = %s, encoding = %s, offset_ptr = %" PRIx64 ", size = %" PRIu64, + name,type,offset_ptr,size); + CompilerType ivar_type = encoding_to_type_sp->RealizeType(type, for_expression); + if (ivar_type) + { + if (log) + log->Printf("[ClassDescriptorV2::iVarsStorage::fill] name = %s, encoding = %s, offset_ptr = %" PRIx64 ", size = %" PRIu64 " , type_size = %" PRIu64, + name,type,offset_ptr,size,ivar_type.GetByteSize(nullptr)); + Scalar offset_scalar; + Error 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) + { + if (log) + log->Printf("[ClassDescriptorV2::iVarsStorage::fill] offset_ptr = %" PRIx64 " --> %" PRIu32, + offset_ptr, offset_scalar.SInt()); + m_ivars.push_back({ ConstString(name), ivar_type, size, offset_scalar.SInt() }); + } + else if (log) + log->Printf("[ClassDescriptorV2::iVarsStorage::fill] offset_ptr = %" PRIx64 " --> read fail, read = %zu", + offset_ptr, read); + } + return stop_loop; + }); +} + +void +ClassDescriptorV2::GetIVarInformation () +{ + m_ivars_storage.fill(m_runtime, *this); +} diff --git a/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.h b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.h new file mode 100644 index 000000000000..18591ecd6e93 --- /dev/null +++ b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.h @@ -0,0 +1,411 @@ +//===-- AppleObjCClassDescriptorV2.h ----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_AppleObjCClassDescriptorV2_h_ +#define liblldb_AppleObjCClassDescriptorV2_h_ + +// C Includes +// C++ Includes +// Other libraries and framework includes +// Project includes +#include "lldb/lldb-private.h" +#include "lldb/Host/Mutex.h" +#include "lldb/Target/ObjCLanguageRuntime.h" +#include "AppleObjCRuntimeV2.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 + } + + // 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; + } + + 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 = (1 << 31); + + struct objc_class_t { + ObjCLanguageRuntime::ObjCISA m_isa; // The class's metaclass. + ObjCLanguageRuntime::ObjCISA m_superclass; + lldb::addr_t m_cache_ptr; + lldb::addr_t m_vtable_ptr; + lldb::addr_t m_data_ptr; + uint8_t m_flags; + + objc_class_t () : + m_isa (0), + m_superclass (0), + m_cache_ptr (0), + m_vtable_ptr (0), + m_data_ptr (0), + m_flags (0) + { + } + + 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 + { + uint32_t m_entsize; + uint32_t m_count; + lldb::addr_t m_first_ptr; + + bool + Read(Process *process, lldb::addr_t addr); + }; + + 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) + { + size_t ptr_size = process->GetAddressByteSize(); + + return ptr_size // SEL name; + + ptr_size // const char *types; + + ptr_size; // IMP imp; + } + + bool + Read(Process *process, lldb::addr_t addr); + }; + + 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); + }; + + class iVarsStorage + { + public: + iVarsStorage (); + + size_t + size (); + + iVarDescriptor& + operator[] (size_t idx); + + void + fill (AppleObjCRuntimeV2& runtime, ClassDescriptorV2& descriptor); + + private: + bool m_filled; + std::vector<iVarDescriptor> m_ivars; + 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() + { + } + + 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; + + 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; +}; + +// 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 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 = payload; + m_info_bits = (m_payload & 0x0FULL); + m_value_bits = (m_payload & ~0x0FULL) >> 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; + } + + 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 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; + bool m_valid; + uint64_t m_info_bits; + uint64_t m_value_bits; + uint64_t m_payload; +}; + +} // namespace lldb_private + +#endif // liblldb_AppleObjCClassDescriptorV2_h_ diff --git a/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCDeclVendor.cpp b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCDeclVendor.cpp new file mode 100644 index 000000000000..cd6ece297ed1 --- /dev/null +++ b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCDeclVendor.cpp @@ -0,0 +1,665 @@ +//===-- AppleObjCDeclVendor.cpp ---------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "AppleObjCDeclVendor.h" + +#include "lldb/Core/Log.h" +#include "lldb/Core/Module.h" +#include "Plugins/ExpressionParser/Clang/ASTDumper.h" +#include "lldb/Symbol/ClangExternalASTSourceCommon.h" +#include "lldb/Target/ObjCLanguageRuntime.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Target.h" + +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclObjC.h" + +using namespace lldb_private; + +class lldb_private::AppleObjCExternalASTSource : public ClangExternalASTSourceCommon +{ +public: + AppleObjCExternalASTSource (AppleObjCDeclVendor &decl_vendor) : + m_decl_vendor(decl_vendor) + { + } + + bool + FindExternalVisibleDeclsByName(const clang::DeclContext *decl_ctx, clang::DeclarationName name) override + { + static unsigned int invocation_id = 0; + unsigned int current_id = invocation_id++; + + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS)); // FIXME - a more appropriate log channel? + + if (log) + { + log->Printf("AppleObjCExternalASTSource::FindExternalVisibleDeclsByName[%u] on (ASTContext*)%p Looking for %s in (%sDecl*)%p", + current_id, + 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.size() != 0); + } + while(0); + + SetNoExternalVisibleDeclsForName(decl_ctx, name); + return false; + } + + void + CompleteType(clang::TagDecl *tag_decl) override + { + static unsigned int invocation_id = 0; + unsigned int current_id = invocation_id++; + + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS)); // FIXME - a more appropriate log channel? + + if (log) + { + log->Printf("AppleObjCExternalASTSource::CompleteType[%u] on (ASTContext*)%p Completing (TagDecl*)%p named %s", + current_id, + static_cast<void*>(&tag_decl->getASTContext()), + static_cast<void*>(tag_decl), + tag_decl->getName().str().c_str()); + + log->Printf(" AOEAS::CT[%u] Before:", current_id); + ASTDumper dumper((clang::Decl*)tag_decl); + dumper.ToLog(log, " [CT] "); + } + + if (log) + { + log->Printf(" AOEAS::CT[%u] After:", current_id); + ASTDumper dumper((clang::Decl*)tag_decl); + dumper.ToLog(log, " [CT] "); + } + return; + } + + void + CompleteType(clang::ObjCInterfaceDecl *interface_decl) override + { + static unsigned int invocation_id = 0; + unsigned int current_id = invocation_id++; + + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS)); // FIXME - a more appropriate log channel? + + if (log) + { + log->Printf("AppleObjCExternalASTSource::CompleteType[%u] on (ASTContext*)%p Completing (ObjCInterfaceDecl*)%p named %s", + current_id, + static_cast<void*>(&interface_decl->getASTContext()), + static_cast<void*>(interface_decl), + interface_decl->getName().str().c_str()); + + log->Printf(" AOEAS::CT[%u] Before:", current_id); + ASTDumper dumper((clang::Decl*)interface_decl); + dumper.ToLog(log, " [CT] "); + } + + m_decl_vendor.FinishDecl(interface_decl); + + if (log) + { + log->Printf(" [CT] After:"); + ASTDumper dumper((clang::Decl*)interface_decl); + dumper.ToLog(log, " [CT] "); + } + return; + } + + 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) : + DeclVendor(), + m_runtime(runtime), + m_ast_ctx(runtime.GetProcess()->GetTarget().GetArchitecture().GetTriple().getTriple().c_str()), + m_type_realizer_sp(m_runtime.GetEncodingToType()) +{ + 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 NULL; + + const 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_external_source->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) : m_is_valid(false) + { + const char *cursor = types; + enum ParserState { + Start = 0, + InType, + InPos + } state = Start; + const char *type = NULL; + int brace_depth = 0; + + uint32_t stepsLeft = 256; + + while (1) + { + 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 = NULL; + } + 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 (clang::ObjCInterfaceDecl *interface_decl, const char *name, bool instance, ObjCLanguageRuntime::EncodingToTypeSP type_realizer_sp) + { + if (!m_is_valid || m_type_vector.size() < 3) + return NULL; + + clang::ASTContext &ast_ctx(interface_decl->getASTContext()); + + clang::QualType return_qual_type; + + const bool isInstance = instance; + const bool isVariadic = false; + const bool isSynthesized = false; + const bool isImplicitlyDeclared = true; + const bool isDefined = false; + const clang::ObjCMethodDecl::ImplementationControl impControl = clang::ObjCMethodDecl::None; + const bool HasRelatedResultType = false; + const bool for_expression = true; + + std::vector <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; + } + } + + clang::Selector sel = ast_ctx.Selectors.getSelector(is_zero_argument ? 0 : selector_components.size(), selector_components.data()); + + clang::QualType ret_type = ClangASTContext::GetQualType(type_realizer_sp->RealizeType(interface_decl->getASTContext(), m_type_vector[0].c_str(), for_expression)); + + if (ret_type.isNull()) + return NULL; + + clang::ObjCMethodDecl *ret = clang::ObjCMethodDecl::Create(ast_ctx, + clang::SourceLocation(), + clang::SourceLocation(), + sel, + ret_type, + NULL, + interface_decl, + isInstance, + isVariadic, + isSynthesized, + 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 = ClangASTContext::GetQualType(type_realizer_sp->RealizeType(ast_ctx, m_type_vector[ai].c_str(), for_expression)); + + if (arg_type.isNull()) + return NULL; // 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(), + NULL, + arg_type, + NULL, + clang::SC_None, + NULL)); + } + + ret->setMethodParams(ast_ctx, llvm::ArrayRef<clang::ParmVarDecl*>(parm_vars), llvm::ArrayRef<clang::SourceLocation>()); + + return ret; + } +private: + typedef std::vector <std::string> TypeVector; + + TypeVector m_type_vector; + bool m_is_valid; +}; + +bool +AppleObjCDeclVendor::FinishDecl(clang::ObjCInterfaceDecl *interface_decl) +{ + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS)); // FIXME - a more appropriate log channel? + + ClangASTMetadata *metadata = m_external_source->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 (interface_decl, name, true, m_type_realizer_sp); + + if (log) + log->Printf("[ 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 (interface_decl, name, false, m_type_realizer_sp); + + if (log) + log->Printf("[ 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; + + if (log) + log->Printf("[ 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), + ClangASTContext::GetQualType(ivar_type), + type_source_info, // TypeSourceInfo * + clang::ObjCIvarDecl::Public, + 0, + is_synthesized); + + if (ivar_decl) + { + interface_decl->addDecl(ivar_decl); + } + } + + return false; + }; + + if (log) + { + ASTDumper method_dumper ((clang::Decl*)interface_decl); + + log->Printf("[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) + { + ASTDumper method_dumper ((clang::Decl*)interface_decl); + + log->Printf("[AppleObjCDeclVendor::FinishDecl] Finished Objective-C interface"); + + method_dumper.ToLog(log, " [AOTV::FD] "); + } + + return true; +} + +uint32_t +AppleObjCDeclVendor::FindDecls (const ConstString &name, + bool append, + uint32_t max_matches, + std::vector <clang::NamedDecl *> &decls) +{ + static unsigned int invocation_id = 0; + unsigned int current_id = invocation_id++; + + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS)); // FIXME - a more appropriate log channel? + + if (log) + log->Printf("AppleObjCDeclVendor::FindTypes [%u] ('%s', %s, %u, )", + current_id, + (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[0])) + { + if (log) + { + clang::QualType result_iface_type = ast_ctx->getObjCInterfaceType(result_iface_decl); + ASTDumper dumper(result_iface_type); + + uint64_t isa_value = LLDB_INVALID_ADDRESS; + ClangASTMetadata *metadata = m_external_source->GetMetadata(result_iface_decl); + if (metadata) + isa_value = metadata->GetISAPtr(); + + log->Printf("AOCTV::FT [%u] Found %s (isa 0x%" PRIx64 ") in the ASTContext", + current_id, + dumper.GetCString(), + isa_value); + } + + decls.push_back(result_iface_decl); + ret++; + break; + } + else + { + if (log) + log->Printf("AOCTV::FT [%u] There's something in the ASTContext, but it's not something we know about", + current_id); + break; + } + } + else if(log) + { + log->Printf("AOCTV::FT [%u] Couldn't find %s in the ASTContext", + current_id, + 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) + { + if (log) + log->Printf("AOCTV::FT [%u] Couldn't find the isa", + current_id); + + break; + } + + clang::ObjCInterfaceDecl *iface_decl = GetDeclForISA(isa); + + if (!iface_decl) + { + if (log) + log->Printf("AOCTV::FT [%u] Couldn't get the Objective-C interface for isa 0x%" PRIx64, + current_id, + (uint64_t)isa); + + break; + } + + if (log) + { + clang::QualType new_iface_type = ast_ctx->getObjCInterfaceType(iface_decl); + ASTDumper dumper(new_iface_type); + log->Printf("AOCTV::FT [%u] Created %s (isa 0x%" PRIx64 ")", + current_id, + dumper.GetCString(), + (uint64_t)isa); + } + + decls.push_back(iface_decl); + ret++; + break; + } while (0); + + return ret; +} diff --git a/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCDeclVendor.h b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCDeclVendor.h new file mode 100644 index 000000000000..88789c7b5a8d --- /dev/null +++ b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCDeclVendor.h @@ -0,0 +1,55 @@ +//===-- AppleObjCDeclVendor.h -----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_AppleObjCDeclVendor_h_ +#define liblldb_AppleObjCDeclVendor_h_ + +// C Includes +// C++ Includes +// Other libraries and framework includes +// Project includes +#include "lldb/lldb-private.h" +#include "lldb/Symbol/ClangASTContext.h" +#include "lldb/Symbol/DeclVendor.h" +#include "lldb/Target/ObjCLanguageRuntime.h" + +namespace lldb_private { + +class AppleObjCExternalASTSource; + +class AppleObjCDeclVendor : public DeclVendor +{ +public: + AppleObjCDeclVendor(ObjCLanguageRuntime &runtime); + + uint32_t + FindDecls(const ConstString &name, + bool append, + uint32_t max_matches, + std::vector <clang::NamedDecl *> &decls) override; + + friend class AppleObjCExternalASTSource; + +private: + clang::ObjCInterfaceDecl *GetDeclForISA(ObjCLanguageRuntime::ObjCISA isa); + bool FinishDecl(clang::ObjCInterfaceDecl *decl); + + ObjCLanguageRuntime &m_runtime; + ClangASTContext 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 // liblldb_AppleObjCDeclVendor_h_ diff --git a/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.cpp b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.cpp new file mode 100644 index 000000000000..cdb95250b2f4 --- /dev/null +++ b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.cpp @@ -0,0 +1,541 @@ +//===-- AppleObjCRuntime.cpp -------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "AppleObjCRuntime.h" +#include "AppleObjCTrampolineHandler.h" + +#include "clang/AST/Type.h" + +#include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Core/ConstString.h" +#include "lldb/Core/Error.h" +#include "lldb/Core/Log.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleList.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/Scalar.h" +#include "lldb/Core/Section.h" +#include "lldb/Core/StreamString.h" +#include "lldb/Core/ValueObject.h" +#include "lldb/Expression/FunctionCaller.h" +#include "lldb/Symbol/ClangASTContext.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 <vector> + +using namespace lldb; +using namespace lldb_private; + +#define PO_FUNCTION_TIMEOUT_USEC 15*1000*1000 + +AppleObjCRuntime::~AppleObjCRuntime() +{ +} + +AppleObjCRuntime::AppleObjCRuntime(Process *process) : + ObjCLanguageRuntime (process), + m_read_objc_library (false), + m_objc_trampoline_handler_ap (), + m_Foundation_major() +{ + ReadObjCLibraryIfNeeded (process->GetTarget().GetImages()); +} + +bool +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 false; + + // 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 false; + + ExecutionContext exe_ctx (valobj.GetExecutionContextRef()); + return GetObjectDescription(str, val, exe_ctx.GetBestExecutionContextScope()); + +} +bool +AppleObjCRuntime::GetObjectDescription (Stream &strm, Value &value, ExecutionContextScope *exe_scope) +{ + if (!m_read_objc_library) + return false; + + ExecutionContext exe_ctx; + exe_scope->CalculateExecutionContext(exe_ctx); + Process *process = exe_ctx.GetProcessPtr(); + if (!process) + return false; + + // 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 false; + + Target *target = exe_ctx.GetTargetPtr(); + CompilerType compiler_type = value.GetCompilerType(); + if (compiler_type) + { + if (!ClangASTContext::IsObjCObjectPointerType(compiler_type)) + { + strm.Printf ("Value doesn't point to an ObjC object.\n"); + return false; + } + } + else + { + // If it is not a pointer, see if we can make it into a pointer. + ClangASTContext *ast_context = target->GetScratchClangASTContext(); + CompilerType opaque_type = ast_context->GetBasicType(eBasicTypeObjCID); + if (!opaque_type) + opaque_type = ast_context->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: + ClangASTContext *ast_context = target->GetScratchClangASTContext(); + + CompilerType return_compiler_type = ast_context->GetCStringType(true); + Value ret; +// ret.SetContext(Value::eContextTypeClangType, return_compiler_type); + ret.SetCompilerType (return_compiler_type); + + if (exe_ctx.GetFramePtr() == NULL) + { + Thread *thread = exe_ctx.GetThreadPtr(); + if (thread == NULL) + { + exe_ctx.SetThreadSP(process->GetThreadList().GetSelectedThread()); + thread = exe_ctx.GetThreadPtr(); + } + if (thread) + { + exe_ctx.SetFrameSP(thread->GetSelectedFrame()); + } + } + + // Now we're ready to call the function: + + StreamString error_stream; + lldb::addr_t wrapper_struct_addr = LLDB_INVALID_ADDRESS; + + if (!m_print_object_caller_up) + { + Error 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(); + strm.Printf("Could not get function runner to call print for debugger function: %s.", error.AsCString()); + return false; + } + m_print_object_caller_up->InsertFunction(exe_ctx, wrapper_struct_addr, error_stream); + } + else + { + m_print_object_caller_up->WriteFunctionArguments(exe_ctx, + wrapper_struct_addr, + arg_value_list, + error_stream); + } + + + + EvaluateExpressionOptions options; + options.SetUnwindOnError(true); + options.SetTryAllThreads(true); + options.SetStopOthers(true); + options.SetIgnoreBreakpoints(true); + options.SetTimeoutUsec(PO_FUNCTION_TIMEOUT_USEC); + + ExpressionResults results = m_print_object_caller_up->ExecuteFunction (exe_ctx, + &wrapper_struct_addr, + options, + error_stream, + ret); + if (results != eExpressionCompleted) + { + strm.Printf("Error evaluating Print Object function: %d.\n", results); + return false; + } + + 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) + { + Error error; + curr_len = process->ReadCStringFromMemory(result_ptr + cstr_len, buf, sizeof(buf), error); + strm.Write (buf, curr_len); + cstr_len += curr_len; + } + return cstr_len > 0; +} + +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.get()) + { + const ModuleList &modules = m_process->GetTarget().GetImages(); + + SymbolContextList contexts; + SymbolContext context; + + if ((!modules.FindSymbolsWithNameAndType(ConstString ("_NSPrintForDebugger"), eSymbolTypeCode, contexts)) && + (!modules.FindSymbolsWithNameAndType(ConstString ("_CFPrintForDebugger"), eSymbolTypeCode, contexts))) + return NULL; + + contexts.GetContextAtIndex(0, context); + + m_PrintForDebugger_addr.reset(new Address(context.symbol->GetAddress())); + } + + return m_PrintForDebugger_addr.get(); +} + +bool +AppleObjCRuntime::CouldHaveDynamicValue (ValueObject &in_value) +{ + return in_value.GetCompilerType().IsPossibleDynamicType (NULL, + 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.hasValue()) + { + const ModuleList& modules = m_process->GetTarget().GetImages(); + uint32_t major = UINT32_MAX; + 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) + { + module_sp->GetVersion(&major,1); + m_Foundation_major = major; + return major; + } + } + return LLDB_INVALID_MODULE_VERSION; + } + else + return m_Foundation_major.getValue(); +} + +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_ap.reset(new AppleObjCTrampolineHandler (m_process->shared_from_this(), module_sp)); + if (m_objc_trampoline_handler_ap.get() != NULL) + { + 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_ap.get()) + thread_plan_sp = m_objc_trampoline_handler_ap->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; + + const ModuleList &target_modules = target.GetImages(); + Mutex::Locker modules_locker(target_modules.GetMutex()); + + size_t num_images = target_modules.GetSize(); + for (size_t i = 0; i < num_images; i++) + { + ModuleSP module_sp = target_modules.GetModuleAtIndexUnlocked(i); + // 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().BreakpointSiteContainsBreakpoint (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; + + if (target.GetImages().FindSymbolsWithNameAndType(s_method_signature, eSymbolTypeCode, sc_list) || + target.GetImages().FindSymbolsWithNameAndType(s_arclite_method_signature, eSymbolTypeCode, sc_list)) + return true; + else + return false; +} + +lldb::SearchFilterSP +AppleObjCRuntime::CreateExceptionSearchFilter () +{ + Target &target = m_process->GetTarget(); + + if (target.GetArchitecture().GetTriple().getVendor() == llvm::Triple::Apple) + { + FileSpecList filter_modules; + filter_modules.Append(FileSpec("libobjc.A.dylib", false)); + return target.GetSearchFilterForModuleList(&filter_modules); + } + else + { + return LanguageRuntime::CreateExceptionSearchFilter(); + } +} + +void +AppleObjCRuntime::ReadObjCLibraryIfNeeded (const ModuleList &module_list) +{ + if (!HasReadObjCLibrary ()) + { + Mutex::Locker locker (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/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.h b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.h new file mode 100644 index 000000000000..342824e79b1f --- /dev/null +++ b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.h @@ -0,0 +1,148 @@ +//===-- AppleObjCRuntime.h --------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_AppleObjCRuntime_h_ +#define liblldb_AppleObjCRuntime_h_ + +// C Includes +// C++ Includes +// Other libraries and framework includes +#include "llvm/ADT/Optional.h" + +// Project includes +#include "lldb/lldb-private.h" +#include "lldb/Target/LanguageRuntime.h" +#include "lldb/Target/ObjCLanguageRuntime.h" +#include "AppleObjCTrampolineHandler.h" +#include "AppleThreadPlanStepThroughObjCTrampoline.h" + +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 bool classof(const ObjCLanguageRuntime* runtime) + { + switch (runtime->GetRuntimeVersion()) + { + case ObjCRuntimeVersions::eAppleObjC_V1: + case ObjCRuntimeVersions::eAppleObjC_V2: + return true; + default: + return false; + } + } + + // These are generic runtime functions: + bool + GetObjectDescription (Stream &str, Value &value, ExecutionContextScope *exe_scope) override; + + bool + 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; + + uint32_t + GetFoundationVersion(); + +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_ap; + lldb::BreakpointSP m_objc_exception_bp_sp; + lldb::ModuleWP m_objc_module_wp; + std::unique_ptr<FunctionCaller> m_print_object_caller_up; + + llvm::Optional<uint32_t> m_Foundation_major; +}; + +} // namespace lldb_private + +#endif // liblldb_AppleObjCRuntime_h_ diff --git a/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV1.cpp b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV1.cpp new file mode 100644 index 000000000000..59b28b63f6b3 --- /dev/null +++ b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV1.cpp @@ -0,0 +1,459 @@ +//===-- AppleObjCRuntimeV1.cpp --------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "AppleObjCRuntimeV1.h" +#include "AppleObjCTrampolineHandler.h" +#include "AppleObjCDeclVendor.h" + +#include "clang/AST/Type.h" + +#include "lldb/Breakpoint/BreakpointLocation.h" +#include "lldb/Core/ConstString.h" +#include "lldb/Core/Error.h" +#include "lldb/Core/Log.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/Scalar.h" +#include "lldb/Core/StreamString.h" +#include "lldb/Expression/FunctionCaller.h" +#include "lldb/Expression/UtilityFunction.h" +#include "lldb/Symbol/ClangASTContext.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 <vector> + +using namespace lldb; +using namespace lldb_private; + +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::eValueTypeScalar; + 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() == false; +} + +//------------------------------------------------------------------ +// 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 NULL; + } + else + return NULL; +} + + +void +AppleObjCRuntimeV1::Initialize() +{ + PluginManager::RegisterPlugin (GetPluginNameStatic(), + "Apple Objective C Language Runtime - Version 1", + CreateInstance); +} + +void +AppleObjCRuntimeV1::Terminate() +{ + PluginManager::UnregisterPlugin (CreateInstance); +} + +lldb_private::ConstString +AppleObjCRuntimeV1::GetPluginNameStatic() +{ + static ConstString g_name("apple-objc-v1"); + return g_name; +} + +//------------------------------------------------------------------ +// PluginInterface protocol +//------------------------------------------------------------------ +ConstString +AppleObjCRuntimeV1::GetPluginName() +{ + return GetPluginNameStatic(); +} + +uint32_t +AppleObjCRuntimeV1::GetPluginVersion() +{ + return 1; +} + +BreakpointResolverSP +AppleObjCRuntimeV1::CreateExceptionResolver (Breakpoint *bkpt, bool catch_bp, bool throw_bp) +{ + BreakpointResolverSP resolver_sp; + + if (throw_bp) + resolver_sp.reset (new BreakpointResolverName (bkpt, + "objc_exception_throw", + eFunctionNameTypeBase, + eLanguageTypeUnknown, + Breakpoint::Exact, + eLazyBoolNo)); + // FIXME: don't do catch yet. + return resolver_sp; +} + +struct BufStruct { + char contents[2048]; +}; + +UtilityFunction * +AppleObjCRuntimeV1::CreateObjectChecker(const char *name) +{ + std::unique_ptr<BufStruct> buf(new BufStruct); + + assert(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" + " (int)strlen(obj->isa->name); \n" + "} \n", + name) < (int)sizeof(buf->contents)); + + Error error; + return GetTargetRef().GetUtilityFunctionForLanguage(buf->contents, eLanguageTypeObjC, name, error); +} + +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; + + Error 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::DataBufferSP 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((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::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) + { + Error 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(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + ProcessSP process_sp = process->shared_from_this(); + + ModuleSP objc_module_sp(GetObjCModule()); + + if (!objc_module_sp) + return; + + uint32_t isa_count = 0; + + 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; + + Error 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.GetPointer(&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; + + isa_count += bucket_isa_count; + + 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()) + log->Printf("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()) + log->Printf("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() +{ + if (!m_decl_vendor_ap.get()) + m_decl_vendor_ap.reset(new AppleObjCDeclVendor(*this)); + + return m_decl_vendor_ap.get(); +} diff --git a/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV1.h b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV1.h new file mode 100644 index 000000000000..9f9fcc69b682 --- /dev/null +++ b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV1.h @@ -0,0 +1,207 @@ +//===-- AppleObjCRuntimeV1.h ------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_AppleObjCRuntimeV1_h_ +#define liblldb_AppleObjCRuntimeV1_h_ + +// C Includes +// C++ Includes +// Other libraries and framework includes +// Project includes +#include "lldb/lldb-private.h" +#include "lldb/Target/ObjCLanguageRuntime.h" +#include "AppleObjCRuntime.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 lldb_private::ConstString + GetPluginNameStatic(); + + static bool classof(const ObjCLanguageRuntime* runtime) + { + switch (runtime->GetRuntimeVersion()) + { + case ObjCRuntimeVersions::eAppleObjC_V1: + return true; + default: + return false; + } + } + + 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; + } + + 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; + + UtilityFunction * + CreateObjectChecker(const char *) override; + + //------------------------------------------------------------------ + // PluginInterface protocol + //------------------------------------------------------------------ + ConstString + GetPluginName() override; + + uint32_t + GetPluginVersion() override; + + ObjCRuntimeVersions + GetRuntimeVersion() const override + { + return ObjCRuntimeVersions::eAppleObjC_V1; + } + + void + UpdateISAToDescriptorMapIfNeeded() override; + + DeclVendor * + GetDeclVendor() override; + +protected: + lldb::BreakpointResolverSP + CreateExceptionResolver(Breakpoint *bkpt, bool catch_bp, bool throw_bp) override; + + class HashTableSignature + { + public: + HashTableSignature () : + m_count (0), + m_num_buckets (0), + m_buckets_ptr (LLDB_INVALID_ADDRESS) + { + } + + 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; + uint32_t m_num_buckets; + lldb::addr_t m_buckets_ptr; + }; + + lldb::addr_t + GetISAHashTablePointer (); + + HashTableSignature m_hash_signature; + lldb::addr_t m_isa_hash_table_ptr; + std::unique_ptr<DeclVendor> m_decl_vendor_ap; + +private: + AppleObjCRuntimeV1(Process *process); +}; + +} // namespace lldb_private + +#endif // liblldb_AppleObjCRuntimeV1_h_ diff --git a/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp new file mode 100644 index 000000000000..8c485d97bdc9 --- /dev/null +++ b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp @@ -0,0 +1,2215 @@ +//===-- AppleObjCRuntimeV2.cpp ----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// C Includes +#include <stdint.h> + +// C++ Includes +#include <string> +#include <vector> + +// Other libraries and framework includes +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclObjC.h" + +// Project includes +#include "lldb/lldb-enumerations.h" +#include "lldb/Core/ClangForward.h" +#include "lldb/Symbol/CompilerType.h" + +#include "lldb/Core/ClangForward.h" +#include "lldb/Core/ConstString.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Error.h" +#include "lldb/Core/Log.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/Scalar.h" +#include "lldb/Core/Section.h" +#include "lldb/Core/Stream.h" +#include "lldb/Core/StreamString.h" +#include "lldb/Core/Timer.h" +#include "lldb/Core/ValueObjectVariable.h" +#include "lldb/Expression/FunctionCaller.h" +#include "lldb/Expression/UtilityFunction.h" +#include "lldb/Host/StringConvert.h" +#include "lldb/Interpreter/CommandObject.h" +#include "lldb/Interpreter/CommandObjectMultiword.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Symbol/ClangASTContext.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Symbol/TypeList.h" +#include "lldb/Symbol/VariableList.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Platform.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" + +#include "AppleObjCRuntimeV2.h" +#include "AppleObjCClassDescriptorV2.h" +#include "AppleObjCTypeEncodingParser.h" +#include "AppleObjCDeclVendor.h" +#include "AppleObjCTrampolineHandler.h" + +#if defined(__APPLE__) +#include "Plugins/Platform/MacOSX/PlatformiOSSimulator.h" +#endif + +using namespace lldb; +using namespace lldb_private; + +// 2 second timeout when running utility functions +#define UTILITY_FUNCTION_TIMEOUT_USEC 2*1000*1000 + +static const char *g_get_dynamic_class_info_name = "__lldb_apple_objc_v2_get_dynamic_class_info"; +// Testing using the new C++11 raw string literals. If this breaks GCC then we will +// need to revert to the code above... +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 ENABLE_DEBUG_PRINTF // COMMENT THIS LINE OUT PRIOR TO CHECKIN +#ifdef ENABLE_DEBUG_PRINTF +#define DEBUG_PRINTF(fmt, ...) printf(fmt, ## __VA_ARGS__) +#else +#define DEBUG_PRINTF(fmt, ...) +#endif + +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) +{ + 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; + if (class_infos_ptr) + { + const size_t max_class_infos = class_infos_byte_size/sizeof(ClassInfo); + ClassInfo *class_infos = (ClassInfo *)class_infos_ptr; + BucketInfo *buckets = (BucketInfo *)grc->buckets; + + uint32_t idx = 0; + for (unsigned i=0; i<=grc->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; + } + ++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_shared_cache_class_info_name = "__lldb_apple_objc_v2_get_shared_cache_class_info"; +// Testing using the new C++11 raw string literals. If this breaks GCC then we will +// need to revert to the code above... +static const char *g_get_shared_cache_class_info_body = R"( + +extern "C" +{ + const char *class_getName(void *objc_class); + size_t strlen(const char *); + char *strncpy (char * s1, const char * s2, size_t n); + int printf(const char * format, ...); +} + +// #define ENABLE_DEBUG_PRINTF // COMMENT THIS LINE OUT PRIOR TO CHECKIN +#ifdef ENABLE_DEBUG_PRINTF +#define DEBUG_PRINTF(fmt, ...) printf(fmt, ## __VA_ARGS__) +#else +#define DEBUG_PRINTF(fmt, ...) +#endif + + +struct objc_classheader_t { + int32_t clsOffset; + int32_t hiOffset; +}; + +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_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 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 *class_infos_ptr, + uint32_t class_infos_byte_size) +{ + uint32_t idx = 0; + DEBUG_PRINTF ("objc_opt_ro_ptr = %p\n", objc_opt_ro_ptr); + DEBUG_PRINTF ("class_infos_ptr = %p\n", class_infos_ptr); + DEBUG_PRINTF ("class_infos_byte_size = %u (%" PRIu64 " class infos)\n", class_infos_byte_size, (size_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 bool is_v14_format = objc_opt->version >= 14; + if (is_v14_format) + { + 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 == 12 || objc_opt->version == 13 || objc_opt->version == 14) + { + const objc_clsopt_t* clsopt = NULL; + if (is_v14_format) + 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); + 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); + for (uint32_t i=0; i<clsopt->capacity; ++i) + { + const int32_t clsOffset = classOffsets[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_getName (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) + h = ((h << 5) + h) + c; + class_infos[idx].hash = h; + } + ++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_getName (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) + 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, + Error& 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()) + { + lldb::addr_t symbol_load_addr = symbol->GetAddressRef().GetLoadAddress(&process->GetTarget()); + if (symbol_load_addr != LLDB_INVALID_ADDRESS) + { + if (read_value) + return process->ReadUnsignedIntegerFromMemory(symbol_load_addr, byte_size, default_value, error); + else + return symbol_load_addr; + } + else + { + error.SetErrorString("symbol address invalid"); + return default_value; + } + } + else + { + error.SetErrorString("no symbol"); + return default_value; + } +} + +AppleObjCRuntimeV2::AppleObjCRuntimeV2 (Process *process, + const ModuleSP &objc_module_sp) : + AppleObjCRuntime (process), + m_get_class_info_code(), + m_get_class_info_args (LLDB_INVALID_ADDRESS), + m_get_class_info_args_mutex (Mutex::eMutexTypeNormal), + m_get_shared_cache_class_info_code(), + m_get_shared_cache_class_info_args (LLDB_INVALID_ADDRESS), + m_get_shared_cache_class_info_args_mutex (Mutex::eMutexTypeNormal), + m_decl_vendor_ap (), + m_isa_hash_table_ptr (LLDB_INVALID_ADDRESS), + m_hash_signature (), + m_has_object_getClass (false), + m_loaded_objc_opt (false), + m_non_pointer_isa_cache_ap(NonPointerISACache::CreateInstance(*this,objc_module_sp)), + m_tagged_pointer_vendor_ap(TaggedPointerVendorV2::CreateInstance(*this,objc_module_sp)), + m_encoding_to_type_sp(), + m_noclasses_warning_emitted(false) +{ + static const ConstString g_gdb_object_getClass("gdb_object_getClass"); + m_has_object_getClass = (objc_module_sp->FindFirstSymbolWithNameAndType(g_gdb_object_getClass, eSymbolTypeCode) != NULL); +} + +bool +AppleObjCRuntimeV2::GetDynamicTypeAndAddress (ValueObject &in_value, + DynamicValueType use_dynamic, + TypeAndOrName &class_type_or_name, + Address &address, + Value::ValueType &value_type) +{ + // The Runtime is attached to a particular process, you shouldn't pass in a value from another process. + assert (in_value.GetProcessSP().get() == m_process); + assert (m_process != NULL); + + class_type_or_name.Clear(); + value_type = Value::ValueType::eValueTypeScalar; + + // 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 + DeclVendor* vendor = GetDeclVendor(); + if (vendor) + { + std::vector<clang::NamedDecl*> decls; + if (vendor->FindDecls(class_name, false, 1, decls) && decls.size()) + class_type_or_name.SetCompilerType(ClangASTContext::GetTypeForDecl(decls[0])); + } + } + } + } + } + return class_type_or_name.IsEmpty() == false; +} + +//------------------------------------------------------------------ +// 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); + else + return NULL; + } + else + return NULL; +} + +class CommandObjectObjC_ClassTable_Dump : public CommandObjectParsed +{ +public: + 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 ) + { + } + + ~CommandObjectObjC_ClassTable_Dump() override = default; + +protected: + bool + DoExecute(Args& command, CommandReturnObject &result) override + { + Process *process = m_exe_ctx.GetProcessPtr(); + ObjCLanguageRuntime *objc_runtime = process->GetObjCLanguageRuntime(); + if (objc_runtime) + { + auto iterators_pair = objc_runtime->GetDescriptorIteratorPair(); + auto iterator = iterators_pair.first; + for(; iterator != iterators_pair.second; iterator++) + { + result.GetOutputStream().Printf("isa = 0x%" PRIx64, iterator->first); + if (iterator->second) + { + result.GetOutputStream().Printf(" name = %s", iterator->second->GetClassName().AsCString("<unknown>")); + result.GetOutputStream().Printf(" instance size = %" PRIu64, iterator->second->GetInstanceSize()); + result.GetOutputStream().Printf(" num ivars = %" PRIuPTR, (uintptr_t)iterator->second->GetNumIVars()); + if (auto superclass = iterator->second->GetSuperclass()) + { + result.GetOutputStream().Printf(" superclass = %s", superclass->GetClassName().AsCString("<unknown>")); + } + result.GetOutputStream().Printf("\n"); + } + else + { + result.GetOutputStream().Printf(" has no associated class.\n"); + } + } + result.SetStatus(lldb::eReturnStatusSuccessFinishResult); + return true; + } + else + { + result.AppendError("current process has no Objective-C runtime loaded"); + result.SetStatus(lldb::eReturnStatusFailed); + return false; + } + } +}; + +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 ) + { + CommandArgumentEntry arg; + CommandArgumentData index_arg; + + // Define the first (and only) variant of this arg. + index_arg.arg_type = eArgTypeAddress; + index_arg.arg_repetition = eArgRepeatPlus; + + // There is only one variant this argument could be; put it into the argument entry. + arg.push_back (index_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back (arg); + } + + ~CommandObjectMultiwordObjC_TaggedPointer_Info() override = default; + +protected: + bool + DoExecute(Args& command, CommandReturnObject &result) override + { + if (command.GetArgumentCount() == 0) + { + result.AppendError("this command requires arguments"); + result.SetStatus(lldb::eReturnStatusFailed); + return false; + } + + Process *process = m_exe_ctx.GetProcessPtr(); + ExecutionContext exe_ctx(process); + ObjCLanguageRuntime *objc_runtime = process->GetObjCLanguageRuntime(); + if (objc_runtime) + { + ObjCLanguageRuntime::TaggedPointerVendor *tagged_ptr_vendor = objc_runtime->GetTaggedPointerVendor(); + if (tagged_ptr_vendor) + { + for (size_t i = 0; + i < command.GetArgumentCount(); + i++) + { + const char *arg_str = command.GetArgumentAtIndex(i); + if (!arg_str) + continue; + Error error; + lldb::addr_t arg_addr = Args::StringToAddress(&exe_ctx, arg_str, LLDB_INVALID_ADDRESS, &error); + if (arg_addr == 0 || arg_addr == LLDB_INVALID_ADDRESS || error.Fail()) + continue; + auto descriptor_sp = tagged_ptr_vendor->GetClassDescriptor(arg_addr); + if (!descriptor_sp) + continue; + 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().Printf("0x%" PRIx64 " is tagged.\n\tpayload = 0x%" PRIx64 "\n\tvalue = 0x%" PRIx64 "\n\tinfo bits = 0x%" PRIx64 "\n\tclass = %s\n", + (uint64_t)arg_addr, + payload, + value_bits, + info_bits, + descriptor_sp->GetClassName().AsCString("<unknown>")); + } + else + { + result.GetOutputStream().Printf("0x%" PRIx64 " is not tagged.\n", (uint64_t)arg_addr); + } + } + } + else + { + result.AppendError("current process has no tagged pointer support"); + result.SetStatus(lldb::eReturnStatusFailed); + return false; + } + result.SetStatus(lldb::eReturnStatusSuccessFinishResult); + return true; + } + else + { + result.AppendError("current process has no Objective-C runtime loaded"); + result.SetStatus(lldb::eReturnStatusFailed); + return false; + } + } +}; + +class CommandObjectMultiwordObjC_ClassTable : public CommandObjectMultiword +{ +public: + CommandObjectMultiwordObjC_ClassTable (CommandInterpreter &interpreter) : + CommandObjectMultiword (interpreter, + "class-table", + "A set of 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", + "A set of 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", + "A set of 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)); + }); +} + +void +AppleObjCRuntimeV2::Terminate() +{ + PluginManager::UnregisterPlugin (CreateInstance); +} + +lldb_private::ConstString +AppleObjCRuntimeV2::GetPluginNameStatic() +{ + static ConstString g_name("apple-objc-v2"); + return g_name; +} + +//------------------------------------------------------------------ +// PluginInterface protocol +//------------------------------------------------------------------ +lldb_private::ConstString +AppleObjCRuntimeV2::GetPluginName() +{ + return GetPluginNameStatic(); +} + +uint32_t +AppleObjCRuntimeV2::GetPluginVersion() +{ + return 1; +} + +BreakpointResolverSP +AppleObjCRuntimeV2::CreateExceptionResolver (Breakpoint *bkpt, bool catch_bp, bool throw_bp) +{ + BreakpointResolverSP resolver_sp; + + if (throw_bp) + resolver_sp.reset (new BreakpointResolverName (bkpt, + "objc_exception_throw", + eFunctionNameTypeBase, + eLanguageTypeUnknown, + Breakpoint::Exact, + 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; +} + +UtilityFunction * +AppleObjCRuntimeV2::CreateObjectChecker(const char *name) +{ + char check_function_code[2048]; + + int len = 0; + if (m_has_object_getClass) + { + len = ::snprintf (check_function_code, + sizeof(check_function_code), + "extern \"C\" void *gdb_object_getClass(void *); \n" + "extern \"C\" int printf(const char *format, ...); \n" + "extern \"C\" void \n" + "%s(void *$__lldb_arg_obj, void *$__lldb_arg_selector) \n" + "{ \n" + " if ($__lldb_arg_obj == (void *)0) \n" + " return; // nil is ok \n" + " if (!gdb_object_getClass($__lldb_arg_obj)) \n" + " *((volatile int *)0) = 'ocgc'; \n" + " else if ($__lldb_arg_selector != (void *)0) \n" + " { \n" + " signed char responds = (signed char) [(id) $__lldb_arg_obj \n" + " respondsToSelector: \n" + " (struct objc_selector *) $__lldb_arg_selector]; \n" + " if (responds == (signed char) 0) \n" + " *((volatile int *)0) = 'ocgc'; \n" + " } \n" + "} \n", + name); + } + else + { + len = ::snprintf (check_function_code, + sizeof(check_function_code), + "extern \"C\" void *gdb_class_getClass(void *); \n" + "extern \"C\" int printf(const char *format, ...); \n" + "extern \"C\" void \n" + "%s(void *$__lldb_arg_obj, void *$__lldb_arg_selector) \n" + "{ \n" + " if ($__lldb_arg_obj == (void *)0) \n" + " return; // nil is ok \n" + " void **$isa_ptr = (void **)$__lldb_arg_obj; \n" + " if (*$isa_ptr == (void *)0 || !gdb_class_getClass(*$isa_ptr)) \n" + " *((volatile int *)0) = 'ocgc'; \n" + " else if ($__lldb_arg_selector != (void *)0) \n" + " { \n" + " signed char responds = (signed char) [(id) $__lldb_arg_obj \n" + " respondsToSelector: \n" + " (struct objc_selector *) $__lldb_arg_selector]; \n" + " if (responds == (signed char) 0) \n" + " *((volatile int *)0) = 'ocgc'; \n" + " } \n" + "} \n", + name); + } + + assert (len < (int)sizeof(check_function_code)); + + Error error; + return GetTargetRef().GetUtilityFunctionForLanguage(check_function_code, eLanguageTypeObjC, name, error); +} + +size_t +AppleObjCRuntimeV2::GetByteOffsetForIvar (CompilerType &parent_ast_type, const char *ivar_name) +{ + uint32_t ivar_offset = LLDB_INVALID_IVAR_OFFSET; + + const char *class_name = parent_ast_type.GetConstTypeName().AsCString(); + if (class_name && class_name[0] && 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); + 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; + + Error 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_ap) + return false; + return m_tagged_pointer_vendor_ap->IsPossibleTaggedPointer(ptr); +} + +class RemoteNXMapTable +{ +public: + RemoteNXMapTable () : + m_count (0), + m_num_buckets_minus_one (0), + m_buckets_ptr (LLDB_INVALID_ADDRESS), + m_process (NULL), + m_end_iterator (*this, -1), + m_load_addr (LLDB_INVALID_ADDRESS), + m_map_pair_size (0), + m_invalid_key (0) + { + } + + 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; + Error 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; + } + + const 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); + + Error 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; + Error 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; + uint32_t m_num_buckets_minus_one; + lldb::addr_t m_buckets_ptr; + lldb_private::Process *m_process; + const_iterator m_end_iterator; + lldb::addr_t m_load_addr; + size_t m_map_pair_size; + lldb::addr_t m_invalid_key; +}; + +AppleObjCRuntimeV2::HashTableSignature::HashTableSignature() : + m_count (0), + m_num_buckets (0), + m_buckets_ptr (0) +{ +} + +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 (m_non_pointer_isa_cache_ap.get()) + class_descriptor_sp = m_non_pointer_isa_cache_ap->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()) + { + addr_t isa_pointer = valobj.GetPointerValue(); + + // tagged pointer + if (IsTaggedPointer(isa_pointer)) + { + return m_tagged_pointer_vendor_ap->GetClassDescriptor(isa_pointer); + } + else + { + ExecutionContext exe_ctx (valobj.GetExecutionContextRef()); + + Process *process = exe_ctx.GetProcessPtr(); + if (process) + { + Error error; + ObjCISA isa = process->ReadPointerFromMemory(isa_pointer, error); + if (isa != LLDB_INVALID_ADDRESS) + { + objc_class_sp = GetClassDescriptorFromISA (isa); + if (isa && !objc_class_sp) + { + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + if (log) + log->Printf("0x%" PRIx64 ": AppleObjCRuntimeV2::GetClassDescriptor() ISA was not in class descriptor cache 0x%" PRIx64, + isa_pointer, + isa); + } + } + } + } + } + return objc_class_sp; +} + +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) + { + Error error; + m_isa_hash_table_ptr = process->ReadPointerFromMemory(gdb_objc_realized_classes_ptr, error); + } + } + } + return m_isa_hash_table_ptr; +} + +bool +AppleObjCRuntimeV2::UpdateISAToDescriptorMapDynamic(RemoteNXMapTable &hash_table) +{ + Process *process = GetProcess(); + + if (process == NULL) + return false; + + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + ExecutionContext exe_ctx; + + ThreadSP thread_sp = process->GetThreadList().GetSelectedThread(); + + if (!thread_sp) + return false; + + thread_sp->CalculateExecutionContext(exe_ctx); + ClangASTContext *ast = process->GetTarget().GetScratchClangASTContext(); + + if (!ast) + return false; + + Address function_address; + + StreamString errors; + + const uint32_t addr_size = process->GetAddressByteSize(); + + Error err; + + // Read the total number of classes from the hash table + const uint32_t num_classes = hash_table.GetCount(); + if (num_classes == 0) + { + if (log) + log->Printf ("No dynamic classes found in gdb_objc_realized_classes."); + return false; + } + + // Make some types for our arguments + CompilerType clang_uint32_t_type = ast->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 32); + CompilerType clang_void_pointer_type = ast->GetBasicType(eBasicTypeVoid).GetPointerType(); + + ValueList arguments; + FunctionCaller *get_class_info_function = nullptr; + + if (!m_get_class_info_code.get()) + { + Error error; + m_get_class_info_code.reset (GetTargetRef().GetUtilityFunctionForLanguage (g_get_dynamic_class_info_body, + eLanguageTypeObjC, + g_get_dynamic_class_info_name, + error)); + if (error.Fail()) + { + if (log) + log->Printf ("Failed to get Utility Function for implementation lookup: %s", error.AsCString()); + m_get_class_info_code.reset(); + } + else + { + errors.Clear(); + + if (!m_get_class_info_code->Install(errors, exe_ctx)) + { + if (log) + log->Printf ("Failed to install implementation lookup: %s.", errors.GetData()); + m_get_class_info_code.reset(); + } + } + if (!m_get_class_info_code.get()) + return false; + + // Next make the runner function for our implementation utility function. + Value value; + value.SetValueType (Value::eValueTypeScalar); + value.SetCompilerType (clang_void_pointer_type); + arguments.PushValue (value); + arguments.PushValue (value); + + value.SetValueType (Value::eValueTypeScalar); + value.SetCompilerType (clang_uint32_t_type); + arguments.PushValue (value); + + get_class_info_function = m_get_class_info_code->MakeFunctionCaller(clang_uint32_t_type, + arguments, + error); + + if (error.Fail()) + { + if (log) + log->Printf("Failed to make function caller for implementation lookup: %s.", error.AsCString()); + return false; + } + } + else + { + get_class_info_function = m_get_class_info_code->GetFunctionCaller(); + if (!get_class_info_function) + { + if (log) + log->Printf ("Failed to get implementation lookup function caller: %s.", errors.GetData()); + return false; + } + arguments = get_class_info_function->GetArgumentValues(); + } + + errors.Clear(); + + 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) + return false; + + Mutex::Locker locker(m_get_class_info_args_mutex); + + // Fill in our function argument values + arguments.GetValueAtIndex(0)->GetScalar() = hash_table.GetTableLoadAddress(); + arguments.GetValueAtIndex(1)->GetScalar() = class_infos_addr; + arguments.GetValueAtIndex(2)->GetScalar() = class_infos_byte_size; + + bool success = false; + + errors.Clear(); + + // Write our function arguments into the process so we can run our function + if (get_class_info_function->WriteFunctionArguments (exe_ctx, + m_get_class_info_args, + arguments, + errors)) + { + EvaluateExpressionOptions options; + options.SetUnwindOnError(true); + options.SetTryAllThreads(false); + options.SetStopOthers(true); + options.SetIgnoreBreakpoints(true); + options.SetTimeoutUsec(UTILITY_FUNCTION_TIMEOUT_USEC); + + Value return_value; + return_value.SetValueType (Value::eValueTypeScalar); + //return_value.SetContext (Value::eContextTypeClangType, clang_uint32_t_type); + return_value.SetCompilerType (clang_uint32_t_type); + return_value.GetScalar() = 0; + + errors.Clear(); + + // Run the function + ExpressionResults results = get_class_info_function->ExecuteFunction (exe_ctx, + &m_get_class_info_args, + options, + errors, + return_value); + + if (results == eExpressionCompleted) + { + // The result is the number of ClassInfo structures that were filled in + uint32_t num_class_infos = return_value.GetScalar().ULong(); + if (log) + log->Printf("Discovered %u ObjC classes\n",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); + ParseClassInfoArray (class_infos_data, num_class_infos); + } + } + success = true; + } + else + { + if (log) + log->Printf("Error evaluating our find class name function: %s.\n", errors.GetData()); + } + } + else + { + if (log) + log->Printf ("Error writing function arguments: \"%s\".", errors.GetData()); + } + + // Deallocate the memory we allocated for the ClassInfo array + process->DeallocateMemory(class_infos_addr); + + return success; +} + +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(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + 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.GetPointer(&offset); + + if (isa == 0) + { + if (log) + log->Printf("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)) + { + 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, NULL)); + AddClass (isa, descriptor_sp, name_hash); + num_parsed++; + if (log && log->GetVerbose()) + log->Printf("AppleObjCRuntimeV2 added isa=0x%" PRIx64 ", hash=0x%8.8x, name=%s", isa, name_hash,descriptor_sp->GetClassName().AsCString("<unknown>")); + } + } + return num_parsed; +} + +AppleObjCRuntimeV2::DescriptorMapUpdateResult +AppleObjCRuntimeV2::UpdateISAToDescriptorMapSharedCache() +{ + Process *process = GetProcess(); + + if (process == NULL) + return DescriptorMapUpdateResult::Fail(); + + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + ExecutionContext exe_ctx; + + ThreadSP thread_sp = process->GetThreadList().GetSelectedThread(); + + if (!thread_sp) + return DescriptorMapUpdateResult::Fail(); + + thread_sp->CalculateExecutionContext(exe_ctx); + ClangASTContext *ast = process->GetTarget().GetScratchClangASTContext(); + + if (!ast) + return DescriptorMapUpdateResult::Fail(); + + Address function_address; + + StreamString errors; + + const uint32_t addr_size = process->GetAddressByteSize(); + + Error err; + + const lldb::addr_t objc_opt_ptr = GetSharedCacheReadOnlyAddress(); + + if (objc_opt_ptr == LLDB_INVALID_ADDRESS) + return DescriptorMapUpdateResult::Fail(); + + // Read the total number of classes from the hash table + const uint32_t num_classes = 128*1024; + if (num_classes == 0) + { + if (log) + log->Printf ("No dynamic classes found in gdb_objc_realized_classes_addr."); + return DescriptorMapUpdateResult::Fail(); + } + + // Make some types for our arguments + CompilerType clang_uint32_t_type = ast->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 32); + CompilerType clang_void_pointer_type = ast->GetBasicType(eBasicTypeVoid).GetPointerType(); + + ValueList arguments; + FunctionCaller *get_shared_cache_class_info_function = nullptr; + + if (!m_get_shared_cache_class_info_code.get()) + { + Error error; + m_get_shared_cache_class_info_code.reset (GetTargetRef().GetUtilityFunctionForLanguage (g_get_shared_cache_class_info_body, + eLanguageTypeObjC, + g_get_shared_cache_class_info_name, + error)); + if (error.Fail()) + { + if (log) + log->Printf ("Failed to get Utility function for implementation lookup: %s.", error.AsCString()); + m_get_shared_cache_class_info_code.reset(); + } + else + { + errors.Clear(); + + if (!m_get_shared_cache_class_info_code->Install(errors, exe_ctx)) + { + if (log) + log->Printf ("Failed to install implementation lookup: %s.", errors.GetData()); + m_get_shared_cache_class_info_code.reset(); + } + } + + if (!m_get_shared_cache_class_info_code.get()) + return DescriptorMapUpdateResult::Fail(); + + // Next make the function caller for our implementation utility function. + Value value; + value.SetValueType (Value::eValueTypeScalar); + //value.SetContext (Value::eContextTypeClangType, clang_void_pointer_type); + value.SetCompilerType (clang_void_pointer_type); + arguments.PushValue (value); + arguments.PushValue (value); + + value.SetValueType (Value::eValueTypeScalar); + //value.SetContext (Value::eContextTypeClangType, clang_uint32_t_type); + value.SetCompilerType (clang_uint32_t_type); + arguments.PushValue (value); + + get_shared_cache_class_info_function = m_get_shared_cache_class_info_code->MakeFunctionCaller(clang_uint32_t_type, + arguments, + error); + + if (get_shared_cache_class_info_function == nullptr) + return DescriptorMapUpdateResult::Fail(); + + } + else + { + get_shared_cache_class_info_function = m_get_shared_cache_class_info_code->GetFunctionCaller(); + if (get_shared_cache_class_info_function == nullptr) + return DescriptorMapUpdateResult::Fail(); + arguments = get_shared_cache_class_info_function->GetArgumentValues(); + } + + errors.Clear(); + + 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) + return DescriptorMapUpdateResult::Fail(); + + Mutex::Locker locker(m_get_shared_cache_class_info_args_mutex); + + // Fill in our function argument values + arguments.GetValueAtIndex(0)->GetScalar() = objc_opt_ptr; + arguments.GetValueAtIndex(1)->GetScalar() = class_infos_addr; + arguments.GetValueAtIndex(2)->GetScalar() = class_infos_byte_size; + + bool success = false; + bool any_found = false; + + errors.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_get_shared_cache_class_info_args, + arguments, + errors)) + { + EvaluateExpressionOptions options; + options.SetUnwindOnError(true); + options.SetTryAllThreads(false); + options.SetStopOthers(true); + options.SetIgnoreBreakpoints(true); + options.SetTimeoutUsec(UTILITY_FUNCTION_TIMEOUT_USEC); + + Value return_value; + return_value.SetValueType (Value::eValueTypeScalar); + //return_value.SetContext (Value::eContextTypeClangType, clang_uint32_t_type); + return_value.SetCompilerType (clang_uint32_t_type); + return_value.GetScalar() = 0; + + errors.Clear(); + + // Run the function + ExpressionResults results = get_shared_cache_class_info_function->ExecuteFunction (exe_ctx, + &m_get_shared_cache_class_info_args, + options, + errors, + return_value); + + if (results == eExpressionCompleted) + { + // The result is the number of ClassInfo structures that were filled in + uint32_t num_class_infos = return_value.GetScalar().ULong(); + if (log) + log->Printf("Discovered %u ObjC classes in shared cache\n",num_class_infos); +#ifdef LLDB_CONFIGURATION_DEBUG + assert (num_class_infos <= num_classes); +#endif + if (num_class_infos > 0) + { + if (num_class_infos > num_classes) + { + num_class_infos = num_classes; + + success = false; + } + else + { + success = true; + } + + // 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); + + any_found = (ParseClassInfoArray (class_infos_data, num_class_infos) > 0); + } + } + else + { + success = true; + } + } + else + { + if (log) + log->Printf("Error evaluating our find class name function: %s.\n", errors.GetData()); + } + } + else + { + if (log) + log->Printf ("Error writing function arguments: \"%s\".", errors.GetData()); + } + + // Deallocate the memory we allocated for the ClassInfo array + process->DeallocateMemory(class_infos_addr); + + return DescriptorMapUpdateResult(success, any_found); +} + +bool +AppleObjCRuntimeV2::UpdateISAToDescriptorMapFromMemory (RemoteNXMapTable &hash_table) +{ + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + Process *process = GetProcess(); + + if (process == NULL) + return false; + + uint32_t num_map_table_isas = 0; + + ModuleSP objc_module_sp(GetObjCModule()); + + if (objc_module_sp) + { + for (RemoteNXMapTable::element elt : hash_table) + { + ++num_map_table_isas; + + if (ISAIsCached(elt.second)) + continue; + + ClassDescriptorSP descriptor_sp = ClassDescriptorSP(new ClassDescriptorV2(*this, elt.second, elt.first.AsCString())); + + if (log && log->GetVerbose()) + log->Printf("AppleObjCRuntimeV2 added (ObjCISA)0x%" PRIx64 " (%s) from dynamic table to isa->descriptor cache", elt.second, elt.first.AsCString()); + + AddClass (elt.second, descriptor_sp, elt.first.AsCString()); + } + } + + return num_map_table_isas > 0; +} + +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; +} + +void +AppleObjCRuntimeV2::UpdateISAToDescriptorMapIfNeeded() +{ + Timer scoped_timer (__PRETTY_FUNCTION__, __PRETTY_FUNCTION__); + + // 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(); + + if (!m_hash_signature.NeedsUpdate(process, this, hash_table)) + return; + + m_hash_signature.UpdateSignature (hash_table); + + // Grab the dynamically loaded objc classes from the hash table in memory + UpdateISAToDescriptorMapDynamic(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) + { + DescriptorMapUpdateResult shared_cache_update_result = UpdateISAToDescriptorMapSharedCache(); + if (!shared_cache_update_result.any_found) + WarnIfNoClassesCached (); + else + m_loaded_objc_opt = true; + } + } + else + { + m_isa_to_descriptor_stop_id = UINT32_MAX; + } +} + +void +AppleObjCRuntimeV2::WarnIfNoClassesCached () +{ + if (m_noclasses_warning_emitted) + return; + +#if defined(__APPLE__) + if (m_process && + m_process->GetTarget().GetPlatform() && + m_process->GetTarget().GetPlatform()->GetPluginName() == PlatformiOSSimulator::GetPluginNameStatic()) + { + // the iOS simulator does not have the objc_opt_ro class table + // so don't actually complain to the user + m_noclasses_warning_emitted = true; + return; + } +#endif + + Debugger &debugger(GetProcess()->GetTarget().GetDebugger()); + + if (debugger.GetAsyncOutputStream()) + { + debugger.GetAsyncOutputStream()->PutCString("warning: could not load any Objective-C class information from the dyld shared cache. This will significantly reduce the quality of type information available.\n"); + m_noclasses_warning_emitted = true; + } +} + +// TODO: should we have a transparent_kvo parameter here to say if we +// want to replace the KVO swizzled class with the actual user-level type? +ConstString +AppleObjCRuntimeV2::GetActualTypeName(ObjCLanguageRuntime::ObjCISA isa) +{ + if (isa == g_objc_Tagged_ISA) + { + static const ConstString g_objc_tagged_isa_name ("_lldb_Tagged_ObjC_ISA"); + return g_objc_tagged_isa_name; + } + if (isa == g_objc_Tagged_ISA_NSAtom) + { + static const ConstString g_objc_tagged_isa_nsatom_name ("NSAtom"); + return g_objc_tagged_isa_nsatom_name; + } + if (isa == g_objc_Tagged_ISA_NSNumber) + { + static const ConstString g_objc_tagged_isa_nsnumber_name ("NSNumber"); + return g_objc_tagged_isa_nsnumber_name; + } + if (isa == g_objc_Tagged_ISA_NSDateTS) + { + static const ConstString g_objc_tagged_isa_nsdatets_name ("NSDateTS"); + return g_objc_tagged_isa_nsdatets_name; + } + if (isa == g_objc_Tagged_ISA_NSManagedObject) + { + static const ConstString g_objc_tagged_isa_nsmanagedobject_name ("NSManagedObject"); + return g_objc_tagged_isa_nsmanagedobject_name; + } + if (isa == g_objc_Tagged_ISA_NSDate) + { + static const ConstString g_objc_tagged_isa_nsdate_name ("NSDate"); + return g_objc_tagged_isa_nsdate_name; + } + return ObjCLanguageRuntime::GetActualTypeName(isa); +} + +DeclVendor * +AppleObjCRuntimeV2::GetDeclVendor() +{ + if (!m_decl_vendor_ap.get()) + m_decl_vendor_ap.reset(new AppleObjCDeclVendor(*this)); + + return m_decl_vendor_ap.get(); +} + +lldb::addr_t +AppleObjCRuntimeV2::LookupRuntimeSymbol (const ConstString &name) +{ + lldb::addr_t ret = LLDB_INVALID_ADDRESS; + + const char *name_cstr = name.AsCString(); + + if (name_cstr) + { + llvm::StringRef name_strref(name_cstr); + + static const llvm::StringRef ivar_prefix("OBJC_IVAR_$_"); + static const llvm::StringRef class_prefix("OBJC_CLASS_$_"); + + if (name_strref.startswith(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.size() && class_and_ivar.second.size()) + { + 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.startswith(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()); + + Error error; + + auto objc_debug_isa_magic_mask = ExtractRuntimeGlobalSymbol(process, + ConstString("objc_debug_isa_magic_mask"), + objc_module_sp, + error); + if (error.Fail()) + return NULL; + + auto objc_debug_isa_magic_value = ExtractRuntimeGlobalSymbol(process, + ConstString("objc_debug_isa_magic_value"), + objc_module_sp, + error); + if (error.Fail()) + return NULL; + + auto objc_debug_isa_class_mask = ExtractRuntimeGlobalSymbol(process, + ConstString("objc_debug_isa_class_mask"), + objc_module_sp, + error); + if (error.Fail()) + return NULL; + + // 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_debug_isa_class_mask, + objc_debug_isa_magic_mask, + objc_debug_isa_magic_value); +} + +AppleObjCRuntimeV2::TaggedPointerVendorV2* +AppleObjCRuntimeV2::TaggedPointerVendorV2::CreateInstance (AppleObjCRuntimeV2& runtime, const lldb::ModuleSP& objc_module_sp) +{ + Process* process(runtime.GetProcess()); + + Error 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); + + + // 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; + + // TODO: make a table + if (foundation_version >= 900) + { + switch (class_bits) + { + case 0: + name = ConstString("NSAtom"); + break; + case 3: + name = ConstString("NSNumber"); + break; + case 4: + name = ConstString("NSDateTS"); + break; + case 5: + name = ConstString("NSManagedObject"); + break; + case 6: + name = ConstString("NSDate"); + break; + default: + return ObjCLanguageRuntime::ClassDescriptorSP(); + } + } + else + { + switch (class_bits) + { + case 1: + name = ConstString("NSNumber"); + break; + case 5: + name = ConstString("NSManagedObject"); + break; + case 6: + name = ConstString("NSDate"); + break; + case 7: + name = ConstString("NSDateTS"); + break; + default: + return ObjCLanguageRuntime::ClassDescriptorSP(); + } + } + return ClassDescriptorSP(new ClassDescriptorV2Tagged(name,ptr)); +} + +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 data_payload; + + if (!IsPossibleTaggedPointer(ptr)) + 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; + Error 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_cache[slot] = actual_class_descriptor_sp; + } + + data_payload = (((uint64_t)ptr << m_objc_debug_taggedpointer_payload_lshift) >> m_objc_debug_taggedpointer_payload_rshift); + + return ClassDescriptorSP(new ClassDescriptorV2Tagged(actual_class_descriptor_sp,data_payload)); +} + +AppleObjCRuntimeV2::NonPointerISACache::NonPointerISACache (AppleObjCRuntimeV2& runtime, + uint64_t objc_debug_isa_class_mask, + uint64_t objc_debug_isa_magic_mask, + uint64_t objc_debug_isa_magic_value) : +m_runtime(runtime), +m_cache(), +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) +{ +} + +ObjCLanguageRuntime::ClassDescriptorSP +AppleObjCRuntimeV2::NonPointerISACache::GetClassDescriptor (ObjCISA isa) +{ + ObjCISA real_isa = 0; + if (EvaluateNonPointerISA(isa, real_isa) == false) + 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) +{ + if ( (isa & ~m_objc_debug_isa_class_mask) == 0) + return false; + 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.reset(new AppleObjCTypeEncodingParser(*this)); + return m_encoding_to_type_sp; +} + +lldb_private::AppleObjCRuntime::ObjCISA +AppleObjCRuntimeV2::GetPointerISA (ObjCISA isa) +{ + ObjCISA ret = isa; + + if (m_non_pointer_isa_cache_ap) + m_non_pointer_isa_cache_ap->EvaluateNonPointerISA(isa, ret); + + return ret; +} diff --git a/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h new file mode 100644 index 000000000000..96b47e8770f9 --- /dev/null +++ b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h @@ -0,0 +1,336 @@ +//===-- AppleObjCRuntimeV2.h ------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_AppleObjCRuntimeV2_h_ +#define liblldb_AppleObjCRuntimeV2_h_ + +// C Includes +// C++ Includes +#include <map> +#include <memory> + +// Other libraries and framework includes +// Project includes +#include "lldb/lldb-private.h" +#include "lldb/Target/ObjCLanguageRuntime.h" +#include "AppleObjCRuntime.h" + +class RemoteNXMapTable; + +namespace lldb_private { + +class AppleObjCRuntimeV2 : + public AppleObjCRuntime +{ +public: + ~AppleObjCRuntimeV2() override = default; + + //------------------------------------------------------------------ + // Static Functions + //------------------------------------------------------------------ + static void + Initialize(); + + static void + Terminate(); + + static lldb_private::LanguageRuntime * + CreateInstance (Process *process, lldb::LanguageType language); + + static lldb_private::ConstString + GetPluginNameStatic(); + + static bool classof(const ObjCLanguageRuntime* runtime) + { + switch (runtime->GetRuntimeVersion()) + { + case ObjCRuntimeVersions::eAppleObjC_V2: + return true; + default: + return false; + } + } + + // 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; + + UtilityFunction * + CreateObjectChecker(const char *) override; + + //------------------------------------------------------------------ + // PluginInterface protocol + //------------------------------------------------------------------ + ConstString + GetPluginName() override; + + uint32_t + GetPluginVersion() override; + + ObjCRuntimeVersions + GetRuntimeVersion() const override + { + return ObjCRuntimeVersions::eAppleObjC_V2; + } + + size_t + GetByteOffsetForIvar(CompilerType &parent_qual_type, const char *ivar_name) override; + + void + UpdateISAToDescriptorMapIfNeeded() override; + + ConstString + GetActualTypeName(ObjCLanguageRuntime::ObjCISA isa) override; + + ClassDescriptorSP + GetClassDescriptor(ValueObject& in_value) override; + + ClassDescriptorSP + GetClassDescriptorFromISA(ObjCISA isa) override; + + DeclVendor * + GetDeclVendor() override; + + lldb::addr_t + LookupRuntimeSymbol(const ConstString &name) override; + + EncodingToTypeSP + GetEncodingToType() override; + + TaggedPointerVendor* + GetTaggedPointerVendor() override + { + return m_tagged_pointer_vendor_ap.get(); + } + + // none of these are valid ISAs - we use them to infer the type + // of tagged pointers - if we have something meaningful to say + // we report an actual type - otherwise, we just say tagged + // there is no connection between the values here and the tagged pointers map + static const ObjCLanguageRuntime::ObjCISA g_objc_Tagged_ISA = 1; + static const ObjCLanguageRuntime::ObjCISA g_objc_Tagged_ISA_NSAtom = 2; + static const ObjCLanguageRuntime::ObjCISA g_objc_Tagged_ISA_NSNumber = 3; + static const ObjCLanguageRuntime::ObjCISA g_objc_Tagged_ISA_NSDateTS = 4; + static const ObjCLanguageRuntime::ObjCISA g_objc_Tagged_ISA_NSManagedObject = 5; + static const ObjCLanguageRuntime::ObjCISA g_objc_Tagged_ISA_NSDate = 6; + +protected: + lldb::BreakpointResolverSP + CreateExceptionResolver(Breakpoint *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; + uint32_t m_num_buckets; + lldb::addr_t m_buckets_ptr; + }; + + class NonPointerISACache + { + public: + static NonPointerISACache* + CreateInstance (AppleObjCRuntimeV2& runtime, + const lldb::ModuleSP& objc_module_sp); + + + ObjCLanguageRuntime::ClassDescriptorSP + GetClassDescriptor (ObjCISA isa); + + private: + NonPointerISACache (AppleObjCRuntimeV2& runtime, + uint64_t objc_debug_isa_class_mask, + uint64_t objc_debug_isa_magic_mask, + uint64_t objc_debug_isa_magic_value); + + bool + EvaluateNonPointerISA (ObjCISA isa, ObjCISA& ret_isa); + + AppleObjCRuntimeV2& m_runtime; + std::map<ObjCISA,ObjCLanguageRuntime::ClassDescriptorSP> m_cache; + uint64_t m_objc_debug_isa_class_mask; + uint64_t m_objc_debug_isa_magic_mask; + uint64_t m_objc_debug_isa_magic_value; + + friend class AppleObjCRuntimeV2; + + DISALLOW_COPY_AND_ASSIGN(NonPointerISACache); + }; + + 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: + DISALLOW_COPY_AND_ASSIGN(TaggedPointerVendorV2); + }; + + 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; + + DISALLOW_COPY_AND_ASSIGN(TaggedPointerVendorRuntimeAssisted); + }; + + 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; + + DISALLOW_COPY_AND_ASSIGN(TaggedPointerVendorLegacy); + }; + + struct DescriptorMapUpdateResult + { + bool update_ran; + bool any_found; + + DescriptorMapUpdateResult (bool ran, + bool found) + { + update_ran = ran; + any_found = found; + } + + static DescriptorMapUpdateResult + Fail () + { + return {false, false}; + } + + static DescriptorMapUpdateResult + Success () + { + return {true, true}; + } + }; + + AppleObjCRuntimeV2 (Process *process, + const lldb::ModuleSP &objc_module_sp); + + ObjCISA + GetPointerISA (ObjCISA isa); + + bool + IsTaggedPointer(lldb::addr_t ptr); + + lldb::addr_t + GetISAHashTablePointer (); + + bool + UpdateISAToDescriptorMapFromMemory (RemoteNXMapTable &hash_table); + + bool + UpdateISAToDescriptorMapDynamic(RemoteNXMapTable &hash_table); + + uint32_t + ParseClassInfoArray (const lldb_private::DataExtractor &data, + uint32_t num_class_infos); + + DescriptorMapUpdateResult + UpdateISAToDescriptorMapSharedCache (); + + void + WarnIfNoClassesCached (); + + lldb::addr_t + GetSharedCacheReadOnlyAddress(); + + friend class ClassDescriptorV2; + + std::unique_ptr<UtilityFunction> m_get_class_info_code; + lldb::addr_t m_get_class_info_args; + Mutex m_get_class_info_args_mutex; + + std::unique_ptr<UtilityFunction> m_get_shared_cache_class_info_code; + lldb::addr_t m_get_shared_cache_class_info_args; + Mutex m_get_shared_cache_class_info_args_mutex; + + std::unique_ptr<DeclVendor> m_decl_vendor_ap; + lldb::addr_t m_isa_hash_table_ptr; + HashTableSignature m_hash_signature; + bool m_has_object_getClass; + bool m_loaded_objc_opt; + std::unique_ptr<NonPointerISACache> m_non_pointer_isa_cache_ap; + std::unique_ptr<TaggedPointerVendor> m_tagged_pointer_vendor_ap; + EncodingToTypeSP m_encoding_to_type_sp; + bool m_noclasses_warning_emitted; +}; + +} // namespace lldb_private + +#endif // liblldb_AppleObjCRuntimeV2_h_ diff --git a/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp new file mode 100644 index 000000000000..d38a076ad5d7 --- /dev/null +++ b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.cpp @@ -0,0 +1,1144 @@ +//===-- AppleObjCTrampolineHandler.cpp ----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "AppleObjCTrampolineHandler.h" + +// C Includes +// C++ Includes +// Other libraries and framework includes +// Project includes +#include "AppleThreadPlanStepThroughObjCTrampoline.h" + +#include "lldb/Breakpoint/StoppointCallbackContext.h" +#include "lldb/Core/ConstString.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Log.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/StreamFile.h" +#include "lldb/Core/Value.h" +#include "lldb/Expression/UserExpression.h" +#include "lldb/Expression/FunctionCaller.h" +#include "lldb/Expression/UtilityFunction.h" +#include "lldb/Host/FileSpec.h" +#include "lldb/Symbol/ClangASTContext.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Target/ABI.h" +#include "lldb/Target/ObjCLanguageRuntime.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/ThreadPlanRunToAddress.h" + +#include "llvm/ADT/STLExtras.h" + +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_function_code = NULL; +const char *AppleObjCTrampolineHandler::g_lookup_implementation_with_stret_function_code = " \n\ +extern \"C\" \n\ +{ \n\ + extern void *class_getMethodImplementation(void *objc_class, void *sel); \n\ + extern void *class_getMethodImplementation_stret(void *objc_class, void *sel); \n\ + extern void * object_getClass (id object); \n\ + extern void * sel_getUid(char *name); \n\ + extern int printf(const char *format, ...); \n\ +} \n\ +extern \"C\" void * __lldb_objc_find_implementation_for_selector (void *object, \n\ + void *sel, \n\ + int is_stret, \n\ + int is_super, \n\ + int is_super2, \n\ + int is_fixup, \n\ + int is_fixed, \n\ + int debug) \n\ +{ \n\ + struct __lldb_imp_return_struct \n\ + { \n\ + void *class_addr; \n\ + void *sel_addr; \n\ + void *impl_addr; \n\ + }; \n\ + \n\ + struct __lldb_objc_class { \n\ + void *isa; \n\ + void *super_ptr; \n\ + }; \n\ + struct __lldb_objc_super { \n\ + void *reciever; \n\ + struct __lldb_objc_class *class_ptr; \n\ + }; \n\ + struct __lldb_msg_ref { \n\ + void *dont_know; \n\ + void *sel; \n\ + }; \n\ + \n\ + struct __lldb_imp_return_struct return_struct; \n\ + \n\ + if (debug) \n\ + printf (\"\\n*** Called with obj: 0x%p sel: 0x%p is_stret: %d is_super: %d, \" \n\ + \"is_super2: %d, is_fixup: %d, is_fixed: %d\\n\", \n\ + object, sel, is_stret, is_super, is_super2, is_fixup, is_fixed); \n\ + if (is_super) \n\ + { \n\ + if (is_super2) \n\ + { \n\ + return_struct.class_addr = ((__lldb_objc_super *) object)->class_ptr->super_ptr; \n\ + } \n\ + else \n\ + { \n\ + return_struct.class_addr = ((__lldb_objc_super *) object)->class_ptr; \n\ + } \n\ + } \n\ + else \n\ + { \n\ + // This code seems a little funny, but has its reasons... \n\ + // The call to [object class] is here because if this is a class, and has not been called into \n\ + // yet, we need to do something to force the class to initialize itself. \n\ + // Then the call to object_getClass will actually return the correct class, either the class \n\ + // if object is a class instance, or the meta-class if it is a class pointer. \n\ + void *class_ptr = (void *) [(id) object class]; \n\ + return_struct.class_addr = (id) object_getClass((id) object); \n\ + if (debug) \n\ + { \n\ + if (class_ptr == object) \n\ + { \n\ + printf (\"Found a class object, need to use the meta class %p -> %p\\n\", \n\ + class_ptr, return_struct.class_addr); \n\ + } \n\ + else \n\ + { \n\ + printf (\"[object class] returned: %p object_getClass: %p.\\n\", \n\ + class_ptr, return_struct.class_addr); \n\ + } \n\ + } \n\ + } \n\ + \n\ + if (is_fixup) \n\ + { \n\ + if (is_fixed) \n\ + { \n\ + return_struct.sel_addr = ((__lldb_msg_ref *) sel)->sel; \n\ + } \n\ + else \n\ + { \n\ + char *sel_name = (char *) ((__lldb_msg_ref *) sel)->sel; \n\ + return_struct.sel_addr = sel_getUid (sel_name); \n\ + if (debug) \n\ + printf (\"\\n*** Got fixed up selector: %p for name %s.\\n\", \n\ + return_struct.sel_addr, sel_name); \n\ + } \n\ + } \n\ + else \n\ + { \n\ + return_struct.sel_addr = sel; \n\ + } \n\ + \n\ + if (is_stret) \n\ + { \n\ + return_struct.impl_addr = class_getMethodImplementation_stret (return_struct.class_addr, \n\ + return_struct.sel_addr); \n\ + } \n\ + else \n\ + { \n\ + return_struct.impl_addr = class_getMethodImplementation (return_struct.class_addr, \n\ + return_struct.sel_addr); \n\ + } \n\ + if (debug) \n\ + printf (\"\\n*** Returning implementation: %p.\\n\", return_struct.impl_addr); \n\ + \n\ + return return_struct.impl_addr; \n\ +} \n\ +"; +const char *AppleObjCTrampolineHandler::g_lookup_implementation_no_stret_function_code = " \n\ +extern \"C\" \n\ +{ \n\ + extern void *class_getMethodImplementation(void *objc_class, void *sel); \n\ + extern void * object_getClass (id object); \n\ + extern void * sel_getUid(char *name); \n\ + extern int printf(const char *format, ...); \n\ +} \n\ +extern \"C\" void * __lldb_objc_find_implementation_for_selector (void *object, \n\ + void *sel, \n\ + int is_stret, \n\ + int is_super, \n\ + int is_super2, \n\ + int is_fixup, \n\ + int is_fixed, \n\ + int debug) \n\ +{ \n\ + struct __lldb_imp_return_struct \n\ + { \n\ + void *class_addr; \n\ + void *sel_addr; \n\ + void *impl_addr; \n\ + }; \n\ + \n\ + struct __lldb_objc_class { \n\ + void *isa; \n\ + void *super_ptr; \n\ + }; \n\ + struct __lldb_objc_super { \n\ + void *reciever; \n\ + struct __lldb_objc_class *class_ptr; \n\ + }; \n\ + struct __lldb_msg_ref { \n\ + void *dont_know; \n\ + void *sel; \n\ + }; \n\ + \n\ + struct __lldb_imp_return_struct return_struct; \n\ + \n\ + if (debug) \n\ + printf (\"\\n*** Called with obj: 0x%p sel: 0x%p is_stret: %d is_super: %d, \" \n\ + \"is_super2: %d, is_fixup: %d, is_fixed: %d\\n\", \n\ + object, sel, is_stret, is_super, is_super2, is_fixup, is_fixed); \n\ + if (is_super) \n\ + { \n\ + if (is_super2) \n\ + { \n\ + return_struct.class_addr = ((__lldb_objc_super *) object)->class_ptr->super_ptr; \n\ + } \n\ + else \n\ + { \n\ + return_struct.class_addr = ((__lldb_objc_super *) object)->class_ptr; \n\ + } \n\ + } \n\ + else \n\ + { \n\ + // This code seems a little funny, but has its reasons... \n\ + // The call to [object class] is here because if this is a class, and has not been called into \n\ + // yet, we need to do something to force the class to initialize itself. \n\ + // Then the call to object_getClass will actually return the correct class, either the class \n\ + // if object is a class instance, or the meta-class if it is a class pointer. \n\ + void *class_ptr = (void *) [(id) object class]; \n\ + return_struct.class_addr = (id) object_getClass((id) object); \n\ + if (debug) \n\ + { \n\ + if (class_ptr == object) \n\ + { \n\ + printf (\"Found a class object, need to return the meta class %p -> %p\\n\", \n\ + class_ptr, return_struct.class_addr); \n\ + } \n\ + else \n\ + { \n\ + printf (\"[object class] returned: %p object_getClass: %p.\\n\", \n\ + class_ptr, return_struct.class_addr); \n\ + } \n\ + } \n\ + } \n\ + \n\ + if (is_fixup) \n\ + { \n\ + if (is_fixed) \n\ + { \n\ + return_struct.sel_addr = ((__lldb_msg_ref *) sel)->sel; \n\ + } \n\ + else \n\ + { \n\ + char *sel_name = (char *) ((__lldb_msg_ref *) sel)->sel; \n\ + return_struct.sel_addr = sel_getUid (sel_name); \n\ + if (debug) \n\ + printf (\"\\n*** Got fixed up selector: %p for name %s.\\n\", \n\ + return_struct.sel_addr, sel_name); \n\ + } \n\ + } \n\ + else \n\ + { \n\ + return_struct.sel_addr = sel; \n\ + } \n\ + \n\ + return_struct.impl_addr = class_getMethodImplementation (return_struct.class_addr, \n\ + return_struct.sel_addr); \n\ + if (debug) \n\ + printf (\"\\n*** Returning implementation: 0x%p.\\n\", return_struct.impl_addr); \n\ + \n\ + return return_struct.impl_addr; \n\ +} \n\ +"; + +AppleObjCTrampolineHandler::AppleObjCVTables::VTableRegion::VTableRegion(AppleObjCVTables *owner, lldb::addr_t header_addr) : + m_valid (true), + m_owner(owner), + m_header_addr (header_addr), + m_code_start_addr(0), + m_code_end_addr (0), + m_next_region (0) +{ + SetUpRegion (); +} + +AppleObjCTrampolineHandler::~AppleObjCTrampolineHandler() +{ +} + +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(); + Error 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.GetPointer(&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; + DataBufferSP 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(); + + const ModuleList &target_modules = target.GetImages(); + Mutex::Locker modules_locker(target_modules.GetMutex()); + size_t num_modules = target_modules.GetSize(); + if (!m_objc_module_sp) + { + for (size_t i = 0; i < num_modules; i++) + { + if (process_sp->GetObjCLanguageRuntime()->IsModuleObjCLibrary (target_modules.GetModuleAtIndexUnlocked(i))) + { + m_objc_module_sp = target_modules.GetModuleAtIndexUnlocked(i); + 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 != NULL) + { + 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 != NULL) + { + 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(); + + ClangASTContext *clang_ast_context = process->GetTarget().GetScratchClangASTContext(); + ValueList argument_values; + Value input_value; + CompilerType clang_void_ptr_type = clang_ast_context->GetBasicType(eBasicTypeVoid).GetPointerType(); + + input_value.SetValueType (Value::eValueTypeScalar); + //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. + Error error; + DataExtractor data; + error = argument_values.GetValueAtIndex(0)->GetValueAsData (&exe_ctx, + data, + 0, + NULL); + lldb::offset_t offset = 0; + lldb::addr_t region_addr = data.GetPointer(&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; + Error 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(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_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); + log->Printf("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 }, +}; + +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) +{ + 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() : NULL; + 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().GetErrorFile()->Printf ("Could not find implementation lookup function \"%s\"" + " step in through ObjC method dispatch will not work.\n", + get_impl_name.AsCString()); + } + return; + } + else 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. + g_lookup_implementation_function_code = g_lookup_implementation_no_stret_function_code; + } + else + { + g_lookup_implementation_function_code = 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 != llvm::array_lengthof(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)); + } + } + + // Build our vtable dispatch handler here: + m_vtables_ap.reset(new AppleObjCVTables(process_sp, m_objc_module_sp)); + if (m_vtables_ap.get()) + m_vtables_ap->ReadRegions(); +} + +lldb::addr_t +AppleObjCTrampolineHandler::SetupDispatchFunction (Thread &thread, ValueList &dispatch_values) +{ + ExecutionContext exe_ctx (thread.shared_from_this()); + StreamString errors; + Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_STEP)); + lldb::addr_t args_addr = LLDB_INVALID_ADDRESS; + FunctionCaller *impl_function_caller = nullptr; + + // Scope for mutex locker: + { + Mutex::Locker locker(m_impl_function_mutex); + + // First stage is to make the ClangUtility to hold our injected function: + + if (!m_impl_code.get()) + { + if (g_lookup_implementation_function_code != NULL) + { + Error error; + m_impl_code.reset (exe_ctx.GetTargetRef().GetUtilityFunctionForLanguage (g_lookup_implementation_function_code, + eLanguageTypeObjC, + g_lookup_implementation_function_name, + error)); + if (error.Fail()) + { + if (log) + log->Printf ("Failed to get Utility Function for implementation lookup: %s.", error.AsCString()); + m_impl_code.reset(); + return args_addr; + } + + if (!m_impl_code->Install(errors, exe_ctx)) + { + if (log) + log->Printf ("Failed to install implementation lookup: %s.", errors.GetData()); + m_impl_code.reset(); + return args_addr; + } + } + else + { + if (log) + log->Printf("No method lookup implementation code."); + errors.Printf ("No method lookup implementation code found."); + return LLDB_INVALID_ADDRESS; + } + + + // Next make the runner function for our implementation utility function. + ClangASTContext *clang_ast_context = thread.GetProcess()->GetTarget().GetScratchClangASTContext(); + CompilerType clang_void_ptr_type = clang_ast_context->GetBasicType(eBasicTypeVoid).GetPointerType(); + Error error; + + impl_function_caller = m_impl_code->MakeFunctionCaller(clang_void_ptr_type, + dispatch_values, + error); + if (error.Fail()) + { + if (log) + log->Printf ("Error getting function caller for dispatch lookup: \"%s\".", error.AsCString()); + return args_addr; + } + } + else + { + impl_function_caller = m_impl_code->GetFunctionCaller(); + } + } + + errors.Clear(); + + // 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... + + if (impl_function_caller->WriteFunctionArguments (exe_ctx, args_addr, dispatch_values, errors)) + { + if (log) + log->Printf ("Error writing function arguments: \"%s\".", errors.GetData()); + return args_addr; + } + + return args_addr; +} + +ThreadPlanSP +AppleObjCTrampolineHandler::GetStepThroughDispatchPlan (Thread &thread, bool stop_others) +{ + ThreadPlanSP ret_plan_sp; + lldb::addr_t curr_pc = thread.GetRegisterContext()->GetPC(); + + DispatchFunction this_dispatch; + bool found_it = false; + + // First 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. + + MsgsendMap::iterator pos; + pos = m_msgSend_map.find (curr_pc); + if (pos != m_msgSend_map.end()) + { + this_dispatch = g_dispatch_functions[(*pos).second]; + found_it = true; + } + + // Next check to see if we are in a vtable region: + + if (!found_it) + { + uint32_t flags; + if (m_vtables_ap.get()) + { + found_it = m_vtables_ap->IsAddressInVTables (curr_pc, flags); + if (found_it) + { + this_dispatch.name = "vtable"; + this_dispatch.stret_return + = (flags & AppleObjCVTables::eOBJC_TRAMPOLINE_STRET) == AppleObjCVTables::eOBJC_TRAMPOLINE_STRET; + this_dispatch.is_super = false; + this_dispatch.is_super2 = false; + this_dispatch.fixedup = DispatchFunction::eFixUpFixed; + } + } + } + + if (found_it) + { + Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_STEP)); + + // We are decoding a method dispatch. + // First job is to pull the arguments out: + + lldb::StackFrameSP thread_cur_frame = thread.GetStackFrameAtIndex(0); + + const ABI *abi = NULL; + ProcessSP process_sp (thread.CalculateProcess()); + if (process_sp) + abi = process_sp->GetABI().get(); + if (abi == NULL) + return ret_plan_sp; + + TargetSP target_sp (thread.CalculateTarget()); + + ClangASTContext *clang_ast_context = target_sp->GetScratchClangASTContext(); + ValueList argument_values; + Value void_ptr_value; + CompilerType clang_void_ptr_type = clang_ast_context->GetBasicType(eBasicTypeVoid).GetPointerType(); + void_ptr_value.SetValueType (Value::eValueTypeScalar); + //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 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 (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) + { + if (log) + log->Printf("Asked to step to dispatch to nil object, returning empty plan."); + return ret_plan_sp; + } + + ExecutionContext exe_ctx (thread.shared_from_this()); + Process *process = exe_ctx.GetProcessPtr(); + // 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 = 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->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->GetAddressByteSize(); + super_value.ResolveValue (&exe_ctx); + if (super_value.GetScalar().IsValid()) + isa_addr = super_value.GetScalar().ULongLong(); + else + { + if (log) + log->Printf("Failed to extract the super class value from the class in objc_super."); + } + } + else + { + if (log) + log->Printf("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->GetAddressByteSize(); + super_value.ResolveValue (&exe_ctx); + + if (super_value.GetScalar().IsValid()) + { + isa_addr = super_value.GetScalar().ULongLong(); + } + else + { + if (log) + log->Printf("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::eValueTypeLoadAddress); + isa_value.ResolveValue(&exe_ctx); + if (isa_value.GetScalar().IsValid()) + { + isa_addr = isa_value.GetScalar().ULongLong(); + } + else + { + if (log) + log->Printf("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 (isa_addr != LLDB_INVALID_ADDRESS) + { + if (log) + { + log->Printf("Resolving call for class - 0x%" PRIx64 " and selector - 0x%" PRIx64, + isa_addr, sel_addr); + } + ObjCLanguageRuntime *objc_runtime = thread.GetProcess()->GetObjCLanguageRuntime (); + assert(objc_runtime != NULL); + + impl_addr = objc_runtime->LookupInMethodCache (isa_addr, sel_addr); + } + + if (impl_addr != LLDB_INVALID_ADDRESS) + { + // Yup, it was in the cache, so we can run to that address directly. + + if (log) + log->Printf ("Found implementation address in cache: 0x%" PRIx64, impl_addr); + + ret_plan_sp.reset (new 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 and some flags, + // and figures out the implementation. Looks like: + // void *__lldb_objc_find_implementation_for_selector (void *object, + // void *sel, + // int is_stret, + // int is_super, + // int is_super2, + // int is_fixup, + // int is_fixed, + // int debug) + // So set up the arguments for that call. + + dispatch_values.PushValue (*(argument_values.GetValueAtIndex(obj_index))); + dispatch_values.PushValue (*(argument_values.GetValueAtIndex(sel_index))); + + Value flag_value; + CompilerType clang_int_type = clang_ast_context->GetBuiltinTypeForEncodingAndBitSize(lldb::eEncodingSint, 32); + flag_value.SetValueType (Value::eValueTypeScalar); + //flag_value.SetContext (Value::eContextTypeClangType, clang_int_type); + flag_value.SetCompilerType (clang_int_type); + + 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); + + + // The step through code might have to fill in the cache, so it is not safe to run only one thread. + // So we override the stop_others value passed in to us here: + const bool trampoline_stop_others = false; + ret_plan_sp.reset (new AppleThreadPlanStepThroughObjCTrampoline (thread, + this, + dispatch_values, + isa_addr, + sel_addr, + trampoline_stop_others)); + if (log) + { + StreamString s; + ret_plan_sp->GetDescription(&s, eDescriptionLevelFull); + log->Printf("Using ObjC step plan: %s.\n", s.GetData()); + } + } + } + + return ret_plan_sp; +} + +FunctionCaller * +AppleObjCTrampolineHandler::GetLookupImplementationFunctionCaller () +{ + return m_impl_code->GetFunctionCaller(); +} diff --git a/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.h b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.h new file mode 100644 index 000000000000..42d3461ddfa5 --- /dev/null +++ b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTrampolineHandler.h @@ -0,0 +1,209 @@ +//===-- AppleObjCTrampolineHandler.h ----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef lldb_AppleObjCTrampolineHandler_h_ +#define lldb_AppleObjCTrampolineHandler_h_ + +// C Includes +// C++ Includes +#include <map> +#include <vector> + +// Other libraries and framework includes +// Project includes +#include "lldb/lldb-public.h" +#include "lldb/Host/Mutex.h" +#include "lldb/Expression/UtilityFunction.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: + typedef enum + { + eFixUpNone, + eFixUpFixed, + eFixUpToFix + } FixUpState; + + const char *name; + bool stret_return; + bool is_super; + bool is_super2; + FixUpState fixedup; + }; + + lldb::addr_t + SetupDispatchFunction (Thread &thread, ValueList &dispatch_values); + +private: + static const char *g_lookup_implementation_function_name; + static const char *g_lookup_implementation_function_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() : + m_valid (false), + m_owner (NULL), + m_header_addr (LLDB_INVALID_ADDRESS), + m_code_start_addr(0), + m_code_end_addr (0), + m_next_region (0) + {} + + 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); + + public: + bool m_valid; + AppleObjCVTables *m_owner; + lldb::addr_t m_header_addr; + lldb::addr_t m_code_start_addr; + lldb::addr_t m_code_end_addr; + std::vector<VTableDescriptor> m_descriptors; + lldb::addr_t m_next_region; + }; + + 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[]; + + typedef std::map<lldb::addr_t, int> MsgsendMap; // This table maps an dispatch fn address to the index in g_dispatch_functions + MsgsendMap m_msgSend_map; + lldb::ProcessWP m_process_wp; + lldb::ModuleSP m_objc_module_sp; + std::unique_ptr<UtilityFunction> m_impl_code; + 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_ap; +}; + +} // namespace lldb_private + +#endif // lldb_AppleObjCTrampolineHandler_h_ diff --git a/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTypeEncodingParser.cpp b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTypeEncodingParser.cpp new file mode 100644 index 000000000000..9308c7a668d2 --- /dev/null +++ b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTypeEncodingParser.cpp @@ -0,0 +1,398 @@ +//===-- AppleObjCTypeEncodingParser.cpp -------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "AppleObjCTypeEncodingParser.h" + +#include "lldb/Symbol/ClangASTContext.h" +#include "lldb/Symbol/CompilerType.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/StringLexer.h" + +#include <vector> + +using namespace lldb_private; +using namespace lldb_utility; + +AppleObjCTypeEncodingParser::AppleObjCTypeEncodingParser (ObjCLanguageRuntime& runtime) : + ObjCLanguageRuntime::EncodingToType(), + m_runtime(runtime) +{ + if (!m_scratch_ast_ctx_ap) + m_scratch_ast_ctx_ap.reset(new ClangASTContext(runtime.GetProcess()->GetTarget().GetArchitecture().GetTriple().str().c_str())); +} + +std::string +AppleObjCTypeEncodingParser::ReadStructName(lldb_utility::StringLexer& type) +{ + StreamString buffer; + while (type.HasAtLeast(1) && type.Peek() != '=') + buffer.Printf("%c",type.Next()); + return buffer.GetString(); +} + +std::string +AppleObjCTypeEncodingParser::ReadQuotedString(lldb_utility::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 buffer.GetString(); +} + +uint32_t +AppleObjCTypeEncodingParser::ReadNumber (lldb_utility::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() : +name(""), +type(clang::QualType()), +bitfield(0) +{} + +AppleObjCTypeEncodingParser::StructElement +AppleObjCTypeEncodingParser::ReadStructElement (clang::ASTContext &ast_ctx, lldb_utility::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 (clang::ASTContext &ast_ctx, lldb_utility::StringLexer& type, bool for_expression) +{ + return BuildAggregate(ast_ctx, type, for_expression, '{', '}', clang::TTK_Struct); +} + +clang::QualType +AppleObjCTypeEncodingParser::BuildUnion (clang::ASTContext &ast_ctx, lldb_utility::StringLexer& type, bool for_expression) +{ + return BuildAggregate(ast_ctx, type, for_expression, '(', ')', clang::TTK_Union); +} + +clang::QualType +AppleObjCTypeEncodingParser::BuildAggregate (clang::ASTContext &ast_ctx, lldb_utility::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! + + ClangASTContext *lldb_ctx = ClangASTContext::GetASTContext(&ast_ctx); + if (!lldb_ctx) + return clang::QualType(); + CompilerType union_type(lldb_ctx->CreateRecordType(nullptr, lldb::eAccessPublic, name.c_str(), kind, lldb::eLanguageTypeC)); + if (union_type) + { + ClangASTContext::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.GetData()); + } + ClangASTContext::AddFieldToRecordType(union_type, element.name.c_str(), CompilerType(&ast_ctx, element.type), lldb::eAccessPublic, element.bitfield); + ++count; + } + ClangASTContext::CompleteTagDeclarationDefinition(union_type); + } + return ClangASTContext::GetQualType(union_type); +} + +clang::QualType +AppleObjCTypeEncodingParser::BuildArray (clang::ASTContext &ast_ctx, lldb_utility::StringLexer& type, bool for_expression) +{ + if (!type.NextIf('[')) + return clang::QualType(); + uint32_t size = ReadNumber(type); + clang::QualType element_type(BuildType(ast_ctx, type, for_expression)); + if (!type.NextIf(']')) + return clang::QualType(); + ClangASTContext *lldb_ctx = ClangASTContext::GetASTContext(&ast_ctx); + if (!lldb_ctx) + return clang::QualType(); + CompilerType array_type(lldb_ctx->CreateArrayType(CompilerType(&ast_ctx, element_type), size, false)); + return ClangASTContext::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 (clang::ASTContext &ast_ctx, lldb_utility::StringLexer& type, bool for_expression) +{ + if (!type.NextIf('@')) + return clang::QualType(); + + 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 '}': + case ')': + case ']': + 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(); + + assert (decl_vendor); // how are we parsing type encodings for expressions if a type vendor isn't in play? + + const bool append = false; + const uint32_t max_matches = 1; + std::vector<clang::NamedDecl *> decls; + + uint32_t num_types = decl_vendor->FindDecls(ConstString(name), + append, + max_matches, + decls); + + // 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. We keep this assert in debug builds so we catch other weird cases. +#ifdef LLDB_CONFIGURATION_DEBUG + assert(num_types); +#else + if (!num_types) + return ast_ctx.getObjCIdType(); +#endif + + return ClangASTContext::GetQualType(ClangASTContext::GetTypeForDecl(decls[0]).GetPointerType()); + } + else + { + // We're going to resolve this dynamically anyway, so just smile and wave. + return ast_ctx.getObjCIdType(); + } +} + +clang::QualType +AppleObjCTypeEncodingParser::BuildType (clang::ASTContext &ast_ctx, StringLexer& type, bool for_expression, uint32_t *bitfield_bit_size) +{ + if (!type.HasAtLeast(1)) + return clang::QualType(); + + switch (type.Peek()) + { + default: + break; + case '{': + return BuildStruct(ast_ctx, type, for_expression); + case '[': + return BuildArray(ast_ctx, type, for_expression); + case '(': + return BuildUnion(ast_ctx, type, for_expression); + case '@': + return BuildObjCObjectPointerType(ast_ctx, type, for_expression); + } + + switch (type.Next()) + { + default: + type.PutBack(1); + return clang::QualType(); + case 'c': + return ast_ctx.CharTy; + case 'i': + return ast_ctx.IntTy; + case 's': + return ast_ctx.ShortTy; + case 'l': + return ast_ctx.getIntTypeForBitwidth(32, true); + // this used to be done like this: + // ClangASTContext *lldb_ctx = ClangASTContext::GetASTContext(&ast_ctx); + // if (!lldb_ctx) + // return clang::QualType(); + // return lldb_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 'q': + return ast_ctx.LongLongTy; + case 'C': + return ast_ctx.UnsignedCharTy; + case 'I': + return ast_ctx.UnsignedIntTy; + case 'S': + return ast_ctx.UnsignedShortTy; + case 'L': + return ast_ctx.getIntTypeForBitwidth(32, false); + // see note for 'l' + case 'Q': + return ast_ctx.UnsignedLongLongTy; + case 'f': + return ast_ctx.FloatTy; + case 'd': + return ast_ctx.DoubleTy; + case 'B': + return ast_ctx.BoolTy; + case 'v': + return ast_ctx.VoidTy; + case '*': + return ast_ctx.getPointerType(ast_ctx.CharTy); + case '#': + return ast_ctx.getObjCClassType(); + case ':': + return ast_ctx.getObjCSelType(); + case 'b': + { + 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 'r': + { + clang::QualType target_type = BuildType(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 '^': + { + if (!for_expression && type.NextIf('?')) + { + // 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(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 '?': + return for_expression ? ast_ctx.UnknownAnyTy : clang::QualType(); + } +} + +CompilerType +AppleObjCTypeEncodingParser::RealizeType (clang::ASTContext &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 CompilerType(&ast_ctx, qual_type); + } + return CompilerType(); +} + diff --git a/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTypeEncodingParser.h b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTypeEncodingParser.h new file mode 100644 index 000000000000..87c49cbc05b9 --- /dev/null +++ b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCTypeEncodingParser.h @@ -0,0 +1,81 @@ +//===-- AppleObjCTypeEncodingParser.h ---------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_AppleObjCTypeEncodingParser_h_ +#define liblldb_AppleObjCTypeEncodingParser_h_ + +// C Includes +// C++ Includes +// Other libraries and framework includes +#include "clang/AST/ASTContext.h" + +// Project includes +#include "lldb/lldb-private.h" +#include "lldb/Target/ObjCLanguageRuntime.h" + +namespace lldb_utility { + class StringLexer; +} + +namespace lldb_private { + + class AppleObjCTypeEncodingParser : public ObjCLanguageRuntime::EncodingToType + { + public: + AppleObjCTypeEncodingParser (ObjCLanguageRuntime& runtime); + ~AppleObjCTypeEncodingParser() override = default; + + CompilerType RealizeType(clang::ASTContext &ast_ctx, const char* name, bool for_expression) override; + + private: + struct StructElement { + std::string name; + clang::QualType type; + uint32_t bitfield; + + StructElement (); + ~StructElement () = default; + }; + + clang::QualType + BuildType (clang::ASTContext &ast_ctx, lldb_utility::StringLexer& type, bool for_expression, uint32_t *bitfield_bit_size = nullptr); + + clang::QualType + BuildStruct (clang::ASTContext &ast_ctx, lldb_utility::StringLexer& type, bool for_expression); + + clang::QualType + BuildAggregate (clang::ASTContext &ast_ctx, lldb_utility::StringLexer& type, bool for_expression, char opener, char closer, uint32_t kind); + + clang::QualType + BuildUnion (clang::ASTContext &ast_ctx, lldb_utility::StringLexer& type, bool for_expression); + + clang::QualType + BuildArray (clang::ASTContext &ast_ctx, lldb_utility::StringLexer& type, bool for_expression); + + std::string + ReadStructName(lldb_utility::StringLexer& type); + + StructElement + ReadStructElement (clang::ASTContext &ast_ctx, lldb_utility::StringLexer& type, bool for_expression); + + clang::QualType + BuildObjCObjectPointerType (clang::ASTContext &ast_ctx, lldb_utility::StringLexer& type, bool for_expression); + + uint32_t + ReadNumber (lldb_utility::StringLexer& type); + + std::string + ReadQuotedString(lldb_utility::StringLexer& type); + + ObjCLanguageRuntime& m_runtime; + }; + +} // namespace lldb_private + +#endif // liblldb_AppleObjCTypeEncodingParser_h_ diff --git a/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp new file mode 100644 index 000000000000..285786a09dbb --- /dev/null +++ b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.cpp @@ -0,0 +1,244 @@ +//===-- AppleThreadPlanStepThroughObjCTrampoline.cpp --------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// C Includes +// C++ Includes +// Other libraries and framework includes +// Project includes +#include "AppleThreadPlanStepThroughObjCTrampoline.h" +#include "AppleObjCTrampolineHandler.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Thread.h" +#include "lldb/Expression/FunctionCaller.h" +#include "lldb/Expression/UtilityFunction.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/ObjCLanguageRuntime.h" +#include "lldb/Target/ThreadPlanRunToAddress.h" +#include "lldb/Target/ThreadPlanStepOut.h" +#include "lldb/Core/Log.h" + + +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, + bool stop_others +) : + 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 (NULL), + m_stop_others (stop_others) +{ + +} + +//---------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------- +AppleThreadPlanStepThroughObjCTrampoline::~AppleThreadPlanStepThroughObjCTrampoline() +{ +} + +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_thread.GetProcess()->AddPreResumeAction (PreResumeInitializeFunctionCaller, (void *) this); +} + +bool +AppleThreadPlanStepThroughObjCTrampoline::InitializeFunctionCaller () +{ + if (!m_func_sp) + { + StreamString errors; + m_args_addr = m_trampoline_handler->SetupDispatchFunction(m_thread, 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(m_stop_others); + m_thread.CalculateExecutionContext(exc_ctx); + m_func_sp = m_impl_function->GetThreadPlanToCallFunction (exc_ctx, + m_args_addr, + options, + errors); + m_func_sp->SetOkayToDiscard(true); + m_thread.QueueThreadPlan (m_func_sp, false); + } + 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, then fetch the target address, and + // queue up a "run to that address" plan. + if (!m_run_to_sp) + { + Value target_addr_value; + ExecutionContext exc_ctx; + m_thread.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(); + Address target_so_addr; + target_so_addr.SetOpcodeLoadAddress(target_addr, exc_ctx.GetTargetPtr()); + Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_STEP)); + if (target_addr == 0) + { + if (log) + log->Printf("Got target implementation of 0x0, stopping."); + SetPlanComplete(); + return true; + } + if (m_trampoline_handler->AddrIsMsgForward(target_addr)) + { + if (log) + log->Printf ("Implementation lookup returned msgForward function: 0x%" PRIx64 ", stopping.", target_addr); + + SymbolContext sc = m_thread.GetStackFrameAtIndex(0)->GetSymbolContext(eSymbolContextEverything); + const bool abort_other_plans = false; + const bool first_insn = true; + const uint32_t frame_idx = 0; + m_run_to_sp = m_thread.QueueThreadPlanForStepOutNoShouldStop (abort_other_plans, + &sc, + first_insn, + m_stop_others, + eVoteNoOpinion, + eVoteNoOpinion, + frame_idx); + m_run_to_sp->SetPrivate(true); + return false; + } + + if (log) + log->Printf("Running to ObjC method implementation: 0x%" PRIx64, target_addr); + + ObjCLanguageRuntime *objc_runtime = GetThread().GetProcess()->GetObjCLanguageRuntime(); + assert (objc_runtime != NULL); + objc_runtime->AddToMethodCache (m_isa_addr, m_sel_addr, target_addr); + if (log) + log->Printf("Adding {isa-addr=0x%" PRIx64 ", sel-addr=0x%" PRIx64 "} = addr=0x%" PRIx64 " to cache.", m_isa_addr, m_sel_addr, target_addr); + + // Extract the target address from the value: + + m_run_to_sp.reset(new ThreadPlanRunToAddress(m_thread, target_so_addr, m_stop_others)); + m_thread.QueueThreadPlan(m_run_to_sp, false); + m_run_to_sp->SetPrivate(true); + return false; + } + else if (m_thread.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 () +{ + if (IsPlanComplete()) + return true; + else + return false; +} + +bool +AppleThreadPlanStepThroughObjCTrampoline::WillStop() +{ + return true; +} diff --git a/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h new file mode 100644 index 000000000000..8db9963fa51a --- /dev/null +++ b/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleThreadPlanStepThroughObjCTrampoline.h @@ -0,0 +1,95 @@ +//===-- AppleThreadPlanStepThroughObjCTrampoline.h --------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef lldb_AppleThreadPlanStepThroughObjCTrampoline_h_ +#define lldb_AppleThreadPlanStepThroughObjCTrampoline_h_ + +// C Includes +// C++ Includes +// Other libraries and framework includes +// Project includes +#include "lldb/lldb-types.h" +#include "lldb/lldb-enumerations.h" +#include "lldb/Core/Value.h" +#include "lldb/Target/ThreadPlan.h" +#include "AppleObjCTrampolineHandler.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, + bool stop_others); + + ~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; + + bool + StopOthers() override + { + return m_stop_others; + } + + // 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; // FIXME - ensure this doesn't go away on us? SP maybe? + lldb::addr_t m_args_addr; // Stores the address for our step through function result structure. + //lldb::addr_t m_object_addr; // This is only for Description. + 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 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. + bool m_stop_others; +}; + +} // namespace lldb_private + +#endif // lldb_AppleThreadPlanStepThroughObjCTrampoline_h_ |