GNU Assembler

目录

0、绪言

1、基本格式

2、标号

2.1、局部标号

3、伪操作

3.1、.section

3.2、.global、.globl、.local

3.3、.macro

3.4、. byte,.short,.long,.quad,.float

3.5、.align

3.6、.end

3.7、.include

3.8、.weak

3.9、.equ、.set

3.10、.if、.else/.endif

3.11、.rept、.endr

3.12、.extern


0、绪言

虽然现在嵌入式开发99%内容都是由 C(C++) 代码构成,但是一旦是做嵌入式,那就避免不了接触汇编代码,和汇编相关的内容,主要由汇编器、CPU 体系架构指令集构成;

使用不同汇编器的情况下,具体汇编实现的语法也会有部分出入,比如使用 ARM Assembler 那一套(KEIL/MDK)和 GNU Assembler那一套(GNU),汇编代码上,会有明显的不一样,这主要取决于汇编器;

本文侧重于介绍 ARM GNU 的汇编部分;

可以参考的内容:

1. 网页版的 GNU Assembler:

Using as

2. 简短的一份快速参考(内容少,都是常用的内容):

GNU ARM Assembler Quick Reference

3. 稍微丰富一点的参考:

Using as, the GNU Assembler

1、基本格式

GNU 风格的汇编需要遵循如下格式:

<label>:  <instruction or directive or pseudo-instruction> @ comments 分解出来为:

label: instruction

label: directive

label: pseudo-instruction

其中:

label 是一个标号

instruction 是集指令

directive 是伪操作

pseudo-instruction 是伪指令

也就是说:GNU 的汇编代码可以包含:伪操作、伪指令和真正的汇编指令;

GNU ARM Assembler Quick Reference 中的一个例子如下:

.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

这个汇编定义了一个 add 的标号,并且是 global 的,他的实现在 add:之后;

其中可以看到一些伪操作,后面会仔细分析;

使用伪操作有什么好处?

如果不使用伪操作(定义标号,跳转),这样会让汇编代码写得非常别扭,比如你要跳转到某个地方执行,那么你需要知道他相当当前 pc 的偏移之类的信息,这会让编码很困难;

2、标号

前面说了基本的格式是:

<label>:  <instruction or directive or pseudo-instruction> @ comments

这里对标号有添加一些说明:

标号只能由a~z,A~Z,0~9,“.”,_等(由点、字母、数字、下划线等组成,除局部标号外,不能以数字开头)字符组成。

Symbol 的本质:代表它所在的地址,因此也可以当作变量或者函数来使用。

  • 段内标号的地址值在汇编时确定;
  • 段外标号的地址值在连接时确定;

Symbol 的分类:3类(依据标号的生成方式)。

<1> 基于PC的标号。基于PC的标号是位于目标指令前的标号或者程序中数据定义伪操作前的标号。这种标号在汇编时将被处理成PC值加上(或减去)一个数字常量,常用于表示跳转指令”b”等的目标地址,或者代码段中所嵌入的少量数据。

<2> 基于寄存器的标号。基于寄存器的标号常用MAP和FIELD来定义,也可以用EQU来定义。这种标号在汇编时将被处理成寄存器的值加上(或减去)一个数字常量,常用于访问数据段中的数据。

<3> 绝对地址。绝对地址是一个32位数据。它可以寻址的范围为[0,232-1]即可以直接寻址整个内存空间。

2.1、局部标号

局部标号主要在局部范围内使用,而且局部标号可以重复出现。它由两部组成:开头是一个0-99直接的数字,后面紧接一个通常表示该局部变量作用范围的符号。局部变量的作用范围通常为当前段,也可以用ROUT来定义局部变量的作用范围。

局部变量定义的语法格式:N{routname}

  • N:为0~99之间的数字。
  • routname:当前局部范围的名称(为符号),通常为该变量作用范围的名称(用ROUT伪操作定义的)。

局部变量引用的语法格式:%{F|B}{A|T}N{routname}

  • %:表示引用操作
  • N:为局部变量的数字号
  • routname:为当前作用范围的名称(用ROUT伪操作定义的)
  • F:指示编译器只向前搜索
  • B:指示编译器只向后搜索
  • A:指示编译器搜索宏的所有嵌套层次
  • T:指示编译器搜索宏的当前层次

例如:uboot 中有如下代码:

 这里就是局部标号用作循环的例子;其中的 "b" 指的是向后搜索,这个,指的是之前的代码;

注意:

  • 如果F和B都没有指定,编译器先向前搜索,再向后搜索
  • 如果A和T都没有指定,编译器搜索所有从当前层次到宏的最高层次,比当前层次低的层次不再搜索。
  • 如果指定了routname,编译器向前搜索最近的ROUT伪操作,若routname与该ROUT伪操作定义的名称不匹配,编译器报告错误,汇编失败。

3、伪操作

3.1、.section

为了解释分段伪操作,我们需要先知道,什么是分段;

我们使用编译器编译一坨代码,最后体现出来的形式是数据和指令;数据我们称之为数据段,代码称之为代码段;

默认情况下,编译器将默认定义一些用于存放我们指令和数据的代码段和数据段;并且细分如下:

.text 表示代码段。
.data 初始化的数据段。
.bss 未初始化的数据段。
.rodata 只读数据段。

当然,除了上面 4 个简单的定义,其实 GNU 工具链还有对更多的段进行预定义,比如 sbss 段,sdata 段,等等,有兴趣的朋友可以翻翻 GNU 的文档查看一下;

除了预定义的段名以外,当然,用户也可以自己定义自己的段名,并将他们描述在 Linker 脚本中;

比如,你可以在你的 C 代码中定义自定义段(数据和代码都可以):

int num __attribute__ ((section(".mysection"))) = 1;

然后在 Linker file 里面去加入它:

__mysection_start = . ;
.mysection  :
{
  *(.mysection);
}
.align = 4;
__mysection_end = . ;

其中,__mysection_start 和 __mysection_end 用于记录段的开始地址与结束地址,

当然,你也可以在你的汇编代码里面通过使用 .section 去定义一个自定义的段,他的语法格式为:

.section section_name[,"flags"[,%type[,flag_specific_arguments]]]

比如:

.section ".customtext", "ax"

每一个段以段名为开始, 以下一个段名或者文件结尾为结束。这些段都有缺省的标志(flags),连接器可以识别这些标志。(与arm asm中的AREA相同)。下面是ELF格式允许的段标志flags:

<标志>     含义:

a          允许段

w          可写段

x          执行段

好了,解释了分段后,现在引出 GNU Assembler 的分段;

GNU Assembler 使用关键字 .section 来表征一个段,定义的段可以有一些 flag,比如 a、w、x;GNU 预定义了一些段(.text、.data、.rodata、.bss、等等),也支持用户在代码(汇编或者C)中进行自行定义段(数据段或者代码段);

3.2、.global、.globl、.local

这个伪操作用来声明一个全局的符号,相当于把这个符号导出到外面,可以让其他文件引用这个符号;通常的,可以由其他的文件或者 Linker file 来引用,比如:

 汇编程序的缺省入口是 _start 标号,用户也可以在连接脚本文件中用 ENTRY 标志指明其它入口点

标号

含义

.global

使得符号对连接器可见,变为对整个工程可用的全局变量

_start

汇编程序的缺省入口是_ start标号,用户也可以在连接脚本文件中用ENTRY标志指明其它入口点.

.local

表示符号对外部不可见,只对本文件可见

3.3、.macro

GNU 汇编中,使用 .macro 来定义一个宏定义;.macro.endm 宏定义类似 C 语言里的宏函数;

语法格式:

   .macro    {$label} 名字{$parameter{,$parameter}…}
    …………code…………
   .endm

macro伪操作可以将一段代码定义为一个整体,称为宏指令。然后就可以在程序中通过宏指令多次调用该段代码。如果宏使用参数,那么在宏体中使用该参数时添加前缀“\”。宏定义时的参数还可以使用默认值。可以使用.exitm伪指令来退出宏。

不带参数的宏函数定义:

 带参数的宏函数定义:

 .macro MOV_PC_LR ,param
    mov r1,\param
    MOV PC,LR
 .endm

调用方法为:

MOV_PC_LR  #12

3.4、. byte,.short,.long,.quad,.float

数据定义伪操作一般用于为特定的数据分配存储单元,同时可完成已分配存储单元的初始化。常见的数据定义伪操作有如下几种:

标号

含义

.byte

单字节定义 0x12,‘a’,23 【必须偶数个】

.short

定义2字节数据 0x1234, 65535

.long /.word

定义4字节数据 0x12345678

.quad

定义8字节 .quad 0x1234567812345678

.float

定义浮点数 .float 0f3.2

.string/.asciz/.ascii

定义字符串 .ascii “abcd\0”, 注意:.ascii 伪操作定义的字符串需要每行添加结尾字符 '\0',其他不需要

.space/.skip

用于分配一块连续的存储区域并初始化为指定的值,如果后面的填充值省略不写则在后面填充为0;

.rept

重复执行接下来的指令,以.rept开始,以.endr结束

用法举例:

 红色部分,预定义了 4 个 32bit 的数,内容分别是 0x00002000,0x00,0x00,0x00;

黄色部分,定义了跳转的地址;

3.5、.align

GNU 汇编中,使用 .align 来进行对齐操作,他的语法为:

.align [absexpr1, absexpr2]

第一个值表示对齐方式,4, 8,16或 32.第二个表达式值表示填充的值;

.align  4     --- 16字节对齐 2的4次方
.align  (4)   --- 4字节对齐

3.6、.end

.end 表明源文件的结束,如果该标号之后还有代码,不会被编译到执行文件中;

3.7、.include

.include:可以将指定的文件在使用.include 的地方展开,一般是头文件,例如:

.include "filename" 

3.8、.weak

用于声明一个弱符号,如果这个符号没有定义,编译就忽略,而不会报错;

3.9、.equ、.set

用于将程序中的 Symbol 定义值,格式为:

.equ  symbol  expression

把某一个符号(symbol)定义成某一个值(expression)

该指令并不分配空间,类似于 C 语言的 #define

举例:

.equ   start,  0x40                                      
mov r1, #start      ;r1里面是0x40     
.equ   PI, 31415

等价于:

#define  PI  3.1415

3.10、.if、.else/.endif

使用这个(.if、.else/.endif)语句,根据一个表达式的值来决定是否要编译下面的代码, 用.endif伪操作来表示条件判断的结束,中间可以使用.else来决定.if的条件不满足的情况下应该编译哪一部分代码,可以理解与 C 语言的条件编译效果一样;

基本语法结构为:

.if  logical-expressing
  ……
.else
  ……
.endif

举例:

.if USE_MMU==1
MOV R1, R2
.endif

.if/.else 的用法还有一些变体,比如:

.ifdef,.else 和 endif

基本上和 C 语言中条件编译用法一样;

3.11、.rept、.endr

使用 rept 和 endr 来表示重复操作。语法如下:
 

.rept 重复次数  
  数据定义 
.endr       @结束重复定义 

举例:

.rept 5 
.byte 0x01 
.endr

3.12、.extern

用于声明一个外部符号,用于兼容性其他汇编,语法为:

.extern label 

当然,还有部分其他伪操作、伪指令的定义,可以参考更多的前面提到的手册;

参考文档:

https://www.iteye.com/blog/xp9802-2012231

什么?Arm放弃了自家的汇编语法?改投GNU了? - 云+社区 - 腾讯云

GNU风格 ARM汇编语法指南(非常详细)_xztelecomlcs_51CTO博客

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值