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(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 | |
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(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 | |
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<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 | |
144 | private: |
145 | std::vector<LineTable::Sequence> m_sequences; |
146 | LineTable::Sequence m_sequence; |
147 | }; |
148 | |
149 | } // namespace |
150 | |
151 | char FakeSymbolFile::ID; |
152 | |
153 | static llvm::Expected<FakeModuleFixture> |
154 | CreateFakeModule(std::vector<LineTable::Sequence> line_sequences) { |
155 | Expected<TestFile> file = TestFile::fromYaml(Yaml: R"( |
156 | --- !ELF |
157 | FileHeader: |
158 | Class: ELFCLASS64 |
159 | Data: ELFDATA2LSB |
160 | Type: ET_EXEC |
161 | Machine: EM_386 |
162 | Sections: |
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 | |
192 | TEST_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 | |
231 | TEST_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 | |
268 | TEST_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 | |