ATT汇编
AT&T汇编和Intel汇编,是两种不同汇编语⾔格式,与具体CPU关系不⼤,只是Intel汇编格式基本只⽤在⾃家的x86系列CPU上,⽽AT&T汇编格式在多种CPU 上都可以使⽤(x86,power,VAX等等)。
在阅读内核源代码的时候,必须先掌握汇编,⼤家都知道,内核代码⽤的编译器是gcc,⽽gcc采⽤的是AT&T的汇编格式,与MS的intel有些区别。
⼀ AT&T的基本语法
语法上主要有以下⼏个不同.
★ 寄存器命名原则
AT&T: %eax Intel: eax
★ 源/⽬的操作数顺序
AT&T: movl %eax,%ebx Intel: mov ebx,eax
★ 常数/⽴即数的格式
AT&T: movl $_value,%ebx Intel: mov eax,_value
把_value的地址放⼊eax寄存器
AT&T: movl $0xd00d,%ebx Intel: mov ebx,0xd00d
★ 操作数长度标识
AT&T: movw %ax,%bx Intel: mov bx,ax
l ,w,b是AT&T汇编中⽤来表⽰操作属性的限定符
l是长字(4字节),
w是双字
b是⼀个字节
加在指令的后边
相当于intel中的
dword ptr
word ptr
byte ptr
⽐如:
subl $8, %esp
leal -792(%ebp), %eax
pushl %eax
movl -796(%ebp), %eax
sall $8, %eax
addl 12(%ebp), %eax
pushl %eax
call _strcpy
addl $16, %esp
在intel 汇编中就相当于:
sub esp,8
lea eax,dword ptr [ebp-792]
push eax
mov eax,dword ptr [ebp- 796]
...
★寻址⽅式
AT&T: immed32(basepointer,indexpointer,indexscale)
Intel: [basepointer + indexpointer*indexscale + imm32)
Linux⼯作于保护模式下,⽤的是32位线性地址,所以在计算地址时 不⽤考虑segment:offset的问题.上式中的地址应为:
imm32 + basepointer + indexpointer*indexscale
下⾯是⼀些例⼦:
★直接寻址
AT&T: _booga ; _booga是⼀个全局的C变量
注意加上$是表⽰地址引⽤,不加是表⽰值引⽤.
注:对于局部变量,可以通过堆栈指针引⽤.
Intel: [_booga]
★寄存器间接寻址
AT&T: (%eax)
Intel: [eax]
★变址寻址
AT&T: _variable(%eax)
Intel: [eax + _variable]
AT&T: _array(,%eax,4)
Intel: [eax*4 + _array]
AT&T: _array(%ebx,%eax,8)
Intel: [ebx + eax*8 + _array]
⼆ 基本的⾏内汇编
基本的⾏内汇编很简单,⼀般是按照下⾯的格式
asm("statements");
例如:asm("nop"); asm("cli");
asm 和 __asm__是完全⼀样的.
如果有多⾏汇编,则每⼀⾏都要加上 "/n/t"
例如:
asm( "pushl %eax/n/t"
"movl $0,%eax/n/t"
"popl %eax");
实际上gcc在处理汇编时,是要把asm(...)的内容"打印"到汇编
⽂件中,所以格式控制字符是必要的.
再例如:
asm("movl %eax,%ebx");
asm("xorl %ebx,%edx");
asm("movl $0,_booga);
在上⾯的例⼦中,由于我们在⾏内汇编中改变了edx和ebx的值,但是 由于gcc的特殊的处理⽅法,即先形成汇编⽂件,再交给GAS去汇编, 所以GAS并不知道我们已经改变了edx和ebx的值,如果程序的上下⽂ 需要edx或ebx作暂存,这样就会引起严重的后果.对于变量_booga也 存在⼀样的问题.为了解决这个问题,就要⽤到扩展的⾏内汇编语法. 三 扩展的⾏内汇编
扩展的⾏内汇编类似于Watcom.
基本的格式是:
asm ( "statements" : output_regs : input_regs : clobbered_regs); clobbered_regs指的是被改变的寄存器.
下⾯是⼀个例⼦(为⽅便起见,我使⽤全局变量):
int count=1;
int value=1;
int buf[10];
void main()
{
asm(
"cld /n/t"
"rep /n/t"
"stosl"
:
: "c" (count), "a" (value) , "D" (buf[0])
: "%ecx","%edi" );
}
得到的主要汇编代码为:
movl count,%ecx
movl value,%eax
movl buf,%edi
#APP
cld
rep
stosl
#NO_APP
cld,rep,stos就不⽤多解释了.
这⼏条语句的功能是向buf中写上count个value值.
冒号后的语句指明输⼊,输出和被改变的寄存器.
通过冒号以后的语句,编译器就知道你的指令需要和改变哪些寄存器, 从⽽可以优化寄存器的分配.
其中符号"c"(count)指⽰要把count的值放⼊ecx寄存器
类似的还有:
a eax
b ebx
c ecx
d edx
S esi
D edi
I 常数值,(0 - 31)
q,r 动态分配的寄存器
g eax,ebx,ecx,edx或内存变量
A 把eax和edx合成⼀个64位的寄存器(use long longs)
我们也可以让gcc⾃⼰选择合适的寄存器.
如下⾯的例⼦:
asm("leal (%1,%1,4),%0"
: "=r" (x)
: "0" (x) );
这段代码实现5*x的快速乘法.
得到的主要汇编代码为:
movl x,%eax
#APP
leal (%eax,%eax,4),%eax
#NO_APP
movl %eax,x
⼏点说明:
1.使⽤q指⽰编译器从eax,ebx,ecx,edx分配寄存器.
使⽤r指⽰编译器从eax,ebx,ecx,edx,esi,edi分配寄存器.
mmap格式怎么打开2.我们不必把编译器分配的寄存器放⼊改变的寄存器列表,因为寄存器
已经记住了它们.
3."="是标⽰输出寄存器,必须这样⽤.
4.数字%n的⽤法:
数字表⽰的寄存器是按照出现和从左到右的顺序映射到⽤"r"或"q"请求
的寄存器.如果我们要重⽤"r"或"q"请求的寄存器的话,就可以使⽤它们. 5.如果强制使⽤固定的寄存器的话,如不⽤%1,⽽⽤ebx,则
asm("leal (%%ebx,%%ebx,4),%0"
: "=r" (x)
: "0" (x) );
注意要使⽤两个%,因为⼀个%的语法已经被%n⽤掉了.
下⾯可以来解释letter 4854-4855的问题:
1、变量加下划线和双下划线有什么特殊含义吗?
加下划线是指全局变量,但我的gcc中加不加都⽆所谓.
2、以上定义⽤如下调⽤时展开会是什么意思?
#define _syscall1(type,name,type1,arg1) /
type name(type1 arg1) /
{ /
long __res; /
/* __res应该是⼀个全局变量 */
__asm__ volatile ("int $0x80" /
/* volatile 的意思是不允许优化,使编译器严格按照你的汇编代码汇编*/ : "=a" (__res) /
/* 产⽣代码 movl %eax, __res */
: "0" (__NR_##name),"b" ((long)(arg1))); /
/* 如果我没记错的话,这⾥##指的是两次宏展开.
即⽤实际的系统调⽤名字代替"name",然后再把__NR_...展开.
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论