Why the linker does not remove unused symbols when linking a static library before shared?

Viewed 796

I've got a static library, a dynamic library (which uses the static), and an executable (that uses both):

$ cat static.cpp
int use_static()
{
    return {};
}

void unused_in_static() {}
$ cat shared.cpp
void use_static();

void use_shared()
{
    use_static();
}

void unused_in_shared() {}
$ cat app.cpp
void use_shared();
void use_static();

int main()
{
    use_shared();
    use_static();
}

Then I build them:

#!/bin/bash -xe
COMPILE_OPTIONS="-ffunction-sections -fPIC"

g++ $COMPILE_OPTIONS -c -o static.o static.cpp
ar qc libstatic.a static.o

g++ $COMPILE_OPTIONS -c -o shared.o shared.cpp
g++ -Wl,-gc-sections -shared -o libshared.so shared.o libstatic.a

g++ $COMPILE_OPTIONS -c -o app.o app.cpp
g++ -Wl,-gc-sections -Wl,-rpath=. -o app app.o libstatic.a libshared.so

The strange thing is, that the compiled app contains the function unused_in_static which is not used anywhere:

readelf -s app | grep unused
    13: 000000000000079a     7 FUNC    GLOBAL DEFAULT   14 _Z16unused_in_staticv
    57: 000000000000079a     7 FUNC    GLOBAL DEFAULT   14 _Z16unused_in_staticv

I would expect that it will be removed (thanks to -ffunction-sections and -Wl,-gc-sections).

The same happens if I add the libstatic to the linker after libshared:

g++ -Wl,-gc-sections -Wl,-rpath=. -o app app.o libstatic.a libshared.so libstatic.a

But, surprisingly, if I reverse the order of libshared and libstatic, everything works ok:

g++ -Wl,-gc-sections -Wl,-rpath=. -o app app.o libshared.so libstatic.a
readelf -s app | grep unused

Why garbage collecting of the unused function depends on link order? Isn't the first or the second linking order correct?

1 Answers

Why the linker does not remove unused symbols when linking a static library before shared?

Because most linkers (e.g. GNU binutils on Linux) are managing entire object files, not individual functions.

If you use a recent GCC (e.g. GCC 10 in august 2021) and accept to spend a lot more computer time to build your software you might compile and link your application using g++ -flto -O2 (to enable link time optimizations) both when compiling C++ code and when linking your object files.

In that case (link time optimization), the "object files" also contain some GIMPLE representation of your code, and build time (and object file size) is approximately doubled.

I've got a static library, a dynamic library (which uses the static), and an executable (that uses both)

You better have (on Linux) your static library contain position -independent code. So compile each member of that library with -fPIC

On Linux, you could load at runtime some shared libraries with dlopen(3) and dlsym(3). In practice, you can load many thousands of them, and you could generate some shared library at runtime (e.g. using libgccjit).

Read more about the elf(5) format. You could use not only readelf(1) but also objdump(1) and nm(1) to inspect object files, shared libraries, and ELF executables.

In principle you could extend GCC and binutils with your own plugins (for GCC, read its runtime library license exception). In practice, you may need months of work to do so (and the performance win might be a few percents only). The HorizonEurope framework might provide partial funding (and you might then contact me by email to basile@starynkevitch.net and basile.starynkevitch@cea.fr if interested to be part of such a project, where I might use RefPerSys). But there is No silver bullet

A possibility (it takes lots of work) might be to develop a GCC plugin which compile every C++ function into a different object file (or member of a static library). So that plugin would split a single C++ translation unit in many smaller ones.

You could also (in theory) write your own C++ compiler and your own linker.

Consider using asmjit and read Levine's book Linkers and loaders and the Dragon book and the GC handbook.

Since all of binutils, GCC and LLVM are opensource software, you could improve each of them for your needs.

NB. In most cases, writing a GCC plugin, or your own compiler, or your own linker is not worth the efforts. In a few cases, you could spend a year of work doing so. Be sure to ask your manager/client/teacher if it is worth that much efforts (for a performance gain of a few percents). Consider getting the permission to make that an open source project. Perhaps start a PhD on that topic....

Related