13

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
user80551
  • 1,134
  • 2
  • 15
  • 26
  • 1
    Do any functions that you use implicitly call `atexit()`? Use of functions such as `fopen()` or `printf()` can cause exit handlers to be installed for streams so they get flushed on exit. – Andrew Henle Feb 09 '18 at 17:52
  • Do you compile your own "newlib" C library? If so, you can use the `--disable-newlib-atexit-dynamic-alloc` ./configure option when you build it to prevent `atexit` using `malloc`. – Ian Abbott Feb 09 '18 at 17:53
  • List all the options you use with `gcc`, so we have a better idea of what you are actually doing. (`-ffreestanding`? `-nodefaultlibs`? `-nostdlib`?) – Nominal Animal Feb 09 '18 at 22:54
  • @NominalAnimal Added to the question. I need some functions from the stdib (`memcpy`, `memset`, math functions, etc) – user80551 Feb 10 '18 at 04:20
  • @IanAbbott No, I didn't compile libc myself. It seems that `/usr/arm-none-eabi/lib/thumb/v7e-m/libc.a` is used. (installed when using the gcc-arm-embedded ppa). (not sure if this is newlib) – user80551 Feb 10 '18 at 04:22
  • @AndrewHenle Not in a way that might show up in static analysis. Neither `register_fini` nor `atexit` are called from anywhare directly in the disassembled final linked elf file. (I don't know if they could be called using function pointers at runtime). – user80551 Feb 10 '18 at 04:28
  • GCC provides [built-in functions](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#Other-Builtins) for those. Are there any you need that are not available as GCC built-ins? – Nominal Animal Feb 10 '18 at 12:20
  • Why are you linking with G++ do call the C++ machinery like `register_fini`? Do you have C++? Do you have static objects? – artless noise Feb 10 '18 at 18:03
  • @artlessnoise https://answers.launchpad.net/gcc-arm-embedded/+question/253559 ; In short, the compiler will call the linker with necessary options as this is a multilib toolchain. The results are the same even if `arm-none-eabi-gcc` is used for linking instead of `arm-none-eabi-g++`. I do not use C++, though I do use static objects. `.bss` is initialized to zero in the `Reset_Handler()` snippet in the question. – user80551 Feb 10 '18 at 19:19

4 Answers4

5

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

vlk
  • 2,581
  • 3
  • 31
  • 35
3

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.

user80551
  • 1,134
  • 2
  • 15
  • 26
3

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.

richarddonkin
  • 261
  • 3
  • 4
  • This actually worked, even without __register_exitproc! Though I had to use a single dash for the -specs option. – Trass3r Dec 18 '18 at 13:56
0

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

Andrey Portnoy
  • 1,430
  • 15
  • 24