Skip to content

Commit a7aee6c

Browse files
georgerimGeorgii Rymar
authored andcommitted
[yaml2obj/obj2yaml] - Add support for SHT_GNU_HASH section.
This adds parsing and dumping support for GNU hash sections. They are described nicely here: https://blogs.oracle.com/solaris/gnu-hash-elf-sections-v2 Differential revision: https://reviews.llvm.org/D69399
1 parent 403cd57 commit a7aee6c

File tree

7 files changed

+633
-0
lines changed

7 files changed

+633
-0
lines changed

llvm/include/llvm/ObjectYAML/ELFYAML.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ struct Section {
139139
NoBits,
140140
Note,
141141
Hash,
142+
GnuHash,
142143
Verdef,
143144
Verneed,
144145
StackSizes,
@@ -249,6 +250,39 @@ struct HashSection : Section {
249250
static bool classof(const Section *S) { return S->Kind == SectionKind::Hash; }
250251
};
251252

253+
struct GnuHashHeader {
254+
// The number of hash buckets.
255+
// Not used when dumping the object, but can be used to override
256+
// the real number of buckets when emiting an object from a YAML document.
257+
Optional<llvm::yaml::Hex32> NBuckets;
258+
259+
// Index of the first symbol in the dynamic symbol table
260+
// included in the hash table.
261+
llvm::yaml::Hex32 SymNdx;
262+
263+
// The number of words in the Bloom filter.
264+
// Not used when dumping the object, but can be used to override the real
265+
// number of words in the Bloom filter when emiting an object from a YAML
266+
// document.
267+
Optional<llvm::yaml::Hex32> MaskWords;
268+
269+
// A shift constant used by the Bloom filter.
270+
llvm::yaml::Hex32 Shift2;
271+
};
272+
273+
struct GnuHashSection : Section {
274+
Optional<yaml::BinaryRef> Content;
275+
276+
Optional<GnuHashHeader> Header;
277+
Optional<std::vector<llvm::yaml::Hex64>> BloomFilter;
278+
Optional<std::vector<llvm::yaml::Hex32>> HashBuckets;
279+
Optional<std::vector<llvm::yaml::Hex32>> HashValues;
280+
281+
GnuHashSection() : Section(SectionKind::GnuHash) {}
282+
283+
static bool classof(const Section *S) { return S->Kind == SectionKind::GnuHash; }
284+
};
285+
252286
struct VernauxEntry {
253287
uint32_t Hash;
254288
uint16_t Flags;
@@ -541,6 +575,10 @@ template <> struct MappingTraits<ELFYAML::StackSizeEntry> {
541575
static void mapping(IO &IO, ELFYAML::StackSizeEntry &Rel);
542576
};
543577

578+
template <> struct MappingTraits<ELFYAML::GnuHashHeader> {
579+
static void mapping(IO &IO, ELFYAML::GnuHashHeader &Rel);
580+
};
581+
544582
template <> struct MappingTraits<ELFYAML::DynamicEntry> {
545583
static void mapping(IO &IO, ELFYAML::DynamicEntry &Rel);
546584
};

llvm/lib/ObjectYAML/ELFEmitter.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,9 @@ template <class ELFT> class ELFState {
182182
void writeSectionContent(Elf_Shdr &SHeader,
183183
const ELFYAML::NoteSection &Section,
184184
ContiguousBlobAccumulator &CBA);
185+
void writeSectionContent(Elf_Shdr &SHeader,
186+
const ELFYAML::GnuHashSection &Section,
187+
ContiguousBlobAccumulator &CBA);
185188

186189
ELFState(ELFYAML::Object &D, yaml::ErrorHandler EH);
187190

@@ -440,6 +443,8 @@ void ELFState<ELFT>::initSectionHeaders(std::vector<Elf_Shdr> &SHeaders,
440443
writeSectionContent(SHeader, *S, CBA);
441444
} else if (auto S = dyn_cast<ELFYAML::NoteSection>(Sec)) {
442445
writeSectionContent(SHeader, *S, CBA);
446+
} else if (auto S = dyn_cast<ELFYAML::GnuHashSection>(Sec)) {
447+
writeSectionContent(SHeader, *S, CBA);
443448
} else {
444449
llvm_unreachable("Unknown section type");
445450
}
@@ -1091,6 +1096,70 @@ void ELFState<ELFT>::writeSectionContent(Elf_Shdr &SHeader,
10911096
SHeader.sh_size = OS.tell() - Offset;
10921097
}
10931098

1099+
template <class ELFT>
1100+
void ELFState<ELFT>::writeSectionContent(Elf_Shdr &SHeader,
1101+
const ELFYAML::GnuHashSection &Section,
1102+
ContiguousBlobAccumulator &CBA) {
1103+
raw_ostream &OS =
1104+
CBA.getOSAndAlignedOffset(SHeader.sh_offset, SHeader.sh_addralign);
1105+
1106+
unsigned Link = 0;
1107+
if (Section.Link.empty() && SN2I.lookup(".dynsym", Link))
1108+
SHeader.sh_link = Link;
1109+
1110+
if (Section.Content) {
1111+
SHeader.sh_size = writeContent(OS, Section.Content, None);
1112+
return;
1113+
}
1114+
1115+
// We write the header first, starting with the hash buckets count. Normally
1116+
// it is the number of entries in HashBuckets, but the "NBuckets" property can
1117+
// be used to override this field, which is useful for producing broken
1118+
// objects.
1119+
if (Section.Header->NBuckets)
1120+
support::endian::write<uint32_t>(OS, *Section.Header->NBuckets,
1121+
ELFT::TargetEndianness);
1122+
else
1123+
support::endian::write<uint32_t>(OS, Section.HashBuckets->size(),
1124+
ELFT::TargetEndianness);
1125+
1126+
// Write the index of the first symbol in the dynamic symbol table accessible
1127+
// via the hash table.
1128+
support::endian::write<uint32_t>(OS, Section.Header->SymNdx,
1129+
ELFT::TargetEndianness);
1130+
1131+
// Write the number of words in the Bloom filter. As above, the "MaskWords"
1132+
// property can be used to set this field to any value.
1133+
if (Section.Header->MaskWords)
1134+
support::endian::write<uint32_t>(OS, *Section.Header->MaskWords,
1135+
ELFT::TargetEndianness);
1136+
else
1137+
support::endian::write<uint32_t>(OS, Section.BloomFilter->size(),
1138+
ELFT::TargetEndianness);
1139+
1140+
// Write the shift constant used by the Bloom filter.
1141+
support::endian::write<uint32_t>(OS, Section.Header->Shift2,
1142+
ELFT::TargetEndianness);
1143+
1144+
// We've finished writing the header. Now write the Bloom filter.
1145+
for (llvm::yaml::Hex64 Val : *Section.BloomFilter)
1146+
support::endian::write<typename ELFT::uint>(OS, Val,
1147+
ELFT::TargetEndianness);
1148+
1149+
// Write an array of hash buckets.
1150+
for (llvm::yaml::Hex32 Val : *Section.HashBuckets)
1151+
support::endian::write<uint32_t>(OS, Val, ELFT::TargetEndianness);
1152+
1153+
// Write an array of hash values.
1154+
for (llvm::yaml::Hex32 Val : *Section.HashValues)
1155+
support::endian::write<uint32_t>(OS, Val, ELFT::TargetEndianness);
1156+
1157+
SHeader.sh_size = 16 /*Header size*/ +
1158+
Section.BloomFilter->size() * sizeof(typename ELFT::uint) +
1159+
Section.HashBuckets->size() * 4 +
1160+
Section.HashValues->size() * 4;
1161+
}
1162+
10941163
template <class ELFT> void ELFState<ELFT>::buildSectionIndex() {
10951164
for (unsigned I = 0, E = Doc.Sections.size(); I != E; ++I) {
10961165
StringRef Name = Doc.Sections[I]->Name;

llvm/lib/ObjectYAML/ELFYAML.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,6 +1039,15 @@ static void sectionMapping(IO &IO, ELFYAML::NoteSection &Section) {
10391039
IO.mapOptional("Notes", Section.Notes);
10401040
}
10411041

1042+
1043+
static void sectionMapping(IO &IO, ELFYAML::GnuHashSection &Section) {
1044+
commonSectionMapping(IO, Section);
1045+
IO.mapOptional("Content", Section.Content);
1046+
IO.mapOptional("Header", Section.Header);
1047+
IO.mapOptional("BloomFilter", Section.BloomFilter);
1048+
IO.mapOptional("HashBuckets", Section.HashBuckets);
1049+
IO.mapOptional("HashValues", Section.HashValues);
1050+
}
10421051
static void sectionMapping(IO &IO, ELFYAML::NoBitsSection &Section) {
10431052
commonSectionMapping(IO, Section);
10441053
IO.mapOptional("Size", Section.Size, Hex64(0));
@@ -1155,6 +1164,11 @@ void MappingTraits<std::unique_ptr<ELFYAML::Section>>::mapping(
11551164
Section.reset(new ELFYAML::NoteSection());
11561165
sectionMapping(IO, *cast<ELFYAML::NoteSection>(Section.get()));
11571166
break;
1167+
case ELF::SHT_GNU_HASH:
1168+
if (!IO.outputting())
1169+
Section.reset(new ELFYAML::GnuHashSection());
1170+
sectionMapping(IO, *cast<ELFYAML::GnuHashSection>(Section.get()));
1171+
break;
11581172
case ELF::SHT_MIPS_ABIFLAGS:
11591173
if (!IO.outputting())
11601174
Section.reset(new ELFYAML::MipsABIFlags());
@@ -1300,6 +1314,29 @@ StringRef MappingTraits<std::unique_ptr<ELFYAML::Section>>::validate(
13001314
return {};
13011315
}
13021316

1317+
if (const auto *Sec = dyn_cast<ELFYAML::GnuHashSection>(Section.get())) {
1318+
if (!Sec->Content && !Sec->Header && !Sec->BloomFilter &&
1319+
!Sec->HashBuckets && !Sec->HashValues)
1320+
return "either \"Content\" or \"Header\", \"BloomFilter\", "
1321+
"\"HashBuckets\" and \"HashBuckets\" must be specified";
1322+
1323+
if (Sec->Header || Sec->BloomFilter || Sec->HashBuckets ||
1324+
Sec->HashValues) {
1325+
if (!Sec->Header || !Sec->BloomFilter || !Sec->HashBuckets ||
1326+
!Sec->HashValues)
1327+
return "\"Header\", \"BloomFilter\", "
1328+
"\"HashBuckets\" and \"HashValues\" must be used together";
1329+
if (Sec->Content)
1330+
return "\"Header\", \"BloomFilter\", "
1331+
"\"HashBuckets\" and \"HashValues\" can't be used together with "
1332+
"\"Content\"";
1333+
return {};
1334+
}
1335+
1336+
// Only Content is specified.
1337+
return {};
1338+
}
1339+
13031340
return {};
13041341
}
13051342

@@ -1335,6 +1372,15 @@ void MappingTraits<ELFYAML::StackSizeEntry>::mapping(
13351372
IO.mapRequired("Size", E.Size);
13361373
}
13371374

1375+
void MappingTraits<ELFYAML::GnuHashHeader>::mapping(IO &IO,
1376+
ELFYAML::GnuHashHeader &E) {
1377+
assert(IO.getContext() && "The IO context is not initialized");
1378+
IO.mapOptional("NBuckets", E.NBuckets);
1379+
IO.mapRequired("SymNdx", E.SymNdx);
1380+
IO.mapOptional("MaskWords", E.MaskWords);
1381+
IO.mapRequired("Shift2", E.Shift2);
1382+
}
1383+
13381384
void MappingTraits<ELFYAML::DynamicEntry>::mapping(IO &IO,
13391385
ELFYAML::DynamicEntry &Rel) {
13401386
assert(IO.getContext() && "The IO context is not initialized");

llvm/test/tools/llvm-readobj/elf-section-types.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ Sections:
210210
Type: SHT_GNU_ATTRIBUTES
211211
- Name: gnu_hash
212212
Type: SHT_GNU_HASH
213+
Content: ""
213214
- Name: gnu_verdef
214215
Type: SHT_GNU_verdef
215216
Info: 0
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
## Check how obj2yaml produces SHT_GNU_HASH section descriptions.
2+
3+
## Check that obj2yaml uses "Header", "BloomFilter", "HashBuckets" and "HashValues"
4+
## tags to describe a SHT_GNU_HASH section when it has content of a correct size.
5+
6+
# RUN: yaml2obj --docnum=1 %s -o %t1
7+
# RUN: obj2yaml %t1 | FileCheck %s --check-prefix=FIELDS
8+
9+
# FIELDS: - Name: .gnu.hash
10+
# FIELDS-NEXT: Type: SHT_GNU_HASH
11+
# FIELDS-NEXT: Flags: [ SHF_ALLOC ]
12+
# FIELDS-NEXT: Header:
13+
# FIELDS-NEXT: SymNdx: 0x00000001
14+
# FIELDS-NEXT: Shift2: 0x00000002
15+
# FIELDS-NEXT: BloomFilter: [ 0x0000000000000003, 0x0000000000000004 ]
16+
# FIELDS-NEXT: HashBuckets: [ 0x00000005, 0x00000006, 0x00000007 ]
17+
# FIELDS-NEXT: HashValues: [ 0x00000008, 0x00000009, 0x0000000A, 0x0000000B ]
18+
19+
--- !ELF
20+
FileHeader:
21+
Class: ELFCLASS32
22+
Data: ELFDATA2LSB
23+
Type: ET_DYN
24+
Machine: EM_386
25+
Sections:
26+
- Name: .gnu.hash
27+
Type: SHT_GNU_HASH
28+
Flags: [ SHF_ALLOC ]
29+
Header:
30+
SymNdx: 0x1
31+
Shift2: 0x2
32+
BloomFilter: [0x3, 0x4]
33+
HashBuckets: [0x5, 0x6, 0x7]
34+
HashValues: [0x8, 0x9, 0xA, 0xB]
35+
36+
## Check how we handle broken cases.
37+
38+
# RUN: yaml2obj --docnum=2 %s -o %t2
39+
# RUN: obj2yaml %t2 | FileCheck %s --check-prefix=INVALID
40+
41+
# INVALID: - Name: .gnu.hash.tooshort
42+
# INVALID-NEXT: Type: SHT_GNU_HASH
43+
# INVALID-NEXT: Flags: [ SHF_ALLOC ]
44+
# INVALID-NEXT: Content: 112233445566778899AABBCCDDEEFF
45+
# INVALID-NEXT: - Name: .gnu.hash.empty
46+
# INVALID-NEXT: Type: SHT_GNU_HASH
47+
# INVALID-NEXT: Flags: [ SHF_ALLOC ]
48+
# INVALID-NEXT: Header:
49+
# INVALID-NEXT: SymNdx: 0x00000000
50+
# INVALID-NEXT: Shift2: 0x00000000
51+
# INVALID-NEXT: BloomFilter: [ ]
52+
# INVALID-NEXT: HashBuckets: [ ]
53+
# INVALID-NEXT: HashValues: [ ]
54+
# INVALID-NEXT: - Name: .gnu.hash.broken.maskwords
55+
# INVALID-NEXT: Type: SHT_GNU_HASH
56+
# INVALID-NEXT: Content: '00000000000000000100000000000000'
57+
# INVALID-NEXT: - Name: .gnu.hash.broken.nbuckets
58+
# INVALID-NEXT: Type: SHT_GNU_HASH
59+
# INVALID-NEXT: Content: '01000000000000000000000000000000'
60+
# INVALID-NEXT: - Name: .gnu.hash.hashvalues.ok
61+
# INVALID-NEXT: Type: SHT_GNU_HASH
62+
# INVALID-NEXT: Header:
63+
# INVALID-NEXT: SymNdx: 0x00000000
64+
# INVALID-NEXT: Shift2: 0x00000000
65+
# INVALID-NEXT: BloomFilter: [ ]
66+
# INVALID-NEXT: HashBuckets: [ ]
67+
# INVALID-NEXT: HashValues: [ 0x00000000 ]
68+
# INVALID-NEXT: - Name: .gnu.hash.hashvalues.fail
69+
# INVALID-NEXT: Type: SHT_GNU_HASH
70+
# INVALID-NEXT: Content: '000000000000000000000000000000000000000000'
71+
72+
--- !ELF
73+
FileHeader:
74+
Class: ELFCLASS32
75+
Data: ELFDATA2LSB
76+
Type: ET_DYN
77+
Machine: EM_386
78+
Sections:
79+
## Case 1: Content is less than 16 bytes.
80+
- Name: .gnu.hash.tooshort
81+
Type: SHT_GNU_HASH
82+
Flags: [ SHF_ALLOC ]
83+
Content: "112233445566778899AABBCCDDEEFF"
84+
## Case2: Check how we handle a fully empty hash section.
85+
## It is almost technically valid, but uncommon. Modern linkers
86+
## create at least one entry in Bloom filter if they want to disable it.
87+
## Also, the dynamic symbol table has a null entry and having SymNdx = 0
88+
## here is at least strange.
89+
- Name: .gnu.hash.empty
90+
Type: SHT_GNU_HASH
91+
Flags: [ SHF_ALLOC ]
92+
Header:
93+
SymNdx: 0x0
94+
Shift2: 0x0
95+
MaskWords: 0x0
96+
NBuckets: 0x0
97+
BloomFilter: []
98+
HashBuckets: []
99+
HashValues: []
100+
## Case 3: MaskWords field is broken: it says that the number of entries
101+
## in the Bloom filter is 1, but the Bloom filter is empty.
102+
- Name: .gnu.hash.broken.maskwords
103+
Type: SHT_GNU_HASH
104+
Header:
105+
SymNdx: 0x0
106+
Shift2: 0x0
107+
MaskWords: 0x1
108+
NBuckets: 0x0
109+
BloomFilter: []
110+
HashBuckets: []
111+
HashValues: []
112+
## Case 4: NBuckets field is broken, it says that the number of entries
113+
## in the hash buckets is 1, but it is empty.
114+
- Name: .gnu.hash.broken.nbuckets
115+
Type: SHT_GNU_HASH
116+
Header:
117+
SymNdx: 0x0
118+
Shift2: 0x0
119+
MaskWords: 0x0
120+
NBuckets: 0x1
121+
BloomFilter: []
122+
HashBuckets: []
123+
HashValues: []
124+
## Case 5: Check that we use the various properties to dump the data when it
125+
## has a size that is a multiple of 4, but fallback to dumping the whole section
126+
## using the "Content" property otherwise.
127+
- Name: .gnu.hash.hashvalues.ok
128+
Type: SHT_GNU_HASH
129+
Content: "0000000000000000000000000000000000000000"
130+
- Name: .gnu.hash.hashvalues.fail
131+
Type: SHT_GNU_HASH
132+
Content: "000000000000000000000000000000000000000000"

0 commit comments

Comments
 (0)