Dynamic Linking Loader in Linux

The four functions dlopen(), dlsym(), dlclose(), dlerror() implement the interface to the dynamic linking loader.

#include <dlfcn.h>

void *dlopen(const char *filename, int flag);

char *dlerror(void);

void *dlsym(void *handle, const char *symbol);

int dlclose(void *handle);

dlerror

dlerror() 返回一个适合程序员阅读的字符串,描述在最近一次的dlopen(), dlsym(), dlclose()调用中出现的错误。如果没有错误返回NULL。

dlopen

dlopen接收一C-style字符串,返回一个对dynamic library的handle.如果文件名为NULL,返回到main program的handle。如果filename中包含"/",就会将其理解为一个(相对或者绝对)路径名。否则dynamic linker按照下面的顺序搜索library:

如果calling program的可执行文件包含DT_RPATH tag,并且不包含DT_RUNPATH tag,那么在DT_PPATH tag中标记的目录会被搜索;

如果环境变量LD_LIBRARY_PATH包含了':'分割的目录列表,就搜索这些目录。(从安全方面考虑,对于set-user-ID和set-group-ID 的程序,这个变量会被忽略)。

如果calling program的可执行文件包含DT_RUNPATH tag,就搜索这个tag中列出的目录。

缓存文件/etc/ld.so.cache

搜索目录/lib and /usr/lib

如果这个library对其他shared library有依赖,这些被依赖的library也会按照同样的规则被自动加载进来。

在dlopen中还需制定flag参数:

RTLD_LAZY

执行lazy binding。只解析那些被引用了的symbol。如果symbol从来没有被引用过,那么就永远不会被解析。(Lazy binding只针对function reference,对变量的reference,是会在library被加载时总是立即bound的)

RTLD_NOW

如果指定了这个flag或者环境变量LD_BIND_NOW被设置为非空字符串,所有没有被定义的symbol会在dlopen返回之前被解析。如果这个过程无法完成,会返回错误。

下面的几个值可以跟上面两个值OR:

RTLD_GLOBAL

在这个library中定义的symbol会让其用于后续加载的library的symbol resolution

RTLD_LOCAL

RTLD_GLOBAL的反义词,也是默认行为。

RTLD_NODELETE (since glibc 2.2)

在dlclose时不要unload library。这样在这个library被reload时,其静态变量不会被再次初始化。

RTLD_NOLOAD (since glibc 2.2)

不要load这个library,这可以用于检查这个library是否存在。如果这个library已经被加载了,可以用于改变library被加载时使用的flag。比如,一个library之前是用RTLD_LOCAL打开的,可以用RTLD_NOLOAD | RTLD_GLOBAL再次打开。

RTLD_DEEPBIND (since glibc 2.3.4)

将对这个library中symbol的搜索置于global scope的前面。这意味着,一个self-contained library会优先使用自己的library中定义的symbol,即使同名的symbol已经在loaded library中声明过了。

如果filename是NULL,会返回对main program的handle。当将其给dlsym(),这个handle会导致对main program中symbol的搜索,然后搜索在程序启动是就加载的library,然后是用dlopen() with flag RTLD_GLOBAL加载的library。

对library中的外部引用会用library的得dependency list中的library来解析,或者任何使用RTLD_GLOBAL加载的library。如果这个可执行程序被link时使用了-rdynamic,可执行程序中的global symbols也会被用来解析被动态加载的引用。

如果同意的library被dlopen加载多次,会返回同样的handle。dl library会维护对library handle的引用计数。所以调用一次dlclose不会把动态library 卸载掉,除非与dlopen同样次数的dlclose被调用。但如果后续的调用使用了RTLD_NOW可能会强迫对一个之前使用RTLD_LAZY加载的library做符号解析。

dlsym

这个函数使用一个dynamic library的handle 和一个null-terminated symbol name做参数,返回这个symbol的内存地址。如果symbol在制定的library或者任何使用dlopen()自动加载的library中没有找到,dlsym()会返回NULL。dlsym()执行的是library的dependency tree的宽度优先搜索。

dlclose

dlclose减少对dynamic library的reference count。如果reference count减为0,并且没有其他已经加载的library使用其中的symbol,这个dynamic library会被卸载。

The obsolete symbols _init and _fini

The linker recognizes special symbols _init and _fini. If a dynamic library exports a routine named _init, then that code is executed after the loading, before dlopen() returns. If the dynamic library exports a routine named _fini, then that routine is called just before the library is unloaded. In case you need to avoid linking against the system startup files, this can be done by giving gcc the "-nostartfiles" parameter on the command line.

Using these routines, or the gcc -nostartfiles or -nostdlib options, is not recommended. Their use may result in undesired behavior, since the constructor/destructor routines will not be executed (unless special measures are taken).

Instead, libraries should export routines using the __attribute__((constructor)) and __attribute__((destructor)) function attributes. See the gcc info pages for information on these. Constructor routines are executed before dlopen() returns, and destructor routines are executed before dlclose() returns.

 

GNU Extensions

Glibc adds two functions not described by POSIX, with prototypes

#define _GNU_SOURCE#include <dlfcn.h>
int dladdr(void *addr, Dl_info *info);
void *dlvsym(void *handle, char *symbol, char *version);

The function dladdr() takes a function pointer and tries to resolve name and file where it is located. Information is stored in the Dl_info structure:

typedef struct {
  const char *dli_fname;/* Filename of defining object */
  void *dli_fbase;      /* Load address of that object */
  const char *dli_sname;/* Name of nearest lower symbol */
  void *dli_saddr;      /* Exact value of nearest symbol */
} Dl_info;

dladdr() returns 0 on error, and non-zero on success.

The function dlvsym() does the same as dlsym() but takes a version string as an additional argument.

你可能感兴趣的:(dynamic)