1. 程式人生 > >AT&T彙編格式

AT&T彙編格式

絕大多數 Linux 程式設計師以前只接觸過DOS/Windows 下的組合語言,這些彙編程式碼都是 Intel 風格的。但在 Unix  Linux 系統中,更多采用的還是 AT&T 格式,兩者在語法格式上有著很大的不同:

 AT&T 彙編格式中,暫存器名要加上 ' %' 作為字首;而在 Intel 彙編格式中,暫存器名不需要加字首。例如:

AT&T 格式

Intel 格式

pushl �x

push eax

 AT&T 彙編格式中,用 '$' 字首表示一個立即運算元;而在 Intel 彙編格式中,立即數的表示不用帶任何字首。例如:

AT&T 格式

Intel 格式

pushl $1

push 1

AT&T  Intel 格式中的源運算元和目標運算元的位置正好相反。在 Intel 彙編格式中,目標運算元在源運算元的左邊;而在 AT&T 彙編格式中,目標運算元在源運算元的右邊。例如:

AT&T 格式

Intel 格式

addl $1, �x

add eax, 1

 AT&T 彙編格式中,運算元的字長由操作符的最後一個字母決定,字尾'b''w''l'分別表示運算元為位元組(byte位元)、字(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(�p), �x

mov eax, [ebp - 4]

movl array(, �x, 4), �x

mov eax, [eax*4 + array]

movw array(�x, �x, 4), %cx

mov cx, [ebx + 4*eax + array]

movb $4, %fs:(�x)

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, �x  # 引數三:字串長度

        movl $msg, �x  # 引數二:要顯示的字串

        movl $1, �x    # 引數一:檔案描述符(stdout)

        movl $4, �x    # 系統呼叫號(sys_write)

        int  $0x80       # 呼叫核心功能

                         # 退出程式

        movl $0,�x     # 引數一:退出程式碼

        movl $1,�x     # 系統呼叫號(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 中,可以找到所有系統呼叫的定義。