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)的更多相关文章

  1. x86汇编语言实践(2)

    0 写在前面 为了更深入的了解程序的实现原理,近期我学习了IBM-PC相关原理,并手工编写了一些x86汇编程序. 在2017年的计算机组成原理中,曾对MIPS体系结构及其汇编语言有过一定的了解,考虑到 ...

  2. x86汇编语言实践(1)

    0 写在前面 为了更深入的了解程序的实现原理,近期我学习了IBM-PC相关原理,并手工编写了一些x86汇编程序. 在2017年的计算机组成原理中,曾对MIPS体系结构及其汇编语言有过一定的了解,考虑到 ...

  3. 进入保护模式(三)——《x86汇编语言:从实模式到保护模式》读书笔记17

    (十)保护模式下的栈 ;以下用简单的示例来帮助阐述32位保护模式下的堆栈操作 mov cx,00000000000_11_000B ;加载堆栈段选择子 mov ss,cx mov esp,0x7c00 ...

  4. VS2013的x86汇编语言开发环境配置

    转载:https://blog.csdn.net/infoworld/article/details/45085415 转载:https://blog.csdn.net/u014792304/arti ...

  5. 存储器的保护(三)——《x86汇编语言:从实模式到保护模式》读书笔记20

    存储器的保护(三) 修改本章代码清单,使之可以检测1MB以上的内存空间(从地址0x0010_0000开始,不考虑高速缓存的影响).要求:对内存的读写按双字的长度进行,并在检测的同时显示已检测的内存数量 ...

  6. 存储器的保护(一)——《x86汇编语言:从实模式到保护模式》读书笔记18

    本文是原书第12章的学习笔记. 说句题外话,这篇博文是补写的,因为让我误删了,可恶的是CSDN的回收站里找不到! 好吧,那就再写一遍,我有坚强的意志.司马迁曰:“文王拘而演<周易>:仲尼厄 ...

  7. 16位模式/32位模式下PUSH指令探究——《x86汇编语言:从实模式到保护模式》读书笔记16

    一.Intel 32 位处理器的工作模式 如上图所示,Intel 32 位处理器有3种工作模式. (1)实模式:工作方式相当于一个8086 (2)保护模式:提供支持多任务环境的工作方式,建立保护机制 ...

  8. 进入保护模式(二)——《x86汇编语言:从实模式到保护模式》读书笔记14

    首先来段题外话:之前我发现我贴出的代码都没有行号,给讲解带来不便.所以从现在起,我要给代码加上行号.我写博客用的这个插入代码的插件,确实不支持自动插入行号.我真的没有找到什么好方法,无奈之下,只能按照 ...

  9. linux平台学x86汇编语言学习集合帖

    linux平台学x86汇编语言学习集合帖 linux平台学x86汇编(一):https://blog.csdn.net/shallnet/article/details/45543237 linux平 ...

随机推荐

  1. 如何利用U盘重装系统

    第一步,下载系统镜像 推荐在msdn上面下载,因为大多数都是 Microsoft 纯净原版镜像,如果要安装的是纯净版系统请先看第六步,然后才看第二步 第二步,下载U盘PE工具 推荐使用大白菜或者老毛桃 ...

  2. js 滚轮控制图片缩放大小和拖动

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. WEB前端学习资源清单

    常用学习资源 JS参考与基础学习系列 [MDN]JS标准参考 es6教程 JS标准参考教程 编程类中文书籍索引 深入理解JS系列 前端开发仓库 <JavaScript 闯关记> JavaS ...

  4. Testlink插件工具

    目的: 使用Testlink时间长了,会发现有些功能体验不是很好,比如用例编写就无法快速复制,且展示能力很弱 使用对象: 测试人员.测试leader,技术经理 xmind2testlink:xmind ...

  5. C#零基础入门-2-Visual Studio (VS)程序初始化及各组成部分

    X:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe 可以使用桌面快捷方式启动,也可以从开始菜单启动,还 ...

  6. [SQL Server] 时间处理:获取今天的00:00:00/获取今天的23:59:59

    获取今天的00:00:00 SELECT CONVERT(DATETIME,CONVERT(VARCHAR(10),GETDATE(),120)) 获取今天的23:59:59 1.SELECT DAT ...

  7. firewalld防火墙设置

    CentOS7/RHEL7系统默认的iptables管理工具是firewalld,不再是以往的iptables-services,命令用起来也是不一样了,当然你也可以选择卸载firewalld,安装i ...

  8. python进阶之生成器

    迭代器 什么叫迭代 可以被for循环的就说明他们是可迭代的,比如:字符串,列表,字典,元祖,们都可以for循环获取里面的数据 下面我们看一个代码: number = 12345 for i in nu ...

  9. SQL AVG 函数

    定义和用法 AVG 函数返回数值列的平均值.NULL 值不包括在计算中. SQL AVG() 语法 SELECT AVG(column_name) FROM table_name SQL AVG() ...

  10. vue nextTick使用

    Vue nextTick使用 vue生命周期 原因是在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue. ...