Author Topic: What's eating my RAM? (Atmel SAM D11)  (Read 5794 times)

0 Members and 1 Guest are viewing this topic.

Online ajbTopic starter

  • Super Contributor
  • ***
  • Posts: 2604
  • Country: us
What's eating my RAM? (Atmel SAM D11)
« on: October 18, 2016, 08:04:25 pm »
I've been working on a project using an Atmel SAMD11D14A, which is a Cortex M0+ With 16kB flash and 4kB SRAM, in Atmel Studio 7, and I am NOT using ASF.  I've been thinking that the RAM usage on the project seemed a bit high, but hadn't gotten around to investigating that until today, when I finally ran out.   :(  I've been fumbling my way through trying to figure out exactly what's eating all of my RAM so that I can decide how best to proceed.  I'm far from an expert on the linker, though, so it's been slow.  Here's what I think I know so far/questions I have:

I'm using the default Atmel Studio-generated flash linker script, which appears to be setting the the stack size to 2k.  That seems rather high, especially since the RAM linker script has the stack at 1k.  Would it be wise to reduce the stack size to 1k?  I'm not doing a lot of nested function calls (again, no ASF), so I would think 2k is a bit excessive.

Trying to get a handle on where the other 2k of RAM is going, I've been looking at the .map file, both directly and in amap.  That identified a couple of things that weren't as const as they could be, so that helped a bit. The section summary looks like this:

Quote
SectionSizeLengthNumber of Records
.text9264926480
.vfp11_veneer000
.v4_bx000
.iplt000
.eh_frame000
.rel.dyn000
.jcr000
.igot.plt000
.ARM.exidx000
.relocate1444144411
.bss47047018
.stack000
.ARM.attributes1176117627
.comment1079107912
.debug_info773167731612
.debug_abbrev5642564212
.debug_loc5018501811
.debug_aranges71271212
.debug_ranges66466411
.debug_macro623176231783
.debug_line159501595012
.debug_str37972937972912
.debug_frame1844184420

From my understanding, .bss and .relocate are the only things that wind up in RAM, although it seems that some debug info ends up there as well, but I'm not sure which debug info in particular that might be.  Anyway, .bss + .relocate plus 2k of stack add up to about the total data memory usage AS reports when I build the project.  Are the variables that wind up in .relocate simply those which are explicitly initialized, versus .bss which are uninitialized or initialized to zero?

I can easily account for most of the names that are listed in .bss and .relocate, except for one, which is this:
Code: [Select]
Section SubSection Address Size Demangled Name Module Name File Name
.relocate .data 200005a8 1064 _erelocate = . c:/program files (x86)/atmel/studio/7.0/toolchain/arm/arm-gnu-toolchain/bin/../lib/gcc/arm-none-eabi/5.3.1/../../../../arm-none-eabi/lib/armv6-m\libc.a lib_a-impure.o

That accounts for fully half of my nonstack RAM allocation!  I'm not using printf or malloc or, as far as I know, any of the other intensive C standard lib functions.  Is it unavoidable that libc will consume so much memory, and if so can I keep it out of RAM?  Or is something else going on?

Any advice is appreciated!

Edit: The forum eats repeated tab characters when you edit a post, so tabular formatting is a pain.  This is helpful
« Last Edit: October 18, 2016, 08:09:41 pm by ajb »
 

Offline andersm

  • Super Contributor
  • ***
  • Posts: 1198
  • Country: fi
Re: What's eating my RAM? (Atmel SAM D11)
« Reply #1 on: October 18, 2016, 08:35:51 pm »
From my understanding, .bss and .relocate are the only things that wind up in RAM, although it seems that some debug info ends up there as well, but I'm not sure which debug info in particular that might be.
Debug info isn't written to the device, it's used by the debugger running on your PC.

Quote
Are the variables that wind up in .relocate simply those which are explicitly initialized, versus .bss which are uninitialized or initialized to zero?
Initialized data and code that should be executed from RAM. The startup code copies the contents of the section from flash to its runtime location in RAM.

Quote
Is it unavoidable that libc will consume so much memory, and if so can I keep it out of RAM?
AFAIK, the impure_data section is related to re-entrancy of Newlib, and the suggested solution seems to be to use Newlib-nano. If you're not using any C library functions, you could also just not link it at all (-nodefaultlibs/-nostdlib).

Online ataradov

  • Super Contributor
  • ***
  • Posts: 11260
  • Country: us
    • Personal site
Re: What's eating my RAM? (Atmel SAM D11)
« Reply #2 on: October 18, 2016, 08:56:53 pm »
I'm using the default Atmel Studio-generated flash linker script, which appears to be setting the the stack size to 2k.  That seems rather high, especially since the RAM linker script has the stack at 1k.  Would it be wise to reduce the stack size to 1k?  I'm not doing a lot of nested function calls (again, no ASF), so I would think 2k is a bit excessive.
It is better to put stack at the end of the RAM and not worry about its exact size. This will also exclude it from memory size calculation, so you see totals of your actual variables.
Alex
 

Online ataradov

  • Super Contributor
  • ***
  • Posts: 11260
  • Country: us
    • Personal site
Re: What's eating my RAM? (Atmel SAM D11)
« Reply #3 on: October 18, 2016, 09:01:18 pm »
Also, look at the output of "arm-none-eabi-nm -Sn build/Bootloader.elf" (probably a bit cleaner than raw map file) and check if you see big things with names you don't recognize. Then investigate what those things are.
Alex
 

Online ajbTopic starter

  • Super Contributor
  • ***
  • Posts: 2604
  • Country: us
Re: What's eating my RAM? (Atmel SAM D11)
« Reply #4 on: October 19, 2016, 03:57:32 am »
Thanks, this is all quite helpful.  Part of the reason I've stuck with Atmel Studio and AVRs as long as I have is so I didn't have to deal with all of this startup and linker stuff :scared:

But I'm learning things, and that's good! 

I started a new bare project that I could poke and prod with impunity, and found that the default flash linker script has the stack as only 1k, unlike the other project I've been working with, not sure what's up with that.  I tried building it with --specs=nano.specs, and my little blinky program builds and runs with no trouble and only 32 bytes of .bss+.relocate, which appears to be a result of the autogenerated startup and system files.

I'd like to know why the standard lib requires such a large RAM footprint.  I see vague mentions that it's to do with reentrancy of library functions, but nothing more detailed than that. 

It is better to put stack at the end of the RAM and not worry about its exact size. This will also exclude it from memory size calculation, so you see totals of your actual variables.
 
Here's the part where I admit my near total lack of linker knowledge.  I've included the linker script for reference below.  I see where the stack size is defined, and I see where .stack is described.  I guess _sstack and _estack define the start and end of the stack, respectively, right?  And if I'm understanding the overall structure of the linker script, the stack simply starts at the end of everything else, and ends 1K later, right?  Is it as simple as setting _estack to the highest RAM address, and _sstack = _estack - 1k?

Clearly the linker is something I could stand to learn more about, as well as what's going on under the hood of a typical C program, so if anyone has any recommendations for good reading material that goes deep into those sorts of details I'd be happy to hear about it.

samd11d14am_flash.ld:
Code: [Select]
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
SEARCH_DIR(.)

/* Memory Spaces Definitions */
MEMORY
{
  rom      (rx)  : ORIGIN = 0x00000000, LENGTH = 0x00004000
  ram      (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00001000
}

/* The stack size used by the application. NOTE: you need to adjust according to your application. */
STACK_SIZE = DEFINED(STACK_SIZE) ? STACK_SIZE : DEFINED(__stack_size__) ? __stack_size__ : 0x400;

/* Section Definitions */
SECTIONS
{
    .text :
    {
        . = ALIGN(4);
        _sfixed = .;
        KEEP(*(.vectors .vectors.*))
        *(.text .text.* .gnu.linkonce.t.*)
        *(.glue_7t) *(.glue_7)
        *(.rodata .rodata* .gnu.linkonce.r.*)
        *(.ARM.extab* .gnu.linkonce.armextab.*)

        /* Support C constructors, and C destructors in both user code
           and the C library. This also provides support for C++ code. */
        . = ALIGN(4);
        KEEP(*(.init))
        . = ALIGN(4);
        __preinit_array_start = .;
        KEEP (*(.preinit_array))
        __preinit_array_end = .;

        . = ALIGN(4);
        __init_array_start = .;
        KEEP (*(SORT(.init_array.*)))
        KEEP (*(.init_array))
        __init_array_end = .;

        . = ALIGN(4);
        KEEP (*crtbegin.o(.ctors))
        KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
        KEEP (*(SORT(.ctors.*)))
        KEEP (*crtend.o(.ctors))

        . = ALIGN(4);
        KEEP(*(.fini))

        . = ALIGN(4);
        __fini_array_start = .;
        KEEP (*(.fini_array))
        KEEP (*(SORT(.fini_array.*)))
        __fini_array_end = .;

        KEEP (*crtbegin.o(.dtors))
        KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
        KEEP (*(SORT(.dtors.*)))
        KEEP (*crtend.o(.dtors))

        . = ALIGN(4);
        _efixed = .;            /* End of text section */
    } > rom

    /* .ARM.exidx is sorted, so has to go in its own output section.  */
    PROVIDE_HIDDEN (__exidx_start = .);
    .ARM.exidx :
    {
      *(.ARM.exidx* .gnu.linkonce.armexidx.*)
    } > rom
    PROVIDE_HIDDEN (__exidx_end = .);

    . = ALIGN(4);
    _etext = .;

    .relocate : AT (_etext)
    {
        . = ALIGN(4);
        _srelocate = .;
        *(.ramfunc .ramfunc.*);
        *(.data .data.*);
        . = ALIGN(4);
        _erelocate = .;
    } > ram

    /* .bss section which is used for uninitialized data */
    .bss (NOLOAD) :
    {
        . = ALIGN(4);
        _sbss = . ;
        _szero = .;
        *(.bss .bss.*)
        *(COMMON)
        . = ALIGN(4);
        _ebss = . ;
        _ezero = .;
    } > ram

    /* stack section */
    .stack (NOLOAD):
    {
        . = ALIGN(8);
        _sstack = .;
        . = . + STACK_SIZE;
        . = ALIGN(8);
        _estack = .;
    } > ram

    . = ALIGN(4);
    _end = . ;
}
 

Online ataradov

  • Super Contributor
  • ***
  • Posts: 11260
  • Country: us
    • Personal site
Re: What's eating my RAM? (Atmel SAM D11)
« Reply #5 on: October 19, 2016, 04:05:10 am »
Thanks, this is all quite helpful.  Part of the reason I've stuck with Atmel Studio and AVRs as long as I have is so I didn't have to deal with all of this startup and linker stuff :scared:
That's because those linker scripts were written by thinking people :)

I'd like to know why the standard lib requires such a large RAM footprint.  I see vague mentions that it's to do with reentrancy of library functions, but nothing more detailed than that. 
It normally does not. You need to check what exactly is using all that memory.

And if I'm understanding the overall structure of the linker script, the stack simply starts at the end of everything else, and ends 1K later, right?
That is correct. Here  https://github.com/ataradov/mcu-starter-projects you will find really small and simple starter projects for various MCUs that include better linker files.

To fix that linker file, remove the whole ".stack (NOLOAD): {}" section and add "PROVIDE(_estack = ORIGIN(ram) + LENGTH(ram));" instead. This will move the stack to the bottom of the ram and it can use all remaining memory, as it should.
Alex
 

Online ajbTopic starter

  • Super Contributor
  • ***
  • Posts: 2604
  • Country: us
Re: What's eating my RAM? (Atmel SAM D11)
« Reply #6 on: October 19, 2016, 04:54:26 am »
Thanks, this is all quite helpful.  Part of the reason I've stuck with Atmel Studio and AVRs as long as I have is so I didn't have to deal with all of this startup and linker stuff :scared:
That's because those linker scripts were written by thinking people :)
  And I immensely respect those people and very much appreciate their work!  I've just always been more interested in designing the hardware and in writing the actual application than mucking around in linker scripts and toolchain settings.  Each to their own; I'm grateful for those hardworking people who have heretofore facilitated my blissful ignorance.  :)

I'd like to know why the standard lib requires such a large RAM footprint.  I see vague mentions that it's to do with reentrancy of library functions, but nothing more detailed than that. 
It normally does not. You need to check what exactly is using all that memory.

Even creating a brand new empty project in Atmel Studio (using the "GCC C Executable" Template, no ASF) and immediately building it with NO additional user code results in >1k of RAM usage.  That's with no printf or any of its ilk, no math.h, nothing but main(){while(1){}}.  Again, lib_a.impure.o comprises almost all of the non-stack RAM allocation.  (In my real project I've explicitly avoided printf and math.h because of the ridiculous code size impact, but even so they didn't impact RAM utilization that badly.)  Using --specs=nano.specs drops that down to practically nothing, so that seems to be a good solution, but I'd like to have a better understanding of what the actual difference is and why it's so drastic. 

Thank you for your github link, I will take a look at it tomorrow.
« Last Edit: October 19, 2016, 04:56:50 am by ajb »
 

Online ataradov

  • Super Contributor
  • ***
  • Posts: 11260
  • Country: us
    • Personal site
Re: What's eating my RAM? (Atmel SAM D11)
« Reply #7 on: October 19, 2016, 05:11:48 am »
the actual application than mucking around in linker scripts and toolchain settings
I personally consider linker scripts to be a part of the program rather than toolchain, so I like that ARM world makes me carry a linker script, even if it is a stock one. At some point you will need to make a change - to accommodate a bootloader, or some special are in flash that your application needs, and that's where you have everything ready and no drastic changes are needed for ARM project.

Again, lib_a.impure.o comprises almost all of the non-stack RAM allocation.

Application from that link compiles with this size results:
Quote
size:
   text      data       bss       dec       hex   filename
    660         0         0       660       294   build/Demo.elf
    660         0         0       660       294   (TOTALS)
And there are custom AS projects. I have no idea what settings AS uses by default, but they are clearly not great for a clean project.
Alex
 

Offline ale500

  • Frequent Contributor
  • **
  • Posts: 415
Re: What's eating my RAM? (Atmel SAM D11)
« Reply #8 on: October 19, 2016, 09:34:46 am »
Something to also have in mind is that globals (and statics inside functions) end up in ram: let's say that you have some initialized array, a key-map or something like that, if such things are not initialized using const they end up in flash and are copied in ram, the compiler doesn't know that you are not going to modify them (but lint probably does, some versions of it).

Code: [Select]
char some_array[16] = { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 };

This is better because it only lives in flash:

Code: [Select]
const char some_array[16] = { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 };
 

Online westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Re: What's eating my RAM? (Atmel SAM D11)
« Reply #9 on: October 19, 2016, 10:15:13 am »
I've also been under the impression that "newlib" originated on rather larger "embedded systems" than the current crop of Cortex microcontrollers - those embedded ARM7/ARM9 things with several MB of RAM...  I'm rather surprised that AS7 doesn't default to using the "nano" version which is smaller.  (perhaps it does, and it's still big compared to the SAMD11?)

For example:
Quote
Newlib's standard input and output facilities are surprisingly complete. The complete C file API is also provided, complete with read and write buffering, seeking, and stream flushing capabilities. Variations like sprintf, fprintf, and vfprintf (takes va_list arguments) are also included, which makes a newlib environment look strikingly similar to one you'd expect to see in a more workstation-oriented programming environment.

Someone should port avr-libc :-)
 

Offline snarkysparky

  • Frequent Contributor
  • **
  • Posts: 414
  • Country: us
Re: What's eating my RAM? (Atmel SAM D11)
« Reply #10 on: October 20, 2016, 12:16:49 pm »
Does the linker link in functions that are not even called in the code if a library is included.  If so WHY ??
 

Offline Kalvin

  • Super Contributor
  • ***
  • Posts: 2145
  • Country: fi
  • Embedded SW/HW.
Re: What's eating my RAM? (Atmel SAM D11)
« Reply #11 on: October 20, 2016, 12:51:18 pm »
Does the linker link in functions that are not even called in the code if a library is included.  If so WHY ??

The linker needs options to be enabled in order to remove the unused functions. The C compiler may need some compiler options in order to place each function in its own section. That's why.

Spend a few minutes reading the documentation, and get to know how to read the linker map files and how to make the compiler to produce assembly output.
« Last Edit: October 20, 2016, 12:54:03 pm by Kalvin »
 

Offline andersm

  • Super Contributor
  • ***
  • Posts: 1198
  • Country: fi
Re: What's eating my RAM? (Atmel SAM D11)
« Reply #12 on: October 20, 2016, 12:54:20 pm »
Does the linker link in functions that are not even called in the code if a library is included.  If so WHY ??
In the GNU toolchain, libraries are linked at object file resolution. If the library is built so that each function is placed in its own section, unused functions can be removed via the linker's garbage collector.

Online mikeselectricstuff

  • Super Contributor
  • ***
  • Posts: 13748
  • Country: gb
    • Mike's Electric Stuff
Re: What's eating my RAM? (Atmel SAM D11)
« Reply #13 on: October 20, 2016, 09:17:43 pm »
Something to also have in mind is that globals (and statics inside functions) end up in ram: let's say that you have some initialized array, a key-map or something like that, if such things are not initialized using const they end up in flash and are copied in ram, the compiler doesn't know that you are not going to modify them (but lint probably does, some versions of it).

Code: [Select]
char some_array[16] = { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 };

This is better because it only lives in flash:

Code: [Select]
const char some_array[16] = { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 };
There are some compilers that put const arrays in RAM unless you add a qualifier to force them into flash. A quick check is to add another element and see if RAM usage changes.
Some linkers include a default allocation for a heap, which is unused if malloc isn't used ( which it generally shouldn't be but that's a whole 'nother thread..). Note Printf may use heap and/or significant stack.
Unless you're using a lot of formatting, printf can often be replaced with much simpler application-specific function. Some compilers have options to select different flavours of printf.


Youtube channel:Taking wierd stuff apart. Very apart.
Mike's Electric Stuff: High voltage, vintage electronics etc.
Day Job: Mostly LEDs
 

Offline dgtl

  • Regular Contributor
  • *
  • Posts: 183
  • Country: ee
Re: What's eating my RAM? (Atmel SAM D11)
« Reply #14 on: October 20, 2016, 10:36:08 pm »
Enable function-sections and data-sections compiler and linker flags and -Wl,--gc-sections linker flag. These flags make each function and each global (or local static) variable put in separate linker section. (so you have .text.myfunc1, .text.myfunc2 etc, not just one big .text).
This has 2 advantages:
* The linker can check what is needed and what is not and throw out the functions that are not referenced but are not static (thus have global linkage)
* The linker output map file has detailed information about every function and global variable flash/ram usage.
In addition to viewing a plain map file, a useful visualization about where the space goes is trace_analyze.py tool made for linux kernel ( https://github.com/ezequielgarcia/trace_analyze ). This can be used to generate a png donut diagram corresponding to project folder structure. Some minor changes are needed to graph .data and .text sections separately.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf