ARM函數調用總結


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返回原函數中,完成整個函數調用過程。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM