14
\$\begingroup\$

I have the answer for Flash memory, but the RAM question still eludes me.

Arduino has this super nice feature which displays flash and RAM usage right at compile time. Ex. in the image below you can see that this Arduino program uses 2084 bytes of flash (6%) and that global and static variables use 188 bytes (9%) of dynamic memory, or SRAM.

enter image description here

When I compile a simple blink program on an STM32F103RB Nucleo dev board in the System Workbench IDE I'd like to know the same thing: how much Flash and RAM is used, and how much is remaining?

When building completes, System Workbench shows:

Generating binary and Printing size information:
arm-none-eabi-objcopy -O binary "STM32F103RB_Nucleo.elf" "STM32F103RB_Nucleo.bin"
arm-none-eabi-size "STM32F103RB_Nucleo.elf"
   text    data     bss     dec     hex filename
   2896      12    1588    4496    1190 STM32F103RB_Nucleo.elf

When I look at the binary output file, "SW4STM32/Debug/STM32F103RB_Nucleo.bin", I see it is 2908 bytes, which is the sum of text + data. Therefore, that must be my Flash usage! Since I have 128KB of Flash, that means I am using 2908/(128*1024) = 2% of the total Flash space.

But how do I figure out how much SRAM my global and static variables are using, and how much is available for local variables (like Arduino shows)?

I don't have a clue what any of this stuff means yet, but if this is helpful to help you help me, here is the output from objdump -h STM32F103RB_Nucleo.elf:

$ objdump -h STM32F103RB_Nucleo.elf

STM32F103RB_Nucleo.elf:     file format elf32-little

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .isr_vector   0000010c  08000000  08000000  00010000  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .text         00000a1c  0800010c  0800010c  0001010c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  2 .rodata       00000028  08000b28  08000b28  00010b28  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .init_array   00000004  08000b50  08000b50  00010b50  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  4 .fini_array   00000004  08000b54  08000b54  00010b54  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  5 .data         00000004  20000000  08000b58  00020000  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  6 .bss          00000030  20000004  08000b5c  00020004  2**2
                  ALLOC
  7 ._user_heap_stack 00000604  20000034  08000b5c  00020034  2**0
                  ALLOC
  8 .ARM.attributes 00000029  00000000  00000000  00020004  2**0
                  CONTENTS, READONLY
  9 .debug_info   00006795  00000000  00000000  0002002d  2**0
                  CONTENTS, READONLY, DEBUGGING
 10 .debug_abbrev 000013b2  00000000  00000000  000267c2  2**0
                  CONTENTS, READONLY, DEBUGGING
 11 .debug_loc    00000d0f  00000000  00000000  00027b74  2**0
                  CONTENTS, READONLY, DEBUGGING
 12 .debug_aranges 000002e0  00000000  00000000  00028888  2**3
                  CONTENTS, READONLY, DEBUGGING
 13 .debug_ranges 00000378  00000000  00000000  00028b68  2**3
                  CONTENTS, READONLY, DEBUGGING
 14 .debug_macro  00001488  00000000  00000000  00028ee0  2**0
                  CONTENTS, READONLY, DEBUGGING
 15 .debug_line   00003dcc  00000000  00000000  0002a368  2**0
                  CONTENTS, READONLY, DEBUGGING
 16 .debug_str    00066766  00000000  00000000  0002e134  2**0
                  CONTENTS, READONLY, DEBUGGING
 17 .comment      0000007f  00000000  00000000  0009489a  2**0
                  CONTENTS, READONLY
 18 .debug_frame  00000610  00000000  00000000  0009491c  2**2
                  CONTENTS, READONLY, DEBUGGING

(Added later) See also:

  1. My own answer to my own question here: Convert binutils size output from “sysv” format (size --format=sysv my_executable) to “berkeley” format (size --format=berkeley my_executable)
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Dynamic memory usage cannot be determined without simulating every possible path of execution. You can determine static RAM allocations, but that excludes both stack usage and malloc() type dynamic usage. \$\endgroup\$ Mar 22, 2018 at 1:06
  • 2
    \$\begingroup\$ Yeah, static RAM allocations is all I'm trying to figure out. That's what Arduino is outputting too, and although it doesn't tell you what your RAM looks like at all of the points in time (in run-time), it does tell you what your static RAM allocation looks like, which still gives you a good feel for how big your application is relative to the microcontroller it is running on. So despite its limitations I understand that's what I'm getting, and that's what I want. Thanks for explicitly pointing this out though. It's worth understanding. \$\endgroup\$ Mar 22, 2018 at 1:11

3 Answers 3

24
\$\begingroup\$

The information you need is all in the output from size (aka arm-none-eabi-size):

   text    data     bss     dec     hex filename
   2896      12    1588    4496    1190 STM32F103RB_Nucleo.elf
  • text is the size of all code in your application.

  • data is the size of initialized global variables. It counts against both flash memory and RAM, as it's copied from flash to RAM during startup.

  • bss is the size of global variables which are initialized to zero (or are uninitialized, and hence default to zero). They're stored in RAM only.

  • dec and hex are the sum of text + data + bss in decimal and hexadecimal. This value doesn't really mean much on a microcontroller, so it should be ignored. (In environments where a program must be loaded into memory before running, it would be the total memory footprint of the program.)

To calculate the RAM usage of your program, add the data and bss columns together.
SRAM = data + bss

To calculate the FLASH usage of your program, add text and data.
FLASH = text + data

\$\endgroup\$
10
  • \$\begingroup\$ So, despite the fact you said that dec and hex are the size of my ELF file, my ELF file is actually 616876 bytes. Any idea why? \$\endgroup\$ Mar 22, 2018 at 0:00
  • \$\begingroup\$ Also, for those who may have overlooked this fact (myself included initially), the command to see these size values is right in the System Workbench output: arm-none-eabi-size STM32F103RB_Nucleo.elf \$\endgroup\$ Mar 22, 2018 at 0:02
  • \$\begingroup\$ Oops, I thought it was the size of the file, but it's actually just the sum of the text/data/bss sections. It should still be ignored. :) \$\endgroup\$
    – user39382
    Mar 22, 2018 at 0:06
  • 1
    \$\begingroup\$ @duskwuff it is actually not very accurate. Many STM32 uC have more than one RAM (most of F3, many F4, all F4 & H7). It does not take it into the consideration. Another problem - the stack, it does not take it into the account. \$\endgroup\$ Mar 22, 2018 at 9:52
  • 1
    \$\begingroup\$ @GabrielStaples - elf contains much more system, symbolic etc configuration. If you enable high level of the debugging information your .elf file may be many MB long :). \$\endgroup\$ Mar 22, 2018 at 9:54
10
\$\begingroup\$

If you want a quick Linux bash script to auto-calculate Flash and SRAM usage for you, see my other answer here.

TLDR

Jump straight down to the "Summary" at the bottom.

Details:

@duskwuff -inactive- answered the crux of my question in her/his answer here, but I'd like to add some additional insight and also answer my own follow-up questions I wrote in the comments under his answer.

First off, the "Basic Linker Script Concepts" section of the GNU Linker manual, included in its entirety in my "References" section below, was key to learning this information. Refer to it at the bottom of this answer, below.

For a more-detailed look into some of the aspects of this answer, refer also to my other answer to a related question I asked on Stack Overflow here.

It turns out that the information from objdump -h STM32F103RB_Nucleo.elf contains the same, more-specific output sub-sections as the arm-none-eabi-size -x --format=sysv "STM32F103RB_Nucleo.elf" command, which shows the size output in the sysv format instead of in the default berkeley format.

Here again is the berkeley-format output from the size (arm-none-eabi-size for STM32 mcus) command:

arm-none-eabi-size "STM32F103RB_Nucleo.elf"
   text    data     bss     dec     hex filename
   2896      12    1588    4496    1190 STM32F103RB_Nucleo.elf

Note that arm-none-eabi-size "STM32F103RB_Nucleo.elf" is equivalent to arm-none-eabi-size --format=berkeley "STM32F103RB_Nucleo.elf", since --format=berkeley is the default.

In the objdump -h STM32F103RB_Nucleo.elf output I posted in my question, all the information we need to answer my question is found, just in a much-more-detailed format is all.

As the GNU linker manual explains (see below), VMA means "Virtual Memory Address", and LMA means "Load Memory Address". The VMA addresses are where the data is located at run-time (which could be in volatile SRAM, since some data is copied from Flash to SRAM at boot), and the LMA is where the data is located when the device is off, and also prior to loading at boot (so it must be in non-volatile Flash memory only). Some data will be copied from Flash (LMA address) to SRAM (VMA address) at boot.

For this STM32 microcontroller, Flash memory begins at address 0x08000000, and SRAM begins at address 0x20000000. So, any output sections in the objdump -h output which have a VMA (runtime address) of 0, are therefore unused (not even on the microcontroller) and can be discarded immediately. That eliminates the latter half of the objdump -h output, most of which is debug information, from the .ARM.attributes output section through the .debug_frame output section, inclusive, leaving us with only these sysv output sections which we care about:

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .isr_vector   0000010c  08000000  08000000  00010000  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .text         00000a1c  0800010c  0800010c  0001010c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  2 .rodata       00000028  08000b28  08000b28  00010b28  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .init_array   00000004  08000b50  08000b50  00010b50  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  4 .fini_array   00000004  08000b54  08000b54  00010b54  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  5 .data         00000004  20000000  08000b58  00020000  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  6 .bss          00000030  20000004  08000b5c  00020004  2**2
                  ALLOC
  7 ._user_heap_stack 00000604  20000034  08000b5c  00020034  2**0
                  ALLOC

Any section marked READONLY, you can see, is stored only in the Flash memory at the 0x08000000-level addresses, and has the same LMA and VMA address. This makes sense, as there's no need to copy it to SRAM if it's read-only. The sections marked READONLY make up the berkeley-format text section. They include:

.isr_vector
.text
.rodata

So, we know that:

.isr_vector + .text + .rodata = text

This can be verified by summing their hex sizes:

10c + a1c + 28 = b50

0xb50 is decimal 2896, which matches the berkeley size output for the text section! And, again, as shown by the 0x08000000-level addresses for both LMA and VMA, this means all of these sections are in Flash memory only!

Here's my description of what these sections are:

SYSV SECTIONS WHICH MAKE UP THE BERKELEY text SECTION AND WHICH ARE IN FLASH MEMORY ONLY:

  1. .isr_vector = the ISR (Interrupt Service Routine) vector table. It simply points to all ISR callback functions for all possible interrupts the microcontroller can handle.
  2. .text = program logic; ie: the actual code.
  3. .rodata = Read-Only data; ie: const and constexpr static and global variables which are read-only.

Next, we can see that the NON-READONLY sections which also are ALLOC and LOAD sections include:

.init_array
.fini_array
.data

So, we know that they make up the berkeley data section:

.init_array + .fini_array + .data = data

This can be verified by summing their hex sizes:

4 + 4 + 4 = c

0xc is decimal 12, which matches the berkeley size output for the data section.

I don't know what the .init_array or .fini_array sections mean (if you know, please answer or post a comment), and I'm confused on their location, as their LMA and VMA addresses are identical, indicating they are both in Flash and Flash only. However, it's clear by the addresses of the .data section, that it takes up both Flash memory (at LMA [load address] = 0x08000b58) and SRAM memory (at VMA [run-time address] = 0x20000000). This means this data is copied from Flash to the beginning of SRAM. This happens during the startup routine. .data contains NON-zero-initialized (ie: initialized with something other than zero) static and global variables. In summary:

SYSV SECTIONS WHICH MAKE UP THE BERKELEY data SECTION AND WHICH ARE IN BOTH FLASH AND SRAM, AND WHICH ARE COPIED FROM FLASH TO SRAM DURING STARTUP:

  1. .data = NON-zero-initialized (ie: initialized with something other than zero) static and global variables

SYSV SECTIONS WHICH MAKE UP THE BERKELEY data SECTION AND WHICH APPARENTLY ARE IN FLASH MEMORY ONLY?:

  1. .init_array = unknown
  2. .fini_array = unknown

That leaves us with just these sections marked with ALLOC and nothing else remaining:

.bss
._user_heap_stack

So, we know they make up the berkeley bss section:

.bss + ._user_heap_stack = bss

This can be verified by summing their hex sizes:

30 + 604 = 634

0x634 is decimal 1588, which matches the berkeley size output for the bss section.

This is really interesting!, as it shows that the berkeley bss section doesn't just include the .bss (zero-initialized static and global variables) output section, but it also includes the ._user_heap_stack output section, which is perhaps (or rather, appears to me to be) the heap size we specify inside the STM32Cube configuration software. In either case, it appears to be the SRAM set aside for both the runtime stack (for local variables) and heap (for dynamically-allocated memory). In summary:

SYSV SECTIONS WHICH MAKE UP THE BERKELEY bss SECTION AND WHICH TAKE UP SPACE IN SRAM ONLY, BUT NOT FLASH:

  1. .bss = zero-initialized static and global variables; this SRAM is set to all zeros at program startup.
  2. ._user_heap_stack = (I think) completely uninitialized SRAM which is set aside for the runtime stack (for local variables) and heap (for dynamically-allocated memory).

Summary:

Here is the breakdown of which sysv output sections from the objdump -h STM32F103RB_Nucleo.elf output (also shown with less detail in the arm-none-eabi-size -x --format=sysv "STM32F103RB_Nucleo.elf" output) make up which berkeley output sections.

In the below image, you can see all 3 berkely output sections boxed in different colors:

  1. [YELLOW] The berkeley-format text output sections (read-only, program logic and const static and global variables) are boxed in yellow.

     .isr_vector + .text + .rodata = text
    
    1. .isr_vector [IN FLASH ONLY] = the ISR (Interrupt Service Routine) vector table. It simply points to all ISR callback functions for all possible interrupts the microcontroller can handle.
    2. .text [IN FLASH ONLY] = program logic; ie: the actual code.
    3. .rodata [IN FLASH ONLY] = Read-Only data; ie: const and constexpr static and global variables which are read-only.
  2. [BLUE] The berkeley-format data output sections (non-zero-initialized [ie: initialized with values other than zero] static and global variables) are boxed in blue.

     .init_array + .fini_array + .data = data
    
    1. .init_array [FLASH ONLY it appears] = unknown.
    2. .fini_array [FLASH ONLY it appears] = unknown.
    3. .data [IN BOTH FLASH AND SRAM] = NON-zero-initialized (ie: initialized with something other than zero) static and global variables. These values must be copied from Flash to SRAM at startup, to initialize their corresponding static or global variables in SRAM.
  3. [RED] The berkeley-format bss output sections (zero-initialized static and global variables, and also, apparently, uninitialized stack and heap space) are boxed in red.

     .bss + ._user_heap_stack = bss
    
    1. .bss [SRAM ONLY] = zero-initialized static and global variables; this SRAM is set to all zeros at program startup.
    2. ._user_heap_stack [SRAM ONLY] = completely uninitialized (I think) SRAM which is set aside for the runtime stack (for local variables) and heap (for dynamically-allocated memory).
  4. [GREY] Discarded sysv output sections which do not contribute to any of the 3 berkeley output sections are boxed in grey.

enter image description here

Memory conclusions:

  1. Flash memory:
    1. Flash memory usage = berkeley text + berkeley data.
      1. Flash memory used by the ISR Function Vector Table only = .isr_vector.
      2. Flash memory used by the program logic only = .text.
      3. Flash memory used by the Read-Only static and global variables only = .rodata.
  2. SRAM memory:
    1. SRAM usage from static and global variables AND allocated for stack and heap usage = berkeley bss + berkeley data.
      1. SRAM used by static and global variables only = sysv (.bss + .data). Notice the dots (.) before each of these names here, as opposed to the lack of dots above.
      2. SRAM allocated specifically for stack (local variables) and heap (dynamic memory allocation) = (apparently) ._user_heap_stack.
      3. SRAM not allocated for anything = SRAM_total - (berkeley bss + berkeley data).

References:

  1. GNU Linker (ld) manual, section "3.1 Basic Linker Script Concepts": https://sourceware.org/binutils/docs/ld/Basic-Script-Concepts.html#Basic-Script-Concepts:

    3.1 Basic Linker Script Concepts

    We need to define some basic concepts and vocabulary in order to describe the linker script language.

    The linker combines input files into a single output file. The output file and each input file are in a special data format known as an object file format. Each file is called an object file. The output file is often called an executable, but for our purposes we will also call it an object file. Each object file has, among other things, a list of sections. We sometimes refer to a section in an input file as an input section; similarly, a section in the output file is an output section.

    Each section in an object file has a name and a size. Most sections also have an associated block of data, known as the section contents. A section may be marked as loadable, which means that the contents should be loaded into memory when the output file is run. A section with no contents may be allocatable, which means that an area in memory should be set aside, but nothing in particular should be loaded there (in some cases this memory must be zeroed out). A section which is neither loadable nor allocatable typically contains some sort of debugging information.

    Every loadable or allocatable output section has two addresses. The first is the VMA, or virtual memory address. This is the address the section will have when the output file is run. The second is the LMA, or load memory address. This is the address at which the section will be loaded. In most cases the two addresses will be the same. An example of when they might be different is when a data section is loaded into ROM, and then copied into RAM when the program starts up (this technique is often used to initialize global variables in a ROM based system). In this case the ROM address would be the LMA, and the RAM address would be the VMA.

    You can see the sections in an object file by using the objdump program with the ‘-h’ option.

    Every object file also has a list of symbols, known as the symbol table. A symbol may be defined or undefined. Each symbol has a name, and each defined symbol has an address, among other information. If you compile a C or C++ program into an object file, you will get a defined symbol for every defined function and global or static variable. Every undefined function or global variable which is referenced in the input file will become an undefined symbol.

    You can see the symbols in an object file by using the nm program, or by using the objdump program with the ‘-t’ option.

  2. My own answer to my own question here: Convert binutils size output from “sysv” format (size --format=sysv my_executable) to “berkeley” format (size --format=berkeley my_executable)

\$\endgroup\$
1
  • 1
    \$\begingroup\$ .init_array and .fini_array -- global variables in C++ will have their constructor and destructors run before main begins executing and after main exits, respectively. These two sections contain (pointers to) construction and destruction functions that must be invoked by the runtime support library. Pure ISO Standard C has no such thing (you cannot initialize a C global variable with a dynamic expression) but gcc adds it to C as an extension using __attribute((constructor))__ and __attribute__((destructor)). \$\endgroup\$
    – Ben Voigt
    Jun 11, 2021 at 16:06
2
\$\begingroup\$

Script/command to auto-calculate Flash and SRAM usage for you

As an extension of the other two answers, including my own, here is a 1-line Linux bash script you can use to automatically calculate FLASH and SRAM used.

This assumes your command to view the Berkeley size information of your .elf file is arm-none-eabi-size "STM32F103RB_Nucleo.elf". Update that path to your .elf file of interest. If looking at Linux .elf files or regular executable files instead of STM32 .elf files, use size instead of arm-none-eabi-size. Ex: size path/to/my_program.elf.

Anyway, here is the script:

size_info=$(arm-none-eabi-size "STM32F103RB_Nucleo.elf" | awk NR\>1); \
text=$(echo "$size_info" | awk '{print $1}'); \
data=$(echo "$size_info" | awk '{print $2}'); \
bss=$(echo "$size_info" | awk '{print $3}'); \
flash=$(($text + $data)); \
sram=$(($bss + $data)); \
echo "FLASH used                    = $flash bytes"; \
echo "SRAM used by global variables = $sram bytes"

Assuming that arm-none-eabi-size "STM32F103RB_Nucleo.elf" shows this:

text    data     bss     dec     hex filename
2896      12    1588    4496    1190 STM32F103RB_Nucleo.elf

...here is a sample output of running the above script:

FLASH used                    = 2908 bytes
SRAM used by global variables = 1600 bytes

Breakdown of Flash, SRAM, and the 3 Berkely sections

  • Flash and SRAM:
    • FLASH = text + data
    • SRAM = bss + data
  • The 3 Berkeley sections:
    • text = program data + ISR vector + rodata (read-only data: const and constexpr static and global read-only variables)
    • data = NON-zero-initialized (ie: initialized with a number other than zero) static and global variables, + .init_array + .fini_array
    • bss = zero-initialized static and global variables + stack and heap space set aside by the linker script

For more details see my other, very detailed, answer here: How do I find out at compile time how much of an STM32's Flash memory and dynamic memory (SRAM) is used up?

References:

  1. the main answer: How do I find out at compile time how much of an STM32's Flash memory and dynamic memory (SRAM) is used up?
  2. my other anwer: How do I find out at compile time how much of an STM32's Flash memory and dynamic memory (SRAM) is used up?
  3. how to add numbers in bash: https://stackoverflow.com/questions/6348902/how-can-i-add-numbers-in-a-bash-script/6348941#6348941
  4. how to use awk to remove the first line of a text blob: https://superuser.com/questions/284258/remove-first-line-in-bash/284270#284270
  5. general awk example from my own notes to remind me how to use it: du -h | tail -n 1 | awk '{print $1}'
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.