[llvm] [llvm-readobj] Dump callgraph section info for ELF (PR #157499)
Paul Kirth via llvm-commits
llvm-commits at lists.llvm.org
Mon Sep 22 14:06:11 PDT 2025
================
@@ -5260,6 +5307,247 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCGProfile() {
OS << "GNUStyle::printCGProfile not implemented\n";
}
+template <class ELFT>
+static std::optional<object::SectionRef>
+getCallGraphSection(const object::ELFObjectFile<ELFT> &ObjF) {
+ // Get the .callgraph section.
+ StringRef CallGraphSectionName(".callgraph");
+ std::optional<object::SectionRef> CallGraphSection;
+ for (auto Sec : ObjF.sections()) {
+ StringRef Name;
+ if (Expected<StringRef> NameOrErr = Sec.getName())
+ Name = *NameOrErr;
+ else
+ consumeError(NameOrErr.takeError());
+
+ if (Name == CallGraphSectionName)
+ return Sec;
+ }
+ return CallGraphSection;
+}
+
+template <class ELFT> bool ELFDumper<ELFT>::processCallGraphSection() {
+ const Elf_Shdr *CGSection = findSectionByName(".callgraph");
+ if (!CGSection) {
+ Error NoSectionErr = createError("No .callgraph section found.");
+ reportError(std::move(NoSectionErr), "Missing section");
+ }
+
+ Expected<ArrayRef<uint8_t>> SectionBytesOrErr =
+ Obj.getSectionContents(*CGSection);
+ if (!SectionBytesOrErr) {
+ Error SectionReadErr = SectionBytesOrErr.takeError();
+ reportError(std::move(SectionReadErr),
+ "Unable to read the .callgraph section");
+ }
+
+ auto PrintMalformedError = [&](Error &E, Twine FuncPC, StringRef Component) {
+ // auto Msg = llvm::Twine("Malformed callgraph section while reading [") +
+ // Component + llvm::Twine("] .\n");
+ std::string Msg =
+ (StringRef("While reading call graph info's [") + Component +
+ StringRef("] for function at [0x") + StringRef(FuncPC.str()) + "]")
+ .str();
+ reportError(std::move(E), StringRef(Msg));
+ };
+
+ DataExtractor Data(SectionBytesOrErr.get(), Obj.isLE(),
+ ObjF.getBytesInAddress());
+
+ uint64_t NotListedCount = 0;
+ uint64_t UnknownCount = 0;
+
+ uint64_t Offset = 0;
+ while (Offset < CGSection->sh_size) {
+ Error CGSectionErr = Error::success();
+ // Format version number.
+ uint64_t FormatVersionNumber = Data.getU64(&Offset, &CGSectionErr);
+
+ if (CGSectionErr)
+ reportError(std::move(CGSectionErr),
+ "While reading call graph info FormatVersionNumber");
+
+ if (FormatVersionNumber != 0) {
+ Error FormatErr = createError("Unknown format version value [" +
+ std::to_string(FormatVersionNumber) +
+ "] in .callgraph section.");
+ reportError(std::move(FormatErr), "Unknown value");
+ }
+
+ // Read function address.
+ typename ELFT::uint FuncAddr =
+ Data.getUnsigned(&Offset, sizeof(FuncAddr), &CGSectionErr);
+ if (CGSectionErr)
+ reportError(std::move(CGSectionErr),
+ "While reading call graph info function entry PC");
+
+ if (FuncCGInfos.find(FuncAddr) != FuncCGInfos.end()) {
+ Error DuplicatePcErr =
+ createError("for function PC: 0x" + Twine::utohexstr(FuncAddr));
+ reportError(std::move(DuplicatePcErr), "Duplicate call graph entry");
+ }
+
+ using FunctionCallgraphInfo =
+ ::FunctionCallgraphInfoImpl<typename ELFT::uint>;
+
+ // Create a new entry for this function.
+ FunctionCallgraphInfo CGInfo;
+ CGInfo.FormatVersionNumber = FormatVersionNumber;
+
+ // Read function kind.
+ uint64_t KindVal = Data.getU64(&Offset, &CGSectionErr);
+ if (CGSectionErr)
+ PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr), "Kind");
+
+ if (KindVal > 3) {
+ Error KindErr =
+ createError("Unknown value [" + std::to_string(KindVal) + "].");
+ PrintMalformedError(KindErr, Twine::utohexstr(FuncAddr), "FunctionKind");
+ }
+
+ FunctionKind Kind = static_cast<FunctionKind>(KindVal);
+ CGInfo.Kind = Kind;
+ if (Kind == FunctionKind::INDIRECT_TARGET_KNOWN_TID) {
+ // Read type id if this function is an indirect call target.
+ uint64_t TypeId = Data.getU64(&Offset, &CGSectionErr);
+ if (CGSectionErr)
+ PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+ "indirect type id");
+ CGInfo.FunctionTypeId = TypeId;
+ }
+ if (Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID)
+ UnknownCount++;
+ if (Kind == FunctionKind::NOT_LISTED)
+ NotListedCount++;
+
+ // Read number of indirect call sites for this function.
+ uint64_t NumIndirectCallsites = Data.getU64(&Offset, &CGSectionErr);
+ if (CGSectionErr)
+ PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+ "number of indirect callsites");
+
+ for (unsigned long I = 0; I < NumIndirectCallsites; I++) {
+ uint64_t TypeId = Data.getU64(&Offset, &CGSectionErr);
+ if (CGSectionErr)
+ PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+ "indirect target type id");
+ typename ELFT::uint CallSitePc =
+ Data.getUnsigned(&Offset, sizeof(CallSitePc), &CGSectionErr);
+ if (CGSectionErr)
+ PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+ "indirect callsite PC");
+ CGInfo.IndirectCallsites.try_emplace(CallSitePc, TypeId);
+ }
+
+ // Read number of direct call sites for this function.
+ uint64_t NumDirectCallees = Data.getU64(&Offset, &CGSectionErr);
+ if (CGSectionErr)
+ PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+ "number of direct callsites");
+ // Read direct call sites and populate FuncCGInfos.
+ for (uint64_t I = 0; I < NumDirectCallees; ++I) {
+ typename ELFT::uint Callee =
+ Data.getUnsigned(&Offset, sizeof(Callee), &CGSectionErr);
+ if (CGSectionErr)
+ PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+ "direct callee PC");
+ CGInfo.DirectCallees.insert(Callee);
+ }
+ FuncCGInfos[FuncAddr] = CGInfo;
+ }
+
+ if (NotListedCount)
+ reportUniqueWarning(".callgraph section does not have information for " +
+ std::to_string(NotListedCount) + " functions.");
+ if (UnknownCount)
+ reportUniqueWarning(".callgraph section has unknown type id for " +
+ std::to_string(UnknownCount) + " indirect targets.");
+
+ // Sort function info by function PC.
+ llvm::sort(FuncCGInfos,
+ [](const auto &A, const auto &B) { return A.first < B.first; });
+ return true;
+}
+
+static StringRef GetFuntionKindString(FunctionKind Kind) {
+ switch (Kind) {
+ case FunctionKind::NOT_INDIRECT_TARGET:
+ return "NOT_INDIRECT";
+ case FunctionKind::INDIRECT_TARGET_UNKNOWN_TID:
+ return "UNKNOWN_TID";
+ case FunctionKind::INDIRECT_TARGET_KNOWN_TID:
+ return "KNOWN_TID";
+ case FunctionKind::NOT_LISTED:
+ return "NO_INFO";
+ }
+ llvm_unreachable("Unknown FunctionKind.");
+}
+
+template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
+ if (!this->processCallGraphSection())
+ return;
+ if (this->FuncCGInfos.size() == 0)
+ return;
+ using FunctionCallgraphInfo =
+ ::FunctionCallgraphInfoImpl<typename ELFT::uint>;
+
+ auto GetFunctionName = [&](typename ELFT::uint EntryPc) {
+ SmallVector<uint32_t> FuncSymIndexes =
+ this->getSymbolIndexesForFunctionAddress(EntryPc, std::nullopt);
+ if (FuncSymIndexes.empty())
+ return std::string("");
+
+ SmallVector<std::string> FuncSymNames;
+ for (uint32_t Index : FuncSymIndexes)
+ FuncSymNames.push_back(this->getStaticSymbolName(Index));
+ return join(FuncSymNames, ", ");
+ };
+
+ OS << "Per-function call graph information:: \n";
+ for (const auto &El : this->FuncCGInfos) {
+ typename ELFT::uint FuncEntryPc = El.first;
+ FunctionCallgraphInfo CGInfo = El.second;
+ std::string FuncSymNames = GetFunctionName(FuncEntryPc);
+ if (!FuncSymNames.empty())
+ OS << "\nFunction:: " << FuncSymNames;
+ OS << "\nFunction PC:: " << format("0x%lx", FuncEntryPc);
+ OS << "\nFormatVersionNumber:: " << CGInfo.FormatVersionNumber;
+ OS << "\nFunction Kind:: " << GetFuntionKindString(CGInfo.Kind);
----------------
ilovepi wrote:
why not use something like a list scope and the print() APIs, like the later changes?
https://github.com/llvm/llvm-project/pull/157499
More information about the llvm-commits
mailing list