我目前正在调查该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, sbytedword
和imul 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 位立即数(并且也可以正常工作),但它们却给出错误或警告?