[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