Can the compiler be set to output the Assembly that would be used?

I want to try and see what some code is doing at the assembly level but I am unsure on how to get Platform IO to output an Assembly file.

The platform I am compiling for is the ESP32.

Any suggestions are welcome.

1 Like

Disassembling the whole firmware will result in a ton of assembly, but you can still do it. PIO builds you the firmware.elf (which has some symbol information in it). So you can use the xtensa-esp32-elf-objdump program of your C:\Users\<user>\.platformio\packages\toolchain-xtensa32\bin package to disassemble that, or one specific file.

E.g. main.cpp

#include <Arduino.h>

int do_some_computations(int a, int b, int c) {
	return a*b+c;
}

void setup() {
	Serial.begin(115200);
	Serial.println("Output: " + String(do_some_computations(1,2,3)));
}

void loop() { }

Then build it

>pio run 

Linking .pio\build\esp32\firmware.elf
Building .pio\build\esp32\firmware.bin
Retrieving maximum program size .pio\build\esp32\firmware.elf
Checking size .pio\build\esp32\firmware.elf
esptool.py v2.6
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
DATA:    [          ]   4.7% (used 15436 bytes from 327680 bytes)
PROGRAM: [==        ]  16.6% (used 217693 bytes from 1310720 bytes)
============================================= [SUCCESS] Took 16.54 seconds =============================================

And disassemble it…

C:\Users\Maxi\Documents\stackoverflow_testing>"C:\Users\Maxi\.platformio\packages\toolchain-xtensa32\bin\xtensa-esp32-elf-objdump.exe" -C -d .pio\build\esp32\firmware.elf

.pio\build\esp32\firmware.elf:     file format elf32-xtensa-le
[...]

You can selectively disassemble one object file (but then it isn’t aware of the other object files yet before linking)

C:\Users\Maxi\Documents\stackoverflow_testing>"C:\Users\Maxi\.platformio\packages\toolchain-xtensa32\bin\xtensa-esp32-elf-objdump.exe" -C -d .pio\build\esp32\src\main.cpp.o

[..]
Disassembly of section .text._Z20do_some_computationsiii:

00000000 <do_some_computations(int, int, int)>:
   0:   004136          entry   a1, 32
   3:   822230          mull    a2, a2, a3
   6:   224a            add.n   a2, a2, a4
   8:   f01d            retw.n

So you can see here how return a*b+c; is implemented via first multiplying the first and second argument register (a2, a3) into the second argument register, to which then the third argument register a4 is added. a1 is the stackpointer and a2 doubles as one return register (see The ESP8266 and ABI Interface - Linux/Xtensa, XTensa reference)

You can also add the GCC option

build_flags = -S 

to the platformio.ini (docs), which will make GCC output the assembly. So now all the .pio\build\esp32\*.o files will actually be assembler output files

So if I e.g. change the function to return a*b+3; recompile and read the file I get

C:\Users\Maxi\Documents\stackoverflow_testing>cat .pio\build\esp32\src\main.cpp.o
        .file   "main.cpp"
        .text
.Ltext0:
        .section        .text._Z20do_some_computationsiii,"ax",@progbits
        .align  4
        .global _Z20do_some_computationsiii
        .type   _Z20do_some_computationsiii, @function
_Z20do_some_computationsiii:
.LFB2430:
        .file 1 "src\\main.cpp"
        .loc 1 3 0
.LVL0:
        entry   sp, 32
.LCFI0:
        .loc 1 4 0
        mull    a2, a2, a3
.LVL1:
        .loc 1 5 0
        addi.n  a2, a2, 3
        retw.n

So now it used mull and addi with a constant 3.

4 Likes

This is my platformio.ini which gets me the avr assembly code files

[env:nanoatmega328]
platform = atmelavr
board = nanoatmega328
framework = arduino

; keep asm .s, .i and .ii files in source dir (need to clean manually)
build_flags = 
   -save-temps
   -fverbose-asm

; Serial Monitor options
monitor_speed = 115200
1 Like

Thanks for the contribution @schaloule, but the OP was after:

as opposed to your AVR specific answer. I’m sure this may come in handy though if someone is after AVR info and comes across this thread though! :wink:

I’d like to say thanks very much to @schaloule, those flags work splendidly for ESP32 as well. I would like to add a suggestion for others visiting here… which is to use the obj option on save-temps (then the temporary files don’t go into the top level folder)…

build_flags = 
   -save-temps=obj
   -fverbose-asm

The obj option is documented here.

so is there anyway to automatically generate a disassembly file after build using objdump tool?

I haven’t needed to do this sort of thing myself, but this link might help:

https://docs.platformio.org/en/latest/projectconf/sections/env/options/advanced/extra_scripts.html

Cheers,
Norm.

i will give it a try,thanks~

Until a few days ago this was working well. By using:

[env:ATmega328P]
platform = atmelavr
board = ATmega328P
#framework = arduino
build_flags =
   -save-temps=obj
   -fverbose-asm

at least two files containing the disassembly listing identical to the listings that could be manually generated by objdump were generated when running build but now .o, .s and .ii files are completely different and do not contain the disassembly listings anymore.

I tried adding -S flag:

build_flags =
   -S
   -save-temps=obj
   -fverbose-asm

but then ld.exe reports this error:

main.o: file format not recognized; treating as linker script

Until a few days ago some of autogenerated files contained the interrupt vectors, main, -exit, __stop_program and commented assembly as generated with:

avr-objdump.exe -S firmware.elf

and

avr-objdump.exe -SD firmware.elf

but not anymore. Seems as the change was caused by some recent update.