【问题标题】:How to read the Intel Opcode notation如何阅读英特尔操作码符号
【发布时间】:2013-02-07 16:27:49
【问题描述】:

我正在阅读一些关于汇编指令的英特尔操作码的材料,但我无法理解操作码字节后面的含义。例如:cwcd/2cp/3

请给我一个提示,这是什么意思,或者我在哪里可以找到完整的参考资料?

E8 cw CALL rel16 调用近、相对、相对于下一条指令的位移 E8 cd CALL rel32 调用近、相对、相对于下一条指令的位移 FF /2 CALL r/m16 近调用,绝对间接,在 r/m16 中给出地址 FF /2 CALL r/m32 近调用,绝对间接,在 r/m32 中给出的地址 9A cd CALL ptr16:16 调用远、绝对、在操作数中给出的地址 9A cp CALL ptr16:32 调用远、绝对、操作数中给出的地址 FF /3 CALL m16:16 呼叫远,绝对间接,地址在 m16:16 中给出 FF /3 CALL m16:32 远调用,绝对间接,地址在 m16:32 中给出

【问题讨论】:

    标签: assembly x86 intel opcode machine-code


    【解决方案1】:

    3.1.1.1 指令汇总表中的操作码列(无VEX前缀的指令)

    上表中的“操作码”列显示了为每种形式的指令生成的目标代码。在可能的情况下,代码以十六进制字节的形式给出,其顺序与它们在内存中出现的顺序相同。 16进制字节以外的条目定义如下:

    • REX.W — 表示使用影响操作数大小或指令语义的 REX 前缀。 REX 前缀和其他可选/强制指令前缀的顺序在第 2 章中讨论。请注意,将旧指令提升为 64 位行为的 REX 前缀未在操作码列中明确列出。

    • /digit — 0 到 7 之间的数字表示指令的 ModR/M 字节仅使用 r/m(寄存器或内存)操作数。 reg 字段包含提供指令操作码扩展的数字。

    • /r — 表示指令的 ModR/M 字节包含一个寄存器操作数和一个 r/m 操作数。

    • cb、cw、cd、cp、co、ct — 1 字节 (cb)、2 字节 (cw)、4 字节 (cd)、6 字节 (cp )、操作码后面的 8 字节 (co) 或 10 字节 (ct) 值。该值用于指定代码偏移量,也可能用于指定代码段寄存器的新值。

    • ib、iw、id、io — 1 字节 (ib)、2 字节 (iw)、4 字节 (id) 或 8 字节 (io) 立即操作数操作码、ModR/M 字节或比例索引字节之后的指令。操作码确定操作数是否为有符号值。所有字、双字和四字都以低位字节在前。

    • +rb, +rw, +rd, +ro — 表示操作码字节的低 3 位用于编码没有 modR/M 字节的寄存器操作数。该指令将低 3 位的操作码字节对应的十六进制值列为 000b。在非 64 位模式下,从 0 到 7 的寄存器代码被添加到操作码字节的十六进制值。在 64 位模式下,表示 REX.b 的四位字段,opcode[2:0] 字段编码指令的寄存器操作数。 “+ro”仅适用于 64 位模式。代码见表3-1。

    • +i — 当操作数之一是 FPU 寄存器堆栈中的 ST(i) 时,浮点指令中使用的数字。数字 i(范围从 0 到 7)与加号左侧给出的十六进制字节相加,形成单个操作码字节。

    3.1.1.3 操作码汇总表中的指令列

    “指令”列给出了在 ASM386 程序中出现的指令语句的语法。

    以下是用于表示指令语句中操作数的符号列表:

    • rel8 — 指令结束前 128 字节到指令结束后 127 字节范围内的相对地址。

    • rel16, rel32 — 与汇编指令在同一代码段内的相对地址。 rel16 符号适用于操作数大小属性为 16 位的指令; rel32 符号适用于具有 32 位操作数大小属性的指令。

    • ptr16:16, ptr16:32 — 远指针,通常指向与指令不同的代码段。符号 16:16 表示指针的值有两个部分。冒号左边的值是一个 16 位选择器或指定给代码段寄存器的值。右侧的值对应于目标段内的偏移量。当指令的操作数大小属性为 16 位时使用 ptr16:16 符号;当操作数大小属性为 32 位时使用 ptr16:32 符号。

    • r8 — 字节通用寄存器之一:AL、CL、DL、BL、AH、CH、DH、BH、BPL、SPL、DIL和SIL;或使用 REX.R 和 64 位模式时可用的字节寄存器 (R8L - R15L) 之一。

    • r16 — 字通用寄存器之一:AX、CX、DX、BX、SP、BP、SI、DI;或使用 REX.R 和 64 位模式时可用的字寄存器 (R8-R15) 之一。

    • r32 — 双字通用寄存器之一:EAX、ECX、EDX、EBX、ESP、EBP、ESI、EDI;或在 64 位模式下使用 REX.R 时可用的双字​​寄存器 (R8D - R15D) 之一。

    • r64 — 四字通用寄存器之一:RAX、RBX、RCX、RDX、RDI、RSI、RBP、RSP、R8–R15。这些在使用 REX.R 和 64 位模式时可用。

    • imm8 — 立即字节值。 imm8 符号是介于 –128 和 +127 之间的有符号数。对于 imm8 与字或双字操作数组合的指令,立即值被符号扩展以形成一个字或双字。字的高字节用立即数的最高位填充。

    • imm16 — 用于操作数大小属性为 16 位的指令的立即字值。这是一个介于 –32,768 和 +32,767 之间的数字。

    • imm32 — 立即双字值,用于操作数大小属性为 32 位的指令。它允许使用介于 +2,147,483,647 和 –2,147,483,648 之间的数字。

    • imm64 — 立即四字值,用于操作数大小属性为 64 位的指令。该值允许使用介于 +9,223,372,036,854,775,807 和 –9,223,372,036,854,775,808 之间的数字。

    • r/m8 — 字节操作数,可以是字节通用寄存器(AL、CL、DL、BL、AH、CH、DH、BH、BPL、SPL)的内容, DIL 和 SIL) 或内存中的一个字节。字节寄存器 R8L - R15L 在 64 位模式下使用 REX.R 可用。

    • r/m16 — 用于操作数大小属性为 16 位的指令的字通用寄存器或内存操作数。通用寄存器一词有:AX、CX、DX、BX、SP、BP、SI、DI。内存的内容在有效地址计算提供的地址处找到。字寄存器 R8W - R15W 在 64 位模式下使用 REX.R 可用。

    • r/m32 — 用于操作数大小属性为 32 位的指令的双字通用寄存器或内存操作数。双字通用寄存器有:EAX、ECX、EDX、EBX、ESP、EBP、ESI、EDI。内存的内容在有效地址计算提供的地址处找到。在 64 位模式下使用 REX.R 时,双字寄存器 R8D - R15D 可用。

    • r/m64 — 四字通用寄存器或内存操作数,用于在使用 REX.W 时操作数大小属性为 64 位的指令。四字通用寄存器有:RAX、RBX、RCX、RDX、RDI、RSI、RBP、RSP、R8-R15;这些仅在 64 位模式下可用。内存的内容在有效地址计算提供的地址处找到。

    • m — 内存中的 16、32 或 64 位操作数。

    • m8 — 内存中的字节操作数,通常表示为变量或数组名称,但由 DS:(E)SI 或 ES:(E)DI 寄存器指向。在 64 位模式下,它由 RSI 或 RDI 寄存器指向。

    • m16 — 内存中的单词操作数,通常表示为变量或数组名称,但由 DS:(E)SI 或 ES:(E)DI 寄存器指向。此命名法仅用于字符串指令。

    • m32 — 内存中的双字操作数,通常表示为变量或数组名称,但由 DS:(E)SI 或 ES:(E)DI 寄存器指向。此命名法仅用于字符串指令。

    • m64 — 内存中的内存四字操作数。

    • m128 — 内存中的内存双四字操作数。

    • m16:16、m16:32 和 m16:64 — 包含由两个数字组成的远指针的内存操作数。冒号左边的数字对应于指针的段选择器。右边的数字对应于它的偏移量。

    • m16&32、m16&16、m32&32、m16&64 — 由数据项对组成的内存操作数,其大小在 & 符号的左侧和右侧指示。允许所有内存寻址模式。 BOUND 指令使用 m16&16 和 m32&32 操作数来提供包含数组索引上限和下限的操作数。 LIDT 和 LGDT 使用 m16&32 操作数来提供用于加载限制字段的字和用于加载相应 GDTR 和 IDTR 寄存器的基字段的双字。 LIDT 和 LGDT 在 64 位模式下使用 m16&64 操作数来提供一个字来加载限制字段,以及一个四字来加载相应的 GDTR 和 IDTR 寄存器的基字段。

    • moffs8、moffs16、moffs32、moffs64 — MOV 指令的某些变体使用的字节、字或双字类型的简单内存变量(内存偏移)。实际地址由相对于段基的简单偏移量给出。指令中没有使用 ModR/M 字节。 moffs 显示的数字表示它的大小,由指令的 address-size 属性决定。

    • Sreg — 段寄存器。段寄存器位分配为 ES = 0、CS = 1、SS = 2、DS = 3、FS = 4 和 GS = 5。

    • m32fp、m64fp、m80fp — 内存中的单精度、双精度和双扩展精度(分别)浮点操作数。这些符号指定用作 x87 FPU 浮点指令的操作数的浮点值。

    • m16int、m32int、m64int — 内存中的字、双字和四字整数(分别)操作数。这些符号指定用作 x87 FPU 整数指令的操作数的整数。

    • ST 或 ST(0) — FPU 寄存器堆栈的顶部元素。

    • ST(i) — FPU 寄存器堆栈顶部的第 i 个元素(i ← 0 到 7)。

    • mm — MMX 寄存器。 64 位 MMX 寄存器为:MM0 到 MM7。

    • mm/m32 — MMX 寄存器或 32 位内存操作数的低 32 位。 64 位 MMX 寄存器是:MM0 到 MM7。内存的内容在有效地址计算提供的地址处找到。

    • mm/m64 — MMX 寄存器或 64 位内存操作数。 64 位 MMX 寄存器是:MM0 到 MM7。内存的内容在有效地址计算提供的地址处找到。

    • xmm — XMM 寄存器。 128 位 XMM 寄存器为:XMM0 到 XMM7; XMM8 到 XMM15 在 64 位模式下使用 REX.R 可用。

    • xmm/m32— XMM 寄存器或 32 位内存操作数。 128 位 XMM 寄存器为 XMM0 到 XMM7; XMM8 到 XMM15 在 64 位模式下使用 REX.R 可用。内存的内容在有效地址计算提供的地址处找到。

    • xmm/m64 — XMM 寄存器或 64 位内存操作数。 128 位 SIMD 浮点寄存器为 XMM0 到 XMM7; XMM8 到 XMM15 在 64 位模式下使用 REX.R 可用。内存的内容在有效地址计算提供的地址处找到。

    • xmm/m128 — XMM 寄存器或 128 位内存操作数。 128 位 XMM 寄存器为 XMM0 到 XMM7; XMM8 到 XMM15 在 64 位模式下使用 REX.R 可用。内存的内容在有效地址计算提供的地址处找到。

    • — 表示隐含使用 XMM0 寄存器。当有歧义时,xmm1 使用 XMM 寄存器指示第一个源操作数,而 xmm2 使用 XMM 寄存器指示第二个源操作数。一些指令使用 XMM0 寄存器作为第三个源操作数,用 . 表示。第三个 XMM 寄存器操作数的使用隐含在指令编码中,不影响 ModR/M 编码。

    • ymm — YMM 寄存器。 256 位 YMM 寄存器为:YMM0 到 YMM7; YMM8 到 YMM15 在 64 位模式下可用。

    • m256 — 内存中的 32 字节操作数。此命名法仅用于 AVX 指令。

    • ymm/m256 — YMM 寄存器或 256 位内存操作数。

    • — 表示使用 YMM0 寄存器作为隐式参数。

    • bnd — 128 位边界寄存器。 BND0 到 BND3。

    • mib — 使用SIB 寻址形式的内存操作数,其中索引寄存器不用于地址计算,Scale 被忽略。有效地址计算只使用基址和位移。

    • m512 — 内存中的 64 字节操作数。

    • zmm/m512 — ZMM 寄存器或 512 位内存操作数。

    • {k1}{z} — 用作指令写掩码的掩码寄存器。 64 位 k 寄存器是:k1 到 k7。写掩码规范仅通过 EVEX 前缀提供。掩蔽可以作为合并掩蔽来完成,其中旧值被保留用于被掩蔽的元素或作为归零掩蔽。掩码类型由 EVEX.z 位决定。

    • {k1} — 没有 {z}:屏蔽寄存器用作指令写屏蔽,用于不允许归零屏蔽但支持合并屏蔽的指令。这对应于要求 aaa 字段的值不为 0 的指令(例如,gather)和只允许合并屏蔽的存储类型指令。

    • k1 — 用作常规操作数(目标或源)的掩码寄存器。 64 位的 k 寄存器是:k0 到 k7。

    • mV — 向量内存操作数;操作数大小取决于指令。

    • vm32{x,y, z} — 使用 VSIB 内存寻址指定的内存操作数向量数组。内存地址数组使用公共基址寄存器、恒定比例因子和向量索引寄存器指定,其中 XMM 寄存器 (vm32x)、YMM 寄存器 (vm32y) 或 ZMM 寄存器中具有 32 位索引值的各个元素(vm32z)。

    • vm64{x,y, z} — 使用 VSIB 内存寻址指定的内存操作数向量数组。内存地址数组使用公共基址寄存器、恒定比例因子和向量索引寄存器指定,其中 XMM 寄存器 (vm64x)、YMM 寄存器 (vm64y) 或 ZMM 寄存器中具有 64 位索引值的各个元素(vm64z)。

    • zmm/m512/m32bcst — 操作数可以是 ZMM 寄存器、512 位内存位置或从 32 位内存位置加载的 512 位向量。

    • zmm/m512/m64bcst — 操作数可以是 ZMM 寄存器、512 位内存位置或从 64 位内存位置加载的 512 位向量。

    • — 表示使用 ZMM0 寄存器作为隐式参数。

    • {er} — 表示支持嵌入式舍入控制,仅适用于寄存器-寄存器形式的指令。这也意味着支持 SAE(抑制所有异常)。

    • {sae} — 表示支持 SAE(抑制所有异常)。这用于支持 SAE 但不支持嵌入式舍入控制的指令。

    • SRC1 — 表示以 VEX/EVEX 前缀编码并具有两个或多个源操作数的指令的指令语法中的第一个源操作数。

    • SRC2 — 表示以 VEX/EVEX 前缀编码并具有两个或多个源操作数的指令的指令语法中的第二个源操作数。

    • SRC3 — 表示以 VEX/EVEX 前缀编码并具有三个源操作数的指令的指令语法中的第三个源操作数。

    • SRC —单源指令中的源。

    • DST — 指令中的目的地。该字段由 reg_field 编码。

    【讨论】:

      【解决方案2】:

      我最喜欢的来源是英特尔本身:Intel® 64 and IA-32 Architectures Software Developer Manuals。与过去的版本不同,所有卷现在都很好地封装在一个(3044 页)PDF 中。

      看起来对您最有帮助的部分是第 2 卷第 3 章中的 3.1.1.1(截至我撰写本文时最新 PDF 的第 432 页)。

      【讨论】:

        【解决方案3】:

        许多指令立即版本的操作码,包括 83,使用 ModR/M 字节中的 3 位 /r 字段作为 3 个额外的操作码位。英特尔的第 2 卷手册记录了这一点,我认为附录中的操作码表包含了它。

        这就是为什么大多数原始 8086 立即数指令,如 and r/m, imm 仍然只允许 2 个操作数,不像 shrd eax, edx, 4imul edx, [rdi], 12345 两个 ModRM 字段都用于编码 dst/src 操作数,以及操作码暗示立即数。

        SHRD/SHLD and were added with 386,并且 imul-immediate 添加了 186。不幸的是,复制和与 (and eax, edx, 0xf) 不可编码,但至少 x86 可以使用 LEA 进行非常常见的复制和添加或子操作。

        但是,如果每条立即数和单操作数指令(如 pushnot)都需要一个完整的操作码,那么 8086 就会用完 1 字节的操作码。 (特别是因为设计者选择在 AL 和 AX 没有 modrm 字节的短格式上花费大量编码空间,例如 cmp ax, 12345 在 16 位模式下只有 3 个字节而不是 4 个字节,或者 cmp eax, imm32 只有 5 个字节在 32 位模式下,cmp r/m32, imm32 而不是 6。对于单字节 xchg-with-ax 和单字节 inc/dec 寄存器。)


        示例:解码 48 83 C4 38(来自 How does one opcode byte decode to different instructions depending on the "register/opcode" field? What is that?,此 Q 的副本)

        48 是一个 REX.W 前缀(REX 只设置了 W 位,因此它表示 64 位操作数大小,但没有高位寄存器)。

        操作码83 表示它可以是 7 条不同的指令,具体取决于名为“注册/操作码字段”的字段

        每条指令都有自己的文档,例如add (html extract of the vol2 manual),为ADD r/m64, imm8 显示类似
        REX.W + 83 /0 ib 的编码,这就是你所拥有的。

        diagram of the ModRM bit fields from wiki.osdev.org

          7                           0
        +---+---+---+---+---+---+---+---+
        |  mod  |    reg    |     rm    |
        +---+---+---+---+---+---+---+---+
        

        0xc4 = 0b11000100,因此 reg 字段 = 0。因此我们的操作码是 83 /0,在 Intel 的表示法中。

        ModRM 的其余字段是:

        • mode = 0b11,因此 rm 字段编码的是寄存器操作数,而不是寻址模式的基址寄存器。
        • rm = 0b100。 reg #4 = SPL/SP/ESP/RSP。 (在这种情况下,RSP 因为它是 64 位操作数大小)。请参阅英特尔手册,或 https://wiki.osdev.org/X86-64_Instruction_Encoding#Registers 获取表格。

        所以指令是add rsp, 0x38

        ndisasm -b64 同意:

        $ cat > foo.asm
        db 0x48, 0x83, 0xC4, 0x38
        $ nasm foo.asm     # create a flat binary with those bytes, not an object file
        $ ndisasm -b64 foo
        00000000  4883C438          add rsp,byte +0x38
        
        猜你喜欢
        • 2013-08-13
        • 2020-10-02
        • 2011-09-18
        • 2011-04-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-08
        相关资源
        最近更新 更多