16

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.
trincot
  • 317,000
  • 35
  • 244
  • 286
Venemo
  • 18,515
  • 13
  • 84
  • 125
  • 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 Oct 19 '16 at 11:51
  • 1
    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 Oct 19 '16 at 11:53
  • 1
    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 Oct 19 '16 at 11:57
  • @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 Oct 19 '16 at 12:30
  • 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 Oct 25 '16 at 04:06
  • Yes you can hit situations where the compiler will generate a malloc, but you can learn to avoid them. – old_timer Oct 25 '16 at 04:07
  • @dwelch Indeed! I believe that so far Matteo's answer is a good solution for this. What do you think? – Venemo Oct 26 '16 at 14:01

2 Answers2

14

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.

Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
  • 1
    Whoah, this is awesome. It seems that removing the C++ standard library from the linker will take care of `new` and `delete`. And your solution nicely takes care of `malloc` and its friends. Very cool. – Venemo Oct 19 '16 at 12:35
  • 2
    Actually, looks like I don't have to bother with `libstdc++` at all because it also uses `malloc` under the hood. – Venemo Oct 19 '16 at 12:41
  • This sounds wrong. `_Znwm` isn't some [standardized name](https://stackoverflow.com/a/10128080/8548828) mangling of `new`. As such that whole bit is just plain wrong. The C++ way of doing it is [this](https://stackoverflow.com/a/8186116/8548828). – Tarick Welling Jun 11 '20 at 10:48
  • 1
    @TarickWelling: nobody said this is standard in any way, this is a specific solution for a specific toolchain, as specified by the original question. "The C++ way" you say is not OK because here we don't want to define a replacement `operator new`, but to undefine it, and there's no standard way AFAIK. – Matteo Italia Jun 15 '20 at 18:18
-1

(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 ...
Community
  • 1
  • 1
Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
  • Unfortunately, the app runs on bare metal, there is no OS. I'll update the question to reflect this. – Venemo Oct 19 '16 at 12:25
  • @Venemo You can compile a static library: `gcc -c heapdetect.c; ar rvs libheapdetect.a heapdetect.o` then link with `-lheapdetect` in such a way as it's used before `libc` and `libstdc++`, assuming that's how you link and those library names are what you're using. Without an OS, `kill( getpid(), SIGSEGV );` probably won't work, either. – Andrew Henle Oct 19 '16 at 14:03
  • Yes, that would work, but it's still a runtime thing and not a compile-time thing. – Venemo Oct 19 '16 at 15:21
  • @Venemo I'd say you *need* a run-time check, as static or even compile-time analysis of your custom code won't find implicit uses of the heap. Are you checking for `strdup()`? And that's an easy one. Every library you link in that you don't have the source code for might use the heap at run-time. You can do static analysis on those libraries, but are you prepared to toss every last one of them that you find a reference to `malloc()` or `delete` in? – Andrew Henle Oct 19 '16 at 15:46
  • Yes, since the heap size is set to 0 I have to throw out everything that uses the heap. And no, I don't have to bother with `strdup` because it also just uses `malloc` under the hood. – Venemo Oct 20 '16 at 14:43