linux c共享库(动态库)高级特性(2)

2018-06-15 18:09:07

动态加载库

当一个可执行文件开始运行之后,动态链接器会加载程序的动态依赖列表中的所有共享库,但有些时候延迟加载库是比较有用的,如只在需要的时候再加载一个插件。动态链接器的这项功能是通过一组 API 来实现的。这组 API 通常被称为 dlopen API。


打开共享库:dlopen()

#include <dlfcn.h>

void *dlopen(const char *libfilename, int flags);

//Returns library handle on success, or NULL on error

将名为libfilename 的共享库加载进进程的虚拟地址空间并增加该库的打开引用计数。

如果libfilename 包含了一个斜线,则会当成绝对路径或相对路径,否则动态链接器会按照上篇文章介绍的规则来搜索共享库。

如果 libfilename 指定的共享库依赖于其他库,dlopen() 会自动加载那些库。

参数 flags 是一个位掩码,它的取值是:

RTLD_LAZY 只有当代码被执行的时候才解析库中未定义的函数符号。延迟解析只适用于函数引用,对变量的引用会被立即解析。

RTLD_NOW 在dlopen() 结束之前立即加载库中所有未定义的符号,不管是否需要用到这些符号,这种做法的结果是打开库变的更慢了,但能够立即检测到任何潜在的未定函数符号错误,而不是在后面的某个时刻才检测到这种错误。在调试程序时这种做法是比较有用的,因为他能够确保应用程序立即返回发生的错误。

RTLD_GLOBAL 这个库及依赖树中的符号在解析由当前进程加载的其他库中的引用和通过dlsym() 查找时可用。

RTLD_LOCAL 与RTLD_GLOBAL相反


错误诊断:dlerror()

如果在dlopen() 调用 或 dlopen API 的其他函数调用中得到了一个错误,那么可以使用dlerror() 来获取一个指向表明错误原因的字符串的指针。

#include <dlfcn.h>

const char *dlerror(void);

如果上次调用 dlerror() 到现在没有发生错误,那么dlerror() 函数返回NULL。


获取符号地址:dlsym()

#include <dlfcn.h>

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

//Returns address of symbol, or NULL if symbol is not found

dlsym() 函数在handle指向的库以及该库的依赖树中搜索名为 symbol 的符号(函数或变量)。如果找到了,返回其地址,否则返回NULL。handle 参数通常是上一个dlopen() 调用返回的库句柄,或者它也可以是下面介绍的其中一个所谓的伪句柄。

dlsym() 返回的符号值可能为NULL,找不到符号也会返回NULL,所以必须配合dlerror() 才能确定是哪一种导致的。

如果symbol是一个变量名称,可以这样赋值

int *ip;

ip = (int *) dlsym(symbol, "myvar");
if (ip != NULL)
    printf("Value is %d\n", *ip);

如果是函数名称,做法如下

int (*funcp)(int);

但是不能简单的将dlsym()的结果赋给此类指针

funcp = dlsym(handle, symbol);

原因是 c99 标准禁止函数指针和void *之间的赋值操作。解决方案为

*(void **)(&funcp) = dlsym(handle, symbol);

#调用
res = (*funcp)(somearg);

读者可能会用下面这种看起来和上述代码等价的代码来取代*(void **)语法

(void *) funcp = dlsym(handle, symbol);

但gcc -pedantic 在遇到上面代码时会发出 "ANSI C forbids the use of cast expressions as lvalues" 的警告信息。

很多unix实现中可以使用下面这样的类型转换消除C编译器的警告。

funcp = (int (*) (int)) dlsym(handle, symbol);

但是SUSv3之处C99标准仍然要求编译器生成警告信息。

在dlsym() 中使用伪句柄

dlsym() 函数中的 handle 参数除了能够取由dlopen() 调用返回的句柄值之外,还能够取下列伪句柄

RTLD_DEFAULT 在主程序开始处开始查找 symbol,接着在所有已加载的共享库中查找,包括那些通过使用了 RTLD_GLOBAL标记的dlopen() 调用动态加载的库,这个标记对应于动态链接器所采用的默认搜索模型。

RTLD_NEXT 在调用 dlsym() 之后加载共享库中搜索 symbol, 这个标记适用于需要创建与在其他地方定义的函数同名的包装函数的情况。如在主程序里可能会定义一个自己的malloc() 函数,而这个函数里又要调用实际的malloc() 函数,那么在自定义函数 malloc() 里,在调用实际 malloc() 之前调用 func = dlsym(RTLD_NEXT, "malloc") 来获取地址(参照 例子-2)。


关闭共享库:dlclose()

#include <dlfcn.h>

int dlclose(void *handle);

//Returns 0 on success, or –1 on error

dlclose() 函数会减少 handle 所引用的库的打开引用计数。当引用计数为0时,就会卸载该库以及依赖的库。

例子-1

//say.c
#include <stdio.h>      /* Standard I/O functions */

char *url = "http://www.freecls.com";

void say_1(){
    printf("hello freecls 1\n");
}

void say_2(){
    printf("hello 沧浪水 2\n");
}
//main.c
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

#include <dlfcn.h>

int main(int argc, char *argv[]){
    void *libHandle;
    void (*funcp_1)(void);
    void (*funcp_2)(void);
    
    char **tmp;
    
    const char *err;


    libHandle = dlopen("./libsay.so", RTLD_LAZY);
    if (libHandle == NULL){
        printf("dlopen: %s\n", dlerror());
        return 1;
    }
        

    (void) dlerror();  /* 清除错误 */
    *(void **) (&funcp_1) = dlsym(libHandle, "say_1");
    err = dlerror();
    if (err != NULL){
        printf("dlsym: %s", err);
        return 1;
    }
    
    *(void **) (&funcp_2) = dlsym(libHandle, "say_2");
    err = dlerror();
    if (err != NULL){
        printf("dlsym: %s", err);
        return 1;
    }
    
    //如果我们知道url为NULL,则可以简化判断错误
    tmp = dlsym(libHandle, "url");
    if(tmp == NULL){
        err = dlerror();
        printf("dlsym: %s", err);
        return 1;
    }
    
    (*funcp_1)();
    (*funcp_2)();

    printf("url:%s\n", *tmp);

    dlclose(libHandle);                         /* Close the library */

    exit(EXIT_SUCCESS);
}
[root@izj6cfw9yi1iqoik31tqbgz c]# gcc -g -fPIC -Wall say.c -shared -o libsay.so
[root@izj6cfw9yi1iqoik31tqbgz c]# gcc main.c -ldl
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out 
hello freecls 1
hello 沧浪水 2
url:http://www.freecls.com


例子-2

//mymalloc.c
#define _GNU_SOURCE
#include <stdio.h>      /* Standard I/O functions */
#include <stdlib.h>
#include <dlfcn.h>

static void * (*real_malloc)(size_t size);

void * malloc(size_t size){
    printf("调用自定义malloc\n");
    
    if(real_malloc == NULL){
        *(void **)(&real_malloc) = dlsym(RTLD_NEXT, "malloc");
    }
    
    return real_malloc(size);
}
//main.c
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

#include <dlfcn.h>

int main(int argc, char *argv[]){
    void * (*malloc)(size_t size);
    
    void *libHandle = dlopen("./libmymalloc.so", RTLD_LAZY);
    if (libHandle == NULL){
        printf("dlopen: %s\n", dlerror());
        return 1;
    }
    
    (void) dlerror();  /* 清除错误 */
    *(void **) (&malloc) = dlsym(libHandle, "malloc");
    char *err = dlerror();
    if (err != NULL){
        printf("dlsym: %s", err);
        return 1;
    }
    
    char *url = (char *)malloc(50);
    strcpy(url, "http://www.freecls.com");
    
    printf("url:%s\n", url);
    free(url);

    exit(EXIT_SUCCESS);
}
[root@izj6cfw9yi1iqoik31tqbgz c]# gcc -g -fPIC -Wall mymalloc.c -shared -o libmymalloc.so
[root@izj6cfw9yi1iqoik31tqbgz c]# gcc main.c -ldl
[root@izj6cfw9yi1iqoik31tqbgz c]# ./a.out 
调用自定义malloc
url:http://www.freecls.com


备注
1.编译器版本gcc4.8,运行环境centos7 64位
2.原文地址http://www.freecls.com/a/2712/5b


©著作权归作者所有
收藏
推荐阅读
  • err
    linux c共享库(动态库)基础(1)

    在很多情况下,源代码文件也可以被多个程序共享。因此要降低工作量的第一步就是将这些源代码文件只编译一次,然后在需要的时候将它们链接进不同的可执行文件中。虽然这项技术能够竹省...

  • linux c进程资源

    每个进程都会消耗诸如内存和CPU时间之类的系统资源,本文将介绍与资源相关的系统调用。#include &lt;sys/resource.h&gt; int getrusage(int who, st...

  • err
    linux c使用syslog记录消息

    syslog 工具提供了一个集中式日志工具,系统中的所有应用程序都可以使用这个记录日志消息。如下图syslogd 从两个不同的源接收日志消息:一个是unix domain...

  • linux c进程调度-CPU亲和力

    进程切换 CPU 时对性能会有一定的影响:如果在原来的 CPU 的高速缓冲器中存在进程的数据,那么为了将进程的一行数据加载进新 CPU 的高速缓冲器中,首先必须使这行数据失效(即在没被修改的情况下丢弃...

  • err
    linux c线程(2)-同步和线程取消

    本篇将介绍线程用来同步彼此行为的两个工具:互斥量和条件变量。我们先来看一个例子。#include &lt;sys/types.h&gt; #include &lt;std...

  • nginx模块 ngx_http_headers_module

    ngx_http_headers_module 模块是用来增加 Expires 和 Cache-control,或者是任意的响应头。Syntax: add_header name value [alw...

  • nginx模块 ngx_http_gunzip_module、ngx_http_gzip_module、ngx_http_gzip_static_module

    ngx_http_gunzip_module 模块将文件解压缩后并在响应头加上 "Content-Encoding: gzip" 返回给客户端。为了解决客户端不支持gzip压缩。编译的时候带上 --w...

  • nginx模块 ngx_http_flv_module、ngx_http_mp4_module

    ngx_http_flv_module模块提供了对 flv 视频的伪流支持。编译的时候带上 --with-http_flv_module。它会根据指定的 start 参数来指定跳过多少字节,并在返回数...

  • nginx模块 ngx_http_fastcgi_module

    ngx_http_fastcgi_module 模块使得nginx可以与 fastcgi 服务器通信。比如目前要使得 nginx 支持 php 就得使用 fastcgi技术,在服务器上装上 nginx...

  • nginx模块 ngx_http_autoindex_module

    ngx_http_autoindex_module 模块可以将uri以 / 结尾时,列出里面的文件和目录。Syntax: autoindex on | off; Default: autoindex ...

简介
天降大任于斯人也,必先苦其心志。