diff --git a/include/eld/Config/GeneralOptions.h b/include/eld/Config/GeneralOptions.h index a3a8c1d0a..b2696ed06 100644 --- a/include/eld/Config/GeneralOptions.h +++ b/include/eld/Config/GeneralOptions.h @@ -522,6 +522,19 @@ class GeneralOptions { return ArchiveMemberReportFile; } + // --emit-symbol-resolution-report + void setSymbolResolutionReportFile(llvm::StringRef File) { + SymbolResolutionReportFile = File.str(); + } + + const std::optional &getSymbolResolutionReportFile() const { + return SymbolResolutionReportFile; + } + + bool shouldEmitSymbolResolutionReport() const { + return SymbolResolutionReportFile.has_value(); + } + // --ld-generated-unwind-info void setGenUnwindInfo(bool PEnable = true) { BGenUnwindInfo = PEnable; } @@ -1364,6 +1377,8 @@ class GeneralOptions { std::optional PluginActivityLogFile; // --plugin-activity-file output path std::optional ArchiveMemberReportFile; // --archive-member-report output path + std::optional + SymbolResolutionReportFile; // --emit-symbol-resolution-report output path std::string MappingFileName; // --Mapping-file std::string MappingDumpFile; // --dump-mapping-file std::string ResponseDumpFile; // --dump-response-file diff --git a/include/eld/Core/Linker.h b/include/eld/Core/Linker.h index db103334d..a753a0355 100644 --- a/include/eld/Core/Linker.h +++ b/include/eld/Core/Linker.h @@ -85,6 +85,11 @@ class Linker { void printLayout(); + /// Emits the JSON symbol resolution report. This function + /// must only be called if symbol resolution report is + /// requested. + bool emitSymbolResolutionReport(); + void unloadPlugins(); // Set the GNU linker driver after sniffing diff --git a/include/eld/Driver/GnuLinkerOptions.td b/include/eld/Driver/GnuLinkerOptions.td index 84de5d72f..d02610cfd 100644 --- a/include/eld/Driver/GnuLinkerOptions.td +++ b/include/eld/Driver/GnuLinkerOptions.td @@ -1556,6 +1556,13 @@ defm ArchiveMemberReportFile MetaVarName<"">, Group; +defm SymbolResolutionReportFile + : mDashDeprEqWithOpt<"emit-symbol-resolution-report", + "SymbolResolutionReportFile", + "Emit JSON symbol resolution report">, + MetaVarName<"">, + Group; + //===----------------------------------------------------------------------===// /// Help! //===----------------------------------------------------------------------===// diff --git a/include/eld/LayoutMap/LayoutInfo.h b/include/eld/LayoutMap/LayoutInfo.h index 695c8a9ec..071fd9342 100644 --- a/include/eld/LayoutMap/LayoutInfo.h +++ b/include/eld/LayoutMap/LayoutInfo.h @@ -81,8 +81,7 @@ class LayoutInfo { ShowTiming = 0x20, ShowDebugStrings = 0x40, ShowRelativePath = 0x80, - ShowInitialLayout = 0x100, - ShowSymbolResolution = 0x200 + ShowInitialLayout = 0x100 }; enum InputKindPrefix { @@ -419,10 +418,6 @@ class LayoutInfo { void printStats(void *H, llvm::raw_ostream &OS) const; - bool showSymbolResolution() const { - return LayoutDetail & LayoutDetail::ShowSymbolResolution; - } - private: Stats LinkStats; std::vector Features; diff --git a/include/eld/LayoutMap/TextLayoutPrinter.h b/include/eld/LayoutMap/TextLayoutPrinter.h index 7d671607d..5fbbadc0b 100644 --- a/include/eld/LayoutMap/TextLayoutPrinter.h +++ b/include/eld/LayoutMap/TextLayoutPrinter.h @@ -170,8 +170,6 @@ class TextLayoutPrinter { void printFragments(Module &Module, ELFSection &OutSect, RuleContainer &R, bool UseColor); - void printSymbolResolution(Module &Module); - void printOffsetHelper(bool HasOffset, std::function F) const; private: diff --git a/include/eld/Object/ObjectLinker.h b/include/eld/Object/ObjectLinker.h index 6fc486d8c..e0d455ced 100644 --- a/include/eld/Object/ObjectLinker.h +++ b/include/eld/Object/ObjectLinker.h @@ -399,6 +399,8 @@ class ObjectLinker { bool emitArchiveMemberReport(llvm::StringRef Filename) const; + bool emitSymbolResolutionReport(llvm::StringRef Filename) const; + private: /// Assigns version nodes to symbols with GNU ld semantics: /// - Pass 1: Exact matches (forward order, first wins, warns on reassign) diff --git a/include/eld/SymbolResolver/NamePool.h b/include/eld/SymbolResolver/NamePool.h index 3ceab2c05..1d96156ad 100644 --- a/include/eld/SymbolResolver/NamePool.h +++ b/include/eld/SymbolResolver/NamePool.h @@ -82,8 +82,7 @@ class NamePool { DiagnosticPrinter *Printer); LDSymbol *createPluginSymbol(InputFile *Input, std::string SymbolName, - Fragment *CurFragment, uint64_t Val, - LayoutInfo *layoutInfo); + Fragment *CurFragment, uint64_t Val); size_t getNumGlobalSize() const { return GlobalSymbols.size(); } diff --git a/include/eld/SymbolResolver/SymbolInfo.h b/include/eld/SymbolResolver/SymbolInfo.h index 9f0ef8a3d..85ec70706 100644 --- a/include/eld/SymbolResolver/SymbolInfo.h +++ b/include/eld/SymbolResolver/SymbolInfo.h @@ -75,6 +75,7 @@ class SymbolInfo { : SymBinding(0), SymType(0), SymVisibility(0), SymSectIndexKind(0), IsBitcode(0) {} unsigned int SymBinding : 2; + // FIXME: SymType needs 4 bits. unsigned int SymType : 2; unsigned int SymVisibility : 2; unsigned int SymSectIndexKind : 3; @@ -87,6 +88,7 @@ class SymbolInfo { void setSymbolSectionIndexKind(ResolveInfo::Binding Binding, ResolveInfo::Desc SymDesc); void setBitcodeAttribute(bool IsBitcode); + // FIXME: Is the below bit pattern correct? /// Information is stored as follows in this bitfield: /// 0b000000000000000000000sssvvttttbb /// b: bits used to represent symbol binding. @@ -99,4 +101,4 @@ class SymbolInfo { }; } // namespace eld -#endif \ No newline at end of file +#endif diff --git a/include/eld/SymbolResolver/SymbolResolutionInfo.h b/include/eld/SymbolResolver/SymbolResolutionInfo.h index 2d3c535c9..cdcd81836 100644 --- a/include/eld/SymbolResolver/SymbolResolutionInfo.h +++ b/include/eld/SymbolResolver/SymbolResolutionInfo.h @@ -10,6 +10,7 @@ #include "llvm/ADT/MapVector.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/JSON.h" #include namespace eld { @@ -18,6 +19,7 @@ class GeneralOptions; class LDSymbol; class LinkerConfig; class LinkerScript; +class Module; class NamePool; class Plugin; @@ -29,8 +31,21 @@ class SymbolResolutionInfo { using CandidatesTableType = llvm::StringMap; using SymbolInfoMapType = llvm::MapVector; - std::string getSymbolInfoAsString(const LDSymbol *Sym, - const GeneralOptions &Options); + /// Returns the (decorated) section name for a defined symbol, or an empty + /// string when the symbol has no associated section (Undef/Abs/unknown + /// bitcode section). + std::string getSymbolSectionName(const LDSymbol *Sym, + const SymbolInfo &SymInfo, + const GeneralOptions &Options) const; + + /// Returns the plugin that provided the symbol, or nullptr for + /// non-plugin symbols. + const Plugin *getSymbolPlugin(const LDSymbol *Sym) const { + auto Iter = SymbolToPluginMap.find(Sym); + if (Iter != SymbolToPluginMap.end()) + return Iter->second; + return nullptr; + } /// Setup symbol resolution candidates information. This information is /// required for creating symbol resolution report. This function does two @@ -61,7 +76,17 @@ class SymbolResolutionInfo { SymbolToPluginMap[Sym] = Plugin; } + /// Emits the symbol resolution report as standalone JSON to \p Filename. + /// Returns false (and raises a diagnostic) if the file cannot be written. + bool emitSymbolResolutionReport(Module &CurModule, llvm::StringRef Filename); + private: + /// Builds the JSON object describing a single symbol resolution candidate. + llvm::json::Object buildCandidateObject(const LDSymbol *Candidate, + const SymbolInfo &SymInfo, + const GeneralOptions &Options, + bool IsSelected); + CandidatesTableType Candidates; SymbolInfoMapType SymbolInfoMap; std::vector LTOObjectSymbols; diff --git a/lib/Core/Linker.cpp b/lib/Core/Linker.cpp index 424a4689d..1c1e5c04e 100644 --- a/lib/Core/Linker.cpp +++ b/lib/Core/Linker.cpp @@ -258,6 +258,12 @@ void Linker::printLayout() { ObjLinker->printlayout(); } +bool Linker::emitSymbolResolutionReport() { + const GeneralOptions &Options = ThisConfig->options(); + return ObjLinker->emitSymbolResolutionReport( + *Options.getSymbolResolutionReportFile()); +} + bool Linker::activateInputs(std::vector &Actions) { LinkerProgress->incrementAndDisplayProgress(); for (auto &Action : Actions) { diff --git a/lib/Core/Module.cpp b/lib/Core/Module.cpp index 874fce223..d6f760877 100644 --- a/lib/Core/Module.cpp +++ b/lib/Core/Module.cpp @@ -509,12 +509,20 @@ llvm::StringRef Module::getStateStr() const { void Module::addSymbolCreatedByPluginToFragment(Fragment *F, std::string Symbol, uint64_t Val, const eld::Plugin *Plugin) { - LayoutInfo *layoutInfo = getLayoutInfo(); LDSymbol *S = SymbolNamePool.createPluginSymbol( - getInternalInput(Module::InternalInputType::Plugin), Symbol, F, Val, - layoutInfo); - if (S && layoutInfo && layoutInfo->showSymbolResolution()) - SymbolNamePool.getSRI().recordPluginSymbol(S, Plugin); + getInternalInput(Module::InternalInputType::Plugin), Symbol, F, Val); + if (S && ThisConfig.options().shouldEmitSymbolResolutionReport()) { + const ResolveInfo *Info = S->resolveInfo(); + SymbolResolutionInfo &SRI = SymbolNamePool.getSRI(); + SRI.recordSymbolInfo( + S, SymbolInfo{Info->resolvedOrigin(), Info->size(), + static_cast(Info->binding()), + static_cast(Info->type()), + Info->visibility(), + static_cast(Info->desc()), + /*isBitcode=*/false}); + SRI.recordPluginSymbol(S, Plugin); + } PluginFragmentToSymbols[F]; PluginFragmentToSymbols[F].push_back(S); llvm::dyn_cast(F->getOwningSection()->getInputFile()) diff --git a/lib/LayoutMap/LayoutInfo.cpp b/lib/LayoutMap/LayoutInfo.cpp index c6bcebab7..b3dea11f8 100644 --- a/lib/LayoutMap/LayoutInfo.cpp +++ b/lib/LayoutMap/LayoutInfo.cpp @@ -182,7 +182,6 @@ LayoutInfo::setLayoutDetail(llvm::StringRef Option, .Case("show-timing", ShowTiming) .Case("show-debug-strings", ShowDebugStrings) .Case("show-initial-layout", ShowInitialLayout) - .Case("show-symbol-resolution", ShowSymbolResolution) .StartsWith(ShowRelativePathOptionStr, ShowRelativePath) .Default(0); LayoutDetail |= OptionLayoutDetail; diff --git a/lib/LayoutMap/TextLayoutPrinter.cpp b/lib/LayoutMap/TextLayoutPrinter.cpp index 6f766685f..30318a4c0 100644 --- a/lib/LayoutMap/TextLayoutPrinter.cpp +++ b/lib/LayoutMap/TextLayoutPrinter.cpp @@ -1338,9 +1338,6 @@ void TextLayoutPrinter::printMapFile(eld::Module &Module) { if (!ThisLayoutInfo->showOnlyLayout()) printPluginInfo(Module); - - if (ThisLayoutInfo->showSymbolResolution()) - printSymbolResolution(Module); } void TextLayoutPrinter::printLayout(eld::Module &Module) { @@ -1536,51 +1533,6 @@ void TextLayoutPrinter::printFragments(Module &Module, ELFSection &OutSect, } } -void TextLayoutPrinter::printSymbolResolution(Module &Module) { - NamePool &NP = Module.getNamePool(); - SymbolResolutionInfo &SRI = NP.getSRI(); - const auto &Symbols = Module.getSymbols(); - const GeneralOptions &Options = ThisLayoutInfo->getConfig().options(); - SRI.setupCandidatesInfo(NP, Module.getScript()); - - outputStream() << "# Symbol Resolution: " - << "\n"; - - size_t Index = 0; - for (const auto *RI : Symbols) { - if (RI->isLocal() && - RI->resolvedOrigin() != Module.getInternalInput(Module::Plugin)) - continue; - ++Index; - llvm::StringRef SymName = RI->getName(); - const SymbolResolutionInfo::CandidatesType Candidates = - SRI.getCandidates(SymName); - outputStream() << Index << ") " << SymName << "\n"; - for (const auto &Candidate : Candidates) { - std::optional OptSymbolInfo = SRI.getSymbolInfo(Candidate); - ASSERT(OptSymbolInfo, "Symbol info must be present!"); - SymbolInfo CandidateInfo = OptSymbolInfo.value(); - - std::string CandidateInfoAsString = - SRI.getSymbolInfoAsString(Candidate, Options); - outputStream() << "\t" << CandidateInfoAsString; - if (Candidate->resolveInfo()->outSymbol() == Candidate || - (CandidateInfo.isBitcodeSymbol() && - CandidateInfo.getInputFile() == - Candidate->resolveInfo()->resolvedOrigin())) - outputStream() << " [Selected]"; - if (CandidateInfo.isBitcodeSymbol()) { - if (const LDSymbol *LTOSym = - SRI.getCorrespondingLTOObjectSymIfAny(Candidate)) { - outputStream() << "\n\t " - << SRI.getSymbolInfoAsString(LTOSym, Options); - } - } - outputStream() << "\n"; - } - } -} - void TextLayoutPrinter::printOffsetHelper(bool HasOffset, std::function F) const { if (!HasOffset) { diff --git a/lib/LinkerWrapper/GnuLdDriver.cpp b/lib/LinkerWrapper/GnuLdDriver.cpp index a76aa2650..58313f14b 100644 --- a/lib/LinkerWrapper/GnuLdDriver.cpp +++ b/lib/LinkerWrapper/GnuLdDriver.cpp @@ -1333,6 +1333,11 @@ bool GnuLdDriver::processOptions(llvm::opt::InputArgList &Args) { Config.options().setArchiveMemberReportFile(A->getValue()); } + // --emit-symbol-resolution-report= + if (llvm::opt::Arg *A = Args.getLastArg(T::SymbolResolutionReportFile)) { + Config.options().setSymbolResolutionReportFile(A->getValue()); + } + if (Args.hasArg(T::use_old_rule_matching)) Config.options().setUseOldRuleMatching(true); @@ -2037,6 +2042,8 @@ bool GnuLdDriver::doLink(llvm::opt::InputArgList &Args, linkStatus = linker.link(); // llvm::errs() << "link: linkStatus: " << linkStatus << "\n"; linker.printLayout(); + if (linkStatus && Config.options().shouldEmitSymbolResolutionReport()) + linkStatus &= linker.emitSymbolResolutionReport(); } if (!linkStatus || Config.options().getRecordInputFiles()) handleReproduce(Args, actions, true); diff --git a/lib/Object/ObjectLinker.cpp b/lib/Object/ObjectLinker.cpp index 219f3af49..714805a6a 100644 --- a/lib/Object/ObjectLinker.cpp +++ b/lib/Object/ObjectLinker.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "eld/Object/ObjectLinker.h" #include "eld/BranchIsland/BranchIslandFactory.h" +#include "eld/Config/GeneralOptions.h" #include "eld/Config/LinkerConfig.h" #include "eld/Core/LinkerScript.h" #include "eld/Core/Module.h" @@ -52,6 +53,7 @@ #include "eld/Script/InputSectDesc.h" #include "eld/Script/OutputSectData.h" #include "eld/Script/OutputSectDesc.h" +#include "eld/Script/Plugin.h" #include "eld/Script/ScriptFile.h" #include "eld/Script/ScriptReader.h" #include "eld/Script/ScriptSymbol.h" @@ -62,7 +64,10 @@ #include "eld/Support/StringRefUtils.h" #include "eld/Support/Utils.h" #include "eld/SymbolResolver/IRBuilder.h" +#include "eld/SymbolResolver/LDSymbol.h" +#include "eld/SymbolResolver/NamePool.h" #include "eld/SymbolResolver/ResolveInfo.h" +#include "eld/SymbolResolver/SymbolResolutionInfo.h" #include "eld/Target/GNULDBackend.h" #include "eld/Target/LDFileFormat.h" #include "eld/Target/Relocator.h" @@ -85,6 +90,7 @@ #include "llvm/Support/raw_ostream.h" #include #include +#include #include #include @@ -160,6 +166,11 @@ bool ObjectLinker::emitArchiveMemberReport(llvm::StringRef Filename) const { ThisConfig.getDiagEngine()); } +bool ObjectLinker::emitSymbolResolutionReport(llvm::StringRef Filename) const { + return ThisModule->getNamePool().getSRI().emitSymbolResolutionReport( + *ThisModule, Filename); +} + /// initStdSections - initialize standard sections bool ObjectLinker::initStdSections() { ObjectBuilder Builder(ThisConfig, *ThisModule); diff --git a/lib/SymbolResolver/IRBuilder.cpp b/lib/SymbolResolver/IRBuilder.cpp index 1615dcf39..3a16bdfd4 100644 --- a/lib/SymbolResolver/IRBuilder.cpp +++ b/lib/SymbolResolver/IRBuilder.cpp @@ -234,8 +234,7 @@ LDSymbol *IRBuilder::addSymbolFromObject( return InputSym; } - if (ThisModule.getLayoutInfo() && - ThisModule.getLayoutInfo()->showSymbolResolution()) + if (ThisModule.getConfig().options().shouldEmitSymbolResolutionReport()) NP.getSRI().recordSymbolInfo(InputSym, SymInfo); bool S = NP.insertNonLocalSymbol(InputSymbolResolveInfo, *InputSym, @@ -336,8 +335,7 @@ LDSymbol *IRBuilder::addSymbolFromDynObj( InputSym->setSectionIndex(Shndx); InputSym->setSymbolIndex(SymIdx); - if (ThisModule.getLayoutInfo() && - ThisModule.getLayoutInfo()->showSymbolResolution()) + if (ThisModule.getConfig().options().shouldEmitSymbolResolutionReport()) ThisModule.getNamePool().getSRI().recordSymbolInfo(InputSym, SymInfo); Resolver::Result ResolvedResult = {nullptr, false, false}; @@ -572,8 +570,7 @@ LDSymbol *IRBuilder::addSymbol( OutputSym->setValue(Value, false); } - if (ThisModule.getLayoutInfo() && - ThisModule.getLayoutInfo()->showSymbolResolution()) { + if (ThisModule.getConfig().options().shouldEmitSymbolResolutionReport()) { SymbolResolutionInfo &SRI = ThisModule.getNamePool().getSRI(); SRI.recordSymbolInfo(OutputSym, SymbolInfo{Input, Size, Binding, Type, Visibility, diff --git a/lib/SymbolResolver/NamePool.cpp b/lib/SymbolResolver/NamePool.cpp index 52050228e..c72e47216 100644 --- a/lib/SymbolResolver/NamePool.cpp +++ b/lib/SymbolResolver/NamePool.cpp @@ -268,8 +268,7 @@ void NamePool::setupNullSymbol() { /// createSymbol - create a symbol LDSymbol *NamePool::createPluginSymbol(InputFile *Input, std::string SymbolName, - Fragment *CurFragment, uint64_t Val, - LayoutInfo *layoutInfo) { + Fragment *CurFragment, uint64_t Val) { llvm::StringRef SymName = Saver.save(SymbolName); ResolveInfo *Info = make(SymName); Info->setIsSymbol(true); @@ -287,15 +286,6 @@ LDSymbol *NamePool::createPluginSymbol(InputFile *Input, std::string SymbolName, Sym->setFragmentRef(make(*CurFragment, Val)); Info->setOutSymbol(Sym); LocalSymbols.push_back(Info); - if (layoutInfo && layoutInfo->showSymbolResolution()) - getSRI().recordSymbolInfo( - Sym, SymbolInfo{Input, Info->size(), - static_cast(Info->binding()), - static_cast(Info->type()), - Info->visibility(), - static_cast(Info->desc()), - /*isBitcode=*/false}); - return Sym; } diff --git a/lib/SymbolResolver/SymbolInfo.cpp b/lib/SymbolResolver/SymbolInfo.cpp index 8a04debfd..14744abca 100644 --- a/lib/SymbolResolver/SymbolInfo.cpp +++ b/lib/SymbolResolver/SymbolInfo.cpp @@ -20,6 +20,7 @@ SymbolInfo::SymbolInfo(const InputFile *InputFile, size_t Size, setBitcodeAttribute(IsBitcode); } +// FIXME: How is absolute binding handled? void SymbolInfo::setSymbolBinding(ResolveInfo::Binding Binding) { if (Binding == ResolveInfo::Local) SymbolInfoBitfield.SymBinding = SymbolBinding::Local; @@ -117,4 +118,4 @@ llvm::StringRef SymbolInfo::getSymbolSectionIndexKindAsStr() const { #undef ADD_CASE } return "UnknownSymbolSectionIndex"; -} \ No newline at end of file +} diff --git a/lib/SymbolResolver/SymbolResolutionInfo.cpp b/lib/SymbolResolver/SymbolResolutionInfo.cpp index 48a0673e2..1cd11b9b9 100644 --- a/lib/SymbolResolver/SymbolResolutionInfo.cpp +++ b/lib/SymbolResolver/SymbolResolutionInfo.cpp @@ -6,12 +6,18 @@ #include "eld/SymbolResolver/SymbolResolutionInfo.h" +#include "eld/Config/GeneralOptions.h" #include "eld/Config/LinkerConfig.h" +#include "eld/Core/Module.h" +#include "eld/Diagnostics/DiagnosticEngine.h" #include "eld/Fragment/FragmentRef.h" #include "eld/Input/BitcodeFile.h" #include "eld/Script/Plugin.h" #include "eld/SymbolResolver/NamePool.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/raw_ostream.h" #include using namespace eld; @@ -64,73 +70,37 @@ SymbolResolutionInfo::getCandidates(llvm::StringRef SymName) { return Iter->second; } -std::string -SymbolResolutionInfo::getSymbolInfoAsString(const LDSymbol *Sym, - const GeneralOptions &Options) { - std::optional OptSymInfo = getSymbolInfo(Sym); - if (!OptSymInfo) +std::string SymbolResolutionInfo::getSymbolSectionName( + const LDSymbol *Sym, const SymbolInfo &SymInfo, + const GeneralOptions &Options) const { + if (SymInfo.getSymbolSectionIndexKind() != SymbolInfo::SectionIndexKind::Def) return ""; - SymbolInfo SymInfo = OptSymInfo.value(); - std::string InputFile = SymInfo.getInputFile()->getInput()->decoratedPath(); - - auto PluginSymIter = SymbolToPluginMap.find(Sym); - if (PluginSymIter != SymbolToPluginMap.end()) { - const Plugin *P = PluginSymIter->second; - InputFile += "[" + P->getPluginName() + "]"; - } - - std::string SymName = - Sym->resolveInfo()->getDecoratedName(/*DoDemangle=*/false); - std::string SymbolInfo = SymName + "(" + InputFile; - if (SymInfo.getSymbolSectionIndexKind() == - SymbolInfo::SectionIndexKind::Def) { - std::string SectName; - if (SymInfo.isBitcodeSymbol()) { - const BitcodeFile *BitcodeInputFile = - llvm::cast(SymInfo.getInputFile()); - Section *BitcodeSect = - BitcodeInputFile->getInputSectionForSymbol(*Sym->resolveInfo()); - // The check is required here because it is undefined behavior to - // initialize std::string with nullptr. - // bitcodeSect can be nullptr in cases where bitcode section cannot be - // determined. For example: we cannot know input sections for asm symbols. - if (BitcodeSect) - SectName = BitcodeSect->name(); - } else { - const FragmentRef *FragRef = Sym->fragRef(); - if (FragRef != FragmentRef::null() && FragRef != FragmentRef::discard() && - FragRef != nullptr && FragRef->frag()) { - Section *S = FragRef->frag()->getOwningSection(); - // Ideally, we should never have a fragment without an owning section. - // Thus, this can be an assert. However, if in some corner case this - // condition is not satisfied, then I don't think we should fail - // the link because of some information required for diagnostics. - if (S) - SectName = S->getDecoratedName(Options); - } + std::string SectName; + if (SymInfo.isBitcodeSymbol()) { + const BitcodeFile *BitcodeInputFile = + llvm::cast(SymInfo.getInputFile()); + Section *BitcodeSect = + BitcodeInputFile->getInputSectionForSymbol(*Sym->resolveInfo()); + // The check is required here because it is undefined behavior to + // initialize std::string with nullptr. + // bitcodeSect can be nullptr in cases where bitcode section cannot be + // determined. For example: we cannot know input sections for asm symbols. + if (BitcodeSect) + SectName = BitcodeSect->name(); + } else { + const FragmentRef *FragRef = Sym->fragRef(); + if (FragRef != FragmentRef::null() && FragRef != FragmentRef::discard() && + FragRef != nullptr && FragRef->frag()) { + Section *S = FragRef->frag()->getOwningSection(); + // Ideally, we should never have a fragment without an owning section. + // Thus, this can be an assert. However, if in some corner case this + // condition is not satisfied, then I don't think we should fail + // the link because of some information required for diagnostics. + if (S) + SectName = S->getDecoratedName(Options); } - if (!SectName.empty()) - SymbolInfo += ":" + SectName; } - SymbolInfo += ")"; - std::vector SymbolAttributes; - SymbolInfo += " ["; - SymbolAttributes.push_back("Size=" + std::to_string(SymInfo.getSize())); - if (SymInfo.isBitcodeSymbol()) - SymbolAttributes.push_back("bitcode"); - SymbolAttributes.push_back(SymInfo.getSymbolSectionIndexKindAsStr().str()); - if (SymInfo.getSymbolSectionIndexKind() != SymbolInfo::SectionIndexKind::Abs) - SymbolAttributes.push_back(SymInfo.getSymbolBindingAsStr().str()); - SymbolAttributes.push_back(SymInfo.getSymbolTypeAsStr().str()); - if (SymInfo.getSymbolVisibility() != ResolveInfo::Visibility::Default) - SymbolAttributes.push_back(SymInfo.getSymbolVisibilityAsStr().str()); - for (size_t I = 0; I < SymbolAttributes.size(); ++I) { - SymbolInfo += SymbolAttributes[I]; - if (I != SymbolAttributes.size() - 1) - SymbolInfo += ", "; - } - SymbolInfo += "]"; - return SymbolInfo; + return SectName; } void SymbolResolutionInfo::recordSymbolInfo(const LDSymbol *Sym, @@ -145,3 +115,131 @@ const LDSymbol *SymbolResolutionInfo::getCorrespondingLTOObjectSymIfAny( return It->second; return nullptr; } + +/// clang-format off +/// JSON schema emitted by --emit-symbol-resolution-report: +/// +/// { +/// "SymbolResolutionReportVersion": 1, +/// "Symbols": [ +/// { +/// "Name": "foo", +/// "Selected": "libc.a(malloc.o)(foo)", // () +/// "Candidates": [ +/// { +/// "Name": "foo", +/// "InputFile": "libc.a(malloc.o)", +/// "Section": ".text", // omitted when no section +/// "Plugin": "MyPlugin", // only for plugin-created symbols +/// "Size": 4, +/// "Bitcode": false, +/// "SectionIndexKind": "Def", +/// "Binding": "Global", // always present +/// "Type": "Object", +/// "Visibility": "Default", // always present +/// "IsSelected": true, +/// "LTOObjectSymbol": { ...same shape... } // bitcode w/ post-LTO sym +/// } +/// ] +/// } +/// ] +/// } +/// clang-format on +llvm::json::Object SymbolResolutionInfo::buildCandidateObject( + const LDSymbol *Candidate, const SymbolInfo &SymInfo, + const GeneralOptions &Options, bool IsSelected) { + llvm::json::Object Obj; + Obj["Name"] = + Candidate->resolveInfo()->getDecoratedName(/*DoDemangle=*/false); + Obj["InputFile"] = SymInfo.getInputFile()->getInput()->decoratedPath(); + std::string SectName = getSymbolSectionName(Candidate, SymInfo, Options); + if (!SectName.empty()) + Obj["Section"] = SectName; + if (const Plugin *P = getSymbolPlugin(Candidate)) + Obj["Plugin"] = P->getPluginName(); + Obj["Size"] = static_cast(SymInfo.getSize()); + Obj["Bitcode"] = SymInfo.isBitcodeSymbol(); + Obj["SectionIndexKind"] = SymInfo.getSymbolSectionIndexKindAsStr(); + Obj["Binding"] = SymInfo.getSymbolBindingAsStr(); + Obj["Type"] = SymInfo.getSymbolTypeAsStr(); + Obj["Visibility"] = SymInfo.getSymbolVisibilityAsStr(); + Obj["IsSelected"] = IsSelected; + return Obj; +} + +bool SymbolResolutionInfo::emitSymbolResolutionReport( + Module &CurModule, llvm::StringRef Filename) { + DiagnosticEngine *DiagEngine = CurModule.getConfig().getDiagEngine(); + std::error_code EC; + llvm::raw_fd_ostream OS(Filename, EC); + if (EC) { + if (DiagEngine) + DiagEngine->raise(Diag::unable_to_write_json_file) + << Filename << EC.message(); + return false; + } + + NamePool &NP = CurModule.getNamePool(); + const GeneralOptions &Options = CurModule.getConfig().options(); + setupCandidatesInfo(NP, CurModule.getScript()); + + llvm::json::Array SymbolsArray; + for (const auto *RI : CurModule.getSymbols()) { + if (RI->isLocal() && + RI->resolvedOrigin() != + CurModule.getInternalInput(Module::InternalInputType::Plugin)) + continue; + + llvm::StringRef SymName = RI->getName(); + const SymbolResolutionInfo::CandidatesType &Candidates = + getCandidates(SymName); + + llvm::json::Object SymEntry; + SymEntry["Name"] = SymName; + + llvm::json::Array CandidatesArray; + std::string Selected; + for (const LDSymbol *Candidate : Candidates) { + std::optional OptSymbolInfo = getSymbolInfo(Candidate); + if (!OptSymbolInfo) + continue; + SymbolInfo CandidateInfo = OptSymbolInfo.value(); + + bool IsSelected = Candidate->resolveInfo()->outSymbol() == Candidate || + (CandidateInfo.isBitcodeSymbol() && + CandidateInfo.getInputFile() == + Candidate->resolveInfo()->resolvedOrigin()); + + llvm::json::Object CandObj = + buildCandidateObject(Candidate, CandidateInfo, Options, IsSelected); + + if (CandidateInfo.isBitcodeSymbol()) { + if (const LDSymbol *LTOSym = + getCorrespondingLTOObjectSymIfAny(Candidate)) { + if (std::optional LTOInfo = getSymbolInfo(LTOSym)) + CandObj["LTOObjectSymbol"] = buildCandidateObject( + LTOSym, LTOInfo.value(), Options, IsSelected); + } + } + + if (IsSelected) + Selected = + (CandidateInfo.getInputFile()->getInput()->decoratedPath() + "(" + + Candidate->resolveInfo()->getDecoratedName(/*DoDemangle=*/false) + + ")"); + + CandidatesArray.push_back(std::move(CandObj)); + } + + if (!Selected.empty()) + SymEntry["Selected"] = Selected; + SymEntry["Candidates"] = std::move(CandidatesArray); + SymbolsArray.push_back(std::move(SymEntry)); + } + + llvm::json::Object Root; + Root["SymbolResolutionReportVersion"] = 1; + Root["Symbols"] = std::move(SymbolsArray); + OS << llvm::formatv("{0:2}\n", llvm::json::Value(std::move(Root))); + return true; +} diff --git a/test/Common/standalone/CommandLine/PrintHelp/PrintModHelp.test b/test/Common/standalone/CommandLine/PrintHelp/PrintModHelp.test index e34c5448e..c8a89c7d1 100644 --- a/test/Common/standalone/CommandLine/PrintHelp/PrintModHelp.test +++ b/test/Common/standalone/CommandLine/PrintHelp/PrintModHelp.test @@ -381,6 +381,10 @@ RUN: %link --help 2>&1 | %filecheck %s #CHECK: -archive-member-report= #CHECK: --archive-member-report #CHECK: -archive-member-report +#CHECK: --emit-symbol-resolution-report= +#CHECK: -emit-symbol-resolution-report= +#CHECK: --emit-symbol-resolution-report +#CHECK: -emit-symbol-resolution-report #CHECK: --eh-frame-hdr #CHECK: --gc-sections #CHECK: -gc-sections diff --git a/test/Common/standalone/SymbolResolutionReport/ArchiveSymbols/ArchiveSymbols.test b/test/Common/standalone/SymbolResolutionReport/ArchiveSymbols/ArchiveSymbols.test index cecefb444..514ba2164 100644 --- a/test/Common/standalone/SymbolResolutionReport/ArchiveSymbols/ArchiveSymbols.test +++ b/test/Common/standalone/SymbolResolutionReport/ArchiveSymbols/ArchiveSymbols.test @@ -7,25 +7,23 @@ RUN: %clang %clangopts -o %t1.1.o %p/Inputs/1.c -c RUN: %clang %clangopts -o %t1.2.o %p/Inputs/2.c -c RUN: %clang %clangopts -o %t1.3.o %p/Inputs/3.c -c RUN: %ar cr %aropts %t1.lib2.a %t1.2.o -RUN: %link -MapStyle txt %linkopts -o %t1.a.out %t1.1.o %t1.lib2.a %t1.3.o -Map %t1.a.map.txt --MapDetail show-symbol-resolution -RUN: %filecheck %s --check-prefix ARCHIVE_FIRST < %t1.a.map.txt -RUN: %link -MapStyle txt %linkopts -o %t1.b.out %t1.1.o %t1.3.o %t1.lib2.a -Map %t1.b.map.txt --MapDetail show-symbol-resolution -RUN: %filecheck %s --check-prefix ARCHIVE_LATER < %t1.b.map.txt +RUN: %link %linkopts -o %t1.a.out %t1.1.o %t1.lib2.a %t1.3.o --emit-symbol-resolution-report %t1.a.json +RUN: %python %eld_src_tools_root/SymbolResolutionInspector/SymbolResolutionInspector.py %t1.a.json | %filecheck %s --check-prefix ARCHIVE_FIRST +RUN: %link %linkopts -o %t1.b.out %t1.1.o %t1.3.o %t1.lib2.a --emit-symbol-resolution-report %t1.b.json +RUN: %python %eld_src_tools_root/SymbolResolutionInspector/SymbolResolutionInspector.py %t1.b.json | %filecheck %s --check-prefix ARCHIVE_LATER -ARCHIVE_FIRST: # Symbol Resolution: -ARCHIVE_FIRST fn -ARCHIVE_FIRST fn({{.*}}1.o:.text) [Size={{.*}}, Def, Global, Function] [Selected] -ARCHIVE_FIRST foo -ARCHIVE_FIRST foo({{.*}}1.o) [Size={{.}}, Undef, Global, NoType] -ARCHIVE_FIRST foo({{.*}}lib2.a({{.*}}2.o):.text) [Size={{.*}}, Def, Global, Function] [Selected] -ARCHIVE_FIRST foo({{.*}}3.o:.text) [Size={{.*}}, Def, Weak, Function] +ARCHIVE_FIRST: fn +ARCHIVE_FIRST: fn({{.*}}1.o:.text) [Size={{.*}}, Def, Global, Function, Default] [Selected] +ARCHIVE_FIRST: foo +ARCHIVE_FIRST: foo({{.*}}1.o) [Size={{.*}}, Undef, Global, NoType, Default] +ARCHIVE_FIRST: foo({{.*}}lib2.a({{.*}}2.o):.text) [Size={{.*}}, Def, Global, Function, Default] [Selected] +ARCHIVE_FIRST: foo({{.*}}3.o:.text) [Size={{.*}}, Def, Weak, Function, Default] -ARCHIVE_LATER: # Symbol Resolution: ARCHIVE_LATER: fn -ARCHIVE_LATER: fn({{.*}}1.o:.text) [Size={{.*}}, Def, Global, Function] [Selected] +ARCHIVE_LATER: fn({{.*}}1.o:.text) [Size={{.*}}, Def, Global, Function, Default] [Selected] ARCHIVE_LATER: foo -ARCHIVE_LATER: foo({{.*}}1.o) [Size={{.*}}, Undef, Global, NoType] -ARCHIVE_LATER: foo({{.*}}3.o:.text) [Size={{.*}}, Def, Weak, Function] [Selected] +ARCHIVE_LATER: foo({{.*}}1.o) [Size={{.*}}, Undef, Global, NoType, Default] +ARCHIVE_LATER: foo({{.*}}3.o:.text) [Size={{.*}}, Def, Weak, Function, Default] [Selected] diff --git a/test/Common/standalone/SymbolResolutionReport/CommonSymbols/CommonSymbols.test b/test/Common/standalone/SymbolResolutionReport/CommonSymbols/CommonSymbols.test index 081464b78..879525c78 100644 --- a/test/Common/standalone/SymbolResolutionReport/CommonSymbols/CommonSymbols.test +++ b/test/Common/standalone/SymbolResolutionReport/CommonSymbols/CommonSymbols.test @@ -8,15 +8,15 @@ RUN: %clang %clangopts -fcommon -o %t1.1.o %p/Inputs/1.c -c RUN: %clang %clangopts -fcommon -o %t1.2.o %p/Inputs/2.c -c RUN: %clang %clangopts -fcommon -o %t1.3.o %p/Inputs/3.c -c RUN: %clang %clangopts -fcommon -o %t1.4.o %p/Inputs/4.c -c -RUN: %link -MapStyle txt %linkopts -o %t1.var.out %t1.1.o %t1.2.o %t1.3.o %t1.4.o -Map %t1.var.map.txt --MapDetail show-symbol-resolution -RUN: %filecheck %s < %t1.var.map.txt +RUN: %link %linkopts -o %t1.var.out %t1.1.o %t1.2.o %t1.3.o %t1.4.o --emit-symbol-resolution-report %t1.var.json +RUN: %python %eld_src_tools_root/SymbolResolutionInspector/SymbolResolutionInspector.py %t1.var.json | %filecheck %s -CHECK: # Symbol Resolution: CHECK: var -CHECK-NEXT: var({{.*}}1.o) [Size={{.*}}, Common, Global, Object] -CHECK-NEXT: var({{.*}}2.o) [Size={{.*}}, Common, Global, Object] -CHECK-NEXT: var({{.*}}3.o) [Size={{.*}}, Common, Global, Object] [Selected] -CHECK-NEXT: var({{.*}}4.o:{{.*}}) [Size={{.*}}, Def, Weak, Object] +CHECK-NEXT: Selected: {{.*}}3.o(var) +CHECK-NEXT: var({{.*}}1.o) [Size={{.*}}, Common, Global, Object, Default] +CHECK-NEXT: var({{.*}}2.o) [Size={{.*}}, Common, Global, Object, Default] +CHECK-NEXT: var({{.*}}3.o) [Size={{.*}}, Common, Global, Object, Default] [Selected] +CHECK-NEXT: var({{.*}}4.o:{{.*}}) [Size={{.*}}, Def, Weak, Object, Default] diff --git a/test/Common/standalone/SymbolResolutionReport/LinkerScriptSymbols/LinkerScriptSymbols.test b/test/Common/standalone/SymbolResolutionReport/LinkerScriptSymbols/LinkerScriptSymbols.test index 0de023712..80d93cd4c 100644 --- a/test/Common/standalone/SymbolResolutionReport/LinkerScriptSymbols/LinkerScriptSymbols.test +++ b/test/Common/standalone/SymbolResolutionReport/LinkerScriptSymbols/LinkerScriptSymbols.test @@ -5,19 +5,18 @@ #END_COMMENT RUN: %clang %clangopts -o %t1.1.o %p/Inputs/1.c -c RUN: %clang %clangopts -o %t1.2.o %p/Inputs/2.c -c -RUN: %link -MapStyle txt %linkopts -o %t1.a.out %t1.1.o %t1.2.o %p/Inputs/script1.t %p/Inputs/script2.t -Map %t1.a.map.txt --MapDetail show-symbol-resolution -RUN: %filecheck %s < %t1.a.map.txt +RUN: %link %linkopts -o %t1.a.out %t1.1.o %t1.2.o %p/Inputs/script1.t %p/Inputs/script2.t --emit-symbol-resolution-report %t1.a.json +RUN: %python %eld_src_tools_root/SymbolResolutionInspector/SymbolResolutionInspector.py %t1.a.json | %filecheck %s -CHECK: # Symbol Resolution: CHECK-DAG: bar -CHECK-DAG: bar({{.*}}2.o:{{.*}}) [Size={{.*}}, Def, Global, Object] [Selected] +CHECK-DAG: bar({{.*}}2.o:{{.*}}) [Size={{.*}}, Def, Global, Object, Default] [Selected] CHECK-DAG: foo -CHECK-DAG: foo({{.*}}1.o:{{.*}}) [Size=4, Def, Global, Object] -CHECK-DAG: foo({{.*}}2.o) [Size=0, Undef, Global, NoType] -CHECK-DAG: foo({{.*}}script1.t) [Size=4, Abs, Object] -CHECK-DAG: foo({{.*}}script1.t) [Size=4, Abs, Object] -CHECK-DAG: foo({{.*}}script2.t) [Size=4, Abs, Object] -CHECK-DAG: foo({{.*}}script2.t) [Size=4, Abs, Object] [Selected] +CHECK-DAG: foo({{.*}}1.o:{{.*}}) [Size=4, Def, Global, Object, Default] +CHECK-DAG: foo({{.*}}2.o) [Size=0, Undef, Global, NoType, Default] +CHECK-DAG: foo({{.*}}script1.t) [Size=4, Abs, {{.*}}, Object, Default] +CHECK-DAG: foo({{.*}}script1.t) [Size=4, Abs, {{.*}}, Object, Default] +CHECK-DAG: foo({{.*}}script2.t) [Size=4, Abs, {{.*}}, Object, Default] +CHECK-DAG: foo({{.*}}script2.t) [Size=4, Abs, {{.*}}, Object, Default] [Selected] diff --git a/test/Common/standalone/SymbolResolutionReport/PluginSymbols/PluginSymbols.test b/test/Common/standalone/SymbolResolutionReport/PluginSymbols/PluginSymbols.test index f353d6833..8a31f1df2 100644 --- a/test/Common/standalone/SymbolResolutionReport/PluginSymbols/PluginSymbols.test +++ b/test/Common/standalone/SymbolResolutionReport/PluginSymbols/PluginSymbols.test @@ -3,13 +3,12 @@ # This test checks the symbol resolution report when there are # plugin-inserted symbols. RUN: %clang %clangopts -o %t1.1.o %p/Inputs/1.c -c -ffunction-sections -RUN: %link -MapStyle txt %linkopts -o %t1.1.out %t1.1.o -L%libsdir/test -T %p/Inputs/script.t -Map %t1.1.map.txt --MapDetail show-symbol-resolution -RUN: %filecheck %s < %t1.1.map.txt +RUN: %link %linkopts -o %t1.1.out %t1.1.o -L%libsdir/test -T %p/Inputs/script.t --emit-symbol-resolution-report %t1.1.json +RUN: %python %eld_src_tools_root/SymbolResolutionInspector/SymbolResolutionInspector.py %t1.1.json | %filecheck %s -CHECK: Symbol Resolution: CHECK: start_of_foo -CHECK: start_of_foo(Plugin[PluginSymbols]:.text.foo) [Size=0, Def, Local, NoType] [Selected] +CHECK: start_of_foo({{.*}}[PluginSymbols]:.text.foo) [Size=0, Def, Local, NoType, Default] [Selected] CHECK: end_of_foo -CHECK: end_of_foo(Plugin[PluginSymbols]:.text.foo) [Size=0, Def, Local, NoType] [Selected] +CHECK: end_of_foo({{.*}}[PluginSymbols]:.text.foo) [Size=0, Def, Local, NoType, Default] [Selected] CHECK: foo -CHECK: foo({{.*}}1.o:.text.foo) [Size={{.*}}, Def, Global, Function] [Selected] +CHECK: foo({{.*}}1.o:.text.foo) [Size={{.*}}, Def, Global, Function, Default] [Selected] diff --git a/test/Common/standalone/SymbolResolutionReport/RemovedMapDetailOption/RemovedMapDetailOption.test b/test/Common/standalone/SymbolResolutionReport/RemovedMapDetailOption/RemovedMapDetailOption.test new file mode 100644 index 000000000..68e740f1a --- /dev/null +++ b/test/Common/standalone/SymbolResolutionReport/RemovedMapDetailOption/RemovedMapDetailOption.test @@ -0,0 +1,11 @@ +# Verify that the old --MapDetail show-symbol-resolution sub-option is +# removed. Symbol resolution reports are now emitted via +# --emit-symbol-resolution-report. The old sub-option must be rejected and +# must not appear in the help output. +RUN: %clang %clangopts -o %t1.o %p/../CommonSymbols/Inputs/3.c -c +RUN: %not %link %linkopts -o %t1.out %t1.o -MapStyle txt -Map %t1.map.txt --MapDetail show-symbol-resolution 2>&1 | %filecheck %s --check-prefix=REJECT +RUN: %link --help 2>&1 | %filecheck %s --check-prefix=HELP + +REJECT: Invalid option show-symbol-resolution specified for --MapDetail + +HELP-NOT: show-symbol-resolution diff --git a/test/Common/standalone/SymbolResolutionReport/SharedLib/SharedLibs.test b/test/Common/standalone/SymbolResolutionReport/SharedLib/SharedLibs.test index 2902185e2..d67a8c6d1 100644 --- a/test/Common/standalone/SymbolResolutionReport/SharedLib/SharedLibs.test +++ b/test/Common/standalone/SymbolResolutionReport/SharedLib/SharedLibs.test @@ -5,44 +5,47 @@ RUN: %clang %clangopts -o %t1.1.o %p/Inputs/1.c -c -fPIC RUN: %clang %clangopts -o %t1.2.o %p/Inputs/2.c -c -fPIC RUN: %clang %clangopts -o %t1.3.o %p/Inputs/3.c -c -RUN: %link -MapStyle txt %linkopts -o %t1.lib1.so %t1.1.o -shared -Map %t1.lib1.map.txt --MapDetail show-symbol-resolution -RUN: %filecheck %s --check-prefix=LIB1 < %t1.lib1.map.txt -RUN: %link -MapStyle txt %linkopts -o %t1.lib2.so %t1.2.o -shared -Map %t1.lib2.map.txt --MapDetail show-symbol-resolution -RUN: %filecheck %s --check-prefix=LIB2 < %t1.lib2.map.txt -RUN: %link -MapStyle txt %linkopts -dy -o %t1.3.out %t1.3.o %t1.lib1.so %t1.lib2.so -Map %t1.3.map.txt --MapDetail show-symbol-resolution -RUN: %filecheck %s < %t1.3.map.txt +RUN: %link %linkopts -o %t1.lib1.so %t1.1.o -shared --emit-symbol-resolution-report %t1.lib1.json +RUN: %python %eld_src_tools_root/SymbolResolutionInspector/SymbolResolutionInspector.py %t1.lib1.json | %filecheck %s --check-prefix=LIB1 +RUN: %link %linkopts -o %t1.lib2.so %t1.2.o -shared --emit-symbol-resolution-report %t1.lib2.json +RUN: %python %eld_src_tools_root/SymbolResolutionInspector/SymbolResolutionInspector.py %t1.lib2.json | %filecheck %s --check-prefix=LIB2 +RUN: %link %linkopts -dy -o %t1.3.out %t1.3.o %t1.lib1.so %t1.lib2.so --emit-symbol-resolution-report %t1.3.json +RUN: %python %eld_src_tools_root/SymbolResolutionInspector/SymbolResolutionInspector.py %t1.3.json | %filecheck %s -LIB1: # Symbol Resolution: LIB1: foo -LIB1-NEXT: foo({{.*}}1.o:.text) [Size={{.*}}, Def, Global, Function] [Selected] +LIB1-NEXT: Selected: {{.*}}1.o(foo) +LIB1-NEXT: foo({{.*}}1.o:.text) [Size={{.*}}, Def, Global, Function, Default] [Selected] LIB1: bar +LIB1-NEXT: Selected: {{.*}}1.o(bar) LIB1-NEXT: bar({{.*}}1.o:.text) [Size={{.*}}, Def, Global, Function, Hidden] [Selected] LIB1: baz -LIB1-NEXT: baz({{.*}}1.o:.text) [Size={{.*}}, Def, Global, Function] [Selected] +LIB1-NEXT: Selected: {{.*}}1.o(baz) +LIB1-NEXT: baz({{.*}}1.o:.text) [Size={{.*}}, Def, Global, Function, Default] [Selected] -LIB2: # Symbol Resolution: LIB2: foo -LIB2-NEXT: foo({{.*}}.2.o:.text) [Size={{.*}}, Def, Global, Function] [Selected] +LIB2-NEXT: Selected: {{.*}}2.o(foo) +LIB2-NEXT: foo({{.*}}.2.o:.text) [Size={{.*}}, Def, Global, Function, Default] [Selected] LIB2: bar -LIB2-NEXT: bar({{.*}}.2.o:.text) [Size={{.*}}, Def, Global, Function] [Selected] +LIB2-NEXT: Selected: {{.*}}2.o(bar) +LIB2-NEXT: bar({{.*}}.2.o:.text) [Size={{.*}}, Def, Global, Function, Default] [Selected] LIB2: baz -LIB2-NEXT: baz({{.*}}.2.o:.text) [Size={{.*}}, Def, Global, Function] [Selected] +LIB2-NEXT: Selected: {{.*}}2.o(baz) +LIB2-NEXT: baz({{.*}}.2.o:.text) [Size={{.*}}, Def, Global, Function, Default] [Selected] -CHECK: # Symbol Resolution: CHECK-DAG: foo -CHECK-DAG: foo({{.*}}3.o) [Size=0, Undef, Global, NoType] -CHECK-DAG: foo({{.*}}lib1.so) [Size={{.*}}, Def, Global, Function] [Selected] -CHECK-DAG: foo({{.*}}lib2.so) [Size={{.*}}, Def, Global, Function] +CHECK-DAG: foo({{.*}}3.o) [Size=0, Undef, Global, NoType, Default] +CHECK-DAG: foo({{.*}}lib1.so) [Size={{.*}}, Def, Global, Function, Default] [Selected] +CHECK-DAG: foo({{.*}}lib2.so) [Size={{.*}}, Def, Global, Function, Default] CHECK-DAG: baz -CHECK-DAG: baz({{.*}}3.o) [Size=0, Undef, Global, NoType] -CHECK-DAG: baz({{.*}}lib1.so) [Size={{.*}}, Def, Global, Function] [Selected] -CHECK-DAG: baz({{.*}}lib2.so) [Size={{.*}}, Def, Global, Function] +CHECK-DAG: baz({{.*}}3.o) [Size=0, Undef, Global, NoType, Default] +CHECK-DAG: baz({{.*}}lib1.so) [Size={{.*}}, Def, Global, Function, Default] [Selected] +CHECK-DAG: baz({{.*}}lib2.so) [Size={{.*}}, Def, Global, Function, Default] CHECK-DAG: bar -CHECK-DAG: bar({{.*}}3.o:.text) [Size={{.*}}, Def, Global, Function] [Selected] -CHECK-DAG: bar({{.*}}lib2.so) [Size={{.*}}, Def, Global, Function] +CHECK-DAG: bar({{.*}}3.o:.text) [Size={{.*}}, Def, Global, Function, Default] [Selected] +CHECK-DAG: bar({{.*}}lib2.so) [Size={{.*}}, Def, Global, Function, Default] CHECK-DAG: main -CHECK-DAG: main({{.*}}3.o:.text) [Size={{.*}}, Def, Global, Function] [Selected] +CHECK-DAG: main({{.*}}3.o:.text) [Size={{.*}}, Def, Global, Function, Default] [Selected] diff --git a/test/Common/standalone/SymbolResolutionReport/WeakVsCommonVsGlobal/WeakVsCommonVsGlobal.test b/test/Common/standalone/SymbolResolutionReport/WeakVsCommonVsGlobal/WeakVsCommonVsGlobal.test index 9d3e8db8b..211103162 100644 --- a/test/Common/standalone/SymbolResolutionReport/WeakVsCommonVsGlobal/WeakVsCommonVsGlobal.test +++ b/test/Common/standalone/SymbolResolutionReport/WeakVsCommonVsGlobal/WeakVsCommonVsGlobal.test @@ -9,17 +9,18 @@ RUN: %clang %clangopts -fcommon -o %t1.1.o %p/Inputs/1.c -c RUN: %clang %clangopts -fcommon -o %t1.2.o %p/Inputs/2.c -c RUN: %clang %clangopts -fcommon -o %t1.3.o %p/Inputs/3.c -c RUN: %clang %clangopts -fcommon -o %t1.4.o %p/Inputs/4.c -c -RUN: %link -MapStyle txt %linkopts -o %t1.foo.out %t1.0.o %t1.1.o %t1.2.o %t1.3.o %t1.4.o -Map %t1.foo.map.txt --MapDetail show-symbol-resolution -RUN: %filecheck %s < %t1.foo.map.txt +RUN: %link %linkopts -o %t1.foo.out %t1.0.o %t1.1.o %t1.2.o %t1.3.o %t1.4.o --emit-symbol-resolution-report %t1.foo.json +RUN: %python %eld_src_tools_root/SymbolResolutionInspector/SymbolResolutionInspector.py %t1.foo.json | %filecheck %s -CHECK: # Symbol Resolution: CHECK: fn -CHECK-NEXT: fn({{.*}}0.o:.text) [Size={{.*}}, Def, Global, Function] [Selected] +CHECK-NEXT: Selected: {{.*}}0.o(fn) +CHECK-NEXT: fn({{.*}}0.o:.text) [Size={{.*}}, Def, Global, Function, Default] [Selected] CHECK: foo -CHECK-NEXT: foo({{.*}}0.o) [Size={{.*}}, Undef, Global, NoType] -CHECK-NEXT: foo({{.*}}1.o:{{.*}}) [Size={{.*}}, Def, Global, Object] [Selected] -CHECK-NEXT: foo({{.*}}2.o:{{.*}}) [Size={{.*}}, Def, Weak, Object] -CHECK-NEXT: foo({{.*}}3.o:{{.*}}) [Size={{.*}}, Def, Weak, Object] -CHECK-NEXT: foo({{.*}}4.o) [Size={{.*}}, Common, Global, Object] +CHECK-NEXT: Selected: {{.*}}1.o(foo) +CHECK-NEXT: foo({{.*}}0.o) [Size={{.*}}, Undef, Global, NoType, Default] +CHECK-NEXT: foo({{.*}}1.o:{{.*}}) [Size={{.*}}, Def, Global, Object, Default] [Selected] +CHECK-NEXT: foo({{.*}}2.o:{{.*}}) [Size={{.*}}, Def, Weak, Object, Default] +CHECK-NEXT: foo({{.*}}3.o:{{.*}}) [Size={{.*}}, Def, Weak, Object, Default] +CHECK-NEXT: foo({{.*}}4.o) [Size={{.*}}, Common, Global, Object, Default] diff --git a/test/Common/standalone/SymbolResolutionReport/WrapSymbol/WrapSymbol.test b/test/Common/standalone/SymbolResolutionReport/WrapSymbol/WrapSymbol.test index c7224ea0a..57a3aad82 100644 --- a/test/Common/standalone/SymbolResolutionReport/WrapSymbol/WrapSymbol.test +++ b/test/Common/standalone/SymbolResolutionReport/WrapSymbol/WrapSymbol.test @@ -5,19 +5,18 @@ UNSUPPORTED: x86 #END_COMMENT RUN: %clang %clangopts -o %t1.1.o %p/Inputs/1.c -c RUN: %clang %clangopts -o %t1.2.o %p/Inputs/2.c -c -RUN: %link -MapStyle txt %linkopts -o %t1.a.out %t1.1.o %t1.2.o -Map %t1.a.map.txt --MapDetail show-symbol-resolution --wrap=foo -RUN: %filecheck %s < %t1.a.map.txt +RUN: %link %linkopts -o %t1.a.out %t1.1.o %t1.2.o --emit-symbol-resolution-report %t1.a.json --wrap=foo +RUN: %python %eld_src_tools_root/SymbolResolutionInspector/SymbolResolutionInspector.py %t1.a.json | %filecheck %s -CHECK: # Symbol Resolution: CHECK-DAG: bar -CHECK-DAG: bar({{.*}}1.o:.text) [Size={{.*}}, Def, Global, Function] [Selected] +CHECK-DAG: bar({{.*}}1.o:.text) [Size={{.*}}, Def, Global, Function, Default] [Selected] CHECK-DAG: baz -CHECK-DAG: baz({{.*}}1.o:.text) [Size={{.*}}, Def, Global, Function] [Selected] +CHECK-DAG: baz({{.*}}1.o:.text) [Size={{.*}}, Def, Global, Function, Default] [Selected] CHECK-DAG: foo -CHECK-DAG: foo({{.*}}1.o) [Size=0, Undef, Global, NoType] -CHECK-DAG: foo({{.*}}2.o:.text) [Size={{.*}}, Def, Global, Function] [Selected] +CHECK-DAG: foo({{.*}}1.o) [Size=0, Undef, Global, NoType, Default] +CHECK-DAG: foo({{.*}}2.o:.text) [Size={{.*}}, Def, Global, Function, Default] [Selected] CHECK-DAG: asdf -CHECK-DAG: asdf({{.*}}2.o:.text) [Size={{.*}}, Def, Global, Function] [Selected] +CHECK-DAG: asdf({{.*}}2.o:.text) [Size={{.*}}, Def, Global, Function, Default] [Selected] CHECK-DAG: __wrap_foo -CHECK-DAG: __wrap_foo({{.*}}1.o) [Size=0, Undef, Global, NoType] -CHECK-DAG: __wrap_foo({{.*}}2.o:.text) [Size={{.*}}, Def, Global, Function] [Selected] +CHECK-DAG: __wrap_foo({{.*}}1.o) [Size=0, Undef, Global, NoType, Default] +CHECK-DAG: __wrap_foo({{.*}}2.o:.text) [Size={{.*}}, Def, Global, Function, Default] [Selected] diff --git a/test/lit.cfg b/test/lit.cfg index 2e6938452..f55a69236 100644 --- a/test/lit.cfg +++ b/test/lit.cfg @@ -102,11 +102,13 @@ if eld_obj_root is not None: eld_src_root = getattr(config, 'eld_src_root', None) test_templates_dir = "" +eld_src_tools_root = "" if eld_src_root is not None: # YAML Map Parser path = Path(eld_src_root) / 'utils' / 'YAMLMapParser' yamlmapparser = (path / 'YAMLMapParser.py').as_posix() test_templates_dir = (Path(eld_src_root) / 'templates').as_posix() + eld_src_tools_root = (Path(eld_src_root) / 'tools').as_posix() xarch_mem_trace = ( Path(eld_src_root) / 'utils' @@ -658,6 +660,7 @@ config.substitutions.append( ("%linkdriverdynopts","".join(linkdriverdynopts)) ) config.substitutions.append( ("%libdl","".join(libdl)) ) config.substitutions.append( ("%libsdir","".join(libsdir)) ) config.substitutions.append( ("%eldsrcroot","".join(eld_src_root)) ) +config.substitutions.append( ("%eld_src_tools_root", eld_src_tools_root) ) config.substitutions.append( ("%llvmobjroot","".join(config.llvm_obj_root)) ) config.substitutions.append( ("%linkdriveropts","".join(linkdriveropts)) ) config.substitutions.append( ("%linkdriverg0opts","".join(linkdriverg0opts)) ) diff --git a/tools/SymbolResolutionInspector/SymbolResolutionInspector.py b/tools/SymbolResolutionInspector/SymbolResolutionInspector.py new file mode 100644 index 000000000..e257e77e6 --- /dev/null +++ b/tools/SymbolResolutionInspector/SymbolResolutionInspector.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +# "===----------------------------------------------------------------------=== +# Part of the eld Project, under the BSD License +# See https://github.com/qualcomm/eld/LICENSE.txt for license information. +# SPDX-License-Identifier: BSD-3-Clause +# "===----------------------------------------------------------------------=== +"""Inspect an ELD symbol resolution report (--emit-symbol-resolution-report). + +Reads the JSON report and lets you filter symbols by name or by the +per-candidate info string. + +--symbol selects whole symbols by name. --filter selects individual +candidates by their info string: a symbol is shown only if at least one of +its candidates matches, and only the matching candidates are printed (the +non-matching candidates of that symbol are dropped from both the text and +JSON output). +""" + +import argparse +import json +import re +import sys + +KNOWN_VERSION = 1 + + +def candidate_info_string(c): + """Create a candidate's info string, matching the linker's rendering. + + For a candidate that has every property set, the result looks like: + + foo(a.bc[LTOPlugin]:.text) [Size=16, bitcode, Def, SB_Global, Function, Default] + + where the parenthesized part is InputFile[Plugin]:Section and the + bracketed part is Size, an optional "bitcode" marker, SectionIndexKind, + Binding, Type, and Visibility. + """ + loc = c["InputFile"] + if c.get("Plugin"): + loc += "[" + c["Plugin"] + "]" + if c.get("Section"): + loc += ":" + c["Section"] + attrs = ["Size={}".format(c.get("Size", 0))] + if c.get("Bitcode"): + attrs.append("bitcode") + attrs += [c["SectionIndexKind"], c["Binding"], c["Type"], c["Visibility"]] + return "{name}({loc}) [{attrs}]".format( + name=c["Name"], loc=loc, attrs=", ".join(attrs) + ) + + +def select_symbol(sym, name_re, filter_re): + """Return the symbol to display, or None if it does not match. + + --symbol matches the whole symbol by Name. --filter is candidate-level: + the returned symbol keeps only the candidates whose info string matches, + and the symbol is dropped entirely if none match. + """ + if name_re and not name_re.search(sym["Name"]): + return None + if not filter_re: + return sym + cands = [ + c + for c in sym.get("Candidates", []) + if filter_re.search(candidate_info_string(c)) + ] + if not cands: + return None + filtered = dict(sym) + filtered["Candidates"] = cands + return filtered + + +def print_symbol(sym): + print(sym["Name"]) + if sym.get("Selected"): + print("\tSelected: {}".format(sym["Selected"])) + for c in sym.get("Candidates", []): + mark = " [Selected]" if c.get("IsSelected") else "" + print("\t{}{}".format(candidate_info_string(c), mark)) + lto = c.get("LTOObjectSymbol") + if lto: + print("\t\tLTO: {}".format(candidate_info_string(lto))) + + +def main(): + p = argparse.ArgumentParser(description=__doc__) + p.add_argument("report", help="JSON report from --emit-symbol-resolution-report") + p.add_argument("--symbol", help="regex selecting whole symbols by Name") + p.add_argument( + "--filter", + help="regex matched against each candidate's info string; " + "shows only the matching candidates.", + ) + p.add_argument("--json", action="store_true", help="emit matching symbols as JSON") + args = p.parse_args() + + with open(args.report) as f: + report = json.load(f) + + version = report.get("SymbolResolutionReportVersion") + if version is not None and version > KNOWN_VERSION: + sys.stderr.write( + "warning: report version {} is newer than this tool ({})\n".format( + version, KNOWN_VERSION + ) + ) + + name_re = re.compile(args.symbol) if args.symbol else None + filter_re = re.compile(args.filter) if args.filter else None + + matched = [ + m + for m in ( + select_symbol(s, name_re, filter_re) for s in report.get("Symbols", []) + ) + if m is not None + ] + + if args.json: + json.dump( + {"SymbolResolutionReportVersion": version, "Symbols": matched}, + sys.stdout, + indent=2, + ) + sys.stdout.write("\n") + else: + for s in matched: + print_symbol(s) + print( + "{} of {} symbols matched".format( + len(matched), len(report.get("Symbols", [])) + ) + ) + + +if __name__ == "__main__": + main()