Skip to content

Commit 441f0c7

Browse files
authored
[BOLT] Add GNUPropertyRewriter and warn on AArch64 BTI note (#161206)
This commit adds the GNUPropertyRewriter, which parses features from the .note.gnu.property 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.
1 parent 05a49da commit 441f0c7

File tree

9 files changed

+278
-0
lines changed

9 files changed

+278
-0
lines changed

bolt/include/bolt/Core/BinaryContext.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,9 @@ class BinaryContext {
190190
/// Unique build ID if available for the binary.
191191
std::optional<std::string> FileBuildID;
192192

193+
/// GNU property note indicating AArch64 BTI.
194+
bool UsesBTI{false};
195+
193196
/// Set of all sections.
194197
struct CompareSections {
195198
bool operator()(const BinarySection *A, const BinarySection *B) const {
@@ -384,6 +387,9 @@ class BinaryContext {
384387
}
385388
void setFileBuildID(StringRef ID) { FileBuildID = std::string(ID); }
386389

390+
bool usesBTI() const { return UsesBTI; }
391+
void setUsesBTI(bool Value) { UsesBTI = Value; }
392+
387393
bool hasSymbolsWithFileName() const { return HasSymbolsWithFileName; }
388394
void setHasSymbolsWithFileName(bool Value) { HasSymbolsWithFileName = Value; }
389395

bolt/include/bolt/Rewrite/MetadataRewriters.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ std::unique_ptr<MetadataRewriter> createPseudoProbeRewriter(BinaryContext &);
2727

2828
std::unique_ptr<MetadataRewriter> createSDTRewriter(BinaryContext &);
2929

30+
std::unique_ptr<MetadataRewriter> createGNUPropertyRewriter(BinaryContext &);
31+
3032
} // namespace bolt
3133
} // namespace llvm
3234

bolt/lib/Rewrite/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ add_llvm_library(LLVMBOLTRewrite
2525
PseudoProbeRewriter.cpp
2626
RewriteInstance.cpp
2727
SDTRewriter.cpp
28+
GNUPropertyRewriter.cpp
2829

2930
NO_EXPORT
3031
DISABLE_LLVM_LINK_LLVM_DYLIB
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
//===- bolt/Rewrite/GNUPropertyRewriter.cpp -------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// Read the .note.gnu.property section.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "bolt/Rewrite/MetadataRewriter.h"
14+
#include "bolt/Rewrite/MetadataRewriters.h"
15+
#include "llvm/Support/Errc.h"
16+
17+
using namespace llvm;
18+
using namespace bolt;
19+
20+
namespace {
21+
22+
class GNUPropertyRewriter final : public MetadataRewriter {
23+
24+
Expected<uint32_t> decodeGNUPropertyNote(StringRef Desc);
25+
26+
public:
27+
GNUPropertyRewriter(StringRef Name, BinaryContext &BC)
28+
: MetadataRewriter(Name, BC) {}
29+
30+
Error sectionInitializer() override;
31+
};
32+
33+
Error GNUPropertyRewriter::sectionInitializer() {
34+
35+
ErrorOr<BinarySection &> Sec =
36+
BC.getUniqueSectionByName(".note.gnu.property");
37+
if (!Sec)
38+
return Error::success();
39+
40+
// Accumulate feature bits
41+
uint32_t FeaturesAcc = 0;
42+
43+
StringRef Buf = Sec->getContents();
44+
DataExtractor DE(Buf, BC.AsmInfo->isLittleEndian(),
45+
BC.AsmInfo->getCodePointerSize());
46+
DataExtractor::Cursor Cursor(0);
47+
while (Cursor && !DE.eof(Cursor)) {
48+
const uint32_t NameSz = DE.getU32(Cursor);
49+
const uint32_t DescSz = DE.getU32(Cursor);
50+
const uint32_t Type = DE.getU32(Cursor);
51+
52+
StringRef Name =
53+
NameSz ? Buf.slice(Cursor.tell(), Cursor.tell() + NameSz) : "<empty>";
54+
Cursor.seek(alignTo(Cursor.tell() + NameSz, 4));
55+
56+
const uint64_t DescOffset = Cursor.tell();
57+
StringRef Desc =
58+
DescSz ? Buf.slice(DescOffset, DescOffset + DescSz) : "<empty>";
59+
Cursor.seek(alignTo(DescOffset + DescSz, 4));
60+
if (!Cursor)
61+
return createStringError(
62+
errc::executable_format_error,
63+
"out of bounds while reading .note.gnu.property section: %s",
64+
toString(Cursor.takeError()).c_str());
65+
66+
if (Type == ELF::NT_GNU_PROPERTY_TYPE_0 && Name.starts_with("GNU") &&
67+
DescSz) {
68+
auto Features = decodeGNUPropertyNote(Desc);
69+
if (!Features)
70+
return Features.takeError();
71+
FeaturesAcc |= *Features;
72+
}
73+
}
74+
75+
if (BC.isAArch64()) {
76+
BC.setUsesBTI(FeaturesAcc & llvm::ELF::GNU_PROPERTY_AARCH64_FEATURE_1_BTI);
77+
if (BC.usesBTI())
78+
BC.outs() << "BOLT-WARNING: binary is using BTI. Optimized binary may be "
79+
"corrupted\n";
80+
}
81+
82+
return Error::success();
83+
}
84+
85+
/// \p Desc contains an array of property descriptors. Each member has the
86+
/// following structure:
87+
/// typedef struct {
88+
/// Elf_Word pr_type;
89+
/// Elf_Word pr_datasz;
90+
/// unsigned char pr_data[PR_DATASZ];
91+
/// unsigned char pr_padding[PR_PADDING];
92+
/// } Elf_Prop;
93+
///
94+
/// As there is no guarantee that the features are encoded in which element of
95+
/// the array, we have to read all, and OR together the result.
96+
Expected<uint32_t> GNUPropertyRewriter::decodeGNUPropertyNote(StringRef Desc) {
97+
DataExtractor DE(Desc, BC.AsmInfo->isLittleEndian(),
98+
BC.AsmInfo->getCodePointerSize());
99+
DataExtractor::Cursor Cursor(0);
100+
const uint32_t Align = DE.getAddressSize();
101+
102+
std::optional<uint32_t> Features = 0;
103+
while (Cursor && !DE.eof(Cursor)) {
104+
const uint32_t PrType = DE.getU32(Cursor);
105+
const uint32_t PrDataSz = DE.getU32(Cursor);
106+
107+
const uint64_t PrDataStart = Cursor.tell();
108+
const uint64_t PrDataEnd = PrDataStart + PrDataSz;
109+
Cursor.seek(PrDataEnd);
110+
if (!Cursor)
111+
return createStringError(
112+
errc::executable_format_error,
113+
"out of bounds while reading .note.gnu.property section: %s",
114+
toString(Cursor.takeError()).c_str());
115+
116+
if (PrType == llvm::ELF::GNU_PROPERTY_AARCH64_FEATURE_1_AND) {
117+
if (PrDataSz != 4) {
118+
return createStringError(
119+
errc::executable_format_error,
120+
"Property descriptor size has to be 4 bytes on AArch64\n");
121+
}
122+
DataExtractor::Cursor Tmp(PrDataStart);
123+
// PrDataSz = 4 -> PrData is uint32_t
124+
const uint32_t FeaturesItem = DE.getU32(Tmp);
125+
if (!Tmp)
126+
return createStringError(
127+
errc::executable_format_error,
128+
"failed to read property from .note.gnu.property section: %s",
129+
toString(Tmp.takeError()).c_str());
130+
Features = Features ? (*Features | FeaturesItem) : FeaturesItem;
131+
}
132+
133+
Cursor.seek(alignTo(PrDataEnd, Align));
134+
if (!Cursor)
135+
return createStringError(errc::executable_format_error,
136+
"out of bounds while reading property array in "
137+
".note.gnu.property section: %s",
138+
toString(Cursor.takeError()).c_str());
139+
}
140+
return Features.value_or(0u);
141+
}
142+
} // namespace
143+
144+
std::unique_ptr<MetadataRewriter>
145+
llvm::bolt::createGNUPropertyRewriter(BinaryContext &BC) {
146+
return std::make_unique<GNUPropertyRewriter>("gnu-property-rewriter", BC);
147+
}

bolt/lib/Rewrite/RewriteInstance.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3331,6 +3331,8 @@ void RewriteInstance::initializeMetadataManager() {
33313331
MetadataManager.registerRewriter(createPseudoProbeRewriter(*BC));
33323332

33333333
MetadataManager.registerRewriter(createSDTRewriter(*BC));
3334+
3335+
MetadataManager.registerRewriter(createGNUPropertyRewriter(*BC));
33343336
}
33353337

33363338
void RewriteInstance::processSectionMetadata() {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
--- !ELF
2+
FileHeader:
3+
Class: ELFCLASS64
4+
Data: ELFDATA2LSB
5+
Type: ET_EXEC
6+
Machine: EM_AARCH64
7+
Entry: 0x400510
8+
ProgramHeaders:
9+
- Type: PT_NOTE
10+
Flags: [ PF_R ]
11+
FirstSec: .note.gnu.property
12+
LastSec: .note.gnu.property
13+
VAddr: 0x400338
14+
Align: 0x8
15+
- Type: PT_LOAD
16+
Flags: [ PF_R ]
17+
VAddr: 0x0
18+
Align: 0x10000
19+
FileSize: 0xf8
20+
MemSize: 0xf8
21+
Offset: 0x0
22+
Sections:
23+
- Name: .text
24+
Type: SHT_PROGBITS
25+
Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
26+
Address: 0x2a0000
27+
AddressAlign: 0x4
28+
Content: 400580d2c0035fd6
29+
- Name: .note.gnu.property
30+
Type: SHT_NOTE
31+
Flags: [ SHF_ALLOC ]
32+
Address: 0x400338
33+
AddressAlign: 0x8
34+
Notes:
35+
- Name: GNU
36+
Desc: 000000C0040000000300000000000000
37+
Type: NT_GNU_PROPERTY_TYPE_0
38+
- Type: SectionHeaderTable
39+
Sections:
40+
- Name: .note.gnu.property
41+
- Name: .symtab
42+
- Name: .strtab
43+
- Name: .shstrtab
44+
- Name: .text
45+
Symbols:
46+
- Name: .note.gnu.property
47+
Type: STT_SECTION
48+
Section: .note.gnu.property
49+
Value: 0x400338
50+
...
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
--- !ELF
2+
FileHeader:
3+
Class: ELFCLASS64
4+
Data: ELFDATA2LSB
5+
Type: ET_EXEC
6+
Machine: EM_AARCH64
7+
Entry: 0x400510
8+
ProgramHeaders:
9+
- Type: PT_NOTE
10+
Flags: [ PF_R ]
11+
FirstSec: .note.gnu.property
12+
LastSec: .note.gnu.property
13+
VAddr: 0x400338
14+
Align: 0x8
15+
- Type: PT_LOAD
16+
Flags: [ PF_R ]
17+
VAddr: 0x0
18+
Align: 0x10000
19+
FileSize: 0xf8
20+
MemSize: 0xf8
21+
Offset: 0x0
22+
Sections:
23+
- Name: .text
24+
Type: SHT_PROGBITS
25+
Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
26+
Address: 0x2a0000
27+
AddressAlign: 0x4
28+
Content: 400580d2c0035fd6
29+
- Name: .note.gnu.property
30+
Type: SHT_NOTE
31+
Flags: [ SHF_ALLOC ]
32+
Address: 0x400338
33+
AddressAlign: 0x8
34+
Notes:
35+
- Name: GNU
36+
Desc: 000000C0040000000200000000000000
37+
Type: NT_GNU_PROPERTY_TYPE_0
38+
- Type: SectionHeaderTable
39+
Sections:
40+
- Name: .note.gnu.property
41+
- Name: .symtab
42+
- Name: .strtab
43+
- Name: .shstrtab
44+
- Name: .text
45+
Symbols:
46+
- Name: .note.gnu.property
47+
Type: STT_SECTION
48+
Section: .note.gnu.property
49+
Value: 0x400338
50+
...

bolt/test/AArch64/bti-note.test

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// This test checks that the GNUPropertyRewriter can decode the BTI feature flag.
2+
// It decodes an executable with BTI, and checks for the warning.
3+
4+
RUN: yaml2obj %p/Inputs/property-note-bti.yaml &> %t.exe
5+
6+
RUN: llvm-readelf -n %t.exe | FileCheck %s
7+
CHECK: BTI
8+
9+
RUN: llvm-bolt %t.exe -o %t.exe.bolt | FileCheck %s -check-prefix=CHECK-BOLT
10+
CHECK-BOLT: BOLT-WARNING: binary is using BTI. Optimized binary may be corrupted

bolt/test/AArch64/no-bti-note.test

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// This test checks that the GNUPropertyRewriter can decode the BTI feature flag.
2+
// It decodes an executable without BTI, and checks for the warning.
3+
4+
RUN: yaml2obj %p/Inputs/property-note-nobti.yaml &> %t.exe
5+
6+
RUN: llvm-readelf -n %t.exe | FileCheck %s
7+
CHECK-NOT: BTI
8+
9+
RUN: llvm-bolt %t.exe -o %t.exe.bolt | FileCheck %s -check-prefix=CHECK-BOLT
10+
CHECK-BOLT-NOT: BOLT-WARNING: binary is using BTI. Optimized binary may be corrupted

0 commit comments

Comments
 (0)