x86汇编语言实践(3)
0 写在前面
为了更深入的了解程序的实现原理,近期我学习了IBM-PC相关原理,并手工编写了一些x86汇编程序。
在2017年的计算机组成原理中,曾对MIPS体系结构及其汇编语言有过一定的了解,考虑到x86体系结构在目前的广泛应用,我通过两个月左右的时间对x86的相关内容进行了学习。
在《x86汇编语言实践》系列中(包括本篇、x86汇编语言实践(1)、x86汇编语言实践(2)、x86汇编语言实践(4)以及x86汇编语言复习笔记),我通过几个具体案例对x86汇编语言进行实践操作,并记录了自己再编写汇编代码中遇到的困难和心得体会,与各位学习x86汇编的朋友共同分享。
我将我编写的一些汇编代码放到了github上,感兴趣的朋友可以点击屏幕左上角的小猫咪进入我的github,或请点击这里下载源代码。
1 递归调用计算N!
1-1 练习要点
递归调用
栈指针的维护
子程序编写与调用
1-2 实现思路
在数据段存储好待计算的N,和用以存储计算结果的RESULT
主程序中首先将N和RESULT压栈
调用CALCULATE进行阶乘的递归计算
结果返回至RESULT
调用DISP_VALUE打印输出阶乘计算结果
1-3 重点难点
参数传递:使用堆栈进行参数传递,需要将参数压栈,注意子程序返回时,必须增加一个常数偏移量RET X。这里的X为压入参数所占的字节数,通常为2的倍数,以保证堆栈平衡
子程序保存现场:在子程序中,往往要用到很多寄存器,但我们希望在子程序返回时,调用子程序位置处周围的变量仍能恢复,这就需要在调用的子程序中保存现场,即子程序中所用到或修改的所有寄存器,都必须压栈处理
子程序中的堆栈寻址:使用BP寄存器寻址,这是为了不修改SP指针,避免弄乱堆栈栈顶指针SP
中间一直困扰我的就是在子程序中获取参数N的方式MOV BX,[BP+6],为什么是BP+6呢?我们来看,BP保存的是子程序中的SP指针,但是距离我们将N压栈之间,我们经历了:将RESULT压栈、调用时将调用处的IP+2压栈以及将BP压栈,三个过程。因此当前的BP和N之间相差了6个字节的距离,故采用[BP+6]的方式进行参数N的寻址
输出上的改进:仍是除10显示,但这次保存余数。为了得到正序输出,将每次的余数压栈,这样在显示的时候就是从高位向低位显示了。此外,在输出时对前导0进行了过滤处理,需要注意的是当遇到第一个非0数字后,需要将标志位置1,这样以后的数字0就可以正常显示。
1-4 代码实现
STACK SEGMENT PARA STACK
DW 100H DUP(?)
STACK ENDS DATA SEGMENT PARA
N DW
RESULT DW ?
DATA ENDS CODE SEGMENT PARA
ASSUME CS:CODE,DS:DATA,SS:STACK
CALCULATE PROC NEAR
CAL_PART:
PUSH BP
MOV BP,SP
PUSH DX
PUSH BX MOV BX,[BP+]
CMP BX,
JNZ CAL1
MOV AX,
JMP SHORT CAL2
CAL1:
PUSH BX
DEC BX
PUSH BX
PUSH RESULT
CALL CALCULATE
POP BX
MUL BX
CAL2:
MOV RESULT,AX
POP BX
POP DX
POP BP
RET
CALCULATE ENDP DISP_VALUE PROC
DISPLAY:
PUSH DX
PUSH CX
PUSH BX
PUSH AX MOV CX,
MOV BX, DLP1:
XOR DX,DX
DIV BX
PUSH DX
LOOP DLP1 MOV BX,
MOV CX,
DLP2:
POP DX
CMP DL,
JNZ DLP2_1
CMP BX,
JZ DLP2_2
DLP2_1:
MOV BX,
OR DL,30H
MOV AH,
INT 21H
DLP2_2:
LOOP DLP2 POP AX
POP BX
POP CX
POP DX
RET
DISP_VALUE ENDP MAIN PROC FAR
MAINPROC:
MOV AX,DATA
MOV DS,AX MOV AX,N
PUSH AX
PUSH RESULT
CALL CALCULATE
MOV AX,RESULT
CALL DISP_VALUE EXIT:
MOV AX,4C00H
INT 21H
MAIN ENDP
CODE ENDS
END MAIN
1-5 实现效果截图
1-5-1 计算N=7时的阶乘计算结果
经验证,发现输出结果符合预期。
1-5-2 查看递归调用到N=4时的堆栈信息
从上面单步执行的寄存器结果中可以看出,BX=4即此时已经执行到N=4,此时堆栈指针SP位于01d2。我们来分析一下,当前堆栈中的内容:
ss:1d2 压入RESULT作为参数向递归函数中传递,值为0
ss:1d4 压入BX(这里也就是N=4)作为参数向递归函数中传递,值为4
ss:1d6 保存的减一之前的N,这是为了在子程序返回时能计算N*AX返回结果
ss:1d8 子程序开始是压入的BX保存的值,值为5
ss:1da 子程序开始是压入的DX保存的值,值为0
ss:1dc 子程序开始是压入的BP保存的值,值为1ea
ss:1de CALL子程序会保存调用处下一条指令的IP并压栈,值为1c,即该子程序返回后会跳转至1c(+偏移值)
2 练习子程序参数传递的两种方法
2-1 练习要点
子程序的编写
使用寄存器向子程序传递参数
使用堆栈向子程序传递参数
复习乘法计算子程序,字符串拷贝子程序,字符串比较子程序,查找子程序
选做部分我练习的是将字符串中全部的大写字母替换成小写字母
2-2 重点难点
寄存器传参比较简单,将用到参数的寄存器保存为相应的参数值即可完成参数传递
堆栈传参需要注意以下几点
压栈顺序一定要注意,在压入多个参数时,需要记住其相对于SP的相对位置,从而避免取出参数时的混乱
在子程序中对参数的索引采用BP指针代替SP指针进行寻址,从而避免改变栈顶SP指针引发的紊乱现象发生
返回时需要加上一个常数偏移量,将压入栈中的参数位置地址恢复,从而维持堆栈平衡
2-3 实现思路
首先为输入和输出单独编写子程序,程序主体采用跳转表实现
为每一个条件单独编写一个子程序,有10中条件(A-E为堆栈传参子程序,a-e为寄存器传参子程序),因此共需编写10个子程序分别对应着实现响应功能
在最外层设置循环结构,使得程序能够处理多组输入
字符串、数据、参数等初始化设置在数据段完成即可
2-4 代码实现
STACK SEGMENT PARA STACK
DW 100H DUP(?)
STACK ENDS DATA SEGMENT PARA
LEN EQU
N EQU ;TIMES OF LOOP
X DW
Y DW
Z DW ?
STRING1 DB 'QIQI',20H,,'$'
STRING2 DB 'CHEN',20H,,'$'
CHAR DB 'C'
OP DB ?
NL DB ,,'$'
MSGEQ DB 'STRING1=STRING2',,,'$'
MSGGT DB 'STRING1>STRING2',,,'$'
MSGLT DB 'STRING1<STRING2',,,'$'
DOFOUND DB 'CHAR FOUND IN STRING2',,,'$'
NOTFOUND DB 'CHAR NOT FOUND IN STRING2',,,'$'
DATA ENDS CODE SEGMENT PARA
ASSUME CS:CODE,DS:DATA,SS:STACK
;PRINT A NEWLINE
NEWLINE MACRO
PUSH DX
PUSH AX
MOV DX,OFFSET NL
MOV AH,
INT 21H
POP AX
POP DX
ENDM
;GET OPERATION TO OP
GETOP MACRO
GETOPM:
MOV AH,
INT 21H
MOV OP,AL
ENDM
;OUTPUT MSG
OUTPUT MACRO MSG
PUSH DX
PUSH AX
MOV DX,OFFSET MSG
MOV AH,
INT 21H
POP AX
POP DX
ENDM
;DISPLAY VALUE IN AX
DISP_VALUE PROC
DISPLAY:
PUSH DX
PUSH CX
PUSH BX
PUSH AX MOV CX,
MOV BX, DLP1:
XOR DX,DX
DIV BX
PUSH DX
LOOP DLP1 MOV BX,
MOV CX,
DLP2:
POP DX
CMP DL,
JNZ DLP2_1
CMP BX,
JZ DLP2_2
DLP2_1:
MOV BX,
OR DL,30H
MOV AH,
INT 21H
DLP2_2:
LOOP DLP2 NEWLINE
POP AX
POP BX
POP CX
POP DX
RET
DISP_VALUE ENDP DISP_STR2 PROC
PRINTSTR2:
PUSH DX
MOV DX,OFFSET STRING2
MOV AH,
INT 21H
NEWLINE
POP DX
RET
DISP_STR2 ENDP MULTIPLE PROC
MULTI:
PUSH BP
MOV BP,SP
PUSH AX
PUSH BX MOV AX,[BP+]
MOV BX,[BP+]
MUL BX
MOV Z,AX POP BX
POP AX
POP BP RET
MULTIPLE ENDP MULTIPLE2 PROC
MULTI2:
MUL BX
MOV Z,AX
RET
MULTIPLE2 ENDP STRCPY PROC
STRCPYPROC:
PUSH BP
MOV BP,SP PUSH DI
PUSH SI
MOV SI,[BP+]
MOV DI,[BP+] CLD
CPYLP:
LODSB
STOSB
CMP AL,
JNZ CPYLP
POP SI
POP DI
POP BP
RET
STRCPY ENDP STRCPY2 PROC
STRCPY2PROC:
CLD
CPYLP2:
LODSB
STOSB
CMP AL,
JNZ CPYLP2
RET
STRCPY2 ENDP STRCMP PROC
STRCMPROC:
PUSH BP
MOV BP,SP PUSH DI
PUSH SI MOV SI,[BP+]
MOV DI,[BP+]
CALL STRCMP2 POP SI
POP DI
POP BP
RET
STRCMP ENDP STRCMP2 PROC
STRCMP2PROC:
PUSH CX
PUSH SI
CLD
PUSH SI
MOV CX,
CMPLP2:
LODSB
CMP AL,
JZ CMPLPBEG2
INC CX
JMP SHORT CMPLP2
CMPLPBEG2:
POP SI
REPE CMPSB
JA L2_1
JB L2_2
OUTPUT MSGEQ
JMP SHORT CMPRET2
L2_1:
OUTPUT MSGGT
JMP SHORT CMPRET2
L2_2:
OUTPUT MSGLT
CMPRET2:
POP SI
POP CX
RET
STRCMP2 ENDP FIND PROC
FINDCHAR:
PUSH BP
MOV BP,SP
PUSH CX MOV DI,[BP+]
MOV CX,LEN
DEC CX
MOV AX,[BP+]
CLD
REPNZ SCASB
JZ FOUND
OUTPUT NOTFOUND
JMP SHORT FIND_RETURN
FOUND:
OUTPUT DOFOUND
FIND_RETURN:
POP CX
POP BP
RET
FIND ENDP FIND2 PROC
FIND2PROC:
PUSH CX
PUSH DI
MOV CX,LEN
DEC CX
CLD
REPNZ SCASB
JZ FOUND2
OUTPUT NOTFOUND
JMP SHORT FIND2RETURN
FOUND2:
OUTPUT DOFOUND
FIND2RETURN:
POP DI
POP CX
RET
FIND2 ENDP TOLOWER PROC
TOLOW:
PUSH BP
MOV BP,SP
PUSH SI
PUSH DI
PUSH CX
PUSH AX MOV SI,[BP + ]
MOV DI,SI
MOV CX,LEN
CLD
TOLOW_LP:
LODSB
CMP AL,'A'
JB TOLOW_CONTINUE
CMP AL,'Z'
JA TOLOW_CONTINUE
ADD AL,20H
TOLOW_CONTINUE:
STOSB
LOOP TOLOW_LP POP AX
POP CX
POP DI
POP SI
POP BP
RET
TOLOWER ENDP TOLOWER2 PROC
TOLOW2:
PUSH SI
PUSH DI
PUSH CX
PUSH AX
MOV DI,SI
MOV CX,LEN
DEC CX
CLD
TOLOW_LP2:
LODSB
CMP AL,'A'
JB TOLOW_CONTINUE2
CMP AL,'Z'
JA TOLOW_CONTINUE2
ADD AL,20H
TOLOW_CONTINUE2:
STOSB
LOOP TOLOW_LP2
POP AX
POP CX
POP DI
POP SI
RET
TOLOWER2 ENDP SWITCH PROC
SWITCHPROC:
PUSH CX
S0:
CMP OP,'A'
JNE S1
PUSH X
PUSH Y
CALL MULTIPLE
MOV AX,Z
CALL DISP_VALUE
JMP CONTINUE
S1:
CMP OP,'B'
JNE S2
MOV DX,OFFSET STRING2
PUSH DX
MOV DX,OFFSET STRING1
PUSH DX
CALL STRCPY
OUTPUT STRING2
NEWLINE
JMP CONTINUE
S2:
CMP OP,'C'
JNE S3
MOV DX,OFFSET STRING2
PUSH DX
MOV DX,OFFSET STRING1
PUSH DX
CALL STRCMP
JMP CONTINUE
S3:
CMP OP,'D'
JNE S4
MOV DX,OFFSET STRING2
PUSH DX
MOV DL,CHAR
XOR DH,DH
PUSH DX
CALL FIND
JMP CONTINUE
S4:
CMP OP,'E'
JNE S5
MOV DX,OFFSET STRING1
PUSH DX
CALL TOLOWER
OUTPUT STRING1
NEWLINE
JMP CONTINUE
S5:
CMP OP,'a'
JNE S6
MOV AX,X
MOV BX,Y
CALL MULTIPLE2
MOV AX,Z
CALL DISP_VALUE
JMP CONTINUE
S6:
CMP OP,'b'
JNE S7
MOV SI,OFFSET STRING1
MOV DI,OFFSET STRING2
CALL STRCPY2
OUTPUT STRING2
NEWLINE
JMP CONTINUE
S7:
CMP OP,'c'
JNE S8
MOV SI,OFFSET STRING1
MOV DI,OFFSET STRING2
CALL STRCMP2
JMP CONTINUE
S8:
CMP OP,'d'
JNE S9
MOV DI,OFFSET STRING2
MOV AL,CHAR
CALL FIND2
JMP CONTINUE
S9:
CMP OP,'e'
JNE CONTINUE
MOV SI,OFFSET STRING2
CALL TOLOWER2
OUTPUT STRING2
NEWLINE
CONTINUE:
POP CX
RET
SWITCH ENDP MAIN PROC FAR
MAINPROC:
MOV AX,DATA
MOV DS,AX
MOV ES,AX MOV CX,N
MAINLOOP:
GETOP
NEWLINE
CALL SWITCH
LOOP MAINLOOP EXIT:
MOV AX,4C00H
INT 21H
MAIN ENDP CODE ENDS
END MAIN
2-5 运行结果
为了验证程序符合预期,需要设计以下样例进行测试。设置循环次数为10次
设置数据区如下:
数据分别表示
LEN 字符串长
N 外循环次数
X,Y,Z 执行A/a操作时的乘数和结果
STRING1,STRING2 待操作的两个字符串
CHAR 待寻找的字符串
OP 读入的操作指令符
NL 回车换行标志
MSGEQ,MSGGT,MSGLT,DOFOUND,NOTFOUND 输出提示信息
运行程序,得到如下结果
显然,运行结果符合预期。
x86汇编语言实践(3)的更多相关文章
- x86汇编语言实践(2)
0 写在前面 为了更深入的了解程序的实现原理,近期我学习了IBM-PC相关原理,并手工编写了一些x86汇编程序. 在2017年的计算机组成原理中,曾对MIPS体系结构及其汇编语言有过一定的了解,考虑到 ...
- x86汇编语言实践(1)
0 写在前面 为了更深入的了解程序的实现原理,近期我学习了IBM-PC相关原理,并手工编写了一些x86汇编程序. 在2017年的计算机组成原理中,曾对MIPS体系结构及其汇编语言有过一定的了解,考虑到 ...
- 进入保护模式(三)——《x86汇编语言:从实模式到保护模式》读书笔记17
(十)保护模式下的栈 ;以下用简单的示例来帮助阐述32位保护模式下的堆栈操作 mov cx,00000000000_11_000B ;加载堆栈段选择子 mov ss,cx mov esp,0x7c00 ...
- VS2013的x86汇编语言开发环境配置
转载:https://blog.csdn.net/infoworld/article/details/45085415 转载:https://blog.csdn.net/u014792304/arti ...
- 存储器的保护(三)——《x86汇编语言:从实模式到保护模式》读书笔记20
存储器的保护(三) 修改本章代码清单,使之可以检测1MB以上的内存空间(从地址0x0010_0000开始,不考虑高速缓存的影响).要求:对内存的读写按双字的长度进行,并在检测的同时显示已检测的内存数量 ...
- 存储器的保护(一)——《x86汇编语言:从实模式到保护模式》读书笔记18
本文是原书第12章的学习笔记. 说句题外话,这篇博文是补写的,因为让我误删了,可恶的是CSDN的回收站里找不到! 好吧,那就再写一遍,我有坚强的意志.司马迁曰:“文王拘而演<周易>:仲尼厄 ...
- 16位模式/32位模式下PUSH指令探究——《x86汇编语言:从实模式到保护模式》读书笔记16
一.Intel 32 位处理器的工作模式 如上图所示,Intel 32 位处理器有3种工作模式. (1)实模式:工作方式相当于一个8086 (2)保护模式:提供支持多任务环境的工作方式,建立保护机制 ...
- 进入保护模式(二)——《x86汇编语言:从实模式到保护模式》读书笔记14
首先来段题外话:之前我发现我贴出的代码都没有行号,给讲解带来不便.所以从现在起,我要给代码加上行号.我写博客用的这个插入代码的插件,确实不支持自动插入行号.我真的没有找到什么好方法,无奈之下,只能按照 ...
- linux平台学x86汇编语言学习集合帖
linux平台学x86汇编语言学习集合帖 linux平台学x86汇编(一):https://blog.csdn.net/shallnet/article/details/45543237 linux平 ...
随机推荐
- jQuery(一)、核心
我认为,学习一门语言,主要是掌握它的思想与用途,就好比谈恋爱一样,你只要猜测到了对方的想法,就能够知情达意.废话不多说,我们开始今天学习的进度,加油ヾ(◍°∇°◍)ノ゙ 1. jQuery([sele ...
- Java集合类源码解析:Vector
[学习笔记]转载 Java集合类源码解析:Vector 引言 之前的文章我们学习了一个集合类 ArrayList,今天讲它的一个兄弟 Vector.为什么说是它兄弟呢?因为从容器的构造来说,Vec ...
- 六大设计原则(四)ISP接口隔离原则(上)
ISP的定义 首先明确接口定义 实例接口 我们在Java中,一个类用New关键字来创建一个实例.抛开Java语言我们其实也可以称为接口.假设Person zhangsan = new Person() ...
- 小程序中使用ECharts 异步加载数据
官网例子都是同步的,怎么引入及同步demo请移步官网 <view class="container"> <ec-canvas id="mychart-d ...
- HTML中块元素与内联元素的概念
HTML中块元素与内联元素的概念 div就是一个块元素,所谓的块元素就是会独占一行的的元素,无论他的内容有多少,他都会独占一整行. p h1 h2 h3 ... div这个标签没有任何语义,就是一个纯 ...
- 利用nginx 反向代理解决跨域问题
说到nginx,不得不说真的很强大,也带来很多便利用于解决一些头疼的难题. 一般来说可以用来做:静态页面的服务器.静态文件缓存服务器.网站反向代理.负载均衡服务器等等,而且实现这一切,基本只需要改改那 ...
- 介绍Dynamics 365的OrgDBOrgSettings工具
摘要: 微软动态CRM专家罗勇 ,回复320或者20190320可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me!我的网站是 www.luoyong.me . 有时候会需要 ...
- 可以让你神操作的手机APP推荐 个个都是爆款系列
手机在我们的生活中显得日益重要,根据手机依赖度调查显示,69%的人出门时必带手机,20%的人经常在吃饭睡觉.上卫生间时使用手机:43%的人早上起床第一件事就是查看手机,不用多说,我们对于手机的依赖性越 ...
- SQL优化小技巧
我们要做到不但会写SQL,还要做到写出性能优良的SQL语句. 1.使用表的别名(Alias): 当在SQL语句中连接多个表时, 请使用表的别名并把别名前缀于每个Column上.这样一来,就可以减少解析 ...
- 使用sftp操作文件并添加事务管理
本文主要针对文件操作的事务管理,即写文件和删除文件并且能保证事务的一致性,可与数据库联合使用,比如需要在服务器存文件,相应的记录存放在数据库,那么数据库的记录和服务器的文件数一定是要一一对应的,该部分 ...