/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "relative_patcher_riscv64.h" #include "linker/linker_patch.h" #include "linker/relative_patcher_test.h" #include "oat/oat_quick_method_header.h" namespace art { namespace linker { class Riscv64RelativePatcherTest : public RelativePatcherTest { public: Riscv64RelativePatcherTest() : RelativePatcherTest(InstructionSet::kRiscv64, "default") { } protected: // C.NOP instruction. static constexpr uint32_t kCNopInsn = 0x0001u; static constexpr size_t kCNopSize = 2u; // Placeholder instructions with unset (zero) registers and immediates. static constexpr uint32_t kAuipcInsn = 0x00000017u; static constexpr uint32_t kAddiInsn = 0x00000013u; static constexpr uint32_t kLwuInsn = 0x00006003u; static constexpr uint32_t kLdInsn = 0x00003003u; // Placeholder offset encoded in AUIPC and used before patching. static constexpr uint32_t kUnpatchedOffset = 0x12345678u; static void PushBackInsn16(std::vector* code, uint32_t insn16) { const uint8_t insn_code[] = { static_cast(insn16), static_cast(insn16 >> 8), }; code->insert(code->end(), insn_code, insn_code + sizeof(insn_code)); } static void PushBackInsn32(std::vector* code, uint32_t insn32) { const uint8_t insn_code[] = { static_cast(insn32), static_cast(insn32 >> 8), static_cast(insn32 >> 16), static_cast(insn32 >> 24), }; code->insert(code->end(), insn_code, insn_code + sizeof(insn_code)); } uint32_t GetMethodOffset(uint32_t method_idx) { auto result = method_offset_map_.FindMethodOffset(MethodRef(method_idx)); CHECK(result.first); CHECK_ALIGNED(result.second, 4u); return result.second; } static constexpr uint32_t ExtractRs1ToRd(uint32_t insn) { // The `rs1` is in bits 15..19 and we need to move it to bits 7..11. return (insn >> (15 - 7)) & (0x1fu << 7); } std::vector GenNopsAndAuipcAndUse(size_t start_cnops, size_t mid_cnops, uint32_t method_offset, uint32_t target_offset, uint32_t use_insn) { CHECK_ALIGNED(method_offset, 4u); uint32_t auipc_offset = method_offset + start_cnops * kCNopSize; uint32_t offset = target_offset - auipc_offset; if (offset != /* unpatched */ 0x12345678u) { CHECK_ALIGNED(target_offset, 4u); } CHECK_EQ(use_insn & 0xfff00000u, 0u); // Prepare `imm12` for `use_insn` and `imm20` for AUIPC, adjusted for sign-extension of `imm12`. uint32_t imm12 = offset & 0xfffu; uint32_t imm20 = (offset >> 12) + ((offset >> 11) & 1u); // Prepare the AUIPC and use instruction. DCHECK_EQ(use_insn & 0xfff00000u, 0u); // Check that `imm12` in `use_insn` is empty. use_insn |= imm12 << 20; // Update `imm12` in `use_insn`. uint32_t auipc = kAuipcInsn | // AUIPC rd, imm20 ExtractRs1ToRd(use_insn) | // where `rd` is `rs1` from `use_insn`. (imm20 << 12); // Create the code. std::vector result; result.reserve((start_cnops + mid_cnops) * kCNopSize + 8u); for (size_t i = 0; i != start_cnops; ++i) { PushBackInsn16(&result, kCNopInsn); } PushBackInsn32(&result, auipc); for (size_t i = 0; i != mid_cnops; ++i) { PushBackInsn16(&result, kCNopInsn); } PushBackInsn32(&result, use_insn); return result; } std::vector GenNopsAndAuipcAndUseUnpatched(size_t start_cnops, size_t mid_cnops, uint32_t use_insn) { uint32_t target_offset = start_cnops * kCNopSize + kUnpatchedOffset; return GenNopsAndAuipcAndUse(start_cnops, mid_cnops, 0u, target_offset, use_insn); } void TestNopsAuipcAddi(size_t start_cnops, size_t mid_cnops, uint32_t string_offset) { constexpr uint32_t kStringIndex = 1u; string_index_to_offset_map_.Put(kStringIndex, string_offset); constexpr uint32_t kAddi = kAddiInsn | (10 << 15) | (11 << 7); // ADDI A1, A0, auto code = GenNopsAndAuipcAndUseUnpatched(start_cnops, mid_cnops, kAddi); size_t auipc_offset = start_cnops * kCNopSize; size_t addi_offset = auipc_offset + 4u + mid_cnops * kCNopSize; const LinkerPatch patches[] = { LinkerPatch::RelativeStringPatch(auipc_offset, nullptr, auipc_offset, kStringIndex), LinkerPatch::RelativeStringPatch(addi_offset, nullptr, auipc_offset, kStringIndex), }; AddCompiledMethod(MethodRef(1u), ArrayRef(code), ArrayRef(patches)); Link(); uint32_t method1_offset = GetMethodOffset(1u); auto expected_code = GenNopsAndAuipcAndUse(start_cnops, mid_cnops, method1_offset, string_offset, kAddi); EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef(expected_code))); } void TestNopsAuipcLwu( size_t start_cnops, size_t mid_cnops, uint32_t bss_begin, uint32_t string_entry_offset) { constexpr uint32_t kStringIndex = 1u; string_index_to_offset_map_.Put(kStringIndex, string_entry_offset); bss_begin_ = bss_begin; constexpr uint32_t kLwu = kLwuInsn | (10 << 15) | (10 << 7); // LWU A0, A0, auto code = GenNopsAndAuipcAndUseUnpatched(start_cnops, mid_cnops, kLwu); size_t auipc_offset = start_cnops * kCNopSize; size_t lwu_offset = auipc_offset + 4u + mid_cnops * kCNopSize; const LinkerPatch patches[] = { LinkerPatch::StringBssEntryPatch(auipc_offset, nullptr, auipc_offset, kStringIndex), LinkerPatch::StringBssEntryPatch(lwu_offset, nullptr, auipc_offset, kStringIndex), }; AddCompiledMethod(MethodRef(1u), ArrayRef(code), ArrayRef(patches)); Link(); uint32_t method1_offset = GetMethodOffset(1u); uint32_t target_offset = bss_begin_ + string_entry_offset; auto expected_code = GenNopsAndAuipcAndUse(start_cnops, mid_cnops, method1_offset, target_offset, kLwu); EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef(expected_code))); } void TestNopsAuipcLd( size_t start_cnops, size_t mid_cnops, uint32_t bss_begin, uint32_t method_entry_offset) { constexpr uint32_t kMethodIndex = 100u; method_index_to_offset_map_.Put(kMethodIndex, method_entry_offset); bss_begin_ = bss_begin; constexpr uint32_t kLd = kLdInsn | (11 << 15) | (10 << 7); // LD A0, A1, auto code = GenNopsAndAuipcAndUseUnpatched(start_cnops, mid_cnops, kLd); size_t auipc_offset = start_cnops * kCNopSize; size_t ld_offset = auipc_offset + 4u + mid_cnops * kCNopSize; const LinkerPatch patches[] = { LinkerPatch::MethodBssEntryPatch(auipc_offset, nullptr, auipc_offset, kMethodIndex), LinkerPatch::MethodBssEntryPatch(ld_offset, nullptr, auipc_offset, kMethodIndex), }; AddCompiledMethod(MethodRef(1u), ArrayRef(code), ArrayRef(patches)); Link(); uint32_t method1_offset = GetMethodOffset(1u); uint32_t target_offset = bss_begin_ + method_entry_offset; auto expected_code = GenNopsAndAuipcAndUse(start_cnops, mid_cnops, method1_offset, target_offset, kLd); EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef(expected_code))); } }; TEST_F(Riscv64RelativePatcherTest, StringReference) { for (size_t start_cnops : {0, 1, 2, 3, 4, 5, 6, 7}) { for (size_t mid_cnops : {0, 1, 2, 3, 4, 5, 6, 7}) { for (uint32_t string_offset : { 0x12345678u, -0x12345678u, 0x123457fcu, 0x12345800u}) { Reset(); TestNopsAuipcAddi(start_cnops, mid_cnops, string_offset); } } } } TEST_F(Riscv64RelativePatcherTest, StringBssEntry) { for (size_t start_cnops : {0, 1, 2, 3, 4, 5, 6, 7}) { for (size_t mid_cnops : {0, 1, 2, 3, 4, 5, 6, 7}) { for (uint32_t bss_begin : { 0x12345678u, -0x12345678u, 0x10000000u, 0x12345000u }) { for (uint32_t string_entry_offset : { 0x1234u, 0x4444u, 0x37fcu, 0x3800u }) { Reset(); TestNopsAuipcLwu(start_cnops, mid_cnops, bss_begin, string_entry_offset); } } } } } TEST_F(Riscv64RelativePatcherTest, MethodBssEntry) { for (size_t start_cnops : {0, 1, 2, 3, 4, 5, 6, 7}) { for (size_t mid_cnops : {0, 1, 2, 3, 4, 5, 6, 7}) { for (uint32_t bss_begin : { 0x12345678u, -0x12345678u, 0x10000000u, 0x12345000u }) { for (uint32_t method_entry_offset : { 0x1234u, 0x4444u, 0x37f8u, 0x3800u }) { Reset(); TestNopsAuipcLd(start_cnops, mid_cnops, bss_begin, method_entry_offset); } } } } } } // namespace linker } // namespace art