8

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();
}
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
4ntoine
  • 17,607
  • 16
  • 70
  • 175

2 Answers2

7

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.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Alec
  • 481
  • 3
  • 16
  • that makes me think that Clang actually does not support weak linking or having a bug – 4ntoine Jan 25 '16 at 13:57
  • 1
    I tend to agree that there's a bug, but I think it's in the OS X version of ld rather than clang. If you inspect the generated binaries (`dyldinfo -build a.out`), with either of these workarounds, I believe it shows the symbol as a "weak import", which is what you want. The problem is just that ld doesn't properly handle a weakly imported symbol if it's not defined at build time. – Alec Jan 25 '16 at 14:44
  • Thanks, the `-Wl,-U,_myfunc` works great! Just took me a little time to figure out how to configure it from Xcode, that is: add `-Wl,-U,_myfunc` to "Other Linker Flags". – Elist Nov 15 '16 at 10:10
5

It fails by design because the linker doesn't have enough information. Specifically, it doesn't work because of a combination of two default linker settings:

-two_levelnamespace

-two_levelnamespace instructs the linker to bind external symbols both by name and by library install path. When used, the linker associates symbols to libraries based on where it finds them at link-time, given the set of libraries that it was passed. If the linker doesn't find the symbol, then it won't know which library it came from.

You can turn off two-level namespacing with -flat_namespace, but in general, I think that it's a good practice to leave it on.

Linux's ld.so does not support two-level namespacing, so this is not a problem. Every undefined symbol is assumed to have a definition in some library, to be discovered at runtime.

-undefined error

The -undefined setting determines how to handle symbols that have no definition visible at link-time, and the default is to error out. The other sensible option is dynamic_lookup, which tells the dynamic linker to figure out where the symbol is on its own.


Changing either of these settings will solve your problem, but it is heavy-handed. You can also tell the linker to use dynamic lookup for specific symbols and keep the default to error by passing -U _my_weak_func to ld, or -Wl,-U,_my_weak_func to Clang (which tells it to pass it forward to the linker). The _ symbol name prefix is necessary.

You can craft a tbd file and use it in place of dynamic libraries to tell the linker exactly where a weak symbol would be found if it was implemented instead of forcing weak functions to use dynamic lookup. Apple uses tbd files for its libraries and frameworks, which is what allows weak linking to work. The process is somewhat tedious, though, because Apple doesn't offer tools to automatically create tbd files for libraries. You'd need to pass a file of the following format as a library to the compiler:

--- !tapi-tbd-v3
archs:           [ $ARCH ]
uuids:           [ '$ARCH: $UUID' ]
platform:        $ARCH
install-name:    $INSTALL_PATH
current-version: $CURRENT_VERSION
objc-constraint: none
exports:         
  - archs:           [ $ARCH ]
    symbols:         [ _my_weak_func ]
...

Where:

  • $ARCH is the architecture name of the thing you want to build (like "x86_64", without quotes)
  • $UUID can be queried with otool -l $path_to_your_lib | grep -A 2 LC_UUID
  • $INSTALL_PATH and $CURRENT_VERSION can be queried with otool -l $path_to_your_lib | grep -A 4 LC_ID_DYLIB

This will let the linker know which library should contain your weak symbol.

zneak
  • 124,558
  • 39
  • 238
  • 307
  • 2
    Actually Apple offers such a tool. It's deep in the Xcode, as most of them: `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/tapi stubify <<>` – zepar Feb 17 '20 at 06:55