簡體   English   中英

如何確保對操作數的讀/寫在擴展ASM的期望時間發生?

[英]What ensures reads/writes of operands occurs at desired timed with extended ASM?

根據GCC的擴展ASM和匯編程序模板 ,要保持指令連續,它們必須位於同一ASM塊中。 我很難理解是什么提供了對具有多個語句的塊中的操作數進行讀寫的調度或計時。

例如,使用CPUID時需要保留EBXRBX ,因為根據ABI,調用者擁有它。 關於EBXRBX的使用存在一些未解決的問題,因此我們希望無條件地保留它(這是一個要求)。 因此,需要將三個指令編碼到單個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 (而不是CPUIDEBX ),如果啟用了PIC,則它很可能是指向全局偏移表(GOT)的指針。

該表達式確切地在何處指定將CPUID%EBX存儲到__EBX (1)在PUSH %EBX (2)在CPUID 但是(3)在POP %EBX之前?

在您的問題中,您將提供一些執行ebx pushpop的代碼。 在使用-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 它定義了宏,以便僅在需要時保存ebxrbx寄存器。 您可以在此處找到GCC 4.8.1宏定義,以了解它們如何處理cpuid.h中的 cpuid

精明的讀者可能會問這個問題-是什么阻止了編譯器選擇ebxrbx作為交換的暫存器。 編譯器在PIC上下文中了解ebxrbx ,因此不會將其用作暫存寄存器。 這是基於我多年來的個人觀察並回顧了從C代碼生成的匯編器(.s)文件。 我不能肯定地說更古老的gcc版本如何處理它,所以可能是一個問題。

我認為您了解但明確地說,“連續”規則意味着:

asm ("a");
asm ("b");
asm ("c");

...可能會插入其他指令,因此,如果不希望這樣做,則必須像這樣重寫:

asm ("a\n"
     "b\n"
     "c");

...現在將其作為一個整體插入。


至於cpuid代碼段,我們有兩個問題:

  1. cpuid指令將覆蓋ebx ,從而破壞了PIC代碼必須保留在其中的數據。

  2. 我們要提取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.

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