ARMv8架構下程序運行時棧幀佈局

結合ARM相關文檔和在飛騰機器上使用gdb調試實際程序來研究ARM的指令和運行時棧幀佈局。主要參考了三篇文檔。

1. Procedure Call Standard for the ARM 64-bit Architecture。參考其中的過程調用標準和運行時棧幀佈局。

2. ARMv8 Instruction Set Overview。參考其中的指令概述。

3. ARM Compiler Migration and Compatibility Guide。參考其中ARM彙編與GNU彙編格式的比較。

在文章1中,對ARM架構下運行時棧幀佈局如圖1所示。

圖1 ARM運行時棧幀佈局

其中,FP(x29)寄存器保存棧幀地址,LR(x30)保存當前過程的返回地址。棧是從高地址向低地址生長。爲驗證圖中的佈局形式,在飛騰機器上安裝gdb,通過調試一個示例程序,來研究ARM的指令特點和棧幀結構。示例程序如圖2所示,函數TestParam定義了兩個局部變量,分別爲數組和標量類型。


圖2 示例程序

使用gdb調試圖2中代碼所產生的程序,然後再反彙編函數TestParam,可以得到如下結果。

=> 0x0000000000400680 <+0>:stpx29, x30, [sp,#-64]!

   0x0000000000400684 <+4>:movx29, sp

   0x0000000000400688 <+8>:strw0, [x29,#28]

   0x000000000040068c <+12>:strw1, [x29,#24]

   0x0000000000400690 <+16>:strw2, [x29,#20]

   0x0000000000400694 <+20>:strw3, [x29,#16]

   0x0000000000400698 <+24>:adrpx0, 0x411000 <[email protected]>

   0x000000000040069c <+28>:addx0, x0, #0x38

   0x00000000004006a0 <+32>:ldrx1, [x0]

   0x00000000004006a4 <+36>:strx1, [x29,#56]

   0x00000000004006a8 <+40>:movx1, #0x0                   // #0

   0x00000000004006ac <+44>:ldrw1, [x29,#28]

   0x00000000004006b0 <+48>:ldrw0, [x29,#24]

   0x00000000004006b4 <+52>:addw0, w1, w0

   0x00000000004006b8 <+56>:strw0, [x29,#48]

   0x00000000004006bc <+60>:ldrw1, [x29,#20]

   0x00000000004006c0 <+64>:ldrw0, [x29,#16]

   0x00000000004006c4 <+68>:subw0, w1, w0

   0x00000000004006c8 <+72>:strw0, [x29,#52]

   0x00000000004006cc <+76>:ldrw1, [x29,#20]

   0x00000000004006d0 <+80>:ldrw0, [x29,#16]

   0x00000000004006d4 <+84>:addw0, w1, w0

   0x00000000004006d8 <+88>:strw0, [x29,#44]

   0x00000000004006dc <+92>:ldrw1, [x29,#48]

   0x00000000004006e0 <+96>:ldrw2, [x29,#52]

   0x00000000004006e4 <+100>:adrpx0, 0x400000

   0x00000000004006e8 <+104>:addx0, x0, #0x808

   0x00000000004006ec <+108>:ldrw3, [x29,#44]

   0x00000000004006f0 <+112>:bl0x400530 <printf@plt>

   0x00000000004006f4 <+116>:adrpx0, 0x411000 <[email protected]>

   0x00000000004006f8 <+120>:addx0, x0, #0x38

   0x00000000004006fc <+124>:ldrx1, [x29,#56]

   0x0000000000400700 <+128>:ldrx0, [x0]

   0x0000000000400704 <+132>:eorx0, x1, x0

   0x0000000000400708 <+136>:cmpx0, xzr

   0x000000000040070c <+140>:b.eq0x400714 <TestParam+148>

   0x0000000000400710 <+144>:bl0x400500 <__stack_chk_fail@plt>

   0x0000000000400714 <+148>:ldpx29, x30, [sp],#64

   0x0000000000400718 <+152>:ret

該程序在運行時的棧幀如圖3所示。


以下是反彙編指令的解釋以及其對棧中內容的影響。

=> 0x0000000000400680 <+0>:stpx29, x30, [sp,#-64]!

   0x0000000000400684 <+4>:movx29, sp

指令stp把一對值x29x30放到SP-64的地址(7ffffff370)中去。此時的SP是舊SP,其值爲7ffffff3b0。值得注意的是,這條語句同時完成了SP的自減運算,也就是執行之後,SP的值也變成了7ffffff370。第二條指令把FP的值設置爲與SP的值相同。

   0x0000000000400688 <+8>:strw0, [x29,#28]

   0x000000000040068c <+12>:strw1, [x29,#24]

   0x0000000000400690 <+16>:strw2, [x29,#20]

   0x0000000000400694 <+20>:strw3, [x29,#16]

4條指令把保存在參數傳遞寄存器中的4個參數保存到棧中。如圖2中的w0, w1, w2, w3所示。

   0x0000000000400698 <+24>:adrpx0, 0x411000 <[email protected]>

   0x000000000040069c <+28>:addx0, x0, #0x38

   0x00000000004006a0 <+32>:ldrx1, [x0]

   0x00000000004006a4 <+36>:strx1, [x29,#56]

   0x00000000004006a8 <+40>:movx1, #0x0                   // #0

5條指令是用於安全保障的。因爲函數TestParam中聲明瞭一個數組,因此有受到緩衝區溢出攻擊的危險。在其他平臺下或者之前版本中,需要在編譯時顯式使用-fstack-protector選項,纔會增加這樣的安全保障指令。而在飛騰ARM配置的編譯器中,默認就增加了。

其主要思路是在編譯時生成一個隨機化的值,如圖中的_stack_guard保存在bss段中。在開始執行函數體時,把它從bss段中取出,放在棧的底部。然後執行函數。若有針對數組e的緩衝區溢出攻擊,則_stack_guard就會被改寫。在函數執行結束時,再把棧底部的值和bss段中的原始值相比較,若兩者不同,就說明有攻擊行爲發生。

5條指令的功能就是從bss段中把_stack_guard的值放到棧的底部。需要注意的是,在查找時使用了相對尋址指令adrp

   0x00000000004006ac <+44>:ldrw1, [x29,#28]

   0x00000000004006b0 <+48>:ldrw0, [x29,#24]

   0x00000000004006b4 <+52>:addw0, w1, w0

   0x00000000004006b8 <+56>:strw0, [x29,#48]

   0x00000000004006bc <+60>:ldrw1, [x29,#20]

   0x00000000004006c0 <+64>:ldrw0, [x29,#16]

   0x00000000004006c4 <+68>:subw0, w1, w0

   0x00000000004006c8 <+72>:strw0, [x29,#52]

8條指令是使用形式參數進行運算,並把結果保存在數組中。數組中只有兩個元素,被放置在靠近棧底的位置。

   0x00000000004006cc <+76>:ldrw1, [x29,#20]

   0x00000000004006d0 <+80>:ldrw0, [x29,#16]

   0x00000000004006d4 <+84>:addw0, w1, w0

   0x00000000004006d8 <+88>:strw0, [x29,#44]

4條指令的作用是計算出f的值,並把它保存到棧中。

   0x00000000004006dc <+92>:ldrw1, [x29,#48]

   0x00000000004006e0 <+96>:ldrw2, [x29,#52]

   0x00000000004006e4 <+100>:adrpx0, 0x400000

   0x00000000004006e8 <+104>:addx0, x0, #0x808

   0x00000000004006ec <+108>:ldrw3, [x29,#44]

   0x00000000004006f0 <+112>:bl0x400530 <printf@plt>

6條指令是負責準備參數寄存器x0w1、 w2w3的值,並調用printfx0中存放的是指向格式字符串的指針。

   0x00000000004006f4 <+116>:adrpx0, 0x411000 <[email protected]>

   0x00000000004006f8 <+120>:addx0, x0, #0x38

   0x00000000004006fc <+124>:ldrx1, [x29,#56]

   0x0000000000400700 <+128>:ldrx0, [x0]

   0x0000000000400704 <+132>:eorx0, x1, x0

   0x0000000000400708 <+136>:cmpx0, xzr

   0x000000000040070c <+140>:b.eq0x400714 <TestParam+148>

   0x0000000000400710 <+144>:bl0x400500 <__stack_chk_fail@plt>

8條指令是比較_stack_guard的值與存放在bss段中的值是否相等,若相等,在跳到400714,繼續執行TestParam函數,否則跳到_stack_chk_fail函數,處理緩衝區溢出發生的情況。

   0x0000000000400714 <+148>:ldpx29, x30, [sp],#64

   0x0000000000400718 <+152>:ret

2條指令從棧中恢復x29x30的值,並返回。需要注意的是ldp指令執行之後,sp的值自動增加了64


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章