簡體   English   中英

即使我有問題,GCC也不會在我的內聯asm函數調用周圍推送寄存器

[英]GCC doesn't push registers around my inline asm function call even though I have clobbers

我有一個修改(ecx)(或任何其他寄存器)的函數(C)

int proc(int n) {
    int ret;
    asm volatile ("movl %1, %%ecx\n\t" // mov (n) to ecx
                  "addl $10, %%ecx\n\t" // add (10) to ecx (n)
                  "movl %%ecx, %0" /* ret = n + 10 */
                  : "=r" (ret) : "r" (n) : "ecx");
    return ret;
}

現在我想在另一個函數中調用該函數,該函數在調用“ proc”函數之前將其值移至“ ecx”中

int main_proc(int n) {
    asm volatile ("movl     $55, %%ecx" ::: "ecx"); /// mov (55) to ecx
    int ret;
    asm volatile ("call     proc" : "=r" (ret) : "r" (n) : "ecx"); // ecx is modified in proc function and the value of ecx is not 55 anymore even with "ecx" clobber

    asm volatile ("addl     %%ecx, %0" : "=r" (ret));

    return ret;
}

在該函數中,將(55)移入“ ecx”寄存器,然后調用“ proc”函數(修改“ ecx”)。 在這種情況下,“ proc”功能必須先按下“ ecx”,然后將其彈出,但是這種情況不會發生! 這是(-O3)優化級別的組裝源

proc:
        movl %edi, %ecx
        addl $10, %ecx
        movl %ecx, %eax
        ret
main_proc:
        movl     $55, %ecx
        call     proc
        addl     %ecx, %eax
        ret

為什么GCC不會為“ ecx”寄存器使用(push)和(pop)? 我也用過“ ecx”垃圾!

您正在使用內聯匯編完全錯誤。 您的輸入/輸出約束需要完全描述每個asm語句的輸入/輸出。 要在asm語句之間獲取數據,必須將它們保存在它們之間的C變量中。

同樣,在內聯匯編中call通常是不安全的,特別是在System V ABI的x86-64代碼中,它踩到了gcc可能一直在保存東西的紅色區域。 沒有辦法宣布這一點。 您可以使用sub $128, %rsp先使用sub $128, %rsp跳過紅色區域,也可以像普通人一樣從純C進行調用,以便編譯器知道這一點。 (請記住, call推送一個寄信人地址。)您的內聯匯編甚至沒有意義; 您的proc需要一個arg,但是您沒有在調用方中做任何事情來傳遞一個。

proc由編譯器生成的代碼也可能破壞了其他所有調用阻塞的寄存器,因此您至少需要在這些寄存器上聲明Clobbers。 或在asm中手寫整個功能,這樣您就知道要放入什么東西了。

為什么GCC不會為“ ecx”寄存器使用(push)和(pop)? 我也用過“ ecx”垃圾!

ecx泄漏告訴GCC,此asm語句破壞了ECX以前在GCC中擁有的所有內容。 在兩個單獨的inline-asm語句中使用ECX Clobber不會聲明它們之間的任何類型的數據依賴關系。

這不等於聲明一個register-asm局部變量,例如
register int foo asm("ecx"); 用作第一個和最后一個asm語句的"+r" (foo)操作數。 (或更簡單地說,您可以使用"+c"約束使普通變量選擇ECX)。

從GCC的角度來看,您的來源僅表示約束+障礙物所說明的內容。

int main_proc(int n) {
    asm volatile ("movl     $55, %%ecx" ::: "ecx");
      // ^^ black box that destroys ECX and produces no outputs
    int ret;
    asm volatile ("call     proc" : "=r" (ret) : "r" (n) : "ecx");
      // ^^ black box that can take `n` in any register, and can produce `ret` in any reg.  And destroys ECX.

    asm volatile ("addl     %%ecx, %0" : "=r" (ret));
     // ^^ black box with no inputs that can produce a new value for `ret` in any register

    return ret;
}

我懷疑您希望最后一個asm語句為"+r"(ret)來讀取/寫入C變量ret而不是告訴GCC它僅是輸出。 因為您的asm會將其用作輸入,也將輸出用作add的目的地。

在第二個asm語句中添加諸如# %%0 = %0 %%1 = %1類的注釋以查看選擇了哪個寄存器"=r""r"約束可能會很有趣。 Godbolt編譯器瀏覽器上

# gcc9.2 -O3 
main_proc:
        movl     $55, %ecx
        call     proc         # %0 = %edi   %1 = %edi
        addl     %ecx, %eax    # "=r" happened to pick EAX,
                      # which happens to still hold the return value from  proc
        ret

在此函數內聯到其他內容之后,可能不會發生選擇EAX作為添加目的地的事故。 或GCC碰巧在asm語句之間放置了一些編譯器生成的指令。 asm volatile是編譯時重新排序的障礙,但不是rog腳的障礙。它肯定會完全停止優化)。

請記住,內聯匯編模板僅是文本替換; 要求編譯器將操作數填充到注釋中與模板字符串中的其他任何地方都沒有不同。 (默認情況下,Godbolt會刪除注釋行,因此有時將它們附加到其他指令或nop上很方便)。

如您所見,這是64位代碼(按照x86-64 SysV調用約定, n到達EDI中,就像您構建代碼的方式一樣),因此push %ecx將不可編碼。 push %rcx

當然,如果GCC確實希望使用"ecx"擋板保留過去的asm語句值,那么它將只使用mov %ecx, %edx或不在擋板列表中的任何其他調用密集的寄存器。

暫無
暫無

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

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