GCC,linux的GNU C编译器使用AT&T/UNIX汇编语法。
(一):AT&T汇编和intel汇编的不同
1:前缀
在intel语法中,寄存器和立即数都没有前缀,而在AT&T中,寄存器使用前缀“%”,而立即数前面使用前缀“$”;
在intel语法中,十六进制和二进制立即数后面缀以”h”和“b”,但在AT&T语法中,在前面缀以”0x”,
2:操作数的方向不同
AT&T和intel汇编操作数的方向正好相反。在intel语法中,第一个操作数是目的操作数,第二个操作数是源操作数,
而在AT&T语法中,第一个操作数是源操作数,第二个是目的操作数
例如:
在 intel语法中: mov eax,[ecx];
在AT&T语法中,movl (%ecx),%eax;
3:内存单元操作数
从上面的例子可以看出,内存操作数也是不同的,在intel语法中,基址寄存器用”[]”扩起来,而在AT&T中,使用”()”括起来
例如:
在intel语法中 mov eax,[ecx+5];
在AT&T语法中 movl 5(%ecx),%eax;
4:间接寻址方式
intel语法的间接寻址指令格式为seggreg:[base+index*scale+disp],而在AT&T语法中,间接寻址的指令格式为
%seggreg:disp(base,index,scale)
很明显,AT&T语法稍微有些难懂,这种寻址方式通常用在访问数据结构数组中某个元素内的一个字段,base为数组的起始地址,index为下表,
scale为每一个数组元素的大小,如果数组元素还是一个结构,则disp为具体字段在结构中的位移。
5:操作码的后缀
在上面的例子中还能看到,AT&T的操作码后面还有一个后缀,用来表示操作码的大小,”l”表示长整数,32位;”w“表示”字“,16位;”b“表示
字节,8位,而在intel语法中,则要在内存单元操作数中加上byte ptr,word ptr和dword ptr,其中dword代表“long”。
(二):AT&T汇编的相关知识
1:GNU汇编程序GAS
当编写完一段程序之后,需要对其进行汇编和链接,在linux下有两种方式,第一种就是使用汇编程序GAS和链接程序ld;第二种就是
使用gcc。首先学习一个GAS和ld的使用;
GAS将汇编语言源文件(.s)转换为目标文件(.o),基本语法为
as finame.s -o filename
ld将目标文件转换成可执行文件
ld finame.o -o filename
如果使用gcc的话,应该要这样写:
gcc filename.S -o filename //其中的扩展名一定要大写,gcc可以自动识别汇编程序中的C预处理指令
2:AT&T中的节(Section)
AT&T语法,一个节由关键字.section来识别,编写汇编语言的时候,至少需要以下三种节
.section .data : 这种节包含程序已初始化的数据,即包含具有初始值的变量,例如
.section data
hello : .string “hello world\n”
hello_len : .long 13
.section .bss : 这种节包含程序还未初始化的数据,也就是说,包含没有初值的变量,当操作系统将
装入这个程序的时候,这些变量都将变为0。例如
.section bss
name :.fill 30
name_len : .long 0
如果,一不小心在bss节中给变量赋值了,这个变量的值初始化的时候也会变成0.
注意:使用bss节相比使用data节的优势在于,使用bss节不占用磁盘空间,一个长整数就足以存放.bss节,
当程序装入内存的时候,操作系统也只分配给这个节4字节的大小。
编译程序把.data和.bss节在4字节上对齐,例如 .data总共有34字节,那么编译器将他对齐在36字节上。
.section .text : 这个字节包含程序的代码,他是只读节,而.data和.bss是读写节
3:汇编程序指令
上面的.section就是汇编程序指令的一种,GNU汇编程序提供了很多这样的汇编指令,这种指令都是一句点
开头的,后面跟指令名(小写字母)。
(1)、.ascii “string”…
.ascii表示零个或多个(用逗号隔开)字符串,并把每个字符串(结尾不自动加0字节)中的字符放在连续的地址单元。
还有一个与.ascii相似的.asciz,z代表0,即每个字符串的结尾自动加上一个“0”字节。
(2)、.byte表达式
.byte表达式表示0个或者是多个表达式,用逗号隔开,每个表达式被放在下一个字节单元中
(3)、.fill表达式
形式: .fill repeat,size,value
含义是反复拷贝repeat次size个字节,并以value填充
例如:
.fill NR_CPUS*4,8,0;
拷贝4次8个字节大小,并以0填充。
size和value为可选项,如果value值不存在,则默认为0,如果size不存在,则默认为1
(4)、.global symbol //相当于定义全局符号
.global 使得连接程序能够看得到symbol,如果你的局部程序中定义了symbol,那么与这个局部程序连接的其他局部
程序也能存取symbol.
例如:
.global SYMBOL_NAME(idt)
.globol SYMBOL_NAME(gdt)
定义idt和gdt全局变量
(5)、quad bugnums
.quad 表示零个或多个bignums,用逗号分隔,对于每一个bignums,缺省值是8个字节,如果超过8个字节,则会打印一个
警告信息,并只取bugnums最低的8个字节
例如,对于全局描述符的填充就是用到这个指令
.quad 0x00cf9a000000ffff /* 0x10 kernel 4G code at 0x00000000 */
(6)、.rept count
把.rept指令与.endr指令之间的行重复count次,如
.rept 3
.long 0
.endr
相当于:
.long 0
.long 0
.long 0
(7)、space size,fill
这个指令保留size个字节的空间,每个字节值为fill,如果逗号和fill被省略,则fill默认为0
.space 1024
表示保留1024个字节的空间,并且每个字节的值为0
(8)、.word expression
这个表达式表示任意一节中的一个或多个表达式,用逗号隔开,表达式的值占两个字节
例如
gdt_descr:
.word GDT_ENTRIES*8-1
变量gdt_descr的值置为GDT_ENTRIES*8-1
(9)、.long expression
与.word相似,不过这里是4个字节
(10)、.org new_lc,fill
把当前节的位置计数器提前到new_lc,new_lc或者是一个常量表达式,或者是一个与当前子节处于同一节的表达式。
也就是说.org不能横跨节,如果new_lc是个错误的值,则.org被忽略,.org只能增加位置计数器的值或者是保持不变,
但是绝不能让位置计数器的值倒退
例如:.org 0x200
ENTRY(pg0);
表示把位置计数器置为0x200,这个位置存放的就是临时页表pg0
(三):GCC嵌入式汇编
在linux内核源代码中,有很多C语言的函数中嵌入一段汇编语言执行段,这就是GCC提供的asm功能。
例如:
#deine read_cr0() ({ unsigned int __dummy; __asm__( "movl %%cr0,%0\n\t" \
: "=r"(__dummy)); __dummy; })
读取寄存器CR0的一个宏。
这并不是标准C所定义的形式,而是GCC对C语言的扩充,一般情况下,嵌入式汇编语言要比单纯的汇编语言代码复杂的多,
因为这里存在怎样分配和使用寄存器,以及把代码中的变量应该存放在哪个寄存器中。
1:嵌入式语言的一般形式
__asm__ [__volatile__]("<asm_route>":output:input:modify);
其中,asm表示汇编程序的开始,而volatile 是可选的,避免asm指令被删除,移动或者是组合。
“”是汇编指令部分,例如,”movl %%cr0,%0\n\t”。数字前加前缀”%”,如%1,%2表示使用
寄存器的样板操作数。可以使用的操作数的总数取决于具体的CPU中通用寄存器的数量。由于在这些样板操作数
的前缀使用了%,因此,在用到具体的寄存器是就在前面加两个%,如%%cr0.
输出部分:
用以规定输出变量如何与寄存器结合的约束,输出部分可以有多个约束,互相以逗号分开。每个约束以“=”
开头,接着用一个字母来表示操作数的类型,然后是关于变量结合的约束。例如:
“=r”(__dummy)
“=r”表示相应的目标操作数,也就是指令部分的%0,可以使用任何一个通用寄存器,并且变量__dummy
存放在这个寄存器中。
但如果是”=m”就表示相应的目标操作数就存放在内存单元__dummy中
r:I/O,表示使用一个通用寄存器,由GCC在%eax/%ax/%al、%ebx/%bx/%bl、%ecx/%cx/%cl、%edx/%dx/%dl中选取一个GCC认为是合适的;
q:I/O,表示使用一个通用寄存器,与r的意义相同;
g:I/O,表示使用寄存器或内存地址;
m:I/O,表示使用内存地址;
a:I/O,表示使用%eax/%ax/%al;
b:I/O,表示使用%ebx/%bx/%bl;
c:I/O,表示使用%ecx/%cx/%cl;
d:I/O,表示使用%edx/%dx/%dl;
D:I/O,表示使用%edi/%di;
S:I/O,表示使用%esi/%si;
f:I/O,表示使用浮点寄存器;
t:I/O,表示使用第一个浮点寄存器;
u:I/O,表示使用第二个浮点寄存器;
A:I/O,表示把%eax与%edx组合成一个64位的整数值;
o:I/O,表示使用一个内存位置的偏移量;
V:I/O,表示仅仅使用一个直接内存位置;
i:I/O,表示使用一个整数类型的立即数;
n:I/O,表示使用一个带有已知整数值的立即数;
F:I/O,表示使用一个浮点类型的立即数;
输入部分:输入部分与输出部分相似,但没有”=”,如果输入部分的一个操作数所要求使用的寄存器
与前面输出部分某个约束所要求的是同一个寄存器,那就把对应操作数的编号放在约束条件中。
修改部分:这部分常常以”memory”为约束条件,以表示操作完成后,内存中的内容已有改变,如果原来
某个寄存器的内容来自内存,那么现在内存中这个单元的内容已经改变。
注意,指令部分是必选项,输出部分,输入部分以及修改部分为可选项,如果输入部分存在,而输出
部分不存在的话,冒号“:”要保留,如果”memory”存在的时候,三个分号都要保留。