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.
Previous article:The difference between MMU and MPU
Next article:Jlink software breakpoints and hardware breakpoints
- Popular Resources
- Popular amplifiers
- Learn ARM development(16)
- Learn ARM development(17)
- Learn ARM development(18)
- Embedded system debugging simulation tool
- A small question that has been bothering me recently has finally been solved~~
- Learn ARM development (1)
- Learn ARM development (2)
- Learn ARM development (4)
- Learn ARM development (6)
Professor at Beihang University, dedicated to promoting microcontrollers and embedded systems for over 20 years.
- LED chemical incompatibility test to see which chemicals LEDs can be used with
- Application of ARM9 hardware coprocessor on WinCE embedded motherboard
- What are the key points for selecting rotor flowmeter?
- LM317 high power charger circuit
- A brief analysis of Embest's application and development of embedded medical devices
- Single-phase RC protection circuit
- stm32 PVD programmable voltage monitor
- Introduction and measurement of edge trigger and level trigger of 51 single chip microcomputer
- Improved design of Linux system software shell protection technology
- What to do if the ABB robot protection device stops
- CGD and Qorvo to jointly revolutionize motor control solutions
- CGD and Qorvo to jointly revolutionize motor control solutions
- Keysight Technologies FieldFox handheld analyzer with VDI spread spectrum module to achieve millimeter wave analysis function
- Infineon's PASCO2V15 XENSIV PAS CO2 5V Sensor Now Available at Mouser for Accurate CO2 Level Measurement
- Advanced gameplay, Harting takes your PCB board connection to a new level!
- Advanced gameplay, Harting takes your PCB board connection to a new level!
- A new chapter in Great Wall Motors R&D: solid-state battery technology leads the future
- Naxin Micro provides full-scenario GaN driver IC solutions
- Interpreting Huawei’s new solid-state battery patent, will it challenge CATL in 2030?
- Are pure electric/plug-in hybrid vehicles going crazy? A Chinese company has launched the world's first -40℃ dischargeable hybrid battery that is not afraid of cold
- How to use Visual Studio Code for FreeRTOS development
- Sha Zhanyou's new monolithic switching power supply design and application technology
- Study Help Post
- stm32L011D3P6 program download problem
- LTC3631EMS8E 12V to 5V circuit schematic
- TMS320DM642StudyReport
- W806 Lianshengde 9.9 yuan development board experience 3 --- littlevgl8.0 transplantation display success
- [Open Source] Test Program Introduction - Crazy Shell Development Board Series
- [2022 Digi-Key Innovation Design Competition] [PLC based on the Internet of Things] --- Design background and goals
- [NUCLEO-L452RE Review] +Serial Port Interrupt Receive