191 lines
6.9 KiB
C++
191 lines
6.9 KiB
C++
/*
|
|
* 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
|