目录
[%assign 指令](#%assign 指令)
[inc 自增指令](#inc 自增指令)
[AND 指令](#AND 指令)
[OR 指令](#OR 指令)
[XOR 指令](#XOR 指令)
[TEST 指令](#TEST 指令)
[NOT 指令](#NOT 指令)
变量
我们下面来聊一聊变量这个概念。NASM提供了各种定义指令来为变量保留存储空间。define assembler指令用于分配存储空间。它可以用于保留以及初始化一个或多个字节。
var_name dw 1234, 2345, ...
这就是一个经典的定义变量的方式。当然,dw可以换成这些内容:
指令 | 目的 | 储存空间 |
---|---|---|
DB | 定义字节 | 分配1个字节 |
DW | 定义字 | 分配2个字节 |
DD | 定义双字 | 分配4个字节 |
DQ | 定义四字 | 分配8个字节 |
DT | 定义十个字节 | 分配10个字节 |
-
字符的每个字节均以十六进制形式存储为其ASCII值。
-
每个十进制值都将自动转换为其等效的16位二进制数,并以十六进制数形式存储。这是在预处理阶段就做好的!
-
处理器使用小尾数字节顺序。
-
负数将转换为其2的补码表示形式。
-
短浮点数和长浮点数分别使用32位或64位表示。
为未初始化的数据分配存储空间
reserve指令用于为未初始化的数据保留空间。reserve指令采用单个操作数,该操作数指定要保留的空间单位数。每个define指令都有一个相关的reserve指令。
指令 | 目的 |
---|---|
RESB | 保留一个字节 |
RESW | 保留字 |
RESD | 保留双字 |
RESQ | 保留四字 |
REST | 保留十个字节 |
多重初始化
TIMES指令允许多次初始化为相同的值。例如,可以使用以下语句定义一个大小为9的标记的数组并将其初始化为零-
marks TIMES 9 DW 0
常量
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
%assign 指令
在**%assign** 指令可以用来定义数字常量像EQU指令。该指令允许重新定义。例如,您可以将常量TOTAL定义为-
%assign TOTAL 10
在代码的后面,您可以将其重新定义为-
%assign TOTAL 20
注意 - 指令区分大小写。
%define指令
在 %define 指令允许定义数值和字符串常量。该指令类似于C中的#define。例如,您可以将常量PTR定义为-
%define PTR [EBP+4]
上面的代码用[EBP + 4]替换了PTR。
该指令还允许重新定义,并且区分大小写。
算数指令
我们知道,存储的数据必须能够被运算,这才有意义。我们下面来看看一些常见的运算指令。
inc 自增指令
inc指令(increase)用于将操作数加1。它对可以在寄存器或内存中的单个操作数起作用。
; 完全等价于 t++, 在C编译器就会翻译成inc指令
inc destination
目标操作数可以是8位,16位或32位操作数。
INC EBX ; 32-bit 寄存器 自增1
INC DL ; 8-bit 寄存器 自增1
INC [count] ; 变量count 自增1
dec指令
dec(decline)指令实际上就是对操作数-1,inc指令类似!
DEC destination
ADD和SUB指令
ADD 和SUB指令用于对字节,字和双字大小的二进制数据进行简单的加/减,即分别用于添加或减去8位,16位或32位操作数。ADD和SUB指令具有以下语法
ADD/SUB destination, source
ADD / SUB指令可以发生用在
-
寄存器
->
寄存器 -
内存
->
寄存器 -
寄存器
->
内存 -
寄存器
->
常量数据 -
内存
->
常量数据
但是,像其他指令一样,使用ADD / SUB指令也无法进行存储器到存储器的操作。**ADD或SUB操作设置或清除溢出和进位标志。**也就是说,我们可以直接因此而检查EFLAGS完成一些条件动作。
imul/mul和idiv/div
mul
指令用于无符号整数的乘法,处理时,通常被乘数在 EAX
中,而乘数可以是任何寄存器或内存位置。乘法的结果会存储在 EDX:EAX
中,EAX
保存低32位结果,EDX
保存高32位结果。这种设计是为了处理可能超出32位的乘法结果。例如,若 EAX
存储值 5,EBX
存储值 3,执行 mul ebx
后,EAX
将变为 15,EDX
将为 0,表示没有高位。
imul
指令也用于乘法,但支持有符号整数。与 mul
不同,imul
可以接受一个或两个操作数。如果只有一个操作数,imul
会将该操作数与 EAX
相乘,结果仍然存储在 EDX:EAX
中。如果有两个操作数,第一个操作数通常在 EAX
中,第二个是乘数。例如,如果 EAX
中存储 -5,执行 imul eax, 3
后,EAX
将变为 -15。使用两个操作数时,如果 EAX
是 -5,EBX
是 3,执行 imul ebx
后,EAX
也会变为 -15。
情况 | 描述 |
---|---|
当两个字节相乘时 | 被乘数在AL寄存器中,而乘数在存储器或另一个寄存器中为一个字节。结果放到AX。乘积的高8位存储在AH中,低8位存储在AL中。 |
当两个单字值相乘时 | 被乘数应位于AX寄存器中,并且乘数是内存或其他寄存器中的一个字。例如,对于MUL DX之类的指令,必须将乘数存储在DX中,将被乘数存储在AX中。结果乘积是一个双字,将需要两个寄存器。高阶(最左侧)部分存储在DX中,而低阶(最右侧)部分存储在AX中。 |
当两个双字值相乘时 | 当两个双字值相乘时,被乘数应位于EAX中,并且该乘数是存储在存储器或另一个寄存器中的双字值。生成的乘积存储在EDX:EAX寄存器中,即,高32位存储在EDX寄存器中,低32位存储在EAX寄存器中。 |
在除法方面,div
用于无符号整数的除法。被除数是 EDX:EAX
,因此在执行之前,通常需要将 EDX
清零,以避免意外的高位影响。除数可以是寄存器或内存,执行后,商将存储在 EAX
中,余数存储在 EDX
中。例如,如果 EAX
是 15,EBX
是 3,执行 div ebx
后,EAX
将变为 5,而 EDX
将为 0,表示没有余数。
idiv
用于有符号整数的除法,其操作方式与 div
类似,被除数依然是 EDX:EAX
。同样,在执行之前,EDX
需要被清零,以确保结果正确。商存储在 EAX
中,余数存储在 EDX
中。如果 EAX
是 -15,而 EBX
是 3,执行 idiv ebx
后,EAX
将变为 -5,EDX
将为 0。idiv
可以处理负数,正确计算出符号。
情况 | 描述 |
---|---|
当除数为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寄存器。 |
实践:我们来写一个简单的一位数加法器
为了方便起见,我们来尝试写一个一位数的加法器试试看。
> ./result
Input your first digit:> 1
Input your second digit:> 2
the result is:> 3
>
请写一个程序,完成上面的动作。下面是一些提示:
首先,你需要至多三个变量,两个存储输入,一个存储输出。笔者出于编程效率,选择三个变量!省事!
从键盘获取到的是ASCII字符,你需要做点转换!
输出的时候,记得也需要是ASCII字符,否则你会发现你的控制台啥也没有~
写好了?答案揭晓:
; --------------------------------------------------
; Program written in 10.20 2024
; Author: Charlie chen
; Functionality: Make Add for digit!
; --------------------------------------------------
; help announce a typical string
%macro ANNOUNCE_STRING 2
%1 db %2
%1_LEN equ $ - %1
%endmacro
; fast use of common value
%define MY_STDOUT 1
%define MY_SYS_WRITE 4
%define MY_STDIN 0
%define MY_SYS_READ 3
; print string in a simple way
%macro PRINT_STRING 2
mov edx, %2
mov ecx, %1
mov ebx, MY_STDOUT
mov eax, MY_SYS_WRITE
int 0x80
%endmacro
%macro EASY_PRINT_STRING 1
PRINT_STRING %1, %1_LEN
%endmacro
; exit program
%macro EXIT 0
mov ebx, 0
mov eax, 1
int 0x80
%endmacro
; transfer from ascii to value
%macro GAIN_NUMBER 1
mov al, [%1]
sub al, '0'
mov [%1], al
%endmacro
; read ascii from console
%macro READ_SINGLE_BYTE_FROM_CONSOLE 1
mov edx, 2
mov ecx, %1
mov ebx, MY_STDIN
mov eax, MY_SYS_READ
int 0x80
%endmacro
%macro PRINT_SLASH 0
mov edx, 1
mov ecx, SLASH
mov ebx, MY_STDOUT
mov eax, MY_SYS_WRITE
int 0x80
%endmacro
section .data
ANNOUNCE_STRING TELL_INPUT_NUM_1, "Input your first digit:> "
ANNOUNCE_STRING TELL_INPUT_NUM_2, "Input your second digit:> "
ANNOUNCE_STRING RESULT_STR, "the result is:> "
SLASH db 0xA
section .bss
num_1 resb 2 ; other byte for \n
num_2 resb 2 ; other byte for \n
result resb 1
section .text
global _start
_start:
EASY_PRINT_STRING TELL_INPUT_NUM_1
READ_SINGLE_BYTE_FROM_CONSOLE num_1
EASY_PRINT_STRING TELL_INPUT_NUM_2
READ_SINGLE_BYTE_FROM_CONSOLE num_2
GAIN_NUMBER num_1
GAIN_NUMBER num_2
mov eax, [num_1]
add eax, [num_2]
add eax, '0'
mov [result], eax
EASY_PRINT_STRING RESULT_STR
PRINT_STRING result, 1
PRINT_SLASH
EXIT
NASM逻辑指令
处理器指令集提供指令AND,OR,XOR,TEST和NOT布尔逻辑,它们根据程序的需要测试,设置和清除位。
指令 | 格式 |
---|---|
AND | AND 操作数1,操作数2 |
OR | OR 操作数1,操作数2 |
XOR | XOR 操作数1,操作数2 |
TEST | TEST 操作数1,操作数2 |
NOT | NOT 操作数1 |
在所有情况下,第一个操作数都可以在寄存器或内存中。第二个操作数可以是寄存器/内存,也可以是立即数(常数)。但是,内存到内存操作是不可能的。这些指令比较或匹配操作数的位,并设置CF,OF,PF,SF和ZF标志。
AND 指令
AND 指令用于通过执行按位AND运算来支持逻辑表达式。如果两个操作数的匹配位均为1,则按位AND运算返回1,否则返回0。它可用于清除一个或多个位。例如,假设BL寄存器包含00111010。如果您需要将高阶位清除为零,则将其与0FH进行"与"运算。
AND BL, 0FH ; This sets BL to 0000 1010
让我们来看另一个例子。如果要检查给定数字是奇数还是偶数,一个简单的测试将是检查数字的最低有效位。如果为1,则数字为奇数,否则为偶数。
假设数字在AL寄存器中,我们可以写-
and al, 01H ; ANDing with 0000 0001
jz tell_is_even_number
OR 指令
OR 指令用于通过执行按位或运算来支持逻辑表达式。如果来自任何一个或两个操作数的匹配位为1,则按位OR运算符将返回1。如果两个位均为零,则返回0。
或运算可用于设置一个或多个位。例如,假设AL寄存器包含0011 1010,则需要设置四个低阶位,您可以将其与值0000 1111(即FH)进行或运算。
OR BL, 0FH ; This sets BL to 0011 1111
下面的示例演示OR指令。让我们将值5和3分别存储在AL和BL寄存器中,然后是指令,
OR AL, BL
XOR 指令
XOR 指令实现按位XOR运算。当且仅当来自操作数的位不同时,XOR运算将结果位设置为1。如果来自操作数的位相同(均为0或均为1),则将结果位清除为0。将操作数与自身进行XOR会将操作数更改为0。这用于清除寄存器。
XOR EAX, EAX
TEST 指令
TEST 指令与AND运算的工作原理相同,但与AND指令不同的是,它不会更改第一个操作数。因此,如果我们需要检查寄存器中的数字是偶数还是奇数,我们也可以使用TEST指令执行此操作,而无需更改原始数字。
TEST AL, 01H
JZ EVEN_NUMBER
NOT 指令
NOT 指令实现按位NOT运算。NOT操作将操作数中的位取反。操作数可以在寄存器中,也可以在存储器中。
Operand1: 0101 0011
After NOT -> Operand1: 1010 1100
实践:测试一个数是技术还是偶数
现在,你需要测试一个4位数是技术还是偶数:
charliechen@Charliechen:~/demo/demo9$ ./result
Input your first digit:> 1234
the result is that the number: 1234 is even
老样子,先写,我给一些提示:
这是超前的:你可以使用jmp跳转到一个标签,我来写一个简单的范例:
... section .data ANNOUNCE_STRING WORK_FLOW_STR, {"Workflow here!", 0xA} section .text global _start _RunFlow_here: EASY_PRINT_STRING WORK_FLOW_STR jmp _exit _start: jmp _RunFlow_here _exit: EXIT
charliechen@Charliechen:~/demo/demo9$ ./result Workflow here!
可以看到我们可以使用jmp指令在标签之间跳转执行,是的,这就是goto的原型!
回到开头,你可以是用test或者是and,然后使用JZ或者是JNZ来完成跳转。
写好了?答案揭晓:
; --------------------------------------------------
; Program written in 10.20 2024
; Author: Charlie chen
; Functionality: test if a number is even or odd
; --------------------------------------------------
; help announce a typical string
%macro ANNOUNCE_STRING 2
%1 db %2
%1_LEN equ $ - %1
%endmacro
; fast use of common value
%define MY_STDOUT 1
%define MY_SYS_WRITE 4
%define MY_STDIN 0
%define MY_SYS_READ 3
; print string in a simple way
%macro PRINT_STRING 2
mov edx, %2
mov ecx, %1
mov ebx, MY_STDOUT
mov eax, MY_SYS_WRITE
int 0x80
%endmacro
%macro EASY_PRINT_STRING 1
PRINT_STRING %1, %1_LEN
%endmacro
; exit program
%macro EXIT 0
mov ebx, 0
mov eax, 1
int 0x80
%endmacro
; read ascii from console
%macro READ_FROM_CONSOLE 2
mov edx, %2
mov ecx, %1
mov ebx, MY_STDIN
mov eax, MY_SYS_READ
int 0x80
%endmacro
%define NUM_LIMIT_MAX 5
section .data
ANNOUNCE_STRING TELL_INPUT_NUM, "Input your first digit:> "
ANNOUNCE_STRING RESULT_STRING, "the result is that the number: "
ANNOUNCE_STRING ODD_STRING, {" is odd!", 0xA}
ANNOUNCE_STRING EVEN_STRING, {" is even", 0xA}
section .bss
num resb NUM_LIMIT_MAX ; other byte for \n
section .text
global _start
_tell_is_even:
EASY_PRINT_STRING EVEN_STRING
jmp _to_exit
_tell_is_odd:
EASY_PRINT_STRING ODD_STRING
jmp _to_exit
_to_exit:
EXIT
_start:
EASY_PRINT_STRING TELL_INPUT_NUM
READ_FROM_CONSOLE num, NUM_LIMIT_MAX
mov eax, dword [num]
sub eax, '0'
test al, 0x1
EASY_PRINT_STRING RESULT_STRING
PRINT_STRING num, 4
jz _tell_is_odd
jmp _tell_is_even