簡體   English   中英

GCC 內聯程序集錯誤:“'int' 的操作數大小不匹配”

[英]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.hsyscall函數:

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

(來自 Godbolt 編譯器資源管理器)

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 系統調用的調用約定是什么

另請參閱標簽維基。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM