linux匯編中的注釋,Linux 匯編器:對照 GAS 和 NASM

Linux 匯編器:對比 GAS 和 NASM

轉自 http://www.ibm.com/developerworks/cn/linux/l-gas-nasm.html#ibm-pcon

與其他語言不同,匯編語言

要求開發人員了解編程所用機器的處理器體系結構。匯編程序不可移植,維護和理解常常比較麻煩,通常包含大量代碼行。但是,在機器上執行的運行時二進制代碼在速度和大小方面有優勢。

對于在 Linux 上進行匯編級編程已經有許多參考資料,本文主要講解語法之間的差異,幫助您更輕松地在匯編形式之間進行轉換。本文源于我自己試圖改進這種轉換的嘗試。

本文使用一系列程序示例。每個程序演示一些特性,然后是對語法的討論和對比。盡管不可能討論 NASM 和 GAS

之間存在的每個差異,但是我試圖討論主要方面,給進一步研究提供一個基礎。那些已經熟悉 NASM 和 GAS

的讀者也可以在這里找到有用的內容,比如宏。

本文假設您至少基本了解匯編的術語,曾經用符合 Intel? 語法的匯編器編寫過程序,可能在 Linux 或 Windows 上使用過 NASM。本文并不講解如何在編輯器中輸入代碼,或者如何進行匯編和鏈接(但是下面的邊欄可以幫助您 快速回憶一下

)。您應該熟悉 Linux 操作系統(任何 Linux 發行版都可以;我使用的是 Red Hat 和 Slackware)和基本的 GNU 工具,比如 gcc 和 ld,還應該在 x86 機器上進行編程。

現在,我描述一下本文討論的范圍。

構建示例

匯編:

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 之間的基本語法差異

常用的匯編級結構,比如變量、循環、標簽和宏

關于調用外部 C 例程和使用函數的信息

匯編助記符差異和使用方法

內存尋址方法

本文不討論:

處理器指令集

一種匯編器特有的各種宏形式和其他結構

NASM 或 GAS 特有的匯編器指令

不常用的特性,或者只在一種匯編器中出現的特性

更多信息請參考匯編器的官方手冊(參見 參考資料

中的鏈接),因為這些手冊是最完整的信息源。

基本結構

清單 1 給出一個非常簡單的程序,它的作用僅僅是使用退出碼 2 退出。這個小程序展示了 NASM 和 GAS 的匯編程序的基本結構。

清單 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

現在解釋一下。

NASM 和 GAS 之間最大的差異之一是語法。GAS 使用 AT&T 語法,這是一種相當老的語法,由 GAS 和一些老式匯編器使用;NASM 使用 Intel 語法,大多數匯編器都支持它,包括 TASM 和 MASM。(GAS 的現代版本支持 .intel_syntax

指令,因此允許在 GAS 中使用 Intel 語法。)

下面是從 GAS 手冊總結出的一些主要差異:

AT&T 和 Intel 語法采用相反的源和目標操作數次序。例如:

Intel:mov eax, 4

AT&T:movl $4, %eax

在 AT&T 語法中,中間操作數前面加 $

;在 Intel 語法中,中間操作數不加前綴。例如:

Intel:push 4

AT&T:pushl $4

在 AT&T 語法中,寄存器操作數前面加 %

。在 Intel 語法中,它們不加前綴。

在 AT&T 語法中,內存操作數的大小由操作碼名稱的最后一個字符決定。操作碼后綴 b

、w

和 l

分別指定字節(8 位)、字(16 位)和長(32 位)內存引用。Intel 語法通過在內存操作數(而不是操作碼本身)前面加 byte ptr

、word ptr

和 dword ptr

來指定大小。所以:

Intel:mov al, byte ptr foo

AT&T:movb foo, %al

在 AT&T 語法中,中間形式長跳轉和調用是 lcall/ljmp $section, $offset

;Intel 語法是 call/jmp far section:offset

。在 AT&T 語法中,遠返回指令是 lret $stack-adjust

,而 Intel 使用 ret far stack-adjust

在這兩種匯編器中,寄存器的名稱是一樣的,但是因為尋址模式不同,使用它們的語法是不同的。另外,GAS 中的匯編器指令以 “.” 開頭,但是在 NASM 中不是。

.text

部分是處理器開始執行代碼的地方。global

(或者 GAS 中的 .globl

或 .global

)關鍵字用來讓一個符號對鏈接器可見,可以供其他鏈接對象模塊使用。在清單 1 的 NASM 部分中,global _start

讓 _start

符號成為可見的標識符,這樣鏈接器就知道跳轉到程序中的什么地方并開始執行。與 NASM 一樣,GAS 尋找這個 _start

標簽作為程序的默認進入點。在 GAS 和 NASM 中標簽都以冒號結尾。

中斷是一種通知操作系統需要它的服務的一種方法。第 16 行中的 int

指令執行這個工作。GAS 和 NASM 對中斷使用同樣的助記符。GAS 使用 0x

前綴指定十六進制數字,NASM 使用 h

后綴。因為在 GAS 中中間操作數帶 $

前綴,所以 80 hex 是 $0x80

int $0x80

(或 NASM 中的 80h

)用來向 Linux 請求一個服務。服務編碼放在 EAX 寄存器中。EAX 中存儲的值是 1(代表 Linux exit 系統調用),這請求程序退出。EBX 寄存器包含退出碼(在這個示例中是 2),也就是返回給操作系統的一個數字。(可以在命令提示下輸入 echo $?

來檢查這個數字。)

最后討論一下注釋。GAS 支持 C 風格(/* */

)、C++ 風格(//

)和 shell 風格(#

)的注釋。NASM 支持以 “;” 字符開頭的單行注釋。

回頁首

變量和內存訪問

本節首先給出一個示例程序,它尋找三個數字中的最大者。

清單 2. 尋找三個數字中最大者的程序

行號

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

在上面的內存變量聲明中可以看到幾點差異。NASM 分別使用 dd

、dw

和 db

指令聲明 32 位、16 位和 8 位數字,而 GAS 分別使用 .long

、.int

和 .byte

。GAS 還有其他指令,比如 .ascii

、.asciz

和 .string

。在 GAS 中,像聲明其他標簽一樣聲明變量(使用冒號),但是在 NASM 中,只需在內存分配指令(dd

、dw

等等)前面輸入變量名,后面加上變量的值。

清單 2 中的第 18 行演示內存直接尋址模式。NASM 使用方括號間接引用一個內存位置指向的地址值:[var1]

。GAS 使用圓括號間接引用同樣的值:(var1)

。本文后面討論其他尋址模式的使用方法。

回頁首

使用宏

清單 3 演示本節討論的概念;它接受用戶名作為輸入并返回一句問候語。

清單 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

本節要討論宏以及 NASM 和 GAS 對它們的支持。但是,在討論宏之前,先與其他幾個特性做一下比較。

清單 3 演示了未初始化內存的概念,這是用 .bss

部分指令(第 14

行)定義的。BSS 代表 “block storage segment” (原來是以一個符號開頭的塊),BSS

部分中保留的內存在程序啟動時初始化為零。BSS 部分中的對象只有一個名稱和大小,沒有值。與數據部分中不同,BSS

部分中聲明的變量并不實際占用空間。

NASM 使用 resb

、resw

和 resd

關鍵字在 BSS 部分中分配字節、字和雙字空間。GAS 使用 .lcomm

關鍵字分配字節級空間。請注意在這個程序的兩個版本中聲明變量名的方式。在 NASM 中,變量名前面加 resb

(或 resw

或 resd

)關鍵字,后面是要保留的空間量;在 GAS 中,變量名放在 .lcomm

關鍵字的后面,然后是一個逗號和要保留的空間量。

NASM:varname resb size

GAS:.lcomm varname, size

清單 3 還演示了位置計數器的概念(第 6 行)。

NASM 提供特殊的變量($

和 $$

變量)來操作位置計數器。在 GAS 中,無法操作位置計數器,必須使用標簽計算下一個存儲位置(數據、指令等等)。

例如,為了計算一個字符串的長度,在 NASM 中會使用以下指令:

prompt_str db 'Enter your name: '

STR_SIZE equ $ - prompt_str

; $ is the location counter

$

提供位置計數器的當前值,從這個位置計數器中減去標簽的值(所有變量名都是標簽),就會得出標簽的聲明和當前位置之間的字節數。equ

用來將變量 STR_SIZE 的值設置為后面的表達式。GAS 中使用的相似指令如下:

prompt_str:

.ascii "Enter Your Name: "

pstr_end:

.set STR_SIZE, pstr_end - prompt_str

末尾標簽(pstr_end

)給出下一個位置地址,減去啟始標簽地址就得出大小。還要注意,這里使用 .set

將變量 STR_SIZE 的值設置為逗號后面的表達式。也可以使用對應的 .equ

。在 NASM 中,沒有與 GAS 的 set

指令對應的指令。

正如前面提到的,清單 3 使用了宏(第 21 行)。在 NASM 和 GAS

中存在不同的宏技術,包括單行宏和宏重載,但是這里只關注基本類型。宏在匯編程序中的一個常見用途是提高代碼的清晰度。通過創建可重用的宏,可以避免重復

輸入相同的代碼段;這不但可以避免重復,而且可以減少代碼量,從而提高代碼的可讀性。

NASM 使用 %beginmacro

指令聲明宏,用 %endmacro

指令結束聲明。%beginmacro

指令后面是宏的名稱。宏名稱后面是一個數字,這是這個宏需要的宏參數數量。在 NASM 中,宏參數是從 1 開始連續編號的。也就是說,宏的第一個參數是 %1,第二個是 %2,第三個是 %3,以此類推。例如:

%beginmacro macroname 2

mov eax, %1

mov ebx, %2

%endmacro

這創建一個有兩個參數的宏,第一個參數是 %1

,第二個參數是 %2

。因此,對上面的宏的調用如下所示:

macroname 5, 6

還可以創建沒有參數的宏,在這種情況下不指定任何數字。

現在看看 GAS 如何使用宏。GAS 提供 .macro

和 .endm

指令來創建宏。.macro

指令后面跟著宏名稱,后面可以有參數,也可以沒有參數。在 GAS 中,宏參數是按名稱指定的。例如:

.macro macroname arg1, arg2

movl \arg1, %eax

movl \arg2, %ebx

.endm

當在宏中使用宏參數名稱時,在名稱前面加上一個反斜線。如果不這么做,鏈接器會把名稱當作標簽而不是參數,因此會報告錯誤。

函數、外部例程和堆棧

本節的示例程序在一個整數數組上實現選擇排序。

清單 4. 在整數數組上實現選擇排序

行號

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 似乎非常復雜,實際上它是非常簡單的。這個清單演示了函數、各種內存尋址方案、堆棧和庫函數的使用方法。這個程序對包含 10 個數字的數組進行排序,并使用外部 C 庫函數 puts

和 printf

輸出未排序數組和已排序數組的完整內容。為了實現模塊化和介紹函數的概念,排序例程本身實現為一個單獨的過程,數組輸出例程也是這樣。我們來逐一分析一下。

在聲明數據之后,這個程序首先執行對 puts

的調用(第 31 行)。puts

函數在控制臺上顯示一個字符串。它惟一的參數是要顯示的字符串的地址,通過將字符串的地址壓入堆棧(第 30 行),將這個參數傳遞給它。

在 NASM 中,任何不屬于我們的程序但是需要在鏈接時解析的標簽都必須預先定義,這就是 extern

關鍵字的作用(第 24 行)。GAS 沒有這樣的要求。在此之后,字符串的地址 usort_str

被壓入堆棧(第 30 行)。在 NASM 中,內存變量(比如 usort_str

)代表內存位置本身,所以 push usort_str

這樣的調用實際上是將地址壓入堆棧的頂部。但是在 GAS 中,變量 usort_str

必須加上前綴 $

,這樣它才會被當作地址。如果不加前綴 $

,那么會將內存變量代表的實際字節壓入堆棧,而不是地址。

因為在堆棧中壓入一個變量會讓堆棧指針移動一個雙字,所以給堆棧指針加 4(雙字的大小)(第 32 行)。

現在將三個參數壓入堆棧,并調用 print_array10

函數(第 37 行)。在 NASM 和 GAS 中聲明函數的方法是相同的。它們僅僅是通過 call

指令調用的標簽。

在調用函數之后,ESP 代表堆棧的頂部。esp + 4

代表返回地址,esp + 8

代表函數的第一個參數。在堆棧指針上加上雙字變量的大小(即 esp + 12

、esp + 16

等等),就可以訪問所有后續參數。

在函數內部,通過將 esp

復制到 ebp

(第 62 行)創建一個局部堆棧框架。和程序中的處理一樣,還可以為局部變量分配空間(第 63 行)。方法是從 esp

中減去所需的字節數。esp – 4

表示為一個局部變量分配 4 字節的空間,只要堆棧中有足夠的空間容納局部變量,就可以繼續分配。

清單 4 演示了基間接尋址模式(第 64 行),也就是首先取得一個基地址,然后在它上面加一個偏移量,從而到達最終的地址。在清單的 NASM 部分中,[ebp + 8]

和 [ebp – 4]

(第 71 行)就是基間接尋址模式的示例。在 GAS 中,尋址方法更簡單一些:4(%ebp)

和 -4(%ebp)

在 print_array10

例程中,在 push_loop

標簽后面可以看到另一種尋址模式(第 74 行)。在 NASM 和 GAS 中的表示方法如下:

NASM:mov al, byte [ebx + esi]

GAS:movb (%ebx, %esi, 1), %al

這種尋址模式稱為基索引尋址模式。這里有三項數據:一個是基地址,第二個是索引寄存器,第三個是乘數。因為不可能決定從一

個內存位置開始訪問的字節數,所以需要用一個方法計算訪問的內存量。NASM 使用字節操作符告訴匯編器要移動一個字節的數據。在 GAS

中,用一個乘數和助記符中的 b

、w

或 l

后綴(例如 movb

)來解決這個問題。初看上去 GAS 的語法似乎有點兒復雜。

GAS 中基索引尋址模式的一般形式如下:

%segment:ADDRESS (, index, multiplier)

%segment:(offset, index, multiplier)

%segment:ADDRESS(base, index, multiplier)

使用這個公式計算最終的地址:

ADDRESS or offset + base + index * multiplier.

因此,要想訪問一個字節,就使用乘數 1;對于字,乘數是 2;對于雙字,乘數是 4。當然,NASM 使用的語法比較簡單。上面的公式在 NASM 中表示為:

Segment:[ADDRESS or offset + index * multiplier]

為了訪問 1、2 或 4 字節的內存,在這個內存地址前面分別加上 byte

、word

或 dword

其他方面

清單 5 讀取命令行參數的列表,將它們存儲在內存中,然后輸出它們。

清單 5. 讀取命令行參數,將它們存儲在內存中,然后輸出它們

行號

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 演示在匯編程序中重復執行指令的方法。很自然,這種結構稱為重復結構。在 GAS 中,重復結構以 .rept

指令開頭(第 6 行)。用一個 .endr

指令結束這個指令(第 8 行)。.rept

后面是一個數字,它指定 .rept/.endr

結構中表達式重復執行的次數。這個結構中的任何指令都相當于編寫這個指令 count

次,每次重復占據單獨的一行。

例如,如果次數是 3:

.rept 3

movl $2, %eax

.endr

就相當于:

movl $2, %eax

movl $2, %eax

movl $2, %eax

在 NASM 中,在預處理器級使用相似的結構。它以 %rep

指令開頭,以 %endrep

結尾。%rep

指令后面是一個表達式(在 GAS 中 .rept

指令后面是一個數字):

%rep

nop

%endrep

在 NASM 中還有另一種結構,times

指令。與 %rep

相似,它也在匯編級起作用,后面也是一個表達式。例如,上面的 %rep

結構相當于:

times nop

以下代碼:

%rep 3

mov eax, 2

%endrep

相當于:

times 3 mov eax, 2

它們都相當于:

mov eax, 2

mov eax, 2

mov eax, 2

在清單 5 中,使用 .rept

(或 %rep

)指令為 10 個雙字創建內存數據區。然后,從堆棧一個個地訪問命令行參數,并將它們存儲在內存區中,直到命令表填滿。

在這兩種匯編器中,訪問命令行參數的方法是相似的。ESP(堆棧頂部)存儲傳遞給程序的命令行參數數量,默認值是 1(表示沒有命令行參數)。esp + 4

存儲第一個命令行參數,這總是從命令行調用的程序的名稱。esp + 8

、esp + 12

等存儲后續命令行參數。

還要注意清單 5 中從兩邊訪問內存命令表的方法。這里使用內存間接尋址模式(第 31 行)訪問命令表,還使用了 ESI(和 EDI)中的偏移量和一個乘數。因此,NASM 中的 [cmd_tbl + esi * 4]

相當于 GAS 中的 cmd_tbl(, %esi, 4)

結束語

盡管在這兩種匯編器之間存在實質性的差異,但是在這兩種形式之間進行轉換并不困難。您最初可能覺得 AT&T 語法難以理解,但是掌握了它之后,它其實和 Intel 語法同樣簡單。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/275489.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/275489.shtml
英文地址,請注明出處:http://en.pswp.cn/news/275489.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

你可能不知道的package.json

大家好,我是若川。最近組織了源碼共度活動:1個月,200人,一起讀了4周源碼,參與的小伙伴都表示收獲很大。如果感興趣可以點擊鏈接掃碼加我微信 ruochuan12。今天推薦一篇相對簡單的文章。前言在上一篇npm init vitejs/ap…

基于上下文的rpn_構建事物-產品評論視頻中基于上下文的情感分析

基于上下文的rpnThe word “Social” has taken a whole new meaning in today’s digital era. Simply going out to enjoy is no longer the only “social” criteria. Social now is — giving a peek in your personal and professional life to your connections. Facebo…

可愛的 Python: 使用 mechanize 和 Beautiful Soup 輕松收集 Web 數據

可愛的 Python: 使用 mechanize 和 Beautiful Soup 輕松收集 Web 數據 使用 Python 工具簡化 Web 站點數據的提取和組織 David Mertz, Ph.D., 開發人員, Gnosis Software, Inc.從 2000 年開始,David Mertz 就一直在為 developerWorks 專欄 Charming Python 和 XML M…

廣西工學院c語言試題答案,廣西工學院的C語言考試試題

廣西工學院鹿山學院 2005 — 2006 學年第 2 學期課程考核試題 考核課程 《C語言程序設計》 (A卷)考核班級 學生數 印數 考核方式 閉卷 考核時間 120 分鐘一、選擇題(每題2分,共40分)1. 一個C語言的源程序中, 。A.必須有一個主函數2. 下列數據…

JavaScript 斷點調試技巧

大家好,我是若川。最近組織了源碼共度活動:1個月,200人,一起讀了4周源碼,參與的小伙伴都表示收獲很大。如果感興趣可以點擊鏈接掃碼加我微信 ruochuan12。之前推薦過很多次調試文章,說明調試的重要性&#…

大學生電子設計大賽案例分析_為大學生設計問答平臺—案例研究

大學生電子設計大賽案例分析Dealing with academic-related questions like picking a course, fulfilling a major requirement can be tedious and ineffective when you have to simultaneously balance school work, social activities, and focus on personal growth and …

最新最詳細最簡潔Eclipse調試PHP配置詳解(Xdebug,Zend Debugger)

搬家注:該日志寫于2011 年 04 月 07 日,Eclipse,PHP等版本號很多,更新也比較快,請注意文章中的版本。本文不一定幫您解決問題,但能給您一些解決問題的思路及一些概念。 最近開始做SRTP項目WebOS&#xff0c…

按鍵精靈易語言c,求助(把按鍵精靈的源碼轉為易語言的)

該樓層疑似違規已被系統折疊 隱藏此樓查看此樓MoveTo 1203,673IfColor 1203,673,"252489",2 ThenMoveTo 417, 242Delay 10072LeftDown 1LeftClick 1LeftUp 1MoveTo 982, 551Delay 7660LeftDoubleClick 1Delay 10Delay 30LeftUp 1LeftUp 1LeftUp 1MoveTo 1102, 709Del…

入門前端學習路線圖【送書】

大家好,我是若川。記得點上方音頻聽小姐姐配音,超級好聽。華章圖書又贊助了書籍送福利給大家。本次送4本書的抽獎方式是:截止到9月6日(周一)20:00,在留言區留言任意內容。我會在留言區抽取「1位」關注我公眾…

單選按鈕設置為被選中狀態_為什么要設置錯誤的按鈕狀態

單選按鈕設置為被選中狀態當正確的方法出錯時 (When the right way goes wrong) Let’s say you want to create a click effect on an HTML button. The first idea that many people get is to do something that reproduces the feeling of the sound emitted by a real but…

「娃娃分享」-常見自校檢分析實例.

自校檢是許多軟件的保護手段之一,對軟件加個簡單的殼再增加自校檢在一定程序上可以抵擋住一大部分新手,不過,對許多人來說,這個保護已經很弱了。。下面講幾種常見的解決自校檢方法,寫的粗略,希望大家補充。…

用VC和MinGW導出dll的def和lib(a)文件

為什么80%的碼農都做不了架構師?>>> 原文地址:http://zhangyafeikimi.iteye.com/blog/404580 有了dll文件需要導出def文件: pexports zlib1.dll > zlib1.def 有了dll和def文件,需要導出MinGW的.a文件:…

51中斷編程c語言,[新人求指教]51C語言編程可否用中斷令循環結束提早結束

該樓層疑似違規已被系統折疊 隱藏此樓查看此樓C51_C語言編程控制流水燈硬件電路 p0 接 led 8 個,P33 接按鍵使用中斷2開機燈按1~8逐位閃爍,并循環按鍵后改為 兩燈亮 的流水燈下面寫了個程序#include #include #define uchar unsigned char#define uint u…

產品設計美學案例分析_美學在產品設計中的重要性

產品設計美學案例分析重點 (Top highlight)In one of my previous jobs, I had really interesting debates with the CEO regarding whether we should spend more time improving the way our app looks and feels. ‘How could he not care that the design is outdated?! …

即將到來的ECMAScript 2022標準

大家好,我是若川。周末分享一篇相對簡單的文章。最近組織了源碼共度活動:1個月,200人,一起讀了4周源碼,參與的小伙伴都表示收獲很大。如果感興趣可以點擊鏈接掃碼加我微信 ruochuan12。另外:昨天的推文入門…

c語言中二叉樹中總結點,C語言二叉樹的三種遍歷方式的實現及原理

二叉樹遍歷分為三種:前序、中序、后序,其中序遍歷最為重要。為啥叫這個名字?是根據根節點的順序命名的。比如上圖正常的一個滿節點,A:根節點、B:左節點、C:右節點,前序順序是ABC(根節…

動態庫的創建與使用

1、動態庫文件的創建 (1)編寫源文件 (2)編譯生成動態庫 g -fPIC -shared -o libfile_operation.so file_operation.cpp 此編譯過程分為兩步,等同于下面的兩個命令: g -c -fPIC file_operation.cpp …

ux設計中的各種地圖_UX寫作中的移情

ux設計中的各種地圖Demetri Martin is a master of comedic situations. If you’ve never seen Demetri Martin是喜劇情境的大師。 如果你從未見過 him before, he has a sort of dry brand of observational humor, relying more on anecdotes than full stories, and often…

字符串搜索。HOJ1530 Compound Words。

stl set實現字符串搜索。。效率一般。(附二分搜索。) Compound WordsTime limit:1sec.Submitted:233Memory limit:32MAccepted:81Source: Waterloo ACM Programming Contest Sep 28, 1996 You are to find all the two-word compound words in a dictionary. A two-word compo…

字節3-1前端面試官自學Vue的正確姿勢

大家好,我是若川。前不久和一個字節前端TL朋友聊天,說到大廠前端供需脫節的情況。特別是使用Vue框架的,因為簡單易學好上手,但是能夠深入理解的人并不多,大多都只停留在應用層面,缺乏更深層面的理解。尤其是…