Why does the Uboot system initialize the stack? Why does C language function call use the stack, but assembly does not need to initialize the stack?

I have read a lot of analysis on uboot before, among them it is said that the stack should be prepared for the operation of C language.

And in the start.S assembly code of Uboot, regarding system initialization, I also saw stack pointer initialization. However, I have only seen someone say that the system initialization needs to initialize the stack, that is, correctly assign a value to the stack pointer sp , but I have never seen anyone explain why the stack is initialized. Therefore, the next content is to try to explain why we need to initialize the stack after some exploration, namely:

Why does C language function call use the stack, but assembly does not need to initialize the stack.

To understand this problem, we must first understand the role of the stack.

Regarding the function of the stack, it will take a long time to explain in detail, so here is just a brief introduction.

In general, the function of the stack is to save the scene/context and pass parameters.

1. Save the scene/context

On-site means the crime scene. There are always some scenes that need to be recorded, otherwise you will not be able to restore the scene after being destroyed by others. The scene mentioned here means that some registers are used when the CPU is running, such as r0, r1, etc. For the value of these registers, if you do not save and jump directly to the sub-function for execution, then it is very likely It was destroyed by it, because these registers are also used in its function execution.

Therefore, before the function is called, these registers should be kept on the scene temporarily, and the scene should be restored after the calling function is executed and returned. So the CPU can continue to execute correctly.

In the computer, you can often see the word context, and the corresponding English is context. Then:

1.1. What is the context

Save the scene, also called save the context.

Context, called context in English, is the above article and the following article, which are related to your current CPU operation at the moment, that is, those registers you use. Therefore, it is the same as the scene above.

To save the value of a register, the push instruction is generally used to put the values ​​of some corresponding registers on the stack one by one, and push the corresponding values ​​into the stack, which is the so-called push.

Then when the called sub-function is executed, call pop again , assign the values ​​in the stack to the corresponding registers that you used when you first pushed the stack , and pop the corresponding value from the stack , The so-called pop.

The saved registers also include the value of lr (because the bl instruction is used to jump, then the value of the previous pc is stored in lr), and then when the subroutine is executed, the value of lr in the stack The value pops out and assigns it to pc, so that the correct return of the sub-function is realized.

2. Passing parameters

When C language makes a function call, it often passes some parameters to the called function. For these C language-level parameters, when they are translated into assembly language by the compiler, find a place to store them, and let the called The function can be accessed, otherwise the parameter will not be passed. There are two situations for finding a place to put it.

One situation is that there are few parameters passed by itself, and parameters can be passed through registers.

Because in the previous action of saving the scene, the value of the corresponding register has been saved, then at this time, these registers are free and available for us to use, then we can put parameters, and if there are few parameters, It is enough to store the parameters. For example, if there are two parameters, use r0 and r1 to store them. (Regarding parameter 1 and parameter 2, which is placed in r0 and which is placed in r1 is related to the "passing/returning parameters between function calls" in APCS. There will be detailed conventions in APCS. Those who are interested To study.)

But if there are too many parameters and the registers are not enough, then the extra parameters must be put on the stack.

That is, you can use the stack to pass all the redundant parameters or those that the register cannot fit.

3. For example, analyze how C language function calls use the stack

The function of the stack explained above is somewhat abstract, and I will use an example to briefly explain it here, and it will be easy to understand:

use:

1. arm-inux-objdump –d u-boot > dump_u-boot.txt

You can get the dump_u-boot.txt file. This file is the executable assembly code that contains the program in u-boot, where we can see the source code of the C language function, which corresponds to the assembly code.

The assembly code of the two functions is posted below,

One is clock_init,

The other is in the same C source file as clock_init, and the other function CopyCode2Ram:

1. 33d0091c <CopyCode2Ram>:

2. 33d0091c: e92d4070 push {r4, r5, r6, lr}

3. 33d00920: e1a06000 mov r6, r0

4. 33d00924: e1a05001 mov r5, r1

5. 33d00928: e1a04002 mov r4, r2

6. 33d0092c: ebffffef bl 33d008f0 <bBootFrmNORFlash>

7. ... ...

8. 33d00984: ebffff14 bl 33d005dc <nand_read_ll>

9. ... ...

10. 33d009a8: e3a00000 mov r0, #0 ; 0x0

11. 33d009ac: e8bd8070 pop {r4, r5, r6, pc}

12.

13. 33d009b0 <clock_init>:

14. 33d009b0: e3a02313 mov r2, #1275068416 ; 0x4c000000

15. 33d009b4: e3a03005 mov r3, #5 ; 0x5

16. 33d009b8: e5823014 str r3, [r2, #20]

17. ... ...

18. 33d009f8: e1a0f00e mov pc, lr

(1) The code of clock_init part

You can see the first line of the function:

1. 33d009b0: e3a02313 mov r2, #1275068416 ; 0x4c000000

There is no push instruction we expected, and there is no way to put some register values ​​on the stack. This is because the contents of our clock_init part, the r2, r3, etc. registers used, and the register r0 used before calling clock_init, do not conflict, so here you can save the value of these registers without pushing, but There is a register to pay attention to, that is r14, that is, lr. When the clock_init is called before, the bl instruction is used, so the value of pc at the jump will be automatically assigned to lr, so there is no need to push the instruction Save the value of PC to the stack.

And the last line of clock_init code:

1. 33d009f8: e1a0f00e mov pc, lr

It is our common mov pc, lr, the value of lr, which is the PC value saved before the function call, is assigned to the current PC, so that the correct return of the function is realized, that is, the return to the next instruction when the function is called s position.

In this way, the CPU can continue to execute the remaining part of the code in the original function.

(2) CopyCode2Ram part of the code

Its first line:

1. 33d0091c: e92d4070 push {r4, r5, r6, lr}

It is what we expect, with the push instruction, r4, r5, r and lr are saved. Use push to save r4, r5, r6, that's because of the so-called save scene, and then restore the scene when the subsequent function returns, and push to save lr, that is because there are other function calls in this function:

1. 33d0092c: ebffffef bl 33d008f0 <bBootFrmNORFlash>

2. ... ...

3. 33d00984: ebffff14 bl 33d005dc <nand_read_ll>

4. ... ...

The bl instruction is also used, which will change the value of lr when we first entered clock_init, so we need to use push to temporarily save it. Correspondingly, the last line of CopyCode2Ram:

1. 33d009ac: e8bd8070 pop {r4, r5, r6, pc}

It is to pop the value of the previous push and return it to the corresponding register. The last one is the value of lr that started the push and pop it out and assign it to the PC, because the function return is realized. In addition, we noticed that the penultimate line in CopyCode2Ram is:

1. 33d009a8: e3a00000 mov r0, #0 ; 0x0

It is to assign 0 to the r0 register. This is what we call the return value transfer through the r0 register.

The return value here is 0, which corresponds to "return 0" in the C source code.

Regarding which register is used to pass the return value:

Of course, you can also use other temporarily idle and unused registers to transfer the return value, but these processing methods are designed according to the use of ARM's APCS register conventions. You'd better not change the use mode casually. It is still handled in accordance with its agreement, so that the procedure is more in line with the norms.

Guess you like

Origin blog.csdn.net/u014426028/article/details/111057612