12
votes

I have an application that runs on an ARM Cortex-M based MCU and is written in C and C++. I use gcc and g++ to compile it and would like to completely disable any heap usage.

In the MCU startup file the heap size is already set to 0. In addition to that, I would also like to disallow any accidental heap use in the code.

In other words, I would like the linker (and/or the compiler) to give me an error when the malloc, calloc, free functions or the new, new[], delete, delete[] operators are used.

So far I've tried -nostdlib which gives me issues like undefined reference to _start. I also tried -nodefaultlibs but that one still does not complain when I try to call malloc. What is the right way to do this?

Notes:

  • This app runs on “bare metal”, there is no operating system.
  • I would also like to avoid any malloc usage in 3rd-party code (vendor-specific libraries, the standard library, printf etc.).
  • I'm fully okay with not using the parts of the C / C++ standard libraries that would require dynamic memory allocations.
  • I'd prefer a compile-time rather than a run-time solution.
2
You can always remove the heap from memory through the linker script, but that won't prevent malloc calls. The only safe way to make the code idiot-proof is probably to use a static analyser, like for example some MISRA checker. But seriously, why would you have any code using dynamic memory in your project? A minimum of code review would easily spot that.Lundin
Without seeing the entirety of your application, getting a definitive answer may be impossible. Even simple C functions such as printf() will use malloc()/free() internally. And with C++ involved, you probably can't use anything that relies on the C++ run-time library. Simply loading the C++ run-time library probably makes extensive use of the heap. I'd venture to say that this would have to be an application requirement from the very beginning of the design to be successful.Andrew Henle
If you have a header visible to the whole program, you could do some hack like #define malloc(dummy) NULL; _Static_assert(0, "Err: use of dynamic memory"). Not pretty but fully portable.Lundin
@AndrewHenle Yes, I would also like to avoid accidentally allowing dynamic allocation in any 3rd party code, including the standard library. And yes, this is an application requirement from the very beginning. :)Venemo
dont link in a malloc implementation. -nostdlib -nostartfiles -ffreestanding is a good start, and yes you need your own boostrap then. And you lose many/most/all C/C++ libraries, but you are bare metal so you say so you should already lose them already. Malloc is not bare metal one could argue it needs memory management that is often part of the operating system. newlib and others can do it, you can easily implement a malloc, but if there is no malloc to link with then you get a compile time failure.old_timer

2 Answers

11
votes

I'm not sure it's the best way to go, however you can use the --wrap flag of ld (which can pass through gcc using -Wl).

The idea is that --wrap allows you to ask to ld to redirect the "real" symbol to your custom one; for example, if you do --wrap=malloc, then ld will look for your __wrap_malloc function to be called instead of the original `malloc.

Now, if you do --wrap=malloc without defining __wrap_malloc you will get away with it if nobody uses it, but if anyone references malloc you'll get a linking error.

$ cat test-nomalloc.c 
#include <stdlib.h>

int main() {
#ifdef USE_MALLOC
    malloc(10);
#endif
    return 0;
}
$ gcc test-nomalloc.c -Wl,--wrap=malloc
$ gcc test-nomalloc.c -DUSE_MALLOC -Wl,--wrap=malloc
/tmp/ccIEUu9v.o: In function `main':
test-nomalloc.c:(.text+0xa): undefined reference to `__wrap_malloc'
collect2: error: ld returned 1 exit status

For new you can use the mangled names _Znwm (operator new(unsigned long)) and _Znam (operator new[](unsigned long)), which should be what every new should come down to in the end.

-1
votes

(posted as an answer because it won't fit in a comment)

If the OS you're running supports the use of LD_PRELOAD, this code should detect attempts to use the heap:

/* remove the LD_PRELOAD from the environment so it
   doesn't kill any child process the app may spawn */
static void lib_init(void) __attribute__((constructor));
static void lib_init( void )
{
    unsetenv( "LD_PRELOAD" );
}

void *malloc( size_t bytes )
{
    kill( getpid(), SIGSEGV );
    return( NULL );
}

void *calloc( size_t n, size_t bytes )
{
    kill( getpid(), SIGSEGV );
    return( NULL );
}

void *realloc( void *ptr, size_t bytes )
{
    kill( getpid(), SIGSEGV );
    return( NULL );
}

void *valloc( size_t bytes )
{
    kill( getpid(), SIGSEGV );
    return( NULL );
}

void *memalign( size_t alignment, size_t bytes )
{
    kill( getpid(), SIGSEGV );
    return( NULL );
}

int posix_memalign( void **ptr, size_t alignment, size_t bytes )
{
    *ptr = NULL;
    kill( getpid(), SIGSEGV );
    return( -1 );
}

Assuming new is implemented using malloc() and delete is implemented using free(), that will catch all heap usage and give you a core file with a stack trace, assuming core files are enabled.

Add the proper headers, compile the file:

gcc [-m32|-m64] -shared heapdetect.c -o heapdetect.so

Run your app:

LD_PRELOAD=/path/to/heapdetect.so /your/app/here args ...