匯編如何傳復雜的參數?
匯編基礎篇 中很詳細的介紹了一段具有代表性很經典的匯編代碼,有循環,有判斷,有運算,有多級函數調用。但有一個問題沒有涉及,就是很復雜的參數如何處理?
在實際開發過程中函數參數往往是很復雜的參數,(比如結構體)匯編怎么傳遞呢?
先看一段C語言及匯編代碼,傳遞一個稍微復雜的參數來說明匯編傳參的過程
#include <stdio.h>
#include <math.h>
struct reg{//參數遠超寄存器數量
int Rn[100];
int pc;
};
int framePoint(reg cpu)
{
return cpu.Rn[0] * cpu.pc;
}
int main()
{
reg cpu;
cpu.Rn[0] = 1;
cpu.pc = 2;
return framePoint(cpu);
}
//編譯器: armv7-a gcc (9.2.1)
framePoint(reg):
sub sp, sp, #16 @申請棧空間
str fp, [sp, #-4]! @保護main函數棧幀,等同于push {fp}
add fp, sp, #0 @fp變成framePoint棧幀,同時也指向了棧頂
add ip, fp, #4 @定位到入棧口,讓4個參數依次入棧
stm ip, {r0, r1, r2, r3}@r0-r3入棧保存
ldr r3, [fp, #4] @取值cpu.pc = 2
ldr r2, [fp, #404] @取值cpu.Rn[0] = 1
mul r3, r2, r3 @cpu.Rn[0] * cpu.pc
mov r0, r3 @返回值由r0保存
add sp, fp, #0 @重置sp,和add fp, sp, #0配套出現
ldr fp, [sp], #4 @恢復main函數棧幀
add sp, sp, #16 @歸還棧空間,sp回落到main函數棧頂位置
bx lr @跳回main函數
main:
push {fp, lr} @入棧保存調用函數現場
add fp, sp, #4 @fp指向sp+4,即main棧幀的底部
sub sp, sp, #800 @分配800個線性地址,即main棧幀的頂部
mov r3, #1 @r3 = 1
str r3, [fp, #-408] @將1放置 fp-408處,即:cpu.Rn[0]處
mov r3, #2 @r3 = 2
str r3, [fp, #-8] @將2放置 fp-8處,即:cpu.pc
mov r0, sp @r0 = sp
sub r3, fp, #392 @r3 = fp - 392
mov r2, #388 @只拷貝388,剩下4個由寄存器傳參
mov r1, r3 @保存由r1保存r3,用于memcpy
bl memcpy @拷貝結構體部分內容,將r1的內容拷貝r2的數量到r0
sub r3, fp, #408 @定位到結構體剩余未拷貝處
ldm r3, {r0, r1, r2, r3} @將剩余結構體內容通過寄存器傳參
bl framePoint(reg) @執行framePoint
mov r3, r0 @返回值給r3
nop @用于程序指令的對齊
mov r0, r3 @再將返回值給r0
sub sp, fp, #4 @恢復SP值
pop {fp, lr} @出棧恢復調用函數現場
bx lr @跳回調用函數
兩個函數對應兩段匯編,干凈利落,去除中間各項干擾,只有一個結構體reg,以下詳細講解如何傳遞它,以及它在棧中的數據變化是怎樣的?
入參方式
結構體總共101個棧空間(一個棧空間單位四個字節),對應就是404個線性地址.
main上來就申請了 sub sp, sp, #800 @申請800個線性地址給main,即 200個棧空間
int main()
{
reg cpu;
cpu.Rn[0] = 1;
cpu.pc = 2;
return framePoint(cpu);
}
但main函數只有一個變量,只需101個棧空間,其他都算上也用不了200個.為什么要這么做呢?
而且注意下里面的數字 388, 408, 392 這些都是什么意思?
看完main匯編能得到一個結論是 200個棧空間中除了存放了main函數本身的變量外 ,還存放了要傳遞給framePoint函數的部分參數值,存放了多少個?答案是 388/4 = 97個. 注意變量沒有共用,而是拷貝了一部份出來.如何拷貝的?繼續看
memcpy匯編調用
mov r0, sp @r0 = sp
sub r3, fp, #392 @r3 = fp - 392
mov r2, #388 @只拷貝388,剩下4個由寄存器傳參
mov r1, r3 @保存由r1保存r3,用于memcpy
bl memcpy @拷貝結構體部分內容,將r1的內容拷貝r2的數量到r0
sub r3, fp, #408 @定位到結構體剩余未拷貝處
ldm r3, {r0, r1, r2, r3} @將剩余結構體內容通過寄存器傳參
看這段匯編拷貝,意思是從r1開始位置拷貝r2數量的數據到r0的位置,注意只拷貝了 388個,也就是 388/4 = 97個棧空間.剩余的4個通過寄存器傳的參數.ldm代表從fp-408的位置將內存地址的值連續的給r0 - r3寄存器,即位置(fp-396,fp-400,fp-404,fp-408)的值.
執行下來的結果就是
r3 = fp-408, r2 = fp-404 ,r1 = fp-400 ,r0 = fp-396 得到虛擬地址的值,這些值整好是memcpy沒有拷貝到變量剩余的值
逐句分析 framePoint
framePoint(reg):
sub sp, sp, #16 @申請棧空間
str fp, [sp, #-4]! @保護main函數棧幀,等同于push {fp}
add fp, sp, #0 @fp變成framePoint棧幀,同時也指向了棧頂
add ip, fp, #4 @定位到入棧口,讓4個參數依次入棧
stm ip, {r0, r1, r2, r3}@r0-r3入棧保存
ldr r3, [fp, #4] @取值cpu.pc = 2
ldr r2, [fp, #404] @取值cpu.Rn[0] = 1
mul r3, r2, r3 @cpu.Rn[0] * cpu.pc
mov r0, r3 @返回值由r0保存
add sp, fp, #0 @重置sp,和add fp, sp, #0配套出現
ldr fp, [sp], #4 @恢復main函數棧幀
add sp, sp, #16 @歸還棧空間,sp回落到main函數棧頂位置
bx lr @跳回main函數
framePoint申請了4個棧空間目的是用來存放四個寄存器值的,以上匯編代碼逐句分析.
第一句: sub sp, sp, #16 @申請棧空間,用來存放r0-r3四個參數
第二句: str fp, [sp, #-4]! @保護main的fp,等同于push {fp},為什么這里要把main函數的fp放到 [sp, #-4]! 位置,注意 !號,表示SP的位置要變動,因為這里必須要保證參數的連續性.
第三句: add fp, sp, #0 @指定framePoint的棧幀位置,同時指向了棧頂 SP
第四句: add ip, fp, #4 @很關鍵,用了ip寄存器,因為此時 fp sp 都已經確定了,但別忘了 r0 - r3 還沒有入棧呢.從哪個位置入棧呢, fp+4位置,因為 main函數的棧幀已經入棧了,在已經fp的位置.中間隔了四個空位,就是給 r0-r3留的.
第五句: stm ip, {r0, r1, r2, r3}@r0-r3入棧,填滿了剩下的四個空位.
第六句: ldr r3, [fp, #4] @取的就是cpu.pc = 2的值,因為上一句就是從這里依次入棧的,最后一個當然就是cpu.pc了.
第七句: ldr r2, [fp, #404] @取值cpu.Rn[0] = 1,其實這一句已經是跳到了main函數的棧幀取值了,所以看明白了沒有,并不是在傳統意義上理解的在framePoint的棧幀中取值.
第八句: mul r3, r2, r3 @cpu.Rn[0] * cpu.pc 做乘法運算
第九句: mov r0, r3 @返回值r0保存運算結構, 目的是return
第十句: add sp, fp, #0 @重置sp,其實這一句可以優化掉,因為此時sp = fp
第十一句: ldr fp, [sp], #4 @恢復fp,等同于pop {fp},因為函數運行完了,需要回到main函數了,所以要拿到main的棧幀
第十二句: add sp, sp, #16 @歸還棧空間,等于把四個入參抹掉了.
最后一句: bx lr @跳回main函數,如此 fp 和 lr 寄存器中保存的都是 main函數的信息,就可以安全著陸了.
總結
因為寄存器數量有限,所以只能通過這種方式來傳遞大的參數,想想也只能在main函數棧中保存大部分參數,同時又必須確保數據的連續性,好像也只能用這種辦法了,一部分通過寄存器傳,一部分通過拷貝的方式倒是挺有意思的.
寫在最后
- 如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:
- 點贊,轉發,有你們的 『點贊和評論』,才是我創造的動力。
- 關注小編,同時可以期待后續文章ing??,不定期分享原創知識。
- 想要獲取更多完整鴻蒙最新學習知識點,請移步前往小編:
https://gitee.com/MNxiaona/733GH/blob/master/jianshu