Linux 汇编语法格式

来源:互联网 发布:网络会议系统方案 编辑:程序博客网 时间:2024/04/30 04:04

绝大多数 Linux程序员以前只接触过DOS/Windows下的汇编语言,这些汇编代码都是 Intel风格的。但在 Unix Linux系统中,更多采用的还是 AT&T格式,两者在语法格式上有着很大的不同:

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'分别表示操作数为字节(byte8比特)、字(word16比特)和长字(long32比特);而在 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中,可以找到所有系统调用的定义。

 

原创粉丝点击