抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

基本语法

汇编程序可以分为三个段

  • data段
  • bss段
  • text段

data段

数据段被用于声明初始化的数据或常数。此数据在运行时不会更改。可以在段中声明各种常用值,文件名或缓冲区大小等。

1
section .data

bss段

在bss声明变量。

1
2
3
section .bss


text段

代码段被用于保持实际的代码。该段必须以全集声明_start开头,该声明告诉内核程序从何处开始执行。

1
2
3
section .test
global _start
_start:

注释

汇编语言注释以分号(;)开头。它可以包含任何可打印字符,包括空格。它可以单独出现在一行上。

1
;该程序在屏幕上显示一条信息

或者,与指令在同一行上。

1
add eax, ebx ;加上ebx的值到eax

汇编语言声明

汇编语言程序包含三种类型的语句

  • 可执行指令或说明
  • 汇编程序指令或伪操作

可执行指令或简单的指令告诉处理器做什么。每个指令由一个操作码组成。每个可执行指令生成一个机器语言指令。该汇编指令或位操作讲述装配过程中的各方面的汇编。这些是不可执行的,不会生成机器语言指令。宏是一种代码替换机制。

汇编语言语句的语法

汇编语言语句每行输入一个语句。每个语句遵循一下格式

1
[label] mnemonic [operands] [;comment]

基本命令包括两段,第一段是要执行的指令(或助记符)的名称,第二段是命令的操作数或参数。以下是一些典型汇编语句的示例

1
2
3
4
5
6
7
inc count         ; 增加内存变量count
mov total, 48 ; 将值48转移到内存变量total
add ah,bh ; 将寄存器BH内容添加到ah寄存器
and mask1, 128 ; 对变量mask1 和 128执行and操作

add marks, 10 ; 将10加到变量marks
mov al, 10 ; 将值10传送到al寄存器

汇编语言的Hello World 程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
section .text
global _start
_start:
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)
int 0x80 ; call kernel

section .data
msg db 'Hello, world!', 0xa ;string to be printed
len equ $ - msg ;length of the string

在NASM中编译和链接程序

1
2
3
nasm -f elf hello.asm
# 链接目标文件
ld -m elf_i386 -s -o hello hello.o

汇编语言 内存段

内存段

如果将section 关键字替换为segment,也会得到相同的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
segment .text      ;code segment
global _start ;must be declared for linker

_start: ;tell linker entry point
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

内存段

分段存储器模型将系统存储器分为独立的分段组,这些分段由位于分段寄存器中的指针引用。每个段用于包含特定类型的数据。一个段用于包含指令代码,另一段用于存储数据元素,第三段保留程序堆栈。

  • 数据段 由.data段和.bss段表示。.data节用于声明存储区,在该存储区中为程序存储了数据元素。声明数据元素后,无法扩展此部分,并且在整个程序中他保持静态。.bss部分也是一个静态内存部分,它包含缓冲区,供稍后在程序中声明的数据使用。这个缓冲区内存是零填充的
  • 代码段 由.text部分表示。这在内存中定义了一个存储指令代码的区域。这也是一个固定区域
  • 堆栈 该段包含传递给程序中的函数和过程的数据值

寄存器

寄存器

处理器操作主要涉及到处理数据。该数据可以存储在存储器中并从其访问。从存储器中读取数据中将数据存储到存储器中会减慢处理器的速度,因为这涉及到通过控制总线发送数据请求并进入存储器存储单元并通过同意通道获取数据的复杂过程。

为了加速处理器的运行,处理器包括一些内存处理器存储位置,称为寄存器。寄存器存储要处理的数据元素,而不必访问存储器。处理器芯片中内置了数量有限的寄存器

处理器寄存器

IA-32体系结构中有10个32位和6个16为处理器寄存器。寄存器分三类

  • 通用寄存器
  • 控制寄存器
  • 段寄存器

通用寄存器分为以下几类

  • 数据寄存器
  • 指针寄存器
  • 索引寄存器

// todo 有个寄存器不知道叫啥

四个32位数据寄存器用于算术,用于逻辑和其他操作。

完整的32位寄存器:EAX,EBX,ECX,EDX。下半部分的32位寄存器可用作四个16位寄存器:AX,BX,CX,DX。四个16位寄存器的上半部分和下半部分可以用作八个8位寄存器:AH,BH,CH,DH,AL,BL,CL,DL

  • AX主要的累加器。用于输出/输入和绝大多数算术指令
  • BX被称作基址寄存器,因为它可以用于索引寻址
  • CX被称作计数寄存器,因为ECX,CX寄存器在迭代操作中存储循环计数
  • DX被称作数据寄存器。它也用于输入/输出操作。他还与AX寄存器以及DX一起使用,用于涉及大数值的乘法和除法运算

指针寄存器

指针寄存器是32位EIP,ESP和EBP寄存器以及相应的16位右部分IP,SP和BP。指针寄存器分为三类

  • 指令指针(IP)16位IP寄存器存储要执行的下一条指令的偏移地址。与CS寄存器
  • 堆栈指针(SP)16位SP寄存器提供程序堆栈内的偏移值。与SS寄存器(SS:SP)关联的SP是指程序堆栈中数据或地址的当前位置。
  • 基本指针(BP)16位BP寄存器主要帮助参考传递给子进程的参数变量。SS寄存器中的地址与BP中的偏移量相结合,以获取参数的位置。BP也可以与DI和SI组合用作特殊寻址的基址寄存器。

索引寄存器

32位索引寄存器ESI和EDI及最右边的16位部分。SI和DI用于索引寻址,有时用于加法和减法。有两组索引指针

  • 源索引(SI)用作字符串操作的源索引
  • 目的索引(DI)用作字符串操作的目标索引

控制寄存器

将32位指令指针寄存器的32位标志寄存器组合起来称为控制寄存器。许多指令涉及比较和数学计算,并更改标志的状态,而其他一些条件指令则测试这些状态标志的值,将控制流带到其他位置

通用标志位是:

  • 溢出标志(OF)指示有符号算术运算后数据的高阶位(最左位)的溢出。
  • 方向标志(DF)它确定向左或向右移动或比较字符串数据的方向。DF值为0时,字符串操作为从左向右的方向;当DF值为1时,字符串操作为从右至左的方向
  • 中断标志(IF)确定是否忽略或处理外部中断(如键盘中断输入等)。当值为0时,它禁用外部中断,当值为1时,使能中断
  • 陷阱标志(TF)允许在单步模式下设置处理器的操作。我们使用的DEBUG程序设置了陷阱标志,因此我们可以一次逐步执行一条指令。
  • 符号标志(SF)显示算术运算结果符号。根据算术运算后数据项的符号设置此标志。该符号由最左位的高位指示。正结果将SF的值清除为0,负结果将其设置为1.
  • 零标志(ZF)指示算术或比较运算的结果。非零结果将零标志清零,零结果将其清零
  • 辅助进位标志(AF)包含经过算术运算后从位3到位4的进位;用于专业算术。当1字节算术运算引起从第3位到第4位的进位时,将设置AF。
  • 奇偶校验标志(PF)指示从算术运算获得的结果中1位的总数。偶数个1位将奇数校验标志位清为0,奇数个1位将将奇偶校验标志清为1。
  • 进位标志(CF)在算术运算后,它包含一个高位(最左边)的进位。它还存储移位或旋转操作的最后一位的内容。

段寄存器

段是程序中定义的特定区域,用于包含数据,代码和堆栈。有三个部分

  • 代码段(CS) 它包含所有要执行的指令。16位代码段寄存器或CS寄存器存储代码段的起始地址。
  • 数据段(DS) 它包含数据、常量和工作区。16位数据段寄存器或DS寄存器存储数据段的起始地址。
  • 堆栈段(SS) 它包含数据或过程或子进程的返回地址。它被实现为“堆栈”数据结构。堆栈段寄存器或SS寄存器存储堆栈的起始地址。

除了DS,CS,SS寄存器外,还有其他段寄存器-ES(额外段),FS和GS,他们提供了用于存储数据的其他段。在汇编语言中,程序需要访问存储器的位置。段中的所有存储位置都相对于段的起始地址。段的其实地址可以时16或者16进制的整数,因此,所有此类存储地址中最右边的十六进制数字为0,通常不存储在段寄存器中。段寄存器存储段的起始地址。为了获得数据或指令在段中的确切位置,需要一个偏移值(或者位移)。为了引用段中的任何存储位置,处理器将段寄存器中的段地址与该位置的偏移值进行组合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
section .text
global _start

_start:
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
mov ecx, s2
mov ebx, 1
mov eax, 4
int 0x80

mov eax, 1 ; system call number (sys_exit)
int 0x80 ; call kernel



section .data
msg db 'Displaying 9 stars', 0xa
len equ $ - msg
s2 times 9 db '*'

系统调用

系统调用

是用户空间和内核空间之间接口的API。我们已经使用了系统调用。sys_write和sys_exit分别用于写入屏幕和退出程序

Linux系统调用

可以通过以下步骤使用linux系统调用

  • 将系统呼叫号放入EAX寄存器中
  • 将参数保存到系统调用中的寄存器EBX、ECX等中
  • 调用相关的中断(80h)
  • 结果通常在EAX寄存器中返回

用于存储系统调用参数的寄存器有六个,分别是EBX、EDX、ESI、EDI和EBP,这些寄存器采用从EBX寄存器开始的连续参数。如果有六个以上的自变量,则第一个自变量的存储位置将存储在EBX寄存器中。

1
2
mov eax,1
int 0x80

sys_write的使用

1
2
3
4
5
mov ebx,4
mov ecx,msg
mov ebx,1
mov eax,4
int 0x80

所有系统调用及其编号都列在/usr/include/asm/unistd.h中

从键盘读取一个数字并将其显示在屏幕上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

section .data
userMsg db 'Please enter a number:'
lenUserMsg equ $-userMsg
dispMsg db 'You have entered:'
lenDispMsg equ $-dispMsg

section .bss
num resb 5

section .txt
global _start

_start:
mov eax,4
mov ebx,1
mov ecx,userMsg
mov edx,lenUserMsg
int 0x80

mov eax,3
mov ebx,0
mov ecx,num
mov edx,5
int 0x80

mov eax,4
mov ebx,1
mov ecx,dispMsg
mov edx,lenDispMsg
int 0x80

mov eax,4
mov ebx,1
mov ecx,num
mov edx,5
int 0x80

mov eax,1
mov ebx,0
int 0x80

寻址模式

寻址模式

大多数汇编语言指令都需要处理操作数。操作数地址提供了要处理的数据存储的位置。有些指令不需要操作数,而另一些指令则需要一个,两个或者三个操作数。当一条指令需要两个操作数时,第一个操作数通常时目的地,他在寄存器或存储器位置中包含数据,第二个操作数是源。源包含要传递的数据(立即寻址)或数据的地址(在寄存器或者存储器中)。通常,操作后源数据保持不变。

寻址的三种基本模式是

  • 寄存器寻址
  • 立即寻址
  • 内存寻址

寄存器寻址

在这种寻址模式下,寄存器包含操作数。根据指令,寄存器可以是第一个操作数,第二个操作数或者两者。

1
2
3
mov dx,tax_rate ;寄存器是第一个操作数
mov count,cx ;寄存器是第二个操作数
mov eax,edx ;两个操作数都是寄存器

由于寄存器之间的数据处理不涉及内存,因此可以最快的处理数据

立即寻址

立即数操作数具有常量值或表达式。当具有两个操作数的指令使用立即寻址时,第二个操作数可以是寄存器或者存储器位置,而第二个操作数是立即数。第一个操作数定义数据的长度

1
2
3
4
BYTE_VALUE DB 150 ;一个字节值被定义
WORD_VALUE DW 300 ;一个字节值被定义
ADD BYTE_VALUE, 65;BYTE_VALUE加一个立即操作数65
MOV AX,45H ;立即常数45H转移到AX

直接内存寻址

在内存寻址模式下指定操作数时,通常需要直接访问主存储器,通常时数据段。这种寻址方式导致数据处理变慢。为了找到数据在内存中的确切位置,我们需要短期是地址(通常在DS寄存器中找到)和偏移量。因此偏移量也称为有效地址。在直接寻址模式下,偏移量值直接作为指令的一部分指定,通常由变量名指示。汇编器计算偏移值并维护一个符号表,该表存储程序中使用的所有变量的偏移值。直接存储寻址中,一个操作数引用一个存储器位置,另一个操作数引用一个寄存器

1
2
ADD BYTE_VALUE,DL ;将寄存器添加到存储位置
MOV BX,WORD_VALUE ;将内存中的操作数添加到寄存器中

直接偏移寻址

该模式使用算术运算符修改地址

1
2
BYTE_TABLE DB 14,15,22,45  ;字节表
WORD_TABLE 134,345,564,123 ;字表

以下操作数将数据从内存中的表访问到寄存器中

1
2
3
4
mov cl,BYTE_TABLE[2]
mov cl,BYTE_TABLE + 2
mov cx,WORD_TABLE[3]
mov cx,WORD_TABLE + 3

间接内存寻址

此寻址模式利用计算机的Segmeng:Offset寻址功能功能。通常,在方括号内百年好的基址寄存器EBX,EBP(或BX,BP)和索引寄存器(DI,SI)用于内存引用

间接寻址通常用于包含多个元素(如数组)的变量。阵列的其实地址存储在EBX寄存器中。

1
2
3
4
5
my_table times 10 DW 0  ; 分配10个字(2个字节),每个字都初始化为0
mov ebx,[my_table] ; ebx中my_table的有效地址
mov [ebx],110 ; my_table[0] = 110
add ebx,2 ; ebx = ebx + 2
mov [ebx],123 ; my_table[1] = 123

MOV 指令

该指令用于将数据从一个存储空间移动到另一个存储空间。mov指令采用两个操作数。

1
MOV destination,source

MOV 指令可能具有以下五种形式之一

1
2
3
4
5
6
MOV 寄存器, 寄存器
MOV 寄存器, 立即数
MOV 寄存器, 内存
MOV 内存, 立即数
MOV 内存, 寄存器

注意

  • MOV操作数中的两个操作数应该具有相同的大小
  • 源操作数的值保持不变

以下命令会引起歧义:

1
2
mov ebx, [my_table]
mov [ebx], 110

目前不清楚要移动等于110的字节等效值还是等效于字的字符。在这种情况下使用类型说明符时明智的。

类型说明符 寻址字节
BYTE 1
WORD 2
DWORD 4
QWORD 8
TBYTE 10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37


section .data
name db 'Alex MO'
nameLength equ $-name

newLine db '\n'
newLineLength equ $-newLine

section .text
global _start

_start:
mov edx,nameLength
mov ecx,name
mov ebx,1
mov eax,4
int 0x80

;输出\n无法实现换行
;mov edx,newLineLength
;mov ecx,newLine
;mov ebx,1
;mov eax,4
;int 0x80

mov [name], dword 'Feng'

mov edx,nameLength
mov ecx,name
mov ebx,1
mov eax,4
int 0x80

mov eax,1
mov ebx,0
int 80h

变量

变量

nasm提供了各种定义指令来为变量保留存储空间。define assembler指令用于分配存储空间。它可以用于保留以及初始化一个或多个字节。

为初始数据分配存储空间

初始化数据的存储分配语句的语法为

1
[variable-name] define-directive initial-value [,initial-value]

其中,变量名是每个存储空间的标识符。汇编器为数据段中定义的每个变量名称关联一个偏移值。

define指令有五种基本形式

指令 目的 存储空间
DB 定义字节 分配一个字节
DW 定义字 分配两个字节
DD 定义双字 分配四个字节
DQ 定义四字 分配八个字节
DT 定义十个字节 分配十个字节

以下时一些使用define指令的示例

1
2
3
4
5
6
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位表示

define指令的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
section .text
global _start

_start:
mov edx,1
mov ecx,choice
mov ebx,1
mov eax,4
int 80h

mov eax,1
mov ebx,1
int 80h

section .data
choice DB 'y'

为未初始化的数据分配存储空间

reserve指令用于为未初始化的数据保留空间。reserve指令采用单个操作数,该操作数指定要保留的空间单位数。每个define指令都有一个相关的reserve指令。

保留指令有五种基本形式

指令 目的
RESB 保留一个字节
RESW 保留字
RESD 保留双字
RESQ 保留四字
REST 保留十个字

多种定义

一个程序中可以有多个数据定义语句。例如

1
2
3
choice DB 'y'
number1 DW 123456
number2 DD 123456789

多重初始化

TIMES 指令允许多次初始化为相同的值。下面语句定义一个大小为9的标记数组并将其初始化为零

1
marks TIMES 9 DW 0

TIMES指令在定义数组和表时很有用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
section .text
global _start
_start:
mov edx,9
mov ecx,stars
mov ebx,1
mov eax,4
int 80h

mov eax,1
mov ebx,0
int 80h

section .data
starts times 9 db '*'

常量

常量

nasm提供了几个定义常量的指令。之前我们已经使用了EQU指令,下面将特别讨论几个

  • EQU
  • %assign
  • %define

EQU指令

EQU指令用于定义常量。EQU指令的语法如下

1
2
3
4
5
6
constant_name equ expression

total_students equ 50
length equ 20
width equ 10
area equ length * width

以上代码段将area定义为200

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
SYS_EXIT equ 1
SYS_WRITE equ 4
SYS_READ equ 3
STDIN equ 0
STDOUT equ 1


section .data
msg_1 db 'Hello, programers!\n',0xA,0XD
length_1 equ $ - msg_1

msg_2 db 'Please input a number:',0XA,0XD
length_2 equ $ - msg_2

msg_3 db 'Read a number:'
length_3 equ $ - msg_3

section .bss
num resb 5


section .text
global _start

_start:
mov edx,length_1
mov ecx,msg_1
mov ebx,STDOUT
mov eax,SYS_WRITE
int 80h

mov edx,length_2
mov ecx,msg_2
mov ebx,STDOUT
mov eax,SYS_WRITE
int 80h

mov edx,5
mov ecx,num
mov ebx,STDIN
mov eax,SYS_READ
int 0x80

mov edx,length_3
mov ecx,msg_3
mov ebx,STDOUT
mov eax,SYS_WRITE
int 80h

mov edx,4
mov ecx,num
mov ebx,STDOUT
mov eax,SYS_WRITE
int 80h

mov ebx,0
mov eax,SYS_EXIT
int 80h

%assign指令

在%assign指令可以用来定义数字常量像equ指令。该指令允许重新定义。

1
2
3
%assign total 10;定义常量total为10

%assign total 20;重新定义为20

注意

该指令区分大小写

%define指令

在%define指令允许定义数值和字符串常量。类似于#define

1
%define PTR [EBP+4]

该指令还允许重新定义,并且区分大小写。

算术指令

INC指令

INC 指令用于将操作数加1.它可以对寄存器或内存中的单个操作数起作用

1
2
3
4
5
6
INC destination

INC EBX; 32位寄存器,自增1
INC DL; 8位寄存器 自增1

INC [count];变量count 自增1

DEC指令

DEC指令用于将操作数减一。它可以对在寄存器或内存中的单个操作数起作用。

1
DEC destination

操作数目的可以是8位,16位或32位操作数

1
2
3
4
5
6
7
8
9
10
11
12
13
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位操作数

1
ADD/SUB destination, source

ADD/SUB指令可以用于

  • 寄存器 to 寄存器
  • 内存 to 寄存器
  • 寄存器 to 内存
  • 寄存器 to 常量数据
  • 内存 to 常量数据

但是,像其他指令一样,使用ADD/SUB指令也无法进行存储器到存储器的操作。ADD或SUB操作设置或清除溢出和进位标志

下面的示例将要求用于输入两位数字,分别将这些数字存储在EAX和EBX寄存器中,将这些值相加,将结果存储在res存储位置中,最后显示显示结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
; 为什么equ 指令可以不在section .data中

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



_start:
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, [num1]
sub eax, '0'

mov ebx, [num2]
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 80h

MUL/IMUL指令

有两条指令用于将二进制数据相乘。MUL(乘法)指令处理unsign的数据,而IMUL(整数乘法)则处理sign数据。两条指令影响进位和溢出标志

MUL/IMUL指令的语法如下:

1
MUL/IMUL multiplier

在这种情况下,被乘数都将在一个累加器中,具体取决于被乘数和乘数的大小,并更具操作数的大小,生成的乘积还将存储在两个寄存器中。以下部分说明了三种不同情况下的MUL指令

情况 描述
当两个字节相乘时 被乘数在AL寄存器中,而乘数在存储器或
另一个寄存器中为一个字节。此情况使用AX。
乘积的高8位存储在AH中,低8位存储在AL中。
当两个单字相乘时 被乘数应位于AX寄存器中,并且乘数是内存
或其他寄存器中的一个字。列如,对于MUL DX之类的指令,
必须将乘数存储在DX中,将被乘数存储在AX中。结果乘积是一个双字。
将需要两个寄存器。高阶(最左侧)部分存储在DX中,而低阶(最右侧)
部分存储在AX中
当两个双字值相乘时 当两个双字值相乘时,被乘数应位于EAX中,并且该乘数是存储
在存储器或另一个存储器中的双字值。生成的乘积存储在EDX:EAX
寄存器中,即,高32位存储在EDX寄存器中,低32位存储在EAX寄存器中
1
2
3
4
5
6
7
MOV AL, 10
MOV DL, 25
MUL DL

MOV DL,0FFH
MOV AL,0BEH
IMUL DL

DIV/IDIV指令

除法运算生成两个元素-商和余数。在除法的情况下,可能会发生溢出。如果发生溢出,处理器将产生中断。DIV用于unsign数据,IDIV用于签名数据

1
DIV/IDIV divisor
情况 描述
当除数是1个字节时 假定被除数在AX寄存器(16位)中。除法后,商进入AL寄存器,余数进入AH寄存器
当除数为1个字时 假定被除数在DX:AX寄存器32位长,高位16在DX中,低位16位在AX中。除后16位的商进入AX寄存器,其余16位进入DX寄存器
当除数为双字时 假定被除数在EDX:EAX寄存器中被除数64位长。高32位在EDX中,低阶32位位于EAX中。除后32位的商进入EAX寄存器,而32位的余数进入EDX寄存器

以下示例将8除以2.被除数8存储在16位AX寄存器中,除数2存储在8位BL寄存器中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
section .data
msg db 'The result is:',0xA,0xD
len equ $-msg

segment .bss
res resb 1

section .text
global _start

_start:
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
mov eax,4
int 80h

mov ecx,res
mov edx,1
mov ebx,1
mov eax,4
int 80h

mov ebx,0
mov eax,1
int 80h

逻辑指令

逻辑指令

处理器指令集提供指令AND、OR、XOR、TEST和NOT布尔逻辑

指令 格式
AND AND 操作数1,操作数2
OR OR 操作数1,操作数2
XOR XOR 操作数1,操作数2
TEST TEST 操作数1,操作数2
NOT NOT 操作数1

在所有情况下,第一个操作数都可以在寄存器或内存中。第二个操作数可以实在寄存器/内存,也可以是常数。但是,内存到内存操作是不可以的

AND指令

AND指令用于通过执行按位AND运算来支持逻辑表达式

1
2
3
4
             Operand1:  0101
Operand2: 0011
----------------------------
After AND -> Operand1: 0001

AND操作可用于清除一个或多个位。例如,假设BL寄存器包含00111010,如果要将高阶位清零,则将其与0FH进行与运算

1
AND BL,0FH

如果要检查给定 的数字是偶数还是奇数,一个简单的测试是检查数字的最低有效位。如果为1 ,则为奇数,否则为偶数。假设数字在AL寄存器中,我们可以写

1
2
AND AL,01H
JZ EVEN_NUMBER

以下程序说明了这点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
section .data
even_msg db 'Even Number!'
len1 equ $ - even_msg

odd_msg db 'Odd Number!'
len2 equ $ - odd_msg


section .text
global _start

_start:
mov ax,8h
and ax,1
jz evnn

mov eax,4
mov ebx,1
mov ecx,odd_msg
mov edx,len2
int 80h
jmp outprog

evnn:
mov ah,09h
mov eax,4
mov ebx,1
mov ecx,even_msg
mov edx,len1
int 80h

outprog:
mov eax,1
mov 80h

同样,要清楚整个寄存器,可以将其与00H进行与运算

OR指令

OR指令用于执行按位或运算来支持逻辑表达式。两个中的任意一个为一或都为一,则按位OR运算符将返回1。如果两个位均为零,则返回0

1
2
3
4
             Operand1:     0101
Operand2: 0011
----------------------------
After OR -> Operand1: 0111

或运算可用于设置一个或多个位。如果AL寄存器包含0011 1010,则需要设置四个低阶位,可以将其与值0000 1111即0FH进行或运算

1
OR bl,0fh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
section .bss
result resb 1

section .text
global _start

_start:
mov al,5
mov bl,3
or al,bl
add al, byte '0'

mov [result],al
mov eax,4
mov ebx,1
mov ecx,result
mov edx,1
int 80h

outprog:
mov eax,1
int 80h

XOR指令

XOR指令实现按位XOR运算。当且仅当来自操作数的位不同时,XOR运算将结果置为1.如果来自操作数的位相同,则将结果位清零

1
2
3
4
             Operand1:     0101
Operand2: 0011
----------------------------
After XOR -> Operand1: 0110

将操作数与自身进行XOR会将操作数更改位0.这用于清楚寄存器

1
XOR EAX,EAX

TEST指令

TEST指令与AND运算的工作原理相同,但是与AND不同的时,他不会更改第一个操作数。因此,如果我们需要检查寄存器中的数字时偶数还是奇数,我们可以使用TEST指令执行此操作,无需更改原始数字

1
2
3
TEST AL,01H

JZ EVEN_NUMBER

NOT指令

NOT指令实现按位NOT运算。NOT操作将操作数中的位取反。操作数可以在寄存器中,也可以在存储器中

1
2
             Operand1:    0101 0011
After NOT -> Operand1: 1010 1100

条件

条件

汇编语言中的条件执行是通过几个循环和分支指令来完成的。这些指令可以更改程序的控制流。

  • 无条件跳转 这是通过JMP指令执行的。条件执行通常涉及将控制权转移到不遵循当前执行指令的指令的地址。控制权的转移可以是前进的(执行新的指令集),也可以是后退的(重新执行相同的步骤)
  • 无条件转移 这取决于条件由一组跳转指令j < condition >执行。条件指令通过中断顺序流程来转移控制,而他们通过更改IP中的偏移值来进行控制的

CMP指令

CMP指令比较两个操作数。它通常用于条件执行中。该指令基本上从一个操作数中减去一个操作数,以比较操作数是否相等。他不会干扰目标或源操作数。它与条件跳转指令一起用于决策

1
CMP destination,source

CMP 比较两个数字数据字段。目标操作数可以在寄存器中或在内存中。源操作数可以是常数数据,寄存器或内存

1
2
3
4
5
6
cmp dx,00
je L7


L7:

CMP 通常用于比较计数器值是否已达到需要运行循环的次数。

1
2
3
inc edx
cmp edx,10
jle LP1 ; 如果edx小于等于10,则跳转LP1

无条件跳转

如前所述,这是通过JMP指令执行的。条件执行通常涉及将控制权转移到不遵循当前执行指令的地址。控制权的转移是可以前进的(执行新的指令集),也可以是后退的(重新执行相同的步骤)

JMP提供一个标签名称,控制流将立即转移到改标签名称。JMP指令的语法是

1
JMP label

下面代码说明了JMP指令

1
2
3
4
5
6
7
8
mov ax,00
mov bx,00
mov cx,01
L20:
add ax,01
add bx,ax
shl cx,1;向左移动cx,这反过来使cx的值翻倍
jmp L20 ;重复语句

条件跳转

如果在条件跳转中满足某些指定条件,则控制流程将转移到目标指令。

以下是用于算术运算的有符号数据的条件跳转指令:

指令 描述 标志测试
JE/JZ 跳转等于或跳转零 ZF
JNE/JNZ 跳转不等于或跳转不为零 ZF
JG/JNLE 跳转大于或跳转不小于/等于 OF,SF,ZF
JGE/JNL 跳转大于/等于或不小于跳转 OF,SF
JL/JNGE 跳转小于或不大于/等于 OF,SF
JLE/JNG 跳转小于/等于或跳不大于

以下是对用于逻辑运算的无符号数据使用的条件跳转指令:

指令 描述 标志测试
JE/JZ 跳转等于或跳转零 ZF
JNE/JNZ 跳转不等于或跳转不为零 ZF
JA/JNBE 跳转向上或不低于/等于 CF,ZF
JAE/JNB 高于/等于或不低于 CF
JB/JNAE 跳转到以下或跳转不高于/等于 CF
JBE/JNA 跳到下面/等于或不跳到上方 AF,CF

下面跳转指令有特殊用途,并检查标志位置

指令 描述 标志测试
JXCZ 如果CX为零则跳转 没有
JC 如果携带则跳转 CF
JNC 如果不携带则跳转 CF
JO 溢出时跳转 OF
JNO 如果没有溢出则跳转 OF
JP/JNP 跳校验或偶校验 PF
JNP/JPO 跳转无就校验或跳转奇偶校验 PF
JS 跳跃符号(负值) SF
JNS 跳转无符号(正值) SF

循环

循环

JMP指令可以实现循环

1
2
3
4
5
mov cl,10
L1:
<LOOP_BODY>
dec cl
jnz l1

但是处理器指令集包括一组用于实现迭代的循环指令。基本的loop指令具有以下语法

1
LOOP label

其中,label是标识目标指令的目标标签,如跳转指令中所述。LOOP指令假定ECX寄存器包含循环计数。当执行循环指令时,ECX寄存器递减,并且控制跳转至目标标签,直到ECX寄存器的值为零为止。

1
2
3
4
mov ecx,10
l1:
<loop body>
loop l1

例子

以下程序在屏幕上打印数字1到9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
section .text
global _start
section .bss
num resb 1

_start:
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 80

mov eax,[num]
sub eax,'0'
inc eax
add eax,'0'
pop ecx
loop l1

mov eax,1
mov ebx,0
int 80

数字

数字

数值数据通常用二进制表示。算术指令对二进制数据进行操作,当数字显示在屏幕上或对键盘进行输入时,他们是ASCII形式。到目前为止,我们已经将该输入数据以ASCII形式转移为二进制以进行算术计算,并将结果转换回二进制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
section .text
global _start

section .data
msg db 'this sum is:',0xA,0xD
len equ $-msg
segment .bss
sum resb 1

_start:
mov eab,'3'
sub eax,'0'

mov ebx,'4'
sub ebx,'0'
add eax,ebx
add eax,'0'

mov [sum],eax
mov edx,len
mov ecx,msg
mov ebx,1
mov eax,4
in 80h

mov edx,1
mov ecx,sum
mov ebx,1
mov eax,4
int 80h

mov eax,1
mov ebx,0
int 80h


但是此类转换会产生开销,并且汇编语言编程允许更有效的方式以二进制形式处理数字。小树可以以两种形式表示

ASCII形式

BCD或二进制编码的十进制形式

ASCII表示

在ASCII表示中,十进制数字存储为ASCII字符字符串。

例如,十进制值存储为1234

1
31 32 33 34h

其中,32H是1的ASCII值,32H是2的ASCII值。有四个指令用于处理ASCII表示形式的数字

AAA-加法后ASCII调整

AAS-减法后ASCII调整

AAM-乘法后ASCII调整

AAD-除法前ASCII调整

这些指令不使用任何操作数,并假定所需的操作数位于AL寄存器中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
section .data
msg db 'The Result is:',0xa
len equ $-msg
section .bss
res resb 1

section .text
global _start

_start:
sub ah,ah
mov al,'9'
sub al,'3'
ass
or al,30h
mov [res],ax

mov edx,len
mov ecx,msg
mov ebx,1
mov eax,4
int 80

mov edx,1
mov ecx,res
mov ebx,1
mov eax,4
int 80h

mov eax,1
mov ebx,0
int 80

BCD表示

BCD表示有两种类型

  • 未打包的BCD表示
  • 打包的BCD表示

在未压缩的BCD表示形式中,每个字节都存储一个十进制数字的二进制等效项

1
01 02 03 04h

有两个指令来处理这些数字

  • AAM-乘法后ASCII调整
  • AAD-除法前后ASCII调整

四个ASCII调整指令AAA,AAS,AAM和AAD也可以与未打包的BCD表示一起使用。在打包的BCD表示中,每个数字使用四位存储。每个十进制数字打包成一个字节.例如,数字1234存储为

1
12 34h

有两个指令来处理这些数字

  • DAA-加法后的十进制调整
  • DAS-减法后的十进制调整

打包的BCD表示形式不支持乘法和除法

以下程序将两个五位十进制数字加起来并显示总和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
section .text
global _start

_start:
mov esi,5
mov ecx,5
clc

add_num:
mov al,[num1+esi]
adc al,[num2+esi]
aaa
pushf

mov [sum+esi],al
dec esi
num add_num

mov edx,len
mov ecx,msg
mov ebx,1
mov eax,4
int 80h

mov edx,5
mov ecx,sum
mov ebx,1
mov eax,4
int 80h

mov eax,1
mov ebx,0
int 80

section .data
msg db 'The Sum is:',0xa
len equ $-msg
num1 db '12345'
num2 db '23456'
sum db ' '

字符串

字符串

在前面的示例中,我们使用了 可变长度的字符串。可变长度字符串可以根据需要包含任意多个字符。通常我们通过以下两种方式之一指定字符串的长度

  • 显示存储字符串长度
  • 使用哨兵字符

我们可以使用表示位置计数器当前的$位置计数器符号来显示存储字符串长度

1
2
msg db 'hello,world!',0xa
len equ $ - msg

$指向字符串变量msg的最后一个字符之后的字节。因此 $-msg给出字符串的长度

1
2
msg db 'hello,world!',0xa
len equ 13

另外,可以存储尾部定点字符的字符串来定界字符串,而不是显示存储字符串长度。前哨字符应为不出现在字符串中的特殊字符

1
message DB 'I am loving it!',0

字符串指令

每个字符串指令可能需要一个源指令,一个目标操作数或两者。对于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位地址,使用SI和DI寄存器,对于32位地址,使用ESI和EDI寄存器

下表提供了各种版本的字符串指令和假定的操作数空间

基本指令 操作数的寄存器 字节运算 字运算 双字运算
MOVS 目的地址 ES:DI,源地址 DS:SI MOVSB MOVSW MOVSD
LODS DS:SI LODSB LODSW LODSD
STOS ES:DI,AX STOSB STOS STOSD
CMPS DS:SI,ES:DI CMPSB CMPSW CMPSD
SCAS ES:DI,AX SCASB SCASW SCASD

MOVS

MOVS指令用于将数据项(字节,字或双字)从源字符串复制到目标字符串。源字符串由DS:SI指向,目标字符串由ES:DI指向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
section .text
global _start

section .data
s1 db 'Hello,world',0
len equ $-s1

section .bss
s2 resb 20


_start:
mov ecx,len
mov esi,s1
mov edi,s2
cld
rep movsb

mov edx,20
mov ecx,s2
mov ebx,1
mov eax,4
int 80h

LODS

在密码术中,凯撒密码是最简单的已知加密技术之一。在这种方法中,要加密的数据中的每个字母都被替换为字母下方固定数量位置的字母。在示例中,我们通过简单的将其中的每个字母替换为两个字母来加密数据,因此a将替换为c,b替换为d等。我们使用LODS将原始字符串‘password’加载到内存中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
section .text
global _start

_start:
mov ecx,len
mov esi,s1
mov edi,s2


loop_here:
lodsb
add a1,02
stosb

loop loop_here
cld
rep movsb

mov edx,20
mov ecx,s2
mov ebx,1
mov eax,4
int 80h

mov eax,1
mov ebx,0
int 80h

section .data
s1 db 'password',0
len equ $-s1

section .bss
s2 resb 10

STOS

STOS指令将数据项从AL(对于字节-STOSB),AX(对于字-STOSW)或EAX(对于双字-STOSD)复制到目标字符串,该目标字符串由内存中的ES:DI指向。以下示例演示了如何是哟该LODS和STOS指令将大写字符串转换为小写值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
section .text
global _start

section .data
s1 db 'HELLO,WORLD',0
len equ $-sq
section .bss
s2 resb 20

_start:
mov ecx,len
mov esi,s1
mov edi,s2

loop_here:
lodsb
or al,20h
stosb
loop loop_here
cld
rep movsb

mov edx,20
mov ecx,s2
mov ebx,1
mov eax,4
int 80h

mov eax,1
mov ebx,0
int 80h

CMPS

CMPS指令比较两个字符串。该指令比较DS:SI和ES:DI寄存器所指向的两个数据项,即一个字节,一个字或一个双字,并相应的设置标志。还可以将条件跳转指令一起使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
section .text
global _start

_start:
mov esi,s1
mov edi,s2
mov ecx,len2
cld
repe cmpsb
jecxz equal

mov edx,len_neq
mov ecx,msg_neq
mov ebx,1
mov eax,4
int 80h

jmp exit

equal:
mov edx,len_eq
mov ecx,msg_eq
mov ebx,1
mov eax,4
int 80h

exit:
mov eax,1
mov ebx,0
int 80h


section .data
s1 db 'Hello,world!',0
len1 equ $ - s1

s2 db 'Hello,there!',0
len2 equ $ - s2

msg_eq db 'Strings are equal!',0xa
len_eq equ $ - msg_eq

msg_neq db 'Strings are not equal!',0xa
len_neq equ $ - msg_neq

SCAS

SCAS指令用于搜索字符串中的特定字符或一组字符。要搜索的数据项应该在AL(对于SCASB),AX(对于SCASW)或EAX(SCASD)寄存器中。要搜索的字符串应在内存中,并由ES:DI(或EDI)寄存器指向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
section .text
global _start
exit:
mov eax,1
mov ebx,0
int 80h

found:
mov eax,4
mov edx,len_found
mov ecx,msg_found
mov ebx,1
int 80h

_start:
mov ecx,len
mov edi,my_string
mov al,'e'
cld
repne scasb
je found

mov edx,len_not_found
mov ecx,msg_not_found
mov ebx,1
mov eax,4
int 80h
jmp exit



section .data
my_string db 'hello world',0
len equ $ - my_string

msg_found db 'found',0xa
len_found equ $ - msg_found

msg_not_found db 'not found!',0xa
len_not_found equ $ - msg_not_found


重复前缀

REP前缀在字符串指令(例如-REP MOVSB)之前设置时,会根据放置在CX寄存器中的计数器使该指令重复。REP执行该指令,将CX减1,然后检查CX是否为零。重复指令处理,知道CX为零为止。

方向标志(DF) 确定操作的方向

  • 使用CLD(清理方向标志,DF=0)使操作从左向右
  • 使用STD(设置方向标志,DF=1)使操作从右向左

REP前缀也有以下变化:

  • REP:这是无条件的重复。重复该操作,知道CX为零为止。
  • REPE或REPZ:这是有条件的重复。当零标志指示等于/零时,他将重复操作。当ZF表示不等于零或CX为零时它将停止。
  • REPNE或REPNZ:这也是有条件的重复。当零标志指示不等于/零时,它将重复操作。当ZF指示等于/零或CX减为零时,它将停止。

数组

数组

汇编程序的数据定义指令用于为变量分配存储空间。变量也可以用一些特定的值初始化。初始化值可以是十六进制,十进制或二进制形式指定。

1
2
3
MONTHS DW 12
MONTHS DW 0CH
MONTHS DW 0110B

数据定义指令也可以用于定义一维数组。

1
NUMBERS DW 34,45,56,67,75,89

上面的定义声明了一个六个单词的数组,每个单词都用数字初始化。这个数组使用了2*6=12个字节的连续存储空间。第一个数字的字符地址为NUMBERS,第二个数字的符号地址为NUMBERS+2。

定义一个大小为8的双字节数组,并将所有值初始化为零

1
2
3
4
INVENTORY DW 0,0,0,0,0,0,0,0

INVENTORY TIMES 8 DW 0

例子

以下示例通过定义一个3元素数组X来演示上述概念,该数组存储三个值:2、3、4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
section .text
global _start

section .data
global x

x:
db 2
db 4
db 3

sum:
db 0


_start:
mov eax,3
mov ebx,0
mov ecx,x

top:
add ebx,[ecx]
add ecx,1
dec eax
jnz top

done:
add ebx,'0'
mov [sum],ebx

display:
mov edx,1
mov ecx,sum
mov ebx,1
mov eax,4
int 80h

mov eax,1
mov ebc,0
int 80h

过程

过程

过程或子过程在汇编语言中非常重要,因为汇编语言程序文件往往会很大。程序由名称标识。在此名称之后,将描述执行明确定义的作业的过程主体。该过程的结束有return语句指示

1
2
3
4
proc_name
procedure body
ret

通过使用CALL指令从另一个函数调用该过程。CALL指令应将被调用过程的名称作为参数

1
CALL proc_name

被调用过程通过使用RET指令将控制权返回给调用过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
sum:
mov eax,ecx
add eax,edx
add eax,'0'
ret

section .text
global _start

section .data
msg db "The sum is:"
len_msg equ $-msg

_start:
mov ecx,'4'
sub ecx,'0'

mov edx,'5'
sub edx,'0'

call sum
mov [res],eax
mov ecx,msg
mov edx,len_msg
mov ebx,1
mov eax,4
int 80h

mov ecx,res
mov edx,1
mov ebx,1
mov eax,4
int 80h

mov ecx,Ah
mov edx,1
mov ebx,1
mov eax,4
int 80h

mov eax,1
mov ebx,0
int 80h



segment .bss
res resb 1

堆栈数据结构

堆栈是内存中类似数组的数据结构,可以在其中存储数据并从称为”堆栈顶部“的位置删除数据。需要存储的数据被“推送”到堆栈中,要检索的数据从堆栈中“弹出”。堆栈是一种LIFO数据结构,即首先存储的数据最后被检索。汇编语言为堆栈操作提供了两条指令:PUSH和POP

1
2
PUSH operand
POP address/register

堆栈段中保留的内存空间用于实现堆栈。寄存器SS和ESP(或SP)用于实现堆栈。SS:ESP寄存器指向堆栈的顶部,该顶部指向插入到堆栈中的最后一个数据项,其中SS寄存器指向堆栈段的开头,而SP(或ESP)将偏移量设置为堆栈段。

堆栈实现具有以下特征:

  • 只能将字或双字存储到堆栈中,而不是字节
  • 堆栈朝反方向增长,即朝着较低的存储器地址增长
  • 堆栈的顶部指向插入堆栈中的最后一个项目。他指向插入的最后一个字的低字节
1
2
3
4
5
6
7
8
9
10
11
PUSH AX
PUSH BX

MOV AX,VALUE1
MOV BX,VALUE2

MOV VALUE1,AX
MOV VALUE2,BX

POP BX
POP AX

以下程序显示整个ASCII字符集。主程序调用一个名为display的过程,该过程显示ASCII字符集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
section .text
global _start

_start:
call display
mov eax,1
int 80h

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'

递归

递归

递归过程是一个调用自身的过程。递归有两种:直接和间接。在直接递归中,过程调用自身,在间接递归中,第一个过程调用第二个过程,第二个过程依次调用第一个过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
section .text
global _start

_start:
mov bx,3
call proc_fact
add ax,30h
mov [fact],ax


mov edx,len
mov ecx,msg
mov ebx,1
mov eax,4
int 80h

mov edx,1
mov ecx,fact
mov ebx,1
mov eax,4
int 80h

mov eax,1
mov ebx,0
int 80h

proc_fact:
cmp bl,1
jg do_calculation
mov ax,1
ret

do_calculation:
dev bl
call proc_fact
inc bl
mul bl
ret
section .data
msg db 'Factorial 3 is:'
len equ $ - msg

section .bss
fact resb 1

编写宏是确保使用汇编语言进行模块化编程的另一种方法

  • 宏是由名称分配的指令序列,可以在程序中的任何位置使用
  • 在NASM中,宏使用%macro和%endmacro指令定义
  • 宏以%macro指令开头,以%endmacro指令结尾

宏定义的语法

1
2
3
%macro macro_name number_of_params
<macro body>
%endmacro

其中,number_of_params指定数字参数,macro_name指定宏的名称。

通过使用宏名称和必要的参数来调用宏。当需要在程序中多次使用某些指令序列时,可以将这些指令放在宏中并使用它,不必一直写指令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
%macro write_string 2
mov edx,%2
mov ecx,%1
mov ebx,1
mov eax,4
int 80h
%endmacro

section .text
global _start

_start:
write_string msg1,len1
write_string msg2,len2
write_string msg3,len3

mov eax,1
mov ebx,0
int 80h

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

文件管理

文件管理

系统将任何输入或输出数据视为字节流。有三个标准文件流

  • 标准输入(stdin)
  • 标准输出(stdout)
  • 标准错误(stderr)

文件描述符

文件描述符是分配给一个文件作为文件ID的16位整数。创新新文件或打开现有文件时,文件描述符用于访问文件。标准文件流的文件描述符-stdin,stdout和stderr分别位0,1,2

文件指针

文件指针指定以字节位单位的文件在随后的读/写操作的位置。每个文件都被视为字节序列。每个打开的文件都与一个文件指针相关联,该文件指针制定相对于文件开头的字节偏移量。打开文件时,文件指针设置为零。

文件处理系统调用

eax 名称 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_clase unsigned int - -
8 sys_create const char * int -
19 sys_lseek unsigned int off_t unsigned int

使用系统调用所需的步骤与我们之前讨论的相同

  • 将系统调用号码放入EAX寄存器中
  • 将参数保存到系统调用中的寄存器EBX,ECX等中
  • 调用相关中断(80h)
  • 结果通常在EAX寄存器中返回

创建和打开文件

要创建和打开文件,请执行以下人物

  • 将系统调用sys_create()数字8放入EAX寄存器
  • 将文件名放入EBX寄存器中
  • 将文件权限放入ECX寄存器

系统调用在EAX寄存器中放回以创建的文件描述符,如果发生错误,则错误代码在EAX寄存器中

打开一个现有文件

要打开现有文件

  • 将系统调用sys_open() 5 放入EAX寄存器中
  • 将文件名放在EBX寄存器中
  • 将文件访问模式放入ECX寄存器中
  • 将文件权限放入EDX寄存器中
  • 系统调用在EAX寄存器中返回已创建文件的文件描述符,如果发生错误,则错误代码在EAX寄存器中。

从文件读取

要从文件读取

  • 将系统调用sys_read() 3 放入EAX寄存器中
  • 将文件描述符放入EBX寄存器
  • 将指针放到ECX寄存器中的输入缓冲区
  • 将缓冲区大小(即要写入的字节数)放入EDX寄存器中
  • 系统调用返回写入EAX寄存器的实际字节,如果发生错误,则错误代码位于EAX寄存器中

关闭文件

  • 将系统调用sys_close() 编号6 放入EAX寄存器中。
  • 将文件描述符放入EBX寄存器。
  • 发生错误时,系统调用将返回EAX寄存器中的错误代码。

更新文件

  • 将系统调用sys_lseek() 编号19 放入EAX寄存器中。
  • 将文件描述符放入EBX寄存器。
  • 将偏移值放入ECX寄存器中。
  • 将偏移量的参考位置放入EDX寄存器中。

参考位置可以是:

  • 文件开头-值0

  • 当前位置-值1

  • 文件结尾-值2

    发生错误时,系统调用将返回EAX寄存器中的错误代码。

内存管理

内存管理

sys_brk()系统调用是由内核提供的,而不需要后移动他的分配内存。调用在内存中的引用程序映像后面分配内存。此系统功能可以在数据部分中设置最高可用地址。参数为最高内存地址,存储在EBX寄存器中。发生任何错误,sys_brk()返回-1或返回负错误代码本身。

以下程序使用sys_brk()系统调用分配16kb的内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
section .text
global _start

_start:
mov eax,45
xor ebx,ebx
int 80h

add eax,16384
mov ebx,eax
mov eax.45
int 80h

cmp eax,0
jl exit ;exit, if error
mov edi,eax
sub edi,4 ;pointing to the last DWORD
mov ecx,4096 ;number of DWORDs allocated
xor eax,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

exit:
mov eax,1
xor ebx,ebx
int 80h

section .data
msg db 'Allocated 16kb of memory!',10
len equ $ - msg

内存复制

堆栈指令

汇编如何写函数

堆栈传参

堆栈平衡

参考文档:
简明 x86 汇编指南
汇编基础语法 菜鸟学院
汇编在线转换
系统调用表

评论