我觀察到 GCC 的 C 編譯器生成以下匯編代碼:
sub $0xffffffffffffff80,%rsp
這相當于
add $0x80,%rsp
即從堆疊中洗掉 128 個位元組。
為什么 GCC 生成第一個子變體而不是添加變體?add 變體對我來說似乎比利用存在下溢更自然。
這在相當大的代碼庫中只發生過一次。我沒有最小的 C 代碼示例來觸發這個。我正在使用 GCC 7.5.0
uj5u.com熱心網友回復:
嘗試組裝兩者,你就會明白為什么。
0: 48 83 ec 80 sub $0xffffffffffffff80,%rsp
4: 48 81 c4 80 00 00 00 add $0x80,%rsp
該sub版本是三個位元組短。
這是因為x86 上的add和sub立即指令有兩種形式。一個采用 8 位符號擴展立即數,另一個采用 32 位符號擴展立即數。見https://www.felixcloutier.com/x86/add;相關形式是(在 Intel 語法中)add r/m64, imm8和add r/m64, imm32. 32 位的顯然要大三個位元組。
該數字0x80不能表示為 8 位有符號立即數;由于設定了高位,它將符號擴展到0xffffffffffffff80而不是所需的0x0000000000000080. 所以add $0x80, %rsp就必須使用 32 位形式add r/m64, imm32。另一方面,0xffffffffffffff80如果我們減去而不是添加,這正是我們想要的,因此我們可以使用sub r/m64, imm8,用更小的代碼給出相同的效果。
我不會真的說這是“利用下溢”。我只是將其解釋為sub $-0x80, %rsp. 編譯器只是選擇發出0xffffffffffffff80而不是等效的-0x80;使用更易讀的版本并不費心。
請注意,0x80 實際上是與此技巧相關的唯一可能數字;它是唯一的 8 位數字,它是它自己的負模 2^8。任何較小的數字都可以使用add,而任何較大的數字無論如何都必須使用 32 位。事實上,0x80 是我們不能sub r/m, imm8從指令集中省略并始終使用add負立即數代替它的唯一原因。我想如果我們想做一個 64 位的添加,就會出現類似的技巧0x0000000080000000;sub會做,但是add根本不能用,因為沒有imm64版本;我們必須先將常量加載到另一個暫存器中。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/391739.html
下一篇:lc3的虛擬機
