ARM处理器的堆栈和函数调用,以及与Sparc的比较

发布时间:2024-07-10  

STACK AND FUNCTIONS

In this part we will look into a special memory region of the process called the Stack. This chapter covers Stack’s purpose and operations related to it. Additionally, we will go through the implementation, types and differences of functions in ARM.


堆栈是进程的一个特殊内存区域。堆栈的使用对于不同处理器的实现是不一样的。介绍堆栈的实现,类型以及。。。


 STACK

Generally speaking, the Stack is a memory region within the program/process. This part of the memory gets allocated when a process is created. We use Stack for storing temporary data such as local variables of some function, environment variables which helps us to transition between the functions, etc. We interact with the stack using PUSH and POP instructions. As explained in Part 4: Memory Instructions: Load And Store PUSH and POP are aliases to some other memory related instructions rather than real instructions, but we use PUSH and POP for simplicity reasons.


堆栈是属于某个程序或进程的。当进程创建时,这部分堆栈内存也被分配。用堆栈存储局部变量,用于帮助我们在函数之间转移的环境变量,等。为简便起见,用PUSH和POP来访问堆栈,类似Sparc的助记符。


Before we look into a practical example it is import for us to know that the Stack can be implemented in various ways. First, when we say that Stack grows, we mean that an item (32 bits of data) is put on to the Stack. The stack can grow UP (when the stack is implemented in a Descending fashion) or DOWN (when the stack is implemented in a Ascending fashion). The actual location where the next (32 bit) piece of information will be put is defined by the Stack Pointer, or to be precise, the memory address stored in the SP register. Here again, the address could be pointing to the current (last) item in the stack or the next available memory slot for the item. If the SP is currently pointing to the last item in the stack (Full stack implementation) the SP will be decreased (in case of Descending Stack) or increased (in case of Ascending Stack) and only then the item will placed in the Stack. If the SP is currently pointing to the next empty slot in the Stack, the data will be first placed and only then the SP will be decreased (Descending Stack) or increased (Ascending Stack).


In our examples we will use the Full descending Stack. Let’s take a quick look into a simple exercise which deals with such a Stack and it’s Stack Pointer.


按照堆栈的生长方向和堆栈指针SP指向的位置,堆栈可以分为4种。例子中使用Full descending Stack,即上图第二种,堆栈向低地址生长,SP指向最后一个数据。


文章制作了很多精美的gif图,下图是一个简单例子中堆栈和寄存器的变化。

We will see that functions take advantage of Stack for saving local variables, preserving register state, etc. To keep everything organized, functions use Stack Frames, a localized memory portion within the stack which is dedicated for a specific function. A stack frame gets created in the prologue (more about this in the next section) of a function. The Frame Pointer (FP) is set to the bottom of the stack frame and then stack buffer for the Stack Frame is allocated. The stack frame (starting from it’s bottom) generally contains the return address (previous LR), previous Frame Pointer, any registers that need to be preserved, function parameters (in case the function accepts more than 4), local variables, etc. While the actual contents of the Stack Frame may vary, the ones outlined before are the most common. Finally, the Stack Frame gets destroyed during the epilogue of a function.


为了使堆栈使用有组织、有条理,函数使用栈帧stack frame,栈帧是专用于某个函数的堆栈的一部分内存区域。整个进程或任务的叫堆栈,某个函数的叫栈帧。


在函数起始处,分配函数的栈帧。FP会设置为栈帧的底部,SP设置为栈帧的顶部?


栈帧一般用于保存返回地址(之前的LR),之前的FR,需要保存的寄存器,函数参数(如果函数参数超过4个的话),局部变量,等。


在函数结束处,栈帧会被释放。


一个例子,


 1 /* azeria@labs:~$ gcc func.c -o func && gdb func */

 2 int main()

 3 {

 4  int res = 0;

 5  int a = 1;

 6  int b = 2;

 7  res = max(a, b);

 8  return res;

 9 }

10 

11 int max(int a,int b)

12 {

13  do_nothing();

14  if(a15  {

16  return b;

17  }

18  else

19  {

20  return a;

21  }

22 }

23 int do_nothing()

24 {

25  return 0;

26 }

We can see in the picture above that currently we are about to leave the function max (see the arrow in the disassembly at the bottom). At this state, the FP (R11) points to 0xbefff254 which is the bottom of our Stack Frame. This address on the Stack (green addresses) stores 0x00010418 which is the return address (previous LR). 4 bytes above this (at 0xbefff250) we have a value 0xbefff26c, which is the address of a previous Frame Pointer. The 0x1 and 0x2 at addresses 0xbefff24c and 0xbefff248 are local variables(其实是输入参数) which were used during the execution of the function max. So the Stack Frame which we just analyzed had only LR, FP and two local variables.


push  {r11, lr},  在该句之前$sp=0xbefff258,在该句之后,$sp=0xbefff250


add  r11, sp, #4  r11=0xbefff254,即fp


sub  sp, sp, #8  之前,已经用了2个单元的堆栈,还需要两个单元用于存储max的输入参数,因此,将sp=sp-8=0xbefff248


  max函数的栈帧即为0xbefff248~~0xbefff254。


str  r0, [r11, #-8]  将输入参数1(放在r0传递进来的)放在max的栈帧中


str  r0, [r11, #-12]  将输入参数2(放在r1传递进来的)放在max的栈帧中


。。。


sub  sp, r11, #4  将r11减去4赋值给sp(应该是+4啊?),即在max结束处,将sp复原为main的栈帧,sp=0xbefff258


pop  {r11, pc}  将max第一句存的lr赋值给pc,将fp恢复回来


 


FUNCTIONS

To understand functions in ARM we first need to get familiar with the structural parts of a function, which are:


Prologue,起始,序曲

Body

Epilogue,结束,尾声

The purpose of the prologue is to save the previous state of the program (by storing values of LR and R11 onto the Stack) and set up the Stack for the local variables of the function. While the implementation of the prologue may differ depending on a compiler that was used, generally this is done by using PUSH/ADD/SUB instructions. An example of a prologue would look like this:


函数起始:


(1)保存之前的状态(将LR和R11保存到堆栈,下面第1句)


(2)设置堆栈的fp,一般是将fp=sp+4(因为之前push已经移动了2个单位)


(3)设置堆栈的sp,sp现在已经移动了2个单位,再移动剩余所需的空间即可。


1 push   {r11, lr}    /* Start of the prologue. Saving Frame Pointer and LR onto the stack */

2 add    r11, sp, #0  /* Setting up the bottom of the stack frame */

3 sub    sp, sp, #16  /* End of the prologue. Allocating some buffer on the stack. This also allocates space for the Stack Frame */

The body part of the function is usually responsible for some kind of unique and specific task. This part of the function may contain various instructions, branches (jumps) to other functions, etc. An example of a body section of a function can be as simple as the following few instructions:


1 mov    r0, #1       /* setting up local variables (a=1). This also serves as setting up the first parameter for the function max */

2 mov    r1, #2       /* setting up local variables (b=2). This also serves as setting up the second parameter for the function max */

3 bl     max          /* Calling/branching to function max */

The sample code above shows a snippet of a function which sets up local variables and then branches to another function. This piece of code also shows us that the parameters of a function (in this case function max) are passed via registers. In some cases, when there are more than 4 parameters to be passed, we would additionally use the Stack to store the remaining parameters. It is also worth mentioning, that a result of a function is returned via the register R0. So what ever the result of a function (max) turns out to be, we should be able to pick it up from the register R0 right after the return from the function. One more thing to point out is that in certain situations the result might be 64 bits in length (exceeds the size of a 32bit register). In that case we can use R0 combined with R1 to return a 64 bit result.


不超过4个的输入参数可以通过寄存器传递,若超过4个参数,则超过的需要通过堆栈传递。函数返回值也是通过R0传递。


The last part of the function, the epilogue, is used to restore the program’s state to it’s initial one (before the function call) so that it can continue from where it left of. For that we need to readjust the Stack Pointer. This is done by using the Frame Pointer register (R11) as a reference and performing add or sub operation. Once we readjust the Stack Pointer, we restore the previously (in prologue) saved register values by poping them from the Stack into respective registers. Depending on the function type, the POP instruction might be the final instruction of the epilogue. However, it might be that after restoring the register values we use BX instruction for leaving the function. An example of an epilogue looks like this:


函数结束,恢复初始状态:


(1)设置堆栈的sp,一般通过r11=fp来设置,通常应该是sp=r11+4。


(2)恢复之前保存的r11=fp和lr到r11和PC。


1 sub    sp, r11, #0  /* Start of the epilogue. Readjusting the Stack Pointer */

2 pop    {r11, pc}    /* End of the epilogue. Restoring Frame Pointer from the Stack, jumping to previously saved LR via direct load into PC. The Stack Frame of a function is finally destroyed at this step. */

So now we know, that:


Prologue sets up the environment for the function;

Body implements the function’s logic and stores result to R0;

Epilogue restores the state so that the program can resume from where it left of before calling the function.

Another key point to know about the functions is their types: leaf and non-leaf. The leaf function is a kind of a function which does not call/branch to another function from itself. A non-leaf function is a kind of a function which in addition to it’s own logic’s does call/branch to another function. The implementation of these two kind of functions are similar. However, they have some differences. To analyze the differences of these functions we will use the following piece of code:


另一个关于函数的要点是,函数分叶子函数和非叶子函数。叶子函数里不再继续调用其它函数,非叶子函数里会继续调用其它函数


 1 /* azeria@labs:~$ as func.s -o func.o && gcc func.o -o func && gdb func */

 2 .global main

 3 

 4 main:

 5     push   {r11, lr}    /* Start of the prologue. Saving Frame Pointer and LR onto the stack */

 6     add    r11, sp, #0  /* Setting up the bottom of the stack frame */

 7     sub    sp, sp, #16  /* End of the prologue. Allocating some buffer on the stack */

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>