//===- bolt/unittest/Core/BinaryContext.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 // //===----------------------------------------------------------------------===// #include "bolt/Core/BinaryContext.h" #include "llvm/BinaryFormat/ELF.h" #include "llvm/DebugInfo/DWARF/DWARFContext.h" #include "llvm/Support/TargetSelect.h" #include "gtest/gtest.h" using namespace llvm; using namespace llvm::object; using namespace llvm::ELF; using namespace bolt; namespace { struct BinaryContextTester : public testing::TestWithParam { void SetUp() override { initalizeLLVM(); prepareElf(); initializeBOLT(); } protected: void initalizeLLVM() { llvm::InitializeAllTargetInfos(); llvm::InitializeAllTargetMCs(); llvm::InitializeAllAsmParsers(); llvm::InitializeAllDisassemblers(); llvm::InitializeAllTargets(); llvm::InitializeAllAsmPrinters(); } void prepareElf() { memcpy(ElfBuf, "\177ELF", 4); ELF64LE::Ehdr *EHdr = reinterpret_cast(ElfBuf); EHdr->e_ident[llvm::ELF::EI_CLASS] = llvm::ELF::ELFCLASS64; EHdr->e_ident[llvm::ELF::EI_DATA] = llvm::ELF::ELFDATA2LSB; EHdr->e_machine = GetParam() == Triple::aarch64 ? EM_AARCH64 : EM_X86_64; MemoryBufferRef Source(StringRef(ElfBuf, sizeof(ElfBuf)), "ELF"); ObjFile = cantFail(ObjectFile::createObjectFile(Source)); } void initializeBOLT() { Relocation::Arch = ObjFile->makeTriple().getArch(); BC = cantFail(BinaryContext::createBinaryContext( ObjFile->makeTriple(), std::make_shared(), ObjFile->getFileName(), nullptr, true, DWARFContext::create(*ObjFile.get()), {llvm::outs(), llvm::errs()})); ASSERT_FALSE(!BC); } char ElfBuf[sizeof(typename ELF64LE::Ehdr)] = {}; std::unique_ptr ObjFile; std::unique_ptr BC; }; } // namespace #ifdef X86_AVAILABLE INSTANTIATE_TEST_SUITE_P(X86, BinaryContextTester, ::testing::Values(Triple::x86_64)); #endif #ifdef AARCH64_AVAILABLE INSTANTIATE_TEST_SUITE_P(AArch64, BinaryContextTester, ::testing::Values(Triple::aarch64)); TEST_P(BinaryContextTester, FlushPendingRelocCALL26) { if (GetParam() != Triple::aarch64) GTEST_SKIP(); // This test checks that encodeValueAArch64 used by flushPendingRelocations // returns correctly encoded values for CALL26 relocation for both backward // and forward branches. // // The offsets layout is: // 4: func1 // 8: bl func1 // 12: bl func2 // 16: func2 constexpr size_t DataSize = 20; uint8_t *Data = new uint8_t[DataSize]; BinarySection &BS = BC->registerOrUpdateSection( ".text", ELF::SHT_PROGBITS, ELF::SHF_EXECINSTR | ELF::SHF_ALLOC, Data, DataSize, 4); MCSymbol *RelSymbol1 = BC->getOrCreateGlobalSymbol(4, "Func1"); ASSERT_TRUE(RelSymbol1); BS.addRelocation(8, RelSymbol1, ELF::R_AARCH64_CALL26, 0, 0, true); MCSymbol *RelSymbol2 = BC->getOrCreateGlobalSymbol(16, "Func2"); ASSERT_TRUE(RelSymbol2); BS.addRelocation(12, RelSymbol2, ELF::R_AARCH64_CALL26, 0, 0, true); std::error_code EC; SmallVector Vect(DataSize); raw_svector_ostream OS(Vect); BS.flushPendingRelocations(OS, [&](const MCSymbol *S) { return S == RelSymbol1 ? 4 : S == RelSymbol2 ? 16 : 0; }); const uint8_t Func1Call[4] = {255, 255, 255, 151}; const uint8_t Func2Call[4] = {1, 0, 0, 148}; EXPECT_FALSE(memcmp(Func1Call, &Vect[8], 4)) << "Wrong backward call value\n"; EXPECT_FALSE(memcmp(Func2Call, &Vect[12], 4)) << "Wrong forward call value\n"; } TEST_P(BinaryContextTester, FlushPendingRelocJUMP26) { if (GetParam() != Triple::aarch64) GTEST_SKIP(); // This test checks that encodeValueAArch64 used by flushPendingRelocations // returns correctly encoded values for R_AARCH64_JUMP26 relocation for both // backward and forward branches. // // The offsets layout is: // 4: func1 // 8: b func1 // 12: b func2 // 16: func2 const uint64_t Size = 20; char *Data = new char[Size]; BinarySection &BS = BC->registerOrUpdateSection( ".text", ELF::SHT_PROGBITS, ELF::SHF_EXECINSTR | ELF::SHF_ALLOC, (uint8_t *)Data, Size, 4); MCSymbol *RelSymbol1 = BC->getOrCreateGlobalSymbol(4, "Func1"); ASSERT_TRUE(RelSymbol1); BS.addRelocation(8, RelSymbol1, ELF::R_AARCH64_JUMP26, 0, 0, true); MCSymbol *RelSymbol2 = BC->getOrCreateGlobalSymbol(16, "Func2"); ASSERT_TRUE(RelSymbol2); BS.addRelocation(12, RelSymbol2, ELF::R_AARCH64_JUMP26, 0, 0, true); std::error_code EC; SmallVector Vect(Size); raw_svector_ostream OS(Vect); BS.flushPendingRelocations(OS, [&](const MCSymbol *S) { return S == RelSymbol1 ? 4 : S == RelSymbol2 ? 16 : 0; }); const uint8_t Func1Call[4] = {255, 255, 255, 23}; const uint8_t Func2Call[4] = {1, 0, 0, 20}; EXPECT_FALSE(memcmp(Func1Call, &Vect[8], 4)) << "Wrong backward branch value\n"; EXPECT_FALSE(memcmp(Func2Call, &Vect[12], 4)) << "Wrong forward branch value\n"; } #endif TEST_P(BinaryContextTester, BaseAddress) { // Check that base address calculation is correct for a binary with the // following segment layout: BC->SegmentMapInfo[0] = SegmentInfo{0, 0x10e8c2b4, 0, 0x10e8c2b4, 0x1000, true}; BC->SegmentMapInfo[0x10e8d2b4] = SegmentInfo{0x10e8d2b4, 0x3952faec, 0x10e8c2b4, 0x3952faec, 0x1000, true}; BC->SegmentMapInfo[0x4a3bddc0] = SegmentInfo{0x4a3bddc0, 0x148e828, 0x4a3bbdc0, 0x148e828, 0x1000, true}; BC->SegmentMapInfo[0x4b84d5e8] = SegmentInfo{0x4b84d5e8, 0x294f830, 0x4b84a5e8, 0x3d3820, 0x1000, true}; std::optional BaseAddress = BC->getBaseAddressForMapping(0x7f13f5556000, 0x10e8c000); ASSERT_TRUE(BaseAddress.has_value()); ASSERT_EQ(*BaseAddress, 0x7f13e46c9000ULL); BaseAddress = BC->getBaseAddressForMapping(0x7f13f5556000, 0x137a000); ASSERT_FALSE(BaseAddress.has_value()); } TEST_P(BinaryContextTester, BaseAddress2) { // Check that base address calculation is correct for a binary if the // alignment in ELF file are different from pagesize. // The segment layout is as follows: BC->SegmentMapInfo[0] = SegmentInfo{0, 0x2177c, 0, 0x2177c, 0x10000, true}; BC->SegmentMapInfo[0x31860] = SegmentInfo{0x31860, 0x370, 0x21860, 0x370, 0x10000, true}; BC->SegmentMapInfo[0x41c20] = SegmentInfo{0x41c20, 0x1f8, 0x21c20, 0x1f8, 0x10000, true}; BC->SegmentMapInfo[0x54e18] = SegmentInfo{0x54e18, 0x51, 0x24e18, 0x51, 0x10000, true}; std::optional BaseAddress = BC->getBaseAddressForMapping(0xaaaaea444000, 0x21000); ASSERT_TRUE(BaseAddress.has_value()); ASSERT_EQ(*BaseAddress, 0xaaaaea413000ULL); BaseAddress = BC->getBaseAddressForMapping(0xaaaaea444000, 0x11000); ASSERT_FALSE(BaseAddress.has_value()); } TEST_P(BinaryContextTester, BaseAddressSegmentsSmallerThanAlignment) { // Check that the correct segment is used to compute the base address // when multiple segments are close together in the ELF file (closer // than the required alignment in the process space). // See https://github.com/llvm/llvm-project/issues/109384 BC->SegmentMapInfo[0] = SegmentInfo{0, 0x1d1c, 0, 0x1d1c, 0x10000, false}; BC->SegmentMapInfo[0x11d40] = SegmentInfo{0x11d40, 0x11e0, 0x1d40, 0x11e0, 0x10000, true}; BC->SegmentMapInfo[0x22f20] = SegmentInfo{0x22f20, 0x10e0, 0x2f20, 0x1f0, 0x10000, false}; BC->SegmentMapInfo[0x33110] = SegmentInfo{0x33110, 0x89, 0x3110, 0x88, 0x10000, false}; std::optional BaseAddress = BC->getBaseAddressForMapping(0xaaaaaaab1000, 0x1000); ASSERT_TRUE(BaseAddress.has_value()); ASSERT_EQ(*BaseAddress, 0xaaaaaaaa0000ULL); }