5.2. Program Startup and Termination

This section documents CS3's model for target initialization prior to invoking the main function of your program, and aspects of program termination that are left unspecified in the C and C++ standards. It explains how you can customize or override the default behavior for your application.

CS3 divides the startup sequence into three phases:

The hard reset and assembly initialization phases are necessarily written in assembly language; at reset, there may not yet be stack to hold compiler temporaries, or perhaps even any RAM accessible to hold the stack. These phases do the minimum necessary to prepare the environment for running simple C code. Then, the code for the final phase may be written in C; CS3 leaves as much as possible to be done at this point.

The CodeSourcery board support library provides default code for all three phases. The hard reset phase is implemented by board- and profile-specific code. The assembly initialization phase is implemented by profile-specific code. The C initialization phase is implemented by generic code.

5.2.1. The Hard Reset Phase

This phase, which begins at __cs3_reset, is responsible for initializing board-specific registers, such as memory base registers and DRAM controllers, or scanning memory to check the available size. It is written in assembler and ends with a jump to __cs3_start_asm, which is where the assembly initialization phase begins.

The hard reset code is in a section named .cs3.reset. CS3 linker scripts define __cs3_reset as an alias for a board- and profile-specific entry point. You may override the CS3-provided reset code by defining your own __cs3_reset entry point in the .cs3.reset section.

Program execution always begins at __cs3_reset, whether the program is started from the reset vector, the debugger, or a boot monitor. However, the __cs3_reset code linked into the application is typically non-empty only for ROM-based profiles. For example, in a RAM-based profile, resetting the memory controllers would overwrite the code being executed.

5.2.2. The Assembly Initialization Phase

This phase is responsible for initializing the stack pointer and creating an initial stack frame. The symbol __cs3_start_asm marks the entry point of the assembly initialization code. The assembly initialization phase ends with a call or jump to __cs3_start_c.

The assembly initialization phase is profile-specific. For example, while bare-board applications typically must initialize the stack themselves, CS3 also supports boot-monitor profiles where the stack is initialized by the boot monitor before it launches the application. Likewise, some simulators automatically initialize the stack pointer and initial stack frame on startup, while others require a supervisory operation on startup to determine the amount of available memory. Each of these scenarios requires different assembly initialization behavior.

Note that on bare-board targets setting the stack pointer explicitly in the assembly initialization phase is required even if the processor itself initializes the stack pointer automatically on reset. This is to support running programs from the debugger as well as from processor reset.

For backwards compatibility with previous versions of CS3, on RAM and ROM profiles the symbol __cs3_start_asm is actually an alias for a symbol named _start. However, referencing or defining _start directly is now deprecated.

The value of the symbol __cs3_stack provides the initial value of the stack pointer for profiles that must set it explicitly. The CodeSourcery linker scripts provide a default value for this symbol, which you may override by defining __cs3_stack yourself. See Section 5.3.3, “Heap and Stack Placement” for an example of a custom stack.

The initial stack frame is created for the use of ordinary C and C++ calling conventions. The stack should be initialized so that backtraces stop cleanly at this point; this might entail zeroing a dynamic link pointer, or providing hand-written DWARF call frame information.

The last action of the assembly initialization phase is to call the C function __cs3_start_c. This function never returns, and __cs3_start_asm need not be prepared to handle a return from it.

As with the hard reset code, the CodeSourcery board support library provides reasonable default assembly initialization code. However, you may provide your own code by providing a definition for __cs3_start_asm, either in an object file or a library.

5.2.3. The C Initialization Phase

Finally, C code can be executed. The C startup function is declared as follows:

void __cs3_start_c (void) __attribute__ ((noreturn));

This function performs the following steps:

  • Initialize all .data-like sections by copying their contents. For example, ROM-profile linker scripts use this mechanism to initialize writable data in RAM from the read-only data program image.

  • Clear all .bss-like sections.

  • Run constructors for statically-allocated objects, recorded using whatever conventions are usual for C++ on the target architecture.

    CS3 reserves priorities from 0 to 100 for use by initialization code. You can handle tasks like enabling interrupts, initializing coprocessors, pointing control registers at interrupt vectors, and so on by defining constructors with appropriate priorities.

  • Call main as appropriate.

  • Call exit, if it is available.

As with the hard reset and assembly initialization code, the CodeSourcery board support library provides a reasonable definition for the __cs3_start_c function. You may override this by providing a definition for __cs3_start_c, either in an object file or in a library.

5.2.4. Arguments to main

The CodeSourcery-provided definition of __cs3_start_c can pass command-line arguments to main using the normal C argc and argv mechanism if the board support package provides corresponding definitions for __cs3_argc and __cs3_argv. For example:

int __cs3_argc;
char **__cs3_argv;

These variables should be initialized using a constructor function, which is run by __cs3_start_c after it initializes the data segment. Use the constructor attribute on the function definition:

__attribute__((constructor)) 
void __cs3_init_args (void) {
   __cs3_argc = ...;
   __cs3_argv = ...;
}

The constructor function must be named __cs3_init_args, since some profiles provide a default definition of this function.

If definitions of __cs3_argc and __cs3_argv are not provided, then the default __cs3_start_c function invokes main with zero as the argc argument and a null pointer as argv.

5.2.5. Program Termination

A program running on an embedded system is usually designed never to exit — it runs until the system is powered down. The C and C++ standards leave it unspecified as to whether exit is called at program termination. If the program never exits, then there is no reason to include exit, facilities to run functions registered with atexit, or global destructors. This code would never be run and would therefore just waste space in the application.

The CS3 startup code, by itself, does not cause exit to be present in the application. It dynamically checks whether exit is present, and only calls it if it is. If you require exit to be present, either refer to it within your application, or add -Wl,-u,exit to the linking command line.

Similarly, code to register global destructors is only invoked when atexit is already in the executable; CS3, by itself, does not cause atexit to be present. If you require atexit, either refer to it within your application, or add -Wl,-u,atexit to the linking command line.