1. 程式人生 > >匯編調用c函數為什麽要設置棧

匯編調用c函數為什麽要設置棧

代碼 初始 子程序 c語言 暫時 包含 準備 通過 並且

之前看了很多關於uboot分析類的文章,其中提到為C語言的運行準備棧。而在uboot start.S匯編代碼中,關於系統初始化,也看到棧指針初始化,即正確給棧指針sp賦值,卻從來沒看到有人解釋,為何要這樣做。接下來,我試圖解釋這個問題。首先了解棧的作用。關於這個,詳細講解要很長的篇幅,故此處只做簡略介紹。 總的來說,它的作用就是:保存現場/上下文,傳遞參數,保存臨時變量 1.保存現場/上下文

現場/上下文相當於案發現場,總有一些案發現場,要記錄下來,否則被別人破壞,便無法恢復。而此處說的現場,是指CPU運行時,用到的一些寄存器,比如r0,r1等,對於這些寄存器的值,如果不保存而直接跳轉到子函數中執行,其很可能被破壞,因為其函數執行也要用到這些寄存器。因此,在函數調用之前,應該將這些寄存器等現場暫時保存
(入棧push),等調用函數執行完畢後出棧(pop)再恢復現場。這樣CPU就可以正確的繼續執行了。

保存寄存器的值,一般用push指令,將對應的某些寄存器的值,一個個放到棧中,即所謂的壓棧。然後待被調用的子函數執行完畢後再調用pop,把棧中的一個個的值,賦值給對應的那些你剛開始壓棧時用到的寄存器,把對應的值從棧中彈出去,即所謂的出棧。

其中保存的寄存器中,也包括lr的值(因為用bl指令進行跳轉的話,之前的pc值存在lr中),在子程序執行完畢後,再把棧中的lrpop出來,賦值給pc,這樣就實現了子函數的正確的返回。

2. 傳遞參數

C語言函數調用時,會傳給被調用函數一些參數,對於這些
C語言級別參數,被編譯器翻譯成匯編語言時,要找個地方存放下來,並且讓被調用函數能訪問,否則沒法傳遞。找個地方存放下來分2種情況。一是,本身傳遞的參數不多於4個,可以通過寄存器傳送。因為在前面的保存現場動作中,已經保存好對應的寄存器的值,此時這些寄存器是空閑的,可以供我們使用存放參數。二是,參數多於4個,寄存器不夠用,就得用棧。

3.臨時變量保存在棧中

這些臨時變量包括函數的非靜態局部變量以及編譯器自動生成的其他臨時變量。



舉例分析C語言函數調用如何使用棧

上面的解釋有些抽象,此處再用例子簡單說明一下,就容易明白了: arm-inux-objdump –d u-boot dump_u-boot.txt得到
dump_u-boot.txt文件。該文件是包含了u-boot可執行匯編代碼,從中我們可以看到相應C程序對應的匯編代碼。

下面貼出兩個函數的匯編代碼,一個是clock_init,另一個是與clock_init在同一C源文件中的函數CopyCode2Ram 33d0091c CopyCode2Ram: 33d0091c: e92d4070 push {r4, r5, r6, lr} 33d00920: e1a06000 mov r6, r0 33d00924: e1a05001 mov r5, r1 33d00928: e1a04002 mov r4, r2 33d0092c: ebffffef bl 33d008f0 b BootFrmNORFlash ...... 33d00984: ebffff14 bl 33d005dc nand_read_ll ...... 33d009a8: e3a00000 mov r0, #0 ; 0x0 33d009ac: e8bd8070 pop {r4, r5, r6, pc} 33d009b0clock_init: 33d009b0: e3a02313 mov r2, #1275068416 ;0x4c000000 33d009b4: e3a03005 mov r3, #5 ; 0x5 33d009b8: e5823014 str r3, ...... 33d009f8: e1a0f00e mov pc, lr

(1) 先分析clock_init對應的匯編代碼,可以看到該函數第一行 33d009b0: e3a02313 mov r2, #1275068416 ;0x4c000000 沒有我們期望的push指令,即沒有將一些寄存器的值放入棧。這是因為,clock_init用到的r2,r3等寄存器,和前面調用clock_init前用到的寄存器r0,沒有沖突,故此處不用push保存,有個寄存器要註意,r14,即lr,前面調用clock_init時,用的bl指令,所以會自動把跳轉時的pc值賦值給lr,所以也不需要pushPC值保存到棧。而clock_init對應的匯編代碼最後一行: 33d009f8: e1a0f00e mov pc, lr 就是我們常見的mov pc,lr,把lr值,即之前保存的函數調用時的PC值,賦值給現在的PC,這樣便實現了函數的正確返回,即返回到了函數調用時下一個指令的位置。CPU可以繼續執行原先函數內剩下的代碼。



(2) CopyCode2Ram對應匯編代碼第一行:33d0091c: e92d4070 push {r4, r5, r6, lr} 就是我們所期望的,用push保存r4,r5,r6lr,是因為此函數還包括其他函數調用 33d0092c: ebffffef bl 33d008f0 b BootFrmNORFlash...... 33d00984: ebffff14 bl 33d005dc nand_read_ll ...... 也用到bl指令,會改變我們最開始進入clock_init時的lr值,所以也要push暫時保存起來。

而對應地,CopyCode2Ram最後一行:33d009ac: e8bd8070 pop {r4, r5, r6,pc}是把之前push的值給pop出來,還給對應的寄存器,其中最後一個是將開始pushlr的值pop出來賦給PC,實現了函數的返回。另外我們註意到,CopyCode2Ram的倒數第二行:33d009a8: e3a00000 movr0, #0 ; 0x0 是把0賦值給r0寄存器,就是我們說的返回值的傳遞,此處的返回值為0,也對應著C代碼中的“ return 0”

當然也可以用其他暫時空閑沒有用到的寄存器來傳遞返回值。

對於使用哪個寄存器來傳遞返回值,是根據ARMAPCS寄存器的使用約定而設計的, 最好按照其約定的來處理,不要隨便改變它。這樣程序將更加規範。

匯編調用c函數為什麽要設置棧