user80551
user80551

Reputation: 1164

How to prevent inclusion of C library destructors and atexit()?

Using arm-none-eabi-gcc for Cortex-M4 (baremetal application), the code for malloc is also emitted even though I never use malloc in my code.

Seeing the assembly output with arm-none-eabi-objdump -xS obj.elf, it seems that malloc is called by __register_exitproc called by atexit called by register_fini

004036a8 <register_fini>:
  4036a8:       4b02            ldr     r3, [pc, #8]    ; (4036b4 <register_fini+0xc>)
  4036aa:       b113            cbz     r3, 4036b2 <register_fini+0xa>
  4036ac:       4802            ldr     r0, [pc, #8]    ; (4036b8 <register_fini+0x10>)
  4036ae:       f000 b805       b.w     4036bc <atexit>
  4036b2:       4770            bx      lr
  4036b4:       00000000        .word   0x00000000
  4036b8:       004036c9        .word   0x004036c9

However, register_fini is never called in the code. main() is called using the following startup code, so even if main exits, the destructors (or functions registered with atexit()) will not get called.

/**
 * \brief This is the code that gets called on processor reset.
 * To initialize the device, and call the main() routine.
 */
void Reset_Handler(void)
{
    uint32_t *pSrc, *pDest;

    /* Initialize the relocate segment */
    pSrc = &_etext;
    pDest = &_srelocate;

    if (pSrc > pDest) {
        for (; pDest < &_erelocate;) {
            *pDest++ = *pSrc++;
        }
    } else if (pSrc < pDest) {
        uint32_t nb_bytes = (uint32_t)&_erelocate - (uint32_t)&_srelocate;
        pSrc = (uint32_t*)((uint32_t)pSrc + nb_bytes) - 1;
        pDest = (uint32_t*)((uint32_t)pDest + nb_bytes) - 1;
        for (;nb_bytes;nb_bytes -= 4) {
            *pDest-- = *pSrc--;
        }
    }
    __NOP();

    /* Clear the zero segment */
    for (pDest = &_szero; pDest < &_ezero;) {
        *pDest++ = 0;
    }

    /* Set the vector table base address */
    pSrc = (uint32_t *) & _sfixed;
    SCB->VTOR = ((uint32_t) pSrc);

    /* Initialize the C library */
    __libc_init_array();

    /* Branch to main function */
    main();

    /* Infinite loop */
    while (1);
}

The code is compiled with -ffunction-sections and -fdata-sections and linked with the flag --gc-sections so that any unreachable code/functions are not included in the output file.


So, how can I prevent these functions (register_fini, atexit, malloc, etc) that are never used in my code from being included in the object file?


Compile options

arm-none-eabi-gcc -o build/main.o -c -mcpu=cortex-m4 -mthumb -pipe -g3 -Wall -Wextra -Wno-expansion-to-defined -Werror -std=gnu11 -fno-strict-aliasing -ffunction-sections -fdata-sections -DARM_MATH_CM4=true -D__SAM4SD32C__ -Ibunch -Iof -Iinclude -Idirs src/main.c

Link options

arm-none-eabi-g++ -o build/tnc.elf -mcpu=cortex-m4 -mthumb -pipe -Wl,--entry=Reset_Handler -Wl,--gc-sections -Wl,--script my/linker/script.ld build/src/bunch.o build/src/of.o build/src/object.o build/src/files.o build/src/main.o -lm

Upvotes: 14

Views: 3996

Answers (4)

Andrey Portnoy
Andrey Portnoy

Reputation: 1509

-nostartfiles and -nostdlib worked for me in a similar context.

Upvotes: 0

richarddonkin
richarddonkin

Reputation: 271

In an environment with limited memory such as the Cortex M4, another option is to use newlib-nano. It provides a __register_exitproc() that is weakly linked. Therefore it is easy to override with your own empty function that avoids calling malloc(). This will have the additional benefit of removing __call_exitprocs() from your binary as well.

  • Add the flag --specs=nano.specs to your compiler and linker options.
  • Create the following function somewhere in your compiled code: void __register_exitproc(void) { }

Note that this will prevent destructors being called for static instances of classes. This seems appropriate in your example.

See the comments in the newlib source for more details.

Upvotes: 3

vlk
vlk

Reputation: 2769

Probably you need -fno-use-cxa-atexit argument for compiler.

Look on this simple example to get working C++ code on pure bare-metal: https://github.com/cortexm/baremetal

Upvotes: 5

user80551
user80551

Reputation: 1164

So here's the workaround that I'm using. Not perfect, but good enough.

Using the --wrap option of ld, I can provide my own definition for atexit that doesn't do anything.

int __wrap_atexit(void __attribute__((unused)) (*function)(void)) {
    return -1;
}

and then link with --wrap=atexit option.

Now, atexit does not call any other functions and the -ffunction-sections and --gc-sections options ensure that malloc is not included in the output file.


Additionally, to enforce that malloc isn't included, a --wrap=malloc option can be passed while linking without defining __wrap_malloc anywhere. This will fail with a link error if some other function happens to use malloc.

Upvotes: 4

Related Questions