| 1 | //=== DebugInfoLinker.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 "DebugInfoLinker.h" |
| 10 | #include "Error.h" |
| 11 | #include "llvm/ADT/StringSwitch.h" |
| 12 | #include "llvm/DWARFLinker/Classic/DWARFLinker.h" |
| 13 | #include "llvm/DWARFLinker/Classic/DWARFStreamer.h" |
| 14 | #include "llvm/DWARFLinker/Parallel/DWARFLinker.h" |
| 15 | #include "llvm/DebugInfo/DWARF/DWARFContext.h" |
| 16 | #include "llvm/DebugInfo/DWARF/LowLevel/DWARFExpression.h" |
| 17 | #include "llvm/Object/ObjectFile.h" |
| 18 | #include <memory> |
| 19 | #include <vector> |
| 20 | |
| 21 | namespace llvm { |
| 22 | using namespace dwarf_linker; |
| 23 | |
| 24 | namespace dwarfutil { |
| 25 | |
| 26 | // ObjFileAddressMap allows to check whether specified DIE referencing |
| 27 | // dead addresses. It uses tombstone values to determine dead addresses. |
| 28 | // The concrete values of tombstone constants were discussed in |
| 29 | // https://reviews.llvm.org/D81784 and https://reviews.llvm.org/D84825. |
| 30 | // So we use following values as indicators of dead addresses: |
| 31 | // |
| 32 | // bfd: (LowPC == 0) or (LowPC == 1 and HighPC == 1 and DWARF v4 (or less)) |
| 33 | // or ([LowPC, HighPC] is not inside address ranges of .text sections). |
| 34 | // |
| 35 | // maxpc: (LowPC == -1) or (LowPC == -2 and DWARF v4 (or less)) |
| 36 | // That value is assumed to be compatible with |
| 37 | // http://www.dwarfstd.org/ShowIssue.php?issue=200609.1 |
| 38 | // |
| 39 | // exec: [LowPC, HighPC] is not inside address ranges of .text sections |
| 40 | // |
| 41 | // universal: maxpc and bfd |
| 42 | class ObjFileAddressMap : public AddressesMap { |
| 43 | public: |
| 44 | ObjFileAddressMap(DWARFContext &Context, const Options &Options, |
| 45 | object::ObjectFile &ObjFile) |
| 46 | : Opts(Options) { |
| 47 | // Remember addresses of existing text sections. |
| 48 | for (const object::SectionRef &Sect : ObjFile.sections()) { |
| 49 | if (!Sect.isText()) |
| 50 | continue; |
| 51 | const uint64_t Size = Sect.getSize(); |
| 52 | if (Size == 0) |
| 53 | continue; |
| 54 | const uint64_t StartAddr = Sect.getAddress(); |
| 55 | TextAddressRanges.insert(Range: {StartAddr, StartAddr + Size}); |
| 56 | } |
| 57 | |
| 58 | // Check CU address ranges for tombstone value. |
| 59 | for (std::unique_ptr<DWARFUnit> &CU : Context.compile_units()) { |
| 60 | Expected<llvm::DWARFAddressRangesVector> ARanges = |
| 61 | CU->getUnitDIE().getAddressRanges(); |
| 62 | if (!ARanges) { |
| 63 | llvm::consumeError(Err: ARanges.takeError()); |
| 64 | continue; |
| 65 | } |
| 66 | |
| 67 | for (auto &Range : *ARanges) { |
| 68 | if (!isDeadAddressRange(LowPC: Range.LowPC, HighPC: Range.HighPC, Version: CU->getVersion(), |
| 69 | Tombstone: Options.Tombstone, AddressByteSize: CU->getAddressByteSize())) { |
| 70 | HasValidAddressRanges = true; |
| 71 | break; |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | if (HasValidAddressRanges) |
| 76 | break; |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | // should be renamed into has valid address ranges |
| 81 | bool hasValidRelocs() override { return HasValidAddressRanges; } |
| 82 | |
| 83 | std::optional<int64_t> getSubprogramRelocAdjustment(const DWARFDie &DIE, |
| 84 | bool Verbose) override { |
| 85 | assert((DIE.getTag() == dwarf::DW_TAG_subprogram || |
| 86 | DIE.getTag() == dwarf::DW_TAG_label) && |
| 87 | "Wrong type of input die" ); |
| 88 | |
| 89 | if (std::optional<uint64_t> LowPC = |
| 90 | dwarf::toAddress(V: DIE.find(Attr: dwarf::DW_AT_low_pc))) { |
| 91 | if (!isDeadAddress(LowPC: *LowPC, Version: DIE.getDwarfUnit()->getVersion(), |
| 92 | Tombstone: Opts.Tombstone, |
| 93 | AddressByteSize: DIE.getDwarfUnit()->getAddressByteSize())) |
| 94 | // Relocation value for the linked binary is 0. |
| 95 | return 0; |
| 96 | } |
| 97 | |
| 98 | return std::nullopt; |
| 99 | } |
| 100 | |
| 101 | std::optional<int64_t> |
| 102 | getExprOpAddressRelocAdjustment(DWARFUnit &U, |
| 103 | const DWARFExpression::Operation &Op, |
| 104 | uint64_t, uint64_t, bool Verbose) override { |
| 105 | switch (Op.getCode()) { |
| 106 | default: { |
| 107 | assert(false && "Specified operation does not have address operand" ); |
| 108 | } break; |
| 109 | case dwarf::DW_OP_const2u: |
| 110 | case dwarf::DW_OP_const4u: |
| 111 | case dwarf::DW_OP_const8u: |
| 112 | case dwarf::DW_OP_const2s: |
| 113 | case dwarf::DW_OP_const4s: |
| 114 | case dwarf::DW_OP_const8s: |
| 115 | case dwarf::DW_OP_addr: { |
| 116 | if (!isDeadAddress(LowPC: Op.getRawOperand(Idx: 0), Version: U.getVersion(), Tombstone: Opts.Tombstone, |
| 117 | AddressByteSize: U.getAddressByteSize())) |
| 118 | // Relocation value for the linked binary is 0. |
| 119 | return 0; |
| 120 | } break; |
| 121 | case dwarf::DW_OP_constx: |
| 122 | case dwarf::DW_OP_addrx: { |
| 123 | if (std::optional<object::SectionedAddress> Address = |
| 124 | U.getAddrOffsetSectionItem(Index: Op.getRawOperand(Idx: 0))) { |
| 125 | if (!isDeadAddress(LowPC: Address->Address, Version: U.getVersion(), Tombstone: Opts.Tombstone, |
| 126 | AddressByteSize: U.getAddressByteSize())) |
| 127 | // Relocation value for the linked binary is 0. |
| 128 | return 0; |
| 129 | } |
| 130 | } break; |
| 131 | } |
| 132 | |
| 133 | return std::nullopt; |
| 134 | } |
| 135 | |
| 136 | std::optional<StringRef> getLibraryInstallName() override { |
| 137 | return std::nullopt; |
| 138 | } |
| 139 | |
| 140 | bool applyValidRelocs(MutableArrayRef<char>, uint64_t, bool) override { |
| 141 | // no need to apply relocations to the linked binary. |
| 142 | return false; |
| 143 | } |
| 144 | |
| 145 | bool needToSaveValidRelocs() override { return false; } |
| 146 | |
| 147 | void updateAndSaveValidRelocs(bool, uint64_t, int64_t, uint64_t, |
| 148 | uint64_t) override {} |
| 149 | |
| 150 | void updateRelocationsWithUnitOffset(uint64_t OriginalUnitOffset, |
| 151 | uint64_t OutputUnitOffset) override {} |
| 152 | |
| 153 | void clear() override {} |
| 154 | |
| 155 | protected: |
| 156 | // returns true if specified address range is inside address ranges |
| 157 | // of executable sections. |
| 158 | bool isInsideExecutableSectionsAddressRange(uint64_t LowPC, |
| 159 | std::optional<uint64_t> HighPC) { |
| 160 | std::optional<AddressRange> Range = |
| 161 | TextAddressRanges.getRangeThatContains(Addr: LowPC); |
| 162 | |
| 163 | if (HighPC) |
| 164 | return Range.has_value() && Range->end() >= *HighPC; |
| 165 | |
| 166 | return Range.has_value(); |
| 167 | } |
| 168 | |
| 169 | uint64_t isBFDDeadAddressRange(uint64_t LowPC, std::optional<uint64_t> HighPC, |
| 170 | uint16_t Version) { |
| 171 | if (LowPC == 0) |
| 172 | return true; |
| 173 | |
| 174 | if ((Version <= 4) && HighPC && (LowPC == 1 && *HighPC == 1)) |
| 175 | return true; |
| 176 | |
| 177 | return !isInsideExecutableSectionsAddressRange(LowPC, HighPC); |
| 178 | } |
| 179 | |
| 180 | uint64_t isMAXPCDeadAddressRange(uint64_t LowPC, |
| 181 | std::optional<uint64_t> HighPC, |
| 182 | uint16_t Version, uint8_t AddressByteSize) { |
| 183 | if (Version <= 4 && HighPC) { |
| 184 | if (LowPC == (dwarf::computeTombstoneAddress(AddressByteSize) - 1)) |
| 185 | return true; |
| 186 | } else if (LowPC == dwarf::computeTombstoneAddress(AddressByteSize)) |
| 187 | return true; |
| 188 | |
| 189 | if (!isInsideExecutableSectionsAddressRange(LowPC, HighPC)) |
| 190 | warning(Message: "Address referencing invalid text section is not marked with " |
| 191 | "tombstone value" ); |
| 192 | |
| 193 | return false; |
| 194 | } |
| 195 | |
| 196 | bool isDeadAddressRange(uint64_t LowPC, std::optional<uint64_t> HighPC, |
| 197 | uint16_t Version, TombstoneKind Tombstone, |
| 198 | uint8_t AddressByteSize) { |
| 199 | switch (Tombstone) { |
| 200 | case TombstoneKind::BFD: |
| 201 | return isBFDDeadAddressRange(LowPC, HighPC, Version); |
| 202 | case TombstoneKind::MaxPC: |
| 203 | return isMAXPCDeadAddressRange(LowPC, HighPC, Version, AddressByteSize); |
| 204 | case TombstoneKind::Universal: |
| 205 | return isBFDDeadAddressRange(LowPC, HighPC, Version) || |
| 206 | isMAXPCDeadAddressRange(LowPC, HighPC, Version, AddressByteSize); |
| 207 | case TombstoneKind::Exec: |
| 208 | return !isInsideExecutableSectionsAddressRange(LowPC, HighPC); |
| 209 | } |
| 210 | |
| 211 | llvm_unreachable("Unknown tombstone value" ); |
| 212 | } |
| 213 | |
| 214 | bool isDeadAddress(uint64_t LowPC, uint16_t Version, TombstoneKind Tombstone, |
| 215 | uint8_t AddressByteSize) { |
| 216 | return isDeadAddressRange(LowPC, HighPC: std::nullopt, Version, Tombstone, |
| 217 | AddressByteSize); |
| 218 | } |
| 219 | |
| 220 | private: |
| 221 | AddressRanges TextAddressRanges; |
| 222 | const Options &Opts; |
| 223 | bool HasValidAddressRanges = false; |
| 224 | }; |
| 225 | |
| 226 | static bool knownByDWARFUtil(StringRef SecName) { |
| 227 | return llvm::StringSwitch<bool>(SecName) |
| 228 | .Case(S: ".debug_info" , Value: true) |
| 229 | .Case(S: ".debug_types" , Value: true) |
| 230 | .Case(S: ".debug_abbrev" , Value: true) |
| 231 | .Case(S: ".debug_loc" , Value: true) |
| 232 | .Case(S: ".debug_loclists" , Value: true) |
| 233 | .Case(S: ".debug_frame" , Value: true) |
| 234 | .Case(S: ".debug_aranges" , Value: true) |
| 235 | .Case(S: ".debug_ranges" , Value: true) |
| 236 | .Case(S: ".debug_rnglists" , Value: true) |
| 237 | .Case(S: ".debug_line" , Value: true) |
| 238 | .Case(S: ".debug_line_str" , Value: true) |
| 239 | .Case(S: ".debug_addr" , Value: true) |
| 240 | .Case(S: ".debug_macro" , Value: true) |
| 241 | .Case(S: ".debug_macinfo" , Value: true) |
| 242 | .Case(S: ".debug_str" , Value: true) |
| 243 | .Case(S: ".debug_str_offsets" , Value: true) |
| 244 | .Case(S: ".debug_pubnames" , Value: true) |
| 245 | .Case(S: ".debug_pubtypes" , Value: true) |
| 246 | .Case(S: ".debug_names" , Value: true) |
| 247 | .Default(Value: false); |
| 248 | } |
| 249 | |
| 250 | template <typename AccelTableKind> |
| 251 | static std::optional<AccelTableKind> |
| 252 | getAcceleratorTableKind(StringRef SecName) { |
| 253 | return llvm::StringSwitch<std::optional<AccelTableKind>>(SecName) |
| 254 | .Case(".debug_pubnames" , AccelTableKind::Pub) |
| 255 | .Case(".debug_pubtypes" , AccelTableKind::Pub) |
| 256 | .Case(".debug_names" , AccelTableKind::DebugNames) |
| 257 | .Default(std::nullopt); |
| 258 | } |
| 259 | |
| 260 | static std::string getMessageForReplacedAcceleratorTables( |
| 261 | SmallVector<StringRef> &AccelTableNamesToReplace, |
| 262 | DwarfUtilAccelKind TargetTable) { |
| 263 | std::string Message; |
| 264 | |
| 265 | Message += "'" ; |
| 266 | for (StringRef Name : AccelTableNamesToReplace) { |
| 267 | if (Message.size() > 1) |
| 268 | Message += ", " ; |
| 269 | Message += Name; |
| 270 | } |
| 271 | |
| 272 | Message += "' will be replaced with requested " ; |
| 273 | |
| 274 | switch (TargetTable) { |
| 275 | case DwarfUtilAccelKind::DWARF: |
| 276 | Message += ".debug_names table" ; |
| 277 | break; |
| 278 | |
| 279 | default: |
| 280 | assert(false); |
| 281 | } |
| 282 | |
| 283 | return Message; |
| 284 | } |
| 285 | |
| 286 | static std::string getMessageForDeletedAcceleratorTables( |
| 287 | SmallVector<StringRef> &AccelTableNamesToReplace) { |
| 288 | std::string Message; |
| 289 | |
| 290 | Message += "'" ; |
| 291 | for (StringRef Name : AccelTableNamesToReplace) { |
| 292 | if (Message.size() > 1) |
| 293 | Message += ", " ; |
| 294 | Message += Name; |
| 295 | } |
| 296 | |
| 297 | Message += "' will be deleted as no accelerator tables are requested" ; |
| 298 | |
| 299 | return Message; |
| 300 | } |
| 301 | |
| 302 | template <typename Linker> |
| 303 | Error linkDebugInfoImpl(object::ObjectFile &File, const Options &Options, |
| 304 | raw_pwrite_stream &OutStream) { |
| 305 | std::mutex ErrorHandlerMutex; |
| 306 | |
| 307 | auto ReportWarn = [&](const Twine &Message, StringRef Context, |
| 308 | const DWARFDie *Die) { |
| 309 | // FIXME: implement warning logging which does not block other threads. |
| 310 | if (!ErrorHandlerMutex.try_lock()) |
| 311 | return; |
| 312 | |
| 313 | warning(Message, Prefix: Context); |
| 314 | if (Options.Verbose && Die) { |
| 315 | DIDumpOptions DumpOpts; |
| 316 | DumpOpts.ChildRecurseDepth = 0; |
| 317 | DumpOpts.Verbose = Options.Verbose; |
| 318 | |
| 319 | WithColor::note() << " in DIE:\n" ; |
| 320 | Die->dump(OS&: errs(), /*Indent=*/indent: 6, DumpOpts); |
| 321 | } |
| 322 | ErrorHandlerMutex.unlock(); |
| 323 | }; |
| 324 | auto ReportErr = [&](const Twine &Message, StringRef Context, |
| 325 | const DWARFDie *) { |
| 326 | // FIXME: implement error logging which does not block other threads. |
| 327 | if (!ErrorHandlerMutex.try_lock()) |
| 328 | return; |
| 329 | |
| 330 | WithColor::error(OS&: errs(), Prefix: Context) << Message << '\n'; |
| 331 | ErrorHandlerMutex.unlock(); |
| 332 | }; |
| 333 | |
| 334 | // Create DWARF linker. |
| 335 | std::unique_ptr<Linker> DebugInfoLinker = |
| 336 | Linker::createLinker(ReportErr, ReportWarn); |
| 337 | |
| 338 | Triple TargetTriple = File.makeTriple(); |
| 339 | std::unique_ptr<classic::DwarfStreamer> Streamer; |
| 340 | if (Expected<std::unique_ptr<classic::DwarfStreamer>> StreamerOrErr = |
| 341 | classic::DwarfStreamer::createStreamer(TheTriple: TargetTriple, |
| 342 | FileType: Linker::OutputFileType::Object, |
| 343 | OutFile&: OutStream, Warning: ReportWarn)) |
| 344 | Streamer = std::move(*StreamerOrErr); |
| 345 | else |
| 346 | return StreamerOrErr.takeError(); |
| 347 | |
| 348 | if constexpr (std::is_same<Linker, |
| 349 | dwarf_linker::parallel::DWARFLinker>::value) { |
| 350 | DebugInfoLinker->setOutputDWARFHandler( |
| 351 | TargetTriple, |
| 352 | [&](std::shared_ptr<dwarf_linker::parallel::SectionDescriptorBase> |
| 353 | Section) { |
| 354 | Streamer->emitSectionContents(SecData: Section->getContents(), |
| 355 | SecKind: Section->getKind()); |
| 356 | }); |
| 357 | } else |
| 358 | DebugInfoLinker->setOutputDWARFEmitter(Streamer.get()); |
| 359 | |
| 360 | DebugInfoLinker->setEstimatedObjfilesAmount(1); |
| 361 | DebugInfoLinker->setNumThreads(Options.NumThreads); |
| 362 | DebugInfoLinker->setNoODR(!Options.DoODRDeduplication); |
| 363 | DebugInfoLinker->setVerbosity(Options.Verbose); |
| 364 | DebugInfoLinker->setUpdateIndexTablesOnly(!Options.DoGarbageCollection); |
| 365 | |
| 366 | std::vector<std::unique_ptr<DWARFFile>> ObjectsForLinking(1); |
| 367 | |
| 368 | // Add object files to the DWARFLinker. |
| 369 | std::unique_ptr<DWARFContext> Context = DWARFContext::create( |
| 370 | File, DWARFContext::ProcessDebugRelocations::Process, nullptr, "" , |
| 371 | [&](Error Err) { |
| 372 | handleAllErrors(std::move(Err), [&](ErrorInfoBase &Info) { |
| 373 | ReportErr(Info.message(), "" , nullptr); |
| 374 | }); |
| 375 | }, |
| 376 | [&](Error Warning) { |
| 377 | handleAllErrors(std::move(Warning), [&](ErrorInfoBase &Info) { |
| 378 | ReportWarn(Info.message(), "" , nullptr); |
| 379 | }); |
| 380 | }); |
| 381 | std::unique_ptr<ObjFileAddressMap> AddressesMap( |
| 382 | std::make_unique<ObjFileAddressMap>(args&: *Context, args: Options, args&: File)); |
| 383 | |
| 384 | ObjectsForLinking[0] = std::make_unique<DWARFFile>( |
| 385 | args: File.getFileName(), args: std::move(Context), args: std::move(AddressesMap)); |
| 386 | |
| 387 | uint16_t MaxDWARFVersion = 0; |
| 388 | std::function<void(const DWARFUnit &Unit)> OnCUDieLoaded = |
| 389 | [&MaxDWARFVersion](const DWARFUnit &Unit) { |
| 390 | MaxDWARFVersion = std::max(a: Unit.getVersion(), b: MaxDWARFVersion); |
| 391 | }; |
| 392 | |
| 393 | for (size_t I = 0; I < ObjectsForLinking.size(); I++) |
| 394 | DebugInfoLinker->addObjectFile(*ObjectsForLinking[I], nullptr, |
| 395 | OnCUDieLoaded); |
| 396 | |
| 397 | // If we haven't seen any CUs, pick an arbitrary valid Dwarf version anyway. |
| 398 | if (MaxDWARFVersion == 0) |
| 399 | MaxDWARFVersion = 3; |
| 400 | |
| 401 | if (Error Err = DebugInfoLinker->setTargetDWARFVersion(MaxDWARFVersion)) |
| 402 | return Err; |
| 403 | |
| 404 | SmallVector<typename Linker::AccelTableKind> AccelTables; |
| 405 | |
| 406 | switch (Options.AccelTableKind) { |
| 407 | case DwarfUtilAccelKind::None: |
| 408 | // Nothing to do. |
| 409 | break; |
| 410 | case DwarfUtilAccelKind::DWARF: |
| 411 | // use .debug_names for all DWARF versions. |
| 412 | AccelTables.push_back(Linker::AccelTableKind::DebugNames); |
| 413 | break; |
| 414 | } |
| 415 | |
| 416 | // Add accelerator tables to DWARFLinker. |
| 417 | for (typename Linker::AccelTableKind Table : AccelTables) |
| 418 | DebugInfoLinker->addAccelTableKind(Table); |
| 419 | |
| 420 | for (std::unique_ptr<DWARFFile> &CurFile : ObjectsForLinking) { |
| 421 | SmallVector<StringRef> AccelTableNamesToReplace; |
| 422 | SmallVector<StringRef> AccelTableNamesToDelete; |
| 423 | |
| 424 | // Unknown debug sections or non-requested accelerator sections would be |
| 425 | // removed. Display warning for such sections. |
| 426 | for (SectionName Sec : CurFile->Dwarf->getDWARFObj().getSectionNames()) { |
| 427 | if (isDebugSection(SecName: Sec.Name)) { |
| 428 | std::optional<typename Linker::AccelTableKind> SrcAccelTableKind = |
| 429 | getAcceleratorTableKind<typename Linker::AccelTableKind>(Sec.Name); |
| 430 | |
| 431 | if (SrcAccelTableKind) { |
| 432 | assert(knownByDWARFUtil(Sec.Name)); |
| 433 | |
| 434 | if (Options.AccelTableKind == DwarfUtilAccelKind::None) |
| 435 | AccelTableNamesToDelete.push_back(Elt: Sec.Name); |
| 436 | else if (!llvm::is_contained(AccelTables, *SrcAccelTableKind)) |
| 437 | AccelTableNamesToReplace.push_back(Elt: Sec.Name); |
| 438 | } else if (!knownByDWARFUtil(SecName: Sec.Name)) { |
| 439 | assert(!SrcAccelTableKind); |
| 440 | warning( |
| 441 | Message: formatv( |
| 442 | Fmt: "'{0}' is not currently supported: section will be skipped" , |
| 443 | Vals&: Sec.Name), |
| 444 | Prefix: Options.InputFileName); |
| 445 | } |
| 446 | } |
| 447 | } |
| 448 | |
| 449 | // Display message for the replaced accelerator tables. |
| 450 | if (!AccelTableNamesToReplace.empty()) |
| 451 | warning(Message: getMessageForReplacedAcceleratorTables(AccelTableNamesToReplace, |
| 452 | TargetTable: Options.AccelTableKind), |
| 453 | Prefix: Options.InputFileName); |
| 454 | |
| 455 | // Display message for the removed accelerator tables. |
| 456 | if (!AccelTableNamesToDelete.empty()) |
| 457 | warning(Message: getMessageForDeletedAcceleratorTables(AccelTableNamesToReplace&: AccelTableNamesToDelete), |
| 458 | Prefix: Options.InputFileName); |
| 459 | } |
| 460 | |
| 461 | // Link debug info. |
| 462 | if (Error Err = DebugInfoLinker->link()) |
| 463 | return Err; |
| 464 | |
| 465 | Streamer->finish(); |
| 466 | return Error::success(); |
| 467 | } |
| 468 | |
| 469 | Error linkDebugInfo(object::ObjectFile &File, const Options &Options, |
| 470 | raw_pwrite_stream &OutStream) { |
| 471 | if (Options.UseDWARFLinkerParallel) |
| 472 | return linkDebugInfoImpl<parallel::DWARFLinker>(File, Options, OutStream); |
| 473 | else |
| 474 | return linkDebugInfoImpl<classic::DWARFLinker>(File, Options, OutStream); |
| 475 | } |
| 476 | |
| 477 | } // end of namespace dwarfutil |
| 478 | } // end of namespace llvm |
| 479 | |