[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