Does LLVM/Clang support the 'weak' attribute for weak linking?

4ntoine picture 4ntoine · Oct 1, 2015 · Viewed 8.4k times · Source

In brief: does LLVM/Clang support the 'weak' attribute?

I'm learning some Arduino library sources (HardwareSerial.cpp to be more detailed) and I've found some interesting attribute weak that I've never used before:

#if defined(HAVE_HWSERIAL0)
  void serialEvent() __attribute__((weak));
  bool Serial0_available() __attribute__((weak));
#endif

I've found it interesting and I've read that the linker should set it to NULL if it's not defined.

However, in my tests with Clang I'm unable to use it.

File lib.cpp:

#include "lib.h"
#include <stdio.h>

void my_weak_func() __attribute__((weak));

void lib_func() {
    printf("lib_func()\n");

    if (my_weak_func)
        my_weak_func();
}

File lib.h:

#ifndef LIB_FUNC
#define LIB_FUNC

void lib_func();

#endif

File main.cpp:

#include "lib.h"
#include <stdio.h>

#ifdef DEFINE_WEAK
void my_weak_func() {
    printf("my_weak_func()\n");
}
#endif

int main() {

    lib_func();

    printf("finished\n");
    return 0;
}

If I use g++ lib.cpp main.cpp -o main -DDEFINE_WEAK I'm able to use it:

MBA-Anton:Weak_issue asmirnov$ ./main
lib_func()
my_weak_func()
finished

But if I use g++ lib.cpp main.cpp -o main I'm unable to link the application:

Undefined symbols for architecture x86_64:
  "my_weak_func()", referenced from:
      lib_func() in lib-ceb555.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

To be more detailed about Clang:

MBA-Anton:Weak_issue asmirnov$ g++ --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/c++/4.2.1
Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn)
Target: x86_64-apple-darwin14.3.0
Thread model: posix

What should I do? Is the weak attribute supported by LLVM/Clang?

PS. I've already tried to rewrite lib.cpp in the way Apple describes and still get the same linker error:

#include "lib.h"
#include <stdio.h>

extern void my_weak_func() __attribute__((weak_import));

void lib_func() {
    printf("lib_func()\n");

    if (my_weak_func != NULL)
        my_weak_func();
}

Answer

Alec picture Alec · Jan 25, 2016

It seems that (best as I can tell), Apple's description of weak linking is misleading. I've only had success marking a function as weak/weak_import if the definition is actually available at link time. This is opposite the usual Linux behavior where a weakly linked symbol need not be defined at link time.

For example, the following compiles on Ubuntu 14.04 with GCC 4.8.2, but not on Mac OS X v10.9.5 (Mavericks) with Clang:

/* test.c */
int weakfunc() __attribute__((weak));

int main()
{
    if (weakfunc) return weakfunc();
    else        return -1;
}

The easiest workaround I've found is to explicitly tell the linker to leave the symbol in question undefined. For example, clang test.c -Wl,-U,_myfunc. Note the the name of the symbol will differ between C and C++. In C (at least for me, I assume this is consistent), the symbol name has an underscore prepended as shown here. In C++ the name is mangled, so you get something like __Z8weakfuncv (not necessarily consistent - I only get one leading underscore on the mangled name on my Ubuntu box).

Following this approach, if the function is defined at runtime (e.g. through a library preloaded by setting the DYLD_INSERT_LIBRARIES environment variable or if the version of a shared library dependency is different at runtime than it was at build time), the symbol will be resolved and the function called as desired. If the symbol isn't defined at runtime, then the check for the function fails and we continue on to return -1 as desired.

A somewhat more complex solution is to link against a dummy library that provides an implementation of the function in question. For example, if you compile the following as libdummy.dylib in the same directory:

int weakfunc()
{
    return 1;
}

You can weakly link against it

clang test.c -weak_library ./libdummy.dylib -flat_namespace

The symbol is then defined at link time, so the linker is happy, and will be marked as weakly linked in the resulting binary. By linking libdummy.dylib with -weak_library rather than with the standard -l/-L linking, the library dependency itself is weak, so the executable will still run even if libdummy.dylib is not available at runtime.

The -flat_namespace argument tells the linker to use a "flat" namespace rather than a "two-level" namespace, which is apparently the default on OS X. In a two-level namespace, each linked symbol is marked with the library that it came from, so without this the linker would only accept a version of weakfunc from a library called libdummy.dylib. Note that in the first case of marking the symbol as undefined, that symbol is treated as being from a flat namespace, since the linker has no idea what library it might be in at runtime.