Skip to content

Commit fadbc33

Browse files
labathJDevlieghere
andauthored
[lldb] Add LineTable::{upper,lower}_bound (llvm#127519)
The motivation is llvm#123622 and the fact that is hard to fine the last line entry in a given range. `FindLineEntryByAddress(range_end-1)` is the best we have, but it's not ideal because it has a magic -1 and that it relies on there existing a line entry at that address (generally, it should be there, but if for some case it isn't, we might end up ignoring the entries that are there (or -- like my incorrect fix in llvm#123622 did -- iterating through the entire line table). What we really want is to get the last entry that exists in the given range. Or, equivalently (and more STL-like) the first entry after that range. This is what these functions do. I've used the STL names since they do pretty much exactly what the standard functions do (the main head-scratcher comes from the fact that our entries represent ranges rather than single values). The functions can also be used to simplify the maze of `if` statements in `FindLineEntryByAddress`, but I'm keeping that as a separate patch. For now, I'm just adding some unit testing for that function to gain more confidence that the patch does not change the function behavior. --------- Co-authored-by: Jonas Devlieghere <jonas@devlieghere.com>
1 parent 404f94a commit fadbc33

File tree

4 files changed

+343
-2
lines changed

4 files changed

+343
-2
lines changed

lldb/include/lldb/Symbol/LineTable.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,19 @@ class LineTable {
102102

103103
void GetDescription(Stream *s, Target *target, lldb::DescriptionLevel level);
104104

105+
/// Helper function for line table iteration. \c lower_bound returns the index
106+
/// of the first line entry which ends after the given address (i.e., the
107+
/// first entry which contains the given address or it comes after it).
108+
/// \c upper_bound returns the index of the first line entry which begins on
109+
/// or after the given address (i.e., the entry which would come after the
110+
/// entry containing the given address, if such an entry exists). Functions
111+
/// return <tt>GetSize()</tt> if there is no such entry. The functions are
112+
/// most useful in combination: iterating from <tt>lower_bound(a)</tt> to
113+
/// <tt>upper_bound(b) returns all line tables which intersect the half-open
114+
/// range <tt>[a,b)</tt>.
115+
uint32_t lower_bound(const Address &so_addr) const;
116+
uint32_t upper_bound(const Address &so_addr) const;
117+
105118
/// Find a line entry that contains the section offset address \a so_addr.
106119
///
107120
/// \param[in] so_addr

lldb/source/Symbol/LineTable.cpp

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ void LineTable::InsertSequence(LineSequence *sequence) {
123123
entry_collection::iterator end_pos = m_entries.end();
124124
LineTable::Entry::LessThanBinaryPredicate less_than_bp(this);
125125
entry_collection::iterator pos =
126-
upper_bound(begin_pos, end_pos, entry, less_than_bp);
126+
std::upper_bound(begin_pos, end_pos, entry, less_than_bp);
127127

128128
// We should never insert a sequence in the middle of another sequence
129129
if (pos != begin_pos) {
@@ -185,6 +185,48 @@ bool LineTable::GetLineEntryAtIndex(uint32_t idx, LineEntry &line_entry) {
185185
return false;
186186
}
187187

188+
uint32_t LineTable::lower_bound(const Address &so_addr) const {
189+
if (so_addr.GetModule() != m_comp_unit->GetModule())
190+
return GetSize();
191+
192+
Entry search_entry;
193+
search_entry.file_addr = so_addr.GetFileAddress();
194+
if (search_entry.file_addr == LLDB_INVALID_ADDRESS)
195+
return GetSize();
196+
197+
// This is not a typo. upper_bound returns the first entry which definitely
198+
// does not contain this address, which means the entry before it *might*
199+
// contain it -- if it is not a termination entry.
200+
auto pos =
201+
llvm::upper_bound(m_entries, search_entry, Entry::EntryAddressLessThan);
202+
203+
if (pos != m_entries.begin() && !std::prev(pos)->is_terminal_entry)
204+
--pos;
205+
206+
return std::distance(m_entries.begin(), pos);
207+
}
208+
209+
uint32_t LineTable::upper_bound(const Address &so_addr) const {
210+
if (so_addr.GetModule() != m_comp_unit->GetModule())
211+
return GetSize();
212+
213+
Entry search_entry;
214+
search_entry.file_addr = so_addr.GetFileAddress();
215+
if (search_entry.file_addr == LLDB_INVALID_ADDRESS)
216+
return GetSize();
217+
218+
// This is not a typo. lower_bound returns the first entry which starts on or
219+
// after the given address, which is exactly what we want -- *except* if the
220+
// entry is a termination entry (in that case, we want the one after it).
221+
auto pos =
222+
llvm::lower_bound(m_entries, search_entry, Entry::EntryAddressLessThan);
223+
if (pos != m_entries.end() && pos->file_addr == search_entry.file_addr &&
224+
pos->is_terminal_entry)
225+
++pos;
226+
227+
return std::distance(m_entries.begin(), pos);
228+
}
229+
188230
bool LineTable::FindLineEntryByAddress(const Address &so_addr,
189231
LineEntry &line_entry,
190232
uint32_t *index_ptr) {
@@ -199,7 +241,7 @@ bool LineTable::FindLineEntryByAddress(const Address &so_addr,
199241
if (search_entry.file_addr != LLDB_INVALID_ADDRESS) {
200242
entry_collection::const_iterator begin_pos = m_entries.begin();
201243
entry_collection::const_iterator end_pos = m_entries.end();
202-
entry_collection::const_iterator pos = lower_bound(
244+
entry_collection::const_iterator pos = std::lower_bound(
203245
begin_pos, end_pos, search_entry, Entry::EntryAddressLessThan);
204246
if (pos != end_pos) {
205247
if (pos != begin_pos) {

lldb/unittests/Symbol/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
add_lldb_unittest(SymbolTests
22
JSONSymbolTest.cpp
3+
LineTableTest.cpp
34
LocateSymbolFileTest.cpp
45
MangledTest.cpp
56
PostfixExpressionTest.cpp
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
//===-- LineTableTest.cpp -------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "Plugins/ObjectFile/ELF/ObjectFileELF.h"
10+
#include "TestingSupport/SubsystemRAII.h"
11+
#include "TestingSupport/TestUtilities.h"
12+
#include "lldb/Core/PluginManager.h"
13+
#include "lldb/Symbol/CompileUnit.h"
14+
#include "lldb/Symbol/SymbolFile.h"
15+
#include "gtest/gtest.h"
16+
#include <memory>
17+
18+
using namespace lldb;
19+
using namespace llvm;
20+
using namespace lldb_private;
21+
22+
namespace {
23+
24+
// A fake symbol file class to allow us to create the line table "the right
25+
// way". Pretty much all methods except for GetCompileUnitAtIndex and
26+
// GetNumCompileUnits are stubbed out.
27+
class FakeSymbolFile : public SymbolFile {
28+
public:
29+
/// LLVM RTTI support.
30+
/// \{
31+
bool isA(const void *ClassID) const override {
32+
return ClassID == &ID || SymbolFile::isA(ClassID);
33+
}
34+
static bool classof(const SymbolFile *obj) { return obj->isA(&ID); }
35+
/// \}
36+
37+
static void Initialize() {
38+
PluginManager::RegisterPlugin("FakeSymbolFile", "", CreateInstance,
39+
DebuggerInitialize);
40+
}
41+
static void Terminate() { PluginManager::UnregisterPlugin(CreateInstance); }
42+
43+
void InjectCompileUnit(std::unique_ptr<CompileUnit> cu_up) {
44+
m_cu_sp = std::move(cu_up);
45+
}
46+
47+
private:
48+
/// LLVM RTTI support.
49+
static char ID;
50+
51+
static SymbolFile *CreateInstance(ObjectFileSP objfile_sp) {
52+
return new FakeSymbolFile(std::move(objfile_sp));
53+
}
54+
static void DebuggerInitialize(Debugger &) {}
55+
56+
StringRef GetPluginName() override { return "FakeSymbolFile"; }
57+
uint32_t GetAbilities() override { return UINT32_MAX; }
58+
uint32_t CalculateAbilities() override { return UINT32_MAX; }
59+
uint32_t GetNumCompileUnits() override { return 1; }
60+
CompUnitSP GetCompileUnitAtIndex(uint32_t) override { return m_cu_sp; }
61+
Symtab *GetSymtab() override { return nullptr; }
62+
LanguageType ParseLanguage(CompileUnit &) override { return eLanguageTypeC; }
63+
size_t ParseFunctions(CompileUnit &) override { return 0; }
64+
bool ParseLineTable(CompileUnit &) override { return true; }
65+
bool ParseDebugMacros(CompileUnit &) override { return true; }
66+
bool ParseSupportFiles(CompileUnit &, SupportFileList &) override {
67+
return true;
68+
}
69+
size_t ParseTypes(CompileUnit &) override { return 0; }
70+
bool ParseImportedModules(const SymbolContext &,
71+
std::vector<SourceModule> &) override {
72+
return false;
73+
}
74+
size_t ParseBlocksRecursive(Function &) override { return 0; }
75+
size_t ParseVariablesForContext(const SymbolContext &) override { return 0; }
76+
Type *ResolveTypeUID(user_id_t) override { return nullptr; }
77+
std::optional<ArrayInfo>
78+
GetDynamicArrayInfoForUID(user_id_t, const ExecutionContext *) override {
79+
return std::nullopt;
80+
}
81+
bool CompleteType(CompilerType &) override { return true; }
82+
uint32_t ResolveSymbolContext(const Address &, SymbolContextItem,
83+
SymbolContext &) override {
84+
return 0;
85+
}
86+
void GetTypes(SymbolContextScope *, TypeClass, TypeList &) override {}
87+
Expected<TypeSystemSP> GetTypeSystemForLanguage(LanguageType) override {
88+
return createStringError(std::errc::not_supported, "");
89+
}
90+
const ObjectFile *GetObjectFile() const override {
91+
return m_objfile_sp.get();
92+
}
93+
ObjectFile *GetObjectFile() override { return m_objfile_sp.get(); }
94+
ObjectFile *GetMainObjectFile() override { return m_objfile_sp.get(); }
95+
void SectionFileAddressesChanged() override {}
96+
void Dump(Stream &) override {}
97+
uint64_t GetDebugInfoSize(bool) override { return 0; }
98+
bool GetDebugInfoIndexWasLoadedFromCache() const override { return false; }
99+
void SetDebugInfoIndexWasLoadedFromCache() override {}
100+
bool GetDebugInfoIndexWasSavedToCache() const override { return false; }
101+
void SetDebugInfoIndexWasSavedToCache() override {}
102+
bool GetDebugInfoHadFrameVariableErrors() const override { return false; }
103+
void SetDebugInfoHadFrameVariableErrors() override {}
104+
TypeSP MakeType(user_id_t, ConstString, std::optional<uint64_t>,
105+
SymbolContextScope *, user_id_t, Type::EncodingDataType,
106+
const Declaration &, const CompilerType &, Type::ResolveState,
107+
uint32_t) override {
108+
return nullptr;
109+
}
110+
TypeSP CopyType(const TypeSP &) override { return nullptr; }
111+
112+
FakeSymbolFile(ObjectFileSP objfile_sp)
113+
: m_objfile_sp(std::move(objfile_sp)) {}
114+
115+
ObjectFileSP m_objfile_sp;
116+
CompUnitSP m_cu_sp;
117+
};
118+
119+
struct FakeModuleFixture {
120+
TestFile file;
121+
ModuleSP module_sp;
122+
SectionSP text_sp;
123+
LineTable *line_table;
124+
};
125+
126+
class LineTableTest : public testing::Test {
127+
SubsystemRAII<ObjectFileELF, FakeSymbolFile> subsystems;
128+
};
129+
130+
class LineSequenceBuilder {
131+
public:
132+
std::vector<std::unique_ptr<LineSequence>> Build() {
133+
return std::move(m_sequences);
134+
}
135+
enum Terminal : bool { Terminal = true };
136+
void Entry(addr_t addr, bool terminal = false) {
137+
LineTable::AppendLineEntryToSequence(
138+
m_seq_up.get(), addr, /*line=*/1, /*column=*/0,
139+
/*file_idx=*/0,
140+
/*is_start_of_statement=*/false, /*is_start_of_basic_block=*/false,
141+
/*is_prologue_end=*/false, /*is_epilogue_begin=*/false, terminal);
142+
if (terminal) {
143+
m_sequences.push_back(std::move(m_seq_up));
144+
m_seq_up = LineTable::CreateLineSequenceContainer();
145+
}
146+
}
147+
148+
private:
149+
std::vector<std::unique_ptr<LineSequence>> m_sequences;
150+
std::unique_ptr<LineSequence> m_seq_up =
151+
LineTable::CreateLineSequenceContainer();
152+
};
153+
154+
} // namespace
155+
156+
char FakeSymbolFile::ID;
157+
158+
static llvm::Expected<FakeModuleFixture>
159+
CreateFakeModule(std::vector<std::unique_ptr<LineSequence>> line_sequences) {
160+
Expected<TestFile> file = TestFile::fromYaml(R"(
161+
--- !ELF
162+
FileHeader:
163+
Class: ELFCLASS64
164+
Data: ELFDATA2LSB
165+
Type: ET_EXEC
166+
Machine: EM_386
167+
Sections:
168+
- Name: .text
169+
Type: SHT_PROGBITS
170+
Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
171+
AddressAlign: 0x0010
172+
Address: 0x0000
173+
Size: 0x1000
174+
)");
175+
if (!file)
176+
return file.takeError();
177+
178+
auto module_sp = std::make_shared<Module>(file->moduleSpec());
179+
SectionSP text_sp =
180+
module_sp->GetSectionList()->FindSectionByName(ConstString(".text"));
181+
if (!text_sp)
182+
return createStringError("No .text");
183+
184+
auto cu_up = std::make_unique<CompileUnit>(module_sp, /*user_data=*/nullptr,
185+
/*support_file_sp=*/nullptr,
186+
/*uid=*/0, eLanguageTypeC,
187+
/*is_optimized=*/eLazyBoolNo);
188+
LineTable *line_table = new LineTable(cu_up.get(), std::move(line_sequences));
189+
cu_up->SetLineTable(line_table);
190+
cast<FakeSymbolFile>(module_sp->GetSymbolFile())
191+
->InjectCompileUnit(std::move(cu_up));
192+
193+
return FakeModuleFixture{std::move(*file), std::move(module_sp),
194+
std::move(text_sp), line_table};
195+
}
196+
197+
TEST_F(LineTableTest, LowerAndUpperBound) {
198+
LineSequenceBuilder builder;
199+
builder.Entry(0);
200+
builder.Entry(10);
201+
builder.Entry(20, LineSequenceBuilder::Terminal);
202+
builder.Entry(20); // Starts right after the previous sequence.
203+
builder.Entry(30, LineSequenceBuilder::Terminal);
204+
builder.Entry(40); // Gap after the previous sequence.
205+
builder.Entry(50, LineSequenceBuilder::Terminal);
206+
207+
llvm::Expected<FakeModuleFixture> fixture = CreateFakeModule(builder.Build());
208+
ASSERT_THAT_EXPECTED(fixture, llvm::Succeeded());
209+
210+
LineTable *table = fixture->line_table;
211+
212+
auto make_addr = [&](addr_t addr) { return Address(fixture->text_sp, addr); };
213+
214+
// Both functions return the same value for boundary values. This way the
215+
// index range for e.g. [0,10) is [0,1).
216+
EXPECT_EQ(table->lower_bound(make_addr(0)), 0u);
217+
EXPECT_EQ(table->upper_bound(make_addr(0)), 0u);
218+
EXPECT_EQ(table->lower_bound(make_addr(10)), 1u);
219+
EXPECT_EQ(table->upper_bound(make_addr(10)), 1u);
220+
EXPECT_EQ(table->lower_bound(make_addr(20)), 3u);
221+
EXPECT_EQ(table->upper_bound(make_addr(20)), 3u);
222+
223+
// In case there's no "real" entry at this address, they return the first real
224+
// entry.
225+
EXPECT_EQ(table->lower_bound(make_addr(30)), 5u);
226+
EXPECT_EQ(table->upper_bound(make_addr(30)), 5u);
227+
228+
EXPECT_EQ(table->lower_bound(make_addr(40)), 5u);
229+
EXPECT_EQ(table->upper_bound(make_addr(40)), 5u);
230+
231+
// For in-between values, their result differs by one. [9,19) maps to [0,2)
232+
// because the first two entries contain a part of that range.
233+
EXPECT_EQ(table->lower_bound(make_addr(9)), 0u);
234+
EXPECT_EQ(table->upper_bound(make_addr(9)), 1u);
235+
EXPECT_EQ(table->lower_bound(make_addr(19)), 1u);
236+
EXPECT_EQ(table->upper_bound(make_addr(19)), 2u);
237+
EXPECT_EQ(table->lower_bound(make_addr(29)), 3u);
238+
EXPECT_EQ(table->upper_bound(make_addr(29)), 4u);
239+
240+
// In a gap, they both return the first entry after the gap.
241+
EXPECT_EQ(table->upper_bound(make_addr(39)), 5u);
242+
EXPECT_EQ(table->upper_bound(make_addr(39)), 5u);
243+
244+
// And if there's no such entry, they return the size of the list.
245+
EXPECT_EQ(table->lower_bound(make_addr(50)), table->GetSize());
246+
EXPECT_EQ(table->upper_bound(make_addr(50)), table->GetSize());
247+
EXPECT_EQ(table->lower_bound(make_addr(59)), table->GetSize());
248+
EXPECT_EQ(table->upper_bound(make_addr(59)), table->GetSize());
249+
}
250+
251+
TEST_F(LineTableTest, FindLineEntryByAddress) {
252+
LineSequenceBuilder builder;
253+
builder.Entry(0);
254+
builder.Entry(10);
255+
builder.Entry(20, LineSequenceBuilder::Terminal);
256+
builder.Entry(20); // Starts right after the previous sequence.
257+
builder.Entry(30, LineSequenceBuilder::Terminal);
258+
builder.Entry(40); // Gap after the previous sequence.
259+
builder.Entry(50, LineSequenceBuilder::Terminal);
260+
261+
llvm::Expected<FakeModuleFixture> fixture = CreateFakeModule(builder.Build());
262+
ASSERT_THAT_EXPECTED(fixture, llvm::Succeeded());
263+
264+
LineTable *table = fixture->line_table;
265+
266+
auto find = [&](addr_t addr) -> std::tuple<addr_t, addr_t, bool> {
267+
LineEntry entry;
268+
if (!table->FindLineEntryByAddress(Address(fixture->text_sp, addr), entry))
269+
return {LLDB_INVALID_ADDRESS, LLDB_INVALID_ADDRESS, false};
270+
return {entry.range.GetBaseAddress().GetFileAddress(),
271+
entry.range.GetByteSize(),
272+
static_cast<bool>(entry.is_terminal_entry)};
273+
};
274+
275+
EXPECT_THAT(find(0), testing::FieldsAre(0, 10, false));
276+
EXPECT_THAT(find(9), testing::FieldsAre(0, 10, false));
277+
EXPECT_THAT(find(10), testing::FieldsAre(10, 10, false));
278+
EXPECT_THAT(find(19), testing::FieldsAre(10, 10, false));
279+
EXPECT_THAT(find(20), testing::FieldsAre(20, 10, false));
280+
EXPECT_THAT(find(30), testing::FieldsAre(LLDB_INVALID_ADDRESS,
281+
LLDB_INVALID_ADDRESS, false));
282+
EXPECT_THAT(find(40), testing::FieldsAre(40, 10, false));
283+
EXPECT_THAT(find(50), testing::FieldsAre(LLDB_INVALID_ADDRESS,
284+
LLDB_INVALID_ADDRESS, false));
285+
}

0 commit comments

Comments
 (0)