一 GNU AS简介
GNU AS是GNU汇编器,主要用于把汇编代码转换成二进制代码,并存放到一个object文件中。GNU
AS工具本身的使用方法比较简单,主要参考文档《Using as--the GNU Assembler》(2.14)。首先看一下1.1
Structure of this Manual。
This manual is intended to describe what you need to know to
use gnu as. We cover the syntax expected in source files, including
notation for symbols, constants, and expressions;the directives that as
understands; and of course how to invoke as.
This manual also describes some of the machine-dependent features of various flavors of the assembler.
On the other hand, this manual is not intended as an
introduction to programming in assembly language—let alone programming
in general! In a similar vein, we make no attempt to introduce the
machine architecture; we do not describe the instruction set, standard
mnemonics, registers or addressing modes that are standard to a
particular architecture. You may want to consult the manufacturer’s
machine architecture manual for this information.
大致意思如下:本手册要介绍如何使用gnu as,涵盖了源代码文件使用的语法(符号、常量、表达式的表示方法)、as识别的指令、如何调用as。
本手册还描述as汇编器所支持的一些机器依赖的特性。
另外,本手册并不介绍如何编写汇编程序。同样的思路,不回尝试去介绍机器架构,不会描述机器指令集、针对于某特定架构的标准寄存器或寻址模式。这些知识的获取,你可以参考厂商的机器架构手册。
从上述描述可以看出,这篇文档仅仅讲述编译器as如何使用,不会教你如何编写汇编程序。原来在instruction和directive上还有所疑惑。
现在可以区分了。instrcution是CPU架构相关,比如ARM体系结构会包含指令集体系结构,这时所给出的指令集就是instruction
set。而directive是汇编器相关,也就是说为了更好的使用汇编语言进行编程,汇编器会增加一部分指令,提高可读性,这部分指令就是
directive。不同的指令集体系结构对应的instruction不同,比如ARM和X86的instruction不同;不同的汇编器对应的
directive也不同,比如GNU AS和ADS汇编器对应的directive不同。
二 命令行选项
这个需要的时候现查现看。
三 语法
1 预处理 Preprocessing
The as internal preprocessor:
·adjusts and removes
extra whitespace. It leaves one space or tab before the keywords on a
line, and turns any other whitespace on the line into a single space.
• removes all comments, replacing them with a single space, or an appropriate number of newlines.
• converts character constants into the appropriate numeric values.
意思如下:as的内部预处理主要包括三个方面的工作。一是调整和去除额外的间隔符。保留每行的关键字前的一个空格或者TAB,其他任意的间隔符都转换为一个空格。二是去除所有注释,代之以一个空格,或者新行的合适的数字。三是把字符常量转换成相应的数字值。
它不能做宏处理和文件包含处理,如果你需要用,那么可以交给C
预处理器来处理。交给CPP处理的文件包含格式是不同的,需要用#include,跟C的一样(AS中用.include)。那么CPP如何识别这样的文
件呢?答案是通过后缀。man gcc可以获得,
file.s
Assembler code.
file.S
Assembler code which must be preprocessed.
这也就是在vivi的[arch/s3c2410/head.S]中扩展名为什么是大写字母S,而不是小写字母s的原因了。vivi的配置机制的结果引用就是通过#include "config.h"来完成的,对它的处理则是通过CPP完成的预处理。
2 间隔符 Whitespace
可用空格或TAB,一个多个均可,无顺序。间隔符的作用是间隔符号,增强汇编程序的可读性。如果不是字符常量,间隔符等效于一个单一的空格,原因可见预处理部分。
3 注释 Comments
有两种方式(注释经过预处理后都等效于一个空格):行注释和段注释。
段注释和C语言的相同,都是/*...*/。而行注释就因TARGET不同而不同了。现在目标S3C2410的核是ARM,行注释采用“;”或者“@”。这就是vivi中为什么那么多“@”的原因了,当然这里可以使用“;”。如果是在X86机上,则使用“#”。
4 符号 Symbols
A symbol is one or more characters chosen from the set of all
letters (both upper and lower case), digits and the three characters
‘_.$’.
符号由字母、数字、(_ . $)这三种组合而成的一个或者多个字符组成。注意不要以数字开头,没有长度限制,所有字母都是有意义的,也就是说大小写敏感。符号以不在字符集内的字符分割,或者是以文件开头分割。
5 语句 Statements
支持空行,其他和X86下的汇编类似,以newline(“\n”)结束。
6 常量 Constants
包含字符常量、字符串、数字常量等。
关于数字常量,以“0X”或“0x”开头表示16进制,以“0B”或“0b”开头表示二进制,以“0”开头表示八进制,以非零开头表示十进制。十进制和八进制容易出错了。还是直接使用16进制好。如果使用十进制,注意开头不能为0.
四 段和重定向
根据本文档的英文资料,粗略的理解,所谓”段“,从物理上来看,就是一段没有间隔的地址空间,在里面存放着数据,这些数据具有相同的属性,也就是段的属性,比如说RO等。所以段就是具有相同属性的数据的集合。
对于多文件工程,汇编器as将汇编程序转换成目标文件,而这些目标文件都是假定从0开始的。链接器ld把这些目标文件结合库或者启动文件整合在一起,形成
可执行文件。那么ld就需要分配最终的地址,以使得不同部分的程序不会重叠。这样虽然非常简单,但也说明了链接器的作用。同时也说明了段的含义。
链接器ld以section为单位,分派实际运行的地址,这个过程就是重定向。as所产生的目标文件至少有三个段:text、data、bss。当然,还
可以包含自定义的段,用.section定义。这些在ld是就需要根据ld
scripts传递给ld,要不然,ld无法知道如何分派实际运行的地址。实际上,有些动态加载库的程序在加载执行时才会完成最终的重定向分配。这个过程
是比较复杂的,应该读一下John R. Levine的《Linkers and
Loaders》,把链接和加载的过程搞清楚,同时也需要分析重定向之后的可执行文件的格式。
ld只处理四类段:
(1)named sections、text section、data section(2)bss section(3)absolute section (4)undefined section
五 符号
1 labels
标签后面跟“:”,注意标签和“:”之间不要有空格。
2 赋值
可以使用“.equ .set =”。比如设置NUM为0x1000,方法如下:
.equ NUM, 0x1000
NUM = 0x1000
.set NUM, 0x1000
3 符号名
关于命名规则,前面提到了。但是这里有些特殊的地方。
(1)局部符号名 Local Symbol names
局部的符号方便程序员使用临时符号。采用一种简单的定义方式“N:”,即数字加冒号。注意这里的N必须是正整数。另外提供了两种表示方式Nf和Nb。其中
Nf中的f代表forward(向前),也就是说后面遇到的第一个符号N,Nb中的b代表backward(向后),也就是说前面遇到的最近的符号N。比
较形象化,别弄混了。
Here is an example:
1: branch 1f
2: branch 1b
1: branch 2f
2: branch 1b
Which is the equivalent of:
label_1: branch label_3
label_2: branch label_1
label_3: branch label_4
label_4: branch label_3
这些局部变量的符号在汇编器使用之前就已经被转化为更为方便的名字,其处理机制就是维护一个符号表。符号表的组成格式如下:
| L | number | C-B | ordinal number |
L:固定,表示Local。除非使用-L选项,否则不会输出局部符号表。
number: 就是你定义的N。
C-B: 不常用的量,避免重复。
ordial number:用来区分,如果你定义同样的N:,就要靠这个来区分符号名了。
比如说,上面的例子中,label_1实际上为L1C-B1, label_2为L2C-B1,label_3为L1C-B2,label_2为L2C-B2。
(2)Dollar Local Labels
以N$的形式定义,Local symbol label最多在一个文件中有效,dollar local label可以在某些文件中有效,除非定义了一个同名的全局变量。
(3)Dot
“.”可以表示当前地址。该符号可以被引用或者赋值。
六 表达式
- |
取相反数,减 |
+ |
加法运算 |
/ |
除 |
== |
等于 |
% |
取余运算 |
<> |
不等于 |
<, << |
左移 |
< |
小于 |
>, >> |
右移 |
> |
大于 |
| |
按位或 |
>= |
大于等于 |
& |
按位与 |
<= |
小于等于 |
^ |
按位异或 |
&& |
逻辑与 |
! |
按位或非 |
| | |
逻辑或 |
~ |
按位取反 |
|
|
1. Highest Precedence
* Multiplication.
/ Division. Truncation is the same as the C operator ‘/’
% Remainder.
<
<< Shift Left. Same as the C operator ‘<<’.
>
>> Shift Right. Same as the C operator ‘>>’.
2. Intermediate precedence
| Bitwise Inclusive Or.
& Bitwise And.
^ Bitwise Exclusive Or.
! Bitwise Or Not.
3. Low Precedence
+ Addition. If either argument is absolute, the result has the
section of the other argument. You may not add together arguments from
different
sections.
- Subtraction. If the right argument is
absolute, the result has the section of the left argument. If both
arguments are in the same section, the result is absolute. You may not
subtract arguments from different sections.
== Is Equal To
<> Is Not Equal To
< Is Less Than
> Is Greater Than
>= Is Greater Than Or Equal To
<= Is Less Than Or Equal To
The comparison operators can be used as infix operators. A
true results has a value of -1 whereas a false result has a value of 0.
Note, these operators perform signed comparisons.
4. Lowest Precedence
&& Logical And.
|| Logical Or.
These two logical operations can be used to combine the
results of sub expressions. Note, unlike the comparison operators a
true result returns a value of 1 but a false results does still return
0. Also note that the logical or operator has a slightly lower
precedence than logical and.
七 汇编命令 Assembler Directives
.align 插入0-3个0x0,以便实现四字节边界对齐
.ascii "string" 定义字符串,后面不带NUL
.asciz "string" 定义字符串,后面带0
.byte 定义一个字节
.word 定义一个字
.hword 定义半字
.long 等同于.int 定义整形整数
.global 定义全局变量
.section name 定义段
.end 表示汇编程序的结束
//实现条件编译
.ifdef
...
.elseif
...
.endif
//实现定义宏
.macro macro_name param_name1, param_name2, ...
...
.endm
八 机器相关特性
现在只选择ARM相关的选项。
1、立即数前可以用“#”,也可以用“$”,不过习惯上用“#”。
2、ARM Machine Directives
.align expression [, expression]
这个对于ARM来说,如果第一个参数为0,则汇编器默认当作2来处理,即2字节边界对齐。其余为4字节对齐。这主要是ARM自己的汇编器的兼容性而设定的。
name .req register name
为寄存器创建一个别名,如foo .req r0
.code [16|32]
选择指令集,.code 16即thumb指令集,.code 32为arm指令集。
.thumb
等同于.code 16
.arm
等同于.code 32
.force_thumb
强制转化为thumb指令集,即使处理器不支持
.ltorg
创建一个文字池,4字节边界对齐,堆放于当前段。
.pool
等同于.ltorg
3 ARM pseudo opcodes
这里主要包括nop,ldr,adr,adrl。使用方法参考ARM使用手册。
八 总结
GAS的使用方法并不难,把上面掌握,在语法上没有什么问题了。汇编语言的设计并不仅仅是语法,更重要的是编程的思想,这是独立于开发语言的。幸好,使用
ARM汇编暂时只用于bootloader的stage1阶段,功能和要求都不是太高,掌握好这些基本够用了。多温习,多练习,即可。
备注:
需要注意的是,特殊的符号的处理与as的版本有关。现在的环境下,测试了几个特殊部分:
# 有两种用法,一是可以用在指令中,后面跟立即数。二是用在指令外,作为行注释。
@ 行注释
$ 用在指令中,后面跟立即数
; 标识行结束,可将几条指令写于一行上(借鉴了C的特性,也是与之兼容),因为可以利用C的预处理器来处理ARM的汇编。所以在封装宏上与C保持了一致。于是,“;”也就不作为行注释符了。