理解APCS-- ARM過程呼叫標準
轉自: http://blog.csdn.net/keyboardota/article/details/6799054
(紅色標記是自己修正部分)
因為需要在QNX Momentics中混合C語言和ARM彙編進行開發,於是初步瞭解了一下APCS。和大家分享一下我自己的學習過程,然後通過一個形象一點的類比講述我自己的理解。
剛開始的時候我是不知道有APCS這回事,所以第一次從C語言中呼叫ARM彙編函式的時候返回就出錯了。也正因為出錯才去網上查詢資料,知道了有APCS這回事。
簡單來講,APCS是ARM彙編中呼叫過程中的一種約定,過程呼叫方和被呼叫方講好了各自該幹什麼,從而保證各自的程式碼能正常工作。一個形象的類比就是你和你一個同事輪流使用一個辦公檯,約好了剪刀用完了放回左上角的筆筒裡,這樣大家要用剪刀的時候就不用去找。也就是說,如果哪天你的同事被幹掉了,這個辦公檯就你一人用,你的剪刀愛放哪放哪,自己能找著就行(找不著就怨自己嘍)。回到實際的ARM彙編程式設計中,如果你的彙編程式碼不呼叫其他人的過程,你的過程也不想被別人呼叫,你是可以扔掉APCS的。只是,哪能保證不呼叫人家的過程呢,所以還是老老實實照著APCS做吧。
理解了APCS產生的原因後,我就開始學習APCS的具體規定。網上的資料很多,百度一下APCS出來一大堆,不過裡面一堆複雜的描述,看了半天也緩不過神來。最後找到一個尚觀的視訊,叫做《ARM指令與ARM彙編入門》,講解的非常清晰,終於理解了APCS,視訊連結如下:
http://video.sina.com.cn/v/b/49256568-1855531553.html
視訊剛開始有主講老師的自我介紹,不過聽不太清,好像是叫“倪老師”,或者是“李老師”。這個“倪老師”是個大牛,對ARM指令非常熟悉,對於APCS也有非常清晰的認識。視訊講解的時候, “倪老師”在Linux環境下使用vi如行雲流水般輸入著ARM彙編指令,真實令人歎為觀止。建議學習ARM彙編的同學們去看看那個視訊。
下面將我從視訊中學習到的內容結合我自己的理解講述一下,應該不算是侵權吧,真有侵權的地方也請“倪老師”指出,一定刪除相關內容。
理解APCS需要一些基本的彙編知識,包括什麼是暫存器,什麼是堆疊,ARM彙編的基本語法等。我自己是因為很多年前還沒畢業的時候使用X86彙編寫過一個五子棋程式,所以學習起來還算是可以跟得上。對這些知識不太瞭解的同學可以先跳過以下內容,等以後掌握一些ARM彙編基礎知識後再來看,或者只是初步看一看,留個印象就好。
在ARM體系的CPU裡有37個暫存器,其中可見的暫存器有15個通用暫存器(叫r0-r14),還有其它的狀態暫存器和一個程式計數器(叫r15 或者是pc)。
在彙編裡需要使用上面提到的通用暫存器儲存各種資料,如果暫存器不夠用,還會使用堆疊儲存資料。APCS就是規定各個過程如何使用這些暫存器和堆疊,從而保證過程在相互呼叫的時候可以正常工作。
為了形象理解,我們還是拿公用的辦公檯作比喻,不過這次討論的不是剪刀的位置,而是辦公檯的抽屜。
你可以把暫存器想象成辦公檯的抽屜,現在這個特殊的辦公檯有一溜抽屜,整整16個,對應我們上面提到的15個通用暫存器和一個PC暫存器。為了和ARM 32位的暫存器嚴格對應起來,我們想象每個抽屜都只能放4張A4紙(把一個位元組想象成一張A4紙)。
另外,辦公檯旁邊還有一個檔案架,每格可以放一張A4紙,這就是我們的“堆疊”了。辦公檯的示意圖如下:
為了對應CPU的使用情況,我們對這個辦公檯的使用做一些規定:
- 開啟抽屜拿檔案比開啟檔案架拿檔案要快(暫存器的訪問速度比堆疊快)
- 在檔案架上存放檔案只能由上往下依次存放,第14個抽屜(編號r13)裡記錄了目前存放到第幾個格子。往檔案架多放一張A4紙檔案,第14個抽屜裡的數字減一,從檔案架裡拿出來一張A4紙檔案,第14個抽屜裡的數字加一。(就是堆疊的使用要求)
- 第16個抽屜指定下一個使用辦公檯的是哪個員工。(就是PC暫存器的作用)
- 一個員工的名字要4張A4紙來寫,這名字也太長了吧!情節需要,情節需要而已。(ARM指令是32位的,佔4個位元組)
我們來看看一群人共用這個辦公檯會出現什麼問題。
第一個出現的問題就是下一個該由誰使用辦公檯的問題。
比如A員工使用了這張辦公檯,使用了一定的時間後在第16個抽屜(R15,PC)中放了個紙條,寫了員工B的名字。按照使用規定,員工B會叫來使用這張辦公檯。員工B使用完辦公檯以後,如果有需要,他當然可以指定由員工C來使用這個辦公檯。如果他沒有需要呢,就是員工B使用完辦公檯以後就不需要給其他員工了,這時辦公檯該交給誰使用呢?一種合理的做法是誰交給員工B的就交回給誰,在上面的情況中就是員工A了。不過員工B並不知道上一個使用者是誰,所以員工B使用了辦公檯以後就不知道該給誰了。隨便叫一個過來?計算機世界裡可沒有隨便的概念,說不定叫出個恐龍來。
解決這個問題的方法是做一個約定,上一個使用者在第15個抽屜(R14,lr)裡放上自己的名字,讓下一個使用者知道辦公檯應該交回給誰使用。
好,時光倒流,再來一遍:
A員工使用了這張辦公檯,使用了一定的時間後先在第15個抽屜(R14,lr)裡放上自己的名字,就是“A員工”,然後在第16個抽屜(R15,PC)中放個紙條,寫了員工B的名字。按照使用規定,員工B會叫來使用這張辦公檯。員工B使用完辦公檯以後,要交還給上一個使用者了,只需要將第15個抽屜(r14,lr)裡的名字放入第16個抽屜(r15, pc)就好了。“A員工”的名字被放入了第16個抽屜,按照規定,A員工被叫來使用辦公檯。搞定!
員工B對應的ARM彙編程式碼就是:
mov pc,lr @將lr暫存器裡的值拷貝到pc暫存器中。
第二個問題是第15個抽屜重複使用的問題。
對於上面情況,問題是否解決了呢,其實不是。
來看看,辦公檯由員工A交給了員工B,這時第15個抽屜(R14,lr)中是“A員工”的名字,如果員工B使用了以後需要交給員工C呢?按照約定,員工B需要在第15個抽屜(R14,lr)中先放上自己的名字“B員工”,然後再叫員工C過來。可是,抽屜裡不是放著“A員工”的名字嗎?
簡單的解決方法是先把“A員工”的名字放到旁邊抽屜裡,旁邊不是還有一溜抽屜嘛。
好,時光再次倒流,再來一遍:
A員工使用了這張辦公檯,使用了一定的時間後先在第15個抽屜(R14,lr)裡放上自己的名字,就是“A員工”,然後在第16個抽屜(R15,PC)中放個紙條,寫了員工B的名字。按照使用規定,員工B會叫來使用這張辦公檯。
員工B使用辦公檯的時候先將寫著“A員工”的紙放到第1個抽屜裡(r0),然後在第15個抽屜(R14,lr)裡放上自己的名字“B員工”,最後在第16個抽屜(R15,PC)裡放上員工C的名字,將辦公檯交給員工C使用。等員工C將辦公檯交回給員工B之後,員工B可以將第1個抽屜(r0)裡的紙條(寫著“A員工”)放回第15個抽屜(R14, lr)。等員工B將辦公檯交給員工A使用時,把第15個抽屜(R14, lr)的紙條放回第16個抽屜(R15,PC)裡就可以了。
員工B對應的ARM彙編程式碼如下:
mov r0,lr
mov lr,”返回地址”
mov pc,”子函式方法地址”
mov lr, r0
mov pc , lr
第三個問題是抽屜的使用權問題。
聰明的同學們很快會發現上面的問題,員工B在第1個抽屜保留了寫著“A員工”的紙條,然後將辦公檯交給了員工C使用,員工C可不知道第1個抽屜裝著什麼,說不定一不小心就第1個抽屜的東西到了。
這裡就有一個抽屜使用權的問題,讓我們再做一些約定:第1個抽屜到第4個抽屜(r0~r3)大家可以隨便使用,不管裡面有什麼東西,要用的時候將抽屜清空就好了;而第5個抽屜到第12個抽屜(r4~r11)裡的東西不能亂動,交到你手裡什麼樣子,交回給上一個使用者時就應該是什麼樣子。
本來覺得有了這些約定就安逸了,第1個到第4個抽屜不能放重要的東西,會被別人扔掉的,那就將重要的東西放第5個到第12個抽屜吧,別人會確保我的抽屜是原樣的。仔細一想就有問題了,我要是使用第5個到第12個抽屜,我將辦公檯交回給上一個使用者的時候這些抽屜就不是原來的樣子了,交不了差。
如果這時候再來時光倒流。。。。
A員工一上來就傻眼了,辦公檯上寫著:第1個抽屜到第4個抽屜會被人倒掉,第5個抽屜到第12個抽屜是人家的東西,不能動。什麼破公司,A員工罵一句就要交辭職報告了。
不要衝動,不要衝動,旁邊不是還有個檔案架嘛!既然第5個抽屜到第12個抽屜是人家的東西,你使用前先將抽屜裡的東西搬到檔案架上,等你用完了抽屜,將東西從檔案架上搬回來就好了嘛。
留住了A員工,再來次時光倒流:
A員工開始使用這張辦公檯,他很規矩地將第5個抽屜到第12個抽屜裡的東西搬到了檔案架上,而且按規定在第14個抽屜(r13,SP)裡記錄了當前的檔案架使用位置,在第15個抽屜(R14,lr)裡放上自己的名字,就是“A員工”,然後在第16個抽屜(R15,PC)中放個紙條,寫了員工B的名字。按照使用規定,員工B會叫來使用這張辦公檯。
員工B使用辦公檯的時候也是很規矩地將第5個抽屜到第12個抽屜裡的東西搬到了檔案架上,而且按規定在第14個抽屜(r13,SP)裡記錄了當前的檔案架使用位置,接著將寫著“A員工”的紙放到第5個抽屜裡(r4),然後在第15個抽屜(R14,lr)裡放上自己的名字“B員工”,最後在第16個抽屜(R15,PC)裡放上員工C的名字,將辦公檯交給員工C使用。
等員工C將辦公檯交回給員工B之後,員工B可以將第5個抽屜(r4)裡的紙條(寫著“A員工”)放到第1個抽屜(r0),然後將A員工的東西從檔案架上搬回到第5個抽屜到第12個抽屜裡,最後是將第1個抽屜(r0)裡的紙條(寫著“A員工”)放回第16個抽屜(R15,PC)裡就可以了。
好暈呀,真難為這些員工了,使用這種辦公檯的員工傷不起呀!操作暫存器的人傷不起呀!
員工B對應的ARM彙編程式碼如下:
sub sp,sp, #12 @開闢三個暫存器的空間,存放r4,r5,r6,樣例而已,r7之後的就不存了
str r4, [sp] @r4入棧
str r5, [sp, #4] @r5入棧
str r6, [sp, #8] @r6入棧
@ 後面應該還有r7,r8…等等地操作,這裡省略
mov r4,lr @ 將返回地址保留在r5中,因為r5不會被子函式破壞。
@呼叫子函式
Mov r0,r4 @將r4中的返回地址放到r0中,因為要開始恢復r4,r5,r6了
ldr r6, [sp, #8] @r4出棧
ldr r5, [sp, #4] @r5出棧
ldr r4, [sp] @r6出棧
mov pc , r0 @將r0中的返回地址給pc,退出
第四個問題是檔案架使用位置的問題。
上面的使用方法還是有問題呢! 員工A從員工B那裡接手辦公檯就怒了:“我在第14個抽屜記錄的檔案架使用位置怎麼不對呀”,原來員工B使用了第14個抽屜,忘了將第14個抽屜的恢復原樣了。
於是又多了一條約定,第14個抽屜(R13,SP)裡的東西也要保持原樣。
這就有點麻煩了,第14個抽屜(R13,SP)記錄的是檔案架的使用位置,一旦使用檔案架就得使用第14個抽屜(R13,SP),現在又要保留第14個抽屜的東西,這不是難為人嗎?
想來想去,員工們想了個辦法,使用的時候將第14個抽屜(R13,SP)的東西也搬到檔案架上,不過有個問題,是先搬東西還是先做記錄呢,怎麼做都有問題。再看看抽屜,好像只剩第13個抽屜可以用了:
第1個抽屜到第4個抽屜(r0~r3):雖然是可以隨便用,不過說不定上個人留了什麼東西我自己需要的呢,先不動
第5個抽屜到第12個抽屜(r4~r11):是別人的東西,一會要搬到檔案架上的
第14個抽屜:記錄的就是檔案架的使用位置
第15個抽屜:上一個使用者的名字
第16個抽屜:不能亂動的,叫別人過來的時候才用
好,就先將第14個抽屜的東西放到第13個抽屜上,然後在第14個抽屜記錄新開了個檔案架的格子,再將第13個抽屜的東西搬到新開的檔案架格子上。為了方便,第15個抽屜的東西也搬到檔案架上,剩下的就是依次將第5個抽屜到第12個抽屜的東西搬到檔案架上。當使用完了再按順序將檔案架的東西搬回來就好了。
時光倒流就不來了,再來要吐了,對應ARM彙編程式碼如下:
mov ip,sp
sub sp,sp, #12
str lr, [sp]
str ip, [sp, #4]
str fp, [sp, #8]
sub fp, ip, #4
@子函式程式碼
ldr lr, [fp, #-8]
ldr ip, [fp, #-4]
ldr fp, [fp,#0]
mov sp, ip
mov pc , lr
對著一堆的抽屜講了一通,大家可能看地時候好像整明白了,回過頭看彙編程式碼又忘了。上面的例子只是希望通過形象的方式讓大家理解,最終能在腦海裡方便記憶的還是彙編程式碼和對應的註釋。
@----------------------------------------------------
@程式剛開始,
@r0到r3(a1到a4)可能會有引數,不能動
@r4到r11(v1到v8)是父函式的東西,需要保留,不能動
@r13(SP)是堆疊指標
@r14(lr)是返回地址
@r15(PC)更是不能隨便動能
mov ip,sp @將Sp(堆疊指標,r13)放在ip中,ip就是r12,目前唯一可以使用的暫存器
sub sp,sp, #12 @ 堆疊指標減12,開出相當於3個暫存器空間的堆疊。
str lr, [sp] @r14入棧,裡面是返回地址
str ip, [sp, #4] @ r12入棧,裡面是父函式的堆疊指標
str fp, [sp, #8] @ r11入棧,裡面是需要為父函式保留的東西,一會要用r11,所以將它入棧
sub fp, ip, #4 @ r11(就是fp)等於ip-4,就是自己的棧底(被呼叫方堆疊棧底),以後可以方便地使用堆疊 (這裡比較難理解一下,請看下圖), 可見,fp儲存lr值, fp-4 相當於是bp, 儲存ip地址,指向old bp
@新增自己的程式碼
@下面是準備退出了,注意堆疊使用時是基於fp定址的,
@因為此時SP可能在自己的程式碼中調整過,不可靠。
ldr lr, [fp, #-8] @ lr出棧,裡面是返回地址 (這行改為:ldr fp, [fp,#-8] @fp出棧,裡面是需要為父函式保留的東西)
ldr ip, [fp, #-4] @ ip出棧,裡面是父函式的堆疊指標
ldr fp, [fp,#0] @fp出棧,裡面是需要為父函式保留的東西 (這行改為: ldr lr, [fp, #0] @ lr出棧,裡面是返回地址)
mov sp, ip @令sp等於ip,幫父函式恢復堆疊指標
mov pc , lr @令pc等於lr,就是等於返回地址,退出子函式,返回到父函式。
@------------------------------------
最後很開心地告訴大家,以上的分析和程式碼都只是為了讓大家理解APCS,其實大家在使用過程中只要按模式在過程(或者說是函式)開始和結束的時候加上標準的APCS程式碼就好了。下面是簡化過的APCS標準程式碼,有興趣的同學可以研究一下,沒興趣研究的話也沒有問題,寫一個過程的時候直接拷貝上去就好了。
mov ip,sp
stmfd sp!, {fp,ip,lr,pc}
sub fp, ip, #4
@......子函式程式碼
sub sp, fp, #12
ldmfd sp,{fp, sp, pc}
能看完這篇文章難為你了,去休息一下吧,ARM彙編的路還很長!!!