位置: 首页 > 热点 > > 正文

汇编器(assembler)到底做了什么?

2023-01-21 05:49:26 来源:哔哩哔哩

注:这部分的内容以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段的基地址+偏移地址就能取到这个段中的所有字符串

下篇笔记中,我将学习:汇编器无法确定的符号的地址,是如何被链接器确定的?链接中所谓的“重定位”到底是什么东西?

标签: LOCAL 汇编语言 ARRAY GLOBAL TEXT Java