Assembly汇编教程 - Assembly汇编
汇编语言是一种低层次的编程语言,一台电脑,或其他具体到一个特定的计算机架构中最高级编程语言,这是一般便携式跨多个系统的可编程器件。汇编语言转换成可执行的机器代码的一个实用程序称为像NASM汇编MASM等
读者
本教程已经被设计为软件程序员需要从头开始了解学习汇编编程语言。本教程给出详细的汇编语言编程语言相关概念和实例讲解。
先决条件
在继续本教程之前,需要了解基本的计算机编程术语。任何编程语言了解将帮助了解汇编语言编程的概念,有助于速入门学习和理解。
Assembly 环境设置 - Assembly汇编
本地环境设置
汇编语言是依赖于指令集和架构的处理器。在本教程中,我们使用英特尔32处理器,如奔腾。要按照本教程中将需要:
- 一台IBM PC兼容电脑或任何同等
- Linux操作系统副本
- NASM汇编程序的副本
有很多很好的汇编程序,如:
- 微软汇编(MASM)
- Borland公司的Turbo汇编(TASM)
- GNU汇编器(GAS)
我们将使用NASM汇编,因为它是:
- 免费。可以从网络下载。
- 有据可查的,会得到大量网上的信息。
- 可以用在Linux和Windows
安装NASM
如果选择“开发工具”,而安装Linux,可以随着Linux操作系统安装NASM,不需要单独下载并安装。检查是否已经安装了NASM,采取以下步骤:
- 打开一个Linux终端。
- 输入whereis NASM,然后按ENTER键。
- 如果已经安装,则类似这样一行,nasm: /usr/bin/nasm将出现。否则将看到的只是NASM,那么需要安装NASM。
要安装NASM采取以下步骤:
- 详细访问The netwide assembler (NASM) 查看最新版本.
- 下载Linux源归档文件nasm-X.XX. ta .gz。GZ,其中X.XX为NASM版本号到存档。
- 解压缩到一个目录,创建一个子目录 nasm-X. XX.
- 进入目录 nasm-X. XX 然后输入 ./configure . 此shell脚本将找到最好的C编译器的使用和设置相应的Makefile。
- 输入 make 来建立NASM和ndisasm的二进制文件。
- 输入make install NASM和ndisasm安装在/usr/local/bin目录和安装手册页。
这应该在系统上安装NASM。另外在Linux系统中可以使用RPM分发。这个版本是简单安装,只需双击该RPM文件。
Assembly 基本语法 - Assembly汇编
汇编程序可以分为三个部分:
- 数据段
- bss段部分
- 文字部分
数据段
用于声明初始化数据或常量的数据段。在运行时,此数据不改变。在本节中可以声明不同的常量值,文件名或缓冲区大小等。
声明数据段的语法是:
section .data
BSS部分是用于声明变量。声明bss段段的语法是:
section .bss
文本段
文字部分用于保存实际的代码。本节开头必须的的声明global_start,告诉内核程序开始执行。
声明文本部分的语法是:
section .text
global _start
_start:
注释
汇编语言注释以分号(;)。它可能包含任何可打印的字符,包括空白。它可以出现一行本身,如:
; This program displays a message on screen
或者,在同一行上的指令,如:
add eax ,ebx ; adds ebx to eax
Assembly汇编语言语句
汇编语言程序包括三个类型的语句:
- 可执行指令或指令
- 汇编指令或伪操作
- 宏
可执行指令或简单指示告诉的处理器该怎么做。每个指令由操作码(操作码)可执行指令生成的机器语言指令。
汇编指令或伪操作告诉汇编有关汇编过程的各个方面。这些都是非可执行文件,并不会产生机器语言指令。
宏基本上是一个文本替换机制。
汇编语言语句的语法
汇编语言语句输入每行一个语句。每个语句如下的格式如下:
[label] mnemonic [operands] [;comment]
方括号中的字段是可选的。基本指令有两部分组成,第一个是要执行的指令(助记符)的名称和所述第二命令的操作数或参数的。
以下是一些典型的汇编语言语句的例子:
INC COUNT ; Increment the memory variable COUNT
MOV TOTAL, 48 ; Transfer the value 48 in the
; memory variable TOTAL
ADD AH, BH ; Add the content of the
; BH register into the AH register
AND MASK1, 128 ; Perform AND operation on the
; variable MASK1 and 128
ADD MARKS, 10 ; Add 10 to the variable MARKS
MOV AL, 10 ; Transfer the value 10 to the AL register
Assembly Hello World程序
下面的汇编语言代码显示字符串 'Hello World'在屏幕上:
section .text
global _start ;must be declared for linker (ld)
_start: ;tells linker entry yiibai
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'Hello, world!', 0xa ;our dear string
len equ $ - msg ;length of our dear string
上面的代码编译和执行时,它会产生以下结果:
Hello, world!
一个汇编程序的编译和链接在NASM
请确保已设置NASM和LD的二进制文件的路径在PATH环境变量中。现在上述程序的编译和链接采取以下步骤:
- 使用文本编辑器,输入上面的代码保存为hello.asm。
- 请确保hello.asm文件保存在同一目录
- 要汇编程序,请键入 nasm -f elf hello.asm
- 如果有错误将提示。否则hello.o程序将创建一个对象文件。
- 要链接目标文件,并创建一个可执行文件名hello,请键入 ld -m elf_i386 -s -o hello hello.o
- 通过输入执行程序 ./hello
如果所做的一切都是正确的,它会显示 Hello, world!在屏幕上。
Assembly 内存段 - Assembly汇编
我们已经讨论了汇编程序的三个部分。这些部分代表不同的内存段。
有趣的是,如果更换部分关键字段,会得到相同的结果。试试下面的代码:
segment .text ;code segment
global _start ;must be declared for linker
_start: ;tell linker entry yiibai
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
segment .data ;data segment
msg db 'Hello, world!',0xa ;our dear string
len equ $ - msg ;length of our dear string
上面的代码编译和执行时,它会产生以下结果:
Hello, world!
内存段
分段存储模型的系统内存划分成独立的段,引用指针位于段寄存器组。每个段是指包含特定类型的数据。一个段被用于包含指令代码,另一个段存储的数据元素,和第三个分部保持程序堆栈。
根据上面的讨论,我们可以指定不同的内存段:
- 数据段 - 它由数据段的和bss段。数据段的用来声明数据元素的存储程序的内存区域。本节不能扩大后的数据元素的声明,并在整个程序中它仍保持不变。
bbs部分是静态内存部分,其中包含的缓冲区进行数据宣布以后在程序。这个缓冲存储器是零填充。
- 代码段 - 它表示文字部分。这定义的区域在存储器中存储的指令代码。这也是一个固定的区域。
- 堆 - 此段包含传递给程序的功能和程序内的数据值。
Assembly 寄存器 - Assembly汇编
处理器操作主要涉及数据处理。这些数据可以被存储在存储器中,并从在其上进行访问。然而,读取数据和将数据存储到存储器的速度变慢的处理器,因为它涉及复杂的过程,整个控制总线发送的数据请求,到存储器的存储单元,并通过同一个通道获取数据。
为了加快该处理器,该处理器包括一些内部存储器中的存储位置,称为寄存器。
寄存器存储数据元素进行处理,而无需直接访问存储器。处理器芯片内置到数量有限的寄存器。
处理器寄存器
IA-32架构中有10个32位和6个16位处理器寄存器。该寄存器被分成三大类:
- 通用寄存器
- 控制寄存器
- 段寄存器
通用寄存器进一步分为以下几类:
- 数据寄存器
- 指针寄存器
- 索引寄存器
数据寄存器
4个32位数据寄存器用于算术,逻辑和其他操作。这些32位的寄存器可以用来在三个方面:
- 32位数据寄存器: EAX, EBX, ECX, EDX.
- 下半部分32位寄存器,可以作为4个16位数据寄存器: AX, BX, CX and DX.
- 8个8位数据寄存器可以用作上面提到的4个16位寄存器的较低和较高的半部 AH, AL, BH, BL, CH, CL, DH, and DL.
这些数据寄存器中的某些具有特定的算术运算中使用。
AX是主要累加器; 它被用在输入/输出和多数算术运算指令。例如,在乘法运算中,一个操作数存储在EAX或AX或AL寄存器操作数的大小。
BX是已知的作为基址寄存器,因为它可以用在索引寻址。
CX称为计数寄存器ECX,CX寄存器存储中的循环计数迭代操作。
DX被称为数据寄存器。它也可以用来在输入/输出操作。它也可用于与AX寄存器连同DX涉及大的值的乘法和除法运算。
指针寄存器
指针寄存器是32位的EIP,ESP和EBP寄存器和相应的16位右部IP,SP和BP。指针寄存器有三类:
- Instruction Yiibaier (IP) - 16位的IP寄存器存储的下一个要执行的指令的偏移地址。 IP与CS寄存器(CS:IP)给出了完整的代码段中的当前指令地址。
- Stack Yiibaier (SP) - SP寄存器的16位提供程序堆栈内的偏移值。 SP与SS寄存器(SS:SP)是指在程序堆栈的当前位置的数据或地址。
- Base Yiibaier (BP) - 在16-bit的BP寄存器主要是帮助在引用的参数变量传递到一个子程序。 SS寄存器中的地址相结合,在BP的偏移得到的参数的位置。 BP也可作为基址寄存器DI和SI结合特殊的寻址。
索引寄存器
32位变址寄存器ESI和EDI和16位的最右边的部分SI和DI用于索引寻址,有时用在加法和减法。有两组的索引指针:
- Source Index (SI) - 它被用作来源分类索引为字符串操作
- Destination Index (DI) -它被用来作为目标指数为字符串操作。
控制寄存器
32位指令指针寄存器和32位标志寄存器组合被视为控制寄存器。
许多指令涉及比较和数学计算和改变的状态标志,和其他一些有条件指令测试这些状态标志值到其他位置的控制流。
常见的标志位:
- Overflow Flag (OF): 指示符号的算术运算操作后的数据的高阶位(最左边的位)的溢出。
- Direction Flag (DF): 决定向左或向右移动或比较字符串数据的方向。当DF值是0,字符串的操作需要左到右的方向和当该值被设置为1时,该字符串的操作需要从右到左的方向。
- Interrupt Flag (IF): 决定是否外部中断。如,键盘输入等是被忽略或处理。它会禁用外部中断,当值为0时,设置为1时,允许中断。
- Trap Flag (TF): 可设置单步模式中的处理器的操作。我们使用调试程序设置陷阱标志,所以我们可以通过执行一个指令在一个时间步。
- Sign Flag (SF): 显示的符号的算术运算的结果。根据一个数据项的符号的算术运算后,这个标志被设置。最左边的位高阶符号表示。一个积极的结果清除SF值0和负结果,将其设置为1。
- Zero Flag (ZF): 指出算术运算或比较操作的结果。一个非零的结果清除零标志为0,结果为零,将其设置为1。
- Auxiliary Carry Flag (AF): 包含进从第3位到第4位算术运算,用于专门算术。 AF被设为1字节的算术操作导致进位从第3位到第4位。
- Parity Flag (PF): 在从一个算术运算的结果为1的位数表示的总数。为1的位数为偶数奇偶标志清零0,为1的位数为奇数个的奇偶校验设置标志位为1。
- Carry Flag (CF): 包含从高阶位(最左边的)算术运算后的进位为0或1。它还存储的内容的最后一个比特的移位或旋转操作。
下表显示在16位的标志寄存器的标志位的位置:
Flag: | O | D | I | T | S | Z | A | P | C | |||||||
Bit no: | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
--- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
段寄存器
段含有数据,代码和堆栈在程序中定义的特定区域。有三个主要部分:
- 代码段:它包含了所有要执行的指令。一个16 - 位代码段寄存器CS寄存器存储的代码段的起始地址。
- 数据段: 它包含数据,常量和工作区。一个16 - 位数据段寄存器DS寄存器存储数据段的起始地址。
- 堆栈段: 它包含数据,程序或子程序的返回地址。它被实现为一个“堆栈”的数据结构。堆栈段寄存器或SS寄存器存储堆栈的起始地址。
除了在DS,CS和SS寄存器,还有其他的额外的段寄存器 - ES(附加段),FS和GS,它提供了用于存储数据的附加段。
在汇编语言编程,程序需要访问的内存位置。段内的所有的内存位置相对于该段的起始地址。在地址均匀分部开始禁用由16或10进制。因此,所有的在所有这样的存储器地址的最右边的十六进制数字是0,这是不一般存放在段寄存器。
段寄存器存储一个段的起始地址。为了得到确切的位置数据或指令段内的偏移值(或位移)是必需的。引用任何在一个段中的内存位置,所述处理器相结合的段寄存器中的段地址的偏移值的位置。
例子:
看看下面这个简单的程序,了解使用汇编编程寄存器。此程序显示在屏幕上的9颗星连同一个简单的信息:
section .text
global _start ;must be declared for linker (gcc)
_start: ;tell linker entry yiibai
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov edx,9 ;message length
mov ecx,s2 ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'Displaying 9 stars',0xa ;a message
len equ $ - msg ;length of message
s2 times 9 db '*'
上面的代码编译和执行时,它会产生以下结果:
Displaying 9 stars
*********
Assembly 系统调用 - Assembly汇编
系统调用是用户空间与内核空间之间的接口的API。我们已经使用该系统调用sys_write的sys_exit的的写入屏幕,然后分别从程序退出。
Linux 系统调用
可以利用Linux系统调用汇编程序。如需要在程序中使用Linux系统调用,请采取以下步骤:
- 把EAX寄存器中的系统调用号。
- 在寄存器存储的参数的系统调用 EBX, ECX等.
- 调用相关的中断 (80h)
- 其结果通常是返回在EAX 寄存器
有6个寄存器存储系统调用的参数。 它们有 EBX, ECX, EDX, ESI, EDI 和 EBP. 这些寄存器采取连续的参数,起始带EBX寄存器。如果有超过六个参数,那么第一个参数的存储位置被存储在EBX寄存器。
下面的代码片段显示了使用系统调用sys_exit:
mov eax,1 ; system call number (sys_exit)
int 0x80 ; call kernel
下面的代码片段显示了使用系统调用sys_write:
mov edx,4 ; message length
mov ecx,msg ; message to write
mov ebx,1 ; file descriptor (stdout)
mov eax,4 ; system call number (sys_write)
int 0x80 ; call kernel
列出了所有的系统调用 /usr/include/asm/unistd.h, 连同他们的编号(之前把在EAX调用int80H)。
下表显示了一些本教程中使用的系统调用:
%eax | Name | %ebx | %ecx | %edx | %esx | %edi |
1 | sys_exit | int | - | - | - | - |
2 | sys_fork | struct pt_regs | - | - | - | - |
3 | sys_read | unsigned int | char * | size_t | - | - |
4 | sys_write | unsigned int | const char * | size_t | - | - |
5 | sys_open | const char * | int | int | - | - |
6 | sys_close | unsigned int | - | - | - | - |
例子
下面的例子从键盘读取,并显示在屏幕上:
section .data ;Data segment
userMsg db 'Please enter a number: ' ;Ask the user to enter a number
lenUserMsg equ $-userMsg ;The length of the message
dispMsg db 'You have entered: '
lenDispMsg equ $-dispMsg
section .bss ;Uninitialized data
num resb 5
section .text ;Code Segment
global _start
_start:
;User prompt
mov eax, 4
mov ebx, 1
mov ecx, userMsg
mov edx, lenUserMsg
int 80h
;Read and store the user input
mov eax, 3
mov ebx, 2
mov ecx, num
mov edx, 5 ;5 bytes (numeric, 1 for sign) of that information
int 80h
;Output the message 'The entered number is: '
mov eax, 4
mov ebx, 1
mov ecx, dispMsg
mov edx, lenDispMsg
int 80h
;Output the number entered
mov eax, 4
mov ebx, 1
mov ecx, num
mov edx, 5
int 80h
; Exit code
mov eax, 1
mov ebx, 0
int 80h
上面的代码编译和执行时,它会产生以下结果:
Please enter a number:
1234
You have entered:1234
Assembly 寻址模式和MOV指令 - Assembly汇编
大多数汇编语言指令的要求,要被处理的操作数。提供一个操作数地址要被处理的数据被存储的位置。某些指令不需要操作数,而其他一些指令可能需要一个,两个或三个操作数。
当一个指令需要两个操作数,第一个操作数是一般的目的,其中包含在一个寄存器或内存位置和第二个操作数源数据。源码包含要传送的数据(立即寻址)或(寄存器或存储器)中的数据的地址。一般来说,操作后的源数据将保持不变。
解决的三种基本模式是:
- 寄存器寻址
- 立即寻址
- 存储器寻址
寄存器寻址
在这种寻址方式中,寄存器包含操作数。根据不同的指令,寄存器可能是第一个操作数,第二个操作数或两者兼而有之。
例如,
MOV DX, TAX_RATE ; Register in first operand
MOV COUNT, CX ; Register in second operand
MOV EAX, EBX ; Both the operands are in registers
随着处理数据寄存器之间不涉及内存,它提供数据的处理速度是最快的。
立即寻址
立即数有一个恒定的值或表达式。当一个指令有两个操作数使用立即寻址,第一个操作数是寄存器或内存中的位置,和第二个操作数是立即数。第一个操作数定义的数据的长度。
例如,
BYTE_VALUE DB 150 ; A byte value is defined
WORD_VALUE DW 300 ; A word value is defined
ADD BYTE_VALUE, 65 ; An immediate operand 65 is added
MOV AX, 45H ; Immediate constant 45H is transferred to AX
直接存储器寻址
当操作数指定内存寻址模式,直接访问主存储器的数据段,通常是必需的。这种方式处理的数据的处理速度较慢的结果。为了找到确切的位置在内存中的数据,我们需要段的起始地址,这是通常出现在DS寄存器和偏移值。这个偏移值也被称为有效的地址。
在直接寻址方式,是直接指定的偏移值作为指令的一部分,通常由变量名表示。汇编程序计算的偏移值,并维护一个符号表,它存储在程序中使用的所有变量的偏移值。
在直接存储器寻址,其中一个操作数是指一个内存位置和另一个操作数引用一个寄存器。
例如,
ADD BYTE_VALUE, DL ; Adds the register in the memory location
MOV BX, WORD_VALUE ; Operand from the memory is added to register
直接偏移量寻址
这种寻址模式使用算术运算符修改一个地址。例如,看看下面的定义来定义数据表:
BYTE_TABLE DB 14, 15, 22, 45 ; Tables of bytes
WORD_TABLE DW 134, 345, 564, 123 ; Tables of words
可以进行以下操作:从存储器到寄存器中的表访问数据:
MOV CL, BYTE_TABLE[2] ; Gets the 3rd element of the BYTE_TABLE
MOV CL, BYTE_TABLE + 2 ; Gets the 3rd element of the BYTE_TABLE
MOV CX, WORD_TABLE[3] ; Gets the 4th element of the WORD_TABLE
MOV CX, WORD_TABLE + 3 ; Gets the 4th element of the WORD_TABLE
间接寻址
这种寻址模式利用计算机的能力分部:偏移寻址。一般基寄存器EBX,EBP(BX,BP)和索引寄存器(DI,SI),编码的方括号内的内存引用,用于此目的。
通常用于含有几个元素的类似,数组变量间接寻址。存储在数组的起始地址是EBX寄存器。
下面的代码片段显示了如何访问不同元素的变量。
MY_TABLE TIMES 10 DW 0 ; Allocates 10 words (2 bytes) each initialized to 0
MOV EBX, [MY_TABLE] ; Effective Address of MY_TABLE in EBX
MOV [EBX], 110 ; MY_TABLE[0] = 110
ADD EBX, 2 ; EBX = EBX +2
MOV [EBX], 123 ; MY_TABLE[1] = 123
MOV指令
我们已经使用MOV指令是用于将数据从一个存储空间移动到另一个。 MOV指令需要两个操作数。
语法:
MOV指令的语法是:
MOV destination, source
MOV指令可以具有以下五种形式之一:
MOV register, register
MOV register, immediate
MOV memory, immediate
MOV register, memory
MOV memory, register
请注意:
- MOV操作操作数应该是同样大小
- 源操作数的值保持不变
MOV指令产生引起歧义次数。例如,下面语句:
MOV EBX, [MY_TABLE] ; Effective Address of MY_TABLE in EBX
MOV [EBX], 110 ; MY_TABLE[0] = 110
目前尚不清楚是否要移动相当于一个字节或字相当于数字110。在这种情况下,比较好的做法是使用类型说明符。
下表列出了一些常见的类型说明符:
Type Specifier | Bytes addressed |
BYTE | 1 |
WORD | 2 |
DWORD | 4 |
QWORD | 8 |
TBYTE | 10 |
例子:
下面的程序说明,上面讨论的一些概念。它存储的名称'Zara Ali'在数据段的内存。然后其值更改为另一个名字''Nuha Ali'编程和显示名称。
section .text
global _start ;must be declared for linker (ld)
_start: ;tell linker entry yiibai
;writing the name 'Zara Ali'
mov edx,9 ;message length
mov ecx, name ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov [name], dword 'Nuha' ; Changed the name to Nuha Ali
;writing the name 'Nuha Ali'
mov edx,8 ;message length
mov ecx,name ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
name db 'Zara Ali '
上面的代码编译和执行时,它会产生以下结果:
Zara Ali Nuha Ali
Assembly 变量声明 - Assembly汇编
NASM提供各种定义变量预留存储空间的指令。定义汇编指令用于分配的存储空间。它可用于预定和初始化一个或多个字节。
初始化数据分配存储空间
初始化数据存储分配语句的语法是:
[variable-name] define-directive initial-value [,initial-value]...
变量名是每个存储空间的标识符。汇编器在数据段中定义的每一个变量名的偏移值。
有五种基本形式定义指令:
Directive | Purpose | Storage Space |
DB | Define Byte | allocates 1 byte |
DW | Define Word | allocates 2 bytes |
DD | Define Doubleword | allocates 4 bytes |
DQ | Define Quadword | allocates 8 bytes |
DT | Define Ten Bytes | allocates 10 bytes |
以下是一些例子,使用define指令:
choice DB 'y'
number DW 12345
neg_number DW -12345
big_number DQ 123456789
real_number1 DD 1.234
real_number2 DQ 123.456
请注意:
- 每个字节的字符以十六进制的ASCII值存储。
- 每个十进制值会自动转换为十六进制数16位二进制存储
- 处理器使用小尾数字节顺序
- 负数转换为2的补码表示
- 短的和长的浮点数使用32位或64位分别表示
下面的程序显示了使用定义指令:
section .text
global _start ;must be declared for linker (gcc)
_start: ;tell linker entry yiibai
mov edx,1 ;message length
mov ecx,choice ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
choice DB 'y'
上面的代码编译和执行时,它会产生以下结果:
y
未初始化的数据分配存储空间
储备指令用于未初始化的数据预留空间。后备指令一个操作数指定要保留空间的单位数量。各自定义指令都有一个相关的后备指令。
有五种基本形式的后备指令:
Directive | Purpose |
RESB | Reserve a Byte |
RESW | Reserve a Word |
RESD | Reserve a Doubleword |
RESQ | Reserve a Quadword |
REST | Reserve a Ten Bytes |
多重定义
可以在程序有多个数据定义语句。例如:
choice DB 'Y' ;ASCII of y = 79H
number1 DW 12345 ;12345D = 3039H
number2 DD 12345679 ;123456789D = 75BCD15H
汇编程序内存分配连续多个变量的定义。
多个初始化
TIMES指令允许多个初始化为相同的值。例如,一个名为标记大小为9的数组可以被定义和初始化为零,使用下面的语句:
marks TIMES 9 DW 0
时代的指令是非常有用在定义数组和表格。下面的程序显示在屏幕上的9星号:
section .text
global _start ;must be declared for linker (ld)
_start: ;tell linker entry yiibai
mov edx,9 ;message length
mov ecx, stars ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
stars times 9 db '*'
上面的代码编译和执行时,它会产生以下结果:
*********
Assembly 常量 - Assembly汇编
有几个NASM定义常量的指令。我们在前面的章节中已经使用EQU指令。我们将特别讨论了三个指令:
- EQU
- %assign
- %define
EQU 指令
EQU指令用于定义常量。 EQU伪指令的语法如下:
CONSTANT_NAME EQU expression
例如,
TOTAL_STUDENTS equ 50
可以在代码中使用这个常量值,如:
mov ecx, TOTAL_STUDENTS
cmp eax, TOTAL_STUDENTS
EQU语句的操作数可以是一个表达式:
LENGTH equ 20
WIDTH equ 10
AREA equ length * width
上面的代码段定义AREA为200。
例子:
下面的例子演示了如何使用EQU指令:
SYS_EXIT equ 1
SYS_WRITE equ 4
STDIN equ 0
STDOUT equ 1
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry yiibai
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg1
mov edx, len1
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg2
mov edx, len2
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg3
mov edx, len3
int 0x80
mov eax,SYS_EXIT ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg1 db 'Hello, programmers!',0xA,0xD
len1 equ $ - msg1
msg2 db 'Welcome to the world of,', 0xA,0xD
len2 equ $ - msg2
msg3 db 'Linux assembly programming! '
len3 equ $- msg3
上面的代码编译和执行时,它会产生以下结果:
Hello, programmers!
Welcome to the world of,
Linux assembly programming!
%assign 指令
%assign 指令可以使用像EQU指令定义数值常量。该指令允许重新定义。例如,您可以定义常量TOTAL :
%assign TOTAL 10
在后面的代码,可以重新定义为:
%assign TOTAL 20
这个指令是区分大小写的。
%define 指令
The %define 指令允许定义数值和字符串常量。这个指令是相似 #define在C#中。例如,可以定义常量的PTR:
%define PTR [EBP+4]
上面的代码取代PTR by [EBP+4].
此指令还允许重新定义,它是区分大小写。
Assembly 算术指令 - Assembly汇编
INC指令
INC指令是一个用于操作数递增。它可以在一个单一的操作数,可以是在一个寄存器或内存。
语法:
INC指令的语法如下:
INC destination
操作数目标可能是8位,16位或32位操作数。
例子:
INC EBX ; Increments 32-bit register
INC DL ; Increments 8-bit register
INC [count] ; Increments the count variable
DEC指令
DEC指令用于由一个操作数递减。它可以在一个单一的操作数,可以是在一个寄存器或内存。
语法:
DEC指令的语法如下:
DEC destination
操作数目标可以是8位,16位或32位操作数。
例子:
segment .data
count dw 0
value db 15
segment .text
inc [count]
dec [value]
mov ebx, count
inc word [ebx]
mov esi, value
dec byte [esi]
ADD和SUB指令
ADD和SUB指令用于执行二进制数据字节,字和双字的大小,即简单的加法/减法,8位,16位或32位操作数分别相加或相减。
语法:
ADD和SUB指令的语法如下:
ADD/SUB destination, source
ADD/ SUB指令之间可能发生:
- 寄存器到寄存器
- 内存到寄存器
- 寄存器到内存
- 寄存器到常量数据
- 内存到常量数据
然而,像其他指令,内存到内存的操作是不可能使用ADD/ SUB指令。 ADD或SUB操作设置或清除溢出和进位标志。
例子:
下面的例子会从用户要求的两个数字,分别在EAX和EBX寄存器存储的数字,增加值,并将结果存储在一个内存位置'清晰度',并最终显示结果。
SYS_EXIT equ 1
SYS_READ equ 3
SYS_WRITE equ 4
STDIN equ 0
STDOUT equ 1
segment .data
msg1 db "Enter a digit ", 0xA,0xD
len1 equ $- msg1
msg2 db "Please enter a second digit", 0xA,0xD
len2 equ $- msg2
msg3 db "The sum is: "
len3 equ $- msg3
segment .bss
num1 resb 2
num2 resb 2
res resb 1
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry yiibai
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg1
mov edx, len1
int 0x80
mov eax, SYS_READ
mov ebx, STDIN
mov ecx, num1
mov edx, 2
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg2
mov edx, len2
int 0x80
mov eax, SYS_READ
mov ebx, STDIN
mov ecx, num2
mov edx, 2
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg3
mov edx, len3
int 0x80
; moving the first number to eax register and second number to ebx
; and subtracting ascii '0' to convert it into a decimal number
mov eax, [number1]
sub eax, '0'
mov ebx, [number2]
sub ebx, '0'
; add eax and ebx
add eax, ebx
; add '0' to to convert the sum from decimal to ASCII
add eax, '0'
; storing the sum in memory location res
mov [res], eax
; print the sum
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, res
mov edx, 1
int 0x80
exit:
mov eax, SYS_EXIT
xor ebx, ebx
int 0x80
上面的代码编译和执行时,它会产生以下结果:
Enter a digit:
3
Please enter a second digit:
4
The sum is:
7
该程序用硬编码的变量:
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry yiibai
mov eax,'3'
sub eax, '0'
mov ebx, '4'
sub ebx, '0'
add eax, ebx
add eax, '0'
mov [sum], eax
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,sum
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The sum is:", 0xA,0xD
len equ $ - msg
segment .bss
sum resb 1
上面的代码编译和执行时,它会产生以下结果:
The sum is:
7
MUL/ IMUL指令
有两个指令乘以二进制数据。 MUL(乘)指令处理无符号数据和IMUL(整数乘法)处理有符号数据。这两个指令影响进位和溢出标志。
语法:
MUL/ IMUL指令,语法如下:
MUL/IMUL multiplier
在这两种情况被乘数是在累加器中,根据被乘数和乘数的大小,所产生的产物也被存储在操作数,大小取决于两个寄存器。下面一节的解释MULL有三种不同的情况指令:
When two bytes are multiplied
The multiplicand is in the AL register, and the multiplier is a byte in the memory or in another register. The product is in AX. High order 8 bits of the product is stored in AH and the low order 8 bits are stored in AL
When two one-word values are multiplied
The multiplicand should be in the AX register, and the multiplier is a word in memory or another register. For example, for an instruction like MUL DX, you must store the multiplier in DX and the multiplicand in AX.The resultant product is a double word, which will need two registers. The High order (leftmost) portion gets stored in DX and the lower-order (rightmost) portion gets stored in AX.
When two doubleword values are multiplied
When two doubleword values are multiplied, the multiplicand should be in EAX and the multiplier is a doubleword value stored in memory or in another register. The product generated is stored in the EDX:EAX registers, i.e., the high order 32 bits gets stored in the EDX register and the low order 32-bits are stored in the EAX register.
例子:
MOV AL, 10
MOV DL, 25
MUL DL
...
MOV DL, 0FFH ; DL= -1
MOV AL, 0BEH ; AL = -66
IMUL DL
例子:
下面的示例与2乘以3,并显示结果:
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry yiibai
mov al,'3'
sub al, '0'
mov bl, '2'
sub bl, '0'
mul bl
add al, '0'
mov [res], al
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,res
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The result is:", 0xA,0xD
len equ $- msg
segment .bss
res resb 1
上面的代码编译和执行时,它会产生以下结果:
The result is:
6
DIV/IDIV 指令
除法运算产生两个元素 - 一个商和余数。在乘法运算的情况下,不会发生溢出,因为双倍长度的寄存器是用来保持产生。然而,在除法的情况下,可能会发生溢出。处理器产生一个中断,如果发生溢出。
DIV(除)指令或无符号数据和IDIV(整数除法)用于有符号数据。
语法:
DIV / IDIV指令的格式为:
DIV/IDIV divisor
被除数是在累加器。两个指令可以处理8位,16位或32位操作数。该操作会影响所有的6个状态标志。以下部分说明了三个例子的划分有不同的操作数大小:
When the divisor is 1 byte
The dividend is assumed to be in the AX register (16 bits). After division, the quotient goes to the AL register and the remainder goes to the AH register.
When the divisor is 1 word
The dividend is assumed to be 32 bits long and in the DX:AX registers. The high order 16 bits are in DX and the low order 16 bits are in AX. After division, the 16 bit quotient goes to the AX register and the 16 bit remainder goes to the DX register.
When the divisor is doubleword
The dividend is assumed to be 64 bits long and in the EDX:EAX registers. The high order 32 bits are in EDX and the low order 32 bits are in EAX. After division, the 32 bit quotient goes to the EAX register and the 32 bit remainder goes to the EDX register.
例子:
下面的例子8除于2。8被存储在16位寄存器EAX和除数2被存储在8位BL寄存器。
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry yiibai
mov ax,'8'
sub ax, '0'
mov bl, '2'
sub bl, '0'
div bl
add ax, '0'
mov [res], ax
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,res
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The result is:", 0xA,0xD
len equ $- msg
segment .bss
res resb 1
上面的代码编译和执行时,它会产生以下结果:
The result is:
4
Assembly 逻辑指令 - Assembly汇编
处理器的指令集提供的指令AND,OR,XOR,TEST和NOT布尔逻辑的测试,根据该方案的需要的位进行置位和清除。
这些指令的格式:
SN | 指令 | 格式 |
1 | AND | AND operand1, operand2 |
2 | OR | OR operand1, operand2 |
3 | XOR | XOR operand1, operand2 |
4 | TEST | TEST operand1, operand2 |
5 | NOT | NOT operand1 |
在所有的情况下,第一个操作数可以是寄存器或内存中。第二个操作数可以是寄存器/存储器或立即值(常量)。但是,内存到内存的操作是不可能的。这些指令可比较或匹配位操作数和CF,PF,SF和ZF标志。
AND 指令
AND指令用于支持逻辑表达式执行按位与运算。按位与运算返回1,如果匹配两个操作数位为1,否则返回0。例如:
Operand1: 0101
Operand2: 0011
----------------------------
After AND -> Operand1: 0001
“与”操作,可用于清除一个或多个位。例如说,BL寄存器包含00111010。如果需要清除的高位零,AND 0FH。
AND BL, 0FH ; This sets BL to 0000 1010
我们的另一个例子。如果想检查一个给定的数字是否是奇数还是偶数,一个简单的测试将是检查的数量最少的显着位。如果是1的为奇数,其他的数是偶数。
假设数字是在AL寄存器,我们可以这样写:
AND AL, 01H ; ANDing with 0000 0001
JZ EVEN_NUMBER
下面的程序说明了这一点:
例子:
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry yiibai
mov ax, 8h ;getting 8 in the ax
and ax, 1 ;and ax with 1
jz evnn
mov eax, 4 ;system call number (sys_write)
mov ebx, 1 ;file descriptor (stdout)
mov ecx, odd_msg ;message to write
mov edx, len2 ;length of message
int 0x80 ;call kernel
jmp outprog
evnn:
mov ah, 09h
mov eax, 4 ;system call number (sys_write)
mov ebx, 1 ;file descriptor (stdout)
mov ecx, even_msg ;message to write
mov edx, len1 ;length of message
int 0x80 ;call kernel
outprog:
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
even_msg db 'Even Number!' ;message showing even number
len1 equ $ - even_msg
odd_msg db 'Odd Number!' ;message showing odd number
len2 equ $ - odd_msg
上面的代码编译和执行时,它会产生以下结果:
Even Number!
一个奇怪的数字,像在AX寄存器中的值更改:
mov ax, 9h ; getting 9 in the ax
该程序会显示:
Odd Number!
同样你可以它清除整个寄存器 :AND和00H.
OR 指令
OR指令用于支持逻辑表达式执行按位OR运算。位OR运算符返回1,如果其中一个或两个操作数位匹配是一个。它返回0,如果两个位都是零。
例如,
Operand1: 0101
Operand2: 0011
----------------------------
After OR -> Operand1: 0111
OR(或)操作可用于设置一个或多个位。例如,让我们假设AL寄存器包含00111010,需要设置四个低阶位,OR 0000 1111,即FH值。
OR BL, 0FH ; This sets BL to 0011 1111
实例:
下面的示例演示OR指令。让我们存储5和3值分别在AL和BL寄存器。然后,该指令
OR AL, BL
应该AL寄存器中存放7:
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry yiibai
mov al, 5 ;getting 5 in the al
mov bl, 3 ;getting 3 in the bl
or al, bl ;or al and bl registers, result should be 7
add al, byte '0' ;converting decimal to ascii
mov [result], al
mov eax, 4
mov ebx, 1
mov ecx, result
mov edx, 1
int 0x80
outprog:
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .bss
result resb 1
上面的代码编译和执行时,它会产生以下结果:
7
XOR 指令
XOR指令实现按位异或操作。异或运算得到位设置为1,当且仅当从操作数的位是不同的。如果操作数的位相同(都为0或为1),将得到的位被清除为0。
实例,
Operand1: 0101
Operand2: 0011
----------------------------
After XOR -> Operand1: 0110
> 异或操作数本身改变操作数为0。这是用来清除寄存器。
XOR EAX, EAX
TEST 指令
测试指令的工作原理相同的“与”操作,但不像AND指令,它不改变它的第一个操作数。所以,如果我们需要检查是否在寄存器数量是偶数还是奇数,我们也可以做到这一点不改变原有号码的情况下使用测试指令。
TEST AL, 01H
JZ EVEN_NUMBER
NOT 指令
指令实现按位非运算。 NOT运算操作数的位逆转。该操作数可能是在一个寄存器或存储器中。
实例,
Operand1: 0101 0011
After NOT -> Operand1: 1010 1100
Assembly 条件 - Assembly汇编
在汇编语言中的条件执行是通过几个循环和分支指令。这些指令可以改变在程序的控制流。有条件的执行过程中观察到两种情况:
SN | 条件说明 |
1 | 无条件跳转 这是通过JMP指令。有条件的执行往往涉及控制权移交给一个指令的地址不遵循当前执行的指令。控制转移可能会执行一组新的指令或向后,以便重新执行相同的步骤。 |
2 | 条件跳转 这是由一组跳转指令Ĵ<条件>视条件而定。条件指令控制转移,打破了连续流程,他们这样做是通过改变IP中的偏移值。 |
让我们来讨论CMP指令在讨论条件指令之前。
CMP 指令
CMP指令比较两个操作数。它通常用于在条件执行。该指令基本上减去一个操作数进行比较的操作数是否等于或不从其他。它不干扰源或目的操作数。它是用来为决策的条件跳转指令。
语法
CMP destination, source
CMP比较两个数字数据字段。目的操作数可以是寄存器或内存中。源操作数可以是一个常数(立即)数据,寄存器或内存。
例子:
CMP DX, 00 ; Compare the DX value with zero
JE L7 ; If yes, then jump to label L7
.
.
L7: ...
CMP往往是用于比较的计数器值是否已经达到了一个循环的时间的数量需要运行。考虑以下典型条件:
INC EDX
CMP EDX, 10 ; Compares whether the counter has reached 10
JLE LP1 ; If it is less than or equal to 10, then jump to LP1
无条件跳转
正如前面提到的,这是在JMP指令执行。有条件的执行往往涉及控制权移交给一个指令的地址不遵循当前执行的指令。控制转移可能会执行一组新的指令或向后,以便重新执行相同的步骤。
语法:
JMP指令立即传送控制流提供了一个标签名称。 JMP指令的语法是:
JMP label
实例:
下面的代码片段说明JMP指令:
MOV AX, 00 ; Initializing AX to 0
MOV BX, 00 ; Initializing BX to 0
MOV CX, 01 ; Initializing CX to 1
L20:
ADD AX, 01 ; Increment AX
ADD BX, AX ; Add AX to BX
SHL CX, 1 ; shift left CX, this in turn doubles the CX value
JMP L20 ; repeats the statements
有条件跳转
如果某些指定的条件跳转条件满足时,控制流程转移到目标指令。有多个条件跳转指令,根据条件和数据。
以下是条件跳转指令用于有符号数据用于算术运算:
Instruction | Description | Flags tested |
JE/JZ | Jump Equal or Jump Zero | ZF |
JNE/JNZ | Jump not Equal or Jump Not Zero | ZF |
JG/JNLE | Jump Greater or Jump Not Less/Equal | OF, SF, ZF |
JGE/JNL | Jump Greater or Jump Not Less | OF, SF |
JL/JNGE | Jump Less or Jump Not Greater/Equal | OF, SF |
JLE/JNG | Jump Less/Equal or Jump Not Greater | OF, SF, ZF |
以下是条件跳转指令用于无符号数据用于进行逻辑运算:
Instruction | Description | Flags tested |
JE/JZ | Jump Equal or Jump Zero | ZF |
JNE/JNZ | Jump not Equal or Jump Not Zero | ZF |
JA/JNBE | Jump Above or Jump Not Below/Equal | CF, ZF |
JAE/JNB | Jump Above/Equal or Jump Not Below | CF |
JB/JNAE | Jump Below or Jump Not Above/Equal | CF |
JBE/JNA | Jump Below/Equal or Jump Not Above | AF, CF |
下列条件跳转指令有特殊的用途及检查的标志值:
Instruction | Description | Flags tested |
JXCZ | Jump if CX is Zero | none |
JC | Jump If Carry | CF |
JNC | Jump If No Carry | CF |
JO | Jump If Overflow | OF |
JNO | Jump If No Overflow | OF |
JP/JPE | Jump Parity or Jump Parity Even | PF |
JNP/JPO | Jump No Parity or Jump Parity Odd | PF |
JS | Jump Sign (negative value) | SF |
JNS | Jump No Sign (positive value) | SF |
在J<条件>的指令集的语法:
例如,
CMP AL, BL
JE EQUAL
CMP AL, BH
JE EQUAL
CMP AL, CL
JE EQUAL
NON_EQUAL: ...
EQUAL: ...
实例:
下面的程序显示的最大的三个变量。的变量均以两位数变量。这三个变量num1, num2 和num3值分别为47,72和31:
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry yiibai
mov ecx, [num1]
cmp ecx, [num2]
jg check_third_num
mov ecx, [num3]
check_third_num:
cmp ecx, [num3]
jg _exit
mov ecx, [num3]
_exit:
mov [largest], ecx
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,largest
mov edx, 2
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax, 1
int 80h
section .data
msg db "The largest digit is: ", 0xA,0xD
len equ $- msg
num1 dd '47'
num2 dd '22'
num3 dd '31'
segment .bss
largest resb 2
上面的代码编译和执行时,它会产生以下结果:
The largest digit is:
47
Assembly 循环 - Assembly汇编
JMP指令可用于实现循环。例如,下面的代码片段可用于执行循环体10次。
MOV CL, 10
L1:
<LOOP-BODY>
DEC CL
JNZ L1
然而,该处理器的指令集包括一组用于执行迭代循环指令。基本循环指令的语法如下:
LOOP label
其中,标签是对象标注,标识中的跳转指令的目标指令。LOOP指令假定ECX寄存器包含循环计数值。当循环指令被执行时,ECX寄存器递减,控制跳转到目标标签直到ECX寄存器的值,即计数器达到零值。
上面的代码可以写成:
mov ECX,10
l1:
<loop body>
loop l1
例子:
下面的程序将打印在屏幕上数字1〜9:
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry yiibai
mov ecx,10
mov eax, '1'
l1:
mov [num], eax
mov eax, 4
mov ebx, 1
push ecx
mov ecx, num
mov edx, 1
int 0x80
mov eax, [num]
sub eax, '0'
inc eax
add eax, '0'
pop ecx
loop l1
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .bss
num resb 1
上面的代码编译和执行时,它会产生以下结果:
123456789:
Assembly 数字 - Assembly汇编
数值数据普遍表示二进制系统。算术指令的操作上的二进制数据。当数字显示在屏幕上,或从键盘输入,它们是ASCII形式。
到目前为止,我们已经转换成ASCII形式输入数据进行算术运算的二进制结果转换回二进制。下面的代码显示:
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry yiibai
mov eax,'3'
sub eax, '0'
mov ebx, '4'
sub ebx, '0'
add eax, ebx
add eax, '0'
mov [sum], eax
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,sum
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The sum is:", 0xA,0xD
len equ $ - msg
segment .bss
sum resb 1
上面的代码编译和执行时,它会产生以下结果:
The sum is:
7
然而,这样的转换是有一个系统开销和汇编语言的程序设计允许更有效的方式,处理数字的二进制形式。十进制数可以表示为两种形式:
- ASCII形式
- BCD或二进制编码的十进制形式
ASCII表示
在ASCII码表示,十进制数字存储ASCII字符串。例如,十进制值1234被存储为:
31 32 33 34H
其中,31H,32H是ASCII值1 ASCII值2,依此类推。有以下四个指令处理数字的ASCII表示:
- AAA - ASCII Adjust After Addition
- AAS - ASCII Adjust After Subtraction
- AAM - ASCII Adjust After Multiplication
- AAD - ASCII Adjust Before Division
这些指令不采取任何操作数,并承担所需的操作数是在AL寄存器中。
下面的示例使用AAS指令来说明这个概念:
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry yiibai
sub ah, ah
mov al, '9'
sub al, '3'
aas
or al, 30h
mov [res], ax
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov edx,1 ;message length
mov ecx,res ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'The Result is:',0xa
len equ $ - msg
section .bss
res resb 1
上面的代码编译和执行时,它会产生以下结果:
The Result is:
6
BCD 表示
BCD表示有两种类型:
- 未打包BCD表示BCD表示
- 压缩BCD表示
在压缩BCD表示,每个字节的存储的二进制相当于十进制数字。例如,被存储为数字1234:
01 02 03 04H
有两种处理这些数字指令:
- AAM - 乘法后ASCII调整
- AAD - ASCII除法前调整
四个ASCII调整指令,AAA,AAS,AAM和AAD也可以用压缩BCD表示。压缩BCD码表示,每个使用四位数字存储。两位十进制数被打包成一个字节。例如,被存储为数字1234:
12 34H
有两种处理这些数字指令:
- DAA - Decimal Adjust After Addition
- DAS - decimal Adjust After Subtraction
乘法和除法包装BCD表示不支持。
例子:
下面的程序增加了两个5位数的十进制数和显示的总和。它使用上述的概念:
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry yiibai
mov esi, 4 ;yiibaiing to the rightmost digit
mov ecx, 5 ;num of digits
clc
add_loop:
mov al, [num1 + esi]
adc al, [num2 + esi]
aaa
pushf
or al, 30h
popf
mov [sum + esi], al
dec esi
loop add_loop
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov edx,5 ;message length
mov ecx,sum ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'The Sum is:',0xa
len equ $ - msg
num1 db '12345'
num2 db '23456'
sum db ' '
上面的代码编译和执行时,它会产生以下结果:
The Sum is:
35801
Assembly汇编 字符串处理 - Assembly汇编
在我们前面的例子中,我们已经使用可变长度的字符串。注意到可变长度的字符串可以有尽可能多的字符。一般情况下,我们指定的字符串长度的两种方法之一:
- 明确存储字符串长度
- 使用定点字符
我们可以明确存储字符串的长度,使用位置计数器符号代表位置计数器的当前值。在下面的例子:
msg db 'Hello, world!',0xa ;our dear string
len equ $ - msg ;length of our dear string
$ 点后的字节的最后一个字符的字符串变量msg。因此,$-msg 给出的字符串的长度。我们也可以写:
msg db 'Hello, world!',0xa ;our dear string
len equ 13 ;length of our dear string
或者可以存储结尾的定点字符分隔的字符串,而不是显式存储的字符串长度的字符串。定点字符应该是一个不会出现在字符串中的特殊字符。
例子:
message DB 'I am loving it!', 0
String指令
每个字符串指令可能需要一个源操作数,目的操作数或两者兼有。对于32位段,串指令使用ESI和EDI寄存器分别指向源和目的操作数。
然而对于16位段,SI和DI寄存器用于分别为指向的源和目标。
有五种基本指令处理字符串。它们分别是:
- MOVS - 该指令移动1字节,字或双字的数据从内存中的位置到另一个。
- LODS - 该指令从存储器加载。如果操作数是一个字节,它被加载到AL寄存器中,如果操作数是一个字,它被装入AX寄存器EAX寄存器被装入一个双字。
- STOS - 该指令寄存器(AL,AX或EAX)内存存储数据。
- CMPS - 这个指令比较两个数据项在内存中。数据可能是一个字节大小,字或双字。
- SCAS - 该指令寄存器(AL,AX或EAX)的内容进行比较,在内存中的一个项目的内容。
上述指令的字节,字和双版本,并可以重复使用重复前缀字符串指令。
这些指令使用ES:DI和DS:SI对寄存器DI和SI寄存器包含有效的偏移地址,是指存储在内存中的字节。 SI通常与DS(数据段)和DI总是与ES(附加段)。
DS:SI(或ESI)和ES:DI(或EDI)的源和目的操作数寄存器指向。源操作数被假设为在DS:SI(或ESI)和目标操作数ES:DI(或EDI)在内存中。
对于16-bit地址SI和DI寄存器的使用和使用ESI和EDI寄存器用于32位地址。
下表提供了各种版本的字符串指令和操作数的假设空间。
Basic Instruction | Operands at | Byte Operation | Word Operation | Double word Operation |
ES:DI, DS:EI | MOVSB | MOVSW | MOVSD | |
AX, DS:SI | LODSB | LODSW | LODSD | |
ES:DI, AX | STOSB | STOSW | STOSD | |
DS:SI, ES: DI | CMPSB | CMPSW | CMPSD | |
ES:DI, AX | SCASB | SCASW | SCASD |
重复前缀
前一个字符串的指令,例如,当设置的REP前缀 - REP MOVSB,使在CX寄存器下计数器的指令的基础上重复。 REP执行的指令,减小CX1,并检查是否CX为0。重复指令处理,,直到CX是零。
方向标志(DF)确定的方向的操作。
- Use CLD (Clear Direction Flag, DF = 0)使操作左到右。
- Use STD (Set Direction Flag, DF = 1) 使操作从右到左。
REP前缀也有以下的变化:
- REP: 它是无条件的重复。它重复操作,直到CX是零。
- REPE or REPZ: 它是有条件的重复。它重复操作,而零标志表示等于/零。它停止时,表示不等于ZF/零或当CX是零。
- REPNE or REPNZ:这也是有条件的重复。重复操作,而零标志表明不等于/零。它停止时,ZF表示零或等于/ CX递减到零。
Assembly汇编 数组 - Assembly汇编
我们已经讨论了用于为变量分配存储的数据定义指令的汇编。变量也可以用一些特定的值被初始化。可以指定初始化值,十六进制,十进制或二进制形式。
例如,我们可以定义一个字变量months 以下方式之一:
MONTHS DW 12
MONTHS DW 0CH
MONTHS DW 0110B
数据定义指令也可以被用于定义一个一维数组。让我们定义一个一维数组存储数字。
NUMBERS DW 34, 45, 56, 67, 75, 89
上述定义数组声明六个字每个初始化的数字34,45,56,67,75,89。此分配2×6=12个字节的连续的存储器空间。符号地址的第一个数字的号码,以及该第二个数字将号码+2,依此类推。
让我们举了另一个例子。可以定义一个数组大小为8的空间,并初始化所有值为零,如:
INVENTORY DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
其中,可以缩写为:
INVENTORY DW 0, 0 , 0 , 0 , 0 , 0 , 0 , 0
TIMES指令也可以被用于多个初始化为相同的值。使用TIMES,数组可以被定义为
INVENTORY TIMES 8 DW 0
例如:
下面的示例演示通过上述概念定义一个3元素数组x,其中存储了三个值:2,3和4。它添加数组中的值并显示的总和9:
section .text
global _start ;must be declared for linker (ld)
_start:
mov eax,3 ;number bytes to be summed
mov ebx,0 ;EBX will store the sum
mov ecx, x ;ECX will yiibai to the current element to be summed
top: add ebx, [ecx]
add ecx,1 ;move yiibaier to next element
dec eax ;decrement counter
jnz top ;if counter not 0, then loop again
done:
add ebx, '0'
mov [sum], ebx ;done, store result in "sum"
display:
mov edx,1 ;message length
mov ecx, sum ;message to write
mov ebx, 1 ;file descriptor (stdout)
mov eax, 4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax, 1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
global x
x:
db 2
db 4
db 3
sum:
db 0
上面的代码编译和执行时,它会产生以下结果:
9
Assembly汇编 过程 - Assembly汇编
过程或子程序在汇编语言是很重要的,汇编语言程序往往是规模大。过程是由一个名称标识。按照这一名称,在过程体(body)中进行了描述,其中执行一个明确定义的工作。一个return语句表示程序结束。
语法:
以下是语法来定义一个过程:
proc_name:
procedure body
...
ret
该过程被称为另一个函数使用CALL指令。 CALL指令应该有所谓的程序的名称作为参数,如下所示:
CALL proc_name
被调用过程返回给调用过程的控制,通过使用RET指令。
例子:
让我们写一个很简单的程序,命名为sum 添加变量存储在ECX和EDX寄存器EAX寄存器中返回的总和:
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry yiibai
mov ecx,'4'
sub ecx, '0'
mov edx, '5'
sub edx, '0'
call sum ;call sum procedure
mov [res], eax
mov ecx, msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx, res
mov edx, 1
mov ebx, 1 ;file descriptor (stdout)
mov eax, 4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
sum:
mov eax, ecx
add eax, edx
add eax, '0'
ret
section .data
msg db "The sum is:", 0xA,0xD
len equ $- msg
segment .bss
res resb 1
上面的代码编译和执行时,它会产生以下结果:
The sum is:
9
堆栈的数据结构:
堆栈是一个类似数组的数据结构在存储器中的数据可以被存储和删除的位置被称为“顶层”堆栈。是'推'入堆栈,要检索的数据是'弹出'从堆栈中的数据需要存储。堆栈是一个后进先出的数据结构,即先存储,数据检索。
汇编语言提供了两种的堆栈操作说明:PUSH和POP。这些指令的语法,如:
PUSH operand
POP address/register
堆栈段中保留的存储器空间用于执行堆栈。用于执行堆栈的寄存器SS和ESP(或SP)。所指向的堆栈的顶部,它指向最后一个数据项插入到堆栈的SS:ESP寄存器SS寄存器指向堆栈段的开始和SP(或ESP),其中给出的偏移量堆栈段。
实现的栈具有以下特点:
- 只有一个字符或二个字符入堆栈,而不是一个字节可以保存。
- 堆栈增长在相反的方向,即,朝向下底部的内存地址
- 堆栈中的堆栈的顶部插入到最后一项,它指向插入的最后一个字的低字节。
正如我们讨论过,它可以存储在堆栈中的寄存器的值,在使用它们之前的一些使用方式如下:
; Save the AX and BX registers in the stack
PUSH AX
PUSH BX
; Use the registers for other purpose
MOV AX, VALUE1
MOV BX, VALUE2
...
MOV VALUE1, AX
MOV VALUE2, BX
; Restore the original values
POP AX
POP BX
例子:
下面的程序显示了整个ASCII字符集。主程序调用一个程序命名为display,显示的ASCII字符集。
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry yiibai
call display
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
display:
mov ecx, 256
next:
push ecx
mov eax, 4
mov ebx, 1
mov ecx, achar
mov edx, 1
int 80h
pop ecx
mov dx, [achar]
cmp byte [achar], 0dh
inc byte [achar]
loop next
ret
section .data
achar db '0'
上面的代码编译和执行时,它会产生以下结果:
0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}
...
...
Assembly汇编 递归 - Assembly汇编
递归过程调用本身。有两种类型:直接和间接的递归。在直接递归过程调用和间接递归,第一个程序调用了第二个过程,这反过来调用的第一个程序。
递归被发现许多的数学算法。例如,考虑的情况下,计算一个数的阶乘。一个数的阶乘是由下式给出:
Fact (n) = n * fact (n-1) for n > 0
例如:5的阶乘是1×2×3×4×5=5×4的阶乘,并显示一个递归的过程,这可能是一个很好的例子。每一个递归算法必须有一个结束条件,即满足某种条件时,应停止递归调用的程序。阶乘算法结束条件的情况下,当n为0时,就结束了。
下面的程序显示了如何阶乘n的汇编语言实现。为了保持程序简单,我们将计算阶乘3。
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry yiibai
mov bx, 3 ;for calculating factorial 3
call proc_fact
add ax, 30h
mov [fact], ax
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov edx,1 ;message length
mov ecx,fact ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
proc_fact:
cmp bl, 1
jg do_calculation
mov ax, 1
ret
do_calculation:
dec bl
call proc_fact
inc bl
mul bl ;ax = al * bl
ret
section .data
msg db 'Factorial 3 is:',0xa
len equ $ - msg
section .bss
fact resb 1
上面的代码编译和执行时,它会产生以下结果:
Factorial 3 is:
6
Assembly汇编 宏 - Assembly汇编
编写宏是确保在汇编语言模块化编程的另一种方式。
- 宏是一个指令序列,通过名称和分配程序可以在任何地方使用。
- 在NASM中,宏定义%macro和%endmacro指令。
- 宏开始%macro指令,结束%endmacro指令。
宏定义的语法:
%macro macro_name number_of_params
<macro body>
%endmacro
其中,number_of_params指定数目的参数,macro_name指定宏名称。
调用宏时,通过使用宏的名称以及必要的参数。当需要使用一些指令序列多次,可以把这些指令在宏,并用它来代替书面说明。
例如,一个很常见的程序需要在屏幕上写了一串字符。显示一串字符,需要下面的指令序列:
mov edx,len ;message length
mov ecx,msg ;message to write
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
我们已经观察到,一些指令IMUL一样,IDIV,INT等,需要的一些信息被储存在一些特定的寄存器,甚至在一些特定的寄存器(次)返回值。如果该程序已经在使用这些寄存器用于保存重要数据,然后从这些寄存器中的现有数据应保存在堆栈的指令被执行后,恢复。
在上述示例中,还显示字符串,寄存器EAX,EBX ECX和EDX,我们将使用INT 80H函数调用。所以,每次在屏幕上显示,需要这些寄存器保存在栈中调用INT 80H,然后恢复从堆栈中的寄存器的原始值。因此,它可能是有用的写两个宏用于保存和恢复数据。
例子:
下面的例子显示了定义和使用宏:
; A macro with two parameters
; Implements the write system call
%macro write_string 2
mov eax, 4
mov ebx, 1
mov ecx, %1
mov edx, %2
int 80h
%endmacro
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry yiibai
write_string msg1, len1
write_string msg2, len2
write_string msg3, len3
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg1 db 'Hello, programmers!',0xA,0xD
len1 equ $ - msg1
msg2 db 'Welcome to the world of,', 0xA,0xD
len2 equ $- msg2
msg3 db 'Linux assembly programming! '
len3 equ $- msg3
上面的代码编译和执行时,它会产生以下结果:
Hello, programmers!
Welcome to the world of,
Linux assembly programming!
Assembly汇编 文件管理 - Assembly汇编
系统认为任何输入或输出数据的字节流。有三种标准的文件流:
- 标准输入 (stdin)
- 标准输出 (stdout)
- 标准错误 (stderr)
文件描述
文件描述符是一个16位的整数,分配到一个文件作为一个文件ID。当一个新的文件被创建,或者打开现有的文件,文件描述符用于访问该文件。
标准的文件流 - 标准输入,输出和错误文件描述符分别为0,1和2。
文件指针
文件指针指定的位置用于后续读/写操作的字节文件。每个文件被认为是一个字节序列。每个打开的文件相关联的一个文件指针,指定的偏移量(以字节为单位),相对于文件开头的。当一个文件被打开时,文件指针被设置为零。
文件处理系统调用
下表简要介绍了相关文件处理系统调用:
%eax | Name | %ebx | %ecx | %edx |
2 | sys_fork | struct pt_regs | - | - |
3 | sys_read | unsigned int | char * | size_t |
4 | sys_write | unsigned int | const char * | size_t |
5 | sys_open | const char * | int | int |
6 | sys_close | unsigned int | - | - |
8 | sys_creat | const char * | int | - |
19 | sys_lseek | unsigned int | off_t | unsigned int |
使用系统调用所需的步骤是一样的,正如我们前面讨论过的:
- 把EAX寄存器中的系统调用号。
- 存储的参数在寄存器中的系统调用EBX,ECX等
- 调用相关的中断(80H)
- 结果通常是在EAX寄存器中返回
创建和打开文件
对于创建和打开一个文件,请执行以下任务:
- 将系统调用sys_creat()数字8,在EAX寄存器中
- 把文件名放到EBX寄存器中
- 将文件权限放到ECX寄存器中
EAX寄存器中创建的文件系统调用返回的文件描述符,在错误的情况下,错误代码是在EAX寄存器中。
打开一个已存在的文件
为了打开一个现有的文件,请执行以下任务:
- 将系统调用sys_open() 数字5到EAX寄存器中
- 把文件名EBX寄存器中
- 将文件访问模式放入到ECX寄存器
- 把文件权限放到EDX寄存器中
EAX寄存器中创建的文件系统调用返回的文件描述符,在错误的情况下,错误代码是在EAX寄存器中。
在该文件的访问模式中,最常用的有:只读(0),只写(1),(2)读写。
文件读取
读取文件,执行以下任务:
- 将系统调用sys_read() 数字3到EAX寄存器中
- 把文件描述符放入 EBX寄存器
- 将输入缓冲区的指针放入 EBX寄存器
- 将缓冲区的大小,即要读取的字节数放到EDX寄存器中
系统调用返回EAX寄存器中读取的字节数,错误代码是在错误的情况下,在EAX寄存器中。
写入文件
写入到一个文件中,执行以下任务:
- 把系统调用 sys_write() 数字4放到 ECX 寄存器
- 把文件描述符放入 EBX 寄存器
- 输出缓冲区的指针放入 EBX 寄存器
- 将缓冲区的大小,即要写入的字节数放入 EBX 寄存器
系统调用返回EAX寄存器中写入的字节的实际数量,在错误的情况下,错误代码是在EAX寄存器中。
关闭文件
为了关闭文件,请执行以下任务:
- 把系统调用sys_close() 数字 6放到 ECX 寄存器
- 把文件描述符放入到EBX寄存器
系统调用返回时,在错误的情况下,在EAX寄存器中的错误代码。
更新文件
对于更新文件,请执行以下任务:
- 把系统调用sys_lseek () 数字19放到 ECX 寄存器中
- 将文件描述符放到 ECX 寄存器中
- 将偏移值放到 ECX 寄存器中
- 将基准位置的偏移量放在EDX寄存器中
参考位置可以是:
- 文件开始的位置 - 0
- 当前文件位置- 1
- 文件尾 - 2
系统调用返回时,在错误的情况下,在EAX寄存器中的错误代码。
例子:
下面的程序创建和打开一个文件,名为myfile.txt,并写入一个文本'Welcome to Yiibai“到这个文件中。接下来,从文件中读取的程序和存储数据到一个缓冲区中的命名信息。最后,它显示的文本信息存储。
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry yiibai
;create the file
mov eax, 8
mov ebx, file_name
mov ecx, 0777 ;read, write and execute by all
int 0x80 ;call kernel
mov [fd_out], eax
; write into the file
mov edx,len ;number of bytes
mov ecx, msg ;message to write
mov ebx, [fd_out] ;file descriptor
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
; close the file
mov eax, 6
mov ebx, [fd_out]
; write the message indicating end of file write
mov eax, 4
mov ebx, 1
mov ecx, msg_done
mov edx, len_done
int 0x80
;open the file for reading
mov eax, 5
mov ebx, file_name
mov ecx, 0 ;for read only access
mov edx, 0777 ;read, write and execute by all
int 0x80
mov [fd_in], eax
;read from file
mov eax, 3
mov ebx, [fd_in]
mov ecx, info
mov edx, 26
int 0x80
; close the file
mov eax, 6
mov ebx, [fd_in]
; print the info
mov eax, 4
mov ebx, 1
mov ecx, info
mov edx, 26
int 0x80
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
file_name db 'myfile.txt'
msg db 'Welcome to yiibai.com'
len equ $-msg
msg_done db 'Written to file', 0xa
len_done equ $-msg_done
section .bss
fd_out resb 1
fd_in resb 1
info resb 26
上面的代码编译和执行时,它会产生以下结果:
Written to file
Welcome to yiibai.com
Assembly汇编 内存管理 - Assembly汇编
由内核提供的sys_brk()系统调用,分配内存而无需移除。这个调用应用图像存储在内存分配内存后面。本系统功能允许您设置的最高的可用地址的数据部分。
这个系统调用需要一个参数,这是最高的内存地址需要设置。这个值被存储在EBX寄存器。
任何错误的情况下sys_brk()返回-1或返回负的错误代码本身。下面的例子演示了动态内存分配。
例子:
下面的程序分配16KB内存使用sys_brk()系统调用:
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry yiibai
mov eax, 45 ;sys_brk
xor ebx, ebx
int 80h
add eax, 16384 ;number of bytes to be reserved
mov ebx, eax
mov eax, 45 ;sys_brk
int 80h
cmp eax, 0
jl exit ;exit, if error
mov edi, eax ;EDI = highest available address
sub edi, 4 ;yiibaiing to the last DWORD
mov ecx, 4096 ;number of DWORDs allocated
xor eax, eax ;clear eax
std ;backward
rep stosd ;repete for entire allocated area
cld ;put DF flag to normal state
mov eax, 4
mov ebx, 1
mov ecx, msg
mov edx, len
int 80h ;print a message
exit:
mov eax, 1
xor ebx, ebx
int 80h
section .data
msg db "Allocated 16 kb of memory!", 10
len equ $ - msg
上面的代码编译和执行时,它会产生以下结果:
Allocated 16 kb of memory!




