我是靠谱客的博主 安详石头,最近开发中收集的这篇文章主要介绍GCC汇编器语法(转载)ARM的ADS汇编器与GCC汇编器,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

转自:

http://argcandargv.com/articles/84.c

Permalink  :  http://argcandargv.com/articles/84.c 

原文地址:http://blogold.chinaunix.net/u1/56757/showart_1815842.html

作者:老蕊

由于Linux内核代码使用GCC汇编器语法,如果可以了解的话,对理解内核有一定帮助。我结合了一些资料总结了一下。由于本人初次研究且水平有限,涉及到的可能只是很简单的一部分,希望各位大虾拍砖。

A. GCC汇编器语法

GCC编译器使用AT&T语法。它与Intel语法有几个主要的不同点:

1. 操作数的目的和源位置颠倒

在Intel语法中,第一个操作数是目的,而第二个操作数是源;而AT&T则正好相反

2. 寄存器命名

在AT&T语法中,寄存器被加上了%前缀,写为%eax等

3. 立即操作数

在AT&T语法中,立即操作数往往需要$前缀,对于16进制的数字加上0x前缀,例如$0x4F;

在Intel语法中往往是4fh,用'h'作为后缀

4. 操作数大小

AT&T语法中,操作数占内存大小决定于汇编命令操作符的最后一个字符的内容。 操作符以'b', 'w'和 'l'为后缀指明内存访问长度是byte(8-bit), word(16-bit)还是long(32-bit). 而Intel语法在操作数前加上'byte ptr','word ptr'和'dword ptr'的内存操作数(这个操作数不是汇编命令操作符)来达到相同目的.

因此, Intel

1
mov al, byte ptr foo

用AT&T语法就是 :

1
movb foo, %al

5. 内存操作数

在Intel的语法中,基址寄存器用'['和']'扩起来,但是在AT&T中改用'('和')'。 此外,在Intel语法中一个间接内存寻址:

1
section:[base + index * scale + disp]

在AT&T中则为:

1
section:disp(base, index, scale)

总是需要记住的一点就是,当一个常数被用作disp或者scale时,就不用加'$'前缀。

对比的例子:

Intel CodeAT&T code
mov eax,1mov $1,%eax
mov ebx,0ffhmov $0xff,%ebx
int 80hint $0x80
mov ebx,eaxmovl %eax,%ebx
mov eax,[ecx]movl (%ecx),%eax
mov eax,[ebx+3]movl 3(%ebx),%eax
mov eax,[ebx+20h]movl 0x20(%ebx),%eax
add eax,[ebx+ecx*2h]addl (%ebx, %ecx, 0x2),%eax
lea eax,[ebx+ecx]leal (%ebx,%ecx),%eax
sub eax,[ebx+ecx*4h-20h]subl -0x02(%ebx, %ecx, 0x04),%eax

B. 基本形式的内联汇编

以下2种都合法:

1
2
__asm__( "assembly code" );
asm( "assembly code" );

如果我们有多余一条的指令,可以分行写,每行要加上"",每个指令末尾添加/n/t,这是因为GCC将每行指令作为一个字符串传给as(GAS),使用换行和TAB可以给汇编器传送正确的格式化好的代码行。

C. 扩展形式的内联汇编

前面介绍的基础形式的内联汇编方法只涉及到嵌入汇编指令。在高级形式中,我们将可以指定操作数,它允许我们指

定输入输出寄存器(内联函数使用这些寄存器作为存储输入输出变量)和程序中涉及到的clobbered寄存器列表(clobbered

registers:内联汇编程序可能要改变其内容的寄存器)也并不是一定要要显式指明使用具体的寄存器,我们也可以把它留给GCC去选择,这样GCC还可能更好的进行优化处理。高级内联汇编的基本格式如下:

1
2
3
4
5
asm ( assembler template
     : output operands /* optional */
     : input operands /* optional */
     : list of clobbered registers /* optional */
     );

其中assembler template包含汇编指令部分。括号中每个操作数用C表达式常量串描述。不同部分之间用冒号分开。相同部分中的每个小部分用逗号分开。

例如:

1
2
3
4
5
6
7
int a=10, b;
asm ( "movl %1, %%eax;
     movl %%eax, %0;"
     : "=r" (b) /* output */
     : "r" (a) /* input */
     : "%eax" /* clobbered register */
     );

上面代码所做的就是用汇编代码把a的值赋给b。值得注意的几点有:
a) "b"是输出操作数,用%0来访问,”a”是输入操作数,用%1来访问。
b) "r" 是一个constraint, 关于constraint后面有详细的介绍。这里我们只要记住这里constraint "r"让GCC自己选择一个寄存器去存储变量a。输出部分的constraint前必须要有个"=",用来说明是一个这是一个输出操作数,并且只写。
c) 你可能看到有的寄存器名字前面写了两个%,这是用来帮助GCC区分操作数和寄存器。操作数只需要一个%前缀。
d) 在第三个冒号后面的clobbered register,%eax说明在内联汇编代码中将要改变eax中的内容,GCC不要用它存储其他值。

当这段代码执行结束后,"b"的值将会被改掉,因为它被指定作为输出操作数。换句话说,在"asm"内部对b的改动将影响到asm外面.

D. 汇编模板

每条指令放在一个双引号内,或者将所有的指令都放着一个双引号内。每条指令都要包含一个分隔符。合法的分隔符是换行符(/n)或者分号。用换行 符的时候通常后面放一个制表符"/t"。我们已经知道为什么使用换行符+制表符了(前面部分有解释)。其中,访问C操作数用%0,%1等等

E. 操作数

C语言表达式(大多情况是C变量)将作为"asm"内部使用的操作数。每一个操作数都以双引号开始。对于输出操作数,还要写一个修改标志(=)。constraint和修改标志都放在双引号内。接下来部分就是C表达式了(放在括号内)。

标准形式如下:

1
"constraint" (C expression) [ 如: "=r" (result) ]

对于输出操作数还有一个修改标志(=)。 constraint主要用来指定操作数的寻址类型(内存寻址或寄存器寻址),也用来指明使用哪个寄存器。如果输出表达式不能直接寻址(比如是bit-field),constraint就必须指定一个寄存器.这种情况下,GCC将使用寄存器作为asm的输出。

现在我们来看一些例子,把一个数字乘以5使用汇编指令lea

1
2
3
4
asm ( "leal (%1,%1,4), %0"
     : "=r" (five_times_x)
     : "r" (x)
     );

这里输入操作数是'x',不指定具体使用那个寄存器,GCC会自己选择输入输出的寄存器来操作。如果我们也可以让GCC把输入和输出寄存器限定同一个。只需要使用读写操作数,使用合适的constraint,看下具体方法:

1
2
3
4
asm ( "leal (%0,%0,4), %0"
     : "=r" (five_times_x)
     : "0" (x)
     );

上面使输入和输出操作数存在相同的寄存器中,我们不知道GCC具体使那个寄存器,但是我们也可以指定一个,像这样:

1
2
3
4
asm ( "leal (%0,%0,4), %0"
     : "=c" (five_times_x)
     : "c" (x)
     );

F. Clobber List

一些指令破坏了一个寄存器值,我们就不得不在asm里面第三个冒号后的Clobber List中标示出来,通知GCC这个里面的值要被改掉。这样GCC将不再假设之前存入这些寄存器中的值是合法的了。我们不需要把输入输出寄存器在这个部分 标出,因为GCC知道asm将使用这些寄存器。(因为它们已经显式的被作为输入输出标出)。如果此外指令中还用到其他寄存器无论显示还是隐式的使用到(没 有在输入输出中标示出的),这些指令必须在clobbered list中标明。

如果指令中以不可预见形式修改了内存值,要加上"memory"到clobbered list中。这使得GCC不去缓存在这些内存值。还有,如果内存被改变而没有被列在输入和出部分 要加上volatile关键字。

如果汇编代码必须在我们放的位置被执行(例如不能被循环优化而移出循环),那就在asm之后()之前,放一个valatile关键字。 这样可以禁止这些代码被移动或删除。

G. 常用constraints

寄存器操作数constraint(r)

constraints "r"被指定时,GCC可能在任何一个可用的通用寄存器中保存这个值。当然如果你要指定具体使用那个寄存器就要指定具体使用哪个寄存器的

1
2
3
4
5
6
7
8
9
10
+---+--------------------+
| r | Register(s)        |
+---+--------------------+
| a | %eax, %ax, %al     |
| b | %ebx, %bx, %bl     |
| c | %ecx, %cx, %cl     |
| d | %edx, %dx, %dl     |
| S | %esi, %si          |
| D | %edi, %di          |
+---+--------------------+

内存操作数constraint(m)

当操作数在内存中时,任何对其操作将直接通过内存地址进行。和寄存器constraint相反,内存操作是先把值存在一个寄存器中,修改后再将值回写到这 个内存地址。寄存器constraint通常只用在对速度要求非常严格的场合。因为内存constraint可以更有效率的将一个C语言变量在asm中跟 新[不需要寄存器中转],而且可能你也不想用一个寄存器来暂存这个变量的值。

匹配constraint

某些情况下,一个变量可能用来保存输入和输出两种用途。这种情况下我们就用匹配constraint

1
asm ( "incl %0" : "=a" (var) : "0" (var));

这个例子中eax寄存器被用来保存输入也用来保存输出变量。输入变量被读入eax中,incl执行之后eax被跟新并

且又保存到变量var中。这儿的constraint "0"指定使用和第一个输出相同的寄存器。就是说,输入的变量应该只能放在eax中。这个constraint可以在下面的情况下被使用:

a) 输入值从一个变量读入,这个变量将被修改并且修改过的值要写回同一个变量;
b) 没有必要把输入和输出操作数分开。

其他constranints

a) "m": 使用一个内存操作数,内存地址可以是机器支持的范围内
b) "o": 使用一个内存操作数,但是要求内存地址范围在在同一段内 。例如,加上一个小的偏移量来形成一个可用的地址
c) "V": 内存操作数,但是不在同一个段内。换句话说,就是使用"m"的所有的情况除了"o"
d) "i": 使用一个立即整数操作数(值固定);也包含仅在编译时才能确定其值的符号常量
e) "n": 一个确定值的立即数。很多系统不支持汇编时常数操作数小于一个字。这时候使用n就比使用i好
f) "g": 除了通用寄存器以外的任何寄存器,内存和立即整数

在使用constraint的时候,为了更精确的控制约束,GCC提供了一些修改标记,常用的修改标记有:
a) "="指这个输出操作数是只写的;之前保存在其中的值将废弃而被输出值所代替。
b) "&"用于对输出操作数的修饰。当使用它进行修饰时,等于向GCC声明:"GCC不得为任何输入操作数分配与此输出操作数相同的寄存器"。其原因是&修饰符意味着被其修饰的输出操作数要在所有的输入操作数被输入前输出。

(转载)ARM的ADS汇编器与GCC汇编器

汇编器与指令集,不同的CPU对应不同的指令集;不同的汇编器对应不同的语法和伪指令集。

每种汇编器都可以有自己的伪指令集和自己的语法,但实际上,由于事实标准的原因,(所有的CPU厂商会提供指令使用手册,手册中的指令书写样式,实际上就是事实汇编语法,何况CPU厂商肯定会提供自己的汇编器的),现代大多数的汇编器都会在主流的汇编语法Intel型和AT&T型这两类语法中选择一种作为自己的语法(Intel型可能会越来越流行)。

ARM官方提供的汇编器/指令手册中使用的是Intel型语法,GNU的汇编器gas使用的是AT&T型语法。

然而,gas for arm版的开发者,为了避免混乱,实现了完全和ARM官方汇编语法是一致的语法。也就是说,当你使用ARM指令集的指令时,按ARM官方手册的语法书写,用gas for arm编译完全没有问题。主要要注意的是伪指令集。

ARM的ADS汇编器与GCC汇编器 (2008-03-19 08:49:14)
一:ads下的一段汇编程序:
__main 
EXPORT  BootReset 
BootReset 
  resetvec_reqset 
  IMPORT  BootEntry 
  IMPORT  |Image$$RO$$Limit|

  AREA  BOOTROM,  CODE,  READONLY 
  LDR  r0,  =|Image$$RO$$Limit| 
  BEQ  %1 
  ldr  pc,  [pc,#-&F20]
转换到gcc下的汇编程序为:

__main 
.global BootReset 
BootReset: 
 resetvec_reqset 

 .extern BootEntry 

 .extern Image_RO_Limit 

AREA BOOTROM, CODE, READONLY 
 LDR r0, =Image_RO_Limit 

 BEQ FUNC1 
 ldr pc, [pc,#-0xF20]

二:将ARM SDT下的汇编码移植到GCC for ARM编译器时,经常要做如下修改:
1、注释行以“@”或""代替“;”
2、伪操作符替换:
INCLUDE 替换成 .INCLUDE
TCLK2 EQU PB25 替换成 .equ TCLK2, PB25
EXPORT 替换成 .global
IMPORT 替换成 .extern
DCD 替换成 .long
IF :DEF: 替换成 .IFDEF
ELSE 替换成 .ELSE
ENDIF 替换成 .ENDIF
:OR: 替换成 |
:SHL: 替换成 <<
END 替换成 .end


符号定义后加":"号
AREA Word, CODE, READONLY --> .text
AREA Block, DATA, READWRITE --> .data
CODE32 --> .arm
CODE16 --> .thumb
LTORG --> .ltorg


3、操作数及运算符号替换
ldr pc, [pc, #&18] 替换成 ldr pc, [pc, #+0x18]
“&”以“+0x”号替换

三:ARM GCC汇编中常量编译控制

编译定义符

说 

语 

例 

.byte

字节定义 expr(8bit数值)

.byte expr {, …}

.byte 25, 0x11, 031, 'A

.hword

半字定义expr (16bit数值)

.hword expr {, …}

.hword 2, 0xFFE0

.short

作用同.hword

.short expr {, …}

.short 257

.word

字长定义expr (32bit数值)

.word expr {, …}

.word 144511, 0x11223

.int

作用同.word

.int expr {, …}

.int 21

.long

作用同.word

.long expr {, …}

.long 1923, 0b10010101

.ascii

定义字符串expr(非零结束符)

.ascii expr {, …}

.ascii "Ascii text is here"

.asciz

定义字符串expr(以0为结束符)

.asciz expr {, …}

.asciz "Zero Terminated Text"

.string

作用同 .asciz

.string expr {, …}

.string "My Cool Stringn"

.quad

定义一个大的数expr (向上分成8bit的数存放)

.quad expr {, …}

.quad 0xDAFADAFA911

.octa

定义一个大的数expr(向上分成16bit的数存放)

.octa expr {, …}

.octa 0xFEDCBA987654321

.float

定义一个32bit IEEE 浮点数expr

.float expr {, …}

.float 0f3.14, 0f359.2e11

.single

作用同.float

.single expr {, …}

.single 0f12341243.14E2

.double

定义64bit IEEE浮点数expr(浮点数)

.double expr {, …}

.double 0f2E1

.fill

size长度value填充repeat次。size缺省为1, value缺省为 0.

.fill repeat {, size}

{, value}

.fill 32, 4, 0xFFFFFFFF


AT&T汇编语言与GCC汇编简介

http://www.cnblogs.com/heiyue/archive/2011/10/27/2226570.html


最后

以上就是安详石头为你收集整理的GCC汇编器语法(转载)ARM的ADS汇编器与GCC汇编器的全部内容,希望文章能够帮你解决GCC汇编器语法(转载)ARM的ADS汇编器与GCC汇编器所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(37)

评论列表共有 0 条评论

立即
投稿
返回
顶部