[llvm] [llvm-readobj] Dump callgraph section info for ELF (PR #157499)
James Henderson via llvm-commits
llvm-commits at lists.llvm.org
Mon Nov 3 01:44:12 PST 2025
================
@@ -5263,6 +5302,274 @@ template <class ELFT> void GNUELFDumper<ELFT>::printCGProfile() {
OS << "GNUStyle::printCGProfile not implemented\n";
}
+template <class ELFT>
+static std::optional<object::SectionRef>
+getCallGraphSection(const object::ELFObjectFile<ELFT> &ObjF) {
+ // Get the .llvm.callgraph section.
+ StringRef CallGraphSectionName(".llvm.callgraph");
+ for (auto Sec : ObjF.sections()) {
+ if (Expected<StringRef> NameOrErr = Sec.getName()) {
+ StringRef Name = *NameOrErr;
+ if (Name == CallGraphSectionName)
+ return Sec;
+ } else
+ consumeError(NameOrErr.takeError());
+ }
+ return std::nullopt;
+}
+
+namespace callgraph {
+LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
+enum Flags : uint8_t {
+ None = 0,
+ IsIndirectTarget = 1u << 0,
+ HasDirectCallees = 1u << 1,
+ HasIndirectCallees = 1u << 2,
+ LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue*/ HasIndirectCallees)
+};
+} // namespace callgraph
+
+template <class ELFT> bool ELFDumper<ELFT>::processCallGraphSection() {
+ const Elf_Shdr *CGSection = findSectionByName(".llvm.callgraph");
+ if (!CGSection)
+ reportError(createError("No .llvm.callgraph section found."),
+ "Missing section");
+
+ Expected<ArrayRef<uint8_t>> SectionBytesOrErr =
+ Obj.getSectionContents(*CGSection);
+ if (!SectionBytesOrErr) {
+ reportError(SectionBytesOrErr.takeError(),
+ "Unable to read the .llvm.callgraph section");
+ }
+
+ auto PrintMalformedError = [&](Error &E, Twine FuncPC, StringRef Component) {
+ reportError(std::move(E),
+ Twine("While reading call graph info's [" + Component +
+ "] for function at [0x" + FuncPC + "]")
+ .str());
+ };
+
+ DataExtractor Data(SectionBytesOrErr.get(), Obj.isLE(),
+ ObjF.getBytesInAddress());
+
+ uint64_t UnknownCount = 0;
+ uint64_t Offset = 0;
+ while (Offset < CGSection->sh_size) {
+ Error CGSectionErr = Error::success();
+ uint8_t FormatVersionNumber = Data.getU8(&Offset, &CGSectionErr);
+ if (CGSectionErr) {
+ reportError(std::move(CGSectionErr),
+ "While reading call graph info FormatVersionNumber");
+ }
+ if (FormatVersionNumber != 0) {
+ reportError(createError("Unknown format version value [" +
+ std::to_string(FormatVersionNumber) +
+ "] in .llvm.callgraph section."),
+ "Unknown value");
+ }
+
+ uint8_t FlagsVal = Data.getU8(&Offset, &CGSectionErr);
+ if (CGSectionErr)
+ reportError(std::move(CGSectionErr),
+ "While reading call graph info's Flags");
+ callgraph::Flags CGFlags = static_cast<callgraph::Flags>(FlagsVal);
+ if (FlagsVal > 7) {
+ reportError(createError("Unexpected value. Expected [0-7] but found [" +
+ std::to_string(FlagsVal) + "]"),
+ "While reading call graph info's Flags");
+ }
+ uint64_t FuncAddrOffset = Offset;
+ typename ELFT::uint FuncAddr =
+ Data.getUnsigned(&Offset, sizeof(FuncAddr), &CGSectionErr);
+ if (CGSectionErr)
+ reportError(std::move(CGSectionErr),
+ "While reading call graph info function entry PC");
+
+ bool IsETREL = this->Obj.getHeader().e_type == ELF::ET_REL;
+ // Create a new entry for this function.
+ FunctionCallgraphInfo CGInfo;
+ CGInfo.FunctionAddress = IsETREL ? FuncAddrOffset : FuncAddr;
+ CGInfo.FormatVersionNumber = FormatVersionNumber;
+ bool IsIndirectTarget =
+ (CGFlags & callgraph::IsIndirectTarget) != callgraph::None;
+ CGInfo.IsIndirectTarget = IsIndirectTarget;
+ uint64_t TypeId = Data.getU64(&Offset, &CGSectionErr);
+ if (CGSectionErr)
+ PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+ "indirect type id");
+ CGInfo.FunctionTypeId = TypeId;
+ if (IsIndirectTarget && TypeId == 0)
+ UnknownCount++;
+
+ bool HasDirectCallees =
+ (CGFlags & callgraph::HasDirectCallees) != callgraph::None;
+ if (HasDirectCallees) {
+ // Read number of direct call sites for this function.
+ uint64_t NumDirectCallees = Data.getULEB128(&Offset, &CGSectionErr);
+ if (CGSectionErr)
+ PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+ "number of direct callsites");
+ // Read uniqeu direct callees and populate FuncCGInfos.
+ for (uint64_t I = 0; I < NumDirectCallees; ++I) {
+ uint64_t CalleeOffset = Offset;
+ typename ELFT::uint Callee =
+ Data.getUnsigned(&Offset, sizeof(Callee), &CGSectionErr);
+ if (CGSectionErr)
+ PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+ "direct callee PC");
+ CGInfo.DirectCallees.insert((IsETREL ? CalleeOffset : Callee));
+ }
+ }
+
+ bool HasIndirectTypeIds =
+ (CGFlags & callgraph::HasIndirectCallees) != callgraph::None;
+ if (HasIndirectTypeIds) {
+ uint64_t NumIndirectTargetTypeIDs =
+ Data.getULEB128(&Offset, &CGSectionErr);
+ if (CGSectionErr)
+ PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+ "number of indirect target type IDs");
+
+ // Read unique indirect target type IDs and populate FuncCGInfos.
+ for (uint64_t I = 0; I < NumIndirectTargetTypeIDs; ++I) {
+ uint64_t TargetType = Data.getU64(&Offset, &CGSectionErr);
+ if (CGSectionErr)
+ PrintMalformedError(CGSectionErr, Twine::utohexstr(FuncAddr),
+ "indirect type ID");
+ CGInfo.IndirectTypeIDs.insert(TargetType);
+ }
+ }
+ FuncCGInfos.push_back(CGInfo);
+ }
+
+ if (UnknownCount)
+ reportUniqueWarning(".llvm.callgraph section has unknown type id for " +
+ std::to_string(UnknownCount) + " indirect targets.");
+ return true;
+}
+
+template <class ELFT>
+void ELFDumper<ELFT>::getCallGraphRelocations(
+ std::vector<Relocation<ELFT>> &Relocations, const Elf_Shdr *&RelocSymTab) {
+ const Elf_Shdr *CGSection = findSectionByName(".llvm.callgraph");
+ if (!CGSection)
+ return;
+
+ const Elf_Shdr *CGRelSection = nullptr;
+ auto IsMatch = [&](const Elf_Shdr &Sec) { return &Sec == CGSection; };
+ Expected<MapVector<const Elf_Shdr *, const Elf_Shdr *>> MapOrErr =
+ Obj.getSectionAndRelocations(IsMatch);
+ if (MapOrErr && !MapOrErr->empty()) {
+ CGRelSection = MapOrErr->front().second;
+ }
+
+ if (CGRelSection) {
+ forEachRelocationDo(*CGRelSection,
+ [&](const Relocation<ELFT> &R, unsigned Ndx,
+ const Elf_Shdr &Sec, const Elf_Shdr *SymTab) {
+ RelocSymTab = SymTab;
+ Relocations.push_back(R);
+ });
+ llvm::stable_sort(Relocations, [](const auto &LHS, const auto &RHS) {
+ return LHS.Offset < RHS.Offset;
+ });
+ }
+}
+
+template <class ELFT> void GNUELFDumper<ELFT>::printCallGraphInfo() {
----------------
jh7370 wrote:
Do we need a GNU-style dumper? In other words, does GNU readelf support the section? If it doesn't, this function shouldn't be implemented, or at the very most, should do the same as the LLVM dumper. See various other modes for examples.
For context: llvm-readobj has three ELF output modes: JSON (which produces legal JSON output), LLVM (which is a human-readable dictionary-like structure), and GNU. GNU output is supposed to mirror GNU's readelf output, as close as possible. This is because people expect it to be a drop-in replacement in this mode. If there isn't a GNU readelf option to dump it, we shouldn't try to make up one.
https://github.com/llvm/llvm-project/pull/157499
More information about the llvm-commits
mailing list