计算机系统基础学习心得——ELF文件相关

最近在学习CSAPP的链接篇,在学习中也有一些小小的发现,在此发出来与大家分享一下。

我们的对象代码如下:

main.c

int sum(int *a, int n);
int array[2] = {1,2};
int main()
{
    int val = sum(array,2);
    return val;
}
sum.c

int sum(int *a, int n)
{
    int i, s = 0;
    for(i = 0; i < n; i++){
        s += a[i];
    }
    return s;
}

代码很简单,main.c和sum.c。main函数初始化一个整数数组,然后调用sum函数来对数组元素求和。而我们本次的目标就是利用gcc对这两个.C源文件进行编译汇编形成.O可重定位文件,再利用readelf命令去看这两个.O文件的相关信息。


以下是一些前要补充知识(大概就差不多是废话的意思,已经了解的朋友可以跳过):

ELF文件大体可分为三种:

1.可执行目标文件:包含二进制代码和数据,其形式可以被直接复制到内存并执行。一般就是a.out文件。

2.可重定位目标文件:包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件。而这次我们的重点就是放在可重定位目标文件上。一般就是指.O文件。

3.共享目标文件:一种特殊类型的可重定位目标文件,可以在加载或者运行是被动态地加载进内存并链接。


以下的话是本次实验的过程:

1.利用gcc命令对两个.C源文件进行编译汇编形成.O可重定位文件

之后我们就成功得到了main.o和sum.o。

2.利用readelf命令加“-h”参数查看两重定位文件的ELF头,结果如下(因为两重定位文件的ELF基本没有什么不同,所以就只用main.o的来说明了),

如图所示,利用这个命令,我们可以得到很多的信息,例如第一行的“Magic”,直译过来就是“魔数”,当然一般有效的只有前四组,就是在本图中的“7f 45 4c 46”,通常被用来确定文件的类型或格式。而这里的魔数就是表示“ELF”了。所以在加载或读取文件时,就可用魔数来确认文件类型是否正确。

之后接下来就还有版本信息,存储方式是大端法还是小端法,操作系统平台,目标文件类型,机器结构,版本等等,在此就不再赘述了。

(P.S.还有需要注意一点的就是在这个ELF头中入口点地址显示为“0x0”,原因就是该文件本身的类型是可重定位.O文件,没有程序头表,所以没有入口地址,而在可执行目标文件a.out中这里的入口地址就不会是0了,这点还需注意。)

3.利用readelf命令加“-S”参数查看节头表信息(注意这里的参数为大写的“S”)(这里也就只用main.o来作代表)。

使用“-S”参数后,我们看到的就是main.o的节头表信息,命令下的第一行内容中的偏移量就是指节头表开始的位置,这个偏移量也会在ELF头中给出,之后会有一个对于各节地址信息的图展示出来。


插入一些感觉比较重要的节的说明(当然也可以跳过啦):

.text:在这里存放已编译程序的机器代码(就是二进制的)。

.data:在这里存放已初始化的全局和静态C变量。(注意是已初始化的!!!)

.bss:在这里存放未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量。还有一个节叫COMMON,也是存放未初始化的全局变量的,上课时老师说这两者的区别就在于0或非0。比较在意这点,就去查了一下。以下摘自博主「Hannibal.」的《.bss与COMMON以及它们之间的区别》(链接:https://blog.csdn.net/weixin_43730678/article/details/88849719):

为什么采取这两种方式来分配全局和静态变量的符号?

采取这两种看起来很绝对的区分方式的原因来自于连接执行符号解析的方式。在某些情况下连接器允许多个模块定义同名的全局符号。当编译器材翻译某个符号时,遇到一个弱全局符号(已初始化的全局变量是强全局符号,未初始化的全局变量是弱全局符号),比如一个未初始化的全局变量x,编译器无法知道其他模块是否也定义了这个x。如果其他模块也定义了x,编译器无法预测连接器该使用x的多重定义中的哪一个。所以编译器把x分配给COMMON,把决定权留给连接器。另一方面,如果x初始化为0,那么他就是一个强全局符号,根据连接器的规则如果有一个强符号和一个弱符号,那么选择强符号。所以编译器会直接把x给.bss。类似的,静态符号的构造就必须是唯一的,所以编译器可以自信的把他们分配到.data或.bss。

.rodata:在这里存放只读数据,比如printf语句中的格式串和switch语句中的跳转表。

以上这几个节都会被分配存储空间。


接下来是节头表的正文部分,其中比较有意思的部分就是Addr那一项,这一项是指节的虚拟地址,一眼看下去全都是“00000000”,这又是为什么呢?当然也是因为文件本身还是重定位目标.O文件的缘故,还并不能被加载,所以也就不会又虚拟地址了。

之后还有off,指的是节在文件中的偏移地址,也是相当重要的一项。它和之后的Size项一起就能帮助我们画出各节地址的图表了。

(P.S.节头表的开头一项总是为NULL,且偏移地址也为0,就是指示相对地址的开头。)

以下是各节地址相关的图表(因为找不到工具,就只能手绘了):

4.利用readelf命令加“-s”参数(注意是小写的“s”)查看符号信息(同上,也是用main.o来作示例):

利用readelf命令加上“-s”参数就可以看到如上图所示的符号表信息。


以下依旧是可以跳过的补充知识:

1.由模块m定义并能被其他模块引用的全局符号(global symbols)。一般就是指非静态的C函数和全局变量(没用加static)。

2.由其他模块定义并被模块m引用的全局符号,被称为外部符号(external symbols)。对应于在其他模块中定义的非静态C函数和全局变量。

3.只被模块m定义和引用的局部符号(local symbols)。它们对应于带static属性的C函数和全局变量。这些符号在模块m中任何位置都可见,但是不能被其他模块调用。


如表中所示,“main.c”是文件类型且只能在该模块中被使用(因为属性是LOCAL),而“array”数组和“main”函数则可以被其他模块调用(因为属性是GLOBAL)。还有比较令人在意的一点是“sum”函数,因为没有和“sum.o”链在一起,所以在这里它被解释为类型不明的(NOTYPE)全局符号(GLOBAL),如果和“sum.o”链接在一起的话应该就能正确显示为外部符号(EXTERNAL)了。

以上。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值