In depth analysis of C program startup code

  • 2020-04-02 00:45:50
  • OfStack

Basic composition of image file
The image file loading time includes RO and RW segments, and the running time includes RO, RW and ZI segments. Of the RO and RW segments content in Load time and run time It's the same thing, but the storage space may be different, The ZI segment is created by the initialization function at run time.
RO segment: read-only segment, including in the source program CODE segment . Read only data segment (including initialization of variables value -- can be any variable, the initial value of global/local, static/dynamic variables; It also includes data constants -- which can be global or local. In other words, the compiler should be both Variables allocate storage space -- the variable is readable, not in the RO section, and is The initial value of a variable allocates storage space They are two different things.
RW: Read-write segment, mainly referred to as rw-data, may also have rw-code . Rw-data means already Initializes a global variable.
ZI segment: zero-initialized segment, which mainly consists of uninitialized global variables, Initialized by the compiler with a value of 0. The Data in this segment is also read-write because it is a variable, but no storage space is allocated for the ZI segment when the image file is loaded, although Total RW Size = (RW Data + ZI Data) is considered in the ADS compiler's Memory map file.

The location of code, data, and variables in the image file
The above briefly summarizes the composition of the segments of the image file. From the perspective of program composition, it can be divided into variables, data and code, in which variables can be divided into global/local or static/dynamic. How is their storage space allocated?
Code: generally read-only , the compiler allocates storage space and places it in the RO segment of the image file.
Data: all data referred to here is constant (variable if variable), also includes Pointer to a constant , then Data that is read-only , the compiler also allocates storage space to the RO segment of the image file.
Variables: are divided primarily by lifetime, because lifetime is defined by lifetime in memory, and scope is independent of memory allocation.
1. Global variables and static variables: including static local variables and global/static pointer variables, by the compiler to allocate storage space, has been initialized in the RW segment, otherwise in the ZI segment;
2. Dynamic variables: mainly refers to local variables, including local pointer variables, function parameters, return values, etc., occupy the stack space.
 
Three, the process of starting the stack initialization
Heap and stack: for ARM, the heap grows up and the stack grows down.
Local variables occupy stack space (but their initial value is data and occupy RO space);
The memory space requested dynamically in programs such as malloc() and new functions occupies heap space.
The following discussion does not use the semihosting mechanism
Therefore, the stack space must be prepared for the C program before moving on to the C application. Depending on the memory resources of the target platform, the stack initialization function of successive user_initial_stackheap() is transplanted, mainly by setting the addresses of the heap and stack correctly. It can be written in either C or ARM assembly language and returns at least the heap base address (stored in R0), with the stack base address (stored in R1) optional. Thus, a simple assembly language is used to write the following function:
EXPORT __user_initial_stackheap
__user_initial_stackheap
LDRR0 = 0 x20000; Heap base
LDRR1 = 0 x40000; The stack base, optional
MOV           PC, R14
Note that if this function is not customized in the project, by default the compiler/linker USES |Image$$ZI$$Limit| as the base address for the heap (i.e. the heap and stack areas are placed above the ZI area, which is also considered a standard implementation [7]). However, if the scatter file is used to implement the scatter loading mechanism and the linker does not generate |Image$$ZI$$Limit|, then you must re-implement the function of s _user_initial_stackheap() and set the heap base address and stack top by yourself, otherwise the link will report an error.
The stack area is also divided into a single-area model and a two-area model, in which stack limits must also be set [4,6,7].
Several points to note when redefining the function of _user_initial_stackheap() : first, do not use a stack of more than 96 bytes, second, do not affect R12 (IP, which is used as a temporary register for inter-process calls), and third, return the parameter value according to the rule (R0: heap base; R1: stack base; R2: heap limit; R3: stack limit), and the fourth is to keep the heap area 8-byte aligned [6].

In the startup code, the stack Pointers for each processor mode are also initialized. This problem is easily confused with the function of the _user_initial_stackheap() function discussed above. This can be illustrated from the following points:
(1) in embedded applications, the startup code is divided into two parts: one is the initialization of the system, including the establishment of interrupt direction scale, clock, storage system initialization, key I/O port initialization, stack pointer initialization in each processor mode, etc.; The second is the application initialization (or C library function initialization), including the RW segment's move and ZI segment's zero clearing, the establishment of C application stack area (s) (s) function initializes the stack pointer) and so on.
In this sense, the two are not directly related.
(2) but the two are not unrelated. Single zone model of stack, for example, because the stack is the growth of down, heap is growing up, the system model of stack pointer (the same as the user mode, sharing a R13 register to describe) actually define the upper limit of the user mode stack order zone model area, while __user_initial_stackheap () function specified heap base becomes lower in the stack.
Therefore, if the stack pointer to the system mode (user mode) has been initialized before, then there is no need to redefine the stack base when the system mode (user mode) function is redefined with the _user_initial_stackheap() function.

Four, the content of the startup code and the initialization sequence
As noted earlier, the startup code consists of the initialization of the system and the initialization of the running environment of the application. Once the initialization is complete, the user host program can be called. Resources [1], [3], and [5] all provide very clear but straightforward steps for both parts of the content and process, which is a bit abstract for beginners.
If you don't need to use the MMU for address remappening, it's also easy to understand in combination with the sample boot code and analysis documentation available on the web, plus the do-it-yourself porting and debugging. If the Remap control register of the processor is used for address remapping, there are also relevant codes on the Internet, such as the boot code of the user twentyone [4510 bootloader implementation and analysis (with source code)] is very clear, in addition, in the "ARM learning report" series of articles also have a detailed analysis of it.
For the system initialization order to use MMU to carry out address remap in the startup process, a reference step is given in the article "notes on debugging MMU address mapper with AXD (ii)", and some explanations are given. By further reference to the authoritative information, here, the system initialization order has made a small improvement and correction as follows:
1) ban all interrupt - > 2) initialization of the clock - (3) initializes the memory - (4) to initialize the stack pointer to (5) of each mode initialization GPIO - 6 copy image file to the SDRAM - > 7) establish heavy address mapping table - and can make the MMU pet-name ruby application initialization (RW&ZI area) to attending enabled abort, ⑾ call the main program (dummyOS).
This paper mainly adjusts the order of enabling exception interrupt and application initialization, that is, initializing the application, and then enabling exception interrupt.
.

Related articles: