Using attribute((weak)) weak symbols in gcc compilation of dynamic library so on Linux system

First explain the code structure: there are three source files, two dynamic library so sources: lib1.c and lib2.c, each exporting one library function (gcc exports by default, unlike Windows msvc which requires explicit dllexport), and one main program source: main.c, which calls the exported functions of the two dynamic libraries, as follows:

lib1.c

#include <stdio.h>

void lib1_func(const char* from)
{
        printf("lib1 func from %s\n", from);
}

lib2.c

#include <stdio.h>

void lib2_func(const char* from)
{
        printf("lib2 func from %s\n", from);
}

main.c

#include <stdio.h>

void lib1_func(const char* from);
void lib2_func(const char* from);

int main()
{
        printf("hello from main\n");
        lib1_func("main");
        lib2_func("main");
        return 0;
}

Next compile and link the above three codes, I used gcc version: gcc version 8.4.0 (Ubuntu 8.4.0-1ubuntu1~18.04), commands as follows:

gcc -c -fpic lib1.c
gcc -shared -o lib1.so lib1.o
gcc -c -fpic lib2.c
gcc -shared -o lib2.so lib2.o
gcc -L./ main.c -l1 -l2
LD_LIBRARY_PATH=./ ./a.out

At this point, the final executable output is:

hello from main
lib1 func from main
lib2 func from main

A very ordinary executable links two dynamic libraries and calls their exported functions. Next, the desired effect is for lib2 to call lib1’s function, modify lib2.c code as follows:

#include <stdio.h>

void lib1_func(const char* from);

void lib2_func(const char* from)
{
        printf("lib2 func from %s\n", from);
        lib1_func("lib2");
}

Then re-execute the above compile, link, and run commands, the output is:

hello from main
lib1 func from main
lib2 func from main
lib1 func from lib2

Ding! Goal achieved, celebration? … Not yet, the content in the title has not appeared. 那么现在的情况是So what is happening now? This is because the default link command does not check for undefined symbols in the code. Therefore, lib2.c with lib1_func call compiles and links successfully to produce the so, and when linking the final executable, the lib1 symbol referenced in lib2 is found. Thus the result matches the expected effect.

However, things are not so simple. In actual development we need the compiler to help check errors, such as syntax errors at compile stage and undefined symbol errors at link stage. Therefore, when linking the two so plus the executable, we should run: gcc -shared -Wl,–no-undefined -o lib2.so lib2.o. At this time linking lib2 shows the following error:

lib2.o: In function `lib2_func':
lib2.c:(.text+0x2c): undefined reference to `lib1_func'
collect2: error: ld returned 1 exit status

lib1_func is undefined when linking lib2, ld command fails, no so produced! This makes our code safer, but the desired effect cannot be achieved… 😂

There are many ways to solve this problem, such as exposing an interface in lib2.c to set the lib1_func function address, then main.c passes lib1_func address before calling lib2, or using dlsym in lib2.so load (attribute((constructor))) to dynamically get lib1_func symbol, etc. These methods require modifying existing code and call style, which is troublesome. The following introduces a minimal modification that still retains ld undefined symbol checking: attribute((weak)). Modify lib2.c as:

#include <stdio.h>

void lib1_func(const char* from) __attribute__((weak));

void lib2_func(const char* from)
{
        printf("lib2 func from %s\n", from);
        if (NULL != &lib1_func)
                lib1_func("lib2");
        else
                printf("lib2 invalid lib1_func\n");
}

Recompile, then link lib2.so with no‑undefined option, this time no error. Rebuild the main executable, and the output shows successful calls as before!

Next do some experiments. First remove use of lib1.so, modify main.c as:

#include <stdio.h>

//void lib1_func(const char* from);
void lib2_func(const char* from);

int main()
{
        printf("hello from main\n");
        //lib1_func("main");
        lib2_func("main");
        return 0;
}

Recompile the main executable, output is:

hello from main
lib2 func from main
lib2 invalid lib1_func

As seen, since the final executable has no lib1_func symbol, the weak symbol null pointer protection in lib2.so worked, printing lib1_func unavailable message. Without this protection, the program would crash.

Next, modify main.c as:

#include <stdio.h>

//void lib1_func(const char* from);
void lib2_func(const char* from);

void lib1_func(const char* from)
{
        printf("lib1_func in main from %s\n", from);
}

int main()
{
        printf("hello from main\n");
        //lib1_func("main");
        lib2_func("main");
        return 0;
}

Recompile and run, output is:

hello from main
lib2 func from main
lib1_func in main from lib2

It can be seen that in main a function with the same name as in lib1.so is defined, and lib2.so successfully found and called this implementation in the main program! (In some cases this usage may require adding the -rdynamic parameter when compiling the main program)

To summarize, by using weak symbols it is very convenient to allow a dynamic library to call functions in other dynamic libraries or in the main program, without needing complex dynamic symbol address lookup. This is especially suitable for scenarios such as automatic binding of log callback functions, plugin dynamic libraries sharing common code in the main executable, and similar cases.

博主友情提示:

如您在评论中需要提及如QQ号、电子邮件地址或其他隐私敏感信息,欢迎使用>>博主专用加密工具v3<<处理后发布,原文只有博主可以看到。