基于x86_64汇编语言简单教程6: 变量,常量,与运算

目录

变量

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

多重初始化

常量

equ指令

[%assign 指令](#%assign 指令)

%define指令

算数指令

[inc 自增指令](#inc 自增指令)

dec指令

ADD和SUB指令

imul/mul和idiv/div

实践:我们来写一个简单的一位数加法器

NASM逻辑指令

[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指令

ADDSUB指令用于对字节,字和双字大小的二进制数据进行简单的加/减,即分别用于添加或减去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
>

请写一个程序,完成上面的动作。下面是一些提示:

  1. 首先,你需要至多三个变量,两个存储输入,一个存储输出。笔者出于编程效率,选择三个变量!省事!

  2. 从键盘获取到的是ASCII字符,你需要做点转换!

  3. 输出的时候,记得也需要是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

老样子,先写,我给一些提示:

  1. 这是超前的:你可以使用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的原型!

  2. 回到开头,你可以是用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
相关推荐
晴空对晚照3 小时前
[动手学习深度学习]26. 网络中的网络 NiN
网络·深度学习·学习
gzgenius4 小时前
独立部署DeepSeek 大语言模型(如 DeepSeek Coder、DeepSeek LLM)可以采用什么框架?
人工智能·学习·架构·deepseek
达帮主4 小时前
16. C语言二级指针
c语言·开发语言·汇编·青少年编程
charlie1145141918 小时前
单片机开发资源分析的实战——以STM32F103C8T6为例子的单片机资源分析
stm32·单片机·嵌入式硬件·学习·教程
努力往上爬de蜗牛8 小时前
react学习1.搭建react环境
javascript·学习·react.js
sealaugh328 小时前
aws(学习笔记第三十三课) 深入使用cdk 练习aws athena
笔记·学习·aws
Liii4039 小时前
Java学习——数据库查询操作
java·数据库·学习
TO_WebNow9 小时前
Python学习- 数据结构类型
开发语言·python·学习
怪我冷i10 小时前
LogicFlow介绍
人工智能·学习·大模型