diff options
26 files changed, 870 insertions, 2 deletions
diff --git a/llvm/docs/Extensions.rst b/llvm/docs/Extensions.rst index ea26784..1ebea4a 100644 --- a/llvm/docs/Extensions.rst +++ b/llvm/docs/Extensions.rst @@ -535,6 +535,27 @@ Example of BBAddrMap with PGO data: .uleb128 1000 # BB_3 basic block frequency (only when enabled) .uleb128 0 # BB_3 successors count (only enabled with branch probabilities) +``SHT_LLVM_FUNC_MAP`` Section (function address map) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +This section stores the mapping from the binary address of function to its +related metadata features. It is used to emit function-level analysis data and +can be enabled through ``--func-map`` option. The fields are encoded in the +following format: + +#. A version number byte used for backward compatibility. +#. The function's entry address. +#. Dynamic Instruction Count, which is calculated as the total PGO counts for all + instructions within the function. + +Example: + +.. code-block:: gas + + .section ".llvm_func_map","",@llvm_func_map + .byte 1 # version number + .quad .Lfunc_begin1 # function address + .uleb128 1000 # dynamic instruction count + ``SHT_LLVM_OFFLOADING`` Section (offloading data) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This section stores the binary data used to perform offloading device linking @@ -725,4 +746,3 @@ follows: add x16, x16, :lo12:__chkstk blr x16 sub sp, sp, x15, lsl #4 - diff --git a/llvm/include/llvm/BinaryFormat/ELF.h b/llvm/include/llvm/BinaryFormat/ELF.h index 48ae0db..46837c4 100644 --- a/llvm/include/llvm/BinaryFormat/ELF.h +++ b/llvm/include/llvm/BinaryFormat/ELF.h @@ -1139,6 +1139,7 @@ enum : unsigned { SHT_LLVM_OFFLOADING = 0x6fff4c0b, // LLVM device offloading data. SHT_LLVM_LTO = 0x6fff4c0c, // .llvm.lto for fat LTO. SHT_LLVM_JT_SIZES = 0x6fff4c0d, // LLVM jump tables sizes. + SHT_LLVM_FUNC_MAP = 0x6fff4c0e, // LLVM function address map. // Android's experimental support for SHT_RELR sections. // https://android.googlesource.com/platform/bionic/+/b7feec74547f84559a1467aca02708ff61346d2a/libc/include/elf.h#512 SHT_ANDROID_RELR = 0x6fffff00, // Relocation entries; only offsets. diff --git a/llvm/include/llvm/CodeGen/AsmPrinter.h b/llvm/include/llvm/CodeGen/AsmPrinter.h index 5291369..5fe35c2 100644 --- a/llvm/include/llvm/CodeGen/AsmPrinter.h +++ b/llvm/include/llvm/CodeGen/AsmPrinter.h @@ -414,6 +414,8 @@ public: void emitBBAddrMapSection(const MachineFunction &MF); + void emitFuncMapSection(const MachineFunction &MF); + void emitKCFITrapEntry(const MachineFunction &MF, const MCSymbol *Symbol); virtual void emitKCFITypeId(const MachineFunction &MF); diff --git a/llvm/include/llvm/MC/MCContext.h b/llvm/include/llvm/MC/MCContext.h index 57ba40f..6fc9eaa 100644 --- a/llvm/include/llvm/MC/MCContext.h +++ b/llvm/include/llvm/MC/MCContext.h @@ -177,6 +177,9 @@ private: /// LLVM_BB_ADDR_MAP version to emit. uint8_t BBAddrMapVersion = 2; + /// LLVM_FUNC_MAP version to emit. + uint8_t FuncMapVersion = 1; + /// The file name of the log file from the environment variable /// AS_SECURE_LOG_FILE. Which must be set before the .secure_log_unique /// directive is used or it is an error. @@ -656,6 +659,8 @@ public: uint8_t getBBAddrMapVersion() const { return BBAddrMapVersion; } + uint8_t getFuncMapVersion() const { return FuncMapVersion; } + /// @} /// \name Dwarf Management diff --git a/llvm/include/llvm/MC/MCObjectFileInfo.h b/llvm/include/llvm/MC/MCObjectFileInfo.h index fb575fe..e344d47 100644 --- a/llvm/include/llvm/MC/MCObjectFileInfo.h +++ b/llvm/include/llvm/MC/MCObjectFileInfo.h @@ -364,6 +364,8 @@ public: MCSection *getBBAddrMapSection(const MCSection &TextSec) const; + MCSection *getFuncMapSection(const MCSection &TextSec) const; + MCSection *getKCFITrapSection(const MCSection &TextSec) const; MCSection *getPseudoProbeSection(const MCSection &TextSec) const; diff --git a/llvm/include/llvm/Object/ELF.h b/llvm/include/llvm/Object/ELF.h index 3aa1d78..a688672 100644 --- a/llvm/include/llvm/Object/ELF.h +++ b/llvm/include/llvm/Object/ELF.h @@ -513,6 +513,13 @@ public: decodeBBAddrMap(const Elf_Shdr &Sec, const Elf_Shdr *RelaSec = nullptr, std::vector<PGOAnalysisMap> *PGOAnalyses = nullptr) const; + /// Returns a vector of FuncMap structs corresponding to each function + /// within the text section that the SHT_LLVM_FUNC_MAP section \p Sec + /// is associated with. If the current ELFFile is relocatable, a corresponding + /// \p RelaSec must be passed in as an argument. + Expected<std::vector<FuncMap>> + decodeFuncMap(const Elf_Shdr &Sec, const Elf_Shdr *RelaSec = nullptr) const; + /// Returns a map from every section matching \p IsMatch to its relocation /// section, or \p nullptr if it has no relocation section. This function /// returns an error if any of the \p IsMatch calls fail or if it fails to diff --git a/llvm/include/llvm/Object/ELFTypes.h b/llvm/include/llvm/Object/ELFTypes.h index 87e4dbe..4ff4ec5 100644 --- a/llvm/include/llvm/Object/ELFTypes.h +++ b/llvm/include/llvm/Object/ELFTypes.h @@ -1027,6 +1027,14 @@ struct PGOAnalysisMap { } }; +// Struct representing the FuncMap for one function. +struct FuncMap { + uint64_t FunctionAddress = 0; // Function entry address. + uint64_t DynamicInstCount = 0; // Dynamic instruction count for this function. + + uint64_t getFunctionAddress() const { return FunctionAddress; } +}; + } // end namespace object. } // end namespace llvm. diff --git a/llvm/include/llvm/ObjectYAML/ELFYAML.h b/llvm/include/llvm/ObjectYAML/ELFYAML.h index dfdfa05..f302bb0 100644 --- a/llvm/include/llvm/ObjectYAML/ELFYAML.h +++ b/llvm/include/llvm/ObjectYAML/ELFYAML.h @@ -195,6 +195,12 @@ struct PGOAnalysisMapEntry { std::optional<std::vector<PGOBBEntry>> PGOBBEntries; }; +struct FuncMapEntry { + uint8_t Version; + llvm::yaml::Hex64 Address; + uint64_t DynamicInstCount; +}; + struct StackSizeEntry { llvm::yaml::Hex64 Address; llvm::yaml::Hex64 Size; @@ -229,6 +235,7 @@ struct Chunk { DependentLibraries, CallGraphProfile, BBAddrMap, + FuncMap, // Special chunks. SpecialChunksStart, @@ -355,6 +362,18 @@ struct BBAddrMapSection : Section { } }; +struct FuncMapSection : Section { + std::optional<std::vector<FuncMapEntry>> Entries; + + FuncMapSection() : Section(ChunkKind::FuncMap) {} + + std::vector<std::pair<StringRef, bool>> getEntries() const override { + return {{"Entries", Entries.has_value()}}; + }; + + static bool classof(const Chunk *S) { return S->Kind == ChunkKind::FuncMap; } +}; + struct StackSizesSection : Section { std::optional<std::vector<StackSizeEntry>> Entries; @@ -762,6 +781,7 @@ bool shouldAllocateFileSpace(ArrayRef<ProgramHeader> Phdrs, } // end namespace llvm LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::ELFYAML::StackSizeEntry) +LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::ELFYAML::FuncMapEntry) LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::ELFYAML::BBAddrMapEntry) LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::ELFYAML::BBAddrMapEntry::BBEntry) LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::ELFYAML::BBAddrMapEntry::BBRangeEntry) @@ -929,6 +949,10 @@ template <> struct MappingTraits<ELFYAML::StackSizeEntry> { static void mapping(IO &IO, ELFYAML::StackSizeEntry &Rel); }; +template <> struct MappingTraits<ELFYAML::FuncMapEntry> { + static void mapping(IO &IO, ELFYAML::FuncMapEntry &E); +}; + template <> struct MappingTraits<ELFYAML::BBAddrMapEntry> { static void mapping(IO &IO, ELFYAML::BBAddrMapEntry &E); }; diff --git a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp index b2a4721..0bce24f 100644 --- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp +++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp @@ -147,6 +147,7 @@ enum class PGOMapFeaturesEnum { BrProb, All, }; + static cl::bits<PGOMapFeaturesEnum> PgoAnalysisMapFeatures( "pgo-analysis-map", cl::Hidden, cl::CommaSeparated, cl::values( @@ -161,6 +162,13 @@ static cl::bits<PGOMapFeaturesEnum> PgoAnalysisMapFeatures( "Enable extended information within the SHT_LLVM_BB_ADDR_MAP that is " "extracted from PGO related analysis.")); +static cl::opt<bool> EmitFuncMap( + "func-map", + cl::desc( + "Emit features of function address map in SHT_LLVM_FUNC_MAP " + "section(Currently only support dynamic instruction count feature)."), + cl::Hidden, cl::init(false)); + static cl::opt<bool> BBAddrMapSkipEmitBBEntries( "basic-block-address-map-skip-bb-entries", cl::desc("Skip emitting basic block entries in the SHT_LLVM_BB_ADDR_MAP " @@ -1548,6 +1556,39 @@ void AsmPrinter::emitBBAddrMapSection(const MachineFunction &MF) { OutStreamer->popSection(); } +void AsmPrinter::emitFuncMapSection(const MachineFunction &MF) { + if (!EmitFuncMap) + return; + + MCSection *FuncMapSection = + getObjFileLowering().getFuncMapSection(*MF.getSection()); + assert(FuncMapSection && ".llvm_func_map section is not initialized."); + const MCSymbol *FunctionSymbol = getFunctionBegin(); + OutStreamer->pushSection(); + OutStreamer->switchSection(FuncMapSection); + OutStreamer->AddComment("version"); + uint8_t FuncMapVersion = OutStreamer->getContext().getFuncMapVersion(); + OutStreamer->emitInt8(FuncMapVersion); + + OutStreamer->AddComment("function address"); + OutStreamer->emitSymbolValue(FunctionSymbol, getPointerSize()); + + const MachineBlockFrequencyInfo *MBFI = + &getAnalysis<LazyMachineBlockFrequencyInfoPass>().getBFI(); + uint64_t DynInstCount = 0; + for (const MachineBasicBlock &MBB : MF) { + for (const MachineInstr &MI : MBB) { + if (MI.isDebugValue() || MI.isPseudoProbe()) + continue; + DynInstCount += MBFI->getBlockProfileCount(&MBB).value_or(0); + } + } + + OutStreamer->AddComment("dynamic instruction count"); + OutStreamer->emitULEB128IntValue(DynInstCount); + OutStreamer->popSection(); +} + void AsmPrinter::emitKCFITrapEntry(const MachineFunction &MF, const MCSymbol *Symbol) { MCSection *Section = @@ -2119,6 +2160,7 @@ void AsmPrinter::emitFunctionBody() { MF->getContext().reportWarning( SMLoc(), "pgo-analysis-map is enabled for function " + MF->getName() + " but it does not have labels"); + emitFuncMapSection(*MF); } // Emit sections containing instruction and function PCs. @@ -2749,7 +2791,7 @@ void AsmPrinter::SetupMachineFunction(MachineFunction &MF) { F.hasFnAttribute("xray-instruction-threshold") || needFuncLabels(MF, *this) || NeedsLocalForSize || MF.getTarget().Options.EmitStackSizeSection || - MF.getTarget().Options.BBAddrMap) { + MF.getTarget().Options.BBAddrMap || EmitFuncMap) { CurrentFnBegin = createTempSymbol("func_begin"); if (NeedsLocalForSize) CurrentFnSymForSize = CurrentFnBegin; diff --git a/llvm/lib/MC/MCObjectFileInfo.cpp b/llvm/lib/MC/MCObjectFileInfo.cpp index 150e38a..02cc207 100644 --- a/llvm/lib/MC/MCObjectFileInfo.cpp +++ b/llvm/lib/MC/MCObjectFileInfo.cpp @@ -1120,6 +1120,23 @@ MCObjectFileInfo::getBBAddrMapSection(const MCSection &TextSec) const { cast<MCSymbolELF>(TextSec.getBeginSymbol())); } +MCSection *MCObjectFileInfo::getFuncMapSection(const MCSection &TextSec) const { + if (Ctx->getObjectFileType() != MCContext::IsELF) + return nullptr; + + const MCSectionELF &ElfSec = static_cast<const MCSectionELF &>(TextSec); + unsigned Flags = ELF::SHF_LINK_ORDER; + StringRef GroupName; + if (const MCSymbol *Group = ElfSec.getGroup()) { + GroupName = Group->getName(); + Flags |= ELF::SHF_GROUP; + } + + return Ctx->getELFSection(".llvm_func_map", ELF::SHT_LLVM_FUNC_MAP, Flags, 0, + GroupName, true, ElfSec.getUniqueID(), + cast<MCSymbolELF>(TextSec.getBeginSymbol())); +} + MCSection * MCObjectFileInfo::getKCFITrapSection(const MCSection &TextSec) const { if (Ctx->getObjectFileType() != MCContext::IsELF) diff --git a/llvm/lib/MC/MCParser/ELFAsmParser.cpp b/llvm/lib/MC/MCParser/ELFAsmParser.cpp index b58210b..c9df8a3 100644 --- a/llvm/lib/MC/MCParser/ELFAsmParser.cpp +++ b/llvm/lib/MC/MCParser/ELFAsmParser.cpp @@ -679,6 +679,8 @@ EndStmt: Type = ELF::SHT_LLVM_LTO; else if (TypeName == "llvm_jt_sizes") Type = ELF::SHT_LLVM_JT_SIZES; + else if (TypeName == "llvm_func_map") + Type = ELF::SHT_LLVM_FUNC_MAP; else if (TypeName.getAsInteger(0, Type)) return TokError("unknown section type"); } diff --git a/llvm/lib/MC/MCSectionELF.cpp b/llvm/lib/MC/MCSectionELF.cpp index 25e62b7..fa1a5eb 100644 --- a/llvm/lib/MC/MCSectionELF.cpp +++ b/llvm/lib/MC/MCSectionELF.cpp @@ -174,6 +174,8 @@ void MCSectionELF::printSwitchToSection(const MCAsmInfo &MAI, const Triple &T, OS << "llvm_lto"; else if (Type == ELF::SHT_LLVM_JT_SIZES) OS << "llvm_jt_sizes"; + else if (Type == ELF::SHT_LLVM_FUNC_MAP) + OS << "llvm_func_map"; else OS << "0x" << Twine::utohexstr(Type); diff --git a/llvm/lib/Object/ELF.cpp b/llvm/lib/Object/ELF.cpp index b6d0699..00b08b8 100644 --- a/llvm/lib/Object/ELF.cpp +++ b/llvm/lib/Object/ELF.cpp @@ -321,6 +321,7 @@ StringRef llvm::object::getELFSectionTypeName(uint32_t Machine, unsigned Type) { STRINGIFY_ENUM_CASE(ELF, SHT_LLVM_OFFLOADING); STRINGIFY_ENUM_CASE(ELF, SHT_LLVM_LTO); STRINGIFY_ENUM_CASE(ELF, SHT_LLVM_JT_SIZES) + STRINGIFY_ENUM_CASE(ELF, SHT_LLVM_FUNC_MAP); STRINGIFY_ENUM_CASE(ELF, SHT_GNU_ATTRIBUTES); STRINGIFY_ENUM_CASE(ELF, SHT_GNU_HASH); STRINGIFY_ENUM_CASE(ELF, SHT_GNU_verdef); @@ -940,6 +941,92 @@ ELFFile<ELFT>::decodeBBAddrMap(const Elf_Shdr &Sec, const Elf_Shdr *RelaSec, } template <class ELFT> +Expected<std::vector<FuncMap>> +ELFFile<ELFT>::decodeFuncMap(const Elf_Shdr &Sec, + const Elf_Shdr *RelaSec) const { + bool IsRelocatable = this->getHeader().e_type == ELF::ET_REL; + + // This DenseMap maps the offset of each function (the location of the + // reference to the function in the SHT_LLVM_FUNC_ADDR_MAP section) to the + // addend (the location of the function in the text section). + llvm::DenseMap<uint64_t, uint64_t> FunctionOffsetTranslations; + if (IsRelocatable && RelaSec) { + assert(RelaSec && + "Can't read a SHT_LLVM_FUNC_ADDR_MAP section in a relocatable " + "object file without providing a relocation section."); + Expected<typename ELFFile<ELFT>::Elf_Rela_Range> Relas = + this->relas(*RelaSec); + if (!Relas) + return createError("unable to read relocations for section " + + describe(*this, Sec) + ": " + + toString(Relas.takeError())); + for (typename ELFFile<ELFT>::Elf_Rela Rela : *Relas) + FunctionOffsetTranslations[Rela.r_offset] = Rela.r_addend; + } + auto GetAddressForRelocation = + [&](unsigned RelocationOffsetInSection) -> Expected<unsigned> { + auto FOTIterator = + FunctionOffsetTranslations.find(RelocationOffsetInSection); + if (FOTIterator == FunctionOffsetTranslations.end()) { + return createError("failed to get relocation data for offset: " + + Twine::utohexstr(RelocationOffsetInSection) + + " in section " + describe(*this, Sec)); + } + return FOTIterator->second; + }; + Expected<ArrayRef<uint8_t>> ContentsOrErr = this->getSectionContents(Sec); + if (!ContentsOrErr) + return ContentsOrErr.takeError(); + ArrayRef<uint8_t> Content = *ContentsOrErr; + DataExtractor Data(Content, this->isLE(), ELFT::Is64Bits ? 8 : 4); + std::vector<FuncMap> FunctionEntries; + + DataExtractor::Cursor Cur(0); + Error ULEBSizeErr = Error::success(); + + // Helper lampda to extract the (possiblly relocatable) address stored at Cur. + auto ExtractAddress = [&]() -> Expected<typename ELFFile<ELFT>::uintX_t> { + uint64_t RelocationOffsetInSection = Cur.tell(); + auto Address = + static_cast<typename ELFFile<ELFT>::uintX_t>(Data.getAddress(Cur)); + if (!Cur) + return Cur.takeError(); + if (!IsRelocatable) + return Address; + assert(Address == 0); + Expected<unsigned> AddressOrErr = + GetAddressForRelocation(RelocationOffsetInSection); + if (!AddressOrErr) + return AddressOrErr.takeError(); + return *AddressOrErr; + }; + + uint8_t Version = 0; + while (!ULEBSizeErr && Cur && Cur.tell() < Content.size()) { + if (Sec.sh_type == ELF::SHT_LLVM_FUNC_MAP) { + Version = Data.getU8(Cur); + if (!Cur) + break; + if (Version > 1) + return createError("unsupported SHT_LLVM_FUNC_MAP version: " + + Twine(static_cast<int>(Version))); + } + typename ELFFile<ELFT>::uintX_t FunctionAddress = 0; + auto AddressOrErr = ExtractAddress(); + if (!AddressOrErr) + return AddressOrErr.takeError(); + FunctionAddress = *AddressOrErr; + uint64_t DynamicInstCount = readULEB128As<uint64_t>(Data, Cur, ULEBSizeErr); + FunctionEntries.push_back({FunctionAddress, DynamicInstCount}); + } + // Either Cur is in the error state, or we have an error in ULEBSizeErr, but + // we join all errors here to be safe. + if (!Cur || ULEBSizeErr) + return joinErrors(Cur.takeError(), std::move(ULEBSizeErr)); + return FunctionEntries; +} + +template <class ELFT> Expected< MapVector<const typename ELFT::Shdr *, const typename ELFT::Shdr *>> ELFFile<ELFT>::getSectionAndRelocations( diff --git a/llvm/lib/ObjectYAML/ELFEmitter.cpp b/llvm/lib/ObjectYAML/ELFEmitter.cpp index 9ae76a7..06efac7 100644 --- a/llvm/lib/ObjectYAML/ELFEmitter.cpp +++ b/llvm/lib/ObjectYAML/ELFEmitter.cpp @@ -288,6 +288,9 @@ template <class ELFT> class ELFState { const ELFYAML::BBAddrMapSection &Section, ContiguousBlobAccumulator &CBA); void writeSectionContent(Elf_Shdr &SHeader, + const ELFYAML::FuncMapSection &Section, + ContiguousBlobAccumulator &CBA); + void writeSectionContent(Elf_Shdr &SHeader, const ELFYAML::HashSection &Section, ContiguousBlobAccumulator &CBA); void writeSectionContent(Elf_Shdr &SHeader, @@ -894,6 +897,8 @@ void ELFState<ELFT>::initSectionHeaders(std::vector<Elf_Shdr> &SHeaders, writeSectionContent(SHeader, *S, CBA); } else if (auto S = dyn_cast<ELFYAML::BBAddrMapSection>(Sec)) { writeSectionContent(SHeader, *S, CBA); + } else if (auto S = dyn_cast<ELFYAML::FuncMapSection>(Sec)) { + writeSectionContent(SHeader, *S, CBA); } else { llvm_unreachable("Unknown section type"); } @@ -1538,6 +1543,28 @@ void ELFState<ELFT>::writeSectionContent( } template <class ELFT> +void ELFState<ELFT>::writeSectionContent(Elf_Shdr &SHeader, + const ELFYAML::FuncMapSection &Section, + ContiguousBlobAccumulator &CBA) { + if (!Section.Entries) + return; + + for (const auto &[Idx, E] : llvm::enumerate(*Section.Entries)) { + if (Section.Type == llvm::ELF::SHT_LLVM_FUNC_MAP) { + if (E.Version > 1) + WithColor::warning() << "unsupported SHT_LLVM_FUNC_MAP version: " + << static_cast<int>(E.Version) + << "; encoding using the most recent version"; + CBA.write(E.Version); + SHeader.sh_size += 1; + } + CBA.write<uintX_t>(E.Address, ELFT::Endianness); + SHeader.sh_size += sizeof(uintX_t); + SHeader.sh_size += CBA.writeULEB128(E.DynamicInstCount); + } +} + +template <class ELFT> void ELFState<ELFT>::writeSectionContent( Elf_Shdr &SHeader, const ELFYAML::LinkerOptionsSection &Section, ContiguousBlobAccumulator &CBA) { diff --git a/llvm/lib/ObjectYAML/ELFYAML.cpp b/llvm/lib/ObjectYAML/ELFYAML.cpp index 539834f..737ce96 100644 --- a/llvm/lib/ObjectYAML/ELFYAML.cpp +++ b/llvm/lib/ObjectYAML/ELFYAML.cpp @@ -723,6 +723,7 @@ void ScalarEnumerationTraits<ELFYAML::ELF_SHT>::enumeration( ECase(SHT_LLVM_PART_PHDR); ECase(SHT_LLVM_BB_ADDR_MAP_V0); ECase(SHT_LLVM_BB_ADDR_MAP); + ECase(SHT_LLVM_FUNC_MAP); ECase(SHT_LLVM_OFFLOADING); ECase(SHT_LLVM_LTO); ECase(SHT_GNU_ATTRIBUTES); @@ -1432,6 +1433,12 @@ static void sectionMapping(IO &IO, ELFYAML::BBAddrMapSection &Section) { IO.mapOptional("PGOAnalyses", Section.PGOAnalyses); } +static void sectionMapping(IO &IO, ELFYAML::FuncMapSection &Section) { + commonSectionMapping(IO, Section); + IO.mapOptional("Content", Section.Content); + IO.mapOptional("Entries", Section.Entries); +} + static void sectionMapping(IO &IO, ELFYAML::StackSizesSection &Section) { commonSectionMapping(IO, Section); IO.mapOptional("Entries", Section.Entries); @@ -1725,6 +1732,12 @@ void MappingTraits<std::unique_ptr<ELFYAML::Chunk>>::mapping( Section.reset(new ELFYAML::BBAddrMapSection()); sectionMapping(IO, *cast<ELFYAML::BBAddrMapSection>(Section.get())); break; + case ELF::SHT_LLVM_FUNC_MAP: + if (!IO.outputting()) + Section.reset(new ELFYAML::FuncMapSection()); + sectionMapping(IO, *cast<ELFYAML::FuncMapSection>(Section.get())); + break; + default: if (!IO.outputting()) { StringRef Name; @@ -1848,6 +1861,14 @@ void MappingTraits<ELFYAML::StackSizeEntry>::mapping( IO.mapRequired("Size", E.Size); } +void MappingTraits<ELFYAML::FuncMapEntry>::mapping(IO &IO, + ELFYAML::FuncMapEntry &E) { + assert(IO.getContext() && "The IO context is not initialized"); + IO.mapRequired("Version", E.Version); + IO.mapOptional("Address", E.Address, Hex64(0)); + IO.mapOptional("DynInstCnt", E.DynamicInstCount, 0); +} + void MappingTraits<ELFYAML::BBAddrMapEntry>::mapping( IO &IO, ELFYAML::BBAddrMapEntry &E) { assert(IO.getContext() && "The IO context is not initialized"); diff --git a/llvm/test/CodeGen/X86/function-address-map-dyn-inst-count.ll b/llvm/test/CodeGen/X86/function-address-map-dyn-inst-count.ll new file mode 100644 index 0000000..7762103 --- /dev/null +++ b/llvm/test/CodeGen/X86/function-address-map-dyn-inst-count.ll @@ -0,0 +1,108 @@ +; Test emitting dynamic instruction count feature in .llvm_func_map section. +; RUN: llc < %s -mtriple=x86_64 -function-sections -func-map | FileCheck %s + +;; Check we add SHF_LINK_ORDER for .llvm_func_map and link it with the corresponding .text sections. +; CHECK: .section .text.foo,"ax",@progbits +; CHECK-LABEL: foo: +; CHECK-NEXT: [[FOO_BEGIN:.Lfunc_begin[0-9]+]]: +; CHECK: .section .llvm_func_map,"o",@llvm_func_map,.text.foo{{$}} +; CHECK-NEXT: .byte 1 # version +; CHECK-NEXT: .quad [[FOO_BEGIN]] # function address +; CHECK-NEXT: .ascii "\252\001" # dynamic instruction count + + +; CHECK: .section .text.main,"ax",@progbits +; CHECK-LABEL: main: +; CHECK-NEXT: [[MAIN_BEGIN:.Lfunc_begin[0-9]+]]: +; CHECK: .section .llvm_func_map,"o",@llvm_func_map,.text.main{{$}} +; CHECK-NEXT: .byte 1 # version +; CHECK-NEXT: .quad [[MAIN_BEGIN]] # function address +; CHECK-NEXT: .ascii "\265\003" # dynamic instruction count + + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +define i32 @foo(i32 %x) !dbg !4 !prof !6 { +entry: + #dbg_value(i32 %x, !7, !DIExpression(), !9) + call void @llvm.pseudoprobe(i64 6699318081062747564, i64 1, i32 0, i64 -1) + %rem = mul i32 %x, 5 + %tobool.not = icmp eq i32 %rem, 0, !dbg !10 + br i1 %tobool.not, label %if.end, label %if.then, !prof !12 + +if.then: ; preds = %entry + %inc = add i32 0, 0 + call void @llvm.pseudoprobe(i64 6699318081062747564, i64 2, i32 0, i64 -1) + #dbg_value(i32 %inc, !7, !DIExpression(), !9) + br label %if.end + +if.end: ; preds = %if.then, %entry + %x.addr.0 = phi i32 [ 0, %if.then ], [ 1, %entry ] + #dbg_value(i32 %x.addr.0, !7, !DIExpression(), !9) + ret i32 %x.addr.0 +} + +define i32 @main() #0 !dbg !13 !prof !15 { +entry: + #dbg_value(i32 0, !16, !DIExpression(), !17) + br label %while.cond + +while.cond: ; preds = %if.then, %if.else, %entry + %i.0 = phi i32 [ 0, %entry ], [ %inc, %if.else ], [ %inc, %if.then ] + #dbg_value(i32 %i.0, !16, !DIExpression(), !17) + %inc = add i32 %i.0, 1 + #dbg_value(i32 %inc, !16, !DIExpression(), !17) + %cmp = icmp ult i32 %i.0, 1600000 + br i1 %cmp, label %while.body, label %while.end, !prof !18 + +while.body: ; preds = %while.cond + %rem = urem i32 %inc, 11 + %tobool.not = icmp eq i32 %rem, 0 + br i1 %tobool.not, label %if.else, label %if.then, !prof !19 + +if.then: ; preds = %while.body + %call = call i32 @foo(i32 0), !dbg !20 + %0 = load volatile i32, ptr null, align 4 + br label %while.cond + +if.else: ; preds = %while.body + store i32 0, ptr null, align 4 + br label %while.cond + +while.end: ; preds = %while.cond + ret i32 0 + +; uselistorder directives + uselistorder label %while.cond, { 1, 0, 2 } + uselistorder i32 %inc, { 2, 1, 0 } +} + +attributes #0 = { "target-cpu"="x86-64" } + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!3} + +!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 20.0.0", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, globals: !2, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "test.c", directory: "/home", checksumkind: CSK_MD5, checksum: "920887ee2258042655d8340f78e732e9") +!2 = !{} +!3 = !{i32 2, !"Debug Info Version", i32 3} +!4 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 3, type: !5, scopeLine: 3, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !2) +!5 = distinct !DISubroutineType(types: !2) +!6 = !{!"function_entry_count", i64 20} +!7 = !DILocalVariable(name: "x", arg: 1, scope: !4, file: !1, line: 3, type: !8) +!8 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!9 = !DILocation(line: 0, scope: !4) +!10 = !DILocation(line: 4, column: 9, scope: !11) +!11 = distinct !DILexicalBlock(scope: !4, file: !1, line: 4, column: 7) +!12 = !{!"branch_weights", i32 15, i32 5} +!13 = distinct !DISubprogram(name: "main", scope: !1, file: !1, line: 9, type: !14, scopeLine: 9, flags: DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !2) +!14 = !DISubroutineType(types: !2) +!15 = !{!"function_entry_count", i64 1} +!16 = !DILocalVariable(name: "i", scope: !13, file: !1, line: 10, type: !8) +!17 = !DILocation(line: 0, scope: !13) +!18 = !{!"branch_weights", i32 22, i32 1} +!19 = !{!"branch_weights", i32 2, i32 20} +!20 = !DILocation(line: 12, column: 22, scope: !21) +!21 = !DILexicalBlockFile(scope: !22, file: !1, discriminator: 455082031) +!22 = distinct !DILexicalBlock(scope: !13, file: !1, line: 12, column: 9) diff --git a/llvm/test/CodeGen/X86/function-address-map-function-sections.ll b/llvm/test/CodeGen/X86/function-address-map-function-sections.ll new file mode 100644 index 0000000..0c5eb2d --- /dev/null +++ b/llvm/test/CodeGen/X86/function-address-map-function-sections.ll @@ -0,0 +1,38 @@ +; RUN: llc < %s -mtriple=x86_64 -function-sections -func-map | FileCheck %s + +$_Z4fooTIiET_v = comdat any + +define dso_local i32 @_Z3barv() { + ret i32 0 +} +;; Check we add SHF_LINK_ORDER for .llvm_func_map and link it with the corresponding .text sections. +; CHECK: .section .text._Z3barv,"ax",@progbits +; CHECK-LABEL: _Z3barv: +; CHECK-NEXT: [[BAR_BEGIN:.Lfunc_begin[0-9]+]]: +; CHECK: .section .llvm_func_map,"o",@llvm_func_map,.text._Z3barv{{$}} +; CHECK-NEXT: .byte 1 # version +; CHECK-NEXT: .quad [[BAR_BEGIN]] # function address + + +define dso_local i32 @_Z3foov() { + %1 = call i32 @_Z4fooTIiET_v() + ret i32 %1 +} +; CHECK: .section .text._Z3foov,"ax",@progbits +; CHECK-LABEL: _Z3foov: +; CHECK-NEXT: [[FOO_BEGIN:.Lfunc_begin[0-9]+]]: +; CHECK: .section .llvm_func_map,"o",@llvm_func_map,.text._Z3foov{{$}} +; CHECK-NEXT: .byte 1 # version +; CHECK-NEXT: .quad [[FOO_BEGIN]] # function address + + +define linkonce_odr dso_local i32 @_Z4fooTIiET_v() comdat { + ret i32 0 +} +;; Check we add .llvm_func_map section to a COMDAT group with the corresponding .text section if such a COMDAT exists. +; CHECK: .section .text._Z4fooTIiET_v,"axG",@progbits,_Z4fooTIiET_v,comdat +; CHECK-LABEL: _Z4fooTIiET_v: +; CHECK-NEXT: [[FOOCOMDAT_BEGIN:.Lfunc_begin[0-9]+]]: +; CHECK: .section .llvm_func_map,"oG",@llvm_func_map,.text._Z4fooTIiET_v,_Z4fooTIiET_v,comdat{{$}} +; CHECK-NEXT: .byte 1 # version +; CHECK-NEXT: .quad [[FOOCOMDAT_BEGIN]] # function address diff --git a/llvm/test/MC/AsmParser/llvm_section_types.s b/llvm/test/MC/AsmParser/llvm_section_types.s index 147b1499..f3f3150 100644 --- a/llvm/test/MC/AsmParser/llvm_section_types.s +++ b/llvm/test/MC/AsmParser/llvm_section_types.s @@ -17,6 +17,8 @@ .byte 1 .section .section8,"",@llvm_lto .byte 1 +.section .section9,"",@llvm_func_map +.byte 1 # CHECK: Name: .section1 # CHECK-NEXT: Type: SHT_LLVM_BB_ADDR_MAP @@ -34,3 +36,5 @@ # CHECK-NEXT: Type: SHT_LLVM_OFFLOADING # CHECK: Name: .section8 # CHECK-NEXT: Type: SHT_LLVM_LTO +# CHECK: Name: .section9 +# CHECK-NEXT: Type: SHT_LLVM_FUNC_MAP diff --git a/llvm/test/tools/llvm-readobj/ELF/func-map.test b/llvm/test/tools/llvm-readobj/ELF/func-map.test new file mode 100644 index 0000000..9677f9f --- /dev/null +++ b/llvm/test/tools/llvm-readobj/ELF/func-map.test @@ -0,0 +1,93 @@ +## This test checks how we handle the --func-map option. + +## Check 64-bit: +# RUN: yaml2obj --docnum=1 %s -DBITS=64 -DADDR=0x999999999 -o %t1.x64.o +# RUN: llvm-readobj %t1.x64.o --func-map 2>&1 | FileCheck %s -DADDR=0x999999999 -DFILE=%t1.x64.o --check-prefix=CHECK +# RUN: llvm-readelf %t1.x64.o --func-map | FileCheck %s --check-prefix=GNU + +## Check 32-bit: +# RUN: yaml2obj --docnum=1 %s -DBITS=32 -o %t1.x32.o +# RUN: llvm-readobj %t1.x32.o --func-map 2>&1 | FileCheck -DADDR=0x11111 %s -DFILE=%t1.x32.o --check-prefix=CHECK +# RUN: llvm-readelf %t1.x32.o --func-map | FileCheck %s --check-prefix=GNU + +## Check that a malformed section can be handled. +# RUN: yaml2obj --docnum=1 %s -DBITS=32 -DSIZE=3 -o %t2.o +# RUN: llvm-readobj %t2.o --func-map 2>&1 | FileCheck %s -DOFFSET=0x3 -DFILE=%t2.o --check-prefix=TRUNCATED + +# CHECK: FuncMap [ +# CHECK-NEXT: Function { +# CHECK-NEXT: At: [[ADDR]] +# CHECK-NEXT: warning: '[[FILE]]': could not identify function symbol for address ([[ADDR]]) in SHT_LLVM_FUNC_MAP section with index 3 +# CHECK-NEXT: Name: <?> +# CHECK-NEXT: DynamicInstCount: 100 +# CHECK-NEXT: } +# CHECK-NEXT: Function { +# CHECK-NEXT: At: 0x22222 +# CHECK-NEXT: Name: foo +# CHECK-NEXT: DynamicInstCount: 200 +# CHECK-NEXT: } +# CHECK-NEXT: ] +# CHECK-NEXT: FuncMap [ +# CHECK-NEXT: Function { +# CHECK-NEXT: At: 0x33333 +# CHECK-NEXT: Name: bar +# CHECK-NEXT: DynamicInstCount: 300 +# CHECK-NEXT: } +# CHECK-NEXT: ] + +# GNU: GNUStyle::printFuncMaps not implemented + +# TRUNCATED: FuncMap [ +# TRUNCATED-NEXT: warning: '[[FILE]]': unable to dump SHT_LLVM_FUNC_MAP section with index 3: unexpected end of data at offset [[OFFSET]] +# TRUNCATED-NEXT: ] +## Check that the other valid section is properly dumped. +# TRUNCATED-NEXT: FuncMap [ +# TRUNCATED-NEXT: Function { +# TRUNCATED-NEXT: At: 0x33333 +# TRUNCATED-NEXT: Name: bar +# TRUNCATED-NEXT: DynamicInstCount: 300 +# TRUNCATED-NEXT: } +# TRUNCATED-NEXT: ] + +--- !ELF +FileHeader: + Class: ELFCLASS[[BITS]] + Data: ELFDATA2LSB + Type: ET_EXEC +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [SHF_ALLOC] + - Name: .text.bar + Type: SHT_PROGBITS + Flags: [SHF_ALLOC] + - Name: .llvm_func_map + Type: SHT_LLVM_FUNC_MAP + ShSize: [[SIZE=<none>]] + Link: .text + Entries: + - Version: 1 + Address: [[ADDR=0x11111]] + DynInstCnt: 100 + - Version: 1 + Address: 0x22222 + DynInstCnt: 200 + - Name: dummy_section + Type: SHT_PROGBITS + Size: 16 + - Name: '.llvm_func_map_2' + Type: SHT_LLVM_FUNC_MAP + Link: .text.bar + Entries: + - Version: 1 + Address: 0x33333 + DynInstCnt: 300 +Symbols: + - Name: foo + Section: .text + Type: STT_FUNC + Value: 0x22222 + - Name: bar + Section: .text.bar + Type: STT_FUNC + Value: 0x33333 diff --git a/llvm/test/tools/obj2yaml/ELF/func-map.yaml b/llvm/test/tools/obj2yaml/ELF/func-map.yaml new file mode 100644 index 0000000..4b0476b --- /dev/null +++ b/llvm/test/tools/obj2yaml/ELF/func-map.yaml @@ -0,0 +1,130 @@ +## Check how obj2yaml produces YAML .llvm_func_map descriptions. + +## Check that obj2yaml uses the "Entries" tag to describe an .llvm_func_map section. + +# RUN: yaml2obj --docnum=1 %s -o %t1 +# RUN: obj2yaml %t1 | FileCheck %s --check-prefix=VALID + +# VALID: --- !ELF +# VALID-NEXT: FileHeader: +# VALID-NEXT: Class: ELFCLASS64 +# VALID-NEXT: Data: ELFDATA2LSB +# VALID-NEXT: Type: ET_EXEC +# VALID-NEXT: Sections: +# VALID-NEXT: - Name: .llvm_func_map +# VALID-NEXT: Type: SHT_LLVM_FUNC_MAP +# VALID-NEXT: Entries: +# VALID-NEXT: - Version: 1 +## The 'Address' field is omitted when it's zero. +# VALID-NEXT: DynInstCnt: 16 +## The 'DynInstCnt' field is omitted when it's zero. +# VALID-NEXT: - Version: 1 +# VALID-NEXT: Address: 0x1 +# VALID-NEXT: - Version: 1 +# VALID-NEXT: Address: 0xFFFFFFFFFFFFFFF1 +# VALID-NEXT: DynInstCnt: 100001 + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC +Sections: + - Name: .llvm_func_map + Type: SHT_LLVM_FUNC_MAP + ShSize: [[SIZE=<none>]] + Entries: + - Version: 1 + Address: 0x0 + DynInstCnt: 16 + - Version: 1 + Address: 0x1 + DynInstCnt: 0 + - Version: 1 + Address: 0xFFFFFFFFFFFFFFF1 + DynInstCnt: 100001 + +## Check obj2yaml can dump empty .llvm_func_map sections. + +# RUN: yaml2obj --docnum=2 %s -o %t2 +# RUN: obj2yaml %t2 | FileCheck %s --check-prefix=EMPTY + +# EMPTY: --- !ELF +# EMPTY-NEXT: FileHeader: +# EMPTY-NEXT: Class: ELFCLASS64 +# EMPTY-NEXT: Data: ELFDATA2LSB +# EMPTY-NEXT: Type: ET_EXEC +# EMPTY-NEXT: Sections: +# EMPTY-NEXT: - Name: .llvm_func_map +# EMPTY-NEXT: Type: SHT_LLVM_FUNC_MAP +# EMPTY-NOT: Content: + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC +Sections: + - Name: .llvm_func_map + Type: SHT_LLVM_FUNC_MAP + Content: "" + +## Check obj2yaml can dump multiple .llvm_func_map sections. + +# RUN: yaml2obj --docnum=3 %s -o %t3 +# RUN: obj2yaml %t3 | FileCheck %s --check-prefix=MULTI + +# MULTI: --- !ELF +# MULTI-NEXT: FileHeader: +# MULTI-NEXT: Class: ELFCLASS64 +# MULTI-NEXT: Data: ELFDATA2LSB +# MULTI-NEXT: Type: ET_EXEC +# MULTI-NEXT: Sections: +# MULTI-NEXT: - Name: .llvm_func_map +# MULTI-NEXT: Type: SHT_LLVM_FUNC_MAP +# MULTI-NEXT: Entries: +# MULTI-NEXT: - Version: 1 +# MULTI-NEXT: Address: 0x2 +# MULTI-NEXT: DynInstCnt: 3 +# MULTI-NEXT: - Name: '.llvm_func_map (1)' +# MULTI-NEXT: Type: SHT_LLVM_FUNC_MAP +# MULTI-NEXT: Entries: +# MULTI-NEXT: - Version: 1 +# MULTI-NEXT: Address: 0xA +# MULTI-NEXT: DynInstCnt: 100 + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC +Sections: + - Name: .llvm_func_map + Type: SHT_LLVM_FUNC_MAP + Entries: + - Version: 1 + Address: 0x2 + DynInstCnt: 3 + - Name: '.llvm_func_map (1)' + Type: SHT_LLVM_FUNC_MAP + Entries: + - Version: 1 + Address: 0xA + DynInstCnt: 100 + +## Check that obj2yaml uses the "Content" tag to describe an .llvm_func_map section +## when it can't extract the entries, for example, when the section is truncated. + +# RUN: yaml2obj --docnum=1 -DSIZE=0x8 %s -o %t4 +# RUN: obj2yaml %t4 | FileCheck %s --check-prefixes=TRUNCATED,INVALID + + +# INVALID: --- !ELF +# INVALID-NEXT: FileHeader: +# INVALID-NEXT: Class: ELFCLASS64 +# INVALID-NEXT: Data: ELFDATA2LSB +# INVALID-NEXT: Type: ET_EXEC +# INVALID-NEXT: Sections: +# INVALID-NEXT: - Name: .llvm_func_map +# INVALID-NEXT: Type: SHT_LLVM_FUNC_MAP +# TRUNCATED-NEXT: Content: '{{([[:xdigit:]]{16})}}'{{$}} diff --git a/llvm/test/tools/yaml2obj/ELF/func-map.yaml b/llvm/test/tools/yaml2obj/ELF/func-map.yaml new file mode 100644 index 0000000..2a51593 --- /dev/null +++ b/llvm/test/tools/yaml2obj/ELF/func-map.yaml @@ -0,0 +1,115 @@ +## Check how yaml2obj produces .llvm_func_map sections. + +# RUN: yaml2obj --docnum=1 %s -o %t1 +# RUN: llvm-readobj --sections --section-data %t1 | FileCheck %s + +## Case 1: Specify content. +# CHECK: Section { +# CHECK: Index: 1 +# CHECK-NEXT: Name: .llvm_func_map (1) +# CHECK-NEXT: Type: SHT_LLVM_FUNC_MAP (0x6FFF4C0E) +# CHECK-NEXT: Flags [ (0x0) +# CHECK-NEXT: ] +# CHECK-NEXT: Address: 0x0 +# CHECK-NEXT: Offset: 0x40 +# CHECK-NEXT: Size: 13 +# CHECK-NEXT: Link: 0 +# CHECK-NEXT: Info: 0 +# CHECK-NEXT: AddressAlignment: 0 +# CHECK-NEXT: EntrySize: 0 +# CHECK-NEXT: SectionData ( +# CHECK-NEXT: 0000: 00000000 00000000 01010203 04 +# CHECK-NEXT: ) +# CHECK-NEXT: } + +## Case 2: Empty. +# CHECK: Name: .llvm_func_map (1) +# CHECK: Size: +# CHECK-SAME: {{^ 0$}} + +## Case 3: Specify Size only. +# CHECK: Name: .llvm_func_map (1) +# CHECK: SectionData ( +# CHECK-NEXT: 0000: 00000000 00000000 +# CHECK-NEXT: ) + +# Case 4: Specify Entries. +# CHECK: Name: .llvm_func_map (1) +# CHECK: SectionData ( +# CHECK-NEXT: 0000: 01222202 00000000 0010 +# CHECK-NEXT: ) + + + + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC +Sections: + +## Test the following cases: + +## 1) We can produce an .llvm_func_map section from a description with section +## content. +## Specify Content. + - Name: '.llvm_func_map (1)' + Type: SHT_LLVM_FUNC_MAP + Content: "00000000000000000101020304" + +# 2) We can produce an empty .llvm_func_map section from a description +# with empty section content. + - Name: '.llvm_func_map (2)' + Type: SHT_LLVM_FUNC_MAP + +## 3) We can produce a zero .llvm_func_map section of a specific size when +## we specify the size only. + - Name: '.llvm_func_map (3)' + Type: SHT_LLVM_FUNC_MAP + Size: 8 + +## 4) We can produce an .llvm_func_map section from a description with +## Entries. + - Name: '.llvm_func_map (4)' + Type: SHT_LLVM_FUNC_MAP + Entries: + - Version: 1 + Address: 0x22222 + DynInstCnt: 0x10 + +## Check we can't use Entries at the same time as either Content or Size. +# RUN: not yaml2obj --docnum=2 -DCONTENT="00" %s 2>&1 | FileCheck %s --check-prefix=INVALID +# RUN: not yaml2obj --docnum=2 -DSIZE="0" %s 2>&1 | FileCheck %s --check-prefix=INVALID + +# INVALID: error: "Entries" cannot be used with "Content" or "Size" + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC +Sections: +## Specify Content and Size + - Name: '.llvm_func_map' + Type: SHT_LLVM_FUNC_MAP + Entries: [] + Content: [[CONTENT=<none>]] + Size: [[SIZE=<none>]] + +## Check that yaml2obj generates a warning when we use unsupported versions. +# RUN: yaml2obj --docnum=3 %s 2>&1 | FileCheck %s --check-prefix=INVALID-VERSION + +# INVALID-VERSION: warning: unsupported SHT_LLVM_FUNC_MAP version: 2; encoding using the most recent version + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC +Sections: + - Name: '.llvm_func_map' + Type: SHT_LLVM_FUNC_MAP + Entries: +# Specify unsupported version + - Version: 2 diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp index bfca65a..bdc1b20 100644 --- a/llvm/tools/llvm-readobj/ELFDumper.cpp +++ b/llvm/tools/llvm-readobj/ELFDumper.cpp @@ -606,6 +606,7 @@ public: void printVersionDependencySection(const Elf_Shdr *Sec) override; void printCGProfile() override; void printBBAddrMaps(bool PrettyPGOAnalysis) override; + void printFuncMaps() override; void printAddrsig() override; void printNotes() override; void printELFLinkerOptions() override; @@ -717,6 +718,7 @@ public: void printVersionDependencySection(const Elf_Shdr *Sec) override; void printCGProfile() override; void printBBAddrMaps(bool PrettyPGOAnalysis) override; + void printFuncMaps() override; void printAddrsig() override; void printNotes() override; void printELFLinkerOptions() override; @@ -5199,6 +5201,10 @@ void GNUELFDumper<ELFT>::printBBAddrMaps(bool /*PrettyPGOAnalysis*/) { OS << "GNUStyle::printBBAddrMaps not implemented\n"; } +template <class ELFT> void GNUELFDumper<ELFT>::printFuncMaps() { + OS << "GNUStyle::printFuncMaps not implemented\n"; +} + static Expected<std::vector<uint64_t>> toULEB128Array(ArrayRef<uint8_t> Data) { std::vector<uint64_t> Ret; const uint8_t *Cur = Data.begin(); @@ -7895,6 +7901,59 @@ void LLVMELFDumper<ELFT>::printBBAddrMaps(bool PrettyPGOAnalysis) { } } +template <class ELFT> void LLVMELFDumper<ELFT>::printFuncMaps() { + bool IsRelocatable = this->Obj.getHeader().e_type == ELF::ET_REL; + using Elf_Shdr = typename ELFT::Shdr; + auto IsMatch = [](const Elf_Shdr &Sec) -> bool { + return Sec.sh_type == ELF::SHT_LLVM_FUNC_MAP; + }; + Expected<MapVector<const Elf_Shdr *, const Elf_Shdr *>> SecRelocMapOrErr = + this->Obj.getSectionAndRelocations(IsMatch); + if (!SecRelocMapOrErr) { + this->reportUniqueWarning("failed to get SHT_LLVM_FUNC_MAP section(s): " + + toString(SecRelocMapOrErr.takeError())); + return; + } + + for (auto const &[Sec, RelocSec] : *SecRelocMapOrErr) { + std::optional<const Elf_Shdr *> FunctionSec; + if (IsRelocatable) + FunctionSec = + unwrapOrError(this->FileName, this->Obj.getSection(Sec->sh_link)); + ListScope L(W, "FuncMap"); + if (IsRelocatable && !RelocSec) { + this->reportUniqueWarning("unable to get relocation section for " + + this->describe(*Sec)); + continue; + } + Expected<std::vector<FuncMap>> FuncMapOrErr = + this->Obj.decodeFuncMap(*Sec, RelocSec); + if (!FuncMapOrErr) { + this->reportUniqueWarning("unable to dump " + this->describe(*Sec) + + ": " + toString(FuncMapOrErr.takeError())); + continue; + } + for (const auto &AM : *FuncMapOrErr) { + DictScope D(W, "Function"); + W.printHex("At", AM.getFunctionAddress()); + SmallVector<uint32_t> FuncSymIndex = + this->getSymbolIndexesForFunctionAddress(AM.getFunctionAddress(), + FunctionSec); + std::string FuncName = "<?>"; + if (FuncSymIndex.empty()) + this->reportUniqueWarning( + "could not identify function symbol for address (0x" + + Twine::utohexstr(AM.getFunctionAddress()) + ") in " + + this->describe(*Sec)); + else + FuncName = this->getStaticSymbolName(FuncSymIndex.front()); + + W.printString("Name", FuncName); + W.printNumber("DynamicInstCount", AM.DynamicInstCount); + } + } +} + template <class ELFT> void LLVMELFDumper<ELFT>::printAddrsig() { ListScope L(W, "Addrsig"); if (!this->DotAddrsigSec) diff --git a/llvm/tools/llvm-readobj/ObjDumper.h b/llvm/tools/llvm-readobj/ObjDumper.h index cd744e3..2c2341e 100644 --- a/llvm/tools/llvm-readobj/ObjDumper.h +++ b/llvm/tools/llvm-readobj/ObjDumper.h @@ -132,6 +132,7 @@ public: // If PrettyPGOAnalysis is true, prints BFI as relative frequency and BPI as // percentage. Otherwise raw values are displayed. virtual void printBBAddrMaps(bool PrettyPGOAnalysis) {} + virtual void printFuncMaps() {} virtual void printAddrsig() {} virtual void printNotes() {} virtual void printELFLinkerOptions() {} diff --git a/llvm/tools/llvm-readobj/Opts.td b/llvm/tools/llvm-readobj/Opts.td index 7d574d8..17c65a6 100644 --- a/llvm/tools/llvm-readobj/Opts.td +++ b/llvm/tools/llvm-readobj/Opts.td @@ -19,6 +19,7 @@ def all : FF<"all", "Equivalent to setting: --file-header, --program-headers, -- "--section-groups and --histogram">; 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 func_map : FF<"func-map", "Display the function address map section">; def pretty_pgo_analysis_map : FF<"pretty-pgo-analysis-map", "Display PGO analysis values with formatting rather than raw numbers">; 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">; diff --git a/llvm/tools/llvm-readobj/llvm-readobj.cpp b/llvm/tools/llvm-readobj/llvm-readobj.cpp index 2f77e5d..91465a6 100644 --- a/llvm/tools/llvm-readobj/llvm-readobj.cpp +++ b/llvm/tools/llvm-readobj/llvm-readobj.cpp @@ -98,6 +98,7 @@ static bool All; static bool ArchSpecificInfo; static bool BBAddrMap; static bool PrettyPGOAnalysisMap; +static bool FuncMap; bool ExpandRelocs; static bool CGProfile; static bool Decompress; @@ -220,6 +221,7 @@ static void parseOptions(const opt::InputArgList &Args) { << "--bb-addr-map must be enabled for --pretty-pgo-analysis-map to " "have an effect\n"; opts::CGProfile = Args.hasArg(OPT_cg_profile); + opts::FuncMap = Args.hasArg(OPT_func_map); opts::Decompress = Args.hasArg(OPT_decompress); opts::Demangle = Args.hasFlag(OPT_demangle, OPT_no_demangle, false); opts::DependentLibraries = Args.hasArg(OPT_dependent_libraries); @@ -473,6 +475,8 @@ static void dumpObject(ObjectFile &Obj, ScopedPrinter &Writer, Dumper->printCGProfile(); if (opts::BBAddrMap) Dumper->printBBAddrMaps(opts::PrettyPGOAnalysisMap); + if (opts::FuncMap) + Dumper->printFuncMaps(); if (opts::Addrsig) Dumper->printAddrsig(); if (opts::Notes) diff --git a/llvm/tools/obj2yaml/elf2yaml.cpp b/llvm/tools/obj2yaml/elf2yaml.cpp index b1c8032..0c7e6a4 100644 --- a/llvm/tools/obj2yaml/elf2yaml.cpp +++ b/llvm/tools/obj2yaml/elf2yaml.cpp @@ -97,6 +97,7 @@ class ELFDumper { dumpStackSizesSection(const Elf_Shdr *Shdr); Expected<ELFYAML::BBAddrMapSection *> dumpBBAddrMapSection(const Elf_Shdr *Shdr); + Expected<ELFYAML::FuncMapSection *> dumpFuncMapSection(const Elf_Shdr *Shdr); Expected<ELFYAML::RawContentSection *> dumpPlaceholderSection(const Elf_Shdr *Shdr); @@ -629,6 +630,8 @@ ELFDumper<ELFT>::dumpSections() { [this](const Elf_Shdr *S) { return dumpCallGraphProfileSection(S); }; case ELF::SHT_LLVM_BB_ADDR_MAP: return [this](const Elf_Shdr *S) { return dumpBBAddrMapSection(S); }; + case ELF::SHT_LLVM_FUNC_MAP: + return [this](const Elf_Shdr *S) { return dumpFuncMapSection(S); }; case ELF::SHT_STRTAB: case ELF::SHT_SYMTAB: case ELF::SHT_DYNSYM: @@ -990,6 +993,51 @@ ELFDumper<ELFT>::dumpBBAddrMapSection(const Elf_Shdr *Shdr) { } template <class ELFT> +Expected<ELFYAML::FuncMapSection *> +ELFDumper<ELFT>::dumpFuncMapSection(const Elf_Shdr *Shdr) { + auto S = std::make_unique<ELFYAML::FuncMapSection>(); + if (Error E = dumpCommonSection(Shdr, *S)) + return std::move(E); + + auto ContentOrErr = Obj.getSectionContents(*Shdr); + if (!ContentOrErr) + return ContentOrErr.takeError(); + + ArrayRef<uint8_t> Content = *ContentOrErr; + if (Content.empty()) + return S.release(); + + DataExtractor Data(Content, Obj.isLE(), ELFT::Is64Bits ? 8 : 4); + + std::vector<ELFYAML::FuncMapEntry> Entries; + DataExtractor::Cursor Cur(0); + uint8_t Version = 0; + uint64_t Address = 0; + while (Cur && Cur.tell() < Content.size()) { + if (Shdr->sh_type == ELF::SHT_LLVM_FUNC_MAP) { + Version = Data.getU8(Cur); + if (Cur && Version > 1) + return createStringError(errc::invalid_argument, + "invalid SHT_LLVM_FUNC_MAP section version: " + + Twine(static_cast<int>(Version))); + } + Address = Data.getAddress(Cur); + uint64_t DynamicInstCount = Data.getULEB128(Cur); + Entries.push_back({Version, Address, DynamicInstCount}); + } + + if (!Cur) { + // If the section cannot be decoded, we dump it as an array of bytes. + consumeError(Cur.takeError()); + S->Content = yaml::BinaryRef(Content); + } else { + S->Entries = std::move(Entries); + } + + return S.release(); +} + +template <class ELFT> Expected<ELFYAML::AddrsigSection *> ELFDumper<ELFT>::dumpAddrsigSection(const Elf_Shdr *Shdr) { auto S = std::make_unique<ELFYAML::AddrsigSection>(); |