[英]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)
問題:變量( identification
和protocol_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
在這里有效。 我啟用了這兩個標志。
什么有用,有什么不可用:
我試着省略-fomit-frame-pointer
。 沒有任何影響。 我將%esp
, esp
和sp
在clobbers列表中 。
我試着省略-fomit-frame-pointer
和-O3
。 這實際上產生了有效的代碼,因為它依賴於%ebp
而不是%esp
。
pushl 16(%ebp) pushl 12(%ebp) pushl 8(%ebp) lcall $456, $0
我嘗試在命令行中指定-O3
和not -fomit-frame-pointer
。 創建壞的,破壞的代碼(依賴於%esp
在整個程序集塊中保持不變,即沒有堆棧幀)。
我嘗試跳過-fomit-frame-pointer
並使用-O2
。 代碼破碎,沒有堆棧框架。
我嘗試使用-O1
。 代碼破碎,沒有堆棧框架。
我嘗試添加cc
作為clobber。 沒有辦法,沒有任何區別。
我嘗試將輸入約束更改為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.