• Linux共享库(so)动态加载和升级


    >>转载请注明来源:飘零的代码 piao2010 ’s blog,谢谢!^_^
    >>本文链接地址:Linux共享库(so)动态加载和升级

    学习Linux共享库动态加载缘于一个生产环境升级apache so文件常见错误操作:apache在运行中直接cp覆盖目标so文件,一段时间后错误日志里面出现关键词:Segmentation fault (段错误) ,一个个worker进程就这样渐渐退出,最后无法处理HTTP请求。
    首先了解一下共享库的创建,源文件test.c

    #include<stdio.h>
    #include<unistd.h>
     
    void test1(void){
        printf("This is do test1\n");
        sleep(10);
        printf("End of test1\n");
    }
     
    void test2(void){
        printf("This is do test2\n");
        sleep(10);
        printf("End of test2\n");
    }

    执行gcc -fPIC -shared -o libtest.so test.c 会生成共享库文件 libtest.so
    参数含义:
    -fPIC/-fpic: Compiler directive to output position independent code, a characteristic required by shared libraries. 创建共享库必须的参数
    -shared: Produce a shared object which can then be linked with other objects to form an executable.

    然后使用共享库:源文件main2.c

    #include <stdio.h>
     
    int main()
    {
        test1();
        test2(); 
        return 0;
    }

    动态库链接:gcc -o main2 -L . -ltest main2.c 生成二进制程序main2
    参数含义:
    -L 指定动态库目录为当前目录
    -l 指定动态库名test,不要写libtest.so

    执行main2程序发现报错:error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory
    原因是共享库不在系统默认的路径里面,可以在shell执行export LD_LIBRARY_PATH=./ 添加当前路径或者在 /etc/ld.so.conf 增加路径并ldconfig生效
    执行main2成功输出:
    This is do test1
    End of test1
    This is do test2
    End of test2

    接下来是主角:动态加载,源文件main.c

    #include <stdio.h>
    #include <dlfcn.h> /* 必须加这个头文件 */
     
    int main()
    {
       void *lib_handle;
       void (*fn1)(void);
       void (*fn2)(void);
       char *error;
     
       lib_handle = dlopen("libtest.so", RTLD_LAZY);
       if (!lib_handle) 
       {
          fprintf(stderr, "%s\n", dlerror());
          return 1;
       }
     
       fn1 = dlsym(lib_handle, "test1");
       if ((error = dlerror()) != NULL)  
       {
          fprintf(stderr, "%s\n", error);
          return 1;
       }
     
       fn1();
     
       fn2 = dlsym(lib_handle, "test2");
       if ((error = dlerror()) != NULL)  
       {
          fprintf(stderr, "%s\n", error);
          return 1;
       }
     
       fn2();
     
       dlclose(lib_handle);
     
       return 0;
    }

    接口函数介绍:
    (1) dlopen
    函数原型:void *dlopen(const char *libname,int flag);
    功能描述:dlopen必须在dlerror,dlsym和dlclose之前调用,表示要将库装载到内存,准备使用。
    如果要装载的库依赖于其它库,必须首先装载依赖库。如果dlopen操作失败,返回NULL值;如果库已经被装载过,则dlopen会返回同样的句柄。
    参数中的libname一般是库的全路径,这样dlopen会直接装载该文件;如果只是指定了库名称,在dlopen会按照下面的机制去搜寻:
    a.根据环境变量LD_LIBRARY_PATH查找
    b.根据/etc/ld.so.cache查找
    c.查找依次在/lib和/usr/lib目录查找。
    flag参数表示处理未定义函数的方式,可以使用RTLD_LAZY或RTLD_NOW。RTLD_LAZY表示暂时不去处理未定义函数,先把库装载到内 存,等用到没定义的函数再说;RTLD_NOW表示马上检查是否存在未定义的函数,若存在,则dlopen以失败告终。

    (2) dlerror
    函数原型:char *dlerror(void);
    功能描述:dlerror可以获得最近一次dlopen,dlsym或dlclose操作的错误信息,返回NULL表示无错误。dlerror在返回错误信息的同时,也会清除错误信息。

    (3) dlsym
    函数原型:void *dlsym(void *handle,const char *symbol);
    功能描述:在dlopen之后,库被装载到内存。dlsym可以获得指定函数(symbol)在内存中的位置(指针)。
    如果找不到指定函数,则dlsym会返回NULL值。但判断函数是否存在最好的方法是使用dlerror函数,

    (4) dlclose
    函数原型:int dlclose(void *);
    功能描述:将已经装载的库句柄减一,如果句柄减至零,则该库会被卸载。如果存在析构函数,则在dlclose之后,析构函数会被调用。

    编译gcc -o main main.c -ldl 生成二进制程序main,执行输出
    This is do test1
    End of test1
    This is do test2
    End of test2

    到这里共享库动态加载就介绍完了:)
    最后模拟一下升级so故障:
    在执行main的时候,趁sleep期间cp 另外的so文件覆盖libtest.so,一会就出现Segmentation fault。
    但是如果是mv 另外的so文件覆盖libtest.so,则无此问题,或者先rm libtest.so 再cp/mv 也不会有问题,因此升级方法就是这两种,当然最好是先停应用再升级。至于原因,可以参考我前一篇博客《Linux cp mv rm ln 命令对于 inode 和 dentry 的影响》


    12.5更新:
    今天咨询了维扬同学,可以用strace观察程序运行期间的系统调用,发现有不少mmap操作:

    省略前面
    open("/root/so/libtest.so", O_RDONLY)   = 3
    read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\240\3\0\0004\0\0\0"..., 512) = 512
    brk(0)                                  = 0x8227000
    brk(0x8248000)                          = 0x8248000
    fstat64(3, {st_dev=makedev(253, 0), st_ino=17559, st_mode=S_IFREG|0755, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=16, st_size=4348, st_atime
    =2012/05/13-14:13:18, st_mtime=2012/05/13-14:13:01, st_ctime=2012/05/13-14:13:01}) = 0
    mmap2(NULL, 5772, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x6e6000
    mmap2(0x6e7000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = 0x6e7000
    close(3)                                = 0
    munmap(0xb7753000, 15020)               = 0
    fstat64(1, {st_dev=makedev(0, 11), st_ino=3, st_mode=S_IFCHR|0620, st_nlink=1, st_uid=0, st_gid=5, st_blksize=1024, st_blocks=0, st_rdev=makedev(136, 0), st_
    atime=2012/05/13-14:56:03, st_mtime=2012/05/13-14:56:03, st_ctime=2012/05/13-14:53:31}) = 0
    mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7756000
    write(1, "This is do test1\n", 17)      = 17
    rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
    rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
    rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
    nanosleep({10, 0}, 0xbfd63fe4)          = 0
    write(1, "End of test1\n", 13)          = 13
    write(1, "This is do test2\n", 17)      = 17
    rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
    rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
    rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
    nanosleep({10, 0}, 0xbfd63fe4)          = 0
    --- SIGSEGV (Segmentation fault) @ 0 (0) ---
    +++ killed by SIGSEGV +++

    SIGSEGV信号估计和mmap只读映射之后写入(覆盖)文件有关?
    详见续篇《为何cp覆盖进程的动态库(so)会导致coredump》

    参考资料:
    http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html
    http://hi.baidu.com/luoxsbupt/item/a9d346b7653a2771254b09bc

  • 相关阅读:
    python 脚本 百里守约100%瞬狙
    Vue.js框架:超出配置登出时间就会退出登陆(前端设置)
    记一次canvas叠加错位的问题(unity2017)
    找到一篇很牛的内存管理文章(转)
    lua 懒注入
    CPP和C#交互语法速览
    CPP和C#交互实践
    CPP和C#交互
    Linux配置SFTP
    pandas Dataframe merge 后出现重复行
  • 原文地址:https://www.cnblogs.com/cnland/p/2969337.html
Copyright © 2020-2023  润新知