summaryrefslogtreecommitdiff
path: root/disassembler
diff options
context:
space:
mode:
Diffstat (limited to 'disassembler')
-rw-r--r--disassembler/Android.bp36
-rw-r--r--disassembler/disassembler.cc1
-rw-r--r--disassembler/disassembler_arm64.cc40
-rw-r--r--disassembler/disassembler_arm64.h12
-rw-r--r--disassembler/disassembler_arm64_test.cc190
5 files changed, 266 insertions, 13 deletions
diff --git a/disassembler/Android.bp b/disassembler/Android.bp
index 4b55673bf0..b7f758ffdc 100644
--- a/disassembler/Android.bp
+++ b/disassembler/Android.bp
@@ -104,7 +104,6 @@ art_cc_library {
],
},
},
-
apex_available: [
"com.android.art",
"com.android.art.debug",
@@ -132,3 +131,38 @@ cc_library_headers {
"com.android.art",
],
}
+
+art_cc_defaults {
+ name: "art_disassembler_tests_defaults",
+ codegen: {
+ arm64: {
+ srcs: ["disassembler_arm64_test.cc"],
+ },
+ },
+}
+
+// Version of ART gtest `art_disassembler_tests` bundled with the ART APEX on target.
+// TODO(b/192274705): Remove this module when the migration to standalone ART gtests is complete.
+art_cc_test {
+ name: "art_disassembler_tests",
+ defaults: [
+ "art_gtest_defaults",
+ "art_disassembler_tests_defaults",
+ ],
+ static_libs: [
+ "libvixld",
+ ],
+}
+
+// Standalone version of ART gtest `art_disassembler_tests`,
+// not bundled with the ART APEX on target.
+art_cc_test {
+ name: "art_standalone_disassembler_tests",
+ defaults: [
+ "art_standalone_gtest_defaults",
+ "art_disassembler_tests_defaults",
+ ],
+ static_libs: [
+ "libvixl",
+ ],
+}
diff --git a/disassembler/disassembler.cc b/disassembler/disassembler.cc
index 53461ce5ae..a05183aed6 100644
--- a/disassembler/disassembler.cc
+++ b/disassembler/disassembler.cc
@@ -62,6 +62,7 @@ Disassembler* Disassembler::Create(InstructionSet instruction_set, DisassemblerO
return new x86::DisassemblerX86(options, /* supports_rex= */ true);
#endif
default:
+ UNUSED(options);
UNIMPLEMENTED(FATAL) << static_cast<uint32_t>(instruction_set);
UNREACHABLE();
}
diff --git a/disassembler/disassembler_arm64.cc b/disassembler/disassembler_arm64.cc
index 0d51cfdc1a..23472a8dca 100644
--- a/disassembler/disassembler_arm64.cc
+++ b/disassembler/disassembler_arm64.cc
@@ -18,6 +18,8 @@
#include <inttypes.h>
+#include <regex>
+
#include <sstream>
#include "android-base/logging.h"
@@ -58,9 +60,34 @@ void CustomDisassembler::AppendRegisterNameToOutput(const Instruction* instr,
Disassembler::AppendRegisterNameToOutput(instr, reg);
}
-void CustomDisassembler::VisitLoadLiteral(const Instruction* instr) {
- Disassembler::VisitLoadLiteral(instr);
+void CustomDisassembler::Visit(vixl::aarch64::Metadata* metadata, const Instruction* instr) {
+ vixl::aarch64::Disassembler::Visit(metadata, instr);
+ const std::string& form = (*metadata)["form"];
+
+ // These regexs are long, but it is an attempt to match the mapping entry keys in the
+ // #define DEFAULT_FORM_TO_VISITOR_MAP(VISITORCLASS) in the file
+ // external/vixl/src/aarch64/decoder-visitor-map-aarch64.h
+ // for the ::VisitLoadLiteralInstr, ::VisitLoadStoreUnsignedOffset or ::VisitUnconditionalBranch
+ // function addresess key values.
+ // N.B. the mapping are many to one.
+ if (std::regex_match(form, std::regex("(ldrsw|ldr|prfm)_(32|64|d|b|h|q|s)_loadlit"))) {
+ VisitLoadLiteralInstr(instr);
+ return;
+ }
+ if (std::regex_match(form, std::regex(
+ "(ldrb|ldrh|ldrsb|ldrsh|ldrsw|ldr|prfm|strb|strh|str)_(32|64|d|b|h|q|s)_ldst_pos"))) {
+ VisitLoadStoreUnsignedOffsetInstr(instr);
+ return;
+ }
+
+ if (std::regex_match(form, std::regex("(bl|b)_only_branch_imm"))) {
+ VisitUnconditionalBranchInstr(instr);
+ return;
+ }
+}
+
+void CustomDisassembler::VisitLoadLiteralInstr(const Instruction* instr) {
if (!read_literals_) {
return;
}
@@ -69,6 +96,7 @@ void CustomDisassembler::VisitLoadLiteral(const Instruction* instr) {
// avoid trying to fetch invalid literals (we can encounter this when
// interpreting raw data as instructions).
void* data_address = instr->GetLiteralAddress<void*>();
+
if (data_address < base_address_ || data_address >= end_address_) {
AppendToOutput(" (?)");
return;
@@ -97,17 +125,13 @@ void CustomDisassembler::VisitLoadLiteral(const Instruction* instr) {
}
}
-void CustomDisassembler::VisitLoadStoreUnsignedOffset(const Instruction* instr) {
- Disassembler::VisitLoadStoreUnsignedOffset(instr);
-
+void CustomDisassembler::VisitLoadStoreUnsignedOffsetInstr(const Instruction* instr) {
if (instr->GetRn() == TR) {
AppendThreadOfsetName(instr);
}
}
-void CustomDisassembler::VisitUnconditionalBranch(const Instruction* instr) {
- Disassembler::VisitUnconditionalBranch(instr);
-
+void CustomDisassembler::VisitUnconditionalBranchInstr(const Instruction* instr) {
if (instr->Mask(UnconditionalBranchMask) == BL) {
const Instruction* target = instr->GetImmPCOffsetTarget();
if (target >= base_address_ &&
diff --git a/disassembler/disassembler_arm64.h b/disassembler/disassembler_arm64.h
index a895dfe823..d0443d2b39 100644
--- a/disassembler/disassembler_arm64.h
+++ b/disassembler/disassembler_arm64.h
@@ -47,16 +47,20 @@ class CustomDisassembler final : public vixl::aarch64::Disassembler {
void AppendRegisterNameToOutput(const vixl::aarch64::Instruction* instr,
const vixl::aarch64::CPURegister& reg) override;
+ // Intercepts the instruction flow captured by the parent method,
+ // to specially instrument for particular instruction types.
+ void Visit(vixl::aarch64::Metadata* metadata, const vixl::aarch64::Instruction* instr) override;
+
+ private:
// Improve the disassembly of literal load instructions.
- void VisitLoadLiteral(const vixl::aarch64::Instruction* instr) override;
+ void VisitLoadLiteralInstr(const vixl::aarch64::Instruction* instr);
// Improve the disassembly of thread offset.
- void VisitLoadStoreUnsignedOffset(const vixl::aarch64::Instruction* instr) override;
+ void VisitLoadStoreUnsignedOffsetInstr(const vixl::aarch64::Instruction* instr);
// Improve the disassembly of branch to thunk jumping to pointer from thread entrypoint.
- void VisitUnconditionalBranch(const vixl::aarch64::Instruction* instr) override;
+ void VisitUnconditionalBranchInstr(const vixl::aarch64::Instruction* instr);
- private:
void AppendThreadOfsetName(const vixl::aarch64::Instruction* instr);
// Indicate if the disassembler should read data loaded from literal pools.
diff --git a/disassembler/disassembler_arm64_test.cc b/disassembler/disassembler_arm64_test.cc
new file mode 100644
index 0000000000..81c067ab56
--- /dev/null
+++ b/disassembler/disassembler_arm64_test.cc
@@ -0,0 +1,190 @@
+/*
+ * 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