汇编语言学习(7)完结篇

2023-12-23 20:20

本文主要是介绍汇编语言学习(7)完结篇,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

更好的阅读体验,请点击 YinKai 's Blog。

过程

​ 过程或子例程在汇编语言中非常重要,它们有助于组织和模块化代码,提高代码的可读性和可维护性。

​ 过程通常以一系列的指令组成,用于完成特定的任务。这些过程可以有参数、局部变量,也可以返回一个值。

​ 过程定义的语法如下:

proc_name:procedure body...ret

​ 使用 CALL 指令从另一个函数调用该过程,被调用过程的名称应作为 CALL 指令 的参数,如下:

CALL proc_name

​ 示例:下面的程序将 ECX 和 EDX 寄存器中存储的变量相加,并将结果总和返回到 EAX 寄存器中,并显示

section .text
global _start   ; 必须为了使用链接器 (gcc)_start:mov ecx, '4'  ; 将字符 '4' 的 ASCII 值加载到 ECX 寄存器sub ecx, '0'   ; 将 '0' 的 ASCII 值从 ECX 中减去,以获得数字 4mov edx, '5'  ; 将字符 '5' 的 ASCII 值加载到 EDX 寄存器sub edx, '0'   ; 将 '0' 的 ASCII 值从 EDX 中减去,以获得数字 5call sum      ; 调用 sum 过程,将结果存储在 EAX 中mov [res], eax ; 将结果存储在 res 变量中; 输出 "The sum is:" 到标准输出mov ecx, msgmov edx, lenmov ebx, 1       ; 文件描述符 (stdout)mov eax, 4       ; 系统调用号 (sys_write)int 0x80         ; 调用内核; 输出结果到标准输出mov ecx, resmov edx, 1mov ebx, 1       ; 文件描述符 (stdout)mov eax, 4       ; 系统调用号 (sys_write)int 0x80         ; 调用内核; 退出程序mov eax, 1       ; 系统调用号 (sys_exit)int 0x80         ; 调用内核sum:mov eax, ecx    ; 将 ECX 中的值移动到 EAXadd eax, edx    ; 将 EDX 中的值加到 EAXadd eax, '0'    ; 将 '0' 的 ASCII 值加到 EAX,以将数字转换回字符ret             ; 返回section .data
msg db "The sum is:", 0xA,0xD  ; 输出消息
len equ $- msg   section .bss
res resb 1      ; 用于存储结果的变量,初始化为 1 个字节

​ 编译运行后输出的结果如下:

The sum is:
9
堆栈数据结构

​ 堆栈是一种内存中的数据结构,类似于数组,用于存储和检索数据。数据可以通过"推入"到堆栈中进行存储,而通过"弹出"从堆栈中取出。堆栈采用后进先出(Last In First Out,LIFO)的原则,即最先存储的数据最后取出。

在汇编语言中,我们可以使用两种堆栈操作指令来进行操作:PUSH 和 POP。这些指令的语法如下:

  • PUSH operand: 将操作数推入堆栈。
  • POP address/register: 从堆栈中弹出数据并存储到指定地址或寄存器中。

堆栈的实现依赖于堆栈段中预留的内存空间。寄存器 SS 和 ESP(或 SP)用于管理堆栈。栈顶指针(ESP)指向最后插入到堆栈中的数据项,其中 SS 寄存器指向堆栈段的开头。堆栈的增长方向是向低内存地址增加,而栈顶指向最后插入的一项,指向插入的最后一个字的低字节。

​ 堆栈的一些特点包括:

  • 只有字(words)或双字(doublewords)可以保存到堆栈中,而不是字节。
  • 堆栈向相反方向增长,即向低内存地址增加。
  • 栈顶指针指向栈中最后插入的一项,它指向插入的最后一个字的低字节。

​ 在使用寄存器的值之前,我们可以先将其存储到堆栈中,如下:

PUSH AX
PUSH BXMOV AX, VALUE1
MOV BX, VALUE2MOV VALUE1, AX
MOV VALUE2, BXPOP BX
POP AX

​ 示例:下面程序利用循环输出整个 ascii 字符集

section .text
global _start   ; 必须为了使用链接器 (gcc)_start:call display   ; 调用 display 过程mov eax, 1      ; 系统调用号 (sys_exit)int 0x80         ; 调用内核display:mov ecx, 256     ; 设置循环计数器,控制输出字符的次数next:push ecx         ; 保存循环计数器的值mov eax, 4       ; 系统调用号 (sys_write)mov ebx, 1       ; 文件描述符 (stdout)mov ecx, achar   ; 输出字符的地址mov edx, 1       ; 输出字符的长度int 80h          ; 调用内核进行输出pop ecx          ; 恢复循环计数器的值mov dx, [achar]  ; 将当前字符的 ASCII 值加载到 DX 寄存器cmp byte [achar], 0dh  ; 比较当前字符是否为回车符 '\r'inc byte [achar]       ; 将字符 '0' 到 '9' 逐个增加loop next        ; 继续循环ret              ; 返回section .data
achar db '0'       ; 存储当前输出的字符

​ 编译运行后的结果输出如下:

0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|} ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿AÃŅLJɉˋ͍ϏёӓՕחٙۛݝߟᢢ䥥稨ꫫ�𐱱󴵵󸹹𽾾ÿ 
...
...

递归

​ 递归过程是一种调用自身的过程。递归又分为两种:直接递归和间接递归。直接递归是过程调用自身;间接递归是第一个过程调用第二个过程,第二个过程又调用第一个过程。

​ 下面我们用汇编以递归的方式实现一个阶乘,计算阶乘 3:

section .text
global _start   ; 必须为了使用链接器 (gcc)_start:mov bx, 3      ; 设置 bx 为 3,用于计算 3 的阶乘call proc_fact ; 调用 proc_fact 过程计算阶乘add ax, 30h    ; 将结果转换为 ASCII 码mov [fact], ax ; 将结果存储在 fact 变量中; 输出 "Factorial 3 is:"mov edx, len   ; 设置消息长度mov ecx, msg   ; 设置消息内容mov ebx, 1     ; 文件描述符 (stdout)mov eax, 4     ; 系统调用号 (sys_write)int 0x80       ; 调用内核进行输出; 输出计算结果mov edx, 1     ; 设置消息长度mov ecx, fact  ; 设置消息内容mov ebx, 1     ; 文件描述符 (stdout)mov eax, 4     ; 系统调用号 (sys_write)int 0x80       ; 调用内核进行输出; 退出程序mov eax, 1     ; 系统调用号 (sys_exit)int 0x80       ; 调用内核退出proc_fact:cmp bl, 1      ; 比较 bl 是否为 1jg do_calculation  ; 如果 bl 大于 1,则进行计算mov ax, 1      ; 如果 bl 等于 1,则结果为 1retdo_calculation:dec bl         ; 减少 bl 的值call proc_fact ; 递归调用 proc_fact 过程inc bl         ; 恢复 bl 的值mul bl         ; 计算阶乘,ax = al * blretsection .data
msg db 'Factorial 3 is:', 0xa ; 消息内容
len equ $ - msg                 ; 消息长度section .bss
fact resb 1                     ; 存储计算结果的变量

​ 编译运行后的结果为:

Factorial 3 is:
6

​ 编写宏是汇编语言实现模块化编程的另一种方式:

  • 宏是一系列指令,由名词指定,可以在程序中的任意位置使用
  • 在 NASM 中,宏使用 %macro%endmarro 指令定义,以前者开头,后者结尾

​ 宏定义的语法 :

%macro macro_name  number_of_params
<macro body>
%endmacro

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

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

示例

​ 下面的示例演示了如何定义宏和使用宏:

%macro write_string 2mov eax, 4mov ebx, 1mov ecx, %1mov edx, %2int 0x80
%endmacrosection .datamsg1 db 'Hello World'len1 equ $ - msg1section .textglobal _start_start:write_string msg1, len1mov eax, 1int 0x80

​ 上面的程序编译运行后的结果如下:

Hello World

文件管理

​ 系统将任何输入或输出数据视为字节流,标准的文件流有 3 种:

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

​ 文件描述符作文文件 ID 分配给文件的 16 位整数。当创建文件或打开现有文件时,文件描述符用于访问文件

​ 标准文件流的文件描述符 - stdin、stdoutstderr 分别为 0、1 和 2。

文件处理系统调用

​ 下表简要描述了与文件处理相关的系统调用 −

%eaxName%ebx%ecx%edx
2sys_forkstruct pt_regs--
3sys_readunsigned intchar *size_t
4sys_writeunsigned intconst char *size_t
5sys_openconst char *intint
6sys_closeunsigned int--
8sys_creatconst char *int-
19sys_lseekunsigned intoff_tunsigned int

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

  • 将系统调用号放入EAX寄存器中。
  • 将系统调用的参数存储在寄存器 EBX、ECX 等中。
  • 调用相关中断(80h)。
  • 结果通常返回到 EAX 寄存器中。
创建并打开文件
  • 将系统调用 sys_creat() 编号 8 放入 EAX 寄存器。
  • 将文件名放入 EBX 寄存器。
  • 将文件权限放入 ECX 寄存器。
  • 系统调用返回 EAX 寄存器中创建的文件的文件描述符,错误代码存储在 EAX 寄存器中。
打开现有文件
  • 将系统调用 sys_open() 编号 5 放入 EAX 寄存器。
  • 将文件名放入 EBX 寄存器。
  • 将文件访问模式放入 ECX 寄存器。
  • 将文件权限放入 EDX 寄存器。
  • 系统调用返回 EAX 寄存器中打开的文件的文件描述符,错误代码存储在 EAX 寄存器中。
  • 常用的文件访问模式包括:只读(0)、只写(1)和读写(2)。
从文件中读取
  • 将系统调用 sys_read() 编号 3 放入 EAX 寄存器。
  • 将文件描述符放入 EBX 寄存器。
  • 将指向输入缓冲区的指针放入 ECX 寄存器。
  • 将缓冲区大小(即要读取的字节数)放入 EDX 寄存器。
  • 系统调用返回在 EAX 寄存器中读取的字节数,错误代码存储在 EAX 寄存器中。
写入文件
  • 将系统调用 sys_write() 编号 4 放入 EAX 寄存器。
  • 将文件描述符放入 EBX 寄存器。
  • 将指向输出缓冲区的指针放入 ECX 寄存器。
  • 将缓冲区大小(即要写入的字节数)放入 EDX 寄存器。
  • 系统调用返回 EAX 寄存器中实际写入的字节数,错误代码存储在 EAX 寄存器中。
关闭文:
  • 将系统调用 sys_close() 编号 6 放入 EAX 寄存器。
  • 将文件描述符放入 EBX 寄存器。
  • 如果出现错误,系统调用将返回 EAX 寄存器中的错误代码。
更新文件
  • 将系统调用 sys_lseek() 编号 19 放入 EAX 寄存器。
  • 将文件描述符放入 EBX 寄存器。
  • 将偏移值放入 ECX 寄存器。
  • 将偏移的参考位置放入 EDX 寄存器。
  • 参考位置可以是文件开头(值 0)、当前位置(值 1)或文件结尾(值 2)。
  • 如果出现错误,系统调用将返回 EAX 寄存器中的错误代码。

示例

​ 下面用一个复杂的例子演示一下如何使用系统调用:

section .textglobal _start         ; 必须声明以供使用gcc_start:                  ; 告诉链接器入口点在这里; 创建文件mov  eax, 8           ; 使用 sys_creat() 系统调用,编号为 8mov  ebx, file_name   ; 文件名存储在 ebx 寄存器中mov  ecx, 0777o       ; 文件权限,八进制表示,为所有用户设置读、写和执行权限int  0x80             ; 调用内核mov [fd_out], eax     ; 存储文件描述符以供后续使用; 写入文件mov	edx, len          ; 要写入的字节数mov	ecx, msg          ; 要写入的消息mov	ebx, [fd_out]     ; 文件描述符mov	eax, 4            ; 使用 sys_write() 系统调用,编号为 4int	0x80             ; 调用内核; 关闭文件mov eax, 6            ; 使用 sys_close() 系统调用,编号为 6mov ebx, [fd_out]     ; 文件描述符int 0x80              ; 调用内核; 写入表示文件写入结束的消息mov eax, 4            ; 使用 sys_write() 系统调用,编号为 4mov ebx, 1            ; 文件描述符为标准输出mov ecx, msg_done     ; 要写入的消息mov edx, len_done     ; 要写入的字节数int  0x80             ; 调用内核; 以只读方式打开文件mov eax, 5            ; 使用 sys_open() 系统调用,编号为 5mov ebx, file_name    ; 文件名存储在 ebx 寄存器中mov ecx, 0            ; 以只读方式打开mov edx, 0777o        ; 文件权限,八进制表示,为所有用户设置读、写和执行权限int  0x80             ; 调用内核mov  [fd_in], eax     ; 存储文件描述符以供后续使用; 从文件中读取mov eax, 3            ; 使用 sys_read() 系统调用,编号为 3mov ebx, [fd_in]      ; 文件描述符mov ecx, info         ; 存储读取的数据的缓冲区mov edx, 26           ; 要读取的字节数int 0x80              ; 调用内核; 关闭文件mov eax, 6            ; 使用 sys_close() 系统调用,编号为 6mov ebx, [fd_in]      ; 文件描述符int 0x80              ; 调用内核; 打印信息mov eax, 4            ; 使用 sys_write() 系统调用,编号为 4mov ebx, 1            ; 文件描述符为标准输出mov ecx, info         ; 要写入的消息mov edx, 26           ; 要写入的字节数int 0x80              ; 调用内核mov	eax, 1             ; 使用 sys_exit() 系统调用,编号为 1int	0x80              ; 调用内核section .data
file_name db 'myfile.txt'  ; 文件名
msg db 'Welcome to Tutorials Point'  ; 要写入文件的消息
len equ  $-msg            ; 计算消息的字节数msg_done db 'Written to file', 0xa  ; 文件写入结束的消息
len_done equ $-msg_done   ; 计算消息的字节数section .bss
fd_out resb 1             ; 存储文件描述符的变量(写入文件用)
fd_in  resb 1             ; 存储文件描述符的变量(读取文件用)
info resb  26             ; 存储从文件读取的数据的缓冲区

​ 上述程序创建并打开名为 myfile.txt 的文件,并在此文件中写入文本"Welcome to Tutorials Point"。 接下来,程序从文件中读取数据并将数据存储到名为 info 的缓冲区中。 最后,它显示存储在 info 中的文本。

内存管理

sys_brk()系统调用由内核提供,用于在应用程序映像的数据部分之后分配内存,而无需在稍后移动它。此调用允许设置数据部分的最高可用地址。系统调用的唯一参数是需要设置的最高内存地址,该值存储在EBX寄存器中。

​ 这个程序使用 sys_brk() 系统调用分配了16 KB的内存:

assemblyCopy codesection .textglobal _start        ;必须为使用gcc而声明_start:                 ;告知链接器入口点mov eax, 45          ;sys_brkxor ebx, ebxint 80hadd eax, 16384       ;要保留的字节数mov ebx, eaxmov eax, 45          ;sys_brkint 80hcmp eax, 0jl exit              ;如果出错则退出 mov edi, eax         ;EDI = 最高可用地址sub edi, 4           ;指向最后一个DWORD  mov ecx, 4096        ;已分配的DWORD数xor eax, eax         ;清空eaxstd                   ;反向rep stosd            ;对整个分配区域重复cld                   ;将DF标志设置回正常状态mov eax, 4mov ebx, 1mov ecx, msgmov edx, lenint 80h               ;打印一条消息exit:mov eax, 1xor ebx, ebxint 80hsection .data
msg     db "分配了16 KB的内存!", 10
len     equ $ - msg

这篇关于汇编语言学习(7)完结篇的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/529322

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

线性代数|机器学习-P36在图中找聚类

文章目录 1. 常见图结构2. 谱聚类 感觉后面几节课的内容跨越太大,需要补充太多的知识点,教授讲得内容跨越较大,一般一节课的内容是书本上的一章节内容,所以看视频比较吃力,需要先预习课本内容后才能够很好的理解教授讲解的知识点。 1. 常见图结构 假设我们有如下图结构: Adjacency Matrix:行和列表示的是节点的位置,A[i,j]表示的第 i 个节点和第 j 个

Node.js学习记录(二)

目录 一、express 1、初识express 2、安装express 3、创建并启动web服务器 4、监听 GET&POST 请求、响应内容给客户端 5、获取URL中携带的查询参数 6、获取URL中动态参数 7、静态资源托管 二、工具nodemon 三、express路由 1、express中路由 2、路由的匹配 3、路由模块化 4、路由模块添加前缀 四、中间件