1 //===- DwarfTransformer.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 <thread>
10 #include <unordered_set>
11 
12 #include "llvm/DebugInfo/DIContext.h"
13 #include "llvm/DebugInfo/DWARF/DWARFCompileUnit.h"
14 #include "llvm/DebugInfo/DWARF/DWARFContext.h"
15 #include "llvm/Support/Error.h"
16 #include "llvm/Support/ThreadPool.h"
17 #include "llvm/Support/raw_ostream.h"
18 
19 #include "llvm/DebugInfo/GSYM/DwarfTransformer.h"
20 #include "llvm/DebugInfo/GSYM/FunctionInfo.h"
21 #include "llvm/DebugInfo/GSYM/GsymCreator.h"
22 #include "llvm/DebugInfo/GSYM/GsymReader.h"
23 #include "llvm/DebugInfo/GSYM/InlineInfo.h"
24 
25 using namespace llvm;
26 using namespace gsym;
27 
28 struct llvm::gsym::CUInfo {
29   const DWARFDebugLine::LineTable *LineTable;
30   const char *CompDir;
31   std::vector<uint32_t> FileCache;
32   uint64_t Language = 0;
33   uint8_t AddrSize = 0;
34 
35   CUInfo(DWARFContext &DICtx, DWARFCompileUnit *CU) {
36     LineTable = DICtx.getLineTableForUnit(CU);
37     CompDir = CU->getCompilationDir();
38     FileCache.clear();
39     if (LineTable)
40       FileCache.assign(LineTable->Prologue.FileNames.size() + 1, UINT32_MAX);
41     DWARFDie Die = CU->getUnitDIE();
42     Language = dwarf::toUnsigned(Die.find(dwarf::DW_AT_language), 0);
43     AddrSize = CU->getAddressByteSize();
44   }
45 
46   /// Return true if Addr is the highest address for a given compile unit. The
47   /// highest address is encoded as -1, of all ones in the address. These high
48   /// addresses are used by some linkers to indicate that a function has been
49   /// dead stripped or didn't end up in the linked executable.
50   bool isHighestAddress(uint64_t Addr) const {
51     if (AddrSize == 4)
52       return Addr == UINT32_MAX;
53     else if (AddrSize == 8)
54       return Addr == UINT64_MAX;
55     return false;
56   }
57 
58   /// Convert a DWARF compile unit file index into a GSYM global file index.
59   ///
60   /// Each compile unit in DWARF has its own file table in the line table
61   /// prologue. GSYM has a single large file table that applies to all files
62   /// from all of the info in a GSYM file. This function converts between the
63   /// two and caches and DWARF CU file index that has already been converted so
64   /// the first client that asks for a compile unit file index will end up
65   /// doing the conversion, and subsequent clients will get the cached GSYM
66   /// index.
67   uint32_t DWARFToGSYMFileIndex(GsymCreator &Gsym, uint32_t DwarfFileIdx) {
68     if (!LineTable)
69       return 0;
70     assert(DwarfFileIdx < FileCache.size());
71     uint32_t &GsymFileIdx = FileCache[DwarfFileIdx];
72     if (GsymFileIdx != UINT32_MAX)
73       return GsymFileIdx;
74     std::string File;
75     if (LineTable->getFileNameByIndex(
76             DwarfFileIdx, CompDir,
77             DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath, File))
78       GsymFileIdx = Gsym.insertFile(File);
79     else
80       GsymFileIdx = 0;
81     return GsymFileIdx;
82   }
83 };
84 
85 
86 static DWARFDie GetParentDeclContextDIE(DWARFDie &Die) {
87   if (DWARFDie SpecDie =
88           Die.getAttributeValueAsReferencedDie(dwarf::DW_AT_specification)) {
89     if (DWARFDie SpecParent = GetParentDeclContextDIE(SpecDie))
90       return SpecParent;
91   }
92   if (DWARFDie AbstDie =
93           Die.getAttributeValueAsReferencedDie(dwarf::DW_AT_abstract_origin)) {
94     if (DWARFDie AbstParent = GetParentDeclContextDIE(AbstDie))
95       return AbstParent;
96   }
97 
98   // We never want to follow parent for inlined subroutine - that would
99   // give us information about where the function is inlined, not what
100   // function is inlined
101   if (Die.getTag() == dwarf::DW_TAG_inlined_subroutine)
102     return DWARFDie();
103 
104   DWARFDie ParentDie = Die.getParent();
105   if (!ParentDie)
106     return DWARFDie();
107 
108   switch (ParentDie.getTag()) {
109   case dwarf::DW_TAG_namespace:
110   case dwarf::DW_TAG_structure_type:
111   case dwarf::DW_TAG_union_type:
112   case dwarf::DW_TAG_class_type:
113   case dwarf::DW_TAG_subprogram:
114     return ParentDie; // Found parent decl context DIE
115   case dwarf::DW_TAG_lexical_block:
116     return GetParentDeclContextDIE(ParentDie);
117   default:
118     break;
119   }
120 
121   return DWARFDie();
122 }
123 
124 /// Get the GsymCreator string table offset for the qualified name for the
125 /// DIE passed in. This function will avoid making copies of any strings in
126 /// the GsymCreator when possible. We don't need to copy a string when the
127 /// string comes from our .debug_str section or is an inlined string in the
128 /// .debug_info. If we create a qualified name string in this function by
129 /// combining multiple strings in the DWARF string table or info, we will make
130 /// a copy of the string when we add it to the string table.
131 static Optional<uint32_t> getQualifiedNameIndex(DWARFDie &Die,
132                                                 uint64_t Language,
133                                                 GsymCreator &Gsym) {
134   // If the dwarf has mangled name, use mangled name
135   if (auto LinkageName =
136           dwarf::toString(Die.findRecursively({dwarf::DW_AT_MIPS_linkage_name,
137                                                dwarf::DW_AT_linkage_name}),
138                           nullptr))
139     return Gsym.insertString(LinkageName, /* Copy */ false);
140 
141   StringRef ShortName(Die.getName(DINameKind::ShortName));
142   if (ShortName.empty())
143     return llvm::None;
144 
145   // For C++ and ObjC, prepend names of all parent declaration contexts
146   if (!(Language == dwarf::DW_LANG_C_plus_plus ||
147         Language == dwarf::DW_LANG_C_plus_plus_03 ||
148         Language == dwarf::DW_LANG_C_plus_plus_11 ||
149         Language == dwarf::DW_LANG_C_plus_plus_14 ||
150         Language == dwarf::DW_LANG_ObjC_plus_plus ||
151         // This should not be needed for C, but we see C++ code marked as C
152         // in some binaries. This should hurt, so let's do it for C as well
153         Language == dwarf::DW_LANG_C))
154     return Gsym.insertString(ShortName, /* Copy */ false);
155 
156   // Some GCC optimizations create functions with names ending with .isra.<num>
157   // or .part.<num> and those names are just DW_AT_name, not DW_AT_linkage_name
158   // If it looks like it could be the case, don't add any prefix
159   if (ShortName.startswith("_Z") &&
160       (ShortName.contains(".isra.") || ShortName.contains(".part.")))
161     return Gsym.insertString(ShortName, /* Copy */ false);
162 
163   DWARFDie ParentDeclCtxDie = GetParentDeclContextDIE(Die);
164   if (ParentDeclCtxDie) {
165     std::string Name = ShortName.str();
166     while (ParentDeclCtxDie) {
167       StringRef ParentName(ParentDeclCtxDie.getName(DINameKind::ShortName));
168       if (!ParentName.empty()) {
169         // "lambda" names are wrapped in < >. Replace with { }
170         // to be consistent with demangled names and not to confuse with
171         // templates
172         if (ParentName.front() == '<' && ParentName.back() == '>')
173           Name = "{" + ParentName.substr(1, ParentName.size() - 2).str() + "}" +
174                 "::" + Name;
175         else
176           Name = ParentName.str() + "::" + Name;
177       }
178       ParentDeclCtxDie = GetParentDeclContextDIE(ParentDeclCtxDie);
179     }
180     // Copy the name since we created a new name in a std::string.
181     return Gsym.insertString(Name, /* Copy */ true);
182   }
183   // Don't copy the name since it exists in the DWARF object file.
184   return Gsym.insertString(ShortName, /* Copy */ false);
185 }
186 
187 static bool hasInlineInfo(DWARFDie Die, uint32_t Depth) {
188   bool CheckChildren = true;
189   switch (Die.getTag()) {
190   case dwarf::DW_TAG_subprogram:
191     // Don't look into functions within functions.
192     CheckChildren = Depth == 0;
193     break;
194   case dwarf::DW_TAG_inlined_subroutine:
195     return true;
196   default:
197     break;
198   }
199   if (!CheckChildren)
200     return false;
201   for (DWARFDie ChildDie : Die.children()) {
202     if (hasInlineInfo(ChildDie, Depth + 1))
203       return true;
204   }
205   return false;
206 }
207 
208 static void parseInlineInfo(GsymCreator &Gsym, CUInfo &CUI, DWARFDie Die,
209                             uint32_t Depth, FunctionInfo &FI,
210                             InlineInfo &parent) {
211   if (!hasInlineInfo(Die, Depth))
212     return;
213 
214   dwarf::Tag Tag = Die.getTag();
215   if (Tag == dwarf::DW_TAG_inlined_subroutine) {
216     // create new InlineInfo and append to parent.children
217     InlineInfo II;
218     DWARFAddressRange FuncRange =
219         DWARFAddressRange(FI.startAddress(), FI.endAddress());
220     Expected<DWARFAddressRangesVector> RangesOrError = Die.getAddressRanges();
221     if (RangesOrError) {
222       for (const DWARFAddressRange &Range : RangesOrError.get()) {
223         // Check that the inlined function is within the range of the function
224         // info, it might not be in case of split functions
225         if (FuncRange.LowPC <= Range.LowPC && Range.HighPC <= FuncRange.HighPC)
226           II.Ranges.insert(AddressRange(Range.LowPC, Range.HighPC));
227       }
228     }
229     if (II.Ranges.empty())
230       return;
231 
232     if (auto NameIndex = getQualifiedNameIndex(Die, CUI.Language, Gsym))
233       II.Name = *NameIndex;
234     II.CallFile = CUI.DWARFToGSYMFileIndex(
235         Gsym, dwarf::toUnsigned(Die.find(dwarf::DW_AT_call_file), 0));
236     II.CallLine = dwarf::toUnsigned(Die.find(dwarf::DW_AT_call_line), 0);
237     // parse all children and append to parent
238     for (DWARFDie ChildDie : Die.children())
239       parseInlineInfo(Gsym, CUI, ChildDie, Depth + 1, FI, II);
240     parent.Children.emplace_back(std::move(II));
241     return;
242   }
243   if (Tag == dwarf::DW_TAG_subprogram || Tag == dwarf::DW_TAG_lexical_block) {
244     // skip this Die and just recurse down
245     for (DWARFDie ChildDie : Die.children())
246       parseInlineInfo(Gsym, CUI, ChildDie, Depth + 1, FI, parent);
247   }
248 }
249 
250 static void convertFunctionLineTable(raw_ostream &Log, CUInfo &CUI,
251                                      DWARFDie Die, GsymCreator &Gsym,
252                                      FunctionInfo &FI) {
253   std::vector<uint32_t> RowVector;
254   const uint64_t StartAddress = FI.startAddress();
255   const uint64_t EndAddress = FI.endAddress();
256   const uint64_t RangeSize = EndAddress - StartAddress;
257   const object::SectionedAddress SecAddress{
258       StartAddress, object::SectionedAddress::UndefSection};
259 
260 
261   if (!CUI.LineTable->lookupAddressRange(SecAddress, RangeSize, RowVector)) {
262     // If we have a DW_TAG_subprogram but no line entries, fall back to using
263     // the DW_AT_decl_file an d DW_AT_decl_line if we have both attributes.
264     std::string FilePath = Die.getDeclFile(
265         DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath);
266     if (FilePath.empty())
267       return;
268     if (auto Line =
269             dwarf::toUnsigned(Die.findRecursively({dwarf::DW_AT_decl_line}))) {
270       LineEntry LE(StartAddress, Gsym.insertFile(FilePath), *Line);
271       FI.OptLineTable = LineTable();
272       FI.OptLineTable->push(LE);
273     }
274     return;
275   }
276 
277   FI.OptLineTable = LineTable();
278   DWARFDebugLine::Row PrevRow;
279   for (uint32_t RowIndex : RowVector) {
280     // Take file number and line/column from the row.
281     const DWARFDebugLine::Row &Row = CUI.LineTable->Rows[RowIndex];
282     const uint32_t FileIdx = CUI.DWARFToGSYMFileIndex(Gsym, Row.File);
283     uint64_t RowAddress = Row.Address.Address;
284     // Watch out for a RowAddress that is in the middle of a line table entry
285     // in the DWARF. If we pass an address in between two line table entries
286     // we will get a RowIndex for the previous valid line table row which won't
287     // be contained in our function. This is usually a bug in the DWARF due to
288     // linker problems or LTO or other DWARF re-linking so it is worth emitting
289     // an error, but not worth stopping the creation of the GSYM.
290     if (!FI.Range.contains(RowAddress)) {
291       if (RowAddress < FI.Range.start()) {
292         Log << "error: DIE has a start address whose LowPC is between the "
293           "line table Row[" << RowIndex << "] with address "
294           << HEX64(RowAddress) << " and the next one.\n";
295         Die.dump(Log, 0, DIDumpOptions::getForSingleDIE());
296         RowAddress = FI.Range.start();
297       } else {
298         continue;
299       }
300     }
301 
302     LineEntry LE(RowAddress, FileIdx, Row.Line);
303     if (RowIndex != RowVector[0] && Row.Address < PrevRow.Address) {
304       // We have seen full duplicate line tables for functions in some
305       // DWARF files. Watch for those here by checking the the last
306       // row was the function's end address (HighPC) and that the
307       // current line table entry's address is the same as the first
308       // line entry we already have in our "function_info.Lines". If
309       // so break out after printing a warning.
310       auto FirstLE = FI.OptLineTable->first();
311       if (FirstLE && *FirstLE == LE) {
312         if (!Gsym.isQuiet()) {
313           Log << "warning: duplicate line table detected for DIE:\n";
314           Die.dump(Log, 0, DIDumpOptions::getForSingleDIE());
315         }
316       } else {
317         // Print out (ignore if os == nulls as this is expensive)
318         Log << "error: line table has addresses that do not "
319              << "monotonically increase:\n";
320         for (uint32_t RowIndex2 : RowVector) {
321           CUI.LineTable->Rows[RowIndex2].dump(Log);
322         }
323         Die.dump(Log, 0, DIDumpOptions::getForSingleDIE());
324       }
325       break;
326     }
327 
328     // Skip multiple line entries for the same file and line.
329     auto LastLE = FI.OptLineTable->last();
330     if (LastLE && LastLE->File == FileIdx && LastLE->Line == Row.Line)
331         continue;
332     // Only push a row if it isn't an end sequence. End sequence markers are
333     // included for the last address in a function or the last contiguous
334     // address in a sequence.
335     if (Row.EndSequence) {
336       // End sequence means that the next line entry could have a lower address
337       // that the previous entries. So we clear the previous row so we don't
338       // trigger the line table error about address that do not monotonically
339       // increase.
340       PrevRow = DWARFDebugLine::Row();
341     } else {
342       FI.OptLineTable->push(LE);
343       PrevRow = Row;
344     }
345   }
346   // If not line table rows were added, clear the line table so we don't encode
347   // on in the GSYM file.
348   if (FI.OptLineTable->empty())
349     FI.OptLineTable = llvm::None;
350 }
351 
352 void DwarfTransformer::handleDie(raw_ostream &OS, CUInfo &CUI, DWARFDie Die) {
353   switch (Die.getTag()) {
354   case dwarf::DW_TAG_subprogram: {
355     Expected<DWARFAddressRangesVector> RangesOrError = Die.getAddressRanges();
356     if (!RangesOrError) {
357       consumeError(RangesOrError.takeError());
358       break;
359     }
360     const DWARFAddressRangesVector &Ranges = RangesOrError.get();
361     if (Ranges.empty())
362       break;
363     auto NameIndex = getQualifiedNameIndex(Die, CUI.Language, Gsym);
364     if (!NameIndex) {
365       OS << "error: function at " << HEX64(Die.getOffset())
366          << " has no name\n ";
367       Die.dump(OS, 0, DIDumpOptions::getForSingleDIE());
368       break;
369     }
370 
371     // Create a function_info for each range
372     for (const DWARFAddressRange &Range : Ranges) {
373       // The low PC must be less than the high PC. Many linkers don't remove
374       // DWARF for functions that don't get linked into the final executable.
375       // If both the high and low pc have relocations, linkers will often set
376       // the address values for both to the same value to indicate the function
377       // has been remove. Other linkers have been known to set the one or both
378       // PC values to a UINT32_MAX for 4 byte addresses and UINT64_MAX for 8
379       // byte addresses to indicate the function isn't valid. The check below
380       // tries to watch for these cases and abort if it runs into them.
381       if (Range.LowPC >= Range.HighPC || CUI.isHighestAddress(Range.LowPC))
382         break;
383 
384       // Many linkers can't remove DWARF and might set the LowPC to zero. Since
385       // high PC can be an offset from the low PC in more recent DWARF versions
386       // we need to watch for a zero'ed low pc which we do using
387       // ValidTextRanges below.
388       if (!Gsym.IsValidTextAddress(Range.LowPC)) {
389         // We expect zero and -1 to be invalid addresses in DWARF depending
390         // on the linker of the DWARF. This indicates a function was stripped
391         // and the debug info wasn't able to be stripped from the DWARF. If
392         // the LowPC isn't zero or -1, then we should emit an error.
393         if (Range.LowPC != 0) {
394           if (!Gsym.isQuiet()) {
395             // Unexpected invalid address, emit a warning
396             OS << "warning: DIE has an address range whose start address is "
397                   "not in any executable sections ("
398                << *Gsym.GetValidTextRanges()
399                << ") and will not be processed:\n";
400             Die.dump(OS, 0, DIDumpOptions::getForSingleDIE());
401           }
402         }
403         break;
404       }
405 
406       FunctionInfo FI;
407       FI.Range = {Range.LowPC, Range.HighPC};
408       FI.Name = *NameIndex;
409       if (CUI.LineTable) {
410         convertFunctionLineTable(OS, CUI, Die, Gsym, FI);
411       }
412       if (hasInlineInfo(Die, 0)) {
413         FI.Inline = InlineInfo();
414         FI.Inline->Name = *NameIndex;
415         FI.Inline->Ranges.insert(FI.Range);
416         parseInlineInfo(Gsym, CUI, Die, 0, FI, *FI.Inline);
417       }
418       Gsym.addFunctionInfo(std::move(FI));
419     }
420   } break;
421   default:
422     break;
423   }
424   for (DWARFDie ChildDie : Die.children())
425     handleDie(OS, CUI, ChildDie);
426 }
427 
428 Error DwarfTransformer::convert(uint32_t NumThreads) {
429   size_t NumBefore = Gsym.getNumFunctionInfos();
430   auto getDie = [&](DWARFUnit &DwarfUnit) -> DWARFDie {
431     DWARFDie ReturnDie = DwarfUnit.getUnitDIE(false);
432     if (llvm::Optional<uint64_t> DWOId = DwarfUnit.getDWOId()) {
433       DWARFUnit *DWOCU = DwarfUnit.getNonSkeletonUnitDIE(false).getDwarfUnit();
434       if (!DWOCU->isDWOUnit()) {
435         std::string DWOName = dwarf::toString(
436             DwarfUnit.getUnitDIE().find(
437                 {dwarf::DW_AT_dwo_name, dwarf::DW_AT_GNU_dwo_name}),
438             "");
439         Log << "warning: Unable to retrieve DWO .debug_info section for "
440             << DWOName << "\n";
441       } else {
442         ReturnDie = DWOCU->getUnitDIE(false);
443       }
444     }
445     return ReturnDie;
446   };
447   if (NumThreads == 1) {
448     // Parse all DWARF data from this thread, use the same string/file table
449     // for everything
450     for (const auto &CU : DICtx.compile_units()) {
451       DWARFDie Die = getDie(*CU);
452       CUInfo CUI(DICtx, dyn_cast<DWARFCompileUnit>(CU.get()));
453       handleDie(Log, CUI, Die);
454     }
455   } else {
456     // LLVM Dwarf parser is not thread-safe and we need to parse all DWARF up
457     // front before we start accessing any DIEs since there might be
458     // cross compile unit references in the DWARF. If we don't do this we can
459     // end up crashing.
460 
461     // We need to call getAbbreviations sequentially first so that getUnitDIE()
462     // only works with its local data.
463     for (const auto &CU : DICtx.compile_units())
464       CU->getAbbreviations();
465 
466     // Now parse all DIEs in case we have cross compile unit references in a
467     // thread pool.
468     ThreadPool pool(hardware_concurrency(NumThreads));
469     for (const auto &CU : DICtx.compile_units())
470       pool.async([&CU]() { CU->getUnitDIE(false /*CUDieOnly*/); });
471     pool.wait();
472 
473     // Now convert all DWARF to GSYM in a thread pool.
474     std::mutex LogMutex;
475     for (const auto &CU : DICtx.compile_units()) {
476       DWARFDie Die = getDie(*CU);
477       if (Die) {
478         CUInfo CUI(DICtx, dyn_cast<DWARFCompileUnit>(CU.get()));
479         pool.async([this, CUI, &LogMutex, Die]() mutable {
480           std::string ThreadLogStorage;
481           raw_string_ostream ThreadOS(ThreadLogStorage);
482           handleDie(ThreadOS, CUI, Die);
483           ThreadOS.flush();
484           if (!ThreadLogStorage.empty()) {
485             // Print ThreadLogStorage lines into an actual stream under a lock
486             std::lock_guard<std::mutex> guard(LogMutex);
487             Log << ThreadLogStorage;
488           }
489         });
490       }
491     }
492     pool.wait();
493   }
494   size_t FunctionsAddedCount = Gsym.getNumFunctionInfos() - NumBefore;
495   Log << "Loaded " << FunctionsAddedCount << " functions from DWARF.\n";
496   return Error::success();
497 }
498 
499 llvm::Error DwarfTransformer::verify(StringRef GsymPath) {
500   Log << "Verifying GSYM file \"" << GsymPath << "\":\n";
501 
502   auto Gsym = GsymReader::openFile(GsymPath);
503   if (!Gsym)
504     return Gsym.takeError();
505 
506   auto NumAddrs = Gsym->getNumAddresses();
507   DILineInfoSpecifier DLIS(
508       DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath,
509       DILineInfoSpecifier::FunctionNameKind::LinkageName);
510   std::string gsymFilename;
511   for (uint32_t I = 0; I < NumAddrs; ++I) {
512     auto FuncAddr = Gsym->getAddress(I);
513     if (!FuncAddr)
514         return createStringError(std::errc::invalid_argument,
515                                   "failed to extract address[%i]", I);
516 
517     auto FI = Gsym->getFunctionInfo(*FuncAddr);
518     if (!FI)
519       return createStringError(std::errc::invalid_argument,
520                             "failed to extract function info for address 0x%"
521                             PRIu64, *FuncAddr);
522 
523     for (auto Addr = *FuncAddr; Addr < *FuncAddr + FI->size(); ++Addr) {
524       const object::SectionedAddress SectAddr{
525           Addr, object::SectionedAddress::UndefSection};
526       auto LR = Gsym->lookup(Addr);
527       if (!LR)
528         return LR.takeError();
529 
530       auto DwarfInlineInfos =
531           DICtx.getInliningInfoForAddress(SectAddr, DLIS);
532       uint32_t NumDwarfInlineInfos = DwarfInlineInfos.getNumberOfFrames();
533       if (NumDwarfInlineInfos == 0) {
534         DwarfInlineInfos.addFrame(
535             DICtx.getLineInfoForAddress(SectAddr, DLIS));
536       }
537 
538       // Check for 1 entry that has no file and line info
539       if (NumDwarfInlineInfos == 1 &&
540           DwarfInlineInfos.getFrame(0).FileName == "<invalid>") {
541         DwarfInlineInfos = DIInliningInfo();
542         NumDwarfInlineInfos = 0;
543       }
544       if (NumDwarfInlineInfos > 0 &&
545           NumDwarfInlineInfos != LR->Locations.size()) {
546         Log << "error: address " << HEX64(Addr) << " has "
547             << NumDwarfInlineInfos << " DWARF inline frames and GSYM has "
548             << LR->Locations.size() << "\n";
549         Log << "    " << NumDwarfInlineInfos << " DWARF frames:\n";
550         for (size_t Idx = 0; Idx < NumDwarfInlineInfos; ++Idx) {
551           const auto &dii = DwarfInlineInfos.getFrame(Idx);
552           Log << "    [" << Idx << "]: " << dii.FunctionName << " @ "
553               << dii.FileName << ':' << dii.Line << '\n';
554         }
555         Log << "    " << LR->Locations.size() << " GSYM frames:\n";
556         for (size_t Idx = 0, count = LR->Locations.size();
557               Idx < count; ++Idx) {
558           const auto &gii = LR->Locations[Idx];
559           Log << "    [" << Idx << "]: " << gii.Name << " @ " << gii.Dir
560               << '/' << gii.Base << ':' << gii.Line << '\n';
561         }
562         DwarfInlineInfos = DICtx.getInliningInfoForAddress(SectAddr, DLIS);
563         Gsym->dump(Log, *FI);
564         continue;
565       }
566 
567       for (size_t Idx = 0, count = LR->Locations.size(); Idx < count;
568             ++Idx) {
569         const auto &gii = LR->Locations[Idx];
570         if (Idx < NumDwarfInlineInfos) {
571           const auto &dii = DwarfInlineInfos.getFrame(Idx);
572           gsymFilename = LR->getSourceFile(Idx);
573           // Verify function name
574           if (dii.FunctionName.find(gii.Name.str()) != 0)
575             Log << "error: address " << HEX64(Addr) << " DWARF function \""
576                 << dii.FunctionName.c_str()
577                 << "\" doesn't match GSYM function \"" << gii.Name << "\"\n";
578           // Verify source file path
579           if (dii.FileName != gsymFilename)
580             Log << "error: address " << HEX64(Addr) << " DWARF path \""
581                 << dii.FileName.c_str() << "\" doesn't match GSYM path \""
582                 << gsymFilename.c_str() << "\"\n";
583           // Verify source file line
584           if (dii.Line != gii.Line)
585             Log << "error: address " << HEX64(Addr) << " DWARF line "
586                 << dii.Line << " != GSYM line " << gii.Line << "\n";
587         }
588       }
589     }
590   }
591   return Error::success();
592 }
593