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

Prabhu Rajasekaran via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 8 09:05:45 PDT 2025


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

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

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

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

>From 1f1c2fede557bd105c637e5626c6fd2ccd14e1fe Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Mon, 8 Sep 2025 09:05:21 -0700
Subject: [PATCH 2/2] 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;



More information about the llvm-commits mailing list