汇编器是将汇编代码翻译为机器目标代码的程序。通常,汇编代码通过汇编器生成目标代码,然后由链接器链接成最终的可执行二进制程序。
编译流程
-
预处理pre- process:gcc的预处理器cpp对各种预处理命令进行处理。包括对头文件的处理、宏定义的展开、条件编译的选择等。预处理完成后会生成test.i文件。
gcc -E test.c -o test.i
-
编译compile:c语言的编译器CC首先对预处理之后的源文件进行词法、语法以及语义分析,然后进行代码优化,最后把c语言代码翻译成汇编代码。
gcc -S test.i -o test.s
-
汇编assemble:汇编器AS把汇编代码翻译成机器语言,并生成可重定位目标文件。汇编完成后,生成test.o文件
as test.s -o test.o
-
链接link:链接器LD会把所有生成的可重定位目标文件以及用到的库文件组合成一个可执行二进制文件
ld -o test test.o -lc
ELF文件
汇编阶段生成的可重定位目标文件以及链接阶段生成的可执行二进制文件都是按照一定的格式组成的二进制文件。在Linux系统中,应用程序常用的可执行文件格式为ELF,它是对象文件的一种格式,用于定义不同类型的对象文件中都存放了什么内容,以及用什么格式存放这些内容。
- ELF header:描述整个文件的基本属性,包括ELF文件版本、目标计算机型号、程序入口地址等信息
- program header table:描述如何创建一个进程的内存镜像
- text section:存放程序源代码编译后生成的机器指令
- rodata section:存储只能读取不能写入的数据
- data section:存放已初始化的全局变量和已初始化的局部静态变量
- bss section:存放未初始化的全局变量和未初始化的局部静态变量
- symtab section:存放函数和全局变量的符号表信息
- rel.text section:存储代码段的重定位信息
- rel.data section:存储数据段的重定位信息
- debug section:存储调试使用的符号表信息
- section header table:描述ELF文件中包含的所有段信息,包括每个段的名字、段的长度、在文件中的偏移量、读写权限以及段的其他属性等
汇编阶段生成的可重定位目标文件和链接阶段生成的可执行二进制文件的主要区别在于:可重定的目标文件的所有段的起始地址都是0,而链接器在链接过程中会根据链接脚本的要求,把所有可重定位目标文件相同的段进行合并,并且重新确定每个段的虚拟地址和加载地址。
汇编语法
注释
- 单行注释 // 或者 #
- 多行注释 / /
符号
符号一般用于标记程序或数据的位置,而不是使用内存地址来进行标记。符号可以由大小写字母、数字以及_ * $组合而成。
符号可以代表它所在的地址,也可以当作变量或函数使用
- 全局符号:使用.global声明。全局符号可以被其它模块应用。c语言中也可以引用全局符号
- 本地符号:使用.L前缀定义本地符号。本地符号不会出现在符号表中
- 本地标签:可供汇编器和程序员临时使用。标签通常使用0~99的整数作为编号,和f指令与b指令一起使用。f表示向前搜索,b表示向后搜索。出现重名的标签时,跳转指令只能引用最近定义的本地标签
伪指令
伪指令是对汇编器发出的命令,它在源程序汇编期间由汇编器处理
-
对齐伪指令 .ALIGN:用于对齐或填充数据等。对齐伪指令一般有三个参数。第一个参数表示对齐的要求,以2^n字节进行对齐。第二个参数表示要填充的值,一般默认为0。第三个参数表示对齐指令应该跳过的最大字节数,如果为了对齐需要跳过比第三个参数更多的字节数,则不会执行对齐操作。
-
数据定义伪指令
- .byte:把8位数当作数据插入汇编代码中
- .hword / .short:把16位数当作数据插入汇编代码中
- .long / .int:把32位数当作数据插入汇编代码中
- .word:把32位数当作数据插入汇编代码中
- .quad:把64位数当作数据插入汇编代码中
- .float:把浮点数当作数据插入汇编代码中
- .ascii / .string:把string当作数据插入汇编代码中,对于ascii伪操作定义的字符串,需要自行添加结尾字符\0
- .asciz:与ascii类似,在string后自动插入\0
- .rept / .endr:重复执行伪操作
- .equ:给符号赋值
-
函数相关伪指令
- .global:定义一个全局的符号,可以是函数的符号,也可以是全局变量的符号
- .include:引用头文件
- .if .else .endif:控制语句
- .ifdef symbol:判断symbol是否被定义
- .ifndef symbol:判断symbol是否没有定义
- .ifc string1,string2:判断字符串是否相等
- .ifeq expression:判断expression是否为0
- .ifeqs string1,string2:等同于ifc
- .ifge expression:判断expression是否大于等于0
- .ifle expression:判断expression是否小于等于0
- .ifne expression:判断expression是否不为0
-
段相关的伪指令
-
.section伪指令表示接下来的汇编会链接到某个段,如代码段、数据段等
.section name, "flag"
name表示段的名称,flag表示段的属性
-
.pushsection .popsection伪指令通常需要配对使用,用于把代码链接到指定的段,其它代码还保留在原来的段中
-
.previous伪指令。通常与.section伪指令配对使用。用来把一段汇编代码链接到特定的段。这里.section表示开始一个新的段,结尾的.previous表示恢复到.section之前的段。
-
-
宏相关的伪指令
-
.macro 和 .endm伪指令可以用来组成一个宏
.macro macname macargs .... .... .endm
在宏里使用参数,需要添加前缀“\”。在定义宏参数时,可以设置默认值。
.macro add_data p1=0 p2 mv a5, \p1 mv a6, \p2 add a1, a5, a6 .endm
把宏的多个参数作为字符串连接在一起
.macro my_entry, rv, label j rv\()\rv()_\label .endm
通过my_entry 1, irq调用宏,展开后变成 j rv1_irq
-
RISC-V特有命令行选项
- -fpic:生成与位置无关的代码
- -fno-pic:不生成与位置无关的代码
- -mani=ABI:指定源代码使用哪个ABI(ilp32 、 lp64)
- -march=ISA:指定目标体系结构
- -miss-spec=ISAspec:选择目标指令集的版本
- -little-endian:小端
- -big-endian:大端