blob: 3a599603ba445f3c393669e6e74f117ae7284083 [file]
//===----------------------------------------------------------------------===//
//
// 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));
}
}