现在最新的32位的ARM/THUMB汇编语言叫做UAL(Unified Assembler Language,统一的汇编语言),已经取代了早期版本的ARM/THUMB汇编语言。用UAL编写的代码可以被汇编为 ARM, Thumb/Thumb-2指令。最新的一些工具反汇编你的代码之后可能都是用UAL来表示了,但是仍然有大量的代码是用旧格式来编写。
汇编语法规则
汇编文件中源码行的一般格式如下:
{label} {instruction|directive|pseudo-instruction} {;comment}
大括号内的每个字段都是可选的。
- label其实是对内存中的一个地址的命名,最终会转化为一个数字值,在编码时你只需在代码中用label即可剩下的交给编译器来处理。
注意:label在代码中只能定义一次,并且需要从行首顶格开始(当然也有一些编译器支持在任意位置放置label,这种需要用分隔符进行分隔,比如冒号)
-
{instruction|directive|pseudo-instruction}跟在label之后,和label之间要有一个或者多个空格或者有tab键隔开(就算前面没有label的话也要这样,否则会被认为是label,一个新手ARM汇编程序员常犯的错误就是没有label时把instruction|directive|pseudo-instruction顶格写)
-
instruction是具体的ARM汇编指令,例如ARM7TDMI的ARM指令集如下图所示:
image.png
对大多数的instruction语句(也有一些是例外)一般的格式如下:
instruction destination, source, source
source有多种格式,可以包括数字,寄存器,寄存器旋转等等
- directive(英文翻译过来也是指令的意思,有的书把它翻译为保留字),从功能上来说其实有点像编译器的预处理命令,不同编译器的directive并不一样。比如下面的ARM官方的keil(使用的是armasm编译器)和TI 的CSS常用的directive。
-
pseudo-instruction,直白翻译过来就是伪指令,它可以像instruction出现在汇编源码行中,但编译器在遇到pseudo-instruction会进一步进行转化和解释把它用其他的instruction进行代替,为什么要这么麻烦呢,主要的目的是减轻汇编编写者的负担,通过汇编器自动去处理而不是手动去处理,从而提高编程效率和降低编程犯错空间。
-
comment,注释,以分号';'开始到行结束
常用的directive
这里以armasm编译器中常用的directive进行说明。
- AREA
功能:定义一个数据块/代码块
语法格式如下:
AREA sectionname{,attr}{,attr}…
sectionname是该段的名称。名字可以随便取,但是如果名字是以数字开头的话需要用一对竖线把名字括起来,比如, |1_DataArea|; 不这样会语法报错。
常用的一些attr(属性)
ALIGN = expr 让该段以2的expr次方的边界对齐。比如, expr = 10,则该段以1K为对齐边界。
CODE 该字段是机器代码(默认是READONLY)
DATA该字段是数据 (默认是READWRITE)
READONLY 该字段放在只读内存 ( CODE段的默认值)
READWRITE 该字段放在可读可写内存 (DATA段的默认值)
例子:
AREA Example,CODE,READONLY ; 一个example代码段.
; code
- RN
功能:可以理解为为寄存器定义一个符号别名
语法格式如下:
name RN expr
expr取值范围:0~15
例子:
dest RN 0 ;
- EQU
功能:为常量指定符号名称或者说把符号和常量等同起来(类似C语言的#define预处理命令)
语法格式:
name EQU expr{,type}
其中name是该分配给该值的符号名称,expr可以是一个寄存器相关的地址,程序相关的地址,绝对的地址,32位的整数常量都可以。type是可选的,可以是下面的任意一个:
ARM
THUMB
CODE16
CODE32
DATA
例子:
xyz EQU label+8;把(label+8)这个地址分配给符号xyz
fiq EQU 0x1C, CODE32 ;把0X1C这个绝对地址分配给fiq,并把它标记为code
- ENTRY
功能:为程序声明一个入口点,你的程序至少含有一个ENTRY点,否则在链接时会发出警告。
语法格式:
ENTRY
例子:
AREA ARMex, CODE, READONLY
ENTRY ; 应用程序的入口点
- DCB, DCW, DCD
功能:分配内存并指定初始值,DCB以字节为单位进行存储, DCW{U}以半字(2字节)为单位,DCD{U}以字为单位(4字节),DCW{U}/DCD{U}带U表示不考虑内存对齐,用DCW需对齐到半字,DCD对齐到字,其他没有差别。
语法格式:
{label} DCB expr{,expr}…
{label} DCW{U} expr{,expr}…
{label} DCD{U} expr{,expr}...
其中expr是一个数值表达式或带引号的字符串(字符串的字符连续的存储在内存中),如果是数值的话DCB的取值范围是−128~255,DCW是 −32768~65535。上面说的对齐,比如DCD可在第一个定义的字前面最多填充3个字节以实现4字节对齐,如果不需要对齐就使用DCDU。DCB是以字节为对齐的,如果后面有跟汇编指令的话最好在后面用ALIGN来对齐一下,确保汇编指令对齐到正确的内存位置。
例子1:
与C中的字符串不同,ARM汇编器字符串不是NULL终止的。 您可以使用DCB构造一个以NULL结尾的字符串,如下所示:
C_string DCB "C_string",0
如果这个字符串从内存中的地址0x4000开始,它在内存中应该是这样
0x4000 43 C
0x4001 5F _
0x4002 73 s
0x4003 74 t
0x4004 72 r
0x4005 69 i
0x4006 6E n
0x4007 67 g
0x4008 00
例子2
coeff DCW 0xFE37, 0x8ECC ;定义了两个半字
DATA1 DCD 1,2,3;定义了3个字1,2,3
DATA2 DCD DATA1+4;定义了一个字包含DATA1地址+4
AREA mydata,DATA,READWRITE
DCB 255;现在未对齐
DATA3 DCDU 3,4,5;定义了3个字包含3,4,5未对齐到字
- ALIGN
功能:把当前位置数据/代码通过填充0对齐到特定的内存边界
语法格式:
ALIGN {expr{,offset}}
当前的位置被对齐到下一个地址的方式如下:
offset + n * expr
其中offset可以是任何数值表达式,n是汇编器为了最小化填充而选择的任何整数,expr可以是数值表达式但算出的结果必须是一个2的次幂,最小为2的零次方,最大为2的31次方。
如果未指定expr这些的话,ALIGN是把当前位置设置到下一个字(4字节)边界处。
例子1:
AREA OffsetExample, CODE
;下面两字节是在同一个字内,字的第一个字节和第4个字节,n=0,
;第二个DCB距第一个DCB的偏移offset是3
DCB 1
ALIGN 4,3
DCB 1
例子2:
start LDR r6, = label1
; code
MOV pc,lr
label1 DCB 1 ; pc现在不是对齐的
ALIGN ; 确保subroutine1能正确寻址到下面的指令
subroutine1 MOV r5, #0x5
- SPACE
功能:预留特定大小的零值内存块。一般用来为子程序预留变量,表,存储数据空间等。
语法格式:
{label} SPACE expr
其中expr表达式的结果为要预留的总字节数。
例子:
AREA MyData, DATA, READWRITE
data1 SPACE 255 ; 定义了255个字节的零值存储空间
- MACRO和MEND
功能:这两个组合用来一起定义一个宏。
语法格式:
MACRO
{$label} macroname{$cond} {$parameter{,$parameter}…}
; code
MEND
其中label参数一般用来做标签的替换符号,在宏被调用时进行替换。macroname是宏的名字。cond是一个特殊的参数被设计用来包含一个条件码,然而他也允许使用有效条件码以外的值。parameter参数在宏被调用时进行对应的参数替换,可以用$parameter="default value"来给该参数赋值默认值。
你可以把这些参数理解为变量,在宏内部你可以像变量一样的使用$label,$cond,$parameter,但是你必须使用$作为开头来引用这些变量以区分普通符号。当然这些参数都是可选的。
例子:
; 宏的定义
MACRO
$label xmac $p1,$p2
; code
$label.loop1 ; code
; code
BGE $label.loop1
$label.loop2 ; code
BL $p1
BGT $label.loop2
; code
ADR $p2
; code
MEND
; 宏的调用
abc xmac subr1,de
下面是宏展开的样子
; code
abcloop1 ; code
; code
BGE abcloop1
abcloop2 ; code
BL subr1
BGT abcloop2
; code
ADR de
; code
- LTORG
功能:指示汇编器立即汇编当前的文字池。
语法格式:
LTORG
汇编器会在每个代码段的末尾汇编当前的文字池。何为代码段的末尾,即以AREA(作为下一代码段开始)或者END作为分割点。为什么需要LTORG,因为有时代码太长了,如果有些伪指令(比如LDR)使用默认的文字池(放在代码段末尾)会超出查找范围而出错。使用LTORG就可以确保文字池在有效的范围内被汇编。需要注意的是LTORG必须放在无条件的分支跳转或者子程序还回汇编指令之后。这样处理器就不会把文字池的常量当作汇编指令来执行。汇编器会按字对齐文字池中的数据。
例子:
AREA Example, CODE, READONLY
start BL func1
func1 ; 函数体
; code
LDR r1,=0x55555555 ; 伪指令转化为汇编=> LDR R1, [pc, #相对文字池1的偏移量]
; code
MOV pc,lr ; 函数结束
LTORG ; 文字池1包含文字 &55555555.
data SPACE 4200 ; 预留了4200个零值的内存空间
END ; 默认的文字池,现在是空的.
备注:如果注释掉LTORG,编译出错,提示文字池太远了,因为用SPACE分配了4200的空间导致末尾的默认文字池离LDR超过了4K的访问,所以需要用LTORG来立即生成一个文字池来存放对应的常量。
- END
功能:告诉汇编器,你已经在文件的末尾了。
语法格式:
END
armasm汇编器常用的运算符
A:MOD:B | A对B取模 |
---|---|
A:ROL:B | 对A循环左移B位 |
A:ROR:B | 对A循环右移B位 |
A:SHL:B 或者 A << B | 对A左移B位 |
A:SHR:B or A >> B | 对A右移B位 |
A + B | 把A加到B |
A - B | 把B从A中减去 |
A:AND:B | 对A和B执行按位与 |
A:EOR:B | 对A和B执行按位异或 |
A:OR:B | 对A和B执行按位或 |
例子:
MOV r0,#1:SHL:33
MOV R1,#1:SHR:1
MOV R2,#1:ROL:33
MOV R3,#1:ROR:1
MOV R4,#5:MOD:3
MOV R5,#0XFF:EOR:0XF0
MOV R6,#0XF0:OR:0X0F
参考文献
【1】ARM Assembly Language Fundamentals and Techniques (2nd ed.)
【2】DUI0801I_armasm_user_guide
网友评论