5.5.1. How C and C++ programs use the library functions

This section describes:

Initializing the execution environment and executing the application

The entry point of a program is at __main in the C library where library code does the following:

  1. Copies nonroot (RO and RW) execution regions from their load addresses to their execution addresses. Also, if any data sections are compressed, they are decompressed from the load address to the execution address. See RealView Compilation Tools v3.0 Linker and Utilities Guide for more details.

  2. Zeroes ZI regions.

  3. Branches to __rt_entry.

If you do not want the library to perform these actions, you can define your own __main that branches to __rt_entry as shown in Example 5.1.

Example 5.1. __main and __rt_entry

    IMPORT __rt_entry	
    EXPORT __main	
    ENTRY	
__main
    B     __rt_entry	
    END

The library function __rt_entry() runs the program as follows:

  1. Calls __rt_stackheap_init() to set up the stack and heap.

  2. Calls __rt_lib_init() to initialize referenced library functions, initialize the locale and, if necessary, set up argc and argv for main().

    For C++, calls the constructors for any top-level objects by way of __cpp_initialize__aeabi_. See C++ initialization, construction, and destruction for more details.

  3. Calls main(), the user-level root of the application.

    From main(), your program might call, among other things, library functions. See Library functions called from main() for more information.

  4. Calls exit() with the value returned by main().

C++ initialization, construction, and destruction

C++ places certain requirements on the construction and destruction of objects with static storage duration (see section 3.6 of the C++ Standard).

The ARM C++ compiler uses the .init_array area to achieve this. This is a const data array of self-relative pointers to functions. For example, you might have the following C++ translation unit, contained in the file test.cpp:

struct T { T(); ~T(); } t;
int f() { return 4; }
int i = f();

This translates into the following pseudo-code:

         AREA ||.text||, CODE, READONLY
     int f() { return 4; }

     static void __sti___8_test_cpp {
         // construct 't' and register its destruction
         __aeabi_atexit(T::T(&t), &T::~T, &__dso_handle);
         i = f();
     }
         AREA ||.init_array||, DATA, READONLY, ALIGN=2
         DCD __sti___8_test_cpp - {PC}

         AREA ||.data||, DATA, ALIGN=2
     t   % 4
     i   % 4

This pseudo-code is for illustration only. To see the code that is generated, compile the C++ source code with armcc -c --cpp -S.

The linker collects each .init_array from the various translation units together. It is important that the .init_array is accumulated in the same order.

The library routine __cpp_initialize__aeabi_ is called from the C library startup code (__rt_lib_init) before main. __cpp_initialize__aeabi_ walks through the .init_array calling each function in turn. On exit, __rt_lib_shutdown calls __cxa_finalize.

Usually there is at most one function for T::T() (mangled name _ZN1TC1Ev), one function for T::~T() (mangled name _ZN1TD1Ev), one __sti__ function, and four bytes of .init_array for each translation unit. The mangled name for the function f() is _Z1fv. There is no way to determine the initialization order between translation units.

Function-local static objects with destructors are also handled using __aeabi_atexit.

Certain sections must be placed contiguously within the same region, for their base/limit symbols to be accessible. If they are not, the linker generates an error such as:


L6216E: Cannot use base/limit symbols for non-contiguous section .init_array

For example, the .init_array area is generated by the C++ compiler. This area must be placed in the same region as __cpp_initialize__aeabi_. To do this, specify the sections in a scatter-loading description file, for example:

LOAD_ROM 0x0000000
{
    EXEC_ROM 0x0000000
    {
        init_aeabi.o(+RO)
        * (.init_array)
        ...
    }
    RAM 0x0100000
    {
        * (+RW,+ZI)
    }
}
Legacy support

In RVCT v2.1, or later, .init_array replaces C$$pi_ctorvec. However, objects with C$$pi_ctorvec are still supported. Therefore, if you have legacy objects, your scatter file contains:

LOAD_ROM 0x0000000
{
    EXEC_ROM 0x0000000
    {
        init_aeabi.o(+RO)
        init.o(+RO)              ; backwards compatibility
        * (.init_array)
        * (C$$pi_ctorvec)        ; backwards compatibility
        ...
    }
    RAM 0x0100000
    {
        * (+RW,+ZI)
    }
}

In RVCT, the C++ name mangling scheme is different from that in ADS v1.2. This might have an effect when loading a newer C++ image into an older debugger.

Exceptions system initialization

The exceptions system can be initialized either on demand (that is, when first used), or before main is entered. Initialization on demand has the advantage of not allocating heap memory unless the exceptions system is used, but has the disadvantage that it becomes impossible to throw any exception (such as std::bad_alloc) if the heap is exhausted at the time of first use.

The default is to initialize on demand. To initialize the exceptions system before main is entered, include the following function in the link:

extern "C" void __cxa_get_globals(void);
extern "C" void __ARM_exceptions_init(void)
{
    __cxa_get_globals();
}

Although you can place the call to __cxa_get_globals directly in your code, placing it in __ARM_exceptions_init ensures that it is called as early as possible. That is, before any global variables are initialized and before main is entered.

__ARM_exceptions_init is weakly referenced by the library initialization mechanism, and is called if it is present as part of __rt_lib_init.

Note

The exception system is initialized by calls to various library functions, for example, std::set_terminate(). Therefore, you might not have to initialize before the entry to main.

Emergency buffer memory for exceptions

You can choose whether or not to allocate emergency memory that is to be used for throwing a std::bad_alloc exception when the heap is exhausted.

To allocate emergency memory, you must include the symbol __ARM_exceptions_buffer_required in the link. A call is then made to __ARM_exceptions_buffer_init() as part of the exceptions system initialization. The symbol is not included by default.

The following routines manage the exceptions emergency buffer:

extern "C" void *__ARM_exceptions_buffer_init()

Called at most once by the runtime, to allocate the emergency buffer memory. It returns a pointer to the emergency buffer memory, or NULL if no memory is allocated.

extern "C" void *__ARM_exceptions_buffer_allocate(void *buffer, size_t size)

Called when an exception is about to be thrown, but there is not enough heap memory available to allocate the exceptions object. buffer is the value previously returned by __ARM_exceptions_buffer_init(), or NULL if that routine was not called. __ARM_exceptions_buffer_allocate() returns a pointer to size bytes of memory that is aligned on an eight-byte boundary, or NULL if the allocation is not possible.

extern "C" void *__ARM_exceptions_buffer_free(void *buffer, void *addr)

Called to free memory possibly allocated by __ARM_exceptions_buffer_allocate(). buffer is the value previously returned by __ARM_exceptions_buffer_init(), or NULL if that routine was not called. The routine determines whether the passed address has been allocated from the emergency memory buffer, and if so, frees it appropriately, then returns a non-NULL value. If the memory at addr was not allocated by __ARM_exceptions_buffer_allocate(), the routine must return NULL.

Default definitions of these routines are present in the image, but you can supply your own versions to override the defaults supplied by the library. The default routines reserve just enough space for a single std::bad_alloc exceptions object. If you do not require an emergency buffer, it is safe to redefine all these routines to return only NULL.

Library functions called from main()

The function main() is the user-level root of the application. It requires that the execution environment is initialized, and that input/output functions can be called. While in main() the program might perform one of the following actions that calls user-customizable functions in the C library:

Copyright © 2002-2006 ARM Limited. All rights reserved.ARM DUI 0205G
Non-Confidential