1 | //===-- CompileUnit.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 "lldb/Symbol/CompileUnit.h" |
10 | #include "lldb/Core/Module.h" |
11 | #include "lldb/Symbol/LineTable.h" |
12 | #include "lldb/Symbol/SymbolFile.h" |
13 | #include "lldb/Symbol/VariableList.h" |
14 | #include "lldb/Target/Language.h" |
15 | #include "lldb/Utility/Timer.h" |
16 | #include <optional> |
17 | |
18 | using namespace lldb; |
19 | using namespace lldb_private; |
20 | |
21 | CompileUnit::CompileUnit(const lldb::ModuleSP &module_sp, void *user_data, |
22 | const char *pathname, const lldb::user_id_t cu_sym_id, |
23 | lldb::LanguageType language, |
24 | lldb_private::LazyBool is_optimized) |
25 | : CompileUnit(module_sp, user_data, |
26 | std::make_shared<SupportFile>(args: FileSpec(pathname)), cu_sym_id, |
27 | language, is_optimized) {} |
28 | |
29 | CompileUnit::CompileUnit(const lldb::ModuleSP &module_sp, void *user_data, |
30 | lldb::SupportFileSP support_file_sp, |
31 | const lldb::user_id_t cu_sym_id, |
32 | lldb::LanguageType language, |
33 | lldb_private::LazyBool is_optimized, |
34 | SupportFileList &&support_files) |
35 | : ModuleChild(module_sp), UserID(cu_sym_id), m_user_data(user_data), |
36 | m_language(language), m_flags(0), |
37 | m_primary_support_file_sp(support_file_sp), |
38 | m_support_files(std::move(support_files)), m_is_optimized(is_optimized) { |
39 | if (language != eLanguageTypeUnknown) |
40 | m_flags.Set(flagsParsedLanguage); |
41 | assert(module_sp); |
42 | } |
43 | |
44 | void CompileUnit::CalculateSymbolContext(SymbolContext *sc) { |
45 | sc->comp_unit = this; |
46 | GetModule()->CalculateSymbolContext(sc); |
47 | } |
48 | |
49 | ModuleSP CompileUnit::CalculateSymbolContextModule() { return GetModule(); } |
50 | |
51 | CompileUnit *CompileUnit::CalculateSymbolContextCompileUnit() { return this; } |
52 | |
53 | void CompileUnit::DumpSymbolContext(Stream *s) { |
54 | GetModule()->DumpSymbolContext(s); |
55 | s->Printf(format: ", CompileUnit{0x%8.8" PRIx64 "}" , GetID()); |
56 | } |
57 | |
58 | void CompileUnit::GetDescription(Stream *s, |
59 | lldb::DescriptionLevel level) const { |
60 | const char *language = GetCachedLanguage(); |
61 | *s << "id = " << (const UserID &)*this << ", file = \"" |
62 | << this->GetPrimaryFile() << "\", language = \"" << language << '"'; |
63 | } |
64 | |
65 | void CompileUnit::ForeachFunction( |
66 | llvm::function_ref<bool(const FunctionSP &)> lambda) const { |
67 | std::vector<lldb::FunctionSP> sorted_functions; |
68 | sorted_functions.reserve(n: m_functions_by_uid.size()); |
69 | for (auto &p : m_functions_by_uid) |
70 | sorted_functions.push_back(x: p.second); |
71 | llvm::sort(C&: sorted_functions, |
72 | Comp: [](const lldb::FunctionSP &a, const lldb::FunctionSP &b) { |
73 | return a->GetID() < b->GetID(); |
74 | }); |
75 | |
76 | for (auto &f : sorted_functions) |
77 | if (lambda(f)) |
78 | return; |
79 | } |
80 | |
81 | lldb::FunctionSP CompileUnit::FindFunction( |
82 | llvm::function_ref<bool(const FunctionSP &)> matching_lambda) { |
83 | LLDB_SCOPED_TIMER(); |
84 | |
85 | lldb::ModuleSP module = CalculateSymbolContextModule(); |
86 | |
87 | if (!module) |
88 | return {}; |
89 | |
90 | SymbolFile *symbol_file = module->GetSymbolFile(); |
91 | |
92 | if (!symbol_file) |
93 | return {}; |
94 | |
95 | // m_functions_by_uid is filled in lazily but we need all the entries. |
96 | symbol_file->ParseFunctions(comp_unit&: *this); |
97 | |
98 | for (auto &p : m_functions_by_uid) { |
99 | if (matching_lambda(p.second)) |
100 | return p.second; |
101 | } |
102 | return {}; |
103 | } |
104 | |
105 | const char *CompileUnit::GetCachedLanguage() const { |
106 | if (m_flags.IsClear(bit: flagsParsedLanguage)) |
107 | return "<not loaded>" ; |
108 | return Language::GetNameForLanguageType(language: m_language); |
109 | } |
110 | |
111 | // Dump the current contents of this object. No functions that cause on demand |
112 | // parsing of functions, globals, statics are called, so this is a good |
113 | // function to call to get an idea of the current contents of the CompileUnit |
114 | // object. |
115 | void CompileUnit::Dump(Stream *s, bool show_context) const { |
116 | const char *language = GetCachedLanguage(); |
117 | |
118 | s->Printf(format: "%p: " , static_cast<const void *>(this)); |
119 | s->Indent(); |
120 | *s << "CompileUnit" << static_cast<const UserID &>(*this) << ", language = \"" |
121 | << language << "\", file = '" << GetPrimaryFile() << "'\n" ; |
122 | |
123 | // m_types.Dump(s); |
124 | |
125 | if (m_variables.get()) { |
126 | s->IndentMore(); |
127 | m_variables->Dump(s, show_context); |
128 | s->IndentLess(); |
129 | } |
130 | |
131 | if (!m_functions_by_uid.empty()) { |
132 | s->IndentMore(); |
133 | ForeachFunction(lambda: [&s, show_context](const FunctionSP &f) { |
134 | f->Dump(s, show_context); |
135 | return false; |
136 | }); |
137 | |
138 | s->IndentLess(); |
139 | s->EOL(); |
140 | } |
141 | } |
142 | |
143 | // Add a function to this compile unit |
144 | void CompileUnit::AddFunction(FunctionSP &funcSP) { |
145 | m_functions_by_uid[funcSP->GetID()] = funcSP; |
146 | } |
147 | |
148 | FunctionSP CompileUnit::FindFunctionByUID(lldb::user_id_t func_uid) { |
149 | auto it = m_functions_by_uid.find(Val: func_uid); |
150 | if (it == m_functions_by_uid.end()) |
151 | return FunctionSP(); |
152 | return it->second; |
153 | } |
154 | |
155 | lldb::LanguageType CompileUnit::GetLanguage() { |
156 | if (m_language == eLanguageTypeUnknown) { |
157 | if (m_flags.IsClear(bit: flagsParsedLanguage)) { |
158 | m_flags.Set(flagsParsedLanguage); |
159 | if (SymbolFile *symfile = GetModule()->GetSymbolFile()) |
160 | m_language = symfile->ParseLanguage(comp_unit&: *this); |
161 | } |
162 | } |
163 | return m_language; |
164 | } |
165 | |
166 | LineTable *CompileUnit::GetLineTable() { |
167 | if (m_line_table_up == nullptr) { |
168 | if (m_flags.IsClear(bit: flagsParsedLineTable)) { |
169 | m_flags.Set(flagsParsedLineTable); |
170 | if (SymbolFile *symfile = GetModule()->GetSymbolFile()) |
171 | symfile->ParseLineTable(comp_unit&: *this); |
172 | } |
173 | } |
174 | return m_line_table_up.get(); |
175 | } |
176 | |
177 | void CompileUnit::SetLineTable(LineTable *line_table) { |
178 | if (line_table == nullptr) |
179 | m_flags.Clear(mask: flagsParsedLineTable); |
180 | else |
181 | m_flags.Set(flagsParsedLineTable); |
182 | m_line_table_up.reset(p: line_table); |
183 | } |
184 | |
185 | DebugMacros *CompileUnit::GetDebugMacros() { |
186 | if (m_debug_macros_sp.get() == nullptr) { |
187 | if (m_flags.IsClear(bit: flagsParsedDebugMacros)) { |
188 | m_flags.Set(flagsParsedDebugMacros); |
189 | if (SymbolFile *symfile = GetModule()->GetSymbolFile()) |
190 | symfile->ParseDebugMacros(comp_unit&: *this); |
191 | } |
192 | } |
193 | |
194 | return m_debug_macros_sp.get(); |
195 | } |
196 | |
197 | void CompileUnit::SetDebugMacros(const DebugMacrosSP &debug_macros_sp) { |
198 | if (debug_macros_sp.get() == nullptr) |
199 | m_flags.Clear(mask: flagsParsedDebugMacros); |
200 | else |
201 | m_flags.Set(flagsParsedDebugMacros); |
202 | m_debug_macros_sp = debug_macros_sp; |
203 | } |
204 | |
205 | VariableListSP CompileUnit::GetVariableList(bool can_create) { |
206 | if (m_variables.get() == nullptr && can_create) { |
207 | SymbolContext sc; |
208 | CalculateSymbolContext(sc: &sc); |
209 | assert(sc.module_sp); |
210 | sc.module_sp->GetSymbolFile()->ParseVariablesForContext(sc); |
211 | } |
212 | |
213 | return m_variables; |
214 | } |
215 | |
216 | std::vector<uint32_t> FindFileIndexes(const SupportFileList &files, |
217 | const FileSpec &file) { |
218 | std::vector<uint32_t> result; |
219 | uint32_t idx = -1; |
220 | while ((idx = files.FindCompatibleIndex(idx: idx + 1, file)) != |
221 | UINT32_MAX) |
222 | result.push_back(x: idx); |
223 | return result; |
224 | } |
225 | |
226 | uint32_t CompileUnit::FindLineEntry(uint32_t start_idx, uint32_t line, |
227 | const FileSpec *file_spec_ptr, bool exact, |
228 | LineEntry *line_entry_ptr) { |
229 | if (!file_spec_ptr) |
230 | file_spec_ptr = &GetPrimaryFile(); |
231 | std::vector<uint32_t> file_indexes = FindFileIndexes(files: GetSupportFiles(), |
232 | file: *file_spec_ptr); |
233 | if (file_indexes.empty()) |
234 | return UINT32_MAX; |
235 | |
236 | // TODO: Handle SourceLocationSpec column information |
237 | SourceLocationSpec location_spec(*file_spec_ptr, line, |
238 | /*column=*/std::nullopt, |
239 | /*check_inlines=*/false, exact); |
240 | |
241 | LineTable *line_table = GetLineTable(); |
242 | if (line_table) |
243 | return line_table->FindLineEntryIndexByFileIndex( |
244 | start_idx, file_idx: file_indexes, src_location_spec: location_spec, line_entry_ptr); |
245 | return UINT32_MAX; |
246 | } |
247 | |
248 | void CompileUnit::ResolveSymbolContext( |
249 | const SourceLocationSpec &src_location_spec, |
250 | SymbolContextItem resolve_scope, SymbolContextList &sc_list) { |
251 | const FileSpec file_spec = src_location_spec.GetFileSpec(); |
252 | const uint32_t line = src_location_spec.GetLine().value_or(u: 0); |
253 | const bool check_inlines = src_location_spec.GetCheckInlines(); |
254 | |
255 | // First find all of the file indexes that match our "file_spec". If |
256 | // "file_spec" has an empty directory, then only compare the basenames when |
257 | // finding file indexes |
258 | bool file_spec_matches_cu_file_spec = |
259 | FileSpec::Match(pattern: file_spec, file: this->GetPrimaryFile()); |
260 | |
261 | // If we are not looking for inlined functions and our file spec doesn't |
262 | // match then we are done... |
263 | if (!file_spec_matches_cu_file_spec && !check_inlines) |
264 | return; |
265 | |
266 | SymbolContext sc(GetModule()); |
267 | sc.comp_unit = this; |
268 | |
269 | if (line == 0) { |
270 | if (file_spec_matches_cu_file_spec && !check_inlines) { |
271 | // only append the context if we aren't looking for inline call sites by |
272 | // file and line and if the file spec matches that of the compile unit |
273 | sc_list.Append(sc); |
274 | } |
275 | return; |
276 | } |
277 | |
278 | std::vector<uint32_t> file_indexes = FindFileIndexes(files: GetSupportFiles(), |
279 | file: file_spec); |
280 | const size_t num_file_indexes = file_indexes.size(); |
281 | if (num_file_indexes == 0) |
282 | return; |
283 | |
284 | // Found a matching source file in this compile unit load its debug info. |
285 | GetModule()->GetSymbolFile()->SetLoadDebugInfoEnabled(); |
286 | |
287 | LineTable *line_table = sc.comp_unit->GetLineTable(); |
288 | |
289 | if (line_table == nullptr) { |
290 | if (file_spec_matches_cu_file_spec && !check_inlines) { |
291 | sc_list.Append(sc); |
292 | } |
293 | return; |
294 | } |
295 | |
296 | uint32_t line_idx; |
297 | LineEntry line_entry; |
298 | |
299 | if (num_file_indexes == 1) { |
300 | // We only have a single support file that matches, so use the line |
301 | // table function that searches for a line entries that match a single |
302 | // support file index |
303 | line_idx = line_table->FindLineEntryIndexByFileIndex( |
304 | start_idx: 0, file_idx: file_indexes.front(), src_location_spec, line_entry_ptr: &line_entry); |
305 | } else { |
306 | // We found multiple support files that match "file_spec" so use the |
307 | // line table function that searches for a line entries that match a |
308 | // multiple support file indexes. |
309 | line_idx = line_table->FindLineEntryIndexByFileIndex( |
310 | start_idx: 0, file_idx: file_indexes, src_location_spec, line_entry_ptr: &line_entry); |
311 | } |
312 | |
313 | // If "exact == true", then "found_line" will be the same as "line". If |
314 | // "exact == false", the "found_line" will be the closest line entry |
315 | // with a line number greater than "line" and we will use this for our |
316 | // subsequent line exact matches below. |
317 | const bool inlines = false; |
318 | const bool exact = true; |
319 | const std::optional<uint16_t> column = |
320 | src_location_spec.GetColumn() ? std::optional<uint16_t>(line_entry.column) |
321 | : std::nullopt; |
322 | |
323 | SourceLocationSpec found_entry(line_entry.GetFile(), line_entry.line, column, |
324 | inlines, exact); |
325 | |
326 | while (line_idx != UINT32_MAX) { |
327 | // If they only asked for the line entry, then we're done, we can |
328 | // just copy that over. But if they wanted more than just the line |
329 | // number, fill it in. |
330 | SymbolContext resolved_sc; |
331 | sc.line_entry = line_entry; |
332 | if (resolve_scope == eSymbolContextLineEntry) { |
333 | sc_list.Append(sc); |
334 | } else { |
335 | line_entry.range.GetBaseAddress().CalculateSymbolContext(sc: &resolved_sc, |
336 | resolve_scope); |
337 | // Sometimes debug info is bad and isn't able to resolve the line entry's |
338 | // address back to the same compile unit and/or line entry. If the compile |
339 | // unit changed, then revert back to just the compile unit and line entry. |
340 | // Prior to this fix, the above code might end up not being able to lookup |
341 | // the address, and then it would clear compile unit and the line entry in |
342 | // the symbol context and the breakpoint would fail to get set even though |
343 | // we have a valid line table entry in this compile unit. The address |
344 | // lookup can also end up finding another function in another compiler |
345 | // unit if the DWARF has overlappging address ranges. So if we end up with |
346 | // no compile unit or a different one after the above function call, |
347 | // revert back to the same results as if resolve_scope was set exactly to |
348 | // eSymbolContextLineEntry. |
349 | if (resolved_sc.comp_unit == this) { |
350 | sc_list.Append(sc: resolved_sc); |
351 | } else { |
352 | if (resolved_sc.comp_unit == nullptr && resolved_sc.module_sp) { |
353 | // Only report an error if we don't map back to any compile unit. With |
354 | // link time optimizations, the debug info might have many compile |
355 | // units that have the same address range due to function outlining |
356 | // or other link time optimizations. If the compile unit is NULL, then |
357 | // address resolving is completely failing and more deserving of an |
358 | // error message the user can see. |
359 | resolved_sc.module_sp->ReportError( |
360 | format: "unable to resolve a line table file address {0:x16} back " |
361 | "to a compile unit, please file a bug and attach the address " |
362 | "and file." , |
363 | args: line_entry.range.GetBaseAddress().GetFileAddress()); |
364 | } |
365 | sc_list.Append(sc); |
366 | } |
367 | } |
368 | |
369 | if (num_file_indexes == 1) |
370 | line_idx = line_table->FindLineEntryIndexByFileIndex( |
371 | start_idx: line_idx + 1, file_idx: file_indexes.front(), src_location_spec: found_entry, line_entry_ptr: &line_entry); |
372 | else |
373 | line_idx = line_table->FindLineEntryIndexByFileIndex( |
374 | start_idx: line_idx + 1, file_idx: file_indexes, src_location_spec: found_entry, line_entry_ptr: &line_entry); |
375 | } |
376 | } |
377 | |
378 | bool CompileUnit::GetIsOptimized() { |
379 | if (m_is_optimized == eLazyBoolCalculate) { |
380 | m_is_optimized = eLazyBoolNo; |
381 | if (SymbolFile *symfile = GetModule()->GetSymbolFile()) { |
382 | if (symfile->ParseIsOptimized(comp_unit&: *this)) |
383 | m_is_optimized = eLazyBoolYes; |
384 | } |
385 | } |
386 | return m_is_optimized; |
387 | } |
388 | |
389 | void CompileUnit::SetVariableList(VariableListSP &variables) { |
390 | m_variables = variables; |
391 | } |
392 | |
393 | const std::vector<SourceModule> &CompileUnit::GetImportedModules() { |
394 | if (m_imported_modules.empty() && |
395 | m_flags.IsClear(bit: flagsParsedImportedModules)) { |
396 | m_flags.Set(flagsParsedImportedModules); |
397 | if (SymbolFile *symfile = GetModule()->GetSymbolFile()) { |
398 | SymbolContext sc; |
399 | CalculateSymbolContext(sc: &sc); |
400 | symfile->ParseImportedModules(sc, imported_modules&: m_imported_modules); |
401 | } |
402 | } |
403 | return m_imported_modules; |
404 | } |
405 | |
406 | bool CompileUnit::ForEachExternalModule( |
407 | llvm::DenseSet<SymbolFile *> &visited_symbol_files, |
408 | llvm::function_ref<bool(Module &)> lambda) { |
409 | if (SymbolFile *symfile = GetModule()->GetSymbolFile()) |
410 | return symfile->ForEachExternalModule(comp_unit&: *this, visited_symbol_files, lambda); |
411 | return false; |
412 | } |
413 | |
414 | const SupportFileList &CompileUnit::GetSupportFiles() { |
415 | if (m_support_files.GetSize() == 0) { |
416 | if (m_flags.IsClear(bit: flagsParsedSupportFiles)) { |
417 | m_flags.Set(flagsParsedSupportFiles); |
418 | if (SymbolFile *symfile = GetModule()->GetSymbolFile()) |
419 | symfile->ParseSupportFiles(comp_unit&: *this, support_files&: m_support_files); |
420 | } |
421 | } |
422 | return m_support_files; |
423 | } |
424 | |
425 | void *CompileUnit::GetUserData() const { return m_user_data; } |
426 | |