对比参考:剖析.o文件ELF组成
相比.o的ELF格式,有哪些变化?
.rel.text和.rel.data消失了
为什么这两个节会消失?
链接器将各.o中同名的.text和.data节整合到一起时,会对整合后的.text和.data进行重定位。其实重定位时主要针对就是.text和.data节,不过这.text和.data节重定位时需要依赖.rel.text和.rel.data中的信息,一旦重定位结束后,这两个节的使命就完成了,自然也就会消失。
多出了两个节
init节
作用
这个节会提供_init等函数,专门用于实现程序的一些初始化。程序入口为_start,从_start开始执行后,在正式调用main函数之前,会先调用_init等函数进行程序的初始化(比如建立函数栈等等)。
init节怎么来的
回顾gcc链接的过程,
-dynamic-linker /lib64/ld-linux-x86-64.so.2 //动态链接器
-lc //libc,常用c函数库——c标准库的子库
crtend.o crtn.o //扫尾代码
“可执行目标文件”的各个节归类
程序最终运行时,需要搬到内存上的节有:ELF/.init/.text/.rodata/.data/.bss。搬到内存上什么位置呢?搬到重定位的“运行地址”所指定的位置。
ELF/.init/.text/.rodata:只读的存储段(代码段)
.data/.bss:可读可写存储段(静态数据段)。之所以称为静态数据段,是因为.data/.bss的空间规划,是在编译时就进行了理论安排,并不是程序运行起来才安排的,所以被称为静态数据段。
程序的加载、运行
编译得到可执行目标文件后,就可以将“可执行目标文件”加载“运行地址”所指的内存位置,然后运行了。不过这里还是要分两种情况来看,第一种是裸机运行的情况,第二种是基于OS虚拟内存运行的情况。
裸机的情况
使用专门针对裸机的编译器来编译程序,最后得到的就是可以在裸机上运行的可执行程序。加载裸机程序时,由专门的加载程序(加载软件)来实现的。
加载
其实加载的过程就是将“代码段”和“数据段”复制到内存上。裸机时,链接器重定位后的“运行地址”是真实的物理地址,加载时直接将“代码段”和“数据段”复制到物理内存中“运行地址”所指定的位置。裸机运行地址是多少,可以由我们程序员自己来定。裸机时就不是ELF格式头了,而是bin格式头。
运行
①CPU的PC(程序计数器)存放第一条指令_start的地址,也就是将PC指向第一条指令_start。pc是cpu的寄存器之一。
②从_start开始执行启动代码。
③启动代码调用_init等函数进行初始化。初始化有一件非常重要的事情就是,从内存划出一片空间出来用作堆和栈,因为空间是以堆和栈的方式来管理的,因此就称为堆 和 栈。
④启动代码调用main函数,main函数再调用各个子函数,我们自己写的代码就开始运行了。
⑤main函数调用return关键字,返回到启动代码。
对于裸机的来说,返回到启动代码就结束了。至于return的返回值,有没有返回值,对于裸机来说都没有什么影响。就算有返回值,将返回值返回给启动代码后,这个返回值对启动代码来说也没有什么意义。所以说,对于裸机来说,其实main函数的返回值没有什么意义,所以大家在学习单片机时候,以前的main函数的返回值都是void的。
void main(void) { return; }