Skip to content
Advertisement

When using the GCC driver, what makes a static lib “incompatible”?

So what I am trying to do is on Ubuntu 14.04 (x86_64) I want to set up musl-libc based on the latest released 1.1.11 version which is available at this moment.

What I did was to:

  1. Install multilib support for GCC: sudo apt-get --no-install-recommends install gcc-multilib
  2. Configure the libraries for 32-bit and 64-bit respectively and install them into separate folders:
    • CFLAGS=-m32 ./configure --prefix=$HOME/bin/musl-32-bit --disable-shared --target=i386-linux-gnu && make && make install
    • CFLAGS=-m64 ./configure --prefix=$HOME/bin/musl-64-bit --disable-shared --target=x86_64-linux-gnu

Then in order to build a statically linked premake4, I invoke GNU make like this on the Makefile generated by premake4:

JavaScript

This appears to work up to the linking step, which bombs with:

JavaScript

The relevant line is:

JavaScript

Now the part I don’t understand about this is, that when I ar x the libc.a (into a folder $HOME/bin/musl-32-bit/lib/libc) generated during the build step of musl-libc (see above), it proves that all of the objects included seem to be of the correct target architecture (all show ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped) as I can prove from coming up empty when issuing the following command:

JavaScript

And in fact this gives no output. Similarly when looking inside the build directory using the same method, I cannot find any object file that doesn’t match my expectation.

For good measure I decided to also task objdump to tell me more about the libc.a in question and came up with the same result:

JavaScript

So my question is twofold:

  1. what disqualifies a static library as “incompatible” when GCC is asked to link it?
  2. what could be the particular issue I am seeing?

The first is what I am really interested in, but with the second I am asking to share your experience with trouble-shooting like this. Which verification steps have I missed, for example?


Please note that the “native” premake4 builds just fine with:

JavaScript

From the output when adding the -v flag to LDFLAGS it appears as if the target always stays at x86_64-linux-gnu. I have yet to come up with a method to fix this, though.

Advertisement

Answer

The solution

It turns out the solution is rather simple.

We need to tell both the linker and the driver to use -m32 … I was that far before. However, it turns out that the missing piece was to pass the linker option to the driver via CFLAGS like this -Wl,-melf_i386.

I am finally able to build and link the 32-bit executable on a multilib-enabled 64-bit host.


NB: Below information is left in place for those who want to learn how I investigated the issue.

Alright, so I investigated the issue a little further and the output gets more enlightening once you extract the object files. To reproduce what I am doing you may have to use Bash or a similar shell that allows for $(...) or you need to adjust the command lines accordingly.

First off it’s important to have gcc-multilib and friends installed in order to target -m32 (i386-linux-gnu which happens to be an alias for i686-linux-gnu here).

The code and the make file

I had the following make file:

JavaScript

and the following small helloworld.c:

JavaScript

and my 32-bit musl-libc was installed to $HOME/bin/musl/{bin,include,lib}32 respectively. The 64-bit one was installed to $HOME/bin/musl/{bin,include,lib}64 respectively.

Attempting to build with:

JavaScript

always failed with the same meaningless lines:

JavaScript

Digging into the details

So after some contemplation I decided to re-do the steps done by the gcc driver manually.

This meant to run roughly (I replaced all occurrences of my home folder with a ~):

  • Compile: /usr/lib/gcc/x86_64-linux-gnu/4.8/cc1 -quiet -imultilib 32 -imultiarch i386-linux-gnu helloworld.c -nostdinc -isystem ~/bin/musl/include32 -isystem /usr/lib/gcc/x86_64-linux-gnu/4.8/include -quiet -dumpbase helloworld.c -m32 -mtune=generic -march=i686 -auxbase helloworld -version -fstack-protector -Wformat -Wformat-security -o /tmp/ccGmMuR1.s
  • Assemble: as -v -v --32 -o /tmp/ccgRGlqf.o /tmp/ccGmMuR1.s
  • Link: env COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.8/:/usr/lib/gcc/x86_64-linux-gnu/4.8/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.8/:/usr/lib/gcc/x86_64-linux-gnu/ LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.8/32/:/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../lib32/:/lib/../lib32/:/usr/lib/../lib32/:/usr/lib/gcc/x86_64-linux-gnu/4.8/:/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../:/lib/:/usr/lib/ COLLECT_GCC_OPTIONS="-v -v -static -m32 -o helloworld -specs=~/bin/musl/lib32/musl-gcc.specs -mtune=generic -march=i686" /usr/lib/gcc/x86_64-linux-gnu/4.8/collect2 -dynamic-linker /lib/ld-musl-i386.so.1 -nostdlib -static -z relro -o helloworld ~/bin/musl/lib32/crt1.o ~/bin/musl/lib32/crti.o /usr/lib/gcc/x86_64-linux-gnu/4.8/32/crtbegin.o -L~/bin/musl/lib32 -L /usr/lib/gcc/x86_64-linux-gnu/4.8/32/. /tmp/ccpL09mJ.o --start-group /usr/lib/gcc/x86_64-linux-gnu/4.8/32/libgcc.a /usr/lib/gcc/x86_64-linux-gnu/4.8/32/libgcc_eh.a -lc --end-group /usr/lib/gcc/x86_64-linux-gnu/4.8/32/crtend.o ~/bin/musl/lib32/crtn.o

Obviously this didn’t change the error message a bit just yet:

JavaScript

So I took out the -lc from the command line for collect2 and created a folder ~/bin/musl/lib32/archive into which I extracted the whole libc.a generated by my musl-libc build attempt. I then instructed collect2 where to find the object files like this:

Link: env COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.8/:/usr/lib/gcc/x86_64-linux-gnu/4.8/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.8/:/usr/lib/gcc/x86_64-linux-gnu/ LIBRARY_PATH=/usr/lib/gc c/x86_64-linux-gnu/4.8/32/:/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../lib32/:/lib/../lib32/:/usr/lib/../lib32/:/usr/lib/gcc/x86_64-linux-gnu/4.8/:/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../:/lib/:/usr/lib/ COLLECT_GCC_OPTIONS="-v -v - static -m32 -o helloworld -specs=~/bin/musl/lib32/musl-gcc.specs -mtune=generic -march=i686" /usr/lib/gcc/x86_64-linux-gnu/4.8/collect2 -dynamic-linker /lib/ld-musl-i386.so.1 -nostdlib -static -z relro -o helloworld $HOME/bin/musl/lib32/crt1.o ~/bin/musl/lib32/crti.o /usr/lib/gcc/x86_64-linux-gnu/4.8/32/crtbegin.o -L~/bin/musl/lib32 -L /usr/lib/gcc/x86_64-linux-gnu/4.8/32/. /tmp/ccpL09mJ.o --start-group /usr/lib/gcc/x86_64-linux-g nu/4.8/32/libgcc.a /usr/lib/gcc/x86_64-linux-gnu/4.8/32/libgcc_eh.a --end-group /usr/lib/gcc/x86_64-linux-gnu/4.8/32/crtend.o ~/bin/musl/lib32/crtn.o $(find ~/bin/musl/lib32/archive -type f -name '*.o')

This gist is taking out -lc and appending $(find ~/bin/musl/lib32/archive -type f -name '*.o').

Which gave me a whole bunch of new, but more meaningful errors similar to the following ones:

JavaScript

The plot thickens. Apparently the collect2 command gets the wrong idea about what to build. Which is odd considering the output for the governing environment variables:

JavaScript

… which I passed using env in my attempt to reproduce the conditions encountered by the collect2 wrapper when linking the libc.a.

In order to find out more about the internals of the GNU compilers one needs to read https://gcc.gnu.org/onlinedocs/gccint/

Unfortunately the part about collect2 doesn’t help us here. But the lengthy output of /usr/lib/gcc/x86_64-linux-gnu/4.8/collect2 --help looks promising.

Just how could we smuggle our command line option to collect2?

For now I was going to settle for whatever I could run manually. So I attempted to tell the linker which output format I expected. Based on the list of supported targets I was interested in elf_i386.

Passing -melf_386 at the end of the previous line gave an interesting error output. Numerous referenced functions such as __vsyscall, __moddi3 and __divdi3 were undefined. That indicated they simply didn’t exist in the object files from the static lib or in any of the startup .o files for that matter:

JavaScript

As I had already clarified in my question, the object files from the archive all stated that they were elf32-i386.

The functions __vsyscall and __vsyscall6 should end up in a file called syscall.o given the source file for i386 in musl-libc: src/internal/i386/syscall.s. Let’s verify that first. Since there is also a file src/misc/syscall.c the name might be different though. Four files have syscall in the file name:

  • __syscall_cp.o
  • syscall_cp.o
  • syscall.o
  • syscall_ret.o

Querying those files using nm gave:

JavaScript

Symbols with the symbol type U are undefined and therefore expected by the linker to come from outside (external to each object file).

A final $ nm --defined-only *.o ../*.o|grep vsyscall was what was needed to verify that those symbols were indeed missing from the libc.a.

So the cross-built libc.a what was faulty after all. Back to the drawing board.

I hope this description helps others to figure out similar issues and look behind the scenes in GCC.

The saga continues

I was really surprised to see:

JavaScript

but for the extracted object files the corresponding command (nm --defined-only *.o ../*.o|grep -B 2 vsyscall) would yield no output.

So inside the libc.a somehow nm sees the two symbols, but after extracting them they disappear? Odd.

Let’s look for syscall.o in the libc.a:

JavaScript

Whoa? So syscall.o exists twice inside the static library? Well that looks like it may just be the error cause we’re looking for. And it certainly does explain why the symbols disappear. Likely the latter syscall.o overwrites the one first extracted when running ar x ....

Confirming:

JavaScript

and looking into the musl-libc source tree after doing the 32-bit build:

JavaScript

Copying the former into the lib32/archive directory under a name that doesn’t collide with existing names gives more errors on other functions, suggesting other object files may also exist as duplicates inside the generated libc.a.

Which ones are duplicates?

JavaScript

This way we see clone.o and syscall.o are affected as duplicates. Which indicates that some object files are missing altogether from the libc.a given undefined references to the following symbols:

  • __divdi3
  • __moddi3
  • __mulxc3
  • __tls_get_new
  • __udivdi3
  • __umoddi3

These names happen to coincide with the ones from the Integer library routines listed for GCC. Which makes me think I am missing one library provided by GCC which to link. Like libgcc?! …

I have package lib32gcc-4.8-dev which means I should have the required file:

JavaScript

After the previous step I decided to set up a x86_32 version on Ubuntu 14.04 that was essentially at the same patch level.

I then compared the libgcc.a from the x86_32 machine with that from the x86_64 machine. They turned out to be almost identical except for the “symbol value” (in nm lingo) of a handful of functions.

Also, since the symbols were in the static library I attempted to link against the static library again. This worked only with -melf_i386 on the linker (collect2) command line.

After trying to use LDFLAGS and noticing that those were also passed to cc1, I set appended -Wl,-melf_i386 to the CFLAGS and now it worked. Brilliant.


As a side-note: I also swapped the /usr/lib/gcc/x86_64-linux-gnu/4.8/collect2 in the command line for ld of which collect2 is supposed to be a wrapper. The error output was identical.

User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement