当前位置:首页 > 芯闻号 > 充电吧
[导读]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;
}
$

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

从汇编角度来说,如果“test %al, %al”能改成“test %0x1, %al”就没有匪夷所思的问题了,如此一来应该会降低CPU的效率,毕竟执行指令还需要一个立即数,我没搞过编译器也没设计过CPU,纯属瞎猜,能搞...

关键字: 汇编 CPU 编译器

【说在前面的话】其实我很久之前就想写这篇文章了,但彼时总觉得这是一个伪命题:既然已经用了MDK,编译出来的代码,无论是体积还是性能都甩下armgcc好几条街,谁还会想用gcc来进行Cortex-M开发呢?对那些只能使用a...

关键字: GCC MDK 汇编

1.背景群里有个小伙伴学习设计加密方法,如同某商用软件输入注册码后就能使用扩展功能。设计时他很自然的想着所写的加密措施是否足够健壮安全,是否有什么方法可以绕过加密检查,也就是破解。权限管理仅在启动后检查一次注册码是否有效...

关键字: 指令 汇编 软件

最近很多伙计问我现在搞嵌入式还有没有必要学习汇编?

关键字: 嵌入式 汇编

来源 :智能软件研究中心,作者:罗宇哲,直接来源:华为开发者社区 声明:本公众号转发仅为传播相关技术知识,不作任何商业用途,如有疑义请联系删除。 01 ARM汇编指令 操作系统中硬件相关的部分集中体现在汇编指令和对寄存器...

关键字: ARM 汇编

Part 1. 机器指令 上一次 我们已经了解了 二进制和 CPU 的基本原理,知道了程序运行时,CPU 每秒数以亿次、十亿次、百亿次地震荡着时钟,同步执行着微小的 「电子操作」,例如:从内存读取一个字节的数据到 CPU...

关键字: 高级编程 编程语言 汇编

来源 :智能软件研究中心,作者:罗宇哲,直接来源:华为开发者社区 声明:本公众号转发仅为传播相关技术知识,不作任何商业用途,如有疑义请联系删除。 01 ARM汇编指令 操作系统中硬件相关的部分集中体现在汇编指令和对寄存器...

关键字: ARM 汇编

北京时间4月30日早间消息,据外媒报道,Facebook首席执行官扎克伯格周三警告称,过早重新开放公共空间“几乎可以肯定”将会导致新冠病毒的继续爆发,并带来不良的经济后果。 扎克伯格在公司第一季度财报

关键字: 扎克伯格 FACEBOOK LTE 汇编

北京时间4月30日早间消息,据外媒报道,Facebook首席执行官扎克伯格周三警告称,过早重新开放公共空间“几乎可以肯定”将会导致新冠病毒的继续爆发,并带来不良的经济后果。扎克伯格在公司第一季度财报电

关键字: 扎克伯格 FACEBOOK LTE 汇编

北京时间5月1日消息,亿万富翁慈善家比尔-盖茨(Bill Gates)在周四提出了全球重新开放在新冠疫苗方面的需求。“切合实际的讲,如果我们要恢复正常,我们需要开发一种安全有效的疫苗。”盖茨在博客中说

关键字: 盖茨 UNIVERSITY 汇编 美的
关闭
关闭