From 2ae228dac7c0203332fa650b86f01a13e6945903 Mon Sep 17 00:00:00 2001 From: Simon Wanner Date: Sun, 27 Mar 2022 23:15:32 +0200 Subject: [PATCH] LibX86: Add basic x86-64 support Most of the 64-bit instructions default to 32-bit operands and select 64-bit using REX.W prefixes. Because of that instead of defining new instruction formats, this reuses the 32-bit formats and changes them to take the REX prefixes into account when necessary. Additionally this removes, adds or modifies the instruction descriptors in the 64-bit table, where they are different from 32-bit. Using 'disasm' these changes seem to cover pretty much all of our 64-bit binaries (except for AVX) :^) Note that UserspaceEmulator will need to account for these prefixed versions in its 32-bit instruction handlers before being usable on x86-64. --- Userland/Libraries/LibX86/Disassembler.h | 2 +- Userland/Libraries/LibX86/Instruction.cpp | 167 ++++++++++++++----- Userland/Libraries/LibX86/Instruction.h | 193 +++++++++++++++++++--- 3 files changed, 295 insertions(+), 67 deletions(-) diff --git a/Userland/Libraries/LibX86/Disassembler.h b/Userland/Libraries/LibX86/Disassembler.h index 1abd5d0ae3..c42695135a 100644 --- a/Userland/Libraries/LibX86/Disassembler.h +++ b/Userland/Libraries/LibX86/Disassembler.h @@ -26,7 +26,7 @@ public: return Instruction::from_stream(m_stream, OperandSize::Size32, AddressSize::Size32); #else # if ARCH(X86_64) - return Instruction::from_stream(m_stream, OperandSize::Size64, AddressSize::Size64); + return Instruction::from_stream(m_stream, OperandSize::Size32, AddressSize::Size64); # else dbgln("Unsupported platform"); return {}; diff --git a/Userland/Libraries/LibX86/Instruction.cpp b/Userland/Libraries/LibX86/Instruction.cpp index 6fca0585e2..58cb91fe0b 100644 --- a/Userland/Libraries/LibX86/Instruction.cpp +++ b/Userland/Libraries/LibX86/Instruction.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2022, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause */ @@ -32,7 +33,7 @@ static bool opcode_has_register_index(u8 op) return false; } -static void build(InstructionDescriptor* table, u8 op, char const* mnemonic, InstructionFormat format, InstructionHandler handler, IsLockPrefixAllowed lock_prefix_allowed) +static void build_in_table(InstructionDescriptor* table, u8 op, char const* mnemonic, InstructionFormat format, InstructionHandler handler, IsLockPrefixAllowed lock_prefix_allowed) { InstructionDescriptor& d = table[op]; @@ -221,7 +222,7 @@ static void build_slash(InstructionDescriptor* table, u8 op, u8 slash, char cons if (!d.slashes) d.slashes = new InstructionDescriptor[8]; - build(d.slashes, slash, mnemonic, format, handler, lock_prefix_allowed); + build_in_table(d.slashes, slash, mnemonic, format, handler, lock_prefix_allowed); } static void build_slash_rm(InstructionDescriptor* table, u8 op, u8 slash, u8 rm, char const* mnemonic, InstructionFormat format, InstructionHandler handler) @@ -242,28 +243,31 @@ static void build_slash_rm(InstructionDescriptor* table, u8 op, u8 slash, u8 rm, } } - build(d.slashes, rm & 7, mnemonic, format, handler, LockPrefixNotAllowed); + build_in_table(d.slashes, rm & 7, mnemonic, format, handler, LockPrefixNotAllowed); } template static void build_base(u8 op, char const* mnemonic, InstructionFormat format, InstructionHandler impl, IsLockPrefixAllowed lock_prefix_allowed = LockPrefixNotAllowed) { - build(table[to_underlying(OperandSize::Size16)], op, mnemonic, format, impl, lock_prefix_allowed); - build(table[to_underlying(OperandSize::Size32)], op, mnemonic, format, impl, lock_prefix_allowed); + build_in_table(table[to_underlying(OperandSize::Size16)], op, mnemonic, format, impl, lock_prefix_allowed); + build_in_table(table[to_underlying(OperandSize::Size32)], op, mnemonic, format, impl, lock_prefix_allowed); + build_in_table(table[to_underlying(OperandSize::Size64)], op, mnemonic, format, impl, lock_prefix_allowed); } template static void build_base(u8 op, char const* mnemonic, InstructionFormat format16, InstructionHandler impl16, InstructionFormat format32, InstructionHandler impl32, IsLockPrefixAllowed lock_prefix_allowed = LockPrefixNotAllowed) { - build(table[to_underlying(OperandSize::Size16)], op, mnemonic, format16, impl16, lock_prefix_allowed); - build(table[to_underlying(OperandSize::Size32)], op, mnemonic, format32, impl32, lock_prefix_allowed); + build_in_table(table[to_underlying(OperandSize::Size16)], op, mnemonic, format16, impl16, lock_prefix_allowed); + build_in_table(table[to_underlying(OperandSize::Size32)], op, mnemonic, format32, impl32, lock_prefix_allowed); + build_in_table(table[to_underlying(OperandSize::Size64)], op, mnemonic, format32, impl32, lock_prefix_allowed); } template static void build_base(u8 op, char const* mnemonic16, InstructionFormat format16, InstructionHandler impl16, char const* mnemonic32, InstructionFormat format32, InstructionHandler impl32, IsLockPrefixAllowed lock_prefix_allowed = LockPrefixNotAllowed) { - build(table[to_underlying(OperandSize::Size16)], op, mnemonic16, format16, impl16, lock_prefix_allowed); - build(table[to_underlying(OperandSize::Size32)], op, mnemonic32, format32, impl32, lock_prefix_allowed); + build_in_table(table[to_underlying(OperandSize::Size16)], op, mnemonic16, format16, impl16, lock_prefix_allowed); + build_in_table(table[to_underlying(OperandSize::Size32)], op, mnemonic32, format32, impl32, lock_prefix_allowed); + build_in_table(table[to_underlying(OperandSize::Size64)], op, mnemonic32, format32, impl32, lock_prefix_allowed); } template @@ -271,6 +275,7 @@ static void build_slash_base(u8 op, u8 slash, char const* mnemonic, InstructionF { build_slash(table[to_underlying(OperandSize::Size16)], op, slash, mnemonic, format, impl, lock_prefix_allowed); build_slash(table[to_underlying(OperandSize::Size32)], op, slash, mnemonic, format, impl, lock_prefix_allowed); + build_slash(table[to_underlying(OperandSize::Size64)], op, slash, mnemonic, format, impl, lock_prefix_allowed); } template @@ -278,6 +283,7 @@ static void build_slash_base(u8 op, u8 slash, char const* mnemonic, InstructionF { build_slash(table[to_underlying(OperandSize::Size16)], op, slash, mnemonic, format16, impl16, lock_prefix_allowed); build_slash(table[to_underlying(OperandSize::Size32)], op, slash, mnemonic, format32, impl32, lock_prefix_allowed); + build_slash(table[to_underlying(OperandSize::Size64)], op, slash, mnemonic, format32, impl32, lock_prefix_allowed); } template @@ -308,6 +314,7 @@ static void build_slash_rm(u8 op, u8 slash, u8 rm, char const* mnemonic, Instruc { build_slash_rm(s_table[to_underlying(OperandSize::Size16)], op, slash, rm, mnemonic, format, impl); build_slash_rm(s_table[to_underlying(OperandSize::Size32)], op, slash, rm, mnemonic, format, impl); + build_slash_rm(s_table[to_underlying(OperandSize::Size64)], op, slash, rm, mnemonic, format, impl); } static void build_slash_reg(u8 op, u8 slash, char const* mnemonic, InstructionFormat format, InstructionHandler impl) @@ -320,14 +327,14 @@ static void build_sse_np(u8 op, char const* mnemonic, InstructionFormat format, { if (s_0f_table[to_underlying(OperandSize::Size32)][op].format == InvalidFormat) { build_0f(op, mnemonic, format, impl, lock_prefix_allowed); - build(s_sse_table_np, op, mnemonic, format, impl, lock_prefix_allowed); + build_in_table(s_sse_table_np, op, mnemonic, format, impl, lock_prefix_allowed); return; } if (s_0f_table[to_underlying(OperandSize::Size32)][op].format != __SSE) build_0f(op, "__SSE_temp", __SSE, nullptr, lock_prefix_allowed); VERIFY(s_0f_table[to_underlying(OperandSize::Size32)][op].format == __SSE); - build(s_sse_table_np, op, mnemonic, format, impl, lock_prefix_allowed); + build_in_table(s_sse_table_np, op, mnemonic, format, impl, lock_prefix_allowed); } static void build_sse_66(u8 op, char const* mnemonic, InstructionFormat format, InstructionHandler impl, IsLockPrefixAllowed lock_prefix_allowed = LockPrefixNotAllowed) @@ -335,7 +342,7 @@ static void build_sse_66(u8 op, char const* mnemonic, InstructionFormat format, if (s_0f_table[to_underlying(OperandSize::Size32)][op].format != __SSE) build_0f(op, "__SSE_temp", __SSE, nullptr, lock_prefix_allowed); VERIFY(s_0f_table[to_underlying(AddressSize::Size32)][op].format == __SSE); - build(s_sse_table_66, op, mnemonic, format, impl, lock_prefix_allowed); + build_in_table(s_sse_table_66, op, mnemonic, format, impl, lock_prefix_allowed); } static void build_sse_f3(u8 op, char const* mnemonic, InstructionFormat format, InstructionHandler impl, IsLockPrefixAllowed lock_prefix_allowed = LockPrefixNotAllowed) @@ -343,7 +350,7 @@ static void build_sse_f3(u8 op, char const* mnemonic, InstructionFormat format, if (s_0f_table[to_underlying(OperandSize::Size32)][op].format != __SSE) build_0f(op, "__SSE_temp", __SSE, nullptr, lock_prefix_allowed); VERIFY(s_0f_table[to_underlying(OperandSize::Size32)][op].format == __SSE); - build(s_sse_table_f3, op, mnemonic, format, impl, lock_prefix_allowed); + build_in_table(s_sse_table_f3, op, mnemonic, format, impl, lock_prefix_allowed); } static void build_sse_f2(u8 op, char const* mnemonic, InstructionFormat format, InstructionHandler impl, IsLockPrefixAllowed lock_prefix_allowed = LockPrefixNotAllowed) @@ -353,7 +360,7 @@ static void build_sse_f2(u8 op, char const* mnemonic, InstructionFormat format, VERIFY(s_0f_table[to_underlying(OperandSize::Size32)][op].format == __SSE); VERIFY(s_sse_table_f2[op].format == InvalidFormat); - build(s_sse_table_f2, op, mnemonic, format, impl, lock_prefix_allowed); + build_in_table(s_sse_table_f2, op, mnemonic, format, impl, lock_prefix_allowed); } static void build_sse_np_slash(u8 op, u8 slash, char const* mnemonic, InstructionFormat format, InstructionHandler impl, IsLockPrefixAllowed lock_prefix_allowed = LockPrefixNotAllowed) @@ -864,6 +871,8 @@ static void build_sse_66_slash(u8 op, u8 slash, char const* mnemonic, Instructio build_0f_slash(0x18, 2, "PREFETCHT1", OP_RM8, &Interpreter::PREFETCHT1); build_0f_slash(0x18, 3, "PREFETCHT2", OP_RM8, &Interpreter::PREFETCHT2); + build_0f_slash(0x1f, 0, "NOP", OP_RM32, &Interpreter::NOP); + // FIXME: Technically NoPrefix (sse_np_slash?) build_0f_slash(0xAE, 2, "LDMXCSR", OP_RM32, &Interpreter::LDMXCSR); build_0f_slash(0xAE, 3, "STMXCSR", OP_RM32, &Interpreter::STMXCSR); @@ -1203,11 +1212,59 @@ static void build_sse_66_slash(u8 op, u8 slash, char const* mnemonic, Instructio build_0f(0xFD, "PADDW", OP_mm1_mm2m64, &Interpreter::PADDW_mm1_mm2m64); build_0f(0xFE, "PADDD", OP_mm1_mm2m64, &Interpreter::PADDD_mm1_mm2m64); build_0f(0xFF, "UD0", OP, &Interpreter::UD0); + + // Changes between 32-bit and 64-bit. These are marked with i64/d64/f64 in the Intel manual's opcode tables + auto* table64 = s_table[to_underlying(OperandSize::Size64)]; + table64[0x06] = {}; // PUSH ES + table64[0x07] = {}; // POP ES + table64[0x16] = {}; // PUSH SS + table64[0x17] = {}; // POP SS + table64[0x27] = {}; // DAA + table64[0x37] = {}; // AAA + for (u8 rex = 0x40; rex < 0x50; rex++) + table64[rex] = {}; // INC/DEC, replaced by REX prefixes + for (u8 pushPop = 0x50; pushPop < 0x60; pushPop++) + table64[pushPop].long_mode_default_64 = true; // PUSH/POP general register + for (u8 i = 0x60; i < 0x68; i++) + table64[i] = {}; // PUSHA{D}, POPA{D}, BOUND + // ARPL replaced by MOVSXD + build_in_table(table64, 0x63, "MOVSXD", OP_RM32_reg32, nullptr, LockPrefixNotAllowed); + table64[0x68].long_mode_default_64 = true; // PUSH + table64[0x6A].long_mode_default_64 = true; // PUSH + for (u8 jmp = 0x70; jmp < 0x80; jmp++) + table64[jmp].long_mode_force_64 = true; // Jcc + table64[0x9A] = {}; // far CALL + table64[0x9C].long_mode_default_64 = true; // PUSHF/D/Q + table64[0x9D].long_mode_default_64 = true; // POPF/D/Q + build_in_table(table64, 0xB8, "MOV", OP_regW_immW, &Interpreter::MOV_reg32_imm32, LockPrefixNotAllowed); + table64[0xC2].long_mode_force_64 = true; // near RET + table64[0xC3].long_mode_force_64 = true; // near RET + table64[0xC4] = {}; // LES + table64[0xC5] = {}; // LDS + table64[0xC9].long_mode_default_64 = true; // LEAVE + table64[0xCE].long_mode_default_64 = true; // INTO + table64[0xD4] = {}; // AAM + table64[0xD5] = {}; // AAD + for (u8 i = 0; i < 4; i++) { + table64[0xE0 | i].long_mode_force_64 = true; // LOOPN[EZ], LOOP[EZ], LOOP, JrCXZ + table64[0xE8 | i].long_mode_force_64 = true; // near CALL, {near,far,short} JMP + } + + auto* table64_0f = s_0f_table[to_underlying(OperandSize::Size64)]; + build_in_table(table64_0f, 0x05, "SYSCALL", OP, nullptr, LockPrefixNotAllowed); + build_in_table(table64_0f, 0x07, "SYSRET", OP, nullptr, LockPrefixNotAllowed); + for (u8 i = 0x80; i < 0x90; i++) + table64_0f[i].long_mode_force_64 = true; // Jcc + table64_0f[0xA0].long_mode_default_64 = true; // PUSH FS + table64_0f[0xA1].long_mode_default_64 = true; // POP FS + table64_0f[0xA8].long_mode_default_64 = true; // PUSH GS + table64_0f[0xA9].long_mode_default_64 = true; // POP GS } static StringView register_name(RegisterIndex8); static StringView register_name(RegisterIndex16); static StringView register_name(RegisterIndex32); +static StringView register_name(RegisterIndex64); static StringView register_name(FpuRegisterIndex); static StringView register_name(SegmentRegister); static StringView register_name(MMXRegisterIndex); @@ -1228,6 +1285,11 @@ StringView Instruction::reg32_name() const return register_name(static_cast(register_index())); } +StringView Instruction::reg64_name() const +{ + return register_name(static_cast(register_index())); +} + String MemoryOrRegisterReference::to_string_o8(Instruction const& insn) const { if (is_register()) @@ -1249,6 +1311,13 @@ String MemoryOrRegisterReference::to_string_o32(Instruction const& insn) const return String::formatted("[{}]", to_string(insn)); } +String MemoryOrRegisterReference::to_string_o64(Instruction const& insn) const +{ + if (is_register()) + return register_name(reg64()); + return String::formatted("[{}]", to_string(insn)); +} + String MemoryOrRegisterReference::to_string_fpu_reg() const { VERIFY(is_register()); @@ -1312,7 +1381,7 @@ String MemoryOrRegisterReference::to_string(Instruction const& insn) const case AddressSize::Size64: return to_string_a64(); case AddressSize::Size32: - return to_string_a32(); + return insn.mode() == ProcessorMode::Long ? to_string_a64() : to_string_a32(); case AddressSize::Size16: return to_string_a16(); } @@ -1371,7 +1440,7 @@ String MemoryOrRegisterReference::to_string_a16() const return String::formatted("{}{}", base, displacement_string); } -String MemoryOrRegisterReference::sib_to_string(ProcessorMode) const +String MemoryOrRegisterReference::sib_to_string(ProcessorMode mode) const { String scale; String index; @@ -1390,16 +1459,16 @@ String MemoryOrRegisterReference::sib_to_string(ProcessorMode) const break; } if (m_sib_index != 4) - index = register_name(RegisterIndex32(m_sib_index)); + index = mode == ProcessorMode::Long ? register_name(RegisterIndex64(m_sib_index)) : register_name(RegisterIndex32(m_sib_index)); if (m_sib_base == 5) { switch (m_reg) { case 1: case 2: - base = "ebp"; + base = mode == ProcessorMode::Long ? "rbp" : "ebp"; break; } } else { - base = register_name(RegisterIndex32(m_sib_base)); + base = mode == ProcessorMode::Long ? register_name(RegisterIndex64(m_sib_base)) : register_name(RegisterIndex32(m_sib_base)); } StringBuilder builder; if (base.is_empty()) { @@ -1418,7 +1487,7 @@ String MemoryOrRegisterReference::sib_to_string(ProcessorMode) const String MemoryOrRegisterReference::to_string_a64() const { if (is_register()) - return register_name(static_cast(m_register_index)); + return register_name(static_cast(m_register_index)); bool has_displacement = false; switch (mod()) { @@ -1436,7 +1505,7 @@ String MemoryOrRegisterReference::to_string_a64() const switch (m_rm) { case 5: if (mod() == 0) - base = String::formatted("{:#08x}", m_displacement32); + base = "rip"; else base = "rbp"; break; @@ -1444,7 +1513,7 @@ String MemoryOrRegisterReference::to_string_a64() const base = sib_to_string(ProcessorMode::Long); break; default: - base = register_name(RegisterIndex32(m_rm)); + base = register_name(RegisterIndex64(m_rm)); } if (!has_displacement) @@ -1583,9 +1652,13 @@ void Instruction::to_string_internal(StringBuilder& builder, u32 origin, SymbolP auto append_rm8 = [&] { builder.append(m_modrm.to_string_o8(*this)); }; auto append_rm16 = [&] { builder.append(m_modrm.to_string_o16(*this)); }; - auto append_rm32 = [&] { builder.append(m_modrm.to_string_o32(*this)); }; - // FIXME: Registers in long-mode - auto append_rm64 = [&] { builder.append(m_modrm.to_string_o32(*this)); }; + auto append_rm32 = [&] { + if (m_operand_size == OperandSize::Size64) + builder.append(m_modrm.to_string_o64(*this)); + else + builder.append(m_modrm.to_string_o32(*this)); + }; + auto append_rm64 = [&] { builder.append(m_modrm.to_string_o64(*this)); }; auto append_fpu_reg = [&] { builder.append(m_modrm.to_string_fpu_reg()); }; auto append_fpu_mem = [&] { builder.append(m_modrm.to_string_fpu_mem(*this)); }; auto append_fpu_ax16 = [&] { builder.append(m_modrm.to_string_fpu_ax16()); }; @@ -1609,16 +1682,24 @@ void Instruction::to_string_internal(StringBuilder& builder, u32 origin, SymbolP }; auto append_reg8 = [&] { builder.append(reg8_name()); }; auto append_reg16 = [&] { builder.append(reg16_name()); }; - auto append_reg32 = [&] { builder.append(reg32_name()); }; + auto append_reg32 = [&] { + if (m_operand_size == OperandSize::Size64) + builder.append(reg64_name()); + else + builder.append(reg32_name()); + }; auto append_seg = [&] { builder.append(register_name(segment_register())); }; auto append_creg = [&] { builder.appendff("cr{}", register_index()); }; auto append_dreg = [&] { builder.appendff("dr{}", register_index()); }; auto append_relative_addr = [&] { - if (m_address_size == AddressSize::Size32) { - formatted_address(origin + 6, x32, i32(imm32())); - } else if (m_address_size == AddressSize::Size16) { + switch (m_address_size) { + case AddressSize::Size16: formatted_address(origin + 4, x32, i32(imm16())); - } else { + break; + case AddressSize::Size32: + formatted_address(origin + 6, x32, i32(imm32())); + break; + default: VERIFY_NOT_REACHED(); } }; @@ -1653,7 +1734,9 @@ void Instruction::to_string_internal(StringBuilder& builder, u32 origin, SymbolP auto append = [&](auto content) { builder.append(content); }; auto append_moff = [&] { builder.append('['); - if (m_address_size == AddressSize::Size32) { + if (m_address_size == AddressSize::Size64) { + append_imm64(); + } else if (m_address_size == AddressSize::Size32) { append_imm32(); } else if (m_address_size == AddressSize::Size16) { append_imm16(); @@ -2428,20 +2511,26 @@ StringView register_name(SegmentRegister index) StringView register_name(RegisterIndex8 register_index) { - static constexpr StringView names[] = { "al"sv, "cl"sv, "dl"sv, "bl"sv, "ah"sv, "ch"sv, "dh"sv, "bh"sv }; - return names[register_index & 7]; + static constexpr StringView names[] = { "al"sv, "cl"sv, "dl"sv, "bl"sv, "ah"sv, "ch"sv, "dh"sv, "bh"sv, "r8b"sv, "r9b"sv, "r10b"sv, "r11b"sv, "r12b"sv, "r13b"sv, "r14b"sv, "r15b"sv }; + return names[register_index & 15]; } StringView register_name(RegisterIndex16 register_index) { - static constexpr StringView names[] = { "ax"sv, "cx"sv, "dx"sv, "bx"sv, "sp"sv, "bp"sv, "si"sv, "di"sv }; - return names[register_index & 7]; + static constexpr StringView names[] = { "ax"sv, "cx"sv, "dx"sv, "bx"sv, "sp"sv, "bp"sv, "si"sv, "di"sv, "r8w"sv, "r9w"sv, "r10w"sv, "r11w"sv, "r12w"sv, "r13w"sv, "r14w"sv, "r15w"sv }; + return names[register_index & 15]; } StringView register_name(RegisterIndex32 register_index) { - static constexpr StringView names[] = { "eax"sv, "ecx"sv, "edx"sv, "ebx"sv, "esp"sv, "ebp"sv, "esi"sv, "edi"sv }; - return names[register_index & 7]; + static constexpr StringView names[] = { "eax"sv, "ecx"sv, "edx"sv, "ebx"sv, "esp"sv, "ebp"sv, "esi"sv, "edi"sv, "r8d"sv, "r9d"sv, "r10d"sv, "r11d"sv, "r12d"sv, "r13d"sv, "r14d"sv, "r15d"sv }; + return names[register_index & 15]; +} + +StringView register_name(RegisterIndex64 register_index) +{ + static constexpr StringView names[] = { "rax"sv, "rcx"sv, "rdx"sv, "rbx"sv, "rsp"sv, "rbp"sv, "rsi"sv, "rdi"sv, "r8"sv, "r9"sv, "r10"sv, "r11"sv, "r12"sv, "r13"sv, "r14"sv, "r15"sv }; + return names[register_index & 15]; } StringView register_name(FpuRegisterIndex register_index) @@ -2458,8 +2547,8 @@ StringView register_name(MMXRegisterIndex register_index) StringView register_name(XMMRegisterIndex register_index) { - static constexpr StringView names[] = { "xmm0"sv, "xmm1"sv, "xmm2"sv, "xmm3"sv, "xmm4"sv, "xmm5"sv, "xmm6"sv, "xmm7"sv }; - return names[register_index & 7]; + static constexpr StringView names[] = { "xmm0"sv, "xmm1"sv, "xmm2"sv, "xmm3"sv, "xmm4"sv, "xmm5"sv, "xmm6"sv, "xmm7"sv, "xmm8"sv, "xmm9"sv, "xmm10"sv, "xmm11"sv, "xmm12"sv, "xmm13"sv, "xmm14"sv, "xmm15"sv }; + return names[register_index & 15]; } } diff --git a/Userland/Libraries/LibX86/Instruction.h b/Userland/Libraries/LibX86/Instruction.h index 7d0121eb3b..87f1426253 100644 --- a/Userland/Libraries/LibX86/Instruction.h +++ b/Userland/Libraries/LibX86/Instruction.h @@ -226,6 +226,8 @@ struct InstructionDescriptor { bool has_rm { false }; unsigned imm1_bytes { 0 }; unsigned imm2_bytes { 0 }; + bool long_mode_default_64 { false }; + bool long_mode_force_64 { false }; // Addressed by the 3 REG bits in the MOD-REG-R/M byte. // Some slash instructions have further subgroups when MOD is 11, @@ -233,10 +235,10 @@ struct InstructionDescriptor { // a non-null slashes member that's indexed by the three R/M bits. InstructionDescriptor* slashes { nullptr }; - unsigned imm1_bytes_for_address_size(AddressSize size) const + unsigned imm1_bytes_for(AddressSize address_size, OperandSize operand_size) const { if (imm1_bytes == CurrentAddressSize) { - switch (size) { + switch (address_size) { case AddressSize::Size64: return 8; case AddressSize::Size32: @@ -246,13 +248,24 @@ struct InstructionDescriptor { } VERIFY_NOT_REACHED(); } + if (imm1_bytes == CurrentOperandSize) { + switch (operand_size) { + case OperandSize::Size64: + return 8; + case OperandSize::Size32: + return 4; + case OperandSize::Size16: + return 2; + } + VERIFY_NOT_REACHED(); + } return imm1_bytes; } - unsigned imm2_bytes_for_address_size(AddressSize size) const + unsigned imm2_bytes_for(AddressSize address_size, OperandSize operand_size) const { if (imm2_bytes == CurrentAddressSize) { - switch (size) { + switch (address_size) { case AddressSize::Size64: return 8; case AddressSize::Size32: @@ -262,6 +275,17 @@ struct InstructionDescriptor { } VERIFY_NOT_REACHED(); } + if (imm2_bytes == CurrentOperandSize) { + switch (operand_size) { + case OperandSize::Size64: + return 8; + case OperandSize::Size32: + return 4; + case OperandSize::Size16: + return 2; + } + VERIFY_NOT_REACHED(); + } return imm2_bytes; } @@ -276,6 +300,8 @@ extern InstructionDescriptor s_sse_table_f3[256]; struct Prefix { enum Op { + REX_Mask = 0xf0, + REX_Base = 0x40, OperandSizeOverride = 0x66, AddressSizeOverride = 0x67, REP = 0xf3, @@ -304,7 +330,15 @@ enum RegisterIndex8 { RegisterAH, RegisterCH, RegisterDH, - RegisterBH + RegisterBH, + RegisterR8B, + RegisterR9B, + RegisterR10B, + RegisterR11B, + RegisterR12B, + RegisterR13B, + RegisterR14B, + RegisterR15B, }; enum RegisterIndex16 { @@ -315,7 +349,15 @@ enum RegisterIndex16 { RegisterSP, RegisterBP, RegisterSI, - RegisterDI + RegisterDI, + RegisterR8W, + RegisterR9W, + RegisterR10W, + RegisterR11W, + RegisterR12W, + RegisterR13W, + RegisterR14W, + RegisterR15W, }; enum RegisterIndex32 { @@ -326,7 +368,34 @@ enum RegisterIndex32 { RegisterESP, RegisterEBP, RegisterESI, - RegisterEDI + RegisterEDI, + RegisterR8D, + RegisterR9D, + RegisterR10D, + RegisterR11D, + RegisterR12D, + RegisterR13D, + RegisterR14D, + RegisterR15D, +}; + +enum RegisterIndex64 { + RegisterRAX = 0, + RegisterRCX, + RegisterRDX, + RegisterRBX, + RegisterRSP, + RegisterRBP, + RegisterRSI, + RegisterRDI, + RegisterR8, + RegisterR9, + RegisterR10, + RegisterR11, + RegisterR12, + RegisterR13, + RegisterR14, + RegisterR15, }; enum FpuRegisterIndex { @@ -359,7 +428,15 @@ enum XMMRegisterIndex { RegisterXMM4, RegisterXMM5, RegisterXMM6, - RegisterXMM7 + RegisterXMM7, + RegisterXMM8, + RegisterXMM9, + RegisterXMM10, + RegisterXMM11, + RegisterXMM12, + RegisterXMM13, + RegisterXMM14, + RegisterXMM15, }; class LogicalAddress { @@ -444,6 +521,7 @@ public: String to_string_o8(Instruction const&) const; String to_string_o16(Instruction const&) const; String to_string_o32(Instruction const&) const; + String to_string_o64(Instruction const&) const; String to_string_fpu_reg() const; String to_string_fpu_mem(Instruction const&) const; String to_string_fpu_ax16() const; @@ -458,6 +536,7 @@ public: bool is_register() const { return m_register_index != 0x7f; } unsigned register_index() const { return m_register_index; } + RegisterIndex64 reg64() const { return static_cast(register_index()); } RegisterIndex32 reg32() const { return static_cast(register_index()); } RegisterIndex16 reg16() const { return static_cast(register_index()); } RegisterIndex8 reg8() const { return static_cast(register_index()); } @@ -507,11 +586,11 @@ private: String to_string_a64() const; template - void decode(InstructionStreamType&, AddressSize); + void decode(InstructionStreamType&, AddressSize, bool has_rex_r, bool has_rex_x, bool has_rex_b); template void decode16(InstructionStreamType&); template - void decode32(InstructionStreamType&); + void decode32(InstructionStreamType&, bool has_rex_r, bool has_rex_x, bool has_rex_b); template LogicalAddress resolve16(const CPU&, Optional); template @@ -613,6 +692,8 @@ public: u8 cc() const { return has_sub_op() ? m_sub_op & 0xf : m_op & 0xf; } AddressSize address_size() const { return m_address_size; } + OperandSize operand_size() const { return m_operand_size; } + ProcessorMode mode() const { return m_mode; } String to_string(u32 origin, SymbolProvider const* = nullptr, bool x32 = true) const; @@ -625,6 +706,7 @@ private: StringView reg8_name() const; StringView reg16_name() const; StringView reg32_name() const; + StringView reg64_name() const; InstructionDescriptor* m_descriptor { nullptr }; mutable MemoryOrRegisterReference m_modrm; @@ -638,9 +720,14 @@ private: u8 m_rep_prefix { 0 }; OperandSize m_operand_size { OperandSize::Size16 }; AddressSize m_address_size { AddressSize::Size16 }; + ProcessorMode m_mode { ProcessorMode::Protected }; bool m_has_lock_prefix : 1 { false }; bool m_has_operand_size_override_prefix : 1 { false }; bool m_has_address_size_override_prefix : 1 { false }; + bool m_has_rex_w : 1 { false }; + bool m_has_rex_r : 1 { false }; + bool m_has_rex_x : 1 { false }; + bool m_has_rex_b : 1 { false }; }; template @@ -723,7 +810,7 @@ ALWAYS_INLINE u32 MemoryOrRegisterReference::evaluate_sib(const CPU& cpu, Segmen u32 index = 0; switch (m_sib_index) { case 0 ... 3: - case 5 ... 7: + case 5 ... 15: index = cpu.const_gpr32((RegisterIndex32)m_sib_index).value(); break; case 4: @@ -734,7 +821,7 @@ ALWAYS_INLINE u32 MemoryOrRegisterReference::evaluate_sib(const CPU& cpu, Segmen u32 base = m_displacement32; switch (m_sib_base) { case 0 ... 3: - case 6 ... 7: + case 6 ... 15: base += cpu.const_gpr32((RegisterIndex32)m_sib_base).value(); break; case 4: @@ -752,7 +839,6 @@ ALWAYS_INLINE u32 MemoryOrRegisterReference::evaluate_sib(const CPU& cpu, Segmen break; default: VERIFY_NOT_REACHED(); - break; } break; } @@ -920,21 +1006,29 @@ ALWAYS_INLINE Instruction::Instruction(InstructionStreamType& stream, OperandSiz : m_operand_size(operand_size) , m_address_size(address_size) { + VERIFY(operand_size != OperandSize::Size64); + // Use address_size as the hint to switch into long mode, m_address_size refers to the default + // size of displacements/immediates, which is 32 even in long mode, with the exception of moffset (see below). + if (address_size == AddressSize::Size64) { + m_operand_size = OperandSize::Size32; + m_address_size = AddressSize::Size64; + m_mode = ProcessorMode::Long; + } u8 prefix_bytes = 0; for (;; ++prefix_bytes) { u8 opbyte = stream.read8(); if (opbyte == Prefix::OperandSizeOverride) { - if (operand_size == OperandSize::Size32) + if (m_operand_size == OperandSize::Size32) m_operand_size = OperandSize::Size16; - else if (operand_size == OperandSize::Size16) + else if (m_operand_size == OperandSize::Size16) m_operand_size = OperandSize::Size32; m_has_operand_size_override_prefix = true; continue; } if (opbyte == Prefix::AddressSizeOverride) { - if (address_size == AddressSize::Size32) + if (m_address_size == AddressSize::Size32) m_address_size = AddressSize::Size16; - else if (address_size == AddressSize::Size16) + else if (m_address_size == AddressSize::Size16) m_address_size = AddressSize::Size32; m_has_address_size_override_prefix = true; continue; @@ -947,6 +1041,15 @@ ALWAYS_INLINE Instruction::Instruction(InstructionStreamType& stream, OperandSiz m_has_lock_prefix = true; continue; } + if (m_mode == ProcessorMode::Long && (opbyte & Prefix::REX_Mask) == Prefix::REX_Base) { + m_has_rex_w = opbyte & 8; + if (m_has_rex_w) + m_operand_size = OperandSize::Size64; + m_has_rex_r = opbyte & 4; + m_has_rex_x = opbyte & 2; + m_has_rex_b = opbyte & 1; + continue; + } auto segment_prefix = to_segment_prefix(opbyte); if (segment_prefix.has_value()) { m_segment_prefix = (u8)segment_prefix.value(); @@ -956,11 +1059,14 @@ ALWAYS_INLINE Instruction::Instruction(InstructionStreamType& stream, OperandSiz break; } + u8 table_index = to_underlying(m_operand_size); + if (m_mode == ProcessorMode::Long && m_operand_size == OperandSize::Size32) + table_index = to_underlying(OperandSize::Size64); if (m_op == 0x0f) { m_sub_op = stream.read8(); - m_descriptor = &s_0f_table[to_underlying(m_operand_size)][m_sub_op]; + m_descriptor = &s_0f_table[table_index][m_sub_op]; } else { - m_descriptor = &s_table[to_underlying(m_operand_size)][m_op]; + m_descriptor = &s_table[table_index][m_op]; } if (m_descriptor->format == __SSE) { @@ -977,13 +1083,21 @@ ALWAYS_INLINE Instruction::Instruction(InstructionStreamType& stream, OperandSiz if (m_descriptor->has_rm) { // Consume ModR/M (may include SIB and displacement.) - m_modrm.decode(stream, m_address_size); + m_modrm.decode(stream, m_address_size, m_has_rex_r, m_has_rex_x, m_has_rex_b); m_register_index = m_modrm.reg(); } else { if (has_sub_op()) m_register_index = m_sub_op & 7; else m_register_index = m_op & 7; + if (m_has_rex_b) + m_register_index |= 8; + } + + if (m_mode == ProcessorMode::Long && (m_descriptor->long_mode_force_64 || m_descriptor->long_mode_default_64)) { + m_operand_size = OperandSize::Size64; + if (!m_descriptor->long_mode_force_64 && m_has_operand_size_override_prefix) + m_operand_size = OperandSize::Size32; } bool has_slash = m_descriptor->format == MultibyteWithSlash; @@ -1010,8 +1124,22 @@ ALWAYS_INLINE Instruction::Instruction(InstructionStreamType& stream, OperandSiz return; } - auto imm1_bytes = m_descriptor->imm1_bytes_for_address_size(m_address_size); - auto imm2_bytes = m_descriptor->imm2_bytes_for_address_size(m_address_size); + // 2.2.1.3 Direct Memory-Offset MOVs + auto effective_address_size = m_address_size; + if (m_mode == ProcessorMode::Long) { + switch (m_descriptor->format) { + case OP_AL_moff8: // A0 MOV AL, moffset + case OP_EAX_moff32: // A1 MOV EAX, moffset + case OP_moff8_AL: // A2 MOV moffset, AL + case OP_moff32_EAX: // A3 MOV moffset, EAX + effective_address_size = AddressSize::Size64; + break; + default: + break; + } + } + auto imm1_bytes = m_descriptor->imm1_bytes_for(effective_address_size, m_operand_size); + auto imm2_bytes = m_descriptor->imm2_bytes_for(effective_address_size, m_operand_size); // Consume immediates if present. switch (imm2_bytes) { @@ -1024,6 +1152,9 @@ ALWAYS_INLINE Instruction::Instruction(InstructionStreamType& stream, OperandSiz case 4: m_imm2 = stream.read32(); break; + case 8: + m_imm2 = stream.read64(); + break; default: VERIFY(imm2_bytes == 0); break; @@ -1039,6 +1170,9 @@ ALWAYS_INLINE Instruction::Instruction(InstructionStreamType& stream, OperandSiz case 4: m_imm1 = stream.read32(); break; + case 8: + m_imm1 = stream.read64(); + break; default: VERIFY(imm1_bytes == 0); break; @@ -1055,7 +1189,7 @@ ALWAYS_INLINE Instruction::Instruction(InstructionStreamType& stream, OperandSiz } template -ALWAYS_INLINE void MemoryOrRegisterReference::decode(InstructionStreamType& stream, AddressSize address_size) +ALWAYS_INLINE void MemoryOrRegisterReference::decode(InstructionStreamType& stream, AddressSize address_size, bool has_rex_r, bool has_rex_x, bool has_rex_b) { u8 mod_rm_byte = stream.read8(); m_mod = mod_rm_byte >> 6; @@ -1063,7 +1197,7 @@ ALWAYS_INLINE void MemoryOrRegisterReference::decode(InstructionStreamType& stre m_rm = mod_rm_byte & 7; if (address_size == AddressSize::Size32) { - decode32(stream); + decode32(stream, has_rex_r, has_rex_x, has_rex_b); switch (m_displacement_bytes) { case 0: break; @@ -1118,8 +1252,10 @@ ALWAYS_INLINE void MemoryOrRegisterReference::decode16(InstructionStreamType&) } template -ALWAYS_INLINE void MemoryOrRegisterReference::decode32(InstructionStreamType& stream) +ALWAYS_INLINE void MemoryOrRegisterReference::decode32(InstructionStreamType& stream, bool has_rex_r, bool has_rex_x, bool has_rex_b) { + m_reg |= has_rex_r << 3; + switch (m_mod) { case 0b00: if (m_rm == 5) { @@ -1134,6 +1270,7 @@ ALWAYS_INLINE void MemoryOrRegisterReference::decode32(InstructionStreamType& st m_displacement_bytes = 4; break; case 0b11: + m_rm |= has_rex_b << 3; m_register_index = rm(); return; } @@ -1142,8 +1279,8 @@ ALWAYS_INLINE void MemoryOrRegisterReference::decode32(InstructionStreamType& st if (m_has_sib) { u8 sib_byte = stream.read8(); m_sib_scale = sib_byte >> 6; - m_sib_index = (sib_byte >> 3) & 7; - m_sib_base = sib_byte & 7; + m_sib_index = (has_rex_x << 3) | ((sib_byte >> 3) & 7); + m_sib_base = (has_rex_b << 3) | (sib_byte & 7); if (m_sib_base == 5) { switch (mod()) { case 0b00: @@ -1159,6 +1296,8 @@ ALWAYS_INLINE void MemoryOrRegisterReference::decode32(InstructionStreamType& st VERIFY_NOT_REACHED(); } } + } else { + m_rm |= has_rex_b << 3; } }