[llvm] [llvm-readobj] Dump callgraph section info for ELF (PR #157499)

Prabhu Rajasekaran via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 8 18:50:39 PDT 2025


https://github.com/Prabhuk updated https://github.com/llvm/llvm-project/pull/157499

>From e2de340cd266fe38bc96a4eaa270fd60be84c49a Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Fri, 5 Sep 2025 17:12:01 -0700
Subject: [PATCH 1/5] [llvm-readobj] Dump callgraph section info for ELF

Introduce a new flag `--call-graph-info` which outputs callgraph ELF
section information to the console as a text output or as JSON output.
---
 llvm/tools/llvm-readobj/ELFDumper.cpp    | 130 +++++++++++++++++++++++
 llvm/tools/llvm-readobj/ObjDumper.h      |   1 +
 llvm/tools/llvm-readobj/Opts.td          |   1 +
 llvm/tools/llvm-readobj/llvm-readobj.cpp |   4 +
 4 files changed, 136 insertions(+)

diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index 7d75f29623ea9..f4b449b0b03c5 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -619,6 +619,7 @@ template <typename ELFT> class GNUELFDumper : public ELFDumper<ELFT> {
   void printVersionDefinitionSection(const Elf_Shdr *Sec) override;
   void printVersionDependencySection(const Elf_Shdr *Sec) override;
   void printCGProfile() override;
+  void printCallGraphInfo() override;
   void printBBAddrMaps(bool PrettyPGOAnalysis) override;
   void printAddrsig() override;
   void printNotes() override;
@@ -730,6 +731,7 @@ template <typename ELFT> class LLVMELFDumper : public ELFDumper<ELFT> {
   void printVersionDefinitionSection(const Elf_Shdr *Sec) override;
   void printVersionDependencySection(const Elf_Shdr *Sec) override;
   void printCGProfile() override;
+  void printCallGraphInfo() override;
   void printBBAddrMaps(bool PrettyPGOAnalysis) override;
   void printAddrsig() override;
   void printNotes() override;
@@ -5217,6 +5219,11 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCGProfile() {
   OS << "GNUStyle::printCGProfile not implemented\n";
 }
 
+template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
+  OS << "GNUStyle::printCallGraphInfo not implemented\n";
+}
+
+
 template <class ELFT>
 void GNUELFDumper<ELFT>::printBBAddrMaps(bool /*PrettyPGOAnalysis*/) {
   OS << "GNUStyle::printBBAddrMaps not implemented\n";
@@ -8048,6 +8055,129 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCGProfile() {
   }
 }
 
+enum class FunctionKind : uint64_t {
+  // Function cannot be target to indirect calls.
+  NOT_INDIRECT_TARGET = 0,
+  // Function may be target to indirect calls but its type id is unknown.
+  INDIRECT_TARGET_UNKNOWN_TID = 1,
+  // Function may be target to indirect calls and its type id is known.
+  INDIRECT_TARGET_KNOWN_TID = 2,
+
+  // Available in the binary but not listed in the call graph section.
+  NOT_LISTED = 3,
+};
+
+struct FunctionInfo {
+  FunctionKind Kind;
+  struct DirectCallSite {
+    uint64_t CallSite;
+    uint64_t Callee;
+    DirectCallSite(uint64_t CallSite, uint64_t Callee)
+        : CallSite(CallSite), Callee(Callee) {}
+  };
+  SmallVector<DirectCallSite> DirectCallSites;
+  SmallVector<uint64_t> IndirectCallSites;
+};
+
+template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
+  // Get the .callgraph section.
+  StringRef CallGraphSectionName(".callgraph");
+  std::optional<object::SectionRef> CallGraphSection;
+  for (auto Sec : this->ObjF.sections()) {
+    StringRef Name;
+    if (Expected<StringRef> NameOrErr = Sec.getName())
+      Name = *NameOrErr;
+    else
+      consumeError(NameOrErr.takeError());
+
+    if (Name == CallGraphSectionName) {
+      CallGraphSection = Sec;
+      break;
+    }
+  }
+  if (!CallGraphSection) {
+    this->reportUniqueWarning("there is no .callgraph section");
+    return;
+  }
+
+  // Map type id to indirect call sites.
+  MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirCallSites;
+  // Map type id to indirect targets.
+  MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirTargets;
+  // Map function entry pc to function info.
+  MapVector<uint64_t, FunctionInfo> FuncInfo;
+  // Instructions that are not indirect calls but have a type id are ignored.
+  // uint64_t IgnoredICallIdCount = 0;
+  // Number of valid indirect calls with type ids.
+  // uint64_t ICallWithTypeIdCount = 0;
+  if (CallGraphSection) {
+    StringRef CGSecContents = cantFail(CallGraphSection.value().getContents());
+    // TODO: some entries are written in pointer size. are they always 64-bit?
+    if (CGSecContents.size() % sizeof(uint64_t))
+      this->reportUniqueWarning(
+                  "Malformed .callgraph section. Unexpected size.");
+
+    size_t Size = CGSecContents.size() / sizeof(uint64_t);
+    auto *It = reinterpret_cast<const uint64_t *>(CGSecContents.data());
+    const auto *const End = It + Size;
+
+    auto CGHasNext = [&]() { return It < End; };
+    auto CGNext = [&]() -> uint64_t {
+      if (!CGHasNext())
+        this->reportUniqueWarning(
+                    "Malformed .callgraph section. Parsing error.");
+      return *It++;
+    };
+
+    // Parse the content
+    while (CGHasNext()) {
+      // Format version number.
+      uint64_t FormatVersionNumber = CGNext();
+      if (FormatVersionNumber != 0)
+        this->reportUniqueWarning(
+                    "Unknown format version in .callgraph section.");
+      // Function entry pc.
+      uint64_t FuncEntryPc = CGNext();
+      // Function kind.
+      uint64_t Kind = CGNext();
+      switch (Kind) {
+      case 0: // not an indirect target
+        FuncInfo[FuncEntryPc].Kind = FunctionKind::NOT_INDIRECT_TARGET;
+        break;
+      case 1: // indirect target with unknown type id
+        FuncInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
+        break;
+      case 2: // indirect target with known type id
+        FuncInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_KNOWN_TID;
+        TypeIdToIndirTargets[CGNext()].push_back(FuncEntryPc);
+        break;
+      default:
+        this->reportUniqueWarning(
+                    "Unknown function kind in .callgraph section.");
+      }
+
+      // Read indirect call sites info.
+      uint64_t IndirectCallSiteCount = CGNext();
+      for (unsigned long I = 0; I < IndirectCallSiteCount; I++) {
+        uint64_t TypeId = CGNext();
+        uint64_t CallSitePc = CGNext();
+        TypeIdToIndirCallSites[TypeId].push_back(CallSitePc);
+        FuncInfo[FuncEntryPc].IndirectCallSites.push_back(CallSitePc);
+      }
+
+      // Read direct call sites info.
+      uint64_t DirectCallSiteCount = CGNext();
+      for (unsigned long I = 0; I < DirectCallSiteCount; I++) {
+        uint64_t CallSitePc = CGNext();
+        uint64_t CalleePc = CGNext();
+        FuncInfo[FuncEntryPc].DirectCallSites.emplace_back(CallSitePc,
+                                                           CalleePc);
+      }
+    }
+  }
+}
+
+
 template <class ELFT>
 void LLVMELFDumper<ELFT>::printBBAddrMaps(bool PrettyPGOAnalysis) {
   bool IsRelocatable = this->Obj.getHeader().e_type == ELF::ET_REL;
diff --git a/llvm/tools/llvm-readobj/ObjDumper.h b/llvm/tools/llvm-readobj/ObjDumper.h
index a654078a770ff..c704a24290b30 100644
--- a/llvm/tools/llvm-readobj/ObjDumper.h
+++ b/llvm/tools/llvm-readobj/ObjDumper.h
@@ -129,6 +129,7 @@ class ObjDumper {
   virtual void printGroupSections() {}
   virtual void printHashHistograms() {}
   virtual void printCGProfile() {}
+  virtual void printCallGraphInfo() {}
   // If PrettyPGOAnalysis is true, prints BFI as relative frequency and BPI as
   // percentage. Otherwise raw values are displayed.
   virtual void printBBAddrMaps(bool PrettyPGOAnalysis) {}
diff --git a/llvm/tools/llvm-readobj/Opts.td b/llvm/tools/llvm-readobj/Opts.td
index 711522c4acb14..209881d67eb0c 100644
--- a/llvm/tools/llvm-readobj/Opts.td
+++ b/llvm/tools/llvm-readobj/Opts.td
@@ -20,6 +20,7 @@ def all : FF<"all", "Equivalent to setting: --file-header, --program-headers, --
 def arch_specific : FF<"arch-specific", "Display architecture-specific information">;
 def bb_addr_map : FF<"bb-addr-map", "Display the BB address map section">;
 def pretty_pgo_analysis_map : FF<"pretty-pgo-analysis-map", "Display PGO analysis values with formatting rather than raw numbers">;
+def call_graph_info : FF<"call-graph-info", "Display call graph information">;
 def cg_profile : FF<"cg-profile", "Display call graph profile section">;
 def decompress : FF<"decompress", "Dump decompressed section content when used with -x or -p">;
 defm demangle : BB<"demangle", "Demangle symbol names", "Do not demangle symbol names (default)">;
diff --git a/llvm/tools/llvm-readobj/llvm-readobj.cpp b/llvm/tools/llvm-readobj/llvm-readobj.cpp
index 2b34761b2cc6c..cb22f95eedbac 100644
--- a/llvm/tools/llvm-readobj/llvm-readobj.cpp
+++ b/llvm/tools/llvm-readobj/llvm-readobj.cpp
@@ -97,6 +97,7 @@ static bool Addrsig;
 static bool All;
 static bool ArchSpecificInfo;
 static bool BBAddrMap;
+static bool CallGraphInfo;
 static bool PrettyPGOAnalysisMap;
 bool ExpandRelocs;
 static bool CGProfile;
@@ -216,6 +217,7 @@ static void parseOptions(const opt::InputArgList &Args) {
   opts::All = Args.hasArg(OPT_all);
   opts::ArchSpecificInfo = Args.hasArg(OPT_arch_specific);
   opts::BBAddrMap = Args.hasArg(OPT_bb_addr_map);
+  opts::CallGraphInfo = Args.hasArg(OPT_call_graph_info);
   opts::PrettyPGOAnalysisMap = Args.hasArg(OPT_pretty_pgo_analysis_map);
   if (opts::PrettyPGOAnalysisMap && !opts::BBAddrMap)
     WithColor::warning(errs(), ToolName)
@@ -474,6 +476,8 @@ static void dumpObject(ObjectFile &Obj, ScopedPrinter &Writer,
       Dumper->printHashHistograms();
     if (opts::CGProfile)
       Dumper->printCGProfile();
+    if (opts::CallGraphInfo)
+      Dumper->printCallGraphInfo();
     if (opts::BBAddrMap)
       Dumper->printBBAddrMaps(opts::PrettyPGOAnalysisMap);
     if (opts::Addrsig)

>From 1f1c2fede557bd105c637e5626c6fd2ccd14e1fe Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Mon, 8 Sep 2025 09:05:21 -0700
Subject: [PATCH 2/5] Format code

---
 llvm/tools/llvm-readobj/ELFDumper.cpp | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index f4b449b0b03c5..e23c95fe1c822 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -5223,7 +5223,6 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
   OS << "GNUStyle::printCallGraphInfo not implemented\n";
 }
 
-
 template <class ELFT>
 void GNUELFDumper<ELFT>::printBBAddrMaps(bool /*PrettyPGOAnalysis*/) {
   OS << "GNUStyle::printBBAddrMaps not implemented\n";
@@ -8115,7 +8114,7 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
     // TODO: some entries are written in pointer size. are they always 64-bit?
     if (CGSecContents.size() % sizeof(uint64_t))
       this->reportUniqueWarning(
-                  "Malformed .callgraph section. Unexpected size.");
+          "Malformed .callgraph section. Unexpected size.");
 
     size_t Size = CGSecContents.size() / sizeof(uint64_t);
     auto *It = reinterpret_cast<const uint64_t *>(CGSecContents.data());
@@ -8125,7 +8124,7 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
     auto CGNext = [&]() -> uint64_t {
       if (!CGHasNext())
         this->reportUniqueWarning(
-                    "Malformed .callgraph section. Parsing error.");
+            "Malformed .callgraph section. Parsing error.");
       return *It++;
     };
 
@@ -8135,7 +8134,7 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
       uint64_t FormatVersionNumber = CGNext();
       if (FormatVersionNumber != 0)
         this->reportUniqueWarning(
-                    "Unknown format version in .callgraph section.");
+            "Unknown format version in .callgraph section.");
       // Function entry pc.
       uint64_t FuncEntryPc = CGNext();
       // Function kind.
@@ -8153,7 +8152,7 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
         break;
       default:
         this->reportUniqueWarning(
-                    "Unknown function kind in .callgraph section.");
+            "Unknown function kind in .callgraph section.");
       }
 
       // Read indirect call sites info.
@@ -8177,7 +8176,6 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
   }
 }
 
-
 template <class ELFT>
 void LLVMELFDumper<ELFT>::printBBAddrMaps(bool PrettyPGOAnalysis) {
   bool IsRelocatable = this->Obj.getHeader().e_type == ELF::ET_REL;

>From 8660e53e21753e1d9e2aa06f34c4066fd654b309 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Mon, 8 Sep 2025 09:16:45 -0700
Subject: [PATCH 3/5] Implement JSON output for printCallGraph()

---
 llvm/tools/llvm-readobj/ELFDumper.cpp | 418 ++++++++++++++++++--------
 1 file changed, 298 insertions(+), 120 deletions(-)

diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index e23c95fe1c822..76783387abdea 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -183,6 +183,31 @@ struct GroupSection {
   std::vector<GroupMember> Members;
 };
 
+// Callgraph section handling implementation.
+enum class FunctionKind : uint64_t {
+  // Function cannot be target to indirect calls.
+  NOT_INDIRECT_TARGET = 0,
+  // Function may be target to indirect calls but its type id is unknown.
+  INDIRECT_TARGET_UNKNOWN_TID = 1,
+  // Function may be target to indirect calls and its type id is known.
+  INDIRECT_TARGET_KNOWN_TID = 2,
+
+  // Available in the binary but not listed in the call graph section.
+  NOT_LISTED = 3,
+};
+
+struct FunctionInfo {
+  FunctionKind Kind;
+  struct DirectCallSite {
+    uint64_t CallSite;
+    uint64_t Callee;
+    DirectCallSite(uint64_t CallSite, uint64_t Callee)
+        : CallSite(CallSite), Callee(Callee) {}
+  };
+  SmallVector<DirectCallSite> DirectCallSites;
+  SmallVector<uint64_t> IndirectCallSites;
+};
+
 namespace {
 
 struct NoteType {
@@ -433,6 +458,14 @@ template <typename ELFT> class ELFDumper : public ObjDumper {
       const typename SFrameParser<ELFT::Endianness>::FDERange::iterator FDE,
       ArrayRef<Relocation<ELFT>> Relocations, const Elf_Shdr *RelocSymTab);
 
+  void callGraphInfoHelper();
+  // Map type id to indirect call sites.
+  MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirCallSites;
+  // Map type id to indirect targets.
+  MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirTargets;
+  // Map function entry pc to function info.
+  MapVector<uint64_t, FunctionInfo> FuncInfo;
+
 private:
   mutable SmallVector<std::optional<VersionEntry>, 0> VersionMap;
 };
@@ -812,6 +845,8 @@ template <typename ELFT> class JSONELFDumper : public LLVMELFDumper<ELFT> {
 
   void printDynamicTable() override;
 
+  void printCallGraphInfo() override;
+
 private:
   void printAuxillaryDynamicTableEntryInfo(const Elf_Dyn &Entry);
 
@@ -5219,8 +5254,189 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCGProfile() {
   OS << "GNUStyle::printCGProfile not implemented\n";
 }
 
+template <class ELFT> void ELFDumper<ELFT>::callGraphInfoHelper() {
+  // Get the .callgraph section.
+  StringRef CallGraphSectionName(".callgraph");
+  std::optional<object::SectionRef> CallGraphSection;
+  for (auto Sec : this->ObjF.sections()) {
+    StringRef Name;
+    if (Expected<StringRef> NameOrErr = Sec.getName())
+      Name = *NameOrErr;
+    else
+      consumeError(NameOrErr.takeError());
+
+    if (Name == CallGraphSectionName) {
+      CallGraphSection = Sec;
+      break;
+    }
+  }
+  if (!CallGraphSection) {
+    this->reportUniqueWarning("there is no .callgraph section");
+    return;
+  }
+
+  // Instructions that are not indirect calls but have a type id are ignored.
+  // uint64_t IgnoredICallIdCount = 0;
+  // Number of valid indirect calls with type ids.
+  // uint64_t ICallWithTypeIdCount = 0;
+  if (CallGraphSection) {
+    StringRef CGSecContents = cantFail(CallGraphSection.value().getContents());
+    // TODO: some entries are written in pointer size. are they always 64-bit?
+    if (CGSecContents.size() % sizeof(uint64_t))
+      this->reportUniqueWarning(
+          "Malformed .callgraph section. Unexpected size.");
+
+    size_t Size = CGSecContents.size() / sizeof(uint64_t);
+    auto *It = reinterpret_cast<const uint64_t *>(CGSecContents.data());
+    const auto *const End = It + Size;
+
+    auto CGHasNext = [&]() { return It < End; };
+    auto CGNext = [&]() -> uint64_t {
+      if (!CGHasNext())
+        this->reportUniqueWarning(
+            "Malformed .callgraph section. Parsing error.");
+      return *It++;
+    };
+
+    // Parse the content
+    while (CGHasNext()) {
+      // Format version number.
+      uint64_t FormatVersionNumber = CGNext();
+      if (FormatVersionNumber != 0)
+        this->reportUniqueWarning(
+            "Unknown format version in .callgraph section.");
+      // Function entry pc.
+      uint64_t FuncEntryPc = CGNext();
+      // Function kind.
+      uint64_t Kind = CGNext();
+      switch (Kind) {
+      case 0: // not an indirect target
+        FuncInfo[FuncEntryPc].Kind = FunctionKind::NOT_INDIRECT_TARGET;
+        break;
+      case 1: // indirect target with unknown type id
+        FuncInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
+        break;
+      case 2: // indirect target with known type id
+        FuncInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_KNOWN_TID;
+        TypeIdToIndirTargets[CGNext()].push_back(FuncEntryPc);
+        break;
+      default:
+        this->reportUniqueWarning(
+            "Unknown function kind in .callgraph section.");
+      }
+
+      // Read indirect call sites info.
+      uint64_t IndirectCallSiteCount = CGNext();
+      for (unsigned long I = 0; I < IndirectCallSiteCount; I++) {
+        uint64_t TypeId = CGNext();
+        uint64_t CallSitePc = CGNext();
+        TypeIdToIndirCallSites[TypeId].push_back(CallSitePc);
+        FuncInfo[FuncEntryPc].IndirectCallSites.push_back(CallSitePc);
+      }
+
+      // Read direct call sites info.
+      uint64_t DirectCallSiteCount = CGNext();
+      for (unsigned long I = 0; I < DirectCallSiteCount; I++) {
+        uint64_t CallSitePc = CGNext();
+        uint64_t CalleePc = CGNext();
+        FuncInfo[FuncEntryPc].DirectCallSites.emplace_back(CallSitePc,
+                                                           CalleePc);
+      }
+    }
+  }
+  llvm::sort(FuncInfo,
+             [](const auto &A, const auto &B) { return A.first < B.first; });
+}
+
 template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
-  OS << "GNUStyle::printCallGraphInfo not implemented\n";
+  this->callGraphInfoHelper();
+  uint64_t NotListedCount = 0;
+  uint64_t UnknownCount = 0;
+
+  for (const auto &El : this->FuncInfo) {
+    NotListedCount += El.second.Kind == FunctionKind::NOT_LISTED;
+    UnknownCount += El.second.Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
+  }
+  if (NotListedCount) // TODO(prabhuk): include object file name to the warning
+    this->reportUniqueWarning(
+        "callgraph section does not have information for " +
+        std::to_string(NotListedCount) + " functions");
+  if (UnknownCount) // TODO(prabhuk): include object file name to the warning
+    this->reportUniqueWarning("callgraph section has unknown type id for " +
+                              std::to_string(UnknownCount) +
+                              " indirect targets");
+
+  // Print indirect targets
+  OS << "\nINDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])";
+
+  // Print indirect targets with unknown type.
+  // For completeness, functions for which the call graph section does not
+  // provide information are included.
+  if (NotListedCount || UnknownCount) {
+    OS << "\nUNKNOWN";
+    for (const auto &El : this->FuncInfo) {
+      uint64_t FuncEntryPc = El.first;
+      FunctionKind FuncKind = El.second.Kind;
+      if (FuncKind == FunctionKind::NOT_LISTED ||
+          FuncKind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID)
+        OS << " " << format("%lx", FuncEntryPc);
+    }
+  }
+
+  // Print indirect targets to type id mapping.
+  for (const auto &El : this->TypeIdToIndirTargets) {
+    uint64_t TypeId = El.first;
+    OS << "\n" << format("%lx", TypeId);
+    for (uint64_t IndirTargetPc : El.second)
+      OS << " " << format("%lx", IndirTargetPc);
+  }
+
+  // Print indirect calls to type id mapping. Any indirect call without a
+  // type id can be deduced by comparing this list to indirect call sites
+  // list.
+  OS << "\n\nINDIRECT CALL TYPES (TYPEID [CALL_SITE_ADDR,])";
+  for (const auto &El : this->TypeIdToIndirCallSites) {
+    uint64_t TypeId = El.first;
+    OS << "\n" << format("%lx", TypeId);
+    for (uint64_t IndirCallSitePc : El.second)
+      OS << " " << format("%lx", IndirCallSitePc);
+  }
+
+  // Print function entry to indirect call site addresses mapping from disasm.
+  OS << "\n\nINDIRECT CALL SITES (CALLER_ADDR [CALL_SITE_ADDR,])";
+  for (const auto &El : this->FuncInfo) {
+    auto CallerPc = El.first;
+    auto FuncIndirCallSites = El.second.IndirectCallSites;
+    if (!FuncIndirCallSites.empty()) {
+      OS << "\n" << format("%lx", CallerPc);
+      for (auto IndirCallSitePc : FuncIndirCallSites)
+        OS << " " << format("%lx", IndirCallSitePc);
+    }
+  }
+
+  // Print function entry to direct call site and target function entry
+  // addresses mapping from disasm.
+  OS << "\n\nDIRECT CALL SITES (CALLER_ADDR [(CALL_SITE_ADDR, TARGET_ADDR),])";
+  for (const auto &El : this->FuncInfo) {
+    auto CallerPc = El.first;
+    auto FuncDirCallSites = El.second.DirectCallSites;
+    if (!FuncDirCallSites.empty()) {
+      OS << "\n" << format("%lx", CallerPc);
+      for (const FunctionInfo::DirectCallSite &DCS : FuncDirCallSites) {
+        OS << " " << format("%lx", DCS.CallSite) << " "
+           << format("%lx", DCS.Callee);
+      }
+    }
+  }
+
+  // Print function entry pc to function name mapping.
+  // OS << "\n\nFUNCTIONS (FUNC_ENTRY_ADDR, SYM_NAME)";
+  // for (const auto &El : FuncInfo) {
+  //   uint64_t EntryPc = El.first;
+  //   const auto &Name = El.second.Name;
+  //   OS << "\n" << format("%lx", EntryPc) << " " << Name;
+  // }
+  OS << "\n";
 }
 
 template <class ELFT>
@@ -7800,6 +8016,86 @@ template <class ELFT> void JSONELFDumper<ELFT>::printDynamicTable() {
   }
 }
 
+template <class ELFT> void JSONELFDumper<ELFT>::printCallGraphInfo() {
+  this->callGraphInfoHelper();
+  uint64_t NotListedCount = 0;
+  uint64_t UnknownCount = 0;
+
+  for (const auto &El : this->FuncInfo) {
+    NotListedCount += El.second.Kind == FunctionKind::NOT_LISTED;
+    UnknownCount += El.second.Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
+  }
+  if (NotListedCount) // TODO(prabhuk): include object file name to the warning
+    this->reportUniqueWarning(
+        "callgraph section does not have information for " +
+        std::to_string(NotListedCount) + " functions");
+  if (UnknownCount) // TODO(prabhuk): include object file name to the warning
+    this->reportUniqueWarning("callgraph section has unknown type id for " +
+                              std::to_string(UnknownCount) +
+                              " indirect targets");
+
+  DictScope D(this->W, "callgraph_info");
+
+  // Use llvm-symbolizer to get function name and address instead.
+  // {
+  //   ListScope A(this->W, "functions");
+  //   for (auto const &F : this->FuncInfo) {
+  //     DictScope D(this->W);
+  //     this->W.printHex("function_address", F.first);
+  //   }
+  // }
+
+  {
+    ListScope A(this->W, "direct_call_sites");
+    for (auto const &F : this->FuncInfo) {
+      if (F.second.DirectCallSites.empty())
+        continue;
+      DictScope D(this->W);
+      this->W.printHex("caller", F.first);
+      ListScope A(this->W, "call_sites");
+      for (auto const &CS : F.second.DirectCallSites) {
+        DictScope D(this->W);
+        this->W.printHex("call_site", CS.CallSite);
+        this->W.printHex("callee", CS.Callee);
+      }
+    }
+  }
+
+  {
+    ListScope A(this->W, "indirect_call_sites");
+    for (auto const &F : this->FuncInfo) {
+      if (F.second.IndirectCallSites.empty())
+        continue;
+      DictScope D(this->W);
+      this->W.printHex("caller", F.first);
+      // ListScope A(this->W, "call_sites");
+      this->W.printHexList("call_sites", F.second.IndirectCallSites);
+    }
+  }
+
+  {
+    ListScope A(this->W, "indirect_call_types");
+    for (auto const &T : this->TypeIdToIndirCallSites) {
+      for (auto const &CS : T.second) {
+        DictScope D(this->W);
+        this->W.printHex("call_site", CS);
+        this->W.printHex("type_id", T.first);
+      }
+    }
+  }
+
+  {
+    ListScope A(this->W, "indirect_target_types");
+    for (auto const &T : this->TypeIdToIndirTargets) {
+      for (auto const &Target : T.second) {
+        DictScope D(this->W);
+        this->W.printHex("function_address", Target);
+        this->W.printHex("type_id", T.first);
+      }
+    }
+  }
+}
+
 template <class ELFT> void LLVMELFDumper<ELFT>::printDynamicRelocations() {
   W.startLine() << "Dynamic Relocations {\n";
   W.indent();
@@ -8054,126 +8350,8 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCGProfile() {
   }
 }
 
-enum class FunctionKind : uint64_t {
-  // Function cannot be target to indirect calls.
-  NOT_INDIRECT_TARGET = 0,
-  // Function may be target to indirect calls but its type id is unknown.
-  INDIRECT_TARGET_UNKNOWN_TID = 1,
-  // Function may be target to indirect calls and its type id is known.
-  INDIRECT_TARGET_KNOWN_TID = 2,
-
-  // Available in the binary but not listed in the call graph section.
-  NOT_LISTED = 3,
-};
-
-struct FunctionInfo {
-  FunctionKind Kind;
-  struct DirectCallSite {
-    uint64_t CallSite;
-    uint64_t Callee;
-    DirectCallSite(uint64_t CallSite, uint64_t Callee)
-        : CallSite(CallSite), Callee(Callee) {}
-  };
-  SmallVector<DirectCallSite> DirectCallSites;
-  SmallVector<uint64_t> IndirectCallSites;
-};
-
 template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
-  // Get the .callgraph section.
-  StringRef CallGraphSectionName(".callgraph");
-  std::optional<object::SectionRef> CallGraphSection;
-  for (auto Sec : this->ObjF.sections()) {
-    StringRef Name;
-    if (Expected<StringRef> NameOrErr = Sec.getName())
-      Name = *NameOrErr;
-    else
-      consumeError(NameOrErr.takeError());
-
-    if (Name == CallGraphSectionName) {
-      CallGraphSection = Sec;
-      break;
-    }
-  }
-  if (!CallGraphSection) {
-    this->reportUniqueWarning("there is no .callgraph section");
-    return;
-  }
-
-  // Map type id to indirect call sites.
-  MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirCallSites;
-  // Map type id to indirect targets.
-  MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirTargets;
-  // Map function entry pc to function info.
-  MapVector<uint64_t, FunctionInfo> FuncInfo;
-  // Instructions that are not indirect calls but have a type id are ignored.
-  // uint64_t IgnoredICallIdCount = 0;
-  // Number of valid indirect calls with type ids.
-  // uint64_t ICallWithTypeIdCount = 0;
-  if (CallGraphSection) {
-    StringRef CGSecContents = cantFail(CallGraphSection.value().getContents());
-    // TODO: some entries are written in pointer size. are they always 64-bit?
-    if (CGSecContents.size() % sizeof(uint64_t))
-      this->reportUniqueWarning(
-          "Malformed .callgraph section. Unexpected size.");
-
-    size_t Size = CGSecContents.size() / sizeof(uint64_t);
-    auto *It = reinterpret_cast<const uint64_t *>(CGSecContents.data());
-    const auto *const End = It + Size;
-
-    auto CGHasNext = [&]() { return It < End; };
-    auto CGNext = [&]() -> uint64_t {
-      if (!CGHasNext())
-        this->reportUniqueWarning(
-            "Malformed .callgraph section. Parsing error.");
-      return *It++;
-    };
-
-    // Parse the content
-    while (CGHasNext()) {
-      // Format version number.
-      uint64_t FormatVersionNumber = CGNext();
-      if (FormatVersionNumber != 0)
-        this->reportUniqueWarning(
-            "Unknown format version in .callgraph section.");
-      // Function entry pc.
-      uint64_t FuncEntryPc = CGNext();
-      // Function kind.
-      uint64_t Kind = CGNext();
-      switch (Kind) {
-      case 0: // not an indirect target
-        FuncInfo[FuncEntryPc].Kind = FunctionKind::NOT_INDIRECT_TARGET;
-        break;
-      case 1: // indirect target with unknown type id
-        FuncInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
-        break;
-      case 2: // indirect target with known type id
-        FuncInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_KNOWN_TID;
-        TypeIdToIndirTargets[CGNext()].push_back(FuncEntryPc);
-        break;
-      default:
-        this->reportUniqueWarning(
-            "Unknown function kind in .callgraph section.");
-      }
-
-      // Read indirect call sites info.
-      uint64_t IndirectCallSiteCount = CGNext();
-      for (unsigned long I = 0; I < IndirectCallSiteCount; I++) {
-        uint64_t TypeId = CGNext();
-        uint64_t CallSitePc = CGNext();
-        TypeIdToIndirCallSites[TypeId].push_back(CallSitePc);
-        FuncInfo[FuncEntryPc].IndirectCallSites.push_back(CallSitePc);
-      }
-
-      // Read direct call sites info.
-      uint64_t DirectCallSiteCount = CGNext();
-      for (unsigned long I = 0; I < DirectCallSiteCount; I++) {
-        uint64_t CallSitePc = CGNext();
-        uint64_t CalleePc = CGNext();
-        FuncInfo[FuncEntryPc].DirectCallSites.emplace_back(CallSitePc,
-                                                           CalleePc);
-      }
-    }
-  }
+  this->callGraphInfoHelper();
 }
 
 template <class ELFT>

>From 6d02828a6cc6ebf822dabbafedb3ac45ee83b839 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Mon, 8 Sep 2025 16:11:33 -0700
Subject: [PATCH 4/5] ELFDumper cleanups

---
 llvm/tools/llvm-readobj/ELFDumper.cpp | 395 +++++++++++++-------------
 1 file changed, 194 insertions(+), 201 deletions(-)

diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index 76783387abdea..66722fadd00bd 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -183,20 +183,19 @@ struct GroupSection {
   std::vector<GroupMember> Members;
 };
 
-// Callgraph section handling implementation.
-enum class FunctionKind : uint64_t {
-  // Function cannot be target to indirect calls.
-  NOT_INDIRECT_TARGET = 0,
-  // Function may be target to indirect calls but its type id is unknown.
-  INDIRECT_TARGET_UNKNOWN_TID = 1,
-  // Function may be target to indirect calls and its type id is known.
-  INDIRECT_TARGET_KNOWN_TID = 2,
-
-  // Available in the binary but not listed in the call graph section.
-  NOT_LISTED = 3,
-};
-
-struct FunctionInfo {
+// Per-function Callgraph information.
+struct FunctionCallgraphInfo {
+  enum class FunctionKind : uint64_t {
+    // Function cannot be target to indirect calls.
+    NOT_INDIRECT_TARGET = 0,
+    // Function may be target to indirect calls but its type id is unknown.
+    INDIRECT_TARGET_UNKNOWN_TID = 1,
+    // Function may be target to indirect calls and its type id is known.
+    INDIRECT_TARGET_KNOWN_TID = 2,
+
+    // Available in the binary but not listed in the call graph section.
+    NOT_LISTED = 3,
+  };
   FunctionKind Kind;
   struct DirectCallSite {
     uint64_t CallSite;
@@ -207,6 +206,7 @@ struct FunctionInfo {
   SmallVector<DirectCallSite> DirectCallSites;
   SmallVector<uint64_t> IndirectCallSites;
 };
+typedef FunctionCallgraphInfo::FunctionKind FunctionKind;
 
 namespace {
 
@@ -458,13 +458,19 @@ template <typename ELFT> class ELFDumper : public ObjDumper {
       const typename SFrameParser<ELFT::Endianness>::FDERange::iterator FDE,
       ArrayRef<Relocation<ELFT>> Relocations, const Elf_Shdr *RelocSymTab);
 
-  void callGraphInfoHelper();
-  // Map type id to indirect call sites.
+  // Callgraph - Main data structure to maintain per function callgraph
+  // information.
+  MapVector<uint64_t, FunctionCallgraphInfo> FuncCGInfo;
+  // Callgraph - 64 bit type id mapped to indirect callsites whose potential
+  // callee(s) should be of given type id.
   MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirCallSites;
-  // Map type id to indirect targets.
+  // Callgraph - 64 bit type id mapped to entry PC addresses of functions which
+  // are of the given type id.
   MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirTargets;
-  // Map function entry pc to function info.
-  MapVector<uint64_t, FunctionInfo> FuncInfo;
+  // Callgraph - Read callgraph section and process its contents to populate
+  // Callgraph related data structures which will be used to dump callgraph
+  // info.
+  void processCallGraphSection();
 
 private:
   mutable SmallVector<std::optional<VersionEntry>, 0> VersionMap;
@@ -845,8 +851,6 @@ template <typename ELFT> class JSONELFDumper : public LLVMELFDumper<ELFT> {
 
   void printDynamicTable() override;
 
-  void printCallGraphInfo() override;
-
 private:
   void printAuxillaryDynamicTableEntryInfo(const Elf_Dyn &Entry);
 
@@ -5254,11 +5258,13 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCGProfile() {
   OS << "GNUStyle::printCGProfile not implemented\n";
 }
 
-template <class ELFT> void ELFDumper<ELFT>::callGraphInfoHelper() {
+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 : this->ObjF.sections()) {
+  for (auto Sec : ObjF.sections()) {
     StringRef Name;
     if (Expected<StringRef> NameOrErr = Sec.getName())
       Name = *NameOrErr;
@@ -5270,101 +5276,106 @@ template <class ELFT> void ELFDumper<ELFT>::callGraphInfoHelper() {
       break;
     }
   }
+  return CallGraphSection;
+}
+
+template <class ELFT> void ELFDumper<ELFT>::processCallGraphSection() {
+  std::optional<object::SectionRef> CallGraphSection =
+      getCallGraphSection(ObjF);
   if (!CallGraphSection) {
-    this->reportUniqueWarning("there is no .callgraph section");
+    reportUniqueWarning("there is no .callgraph section in " +
+                        getElfObject().getFileName());
     return;
   }
 
-  // Instructions that are not indirect calls but have a type id are ignored.
-  // uint64_t IgnoredICallIdCount = 0;
-  // Number of valid indirect calls with type ids.
-  // uint64_t ICallWithTypeIdCount = 0;
-  if (CallGraphSection) {
-    StringRef CGSecContents = cantFail(CallGraphSection.value().getContents());
-    // TODO: some entries are written in pointer size. are they always 64-bit?
-    if (CGSecContents.size() % sizeof(uint64_t))
-      this->reportUniqueWarning(
-          "Malformed .callgraph section. Unexpected size.");
-
-    size_t Size = CGSecContents.size() / sizeof(uint64_t);
-    auto *It = reinterpret_cast<const uint64_t *>(CGSecContents.data());
-    const auto *const End = It + Size;
-
-    auto CGHasNext = [&]() { return It < End; };
-    auto CGNext = [&]() -> uint64_t {
-      if (!CGHasNext())
-        this->reportUniqueWarning(
-            "Malformed .callgraph section. Parsing error.");
-      return *It++;
-    };
+  StringRef CGSecContents = cantFail(CallGraphSection.value().getContents());
+  // TODO: some entries are written in pointer size. are they always 64-bit?
+  if (CGSecContents.size() % sizeof(uint64_t))
+    reportUniqueWarning("Malformed .callgraph section. Unexpected size in " +
+                        getElfObject().getFileName());
 
-    // Parse the content
-    while (CGHasNext()) {
-      // Format version number.
-      uint64_t FormatVersionNumber = CGNext();
-      if (FormatVersionNumber != 0)
-        this->reportUniqueWarning(
-            "Unknown format version in .callgraph section.");
-      // Function entry pc.
-      uint64_t FuncEntryPc = CGNext();
-      // Function kind.
-      uint64_t Kind = CGNext();
-      switch (Kind) {
-      case 0: // not an indirect target
-        FuncInfo[FuncEntryPc].Kind = FunctionKind::NOT_INDIRECT_TARGET;
-        break;
-      case 1: // indirect target with unknown type id
-        FuncInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
-        break;
-      case 2: // indirect target with known type id
-        FuncInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_KNOWN_TID;
-        TypeIdToIndirTargets[CGNext()].push_back(FuncEntryPc);
-        break;
-      default:
-        this->reportUniqueWarning(
-            "Unknown function kind in .callgraph section.");
-      }
+  size_t Size = CGSecContents.size() / sizeof(uint64_t);
+  auto *It = reinterpret_cast<const uint64_t *>(CGSecContents.data());
+  const auto *const End = It + Size;
 
-      // Read indirect call sites info.
-      uint64_t IndirectCallSiteCount = CGNext();
-      for (unsigned long I = 0; I < IndirectCallSiteCount; I++) {
-        uint64_t TypeId = CGNext();
-        uint64_t CallSitePc = CGNext();
-        TypeIdToIndirCallSites[TypeId].push_back(CallSitePc);
-        FuncInfo[FuncEntryPc].IndirectCallSites.push_back(CallSitePc);
-      }
+  auto CGHasNext = [&]() { return It < End; };
+  auto CGNext = [&]() -> uint64_t {
+    if (!CGHasNext())
+      reportUniqueWarning("Malformed .callgraph section. Parsing error in " +
+                          getElfObject().getFileName());
+    return *It++;
+  };
 
-      // Read direct call sites info.
-      uint64_t DirectCallSiteCount = CGNext();
-      for (unsigned long I = 0; I < DirectCallSiteCount; I++) {
-        uint64_t CallSitePc = CGNext();
-        uint64_t CalleePc = CGNext();
-        FuncInfo[FuncEntryPc].DirectCallSites.emplace_back(CallSitePc,
+  while (CGHasNext()) {
+    // Format version number.
+    uint64_t FormatVersionNumber = CGNext();
+    if (FormatVersionNumber != 0)
+      reportUniqueWarning("Unknown format version in .callgraph section in " +
+                          getElfObject().getFileName());
+    // Function entry pc.
+    uint64_t FuncEntryPc = CGNext();
+    // Function kind.
+    uint64_t Kind = CGNext();
+    switch (Kind) {
+    case 0: // not an indirect target
+      FuncCGInfo[FuncEntryPc].Kind = FunctionKind::NOT_INDIRECT_TARGET;
+      break;
+    case 1: // indirect target with unknown type id
+      FuncCGInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
+      break;
+    case 2: // indirect target with known type id
+      FuncCGInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_KNOWN_TID;
+      TypeIdToIndirTargets[CGNext()].push_back(FuncEntryPc);
+      break;
+    default:
+      this->reportUniqueWarning(
+          "Unknown function kind in .callgraph section in " +
+          getElfObject().getFileName());
+    }
+    // Read indirect call sites info.
+    uint64_t IndirectCallSiteCount = CGNext();
+    for (unsigned long I = 0; I < IndirectCallSiteCount; I++) {
+      uint64_t TypeId = CGNext();
+      uint64_t CallSitePc = CGNext();
+      TypeIdToIndirCallSites[TypeId].push_back(CallSitePc);
+      FuncCGInfo[FuncEntryPc].IndirectCallSites.push_back(CallSitePc);
+    }
+    // Read direct call sites info.
+    uint64_t DirectCallSiteCount = CGNext();
+    for (unsigned long I = 0; I < DirectCallSiteCount; I++) {
+      uint64_t CallSitePc = CGNext();
+      uint64_t CalleePc = CGNext();
+      FuncCGInfo[FuncEntryPc].DirectCallSites.emplace_back(CallSitePc,
                                                            CalleePc);
-      }
     }
   }
-  llvm::sort(FuncInfo,
+
+  // Sort function info by function PC.
+  llvm::sort(FuncCGInfo,
              [](const auto &A, const auto &B) { return A.first < B.first; });
-}
 
-template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
-  this->callGraphInfoHelper();
   uint64_t NotListedCount = 0;
   uint64_t UnknownCount = 0;
-
-  for (const auto &El : this->FuncInfo) {
+  for (const auto &El : FuncCGInfo) {
     NotListedCount += El.second.Kind == FunctionKind::NOT_LISTED;
     UnknownCount += El.second.Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
   }
-  if (NotListedCount) // TODO(prabhuk): include object file name to the warning
-    this->reportUniqueWarning(
-        "callgraph section does not have information for " +
-        std::to_string(NotListedCount) + " functions");
-  if (UnknownCount) // TODO(prabhuk): include object file name to the warning
-    this->reportUniqueWarning("callgraph section has unknown type id for " +
-                              std::to_string(UnknownCount) +
-                              " indirect targets");
+  for (const auto &El : FuncCGInfo) {
+    NotListedCount += El.second.Kind == FunctionKind::NOT_LISTED;
+    UnknownCount += El.second.Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
+  }
+  if (NotListedCount)
+    reportUniqueWarning("callgraph section does not have information for " +
+                        std::to_string(NotListedCount) + " functions in " +
+                        getElfObject().getFileName());
+  if (UnknownCount)
+    reportUniqueWarning("callgraph section has unknown type id for " +
+                        std::to_string(UnknownCount) + " indirect targets in " +
+                        getElfObject().getFileName());
+}
+
+template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
+  this->processCallGraphSection();
 
   // Print indirect targets
   OS << "\nINDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])";
@@ -5372,17 +5383,14 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
   // Print indirect targets with unknown type.
   // For completeness, functions for which the call graph section does not
   // provide information are included.
-  if (NotListedCount || UnknownCount) {
-    OS << "\nUNKNOWN";
-    for (const auto &El : this->FuncInfo) {
+  for (const auto &El : this->FuncCGInfo) {
+    FunctionKind FuncKind = El.second.Kind;
+    if (FuncKind == FunctionKind::NOT_LISTED ||
+        FuncKind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID) {
       uint64_t FuncEntryPc = El.first;
-      FunctionKind FuncKind = El.second.Kind;
-      if (FuncKind == FunctionKind::NOT_LISTED ||
-          FuncKind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID)
-        OS << " " << format("%lx", FuncEntryPc);
+      OS << " " << format("%lx", FuncEntryPc);
     }
   }
-
   // Print indirect targets to type id mapping.
   for (const auto &El : this->TypeIdToIndirTargets) {
     uint64_t TypeId = El.first;
@@ -5404,7 +5412,7 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
 
   // Print function entry to indirect call site addresses mapping from disasm.
   OS << "\n\nINDIRECT CALL SITES (CALLER_ADDR [CALL_SITE_ADDR,])";
-  for (const auto &El : this->FuncInfo) {
+  for (const auto &El : this->FuncCGInfo) {
     auto CallerPc = El.first;
     auto FuncIndirCallSites = El.second.IndirectCallSites;
     if (!FuncIndirCallSites.empty()) {
@@ -5417,25 +5425,18 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
   // Print function entry to direct call site and target function entry
   // addresses mapping from disasm.
   OS << "\n\nDIRECT CALL SITES (CALLER_ADDR [(CALL_SITE_ADDR, TARGET_ADDR),])";
-  for (const auto &El : this->FuncInfo) {
+  for (const auto &El : this->FuncCGInfo) {
     auto CallerPc = El.first;
     auto FuncDirCallSites = El.second.DirectCallSites;
     if (!FuncDirCallSites.empty()) {
       OS << "\n" << format("%lx", CallerPc);
-      for (const FunctionInfo::DirectCallSite &DCS : FuncDirCallSites) {
+      for (const FunctionCallgraphInfo::DirectCallSite &DCS :
+           FuncDirCallSites) {
         OS << " " << format("%lx", DCS.CallSite) << " "
            << format("%lx", DCS.Callee);
       }
     }
   }
-
-  // Print function entry pc to function name mapping.
-  // OS << "\n\nFUNCTIONS (FUNC_ENTRY_ADDR, SYM_NAME)";
-  // for (const auto &El : FuncInfo) {
-  //   uint64_t EntryPc = El.first;
-  //   const auto &Name = El.second.Name;
-  //   OS << "\n" << format("%lx", EntryPc) << " " << Name;
-  // }
   OS << "\n";
 }
 
@@ -8016,86 +8017,6 @@ template <class ELFT> void JSONELFDumper<ELFT>::printDynamicTable() {
   }
 }
 
-template <class ELFT> void JSONELFDumper<ELFT>::printCallGraphInfo() {
-  this->callGraphInfoHelper();
-  uint64_t NotListedCount = 0;
-  uint64_t UnknownCount = 0;
-
-  for (const auto &El : this->FuncInfo) {
-    NotListedCount += El.second.Kind == FunctionKind::NOT_LISTED;
-    UnknownCount += El.second.Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
-  }
-  if (NotListedCount) // TODO(prabhuk): include object file name to the warning
-    this->reportUniqueWarning(
-        "callgraph section does not have information for " +
-        std::to_string(NotListedCount) + " functions");
-  if (UnknownCount) // TODO(prabhuk): include object file name to the warning
-    this->reportUniqueWarning("callgraph section has unknown type id for " +
-                              std::to_string(UnknownCount) +
-                              " indirect targets");
-
-  DictScope D(this->W, "callgraph_info");
-
-  // Use llvm-symbolizer to get function name and address instead.
-  // {
-  //   ListScope A(this->W, "functions");
-  //   for (auto const &F : this->FuncInfo) {
-  //     DictScope D(this->W);
-  //     this->W.printHex("function_address", F.first);
-  //   }
-  // }
-
-  {
-    ListScope A(this->W, "direct_call_sites");
-    for (auto const &F : this->FuncInfo) {
-      if (F.second.DirectCallSites.empty())
-        continue;
-      DictScope D(this->W);
-      this->W.printHex("caller", F.first);
-      ListScope A(this->W, "call_sites");
-      for (auto const &CS : F.second.DirectCallSites) {
-        DictScope D(this->W);
-        this->W.printHex("call_site", CS.CallSite);
-        this->W.printHex("callee", CS.Callee);
-      }
-    }
-  }
-
-  {
-    ListScope A(this->W, "indirect_call_sites");
-    for (auto const &F : this->FuncInfo) {
-      if (F.second.IndirectCallSites.empty())
-        continue;
-      DictScope D(this->W);
-      this->W.printHex("caller", F.first);
-      // ListScope A(this->W, "call_sites");
-      this->W.printHexList("call_sites", F.second.IndirectCallSites);
-    }
-  }
-
-  {
-    ListScope A(this->W, "indirect_call_types");
-    for (auto const &T : this->TypeIdToIndirCallSites) {
-      for (auto const &CS : T.second) {
-        DictScope D(this->W);
-        this->W.printHex("call_site", CS);
-        this->W.printHex("type_id", T.first);
-      }
-    }
-  }
-
-  {
-    ListScope A(this->W, "indirect_target_types");
-    for (auto const &T : this->TypeIdToIndirTargets) {
-      for (auto const &Target : T.second) {
-        DictScope D(this->W);
-        this->W.printHex("function_address", Target);
-        this->W.printHex("type_id", T.first);
-      }
-    }
-  }
-}
-
 template <class ELFT> void LLVMELFDumper<ELFT>::printDynamicRelocations() {
   W.startLine() << "Dynamic Relocations {\n";
   W.indent();
@@ -8351,7 +8272,79 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCGProfile() {
 }
 
 template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
-  this->callGraphInfoHelper();
+  this->processCallGraphSection();
+
+  DictScope D(this->W, "callgraph_info");
+  // Use llvm-symbolizer to get function name and address instead.
+  // stack-sizes embeds function mangled name directly in JSON.
+  // {
+  //   ListScope A(this->W, "functions");
+  //   for (auto const &F : this->FuncCGInfo) {
+  //     DictScope D(this->W);
+  //     this->W.printHex("function_address", F.first);
+  //   }
+  // }
+
+  {
+    ListScope A(this->W, "indirect_target_types");
+    SmallVector<uint64_t, 4> Unknowns;
+    for (const auto &El : this->FuncCGInfo) {
+      FunctionKind FuncKind = El.second.Kind;
+      if (FuncKind == FunctionKind::NOT_LISTED ||
+          FuncKind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID) {
+        uint64_t FuncEntryPc = El.first;
+        Unknowns.emplace_back(FuncEntryPc);
+      }
+    }
+    if (!Unknowns.empty())
+      this->W.printHexList("unknown", Unknowns);
+    for (auto const &T : this->TypeIdToIndirTargets) {
+      for (auto const &Target : T.second) {
+        DictScope D(this->W);
+        this->W.printHex("function_address", Target);
+        this->W.printHex("type_id", T.first);
+      }
+    }
+  }
+
+  {
+    ListScope A(this->W, "direct_call_sites");
+    for (auto const &F : this->FuncCGInfo) {
+      if (F.second.DirectCallSites.empty())
+        continue;
+      DictScope D(this->W);
+      this->W.printHex("caller", F.first);
+      ListScope A(this->W, "call_sites");
+      for (auto const &CS : F.second.DirectCallSites) {
+        DictScope D(this->W);
+        this->W.printHex("call_site", CS.CallSite);
+        this->W.printHex("callee", CS.Callee);
+      }
+    }
+  }
+
+  {
+    ListScope A(this->W, "indirect_call_sites");
+    for (auto const &F : this->FuncCGInfo) {
+      if (F.second.IndirectCallSites.empty())
+        continue;
+      DictScope D(this->W);
+      this->W.printHex("caller", F.first);
+      // ListScope A(this->W, "call_sites");
+      this->W.printHexList("call_sites", F.second.IndirectCallSites);
+    }
+  }
+
+  {
+    ListScope A(this->W, "indirect_call_types");
+    for (auto const &T : this->TypeIdToIndirCallSites) {
+      for (auto const &CS : T.second) {
+        DictScope D(this->W);
+        this->W.printHex("call_site", CS);
+        this->W.printHex("type_id", T.first);
+      }
+    }
+  }
 }
 
 template <class ELFT>

>From c05932e9204eaf0e653cd6561427b9b53680c3af Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Mon, 8 Sep 2025 18:48:06 -0700
Subject: [PATCH 5/5] Add readelf callgraph info tests.

---
 .../call-graph-info-callgraph-section.test    | 77 +++++++++++++++++++
 ...call-graph-info-err-invalid-func-kind.test | 19 +++++
 ...-info-err-malformed-callgraph-section.test | 21 +++++
 ...info-err-malformed-callgraph-section2.test | 21 +++++
 ...info-err-malformed-callgraph-section3.test | 23 ++++++
 ...all-graph-info-invalid-format-version.test | 17 ++++
 ...-graph-info-warn-no-callgraph-section.test | 15 ++++
 llvm/tools/llvm-readobj/ELFDumper.cpp         | 67 ++++++++--------
 8 files changed, 227 insertions(+), 33 deletions(-)
 create mode 100644 llvm/test/tools/llvm-readobj/ELF/call-graph-info-callgraph-section.test
 create mode 100644 llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-func-kind.test
 create mode 100644 llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section.test
 create mode 100644 llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section2.test
 create mode 100644 llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section3.test
 create mode 100644 llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test
 create mode 100644 llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-no-callgraph-section.test

diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-callgraph-section.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-callgraph-section.test
new file mode 100644
index 0000000000000..60d1c8323f8e8
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-callgraph-section.test
@@ -0,0 +1,77 @@
+## Tests --call-graph-info prints information from call graph section.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
+
+# CHECK: INDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])
+# CHECK-NEXT: UNKNOWN 6
+# CHECK-NEXT: 20 a
+# CHECK-EMPTY:
+# CHECK-NEXT: INDIRECT CALL TYPES (TYPEID [CALL_SITE_ADDR,])
+# CHECK-NEXT: 10 9
+# CHECK-EMPTY:
+# CHECK-NEXT: INDIRECT CALL SITES (CALLER_ADDR [CALL_SITE_ADDR,])
+# CHECK-NEXT: 6 9
+# CHECK-EMPTY:
+# CHECK-NEXT: DIRECT CALL SITES (CALLER_ADDR [(CALL_SITE_ADDR, TARGET_ADDR),])
+# CHECK-NEXT: 0 5 5
+#
+#FUNCTIONS (FUNC_ENTRY_ADDR, SYM_NAME)
+# 0 foo
+# 6 bar
+# a baz
+# b qux
+
+.text
+
+.globl foo
+.type foo, at function
+foo:                  #< foo is at 0.
+.Lfoo_begin:
+ callq foo            #< direct call is at 5. target is foo (5).
+ retq
+
+.globl bar
+.type bar, at function
+bar:                  #< bar is at 6.
+ callq	*-40(%rbp)    #< indirect call is at 9.
+ retq
+
+.globl baz
+.type baz, at function
+baz:                  #< baz is at 10 (a).
+ retq
+
+.globl qux
+.type qux, at function
+qux:                  #< qux is at 11 (b).
+ retq
+
+.section	.callgraph,"o", at progbits,.text
+.quad	0       #< Format version number.
+.quad	0       #< foo()'s entry address.
+.quad	0       #< Function kind: not an indirect target.
+.quad	0       #< Count of indirect call sites that follow: 0.
+.quad   1       #< Count of direct call sites that follow: 1>
+.quad   5       #< Direct callsite call to foo>
+.quad   5       #< Direct callee foo's address>
+
+.quad	0       #< Format version number.
+.quad	6       #< bar()'s entry address.
+.quad	1       #< Function kind: indirect target with unknown type id.
+.quad	1       #< Count of indirect call sites that follow: 1.
+.quad 16      #< Indirect call type id.
+.quad 9       #< Indirect call site.
+.quad   0       #< Count of direct call sites that follow: 0>
+
+.quad	0       #< Format version number.
+.quad	10      #< baz()'s entry address.
+.quad	2       #< Function kind: indirect target with known type id.
+.quad 32      #< Indirect target type id.
+.quad	0       #< Count of indirect call sites that follow: 0>.
+.quad   0       #< Count of direct call sites that follow: 0>
+
+# No call graph section entry for qux. 
+# Technically its "UNKNOWN" type id but will not be printed as such by llvm-readelf.  
+
+.text
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-func-kind.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-func-kind.test
new file mode 100644
index 0000000000000..b55717b25fee0
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-func-kind.test
@@ -0,0 +1,19 @@
+## Tests that --call-graph-info fails if .callgraph section has invalid
+## function kind value.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+
+# ERR: llvm-readelf: warning: '[[FILE]]': Unknown function kind in .callgraph section.
+
+.text
+.globl _Z3foov
+.type _Z3foov, at function
+_Z3foov:
+ callq _Z3foov at PLT
+
+.section	.callgraph,"o", at progbits,.text
+.quad	0   #< Format version number.
+.quad 0   #< Function entry address.
+.quad 3   #< Invalid function kind: must be one of 0, 1, 2.
+.text
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section.test
new file mode 100644
index 0000000000000..459d002d35352
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section.test
@@ -0,0 +1,21 @@
+## Tests that --call-graph-info fails if .callgraph section has invalid size.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+
+# ERR: llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section.
+
+.text
+.globl _Z3foov
+.type _Z3foov, at function
+_Z3foov:
+ callq _Z3foov at PLT
+
+.section	.callgraph,"o", at progbits,.text
+# Each unit in .callgraph section must have 64bit size. Therefore, byte size
+# must be divisible by 64.
+.quad 0
+.quad 0
+.quad 0
+.byte 0
+.text
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section2.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section2.test
new file mode 100644
index 0000000000000..2c84469526552
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section2.test
@@ -0,0 +1,21 @@
+## Tests that --call-graph-info fails if .callgraph section does not have
+## an expected value, e.g., not as much call sites as the given count.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+
+# ERR: llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section.
+
+.text
+.globl _Z3foov
+.type _Z3foov, at function
+_Z3foov:
+ callq _Z3foov at PLT
+
+.section	.callgraph,"o", at progbits,.text
+.quad 0  #< Format version number.
+.quad 0  #< Function entry address.
+.quad 0  #< Function kind.
+.quad 1  #< Indirect call site count that follows.
+         #< Missing indirect call entries here.
+.text
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section3.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section3.test
new file mode 100644
index 0000000000000..5bfbd709c78e2
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section3.test
@@ -0,0 +1,23 @@
+## Tests that --call-graph-info fails if .callgraph section does not have
+## an expected value, e.g., not as much call sites as the given count.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+
+# ERR: llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section.
+
+.text
+.globl _Z3foov
+.type _Z3foov, at function
+_Z3foov:
+ callq _Z3foov
+ retq
+
+.section	.callgraph,"o", at progbits,.text
+.quad 0  #< Format version number.
+.quad 0  #< Function entry address.
+.quad 0  #< Function kind.
+.quad 0  #< Indirect call site count.
+.quad 1  #< Direct call site count that follows.
+         #< Missing direct call entries here.
+.text
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test
new file mode 100644
index 0000000000000..4f9d7dbeb844a
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test
@@ -0,0 +1,17 @@
+## Tests that --call-graph-info fails if .callgraph section has unknown format
+## version number.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+
+# ERR: llvm-readelf: warning: '[[FILE]]': Unknown format version in .callgraph section.
+
+.text
+.globl _Z3foov
+.type _Z3foov, at function
+_Z3foov:
+ callq _Z3foov at PLT
+
+.section	.callgraph,"o", at progbits,.text
+.quad	1  #< Invalid format version number: the only supported version is 0.
+.text
\ No newline at end of file
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-no-callgraph-section.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-no-callgraph-section.test
new file mode 100644
index 0000000000000..cc904dbed2e67
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-no-callgraph-section.test
@@ -0,0 +1,15 @@
+## Tests that --call-graph-info warns if there is no .callgraph section.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
+
+# CHECK: llvm-readelf: warning: '[[FILE]]': No .callgraph section found.
+## Make sure callgraph info header is not printed when there is no .callgraph section
+# CHECK-NOT: INDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])
+# CHECK-NOT: INDIRECT CALL TYPES (TYPEID [CALL_SITE_ADDR,])
+
+.text
+.globl _Z3foov
+.type _Z3foov, at function
+_Z3foov:
+ callq _Z3foov at PLT
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index 66722fadd00bd..ea158d80bcb53 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -469,8 +469,8 @@ template <typename ELFT> class ELFDumper : public ObjDumper {
   MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirTargets;
   // Callgraph - Read callgraph section and process its contents to populate
   // Callgraph related data structures which will be used to dump callgraph
-  // info.
-  void processCallGraphSection();
+  // info. Returns false if there is no .callgraph section in the input file.
+  bool processCallGraphSection();
 
 private:
   mutable SmallVector<std::optional<VersionEntry>, 0> VersionMap;
@@ -5271,28 +5271,26 @@ getCallGraphSection(const object::ELFObjectFile<ELFT> &ObjF) {
     else
       consumeError(NameOrErr.takeError());
 
-    if (Name == CallGraphSectionName) {
-      CallGraphSection = Sec;
-      break;
-    }
+    if (Name == CallGraphSectionName)
+      return Sec;
   }
   return CallGraphSection;
 }
 
-template <class ELFT> void ELFDumper<ELFT>::processCallGraphSection() {
+template <class ELFT> bool ELFDumper<ELFT>::processCallGraphSection() {
   std::optional<object::SectionRef> CallGraphSection =
       getCallGraphSection(ObjF);
-  if (!CallGraphSection) {
-    reportUniqueWarning("there is no .callgraph section in " +
-                        getElfObject().getFileName());
-    return;
+  if (!CallGraphSection.has_value()) {
+    reportUniqueWarning("No .callgraph section found.");
+    return false;
   }
 
   StringRef CGSecContents = cantFail(CallGraphSection.value().getContents());
   // TODO: some entries are written in pointer size. are they always 64-bit?
-  if (CGSecContents.size() % sizeof(uint64_t))
-    reportUniqueWarning("Malformed .callgraph section. Unexpected size in " +
-                        getElfObject().getFileName());
+  if (CGSecContents.size() % sizeof(uint64_t)) {
+    reportUniqueWarning("Malformed .callgraph section. Unexpected size.");
+    exit(1);
+  }
 
   size_t Size = CGSecContents.size() / sizeof(uint64_t);
   auto *It = reinterpret_cast<const uint64_t *>(CGSecContents.data());
@@ -5300,18 +5298,20 @@ template <class ELFT> void ELFDumper<ELFT>::processCallGraphSection() {
 
   auto CGHasNext = [&]() { return It < End; };
   auto CGNext = [&]() -> uint64_t {
-    if (!CGHasNext())
-      reportUniqueWarning("Malformed .callgraph section. Parsing error in " +
-                          getElfObject().getFileName());
+    if (!CGHasNext()) {
+      reportUniqueWarning("Malformed .callgraph section. Parsing error.");
+      exit(1);
+    }
     return *It++;
   };
 
   while (CGHasNext()) {
     // Format version number.
     uint64_t FormatVersionNumber = CGNext();
-    if (FormatVersionNumber != 0)
-      reportUniqueWarning("Unknown format version in .callgraph section in " +
-                          getElfObject().getFileName());
+    if (FormatVersionNumber != 0) {
+      reportUniqueWarning("Unknown format version in .callgraph section.");
+      exit(1);
+    }
     // Function entry pc.
     uint64_t FuncEntryPc = CGNext();
     // Function kind.
@@ -5328,9 +5328,8 @@ template <class ELFT> void ELFDumper<ELFT>::processCallGraphSection() {
       TypeIdToIndirTargets[CGNext()].push_back(FuncEntryPc);
       break;
     default:
-      this->reportUniqueWarning(
-          "Unknown function kind in .callgraph section in " +
-          getElfObject().getFileName());
+      this->reportUniqueWarning("Unknown function kind in .callgraph section.");
+      exit(1);
     }
     // Read indirect call sites info.
     uint64_t IndirectCallSiteCount = CGNext();
@@ -5360,22 +5359,18 @@ template <class ELFT> void ELFDumper<ELFT>::processCallGraphSection() {
     NotListedCount += El.second.Kind == FunctionKind::NOT_LISTED;
     UnknownCount += El.second.Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
   }
-  for (const auto &El : FuncCGInfo) {
-    NotListedCount += El.second.Kind == FunctionKind::NOT_LISTED;
-    UnknownCount += El.second.Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
-  }
   if (NotListedCount)
     reportUniqueWarning("callgraph section does not have information for " +
-                        std::to_string(NotListedCount) + " functions in " +
-                        getElfObject().getFileName());
+                        std::to_string(NotListedCount) + " functions.");
   if (UnknownCount)
     reportUniqueWarning("callgraph section has unknown type id for " +
-                        std::to_string(UnknownCount) + " indirect targets in " +
-                        getElfObject().getFileName());
+                        std::to_string(UnknownCount) + " indirect targets.");
+  return true;
 }
 
 template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
-  this->processCallGraphSection();
+  if (!this->processCallGraphSection())
+    return;
 
   // Print indirect targets
   OS << "\nINDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])";
@@ -5383,10 +5378,15 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
   // Print indirect targets with unknown type.
   // For completeness, functions for which the call graph section does not
   // provide information are included.
+  bool printedHeader = false;
   for (const auto &El : this->FuncCGInfo) {
     FunctionKind FuncKind = El.second.Kind;
     if (FuncKind == FunctionKind::NOT_LISTED ||
         FuncKind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID) {
+      if (!printedHeader) {
+        OS << "\nUNKNOWN";
+        printedHeader = true;
+      }
       uint64_t FuncEntryPc = El.first;
       OS << " " << format("%lx", FuncEntryPc);
     }
@@ -8272,7 +8272,8 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCGProfile() {
 }
 
 template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
-  this->processCallGraphSection();
+  if (!this->processCallGraphSection())
+    return;
 
   DictScope D(this->W, "callgraph_info");
   // Use llvm-symbolizer to get function name and address instead.



More information about the llvm-commits mailing list