; 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 |
---|
; 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 |
---|
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 |
---|
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
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
_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
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
_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 |
---|
section .data
; Command table to store at most
; 10 command line arguments
cmd_tbl:
%rep 10
%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 <expression> nop %endrep
在 NASM 中还有另一种结构,times 指令。与 %rep 相似,它也在汇编级起作用,后面也是一个表达式。例如,上面的 %rep 结构相当于:
times <expression> 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 语法同样简单。
获得产品和技术
- 使用 IBM 试用软件 构建您的下一个 Linux 开发项目,这些软件可以从 developerWorks 直接下载。
Ram 拥有计算机科学研究生学位,目前是 IBM Rational Division India Software Labs 的软件工程师,从事 Rational Clearcase 特性的开发。他使用过各种风格的 Linux/UNIX 和 Windows,以及基于移动设备的实时操作系统,比如 Symbian 和 Windows mobile。在空闲时间,他喜欢研究 Linux 和读书。
original link:http://www.ibm.com/developerworks/cn/linux/l-gas-nasm.html
Linux 汇编器:对比 GAS 和 NASM对比 GNU Assembler(GAS)和 Netwide Assembler(NASM)Ram Narayam, 软件工程师, IBM简介: 本文解释两种最流行的 Linux® 汇编器 —— GNU Assembler(GAS)和 Netwide Assembler(NASM) —— 之间一些比较重要的语法差
第二章有使用 NASM 汇编写主引导记录 MBR 的内容。
在写第二章的代码前,每天晚上下班后花一些时间简单地回顾了 NASM 汇编的内容,只复习了最简单的语法,之后写 OS 时再边写边查资料。
数据传送指令。
mov eax, 1 ; eax = 1
mov ebx, 2 ; ebx = 2
mov ecx, eax ; ecx = eax
加法指令。
add eax, 2 ; eax = eax + 2
add ebx, eax ; ebx = ebx + e
最近在学习汇编语言,王爽的那本书。他用的汇编编译器是masm;而用选择的nasm;所以最近用了下,对两者区别有点体会,记录下。
首先,why nasm?我选择nasm,最重要的一个原因是它开源:我现在一般选择的软件,都是开源免费的,不再用盗版的、不正规的软件了。这是我的一个起誓。我努力去守誓!另外,就是跟masn比较相似,有利于我学习这本书。
汇编语言分为汇编指令,伪指令、标号
文章目录mov指令and指令or指令xor指令push指令pop指令jmp指令cmp指令JG / JL指令JGE / JLE指令call指令ret指令未完…
mov指令
mov OPRD1, OPRD2
将OPRD2送到OPRD1中
and指令
and OPRD1, OPRD2
两操作数进行按位与运算,结果存于OPRD1
两操作数长度必须一致
or OPRD1, OPRD2
两操作数进行按位或运算,结果存于OPRD1
两操作数长度必须一致
xor指令
xor OPRD1, OPRD2
两操作数进行
【汇编代码】
extern:EXTERN 伪操作告诉编译器当前的符号不是在本源文件中定义的,而是在其他源文件中定义的,在本源文件中可能引用该符号。如果本源文件没有实际引用该符号,该符号将不会被加入到本源文件的符号表中。
global:关键字,把符号导出到其他模块中。global是extern的对立面,如果一个模块声明一个extern的符号,然后引用它,为了防止链接错误,另外某一个模块必须确实定
编写操作系统,免不了和硬件打交道。而想要和硬件打交道,就绕不开汇编。所以先学习一些汇编的基础知识。
通用寄存器
既然要和硬件打交道,我们就要了解一些硬件,其中寄存器是十分重要的部分。先来了解一些通用寄存器的大小。
再来了解一下通用寄存器的约定俗成的功能。
一个程序一般可分为数据段,代码段和栈段。而每个段都有对应的寄存器保存该段所处的地址。
在了解各个寄存器之后,就可以尝试利用寄存器来保存数据了。
mov ax, 0x1234 ; 相当于 ax = 0x1234
一、无条件跳转: JMP;
二、根据 CX、ECX 寄存器的值跳转: JCXZ(CX 为 0 则跳转)、JECXZ(ECX 为 0 则跳转);
三、根据 EFLAGS 寄存器的标志位跳转, 这个太多了.
根据标志位跳转的指令:
JE ;等于则跳转
JNE ;不等于则跳转
JZ ;为 0 则跳转
JNZ ;不为 0 则跳转
JS...
push 把参数入栈
pop 从栈顶弹出数据,存入给定参数中
PUSHA 把AX,CX,DX,BX,SP,BP,SI,DI依次压入堆栈.(在不同模式下面,对应不同的寄存器,如32bit模式,对应32位寄存器)
POPA 把DI,SI,BP,SP,BX,DX,C
|
|
|
|
|
|
|
|
|
|