linux汇编中的注释,Linux 汇编器:对照 GAS 和 NASM
Linux 匯編器:對比 GAS 和 NASM
轉(zhuǎn)自 http://www.ibm.com/developerworks/cn/linux/l-gas-nasm.html#ibm-pcon
與其他語言不同,匯編語言
要求開發(fā)人員了解編程所用機器的處理器體系結(jié)構(gòu)。匯編程序不可移植,維護和理解常常比較麻煩,通常包含大量代碼行。但是,在機器上執(zhí)行的運行時二進(jìn)制代碼在速度和大小方面有優(yōu)勢。
對于在 Linux 上進(jìn)行匯編級編程已經(jīng)有許多參考資料,本文主要講解語法之間的差異,幫助您更輕松地在匯編形式之間進(jìn)行轉(zhuǎn)換。本文源于我自己試圖改進(jìn)這種轉(zhuǎn)換的嘗試。
本文使用一系列程序示例。每個程序演示一些特性,然后是對語法的討論和對比。盡管不可能討論 NASM 和 GAS
之間存在的每個差異,但是我試圖討論主要方面,給進(jìn)一步研究提供一個基礎(chǔ)。那些已經(jīng)熟悉 NASM 和 GAS
的讀者也可以在這里找到有用的內(nèi)容,比如宏。
本文假設(shè)您至少基本了解匯編的術(shù)語,曾經(jīng)用符合 Intel? 語法的匯編器編寫過程序,可能在 Linux 或 Windows 上使用過 NASM。本文并不講解如何在編輯器中輸入代碼,或者如何進(jìn)行匯編和鏈接(但是下面的邊欄可以幫助您 快速回憶一下
)。您應(yīng)該熟悉 Linux 操作系統(tǒng)(任何 Linux 發(fā)行版都可以;我使用的是 Red Hat 和 Slackware)和基本的 GNU 工具,比如 gcc 和 ld,還應(yīng)該在 x86 機器上進(jìn)行編程。
現(xiàn)在,我描述一下本文討論的范圍。
構(gòu)建示例
匯編:
GAS:
as –o program.o program.s
NASM:
nasm –f elf –o program.o program.asm
鏈接(對于兩種匯編器通用):
ld –o program program.o
在使用外部 C 庫時的鏈接方法:
ld –-dynamic-linker /lib/ld-linux.so.2 –lc –o program program.o
本文討論:
NASM 和 GAS 之間的基本語法差異
常用的匯編級結(jié)構(gòu),比如變量、循環(huán)、標(biāo)簽和宏
關(guān)于調(diào)用外部 C 例程和使用函數(shù)的信息
匯編助記符差異和使用方法
內(nèi)存尋址方法
本文不討論:
處理器指令集
一種匯編器特有的各種宏形式和其他結(jié)構(gòu)
NASM 或 GAS 特有的匯編器指令
不常用的特性,或者只在一種匯編器中出現(xiàn)的特性
更多信息請參考匯編器的官方手冊(參見 參考資料
中的鏈接),因為這些手冊是最完整的信息源。
基本結(jié)構(gòu)
清單 1 給出一個非常簡單的程序,它的作用僅僅是使用退出碼 2 退出。這個小程序展示了 NASM 和 GAS 的匯編程序的基本結(jié)構(gòu)。
清單 1. 一個使用退出碼 2 退出的程序
行號
NASM
GAS
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
; Text segment begins
section .text
global _start
; Program entry point
_start:
; Put the code number for system call
mov eax, 1
; Return value
mov ebx, 2
; Call the OS
int 80h
# Text segment begins
.section .text
.globl _start
# Program entry point
_start:
# Put the code number for system call
movl $1, %eax
/* Return value */
movl $2, %ebx
# Call the OS
int $0x80
現(xiàn)在解釋一下。
NASM 和 GAS 之間最大的差異之一是語法。GAS 使用 AT&T 語法,這是一種相當(dāng)老的語法,由 GAS 和一些老式匯編器使用;NASM 使用 Intel 語法,大多數(shù)匯編器都支持它,包括 TASM 和 MASM。(GAS 的現(xiàn)代版本支持 .intel_syntax
指令,因此允許在 GAS 中使用 Intel 語法。)
下面是從 GAS 手冊總結(jié)出的一些主要差異:
AT&T 和 Intel 語法采用相反的源和目標(biāo)操作數(shù)次序。例如:
Intel:mov eax, 4
AT&T:movl $4, %eax
在 AT&T 語法中,中間操作數(shù)前面加 $
;在 Intel 語法中,中間操作數(shù)不加前綴。例如:
Intel:push 4
AT&T:pushl $4
在 AT&T 語法中,寄存器操作數(shù)前面加 %
。在 Intel 語法中,它們不加前綴。
在 AT&T 語法中,內(nèi)存操作數(shù)的大小由操作碼名稱的最后一個字符決定。操作碼后綴 b
、w
和 l
分別指定字節(jié)(8 位)、字(16 位)和長(32 位)內(nèi)存引用。Intel 語法通過在內(nèi)存操作數(shù)(而不是操作碼本身)前面加 byte ptr
、word ptr
和 dword ptr
來指定大小。所以:
Intel:mov al, byte ptr foo
AT&T:movb foo, %al
在 AT&T 語法中,中間形式長跳轉(zhuǎn)和調(diào)用是 lcall/ljmp $section, $offset
;Intel 語法是 call/jmp far section:offset
。在 AT&T 語法中,遠(yuǎn)返回指令是 lret $stack-adjust
,而 Intel 使用 ret far stack-adjust
。
在這兩種匯編器中,寄存器的名稱是一樣的,但是因為尋址模式不同,使用它們的語法是不同的。另外,GAS 中的匯編器指令以 “.” 開頭,但是在 NASM 中不是。
.text
部分是處理器開始執(zhí)行代碼的地方。global
(或者 GAS 中的 .globl
或 .global
)關(guān)鍵字用來讓一個符號對鏈接器可見,可以供其他鏈接對象模塊使用。在清單 1 的 NASM 部分中,global _start
讓 _start
符號成為可見的標(biāo)識符,這樣鏈接器就知道跳轉(zhuǎn)到程序中的什么地方并開始執(zhí)行。與 NASM 一樣,GAS 尋找這個 _start
標(biāo)簽作為程序的默認(rèn)進(jìn)入點。在 GAS 和 NASM 中標(biāo)簽都以冒號結(jié)尾。
中斷是一種通知操作系統(tǒng)需要它的服務(wù)的一種方法。第 16 行中的 int
指令執(zhí)行這個工作。GAS 和 NASM 對中斷使用同樣的助記符。GAS 使用 0x
前綴指定十六進(jìn)制數(shù)字,NASM 使用 h
后綴。因為在 GAS 中中間操作數(shù)帶 $
前綴,所以 80 hex 是 $0x80
。
int $0x80
(或 NASM 中的 80h
)用來向 Linux 請求一個服務(wù)。服務(wù)編碼放在 EAX 寄存器中。EAX 中存儲的值是 1(代表 Linux exit 系統(tǒng)調(diào)用),這請求程序退出。EBX 寄存器包含退出碼(在這個示例中是 2),也就是返回給操作系統(tǒng)的一個數(shù)字。(可以在命令提示下輸入 echo $?
來檢查這個數(shù)字。)
最后討論一下注釋。GAS 支持 C 風(fēng)格(/* */
)、C++ 風(fēng)格(//
)和 shell 風(fēng)格(#
)的注釋。NASM 支持以 “;” 字符開頭的單行注釋。
回頁首
變量和內(nèi)存訪問
本節(jié)首先給出一個示例程序,它尋找三個數(shù)字中的最大者。
清單 2. 尋找三個數(shù)字中最大者的程序
行號
NASM
GAS
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
; Data section begins
section .data
var1 dd 40
var2 dd 20
var3 dd 30
section .text
global _start
_start:
; Move the contents of variables
mov ecx, [var1]
cmp ecx, [var2]
jg check_third_var
mov ecx, [var2]
check_third_var:
cmp ecx, [var3]
jg _exit
mov ecx, [var3]
_exit:
mov eax, 1
mov ebx, ecx
int 80h
// Data section begins
.section .data
var1:
.int 40
var2:
.int 20
var3:
.int 30
.section .text
.globl _start
_start:
# move the contents of variables
movl (var1), %ecx
cmpl (var2), %ecx
jg check_third_var
movl (var2), %ecx
check_third_var:
cmpl (var3), %ecx
jg _exit
movl (var3), %ecx
_exit:
movl $1, %eax
movl %ecx, %ebx
int $0x80
在上面的內(nèi)存變量聲明中可以看到幾點差異。NASM 分別使用 dd
、dw
和 db
指令聲明 32 位、16 位和 8 位數(shù)字,而 GAS 分別使用 .long
、.int
和 .byte
。GAS 還有其他指令,比如 .ascii
、.asciz
和 .string
。在 GAS 中,像聲明其他標(biāo)簽一樣聲明變量(使用冒號),但是在 NASM 中,只需在內(nèi)存分配指令(dd
、dw
等等)前面輸入變量名,后面加上變量的值。
清單 2 中的第 18 行演示內(nèi)存直接尋址模式。NASM 使用方括號間接引用一個內(nèi)存位置指向的地址值:[var1]
。GAS 使用圓括號間接引用同樣的值:(var1)
。本文后面討論其他尋址模式的使用方法。
回頁首
使用宏
清單 3 演示本節(jié)討論的概念;它接受用戶名作為輸入并返回一句問候語。
清單 3. 讀取字符串并向用戶顯示問候語的程序
行號
NASM
GAS
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
section .data
prompt_str db 'Enter your name: '
; $ is the location counter
STR_SIZE equ $ - prompt_str
greet_str db 'Hello '
GSTR_SIZE equ $ - greet_str
section .bss
; Reserve 32 bytes of memory
buff resb 32
; A macro with two parameters
; Implements the write system call
%macro write 2
mov eax, 4
mov ebx, 1
mov ecx, %1
mov edx, %2
int 80h
%endmacro
; Implements the read system call
%macro read 2
mov eax, 3
mov ebx, 0
mov ecx, %1
mov edx, %2
int 80h
%endmacro
section .text
global _start
_start:
write prompt_str, STR_SIZE
read buff, 32
; Read returns the length in eax
push eax
; Print the hello text
write greet_str, GSTR_SIZE
pop edx
; edx = length returned by read
write buff, edx
_exit:
mov eax, 1
mov ebx, 0
int 80h
.section .data
prompt_str:
.ascii "Enter Your Name: "
pstr_end:
.set STR_SIZE, pstr_end - prompt_str
greet_str:
.ascii "Hello "
gstr_end:
.set GSTR_SIZE, gstr_end - greet_str
.section .bss
// Reserve 32 bytes of memory
.lcomm buff, 32
// A macro with two parameters
// implements the write system call
.macro write str, str_size
movl $4, %eax
movl $1, %ebx
movl \str, %ecx
movl \str_size, %edx
int $0x80
.endm
// Implements the read system call
.macro read buff, buff_size
movl $3, %eax
movl $0, %ebx
movl \buff, %ecx
movl \buff_size, %edx
int $0x80
.endm
.section .text
.globl _start
_start:
write $prompt_str, $STR_SIZE
read $buff, $32
// Read returns the length in eax
pushl %eax
// Print the hello text
write $greet_str, $GSTR_SIZE
popl %edx
// edx = length returned by read
write $buff, %edx
_exit:
movl $1, %eax
movl $0, %ebx
int $0x80
本節(jié)要討論宏以及 NASM 和 GAS 對它們的支持。但是,在討論宏之前,先與其他幾個特性做一下比較。
清單 3 演示了未初始化內(nèi)存的概念,這是用 .bss
部分指令(第 14
行)定義的。BSS 代表 “block storage segment” (原來是以一個符號開頭的塊),BSS
部分中保留的內(nèi)存在程序啟動時初始化為零。BSS 部分中的對象只有一個名稱和大小,沒有值。與數(shù)據(jù)部分中不同,BSS
部分中聲明的變量并不實際占用空間。
NASM 使用 resb
、resw
和 resd
關(guān)鍵字在 BSS 部分中分配字節(jié)、字和雙字空間。GAS 使用 .lcomm
關(guān)鍵字分配字節(jié)級空間。請注意在這個程序的兩個版本中聲明變量名的方式。在 NASM 中,變量名前面加 resb
(或 resw
或 resd
)關(guān)鍵字,后面是要保留的空間量;在 GAS 中,變量名放在 .lcomm
關(guān)鍵字的后面,然后是一個逗號和要保留的空間量。
NASM:varname resb size
GAS:.lcomm varname, size
清單 3 還演示了位置計數(shù)器的概念(第 6 行)。
NASM 提供特殊的變量($
和 $$
變量)來操作位置計數(shù)器。在 GAS 中,無法操作位置計數(shù)器,必須使用標(biāo)簽計算下一個存儲位置(數(shù)據(jù)、指令等等)。
例如,為了計算一個字符串的長度,在 NASM 中會使用以下指令:
prompt_str db 'Enter your name: '
STR_SIZE equ $ - prompt_str
; $ is the location counter
$
提供位置計數(shù)器的當(dāng)前值,從這個位置計數(shù)器中減去標(biāo)簽的值(所有變量名都是標(biāo)簽),就會得出標(biāo)簽的聲明和當(dāng)前位置之間的字節(jié)數(shù)。equ
用來將變量 STR_SIZE 的值設(shè)置為后面的表達(dá)式。GAS 中使用的相似指令如下:
prompt_str:
.ascii "Enter Your Name: "
pstr_end:
.set STR_SIZE, pstr_end - prompt_str
末尾標(biāo)簽(pstr_end
)給出下一個位置地址,減去啟始標(biāo)簽地址就得出大小。還要注意,這里使用 .set
將變量 STR_SIZE 的值設(shè)置為逗號后面的表達(dá)式。也可以使用對應(yīng)的 .equ
。在 NASM 中,沒有與 GAS 的 set
指令對應(yīng)的指令。
正如前面提到的,清單 3 使用了宏(第 21 行)。在 NASM 和 GAS
中存在不同的宏技術(shù),包括單行宏和宏重載,但是這里只關(guān)注基本類型。宏在匯編程序中的一個常見用途是提高代碼的清晰度。通過創(chuàng)建可重用的宏,可以避免重復(fù)
輸入相同的代碼段;這不但可以避免重復(fù),而且可以減少代碼量,從而提高代碼的可讀性。
NASM 使用 %beginmacro
指令聲明宏,用 %endmacro
指令結(jié)束聲明。%beginmacro
指令后面是宏的名稱。宏名稱后面是一個數(shù)字,這是這個宏需要的宏參數(shù)數(shù)量。在 NASM 中,宏參數(shù)是從 1 開始連續(xù)編號的。也就是說,宏的第一個參數(shù)是 %1,第二個是 %2,第三個是 %3,以此類推。例如:
%beginmacro macroname 2
mov eax, %1
mov ebx, %2
%endmacro
這創(chuàng)建一個有兩個參數(shù)的宏,第一個參數(shù)是 %1
,第二個參數(shù)是 %2
。因此,對上面的宏的調(diào)用如下所示:
macroname 5, 6
還可以創(chuàng)建沒有參數(shù)的宏,在這種情況下不指定任何數(shù)字。
現(xiàn)在看看 GAS 如何使用宏。GAS 提供 .macro
和 .endm
指令來創(chuàng)建宏。.macro
指令后面跟著宏名稱,后面可以有參數(shù),也可以沒有參數(shù)。在 GAS 中,宏參數(shù)是按名稱指定的。例如:
.macro macroname arg1, arg2
movl \arg1, %eax
movl \arg2, %ebx
.endm
當(dāng)在宏中使用宏參數(shù)名稱時,在名稱前面加上一個反斜線。如果不這么做,鏈接器會把名稱當(dāng)作標(biāo)簽而不是參數(shù),因此會報告錯誤。
函數(shù)、外部例程和堆棧
本節(jié)的示例程序在一個整數(shù)數(shù)組上實現(xiàn)選擇排序。
清單 4. 在整數(shù)數(shù)組上實現(xiàn)選擇排序
行號
NASM
GAS
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
section .data
array db
89, 10, 67, 1, 4, 27, 12, 34,
86, 3
ARRAY_SIZE equ $ - array
array_fmt db " %d", 0
usort_str db "unsorted array:", 0
sort_str db "sorted array:", 0
newline db 10, 0
section .text
extern puts
global _start
_start:
push usort_str
call puts
add esp, 4
push ARRAY_SIZE
push array
push array_fmt
call print_array10
add esp, 12
push ARRAY_SIZE
push array
call sort_routine20
; Adjust the stack pointer
add esp, 8
push sort_str
call puts
add esp, 4
push ARRAY_SIZE
push array
push array_fmt
call print_array10
add esp, 12
jmp _exit
extern printf
print_array10:
push ebp
mov ebp, esp
sub esp, 4
mov edx, [ebp + 8]
mov ebx, [ebp + 12]
mov ecx, [ebp + 16]
mov esi, 0
push_loop:
mov [ebp - 4], ecx
mov edx, [ebp + 8]
xor eax, eax
mov al, byte [ebx + esi]
push eax
push edx
call printf
add esp, 8
mov ecx, [ebp - 4]
inc esi
loop push_loop
push newline
call printf
add esp, 4
mov esp, ebp
pop ebp
ret
sort_routine20:
push ebp
mov ebp, esp
; Allocate a word of space in stack
sub esp, 4
; Get the address of the array
mov ebx, [ebp + 8]
; Store array size
mov ecx, [ebp + 12]
dec ecx
; Prepare for outer loop here
xor esi, esi
outer_loop:
; This stores the min index
mov [ebp - 4], esi
mov edi, esi
inc edi
inner_loop:
cmp edi, ARRAY_SIZE
jge swap_vars
xor al, al
mov edx, [ebp - 4]
mov al, byte [ebx + edx]
cmp byte [ebx + edi], al
jge check_next
mov [ebp - 4], edi
check_next:
inc edi
jmp inner_loop
swap_vars:
mov edi, [ebp - 4]
mov dl, byte [ebx + edi]
mov al, byte [ebx + esi]
mov byte [ebx + esi], dl
mov byte [ebx + edi], al
inc esi
loop outer_loop
mov esp, ebp
pop ebp
ret
_exit:
mov eax, 1
mov ebx, 0
int 80h
.section .data
array:
.byte 89, 10, 67, 1, 4, 27, 12,
34, 86, 3
array_end:
.equ ARRAY_SIZE, array_end - array
array_fmt:
.asciz " %d"
usort_str:
.asciz "unsorted array:"
sort_str:
.asciz "sorted array:"
newline:
.asciz "\n"
.section .text
.globl _start
_start:
pushl $usort_str
call puts
addl $4, %esp
pushl $ARRAY_SIZE
pushl $array
pushl $array_fmt
call print_array10
addl $12, %esp
pushl $ARRAY_SIZE
pushl $array
call sort_routine20
# Adjust the stack pointer
addl $8, %esp
pushl $sort_str
call puts
addl $4, %esp
pushl $ARRAY_SIZE
pushl $array
pushl $array_fmt
call print_array10
addl $12, %esp
jmp _exit
print_array10:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %edx
movl 12(%ebp), %ebx
movl 16(%ebp), %ecx
movl $0, %esi
push_loop:
movl %ecx, -4(%ebp)
movl 8(%ebp), %edx
xorl %eax, %eax
movb (%ebx, %esi, 1), %al
pushl %eax
pushl %edx
call printf
addl $8, %esp
movl -4(%ebp), %ecx
incl %esi
loop push_loop
pushl $newline
call printf
addl $4, %esp
movl %ebp, %esp
popl %ebp
ret
sort_routine20:
pushl %ebp
movl %esp, %ebp
# Allocate a word of space in stack
subl $4, %esp
# Get the address of the array
movl 8(%ebp), %ebx
# Store array size
movl 12(%ebp), %ecx
decl %ecx
# Prepare for outer loop here
xorl %esi, %esi
outer_loop:
# This stores the min index
movl %esi, -4(%ebp)
movl %esi, %edi
incl %edi
inner_loop:
cmpl $ARRAY_SIZE, %edi
jge swap_vars
xorb %al, %al
movl -4(%ebp), %edx
movb (%ebx, %edx, 1), %al
cmpb %al, (%ebx, %edi, 1)
jge check_next
movl %edi, -4(%ebp)
check_next:
incl %edi
jmp inner_loop
swap_vars:
movl -4(%ebp), %edi
movb (%ebx, %edi, 1), %dl
movb (%ebx, %esi, 1), %al
movb %dl, (%ebx, %esi, 1)
movb %al, (%ebx, %edi, 1)
incl %esi
loop outer_loop
movl %ebp, %esp
popl %ebp
ret
_exit:
movl $1, %eax
movl 0, %ebx
int $0x80
初看起來清單 4 似乎非常復(fù)雜,實際上它是非常簡單的。這個清單演示了函數(shù)、各種內(nèi)存尋址方案、堆棧和庫函數(shù)的使用方法。這個程序?qū)Π?10 個數(shù)字的數(shù)組進(jìn)行排序,并使用外部 C 庫函數(shù) puts
和 printf
輸出未排序數(shù)組和已排序數(shù)組的完整內(nèi)容。為了實現(xiàn)模塊化和介紹函數(shù)的概念,排序例程本身實現(xiàn)為一個單獨的過程,數(shù)組輸出例程也是這樣。我們來逐一分析一下。
在聲明數(shù)據(jù)之后,這個程序首先執(zhí)行對 puts
的調(diào)用(第 31 行)。puts
函數(shù)在控制臺上顯示一個字符串。它惟一的參數(shù)是要顯示的字符串的地址,通過將字符串的地址壓入堆棧(第 30 行),將這個參數(shù)傳遞給它。
在 NASM 中,任何不屬于我們的程序但是需要在鏈接時解析的標(biāo)簽都必須預(yù)先定義,這就是 extern
關(guān)鍵字的作用(第 24 行)。GAS 沒有這樣的要求。在此之后,字符串的地址 usort_str
被壓入堆棧(第 30 行)。在 NASM 中,內(nèi)存變量(比如 usort_str
)代表內(nèi)存位置本身,所以 push usort_str
這樣的調(diào)用實際上是將地址壓入堆棧的頂部。但是在 GAS 中,變量 usort_str
必須加上前綴 $
,這樣它才會被當(dāng)作地址。如果不加前綴 $
,那么會將內(nèi)存變量代表的實際字節(jié)壓入堆棧,而不是地址。
因為在堆棧中壓入一個變量會讓堆棧指針移動一個雙字,所以給堆棧指針加 4(雙字的大小)(第 32 行)。
現(xiàn)在將三個參數(shù)壓入堆棧,并調(diào)用 print_array10
函數(shù)(第 37 行)。在 NASM 和 GAS 中聲明函數(shù)的方法是相同的。它們僅僅是通過 call
指令調(diào)用的標(biāo)簽。
在調(diào)用函數(shù)之后,ESP 代表堆棧的頂部。esp + 4
代表返回地址,esp + 8
代表函數(shù)的第一個參數(shù)。在堆棧指針上加上雙字變量的大小(即 esp + 12
、esp + 16
等等),就可以訪問所有后續(xù)參數(shù)。
在函數(shù)內(nèi)部,通過將 esp
復(fù)制到 ebp
(第 62 行)創(chuàng)建一個局部堆棧框架。和程序中的處理一樣,還可以為局部變量分配空間(第 63 行)。方法是從 esp
中減去所需的字節(jié)數(shù)。esp – 4
表示為一個局部變量分配 4 字節(jié)的空間,只要堆棧中有足夠的空間容納局部變量,就可以繼續(xù)分配。
清單 4 演示了基間接尋址模式(第 64 行),也就是首先取得一個基地址,然后在它上面加一個偏移量,從而到達(dá)最終的地址。在清單的 NASM 部分中,[ebp + 8]
和 [ebp – 4]
(第 71 行)就是基間接尋址模式的示例。在 GAS 中,尋址方法更簡單一些:4(%ebp)
和 -4(%ebp)
。
在 print_array10
例程中,在 push_loop
標(biāo)簽后面可以看到另一種尋址模式(第 74 行)。在 NASM 和 GAS 中的表示方法如下:
NASM:mov al, byte [ebx + esi]
GAS:movb (%ebx, %esi, 1), %al
這種尋址模式稱為基索引尋址模式。這里有三項數(shù)據(jù):一個是基地址,第二個是索引寄存器,第三個是乘數(shù)。因為不可能決定從一
個內(nèi)存位置開始訪問的字節(jié)數(shù),所以需要用一個方法計算訪問的內(nèi)存量。NASM 使用字節(jié)操作符告訴匯編器要移動一個字節(jié)的數(shù)據(jù)。在 GAS
中,用一個乘數(shù)和助記符中的 b
、w
或 l
后綴(例如 movb
)來解決這個問題。初看上去 GAS 的語法似乎有點兒復(fù)雜。
GAS 中基索引尋址模式的一般形式如下:
%segment:ADDRESS (, index, multiplier)
或
%segment:(offset, index, multiplier)
或
%segment:ADDRESS(base, index, multiplier)
使用這個公式計算最終的地址:
ADDRESS or offset + base + index * multiplier.
因此,要想訪問一個字節(jié),就使用乘數(shù) 1;對于字,乘數(shù)是 2;對于雙字,乘數(shù)是 4。當(dāng)然,NASM 使用的語法比較簡單。上面的公式在 NASM 中表示為:
Segment:[ADDRESS or offset + index * multiplier]
為了訪問 1、2 或 4 字節(jié)的內(nèi)存,在這個內(nèi)存地址前面分別加上 byte
、word
或 dword
。
其他方面
清單 5 讀取命令行參數(shù)的列表,將它們存儲在內(nèi)存中,然后輸出它們。
清單 5. 讀取命令行參數(shù),將它們存儲在內(nèi)存中,然后輸出它們
行號
NASM
GAS
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
section .data
; Command table to store at most
; 10 command line arguments
cmd_tbl:
%rep 10
dd 0
%endrep
section .text
global _start
_start:
; Set up the stack frame
mov ebp, esp
; Top of stack contains the
; number of command line arguments.
; The default value is 1
mov ecx, [ebp]
; Exit if arguments are more than 10
cmp ecx, 10
jg _exit
mov esi, 1
mov edi, 0
; Store the command line arguments
; in the command table
store_loop:
mov eax, [ebp + esi * 4]
mov [cmd_tbl + edi * 4], eax
inc esi
inc edi
loop store_loop
mov ecx, edi
mov esi, 0
extern puts
print_loop:
; Make some local space
sub esp, 4
; puts function corrupts ecx
mov [ebp - 4], ecx
mov eax, [cmd_tbl + esi * 4]
push eax
call puts
add esp, 4
mov ecx, [ebp - 4]
inc esi
loop print_loop
jmp _exit
_exit:
mov eax, 1
mov ebx, 0
int 80h
.section .data
// Command table to store at most
// 10 command line arguments
cmd_tbl:
.rept 10
.long 0
.endr
.section .text
.globl _start
_start:
// Set up the stack frame
movl %esp, %ebp
// Top of stack contains the
// number of command line arguments.
// The default value is 1
movl (%ebp), %ecx
// Exit if arguments are more than 10
cmpl $10, %ecx
jg _exit
movl $1, %esi
movl $0, %edi
// Store the command line arguments
// in the command table
store_loop:
movl (%ebp, %esi, 4), %eax
movl %eax, cmd_tbl( , %edi, 4)
incl %esi
incl %edi
loop store_loop
movl %edi, %ecx
movl $0, %esi
print_loop:
// Make some local space
subl $4, %esp
// puts functions corrupts ecx
movl %ecx, -4(%ebp)
movl cmd_tbl( , %esi, 4), %eax
pushl %eax
call puts
addl $4, %esp
movl -4(%ebp), %ecx
incl %esi
loop print_loop
jmp _exit
_exit:
movl $1, %eax
movl $0, %ebx
int $0x80
清單 5 演示在匯編程序中重復(fù)執(zhí)行指令的方法。很自然,這種結(jié)構(gòu)稱為重復(fù)結(jié)構(gòu)。在 GAS 中,重復(fù)結(jié)構(gòu)以 .rept
指令開頭(第 6 行)。用一個 .endr
指令結(jié)束這個指令(第 8 行)。.rept
后面是一個數(shù)字,它指定 .rept/.endr
結(jié)構(gòu)中表達(dá)式重復(fù)執(zhí)行的次數(shù)。這個結(jié)構(gòu)中的任何指令都相當(dāng)于編寫這個指令 count
次,每次重復(fù)占據(jù)單獨的一行。
例如,如果次數(shù)是 3:
.rept 3
movl $2, %eax
.endr
就相當(dāng)于:
movl $2, %eax
movl $2, %eax
movl $2, %eax
在 NASM 中,在預(yù)處理器級使用相似的結(jié)構(gòu)。它以 %rep
指令開頭,以 %endrep
結(jié)尾。%rep
指令后面是一個表達(dá)式(在 GAS 中 .rept
指令后面是一個數(shù)字):
%rep
nop
%endrep
在 NASM 中還有另一種結(jié)構(gòu),times
指令。與 %rep
相似,它也在匯編級起作用,后面也是一個表達(dá)式。例如,上面的 %rep
結(jié)構(gòu)相當(dāng)于:
times nop
以下代碼:
%rep 3
mov eax, 2
%endrep
相當(dāng)于:
times 3 mov eax, 2
它們都相當(dāng)于:
mov eax, 2
mov eax, 2
mov eax, 2
在清單 5 中,使用 .rept
(或 %rep
)指令為 10 個雙字創(chuàng)建內(nèi)存數(shù)據(jù)區(qū)。然后,從堆棧一個個地訪問命令行參數(shù),并將它們存儲在內(nèi)存區(qū)中,直到命令表填滿。
在這兩種匯編器中,訪問命令行參數(shù)的方法是相似的。ESP(堆棧頂部)存儲傳遞給程序的命令行參數(shù)數(shù)量,默認(rèn)值是 1(表示沒有命令行參數(shù))。esp + 4
存儲第一個命令行參數(shù),這總是從命令行調(diào)用的程序的名稱。esp + 8
、esp + 12
等存儲后續(xù)命令行參數(shù)。
還要注意清單 5 中從兩邊訪問內(nèi)存命令表的方法。這里使用內(nèi)存間接尋址模式(第 31 行)訪問命令表,還使用了 ESI(和 EDI)中的偏移量和一個乘數(shù)。因此,NASM 中的 [cmd_tbl + esi * 4]
相當(dāng)于 GAS 中的 cmd_tbl(, %esi, 4)
。
結(jié)束語
盡管在這兩種匯編器之間存在實質(zhì)性的差異,但是在這兩種形式之間進(jìn)行轉(zhuǎn)換并不困難。您最初可能覺得 AT&T 語法難以理解,但是掌握了它之后,它其實和 Intel 語法同樣簡單。
總結(jié)
以上是生活随笔為你收集整理的linux汇编中的注释,Linux 汇编器:对照 GAS 和 NASM的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 64位userdata.dll丢失_有什
- 下一篇: mysql 表ful,你所不知的tabl