1

我目前正在调查该imul指令的一些奇怪行为,因为英特尔官方手册似乎与现实略有不同。

我注意到的第一件事是英特尔手册不认为这个例子是正确的指令:

imul rax, 2

然而 GCC/GAS (with .intel_syntax noprefix) 和 NASM 都毫无问题地接受这个指令。使用objdump -d向我展示了这一点:

48 6b c0 02             imul   $0x2,%rax,%rax

这意味着它被翻译成实际上记录在手册中的不同指令。

我已经觉得这很奇怪,并且想知道为什么它会存在。我唯一能找到这个文档的地方是在NASM 指令集中,而且奇怪的是,在imul英特尔手册中的指令描述中。后者写道:

  • 双操作数形式— 使用这种形式,目标操作数(第一个操作数)乘以源操作数(第二个操作数)。目标操作数是通用寄存器,源操作数是立即数、通用寄存器或内存位置。中间产品(输入操作数大小的两倍)被截断并存储在目标操作数位置。

这与同一条指令的操作码表不一致。

NASM指令集也提到imul reg64, sbytedwordimul reg64, imm指令,我都不明白它们的意思。imm这意味着也可以使用 64 位立即数,不是吗?sbytedword我不清楚的含义。

现在到 32 位立即数:NASM 指令集提到imul reg64, imm32,而英特尔手册和 NASM 集都提到imul r64, r/m64, imm32。但是,通常当使用比目标操作数低的位计数的立即数时,英特尔手册在操作码表的描述列中特别提到了符号扩展。在这种情况下,它没有被提及,所以我想知道如果我碰巧使用负的 32 位立即数(换句话说,需要所有 32 位)会发生什么。

这是我测试过的汇编代码:

        global  imm_test

        section .text

imm_test:
        mov     rax, rdi
        imul    rax, 0xFFDFFFFF
        ret

然后我从 C 中调用了imm_test函数:

#include <stdio.h>

int imm_test(int n);

int main() {
    printf("%d\n", imm_test(1));
    return 0;
}

如果要对 32 位立即数进行符号扩展,我假设必须打印的值为-2097153,当使用 NASM 进行汇编和 GCC 编译和链接时,这正是打印的内容。
然而 NASM 给了我这个警告:

test.asm:7: warning: signed dword immediate exceeds bounds [-w+number-overflow]
test.asm:7: warning: dword data exceeds bounds [-w+number-overflow]

但是,再次查看反汇编代码,该指令的编码方式与我期望的完全一样:

48 69 c0 ff ff df ff    imul   $0xffffffffffdfffff,%rax,%rax

它是一个 32 位立即符号扩展为 64 位。

当我将汇编代码的语法更改为 GAS 时.intel_syntax noprefix,如下所示:

        .intel_syntax noprefix
        .global  imm_test

        .text

imm_test:
        mov     rax, rdi
        imul    rax, 0xFFDFFFFF
        ret

并尝试使用 GNU 汇编器来组装它,我不只是得到一个警告,我得到一个错误:

test.S: Assembler messages:
test.S:8: Error: operand type mismatch for `imul

将说明更改imul为正确记录的imul rax, rax, 0xFFDFFFFF版本不会改变任何内容。

所以我想知道,为什么文档imul如此不一致,为什么官方支持 32 位立即数(并且也可以正常工作),但它们却给出错误或警告?

4

2 回答 2

3

汇编源使用,而不是立即位模式编码

imul r64, r/m64, sign_extended_imm32或 imm8 是唯一具有 64 位操作数大小的形式1;请参阅英特尔的手册(https://www.felixcloutier.com/x86/imul),因此0x0000_0000_FFDF_FFFF不可编码。

但这就是0xFFDF_FFFF意思;与任何写入数字的位值方式一样,左侧未写入的位置被假定为 0。

NASM 对截断发出警告,GAS 只是错误并显示一条不太有用的消息,但在这两种情况下,唯一的问题是常量的数值。在.intel_syntax noprefixGAS 中, imul rax, rax, 0x7FDFFFFF组装得很好。带符号的正 32 位数字不是问题。(高位 = 0。)

mov eax, 0xFFDF_FFFF是可编码的,因为操作数大小是 32 位,因此源操作数是原始 32 位值,不会隐式地符号扩展为 64 位。

作为执行mov到 EAX的一部分,RAX 的高 32 位被归零。您可以将其视为将零扩展为 64 位的常量,但该扩展是作为在 x86-64 上写入 32 位寄存器的 32 位指令的一部分发生的。 add eax, 0xFFDF_FFFF是一个更清晰的例子:它正在执行 32 位加法,将结果截断为 32 位,然后将其写入 EAX。RAX 的隐式零扩展发生在添加之后的寄存器写入期间,而不是在读取输入时。只有mov复制一个不变的值,才有空间以不同的方式看待它。

无论哪种方式,汇编程序都了解您编写的全部值,并会告诉您是否不可能将该值编码为任何操作数大小的操作数。 请记住,汇编源代码使用,而不是机器代码的位模式。这就是您使用汇编程序的部分原因。如果你的意思是0xFFFF_FFFF_FFDF_FFFF,你应该写那个。


imul rax,2成为“单独的形式”?

NASM(以及包括 GAS 在内的大多数其他汇编程序)imul x, imm接受imul x, x, imm. 与 AVX 指令相同,例如vpand xmm0, xmm0, xmm1.

当您不想利用非破坏性的单独目标时,它只是使您不必将相同的寄存器作为目标和第一个源重复两次。该形式没有不同的机器编码,只有汇编级别的语法,这就是为什么您在英特尔的手册中找不到它,以及为什么反汇编显示汇编程序选择的真实形式。


脚注 1: 您提到了NASM 附录 B,其中显示:

IMUL             reg64,reg64,imm8         X64 
IMUL             reg64,reg64,sbytedword   X64,ND 
IMUL             reg64,reg64,imm32        X64 
IMUL             reg64,reg64,imm          X64,ND 

我不知道这些ND条目的意义是什么,但mov它是 x86-64 中唯一可以采用 64 位立即数的指令。imm8imm32表格是您选项的完整枚举。sbytedword(有符号字节或双字)也是如此。普通的不合格imm只是令人困惑和错误的。

(NASM 将reg64,reg64,imm8表单与 分开记录reg64,imm8,这只是 NASM 让中间操作数隐式地与第一个操作数相同。机器编码仍然imul r64, r/m64, immediate使用两种不同的操作码,一个用于 8 位立即数,一个用于 32 位立即数。相同的操作码做 32 位和 16 位操作数大小,没有或不同的前缀。)

NASM 的附录 B 之前一直是错误的,例如关于每条指令的每种形式是新的 CPU 版本 。NASM 2.05 附录的这个分支纠正了这些错误,并且是一个有用的参考。它仍然包含更多文本描述,当指令列表变长时,后来的 NASM 版本删除了这些描述。(但是,它只列出了 32 位和 16 位模式指令,没有列出 64 位模式指令。)

但实际上,当我想检查imul r,r/m,imm186 中是否是新的或类似的东西时,我只会参考 ecm 的 NASM 附录分支。 如果我想知道 x86 ISA 的当前状态、可用的指令形式,我会查看 Intel 的手册。(或者实际上是从英特尔的第 2 卷 PDF 中抓取的 HTML,位于https://www.felixcloutier.com/x86/上。)英特尔有时会出现错误,但不是关于手册中重要/基本的东西。

于 2021-10-02T16:46:18.017 回答
1

为什么官方支持 32 位立即数(并且也可以正常工作),但它们却给出错误或警告?

我假设 64 位 GCC 编译器在内部使用带符号的 64 位整数。

我使用的版本会打印一条错误消息,因为 64 位值+0xFFDFFFFF不在范围内-0x80000000……+0x7FFFFFFF因此无法转换为 32 位有符号值!

注意:“支持 32 位常量”并不意味着如果需要超过 32 位,汇编器会自动将常量截断为 32 位!而且您至少需要 33 位来将值存储0xFFDFFFFF为有符号数!

把常数写成 as0xFFFFFFFFFFDFFFFFFFFF或 as-0x200001效果很好。

也许 NASM 的开发人员希望将类似的数字0xFFFFFFFE解释为与可以写为-2的旧程序的兼容性;-20xFFFFFFFE

...而 GNU AS 的开发人员假设开发人员键入0xFFFFFFFE64 位程序的真正含义是+4294967294而不是-2.

...这意味着 GAS 假设应具有与a 之后imul rax, rax, 0xFFDFFFFF相同的结果。imul rax, rbxmov rbx, 0xFFDFFFFF

结果,GNU AS 打印一条错误消息,因为imul rax, rax, immediate不能rax+4294967294.

所以我想知道,为什么文档imul如此不一致......

这与文档无关:

GCC(和 GNU 汇编器)使用与 Intel 官方语法不同的语法。“nasm”更接近官方语法。

...我认为这在某处有记录。

于 2021-10-02T09:30:46.497 回答