基于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
相关推荐
Y.O.U..4 小时前
STL学习-容器适配器
开发语言·c++·学习·stl·1024程序员节
T_Y99436 小时前
selenium学习日记
学习·selenium·测试工具
糊涂君-Q7 小时前
Python小白学习教程从入门到入坑------第十九课 异常模块与包【下】(语法基础)
开发语言·python·学习·程序人生·改行学it
爱编程的小新☆7 小时前
Java篇图书管理系统
java·开发语言·学习
致奋斗的我们8 小时前
RHCE的学习(7)
linux·服务器·网络·学习·redhat·rhce·rhcsa
孤客网络科技工作室10 小时前
深入学习 Scrapy 框架:从入门到精通的全面指南
学习·scrapy
Kalika0-010 小时前
多层感知机从零开始实现
pytorch·学习
聪明的墨菲特i12 小时前
Vue组件学习 | 二、Vuex组件
前端·vue.js·学习·前端框架·1024程序员节
东林知识库12 小时前
2024年10月HarmonyOS应用开发者基础认证全新题库
学习·华为·harmonyos