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