[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