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

Prabhu Rajasekaran via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 17 12:06:30 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 01/12] [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 02/12] 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 03/12] 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 04/12] 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 05/12] 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.

>From c35b6d37e337fb6a3f7a2a42c83a532a42475bac Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Tue, 9 Sep 2025 10:37:42 -0700
Subject: [PATCH 06/12] more tests

---
 ...all-graph-info-invalid-format-version.test |   2 +-
 .../llvm-readobj/ELF/call-graph-info.test     | 496 ++++++++++++++++++
 2 files changed, 497 insertions(+), 1 deletion(-)
 create mode 100644 llvm/test/tools/llvm-readobj/ELF/call-graph-info.test

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
index 4f9d7dbeb844a..a1213db081951 100644
--- 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
@@ -14,4 +14,4 @@ _Z3foov:
 
 .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
+.text
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
new file mode 100644
index 0000000000000..547b608cc5950
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
@@ -0,0 +1,496 @@
+## Tests how --call-graph-info prints the call graph information.
+# RUN: yaml2obj --docnum=1 %s -o %t
+# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
+
+# Yaml input is obtained by compiling the below source to object with:
+#   clang -fexperimental-call-graph-section test.c -o test.o
+# then to yaml with:
+#   obj2yaml test.o > test.yaml
+# Remove ProgramHeaders if obj2yaml fails.
+
+# The content of the .callgraph section is fixed with this yaml in raw format.
+
+# Source:
+#   void foo() {}
+#
+#   void bar() {}
+#
+#   int baz(char a) {
+#     return 0;
+#   }
+#
+#   int main() {
+#     // Indirect calls.
+#     void (*fp_foo)() = foo;
+#     fp_foo();
+#
+#     void (*fp_bar)() = bar;
+#     fp_bar();
+#
+#     char a;
+#     int (*fp_baz)(char) = baz;
+#     fp_baz(a);
+#
+#     // Direct calls.
+#     foo();
+#     bar();
+#     baz(a);
+#
+#     return 0;
+#   }
+
+# CHECK: INDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])
+# CHECK-NEXT: f85c699bb8ef20a2 17a0 17b0
+# CHECK-NEXT: 308e4b8159bc8654 17c0
+# CHECK-NEXT: a9494def81a01dc 17d0
+# CHECK-EMPTY:
+# CHECK-NEXT: INDIRECT CALL TYPES (TYPEID [CALL_SITE_ADDR,])
+# CHECK-NEXT: f85c699bb8ef20a2 17ed 17fb
+# CHECK-NEXT: 308e4b8159bc8654 1810
+# CHECK-EMPTY:
+# CHECK-NEXT: INDIRECT CALL SITES (CALLER_ADDR [CALL_SITE_ADDR,])
+# CHECK-NEXT: 17d0 17ed 17fb 1810
+# CHECK-EMPTY:
+# CHECK-NEXT: DIRECT CALL SITES (CALLER_ADDR [(CALL_SITE_ADDR, TARGET_ADDR),])
+# CHECK-NEXT: 17d0 1815 17a0 181a 17b0 1823 17c0
+
+--- !ELF
+FileHeader:
+  Class:           ELFCLASS64
+  Data:            ELFDATA2LSB
+  Type:            ET_DYN
+  Machine:         EM_X86_64
+  Entry:           0x16E0
+Sections:
+  - Name:            .interp
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC ]
+    Address:         0x2A8
+    AddressAlign:    0x1
+    Content:         2F6C696236342F6C642D6C696E75782D7838362D36342E736F2E3200
+  - Name:            .note.ABI-tag
+    Type:            SHT_NOTE
+    Flags:           [ SHF_ALLOC ]
+    Address:         0x2C4
+    AddressAlign:    0x4
+    Notes:
+      - Name:            GNU
+        Desc:            '00000000030000000200000000000000'
+        Type:            NT_VERSION
+  - Name:            .note.gnu.build-id
+    Type:            SHT_NOTE
+    Flags:           [ SHF_ALLOC ]
+    Address:         0x2E4
+    AddressAlign:    0x4
+    Notes:
+      - Name:            GNU
+        Desc:            3E3D197EC968B0AD7D4A3809E7355CA3BE816929
+        Type:            NT_PRPSINFO
+  - Name:            .dynsym
+    Type:            SHT_DYNSYM
+    Flags:           [ SHF_ALLOC ]
+    Address:         0x308
+    Link:            .dynstr
+    AddressAlign:    0x8
+  - Name:            .gnu.version
+    Type:            SHT_GNU_versym
+    Flags:           [ SHF_ALLOC ]
+    Address:         0x398
+    Link:            .dynsym
+    AddressAlign:    0x2
+    Entries:         [ 0, 2, 1, 1, 3, 1 ]
+  - Name:            .gnu.version_r
+    Type:            SHT_GNU_verneed
+    Flags:           [ SHF_ALLOC ]
+    Address:         0x3A4
+    Link:            .dynstr
+    AddressAlign:    0x4
+    Dependencies:
+      - Version:         1
+        File:            libc.so.6
+        Entries:
+          - Name:            GLIBC_2.2.5
+            Hash:            157882997
+            Flags:           0
+            Other:           3
+          - Name:            GLIBC_2.34
+            Hash:            110530996
+            Flags:           0
+            Other:           2
+  - Name:            .gnu.hash
+    Type:            SHT_GNU_HASH
+    Flags:           [ SHF_ALLOC ]
+    Address:         0x3D8
+    Link:            .dynsym
+    AddressAlign:    0x8
+    Header:
+      SymNdx:          0x6
+      Shift2:          0x1A
+    BloomFilter:     [ 0x0 ]
+    HashBuckets:     [ 0x0 ]
+    HashValues:      [  ]
+  - Name:            .dynstr
+    Type:            SHT_STRTAB
+    Flags:           [ SHF_ALLOC ]
+    Address:         0x3F4
+    AddressAlign:    0x1
+  - Name:            .rela.dyn
+    Type:            SHT_RELA
+    Flags:           [ SHF_ALLOC ]
+    Address:         0x480
+    Link:            .dynsym
+    AddressAlign:    0x8
+    Relocations:
+      - Offset:          0x2890
+        Type:            R_X86_64_RELATIVE
+        Addend:          5904
+      - Offset:          0x2898
+        Type:            R_X86_64_RELATIVE
+        Addend:          5968
+      - Offset:          0x3A80
+        Type:            R_X86_64_RELATIVE
+        Addend:          14976
+      - Offset:          0x2A50
+        Symbol:          __libc_start_main
+        Type:            R_X86_64_GLOB_DAT
+      - Offset:          0x2A58
+        Symbol:          __gmon_start__
+        Type:            R_X86_64_GLOB_DAT
+      - Offset:          0x2A60
+        Symbol:          __register_frame_info
+        Type:            R_X86_64_GLOB_DAT
+      - Offset:          0x2A68
+        Symbol:          __cxa_finalize
+        Type:            R_X86_64_GLOB_DAT
+      - Offset:          0x2A70
+        Symbol:          __deregister_frame_info
+        Type:            R_X86_64_GLOB_DAT
+  - Name:            .rela.plt
+    Type:            SHT_RELA
+    Flags:           [ SHF_ALLOC, SHF_INFO_LINK ]
+    Address:         0x540
+    Link:            .dynsym
+    AddressAlign:    0x8
+    Info:            .got.plt
+    Relocations:
+      - Offset:          0x3AA0
+        Symbol:          __register_frame_info
+        Type:            R_X86_64_JUMP_SLOT
+      - Offset:          0x3AA8
+        Symbol:          __cxa_finalize
+        Type:            R_X86_64_JUMP_SLOT
+      - Offset:          0x3AB0
+        Symbol:          __deregister_frame_info
+        Type:            R_X86_64_JUMP_SLOT
+  - Name:            .rodata
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_MERGE ]
+    Address:         0x588
+    AddressAlign:    0x4
+    EntSize:         0x4
+    Content:         '01000200'
+  - Name:            .eh_frame_hdr
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC ]
+    Address:         0x58C
+    AddressAlign:    0x4
+    Content:         011B033B4000000007000000541100005C0000008411000088000000C4110000A800000014120000C800000024120000E800000034120000080100004412000028010000
+  - Name:            .eh_frame
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC ]
+    Address:         0x5D0
+    AddressAlign:    0x8
+    Content:         1400000000000000017A5200017810011B0C070890010710100000001C000000F010000022000000000000001400000000000000017A5200017810011B0C0708900100001C0000001C000000F41000003800000000410E108602430D06730C07080000001C0000003C000000141100004A00000000410E108602430D0602450C070800001C0000005C000000441100000600000000410E108602430D06410C07080000001C0000007C000000341100000600000000410E108602430D06410C07080000001C0000009C000000241100000E00000000410E108602430D06490C07080000001C000000BC000000141100005B00000000410E108602430D0602560C0708000000000000
+  - Name:            .text
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x16E0
+    AddressAlign:    0x10
+    Content:         31ED4989D15E4889E24883E4F050544531C031C9488D3DD5000000FF154F130000F4CCCCCCCCCCCCCCCCCCCCCCCCCCCC554889E5F6059D230000017402EB27C6059223000001488B05331300004883F8007413488D3DC2EEFFFF488D357F230000E81A0100005DC30F1F840000000000554889E5F605A5230000017402EB39C6059A23000001488B05FB1200004883F800740C488B3D06230000E8F1000000488B05EA1200004883F800740C488D3D69EEFFFFE8E80000005DC3CCCCCCCCCCCC554889E55DC3662E0F1F840000000000554889E55DC3662E0F1F840000000000554889E54088F88845FF31C05DC36690554889E54883EC30C745FC00000000488D05BAFFFFFF488945F0FF55F0488D05BCFFFFFF488945E8FF55E8488D05BEFFFFFF488945D8488B45D80FBE7DE7FFD0E88BFFFFFFE896FFFFFF0FBE7DE7E89DFFFFFF31C04883C4305DC3CC
+  - Name:            .init
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x182C
+    AddressAlign:    0x4
+    Content:         4883EC08488B05211200004885C07402FFD04883C408C3
+  - Name:            .fini
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x1844
+    AddressAlign:    0x4
+    Content:         4883EC084883C408C3
+  - Name:            .plt
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x1850
+    AddressAlign:    0x10
+    Content:         FF353A220000FF253C2200000F1F4000FF253A2200006800000000E9E0FFFFFFFF25322200006801000000E9D0FFFFFFFF252A2200006802000000E9C0FFFFFF
+  - Name:            .init_array
+    Type:            SHT_INIT_ARRAY
+    Flags:           [ SHF_WRITE, SHF_ALLOC ]
+    Address:         0x2890
+    AddressAlign:    0x8
+    Content:         '0000000000000000'
+  - Name:            .fini_array
+    Type:            SHT_FINI_ARRAY
+    Flags:           [ SHF_WRITE, SHF_ALLOC ]
+    Address:         0x2898
+    AddressAlign:    0x8
+    Content:         '0000000000000000'
+  - Name:            .dynamic
+    Type:            SHT_DYNAMIC
+    Flags:           [ SHF_WRITE, SHF_ALLOC ]
+    Address:         0x28A0
+    Link:            .dynstr
+    AddressAlign:    0x8
+    Entries:
+      - Tag:             DT_NEEDED
+        Value:           0x80
+      - Tag:             DT_NEEDED
+        Value:           0x5F
+      - Tag:             DT_FLAGS_1
+        Value:           0x8000000
+      - Tag:             DT_DEBUG
+        Value:           0x0
+      - Tag:             DT_RELA
+        Value:           0x480
+      - Tag:             DT_RELASZ
+        Value:           0xC0
+      - Tag:             DT_RELAENT
+        Value:           0x18
+      - Tag:             DT_RELACOUNT
+        Value:           0x3
+      - Tag:             DT_JMPREL
+        Value:           0x540
+      - Tag:             DT_PLTRELSZ
+        Value:           0x48
+      - Tag:             DT_PLTGOT
+        Value:           0x3A88
+      - Tag:             DT_PLTREL
+        Value:           0x7
+      - Tag:             DT_SYMTAB
+        Value:           0x308
+      - Tag:             DT_SYMENT
+        Value:           0x18
+      - Tag:             DT_STRTAB
+        Value:           0x3F4
+      - Tag:             DT_STRSZ
+        Value:           0x8A
+      - Tag:             DT_GNU_HASH
+        Value:           0x3D8
+      - Tag:             DT_INIT_ARRAY
+        Value:           0x2890
+      - Tag:             DT_INIT_ARRAYSZ
+        Value:           0x8
+      - Tag:             DT_FINI_ARRAY
+        Value:           0x2898
+      - Tag:             DT_FINI_ARRAYSZ
+        Value:           0x8
+      - Tag:             DT_INIT
+        Value:           0x182C
+      - Tag:             DT_FINI
+        Value:           0x1844
+      - Tag:             DT_VERSYM
+        Value:           0x398
+      - Tag:             DT_VERNEED
+        Value:           0x3A4
+      - Tag:             DT_VERNEEDNUM
+        Value:           0x1
+      - Tag:             DT_NULL
+        Value:           0x0
+  - Name:            .got
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_WRITE, SHF_ALLOC ]
+    Address:         0x2A50
+    AddressAlign:    0x8
+    Content:         '00000000000000000000000000000000000000000000000000000000000000000000000000000000'
+  - Name:            .relro_padding
+    Type:            SHT_NOBITS
+    Flags:           [ SHF_WRITE, SHF_ALLOC ]
+    Address:         0x2A78
+    AddressAlign:    0x1
+    Size:            0x588
+  - Name:            .data
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_WRITE, SHF_ALLOC ]
+    Address:         0x3A78
+    AddressAlign:    0x8
+    Content:         '00000000000000000000000000000000'
+  - Name:            .got.plt
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_WRITE, SHF_ALLOC ]
+    Address:         0x3A88
+    AddressAlign:    0x8
+    Content:         A02800000000000000000000000000000000000000000000661800000000000076180000000000008618000000000000
+  - Name:            .bss
+    Type:            SHT_NOBITS
+    Flags:           [ SHF_WRITE, SHF_ALLOC ]
+    Address:         0x3AB8
+    AddressAlign:    0x8
+    Size:            0x49
+  - Name:            .comment
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_MERGE, SHF_STRINGS ]
+    AddressAlign:    0x1
+    EntSize:         0x1
+    Content:         004675636873696120636C616E672076657273696F6E2032322E302E306769742028676974406769746875622E636F6D3A6C6C766D2F6C6C766D2D70726F6A6563742E676974206135343965373363616436303333366438653963303632326165376164383661613635656634636529004C696E6B65723A2046756368736961204C4C442032322E302E302028676974406769746875622E636F6D3A5072616268756B2F6C6C766D2D70726F6A6563742E676974203239343432666166656337373437633632356135653466393937303861633532323139373662663729004675636873696120636C616E672076657273696F6E2032322E302E3067697420282F7573722F6C6F63616C2F676F6F676C652F686F6D652F7072616268756B722F6C6C766D2F6C6C766D2D70726F6A6563742F636C616E67203335383463366161636163363239633531633464316538653038323538633063323466336131363529004675636873696120636C616E672076657273696F6E2032322E302E306769742028676974406769746875622E636F6D3A5072616268756B2F6C6C766D2D70726F6A6563742E67697420323934343266616665633737343763363235613565346639393730386163353232313937366266372900
+  - Name:            .callgraph
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_LINK_ORDER ]
+    Link:            .text
+    AddressAlign:    0x1
+    Content:         0000000000000000A0170000000000000200000000000000A220EFB89B695CF8000000000000000000000000000000000000000000000000B0170000000000000200000000000000A220EFB89B695CF8000000000000000000000000000000000000000000000000C01700000000000002000000000000005486BC59814B8E30000000000000000000000000000000000000000000000000D0170000000000000200000000000000DC011AF8DE94940A0300000000000000A220EFB89B695CF8ED17000000000000A220EFB89B695CF8FB170000000000005486BC59814B8E30101800000000000003000000000000001518000000000000A0170000000000001A18000000000000B0170000000000002318000000000000C017000000000000
+Symbols:
+  - Name:            __abi_tag
+    Type:            STT_OBJECT
+    Section:         .note.ABI-tag
+    Value:           0x2C4
+    Size:            0x20
+  - Name:            crtbegin.c
+    Type:            STT_FILE
+    Index:           SHN_ABS
+  - Name:            __do_init
+    Type:            STT_FUNC
+    Section:         .text
+    Value:           0x1710
+    Size:            0x38
+  - Name:            __do_init.__initialized
+    Type:            STT_OBJECT
+    Section:         .bss
+    Value:           0x3AB8
+    Size:            0x1
+  - Name:            __EH_FRAME_LIST__
+    Type:            STT_OBJECT
+    Section:         .eh_frame
+    Value:           0x5FC
+  - Name:            __do_init.__object
+    Type:            STT_OBJECT
+    Section:         .bss
+    Value:           0x3AC0
+    Size:            0x40
+  - Name:            __do_fini
+    Type:            STT_FUNC
+    Section:         .text
+    Value:           0x1750
+    Size:            0x4A
+  - Name:            __do_fini.__finalized
+    Type:            STT_OBJECT
+    Section:         .bss
+    Value:           0x3B00
+    Size:            0x1
+  - Name:            __init
+    Type:            STT_OBJECT
+    Section:         .init_array
+    Value:           0x2890
+    Size:            0x8
+  - Name:            __fini
+    Type:            STT_OBJECT
+    Section:         .fini_array
+    Value:           0x2898
+    Size:            0x8
+  - Name:            __dso_handle
+    Type:            STT_OBJECT
+    Section:         .data
+    Value:           0x3A80
+    Size:            0x8
+    Other:           [ STV_HIDDEN ]
+  - Name:            call_graph_info.cpp
+    Type:            STT_FILE
+    Index:           SHN_ABS
+  - Name:            crtend.c
+    Type:            STT_FILE
+    Index:           SHN_ABS
+  - Name:            __EH_FRAME_LIST_END__
+    Type:            STT_OBJECT
+    Section:         .eh_frame
+    Value:           0x5D0
+    Size:            0x4
+    Other:           [ STV_HIDDEN ]
+  - Name:            _GLOBAL_OFFSET_TABLE_
+    Section:         .got.plt
+    Value:           0x3A88
+    Other:           [ STV_HIDDEN ]
+  - Name:            _DYNAMIC
+    Section:         .dynamic
+    Value:           0x28A0
+    Other:           [ STV_HIDDEN ]
+  - Name:            _init
+    Type:            STT_FUNC
+    Section:         .init
+    Value:           0x182C
+    Other:           [ STV_HIDDEN ]
+  - Name:            _fini
+    Type:            STT_FUNC
+    Section:         .fini
+    Value:           0x1844
+    Other:           [ STV_HIDDEN ]
+  - Name:            _start
+    Type:            STT_FUNC
+    Section:         .text
+    Binding:         STB_GLOBAL
+    Value:           0x16E0
+    Size:            0x22
+  - Name:            main
+    Type:            STT_FUNC
+    Section:         .text
+    Binding:         STB_GLOBAL
+    Value:           0x17D0
+    Size:            0x5B
+  - Name:            data_start
+    Section:         .data
+    Binding:         STB_WEAK
+    Value:           0x3A78
+  - Name:            _IO_stdin_used
+    Type:            STT_OBJECT
+    Section:         .rodata
+    Binding:         STB_GLOBAL
+    Value:           0x588
+    Size:            0x4
+  - Name:            __libc_start_main
+    Type:            STT_FUNC
+    Binding:         STB_GLOBAL
+  - Name:            __data_start
+    Section:         .data
+    Binding:         STB_GLOBAL
+    Value:           0x3A78
+  - Name:            __gmon_start__
+    Binding:         STB_WEAK
+  - Name:            __register_frame_info
+    Binding:         STB_WEAK
+  - Name:            __cxa_finalize
+    Type:            STT_FUNC
+    Binding:         STB_WEAK
+  - Name:            __deregister_frame_info
+    Binding:         STB_WEAK
+  - Name:            _Z3foov
+    Type:            STT_FUNC
+    Section:         .text
+    Binding:         STB_GLOBAL
+    Value:           0x17A0
+    Size:            0x6
+  - Name:            _Z3barv
+    Type:            STT_FUNC
+    Section:         .text
+    Binding:         STB_GLOBAL
+    Value:           0x17B0
+    Size:            0x6
+  - Name:            _Z3bazc
+    Type:            STT_FUNC
+    Section:         .text
+    Binding:         STB_GLOBAL
+    Value:           0x17C0
+    Size:            0xE
+DynamicSymbols:
+  - Name:            __libc_start_main
+    Type:            STT_FUNC
+    Binding:         STB_GLOBAL
+  - Name:            __gmon_start__
+    Binding:         STB_WEAK
+  - Name:            __register_frame_info
+    Binding:         STB_WEAK
+  - Name:            __cxa_finalize
+    Type:            STT_FUNC
+    Binding:         STB_WEAK
+  - Name:            __deregister_frame_info
+    Binding:         STB_WEAK
+...

>From 6560cfa6a9cf440168dfca48feb3891d6c8888c3 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Tue, 9 Sep 2025 11:27:29 -0700
Subject: [PATCH 07/12] Extend tests to json

---
 .../call-graph-info-callgraph-section.test    | 108 +++++++++++++++---
 llvm/tools/llvm-readobj/ELFDumper.cpp         |  16 +--
 2 files changed, 98 insertions(+), 26 deletions(-)

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
index 60d1c8323f8e8..85a8dce015a62 100644
--- 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
@@ -1,26 +1,96 @@
 ## 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
+# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=GNU
+# RUN: llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=LLVM
+# RUN: llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=JSON
 
-# 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
+# GNU: llvm-readelf: warning: '[[FILE]]': callgraph section has unknown type id for 1 indirect targets.
+# GNU-EMPTY:
+# GNU-NEXT: INDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])
+# GNU-NEXT: UNKNOWN 6
+# GNU-NEXT: 20 a
+# GNU-EMPTY:
+# GNU-NEXT: INDIRECT CALL TYPES (TYPEID [CALL_SITE_ADDR,])
+# GNU-NEXT: 10 9
+# GNU-EMPTY:
+# GNU-NEXT: INDIRECT CALL SITES (CALLER_ADDR [CALL_SITE_ADDR,])
+# GNU-NEXT: 6 9
+# GNU-EMPTY:
+# GNU-NEXT: DIRECT CALL SITES (CALLER_ADDR [(CALL_SITE_ADDR, TARGET_ADDR),])
+# GNU-NEXT: 0 5 5
+
+# LLVM: {{.*}}llvm-readelf: warning: '[[FILE]]': callgraph section has unknown type id for 1 indirect targets.
+# LLVM: callgraph_info {
+# LLVM-NEXT:  unknown_target_types: [0x6]
+# LLVM-NEXT:  indirect_target_types [
+# LLVM-NEXT:    {
+# LLVM-NEXT:     function_address: 0xA
+# LLVM-NEXT:      type_id: 0x20
+# LLVM-NEXT:    }
+# LLVM-NEXT:  ]
+# LLVM-NEXT:  direct_call_sites [
+# LLVM-NEXT:    {
+# LLVM-NEXT:      caller: 0x0
+# LLVM-NEXT:      call_sites [
+# LLVM-NEXT:        {
+# LLVM-NEXT:          call_site: 0x5
+# LLVM-NEXT:          callee: 0x5
+# LLVM-NEXT:        }
+# LLVM-NEXT:      ]
+# LLVM-NEXT:    }
+# LLVM-NEXT:  ]
+# LLVM-NEXT:  indirect_call_sites [
+# LLVM-NEXT:    {
+# LLVM-NEXT:      caller: 0x6
+# LLVM-NEXT:      call_sites: [0x9]
+# LLVM-NEXT:    }
+# LLVM-NEXT:  ]
+# LLVM-NEXT:  indirect_call_types [
+# LLVM-NEXT:    {
+# LLVM-NEXT:      call_site: 0x9
+# LLVM-NEXT:      type_id: 0x10
+# LLVM-NEXT:    }
+# LLVM-NEXT:  ]
+# LLVM-NEXT: }
+
+# JSON: {{.*}}llvm-readelf: warning: '[[FILE]]': callgraph section has unknown type id for 1 indirect targets.
+# JSON: "callgraph_info": {
+# JSON-NEXT:      "unknown_target_types": [
+# JSON-NEXT:        6
+# JSON-NEXT:      ],
+# JSON-NEXT:      "indirect_target_types": [
+# JSON-NEXT:        {
+# JSON-NEXT:          "function_address": 10,
+# JSON-NEXT:          "type_id": 32
+# JSON-NEXT:        }
+# JSON-NEXT:      ],
+# JSON-NEXT:      "direct_call_sites": [
+# JSON-NEXT:        {
+# JSON-NEXT:          "caller": 0,
+# JSON-NEXT:          "call_sites": [
+# JSON-NEXT:            {
+# JSON-NEXT:              "call_site": 5,
+# JSON-NEXT:              "callee": 5
+# JSON-NEXT:            }
+# JSON-NEXT:          ]
+# JSON-NEXT:        }
+# JSON-NEXT:      ],
+# JSON-NEXT:      "indirect_call_sites": [
+# JSON-NEXT:        {
+# JSON-NEXT:          "caller": 6,
+# JSON-NEXT:          "call_sites": [
+# JSON-NEXT:            9
+# JSON-NEXT:          ]
+# JSON-NEXT:        }
+# JSON-NEXT:      ],
+# JSON-NEXT:      "indirect_call_types": [
+# JSON-NEXT:        {
+# JSON-NEXT:          "call_site": 9,
+# JSON-NEXT:          "type_id": 16
+# JSON-NEXT:        }
+# JSON-NEXT:      ]
+# JSON-NEXT:    }
 
 .text
 
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index ea158d80bcb53..8b9d84fdaba71 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -8287,7 +8287,6 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
   // }
 
   {
-    ListScope A(this->W, "indirect_target_types");
     SmallVector<uint64_t, 4> Unknowns;
     for (const auto &El : this->FuncCGInfo) {
       FunctionKind FuncKind = El.second.Kind;
@@ -8298,7 +8297,11 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
       }
     }
     if (!Unknowns.empty())
-      this->W.printHexList("unknown", Unknowns);
+      this->W.printHexList("unknown_target_types", Unknowns);
+  }
+
+  {
+    ListScope ITT(this->W, "indirect_target_types");
     for (auto const &T : this->TypeIdToIndirTargets) {
       for (auto const &Target : T.second) {
         DictScope D(this->W);
@@ -8309,13 +8312,13 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
   }
 
   {
-    ListScope A(this->W, "direct_call_sites");
+    ListScope DCT(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");
+      ListScope CT(this->W, "call_sites");
       for (auto const &CS : F.second.DirectCallSites) {
         DictScope D(this->W);
         this->W.printHex("call_site", CS.CallSite);
@@ -8325,19 +8328,18 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
   }
 
   {
-    ListScope A(this->W, "indirect_call_sites");
+    ListScope ICT(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");
+    ListScope ICT(this->W, "indirect_call_types");
     for (auto const &T : this->TypeIdToIndirCallSites) {
       for (auto const &CS : T.second) {
         DictScope D(this->W);

>From 1ccb2332f28a149095758185895c928b01de4d3c Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Tue, 9 Sep 2025 15:10:53 -0700
Subject: [PATCH 08/12] Expand all tests to include JSON and LLVM formats.

---
 ...call-graph-info-err-invalid-func-kind.test |   2 +
 ...-info-err-malformed-callgraph-section.test |   4 +-
 ...info-err-malformed-callgraph-section2.test |   2 +
 ...info-err-malformed-callgraph-section3.test |   2 +
 ...all-graph-info-invalid-format-version.test |   2 +
 ...-graph-info-warn-no-callgraph-section.test |   5 +-
 .../llvm-readobj/ELF/call-graph-info.test     | 129 +++++++++++++++++-
 7 files changed, 141 insertions(+), 5 deletions(-)

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
index b55717b25fee0..3bea2d057b4d7 100644
--- 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
@@ -3,6 +3,8 @@
 
 # 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
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
 
 # ERR: llvm-readelf: warning: '[[FILE]]': Unknown function kind in .callgraph section.
 
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
index 459d002d35352..b9219cd64e019 100644
--- 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
@@ -2,7 +2,9 @@
 
 # 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
-
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+ 
 # ERR: llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section.
 
 .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
index 2c84469526552..83e7825de0abb 100644
--- 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
@@ -3,6 +3,8 @@
 
 # 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
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
 
 # ERR: llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section.
 
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
index 5bfbd709c78e2..7488d15ca94a7 100644
--- 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
@@ -3,6 +3,8 @@
 
 # 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
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
 
 # ERR: llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section.
 
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
index a1213db081951..3bac1f1850bef 100644
--- 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
@@ -3,6 +3,8 @@
 
 # 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
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
 
 # ERR: llvm-readelf: warning: '[[FILE]]': Unknown format version in .callgraph section.
 
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
index cc904dbed2e67..fe0428f6635f6 100644
--- 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
@@ -2,11 +2,10 @@
 
 # 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
+# RUN: llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
+# RUN: llvm-readelf --elf-output-style=JSON --pretty-print --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
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
index 547b608cc5950..778813197f70b 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
@@ -1,6 +1,8 @@
 ## Tests how --call-graph-info prints the call graph information.
 # RUN: yaml2obj --docnum=1 %s -o %t
-# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
+# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
+# RUN: llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines --check-prefix=LLVM -DFILE=%t
+# RUN: llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines --check-prefix=JSON -DFILE=%t
 
 # Yaml input is obtained by compiling the below source to object with:
 #   clang -fexperimental-call-graph-section test.c -o test.o
@@ -54,6 +56,131 @@
 # CHECK-NEXT: DIRECT CALL SITES (CALLER_ADDR [(CALL_SITE_ADDR, TARGET_ADDR),])
 # CHECK-NEXT: 17d0 1815 17a0 181a 17b0 1823 17c0
 
+# LLVM: callgraph_info {
+# LLVM-NEXT:   indirect_target_types [
+# LLVM-NEXT:     {
+# LLVM-NEXT:       function_address: 0x17A0
+# LLVM-NEXT:       type_id: 0xF85C699BB8EF20A2
+# LLVM-NEXT:     }
+# LLVM-NEXT:     {
+# LLVM-NEXT:       function_address: 0x17B0
+# LLVM-NEXT:       type_id: 0xF85C699BB8EF20A2
+# LLVM-NEXT:     }
+# LLVM-NEXT:     {
+# LLVM-NEXT:       function_address: 0x17C0
+# LLVM-NEXT:       type_id: 0x308E4B8159BC8654
+# LLVM-NEXT:     }
+# LLVM-NEXT:     {
+# LLVM-NEXT:       function_address: 0x17D0
+# LLVM-NEXT:       type_id: 0xA9494DEF81A01DC
+# LLVM-NEXT:     }
+# LLVM-NEXT:   ]
+# LLVM-NEXT:   direct_call_sites [
+# LLVM-NEXT:     {
+# LLVM-NEXT:       caller: 0x17D0
+# LLVM-NEXT:       call_sites [
+# LLVM-NEXT:         {
+# LLVM-NEXT:           call_site: 0x1815
+# LLVM-NEXT:           callee: 0x17A0
+# LLVM-NEXT:         }
+# LLVM-NEXT:         {
+# LLVM-NEXT:           call_site: 0x181A
+# LLVM-NEXT:           callee: 0x17B0
+# LLVM-NEXT:         }
+# LLVM-NEXT:         {
+# LLVM-NEXT:           call_site: 0x1823
+# LLVM-NEXT:           callee: 0x17C0
+# LLVM-NEXT:         }
+# LLVM-NEXT:       ]
+# LLVM-NEXT:     }
+# LLVM-NEXT:   ]
+# LLVM-NEXT:   indirect_call_sites [
+# LLVM-NEXT:     {
+# LLVM-NEXT:       caller: 0x17D0
+# LLVM-NEXT:       call_sites: [0x17ED, 0x17FB, 0x1810]
+# LLVM-NEXT:     }
+# LLVM-NEXT:   ]
+# LLVM-NEXT:   indirect_call_types [
+# LLVM-NEXT:     {
+# LLVM-NEXT:       call_site: 0x17ED
+# LLVM-NEXT:       type_id: 0xF85C699BB8EF20A2
+# LLVM-NEXT:     }
+# LLVM-NEXT:     {
+# LLVM-NEXT:       call_site: 0x17FB
+# LLVM-NEXT:       type_id: 0xF85C699BB8EF20A2
+# LLVM-NEXT:     }
+# LLVM-NEXT:     {
+# LLVM-NEXT:       call_site: 0x1810
+# LLVM-NEXT:       type_id: 0x308E4B8159BC8654
+# LLVM-NEXT:     }
+# LLVM-NEXT:   ]
+# LLVM-NEXT: }
+
+#JSON:  "callgraph_info": {
+#JSON-NEXT:      "indirect_target_types": [
+#JSON-NEXT:        {
+#JSON-NEXT:          "function_address": 6048,
+#JSON-NEXT:          "type_id": 17896295136807035042
+#JSON-NEXT:        },
+#JSON-NEXT:        {
+#JSON-NEXT:          "function_address": 6064,
+#JSON-NEXT:          "type_id": 17896295136807035042
+#JSON-NEXT:        },
+#JSON-NEXT:        {
+#JSON-NEXT:          "function_address": 6080,
+#JSON-NEXT:          "type_id": 3498816979441845844
+#JSON-NEXT:        },
+#JSON-NEXT:        {
+#JSON-NEXT:          "function_address": 6096,
+#JSON-NEXT:          "type_id": 762397922298560988
+#JSON-NEXT:        }
+#JSON-NEXT:      ],
+#JSON-NEXT:      "direct_call_sites": [
+#JSON-NEXT:        {
+#JSON-NEXT:          "caller": 6096,
+#JSON-NEXT:          "call_sites": [
+#JSON-NEXT:            {
+#JSON-NEXT:              "call_site": 6165,
+#JSON-NEXT:              "callee": 6048
+#JSON-NEXT:            },
+#JSON-NEXT:            {
+#JSON-NEXT:              "call_site": 6170,
+#JSON-NEXT:              "callee": 6064
+#JSON-NEXT:            },
+#JSON-NEXT:            {
+#JSON-NEXT:              "call_site": 6179,
+#JSON-NEXT:              "callee": 6080
+#JSON-NEXT:            }
+#JSON-NEXT:          ]
+#JSON-NEXT:        }
+#JSON-NEXT:      ],
+#JSON-NEXT:      "indirect_call_sites": [
+#JSON-NEXT:        {
+#JSON-NEXT:          "caller": 6096,
+#JSON-NEXT:          "call_sites": [
+#JSON-NEXT:            6125,
+#JSON-NEXT:            6139,
+#JSON-NEXT:            6160
+#JSON-NEXT:          ]
+#JSON-NEXT:        }
+#JSON-NEXT:      ],
+#JSON-NEXT:      "indirect_call_types": [
+#JSON-NEXT:        {
+#JSON-NEXT:          "call_site": 6125,
+#JSON-NEXT:          "type_id": 17896295136807035042
+#JSON-NEXT:        },
+#JSON-NEXT:        {
+#JSON-NEXT:          "call_site": 6139,
+#JSON-NEXT:          "type_id": 17896295136807035042
+#JSON-NEXT:        },
+#JSON-NEXT:        {
+#JSON-NEXT:          "call_site": 6160,
+#JSON-NEXT:          "type_id": 3498816979441845844
+#JSON-NEXT:        }
+#JSON-NEXT:      ]
+#JSON-NEXT:    }
+#JSON-NEXT:  }
+
 --- !ELF
 FileHeader:
   Class:           ELFCLASS64

>From 9aeaa7c5248d77d846a65742df738f96f989d949 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Tue, 9 Sep 2025 15:32:18 -0700
Subject: [PATCH 09/12] Improve tests. Match full lines.

---
 .../ELF/call-graph-info-callgraph-section.test            | 4 ++--
 .../ELF/call-graph-info-err-invalid-func-kind.test        | 8 ++++----
 .../call-graph-info-err-malformed-callgraph-section.test  | 8 ++++----
 .../call-graph-info-err-malformed-callgraph-section2.test | 8 ++++----
 .../call-graph-info-err-malformed-callgraph-section3.test | 8 ++++----
 .../ELF/call-graph-info-invalid-format-version.test       | 8 ++++----
 .../ELF/call-graph-info-warn-no-callgraph-section.test    | 8 ++++----
 7 files changed, 26 insertions(+), 26 deletions(-)

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
index 85a8dce015a62..fd957e983760d 100644
--- 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
@@ -1,11 +1,11 @@
 ## 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-prefix=GNU
+# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --match-full-lines --check-prefix=GNU
 # RUN: llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=LLVM
 # RUN: llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=JSON
 
-# GNU: llvm-readelf: warning: '[[FILE]]': callgraph section has unknown type id for 1 indirect targets.
+# GNU: {{.*}}llvm-readelf: warning: '[[FILE]]': callgraph section has unknown type id for 1 indirect targets.
 # GNU-EMPTY:
 # GNU-NEXT: INDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])
 # GNU-NEXT: UNKNOWN 6
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
index 3bea2d057b4d7..ea1152ddb45e1 100644
--- 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
@@ -2,11 +2,11 @@
 ## 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
-# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
-# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
 
-# ERR: llvm-readelf: warning: '[[FILE]]': Unknown function kind in .callgraph section.
+# ERR: {{.*}}llvm-readelf: warning: '[[FILE]]': Unknown function kind in .callgraph section.
 
 .text
 .globl _Z3foov
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
index b9219cd64e019..c1f7585e2795c 100644
--- 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
@@ -1,11 +1,11 @@
 ## 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
-# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
-# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
  
-# ERR: llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section.
+# ERR: {{.*}}llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section. Unexpected size.
 
 .text
 .globl _Z3foov
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
index 83e7825de0abb..4a0a82b5ca99e 100644
--- 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
@@ -2,11 +2,11 @@
 ## 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
-# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
-# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
 
-# ERR: llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section.
+# ERR: {{.*}}llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section. Parsing error.
 
 .text
 .globl _Z3foov
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
index 7488d15ca94a7..825bafb2e0e93 100644
--- 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
@@ -2,11 +2,11 @@
 ## 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
-# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
-# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
 
-# ERR: llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section.
+# ERR: {{.*}}llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section. Parsing error.
 
 .text
 .globl _Z3foov
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
index 3bac1f1850bef..f7edebc0931b5 100644
--- 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
@@ -2,11 +2,11 @@
 ## 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
-# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
-# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
 
-# ERR: llvm-readelf: warning: '[[FILE]]': Unknown format version in .callgraph section.
+# ERR: {{.*}}llvm-readelf: warning: '[[FILE]]': Unknown format version in .callgraph section.
 
 .text
 .globl _Z3foov
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
index fe0428f6635f6..f9e82d0b62bc9 100644
--- 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
@@ -1,11 +1,11 @@
 ## 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
-# RUN: llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
-# RUN: llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
+# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
+# RUN: llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
+# RUN: llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
 
-# CHECK: llvm-readelf: warning: '[[FILE]]': No .callgraph section found.
+# CHECK: {{.*}}llvm-readelf: warning: '[[FILE]]': No .callgraph section found.
 
 .text
 .globl _Z3foov

>From f42a736c63725965a1c2b114986097b87a0f693e Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Wed, 17 Sep 2025 10:07:26 -0700
Subject: [PATCH 10/12] Make call graph processing agnostic of target
 architecture pointer width size.

---
 .../call-graph-info-callgraph-section.test    |   6 +-
 ...raph-info-err-invalid-format-version.test} |   2 +-
 ...call-graph-info-err-invalid-func-kind.test |   4 +-
 ...-info-err-malformed-callgraph-section.test |   2 +-
 ...info-err-malformed-callgraph-section2.test |   2 +-
 ...info-err-malformed-callgraph-section3.test |   2 +-
 ...l-graph-info-err-no-callgraph-section.test |  14 +
 ...-graph-info-warn-no-callgraph-section.test |  14 -
 .../llvm-readobj/ELF/call-graph-info.test     |   2 +-
 llvm/tools/llvm-readobj/ELFDumper.cpp         | 272 ++++++++++++------
 10 files changed, 204 insertions(+), 116 deletions(-)
 rename llvm/test/tools/llvm-readobj/ELF/{call-graph-info-invalid-format-version.test => call-graph-info-err-invalid-format-version.test} (87%)
 create mode 100644 llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-no-callgraph-section.test
 delete 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
index fd957e983760d..57410588a2a7e 100644
--- 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
@@ -5,7 +5,7 @@
 # RUN: llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=LLVM
 # RUN: llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=JSON
 
-# GNU: {{.*}}llvm-readelf: warning: '[[FILE]]': callgraph section has unknown type id for 1 indirect targets.
+# GNU: {{.*}}llvm-readelf: warning: '[[FILE]]': .callgraph section has unknown type id for 1 indirect targets.
 # GNU-EMPTY:
 # GNU-NEXT: INDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])
 # GNU-NEXT: UNKNOWN 6
@@ -20,7 +20,7 @@
 # GNU-NEXT: DIRECT CALL SITES (CALLER_ADDR [(CALL_SITE_ADDR, TARGET_ADDR),])
 # GNU-NEXT: 0 5 5
 
-# LLVM: {{.*}}llvm-readelf: warning: '[[FILE]]': callgraph section has unknown type id for 1 indirect targets.
+# LLVM: {{.*}}llvm-readelf: warning: '[[FILE]]': .callgraph section has unknown type id for 1 indirect targets.
 # LLVM: callgraph_info {
 # LLVM-NEXT:  unknown_target_types: [0x6]
 # LLVM-NEXT:  indirect_target_types [
@@ -54,7 +54,7 @@
 # LLVM-NEXT:  ]
 # LLVM-NEXT: }
 
-# JSON: {{.*}}llvm-readelf: warning: '[[FILE]]': callgraph section has unknown type id for 1 indirect targets.
+# JSON: {{.*}}llvm-readelf: warning: '[[FILE]]': .callgraph section has unknown type id for 1 indirect targets.
 # JSON: "callgraph_info": {
 # JSON-NEXT:      "unknown_target_types": [
 # JSON-NEXT:        6
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-err-invalid-format-version.test
similarity index 87%
rename from llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test
rename to llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-format-version.test
index f7edebc0931b5..233d4bb8f63d4 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-format-version.test
@@ -6,7 +6,7 @@
 # RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
 # RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
 
-# ERR: {{.*}}llvm-readelf: warning: '[[FILE]]': Unknown format version in .callgraph section.
+# ERR: {{.*}}llvm-readelf: error: 'Unknown value': Unknown format version value [1] in .callgraph section.
 
 .text
 .globl _Z3foov
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
index ea1152ddb45e1..eef4eefc7da4c 100644
--- 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
@@ -6,7 +6,7 @@
 # RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
 # RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
 
-# ERR: {{.*}}llvm-readelf: warning: '[[FILE]]': Unknown function kind in .callgraph section.
+# ERR: {{.*}}llvm-readelf: error: 'While reading call graph info's [FunctionKind] for function at [0x0]': Unknown value [4].
 
 .text
 .globl _Z3foov
@@ -17,5 +17,5 @@ _Z3foov:
 .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.
+.quad 4   #< Invalid function kind: must be one of 0, 1, 2, 3.
 .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
index c1f7585e2795c..7872b5d749ef9 100644
--- 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
@@ -5,7 +5,7 @@
 # RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
 # RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
  
-# ERR: {{.*}}llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section. Unexpected size.
+# ERR: {{.*}}llvm-readelf: error: 'While reading call graph info's [number of indirect callsites] for function at [0x0]': unexpected end of data at offset 0x19 while reading [0x18, 0x20)
 
 .text
 .globl _Z3foov
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
index 4a0a82b5ca99e..38dc0eca939a7 100644
--- 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
@@ -6,7 +6,7 @@
 # RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
 # RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
 
-# ERR: {{.*}}llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section. Parsing error.
+# ERR: {{.*}}llvm-readelf: error: 'While reading call graph info's [indirect target type id] for function at [0x0]': unexpected end of data at offset 0x20 while reading [0x20, 0x28)
 
 .text
 .globl _Z3foov
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
index 825bafb2e0e93..88204bd28f494 100644
--- 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
@@ -6,7 +6,7 @@
 # RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
 # RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
 
-# ERR: {{.*}}llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section. Parsing error.
+# ERR: {{.*}}llvm-readelf: error: 'While reading call graph info's [direct callsite PC] for function at [0x0]': unexpected end of data at offset 0x28 while reading [0x28, 0x30)
 
 .text
 .globl _Z3foov
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-no-callgraph-section.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-no-callgraph-section.test
new file mode 100644
index 0000000000000..cece28f776b5f
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-no-callgraph-section.test
@@ -0,0 +1,14 @@
+## 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: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
+
+# CHECK: {{.*}}llvm-readelf: error: 'Missing section': No .callgraph section found.
+
+.text
+.globl _Z3foov
+.type _Z3foov, at function
+_Z3foov:
+ callq _Z3foov at PLT
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
deleted file mode 100644
index f9e82d0b62bc9..0000000000000
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-no-callgraph-section.test
+++ /dev/null
@@ -1,14 +0,0 @@
-## 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 --match-full-lines -DFILE=%t
-# RUN: llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
-# RUN: llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
-
-# CHECK: {{.*}}llvm-readelf: warning: '[[FILE]]': No .callgraph section found.
-
-.text
-.globl _Z3foov
-.type _Z3foov, at function
-_Z3foov:
- callq _Z3foov at PLT
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
index 778813197f70b..26ae628135d22 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
@@ -1,6 +1,6 @@
 ## Tests how --call-graph-info prints the call graph information.
 # RUN: yaml2obj --docnum=1 %s -o %t
-# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
+# llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
 # RUN: llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines --check-prefix=LLVM -DFILE=%t
 # RUN: llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines --check-prefix=JSON -DFILE=%t
 
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index 8b9d84fdaba71..491c3f6f0851b 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -47,7 +47,10 @@
 #include "llvm/Support/ARMBuildAttributes.h"
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/Compiler.h"
+#include "llvm/Support/DataExtractor.h"
+#include "llvm/Support/Debug.h"
 #include "llvm/Support/Endian.h"
+#include "llvm/Support/Error.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/Format.h"
 #include "llvm/Support/FormatVariadic.h"
@@ -74,6 +77,7 @@
 #include <optional>
 #include <string>
 #include <system_error>
+#include <utility>
 #include <vector>
 
 using namespace llvm;
@@ -183,30 +187,31 @@ struct GroupSection {
   std::vector<GroupMember> Members;
 };
 
-// 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,
-  };
+// Call graph function kind.
+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,
+};
+
+// Per-function call graph information.
+template <typename AddrType> struct FunctionCallgraphInfoImpl {
   FunctionKind Kind;
   struct DirectCallSite {
-    uint64_t CallSite;
-    uint64_t Callee;
-    DirectCallSite(uint64_t CallSite, uint64_t Callee)
+    AddrType CallSite;
+    AddrType Callee;
+    DirectCallSite(AddrType CallSite, AddrType Callee)
         : CallSite(CallSite), Callee(Callee) {}
   };
   SmallVector<DirectCallSite> DirectCallSites;
-  SmallVector<uint64_t> IndirectCallSites;
+  SmallVector<AddrType> IndirectCallSites;
 };
-typedef FunctionCallgraphInfo::FunctionKind FunctionKind;
 
 namespace {
 
@@ -458,15 +463,18 @@ template <typename ELFT> class ELFDumper : public ObjDumper {
       const typename SFrameParser<ELFT::Endianness>::FDERange::iterator FDE,
       ArrayRef<Relocation<ELFT>> Relocations, const Elf_Shdr *RelocSymTab);
 
+  using FunctionCallgraphInfo =
+      ::FunctionCallgraphInfoImpl<typename ELFT::uint>;
+
   // Callgraph - Main data structure to maintain per function callgraph
   // information.
-  MapVector<uint64_t, FunctionCallgraphInfo> FuncCGInfo;
+  MapVector<typename ELFT::uint, 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;
+  MapVector<uint64_t, SmallVector<typename ELFT::uint>> TypeIdToIndirCallSites;
   // 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;
+  MapVector<uint64_t, SmallVector<typename ELFT::uint>> TypeIdToIndirTargets;
   // Callgraph - Read callgraph section and process its contents to populate
   // Callgraph related data structures which will be used to dump callgraph
   // info. Returns false if there is no .callgraph section in the input file.
@@ -5278,93 +5286,170 @@ getCallGraphSection(const object::ELFObjectFile<ELFT> &ObjF) {
 }
 
 template <class ELFT> bool ELFDumper<ELFT>::processCallGraphSection() {
-  std::optional<object::SectionRef> CallGraphSection =
-      getCallGraphSection(ObjF);
-  if (!CallGraphSection.has_value()) {
-    reportUniqueWarning("No .callgraph section found.");
+  const Elf_Shdr *CGSection = findSectionByName(".callgraph");
+  if (!CGSection) {
+    Error NoSectionErr = createError("No .callgraph section found.");    
+    reportError(std::move(NoSectionErr), "Missing section");
     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.");
-    exit(1);
+  Expected<ArrayRef<uint8_t>> SectionBytesOrErr =
+      Obj.getSectionContents(*CGSection);
+  if (!SectionBytesOrErr) {
+    Error SectionReadErr = SectionBytesOrErr.takeError();
+    reportError(std::move(SectionReadErr), "Unable to read the .callgraph section");
+    return false;
   }
 
-  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()) {
-      reportUniqueWarning("Malformed .callgraph section. Parsing error.");
-      exit(1);
-    }
-    return *It++;
+  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));
   };
 
-  while (CGHasNext()) {
+  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 = CGNext();
-    if (FormatVersionNumber != 0) {
-      reportUniqueWarning("Unknown format version in .callgraph section.");
-      exit(1);
-    }
-    // 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.");
-      exit(1);
-    }
-    // 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);
+    uint64_t FormatVersionNumber = Data.getU64(&Offset, &CGSectionErr);
+
+    if (CGSectionErr) {
+      reportError(std::move(CGSectionErr),
+                  "While reading call graph info FormatVersionNumber");
+      return false;
     }
-    // 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);
+
+    if (FormatVersionNumber != 0) {      
+      Error FormatErr = createError("Unknown format version value [" + std::to_string(FormatVersionNumber) + "] in .callgraph section.");
+      reportError(std::move(FormatErr), "Unknown value");
+      return false;
     }
-  }
 
-  // Sort function info by function PC.
-  llvm::sort(FuncCGInfo,
-             [](const auto &A, const auto &B) { return A.first < B.first; });
+    // 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");
+      return false;
+    }
 
-  uint64_t NotListedCount = 0;
-  uint64_t UnknownCount = 0;
-  for (const auto &El : FuncCGInfo) {
-    NotListedCount += El.second.Kind == FunctionKind::NOT_LISTED;
-    UnknownCount += El.second.Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
+    if (FuncCGInfo.find(FuncAddr) != FuncCGInfo.end()) {
+      Error DuplicatePcErr = createError("for function PC: 0x" + Twine::utohexstr(FuncAddr));
+      reportError(std::move(DuplicatePcErr), "Duplicate call graph entry");      
+      return false;
+    }
+
+    // Read function kind.
+    uint64_t KindVal = Data.getU64(&Offset, &CGSectionErr);
+    if (CGSectionErr) {
+      PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr), "Kind");
+      return false;
+    }
+
+    if (KindVal > 3) {
+      Error KindErr = createError("Unknown value [" + std::to_string(KindVal) + "].");
+      PrintMalformedError(KindErr, Twine::utohexstr(FuncAddr), "FunctionKind");
+      return false;
+    }
+
+    FunctionKind Kind = static_cast<FunctionKind>(KindVal);
+    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");
+        return false;
+      }
+      TypeIdToIndirTargets[TypeId].push_back(FuncAddr);
+    }
+    if (Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID)
+      UnknownCount++;
+    if (Kind == FunctionKind::NOT_LISTED)
+      NotListedCount++;
+
+    using FunctionCallgraphInfo =
+        ::FunctionCallgraphInfoImpl<typename ELFT::uint>;
+
+    // Create a new entry for this function.
+    FunctionCallgraphInfo CGInfo;
+    CGInfo.Kind = Kind;
+
+    // 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");
+      return false;
+    }
+
+    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");
+        return false;
+      }
+      typename ELFT::uint CallSitePc =
+          Data.getUnsigned(&Offset, sizeof(CallSitePc), &CGSectionErr);
+      if (CGSectionErr) {
+        PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+                            "indirect callsite PC");
+        return false;
+      }
+      TypeIdToIndirCallSites[TypeId].push_back(CallSitePc);
+      CGInfo.IndirectCallSites.push_back(CallSitePc);
+    }
+
+    // Read number of direct call sites for this function.
+    uint64_t NumDirectCallSites = Data.getU64(&Offset, &CGSectionErr);
+    if (CGSectionErr) {
+      PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+                          "number of direct callsites");
+      return false;
+    }
+    // Read direct call sites and populate FuncCGInfo.
+    for (uint64_t I = 0; I < NumDirectCallSites; ++I) {
+      typename ELFT::uint CallSite =
+          Data.getUnsigned(&Offset, sizeof(CallSite), &CGSectionErr);
+      if (CGSectionErr) {
+        PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+                            "direct callsite PC");
+        return false;
+      }
+      typename ELFT::uint Callee =
+          Data.getUnsigned(&Offset, sizeof(Callee), &CGSectionErr);
+      if (CGSectionErr) {
+        PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+                            "indirect callee PC");
+        return false;
+      }
+      CGInfo.DirectCallSites.emplace_back(CallSite, Callee);
+    }
+    FuncCGInfo[FuncAddr] = CGInfo;
   }
+
   if (NotListedCount)
-    reportUniqueWarning("callgraph section does not have information for " +
+    reportUniqueWarning(".callgraph section does not have information for " +
                         std::to_string(NotListedCount) + " functions.");
   if (UnknownCount)
-    reportUniqueWarning("callgraph section has unknown type id for " +
+    reportUniqueWarning(".callgraph section has unknown type id for " +
                         std::to_string(UnknownCount) + " indirect targets.");
+
+  // Sort function info by function PC.
+  llvm::sort(FuncCGInfo,
+             [](const auto &A, const auto &B) { return A.first < B.first; });
   return true;
 }
 
@@ -5422,6 +5507,9 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
     }
   }
 
+  using FunctionCallgraphInfo =
+      ::FunctionCallgraphInfoImpl<typename ELFT::uint>;
+
   // 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),])";
@@ -5430,7 +5518,7 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
     auto FuncDirCallSites = El.second.DirectCallSites;
     if (!FuncDirCallSites.empty()) {
       OS << "\n" << format("%lx", CallerPc);
-      for (const FunctionCallgraphInfo::DirectCallSite &DCS :
+      for (typename FunctionCallgraphInfo::DirectCallSite &DCS :
            FuncDirCallSites) {
         OS << " " << format("%lx", DCS.CallSite) << " "
            << format("%lx", DCS.Callee);

>From 9f0a1398a445d3ca894212840939d5c7ad8ffde1 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Wed, 17 Sep 2025 10:46:54 -0700
Subject: [PATCH 11/12] Remove unnecessary function returns after reportError
 calls which bottoms out to llvm_unreachable.

---
 llvm/tools/llvm-readobj/ELFDumper.cpp | 52 ++++++++-------------------
 1 file changed, 14 insertions(+), 38 deletions(-)

diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index 491c3f6f0851b..4ffa1905b28d9 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -5288,17 +5288,16 @@ getCallGraphSection(const object::ELFObjectFile<ELFT> &ObjF) {
 template <class ELFT> bool ELFDumper<ELFT>::processCallGraphSection() {
   const Elf_Shdr *CGSection = findSectionByName(".callgraph");
   if (!CGSection) {
-    Error NoSectionErr = createError("No .callgraph section found.");    
+    Error NoSectionErr = createError("No .callgraph section found.");
     reportError(std::move(NoSectionErr), "Missing section");
-    return false;
   }
 
   Expected<ArrayRef<uint8_t>> SectionBytesOrErr =
       Obj.getSectionContents(*CGSection);
   if (!SectionBytesOrErr) {
     Error SectionReadErr = SectionBytesOrErr.takeError();
-    reportError(std::move(SectionReadErr), "Unable to read the .callgraph section");
-    return false;
+    reportError(std::move(SectionReadErr),
+                "Unable to read the .callgraph section");
   }
 
   auto PrintMalformedError = [&](Error &E, Twine FuncPC, StringRef Component) {
@@ -5323,55 +5322,44 @@ template <class ELFT> bool ELFDumper<ELFT>::processCallGraphSection() {
     // Format version number.
     uint64_t FormatVersionNumber = Data.getU64(&Offset, &CGSectionErr);
 
-    if (CGSectionErr) {
+    if (CGSectionErr)
       reportError(std::move(CGSectionErr),
                   "While reading call graph info FormatVersionNumber");
-      return false;
-    }
 
     if (FormatVersionNumber != 0) {      
       Error FormatErr = createError("Unknown format version value [" + std::to_string(FormatVersionNumber) + "] in .callgraph section.");
       reportError(std::move(FormatErr), "Unknown value");
-      return false;
     }
 
     // Read function address.
     typename ELFT::uint FuncAddr =
         Data.getUnsigned(&Offset, sizeof(FuncAddr), &CGSectionErr);
-    if (CGSectionErr) {
+    if (CGSectionErr)
       reportError(std::move(CGSectionErr),
                   "While reading call graph info function entry PC");
-      return false;
-    }
 
     if (FuncCGInfo.find(FuncAddr) != FuncCGInfo.end()) {
       Error DuplicatePcErr = createError("for function PC: 0x" + Twine::utohexstr(FuncAddr));
-      reportError(std::move(DuplicatePcErr), "Duplicate call graph entry");      
-      return false;
+      reportError(std::move(DuplicatePcErr), "Duplicate call graph entry");
     }
 
     // Read function kind.
     uint64_t KindVal = Data.getU64(&Offset, &CGSectionErr);
-    if (CGSectionErr) {
+    if (CGSectionErr)
       PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr), "Kind");
-      return false;
-    }
 
     if (KindVal > 3) {
       Error KindErr = createError("Unknown value [" + std::to_string(KindVal) + "].");
       PrintMalformedError(KindErr, Twine::utohexstr(FuncAddr), "FunctionKind");
-      return false;
     }
 
     FunctionKind Kind = static_cast<FunctionKind>(KindVal);
     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) {
+      if (CGSectionErr)
         PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
                             "indirect type id");
-        return false;
-      }
       TypeIdToIndirTargets[TypeId].push_back(FuncAddr);
     }
     if (Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID)
@@ -5388,53 +5376,41 @@ template <class ELFT> bool ELFDumper<ELFT>::processCallGraphSection() {
 
     // Read number of indirect call sites for this function.
     uint64_t NumIndirectCallSites = Data.getU64(&Offset, &CGSectionErr);
-    if (CGSectionErr) {
+    if (CGSectionErr)
       PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
                           "number of indirect callsites");
-      return false;
-    }
 
     for (unsigned long I = 0; I < NumIndirectCallSites; I++) {
       uint64_t TypeId = Data.getU64(&Offset, &CGSectionErr);
-      if (CGSectionErr) {
+      if (CGSectionErr)
         PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
                             "indirect target type id");
-        return false;
-      }
       typename ELFT::uint CallSitePc =
           Data.getUnsigned(&Offset, sizeof(CallSitePc), &CGSectionErr);
-      if (CGSectionErr) {
+      if (CGSectionErr)
         PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
                             "indirect callsite PC");
-        return false;
-      }
       TypeIdToIndirCallSites[TypeId].push_back(CallSitePc);
       CGInfo.IndirectCallSites.push_back(CallSitePc);
     }
 
     // Read number of direct call sites for this function.
     uint64_t NumDirectCallSites = Data.getU64(&Offset, &CGSectionErr);
-    if (CGSectionErr) {
+    if (CGSectionErr)
       PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
                           "number of direct callsites");
-      return false;
-    }
     // Read direct call sites and populate FuncCGInfo.
     for (uint64_t I = 0; I < NumDirectCallSites; ++I) {
       typename ELFT::uint CallSite =
           Data.getUnsigned(&Offset, sizeof(CallSite), &CGSectionErr);
-      if (CGSectionErr) {
+      if (CGSectionErr)
         PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
                             "direct callsite PC");
-        return false;
-      }
       typename ELFT::uint Callee =
           Data.getUnsigned(&Offset, sizeof(Callee), &CGSectionErr);
-      if (CGSectionErr) {
+      if (CGSectionErr)
         PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
                             "indirect callee PC");
-        return false;
-      }
       CGInfo.DirectCallSites.emplace_back(CallSite, Callee);
     }
     FuncCGInfo[FuncAddr] = CGInfo;

>From 3499c2d2d5c075f64992d266ba61b03d2acbf8e1 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Wed, 17 Sep 2025 12:06:01 -0700
Subject: [PATCH 12/12] Formatting fixes.

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

diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index 4ffa1905b28d9..95db1518cc0aa 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -5326,8 +5326,10 @@ template <class ELFT> bool ELFDumper<ELFT>::processCallGraphSection() {
       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.");
+    if (FormatVersionNumber != 0) {
+      Error FormatErr = createError("Unknown format version value [" +
+                                    std::to_string(FormatVersionNumber) +
+                                    "] in .callgraph section.");
       reportError(std::move(FormatErr), "Unknown value");
     }
 
@@ -5339,7 +5341,8 @@ template <class ELFT> bool ELFDumper<ELFT>::processCallGraphSection() {
                   "While reading call graph info function entry PC");
 
     if (FuncCGInfo.find(FuncAddr) != FuncCGInfo.end()) {
-      Error DuplicatePcErr = createError("for function PC: 0x" + Twine::utohexstr(FuncAddr));
+      Error DuplicatePcErr =
+          createError("for function PC: 0x" + Twine::utohexstr(FuncAddr));
       reportError(std::move(DuplicatePcErr), "Duplicate call graph entry");
     }
 
@@ -5349,7 +5352,8 @@ template <class ELFT> bool ELFDumper<ELFT>::processCallGraphSection() {
       PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr), "Kind");
 
     if (KindVal > 3) {
-      Error KindErr = createError("Unknown value [" + std::to_string(KindVal) + "].");
+      Error KindErr =
+          createError("Unknown value [" + std::to_string(KindVal) + "].");
       PrintMalformedError(KindErr, Twine::utohexstr(FuncAddr), "FunctionKind");
     }
 



More information about the llvm-commits mailing list