bulbazord created this revision. bulbazord added a reviewer: JDevlieghere. Herald added a project: All. bulbazord requested review of this revision. Herald added a project: LLDB. Herald added a subscriber: lldb-commits.
The Objective-C runtime and the shared cache has changed slightly. Given a class_ro_t, the baseMethods ivar is now a pointer union and may either be a method_list_t pointer or a pointer to a relative list of lists. The entries of this relative list of lists are indexes that refer to a specific image in the shared cache in addition to a pointer offset to find the accompanying method_list_t. We have to go over each of these entries, parse it, and then if the relevant image is loaded in the process, we add those methods to the relevant clang Decl. In order to determine if an image is loaded, the Objective-C runtime exposes a symbol that lets us determine if a particular image is loaded. We maintain a data structure SharedCacheImageHeaders to keep track of that information. There is a known issue where if an image is loaded after we create a Decl for a class, the Decl will not have the relevant methods from that image (i.e. for Categories). rdar://107957209 Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D153597 Files: lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.h lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h lldb/test/API/lang/objc/exceptions/TestObjCExceptions.py
Index: lldb/test/API/lang/objc/exceptions/TestObjCExceptions.py =================================================================== --- lldb/test/API/lang/objc/exceptions/TestObjCExceptions.py +++ 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, "<nil>") self.expect( "frame variable e2", substrs=["(NSException *) e2 = ", '"SomeReason"'] Index: lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h =================================================================== --- lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.h +++ 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<uint64_t> 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<SharedCacheImageHeaders> + CreateSharedCacheImageHeaders(AppleObjCRuntimeV2 &runtime); + + void SetNeedsUpdate() { m_needs_update = true; } + + bool IsImageLoaded(uint16_t image_index); + + uint64_t GetVersion(); + + private: + SharedCacheImageHeaders(AppleObjCRuntimeV2 &runtime, + lldb::addr_t headerInfoRWs_ptr, uint32_t count, + uint32_t entsize) + : m_runtime(runtime), m_headerInfoRWs_ptr(headerInfoRWs_ptr), + m_loaded_images(count, false), m_version(0), m_count(count), + m_entsize(entsize), m_needs_update(true) {} + llvm::Error UpdateIfNeeded(); + + AppleObjCRuntimeV2 &m_runtime; + lldb::addr_t m_headerInfoRWs_ptr; + llvm::BitVector m_loaded_images; + uint64_t m_version; + uint32_t m_count; + uint32_t m_entsize; + bool m_needs_update; + }; + AppleObjCRuntimeV2(Process *process, const lldb::ModuleSP &objc_module_sp); ObjCISA GetPointerISA(ObjCISA isa); @@ -435,6 +472,7 @@ std::once_flag m_no_expanded_cache_warning; std::optional<std::pair<lldb::addr_t, lldb::addr_t>> m_CFBoolean_values; uint64_t m_realized_class_generation_count; + std::unique_ptr<SharedCacheImageHeaders> m_shared_cache_image_headers_up; }; } // namespace lldb_private Index: lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp =================================================================== --- lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp +++ lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp @@ -1619,6 +1619,146 @@ return m_isa_hash_table_ptr; } +std::unique_ptr<AppleObjCRuntimeV2::SharedCacheImageHeaders> +AppleObjCRuntimeV2::SharedCacheImageHeaders::CreateSharedCacheImageHeaders( + AppleObjCRuntimeV2 &runtime) { + Log *log = GetLog(LLDBLog::Process | LLDBLog::Types); + Process *process = runtime.GetProcess(); + ModuleSP objc_module_sp(runtime.GetObjCModule()); + if (!objc_module_sp || !process) + return nullptr; + + const Symbol *symbol = objc_module_sp->FindFirstSymbolWithNameAndType( + ConstString("objc_debug_headerInfoRWs"), lldb::eSymbolTypeAny); + if (!symbol) { + LLDB_LOG(log, "Symbol 'objc_debug_headerInfoRWs' unavailable. Some " + "information concerning the shared cache may be unavailable"); + return nullptr; + } + + lldb::addr_t objc_debug_headerInfoRWs_addr = + symbol->GetLoadAddress(&process->GetTarget()); + if (objc_debug_headerInfoRWs_addr == LLDB_INVALID_ADDRESS) { + LLDB_LOG(log, "Symbol 'objc_debug_headerInfoRWs' was found but we were " + "unable to get its load address"); + return nullptr; + } + + Status error; + lldb::addr_t objc_debug_headerInfoRWs_ptr = + process->ReadPointerFromMemory(objc_debug_headerInfoRWs_addr, error); + if (error.Fail()) { + LLDB_LOG(log, + "Failed to read address of 'objc_debug_headerInfoRWs' at {0:x}", + objc_debug_headerInfoRWs_addr); + return nullptr; + } + + const size_t metadata_size = + sizeof(uint32_t) + sizeof(uint32_t); // count + entsize + DataBufferHeap metadata_buffer(metadata_size, '\0'); + process->ReadMemory(objc_debug_headerInfoRWs_ptr, metadata_buffer.GetBytes(), + metadata_size, error); + if (error.Fail()) { + LLDB_LOG(log, + "Unable to read metadata for 'objc_debug_headerInfoRWs' at {0:x}", + objc_debug_headerInfoRWs_ptr); + return nullptr; + } + + DataExtractor metadata_extractor(metadata_buffer.GetBytes(), metadata_size, + process->GetByteOrder(), + process->GetAddressByteSize()); + lldb::offset_t cursor = 0; + uint32_t count = metadata_extractor.GetU32_unchecked(&cursor); + uint32_t entsize = metadata_extractor.GetU32_unchecked(&cursor); + if (count == 0 || entsize == 0) { + LLDB_LOG(log, + "'objc_debug_headerInfoRWs' had count {0} with entsize {1}. These " + "should both be non-zero.", + count, entsize); + return nullptr; + } + + std::unique_ptr<SharedCacheImageHeaders> shared_cache_image_headers( + new SharedCacheImageHeaders(runtime, objc_debug_headerInfoRWs_ptr, count, + entsize)); + if (auto Err = shared_cache_image_headers->UpdateIfNeeded()) { + LLDB_LOG_ERROR(log, std::move(Err), + "Failed to update SharedCacheImageHeaders"); + 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<UtilityFunction> 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<uint64_t> AppleObjCRuntimeV2::GetSharedCacheImageHeaderVersion() { + if (!m_shared_cache_image_headers_up) { + m_shared_cache_image_headers_up = + SharedCacheImageHeaders::CreateSharedCacheImageHeaders(*this); + } + if (m_shared_cache_image_headers_up) + return m_shared_cache_image_headers_up->GetVersion(); + + return std::nullopt; +} + #pragma mark Frame recognizers class ObjCExceptionRecognizedStackFrame : public RecognizedStackFrame { Index: lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.h =================================================================== --- lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.h +++ lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.h @@ -146,6 +146,9 @@ bool Read(Process *process, lldb::addr_t addr); }; + std::optional<method_list_t> + GetMethodList(Process *process, lldb::addr_t method_list_ptr) const; + struct method_t { lldb::addr_t m_name_ptr; lldb::addr_t m_types_ptr; @@ -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_t> &objc_class) const; @@ -232,6 +251,15 @@ std::unique_ptr<class_ro_t> &class_ro, std::unique_ptr<class_rw_t> &class_rw) const; + bool ProcessMethodList(std::function<bool(const char *, const char *)> const + &instance_method_func, + method_list_t &method_list) const; + + bool ProcessRelativeMethodLists( + std::function<bool(const char *, const char *)> const + &instance_method_func, + lldb::addr_t relative_method_list_ptr) const; + AppleObjCRuntimeV2 &m_runtime; // The runtime, so we can read information lazily. lldb::addr_t m_objc_class_ptr; // The address of the objc_class_t. (I.e., @@ -239,6 +267,10 @@ // their ISA) ConstString m_name; // May be NULL iVarsStorage m_ivars_storage; + + mutable std::map<uint16_t, std::vector<method_list_t>> + m_image_to_method_lists; + mutable std::optional<uint64_t> m_last_version_updated; }; // tagged pointer descriptor Index: lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp =================================================================== --- lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp +++ 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::method_list_t> +ClassDescriptorV2::GetMethodList(Process *process, + lldb::addr_t method_list_ptr) const { + Log *log = GetLog(LLDBLog::Types); + ClassDescriptorV2::method_list_t method_list; + if (!method_list.Read(process, method_list_ptr)) + return std::nullopt; + + const size_t method_size = method_t::GetSize(process, method_list.m_is_small); + if (method_list.m_entsize != method_size) { + LLDB_LOG(log, + "method_list_t at address 0x" PRIx64 " has an entsize of " PRIu16 + " but method size should be " PRIu64, + method_list_ptr, method_list.m_entsize, method_size); + return std::nullopt; + } + + return method_list; +} + +bool ClassDescriptorV2::ProcessMethodList( + std::function<bool(const char *, const char *)> const &instance_method_func, + ClassDescriptorV2::method_list_t &method_list) const { + lldb_private::Process *process = m_runtime.GetProcess(); + auto method = std::make_unique<method_t>(); + lldb::addr_t relative_selector_base_addr = + m_runtime.GetRelativeSelectorBaseAddr(); + for (uint32_t i = 0, e = method_list.m_count; i < e; ++i) { + method->Read(process, method_list.m_first_ptr + (i * method_list.m_entsize), + relative_selector_base_addr, method_list.m_is_small, + method_list.m_has_direct_selector); + if (instance_method_func(method->m_name.c_str(), method->m_types.c_str())) + break; + } + return true; +} + +// The relevant data structures: +// - relative_list_list_t +// - uint32_t count +// - uint32_t entsize +// - Followed by <count> number of relative_list_entry_t of size <entsize> +// +// - relative_list_entry_t +// - uint64_t image_index : 16 +// - int64_t list_offset : 48 +// - Note: The above 2 fit into 8 bytes always +// +// image_index corresponds to an image in the shared cache +// list_offset is used to calculate the address of the method_list_t we want +bool ClassDescriptorV2::ProcessRelativeMethodLists( + std::function<bool(const char *, const char *)> const &instance_method_func, + lldb::addr_t relative_method_list_ptr) const { + lldb_private::Process *process = m_runtime.GetProcess(); + auto relative_method_lists = std::make_unique<relative_list_list_t>(); + + // 1. Process the count and entsize of the relative_list_list_t + if (!relative_method_lists->Read(process, relative_method_list_ptr)) + return false; + + auto entry = std::make_unique<relative_list_entry_t>(); + for (uint32_t i = 0; i < relative_method_lists->m_count; i++) { + // 2. Extract the image index and the list offset from the + // relative_list_entry_t + const lldb::addr_t entry_addr = relative_method_lists->m_first_ptr + + (i * relative_method_lists->m_entsize); + if (!entry->Read(process, entry_addr)) + return false; + + // 3. Calculate the pointer to the method_list_t from the + // relative_list_entry_t + const lldb::addr_t method_list_addr = entry_addr + entry->m_list_offset; + + // 4. Get the method_list_t from the pointer + std::optional<method_list_t> method_list = + GetMethodList(process, method_list_addr); + if (!method_list) + return false; + + // 5. Cache the result so we don't need to reconstruct it later. + m_image_to_method_lists[entry->m_image_index].emplace_back(*method_list); + + // 6. If the relevant image is loaded, add the methods to the Decl + if (!m_runtime.IsSharedCacheImageLoaded(entry->m_image_index)) + continue; + + if (!ProcessMethodList(instance_method_func, *method_list)) + return false; + } + + // We need to keep track of the last time we updated so we can re-update the + // type information in the future + m_last_version_updated = m_runtime.GetSharedCacheImageHeaderVersion(); + + return true; +} + bool ClassDescriptorV2::Describe( std::function<void(ObjCLanguageRuntime::ObjCISA)> const &superclass_func, std::function<bool(const char *, const char *)> const &instance_method_func, @@ -393,29 +542,18 @@ superclass_func(objc_class->m_superclass); if (instance_method_func) { - std::unique_ptr<method_list_t> base_method_list; - - base_method_list = std::make_unique<method_list_t>(); - 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_t> method = std::make_unique<method_t>(); - 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<method_list_t> base_method_list = + GetMethodList(process, class_ro->m_baseMethods_ptr); + if (!base_method_list) + return false; + if (!ProcessMethodList(instance_method_func, *base_method_list)) + return false; } }
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits