[英]What ensures reads/writes of operands occurs at desired timed with extended ASM?
根據GCC的擴展ASM和匯編程序模板 ,要保持指令連續,它們必須位於同一ASM塊中。 我很難理解是什么提供了對具有多個語句的塊中的操作數進行讀寫的調度或計時。
例如,使用CPUID
時需要保留EBX
或RBX
,因為根據ABI,調用者擁有它。 關於EBX
和RBX
的使用存在一些未解決的問題,因此我們希望無條件地保留它(這是一個要求)。 因此,需要將三個指令編碼到單個ASM塊中,以確保指令的連續性(例如:第一段中討論的匯編模板):
unsigned int __FUNC = 1, __SUBFUNC = 0;
unsigned int __EAX, __EBX, __ECX, __EDX;
__asm__ __volatile__ (
"push %ebx;"
"cpuid;"
"pop %ebx"
: "=a"(__EAX), "=b"(__EBX), "=c"(__ECX), "=d"(__EDX)
: "a"(__FUNC), "c"(__SUBFUNC)
);
如果在錯誤的時間點解釋了表示操作數的表達式,則__EBX
將是保存的EBX
(而不是CPUID
的EBX
),如果啟用了PIC,則它很可能是指向全局偏移表(GOT)的指針。
該表達式確切地在何處指定將CPUID
的%EBX
存儲到__EBX
(1)在PUSH %EBX
; (2)在CPUID
; 但是(3)在POP %EBX
之前?
在您的問題中,您將提供一些執行ebx
push
和pop
的代碼。 在使用-fPIC
(位置無關代碼)使用gcc進行編譯時,保存ebx
的想法是正確的。 在這種情況下返回時,不要破壞ebx
是我們的職責。 不幸的是,您使用ebx
明確定義約束的方式。 通常,如果您使用的是PIC代碼並且將=b
指定為輸出約束,則編譯器會警告您( 錯誤:'asm'中的操作數約束不一致 )。 為什么它不會為您發出警告,這很不尋常。
要解決此問題,您可以讓匯編器模板為您選擇一個寄存器。 無需推送和彈出,我們只需將%ebx
與編譯器選擇的未使用寄存器交換,然后通過將其交換回來來恢復它。 由於我們不希望在交換過程中讓編譯器破壞我們的輸入寄存器,因此我們指定了早期的clobber修飾符,因此最終以=&r
(而不是OPs代碼中的=b
)為約束。 在這里可以找到更多關於修飾符的信息 。 您的代碼(32位)如下所示:
unsigned int __FUNC = 1, __SUBFUNC = 0;
unsigned int __EAX, __EBX, __ECX, __EDX;
__asm__ __volatile__ (
"xchgl\t%%ebx, %k1\n\t" \
"cpuid\n\t" \
"xchgl\t%%ebx, %k1\n\t"
: "=a"(__EAX), "=&r"(__EBX), "=c"(__ECX), "=d"(__EDX)
: "a"(__FUNC), "c"(__SUBFUNC));
如果打算針對X86_64(64位)進行編譯,則需要保存%rbx
的全部內容。 上面的代碼將無法正常工作。 您將必須使用類似:
uint32_t __FUNC = 1, __SUBFUNC = 0;
uint32_t __EAX, __ECX, __EDX;
uint64_t __BX; /* Big enough to hold a 64 bit value */
__asm__ __volatile__ (
"xchgq\t%%rbx, %q1\n\t" \
"cpuid\n\t" \
"xchgq\t%%rbx, %q1\n\t"
: "=a"(__EAX), "=&r"(__BX), "=c"(__ECX), "=d"(__EDX)
: "a"(__FUNC), "c"(__SUBFUNC));
您可以使用條件編譯來處理X86_64和i386:
uint32_t __FUNC = 1, __SUBFUNC = 0;
uint32_t __EAX, __ECX, __EDX;
uint64_t __BX; /* Big enough to hold a 64 bit value */
#if defined(__i386__)
__asm__ __volatile__ (
"xchgl\t%%ebx, %k1\n\t" \
"cpuid\n\t" \
"xchgl\t%%ebx, %k1\n\t"
: "=a"(__EAX), "=&r"(__BX), "=c"(__ECX), "=d"(__EDX)
: "a"(__FUNC), "c"(__SUBFUNC));
#elif defined(__x86_64__)
__asm__ __volatile__ (
"xchgq\t%%rbx, %q1\n\t" \
"cpuid\n\t" \
"xchgq\t%%rbx, %q1\n\t"
: "=a"(__EAX), "=&r"(__BX), "=c"(__ECX), "=d"(__EDX)
: "a"(__FUNC), "c"(__SUBFUNC));
#else
#error "Unknown architecture."
#endif
GCC有__cpuid
中定義的宏cpuid.h
。 它定義了宏,以便僅在需要時保存ebx
和rbx
寄存器。 您可以在此處找到GCC 4.8.1宏定義,以了解它們如何處理cpuid.h中的 cpuid
。
精明的讀者可能會問這個問題-是什么阻止了編譯器選擇ebx
或rbx
作為交換的暫存器。 編譯器在PIC上下文中了解ebx
和rbx
,因此不會將其用作暫存寄存器。 這是基於我多年來的個人觀察並回顧了從C代碼生成的匯編器(.s)文件。 我不能肯定地說更古老的gcc版本如何處理它,所以可能是一個問題。
我認為您了解但明確地說,“連續”規則意味着:
asm ("a");
asm ("b");
asm ("c");
...可能會插入其他指令,因此,如果不希望這樣做,則必須像這樣重寫:
asm ("a\n"
"b\n"
"c");
...現在將其作為一個整體插入。
至於cpuid
代碼段,我們有兩個問題:
cpuid
指令將覆蓋ebx
,從而破壞了PIC代碼必須保留在其中的數據。
我們要提取cpuid
放在ebx
的值,而永遠不要返回帶有“錯誤” ebx
值的編譯代碼。
一種可能的解決方案是:
unsigned int __FUNC = 1, __SUBFUNC = 0;
unsigned int __EAX, __EBX, __ECX, __EDX;
__asm__ __volatile__ (
"push %ebx;"
"cpuid;"
"mov %ebx, %ecx"
"pop %ebx"
: "=c"(__EBX)
: "a"(__FUNC), "c"(__SUBFUNC)
: "eax", "edx"
);
__asm__ __volatile__ (
"push %ebx;"
"cpuid;"
"pop %ebx"
: "=a"(__EAX), "=c"(__ECX), "=d"(__EDX)
: "a"(__FUNC), "c"(__SUBFUNC)
);
無需將ebx
標記為已破壞,而是將其放回原來的狀態。
(我沒有做太多的Intel編程,所以我可能有一些特定於匯編器的詳細信息,但這是asm
工作方式。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.