AT&T汇编格式

时间:2022-02-21 03:15:02

因为在linux的内核中,很多跟底层硬件接触的都使用汇编语言,但是Linux不仅使用一种汇编语言,除了Intel的汇编语言之外,还是用AT&T的汇编语言,因此可以说这两个是一个基础,Intel的汇编相信很多学计算机的人都学习过,但是AT&T的就不一定了,个人认为他们的思想都是一样的,只不过是语法不同,初级学习可以看如下的文章(文章转自http://blog.chinaunix.net/u1/59572/showart_1148334.html)

一、AT&T 格式Linux 汇编语法格式
  
  在 AT&T 汇编格式中,寄存器名要加上 '%' 作为前缀;而在 Intel 汇编格式中,寄存器名不需要加前缀。例如:
  AT&T 格式
   Intel 格式
   
  pushl %eax
   push eax
   
  
  在 AT&T 汇编格式中,用 '$' 前缀表示一个立即操作数;而在 Intel 汇编格式中,立即数的表示不用带任何前缀。例如:
  AT&T 格式
   Intel 格式
   
  pushl $1
   push 1
   
  
  AT&T 和 Intel 格式中的源操作数和目标操作数的位置正好相反。在 Intel 汇编格式中,目标操作数在源操作数的左边;而在 AT&T 汇编格式中,目标操作数在源操作数的右边。例如:
  AT&T 格式
   Intel 格式
   
  addl $1, %eax
   add eax, 1
   
  
  在 AT&T 汇编格式中,操作数的字长由操作符的最后一个字母决定,后缀'b'、'w'、'l'分别表示操作数为字节(byte,8 比特)、字(word,16 比特)和长字(long,32比特);而在 Intel 汇编格式中,操作数的字长是用 "byte ptr" 和 "word ptr" 等前缀来表示的。例如:
  AT&T 格式
   Intel 格式
   
  movb val, %al
   mov al, byte ptr val
   
  
  在 AT&T 汇编格式中,绝对转移和调用指令(jump/call)的操作数前要加上'*'作为前缀,而在 Intel 格式中则不需要。 
  远程转移指令和远程子调用指令的操作码,在 AT&T 汇编格式中为 "ljump" 和 "lcall",而在 Intel 汇编格式中则为 "jmp far" 和 "call far",即:
  AT&T 格式
   Intel 格式
   
  ljump $section, $offset
   jmp far section:offset
   
  lcall $section, $offset
   call far section:offset
   
  
  与之相应的远程返回指令则为:
  AT&T 格式
   Intel 格式
   
  lret $stack_adjust
   ret far stack_adjust
   
  
  在 AT&T 汇编格式中,内存操作数的寻址方式是
  section:disp(base, index, scale)
   
  
  而在 Intel 汇编格式中,内存操作数的寻址方式为:
  section:[base + index*scale + disp]
   
  
  由于 Linux 工作在保护模式下,用的是 32 位线性地址,所以在计算地址时不用考虑段基址和偏移量,而是采用如下的地址计算方法:
  disp + base + index * scale
   
  
  下面是一些内存操作数的例子:
  AT&T 格式
   Intel 格式
   
  movl -4(%ebp), %eax
   mov eax, [ebp - 4]
   
  movl array(, %eax, 4), %eax
   mov eax, [eax*4 + array]
   
  movw array(%ebx, %eax, 4), %cx
   mov cx, [ebx + 4*eax + array]
   
  movb $4, %fs:(%eax)
   mov fs:eax, 4
   
  
  二、Hello World!
  
  既然所有程序设计语言的第一个例子都是在屏幕上打印一个字符串 "Hello World!",那我们也以这种方式来开始介绍 Linux 下的汇编语言程序设计。
  
  在 Linux 操作系统中,你有很多办法可以实现在屏幕上显示一个字符串,但最简洁的方式是使用 Linux 内核提供的系统调用。使用这种方法最大的好处是可以直接和操作系统的内核进行通讯,不需要链接诸如 libc 这样的函数库,也不需要使用 ELF 解释器,因而代码尺寸小且执行速度快。
  
  Linux 是一个运行在保护模式下的 32 位操作系统,采用 flat memory 模式,目前最常用到的是 ELF 格式的二进制代码。一个 ELF 格式的可执行程序通常划分为如下几个部分:.text、.data 和 .bss,其中 .text 是只读的代码区,.data 是可读可写的数据区,而 .bss 则是可读可写且没有初始化的数据区。代码区和数据区在 ELF 中统称为 section,根据实际需要你可以使用其它标准的 section,也可以添加自定义 section,但一个 ELF 可执行程序至少应该有一个 .text 部分。下面给出我们的第一个汇编程序,用的是 AT&T 汇编语言格式:
  
  例1. AT&T 格式
  
  #hello.s 
  
  .data # 数据段声明
  
   msg : .string "Hello, world!//n" # 要输出的字符串
  
   len = . - msg # 字串长度
  
  .text # 代码段声明
  
  .global _start # 指定入口函数 
  
  _start: # 在屏幕上显示一个字符串
  
   movl $len, %edx # 参数三:字符串长度
  
   movl $msg, %ecx # 参数二:要显示的字符串
  
   movl $1, %ebx # 参数一:文件描述符(stdout) 
  
   movl $4, %eax # 系统调用号(sys_write) 
  
   int $0x80 # 调用内核功能
  
   # 退出程序
  
   movl $0,%ebx # 参数一:退出代码
  
   movl $1,%eax # 系统调用号(sys_exit) 
  
   int $0x80 # 调用内核功能
   
  
  初次接触到 AT&T 格式的汇编代码时,很多程序员都认为太晦涩难懂了,没有关系,在 Linux 平台上你同样可以使用 Intel 格式来编写汇编程序:
  
  例2. Intel 格式
  
  ; hello.asm 
  
  section .data ; 数据段声明
  
   msg db "Hello, world!", 0xA ; 要输出的字符串
  
   len equ $ - msg ; 字串长度
  
  section .text ; 代码段声明
  
  global _start ; 指定入口函数
  
  _start: ; 在屏幕上显示一个字符串
  
   mov edx, len ; 参数三:字符串长度
  
   mov ecx, msg ; 参数二:要显示的字符串
  
   mov ebx, 1 ; 参数一:文件描述符(stdout) 
  
   mov eax, 4 ; 系统调用号(sys_write) 
  
   int 0x80 ; 调用内核功能
  
   ; 退出程序
  
   mov ebx, 0 ; 参数一:退出代码
  
   mov eax, 1 ; 系统调用号(sys_exit) 
  
   int 0x80 ; 调用内核功能
   
  
  上面两个汇编程序采用的语法虽然完全不同,但功能却都是调用 Linux 内核提供的 sys_write 来显示一个字符串,然后再调用 sys_exit 退出程序。在 Linux 内核源文件 include/asm-i386/unistd.h 中,可以找到所有系统调用的定义。
  
  三、Linux 汇编工具
  
  
  
  Linux 平台下的汇编工具虽然种类很多,但同 DOS/Windows 一样,最基本的仍然是汇编器、连接器和调试器。
  
  
  1.汇编器
  
  汇编器(assembler)的作用是将用汇编语言编写的源程序转换成二进制形式的目标代码。Linux 平台的标准汇编器是 GAS,它是 GCC 所依赖的后台汇编工具,通常包含在 binutils 软件包中。GAS 使用标准的 AT&T 汇编语法,可以用来汇编用 AT&T 格式编写的程序:
  
  
  [xiaowp@gary code]$ as -o hello.o hello.s
  
   
  
  
  Linux 平台上另一个经常用到的汇编器是 NASM,它提供了很好的宏指令功能,并能够支持相当多的目标代码格式,包括 bin、a.out、coff、elf、rdf 等。NASM 采用的是人工编写的语法分析器,因而执行速度要比 GAS 快很多,更重要的是它使用的是 Intel 汇编语法,可以用来编译用 Intel 语法格式编写的汇编程序:
  
  
  [xiaowp@gary code]$ nasm -f elf hello.asm
  
   
  
  
  2.链接器
  
  由汇编器产生的目标代码是不能直接在计算机上运行的,它必须经过链接器的处理才能生成可执行代码。链接器通常用来将多个目标代码连接成一个可执行代码,这样可以先将整个程序分成几个模块来单独开发,然后才将它们组合(链接)成一个应用程序。 Linux 使用 ld 作为标准的链接程序,它同样也包含在 binutils 软件包中。汇编程序在成功通过 GAS 或 NASM 的编译并生成目标代码后,就可以使用 ld 将其链接成可执行程序了:
  
  
  [xiaowp@gary code]$ ld -s -o hello hello.o
  
   
  
  
  3.调试器
  
  有人说程序不是编出来而是调出来的,足见调试在软件开发中的重要作用,在用汇编语言编写程序时尤其如此。Linux 下调试汇编代码既可以用 GDB、DDD 这类通用的调试器,也可以使用专门用来调试汇编代码的 ALD(Assembly Language Debugger)。
  
  
  从调试的角度来看,使用 GAS 的好处是可以在生成的目标代码中包含符号表(symbol table),这样就可以使用 GDB 和 DDD 来进行源码级的调试了。要在生成的可执行程序中包含符号表,可以采用下面的方式进行编译和链接:
  
  
  [xiaowp@gary code]$ as --gstabs -o hello.o hello.s
  
  
  [xiaowp@gary code]$ ld -o hello hello.o
  
   
  
  
  执行 as 命令时带上参数 --gstabs 可以告诉汇编器在生成的目标代码中加上符号表,同时需要注意的是,在用 ld 命令进行链接时不要加上 -s 参数,否则目标代码中的