gcc detect duplicate symbols/functions in static libraries - c

Is there any way we can get gcc to detect a duplicate symbol in static libraries vs the main code (Or another static library ?)
Here's the situation:
main.c erroneously contained a function definition, e.g. with the signature uint foohash(const char*)
foo.c also contains a function definition with the signature uint foohash(const char*)
foo.c and other source files are compiled to a static util library, which the main program links in, i.e. something like:
gcc -o main main.o util.o -L ./libs -lfooutils
So, now main.o and libs/libfooutils.a both contain a foohash function. Presumably the linker found that symbol in main.o and doesn't bother looking for it elsewhere.
Is there any way we can get gcc to detect such a situation ?

Indeed as Simon Richter stated, --whole-archive option can be useful. Try to change your command-line to:
gcc -o main main.o util.o -L ./libs -Wl,--whole-archive -lfooutils -Wl,--no-whole-archive
and you'll see a multiple definition error.

gcc calls the ld program for linking. The relevant ld options are:
--no-define-common
--traditional-format
--warn-common
See the man page for ld. These should be what you need to experiment with to get the warnings sought.

Short answer: no.
GCC does not actually do anything with libraries. It is the task of ld, the linker (called automatically by GCC) to pull in symbols from libraries, and that's really a fairly dumb tool.
The linker has lots of complex jiggery pokery for combining different types of data from different sources, and supporting different file formats, and all the evil little details of binary executables, but in the end, all it really does is look for undefined symbols and find the definitions.
What you can do is a link trace (pass -t to gcc) to see what comes from where. Or else run nm on all the object files and libraries in your system, and write a script to detect duplicates.

Related

Can IAR produce a static library that GCC can link to?

There is a vendor whose software I'd like to work with. They have a code base which they can only compile using IAR Embedded Workbench (as far as I know, their code does not compile with GCC). Unfortunately their hardware only works with their software stack, so I don't really have a choice about whether or not I'd like to use it. They distribute this code as a .a static library file (and accompanying headers) compiled for the ARM Cortex-M4 CPU. (They don't want to distribute sources.) For the sake of this discussion, let's call it evil_sw_stack.a.
I'd like to use this piece of code but I don't have an IAR license and have zero expertise with IAR. I'd like to use GCC.
Is there a way to make IAR produce such a static library that GCC can link to? What kind of compiler option would the vendor need to use to produce such a binary?
(I would guess that the ABI of the resulting binary can be somehow specified and set to a setting which statisfies GCC. )
Example usage of GCC
Their default software stack is very GCC-friendly, this specific one is the only one in their offering which isn't. Generally, I can compile a simple piece of example code if I have the following:
startup_(devicename).S: GCC-specific assembly file
system_(devicename).c
(devicename).ld: linker script
Some header files for the specific device
For example, I can compile a simple piece of example like this:
$ arm-none-eabi-gcc helloworld.c startup_(devicename).S system_(devicename).c -T (devicename).ld -o helloworld -D(devicename) -I. -fno-builtin -ffunction-sections -fdata-sections -mfpu=fpv4-sp-d16 -mfloat-abi=softfp -mcpu=cortex-m4 -mthumb -mno-sched-prolog -Wl,--start-group -lgcc -lc -lnosys -Wl,--end-group
So far, so good. No warnings, no errors.
How I try to use the static library
For the sake of this discussion, let's call it evil_sw_stack.a.
This is how I attempted to use it:
$ arm-none-eabi-gcc evil_sw_stack.a helloworld.c startup_(devicename).S system_(devicename).c -T (devicename).ld -o helloworld -D(devicename) -I. -fno-builtin -ffunction-sections -fdata-sections -mfpu=fpv4-sp-d16 -mfloat-abi=softfp -mcpu=cortex-m4 -mthumb -mno-sched-prolog -Wl,--start-group -lgcc -lc -lnosys -Wl,--end-group
Unfortunately this complains about multiple definitions of a bunch of functions that are defined in system_(devicename).c. Maybe they accidentally compiled that into this library? Or maybe IAR just compiled it this way? Now, if I try to remove system_(devicename).c from the GCC command line and simply link to the .a file, I get these errors:
/usr/lib/gcc/arm-none-eabi/5.2.0/../../../../arm-none-eabi/bin/ld: warning: thelibrary.a(startup_chipname.o) uses 2-byte wchar_t yet the output is to use 4-byte wchar_t; use of wchar_t values across objects may fail
undefined reference to `__iar_program_start'
undefined reference to `CSTACK$$Limit'
undefined reference to `__iar_program_start'
Poking the file with readelf gets me nowhere:
$ readelf -h evil_sw_stack.a
readelf: Error: evil_sw_stack.a: did not find a valid archive header
Interestingly though, this seems to be getting somewhere:
$ arm-none-eabi-ar x evil_sw_stack.a
Now I've got a bunch of object files which do have ELF headers according to readelf, and yup, they did compile a startup file (of another of their devices) into the library... I'm wondering why, but I think this is a mistake.
This also works:
$ arm-none-eabi-objdump -t evil_sw_stack_objfile.o
So now the question is, is it safe to try to compile these object files into my own application using GCC? According to this other SO question, the object file formats are not compatible.
I assume that the startup code is mistakenly compiled into the library. I can delete it:
$ arm-none-eabi-ar d evil_sw_stack.a startup_(otherdevicename).o
$ arm-none-eabi-ar d evil_sw_stack.a system_(otherdevicename).o
Now I get an evil_sw_stack.a which gcc can accept as an input without complaining.
However, there is one thing that still worries me. When I use the object files instead of the static library, I get these warnings:
/usr/lib/gcc/arm-none-eabi/5.2.0/../../../../arm-none-eabi/bin/ld: warning: evil_objfile.o uses 2-byte wchar_t yet the output is to use 4-byte wchar_t; use of wchar_t values across objects may fail
/usr/lib/gcc/arm-none-eabi/5.2.0/../../../../arm-none-eabi/bin/ld: warning: evil_objfile.o uses 32-bit enums yet the output is to use variable-size enums; use of enum values across objects may fail
So it seems that evil_sw_stack.a was compiled with (the IAR equivalents of) -fno-short-enums and -fshort-wchar. GCC doesn't complain about this when I use evil_sw_stack.a at its command line but it does complain when I try to use any object file that I extracted from the library. Should I worry about this?
I don't use wchar_t in my code so I believe that one doesn't matter, but I would like to pass enums between my code and the library.
Update
Even though the linker doesn't complain, it doesn't work when I actually call some functions from the static library. In that case, make sure to put the libraries in the correct order when you call the linker. According to the accepted answer to this question, they need to be in reverse order of dependency. After doing this, it still misses some IAR crap:
undefined reference to `__aeabi_memclr4'
undefined reference to `__aeabi_memclr'
undefined reference to `__aeabi_memmove'
undefined reference to `__aeabi_memset4'
undefined reference to `__aeabi_memset'
undefined reference to `__iar_vla_alloc2'
undefined reference to `__iar_vla_dealloc2'
undefined reference to `__aeabi_memclr4'
I've found out that the __aeabi functions are defined in libgcc but even though I link to libgcc too, the definition in libgcc doesn't seem to be good enough for the function inside evil_sw_stack.a.
EDIT: after some googling around, it seems that arm-none-eabi-gcc doesn't support these specific __aeabi functions. Take a look at this issue.
Anyway, after taking a look at ARM's runtime ABI docs, the missing __aeabi functions can be trivially implemented using their standard C library equivalents. But I'm not quite sure how __iar_vla_alloc2 and __iar_vla_dealloc2 should work and couldn't find any documentation on them online. The only thing I found out is that VLA means "variable length array".
So, it seems that this will never work unless the chip vendor can compile their static library in such a way that it doesn't use these symbols. Is that right?
Disclaimer
I'd prefer not to disclose who the vendor is and not to disclose which product I work with. They are not proud that this thing doesn't work properly and asked me not to. I'm asking this question to help and not to discredit them.

How to include syscalls.c from a separate library file?

My code includes indirect calls to functions like _write() and _sbrk(). Within the project I have a file called syscalls.c that defines my custom implementations of these functions, and the compiler/linker finds this file, and links to the functions correctly when I run make. The compile line make creates looks something like this:
arm-none-eabi-gcc -nostartfiles -mcpu=arm7tdmi -Wl,--gc-sections -Wl,--cref -L../hardware_drivers/lib -L../framework/lib -T../linker-script.lds -Wl,-Map,./build/bin/Mapfile.map -o build/bin/Elffile.elf ./build/obj/Main.o ./build/obj/SomeCode.o ./build/obj/syscalls.o -Wl,--start-group -lhardware_drivers -lframework -Wl,--end-group
This works perfectly. However, I want to move syscalls.c to the hardware_drivers project that I have, so they should be included in the libharware_drivers.a file that gets created when I compile hardware_drivers, and included by the gcc line above. Moving the file and recompiling all my projects does include my syscalls.c in the .a file (shown using arm-none-eabi-ar). However, when it comes to compile my top level project, I get this error:
../../arm-none-eabi/lib/libc.a(lib_a-fstatr.o): In function `_fstat_r':
fstatr.c:(.text._fstat_r+0x1c): undefined reference to `_fstat'
I'm using arm-none-eabi-gcc v4.8 from code sourcery and I'm compiling it for a AT91SAM7A1 chip, if that has any relevance.
Is there something special you need to do to point the linker to syscalls when it's in a separate library file?
You use,
-nostartfiles
You create a file with _fstat in libhardware_drivers.a
You use some code with _fstat_r calling ../../arm-none-eabi/lib/libc.a(lib_a-fstatr.o)
Here is the error message,
../../arm-none-eabi/lib/libc.a(lib_a-fstatr.o): In function '_fstat_r':
fstatr.c:(.text._fstat_r+0x1c): undefined reference to '_fstat'
You can try to find the code which uses the _fstat_r from a map file or with -nodefaultlibs or -nostdlibs. The issue is that libraries are resolved in a first to last order. You have an implicit -lc at the end of your linker list. If you intend to use the the 'C' library, then you must alter your linker command to position libhardware_drivers.a later in the link.
For example,
arm-none-eabi-gcc -nostartfiles -mcpu=arm7tdmi -Wl,--gc-sections -Wl,--cref\
-L../hardware_drivers/lib -L../framework/lib -T../linker-script.lds \
-Wl,-Map,./build/bin/Mapfile.map -o build/bin/Elffile.elf ./build/obj/Main.o\
./build/obj/SomeCode.o ./build/obj/syscalls.o \
-Wl,--start-group -lc -lhardware_drivers -lframework -Wl,--end-group
Here, a -lc is placed before -lhardware_drivers. This will let the linker resolve the lib_a-fstatr.o reference to _fstat in your syscall.o. Another way is to force some synthetic reference earlier in another object file (like Main.o). A macro can force the link,
#define FORCE_LINK(x) void* __ ## x ## _force_link =(void*)&x
FORCE_LINK(fstat);
Most likely you have circular references in your static libraries. Ie, hardware_drivers refers to framework refers to libc (and libc refers back to hardware_drivers to make things work). Methods to overcome this are to list the libraries multiple times on the command line or restructure your code which is probably better long term.
The restructure is as simple as a separate libsyscall.a, which is listed after -lc.

Trying to understand the main function with GCC and Windows

They say that main() is a function like any other function, but "marked" as an entry point inside the binary, an entry point that the operating system may find (Don't know how) and start the program from there. So, I'm trying to find out more about this function. What have I done? I created a simple .C file with this code inside:
int main(int argc, char **argv) {
return (0);
}
I saved the file, installed the GCC compiler (in Windows, MingW environment) and created a batch file like this:
gcc -c test.c -nostartfiles -nodefaultlibs -nostdlib -nostdinc -o test.o
gcc -o test.exe -nostartfiles -nodefaultlibs -nostdlib -nostdinc -s -O2 test.o
#%comspec%
I did this to obtain a very simplistic compiler and linker, no library, no header, just the compiler. So, the compiling goes well but the linking stops with this error:
test.c:(.text+0xa): undefined reference to '___main'
collect2.exe: error: Id returned 1 exit status
I thought that the main function is exported by the linker but I believed that you didn't need any library with additional information about it. But it looks like it does. In my case I supposed that it must be the standard GCC library, so I downloaded the source code of it and opened this file: libgcc2.c
Now, I don't know if that is the file where the main function is constructed to be linked by GCC. In fact, I don't understand how the main function is used by GCC. Why does the linker need the gcc standard libraries? To know what about main? I hope this has made my question quite specific and clear. Thanks!
When gcc puts together all object files (test.o) and libraries to form a binary it also prepends a small object (usually crt0.o or crt1.o), which is responsible for calling your main(). You can see what gcc is doing, when you add -v on the command line:
$ gcc -v -o test.exe test.o
crt0/crt1 does some setup and then calls into main. But the linker is finally responsible for building the executable according to the OS. With -v you can see also an option for the target system. In my case it's for Linux 64 bit: -m elf_x86_64. For your system this will be something like -m windows or -m mingw.
The error happens because you use these two options: -nodefaultlibs -nostdlib
These tell GCC that it should not link your code against libc.a/c.lib which contains the code which really calls main(). In a nutshell, every OS is slightly different and most of them don't care about C and main(). Each has their own special way to start a process and most of them are not compatible with the C API.
So the solution of the C developers was to put "glue code" into the C standard library libc.a which contains the interface which the OS expects, creates the standard C environment (setting up the memory allocation structures so malloc() will map the OS's memory management functions, set up stdio, etc) and eventually calls main()
For C developers, this means they get a libc.a for their OS (along with the compiler binaries) and they don't need to care about how the setup works.
Another source of confusion is the name of the reference. On most systems, the symbolic name of main() is _main (i.e. one underscore) while __main is the name of an internal function called by the setup code which eventually calls the real main()

Statically linking against LAPACK

I'm attempting to do a release of some software and am currently working through a script for the build process. I'm stuck on something I never thought I would be, statically linking LAPACK on x86_64 linux. During configuration AC_SEARCH_LIB([main],[lapack]) works, but compilation of the lapack units do not work, for example undefiend reference to 'dsyev_' --no lapack/blas routine goes unnoticed.
I've confirmed I have the libraries installed and even compiled them myself with the appropriate options to make them static with the same results.
Here is an example I had used in my first experience with LAPACK a few years ago that works dynamically, but not statically: http://pastebin.com/cMm3wcwF
The two methods I'm using to compile are the following,
gcc -llapack -o eigen eigen.c
gcc -static -llapack -o eigen eigen.c
Your linking order is wrong. Link libraries after the code that requires them, not before. Like this:
gcc -o eigen eigen.c -llapack
gcc -static -o eigen eigen.c -llapack
That should resolve the linkage problems.
To answer the subsequent question why this works, the GNU ld documentation say this:
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 libraryz' after
file foo.o but before bar.o. If bar.o refers to functions in `z',
those functions may not be loaded.
........
Normally the files found this way are library files—archive files
whose members are object files. The linker handles an archive file by
scanning through it for members which define symbols that have so far
been referenced but not defined. But if the file that is found is an
ordinary object file, it is linked in the usual fashion.
ie. the linker is going to make one pass through a file looking for unresolved symbols, and it follows files in the order you provide them (ie. "left to right"). If you have not yet specified a dependency when a file is read, the linker will not be able to satisfy the dependency. Every object in the link list is parsed only once.
Note also that GNU ld can do reordering in cases where circular dependencies are detected when linking shared libraries or object files. But static libraries are only parsed for unknown symbols once.

Static library symbols not being found even with -l

I have a static library, liborc-0.4.a with no shared library. I have another library, libschroedinger-1.0.a (no shared) that depends on symbols in liborc-0.4.a. If I run nm on liborc-0.4.a, symbols such as orc_init show up as T (meaning they are defined). I built libschroedinger-1.0.a with the command line flag -lorc-0.4 so it saw the symbols and was ok.
However, now I have a small executable that depends on libschroedinger-1.0.a. It compiles fine, but when I run the linker
gcc -lschroedinger-1.0 -lorc-0.4 -o output input.o
It gives errors such as:
/usr/local/lib/libschroedinger-1.0.a(libschroedinger_1.0_la-schro.o):schro.c:(.text+0x21):
undefined reference to `orc_init'
gcc is sensitive to the order of libraries. When it's compiling liborc-0.4.a in, there is no need for orc_init, so it's not included. The solution is to put the LDFLAGS at the end of the command:
gcc -o output input.o -lschroedinger-1.0 -lorc-0.4
You most probably compiled libschroedinger with shared liborc. Static library is the same as bunch of object files in an archive, so they don't need to see more than headers. Write like the following to be sure (the same apples to liborc).
gcc /path/to/libschroedinger-1.0.a /path/to/liborc-0.4.a -o output input.o

Resources