| //===----------------------------------------------------------------------===// | |
| // | |
| // 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/Core/Module.h" | |
| #include "Plugins/Language/CPlusPlus/CPlusPlusLanguage.h" | |
| #include "Plugins/ObjectFile/ELF/ObjectFileELF.h" | |
| #include "Plugins/SymbolFile/Symtab/SymbolFileSymtab.h" | |
| #include "TestingSupport/SubsystemRAII.h" | |
| #include "TestingSupport/TestUtilities.h" | |
| #include "lldb/Core/PluginManager.h" | |
| #include "lldb/Core/Section.h" | |
| #include "lldb/Host/FileSystem.h" | |
| #include "lldb/Host/HostInfo.h" | |
| #include "lldb/Target/Language.h" | |
| #include "lldb/Utility/ConstString.h" | |
| #include "gtest/gtest.h" | |
| #include <condition_variable> | |
| #include <mutex> | |
| #include <optional> | |
| #include <thread> | |
| #include <vector> | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| // Test that Module::FindFunctions correctly finds C++ mangled symbols | |
| // even when multiple language plugins are registered. | |
| TEST(ModuleTest, FindFunctionsCppMangledName) { | |
| // Create a mock language. The point of this language is to return something | |
| // in GetFunctionNameInfo that would interfere with the C++ language plugin, | |
| // were they sharing the same LookupInfo. | |
| class MockLanguageWithBogusLookupInfo : public Language { | |
| public: | |
| MockLanguageWithBogusLookupInfo() = default; | |
| ~MockLanguageWithBogusLookupInfo() override = default; | |
| lldb::LanguageType GetLanguageType() const override { | |
| // The language here doesn't really matter, it just has to be something | |
| // that is not C/C++/ObjC. | |
| return lldb::eLanguageTypeSwift; | |
| } | |
| llvm::StringRef GetPluginName() override { return "mock-bogus-language"; } | |
| bool IsSourceFile(llvm::StringRef file_path) const override { | |
| return file_path.ends_with(".swift"); | |
| } | |
| std::pair<lldb::FunctionNameType, std::optional<ConstString>> | |
| GetFunctionNameInfo(ConstString name) const override { | |
| // Say that every function is a selector. | |
| return {lldb::eFunctionNameTypeSelector, ConstString("BOGUS_BASENAME")}; | |
| } | |
| static void Initialize() { | |
| PluginManager::RegisterPlugin(GetPluginNameStatic(), "Mock Language", | |
| CreateInstance); | |
| } | |
| static void Terminate() { PluginManager::UnregisterPlugin(CreateInstance); } | |
| static lldb_private::Language *CreateInstance(lldb::LanguageType language) { | |
| if (language == lldb::eLanguageTypeSwift) | |
| return new MockLanguageWithBogusLookupInfo(); | |
| return nullptr; | |
| } | |
| static llvm::StringRef GetPluginNameStatic() { | |
| return "mock-bogus-language"; | |
| } | |
| }; | |
| SubsystemRAII<FileSystem, HostInfo, ObjectFileELF, SymbolFileSymtab, | |
| CPlusPlusLanguage, MockLanguageWithBogusLookupInfo> | |
| subsystems; | |
| // Create a simple ELF module with std::vector::size() as the only symbol. | |
| auto ExpectedFile = TestFile::fromYaml(R"( | |
| --- !ELF | |
| FileHeader: | |
| Class: ELFCLASS64 | |
| Data: ELFDATA2LSB | |
| Type: ET_DYN | |
| Machine: EM_X86_64 | |
| Sections: | |
| - Name: .text | |
| Type: SHT_PROGBITS | |
| Flags: [ SHF_ALLOC, SHF_EXECINSTR ] | |
| Address: 0x1000 | |
| AddressAlign: 0x10 | |
| Size: 0x100 | |
| Symbols: | |
| - Name: _ZNSt6vectorIiE4sizeEv | |
| Type: STT_FUNC | |
| Section: .text | |
| Value: 0x1030 | |
| Size: 0x20 | |
| ... | |
| )"); | |
| ASSERT_THAT_EXPECTED(ExpectedFile, llvm::Succeeded()); | |
| auto module_sp = std::make_shared<Module>(ExpectedFile->moduleSpec()); | |
| // Verify both C++ and our mock language are registered. | |
| Language *cpp_lang = Language::FindPlugin(lldb::eLanguageTypeC_plus_plus); | |
| Language *mock_lang = Language::FindPlugin(lldb::eLanguageTypeSwift); | |
| ASSERT_NE(cpp_lang, nullptr) << "C++ language plugin should be registered"; | |
| ASSERT_NE(mock_lang, nullptr) | |
| << "Mock Swift language plugin should be registered"; | |
| ModuleFunctionSearchOptions function_options; | |
| function_options.include_symbols = true; | |
| ConstString symbol_name("_ZNSt6vectorIiE4sizeEv"); | |
| SymbolContextList results; | |
| module_sp->FindFunctions(symbol_name, CompilerDeclContext(), | |
| eFunctionNameTypeAuto, function_options, results); | |
| // Assert that we found one symbol. | |
| ASSERT_EQ(results.GetSize(), 1u); | |
| auto result = results[0]; | |
| auto name = result.GetFunctionName(); | |
| // Assert that the symbol we found is what we expected. | |
| ASSERT_EQ(name, "std::vector<int>::size()"); | |
| ASSERT_EQ(result.GetLanguage(), eLanguageTypeC_plus_plus); | |
| } | |
| TEST(ModuleTest, ResolveSymbolContextForAddressExactMatch) { | |
| // Test that ResolveSymbolContextForAddress prefers exact symbol matches | |
| // over symbols that merely contain the address. | |
| SubsystemRAII<FileSystem, HostInfo, ObjectFileELF, SymbolFileSymtab> | |
| subsystems; | |
| auto ExpectedFile = TestFile::fromYaml(R"( | |
| --- !ELF | |
| FileHeader: | |
| Class: ELFCLASS64 | |
| Data: ELFDATA2LSB | |
| Type: ET_DYN | |
| Machine: EM_X86_64 | |
| Sections: | |
| - Name: .text | |
| Type: SHT_PROGBITS | |
| Flags: [ SHF_ALLOC, SHF_EXECINSTR ] | |
| Address: 0x1000 | |
| AddressAlign: 0x10 | |
| Size: 0x200 | |
| Symbols: | |
| - Name: outer_function | |
| Type: STT_FUNC | |
| Section: .text | |
| Value: 0x1000 | |
| Size: 0x100 | |
| - Name: inner_function | |
| Type: STT_FUNC | |
| Section: .text | |
| Value: 0x1050 | |
| Size: 0x10 | |
| ... | |
| )"); | |
| ASSERT_THAT_EXPECTED(ExpectedFile, llvm::Succeeded()); | |
| auto module_sp = std::make_shared<Module>(ExpectedFile->moduleSpec()); | |
| Address addr(module_sp->GetSectionList()->GetSectionAtIndex(0), 0x50); | |
| SymbolContext sc; | |
| uint32_t resolved = | |
| module_sp->ResolveSymbolContextForAddress(addr, eSymbolContextSymbol, sc); | |
| ASSERT_TRUE(resolved & eSymbolContextSymbol); | |
| ASSERT_NE(sc.symbol, nullptr); | |
| EXPECT_STREQ(sc.symbol->GetName().GetCString(), "inner_function"); | |
| } | |
| // Module::GetSectionList builds the module's section list lazily. Concurrent | |
| // first-time callers (e.g. AppleObjCRuntime::GetObjCVersion during parallel | |
| // SBTarget module loading) must not race on m_sections_up. This hammers | |
| // GetSectionList from several threads on a fresh module so a sanitizer flags an | |
| // unsynchronized build. | |
| TEST(ModuleTest, GetSectionListConcurrent) { | |
| SubsystemRAII<FileSystem, HostInfo, ObjectFileELF, SymbolFileSymtab> | |
| subsystems; | |
| // Several sections widen the window during which CreateSections is appending | |
| // to the SectionList vector while another thread iterates it. | |
| const char *yaml = R"( | |
| --- !ELF | |
| FileHeader: | |
| Class: ELFCLASS64 | |
| Data: ELFDATA2LSB | |
| Type: ET_DYN | |
| Machine: EM_X86_64 | |
| Sections: | |
| - Name: .text | |
| Type: SHT_PROGBITS | |
| Flags: [ SHF_ALLOC, SHF_EXECINSTR ] | |
| Address: 0x1000 | |
| AddressAlign: 0x10 | |
| Size: 0x100 | |
| - Name: .data | |
| Type: SHT_PROGBITS | |
| Flags: [ SHF_ALLOC, SHF_WRITE ] | |
| Address: 0x2000 | |
| AddressAlign: 0x10 | |
| Size: 0x100 | |
| - Name: .rodata | |
| Type: SHT_PROGBITS | |
| Flags: [ SHF_ALLOC ] | |
| Address: 0x3000 | |
| AddressAlign: 0x10 | |
| Size: 0x100 | |
| - Name: .bss | |
| Type: SHT_NOBITS | |
| Flags: [ SHF_ALLOC, SHF_WRITE ] | |
| Address: 0x4000 | |
| AddressAlign: 0x10 | |
| Size: 0x100 | |
| ... | |
| )"; | |
| llvm::StringRef text_name(".text"); | |
| constexpr int kThreads = 8; | |
| // Each iteration uses a fresh module so the lazy build (and its race) is | |
| // re-triggered every time. | |
| for (int iter = 0; iter < 100; ++iter) { | |
| auto ExpectedFile = TestFile::fromYaml(yaml); | |
| ASSERT_THAT_EXPECTED(ExpectedFile, llvm::Succeeded()); | |
| auto module_sp = std::make_shared<Module>(ExpectedFile->moduleSpec()); | |
| // Release the threads together with a blocking gate. A busy-wait would peg | |
| // every core and starve the workers when many test binaries run at once. | |
| std::mutex mutex; | |
| std::condition_variable cv; | |
| bool go = false; | |
| std::vector<std::thread> threads; | |
| threads.reserve(kThreads); | |
| for (int t = 0; t < kThreads; ++t) { | |
| threads.emplace_back([&] { | |
| { | |
| std::unique_lock<std::mutex> lock(mutex); | |
| cv.wait(lock, [&] { return go; }); | |
| } | |
| if (SectionList *sections = module_sp->GetSectionList()) | |
| sections->FindSectionByName(text_name); | |
| }); | |
| } | |
| { | |
| std::lock_guard<std::mutex> lock(mutex); | |
| go = true; | |
| } | |
| cv.notify_all(); | |
| for (auto &th : threads) | |
| th.join(); | |
| // The concurrently-built list must be intact and complete. | |
| SectionList *sections = module_sp->GetSectionList(); | |
| ASSERT_NE(sections, nullptr); | |
| EXPECT_TRUE(sections->FindSectionByName(text_name)); | |
| } | |
| } |