?
1、介紹
- FP:棧頂指針,指向一個棧幀的頂部,當函數(shù)發(fā)生跳轉(zhuǎn)時,會記錄當時的棧的起始位置。
- SP:棧指針(也稱為棧底指針),指向棧當前的位置,
- LR:鏈接寄存器,保存函數(shù)返回的地址。
關(guān)于gcc就有一個關(guān)于stack frame的優(yōu)化選項,加上該選項則忽略掉FP棧頂指針,(記得高版本默認是不加FP的,gcc4.8以上吧(待確認))
- -fomit-frame-pointer
Don’t keep the frame pointer in a register for functions that don’t need one. This avoids the instructions to save, set up and restore frame pointers; it also makes an extra register available in many functions. It also makes debugging impossible on some machines.
(大概意思 )不需要棧幀的時候不要加這個編譯選項,這可以節(jié)省很多指令去保存,傳遞和恢復,同時也省出一個寄存器可以在函數(shù)中做更多事情,也使得在某些機制下更容易去debug
arm cc5編譯也有關(guān)于FP生成的編譯選項,默認是不加的。
- –use_frame_pointer, --no_use_frame_pointer
Sets the frame pointer to the current stack frame.Using the --use_frame_pointer option reserves a register to store the 「frame pointer」.For newer processors that support Thumb-2 technology (ARMv6T2 and later), the reserved register isalways R11.(arm v7)如果是arm v8 -a 系列,則是X29來表示。For older processors that do not support Thumb-2 technology, the reserved register is R11 in ARM codeand R7 in Thumb code.Default「The default is --no_use_frame_pointer」. That is, register R11 (or register R7 for Thumb code on olderprocessors) is available for use as a general-purpose registe
2、作用
2.1 FP的作用
關(guān)于APCS(ARM Procedure Call Standard,ARM 程序調(diào)用標準)的說法 ,
- 除非子程序沒有修改鏈接寄存器,否則FP都需要記錄有效的棧幀位置
- 其寄存器(r11或者x29)不能被用做一個通用型的寄存器
FP的主要作用就是用來「棧回溯」,找到子程序的調(diào)用關(guān)系,也成為backtrace,當然一級一級的子程序調(diào)用時,F(xiàn)P的記錄也在變化,也會一級一級的保存到棧中,最后通過FP的值來反推出一級一級的調(diào)用關(guān)系。
以ARM CC5 編譯器為例,其棧回溯的主要邏輯如下圖所示:
通過上圖可以看出,main->fun1->fun2,每調(diào)用一級的時候,都會將FP、LR以及參數(shù)等壓棧,而每個FP指向了上一級的棧頂,通過保存關(guān)系,可以找到LR,從而找到上一級的調(diào)用函數(shù)。
具體的流程圖就如右圖所示,按照這樣的方法可以找到backtrace,再比如可以通過stack memory查找調(diào)用棧信息,
?
左圖為棧memory 右圖為寄存器信息。
上圖中:backtrace 第一級是寄存器中的LR,之后就是從棧中進入回溯來找到的。(FP、LR)1、0x1F7BC 0x40BBAA42、0x1F7E4 0x18A3C3、0x1F7EC 0x188184、 0x1F7F4 0x40A41085、 0x1F7FC 0x15946、 0x184BC 0x40A0015
圖中 LR地址都-4 這是因為LR總是保存PC的下一個運行地址,所以找到PC進函數(shù)的位置,則需要LR-4可以得到。
圖中 最后棧停止回溯,可以看到棧的邊界到了0x1f800,所以停止,不然會繼續(xù)一直進行回溯。
backtrace的C代碼如下
void?get_backtrace(u32?lr,?u32?fP)
{
?u8?backtrace_deep?=?0
?u32?stack_limit=getStackLimit()
?u32?stack_base=getStackBase()
?
?printf("Bactrace?info:
")
?do{
??if((fp?<=?stack_base)?&&(fp?>=?stack_limit))
???break;
??lr?=?*(u32*)(fp)
??lr?(lr?==?OxFFFFFFFF?||?lr?==?0x0)
???break;
??fp=*(u32*)(fp-sizeof(u32))
??if(backtrace_deep++>MAX_BACKTRACE_DEPTH)
???break;
?}while(1);
?printf("
");
}
12345678910111213141516171819
2.2 SP的作用
sp 為棧指針,通過push pop 實現(xiàn)對棧存儲的訪問,棧主要是用來存儲局部變量 中間值 等數(shù)據(jù),同樣和全部變量等存儲的區(qū)域一樣,也是一塊memory,沒有任何區(qū)別,只是使用的方式不一樣。
接下來簡單介紹一下各個處理器架構(gòu)的SP指針。
- CortexM3/4(ARMv7)
- CortexM3/4中,「SP分為MSP與PSP」,主棧與線程棧,任何時刻只有一個棧指針有效,通過「CONTROL 寄存器」來選擇棧指針。
- 程序剛運行時就處在主棧(特權(quán)模式),之后可以切到線程棧(非特權(quán)模式),之所以設(shè)置這樣的原因是,一般OS會運行在主棧,而應用程序出在線程棧,應用程序即使出錯,也不會影響OS的運行,也不會影響主棧。通過簡單的程序無需這樣運行,直接在主棧特權(quán)模式下面運行就可以。
- MSP的初值通過存儲器的第一個DWORD中獲取。
- MSP與PSP 都是32位,低兩位均是0.
- CortexR5(Cortexv7)
-
Cortex R5系列比較復雜,繼承了多種工作模式的特性,大多數(shù)模式下都有獨立的棧。
?
- 總共七種工作模式,SYS/FIQ/SYS/SVC/ABORT/IRQ/UND 以及USER,前面六種都是特權(quán)模式 后面是用戶模式也是非特權(quán)模式。可以看到基本都有獨立的棧寄存器,意味著每個模式下可以設(shè)置獨立的棧空間
-
- CortexA53 (ARMv8 -A系列)
- 其有變化了 分為EL1 EL2 EL3 EL4四種模式(AArch64狀態(tài))。每種模式下有自己的SP指針,SP_EL0,SP_EL1,SP_EL2,SP_EL3。通過SPSel來選擇是哪一種的SP指針。
-
- SP_EL1t 代表SP_EL0的指針,SP_ELxH代表相應等級下的SP指針。
- 如果用作基址運算時,SP的低四位[3:0]必須為0,否則會產(chǎn)生SP非對齊異常,系統(tǒng)自動會進行check。
CheckSPAlignment()
?bits(64)?sp?=?SP[];
?if?PSTATE.EL?==?EL0?then
??stack_align_check?=?(SCTLR[].SA0?!=?'0');
?else
??stack_align_check?=?(SCTLR[].SA?!=?'0');
?if?stack_align_check?&&?sp?!=?Align(sp,?16)?then
??AArch64.SPAlignmentFault();
return;
123456789
由下圖可以看到EL3下的SP有值,且與系統(tǒng)的SP值相同(X15下面),則處于EL3模式。
2.3 LR的作用
-
LR為程序跳轉(zhuǎn)時需要用到的寄存器,用來保存「返回地址」(同時也包含異常返回地址)。
-
程序經(jīng)常會存在調(diào)用關(guān)系,當程序執(zhí)行完子程序之后,肯定會返回到主程序,這是返回到主程序的地址就是在LR保存。
-
在一些CorteM系列的處理,LR的第0位會置1 表示,表示Thumb狀態(tài)。
-
當然沒有LR這個寄存器也可以的,直接將返回地址保存到棧中,最后執(zhí)行完之后彈出到PC也行,但是寄存器的訪問速度可以遠高于棧(存儲器SRAM),所以LR的作用還是很明顯的。
-
此外對應ARMv8系列,還有ELR寄存器,對應的是異常狀態(tài)下的返回地址。
a. 當程序執(zhí)行到異常時,異常的返回地址保存到ELR中,當然ARMv8有四種模式,EL0沒有異常處理,所以只有三個ELR寄存器,處理三種異常時的返回地址。b. AArch32到AArch64狀態(tài)時,保存的是32位的地址,高8位均為0。
-
2.3.1 LR的地址保存
當假如程序A->B->C,
void?A()
{
?....??//1地址
?B();??//;BL?B
?....?//2地址
?return;
}
void?B()
{
?....?//3地址
?C();?//BL?C
?....?//4地址
?return;??//pop?lr->PC
}
void?C()
{
?....
?return;?//B?LR
}
12345678910111213141516171819
- 程序A調(diào)用B程序,此時LR更新為「2地址」,
- 跳轉(zhuǎn)到B程序時,B發(fā)現(xiàn)還要跳轉(zhuǎn)到C程序,所以LR會被覆蓋,所以在B程序開始的時候,會講LR保存到棧中。
- 挑轉(zhuǎn)到C程序時,此時LR更新到「4地址」,
- C程序執(zhí)行開始時,發(fā)現(xiàn)沒有子程序跳轉(zhuǎn)了,所以此時的LR不會被覆蓋,所以也不需要將LR保存,退出時直接跳轉(zhuǎn)到「4地址」即可。
- B程序執(zhí)行完時,發(fā)現(xiàn)LR還是錯的,會將壓棧的LR彈出,這樣程序就可以回到「2地址」。
- 如此一來,程序就完成調(diào)用過程,全部執(zhí)行完畢。
2.3.2 接著來說跳轉(zhuǎn)的指令
-
B
- 用法:B Lable,直接跳轉(zhuǎn)Lable處的地址,不改變LR,有限范圍內(nèi)的跳轉(zhuǎn),是不返回的跳轉(zhuǎn)。可以看到上圖B跳轉(zhuǎn)的地址 就是在附近,說明可能是跳到后面的程序的指令,不帶返回的。
-
-
BL
- 用法:BL Lable,將LR=PC+4,(比如在32位程序上+4,Thumb是+2,64位程序上可能是+8)然后跳轉(zhuǎn)到Lable地址,帶鏈接的挑戰(zhàn),說明還會回來的。圖中0x8000F300 地址不在該程序范圍內(nèi),說明是跳到其他地址處 執(zhí)行完成之后,w0是返回值,然后再跳到此次,是帶鏈接的跳轉(zhuǎn)。
-
-
BX:
- 用法:BX Lable,跳轉(zhuǎn)到對應Label地址,Lable中最后一位(bit)為指令集標志,1表示Thumb,0表示ARM狀態(tài),可能會進行模式切換,是不返回的跳轉(zhuǎn)。
- 用法:BX reg,跳轉(zhuǎn)到 reg里面保存的地址,同上,可能會切換模式。該程序直接跳到lr所指示的地址,即返回地址。
-
-
BLX:
- 用法:BLX Lable,跳轉(zhuǎn)到對應Label地址,可能會切換模式,同時LR保存了返回的地址。
- 用法:BLX reg,跳轉(zhuǎn)到 reg里面保存的地址,可能會切換模式,同時LR保存了返回的地址。
-
BR:
- 用法:BR reg,跳轉(zhuǎn)到 reg里面保存的地址,是不返回的跳轉(zhuǎn)。
-
BLR:
- 用法:BLR reg,跳轉(zhuǎn)到 reg里面保存的地址,同時LR保存了返回的地址。
-
B.
- 用法:B.Cond label,根據(jù)狀態(tài)位進行跳轉(zhuǎn),比如 ZCNV 等狀態(tài)位,
- 例如:BHI Lable 、BCS Lable
-
- b.cs 如果w8 >= 0x397 則跳到0x800c0988地址處。
-
評論
查看更多