> Call stack parsing concepts:
Any architecture of the CPU, has designed a set of general registers, status registers and other control registers to maintain the normal operation of the system. During a function call, the CPU generally needs to deal with several things: Save the field of the parent function (register value), store the return address of the called function in the appropriate register (MIPS RA register, ARM's LR register), to ensure that the system can re-jump back to the correct position after the call function execution is finished. And can have the right execution environment to continue running. Since the CPU general register and even the state register are reused between different functions, this determines that the current CPU state needs to be temporarily saved to a storage space during the function call, and the corresponding data is retrieved from the storage space after the called function is returned to restore the CPU state context.
The space used to store the CPU state is stack stacks, and the storage space used to hold the CPU state during a function call is called the call stack.
ARM architecture support for 64-bit starting from ARMV8, V7 and previous versions are not supported. V8 registers and instruction sets have undergone major changes, resulting in the V7 of the previous version of the Call stack parsing tool is no longer available, need or new design to implement a set.
Ø Call Stack Q&a
Q1: No all functions have a call stack.
Not all functions have a call stack, such as a function with the deepest hierarchy, and you can use the CPU state directly, without needing to care about the call stack. A function that has a call stack can be called a non-leaf function, and no call stack is called a leaf function.
Q2: Does it normally need to be aware of the function call stack
No need. The call stack is ubiquitous, but in general the programmer is unaware of its existence.
Q3: What is the memory management of the call stack?
The call stack is in the stack section of the system, and most of the stacks in the system are in the low-address direction; When each function is called, the system opens up the stack, and at the end of the function, the stack is freed.
Q4: How the call stack is managed during the function run.
The management of the call stack is mainly determined by the compiler and processor architecture, and the size of the function call stack is calculated at the compile stage according to the function's temporal variable size and whether the child function is called. The processor typically has an SP universal register pointer that points to the bottom of a function (the lowest address). During a function run, access to the stack is accessed through the SP pointer +-offset (store or read)
Q5: The size of the call stack is determined by WHO
The size of the call stack is determined by the implementation of the function, such as the number of temporary function variables, whether the function calls child functions, and so on. Compile stage determines the size of the function call stack
Q6: Whether the call stack is dynamically grown or statically divided
From the point of view of system memory management, the function call stack is definitely dynamic growth;
And from the call stack of a function itself, there are both dynamic and static methods. such as MIPS stack is generally at the entrance of the function at one time Open: Addiu sp sp–x; The function stack of ARM 32 is also dynamically growing inside the function, and at arm 64, the function call stack becomes statically programmed.
Q7: Why do Call stack parsing
The most direct data is stored in the stack while the system is running. By parsing the call stack, it is of great value to analyze the system error information and quickly locate the problem. Almost all operating system exception handling, in addition to logging exception status registers and stack information, will contain the results of the call stack trajectory analysis. At the very least, through the call stack to parse the resulting trajectory, you can know which part of the system is wrong.
Ø Call stack parsing design problem decomposition
To figure out how to design the path of the call stack analytic function, we need to have a basic concept and understanding of several things.
1) Retrieve of function return address
The return address of a function can be obtained from two paths: The Return address register (RA, LR, and so on), or the stack. Specific analysis of the specific problem from which access needs to be specific. The leaf function is relatively simple and can be obtained directly from the register because the leaf function has no stack at all. The non-leaf function is relatively complex, and both of these may exist. It depends on the sequence of instructions that lead to the system crash and the LR register stack storage instructions. In general, the modern architecture and compiler design will complete the stack in the first few instructions of the function to open the storage of the function return address, so take the stack of data sorta is correct. If we want to comb the order relationship, then the system design will be more complex. Since ARM64 does not seem to be involved at this point, this article will not be detailed in this case. Therefore, the non-leaf function can be considered simply, get the return address from the stack.
2) What are the key instructions that follow the function call stack analysis?
There are a few of the following:
Stack opening instruction-Gets the stack size for calculating the stack-bottom pointer of the previous function: Sp_pre = sp_base + sp_size
Return address stack store instruction-get function return address in PVP storage location
This is the most basic two instructions, in some architectures, it is also necessary to involve a function call instruction, such as BL, to get the entry address of the called function. In this case, if you design a mechanism similar to the symbol table in the system, you do not need to analyze such directives, by looking at the symbol table, you can greatly reduce the workload.
3) How to analyze and judge instructions
Please review the instruction encoding format in the processor manual.
4) How to prevent cross-border during the command lookup process. (The pointer ++--, will inevitably run to other function sites to go)
The simplest way is to use the symbol table to define the upper and lower bounds of the function. Simple and safe. Although it is possible to use the command feature, a certain probability will fail and no longer be carried out in this way.
OK, call stack parsing design problem decomposition is basically done, is not very simple. The following is a simple addition to the implementation process of ARM64 call stack parsing.
First, the function call stack of ARM64 is as follows: Stack static opening, stack low address direction growth, non-leaf function entrance will open up the stack and save the return address, function call instruction execution when the LR register will save the called function return address, function stack strictly follow load, Store and access the stack in the same way.
The call stack parsing process is as follows:
1) System Crash Processing module provides exception context information 2) Start backward from the exception pointer to find out if the function has a heap
3a) No stack of leaf function, directly take the LR register value; the current function stack bottom as a function stack bottom continuation analysis
Or
3b) Gets the stack size and calculates the bottom of the previous layer function stack.
4) Find the stack store return address instruction, get the stack offset value, get return address 5 from the stack, repeat 3-4 steps until you get enough information.