|  | /* | 
|  | * 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 <inttypes.h> | 
|  |  | 
|  | #include <regex> | 
|  |  | 
|  | #include <sstream> | 
|  |  | 
|  | #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<uint8_t*>(0x0), | 
|  | reinterpret_cast<uint8_t*>(end_address), | 
|  | /* can_read_literals_= */ true, | 
|  | &Thread::DumpThreadOffset<PointerSize::k64>)); | 
|  | disasm.reset(new CustomDisassembler(&*disamOptions)); | 
|  | decoder.AppendVisitor(disasm.get()); | 
|  | masm.SetGenerateSimulatorCode(false); | 
|  | } | 
|  |  | 
|  | static constexpr size_t kMaxSizeGenerated = 1024; | 
|  |  | 
|  | template <typename LamdaType> | 
|  | 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 <typename LamdaType> | 
|  | 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<Instruction*>()-> | 
|  | GetInstructionAtOffset(-static_cast<signed>(kInstructionSize)); | 
|  |  | 
|  | decoder.Decode(targetInstruction); | 
|  |  | 
|  | const char* disassembly = disasm->GetOutput(); | 
|  |  | 
|  | if (!std::regex_match(disassembly, std::regex(EXP))) { | 
|  | const uint32_t encoding = static_cast<uint32_t>(targetInstruction->GetInstructionBits()); | 
|  |  | 
|  | printf("\nEncoding: %08" PRIx32 "\nExpected: %s\nFound:    %s\n", | 
|  | encoding, | 
|  | EXP, | 
|  | disassembly); | 
|  |  | 
|  | ADD_FAILURE(); | 
|  | } | 
|  | printf("----\n%s\n", disassembly); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<CustomDisassembler> disasm; | 
|  | std::unique_ptr<DisassemblerOptions> 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 |