前面两篇讲了ARM64汇编指令,本篇主要讲一下汇编器,何为汇编器呢?
汇编器主要是将汇编语言翻译为及其目标代码的程序
编译流程与ELF文件
-
编译流程
1、预处理:GCC的预编译器(CPP)对各种预处理命令进行处理,例如对头文件的处理、宏定义的展开、条件编译的选择等。
gcc -E test.c - o test.i
2、编译:C语言的编译器(ccl)首先对预处理之后的源文件进行词法、语法以及语义分析,然后进行代码优化,最后把C语言代码翻译成汇编代码
gcc -S test.i -o test.s
3、汇编:汇编器(as)将汇编代码翻译成机器语言,并生成可重定位的目标文件
as test.s -o test.o
4、链接:链接器(ld)会把所有生成的可重定位目标文件以及用到的库文件综合成一个可执行二进制文件。
ld -o test test.o -lc
-
ELF文件
汇编阶段生成的可重定位目标文件以及链接阶段生成的可执行二进制文件都是按照一定的文件格式(ELF)组成的二进制目标文件
ELF文件格式
ELF文件头 | 包含了描述整个文件的基本属性,如ELF版本,程序入口等 | readelf - h test |
---|---|---|
程序头表 | 描述如何创建一个进程的内存映像 | |
.text段 | 代码段:存放程序源代码编译后的机器指令等 | |
.rodata段 | 只读数据段:存储只能读取不能写入的数据 | |
.data段 | 数据段:存放已初始化的全局变量和已初始化的局部静态变量 | |
.bss段 | 未初始化数据段:存放未初始化的全局变量和局部静态变量 | |
.symtab段 | 符号表:存放函数和全局变量的符号表信息 | readelf -s test |
.rel.text段 | 可重定位代码段:存放代码段的重定位信息 | |
.rel.data段 | 可重定位数据段:存储数据段的重定位信息 | |
.debug段 | 调试符号表段:存储调试使用的符号表信息 | |
….. | ||
段头表(section header) | 用于描述ELF文件中包含的所有段信息,段名字,长度,偏移量 | readelf -S test |
汇编阶段生成的可重定向目标文件和链接阶段生成的可执行二进制文件的主要区别在于,可重定向目标文件的所有段的起始地址都是0。
ld --verbose 可查看自带的链接脚本
汇编语法
-
注释
“//”或者“#”:注释某一行
“/**/”:可以跨行注释
-
符号
符号一般用来标记程序或数据的位置,而不用内存地址来标记它们。
符号可以代表它所在的地址,也可以当作变量或者函数来使用
全局符号: .global声明。全局符号可以被其他模块引用。
本地符号:主要在本地汇编代码中引用。在ELF格式中,通常使用“.L”前缀来定义一个本地符号,本地符号不会出现在符号表中
本地标签:可供汇编器和程序员临时使用,通常使用0~99的整数作为编号,和f指令和b指令一起使用。f表示汇编器向前搜索,b表示汇编器向后搜索。
-
常用的伪指令
伪指令是向汇编器发出的命令,伪指令仅仅在汇编器编译期间起作用,当汇编结束时,伪指令的使用也就结束 了。伪操作可以实现一下功能:
-
符号定义
-
数据定义和对齐
-
汇编控制
-
汇编宏
-
段描述
-
对齐伪指令
.align 有三个参数,第一个参数表示对齐的要求,第二个参数表示要填充的数(可忽略),第三个参数表示这个对齐应该跳过的最大字节数。通常只是用第一个参数。
.align 2 #4字节对齐 |
---|
-
数据定义伪指令
.byte:1字节 .hword和.short:2字节 .long和.int:4字节 .word:4字节 .quad:8字节 .float:浮点数 .ascii和.string:对于.ascii伪操作定义的字符串,需要自行添加结尾字符'\0' .asciz:类似于.ascii,会自动插入一个结尾字符'\0' .rept和.endr:重复执行伪操作 .equ:给符号赋值 |
---|
例1:
.rept 3 //相当于执行三次.long 0 .long 0 .endr |
---|
例2:.equ my_data1,100 //为mydata1符号赋值100
-
与函数相关的伪指令
.global:定义一个全局符号
.include :引用头文件
.if, .else, .endif:控制语句
.ifdef symbol:判断symbol是否定义
.ifc string1,string2:判断字符串是否相等
.ifeq expression: 判断是否等于0
.ifeqs string1,string2:等同.ifc
.ifge expression:判断expression的值是否大于等于0.
.ifle expression:判断是否小于等于0
.ifne expression:判断是否不为0
-
与段相关的伪指令
-
.section伪指令
表示接下来的汇编会链接到某个段,例如代码段、数据段等
格式:.section name, "flag" name表示段的名称;flag表示段的属性,如下表
属性 | 说明 |
---|---|
a | 段具有可分配属性 |
d | 具有GNU_MBIND属性的段 |
e | 段被排除在可执行和共享库之外 |
w | 段具有可写属性 |
x | 段具有可执行属性 |
M | 段具有可合并属性 |
S | 段包含零终止字符串 |
G | 段是段组的成员 |
T | 段用于线程本地存储 |
-
.pushsection 和 .popsection伪指令
一般配对使用,把代码链接到指定的段,
例:.pushsectionn ".idmap.text", "awx"
…..
.popsection
-
与宏相关的伪指令
.marco和.endm伪指令可以用来组成一个宏。.marco伪指令的格式:
.marco macname macargs 宏名 宏参数
-
宏的参数使用
①在宏里使用参数,需要添加前缀“\”
例1:
.marco reserve p1 p2 add x0, \p1,\p2.endm //使用reserve a,b来调用 |
---|
②在宏参数后面加入“:req”表示在宏调用工程中必须传递一个值,否则会报错
例2
.marco reserve p1:req p2 add x0, \p1,\p2 .endm //使用reserve a,b来调用 |
---|
-
宏的特殊字符
①把宏的参数作为字符串连接在一起
.macro opcode base length
\base\().\length //\()来告知汇编器,宏的参数什么时候结束
.endm //输出base.length
AArch64依赖特性
GNU汇编器中AArch64特有的命令行选项
选项 | 说明 |
---|---|
-EB | 大端(big-endian)处理器编码 |
-EL | 小端(little-endian)处理器编码 |
-mabi=abi | 指定源代码使用哪个ABI,可识别的参数是ilp32和ilp64,分别生成ELF32或ELF64格式的对象文件 |
-mcpu=processor[+extension..] | 用来指定目标处理器和扩展特性,例如指定cortex-a72等。 |
-march=architecture[+exten] | 用来指定目标体系结构,例如armv8.1-a |
-mverbose-error | 启用详细的错误消息 |
-mno-verbose-error | 关闭详细的错误消息 |
语法
1.特殊字符
//:注释一行
#:如果一行的第一个字符是“#”,则整行被视为注释
2.重定位
ADRP
AArch64特有的伪指令
.arch name:设置目标体系结构
.arch_extension name:向目标体系结构添加扩展或从目标体系结构删除扩展
.bss:切换到.bss段
.cpu name:设置目标处理器
.dword expressions:64位数
.even:将输出对齐到下一个偶数字节边界
name .req register name:为寄存器定义一个别名,如: foo .req w0
.xword expressions:64位数