arm匯編進入C函數分析,C函數壓棧,出棧,傳參,返回值


  • 環境及代碼介紹
    • 環境和源碼

  由於有時候要透徹的理解C里面的一些細節問題,所有有必要看看匯編,首先這一切的開始就是從匯編代碼進入C的main函數過程。這里不使用編譯器自動生成的這部分匯編代碼,因為編譯器自動生成的代碼會涉及環境變量的傳遞,參數的傳遞等等一系列問題。以ARM匯編來進行分析。使用一個啟動匯編文件和一個main.c的文件,在ARM 2440板子上調試這段程序,使用JLinkExe借助jlink來調試:

init.s:

1 .text
2 .global _start
3 _start:
4         ldr sp,=4096  @設置堆棧指針以便調用C函數
5         bl main
6 loop:
7         b loop

main.c:

1 void main(void)
2 {
3 }

   為什么main函數沒有使用 int main(int argc,char **argv) 這種形式?因為我這里是使用的自己寫的啟動匯編文件,由它來完成從匯編到C代碼的進入。

    • 寄存器介紹  

  ARM在任何一種模式下,都可以訪問16個通用寄存器(R0-R15)和1-2個狀態寄存器(CPSR,SPSR),只是有些寄存器是每種模式下都共用的(R0-R7),另外一些是同名但是使用的是不同硬件單元(其他,每種模式下有所不同)。這里的寄存器有些有特定用途:

    R15--PC:程序計數器,指向要取指的那條指令

    R14--LR:鏈接寄存器,保存發生跳轉時,下一條指令的地址,方便使用BL跳回

    R13--SP:堆棧指針

    R12--IP:暫存SP值

    R11--FP: 保存堆棧frame的地址

    后面的IP, FP可能需要結合實際代碼來理解。

  另外,編譯器在處理C程序的時候,R0通常用作傳遞返回值,R1-R4用來傳遞函數參數。

 

  稍微解釋下這段匯編代碼的 ldr sp,=4096 ,為什么設置為4096?有2個原因:

    1.我這里使用的是nand啟動,代碼在內部4K SRAM里面執行。

    2.ARM壓棧時采用的是滿遞減堆棧。

  我覺得更准確的講是由編譯器決定的,其實ARM指令里面有各種類型的堆棧操作指令而不是單單的滿遞減。滿遞減就是指堆棧的增長方向向下,堆棧指針指向堆棧的頂端。如果是空遞減,它會指向堆棧頂端的下一個地址,這個地址未存放有效堆棧數據。其實這里sp = 4096這個內存地址是無法訪問的,4K最大的地址是4096-4,因此進行數據壓棧時,要先調整堆棧指針,然后再壓入數據,這也是所有滿類型堆棧要遵循的原則。

    • 反匯編分析壓棧出棧      

      使用 arm-linux-objdump -DS main.elf > dump 進行反匯編

 1 00000000 <_start>:
 2 .text
 3 .global _start
 4 _start:
 5         ldr sp,=4096
 6    0:    e3a0da01     mov    sp, #4096    ; 0x1000
 7         bl main
 8    4:    eb000000     bl    c <main>
 9 
10 00000008 <loop>:
11 loop:
12         b loop
13    8:    eafffffe     b    8 <loop>
14 
15 0000000c <main>:
16 void main(void)
17 {
18    c:    e52db004     push    {fp}        ; (str fp, [sp, #-4]!)
19   10:    e28db000     add    fp, sp, #0
20 }
21   14:    e28bd000     add    sp, fp, #0
22   18:    e8bd0800     pop    {fp}
23   1c:    e12fff1e     bx    lr

   可以看到進入C函數第一步就是壓棧操作,出C函數里面出棧操作,然后跳轉返回。關於push,pop  ARM官方的文檔給出的說明:

    PUSH is a synonym for STMDB sp!, reglist and POP is a synonym for LDMIA sp! reglist. PUSH and POP are the preferred mnemonics in these cases.

  僅僅是個別名而已,並且是針對sp寄存器進行操作。

  由於我這里的main過於簡單,所有並看不出說明名堂,在main中增加點東西:

1 int main(void)
2 {
3     int a;
4     a = 3;
5 
6     return 0;
7 }

  繼續反匯編,只關注main:

 1 0000000c <main>:
 2 int main(void)
 3 {
 4    c:    e52db004     push    {fp}        ; (str fp, [sp, #-4]!)
 5   10:    e28db000     add    fp, sp, #0
 6   14:    e24dd00c     sub    sp, sp, #12
 7     int a;
 8     a = 3;
 9   18:    e3a03003     mov    r3, #3
10   1c:    e50b3008     str    r3, [fp, #-8]
11 
12     return 0;
13   20:    e3a03000     mov    r3, #0
14 }
15   24:    e1a00003     mov    r0, r3
16   28:    e28bd000     add    sp, fp, #0
17   2c:    e8bd0800     pop    {fp}
18   30:    e12fff1e     bx    lr

可以看到有一對互為逆向操作的指令組合 push {fp}; add fp, sp, #0 <-------> add sp, fp, #0pop {fp},在這對組合指令之間的代碼是不會去修改fp的值的,這樣就實現了恢復調用前fp sp的值,而在它們之間的指令是通過修改sp來訪問堆棧。但是這里有個問題,此處我僅定義了一個int型變量,為何堆棧向下偏移了12個字節?按道理sp-4即可。未找到原因,雖然對於堆棧,Procedure Call Standard for the ARM Architecture,要求遵守幾個約定,比如堆棧指針必須是4字節對齊,此外,對於public interface即全局的接口,要求sp 8字節對齊。這里我的main算是個public interface,因此8字節對齊必須遵守,但是sp-4也是8字節對齊啊,搞不清為什么-12。增加局部變量可以很明細看出8字節對齊的約定。

    • 傳參   
 1 int foo(int a, int b, int c, int d)
 2 {
 3     int A,B,C,D;
 4     A = a;
 5     B = b;
 6     C = c;
 7     D = d;
 8 
 9     return 0;
10 }
11 void main(void)
12 {
13     int a;    
14     a = foo(1,2,3,4);
15 }

  反匯編:

 1 00000000 <_start>:
 2 .text
 3 .global _start
 4 _start:
 5         ldr sp,=4096
 6    0:    e3a0da01     mov    sp, #4096    ; 0x1000
 7         bl main
 8    4:    eb000014     bl    5c <main>
 9 
10 00000008 <loop>:
11 loop:
12         b loop
13    8:    eafffffe     b    8 <loop>
14 
15 0000000c <foo>:
16 int foo(int a, int b, int c, int d)
17 {
18    c:    e52db004     push    {fp}        ; (str fp, [sp, #-4]!)
19   10:    e28db000     add    fp, sp, #0
20   14:    e24dd024     sub    sp, sp, #36    ; 0x24
21   18:    e50b0018     str    r0, [fp, #-24]
22   1c:    e50b101c     str    r1, [fp, #-28]
23   20:    e50b2020     str    r2, [fp, #-32]
24   24:    e50b3024     str    r3, [fp, #-36]    ; 0x24
25     int A,B,C,D;
26     A = a;
27   28:    e51b3018     ldr    r3, [fp, #-24]
28   2c:    e50b3014     str    r3, [fp, #-20]
29     B = b;
30   30:    e51b301c     ldr    r3, [fp, #-28]
31   34:    e50b3010     str    r3, [fp, #-16]
32     C = c;
33   38:    e51b3020     ldr    r3, [fp, #-32]
34   3c:    e50b300c     str    r3, [fp, #-12]
35     D = d;
36   40:    e51b3024     ldr    r3, [fp, #-36]    ; 0x24
37   44:    e50b3008     str    r3, [fp, #-8]
38 
39     return 0;
40   48:    e3a03000     mov    r3, #0
41 }
42   4c:    e1a00003     mov    r0, r3
43   50:    e28bd000     add    sp, fp, #0
44   54:    e8bd0800     pop    {fp}
45   58:    e12fff1e     bx    lr
46 
47 0000005c <main>:
48 void main(void)
49 {
50   5c:    e92d4800     push    {fp, lr}
51   60:    e28db004     add    fp, sp, #4
52   64:    e24dd008     sub    sp, sp, #8
53     int a;    
54     a = foo(1,2,3,4);
55   68:    e3a00001     mov    r0, #1
56   6c:    e3a01002     mov    r1, #2
57   70:    e3a02003     mov    r2, #3
58   74:    e3a03004     mov    r3, #4
59   78:    ebffffe3     bl    c <foo>
60   7c:    e1a03000     mov    r3, r0
61   80:    e50b3008     str    r3, [fp, #-8]
62 }
63   84:    e24bd004     sub    sp, fp, #4
64   88:    e8bd4800     pop    {fp, lr}
65   8c:    e12fff1e     bx    lr

   可以看到參數通過R0-R3寄存器傳遞過去,函數里面將寄存器值壓棧,要用時從棧里面取出值即可。當寄存器不夠用時,總共超過4個字長度,就會通過堆棧傳遞了:

void main(void)
{
  64:    e92d4800     push    {fp, lr}
  68:    e28db004     add    fp, sp, #4
  6c:    e24dd010     sub    sp, sp, #16
    int a;    
    a = foo(1,2,3,4,5);
  70:    e3a03005     mov    r3, #5
  74:    e58d3000     str    r3, [sp]  @通過堆棧傳遞多出來的參數
  78:    e3a00001     mov    r0, #1
  7c:    e3a01002     mov    r1, #2
  80:    e3a02003     mov    r2, #3
  84:    e3a03004     mov    r3, #4
  88:    ebffffdf     bl    c <foo>
  8c:    e1a03000     mov    r3, r0
  90:    e50b3008     str    r3, [fp, #-8]
}
  94:    e24bd004     sub    sp, fp, #4
  98:    e8bd4800     pop    {fp, lr}
  9c:    e12fff1e     bx    lr

 

   返回值好像也是通過寄存器或者堆棧傳遞。


免責聲明!

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



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