ARM架構寄存器介紹
ARM架構下處理器有7種工作模式:
1. USR模式:正常用戶模式,在USR模式下進程正常執行
2. FIQ模式(Fast Interrupt Request):處理快速中斷模式,用於高速數據傳送或者通道處理。
3. IRQ模式((Interrupt Request):用於處理普通中斷。
4. SVC模式(Supervisor):操作系統的保護模式,用於處理軟件中斷。
5. ABT中止模式(Abort mode):處理存儲器故障模式、實現虛擬存儲器和存儲器保護兩種功能。
6. UND 未定義(Undefined):處理未定義的指令陷阱,並且支持硬件協處理器的軟件仿真。
7. SYS 系統模式(System):操作系統模式,用於運行特權操作系統任務。
在這7種模式中,除開USR用戶模式之外的其余6種模式都被稱為特權模式。在特權模式下的程序能夠訪問全部的系統資源,也能夠隨意地完成處理器模式的切換。而除開系統模式外的五種特權模式又稱為異常模式。大多數的用戶程序運行在用戶模式下。如果需要切換處理器模式的時候,程序會拋出異常處理,模式的切換便會在異常處理中完成。
在ARM架構中一共有 37 個寄存器(包含了31個通用寄存器和6個狀態寄存器):
1. 31個通用寄存器包括:
R0-R15, R13_svc, R14_svc, R13_abt, R14_abt, R13_und, R14_und, R13_irq, R14_irq, R8_fiq-R14_fiq
2. 6個狀態寄存器包括:
CPSR, SPSR_svc, SPSR-abt, SPSR_und, SPSR_irq, SPSR_fiq
這里主要對通用寄存器做介紹:
1. 不分組寄存器R0~R7
在所有模式下,未分組寄存器都代表着一個物理寄存器,不分組寄存器沒有被操作系統用作特定用途。
2. 分組寄存器R8~R12
當前所訪問的寄存器與現在處理器運行的模式有關。當處於fiq快速中斷模式的時候,訪問的寄存器是R8_fiq~R12_fiq;而在除開fiq模式以外模式時,訪問的寄存器是R8~R12。
其中R11又被稱為FP寄存器,即frame pointer棧幀指針寄存器。
R12是內部調用暫時寄存器 ip。它在過程鏈接膠合代碼中用於此角色。在過程調用之間,可以將它用於任何用途。被調用函數在返回之前不必恢復 R12。
3. R13、R14:
R13,R4寄存器分別對應着6個不同的物理寄存器
用戶模式與系統模式共用同一種寄存器外,其余5個物理寄存器對應其余5種運行模式。為方便區分采用對應模式的記號來區分不同的物理寄存器:R13_,R14_ R13_usr、R13_fiq、R13_irq、R13_svc、R13_abt、R13_und,R14_usr、R14_fiq、R14_irq、R14_svc、R14_abt、R14_und。
R13/sp:R13在ARM指令集中通常用作堆棧指針。
R14/lr:R14被稱為子程序連接寄存器或者連接寄存器LR。在執行BL子程序調用指令的時候,可以從R14中得到R15(程序計數器PC)的備份。在其他情況下,R14可以當作普通的通用寄存器。
在7種模式下,LR都能夠用於保存子程序的返回地址。當采用BL或BLX指令調用子程序時,處理器會把PC的當前值復制到R14中;然后在執行完子程序后,再將R14的值返回到PC中,處理器通過這種方法完成子程序的調用返回。
4. 程序計數器—R15/PC
R15時程序計數器(PC)所對應的物理寄存器,因為ARM體系結構采用多級流水線技術, R15會指向當前指令后兩條指令的地址,即PC讀取值為當前指令的地址再加8個字節。PC寄存器中目前所保存的值即為下一條執行的指令。
ARM架構函數調用過程介紹
簡單程序分析,當參數小於4個時:
/*test1.c*/ #include <stdio.h> int foo1(int m,int n,int p) { int x = m + n + p; return x; } int main(int argc,char** argv) { int x,y,z,result; x=11; y=22; z=33; result = foo1(x,y,z); printf("result=%d\n",result); return 0; }
在main函數中定義了x,y,z,result,四個局部變量,然后調用了foo1()函數做進一步加法,並且返回最后得到的值。程序用arm-linux-gcc靜態編譯后得到可以在arm開發板上可執行程序test1,利用arm-linux-objdump對逆向后可以得到我們需要的可執行程序匯編代碼:
//省略不相關代碼 0000826c <foo1>: 826c: e52db004 push {fp} ; (str fp, [sp, #-4]!) 8270: e28db000 add fp, sp, #0 8274: e24dd01c sub sp, sp, #28 8278: e50b0010 str r0, [fp, #-16] 827c: e50b1014 str r1, [fp, #-20] 8280: e50b2018 str r2, [fp, #-24] 8284: e51b2010 ldr r2, [fp, #-16] 8288: e51b3014 ldr r3, [fp, #-20] 828c: e0822003 add r2, r2, r3 8290: e51b3018 ldr r3, [fp, #-24] 8294: e0823003 add r3, r2, r3 8298: e50b3008 str r3, [fp, #-8] 829c: e51b3008 ldr r3, [fp, #-8] 82a0: e1a00003 mov r0, r3 82a4: e28bd000 add sp, fp, #0 82a8: e8bd0800 pop {fp} 82ac: e12fff1e bx lr 000082b0 <main>: 82b0: e92d4800 push {fp, lr} 82b4: e28db004 add fp, sp, #4 82b8: e24dd010 sub sp, sp, #16 82bc: e3a0300b mov r3, #11 82c0: e50b3014 str r3, [fp, #-20] 82c4: e3a03016 mov r3, #22 82c8: e50b3010 str r3, [fp, #-16] 82cc: e3a03021 mov r3, #33 ; 0x21 82d0: e50b300c str r3, [fp, #-12] 82d4: e51b0014 ldr r0, [fp, #-20] 82d8: e51b1010 ldr r1, [fp, #-16] 82dc: e51b200c ldr r2, [fp, #-12] 82e0: ebffffe1 bl 826c <foo1> 82e4: e1a03000 mov r3, r0 82e8: e50b3008 str r3, [fp, #-8] 82ec: e59f301c ldr r3, [pc, #28] ; 8310 <main+0x60> 82f0: e1a00003 mov r0, r3 82f4: e51b1008 ldr r1, [fp, #-8] 82f8: eb000347 bl 901c <_IO_printf> 82fc: e59f0010 ldr r0, [pc, #16] ; 8314 <main+0x64> 8300: eb000354 bl 9058 <_IO_puts> 8304: e24bd004 sub sp, fp, #4 8308: e8bd4800 pop {fp, lr} 830c: e12fff1e bx lr 8310: 00063214 andeq r3, r6, r4, lsl r2 8314: 00063220 andeq r3, r6, r0, lsr #4 //省略不相關代碼
將程序test1拷貝到arm開發板中,利用gdbserver開始調試。
在main函數開始執行前,在棧空間中就有其他數據了,在此時test1的棧空間布局大致如下圖所示:
完成foo1函數棧底和棧頂的確定后,程序繼續完成參數傳遞的過程。此時r0,r1,r2寄存器中分別保存着11,22,33,test1程序會將三個參數通過寄存器壓入foo1函數的棧空間中:
Str r0, [r11, #-16]
Str r1, [r11, #-20]
Str r2, [r11, #-16]
Foo1函數棧空間也因此改變:
完成參數傳遞后,程序將會開始執行int x=m + n + p;而整個加法過程都是借助寄存器中完成的,然后程序期望將得到的值返回到main函數中,因此需要將結果放入到r0寄存器中,在此過程中,棧並沒有發生變化。
ldr r3, [r11, #-8] mov r0, r3
接下來程序將會執行返回到main函數的過程,sp指針返回到棧底地址后,然后執行指令ldmfd sp!,{r11}(以sp存放的數據作為地址,將地址中的數據加載進r11中,然后sp+4,)sp指針已經返回到了main函數的棧頂,fp指向了main函數的main函數的棧底。此時lr寄存器的值為0x82e4,
在執行bx lr指令后,返回到main函數中,pc(下一條指令)為0x82e4
當參數大於4的時候:
/*test2.c*/ #include <stdio.h> int foo1(int m,int n, int p,int q,int i,int j) { int x=m + n + p + q + i + j; return x; } void main() { int x,y,z,a,b,c,result; x=11; y=22; z=33; a=44; b=55; c=66; result = foo1(x,y,z,a,b,c); printf("result=%d\n",result); printf("hello world\n"); }
用與test1相同的方法可以得到test2和其匯編代碼:
//省略不相關代碼 0000826c <foo1>: 826c: e52db004 push {fp} ; (str fp, [sp, #-4]!) 8270: e28db000 add fp, sp, #0 8274: e24dd01c sub sp, sp, #28 8278: e50b0010 str r0, [fp, #-16] 827c: e50b1014 str r1, [fp, #-20] 8280: e50b2018 str r2, [fp, #-24] 8284: e50b301c str r3, [fp, #-28] 8288: e51b2010 ldr r2, [fp, #-16] 828c: e51b3014 ldr r3, [fp, #-20] 8290: e0822003 add r2, r2, r3 8294: e51b3018 ldr r3, [fp, #-24] 8298: e0822003 add r2, r2, r3 829c: e51b301c ldr r3, [fp, #-28] 82a0: e0822003 add r2, r2, r3 82a4: e59b3004 ldr r3, [fp, #4] 82a8: e0822003 add r2, r2, r3 82ac: e59b3008 ldr r3, [fp, #8] 82b0: e0823003 add r3, r2, r3 82b4: e50b3008 str r3, [fp, #-8] 82b8: e51b3008 ldr r3, [fp, #-8] 82bc: e1a00003 mov r0, r3 82c0: e28bd000 add sp, fp, #0 82c4: e8bd0800 pop {fp} 82c8: e12fff1e bx lr 000082cc <main>: 82cc: e92d4800 push {fp, lr} 82d0: e28db004 add fp, sp, #4 82d4: e24dd028 sub sp, sp, #40 ; 0x28 82d8: e3a0300b mov r3, #11 82dc: e50b3020 str r3, [fp, #-32] 82e0: e3a03016 mov r3, #22 82e4: e50b301c str r3, [fp, #-28] 82e8: e3a03021 mov r3, #33 ; 0x21 82ec: e50b3018 str r3, [fp, #-24] 82f0: e3a0302c mov r3, #44 ; 0x2c 82f4: e50b3014 str r3, [fp, #-20] 82f8: e3a03037 mov r3, #55 ; 0x37 82fc: e50b3010 str r3, [fp, #-16] 8300: e3a03042 mov r3, #66 ; 0x42 8304: e50b300c str r3, [fp, #-12] 8308: e51b3010 ldr r3, [fp, #-16] 830c: e58d3000 str r3, [sp] 8310: e51b300c ldr r3, [fp, #-12] 8314: e58d3004 str r3, [sp, #4] 8318: e51b0020 ldr r0, [fp, #-32] 831c: e51b101c ldr r1, [fp, #-28] 8320: e51b2018 ldr r2, [fp, #-24] 8324: e51b3014 ldr r3, [fp, #-20] 8328: ebffffcf bl 826c <foo1> 832c: e1a03000 mov r3, r0 8330: e50b3008 str r3, [fp, #-8] 8334: e59f301c ldr r3, [pc, #28] ; 8358 <main+0x8c> 8338: e1a00003 mov r0, r3 833c: e51b1008 ldr r1, [fp, #-8] 8340: eb000349 bl 906c <_ //省略不相關代碼
由於在test2中將會繼續執行result = foo1(x,y,z,a,b,c);因此需要將參數傳遞到foo1函數中,但是由於參數大於4個參數,r0-r3寄存器不能將其全部傳遞,因此后兩個參數通過棧來完成參數傳遞的目的,前四個參數依然通過r0-r3的4個寄存器來傳遞。
而寄存器r0=11,r1=22,r2=33,r3=44
foo1函數棧空間的開辟,將r0-r3寄存器中存儲的數據拷貝到棧中
棧布局變化為
前四個參數都從棧上拷貝到寄存器中並且完成了加法,得到結果r2=110(11+22+33+44)
在加法過程中我們可以發現55與66並沒有保存在foo1的棧空間中,而是在棧頂fp的上方,因此需要將位於棧頂上方的值(也就是位於main函數棧底的值)拷貝進入寄存器再進行操作。
ldr r3, [fp, #4] dd r2, r2, r3 ldr r3, [fp, #8] add r3, r2, r3
終於得到了最后的結果r3=231(110+55+66),由於后續操作與test1類似,就不再贅述了。
Arm架構函數調用總結
1.在開始函數調用時,將新函數所需要的參數賦值分別給從r0開始到r3為止的寄存器,若大於4個參數時,將其余的參數壓入棧中。
2.然后調用bl指令將pc寄存器中的地址賦值給lr寄存器作為子函數的返回地址,並且將子函數的地址拷貝到pc中,由此離開原函數進入到子函數中。
3.將r11(原函數的棧底地址)壓入棧中,作為子函數的棧底fp,並把此時棧底地址放入r11中,sp移動預留局部變量存儲空間,確定了子函數的棧底和棧頂地址。
4.將r0到r3寄存器存儲的值復制到子函數的棧中,然后開始進一步操作
5. 在完成子函數目的后,將需要返回的值放入r0寄存器,利用ldmfd指令出棧,然后利用bx lr返回原函數中,完成整個函數調用過程。