| // 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_ipc::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_ipc::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_ipc::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_ipc::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_ipc::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]); | |
| // LLVM emits a comment "=0x10" here which isn't very helpful. If this | |
| // changes in a future LLVM update, it's fine. | |
| EXPECT_EQ(Row(0x123456788, &data[8], 4, "add", "x29, sp, #0x10", "// =0x10"), out[2]); | |
| // 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_ipc::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). | |
| }; | |
| 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, 2, &out); | |
| EXPECT_EQ(10u, consumed); | |
| ASSERT_EQ(2u, out.size()); | |
| EXPECT_EQ(Row(0x123456780, &block_with_data.data[0], 5, "call", "0xce", "", | |
| block_with_data.address + 5 /* = length of instruction */ + 0xce), | |
| out[0]); | |
| EXPECT_EQ(Row(0x123456785, &block_with_data.data[5], 5, "call", "-0xc", "", | |
| block_with_data.address + 10 - 12), | |
| out[1]); | |
| } | |
| TEST(Disassembler, CallArm64) { | |
| ArchInfo arch; | |
| Err err = arch.Init(debug_ipc::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) | |
| }; | |
| 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(4u, consumed); | |
| ASSERT_EQ(1u, out.size()); | |
| EXPECT_EQ(Row(0xc55f8, &block_with_data.data[0], 4, "bl", "#0x18", "", 0xc5610), out[0]); | |
| } | |
| } // namespace zxdb |