Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Force a linker to fail with a multiple definition error, even if including --whole-archive

This example consists of multiple files:

// baz.cxx
int wat = 0;
int counter = ++wat;
// foo.cxx (empty)
// bar.cxx (empty)
// main.cxx
#include <iostream>
extern int wat;
int main() {
    std::cout << wat << '\n';
}
// makefile
run : main.cxx foo.so bar.so
    g++ -std=c++11 $^ -o $@

baz.a : baz.cxx
    g++ -std=c++11 -c $^ -o baz.o -fPIC
    ar rcs $@ baz.o

%.so : %.cxx baz.a
    g++ -std=c++11 $< -Wl,--whole-archive baz.a -Wl,--no-whole-archive -o $@ -shared -fPIC

As-is, if you just run make && LD_LIBRARY_PATH=. ./run, everything with compile, build, link, run and output 2. This is because both foo.so and bar.so provide wat, and the initialization for counter is run twice.

Is there a way to somehow force run to fail to link with a multiple definition error in this case, while still ensuring that both foo.so and bar.so have a definition for wat?

like image 934
Barry Avatar asked Jul 11 '17 20:07

Barry


2 Answers

You could use linker scripts libfoo.so and libbar.so with a static part which creates a symbol conflict (and install the actual DSOs in some non-obvious place, so that -lfoo and -lbar do not pick them up).

Something like this:

  • libfoo.so

    INPUT(conflict.o)
    INPUT(./foo.so)
    
  • libbar.so

    INPUT(conflict.o)
    INPUT(./bar.so)
    
  • conflict.cxx

    int conflict;
    

This results in:

g++ -std=c++11 main.cxx -o run -L. -lfoo -lbar
conflict.o:(.bss+0x0): multiple definition of `conflict'
conflict.o:(.bss+0x0): first defined here
collect2: error: ld returned 1 exit status

This will only detect conflicts within the same run of the link editor. You could still link two different DSOs with -lfoo and -lbar, and link those two into the application, without an error from the link editor.

A similar problem is solved by ABI annotations, but those need specific ld changes, I think.

If a run-time check is acceptable, you could have a list of weak symbols defined by each DSOs which needs to conflict, and implement an ELF constructor which aborts the process if more than one of them is defined. I'm not aware of anything which achieves something equally reliable at static link time with the GNU toolchain. Maybe this is worth an RFE bug against binutils.

like image 195
Florian Weimer Avatar answered Nov 16 '22 02:11

Florian Weimer


I think that the only proper way to solve the problem is to move content of bar.a solution to some shared library and thus resolving diamond inheritance problem... but i think that's out of scope of this problem.

The only thing i can think for you is to create "custom" validator executed just before linking final executable. Stg like this:

nm *.so | \ # get all symbols
    grep " [B|T] " | \ # only exported ones
    egrep -v "__bss_start|_end|_fini|_init" | \ # filter out some commons, probably to be extended
    awk '{print $3}' | \ # get only symbol name
    sort | uniq -c | \ # sort and count
    egrep -v "^[ ]+1 " # get only those that have multiple definitions

This prints all strong (exported) symbols from libraries that are defined more than once. You can easily wrap it in a script that returns error status code if output is not empty with meaningful message.

Experimental version of your patched makefile would look like this:

run: main.cxx foo.so bar.so
    ! nm foo.so bar.so | grep " [B|T] " | egrep -v "__bss_start|_end|_fini|_init" | awk '{print $3}' | sort | uniq -c | egrep -v "^[ ]+1 "
    g++ -std=c++11 $^ -o $@

(note ! to reverse exit code of final grep that searches for any uniq -c output that doesn't start with bare 1)

I know that it's really hackish, ugly and non-portable solution but i thought it may be of some value in corner cases like yours.

like image 23
Zbigniew Zagórski Avatar answered Nov 16 '22 03:11

Zbigniew Zagórski