How does the stack change when a C language function is called in ARM?
Why write an article about stack changes?
When doing system analysis, you must have encountered some tricky problems such as crashes and oops. Generally, everyone will use tools such as gdb, objdump or addr2line to analyze the PC position to locate the error.
However, the essential principles behind these analysis tools may not be deeply understood, and sometimes you are confused when facing a series of backtraces or stack logs.
Today, let’s take a look at how to use the stack to analyze the causes and consequences of changes in crash logs.
Introduction to Arm instruction set
1. r0-r3 are used as parameters for incoming functions and return values for outgoing functions. Between subroutine calls, r0-r3 can be used for any purpose. The called function does not have to restore r0-r3 before returning. ---If the calling function needs to use the contents of r0-r3 again, it must preserve them.2. r4-r11 are used to store the local variables of the function. If the called function uses these registers, it must restore the values of these registers before returning. r11 is the stack frame pointer fp .3. r12 is the intra-call temporary register ip . It is used for this role in procedure link veneers (e.g., interoperation veneers). Between procedure calls, it can be used for any purpose. The called function does not have to restore r12 before returning.4. Register r13 is the stack pointer sp . It cannot be used for any other purpose. The value stored in sp must be the same when exiting the called function as when entering.5. Register r14 is the link register lr . If you save the return address, you can use r14 for other purposes between calls and restore it when the program returns.6. Register r15 is the program counter pc . It cannot be used for any other purpose.
Demo Code
#include <stdio.h>
int m = 8;
int fun(int a,int b)
{
int c = 0;
c = a + b;
return c;
}
int main()
{
int i = 4;
int j = 5;
m = fun(i, j);
return 0;
}
Compile and then disassemble:
$ arm-linux-gnueabi-gcc main.c -o main$ arm-linux-gnueabi-objdump -D -D main
00010400 <fun>:
10400: e52db004 push {fp} ; (str fp, [sp, #-4]!)
10404: e28db000 add fp, sp, #0
10408: e24dd014 sub sp, sp, #20
1040c: e50b0010 str r0, [fp, #-16]
10410: e50b1014 str r1, [fp, #-20] ; 0xffffffec
10414: e3a03000 mov r3, #0
10418: e50b3008 str r3, [fp, #-8]
1041c: e51b2010 ldr r2, [fp, #-16]
10420: e51b3014 ldr r3, [fp, #-20] ; 0xffffffec
10424: e0823003 add r3, r2, r3
10428: e50b3008 str r3, [fp, #-8]
1042c: e51b3008 ldr r3, [fp, #-8]
10430: e1a00003 mov r0, r3
10434: e24bd000 sub sp, fp, #0
10438: e49db004 pop {fp} ; (ldr fp, [sp], #4)
1043c: e12fff1e bx lr
00010440 <main>:
10440: e92d4800 push {fp, lr}
10444: e28db004 add fp, sp, #4
10448: e24dd008 sub sp, sp, #8
1044c: e3a03004 mov r3, #4
10450: e50b300c str r3, [fp, #-12]
10454: e3a03005 mov r3, #5
10458: e50b3008 str r3, [fp, #-8]
1045c: e51b1008 ldr r1, [fp, #-8]
10460: e51b000c ldr r0, [fp, #-12]
10464: ebffffe5 bl 10400 <fun>
10468: e1a02000 mov r2, r0
1046c: e59f3010 ldr r3, [pc, #16] ; 10484 <main+0x44>
10470: e5832000 str r2, [r3]
10474: e3a03000 mov r3, #0
10478: e1a00003 mov r0, r3
1047c: e24bd004 sub sp, fp, #4
10480: e8bd8800 pop {fp, pc}
10484: 00021024 andeq r1, r2, r4, lsr #32
The changing process of the diagram stack
2.
Assignment of global variable m
3. Save the bottom of the stack before entering main, and the current function stack is between fp-sp
4. The stack of function main is ready
5. i is pushed into the stack
6.j pushes into the stack
7. Prepare to call the function fun, push the parameters in reverse order, and push parameter b first
8. Parameter a is pushed onto the stack
9. Leave an address blank as the return value of fun and fill it in later when it returns
10. The return address of fun is pushed onto the stack, usually the next address of the current pc pointer of the main function
11. The bottom address of the main function is pushed onto the stack
12. PC pointer jumps to fun code
13.c Push to stack
14. You can see that the data parameters a and b of the function fun are in the stack of the previous function. Some are on their own stack. This step takes the value to the adder for addition and then assigns it to c.
15.c is assigned to the return value and fills in the blank position above
16. Restore the previous layer from the bottom of the stack
17. lr is assigned to pc, realizing the jump
18. The return value is assigned to the global variable m
19. The parameters of the previous function call are no longer useful, roll back sp
20. Function returns, clean up main's stack space
Summarize
Add Geek Assistant WeChat and join the technical exchange group
Long press, scan the code, and follow the official account