当前位置:首页 > 芯闻号 > 充电吧
[导读]AT&T汇编与Intel汇编的比较 文章作者:linuxkernel (newbie) 既然大家对汇编感兴趣,不妨我也来凑凑热闹。废话少说,言归正传。 Intel和AT&T语法的

AT&T汇编与Intel汇编的比较 文章作者:linuxkernel (newbie)


既然大家对汇编感兴趣,不妨我也来凑凑热闹。废话少说,言归正传。

Intel和AT&T语法的区别
Intel和AT&T汇编语言的语法表面上各不相同,这将导致刚刚学会INTEL汇编的人第一次见到AT&T汇编时
会感到困惑,或者反之。因此让我们从基础的东西开始。

前缀
在Intel汇编中没有寄存器前缀或者立即数前缀。而在AT&T汇编中寄存器有一个“%”前缀,立即数有
一个“$”前缀。Intel语句中十六进制和二进制数据分别带有“h”和“b”后缀,并且如果十六进制
数字的第一位是字母的话,那么数值的前面要加一个“0”前缀。
例如,
Intex Syntax
mov   eax,1
mov   ebx,0ffh
int   80h

AT&T Syntax
movl   $1,%eax
movl   $0xff,%ebx
int    $0x80
就像你看到的,AT&T非常难懂。[base+index*scale+disp] 看起来比disp(base,index,scale)更好理解。


操作数的用法
intel语句中操作数的用法和AT&T中的用法相反。在Intel语句中,第一个操作数表示目的,第二个
操作数表示源。然而在AT&T语句中第一个操作数表示源而第二个操作数表示目的。在这种情形下AT&T语法
的好处是显而易见的。我们从左向右读,也从左向右写,这样比较自然。
例如,
Intex Syntax
instr   dest,source
mov   eax,[ecx]
   
AT&T Syntax
instr    source,dest
movl   (%ecx),%eax

存储器操作数
如同上面所看到的,存储器操作数的用法也不相同。在Intel语句中基址寄存器用“[”和“]”括起来
而在AT&T语句中是用“(”和“)”括起来的。
例如,
Intex Syntax
mov   eax,[ebx]
mov   eax,[ebx+3]
AT&T Syntax
movl   (%ebx),%eax
movl   3(%ebx),%eax
AT&T语法中用来处理复杂的操作的指令的形式和Intel语法中的形式比较起来要难懂得多。在Intel语句
中这样的形式是segreg:[base+index*scale+disp]。在AT&T语句中这样的形式是
%segreg:disp(base,index,scale)。
Index/scale/disp/segreg 都是可选并且可以去掉的。Scale在本身没有说明而index已指定的情况下
缺省值为1。segreg的确定依赖于指令本身以及程序运行在实模式还是pmode。在实模式下它依赖于
指令本身而pmode模式下它是不需要的。在AT&T语句中用作scale/disp的立即数不要加“$”前缀。
例如
Intel Syntax
instr    foo,segreg:[base+index*scale+disp]
mov   eax,[ebx+20h]
add   eax,[ebx+ecx*2h]
lea   eax,[ebx+ecx]
sub   eax,[ebx+ecx*4h-20h]   
AT&T Syntax
instr   %segreg:disp(base,index,scale),foo
movl   0x20(%ebx),%eax
addl   (%ebx,%ecx,0x2),%eax
leal   (%ebx,%ecx),%eax
subl   -0x20(%ebx,%ecx,0x4),%eax

后缀
就像你已经注意到的,AT&T语法中有一个后缀,它的意义是表示操作数的大小。“l”代表long,
“w”代表word,“b”代表byte。Intel语法中在处理存储器操作数时也有类似的表示,
如byte ptr, word ptr, dword ptr。"dword" 显然对应于“long”。这有点类似于C语言中定义的
类型,但是既然使用的寄存器的大小对应着假定的数据类型,这样就显得不必要了。
例子:
Intel Syntax
mov   al,bl
mov   ax,bx
mov   eax,ebx
mov   eax, dword ptr [ebx]   
AT&T Syntax
movb   %bl,%al
movw   %bx,%ax
movl   %ebx,%eax
movl   (%ebx),%eax

注意:从此开始所有的例子都使用AT&T语法
系统调用
本节将介绍linux中汇编语言系统调用的用法。系统调用包括位于/usr/man/man2的手册里第二部分所有
的函数。这些函数也在/usr/include/sys/syscall.h中列出来了。一个重要的关于这些函数的列表是
在http://www.linuxassembly.org/syscall.html里。这些函数通过linux中断服务:int $0x80来被执行
小于六个参数的系统调用
对于所有的系统调用,系统调用号在%eax中。对于小于六个参数的系统调用,参数依次存放
在%ebx,%ecx,%edx,%esi,%edi中,系统调用的返回值保存在%eax中。
系统调用号可以在/usr/include/sys/syscall.h中找到。宏被定义成SYS_的形式,
如SYS_exit, SYS_close等。
例子:(hello world 程序)
参照write(2)的帮助手册,写操作被声明为ssize_t write(int fd, const void *buf, size_t count);
这样,fd应存放在%ebx中,buf放在 %ecx, count 放在 %edx , SYS_write 放在 %eax中,紧跟着是
int $0x80语句来执行系统调用。系统调用的返回值保存在%eax中。
$ cat write.s
.include "defines.h"
.data
hello:
   .string "hello world/n"

.globl   main
main:
   movl   $SYS_write,%eax
   movl   $STDOUT,%ebx
   movl   $hello,%ecx
   movl   $12,%edx
   int   $0x80

   ret
$
少于5个参数的系统调用的处理也是这样的。只是没有用到的寄存器保持不变罢了。象open或者fcntl这样
带有一个可选的额外参数的系统调用也就知道怎么用了。
大于5个参数的系统调用
参数个数大于五个的系统调用仍然把系统调用号保存在%eax中,但是参数存放在内存中,并且指向第一个
参数的指针保存在%ebx中。
如果你使用栈,参数必须被逆序压进栈里,即按最后一个参数到第一个参数的顺序。然后将栈的指针拷贝
到%ebx中。或者将参数拷贝到一块分配的内存区域,然后把第一个参数的地址保存在%ebx中。
例子:(使用mmap作为系统调用的例子)。在C中使用mmap():
#include
#include
#include
#include
#include

#define STDOUT   1

void main(void) {
   char file[]="mmap.s";
   char *mappedptr;
   int fd,filelen;

   fd=fopen(file, O_RDONLY);
   filelen=lseek(fd,0,SEEK_END);
   mappedptr=mmap(NULL,filelen,PROT_READ,MAP_SHARED,fd,0);
   write(STDOUT, mappedptr, filelen);
   munmap(mappedptr, filelen);
   close(fd);
}
mmap()参数在内存中的排列:
%esp   %esp+4   %esp+8   %esp+12   %esp+16   %esp+20
00000000   filelen   00000001   00000001   fd   00000000
等价的汇编程序:
$ cat mmap.s
.include "defines.h"

.data
file:
   .string "mmap.s"
fd:
   .long    0
filelen:
   .long    0
mappedptr:
   .long    0

.globl main
main:
   push   %ebp
   movl   %esp,%ebp
   subl   $24,%esp

//   open($file, $O_RDONLY);

   movl   $fd,%ebx   // save fd
   movl   %eax,(%ebx)

//   lseek($fd,0,$SEEK_END);

   movl   $filelen,%ebx   // save file length
   movl   %eax,(%ebx)

   xorl   %edx,%edx

//   mmap(NULL,$filelen,PROT_READ,MAP_SHARED,$fd,0);
   movl   %edx,(%esp)
   movl   %eax,4(%esp)   // file length still in %eax
   movl   $PROT_READ,8(%esp)
   movl   $MAP_SHARED,12(%esp)
   movl   $fd,%ebx   // load file descriptor
   movl   (%ebx),%eax
   movl   %eax,16(%esp)
   movl   %edx,20(%esp)
   movl   $SYS_mmap,%eax
   movl   %esp,%ebx
   int   $0x80

   movl   $mappedptr,%ebx   // save ptr
   movl   %eax,(%ebx)
      
//    write($stdout, $mappedptr, $filelen);
//   munmap($mappedptr, $filelen);
//   close($fd);
   
   movl   %ebp,%esp
   popl   %ebp

   ret
$
注意:上面所列出的源代码和本文结束部分的例子的源代码不同。上面列出的代码中没有说明其它的
系统调用,因为这不是本节的重点,上面列出的源代码仅仅打开mmap.s文件,而例子的源代码要读
命令行的参数。这个mmap的例子还用到lseek来获取文件大小。
Socket系统调用
Socket系统调用使用唯一的系统调用号:SYS_socketcall,它保存在%eax中。Socket函数是通过位于
/usr/include/linux/net.h的一个子函数号来确定的,并且它们被保存在%ebx中。指向系统调用参数
的一个指针存放在%ecx中。Socket系统调用也是通过int $0x80来执行的。
$ cat socket.s
.include "defines.h"

.globl   _start
_start:
   pushl   %ebp
   movl   %esp,%ebp
   sub   $12,%esp

//   socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
   movl   $AF_INET,(%esp)
   movl   $SOCK_STREAM,4(%esp)
   movl   $IPPROTO_TCP,8(%esp)

   movl   $SYS_socketcall,%eax
   movl   $SYS_socketcall_socket,%ebx
   movl   %esp,%ecx
   int   $0x80

   movl    $SYS_exit,%eax
   xorl    %ebx,%ebx
   int    $0x80

   movl   %ebp,%esp
   popl   %ebp
   ret
$

命令行参数
在linux中执行的时候命令行参数是放在栈上的。先是argc,跟着是一个由指向命令行中各字符串的
指针组成的数组(**argv)并以空指针结束。接下来是一个由指向环境变量的指针组成的
数组(**envp)。这些东西在asm中都可以很容易的获得,并且在例子代码(args.s)中有示范。


GCC内联汇编
本节中GCC内联汇编仅涉及x86的应用程序。操作数约束会和其它处理器上的有所不同。关于这部分
的说明放在本文的最后。
gcc中基本的内联汇编非常易懂,如
__asm__("movl   %esp,%eax");   // look familiar ?

或者是
__asm__("
        movl   $1,%eax      // SYS_exit
        xor   %ebx,%ebx
        int   $0x80
   ");
如果指定了用作asm的输入、输出数据并指出哪一个寄存器会被修改,会使程序的执行效率提高。
input/output/modify都不是必需的。格式如下:
__asm__("" : output : input : modify);
output和input中必须包含一个操作数约束字符串,并紧跟一个用圆括号括起来的C语言表达式。
输出操作数约束的前面必须有一个“=”,表示这是一个输出。可能会有多个输出,多个输入和
多个修改过的寄存器。每个“入口”应该用“,”分隔开,并且入口的总数不多有10个。
操作数约束字符串可以是包含整个寄存器的名称也可以是简写。
Abbrev Table
Abbrev   Register
a   %eax/%ax/%al
b   %ebx/%bx/%bl
c   %ecx/%cx/%cl
d   %edx/%dx/%dl
S   %esi/%si
D   %edi/%di
m   memory
例如:

   __asm__("test   %%eax,%%eax", : /* no output */ : "a"(foo));


或者是

   __asm__("test   %%eax,%%eax", : /* no output */ : "eax"(foo));
你可以在__asm__后使用关键字__volatile__:“你可以利用在__asm__后使用关键字__volatile__的
方法防止一条‘asm’指令被删除、移动或者被重新组合。”(出自gcc的info文件中"Assembler
Instructions with C Expression Operands" 部分)
$ cat inline1.c
#include

int main(void) {
   int foo=10,bar=15;
   
   __asm__ __volatile__ ("addl    %%ebxx,%%eax"
      : "=eax"(foo)      // ouput
      : "eax"(foo), "ebx"(bar)// input
      : "eax"        // modify
   );
   printf("foo+bar=%d/n", foo);
   return 0;
}
$
你可能已经注意到现在寄存器使用“%%”前缀而不是“%”。这在使用output/input/modify域时是必要的,
这是因为此时基于其它域的寄存器的别名的使用。我马上来讨论这个问题。
你可以很简单的指定“a”而不是写“eax”或者强制使用一个特殊寄存器如"eax"、"ax"、"al",
这同样适用于其它一般用途的寄存器(在Abbrev表中列出的)。当你在当前的代码中使用特殊的寄存器
时这好像毫无用处,因此gcc提供了寄存器别名。最多有10个别名(%0—%9),这也是为什么只允许10个
输入/输出的原因。
$ cat inline2.c
int main(void) {
   long eax;
   short bx;
   char cl;

   __asm__("nop;nop;nop"); // to separate inline asm from the rest of
           // the code
   __volatile__ __asm__("
      test   %0,%0
      test   %1,%1
      test   %2,%2"
      : /* no outputs */
      : "a"((long)eax), "b"((short)bx), "c"((char)cl)
   );
   __asm__("nop;nop;nop");
   return 0;
}
$ gcc -o inline2 inline2.c
$ gdb ./inline2
GNU gdb 4.18
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnulibc1"...
(no debugging symbols found)...
(gdb) disassemble main
Dump of assembler code for function main:
... start: inline asm ...
0x8048427 : nop
0x8048428 : nop
0x8048429 : nop
0x804842a : mov 0xfffffffc(%ebp),%eax
0x804842d : mov 0xfffffffa(%ebp),%bx
0x8048431 : mov 0xfffffff9(%ebp),%cl
0x8048434 : test %eax,%eax
0x8048436 : test %bx,%bx
0x8048439 : test %cl,%cl
0x804843b : nop
0x804843c : nop
0x804843d : nop
... end: inline asm ...
End of assembler dump.
$
就像你看到的,由内联汇编生成的代码将变量的值放入它们在input域中指定的寄存器中,然后继续
执行当前的代码。编译器自动根据变量的大小来侦测操作数的大小,这样相应的寄存器就被
别名%0, %1 和 %2代替了(当使用寄存器别名时在存储器里指定操作数的大小回导致编译时发生错误)
在操作数约束里也可以使用别名。这不允许你在输入/输出域中指定多于10个的入口。我能想到的这样
做的唯一用法是在你指定操作数约束为“q”以便让编译器在a,b,c,d寄存器之间进行选择的时候。
当这个寄存器被修改时,我们不会知道选中了那个寄存器,因而不能在modify域中指定它。
这种情况下你只需指定""。
例子:
$ cat inline3.c
#include

int main(void) {
   long eax=1,ebx=2;

   __asm__ __volatile__ ("add %0,%2"
      : "=b"((long)ebx)
      : "a"((long)eax), "q"(ebx)
      : "2"
   );
   printf("ebx=%x/n", ebx);
   return 0;
}
$

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭
关闭