跳至主要内容

【转】vivi开发笔记(七):GNU AS基本使用方法

文章说明:calmarrow(lqm)原创

文章引自:http://piaoxiang.cublog.cn

 
一 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保持了一致。于是,";"也就不作为行注释符了。

评论

此博客中的热门博文

【转】AMBA、AHB、APB总线简介

AMBA 简介 随着深亚微米工艺技术日益成熟,集成电路芯片的规模越来越大。数字IC从基于时序驱动的设计方法,发展到基于IP复用的设计方法,并在SOC设计中得到了广泛应用。在基于IP复用的SoC设计中,片上总线设计是最关键的问题。为此,业界出现了很多片上总线标准。其中,由ARM公司推出的AMBA片上总线受到了广大IP开发商和SoC系统集成者的青睐,已成为一种流行的工业标准片上结构。AMBA规范主要包括了AHB(Advanced High performance Bus)系统总线和APB(Advanced Peripheral Bus)外围总线。   AMBA 片上总线        AMBA 2.0 规范包括四个部分:AHB、ASB、APB和Test Methodology。AHB的相互连接采用了传统的带有主模块和从模块的共享总线,接口与互连功能分离,这对芯片上模块之间的互连具有重要意义。AMBA已不仅是一种总线,更是一种带有接口模块的互连体系。下面将简要介绍比较重要的AHB和APB总线。 基于 AMBA 的片上系统        一个典型的基于AMBA总线的系统框图如图3所示。        大多数挂在总线上的模块(包括处理器)只是单一属性的功能模块:主模块或者从模块。主模块是向从模块发出读写操作的模块,如CPU,DSP等;从模块是接受命令并做出反应的模块,如片上的RAM,AHB/APB 桥等。另外,还有一些模块同时具有两种属性,例如直接存储器存取(DMA)在被编程时是从模块,但在系统读传输数据时必须是主模块。如果总线上存在多个主模块,就需要仲裁器来决定如何控制各种主模块对总线的访问。虽然仲裁规范是AMBA总线规范中的一部分,但具体使用的算法由RTL设计工程师决定,其中两个最常用的算法是固定优先级算法和循环制算法。AHB总线上最多可以有16个主模块和任意多个从模块,如果主模块数目大于16,则需再加一层结构(具体参阅ARM公司推出的Multi-layer AHB规范)。APB 桥既是APB总线上唯一的主模块,也是AHB系统总线上的从模块。其主要功能是锁存来自AHB系统总...

【转】GPIO编程模拟I2C入门

ARM编程:ARM普通GPIO口线模拟I2C  请教个问题: 因为需要很多EEPROM进行点对点控制,所以我现在要用ARM的GPIO模拟I2C,管脚方向我设 置的是向外的。我用网上的RW24C08的万能程序修改了一下,先进行两根线的模拟,SDA6, SCL6,但是读出来的数不对。我做了一个简单的实验,模拟SDA6,SCL6输出方波,在示波 器上看到正确方波,也就是说,我的输出控制是没问题的。 哪位大哥能指点一下,是否在接收时管脚方向要设为向内?(不过IOPIN不管什么方向都可 以读出当前状态值的阿) 附修改的RW24C08()程序: #define  SomeNOP() delay(300); /**/ /* *********************************  RW24C08   **************************************** */ /**/ /* ----------------------------------------------------------------------------- ---  调用方式:void I2CInit(void)   函数说明:私有函数,I2C专用 ------------------------------------------------------------------------------- -- */ void  I2CInit( void ) ... {  IO0CLR  =  SCL6;      // 初始状态关闭总线  SomeNOP();  // 延时   I2CStop();  // 确保初始化,此时数据线是高电平 }   /**/ /* ---------------------------------------------------------------------------- ----  调用方式:void I2CSta...

【转】cs8900网卡的移植至基于linux2.6内核的s3c2410平台

cs8900网卡的移植至基于linux2.6内核的s3c2410平台(转) 2008-03-11 20:58 硬件环境:SBC-2410X开发板(CPU:S3C2410X) 内核版本:2.6.11.1 运行环境:Debian2.6.8 交叉编译环境:gcc-3.3.4-glibc-2.3.3 第一部分 网卡CS8900A驱动程序的移植 一、从网上将Linux内核源代码下载到本机上,并将其解压: #tar jxf linux-2.6.11.1.tar.bz2 二、打开内核顶层目录中的Makefile文件,这个文件中需要修改的内容包括以下两个方面。 (1)指定目标平台。 移植前:         ARCH?= $(SUBARCH) 移植后: ARCH            :=arm (2)指定交叉编译器。 移植前: CROSS_COMPILE ?= 移植后: CROSS_COMPILE   :=/opt/crosstool/arm-s3c2410-linux-gnu/gcc-3.3.4-glibc-2.3.3/bin/arm-s3c2410-linux-gnu- 注:这里假设编译器就放在本机的那个目录下。 三、添加驱动程序源代码,这涉及到以下几个方面。(1)、从网上下载了cs8900.c和cs8900.h两个针对2.6.7的内核的驱动程序源代码,将其放在drivers/net/arm/目录下面。 #cp cs8900.c ./drivers/net/arm/ #cp cs8900.h ./drivers/net/arm/ 并在cs8900_probe()函数中,memset (&priv,0,sizeof (cs8900_t));函数之后添加如下两条语句: __raw_writel(0x2211d110,S3C2410_BWSCON); __raw_writel(0x1f7c,S3C2410_BANKCON3); 注:其原因在"第二部分"解释。 (2)、修改drivers/net/arm/目录下的Kconfig文件,在最后添加如...