/* * Copyright (C) 2022 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 #include #include #include "base/common_art_test.h" #include "disassembler_arm64.h" #include "thread.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wshadow" #include "aarch64/disasm-aarch64.h" #include "aarch64/macro-assembler-aarch64.h" #pragma GCC diagnostic pop using namespace vixl::aarch64; // NOLINT(build/namespaces) namespace art { namespace arm64 { /** * Fixture class for the ArtDisassemblerTest tests. */ class ArtDisassemblerTest : public CommonArtTest { public: ArtDisassemblerTest() { } void SetupAssembly(uint64_t end_address) { masm.GetCPUFeatures()->Combine(vixl::CPUFeatures::All()); disamOptions.reset(new DisassemblerOptions(/* absolute_addresses= */ true, reinterpret_cast(0x0), reinterpret_cast(end_address), /* can_read_literals_= */ true, &Thread::DumpThreadOffset)); disasm.reset(new CustomDisassembler(&*disamOptions)); decoder.AppendVisitor(disasm.get()); masm.SetGenerateSimulatorCode(false); } static constexpr size_t kMaxSizeGenerated = 1024; template void ImplantInstruction(LamdaType fn) { vixl::ExactAssemblyScope guard(&masm, kMaxSizeGenerated, vixl::ExactAssemblyScope::kMaximumSize); fn(); } // Appends an instruction to the existing buffer and then // attempts to match the output of that instructions disassembly // against a regex expression. Fails if no match is found. template void CompareInstruction(LamdaType fn, const char* EXP) { ImplantInstruction(fn); masm.FinalizeCode(); // This gets the last instruction in the buffer. // The end address of the buffer is at the end of the last instruction. // sizeof(Instruction) is 1 byte as it in an empty class. // Therefore we need to go back kInstructionSize * sizeof(Instruction) bytes // in order to get to the start of the last instruction. const Instruction* targetInstruction = masm.GetBuffer()->GetEndAddress()-> GetInstructionAtOffset(-static_cast(kInstructionSize)); decoder.Decode(targetInstruction); const char* disassembly = disasm->GetOutput(); if (!std::regex_match(disassembly, std::regex(EXP))) { const uint32_t encoding = static_cast(targetInstruction->GetInstructionBits()); printf("\nEncoding: %08" PRIx32 "\nExpected: %s\nFound: %s\n", encoding, EXP, disassembly); ADD_FAILURE(); } printf("----\n%s\n", disassembly); } std::unique_ptr disasm; std::unique_ptr disamOptions; Decoder decoder; MacroAssembler masm; }; #define IMPLANT(fn) \ do { \ ImplantInstruction([&]() { this->masm.fn; }); \ } while (0) #define COMPARE(fn, output) \ do { \ CompareInstruction([&]() { this->masm.fn; }, (output)); \ } while (0) // These tests map onto the named per instruction instrumentation functions in: // ART/art/disassembler/disassembler_arm.cc // Context can be found in the logic conditional on incoming instruction types and sequences in the // ART disassembler. As of writing the functionality we are testing for that of additional // diagnostic info being appended to the end of the ART disassembly output. TEST_F(ArtDisassemblerTest, LoadLiteralVisitBadAddress) { SetupAssembly(0xffffff); // Check we append an erroneous hint "(?)" for literal load instructions with // out of scope literal pool value addresses. COMPARE(ldr(x0, vixl::aarch64::Assembler::ImmLLiteral(1000)), "ldr x0, pc\\+128000 \\(addr -?0x[0-9a-fA-F]+\\) \\(\\?\\)"); } TEST_F(ArtDisassemblerTest, LoadLiteralVisit) { SetupAssembly(0xffffffffffffffff); // Test that we do not append anything for ineligible instruction. COMPARE(ldr(x0, MemOperand(x18, 0)), "ldr x0, \\[x18\\]$"); // Check we do append some extra info in the right text format for valid literal load instruction. COMPARE(ldr(w0, vixl::aarch64::Assembler::ImmLLiteral(0)), "ldr w0, pc\\+0 \\(addr -?0x[0-9a-f]+\\) \\(0x18000000 / 402653184\\)"); // We don't compare with exact value even though it's a known literal (the encoding of the // instruction itself) since the precision of printed floating point values could change. COMPARE(ldr(s0, vixl::aarch64::Assembler::ImmLLiteral(0)), "ldr s0, pc\\+0 \\(addr -?0x[0-9a-f]+\\) \\([0-9]+.[0-9]+e(\\+|-)[0-9]+\\)"); } TEST_F(ArtDisassemblerTest, LoadStoreUnsignedOffsetVisit) { SetupAssembly(0xffffffffffffffff); // Test that we do not append anything for ineligible instruction. COMPARE(ldr(x0, MemOperand(x18, 8)), "ldr x0, \\[x18, #8\\]$"); // Test that we do append the function name if the instruction is a load from the address // stored in the TR register. COMPARE(ldr(x0, MemOperand(x19, 8)), "ldr x0, \\[tr, #8\\] ; thin_lock_thread_id"); } TEST_F(ArtDisassemblerTest, UnconditionalBranchNoAppendVisit) { SetupAssembly(0xffffffffffffffff); vixl::aarch64::Label destination; masm.Bind(&destination); IMPLANT(ldr(x16, MemOperand(x18, 0))); // Test that we do not append anything for ineligible instruction. COMPARE(bl(&destination), "bl #-0x4 \\(addr -?0x[0-9a-f]+\\)$"); } TEST_F(ArtDisassemblerTest, UnconditionalBranchVisit) { SetupAssembly(0xffffffffffffffff); vixl::aarch64::Label destination; masm.Bind(&destination); IMPLANT(ldr(x16, MemOperand(x19, 0))); IMPLANT(br(x16)); // Test that we do append the function name if the instruction is a branch // to a load that reads data from the address in the TR register, into the IPO register // followed by a BR branching using the IPO register. COMPARE(bl(&destination), "bl #-0x8 \\(addr -?0x[0-9a-f]+\\) ; state_and_flags"); } } // namespace arm64 } // namespace art