[llvm] [llvm-readobj] Dump callgraph section info for ELF (PR #157499)
Prabhu Rajasekaran via llvm-commits
llvm-commits at lists.llvm.org
Wed Sep 17 12:06:30 PDT 2025
https://github.com/Prabhuk updated https://github.com/llvm/llvm-project/pull/157499
>From e2de340cd266fe38bc96a4eaa270fd60be84c49a Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Fri, 5 Sep 2025 17:12:01 -0700
Subject: [PATCH 01/12] [llvm-readobj] Dump callgraph section info for ELF
Introduce a new flag `--call-graph-info` which outputs callgraph ELF
section information to the console as a text output or as JSON output.
---
llvm/tools/llvm-readobj/ELFDumper.cpp | 130 +++++++++++++++++++++++
llvm/tools/llvm-readobj/ObjDumper.h | 1 +
llvm/tools/llvm-readobj/Opts.td | 1 +
llvm/tools/llvm-readobj/llvm-readobj.cpp | 4 +
4 files changed, 136 insertions(+)
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index 7d75f29623ea9..f4b449b0b03c5 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -619,6 +619,7 @@ template <typename ELFT> class GNUELFDumper : public ELFDumper<ELFT> {
void printVersionDefinitionSection(const Elf_Shdr *Sec) override;
void printVersionDependencySection(const Elf_Shdr *Sec) override;
void printCGProfile() override;
+ void printCallGraphInfo() override;
void printBBAddrMaps(bool PrettyPGOAnalysis) override;
void printAddrsig() override;
void printNotes() override;
@@ -730,6 +731,7 @@ template <typename ELFT> class LLVMELFDumper : public ELFDumper<ELFT> {
void printVersionDefinitionSection(const Elf_Shdr *Sec) override;
void printVersionDependencySection(const Elf_Shdr *Sec) override;
void printCGProfile() override;
+ void printCallGraphInfo() override;
void printBBAddrMaps(bool PrettyPGOAnalysis) override;
void printAddrsig() override;
void printNotes() override;
@@ -5217,6 +5219,11 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCGProfile() {
OS << "GNUStyle::printCGProfile not implemented\n";
}
+template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
+ OS << "GNUStyle::printCallGraphInfo not implemented\n";
+}
+
+
template <class ELFT>
void GNUELFDumper<ELFT>::printBBAddrMaps(bool /*PrettyPGOAnalysis*/) {
OS << "GNUStyle::printBBAddrMaps not implemented\n";
@@ -8048,6 +8055,129 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCGProfile() {
}
}
+enum class FunctionKind : uint64_t {
+ // Function cannot be target to indirect calls.
+ NOT_INDIRECT_TARGET = 0,
+ // Function may be target to indirect calls but its type id is unknown.
+ INDIRECT_TARGET_UNKNOWN_TID = 1,
+ // Function may be target to indirect calls and its type id is known.
+ INDIRECT_TARGET_KNOWN_TID = 2,
+
+ // Available in the binary but not listed in the call graph section.
+ NOT_LISTED = 3,
+};
+
+struct FunctionInfo {
+ FunctionKind Kind;
+ struct DirectCallSite {
+ uint64_t CallSite;
+ uint64_t Callee;
+ DirectCallSite(uint64_t CallSite, uint64_t Callee)
+ : CallSite(CallSite), Callee(Callee) {}
+ };
+ SmallVector<DirectCallSite> DirectCallSites;
+ SmallVector<uint64_t> IndirectCallSites;
+};
+
+template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
+ // Get the .callgraph section.
+ StringRef CallGraphSectionName(".callgraph");
+ std::optional<object::SectionRef> CallGraphSection;
+ for (auto Sec : this->ObjF.sections()) {
+ StringRef Name;
+ if (Expected<StringRef> NameOrErr = Sec.getName())
+ Name = *NameOrErr;
+ else
+ consumeError(NameOrErr.takeError());
+
+ if (Name == CallGraphSectionName) {
+ CallGraphSection = Sec;
+ break;
+ }
+ }
+ if (!CallGraphSection) {
+ this->reportUniqueWarning("there is no .callgraph section");
+ return;
+ }
+
+ // Map type id to indirect call sites.
+ MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirCallSites;
+ // Map type id to indirect targets.
+ MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirTargets;
+ // Map function entry pc to function info.
+ MapVector<uint64_t, FunctionInfo> FuncInfo;
+ // Instructions that are not indirect calls but have a type id are ignored.
+ // uint64_t IgnoredICallIdCount = 0;
+ // Number of valid indirect calls with type ids.
+ // uint64_t ICallWithTypeIdCount = 0;
+ if (CallGraphSection) {
+ StringRef CGSecContents = cantFail(CallGraphSection.value().getContents());
+ // TODO: some entries are written in pointer size. are they always 64-bit?
+ if (CGSecContents.size() % sizeof(uint64_t))
+ this->reportUniqueWarning(
+ "Malformed .callgraph section. Unexpected size.");
+
+ size_t Size = CGSecContents.size() / sizeof(uint64_t);
+ auto *It = reinterpret_cast<const uint64_t *>(CGSecContents.data());
+ const auto *const End = It + Size;
+
+ auto CGHasNext = [&]() { return It < End; };
+ auto CGNext = [&]() -> uint64_t {
+ if (!CGHasNext())
+ this->reportUniqueWarning(
+ "Malformed .callgraph section. Parsing error.");
+ return *It++;
+ };
+
+ // Parse the content
+ while (CGHasNext()) {
+ // Format version number.
+ uint64_t FormatVersionNumber = CGNext();
+ if (FormatVersionNumber != 0)
+ this->reportUniqueWarning(
+ "Unknown format version in .callgraph section.");
+ // Function entry pc.
+ uint64_t FuncEntryPc = CGNext();
+ // Function kind.
+ uint64_t Kind = CGNext();
+ switch (Kind) {
+ case 0: // not an indirect target
+ FuncInfo[FuncEntryPc].Kind = FunctionKind::NOT_INDIRECT_TARGET;
+ break;
+ case 1: // indirect target with unknown type id
+ FuncInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
+ break;
+ case 2: // indirect target with known type id
+ FuncInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_KNOWN_TID;
+ TypeIdToIndirTargets[CGNext()].push_back(FuncEntryPc);
+ break;
+ default:
+ this->reportUniqueWarning(
+ "Unknown function kind in .callgraph section.");
+ }
+
+ // Read indirect call sites info.
+ uint64_t IndirectCallSiteCount = CGNext();
+ for (unsigned long I = 0; I < IndirectCallSiteCount; I++) {
+ uint64_t TypeId = CGNext();
+ uint64_t CallSitePc = CGNext();
+ TypeIdToIndirCallSites[TypeId].push_back(CallSitePc);
+ FuncInfo[FuncEntryPc].IndirectCallSites.push_back(CallSitePc);
+ }
+
+ // Read direct call sites info.
+ uint64_t DirectCallSiteCount = CGNext();
+ for (unsigned long I = 0; I < DirectCallSiteCount; I++) {
+ uint64_t CallSitePc = CGNext();
+ uint64_t CalleePc = CGNext();
+ FuncInfo[FuncEntryPc].DirectCallSites.emplace_back(CallSitePc,
+ CalleePc);
+ }
+ }
+ }
+}
+
+
template <class ELFT>
void LLVMELFDumper<ELFT>::printBBAddrMaps(bool PrettyPGOAnalysis) {
bool IsRelocatable = this->Obj.getHeader().e_type == ELF::ET_REL;
diff --git a/llvm/tools/llvm-readobj/ObjDumper.h b/llvm/tools/llvm-readobj/ObjDumper.h
index a654078a770ff..c704a24290b30 100644
--- a/llvm/tools/llvm-readobj/ObjDumper.h
+++ b/llvm/tools/llvm-readobj/ObjDumper.h
@@ -129,6 +129,7 @@ class ObjDumper {
virtual void printGroupSections() {}
virtual void printHashHistograms() {}
virtual void printCGProfile() {}
+ virtual void printCallGraphInfo() {}
// If PrettyPGOAnalysis is true, prints BFI as relative frequency and BPI as
// percentage. Otherwise raw values are displayed.
virtual void printBBAddrMaps(bool PrettyPGOAnalysis) {}
diff --git a/llvm/tools/llvm-readobj/Opts.td b/llvm/tools/llvm-readobj/Opts.td
index 711522c4acb14..209881d67eb0c 100644
--- a/llvm/tools/llvm-readobj/Opts.td
+++ b/llvm/tools/llvm-readobj/Opts.td
@@ -20,6 +20,7 @@ def all : FF<"all", "Equivalent to setting: --file-header, --program-headers, --
def arch_specific : FF<"arch-specific", "Display architecture-specific information">;
def bb_addr_map : FF<"bb-addr-map", "Display the BB address map section">;
def pretty_pgo_analysis_map : FF<"pretty-pgo-analysis-map", "Display PGO analysis values with formatting rather than raw numbers">;
+def call_graph_info : FF<"call-graph-info", "Display call graph information">;
def cg_profile : FF<"cg-profile", "Display call graph profile section">;
def decompress : FF<"decompress", "Dump decompressed section content when used with -x or -p">;
defm demangle : BB<"demangle", "Demangle symbol names", "Do not demangle symbol names (default)">;
diff --git a/llvm/tools/llvm-readobj/llvm-readobj.cpp b/llvm/tools/llvm-readobj/llvm-readobj.cpp
index 2b34761b2cc6c..cb22f95eedbac 100644
--- a/llvm/tools/llvm-readobj/llvm-readobj.cpp
+++ b/llvm/tools/llvm-readobj/llvm-readobj.cpp
@@ -97,6 +97,7 @@ static bool Addrsig;
static bool All;
static bool ArchSpecificInfo;
static bool BBAddrMap;
+static bool CallGraphInfo;
static bool PrettyPGOAnalysisMap;
bool ExpandRelocs;
static bool CGProfile;
@@ -216,6 +217,7 @@ static void parseOptions(const opt::InputArgList &Args) {
opts::All = Args.hasArg(OPT_all);
opts::ArchSpecificInfo = Args.hasArg(OPT_arch_specific);
opts::BBAddrMap = Args.hasArg(OPT_bb_addr_map);
+ opts::CallGraphInfo = Args.hasArg(OPT_call_graph_info);
opts::PrettyPGOAnalysisMap = Args.hasArg(OPT_pretty_pgo_analysis_map);
if (opts::PrettyPGOAnalysisMap && !opts::BBAddrMap)
WithColor::warning(errs(), ToolName)
@@ -474,6 +476,8 @@ static void dumpObject(ObjectFile &Obj, ScopedPrinter &Writer,
Dumper->printHashHistograms();
if (opts::CGProfile)
Dumper->printCGProfile();
+ if (opts::CallGraphInfo)
+ Dumper->printCallGraphInfo();
if (opts::BBAddrMap)
Dumper->printBBAddrMaps(opts::PrettyPGOAnalysisMap);
if (opts::Addrsig)
>From 1f1c2fede557bd105c637e5626c6fd2ccd14e1fe Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Mon, 8 Sep 2025 09:05:21 -0700
Subject: [PATCH 02/12] Format code
---
llvm/tools/llvm-readobj/ELFDumper.cpp | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index f4b449b0b03c5..e23c95fe1c822 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -5223,7 +5223,6 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
OS << "GNUStyle::printCallGraphInfo not implemented\n";
}
-
template <class ELFT>
void GNUELFDumper<ELFT>::printBBAddrMaps(bool /*PrettyPGOAnalysis*/) {
OS << "GNUStyle::printBBAddrMaps not implemented\n";
@@ -8115,7 +8114,7 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
// TODO: some entries are written in pointer size. are they always 64-bit?
if (CGSecContents.size() % sizeof(uint64_t))
this->reportUniqueWarning(
- "Malformed .callgraph section. Unexpected size.");
+ "Malformed .callgraph section. Unexpected size.");
size_t Size = CGSecContents.size() / sizeof(uint64_t);
auto *It = reinterpret_cast<const uint64_t *>(CGSecContents.data());
@@ -8125,7 +8124,7 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
auto CGNext = [&]() -> uint64_t {
if (!CGHasNext())
this->reportUniqueWarning(
- "Malformed .callgraph section. Parsing error.");
+ "Malformed .callgraph section. Parsing error.");
return *It++;
};
@@ -8135,7 +8134,7 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
uint64_t FormatVersionNumber = CGNext();
if (FormatVersionNumber != 0)
this->reportUniqueWarning(
- "Unknown format version in .callgraph section.");
+ "Unknown format version in .callgraph section.");
// Function entry pc.
uint64_t FuncEntryPc = CGNext();
// Function kind.
@@ -8153,7 +8152,7 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
break;
default:
this->reportUniqueWarning(
- "Unknown function kind in .callgraph section.");
+ "Unknown function kind in .callgraph section.");
}
// Read indirect call sites info.
@@ -8177,7 +8176,6 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
}
}
-
template <class ELFT>
void LLVMELFDumper<ELFT>::printBBAddrMaps(bool PrettyPGOAnalysis) {
bool IsRelocatable = this->Obj.getHeader().e_type == ELF::ET_REL;
>From 8660e53e21753e1d9e2aa06f34c4066fd654b309 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Mon, 8 Sep 2025 09:16:45 -0700
Subject: [PATCH 03/12] Implement JSON output for printCallGraph()
---
llvm/tools/llvm-readobj/ELFDumper.cpp | 418 ++++++++++++++++++--------
1 file changed, 298 insertions(+), 120 deletions(-)
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index e23c95fe1c822..76783387abdea 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -183,6 +183,31 @@ struct GroupSection {
std::vector<GroupMember> Members;
};
+// Callgraph section handling implementation.
+enum class FunctionKind : uint64_t {
+ // Function cannot be target to indirect calls.
+ NOT_INDIRECT_TARGET = 0,
+ // Function may be target to indirect calls but its type id is unknown.
+ INDIRECT_TARGET_UNKNOWN_TID = 1,
+ // Function may be target to indirect calls and its type id is known.
+ INDIRECT_TARGET_KNOWN_TID = 2,
+
+ // Available in the binary but not listed in the call graph section.
+ NOT_LISTED = 3,
+};
+
+struct FunctionInfo {
+ FunctionKind Kind;
+ struct DirectCallSite {
+ uint64_t CallSite;
+ uint64_t Callee;
+ DirectCallSite(uint64_t CallSite, uint64_t Callee)
+ : CallSite(CallSite), Callee(Callee) {}
+ };
+ SmallVector<DirectCallSite> DirectCallSites;
+ SmallVector<uint64_t> IndirectCallSites;
+};
+
namespace {
struct NoteType {
@@ -433,6 +458,14 @@ template <typename ELFT> class ELFDumper : public ObjDumper {
const typename SFrameParser<ELFT::Endianness>::FDERange::iterator FDE,
ArrayRef<Relocation<ELFT>> Relocations, const Elf_Shdr *RelocSymTab);
+ void callGraphInfoHelper();
+ // Map type id to indirect call sites.
+ MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirCallSites;
+ // Map type id to indirect targets.
+ MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirTargets;
+ // Map function entry pc to function info.
+ MapVector<uint64_t, FunctionInfo> FuncInfo;
+
private:
mutable SmallVector<std::optional<VersionEntry>, 0> VersionMap;
};
@@ -812,6 +845,8 @@ template <typename ELFT> class JSONELFDumper : public LLVMELFDumper<ELFT> {
void printDynamicTable() override;
+ void printCallGraphInfo() override;
+
private:
void printAuxillaryDynamicTableEntryInfo(const Elf_Dyn &Entry);
@@ -5219,8 +5254,189 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCGProfile() {
OS << "GNUStyle::printCGProfile not implemented\n";
}
+template <class ELFT> void ELFDumper<ELFT>::callGraphInfoHelper() {
+ // Get the .callgraph section.
+ StringRef CallGraphSectionName(".callgraph");
+ std::optional<object::SectionRef> CallGraphSection;
+ for (auto Sec : this->ObjF.sections()) {
+ StringRef Name;
+ if (Expected<StringRef> NameOrErr = Sec.getName())
+ Name = *NameOrErr;
+ else
+ consumeError(NameOrErr.takeError());
+
+ if (Name == CallGraphSectionName) {
+ CallGraphSection = Sec;
+ break;
+ }
+ }
+ if (!CallGraphSection) {
+ this->reportUniqueWarning("there is no .callgraph section");
+ return;
+ }
+
+ // Instructions that are not indirect calls but have a type id are ignored.
+ // uint64_t IgnoredICallIdCount = 0;
+ // Number of valid indirect calls with type ids.
+ // uint64_t ICallWithTypeIdCount = 0;
+ if (CallGraphSection) {
+ StringRef CGSecContents = cantFail(CallGraphSection.value().getContents());
+ // TODO: some entries are written in pointer size. are they always 64-bit?
+ if (CGSecContents.size() % sizeof(uint64_t))
+ this->reportUniqueWarning(
+ "Malformed .callgraph section. Unexpected size.");
+
+ size_t Size = CGSecContents.size() / sizeof(uint64_t);
+ auto *It = reinterpret_cast<const uint64_t *>(CGSecContents.data());
+ const auto *const End = It + Size;
+
+ auto CGHasNext = [&]() { return It < End; };
+ auto CGNext = [&]() -> uint64_t {
+ if (!CGHasNext())
+ this->reportUniqueWarning(
+ "Malformed .callgraph section. Parsing error.");
+ return *It++;
+ };
+
+ // Parse the content
+ while (CGHasNext()) {
+ // Format version number.
+ uint64_t FormatVersionNumber = CGNext();
+ if (FormatVersionNumber != 0)
+ this->reportUniqueWarning(
+ "Unknown format version in .callgraph section.");
+ // Function entry pc.
+ uint64_t FuncEntryPc = CGNext();
+ // Function kind.
+ uint64_t Kind = CGNext();
+ switch (Kind) {
+ case 0: // not an indirect target
+ FuncInfo[FuncEntryPc].Kind = FunctionKind::NOT_INDIRECT_TARGET;
+ break;
+ case 1: // indirect target with unknown type id
+ FuncInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
+ break;
+ case 2: // indirect target with known type id
+ FuncInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_KNOWN_TID;
+ TypeIdToIndirTargets[CGNext()].push_back(FuncEntryPc);
+ break;
+ default:
+ this->reportUniqueWarning(
+ "Unknown function kind in .callgraph section.");
+ }
+
+ // Read indirect call sites info.
+ uint64_t IndirectCallSiteCount = CGNext();
+ for (unsigned long I = 0; I < IndirectCallSiteCount; I++) {
+ uint64_t TypeId = CGNext();
+ uint64_t CallSitePc = CGNext();
+ TypeIdToIndirCallSites[TypeId].push_back(CallSitePc);
+ FuncInfo[FuncEntryPc].IndirectCallSites.push_back(CallSitePc);
+ }
+
+ // Read direct call sites info.
+ uint64_t DirectCallSiteCount = CGNext();
+ for (unsigned long I = 0; I < DirectCallSiteCount; I++) {
+ uint64_t CallSitePc = CGNext();
+ uint64_t CalleePc = CGNext();
+ FuncInfo[FuncEntryPc].DirectCallSites.emplace_back(CallSitePc,
+ CalleePc);
+ }
+ }
+ }
+ llvm::sort(FuncInfo,
+ [](const auto &A, const auto &B) { return A.first < B.first; });
+}
+
template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
- OS << "GNUStyle::printCallGraphInfo not implemented\n";
+ this->callGraphInfoHelper();
+ uint64_t NotListedCount = 0;
+ uint64_t UnknownCount = 0;
+
+ for (const auto &El : this->FuncInfo) {
+ NotListedCount += El.second.Kind == FunctionKind::NOT_LISTED;
+ UnknownCount += El.second.Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
+ }
+ if (NotListedCount) // TODO(prabhuk): include object file name to the warning
+ this->reportUniqueWarning(
+ "callgraph section does not have information for " +
+ std::to_string(NotListedCount) + " functions");
+ if (UnknownCount) // TODO(prabhuk): include object file name to the warning
+ this->reportUniqueWarning("callgraph section has unknown type id for " +
+ std::to_string(UnknownCount) +
+ " indirect targets");
+
+ // Print indirect targets
+ OS << "\nINDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])";
+
+ // Print indirect targets with unknown type.
+ // For completeness, functions for which the call graph section does not
+ // provide information are included.
+ if (NotListedCount || UnknownCount) {
+ OS << "\nUNKNOWN";
+ for (const auto &El : this->FuncInfo) {
+ uint64_t FuncEntryPc = El.first;
+ FunctionKind FuncKind = El.second.Kind;
+ if (FuncKind == FunctionKind::NOT_LISTED ||
+ FuncKind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID)
+ OS << " " << format("%lx", FuncEntryPc);
+ }
+ }
+
+ // Print indirect targets to type id mapping.
+ for (const auto &El : this->TypeIdToIndirTargets) {
+ uint64_t TypeId = El.first;
+ OS << "\n" << format("%lx", TypeId);
+ for (uint64_t IndirTargetPc : El.second)
+ OS << " " << format("%lx", IndirTargetPc);
+ }
+
+ // Print indirect calls to type id mapping. Any indirect call without a
+ // type id can be deduced by comparing this list to indirect call sites
+ // list.
+ OS << "\n\nINDIRECT CALL TYPES (TYPEID [CALL_SITE_ADDR,])";
+ for (const auto &El : this->TypeIdToIndirCallSites) {
+ uint64_t TypeId = El.first;
+ OS << "\n" << format("%lx", TypeId);
+ for (uint64_t IndirCallSitePc : El.second)
+ OS << " " << format("%lx", IndirCallSitePc);
+ }
+
+ // Print function entry to indirect call site addresses mapping from disasm.
+ OS << "\n\nINDIRECT CALL SITES (CALLER_ADDR [CALL_SITE_ADDR,])";
+ for (const auto &El : this->FuncInfo) {
+ auto CallerPc = El.first;
+ auto FuncIndirCallSites = El.second.IndirectCallSites;
+ if (!FuncIndirCallSites.empty()) {
+ OS << "\n" << format("%lx", CallerPc);
+ for (auto IndirCallSitePc : FuncIndirCallSites)
+ OS << " " << format("%lx", IndirCallSitePc);
+ }
+ }
+
+ // Print function entry to direct call site and target function entry
+ // addresses mapping from disasm.
+ OS << "\n\nDIRECT CALL SITES (CALLER_ADDR [(CALL_SITE_ADDR, TARGET_ADDR),])";
+ for (const auto &El : this->FuncInfo) {
+ auto CallerPc = El.first;
+ auto FuncDirCallSites = El.second.DirectCallSites;
+ if (!FuncDirCallSites.empty()) {
+ OS << "\n" << format("%lx", CallerPc);
+ for (const FunctionInfo::DirectCallSite &DCS : FuncDirCallSites) {
+ OS << " " << format("%lx", DCS.CallSite) << " "
+ << format("%lx", DCS.Callee);
+ }
+ }
+ }
+
+ // Print function entry pc to function name mapping.
+ // OS << "\n\nFUNCTIONS (FUNC_ENTRY_ADDR, SYM_NAME)";
+ // for (const auto &El : FuncInfo) {
+ // uint64_t EntryPc = El.first;
+ // const auto &Name = El.second.Name;
+ // OS << "\n" << format("%lx", EntryPc) << " " << Name;
+ // }
+ OS << "\n";
}
template <class ELFT>
@@ -7800,6 +8016,86 @@ template <class ELFT> void JSONELFDumper<ELFT>::printDynamicTable() {
}
}
+template <class ELFT> void JSONELFDumper<ELFT>::printCallGraphInfo() {
+ this->callGraphInfoHelper();
+ uint64_t NotListedCount = 0;
+ uint64_t UnknownCount = 0;
+
+ for (const auto &El : this->FuncInfo) {
+ NotListedCount += El.second.Kind == FunctionKind::NOT_LISTED;
+ UnknownCount += El.second.Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
+ }
+ if (NotListedCount) // TODO(prabhuk): include object file name to the warning
+ this->reportUniqueWarning(
+ "callgraph section does not have information for " +
+ std::to_string(NotListedCount) + " functions");
+ if (UnknownCount) // TODO(prabhuk): include object file name to the warning
+ this->reportUniqueWarning("callgraph section has unknown type id for " +
+ std::to_string(UnknownCount) +
+ " indirect targets");
+
+ DictScope D(this->W, "callgraph_info");
+
+ // Use llvm-symbolizer to get function name and address instead.
+ // {
+ // ListScope A(this->W, "functions");
+ // for (auto const &F : this->FuncInfo) {
+ // DictScope D(this->W);
+ // this->W.printHex("function_address", F.first);
+ // }
+ // }
+
+ {
+ ListScope A(this->W, "direct_call_sites");
+ for (auto const &F : this->FuncInfo) {
+ if (F.second.DirectCallSites.empty())
+ continue;
+ DictScope D(this->W);
+ this->W.printHex("caller", F.first);
+ ListScope A(this->W, "call_sites");
+ for (auto const &CS : F.second.DirectCallSites) {
+ DictScope D(this->W);
+ this->W.printHex("call_site", CS.CallSite);
+ this->W.printHex("callee", CS.Callee);
+ }
+ }
+ }
+
+ {
+ ListScope A(this->W, "indirect_call_sites");
+ for (auto const &F : this->FuncInfo) {
+ if (F.second.IndirectCallSites.empty())
+ continue;
+ DictScope D(this->W);
+ this->W.printHex("caller", F.first);
+ // ListScope A(this->W, "call_sites");
+ this->W.printHexList("call_sites", F.second.IndirectCallSites);
+ }
+ }
+
+ {
+ ListScope A(this->W, "indirect_call_types");
+ for (auto const &T : this->TypeIdToIndirCallSites) {
+ for (auto const &CS : T.second) {
+ DictScope D(this->W);
+ this->W.printHex("call_site", CS);
+ this->W.printHex("type_id", T.first);
+ }
+ }
+ }
+
+ {
+ ListScope A(this->W, "indirect_target_types");
+ for (auto const &T : this->TypeIdToIndirTargets) {
+ for (auto const &Target : T.second) {
+ DictScope D(this->W);
+ this->W.printHex("function_address", Target);
+ this->W.printHex("type_id", T.first);
+ }
+ }
+ }
+}
+
template <class ELFT> void LLVMELFDumper<ELFT>::printDynamicRelocations() {
W.startLine() << "Dynamic Relocations {\n";
W.indent();
@@ -8054,126 +8350,8 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCGProfile() {
}
}
-enum class FunctionKind : uint64_t {
- // Function cannot be target to indirect calls.
- NOT_INDIRECT_TARGET = 0,
- // Function may be target to indirect calls but its type id is unknown.
- INDIRECT_TARGET_UNKNOWN_TID = 1,
- // Function may be target to indirect calls and its type id is known.
- INDIRECT_TARGET_KNOWN_TID = 2,
-
- // Available in the binary but not listed in the call graph section.
- NOT_LISTED = 3,
-};
-
-struct FunctionInfo {
- FunctionKind Kind;
- struct DirectCallSite {
- uint64_t CallSite;
- uint64_t Callee;
- DirectCallSite(uint64_t CallSite, uint64_t Callee)
- : CallSite(CallSite), Callee(Callee) {}
- };
- SmallVector<DirectCallSite> DirectCallSites;
- SmallVector<uint64_t> IndirectCallSites;
-};
-
template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
- // Get the .callgraph section.
- StringRef CallGraphSectionName(".callgraph");
- std::optional<object::SectionRef> CallGraphSection;
- for (auto Sec : this->ObjF.sections()) {
- StringRef Name;
- if (Expected<StringRef> NameOrErr = Sec.getName())
- Name = *NameOrErr;
- else
- consumeError(NameOrErr.takeError());
-
- if (Name == CallGraphSectionName) {
- CallGraphSection = Sec;
- break;
- }
- }
- if (!CallGraphSection) {
- this->reportUniqueWarning("there is no .callgraph section");
- return;
- }
-
- // Map type id to indirect call sites.
- MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirCallSites;
- // Map type id to indirect targets.
- MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirTargets;
- // Map function entry pc to function info.
- MapVector<uint64_t, FunctionInfo> FuncInfo;
- // Instructions that are not indirect calls but have a type id are ignored.
- // uint64_t IgnoredICallIdCount = 0;
- // Number of valid indirect calls with type ids.
- // uint64_t ICallWithTypeIdCount = 0;
- if (CallGraphSection) {
- StringRef CGSecContents = cantFail(CallGraphSection.value().getContents());
- // TODO: some entries are written in pointer size. are they always 64-bit?
- if (CGSecContents.size() % sizeof(uint64_t))
- this->reportUniqueWarning(
- "Malformed .callgraph section. Unexpected size.");
-
- size_t Size = CGSecContents.size() / sizeof(uint64_t);
- auto *It = reinterpret_cast<const uint64_t *>(CGSecContents.data());
- const auto *const End = It + Size;
-
- auto CGHasNext = [&]() { return It < End; };
- auto CGNext = [&]() -> uint64_t {
- if (!CGHasNext())
- this->reportUniqueWarning(
- "Malformed .callgraph section. Parsing error.");
- return *It++;
- };
-
- // Parse the content
- while (CGHasNext()) {
- // Format version number.
- uint64_t FormatVersionNumber = CGNext();
- if (FormatVersionNumber != 0)
- this->reportUniqueWarning(
- "Unknown format version in .callgraph section.");
- // Function entry pc.
- uint64_t FuncEntryPc = CGNext();
- // Function kind.
- uint64_t Kind = CGNext();
- switch (Kind) {
- case 0: // not an indirect target
- FuncInfo[FuncEntryPc].Kind = FunctionKind::NOT_INDIRECT_TARGET;
- break;
- case 1: // indirect target with unknown type id
- FuncInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
- break;
- case 2: // indirect target with known type id
- FuncInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_KNOWN_TID;
- TypeIdToIndirTargets[CGNext()].push_back(FuncEntryPc);
- break;
- default:
- this->reportUniqueWarning(
- "Unknown function kind in .callgraph section.");
- }
-
- // Read indirect call sites info.
- uint64_t IndirectCallSiteCount = CGNext();
- for (unsigned long I = 0; I < IndirectCallSiteCount; I++) {
- uint64_t TypeId = CGNext();
- uint64_t CallSitePc = CGNext();
- TypeIdToIndirCallSites[TypeId].push_back(CallSitePc);
- FuncInfo[FuncEntryPc].IndirectCallSites.push_back(CallSitePc);
- }
-
- // Read direct call sites info.
- uint64_t DirectCallSiteCount = CGNext();
- for (unsigned long I = 0; I < DirectCallSiteCount; I++) {
- uint64_t CallSitePc = CGNext();
- uint64_t CalleePc = CGNext();
- FuncInfo[FuncEntryPc].DirectCallSites.emplace_back(CallSitePc,
- CalleePc);
- }
- }
- }
+ this->callGraphInfoHelper();
}
template <class ELFT>
>From 6d02828a6cc6ebf822dabbafedb3ac45ee83b839 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Mon, 8 Sep 2025 16:11:33 -0700
Subject: [PATCH 04/12] ELFDumper cleanups
---
llvm/tools/llvm-readobj/ELFDumper.cpp | 395 +++++++++++++-------------
1 file changed, 194 insertions(+), 201 deletions(-)
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index 76783387abdea..66722fadd00bd 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -183,20 +183,19 @@ struct GroupSection {
std::vector<GroupMember> Members;
};
-// Callgraph section handling implementation.
-enum class FunctionKind : uint64_t {
- // Function cannot be target to indirect calls.
- NOT_INDIRECT_TARGET = 0,
- // Function may be target to indirect calls but its type id is unknown.
- INDIRECT_TARGET_UNKNOWN_TID = 1,
- // Function may be target to indirect calls and its type id is known.
- INDIRECT_TARGET_KNOWN_TID = 2,
-
- // Available in the binary but not listed in the call graph section.
- NOT_LISTED = 3,
-};
-
-struct FunctionInfo {
+// Per-function Callgraph information.
+struct FunctionCallgraphInfo {
+ enum class FunctionKind : uint64_t {
+ // Function cannot be target to indirect calls.
+ NOT_INDIRECT_TARGET = 0,
+ // Function may be target to indirect calls but its type id is unknown.
+ INDIRECT_TARGET_UNKNOWN_TID = 1,
+ // Function may be target to indirect calls and its type id is known.
+ INDIRECT_TARGET_KNOWN_TID = 2,
+
+ // Available in the binary but not listed in the call graph section.
+ NOT_LISTED = 3,
+ };
FunctionKind Kind;
struct DirectCallSite {
uint64_t CallSite;
@@ -207,6 +206,7 @@ struct FunctionInfo {
SmallVector<DirectCallSite> DirectCallSites;
SmallVector<uint64_t> IndirectCallSites;
};
+typedef FunctionCallgraphInfo::FunctionKind FunctionKind;
namespace {
@@ -458,13 +458,19 @@ template <typename ELFT> class ELFDumper : public ObjDumper {
const typename SFrameParser<ELFT::Endianness>::FDERange::iterator FDE,
ArrayRef<Relocation<ELFT>> Relocations, const Elf_Shdr *RelocSymTab);
- void callGraphInfoHelper();
- // Map type id to indirect call sites.
+ // Callgraph - Main data structure to maintain per function callgraph
+ // information.
+ MapVector<uint64_t, FunctionCallgraphInfo> FuncCGInfo;
+ // Callgraph - 64 bit type id mapped to indirect callsites whose potential
+ // callee(s) should be of given type id.
MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirCallSites;
- // Map type id to indirect targets.
+ // Callgraph - 64 bit type id mapped to entry PC addresses of functions which
+ // are of the given type id.
MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirTargets;
- // Map function entry pc to function info.
- MapVector<uint64_t, FunctionInfo> FuncInfo;
+ // Callgraph - Read callgraph section and process its contents to populate
+ // Callgraph related data structures which will be used to dump callgraph
+ // info.
+ void processCallGraphSection();
private:
mutable SmallVector<std::optional<VersionEntry>, 0> VersionMap;
@@ -845,8 +851,6 @@ template <typename ELFT> class JSONELFDumper : public LLVMELFDumper<ELFT> {
void printDynamicTable() override;
- void printCallGraphInfo() override;
-
private:
void printAuxillaryDynamicTableEntryInfo(const Elf_Dyn &Entry);
@@ -5254,11 +5258,13 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCGProfile() {
OS << "GNUStyle::printCGProfile not implemented\n";
}
-template <class ELFT> void ELFDumper<ELFT>::callGraphInfoHelper() {
+template <class ELFT>
+static std::optional<object::SectionRef>
+getCallGraphSection(const object::ELFObjectFile<ELFT> &ObjF) {
// Get the .callgraph section.
StringRef CallGraphSectionName(".callgraph");
std::optional<object::SectionRef> CallGraphSection;
- for (auto Sec : this->ObjF.sections()) {
+ for (auto Sec : ObjF.sections()) {
StringRef Name;
if (Expected<StringRef> NameOrErr = Sec.getName())
Name = *NameOrErr;
@@ -5270,101 +5276,106 @@ template <class ELFT> void ELFDumper<ELFT>::callGraphInfoHelper() {
break;
}
}
+ return CallGraphSection;
+}
+
+template <class ELFT> void ELFDumper<ELFT>::processCallGraphSection() {
+ std::optional<object::SectionRef> CallGraphSection =
+ getCallGraphSection(ObjF);
if (!CallGraphSection) {
- this->reportUniqueWarning("there is no .callgraph section");
+ reportUniqueWarning("there is no .callgraph section in " +
+ getElfObject().getFileName());
return;
}
- // Instructions that are not indirect calls but have a type id are ignored.
- // uint64_t IgnoredICallIdCount = 0;
- // Number of valid indirect calls with type ids.
- // uint64_t ICallWithTypeIdCount = 0;
- if (CallGraphSection) {
- StringRef CGSecContents = cantFail(CallGraphSection.value().getContents());
- // TODO: some entries are written in pointer size. are they always 64-bit?
- if (CGSecContents.size() % sizeof(uint64_t))
- this->reportUniqueWarning(
- "Malformed .callgraph section. Unexpected size.");
-
- size_t Size = CGSecContents.size() / sizeof(uint64_t);
- auto *It = reinterpret_cast<const uint64_t *>(CGSecContents.data());
- const auto *const End = It + Size;
-
- auto CGHasNext = [&]() { return It < End; };
- auto CGNext = [&]() -> uint64_t {
- if (!CGHasNext())
- this->reportUniqueWarning(
- "Malformed .callgraph section. Parsing error.");
- return *It++;
- };
+ StringRef CGSecContents = cantFail(CallGraphSection.value().getContents());
+ // TODO: some entries are written in pointer size. are they always 64-bit?
+ if (CGSecContents.size() % sizeof(uint64_t))
+ reportUniqueWarning("Malformed .callgraph section. Unexpected size in " +
+ getElfObject().getFileName());
- // Parse the content
- while (CGHasNext()) {
- // Format version number.
- uint64_t FormatVersionNumber = CGNext();
- if (FormatVersionNumber != 0)
- this->reportUniqueWarning(
- "Unknown format version in .callgraph section.");
- // Function entry pc.
- uint64_t FuncEntryPc = CGNext();
- // Function kind.
- uint64_t Kind = CGNext();
- switch (Kind) {
- case 0: // not an indirect target
- FuncInfo[FuncEntryPc].Kind = FunctionKind::NOT_INDIRECT_TARGET;
- break;
- case 1: // indirect target with unknown type id
- FuncInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
- break;
- case 2: // indirect target with known type id
- FuncInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_KNOWN_TID;
- TypeIdToIndirTargets[CGNext()].push_back(FuncEntryPc);
- break;
- default:
- this->reportUniqueWarning(
- "Unknown function kind in .callgraph section.");
- }
+ size_t Size = CGSecContents.size() / sizeof(uint64_t);
+ auto *It = reinterpret_cast<const uint64_t *>(CGSecContents.data());
+ const auto *const End = It + Size;
- // Read indirect call sites info.
- uint64_t IndirectCallSiteCount = CGNext();
- for (unsigned long I = 0; I < IndirectCallSiteCount; I++) {
- uint64_t TypeId = CGNext();
- uint64_t CallSitePc = CGNext();
- TypeIdToIndirCallSites[TypeId].push_back(CallSitePc);
- FuncInfo[FuncEntryPc].IndirectCallSites.push_back(CallSitePc);
- }
+ auto CGHasNext = [&]() { return It < End; };
+ auto CGNext = [&]() -> uint64_t {
+ if (!CGHasNext())
+ reportUniqueWarning("Malformed .callgraph section. Parsing error in " +
+ getElfObject().getFileName());
+ return *It++;
+ };
- // Read direct call sites info.
- uint64_t DirectCallSiteCount = CGNext();
- for (unsigned long I = 0; I < DirectCallSiteCount; I++) {
- uint64_t CallSitePc = CGNext();
- uint64_t CalleePc = CGNext();
- FuncInfo[FuncEntryPc].DirectCallSites.emplace_back(CallSitePc,
+ while (CGHasNext()) {
+ // Format version number.
+ uint64_t FormatVersionNumber = CGNext();
+ if (FormatVersionNumber != 0)
+ reportUniqueWarning("Unknown format version in .callgraph section in " +
+ getElfObject().getFileName());
+ // Function entry pc.
+ uint64_t FuncEntryPc = CGNext();
+ // Function kind.
+ uint64_t Kind = CGNext();
+ switch (Kind) {
+ case 0: // not an indirect target
+ FuncCGInfo[FuncEntryPc].Kind = FunctionKind::NOT_INDIRECT_TARGET;
+ break;
+ case 1: // indirect target with unknown type id
+ FuncCGInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
+ break;
+ case 2: // indirect target with known type id
+ FuncCGInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_KNOWN_TID;
+ TypeIdToIndirTargets[CGNext()].push_back(FuncEntryPc);
+ break;
+ default:
+ this->reportUniqueWarning(
+ "Unknown function kind in .callgraph section in " +
+ getElfObject().getFileName());
+ }
+ // Read indirect call sites info.
+ uint64_t IndirectCallSiteCount = CGNext();
+ for (unsigned long I = 0; I < IndirectCallSiteCount; I++) {
+ uint64_t TypeId = CGNext();
+ uint64_t CallSitePc = CGNext();
+ TypeIdToIndirCallSites[TypeId].push_back(CallSitePc);
+ FuncCGInfo[FuncEntryPc].IndirectCallSites.push_back(CallSitePc);
+ }
+ // Read direct call sites info.
+ uint64_t DirectCallSiteCount = CGNext();
+ for (unsigned long I = 0; I < DirectCallSiteCount; I++) {
+ uint64_t CallSitePc = CGNext();
+ uint64_t CalleePc = CGNext();
+ FuncCGInfo[FuncEntryPc].DirectCallSites.emplace_back(CallSitePc,
CalleePc);
- }
}
}
- llvm::sort(FuncInfo,
+
+ // Sort function info by function PC.
+ llvm::sort(FuncCGInfo,
[](const auto &A, const auto &B) { return A.first < B.first; });
-}
-template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
- this->callGraphInfoHelper();
uint64_t NotListedCount = 0;
uint64_t UnknownCount = 0;
-
- for (const auto &El : this->FuncInfo) {
+ for (const auto &El : FuncCGInfo) {
NotListedCount += El.second.Kind == FunctionKind::NOT_LISTED;
UnknownCount += El.second.Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
}
- if (NotListedCount) // TODO(prabhuk): include object file name to the warning
- this->reportUniqueWarning(
- "callgraph section does not have information for " +
- std::to_string(NotListedCount) + " functions");
- if (UnknownCount) // TODO(prabhuk): include object file name to the warning
- this->reportUniqueWarning("callgraph section has unknown type id for " +
- std::to_string(UnknownCount) +
- " indirect targets");
+ for (const auto &El : FuncCGInfo) {
+ NotListedCount += El.second.Kind == FunctionKind::NOT_LISTED;
+ UnknownCount += El.second.Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
+ }
+ if (NotListedCount)
+ reportUniqueWarning("callgraph section does not have information for " +
+ std::to_string(NotListedCount) + " functions in " +
+ getElfObject().getFileName());
+ if (UnknownCount)
+ reportUniqueWarning("callgraph section has unknown type id for " +
+ std::to_string(UnknownCount) + " indirect targets in " +
+ getElfObject().getFileName());
+}
+
+template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
+ this->processCallGraphSection();
// Print indirect targets
OS << "\nINDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])";
@@ -5372,17 +5383,14 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
// Print indirect targets with unknown type.
// For completeness, functions for which the call graph section does not
// provide information are included.
- if (NotListedCount || UnknownCount) {
- OS << "\nUNKNOWN";
- for (const auto &El : this->FuncInfo) {
+ for (const auto &El : this->FuncCGInfo) {
+ FunctionKind FuncKind = El.second.Kind;
+ if (FuncKind == FunctionKind::NOT_LISTED ||
+ FuncKind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID) {
uint64_t FuncEntryPc = El.first;
- FunctionKind FuncKind = El.second.Kind;
- if (FuncKind == FunctionKind::NOT_LISTED ||
- FuncKind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID)
- OS << " " << format("%lx", FuncEntryPc);
+ OS << " " << format("%lx", FuncEntryPc);
}
}
-
// Print indirect targets to type id mapping.
for (const auto &El : this->TypeIdToIndirTargets) {
uint64_t TypeId = El.first;
@@ -5404,7 +5412,7 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
// Print function entry to indirect call site addresses mapping from disasm.
OS << "\n\nINDIRECT CALL SITES (CALLER_ADDR [CALL_SITE_ADDR,])";
- for (const auto &El : this->FuncInfo) {
+ for (const auto &El : this->FuncCGInfo) {
auto CallerPc = El.first;
auto FuncIndirCallSites = El.second.IndirectCallSites;
if (!FuncIndirCallSites.empty()) {
@@ -5417,25 +5425,18 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
// Print function entry to direct call site and target function entry
// addresses mapping from disasm.
OS << "\n\nDIRECT CALL SITES (CALLER_ADDR [(CALL_SITE_ADDR, TARGET_ADDR),])";
- for (const auto &El : this->FuncInfo) {
+ for (const auto &El : this->FuncCGInfo) {
auto CallerPc = El.first;
auto FuncDirCallSites = El.second.DirectCallSites;
if (!FuncDirCallSites.empty()) {
OS << "\n" << format("%lx", CallerPc);
- for (const FunctionInfo::DirectCallSite &DCS : FuncDirCallSites) {
+ for (const FunctionCallgraphInfo::DirectCallSite &DCS :
+ FuncDirCallSites) {
OS << " " << format("%lx", DCS.CallSite) << " "
<< format("%lx", DCS.Callee);
}
}
}
-
- // Print function entry pc to function name mapping.
- // OS << "\n\nFUNCTIONS (FUNC_ENTRY_ADDR, SYM_NAME)";
- // for (const auto &El : FuncInfo) {
- // uint64_t EntryPc = El.first;
- // const auto &Name = El.second.Name;
- // OS << "\n" << format("%lx", EntryPc) << " " << Name;
- // }
OS << "\n";
}
@@ -8016,86 +8017,6 @@ template <class ELFT> void JSONELFDumper<ELFT>::printDynamicTable() {
}
}
-template <class ELFT> void JSONELFDumper<ELFT>::printCallGraphInfo() {
- this->callGraphInfoHelper();
- uint64_t NotListedCount = 0;
- uint64_t UnknownCount = 0;
-
- for (const auto &El : this->FuncInfo) {
- NotListedCount += El.second.Kind == FunctionKind::NOT_LISTED;
- UnknownCount += El.second.Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
- }
- if (NotListedCount) // TODO(prabhuk): include object file name to the warning
- this->reportUniqueWarning(
- "callgraph section does not have information for " +
- std::to_string(NotListedCount) + " functions");
- if (UnknownCount) // TODO(prabhuk): include object file name to the warning
- this->reportUniqueWarning("callgraph section has unknown type id for " +
- std::to_string(UnknownCount) +
- " indirect targets");
-
- DictScope D(this->W, "callgraph_info");
-
- // Use llvm-symbolizer to get function name and address instead.
- // {
- // ListScope A(this->W, "functions");
- // for (auto const &F : this->FuncInfo) {
- // DictScope D(this->W);
- // this->W.printHex("function_address", F.first);
- // }
- // }
-
- {
- ListScope A(this->W, "direct_call_sites");
- for (auto const &F : this->FuncInfo) {
- if (F.second.DirectCallSites.empty())
- continue;
- DictScope D(this->W);
- this->W.printHex("caller", F.first);
- ListScope A(this->W, "call_sites");
- for (auto const &CS : F.second.DirectCallSites) {
- DictScope D(this->W);
- this->W.printHex("call_site", CS.CallSite);
- this->W.printHex("callee", CS.Callee);
- }
- }
- }
-
- {
- ListScope A(this->W, "indirect_call_sites");
- for (auto const &F : this->FuncInfo) {
- if (F.second.IndirectCallSites.empty())
- continue;
- DictScope D(this->W);
- this->W.printHex("caller", F.first);
- // ListScope A(this->W, "call_sites");
- this->W.printHexList("call_sites", F.second.IndirectCallSites);
- }
- }
-
- {
- ListScope A(this->W, "indirect_call_types");
- for (auto const &T : this->TypeIdToIndirCallSites) {
- for (auto const &CS : T.second) {
- DictScope D(this->W);
- this->W.printHex("call_site", CS);
- this->W.printHex("type_id", T.first);
- }
- }
- }
-
- {
- ListScope A(this->W, "indirect_target_types");
- for (auto const &T : this->TypeIdToIndirTargets) {
- for (auto const &Target : T.second) {
- DictScope D(this->W);
- this->W.printHex("function_address", Target);
- this->W.printHex("type_id", T.first);
- }
- }
- }
-}
-
template <class ELFT> void LLVMELFDumper<ELFT>::printDynamicRelocations() {
W.startLine() << "Dynamic Relocations {\n";
W.indent();
@@ -8351,7 +8272,79 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCGProfile() {
}
template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
- this->callGraphInfoHelper();
+ this->processCallGraphSection();
+
+ DictScope D(this->W, "callgraph_info");
+ // Use llvm-symbolizer to get function name and address instead.
+ // stack-sizes embeds function mangled name directly in JSON.
+ // {
+ // ListScope A(this->W, "functions");
+ // for (auto const &F : this->FuncCGInfo) {
+ // DictScope D(this->W);
+ // this->W.printHex("function_address", F.first);
+ // }
+ // }
+
+ {
+ ListScope A(this->W, "indirect_target_types");
+ SmallVector<uint64_t, 4> Unknowns;
+ for (const auto &El : this->FuncCGInfo) {
+ FunctionKind FuncKind = El.second.Kind;
+ if (FuncKind == FunctionKind::NOT_LISTED ||
+ FuncKind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID) {
+ uint64_t FuncEntryPc = El.first;
+ Unknowns.emplace_back(FuncEntryPc);
+ }
+ }
+ if (!Unknowns.empty())
+ this->W.printHexList("unknown", Unknowns);
+ for (auto const &T : this->TypeIdToIndirTargets) {
+ for (auto const &Target : T.second) {
+ DictScope D(this->W);
+ this->W.printHex("function_address", Target);
+ this->W.printHex("type_id", T.first);
+ }
+ }
+ }
+
+ {
+ ListScope A(this->W, "direct_call_sites");
+ for (auto const &F : this->FuncCGInfo) {
+ if (F.second.DirectCallSites.empty())
+ continue;
+ DictScope D(this->W);
+ this->W.printHex("caller", F.first);
+ ListScope A(this->W, "call_sites");
+ for (auto const &CS : F.second.DirectCallSites) {
+ DictScope D(this->W);
+ this->W.printHex("call_site", CS.CallSite);
+ this->W.printHex("callee", CS.Callee);
+ }
+ }
+ }
+
+ {
+ ListScope A(this->W, "indirect_call_sites");
+ for (auto const &F : this->FuncCGInfo) {
+ if (F.second.IndirectCallSites.empty())
+ continue;
+ DictScope D(this->W);
+ this->W.printHex("caller", F.first);
+ // ListScope A(this->W, "call_sites");
+ this->W.printHexList("call_sites", F.second.IndirectCallSites);
+ }
+ }
+
+ {
+ ListScope A(this->W, "indirect_call_types");
+ for (auto const &T : this->TypeIdToIndirCallSites) {
+ for (auto const &CS : T.second) {
+ DictScope D(this->W);
+ this->W.printHex("call_site", CS);
+ this->W.printHex("type_id", T.first);
+ }
+ }
+ }
}
template <class ELFT>
>From c05932e9204eaf0e653cd6561427b9b53680c3af Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Mon, 8 Sep 2025 18:48:06 -0700
Subject: [PATCH 05/12] Add readelf callgraph info tests.
---
.../call-graph-info-callgraph-section.test | 77 +++++++++++++++++++
...call-graph-info-err-invalid-func-kind.test | 19 +++++
...-info-err-malformed-callgraph-section.test | 21 +++++
...info-err-malformed-callgraph-section2.test | 21 +++++
...info-err-malformed-callgraph-section3.test | 23 ++++++
...all-graph-info-invalid-format-version.test | 17 ++++
...-graph-info-warn-no-callgraph-section.test | 15 ++++
llvm/tools/llvm-readobj/ELFDumper.cpp | 67 ++++++++--------
8 files changed, 227 insertions(+), 33 deletions(-)
create mode 100644 llvm/test/tools/llvm-readobj/ELF/call-graph-info-callgraph-section.test
create mode 100644 llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-func-kind.test
create mode 100644 llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section.test
create mode 100644 llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section2.test
create mode 100644 llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section3.test
create mode 100644 llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test
create mode 100644 llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-no-callgraph-section.test
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-callgraph-section.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-callgraph-section.test
new file mode 100644
index 0000000000000..60d1c8323f8e8
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-callgraph-section.test
@@ -0,0 +1,77 @@
+## Tests --call-graph-info prints information from call graph section.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
+
+# CHECK: INDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])
+# CHECK-NEXT: UNKNOWN 6
+# CHECK-NEXT: 20 a
+# CHECK-EMPTY:
+# CHECK-NEXT: INDIRECT CALL TYPES (TYPEID [CALL_SITE_ADDR,])
+# CHECK-NEXT: 10 9
+# CHECK-EMPTY:
+# CHECK-NEXT: INDIRECT CALL SITES (CALLER_ADDR [CALL_SITE_ADDR,])
+# CHECK-NEXT: 6 9
+# CHECK-EMPTY:
+# CHECK-NEXT: DIRECT CALL SITES (CALLER_ADDR [(CALL_SITE_ADDR, TARGET_ADDR),])
+# CHECK-NEXT: 0 5 5
+#
+#FUNCTIONS (FUNC_ENTRY_ADDR, SYM_NAME)
+# 0 foo
+# 6 bar
+# a baz
+# b qux
+
+.text
+
+.globl foo
+.type foo, at function
+foo: #< foo is at 0.
+.Lfoo_begin:
+ callq foo #< direct call is at 5. target is foo (5).
+ retq
+
+.globl bar
+.type bar, at function
+bar: #< bar is at 6.
+ callq *-40(%rbp) #< indirect call is at 9.
+ retq
+
+.globl baz
+.type baz, at function
+baz: #< baz is at 10 (a).
+ retq
+
+.globl qux
+.type qux, at function
+qux: #< qux is at 11 (b).
+ retq
+
+.section .callgraph,"o", at progbits,.text
+.quad 0 #< Format version number.
+.quad 0 #< foo()'s entry address.
+.quad 0 #< Function kind: not an indirect target.
+.quad 0 #< Count of indirect call sites that follow: 0.
+.quad 1 #< Count of direct call sites that follow: 1>
+.quad 5 #< Direct callsite call to foo>
+.quad 5 #< Direct callee foo's address>
+
+.quad 0 #< Format version number.
+.quad 6 #< bar()'s entry address.
+.quad 1 #< Function kind: indirect target with unknown type id.
+.quad 1 #< Count of indirect call sites that follow: 1.
+.quad 16 #< Indirect call type id.
+.quad 9 #< Indirect call site.
+.quad 0 #< Count of direct call sites that follow: 0>
+
+.quad 0 #< Format version number.
+.quad 10 #< baz()'s entry address.
+.quad 2 #< Function kind: indirect target with known type id.
+.quad 32 #< Indirect target type id.
+.quad 0 #< Count of indirect call sites that follow: 0>.
+.quad 0 #< Count of direct call sites that follow: 0>
+
+# No call graph section entry for qux.
+# Technically its "UNKNOWN" type id but will not be printed as such by llvm-readelf.
+
+.text
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-func-kind.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-func-kind.test
new file mode 100644
index 0000000000000..b55717b25fee0
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-func-kind.test
@@ -0,0 +1,19 @@
+## Tests that --call-graph-info fails if .callgraph section has invalid
+## function kind value.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+
+# ERR: llvm-readelf: warning: '[[FILE]]': Unknown function kind in .callgraph section.
+
+.text
+.globl _Z3foov
+.type _Z3foov, at function
+_Z3foov:
+ callq _Z3foov at PLT
+
+.section .callgraph,"o", at progbits,.text
+.quad 0 #< Format version number.
+.quad 0 #< Function entry address.
+.quad 3 #< Invalid function kind: must be one of 0, 1, 2.
+.text
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section.test
new file mode 100644
index 0000000000000..459d002d35352
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section.test
@@ -0,0 +1,21 @@
+## Tests that --call-graph-info fails if .callgraph section has invalid size.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+
+# ERR: llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section.
+
+.text
+.globl _Z3foov
+.type _Z3foov, at function
+_Z3foov:
+ callq _Z3foov at PLT
+
+.section .callgraph,"o", at progbits,.text
+# Each unit in .callgraph section must have 64bit size. Therefore, byte size
+# must be divisible by 64.
+.quad 0
+.quad 0
+.quad 0
+.byte 0
+.text
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section2.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section2.test
new file mode 100644
index 0000000000000..2c84469526552
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section2.test
@@ -0,0 +1,21 @@
+## Tests that --call-graph-info fails if .callgraph section does not have
+## an expected value, e.g., not as much call sites as the given count.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+
+# ERR: llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section.
+
+.text
+.globl _Z3foov
+.type _Z3foov, at function
+_Z3foov:
+ callq _Z3foov at PLT
+
+.section .callgraph,"o", at progbits,.text
+.quad 0 #< Format version number.
+.quad 0 #< Function entry address.
+.quad 0 #< Function kind.
+.quad 1 #< Indirect call site count that follows.
+ #< Missing indirect call entries here.
+.text
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section3.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section3.test
new file mode 100644
index 0000000000000..5bfbd709c78e2
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section3.test
@@ -0,0 +1,23 @@
+## Tests that --call-graph-info fails if .callgraph section does not have
+## an expected value, e.g., not as much call sites as the given count.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+
+# ERR: llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section.
+
+.text
+.globl _Z3foov
+.type _Z3foov, at function
+_Z3foov:
+ callq _Z3foov
+ retq
+
+.section .callgraph,"o", at progbits,.text
+.quad 0 #< Format version number.
+.quad 0 #< Function entry address.
+.quad 0 #< Function kind.
+.quad 0 #< Indirect call site count.
+.quad 1 #< Direct call site count that follows.
+ #< Missing direct call entries here.
+.text
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test
new file mode 100644
index 0000000000000..4f9d7dbeb844a
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test
@@ -0,0 +1,17 @@
+## Tests that --call-graph-info fails if .callgraph section has unknown format
+## version number.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+
+# ERR: llvm-readelf: warning: '[[FILE]]': Unknown format version in .callgraph section.
+
+.text
+.globl _Z3foov
+.type _Z3foov, at function
+_Z3foov:
+ callq _Z3foov at PLT
+
+.section .callgraph,"o", at progbits,.text
+.quad 1 #< Invalid format version number: the only supported version is 0.
+.text
\ No newline at end of file
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-no-callgraph-section.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-no-callgraph-section.test
new file mode 100644
index 0000000000000..cc904dbed2e67
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-no-callgraph-section.test
@@ -0,0 +1,15 @@
+## Tests that --call-graph-info warns if there is no .callgraph section.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
+
+# CHECK: llvm-readelf: warning: '[[FILE]]': No .callgraph section found.
+## Make sure callgraph info header is not printed when there is no .callgraph section
+# CHECK-NOT: INDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])
+# CHECK-NOT: INDIRECT CALL TYPES (TYPEID [CALL_SITE_ADDR,])
+
+.text
+.globl _Z3foov
+.type _Z3foov, at function
+_Z3foov:
+ callq _Z3foov at PLT
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index 66722fadd00bd..ea158d80bcb53 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -469,8 +469,8 @@ template <typename ELFT> class ELFDumper : public ObjDumper {
MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirTargets;
// Callgraph - Read callgraph section and process its contents to populate
// Callgraph related data structures which will be used to dump callgraph
- // info.
- void processCallGraphSection();
+ // info. Returns false if there is no .callgraph section in the input file.
+ bool processCallGraphSection();
private:
mutable SmallVector<std::optional<VersionEntry>, 0> VersionMap;
@@ -5271,28 +5271,26 @@ getCallGraphSection(const object::ELFObjectFile<ELFT> &ObjF) {
else
consumeError(NameOrErr.takeError());
- if (Name == CallGraphSectionName) {
- CallGraphSection = Sec;
- break;
- }
+ if (Name == CallGraphSectionName)
+ return Sec;
}
return CallGraphSection;
}
-template <class ELFT> void ELFDumper<ELFT>::processCallGraphSection() {
+template <class ELFT> bool ELFDumper<ELFT>::processCallGraphSection() {
std::optional<object::SectionRef> CallGraphSection =
getCallGraphSection(ObjF);
- if (!CallGraphSection) {
- reportUniqueWarning("there is no .callgraph section in " +
- getElfObject().getFileName());
- return;
+ if (!CallGraphSection.has_value()) {
+ reportUniqueWarning("No .callgraph section found.");
+ return false;
}
StringRef CGSecContents = cantFail(CallGraphSection.value().getContents());
// TODO: some entries are written in pointer size. are they always 64-bit?
- if (CGSecContents.size() % sizeof(uint64_t))
- reportUniqueWarning("Malformed .callgraph section. Unexpected size in " +
- getElfObject().getFileName());
+ if (CGSecContents.size() % sizeof(uint64_t)) {
+ reportUniqueWarning("Malformed .callgraph section. Unexpected size.");
+ exit(1);
+ }
size_t Size = CGSecContents.size() / sizeof(uint64_t);
auto *It = reinterpret_cast<const uint64_t *>(CGSecContents.data());
@@ -5300,18 +5298,20 @@ template <class ELFT> void ELFDumper<ELFT>::processCallGraphSection() {
auto CGHasNext = [&]() { return It < End; };
auto CGNext = [&]() -> uint64_t {
- if (!CGHasNext())
- reportUniqueWarning("Malformed .callgraph section. Parsing error in " +
- getElfObject().getFileName());
+ if (!CGHasNext()) {
+ reportUniqueWarning("Malformed .callgraph section. Parsing error.");
+ exit(1);
+ }
return *It++;
};
while (CGHasNext()) {
// Format version number.
uint64_t FormatVersionNumber = CGNext();
- if (FormatVersionNumber != 0)
- reportUniqueWarning("Unknown format version in .callgraph section in " +
- getElfObject().getFileName());
+ if (FormatVersionNumber != 0) {
+ reportUniqueWarning("Unknown format version in .callgraph section.");
+ exit(1);
+ }
// Function entry pc.
uint64_t FuncEntryPc = CGNext();
// Function kind.
@@ -5328,9 +5328,8 @@ template <class ELFT> void ELFDumper<ELFT>::processCallGraphSection() {
TypeIdToIndirTargets[CGNext()].push_back(FuncEntryPc);
break;
default:
- this->reportUniqueWarning(
- "Unknown function kind in .callgraph section in " +
- getElfObject().getFileName());
+ this->reportUniqueWarning("Unknown function kind in .callgraph section.");
+ exit(1);
}
// Read indirect call sites info.
uint64_t IndirectCallSiteCount = CGNext();
@@ -5360,22 +5359,18 @@ template <class ELFT> void ELFDumper<ELFT>::processCallGraphSection() {
NotListedCount += El.second.Kind == FunctionKind::NOT_LISTED;
UnknownCount += El.second.Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
}
- for (const auto &El : FuncCGInfo) {
- NotListedCount += El.second.Kind == FunctionKind::NOT_LISTED;
- UnknownCount += El.second.Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
- }
if (NotListedCount)
reportUniqueWarning("callgraph section does not have information for " +
- std::to_string(NotListedCount) + " functions in " +
- getElfObject().getFileName());
+ std::to_string(NotListedCount) + " functions.");
if (UnknownCount)
reportUniqueWarning("callgraph section has unknown type id for " +
- std::to_string(UnknownCount) + " indirect targets in " +
- getElfObject().getFileName());
+ std::to_string(UnknownCount) + " indirect targets.");
+ return true;
}
template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
- this->processCallGraphSection();
+ if (!this->processCallGraphSection())
+ return;
// Print indirect targets
OS << "\nINDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])";
@@ -5383,10 +5378,15 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
// Print indirect targets with unknown type.
// For completeness, functions for which the call graph section does not
// provide information are included.
+ bool printedHeader = false;
for (const auto &El : this->FuncCGInfo) {
FunctionKind FuncKind = El.second.Kind;
if (FuncKind == FunctionKind::NOT_LISTED ||
FuncKind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID) {
+ if (!printedHeader) {
+ OS << "\nUNKNOWN";
+ printedHeader = true;
+ }
uint64_t FuncEntryPc = El.first;
OS << " " << format("%lx", FuncEntryPc);
}
@@ -8272,7 +8272,8 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCGProfile() {
}
template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
- this->processCallGraphSection();
+ if (!this->processCallGraphSection())
+ return;
DictScope D(this->W, "callgraph_info");
// Use llvm-symbolizer to get function name and address instead.
>From c35b6d37e337fb6a3f7a2a42c83a532a42475bac Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Tue, 9 Sep 2025 10:37:42 -0700
Subject: [PATCH 06/12] more tests
---
...all-graph-info-invalid-format-version.test | 2 +-
.../llvm-readobj/ELF/call-graph-info.test | 496 ++++++++++++++++++
2 files changed, 497 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test
index 4f9d7dbeb844a..a1213db081951 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test
@@ -14,4 +14,4 @@ _Z3foov:
.section .callgraph,"o", at progbits,.text
.quad 1 #< Invalid format version number: the only supported version is 0.
-.text
\ No newline at end of file
+.text
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
new file mode 100644
index 0000000000000..547b608cc5950
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
@@ -0,0 +1,496 @@
+## Tests how --call-graph-info prints the call graph information.
+# RUN: yaml2obj --docnum=1 %s -o %t
+# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
+
+# Yaml input is obtained by compiling the below source to object with:
+# clang -fexperimental-call-graph-section test.c -o test.o
+# then to yaml with:
+# obj2yaml test.o > test.yaml
+# Remove ProgramHeaders if obj2yaml fails.
+
+# The content of the .callgraph section is fixed with this yaml in raw format.
+
+# Source:
+# void foo() {}
+#
+# void bar() {}
+#
+# int baz(char a) {
+# return 0;
+# }
+#
+# int main() {
+# // Indirect calls.
+# void (*fp_foo)() = foo;
+# fp_foo();
+#
+# void (*fp_bar)() = bar;
+# fp_bar();
+#
+# char a;
+# int (*fp_baz)(char) = baz;
+# fp_baz(a);
+#
+# // Direct calls.
+# foo();
+# bar();
+# baz(a);
+#
+# return 0;
+# }
+
+# CHECK: INDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])
+# CHECK-NEXT: f85c699bb8ef20a2 17a0 17b0
+# CHECK-NEXT: 308e4b8159bc8654 17c0
+# CHECK-NEXT: a9494def81a01dc 17d0
+# CHECK-EMPTY:
+# CHECK-NEXT: INDIRECT CALL TYPES (TYPEID [CALL_SITE_ADDR,])
+# CHECK-NEXT: f85c699bb8ef20a2 17ed 17fb
+# CHECK-NEXT: 308e4b8159bc8654 1810
+# CHECK-EMPTY:
+# CHECK-NEXT: INDIRECT CALL SITES (CALLER_ADDR [CALL_SITE_ADDR,])
+# CHECK-NEXT: 17d0 17ed 17fb 1810
+# CHECK-EMPTY:
+# CHECK-NEXT: DIRECT CALL SITES (CALLER_ADDR [(CALL_SITE_ADDR, TARGET_ADDR),])
+# CHECK-NEXT: 17d0 1815 17a0 181a 17b0 1823 17c0
+
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2LSB
+ Type: ET_DYN
+ Machine: EM_X86_64
+ Entry: 0x16E0
+Sections:
+ - Name: .interp
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC ]
+ Address: 0x2A8
+ AddressAlign: 0x1
+ Content: 2F6C696236342F6C642D6C696E75782D7838362D36342E736F2E3200
+ - Name: .note.ABI-tag
+ Type: SHT_NOTE
+ Flags: [ SHF_ALLOC ]
+ Address: 0x2C4
+ AddressAlign: 0x4
+ Notes:
+ - Name: GNU
+ Desc: '00000000030000000200000000000000'
+ Type: NT_VERSION
+ - Name: .note.gnu.build-id
+ Type: SHT_NOTE
+ Flags: [ SHF_ALLOC ]
+ Address: 0x2E4
+ AddressAlign: 0x4
+ Notes:
+ - Name: GNU
+ Desc: 3E3D197EC968B0AD7D4A3809E7355CA3BE816929
+ Type: NT_PRPSINFO
+ - Name: .dynsym
+ Type: SHT_DYNSYM
+ Flags: [ SHF_ALLOC ]
+ Address: 0x308
+ Link: .dynstr
+ AddressAlign: 0x8
+ - Name: .gnu.version
+ Type: SHT_GNU_versym
+ Flags: [ SHF_ALLOC ]
+ Address: 0x398
+ Link: .dynsym
+ AddressAlign: 0x2
+ Entries: [ 0, 2, 1, 1, 3, 1 ]
+ - Name: .gnu.version_r
+ Type: SHT_GNU_verneed
+ Flags: [ SHF_ALLOC ]
+ Address: 0x3A4
+ Link: .dynstr
+ AddressAlign: 0x4
+ Dependencies:
+ - Version: 1
+ File: libc.so.6
+ Entries:
+ - Name: GLIBC_2.2.5
+ Hash: 157882997
+ Flags: 0
+ Other: 3
+ - Name: GLIBC_2.34
+ Hash: 110530996
+ Flags: 0
+ Other: 2
+ - Name: .gnu.hash
+ Type: SHT_GNU_HASH
+ Flags: [ SHF_ALLOC ]
+ Address: 0x3D8
+ Link: .dynsym
+ AddressAlign: 0x8
+ Header:
+ SymNdx: 0x6
+ Shift2: 0x1A
+ BloomFilter: [ 0x0 ]
+ HashBuckets: [ 0x0 ]
+ HashValues: [ ]
+ - Name: .dynstr
+ Type: SHT_STRTAB
+ Flags: [ SHF_ALLOC ]
+ Address: 0x3F4
+ AddressAlign: 0x1
+ - Name: .rela.dyn
+ Type: SHT_RELA
+ Flags: [ SHF_ALLOC ]
+ Address: 0x480
+ Link: .dynsym
+ AddressAlign: 0x8
+ Relocations:
+ - Offset: 0x2890
+ Type: R_X86_64_RELATIVE
+ Addend: 5904
+ - Offset: 0x2898
+ Type: R_X86_64_RELATIVE
+ Addend: 5968
+ - Offset: 0x3A80
+ Type: R_X86_64_RELATIVE
+ Addend: 14976
+ - Offset: 0x2A50
+ Symbol: __libc_start_main
+ Type: R_X86_64_GLOB_DAT
+ - Offset: 0x2A58
+ Symbol: __gmon_start__
+ Type: R_X86_64_GLOB_DAT
+ - Offset: 0x2A60
+ Symbol: __register_frame_info
+ Type: R_X86_64_GLOB_DAT
+ - Offset: 0x2A68
+ Symbol: __cxa_finalize
+ Type: R_X86_64_GLOB_DAT
+ - Offset: 0x2A70
+ Symbol: __deregister_frame_info
+ Type: R_X86_64_GLOB_DAT
+ - Name: .rela.plt
+ Type: SHT_RELA
+ Flags: [ SHF_ALLOC, SHF_INFO_LINK ]
+ Address: 0x540
+ Link: .dynsym
+ AddressAlign: 0x8
+ Info: .got.plt
+ Relocations:
+ - Offset: 0x3AA0
+ Symbol: __register_frame_info
+ Type: R_X86_64_JUMP_SLOT
+ - Offset: 0x3AA8
+ Symbol: __cxa_finalize
+ Type: R_X86_64_JUMP_SLOT
+ - Offset: 0x3AB0
+ Symbol: __deregister_frame_info
+ Type: R_X86_64_JUMP_SLOT
+ - Name: .rodata
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC, SHF_MERGE ]
+ Address: 0x588
+ AddressAlign: 0x4
+ EntSize: 0x4
+ Content: '01000200'
+ - Name: .eh_frame_hdr
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC ]
+ Address: 0x58C
+ AddressAlign: 0x4
+ Content: 011B033B4000000007000000541100005C0000008411000088000000C4110000A800000014120000C800000024120000E800000034120000080100004412000028010000
+ - Name: .eh_frame
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC ]
+ Address: 0x5D0
+ AddressAlign: 0x8
+ Content: 1400000000000000017A5200017810011B0C070890010710100000001C000000F010000022000000000000001400000000000000017A5200017810011B0C0708900100001C0000001C000000F41000003800000000410E108602430D06730C07080000001C0000003C000000141100004A00000000410E108602430D0602450C070800001C0000005C000000441100000600000000410E108602430D06410C07080000001C0000007C000000341100000600000000410E108602430D06410C07080000001C0000009C000000241100000E00000000410E108602430D06490C07080000001C000000BC000000141100005B00000000410E108602430D0602560C0708000000000000
+ - Name: .text
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
+ Address: 0x16E0
+ AddressAlign: 0x10
+ Content: 31ED4989D15E4889E24883E4F050544531C031C9488D3DD5000000FF154F130000F4CCCCCCCCCCCCCCCCCCCCCCCCCCCC554889E5F6059D230000017402EB27C6059223000001488B05331300004883F8007413488D3DC2EEFFFF488D357F230000E81A0100005DC30F1F840000000000554889E5F605A5230000017402EB39C6059A23000001488B05FB1200004883F800740C488B3D06230000E8F1000000488B05EA1200004883F800740C488D3D69EEFFFFE8E80000005DC3CCCCCCCCCCCC554889E55DC3662E0F1F840000000000554889E55DC3662E0F1F840000000000554889E54088F88845FF31C05DC36690554889E54883EC30C745FC00000000488D05BAFFFFFF488945F0FF55F0488D05BCFFFFFF488945E8FF55E8488D05BEFFFFFF488945D8488B45D80FBE7DE7FFD0E88BFFFFFFE896FFFFFF0FBE7DE7E89DFFFFFF31C04883C4305DC3CC
+ - Name: .init
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
+ Address: 0x182C
+ AddressAlign: 0x4
+ Content: 4883EC08488B05211200004885C07402FFD04883C408C3
+ - Name: .fini
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
+ Address: 0x1844
+ AddressAlign: 0x4
+ Content: 4883EC084883C408C3
+ - Name: .plt
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
+ Address: 0x1850
+ AddressAlign: 0x10
+ Content: FF353A220000FF253C2200000F1F4000FF253A2200006800000000E9E0FFFFFFFF25322200006801000000E9D0FFFFFFFF252A2200006802000000E9C0FFFFFF
+ - Name: .init_array
+ Type: SHT_INIT_ARRAY
+ Flags: [ SHF_WRITE, SHF_ALLOC ]
+ Address: 0x2890
+ AddressAlign: 0x8
+ Content: '0000000000000000'
+ - Name: .fini_array
+ Type: SHT_FINI_ARRAY
+ Flags: [ SHF_WRITE, SHF_ALLOC ]
+ Address: 0x2898
+ AddressAlign: 0x8
+ Content: '0000000000000000'
+ - Name: .dynamic
+ Type: SHT_DYNAMIC
+ Flags: [ SHF_WRITE, SHF_ALLOC ]
+ Address: 0x28A0
+ Link: .dynstr
+ AddressAlign: 0x8
+ Entries:
+ - Tag: DT_NEEDED
+ Value: 0x80
+ - Tag: DT_NEEDED
+ Value: 0x5F
+ - Tag: DT_FLAGS_1
+ Value: 0x8000000
+ - Tag: DT_DEBUG
+ Value: 0x0
+ - Tag: DT_RELA
+ Value: 0x480
+ - Tag: DT_RELASZ
+ Value: 0xC0
+ - Tag: DT_RELAENT
+ Value: 0x18
+ - Tag: DT_RELACOUNT
+ Value: 0x3
+ - Tag: DT_JMPREL
+ Value: 0x540
+ - Tag: DT_PLTRELSZ
+ Value: 0x48
+ - Tag: DT_PLTGOT
+ Value: 0x3A88
+ - Tag: DT_PLTREL
+ Value: 0x7
+ - Tag: DT_SYMTAB
+ Value: 0x308
+ - Tag: DT_SYMENT
+ Value: 0x18
+ - Tag: DT_STRTAB
+ Value: 0x3F4
+ - Tag: DT_STRSZ
+ Value: 0x8A
+ - Tag: DT_GNU_HASH
+ Value: 0x3D8
+ - Tag: DT_INIT_ARRAY
+ Value: 0x2890
+ - Tag: DT_INIT_ARRAYSZ
+ Value: 0x8
+ - Tag: DT_FINI_ARRAY
+ Value: 0x2898
+ - Tag: DT_FINI_ARRAYSZ
+ Value: 0x8
+ - Tag: DT_INIT
+ Value: 0x182C
+ - Tag: DT_FINI
+ Value: 0x1844
+ - Tag: DT_VERSYM
+ Value: 0x398
+ - Tag: DT_VERNEED
+ Value: 0x3A4
+ - Tag: DT_VERNEEDNUM
+ Value: 0x1
+ - Tag: DT_NULL
+ Value: 0x0
+ - Name: .got
+ Type: SHT_PROGBITS
+ Flags: [ SHF_WRITE, SHF_ALLOC ]
+ Address: 0x2A50
+ AddressAlign: 0x8
+ Content: '00000000000000000000000000000000000000000000000000000000000000000000000000000000'
+ - Name: .relro_padding
+ Type: SHT_NOBITS
+ Flags: [ SHF_WRITE, SHF_ALLOC ]
+ Address: 0x2A78
+ AddressAlign: 0x1
+ Size: 0x588
+ - Name: .data
+ Type: SHT_PROGBITS
+ Flags: [ SHF_WRITE, SHF_ALLOC ]
+ Address: 0x3A78
+ AddressAlign: 0x8
+ Content: '00000000000000000000000000000000'
+ - Name: .got.plt
+ Type: SHT_PROGBITS
+ Flags: [ SHF_WRITE, SHF_ALLOC ]
+ Address: 0x3A88
+ AddressAlign: 0x8
+ Content: A02800000000000000000000000000000000000000000000661800000000000076180000000000008618000000000000
+ - Name: .bss
+ Type: SHT_NOBITS
+ Flags: [ SHF_WRITE, SHF_ALLOC ]
+ Address: 0x3AB8
+ AddressAlign: 0x8
+ Size: 0x49
+ - Name: .comment
+ Type: SHT_PROGBITS
+ Flags: [ SHF_MERGE, SHF_STRINGS ]
+ AddressAlign: 0x1
+ EntSize: 0x1
+ Content: 004675636873696120636C616E672076657273696F6E2032322E302E306769742028676974406769746875622E636F6D3A6C6C766D2F6C6C766D2D70726F6A6563742E676974206135343965373363616436303333366438653963303632326165376164383661613635656634636529004C696E6B65723A2046756368736961204C4C442032322E302E302028676974406769746875622E636F6D3A5072616268756B2F6C6C766D2D70726F6A6563742E676974203239343432666166656337373437633632356135653466393937303861633532323139373662663729004675636873696120636C616E672076657273696F6E2032322E302E3067697420282F7573722F6C6F63616C2F676F6F676C652F686F6D652F7072616268756B722F6C6C766D2F6C6C766D2D70726F6A6563742F636C616E67203335383463366161636163363239633531633464316538653038323538633063323466336131363529004675636873696120636C616E672076657273696F6E2032322E302E306769742028676974406769746875622E636F6D3A5072616268756B2F6C6C766D2D70726F6A6563742E67697420323934343266616665633737343763363235613565346639393730386163353232313937366266372900
+ - Name: .callgraph
+ Type: SHT_PROGBITS
+ Flags: [ SHF_LINK_ORDER ]
+ Link: .text
+ AddressAlign: 0x1
+ Content: 0000000000000000A0170000000000000200000000000000A220EFB89B695CF8000000000000000000000000000000000000000000000000B0170000000000000200000000000000A220EFB89B695CF8000000000000000000000000000000000000000000000000C01700000000000002000000000000005486BC59814B8E30000000000000000000000000000000000000000000000000D0170000000000000200000000000000DC011AF8DE94940A0300000000000000A220EFB89B695CF8ED17000000000000A220EFB89B695CF8FB170000000000005486BC59814B8E30101800000000000003000000000000001518000000000000A0170000000000001A18000000000000B0170000000000002318000000000000C017000000000000
+Symbols:
+ - Name: __abi_tag
+ Type: STT_OBJECT
+ Section: .note.ABI-tag
+ Value: 0x2C4
+ Size: 0x20
+ - Name: crtbegin.c
+ Type: STT_FILE
+ Index: SHN_ABS
+ - Name: __do_init
+ Type: STT_FUNC
+ Section: .text
+ Value: 0x1710
+ Size: 0x38
+ - Name: __do_init.__initialized
+ Type: STT_OBJECT
+ Section: .bss
+ Value: 0x3AB8
+ Size: 0x1
+ - Name: __EH_FRAME_LIST__
+ Type: STT_OBJECT
+ Section: .eh_frame
+ Value: 0x5FC
+ - Name: __do_init.__object
+ Type: STT_OBJECT
+ Section: .bss
+ Value: 0x3AC0
+ Size: 0x40
+ - Name: __do_fini
+ Type: STT_FUNC
+ Section: .text
+ Value: 0x1750
+ Size: 0x4A
+ - Name: __do_fini.__finalized
+ Type: STT_OBJECT
+ Section: .bss
+ Value: 0x3B00
+ Size: 0x1
+ - Name: __init
+ Type: STT_OBJECT
+ Section: .init_array
+ Value: 0x2890
+ Size: 0x8
+ - Name: __fini
+ Type: STT_OBJECT
+ Section: .fini_array
+ Value: 0x2898
+ Size: 0x8
+ - Name: __dso_handle
+ Type: STT_OBJECT
+ Section: .data
+ Value: 0x3A80
+ Size: 0x8
+ Other: [ STV_HIDDEN ]
+ - Name: call_graph_info.cpp
+ Type: STT_FILE
+ Index: SHN_ABS
+ - Name: crtend.c
+ Type: STT_FILE
+ Index: SHN_ABS
+ - Name: __EH_FRAME_LIST_END__
+ Type: STT_OBJECT
+ Section: .eh_frame
+ Value: 0x5D0
+ Size: 0x4
+ Other: [ STV_HIDDEN ]
+ - Name: _GLOBAL_OFFSET_TABLE_
+ Section: .got.plt
+ Value: 0x3A88
+ Other: [ STV_HIDDEN ]
+ - Name: _DYNAMIC
+ Section: .dynamic
+ Value: 0x28A0
+ Other: [ STV_HIDDEN ]
+ - Name: _init
+ Type: STT_FUNC
+ Section: .init
+ Value: 0x182C
+ Other: [ STV_HIDDEN ]
+ - Name: _fini
+ Type: STT_FUNC
+ Section: .fini
+ Value: 0x1844
+ Other: [ STV_HIDDEN ]
+ - Name: _start
+ Type: STT_FUNC
+ Section: .text
+ Binding: STB_GLOBAL
+ Value: 0x16E0
+ Size: 0x22
+ - Name: main
+ Type: STT_FUNC
+ Section: .text
+ Binding: STB_GLOBAL
+ Value: 0x17D0
+ Size: 0x5B
+ - Name: data_start
+ Section: .data
+ Binding: STB_WEAK
+ Value: 0x3A78
+ - Name: _IO_stdin_used
+ Type: STT_OBJECT
+ Section: .rodata
+ Binding: STB_GLOBAL
+ Value: 0x588
+ Size: 0x4
+ - Name: __libc_start_main
+ Type: STT_FUNC
+ Binding: STB_GLOBAL
+ - Name: __data_start
+ Section: .data
+ Binding: STB_GLOBAL
+ Value: 0x3A78
+ - Name: __gmon_start__
+ Binding: STB_WEAK
+ - Name: __register_frame_info
+ Binding: STB_WEAK
+ - Name: __cxa_finalize
+ Type: STT_FUNC
+ Binding: STB_WEAK
+ - Name: __deregister_frame_info
+ Binding: STB_WEAK
+ - Name: _Z3foov
+ Type: STT_FUNC
+ Section: .text
+ Binding: STB_GLOBAL
+ Value: 0x17A0
+ Size: 0x6
+ - Name: _Z3barv
+ Type: STT_FUNC
+ Section: .text
+ Binding: STB_GLOBAL
+ Value: 0x17B0
+ Size: 0x6
+ - Name: _Z3bazc
+ Type: STT_FUNC
+ Section: .text
+ Binding: STB_GLOBAL
+ Value: 0x17C0
+ Size: 0xE
+DynamicSymbols:
+ - Name: __libc_start_main
+ Type: STT_FUNC
+ Binding: STB_GLOBAL
+ - Name: __gmon_start__
+ Binding: STB_WEAK
+ - Name: __register_frame_info
+ Binding: STB_WEAK
+ - Name: __cxa_finalize
+ Type: STT_FUNC
+ Binding: STB_WEAK
+ - Name: __deregister_frame_info
+ Binding: STB_WEAK
+...
>From 6560cfa6a9cf440168dfca48feb3891d6c8888c3 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Tue, 9 Sep 2025 11:27:29 -0700
Subject: [PATCH 07/12] Extend tests to json
---
.../call-graph-info-callgraph-section.test | 108 +++++++++++++++---
llvm/tools/llvm-readobj/ELFDumper.cpp | 16 +--
2 files changed, 98 insertions(+), 26 deletions(-)
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-callgraph-section.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-callgraph-section.test
index 60d1c8323f8e8..85a8dce015a62 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-callgraph-section.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-callgraph-section.test
@@ -1,26 +1,96 @@
## Tests --call-graph-info prints information from call graph section.
# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
-# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
+# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=GNU
+# RUN: llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=LLVM
+# RUN: llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=JSON
-# CHECK: INDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])
-# CHECK-NEXT: UNKNOWN 6
-# CHECK-NEXT: 20 a
-# CHECK-EMPTY:
-# CHECK-NEXT: INDIRECT CALL TYPES (TYPEID [CALL_SITE_ADDR,])
-# CHECK-NEXT: 10 9
-# CHECK-EMPTY:
-# CHECK-NEXT: INDIRECT CALL SITES (CALLER_ADDR [CALL_SITE_ADDR,])
-# CHECK-NEXT: 6 9
-# CHECK-EMPTY:
-# CHECK-NEXT: DIRECT CALL SITES (CALLER_ADDR [(CALL_SITE_ADDR, TARGET_ADDR),])
-# CHECK-NEXT: 0 5 5
-#
-#FUNCTIONS (FUNC_ENTRY_ADDR, SYM_NAME)
-# 0 foo
-# 6 bar
-# a baz
-# b qux
+# GNU: llvm-readelf: warning: '[[FILE]]': callgraph section has unknown type id for 1 indirect targets.
+# GNU-EMPTY:
+# GNU-NEXT: INDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])
+# GNU-NEXT: UNKNOWN 6
+# GNU-NEXT: 20 a
+# GNU-EMPTY:
+# GNU-NEXT: INDIRECT CALL TYPES (TYPEID [CALL_SITE_ADDR,])
+# GNU-NEXT: 10 9
+# GNU-EMPTY:
+# GNU-NEXT: INDIRECT CALL SITES (CALLER_ADDR [CALL_SITE_ADDR,])
+# GNU-NEXT: 6 9
+# GNU-EMPTY:
+# GNU-NEXT: DIRECT CALL SITES (CALLER_ADDR [(CALL_SITE_ADDR, TARGET_ADDR),])
+# GNU-NEXT: 0 5 5
+
+# LLVM: {{.*}}llvm-readelf: warning: '[[FILE]]': callgraph section has unknown type id for 1 indirect targets.
+# LLVM: callgraph_info {
+# LLVM-NEXT: unknown_target_types: [0x6]
+# LLVM-NEXT: indirect_target_types [
+# LLVM-NEXT: {
+# LLVM-NEXT: function_address: 0xA
+# LLVM-NEXT: type_id: 0x20
+# LLVM-NEXT: }
+# LLVM-NEXT: ]
+# LLVM-NEXT: direct_call_sites [
+# LLVM-NEXT: {
+# LLVM-NEXT: caller: 0x0
+# LLVM-NEXT: call_sites [
+# LLVM-NEXT: {
+# LLVM-NEXT: call_site: 0x5
+# LLVM-NEXT: callee: 0x5
+# LLVM-NEXT: }
+# LLVM-NEXT: ]
+# LLVM-NEXT: }
+# LLVM-NEXT: ]
+# LLVM-NEXT: indirect_call_sites [
+# LLVM-NEXT: {
+# LLVM-NEXT: caller: 0x6
+# LLVM-NEXT: call_sites: [0x9]
+# LLVM-NEXT: }
+# LLVM-NEXT: ]
+# LLVM-NEXT: indirect_call_types [
+# LLVM-NEXT: {
+# LLVM-NEXT: call_site: 0x9
+# LLVM-NEXT: type_id: 0x10
+# LLVM-NEXT: }
+# LLVM-NEXT: ]
+# LLVM-NEXT: }
+
+# JSON: {{.*}}llvm-readelf: warning: '[[FILE]]': callgraph section has unknown type id for 1 indirect targets.
+# JSON: "callgraph_info": {
+# JSON-NEXT: "unknown_target_types": [
+# JSON-NEXT: 6
+# JSON-NEXT: ],
+# JSON-NEXT: "indirect_target_types": [
+# JSON-NEXT: {
+# JSON-NEXT: "function_address": 10,
+# JSON-NEXT: "type_id": 32
+# JSON-NEXT: }
+# JSON-NEXT: ],
+# JSON-NEXT: "direct_call_sites": [
+# JSON-NEXT: {
+# JSON-NEXT: "caller": 0,
+# JSON-NEXT: "call_sites": [
+# JSON-NEXT: {
+# JSON-NEXT: "call_site": 5,
+# JSON-NEXT: "callee": 5
+# JSON-NEXT: }
+# JSON-NEXT: ]
+# JSON-NEXT: }
+# JSON-NEXT: ],
+# JSON-NEXT: "indirect_call_sites": [
+# JSON-NEXT: {
+# JSON-NEXT: "caller": 6,
+# JSON-NEXT: "call_sites": [
+# JSON-NEXT: 9
+# JSON-NEXT: ]
+# JSON-NEXT: }
+# JSON-NEXT: ],
+# JSON-NEXT: "indirect_call_types": [
+# JSON-NEXT: {
+# JSON-NEXT: "call_site": 9,
+# JSON-NEXT: "type_id": 16
+# JSON-NEXT: }
+# JSON-NEXT: ]
+# JSON-NEXT: }
.text
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index ea158d80bcb53..8b9d84fdaba71 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -8287,7 +8287,6 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
// }
{
- ListScope A(this->W, "indirect_target_types");
SmallVector<uint64_t, 4> Unknowns;
for (const auto &El : this->FuncCGInfo) {
FunctionKind FuncKind = El.second.Kind;
@@ -8298,7 +8297,11 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
}
}
if (!Unknowns.empty())
- this->W.printHexList("unknown", Unknowns);
+ this->W.printHexList("unknown_target_types", Unknowns);
+ }
+
+ {
+ ListScope ITT(this->W, "indirect_target_types");
for (auto const &T : this->TypeIdToIndirTargets) {
for (auto const &Target : T.second) {
DictScope D(this->W);
@@ -8309,13 +8312,13 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
}
{
- ListScope A(this->W, "direct_call_sites");
+ ListScope DCT(this->W, "direct_call_sites");
for (auto const &F : this->FuncCGInfo) {
if (F.second.DirectCallSites.empty())
continue;
DictScope D(this->W);
this->W.printHex("caller", F.first);
- ListScope A(this->W, "call_sites");
+ ListScope CT(this->W, "call_sites");
for (auto const &CS : F.second.DirectCallSites) {
DictScope D(this->W);
this->W.printHex("call_site", CS.CallSite);
@@ -8325,19 +8328,18 @@ template <class ELFT> void LLVMELFDumper<ELFT>::printCallGraphInfo() {
}
{
- ListScope A(this->W, "indirect_call_sites");
+ ListScope ICT(this->W, "indirect_call_sites");
for (auto const &F : this->FuncCGInfo) {
if (F.second.IndirectCallSites.empty())
continue;
DictScope D(this->W);
this->W.printHex("caller", F.first);
- // ListScope A(this->W, "call_sites");
this->W.printHexList("call_sites", F.second.IndirectCallSites);
}
}
{
- ListScope A(this->W, "indirect_call_types");
+ ListScope ICT(this->W, "indirect_call_types");
for (auto const &T : this->TypeIdToIndirCallSites) {
for (auto const &CS : T.second) {
DictScope D(this->W);
>From 1ccb2332f28a149095758185895c928b01de4d3c Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Tue, 9 Sep 2025 15:10:53 -0700
Subject: [PATCH 08/12] Expand all tests to include JSON and LLVM formats.
---
...call-graph-info-err-invalid-func-kind.test | 2 +
...-info-err-malformed-callgraph-section.test | 4 +-
...info-err-malformed-callgraph-section2.test | 2 +
...info-err-malformed-callgraph-section3.test | 2 +
...all-graph-info-invalid-format-version.test | 2 +
...-graph-info-warn-no-callgraph-section.test | 5 +-
.../llvm-readobj/ELF/call-graph-info.test | 129 +++++++++++++++++-
7 files changed, 141 insertions(+), 5 deletions(-)
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-func-kind.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-func-kind.test
index b55717b25fee0..3bea2d057b4d7 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-func-kind.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-func-kind.test
@@ -3,6 +3,8 @@
# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
# ERR: llvm-readelf: warning: '[[FILE]]': Unknown function kind in .callgraph section.
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section.test
index 459d002d35352..b9219cd64e019 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section.test
@@ -2,7 +2,9 @@
# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
-
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+
# ERR: llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section.
.text
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section2.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section2.test
index 2c84469526552..83e7825de0abb 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section2.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section2.test
@@ -3,6 +3,8 @@
# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
# ERR: llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section.
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section3.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section3.test
index 5bfbd709c78e2..7488d15ca94a7 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section3.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section3.test
@@ -3,6 +3,8 @@
# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
# ERR: llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section.
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test
index a1213db081951..3bac1f1850bef 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test
@@ -3,6 +3,8 @@
# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
# ERR: llvm-readelf: warning: '[[FILE]]': Unknown format version in .callgraph section.
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-no-callgraph-section.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-no-callgraph-section.test
index cc904dbed2e67..fe0428f6635f6 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-no-callgraph-section.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-no-callgraph-section.test
@@ -2,11 +2,10 @@
# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
+# RUN: llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
+# RUN: llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
# CHECK: llvm-readelf: warning: '[[FILE]]': No .callgraph section found.
-## Make sure callgraph info header is not printed when there is no .callgraph section
-# CHECK-NOT: INDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])
-# CHECK-NOT: INDIRECT CALL TYPES (TYPEID [CALL_SITE_ADDR,])
.text
.globl _Z3foov
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
index 547b608cc5950..778813197f70b 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
@@ -1,6 +1,8 @@
## Tests how --call-graph-info prints the call graph information.
# RUN: yaml2obj --docnum=1 %s -o %t
-# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
+# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
+# RUN: llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines --check-prefix=LLVM -DFILE=%t
+# RUN: llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines --check-prefix=JSON -DFILE=%t
# Yaml input is obtained by compiling the below source to object with:
# clang -fexperimental-call-graph-section test.c -o test.o
@@ -54,6 +56,131 @@
# CHECK-NEXT: DIRECT CALL SITES (CALLER_ADDR [(CALL_SITE_ADDR, TARGET_ADDR),])
# CHECK-NEXT: 17d0 1815 17a0 181a 17b0 1823 17c0
+# LLVM: callgraph_info {
+# LLVM-NEXT: indirect_target_types [
+# LLVM-NEXT: {
+# LLVM-NEXT: function_address: 0x17A0
+# LLVM-NEXT: type_id: 0xF85C699BB8EF20A2
+# LLVM-NEXT: }
+# LLVM-NEXT: {
+# LLVM-NEXT: function_address: 0x17B0
+# LLVM-NEXT: type_id: 0xF85C699BB8EF20A2
+# LLVM-NEXT: }
+# LLVM-NEXT: {
+# LLVM-NEXT: function_address: 0x17C0
+# LLVM-NEXT: type_id: 0x308E4B8159BC8654
+# LLVM-NEXT: }
+# LLVM-NEXT: {
+# LLVM-NEXT: function_address: 0x17D0
+# LLVM-NEXT: type_id: 0xA9494DEF81A01DC
+# LLVM-NEXT: }
+# LLVM-NEXT: ]
+# LLVM-NEXT: direct_call_sites [
+# LLVM-NEXT: {
+# LLVM-NEXT: caller: 0x17D0
+# LLVM-NEXT: call_sites [
+# LLVM-NEXT: {
+# LLVM-NEXT: call_site: 0x1815
+# LLVM-NEXT: callee: 0x17A0
+# LLVM-NEXT: }
+# LLVM-NEXT: {
+# LLVM-NEXT: call_site: 0x181A
+# LLVM-NEXT: callee: 0x17B0
+# LLVM-NEXT: }
+# LLVM-NEXT: {
+# LLVM-NEXT: call_site: 0x1823
+# LLVM-NEXT: callee: 0x17C0
+# LLVM-NEXT: }
+# LLVM-NEXT: ]
+# LLVM-NEXT: }
+# LLVM-NEXT: ]
+# LLVM-NEXT: indirect_call_sites [
+# LLVM-NEXT: {
+# LLVM-NEXT: caller: 0x17D0
+# LLVM-NEXT: call_sites: [0x17ED, 0x17FB, 0x1810]
+# LLVM-NEXT: }
+# LLVM-NEXT: ]
+# LLVM-NEXT: indirect_call_types [
+# LLVM-NEXT: {
+# LLVM-NEXT: call_site: 0x17ED
+# LLVM-NEXT: type_id: 0xF85C699BB8EF20A2
+# LLVM-NEXT: }
+# LLVM-NEXT: {
+# LLVM-NEXT: call_site: 0x17FB
+# LLVM-NEXT: type_id: 0xF85C699BB8EF20A2
+# LLVM-NEXT: }
+# LLVM-NEXT: {
+# LLVM-NEXT: call_site: 0x1810
+# LLVM-NEXT: type_id: 0x308E4B8159BC8654
+# LLVM-NEXT: }
+# LLVM-NEXT: ]
+# LLVM-NEXT: }
+
+#JSON: "callgraph_info": {
+#JSON-NEXT: "indirect_target_types": [
+#JSON-NEXT: {
+#JSON-NEXT: "function_address": 6048,
+#JSON-NEXT: "type_id": 17896295136807035042
+#JSON-NEXT: },
+#JSON-NEXT: {
+#JSON-NEXT: "function_address": 6064,
+#JSON-NEXT: "type_id": 17896295136807035042
+#JSON-NEXT: },
+#JSON-NEXT: {
+#JSON-NEXT: "function_address": 6080,
+#JSON-NEXT: "type_id": 3498816979441845844
+#JSON-NEXT: },
+#JSON-NEXT: {
+#JSON-NEXT: "function_address": 6096,
+#JSON-NEXT: "type_id": 762397922298560988
+#JSON-NEXT: }
+#JSON-NEXT: ],
+#JSON-NEXT: "direct_call_sites": [
+#JSON-NEXT: {
+#JSON-NEXT: "caller": 6096,
+#JSON-NEXT: "call_sites": [
+#JSON-NEXT: {
+#JSON-NEXT: "call_site": 6165,
+#JSON-NEXT: "callee": 6048
+#JSON-NEXT: },
+#JSON-NEXT: {
+#JSON-NEXT: "call_site": 6170,
+#JSON-NEXT: "callee": 6064
+#JSON-NEXT: },
+#JSON-NEXT: {
+#JSON-NEXT: "call_site": 6179,
+#JSON-NEXT: "callee": 6080
+#JSON-NEXT: }
+#JSON-NEXT: ]
+#JSON-NEXT: }
+#JSON-NEXT: ],
+#JSON-NEXT: "indirect_call_sites": [
+#JSON-NEXT: {
+#JSON-NEXT: "caller": 6096,
+#JSON-NEXT: "call_sites": [
+#JSON-NEXT: 6125,
+#JSON-NEXT: 6139,
+#JSON-NEXT: 6160
+#JSON-NEXT: ]
+#JSON-NEXT: }
+#JSON-NEXT: ],
+#JSON-NEXT: "indirect_call_types": [
+#JSON-NEXT: {
+#JSON-NEXT: "call_site": 6125,
+#JSON-NEXT: "type_id": 17896295136807035042
+#JSON-NEXT: },
+#JSON-NEXT: {
+#JSON-NEXT: "call_site": 6139,
+#JSON-NEXT: "type_id": 17896295136807035042
+#JSON-NEXT: },
+#JSON-NEXT: {
+#JSON-NEXT: "call_site": 6160,
+#JSON-NEXT: "type_id": 3498816979441845844
+#JSON-NEXT: }
+#JSON-NEXT: ]
+#JSON-NEXT: }
+#JSON-NEXT: }
+
--- !ELF
FileHeader:
Class: ELFCLASS64
>From 9aeaa7c5248d77d846a65742df738f96f989d949 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Tue, 9 Sep 2025 15:32:18 -0700
Subject: [PATCH 09/12] Improve tests. Match full lines.
---
.../ELF/call-graph-info-callgraph-section.test | 4 ++--
.../ELF/call-graph-info-err-invalid-func-kind.test | 8 ++++----
.../call-graph-info-err-malformed-callgraph-section.test | 8 ++++----
.../call-graph-info-err-malformed-callgraph-section2.test | 8 ++++----
.../call-graph-info-err-malformed-callgraph-section3.test | 8 ++++----
.../ELF/call-graph-info-invalid-format-version.test | 8 ++++----
.../ELF/call-graph-info-warn-no-callgraph-section.test | 8 ++++----
7 files changed, 26 insertions(+), 26 deletions(-)
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-callgraph-section.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-callgraph-section.test
index 85a8dce015a62..fd957e983760d 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-callgraph-section.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-callgraph-section.test
@@ -1,11 +1,11 @@
## Tests --call-graph-info prints information from call graph section.
# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
-# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=GNU
+# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --match-full-lines --check-prefix=GNU
# RUN: llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=LLVM
# RUN: llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=JSON
-# GNU: llvm-readelf: warning: '[[FILE]]': callgraph section has unknown type id for 1 indirect targets.
+# GNU: {{.*}}llvm-readelf: warning: '[[FILE]]': callgraph section has unknown type id for 1 indirect targets.
# GNU-EMPTY:
# GNU-NEXT: INDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])
# GNU-NEXT: UNKNOWN 6
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-func-kind.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-func-kind.test
index 3bea2d057b4d7..ea1152ddb45e1 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-func-kind.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-func-kind.test
@@ -2,11 +2,11 @@
## function kind value.
# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
-# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
-# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
-# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
-# ERR: llvm-readelf: warning: '[[FILE]]': Unknown function kind in .callgraph section.
+# ERR: {{.*}}llvm-readelf: warning: '[[FILE]]': Unknown function kind in .callgraph section.
.text
.globl _Z3foov
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section.test
index b9219cd64e019..c1f7585e2795c 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section.test
@@ -1,11 +1,11 @@
## Tests that --call-graph-info fails if .callgraph section has invalid size.
# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
-# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
-# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
-# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
-# ERR: llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section.
+# ERR: {{.*}}llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section. Unexpected size.
.text
.globl _Z3foov
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section2.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section2.test
index 83e7825de0abb..4a0a82b5ca99e 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section2.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section2.test
@@ -2,11 +2,11 @@
## an expected value, e.g., not as much call sites as the given count.
# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
-# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
-# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
-# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
-# ERR: llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section.
+# ERR: {{.*}}llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section. Parsing error.
.text
.globl _Z3foov
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section3.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section3.test
index 7488d15ca94a7..825bafb2e0e93 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section3.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section3.test
@@ -2,11 +2,11 @@
## an expected value, e.g., not as much call sites as the given count.
# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
-# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
-# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
-# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
-# ERR: llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section.
+# ERR: {{.*}}llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section. Parsing error.
.text
.globl _Z3foov
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test
index 3bac1f1850bef..f7edebc0931b5 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test
@@ -2,11 +2,11 @@
## version number.
# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
-# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
-# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
-# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
-# ERR: llvm-readelf: warning: '[[FILE]]': Unknown format version in .callgraph section.
+# ERR: {{.*}}llvm-readelf: warning: '[[FILE]]': Unknown format version in .callgraph section.
.text
.globl _Z3foov
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-no-callgraph-section.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-no-callgraph-section.test
index fe0428f6635f6..f9e82d0b62bc9 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-no-callgraph-section.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-no-callgraph-section.test
@@ -1,11 +1,11 @@
## Tests that --call-graph-info warns if there is no .callgraph section.
# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
-# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
-# RUN: llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
-# RUN: llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s -DFILE=%t
+# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
+# RUN: llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
+# RUN: llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
-# CHECK: llvm-readelf: warning: '[[FILE]]': No .callgraph section found.
+# CHECK: {{.*}}llvm-readelf: warning: '[[FILE]]': No .callgraph section found.
.text
.globl _Z3foov
>From f42a736c63725965a1c2b114986097b87a0f693e Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Wed, 17 Sep 2025 10:07:26 -0700
Subject: [PATCH 10/12] Make call graph processing agnostic of target
architecture pointer width size.
---
.../call-graph-info-callgraph-section.test | 6 +-
...raph-info-err-invalid-format-version.test} | 2 +-
...call-graph-info-err-invalid-func-kind.test | 4 +-
...-info-err-malformed-callgraph-section.test | 2 +-
...info-err-malformed-callgraph-section2.test | 2 +-
...info-err-malformed-callgraph-section3.test | 2 +-
...l-graph-info-err-no-callgraph-section.test | 14 +
...-graph-info-warn-no-callgraph-section.test | 14 -
.../llvm-readobj/ELF/call-graph-info.test | 2 +-
llvm/tools/llvm-readobj/ELFDumper.cpp | 272 ++++++++++++------
10 files changed, 204 insertions(+), 116 deletions(-)
rename llvm/test/tools/llvm-readobj/ELF/{call-graph-info-invalid-format-version.test => call-graph-info-err-invalid-format-version.test} (87%)
create mode 100644 llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-no-callgraph-section.test
delete mode 100644 llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-no-callgraph-section.test
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-callgraph-section.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-callgraph-section.test
index fd957e983760d..57410588a2a7e 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-callgraph-section.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-callgraph-section.test
@@ -5,7 +5,7 @@
# RUN: llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=LLVM
# RUN: llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=JSON
-# GNU: {{.*}}llvm-readelf: warning: '[[FILE]]': callgraph section has unknown type id for 1 indirect targets.
+# GNU: {{.*}}llvm-readelf: warning: '[[FILE]]': .callgraph section has unknown type id for 1 indirect targets.
# GNU-EMPTY:
# GNU-NEXT: INDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,])
# GNU-NEXT: UNKNOWN 6
@@ -20,7 +20,7 @@
# GNU-NEXT: DIRECT CALL SITES (CALLER_ADDR [(CALL_SITE_ADDR, TARGET_ADDR),])
# GNU-NEXT: 0 5 5
-# LLVM: {{.*}}llvm-readelf: warning: '[[FILE]]': callgraph section has unknown type id for 1 indirect targets.
+# LLVM: {{.*}}llvm-readelf: warning: '[[FILE]]': .callgraph section has unknown type id for 1 indirect targets.
# LLVM: callgraph_info {
# LLVM-NEXT: unknown_target_types: [0x6]
# LLVM-NEXT: indirect_target_types [
@@ -54,7 +54,7 @@
# LLVM-NEXT: ]
# LLVM-NEXT: }
-# JSON: {{.*}}llvm-readelf: warning: '[[FILE]]': callgraph section has unknown type id for 1 indirect targets.
+# JSON: {{.*}}llvm-readelf: warning: '[[FILE]]': .callgraph section has unknown type id for 1 indirect targets.
# JSON: "callgraph_info": {
# JSON-NEXT: "unknown_target_types": [
# JSON-NEXT: 6
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-format-version.test
similarity index 87%
rename from llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test
rename to llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-format-version.test
index f7edebc0931b5..233d4bb8f63d4 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-invalid-format-version.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-format-version.test
@@ -6,7 +6,7 @@
# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
-# ERR: {{.*}}llvm-readelf: warning: '[[FILE]]': Unknown format version in .callgraph section.
+# ERR: {{.*}}llvm-readelf: error: 'Unknown value': Unknown format version value [1] in .callgraph section.
.text
.globl _Z3foov
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-func-kind.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-func-kind.test
index ea1152ddb45e1..eef4eefc7da4c 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-func-kind.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-invalid-func-kind.test
@@ -6,7 +6,7 @@
# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
-# ERR: {{.*}}llvm-readelf: warning: '[[FILE]]': Unknown function kind in .callgraph section.
+# ERR: {{.*}}llvm-readelf: error: 'While reading call graph info's [FunctionKind] for function at [0x0]': Unknown value [4].
.text
.globl _Z3foov
@@ -17,5 +17,5 @@ _Z3foov:
.section .callgraph,"o", at progbits,.text
.quad 0 #< Format version number.
.quad 0 #< Function entry address.
-.quad 3 #< Invalid function kind: must be one of 0, 1, 2.
+.quad 4 #< Invalid function kind: must be one of 0, 1, 2, 3.
.text
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section.test
index c1f7585e2795c..7872b5d749ef9 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section.test
@@ -5,7 +5,7 @@
# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
-# ERR: {{.*}}llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section. Unexpected size.
+# ERR: {{.*}}llvm-readelf: error: 'While reading call graph info's [number of indirect callsites] for function at [0x0]': unexpected end of data at offset 0x19 while reading [0x18, 0x20)
.text
.globl _Z3foov
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section2.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section2.test
index 4a0a82b5ca99e..38dc0eca939a7 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section2.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section2.test
@@ -6,7 +6,7 @@
# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
-# ERR: {{.*}}llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section. Parsing error.
+# ERR: {{.*}}llvm-readelf: error: 'While reading call graph info's [indirect target type id] for function at [0x0]': unexpected end of data at offset 0x20 while reading [0x20, 0x28)
.text
.globl _Z3foov
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section3.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section3.test
index 825bafb2e0e93..88204bd28f494 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section3.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-malformed-callgraph-section3.test
@@ -6,7 +6,7 @@
# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t --check-prefix=ERR
-# ERR: {{.*}}llvm-readelf: warning: '[[FILE]]': Malformed .callgraph section. Parsing error.
+# ERR: {{.*}}llvm-readelf: error: 'While reading call graph info's [direct callsite PC] for function at [0x0]': unexpected end of data at offset 0x28 while reading [0x28, 0x30)
.text
.globl _Z3foov
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-no-callgraph-section.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-no-callgraph-section.test
new file mode 100644
index 0000000000000..cece28f776b5f
--- /dev/null
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-err-no-callgraph-section.test
@@ -0,0 +1,14 @@
+## Tests that --call-graph-info warns if there is no .callgraph section.
+
+# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
+# RUN: not llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
+# RUN: not llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
+# RUN: not llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
+
+# CHECK: {{.*}}llvm-readelf: error: 'Missing section': No .callgraph section found.
+
+.text
+.globl _Z3foov
+.type _Z3foov, at function
+_Z3foov:
+ callq _Z3foov at PLT
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-no-callgraph-section.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-no-callgraph-section.test
deleted file mode 100644
index f9e82d0b62bc9..0000000000000
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info-warn-no-callgraph-section.test
+++ /dev/null
@@ -1,14 +0,0 @@
-## Tests that --call-graph-info warns if there is no .callgraph section.
-
-# RUN: llvm-mc %s -filetype=obj -triple=x86_64-pc-linux -o %t
-# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
-# RUN: llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
-# RUN: llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
-
-# CHECK: {{.*}}llvm-readelf: warning: '[[FILE]]': No .callgraph section found.
-
-.text
-.globl _Z3foov
-.type _Z3foov, at function
-_Z3foov:
- callq _Z3foov at PLT
diff --git a/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test b/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
index 778813197f70b..26ae628135d22 100644
--- a/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
+++ b/llvm/test/tools/llvm-readobj/ELF/call-graph-info.test
@@ -1,6 +1,6 @@
## Tests how --call-graph-info prints the call graph information.
# RUN: yaml2obj --docnum=1 %s -o %t
-# RUN: llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
+# llvm-readelf --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines -DFILE=%t
# RUN: llvm-readelf --elf-output-style=LLVM --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines --check-prefix=LLVM -DFILE=%t
# RUN: llvm-readelf --elf-output-style=JSON --pretty-print --call-graph-info %t 2>&1 | FileCheck %s --match-full-lines --check-prefix=JSON -DFILE=%t
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index 8b9d84fdaba71..491c3f6f0851b 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -47,7 +47,10 @@
#include "llvm/Support/ARMBuildAttributes.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Compiler.h"
+#include "llvm/Support/DataExtractor.h"
+#include "llvm/Support/Debug.h"
#include "llvm/Support/Endian.h"
+#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/FormatVariadic.h"
@@ -74,6 +77,7 @@
#include <optional>
#include <string>
#include <system_error>
+#include <utility>
#include <vector>
using namespace llvm;
@@ -183,30 +187,31 @@ struct GroupSection {
std::vector<GroupMember> Members;
};
-// Per-function Callgraph information.
-struct FunctionCallgraphInfo {
- enum class FunctionKind : uint64_t {
- // Function cannot be target to indirect calls.
- NOT_INDIRECT_TARGET = 0,
- // Function may be target to indirect calls but its type id is unknown.
- INDIRECT_TARGET_UNKNOWN_TID = 1,
- // Function may be target to indirect calls and its type id is known.
- INDIRECT_TARGET_KNOWN_TID = 2,
-
- // Available in the binary but not listed in the call graph section.
- NOT_LISTED = 3,
- };
+// Call graph function kind.
+enum class FunctionKind : uint64_t {
+ // Function cannot be target to indirect calls.
+ NOT_INDIRECT_TARGET = 0,
+ // Function may be target to indirect calls but its type id is unknown.
+ INDIRECT_TARGET_UNKNOWN_TID = 1,
+ // Function may be target to indirect calls and its type id is known.
+ INDIRECT_TARGET_KNOWN_TID = 2,
+
+ // Available in the binary but not listed in the call graph section.
+ NOT_LISTED = 3,
+};
+
+// Per-function call graph information.
+template <typename AddrType> struct FunctionCallgraphInfoImpl {
FunctionKind Kind;
struct DirectCallSite {
- uint64_t CallSite;
- uint64_t Callee;
- DirectCallSite(uint64_t CallSite, uint64_t Callee)
+ AddrType CallSite;
+ AddrType Callee;
+ DirectCallSite(AddrType CallSite, AddrType Callee)
: CallSite(CallSite), Callee(Callee) {}
};
SmallVector<DirectCallSite> DirectCallSites;
- SmallVector<uint64_t> IndirectCallSites;
+ SmallVector<AddrType> IndirectCallSites;
};
-typedef FunctionCallgraphInfo::FunctionKind FunctionKind;
namespace {
@@ -458,15 +463,18 @@ template <typename ELFT> class ELFDumper : public ObjDumper {
const typename SFrameParser<ELFT::Endianness>::FDERange::iterator FDE,
ArrayRef<Relocation<ELFT>> Relocations, const Elf_Shdr *RelocSymTab);
+ using FunctionCallgraphInfo =
+ ::FunctionCallgraphInfoImpl<typename ELFT::uint>;
+
// Callgraph - Main data structure to maintain per function callgraph
// information.
- MapVector<uint64_t, FunctionCallgraphInfo> FuncCGInfo;
+ MapVector<typename ELFT::uint, FunctionCallgraphInfo> FuncCGInfo;
// Callgraph - 64 bit type id mapped to indirect callsites whose potential
// callee(s) should be of given type id.
- MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirCallSites;
+ MapVector<uint64_t, SmallVector<typename ELFT::uint>> TypeIdToIndirCallSites;
// Callgraph - 64 bit type id mapped to entry PC addresses of functions which
// are of the given type id.
- MapVector<uint64_t, SmallVector<uint64_t>> TypeIdToIndirTargets;
+ MapVector<uint64_t, SmallVector<typename ELFT::uint>> TypeIdToIndirTargets;
// Callgraph - Read callgraph section and process its contents to populate
// Callgraph related data structures which will be used to dump callgraph
// info. Returns false if there is no .callgraph section in the input file.
@@ -5278,93 +5286,170 @@ getCallGraphSection(const object::ELFObjectFile<ELFT> &ObjF) {
}
template <class ELFT> bool ELFDumper<ELFT>::processCallGraphSection() {
- std::optional<object::SectionRef> CallGraphSection =
- getCallGraphSection(ObjF);
- if (!CallGraphSection.has_value()) {
- reportUniqueWarning("No .callgraph section found.");
+ const Elf_Shdr *CGSection = findSectionByName(".callgraph");
+ if (!CGSection) {
+ Error NoSectionErr = createError("No .callgraph section found.");
+ reportError(std::move(NoSectionErr), "Missing section");
return false;
}
- StringRef CGSecContents = cantFail(CallGraphSection.value().getContents());
- // TODO: some entries are written in pointer size. are they always 64-bit?
- if (CGSecContents.size() % sizeof(uint64_t)) {
- reportUniqueWarning("Malformed .callgraph section. Unexpected size.");
- exit(1);
+ Expected<ArrayRef<uint8_t>> SectionBytesOrErr =
+ Obj.getSectionContents(*CGSection);
+ if (!SectionBytesOrErr) {
+ Error SectionReadErr = SectionBytesOrErr.takeError();
+ reportError(std::move(SectionReadErr), "Unable to read the .callgraph section");
+ return false;
}
- size_t Size = CGSecContents.size() / sizeof(uint64_t);
- auto *It = reinterpret_cast<const uint64_t *>(CGSecContents.data());
- const auto *const End = It + Size;
-
- auto CGHasNext = [&]() { return It < End; };
- auto CGNext = [&]() -> uint64_t {
- if (!CGHasNext()) {
- reportUniqueWarning("Malformed .callgraph section. Parsing error.");
- exit(1);
- }
- return *It++;
+ auto PrintMalformedError = [&](Error &E, Twine FuncPC, StringRef Component) {
+ // auto Msg = llvm::Twine("Malformed callgraph section while reading [") +
+ // Component + llvm::Twine("] .\n");
+ std::string Msg =
+ (StringRef("While reading call graph info's [") + Component +
+ StringRef("] for function at [0x") + StringRef(FuncPC.str()) + "]")
+ .str();
+ reportError(std::move(E), StringRef(Msg));
};
- while (CGHasNext()) {
+ DataExtractor Data(SectionBytesOrErr.get(), Obj.isLE(),
+ ObjF.getBytesInAddress());
+
+ uint64_t NotListedCount = 0;
+ uint64_t UnknownCount = 0;
+
+ uint64_t Offset = 0;
+ while (Offset < CGSection->sh_size) {
+ Error CGSectionErr = Error::success();
// Format version number.
- uint64_t FormatVersionNumber = CGNext();
- if (FormatVersionNumber != 0) {
- reportUniqueWarning("Unknown format version in .callgraph section.");
- exit(1);
- }
- // Function entry pc.
- uint64_t FuncEntryPc = CGNext();
- // Function kind.
- uint64_t Kind = CGNext();
- switch (Kind) {
- case 0: // not an indirect target
- FuncCGInfo[FuncEntryPc].Kind = FunctionKind::NOT_INDIRECT_TARGET;
- break;
- case 1: // indirect target with unknown type id
- FuncCGInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
- break;
- case 2: // indirect target with known type id
- FuncCGInfo[FuncEntryPc].Kind = FunctionKind::INDIRECT_TARGET_KNOWN_TID;
- TypeIdToIndirTargets[CGNext()].push_back(FuncEntryPc);
- break;
- default:
- this->reportUniqueWarning("Unknown function kind in .callgraph section.");
- exit(1);
- }
- // Read indirect call sites info.
- uint64_t IndirectCallSiteCount = CGNext();
- for (unsigned long I = 0; I < IndirectCallSiteCount; I++) {
- uint64_t TypeId = CGNext();
- uint64_t CallSitePc = CGNext();
- TypeIdToIndirCallSites[TypeId].push_back(CallSitePc);
- FuncCGInfo[FuncEntryPc].IndirectCallSites.push_back(CallSitePc);
+ uint64_t FormatVersionNumber = Data.getU64(&Offset, &CGSectionErr);
+
+ if (CGSectionErr) {
+ reportError(std::move(CGSectionErr),
+ "While reading call graph info FormatVersionNumber");
+ return false;
}
- // Read direct call sites info.
- uint64_t DirectCallSiteCount = CGNext();
- for (unsigned long I = 0; I < DirectCallSiteCount; I++) {
- uint64_t CallSitePc = CGNext();
- uint64_t CalleePc = CGNext();
- FuncCGInfo[FuncEntryPc].DirectCallSites.emplace_back(CallSitePc,
- CalleePc);
+
+ if (FormatVersionNumber != 0) {
+ Error FormatErr = createError("Unknown format version value [" + std::to_string(FormatVersionNumber) + "] in .callgraph section.");
+ reportError(std::move(FormatErr), "Unknown value");
+ return false;
}
- }
- // Sort function info by function PC.
- llvm::sort(FuncCGInfo,
- [](const auto &A, const auto &B) { return A.first < B.first; });
+ // Read function address.
+ typename ELFT::uint FuncAddr =
+ Data.getUnsigned(&Offset, sizeof(FuncAddr), &CGSectionErr);
+ if (CGSectionErr) {
+ reportError(std::move(CGSectionErr),
+ "While reading call graph info function entry PC");
+ return false;
+ }
- uint64_t NotListedCount = 0;
- uint64_t UnknownCount = 0;
- for (const auto &El : FuncCGInfo) {
- NotListedCount += El.second.Kind == FunctionKind::NOT_LISTED;
- UnknownCount += El.second.Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID;
+ if (FuncCGInfo.find(FuncAddr) != FuncCGInfo.end()) {
+ Error DuplicatePcErr = createError("for function PC: 0x" + Twine::utohexstr(FuncAddr));
+ reportError(std::move(DuplicatePcErr), "Duplicate call graph entry");
+ return false;
+ }
+
+ // Read function kind.
+ uint64_t KindVal = Data.getU64(&Offset, &CGSectionErr);
+ if (CGSectionErr) {
+ PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr), "Kind");
+ return false;
+ }
+
+ if (KindVal > 3) {
+ Error KindErr = createError("Unknown value [" + std::to_string(KindVal) + "].");
+ PrintMalformedError(KindErr, Twine::utohexstr(FuncAddr), "FunctionKind");
+ return false;
+ }
+
+ FunctionKind Kind = static_cast<FunctionKind>(KindVal);
+ if (Kind == FunctionKind::INDIRECT_TARGET_KNOWN_TID) {
+ // Read type id if this function is an indirect call target.
+ uint64_t TypeId = Data.getU64(&Offset, &CGSectionErr);
+ if (CGSectionErr) {
+ PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+ "indirect type id");
+ return false;
+ }
+ TypeIdToIndirTargets[TypeId].push_back(FuncAddr);
+ }
+ if (Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID)
+ UnknownCount++;
+ if (Kind == FunctionKind::NOT_LISTED)
+ NotListedCount++;
+
+ using FunctionCallgraphInfo =
+ ::FunctionCallgraphInfoImpl<typename ELFT::uint>;
+
+ // Create a new entry for this function.
+ FunctionCallgraphInfo CGInfo;
+ CGInfo.Kind = Kind;
+
+ // Read number of indirect call sites for this function.
+ uint64_t NumIndirectCallSites = Data.getU64(&Offset, &CGSectionErr);
+ if (CGSectionErr) {
+ PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+ "number of indirect callsites");
+ return false;
+ }
+
+ for (unsigned long I = 0; I < NumIndirectCallSites; I++) {
+ uint64_t TypeId = Data.getU64(&Offset, &CGSectionErr);
+ if (CGSectionErr) {
+ PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+ "indirect target type id");
+ return false;
+ }
+ typename ELFT::uint CallSitePc =
+ Data.getUnsigned(&Offset, sizeof(CallSitePc), &CGSectionErr);
+ if (CGSectionErr) {
+ PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+ "indirect callsite PC");
+ return false;
+ }
+ TypeIdToIndirCallSites[TypeId].push_back(CallSitePc);
+ CGInfo.IndirectCallSites.push_back(CallSitePc);
+ }
+
+ // Read number of direct call sites for this function.
+ uint64_t NumDirectCallSites = Data.getU64(&Offset, &CGSectionErr);
+ if (CGSectionErr) {
+ PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+ "number of direct callsites");
+ return false;
+ }
+ // Read direct call sites and populate FuncCGInfo.
+ for (uint64_t I = 0; I < NumDirectCallSites; ++I) {
+ typename ELFT::uint CallSite =
+ Data.getUnsigned(&Offset, sizeof(CallSite), &CGSectionErr);
+ if (CGSectionErr) {
+ PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+ "direct callsite PC");
+ return false;
+ }
+ typename ELFT::uint Callee =
+ Data.getUnsigned(&Offset, sizeof(Callee), &CGSectionErr);
+ if (CGSectionErr) {
+ PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+ "indirect callee PC");
+ return false;
+ }
+ CGInfo.DirectCallSites.emplace_back(CallSite, Callee);
+ }
+ FuncCGInfo[FuncAddr] = CGInfo;
}
+
if (NotListedCount)
- reportUniqueWarning("callgraph section does not have information for " +
+ reportUniqueWarning(".callgraph section does not have information for " +
std::to_string(NotListedCount) + " functions.");
if (UnknownCount)
- reportUniqueWarning("callgraph section has unknown type id for " +
+ reportUniqueWarning(".callgraph section has unknown type id for " +
std::to_string(UnknownCount) + " indirect targets.");
+
+ // Sort function info by function PC.
+ llvm::sort(FuncCGInfo,
+ [](const auto &A, const auto &B) { return A.first < B.first; });
return true;
}
@@ -5422,6 +5507,9 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
}
}
+ using FunctionCallgraphInfo =
+ ::FunctionCallgraphInfoImpl<typename ELFT::uint>;
+
// Print function entry to direct call site and target function entry
// addresses mapping from disasm.
OS << "\n\nDIRECT CALL SITES (CALLER_ADDR [(CALL_SITE_ADDR, TARGET_ADDR),])";
@@ -5430,7 +5518,7 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
auto FuncDirCallSites = El.second.DirectCallSites;
if (!FuncDirCallSites.empty()) {
OS << "\n" << format("%lx", CallerPc);
- for (const FunctionCallgraphInfo::DirectCallSite &DCS :
+ for (typename FunctionCallgraphInfo::DirectCallSite &DCS :
FuncDirCallSites) {
OS << " " << format("%lx", DCS.CallSite) << " "
<< format("%lx", DCS.Callee);
>From 9f0a1398a445d3ca894212840939d5c7ad8ffde1 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Wed, 17 Sep 2025 10:46:54 -0700
Subject: [PATCH 11/12] Remove unnecessary function returns after reportError
calls which bottoms out to llvm_unreachable.
---
llvm/tools/llvm-readobj/ELFDumper.cpp | 52 ++++++++-------------------
1 file changed, 14 insertions(+), 38 deletions(-)
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index 491c3f6f0851b..4ffa1905b28d9 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -5288,17 +5288,16 @@ getCallGraphSection(const object::ELFObjectFile<ELFT> &ObjF) {
template <class ELFT> bool ELFDumper<ELFT>::processCallGraphSection() {
const Elf_Shdr *CGSection = findSectionByName(".callgraph");
if (!CGSection) {
- Error NoSectionErr = createError("No .callgraph section found.");
+ Error NoSectionErr = createError("No .callgraph section found.");
reportError(std::move(NoSectionErr), "Missing section");
- return false;
}
Expected<ArrayRef<uint8_t>> SectionBytesOrErr =
Obj.getSectionContents(*CGSection);
if (!SectionBytesOrErr) {
Error SectionReadErr = SectionBytesOrErr.takeError();
- reportError(std::move(SectionReadErr), "Unable to read the .callgraph section");
- return false;
+ reportError(std::move(SectionReadErr),
+ "Unable to read the .callgraph section");
}
auto PrintMalformedError = [&](Error &E, Twine FuncPC, StringRef Component) {
@@ -5323,55 +5322,44 @@ template <class ELFT> bool ELFDumper<ELFT>::processCallGraphSection() {
// Format version number.
uint64_t FormatVersionNumber = Data.getU64(&Offset, &CGSectionErr);
- if (CGSectionErr) {
+ if (CGSectionErr)
reportError(std::move(CGSectionErr),
"While reading call graph info FormatVersionNumber");
- return false;
- }
if (FormatVersionNumber != 0) {
Error FormatErr = createError("Unknown format version value [" + std::to_string(FormatVersionNumber) + "] in .callgraph section.");
reportError(std::move(FormatErr), "Unknown value");
- return false;
}
// Read function address.
typename ELFT::uint FuncAddr =
Data.getUnsigned(&Offset, sizeof(FuncAddr), &CGSectionErr);
- if (CGSectionErr) {
+ if (CGSectionErr)
reportError(std::move(CGSectionErr),
"While reading call graph info function entry PC");
- return false;
- }
if (FuncCGInfo.find(FuncAddr) != FuncCGInfo.end()) {
Error DuplicatePcErr = createError("for function PC: 0x" + Twine::utohexstr(FuncAddr));
- reportError(std::move(DuplicatePcErr), "Duplicate call graph entry");
- return false;
+ reportError(std::move(DuplicatePcErr), "Duplicate call graph entry");
}
// Read function kind.
uint64_t KindVal = Data.getU64(&Offset, &CGSectionErr);
- if (CGSectionErr) {
+ if (CGSectionErr)
PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr), "Kind");
- return false;
- }
if (KindVal > 3) {
Error KindErr = createError("Unknown value [" + std::to_string(KindVal) + "].");
PrintMalformedError(KindErr, Twine::utohexstr(FuncAddr), "FunctionKind");
- return false;
}
FunctionKind Kind = static_cast<FunctionKind>(KindVal);
if (Kind == FunctionKind::INDIRECT_TARGET_KNOWN_TID) {
// Read type id if this function is an indirect call target.
uint64_t TypeId = Data.getU64(&Offset, &CGSectionErr);
- if (CGSectionErr) {
+ if (CGSectionErr)
PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
"indirect type id");
- return false;
- }
TypeIdToIndirTargets[TypeId].push_back(FuncAddr);
}
if (Kind == FunctionKind::INDIRECT_TARGET_UNKNOWN_TID)
@@ -5388,53 +5376,41 @@ template <class ELFT> bool ELFDumper<ELFT>::processCallGraphSection() {
// Read number of indirect call sites for this function.
uint64_t NumIndirectCallSites = Data.getU64(&Offset, &CGSectionErr);
- if (CGSectionErr) {
+ if (CGSectionErr)
PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
"number of indirect callsites");
- return false;
- }
for (unsigned long I = 0; I < NumIndirectCallSites; I++) {
uint64_t TypeId = Data.getU64(&Offset, &CGSectionErr);
- if (CGSectionErr) {
+ if (CGSectionErr)
PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
"indirect target type id");
- return false;
- }
typename ELFT::uint CallSitePc =
Data.getUnsigned(&Offset, sizeof(CallSitePc), &CGSectionErr);
- if (CGSectionErr) {
+ if (CGSectionErr)
PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
"indirect callsite PC");
- return false;
- }
TypeIdToIndirCallSites[TypeId].push_back(CallSitePc);
CGInfo.IndirectCallSites.push_back(CallSitePc);
}
// Read number of direct call sites for this function.
uint64_t NumDirectCallSites = Data.getU64(&Offset, &CGSectionErr);
- if (CGSectionErr) {
+ if (CGSectionErr)
PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
"number of direct callsites");
- return false;
- }
// Read direct call sites and populate FuncCGInfo.
for (uint64_t I = 0; I < NumDirectCallSites; ++I) {
typename ELFT::uint CallSite =
Data.getUnsigned(&Offset, sizeof(CallSite), &CGSectionErr);
- if (CGSectionErr) {
+ if (CGSectionErr)
PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
"direct callsite PC");
- return false;
- }
typename ELFT::uint Callee =
Data.getUnsigned(&Offset, sizeof(Callee), &CGSectionErr);
- if (CGSectionErr) {
+ if (CGSectionErr)
PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
"indirect callee PC");
- return false;
- }
CGInfo.DirectCallSites.emplace_back(CallSite, Callee);
}
FuncCGInfo[FuncAddr] = CGInfo;
>From 3499c2d2d5c075f64992d266ba61b03d2acbf8e1 Mon Sep 17 00:00:00 2001
From: prabhukr <prabhukr at google.com>
Date: Wed, 17 Sep 2025 12:06:01 -0700
Subject: [PATCH 12/12] Formatting fixes.
---
llvm/tools/llvm-readobj/ELFDumper.cpp | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp
index 4ffa1905b28d9..95db1518cc0aa 100644
--- a/llvm/tools/llvm-readobj/ELFDumper.cpp
+++ b/llvm/tools/llvm-readobj/ELFDumper.cpp
@@ -5326,8 +5326,10 @@ template <class ELFT> bool ELFDumper<ELFT>::processCallGraphSection() {
reportError(std::move(CGSectionErr),
"While reading call graph info FormatVersionNumber");
- if (FormatVersionNumber != 0) {
- Error FormatErr = createError("Unknown format version value [" + std::to_string(FormatVersionNumber) + "] in .callgraph section.");
+ if (FormatVersionNumber != 0) {
+ Error FormatErr = createError("Unknown format version value [" +
+ std::to_string(FormatVersionNumber) +
+ "] in .callgraph section.");
reportError(std::move(FormatErr), "Unknown value");
}
@@ -5339,7 +5341,8 @@ template <class ELFT> bool ELFDumper<ELFT>::processCallGraphSection() {
"While reading call graph info function entry PC");
if (FuncCGInfo.find(FuncAddr) != FuncCGInfo.end()) {
- Error DuplicatePcErr = createError("for function PC: 0x" + Twine::utohexstr(FuncAddr));
+ Error DuplicatePcErr =
+ createError("for function PC: 0x" + Twine::utohexstr(FuncAddr));
reportError(std::move(DuplicatePcErr), "Duplicate call graph entry");
}
@@ -5349,7 +5352,8 @@ template <class ELFT> bool ELFDumper<ELFT>::processCallGraphSection() {
PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr), "Kind");
if (KindVal > 3) {
- Error KindErr = createError("Unknown value [" + std::to_string(KindVal) + "].");
+ Error KindErr =
+ createError("Unknown value [" + std::to_string(KindVal) + "].");
PrintMalformedError(KindErr, Twine::utohexstr(FuncAddr), "FunctionKind");
}
More information about the llvm-commits
mailing list