If linking fails, there will be no .map
and no .elf
. The .map
will also not be there unless you explicitly use build_flags
to add it (Generate a .map file - #15 by krishna_chaitanya).
But there’s still some tricks to be used. We can ‘simply’ change the linker file and expand the size of the region dram0_0_seg
so that it forcefully builds, and then take a look at what’s hogging up memory so badly.
After doing a verbose build, we see the final linker command (is huge)…
xtensa-esp32-elf-g++ -o .pio\build\nodemcu-32s\firmware.elf -T esp32_out.ld -nostdlib -Wl,-static -u call_user_start_cpu0 -Wl,–undefined=uxTopUsedPriority -Wl,–gc-sections -Wl,-EL -T esp32.common.ld -T esp32.rom.ld -T esp32.peripherals.ld -T esp32.rom.libgcc.ld -T esp32.rom.spiram_incompatible_fns.ld -u ld_include_panic_highint_hdl -u __cxa_guard_dummy -u __cxx_fatal_exception .pio\build\nodemcu-32s\src\dbuddy.cpp.o .pio\build\nodemcu-32s\src\fonts\roboto_black_16.c.o .pio\build\nodemcu-32s\src\fonts\roboto_black_24.c.o .pio\build\nodemcu-32s\src\fonts\roboto_black_72.c.o .pio\build\nodemcu-32s\src\fonts\roboto_regular_12.c.o .pio\build\nodemcu-32s\src\fonts\roboto_regular_14.c.o .pio\build\nodemcu-32s\src\fonts\roboto_regular_16.c.o .pio\build\nodemcu-32s\src\fonts\roboto_regular_18.c.o .pio\build\nodemcu-32s\src\main.cpp.o .pio\build\nodemcu-32s\src\ui.cpp.o -L.pio\build\nodemcu-32s -LC:\Users\Max.platformio\packages\framework-arduinoespressif32\tools\sdk\lib -LC:\Users\Max.platformio\packages\framework-arduinoespressif32\tools\sdk\ld -Wl,–start-group .pio\build\nodemcu-32s\lib43a\libEEPROM.a .pio\build\nodemcu-32s\lib274\libSPI.a .pio\build\nodemcu-32s\lib816\libWire.a “.pio\build\nodemcu-32s\lib2c5\libAdafruit BusIO.a” “.pio\build\nodemcu-32s\lib9cb\libAdafruit GFX Library.a” “.pio\build\nodemcu-32s\lib85a\libAdafruit RA8875.a” .pio\build\nodemcu-32s\libaa1\libBLE.a .pio\build\nodemcu-32s\lib3f9\liblvgl.a .pio\build\nodemcu-32s\libFrameworkArduinoVariant.a .pio\build\nodemcu-32s\libFrameworkArduino.a -lgcc -lesp32 -lphy -lesp_http_client -lmbedtls -lrtc -lesp_http_server -lbtdm_app -lspiffs -lbootloader_support -lmdns -lnvs_flash -lfatfs -lpp -lnet80211 -ljsmn -lface_detection -llibsodium -lvfs -ldl_lib -llog -lfreertos -lcxx -lsmartconfig_ack -lxtensa-debug-module -lheap -ltcpip_adapter -lmqtt -lulp -lfd -lfb_gfx -lnghttp -lprotocomm -lsmartconfig -lm -lethernet -limage_util -lc_nano -lsoc -ltcp_transport -lc -lmicro-ecc -lface_recognition -ljson -lwpa_supplicant -lmesh -lesp_https_ota -lwpa2 -lexpat -llwip -lwear_levelling -lapp_update -ldriver -lbt -lespnow -lcoap -lasio -lnewlib -lconsole -lapp_trace -lesp32-camera -lhal -lprotobuf-c -lsdmmc -lcore -lpthread -lcoexist -lfreemodbus -lspi_flash -lesp-tls -lwpa -lwifi_provisioning -lwps -lesp_adc_cal -lesp_event -lopenssl -lesp_ringbuf -lfr -lstdc++ -Wl,–end-group
But we know it’s linked against esp32_out.ld
. That is in the framework. So we open up the file C:\Users\<user>\.platformio\packages\framework-arduinoespressif32\tools\sdk\ld\esp32_out.ld
and see…
that is ~124K available. See comment above on why that is so low.
Anyways, we change the len
of that to something we know that fits (old size plus overflowed-by plus a little extra)
dram0_0_seg (RW) : org = 0x3FFB0000 + 0xdb5c,
len = 289888
And we relink the binary by pressing “Build” again…
Linking .pio\build\nodemcu-32s\firmware.elf
Retrieving maximum program size .pio\build\nodemcu-32s\firmware.elf
Building .pio\build\nodemcu-32s\firmware.bin
Checking size .pio\build\nodemcu-32s\firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM: [========= ] 85.9% (used 281332 bytes from 327680 bytes)
Flash: [===== ] 49.2% (used 644605 bytes from 1310720 bytes)
esptool.py v3.0
======================= [SUCCESS] Took 8.44 seconds =======================
and that was “ok” and we have the ELF now.
Next for analysis, I’ll be cloning GitHub - ARMmbed/mbed-os-linker-report: Post-processing of linker output to calculate and visualize memory usage for elf-sections, but that change
to the absolute path where my xtensa-esp32-elf-nm
is (C:\Users\Max\.platformio\packages\toolchain-xtensa32\bin\xtensa-esp32-elf-nm
). The thing was originally written for ARM. So I’ll just let it run on the .elf
file…
C:\Users\Max\mbed-os-linker-report>elfsize.py -i C:\Users\Max\Documents\PlatformIO\Projects\dbuddy\.pio\build\nodemcu-32s\firmware.elf -b
C:\Users\Max\.platformio\packages\toolchain-xtensa32\bin\xtensa-esp32-elf-nm -l -S -C -f sysv C:\Users\Max\Documents\PlatformIO\Projects\dbuddy\.pio\build\nodemcu-32s\firmware.elf
[INFO] data written to C:\Users\Max\mbed-os-linker-report\html\data-flare.js
[INFO] opening in browser file://C:\Users\Max\mbed-os-linker-report\index.html
and we get nice graphs that show how memory is being used in flash (code, constant variables, …) and ram (bss, global initialized but modifyable variables, etc.)
Clicking on the .dram0.bss
section shows that mainly a single symbol is eating all memory…
A symbol called work_mem_int
. Now where does that come from?
C:\Users\Max\Documents\PlatformIO\Projects\dbuddy>grep -r "work_mem_int" .
..
./.pio/libdeps/nodemcu-32s/lvgl/src/lv_misc/lv_mem.c: static LV_MEM_ATTR MEM_UNIT work_mem_int[LV_MEM_SIZE / sizeof(MEM_UNIT)];
./.pio/libdeps/nodemcu-32s/lvgl/src/lv_misc/lv_mem.c: work_mem = (uint8_t *)work_mem_int;
aha. The LVGL library is allocating a unified working buffer to do everything.
static LV_MEM_ATTR MEM_UNIT work_mem_int[LV_MEM_SIZE / sizeof(MEM_UNIT)];
and that thing is 256 kilobytes in its current config!! Much too large than what fits in the bss section for the ESP32 when using the Arduino-ESP32 linker script (and they probably put a lot of thought into that).
Now let’s modify what you have written in your config file…
to set that to (64*1024) and also revert the linker file hacks in esp32_out.ld
and build again…
and it builds. Can LVGL work with that buffer space? No idea. But now you know why it doesn’t build.
Hint: The comment in the linker file section talks about how more memory may be available in the heap. So if static allocation in RAM doesn’t work, try LV_MEM_CUSTOM
and do a heap alloc.
Even better, on a board with PSRAM, you can have a large buffer size allocated in PSRAM, if memory really is not enough in either stack or heap in the internal memory.