diff --git a/llvm/docs/CommandGuide/llvm-readelf.rst b/llvm/docs/CommandGuide/llvm-readelf.rst --- a/llvm/docs/CommandGuide/llvm-readelf.rst +++ b/llvm/docs/CommandGuide/llvm-readelf.rst @@ -32,6 +32,11 @@ Display architecture-specific information, e.g. the ARM attributes section on ARM. +.. option:: --bb-addr-map + + Display the contents of the basic block address map section(s), which contain the + address of each function, along with the relative offset of each basic block. + .. option:: --color Use colors in the output for warnings and errors. diff --git a/llvm/docs/CommandGuide/llvm-readobj.rst b/llvm/docs/CommandGuide/llvm-readobj.rst --- a/llvm/docs/CommandGuide/llvm-readobj.rst +++ b/llvm/docs/CommandGuide/llvm-readobj.rst @@ -148,6 +148,11 @@ Display architecture-specific information, e.g. the ARM attributes section on ARM. +.. option:: --bb-addr-map + + Display the contents of the basic block address map section(s), which contain the + address of each function, along with the relative offset of each basic block. + .. option:: --demangle, -C Display demangled symbol names in the output. diff --git a/llvm/include/llvm/Object/ELF.h b/llvm/include/llvm/Object/ELF.h --- a/llvm/include/llvm/Object/ELF.h +++ b/llvm/include/llvm/Object/ELF.h @@ -392,6 +392,8 @@ Expected> getSectionContentsAsArray(const Elf_Shdr &Sec) const; Expected> getSectionContents(const Elf_Shdr &Sec) const; Expected> getSegmentContents(const Elf_Phdr &Phdr) const; + Expected> + decodeBBAddrMap(const Elf_Shdr &Sec) const; }; using ELF32LEFile = ELFFile; diff --git a/llvm/include/llvm/Object/ELFTypes.h b/llvm/include/llvm/Object/ELFTypes.h --- a/llvm/include/llvm/Object/ELFTypes.h +++ b/llvm/include/llvm/Object/ELFTypes.h @@ -43,6 +43,7 @@ template class Elf_Note_Impl; template class Elf_Note_Iterator_Impl; template struct Elf_CGProfile_Impl; +template struct Elf_BBAddrMap_Impl; template struct ELFType { private: @@ -74,6 +75,7 @@ using Note = Elf_Note_Impl>; using NoteIterator = Elf_Note_Iterator_Impl>; using CGProfile = Elf_CGProfile_Impl>; + using BBAddrMap = Elf_BBAddrMap_Impl>; using DynRange = ArrayRef; using ShdrRange = ArrayRef; using SymRange = ArrayRef; @@ -128,13 +130,14 @@ using Elf_Note = typename ELFT::Note; \ using Elf_Note_Iterator = typename ELFT::NoteIterator; \ using Elf_CGProfile = typename ELFT::CGProfile; \ + using Elf_BBAddrMap = typename ELFT::BBAddrMap; \ using Elf_Dyn_Range = typename ELFT::DynRange; \ using Elf_Shdr_Range = typename ELFT::ShdrRange; \ using Elf_Sym_Range = typename ELFT::SymRange; \ using Elf_Rel_Range = typename ELFT::RelRange; \ using Elf_Rela_Range = typename ELFT::RelaRange; \ using Elf_Relr_Range = typename ELFT::RelrRange; \ - using Elf_Phdr_Range = typename ELFT::PhdrRange; \ + using Elf_Phdr_Range = typename ELFT::PhdrRange; #define LLVM_ELF_COMMA , #define LLVM_ELF_IMPORT_TYPES(E, W) \ @@ -788,6 +791,28 @@ Elf_Word flags2; // General flags }; +// Struct representing the BBAddrMap for one function. +template struct Elf_BBAddrMap_Impl { + LLVM_ELF_IMPORT_TYPES_ELFT(ELFT) + uintX_t Addr; // Function address + // Struct representing the BBAddrMap information for one basic block. + struct BBEntry { + uint32_t Offset; // Offset of basic block relative to function start. + uint32_t Size; // Size of the basic block. + + // The following fields are decoded from the Metadata field. The encoding + // happens in AsmPrinter.cpp:getBBAddrMapMetadata. + bool HasReturn; // If this block ends with a return (or tail call). + bool HasTailCall; // If this block ends with a tail call. + bool IsEHPad; // If this is an exception handling block. + + BBEntry(uint32_t Offset, uint32_t Size, uint32_t Metadata) + : Offset(Offset), Size(Size), HasReturn(Metadata & 1), + HasTailCall(Metadata & (1 << 1)), IsEHPad(Metadata & (1 << 2)){}; + }; + std::vector BBEntries; // Basic block entries for this function. +}; + } // end namespace object. } // end namespace llvm. diff --git a/llvm/include/llvm/ObjectYAML/ELFYAML.h b/llvm/include/llvm/ObjectYAML/ELFYAML.h --- a/llvm/include/llvm/ObjectYAML/ELFYAML.h +++ b/llvm/include/llvm/ObjectYAML/ELFYAML.h @@ -161,7 +161,7 @@ llvm::yaml::Hex64 Metadata; }; llvm::yaml::Hex64 Address; - Optional NumBlocks; + Optional NumBlocks; Optional> BBEntries; }; diff --git a/llvm/lib/Object/ELF.cpp b/llvm/lib/Object/ELF.cpp --- a/llvm/lib/Object/ELF.cpp +++ b/llvm/lib/Object/ELF.cpp @@ -612,6 +612,58 @@ return base() + Offset; } +template +Expected> +ELFFile::decodeBBAddrMap(const Elf_Shdr &Sec) const { + Expected> ContentsOrErr = getSectionContents(Sec); + if (!ContentsOrErr) + return ContentsOrErr.takeError(); + ArrayRef Content = *ContentsOrErr; + DataExtractor Data(Content, isLE(), ELFT::Is64Bits ? 8 : 4); + std::vector FunctionEntries; + + DataExtractor::Cursor Cur(0); + Error ULEBSizeErr = Error::success(); + + // Helper to extract and decode the next ULEB128 value as uint32_t. + // Returns zero and sets ULEBSizeErr if the ULEB128 value exceeds the uint32_t + // limit. + // Also returns zero if ULEBSizeErr is already in an error state. + auto ReadULEB128AsUInt32 = [&Data, &Cur, &ULEBSizeErr]() -> uint32_t { + // Bail out and do not extract data if ULEBSizeErr is already set. + if (ULEBSizeErr) + return 0; + uint64_t Offset = Cur.tell(); + uint64_t Value = Data.getULEB128(Cur); + if (Value > UINT32_MAX) { + ULEBSizeErr = createError( + "ULEB128 value at offset 0x" + Twine::utohexstr(Offset) + + " exceeds UINT32_MAX (0x" + Twine::utohexstr(Value) + ")"); + return 0; + } + return static_cast(Value); + }; + + while (!ULEBSizeErr && Cur && Cur.tell() < Content.size()) { + uintX_t Address = static_cast(Data.getAddress(Cur)); + uint32_t NumBlocks = ReadULEB128AsUInt32(); + std::vector BBEntries; + for (uint32_t BlockID = 0; !ULEBSizeErr && Cur && (BlockID < NumBlocks); + ++BlockID) { + uint32_t Offset = ReadULEB128AsUInt32(); + uint32_t Size = ReadULEB128AsUInt32(); + uint32_t Metadata = ReadULEB128AsUInt32(); + BBEntries.push_back({Offset, Size, Metadata}); + } + FunctionEntries.push_back({Address, BBEntries}); + } + // Either Cur is in the error state, or ULEBSizeError is set (not both), but + // we join the two errors here to be safe. + if (!Cur || ULEBSizeErr) + return joinErrors(Cur.takeError(), std::move(ULEBSizeErr)); + return FunctionEntries; +} + template class llvm::object::ELFFile; template class llvm::object::ELFFile; template class llvm::object::ELFFile; diff --git a/llvm/test/tools/llvm-readobj/ELF/bb-addr-map.test b/llvm/test/tools/llvm-readobj/ELF/bb-addr-map.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-readobj/ELF/bb-addr-map.test @@ -0,0 +1,119 @@ +## This test checks how we handle the --bb-addr-map option. + +## Check 64-bit: +# RUN: yaml2obj %s -DBITS=64 -DADDR=0xFFFFFFFF1 -o %t1.x64.o +# RUN: llvm-readobj %t1.x64.o --bb-addr-map | FileCheck %s -DADDR=0xFFFFFFFF1 --check-prefix=LLVM +# RUN: llvm-readelf %t1.x64.o --bb-addr-map | FileCheck %s --check-prefix=GNU + +## Check 32-bit: +# RUN: yaml2obj %s -DBITS=32 -o %t1.x32.o +# RUN: llvm-readobj %t1.x32.o --bb-addr-map | FileCheck -DADDR=0x11111 %s --check-prefix=LLVM +# RUN: llvm-readelf %t1.x32.o --bb-addr-map | FileCheck %s --check-prefix=GNU + +## Check that a malformed section can be handled. +# RUN: yaml2obj %s -DBITS=32 -DSIZE=4 -o %t2.o +# RUN: llvm-readobj %t2.o --bb-addr-map 2>&1 | FileCheck %s -DOFFSET=0x00000004 -DFILE=%t2.o --check-prefix=TRUNCATED + +# LLVM: BBAddrMap [ +# LLVM-NEXT: Function { +# LLVM-NEXT: At: [[ADDR]] +# LLVM-NEXT: BB entries [ +# LLVM-NEXT: { +# LLVM-NEXT: Offset: 0x0 +# LLVM-NEXT: Size: 0x1 +# LLVM-NEXT: HasReturn: No +# LLVM-NEXT: HasTailCall: Yes +# LLVM-NEXT: IsEHPad: No +# LLVM-NEXT: } +# LLVM-NEXT: { +# LLVM-NEXT: Offset: 0x3 +# LLVM-NEXT: Size: 0x4 +# LLVM-NEXT: HasReturn: Yes +# LLVM-NEXT: HasTailCall: No +# LLVM-NEXT: IsEHPad: Yes +# LLVM-NEXT: } +# LLVM-NEXT: ] +# LLVM-NEXT: } +# LLVM-NEXT: Function { +# LLVM-NEXT: At: 0x22222 +# LLVM-NEXT: BB entries [ +# LLVM-NEXT: { +# LLVM-NEXT: Offset: 0x6 +# LLVM-NEXT: Size: 0x7 +# LLVM-NEXT: HasReturn: No +# LLVM-NEXT: HasTailCall: No +# LLVM-NEXT: IsEHPad: No +# LLVM-NEXT: } +# LLVM-NEXT: ] +# LLVM-NEXT: } +# LLVM-NEXT: ] +# LLVM-NEXT: BBAddrMap [ +# LLVM-NEXT: Function { +# LLVM-NEXT: At: 0x33333 +# LLVM-NEXT: BB entries [ +# LLVM-NEXT: { +# LLVM-NEXT: Offset: 0x9 +# LLVM-NEXT: Size: 0xA +# LLVM-NEXT: HasReturn: Yes +# LLVM-NEXT: HasTailCall: Yes +# LLVM-NEXT: IsEHPad: No +# LLVM-NEXT: } +# LLVM-NEXT: ] +# LLVM-NEXT: } +# LLVM-NEXT: ] + +# GNU: GNUStyle::printBBAddrMaps not implemented + +# TRUNCATED: BBAddrMap [ +# TRUNCATED-NEXT: warning: '[[FILE]]': unable to dump SHT_LLVM_BB_ADDR_MAP section with index 1: unable to decode LEB128 at offset [[OFFSET]]: malformed uleb128, extends past end +# TRUNCATED-NEXT: ] +## Check that the other valid section is properly dumped. +# TRUNCATED-NEXT: BBAddrMap [ +# TRUNCATED-NEXT: Function { +# TRUNCATED-NEXT: At: 0x33333 +# TRUNCATED-NEXT: BB entries [ +# TRUNCATED-NEXT: { +# TRUNCATED-NEXT: Offset: 0x9 +# TRUNCATED-NEXT: Size: 0xA +# TRUNCATED-NEXT: HasReturn: Yes +# TRUNCATED-NEXT: HasTailCall: Yes +# TRUNCATED-NEXT: IsEHPad: No +# TRUNCATED-NEXT: } +# TRUNCATED-NEXT: ] +# TRUNCATED-NEXT: } +# TRUNCATED-NEXT: ] + +--- !ELF +FileHeader: + Class: ELFCLASS[[BITS]] + Data: ELFDATA2LSB + Type: ET_EXEC +Sections: + - Name: bb_addr_map_1 + Type: SHT_LLVM_BB_ADDR_MAP + ShSize: [[SIZE=]] + Entries: + - Address: [[ADDR=0x11111]] + BBEntries: + - AddressOffset: 0x0 + Size: 0x1 + Metadata: 0xF0000002 + - AddressOffset: 0x3 + Size: 0x4 + Metadata: 0x5 + - Address: 0x22222 + BBEntries: + - AddressOffset: 0x6 + Size: 0x7 + Metadata: 0x8 + - Name: dummy_section + Type: SHT_PROGBITS + Size: 16 + - Name: bb_addr_map_2 + Type: SHT_LLVM_BB_ADDR_MAP + Entries: + - Address: 0x33333 + BBEntries: + - AddressOffset: 0x9 + Size: 0xa + Metadata: 0xb diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp --- a/llvm/tools/llvm-readobj/ELFDumper.cpp +++ b/llvm/tools/llvm-readobj/ELFDumper.cpp @@ -550,6 +550,7 @@ void printVersionDependencySection(const Elf_Shdr *Sec) override; void printHashHistograms() override; void printCGProfile() override; + void printBBAddrMaps() override; void printAddrsig() override; void printNotes() override; void printELFLinkerOptions() override; @@ -660,6 +661,7 @@ void printVersionDependencySection(const Elf_Shdr *Sec) override; void printHashHistograms() override; void printCGProfile() override; + void printBBAddrMaps() override; void printAddrsig() override; void printNotes() override; void printELFLinkerOptions() override; @@ -4617,6 +4619,10 @@ OS << "GNUStyle::printCGProfile not implemented\n"; } +template void GNUELFDumper::printBBAddrMaps() { + OS << "GNUStyle::printBBAddrMaps not implemented\n"; +} + static Expected> toULEB128Array(ArrayRef Data) { std::vector Ret; const uint8_t *Cur = Data.begin(); @@ -6520,6 +6526,34 @@ } } +template void LLVMELFDumper::printBBAddrMaps() { + for (const Elf_Shdr &Sec : cantFail(this->Obj.sections())) { + if (Sec.sh_type != SHT_LLVM_BB_ADDR_MAP) + continue; + ListScope L(W, "BBAddrMap"); + Expected> BBAddrMapOrErr = + this->Obj.decodeBBAddrMap(Sec); + if (!BBAddrMapOrErr) { + this->reportUniqueWarning("unable to dump " + this->describe(Sec) + ": " + + toString(BBAddrMapOrErr.takeError())); + continue; + } + for (const Elf_BBAddrMap &AM : *BBAddrMapOrErr) { + DictScope D(W, "Function"); + W.printHex("At", AM.Addr); + ListScope L(W, "BB entries"); + for (const typename Elf_BBAddrMap::BBEntry &BBE : AM.BBEntries) { + DictScope L(W); + W.printHex("Offset", BBE.Offset); + W.printHex("Size", BBE.Size); + W.printBoolean("HasReturn", BBE.HasReturn); + W.printBoolean("HasTailCall", BBE.HasTailCall); + W.printBoolean("IsEHPad", BBE.IsEHPad); + } + } + } +} + 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 --- a/llvm/tools/llvm-readobj/ObjDumper.h +++ b/llvm/tools/llvm-readobj/ObjDumper.h @@ -72,6 +72,7 @@ virtual void printGroupSections() {} virtual void printHashHistograms() {} virtual void printCGProfile() {} + virtual void printBBAddrMaps() {} virtual void printAddrsig() {} virtual void printNotes() {} virtual void printELFLinkerOptions() {} diff --git a/llvm/tools/llvm-readobj/llvm-readobj.cpp b/llvm/tools/llvm-readobj/llvm-readobj.cpp --- a/llvm/tools/llvm-readobj/llvm-readobj.cpp +++ b/llvm/tools/llvm-readobj/llvm-readobj.cpp @@ -367,6 +367,10 @@ cl::alias ELFCGProfile("elf-cg-profile", cl::desc("Alias for --cg-profile"), cl::aliasopt(CGProfile)); + // --bb-addr-map + cl::opt BBAddrMap("bb-addr-map", + cl::desc("Display the BB address map section")); + // -addrsig cl::opt Addrsig("addrsig", cl::desc("Display address-significance table")); @@ -542,6 +546,8 @@ Dumper->printHashHistograms(); if (opts::CGProfile) Dumper->printCGProfile(); + if (opts::BBAddrMap) + Dumper->printBBAddrMaps(); if (opts::Addrsig) Dumper->printAddrsig(); if (opts::Notes) diff --git a/llvm/unittests/Object/ELFObjectFileTest.cpp b/llvm/unittests/Object/ELFObjectFileTest.cpp --- a/llvm/unittests/Object/ELFObjectFileTest.cpp +++ b/llvm/unittests/Object/ELFObjectFileTest.cpp @@ -482,3 +482,111 @@ DoCheck(0xFFFFFFFF, "can't read an entry at 0x17ffffffe8: it goes past the " "end of the section (0x18)"); } + +// Tests for error paths of the ELFFile::decodeBBAddrMap API. +TEST(ELFObjectFileTest, InvalidBBAddrMap) { + StringRef CommonYamlString(R"( +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC +Sections: + - Name: .llvm_bb_addr_map + Type: SHT_LLVM_BB_ADDR_MAP + Entries: + - Address: 0x11111 + BBEntries: + - AddressOffset: 0x0 + Size: 0x1 + Metadata: 0x2 +)"); + + auto DoCheck = [&](StringRef YamlString, const char *ErrMsg) { + SmallString<0> Storage; + Expected> ElfOrErr = + toBinary(Storage, YamlString); + ASSERT_THAT_EXPECTED(ElfOrErr, Succeeded()); + const ELFFile &Elf = ElfOrErr->getELFFile(); + + Expected BBAddrMapSecOrErr = + Elf.getSection(1); + ASSERT_THAT_EXPECTED(BBAddrMapSecOrErr, Succeeded()); + EXPECT_THAT_ERROR(Elf.decodeBBAddrMap(**BBAddrMapSecOrErr).takeError(), + FailedWithMessage(ErrMsg)); + }; + + // Check that we can detect the malformed encoding when the section is + // truncated. + SmallString<128> TruncatedYamlString(CommonYamlString); + TruncatedYamlString += R"( + ShSize: 0x8 +)"; + DoCheck(TruncatedYamlString, "unable to decode LEB128 at offset 0x00000008: " + "malformed uleb128, extends past end"); + + // Check that we can detect when the encoded BB entry fields exceed the UINT32 + // limit. + SmallVector, 3> OverInt32LimitYamlStrings(3, + CommonYamlString); + OverInt32LimitYamlStrings[0] += R"( + - AddressOffset: 0x100000000 + Size: 0xFFFFFFFF + Metadata: 0xFFFFFFFF +)"; + + OverInt32LimitYamlStrings[1] += R"( + - AddressOffset: 0xFFFFFFFF + Size: 0x100000000 + Metadata: 0xFFFFFFFF +)"; + + OverInt32LimitYamlStrings[2] += R"( + - AddressOffset: 0xFFFFFFFF + Size: 0xFFFFFFFF + Metadata: 0x100000000 +)"; + + DoCheck(OverInt32LimitYamlStrings[0], + "ULEB128 value at offset 0xc exceeds UINT32_MAX (0x100000000)"); + DoCheck(OverInt32LimitYamlStrings[1], + "ULEB128 value at offset 0x11 exceeds UINT32_MAX (0x100000000)"); + DoCheck(OverInt32LimitYamlStrings[2], + "ULEB128 value at offset 0x16 exceeds UINT32_MAX (0x100000000)"); + + // Check the proper error handling when the section has fields exceeding + // UINT32 and is also truncated. This is for checking that we don't generate + // unhandled errors. + SmallVector, 3> OverInt32LimitAndTruncated( + 3, OverInt32LimitYamlStrings[1]); + // Truncate before the end of the 5-byte field. + OverInt32LimitAndTruncated[0] += R"( + ShSize: 0x15 +)"; + // Truncate at the end of the 5-byte field. + OverInt32LimitAndTruncated[1] += R"( + ShSize: 0x16 +)"; + // Truncate after the end of the 5-byte field. + OverInt32LimitAndTruncated[2] += R"( + ShSize: 0x17 +)"; + + DoCheck(OverInt32LimitAndTruncated[0], + "unable to decode LEB128 at offset 0x00000011: malformed uleb128, " + "extends past end"); + DoCheck(OverInt32LimitAndTruncated[1], + "ULEB128 value at offset 0x11 exceeds UINT32_MAX (0x100000000)"); + DoCheck(OverInt32LimitAndTruncated[2], + "ULEB128 value at offset 0x11 exceeds UINT32_MAX (0x100000000)"); + + // Check for proper error handling when the 'NumBlocks' field is overridden + // with an out-of-range value. + SmallString<128> OverLimitNumBlocks(CommonYamlString); + OverLimitNumBlocks += R"( + NumBlocks: 0x100000000 +)"; + + DoCheck(OverLimitNumBlocks, + "ULEB128 value at offset 0x8 exceeds UINT32_MAX (0x100000000)"); +}