之前一直沒怎么在意這個問題,直到最近搞了個奇奇怪怪的項目,才發現這部分知識得補上來,記錄一下。
ARM有一個標準,叫《Procedure Call Standard for the Arm Architecture》,人話就是ARM架構過程調用標準,它的目標就是為不同編譯器生成的代碼提供了統一的調用規范,使得單獨編譯的C程序和匯編程序能夠相互調用。
文章里詳細說明了寄存器的分類:R0-R3用于參數傳遞(調用者保存寄存器),R4-R11用于保存局部變量(被調用者保存寄存器),解決了函數調用過程中寄存器值可能被意外覆蓋的問題。
同時比較關鍵也是今天主題,就是參數傳遞機制的統一,它規定了函數的前四個參數通過寄存器傳遞,超出部分使用棧傳遞,返回值通過R0返回。這種標準化避免了不同編譯器采用不同傳遞方式導致的兼容性問題。特別提到在64位架構中,寄存器數量增加到31個,可以支持8個參數通過寄存器傳遞。
那我們來看看具體示例,以M4為例。
首先定義函數,分別涉及到傳2個參數,傳5個參數,傳結構體,傳結構體指針,如下:
?然后來看匯編,
(1)AddTest1,僅傳遞2個參數,根據規則當然應該直接用寄存器即可,匯編代碼如下:
代碼首先將參數存放到寄存器R0和R1中, 然后跳到AddTest,該函數的匯編代碼如下:
也符合規范里使用R0作為返回值的要求。
(2)AddTest2,傳遞5個參數,按照規范前4個參數使用寄存器R0-R3,匯編代碼如下:
代碼首先將第5個參數壓到棧里,對應地址0x1ffE4290,然后就其余四個參數放到寄存器R0-R4里,最后跳到函數里執行,具體代碼如下:
首先是把R4壓棧,保護現場,然后使用R4作為中間寄存器來存放棧里的第5個參數,最后分別加R0-R4,最后以R0承接返回值,如下:
(3)我們再來看看直接傳結構體是什么情況,匯編如下:
?這里就需要注意了,首先我們在main里定義了結構體add_type addStruct = {1,2,3},本身就存在棧里,如下:
因此,第一步就是要從棧里把數據取出來,由于該結構體只有三個參數,所以還是用寄存器傳遞的方式,如下:
在函數里就直接使用寄存器進行相加即可,
?那如果是傳遞的結構體超過了4個參數,舉一反三即可。?
(4)如果是傳遞指針?,情況又不太一樣,我們來看匯編。
代碼直接就該結構體對應的棧地址作為參數通過R0傳遞過去,?在該函數內部取棧里取數據,如下:
好了,奇奇怪怪的知識又增加了。了解這個有什么用呢,或許大概MCU里有一套固件API,不想和調用者共用棧,以免泄露信息,知道傳參的邏輯,才能在固件API運行時有正確的參數值。