//===-- LineTableTest.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/ELF/ObjectFileELF.h" #include "TestingSupport/SubsystemRAII.h" #include "TestingSupport/TestUtilities.h" #include "lldb/Core/PluginManager.h" #include "lldb/Symbol/CompileUnit.h" #include "lldb/Symbol/SymbolFile.h" #include "gtest/gtest.h" #include using namespace lldb; using namespace llvm; using namespace lldb_private; namespace { // A fake symbol file class to allow us to create the line table "the right // way". Pretty much all methods except for GetCompileUnitAtIndex and // GetNumCompileUnits are stubbed out. class FakeSymbolFile : public SymbolFile { public: /// LLVM RTTI support. /// \{ bool isA(const void *ClassID) const override { return ClassID == &ID || SymbolFile::isA(ClassID); } static bool classof(const SymbolFile *obj) { return obj->isA(&ID); } /// \} static void Initialize() { PluginManager::RegisterPlugin("FakeSymbolFile", "", CreateInstance, DebuggerInitialize); } static void Terminate() { PluginManager::UnregisterPlugin(CreateInstance); } void InjectCompileUnit(std::unique_ptr cu_up) { m_cu_sp = std::move(cu_up); } private: /// LLVM RTTI support. static char ID; static SymbolFile *CreateInstance(ObjectFileSP objfile_sp) { return new FakeSymbolFile(std::move(objfile_sp)); } static void DebuggerInitialize(Debugger &) {} StringRef GetPluginName() override { return "FakeSymbolFile"; } uint32_t GetAbilities() override { return UINT32_MAX; } uint32_t CalculateAbilities() override { return UINT32_MAX; } uint32_t GetNumCompileUnits() override { return 1; } CompUnitSP GetCompileUnitAtIndex(uint32_t) override { return m_cu_sp; } Symtab *GetSymtab(bool can_create = true) override { return nullptr; } LanguageType ParseLanguage(CompileUnit &) override { return eLanguageTypeC; } size_t ParseFunctions(CompileUnit &) override { return 0; } bool ParseLineTable(CompileUnit &) override { return true; } bool ParseDebugMacros(CompileUnit &) override { return true; } bool ParseSupportFiles(CompileUnit &, SupportFileList &) override { return true; } size_t ParseTypes(CompileUnit &) override { return 0; } bool ParseImportedModules(const SymbolContext &, std::vector &) override { return false; } size_t ParseBlocksRecursive(Function &) override { return 0; } size_t ParseVariablesForContext(const SymbolContext &) override { return 0; } Type *ResolveTypeUID(user_id_t) override { return nullptr; } std::optional GetDynamicArrayInfoForUID(user_id_t, const ExecutionContext *) override { return std::nullopt; } bool CompleteType(CompilerType &) override { return true; } uint32_t ResolveSymbolContext(const Address &, SymbolContextItem, SymbolContext &) override { return 0; } void GetTypes(SymbolContextScope *, TypeClass, TypeList &) override {} Expected GetTypeSystemForLanguage(LanguageType) override { return createStringError(std::errc::not_supported, ""); } const ObjectFile *GetObjectFile() const override { return m_objfile_sp.get(); } ObjectFile *GetObjectFile() override { return m_objfile_sp.get(); } ObjectFile *GetMainObjectFile() override { return m_objfile_sp.get(); } void SectionFileAddressesChanged() override {} void Dump(Stream &) override {} uint64_t GetDebugInfoSize(bool) override { return 0; } bool GetDebugInfoIndexWasLoadedFromCache() const override { return false; } void SetDebugInfoIndexWasLoadedFromCache() override {} bool GetDebugInfoIndexWasSavedToCache() const override { return false; } void SetDebugInfoIndexWasSavedToCache() override {} bool GetDebugInfoHadFrameVariableErrors() const override { return false; } void SetDebugInfoHadFrameVariableErrors() override {} TypeSP MakeType(user_id_t, ConstString, std::optional, SymbolContextScope *, user_id_t, Type::EncodingDataType, const Declaration &, const CompilerType &, Type::ResolveState, uint32_t) override { return nullptr; } TypeSP CopyType(const TypeSP &) override { return nullptr; } FakeSymbolFile(ObjectFileSP objfile_sp) : m_objfile_sp(std::move(objfile_sp)) {} ObjectFileSP m_objfile_sp; CompUnitSP m_cu_sp; }; struct FakeModuleFixture { TestFile file; ModuleSP module_sp; SectionSP text_sp; LineTable *line_table; }; class LineTableTest : public testing::Test { SubsystemRAII subsystems; }; class LineSequenceBuilder { public: std::vector Build() { return std::move(m_sequences); } enum Terminal : bool { Terminal = true }; void Entry(addr_t addr, bool terminal = false) { LineTable::AppendLineEntryToSequence( m_sequence, addr, /*line=*/1, /*column=*/0, /*file_idx=*/0, /*is_start_of_statement=*/false, /*is_start_of_basic_block=*/false, /*is_prologue_end=*/false, /*is_epilogue_begin=*/false, terminal); if (terminal) m_sequences.push_back(std::move(m_sequence)); } private: std::vector m_sequences; LineTable::Sequence m_sequence; }; } // namespace char FakeSymbolFile::ID; static llvm::Expected CreateFakeModule(std::vector line_sequences) { Expected file = TestFile::fromYaml(R"( --- !ELF FileHeader: Class: ELFCLASS64 Data: ELFDATA2LSB Type: ET_EXEC Machine: EM_386 Sections: - Name: .text Type: SHT_PROGBITS Flags: [ SHF_ALLOC, SHF_EXECINSTR ] AddressAlign: 0x0010 Address: 0x0000 Size: 0x1000 )"); if (!file) return file.takeError(); auto module_sp = std::make_shared(file->moduleSpec()); SectionSP text_sp = module_sp->GetSectionList()->FindSectionByName(ConstString(".text")); if (!text_sp) return createStringError("No .text"); auto cu_up = std::make_unique( module_sp, /*user_data=*/nullptr, /*support_file_nsp=*/std::make_shared(), /*uid=*/0, eLanguageTypeC, /*is_optimized=*/eLazyBoolNo); LineTable *line_table = new LineTable(cu_up.get(), std::move(line_sequences)); cu_up->SetLineTable(line_table); cast(module_sp->GetSymbolFile()) ->InjectCompileUnit(std::move(cu_up)); return FakeModuleFixture{std::move(*file), std::move(module_sp), std::move(text_sp), line_table}; } TEST_F(LineTableTest, lower_bound) { LineSequenceBuilder builder; builder.Entry(0); builder.Entry(10); builder.Entry(20, LineSequenceBuilder::Terminal); builder.Entry(20); // Starts right after the previous sequence. builder.Entry(30, LineSequenceBuilder::Terminal); builder.Entry(40); // Gap after the previous sequence. builder.Entry(50, LineSequenceBuilder::Terminal); llvm::Expected fixture = CreateFakeModule(builder.Build()); ASSERT_THAT_EXPECTED(fixture, llvm::Succeeded()); LineTable *table = fixture->line_table; auto make_addr = [&](addr_t addr) { return Address(fixture->text_sp, addr); }; EXPECT_EQ(table->lower_bound(make_addr(0)), 0u); EXPECT_EQ(table->lower_bound(make_addr(9)), 0u); EXPECT_EQ(table->lower_bound(make_addr(10)), 1u); EXPECT_EQ(table->lower_bound(make_addr(19)), 1u); // Skips over the terminal entry. EXPECT_EQ(table->lower_bound(make_addr(20)), 3u); EXPECT_EQ(table->lower_bound(make_addr(29)), 3u); // In case there's no "real" entry at this address, the function returns the // first real entry. EXPECT_EQ(table->lower_bound(make_addr(30)), 5u); EXPECT_EQ(table->lower_bound(make_addr(40)), 5u); // In a gap, return the first entry after the gap. EXPECT_EQ(table->lower_bound(make_addr(39)), 5u); // And if there's no such entry, return the size of the list. EXPECT_EQ(table->lower_bound(make_addr(50)), table->GetSize()); EXPECT_EQ(table->lower_bound(make_addr(59)), table->GetSize()); } TEST_F(LineTableTest, GetLineEntryIndexRange) { LineSequenceBuilder builder; builder.Entry(0); builder.Entry(10); builder.Entry(20, LineSequenceBuilder::Terminal); llvm::Expected fixture = CreateFakeModule(builder.Build()); ASSERT_THAT_EXPECTED(fixture, llvm::Succeeded()); LineTable *table = fixture->line_table; auto make_range = [&](addr_t addr, addr_t size) { return AddressRange(fixture->text_sp, addr, size); }; EXPECT_THAT(table->GetLineEntryIndexRange(make_range(0, 10)), testing::Pair(0, 1)); EXPECT_THAT(table->GetLineEntryIndexRange(make_range(0, 20)), testing::Pair(0, 3)); // Includes the terminal entry. // Partial overlap on one side. EXPECT_THAT(table->GetLineEntryIndexRange(make_range(3, 7)), testing::Pair(0, 1)); // On the other side EXPECT_THAT(table->GetLineEntryIndexRange(make_range(0, 15)), testing::Pair(0, 2)); // On both sides EXPECT_THAT(table->GetLineEntryIndexRange(make_range(2, 3)), testing::Pair(0, 1)); // Empty ranges EXPECT_THAT(table->GetLineEntryIndexRange(make_range(0, 0)), testing::Pair(0, 0)); EXPECT_THAT(table->GetLineEntryIndexRange(make_range(5, 0)), testing::Pair(0, 0)); EXPECT_THAT(table->GetLineEntryIndexRange(make_range(10, 0)), testing::Pair(1, 1)); } TEST_F(LineTableTest, FindLineEntryByAddress) { LineSequenceBuilder builder; builder.Entry(0); builder.Entry(10); builder.Entry(20, LineSequenceBuilder::Terminal); builder.Entry(20); // Starts right after the previous sequence. builder.Entry(30, LineSequenceBuilder::Terminal); builder.Entry(40); // Gap after the previous sequence. builder.Entry(50, LineSequenceBuilder::Terminal); llvm::Expected fixture = CreateFakeModule(builder.Build()); ASSERT_THAT_EXPECTED(fixture, llvm::Succeeded()); LineTable *table = fixture->line_table; auto find = [&](addr_t addr) -> std::tuple { LineEntry entry; if (!table->FindLineEntryByAddress(Address(fixture->text_sp, addr), entry)) return {LLDB_INVALID_ADDRESS, LLDB_INVALID_ADDRESS, false}; return {entry.range.GetBaseAddress().GetFileAddress(), entry.range.GetByteSize(), static_cast(entry.is_terminal_entry)}; }; EXPECT_THAT(find(0), testing::FieldsAre(0, 10, false)); EXPECT_THAT(find(9), testing::FieldsAre(0, 10, false)); EXPECT_THAT(find(10), testing::FieldsAre(10, 10, false)); EXPECT_THAT(find(19), testing::FieldsAre(10, 10, false)); EXPECT_THAT(find(20), testing::FieldsAre(20, 10, false)); EXPECT_THAT(find(30), testing::FieldsAre(LLDB_INVALID_ADDRESS, LLDB_INVALID_ADDRESS, false)); EXPECT_THAT(find(40), testing::FieldsAre(40, 10, false)); EXPECT_THAT(find(50), testing::FieldsAre(LLDB_INVALID_ADDRESS, LLDB_INVALID_ADDRESS, false)); }