[英]GCC Inline-Assembly Error: “Operand size mismatch for 'int'”
首先,如果有人知道標准 C 庫的一個函數,該函數無需查找二進制零即可打印字符串,但需要繪制字符數,請告訴我!
否則,我有這個問題:
void printStringWithLength(char *str_ptr, int n_chars){
asm("mov 4, %rax");//Function number (write)
asm("mov 1, %rbx");//File descriptor (stdout)
asm("mov $str_ptr, %rcx");
asm("mov $n_chars, %rdx");
asm("int 0x80");
return;
}
GCC 將以下錯誤告知“int”指令:
"Error: operand size mismatch for 'int'"
有人可以告訴我這個問題嗎?
您的代碼存在許多問題。 讓我一步一步地回顧它們。
首先, int $0x80
系統調用接口僅適用於 32 位代碼。 您不應該在 64 位代碼中使用它,因為它只接受 32 位參數。 在 64 位代碼中,使用syscall
接口。 系統調用是相似的,但有些數字不同。
其次,在 AT&T 匯編語法中,立即數必須以美元符號為前綴。 所以它是mov $4, %rax
,而不是mov 4, %rax
。 后者會嘗試將地址4
的內容移動到rax
,這顯然不是您想要的。
第三,不能在內聯匯編中只引用自動變量的名稱。 如果需要,您必須使用擴展程序集告訴編譯器您想使用哪些變量。 例如,在您的代碼中,您可以執行以下操作:
asm volatile("mov $4, %%eax; mov $1, %%edi; mov %0, %%esi; mov %2, %%edx; syscall"
:: "r"(str_ptr), "r"(n_chars) : "rdi", "rsi", "rdx", "rax", "memory");
第四,gcc是一個優化編譯器。 默認情況下,它假定內聯匯編語句類似於純函數,輸出是顯式輸入的純函數。 如果輸出未使用,則可以優化 asm 語句,或者在使用相同輸入運行時將其提升到循環之外。
但是像write
這樣的系統調用有一個副作用,你需要編譯器來保持,所以它不是純粹的。 您需要 asm 語句以與 C 抽象機相同的次數和相同的順序運行。 asm volatile
將使這發生。 (沒有輸出的 asm 語句是隱式易失性的,但是當副作用是 asm 語句的主要目的時,最好將其明確化。另外,我們確實希望使用輸出操作數來告訴編譯器 RAX 已修改,以及作為輸入,這是我們無法用 clobber 做的。)
您總是需要使用擴展內聯匯編語法向編譯器准確描述 asm 的輸入、輸出和破壞。 否則你會踩到編譯器的腳趾(它假設寄存器不變,除非它們是輸出或破壞)。 (相關: ?我怎樣才能表明該內存*可以使用尖*通過內聯ASM參數顯示,指針輸入操作數本身並不意味着指向的內存也是一個輸入用的虛擬。 "m"
輸入或"memory"
破壞以強制所有可訪問的內存同步。)
您應該簡化代碼,不要編寫自己的mov
指令將數據放入寄存器,而是讓編譯器執行此操作。 例如,您的程序集變為:
ssize_t retval;
asm volatile ("syscall" // note only 1 instruction in the template
: "=a"(retval) // RAX gets the return value
: "a"(SYS_write), "D"(STDOUT_FILENO), "S"(str_ptr), "d"(n_chars)
: "memory", "rcx", "r11" // syscall destroys RCX and R11
);
其中SYS_WRITE
在<sys/syscall.h>
, STDOUT_FILENO
在<stdio.h>
。 我不會向您解釋擴展內聯匯編的所有細節。 通常使用內聯匯編通常是一個壞主意。 如果您有興趣,請閱讀文檔。 ( https://stackoverflow.com/tags/inline-assembly/info )
第五,盡可能避免使用內聯匯編。 例如,要進行系統調用,請使用unistd.h
的syscall
函數:
syscall(SYS_write, STDOUT_FILENO, str_ptr, (size_t)n_chars);
這是正確的。 但它不會內聯到您的代碼中,因此例如,如果您想真正內聯系統調用而不是調用 libc 函數,請使用 MUSL 中的包裝宏。
第六,經常檢查你要調用的系統調用是否已經在C標准庫中可用。 在這種情況下,它是,所以你應該只寫
write(STDOUT_FILENO, str_ptr, n_chars);
並完全避免這一切。
第七,如果您更喜歡使用stdio
,請改用fwrite
:
fwrite(str_ptr, 1, n_chars, stdout);
你的代碼有很多問題(而且很少有理由使用內聯 asm),以至於不值得嘗試真正糾正所有這些問題。 相反,使用write(2)
系統調用以正常方式,通過手冊頁中記錄的 POSIX 函數/libc 包裝器,或使用 ISO C <stdio.h>
fwrite(3)
。
#include <unistd.h>
static inline
void printStringWithLength(const char *str_ptr, int n_chars){
write(1, str_ptr, n_chars);
// TODO: check error return value
}
為什么你的代碼沒有組裝:
在 AT&T 語法中,立即數總是需要一個$
裝飾器。 如果您使用asm("int $0x80")
您的代碼將被組裝。
匯編器抱怨0x80
,這是對絕對地址0x80
的內存引用。 沒有任何形式的int
將中斷向量作為立即數以外的任何東西。 我不確定它為什么抱怨size ,因為內存引用在 AT&T 語法中沒有隱含的大小。
這將使其組裝,此時您將收到鏈接器錯誤:
In function `printStringWithLength':
5 : <source>:5: undefined reference to `str_ptr'
6 : <source>:6: undefined reference to `n_chars'
collect2: error: ld returned 1 exit status
mov $str_ptr, %rcx
意味着將符號str_ptr
的地址立即str_ptr
到%rcx
。 在 AT&T 語法中,您不必在使用它們之前聲明外部符號,因此未知名稱被假定為全局/靜態標簽。 如果您有一個名為str_ptr
的全局變量,則該指令將引用其地址(這是一個鏈接時常量,因此可以用作立即數)。
正如其他人所說,這完全是使用 GNU C 內聯 asm 處理事情的錯誤方法。 有關指南的更多鏈接,請參閱內聯程序集標記 wiki。
此外,您使用了錯誤的 ABI。 int $0x80
是 x86 32 位系統調用 ABI,因此它不適用於 64 位指針。 x86-64 上 UNIX 和 Linux 系統調用的調用約定是什么
另請參閱x86標簽維基。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.