diff --git a/llvm/include/llvm/Object/ELF.h b/llvm/include/llvm/Object/ELF.h index 3aa1d7864fcb7..a688672a3e519 100644 --- a/llvm/include/llvm/Object/ELF.h +++ b/llvm/include/llvm/Object/ELF.h @@ -513,6 +513,13 @@ class ELFFile { decodeBBAddrMap(const Elf_Shdr &Sec, const Elf_Shdr *RelaSec = nullptr, std::vector *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> + 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/lib/Object/ELF.cpp b/llvm/lib/Object/ELF.cpp index 41c3fb4cc5e40..ed2428cc5e149 100644 --- a/llvm/lib/Object/ELF.cpp +++ b/llvm/lib/Object/ELF.cpp @@ -940,6 +940,92 @@ ELFFile::decodeBBAddrMap(const Elf_Shdr &Sec, const Elf_Shdr *RelaSec, return std::move(AddrMapsOrErr); } +template +Expected> +ELFFile::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_MAP section) to the + // addend (the location of the function in the text section). + llvm::DenseMap 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::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::Elf_Rela Rela : *Relas) + FunctionOffsetTranslations[Rela.r_offset] = Rela.r_addend; + } + auto GetAddressForRelocation = + [&](unsigned RelocationOffsetInSection) -> Expected { + 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> ContentsOrErr = this->getSectionContents(Sec); + if (!ContentsOrErr) + return ContentsOrErr.takeError(); + ArrayRef Content = *ContentsOrErr; + DataExtractor Data(Content, this->isLE(), ELFT::Is64Bits ? 8 : 4); + std::vector FunctionEntries; + + DataExtractor::Cursor Cur(0); + + // Helper lampda to extract the (possiblly relocatable) address stored at Cur. + auto ExtractAddress = [&]() -> Expected::uintX_t> { + uint64_t RelocationOffsetInSection = Cur.tell(); + auto Address = + static_cast::uintX_t>(Data.getAddress(Cur)); + if (!Cur) + return Cur.takeError(); + if (!IsRelocatable) + return Address; + assert(Address == 0); + Expected AddressOrErr = + GetAddressForRelocation(RelocationOffsetInSection); + if (!AddressOrErr) + return AddressOrErr.takeError(); + return *AddressOrErr; + }; + + uint8_t Version = 0; + while (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(Version))); + } + typename ELFFile::uintX_t FunctionAddress = 0; + auto AddressOrErr = ExtractAddress(); + if (!AddressOrErr) + return AddressOrErr.takeError(); + FunctionAddress = *AddressOrErr; + uint64_t DynamicInstCount = Data.getU64(Cur); + if (!Cur) + break; + FunctionEntries.push_back({FunctionAddress, DynamicInstCount}); + } + + if (!Cur) + return Cur.takeError(); + return FunctionEntries; +} + template Expected< MapVector> 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 0000000000000..9677f9f67ce10 --- /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=]] + 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/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp index 7806eea6a0c52..fcd9842fd1cac 100644 --- a/llvm/tools/llvm-readobj/ELFDumper.cpp +++ b/llvm/tools/llvm-readobj/ELFDumper.cpp @@ -606,6 +606,7 @@ template class GNUELFDumper : public ELFDumper { 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 @@ template class LLVMELFDumper : public ELFDumper { 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::printBBAddrMaps(bool /*PrettyPGOAnalysis*/) { OS << "GNUStyle::printBBAddrMaps not implemented\n"; } +template void GNUELFDumper::printFuncMaps() { + OS << "GNUStyle::printFuncMaps not implemented\n"; +} + static Expected> toULEB128Array(ArrayRef Data) { std::vector Ret; const uint8_t *Cur = Data.begin(); @@ -7922,6 +7928,59 @@ void LLVMELFDumper::printBBAddrMaps(bool PrettyPGOAnalysis) { } } +template void LLVMELFDumper::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> 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 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> 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 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 void LLVMELFDumper::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 cd744e3bbfb71..2c2341e046815 100644 --- a/llvm/tools/llvm-readobj/ObjDumper.h +++ b/llvm/tools/llvm-readobj/ObjDumper.h @@ -132,6 +132,7 @@ class ObjDumper { // 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 7d574d875d22e..17c65a6feb789 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 2f77e5d350553..91465a631cba3 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)