Skip to content


C/C++ Programming »

Library Linking

Developers have a choice of using static or dynamic linking when building applications with fully compiled languages. In general, static linking makes libraries part of the resulting executable file, but dynamic linking keeps these libraries as separate files.

Last update: 2022-06-07


Library Naming Conventions

A library known as foo is expected to exist as the file libfoo.so or libfoo.a.

  • When linking against the library, the library can be specified only by its name foo with the -l option as -lfoo

  • When creating the library, the full file name libfoo.so or libfoo.a must be specified

Static and Dynamic linking#

Developers have a choice of using static or dynamic linking when building applications with fully compiled languages. In general, static linking makes libraries part of the resulting executable file, but dynamic linking keeps these libraries as separate files.

Dynamic and static linking can be compared in a number of ways:

Resource use

Static linking results in larger executable files which contain more code. This additional code coming from libraries cannot be shared across multiple programs on the system, increasing file system usage and memory usage at run time. Multiple processes running the same statically linked program will still share the code.

On the other hand, static applications need fewer run-time relocations, leading to reduced startup time, and require less private resident set size (RSS) memory. Generated code for static linking can be more efficient than for dynamic linking due to the overhead introduced by position-independent code (PIC).

Security:

Dynamically linked libraries which provide ABI compatibility can be updated without changing the executable files depending on these libraries. This is especially important for libraries provided by Red Hat as part of Red Hat Enterprise Linux, where Red Hat provides security updates. Static linking against any such libraries is strongly discouraged.

Additionally, security measures such as load address randomization cannot be used with a statically linked executable file. This further reduces security of the resulting application.

Compatibility
Static linking appears to provide executable files independent of the versions of libraries provided by the operating system. However, most libraries depend on other libraries. With static linking, this dependency becomes inflexible and as a result, both forward and backward compatibility is lost. Static linking is guaranteed to work only on the system where the executable file was built.

Example source code#

build-lib.zip

We are creating a new library name foo in the lib folder:

.
├── app.c
└── lib
    ├── foo.c
    └── foo.h
foo.h
void setFoo(int v);
int getFoo();
foo.c
#include <stdio.h>
#include "foo.h"

int __foo;

void __attribute__ ((constructor)) constructorFoo();
void __attribute__ ((destructor )) destructorFoo();

void constructorFoo() {
    printf("Foo is Loaded!\n");
}

void destructorFoo() {
    printf("Foo is Unloaded!\n"); 
}

void setFoo(int f) {
    __foo = f;
}

int getFoo() {
    return __foo;
}
app.c
#include <stdio.h>
#include <foo.h>

int main() {
    printf("Init foo = %d\n", getFoo());
    setFoo(5);
    printf("New foo = %d\n", getFoo());
    return 0;
}

Create Static Library#

Build a static library in the lib/static:

mkdir -p lib/static
gcc -c lib/foo.c -o lib/static/libfoo.o
ar rcs lib/static/libfoo.a lib/static/libfoo.o

Static Lib compilation

Local linking

Compile:

gcc app.c -Ilib -Llib/static -lfoo -o app_static_local

Run:

./app_static_local

Global linking

Install:

sudo install -m 755     \
    lib/foo.h           \
    /usr/include
sudo install -m 755     \
    lib/static/libfoo.a \
    /usr/lib/

Compile:

gcc app.c -lfoo -o app_static

Run:

./app_static

Remove library:

sudo rm /usr/lib/libfoo.a 
sudo rm /usr/include/foo.h
sudo rm -rf lib/static

Create Dynamic Library#

Build a dynamic library in the lib/dynamic:

mkdir -p lib/dynamic
gcc -c -fPIC lib/foo.c -o lib/dynamic/libfoo.o
gcc -shared lib/dynamic/libfoo.o -o lib/dynamic/libfoo.so

Dynamic library Linking

Local linking

Compile:

gcc app.c -Ilib -Llib/dynamic -lfoo -o app_dynamic_local

Run:

LD_LIBRARY_PATH=lib/dynamic ./app_dynamic_local

Global linking

Install:

sudo install -m 755         \
    lib/foo.h               \
    /usr/include
sudo install -m 755         \
    lib/dynamic/libfoo.so   \
    /usr/lib/

Compile:

gcc app.c -lfoo -o app_dynamic

Run:

./app_dynamic

Remove library:

sudo rm /usr/lib/libfoo.so 
sudo rm /usr/include/foo.h
sudo rm -rf lib/dynamic

Symbol tables

Use nm to list all symbols in an object file. Here we compare the symbols in app_static and app_dynamic to see how symbols are declared:

Static linked app:

0000000000201010 B __bss_start
0000000000201010 b completed.7698
0000000000000709 T constructorFoo
                 w __cxa_finalize@@GLIBC_2.2.5
0000000000201000 D __data_start
0000000000201000 W data_start
00000000000005e0 t deregister_tm_clones
000000000000071c T destructorFoo
0000000000000670 t __do_global_dtors_aux
0000000000200db0 t __do_global_dtors_aux_fini_array_entry
0000000000201008 D __dso_handle
0000000000200dc0 d _DYNAMIC
0000000000201010 D _edata
0000000000201018 B _end
00000000000007c4 T _fini
0000000000201014 B __foo
00000000000006b0 t frame_dummy
0000000000200da0 t __frame_dummy_init_array_entry
00000000000009f4 r __FRAME_END__
0000000000000742 T getFoo
0000000000200fb0 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
0000000000000814 r __GNU_EH_FRAME_HDR
0000000000000558 T _init
0000000000200db0 t __init_array_end
0000000000200da0 t __init_array_start
00000000000007d0 R _IO_stdin_used
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
00000000000007c0 T __libc_csu_fini
0000000000000750 T __libc_csu_init
                 U __libc_start_main@@GLIBC_2.2.5
00000000000006ba T main
                 U printf@@GLIBC_2.2.5
                 U puts@@GLIBC_2.2.5
0000000000000620 t register_tm_clones
000000000000072f T setFoo
00000000000005b0 T _start
0000000000201010 D __TMC_END__

Dynamic linked app:

0000000000201010 B __bss_start
0000000000201010 b completed.7698
                 w __cxa_finalize@@GLIBC_2.2.5
0000000000201000 D __data_start
0000000000201000 W data_start
00000000000006c0 t deregister_tm_clones
0000000000000750 t __do_global_dtors_aux
0000000000200da0 t __do_global_dtors_aux_fini_array_entry
0000000000201008 D __dso_handle
0000000000200da8 d _DYNAMIC
0000000000201010 D _edata
0000000000201018 B _end
0000000000000864 T _fini
0000000000000790 t frame_dummy
0000000000200d98 t __frame_dummy_init_array_entry
00000000000009d4 r __FRAME_END__
                 U getFoo
0000000000200fa8 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
0000000000000894 r __GNU_EH_FRAME_HDR
0000000000000628 T _init
0000000000200da0 t __init_array_end
0000000000200d98 t __init_array_start
0000000000000870 R _IO_stdin_used
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
0000000000000860 T __libc_csu_fini
00000000000007f0 T __libc_csu_init
                 U __libc_start_main@@GLIBC_2.2.5
000000000000079a T main
                 U printf@@GLIBC_2.2.5
0000000000000700 t register_tm_clones
                 U setFoo
0000000000000690 T _start
0000000000201010 D __TMC_END__

Load library at Runtime#

It’s also possible to dynamically load a library from an executable. The necessary functions are dlopen(), dlsym() etc. whose definitions are found in dlfcn.h.

app_load_lib.c
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
// do not include foo.h, just know the declaration

int main() {
    void *ptr;
    // declare function pointers
    // according to the target function calls
    void (*fptr_set)(int); // void setFoo(int f);
    int  (*fptr_get)(); // int getFoo();

    // Open the target dynamic lib
    void* foolib = dlopen("libfoo.so",  RTLD_LAZY | RTLD_GLOBAL);
    if(!foolib) {
        printf("ERROR! Can not open libfoo.so\n");
        exit(1);
    }

    // Get function pointers
    ptr = dlsym(foolib, "setFoo");
    if(!ptr) {
        printf("ERROR! Can not find function setFoo\n");
        exit(1);    
    }

    fptr_set = (void (*)(int))ptr;

    ptr = dlsym(foolib, "getFoo");
    if(!ptr) {
        printf("ERROR! Can not find function getFoo\n");
        exit(1);    
    }

    fptr_get = (int (*)())ptr;

    // Call function via function pointers
    printf("Init foo = %d\n", fptr_get());
    fptr_set(5);
    printf("New foo = %d\n", fptr_get());
    return 0;
}

Compile:

gcc app_load_lib.c -ldl -o app_load_lib

Run if libfoo.so is not install to system:

LD_LIBRARY_PATH=lib/dynamic ./app_load_lib

Dynamic Linking Debug#

The ldconfig command checks the header and file names of the libraries it encounters when determining which versions should have their links updated. This command also creates a file called /etc/ld.so.cache which is used to speed up linking.

sudo ldconfig -v
...
/lib/x86_64-linux-gnu:
    libc.so.6 -> libc-2.27.so
/usr/lib:
    libfoo.so -> libfoo.so
...


Use LD_DEBUG=<option> to enable debugging log for dynamic linking.

LD_DEBUG=help ./app_dynamic
Valid options for the LD_DEBUG environment variable are:

  libs        display library search paths
  reloc       display relocation processing
  files       display progress for input file
  symbols     display symbol table processing
  bindings    display information about symbol binding
  versions    display version dependencies
  scopes      display scope information
  all         all previous options combined
  statistics  display relocation statistics
  unused      determined unused DSOs
  help        display this help message and exit

To direct the debugging output into a file instead of standard output a filename can be specified using the LD_DEBUG_OUTPUT environment variable.

Example: search paths and loaded libraries:

LD_DEBUG=libs ./app_dynamic
3824:     find library=libfoo.so [0]; searching
3824:      search cache=/etc/ld.so.cache
3824:      search path=<system search paths>
3824:       trying file=/usr/lib/libfoo.so
3824:     calling init: /usr/lib/libfoo.so
3824:     initialize program: ./app_dynamic
3824:     transferring control: ./app_dynamic
3824:     calling fini: ./app_dynamic [0]
3824:     calling fini: /usr/lib/libfoo.so [0]

Callback in a Library#

Symbols of an executable are not exported by default.

When you want to export a callback symbol which is defined in main application and will be used in library, you have to explicitly use the option -Wl,-export-dynamic (or -rdynamic) when compiling it.


Example:

bar.h
void run();

Library bar calls to callback function which is not implemented in library:

bar.c
#include <stdio.h>

// compilable, but not runnable
extern void callback();

void run() {
    printf("Run from BAR!\n");
    callback();
}

In the main app, the function callback is implemented:

app.c
#include <stdio.h>
#include "bar.h"

// definition
void callback() {
    printf("Callback in APP!\n");
}

void main() {
    run();
}

We build a dynamic libbar.so library, can compile the main app with -rdynamic to export callback symbol:

gcc lib/bar.c -fpic -shared -o lib/libbar.so
gcc app.c -Ilib -Llib -lbar -rdynamic -o app

Try to run the main app, with LD_DEBUG=symbols option to show how symbols are looked up:

LD_LIBRARY_PATH=. LD_DEBUG=symbols ./app
    4685:     calling init: ./libbar.so
    4685:     initialize program: ./app
    4685:     transferring control: ./app
Run from BAR!
    4685:     symbol=callback;  lookup in file=./app [0]
Callback in APP!
    4685:     calling fini: ./app [0]
    4685:     calling fini: ./libbar.so [0]

Exercise#

Do you really know how a dynamic library is loaded into a program? Describe the steps in that order Linker loads a dynamic library!

Can we create a global shared variable between applications which load the same library?

Comments