日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux汇编中的注释,Linux 汇编器:对照 GAS 和 NASM

發(fā)布時間:2023/12/9 linux 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux汇编中的注释,Linux 汇编器:对照 GAS 和 NASM 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

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)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。