0

let me start by saying that this is my first time really meddling with GCC, so I apologize if this question is not very constructive or has been answered before.

I have two static libraries:

"L1.h"

void __attribute__((weak)) Logger_Init(void) { // Invalid... }

"L2.h"

void Logger_Init(void);

"L2.c"

void Logger_Init(void)
{
   // Do stuff...
}

My goal is for certain executables to include "L2" into the compilation process and override "L1"'s implementation of Logger_Init.

I read here that you cannot link two static libraries, but I do the check of whether to even call Logger_Init at runtime, so I need the declaration of the function during compilation.

In the executable project, there is an option to add library paths and names, in which I added both of these libraries. My first question is, does the order matter here? I played with these two commands below and it made no difference (they're trimmed up quite a bit, but I assume that this is the relevant part).

gcc -L(L1_PATH) -L(L2_PATH) -l(L1_NAME) -l(L2_NAME)

gcc -L(L2_PATH) -L(L1_PATH) -l(L2_NAME) -l(L1_NAME)

My main question here though, is how to tell the linker(?) that there is a strong definition of the function in another static library, or something along those lines?

I've also read that using the __attribute__((weak)) is not very helpful for static libraries, but I'm not really understanding why, it seems like it does exactly what I am trying to achieve.

I can compile and run everything here, however "L2" implementation of Logger_Init doesn't get called.

I am working in an embedded environment, so using dynamic/shared libraries is out of the question.

EDIT:

Per request, Bodo, here is the entire list of command. I am not writing these commands, they're all auto-generated by Vitis. This is where I'm running my code. I turned verbose on and let it rip. Over half of this stuff is gibberish to me, I am sorry for the bloat. Towards the very bottom is the useful stuff.

make all 

make --no-print-directory pre-build

a9-linaro-pre-build-step

' '

make --no-print-directory main-build

'Building file: ../src/main.c'

'Invoking: ARM v7 gcc compiler'

arm-none-eabi-gcc -Wall -O0 -g3 -c -fmessage-length=0 -MT"src/main.o"
-mcpu=cortex-a9 -mfpu=vfpv3 -mfloat-abi=hard -v -IC:/Projects/Xilinx/Experiment/PicoZed/PicoZed.vitis/PicoZed/export/PicoZed/sw/PicoZed/standalone_domain/bspinclude/include
-MMD -MP -MF"src/main.d" -MT"src/main.o" -o "src/main.o" "../src/main.c"

Using built-in specs.

COLLECT_GCC=C:\Xilinx\Vitis\2020.2\gnu\aarch32\nt\gcc-arm-none-eabi\bin\\..\x86_64-oesdk-mingw32\usr\bin\arm-xilinx-eabi\arm-xilinx-eabi-gcc.exe

Target: arm-xilinx-eabi

Configured with: ../../../../../../work-shared/gcc-9.2.0-r0/gcc-9.2.0/configure
--build=x86_64-linux --host=x86_64-oesdk-mingw32 --target=arm-xilinx-eabi --prefix=/usr/local/oecore-x86_64/sysroots/x86_64-oesdk-mingw32/usr --exec_prefix=/usr/local/oecore-x86_64/sysroots/x86_64-oesdk-mingw32/usr
--bindir=/usr/local/oecore-x86_64/sysroots/x86_64-oesdk-mingw32/usr/bin/arm-xilinx-eabi
--sbindir=/usr/local/oecore-x86_64/sysroots/x86_64-oesdk-mingw32/usr/bin/arm-xilinx-eabi
--libexecdir=/usr/local/oecore-x86_64/sysroots/x86_64-oesdk-mingw32/usr/libexec/arm-xilinx-eabi
--datadir=/usr/local/oecore-x86_64/sysroots/x86_64-oesdk-mingw32/usr/share
--sysconfdir=/usr/local/oecore-x86_64/sysroots/x86_64-oesdk-mingw32/etc --sharedstatedir=/usr/local/oecore-x86_64/sysroots/x86_64-oesdk-mingw32/com
--localstatedir=/usr/local/oecore-x86_64/sysroots/x86_64-oesdk-mingw32/var
--libdir=/usr/local/oecore-x86_64/sysroots/x86_64-oesdk-mingw32/usr/lib/arm-xilinx-eabi
--includedir=/usr/local/oecore-x86_64/sysroots/x86_64-oesdk-mingw32/us r/include
--oldincludedir=/usr/local/oecore-x86_64/sysroots/x86_64-oesdk-mingw32/usr/include
--infodir=/usr/local/oecore-x86_64/sysroots/x86_64-oesdk-mingw32/usr/share/info --mandir=/usr/local/oecore-x86_64/sysroots/x86_64-oesdk-mingw32/usr/share/man
--disable-silent-rules --disable-dependency-tracking --with-libtool-sysroot=/scratch/mhatle/baremetal-toolchains/20200708-172230/build/aarch32-tc-x86_64-mingw32/work/x86_64-nativesdk-mingw32-oesdk-mingw32/gcc-cross-canadian-arm/9.2.0-r0/recipe-sysroot
--enable-clocale=generic --with-gnu-ld --enable-shared --enable-languages=c,c++ --enable-threads=posix --enable-multilib --enable-c99 --enable-long-long --enable-libstdcxx-pch --program-prefix=arm-xilinx-eabi- --without-local-prefix --enable-lto --disable-libssp --enable-libitm --disable-bootstrap --disable-libmudflap --with-system-zlib --enable-linker-build-id --with-ppl=no --with-cloog=no --enable-checking=release --enable-cheaders=c_global --without-isl --with-gxx-include-dir=/not/exist/usr/include/c++/9.2.0 --wi th-build-time-tools=/scratch/mhatle/baremetal-toolchains/20200708-172230/build/aarch32-tc-x86_64-mingw32/work/x86_64-nativesdk-mingw32-oesdk-mingw32/gcc-cross-canadian-arm/9.2.0-r0/recipe-sysroot-native/usr/arm-xilinx-eabi/bin
--with-build-sysroot=/scratch/mhatle/baremetal-toolchains/20200708-172230/build/aarch32-tc-x86_64-mingw32/work/x86_64-nativesdk-mingw32-oesdk-mingw32/gcc-cross-canadian-arm/9.2.0-r0/recipe-sysroot
--enable-poison-system-directories --disable-nls --enable-initfini-array --without-headers --with-newlib --disable-libstdcxx-pch --with-newlib --disable-threads --enable-plugins --with-gnu-as --disable-libitm --with-multilib-list=aprofile --enable-multilib --disable-nls --enable-mingw-wildcard --with-sysroot=/not/exist

Thread model: single

gcc version 9.2.0 (GCC) 

COLLECT_GCC_OPTIONS='--sysroot=C:\Xilinx\Vitis\2020.2\gnu\aarch32\nt\gcc-arm-none-eabi\bin\\..\aarch32-xilinx-eabi' '-Wall' '-O0' '-g3' '-c' '-fmessage-length=0' '-MT' 'src/main.o' '-mcpu=cortex-a9' '-mfpu=vfpv3' '-mfloat-abi=hard' '-v' '-I' 'C:/Projects/Xilinx/Experiment/PicoZed/PicoZed.vitis/PicoZed/export/PicoZed/sw/PicoZed/standalone_domain/bspinclude/include' '-MMD' '-MP' '-MF' 'src/main.d' '-MT' 'src/main.o' '-o' 'src/main.o' '-marm' '-march=armv7-a+mp+sec+vfpv3'

 c:/xilinx/vitis/2020.2/gnu/aarch32/nt/gcc-arm-none-eabi/x86_64-oesdk-mingw32/usr/bin/arm-xilinx-eabi/../../libexec/arm-xilinx-eabi/gcc/arm-xilinx-eabi/9.2.0/cc1.exe
-quiet -v -I C:/Projects/Xilinx/Experiment/PicoZed/PicoZed.vitis/PicoZed/export/PicoZed/sw/PicoZed/standalone_domain/bspinclude/include
-imultilib thumb/v7-a+fp/hard -iprefix c:\xilinx\vitis\2020.2\gnu\aarch32\nt\gcc-arm-none-eabi\x86_64-oesdk-mingw32\usr\bin\arm-xilinx-eabi\../../lib/arm-xilinx-eabi/gcc/arm-xilinx-eabi/9.2.0/
-isysroot C:\Xilinx\Vitis\2020.2\gnu\aarch32\nt\gcc-arm-none-eabi\bin\\..\aarch32-xilinx-eabi
-MMD src/main.d -MF src/main.d -MP -MT src/main.o -MT src/main.o -dD -D__USES_INITFINI__ ../src/main.c -quiet -dumpbase main.c -mcpu=cortex-a9 -mfpu=vfpv3 -mfloat-abi=hard -marm -march=armv7-a+mp+sec+vfpv3 -auxbase-strip src/main.o -g3 -O0 -Wall -version -fmessage-length=0 -o C:\Users\M257B~1.BLA\AppData\Local\Temp\ccOuqloW.s

GNU C17 (GCC) version 9.2.0 (arm-xilinx-eabi)

    compiled by GNU C version 9.2.0, GMP version 6.1.2, MPFR version
4.0.2, MPC version 1.1.0, isl version none

GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072

ignoring nonexistent directory "c:\xilinx\vitis\2020.2\gnu\aarch32\nt\gcc-arm-none-eabi\x86_64-oesdk-mingw32\usr\bin\arm-xilinx-eabi\../../lib/arm-xilinx-eabi/gcc/arm-xilinx-eabi/9.2.0/../../../../../arm-xilinx-eabi/include"

ignoring duplicate directory "c:/xilinx/vitis/2020.2/gnu/aarch32/nt/gcc-arm-none-eabi/x86_64-oesdk-mingw32/usr/lib/arm-xilinx-eabi/gcc/../../../lib/arm-xilinx-eabi/gcc/arm-xilinx-eabi/9.2.0/include"

ignoring nonexistent directory "C:\Xilinx\Vitis\2020.2\gnu\aarch32\nt\gcc-arm-none-eabi\bin\\..\aarch32-xilinx-eabi/usr/lib/arm-xilinx-eabi/9.2.0/include"

ignoring nonexistent directory "C:\Xilinx\Vitis\2020.2\gnu\aarch32\nt\gcc-arm-none-eabi\bin\\..\aarch32-xilinx-eabi/usr/local/oecore-x86_64/sysroots/x86_64-oesdk-mingw32/usr/lib/arm-xilinx-eabi/gcc/arm-xilinx-eabi/9.2.0/../../../../include"

ignoring duplicate directory "c:/xilinx/vitis/2020.2/gnu/aarch32/nt/gcc-arm-none-eabi/x86_64-oesdk-mingw32/usr/lib/arm-xilinx-eabi/gcc/../../../lib/arm-xilinx-eabi/gcc/arm-xilinx-eabi/9.2.0/include-fixed"

ignoring nonexistent directory "c:/xilinx/vitis/2020.2/gnu/aarch32/nt/gcc-arm-none-eabi/x86_64-oesdk-mingw32/usr/lib/arm-xilinx-eabi/gcc/../../../lib/arm-xilinx-eabi/gcc/arm-xilinx-eabi/9.2.0/../../../../../arm-xilinx-eabi/include"

#include "..." search starts here:

#include <...> search starts here:

 C:/Projects/Xilinx/Experiment/PicoZed/PicoZed.vitis/PicoZed/export/PicoZed/sw/PicoZed/standalone_domain/bspinclude/include

 c:\xilinx\vitis\2020.2\gnu\aarch32\nt\gcc-arm-none-eabi\x86_64-oesdk-mingw32\usr\bin\arm-xilinx-eabi\../../lib/arm-xilinx-eabi/gcc/arm-xilinx-eabi/9.2.0/include

 c:\xilinx\vitis\2020.2\gnu\aarch32\nt\gcc-arm-none-eabi\x86_64-oesdk-mingw32\usr\bin\arm-xilinx-eabi\../../lib/arm-xilinx-eabi/gcc/arm-xilinx-eabi/9.2.0/include-fixed

 C:\Xilinx\Vitis\2020.2\gnu\aarch32\nt\gcc-arm-none-eabi\bin\\..\aarch32-xilinx-eabi/usr/include

End of search list.

GNU C17 (GCC) version 9.2.0 (arm-xilinx-eabi)

    compiled by GNU C version 9.2.0, GMP version 6.1.2, MPFR version
4.0.2, MPC version 1.1.0, isl version none

GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072

Compiler executable checksum: 2516cb1a757555b312b0344a413e7d9c

COLLECT_GCC_OPTIONS='--sysroot=C:\Xilinx\Vitis\2020.2\gnu\aarch32\nt\gcc-arm-none-eabi\bin\\..\aarch32-xilinx-eabi' '-Wall' '-O0' '-g3' '-c' '-fmessage-length=0' '-MT' 'src/main.o' '-mcpu=cortex-a9' '-mfpu=vfpv3' '-mfloat-abi=hard' '-v' '-I' 'C:/Projects/Xilinx/Experiment/PicoZed/PicoZed.vitis/PicoZed/export/PicoZed/sw/PicoZed/standalone_domain/bspinclude/include' '-MMD' '-MP' '-MF' 'src/main.d' '-MT' 'src/main.o' '-o' 'src/main.o' '-marm' '-march=armv7-a+mp+sec+vfpv3'

 c:/xilinx/vitis/2020.2/gnu/aarch32/nt/gcc-arm-none-eabi/x86_64-oesdk-mingw32/usr/bin/arm-xilinx-eabi/../../libexec/arm-xilinx-eabi/gcc/arm-xilinx-eabi/9.2.0/as.exe
-v -I C:/Projects/Xilinx/Experiment/PicoZed/PicoZed.vitis/PicoZed/export/PicoZed/sw/PicoZed/standalone_domain/bspinclude/include
-march=armv7-a+mp+sec -mfloat-abi=hard -mfpu=vfpv3 -meabi=5 -o src/main.o C:\Users\M257B~1.BLA\AppData\Local\Temp\ccOuqloW.s

GNU assembler version 2.32.0 (arm-xilinx-eabi) using BFD version (GNU Binutils) 2.32.0.20190204

COMPILER_PATH=c:/xilinx/vitis/2020.2/gnu/aarch32/nt/gcc-arm-none-eabi/x86_64-oesdk-mingw32/usr/bin/arm-xilinx-eabi/../../libexec/arm-xilinx-eabi/gcc/arm-xilinx-eabi/9.2.0/;c:/xilinx/vitis/2020.2/gnu/aarch32/nt/gcc-arm-none-eabi/x86_64-oesdk-mingw32/usr/bin/arm-xilinx-eabi/../../libexec/arm-xilinx-eabi/gcc/

LIBRARY_PATH=C:/Xilinx/Vitis/2020.2/gnu/aarch32/nt/gcc-arm-none-eabi/bin//../aarch32-xilinx-eabi/usr/lib/thumb/v7-a+fp/hard/;c:/xilinx/vitis/2020.2/gnu/aarch32/nt/gcc-arm-none-eabi/x86_64-oesdk-mingw32/usr/bin/arm-xilinx-eabi/../../lib/arm-xilinx-eabi/gcc/arm-xilinx-eabi/9.2.0/;c:/xilinx/vitis/2020.2/gnu/aarch32/nt/gcc-arm-none-eabi/x86_64-oesdk-mingw32/usr/bin/arm-xilinx-eabi/../../lib/arm-xilinx-eabi/gcc/;C:/Xilinx/Vitis/2020.2/gnu/aarch32/nt/gcc-arm-none-eabi/bin//../aarch32-xilinx-eabi/usr/lib/arm-xilinx-eabi/9.2.0/;C:/Xilinx/Vitis/2020.2/gnu/aarch32/nt/gcc-arm-none-eabi/bin//../aarch32-xilinx-eabi/usr/lib/

COLLECT_GCC_OPTIONS='--sysroot=C:\Xilinx\Vitis\2020.2\gnu\aarch32\nt\gcc-arm-none-eabi\bin\\..\aarch32-xilinx-eabi' '-Wall' '-O0' '-g3' '-c' '-fmessage-length=0' '-MT' 'src/main.o' '-mcpu=cortex-a9' '-mfpu=vfpv3' '-mfloat-abi=hard' '-v' '-I' 'C:/Projects/Xilinx/Experiment/PicoZed/PicoZed.vitis/PicoZed/export/PicoZed/sw/PicoZed/standalone_domain/bspinclude/include' '-MMD' '-MP' '-MF' 'src/main.d' '-MT' 'src/main.o' '-o' 'src/main.o' '-marm' '-march=armv7-a+mp+sec+vfpv3'

'Finished building: ../src/main.c'

' '

'Building target: PicoZed_Main.elf'

'Invoking: ARM v7 gcc linker'

arm-none-eabi-gcc
-L"C:\Projects\Xilinx\Experiment\PicoZed\PicoZed.vitis\Library1\Debug" -L"C:\Projects\Xilinx\Experiment\PicoZed\PicoZed.vitis\Library2\Debug" -mcpu=cortex-a9 -mfpu=vfpv3 -mfloat-abi=hard -Wl,-build-id=none -specs=Xilinx.spec -Wl,-T -Wl,../src/lscript.ld -LC:/Projects/Xilinx/Experiment/PicoZed/PicoZed.vitis/PicoZed/export/PicoZed/sw/PicoZed/standalone_domain/bsplib/lib
-o "PicoZed_Main.elf"  ./src/main.o   -lLibrary1 -lLibrary2 -Wl,--start-group,-lxil,-lgcc,-lc,--end-group

'Finished building target: PicoZed_Main.elf'

' '

'Invoking: ARM v7 Print Size'

arm-none-eabi-size PicoZed_Main.elf  |tee "PicoZed_Main.elf.size"

   text    data     bss     dec     hex filename

  18736    1144   22568   42448    a5d0 PicoZed_Main.elf

'Finished building: PicoZed_Main.elf.size'

' '
KamilCuk
  • 69,546
  • 5
  • 27
  • 60
Maty
  • 75
  • 2
  • 10
  • Please [edit] your question and show example code that includes the header files and the commands you use to build the libraries. The function definition in a header file `L1.h` will be part of every translation unit that includes this header. I suggest to define the function in a .c file. – Bodo Mar 16 '21 at 14:25
  • We have experienced issues last year with weak functions and libraries. If I remind our issue correctly, we had a library with function implementations and we tried to link this library to a project with weak functions. We hoped that the function implementations from the library would be used instead of the weak ones. The result was somehow undefined. Sometimes it worked, sometimes not. We have stopped using a library for this. – Bktero Mar 16 '21 at 14:42
  • Some interesting discussion: - https://stackoverflow.com/questions/13089166/how-to-make-gcc-link-strong-symbol-in-static-library-to-overwrite-weak-symbol - https://answers.launchpad.net/gcc-arm-embedded/+question/251943 – Bktero Mar 16 '21 at 14:58
  • @Bktero That's useful information, thank you. What did you end up doing, if not using a static library? – Maty Mar 16 '21 at 15:08
  • `"L1.h"` Why do you have function implementation in a header file? – KamilCuk Mar 16 '21 at 16:33
  • @Maty We used the source files directly... Because the library was used only in internal projects, it was not an issue. The weak functions were part of a 3rd party library (package as a set of source files too) so it was not possible to use another mechanism. – Bktero Mar 17 '21 at 21:51

1 Answers1

1

My first question is, does the order matter here?

Yes, literally from gcc documentation:

-l library

...

It makes a difference where in the command you write this option; the linker searches and processes libraries and object files in the order they are specified. Thus, ‘foo.o -lz bar.o’ searches library ‘z’ after file foo.o but before bar.o. If bar.o refers to functions in ‘z’, those functions may not be loaded.

how to tell the linker(?) that there is a strong definition of the function in another static library, or something along those lines?

Typically in modern embedded:

-Wl,--whole-archive -l(L1_NAME) -l(L2_NAME) -Wl,--no-whole-archive 

Another alternative is to unpack both libraries and pass object files to gcc.

but I'm not really understanding why

Linker searches up until the first symbol in libraries, in order. So whichever you put first - the library with weak or with strong - that symbol will be used and all following same symbols will just be ignored.

I can compile and run everything here, however "L2" implementation of Logger_Init doesn't get called.

That's odd, because -l(L2_NAME) -l(L1_NAME) that is in your post should result in l2 implementation called. An MCVE:

cat >Makefile <<EOF
all: l1.a l2.a main.o
    gcc main.o l1.a l2.a
    ./a.out
    gcc main.o l2.a l1.a
    ./a.out
l2.a: l2.o
    ar rcs $@ $<
l1.a: l1.o
    ar rcs $@ $<
EOF
cat >main.c <<EOF
int main() {
    void Logger_Init();
    Logger_Init();
}

EOF
cat >l1.c <<EOF
void __attribute__((weak)) Logger_Init(void) {
    puts("I am weak");
}

EOF
cat >l2.c <<EOF
void Logger_Init(void)
{
   puts("I am strong");
}

EOF

results in:

$ make
...
gcc main.o l1.a l2.a
./a.out
I am weak
gcc main.o l2.a l1.a
./a.out
I am strong

ie. whichever is first, is first. I tested it also with -L. -ll2 -ll1 and renamed libraries to libl*.a - same result. I suspect your method of checking was flawed.


Make has 44 years, it's a grandpa with a long beard. Use something modern, like cmake.

Using weak attribute may lead to very bad spaghetti code that is very, very hard to fix and refactor later. Consider just using a function pointer with get/set accessors.


Can you tell us more about why you think weak functions are bad? @Bktero

Opinion based:

Disadvantages:

  • Compiler specific syntax, non-portable code, non-standard.
  • If non-reentrant, hard to patch to allow to pass user specified context.
  • You can overwrite the function from anywhere in the code. It's basically a hot sauce for your spaghetti code.
  • IDE is confused and do not know where to jump to definition, because there are two.
  • Hard to unit test, because:
    • one executable can provide one overload of the function, so:
    • unit testing with multiple executables requires more writing
      • which makes it really horrible to use an IDE "go to definition" feature, because then you'll get more and more definitions of the same function
    • unit testing with a wrapper with a settable function pointer is the solution, which... it could be just implemented as the callback itself.
  • If it's meant to be a callback from the library/subsystem:
    • allows basically for one instance of that library
    • as programming grows, multiple instances of the same library are needed
    • which requires writing/merging a single entrypoint a single place from multiple code places.
    • Merging multiple projects is hard, because:
      • imagine a library with a weak callback
      • two libraries that are using that library and overwriting that weak callback
      • then you can't use those two libraries together, you'll get just a duplicated definition of a function.
      • It requires patching or selective compiling of that libraries.
      • It requires a single entry point with a dispatch table from which library it was called.
      • Spaghetti code.
  • As noted by OP - problems with linking static libraries related to order, needed to be solved with compiler specific syntax --whole-archive. Because it depends on order, it can be surprising, sometimes work and sometimes not, resulting in surprising and unexpected bugs, because you only change the order of libraries and oops - your application does not work.

Similar problems to global variables. Basically same problems as with signals. Same solution as to signals - using function pointers. Just like using SA_SIGINFO with sigaction allows to pass a custom function pointer with custom void *context pointer.

Advantages:

  • faster code
  • less typing

Any experience to share?

Well, I used and researched weak (or undefined) functions as callback from libraries and it resulted in code that is hard to merge and hard to refactor.

STM32 HAL libraries were implemented with weak functions, they optionally enabled function pointers later, now there is a #if (USE_HAL_*_REGISTER_CALLBACKS == 1) stuff in their sources, see ex stm32l0xx_hal_uart.h#L248

KamilCuk
  • 69,546
  • 5
  • 27
  • 60
  • Thank you for the explanation, it's all starting to slowly click together. I will double check my work. I've been reading about `--whole-archive` and `--no-whole-archive`, and you even used it in your answer. I suspect that this might be the key. – Maty Mar 16 '21 at 16:58
  • That did it! `--whole-archive` was the key. The library was not being forced into the linker because it wasn't being used. Thank you, your answer definitely helped me clear up some things in my head. I'll look into cmake, I've heard if it, but that's as far as my knowledge on that goes. – Maty Mar 16 '21 at 17:23
  • Can you tell us more about why you think weak functions are bad? Any experience to share? – Bktero Mar 17 '21 at 21:55
  • 1
    @Bktero well, I guess I edited. – KamilCuk Mar 17 '21 at 22:25
  • Thank you very much! – Bktero Mar 19 '21 at 09:49