WindhoverLabs / airliner

7 stars 3 forks source link

Build system for unit tests relies on undefined linking behaviour #335

Open Baltoli opened 1 year ago

Baltoli commented 1 year ago

The Airliner unit test system appears to link multiple copies of the same object file when building tests, relying on the linker to silently discard one of the copies. I believe doing so is undefined behaviour, as multiple definitions of the symbols in that object are given. [^1]

Consider an example from the mm-ut unit tests; in particular, the file mm_app_test.c. The file is compiled once:

Building C object target/apps/mm/CMakeFiles/mm-ut_no_symtab.dir/__/unit_test/mm_app_test.c.o
cd /airliner/build/bebop2/kcc/target/target/apps/mm && kcc  -I/airliner/apps/mm/fsw/for_build/../src -I/airliner/apps/mm/fsw/for_build/../unit_test -I/airliner/core/base/cfe/fsw/src/inc -I/airliner/core/base/cfe/fsw/src/inc/private -I/airliner/core/base/cfe/fsw/src/es -I/airliner/core/base/cfe/fsw/src/evs -I/airliner/core/base/cfe/fsw/src/fs -I/airliner/core/base/cfe/fsw/src/sb -I/airliner/core/base/cfe/fsw/src/tbl -I/airliner/core/base/cfe/fsw/src/time -I/airliner/core/base/osal/inc -I/airliner/core/base/psp/make/../fsw/inc -I/airliner/core/psp/kcc/make/../inc -I/airliner/config/bebop2/kcc/target/inc -I/airliner/config/bebop2/kcc/target/../inc -I/airliner/config/shared/inc -I/airliner/apps/cfs_lib/fsw/for_build/../public_inc -I/airliner/apps/px4lib/fsw/for_build/../public_inc -I/airliner/apps/prmlib/fsw/for_build/../public_inc -I/airliner/apps/io_lib/fsw/for_build/../public_inc -I/airliner/apps/sim/fsw/for_build/../public_inc -I/airliner/core/base/ut_assert/inc  -g -O2 -Wno-pointer-to-int-cast -D__signed__=signed -Xclang='-fPIE' -DOSAPI_NO_SPECIAL_ATTRIBS -D_POSIX_SOURCE -D_DEFAULT_SOURCE -D'__pragma(args)=_Pragma(#args)' -Wno-object-to-function -Wno-function-to-object   -g -o CMakeFiles/mm-ut_no_symtab.dir/__/unit_test/mm_app_test.c.o   -c /airliner/apps/mm/fsw/unit_test/mm_app_test.c

then subsequently linked into a static archive libmm-ut_no_symtab.a:

/usr/bin/ar qc libmm-ut_no_symtab.a  CMakeFiles/mm-ut_no_symtab.dir/__/unit_test/mm_app_test.c.o CMakeFiles/mm-ut_no_symtab.dir/__/unit_test/mm_dump_test.c.o CMakeFiles/mm-ut_no_symtab.dir/__/unit_test/mm_load_test.c.o CMakeFiles/mm-ut_no_symtab.dir/__/unit_test/mm_mem16_test.c.o CMakeFiles/mm-ut_no_symtab.dir/__/unit_test/mm_mem32_test.c.o CMakeFiles/mm-ut_no_symtab.dir/__/unit_test/mm_mem8_test.c.o CMakeFiles/mm-ut_no_symtab.dir/__/unit_test/mm_testrunner.c.o CMakeFiles/mm-ut_no_symtab.dir/__/unit_test/mm_test_utils.c.o CMakeFiles/mm-ut_no_symtab.dir/__/unit_test/mm_utils_test.c.o CMakeFiles/mm-ut_no_symtab.dir/__/src/mm_app.c.o CMakeFiles/mm-ut_no_symtab.dir/__/src/mm_dump.c.o CMakeFiles/mm-ut_no_symtab.dir/__/src/mm_load.c.o CMakeFiles/mm-ut_no_symtab.dir/__/src/mm_mem16.c.o CMakeFiles/mm-ut_no_symtab.dir/__/src/mm_mem32.c.o CMakeFiles/mm-ut_no_symtab.dir/__/src/mm_mem8.c.o CMakeFiles/mm-ut_no_symtab.dir/__/src/mm_utils.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/cfs_lib/fsw/src/cfs_utils.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/utassert.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_es_hooks.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_es_stubs.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_evs_hooks.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_evs_stubs.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_fs_stubs.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_psp_eeprom_stubs.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_psp_memrange_stubs.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_psp_memutils_stubs.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_psp_ram_stubs.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_psp_timer_stubs.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_psp_watchdog_stubs.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_sb_hooks.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_sb_stubs.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_tbl_hooks.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_tbl_stubs.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_time_hooks.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_time_stubs.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/utlist.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/ut_osapi_stubs.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/ut_osfileapi_stubs.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/uttest.c.o CMakeFiles/mm-ut_no_symtab.dir/__/__/__/__/core/base/ut_assert/src/uttools.c.o

The file is then rebuilt with the only difference being the -Dmm_ut_EXPORTS flag:

Building C object target/apps/mm/CMakeFiles/mm-ut.dir/__/unit_test/mm_app_test.c.o
cd /airliner/build/bebop2/kcc/target/target/apps/mm && kcc -Dmm_ut_EXPORTS -I/airliner/apps/mm/fsw/for_build/../src -I/airliner/apps/mm/fsw/for_build/../unit_test -I/airliner/core/base/cfe/fsw/src/inc -I/airliner/core/base/cfe/fsw/src/inc/private -I/airliner/core/base/cfe/fsw/src/es -I/airliner/core/base/cfe/fsw/src/evs -I/airliner/core/base/cfe/fsw/src/fs -I/airliner/core/base/cfe/fsw/src/sb -I/airliner/core/base/cfe/fsw/src/tbl -I/airliner/core/base/cfe/fsw/src/time -I/airliner/core/base/osal/inc -I/airliner/core/base/psp/make/../fsw/inc -I/airliner/core/psp/kcc/make/../inc -I/airliner/config/bebop2/kcc/target/inc -I/airliner/config/bebop2/kcc/target/../inc -I/airliner/config/shared/inc -I/airliner/apps/cfs_lib/fsw/for_build/../public_inc -I/airliner/apps/px4lib/fsw/for_build/../public_inc -I/airliner/apps/prmlib/fsw/for_build/../public_inc -I/airliner/apps/io_lib/fsw/for_build/../public_inc -I/airliner/apps/sim/fsw/for_build/../public_inc -I/airliner/core/base/ut_assert/inc  -g -O2 -Wno-pointer-to-int-cast -D__signed__=signed -Xclang='-fPIE' -DOSAPI_NO_SPECIAL_ATTRIBS -D_POSIX_SOURCE -D_DEFAULT_SOURCE -D'__pragma(args)=_Pragma(#args)' -Wno-object-to-function -Wno-function-to-object   -g -o CMakeFiles/mm-ut.dir/__/unit_test/mm_app_test.c.o   -c /airliner/apps/mm/fsw/unit_test/mm_app_test.c

Finally, the two different objects are compiled into the mm-ut executable:

kcc -g -O2 -Wno-pointer-to-int-cast -D__signed__=signed -Xclang='-fPIE' -DOSAPI_NO_SPECIAL_ATTRIBS -D_POSIX_SOURCE -D_DEFAULT_SOURCE -D'__pragma(args)=_Pragma(#args)' -Wno-object-to-function -Wno-function-to-object  -Wl,--export-dynamic -rdynamic CMakeFiles/mm-ut.dir/__/unit_test/mm_app_test.c.o CMakeFiles/mm-ut.dir/__/unit_test/mm_dump_test.c.o CMakeFiles/mm-ut.dir/__/unit_test/mm_load_test.c.o CMakeFiles/mm-ut.dir/__/unit_test/mm_mem16_test.c.o CMakeFiles/mm-ut.dir/__/unit_test/mm_mem32_test.c.o CMakeFiles/mm-ut.dir/__/unit_test/mm_mem8_test.c.o CMakeFiles/mm-ut.dir/__/unit_test/mm_testrunner.c.o CMakeFiles/mm-ut.dir/__/unit_test/mm_test_utils.c.o CMakeFiles/mm-ut.dir/__/unit_test/mm_utils_test.c.o CMakeFiles/mm-ut.dir/__/src/mm_app.c.o CMakeFiles/mm-ut.dir/__/src/mm_dump.c.o CMakeFiles/mm-ut.dir/__/src/mm_load.c.o CMakeFiles/mm-ut.dir/__/src/mm_mem16.c.o CMakeFiles/mm-ut.dir/__/src/mm_mem32.c.o CMakeFiles/mm-ut.dir/__/src/mm_mem8.c.o CMakeFiles/mm-ut.dir/__/src/mm_utils.c.o CMakeFiles/mm-ut.dir/__/__/__/cfs_lib/fsw/src/cfs_utils.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/utassert.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_es_hooks.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_es_stubs.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_evs_hooks.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_evs_stubs.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_fs_stubs.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_psp_eeprom_stubs.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_psp_memrange_stubs.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_psp_memutils_stubs.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_psp_ram_stubs.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_psp_timer_stubs.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_psp_watchdog_stubs.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_sb_hooks.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_sb_stubs.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_tbl_hooks.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_tbl_stubs.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_time_hooks.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/ut_cfe_time_stubs.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/utlist.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/ut_osapi_stubs.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/ut_osfileapi_stubs.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/uttest.c.o CMakeFiles/mm-ut.dir/__/__/__/__/core/base/ut_assert/src/uttools.c.o  -o mm-ut  libmm-ut_no_symtab.a -lpthread -ldl -lrt

We then see a number of linker errors like:

/usr/bin/ld: ./.tmp-kcc-h49pwzks/libmm-ut_no_symtab-native.a(native-tu-41.o): in function `MM_APP_TEST_CFE_ES_ExitAppHook':
mm_app_test.c:(.text+0x0): multiple definition of `MM_APP_TEST_CFE_ES_ExitAppHook'; ./.tmp-kcc-h49pwzks/native-tu-1.o:mm_app_test.c:(.text+0x0): first defined here

The offending symbol (MM_APP_TEST_CFE_ES_ExitAppHook) does indeed exist in both the static library and the mm_app_test.c.o object file:

$ cd /airliner/build/bebop2/kcc/target/target/apps/mm
$ nm CMakeFiles/mm-ut.dir/__/unit_test/mm_app_test.c.o | grep MM_APP_TEST_CFE_ES_ExitAppHook
0000000000000000 r .L__func__.MM_APP_TEST_CFE_ES_ExitAppHook
0000000000000000 T MM_APP_TEST_CFE_ES_ExitAppHook
$ nm libmm-ut_no_symtab.a | grep MM_APP_TEST_CFE_ES_ExitAppHook
0000000000000000 r .L__func__.MM_APP_TEST_CFE_ES_ExitAppHook
0000000000000000 T MM_APP_TEST_CFE_ES_ExitAppHook

My full build log is here: mm.txt

[^1]: Note that the compiler invocations below use the Runtime Verification kcc tool; I can provide further context on this if needed, but it produces faithful native binaries using Clang, which also include additional instrumentation code.