Linux加油站 - ELF文件

目录

定义

一 :可重定位文件

二 :可执行文件

三:共享对象文件


定义

        在Linux下面,二进制的程序也要有严格的格式,这个格式称为ELF(Executeable and Linkable Format,可执行与可链接格式)。该格式可以根据编译的结果不同,分为不同的格式。

一 :可重定位文件

        定义头文件 cal.h ,定义两个声明:

#include<stdio.h>

// 加法声明
int add(int a, int b);
//乘法声明
int mult(int a , int b);

       add.c , mult.c  两个文件分别为函数的实现:

// add.c
#include "cal.h"

int add(int a , int b){
    return a+b;
}


//mult.c
#include "cal.h"

int mult(int a , int b){
    return a*b;
}

        编写main函数,main函数里用到了上面定义的两个函数:

#include<stdio.h>
#include "cal.h"

int main(){
    
    int a = 20;
    int b = 12;
    printf("a = %d , b = %d\n", a, b);
    printf("a + b = %d\n", add(a, b));
    return 0;
}

        执行以下:

gcc -c add.c mult.c 

// -c参数是gcc编译器的可选参数,表示只编译源文件但不链接

        可以看到生成了两个 .o 文件,就是ELF的第一种类型,可重定位文件(Relocatable File)。

        该文件类型的描述图:

        一个一个的section,我们也叫节。这个编译好的二进制文件里面,应该是代码,还有一些全局变量、静态变量等等。

        为什么只有全局变量呢?局部变量是放在栈里面的,是程序运行过程中随时分配空间,随时释放的,现在讨论的是二进制文件,还没启动所以只需要讨论在哪里保存全局变量。

  • .text:放编译好的二进制可执行代码
  • .data:已经初始化好的全局变量
  • .rodata:只读数据,例如字符串常量、const的变量
  • .bss:未初始化全局变量,运行时会置0
  • .symtab:符号表,记录的则是函数和变量
  • .strtab:字符串表、字符串常量和变量名

        为什么叫可重定位文件?可以想象一下,这个编译好的代码和变量,将来加载到内存里面的时候,都是要加载到一定位置的。比如说,调用一个函数,其实就是跳到这个函数所在的代码位置执行;再比如修改一个全局变量,也是要到变量的位置那里去修改。但是现在这个时候还是一个 .o文件,不是一个可以直接运行的程序,这里面只是部分代码片段。

        例如这里的 add(),mult() 函数,将来被谁调用,在哪里调用都不清楚,就更别提确定位置了。所以,.o里面的位置是不确定的,但是必须是可重新定位的,因为它将来是要做函数库的,就是一块砖,哪里需要哪里搬,搬到哪里就重新定位这些代码、变量的位置。


二 :可执行文件

        要想让add(),mult() 两个函数作为库文件被重用,不能以.o的形式存在,而是要形成库文件,最简单的类型是 静态链接库.a文件(Archives)。

        拿上面的两个编译后的 .o 文件,执行以下代码创建一个我们自己的 cal.a 静态库:

ar -cr libcal.a add.o mult.o

// ar 是创建静态库命令
// -cr 是可选参数
// libcal.a 这个.a 文件就是自己创建好的静态库文件 .a(Achive)
// add.o mult.o 两个文件进行打包,作为静态库文件以便使用

         创建好了静态库,就可以与我们的main 进行链接,main就可以使用静态库里面的函数了,执行以下:

gcc -o mainexe main.c -L . -l cal

// -o是gcc编译器可选参数,用于指定编译链接完成后的可执行文件名
// -L 指定静态库的所在存储位置, .表示当前路径
// -l 指定静态库的名称,不需要前面的lib和扩展名.a,他会自动加上,只需要中间的库名字部分

 

         形成的二进制文件mainexe称为可执行文件,是ELF的第二种格式。

        

        该文件类型的描述图:

        格式和.o文件大致相似,还是分成一个个的section,并且被节头表描述。只不过这些section是多个.o文件合并过的。

        但是这个时候,这个文件已经是马上就可以加载到内存里面执行的文件了,因而这些section被分成了需要加载到内存里面的代码段、数据段和不需要加载到内存里面的部分,将小的section合成了大的段segment(例如代码段就属于一个segment)。


三:共享对象文件

        前面的静态库有些缺点:静态链接库一旦链接进去,代码和变量的section都合并了,因而程序运行的时候,就不依赖于这个库是否存在。但是这样有一个缺点,就是相同的代码段,如果被多个程序使用的话,在内存里面就有多份,而且一旦静态链接库更新了,如果二进制执行文件不重新编译,也不随着更新。

        动态链接库  .so 文件:不仅仅是一组对象文件的简单归档,而是多个对象文件的重新组合,可被多个程序共享。

        从头开始,执行以下代码,先编译两个库文件,然后创建一个动态链接库 cal.so文件。:

gcc -c -fpic add.c mult.c

// 编译源文件生成 .o目标文件。
// -fPIC ,gcc编译器可选参数作用于编译阶段,告诉编译器产生与位置无关代码,
    产生的代码中没有绝对地址,全部使用相对地址,故代码可以被加载器加载到内
    存的任意位置,都可以正确的执行。这正是动态库所要求的,动态库被加载时,
    在内存的位置不是固定的


gcc -shared add.o mult.o -o libcal.so

// -shared也是gcc编译器可选参数,作用是告诉编译器生成一个动态链接库。
// libcal.so 就是自己所指定的动态库文件名字。

        可以看到动态库文件也是属于可执行文件,将我们的main.c 文件与动态库进行链接:

gcc -o mainexe main.c -L. -l cal

//各参数含义与静态库链接含义一样。

        完成后会生成可执行文件 mainexe , 我们 ./运行以下该执行文件:

         为什么会有这种错误 ? 

补充一下两种库的工作原理:

        静态库 GCC 进行链接时,会把静态库中代码打包(复制)到可执行程序中,程序运行时,可执行文件里就包含了所有的代码,直接运行。

        动态库 GCC 进行链接时,动态库的代码不会被打包到可执行程序中,只是打包一些有关于动态库的信息,在运行时才找到动态库文件位置,加载代码后才运行。

        程序启动之后,动态库会被动态加载到内存中,通过 ldd (list dynamic dependencies) 命令检查动态库依赖关系。

        使用lld + 可执行程序  命令(lld mainexe ): 

         框起来的就是我们自己制作的动态库,显示 not found, 那程序运行起来时,是如何查找定位动态库的呢?

        当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统的动态载入器来获取该绝对路径。

        对于ELF 格式的可执行程序,是由 ld-linux.so 来完成的,它先后搜索 elf文件的 DT_RPATH段  —  环境变量LD_LIBRARY_PATH  —  /etc/ld.so.cache文件列表  —  /lib/ 或 /usr/lib目录 找到库文件后将其载入内存。

        可以设定LD_LIBRARY_PATH环境变量,程序运行时会在此环境变量指定的文件夹下寻找动态链接库:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/hua/Myproject/1_basic/lib

//$LD_LIBRARY_PATH 表示先获取原先环境变量的内容。
// :后面就代表追加的新环境变量值,此处追加的是我们制作的 libcal.so动态库的绝对路径。

        执行完以上命令后,我们再次看看动态库依赖关系 lld mainexe :

         可以看到我们的动态库查找得到路径了,运行 maixnexe 则可以成功!(但这只是临时配置好的环境变量,把终端关闭后在运行还是会报错误,因此需要永久配置环境变量,此处不在展开。)

   

        动态链接库,就是ELF的第三种类型,共享对象文件(Shared Object)。

        基于动态连接库创建出来的二进制文件格式还是ELF,但是稍有不同。首先,多了一个.interp的Segment,这里面就是ld-linux.so,这是动态链接器,也就是说,运行时的链接动作都是它做的。

关于动态库更详细了解参考:

so库生成和用法汇总_so库是编译生成的么_dinghuiyang的博客-CSDN博客

总结

        1. 编译后的 .o 文件就是ELF的第一种文件类型 :可重定位文件。

        2. 编译后且用静态库或者动态库完成链接后的文件,叫做直接运行的程序,是第二种类型:可执行文件。

        3. 单独的一个动态库文件 .so ,是第三种类型:共享对象文件。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值