0 写在前面
为了更深入的了解程序的实现原理,近期我学习了IBM-PC相关原理,并手工编写了一些x86汇编程序。
在2017年的计算机组成原理中,曾对MIPS体系结构及其汇编语言有过一定的了解,考虑到x86体系结构在目前的广泛应用,我通过两个月左右的时间对x86的相关内容进行了学习。
在《x86汇编语言实践》系列中(包括本篇、x86汇编语言实践(1)以及x86汇编语言实践(2)),我通过几个具体案例对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 代码实现
1 STACK SEGMENT PARA STACK 2 DW 100H DUP(?) 3 STACK ENDS 4 5 DATA SEGMENT PARA 6 N DW 7 7 RESULT DW ? 8 DATA ENDS 9 10 CODE SEGMENT PARA 11 ASSUME CS:CODE,DS:DATA,SS:STACK 12 CALCULATE PROC NEAR 13 CAL_PART: 14 PUSH BP 15 MOV BP,SP 16 PUSH DX 17 PUSH BX 18 19 MOV BX,[BP+6] 20 CMP BX,0 21 JNZ CAL1 22 MOV AX,1 23 JMP SHORT CAL2 24 CAL1: 25 PUSH BX 26 DEC BX 27 PUSH BX 28 PUSH RESULT 29 CALL CALCULATE 30 POP BX 31 MUL BX 32 CAL2: 33 MOV RESULT,AX 34 POP BX 35 POP DX 36 POP BP 37 RET 4 38 CALCULATE ENDP 39 40 DISP_VALUE PROC 41 DISPLAY: 42 PUSH DX 43 PUSH CX 44 PUSH BX 45 PUSH AX 46 47 MOV CX,5 48 MOV BX,10 49 50 DLP1: 51 XOR DX,DX 52 DIV BX 53 PUSH DX 54 LOOP DLP1 55 56 MOV BX,0 57 MOV CX,5 58 DLP2: 59 POP DX 60 CMP DL,0 61 JNZ DLP2_1 62 CMP BX,0 63 JZ DLP2_2 64 DLP2_1: 65 MOV BX,1 66 OR DL,30H 67 MOV AH,2 68 INT 21H 69 DLP2_2: 70 LOOP DLP2 71 72 POP AX 73 POP BX 74 POP CX 75 POP DX 76 RET 77 DISP_VALUE ENDP 78 79 80 MAIN PROC FAR 81 MAINPROC: 82 MOV AX,DATA 83 MOV DS,AX 84 85 MOV AX,N 86 PUSH AX 87 PUSH RESULT 88 CALL CALCULATE 89 MOV AX,RESULT 90 CALL DISP_VALUE 91 92 EXIT: 93 MOV AX,4C00H 94 INT 21H 95 MAIN ENDP 96 CODE ENDS 97 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 代码实现
1 STACK SEGMENT PARA STACK 2 DW 100H DUP(?) 3 STACK ENDS 4 5 DATA SEGMENT PARA 6 LEN EQU 7 7 N EQU 10 ;TIMES OF LOOP 8 X DW 7 9 Y DW 8 10 Z DW ? 11 STRING1 DB 'QIQI',20H,0,'$' 12 STRING2 DB 'CHEN',20H,0,'$' 13 CHAR DB 'C' 14 OP DB ? 15 NL DB 13,10,'$' 16 MSGEQ DB 'STRING1=STRING2',13,10,'$' 17 MSGGT DB 'STRING1>STRING2',13,10,'$' 18 MSGLT DB 'STRING1<STRING2',13,10,'$' 19 DOFOUND DB 'CHAR FOUND IN STRING2',13,10,'$' 20 NOTFOUND DB 'CHAR NOT FOUND IN STRING2',13,10,'$' 21 DATA ENDS 22 23 CODE SEGMENT PARA 24 ASSUME CS:CODE,DS:DATA,SS:STACK 25 ;PRINT A NEWLINE 26 NEWLINE MACRO 27 PUSH DX 28 PUSH AX 29 MOV DX,OFFSET NL 30 MOV AH,9 31 INT 21H 32 POP AX 33 POP DX 34 ENDM 35 ;GET OPERATION TO OP 36 GETOP MACRO 37 GETOPM: 38 MOV AH,1 39 INT 21H 40 MOV OP,AL 41 ENDM 42 ;OUTPUT MSG 43 OUTPUT MACRO MSG 44 PUSH DX 45 PUSH AX 46 MOV DX,OFFSET MSG 47 MOV AH,9 48 INT 21H 49 POP AX 50 POP DX 51 ENDM 52 ;DISPLAY VALUE IN AX 53 DISP_VALUE PROC 54 DISPLAY: 55 PUSH DX 56 PUSH CX 57 PUSH BX 58 PUSH AX 59 60 MOV CX,5 61 MOV BX,10 62 63 DLP1: 64 XOR DX,DX 65 DIV BX 66 PUSH DX 67 LOOP DLP1 68 69 MOV BX,0 70 MOV CX,5 71 DLP2: 72 POP DX 73 CMP DL,0 74 JNZ DLP2_1 75 CMP BX,0 76 JZ DLP2_2 77 DLP2_1: 78 MOV BX,1 79 OR DL,30H 80 MOV AH,2 81 INT 21H 82 DLP2_2: 83 LOOP DLP2 84 85 NEWLINE 86 POP AX 87 POP BX 88 POP CX 89 POP DX 90 RET 91 DISP_VALUE ENDP 92 93 DISP_STR2 PROC 94 PRINTSTR2: 95 PUSH DX 96 MOV DX,OFFSET STRING2 97 MOV AH,9 98 INT 21H 99 NEWLINE 100 POP DX 101 RET 102 DISP_STR2 ENDP 103 104 MULTIPLE PROC 105 MULTI: 106 PUSH BP 107 MOV BP,SP 108 PUSH AX 109 PUSH BX 110 111 MOV AX,[BP+4] 112 MOV BX,[BP+6] 113 MUL BX 114 MOV Z,AX 115 116 POP BX 117 POP AX 118 POP BP 119 120 RET 4 121 MULTIPLE ENDP 122 123 MULTIPLE2 PROC 124 MULTI2: 125 MUL BX 126 MOV Z,AX 127 RET 128 MULTIPLE2 ENDP 129 130 STRCPY PROC 131 STRCPYPROC: 132 PUSH BP 133 MOV BP,SP 134 135 PUSH DI 136 PUSH SI 137 MOV SI,[BP+4] 138 MOV DI,[BP+6] 139 140 CLD 141 CPYLP: 142 LODSB 143 STOSB 144 CMP AL,0 145 JNZ CPYLP 146 POP SI 147 POP DI 148 POP BP 149 RET 4 150 STRCPY ENDP 151 152 STRCPY2 PROC 153 STRCPY2PROC: 154 CLD 155 CPYLP2: 156 LODSB 157 STOSB 158 CMP AL,0 159 JNZ CPYLP2 160 RET 161 STRCPY2 ENDP 162 163 STRCMP PROC 164 STRCMPROC: 165 PUSH BP 166 MOV BP,SP 167 168 PUSH DI 169 PUSH SI 170 171 MOV SI,[BP+4] 172 MOV DI,[BP+6] 173 CALL STRCMP2 174 175 POP SI 176 POP DI 177 POP BP 178 RET 4 179 STRCMP ENDP 180 181 STRCMP2 PROC 182 STRCMP2PROC: 183 PUSH CX 184 PUSH SI 185 CLD 186 PUSH SI 187 MOV CX,1 188 CMPLP2: 189 LODSB 190 CMP AL,0 191 JZ CMPLPBEG2 192 INC CX 193 JMP SHORT CMPLP2 194 CMPLPBEG2: 195 POP SI 196 REPE CMPSB 197 JA L2_1 198 JB L2_2 199 OUTPUT MSGEQ 200 JMP SHORT CMPRET2 201 L2_1: 202 OUTPUT MSGGT 203 JMP SHORT CMPRET2 204 L2_2: 205 OUTPUT MSGLT 206 CMPRET2: 207 POP SI 208 POP CX 209 RET 210 STRCMP2 ENDP 211 212 FIND PROC 213 FINDCHAR: 214 PUSH BP 215 MOV BP,SP 216 PUSH CX 217 218 MOV DI,[BP+6] 219 MOV CX,LEN 220 DEC CX 221 MOV AX,[BP+4] 222 CLD 223 REPNZ SCASB 224 JZ FOUND 225 OUTPUT NOTFOUND 226 JMP SHORT FIND_RETURN 227 FOUND: 228 OUTPUT DOFOUND 229 FIND_RETURN: 230 POP CX 231 POP BP 232 RET 4 233 FIND ENDP 234 235 FIND2 PROC 236 FIND2PROC: 237 PUSH CX 238 PUSH DI 239 MOV CX,LEN 240 DEC CX 241 CLD 242 REPNZ SCASB 243 JZ FOUND2 244 OUTPUT NOTFOUND 245 JMP SHORT FIND2RETURN 246 FOUND2: 247 OUTPUT DOFOUND 248 FIND2RETURN: 249 POP DI 250 POP CX 251 RET 252 FIND2 ENDP 253 254 TOLOWER PROC 255 TOLOW: 256 PUSH BP 257 MOV BP,SP 258 PUSH SI 259 PUSH DI 260 PUSH CX 261 PUSH AX 262 263 MOV SI,[BP + 4] 264 MOV DI,SI 265 MOV CX,LEN 266 CLD 267 TOLOW_LP: 268 LODSB 269 CMP AL,'A' 270 JB TOLOW_CONTINUE 271 CMP AL,'Z' 272 JA TOLOW_CONTINUE 273 ADD AL,20H 274 TOLOW_CONTINUE: 275 STOSB 276 LOOP TOLOW_LP 277 278 POP AX 279 POP CX 280 POP DI 281 POP SI 282 POP BP 283 RET 2 284 TOLOWER ENDP 285 286 TOLOWER2 PROC 287 TOLOW2: 288 PUSH SI 289 PUSH DI 290 PUSH CX 291 PUSH AX 292 MOV DI,SI 293 MOV CX,LEN 294 DEC CX 295 CLD 296 TOLOW_LP2: 297 LODSB 298 CMP AL,'A' 299 JB TOLOW_CONTINUE2 300 CMP AL,'Z' 301 JA TOLOW_CONTINUE2 302 ADD AL,20H 303 TOLOW_CONTINUE2: 304 STOSB 305 LOOP TOLOW_LP2 306 POP AX 307 POP CX 308 POP DI 309 POP SI 310 RET 311 TOLOWER2 ENDP 312 313 SWITCH PROC 314 SWITCHPROC: 315 PUSH CX 316 S0: 317 CMP OP,'A' 318 JNE S1 319 PUSH X 320 PUSH Y 321 CALL MULTIPLE 322 MOV AX,Z 323 CALL DISP_VALUE 324 JMP CONTINUE 325 S1: 326 CMP OP,'B' 327 JNE S2 328 MOV DX,OFFSET STRING2 329 PUSH DX 330 MOV DX,OFFSET STRING1 331 PUSH DX 332 CALL STRCPY 333 OUTPUT STRING2 334 NEWLINE 335 JMP CONTINUE 336 S2: 337 CMP OP,'C' 338 JNE S3 339 MOV DX,OFFSET STRING2 340 PUSH DX 341 MOV DX,OFFSET STRING1 342 PUSH DX 343 CALL STRCMP 344 JMP CONTINUE 345 S3: 346 CMP OP,'D' 347 JNE S4 348 MOV DX,OFFSET STRING2 349 PUSH DX 350 MOV DL,CHAR 351 XOR DH,DH 352 PUSH DX 353 CALL FIND 354 JMP CONTINUE 355 S4: 356 CMP OP,'E' 357 JNE S5 358 MOV DX,OFFSET STRING1 359 PUSH DX 360 CALL TOLOWER 361 OUTPUT STRING1 362 NEWLINE 363 JMP CONTINUE 364 S5: 365 CMP OP,'a' 366 JNE S6 367 MOV AX,X 368 MOV BX,Y 369 CALL MULTIPLE2 370 MOV AX,Z 371 CALL DISP_VALUE 372 JMP CONTINUE 373 S6: 374 CMP OP,'b' 375 JNE S7 376 MOV SI,OFFSET STRING1 377 MOV DI,OFFSET STRING2 378 CALL STRCPY2 379 OUTPUT STRING2 380 NEWLINE 381 JMP CONTINUE 382 S7: 383 CMP OP,'c' 384 JNE S8 385 MOV SI,OFFSET STRING1 386 MOV DI,OFFSET STRING2 387 CALL STRCMP2 388 JMP CONTINUE 389 S8: 390 CMP OP,'d' 391 JNE S9 392 MOV DI,OFFSET STRING2 393 MOV AL,CHAR 394 CALL FIND2 395 JMP CONTINUE 396 S9: 397 CMP OP,'e' 398 JNE CONTINUE 399 MOV SI,OFFSET STRING2 400 CALL TOLOWER2 401 OUTPUT STRING2 402 NEWLINE 403 CONTINUE: 404 POP CX 405 RET 406 SWITCH ENDP 407 408 MAIN PROC FAR 409 MAINPROC: 410 MOV AX,DATA 411 MOV DS,AX 412 MOV ES,AX 413 414 MOV CX,N 415 MAINLOOP: 416 GETOP 417 NEWLINE 418 CALL SWITCH 419 LOOP MAINLOOP 420 421 EXIT: 422 MOV AX,4C00H 423 INT 21H 424 MAIN ENDP 425 426 CODE ENDS 427 END MAIN
2-5 运行结果
为了验证程序符合预期,需要设计以下样例进行测试。设置循环次数为10次
设置数据区如下:
数据分别表示
-
LEN 字符串长
-
N 外循环次数
-
X,Y,Z 执行A/a操作时的乘数和结果
-
STRING1,STRING2 待操作的两个字符串
-
CHAR 待寻找的字符串
-
OP 读入的操作指令符
-
NL 回车换行标志
-
MSGEQ,MSGGT,MSGLT,DOFOUND,NOTFOUND 输出提示信息
运行程序,得到如下结果
显然,运行结果符合预期。