1 //===-- SymbolFileBreakpad.cpp ----------------------------------*- C++ -*-===//
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/SymbolFile/Breakpad/SymbolFileBreakpad.h"
10 #include "Plugins/ObjectFile/Breakpad/BreakpadRecords.h"
11 #include "Plugins/ObjectFile/Breakpad/ObjectFileBreakpad.h"
12 #include "lldb/Core/Module.h"
13 #include "lldb/Core/PluginManager.h"
14 #include "lldb/Core/Section.h"
15 #include "lldb/Host/FileSystem.h"
16 #include "lldb/Symbol/CompileUnit.h"
17 #include "lldb/Symbol/ObjectFile.h"
18 #include "lldb/Symbol/SymbolVendor.h"
19 #include "lldb/Symbol/TypeMap.h"
20 #include "lldb/Utility/Log.h"
21 #include "llvm/ADT/StringExtras.h"
22 
23 using namespace lldb;
24 using namespace lldb_private;
25 using namespace lldb_private::breakpad;
26 
27 class SymbolFileBreakpad::LineIterator {
28 public:
29   // begin iterator for sections of given type
30   LineIterator(ObjectFile &obj, Record::Kind section_type)
31       : m_obj(&obj), m_section_type(toString(section_type)),
32         m_next_section_idx(0), m_next_line(llvm::StringRef::npos) {
33     ++*this;
34   }
35 
36   // An iterator starting at the position given by the bookmark.
37   LineIterator(ObjectFile &obj, Record::Kind section_type, Bookmark bookmark);
38 
39   // end iterator
40   explicit LineIterator(ObjectFile &obj)
41       : m_obj(&obj),
42         m_next_section_idx(m_obj->GetSectionList()->GetNumSections(0)),
43         m_current_line(llvm::StringRef::npos),
44         m_next_line(llvm::StringRef::npos) {}
45 
46   friend bool operator!=(const LineIterator &lhs, const LineIterator &rhs) {
47     assert(lhs.m_obj == rhs.m_obj);
48     if (lhs.m_next_section_idx != rhs.m_next_section_idx)
49       return true;
50     if (lhs.m_current_line != rhs.m_current_line)
51       return true;
52     assert(lhs.m_next_line == rhs.m_next_line);
53     return false;
54   }
55 
56   const LineIterator &operator++();
57   llvm::StringRef operator*() const {
58     return m_section_text.slice(m_current_line, m_next_line);
59   }
60 
61   Bookmark GetBookmark() const {
62     return Bookmark{m_next_section_idx, m_current_line};
63   }
64 
65 private:
66   ObjectFile *m_obj;
67   ConstString m_section_type;
68   uint32_t m_next_section_idx;
69   llvm::StringRef m_section_text;
70   size_t m_current_line;
71   size_t m_next_line;
72 
73   void FindNextLine() {
74     m_next_line = m_section_text.find('\n', m_current_line);
75     if (m_next_line != llvm::StringRef::npos) {
76       ++m_next_line;
77       if (m_next_line >= m_section_text.size())
78         m_next_line = llvm::StringRef::npos;
79     }
80   }
81 };
82 
83 SymbolFileBreakpad::LineIterator::LineIterator(ObjectFile &obj,
84                                                Record::Kind section_type,
85                                                Bookmark bookmark)
86     : m_obj(&obj), m_section_type(toString(section_type)),
87       m_next_section_idx(bookmark.section), m_current_line(bookmark.offset) {
88   Section &sect =
89       *obj.GetSectionList()->GetSectionAtIndex(m_next_section_idx - 1);
90   assert(sect.GetName() == m_section_type);
91 
92   DataExtractor data;
93   obj.ReadSectionData(&sect, data);
94   m_section_text = toStringRef(data.GetData());
95 
96   assert(m_current_line < m_section_text.size());
97   FindNextLine();
98 }
99 
100 const SymbolFileBreakpad::LineIterator &
101 SymbolFileBreakpad::LineIterator::operator++() {
102   const SectionList &list = *m_obj->GetSectionList();
103   size_t num_sections = list.GetNumSections(0);
104   while (m_next_line != llvm::StringRef::npos ||
105          m_next_section_idx < num_sections) {
106     if (m_next_line != llvm::StringRef::npos) {
107       m_current_line = m_next_line;
108       FindNextLine();
109       return *this;
110     }
111 
112     Section &sect = *list.GetSectionAtIndex(m_next_section_idx++);
113     if (sect.GetName() != m_section_type)
114       continue;
115     DataExtractor data;
116     m_obj->ReadSectionData(&sect, data);
117     m_section_text = toStringRef(data.GetData());
118     m_next_line = 0;
119   }
120   // We've reached the end.
121   m_current_line = m_next_line;
122   return *this;
123 }
124 
125 llvm::iterator_range<SymbolFileBreakpad::LineIterator>
126 SymbolFileBreakpad::lines(Record::Kind section_type) {
127   return llvm::make_range(LineIterator(*m_obj_file, section_type),
128                           LineIterator(*m_obj_file));
129 }
130 
131 namespace {
132 // A helper class for constructing the list of support files for a given compile
133 // unit.
134 class SupportFileMap {
135 public:
136   // Given a breakpad file ID, return a file ID to be used in the support files
137   // for this compile unit.
138   size_t operator[](size_t file) {
139     return m_map.try_emplace(file, m_map.size() + 1).first->second;
140   }
141 
142   // Construct a FileSpecList containing only the support files relevant for
143   // this compile unit (in the correct order).
144   FileSpecList translate(const FileSpec &cu_spec,
145                          llvm::ArrayRef<FileSpec> all_files);
146 
147 private:
148   llvm::DenseMap<size_t, size_t> m_map;
149 };
150 } // namespace
151 
152 FileSpecList SupportFileMap::translate(const FileSpec &cu_spec,
153                                        llvm::ArrayRef<FileSpec> all_files) {
154   std::vector<FileSpec> result;
155   result.resize(m_map.size() + 1);
156   result[0] = cu_spec;
157   for (const auto &KV : m_map) {
158     if (KV.first < all_files.size())
159       result[KV.second] = all_files[KV.first];
160   }
161   return FileSpecList(std::move(result));
162 }
163 
164 void SymbolFileBreakpad::Initialize() {
165   PluginManager::RegisterPlugin(GetPluginNameStatic(),
166                                 GetPluginDescriptionStatic(), CreateInstance,
167                                 DebuggerInitialize);
168 }
169 
170 void SymbolFileBreakpad::Terminate() {
171   PluginManager::UnregisterPlugin(CreateInstance);
172 }
173 
174 ConstString SymbolFileBreakpad::GetPluginNameStatic() {
175   static ConstString g_name("breakpad");
176   return g_name;
177 }
178 
179 uint32_t SymbolFileBreakpad::CalculateAbilities() {
180   if (!m_obj_file)
181     return 0;
182   if (m_obj_file->GetPluginName() != ObjectFileBreakpad::GetPluginNameStatic())
183     return 0;
184 
185   return CompileUnits | Functions | LineTables;
186 }
187 
188 uint32_t SymbolFileBreakpad::GetNumCompileUnits() {
189   ParseCUData();
190   return m_cu_data->GetSize();
191 }
192 
193 CompUnitSP SymbolFileBreakpad::ParseCompileUnitAtIndex(uint32_t index) {
194   if (index >= m_cu_data->GetSize())
195     return nullptr;
196 
197   CompUnitData &data = m_cu_data->GetEntryRef(index).data;
198 
199   ParseFileRecords();
200 
201   FileSpec spec;
202 
203   // The FileSpec of the compile unit will be the file corresponding to the
204   // first LINE record.
205   LineIterator It(*m_obj_file, Record::Func, data.bookmark), End(*m_obj_file);
206   assert(Record::classify(*It) == Record::Func);
207   ++It; // Skip FUNC record.
208   if (It != End) {
209     auto record = LineRecord::parse(*It);
210     if (record && record->FileNum < m_files->size())
211       spec = (*m_files)[record->FileNum];
212   }
213 
214   auto cu_sp = std::make_shared<CompileUnit>(m_obj_file->GetModule(),
215                                              /*user_data*/ nullptr, spec, index,
216                                              eLanguageTypeUnknown,
217                                              /*is_optimized*/ eLazyBoolNo);
218 
219   GetSymbolVendor().SetCompileUnitAtIndex(index, cu_sp);
220   return cu_sp;
221 }
222 
223 size_t SymbolFileBreakpad::ParseFunctions(CompileUnit &comp_unit) {
224   // TODO
225   return 0;
226 }
227 
228 bool SymbolFileBreakpad::ParseLineTable(CompileUnit &comp_unit) {
229   CompUnitData &data = m_cu_data->GetEntryRef(comp_unit.GetID()).data;
230 
231   if (!data.line_table_up)
232     ParseLineTableAndSupportFiles(comp_unit, data);
233 
234   comp_unit.SetLineTable(data.line_table_up.release());
235   return true;
236 }
237 
238 bool SymbolFileBreakpad::ParseSupportFiles(CompileUnit &comp_unit,
239                                            FileSpecList &support_files) {
240   CompUnitData &data = m_cu_data->GetEntryRef(comp_unit.GetID()).data;
241   if (!data.support_files)
242     ParseLineTableAndSupportFiles(comp_unit, data);
243 
244   support_files = std::move(*data.support_files);
245   return true;
246 }
247 
248 uint32_t
249 SymbolFileBreakpad::ResolveSymbolContext(const Address &so_addr,
250                                          SymbolContextItem resolve_scope,
251                                          SymbolContext &sc) {
252   if (!(resolve_scope & (eSymbolContextCompUnit | eSymbolContextLineEntry)))
253     return 0;
254 
255   ParseCUData();
256   uint32_t idx =
257       m_cu_data->FindEntryIndexThatContains(so_addr.GetFileAddress());
258   if (idx == UINT32_MAX)
259     return 0;
260 
261   sc.comp_unit = GetSymbolVendor().GetCompileUnitAtIndex(idx).get();
262   SymbolContextItem result = eSymbolContextCompUnit;
263   if (resolve_scope & eSymbolContextLineEntry) {
264     if (sc.comp_unit->GetLineTable()->FindLineEntryByAddress(so_addr,
265                                                              sc.line_entry)) {
266       result |= eSymbolContextLineEntry;
267     }
268   }
269 
270   return result;
271 }
272 
273 uint32_t SymbolFileBreakpad::ResolveSymbolContext(
274     const FileSpec &file_spec, uint32_t line, bool check_inlines,
275     lldb::SymbolContextItem resolve_scope, SymbolContextList &sc_list) {
276   if (!(resolve_scope & eSymbolContextCompUnit))
277     return 0;
278 
279   uint32_t old_size = sc_list.GetSize();
280   for (size_t i = 0, size = GetNumCompileUnits(); i < size; ++i) {
281     CompileUnit &cu = *GetSymbolVendor().GetCompileUnitAtIndex(i);
282     cu.ResolveSymbolContext(file_spec, line, check_inlines,
283                             /*exact*/ false, resolve_scope, sc_list);
284   }
285   return sc_list.GetSize() - old_size;
286 }
287 
288 uint32_t SymbolFileBreakpad::FindFunctions(
289     ConstString name, const CompilerDeclContext *parent_decl_ctx,
290     FunctionNameType name_type_mask, bool include_inlines, bool append,
291     SymbolContextList &sc_list) {
292   // TODO
293   if (!append)
294     sc_list.Clear();
295   return sc_list.GetSize();
296 }
297 
298 uint32_t SymbolFileBreakpad::FindFunctions(const RegularExpression &regex,
299                                            bool include_inlines, bool append,
300                                            SymbolContextList &sc_list) {
301   // TODO
302   if (!append)
303     sc_list.Clear();
304   return sc_list.GetSize();
305 }
306 
307 uint32_t SymbolFileBreakpad::FindTypes(
308     ConstString name, const CompilerDeclContext *parent_decl_ctx,
309     bool append, uint32_t max_matches,
310     llvm::DenseSet<SymbolFile *> &searched_symbol_files, TypeMap &types) {
311   if (!append)
312     types.Clear();
313   return types.GetSize();
314 }
315 
316 size_t
317 SymbolFileBreakpad::FindTypes(const std::vector<CompilerContext> &context,
318                               bool append, TypeMap &types) {
319   if (!append)
320     types.Clear();
321   return types.GetSize();
322 }
323 
324 void SymbolFileBreakpad::AddSymbols(Symtab &symtab) {
325   Log *log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_SYMBOLS);
326   Module &module = *m_obj_file->GetModule();
327   addr_t base = GetBaseFileAddress();
328   if (base == LLDB_INVALID_ADDRESS) {
329     LLDB_LOG(log, "Unable to fetch the base address of object file. Skipping "
330                   "symtab population.");
331     return;
332   }
333 
334   const SectionList &list = *module.GetSectionList();
335   llvm::DenseMap<addr_t, Symbol> symbols;
336   auto add_symbol = [&](addr_t address, llvm::Optional<addr_t> size,
337                         llvm::StringRef name) {
338     address += base;
339     SectionSP section_sp = list.FindSectionContainingFileAddress(address);
340     if (!section_sp) {
341       LLDB_LOG(log,
342                "Ignoring symbol {0}, whose address ({1}) is outside of the "
343                "object file. Mismatched symbol file?",
344                name, address);
345       return;
346     }
347     symbols.try_emplace(
348         address, /*symID*/ 0, Mangled(name, /*is_mangled*/ false),
349         eSymbolTypeCode, /*is_global*/ true, /*is_debug*/ false,
350         /*is_trampoline*/ false, /*is_artificial*/ false,
351         AddressRange(section_sp, address - section_sp->GetFileAddress(),
352                      size.getValueOr(0)),
353         size.hasValue(), /*contains_linker_annotations*/ false, /*flags*/ 0);
354   };
355 
356   for (llvm::StringRef line : lines(Record::Func)) {
357     if (auto record = FuncRecord::parse(line))
358       add_symbol(record->Address, record->Size, record->Name);
359   }
360 
361   for (llvm::StringRef line : lines(Record::Public)) {
362     if (auto record = PublicRecord::parse(line))
363       add_symbol(record->Address, llvm::None, record->Name);
364     else
365       LLDB_LOG(log, "Failed to parse: {0}. Skipping record.", line);
366   }
367 
368   for (auto &KV : symbols)
369     symtab.AddSymbol(std::move(KV.second));
370   symtab.CalculateSymbolSizes();
371 }
372 
373 SymbolVendor &SymbolFileBreakpad::GetSymbolVendor() {
374   return *m_obj_file->GetModule()->GetSymbolVendor();
375 }
376 
377 addr_t SymbolFileBreakpad::GetBaseFileAddress() {
378   return m_obj_file->GetModule()
379       ->GetObjectFile()
380       ->GetBaseAddress()
381       .GetFileAddress();
382 }
383 
384 // Parse out all the FILE records from the breakpad file. These will be needed
385 // when constructing the support file lists for individual compile units.
386 void SymbolFileBreakpad::ParseFileRecords() {
387   if (m_files)
388     return;
389   m_files.emplace();
390 
391   Log *log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_SYMBOLS);
392   for (llvm::StringRef line : lines(Record::File)) {
393     auto record = FileRecord::parse(line);
394     if (!record) {
395       LLDB_LOG(log, "Failed to parse: {0}. Skipping record.", line);
396       continue;
397     }
398 
399     if (record->Number >= m_files->size())
400       m_files->resize(record->Number + 1);
401     FileSpec::Style style = FileSpec::GuessPathStyle(record->Name)
402                                 .getValueOr(FileSpec::Style::native);
403     (*m_files)[record->Number] = FileSpec(record->Name, style);
404   }
405 }
406 
407 void SymbolFileBreakpad::ParseCUData() {
408   if (m_cu_data)
409     return;
410 
411   m_cu_data.emplace();
412   Log *log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_SYMBOLS);
413   addr_t base = GetBaseFileAddress();
414   if (base == LLDB_INVALID_ADDRESS) {
415     LLDB_LOG(log, "SymbolFile parsing failed: Unable to fetch the base address "
416                   "of object file.");
417   }
418 
419   // We shall create one compile unit for each FUNC record. So, count the number
420   // of FUNC records, and store them in m_cu_data, together with their ranges.
421   for (LineIterator It(*m_obj_file, Record::Func), End(*m_obj_file); It != End;
422        ++It) {
423     if (auto record = FuncRecord::parse(*It)) {
424       m_cu_data->Append(CompUnitMap::Entry(base + record->Address, record->Size,
425                                            CompUnitData(It.GetBookmark())));
426     } else
427       LLDB_LOG(log, "Failed to parse: {0}. Skipping record.", *It);
428   }
429   m_cu_data->Sort();
430 }
431 
432 // Construct the list of support files and line table entries for the given
433 // compile unit.
434 void SymbolFileBreakpad::ParseLineTableAndSupportFiles(CompileUnit &cu,
435                                                        CompUnitData &data) {
436   addr_t base = GetBaseFileAddress();
437   assert(base != LLDB_INVALID_ADDRESS &&
438          "How did we create compile units without a base address?");
439 
440   SupportFileMap map;
441   data.line_table_up = llvm::make_unique<LineTable>(&cu);
442   std::unique_ptr<LineSequence> line_seq_up(
443       data.line_table_up->CreateLineSequenceContainer());
444   llvm::Optional<addr_t> next_addr;
445   auto finish_sequence = [&]() {
446     data.line_table_up->AppendLineEntryToSequence(
447         line_seq_up.get(), *next_addr, /*line*/ 0, /*column*/ 0,
448         /*file_idx*/ 0, /*is_start_of_statement*/ false,
449         /*is_start_of_basic_block*/ false, /*is_prologue_end*/ false,
450         /*is_epilogue_begin*/ false, /*is_terminal_entry*/ true);
451     data.line_table_up->InsertSequence(line_seq_up.get());
452     line_seq_up->Clear();
453   };
454 
455   LineIterator It(*m_obj_file, Record::Func, data.bookmark), End(*m_obj_file);
456   assert(Record::classify(*It) == Record::Func);
457   for (++It; It != End; ++It) {
458     auto record = LineRecord::parse(*It);
459     if (!record)
460       break;
461 
462     record->Address += base;
463 
464     if (next_addr && *next_addr != record->Address) {
465       // Discontiguous entries. Finish off the previous sequence and reset.
466       finish_sequence();
467     }
468     data.line_table_up->AppendLineEntryToSequence(
469         line_seq_up.get(), record->Address, record->LineNum, /*column*/ 0,
470         map[record->FileNum], /*is_start_of_statement*/ true,
471         /*is_start_of_basic_block*/ false, /*is_prologue_end*/ false,
472         /*is_epilogue_begin*/ false, /*is_terminal_entry*/ false);
473     next_addr = record->Address + record->Size;
474   }
475   if (next_addr)
476     finish_sequence();
477   data.support_files = map.translate(cu, *m_files);
478 }
479