GCC: How to disable heap usage entirely on an MCU?

2020-07-06 05:14发布

问题:

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.

回答1:

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.



回答2:

(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 ...