EmbeddedRelated.com
Forums

g++ on Cortex-M with no dynamic memory

Started by Dave Nadler November 5, 2016
For years *one* of the *many* ways we've made reliable C++
embedded is by forbidding dynamic storage (except heap).
Typically that requires:
- no exceptions or RTTI
- class new/delete/new[]/delete[] over-ridden with HCF
- all free-storage run-time pruned from the libraries
- minor compiler-specific stubs for un-used C++ stuff

A quick C++ trial showed that generated code didn't
drag too much stuff in: only thing I see first pass is
a pure base class vtable calls a
"you-called-a-pure-virtual-function-you-idiot" function
that dragged in free storage (easy to stub out).

For g++ on cortex-M, is there a standard way to do this?
I didn't find anything at first Google...
The toolchain I'm using is set up for nano.

Thanks in advance for any pointers!
Best Regards, Dave
Dave Nadler <drn@nadler.com> wrote:
> For years *one* of the *many* ways we've made reliable C++ > embedded is by forbidding dynamic storage (except heap).
^^^^ You mean stack? Allocating memory at startup is reasonably safe: each time you allocate the same amount so can easily test if you stay within limit. Such allocation can be useful for static constructors in library functions.
> Typically that requires: > - no exceptions or RTTI > - class new/delete/new[]/delete[] over-ridden with HCF > - all free-storage run-time pruned from the libraries > - minor compiler-specific stubs for un-used C++ stuff > > A quick C++ trial showed that generated code didn't > drag too much stuff in: only thing I see first pass is > a pure base class vtable calls a > "you-called-a-pure-virtual-function-you-idiot" function > that dragged in free storage (easy to stub out). > > For g++ on cortex-M, is there a standard way to do this? > I didn't find anything at first Google... > The toolchain I'm using is set up for nano.
AFAICS there are too many different approaches to talk about standard. You can use environment like Arduino, 'libmaple' or a C++ RTOS which sets up things for you. I used minimal setup: - -fno-rtti -fno-exceptions flags to C++ compiler - -fno-rtti -fno-exceptions -nostartfiles -nostdlib to the linker and stubs for __cxa_pure_virtual and __aeabi_atexit: void __cxa_pure_virtual(void) { while(1); ; } int __aeabi_atexit (void *object, void (*destructor) (void *), void *dso_handle) { return 0; } -- Waldek Hebisch
Op 05-Nov-16 om 10:09 PM schreef Dave Nadler:
> For years *one* of the *many* ways we've made reliable C++ > embedded is by forbidding dynamic storage (except heap). > Typically that requires: > - no exceptions or RTTI > - class new/delete/new[]/delete[] over-ridden with HCF > - all free-storage run-time pruned from the libraries > - minor compiler-specific stubs for un-used C++ stuff > > A quick C++ trial showed that generated code didn't > drag too much stuff in: only thing I see first pass is > a pure base class vtable calls a > "you-called-a-pure-virtual-function-you-idiot" function > that dragged in free storage (easy to stub out).
Nice to hear that I'm not the only user of that trick :)
> For g++ on cortex-M, is there a standard way to do this? > I didn't find anything at first Google... > The toolchain I'm using is set up for nano.
Dunno about any standard way, you seem to use the same approach as I do. Wouter "Objects? No Thanks!" van Ooijen
On 07/11/16 02:55, antispam@math.uni.wroc.pl wrote:
> Dave Nadler <drn@nadler.com> wrote: >> For years *one* of the *many* ways we've made reliable C++ >> embedded is by forbidding dynamic storage (except heap). > ^^^^ > You mean stack? Allocating memory at startup is reasonably > safe: each time you allocate the same amount so can easily > test if you stay within limit. Such allocation can be > useful for static constructors in library functions.
Except where the programmer then proceeds to create their own version of malloc/free on top of it :( Seen that, doh!
> AFAICS there are too many different approaches to talk > about standard. You can use environment like Arduino, > 'libmaple' or a C++ RTOS which sets up things for you. > I used minimal setup: > > - -fno-rtti -fno-exceptions flags to C++ compiler > - -fno-rtti -fno-exceptions -nostartfiles -nostdlib to the linker
That's a (surmountable) problem for C/C++, since effectively the compiler flags become as important as the code itself: getting either wrong can cause application faults. Let's hope compiler version x.y.z hasn't changed the effect of each specific flag. It is doubly unpleasant when you consider libraries supplied by a different company, possibly as a binary blob compiled with a different compiler to the one you are using.
Op 07-Nov-16 om 8:52 AM schreef Tom Gardner:
> On 07/11/16 02:55, antispam@math.uni.wroc.pl wrote: >> Dave Nadler <drn@nadler.com> wrote: >>> For years *one* of the *many* ways we've made reliable C++ >>> embedded is by forbidding dynamic storage (except heap). >> ^^^^ >> You mean stack? Allocating memory at startup is reasonably >> safe: each time you allocate the same amount so can easily >> test if you stay within limit. Such allocation can be >> useful for static constructors in library functions. > > Except where the programmer then proceeds to create > their own version of malloc/free on top of it :( > Seen that, doh! > > >> AFAICS there are too many different approaches to talk >> about standard. You can use environment like Arduino, >> 'libmaple' or a C++ RTOS which sets up things for you. >> I used minimal setup: >> >> - -fno-rtti -fno-exceptions flags to C++ compiler >> - -fno-rtti -fno-exceptions -nostartfiles -nostdlib to the linker > > That's a (surmountable) problem for C/C++, since > effectively the compiler flags become as important > as the code itself: getting either wrong can cause > application faults.
How? The compiler flags make sure that the compiled code doesn't use RTTI & exceptions. Enabling either or both won't change the semantics of the code. But generally yeah, when building your application for a small embedded system such low-level stuff is nearly unavoidably target and toolchain specific. But the bright side is that GCC is the only realistic toolchain that you can use over a range of such platforms. (Hey clang folks, when can I download a pre-built Clang cross system?) Wouter "Objects? No Thanks" van Ooijen
On 07/11/16 08:52, Tom Gardner wrote:
> On 07/11/16 02:55, antispam@math.uni.wroc.pl wrote: >> Dave Nadler <drn@nadler.com> wrote: >>> For years *one* of the *many* ways we've made reliable C++ >>> embedded is by forbidding dynamic storage (except heap). >> ^^^^ >> You mean stack? Allocating memory at startup is reasonably >> safe: each time you allocate the same amount so can easily >> test if you stay within limit. Such allocation can be >> useful for static constructors in library functions. > > Except where the programmer then proceeds to create > their own version of malloc/free on top of it :( > Seen that, doh! > > >> AFAICS there are too many different approaches to talk >> about standard. You can use environment like Arduino, >> 'libmaple' or a C++ RTOS which sets up things for you. >> I used minimal setup: >> >> - -fno-rtti -fno-exceptions flags to C++ compiler >> - -fno-rtti -fno-exceptions -nostartfiles -nostdlib to the linker > > That's a (surmountable) problem for C/C++, since > effectively the compiler flags become as important > as the code itself: getting either wrong can cause > application faults. Let's hope compiler version > x.y.z hasn't changed the effect of each specific > flag.
And that is why compiler flags are given in your Makefile, and the Makefile also refers to the /exact/ version of your toolchain (compiler and library) used to build the code. And your Makefile is part of your source code, kept in your version control system along with the rest of the source code. If you simply build your projects based on whatever version of "arm-none-eabi-gcc" happens to be first on your $PATH, or whatever happens to come with the latest version of the manufacturer's development tools, then you might have something that will work fine today - but you don't have something that will be fine in the future. For the extra paranoid, you can add a sanity check file to your project, with code like this: #if (__GNUC__ != 4) || (__GNUC_MINOR__ != 7) || (__GNUC_PATCHLEVEL__ != 2) #error This code is only tested for gcc 4.7.2 #endif #if __EXCEPTIONS #error This code should be compiled with -fno-exceptions #endif #if __GXX_RTTI #error This code should be compiled with -fno-rtti #endif
> > It is doubly unpleasant when you consider libraries > supplied by a different company, possibly as a > binary blob compiled with a different compiler to > the one you are using. >
On Sunday, November 6, 2016 at 9:55:33 PM UTC-5, anti...@math.uni.wroc.pl wrote:
> Dave Nadler <drn@nadler.com> wrote: > > For years *one* of the *many* ways we've made reliable C++ > > embedded is by forbidding dynamic storage (except heap). > ^^^^ > You mean stack?
Sorry, stack only (no malloc/free/new/delete). And of course static ctors. I was hoping for a standard way to: - prune all memory allocation functions from libraries - stub any RTL bits as you mention Thanks, Best Regards,Dave
On Mon, 07 Nov 2016 09:11:32 +0100, Wouter van Ooijen wrote:

> Op 07-Nov-16 om 8:52 AM schreef Tom Gardner: >> On 07/11/16 02:55, antispam@math.uni.wroc.pl wrote: >>> Dave Nadler <drn@nadler.com> wrote: >>>> For years *one* of the *many* ways we've made reliable C++ >>>> embedded is by forbidding dynamic storage (except heap). >>> ^^^^ >>> You mean stack? Allocating memory at startup is reasonably safe: each >>> time you allocate the same amount so can easily test if you stay >>> within limit. Such allocation can be useful for static constructors >>> in library functions. >> >> Except where the programmer then proceeds to create their own version >> of malloc/free on top of it :( Seen that, doh! >> >> >>> AFAICS there are too many different approaches to talk about standard. >>> You can use environment like Arduino, 'libmaple' or a C++ RTOS which >>> sets up things for you. >>> I used minimal setup: >>> >>> - -fno-rtti -fno-exceptions flags to C++ compiler - -fno-rtti >>> -fno-exceptions -nostartfiles -nostdlib to the linker >> >> That's a (surmountable) problem for C/C++, since effectively the >> compiler flags become as important as the code itself: getting either >> wrong can cause application faults. > > How? > > The compiler flags make sure that the compiled code doesn't use RTTI & > exceptions. Enabling either or both won't change the semantics of the > code. > > But generally yeah, when building your application for a small embedded > system such low-level stuff is nearly unavoidably target and toolchain > specific. But the bright side is that GCC is the only realistic > toolchain that you can use over a range of such platforms. (Hey clang > folks, when can I download a pre-built Clang cross system?) > > Wouter "Objects? No Thanks" van Ooijen
The flags do more than that. There's definitely a difference in size, and what gets pulled in, when you have those turned on. -- Tim Wescott Control systems, embedded software and circuit design I'm looking for work! See my website if you're interested http://www.wescottdesign.com
On Mon, 07 Nov 2016 04:13:31 -0800, Dave Nadler wrote:

> On Sunday, November 6, 2016 at 9:55:33 PM UTC-5, > anti...@math.uni.wroc.pl wrote: >> Dave Nadler <drn@nadler.com> wrote: >> > For years *one* of the *many* ways we've made reliable C++ >> > embedded is by forbidding dynamic storage (except heap). >> ^^^^ >> You mean stack? > > Sorry, stack only (no malloc/free/new/delete). > And of course static ctors. > > I was hoping for a standard way to: > - prune all memory allocation functions from libraries - stub any RTL > bits as you mention > > Thanks, > Best Regards,Dave
I don't know if there's a standard way, but there are various ways. It's been a while since I've done it, but search on topics like "porting newlib to a bace-metal platform" or some such. Newlib is the run-time library for no-OS or RTOS operation, and it leaves about a dozen functions un- functioned. Personally, I allow for one-time object creation off of the heap, as long as it's all before the tasks start up and as long as nothing gets deleted and recreated. But that makes it hard to be strict about not allowing it. IIRC, newlib calls the "OS base memory allocation" procedure -- I don't think you can get rid of it entirely, but you can stub this off to an assert. If you _do_ get rid of all allocation off of the heap, then just leaving it out of your collection -o- stubs will make the link fail when someone does something that calls 'new'. -- Tim Wescott Control systems, embedded software and circuit design I'm looking for work! See my website if you're interested http://www.wescottdesign.com
Op 07-Nov-16 om 5:15 PM schreef Tim Wescott:
> On Mon, 07 Nov 2016 04:13:31 -0800, Dave Nadler wrote: > >> On Sunday, November 6, 2016 at 9:55:33 PM UTC-5, >> anti...@math.uni.wroc.pl wrote: >>> Dave Nadler <drn@nadler.com> wrote: >>>> For years *one* of the *many* ways we've made reliable C++ >>>> embedded is by forbidding dynamic storage (except heap). >>> ^^^^ >>> You mean stack? >> >> Sorry, stack only (no malloc/free/new/delete). >> And of course static ctors. >> >> I was hoping for a standard way to: >> - prune all memory allocation functions from libraries - stub any RTL >> bits as you mention >> >> Thanks, >> Best Regards,Dave > > I don't know if there's a standard way, but there are various ways. It's > been a while since I've done it, but search on topics like "porting newlib > to a bace-metal platform" or some such. Newlib is the run-time library > for no-OS or RTOS operation, and it leaves about a dozen functions un- > functioned. > > Personally, I allow for one-time object creation off of the heap, as long > as it's all before the tasks start up and as long as nothing gets deleted > and recreated. But that makes it hard to be strict about not allowing it. > > IIRC, newlib calls the "OS base memory allocation" procedure -- I don't > think you can get rid of it entirely, but you can stub this off to an > assert. If you _do_ get rid of all allocation off of the heap, then just > leaving it out of your collection -o- stubs will make the link fail when > someone does something that calls 'new'.
That's exactly what I do: // using malloc must cause a linker error void * malloc( size_t n ){ void if_you_see_this_you_are_using_new_or_malloc(); if_you_see_this_you_are_using_new_or_malloc(); return NULL; } // using free must cause a linker error void free( void * p ){ void if_you_see_this_you_are_using_free_or_delete(); if_you_see_this_you_are_using_free_or_delete(); } And btw I hate global ctor's (ctor ordering problem), so my linkerscript has: .IF_YOU_SEE_THIS_YOU_HAVE_ONE_OR_MORE_GLOBAL_OBJECT_CONSTRUCTORS : { KEEP(*(.init)); KEEP(*(.preinit_array)); KEEP(*(SORT(.init_array.*))); KEEP(*(.init_array)); } > nul Wouter "Objects? No Thanks!" van Ooijen