diff --git a/lldb/include/lldb/Host/HostInfoBase.h b/lldb/include/lldb/Host/HostInfoBase.h --- a/lldb/include/lldb/Host/HostInfoBase.h +++ b/lldb/include/lldb/Host/HostInfoBase.h @@ -11,6 +11,7 @@ #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/UUID.h" #include "lldb/Utility/UserIDResolver.h" #include "lldb/Utility/XcodeSDK.h" #include "lldb/lldb-enumerations.h" @@ -24,6 +25,11 @@ class FileSpec; +struct SharedCacheImageInfo { + UUID uuid; + lldb::DataBufferSP data_sp; +}; + class HostInfoBase { private: // Static class, unconstructable. @@ -98,6 +104,13 @@ /// Return the directory containing a specific Xcode SDK. static llvm::StringRef GetXcodeSDKPath(XcodeSDK sdk) { return {}; } + /// Return information about module \p image_name if it is loaded in + /// the current process's address space. + static SharedCacheImageInfo + GetSharedCacheImageInfo(llvm::StringRef image_name) { + return {}; + } + protected: static bool ComputeSharedLibraryDirectory(FileSpec &file_spec); static bool ComputeSupportExeDirectory(FileSpec &file_spec); diff --git a/lldb/include/lldb/Host/macosx/HostInfoMacOSX.h b/lldb/include/lldb/Host/macosx/HostInfoMacOSX.h --- a/lldb/include/lldb/Host/macosx/HostInfoMacOSX.h +++ b/lldb/include/lldb/Host/macosx/HostInfoMacOSX.h @@ -37,6 +37,11 @@ /// Query xcrun to find an Xcode SDK directory. static llvm::StringRef GetXcodeSDKPath(XcodeSDK sdk); + + /// Shared cache utilities + static SharedCacheImageInfo + GetSharedCacheImageInfo(llvm::StringRef image_name); + protected: static bool ComputeSupportExeDirectory(FileSpec &file_spec); static void ComputeHostArchitectureSupport(ArchSpec &arch_32, diff --git a/lldb/source/Host/macosx/objcxx/HostInfoMacOSX.mm b/lldb/source/Host/macosx/objcxx/HostInfoMacOSX.mm --- a/lldb/source/Host/macosx/objcxx/HostInfoMacOSX.mm +++ b/lldb/source/Host/macosx/objcxx/HostInfoMacOSX.mm @@ -12,8 +12,10 @@ #include "lldb/Host/HostInfo.h" #include "lldb/Utility/Args.h" #include "lldb/Utility/Log.h" +#include "Utility/UuidCompatibility.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringMap.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" @@ -457,3 +459,64 @@ auto it_new = g_sdk_path.insert({sdk.GetString(), GetXcodeSDK(sdk)}); return it_new.first->second; } + +namespace { +struct dyld_shared_cache_dylib_text_info { + uint64_t version; // current version 1 + // following fields all exist in version 1 + uint64_t loadAddressUnslid; + uint64_t textSegmentSize; + uuid_t dylibUuid; + const char *path; // pointer invalid at end of iterations + // following fields all exist in version 2 + uint64_t textSegmentOffset; // offset from start of cache +}; +typedef struct dyld_shared_cache_dylib_text_info + dyld_shared_cache_dylib_text_info; +} + +extern "C" int dyld_shared_cache_iterate_text( + const uuid_t cacheUuid, + void (^callback)(const dyld_shared_cache_dylib_text_info *info)); +extern "C" uint8_t *_dyld_get_shared_cache_range(size_t *length); +extern "C" bool _dyld_get_shared_cache_uuid(uuid_t uuid); + +namespace { +class SharedCacheInfo { +public: + const UUID &GetUUID() const { return m_uuid; }; + const llvm::StringMap &GetImages() const { + return m_images; + }; + + SharedCacheInfo(); + +private: + llvm::StringMap m_images; + UUID m_uuid; +}; +} + +SharedCacheInfo::SharedCacheInfo() { + size_t shared_cache_size; + uint8_t *shared_cache_start = + _dyld_get_shared_cache_range(&shared_cache_size); + uuid_t dsc_uuid; + _dyld_get_shared_cache_uuid(dsc_uuid); + m_uuid = UUID::fromData(dsc_uuid); + + dyld_shared_cache_iterate_text( + dsc_uuid, ^(const dyld_shared_cache_dylib_text_info *info) { + m_images[info->path] = SharedCacheImageInfo{ + UUID::fromData(info->dylibUuid, 16), + std::make_shared( + shared_cache_start + info->textSegmentOffset, + shared_cache_size - info->textSegmentOffset)}; + }); +} + +SharedCacheImageInfo +HostInfoMacOSX::GetSharedCacheImageInfo(llvm::StringRef image_name) { + static SharedCacheInfo g_shared_cache_info; + return g_shared_cache_info.GetImages().lookup(image_name); +} diff --git a/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderDarwin.cpp b/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderDarwin.cpp --- a/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderDarwin.cpp +++ b/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderDarwin.cpp @@ -16,6 +16,7 @@ #include "lldb/Core/Section.h" #include "lldb/Expression/DiagnosticManager.h" #include "lldb/Host/FileSystem.h" +#include "lldb/Host/HostInfo.h" #include "lldb/Symbol/Function.h" #include "lldb/Symbol/ObjectFile.h" #include "lldb/Target/ABI.h" @@ -123,19 +124,39 @@ module_sp.reset(); } - if (!module_sp) { - if (can_create) { - // We'll call Target::ModulesDidLoad after all the modules have been - // added to the target, don't let it be called for every one. - module_sp = target.GetOrCreateModule(module_spec, false /* notify */); - if (!module_sp || module_sp->GetObjectFile() == nullptr) - module_sp = m_process->ReadModuleFromMemory(image_info.file_spec, - image_info.address); - - if (did_create_ptr) - *did_create_ptr = (bool)module_sp; + if (module_sp || !can_create) + return module_sp; + + if (HostInfo::GetArchitecture().IsCompatibleMatch(target.GetArchitecture())) { + // When debugging on the host, we are most likely using the same shared + // cache as our inferior. The dylibs from the shared cache might not + // exist on the filesystem, so let's use the images in our own memory + // to create the modules. + // Check if the requested image is in our shared cache. + SharedCacheImageInfo image_info = + HostInfo::GetSharedCacheImageInfo(module_spec.GetFileSpec().GetPath()); + + // If we found it and it has the correct UUID, let's proceed with + // creating a module from the memory contents. + if (image_info.uuid && + (!module_spec.GetUUID() || module_spec.GetUUID() == image_info.uuid)) { + ModuleSpec shared_cache_spec(module_spec.GetFileSpec(), image_info.uuid, + image_info.data_sp); + module_sp = + target.GetOrCreateModule(shared_cache_spec, false /* notify */); } } + // We'll call Target::ModulesDidLoad after all the modules have been + // added to the target, don't let it be called for every one. + if (!module_sp) + module_sp = target.GetOrCreateModule(module_spec, false /* notify */); + if (!module_sp || module_sp->GetObjectFile() == nullptr) + module_sp = m_process->ReadModuleFromMemory(image_info.file_spec, + image_info.address); + + if (did_create_ptr) + *did_create_ptr = (bool)module_sp; + return module_sp; } diff --git a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h --- a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h +++ b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h @@ -225,6 +225,8 @@ typedef lldb_private::RangeVector FileRangeArray; lldb_private::Address m_entry_point_address; FileRangeArray m_thread_context_offsets; + lldb::offset_t m_linkedit_original_offset; + lldb::addr_t m_text_address; bool m_thread_context_offsets_valid; lldb_private::FileSpecList m_reexported_dylibs; bool m_allow_assembly_emulation_unwind_plans; diff --git a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp --- a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp +++ b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp @@ -47,8 +47,8 @@ #include "ObjectFileMachO.h" -#if defined(__APPLE__) && \ - (defined(__arm__) || defined(__arm64__) || defined(__aarch64__)) +#if defined(__APPLE__) +#include // GetLLDBSharedCacheUUID() needs to call dlsym() #include #endif @@ -1328,6 +1328,19 @@ if (m_length == 0 || seg_cmd.filesize == 0) return; + if ((m_header.flags & MH_DYLIB_IN_CACHE) && !IsInMemory()) { + // In shared cache images, the load commands are relative to the + // shared cache file, and not the the specific image we are + // examining. Let's fix this up so that it looks like a normal + // image. + if (strncmp(seg_cmd.segname, "__TEXT", sizeof(seg_cmd.segname)) == 0) + m_text_address = seg_cmd.vmaddr; + if (strncmp(seg_cmd.segname, "__LINKEDIT", sizeof(seg_cmd.segname)) == 0) + m_linkedit_original_offset = seg_cmd.fileoff; + + seg_cmd.fileoff = seg_cmd.vmaddr - m_text_address; + } + if (seg_cmd.fileoff > m_length) { // We have a load command that says it extends past the end of the file. // This is likely a corrupt file. We don't have any way to return an error @@ -1664,6 +1677,10 @@ if (m_data.GetU32(&offset, §64.offset, num_u32s) == nullptr) break; + if ((m_header.flags & MH_DYLIB_IN_CACHE) && !IsInMemory()) { + sect64.offset = sect64.addr - m_text_address; + } + // Keep a list of mach sections around in case we need to get at data that // isn't stored in the abstracted Sections. m_mach_sections.push_back(sect64); @@ -2264,14 +2281,17 @@ Process *process = process_sp.get(); uint32_t memory_module_load_level = eMemoryModuleLoadLevelComplete; + bool is_shared_cache_image = m_header.flags & MH_DYLIB_IN_CACHE; + bool is_local_shared_cache_image = is_shared_cache_image && !IsInMemory(); + SectionSP linkedit_section_sp( + section_list->FindSectionByName(GetSegmentNameLINKEDIT())); - if (process && m_header.filetype != llvm::MachO::MH_OBJECT) { + if (process && m_header.filetype != llvm::MachO::MH_OBJECT && + !is_local_shared_cache_image) { Target &target = process->GetTarget(); memory_module_load_level = target.GetMemoryModuleLoadLevel(); - SectionSP linkedit_section_sp( - section_list->FindSectionByName(GetSegmentNameLINKEDIT())); // Reading mach file from memory in a process or core file... if (linkedit_section_sp) { @@ -2293,62 +2313,6 @@ strtab_addr = linkedit_load_addr + symtab_load_command.stroff - linkedit_file_offset; - bool data_was_read = false; - -#if defined(__APPLE__) && \ - (defined(__arm__) || defined(__arm64__) || defined(__aarch64__)) - if (m_header.flags & MH_DYLIB_IN_CACHE && - process->GetAddressByteSize() == sizeof(void *)) { - // This mach-o memory file is in the dyld shared cache. If this - // program is not remote and this is iOS, then this process will - // share the same shared cache as the process we are debugging and we - // can read the entire __LINKEDIT from the address space in this - // process. This is a needed optimization that is used for local iOS - // debugging only since all shared libraries in the shared cache do - // not have corresponding files that exist in the file system of the - // device. They have been combined into a single file. This means we - // always have to load these files from memory. All of the symbol and - // string tables from all of the __LINKEDIT sections from the shared - // libraries in the shared cache have been merged into a single large - // symbol and string table. Reading all of this symbol and string - // table data across can slow down debug launch times, so we optimize - // this by reading the memory for the __LINKEDIT section from this - // process. - - UUID lldb_shared_cache; - addr_t lldb_shared_cache_addr; - GetLLDBSharedCacheUUID(lldb_shared_cache_addr, lldb_shared_cache); - UUID process_shared_cache; - addr_t process_shared_cache_addr; - GetProcessSharedCacheUUID(process, process_shared_cache_addr, - process_shared_cache); - bool use_lldb_cache = true; - if (lldb_shared_cache.IsValid() && process_shared_cache.IsValid() && - (lldb_shared_cache != process_shared_cache || - process_shared_cache_addr != lldb_shared_cache_addr)) { - use_lldb_cache = false; - } - - PlatformSP platform_sp(target.GetPlatform()); - if (platform_sp && platform_sp->IsHost() && use_lldb_cache) { - data_was_read = true; - nlist_data.SetData((void *)symoff_addr, nlist_data_byte_size, - eByteOrderLittle); - strtab_data.SetData((void *)strtab_addr, strtab_data_byte_size, - eByteOrderLittle); - if (function_starts_load_command.cmd) { - const addr_t func_start_addr = - linkedit_load_addr + function_starts_load_command.dataoff - - linkedit_file_offset; - function_starts_data.SetData((void *)func_start_addr, - function_starts_load_command.datasize, - eByteOrderLittle); - } - } - } -#endif - - if (!data_was_read) { // Always load dyld - the dynamic linker - from memory if we didn't // find a binary anywhere else. lldb will not register // dylib/framework/bundle loads/unloads if we don't have the dyld @@ -2379,7 +2343,7 @@ // problem. For binaries outside the shared cache, it's faster to // read the entire strtab at once instead of piece-by-piece as we // process the nlist records. - if ((m_header.flags & MH_DYLIB_IN_CACHE) == 0) { + if (!is_shared_cache_image) { DataBufferSP strtab_data_sp( ReadMemory(process_sp, strtab_addr, strtab_data_byte_size)); if (strtab_data_sp) { @@ -2388,7 +2352,6 @@ } } } - } if (memory_module_load_level >= eMemoryModuleLoadLevelPartial) { if (function_starts_load_command.cmd) { const addr_t func_start_addr = @@ -2405,6 +2368,24 @@ } } } else { + if (is_local_shared_cache_image) { + // The load commands in shared cache images are relative to the + // beginning of the shared cache, not the library image. The + // data we get handed when creating the ObjectFileMachO starts + // at the beginning of a specific library and spans to the end + // of the cache to be able to reach the shared LINKEDIT + // segments. We need to convert the load command offsets to be + // relative to the beginning of our specific image. + lldb::addr_t linkedit_offset = linkedit_section_sp->GetFileOffset(); + lldb::offset_t linkedit_slide = + linkedit_offset - m_linkedit_original_offset; + symtab_load_command.symoff += linkedit_slide; + symtab_load_command.stroff += linkedit_slide; + dyld_info.export_off += linkedit_slide; + m_dysymtab.indirectsymoff += linkedit_slide; + function_starts_load_command.dataoff += linkedit_slide; + } + nlist_data.SetData(m_data, symtab_load_command.symoff, nlist_data_byte_size); strtab_data.SetData(m_data, symtab_load_command.stroff, @@ -5807,8 +5788,7 @@ uuid.Clear(); base_addr = LLDB_INVALID_ADDRESS; -#if defined(__APPLE__) && \ - (defined(__arm__) || defined(__arm64__) || defined(__aarch64__)) +#if defined(__APPLE__) uint8_t *(*dyld_get_all_image_infos)(void); dyld_get_all_image_infos = (uint8_t * (*)()) dlsym(RTLD_DEFAULT, "_dyld_get_all_image_infos"); diff --git a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp --- a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp +++ b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp @@ -237,6 +237,30 @@ Status err; + if (IsHost()) { + // When debugging on the host, we are most likely using the same shared + // cache as our inferior. The dylibs from the shared cache might not + // exist on the filesystem, so let's use the images in our own memory + // to create the modules. + + // Check if the requested image is in our shared cache. + SharedCacheImageInfo image_info = + HostInfo::GetSharedCacheImageInfo(module_spec.GetFileSpec().GetPath()); + + // If we found it and it has the correct UUID, let's proceed with + // creating a module from the memory contents. + if (image_info.uuid && + (!module_spec.GetUUID() || module_spec.GetUUID() == image_info.uuid)) { + ModuleSpec shared_cache_spec(module_spec.GetFileSpec(), image_info.uuid, + image_info.data_sp); + err = ModuleList::GetSharedModule(shared_cache_spec, module_sp, + module_search_paths_ptr, + old_module_sp_ptr, did_create_ptr); + if (module_sp) + return err; + } + } + err = ModuleList::GetSharedModule(module_spec, module_sp, module_search_paths_ptr, old_module_sp_ptr, did_create_ptr); diff --git a/lldb/unittests/ObjectFile/CMakeLists.txt b/lldb/unittests/ObjectFile/CMakeLists.txt --- a/lldb/unittests/ObjectFile/CMakeLists.txt +++ b/lldb/unittests/ObjectFile/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(Breakpad) add_subdirectory(ELF) +add_subdirectory(MachO) add_subdirectory(PECOFF) diff --git a/lldb/unittests/ObjectFile/MachO/CMakeLists.txt b/lldb/unittests/ObjectFile/MachO/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/lldb/unittests/ObjectFile/MachO/CMakeLists.txt @@ -0,0 +1,10 @@ +add_lldb_unittest(ObjectFileMachOTests + TestObjectFileMachO.cpp + + LINK_LIBS + lldbPluginObjectFileMachO + lldbPluginSymbolFileSymtab + lldbCore + lldbUtilityHelpers + LLVMTestingSupport + ) diff --git a/lldb/unittests/ObjectFile/MachO/TestObjectFileMachO.cpp b/lldb/unittests/ObjectFile/MachO/TestObjectFileMachO.cpp new file mode 100644 --- /dev/null +++ b/lldb/unittests/ObjectFile/MachO/TestObjectFileMachO.cpp @@ -0,0 +1,79 @@ +//===-- ObjectFileMachOTest.cpp -------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/Host/HostInfo.h" +#include "Plugins/ObjectFile/Mach-O/ObjectFileMachO.h" +#include "TestingSupport/SubsystemRAII.h" +#include "TestingSupport/TestUtilities.h" +#include "lldb/Core/Module.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/lldb-defines.h" +#include "gtest/gtest.h" + +#ifdef __APPLE__ +#include +#endif + +using namespace lldb_private; +using namespace llvm; + +namespace { +class ObjectFileMachOTest : public ::testing::Test { + SubsystemRAII subsystems; +}; +} // namespace + +#if defined(__APPLE__) +TEST_F(ObjectFileMachOTest, ModuleFromSharedCacheInfo) { + SharedCacheImageInfo image_info = + HostInfo::GetSharedCacheImageInfo("/usr/lib/libobjc.A.dylib"); + EXPECT_TRUE(image_info.uuid); + EXPECT_TRUE(image_info.data_sp); + + ModuleSpec spec(FileSpec(), UUID(), image_info.data_sp); + lldb::ModuleSP module = std::make_shared(spec); + ObjectFile *OF = module->GetObjectFile(); + ASSERT_TRUE(llvm::isa(OF)); + EXPECT_TRUE( + OF->GetArchitecture().IsCompatibleMatch(HostInfo::GetArchitecture())); + Symtab *symtab = OF->GetSymtab(); + ASSERT_NE(symtab, nullptr); + void *libobjc = dlopen("/usr/lib/libobjc.A.dylib", RTLD_LAZY); + ASSERT_NE(libobjc, nullptr); + + // This function checks that if we read something from the + // ObjectFile we get through the shared cache in-mmeory + // buffer, it matches what we get by reading directly the + // memory of the symbol. + auto check_symbol = [&](const char *sym_name) { + std::vector symbol_indices; + symtab->FindAllSymbolsWithNameAndType(ConstString(sym_name), + lldb::eSymbolTypeAny, symbol_indices); + EXPECT_EQ(symbol_indices.size(), 1u); + + Symbol *sym = symtab->SymbolAtIndex(symbol_indices[0]); + ASSERT_NE(sym, nullptr); + Address base = sym->GetAddress(); + size_t size = sym->GetByteSize(); + ASSERT_NE(size, 0u); + uint8_t buffer[size]; + EXPECT_EQ(OF->ReadSectionData(base.GetSection().get(), base.GetOffset(), + buffer, size), + size); + + void *sym_addr = dlsym(libobjc, sym_name); + ASSERT_NE(sym_addr, nullptr); + EXPECT_EQ(memcmp(buffer, sym_addr, size), 0); + }; + + // Read a symbol from the __TEXT segment... + check_symbol("objc_msgSend"); + // ... and one from the __DATA segment + check_symbol("OBJC_CLASS_$_NSObject"); +} +#endif