Linkage error when using gmock for a test with an esp8266 (d1_mini)

After a longer sabbatical :wink: I’ve started again working on a small library. After the necessary updates nearly everything worked as fine as before - with one weird exception. Running the tests for a d1_mini-board aborts with a linkage error

~/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/10.3.0/../../../../xtensa-lx106-elf/bin/ld: .pio/build/d1_mini/test/Mock_Adafruit_GFX.cpp.o:(.text._ZN7testing8internal17SharedPayloadBase3RefEv[_ZN7testing8internal17SharedPayloadBase3RefEv]+0x0): undefined reference to `__atomic_fetch_add_4'
~/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/10.3.0/../../../../xtensa-lx106-elf/bin/ld: .pio/build/d1_mini/test/Mock_Adafruit_GFX.cpp.o:(.text._ZN7testing8internal17SharedPayloadBase5UnrefEv[_ZN7testing8internal17SharedPayloadBase5UnrefEv]+0x0): undefined reference to `__atomic_fetch_sub_4'
~/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/10.3.0/../../../../xtensa-lx106-elf/bin/ld: .pio/build/d1_mini/test/Mock_Adafruit_GFX.cpp.o: in function `_ZN7testing8internal17SharedPayloadBase5UnrefEv':
Mock_Adafruit_GFX.cpp:(.text._ZN7testing8internal17SharedPayloadBase5UnrefEv[_ZN7testing8internal17SharedPayloadBase5UnrefEv]+0xe): undefined reference to `__atomic_fetch_sub_4'

The test is using gmock for mocking Adafruit_GFX resp. XPT2046_Touchscreen. nm shows me that the object-files containing those missing references are the two mock-classes and the test-class

xtensa-lx106-elf-nm -l .pio/build/d1_mini/test/*.o |grep atomic
         U __atomic_fetch_add_4
         U __atomic_fetch_sub_4 Mock_Adafruit_GFX.cpp:0
         U __atomic_fetch_add_4 Mock_XPT2046_Touchscreen.cpp:0
         U __atomic_fetch_sub_4 Mock_XPT2046_Touchscreen.cpp:0
         U __atomic_fetch_add_4
         U __atomic_fetch_sub_4 test_ete.cpp:0

Running the same tests for an esp32 works fine.

My platformio.ini looks like that

[platformio]
default_envs = 
    esp32dev
    d1_mini

[env]
framework = arduino
test_framework = googletest
lib_deps =
    Wire
    googletest
    Adafruit GFX Library
    XPT2046_Touchscreen
    Adafruit BusIO
test_build_src = true


[env:esp32dev]
platform = espressif32
board = esp32dev
lib_ignore = ArduinoFake
test_port=/dev/ttyUSB0
test_speed=921600
monitor_port = /dev/ttyUSB0
monitor_speed=921600


[env:d1_mini]
platform = espressif8266
board = d1_mini
lib_ignore = ArduinoFake
test_speed=460800
test_port=/dev/ttyUSB0
monitor_speed=460800
monitor_port = /dev/ttyUSB0
build_flags =
    -D PIO_FRAMEWORK_ARDUINO_MMU_CUSTOM
    -D MMU_IRAM_SIZE=0xC000
    -D MMU_ICACHE_SIZE=0x4000
    -D MMU_IRAM_HEAP

Any idea what’s going wrong?

Hi altogether,

in the meantime I’ve found some kind of workaround - like all workarounds it works but it isn’t satisfying :thinking: - I’ve downgraded googletest to 1.10.0. This was the version I’ve used before.

Unfortunately upgrading to 1.14.0 shows the same behavior as with 1.12.1 - dead end :unamused:

Hi altogether,

this strange phenomenon is nearly driving me nuts :roll_eyes:

In the meantime I wrote a minimalistic reproducer consisting of a simple class with one method, the corresponding mock and a test class using the mock. You can find it here. Here I get exact the same error - so far so good.

But now begins the fun.

When compiling googletest / googlemock itself linking succeeds. Looking with nm at the object files shows me that some of them also contain the offending external references.

It would now be logical that the linker commands differ

  • linking googlemock
xtensa-lx106-elf-g++
 -o .pio/build/googlemock_esp8266/firmware.elf
 -T eagle.flash.4m1m.ld
 -Os
 -nostdlib
 -Wl,--no-check-sections
 -Wl,-static
 -Wl,--gc-sections
 -Wl,-wrap,system_restart_local
 -Wl,-wrap,spi_flash_read
 -u app_entry
 -u _printf_float
 -u _scanf_float
 -u _DebugExceptionVector
 -u _DoubleExceptionVector
 -u _KernelExceptionVector
 -u _NMIExceptionVector
 -u _UserExceptionVector .pio/build/googlemock_esp8266/src/googlemock/src/gmock-all.cc.o .pio/build/googlemock_esp8266/src/googlemock/src/gmock_main.cc.o .pio/build/googlemock_esp8266/src/googletest/src/gtest-all.cc.o
 -L.pio/build/googlemock_esp8266
 -L.pio/build/googlemock_esp8266/ld
 -L~/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lib
 -L~/.platformio/packages/framework-arduinoespressif8266/tools/sdk/ld
 -L~/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lib/NONOSDK22x_190703
 -Wl,--start-group .pio/build/googlemock_esp8266/libFrameworkArduinoVariant.a .pio/build/googlemock_esp8266/libFrameworkArduino.a
 -lhal
 -lphy
 -lpp
 -lnet80211
 -llwip2-536-feat
 -lwpa
 -lcrypto
 -lmain
 -lwps
 -lbearssl
 -lespnow
 -lsmartconfig
 -lairkiss
 -lwpa2
 -lstdc++
 -lm
 -lc
 -lgcc
 -Wl,--end-group
  • linking reproducer
xtensa-lx106-elf-g++
 -o .pio/build/d1_mini/firmware.elf
 -T eagle.flash.4m1m.ld
 -Os
 -nostdlib
 -Wl,--no-check-sections
 -Wl,-static
 -Wl,--gc-sections
 -Wl,-wrap,system_restart_local
 -Wl,-wrap,spi_flash_read
 -u app_entry
 -u _printf_float
 -u _scanf_float
 -u _DebugExceptionVector
 -u _DoubleExceptionVector
 -u _KernelExceptionVector
 -u _NMIExceptionVector
 -u _UserExceptionVector .pio/build/d1_mini/test/Mock_Bar.cpp.o .pio/build/d1_mini/test/test_atomic.cpp.o
 -L.pio/build/d1_mini
 -L.pio/build/d1_mini/ld
 -L~/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lib
 -L~/.platformio/packages/framework-arduinoespressif8266/tools/sdk/ld
 -L~/.platformio/packages/framework-arduinoespressif8266/tools/sdk/lib/NONOSDK22x_190703
 -Wl,--start-group .pio/build/d1_mini/libff2/libgoogletest.a .pio/build/d1_mini/libFrameworkArduinoVariant.a .pio/build/d1_mini/libFrameworkArduino.a
 -lhal
 -lphy
 -lpp
 -lnet80211
 -llwip2-536-feat
 -lwpa
 -lcrypto
 -lmain
 -lwps
 -lbearssl
 -lespnow
 -lsmartconfig
 -lairkiss
 -lwpa2
 -lstdc++
 -lm
 -lc
 -lgcc
 -Wl,--end-group

A diff shows no obvious differences (with the exception of the expected ones due to the different file names :wink:)

diff ld ~/source/arduino/issues/atomic/
2c2
<  -o .pio/build/googlemock_esp8266/firmware.elf
---
>  -o .pio/build/d1_mini/firmware.elf
18,20c18,20
<  -u _UserExceptionVector .pio/build/googlemock_esp8266/src/googlemock/src/gmock-all.cc.o .pio/build/googlemock_esp8266/src/googlemock/src/gmock_main.cc.o .pio/build/googlemock_esp8266/src/googletest/src/gtest-all.cc.o
<  -L.pio/build/googlemock_esp8266
<  -L.pio/build/googlemock_esp8266/ld
---
>  -u _UserExceptionVector .pio/build/d1_mini/test/Mock_Bar.cpp.o .pio/build/d1_mini/test/test_atomic.cpp.o
>  -L.pio/build/d1_mini
>  -L.pio/build/d1_mini/ld
24c24
<  -Wl,--start-group .pio/build/googlemock_esp8266/libFrameworkArduinoVariant.a .pio/build/googlemock_esp8266/libFrameworkArduino.a
---
>  -Wl,--start-group .pio/build/d1_mini/libff2/libgoogletest.a .pio/build/d1_mini/libFrameworkArduinoVariant.a .pio/build/d1_mini/libFrameworkArduino.a

The only remaining difference I see is that the google project was compiled in production mode (pio run), while the reproducer was compiled in test mode (pio test). But that shouldn’t explain a different behavior of the linker, right?

Fun fact 2 - if I copy Mock_Bar.cpp.o into the google build folder and execute a modified linker command with this additional object file, linking succeeds too, the resulting firmware.elf is a little bit larger and nm shows additional entries obviously coming from the additional object file.

Any ideas or proposals what’s going wrong?