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 语法同样简单。

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

  1. mint linux更新内核,如何在Ubuntu/Linux Mint中安装最新Linux 5.2.5内核

    原标题:如何在Ubuntu/Linux Mint中安装最新Linux 5.2.5内核 Linux 5.2的Ubuntu主线内核包最终可以在32位和64位操作系统中下载和安装. 由于构建失败,Linux ...

  2. linux文件中的注释符号,Linux中特殊符号的作用

    如果不学习Linux或者学习编程语言,也许键盘上有些字符一辈子都不会用到,但是现在学习了敲命令,这些字符都"活"了,而且还有非常非常重要的作用,这里我们就来系统的总结一下,这些特殊 ...

  3. linux内核中的jiffies,Linux内核中的jiffies及其作用介绍及jiffies等相关函数详解

    在LINUX的时钟中断中涉及至二个全局变量一个是xtime,它是timeval数据结构变量,另一个则是jiffies,首先看timeval结构 struct timeval { time_t tv_s ...

  4. 【Linux 内核】进程管理 ( 进程特殊形式 | 内核线程 | 用户线程 | C 标准库与 Linux 内核中进程相关概念 | Linux 查看进程命令及输出字段解析 )

    文章目录 一.进程特殊形式 ( 内核线程 | 用户线程 ) 二.C 标准库与 Linux 内核中进程相关概念 三.Linux 查看进程命令及输出字段解析 一.进程特殊形式 ( 内核线程 | 用户线程 ...

  5. head在linux命令中什么意思,linux系统中head命令使用说明

    linux系统中head命令使用说明 head 与 tail 就像它的名字一样的'浅显易懂,它是用来显示开头或结尾某个数量的文字区块,head 用来显示档案的开头至标准输出中,而 tail 想当然尔就 ...

  6. linux命令中选项分为,Linux 考试试题

    Linux 考试试题 一.选择题 (每小题2分,共50分) 1.在创建Linux分区时,一定要创建( D )两个分区 A. FAT/NTFS B. FAT/SWAP C. NTFS/SWAP D.SW ...

  7. linux系统中mywho命令,linux查看在线用户 who命令参数及用法

    linux who 命令 详解 Linux最常用命令之一 功能说明:显示目前登入系统的用户信息. 语 法:who [-Himqsw][--help][--version][am i][记录文件] 补充 ...

  8. linux系统中ssh命令,Linux系统中SSH 命令的用法有哪些?

    今天小编要跟大家分享的文章是关于Linux系统中SSH命令的用法有哪些?一个系统管理员可能会同时管理着多台服务器,这些服务器也许会放在不同的地方.要亲自一台一台的去访问来管理它们显然不是最好的方法,通 ...

  9. linux命令中插入制表符,linux 中grep 匹配制表符 和 换行符的命令

    linux 中grep 匹配制表符 和 换行符的命令 使用: [root@dhcp-9-79 ~]# grep $'\n' log.txt [root@dhcp-9-79 ~]# grep $'\t' ...

最新文章

  1. 在Ubuntu 14.04 64bit上进行md5加密编程
  2. java jar 版本号_java – 获取JAR文件版本号
  3. 如何使用Spring优雅地处理REST异常?
  4. java url特殊字符处理_简单实例处理url特殊符号处理(2种方法)
  5. 论文,采购管理(背诵)
  6. MySql数据库索引底层数据结构
  7. Fragment的布局中自定义Layout的onSizeChanged添加组件失败的问题
  8. 作者:罗威,男,中国国防科技信息中心副研究员。
  9. 如何学计算机课程,一张图告诉你大学如何学好计算机专业课程
  10. mysql mvcc实例讲解_轻松理解MYSQL MVCC 实现机制
  11. python数据可视化源码_Python数据分析:数据可视化实战教程
  12. Dokcer使用总结(Dockerfile、Compose、Swarm)
  13. vivi开发笔记【专辑】
  14. 线程池(python)
  15. Py之shap:shap.explainers.shap_values函数的简介、解读(shap_values[1]索引为1的原因)、使用方法之详细攻略
  16. mysql5.7 1698 28000_MySqlError1698(28000)问题的解决方法
  17. vb.net画上下左右方向的箭头
  18. C/C++超全资料,编程发烧友不可不分享~~~~~~~~~
  19. 知云文献翻译打不开_PDF翻译/PDF边划边译,最好的PDF翻译软件
  20. 联想计算机如何进bois,小编教你联想电脑怎么进入bios

热门文章

  1. hdu1728--------坑爹啊
  2. 深入浅出Nintex——更新PeopleandGroup类型的Field
  3. 如何定位死循环或高CPU使用率(linux)
  4. 浅析Page.LoadTemplate(模板)方法动态获取绑定模板后,通过FindControl获取服务端控件的方法。...
  5. getAndIncrement中使用cas
  6. java证明ArrayList是线程不安全的
  7. 为对象分配内存TLAB
  8. 福禄克宣布推出 FiberLert,口袋大小的实时光纤探测器
  9. json 取值判断_【收藏级】.NETCore3.1中的Json互操作解读
  10. 20个堪称神器的Linux命令行软件