LTO and overriding stdlib functions in static libraries

334 views Asked by At

I have an embedded platform that brings its own stdlib functions like malloc and printf in a static library. I need to compile this library with LTO. Unfortunately in this combination (-flto + -nostdlib + linking with stdlib replacements from an .a) the linker cannot find the functions.

I have prepared a MWE that should run on most Unix machines but since it contains multiple files I have put it into a repo: https://github.com/stefanct/lto_static_libs

The included makefile allows to switch on some features on and off for testing:

  • nostdlib=y: add -nostdlib to the linking stage
  • nolto=y: disable LTO
  • libfunc=y: enable a call to a non-standard function within the library (you will see why at the end!)

The gist is to have one module containing a standard function, e.g.:

int puts(const char *s) {
  return 2;
}

Compiling that into an object file with -flto, putting it into a static library with gcc-ar and eventually using that when linking with an application.

In my setup (GCC 11 branch built from source and GNU ld 2.31.1 from Debian Buster) I get the following results:

No options set: OK - the printf from the library gets overridden(?) by the standard function:

$ make -B 
Using GCC 11.0.0
gcc -Wall -Wextra -Wno-unused-parameter -flto -ffat-lto-objects     -c -o libtest.o libtest.c
lto-dump -list libtest.o
Type   Visibility  Size  Name
function  default     4  lib_func  
function  default     4  puts  
function  default     4  printf  

gcc-nm libtest.o
00000000 T lib_func
00000000 T printf
00000000 T puts
rm -f libtest.a
gcc-ar -cvq libtest.a libtest.o
a - libtest.o
gcc -Wall -Wextra -Wno-unused-parameter -flto -ffat-lto-objects     -c -o main.o main.c
gcc -Wall -Wextra -Wno-unused-parameter -flto -ffat-lto-objects   -o exe main.o -L. -ltest 

$ ./exe 
hurga

No stdlib but also without LTO: OK - linking works fine(ish - running segfaults but that's to be expected I guess and could be worked around with -nodefaultlibs but I don't care here)

$ make -B nostdlib=y nolto=y
Using GCC 11.0.0
gcc -Wall -Wextra -Wno-unused-parameter     -c -o libtest.o libtest.c
gcc-nm libtest.o
0000000000000074 T lib_func
0000000000000000 T printf
0000000000000065 T puts
rm -f libtest.a
gcc-ar -cvq libtest.a libtest.o
a - libtest.o
gcc -Wall -Wextra -Wno-unused-parameter     -c -o main.o main.c
gcc -Wall -Wextra -Wno-unused-parameter   -o exe main.o -L. -ltest -nostdlib
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000

No stdlib but leaving LTO enabled: suddenly puts is no longer found. However, as you can see, the object file that gets put into the library contains the function just fine (and evengcc-mn libtest.a shows the same). This is the case I would like to fix. Why is this breaking?

$ make -B nostdlib=y 
Using GCC 11.0.0
gcc -Wall -Wextra -Wno-unused-parameter -flto -ffat-lto-objects     -c -o libtest.o libtest.c
lto-dump -list libtest.o
Type   Visibility  Size  Name
function  default     4  lib_func  
function  default     4  puts  
function  default     4  printf  

gcc-nm libtest.o
00000000 T lib_func
00000000 T printf
00000000 T puts
rm -f libtest.a
gcc-ar -cvq libtest.a libtest.o
a - libtest.o
gcc -Wall -Wextra -Wno-unused-parameter -flto -ffat-lto-objects     -c -o main.o main.c
gcc -Wall -Wextra -Wno-unused-parameter -flto -ffat-lto-objects   -o exe main.o -L. -ltest -nostdlib
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
/usr/bin/ld: /tmp/cczvyrrg.ltrans0.ltrans.o: in function `main':
<artificial>:(.text+0xe): undefined reference to `puts'
collect2: error: ld returned 1 exit status
make: *** [makefile:39: exe] Error 1

Interestingly enough, if we call another unrelated (non-standard) function in the same library things start to work again!?

$ make -B nostdlib=y libfunc=y
Using GCC 11.0.0
gcc -Wall -Wextra -Wno-unused-parameter -flto -ffat-lto-objects  -D LIB_FUNC   -c -o libtest.o libtest.c
lto-dump -list libtest.o
Type   Visibility  Size  Name
function  default     4  lib_func  
function  default     4  puts  
function  default     4  printf  

gcc-nm libtest.o
00000000 T lib_func
00000000 T printf
00000000 T puts
rm -f libtest.a
gcc-ar -cvq libtest.a libtest.o
a - libtest.o
gcc -Wall -Wextra -Wno-unused-parameter -flto -ffat-lto-objects  -D LIB_FUNC   -c -o main.o main.c
gcc -Wall -Wextra -Wno-unused-parameter -flto -ffat-lto-objects  -D LIB_FUNC -o exe main.o -L. -ltest -nostdlib
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000

Do I see a bug in binutils/ld? Is this fixed upstream?

0

There are 0 answers