嵌入式Linux ARM汇编(六)——GNU ARM汇编编程
不同的汇编器对汇编语言的语法要求不一样。目前常用的ARM汇编环境有以下两种:
A、ARM ASM:ARM公司的汇编器,适合在Windows平台下使用
B、GNU ARM ASM:GNU交叉编译工具链中的汇编器,适合于Linux开发平台。
GNU汇编器是GNU工具集的一部分,用于将汇编语言文件转化为二进制obj文件。GNU汇编器针对的是多种处理器架构,这意味着GNU汇编器的语法不同于ARM工具链的汇编器。
一、GUN ARM汇编指令格式
1、GNU ARM汇编指令格式
GNUARM汇编指令的格式如下:
[
为标号, GNU汇编中,任何以冒号结尾的标识符都被认为是一个标号,而不一定非要在一行的开始。
Instruction:指令
Directive:伪操作
pseudo-instruction:伪指令
@comment:注释语句
2、标号label的解析
GNU ARM汇编指令中的标号只能由a~z,A~Z,0~9,“.”,_等(由点、字母、数字、下划线等组成,除局部标号外,不能以数字开头)字符组成。
标号label本质上代表它所在行的地址,因此也可以当作变量或者函数来使用。段内标号的地址值在汇编时确定,段外标号的地址值在连接时确定。
标号label依据生成方式分为三类:
A、基于PC的标号。基于PC的标号是位于目标指令前的标号或者程序中数据定义伪操作前的标号。这种标号在汇编时将被处理成PC值加上(或减去)一个数字常量,常用于表示跳转指令”b”等的目标地址,或者代码段中所嵌入的少量数据。
B、基于寄存器的标号。基于寄存器的标号常用MAP和FIELD来定义,也可以用EQU来定义。这种标号在汇编时将被处理成寄存器的值加上(或减去)一个数字常量,常用于访问数据段中的数据。
C、绝对地址。绝对地址是一个32位数据。它可以寻址的范围为[0x00000000,0xFFFFFFFF],即可以直接寻址整个内存空间。
局部标号的使用说明:
局部标号主要在局部范围内使用,而且局部标号可以重复出现。它由两部组成:开头是一个0-99直接的数字,后面紧接一个通常表示该局部变量作用范围的符号。局部变量的作用范围通常为当前段,也可以用ROUT来定义局部变量的作用范围。
局部变量定义的语法格式:N{routname}
A、N:为0~99之间的数字。
B、routname:当前局部范围的名称(为符号),通常为该变量作用范围的名称(用ROUT伪操作定义的)。
局部变量引用的语法格式:%{F|B}{A|T}N{routname}
A、%:表示引用操作
B、N:为局部变量的数字号
C、routname:为当前作用范围的名称(用ROUT伪操作定义的)
D、F:指示编译器只向前搜索
E、B:指示编译器只向后搜索
F、A:指示编译器搜索宏的所有嵌套层次
G、T:指示编译器搜索宏的当前层次
例:使用局部符号的例子,一段循环程序
1:
subs r0, r0, #1 @每次循环使r0=r0-1
bne 1F @跳转到1标号去执行
如果F和B都没有指定,编译器先向前搜索,再向后搜索;
如果A和T都没有指定,编译器搜索所有从当前层次到宏的最高层次,比当前层次低的层次不再搜索。
如果指定了routname,编译器向前搜索最近的ROUT伪操作,若routname与该ROUT伪操作定义的名称不匹配,编译器报告错误,汇编失败。
二、GNU ARM汇编程序的分段
1、GNU ARM汇编中段的定义
GNU ARM汇编中通过.section伪操作来自定义一个段,格式如下:
.section section_name [, "flags"[, %type[,flag_specific_arguments]]]
Section_name:段名
Flags: ELF格式允许的段标志: a:可分配 w:可写段 x:执行段
Type:段类型
flag_specific_arguments:指定参数内容
定义一个段,每一个段以段名为开始,以下一个段名或者文件结尾为结束。
2、GNU ARM汇编系统预定义的段
.text @代码段
.data @初始化数据段 .data Read-write initialized long data.
.bss @未初始化数据段
.sdata @ .sdata Read-write initialized short data.
.sbss @
注意:源程序中.bss段应该在.text段之前。
程序执行代码放于.text段,需要读写的数据放于.data段,只读数据放于.rodata段,未初始化数据放于.bss段
三、GNU ARM汇编程序的入口点
汇编程序的缺省入口是_start标号,用户也可以在连接脚本文件xxx.lds中用ENTRY标志指明其它入口点。
例:定义入口点
.section .data
< initialized data here>
.section .bss
< uninitialized data here>
.section .text
.globl _start
_start:
四、GNU ARM汇编的宏定义
宏定义的格式如下:
.macro 宏名 参数名列表 @伪操作.macro定义一个宏
宏体
.endm @.endm表示宏结束
如果宏使用参数,那么在宏体中使用该参数时添加前缀“\”。宏定义时的参数还可以使用默认值。可以使用.exitm伪指令来退出宏。
五、GNU ARM汇编中的常数
A、十进制数以非0数字开头,如:123和9876;
B、二进制数以0b开头,其中字母也可以为大写;
C、八进制数以0开始,如:0456,0123;
D、十六进制数以0x开头,如:0xabcd,0X123f;
E、字符串常量需要用引号括起来,中间也可以使用转义字符,如: “You are welcome!\n”;
F、当前地址以“.”表示,在汇编程序中可以使用这个符号代表当前指令的地址;
G、表达式:在汇编程序中的表达式可以使用常数或者数值, “-”表示取负数, “~”表示取补,“<>”表示不相等,其他的符号如:+、-、*、 /、%、<、<<、>、>>、|、&、^、!、==、>=、<=、&&、|| 跟C语言中的用法相似。
六、GNU ARM汇编的伪操作
1、数据定义伪操作
A、.byte:单字节定义,如:.byte 1,2,0b01,0x34,072,'s' ;
B、.short:定义双字节数据,如:.short 0x1234,60000 ;
C、.long:定义4字节数据,如:.long 0x12345678,23876565
D、.quad:定义8字节,如:.quad 0x1234567890abcd
E、.float:定义浮点数,如:
.float 0f-314159265358979323846264338327\
95028841971.693993751E-40 @ - pi
F、.string/.asciz/.ascii:定义多个字符串,如:
.string "abcd", "efgh", "hello!"
.asciz "qwer", "sun", "world!"
.ascii "welcome\0"
需要注意的是:.ascii伪操作定义的字符串需要自行添加结尾字符'\0'。
G、.rept:重复定义伪操作, 格式如下:
.rept 重复次数
数据定义
.endr @结束重复定义
例如:
.rept 3
.byte 0x23
.endr
H、.equ/.set: 赋值语句, 格式如下:
.equ(.set) 变量名,表达式
例如:
.equ abc 3 @让abc=3
I、.int:定义一个整型
语法格式:.int expressions
2、.if伪操作
根据一个表达式的值来决定是否要编译下面的代码, 用.endif伪操作来表示条件判断的结束, 中间可以使用.else来决定.if的条件不满足的情况下应该编译哪一部分代码。
A、.ifdef label @判断label是否定义
B、.ifc string1,string2 @字符串string1和string2是否相等,字符串可以用单引号括起来
C、.ifeq expression_r @判断expression_r的值是否为0
E、.ifeqs string1,string2 @判断string1和string2是否相等,字符串必须用双引号括起来
F、.ifge expression_r @判断expression_r的值是否大于等于0
G、.ifgt absolute expression_r @判断expression_r的值是否大于0
H、.ifle expression_r @判断expression_r的值是否小于等于0
I、.iflt absolute expression_r @判断expression_r的值是否小于0
J、.ifnc string1,string2 @判断string1和string2是否不相等, 其用法跟.ifc恰好相反。
K、.ifndef symbol, .ifnotdef symbol @判断是否没有定义symbol, 跟.ifdef恰好相反
L、.ifne expression_r @如果expression_r的值不是0, 那么编译器将编译下面的代码
M、.ifnes string1,string2 @如果字符串string1和string2不相 等, 那么编译器将编译下面的代码.
3、操作码
A、NOP
nop
空操作, 相当于MOV r0, r0
B、LDR
ldr
相当于PC寄存器或其它寄存器的长转移.
C、ADR
adr
相于PC寄存器或其它寄存器的小范围转移.
D、ADRL
adrl
相于PC寄存器或其寄存器的中范围转移。
4、其他伪操作
A、.end:表明源文件的结束
B、.extern:用于声明一个外部标号
语法格式:.extern label
C、.global/.globl:用来声明一个全局标号
语法格式:.global/.globl label
D、.zero、.space、.skip
分配number_of_bytes字节的数据空间,.zero伪操作用0填充内存,.space/.skip用值为fill_byte填充内存,若未指定该值,缺省填充0
语法格式:
.zero
.space/.skip
E、.reg、.unreq
.reg: 用来给寄存器赋予别名,格式如下:
别名 .req 寄存器名
.unreq: 用来取消一个寄存器的别名,格式如下:
.unreq 寄存器别名
注意被取消的别名必须事先定义过,否则编译器就会报错,这个伪操作也可以用来取消系统预制的别名,
F、.include:
语法格式:.include "filename"
在当前源文件中展开filename的内容。
G、.align
用来指定数据的对齐方式
语法格式:
.align [absexpr1, absexpr2]
以某种对齐方式,在未使用的存储区域填充值. 第一个值表示对齐方式,
表示2^n字节对齐,第二个表达式值表示填充的值。
.balignl 4 0xdeadbeef 4字节对齐,l表示以四字节单位填充,填充0xdeadbeef
H、.incbin
.incbin伪操作可以将原封不动的一个二进制文件编译到当前文件中,语法格式:
.incbin "file"[,skip[,count]]
skip表明是从文件开始跳过skip个字节开始读取文件,count是读取的字数。
七、GUN ARM汇编中的寄存器
寄存器命名规范:
通用寄存器:R0--R15
栈指针寄存器:SP(R13)
帧指针寄存器:FP(R11)
链接寄存器:LR(R14)
程序计数器:PC(R15)
程序状态寄存器:xPSR, xPSR_all, xPSR_f, xPSR_x, xPSR_ctl, xPSR_fs,
xPSR_fx, xPSR_f, xPSR_cs, xPSR_cf, xPSR_cx
编程过程中对所有寄存器的赋值必须遵循AAPCS(The ARM Architecture Procedure Call Standard)。
八、GNU ARM汇编与ARM ASM汇编语法比较
GNU汇编器与ARM汇编器语法比较:
GNU Assembler |
ARM ASM |
备注 |
@ |
; |
注释 |
#& |
#0x |
十六进制立即数 |
.if |
IFDEF,IF |
不完全相同 |
.else |
ELSE |
|
.elseif |
ELSEIF |
|
.endif |
ENDIF |
|
.ltorg |
LTORG |
|
| |
:OR: |
OR |
& |
:AND: |
AND |
<< |
:SHL: |
Shift left |
>> |
:SHR: |
Shift right |
.macro |
MACRO |
开始宏 |
.end |
ENDM |
结束宏 |
.include |
INCLUDE |
|
.word |
DCB |
A data word |
.short |
DCW |
|
.long |
DCD |
|
.byte |
DCB |
|
.req |
RN |
|
.global |
IMPORT |
|
EXPORT |
||
.equ |
EQU |