| //===-- 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 "Plugins/ObjectFile/Mach-O/ObjectFileMachO.h" |
| #include "Plugins/Platform/MacOSX/PlatformMacOSX.h" |
| #include "Plugins/Platform/MacOSX/PlatformRemoteMacOSX.h" |
| #include "TestingSupport/SubsystemRAII.h" |
| #include "TestingSupport/TestUtilities.h" |
| #include "lldb/Core/Module.h" |
| #include "lldb/Core/ModuleSpec.h" |
| #include "lldb/Host/FileSystem.h" |
| #include "lldb/Host/HostInfo.h" |
| #include "lldb/Symbol/ObjectFile.h" |
| #include "lldb/Symbol/Symtab.h" |
| #include "lldb/Utility/DataExtractor.h" |
| #include "lldb/Utility/FileSpec.h" |
| #include "lldb/lldb-defines.h" |
| #include "llvm/Testing/Support/Error.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| |
| #ifdef __APPLE__ |
| #include <dlfcn.h> |
| #endif |
| |
| using namespace lldb; |
| using namespace lldb_private; |
| using namespace llvm; |
| |
| namespace { |
| class ObjectFileMachOTest : public ::testing::Test { |
| SubsystemRAII<FileSystem, HostInfo, ObjectFileMachO> subsystems; |
| }; |
| } // namespace |
| |
| #if defined(__APPLE__) |
| TEST_F(ObjectFileMachOTest, ModuleFromSharedCacheInfo) { |
| ArchSpec arch("arm64-apple-macosx-"); |
| |
| Platform::SetHostPlatform(PlatformRemoteMacOSX::CreateInstance(true, &arch)); |
| |
| SharedCacheImageInfo image_info = HostInfo::GetSharedCacheImageInfo( |
| ConstString("/usr/lib/libobjc.A.dylib"), |
| lldb::eSymbolSharedCacheUseHostSharedCache); |
| EXPECT_TRUE(image_info.GetUUID()); |
| EXPECT_TRUE(image_info.GetExtractor()); |
| |
| ModuleSpec spec(FileSpec(), UUID(), image_info.GetExtractor()); |
| lldb::ModuleSP module = std::make_shared<Module>(spec); |
| ObjectFile *OF = module->GetObjectFile(); |
| ASSERT_TRUE(llvm::isa<ObjectFileMachO>(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<uint32_t> symbol_indices; |
| symtab->FindAllSymbolsWithNameAndType(ConstString(sym_name), |
| lldb::eSymbolTypeAny, symbol_indices); |
| EXPECT_EQ(symbol_indices.size(), 1u); |
| |
| const 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_begin_catch"); |
| // ... and one from the __DATA segment |
| check_symbol("OBJC_IVAR_$_NSObject.isa"); |
| } |
| |
| TEST_F(ObjectFileMachOTest, IndirectSymbolsInTheSharedCache) { |
| SharedCacheImageInfo image_info = HostInfo::GetSharedCacheImageInfo( |
| ConstString( |
| "/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit"), |
| lldb::eSymbolSharedCacheUseHostSharedCache); |
| ModuleSpec spec(FileSpec(), UUID(), image_info.GetExtractor()); |
| lldb::ModuleSP module = std::make_shared<Module>(spec); |
| |
| ObjectFile *OF = module->GetObjectFile(); |
| ASSERT_TRUE(llvm::isa<ObjectFileMachO>(OF)); |
| EXPECT_TRUE( |
| OF->GetArchitecture().IsCompatibleMatch(HostInfo::GetArchitecture())); |
| |
| // Check that we can parse the symbol table several times over without |
| // crashing. |
| Symtab symtab(OF); |
| for (size_t i = 0; i < 10; i++) |
| OF->ParseSymtab(symtab); |
| } |
| #endif |
| |
| // Regression fixture: a Mach-O whose load commands all have cmdsize = 0. |
| // With ncmds set near INT_MAX the functions hang. The fix breaks out of |
| // the loop as soon as cmdsize < sizeof(load_command). Found by |
| // lldb-target-fuzzer. |
| TEST_F(ObjectFileMachOTest, ZeroCmdSize) { |
| // Minimal little-endian x86_64 Mach-O: mach_header_64 (32 bytes) followed |
| // by a single load_command with cmdsize = 0. ncmds is set to 0x7FFFFFFF so |
| // that without the fix the loops in GetAllArchSpecs/GetUUID never return in |
| // practice; with the fix they break on the very first iteration. |
| // Reaching the assertion below is the regression check. |
| auto ExpectedFile = TestFile::fromYaml(R"( |
| --- !mach-o |
| FileHeader: |
| magic: 0xFEEDFACF |
| cputype: 0x01000007 |
| cpusubtype: 0x80000003 |
| filetype: 0x00000002 |
| ncmds: 0x7FFFFFFF |
| sizeofcmds: 8 |
| flags: 0x00000000 |
| reserved: 0x00000000 |
| LoadCommands: |
| - cmd: LC_THREAD |
| cmdsize: 0 |
| ... |
| )"); |
| ASSERT_THAT_EXPECTED(ExpectedFile, llvm::Succeeded()); |
| |
| ModuleSpec Spec = ExpectedFile->moduleSpec(); |
| lldb::DataExtractorSP DataSP = Spec.GetExtractor(); |
| // Before the fix GetAllArchSpecs loops ~0x7FFFFFFF times and never returns. |
| (void)ObjectFile::GetModuleSpecifications(FileSpec(), DataSP, 0, |
| DataSP->GetByteSize()); |
| } |
| |
| // A Mach-O whose MH_DYLIB_IN_CACHE flag is set but which has no __LINKEDIT |
| // segment. |
| TEST_F(ObjectFileMachOTest, ParseSymtabSharedCacheMissingLinkedit) { |
| const char *yamldata = R"( |
| --- !mach-o |
| FileHeader: |
| magic: 0xFEEDFACF |
| cputype: 0x01000007 |
| cpusubtype: 0x00000003 |
| filetype: 0x00000006 |
| ncmds: 2 |
| sizeofcmds: 96 |
| flags: 0x80000000 |
| reserved: 0x00000000 |
| LoadCommands: |
| - cmd: LC_SEGMENT_64 |
| cmdsize: 72 |
| segname: __TEXT |
| vmaddr: 0 |
| vmsize: 4096 |
| fileoff: 0 |
| filesize: 0 |
| maxprot: 7 |
| initprot: 5 |
| nsects: 0 |
| flags: 0 |
| - cmd: LC_SYMTAB |
| cmdsize: 24 |
| symoff: 0 |
| nsyms: 0 |
| stroff: 0 |
| strsize: 0 |
| ... |
| )"; |
| |
| llvm::Expected<TestFile> file = TestFile::fromYaml(yamldata); |
| ASSERT_THAT_EXPECTED(file, llvm::Succeeded()); |
| lldb::ModuleSP module = std::make_shared<Module>(file->moduleSpec()); |
| ObjectFile *OF = module->GetObjectFile(); |
| ASSERT_TRUE(llvm::isa<ObjectFileMachO>(OF)); |
| |
| // Simply no crashing is the regression check. |
| Symtab symtab(OF); |
| OF->ParseSymtab(symtab); |
| } |