Search code examples
c++xcodemacosstripmach-o

Why OSX's strip can not remove weak symbols?


While trying to strip out unneeded things from my linked executable I've found some strange thing. Assume we have a simple, straightforward C++ program:

class Foo {
public:
    template <typename T>
    char* getPtr() {
        static char c;
        return &c;
    }
};

char* bar() {
    Foo i;
    return i.getPtr<int>();
}

int main() {
    bar();
} 

Binary being built with clang++ t.cc has next dynamic symbol table:

$ gobjdump -T ./a.out

./a.out:     file format mach-o-x86-64

DYNAMIC SYMBOL TABLE:
0000000100000f40 g       0f SECT   01 0000 [.text] __Z3barv
0000000100000f60 g       0f SECT   01 0080 [.text] __ZN3Foo6getPtrIiEEPcv
0000000100001020 g       0f SECT   08 0080 [.data] __ZZN3Foo6getPtrIiEEPcvE1c
0000000100000000 g       0f SECT   01 0010 [.text] __mh_execute_header
0000000100000f80 g       0f SECT   01 0000 [.text] _main
0000000000000000 g       01 UND    00 0200 dyld_stub_binder

Considering that it is an executable and not a dylib I would like to strip all entries except those for undefined symbols. Theoretically binary still will work since info about required dyld binding is still there and entry point is defined in some load command after mach-o header (so nothing to do with symbol table).

Trying strip gives some weird result:

$ strip ./a.out
$ gobjdump -T ./a.out

./a.out:     file format mach-o-x86-64

DYNAMIC SYMBOL TABLE:
0000000005614542      d  3c OPT    00 0000 radr://5614542
0000000100000f60 g       0f SECT   01 0080 [.text] __ZN3Foo6getPtrIiEEPcv
0000000100001020 g       0f SECT   08 0080 [.data] __ZZN3Foo6getPtrIiEEPcvE1c
0000000100000000 g       0f SECT   01 0010 [.text] __mh_execute_header
0000000000000000 g       01 UND    00 0200 dyld_stub_binder

Last two entries shouldn't be stripped anyway as they are required by dyld to handle this executable. At the same time we see that _main and __Z3barv are gone. But symbols from class Foo are still there. The only difference between them and stripped ones is that former ones have N_WEAK_DEF flag set (0080). Here is some scant info from <mach-o/nlist.h> about that flag:

 /*
 * The N_WEAK_DEF bit of the n_desc field indicates to the static and dynamic
 * linkers that the symbol definition is weak, allowing a non-weak symbol to
 * also be used which causes the weak definition to be discared.  Currently this
 * is only supported for symbols in coalesed sections.
 */
#define N_WEAK_DEF  0x0080 /* coalesed symbol is a weak definition */

Unfortunately it doesn't explain why strip ignores symbols with that flag.

So the question is - how to teach strip to remove even N_WEAK_DEF symbols in case user just doesn't want to export them.

P.S. I've looked into command options and haven't found anything useful (-N removes undefined symbols too, so it is not an option). Declaring that class with visibility("hidden") makes the puzzle, but unfortunately it is not easy to do with the real project.


Solution

  • My first conclusion would be to jump to it being a result of radar bug 5614542 hence that weird symbol, but it's not related to it.

    I'll draw some assumptions and guess from the fact that it seems that you're using nlist relocations and not new bytecode based relocations (you can check by looking for the dyld info load command), this is either built with an ancient toolchain or is a MH_OBJECT file for a main executable that has not gone through the final linking step. I'm not 100% sure if that is the case here- but either way,

    Sorry for my above assumption, but the original answer still applies unless you use really want to opt out of symbol coalescing in which case build your application with private linkage but again this template instantiation forces the symbol as weak for a very good reason, it has a static constructor and an implicitly instantiated template, it prefers safety so it keeps the symbol. You can not export it at all outside of the executable, while you have a small case here, C++ programs tend to use things like boost, or C++ libs that depend on other C++ libs, that all creates chains and eventually you end up with multiple definitions within the shared namespace just because of C++ semantics. In your small test case you can get away with it, in a larger application unless you really know what you're doing and examining things like dependency trees for dylibs, just let dyld do its job. I think my original answer still applies for a major part as it explains why your symbol is marked as weak (ODR is a C++ specific concept but it's dealt differently by different static linkers):


    For a longer explanation - it's to do with C++ semantics, namely the one definition rule (ODR) which is a close but not the same concept as not being able to have duplicate strong symbols in the same namespace (I mean a link namespace, not an C++ namespace, this gets confusing very quickly).

    If you want to know why it's marked as weak, it's for dyld to be able to coalesce it during dynamic linking, since reusing that template would instantiate it again (causing an ODR violation and depending on the context a link time error), as it's an implicit instantion, which may or may not require coalescing (which is not known until static or even dynamic link time, unless of course you define it as hidden in which case you have to be extremely careful since semantics will vary a lot depending on factors like whether it's a modular build or not (I mean LLVM "modules", not the Modules TS for C++).

    Without it being weak, you'd be causing an ODR violation per C++ rules by defining it as hidden across more than 1 translation unit (if you reused that template, say in a header within the module, you would get duplicate symbol errors). You could get away with violating ODR since it's not actually enforced, but be prepared for some nasty surprises (ie. by using non modular builds aka "every translation unit is a module").

    By defining it as weak, dyld is able to select correct definitions per final linked object be that a shared library or an executable (and don't forget about the shared cache) at runtime and bind/relocate them appropriately within the otherwise flat namespace.

    The above is a lot to be able to deduce by a compiler without any form of a hint, hidden linkage is a really bad idea unless you understand the implication, you want internal visibility if you really want to re-instantiate and copy the template every time. OSX has a fairly complicated linking model in general, a lot of landmines to step on potentially.

    And if I'm right about the object file thing, you shouldn't really run strip on object files before they are fed into the static linker.