ARM assembly enters C function analysis, C function pushes stack, pops stack, passes parameters, and returns values

Publisher:HarmonyJoyLatest update time:2024-08-01 Source: cnblogsKeywords:arm Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere

Environment and code introduction

Environment and source code

Because sometimes we need to thoroughly understand some details in C, it is necessary to look at the assembly. First of all, it all starts from the assembly code to enter the C main function process. The assembly code automatically generated by the compiler is not used here, because the code automatically generated by the compiler will involve a series of problems such as the transfer of environment variables and parameters. ARM assembly is used for analysis. Use a startup assembly file and a main.c file to debug this program on the ARM 2440 board, and use JLinkExe to debug with the help of jlink:


init.s:



1.text

2. global _start

3 _start:

4 ldr sp,=4096 @Set the stack pointer to call the C function

5 bl main

6 loop:

7 b loop


main.c:


1 void main(void)

2 {

3 }


Why doesn't the main function use the form of int main(int argc, char **argv)? Because I am using a startup assembly file that I wrote myself, which completes the transition from assembly to C code.


Register Introduction

In any mode, ARM can access 16 general registers (R0-R15) and 1-2 status registers (CPSR, SPSR), but some registers are shared in each mode (R0-R7), and some have the same name but use different hardware units (others are different in each mode). Some of the registers here have specific uses:


R15--PC: Program counter, pointing to the instruction to be fetched


R14--LR: Link register, saves the address of the next instruction when a jump occurs, convenient for jumping back using BL


R13--SP: stack pointer


R12--IP: Temporarily store SP value


R11--FP: save the address of the stack frame


The IP and FP mentioned below may need to be understood in conjunction with actual code.


In addition, when the compiler processes a C program, R0 is usually used to pass the return value, and R1-R4 are used to pass function parameters.



Let me explain briefly why ldr sp,=4096 in this assembly code is set to 4096. There are two reasons:


1. I use nand boot here, and the code is executed in the internal 4K SRAM.


2.ARM uses a full descending stack when pushing the stack.


I think it is more accurate to say that it is determined by the compiler. In fact, there are various types of stack operation instructions in the ARM instruction set, not just full decrement. Full decrement means that the stack grows downward, and the stack pointer points to the top of the stack. If it is an empty decrement, it will point to the next address at the top of the stack, which does not store valid stack data. In fact, the memory address sp = 4096 here is inaccessible, and the largest address of 4K is 4096-4. Therefore, when pushing data onto the stack, the stack pointer must be adjusted first, and then the data is pushed in. This is also the principle that all full-type stacks must follow.


Disassembly analysis push and pop


Disassemble using 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

9

10 00000008 :

11 loop:

12 b loop

13 8: eafffffe b 8

14

15 0000000c

:

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


You can see that the first step to enter a C function is to push the stack, then pop the stack in the C function and then jump back. The official ARM documentation on push and pop gives the following description:


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.


It is just an alias and operates on the sp register.


Since my main here is too simple, I can't see the meaning of the description. Let's add something to main:


1 int main(void)

2 {

3 int a;

4a = 3;

5

6 return 0;

7 }


Continue disassembling and focus only on main:


1 0000000c

:

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


You can see that there is a pair of instructions that are reverse operations: push {fp}; add fp, sp, #0 <-------> add sp, fp, #0; pop {fp}. The code between this pair of instructions will not modify the value of fp, so that the value of fp sp before the call is restored. The instructions between them access the stack by modifying sp. But there is a problem here. I only defined an int variable here, so why is the stack offset 12 bytes downward? In theory, sp-4 is enough. The reason was not found. Although the Procedure Call Standard for the ARM Architecture requires several conventions to be followed for the stack, such as the stack pointer must be 4-byte aligned. In addition, for the public interface, that is, the global interface, sp is required to be 8-byte aligned. Here, my main is considered a public interface, so 8-byte alignment must be followed, but sp-4 is also 8-byte aligned. I don't understand why it is -12. Adding local variables can clearly see the 8-byte alignment convention.


Passing parameters


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 }


Disassembly:


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

9

10 00000008 :

11 loop:

12 b loop

13 8: eafffffe b 8

14

15 0000000c :

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

:

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

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


You can see that parameters are passed through registers R0-R3. The register values ​​are pushed onto the stack in the function, and the values ​​are taken out of the stack when needed. When the registers are insufficient and the total length exceeds 4 words, they are passed through the stack:


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] @Pass extra parameters through the stack

78: e3a00001 mov r0, #1

7c: e3a01002 mov r1, #2

80: e3a02003 mov r2, #3

84: e3a03004 mov r3, #4

88: ebffffdf bl c

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


The return value seems to be passed through registers or stack.


Keywords:arm Reference address:ARM assembly enters C function analysis, C function pushes stack, pops stack, passes parameters, and returns values

Previous article:The difference between MMU and MPU
Next article:Jlink software breakpoints and hardware breakpoints

Latest Microcontroller Articles
Change More Related Popular Components

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号