[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