diff --git a/bolt/include/bolt/Core/BinaryContext.h b/bolt/include/bolt/Core/BinaryContext.h index 082f1cec34d52..8960b1984745f 100644 --- a/bolt/include/bolt/Core/BinaryContext.h +++ b/bolt/include/bolt/Core/BinaryContext.h @@ -190,6 +190,9 @@ class BinaryContext { /// Unique build ID if available for the binary. std::optional FileBuildID; + /// GNU property note indicating AArch64 BTI. + bool UsesBTI{false}; + /// Set of all sections. struct CompareSections { bool operator()(const BinarySection *A, const BinarySection *B) const { @@ -384,6 +387,9 @@ class BinaryContext { } void setFileBuildID(StringRef ID) { FileBuildID = std::string(ID); } + bool usesBTI() const { return UsesBTI; } + void setUsesBTI(bool Value) { UsesBTI = Value; } + bool hasSymbolsWithFileName() const { return HasSymbolsWithFileName; } void setHasSymbolsWithFileName(bool Value) { HasSymbolsWithFileName = Value; } diff --git a/bolt/include/bolt/Rewrite/MetadataRewriters.h b/bolt/include/bolt/Rewrite/MetadataRewriters.h index b71bd6cad2505..2c09c879b9128 100644 --- a/bolt/include/bolt/Rewrite/MetadataRewriters.h +++ b/bolt/include/bolt/Rewrite/MetadataRewriters.h @@ -27,6 +27,8 @@ std::unique_ptr createPseudoProbeRewriter(BinaryContext &); std::unique_ptr createSDTRewriter(BinaryContext &); +std::unique_ptr createGNUPropertyRewriter(BinaryContext &); + } // namespace bolt } // namespace llvm diff --git a/bolt/lib/Rewrite/CMakeLists.txt b/bolt/lib/Rewrite/CMakeLists.txt index 775036063dd5a..5b15edcacb482 100644 --- a/bolt/lib/Rewrite/CMakeLists.txt +++ b/bolt/lib/Rewrite/CMakeLists.txt @@ -25,6 +25,7 @@ add_llvm_library(LLVMBOLTRewrite PseudoProbeRewriter.cpp RewriteInstance.cpp SDTRewriter.cpp + GNUPropertyRewriter.cpp NO_EXPORT DISABLE_LLVM_LINK_LLVM_DYLIB diff --git a/bolt/lib/Rewrite/GNUPropertyRewriter.cpp b/bolt/lib/Rewrite/GNUPropertyRewriter.cpp new file mode 100644 index 0000000000000..f61c08ec46fe6 --- /dev/null +++ b/bolt/lib/Rewrite/GNUPropertyRewriter.cpp @@ -0,0 +1,147 @@ +//===- bolt/Rewrite/GNUPropertyRewriter.cpp -------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Read the .note.gnu.property section. +// +//===----------------------------------------------------------------------===// + +#include "bolt/Rewrite/MetadataRewriter.h" +#include "bolt/Rewrite/MetadataRewriters.h" +#include "llvm/Support/Errc.h" + +using namespace llvm; +using namespace bolt; + +namespace { + +class GNUPropertyRewriter final : public MetadataRewriter { + + Expected decodeGNUPropertyNote(StringRef Desc); + +public: + GNUPropertyRewriter(StringRef Name, BinaryContext &BC) + : MetadataRewriter(Name, BC) {} + + Error sectionInitializer() override; +}; + +Error GNUPropertyRewriter::sectionInitializer() { + + ErrorOr Sec = + BC.getUniqueSectionByName(".note.gnu.property"); + if (!Sec) + return Error::success(); + + // Accumulate feature bits + uint32_t FeaturesAcc = 0; + + StringRef Buf = Sec->getContents(); + DataExtractor DE(Buf, BC.AsmInfo->isLittleEndian(), + BC.AsmInfo->getCodePointerSize()); + DataExtractor::Cursor Cursor(0); + while (Cursor && !DE.eof(Cursor)) { + const uint32_t NameSz = DE.getU32(Cursor); + const uint32_t DescSz = DE.getU32(Cursor); + const uint32_t Type = DE.getU32(Cursor); + + StringRef Name = + NameSz ? Buf.slice(Cursor.tell(), Cursor.tell() + NameSz) : ""; + Cursor.seek(alignTo(Cursor.tell() + NameSz, 4)); + + const uint64_t DescOffset = Cursor.tell(); + StringRef Desc = + DescSz ? Buf.slice(DescOffset, DescOffset + DescSz) : ""; + Cursor.seek(alignTo(DescOffset + DescSz, 4)); + if (!Cursor) + return createStringError( + errc::executable_format_error, + "out of bounds while reading .note.gnu.property section: %s", + toString(Cursor.takeError()).c_str()); + + if (Type == ELF::NT_GNU_PROPERTY_TYPE_0 && Name.starts_with("GNU") && + DescSz) { + auto Features = decodeGNUPropertyNote(Desc); + if (!Features) + return Features.takeError(); + FeaturesAcc |= *Features; + } + } + + if (BC.isAArch64()) { + BC.setUsesBTI(FeaturesAcc & llvm::ELF::GNU_PROPERTY_AARCH64_FEATURE_1_BTI); + if (BC.usesBTI()) + BC.outs() << "BOLT-WARNING: binary is using BTI. Optimized binary may be " + "corrupted\n"; + } + + return Error::success(); +} + +/// \p Desc contains an array of property descriptors. Each member has the +/// following structure: +/// typedef struct { +/// Elf_Word pr_type; +/// Elf_Word pr_datasz; +/// unsigned char pr_data[PR_DATASZ]; +/// unsigned char pr_padding[PR_PADDING]; +/// } Elf_Prop; +/// +/// As there is no guarantee that the features are encoded in which element of +/// the array, we have to read all, and OR together the result. +Expected GNUPropertyRewriter::decodeGNUPropertyNote(StringRef Desc) { + DataExtractor DE(Desc, BC.AsmInfo->isLittleEndian(), + BC.AsmInfo->getCodePointerSize()); + DataExtractor::Cursor Cursor(0); + const uint32_t Align = DE.getAddressSize(); + + std::optional Features = 0; + while (Cursor && !DE.eof(Cursor)) { + const uint32_t PrType = DE.getU32(Cursor); + const uint32_t PrDataSz = DE.getU32(Cursor); + + const uint64_t PrDataStart = Cursor.tell(); + const uint64_t PrDataEnd = PrDataStart + PrDataSz; + Cursor.seek(PrDataEnd); + if (!Cursor) + return createStringError( + errc::executable_format_error, + "out of bounds while reading .note.gnu.property section: %s", + toString(Cursor.takeError()).c_str()); + + if (PrType == llvm::ELF::GNU_PROPERTY_AARCH64_FEATURE_1_AND) { + if (PrDataSz != 4) { + return createStringError( + errc::executable_format_error, + "Property descriptor size has to be 4 bytes on AArch64\n"); + } + DataExtractor::Cursor Tmp(PrDataStart); + // PrDataSz = 4 -> PrData is uint32_t + const uint32_t FeaturesItem = DE.getU32(Tmp); + if (!Tmp) + return createStringError( + errc::executable_format_error, + "failed to read property from .note.gnu.property section: %s", + toString(Tmp.takeError()).c_str()); + Features = Features ? (*Features | FeaturesItem) : FeaturesItem; + } + + Cursor.seek(alignTo(PrDataEnd, Align)); + if (!Cursor) + return createStringError(errc::executable_format_error, + "out of bounds while reading property array in " + ".note.gnu.property section: %s", + toString(Cursor.takeError()).c_str()); + } + return Features.value_or(0u); +} +} // namespace + +std::unique_ptr +llvm::bolt::createGNUPropertyRewriter(BinaryContext &BC) { + return std::make_unique("gnu-property-rewriter", BC); +} diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp index 8b78c53aa99b3..908dfd4fe626a 100644 --- a/bolt/lib/Rewrite/RewriteInstance.cpp +++ b/bolt/lib/Rewrite/RewriteInstance.cpp @@ -3341,6 +3341,8 @@ void RewriteInstance::initializeMetadataManager() { MetadataManager.registerRewriter(createPseudoProbeRewriter(*BC)); MetadataManager.registerRewriter(createSDTRewriter(*BC)); + + MetadataManager.registerRewriter(createGNUPropertyRewriter(*BC)); } void RewriteInstance::processSectionMetadata() { diff --git a/bolt/test/AArch64/Inputs/property-note-bti.yaml b/bolt/test/AArch64/Inputs/property-note-bti.yaml new file mode 100644 index 0000000000000..541ae92c92f6f --- /dev/null +++ b/bolt/test/AArch64/Inputs/property-note-bti.yaml @@ -0,0 +1,50 @@ +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_AARCH64 + Entry: 0x400510 +ProgramHeaders: + - Type: PT_NOTE + Flags: [ PF_R ] + FirstSec: .note.gnu.property + LastSec: .note.gnu.property + VAddr: 0x400338 + Align: 0x8 + - Type: PT_LOAD + Flags: [ PF_R ] + VAddr: 0x0 + Align: 0x10000 + FileSize: 0xf8 + MemSize: 0xf8 + Offset: 0x0 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x2a0000 + AddressAlign: 0x4 + Content: 400580d2c0035fd6 + - Name: .note.gnu.property + Type: SHT_NOTE + Flags: [ SHF_ALLOC ] + Address: 0x400338 + AddressAlign: 0x8 + Notes: + - Name: GNU + Desc: 000000C0040000000300000000000000 + Type: NT_GNU_PROPERTY_TYPE_0 + - Type: SectionHeaderTable + Sections: + - Name: .note.gnu.property + - Name: .symtab + - Name: .strtab + - Name: .shstrtab + - Name: .text +Symbols: + - Name: .note.gnu.property + Type: STT_SECTION + Section: .note.gnu.property + Value: 0x400338 +... diff --git a/bolt/test/AArch64/Inputs/property-note-nobti.yaml b/bolt/test/AArch64/Inputs/property-note-nobti.yaml new file mode 100644 index 0000000000000..a041a586d7804 --- /dev/null +++ b/bolt/test/AArch64/Inputs/property-note-nobti.yaml @@ -0,0 +1,50 @@ +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_AARCH64 + Entry: 0x400510 +ProgramHeaders: + - Type: PT_NOTE + Flags: [ PF_R ] + FirstSec: .note.gnu.property + LastSec: .note.gnu.property + VAddr: 0x400338 + Align: 0x8 + - Type: PT_LOAD + Flags: [ PF_R ] + VAddr: 0x0 + Align: 0x10000 + FileSize: 0xf8 + MemSize: 0xf8 + Offset: 0x0 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x2a0000 + AddressAlign: 0x4 + Content: 400580d2c0035fd6 + - Name: .note.gnu.property + Type: SHT_NOTE + Flags: [ SHF_ALLOC ] + Address: 0x400338 + AddressAlign: 0x8 + Notes: + - Name: GNU + Desc: 000000C0040000000200000000000000 + Type: NT_GNU_PROPERTY_TYPE_0 + - Type: SectionHeaderTable + Sections: + - Name: .note.gnu.property + - Name: .symtab + - Name: .strtab + - Name: .shstrtab + - Name: .text +Symbols: + - Name: .note.gnu.property + Type: STT_SECTION + Section: .note.gnu.property + Value: 0x400338 +... diff --git a/bolt/test/AArch64/bti-note.test b/bolt/test/AArch64/bti-note.test new file mode 100644 index 0000000000000..1ec9d774b3271 --- /dev/null +++ b/bolt/test/AArch64/bti-note.test @@ -0,0 +1,10 @@ +// This test checks that the GNUPropertyRewriter can decode the BTI feature flag. +// It decodes an executable with BTI, and checks for the warning. + +RUN: yaml2obj %p/Inputs/property-note-bti.yaml &> %t.exe + +RUN: llvm-readelf -n %t.exe | FileCheck %s +CHECK: BTI + +RUN: llvm-bolt %t.exe -o %t.exe.bolt | FileCheck %s -check-prefix=CHECK-BOLT +CHECK-BOLT: BOLT-WARNING: binary is using BTI. Optimized binary may be corrupted diff --git a/bolt/test/AArch64/no-bti-note.test b/bolt/test/AArch64/no-bti-note.test new file mode 100644 index 0000000000000..28cce345deaab --- /dev/null +++ b/bolt/test/AArch64/no-bti-note.test @@ -0,0 +1,10 @@ +// This test checks that the GNUPropertyRewriter can decode the BTI feature flag. +// It decodes an executable without BTI, and checks for the warning. + +RUN: yaml2obj %p/Inputs/property-note-nobti.yaml &> %t.exe + +RUN: llvm-readelf -n %t.exe | FileCheck %s +CHECK-NOT: BTI + +RUN: llvm-bolt %t.exe -o %t.exe.bolt | FileCheck %s -check-prefix=CHECK-BOLT +CHECK-BOLT-NOT: BOLT-WARNING: binary is using BTI. Optimized binary may be corrupted