美文网首页
Liunx链接库生成与链接原理

Liunx链接库生成与链接原理

作者: 小小混世魔王 | 来源:发表于2019-05-17 17:25 被阅读0次

    Android是一种基于Linux的开放源代码的操作系统,在之前写的OpenCV人脸校验,人脸识别文中https://www.jianshu.com/p/b833d0d8af80。文中提到Liunx平台下怎么编译so库问题。接下啦我们就来具体看看单个C文件的编译过程。

    1.gcc 编译步骤

    以hello.c文件为例

    #include<stdio.h>
    #define TAG "yang"
    
    void main()
    {
       printf("hello world %s",TAG);
    }
    
    
    gcc -o hello hello.c 
    

    通过gcc -o hello hello.c命令 经过一系列的操作将hello.c文件编译打包成一个hello的可执行项目。.c文件-->可执行文件经历了哪些步骤呢?接下來把gcc编译打包成执行文件过程拆开。
    1.预处理阶段:

    gcc -E -o hello.i hello.c
    

    通过gcc -E的命令将.c文件生成.i文件。用vim查看.i文件的内容,发现.i文件的内容非常的多,从中复制一段

      省略代码多行.........
    
    extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
    # 868 "/usr/include/stdio.h" 3 4
    
    # 2 "hello.c" 2
    
    
    
    
    # 5 "hello.c"
    void main()
    {
       printf("hello world %s","yang");
    }
    
    

    上面内容看到 stdio.h 的内容都插到文件里去了,包括printf()函数里面的TAG 已近替换上了具体的字符串“yang”。可以得知gcc编译的第一阶段预处理阶段是将源文件中引入的头文件进行展开,define的清除,注释的删除,包括宏定义的替换等。
    2.编译阶段:

    gcc -S -o hello.s hello.i
    

    执行gcc -S命令将.i文件编成.s文件,查看.s文件的内容如下

            .file   "hello.c"
            .section        .rodata
    .LC0:
            .string "yang"
    .LC1:
            .string "hello world %s"
            .text
            .globl  main
            .type   main, @function
    main:
    .LFB0:
            .cfi_startproc
            pushq   %rbp
            .cfi_def_cfa_offset 16
            .cfi_offset 6, -16
            movq    %rsp, %rbp
            .cfi_def_cfa_register 6
            leaq    .LC0(%rip), %rsi
            leaq    .LC1(%rip), %rdi
            movl    $0, %eax
            call    printf@PLT
            nop
            popq    %rbp
            .cfi_def_cfa 7, 8
            ret
            .cfi_endproc
    .LFE0:
            .size   main, .-main
            .ident  "GCC: (Ubuntu 7.2.0-8ubuntu3.2) 7.2.0"
            .section        .note.GNU-stack,"",@progbits
    

    相比.i文件.s文件内容又少了,但是似乎我们看的并不是太懂。gcc -S这个过程将c代码翻译成汇编代码包括词法分析、语法分析、语义分析等。
    3.汇编阶段

    gcc -c hello.o -o hello.s
    

    这个时候我们在去查看文件发现是乱码。这个过程将第二阶段生产的汇编代码编译成机器可执行的指令。
    4.链接阶段(链接静态库,动态库)

    gcc -o hello hello.o
    

    由于源文件会用到其他库的函数,在这个过程会去计算逻辑地址,合并数据段等。

    2.静态库与动态库

    2.1静态库的制作
    以mathUtils,c为例编译成.a静态库。

    #include<stdio.h>
    
    int add(int a,int b) {
    
         return a+b;
    
    }
    

    2.1.1生成目标.o文件

    gcc -c mathUtils.c -o mathUtils.o 此过程生成.o文件但是不会去链接库。
    

    2.1.2将目标文件进行打包

    ar cvr myMathUtils.a mathUtils.o
    

    2.1.3这样静态库就生成了。接着以来看看怎么使用链接这个静态库。以statictest.c为例

    #include<stdio.h>
    #include "mathUtils.c"
    
    void main(){
        int a = 10;
        int b = 10;
        int result = add(a,b);
        printf("result = %d",result);
    
    }
    

    将statictest.c编译成.o文件不去链接

    gcc -c statictest.c -o statictest.o
    

    接下来我们就去链接myMathUtils.a库

    gcc -o statictest statictest.o libmathUtils.a
    

    这个过程链接libmathUtils.a库并生成可执行文件statictest 。
    statictest 运行效果


    statictest.jpg

    2.1.4链接静态库的原理:
    objdump 反汇编

    objdump -S statictest > test.txt
    
    反汇编.png

    可以看到我标记的红色的重要地方,这些就是链接过程中计算逻辑地址(相对的地址),main()函数调用add()函数 是 callq 64a <add>固定地址值,指向 000000000000064a <add> 地址,也就是add函数的相对地址。
    通过分析可以得出,一个项目引入静态库中的函数,就相当于拷贝了静态库中的函数。所以编译过的项目,即使没有静态库,项目也是可以运行的。
    静态库的缺点:
    (1)同一个模块被多个模块链接时,那么这个模块在磁盘和内存中都有多个副本,导致很大一部分空间被浪费了。
    (2)当程序的任意一个模块发生更新时,整个程序都要重新链接。

    2.2动态库的制作:
    2.2.1 将 .c文件生成与位置无关的目标.o文件

    gcc -c mathUtils.c -o mathUtils.o -fPIC
    

    2.2.2使用 gcc -shared 制作动态库

    gcc -shared mathUtils.o -o libmathUtils.so
    

    2.2.3 编译sharedtest.c 不去链接.so库

    gcc -c sharedtest.c -o sharedtest.o
    

    2.2.4链接动态库并生成可执行文件

    gcc -o sharedtest sharedtest.o libmathUtils.so
    

    2.2.5动态库链接原理:
    我们依旧使用上图(反汇编图)来分析动态库的链接原理,上面我们在main()函数中用到了printf()函数,调用时是callq 520 < printf@plt> 。
    Plt(延迟绑定),程序引用了动态库,在没有运行程序时链接函数的地址是不确定的,要运行程序就必须先加载动态库与动态链接器到进程地址空间,系统运行可执行文件之前,会将控制权交给动态链接器,由它完成所有的动态链接工作以后再把控制权交给可执行文件。这样就链接到函数地址。
    动态库的缺点:
    (1)链接的过程是在程序运行之后开始的,并经过链接器一系列的寻址,才能链接到函数,会导致程序运行效率变慢。

    小结:

    上面介绍单个c文件的编译过程,对于整个项目包含许多c文件,编译项目采用Makefile文件。Makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译。Makefile的编写基于单个文件编译过程。通过Makefile可以实现编译的自动化。

    相关文章

      网友评论

          本文标题:Liunx链接库生成与链接原理

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