C語言在ARM中函數(shù)調(diào)用時,棧是如何變化的?
今天和大家一起看下面對 crash 日志的時候,如何利用 stack 來分析其變化的來龍去脈。
Arm指令集介紹崇尚簡單粗暴的介紹方式,我們直接來看各個寄存器的大體用法,詳細(xì)用法可百度,不,谷歌。
1. ?? r0-r3 用作傳入函數(shù)參數(shù),傳出函數(shù)返回值。在子程序調(diào)用之間,可以將 r0-r3 用于任何用途。被調(diào)用函數(shù)在返回之前不必恢復(fù) r0-r3。---如果調(diào)用函數(shù)需要再次使用 r0-r3 的內(nèi)容,則它必須保留這些內(nèi)容。2. ?? r4-r11 被用來存放函數(shù)的局部變量。如果被調(diào)用函數(shù)使用了這些寄存器,它在返回之前必須恢復(fù)這些寄存器的值。r11 是棧幀指針?fp。3.????r12 是內(nèi)部調(diào)用暫時寄存器?ip。它在過程鏈接膠合代碼(例如,交互操作膠合代碼)中用于此角色。在過程調(diào)用之間,可以將它用于任何用途。被調(diào)用函數(shù)在返回之前不必恢復(fù) r12。4.????寄存器 r13 是棧指針?sp。它不能用于任何其它用途。sp 中存放的值在退出被調(diào)用函數(shù)時必須與進(jìn)入時的值相同。5.????寄存器 r14 是鏈接寄存器?lr。如果您保存了返回地址,則可以在調(diào)用之間將 r14 用于其它用途,程序返回時要恢復(fù)6.????寄存器 r15 是程序計數(shù)器?pc。它不能用于任何其它用途。
演示代碼假如現(xiàn)在你已經(jīng)掌握了 arm 指令的用法,即便沒有掌握也沒關(guān)系,“書到用時回頭翻”。這里以一段簡單的 c 語言為例:
#include
int m = 8;
int fun(int a,int b)
{
int c = 0;
c = a b;
return c;
}
int main()
{
int i = 4;
int j = 5;
m = fun(i, j);
return 0;
}
編譯一下,然后反匯編:$ arm-linux-gnueabi-gcc main.c -o main?$ arm-linux-gnueabi-objdump -D -D main
00010400 :
10400: e52db004 push {fp} ; (str fp, [sp, #-4]!)
10404: e28db000 add fp, sp, #0
10408: e24dd014 sub sp, sp, #20
1040c: e50b0010 str r0, [fp, #-16]
10410: e50b1014 str r1, [fp, #-20] ; 0xffffffec
10414: e3a03000 mov r3, #0
10418: e50b3008 str r3, [fp, #-8]
1041c: e51b2010 ldr r2, [fp, #-16]
10420: e51b3014 ldr r3, [fp, #-20] ; 0xffffffec
10424: e0823003 add r3, r2, r3
10428: e50b3008 str r3, [fp, #-8]
1042c: e51b3008 ldr r3, [fp, #-8]
10430: e1a00003 mov r0, r3
10434: e24bd000 sub sp, fp, #0
10438: e49db004 pop {fp} ; (ldr fp, [sp], #4)
1043c: e12fff1e bx lr
00010440 :
10440: e92d4800 push {fp, lr}
10444: e28db004 add fp, sp, #4
10448: e24dd008 sub sp, sp, #8
1044c: e3a03004 mov r3, #4
10450: e50b300c str r3, [fp, #-12]
10454: e3a03005 mov r3, #5
10458: e50b3008 str r3, [fp, #-8]
1045c: e51b1008 ldr r1, [fp, #-8]
10460: e51b000c ldr r0, [fp, #-12]
10464: ebffffe5 bl 10400
10468: e1a02000 mov r2, r0
1046c: e59f3010 ldr r3, [pc, #16] ; 10484
10470: e5832000 str r2, [r3]
10474: e3a03000 mov r3, #0
10478: e1a00003 mov r0, r3
1047c: e24bd004 sub sp, fp, #4
10480: e8bd8800 pop {fp, pc}
10484: 00021024 andeq r1, r2, r4, lsr #32
圖解棧的變化過程如何能讓讀者接受吸收的更快,我一直覺得按照學(xué)習(xí)效率來講的話順序應(yīng)該是視頻,圖文,文字。反正我是比較喜歡視頻類的教學(xué)。這里給大家畫下棧變化的過程是什么樣子的。這里的圖是結(jié)合上面的代碼來畫的,希望有助于讀者的理解。
1.程序在內(nèi)存分布區(qū)域
2.全局變量m賦值
3.保存進(jìn)入main之前的棧底, fp-sp之間是當(dāng)前函數(shù)棧
4.函數(shù)main的棧已經(jīng)準(zhǔn)備好了
5.i入棧
6.j入棧
7.準(zhǔn)備函數(shù)fun的調(diào)用, 形參反向入棧 先形參b入棧
8.形參a入棧
9.留空一個地址作為fun返回值, 待后面返回時填入
10.fun返回地址入棧, 通常是main函數(shù)當(dāng)前pc指針的下一個
11.main函數(shù)的棧底地址入棧
12.pc指針跳轉(zhuǎn)fun代碼
13.c入棧
14.可以看到函數(shù)fun的數(shù)據(jù) 形參a,b 在上一層函數(shù)的棧中. 一部分在自己的棧上. 此步取值到加法器中進(jìn)行加法運(yùn)算,再賦值給c
15.c賦給返回值,填入上面的留空位置
16.棧底恢復(fù)上一層
17.lr賦值給pc, 實現(xiàn)了跳轉(zhuǎn)
18.返回值賦值給全局變量m
19.前面函數(shù)調(diào)用的形參已經(jīng)無用,回滾sp
20.函數(shù)返回,清理main的??臻g
總結(jié)這么多圖有沒有看花?相信到這里你已經(jīng)了解了棧背后的來龍去脈,下一篇我們一起根據(jù)實際的 stack 錯誤案例剖析錯誤的可能性。
往期推薦:物聯(lián)網(wǎng)通信協(xié)議大匯總!
極客感十足的電子胸牌 ART-Badge V2.0
點擊閱讀原文,查看更多分享