4

When compiling a sketch, after linking, the build outputs a kind of forecast on RAM usage, e.g.:

Minimum Memory Usage: 1456 bytes (71% of a 2048 byte maximum)

Is it possible to somehow get a breakdown or itemization of these 1456 bytes and details on exactly where in my code, or in included libraries, they are used?

7

Since you seem interested about RAM, rather than flash usage, I suggest you look at the symbol table of your compiled program, which will be more telling than the disassembly. On my Linux system, I usually do the following:

avr-nm -Crtd --size-sort the_program.elf | grep -i ' [dbv] '

avr-nm is the utility for displaying the symbol table. It is normally packaged with the Arduino IDE. It is invoked here with the following options:

  • --size-sort is quite explicit
  • -C means “demangle the C++ names”
  • -r means “reverse sort”, i.e. sort from largest to smallest
  • -td means “display numbers in decimal rather than hexadecimal”

This will output the list of all symbols that have a size, in three columns: size, type and name. You are only interested in the symbols that consume RAM, i.e. those of type b (BSS, or uninitialized data), d (initialized data) and v (vtable), either in upper or lower case. The grep command is a standard Unix utility which is used here to extract only the relevant lines from the output of avr-nm.

Example output for a small program:

00000068 B tx_buffer
00000068 B rx_buffer
00000034 B Serial
00000016 V vtable for HardwareSerial
00000004 B timer0_overflow_count
00000004 B timer0_millis
00000002 b loop::last_print
00000001 b timer0_fract

The first 4 entries come from the Serial object. The 3 entries starting with timer0_ are used by the Arduino timekeeping functions (millis(), micros() and delay()). The entry named loop::last_print is a static local variable I declared in loop().

  • That looks promising, but I will have to wait until tonight to try it. Will it only list the 'local' name of the variables? I suppose that e.g. the tx_buffer and rx_buffer variables listed in your example are actually HardwareSerial::_tx_buffer and HardwareSerial::_rx_buffer? Might omitting the -C option give any hint on the variable's class name? – jarnbjo Nov 14 '16 at 10:23
  • @jarnbjo: You get the full name of the variable, e.g. loop::last_print is a local variable from loop() named last_print. The serial buffers are actually outside HardwareSerial in my version of the Arduino core, and HardwareSerial has no static variables, so you see no HardwareSerial::foo. The per-instance variables are part of the sole instance Serial and are not listed separately. Omitting -C will only hurt readability, e.g. it will print _ZZ4loopE10last_print instead of loop::last_print. – Edgar Bonet Nov 14 '16 at 11:13
  • I see. The fields in the HardwareSerial class were the closest match I could find for tx_buffer and rx_buffer in the current version of the library source code and I didn't consider that you might have an older library version. That makes sense. – jarnbjo Nov 14 '16 at 13:41
  • This solved my problem. I had expected the compiler optimizer to be much more clever than it obviously is and I had lots of dependencies linked into the binary, which are never used. Without much more than deleting unused library files saved me almost 500 bytes of RAM usage. – jarnbjo Nov 14 '16 at 23:56
1

Looking at an assembly listing of your code is the most direct method for finding out details of space allocation. On my Ubuntu Linux system, I use a shell script (that is, a file containing shell commands) like the following to produce an assembly listing of the most-recently-compiled .ino file, which must be in the working directory when the script executes:

#!/bin/sh
# Look for .ino in current directory, and find recent /tmp/build...
item=*.ino
[ -z "$item" ] && echo ino file not found && exit
BDIR=/tmp/$(ls -t /tmp | egrep -m1 build.*tmp)
BASE=$(basename $item .ino)
avr-objdump -S -I$PWD $BDIR/$BASE.cpp.elf > $BASE.ino.asm

All the stuff before the avr-objdump line allows the script to find out the name of the current .ino file and the name of the /tmp/ subdirectory where the .ino's .elf file is found.

You can also use avr-objdump to get a more concise size listing, as follows, where $BDIR and $BASE are as in the script above and represent the build directory in /tmp/, and the basename of the .ino file.

avr-objdump -C -d $BDIR/$BASE.cpp.o| egrep -C2 '^Disassembly' | egrep -v '^--|^$|^Disassembly' | less

This will produce a list with two-line entries like the following, in which each line beginning with 00000000 is a subroutine name, and the next line shows the last line of its compiled code. In this example output, the setup_PowerDown() routine has 0xA4 bytes of code, ISR __vector_13 has 0xC6 bytes, keepAlive() has 0x10 bytes, etc.

00000000 <setup_PowerDown()>:
  a4:   08 95           ret
00000000 <__vector_13>:
  c6:   18 95           reti
00000000 <keepAlive()>:
  10:   08 95           ret
00000000 <eeWrite1(unsigned char, unsigned char)>:
  3a:   08 95           ret
00000000 <eeWrite2(unsigned char, unsigned char)>:
  18:   0c 94 00 00     jmp     0       ; 0x0 <eeWrite2(unsigned char, unsigned char)>
00000000 <parValidate(unsigned char, unsigned char, unsigned char)>:
  64:   08 95           ret
00000000 <loadSettings()>:
  e0:   08 95           ret
00000000 <applySettings()>:
  84:   08 95           ret

Some IDEs provide symbol table information that includes sizes of routines, stack frames, and data. I don't know if that's available via Arduino IDE, AVR-libc, or GNU Binutils. The symbol table provided by avr-readelf -s (as in following command) provides quite detailed symbol information that seems quite useless in its raw form.

avr-readelf  -s  $BDIR/$BASE.cpp.o

The methods described above are somewhat ad hoc, and depend on files left after avr-gcc or avr-g++ run. I don't know of any “official” methods for getting the information you want to see.

1

You can automatically generate a "map" file every time you compile. This gives you a full breakdown of how everything is linked together by the linker, including the size of each section and other useful debugging information.

A simple modification to platforms.txt in hardware/arduino/avr is all that is needed. Find the line starting compiler.c.elf.flags= and add to the end of it:

 -Wl,-Map,"{build.path}/{build.project_name}.map"

If you like you can replace {build.path} with a hard-coded path, such as your desktop, and the .map with .txt to allow it to easily be opened by notepad or whatever you use (assuming Windows, of course).

  • Also very helpful, but do you happen to know why the avr-nm tool suggested by Edgar in his answer lists memory usage, which is not accounted for in this generated map. avr-nm e.g. lists 64 bytes used by an rxBuffer (full name _ZL8rxBuffer), which is not listed in the map, and which I am not able to find in any dependency. – jarnbjo Nov 15 '16 at 0:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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