动态库

作者: 欧阳_z | 来源:发表于2020-09-14 17:08 被阅读0次

    1、编译隐藏的过程
    假设有一个源文件:

    #include <stdio.h>
    #pragma pack(2)
    
    #define N 666
    #define PR_D(x) printf(#x" = %d\n", x )
    #define CONNECT(a,b) (a##b)
    
    #ifdef N
        #define VERSION "2.0"
    #else
        #define VERSION "0.1"
    #endif
    
    int ijk;
    
    int main(void)
    {
        printf("(%s,%s,%s,%d)\n", __TIME__, __FILE__, __FUNCTION__,  __LINE__); /* print __LINE__ */
        printf("%s\n", VERSION); // print version
        PR_D(ijk);
        printf("%d\n", CONNECT(i,jk) );
    
        return 0;
    }
    

    通常用 gcc 完整的编译命令是 gcc hello.c -o hello
    上面的完整编译可以分解为 预处理、编译、汇编、链接。

    预处理可以用命令 cpp hello.c > hello.i 或者 gcc -E hello.c -o hello.i

    $ gcc -E hello.c
    ...
    # 2 "hello.c" 2
    #pragma pack(2)
    # 14 "hello.c"
    
    # 14 "hello.c"
    int ijk;
    
    int main(void)
    {
        printf("(%s,%s,%s,%d)\n", "07:03:49", "hello.c", __FUNCTION__, 18);
        printf("%s\n", "2.0");
        printf("ijk"" = %d\n", ijk );
        printf("%d\n", (ijk) );
    
        return 0;
    }
    

    可以看到:
    #include 被展开了,头文件内容太多不放在这里了。
    #define 被展开了,除了__FUNCTION__,其他都替换了。
    #if#ifdef 等条件编译被展开了,VERSION 已经替换成 "2.0" 了。
    注释已经被删除了。
    #pragma 会保留,在后面编译阶段处理。
    添加了行号、文件名等标识,以便下一个过程编译可以产生调试用的行号,以及如果编译报错时可以打印行号。

    如果代码里面有太多条件编译,不知道哪个定义了,哪个没定义时,可以查看 gcc -E的信息,或者直接使用#pragma message

    #ifdef N
        #pragma message("N defined!")
    #else
        #pragma message("N undeclared!")
    #endif
    

    编译阶段的命令是 gcc -S hello.i -o hello.s
    汇编段的命令是 as hello.s -o hello.o 或者 gcc -c hello.s -o hello.o

    查看 .c 代码的汇编代码可以用 gcc -S -g -o hello.s hello.c 查看 .o文件的汇编代码可以用 objdump -S hello.o > hello.s

    2、静态链接
    如果一些函数很通用,其他项目也要用,可以抽出来,单独做成库,这样就不用写重复代码了。
    生成静态库:

    gcc -c xxx.c -o xxx.o
    ar -rcs libxxx.a xxx.o
    

    链接:

    gcc main.o -L . -lxxx -o s.out
    

    可以查看静态库包文件中含了哪些文件:

    $ ar -t libxxx.a
    xxx.o
    

    ar tv /usr/lib/gcc/x86_64-linux-gnu/8/libgcc.a
    

    ar -x 可以从 *.a 解压出 *.o文件:

    ar -x libxxx.a
    

    静态链接的缺点:
    一是浪费空间,每个可执行程序中都有一份所有需要的目标文件的副本;
    二是更新比较麻烦,每当库函数的代码修改了,不仅要重新编译静态库,还需要重新链接所有用到该库的项目,以形成新的可执行程序。
    优点就是执行的时候速度快一些。

    3、动态链接
    可执行文件较小,在程序运行时才将它们链接在一起形成一个完整的程序,不会在内存中存在多份副本。

    生成动态库的命令:

    gcc -c xxx.c -o xxx.o
    gcc -fPIC -shared xxx.o -o libxxx.so
    

    链接的命令和静态是一样的,不过动态链接后,运行时可能会报错

    error while loading shared libraries: libxxx.so: cannot open shared object file: No such file or directory
    

    因为找不到该动态库,需要把库文件所在目录添加到环境变量:

    export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
    

    查询一个可执行文件依赖哪些动态库:

    $ ldd a.out 
        linux-vdso.so.1 (0x00007ffd04588000)
        libxxx.so => ./libxxx.so (0x00007f3989050000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3988c5f000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f3989454000)
    

    查看某个函数是否在某个库文件中

    $ nm -D /lib/x86_64-linux-gnu/libc.so.6 |grep xxx_1
    $ nm -D libxxx.so |grep xxx_1
    000000000000065a T xxx_1
    

    除了 nm 命令,还可以使用 readelfobjdumpstrings 命令,这些命令都属于 GNU binutils 工具集。:

    $ readelf -a libxxx.so |grep xxx_1
         8: 000000000000065a    31 FUNC    GLOBAL DEFAULT   12 xxx_1
        55: 000000000000065a    31 FUNC    GLOBAL DEFAULT   12 xxx_1
    $ objdump -tT libxxx.so |grep xxx_1
    000000000000065a g     F .text  000000000000001f              xxx_1
    000000000000065a g    DF .text  000000000000001f  Base        xxx_1
    $ strings libxxx.so |grep xxx_1
    xxx_1
    xxx_1
    xxx_1
    

    比如我们在嵌入式编程,改了一个函数,想看看效果,又不想完整编译、重新升级,这时候替换板子上的 .so 文件会比较快,这时候就需要确认一下这个函数属于哪一个库文件。

    /usr/lib//usr/local/lib//lib/ 目录有很多 *.a 和 *.so 文件,可以用这些命令来测试玩一玩。

    相关文章

      网友评论

          本文标题:动态库

          本文链接:https://www.haomeiwen.com/subject/hdbzektx.html