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.
264 dwarf::toUnsigned(Die
.findRecursively({dwarf::DW_AT_decl_file
}))) {
266 dwarf::toUnsigned(Die
.findRecursively({dwarf::DW_AT_decl_line
}))) {
267 LineEntry
LE(StartAddress
, CUI
.DWARFToGSYMFileIndex(Gsym
, *FileIdx
),
269 FI
.OptLineTable
= LineTable();
270 FI
.OptLineTable
->push(LE
);
271 // LE.Addr = EndAddress;
272 // FI.OptLineTable->push(LE);
278 FI
.OptLineTable
= LineTable();
279 DWARFDebugLine::Row PrevRow
;
280 for (uint32_t RowIndex
: RowVector
) {
281 // Take file number and line/column from the row.
282 const DWARFDebugLine::Row
&Row
= CUI
.LineTable
->Rows
[RowIndex
];
283 const uint32_t FileIdx
= CUI
.DWARFToGSYMFileIndex(Gsym
, Row
.File
);
284 uint64_t RowAddress
= Row
.Address
.Address
;
285 // Watch out for a RowAddress that is in the middle of a line table entry
286 // in the DWARF. If we pass an address in between two line table entries
287 // we will get a RowIndex for the previous valid line table row which won't
288 // be contained in our function. This is usually a bug in the DWARF due to
289 // linker problems or LTO or other DWARF re-linking so it is worth emitting
290 // an error, but not worth stopping the creation of the GSYM.
291 if (!FI
.Range
.contains(RowAddress
)) {
292 if (RowAddress
< FI
.Range
.Start
) {
293 Log
<< "error: DIE has a start address whose LowPC is between the "
294 "line table Row[" << RowIndex
<< "] with address "
295 << HEX64(RowAddress
) << " and the next one.\n";
296 Die
.dump(Log
, 0, DIDumpOptions::getForSingleDIE());
297 RowAddress
= FI
.Range
.Start
;
303 LineEntry
LE(RowAddress
, FileIdx
, Row
.Line
);
304 if (RowIndex
!= RowVector
[0] && Row
.Address
< PrevRow
.Address
) {
305 // We have seen full duplicate line tables for functions in some
306 // DWARF files. Watch for those here by checking the the last
307 // row was the function's end address (HighPC) and that the
308 // current line table entry's address is the same as the first
309 // line entry we already have in our "function_info.Lines". If
310 // so break out after printing a warning.
311 auto FirstLE
= FI
.OptLineTable
->first();
312 if (FirstLE
&& *FirstLE
== LE
) {
313 if (!Gsym
.isQuiet()) {
314 Log
<< "warning: duplicate line table detected for DIE:\n";
315 Die
.dump(Log
, 0, DIDumpOptions::getForSingleDIE());
318 // Print out (ignore if os == nulls as this is expensive)
319 Log
<< "error: line table has addresses that do not "
320 << "monotonically increase:\n";
321 for (uint32_t RowIndex2
: RowVector
) {
322 CUI
.LineTable
->Rows
[RowIndex2
].dump(Log
);
324 Die
.dump(Log
, 0, DIDumpOptions::getForSingleDIE());
329 // Skip multiple line entries for the same file and line.
330 auto LastLE
= FI
.OptLineTable
->last();
331 if (LastLE
&& LastLE
->File
== FileIdx
&& LastLE
->Line
== Row
.Line
)
333 // Only push a row if it isn't an end sequence. End sequence markers are
334 // included for the last address in a function or the last contiguous
335 // address in a sequence.
336 if (Row
.EndSequence
) {
337 // End sequence means that the next line entry could have a lower address
338 // that the previous entries. So we clear the previous row so we don't
339 // trigger the line table error about address that do not monotonically
341 PrevRow
= DWARFDebugLine::Row();
343 FI
.OptLineTable
->push(LE
);
347 // If not line table rows were added, clear the line table so we don't encode
348 // on in the GSYM file.
349 if (FI
.OptLineTable
->empty())
350 FI
.OptLineTable
= llvm::None
;
353 void DwarfTransformer::handleDie(raw_ostream
&OS
, CUInfo
&CUI
, DWARFDie Die
) {
354 switch (Die
.getTag()) {
355 case dwarf::DW_TAG_subprogram
: {
356 Expected
<DWARFAddressRangesVector
> RangesOrError
= Die
.getAddressRanges();
357 if (!RangesOrError
) {
358 consumeError(RangesOrError
.takeError());
361 const DWARFAddressRangesVector
&Ranges
= RangesOrError
.get();
364 auto NameIndex
= getQualifiedNameIndex(Die
, CUI
.Language
, Gsym
);
366 OS
<< "error: function at " << HEX64(Die
.getOffset())
367 << " has no name\n ";
368 Die
.dump(OS
, 0, DIDumpOptions::getForSingleDIE());
372 // Create a function_info for each range
373 for (const DWARFAddressRange
&Range
: Ranges
) {
374 // The low PC must be less than the high PC. Many linkers don't remove
375 // DWARF for functions that don't get linked into the final executable.
376 // If both the high and low pc have relocations, linkers will often set
377 // the address values for both to the same value to indicate the function
378 // has been remove. Other linkers have been known to set the one or both
379 // PC values to a UINT32_MAX for 4 byte addresses and UINT64_MAX for 8
380 // byte addresses to indicate the function isn't valid. The check below
381 // tries to watch for these cases and abort if it runs into them.
382 if (Range
.LowPC
>= Range
.HighPC
|| CUI
.isHighestAddress(Range
.LowPC
))
385 // Many linkers can't remove DWARF and might set the LowPC to zero. Since
386 // high PC can be an offset from the low PC in more recent DWARF versions
387 // we need to watch for a zero'ed low pc which we do using
388 // ValidTextRanges below.
389 if (!Gsym
.IsValidTextAddress(Range
.LowPC
)) {
390 // We expect zero and -1 to be invalid addresses in DWARF depending
391 // on the linker of the DWARF. This indicates a function was stripped
392 // and the debug info wasn't able to be stripped from the DWARF. If
393 // the LowPC isn't zero or -1, then we should emit an error.
394 if (Range
.LowPC
!= 0) {
395 if (!Gsym
.isQuiet()) {
396 // Unexpected invalid address, emit a warning
397 Log
<< "warning: DIE has an address range whose start address is "
398 "not in any executable sections ("
399 << *Gsym
.GetValidTextRanges()
400 << ") and will not be processed:\n";
401 Die
.dump(Log
, 0, DIDumpOptions::getForSingleDIE());
408 FI
.setStartAddress(Range
.LowPC
);
409 FI
.setEndAddress(Range
.HighPC
);
410 FI
.Name
= *NameIndex
;
412 convertFunctionLineTable(OS
, CUI
, Die
, Gsym
, FI
);
414 if (hasInlineInfo(Die
, 0)) {
415 FI
.Inline
= InlineInfo();
416 FI
.Inline
->Name
= *NameIndex
;
417 FI
.Inline
->Ranges
.insert(FI
.Range
);
418 parseInlineInfo(Gsym
, CUI
, Die
, 0, FI
, *FI
.Inline
);
420 Gsym
.addFunctionInfo(std::move(FI
));
426 for (DWARFDie ChildDie
: Die
.children())
427 handleDie(OS
, CUI
, ChildDie
);
430 Error
DwarfTransformer::convert(uint32_t NumThreads
) {
431 size_t NumBefore
= Gsym
.getNumFunctionInfos();
432 if (NumThreads
== 1) {
433 // Parse all DWARF data from this thread, use the same string/file table
435 for (const auto &CU
: DICtx
.compile_units()) {
436 DWARFDie Die
= CU
->getUnitDIE(false);
437 CUInfo
CUI(DICtx
, dyn_cast
<DWARFCompileUnit
>(CU
.get()));
438 handleDie(Log
, CUI
, Die
);
441 // LLVM Dwarf parser is not thread-safe and we need to parse all DWARF up
442 // front before we start accessing any DIEs since there might be
443 // cross compile unit references in the DWARF. If we don't do this we can
446 // We need to call getAbbreviations sequentially first so that getUnitDIE()
447 // only works with its local data.
448 for (const auto &CU
: DICtx
.compile_units())
449 CU
->getAbbreviations();
451 // Now parse all DIEs in case we have cross compile unit references in a
453 ThreadPool
pool(hardware_concurrency(NumThreads
));
454 for (const auto &CU
: DICtx
.compile_units())
455 pool
.async([&CU
]() { CU
->getUnitDIE(false /*CUDieOnly*/); });
458 // Now convert all DWARF to GSYM in a thread pool.
460 for (const auto &CU
: DICtx
.compile_units()) {
461 DWARFDie Die
= CU
->getUnitDIE(false /*CUDieOnly*/);
463 CUInfo
CUI(DICtx
, dyn_cast
<DWARFCompileUnit
>(CU
.get()));
464 pool
.async([this, CUI
, &LogMutex
, Die
]() mutable {
465 std::string ThreadLogStorage
;
466 raw_string_ostream
ThreadOS(ThreadLogStorage
);
467 handleDie(ThreadOS
, CUI
, Die
);
469 if (!ThreadLogStorage
.empty()) {
470 // Print ThreadLogStorage lines into an actual stream under a lock
471 std::lock_guard
<std::mutex
> guard(LogMutex
);
472 Log
<< ThreadLogStorage
;
479 size_t FunctionsAddedCount
= Gsym
.getNumFunctionInfos() - NumBefore
;
480 Log
<< "Loaded " << FunctionsAddedCount
<< " functions from DWARF.\n";
481 return Error::success();
484 llvm::Error
DwarfTransformer::verify(StringRef GsymPath
) {
485 Log
<< "Verifying GSYM file \"" << GsymPath
<< "\":\n";
487 auto Gsym
= GsymReader::openFile(GsymPath
);
489 return Gsym
.takeError();
491 auto NumAddrs
= Gsym
->getNumAddresses();
492 DILineInfoSpecifier
DLIS(
493 DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath
,
494 DILineInfoSpecifier::FunctionNameKind::LinkageName
);
495 std::string gsymFilename
;
496 for (uint32_t I
= 0; I
< NumAddrs
; ++I
) {
497 auto FuncAddr
= Gsym
->getAddress(I
);
499 return createStringError(std::errc::invalid_argument
,
500 "failed to extract address[%i]", I
);
502 auto FI
= Gsym
->getFunctionInfo(*FuncAddr
);
504 return createStringError(std::errc::invalid_argument
,
505 "failed to extract function info for address 0x%"
508 for (auto Addr
= *FuncAddr
; Addr
< *FuncAddr
+ FI
->size(); ++Addr
) {
509 const object::SectionedAddress SectAddr
{
510 Addr
, object::SectionedAddress::UndefSection
};
511 auto LR
= Gsym
->lookup(Addr
);
513 return LR
.takeError();
515 auto DwarfInlineInfos
=
516 DICtx
.getInliningInfoForAddress(SectAddr
, DLIS
);
517 uint32_t NumDwarfInlineInfos
= DwarfInlineInfos
.getNumberOfFrames();
518 if (NumDwarfInlineInfos
== 0) {
519 DwarfInlineInfos
.addFrame(
520 DICtx
.getLineInfoForAddress(SectAddr
, DLIS
));
523 // Check for 1 entry that has no file and line info
524 if (NumDwarfInlineInfos
== 1 &&
525 DwarfInlineInfos
.getFrame(0).FileName
== "<invalid>") {
526 DwarfInlineInfos
= DIInliningInfo();
527 NumDwarfInlineInfos
= 0;
529 if (NumDwarfInlineInfos
> 0 &&
530 NumDwarfInlineInfos
!= LR
->Locations
.size()) {
531 Log
<< "error: address " << HEX64(Addr
) << " has "
532 << NumDwarfInlineInfos
<< " DWARF inline frames and GSYM has "
533 << LR
->Locations
.size() << "\n";
534 Log
<< " " << NumDwarfInlineInfos
<< " DWARF frames:\n";
535 for (size_t Idx
= 0; Idx
< NumDwarfInlineInfos
; ++Idx
) {
536 const auto dii
= DwarfInlineInfos
.getFrame(Idx
);
537 Log
<< " [" << Idx
<< "]: " << dii
.FunctionName
<< " @ "
538 << dii
.FileName
<< ':' << dii
.Line
<< '\n';
540 Log
<< " " << LR
->Locations
.size() << " GSYM frames:\n";
541 for (size_t Idx
= 0, count
= LR
->Locations
.size();
542 Idx
< count
; ++Idx
) {
543 const auto &gii
= LR
->Locations
[Idx
];
544 Log
<< " [" << Idx
<< "]: " << gii
.Name
<< " @ " << gii
.Dir
545 << '/' << gii
.Base
<< ':' << gii
.Line
<< '\n';
547 DwarfInlineInfos
= DICtx
.getInliningInfoForAddress(SectAddr
, DLIS
);
548 Gsym
->dump(Log
, *FI
);
552 for (size_t Idx
= 0, count
= LR
->Locations
.size(); Idx
< count
;
554 const auto &gii
= LR
->Locations
[Idx
];
555 if (Idx
< NumDwarfInlineInfos
) {
556 const auto dii
= DwarfInlineInfos
.getFrame(Idx
);
557 gsymFilename
= LR
->getSourceFile(Idx
);
558 // Verify function name
559 if (dii
.FunctionName
.find(gii
.Name
.str()) != 0)
560 Log
<< "error: address " << HEX64(Addr
) << " DWARF function \""
561 << dii
.FunctionName
.c_str()
562 << "\" doesn't match GSYM function \"" << gii
.Name
<< "\"\n";
563 // Verify source file path
564 if (dii
.FileName
!= gsymFilename
)
565 Log
<< "error: address " << HEX64(Addr
) << " DWARF path \""
566 << dii
.FileName
.c_str() << "\" doesn't match GSYM path \""
567 << gsymFilename
.c_str() << "\"\n";
568 // Verify source file line
569 if (dii
.Line
!= gii
.Line
)
570 Log
<< "error: address " << HEX64(Addr
) << " DWARF line "
571 << dii
.Line
<< " != GSYM line " << gii
.Line
<< "\n";
576 return Error::success();