[llvm] [llvm-readobj] Print all callgraph sections (PR #198929)

Prabhu Rajasekaran via llvm-commits llvm-commits at lists.llvm.org
Fri Jul 17 09:39:29 PDT 2026


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

>From 291f13969b5f25fe27b1fdd59a49a51987768569 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Wed, 20 May 2026 16:00:27 -0700
Subject: [PATCH 1/5] [llvm-readobj] Print all callgraph sections

---
 .../ELF/call-graph-info-warn-malformed.test   |  37 +++-
 llvm/tools/llvm-readobj/ELFDumper.cpp         | 174 +++++++++---------
 2 files changed, 125 insertions(+), 86 deletions(-)

diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-malformed.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-malformed.test
index 345c97c79ec29..4312f33a2219a 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-malformed.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-malformed.test
@@ -293,7 +293,21 @@ Symbols:
 # LLVM-FIRST-CGSECTION-NEXT:       NumIndirectTargetTypeIDs: 0
 # LLVM-FIRST-CGSECTION-NEXT:       IndirectTypeIDs: []
 # LLVM-FIRST-CGSECTION-NEXT:     }
-# LLVM-FIRST-CGSECTION-NEXT:    ]
+# LLVM-FIRST-CGSECTION-NEXT:   ]
+# LLVM-FIRST-CGSECTION-NEXT:   CallGraph [
+# LLVM-FIRST-CGSECTION-NEXT:     Function {
+# LLVM-FIRST-CGSECTION-NEXT:       Names: [bar]
+# LLVM-FIRST-CGSECTION-NEXT:       Address: 0x17A0
+# LLVM-FIRST-CGSECTION-NEXT:       Version: 0
+# LLVM-FIRST-CGSECTION-NEXT:       IsIndirectTarget: Yes
+# LLVM-FIRST-CGSECTION-NEXT:       TypeID: 0x3ECBEEF531F74424
+# LLVM-FIRST-CGSECTION-NEXT:       NumDirectCallees: 0
+# LLVM-FIRST-CGSECTION-NEXT:       DirectCallees [
+# LLVM-FIRST-CGSECTION-NEXT:       ]
+# LLVM-FIRST-CGSECTION-NEXT:       NumIndirectTargetTypeIDs: 0
+# LLVM-FIRST-CGSECTION-NEXT:       IndirectTypeIDs: []
+# LLVM-FIRST-CGSECTION-NEXT:     }
+# LLVM-FIRST-CGSECTION-NEXT:   ]
 # LLVM-FIRST-CGSECTION-NOT: {{.}}
 
 # JSON-FIRST-CGSECTION:     "CallGraph": [
@@ -312,6 +326,23 @@ Symbols:
 # JSON-FIRST-CGSECTION-NEXT:          "IndirectTypeIDs": []
 # JSON-FIRST-CGSECTION-NEXT:        }
 # JSON-FIRST-CGSECTION-NEXT:      }
+# JSON-FIRST-CGSECTION-NEXT:    ],
+# JSON-FIRST-CGSECTION-NEXT:    "CallGraph": [
+# JSON-FIRST-CGSECTION-NEXT:      {
+# JSON-FIRST-CGSECTION-NEXT:        "Function": {
+# JSON-FIRST-CGSECTION-NEXT:          "Names": [
+# JSON-FIRST-CGSECTION-NEXT:            "bar"
+# JSON-FIRST-CGSECTION-NEXT:          ],
+# JSON-FIRST-CGSECTION-NEXT:          "Address": 6048,
+# JSON-FIRST-CGSECTION-NEXT:          "Version": 0,
+# JSON-FIRST-CGSECTION-NEXT:          "IsIndirectTarget": true,
+# JSON-FIRST-CGSECTION-NEXT:          "TypeID": 4524972987496481828,
+# JSON-FIRST-CGSECTION-NEXT:          "NumDirectCallees": 0,
+# JSON-FIRST-CGSECTION-NEXT:          "DirectCallees": [],
+# JSON-FIRST-CGSECTION-NEXT:          "NumIndirectTargetTypeIDs": 0,
+# JSON-FIRST-CGSECTION-NEXT:          "IndirectTypeIDs": []
+# JSON-FIRST-CGSECTION-NEXT:        }
+# JSON-FIRST-CGSECTION-NEXT:      }
 # JSON-FIRST-CGSECTION-NEXT:    ]
 # JSON-FIRST-CGSECTION-NEXT:   }
 # JSON-FIRST-CGSECTION-NEXT:  ]
@@ -349,6 +380,10 @@ Symbols:
     Type:     STT_FUNC
     Section:  .text
     Value:    0x1790
+  - Name:     bar
+    Type:     STT_FUNC
+    Section:  .text
+    Value:    0x17A0
 
 ## Check warning when the relocation section linked to SHT_LLVM_CALL_GRAPH has an invalid sh_link.
 # RUN: yaml2obj --docnum=8 -DLINK=255 -DINFO=.llvm_call_graph %s -o %t13
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index 0169e7fb0fff8..6d411822e1e15 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -8252,101 +8252,105 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
     return;
   }
 
-  // Process and print the first SHT_LLVM_CALL_GRAPH type section found.
-  if (!this->processCallGraphSection(MapOrErr->begin()->first) ||
-      this->FuncCGInfos.empty())
-    return;
+  for (const auto &CGMapEntry : *MapOrErr) {
+    const Elf_Shdr *CGSection = CGMapEntry.first;
+    const Elf_Shdr *CGRelSection = CGMapEntry.second;
 
-  std::vector<Relocation<ELFT>> Relocations;
-  const Elf_Shdr *RelocSymTab = nullptr;
-  if (this->Obj.getHeader().e_type == ELF::ET_REL) {
-    const Elf_Shdr *CGRelSection = MapOrErr->front().second;
-    if (CGRelSection) {
-      Expected<const typename ELFT::Shdr *> SymtabOrErr =
-          this->Obj.getSection(CGRelSection->sh_link);
-      if (!SymtabOrErr) {
-        reportWarning(createError("invalid section linked to " +
-                                  this->describe(*CGRelSection) + ": " +
-                                  toString(SymtabOrErr.takeError())),
-                      this->FileName);
-        return;
+    this->FuncCGInfos.clear();
+    if (!this->processCallGraphSection(CGSection) ||
+        this->FuncCGInfos.empty())
+      continue;
+
+    std::vector<Relocation<ELFT>> Relocations;
+    const Elf_Shdr *RelocSymTab = nullptr;
+    if (this->Obj.getHeader().e_type == ELF::ET_REL) {
+      if (CGRelSection) {
+        Expected<const typename ELFT::Shdr *> SymtabOrErr =
+            this->Obj.getSection(CGRelSection->sh_link);
+        if (!SymtabOrErr) {
+          reportWarning(createError("invalid section linked to " +
+                                    this->describe(*CGRelSection) + ": " +
+                                    toString(SymtabOrErr.takeError())),
+                        this->FileName);
+          return;
+        }
+        RelocSymTab = *SymtabOrErr;
+        this->forEachRelocationDo(
+            *CGRelSection, [&](const auto &R, ...) { Relocations.push_back(R); });
+        llvm::stable_sort(Relocations, [](const auto &LHS, const auto &RHS) {
+          return LHS.Offset < RHS.Offset;
+        });
       }
-      RelocSymTab = *SymtabOrErr;
-      this->forEachRelocationDo(
-          *CGRelSection, [&](const auto &R, ...) { Relocations.push_back(R); });
-      llvm::stable_sort(Relocations, [](const auto &LHS, const auto &RHS) {
-        return LHS.Offset < RHS.Offset;
-      });
     }
-  }
 
-  auto GetFunctionNames = [&](uint64_t FuncAddr) {
-    SmallVector<uint32_t> FuncSymIndexes =
-        this->getSymbolIndexesForFunctionAddress(FuncAddr, std::nullopt);
-    SmallVector<std::string> FuncSymNames;
-    FuncSymNames.reserve(FuncSymIndexes.size());
-    for (uint32_t Index : FuncSymIndexes)
-      FuncSymNames.push_back(this->getStaticSymbolName(Index));
-    return FuncSymNames;
-  };
+    auto GetFunctionNames = [&](uint64_t FuncAddr) {
+      SmallVector<uint32_t> FuncSymIndexes =
+          this->getSymbolIndexesForFunctionAddress(FuncAddr, std::nullopt);
+      SmallVector<std::string> FuncSymNames;
+      FuncSymNames.reserve(FuncSymIndexes.size());
+      for (uint32_t Index : FuncSymIndexes)
+        FuncSymNames.push_back(this->getStaticSymbolName(Index));
+      return FuncSymNames;
+    };
 
-  auto PrintNonRelocatableFuncSymbol = [&](uint64_t FuncEntryPC) {
-    SmallVector<std::string> FuncSymNames = GetFunctionNames(FuncEntryPC);
-    if (!FuncSymNames.empty())
-      W.printList("Names", FuncSymNames);
-    W.printHex("Address", FuncEntryPC);
-  };
+    auto PrintNonRelocatableFuncSymbol = [&](uint64_t FuncEntryPC) {
+      SmallVector<std::string> FuncSymNames = GetFunctionNames(FuncEntryPC);
+      if (!FuncSymNames.empty())
+        W.printList("Names", FuncSymNames);
+      W.printHex("Address", FuncEntryPC);
+    };
 
-  auto PrintRelocatableFuncSymbol = [&](uint64_t RelocOffset) {
-    auto R = llvm::find_if(Relocations, [&](const Relocation<ELFT> &R) {
-      return R.Offset == RelocOffset;
-    });
-    if (R == Relocations.end()) {
-      this->reportUniqueWarning("missing relocation for symbol at offset " +
-                                Twine(RelocOffset));
-      return;
-    }
-    Expected<RelSymbol<ELFT>> RelSymOrErr =
-        this->getRelocationTarget(*R, RelocSymTab);
-    if (!RelSymOrErr) {
-      this->reportUniqueWarning(RelSymOrErr.takeError());
-      return;
-    }
-    W.printString("Name", RelSymOrErr->Name);
-  };
+    auto PrintRelocatableFuncSymbol = [&](uint64_t RelocOffset) {
+      auto R = llvm::find_if(Relocations, [&](const Relocation<ELFT> &R) {
+        return R.Offset == RelocOffset;
+      });
+      if (R == Relocations.end()) {
+        this->reportUniqueWarning("missing relocation for symbol at offset " +
+                                  Twine(RelocOffset));
+        return;
+      }
+      Expected<RelSymbol<ELFT>> RelSymOrErr =
+          this->getRelocationTarget(*R, RelocSymTab);
+      if (!RelSymOrErr) {
+        this->reportUniqueWarning(RelSymOrErr.takeError());
+        return;
+      }
+      W.printString("Name", RelSymOrErr->Name);
+    };
 
-  auto PrintFunc = [&](uint64_t FuncPC) {
-    uint64_t FuncEntryPC = FuncPC;
-    // In ARM thumb mode the LSB of the function pointer is set to 1. Since this
-    // detail is unncessary in call graph reconstruction, we are clearing this
-    // bit to facilate tooling.
-    if (this->Obj.getHeader().e_machine == ELF::EM_ARM)
-      FuncEntryPC = FuncPC & ~1;
-    if (this->Obj.getHeader().e_type == ELF::ET_REL)
-      PrintRelocatableFuncSymbol(FuncEntryPC);
-    else
-      PrintNonRelocatableFuncSymbol(FuncEntryPC);
-  };
+    auto PrintFunc = [&](uint64_t FuncPC) {
+      uint64_t FuncEntryPC = FuncPC;
+      // In ARM thumb mode the LSB of the function pointer is set to 1. Since this
+      // detail is unncessary in call graph reconstruction, we are clearing this
+      // bit to facilate tooling.
+      if (this->Obj.getHeader().e_machine == ELF::EM_ARM)
+        FuncEntryPC = FuncPC & ~1;
+      if (this->Obj.getHeader().e_type == ELF::ET_REL)
+        PrintRelocatableFuncSymbol(FuncEntryPC);
+      else
+        PrintNonRelocatableFuncSymbol(FuncEntryPC);
+    };
 
-  ListScope CGI(W, "CallGraph");
-  for (const FunctionCallgraphInfo &CGInfo : this->FuncCGInfos) {
-    DictScope D(W, "Function");
-    PrintFunc(CGInfo.FunctionAddress);
-    W.printNumber("Version", CGInfo.FormatVersionNumber);
-    W.printBoolean("IsIndirectTarget", CGInfo.IsIndirectTarget);
-    W.printHex("TypeID", CGInfo.FunctionTypeID);
-    W.printNumber("NumDirectCallees", CGInfo.DirectCallees.size());
-    {
-      ListScope DCs(W, "DirectCallees");
-      for (uint64_t CalleePC : CGInfo.DirectCallees) {
-        DictScope D(W);
-        PrintFunc(CalleePC);
+    ListScope CGI(W, "CallGraph");
+    for (const FunctionCallgraphInfo &CGInfo : this->FuncCGInfos) {
+      DictScope D(W, "Function");
+      PrintFunc(CGInfo.FunctionAddress);
+      W.printNumber("Version", CGInfo.FormatVersionNumber);
+      W.printBoolean("IsIndirectTarget", CGInfo.IsIndirectTarget);
+      W.printHex("TypeID", CGInfo.FunctionTypeID);
+      W.printNumber("NumDirectCallees", CGInfo.DirectCallees.size());
+      {
+        ListScope DCs(W, "DirectCallees");
+        for (uint64_t CalleePC : CGInfo.DirectCallees) {
+          DictScope D(W);
+          PrintFunc(CalleePC);
+        }
       }
+      W.printNumber("NumIndirectTargetTypeIDs", CGInfo.IndirectTypeIDs.size());
+      SmallVector<uint64_t, 4> IndirectTypeIDsList(CGInfo.IndirectTypeIDs.begin(),
+                                                   CGInfo.IndirectTypeIDs.end());
+      W.printHexList("IndirectTypeIDs", ArrayRef(IndirectTypeIDsList));
     }
-    W.printNumber("NumIndirectTargetTypeIDs", CGInfo.IndirectTypeIDs.size());
-    SmallVector<uint64_t, 4> IndirectTypeIDsList(CGInfo.IndirectTypeIDs.begin(),
-                                                 CGInfo.IndirectTypeIDs.end());
-    W.printHexList("IndirectTypeIDs", ArrayRef(IndirectTypeIDsList));
   }
 }
 

>From a1207f76e9d4dda2d90fada99533390360357481 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Thu, 21 May 2026 21:00:00 +0000
Subject: [PATCH 2/5] Fix comment and format.

---
 .../ELF/call-graph-info-warn-malformed.test    |  6 +-----
 llvm/tools/llvm-readobj/ELFDumper.cpp          | 18 +++++++++---------
 2 files changed, 10 insertions(+), 14 deletions(-)

diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-malformed.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-malformed.test
index 4312f33a2219a..50fbe35576561 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-malformed.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-malformed.test
@@ -271,7 +271,7 @@ Symbols:
   - Name:  baz
 ...
 
-## Check that the first call graph section is processed and the second call graph section is ignored.
+## Check that both the call graph sections are processed and printed.
 ## Note that we do not expect an object file to have more than one call graph section as we merge the
 ## call graph sections when there are more than one.
 # RUN: yaml2obj --docnum=7 %s -o %t12
@@ -308,7 +308,6 @@ Symbols:
 # LLVM-FIRST-CGSECTION-NEXT:       IndirectTypeIDs: []
 # LLVM-FIRST-CGSECTION-NEXT:     }
 # LLVM-FIRST-CGSECTION-NEXT:   ]
-# LLVM-FIRST-CGSECTION-NOT: {{.}}
 
 # JSON-FIRST-CGSECTION:     "CallGraph": [
 # JSON-FIRST-CGSECTION-NEXT:      {
@@ -344,9 +343,6 @@ Symbols:
 # JSON-FIRST-CGSECTION-NEXT:        }
 # JSON-FIRST-CGSECTION-NEXT:      }
 # JSON-FIRST-CGSECTION-NEXT:    ]
-# JSON-FIRST-CGSECTION-NEXT:   }
-# JSON-FIRST-CGSECTION-NEXT:  ]
-# JSON-FIRST-CGSECTION-NOT: {{.}}
 
 --- !ELF
 FileHeader:
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index 6d411822e1e15..c505f478f13cf 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -8257,8 +8257,7 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
     const Elf_Shdr *CGRelSection = CGMapEntry.second;
 
     this->FuncCGInfos.clear();
-    if (!this->processCallGraphSection(CGSection) ||
-        this->FuncCGInfos.empty())
+    if (!this->processCallGraphSection(CGSection) || this->FuncCGInfos.empty())
       continue;
 
     std::vector<Relocation<ELFT>> Relocations;
@@ -8275,8 +8274,9 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
           return;
         }
         RelocSymTab = *SymtabOrErr;
-        this->forEachRelocationDo(
-            *CGRelSection, [&](const auto &R, ...) { Relocations.push_back(R); });
+        this->forEachRelocationDo(*CGRelSection, [&](const auto &R, ...) {
+          Relocations.push_back(R);
+        });
         llvm::stable_sort(Relocations, [](const auto &LHS, const auto &RHS) {
           return LHS.Offset < RHS.Offset;
         });
@@ -8320,9 +8320,9 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
 
     auto PrintFunc = [&](uint64_t FuncPC) {
       uint64_t FuncEntryPC = FuncPC;
-      // In ARM thumb mode the LSB of the function pointer is set to 1. Since this
-      // detail is unncessary in call graph reconstruction, we are clearing this
-      // bit to facilate tooling.
+      // In ARM thumb mode the LSB of the function pointer is set to 1. Since
+      // this detail is unncessary in call graph reconstruction, we are clearing
+      // this bit to facilate tooling.
       if (this->Obj.getHeader().e_machine == ELF::EM_ARM)
         FuncEntryPC = FuncPC & ~1;
       if (this->Obj.getHeader().e_type == ELF::ET_REL)
@@ -8347,8 +8347,8 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
         }
       }
       W.printNumber("NumIndirectTargetTypeIDs", CGInfo.IndirectTypeIDs.size());
-      SmallVector<uint64_t, 4> IndirectTypeIDsList(CGInfo.IndirectTypeIDs.begin(),
-                                                   CGInfo.IndirectTypeIDs.end());
+      SmallVector<uint64_t, 4> IndirectTypeIDsList(
+          CGInfo.IndirectTypeIDs.begin(), CGInfo.IndirectTypeIDs.end());
       W.printHexList("IndirectTypeIDs", ArrayRef(IndirectTypeIDsList));
     }
   }

>From d3a99db5c42f68ef9b344786133b1763b83d03ae Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Mon, 29 Jun 2026 22:31:10 +0000
Subject: [PATCH 3/5] Clean up tests.

---
 .../ELF/call-graph-info-warn-malformed.test   |   8 +-
 .../llvm-readobj/ELF/call-graph-info.test     | 107 ++++++++++++++++++
 llvm/tools/llvm-readobj/ELFDumper.cpp         |  25 ++--
 3 files changed, 123 insertions(+), 17 deletions(-)

diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-malformed.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-malformed.test
index 50fbe35576561..61845a76f05ce 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-malformed.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-malformed.test
@@ -293,8 +293,6 @@ Symbols:
 # LLVM-FIRST-CGSECTION-NEXT:       NumIndirectTargetTypeIDs: 0
 # LLVM-FIRST-CGSECTION-NEXT:       IndirectTypeIDs: []
 # LLVM-FIRST-CGSECTION-NEXT:     }
-# LLVM-FIRST-CGSECTION-NEXT:   ]
-# LLVM-FIRST-CGSECTION-NEXT:   CallGraph [
 # LLVM-FIRST-CGSECTION-NEXT:     Function {
 # LLVM-FIRST-CGSECTION-NEXT:       Names: [bar]
 # LLVM-FIRST-CGSECTION-NEXT:       Address: 0x17A0
@@ -324,9 +322,7 @@ Symbols:
 # JSON-FIRST-CGSECTION-NEXT:          "NumIndirectTargetTypeIDs": 0,
 # JSON-FIRST-CGSECTION-NEXT:          "IndirectTypeIDs": []
 # JSON-FIRST-CGSECTION-NEXT:        }
-# JSON-FIRST-CGSECTION-NEXT:      }
-# JSON-FIRST-CGSECTION-NEXT:    ],
-# JSON-FIRST-CGSECTION-NEXT:    "CallGraph": [
+# JSON-FIRST-CGSECTION-NEXT:      },
 # JSON-FIRST-CGSECTION-NEXT:      {
 # JSON-FIRST-CGSECTION-NEXT:        "Function": {
 # JSON-FIRST-CGSECTION-NEXT:          "Names": [
@@ -361,7 +357,7 @@ Sections:
       0x90, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ## FunctionEntryPC: 0x1790
       0x24, 0x44, 0xF7, 0x31, 0xF5, 0xEE, 0xCB, 0x3E, ## FunctionTypeID: 0x3ECBEEF531F74424
     ]
-  ## This following second call graph section will be completely ignored.
+  ## This following second call graph section will also be processed.
   - Type:   SHT_LLVM_CALL_GRAPH
     Link:   .text
     ContentArray: [
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 4d333a26d6de5..603a14923a98e 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
@@ -574,3 +574,110 @@ Symbols:
     Type:     STT_FUNC
     Section:  .text
     Value:    0x1000
+
+## Check handling of multiple call graph sections in a relocatable object file.
+# RUN: yaml2obj --docnum=5 %s -o %t5
+# RUN: llvm-readobj --elf-output-style=LLVM --call-graph-info %t5 2>&1 | \
+# RUN:   FileCheck %s --match-full-lines --check-prefix=LLVM-MULTI-RELOC -DFILE=%t5
+# RUN: llvm-readobj --elf-output-style=JSON --pretty-print --call-graph-info %t5 2>&1 | \
+# RUN:   FileCheck %s --match-full-lines --check-prefix=JSON-MULTI-RELOC -DFILE=%t5
+
+# LLVM-MULTI-RELOC:      CallGraph [
+# LLVM-MULTI-RELOC-NEXT:   Function {
+# LLVM-MULTI-RELOC-NEXT:     Name: foo
+# LLVM-MULTI-RELOC-NEXT:     Version: 0
+# LLVM-MULTI-RELOC-NEXT:     IsIndirectTarget: Yes
+# LLVM-MULTI-RELOC-NEXT:     TypeID: 0xF85C699BB8EF20A2
+# LLVM-MULTI-RELOC-NEXT:     NumDirectCallees: 0
+# LLVM-MULTI-RELOC-NEXT:     DirectCallees [
+# LLVM-MULTI-RELOC-NEXT:     ]
+# LLVM-MULTI-RELOC-NEXT:     NumIndirectTargetTypeIDs: 0
+# LLVM-MULTI-RELOC-NEXT:     IndirectTypeIDs: []
+# LLVM-MULTI-RELOC-NEXT:   }
+# LLVM-MULTI-RELOC-NEXT:   Function {
+# LLVM-MULTI-RELOC-NEXT:     Name: bar
+# LLVM-MULTI-RELOC-NEXT:     Version: 0
+# LLVM-MULTI-RELOC-NEXT:     IsIndirectTarget: Yes
+# LLVM-MULTI-RELOC-NEXT:     TypeID: 0xF85C699BB8EF20A2
+# LLVM-MULTI-RELOC-NEXT:     NumDirectCallees: 0
+# LLVM-MULTI-RELOC-NEXT:     DirectCallees [
+# LLVM-MULTI-RELOC-NEXT:     ]
+# LLVM-MULTI-RELOC-NEXT:     NumIndirectTargetTypeIDs: 0
+# LLVM-MULTI-RELOC-NEXT:     IndirectTypeIDs: []
+# LLVM-MULTI-RELOC-NEXT:   }
+# LLVM-MULTI-RELOC-NEXT: ]
+
+# JSON-MULTI-RELOC:     "CallGraph": [
+# JSON-MULTI-RELOC-NEXT:      {
+# JSON-MULTI-RELOC-NEXT:        "Function": {
+# JSON-MULTI-RELOC-NEXT:          "Name": "foo",
+# JSON-MULTI-RELOC-NEXT:          "Version": 0,
+# JSON-MULTI-RELOC-NEXT:          "IsIndirectTarget": true,
+# JSON-MULTI-RELOC-NEXT:          "TypeID": 17896295136807035042,
+# JSON-MULTI-RELOC-NEXT:          "NumDirectCallees": 0,
+# JSON-MULTI-RELOC-NEXT:          "DirectCallees": [],
+# JSON-MULTI-RELOC-NEXT:          "NumIndirectTargetTypeIDs": 0,
+# JSON-MULTI-RELOC-NEXT:          "IndirectTypeIDs": []
+# JSON-MULTI-RELOC-NEXT:        }
+# JSON-MULTI-RELOC-NEXT:      },
+# JSON-MULTI-RELOC-NEXT:      {
+# JSON-MULTI-RELOC-NEXT:        "Function": {
+# JSON-MULTI-RELOC-NEXT:          "Name": "bar",
+# JSON-MULTI-RELOC-NEXT:          "Version": 0,
+# JSON-MULTI-RELOC-NEXT:          "IsIndirectTarget": true,
+# JSON-MULTI-RELOC-NEXT:          "TypeID": 17896295136807035042,
+# JSON-MULTI-RELOC-NEXT:          "NumDirectCallees": 0,
+# JSON-MULTI-RELOC-NEXT:          "DirectCallees": [],
+# JSON-MULTI-RELOC-NEXT:          "NumIndirectTargetTypeIDs": 0,
+# JSON-MULTI-RELOC-NEXT:          "IndirectTypeIDs": []
+# JSON-MULTI-RELOC-NEXT:        }
+# JSON-MULTI-RELOC-NEXT:      }
+# JSON-MULTI-RELOC-NEXT:    ]
+
+--- !ELF
+FileHeader:
+  Class:    ELFCLASS64
+  Data:     ELFDATA2LSB
+  Type:     ET_REL
+  Machine:  EM_X86_64
+Sections:
+  - Name:   .text
+    Type:   SHT_PROGBITS
+  - Name:   .llvm.callgraph.1
+    Type:   SHT_LLVM_CALL_GRAPH
+    Link:   .text
+    ContentArray: [
+      ## --- foo ---
+      0x00, ## Format Version
+      0x01, ## Flags
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ## Address of foo
+      0xA2, 0x20, 0xEF, 0xB8, 0x9B, 0x69, 0x5C, 0xF8  ## TypeID of foo
+    ]
+  - Name:   .rela.llvm.callgraph.1
+    Type:   SHT_RELA
+    Info:   .llvm.callgraph.1
+    Relocations:
+      - Offset:  0x2
+        Symbol:  foo
+        Type:    R_X86_64_64
+  - Name:   .llvm.callgraph.2
+    Type:   SHT_LLVM_CALL_GRAPH
+    Link:   .text
+    ContentArray: [
+      ## --- bar ---
+      0x00, ## Format Version
+      0x01, ## Flags
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ## Address of bar
+      0xA2, 0x20, 0xEF, 0xB8, 0x9B, 0x69, 0x5C, 0xF8  ## TypeID of bar
+    ]
+  - Name:   .rela.llvm.callgraph.2
+    Type:   SHT_RELA
+    Info:   .llvm.callgraph.2
+    Relocations:
+      - Offset:  0x2
+        Symbol:  bar
+        Type:    R_X86_64_64
+Symbols:
+  - Name:  foo
+  - Name:  bar
+...
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index a9daef67f812e..26b3a98996bc7 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -450,15 +450,13 @@ template <typename ELFT> class ELFDumper : public ObjDumper {
       const SFrameParser<ELFT::Endianness> &Parser,
       const typename SFrameParser<ELFT::Endianness>::FDERange::iterator FDE,
       ArrayRef<Relocation<ELFT>> Relocations, const Elf_Shdr *RelocSymTab);
-  // Callgraph - Main data structure to maintain per function callgraph
-  // information.
-  SmallVector<FunctionCallgraphInfo, 16> FuncCGInfos;
-
   // Read the SHT_LLVM_CALL_GRAPH type section and process its contents to
   // populate call graph related data structures which will be used to dump call
   // graph info. Returns false if there is no SHT_LLVM_CALL_GRAPH type section
   // in the input file.
-  bool processCallGraphSection(const Elf_Shdr *CGSection);
+  bool
+  processCallGraphSection(const Elf_Shdr *CGSection,
+                          SmallVectorImpl<FunctionCallgraphInfo> &FuncCGInfos);
 
   std::string getProgramHeadersNumString();
 
@@ -5337,7 +5335,9 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCGProfile() {
 }
 
 template <class ELFT>
-bool ELFDumper<ELFT>::processCallGraphSection(const Elf_Shdr *CGSection) {
+bool ELFDumper<ELFT>::processCallGraphSection(
+    const Elf_Shdr *CGSection,
+    SmallVectorImpl<FunctionCallgraphInfo> &FuncCGInfos) {
   ArrayRef<uint8_t> Contents = cantFail(Obj.getSectionContents(*CGSection));
   DataExtractor Data(Contents, Obj.isLE());
   DataExtractor::Cursor C(0);
@@ -8338,12 +8338,15 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
     return;
   }
 
+  std::unique_ptr<ListScope> CGI;
+  SmallVector<FunctionCallgraphInfo, 16> FuncCGInfos;
   for (const auto &CGMapEntry : *MapOrErr) {
     const Elf_Shdr *CGSection = CGMapEntry.first;
     const Elf_Shdr *CGRelSection = CGMapEntry.second;
 
-    this->FuncCGInfos.clear();
-    if (!this->processCallGraphSection(CGSection) || this->FuncCGInfos.empty())
+    FuncCGInfos.clear();
+    if (!this->processCallGraphSection(CGSection, FuncCGInfos) ||
+        FuncCGInfos.empty())
       continue;
 
     std::vector<Relocation<ELFT>> Relocations;
@@ -8416,9 +8419,9 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
       else
         PrintNonRelocatableFuncSymbol(FuncEntryPC);
     };
-
-    ListScope CGI(W, "CallGraph");
-    for (const FunctionCallgraphInfo &CGInfo : this->FuncCGInfos) {
+    if (!CGI)
+      CGI = std::make_unique<ListScope>(W, "CallGraph");
+    for (const FunctionCallgraphInfo &CGInfo : FuncCGInfos) {
       DictScope D(W, "Function");
       PrintFunc(CGInfo.FunctionAddress);
       W.printNumber("Version", CGInfo.FormatVersionNumber);

>From 1c432972b578c1cfe56547fddc39095a750a3944 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Thu, 16 Jul 2026 18:55:52 +0000
Subject: [PATCH 4/5] Address review comments.

---
 .../ELF/call-graph-info-warn-malformed.test   | 110 +-----------------
 .../llvm-readobj/ELF/call-graph-info.test     | 107 +++++++++++++++++
 llvm/tools/llvm-readobj/ELFDumper.cpp         |  42 ++++---
 3 files changed, 129 insertions(+), 130 deletions(-)

diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-malformed.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-malformed.test
index 61845a76f05ce..b22f29e5aeeb9 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-malformed.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-malformed.test
@@ -271,114 +271,8 @@ Symbols:
   - Name:  baz
 ...
 
-## Check that both the call graph sections are processed and printed.
-## Note that we do not expect an object file to have more than one call graph section as we merge the
-## call graph sections when there are more than one.
-# RUN: yaml2obj --docnum=7 %s -o %t12
-# RUN: llvm-readobj --elf-output-style=LLVM --call-graph-info %t12 2>&1 | \
-# RUN:   FileCheck %s -DFILE=%t12 --check-prefix=LLVM-FIRST-CGSECTION --match-full-lines
-# RUN: llvm-readobj --elf-output-style=JSON --pretty-print --call-graph-info %t12 2>&1 | \
-# RUN:   FileCheck %s -DFILE=%t12 --check-prefix=JSON-FIRST-CGSECTION --match-full-lines
-
-# LLVM-FIRST-CGSECTION:      CallGraph [
-# LLVM-FIRST-CGSECTION-NEXT:     Function {
-# LLVM-FIRST-CGSECTION-NEXT:       Names: [foo]
-# LLVM-FIRST-CGSECTION-NEXT:       Address: 0x1790
-# LLVM-FIRST-CGSECTION-NEXT:       Version: 0
-# LLVM-FIRST-CGSECTION-NEXT:       IsIndirectTarget: Yes
-# LLVM-FIRST-CGSECTION-NEXT:       TypeID: 0x3ECBEEF531F74424
-# LLVM-FIRST-CGSECTION-NEXT:       NumDirectCallees: 0
-# LLVM-FIRST-CGSECTION-NEXT:       DirectCallees [
-# LLVM-FIRST-CGSECTION-NEXT:       ]
-# LLVM-FIRST-CGSECTION-NEXT:       NumIndirectTargetTypeIDs: 0
-# LLVM-FIRST-CGSECTION-NEXT:       IndirectTypeIDs: []
-# LLVM-FIRST-CGSECTION-NEXT:     }
-# LLVM-FIRST-CGSECTION-NEXT:     Function {
-# LLVM-FIRST-CGSECTION-NEXT:       Names: [bar]
-# LLVM-FIRST-CGSECTION-NEXT:       Address: 0x17A0
-# LLVM-FIRST-CGSECTION-NEXT:       Version: 0
-# LLVM-FIRST-CGSECTION-NEXT:       IsIndirectTarget: Yes
-# LLVM-FIRST-CGSECTION-NEXT:       TypeID: 0x3ECBEEF531F74424
-# LLVM-FIRST-CGSECTION-NEXT:       NumDirectCallees: 0
-# LLVM-FIRST-CGSECTION-NEXT:       DirectCallees [
-# LLVM-FIRST-CGSECTION-NEXT:       ]
-# LLVM-FIRST-CGSECTION-NEXT:       NumIndirectTargetTypeIDs: 0
-# LLVM-FIRST-CGSECTION-NEXT:       IndirectTypeIDs: []
-# LLVM-FIRST-CGSECTION-NEXT:     }
-# LLVM-FIRST-CGSECTION-NEXT:   ]
-
-# JSON-FIRST-CGSECTION:     "CallGraph": [
-# JSON-FIRST-CGSECTION-NEXT:      {
-# JSON-FIRST-CGSECTION-NEXT:        "Function": {
-# JSON-FIRST-CGSECTION-NEXT:          "Names": [
-# JSON-FIRST-CGSECTION-NEXT:            "foo"
-# JSON-FIRST-CGSECTION-NEXT:          ],
-# JSON-FIRST-CGSECTION-NEXT:          "Address": 6032,
-# JSON-FIRST-CGSECTION-NEXT:          "Version": 0,
-# JSON-FIRST-CGSECTION-NEXT:          "IsIndirectTarget": true,
-# JSON-FIRST-CGSECTION-NEXT:          "TypeID": 4524972987496481828,
-# JSON-FIRST-CGSECTION-NEXT:          "NumDirectCallees": 0,
-# JSON-FIRST-CGSECTION-NEXT:          "DirectCallees": [],
-# JSON-FIRST-CGSECTION-NEXT:          "NumIndirectTargetTypeIDs": 0,
-# JSON-FIRST-CGSECTION-NEXT:          "IndirectTypeIDs": []
-# JSON-FIRST-CGSECTION-NEXT:        }
-# JSON-FIRST-CGSECTION-NEXT:      },
-# JSON-FIRST-CGSECTION-NEXT:      {
-# JSON-FIRST-CGSECTION-NEXT:        "Function": {
-# JSON-FIRST-CGSECTION-NEXT:          "Names": [
-# JSON-FIRST-CGSECTION-NEXT:            "bar"
-# JSON-FIRST-CGSECTION-NEXT:          ],
-# JSON-FIRST-CGSECTION-NEXT:          "Address": 6048,
-# JSON-FIRST-CGSECTION-NEXT:          "Version": 0,
-# JSON-FIRST-CGSECTION-NEXT:          "IsIndirectTarget": true,
-# JSON-FIRST-CGSECTION-NEXT:          "TypeID": 4524972987496481828,
-# JSON-FIRST-CGSECTION-NEXT:          "NumDirectCallees": 0,
-# JSON-FIRST-CGSECTION-NEXT:          "DirectCallees": [],
-# JSON-FIRST-CGSECTION-NEXT:          "NumIndirectTargetTypeIDs": 0,
-# JSON-FIRST-CGSECTION-NEXT:          "IndirectTypeIDs": []
-# JSON-FIRST-CGSECTION-NEXT:        }
-# JSON-FIRST-CGSECTION-NEXT:      }
-# JSON-FIRST-CGSECTION-NEXT:    ]
-
---- !ELF
-FileHeader:
-  Class:    ELFCLASS64
-  Data:     ELFDATA2LSB
-  Type:     ET_DYN
-Sections:
-  - Name:   .text
-    Type:   SHT_PROGBITS
-  - Type:   SHT_LLVM_CALL_GRAPH
-    Link:   .text
-    ContentArray: [
-      ## --- foo ---
-      0x00, ## Version
-      0x01, ## Flags
-      0x90, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ## FunctionEntryPC: 0x1790
-      0x24, 0x44, 0xF7, 0x31, 0xF5, 0xEE, 0xCB, 0x3E, ## FunctionTypeID: 0x3ECBEEF531F74424
-    ]
-  ## This following second call graph section will also be processed.
-  - Type:   SHT_LLVM_CALL_GRAPH
-    Link:   .text
-    ContentArray: [
-      ## --- bar ---
-      0x00, ## Version
-      0x01, ## Flags
-      0xA0, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ## FunctionEntryPC: 0x17A0
-      0x24, 0x44, 0xF7, 0x31, 0xF5, 0xEE, 0xCB, 0x3E, ## FunctionTypeID: 0x3ECBEEF531F74424
-    ]
-Symbols:
-  - Name:     foo
-    Type:     STT_FUNC
-    Section:  .text
-    Value:    0x1790
-  - Name:     bar
-    Type:     STT_FUNC
-    Section:  .text
-    Value:    0x17A0
-
 ## Check warning when the relocation section linked to SHT_LLVM_CALL_GRAPH has an invalid sh_link.
-# RUN: yaml2obj --docnum=8 -DLINK=255 -DINFO=.llvm_call_graph %s -o %t13
+# RUN: yaml2obj --docnum=7 -DLINK=255 -DINFO=.llvm_call_graph %s -o %t13
 # RUN: llvm-readobj --elf-output-style=LLVM --call-graph-info %t13 2>&1 | \
 # RUN:   FileCheck %s -DFILE=%t13 --check-prefix=INVALID-RELA-LINK
 # RUN: llvm-readobj --elf-output-style=JSON --call-graph-info %t13 2>&1 | \
@@ -387,7 +281,7 @@ Symbols:
 # INVALID-RELA-LINK: warning: '[[FILE]]': invalid section linked to SHT_RELA section with index 2: invalid section index: 255
 
 ## Check warning when getSectionAndRelocations fails to read sections.
-# RUN: yaml2obj --docnum=8 -DLINK=0 -DINFO=255 %s -o %t14
+# RUN: yaml2obj --docnum=7 -DLINK=0 -DINFO=255 %s -o %t14
 # RUN: llvm-readobj --elf-output-style=LLVM --call-graph-info %t14 2>&1 | \
 # RUN:   FileCheck %s -DFILE=%t14 --check-prefix=ERR-READ-SECTION
 # RUN: llvm-readobj --elf-output-style=JSON --call-graph-info %t14 2>&1 | \
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 603a14923a98e..9bc5dd228e83f 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
@@ -441,6 +441,7 @@ Symbols:
 
 ## Check ARM 32-bit Thumb address handling.
 # RUN: yaml2obj --docnum=3 %s -o %t3
+# RUN: llvm-readobj --elf-output-style=GNU --call-graph-info %t3 2>&1 | count 0
 # RUN: llvm-readobj --elf-output-style=LLVM --call-graph-info %t3 2>&1 | \
 # RUN:   FileCheck %s --match-full-lines --check-prefix=LLVM-ARM-THUMB -DFILE=%t3
 # RUN: llvm-readobj --elf-output-style=JSON --pretty-print --call-graph-info %t3 2>&1 | \
@@ -509,6 +510,7 @@ Symbols:
 
 ## Check multiple function names handling.
 # RUN: yaml2obj --docnum=4 %s -o %t4
+# RUN: llvm-readobj --elf-output-style=GNU --call-graph-info %t4 2>&1 | count 0
 # RUN: llvm-readobj --elf-output-style=LLVM --call-graph-info %t4 2>&1 | \
 # RUN:   FileCheck %s --match-full-lines --check-prefix=LLVM-MULTI-NAMES -DFILE=%t4
 # RUN: llvm-readobj --elf-output-style=JSON --pretty-print --call-graph-info %t4 2>&1 | \
@@ -681,3 +683,108 @@ Symbols:
   - Name:  foo
   - Name:  bar
 ...
+
+## Check handling of multiple call graph sections in a non-relocatable object file.
+# RUN: yaml2obj --docnum=6 %s -o %t6
+# RUN: llvm-readobj --elf-output-style=GNU --call-graph-info %t6 2>&1 | count 0
+# RUN: llvm-readobj --elf-output-style=LLVM --call-graph-info %t6 2>&1 | \
+# RUN:   FileCheck %s --match-full-lines --check-prefix=LLVM-MULTI-NONRELOC -DFILE=%t6
+# RUN: llvm-readobj --elf-output-style=JSON --pretty-print --call-graph-info %t6 2>&1 | \
+# RUN:   FileCheck %s --match-full-lines --check-prefix=JSON-MULTI-NONRELOC -DFILE=%t6
+
+# LLVM-MULTI-NONRELOC:      CallGraph [
+# LLVM-MULTI-NONRELOC-NEXT:     Function {
+# LLVM-MULTI-NONRELOC-NEXT:       Names: [foo]
+# LLVM-MULTI-NONRELOC-NEXT:       Address: 0x1790
+# LLVM-MULTI-NONRELOC-NEXT:       Version: 0
+# LLVM-MULTI-NONRELOC-NEXT:       IsIndirectTarget: Yes
+# LLVM-MULTI-NONRELOC-NEXT:       TypeID: 0x3ECBEEF531F74424
+# LLVM-MULTI-NONRELOC-NEXT:       NumDirectCallees: 0
+# LLVM-MULTI-NONRELOC-NEXT:       DirectCallees [
+# LLVM-MULTI-NONRELOC-NEXT:       ]
+# LLVM-MULTI-NONRELOC-NEXT:       NumIndirectTargetTypeIDs: 0
+# LLVM-MULTI-NONRELOC-NEXT:       IndirectTypeIDs: []
+# LLVM-MULTI-NONRELOC-NEXT:     }
+# LLVM-MULTI-NONRELOC-NEXT:     Function {
+# LLVM-MULTI-NONRELOC-NEXT:       Names: [bar]
+# LLVM-MULTI-NONRELOC-NEXT:       Address: 0x17A0
+# LLVM-MULTI-NONRELOC-NEXT:       Version: 0
+# LLVM-MULTI-NONRELOC-NEXT:       IsIndirectTarget: Yes
+# LLVM-MULTI-NONRELOC-NEXT:       TypeID: 0x3ECBEEF531F74424
+# LLVM-MULTI-NONRELOC-NEXT:       NumDirectCallees: 0
+# LLVM-MULTI-NONRELOC-NEXT:       DirectCallees [
+# LLVM-MULTI-NONRELOC-NEXT:       ]
+# LLVM-MULTI-NONRELOC-NEXT:       NumIndirectTargetTypeIDs: 0
+# LLVM-MULTI-NONRELOC-NEXT:       IndirectTypeIDs: []
+# LLVM-MULTI-NONRELOC-NEXT:     }
+# LLVM-MULTI-NONRELOC-NEXT:   ]
+
+# JSON-MULTI-NONRELOC:     "CallGraph": [
+# JSON-MULTI-NONRELOC-NEXT:      {
+# JSON-MULTI-NONRELOC-NEXT:        "Function": {
+# JSON-MULTI-NONRELOC-NEXT:          "Names": [
+# JSON-MULTI-NONRELOC-NEXT:            "foo"
+# JSON-MULTI-NONRELOC-NEXT:          ],
+# JSON-MULTI-NONRELOC-NEXT:          "Address": 6032,
+# JSON-MULTI-NONRELOC-NEXT:          "Version": 0,
+# JSON-MULTI-NONRELOC-NEXT:          "IsIndirectTarget": true,
+# JSON-MULTI-NONRELOC-NEXT:          "TypeID": 4524972987496481828,
+# JSON-MULTI-NONRELOC-NEXT:          "NumDirectCallees": 0,
+# JSON-MULTI-NONRELOC-NEXT:          "DirectCallees": [],
+# JSON-MULTI-NONRELOC-NEXT:          "NumIndirectTargetTypeIDs": 0,
+# JSON-MULTI-NONRELOC-NEXT:          "IndirectTypeIDs": []
+# JSON-MULTI-NONRELOC-NEXT:        }
+# JSON-MULTI-NONRELOC-NEXT:      },
+# JSON-MULTI-NONRELOC-NEXT:      {
+# JSON-MULTI-NONRELOC-NEXT:        "Function": {
+# JSON-MULTI-NONRELOC-NEXT:          "Names": [
+# JSON-MULTI-NONRELOC-NEXT:            "bar"
+# JSON-MULTI-NONRELOC-NEXT:          ],
+# JSON-MULTI-NONRELOC-NEXT:          "Address": 6048,
+# JSON-MULTI-NONRELOC-NEXT:          "Version": 0,
+# JSON-MULTI-NONRELOC-NEXT:          "IsIndirectTarget": true,
+# JSON-MULTI-NONRELOC-NEXT:          "TypeID": 4524972987496481828,
+# JSON-MULTI-NONRELOC-NEXT:          "NumDirectCallees": 0,
+# JSON-MULTI-NONRELOC-NEXT:          "DirectCallees": [],
+# JSON-MULTI-NONRELOC-NEXT:          "NumIndirectTargetTypeIDs": 0,
+# JSON-MULTI-NONRELOC-NEXT:          "IndirectTypeIDs": []
+# JSON-MULTI-NONRELOC-NEXT:        }
+# JSON-MULTI-NONRELOC-NEXT:      }
+# JSON-MULTI-NONRELOC-NEXT:    ]
+
+--- !ELF
+FileHeader:
+  Class:    ELFCLASS64
+  Data:     ELFDATA2LSB
+  Type:     ET_DYN
+Sections:
+  - Name:   .text
+    Type:   SHT_PROGBITS
+  - Type:   SHT_LLVM_CALL_GRAPH
+    Link:   .text
+    ContentArray: [
+      ## --- foo ---
+      0x00, ## Version
+      0x01, ## Flags
+      0x90, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ## FunctionEntryPC: 0x1790
+      0x24, 0x44, 0xF7, 0x31, 0xF5, 0xEE, 0xCB, 0x3E, ## FunctionTypeID: 0x3ECBEEF531F74424
+    ]
+  - Type:   SHT_LLVM_CALL_GRAPH
+    Link:   .text
+    ContentArray: [
+      ## --- bar ---
+      0x00, ## Version
+      0x01, ## Flags
+      0xA0, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ## FunctionEntryPC: 0x17A0
+      0x24, 0x44, 0xF7, 0x31, 0xF5, 0xEE, 0xCB, 0x3E, ## FunctionTypeID: 0x3ECBEEF531F74424
+    ]
+Symbols:
+  - Name:     foo
+    Type:     STT_FUNC
+    Section:  .text
+    Value:    0x1790
+  - Name:     bar
+    Type:     STT_FUNC
+    Section:  .text
+    Value:    0x17A0
+...
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index 26b3a98996bc7..138dbb9969efa 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -452,11 +452,10 @@ template <typename ELFT> class ELFDumper : public ObjDumper {
       ArrayRef<Relocation<ELFT>> Relocations, const Elf_Shdr *RelocSymTab);
   // Read the SHT_LLVM_CALL_GRAPH type section and process its contents to
   // populate call graph related data structures which will be used to dump call
-  // graph info. Returns false if there is no SHT_LLVM_CALL_GRAPH type section
-  // in the input file.
-  bool
-  processCallGraphSection(const Elf_Shdr *CGSection,
-                          SmallVectorImpl<FunctionCallgraphInfo> &FuncCGInfos);
+  // graph info. Returns an empty vector if there is no SHT_LLVM_CALL_GRAPH
+  // type section or if parsing fails.
+  SmallVector<FunctionCallgraphInfo, 16>
+  processCallGraphSection(const Elf_Shdr *CGSection);
 
   std::string getProgramHeadersNumString();
 
@@ -5335,9 +5334,9 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCGProfile() {
 }
 
 template <class ELFT>
-bool ELFDumper<ELFT>::processCallGraphSection(
-    const Elf_Shdr *CGSection,
-    SmallVectorImpl<FunctionCallgraphInfo> &FuncCGInfos) {
+SmallVector<FunctionCallgraphInfo, 16>
+ELFDumper<ELFT>::processCallGraphSection(const Elf_Shdr *CGSection) {
+  SmallVector<FunctionCallgraphInfo, 16> FuncCGInfos;
   ArrayRef<uint8_t> Contents = cantFail(Obj.getSectionContents(*CGSection));
   DataExtractor Data(Contents, Obj.isLE());
   DataExtractor::Cursor C(0);
@@ -5351,7 +5350,7 @@ bool ELFDumper<ELFT>::processCallGraphSection(
                                 std::to_string(FormatVersionNumber) +
                                 "] in SHT_LLVM_CALL_GRAPH type section"),
                     FileName);
-      return false;
+      return {};
     }
 
     uint8_t FlagsVal = Data.getU8(C);
@@ -5360,7 +5359,7 @@ bool ELFDumper<ELFT>::processCallGraphSection(
           createError("failed while reading call graph info's Flags: " +
                       toString(C.takeError())),
           FileName);
-      return false;
+      return {};
     }
     callgraph::Flags CGFlags = static_cast<callgraph::Flags>(FlagsVal);
     constexpr callgraph::Flags ValidFlags = callgraph::IsIndirectTarget |
@@ -5371,7 +5370,7 @@ bool ELFDumper<ELFT>::processCallGraphSection(
       reportWarning(createError("unsupported Flags value [" +
                                 std::to_string(FlagsVal) + "] "),
                     FileName);
-      return false;
+      return {};
     }
 
     uint64_t FuncAddrOffset = C.tell();
@@ -5383,7 +5382,7 @@ bool ELFDumper<ELFT>::processCallGraphSection(
               "failed while reading call graph info function entry PC: " +
               toString(C.takeError())),
           FileName);
-      return false;
+      return {};
     }
 
     bool IsETREL = this->Obj.getHeader().e_type == ELF::ET_REL;
@@ -5399,7 +5398,7 @@ bool ELFDumper<ELFT>::processCallGraphSection(
       reportWarning(createError("failed while reading function type ID: " +
                                 toString(C.takeError())),
                     FileName);
-      return false;
+      return {};
     }
     CGInfo.FunctionTypeID = TypeID;
     if (IsIndirectTarget && TypeID == 0)
@@ -5413,7 +5412,7 @@ bool ELFDumper<ELFT>::processCallGraphSection(
             createError("failed while reading number of direct callees: " +
                         toString(C.takeError())),
             FileName);
-        return false;
+        return {};
       }
       // Read unique direct callees and populate FuncCGInfos.
       for (uint64_t I = 0; I < NumDirectCallees; ++I) {
@@ -5424,7 +5423,7 @@ bool ELFDumper<ELFT>::processCallGraphSection(
           reportWarning(createError("failed while reading direct callee: " +
                                     toString(C.takeError())),
                         FileName);
-          return false;
+          return {};
         }
         CGInfo.DirectCallees.insert((IsETREL ? CalleeOffset : Callee));
       }
@@ -5438,7 +5437,7 @@ bool ELFDumper<ELFT>::processCallGraphSection(
                 "failed while reading number of indirect target type IDs: " +
                 toString(C.takeError())),
             FileName);
-        return false;
+        return {};
       }
       // Read unique indirect target type IDs and populate FuncCGInfos.
       for (uint64_t I = 0; I < NumIndirectTargetTypeIDs; ++I) {
@@ -5448,7 +5447,7 @@ bool ELFDumper<ELFT>::processCallGraphSection(
               createError("failed while reading indirect target type ID: " +
                           toString(C.takeError())),
               FileName);
-          return false;
+          return {};
         }
         CGInfo.IndirectTypeIDs.insert(TargetType);
       }
@@ -5460,7 +5459,7 @@ bool ELFDumper<ELFT>::processCallGraphSection(
     reportUniqueWarning(
         "SHT_LLVM_CALL_GRAPH type section has unknown type ID for " +
         Twine(UnknownCount) + " indirect targets");
-  return true;
+  return FuncCGInfos;
 }
 
 template <class ELFT>
@@ -8339,14 +8338,13 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
   }
 
   std::unique_ptr<ListScope> CGI;
-  SmallVector<FunctionCallgraphInfo, 16> FuncCGInfos;
   for (const auto &CGMapEntry : *MapOrErr) {
     const Elf_Shdr *CGSection = CGMapEntry.first;
     const Elf_Shdr *CGRelSection = CGMapEntry.second;
 
-    FuncCGInfos.clear();
-    if (!this->processCallGraphSection(CGSection, FuncCGInfos) ||
-        FuncCGInfos.empty())
+    SmallVector<FunctionCallgraphInfo, 16> FuncCGInfos =
+        this->processCallGraphSection(CGSection);
+    if (FuncCGInfos.empty())
       continue;
 
     std::vector<Relocation<ELFT>> Relocations;

>From 5922f07b5ac962d2443eb6070f205f6c0118f6be Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Fri, 17 Jul 2026 16:38:06 +0000
Subject: [PATCH 5/5] Add missing GNU test. Fix comments.

---
 llvm/test/tools/llvm-readobj/ELF/call-graph-info.test | 1 +
 llvm/tools/llvm-readobj/ELFDumper.cpp                 | 6 +++---
 2 files changed, 4 insertions(+), 3 deletions(-)

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 9bc5dd228e83f..68e258e3c828f 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
@@ -579,6 +579,7 @@ Symbols:
 
 ## Check handling of multiple call graph sections in a relocatable object file.
 # RUN: yaml2obj --docnum=5 %s -o %t5
+# RUN: llvm-readobj --elf-output-style=GNU --call-graph-info %t5 2>&1 | count 0
 # RUN: llvm-readobj --elf-output-style=LLVM --call-graph-info %t5 2>&1 | \
 # RUN:   FileCheck %s --match-full-lines --check-prefix=LLVM-MULTI-RELOC -DFILE=%t5
 # RUN: llvm-readobj --elf-output-style=JSON --pretty-print --call-graph-info %t5 2>&1 | \
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index 138dbb9969efa..bdc3062f3a6b6 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -450,10 +450,10 @@ template <typename ELFT> class ELFDumper : public ObjDumper {
       const SFrameParser<ELFT::Endianness> &Parser,
       const typename SFrameParser<ELFT::Endianness>::FDERange::iterator FDE,
       ArrayRef<Relocation<ELFT>> Relocations, const Elf_Shdr *RelocSymTab);
-  // Read the SHT_LLVM_CALL_GRAPH type section and process its contents to
+  // Read the SHT_LLVM_CALL_GRAPH type sections and process their contents to
   // populate call graph related data structures which will be used to dump call
-  // graph info. Returns an empty vector if there is no SHT_LLVM_CALL_GRAPH
-  // type section or if parsing fails.
+  // graph info. Returns an empty vector if there are no such sections or if
+  // parsing fails.
   SmallVector<FunctionCallgraphInfo, 16>
   processCallGraphSection(const Elf_Shdr *CGSection);
 



More information about the llvm-commits mailing list