ATT汇编.docx
- 文档编号:25847753
- 上传时间:2023-06-16
- 格式:DOCX
- 页数:16
- 大小:22.65KB
ATT汇编.docx
《ATT汇编.docx》由会员分享,可在线阅读,更多相关《ATT汇编.docx(16页珍藏版)》请在冰豆网上搜索。
ATT汇编
昨晚和同学讨论c语言的问题时,有些东西需要看一下对应的汇编代码。
期间遇到有的指令需要查手册时,发现实际代码中的操作数顺序和手册中介绍的不一样。
当时没怎么想就过去了,事后才想起来这是AT&T汇编与Intel汇编的不同。
不由得又感叹了一下自己的记忆力,因为一年多前还看过这方面的资料的,当时要用到gcc内联汇编的语法还专门看过AT&T汇编的。
用gcc-S和objdump-D得到的汇编代码都是AT&T汇编(估计是GNU开发这些工具的时候选择的是AT&T汇编,与Linux本身应该没有关系,因为Linux源代码中都是Intel汇编),它与Intel汇编有一些差别,不过影响不大,概括一下:
前缀
在Intel汇编中没有寄存器前缀或者立即数前缀。
而在AT&T汇编中寄存器有一个“%”前缀,立即数有一个“$”前缀。
如,
movl $1,%eax
movl $0xff,%ebx
int $0x80
操作数的用法
intel语句中操作数的用法和AT&T中的用法相反。
在AT&T语句中第一个操作数表示源而第二个操作数表示目的,从上面的例子可以看出。
个人觉得这样比较自然。
(忘了原来学过的Arm汇编是不是这样的了。
)
存储器操作数
如同上面所看到的,存储器操作数的用法也不相同。
在Intel语句中基址寄存器用“[”和“]”括起来而在AT&T语句中是用“(”和“)”括起来的。
例如,
IntexSyntax
mov eax,[ebx]
mov eax,[ebx+3]
AT&TSyntax
movl (%ebx),%eax
movl 3(%ebx),%eax
AT&T语法中用来处理复杂的操作的指令的形式和Intel语法中的形式比较起来要简洁,不过可读性差点。
在Intel语句中这样的形式是segreg:
[base+index*scale+disp]。
在AT&T语句中这样的形式是%segreg:
disp(base,index,scale)。
在AT&T语句中用作scale/disp的立即数不要加“$”前缀。
后缀
AT&T语法中有一个后缀,它的意义是表示操作数的大小。
“l”代表long,“w”代表word,“b”代表byte。
对于程序员来说似乎有些多余,可能是movb/movw/movl等对应的机器代码不一样,开发汇编器的人就搞了不同的汇编指令出来?
movb %bl,%al
movw %bx,%ax
movl %ebx,%eax
movl (%ebx),%eax
gcc采用的是AT&T的汇编格式,MS采用Intel的格式.
一 基本语法
语法上主要有以下几个不同.
★寄存器命名原则
AT&T:
%eaxIntel:
eax
★源/目的操作数顺序
AT&T:
movl%eax,%ebxIntel:
movebx,eax
★常数/立即数的格式
AT&T:
movl$_value,%ebxIntel:
moveax,_value
把_value的地址放入eax寄存器
AT&T:
movl$0xd00d,%ebxIntel:
movebx,0xd00d
★操作数长度标识
AT&T:
movw%ax,%bxIntel:
movbx,ax
★寻址方式
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的特殊的处理方法,即先形
、大小写
INTEL格式的指令使用大写字母,而AT&T格式的使用小写字母。
例:
INTEL AT&T
MOVEAX,EBX movl%ebx,%eax
2、操作数赋值方向
在INTEL语法中,第一个表示目的操作数,第二个表示源操作数,赋值方向从右向左。
AT&T语法第一个为源操作数,第二个为目的操作数,方向从左到右,合乎自然。
例:
INTEL AT&T
MOVEAX,EBX movl%ebx,%eax
3、前缀
在INTEL语法中寄存器和立即数不需要前缀;AT&T中寄存器需要加前缀“%”;立即数
需要加前缀“$”。
例:
INTEL AT&T
MOVEAX,1 movl$1,%eax
符号常数直接引用,不需要加前缀,如:
movlvalue,%ebx,value为一常数;在符
号前加前缀$表示引用符号地址,如movl$value,%ebx,是将value的地址放到ebx中。
总线锁定前缀“lock”:
总线锁定操作。
“lock”前缀在Linux核心代码中使用很多,特
别是SMP代码中。
当总线锁定后其它CPU不能存取锁定地址处的内存单元。
远程跳转指令和子过程调用指令的操作码使用前缀“l“,分别为ljmp,lcall,与之
相应的返回指令伪lret。
例:
INTEL AT&T
CALLFARSECTION:
OFFSET lcall$secion:
$offset
JMPFARSECTION:
OFFSET ljmp$secion:
$offset
RETFARSATCK_ADJUST lret$stack_adjust
4、间接寻址语法
INTEL中基地址使用“[”、“]”,而在AT&T中使用“(”、“)”;另外处理复杂操作数的
语法也不同,INTEL为Segreg:
[base+index*scale+disp],而在AT&T中为
%segreg:
disp(base,index,sale),其中segreg,index,scale,disp都是可选的,在指定
index而没有显式指定Scale的情况下使用默认值1。
Scale和disp不需要加前缀“&”。
INTEL AT&T
Instr instr
foo,segreg:
[base+index*scale+disp] %segreg:
disp(base,index,scale),foo
5、后缀
AT&T语法中大部分指令操作码的最后一个字母表示操作数大小,“b”表示byte(一个
字节);“w”表示word(2个字节);“l”表示long(4个字节)。
INTEL中处理内存操作数
时也有类似的语法如:
BYTEPTR、WORDPTR、DWORDPTR。
例:
INTEL AT&T
moval,bl movb%bl,%al
movax,bx movw%bx,%ax
moveax,dwordptr[ebx] movl(%ebx),%eax
在AT&T汇编指令中,操作数扩展指令有两个后缀,一个指定源操作数的字长,另一个
指定目标操作数的字长。
AT&T的符号扩展指令的为“movs”,零扩展指令为“movz”(相应
的Intel指令为“movsx”和“movzx”)。
因此,“movsbl%al,%edx”表示对寄存器al中的
字节数据进行字节到长字的符号扩展,计算结果存放在寄存器edx中。
下面是一些允许的操
作数扩展后缀:
bl:
字节->长字
bw:
字节->字
wl:
字->长字
跳转指令标号后的后缀表示跳转方向,“f”表示向前(forward),“b”表示向后(back)。
例:
jmp1f
1:
jmp1f
1:
6、指令
INTEL汇编与AT&T汇编指令基本相同,差别仅在语法上。
关于每条指令的语法可以参考I386Manual。
itle:
at&t汇编语法简单说明这些是从网上的一些资料整理而得,不知道说清楚了没有,或是有什么错误,请指正。
1:
寄存器引用引用寄存器要在寄存器号前加%,如mov%eax,%ebx2:
操作数顺序操作数排列是从源(左)到目的(右),如mov%eax(源),%ebx(目的)3:
常数/立即数的格式使用立即数,要在数前面加$,如mov,%ebx
符号常数直接引用如movvalue,%ebx(value是一常数,已在前面定义)
引用符号地址在符号前加$,如mov$value,%ebx(是将value的地址放到ebx中)
4:
操作数的长度
操作数的长度用加在指令后的符号表示
b(byte),w(word),l(long)如movw%ax,%bx
5:
寻址方式
内存寻址可以用
section:
disp(base,index,scale)表示,计算方法是
base+index*scale+disp,section在实模式下有用,保护模式下用线性地址,不用section。
例如:
call*SYMBOL_NAME(sys_call_table)(,%eax,4)
这是entry.S中的一句,对应应该是
%eax*4+sys_call_table,在sys_call_table中找到相应系统调用的地址。
二 基本的行内汇编
基本的行内汇编很简单,一般是按照下面的格式
asm("statements");
例如:
asm("nop");asm("cli");
asm 和 __asm__是完全一样的.
如果有多行汇编,则每一行都要加上 "\n\t"
例如:
asm("pushl%eax\n\t"
"movl,%eax\n\t"
"popl%eax");
实际上gcc在处理汇编时,是要把asm(...)的内容"打印"到汇编
文件中,所以格式控制字符是必要的.
再例如:
asm("movl%eax,%ebx");
asm("xorl%ebx,%edx");
asm("movl,_booga);
在上面的例子中,由于我们在行内汇编中改变了edx和ebx的值,但是
由于gcc的特殊的处理方法,即先形成汇编文件,再交给GAS去汇编,
所以GAS并不知道我们已经改变了edx和ebx的值,如果程序的上下文
需要edx或ebx作暂存,这样就会引起严重的后果.对于变量_booga也
存在一样的问题.为了解决这个问题,就要用到扩展的行内汇编语法.
三 扩展的行内汇编
基本的格式是:
asm(statements:
outputs:
inputs:
registers-modified);
statements是一些汇编语句,outputs是输出寄存器,inputs是输入寄存器,registers-modified
是在这个过程中改变的寄存器。
例如:
inti=0,j=1,k=0;
__asm____volatile__("
pushl%%eax\n
movl%1,%%eax\n
addl%2,%%eax\n
movl%%eax,%0\n
:
"=g"(k)
:
"g"(i),"g"(j)
);//k=i+j
在上面的这段代码中,输入寄存器用了"g"限定符,它的意思是将输入变量放入
eax,ebx,ecx,edx或内存变量其中之一,类似的限定还有:
"a"eax
"b"ebx
"c"ecx
"d"edx
"S"esi
"D"edi
"q"从eax,ebx,ecx,edx分配寄存器
"r"从eax,ebx,ecx,edx,esi,edi分配寄存器
"g"eax,ebx,ecx,edx或内存变量
"A"把eax和edx合成一个64位的寄存器(uselonglongs)
"I"I是常数值,例如"1",它是把输出寄存器和输入寄存器由左到右,由上到下顺序往下数对应的寄存器
在上面这段代码中,%0对应k存放的寄存器,,%1对应i存放的寄存器,%2对应j存放的寄存器.
"i"立即数
"m"内存变量
输出寄存器要在前面加"=",指示输出的位置。
上面的代码展开大概是:
movi,%eax
movj,%ebx
pushl%%eax
movl%eax,%%eax
movl%ebx,%%eax
movl%%eax,%ecx
popl%%eax
又如:
do{\
int__d0,__d1;\
__asm____volatile__("movw%%dx,%%ax\n\t"\
"movw%4,%%dx\n\t"\
"movl%%eax,%0\n\t"\
"movl%%edx,%1"\
:
"=m"(*((long*)(gate_addr))),\
"=m"(*(1+(long*)(gate_addr))),"=&a"(__d0),"=&d"(__d1)\
:
"i"((short)(0x8000+(dpl<<13)+(type<<8))),\
"3"((char*)(addr)),"2"(__KERNEL_CS<<16));\
}while(0)中
%3对应edx,%2对应eax,%1是(*(1+(long*)(gate_addr))).这段代码是将中断处理函数的地址填到
ldt(中断向量表)中。
我是在Linux下用的,不过gcc 的一套东西都是通用的,
Linux和unix 都差不多.
关于as,我没有直接用它.一般的来说,我觉得如果不是一定要使用手工编辑
.S文件,我就使用C语言加上内嵌的汇编语句来解决.用GCC来编译C文件时.加一个 -S
的选项,就能够看输出的汇编文件了.我觉得那样的方法对照C和.S文件比较好.
但愿这些对你有所帮助.
gcc采用的是AT&T的汇编格式,MS采用Intel的格式.
一 基本语法
语法上主要有以下几个不同.
★ 寄存器命名原则
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
★寻址方式
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"
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- ATT 汇编