woshi_ziyu
发表于: 2016-2-25 14:51:17 | 显示全部楼层

函数调用的基础知识


当讲解有关嵌入式C或嵌入式C++编程的课程时,我们常常遇到需要解决的一个话题是“函数的实参来自哪块内存?”

以下面这个简单的C函数为例:

  1. void test_function(int a, int b, int c, int d);
复制代码

当我们调用该函数时,函数的实参存储在哪?

  1. int main(void)
  2. {
  3.   //...
  4.   test_function(1,2,3,4);
  5.   //...
  6. }
复制代码

最常见的答案是“不知道”,然后是“在堆栈里”,这一点也不奇怪。当然如果你是在编译X86,这个结果是正确的。可以从下面的main函数的X86汇编看出来,该main函数设置了调用test_function(注意:如果你变异的是64位的处理器会有所不同)。

  1. ...
  2.   subl $16, %esp
  3.   movl $4, 12(%esp)
  4.   movl $3, 8(%esp)
  5.   movl $2, 4(%esp)
  6.   movl $1, (%esp)
  7.   call _test_function
  8.   ...
复制代码

堆栈减少了16个字节,然后这四个整形在调用test_function之前移到堆栈。

除了压入函数的实参外,该调用也会压入返回地址(即调用后下一个指令的程序计数器)。在X86术语中,通常被称为栈的保存帧指针。帧指针用来引用局部变量以及保存在栈上的其他数据。

这个栈帧的格式很容易理解,同时历史上也是通过修改返回地址的恶意缓冲区溢出的目标。

不过,我们在这里不是讨论X86,我们感兴趣的是ARM架构。


AAPCS

ARM是RISC架构,而X86是CISC架构。自2003年以来ARM已经发布了文档,详细阐述了单独编译和链接的代码单元如何协同工作。几年里,该文档更换过很多名称,但现在它正式称为《ARM 体系结构的过程调用标准》(Procedure Call Standard for the ARM Architecture)或者是AAPCS。

如果我们使用armcc编译器重新编译ARM平台下的main.c:

  1. > armcc -S main.c
复制代码

我们得到如下:

  1. ...
  2.      MOV      r3,#4
  3.      MOV      r2,#3
  4.      MOV      r1,#2
  5.      MOV      r0,#1
  6.      BL       test_function
  7.      ...
复制代码

在这里,我们可以看到四个参数已放在寄存器r0-r3中。其测试是“与链接相关分支”指令。那么该调用使用了多少栈呢。简短的回答是没有使用,因为BL指令将返回地址放入到链接寄存器(lr/r14)而不是像X86模式中的压入到堆栈中。


寄存器组

我想很多读者都熟悉ARM寄存器组,复习一下:

     ■    有16个数据/内核寄存器r0-r15

     ■    在这16个中,有是哪个是专用的寄存器

            ■    寄存器R13用作堆栈指针(SP)

            ■    寄存器R14用作链接寄存器(LR)

            ■    寄存器r15用作程序计数器(PC)


基本模型

如果有4个或者更少的32位参数,那么以上就是基本的函数调用模型,R0到R3用来传递参数,并且调用返回的地址存储在链接寄存器。

如果我们添加了第五个参数,如下:

  1. void test_function2(int a, int b, int c, int d, int e);
  2. int main(void)
  3. {
  4.   //...
  5.   test_function2(1,2,3,4,5);
  6.   //...;
  7. }
复制代码

我们得到如下:

  1.       ...
  2.         MOV      r0,#5
  3.         MOV      r3,#4
  4.         MOV      r2,#3
  5.         STR      r0,[sp,#0]
  6.         MOV      r1,#2
  7.         MOV      r0,#1
  8.         BL       test_function2
  9.         ...
复制代码

在这里,第五个参数在调用之前存入到栈中。

但是请注意,在较大的代码库中,你可能会看到至少有一个其他的栈压入在这(通常是R4),在调用函数中从没有访问过。这是因为AAPCS定义的堆栈对齐要求,在相同的交换单元里的函数调用不同于交叉的交换单元。该栈的基本要求是:

  1. SP % 4 == 0
复制代码

但是,如果调用用作公共接口时,那么栈也必须符合:

  1. SP % 8 == 0
复制代码

该译文翻译自:Function Parameters and Arguments on 32-bit ARM



--- 未完待续


跳转到指定楼层
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



手机版|

GMT+8, 2024-11-8 05:59 , Processed in 0.039482 second(s), 5 queries , Gzip On, MemCache On. Powered by Discuz! X3.5

YiBoard一板网 © 2015-2022 地址:河北省石家庄市长安区高营大街 ( 冀ICP备18020117号 )

快速回复 返回顶部 返回列表