簡體   English   中英

GCC / x86內聯asm:你怎么告訴gcc內聯匯編部分會修改%esp?

[英]GCC/x86 inline asm: How do you tell gcc that inline assembly section will modify %esp?

在試圖讓一些舊代碼再次運行時( https://github.com/chaos4ever/chaos/blob/master/libraries/system/system_calls.h#L387,FWIW )我發現gcc一些語義似乎有在最近10到15年間,它以一種非常微妙但仍然危險的方式改變了......:P

該代碼用於與舊版本的gcc ,如2.95。 無論如何,這是代碼:

static inline return_type system_call_service_get(const char *protocol_name, service_parameter_type *service_parameter,
    tag_type *identification)
{
    return_type return_value;

    asm volatile("pushl %2\n"
                 "pushl %3\n"
                 "pushl %4\n"
                 "lcall %5, $0"
                 : "=a" (return_value),
                   "=g" (*service_parameter)
                 : "g" (identification),
                   "g" (service_parameter),
                   "g" (protocol_name),
                   "n" (SYSTEM_CALL_SERVICE_GET << 3));

    return return_value;
}

上面代碼的問題是gcc (在我的情況下為4.7)會將其編譯為以下asm代碼(AT&T語法):

# 392 "../system/system_calls.h" 1
pushl 68(%esp)  # This pointer (%esp + 0x68) is valid when the inline asm is entered.
pushl %eax
pushl 48(%esp)  # ...but this one is not (%esp + 0x48), since two dwords have now been pushed onto the stack, so %esp is not what the compiler expects it to be
lcall $456, $0

# Restoration of %esp at this point is done in the called method (i.e. lret $12)

問題:變量( identificationprotocol_name )位於調用上下文的堆棧中。 因此gcc (優化結果,不確定是否重要)將從那里獲取值並將其交給內聯asm部分。 但是由於我在堆棧上推送東西, gcc計算的偏移量將在第三次調用中偏離8( pushl 48(%esp) )。 :)

這花了我很長時間才弄清楚,起初並不是很明顯。

最簡單的方法當然是使用r輸入約束,以確保該值在寄存器中。 但還有另一種更好的方法嗎? 一個顯而易見的方法當然是重寫整個系統調用接口,而不是首先在堆棧上推送東西(而是使用寄存器,比如Linux),但這不是我今晚想做的重構......

有沒有辦法告訴gcc inline asm“堆棧是不穩定的”? 你們過去一直在處理這樣的事情?


同一天晚上更新 :我確實找到了相關的gcc ML線程( https://gcc.gnu.org/ml/gcc-help/2011-06/msg00206.html ),但它似乎沒有幫助。 似乎在clobber列表中指定%esp 應該使它從%ebp做偏移,但它不起作用,我懷疑-O2 -fomit-frame-pointer在這里有效。 我啟用了這兩個標志。

什么有用,有什么不可用:

  1. 我試着省略-fomit-frame-pointer 沒有任何影響。 我將%espespspclobbers列表中

  2. 我試着省略-fomit-frame-pointer-O3 這實際上產生了有效的代碼,因為它依賴於%ebp而不是%esp

     pushl 16(%ebp) pushl 12(%ebp) pushl 8(%ebp) lcall $456, $0 
  3. 我嘗試在命令行中指定-O3和not -fomit-frame-pointer 創建壞的,破壞的代碼(依賴於%esp在整個程序集塊中保持不變,即沒有堆棧幀)。

  4. 我嘗試跳過-fomit-frame-pointer並使用-O2 代碼破碎,沒有堆棧框架。

  5. 我嘗試使用-O1 代碼破碎,沒有堆棧框架。

  6. 我嘗試添加cc作為clobber。 沒有辦法,沒有任何區別。

  7. 我嘗試將輸入約束更改為ri ,給出下面的輸入和輸出代碼。 這當然有效,但比我希望的稍微不那么優雅。 然后, 完美是好的敵人所以也許我現在必須忍受這個。

輸入C代碼:

static inline return_type system_call_service_get(const char *protocol_name, service_parameter_type *service_parameter,
    tag_type *identification)
{
    return_type return_value;

    asm volatile("pushl %2\n"
                 "pushl %3\n"
                 "pushl %4\n"
                 "lcall %5, $0"
                 : "=a" (return_value),
                   "=g" (*service_parameter)
                 : "ri" (identification),
                   "ri" (service_parameter),
                   "ri" (protocol_name),
                   "n" (SYSTEM_CALL_SERVICE_GET << 3));

    return return_value;
}

輸出asm代碼。 可以看出,使用寄存器而不是應該始終是安全的(但由於編譯器必須移動東西,可能性能稍差):

#APP
# 392 "../system/system_calls.h" 1
pushl %esi
pushl %eax
pushl %ebx
lcall $456, $0

暫無
暫無

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

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