GNU ARM 汇编伪指令(Assembler Directives)

 

原文:http://blog.sina.com.cn/s/blog_6859cadf0101i821.html

GNU ARM 汇编伪指令(Assembler Directives)

以前用ARM的IDE工具,使用的是ARM标准的汇编语言。现在要使用GNU的工具,当然要了解一点GNU ARM汇编的不同之处。其实非常的简单,浏览一下文档然后再看看程序就完全可以搞定了,或者你硬着头皮看GNU ARM的汇编程序,用不了多少时间你就就可以无师自通了。

ARM汇编语言源程序语句 ,一般由指令、伪操作、宏指令和伪指令作成。ARM汇编语言的设计基础是汇编伪指令,汇编伪操作和宏指令。 

目前常用的ARM编译环境有2种:
ARMASM: ARM公司的IDE中使用了CodeWarrior的编译器,绝大多数windows下的开发者都在使用这一环境,完全按照ARM的规定; 
GNU ARM ASM:GNU工具的ARM版本,与ARMASM略有不同;

 

 

1. GNU ARM 汇编简介

    任何汇编行都是如下结构:

[:] [} @ comment
[<标签>:] [<指令>} @ 注释
 

     GNU ARM 汇编中,任何以冒号结尾的都被认为是一个标签,而不一定非要在一行的开始。下面是一个简单的例子,这段汇编程序定义了一个"add"的函数,该函数返回两个参数的和:

 

[cpp] view plaincopy

  1. .section .text, “x”  
  2. .global add                      @ give the symbol add external linkage  
  3. add:  
  4.          ADD r0, r0, r1          @ add input arguments  
  5.          MOV pc, lr              @ return from subroutine  
  6.                                  @ end of program  

 

2. GNU ARM汇编伪指令

    GNU ARM汇编伪指令均以.开始。常用汇编伪指如下所示:

 

汇编伪指令 描述 举例
.ascii “” 插入字符串到目标文件,但不以NULL结束 .ascii "JNZ" @插入字节0x4A 0x4E 0x5A
.asciz “” 与.ascii类似,但以NULL结束 .asciz "JNZ" @插入字节0x4A 0x4E 0x5A 0x00
.balign
      {,
    {,} }
使下面的位置对齐 .byte 0x55       @插入字节: 0x55
.balign 4,0      @插入3个对齐字节:0x00 0x00 0x00
.word 0xAA55EE11 @插入字节:0x11 0xEE 0x55 0xAA (LSB order)
.byte {,} … 插入字节到目标文件 .byte 64, 'A'         @ inserts the bytes 0x40 0x41
.byte 0x42            @ inserts the byte 0x42
.byte 0b1000011, 0104 @ inserts the bytes 0x43 0x44
.code 设置指令位数,16位Thumb或32位ARM .code 16    @设置以下汇编为Thumb指令
.else 与 .if 和 .endif 配合使用  
.end 标志汇编文件结束,汇编器不再读后面的内容  
.endif 怀.if 配合使用  
.endm 标志宏定义结束,与.macro配合使用  
.endr 结束一个loop,与.rept 和 .irp 配合使用  
.equ , 设置一个符号的值 .equ adams, (5 * 8) + 2  @ set adams to 42
.set adams, 0x2A         @ set adams to 42
adams = 0b00101010       @ set adams to 42
.err 汇编时如遇到.err,则停止编译  
.exitm 中途退出宏定义  
.global 标识symbol被程序的其它模块(汇编或C)访问,
相当于Kernel中的EXPORT_SYMBOL
.global _my_test_count  @它可被其它模块R/W
.hword {,}
插入16位(半字)值到目标文件 .hword 0xAA55, 12345 @ 插入字节 0x55 0xAA 0x39 0x30
.2byte 0x55AA, -1 @插入字节 0xAA 0x55 0xFF 0xFF
@ Least Significant Byte (LSB) ordering assumed
.if 与 .endif 配合使用  
.ifdef 与 .endif 配合使用  
.ifndef 与 .endif 配合使用  
.include “” 与C的#include类似  
.irp {,}
{,} …
Repeats a block of code, once for each value
 in the value list. Mark the end of the block 
using a .endr directive. In the repeated code block,
 use \ to substitute the associated 
value in the value list.
 
.macro {
{,} … {,}
定义一个名为name的汇编宏,它有N个参数,
且必须以.endm结束。
.macro SHIFTLEFT a, b
    .if \b < 0
        MOV \a, \a, ASR #-\b
       .exitm
    .endif
    MOV \a, \a, LSL #\b
.endm
.section
{,””}
开始一个新的section, section_name可为:
   .text: 代码段
   .data: 已初始化的数据段
   .bss: 未初始化的数据段
flags可为:
   a allowable section
   w writable section
   x executable section
.section .text, “x”
.set , 设置变化的值,与.equ一样 .set adams, 0x2A         @ set adams to 42
.space
{,}
预留给定字节空间,并设置为0或fill_byte .space 0x400,0
.word {,} … 插入32位字列表到目标文件 head_ptr: .word 0 @ Head pointer to within buffer (initially zero)
tail_ptr: .word 0 @ Tail pointer to within buffer (initially zero)
.word 0xDEADBEEF  @inserts the bytes 0xEF 0xBE 0xAD 0xDE
.4byte -42        @ inserts the bytes 0xD6 0xFF 0xFF 0xFF
         @ Least Significant Byte (LSB) ordering assumed
.rept 重复执行代码块number_of_times次,与C中for
类似,以endr结束
 
.req 寄存器重新命名 acc .req r0

 

 

3. ARM特殊字符和语法

 

@ 代码行中的注释符号
# 整行注释符号
; 语句分离符号
#或$ 直接操作数前缀
.arm 汇编使用ARM指令
.thumb 汇编使用Thumb指令
.code16 汇编使用Thumb指令
.code32 汇编使用ARM指令
.force_thumb 强制使用thumb模式,即使不支持
.thumb_func Mark entry point as thumb coded (force bx entry)
.ltorg Start a new literal pool(文字池:嵌入在代码中的用以存放常数的区域)

 

 

0 8进制
0x或0X 16进制
0b或0B 二进制
无前导符 10进制

 

 

 

.data 标识把随后的语句放入目标文件数据段
.text 标识把随后的语句放入目标文件的代码段
.extern symbol 从其它模块引入符号,类似C中的extern
.skip expression  在目标文件中skip expression指定的字节数
buffer: .skip 512 @Buffer of 512 bytes, uninitialised

 

 

第一部分 Linux下ARM汇编语法尽管在Linux下使用C或C++编写程序很方便,但汇编源程序用于系统最基本的初始化,如初始化堆栈指针、设置页表、操作 ARM的协处理器等。初始化完成后就可以跳转到C代码执行。需要注意的是,GNU的汇编器遵循AT&T的汇编语法,可以从GNU的站点(www.gnu.org)上下载有关规范。

一. Linux汇编行结构
任何汇编行都是如下结构:
[:] [} @ comment
[:] [} @ 注释
Linux ARM 汇编中,任何以冒号结尾的标识符都被认为是一个标号,而不一定非要在一行的开始。
【例1】定义一个"add"的函数,返回两个参数的和。
.section .text, “x”
.global add @ give the symbol add external linkage
add:
ADD r0, r0, r1 @ add input arguments
MOV pc, lr @ return from subroutine
@ end of program

二. Linux 汇编程序中的标号
标号只能由a~z,A~Z,0~9,“.”,_等字符组成。当标号为0~9的数字时为局部标号,局部标号可以重复出现,使用方法如下:
标号f: 在引用的地方向前的标号
标号b: 在引用的地方向后的标号
【例2】使用局部符号的例子,一段循环程序
1:
subs r0,r0,#1 @每次循环使r0=r0-1
bne 1f @跳转到1标号去执行
局部标号代表它所在的地址,因此也可以当作变量或者函数来使用。

三. Linux汇编程序中的分段
(1).section伪操作
用户可以通过.section伪操作来自定义一个段,格式如下:
.section section_name [, "flags"[, %type[,flag_specific_arguments]]]
每一个段以段名为开始, 以下一个段名或者文件结尾为结束。这些段都有缺省的标志(flags),连接器可以识别这些标志。(与armasm中的AREA相同)。

下面是ELF格式允许的段标志
<标志> 含义
a 允许段
w 可写段
x 执行段

【例3】定义段
.section .mysection @自定义数据段,段名为 “.mysection”
.align 2
strtemp:
.ascii "Temp string \n\0"


(2)汇编系统预定义的段名
.text @代码段
.data @初始化数据段
.bss @未初始化数据段
.sdata @
.sbss @
需要注意的是,源程序中.bss段应该在.text之前。
四. 定义入口点
汇编程序的缺省入口是 start标号,用户也可以在连接脚本文件中用ENTRY标志指明其它入口点。
【例4】定义入口点
.section.data
< initialized data here>
.section .bss
< uninitialized data here>
.section .text
.globl _start
_start:


五. Linux汇编程序中的宏定义
格式如下:
.macro 宏名 参数名列表 @伪指令.macro定义一个宏
宏体
.endm @.endm表示宏结束
如果宏使用参数,那么在宏体中使用该参数时添加前缀“\”。宏定义时的参数还可以使用默认值。
可以使用.exitm伪指令来退出宏。
【例5】宏定义
.macro SHIFTLEFT a, b
.if \b < 0
MOV \a, \a, ASR #-\b
.exitm
.endif
MOV \a, \a, LSL #\b
.endm

六. Linux汇编程序中的常数
(1)十进制数以非0数字开头,如:123和9876;
(2)二进制数以0b开头,其中字母也可以为大写;
(3)八进制数以0开始,如:0456,0123;
(4)十六进制数以0x开头,如:0xabcd,0X123f;
(5)字符串常量需要用引号括起来,中间也可以使用转义字符,如: “You are welcome!\n”;
(6)当前地址以“.”表示,在汇编程序中可以使用这个符号代表当前指令的地址;
(7)表达式:在汇编程序中的表达式可以使用常数或者数值, “-”表示取负数, “~”表示取补,“<>”表示不相等,其他的符号如:+、-、*、 /、%、<、<<、>、>>、|、&、^、!、==、>=、<=、&&、|| 跟C语言中的用法相似。

七. Linux下ARM汇编的常用伪操作
在前面已经提到过了一些为操作,还有下面一些为操作:
数据定义伪操作: .byte,.short,.long,.quad,.float,.string/.asciz/.ascii,重复定义伪操作.rept,赋值语句.equ/.set ;
函数的定义 ;
对齐方式伪操作 .align;
源文件结束伪操作.end;
.include伪操作;
if伪操作;
.global/ .globl 伪操作 ;
.type伪操作 ;
列表控制语句 ;
区别于gas汇编的通用伪操作,下面是ARM特有的伪操作 :.reg ,.unreq ,.code ,.thumb ,.thumb_func ,.thumb_set, .ltorg ,.pool
1. 数据定义伪操作
(1) .byte:单字节定义,如:.byte 1,2,0b01,0x34,072,'s' ;
(2) .short:定义双字节数据,如:.short 0x1234,60000 ;
(3) .long:定义4字节数据,如:.long 0x12345678,23876565
(4) .quad:定义8字节,如:.quad 0x1234567890abcd
(5) .float:定义浮点数,如:
.float 0f-314159265358979323846264338327\
95028841971.693993751E-40 @ - pi
(6) .string/.asciz/.ascii:定义多个字符串,如:
.string "abcd", "efgh", "hello!"
.asciz "qwer", "sun", "world!"
.ascii "welcome\0"
需要注意的是:.ascii伪操作定义的字符串需要自行添加结尾字符'\0'。
(7) .rept:重复定义伪操作, 格式如下:
.rept 重复次数
数据定义
.endr @结束重复定义
例如:
.rept 3
.byte 0x23
.endr
(8) .equ/.set: 赋值语句, 格式如下:
.equ(.set) 变量名,表达式
例如:
.equ abc 3 @让abc=3

2.函数的定义伪操作
(1)函数的定义,格式如下:
函数名:
函数体
返回语句
一般的,函数如果需要在其他文件中调用, 需要用到.global伪操作将函数声明为全局函数。为了不至于在其他程序在调用某个C函数时发生混乱,对寄存器的使用我们需要遵循APCS准则。函数编译器将处理为函数代码为一段.global的汇编码。
(2)函数的编写应当遵循如下规则:
 a1-a4寄存器(参数、结果或暂存寄存器,r0到r3 的同义字)以及浮点寄存器f0-f3(如果存在浮点协处理器)在函数中是不必保存的;
 如果函数返回一个不大于一个字大小的值,则在函数结束时应该把这个值送到 r0 中;
 如果函数返回一个浮点数,则在函数结束时把它放入浮点寄存器f0中;
 如果函数的过程改动了sp(堆栈指针,r13)、fp(框架指针,r11)、sl(堆栈限制,r10)、lr(连接寄存器,r14)、v1-v8(变量寄存器,r4 到 r11)和 f4-f7,那么函数结束时这些寄存器应当被恢复为包含在进入函数时它所持有的值。

3. .align .end .include .incbin伪操作
(1).align:用来指定数据的对齐方式,格式如下:
.align [absexpr1, absexpr2]
以某种对齐方式,在未使用的存储区域填充值. 第一个值表示对齐方式,4, 8,16或 32. 第二个表达式值表示填充的值。
(2).end:表明源文件的结束。
(3).include:可以将指定的文件在使用.include 的地方展开,一般是头文件,例如:
.include “myarmasm.h”
(4).incbin伪操作可以将原封不动的一个二进制文件编译到当前文件中,使用方法如下:
.incbin "file"[,skip[,count]]
skip表明是从文件开始跳过skip个字节开始读取文件,count是读取的字数.

4. .if伪操作
根据一个表达式的值来决定是否要编译下面的代码, 用.endif伪操作来表示条件判断的结束, 中间可以使用.else来决定.if的条件不满足的情况下应该编译哪一部分代码。
.if有多个变种:
.ifdef symbol @判断symbol是否定义
.ifc string1,string2 @字符串string1和string2是否相等,字符串可以用单引号括起来
.ifeq expression_r @判断expression_r的值是否为0
.ifeqs string1,string2 @判断string1和string2是否相等,字符 串必须用双引号括起来
.ifge expression_r @判断expression_r的值是否大于等于0
.ifgt absolute expression_r @判断expression_r的值是否大于0
.ifle expression_r @判断expression_r的值是否小于等于0
.iflt absolute expression_r @判断expression_r的值是否小于0
.ifnc string1,string2 @判断string1和string2是否不相等, 其用法跟.ifc恰好相反。
.ifndef symbol, .ifnotdef symbol @判断是否没有定义symbol, 跟.ifdef恰好相反
.ifne expression_r @如果expression_r的值不是0, 那么编译器将编译下面的代码
.ifnes string1,string2 @如果字符串string1和string2不相 等, 那么编译器将编译下面的代码.

5. .global .type .title .list
(1).global/ .globl :用来定义一个全局的符号,格式如下:
.global symbol 或者 .globl symbol
(2).type:用来指定一个符号的类型是函数类型或者是对象类型, 对象类型一般是数据, 格式如下:
.type 符号, 类型描述
【例6】
.globl a
.data
.align 4
.type a, @object
.size a, 4
a:
.long 10
【例7】
.section .text
.type asmfunc, @function
.globl asmfunc
asmfunc:

mov pc, lr

(3)列表控制语句:
.title:用来指定汇编列表的标题,例如:
.title “my program”
.list:用来输出列表文件.

6. ARM特有的伪操作
(1) .reg: 用来给寄存器赋予别名,格式如下:
别名 .req 寄存器名
(2) .unreq: 用来取消一个寄存器的别名,格式如下:
.unreq 寄存器别名
注意被取消的别名必须事先定义过,否则编译器就会报错,这个伪操作也可以用来取消系统预制的别名, 例如r0, 但如果没有必要的话不推荐那样做。
(3) .code伪操作用来选择ARM或者Thumb指令集,格式如下:
.code 表达式
如果表达式的值为16则表明下面的指令为Thumb指令,如果表达式的值为32则表明下面的指令为ARM指令.
(4) .thumb伪操作等同于.code 16, 表明使用Thumb指令, 类似的.arm等同于.code 32
(5) .force_thumb伪操作用来强制目标处理器选择thumb的指令集而不管处理器是否支持
(6) .thumb_func伪操作用来指明一个函数是thumb指令集的函数
(7) .thumb_set伪操作的作用类似于.set, 可以用来给一个标志起一个别名, 比.set功能增加的一点是可以把一个标志标记为thumb函数的入口, 这点功能等同于.thumb_func
(8) .ltorg用于声明一个数据缓冲池(literal pool)的开始,它可以分配很大的空间。
(9) .pool的作用等同.ltorg
(9).space {,}
分配number_of_bytes字节的数据空间,并填充其值为fill_byte,若未指定该值,缺省填充0。(与armasm中的SPACE功能相同)
(10).word {,} …
插入一个32-bit的数据队列。(与armasm中的DCD功能相同)
可以使用.word把标识符作为常量使用
例如:
Start:
valueOfStart:
.word Start
这样程序的开头Start便被存入了内存变量valueOfStart中。
(11).hword {,} …
插入一个16-bit的数据队列。(与armasm中的DCW相同)

八. GNU ARM汇编特殊字符和语法
代码行中的注释符号: ‘@’
整行注释符号: ‘#’
语句分离符号: ‘;’
直接操作数前缀: ‘#’ 或 ‘$’

第二部分 GNU的编译器和调试工具

一. 编译工具
1.编辑工具介绍
GNU 提供的编译工具包括汇编器as、C编译器gcc、C++编译器g++、连接器ld和二进制转换工具objcopy。基于ARM平台的工具分别为arm- linux-as、arm-linux-gcc、arm-linux-g++、arm-linux-ld和arm-linux- objcopy。GNU的编译器功能非常强大,共有上百个操作选项,这也是这类工具让初学者头痛的原因。不过,实际开发中只需要用到有限的几个,大部分可 以采用缺省选项。GNU工具的开发流程如下:编写C、C++语言或汇编源程序,用gcc或g++生成目标文件,编写连接脚本文件,用连接器生成最终目标文 件(elf格式),用二进制转换工具生成可下载的二进制代码。
(1)编写C、C++语言或汇编源程序
通常汇编源程序用于系统最基本的初始化,如初始化堆栈指针、设置页表、操作ARM的协处理器等。初始化完成后就可以跳转到C代码执行。需要注意的 是,GNU的汇编器遵循AT&T的汇编语法,读者可以从GNU的站点(www.gnu.org)上下载有关规范。汇编程序的缺省入口是 start标号,用户也可以在连接脚本文件中用ENTRY标志指明其它入口点(见下文关于连接脚本的说明)。

(2)用gcc或g++生成目标文件
如果应用程序包括多个文件,就需要进行分别编译,最后用连接器连接起来。如笔者的引导程序包括3个文件:init.s(汇编代码、初始化硬件)xmrecever.c(通信模块,采用Xmode协议)和flash.c(Flash擦写模块)。
分别用如下命令生成目标文件: arm-linux-gcc-c-O2-oinit.oinit.s arm-linux-gcc-c-O2-oxmrecever.oxmrecever.c arm-linux-gcc-c-O2-oflash.oflash.c 其中-c命令表示只生成目标代码,不进行连接;-o命令指明目标文件的名称;-O2表示采用二级优化,采用优化后可使生成的代码更短,运行速度更快。如果项目包含很多文件,则需要编写makefile文件。关于makefile的内容,请感兴趣的读者参考相关资料。
(3)编写连接脚本文件
gcc 等编译器内置有缺省的连接脚本。如果采用缺省脚本,则生成的目标代码需要操作系统才能加载运行。为了能在嵌入式系统上直接运行,需要编写自己的连接脚本文 件。编写连接脚本,首先要对目标文件的格式有一定了解。GNU编译器生成的目标文件缺省为elf格式。elf文件由若干段(section)组成,如不特 殊指明,由C源程序生成的目标代码中包含如下段:.text(正文段)包含程序的指令代码;.data(数据段)包含固定的数据,如常量、字符 串;.bss(未初始化数据段)包含未初始化的变量、数组等。C++源程序生成的目标代码中还包括.fini(析构函数代码)和. init(构造函数代码)等。连接器的任务就是将多个目标文件的.text、.data和.bss等段连接在一起,而连接脚本文件是告诉连接器从什么地址 开始放置这些段。例如连接文件link.lds为:
ENTRY(begin)
SECTION
{
.=0x30000000;
.text:{*(.text)}
.data:{*(.data)}
.bss:{*(.bss)}
}
其中,ENTRY(begin)指明程序的入口点为begin标号;.=0x00300000指明目标代码的起始地址为0x30000000,这一段地址为 MX1的片内RAM;.text:{*(.text)}表示从0x30000000开始放置所有目标文件的代码段,随后的.data:{* (.data)}表示数据段从代码段的末尾开始,再后是.bss段。
(4)用连接器生成最终目标文件
有了连接脚本文件,如下命令可生成最终的目标文件:
arm-linux-ld –no stadlib –o bootstrap.elf -Tlink.lds init.o xmrecever.o flash.o
其中,ostadlib表示不连接系统的运行库,而是直接从begin入口;-o指明目标文件的名称;-T指明采用的连接脚本文件(也可以使用-Ttext address,address表示执行区地址);最后是需要连接的目标文件列表。
(5)生成二进制代码
连接生成的elf文件还不能直接下载执行,通过objcopy工具可生成最终的二进制文件:
arm-linux-objcopy –O binary bootstrap.elf bootstrap.bin
其中-O binary指定生成为二进制格式文件。Objcopy还可以生成S格式的文件,只需将参数换成-O srec。还可以使用-S选项,移除所有的符号信息及重定位信息。如果想将生成的目标代码反汇编,还可以用objdump工具:
arm-linux-objdump -D bootstrap.elf
至此,所生成的目标文件就可以直接写入Flash中运行了。

2.Makefile实例
example: head.s main.c
arm-linux-gcc -c -o head.o head.s
arm-linux-gcc -c -o main.o main.c
arm-linux-ld -Tlink.lds head.o ain.o -o example.elf
arm-linux-objcopy -O binary -S example_tmp.o example
arm-linux-objdump -D -b binary -m arm example >ttt.s

二. 调试工具
Linux 下的GNU调试工具主要是gdb、gdbserver和kgdb。其中gdb和gdbserver可完成对目标板上Linux下应用程序的远程调试。 gdbserver是一个很小的应用程序,运行于目标板上,可监控被调试进程的运行,并通过串口与上位机上的gdb通信。开发者可以通过上位机的gdb输 入命令,控制目标板上进程的运行,查看内存和寄存器的内容。gdb5.1.1以后的版本加入了对ARM处理器的支持,在初始化时加入- target==arm参数可直接生成基于ARM平台的gdbserver。gdb工具可以从ftp: //ftp.gnu.org/pub/gnu/gdb/上下载。
对于Linux内核的调试,可以采用kgdb工具,同样需要通过串口与上位机上的gdb通信,对目标板的Linux内核进行调试。可以从http://oss.sgi.com/projects/kgdb/上了解具体的使用方法。


参考资料:
1. Richard Blum,Professional Assembly Language
2. GNU ARM 汇编快速入门,http://blog.chinaunix.net/u/31996/showart.php?id=326146
3. ARM GNU 汇编伪指令简介,http://www.cppblog.com/jb8164/archive/2008/01/22/41661.aspx
4. GNU汇编使用经验,http://blog.chinaunix.net/u1/37614/showart_390095.html
5. GNU的编译器和开发工具,http://blog.ccidnet.com/blog-htm-do-showone-uid-34335-itemid-81387-type-blog.html
6. 用GNU工具开发基于ARM的嵌入式系统,http://blog.163.com/liren0@126/blog/static/32897598200821211144696/
7. objcopy命令介绍,http://blog.csdn.net/junhua198310/archive/2007/06/27/1669545.aspx

 

GNUARM汇编快速入门
前言:

以前用ARM的IDE工具,使用的是ARM标准的汇编语言。现在要使用GNU的工具,当然要了解一点GNUARM汇编的不同之处。其实非常的简单,浏览一下文档然后再看看程序就完全可以搞定了,或者你硬着头皮看GNUARM的汇编程序,用不了多少时间你就就可以无师自通了。个人比较健忘,还是把文档翻译了一下,算是给自己一个避免遗忘的理由吧。

ARM汇编语言源程序语句,一般由指令,伪操作,宏指令和伪指令作成.ARM汇编语言的设计基础是汇编伪指令,汇编伪操作和宏指令.

目前常用的ARM编译环境有2种:
ARMASM:ARM公司的IDE中使用了CodeWarrior的编译器,绝大多数windows下的开发者都在使用这一环境,完全按照ARM的规定;
GNUARMASM:GNU工具的ARM版本,与ARMASM略有不同;

关于CodeWarrirorARM汇编的书和文章很多,本文假定你已经完全了解ARMASM,这里只说明GNUARM汇编,并针对ARMASM给出说明。本文翻译自:GNUARMAssemblerQuickReference,本人水平有限,错误难免,转载随意,请注明出处。英文原文地址不详。


GNUARM汇编快速入门

任何汇编行都是如下结构:
[:][}@comment
[<标签>:][<指令>}@注释
GNUARM汇编中,任何以冒号结尾的都被认为是一个标签,而不一定非要在一行的开始。下面是一个简单的例子,这段汇编程序定义了一个"add"的函数,该函数返回两个参数的和:
.section.text,“x”

add:
ADDr0,r0,r1@addinputarguments
MOVpc,lr@returnfromsubroutine
@endofprogram
GNUARM汇编伪指令

下面列出了一些GNUARM汇编伪指令,并给出了相应说明。

.ascii“”在汇编中定义字符串并为之分配存储空间(与armasm中的DCB功能类似)。
.asciz“”和.ascii类似,但不分配存储空间。
.balign{,{,}}
以某种排列方式在内存中填充数值。(该指令与armasm中的ALIGN类似)。
power_of_2表示排列方式,其值可为4,8,16或32,单位是byte;
fill_value是要填充的值;
max_padding最大的填充界限,请求填充的bytes数超过该值,将被忽略。
.byte{,}…定义一个或多个Byte,并为之分配空间(与armasm的DCB类似)。
.code设定指令宽度,16表示Thumb,32表示ARMassembly
(和armasm中的CODE16,CODE32相同)。
.if
.else
.endif
预编译宏(与armasm中的IFELSEENDIF相同)。
.end汇编文件结束标志,常常省略不用。

.endm宏结束标志。
.exitm宏跳出。
.macro{}…{,}
定义一段名为name的宏,arg_xxx为参数。
必须有对应的.endm结尾。
可以使用.exitm从中间跳出宏。(与armasm中的MACRO,MEND,MEXIT相同)。
在使用宏参数时必须这样使用:“\”。
例如:
[CODE].macroSHIFTLEFTa,b
.if\b<0
MOV\a,\a,ASR#-\b
.exitm
.endif
MOV\a,\a,LSL#\b
.endm

.rept循环执行.endr前的代码段number_of_times次。
(与armasm中的WEN相似)

.irp{,}{,}…
循环执行.endr前的代码段,param依次取后面给出的值。
在循环执行的代码段中必须以“\”表示参数。

.endr结束循环(与armasm中的WEND相似).
.equ,为一个标号赋值,类似C中的#define。(与armasm中的EQU相同)
.err编译错误报告,将引起编译的终止。
.global全局声明标志,这样声明的标号将可以被外部使用。(与armasm中的EXPORT相同)。
.hword{,}…
插入一个16-bit的数据队列。(与armasm中的DCW相同)
.ifdef如果被定义,该快代码将被编译。以.endif结束。
.ifndef如果未被定义,该快代码将被编译。以.endif结束。
.include“”包含文件。(与armasm中的INCLUDE或者C中的#include一样)
.req
定义一个寄存器,.req的左边是定义的寄存器名,右边是使用的真正使用的寄存器。
(与armasm中的RN类似)
例如:acc.reqr0
[CODE].section{,””}
开始一个新的代码或数据段。.text,代码段;.data,初始化数据段;.bss,未初始化数据段。
这些段都有缺省的标志(flags),联接器可以识别这些标志。(与armasm中的AREA相同)。
下面是ELF格式允许的段标志
<标志>含义
a允许段
w可写段
x执行段
.set,变量赋值。(与armasm中的SETA相同)
.space{,}
分配number_of_bytes字节的数据空间,并填充其值为fill_byte,若未指定该值,缺省填充0。
(与armasm中的SPACE功能相同)
.word{,}…
插入一个32-bit的数据队列。(与armasm中的DCD功能相同)

GNUARM汇编特殊字符和语法

代码行中的注释符号:‘@’
整行注释符号:‘#’
语句分离符号:‘;’
直接操作数前缀:‘#’或‘$’
.arm以arm格式编译,同code32
.thumb以thumb格式编译,同code16
.code16以thumb格式编译
.code32以arm格式编译
篇后语:

更详细的使用说明请参照:ARMArchitectureReferenceManual,Addison-WesleyISBN0-201-73719-1
补充:

4ARMGNU常用汇编语言介绍
4.1ARMGNU常用汇编伪指令介绍
1.abort
.abort:停止汇编
.alignabs-expr1,abs-expr2:以某种对齐方式,在未使用的存储区域填充值.第一个值表示对齐方式,4,8,16或32.第
二个表达式值表示填充的值.
2.if...else...endif
.if
.else
.endif:支持条件预编译
3.include
.include"file":包含指定的头文件,可以把一个汇编常量定义放在头文件中.
4.comm
.commsymbol,length:在bss段申请一段命名空间,该段空间的名称叫symbol,长度为length.Ld连接器在连接会
为它留出空间.
5.data
.datasubsection:说明接下来的定义归属于subsection数据段.
6.equ
.equsymbol,expression:把某一个符号(symbol)定义成某一个值(expression).该指令并不分配空间.
7.global
.globalsymbol:定义一个全局符号,通常是为ld使用.
8.ascii
.ascii"string":定义一个字符串并为之分配空间.
9.byte
.byteexpressions:定义一个字节,并为之分配空间.
10.short
.shortexpressions:定义一个短整型,并为之分配空间.
11.int
.intexpressions:定义一个整型,并为之分配空间.
12long
.longexpressions:定义一个长整型,并为之分配空间.
13word
.wordexpressions:定义一个字,并为之分配空间,4bytes.
14.macro/endm
.macro:定义一段宏代码,.macro表示代码的开始,.endm表示代码的结束.
15.req
name.reqregistername:为寄存器定义一个别名.
16.code
.code[16|32]:指定指令代码产生的长度,16表示Thumb指令,32表示ARM指令.
17.ltorg
.ltorg:表示当前往下的定义在归于当前段,并为之分配空间.
4.2ARMGNU专有符号
1.@
表示注释从当前位置到行尾的字符.
2.#
注释掉一整行.
3.;
新行分隔符.
4.3操作码
1.NOP
nop
空操作,相当于MOVr0,r0
2.LDR
ldr,=
相当于PC寄存器或其它寄存器的长转移.
3.ADR
adr
相于PC寄存器或其它寄存器的小范围转移.
ADRL
adrl
相于PC寄存器或其寄存器的中范围转移.

---------------------------------------------------------------------------------------

ARM指令
第一部分Linux下ARM汇编语法尽管在Linux下使用C或C++编写程序很方便,但汇编源程序用于系统最基本的初始化,如初始化堆栈指针、设置页表、操作ARM的协处理器等。初始化完成后就可以跳转到C代码执行。需要注意的是,GNU的汇编器遵循AT&T的汇编语法,可以从GNU的站点(www.gnu.org)上下载有关规范。

一.Linux汇编行结构
任何汇编行都是如下结构:
[:][}@comment
[:][}@注释
LinuxARM汇编中,任何以冒号结尾的标识符都被认为是一个标号,而不一定非要在一行的开始。
【例1】定义一个"add"的函数,返回两个参数的和。
.section.text,“x”

add:
ADDr0,r0,r1@addinputarguments
MOVpc,lr@returnfromsubroutine
@endofprogram

二.Linux汇编程序中的标号
标号只能由a~z,A~Z,0~9,“.”,_等字符组成。当标号为0~9的数字时为局部标号,局部标号可以重复出现,使用方法如下:
标号f:在引用的地方向前的标号
标号b:在引用的地方向后的标号
【例2】使用局部符号的例子,一段循环程序
1:
subsr0,r0,#1@每次循环使r0=r0-1
bne1f@跳转到1标号去执行
局部标号代表它所在的地址,因此也可以当作变量或者函数来使用。

三.Linux汇编程序中的分段
(1).section伪操作
用户可以通过.section伪操作来自定义一个段,格式如下:
.sectionsection_name[,"flags"[,%type[,flag_specific_arguments]]]
每一个段以段名为开始,以下一个段名或者文件结尾为结束。这些段都有缺省的标志(flags),连接器可以识别这些标志。(与armasm中的AREA相同)。

下面是ELF格式允许的段标志
<标志>含义
a允许段
w可写段
x执行段

【例3】定义段
.section.mysection@自定义数据段,段名为“.mysection”
.align2
strtemp:
.ascii"Tempstring\n\0"


(2)汇编系统预定义的段名
.text@代码段
.data@初始化数据段
.bss@未初始化数据段
.sdata@
.sbss@
需要注意的是,源程序中.bss段应该在.text之前。
四.定义入口点
汇编程序的缺省入口是start标号,用户也可以在连接脚本文件中用ENTRY标志指明其它入口点。
【例4】定义入口点
.section.data

.section.bss

.section.text
.globl_start
_start:

五.Linux汇编程序中的宏定义
格式如下:
.macro宏名参数名列表@伪指令.macro定义一个宏
宏体
表示宏结束
如果宏使用参数,那么在宏体中使用该参数时添加前缀“\”。宏定义时的参数还可以使用默认值。
可以使用.exitm伪指令来退出宏。
【例5】宏定义
.macroSHIFTLEFTa,b
.if\b<0
MOV\a,\a,ASR#-\b
.exitm
.endif
MOV\a,\a,LSL#\b
.endm

六.Linux汇编程序中的常数
(1)十进制数以非0数字开头,如:123和9876;
(2)二进制数以0b开头,其中字母也可以为大写;
(3)八进制数以0开始,如:0456,0123;
(4)十六进制数以0x开头,如:0xabcd,0X123f;
(5)字符串常量需要用引号括起来,中间也可以使用转义字符,如:“Youarewelcome!\n”;
(6)当前地址以“.”表示,在汇编程序中可以使用这个符号代表当前指令的地址;
(7)表达式:在汇编程序中的表达式可以使用常数或者数值,“-”表示取负数,“~”表示取补,“<>”表示不相等,其他的符号如:+、-、*、/、%、<、<<、>、>>、|、&、^、!、==、>=、<=、&&、||跟C语言中的用法相似。

七.Linux下ARM汇编的常用伪操作
在前面已经提到过了一些为操作,还有下面一些为操作:
数据定义伪操作:.byte,.short,.long,.quad,.float,.string/.asciz/.ascii,重复定义伪操作.rept,赋值语句.equ/.set;
函数的定义;
对齐方式伪操作.align;
源文件结束伪操作.end;
.include伪操作;
if伪操作;
.global/.globl伪操作;
.type伪操作;
列表控制语句;
区别于gas汇编的通用伪操作,下面是ARM特有的伪操作:.reg,.unreq,.code,.thumb,.thumb_func,.thumb_set,.ltorg,.pool
1.数据定义伪操作
(1).byte:单字节定义,如:.byte1,2,0b01,0x34,072,'s';
(2).short:定义双字节数据,如:.short0x1234,60000;
(3).long:定义4字节数据,如:.long0x12345678,23876565
(4).quad:定义8字节,如:.quad0x1234567890abcd
(5).float:定义浮点数,如:
.float0f-314159265358979323846264338327\

(6).string/.asciz/.ascii:定义多个字符串,如:
.string"abcd","efgh","hello!"
.asciz"qwer","sun","world!"
.ascii"welcome\0"
需要注意的是:.ascii伪操作定义的字符串需要自行添加结尾字符'\0'。
(7).rept:重复定义伪操作,格式如下:
.rept重复次数
数据定义
.endr@结束重复定义
例如:
.rept3
.byte0x23
.endr
(8).equ/.set:赋值语句,格式如下:
.equ(.set)变量名,表达式
例如:
.equabc3@让abc=3

2.函数的定义伪操作
(1)函数的定义,格式如下:
函数名:
函数体
返回语句
一般的,函数如果需要在其他文件中调用,需要用到.global伪操作将函数声明为全局函数。为了不至于在其他程序在调用某个C函数时发生混乱,对寄存器的使用我们需要遵循APCS准则。函数编译器将处理为函数代码为一段.global的汇编码。
(2)函数的编写应当遵循如下规则:
la1-a4寄存器(参数、结果或暂存寄存器,r0到r3的同义字)以及浮点寄存器f0-f3(如果存在浮点协处理器)在函数中是不必保存的;
如果函数返回一个不大于一个字大小的值,则在函数结束时应该把这个值送到r0中;l
如果函数返回一个浮点数,则在函数结束时把它放入浮点寄存器f0中;l
如果函数的过程改动了sp(堆栈指针,r13)、fp(框架指针,r11)、sl(堆栈限制,r10)、lr(连接寄存器,r14)、v1-v8(变量寄存器,r4l到r11)和f4-f7,那么函数结束时这些寄存器应当被恢复为包含在进入函数时它所持有的值。

3..align.end.include.incbin伪操作
(1).align:用来指定数据的对齐方式,格式如下:
.align[absexpr1,absexpr2]
以某种对齐方式,在未使用的存储区域填充值.第一个值表示对齐方式,4,8,16或32.第二个表达式值表示填充的值。
(2).end:表明源文件的结束。
(3).include:可以将指定的文件在使用.include的地方展开,一般是头文件,例如:
.include“myarmasm.h”
(4).incbin伪操作可以将原封不动的一个二进制文件编译到当前文件中,使用方法如下:
.incbin"file"[,skip[,count]]
skip表明是从文件开始跳过skip个字节开始读取文件,count是读取的字数.

4..if伪操作
根据一个表达式的值来决定是否要编译下面的代码,用.endif伪操作来表示条件判断的结束,中间可以使用.else来决定.if的条件不满足的情况下应该编译哪一部分代码。
.if有多个变种:
.ifdefsymbol@判断symbol是否定义
.ifcstring1,string2@字符串string1和string2是否相等,字符串可以用单引号括起来
.ifeqexpression@判断expression的值是否为0
.ifeqsstring1,string2@判断string1和string2是否相等,字符串必须用双引号括起来
.ifgeexpression@判断expression的值是否大于等于0
.ifgtabsoluteexpression@判断expression的值是否大于0
.ifleexpression@判断expression的值是否小于等于0
.ifltabsoluteexpression@判断expression的值是否小于0
.ifncstring1,string2@判断string1和string2是否不相等,其用法跟.ifc恰好相反。
.ifndefsymbol,.ifnotdefsymbol@判断是否没有定义symbol,跟.ifdef恰好相反
.ifneexpression@如果expression的值不是0,那么编译器将编译下面的代码
.ifnesstring1,string2@如果字符串string1和string2不相等,那么编译器将编译下面的代码.

5..global.type.title.list
(1).global/.globl:用来定义一个全局的符号,格式如下:
.globalsymbol或者.globlsymbol
(2).type:用来指定一个符号的类型是函数类型或者是对象类型,对象类型一般是数据,格式如下:
.type符号,类型描述
【例6】
.globla
.data
.align4
.typea,@object
.sizea,4
a:
.long10
【例7】
.section.text
.typeasmfunc,@function
.globlasmfunc
asmfunc:

movpc,lr

(3)列表控制语句:
.title:用来指定汇编列表的标题,例如:
.title“myprogram”
.list:用来输出列表文件.

6.ARM特有的伪操作
(1).reg:用来给寄存器赋予别名,格式如下:
别名.req寄存器名
(2).unreq:用来取消一个寄存器的别名,格式如下:
       .unreq寄存器别名
  注意被取消的别名必须事先定义过,否则编译器就会报错,这个伪操作也可以用来取消系统预制的别名,例如r0,但如果没有必要的话不推荐那样做。
(3).code伪操作用来选择ARM或者Thumb指令集,格式如下:
           .code表达式
  如果表达式的值为16则表明下面的指令为Thumb指令,如果表达式的值为32则表明下面的指令为ARM指令.
(4).thumb伪操作等同于.code16,表明使用Thumb指令,类似的.arm等同于.code32
(5).force_thumb伪操作用来强制目标处理器选择thumb的指令集而不管处理器是否支持
(6).thumb_func伪操作用来指明一个函数是thumb指令集的函数
(7).thumb_set伪操作的作用类似于.set,可以用来给一个标志起一个别名,比.set功能增加的一点是可以把一个标志标记为thumb函数的入口,这点功能等同于.thumb_func
(8).ltorg用于声明一个数据缓冲池(literalpool)的开始,它可以分配很大的空间。
(9).pool的作用等同.ltorg
(9).space{,}
分配number_of_bytes字节的数据空间,并填充其值为fill_byte,若未指定该值,缺省填充0。(与armasm中的SPACE功能相同)
(10).word{,}…
插入一个32-bit的数据队列。(与armasm中的DCD功能相同)
可以使用.word把标识符作为常量使用
例如:
Start:
valueOfStart:
.wordStart
这样程序的开头Start便被存入了内存变量valueOfStart中。
(11).hword{,}…
插入一个16-bit的数据队列。(与armasm中的DCW相同)

八.GNUARM汇编特殊字符和语法
代码行中的注释符号:‘@’
整行注释符号:‘#’
语句分离符号:‘;’
直接操作数前缀:‘#’或‘$’

第二部分GNU的编译器和调试工具

一.编译工具
1.编辑工具介绍
GNU提供的编译工具包括汇编器as、C编译器gcc、C++编译器g++、连接器ld和二进制转换工具objcopy。基于ARM平台的工具分别为arm-linux-as、arm-linux-gcc、arm-linux-g++、arm-linux-ld和arm-linux-objcopy。GNU的编译器功能非常强大,共有上百个操作选项,这也是这类工具让初学者头痛的原因。不过,实际开发中只需要用到有限的几个,大部分可以采用缺省选项。GNU工具的开发流程如下:编写C、C++语言或汇编源程序,用gcc或g++生成目标文件,编写连接脚本文件,用连接器生成最终目标文件(elf格式),用二进制转换工具生成可下载的二进制代码。
(1)编写C、C++语言或汇编源程序
通常汇编源程序用于系统最基本的初始化,如初始化堆栈指针、设置页表、操作ARM的协处理器等。初始化完成后就可以跳转到C代码执行。需要注意的是,GNU的汇编器遵循AT&T的汇编语法,读者可以从GNU的站点(www.gnu.org)上下载有关规范。汇编程序的缺省入口是start标号,用户也可以在连接脚本文件中用ENTRY标志指明其它入口点(见下文关于连接脚本的说明)。

(2)用gcc或g++生成目标文件
如果应用程序包括多个文件,就需要进行分别编译,最后用连接器连接起来。如笔者的引导程序包括3个文件:init.s(汇编代码、初始化硬件)xmrecever.c(通信模块,采用Xmode协议)和flash.c(Flash擦写模块)。
分别用如下命令生成目标文件:arm-linux-gcc-c-O2-oinit.oinit.sarm-linux-gcc-c-O2-oxmrecever.oxmrecever.carm-linux-gcc-c-O2-oflash.oflash.c其中-c命令表示只生成目标代码,不进行连接;-o命令指明目标文件的名称;-O2表示采用二级优化,采用优化后可使生成的代码更短,运行速度更快。如果项目包含很多文件,则需要编写makefile文件。关于makefile的内容,请感兴趣的读者参考相关资料。
(3)编写连接脚本文件
gcc等编译器内置有缺省的连接脚本。如果采用缺省脚本,则生成的目标代码需要操作系统才能加载运行。为了能在嵌入式系统上直接运行,需要编写自己的连接脚本文件。编写连接脚本,首先要对目标文件的格式有一定了解。GNU编译器生成的目标文件缺省为elf格式。elf文件由若干段(section)组成,如不特殊指明,由C源程序生成的目标代码中包含如下段:.text(正文段)包含程序的指令代码;.data(数据段)包含固定的数据,如常量、字符串;.bss(未初始化数据段)包含未初始化的变量、数组等。C++源程序生成的目标代码中还包括.fini(析构函数代码)和.init(构造函数代码)等。连接器的任务就是将多个目标文件的.text、.data和.bss等段连接在一起,而连接脚本文件是告诉连接器从什么地址开始放置这些段。例如连接文件link.lds为:
ENTRY(begin)
SECTION
{
.=0x30000000;
.text:{*(.text)}
.data:{*(.data)}
.bss:{*(.bss)}
}
其中,ENTRY(begin)指明程序的入口点为begin标号;.=0x00300000指明目标代码的起始地址为0x30000000,这一段地址为MX1的片内RAM;.text:{*(.text)}表示从0x30000000开始放置所有目标文件的代码段,随后的.data:{*(.data)}表示数据段从代码段的末尾开始,再后是.bss段。
(4)用连接器生成最终目标文件
有了连接脚本文件,如下命令可生成最终的目标文件:
arm-linux-ld–nostadlib–obootstrap.elf-Tlink.ldsinit.oxmrecever.oflash.o
其中,ostadlib表示不连接系统的运行库,而是直接从begin入口;-o指明目标文件的名称;-T指明采用的连接脚本文件(也可以使用-Ttextaddress,address表示执行区地址);最后是需要连接的目标文件列表。
(5)生成二进制代码
连接生成的elf文件还不能直接下载执行,通过objcopy工具可生成最终的二进制文件:
arm-linux-objcopy–Obinarybootstrap.elfbootstrap.bin
其中-Obinary指定生成为二进制格式文件。Objcopy还可以生成S格式的文件,只需将参数换成-Osrec。还可以使用-S选项,移除所有的符号信息及重定位信息。如果想将生成的目标代码反汇编,还可以用objdump工具:
arm-linux-objdump-Dbootstrap.elf
至此,所生成的目标文件就可以直接写入Flash中运行了。

2.Makefile实例
example:head.smain.c
arm-linux-gcc-c-ohead.ohead.s
arm-linux-gcc-c-omain.omain.c
arm-linux-ld-Tlink.ldshead.oain.o-oexample.elf
arm-linux-objcopy-Obinary-Sexample_tmp.oexample
arm-linux-objdump-D-bbinary-marmexample>ttt.s

二.调试工具
Linux下的GNU调试工具主要是gdb、gdbserver和kgdb。其中gdb和gdbserver可完成对目标板上Linux下应用程序的远程调试。gdbserver是一个很小的应用程序,运行于目标板上,可监控被调试进程的运行,并通过串口与上位机上的gdb通信。开发者可以通过上位机的gdb输入命令,控制目标板上进程的运行,查看内存和寄存器的内容。gdb5.1.1以后的版本加入了对ARM处理器的支持,在初始化时加入-target==arm参数可直接生成基于ARM平台的gdbserver。gdb工具可以从上下载。
对于Linux内核的调试,可以采用kgdb工具,同样需要通过串口与上位机上的gdb通信,对目标板的Linux内核进行调试。可以从http://oss.sgi.com/projects/kgdb/上了解具体的使用方法。


参考资料:
1.RichardBlum,ProfessionalAssemblyLanguage
2.GNUARM汇编快速入门,http://blog.chinaunix.net/u/31996/showart.php?id=326146
3.ARMGNU汇编伪指令简介,http://www.cppblog.com/jb8164/archive/2008/01/22/41661.aspx
4.GNU汇编使用经验,http://blog.chinaunix.net/u1/37614/showart_390095.html
5.GNU的编译器和开发工具,http://blog.ccidnet.com/blog-htm-do-showone-uid-34335-itemid-81387-type-blog.html
6.用GNU工具开发基于ARM的嵌入式系统,http://blog.163.com/liren0@126/blog/static/32897598200821211144696/
7.objcopy命令介绍,http://blog.csdn.net/junhua198310/archive/2007/06/27/1669545.aspx

 

 

 

 

--------------------------------------------------------------------------------

ARM汇编伪指令介绍.
在ARM汇编语言程序里,有一些特殊指令助记符,这些助记符与指令系统的助记符不同,没有相对应的操作码,通常称这些特殊指令助记符为伪指令,他们所完成的操作称为伪操作。伪指令在源程序中的作用是为完成汇编程序作各种准备工作的,这些伪指令仅在汇编过程中起作用,一旦汇编结束,伪指令的使命就完成。

在ARM的汇编程序中,有如下几种伪指令:符号定义伪指令、数据定义伪指令、汇编控制伪指令、宏指令以及其他伪指令。
符号定义(SymbolDefinition)伪指令
符号定义伪指令用于定义ARM汇编程序中的变量、对变量赋值以及定义寄存器的别名等操作。
常见的符号定义伪指令有如下几种:
—用于定义全局变量的GBLA、GBLL和GBLS。
—用于定义局部变量的LCLA、LCLL和LCLS。
—用于对变量赋值的SETA、SETL、SETS。
—为通用寄存器列表定义名称的RLIST。
1、GBLA、GBLL和GBLS
语法格式:
GBLA(GBLL或GBLS)全局变量名
GBLA、GBLL和GBLS伪指令用于定义一个ARM程序中的全局变量,并将其初始化。其中:
GBLA伪指令用于定义一个全局的数字变量,并初始化为0;
GBLL伪指令用于定义一个全局的逻辑变量,并初始化为F(假);
GBLS伪指令用于定义一个全局的字符串变量,并初始化为空;
由于以上三条伪指令用于定义全局变量,因此在整个程序范围内变量名必须唯一。
使用示例:
GBLATest1;定义一个全局的数字变量,变量名为Test1
Test1SETA0xaa;将该变量赋值为0xaa
GBLLTest2;定义一个全局的逻辑变量,变量名为Test2
Test2SETL{TRUE};将该变量赋值为真
GBLSTest3;定义一个全局的字符串变量,变量名为Test3
Test3SETS“Testing”;将该变量赋值为“Testing”

2、LCLA、LCLL和LCLS
语法格式:
LCLA(LCLL或LCLS)局部变量名
LCLA、LCLL和LCLS伪指令用于定义一个ARM程序中的局部变量,并将其初始化。其中:
LCLA伪指令用于定义一个局部的数字变量,并初始化为0;
LCLL伪指令用于定义一个局部的逻辑变量,并初始化为F(假);
LCLS伪指令用于定义一个局部的字符串变量,并初始化为空;
以上三条伪指令用于声明局部变量,在其作用范围内变量名必须唯一。
使用示例:
LCLATest4;声明一个局部的数字变量,变量名为Test4
Test3SETA0xaa;将该变量赋值为0xaa
LCLLTest5;声明一个局部的逻辑变量,变量名为Test5
Test4SETL{TRUE};将该变量赋值为真
LCLSTest6;定义一个局部的字符串变量,变量名为Test6
Test6SETS“Testing”;将该变量赋值为“Testing”

3、SETA、SETL和SETS
语法格式:
变量名SETA(SETL或SETS)表达式
伪指令SETA、SETL、SETS用于给一个已经定义的全局变量或局部变量赋值。
SETA伪指令用于给一个数学变量赋值;
SETL伪指令用于给一个逻辑变量赋值;
SETS伪指令用于给一个字符串变量赋值;
其中,变量名为已经定义过的全局变量或局部变量,表达式为将要赋给变量的值。
使用示例:
LCLATest3;声明一个局部的数字变量,变量名为Test3
Test3SETA0xaa;将该变量赋值为0xaa
LCLLTest4;声明一个局部的逻辑变量,变量名为Test4
Test4SETL{TRUE};将该变量赋值为真

4、RLIST
语法格式:
名称RLIST{寄存器列表}
RLIST伪指令可用于对一个通用寄存器列表定义名称,使用该伪指令定义的名称可在ARM指令LDM/STM中使用。在LDM/STM指令中,列表中的寄存器访问次序为根据寄存器的编号由低到高,而与列表中的寄存器排列次序无关。
使用示例:
RegListRLIST{R0-R5,R8,R10};将寄存器列表名称定义为RegList,可在ARM指令LDM/STM中通过该名称访问寄存器列表。

数据定义(DataDefinition)伪指令
数据定义伪指令一般用于为特定的数据分配存储单元,同时可完成已分配存储单元的初始化。
常见的数据定义伪指令有如下几种:
—DCB用于分配一片连续的字节存储单元并用指定的数据初始化。
—DCW(DCWU)用于分配一片连续的半字存储单元并用指定的数据初始化。
—DCD(DCDU)用于分配一片连续的字存储单元并用指定的数据初始化。
—DCFD(DCFDU)用于为双精度的浮点数分配一片连续的字存储单元并用指定的数据初始
化。
—DCFS(DCFSU)用于为单精度的浮点数分配一片连续的字存储单元并用指定的数据初
始化。
—DCQ(DCQU)用于分配一片以8字节为单位的连续的存储单元并用指定的数据初始
化。
—SPACE用于分配一片连续的存储单元
—MAP用于定义一个结构化的内存表首地址
—FIELD用于定义一个结构化的内存表的数据域
1、DCB
语法格式:
标号DCB表达式
DCB伪指令用于分配一片连续的字节存储单元并用伪指令中指定的表达式初始化。其中,表达式可以为0~255的数字或字符串。DCB也可用“=”代替。
使用示例:
StrDCB“Thisisatest!”;分配一片连续的字节存储单元并初始化。

2、DCW(或DCWU)
语法格式:
标号DCW(或DCWU)表达式
DCW(或DCWU)伪指令用于分配一片连续的半字存储单元并用伪指令中指定的表达式初始化。
其中,表达式可以为程序标号或数字表达式。。
用DCW分配的字存储单元是半字对齐的,而用DCWU分配的字存储单元并不严格半字对齐。
使用示例:
DataTestDCW1,2,3;分配一片连续的半字存储单元并初始化。

3、DCD(或DCDU)
语法格式:
标号DCD(或DCDU)表达式
DCD(或DCDU)伪指令用于分配一片连续的字存储单元并用伪指令中指定的表达式初始化。其中,表达式可以为程序标号或数字表达式。DCD也可用“&”代替。
用DCD分配的字存储单元是字对齐的,而用DCDU分配的字存储单元并不严格字对齐。
使用示例:
DataTestDCD4,5,6;分配一片连续的字存储单元并初始化。

4、DCFD(或DCFDU)
语法格式:
标号DCFD(或DCFDU)表达式
DCFD(或DCFDU)伪指令用于为双精度的浮点数分配一片连续的字存储单元并用伪指令中指定的表达式初始化。每个双精度的浮点数占据两个字单元。用DCFD分配的字存储单元是字对齐的,而用DCFDU分配的字存储单元并不严格字对齐。
使用示例:
FDataTestDCFD2E115,-5E7;分配一片连续的字存储单元并初始化为指定的双精度数。

5、DCFS(或DCFSU)
语法格式:
标号DCFS(或DCFSU)表达式
DCFS(或DCFSU)伪指令用于为单精度的浮点数分配一片连续的字存储单元并用伪指令中指定的表达式初始化。每个单精度的浮点数占据一个字单元。用DCFS分配的字存储单元是字对齐的,而用DCFSU分配的字存储单元并不严格字对齐。
使用示例:
FDataTestDCFS2E5,-5E-7;分配一片连续的字存储单元并初始化为指定的单精度数。

6、DCQ(或DCQU)
语法格式:
标号DCQ(或DCQU)表达式
DCQ(或DCQU)伪指令用于分配一片以8个字节为单位的连续存储区域并用伪指令中指定的表达式初始化。
用DCQ分配的存储单元是字对齐的,而用DCQU分配的存储单元并不严格字对齐。
使用示例:
DataTestDCQ100;分配一片连续的存储单元并初始化为指定的值。

7、SPACE
语法格式:
标号SPACE表达式
SPACE伪指令用于分配一片连续的存储区域并初始化为0。其中,表达式为要分配的字节数。
SPACE也可用“%”代替。
使用示例:
DataSpaceSPACE100;分配连续100字节的存储单元并初始化为0。

8、MAP
语法格式:
MAP表达式{,基址寄存器}
MAP伪指令用于定义一个结构化的内存表的首地址。MAP也可用“^”代替。
表达式可以为程序中的标号或数学表达式,基址寄存器为可选项,当基址寄存器选项不存在时,表达式的值即为内存表的首地址,当该选项存在时,内存表的首地址为表达式的值与基址寄存器的和。
MAP伪指令通常与FIELD伪指令配合使用来定义结构化的内存表。
使用示例:
MAP0x100,R0;定义结构化内存表首地址的值为0x100+R0。

9、FILED
语法格式:
标号FIELD表达式
FIELD伪指令用于定义一个结构化内存表中的数据域。FILED也可用“#”代替。
表达式的值为当前数据域在内存表中所占的字节数。
FIELD伪指令常与MAP伪指令配合使用来定义结构化的内存表。MAP伪指令定义内存表的首地址,FIELD伪指令定义内存表中的各个数据域,并可以为每个数据域指定一个标号供其他的指令引用。
注意MAP和FIELD伪指令仅用于定义数据结构,并不实际分配存储单元。
使用示例:
MAP0x100;定义结构化内存表首地址的值为0x100。
AFIELD16;定义A的长度为16字节,位置为0x100
BFIELD32;定义B的长度为32字节,位置为0x110
SFIELD256;定义S的长度为256字节,位置为0x130

汇编控制(AssemblyControl)伪指令
汇编控制伪指令用于控制汇编程序的执行流程,常用的汇编控制伪指令包括以下几条:
—IF、ELSE、ENDIF
—WHILE、WEND
—MACRO、MEND
—MEXIT
1、IF、ELSE、ENDIF
语法格式:
IF逻辑表达式
指令序列1
ELSE
指令序列2
ENDIF
IF、ELSE、ENDIF伪指令能根据条件的成立与否决定是否执行某个指令序列。当IF后面的逻辑表达式为真,则执行指令序列1,否则执行指令序列2。其中,ELSE及指令序列2可以没有,此时,当IF后面的逻辑表达式为真,则执行指令序列1,否则继续执行后面的指令。
IF、ELSE、ENDIF伪指令可以嵌套使用。
使用示例:
GBLLTest;声明一个全局的逻辑变量,变量名为Test……
IFTest=TRUE
指令序列1
ELSE
指令序列2
ENDIF

2、WHILE、WEND
语法格式:
WHILE逻辑表达式
指令序列
WEND
WHILE、WEND伪指令能根据条件的成立与否决定是否循环执行某个指令序列。当WHILE后面的逻辑表达式为真,则执行指令序列,该指令序列执行完毕后,再判断逻辑表达式的值,若为真则继续执行,一直到逻辑表达式的值为假。
WHILE、WEND伪指令可以嵌套使用。
使用示例:
GBLACounter;声明一个全局的数学变量,变量名为Counter
CounterSETA3;由变量Counter控制循环次数
……
WHILECounter<10
指令序列
WEND

3、MACRO、MEND
语法格式:
$标号宏名$参数1,$参数2,……
指令序列
MEND
MACRO、MEND伪指令可以将一段代码定义为一个整体,称为宏指令,然后就可以在程序中通过宏指令多次调用该段代码。其中,$标号在宏指令被展开时,标号会被替换为用户定义的符号,宏指令可以使用一个或多个参数,当宏指令被展开时,这些参数被相应的值替换。
宏指令的使用方式和功能与子程序有些相似,子程序可以提供模块化的程序设计、节省存储空间并提高运行速度。但在使用子程序结构时需要保护现场,从而增加了系统的开销,因此,在代码较短且需要传递的参数较多时,可以使用宏指令代替子程序。
包含在MACRO和MEND之间的指令序列称为宏定义体,在宏定义体的第一行应声明宏的原型(包含宏名、所需的参数),然后就可以在汇编程序中通过宏名来调用该指令序列。在源程序被编译时,汇编器将宏调用展开,用宏定义中的指令序列代替程序中的宏调用,并将实际参数的值传递给宏定义中的形式参数。
MACRO、MEND伪指令可以嵌套使用。

4、MEXIT
语法格式:
MEXIT
MEXIT用于从宏定义中跳转出去。

其他常用的伪指令
还有一些其他的伪指令,在汇编程序中经常会被使用,包括以下几条:
—AREA
—ALIGN
—CODE16、CODE32
—ENTRY
—END
—EQU
—EXPORT(或GLOBAL)
—IMPORT
—EXTERN
—GET(或INCLUDE)
—INCBIN
—RN
—ROUT
1、AREA
语法格式:
AREA段名属性1,属性2,……
AREA伪指令用于定义一个代码段或数据段。其中,段名若以数字开头,则该段名需用“|”括起来,如|1_test|。
属性字段表示该代码段(或数据段)的相关属性,多个属性用逗号分隔。常用的属性如下:
—CODE属性:用于定义代码段,默认为READONLY。
—DATA属性:用于定义数据段,默认为READWRITE。
—READONLY属性:指定本段为只读,代码段默认为READONLY。
—READWRITE属性:指定本段为可读可写,数据段的默认属性为READWRITE。
—ALIGN属性:使用方式为ALIGN表达式。在默认时,ELF(可执行连接文件)的代码段和数据段是按字对齐的,表达式的取值范围为0~31,相应的对齐方式为2表达式次方。
—COMMON属性:该属性定义一个通用的段,不包含任何的用户代码和数据。各源文件中同名的COMMON段共享同一段存储单元。
一个汇编语言程序至少要包含一个段,当程序太长时,也可以将程序分为多个代码段和数据段。
使用示例:
AREAInit,CODE,READONLY
该伪指令定义了一个代码段,段名为Init,属性为只读

2、ALIGN
语法格式:
ALIGN{表达式{,偏移量}}
ALIGN伪指令可通过添加填充字节的方式,使当前位置满足一定的对其方式|。其中,表达式的值用于指定对齐方式,可能的取值为2的幂,如1、2、4、8、16等。若未指定表达式,则将当前位置对齐到下一个字的位置。偏移量也为一个数字表达式,若使用该字段,则当前位置的对齐方式为:2的表达式次幂+偏移量。
使用示例:
AREAInit,CODE,READONLY,ALIEN=3;指定后面的指令为8字节对齐。
指令序列
END

3、CODE16、CODE32
语法格式:
CODE16(或CODE32)
CODE16伪指令通知编译器,其后的指令序列为16位的Thumb指令。
CODE32伪指令通知编译器,其后的指令序列为32位的ARM指令。
若在汇编源程序中同时包含ARM指令和Thumb指令时,可用CODE16伪指令通知编译器其后的指令序列为16位的Thumb指令,CODE32伪指令通知编译器其后的指令序列为32位的ARM指令。因此,在使用ARM指令和Thumb指令混合编程的代码里,可用这两条伪指令进行切换,但注意他们只通知编译器其后指令的类型,并不能对处理器进行状态的切换。
使用示例:
AREAInit,CODE,READONLY
……
CODE32;通知编译器其后的指令为32位的ARM指令
LDRR0,=NEXT+1;将跳转地址放入寄存器R0
BXR0;程序跳转到新的位置执行,并将处理器切换到Thumb工作状态
……
CODE16;通知编译器其后的指令为16位的Thumb指令
NEXTLDRR3,=0x3FF
……
END;程序结束

4、ENTRY
语法格式:
ENTRY
ENTRY伪指令用于指定汇编程序的入口点。在一个完整的汇编程序中至少要有一个ENTRY(也可以有多个,当有多个ENTRY时,程序的真正入口点由链接器指定),但在一个源文件里最多只能有一个ENTRY(可以没有)。
使用示例:
AREAInit,CODE,READONLY
ENTRY;指定应用程序的入口点
……

5、END
语法格式:
END
END伪指令用于通知编译器已经到了源程序的结尾。
使用示例:
AREAInit,CODE,READONLY
……
END;指定应用程序的结尾

6、EQU
语法格式:
名称EQU表达式{,类型}
EQU伪指令用于为程序中的常量、标号等定义一个等效的字符名称,类似于C语言中的#define。
其中EQU可用“*”代替。
名称为EQU伪指令定义的字符名称,当表达式为32位的常量时,可以指定表达式的数据类型,可以有以下三种类型:
CODE16、CODE32和DATA
使用示例:
TestEQU50;定义标号Test的值为50
AddrEQU0x55,CODE32;定义Addr的值为0x55,且该处为32位的ARM指令。

7、EXPORT(或GLOBAL)
语法格式:
EXPORT标号{[WEAK]}
EXPORT伪指令用于在程序中声明一个全局的标号,该标号可在其他的文件中引用。EXPORT可用GLOBAL代替。标号在程序中区分大小写,[WEAK]选项声明其他的同名标号优先于该标号被引用。
使用示例:
AREAInit,CODE,READONLY
EXPORTStest;声明一个可全局引用的标号Stest……
END

8、IMPORT
语法格式:
IMPORT标号{[WEAK]}
IMPORT伪指令用于通知编译器要使用的标号在其他的源文件中定义,但要在当前源文件中引用,而且无论当前源文件是否引用该标号,该标号均会被加入到当前源文件的符号表中。
标号在程序中区分大小写,[WEAK]选项表示当所有的源文件都没有定义这样一个标号时,编译器也不给出错误信息,在多数情况下将该标号置为0,若该标号为B或BL指令引用,则将B或BL指令置为NOP操作。
使用示例:
AREAInit,CODE,READONLY
IMPORTMain;通知编译器当前文件要引用标号Main,但Main在其他源文件中定义……
END

9、EXTERN
语法格式:
EXTERN标号{[WEAK]}
EXTERN伪指令用于通知编译器要使用的标号在其他的源文件中定义,但要在当前源文件中引用,如果当前源文件实际并未引用该标号,该标号就不会被加入到当前源文件的符号表中。标号在程序中区分大小写,[WEAK]选项表示当所有的源文件都没有定义这样一个标号时,编译器也不给出错误信息,在多数情况下将该标号置为0,若该标号为B或BL指令引用,则将B或BL指令置为NOP操作。
使用示例:
AREAInit,CODE,READONLY
EXTERNMain;通知编译器当前文件要引用标号Main,但Main在其他源文件中定义……
END

10、GET(或INCLUDE)
语法格式:
GET文件名
GET伪指令用于将一个源文件包含到当前的源文件中,并将被包含的源文件在当前位置进行汇编处理。可以使用INCLUDE代替GET。
汇编程序中常用的方法是在某源文件中定义一些宏指令,用EQU定义常量的符号名称,用MAP和FIELD定义结构化的数据类型,然后用GET伪指令将这个源文件包含到其他的源文件中。使用方法与C语言中的“include”相似。
GET伪指令只能用于包含源文件,包含目标文件需要使用INCBIN伪指令
使用示例:
AREAInit,CODE,READONLY
GETa1.s;通知编译器当前源文件包含源文件a1.s
GETC:\a2.s;通知编译器当前源文件包含源文件C:\a2.s……
END

11、INCBIN
语法格式:
INCBIN文件名
INCBIN伪指令用于将一个目标文件或数据文件包含到当前的源文件中,被包含的文件不作任何变动的存放在当前文件中,编译器从其后开始继续处理。
使用示例:
AREAInit,CODE,READONLY
INCBINa1.dat;通知编译器当前源文件包含文件a1.dat
INCBINC:\a2.txt;通知编译器当前源文件包含文件C:\a2.txt……
END

12、RN
语法格式:
名称RN表达式
RN伪指令用于给一个寄存器定义一个别名。采用这种方式可以方便程序员记忆该寄存器的功能。其中,名称为给寄存器定义的别名,表达式为寄存器的编码。
使用示例:
TempRNR0;将R0定义一个别名Temp

13、ROUT
语法格式:
{名称}ROUT
ROUT伪指令用于给一个局部变量定义作用范围。在程序中未使用该伪指令时,局部变量的作用范围为所在的AREA,而使用ROUT后,局部变量的作为范围为当前ROUT和下一个ROUT之间。

 

你可能感兴趣的:(GNU ARM 汇编伪指令(Assembler Directives))