Hello!
I use arm-none-eabi to compile my STM32 project. I want to link my project files to an archive, e.g. libexample.a, to allow link with other objects, e.g. example.o. The libexample.a contains weak symbol replaced by example.o and example.o uses functions from libexample.a.
Now I face the following problem: When I create libexample.a and then link libexample.a with example.o, the resulting elf/bin is malfunction.
When I take example.o and link it directly with .o files used to crate libexample.a, the resulting elf/bin is OK.
Compile flags are equal.
The further investigation showed, that:
The Makefile snippet is below:
libexample.a: $(OBJS) @echo Creating static library $@ $(AR) rcs $@ $(OBJS) ofiles: @echo Compiling @arm-none-eabi-gcc -c $(CFLAGS) $(INCLUDE) example.c -o example.o @echo "Linking (creating .elf)" @arm-none-eabi-gcc $(OBJS) example.o $(LDFLAGS) -o example.elf @echo "Creating .hex file" @arm-none-eabi-objcopy -O ihex example.elf example.hex @echo "Creating .bin file" @arm-none-eabi-objcopy -O binary -S example.elf example.bin afile: libexample.a @echo Compiling @arm-none-eabi-gcc -c $(CFLAGS) $(INCLUDE) example.c -o example.o @echo "Linking (creating .elf)" @arm-none-eabi-gcc -L. -lexample example.o $(LDFLAGS) -o example2.elf @echo "Creating .hex file" @arm-none-eabi-objcopy -O ihex example2.elf example2.hex @echo "Creating .bin file" @arm-none-eabi-objcopy -O binary -S example2.elf example2.bin
example.bin size is 172Kexample2.bin size is 144K
Jan Belohoubek said:example.bin size is 172Kexample2.bin size is 144K
The size difference is likely due to the fact that the linker can choose the object files to link from an archive, depending on the symbols it needs to resolve. As a result, the size of the output binary may be less than that of a binary built by linking all the object files. (See also the linker option --whole-archive.)
Jan Belohoubek said:the resulting elf/bin is malfunction
What exactly does a malfunction mean? How do the two elf binaries differ, besides in their sizes - any differences in symbols linked, or in the disassemblies of a function?
These options, if provided to gcc's LDFLAGS, output details about the objects being linked, and about the symbols, their references and definitions:
With these, you should be able to see how the linker resolves the symbols in the two cases, and detect any differences if there are.
Does switching from (a) to (b), or from (a) to (c), or from (a) to (d), (see below) make any difference?
(a)@arm-none-eabi-gcc -L. -lexample example.o $(LDFLAGS) -o example2.elf
(b)@arm-none-eabi-gcc example.o -L. -lexample $(LDFLAGS) -o example2.elf
(c)@arm-none-eabi-gcc -Wl,--whole-archive -L. -lexample example.o $(LDFLAGS) -o example2.elf
(d)@arm-none-eabi-gcc -Wl,--whole-archive example.o -L. -lexample $(LDFLAGS) -o example2.elf
Note also that the search for objects inside an archive is dependent on the ordering of the objects stored. See here.
Hello and Thank you for your answers!
It helped much, but still, there is something strange.
By the malfunction, I meant, that the application-level test didn't pass (the application should produce some messages to UART) - I performed no additional testing, as I expected, that there is some build-configuration error, as the binary sizes were different.
I did further investigation as you suggested and I found the following:
To conclude:
In fact, I'm surprised, that compilation with archive may differ from compilation with objects, as the -Os parameter also removes unused code...even in objects What is the reason behind different behavior with the archive?
Jan Belohoubek said:why are the Int Handlers removed when linked with the archive library and not removed when linked with objects only?
When the object files are provided to the linker directly on the commandline, it has no choice but to link them fully. But when the object files are provided wrapped within an archive file, the linker does choose which files to link and which file to leave, depending on what symbols it needs to resolve.
It is a good practice to list the .o files, if any, first on the linker commandline before listing any libraries. There's a proper order, in general, to list the libraries too.
Jan Belohoubek said:why are int handlers not removed when linked with "reduced" archive! This is strange ...
How are the interrupt/exception handler symbols declared? Are they weak symbols, declared as aliases to a dummy/default handler?
If so, it seems likely that when linking with the full archive file, the linker resolves some handlers, like BusFault_Handler, as the dummy/default handler.
This could happen if (a) the actual BusFault_Handler is defined in an object file which is later in the order than the object file containing the dummy/default handler, and if (b) the symbols that the linker need to resolve are such that the object file containing the actual BusFault_Handler isn't needed.
Removing unnecessary object files both change the order of the files in the archive and/or expose other object files which were earlier 'hidden' behind the files just removed. (hidden in the context of resolving a particular symbol).
When linking with the reduced archive, it is likely that the linker is now forced to include object files containing the actual definitions of those handlers, and therefore, the handlers become available.
The -Wl,--target-symbol=symbol_name option prints out the references to the given symbol_name and the particular definition the linker found in order to resolve those references. You can check, for e.g., for BusFault_Handler in the two cases - one linking with the full archive and other linking with the reduced archive.
You may also want to change the order in which you specify the files when creating the full archive. For e.g., provide the object files containing the actual handler functions first, and the rest later.
Or, in the end, apply --whole-archive to fall back to the ofiles behaviour.
Edit: Above, replace --target-symbol with --trace-symbol. Apologies. I am not sure what I was thinking at the moment that led me to write --target-symbol instead of --trace-symbol.
Thank you for guiding me through the process!
Finally, I have the problem!
You were right from the beginning: my archive contains both weak and non-weak declarations of the same symbol and when ordering is changed, different symbols are selected. I'm sorry for ignoring parts of your reply!
But subsequently, I found, that the ordering changes even when I copy "build" (containing object files) directory to a different location...this is somehow strange.
I understand the recommendation not to have weak and non-weak symbols in the same archive, but because off modularity, it is unfortunately our case ... :-(
In our case, partial linking should be good workaround, but from my perspective, this behaviour (first-hit symbol archive search) is really bad, as in general it is misleading and not consistent with C-level declarations.
It may be beneficial to investigate the dependencies between the object files and then decide how to setup the archive. Splitting the archive into multiple smaller archives may also help.
It seems that the archive contains multiple implementations (i.e. definitions/code) of a single function-name, with each implementation in its own .o file. These .o files do not conflict with each other during the ofiles-build, because all instances of that name, except possibly one, are declared as weak functions. Is that correct?
But these weakly-declared instances of the function aren't necessary, since in the ofiles-build they are anyways ignored and overridden. Would it not, therefore, be proper to remove their code from the source altogether?
If the end-goal is to provide multiple, compiled implementations of a single functionality (for e.g. 5 different implementations of malloc), then each implementation can be separated out in its own archive file, and then linked, with the single, large, original archive, in proper order. The choice of the implementation (and hence, the choice of the corresponding archive) can become a configurable parameter, which the user must set when linking their application with your library.
You are absolutely right!
To clarify: we use stm32 HAL libraries, which contain a number of weak functions to allow user-code minimization. In the user code, only required functions are implemented, the undefined parts are taken from the HAL library.
Our project is a modular firmware and in fact "example.a" contains complete and operational embedded code: we provide "symbol-identical" .bin and .a files. The .a file is provided to allow external module creation and incorporation (again by using weak function calls in the.a file).
To respect the hierarchy of the libraries, it would be probably better to compile HAL libraries into separate .a file and our "application" code into the second .a file. But in our use-case, both libraries would be used always together. It would be better - to prevent user confusion - to provide a single .a file. I decided to create the static library with a single symbol table (-r linker parameter), thus weak functions inside .a file are resolved and the user has access to a static library with no built-in conflicts.
For now, there is just one possible configuration of the static library. If there would be more configurations, separate .a files will be provided.
Thank you! You were really helpful!