[Arduino] View code size and assembly code

While developing ArduinoJson, I’ve always been obsessed with code size. Arduinos have such a small amount of Flash (32KB for a Duemilanove) that every byte is important.

Here are two techniques that I use. I tested them on Windows for the AVR platform, they can probably be adapted to other platforms.

We’ll use two tools from the AVR tool chain: avr-nm and avr-objdump, they are both provided along with the Arduino IDE.

Finding the .elf file

So let’s assume that you wrote an Arduino sketch, and you want to optimize the size.

Once you compiled it, the first thing you need to do is find the compiled binary file. The easiest way is to “Enable verbose output during compilation” and look for the path to the .elf file at the end of the log.

Enable verbose output during compilation

Look for the .elf file

View code size with avr-nm

When optimizing the size of a program, it’s very important to focus your effort on the right place. Using avr-nm, you’ll know the size of each function.

Open a command prompt and type:

set PATH=%PATH%;C:\Program Files (x86)\Arduino\hardware\tools\avr\bin\
avr-nm --size-sort -C -r "<path-to-the-elf-file>"

The size of all functions will be printed:

00000094 T __vector_16
00000076 T init
00000072 T pinMode
0000006c T digitalWrite
00000052 t turnOffPWM
00000050 T delay
00000046 T micros
00000028 T loop
0000001e T main
00000014 T digital_pin_to_timer_PGM
00000014 T digital_pin_to_port_PGM
00000014 T digital_pin_to_bit_mask_PGM
00000010 T __do_clear_bss
0000000a T port_to_output_PGM
0000000a T port_to_mode_PGM
00000008 T setup
00000004 B timer0_overflow_count
00000004 B timer0_millis
00000002 W yield
00000002 W initVariant
00000002 t __empty
00000001 b timer0_fract

Functions are printed with the biggest on top of the list and the size is shown in hexadecimal.

View assembly with avr-objdump

When avr-nm is not enough, you have no choice but use the biggest weapon: the disassembler. With avr-objdump you can extract the assembly code from the .elf file.

In a command prompt, type:

set PATH=%PATH%;C:\Program Files (x86)\Arduino\hardware\tools\avr\bin\
avr-objdump -C -d "<path-to-the-elf-file>"

The assembly code of the whole program will be printed. Here it what it shows for the loop() function of the Blink example:

000000e8 <loop>:
  e8:   61 e0           ldi     r22, 0x01       ; 1
  ea:   8d e0           ldi     r24, 0x0D       ; 13
  ec:   0e 94 ba 01     call    0x374   ; 0x374 <digitalWrite>
  f0:   68 ee           ldi     r22, 0xE8       ; 232
  f2:   73 e0           ldi     r23, 0x03       ; 3
  f4:   80 e0           ldi     r24, 0x00       ; 0
  f6:   90 e0           ldi     r25, 0x00       ; 0
  f8:   0e 94 f5 00     call    0x1ea   ; 0x1ea <delay>
  fc:   60 e0           ldi     r22, 0x00       ; 0
  fe:   8d e0           ldi     r24, 0x0D       ; 13
 100:   0e 94 ba 01     call    0x374   ; 0x374 <digitalWrite>
 104:   68 ee           ldi     r22, 0xE8       ; 232
 106:   73 e0           ldi     r23, 0x03       ; 3
 108:   80 e0           ldi     r24, 0x00       ; 0
 10a:   90 e0           ldi     r25, 0x00       ; 0
 10c:   0c 94 f5 00     jmp     0x1ea   ; 0x1ea <delay>

We can see the calls to digitalWrite() and delay() from the Blink example.

EDIT: as a redditor pointed out, Arduino-Makefile provides commands like make symbol_sizes and make generate_assembly for that.