blob: 3ff79b13331e9862def188cf567da97d0ca5f7e2 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/developer/debug/zxdb/client/disassembler.h"
#include <iterator>
#include <string>
#include <gtest/gtest.h>
#include "src/developer/debug/zxdb/client/arch_info.h"
#include "src/developer/debug/zxdb/client/memory_dump.h"
namespace zxdb {
using Row = Disassembler::Row;
TEST(Disassembler, X64Individual) {
ArchInfo arch;
Err err = arch.Init(debug::Arch::kX64);
ASSERT_FALSE(err.has_error()) << err.msg();
Disassembler d;
err = d.Init(&arch);
ASSERT_FALSE(err.has_error()) << err.msg();
Disassembler::Options opts;
Row out;
// "int3".
const uint8_t int3_data[1] = {0xCC};
size_t consumed = d.DisassembleOne(int3_data, std::size(int3_data), 0x1234567890, opts, &out);
EXPECT_EQ(1u, consumed);
EXPECT_EQ(std::vector<uint8_t>({0xcc}), out.bytes);
EXPECT_EQ("int3", out.op);
EXPECT_EQ("", out.params); // Params.
EXPECT_EQ("", out.comment); // Comment.
// "mov edi, 0x28e5e0" with bytes and address.
const uint8_t mov_data[5] = {0xbf, 0xe0, 0xe5, 0x28, 0x00};
consumed = d.DisassembleOne(mov_data, std::size(mov_data), 0x1234, opts, &out);
EXPECT_EQ(5u, consumed);
EXPECT_EQ(std::vector<uint8_t>({0xbf, 0xe0, 0xe5, 0x28, 0x00}), out.bytes);
EXPECT_EQ("mov", out.op);
EXPECT_EQ("edi, 0x28e5e0", out.params); // Params.
EXPECT_EQ("", out.comment); // Comment.
}
TEST(Disassembler, X64Undecodable) {
ArchInfo arch;
Err err = arch.Init(debug::Arch::kX64);
ASSERT_FALSE(err.has_error()) << err.msg();
Disassembler d;
err = d.Init(&arch);
ASSERT_FALSE(err.has_error()) << err.msg();
Disassembler::Options opts;
Row out;
// This instruction is "mov edi, 0x28e5e0". Cutting this shorter will give
// undecodable instructions.
const uint8_t mov_data[5] = {0xbf, 0xe0, 0xe5, 0x28, 0x00};
// Check with no emitting undecodable.
opts.emit_undecodable = false;
size_t consumed = d.DisassembleOne(mov_data, std::size(mov_data) - 1, 0x1234, opts, &out);
EXPECT_EQ(0u, consumed);
EXPECT_TRUE(out.op.empty());
// Emit undecodable. On X64 this will consume one byte.
opts.emit_undecodable = true;
consumed = d.DisassembleOne(mov_data, std::size(mov_data) - 1, 0x1234, opts, &out);
EXPECT_EQ(1u, consumed);
EXPECT_EQ(std::vector<uint8_t>({0xbf}), out.bytes);
EXPECT_EQ(".byte", out.op);
EXPECT_EQ("0xbf", out.params);
EXPECT_EQ("# Invalid instruction.", out.comment);
}
TEST(Disassembler, X64Many) {
ArchInfo arch;
Err err = arch.Init(debug::Arch::kX64);
ASSERT_FALSE(err.has_error()) << err.msg();
Disassembler d;
err = d.Init(&arch);
ASSERT_FALSE(err.has_error()) << err.msg();
Disassembler::Options opts;
std::vector<Row> out;
const uint8_t data[] = {
0xbf, 0xe0, 0xe5, 0x28, 0x00, // mov edi, 0x28e5e0
0x48, 0x89, 0xde, // mov rsi, rbx
0x48, 0x8d, 0x7c, 0x24, 0x0c // lea rdi, [rsp + 0xc]
};
// Full block.
size_t consumed = d.DisassembleMany(data, std::size(data), 0x123456780, opts, 0, &out);
EXPECT_EQ(std::size(data), consumed);
ASSERT_EQ(3u, out.size());
EXPECT_EQ(Row(0x123456780, &data[0], 5, "mov", "edi, 0x28e5e0", ""), out[0]);
EXPECT_EQ(Row(0x123456785, &data[5], 3, "mov", "rsi, rbx", ""), out[1]);
EXPECT_EQ(Row(0x123456788, &data[8], 5, "lea", "rdi, [rsp + 0xc]", ""), out[2]);
// Limit the number of instructions.
out.clear();
consumed = d.DisassembleMany(data, std::size(data), 0x123456780, opts, 2, &out);
EXPECT_EQ(8u, consumed);
ASSERT_EQ(2u, out.size());
EXPECT_EQ(Row(0x123456780, &data[0], 5, "mov", "edi, 0x28e5e0", ""), out[0]);
EXPECT_EQ(Row(0x123456785, &data[5], 3, "mov", "rsi, rbx", ""), out[1]);
// Have 3 bytes off the end.
opts.emit_undecodable = false; // Should be overridden.
out.clear();
consumed = d.DisassembleMany(data, std::size(data) - 3, 0x123456780, opts, 0, &out);
EXPECT_EQ(std::size(data) - 3, consumed);
ASSERT_EQ(4u, out.size());
EXPECT_EQ(Row(0x123456780, &data[0], 5, "mov", "edi, 0x28e5e0", ""), out[0]);
EXPECT_EQ(Row(0x123456785, &data[5], 3, "mov", "rsi, rbx", ""), out[1]);
EXPECT_EQ(Row(0x123456788, &data[8], 1, ".byte", "0x48", "# Invalid instruction."), out[2]);
EXPECT_EQ(Row(0x123456789, &data[9], 1, ".byte", "0x8d", "# Invalid instruction."), out[3]);
}
TEST(Disassembler, Dump) {
ArchInfo arch;
Err err = arch.Init(debug::Arch::kX64);
ASSERT_FALSE(err.has_error()) << err.msg();
Disassembler d;
err = d.Init(&arch);
ASSERT_FALSE(err.has_error()) << err.msg();
Disassembler::Options opts;
std::vector<Row> out;
// Make a little memory block with valid instructions in it.
debug_ipc::MemoryBlock block_with_data;
block_with_data.address = 0;
block_with_data.valid = true;
block_with_data.data = std::vector<uint8_t>{
0xbf, 0xe0, 0xe5, 0x28, 0x00, // mov edi, 0x28e5e0
0x48, 0x89, 0xde, // mov rsi, rbx
0x48, 0x8d, 0x7c, 0x24, 0x0c // lea rdi, [rsp + 0xc]
};
block_with_data.size = static_cast<uint32_t>(block_with_data.data.size());
// Two valid memory regions that just follow on each other. This sets a limit on the total
// instructions.
std::vector<debug_ipc::MemoryBlock> vect;
vect.push_back(block_with_data);
vect.push_back(block_with_data);
constexpr uint64_t start_address = 0x123456780;
vect[0].address = start_address;
vect[1].address = vect[0].address + vect[0].size;
MemoryDump dump(std::move(vect));
size_t consumed = d.DisassembleDump(dump, start_address, opts, 5, &out);
EXPECT_EQ(21u, consumed);
ASSERT_EQ(5u, out.size());
EXPECT_EQ(Row(0x123456780, &block_with_data.data[0], 5, "mov", "edi, 0x28e5e0", ""), out[0]);
EXPECT_EQ(Row(0x123456785, &block_with_data.data[5], 3, "mov", "rsi, rbx", ""), out[1]);
EXPECT_EQ(Row(0x123456788, &block_with_data.data[8], 5, "lea", "rdi, [rsp + 0xc]", ""), out[2]);
EXPECT_EQ(Row(0x12345678d, &block_with_data.data[0], 5, "mov", "edi, 0x28e5e0", ""), out[3]);
EXPECT_EQ(Row(0x123456792, &block_with_data.data[5], 3, "mov", "rsi, rbx", ""), out[4]);
// Empty dump (with one block but 0 size).
out.clear();
dump = MemoryDump(std::vector<debug_ipc::MemoryBlock>());
consumed = d.DisassembleDump(dump, start_address, opts, 0, &out);
EXPECT_EQ(0u, consumed);
EXPECT_EQ(0u, out.size());
// Test a memory dump that's completely invalid.
debug_ipc::MemoryBlock invalid_block;
invalid_block.address = start_address;
invalid_block.valid = false;
invalid_block.size = 16;
out.clear();
dump = MemoryDump(std::vector<debug_ipc::MemoryBlock>{invalid_block});
consumed = d.DisassembleDump(dump, start_address, opts, 0, &out);
EXPECT_EQ(invalid_block.size, consumed);
ASSERT_EQ(1u, out.size());
EXPECT_EQ(Row(start_address, nullptr, 0, "??", "", "# Invalid memory @ 0x123456780"), out[0]);
// Test two valid memory blocks with a sandwich of invalid in-between.
vect.clear();
vect.push_back(block_with_data);
vect.push_back(invalid_block);
vect.push_back(block_with_data);
vect[0].address = start_address;
vect[1].address = vect[0].address + vect[0].size;
vect[2].address = vect[1].address + vect[1].size;
size_t total_bytes = vect[2].address + vect[2].size - vect[0].address;
out.clear();
dump = MemoryDump(std::move(vect));
consumed = d.DisassembleDump(dump, start_address, opts, 0, &out);
EXPECT_EQ(total_bytes, consumed);
ASSERT_EQ(7u, out.size());
EXPECT_EQ(Row(0x123456780, &block_with_data.data[0], 5, "mov", "edi, 0x28e5e0", ""), out[0]);
EXPECT_EQ(Row(0x123456785, &block_with_data.data[5], 3, "mov", "rsi, rbx", ""), out[1]);
EXPECT_EQ(Row(0x123456788, &block_with_data.data[8], 5, "lea", "rdi, [rsp + 0xc]", ""), out[2]);
EXPECT_EQ(Row(0x12345678d, nullptr, 0, "??", "", "# Invalid memory @ 0x12345678d - 0x12345679c"),
out[3]);
EXPECT_EQ(Row(0x12345679d, &block_with_data.data[0], 5, "mov", "edi, 0x28e5e0", ""), out[4]);
EXPECT_EQ(Row(0x1234567a2, &block_with_data.data[5], 3, "mov", "rsi, rbx", ""), out[5]);
EXPECT_EQ(Row(0x1234567a5, &block_with_data.data[8], 5, "lea", "rdi, [rsp + 0xc]", ""), out[6]);
}
TEST(Disassembler, Arm64Many) {
ArchInfo arch;
Err err = arch.Init(debug::Arch::kArm64);
ASSERT_FALSE(err.has_error()) << err.msg();
Disassembler d;
err = d.Init(&arch);
ASSERT_FALSE(err.has_error()) << err.msg();
std::vector<Row> out;
const uint8_t data[] = {
0xf3, 0x0f, 0x1e, 0xf8, // str x19, [sp, #-0x20]!
0xfd, 0x7b, 0x01, 0xa9, // stp x29, x30, [sp, #0x10]
0xfd, 0x43, 0x00, 0x91 // add x29, sp, #16
};
Disassembler::Options opts;
size_t consumed = d.DisassembleMany(data, std::size(data), 0x123456780, opts, 0, &out);
EXPECT_EQ(std::size(data), consumed);
ASSERT_EQ(3u, out.size());
EXPECT_EQ(Row(0x123456780, &data[0], 4, "str", "x19, [sp, #-0x20]!", ""), out[0]);
EXPECT_EQ(Row(0x123456784, &data[4], 4, "stp", "x29, x30, [sp, #0x10]", ""), out[1]);
#if defined(LLVM_USING_OLD_PREBUILT)
EXPECT_EQ(Row(0x123456788, &data[8], 4, "add", "x29, sp, #0x10", "// =0x10"), out[2]);
#else
EXPECT_EQ(Row(0x123456788, &data[8], 4, "add", "x29, sp, #0x10", ""), out[2]);
#endif
// Test an instruction off the end.
out.clear();
consumed = d.DisassembleMany(data, std::size(data) - 1, 0x123456780, opts, 0, &out);
EXPECT_EQ(std::size(data) - 1, consumed);
ASSERT_EQ(3u, out.size());
EXPECT_EQ(Row(0x123456780, &data[0], 4, "str", "x19, [sp, #-0x20]!", ""), out[0]);
EXPECT_EQ(Row(0x123456784, &data[4], 4, "stp", "x29, x30, [sp, #0x10]", ""), out[1]);
EXPECT_EQ(Row(0x123456788, &data[8], 3, ".byte", "0xfd 0x43 0x00", "// Invalid instruction."),
out[2]);
}
TEST(Disassembler, CallX64) {
ArchInfo arch;
Err err = arch.Init(debug::Arch::kX64);
ASSERT_FALSE(err.has_error()) << err.msg();
Disassembler d;
err = d.Init(&arch);
ASSERT_FALSE(err.has_error()) << err.msg();
Disassembler::Options opts;
std::vector<Row> out;
// Make a little memory block with valid instructions in it.
debug_ipc::MemoryBlock block_with_data;
block_with_data.address = 0x123456780;
block_with_data.valid = true;
block_with_data.data = std::vector<uint8_t>{
0xe8, 0xce, 0x00, 0x00, 0x00, // call +0xce (relative to next instruction).
0xe8, 0xf4, 0xff, 0xff, 0xff, // call -0x0c (relative to next instruction).
0xff, 0xd0 // call rax (indirect call to register value).
};
block_with_data.size = static_cast<uint32_t>(block_with_data.data.size());
std::vector<debug_ipc::MemoryBlock> vect;
vect.push_back(block_with_data);
MemoryDump dump(std::move(vect));
size_t consumed = d.DisassembleDump(dump, block_with_data.address, opts, 0, &out);
EXPECT_EQ(12u, consumed);
ASSERT_EQ(3u, out.size());
EXPECT_EQ(Row(0x123456780, &block_with_data.data[0], 5, "call", "0xce", "",
Disassembler::InstructionType::kCallDirect,
block_with_data.address + 5 /* = length of instruction */ + 0xce),
out[0]);
EXPECT_EQ(Row(0x123456785, &block_with_data.data[5], 5, "call", "-0xc", "",
Disassembler::InstructionType::kCallDirect, block_with_data.address + 10 - 12),
out[1]);
EXPECT_EQ(Row(0x12345678a, &block_with_data.data[10], 2, "call", "rax", "",
Disassembler::InstructionType::kCallIndirect),
out[2]);
}
TEST(Disassembler, CallArm64) {
ArchInfo arch;
Err err = arch.Init(debug::Arch::kArm64);
ASSERT_FALSE(err.has_error()) << err.msg();
Disassembler d;
err = d.Init(&arch);
ASSERT_FALSE(err.has_error()) << err.msg();
Disassembler::Options opts;
std::vector<Row> out;
// Make a little memory block with valid instructions in it.
debug_ipc::MemoryBlock block_with_data;
block_with_data.address = 0xc55f8;
block_with_data.valid = true;
block_with_data.data = std::vector<uint8_t>{
0x06, 0x00, 0x00, 0x94, // bl +0x06 (relative to this instruction)
0x00, 0x00, 0x3f, 0xd6, // blr x0
};
block_with_data.size = static_cast<uint32_t>(block_with_data.data.size());
std::vector<debug_ipc::MemoryBlock> vect;
vect.push_back(block_with_data);
MemoryDump dump(std::move(vect));
size_t consumed = d.DisassembleDump(dump, block_with_data.address, opts, 4, &out);
EXPECT_EQ(8u, consumed);
ASSERT_EQ(2u, out.size());
EXPECT_EQ(Row(0xc55f8, &block_with_data.data[0], 4, "bl", "#0x18", "",
Disassembler::InstructionType::kCallDirect, 0xc5610),
out[0]);
EXPECT_EQ(Row(0xc55fc, &block_with_data.data[4], 4, "blr", "x0", "",
Disassembler::InstructionType::kCallIndirect),
out[1]);
*out[0].call_dest;
}
} // namespace zxdb