我目前正在調查該imul指令的一些奇怪行為,因為官方英特爾手冊似乎與實際情況略有不同。
我注意到的第一件事是英特爾手冊不認為這個例子是正確的指令:
imul rax, 2
然而 GCC/GAS(帶有.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;
}
If that 32-bit immediate were to be sign-extended, the value I would assume would have to be printed is -2097153, which, when using NASM to assemble and GCC to compile and link, is exactly what is printed.
And yet NASM gives me this warning:
test.asm:7: warning: signed dword immediate exceeds bounds [-w number-overflow] test.asm:7: warning: dword data exceeds bounds [-w number-overflow]
However, looking at the disassembly again, the instruction is encoded exactly the way I would expect it to be:
48 69 c0 ff ff df ff imul $0xffffffffffdfffff,%rax,%rax
It's a 32-bit immediate sign-extended to 64-bit.
When I change the syntax of the assembly code to GAS's .intel_syntax noprefix like so:
.intel_syntax noprefix
.global imm_test
.text
imm_test:
mov rax, rdi
imul rax, 0xFFDFFFFF
ret
and try to assemble this with the GNU assembler, I don't just get a warning, I get an error:
test.S: Assembler messages: test.S:8: Error: operand type mismatch for `imul
Changing the imul instructions to the properly documented imul rax, rax, 0xFFDFFFFF version does not change anything.
So I'm wondering, why is the documentation for imul so inconsistent, and why are 32-bit immediates officially supported (and also work correctly), yet they give errors or warnings?
uj5u.com熱心網友回復:
匯編源使用值,而不是立即位模式編碼
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 noprefix燃氣, 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(以及大多數其他的匯編包括天然氣)接受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 位指令的指令。在imm8和imm32形式是你的選擇一個完整的列舉。那么,sbytedword(有符號位元組或DWORD)。簡單的不合格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 的手冊。(或者實際上是從https://www.felixcloutier.com/x86/上的英特爾第 2 卷 PDF 中抓取的 HTML 。)英特爾有時會出錯,但不是關于手冊要點的重要/基本內容。
uj5u.com熱心網友回復:
為什么官方支持 32 位立即數(并且也可以正常作業),但它們會給出錯誤或警告?
我假設 64 位 GCC 編譯器在內部使用帶符號的 64 位整數。
我使用的版本會列印一條錯誤訊息,因為 64 位值 0xFFDFFFFF不在范圍內-0x80000000…… 0x7FFFFFFF因此無法轉換為 32 位有符號值!
注意:“支持 32 位常量”并不意味著如果需要超過 32 位,匯編器會自動將常量截斷為 32 位!并且您至少需要 33 位才能將值存盤0xFFDFFFFF為有符號數!
將常量寫入 as0xFFFFFFFFFFDFFFFFFFFF或 as-0x200001效果很好。
也許 NASM 的開發人員希望像這樣的數字0xFFFFFFFE被解釋為-2與舊程式兼容,-2可以寫成0xFFFFFFFE;
... GNU AS 的開發人員假設開發人員輸入0xFFFFFFFE64 位程式確實意味著 4294967294而不是-2。
...這意味著 GAS 假設imul rax, rax, 0xFFDFFFFF應具有與imul rax, rbxa 之后相同的結果mov rbx, 0xFFDFFFFF。
因此,GNU AS 會列印一條錯誤訊息,因為imul rax, rax, immediate不能rax與相乘 4294967294。
所以我想知道,為什么檔案
imul如此不一致......
這與檔案無關:
GCC(和 GNU 匯編器)使用與 Intel 官方語法不同的語法。“nasm”更接近官方語法。
......我認為這是在某處記錄的。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/313750.html
標籤:assembly x86-64 nasm gnu-assembler immediate-operand
上一篇:為什么要保存某些暫存器?如果沒有,會出現什么問題?[復制]
下一篇:澄清TST指令-ARM匯編
