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. Web前端 web的学习之路2

    2019 年 Web 开发技术指南和趋势 2019/01/23 · JavaScript · 趋势 转载:原文出处: 李棠辉(http://web.jobbole.com/95622/)    以下内 ...

  2. flex 实例 豆瓣手机端布局实现

    0.最终成品

  3. servlet与jsp篇(一)$.ajax交互

    servlet其实是利用java类编写的服务器端应用程序,他的生命周期可以分为三个阶段:初始化阶段.运行阶段和消亡阶段; jsp页面实质上是一个HTML页面,但他包含了用户产生动态网页内容的java代 ...

  4. MyDAL - .UpdateAsync() 之 .Set() 使用

    索引: 目录索引 一.API 列表 1.Set<M, F>(Expression<Func<M, F>> propertyFunc, F newVal) 如: .S ...

  5. Spring Boot使用Maven打包替换资源文件占位符

    在Spring Boot开发中,通过Maven构建项目依赖是一件比较舒心的事,可以为我们省去处理冲突等大部分问题,将更多的精力用于业务功能上.近期在项目中,由于项目集成了其他外部系统资源文件,需要根据 ...

  6. ILRuntime官方Demo笔记

    调用/执行 热更中的方法 调用热更代码中方法,写在AppDomain中,记录一下主要几个方法: AppDomain.LoadAssembly 加载热更dll 执行热更代码的方法,有两种方式: appd ...

  7. MATLAB中“fitgmdist”的用法及其GMM聚类算法

    MATLAB中“fitgmdist”的用法及其GMM聚类算法 作者:凯鲁嘎吉 - 博客园http://www.cnblogs.com/kailugaji/ 高斯混合模型的基本原理:聚类——GMM,MA ...

  8. 你想知道的3D Touch开发全在这里了

    前言 iPhone 6s和iPhone 6s Plus为多点触摸界面带来了强大的3D触摸新维度.这项新技术可以感知用户按下显示屏的深度,让他们比以往任何时候都更能使用你的应用程序和游戏.更多关于3D ...

  9. mysql 分组内 排序

    mysql 分组内 排序 类似于 sqlserver over partition by   因为mysql中木有sqlserver over partition by这个函数,要从sqlserver ...

  10. ARTS打卡第三周

    Algorithm 题目描述 Given an array of integers, find if the array contains any duplicates. Your function ...