x86汇编语言实践(4)

0 写在前面

  为了更深刻的了解程序的实现原理,近期我学习了IBM-PC相关原理,并手工编写了一些x86汇编程序。html

  在2017年的计算机组成原理中,曾对MIPS体系结构及其汇编语言有过必定的了解,考虑到x86体系结构在目前的普遍应用,我经过两个月左右的时间对x86的相关内容进行了学习。git

  在《x86汇编语言实践》系列中(包括本篇、x86汇编语言实践(1)x86汇编语言实践(2)x86汇编语言实践(3)以及x86汇编语言复习笔记),我经过几个具体案例对x86汇编语言进行实践操做,并记录了本身再编写汇编代码中遇到的困难和心得体会,与各位学习x86汇编的朋友共同分享。github

  我将我编写的一些汇编代码放到了github上,感兴趣的朋友能够点击屏幕左上角的小猫咪进入个人github,或请点击这里下载源代码。学习

1 程序设计复习1

1-1 练习要点

  • 字符串中查找指定字符url

  • 字符串中删除指定字符(使用快慢指针spa

  • 子程序调用的堆栈参数传递.net

1-2 实现思路

  • 在数据段存储好待查找的CHAR,和目标字符串STR1,并将两者初始化设计

  • 主程序中首先将CHAR和STR1压栈3d

  • 调用FIND_CH子程序查找是否有CHAR指针

  • 若找到CHAR则调用DELX删除STR1中的X

  • 为了在STR1的原内存空间上操做字符串的修改动做,采用快慢指针的方式进行删除。

1-3 重点难点

  • 参数传递:使用堆栈进行参数传递,须要将参数压栈,注意子程序返回时,必须增长一个常数偏移量RET X。这里的X为压入参数所占的字节数,一般为2的倍数,以保证堆栈平衡

  • 子程序保存现场:在子程序中,每每要用到不少寄存器,但咱们但愿在子程序返回时,调用子程序位置处周围的变量仍能恢复,这就须要在调用的子程序中保存现场,即子程序中所用到或修改的全部寄存器,都必须压栈处理

  • 子程序中的堆栈寻址:使用BP寄存器寻址,这是为了避免修改SP指针,避免弄乱堆栈栈顶指针SP

  • 快慢指针:与高级语言程序设计中的思路相似,首先将快慢指针指向STR1的头部,以后循环STR1的长度LEN次,若快指针SI指向的位置的字符不为CHAR,则将SI复制到慢指针DI,不然只将SI++。这里用到的技巧是可使用LODSB和STOSB自动实现快指针SI与慢指针DI的自增操做

1-4 代码实现

 1 STACK SEGMENT PARA STACK  2  DW 100H DUP(?)  3 STACK ENDS  4 
 5 DATA SEGMENT PARA  6     LEN        EQU 12
 7     CHAR     DB     'X'
 8     STR1     DB     'CHENQIXIAN',13,10,'$'
 9     MSG1     DB     'X IN CHENQIXIAN',13,10,'$'
 10     MSG2    DB     'NOT FOUND X IN CHENQIXIAN',13,10,'$'
 11  STR2 DB LEN DUP(?)  12 DATA ENDS  13 
 14 CODE SEGMENT PARA  15         ASSUME    CS:CODE,DS:DATA,SS:STACK  16 OUTPUT MACRO MSG  17         PUSH AX  18         PUSH DX  19         MOV DX,OFFSET MSG  20         MOV     AH,9
 21         INT 21H  22         POP DX  23         POP AX  24  ENDM  25 
 26 DELX PROC  27     DELETEX:
 28         PUSH BP  29         MOV BP,SP  30         PUSH SI  31         PUSH DI  32         PUSH CX  33         PUSH AX  34 
 35         MOV     SI,[BP+4]  36         MOV     DI,[BP+6]  37         MOV CX,LEN  38 
 39     DELX_LP:
 40         LODSB
 41         CMP     AL,'X'
 42         JE DELX_CONTINUE  43         STOSB
 44     DELX_CONTINUE:    
 45  LOOP DELX_LP  46 
 47     DELETEXRET:
 48         MOV     AL,'$'
 49         STOSB
 50         POP AX  51         POP CX  52         POP DI  53         POP SI  54         POP BP  55         RET     4
 56 DELX ENDP  57 
 58 FIND_CH PROC  59     FINDCHAR:
 60         PUSH BP  61         MOV BP,SP  62         PUSH AX  63         PUSH DI  64         PUSH CX  65 
 66         MOV         AX,[BP+4]  67         MOV     DI,[BP+6]  68         MOV CX,LEN  69 
 70         CLD
 71         REPNZ     SCASB
 72         JZ FOUND  73  OUTPUT MSG2  74         JMP SHORT FINDCHARRET  75     FOUND:
 76  OUTPUT MSG1  77         MOV DX,OFFSET STR1  78         PUSH DX  79         PUSH DX  80         CALL DELX  81     FINDCHARRET:
 82         POP CX  83         POP DI  84         POP AX  85         POP BP  86         RET     4
 87 FIND_CH ENDP  88 
 89 MAIN PROC FAR  90     MAINPROC:
 91         MOV AX,DATA  92         MOV DS,AX  93         MOV ES,AX  94 
 95         MOV DX,OFFSET STR1  96         PUSH DX  97         MOV DL,CHAR  98         XOR DH,DH  99         PUSH DX 100         CALL FIND_CH 101 
102     EXIT:    
103         MOV AX,4C00H 104         INT 21H 105 MAIN ENDP 106 
107 CODE ENDS 108         END     MAIN

1-5 实现效果截图

1-5-1 程序运行结果

 

经验证,发现输出结果符合预期

1-5-2 查看删除后内存中新的字符串

 

经验证,发现内存中的结果符合预期

2 程序设计复习2

2-1 练习要点

  • 字符的输入输出

  • 数字读入存储逻辑

  • 数字的最优输出方式

2-2 实现思路

  • 首先为读入字符和输出数字分别单独编写子程序

  • 主程序中循环调用读入字符,因为题目固定读入两位十进制数,所以读入的第一个数乘10加上第二个读入的数,即为读入的数字

  • 在输出上的改进:还是除10显示,但此次保存余数。为了获得正序输出,将每次的余数压栈,这样在显示的时候就是从高位向低位显示了。此外,在输出时对前导0进行了过滤处理,须要注意的是当遇到第一个非0数字后,须要将标志位置1,这样之后的数字0就能够正常显示

2-3 代码实现

 1 STACK SEGMENT PARA STACK  2  DW 100H DUP(?)  3 STACK ENDS  4 
 5 DATA SEGMENT PARA  6     LEN EQU 5
 7     X     DB     0
 8     Y     DB     0
 9  Z DB ?  10     NL     DB    13,10,'$'
 11 DATA ENDS  12 
 13 CODE SEGMENT PARA  14         ASSUME    CS:CODE,DS:DATA,SS:STACK  15 NEWLINE MACRO  16         PUSH AX  17         PUSH DX  18         MOV DX,OFFSET NL  19         MOV     AH,9
 20         INT 21H  21         POP DX  22         POP AX  23  ENDM  24 GETNUM PROC  25     INPUT:
 26         MOV     AH,1
 27         INT 21H  28         SUB AL,30H  29         XOR AH,AH  30         RET
 31 GETNUM ENDP  32 
 33 OUTPUT PROC  34     PRINT:
 35         PUSH DX  36         PUSH CX  37         PUSH BX  38         PUSH AX  39  NEWLINE  40         MOV CX,LEN  41         MOV     BX,10
 42     PRINT_LP1:
 43         XOR DX,DX  44         DIV BX  45         PUSH DX  46  LOOP PRINT_LP1  47 
 48         MOV CX,LEN  49         MOV     BX,0
 50     PRINT_LP2:
 51         POP DX  52         CMP     DL,0
 53         JNE PRINT_LP2_1  54         CMP        BX,0
 55         JZ PRINT_LP2_2  56     PRINT_LP2_1:
 57         MOV     BX,1
 58         MOV     AH,2
 59         OR DL,30H  60         INT 21H  61         
 62     PRINT_LP2_2:
 63  LOOP PRINT_LP2  64         POP AX  65         POP BX  66         POP CX  67         POP DX  68         RET
 69 OUTPUT ENDP  70 
 71 
 72 MAIN PROC FAR  73     MAINPROC:
 74         MOV AX,DATA  75         MOV DS,AX  76         MOV ES,AX  77 
 78         CALL GETNUM  79         MOV     BL,10
 80         MUL BL  81         MOV X,AL  82         CALL GETNUM  83         ADD X,AL  84 
 85         CALL GETNUM  86         MOV        BL,10
 87         MUL BL  88         MOV Y,AL  89         CALL GETNUM  90         ADD Y,AL  91 
 92         MOV AL,X  93         MOV BL,Y  94         MUL BL  95 
 96         CALL OUTPUT  97 
 98     EXIT:    
 99         MOV AX,4C00H 100         INT 21H 101 MAIN ENDP 102 
103 CODE ENDS 104         END     MAIN

2-4 运行结果

   

显然,运行结果符合预期。

3 程序设计复习3

3-1 练习要点

  • 字符串读取:0AH号中断调用

  • 字符串拷贝

  • 子程序调用参数的传递与保持

3-2 实现思路

  • 首先为读入字符串和输出字符串分别单独编写子程序

  • 输入待插入字符串后,首先调用第一次拷贝字符串子程序,判断条件为读取到空格即中止拷贝。注意边界条件的判断,以及最后一次拷贝后SI与DI的保持

  • 紧接着在主程序中将SI压栈保存,将SI指向待插入字符串首地址,调用插入子程序。将待插入字符串拼接到目标串尾部

  • 最后将SI弹出栈恢复,即又指向原列表空格后的第一个字符的位置处,调用第二次拷贝字符串子程序。此时边界判断条件为’$’符号

  • 输出目标串

3-3 代码实现

 1 STACK SEGMENT PARA STACK  2  DW 100H DUP(?)  3 STACK ENDS  4 
 5 DATA SEGMENT PARA  6     LEN     EQU    32
 7     LIST    DB    'ABOVE ZEBRA$'
 8  TEMP DB LEN DUP(?)  9     NL         DB     13,10,'$'
 10     STR1    DB    LEN-1
 11  DB ?  12  DB LEN DUP(?)  13 DATA ENDS  14 
 15 CODE SEGMENT PARA  16         ASSUME    CS:CODE,DS:DATA,SS:STACK  17 NEWLINE MACRO  18         PUSH DX  19         PUSH AX  20         MOV DX,OFFSET NL  21         MOV     AH,9
 22         INT 21H  23         POP AX  24         POP DX  25  ENDM  26 
 27 OUTPUT MACRO MSG  28         PUSH DX  29         PUSH AX  30  NEWLINE  31         MOV DX,OFFSET MSG  32         MOV     AH,9
 33         INT 21H  34         POP AX  35         POP DX  36  ENDM  37 
 38 INPUT PROC  39     INPUTSTR1:
 40         PUSH DX  41         PUSH AX  42         PUSH SI  43 
 44         MOV DX,OFFSET STR1  45         MOV AH,0AH  46         INT 21H  47         MOV     SI,OFFSET STR1+2
 48         MOV     AL,STR1+1
 49         XOR AH,AH  50         ADD SI,AX  51         MOV     BYTE PTR [SI],'$'
 52 
 53         POP SI  54         POP AX  55         POP DX  56         RET
 57 INPUT ENDP  58 
 59 COPY PROC  60     STRCPY:
 61         LODSB
 62         CMP AL,20H  63         JE COPYRET  64         STOSB
 65         JMP STRCPY  66     COPYRET:
 67         STOSB
 68         RET
 69 COPY ENDP  70 
 71 INSERT PROC  72     INSERT_STR1:
 73         MOV     CL,STR1+1
 74         XOR CH,CH  75     INSERT_LP:
 76         LODSB
 77         STOSB
 78  LOOP INSERT_LP  79         MOV AL,20H  80         STOSB
 81         RET
 82 INSERT ENDP  83 
 84 COPY2 PROC  85     STRCPY2:
 86         LODSB
 87         CMP     AL,'$'
 88         JE COPYRET2  89         STOSB
 90         JMP STRCPY2  91     COPYRET2:
 92         STOSB
 93         RET
 94 COPY2 ENDP  95 
 96 MAIN PROC FAR  97     MAINPROC:
 98         MOV AX,DATA  99         MOV DS,AX 100         MOV ES,AX 101  OUTPUT LIST 102         CALL INPUT 103         MOV SI,OFFSET LIST 104         MOV DI,OFFSET TEMP 105         CALL COPY 106         PUSH SI 107         MOV     SI,OFFSET STR1+2
108         CALL INSERT 109         POP SI 110         CALL COPY2 111  OUTPUT TEMP 112 
113     EXIT:    
114         MOV AX,4C00H 115         INT 21H 116 MAIN ENDP 117 
118 CODE ENDS 119         END     MAIN

3-4 运行结果

 

4 程序设计复习4

4-1 练习要点

  • 16进制输出方式

  • 从10向A的转化

  • 2号中断调用输出单个字符

4-2 实现思路

  • 首先在数据段初始化一个64位数字

  • 注意因为一个字是2个字节16位,所以在输出时,要依次在基地址的基础上+2

  • 因为是循环访问数据段中的除数,所以用SI寄存器记录数据段中除数的位置,每次循环都要使用两次INC指令,保证访问到下一个字中的内容。

  • 访问除数必须用WORD PTR [SI],不然会提示 ’must have size’

  • 判断16进制输出的数字是否大于10,若不大于则直接输出,不然须要加7(在ASCII数值上‘9’和‘A’之间差8),注意从数字转换为ASCII码此处必须用ADD 30H 来代替 OR 30H,不然会出现错误。

4-3 代码实现

 1 STACK SEGMENT PARA STACK  2  DW 100H DUP(?)  3 STACK ENDS  4 
 5 DATA SEGMENT PARA  6  NUM DW 1606H,1160H,1234H,0FFFFH  7  DIVISOR DW 1000H,100H,10H,1H  8 DATA ENDS  9 
10 CODE SEGMENT PARA 11         ASSUME    CS:CODE,DS:DATA,SS:STACK 12 
13 OUTPUT PROC 14     PRINT:
15         MOV SI,OFFSET DIVISOR 16         MOV     CX,4
17     OUTPUT_LP:
18         XOR DX,DX 19         DIV WORD PTR [SI] 20         PUSH DX 21         CMP     AL,10
22         JB OUTPUT_CONTINUE 23         ADD     AL,7
24     OUTPUT_CONTINUE:
25         ADD AL,30H 26         MOV DL,AL 27         MOV     AH,2
28         INT 21H 29         INC SI 30         INC SI 31         POP AX 32  LOOP OUTPUT_LP 33         RET
34 OUTPUT ENDP 35 
36 MAIN PROC FAR 37     MAINPROC:
38         MOV AX,DATA 39         MOV DS,AX 40         MOV ES,AX 41 
42         MOV AX,NUM 43         CALL OUTPUT 44 
45         MOV     AX,NUM+2
46         CALL OUTPUT 47 
48         MOV     AX,NUM+4
49         CALL OUTPUT 50 
51         MOV     AX,NUM+6
52         CALL OUTPUT 53 
54     EXIT:    
55         MOV AX,4C00H 56         INT 21H 57 MAIN ENDP 58 
59 CODE ENDS 60         END     MAIN

4-4 运行结果

【数据段】

 

【运行结果】

 

相关文章
相关标签/搜索