diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.h b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.h --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.h +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.h @@ -146,6 +146,9 @@ bool Read(Process *process, lldb::addr_t addr); }; + std::optional + GetMethodList(Process *process, lldb::addr_t method_list_ptr) const; + struct method_t { lldb::addr_t m_name_ptr; lldb::addr_t m_types_ptr; @@ -201,6 +204,21 @@ bool Read(Process *process, lldb::addr_t addr); }; + struct relative_list_entry_t { + uint16_t m_image_index; + int64_t m_list_offset; + + bool Read(Process *process, lldb::addr_t addr); + }; + + struct relative_list_list_t { + uint32_t m_entsize; + uint32_t m_count; + lldb::addr_t m_first_ptr; + + bool Read(Process *process, lldb::addr_t addr); + }; + class iVarsStorage { public: iVarsStorage(); @@ -223,7 +241,8 @@ ClassDescriptorV2(AppleObjCRuntimeV2 &runtime, ObjCLanguageRuntime::ObjCISA isa, const char *name) : m_runtime(runtime), m_objc_class_ptr(isa), m_name(name), - m_ivars_storage() {} + m_ivars_storage(), m_image_to_method_lists(), m_last_version_updated() { + } bool Read_objc_class(Process *process, std::unique_ptr &objc_class) const; @@ -232,6 +251,15 @@ std::unique_ptr &class_ro, std::unique_ptr &class_rw) const; + bool ProcessMethodList(std::function const + &instance_method_func, + method_list_t &method_list) const; + + bool ProcessRelativeMethodLists( + std::function const + &instance_method_func, + lldb::addr_t relative_method_list_ptr) const; + AppleObjCRuntimeV2 &m_runtime; // The runtime, so we can read information lazily. lldb::addr_t m_objc_class_ptr; // The address of the objc_class_t. (I.e., @@ -239,6 +267,10 @@ // their ISA) ConstString m_name; // May be NULL iVarsStorage m_ivars_storage; + + mutable std::map> + m_image_to_method_lists; + mutable std::optional m_last_version_updated; }; // tagged pointer descriptor diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp @@ -370,6 +370,155 @@ return !error.Fail(); } +bool ClassDescriptorV2::relative_list_entry_t::Read(Process *process, + lldb::addr_t addr) { + Log *log = GetLog(LLDBLog::Types); + size_t size = sizeof(uint64_t); // m_image_index : 16 + // m_list_offset : 48 + + DataBufferHeap buffer(size, '\0'); + Status error; + + process->ReadMemory(addr, buffer.GetBytes(), size, error); + // FIXME: Propagate this error up + if (error.Fail()) { + LLDB_LOG(log, "Failed to read relative_list_entry_t at address {0:x}", + addr); + return false; + } + + DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(), + process->GetAddressByteSize()); + lldb::offset_t cursor = 0; + uint64_t raw_entry = extractor.GetU64_unchecked(&cursor); + m_image_index = raw_entry & 0xFFFF; + m_list_offset = (int64_t)(raw_entry >> 16); + return true; +} + +bool ClassDescriptorV2::relative_list_list_t::Read(Process *process, + lldb::addr_t addr) { + Log *log = GetLog(LLDBLog::Types); + size_t size = sizeof(uint32_t) // m_entsize + + sizeof(uint32_t); // m_count + + DataBufferHeap buffer(size, '\0'); + Status error; + + // FIXME: Propagate this error up + process->ReadMemory(addr, buffer.GetBytes(), size, error); + if (error.Fail()) { + LLDB_LOG(log, "Failed to read relative_list_list_t at address 0x" PRIx64, + addr); + return false; + } + + DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(), + process->GetAddressByteSize()); + lldb::offset_t cursor = 0; + m_entsize = extractor.GetU32_unchecked(&cursor); + m_count = extractor.GetU32_unchecked(&cursor); + m_first_ptr = addr + cursor; + return true; +} + +std::optional +ClassDescriptorV2::GetMethodList(Process *process, + lldb::addr_t method_list_ptr) const { + Log *log = GetLog(LLDBLog::Types); + ClassDescriptorV2::method_list_t method_list; + if (!method_list.Read(process, method_list_ptr)) + return std::nullopt; + + const size_t method_size = method_t::GetSize(process, method_list.m_is_small); + if (method_list.m_entsize != method_size) { + LLDB_LOG(log, + "method_list_t at address 0x" PRIx64 " has an entsize of " PRIu16 + " but method size should be " PRIu64, + method_list_ptr, method_list.m_entsize, method_size); + return std::nullopt; + } + + return method_list; +} + +bool ClassDescriptorV2::ProcessMethodList( + std::function const &instance_method_func, + ClassDescriptorV2::method_list_t &method_list) const { + lldb_private::Process *process = m_runtime.GetProcess(); + auto method = std::make_unique(); + lldb::addr_t relative_selector_base_addr = + m_runtime.GetRelativeSelectorBaseAddr(); + for (uint32_t i = 0, e = method_list.m_count; i < e; ++i) { + method->Read(process, method_list.m_first_ptr + (i * method_list.m_entsize), + relative_selector_base_addr, method_list.m_is_small, + method_list.m_has_direct_selector); + if (instance_method_func(method->m_name.c_str(), method->m_types.c_str())) + break; + } + return true; +} + +// The relevant data structures: +// - relative_list_list_t +// - uint32_t count +// - uint32_t entsize +// - Followed by number of relative_list_entry_t of size +// +// - relative_list_entry_t +// - uint64_t image_index : 16 +// - int64_t list_offset : 48 +// - Note: The above 2 fit into 8 bytes always +// +// image_index corresponds to an image in the shared cache +// list_offset is used to calculate the address of the method_list_t we want +bool ClassDescriptorV2::ProcessRelativeMethodLists( + std::function const &instance_method_func, + lldb::addr_t relative_method_list_ptr) const { + lldb_private::Process *process = m_runtime.GetProcess(); + auto relative_method_lists = std::make_unique(); + + // 1. Process the count and entsize of the relative_list_list_t + if (!relative_method_lists->Read(process, relative_method_list_ptr)) + return false; + + auto entry = std::make_unique(); + for (uint32_t i = 0; i < relative_method_lists->m_count; i++) { + // 2. Extract the image index and the list offset from the + // relative_list_entry_t + const lldb::addr_t entry_addr = relative_method_lists->m_first_ptr + + (i * relative_method_lists->m_entsize); + if (!entry->Read(process, entry_addr)) + return false; + + // 3. Calculate the pointer to the method_list_t from the + // relative_list_entry_t + const lldb::addr_t method_list_addr = entry_addr + entry->m_list_offset; + + // 4. Get the method_list_t from the pointer + std::optional method_list = + GetMethodList(process, method_list_addr); + if (!method_list) + return false; + + // 5. Cache the result so we don't need to reconstruct it later. + m_image_to_method_lists[entry->m_image_index].emplace_back(*method_list); + + // 6. If the relevant image is loaded, add the methods to the Decl + if (!m_runtime.IsSharedCacheImageLoaded(entry->m_image_index)) + continue; + + if (!ProcessMethodList(instance_method_func, *method_list)) + return false; + } + + // We need to keep track of the last time we updated so we can re-update the + // type information in the future + m_last_version_updated = m_runtime.GetSharedCacheImageHeaderVersion(); + + return true; +} + bool ClassDescriptorV2::Describe( std::function const &superclass_func, std::function const &instance_method_func, @@ -393,29 +542,18 @@ superclass_func(objc_class->m_superclass); if (instance_method_func) { - std::unique_ptr base_method_list; - - base_method_list = std::make_unique(); - if (!base_method_list->Read(process, class_ro->m_baseMethods_ptr)) - return false; - - bool is_small = base_method_list->m_is_small; - bool has_direct_selector = base_method_list->m_has_direct_selector; - - if (base_method_list->m_entsize != method_t::GetSize(process, is_small)) - return false; - - std::unique_ptr method = std::make_unique(); - lldb::addr_t relative_selector_base_addr = - m_runtime.GetRelativeSelectorBaseAddr(); - 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), - relative_selector_base_addr, is_small, has_direct_selector); - - if (instance_method_func(method->m_name.c_str(), method->m_types.c_str())) - break; + // This is a relative list of lists + if (class_ro->m_baseMethods_ptr & 1) { + if (!ProcessRelativeMethodLists(instance_method_func, + class_ro->m_baseMethods_ptr ^ 1)) + return false; + } else { + std::optional base_method_list = + GetMethodList(process, class_ro->m_baseMethods_ptr); + if (!base_method_list) + return false; + if (!ProcessMethodList(instance_method_func, *base_method_list)) + return false; } } diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h @@ -19,6 +19,8 @@ #include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h" +#include "llvm/ADT/BitVector.h" + class RemoteNXMapTable; namespace lldb_private { @@ -96,6 +98,12 @@ void GetValuesForGlobalCFBooleans(lldb::addr_t &cf_true, lldb::addr_t &cf_false) override; + void ModulesDidLoad(const ModuleList &module_list) override; + + bool IsSharedCacheImageLoaded(uint16_t image_index); + + std::optional GetSharedCacheImageHeaderVersion(); + protected: lldb::BreakpointResolverSP CreateExceptionResolver(const lldb::BreakpointSP &bkpt, bool catch_bp, @@ -374,6 +382,35 @@ lldb::addr_t m_args = LLDB_INVALID_ADDRESS; }; + class SharedCacheImageHeaders { + public: + static std::unique_ptr + CreateSharedCacheImageHeaders(AppleObjCRuntimeV2 &runtime); + + void SetNeedsUpdate() { m_needs_update = true; } + + bool IsImageLoaded(uint16_t image_index); + + uint64_t GetVersion(); + + private: + SharedCacheImageHeaders(AppleObjCRuntimeV2 &runtime, + lldb::addr_t headerInfoRWs_ptr, uint32_t count, + uint32_t entsize) + : m_runtime(runtime), m_headerInfoRWs_ptr(headerInfoRWs_ptr), + m_loaded_images(count, false), m_version(0), m_count(count), + m_entsize(entsize), m_needs_update(true) {} + llvm::Error UpdateIfNeeded(); + + AppleObjCRuntimeV2 &m_runtime; + lldb::addr_t m_headerInfoRWs_ptr; + llvm::BitVector m_loaded_images; + uint64_t m_version; + uint32_t m_count; + uint32_t m_entsize; + bool m_needs_update; + }; + AppleObjCRuntimeV2(Process *process, const lldb::ModuleSP &objc_module_sp); ObjCISA GetPointerISA(ObjCISA isa); @@ -435,6 +472,7 @@ std::once_flag m_no_expanded_cache_warning; std::optional> m_CFBoolean_values; uint64_t m_realized_class_generation_count; + std::unique_ptr m_shared_cache_image_headers_up; }; } // namespace lldb_private diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp @@ -1619,6 +1619,146 @@ return m_isa_hash_table_ptr; } +std::unique_ptr +AppleObjCRuntimeV2::SharedCacheImageHeaders::CreateSharedCacheImageHeaders( + AppleObjCRuntimeV2 &runtime) { + Log *log = GetLog(LLDBLog::Process | LLDBLog::Types); + Process *process = runtime.GetProcess(); + ModuleSP objc_module_sp(runtime.GetObjCModule()); + if (!objc_module_sp || !process) + return nullptr; + + const Symbol *symbol = objc_module_sp->FindFirstSymbolWithNameAndType( + ConstString("objc_debug_headerInfoRWs"), lldb::eSymbolTypeAny); + if (!symbol) { + LLDB_LOG(log, "Symbol 'objc_debug_headerInfoRWs' unavailable. Some " + "information concerning the shared cache may be unavailable"); + return nullptr; + } + + lldb::addr_t objc_debug_headerInfoRWs_addr = + symbol->GetLoadAddress(&process->GetTarget()); + if (objc_debug_headerInfoRWs_addr == LLDB_INVALID_ADDRESS) { + LLDB_LOG(log, "Symbol 'objc_debug_headerInfoRWs' was found but we were " + "unable to get its load address"); + return nullptr; + } + + Status error; + lldb::addr_t objc_debug_headerInfoRWs_ptr = + process->ReadPointerFromMemory(objc_debug_headerInfoRWs_addr, error); + if (error.Fail()) { + LLDB_LOG(log, + "Failed to read address of 'objc_debug_headerInfoRWs' at {0:x}", + objc_debug_headerInfoRWs_addr); + return nullptr; + } + + const size_t metadata_size = + sizeof(uint32_t) + sizeof(uint32_t); // count + entsize + DataBufferHeap metadata_buffer(metadata_size, '\0'); + process->ReadMemory(objc_debug_headerInfoRWs_ptr, metadata_buffer.GetBytes(), + metadata_size, error); + if (error.Fail()) { + LLDB_LOG(log, + "Unable to read metadata for 'objc_debug_headerInfoRWs' at {0:x}", + objc_debug_headerInfoRWs_ptr); + return nullptr; + } + + DataExtractor metadata_extractor(metadata_buffer.GetBytes(), metadata_size, + process->GetByteOrder(), + process->GetAddressByteSize()); + lldb::offset_t cursor = 0; + uint32_t count = metadata_extractor.GetU32_unchecked(&cursor); + uint32_t entsize = metadata_extractor.GetU32_unchecked(&cursor); + if (count == 0 || entsize == 0) { + LLDB_LOG(log, + "'objc_debug_headerInfoRWs' had count {0} with entsize {1}. These " + "should both be non-zero.", + count, entsize); + return nullptr; + } + + std::unique_ptr shared_cache_image_headers( + new SharedCacheImageHeaders(runtime, objc_debug_headerInfoRWs_ptr, count, + entsize)); + if (auto Err = shared_cache_image_headers->UpdateIfNeeded()) { + LLDB_LOG_ERROR(log, std::move(Err), + "Failed to update SharedCacheImageHeaders"); + return nullptr; + } + + return shared_cache_image_headers; +} + +llvm::Error AppleObjCRuntimeV2::SharedCacheImageHeaders::UpdateIfNeeded() { + if (!m_needs_update) + return llvm::Error::success(); + + Process *process = m_runtime.GetProcess(); + constexpr lldb::addr_t metadata_size = + sizeof(uint32_t) + sizeof(uint32_t); // count + entsize + + Status error; + const lldb::addr_t first_header_addr = m_headerInfoRWs_ptr + metadata_size; + DataBufferHeap header_buffer(m_entsize, '\0'); + lldb::offset_t cursor = 0; + for (uint32_t i = 0; i < m_count; i++) { + const lldb::addr_t header_addr = first_header_addr + (i * m_entsize); + process->ReadMemory(header_addr, header_buffer.GetBytes(), m_entsize, + error); + if (error.Fail()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Failed to read memory from inferior when " + "populating SharedCacheImageHeaders"); + + DataExtractor header_extractor(header_buffer.GetBytes(), m_entsize, + process->GetByteOrder(), + process->GetAddressByteSize()); + cursor = 0; + bool is_loaded = false; + if (m_entsize == 4) { + uint32_t header = header_extractor.GetU32_unchecked(&cursor); + if (header & 1) + is_loaded = true; + } else { + uint64_t header = header_extractor.GetU64_unchecked(&cursor); + if (header & 1) + is_loaded = true; + } + + if (is_loaded) + m_loaded_images.set(i); + else + m_loaded_images.reset(i); + } + m_needs_update = false; + m_version++; + return llvm::Error::success(); +} + +bool AppleObjCRuntimeV2::SharedCacheImageHeaders::IsImageLoaded( + uint16_t image_index) { + if (image_index >= m_count) + return false; + if (auto Err = UpdateIfNeeded()) { + Log *log = GetLog(LLDBLog::Process | LLDBLog::Types); + LLDB_LOG_ERROR(log, std::move(Err), + "Failed to update SharedCacheImageHeaders"); + } + return m_loaded_images.test(image_index); +} + +uint64_t AppleObjCRuntimeV2::SharedCacheImageHeaders::GetVersion() { + if (auto Err = UpdateIfNeeded()) { + Log *log = GetLog(LLDBLog::Process | LLDBLog::Types); + LLDB_LOG_ERROR(log, std::move(Err), + "Failed to update SharedCacheImageHeaders"); + } + return m_version; +} + std::unique_ptr AppleObjCRuntimeV2::DynamicClassInfoExtractor::GetClassInfoUtilityFunctionImpl( ExecutionContext &exe_ctx, Helper helper, std::string code, @@ -3239,6 +3379,34 @@ this->AppleObjCRuntime::GetValuesForGlobalCFBooleans(cf_true, cf_false); } +void AppleObjCRuntimeV2::ModulesDidLoad(const ModuleList &module_list) { + AppleObjCRuntime::ModulesDidLoad(module_list); + if (HasReadObjCLibrary() && m_shared_cache_image_headers_up) + m_shared_cache_image_headers_up->SetNeedsUpdate(); +} + +bool AppleObjCRuntimeV2::IsSharedCacheImageLoaded(uint16_t image_index) { + if (!m_shared_cache_image_headers_up) { + m_shared_cache_image_headers_up = + SharedCacheImageHeaders::CreateSharedCacheImageHeaders(*this); + } + if (m_shared_cache_image_headers_up) + return m_shared_cache_image_headers_up->IsImageLoaded(image_index); + + return false; +} + +std::optional AppleObjCRuntimeV2::GetSharedCacheImageHeaderVersion() { + if (!m_shared_cache_image_headers_up) { + m_shared_cache_image_headers_up = + SharedCacheImageHeaders::CreateSharedCacheImageHeaders(*this); + } + if (m_shared_cache_image_headers_up) + return m_shared_cache_image_headers_up->GetVersion(); + + return std::nullopt; +} + #pragma mark Frame recognizers class ObjCExceptionRecognizedStackFrame : public RecognizedStackFrame { diff --git a/lldb/test/API/lang/objc/exceptions/TestObjCExceptions.py b/lldb/test/API/lang/objc/exceptions/TestObjCExceptions.py --- a/lldb/test/API/lang/objc/exceptions/TestObjCExceptions.py +++ b/lldb/test/API/lang/objc/exceptions/TestObjCExceptions.py @@ -87,7 +87,6 @@ "userInfo = ", "1 key/value pair", "reserved = ", - "nil", ], ) @@ -105,7 +104,6 @@ self.assertEqual( userInfo.GetChildAtIndex(0).GetChildAtIndex(1).description, "some_value" ) - self.assertEqual(e1.GetChildMemberWithName("reserved").description, "") self.expect( "frame variable e2", substrs=["(NSException *) e2 = ", '"SomeReason"']