汇编源程序一般用于系统最基本的初始化:初始化堆栈指针、设置页表、操作 ARM的协处理器等。这些初始化工作完成后就可以跳转到C代码main函数中执行。
1、GNU汇编语言语句格式
任何Linux汇编行都是如下结构:[<label>:][<instruction or directive orpseudo-instruction>}@comment
linstruction为指令
ldirective为伪操作
lpseudo-instruction为伪指令
l<label>:为标号,GNU汇编中,任何以冒号结尾的标识符都被认为是一个标号,而不一定非要在一行的开始。
lcomment为语句的注释
下面定义一个"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
注意:
lARM指令,伪指令,伪操作,寄存器名可以全部为大写字母,也可全部为小写字母,但不可大小写混用。
l如果语句太长,可以将一条语句分几行来书写,在行末用“\”表示换行(即下一行与本行为同一语句)。“\”后不能有任何字符,包含空格和制表符(Tab)。
GNU ARM汇编伪指令均以.开始。常用汇编伪指如下所示:
汇编伪指令 | 描述 | 举例 |
.ascii “<string>” | 插入字符串到目标文件,但不以NULL结束 | .ascii "JNZ" @插入字节0x4A 0x4E 0x5A |
.asciz “<string>” | 与.ascii类似,但以NULL结束 | .asciz "JNZ" @插入字节0x4A 0x4E 0x5A 0x00 |
.balign <power_of_2> {,<fill_value> {,<max_padding>} } | 使下面的位置<power_of_2>对齐 | .byte 0x55 @插入字节: 0x55 .balign 4,0 @插入3个对齐字节:0x00 0x00 0x00 .word 0xAA55EE11 @插入字节:0x11 0xEE 0x55 0xAA (LSB order) |
.byte <byte1> {,<byte2>} … | 插入字节到目标文件 | .byte 64, 'A' @ inserts the bytes 0x40 0x41 .byte 0x42 @ inserts the byte 0x42 .byte 0b1000011, 0104 @ inserts the bytes 0x43 0x44 |
.code <number_of_bits> | 设置指令位数,16位Thumb或32位ARM | .code 16 @设置以下汇编为Thumb指令 |
.else | 与 .if 和 .endif 配合使用 | |
.end | 标志汇编文件结束,汇编器不再读后面的内容 | |
.endif | 怀.if 配合使用 | |
.endm | 标志宏定义结束,与.macro配合使用 | |
.endr | 结束一个loop,与.rept 和 .irp 配合使用 | |
.equ <symbol name>, <value> | 设置一个符号的值 | .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> | 标识symbol被程序的其它模块(汇编或C)访问, 相当于Kernel中的EXPORT_SYMBOL | .global _my_test_count @它可被其它模块R/W |
.hword <short1> {,<short2>} … | 插入16位(半字)值到目标文件 | .hword 0xAA55, 12345 @ 插入字节 0x55 0xAA 0x39 0x30 .2byte 0x55AA, -1 @插入字节 0xAA 0x55 0xFF 0xFF @ Least Significant Byte (LSB) ordering assumed |
.if <logical_expression> | 与 .endif 配合使用 | |
.ifdef <symbol> | 与 .endif 配合使用 | |
.ifndef <symbol> | 与 .endif 配合使用 | |
.include “<filename>” | 与C的#include类似 | |
.irp <param> {,<val_1>} {,<val_2>} … | 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 \<param> to substitute the associated value in the value list. | |
.macro <name> {<arg_1} {,<arg_2>} … {,<arg_N>} | 定义一个名为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_name> {,”<flags>”} | 开始一个新的section, section_name可为: .text: 代码段 .data: 已初始化的数据段 .bss: 未初始化的数据段 flags可为: a allowable section w writable section x executable section | .section .text, “x” |
.set <variable_name>, <variable_value> | 设置变化的值,与.equ一样 | .set adams, 0x2A @ set adams to 42 |
.space <number_of_bytes> {,<fill_byte>} | 预留给定字节空间,并设置为0或fill_byte | .space 0x400,0 |
.word <word1> {,<word2>} … | 插入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> | 重复执行代码块number_of_times次,与C中for 类似,以endr结束 | |
<register_name> .req <register_name> | 寄存器重新命名 | 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 |
4、GNU汇编程序中的标号symbol(或label)
标号只能由a~z,A~Z,0~9,“.”,_等(由点、字母、数字、下划线等组成,除局部标号外,不能以数字开头)字符组成。
Symbol的本质:代表它所在的地址,因此也可以当作变量或者函数来使用。
l段内标号的地址值在汇编时确定;
l段外标号的地址值在连接时确定。
Symbol的分类:3类(依据标号的生成方式)。
<1>基于PC的标号。基于PC的标号是位于目标指令前的标号或者程序中数据定义伪操作前的标号。这种标号在汇编时将被处理成PC值加上(或减去)一个数字常量,常用于表示跳转指令”b”等的目标地址,或者代码段中所嵌入的少量数据。
<2>基于寄存器的标号。基于寄存器的标号常用MAP和FIELD来定义,也可以用EQU来定义。这种标号在汇编时将被处理成寄存器的值加上(或减去)一个数字常量,常用于访问数据段中的数据。
<3>绝对地址。绝对地址是一个32位数据。它可以寻址的范围为[0,232-1]即可以直接寻址整个内存空间。
特别说明:局部标号Symbol
局部标号主要在局部范围内使用,而且局部标号可以重复出现。它由两部组成:开头是一个0-99直接的数字,后面紧接一个通常表示该局部变量作用范围的符号。局部变量的作用范围通常为当前段,也可以用ROUT来定义局部变量的作用范围。
局部变量定义的语法格式:N{routname}
lN:为0~99之间的数字。
lroutname:当前局部范围的名称(为符号),通常为该变量作用范围的名称(用ROUT伪操作定义的)。
局部变量引用的语法格式:%{F|B}{A|T}N{routname}
l%:表示引用操作
lN:为局部变量的数字号
lroutname:为当前作用范围的名称(用ROUT伪操作定义的)
lF:指示编译器只向前搜索
lB:指示编译器只向后搜索
lA:指示编译器搜索宏的所有嵌套层次
lT:指示编译器搜索宏的当前层次
例:使用局部符号的例子,一段循环程序
1:
subs r0, r0, #1 @每次循环使r0=r0-1
bne 1F @跳转到1标号去执行
注意:
l如果F和B都没有指定,编译器先向前搜索,再向后搜索
l如果A和T都没有指定,编译器搜索所有从当前层次到宏的最高层次,比当前层次低的层次不再搜索。
l如果指定了routname,编译器向前搜索最近的ROUT伪操作,若routname与该ROUT伪操作定义的名称不匹配,编译器报告错误,汇编失败。
*一个重要的全局符号是_start,GNULinker需要用它来指定程序中的第一条指令,它总是一个.global标记的符号,并且只能出现一次。
5、GNU汇编程序中的分段
<1>.section伪操作
.section <section_name> {,”<flags>”}
Starts a new code or data section. Sections in GNU are called .text, a code section, .data, an initialized data section, and .bss, an uninitialized data section.
These sections have default flags, and the linker understands the default names(similar directive to the armasm directive AREA).The following are allowable.section flags for ELF format files:
<Flag> Meaning
a allowable section
w writable section
x executable section
中文解释:
用户可以通过.section伪操作来自定义一个段,格式如下:
.section section_name [, "flags"[, %type[,flag_specific_arguments]]]
每一个段以段名为开始,以下一个段名或者文件结尾为结束。这些段都有缺省的标志(flags),连接器可以识别这些标志。(与arm asm中的AREA相同)。下面是ELF格式允许的段标志flags:
<标志> 含义
a 允许段
w 可写段
x 执行段
例:定义一个“段”
.section .mysection @自定义数据段,段名为 “.mysection”
.align 2
strtemp:
.ascii "Temp string \n\0" @对这一句的理解,我觉得应该是:将"Temp string \n\0"这个字符串存储在以标号strtemp:
@为起始地址的一段内存空间里
<2>汇编系统预定义的段名
l.text @代码段
l.data @初始化数据段.data Read-write initialized long data.
l.bss @未初始化数据段
l.sdata @.sdata Read-write initialized short data.
l.sbss @
注意:源程序中.bss段应该在.text段之前。
6、GNU汇编语言定义入口点
汇编程序的缺省入口是_start标号,用户也可以在连接脚本文件中用ENTRY标志指明其它入口点。
例:定义入口点
.section .data
< initialized data here>
.section .bss
< uninitialized data here>
.section .text
.globl _start
_start:
<instruction code goes here>
7、GNU汇编程序中的宏定义
格式如下:
.macro 宏名参数名列表 @伪指令.macro定义一个宏
宏体
.endm @.endm表示宏结束
如果宏使用参数,那么在宏体中使用该参数时添加前缀“\”。宏定义时的参数还可以使用默认值。可以使用.exitm伪指令来退出宏。
例:宏定义
.macro SHIFTLEFT a, b
.if \b < 0
MOV \a, \a, ASR #-\b
.exitm
.endif
MOV \a, \a, LSL #\b
.endm
8、GNU汇编程序中的常数
<1>十进制数以非0数字开头,如:123和9876;
<2>二进制数以0b开头,其中字母也可以为大写;
<3>八进制数以0开始,如:0456,0123;
<4>十六进制数以0x开头,如:0xabcd,0X123f;
<5>字符串常量需要用引号括起来,中间也可以使用转义字符,如: “You are welcome!\n”;
<6>当前地址以“.”表示,在GNU汇编程序中可以使用这个符号代表当前指令的地址;
<7>表达式:在汇编程序中的表达式可以使用常数或者数值, “-”表示取负数, “~”表示取补,“<>”表示不相等,其他的符号如:+、-、*、 /、%、<、<<、>、>>、|、&、^、!、==、>=、<=、&&、||跟C语言中的用法相似。
9、GNU ARM汇编的常用伪操作
在前面已经提到过了一些为操作,还有下面一些为操作:
l数据定义伪操作: .byte,.short,.long,.quad,.float,.string/.asciz/.ascii,重复定义伪操作.rept,赋值语句.equ/.set;
l函数的定义;
l对齐方式伪操作 .align;
l源文件结束伪操作.end;
l.include伪操作;
lif伪操作;
l.global/ .globl伪操作;
l.type伪操作;
l列表控制语句;
别于GNUAS汇编的通用伪操作,下面是ARM特有的伪操作:
.reg ,.unreq,.code,.thumb,.thumb_func,.thumb_set, .ltorg,.pool
<1>数据定义伪操作
l.byte:单字节定义,如:.byte 1,2,0b01,0x34,072,'s' ;
l.short:定义双字节数据,如:.short 0x1234,60000 ;
l.long:定义4字节数据,如:.long 0x12345678,23876565
l.quad:定义8字节,如:.quad 0x1234567890abcd
l.float:定义浮点数,如:.float 0f-314159265358979323846264338327\
95028841971.693993751E-40 @- pi
l.string/.asciz/.ascii:定义多个字符串,如:
.string "abcd", "efgh", "hello!"
.asciz "qwer", "sun", "world!"
.ascii "welcome\0"
注意:ascii伪操作定义的字符串需要自行添加结尾字符'\0'。
l.rept:重复定义伪操作,格式如下:
.rept重复次数
数据定义
.endr @结束重复定义
例:
.rept 3
.byte 0x23
.endr
l.equ/.set:赋值语句,格式如下:
.equ(.set)变量名,表达式
例:
.equ abc, 3 @让abc=3
<2>函数的定义伪操作
l函数的定义,格式如下:
函数名:
函数体
返回语句
一般的,函数如果需要在其他文件中调用,需要用到.global伪操作将函数声明为全局函数。为了不至于在其他程序在调用某个C函数时发生混乱,对寄存器的使用我们需要遵循APCS准则。函数编译器将处理函数代码为一段.global的汇编码。
l函数的编写应当遵循如下规则:
a.a1-a4寄存器(参数、结果或暂存寄存器,r0到r3的同义字)以及浮点寄存器f0-f3(如果存在浮点协处理器)在函数中是不必保存的;
b.如果函数返回一个不大于一个字大小的值,则在函数结束时应该把这个值送到 r0中;
c.如果函数返回一个浮点数,则在函数结束时把它放入浮点寄存器f0中;
d.如果函数的过程改动了sp(堆栈指针,r13)、fp(框架指针,r11)、sl(堆栈限制,r10)、lr(连接寄存器,r14)、v1-v8(变量寄存器,r4到 r11)和 f4-f7,那么函数结束时这些寄存器应当被恢复为包含在进入函数时它所持有的值。
<3>.align .end .include .incbin伪操作
l.align:用来指定数据的对齐方式,格式如下:
.align [absexpr1, absexpr2]
以某种对齐方式,在未使用的存储区域填充值.第一个值表示对齐方式,4, 8,16或 32.第二个表达式值表示填充的值。
l.end:表明源文件的结束。
l.include:可以将指定的文件在使用.include的地方展开,一般是头文件,例如:
.include “myarmasm.h”
l.incbin伪操作可以将原封不动的一个二进制文件编译到当前文件中,使用方法如下:
.incbin "file"[,skip[,count]]
skip表明是从文件开始跳过skip个字节开始读取文件,count是读取的字数.
<4>..if伪操作
根据一个表达式的值来决定是否要编译下面的代码,用.endif伪操作来表示条件判断的结束,中间可以使用.else来决定.if的条件不满足的情况下应该编译哪一部分代码。
.if有多个变种:
.ifdef symbol @判断symbol是否定义
.ifc string1,string2 @字符串string1和string2是否相等,字符串可以用单引号括起来
.ifeq expression @判断expression的值是否为0
.ifeqs string1,string2 @判断string1和string2是否相等,字符串必须用双引号括起来
.ifge expression @判断expression的值是否大于等于0
.ifgt absolute expression @判断expression的值是否大于0
.ifle expression @判断expression的值是否小于等于0
.iflt absolute expression @判断expression的值是否小于0
.ifnc string1,string2 @判断string1和string2是否不相等,其用法跟.ifc恰好相反。
.ifndef symbol, .ifnotdef symbol @判断是否没有定义symbol,跟.ifdef恰好相反
.ifne expression @如果expression的值不是0,那么编译器将编译下面的代码
.ifnes string1,string2 @如果字符串string1和string2不相等,那么编译器将编译下面的代码.
<5>.global .type .title .list
l.global/ .globl:用来定义一个全局的符号,格式如下:
.global symbol 或者 .globl symbol
l.type:用来指定一个符号的类型是函数类型或者是对象类型,对象类型一般是数据,格式如下:
.type符号,类型描述
例:
.globl a
.data
.align 4
.type a, @object
.size a, 4
a:
.long 10
例:
.section .text
.type asmfunc, @function
.globl asmfunc
asmfunc:
mov pc, lr
<6>列表控制语句:
.title:用来指定汇编列表的标题,例如:
.title “my program”
.list:用来输出列表文件.
<7>ARM特有的伪操作
l.reg:用来给寄存器赋予别名,格式如下:
别名 .req寄存器名
l.unreq:用来取消一个寄存器的别名,格式如下:
.unreq 寄存器别名
注意被取消的别名必须事先定义过,否则编译器就会报错,这个伪操作也可以用来取消系统预制的别名,例如r0,但如果没有必要的话不推荐那样做。
l.code伪操作用来选择ARM或者Thumb指令集,格式如下:
.code 表达式
如果表达式的值为16则表明下面的指令为Thumb指令,如果表达式的值为32则表明下面的指令为ARM指令.
l.thumb伪操作等同于.code 16, 表明使用Thumb指令,类似的.arm等同于.code 32
l.force_thumb伪操作用来强制目标处理器选择thumb的指令集而不管处理器是否支持
l.thumb_func伪操作用来指明一个函数是thumb指令集的函数
l.thumb_set伪操作的作用类似于.set,可以用来给一个标志起一个别名,比.set功能增加的一点是可以把一个标志标记为thumb函数的入口,这点功能等同于.thumb_func
l.ltorg用于声明一个数据缓冲池(literal pool)的开始,它可以分配很大的空间。
l.pool的作用等同.ltorg
l.space <number_of_bytes> {,<fill_byte>}
分配number_of_bytes字节的数据空间,并填充其值为fill_byte,若未指定该值,缺省填充0。(与armasm中的SPACE功能相同)
l.word <word1> {,<word2>} …
插入一个32-bit的数据队列。(与armasm中的DCD功能相同)。可以使用.word把标识符作为常量使用。
例:
Start:
valueOfStart:
.word Start
这样程序的开头Start便被存入了内存变量valueOfStart中。
l.hword <short1> {,<short2>} …
插入一个16-bit的数据队列。(与armasm中的DCW相同)
10、GNU ARM汇编特殊字符和语法
<1>代码行中的注释符号: ‘@’
<2>整行注释符号: ‘#’
<3>语句分离符号: ‘;’
<4>立即数前缀: ‘#’或 ‘$’