Debugging and profiling libtool binaries

libtool takes care of a lot of the details of building binaries and shared libraries for you, but one of the side effects of this is that until you install your binaries the binaries in your build directory are actually shell script wrappers. Running gdb or profiling tools on the shell script won’t work:

# gdb ./mybinary 
GNU gdb (GDB) Fedora 7.5.1-42.fc18
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
...
"/home/will/mybinary": not in executable format: File format not recognized
(gdb)

However, you can get libtool to help you:

# libtool --mode=execute gdb ./mybinary
GNU gdb (GDB) Fedora 7.5.1-42.fc18
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
...
Reading symbols from /home/will/.libs/lt-mybinary...done.
(gdb)

This trick will also work for other tools like gprof and perf.

Using GNU indirect functions

GNU indirect functions are an extension of ELF that allows you to make a decision about which implementation of a function to use at link time. This allows you to choose the fastest code for a particular processor, which is very useful if you’re shipping your code as binaries. It involves an ELF symbol type (STT_GNU_IFUNC) and a new dynamic relocation (R_*_IRELATIVE) which have been added to the GNU ld and gold linkers and the glibc dynamic linker. Support exists for arm, powerpc, s390, sparc, i386 and x86_64 with aarch64 support coming soon.

Indirect functions aren’t used very widely at the moment, as far as I can tell only glibc uses them at the moment for things like string functions, but they can be used by any other application or library. I’ll try and explain with a short example how they can be used.

#include 
#include  /* Needed for HWCAP definitions. */

static void func1_neon(void)
{
	printf("NEON implementationn");
}

static void func1_vfp(void)
{
	printf("VFP implementationn");
}

static void func1_arm(void)
{
	printf("ARM implementationn");
}

/* This line makes func1 a GNU indirect function. */
asm (".type func1, %gnu_indirect_function");

void *func1(unsigned long int hwcap)
{
	if (hwcap & HWCAP_ARM_NEON)
		return &func1_neon;
	else if (hwcap & HWCAP_ARM_VFP)
		return &func1_vfp;
	else
		return &func1_arm;
}

This code defines a resolver function, func1, which returns a pointer to a function implementation based on the value of hwcap that is passed to it. The value of hwcap is architecture specific and on some architectures, like x86_64, it is not passed at all, and you have to determine hardware capabilities by hand.

int main(void)
{
	func1();
	return 0;
}

This main function must be defined in a separate file to prevent the compiler from getting the wrong type for func1. Somewhat confusingly the function called func1 from the caller’s perspective is of the type of its implementations rather than of the type of the indirect function. All indirect functions have the same prototype – they take an optional hwcap argument and return a pointer to a function.

Build these two pieces of code and link them together:


# gcc -O2 ifunc.c -c
# gcc -O2 main.c -c
# gcc main.o ifunc.o -o ifunc
# ./ifunc
NEON implementation
#

This is the result I get on my ARM Chromebook, your result will vary depending on which hardware you actually have. So why is this better than the alternatives? You could dispatch to the optimized routine dynamically yourself, but that adds an extra comparison and indirect call to every call to the function and you will also have to find the hardware capabilities yourself, which can be tricky. Alternatively you could build optimized libraries for every bit of hardware you support, but this takes up a lot of disk space and takes more significant infrastructure to dynamically open the correct library.

Indirect functions are not without their downsides however. They are currently supported only on Linux with the GNU toolchain and only on a subset of Linux supported architectures, and in some cases the tools are not very well tested yet – for example, at the moment this will only work correctly on ARM with the as yet unreleased glibc 2.18 due to a bug in the dynamic linker – but they are a powerful tool that deserves to be more widely used.