1 | //===- bolt/unittest/Core/BinaryContext.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 | #include "bolt/Core/BinaryContext.h" |
10 | #include "bolt/Utils/CommandLineOpts.h" |
11 | #include "llvm/BinaryFormat/ELF.h" |
12 | #include "llvm/DebugInfo/DWARF/DWARFContext.h" |
13 | #include "llvm/Support/TargetSelect.h" |
14 | #include "gtest/gtest.h" |
15 | |
16 | using namespace llvm; |
17 | using namespace llvm::object; |
18 | using namespace llvm::ELF; |
19 | using namespace bolt; |
20 | |
21 | namespace { |
22 | struct BinaryContextTester : public testing::TestWithParam<Triple::ArchType> { |
23 | void SetUp() override { |
24 | initalizeLLVM(); |
25 | prepareElf(); |
26 | initializeBOLT(); |
27 | } |
28 | |
29 | protected: |
30 | void initalizeLLVM() { |
31 | #define BOLT_TARGET(target) \ |
32 | LLVMInitialize##target##TargetInfo(); \ |
33 | LLVMInitialize##target##TargetMC(); \ |
34 | LLVMInitialize##target##AsmParser(); \ |
35 | LLVMInitialize##target##Disassembler(); \ |
36 | LLVMInitialize##target##Target(); \ |
37 | LLVMInitialize##target##AsmPrinter(); |
38 | |
39 | #include "bolt/Core/TargetConfig.def" |
40 | } |
41 | |
42 | void prepareElf() { |
43 | memcpy(dest: ElfBuf, src: "\177ELF" , n: 4); |
44 | ELF64LE::Ehdr *EHdr = reinterpret_cast<typename ELF64LE::Ehdr *>(ElfBuf); |
45 | EHdr->e_ident[llvm::ELF::EI_CLASS] = llvm::ELF::ELFCLASS64; |
46 | EHdr->e_ident[llvm::ELF::EI_DATA] = llvm::ELF::ELFDATA2LSB; |
47 | EHdr->e_machine = GetParam() == Triple::aarch64 ? EM_AARCH64 : EM_X86_64; |
48 | MemoryBufferRef Source(StringRef(ElfBuf, sizeof(ElfBuf)), "ELF" ); |
49 | ObjFile = cantFail(ValOrErr: ObjectFile::createObjectFile(Object: Source)); |
50 | } |
51 | |
52 | void initializeBOLT() { |
53 | Relocation::Arch = ObjFile->makeTriple().getArch(); |
54 | BC = cantFail(ValOrErr: BinaryContext::createBinaryContext( |
55 | TheTriple: ObjFile->makeTriple(), SSP: std::make_shared<orc::SymbolStringPool>(), |
56 | InputFileName: ObjFile->getFileName(), Features: nullptr, IsPIC: true, DwCtx: DWARFContext::create(Obj: *ObjFile), |
57 | Logger: {.Out: llvm::outs(), .Err: llvm::errs()})); |
58 | ASSERT_FALSE(!BC); |
59 | } |
60 | |
61 | char ElfBuf[sizeof(typename ELF64LE::Ehdr)] = {}; |
62 | std::unique_ptr<ObjectFile> ObjFile; |
63 | std::unique_ptr<BinaryContext> BC; |
64 | }; |
65 | } // namespace |
66 | |
67 | #ifdef X86_AVAILABLE |
68 | |
69 | INSTANTIATE_TEST_SUITE_P(X86, BinaryContextTester, |
70 | ::testing::Values(Triple::x86_64)); |
71 | |
72 | #endif |
73 | |
74 | #ifdef AARCH64_AVAILABLE |
75 | |
76 | INSTANTIATE_TEST_SUITE_P(AArch64, BinaryContextTester, |
77 | ::testing::Values(Triple::aarch64)); |
78 | |
79 | TEST_P(BinaryContextTester, FlushPendingRelocCALL26) { |
80 | if (GetParam() != Triple::aarch64) |
81 | GTEST_SKIP(); |
82 | |
83 | // This test checks that encodeValueAArch64 used by flushPendingRelocations |
84 | // returns correctly encoded values for CALL26 relocation for both backward |
85 | // and forward branches. |
86 | // |
87 | // The offsets layout is: |
88 | // 4: func1 |
89 | // 8: bl func1 |
90 | // 12: bl func2 |
91 | // 16: func2 |
92 | |
93 | constexpr size_t DataSize = 20; |
94 | uint8_t *Data = new uint8_t[DataSize]; |
95 | BinarySection &BS = BC->registerOrUpdateSection( |
96 | Name: ".text" , ELFType: ELF::SHT_PROGBITS, ELFFlags: ELF::SHF_EXECINSTR | ELF::SHF_ALLOC, Data, |
97 | Size: DataSize, Alignment: 4); |
98 | MCSymbol *RelSymbol1 = BC->getOrCreateGlobalSymbol(Address: 4, Prefix: "Func1" ); |
99 | ASSERT_TRUE(RelSymbol1); |
100 | BS.addPendingRelocation( |
101 | Rel: Relocation{8, RelSymbol1, ELF::R_AARCH64_CALL26, 0, 0}); |
102 | MCSymbol *RelSymbol2 = BC->getOrCreateGlobalSymbol(Address: 16, Prefix: "Func2" ); |
103 | ASSERT_TRUE(RelSymbol2); |
104 | BS.addPendingRelocation( |
105 | Rel: Relocation{12, RelSymbol2, ELF::R_AARCH64_CALL26, 0, 0}); |
106 | |
107 | SmallVector<char> Vect(DataSize); |
108 | raw_svector_ostream OS(Vect); |
109 | |
110 | BS.flushPendingRelocations(OS, Resolver: [&](const MCSymbol *S) { |
111 | return S == RelSymbol1 ? 4 : S == RelSymbol2 ? 16 : 0; |
112 | }); |
113 | |
114 | const uint8_t Func1Call[4] = {255, 255, 255, 151}; |
115 | const uint8_t Func2Call[4] = {1, 0, 0, 148}; |
116 | |
117 | EXPECT_FALSE(memcmp(Func1Call, &Vect[8], 4)) << "Wrong backward call value\n" ; |
118 | EXPECT_FALSE(memcmp(Func2Call, &Vect[12], 4)) << "Wrong forward call value\n" ; |
119 | } |
120 | |
121 | TEST_P(BinaryContextTester, FlushPendingRelocJUMP26) { |
122 | if (GetParam() != Triple::aarch64) |
123 | GTEST_SKIP(); |
124 | |
125 | // This test checks that encodeValueAArch64 used by flushPendingRelocations |
126 | // returns correctly encoded values for R_AARCH64_JUMP26 relocation for both |
127 | // backward and forward branches. |
128 | // |
129 | // The offsets layout is: |
130 | // 4: func1 |
131 | // 8: b func1 |
132 | // 12: b func2 |
133 | // 16: func2 |
134 | |
135 | const uint64_t Size = 20; |
136 | char *Data = new char[Size]; |
137 | BinarySection &BS = BC->registerOrUpdateSection( |
138 | Name: ".text" , ELFType: ELF::SHT_PROGBITS, ELFFlags: ELF::SHF_EXECINSTR | ELF::SHF_ALLOC, |
139 | Data: (uint8_t *)Data, Size, Alignment: 4); |
140 | MCSymbol *RelSymbol1 = BC->getOrCreateGlobalSymbol(Address: 4, Prefix: "Func1" ); |
141 | ASSERT_TRUE(RelSymbol1); |
142 | BS.addPendingRelocation( |
143 | Rel: Relocation{8, RelSymbol1, ELF::R_AARCH64_JUMP26, 0, 0}); |
144 | MCSymbol *RelSymbol2 = BC->getOrCreateGlobalSymbol(Address: 16, Prefix: "Func2" ); |
145 | ASSERT_TRUE(RelSymbol2); |
146 | BS.addPendingRelocation( |
147 | Rel: Relocation{12, RelSymbol2, ELF::R_AARCH64_JUMP26, 0, 0}); |
148 | |
149 | SmallVector<char> Vect(Size); |
150 | raw_svector_ostream OS(Vect); |
151 | |
152 | BS.flushPendingRelocations(OS, Resolver: [&](const MCSymbol *S) { |
153 | return S == RelSymbol1 ? 4 : S == RelSymbol2 ? 16 : 0; |
154 | }); |
155 | |
156 | const uint8_t Func1Call[4] = {255, 255, 255, 23}; |
157 | const uint8_t Func2Call[4] = {1, 0, 0, 20}; |
158 | |
159 | EXPECT_FALSE(memcmp(Func1Call, &Vect[8], 4)) |
160 | << "Wrong backward branch value\n" ; |
161 | EXPECT_FALSE(memcmp(Func2Call, &Vect[12], 4)) |
162 | << "Wrong forward branch value\n" ; |
163 | } |
164 | |
165 | TEST_P(BinaryContextTester, |
166 | FlushOptionalOutOfRangePendingRelocCALL26_ForcePatchOn) { |
167 | if (GetParam() != Triple::aarch64) |
168 | GTEST_SKIP(); |
169 | |
170 | // Tests that flushPendingRelocations can skip flushing any optional pending |
171 | // relocations that cannot be encoded, given that PatchEntries runs. |
172 | opts::ForcePatch = true; |
173 | |
174 | opts::Verbosity = 1; |
175 | testing::internal::CaptureStdout(); |
176 | |
177 | BinarySection &BS = BC->registerOrUpdateSection( |
178 | Name: ".text" , ELFType: ELF::SHT_PROGBITS, ELFFlags: ELF::SHF_EXECINSTR | ELF::SHF_ALLOC); |
179 | MCSymbol *RelSymbol = BC->getOrCreateGlobalSymbol(Address: 4, Prefix: "Func" ); |
180 | ASSERT_TRUE(RelSymbol); |
181 | Relocation Reloc{8, RelSymbol, ELF::R_AARCH64_CALL26, 0, 0}; |
182 | Reloc.setOptional(); |
183 | BS.addPendingRelocation(Rel: Reloc); |
184 | |
185 | SmallVector<char> Vect; |
186 | raw_svector_ostream OS(Vect); |
187 | |
188 | // Resolve relocation symbol to a high value so encoding will be out of range. |
189 | BS.flushPendingRelocations(OS, Resolver: [&](const MCSymbol *S) { return 0x800000F; }); |
190 | outs().flush(); |
191 | std::string CapturedStdOut = testing::internal::GetCapturedStdout(); |
192 | EXPECT_EQ(CapturedStdOut, |
193 | "BOLT-INFO: skipped 1 out-of-range optional relocations\n" ); |
194 | } |
195 | |
196 | #endif |
197 | |
198 | TEST_P(BinaryContextTester, BaseAddress) { |
199 | // Check that base address calculation is correct for a binary with the |
200 | // following segment layout: |
201 | BC->SegmentMapInfo[0] = |
202 | SegmentInfo{.Address: 0, .Size: 0x10e8c2b4, .FileOffset: 0, .FileSize: 0x10e8c2b4, .Alignment: 0x1000, .IsExecutable: true}; |
203 | BC->SegmentMapInfo[0x10e8d2b4] = |
204 | SegmentInfo{.Address: 0x10e8d2b4, .Size: 0x3952faec, .FileOffset: 0x10e8c2b4, .FileSize: 0x3952faec, .Alignment: 0x1000, .IsExecutable: true}; |
205 | BC->SegmentMapInfo[0x4a3bddc0] = |
206 | SegmentInfo{.Address: 0x4a3bddc0, .Size: 0x148e828, .FileOffset: 0x4a3bbdc0, .FileSize: 0x148e828, .Alignment: 0x1000, .IsExecutable: true}; |
207 | BC->SegmentMapInfo[0x4b84d5e8] = |
208 | SegmentInfo{.Address: 0x4b84d5e8, .Size: 0x294f830, .FileOffset: 0x4b84a5e8, .FileSize: 0x3d3820, .Alignment: 0x1000, .IsExecutable: true}; |
209 | |
210 | std::optional<uint64_t> BaseAddress = |
211 | BC->getBaseAddressForMapping(MMapAddress: 0x7f13f5556000, FileOffset: 0x10e8c000); |
212 | ASSERT_TRUE(BaseAddress.has_value()); |
213 | ASSERT_EQ(*BaseAddress, 0x7f13e46c9000ULL); |
214 | |
215 | BaseAddress = BC->getBaseAddressForMapping(MMapAddress: 0x7f13f5556000, FileOffset: 0x137a000); |
216 | ASSERT_FALSE(BaseAddress.has_value()); |
217 | } |
218 | |
219 | TEST_P(BinaryContextTester, BaseAddress2) { |
220 | // Check that base address calculation is correct for a binary if the |
221 | // alignment in ELF file are different from pagesize. |
222 | // The segment layout is as follows: |
223 | BC->SegmentMapInfo[0] = SegmentInfo{.Address: 0, .Size: 0x2177c, .FileOffset: 0, .FileSize: 0x2177c, .Alignment: 0x10000, .IsExecutable: true}; |
224 | BC->SegmentMapInfo[0x31860] = |
225 | SegmentInfo{.Address: 0x31860, .Size: 0x370, .FileOffset: 0x21860, .FileSize: 0x370, .Alignment: 0x10000, .IsExecutable: true}; |
226 | BC->SegmentMapInfo[0x41c20] = |
227 | SegmentInfo{.Address: 0x41c20, .Size: 0x1f8, .FileOffset: 0x21c20, .FileSize: 0x1f8, .Alignment: 0x10000, .IsExecutable: true}; |
228 | BC->SegmentMapInfo[0x54e18] = |
229 | SegmentInfo{.Address: 0x54e18, .Size: 0x51, .FileOffset: 0x24e18, .FileSize: 0x51, .Alignment: 0x10000, .IsExecutable: true}; |
230 | |
231 | std::optional<uint64_t> BaseAddress = |
232 | BC->getBaseAddressForMapping(MMapAddress: 0xaaaaea444000, FileOffset: 0x21000); |
233 | ASSERT_TRUE(BaseAddress.has_value()); |
234 | ASSERT_EQ(*BaseAddress, 0xaaaaea413000ULL); |
235 | |
236 | BaseAddress = BC->getBaseAddressForMapping(MMapAddress: 0xaaaaea444000, FileOffset: 0x11000); |
237 | ASSERT_FALSE(BaseAddress.has_value()); |
238 | } |
239 | |
240 | TEST_P(BinaryContextTester, BaseAddressSegmentsSmallerThanAlignment) { |
241 | // Check that the correct segment is used to compute the base address |
242 | // when multiple segments are close together in the ELF file (closer |
243 | // than the required alignment in the process space). |
244 | // See https://github.com/llvm/llvm-project/issues/109384 |
245 | BC->SegmentMapInfo[0] = SegmentInfo{.Address: 0, .Size: 0x1d1c, .FileOffset: 0, .FileSize: 0x1d1c, .Alignment: 0x10000, .IsExecutable: false}; |
246 | BC->SegmentMapInfo[0x11d40] = |
247 | SegmentInfo{.Address: 0x11d40, .Size: 0x11e0, .FileOffset: 0x1d40, .FileSize: 0x11e0, .Alignment: 0x10000, .IsExecutable: true}; |
248 | BC->SegmentMapInfo[0x22f20] = |
249 | SegmentInfo{.Address: 0x22f20, .Size: 0x10e0, .FileOffset: 0x2f20, .FileSize: 0x1f0, .Alignment: 0x10000, .IsExecutable: false}; |
250 | BC->SegmentMapInfo[0x33110] = |
251 | SegmentInfo{.Address: 0x33110, .Size: 0x89, .FileOffset: 0x3110, .FileSize: 0x88, .Alignment: 0x10000, .IsExecutable: false}; |
252 | |
253 | std::optional<uint64_t> BaseAddress = |
254 | BC->getBaseAddressForMapping(MMapAddress: 0xaaaaaaab1000, FileOffset: 0x1000); |
255 | ASSERT_TRUE(BaseAddress.has_value()); |
256 | ASSERT_EQ(*BaseAddress, 0xaaaaaaaa0000ULL); |
257 | } |
258 | |