linux汇编AT&T编程

目录:

·介绍

·intelAT&T语法

         ·前缀

         ·操作数方向

         ·内存操作数

         ·后缀

·系统调用

         ·少于6个参数的系统调用

         ·多于5个参数的系统调用

         ·socket系统调用

·命令行参数

·GCC内联ASM

·编译

·参考

 

介绍

这篇文章主要是一篇linux汇编语言编程入门教程,包含了IntelAT&T语法格式的比较以及汇编语言中如何使用系统调用和介绍gcc中如何使用内联汇编

IntelAT&T语法

IntelAT&T在语法格式上有很大的不同,主要表现在以下几个方面:     

         前缀

Intel语法

 

move   ax,1

mov    ebx,0ffh

int     80h

AT&T语法

 

movl   $1,%eax

movl   $0xff,%ebx

int     80h

 

         操作数方向

         Intel语法中操作数方向与AT&T是相反的,在Intel语法中第一个操作数是目的操作数,第二个操作数是源操作数,但AT&T语法格式中,第一个操作数是源操作数,第二个操作数是目的操作数,AT&T语法的优点就是比较人性化,符号我们从左到右阅读的习惯逻辑。

Intel语法

 

instr    dest,source

mov    eax,[ecx]

AT&T语法

 

instr    source,dest

movl   (%ecx),%eax

 

         内存操作数

         从上面表格中也能看出两种语法格式在使用内存操作数时也有不同,Intel语法格式用’[‘’]’表示引用一个基地址,而AT&T语法中使用’(‘’)’来引用一个基地址

 

Intel语法

 

move   eax,[ebx]

mov    eax,[ebx+3]

AT&T语法

 

movl   (%ebx),%eax

movl   3(%ebx),%ebx

         另外特别是在进行对内存寻址的时候,AT&T语法可能就不如intel语法格式那么直观,比如intel语法采用segreg:[base+index*scale+disp]AT&T采用segreg:disp(base,index,scale)来表示变址寻址。

 index/scale/disp/segreg都是简单的位于左边的选项,如果没有指定scale,而指定了index,那么scale默认为1segreg选项依赖于应用是运行在实模式还是保护模式,并且它只用于实模式中。另外,在Intel语法格式中立即数不需要加’$’,而在AT&T语法中加’$’前缀来表示立即数。

Intel语法

 

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语法

 

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’,后缀的特征表示操作数大小,如’l’表示long类型,’w’表示一个字长,’b’表示一个字节长,intel语法也有类似的指令用在内存操作数中,例如byte ptr,word ptr,dword ptr,’dword’表示’’long’型,这有点类似于C语言中的强制转换,但也并不一定非这样不可,因为寄存器的大小已经使用在假定的数据类型中了。

Intel语法

 

move   al,bl

mov    ax,bx

mov    eax,ebx

mov    eax,dword ptr [ebx]

AT&T语法

 

movb   %bl,%al

movw  %bx,%ax

movl   %ebx,%eax

movl    (%ebx),%eax

 

      系统调用

                   系统调用的所有函数原型在manual手册的第二段,文件/usr/man/man2中,也可以在/usr/include/sys/syscall.h中找到,也可以通过这个链接访问,所有的系统调用函数都通过linux软中断服务(int $0x80)来执行的。

                   这个我前一篇文章中已经提到了,linux下系统调用的设计都会遵循这个原则,只是在AT&T语法中多了一个前缀符而已,这里我就再讲了,下面是参数小于6个和大于5个时的两段代码:

        

syscall < 6 args

$ 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

mmap()参数在栈中的位置:

%esp

%esp+4

%esp+8

%esp+12

%esp+16

%esp+20

00000000

filele

00000001

0000000

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

$

socket系统调用

         socket系统调用仅仅使用一个系统调用号:SYS_socket,放在%eax中,socket函数通过一个字函数号进行区分,位于/usr/include/linux/net.h中,子函数号存在%ebx中,指向系统调用参数的指针存在%ecx中,同样通过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

命令行参数

         见上篇文章NASM汇编入门

gcc内联汇编

下面是一篇关于这个话题讲得很仔细的文章:

         linux x86的内联汇编

编译

编译汇编语言跟编译C差不多,下面是两种不同代码的相应编译方式:

  • Listing 1

$ cat write.s

.data

hw:

        .string "hello world/n"

.text

.globl main

main:

        movl    $SYS_write,%eax

        movl    $1,%ebx

        movl    $hw,%ecx

        movl    $12,%edx

        int     $0x80

        movl    $SYS_exit,%eax

        xorl    %ebx,%ebx

        int     $0x80

        ret

$ gcc -o write write.s

$ wc -c ./write

   4790 ./write

$ strip ./write

$ wc -c ./write

   2556 ./write

 

  • Listing 2

$ cat write.s

.data

hw:

        .string "hello world/n"

.text

.globl _start

_start:

        movl    $SYS_write,%eax

        movl    $1,%ebx

        movl    $hw,%ecx

        movl    $12,%edx

        int     $0x80

        movl    $SYS_exit,%eax

        xorl    %ebx,%ebx

        int     $0x80

 

$ gcc -c write.s

$ ld -s -o write write.o

$ wc -c ./write

    408 ./write

 

 

-s开关选项实际是执行strip,去除一些无关的信息,使得ELF文件更小

参考链接

http://asm.sourceforge.net/

 

 

Logo

更多推荐