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
18using namespace lldb;
19using namespace llvm;
20using namespace lldb_private;
21
22namespace {
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.
27class FakeSymbolFile : public SymbolFile {
28public:
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(ClassID: &ID); }
35 /// \}
36
37 static void Initialize() {
38 PluginManager::RegisterPlugin(name: "FakeSymbolFile", description: "", create_callback: CreateInstance,
39 debugger_init_callback: DebuggerInitialize);
40 }
41 static void Terminate() { PluginManager::UnregisterPlugin(create_callback: CreateInstance); }
42
43 void InjectCompileUnit(std::unique_ptr<CompileUnit> cu_up) {
44 m_cu_sp = std::move(cu_up);
45 }
46
47private:
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(bool can_create = true) 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(EC: std::errc::not_supported, Fmt: "");
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
119struct FakeModuleFixture {
120 TestFile file;
121 ModuleSP module_sp;
122 SectionSP text_sp;
123 LineTable *line_table;
124};
125
126class LineTableTest : public testing::Test {
127 SubsystemRAII<ObjectFileELF, FakeSymbolFile> subsystems;
128};
129
130class LineSequenceBuilder {
131public:
132 std::vector<LineTable::Sequence> Build() { return std::move(m_sequences); }
133 enum Terminal : bool { Terminal = true };
134 void Entry(addr_t addr, bool terminal = false) {
135 LineTable::AppendLineEntryToSequence(
136 sequence&: m_sequence, file_addr: addr, /*line=*/1, /*column=*/0,
137 /*file_idx=*/0,
138 /*is_start_of_statement=*/false, /*is_start_of_basic_block=*/false,
139 /*is_prologue_end=*/false, /*is_epilogue_begin=*/false, is_terminal_entry: terminal);
140 if (terminal)
141 m_sequences.push_back(x: std::move(m_sequence));
142 }
143
144private:
145 std::vector<LineTable::Sequence> m_sequences;
146 LineTable::Sequence m_sequence;
147};
148
149} // namespace
150
151char FakeSymbolFile::ID;
152
153static llvm::Expected<FakeModuleFixture>
154CreateFakeModule(std::vector<LineTable::Sequence> line_sequences) {
155 Expected<TestFile> file = TestFile::fromYaml(Yaml: R"(
156--- !ELF
157FileHeader:
158 Class: ELFCLASS64
159 Data: ELFDATA2LSB
160 Type: ET_EXEC
161 Machine: EM_386
162Sections:
163 - Name: .text
164 Type: SHT_PROGBITS
165 Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
166 AddressAlign: 0x0010
167 Address: 0x0000
168 Size: 0x1000
169)");
170 if (!file)
171 return file.takeError();
172
173 auto module_sp = std::make_shared<Module>(args: file->moduleSpec());
174 SectionSP text_sp =
175 module_sp->GetSectionList()->FindSectionByName(section_dstr: ConstString(".text"));
176 if (!text_sp)
177 return createStringError(Fmt: "No .text");
178
179 auto cu_up = std::make_unique<CompileUnit>(args&: module_sp, /*user_data=*/args: nullptr,
180 /*support_file_sp=*/args: nullptr,
181 /*uid=*/args: 0, args: eLanguageTypeC,
182 /*is_optimized=*/args: eLazyBoolNo);
183 LineTable *line_table = new LineTable(cu_up.get(), std::move(line_sequences));
184 cu_up->SetLineTable(line_table);
185 cast<FakeSymbolFile>(Val: module_sp->GetSymbolFile())
186 ->InjectCompileUnit(cu_up: std::move(cu_up));
187
188 return FakeModuleFixture{.file: std::move(*file), .module_sp: std::move(module_sp),
189 .text_sp: std::move(text_sp), .line_table: line_table};
190}
191
192TEST_F(LineTableTest, lower_bound) {
193 LineSequenceBuilder builder;
194 builder.Entry(addr: 0);
195 builder.Entry(addr: 10);
196 builder.Entry(addr: 20, terminal: LineSequenceBuilder::Terminal);
197 builder.Entry(addr: 20); // Starts right after the previous sequence.
198 builder.Entry(addr: 30, terminal: LineSequenceBuilder::Terminal);
199 builder.Entry(addr: 40); // Gap after the previous sequence.
200 builder.Entry(addr: 50, terminal: LineSequenceBuilder::Terminal);
201
202 llvm::Expected<FakeModuleFixture> fixture = CreateFakeModule(line_sequences: builder.Build());
203 ASSERT_THAT_EXPECTED(fixture, llvm::Succeeded());
204
205 LineTable *table = fixture->line_table;
206
207 auto make_addr = [&](addr_t addr) { return Address(fixture->text_sp, addr); };
208
209 EXPECT_EQ(table->lower_bound(make_addr(0)), 0u);
210 EXPECT_EQ(table->lower_bound(make_addr(9)), 0u);
211 EXPECT_EQ(table->lower_bound(make_addr(10)), 1u);
212 EXPECT_EQ(table->lower_bound(make_addr(19)), 1u);
213
214 // Skips over the terminal entry.
215 EXPECT_EQ(table->lower_bound(make_addr(20)), 3u);
216 EXPECT_EQ(table->lower_bound(make_addr(29)), 3u);
217
218 // In case there's no "real" entry at this address, the function returns the
219 // first real entry.
220 EXPECT_EQ(table->lower_bound(make_addr(30)), 5u);
221 EXPECT_EQ(table->lower_bound(make_addr(40)), 5u);
222
223 // In a gap, return the first entry after the gap.
224 EXPECT_EQ(table->lower_bound(make_addr(39)), 5u);
225
226 // And if there's no such entry, return the size of the list.
227 EXPECT_EQ(table->lower_bound(make_addr(50)), table->GetSize());
228 EXPECT_EQ(table->lower_bound(make_addr(59)), table->GetSize());
229}
230
231TEST_F(LineTableTest, GetLineEntryIndexRange) {
232 LineSequenceBuilder builder;
233 builder.Entry(addr: 0);
234 builder.Entry(addr: 10);
235 builder.Entry(addr: 20, terminal: LineSequenceBuilder::Terminal);
236
237 llvm::Expected<FakeModuleFixture> fixture = CreateFakeModule(line_sequences: builder.Build());
238 ASSERT_THAT_EXPECTED(fixture, llvm::Succeeded());
239
240 LineTable *table = fixture->line_table;
241
242 auto make_range = [&](addr_t addr, addr_t size) {
243 return AddressRange(fixture->text_sp, addr, size);
244 };
245
246 EXPECT_THAT(table->GetLineEntryIndexRange(make_range(0, 10)),
247 testing::Pair(0, 1));
248 EXPECT_THAT(table->GetLineEntryIndexRange(make_range(0, 20)),
249 testing::Pair(0, 3)); // Includes the terminal entry.
250 // Partial overlap on one side.
251 EXPECT_THAT(table->GetLineEntryIndexRange(make_range(3, 7)),
252 testing::Pair(0, 1));
253 // On the other side
254 EXPECT_THAT(table->GetLineEntryIndexRange(make_range(0, 15)),
255 testing::Pair(0, 2));
256 // On both sides
257 EXPECT_THAT(table->GetLineEntryIndexRange(make_range(2, 3)),
258 testing::Pair(0, 1));
259 // Empty ranges
260 EXPECT_THAT(table->GetLineEntryIndexRange(make_range(0, 0)),
261 testing::Pair(0, 0));
262 EXPECT_THAT(table->GetLineEntryIndexRange(make_range(5, 0)),
263 testing::Pair(0, 0));
264 EXPECT_THAT(table->GetLineEntryIndexRange(make_range(10, 0)),
265 testing::Pair(1, 1));
266}
267
268TEST_F(LineTableTest, FindLineEntryByAddress) {
269 LineSequenceBuilder builder;
270 builder.Entry(addr: 0);
271 builder.Entry(addr: 10);
272 builder.Entry(addr: 20, terminal: LineSequenceBuilder::Terminal);
273 builder.Entry(addr: 20); // Starts right after the previous sequence.
274 builder.Entry(addr: 30, terminal: LineSequenceBuilder::Terminal);
275 builder.Entry(addr: 40); // Gap after the previous sequence.
276 builder.Entry(addr: 50, terminal: LineSequenceBuilder::Terminal);
277
278 llvm::Expected<FakeModuleFixture> fixture = CreateFakeModule(line_sequences: builder.Build());
279 ASSERT_THAT_EXPECTED(fixture, llvm::Succeeded());
280
281 LineTable *table = fixture->line_table;
282
283 auto find = [&](addr_t addr) -> std::tuple<addr_t, addr_t, bool> {
284 LineEntry entry;
285 if (!table->FindLineEntryByAddress(so_addr: Address(fixture->text_sp, addr), line_entry&: entry))
286 return {LLDB_INVALID_ADDRESS, LLDB_INVALID_ADDRESS, false};
287 return {entry.range.GetBaseAddress().GetFileAddress(),
288 entry.range.GetByteSize(),
289 static_cast<bool>(entry.is_terminal_entry)};
290 };
291
292 EXPECT_THAT(find(0), testing::FieldsAre(0, 10, false));
293 EXPECT_THAT(find(9), testing::FieldsAre(0, 10, false));
294 EXPECT_THAT(find(10), testing::FieldsAre(10, 10, false));
295 EXPECT_THAT(find(19), testing::FieldsAre(10, 10, false));
296 EXPECT_THAT(find(20), testing::FieldsAre(20, 10, false));
297 EXPECT_THAT(find(30), testing::FieldsAre(LLDB_INVALID_ADDRESS,
298 LLDB_INVALID_ADDRESS, false));
299 EXPECT_THAT(find(40), testing::FieldsAre(40, 10, false));
300 EXPECT_THAT(find(50), testing::FieldsAre(LLDB_INVALID_ADDRESS,
301 LLDB_INVALID_ADDRESS, false));
302}
303

source code of lldb/unittests/Symbol/LineTableTest.cpp