汇编器(assembler)到底做了什么?
(注:这部分的内容以ARM64为目标平台)
强烈推荐去看看CSAPP,这个笔记只对后续用到的部分进行粗略的学习;
一段代码从文本变成可执行文件,需要经历 预编译,编译,汇编,链接,这四个步骤中,这篇笔记着重了解后两步;
(资料图)
首先准备2个C语言文件:main.c和sum.c;(汇编阶段我们以main.c举例)
对main.c进行编译,生成main.s方便后续分析;请注意,这里的main.s只不过是由main.c翻译而来的由汇编语言写成的代码,main.s只是代码,只是代码,只是代码;
汇编(as)与ELF文件:
使用as对一个.s(汇编语言编写的代码)进行汇编,会产生一个.o文件(我们一般称其为“目标文件”);这个过程中汇编器做了什么?目标文件里又包含了什么信息?
main.s
经过汇编之后生成的main.o
这个目标文件是ELF格式文件,即executable and linkable format(即可执行与可链接格式);使用readelf命令可以查看ELF文件的内容;
其中:
elf文件的elf header
elf头中包含的诸多内容中较为重要的是:
Magic:魔数,这个值决定了该文件的格式;例如java文件的Magic就是cafebabe(coffee baby),只有以cafebabe开头的文件才会被java虚拟机认为是java程序;
Class:文件类型,表明这个文件是64elf还是32elf
Data:大小端等信息
Type ELF:说明文件是Relocatable file还是Executable file(可执行文件)还是shared object file(共享对象文件)还是core dump file(core dump文件);例子中这个文件是relocatable file(可重定位文件)
Entry point address:入口指针,这个值指向程序的入口,因为目前我们分析的是一个可重定位文件而非可执行文件,因此没有入口,所以这里是0x00
Start of section headers:表示段表(section table)的起始地址;这个例子中888(换算成十六进制是0x378)
elf文件的section header
section header中记录了程序的“段信息”(也就是我们常说的数据段,代码段...的段信息);
段信息包括:
我们的例子中有11个段(Nr=0的那个不算):.text(代码段),.rela.text(需要重定位的指令),.data(数据段),.bss(未初始化数据段),.comment(存放编译器版本信息),.note.GNU-stack,.eh_frame(),.rela.eh_frame(),.symtab(符号表),.strtab(字符串表),.shstrtab(section header string table);
详细说明一下其中部分表的作用:
rela.text:用于保存text段需要重定位的指令;
data数据段:保存 已经初始化的全局变量 和 已经初始化的局部静态变量(注意:这里只存放这两种数据,普通的局部变量并不会放在data里)
bss未初始化数据段:保存 未初始化的全局变量 和 未初始化的局部静态变量(注意:初始化为0 会被视为没有初始化...)bss段实际上在这个阶段是不占位置的
strtab字符串表:这个段中会存储elf文件中用到的各种字符串
section header中给出了各个段的offset(偏移地址),所以我们可以直观的看到经过汇编之后的elf文件的数据信息分布:
这里要注意,在完成汇编之后,各个段虽然拿到了偏移地址,但是Address(虚拟地址)还是0
elf文件中.text段中的具体内容
text段即为代码段,这个段中存储着程序中的代码;
使用objdump对main.o进行反汇编可以看到代码段的内容
这几条指令明显的对应着C语言中的
从机器码的角度看,这三条指令操作码后的内容是0,也就是说这几条指令引用的符号地址是0,0当然不可能是符号真正的地址;
为什么会这样?因为这一句代码中使用了sum
函数,而sum
函数在main.c中没有定义,汇编器无法确定符号地址;
这个地址,在链接的时候才会被具体的设置;
elf文件中.symtab符号表的详细说明
使用readelf -s [文件名]可以查看符号表
符号表将在链接过程中起着无比重要的作用;符号表的结构被定义在/usr/include/elf.h中的
这是64位的elf符号表的结构
其中:
st_name:符号名
st_info:符号类型和绑定信息
st_other:符号能见度?(这个貌似目前也没用到...)
st_shndx:符号所在的段目录(即符号在哪个段,如果这个符号就在本文件里;那么这里的值就是符号所在的段,例如text或者data等..但是如果这个符号不是本文件里的,需要从外部链接,则会有所不同,下文详细介绍)
st_value:符号的值
st_size:符号的大小,对于包含着数据的符号这个值则表示数据的长度;
符号类型与绑定(st_info):
从定义来看,符号的绑定 指的是符号的生效范围,是local,global,或者是weak;
符号类型 实际上是相当丰富的,可以是未定义,可以是一个对象,可以是一个函数,一个文件,一个段等等...
符号所在段(st_shndx):
常用的特殊段有:
SHN_ABS:符号包含了一个绝对值
SHN_COMMON:表示这类的符号要在链接的时候被处理;(如果定义了一个未初始化的全局变量,这个变量就会被归到这里)
SHN_UNDEF:表示这个符号没有被定义,需要到其他的文件中找
符号值(st_value)
如果符号是一个函数或者是一个变量,那么符号的值就是这个符号的地址;
(对于目标文件)如果符号不是上面说的common类型的,那么符号值是段中的 偏移地址
(对于目标文件)如果符号是common类型的,那么符号值是符号的"对齐属性"
(对于可执行文件)st_value是符号的虚拟地址;
elf文件中.strtab字符串表
使用readelf -x 指定打印strtab信息
strtab的中的字符串是代码中用到的字符串,诸如函数的名字(例如main函数使用的"main"就可以在这里被找到),变量的名字(例如程序中声明的数组的名字”array“可以在这被找到),还有文件的名字(main.c);
但是 strtab段 不包括程序中用于输出的字符串!(如果程序中有printf("helloworld")之类的代码,那么"helloworld"这个字符串将会被放在.rodata中)
要注意这个段里的内容实际上是连续的,这样我们只需要用strtab段的基地址+偏移地址就能取到这个段中的所有字符串
下篇笔记中,我将学习:汇编器无法确定的符号的地址,是如何被链接器确定的?链接中所谓的“重定位”到底是什么东西?