0

I'm currently trying to compile Clang/LLVM for a bare metal aarch64 target. Compiling Clang was straightforward - in fact I have compiled to target multiple architectures including arm and aarch64. For the backend I'm using binutils. Since binutils can only target one architecture I've built both aarch64 and arm versions of this. Again building seemed to be straightforward. I built binutils for aarch64 using:

mkdir build
cd build
../configure --target=aarch64-none-elf
make

from an unpacked source package of binutils 2.24.

The problem I'm having is that I can't get my custom build of ld to handle archive files properly. It seems to find and open archive files without a problem but fails to search it for undefined symbols when compiling the final binary. Here's some more concrete details via an example:

Create a simple main.s file by compiling:

int foo();
int main() { return foo(); } 

with Clang and using --target=aarch64 -S, i.e. to emit an architecture specific assembly file.

Do the same to create foo.s by compiling:

int foo() { return 0; }

Assemble both .s files to .o files using custom gas build from binutils.

Create an archive libfoo.a by running custom ar and ranlib builds from binutils using ar cr foo.a foo.s and ranlib libfoo.a.

Try to link the two files together using ld -L. -lfoo -o main.elf main.o.

The output shows an undefined reference to foo.

bar.cpp:(.text+0x14): undefined reference to `foo()'

I can run readelf on foo.a and it seems perfectly well formed. In particular I can see the public symbol for foo. If I run ld with --verbose then I see

attempt to open ./libfoo.a succeeded

but no indication that it's found any symbols.

If I link directly with the object files, i.e. using ld -o main.elf foo.o main.o then I get a well formed elf file. Furthermore, if I extract foo.o back out of libfoo.a then I can also link succesfully with the extracted foo.o.

Does anyone have any ideas why this might be happening? I'm pretty sure my clang build is okay as the emitted assembly files effectively decouple the problem from clang. I'd guess also that the assembled .o files are fine. So this only leaves ar/ranlib or ld as the culprit.

To try to eliminate ar/ranlib as the culprits I rebuilt binutils for arm (same steps but using --target=arm-none-eabi). If I integrate the built ar/ranlib binaries into a known good arm-eabi-none GCC toolchain then they seem to work correctly. My suspicions thus point to ld at the moment. Note that I get the same problem with the main/foo example as above if I use my Clang build with the arm build of binutils.

I also tried integrating the arm version of ld into the existing known good GCC toolchain, but got:

this linker was not configured to use sysroots

Need to figure that out. That's still a bit cryptic for me right now. It prevents me from sanity checking the arm ld build.

It might well be that I'm doing something obviously wrong, but right now I don't see it.

Notes:

  • I had to hack the make script for binutils to add a couple of -Wno-xxx flags. I need to do this the "correct" way, but I don't think this hack should affect the output.

  • I'm building/hosting all tools on OSX 10.9.4 64-bit.

UPDATE:

The error about sysroots is simple. The pre-existing toolchain was configured with flags:

--with-prefix $SOME_INSTALL_DIR
--with-sysroot $SOME_INSTALL_DIR/arm-none-eabi

If I rebuild with these then I can slot in my ld version without a problem and it works. I got excited thinking that this might have been my mistake. Previously I wasn't running make install as I didn't really care about installing at this stage. I thought perhaps my new build of ld was referencing OSX system libs/exes somehow (although I'm not sure exactly what it could reference, other than perhaps ar, i.e. if it runs ar to handle archives). However I still have the same problem with both the arm and arrach64 versions of binutils even when configured like this.

Andrew Parker
  • 1,425
  • 2
  • 20
  • 28

1 Answers1

0

Looks like I was dramatically overthinking the problem and also missing something I'd never quite realised about ld. The issue was with the line:

 ld -L. -lfoo -o main.elf main.o

I hadn't realise that ld is sensitive to object file/library ordering. The undefined reference to foo() was in main.o. I specified the library libfoo.a as input before main.o is parsed. This means that the linker doesn't bother searching libfoo.a for the symbol as it's already processed this library. main.o must be specified before, e.g.

ld -L. main.o -lfoo -o main.elf

Even wrapping -lfoo with --start-group ... --end-group doesn't fix this.

I'm still feeling somewhat surprised by this. And I've yet to convince myself it's a good feature of ld. Seems like the only use case I can think of right now is to sneakily allow duplicate symbols.

Time for a cup of tea!

Andrew Parker
  • 1,425
  • 2
  • 20
  • 28