What This File Covers
This file completes the dlopen API with three final topics: dlclose() for properly unloading libraries, dladdr() for reverse-lookup of symbol information, and the –export-dynamic linker option that allows loaded plugins to call back into the main program. Finally, we bring it all together with the complete dynload example from TLPI.
42.1.4 — dlclose(): Closing a Shared Library
#include <dlfcn.h>
int dlclose(void *handle);
/* Returns: 0 on success, -1 on error */
dlclose() does the following:
- Decrements the reference count of the library referred to by
handle. - If the reference count falls to 0 AND no other loaded library still needs symbols from it, the library is unmapped from the process’s address space.
- Any libraries in the dependency tree are also dlclosed recursively.
- When the library is actually unloaded, all destructor functions (
__attribute__((destructor))) are called first.
/* Correct dlclose usage with error check */
if (dlclose(handle) != 0) {
fprintf(stderr, "dlclose error: %s\n", dlerror());
}
42.1.5 — dladdr(): Reverse Symbol Lookup
#define _GNU_SOURCE
#include <dlfcn.h>
int dladdr(const void *addr, Dl_info *info);
/* Returns: nonzero if addr was found in a shared library, 0 otherwise */
dladdr() is the reverse of dlsym(). Given an address (typically one returned by dlsym()), it fills in a Dl_info structure with information about where that address came from.
The Dl_info structure:
typedef struct {
const char *dli_fname; /* Pathname of the shared library containing addr */
void *dli_fbase; /* Base address at which that library is loaded */
const char *dli_sname; /* Name of nearest symbol with address <= addr */
void *dli_saddr; /* Actual address of the symbol in dli_sname */
} Dl_info;
| Field | Meaning | Example |
|---|---|---|
| dli_fname | Full path of the .so file | /lib/x86_64-linux-gnu/libm.so.6 |
| dli_fbase | Address where library is mapped in memory | 0x7f8a3c400000 |
| dli_sname | Name of the nearest symbol at or before addr | “sin” |
| dli_saddr | Actual address of that symbol | 0x7f8a3c412340 |
#define _GNU_SOURCE.42.1.6 — –export-dynamic: Callbacks into the Main Program
Consider this scenario: your main program loads a plugin using dlopen(). The plugin calls a function callback_fn() which is defined in the main program. Will it work?
By default — NO. Global symbols in the main program are not available to dynamically loaded libraries unless you explicitly export them.
The –export-dynamic linker option adds all global-scope symbols of the main program to its dynamic symbol table, making them available to any dynamically loaded library:
# Without --export-dynamic: plugin cannot find main program functions
gcc -o myprog main.c -ldl
# With --export-dynamic: plugin CAN find and call main program functions
gcc -Wl,--export-dynamic -o myprog main.c -ldl
# These are all equivalent:
gcc -export-dynamic -o myprog main.c -ldl # same
gcc -rdynamic -o myprog main.c -ldl # same (gcc shorthand)
gcc -Wl,-E -o myprog main.c -ldl # same (another synonym)
Plugin tries to call main_callback() → dlsym returns NULL → crash or silent failure
Plugin calls dlsym(RTLD_DEFAULT, “main_callback”) → finds it → callback works correctly
Code Example 1: dlclose() and atexit() in a Library
/* cleanup_lib.c — library that registers cleanup via atexit() */
#include <stdio.h>
#include <stdlib.h>
static void library_cleanup(void) {
printf("[cleanup_lib] atexit cleanup running — freeing resources\n");
}
void library_init(void) {
printf("[cleanup_lib] library_init() called\n");
atexit(library_cleanup); /* Register cleanup for when library is unloaded */
}
/* dlclose_demo.c — shows when the atexit cleanup fires */
#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>
int main(void) {
void *lib = dlopen("./cleanup_lib.so", RTLD_LAZY);
if (!lib) { fprintf(stderr, "%s\n", dlerror()); exit(1); }
void (*init_fn)(void);
dlerror();
*(void **)(&init_fn) = dlsym(lib, "library_init");
if (!dlerror()) (*init_fn)();
printf("About to call dlclose()...\n");
dlclose(lib);
printf("dlclose() returned — library unloaded\n");
return 0;
}
gcc -fPIC -shared -o cleanup_lib.so cleanup_lib.c
gcc dlclose_demo.c -ldl -o dlclose_demo
./dlclose_demo
# [cleanup_lib] library_init() called
# About to call dlclose()...
# [cleanup_lib] atexit cleanup running — freeing resources
# dlclose() returned — library unloaded
Code Example 2: dladdr() — Inspecting Symbol Information
/* dladdr_demo.c — use dladdr() to inspect addresses */
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
#include <math.h>
#include <stdlib.h>
void print_symbol_info(const char *label, void *addr) {
Dl_info info;
printf("\n--- %s (addr=%p) ---\n", label, addr);
if (dladdr(addr, &info)) {
printf(" Library path : %s\n", info.dli_fname ? info.dli_fname : "(null)");
printf(" Library base : %p\n", info.dli_fbase);
printf(" Symbol name : %s\n", info.dli_sname ? info.dli_sname : "(null)");
printf(" Symbol addr : %p\n", info.dli_saddr);
printf(" Offset : %ld bytes\n",
(long)((char*)addr - (char*)info.dli_saddr));
} else {
printf(" Not found in any shared library\n");
}
}
int main(void) {
/* Look up sin() from libm */
void *lib = dlopen("libm.so.6", RTLD_LAZY | RTLD_GLOBAL);
if (!lib) { fprintf(stderr, "%s\n", dlerror()); exit(1); }
double (*sin_fn)(double);
dlerror();
*(void **)(&sin_fn) = dlsym(lib, "sin");
if (!dlerror()) {
print_symbol_info("sin()", (void*)sin_fn);
}
/* Also inspect main() itself — it's in the main executable */
print_symbol_info("main()", (void*)main);
dlclose(lib);
return 0;
}
gcc dladdr_demo.c -ldl -lm -o dladdr_demo
./dladdr_demo
# --- sin() (addr=0x7f...) ---
# Library path : /lib/x86_64-linux-gnu/libm.so.6
# Library base : 0x7f...
# Symbol name : sin
# Symbol addr : 0x7f...
# Offset : 0 bytes
#
# --- main() (addr=0x55...) ---
# Library path : ./dladdr_demo
# Library base : 0x55...
# Symbol name : main
# Symbol addr : 0x55...
# Offset : 0 bytes
Code Example 3: Complete dynload.c (from TLPI) + –export-dynamic
This is the complete dynload.c example from TLPI Listing 42-1, extended with dladdr() and export-dynamic callback support:
/* dynload_full.c — complete example: load a library, find a function,
call it, inspect its address with dladdr() */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
/* This function is in the main program.
With --export-dynamic, a loaded library can call it back. */
void main_program_callback(const char *msg) {
printf("[MAIN CALLBACK] received: %s\n", msg);
}
int main(int argc, char *argv[]) {
void *libHandle;
void (*funcp)(void);
const char *err;
if (argc != 3 || strcmp(argv[1], "--help") == 0) {
fprintf(stderr, "Usage: %s lib-path func-name\n", argv[0]);
exit(EXIT_FAILURE);
}
/* Load the shared library */
libHandle = dlopen(argv[1], RTLD_LAZY);
if (libHandle == NULL) {
fprintf(stderr, "dlopen: %s\n", dlerror());
exit(EXIT_FAILURE);
}
printf("Loaded: %s\n", argv[1]);
/* Look up the requested function */
dlerror();
*(void **)(&funcp) = dlsym(libHandle, argv[2]);
err = dlerror();
if (err != NULL) {
fprintf(stderr, "dlsym: %s\n", err);
dlclose(libHandle);
exit(EXIT_FAILURE);
}
if (funcp == NULL) {
printf("Symbol '%s' has NULL value\n", argv[2]);
} else {
printf("Calling %s()...\n", argv[2]);
(*funcp)();
/* Use dladdr to inspect the function's origin */
Dl_info info;
if (dladdr((void*)funcp, &info)) {
printf("Function '%s' is in: %s (loaded at %p)\n",
info.dli_sname ? info.dli_sname : "?",
info.dli_fname ? info.dli_fname : "?",
info.dli_fbase);
}
}
dlclose(libHandle);
exit(EXIT_SUCCESS);
}
# Build with --export-dynamic so loaded libs can call main_program_callback:
gcc -Wl,--export-dynamic -o dynload_full dynload_full.c -ldl
# Create a test library:
# (libdemo.c defines x1() which may call back into main)
gcc -fPIC -shared -o libdemo.so libdemo.c
# Run:
./dynload_full ./libdemo.so x1
