Search code examples
linuxgcclinkerweak-symbol

__attribute__ ((weak)) not work for global variable


pqy@localhost ~/src/test/a $ cat m.c
#include <stdio.h>
int aaaaa __attribute__ ((weak)) =8;
int main(void){
    printf("%d\n", aaaaa);
    return 0;
}
pqy@localhost ~/src/test/a $ cat lib.c
int aaaaa = 5;

pqy@localhost ~/src/test/a $ gcc lib.c -fPIC -shared -o libb.so;gcc m.c -o m -L. -lb -Wl,-rpath=$PWD;./m
8

Above is my code and test result. I am confused why it does not work as expected.

Also try function, not work ether. Below is the test result.

pqy@localhost ~/src/test/a $ cat lib.c
int fun() {
    return 5;
}
pqy@localhost ~/src/test/a $ cat m.c
#include <stdio.h>
__attribute__((weak)) int fun() {
    return 8;
}
int main(void){
    printf("%d\n", fun());
    return 0;
}
pqy@localhost ~/src/test/a $ gcc lib.c -fPIC -shared -o libb.so;gcc m.c -O0 -o m -L. -lb -Wl,-rpath=$PWD;./m
8
pqy@localhost ~/src/test/a $ ldd m
        linux-vdso.so.1 (0x00007ffd819ec000)
        libb.so => /home/pqy/src/test/a/libb.so (0x00007f7226738000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f7226533000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f7226744000)
pqy@localhost ~/src/test/a $

Solution

  • At bottom what you have observed here is just the fact that the linker will not resolve a symbol dynamically if it can resolve it statically. See:

    main.c

    extern void foo(void);
    extern void need_dynamic_foo(void);
    extern void need_static_foo(void);
    
    int main(void){
        foo();
        need_dynamic_foo();
        need_static_foo();
        return 0;
    }
    

    dynamic_foo.c

    #include <stdio.h>
    
    void foo(void)
    {
        puts("foo (dynamic)");
    }
    
    void need_dynamic_foo(void)
    {
        puts(__func__);
    }
    

    static_foo.c

    #include <stdio.h>
    
    void foo(void)
    {
        puts("foo (static)");
    }
    
    void need_static_foo(void)
    {
        puts(__func__);
    }
    

    Compile the sources so:

    $ gcc -Wall -c main.c static_foo.c
    $ gcc -Wall -fPIC -c dynamic_foo.c
    

    Make a shared library:

    $ gcc -shared -o libfoo.so dynamic_foo.o
    

    And link a program:

    $ gcc -o prog main.o static_foo.o libfoo.so -Wl,-rpath=$PWD
    

    It runs like:

    $ ./prog
    foo (static)
    need_dynamic_foo
    need_static_foo
    

    So foo and need_static_foo were statically resolved to the definitions from static_foo.o and the definition of foo from libfoo.so was ignored, despite the fact that libfoo.so was needed and provided the definition of need_dynamic_foo. It makes no difference if we change the linkage order to:

    $ gcc -o prog main.o libfoo.so static_foo.o -Wl,-rpath=$PWD
    $ ./prog
    foo (static)
    need_dynamic_foo
    need_static_foo
    

    It also makes no difference if we replace static_foo.c with:

    static_weak_foo.c

    #include <stdio.h>
    
    void __attribute__((weak)) foo(void)
    {
        puts("foo (static weak)");
    }
    
    void need_static_foo(void)
    {
        puts(__func__);
    }
    

    Compile that and relink:

    $ gcc -Wall -c static_weak_foo.c
    $ gcc -o prog main.o libfoo.so static_weak_foo.o -Wl,-rpath=$PWD
    $ ./prog
    foo (static weak)
    need_dynamic_foo
    need_static_foo
    

    Although the definition of foo in static_weak_foo.c is now declared weak, the fact that foo can be statically resolved to this definition still preempts any need to resolve it dynamically.

    Now if we write another source file containing another strong definition of foo:

    static_strong_foo.c

    #include <stdio.h>
    
    void foo(void)
    {
        puts("foo (static strong)");
    }
    

    and compile it and link as follows:

    $ gcc -Wall -c static_strong_foo.c
    $ gcc -o prog main.o static_weak_foo.o libfoo.so static_strong_foo.o -Wl,-rpath=$PWD
    

    we see:

    $ ./prog
    foo (static strong)
    need_dynamic_foo
    need_static_foo
    

    Now, libfoo.so still provides the definition of need_dynamic_foo, because there is no other; static_weak_foo.o still provides the only definition of need_static_foo, and the definition of foo in libfoo.so is still ignored because the symbol can be statically resolved.

    But in this case there are two definitions of foo in different files that are available to resolve it statically: the weak definition in static_weak_foo.o and the strong definition in static_strong_foo.o. By the linkage rules that you are familiar with, the strong definition wins.

    If both of these statically linked definitions of foo were strong, there would of course be a multiple definition error, just like:

    $ gcc -o prog main.o static_foo.o libfoo.so static_strong_foo.o -Wl,-rpath=$PWD
    static_strong_foo.o: In function `foo':
    static_strong_foo.c:(.text+0x0): multiple definition of `foo'
    static_foo.o:static_foo.c:(.text+0x0): first defined here
    collect2: error: ld returned 1 exit status
    

    in which the dynamic definition in libfoo.so plays no part. So you can be guided by this practical principle: The rules you are familiar with for arbitrating between weak and strong definitions of the same symbol in a linkage only apply to rival definitions which would provoke a multiple definition error in the absence of the weak attribute.