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