diff --git a/lldb/include/lldb/Symbol/LineTable.h b/lldb/include/lldb/Symbol/LineTable.h
index 6d158ab518879..f66081b6ee110 100644
--- a/lldb/include/lldb/Symbol/LineTable.h
+++ b/lldb/include/lldb/Symbol/LineTable.h
@@ -102,6 +102,19 @@ class LineTable {
void GetDescription(Stream *s, Target *target, lldb::DescriptionLevel level);
+ /// Helper function for line table iteration. \c lower_bound returns the index
+ /// of the first line entry which ends after the given address (i.e., the
+ /// first entry which contains the given address or it comes after it).
+ /// \c upper_bound returns the index of the first line entry which begins on
+ /// or after the given address (i.e., the entry which would come after the
+ /// entry containing the given address, if such an entry exists). Functions
+ /// return GetSize() if there is no such entry. The functions are
+ /// most useful in combination: iterating from lower_bound(a) to
+ /// upper_bound(b) returns all line tables which intersect the half-open
+ /// range [a,b).
+ uint32_t lower_bound(const Address &so_addr) const;
+ uint32_t upper_bound(const Address &so_addr) const;
+
/// Find a line entry that contains the section offset address \a so_addr.
///
/// \param[in] so_addr
diff --git a/lldb/source/Symbol/LineTable.cpp b/lldb/source/Symbol/LineTable.cpp
index 3d2afcdd11997..aae4ab59ff156 100644
--- a/lldb/source/Symbol/LineTable.cpp
+++ b/lldb/source/Symbol/LineTable.cpp
@@ -123,7 +123,7 @@ void LineTable::InsertSequence(LineSequence *sequence) {
entry_collection::iterator end_pos = m_entries.end();
LineTable::Entry::LessThanBinaryPredicate less_than_bp(this);
entry_collection::iterator pos =
- upper_bound(begin_pos, end_pos, entry, less_than_bp);
+ std::upper_bound(begin_pos, end_pos, entry, less_than_bp);
// We should never insert a sequence in the middle of another sequence
if (pos != begin_pos) {
@@ -185,6 +185,48 @@ bool LineTable::GetLineEntryAtIndex(uint32_t idx, LineEntry &line_entry) {
return false;
}
+uint32_t LineTable::lower_bound(const Address &so_addr) const {
+ if (so_addr.GetModule() != m_comp_unit->GetModule())
+ return GetSize();
+
+ Entry search_entry;
+ search_entry.file_addr = so_addr.GetFileAddress();
+ if (search_entry.file_addr == LLDB_INVALID_ADDRESS)
+ return GetSize();
+
+ // This is not a typo. upper_bound returns the first entry which definitely
+ // does not contain this address, which means the entry before it *might*
+ // contain it -- if it is not a termination entry.
+ auto pos =
+ llvm::upper_bound(m_entries, search_entry, Entry::EntryAddressLessThan);
+
+ if (pos != m_entries.begin() && !std::prev(pos)->is_terminal_entry)
+ --pos;
+
+ return std::distance(m_entries.begin(), pos);
+}
+
+uint32_t LineTable::upper_bound(const Address &so_addr) const {
+ if (so_addr.GetModule() != m_comp_unit->GetModule())
+ return GetSize();
+
+ Entry search_entry;
+ search_entry.file_addr = so_addr.GetFileAddress();
+ if (search_entry.file_addr == LLDB_INVALID_ADDRESS)
+ return GetSize();
+
+ // This is not a typo. lower_bound returns the first entry which starts on or
+ // after the given address, which is exactly what we want -- *except* if the
+ // entry is a termination entry (in that case, we want the one after it).
+ auto pos =
+ llvm::lower_bound(m_entries, search_entry, Entry::EntryAddressLessThan);
+ if (pos != m_entries.end() && pos->file_addr == search_entry.file_addr &&
+ pos->is_terminal_entry)
+ ++pos;
+
+ return std::distance(m_entries.begin(), pos);
+}
+
bool LineTable::FindLineEntryByAddress(const Address &so_addr,
LineEntry &line_entry,
uint32_t *index_ptr) {
@@ -199,7 +241,7 @@ bool LineTable::FindLineEntryByAddress(const Address &so_addr,
if (search_entry.file_addr != LLDB_INVALID_ADDRESS) {
entry_collection::const_iterator begin_pos = m_entries.begin();
entry_collection::const_iterator end_pos = m_entries.end();
- entry_collection::const_iterator pos = lower_bound(
+ entry_collection::const_iterator pos = std::lower_bound(
begin_pos, end_pos, search_entry, Entry::EntryAddressLessThan);
if (pos != end_pos) {
if (pos != begin_pos) {
diff --git a/lldb/unittests/Symbol/CMakeLists.txt b/lldb/unittests/Symbol/CMakeLists.txt
index e1d24357e33db..ab5cecd101833 100644
--- a/lldb/unittests/Symbol/CMakeLists.txt
+++ b/lldb/unittests/Symbol/CMakeLists.txt
@@ -1,5 +1,6 @@
add_lldb_unittest(SymbolTests
JSONSymbolTest.cpp
+ LineTableTest.cpp
LocateSymbolFileTest.cpp
MangledTest.cpp
PostfixExpressionTest.cpp
diff --git a/lldb/unittests/Symbol/LineTableTest.cpp b/lldb/unittests/Symbol/LineTableTest.cpp
new file mode 100644
index 0000000000000..2fa2913f67f9e
--- /dev/null
+++ b/lldb/unittests/Symbol/LineTableTest.cpp
@@ -0,0 +1,285 @@
+//===-- 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() 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_seq_up.get(), 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_seq_up));
+ m_seq_up = LineTable::CreateLineSequenceContainer();
+ }
+ }
+
+private:
+ std::vector> m_sequences;
+ std::unique_ptr m_seq_up =
+ LineTable::CreateLineSequenceContainer();
+};
+
+} // 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_sp=*/nullptr,
+ /*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, LowerAndUpperBound) {
+ 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); };
+
+ // Both functions return the same value for boundary values. This way the
+ // index range for e.g. [0,10) is [0,1).
+ EXPECT_EQ(table->lower_bound(make_addr(0)), 0u);
+ EXPECT_EQ(table->upper_bound(make_addr(0)), 0u);
+ EXPECT_EQ(table->lower_bound(make_addr(10)), 1u);
+ EXPECT_EQ(table->upper_bound(make_addr(10)), 1u);
+ EXPECT_EQ(table->lower_bound(make_addr(20)), 3u);
+ EXPECT_EQ(table->upper_bound(make_addr(20)), 3u);
+
+ // In case there's no "real" entry at this address, they return the first real
+ // entry.
+ EXPECT_EQ(table->lower_bound(make_addr(30)), 5u);
+ EXPECT_EQ(table->upper_bound(make_addr(30)), 5u);
+
+ EXPECT_EQ(table->lower_bound(make_addr(40)), 5u);
+ EXPECT_EQ(table->upper_bound(make_addr(40)), 5u);
+
+ // For in-between values, their result differs by one. [9,19) maps to [0,2)
+ // because the first two entries contain a part of that range.
+ EXPECT_EQ(table->lower_bound(make_addr(9)), 0u);
+ EXPECT_EQ(table->upper_bound(make_addr(9)), 1u);
+ EXPECT_EQ(table->lower_bound(make_addr(19)), 1u);
+ EXPECT_EQ(table->upper_bound(make_addr(19)), 2u);
+ EXPECT_EQ(table->lower_bound(make_addr(29)), 3u);
+ EXPECT_EQ(table->upper_bound(make_addr(29)), 4u);
+
+ // In a gap, they both return the first entry after the gap.
+ EXPECT_EQ(table->upper_bound(make_addr(39)), 5u);
+ EXPECT_EQ(table->upper_bound(make_addr(39)), 5u);
+
+ // And if there's no such entry, they return the size of the list.
+ EXPECT_EQ(table->lower_bound(make_addr(50)), table->GetSize());
+ EXPECT_EQ(table->upper_bound(make_addr(50)), table->GetSize());
+ EXPECT_EQ(table->lower_bound(make_addr(59)), table->GetSize());
+ EXPECT_EQ(table->upper_bound(make_addr(59)), table->GetSize());
+}
+
+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));
+}