Linux汇编入门大杂烩上!

鼠鼠我啊,曾今年少无知,以为计算机系统要自己手写汇编代码,于是开启了汇编入门的道路,But!!!等到我有个地方问学长的时候,发现!!!!根本不需要!!!!

QAQ但是鼠鼠想着“来都来了”(bushi),硬着头皮学完了,这部分没有写数组,字符串,递归,宏和文件内存管理,一方面是我看着不是很难(很难得我们学校估计也不考,另一方面是(主要方面学校不教 

ok废话少说,正文开始,本文基于众多大佬的博客总结出的,但依然存在一些不足,每个知识点都有配套的程序。

本文是intel格式代码,一方面是intel格式方便入门,代码简单易懂,另一方面,AT&T格式与intel格式的区别会专门再写一个,来做辅助笔记。

注意本文较长不建议一次性食用


寄存器组

作用

处理器操作主要涉及处理数据。 该数据可以存储在存储器中并从存储器中访问。

然而,从内存中读取数据和将数据存储到内存中会降低处理器的速度,因为它涉及通过控制总线发送数据请求并进入内存存储单元并通过同一通道获取数据的复杂过程。

为了加速处理器操作,处理器包含一些内部存储器存储位置,称为寄存器。寄存器存储数据元素以进行处理,而无需访问内存。 处理器芯片中内置了有限数量的寄存器。

寄存器的分类

  • 通用寄存器
  • 控制寄存器
  • 段寄存器
通用寄存器的组成

1. 数据寄存器
2. 指针寄存器
3. 索引寄存器

数据寄存器

使用方式
四个 32 位数据寄存器用于算术、逻辑和其他运算。 这些 32 位寄存器可以通过三种方式使用

  • 作为完整的32位数据寄存器:EAX、EBX、ECX、EDX
  • 32 位寄存器的下半部分可用作四个 16 位数据寄存器:AX、BX、CX 和 DX
  • 上述4个16位寄存器的下半部分和上半部分可以用作8个8位数据寄存器:AH、AL、BH、BL、CH、CL、DH和DL

不同部分功能

  • AX 为主累加器:它用于输入/输出和大多数算术指令。 例如,在乘法运算中,根据操作数的大小将一个操作数存储在EAX或AX或AL寄存器中
  • BX 被称为基址寄存器:因为它可用于索引寻址
  • CX 被称为计数寄存器:与 ECX 一样,CX 寄存器存储迭代操作中的循环计数
  • DX被称为数据寄存器: 它还用于输入/输出操作。 它还与 AX 寄存器和 DX 一起使用,用于涉及大值的乘法和除法运算
指针寄存器

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

  • 指令指针(IP): 16位IP寄存器存储下一条要执行的指令的偏移地址。 IP 与 CS 寄存器(如 CS:IP)关联给出了代码段中当前指令的完整地址。
  • 堆栈指针(SP): 16 位 SP 寄存器提供程序堆栈内的偏移值。 SP 与 SS 寄存器 (SS:SP) 相关,指的是程序堆栈中数据或地址的当前位置
  • 基址指针(BP): 16 位 BP 寄存器主要帮助引用传递给子程序的参数变量。 SS寄存器中的地址与BP中的偏移量相结合,得到参数的位置。 BP还可以与DI、SI组合作为基址寄存器进行特殊寻址。

索引寄存器

组成

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

  • 来源索引 (SI) +minus; 它用作字符串操作的源索引
  • 目的地索引 (DI)− 它用作字符串操作的目标索引


段寄存器:

组成

段是程序中定义的用于包含数据、代码和堆栈的特定区域。

主要分为三个部分

  • 代码段 : 它包含所有要执行的指令。 16位代码段寄存器或CS寄存器存储代码段的起始地址
  • 数据段 : 它包含数据、常量和工作区。 16位数据段寄存器或DS寄存器存储数据段的起始地址
  • 堆栈段 : 它包含过程或子例程的数据和返回地址。 它被实现为"堆栈"数据结构。 堆栈段寄存器或SS寄存器存储堆栈的起始地址
  • 除了 DS、CS 和 SS 寄存器外,还有其他额外段寄存器 - ES(额外段)、FS 和 GS,它们提供额外的段来存储数据

作用

  • 在汇编编程中,程序需要访问内存位置。 段内的所有内存位置都相对于段的起始地址。 段从可被 16 或十六进制 10 整除的地址开始。因此,所有此类内存地址中最右边的十六进制数字都是 0,通常不存储在段寄存器中
  • 段寄存器存储段的起始地址。 为了获得段内数据或指令的准确位置,需要偏移值(或位移)。 为了引用段中的任何内存位置,处理器将段寄存器中的段地址与该位置的偏移值组合起来

汇编程序

汇编程序可以分成三个部分:data部分,bss部分,text部分

data部分

data部分用于声明初始化数据或常量。该数据在运行是不会改变。可以在本节生命各种常量值,文件名或缓冲区大小。

语法:

section.data
bss部分

bss部分用于声明变量。

语法:

section.bss
text部分

text部分用于保存实际代码。此部分必须以声明global_start开头,他告诉内核程序执行开始的位置。语法:

section.text
    global _start
_start:
注释部分

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

; This program displays a message on screen
---------------------------------------------------
add eax, ebx     ; adds ebx to eax

汇编基本语句

汇编语言程序主要有三种类型的语句组成
可执行指令或说明
    可执行指令或简单的指令告诉处理器要做什么。 每条指令由一个操作码(opcode)组成。 每条可执行指令生成一条机器语言指令。
汇编器指令或伪操作
    汇编程序指令伪操作告诉汇编程序有关汇编过程的各个方面。 它们是不可执行的,并且不会生成机器语言指令。

    宏基本上是一种文本替换机制。

 汇编语言语句语法

语句格式:

[label]   mnemonic   [operands]   [;comment]
[标识符]  指令助记符  [操作符]   [;注释]

方括号中的字段是可选的。基本指令由两部分组成:

  1. 要执行的指令名称(或助记符)
  2. 命令的操作时或参数。

示例:

INC COUNT        ; Increment the memory variable COUNTMOV 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

指令中的各项要用分隔符分开:
    标识符是标号时后面要有冒号作为分隔开。
    为增加查询可读性,对指令做解释时,要将指令和解释内容用分号分隔开 。
    操作数之间用逗号分开,其他用空格分开。

标识符(Label)
  1. 作用:用来标识段名、子程序名、宏指令名、标号、变量名和常量名等
  2. 可用符号包括数字、字母和特殊符号“?”、“$”、“@”、“_”;
  3. 数字不能作为名字(变量或标号)的第一个符号;
  4. 名字长度不能超过31个字符;
  5. 汇编语言中有特定含义的保留字,如操作码、寄存器名等,不能作为名字使用;
  6. 汇编语言不区分字母的大小写。
  7. 除注释外,程序中不能出现中文和中文标点符号
指令助记符(Mnemonic)

即指令操作码,用来指定操作的性质或功能,如“MOV”表示传送指令,“ADD”表示加法指令等,”SUB”表示减法指令。

操作数(Operator)

用来指定参与操作的数据,可以是直接参与操作的数据,也可以是数据所在的地址。

注释(;Comment)

程序的解释说明部分,由分号开始,用来对程序,指令的功能加以说明。


编写一个NASM程序

hello world程序

新建文件hello.asm

写入汇编语言代码,hello程序包括text部分data部分

section .text
    global _start   ;必须声明

_start:   ;作为汇编程序的一个标号,定义了程序的入口,既程序从start:处开始执行
    mov edx,len ;信息长度
    mov ecx,msg ;要输出的信息
    mov ebx,1 ;第一个为文件描述符,1为标准输出
    mov eax,4 ;系统调用号4,表示write
    int 0x80 ;调用内核

    mov eax,1 ;系统调用号1,表示exit
    int 0x80 ;调用内核

section .data
    msg db 'Hello,World!',0Ah ;要打印的字符串变量
len equ $ - msg ;字符串的长度

输入下面命令,将源文件`hello.asm`,编译,会生成`hello.o`文件:

nasm -f elf Hello.asm

然后输入下面命令,把`hello.o`文件进行链接,生成可执行文件`hello`:

ld -m elf_i386 Hello.o -o  Hello

然后输入下面命令,执行`hello`可执行文件,成功输出:

./hello

结果

相关知识 

汇编发起Linux系统调用
发起系统调用的步骤

  1. 给ax(eax)寄存器赋值系统调用号
  2. 把参数赋值到其他寄存器,如bx(ebx),cx(ecx)等
  3. 使用`int 0x80`发起中断,`0x80`该中断号表示系统调用
  4. 如果有返回值,结果会保存到ax(eax)

系统调用号对应表

给ax/eax赋值不同的系统调用号,发起的是不同的系统调用,下面是不同的系统调用号对应的不同的系统调用:


可执行指令代码 

格式:指令    源操作数,目的操作数

示例:movl    $8(立即数)  , %eax           (把数字8移到eax这个数据寄存器中)

操作数        注意CPU计算总是仅从寄存器直接存/取数据

  1. 立即数(immediate)见立即寻址部分
  2. 寄存器(register)
  3. 存储器(memory)

 汇编程序示例(AT&T格式)

.section .text
 .global _start
_start:
    movl $4, %eax
    movl $1, %ebx
    movl $5, %ecx
    movl $13, %edx
    movl $1, %eax
    movl $0, %ebx
    int $0x80 

内存段

 程序示例

我们已经讨论了汇编程序的三个部分。 这些部分也代表各种内存段。
有趣的是如果将section替换为segment,会得到相同的结果。

segment .text
    global _start

_start:
    mov edx,len
    mov ecx,msg
    mov ebx,1
    mov eax,4
    int 0x80

    mov eax,1
    int 0x80

segment .data
msg db 'Hello,Segment!',0xa
len equ $ -msg

当上面的代码被编译并执行时,会产生以下结果:
`Hello,Segment!`

 相关知识 

分段内存模型系统内存划分为由位于段寄存器中的指针引用的独立段组

每个段用于包含特定类型的数据。其中一个段用于包含指令代码(代码段),另一段用于存储数据元素(数据段),还有一个段用于保存程序堆栈(堆栈段)。

根据讨论我们将各种内存段指定:

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

系统调用 

系统调用是用户空间(User Space)内核空间(Kernel Space)之间接口的API。
    Kernal Space是Linux内核的运行空间,User Space是用户程序的运行空间。为了安全,两者隔离,即使用户程序崩溃了,内核也不受影响![[Pasted image 20240214123130.png]]
    Kernal Space可以执行任意命令调用系统的一切资源;User Space只能执行简单的运算,不能直接调用系统资源,必须通过系统接口(system call)才能向内核发出指令
我们已经使用过系统调用: sys_write(`mov eax,4`)和sys_exit(`mov eax,1`),分别用于写入屏幕和退出程序。

 Linux系统调用

汇编程序中使用Linux系统调用的步骤:

  1. 将系统调用号放入EAX寄存器中
  2. 将系统调用的参数存储在寄存器EBX、ECX等中
  3. 调用相关终端(80h`0x80`)
  4. 结果通常返回到EAX寄存器中

有六个寄存器存储所使用的系统调用的参数:EBX、ECX、EDX、ESI、EDI和EBP。

这些寄存器采用连续的参数,从EBX寄存器开始。如果参数超过六个,则第一个参数的内存位置存储在EBX寄存器中。

所有系统调用及其编号(在调用 int 80h 之前放入 EAX 中的值)都列在 /usr/include/asm/unistd.h 中。系统的一些调用见下表:

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

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

section .bss
    num resb 5

section .text
    global _start

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

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

    mov eax,4
    mov ebx,1
    mov ecx,dispMsg
    mov edx,lenDisMsg
    int 80h    mov eax,4
    mov ebx,1
    mov ecx,num
    mov edx,5
    int 80h    mov eax,1
    mov ebx,0
    int 80h

程序最大输入长度为5的数字


寻址模式 

大部分汇编语言指令需要处理操作数(如mov,add)。操作数地址提供了存储要处理的数据的位置。一些指令不需要操作数,而另一些指令可能需要一个,两个或三个操作数。

当一条指令需要两个操作数时,第一个操作数通常是目标,其中包含寄存器或内存位置中的数据,第二个操作数是源。源包含要传送的数据(立即寻址)或数据的地址(在寄存器或内存中)。一般情况下,操作后源数据不会发生改变。

三种基本寻址模式:

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

寄存器寻址

在此寻址模式下,寄存器包含操作数。根据指令的不同,寄存器可能是第一个操作数,第二操作数或两者均是。示例:

mov dx,tax_rate ;寄存器是第一个操作数
mov count,cx ;寄存器是第二个操作数
mov eax,ebx ;寄存器是第三个操作数

由于在寄存器之间处理数据不涉及内存,所以提供了最快的数据处理速度。

立即寻址 

立即操作数具有常量值或表达式。

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

示例:

byte_value db 150 ;一字节值被定义为150
word_value dw 300 ;一字值被定义为300
add byte_value 65 ;立即操作,一字节值加上65
mov ax,45H ;立即常量,45h被移动到ax寄存器
直接内存寻址

当操作数以内存寻址方式指定时,需要直接访问主存,通常是数据段(见内存段部分)。

这种寻址方式数据处理速度较慢。为了定位内存中数据的准确位置,我们需要段起始地址(通常在DS寄存器中找到)和偏移值,该偏移值也被称为有效地址

在直接寻址模式下,偏移值直接指定为指令的一部分,通常由变量名指示。

 汇编器计算偏移值并维护一个符号表,其中存储了程序中使用的所有变量的偏移值。

在直接内存寻址中,一个操作数引用内存位置,另一个操作数引用寄存器
示例:

add byte_value,dl ;将寄存器添加到内存位置
moc bx,word_value ;内存中的操作数被加到寄存器中
直接偏移寻址

此寻址模式使用算术运算符来修改地址。例如,查看以下定义数据表的定义

byte_table db 14,15,22,45 ;字节表
word_table db 134,345,564,123;字表

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

mov cl,byte_table[2] ;选择byte_table的第三个元素移动到内存位置
mov cl,byte_table + 2 ;选择byte_table的第三个元素移动到内存位置
mov cx,word_table[3] ;选择word_table的第四个元素移动到内存位置
mov cx,word_table + 3 ;选择word_table的第四个元素移动到内存位置
间接内存寻址

此寻址模式利用计算机的段:偏移寻址能力

通常,基址寄存器 EBX、EBP(或 BX、BP)和索引寄存器(DI、SI)被编码在方括号内用于存储器引用,用于此目的。

间接寻址通常用于包含多个元素的变量,例如数组。数组的起始地址存储在EBX寄存器中。
以下代码片显示了如何访问变量不同的元素。

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


MOV指令

功能:用于将数据从一个存储空间移动到另一个存储空间。
MOV指令需要两个操作数。

mov destination,source

MOV指令有五种形式:

mov register,register
mov register,immediate
mov memory,immediate
mov register,memory
mov memory,regitser

注意

  • MOV指令运算中的两个操作数大小应该相同
  • 源操作数的值不变

MOV指令有时会引起歧义。示例:

mov ebx,[my_table] ;my_table在ebx中的有效地址
mov [ebx],110 ;my_table[0]=110


不清楚您是否要移动数字 110 的字节等效项或字等效项。在这种情况下,明智的做法是使用类型说明符
字节等效
下表显示了一些常见的类型说明符:

示例

以下程序说明了上面讨论的一些概念。 它将名称"Zara Ali"存储在内存的数据部分中,然后以编程方式将其值更改为另一个名称"Nuha Ali"并显示这两个名称

section .data
    name db 'Zara Ali'

section .text
    global _start
_start:
    mov eax,4
    mov ebx,1
    mov ecx,name
    mov edx,8
    int 80h

    mov [name],dword 'Nuha'

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

    mov eax,1
    int 80h

结果 


nasm伪指令:变量

NASM提供了各种**define**指令来为变量保留存储空间。定义汇编指令用于**分配存储空间**,可用于保留和初始化一个或多个字节。

伪指令不是 x86/x64 机器的真实指令,伪指令是用于给编译器指示如何进行编译。

nasm定义的七种数据size

oword 可以对应 Microsoft MASM 的 xmmword 类型,yword 对应 Microsoft MASM 的 ymmword 类型。

tword, oword 以及 yword 使用在 非整型 数据,使用在 float 和 SSE 型数据。

定义初始化数据:db家族

语法:

[variable-name]    define-directive    initial-value   [,initial-value]...
[变量名称]          定义指令             初始值           [,初始值]...

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


示例:

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 位表示。

nasm 定义了用于初始化上面 7 种 size 的 db 家族,它们用于定义初化常量值。
正如前面所说的:

  • dt , do , dy 不接受整型数值常量,它们被使用在定义 float 或 SSE 数据常量。
  • dt 可以定义 extended-precision float 数据
  • do 可以定义 quad-precision float
  • dy 可定义 ymm 数据
  • dq 可以定义 double-precision float 数据
  • dd 可以定义 single-precision float 数据

示例:

db 0x55 ; just the byte 0x55 
db 0x55,0x56,0x57 ; three bytes in succession 
db 'a',0x55 ; character constants are OK 
db 'hello',13,10,'$' ; so are string constants 
dw 0x1234 ; 0x34 0x12 
dw 'a' ; 0x61 0x00 (it's just a number) 
dw 'ab' ; 0x61 0x62 (character constant) 
dw 'abc' ; 0x61 0x62 0x63 0x00 (string) 
dd 0x12345678 ; 0x78 0x56 0x34 0x12 
dd 1.234567e20 ; floating-point constant 
dq 0x123456789abcdef0 ; eight byte constant 
dq 1.234567e20 ; double-precision float 
dt 1.234567e20 ; extended-precision float
定义非初始化数据:resb家族 

程序中使用的非初始化数据通常放在 bss section 里,bss代表uninitialized storage space
nasm使用了resb(reserve byte)家族来定义非初始化数据

保留指令用于为未初始化的数据保留空间。 

保留指令采用单个操作数来指定要保留的空间单位数。 每个定义指令都有一个相关的保留指令。

多重定义 

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

choice db 'Y' ;ASCII of y = 79H
number dw 12345
number dd 123456789

汇编器为多个变量分配连续的内存

多次初始化 

times指令允许对同一只进行多次初始化。

例如,可以使用以下语句定义一个名为marks、大小为 9 的数组并将其初始化为零

marks times 9 dw 0

times指令在定义数组和表示很有用。


nasm伪指令:常量 

NASM 提供了几个定义常量的指令。 我们已经在前面的章节中使用过 EQU 指令。 我们将特别讨论三个指令:

  • equ
  • %assign
  • %define
equ指令 

equ指令用于定义常量。equ 用来为标识符定义一个整型常量,它的作用类似 C 语言中的 #define
语法:

constant_name equ experssion


例如:

total_students equ 50

然后就可以在代码中使用这个常量值。如

mov ecx,total_students
cmp eax,total_students

equ语句的操作数可以是表达式

length equ 20
width equ 10
area equ width*length ;将 AREA 定义为 200
len equ $ -string

以下示例说明了 EQU 指令的使用:

;0xa和0xd分别是ASCII码中的换行符和回车符。在NASM汇编中,它们通常用于处理文本文件和字符串。
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!',0xa,0xd
    len3 equ $ -msg3

SYS_EXIT  equ 1
SYS_WRITE equ 4
STDIN     equ 0
STDOUT    equ 1

section .text 
    global _start
_start:
    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 80h
    mov eax,SYS_WRITE
    mov ebx,STDOUT
    mov ecx,msg3
    mov edx,len3
    int 80h
    mov eax,SYS_EXIT
    int 0x80

结果:

%assign指令 

%assign指令用于定义数字常量,允许重新定义,指令区分大小写

例如,可以将total定义为

assign total 10

在代码的后面,可以重新定义为

%assign total 20
%define指令 

%define指令允许定义数字和字符串常量。 该指令类似于C 中的#define

例如,您可以将常量 PTR 定义为:

%define ptr [ebp+4]

以上代码将PTR替换为[EBP+4]。该指令还允许重新定义并且区分大小写。


算术指令 

INC指令 

功能:INC指令用于将操作数加一,再将结果送回到该操作数。

INC指令将影响SF,AF,ZF,PF,OF标志位,但是不影响CF标志位。

INC指令的操作数的类型可以是通用寄存器或存储单元,但不可以是段寄存器,字或字节操作均可。
对于存储单元需要用byte,ptr或word ptr说明是字节还是字操作
语法:

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指令用于将操作数减一,其他与inc指令相同。

语法:

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 destination , source

ADD/SUB 指令可以发生在 

  • Register to register
  • Memory to register
  • Register to memory
  • Register to constant data
  • Memory to constant data

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

示例:

以下示例将向用户询问两个数字,分别将数字存储在 EAX 和 EBX 寄存器中,将值相加,将结果存储在内存位置"res"中,最后显示结果。

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 point
   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 0x80exit:    
   
   mov eax, SYS_EXIT   
   xor ebx, ebx 
   int 0x80
 

结果
带有硬编码变量(将可变变量用一个固定值来代替的方法)的程序

section    .text
   global _start    ;must be declared for using gcc
    
_start:             ;tell linker entry point
   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
MUL/IMUL 指令 

有两条指令用于将二进制数据相乘。 MUL(乘法)指令处理无符号数据,IMUL(整数乘法)指令处理有符号数据。 这两条指令都会影响进位和溢出标志。

语法:

mul/imul multiplier

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

示例:

MOV AL, 10
MOV DL, 25
MUL DL
...
MOV DL, 0FFH    ; DL= -1
MOV AL, 0BEH    ; AL = -66
IMUL DL

以下示例将 3 与 2 相乘,并显示结果 

section    .text
   global _start    ;must be declared for using gcc
    
_start:             ;tell linker entry point

   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

结果

DIV/IDIV指令 

除法运算生成两个元素:一个和一个余数

在乘法的情况下,不会发生溢出,因为使用双倍长度寄存器来保存乘积。 然而,在除法的情况下,可能会发生溢出。 如果发生溢出,处理器会产生中断。

DIV(除法)指令用于无符号数据,IDIV(整数除法)指令用于有符号数据。

语法

div/idiv   divisor

根据以上表格可以总结出,div指令,当操作数是8位二进制,被除数就一定是16位,操作数是16位则被除数一定32位(两个16进制寄存器连起来)。也就是说被除数永远大于除数。

这两个指令都可以使用 8 位、16 位或 32 位操作数。 该操作影响所有六个状态标志。 以下部分解释了具有不同操作数大小的除法的三种情况

示例:

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

section    .text
   global _start    ;must be declared for using gcc
    
_start:             ;tell linker entry point
   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

逻辑指令 

处理器指令集提供AND、OR、XOR、TEST和NOT布尔逻辑指令,根据程序的需要测试、设置和清除位。


所有情况下的第一个操作数可以位于寄存器或内存中。 第二个操作数可以位于寄存器/内存中或立即(常量)值。 然而,内存到内存的操作是不可能的。 这些指令比较或匹配操作数的位并设置 CF、OF、PF、SF 和 ZF 标志。

and指令 

AND 指令用于通过执行按位 AND 运算来支持逻辑表达式。 如果两个操作数的匹配位均为 1,则按位 与运算返回 1,否则返回 0。

AND 运算可用于清除一位或多位。 例如,假设BL寄存器包含0011 1010。如果需要将高位清零,则与0FH进行AND运算。

and   BL,   0FH   ; This sets BL to 0000 1010

如果您想检查给定的数字是奇数还是偶数,一个简单的测试就是检查该数字的最低有效位。 如果为 1,则该数字为奇数,否则该数字为偶数。

假设数字在AL寄存器中,我们可以写

AND    AL, 01H     ; ANDing with 0000 0001

类似地,要清除整个寄存器,您可以将其与 00H 进行 AND 运算。

示例:

section .text
   global _start            ;must be declared for using gcc
    
_start:                     ;tell linker entry point
   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
or指令 

OR 指令用于通过执行按位或运算来支持逻辑表达式。 如果任一操作数或两个操作数的匹配位均为 1,则按位 OR 运算符返回 1。 如果两个位都为零,则返回 0。
OR 运算可用于设置一位或多位。 例如,假设AL寄存器包含0011 1010,则需要设置低四位,可以将其与值0000 1111(即FH)进行或运算。


示例:下面的示例演示了 OR 指令。

让我们将值 5 和 3 分别存储在 AL 和 BL 寄存器中,然后指令

or al,bl

应将 7 存储在 AL 寄存器中

section .text
   global _start            ;must be declared for using gcc
    
_start:                     ;tell linker entry point
   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
xor指令 

XOR 异或指令实现按位异或运算。 当且仅当操作数中的位不同时,XOR 运算将结果位设置为 1。 如果操作数的位相同(均为 0 或均为 1),则结果位清除为 0。
对操作数与其自身进行异或会将操作数更改为0。 这用于清除寄存器。例如

xor   EAX, EAX
test指令 

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

not指令 

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


条件执行 

条件执行是通过多个循环和分支指令完成的。 这些指令可以改变程序中的控制流程。 在两种情况下观察到条件执行:
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   label

示例

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
 条件跳转

如果条件跳转中满足某个指定条件,则控制流转移到目标指令。 根据条件和数据,有许多条件跳转指令。以下是用于算术运算的有符号数据上使用的条件跳转指令:


以下是用于逻辑运算的无符号数据的条件跳转指令
以下条件跳转指令有特殊用途并检查标志的值| 说明 | 描述 | 已测试标志 |

J<condition>指令集的语法 示例:

CMP    AL, BL
JE    EQUAL
CMP    AL, BH
JE    EQUAL
CMP    AL, CL
JE    EQUAL
NON_EQUAL: ...
EQUAL: ...

以下程序显示三个变量中最大的一个。 变量是两位数变量。 三个变量 num1、num2 和 num3 的值分别为 47、22 和 31

section    .text
   global _start         ;must be declared for using gcc

_start:                     ;tell linker entry point
   mov   ecx, [num1]
   cmp   ecx, [num2]
   jg    check_third_num
   mov   ecx, [num2]
   
    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

循环

JMP指令可用于实现循环。

例如,以下代码片段可用于执行循环体 10 次。

MOV    CL, 10
L1:
<LOOP-BODY>
DEC    CL
JNZ    L1

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

loop    label

cpu执行loop指令的时候,要进行两步操作:

  1. (cx)=(cx)-1     
  2. 判断cx中的值,不为零则转至标号处执行,如果为零,则向下执行

其中,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 point
   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

数字

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

下面的代码展示了这一点

section    .text
   global _start        ;must be declared for using gcc
    
_start:                    ;tell linker entry point
   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:

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

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

ASCII形式

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

例如,十进制值 1,2,3,4 存储为31h,32h,33h,34h(31H 为 1 的 ASCII 值,32H 为 2 的 ASCII 值,依此类推)
有四个指令用于处理 ASCII 表示的数字:

  1. AAA− 加法后ASCII调整
  2. AAS− 减法后调整 ASCII
  3. AAM− 乘法后的 ASCII 调整
  4. AAD − 除法前的 ASCII 调整

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

AAA指令示例 

aaa指令本质上是将相加后的结果拆成两个部分(拆成非压缩BCD码(四位二进制)的形式)
ah = 0,al = 06h,bl = 09h,执行指令

add al,bl

得ah = 01h,al = 05h
aaa的作用就是将相加后al中的结果15拆分成两部分  (将0fh十进制为15的数字拆分成一个1和一个5,1放在ax的高位ah中,5放在ax的低位al中)

汇编代码示例 
mov        ah,0                ;ax = 0038h
mov       al,'8'                ;'8'的ascii码值十进制为56,十六进制为0038h,所以ax = 0038h
add        al,'2'                ;'2'的ascii码值十进制为50,十六进制为0032h,所以ax = 0032h,相加后结果为006Ah
aaa                            ;执行aaa指令后ax = 0100h,本质上是将al低位的a进行化简,a的值为10,转化为BCD码值为01 00h
or            ax,3030h        ;相当于or        ah,30h与or    al,30h操作
;将高位的ah与低位的al分别进行ascii码值的转换

AAS指令示例 

AAS 减法的ASCII调整指令(ASCII Adjust for Subtraction)  
若AL寄存器的低4位>9或AF=1,则:  

  1. AL=AL-6,AF置1;  
  2. 将AL寄存器高4位清零;  
  3. AH=AH-1,CF置1。  

否则,不需要调整。

汇编代码示例
section    .text
   global _start        ;must be declared for using gcc
    
_start:                    ;tell linker entry point
   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 表示法有两种类型:

  1. 解压缩的 BCD 表示
  2. 压缩 BCD 表示

 解压缩的BCD表示

在解压缩的 BCD 表示形式中,每个字节存储十进制数字的二进制等价物。

例如,数字 1234 存储为01h    02h    03h    04h

有两条指令用于处理这些数字:

  1. AAM乘法后的 ASCII 调整
  2. AAD除法前的 ASCII 调整

四个 ASCII 调整指令 AAA、AAS、AAM 和 AAD 也可以与解包 BCD 表示形式一起使用。

压缩的BCD表示 

在压缩 BCD 表示中,每个数字使用四位存储。 两个十进制数字被打包成一个字节。

例如,数字 1234 存储为`12    34H`
有两条指令用于处理这些数字:

  1. DAA加法后小数调整
  2. DAS减法后小数调整

压缩 BCD 表示形式不支持乘法和除法。
以下程序将两个 5 位十进制数相加并显示总和。 它使用了上面的概念示例:

section    .text
   global _start        ;must be declared for using gcc

_start:                    ;tell linker entry point

   mov     esi, 4       ;pointing 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 kernelsection    .data
msg db 'The Sum is:',0xa    
len equ $ - msg            
num1 db '12345'
num2 db '23456'
sum db '     '

过程

过程或子例程在汇编语言中非常重要,因为汇编语言程序的大小往往很大。 过程由名称来标识。 遵循该名称,描述了执行明确定义的工作的过程主体。 过程的结束由返回语句指示。
 定义过程的语法:

 proc_name:
   procedure body
   ...
   ret

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

call proc_name

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

ret

示例

我们编写一个名为sum的非常简单的过程,它将 ECX 和 EDX 寄存器中存储的变量相加,并将总和返回到 EAX 寄存器中

section    .text
   global _start        ;must be declared for using gcc
    
_start:                    ;tell linker entry point
   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

堆栈数据结构 

堆栈是内存中类似数组的数据结构,可以在其中存储数据,也可以从称为堆栈"顶部"的位置删除数据。 需要存储的数据被"推入"堆栈,而要检索的数据则从堆栈"弹出"。 堆栈是一种后进先出的数据结构,即先存储的数据最后取出。
汇编语言提供了两种堆栈操作指令:PUSH 和 POP。 这些指令的语法如下

push    operand
pop    address/register

堆栈段中保留的内存空间用于实现堆栈。 寄存器SS和ESP(或SP)用于实现堆栈。
栈:先进后出  
栈顶指针:保存在%esp寄存器中

栈顶,指向最后插入栈中的数据项,由 SS:ESP 寄存器指向,其中 SS 寄存器指向栈段的开头,SP(或 ESP)给出栈中的偏移量 部分。
堆栈实现具有以下特点:

  • 只有wordsdoublewords可以保存到堆栈中,而不是字节。
  • 堆栈向相反方向增长,即向低内存地址增长
  • 栈顶指向栈中最后插入的一项; 它指向插入的最后一个单词的低字节。

正如我们讨论的那样,在将寄存器的值用于某些用途之前将其存储在堆栈中; 可以通过以下方式完成

; 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    BX
POP    AX

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

section    .text
   global _start        ;must be declared for using gcc
    
_start:                    ;tell linker entry point
   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'

好!结束了,感觉命都没了!再也不写这么长的了,呕.......
 

  • 29
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值