[llvm] [BOLT] Add GNUPropertyRewriter and warn on AArch64 BTI note (PR #161206)
Gergely Bálint via llvm-commits
llvm-commits at lists.llvm.org
Fri Oct 3 00:31:11 PDT 2025
https://github.com/bgergely0 updated https://github.com/llvm/llvm-project/pull/161206
>From 30e3ccda9a8e0abd2b1db7fc962d4213a8aae99a Mon Sep 17 00:00:00 2001
From: Gergely Balint <gergely.balint at arm.com>
Date: Fri, 26 Sep 2025 16:27:12 +0000
Subject: [PATCH 1/3] [BOLT] Add GNUPropertyRewriter and warn on AArch64 BTI
note
This commit adds the GNUPropertyRewriter, which parses features from the
.gnu.property.note section.
Currently we only read the bit indicating BTI support
(GNU_PROPERTY_AARCH64_FEATURE_1_BTI).
As BOLT does not add BTI landing pads to targets of indirect
branches/calls, we have to emit a warning that the output binary may be
corrupted.
---
bolt/include/bolt/Core/BinaryContext.h | 6 +
bolt/include/bolt/Rewrite/MetadataRewriters.h | 2 +
bolt/lib/Rewrite/CMakeLists.txt | 1 +
bolt/lib/Rewrite/GNUPropertyRewriter.cpp | 147 ++++++++++++++++++
bolt/lib/Rewrite/RewriteInstance.cpp | 2 +
.../AArch64/Inputs/property-note-bti.yaml | 50 ++++++
.../AArch64/Inputs/property-note-nobti.yaml | 50 ++++++
bolt/test/AArch64/bti-note.test | 10 ++
bolt/test/AArch64/no-bti-note.test | 10 ++
9 files changed, 278 insertions(+)
create mode 100644 bolt/lib/Rewrite/GNUPropertyRewriter.cpp
create mode 100644 bolt/test/AArch64/Inputs/property-note-bti.yaml
create mode 100644 bolt/test/AArch64/Inputs/property-note-nobti.yaml
create mode 100644 bolt/test/AArch64/bti-note.test
create mode 100644 bolt/test/AArch64/no-bti-note.test
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<std::string> 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<MetadataRewriter> createPseudoProbeRewriter(BinaryContext &);
std::unique_ptr<MetadataRewriter> createSDTRewriter(BinaryContext &);
+std::unique_ptr<MetadataRewriter> 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..ecef9c61e3e0d
--- /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 .gnu.property.note 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<uint32_t> decodeGNUPropertyNote(StringRef Desc);
+
+public:
+ GNUPropertyRewriter(StringRef Name, BinaryContext &BC)
+ : MetadataRewriter(Name, BC) {}
+
+ Error sectionInitializer() override;
+};
+
+Error GNUPropertyRewriter::sectionInitializer() {
+
+ ErrorOr<BinarySection &> 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) : "<empty>";
+ Cursor.seek(alignTo(Cursor.tell() + NameSz, 4));
+
+ const uint64_t DescOffset = Cursor.tell();
+ StringRef Desc =
+ DescSz ? Buf.slice(DescOffset, DescOffset + DescSz) : "<empty>";
+ Cursor.seek(alignTo(DescOffset + DescSz, 4));
+ if (!Cursor)
+ return createStringError(
+ errc::executable_format_error,
+ "out of bounds while reading .gnu.property.note 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();
+}
+
+/// 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<uint32_t> 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<uint32_t> 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 .gnu.property.note 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,
+ "out of bounds while reading .gnu.property.note 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 .gnu.property.note section: %s",
+ toString(Cursor.takeError()).c_str());
+ }
+ return Features.value_or(0u);
+}
+} // namespace
+
+std::unique_ptr<MetadataRewriter>
+llvm::bolt::createGNUPropertyRewriter(BinaryContext &BC) {
+ return std::make_unique<GNUPropertyRewriter>("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..825456268a247
--- /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.
diff --git a/bolt/test/AArch64/no-bti-note.test b/bolt/test/AArch64/no-bti-note.test
new file mode 100644
index 0000000000000..92ed747ef7ac1
--- /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.
>From 890ea8b5b42b7daf7c0e5e97a406a25280f292e4 Mon Sep 17 00:00:00 2001
From: Gergely Balint <gergely.balint at arm.com>
Date: Wed, 1 Oct 2025 10:41:32 +0000
Subject: [PATCH 2/3] [BOLT] Review changes
- full warning message in lit tests
- \p in comment
- more specific error messages
---
bolt/lib/Rewrite/GNUPropertyRewriter.cpp | 12 ++++++------
bolt/test/AArch64/bti-note.test | 2 +-
bolt/test/AArch64/no-bti-note.test | 2 +-
3 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/bolt/lib/Rewrite/GNUPropertyRewriter.cpp b/bolt/lib/Rewrite/GNUPropertyRewriter.cpp
index ecef9c61e3e0d..32146ac53b492 100644
--- a/bolt/lib/Rewrite/GNUPropertyRewriter.cpp
+++ b/bolt/lib/Rewrite/GNUPropertyRewriter.cpp
@@ -82,7 +82,7 @@ Error GNUPropertyRewriter::sectionInitializer() {
return Error::success();
}
-/// Desc contains an array of property descriptors. Each member has the
+/// \p Desc contains an array of property descriptors. Each member has the
/// following structure:
/// typedef struct {
/// Elf_Word pr_type;
@@ -125,17 +125,17 @@ Expected<uint32_t> GNUPropertyRewriter::decodeGNUPropertyNote(StringRef Desc) {
if (!Tmp)
return createStringError(
errc::executable_format_error,
- "out of bounds while reading .gnu.property.note section: %s",
+ "failed to read property from .gnu.property.note 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 .gnu.property.note section: %s",
- toString(Cursor.takeError()).c_str());
+ return createStringError(errc::executable_format_error,
+ "out of bounds while reading property array in "
+ ".gnu.property.note section: %s",
+ toString(Cursor.takeError()).c_str());
}
return Features.value_or(0u);
}
diff --git a/bolt/test/AArch64/bti-note.test b/bolt/test/AArch64/bti-note.test
index 825456268a247..1ec9d774b3271 100644
--- a/bolt/test/AArch64/bti-note.test
+++ b/bolt/test/AArch64/bti-note.test
@@ -7,4 +7,4 @@ 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.
+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
index 92ed747ef7ac1..28cce345deaab 100644
--- a/bolt/test/AArch64/no-bti-note.test
+++ b/bolt/test/AArch64/no-bti-note.test
@@ -7,4 +7,4 @@ 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.
+CHECK-BOLT-NOT: BOLT-WARNING: binary is using BTI. Optimized binary may be corrupted
>From b6ee08057bd6bdaee9b5b4cfe7c247af0e1a07af Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gergely=20B=C3=A1lint?= <gergely.balint at arm.com>
Date: Fri, 3 Oct 2025 09:31:01 +0200
Subject: [PATCH 3/3] [BOLT] correct name of .note.gnu.property section
.gnu.property.note -> .note.gnu.property
---
bolt/lib/Rewrite/GNUPropertyRewriter.cpp | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/bolt/lib/Rewrite/GNUPropertyRewriter.cpp b/bolt/lib/Rewrite/GNUPropertyRewriter.cpp
index 32146ac53b492..f61c08ec46fe6 100644
--- a/bolt/lib/Rewrite/GNUPropertyRewriter.cpp
+++ b/bolt/lib/Rewrite/GNUPropertyRewriter.cpp
@@ -6,7 +6,7 @@
//
//===----------------------------------------------------------------------===//
//
-// Read the .gnu.property.note section.
+// Read the .note.gnu.property section.
//
//===----------------------------------------------------------------------===//
@@ -60,7 +60,7 @@ Error GNUPropertyRewriter::sectionInitializer() {
if (!Cursor)
return createStringError(
errc::executable_format_error,
- "out of bounds while reading .gnu.property.note section: %s",
+ "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") &&
@@ -110,7 +110,7 @@ Expected<uint32_t> GNUPropertyRewriter::decodeGNUPropertyNote(StringRef Desc) {
if (!Cursor)
return createStringError(
errc::executable_format_error,
- "out of bounds while reading .gnu.property.note section: %s",
+ "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) {
@@ -125,7 +125,7 @@ Expected<uint32_t> GNUPropertyRewriter::decodeGNUPropertyNote(StringRef Desc) {
if (!Tmp)
return createStringError(
errc::executable_format_error,
- "failed to read property from .gnu.property.note section: %s",
+ "failed to read property from .note.gnu.property section: %s",
toString(Tmp.takeError()).c_str());
Features = Features ? (*Features | FeaturesItem) : FeaturesItem;
}
@@ -134,7 +134,7 @@ Expected<uint32_t> GNUPropertyRewriter::decodeGNUPropertyNote(StringRef Desc) {
if (!Cursor)
return createStringError(errc::executable_format_error,
"out of bounds while reading property array in "
- ".gnu.property.note section: %s",
+ ".note.gnu.property section: %s",
toString(Cursor.takeError()).c_str());
}
return Features.value_or(0u);
More information about the llvm-commits
mailing list