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. 使用cobbler工具实现centos 6,7系统的自动化安装

    vmware里面准备两台虚拟机,一台用于安装cobbler服务器,另一台当作测试机使用,cobbler服务器需要两块网卡,一块需要连接外网,需要使用epel源.测试机使用一块仅主机的模式的网卡,注意要 ...

  2. 单机Qps上限是多少?

    现在这个年代,你要是不懂高并发,你都不好意思说自己是搞互联网的! 一.什么是并发,什么是高并发 并发,两个及以上的行为一起发生,比如你一边吃饭一边看电视 高并发,多个行为(至于是多少,这个没有定数,你 ...

  3. 个人博客制作如何选择前端模板 thinkcmf后台加载新模板 CSS js文件

    我们的博客后台已经搭建好了,接下来我就要选择一个合适的模板做自己的博客,首先要定位你的博客是做什么用的,是属于什么行业,根据自己博客的定位选择适合的模板. 如果你是设计师,又会前端设计开发,那就可以自 ...

  4. JavaScript 运行机制 (Event Loop)

    单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务.如果前一个任务耗时很长,后一个任务就不得不一直等着. 所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步 ...

  5. 第十二课 CSS基本选择器 css学习2

    基础选择器一.标签选择器(元素选择器)标签选择器是指用HTML标签名称作为选择器,按标签名称分类语法:标签名{属性1:属性值1;属性2:属性值2;属性3:属性值3;} 二.类选择器1.类选择器使用&q ...

  6. arcgis api 3.x for js 之 echarts 开源 js 库实现地图统计图分析(附源码下载)

    前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 3.x for js:esri 官网 api,里面详细的介绍 arcgis api 3.x 各个类 ...

  7. Android 系统服务的获取与创建

    在Android系统中,有一群很厉害的“家伙”,如果把Android系统比喻成一个大帮派,那么这群“家伙”的地位就像那各个分堂的堂主一样,所有的应用就像是各个小马哥,他们要做什么事情,都要堂主审批,审 ...

  8. DVWA 黑客攻防演练(十)反射型 XSS 攻击 Reflected Cross Site Scripting

    XSS (Cross-site scripting) 攻击,为和 CSS 有所区分,所以叫 XSS.又是一种防不胜防的攻击,应该算是一种 "HTML注入攻击",原本开发者想的是显示 ...

  9. BitSet: 有1千万个随机数,随机数的范围在1到1亿之间。现在要求写出一种算法,将1到1亿之间没有在随机数中的数求出来?

    package common; import java.util.ArrayList; import java.util.BitSet; import java.util.List; import j ...

  10. Unity切换到安卓平台Shader丢失(opengl)

    Unity安卓平台shader平台丢失 Unity的工程切换到Android平台后,运行游戏出现shader丢失 解决办法:在Unity桌面图标的快捷方式后添加 -force-gles20 示例:&q ...