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 代码实现
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 输出提示信息
运行程序,得到如下结果
显然,运行结果符合预期。
来源:oschina
链接:https://my.oschina.net/u/4297712/blog/3559775