├── README.md ├── software ├── DEBUG.EXE ├── DOSBox0.74-2-win32-installer.exe ├── EDIT.COM ├── EXE2BIN.EXE ├── LIB.EXE ├── LINK.EXE ├── MASM.EXE ├── TC2.0.rar └── masm.IMg ├── test ├── course_design_1.asm ├── test10_1.asm ├── test10_2.asm ├── test10_3.asm ├── test11.asm ├── test12.asm ├── test2.asm ├── test3.asm ├── test4_1.asm ├── test4_2.asm ├── test4_3.asm ├── test5_1.asm ├── test5_2.asm ├── test5_3.asm ├── test5_4.asm ├── test5_5.asm ├── test5_6.asm ├── test6_1.asm ├── test6_2.asm ├── test6_3.asm ├── test6_4.asm ├── test6_5.asm ├── test6_6.asm ├── test7.asm ├── test8.asm └── test9.asm ├── 《汇编语言》第三版检测点答案.md ├── 《汇编语言》第三版阅读笔记.md └── 参考资料 ├── 8086Registers.pdf └── DEBUG command - Chinese ver.pdf /README.md: -------------------------------------------------------------------------------- 1 | # AssemblyLanguageTest 2 | 王爽《汇编语言》第三版学习笔记。包括读书笔记、课后实验及检测点答案。 3 | 4 | 内容为个人整理所得,如有纰漏欢迎提出宝贵意见。 5 | -------------------------------------------------------------------------------- /software/DEBUG.EXE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanmianti/AssemblyLanguageTest/8265babc5041e81b0f5435619c87fadbeb867f98/software/DEBUG.EXE -------------------------------------------------------------------------------- /software/DOSBox0.74-2-win32-installer.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanmianti/AssemblyLanguageTest/8265babc5041e81b0f5435619c87fadbeb867f98/software/DOSBox0.74-2-win32-installer.exe -------------------------------------------------------------------------------- /software/EDIT.COM: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanmianti/AssemblyLanguageTest/8265babc5041e81b0f5435619c87fadbeb867f98/software/EDIT.COM -------------------------------------------------------------------------------- /software/EXE2BIN.EXE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanmianti/AssemblyLanguageTest/8265babc5041e81b0f5435619c87fadbeb867f98/software/EXE2BIN.EXE -------------------------------------------------------------------------------- /software/LIB.EXE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanmianti/AssemblyLanguageTest/8265babc5041e81b0f5435619c87fadbeb867f98/software/LIB.EXE -------------------------------------------------------------------------------- /software/LINK.EXE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanmianti/AssemblyLanguageTest/8265babc5041e81b0f5435619c87fadbeb867f98/software/LINK.EXE -------------------------------------------------------------------------------- /software/MASM.EXE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanmianti/AssemblyLanguageTest/8265babc5041e81b0f5435619c87fadbeb867f98/software/MASM.EXE -------------------------------------------------------------------------------- /software/TC2.0.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanmianti/AssemblyLanguageTest/8265babc5041e81b0f5435619c87fadbeb867f98/software/TC2.0.rar -------------------------------------------------------------------------------- /software/masm.IMg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanmianti/AssemblyLanguageTest/8265babc5041e81b0f5435619c87fadbeb867f98/software/masm.IMg -------------------------------------------------------------------------------- /test/course_design_1.asm: -------------------------------------------------------------------------------- 1 | ;课程设计1 2 | ;将实验7中的Power idea 公司的数据按照图10.2所示的格式在屏幕上显示出来 3 | ; 4 | ;思路 5 | ;寻址方式 6 | ;DS:[0]:[SI]指向原始数据年份 7 | ;DS:[84]:[DI]指向原始数据收入 8 | ;DS:[84+84][BX]指向原始数据雇员人数 9 | ;DS:[0+84+84+42]指向临时护具存储区 10 | ;ES:[BP]指向目标表格每行首地址 11 | ; 12 | ; 13 | ;年份的显示 14 | ;年份原始数据即是字符本身的ASCII码,不用做变换,直接复制到显存即可。 15 | ;DS:[0]:[0] → ES:[0][0] ;复制'19' 16 | ;DS:[0]:[2] → ES:[0][2] ;复制'75' 17 | ; 18 | ;收入的显示 19 | ;收入数每位要先转换为对应数符的ASCII码,然后在复制到显存 20 | ; 21 | ; 22 | ;雇员人数的显示 23 | ;雇员人数每位同样要先转换为对应数符的ASCII吗,然后在复制到显存 24 | ; 25 | ;人均收入的显示 26 | ;人均收入原始数据未给出。所以要通过收入/人数的方式计算人均收入。 27 | ;计算人均收入后将收入每位转换为数符对应的ASCII码,然后复制到显存。 28 | ; 29 | assume cs:codesg, ds:datasg, ss:stacksg 30 | 31 | ;原始数据存储区 32 | datasg segment 33 | ;Power idea公司原始数据 34 | ;定义年份,占84个字节 35 | db '1975', '1976', '1977', '1978', '1979', '1980', '1981', '1982' 36 | db '1983', '1984', '1985', '1986', '1987', '1988', '1989', '1990' 37 | db '1991', '1992', '1993', '1994', '1995' 38 | ;定义收入, 双字型,占84个字节 39 | dd 16, 22, 382, 1356, 2390, 8000, 16000, 24486 40 | dd 50065, 97479, 140417, 197514, 345980, 590827, 803530, 1183000 41 | dd 1843000, 2759000, 3753000, 4649000, 5937000 42 | ;定义雇员人数,单字型,占42个字节 43 | dw 3, 7, 9, 13, 28, 38, 130, 220 44 | dw 476, 778, 1001, 1442, 2258, 2793, 4037,5635 45 | dw 8226, 11542, 14430, 15257, 17800 46 | ;额外开辟16个字节的临时数据存储区 47 | dw 8 dup (0) 48 | datasg ends 49 | 50 | ;栈空间 51 | stacksg segment 52 | dw 32 dup (0) 53 | stacksg ends 54 | 55 | codesg segment 56 | start: 57 | ;初始化原始数据区 58 | mov ax, datasg 59 | mov ds, ax 60 | 61 | ;初始化栈区 62 | mov ax, stacksg 63 | mov ss, ax 64 | mov sp, 64 65 | 66 | mov cx, 21 ;循环次数 67 | mov al, 2 ;行号 68 | mov si, 0 ;年份索引号 69 | mov di, 84 ;收入索引号 70 | mov bx, [84+84] ;雇员人数索引号 71 | loop1: 72 | 73 | call short show_year 74 | call short show_income 75 | call short show_people_num 76 | call short show_average_income 77 | 78 | add si, 4 ;年份索引+4,指向下一年 79 | add di, 4 ;收入索引+4,指向下一年收入 80 | add bx, 2 ;人数索引+2,指向下一年雇员人数 81 | inc al ;行号+1,指向下一行 82 | 83 | loop loop1 84 | 85 | mov ax, 4c00h 86 | int 21h 87 | 88 | 89 | ;=======================计算人均收入并展示======================== 90 | ;子程序描述 91 | ;名称: 92 | ; show_average_income 93 | ;功能: 94 | ; 根据历年总收入及雇员人数计算平均收入,并显示在屏幕上。 95 | ; 因为在主函数中loop循环对IP的修改限制在 -128~127之间。为了避免该限制 96 | ; 将这一部分代码构造一个子程序。 97 | ;参数: 98 | ; (di)= 总收入索引 99 | ; (bx)= 雇员人数索引 100 | ; (al)= 结果展示行号 101 | ;返回值: 102 | ; 无 103 | show_average_income: 104 | 105 | push dx 106 | push ax 107 | push cx 108 | push si 109 | 110 | ;计算人均收入并展示 111 | ;计算人均收入 112 | push ax ;备份ax 113 | mov dx, ds:[di+2] ;收入高16位 114 | mov ax, ds:[di] ;收入低16位 115 | mov cx, ds:[bx] ;人数 116 | call short divdw ;除法运算,计算人均收入 117 | 118 | ;将人均收入数符转换为字符 119 | mov si, 210 120 | call short dtoc 121 | ;显示人均收入 122 | pop ax 123 | mov dh, al 124 | mov dl, 25 125 | mov cl, 2 126 | mov si, 210 127 | call short show_str 128 | 129 | pop si 130 | pop cx 131 | pop ax 132 | pop dx 133 | 134 | ret 135 | ;================================================================= 136 | 137 | ;=======================展示雇员人数============================== 138 | ;子程序描述 139 | ;名称: 140 | ; show_people_num 141 | ;功能: 142 | ; 展示雇员人数 143 | ;参数: 144 | ; (si)= 雇员人数索引 145 | ;返回值: 146 | ; 无 147 | show_people_num: 148 | 149 | ;雇员人数数符转换为对应十进制字符 150 | push dx 151 | push ax 152 | push si 153 | 154 | mov dx, 0 155 | mov ax, ds:[bx] 156 | mov si, 210 157 | call short dtoc 158 | pop si 159 | pop ax 160 | pop dx 161 | ;显示雇员人数 162 | push dx 163 | push cx 164 | push si 165 | mov dh, al 166 | mov dl, 18 167 | mov cl, 2 168 | mov si, 210 169 | call short show_str 170 | 171 | pop si 172 | pop cx 173 | pop dx 174 | 175 | 176 | ret 177 | ;================================================================= 178 | ;=======================展示收入================================== 179 | ;子程序描述 180 | ;名称: 181 | ; show_income 182 | ;功能: 183 | ; 展示收入 184 | ;参数: 185 | ; (di)= 收入索引 186 | ;返回值: 187 | ; 无 188 | show_income: 189 | 190 | ;将十进制收入数符转换为字符,存入ds:[si]处 191 | push ax 192 | push dx 193 | push si 194 | mov ax, ds:[di] 195 | mov dx, ds:[di+2] 196 | mov si, 210 197 | call short dtoc 198 | pop si 199 | pop dx 200 | pop ax 201 | ;显示字符串 202 | push dx 203 | push cx 204 | push si 205 | mov dh, al ;行号 206 | mov dl, 7 ;列号 207 | mov cl, 2 ;字符串颜色 208 | mov si, 210 ;字符串首地址 209 | call short show_str 210 | pop si 211 | pop cx 212 | pop dx 213 | 214 | ret 215 | ;================================================================= 216 | 217 | ;=======================展示年份================================== 218 | ;子程序描述 219 | ;名称: 220 | ; show_year 221 | ;功能: 222 | ; 展示年份列表 223 | ;参数: 224 | ; (si)= 年份索引 225 | ;返回值: 226 | ; 无 227 | show_year: 228 | 229 | push si ;备份si 230 | push cx ;备份cx 231 | push dx 232 | push bx 233 | 234 | mov bx, ds:[si] ;年份前两个字节 235 | mov ds:[84+84+42], bx 236 | mov bx, ds:[si+2] ;年份后两个字节 237 | mov ds:[84+84+42].[2], bx 238 | mov byte ptr ds:[84+84+42].[4], 0 239 | 240 | mov dh, al ;行号 241 | mov dl, 0 ;列号 242 | mov cl, 2 ;字符串颜色 243 | mov si, 210 ;字符串首地址 244 | call short show_str 245 | 246 | pop bx 247 | pop dx 248 | pop cx 249 | pop si 250 | 251 | ret 252 | ;================================================================= 253 | 254 | ;=======================十进制数符转换为字符串==================== 255 | 256 | ;子程序描述 257 | ;名称: 258 | ; dtoc 259 | ;功能: 260 | ; 将word类型数据转变为表示十进制数的字符串 261 | ;参数: 262 | ; (dx)= word型数据高16位 263 | ; (ax)= word型数据低16位 264 | ; (ds:[si]) = 字符串的首地址 265 | ;返回: 266 | ; 无 267 | dtoc: 268 | 269 | ;备份寄存器 270 | push cx 271 | push si 272 | push bx 273 | 274 | ;字符串用0表示结束 275 | mov bx, 0 276 | push bx 277 | step1: 278 | ;声明参数,并调用子程序divdw 279 | mov cx, 10 280 | call divdw 281 | 282 | ;余数+30h构建成ASCII码,然后入栈暂存 283 | add cx, 30H 284 | push cx 285 | 286 | ;判断商是否为0 287 | mov bx, 0 288 | add bx, ax 289 | add bx, dx 290 | mov cx, bx 291 | jcxz step2 292 | jmp short step1 293 | 294 | step2: 295 | pop cx 296 | mov ds:[si], cl 297 | jcxz step3 298 | inc si 299 | jmp short step2 300 | step3: 301 | pop bx 302 | pop si 303 | pop cx 304 | ret 305 | ;================================================================= 306 | 307 | 308 | ;====================字符串展示子程序============================= 309 | ;子程序名称: 310 | ; show_str 311 | ;子程序功能: 312 | ; 在指定位置,用指定的颜色,显示一个用0结束的字符串 313 | ;子程序参数: 314 | ; (dh)= 行号(取值范围0~24) 315 | ; (dl)= 列号(取值范围0~79) 316 | ; (cl)= 颜色 317 | ; ds:[si]= 字符串的首地址。 318 | ;子程序返回值: 319 | ; 无 320 | show_str: 321 | push ax 322 | push cx 323 | push dx 324 | push es 325 | push si 326 | push bp 327 | 328 | ;使用es:bp指向目标显存首地址 329 | mov ax, 0b800h 330 | mov es, ax 331 | 332 | ;计算bp的初值 333 | mov ax, 160 334 | mul dh 335 | mov dh, 0 336 | add ax, dx 337 | add ax, dx 338 | mov bp, ax 339 | 340 | copy: 341 | ;复制一个字母 342 | mov al, ds:[si] 343 | ;判断是否是字符串尾部,此处要用到cx,因为cx已被使用,所以要先备份cx 344 | mov dx, cx 345 | mov ch, 0 346 | mov cl, al 347 | jcxz ok 348 | mov cx, dx 349 | ;粘贴一个字母 350 | mov byte ptr es:[bp],al 351 | ;粘贴字母属性 352 | mov byte ptr es:[bp+1], cl 353 | add si, 1 354 | add bp, 2 355 | jmp short copy 356 | 357 | ok: 358 | pop bp 359 | pop si 360 | pop es 361 | pop dx 362 | pop cx 363 | pop ax 364 | 365 | ret 366 | 367 | ;================================================================= 368 | 369 | 370 | ;======================非溢出除法运算子程序======================= 371 | ;子程序描述 372 | ;名称: 373 | ; divdw 374 | ;功能: 375 | ; 进行不会产生溢出的除法运算,被除数为双字(dword)型, 376 | ; 除数为单字(word)型,商为双字型,余数为单字型。 377 | ;参数: 378 | ; (dx) = 被除数的高16位 379 | ; (ax) = 被除数的低16位 380 | ; (cx) = 除数 381 | ;返回: 382 | ; (dx) = 商的高16位 383 | ; (ax) = 商的低16位 384 | ; (cx) = 余数 385 | ;公式: 386 | ; X/N = int(H/N)*65536 + [rem(H/N)*65536 + L]/N。 387 | divdw: 388 | push bx 389 | 390 | mov bx, ax ;(bx) = L 391 | mov ax, dx ;(ax) = H 392 | mov dx, 0 ;(dx) = 0 393 | div cx ;(dx) = 0,(ax) = H, (cx) = N 394 | push ax ;(ax) = int(H/N) 395 | 396 | mov ax, bx ;(bx) = L 397 | div cx ;(dx) = rem(H/N),(ax) = L, (cx) = N 398 | mov cx, dx ;(dx) = rem{[rem(H/N)*65536 + L]/N}, (ax) = int{[rem(H/N)*65536 + L]/N} 399 | pop dx ;(ss:sp) = int(H/N) 400 | 401 | pop bx 402 | ret 403 | ;================================================================= 404 | codesg ends 405 | end start -------------------------------------------------------------------------------- /test/test10_1.asm: -------------------------------------------------------------------------------- 1 | ;实验名称:显示字符串 2 | 3 | ;应用举例:在屏幕的8行3列,用绿色显示data段中的字符串 4 | assume cs:code, ds:data, ss:stacksg 5 | data segment 6 | db 'Welcome to masm!', 0 7 | data ends 8 | 9 | stacksg segment 10 | dw 16 dup (0) 11 | stacksg ends 12 | 13 | code segment 14 | 15 | ;=======================程序入口================================== 16 | start: 17 | mov ax, stacksg 18 | mov ss, ax 19 | mov sp, 32 20 | 21 | mov dh, 8 22 | mov dl, 3 23 | mov cl, 2 24 | ;使用ds:si指向字符串的首地址 25 | mov ax, data 26 | mov ds, ax 27 | mov si, 0 28 | call show_str 29 | 30 | mov ax, 4c00h 31 | int 21h 32 | ;================================================================= 33 | 34 | ;=======================在屏幕指定位置显示字符串================== 35 | ;子程序名称: 36 | ; show_str 37 | ;子程序功能: 38 | ; 在指定位置,用指定的颜色,显示一个用0结束的字符串 39 | ;子程序参数: 40 | ; (dh)= 行号(取值范围0~24), 41 | ; (dl)= 列号(取值范围0~79) 42 | ; (cl) = 颜色, 43 | ; ds:[si] = 字符串的首地址。 44 | ;子程序返回值: 45 | ; 无 46 | show_str: 47 | 48 | push ax 49 | push cx 50 | push dx 51 | push es 52 | push si 53 | push bp 54 | 55 | ;使用es:bp指向目标显存首地址 56 | mov ax, 0b800h 57 | mov es, ax 58 | 59 | ;计算bp的初值 60 | mov al, 160 61 | mul dh 62 | mov dh, 0 63 | add ax, dx ;注意此处要加两次,因为每个字符占两个字节 64 | add ax, dx 65 | mov bp, ax 66 | 67 | copy: 68 | ;复制一个字母 69 | mov al, ds:[si] 70 | ;判断是否是字符串尾部,此处要用到cx,因为cx已被使用,所以要先备份cx 71 | mov dx, cx 72 | mov ch, 0 73 | mov cl, al 74 | jcxz ok 75 | mov cx, dx 76 | ;粘贴一个字母 77 | mov byte ptr es:[bp],al 78 | ;粘贴字母属性 79 | mov byte ptr es:[bp+1], cl 80 | add si, 1 81 | add bp, 2 82 | jmp short copy 83 | 84 | ok: 85 | pop bp 86 | pop si 87 | pop es 88 | pop dx 89 | pop cx 90 | pop ax 91 | 92 | ret 93 | ;================================================================= 94 | 95 | code ends 96 | end start -------------------------------------------------------------------------------- /test/test10_2.asm: -------------------------------------------------------------------------------- 1 | ;实验名称:解决除法溢出的问题 2 | ; 3 | ;应用举例:计算 1000000/10(F424H/01H) 4 | ;结果 (dx)=001H, (ax)= 86A0H, (cx)=0 5 | assume cs:codesg, ss:stacksg 6 | 7 | ;声明8个字的连续内存空间用做栈 8 | stacksg segment 9 | dw 8 dup (0) 10 | stacksg ends 11 | 12 | codesg segment 13 | ;=======================程序入口================================== 14 | start: 15 | 16 | ;初始化运行环境及参数 17 | ;初始化栈 18 | mov ax, stacksg 19 | mov ss, ax 20 | mov sp, 16 21 | 22 | ;声明参数并调用子程序 23 | mov dx, 0fh 24 | mov ax, 4240h 25 | mov cx, 0ah 26 | call divdw 27 | 28 | ;退出程序 29 | mov ax, 4c00h 30 | int 21h 31 | ;================================================================= 32 | 33 | ;=====================不会发生溢出的16位除法运算================== 34 | ;子程序描述 35 | ;名称:divdw 36 | ;功能:进行不会产生溢出的除法运算,被除数为双字(dword)型, 37 | ; 除数为单字(word)型,商为双字型,余数为单字型。 38 | ;参数: (dx) = 被除数的高16位 39 | ; (ax) = 被除数的低16位 40 | ; (cx) = 除数 41 | ;返回: (dx) = 商的高16位, 42 | ; (ax) = 商的低16位 43 | ; (cx) = 余数 44 | ;公式:X/N = int(H/N)*65536 + [rem(H/N)*65536 + L]/N。 45 | divdw: 46 | push bx 47 | 48 | mov bx, ax ;(bx) = L 49 | mov ax, dx ;(ax) = H 50 | mov dx, 0 ;(dx) = 0 51 | div cx ;(dx) = 0,(ax) = H, (cx) = N 52 | push ax ;(ax) = int(H/N) 53 | 54 | mov ax, bx ;(bx) = L 55 | div cx ;(dx) = rem(H/N),(ax) = L, (cx) = N 56 | mov cx, dx ;(dx) = rem{[rem(H/N)*65536 + L]/N}, (ax) = int{[rem(H/N)*65536 + L]/N} 57 | pop dx ;(ss:sp) = int(H/N) 58 | 59 | pop bx 60 | ret 61 | ;================================================================= 62 | codesg ends 63 | end start -------------------------------------------------------------------------------- /test/test10_3.asm: -------------------------------------------------------------------------------- 1 | ;实验名称: 数值显示 2 | 3 | assume cs:code 4 | ;声明10个字节的内存空间,用以存放字符串ASCII码 5 | data segment 6 | db 32 dup (0) 7 | data ends 8 | 9 | ;声明一个栈空间,用以临时备份数据 10 | stack segment 11 | dw 16 dup (0) 12 | stack ends 13 | 14 | code segment 15 | ;============================程序入口============================= 16 | start: 17 | ;初始化运行环境 18 | 19 | ;初始化栈 20 | mov bx, stack 21 | mov ss, bx 22 | mov sp, 16 23 | 24 | ;声明参数,并调用子程序dtoc 25 | mov ax, 12600 26 | mov bx, data 27 | mov ds, bx 28 | mov si, 0 29 | call dtoc 30 | 31 | ;声明参数,并调用子程序show_str 32 | mov dh, 8 33 | mov dl, 3 34 | mov cl, 2 35 | mov ax, data 36 | mov ds, ax 37 | mov si, 0 38 | call show_str 39 | 40 | mov ax, 4c00h 41 | int 21h 42 | ;================================================================= 43 | 44 | ;=======================十进制数据显示子程序====================== 45 | 46 | ;子程序描述 47 | ;名称: 48 | ; dtoc 49 | ;功能: 50 | ; 将word类型数据转变为表示十进制数的字符串 51 | ;参数: 52 | ; (ax)= word型数据 53 | ; ds:[si]=指向字符串的首地址 54 | ;返回: 55 | ; 无 56 | dtoc: 57 | ;字符串用0表示结束 58 | mov bx, 0 59 | push bx 60 | step1: 61 | ;声明参数,并调用子程序divdw 62 | mov dx, 0 63 | mov cx, 10 64 | call divdw 65 | 66 | ;余数+30h构建成ASCII码,然后入栈暂存 67 | add cx, 30H 68 | push cx 69 | 70 | ;判断商是否为0 71 | mov cx, ax 72 | jcxz step2 73 | jmp short step1 74 | 75 | step2: 76 | pop cx 77 | mov ds:[si], cl 78 | jcxz step3 79 | inc si 80 | jmp short step2 81 | step3: 82 | ret 83 | ;================================================================= 84 | 85 | ;======================非溢出除法运算子程序======================= 86 | ;子程序描述 87 | ;名称: 88 | ; divdw 89 | ;功能: 90 | ; 进行不会产生溢出的除法运算,被除数为双字(dword)型, 91 | ; 除数为单字(word)型,商为双字型,余数为单字型。 92 | ;参数: 93 | ; (dx) = 被除数的高16位 94 | ; (ax) = 被除数的低16位 95 | ; (cx) = 除数 96 | ;返回: 97 | ; (dx) = 商的高16位 98 | ; (ax) = 商的低16位 99 | ; (cx) = 余数 100 | ;公式: 101 | ; X/N = int(H/N)*65536 + [rem(H/N)*65536 + L]/N。 102 | divdw: 103 | 104 | mov bx, ax ;(bx) = L 105 | mov ax, dx ;(ax) = H 106 | mov dx, 0 ;(dx) = 0 107 | div cx ;(dx) = 0,(ax) = H, (cx) = N 108 | push ax ;(ax) = int(H/N) 109 | 110 | mov ax, bx ;(bx) = L 111 | div cx ;(dx) = rem(H/N),(ax) = L, (cx) = N 112 | mov cx, dx ;(dx) = rem{[rem(H/N)*65536 + L]/N}, (ax) = int{[rem(H/N)*65536 + L]/N} 113 | pop dx ;(ss:sp) = int(H/N) 114 | 115 | ret 116 | ;================================================================= 117 | 118 | ;====================字符串展示子程序============================= 119 | ;子程序名称: 120 | ; show_str 121 | ;子程序功能: 122 | ; 在指定位置,用指定的颜色,显示一个用0结束的字符串 123 | ;子程序参数: 124 | ; (dh)= 行号(取值范围0~24) 125 | ; (dl)= 列号(取值范围0~79) 126 | ; (cl) = 颜色,ds:si指向字符串的首地址。 127 | ;子程序返回值: 128 | ; 无 129 | show_str: 130 | ;使用es:bp指向目标显存首地址 131 | mov ax, 0b800h 132 | mov es, ax 133 | 134 | ;计算bp的初值 135 | mov ax, 160 136 | mul dh 137 | mov dh, 0 138 | add ax, dx 139 | add ax, dx 140 | mov bp, ax 141 | 142 | copy: 143 | ;复制一个字母 144 | mov al, ds:[si] 145 | ;判断是否是字符串尾部,此处要用到cx,因为cx已被使用,所以要先备份cx 146 | mov dx, cx 147 | mov ch, 0 148 | mov cl, al 149 | jcxz ok 150 | mov cx, dx 151 | ;粘贴一个字母 152 | mov byte ptr es:[bp],al 153 | ;粘贴字母属性 154 | mov byte ptr es:[bp+1], cl 155 | add si, 1 156 | add bp, 2 157 | jmp short copy 158 | ok: 159 | ret 160 | ;================================================================= 161 | 162 | code ends 163 | end start -------------------------------------------------------------------------------- /test/test11.asm: -------------------------------------------------------------------------------- 1 | ;实验11 编写子程序 2 | ; 3 | ;编写一个子程序,将包含任意字符,以0结尾的字符串中的 4 | ;小写字母转变成大写字母。 5 | ; 6 | ;a-z对应的ASCII码为97-122 7 | ;A-Z对应的ASCII码为65-90 8 | ;小写转大写:与 1101 1111B 9 | ;大写转小写:或 0010 0000B 10 | assume cs:codesg, ds:codesg 11 | datasg segment 12 | db "Beginner's All-purpose Symbolic Instruction Code.", 0 13 | datasg ends 14 | 15 | codesg segment 16 | begin: 17 | mov ax, datasg 18 | mov ds, ax 19 | mov si, 0 20 | call letterc 21 | 22 | ;打印被修改的字符串 23 | mov dh, 1 ;第一行 24 | mov dl, 0 ;第零列 25 | mov cl, 2 ;字体颜色 26 | call show_str 27 | 28 | mov ax, 4c00h 29 | int 21h 30 | 31 | ;==========将字符串小写字母转换为大写子程序=================== 32 | ;名称:letterc 33 | ;功能:将以0结尾的字符串中的小写字母转变为大写字母 34 | ;参数:di:si指向字符串首地址 35 | ;返回:di:si指向被修改字符串首地址 36 | letterc: 37 | push ax 38 | push si 39 | iterator: 40 | mov al, ds:[si] 41 | ;如果al==0 42 | cmp al, 0 43 | je letterc_ok 44 | ;如果al<97 45 | cmp al, 97 46 | jb incsi 47 | ;如果al>122 48 | cmp al, 122 49 | ja incsi 50 | ;如果97<=al<=122 51 | and al, 11011111B 52 | mov ds:[si], al 53 | incsi: 54 | inc si 55 | jmp short iterator 56 | 57 | letterc_ok: 58 | pop si 59 | pop ax 60 | ret 61 | 62 | ;============================================================= 63 | 64 | ;====================字符串展示子程序========================= 65 | ;子程序名称: 66 | ; show_str 67 | ;子程序功能: 68 | ; 在指定位置,用指定的颜色,显示一个用0结束的字符串 69 | ;子程序参数: 70 | ; (dh)= 行号(取值范围0~24) 71 | ; (dl)= 列号(取值范围0~79) 72 | ; (cl)= 颜色 73 | ; ds:[si]= 字符串的首地址。 74 | ;子程序返回值: 75 | ; 无 76 | show_str: 77 | push ax 78 | push cx 79 | push dx 80 | push es 81 | push si 82 | push bp 83 | 84 | ;使用es:bp指向目标显存首地址 85 | mov ax, 0b800h 86 | mov es, ax 87 | 88 | ;计算bp的初值 89 | mov ax, 160 90 | mul dh 91 | mov dh, 0 92 | add ax, dx 93 | add ax, dx 94 | mov bp, ax 95 | 96 | copy: 97 | ;复制一个字母 98 | mov al, ds:[si] 99 | ;判断是否是字符串尾部,此处要用到cx,因为cx已被使用,所以要先备份cx 100 | mov dx, cx 101 | mov ch, 0 102 | mov cl, al 103 | jcxz ok 104 | mov cx, dx 105 | ;粘贴一个字母 106 | mov byte ptr es:[bp],al 107 | ;粘贴字母属性 108 | mov byte ptr es:[bp+1], cl 109 | add si, 1 110 | add bp, 2 111 | jmp short copy 112 | 113 | ok: 114 | pop bp 115 | pop si 116 | pop es 117 | pop dx 118 | pop cx 119 | pop ax 120 | 121 | ret 122 | 123 | ;================================================================= 124 | codesg ends 125 | end begin -------------------------------------------------------------------------------- /test/test12.asm: -------------------------------------------------------------------------------- 1 | ; 实验 12 编写0号中断的处理程序 2 | 3 | assume cs:code 4 | 5 | code segment 6 | 7 | start: 8 | ;安装中断处理程序,其实就是代码字符在内存中复制的过程↓ 9 | mov ax, cs 10 | mov ds, ax 11 | mov si, offset do0 ;设置ds:si指向源地址 12 | mov ax, 0 13 | mov es, ax 14 | mov di, 200h ;设置es:di指向目的地址 15 | mov cx, offset do0end - offset do0 ;设置cx为传输长度,即中断处理程序的长度 16 | cld ;设置传输方向为正 17 | rep movsb 18 | 19 | ;设置中断向量表,其实就是将中断处理程序的入口地址存放在0号中断源所对应的表项中 ↓ 20 | mov ax, 0 21 | mov es, ax 22 | mov word ptr es:[0*4], 200h ;设置中断处理程序入口偏移地址 23 | mov word ptr es:[0*4+2], 0 ;设置中断处理程序入口偏移地址 24 | 25 | ;除法溢出运算 26 | mov ax, 1000h 27 | mov bh, 1 28 | div bh 29 | 30 | ;验证下面的代码还会继续向下执行吗?如果执行的话屏幕中间会显示字符 ABC 31 | ;结果证明,下面的代码实际上并未执行,CPU在执行完do0后就退出返回到DOS中了, 32 | ;并未继续向下执行,有些不太理解! 33 | mov ax, 0b800h 34 | mov es, ax 35 | mov di, 13*160 +36*2 ; 36 | mov byte ptr es:[di], 41h 37 | mov byte ptr es:[di+2], 42h 38 | mov byte ptr es:[di+4], 43h 39 | 40 | mov ax, 4c00h 41 | int 21h 42 | 43 | do0:jmp short do0start 44 | db 'overflow!' 45 | 46 | ;中断处理程序 ↓ 47 | do0start: mov ax, cs 48 | mov ds, ax 49 | mov si, 202h ;设置ds:si指向字符串 50 | 51 | mov ax, 0b800h 52 | mov es, ax 53 | mov di, 12*160 +36*2 ;设置es:di指向显存空间的中间位置 54 | 55 | mov cx, 9 56 | s: mov al, [si] 57 | mov es:[di], al 58 | mov byte ptr es:[di+1], 11000010b ;为了增加显示效果,显示红底闪烁绿字 59 | inc si 60 | add di, 2 61 | loop s 62 | 63 | mov ax, 4c00h 64 | int 21h 65 | 66 | do0end: nop 67 | code ends 68 | end start -------------------------------------------------------------------------------- /test/test2.asm: -------------------------------------------------------------------------------- 1 | ;=============================================================================== 2 | ;(1)使用Debug,将下面的程序写入内存,逐条执行,根据指令 3 | ;执行后的实际运行情况填空 4 | 5 | mov ax, 0ffff 6 | mov ds, ax 7 | 8 | mov ax, 2200 9 | mov ss, ax 10 | 11 | mov sp, 0100 12 | 13 | mov ax, [0] ;ax=(COEA) 14 | mov ax, [2] ;ax=(0012) 15 | mov bx, [4] ;bx=(30F0) 16 | mov bx, [6] ;bx=(2F31) 17 | 18 | push ax ;sp=(00FE); 修改的内存单元的地址是(2200:00FE) 内容为(0012) 19 | push bx ;sp=(00FC); 修改的内存单元的地址是(2200:00FC) 内容为(2F31) 20 | pop ax ;sp=(00FE); ax=(2F31) 21 | pop bx ;sp=(0100); bx=(0012) 22 | 23 | push [4] ;sp=(00FE); 修改的内存单元的地址是(2200:00FE) 内容为(30F0) 24 | push [6] ;sp=(00FC); 修改的内存单元的地址是(2200:00FC) 内容为(2F31) 25 | 26 | ;=============================================================================== 27 | ;(2)仔细观察图3.19中的实验过程,然后分析:为什么2000:0~2000:f中的内容会发生改变? 28 | ; 29 | ;仔细观察2000:0~2000:f变化后的值为: 30 | ;2000:000E 059D 未知 31 | ;2000:000C 0B39 CS和DS的值 32 | ;2000:000A 0108 IP的值 33 | ;2000:0008 0000 未知 34 | ;2000:0006 2000 SS和AX的值 35 | ; 36 | ;猜测是与中断时环境保存有关。 37 | ;=============================================================================== 38 | 39 | -------------------------------------------------------------------------------- /test/test3.asm: -------------------------------------------------------------------------------- 1 | ;实验3 编程、编译、连接、跟踪 2 | ;=========================================================== 3 | ;(1)将下面的程序保存为t1.asm文件,将其生成可执行文件t1.exe 4 | assume cs:codesg 5 | 6 | codesg segment 7 | 8 | mov ax, 2000H 9 | mov ss, ax 10 | mov sp, 0 11 | add sp, 10 12 | pop ax 13 | pop bx 14 | push ax 15 | push bx 16 | pop ax 17 | pop bx 18 | 19 | mov ax, 4c00h 20 | int 21h 21 | codesg ends 22 | end 23 | 24 | ;=========================================================== 25 | ;(2)用Debug跟踪t1.exe的执行过程,写出每一步执行后,相关寄存器 26 | ;中的内容和栈顶的内容。 27 | ; 28 | ;初始值: 29 | ;AX=FFFF BX=0000 CX=0016 DS=075A CS=076A IP=0000 SS=0769 SP=0000 30 | ;mov ax, 2000 31 | ;AX=2000 BX=0000 CX=0016 DS=075A CS=076A IP=0003 SS=0769 SP=0000 32 | ;mov ss, ax 33 | ;mov sp, 0 34 | ;AX=2000 BX=0000 CX=0016 DS=075A CS=076A IP=0008 SS=2000 SP=0000 35 | ;add sp, 10 36 | ;AX=2000 BX=0000 CX=0016 DS=075A CS=076A IP=000B SS=2000 SP=000A 37 | ;pop ax 38 | ;AX=0000 BX=0000 CX=0016 DS=075A CS=076A IP=000C SS=2000 SP=000C 39 | ;pop bx 40 | ;AX=0000 BX=0000 CX=0016 DS=075A CS=076A IP=000D SS=2000 SP=000E 41 | ;push ax 42 | ;AX=0000 BX=0000 CX=0016 DS=075A CS=076A IP=000E SS=2000 SP=000C 43 | ;push bx 44 | ;AX=0000 BX=0000 CX=0016 DS=075A CS=076A IP=000F SS=2000 SP=000A 45 | ;pop ax 46 | ;AX=0000 BX=0000 CX=0016 DS=075A CS=076A IP=0010 SS=2000 SP=000C 47 | ;pop bx 48 | ;AX=0000 BX=0000 CX=0016 DS=075A CS=076A IP=0011 SS=2000 SP=000E 49 | ;mov ax, 4C00 50 | ;AX=4C00 BX=0000 CX=0016 DS=075A CS=076A IP=0014 SS=2000 SP=000E 51 | ;=========================================================== 52 | ;(3)PSP的头两个字节是CD 20,用Debug加载t1.exe,查看PSP的内容。 53 | ; 54 | ;075A:0000 CD 20 FF 9F 00 EA FF FF-AD DE 4F 03 A3 01 8A 03 55 | ;... 56 | ;... 57 | ;=========================================================== -------------------------------------------------------------------------------- /test/test4_1.asm: -------------------------------------------------------------------------------- 1 | ;实验4 [bx]和loop的使用 2 | ; 3 | ;(1)编程,向内存0:200~023F依次传送数据0~63(3FH)。 4 | 5 | assume cs:code 6 | 7 | code segment 8 | 9 | mov bx, 0 ;初始值 10 | mov cx, 64 ;循环次数 11 | mov ax, 0020h 12 | mov ds, ax ;将段地址送入ds寄存器 13 | 14 | s: mov ds:[bx], bx ;将数据送入ds:bx指向的内存单元 15 | inc bx ;bx+1 16 | loop s ;判断循环是否结束 17 | 18 | mov ax, 4c00h 19 | int 21h 20 | 21 | code ends 22 | end -------------------------------------------------------------------------------- /test/test4_2.asm: -------------------------------------------------------------------------------- 1 | ;实验4 [bx]和loop的使用 2 | ; 3 | ;(2)编程,向内存0:200~023F依次传送数据0~63(3FH), 4 | ;程序中只能使用9条指令,条指令包括”mov ax,4c00h”和“int 21h” 5 | 6 | assume cs:code 7 | 8 | code segment 9 | 10 | mov bx, 0 ;初始值 11 | mov cx, 64 ;循环次数 12 | mov ax, 0020h 13 | mov ds, ax ;将段地址送入ds寄存器 14 | 15 | s: mov ds:[bx], bx ;将数据送入ds:bx指向的内存单元 16 | inc bx ;bx+1 17 | loop s ;判断循环是否结束 18 | 19 | mov ax, 4c00h 20 | int 21h 21 | 22 | code ends 23 | end -------------------------------------------------------------------------------- /test/test4_3.asm: -------------------------------------------------------------------------------- 1 | ;实验4 [bx]和loop的使用 2 | ; 3 | ;(3)下面的程序的功能是将“mov ax,4c00h”之前的指令复制到内存0:200处, 4 | ;补全程序。上机调试,跟踪运行结果。 5 | 6 | assume cs:code 7 | 8 | code segment 9 | 10 | mov ax, cs ;源程序首地址 11 | mov ds, ax 12 | mov ax, 0020h 13 | mov es, ax 14 | mov bx, 0 15 | mov cx, 0017h ;要复制字节的数量 16 | 17 | s: mov al, [bx] 18 | mov es:[bx], al 19 | inc bx 20 | loop s 21 | 22 | mov ax, 4c00h 23 | int 21h 24 | 25 | code ends 26 | end 27 | 28 | ;提示: 29 | ;(1)复制的是什么?从哪里到哪里? 30 | ;答: 31 | ;以字节为单位复制源程序所对应的二进制码。从cs:0000复制到0020:0000。 32 | ;(2)复制的是什么?有多少个字节?你是如何知道要复制的字节的数量? 33 | ;答: 34 | ;通过U命令查看mov ax, 4c00h所在地址的偏移地址,拿该偏移地址减去ip的 35 | ;初始值即可获得要复制的字节的数量。 -------------------------------------------------------------------------------- /test/test5_1.asm: -------------------------------------------------------------------------------- 1 | ;实验5 编写、调试具有多个段的程序 2 | ; 3 | ;(1)将下面的程序编译、连接,用debug加载、跟踪,然后回答问题。 4 | 5 | assume cs:code, ds:data, ss:stack 6 | 7 | data segment 8 | dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h 9 | data ends 10 | 11 | stack segment 12 | dw 0, 0, 0, 0, 0, 0, 0, 0 13 | stack ends 14 | 15 | code segment 16 | 17 | start: mov ax, stack 18 | mov ss, ax 19 | mov sp, 16 20 | 21 | mov ax, data 22 | mov ds, ax 23 | 24 | push ds:[0] 25 | push ds:[2] 26 | pop ds:[2] 27 | pop ds:[0] 28 | 29 | mov ax, 4c00h 30 | int 21h 31 | 32 | code ends 33 | end start 34 | 35 | ;(1)CPU执行程序,程序返回前,data段中的数据为多少? 36 | ;答:值未变: 37 | ;0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h 38 | ; 39 | ;(2)CPU执行程序,程序返回前,cs=____、ss=____、ds=____。 40 | ;答:cs = 076C, ss = 076B,ds = 076A 41 | ; 42 | ;(3)程序加载后,code段的段地址为X,则data段的段地址为____,stack段的段地址为____。 43 | ;答:data段的段地址为X-2,stack段的段地址为X-1。 44 | -------------------------------------------------------------------------------- /test/test5_2.asm: -------------------------------------------------------------------------------- 1 | ;实验5 编写、调试具有多个段的程序 2 | ; 3 | ;(2)将下面的程序编译、连接,用debug加载、跟踪,然后回答问题。 4 | 5 | assume cs:code, ds:data, ss:stack 6 | 7 | data segment 8 | dw 123H, 0456H 9 | data ends 10 | 11 | stack segment 12 | dw 0, 0 13 | stack ends 14 | 15 | code segment 16 | 17 | start: mov ax, stack 18 | mov ss, ax 19 | mov sp, 16 20 | 21 | mov ax, data 22 | mov ds, ax 23 | 24 | push ds:[0] 25 | push ds:[2] 26 | pop ds:[2] 27 | pop ds:[0] 28 | 29 | mov ax, 4c00h 30 | int 21h 31 | 32 | code ends 33 | 34 | end start 35 | 36 | ;(1)CPU执行程序,程序返回前,data段中的数据为多少? 37 | ;答:值未变: 38 | ;0123h, 0456h 39 | ; 40 | ;(2)CPU执行程序,程序返回前,cs=____、ss=____、ds=____。 41 | ;答:cs = 076C, ss = 076B,ds = 076A 42 | ; 43 | ;(3)程序加载后,code段的段地址为X,则data段的段地址为____,stack段的段地址为____。 44 | ;答:data段的段地址为X-2,stack段的段地址为X-1。 45 | ; 46 | ;(4)对于如下定义的段: 47 | ;name segment 48 | ;... 49 | ;name ends 50 | ;如果段中的数据占N个字节,则程序加载后,该段实际占有的空间为____。 51 | ;答:实际占用的空间为16的倍数。最大不超过64KB。 52 | -------------------------------------------------------------------------------- /test/test5_3.asm: -------------------------------------------------------------------------------- 1 | ;实验5 编写、调试具有多个段的程序 2 | ; 3 | ;(3)将下面的程序编译、连接,用debug加载、跟踪,然后回答问题。 4 | 5 | assume cs:code, ds:data, ss:stack 6 | 7 | code segment 8 | 9 | start: mov ax, stack 10 | mov ss, ax 11 | mov sp, 16 12 | 13 | mov ax, data 14 | mov ds, ax 15 | 16 | push ds:[0] 17 | push ds:[2] 18 | pop ds:[2] 19 | pop ds:[0] 20 | 21 | mov ax, 4c00h 22 | int 21h 23 | 24 | code ends 25 | 26 | data segment 27 | dw 0123h, 0456h 28 | data ends 29 | 30 | stack segment 31 | dw 0, 0 32 | stack ends 33 | 34 | end start 35 | 36 | ;(1)CPU执行程序,程序返回前,data段中的数据为多少? 37 | ;答:值未变: 38 | ;0123h, 0456h 39 | ; 40 | ;(2)CPU执行程序,程序返回前,cs=____、ss=____、ds=____。 41 | ;答:cs = 076A, ss = 076E,ds = 076D 42 | ; 43 | ;(3)程序加载后,code段的段地址为X,则data段的段地址为____,stack段的段地址为____。 44 | ;答:data段的段地址为X+3,stack段的段地址为X+4。 45 | ; 46 | -------------------------------------------------------------------------------- /test/test5_4.asm: -------------------------------------------------------------------------------- 1 | ;实验5 编写、调试具有多个段的程序 2 | ; 3 | ;(4)如果将(1)、(2)、(3)题中的最后一条伪指令"end start"改为"end" 4 | ;(也就是说,不指明程序入口),则哪个程序可以正常执行?清说明原因。 5 | ; 6 | ;答:程序(3)可以正常执行。程序加载完成后,CS默认指向程序首地址。IP默认为0。 7 | ;此时程序(3)CS:IP恰巧指向程序代码段。 8 | -------------------------------------------------------------------------------- /test/test5_5.asm: -------------------------------------------------------------------------------- 1 | ;实验5 编写、调试具有多个段的程序 2 | ; 3 | ;(5)程序如下,编写code段中的代码,将a段b段中的数据依次相加,将结果保存在c段中 4 | 5 | assume cs:code 6 | 7 | a segment 8 | db 1,2,3,4,5,6,7,8 9 | a ends 10 | 11 | b segment 12 | db 1,2,3,4,5,6,7,8 13 | b ends 14 | 15 | c segment 16 | db 0,0,0,0,0,0,0,0 17 | c ends 18 | 19 | code segment 20 | 21 | start: 22 | 23 | mov ax, a 24 | mov ds, ax 25 | mov ax, b 26 | mov ss, ax 27 | mov ax, c 28 | mov es, ax 29 | 30 | mov cx, 8 31 | mov bx, 0 32 | 33 | s: mov ah, ds:[bx] 34 | add ah, ss:[bx] 35 | mov es:[bx], ah 36 | inc bx 37 | loop s 38 | 39 | mov ax, 4c00h 40 | int 21h 41 | 42 | code ends 43 | end start -------------------------------------------------------------------------------- /test/test5_6.asm: -------------------------------------------------------------------------------- 1 | ;实验5 编写、调试具有多个段的程序 2 | ; 3 | ;(6)程序如下,编写code段中的代码,用push指令将a段中的前8个字型数据, 4 | ;逆序存储到b段中。 5 | 6 | assume cs:code 7 | 8 | a segment 9 | dw 1,2,3,4,5,6,7,8,9,0ah,0bh,0ch,0dh,0eh,0fh,0ffh 10 | a ends 11 | 12 | b segment 13 | dw 0,0,0,0,0,0,0,0 14 | b ends 15 | 16 | 17 | 18 | code segment 19 | 20 | start: 21 | 22 | mov ax, a 23 | mov ds, ax 24 | 25 | mov ax, b 26 | mov ss, ax 27 | mov sp, 16 28 | 29 | mov bx, 0 30 | mov cx, 8 31 | 32 | s: push [bx] 33 | add bx, 2 34 | loop s 35 | 36 | mov ax, 4c00h 37 | int 21h 38 | 39 | code ends 40 | end start -------------------------------------------------------------------------------- /test/test6_1.asm: -------------------------------------------------------------------------------- 1 | ;实验6 实践课程中的程序 2 | ; 3 | ;(1)在codesg中填写代码,将datasg中的第一个字符串转换为大写, 4 | ;第二个字符串转化为小写。 5 | ; 6 | ;将ASCII码的第五位置为0,变为大写字母 7 | ;将ASCII码的第五位置为1,变为小写字母 8 | 9 | assume cs:codesg, ds:datasg 10 | 11 | datasg segment 12 | db 'BaSiC' 13 | db 'iNfOrMaTiOn' 14 | datasg ends 15 | 16 | codesg segment 17 | 18 | start: mov ax, datasg 19 | mov ds, ax 20 | 21 | mov bx, 0 22 | mov cx, 5 23 | 24 | s1: mov al, [bx] 25 | and al, 11011111B 26 | mov [bx], al 27 | inc bx 28 | loop s1 29 | 30 | mov bx, 5 31 | mov cx, 11 32 | 33 | s2: mov al, [bx] 34 | or al, 00100000B 35 | mov [bx], al 36 | inc bx 37 | loop s2 38 | 39 | mov ax, 4c00h 40 | int 21h 41 | codesg ends 42 | 43 | end start 44 | -------------------------------------------------------------------------------- /test/test6_2.asm: -------------------------------------------------------------------------------- 1 | ;实验6 实践课程中的程序 2 | ; 3 | ;(2)使用[bx+idata]的寻址方式,编写代码,将datasg中的第一个字符串转换为大写, 4 | ;第二个字符串转化为小写。 5 | ; 6 | ;将ASCII码的第五位置为0,变为大写字母 7 | ;将ASCII码的第五位置为1,变为小写字母 8 | 9 | assume cs:codesg, ds:datasg 10 | 11 | datasg segment 12 | db 'BaSiC' 13 | db 'MinIX' 14 | datasg ends 15 | 16 | codesg segment 17 | 18 | start: mov ax, datasg 19 | mov ds, ax 20 | 21 | mov bx, 0 22 | mov cx, 5 23 | 24 | s: mov al, [bx] 25 | and al, 11011111B 26 | mov [bx], al 27 | 28 | mov al, [bx+5] 29 | or al, 00100000B 30 | mov [bx+5], al 31 | 32 | inc bx 33 | loop s 34 | 35 | mov ax, 4c00h 36 | int 21h 37 | codesg ends 38 | 39 | end start 40 | -------------------------------------------------------------------------------- /test/test6_3.asm: -------------------------------------------------------------------------------- 1 | ;实验6 实践课程中的程序 2 | ; 3 | ;(3)用si和di实现将字符串'welcome to masm'复制到它后面的数据区中。 4 | ; 5 | ; 可以一次拷贝一个字符,为了提升效率也可以一次拷贝两个字符(一个字) 6 | 7 | assume cs:codesg, ds:datasg 8 | 9 | datasg segment 10 | db 'welcome to masm!' 11 | db '................' 12 | datasg ends 13 | 14 | codesg segment 15 | 16 | start: mov ax, datasg ;指定数据段的段地址 17 | mov ds, ax 18 | 19 | mov si, 0 20 | mov di, 16 21 | 22 | mov cx, 16 23 | 24 | s: mov al, [si] ;拷贝一个字符至al 25 | mov [di], al ;将字符复制至ds:[di] 26 | inc si 27 | inc di 28 | loop s 29 | 30 | mov ax, 4c00h 31 | int 21h 32 | 33 | codesg ends 34 | 35 | end start 36 | -------------------------------------------------------------------------------- /test/test6_4.asm: -------------------------------------------------------------------------------- 1 | ;实验6 实践课程中的程序 2 | ; 3 | ;(4)编程,将datasg段中每个单词的头一个字母改为大写字母。 4 | ; 5 | ;将6个字符串看成一个二维数组。使用变量bx定位行,使用常量3定位列。 6 | 7 | assume cs:codesg, ds:datasg 8 | 9 | datasg segment 10 | db '1. file ' 11 | db '2. edit ' 12 | db '3. search ' 13 | db '4. view ' 14 | db '5. options ' 15 | db '6. help ' 16 | datasg ends 17 | 18 | codesg segment 19 | 20 | start: mov ax, datasg 21 | mov ds, ax 22 | 23 | mov bx, 0 24 | mov cx, 6 25 | 26 | s: mov al, [bx+3] 27 | and al, 11011111B 28 | mov [bx+3], al 29 | add bx, 16 30 | loop s 31 | 32 | mov ax, 4c00h 33 | int 21h 34 | 35 | codesg ends 36 | end start 37 | 38 | -------------------------------------------------------------------------------- /test/test6_5.asm: -------------------------------------------------------------------------------- 1 | ;实验6 实践课程中的程序 2 | ; 3 | ;(5)编程,将datasg段中每个单词改为大写字母。 4 | ; 5 | ;本实验练习[bx+si]的寻址方式。 6 | ; 7 | assume cs:codesg, ds:datasg, ss:stacksg 8 | 9 | datasg segment 10 | db 'ibm ' 11 | db 'dec ' 12 | db 'dos ' 13 | db 'vax ' 14 | datasg ends 15 | 16 | stacksg segment ;定义一个段,用来做栈段,容量为8个字 17 | dw 0,0,0,0,0,0,0,0 18 | stacksg ends 19 | 20 | codesg segment 21 | 22 | start: mov ax, datasg ;初始化数据段 23 | mov ds, ax 24 | 25 | mov ax, stacksg ;初始化栈段 26 | mov ss, ax 27 | mov sp, 16 28 | 29 | mov bx, 0 30 | mov cx, 4 31 | 32 | s0: mov si, 0 ;外层循环,遍历行(4行) 33 | push cx ;临时保存cx的值 34 | 35 | mov cx, 3 36 | s1: mov al, [bx+si] ;内层循环,遍历列(3列) 37 | and al, 11011111B 38 | mov [bx+si], al 39 | inc si 40 | 41 | pop cx ;恢复外层循环cx的值 42 | add bx, 16 43 | loop s0 44 | 45 | mov ax, 4c00h 46 | int 21h 47 | 48 | codesg ends 49 | end start 50 | 51 | -------------------------------------------------------------------------------- /test/test6_6.asm: -------------------------------------------------------------------------------- 1 | ;实验6 实践课程中的程序 2 | ; 3 | ;(6)编程,将datasg段中每个单词的前4个字母改为大写字母。 4 | ; 5 | ;本实验练习[bx+si+idata]的寻址方式。 6 | ; 7 | assume cs:codesg, ss:stacksg, ds:datasg 8 | 9 | stacksg segment 10 | dw 0,0,0,0,0,0,0,0 11 | stacksg ends 12 | 13 | datasg segment 14 | db '1. display ' ;16个字节 15 | db '2. brows ' 16 | db '3. replace ' 17 | db '4. modify ' 18 | datasg ends 19 | 20 | codesg segment 21 | 22 | start: mov ax, datasg ;初始化栈段 23 | mov ds, ax 24 | 25 | mov ax, stacksg ;初始化栈段 26 | mov ss, ax 27 | mov sp, 16 28 | 29 | mov bx, 0 30 | mov cx, 4 ;外层循环执行次数 31 | 32 | s0: mov si, 0 33 | push cx 34 | mov cx, 4 35 | 36 | s1: mov al, [bx+si+3] ;取一个字符 37 | and al, 11011111B ;ASCII码转大写 38 | mov [bx+si+3], al ;存转换后的字符 39 | inc si 40 | 41 | loop s1 42 | 43 | pop cx 44 | add bx, 16 45 | loop s0 46 | 47 | mov ax, 4c00h 48 | int 21h 49 | 50 | codesg ends 51 | end start 52 | 53 | -------------------------------------------------------------------------------- /test/test7.asm: -------------------------------------------------------------------------------- 1 | ;实验7 寻址方式在结构化数据访问中的应用 2 | ; 3 | assume cs:codesg, ds:datasg, ss:table 4 | 5 | datasg segment 6 | ;定义年份,占84个字节 7 | db '1975', '1976', '1977', '1978', '1979', '1980', '1981', '1982' 8 | db '1983', '1984', '1985', '1986', '1987', '1988', '1989', '1990' 9 | db '1991', '1992', '1993', '1994', '1995' 10 | ;定义收入, 占84个字节 11 | dd 16, 22, 382, 1356, 2390, 8000, 16000, 24486 12 | dd 50065, 97479, 140417, 197514, 345980, 590827, 803530, 1183000 13 | dd 1843000, 2759000, 3753000, 4649000, 5937000 14 | ;定义雇员人数,占42个字节 15 | dw 3, 7, 9, 13, 28, 38, 130, 220 16 | dw 476, 778, 1001, 1442, 2258, 2793, 4037,5635 17 | dw 8226, 11542, 14430, 15257, 17800 18 | datasg ends 19 | 20 | table segment 21 | ;定义21行的一个表格,每行16个字节,共占246个字节 22 | db 21 dup ('year summ ne ?? ') 23 | table ends 24 | 25 | codesg segment 26 | start: 27 | ;第一步 设置 28 | ;ds指向datasg,bx用于定位datasg数组 29 | ;ss指向table, bp用于定位table行 30 | 31 | mov ax, datasg 32 | mov ds, ax 33 | mov bx, 0 34 | mov ax, table 35 | mov ss, ax 36 | mov bp, 0 37 | 38 | ;复制年份和空格 39 | mov cx, 21 ;循环执行次数 40 | mov si, 0 ;定位年份 41 | mov di, 0 ;定位收入 42 | 43 | loop1: 44 | 45 | ;使用ds:[bx+0+si]定位年份 46 | mov ax, ds:[bx+si] ;复制年份前两个字节 47 | mov ss:[bp+0], ax ;粘贴年份前两个字节 48 | add si, 2 ;年份下标后移两位 49 | mov ax, [bx+si] ;复制年份后两个字节 50 | mov ss:[bp+2], ax ;粘贴年份后两个字节 51 | mov byte ptr [bp+4], ' ';粘贴一个空格 52 | add si, 2 53 | 54 | ;使用ds:[bx+84+di]定位收入 55 | mov ax, [bx+84+di] ;复制收入前两个字节 56 | mov ss:[bp+5], ax ;粘贴收入前两个字节 57 | add di, 2 ;收入索引+2 58 | mov ax, [bx+84+di] ;复制收入后两个字节 59 | mov ss:[bp+7],ax ;粘贴收入后两个字节 60 | mov byte ptr [bp+9], ' ';复制空格 61 | add di, 2 ;收入索引+2 62 | 63 | add bp, 16 64 | loop loop1 65 | 66 | mov cx, 21 67 | mov si, 0 68 | mov bp, 0 69 | loop2: 70 | ;使用ds:[bx+84+84+si]定位雇员人数 71 | mov ax, [bx+84+84+si] ;复制雇员数量 72 | mov [bp+10], ax ;粘贴雇员人数 73 | add si, 2 ;雇员人数索引+2 74 | mov byte ptr [bp+12],' ';复制空格 75 | 76 | mov dx, [bp+7] ;收入高16位 77 | mov ax, [bp+5] ;收入低16位 78 | div word ptr [bp+10] ;除以人数 79 | mov [bp+13], ax ;粘贴商 80 | mov byte ptr [bp+15],' ';复制空格 81 | 82 | 83 | add bp, 16 84 | loop loop2 85 | 86 | 87 | mov ax, 4c00h 88 | int 21h 89 | 90 | codesg ends 91 | end start -------------------------------------------------------------------------------- /test/test8.asm: -------------------------------------------------------------------------------- 1 | ;实验8 分析一个奇怪的程序 2 | ; 3 | ;分析下面的程序,在运行前思考:这个程序可以正确返回吗? 4 | ;运行后思考:为什么是这种结果? 5 | ; 6 | ;解析:程序可以正确返回。 7 | ;jmp short指令是根据转移目的地址和转移起始之间的位移来进行的。 8 | ;jmp short s1 转换为机器指令的16进制表示形式为:EBF6。F6为8位位移 9 | ;的补码表示形式。当程序执行至标号s处时,根据jmp short 指令的定义 : 10 | ;(IP)= (IP)+8位位移。可以算出,IP = 0。 11 | ; 12 | ; 13 | assume cs:codesg 14 | 15 | codesg segment 16 | 17 | mov ax, 4c00h 18 | int 21h 19 | 20 | start: mov ax, 0 21 | s: nop 22 | nop 23 | 24 | mov di, offset s 25 | mov si, offset s2 26 | mov ax, cs:[si] 27 | mov cs:[di], ax 28 | 29 | s0: jmp short s 30 | 31 | s1: mov ax, 0 32 | int 21h 33 | mov ax, 0 34 | 35 | s2: jmp short s1 36 | nop 37 | 38 | codesg ends 39 | end start -------------------------------------------------------------------------------- /test/test9.asm: -------------------------------------------------------------------------------- 1 | ;实验9 根据材料编程 2 | ; 3 | ;编程:在屏幕中间分别显示绿色、绿底红色、白底蓝色的字符串'welcome to masm!' 4 | ; 5 | ; 6 | assume cs:codesg, ds:datasg 7 | 8 | datasg segment 9 | db 'welcome to masm!' ;16个字节 10 | datasg ends 11 | 12 | codesg segment 13 | 14 | start: 15 | mov ax, datasg ;源数据段地址 16 | mov ds, ax 17 | 18 | mov ax, 0B800H ;目的数据段地址 19 | mov es, ax 20 | 21 | mov cx, 16 22 | mov si, 0 23 | mov bx, 0140H ;用于定位行,从第三行开始 24 | mov di, 0 ;用于定位列 25 | 26 | s: mov al, ds:[si] ;复制一个字节 27 | mov es:[bx][di], al ;第一行,偶数列 28 | mov es:160[bx][di], al ;第二行,偶数列 29 | mov es:320[bx][di], al ;第三行,偶数列 30 | mov byte ptr es:[bx][di+1], 00000010B ;第一行,奇数列 31 | mov byte ptr es:160[bx][di+1], 00100100B ;第二行,奇数列 32 | mov byte ptr es:320[bx][di+1], 01110001B ;第三行,奇数列 33 | inc si 34 | add bx, 2 35 | loop s 36 | 37 | mov ax, 4c00h 38 | int 21h 39 | 40 | codesg ends 41 | end start -------------------------------------------------------------------------------- /《汇编语言》第三版检测点答案.md: -------------------------------------------------------------------------------- 1 | # **王爽《汇编语言》第三版检测点答案** 2 | 3 | >注:该答案为个人整理所得,如有纰漏,欢迎指正。 4 | ## 检测点1.1 5 | --- 6 | 7 | ### 问题 8 | (1) 1个CPU的寻址能力为8KB,那么他的地址总线的宽度为【 】。 9 | (2) 1KB的存储器有 【 】个存储单元。存储单元编号从【 】到【 】。 10 | (3) 1KB的存储器可以存储【 】个bit,【 】个Byte。 11 | (4) 1GB、1MB、1KB分别是【 】Byte。 12 | (5) 8080、8088、8026、80386的地址总线宽度分别为16根、20根、24根、32根,则他们的寻址能力分别为:【 】(KB)、【 】(MB)、【 】(MB)、【 】(GB)。 13 | (6) 8080、8088、8086、8026、80386的数据总线宽度分别为8根、8根、16根、16根、32根。则它们一次可以传送的数据为:【 】(B)、【 】(B)、【 】(B)、【 】(B)、【 】(B)。 14 | (7) 从内存中读取1024字节的数据,8086至少要读【 】次,80386至少要读【 】次。 15 | (8)在存储器中,数据和程序以【 】形式存放。 16 | 17 | ### 答案 18 | (1) 1个CPU的寻址能力为8KB,那么他的地址总线的宽度为【 **13** 】。 19 | (2) 1KB的存储器有 【 **1024** 】个存储单元。存储单元编号从【 **0000** 】到【 **1023** 】。 20 | (3) 1KB的存储器可以存储【 **1024 x 8** 】个bit,【 **1024** 】个Byte。 21 | (4) 1GB、1MB、1KB分别是【**1024x1024x1024、1024x1024、1024** 】Byte。 22 | (5) 8080、8088、8026、80386的地址总线宽度分别为16根、20根、24根、32根,则他们的寻址能力分别为:【 **64** 】(KB)、【 **1** 】(MB)、【 **16** 】(MB)、【 **4** 】(GB)。 23 | (6) 8080、8088、8086、8026、80386的数据总线宽度分别为8根、8根、16根、16根、32根。则它们一次可以传送的数据为:【 **1** 】(B)、【 **1** 】(B)、【 **2** 】(B)、【 **2** 】(B)、【 **4** 】(B)。 24 | (7) 从内存中读取1024字节的数据,8086至少要读【 **512** 】次,80386至少要读【 **256** 】次。 25 | (8)在存储器中,数据和程序以【 **二进制码** 】形式存放。 26 | 27 | ### 解析 28 | 略。 29 | 30 | ## 检测点2.1 31 | --- 32 | 33 | ### 问题 34 | (1) 写出每条汇编指令执行后相关寄存器中的值。 35 | 36 | | 指令 | 寄存器 | 37 | |--|--| 38 | | mov ax, 62627 | AX = __ | 39 | | mov ah, 31H | AX = __ | 40 | | mov al, 23H | AX = __ | 41 | | add ax, ax | AX = __ | 42 | | mov bx, 826CH | BX = __ | 43 | | mov cx, ax | CX = __ | 44 | | mov ax, bx | AX = __ | 45 | | add ax, bx | AX = __ | 46 | | mov al, bh | AX = __ | 47 | | mov ah, bl | AX = __ | 48 | | add ah, ah | AX = __ | 49 | | add al, 6 | AX = __ | 50 | | add al, al | AX = __ | 51 | | mov ax, cx | AX = __ | 52 | 53 | (2) 只能使用目前学过的汇编指令,最多使用4条指令,编程计算2的4次方。 54 | 55 | ### 答案 56 | 57 | (1) 写出每条汇编指令执行后相关寄存器中的值。 58 | 59 | | 指令 | 寄存器 | 60 | |--|--| 61 | | mov ax, 62627 | AX = F4A3H | 62 | | mov ah, 31H | AX = 31A3H | 63 | | mov al, 23H | AX = 3123H | 64 | | add ax, ax | AX = 6246H | 65 | | mov bx, 826CH | BX = 826CH | 66 | | mov cx, ax | CX = 6246H | 67 | | mov ax, bx | AX = 826CH | 68 | | add ax, bx | AX = 04D8H | 69 | | mov al, bh | AX = 0482H | 70 | | mov ah, bl | AX = 6C82H | 71 | | add ah, ah | AX = D882H | 72 | | add al, 6 | AX = D888H | 73 | | add al, al | AX = D810H | 74 | | mov ax, cx | AX = 6246H | 75 | 76 | (2) 只能使用目前学过的汇编指令,最多使用4条指令,编程计算2的4次方。 77 | ``` 78 | mov ax, 2H 79 | add ax, ax 80 | add ax, ax 81 | add ax, ax 82 | ``` 83 | 84 | ### 解析 85 | 略。 86 | 87 | ## 检测点2.2 88 | --- 89 | 90 | ### 问题 91 | (1) 给定段地址为0001H,仅通过变化偏移地址寻址,CPU的寻址范围为【 】到【 】。 92 | (2) 有一数据存放在内存20000H单元中,现给定段地址为SA,若想用偏移地址寻到此单元。则SA应满足的条件是:最小为【 】,最大为【】。 93 | 提示,反过来思考一下,当段地址给定为多少,CPU无论怎么变化偏移地址都无法寻到20000H单元。 94 | 95 | ### 答案 96 | (1) 给定段地址为0001H,仅通过变化偏移地址寻址,CPU的寻址范围为【 **00010H** 】到【 **1000FH** 】。 97 | (2) 有一数据存放在内存20000H单元中,现给定段地址为SA,若想用偏移地址寻到此单元。则SA应满足的条件是:最小为【 **1001H** 】,最大为【 **2000H** 】。 98 | 99 | ### 解析 100 | (1)偏移地址最小为 0000H,最大为FFFFH。按(段地址*16 + 偏移地址)计算可得。 101 | (2)偏移地址最小为 0000H,最大为FFFFH。偏移地址取最小值0000H时,段地址最大为2000H。偏移地址取最大值FFFFH时,段地址为:(20000H - FFFFH)/16 = 10001H/16 = 1000H。 102 | 因为段地址要满足可以寻址到20000H单元的条件,所以段地址取1001H。 103 | 104 | ## 检测点2.3 105 | --- 106 | 107 | ### 问题 108 | 下面的3条指令执行后,CPU几次修改IP?都是在什么时候?最后IP中的值是多少? 109 | mov ax, bx 110 | sub ax, ax 111 | jmp ax 112 | ### 答案 113 | 三条指令执行后,CPU共计4次修改IP。 114 | 115 | 第1次是在mov ax, bx指令加载至指令缓冲器后。 116 | 第2次是在sub ax, ax指令加载至指令缓冲器后。 117 | 第3次是在jmp ax指令加载至指令缓冲器后。 118 | 第4次是在jmp ax指令执行完毕后。 119 | 120 | 最后IP中的值是0000H。 121 | ### 解析 122 | 8086CPU的工作过程可以简要描述如下: 123 | (1) 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器; 124 | (2) IP = IP + 所读取指令的长度,从而指向下一条指令; 125 | (3) 执行指令。转到步骤(1),重复这个过程。 126 | 127 | “Jmp 段地址:偏移地址” 指令的功能为:用指令中给出的段地址修改CS,偏移地址修改IP。 128 | “Jmp 偏移地址” 指令的功能为:用寄存器中的值修改IP。 129 | 130 | ## 检测点3.1 131 | --- 132 | ### 问题 133 | (1)在Debug中,用“d 0:0 1f”查看内存,结果如下。 134 | ``` 135 | 0000:0000 70 80 f0 30 EF 60 30 E2-00 80 80 12 66 20 22 60 136 | 0000:0010 62 26 E6 D6 CC 2E 3C 3B-AB BA 00 00 26 06 66 88 137 | ``` 138 | 下面的程序执行前,AX=0,BX=0,写出每条汇编指令执行完后相关寄存器中的值。 139 | 140 | |指令|寄存器| 141 | |--|--| 142 | | mov ax, 1 | | 143 | | mov dx, ax | | 144 | | mov ax, [0000] | AX=__ | 145 | | mov bx, [0001] | BX=__ | 146 | | mov ax, bx | AX=__ | 147 | | mov ax, [0000] | AX=__ | 148 | | mov bx, [0002] | BX=__ | 149 | | add ax, bx | AX=__ | 150 | | add ax, [0004] | AX=__ | 151 | | mov ax, 0 | AX=__ | 152 | | mov al, [0002] | AX=__ | 153 | | mov bx, 0 | BX=__ | 154 | | mov bl, [000C] | BX=__ | 155 | | add al, bl | AX=__ | 156 | 157 | (2)内存中的情况如图3.6所示(图片位于课本P56)。 158 | 159 | 各寄存器的初始值:CS=2000H,IP=0,DS=1000H,AX=0,BX=0; 160 | 161 | 1. 写出CPU执行的指令序列(用汇编指令写出)。 162 | 2. 写出CPU执行每条指令后,CS、IP和相关寄存器中的数值。 163 | 3. 再次体会:数据和程序有区别吗?如何确定内存中的信息哪些是数据,哪些是程序? 164 | 165 | ### 答案 166 | (1) 167 | 168 | |指令|寄存器| 169 | |--|--| 170 | | mov ax, 1 | | 171 | | mov dx, ax | | 172 | | mov ax, [0000] | AX=2662H | 173 | | mov bx, [0001] | BX=E626H | 174 | | mov ax, bx | AX=E626H | 175 | | mov ax, [0000] | AX=2662H | 176 | | mov bx, [0002] | BX=D6E6H | 177 | | add ax, bx | AX=FD48H | 178 | | add ax, [0004] | AX=2C14H | 179 | | mov ax, 0 | AX=0000H | 180 | | mov al, [0002] | AX=00E6H | 181 | | mov bx, 0 | BX=0000H | 182 | | mov bl, [000C] | BX=0026H | 183 | | add al, bl | AX=000CH | 184 | 185 | (2) 186 | 187 | 1. CPU执行序列如下: 188 | 189 | ```c 190 | mov ax, 6622H 191 | jmp 0ff0:0100 192 | mov ax, 2000H 193 | mov ds, ax 194 | mov ax, [0008] 195 | mov ax, [0002] 196 | ``` 197 | 2. CPU执行每条指令后,CS、IP和相关寄存器中的数值如下表所示: 198 | 199 | | 指令 | CS | IP | DS | AX | BX | 200 | |---------------|-------|-------|-------|-------|-------| 201 | | mov ax, 6622H | 2000H | 0003H | 1000H | 6622H | 0000H | 202 | | jmp 0ff0:0100 | 0ff0H | 0100H | 1000H | 6622H | 0000H | 203 | | mov ax, 2000H | 0ff0H | 0103H | 1000H | 2000H | 0000H | 204 | | mov ds, ax | 0ff0H | 0105H | 2000H | 2000H | 0000H | 205 | | mov ax, [0008]| 0ff0H | 0108H | 2000H | C389H | 0000H | 206 | | mov ax, [0002]| 0ff0H | 010BH | 2000H | EA66H | 0000H | 207 | 208 | 3. 程序和数据没有区别,都是内存单元中的二进制码。当内存单元被CS:IP指定时其存储的就被当做程序执行。当内存单元被DS:[address]指定时其存储的就是数据。 209 | 210 | ### 解析 211 | 略。 212 | 213 | 214 | ## 检测点3.2 215 | --- 216 | ### 问题 217 | (1)补全下面的程序,使其可以将10000H~1000FH中的8个字,逆序复制到20000H~2000FH中。逆 218 | 序复制的含义如图3.17(P70)所示(图中内存里的数据均为假设)。 219 | 220 | ```c 221 | mov ax, 1000H 222 | mov dx, ax 223 | ______________ 224 | ______________ 225 | ______________ 226 | push [0] 227 | push [2] 228 | push [4] 229 | push [6] 230 | push [8] 231 | push [A] 232 | push [C] 233 | push [E] 234 | ``` 235 | (2)补全下面的程序,使其可以将10000H~1000FH中的8个字,逆序复制到20000H~2000FH中。 236 | 237 | ```c 238 | mov ax, 2000H 239 | mov ds, ax 240 | ______________ 241 | ______________ 242 | ______________ 243 | pop [E] 244 | pop [C] 245 | pop [A] 246 | pop [8] 247 | pop [6] 248 | pop [4] 249 | pop [2] 250 | pop [0] 251 | ``` 252 | ### 答案 253 | (1) 254 | ```c 255 | mov ax, 2000H 256 | mov ss, ax 257 | mov sp, 0010H 258 | ``` 259 | (2) 260 | ```c 261 | mov ax 1000H 262 | mov ss, ax 263 | mov sp, 0000H 264 | ``` 265 | ### 解析 266 | 略。 267 | 268 | 269 | 270 | 271 | ## 检测点 6.1 272 | --- 273 | ### 问题 274 | (1)下面的程序实现依次用内存0:0~0:15单元中的内容改写程序中的数据,完成程序: 275 | ```c 276 | assume cs:codesg 277 | 278 | codesg segment 279 | dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h 280 | 281 | start: mov ax, 0 282 | mov ds, ax 283 | mov bx, 0 284 | 285 | mov cx, 8 286 | s: mov ax, [bx] 287 | 288 | ______________ 289 | add bx, 2 290 | loop s 291 | 292 | mov ax, 4c00h 293 | int 21h 294 | codesg ends 295 | end start 296 | 297 | ``` 298 | (2)下面的程序实现依次用内存0:00~0:15单元中的内容改写程序中的数据,数据的传送用栈来 299 | 进行。栈空间设置在程序内。完成程序: 300 | ```c 301 | assume cs:codesg 302 | 303 | codesg segment 304 | 305 | dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h 306 | 307 | dw 0,0,0,0,0,0,0,0,0,0,0,0 308 | 309 | start: mov ax, ______________ 310 | mov ss, ax 311 | mov sp, ______________ 312 | 313 | mov ax, 0 314 | mov ds, ax 315 | mov bx, 0 316 | mov cx, 8 317 | s: push [bx] 318 | 319 | ______________ 320 | add bx, 2 321 | loop 2 322 | 323 | mov ax, 4c00h 324 | int 21h 325 | 326 | codesg ends 327 | end start 328 | ``` 329 | ### 答案 330 | (1) 331 | ```c 332 | mov cs:[bx], ax 333 | ``` 334 | (2) 335 | ```c 336 | mov ax, cs 337 | ... 338 | mov sp 24h 339 | ... 340 | pop cx:[bx] 341 | ``` 342 | ### 解析 343 | 略。 344 | 345 | 346 | ## 检测点 9.1 347 | --- 348 | ### 问题 349 | 350 | (1)程序如下 351 | 352 | ```c 353 | assume cs:code 354 | 355 | data segment 356 | ? 357 | data ends 358 | 359 | code segment 360 | start:mov ax, data 361 | mov ds, ax 362 | mov bx, 0 363 | jmp word ptr [bx+1] 364 | code ends 365 | end start 366 | 367 | ``` 368 | 若要使程序中的jmp指令执行后,CS:IP指向程序的第一条指令,在data段中应该定义哪些数据? 369 | 370 | (2)程序如下: 371 | 372 | ```c 373 | assume cs:code 374 | 375 | data segment 376 | dd 12345678H 377 | data ends 378 | 379 | code segment 380 | start: mov ax, data 381 | mov ds, ax 382 | mov bx, 0 383 | mov [bx], ______________ 384 | mov [bx+2], ______________ 385 | jum dword ptr ds:[0] 386 | code ends 387 | end start 388 | ``` 389 | 补全程序,使jmp指令执行后,CS:IP指向程序第一条指令。 390 | 391 | (3)用debug查看内存,结果如下: 392 | 393 | ```c 394 | 2000:1000 BE 00 06 00 00 00 ...... 395 | ``` 396 | 则此时,CPU执行指令: 397 | ```c 398 | mov ax, 2000H 399 | mov es, ax 400 | jmp dword ptr es:[1000H] 401 | ``` 402 | 后,(CS)=?,(IP)=? 403 | ### 答案 404 | 405 | (1)完整程序如下 406 | 407 | ```c 408 | assume cs:code 409 | 410 | data segment 411 | db 0, 0, 0 ;第一个字节可以取8位任意值 412 | data ends 413 | 414 | code segment 415 | start:mov ax, data 416 | mov ds, ax 417 | mov bx, 0 418 | jmp word ptr [bx+1] 419 | code ends 420 | end start 421 | ``` 422 | 423 | (2)完整程序如下: 424 | 425 | ```c 426 | assume cs:code 427 | 428 | data segment 429 | dd 12345678H 430 | data ends 431 | 432 | code segment 433 | start: mov ax, data 434 | mov ds, ax 435 | mov bx, 0 436 | mov [bx], bx 437 | mov [bx+2], cs 438 | jmp dword ptr ds:[0] 439 | code ends 440 | end start 441 | ``` 442 | (3) 443 | 444 | (CS)= 0006H,(IP)=00BEH 445 | 446 | ### 解析 447 | 448 | 略。 449 | 450 | 451 | ## 检测点 9.2 452 | --- 453 | ### 问题 454 | 455 | 补全程序,利用jcxz指令,实现在内存2000H段中查找第一个值为0的字节,找到后,将它的偏移地址存储在dx中。 456 | 457 | ```c 458 | assume cs:code 459 | 460 | code segment 461 | 462 | start: mov ax, 2000H 463 | mov ds, ax 464 | mov bx, 0 465 | s: ______________ 466 | ______________ 467 | ______________ 468 | ______________ 469 | jmp short s 470 | ok: mov ds, bx 471 | 472 | mov ax, 4c00h 473 | int 21h 474 | 475 | code ends 476 | end start 477 | 478 | ``` 479 | 480 | ### 答案 481 | 482 | 完整程序如下: 483 | 484 | ```c 485 | assume cs:code 486 | 487 | code segment 488 | start: mov ax, 2000H 489 | mov ds, ax 490 | mov bx, 0 491 | 492 | s: mov ch, 0 493 | mov cl, [bx] 494 | jcxz ok 495 | inc bx 496 | jmp short s 497 | 498 | ok: mov ds, bx 499 | 500 | mov ax, 4c00h 501 | int 21h 502 | 503 | code ends 504 | end start 505 | ``` 506 | 507 | ### 解析 508 | 略。 509 | 510 | ### 检测点9.3 511 | 512 | ### 问题 513 | 514 | 补全编程,利用loop指令,实现在内存2000H段中查找第一个值为0的字节,找到后,将它的偏移地址存储在dx中。 515 | 516 | ```c 517 | assume cs:code 518 | 519 | code segment 520 | 521 | start:mov ax, 2000H 522 | mov ds, ax 523 | mov bx, 0 524 | 525 | s: mov cl, [bx] 526 | mov ch, 0 527 | ______________ 528 | inc bx 529 | loop s 530 | 531 | ok: dec bx 532 | mov dx, bx 533 | 534 | mov ax, 4c00h 535 | int 21h 536 | code ends 537 | end start 538 | ``` 539 | 540 | ### 答案 541 | 542 | 完整程序如下: 543 | 544 | ```c 545 | assume cs:code 546 | 547 | code segment 548 | 549 | start:mov ax, 2000H 550 | mov ds, ax 551 | mov bx, 0 552 | 553 | s: mov cl, [bx] 554 | mov ch, 0 555 | inc cx 556 | inc bx 557 | loop s 558 | 559 | ok: dec bx 560 | mov dx, bx 561 | 562 | mov ax, 4c00h 563 | int 21h 564 | code ends 565 | end start 566 | ``` 567 | 568 | ### 解析 569 | 略。 570 | 571 | 572 | ## 检测点10.1 573 | --- 574 | ### 问题 575 | 576 | 补全程序,实现从内存1000:0000处开始执行指令。 577 | 578 | ```c 579 | assume cs:code 580 | 581 | stack segment 582 | db 16 dup (0) 583 | stack ends 584 | 585 | code segment 586 | start: mov ax, stack 587 | mov ss, ax 588 | mov sp, 16 589 | mov ax, ____ 590 | push ax 591 | mov ax, ____ 592 | push ax 593 | retf 594 | code ends 595 | ``` 596 | 597 | ### 答案 598 | 599 | ```c 600 | ... 601 | mov ax, 1000h 602 | ... 603 | mov ax, 0000 604 | ... 605 | 606 | ``` 607 | 608 | ### 解析 609 | 略。 610 | 611 | ## 检测点 10.2 612 | --- 613 | 614 | ### 问题 615 | 616 | 下面的程序执行后,ax中的值为多少? 617 | 618 | | 内存地址 | 机器码 | 汇编指令 | 619 | |---------|--------|---------| 620 | |1000:0 |b8 00 00| mov ax, 0| 621 | |1000:3 |e8 01 00| call s | 622 | |1000:6 |40 | inc ax | 623 | |1000:7 |58 | s:pop ax | 624 | 625 | 626 | ### 答案 627 | 628 | ax值为6 629 | 630 | ### 解析 631 | 预加载指令 call s 时,ip指向下条指令地址 ip = 6。 632 | 633 | ## 检测点10.3 634 | --- 635 | ### 问题 636 | 637 | 下面的程序执行后,ax中的数值为多少? 638 | 639 | | 内存地址 | 机器码 | 汇编指令 | 640 | |---------|--------|--------| 641 | |1000:0 | b8 00 00 | mov ax, 0 | 642 | |1000:3 | 9A 09 00 00 10 | call far ptr s | 643 | |1000:8 | 40 | inc ax | 644 | |1000:9 | 58 | s : pop ax | 645 | | | |add ax, ax| 646 | | | |pop bx | 647 | | | |add ax, bx| 648 | 649 | 650 | ### 答案 651 | 652 | ax = 1010h 653 | 654 | ### 解析 655 | 各条指令执行后,相关寄存器的值的变化如下表所示: 656 | 657 | | 汇编指令 | 寄存器值 | 658 | |--------- |--------- | 659 | | mov ax, 0 |cs=1000h, ip=3, ax=0 | 660 | | call far ptr s |cs=1000h,ip=9, 原cs(1000)和ip(8)先后入栈 | 661 | | inc ax | 未执行 | 662 | | s : pop ax | ax=8 | 663 | | add ax, ax | ax=0010h | 664 | | pop bx | bx=1000h | 665 | | add ax, bx | ax=1000h+0010h=1010h| 666 | 667 | ## 检测点10.4 668 | --- 669 | 670 | ### 问题 671 | 672 | 下面的程序执行后,ax中的数值为多少? 673 | 674 | | 内存地址 | 机器码 | 汇编指令 | 675 | |---------|--------|---------| 676 | | 1000:0 | b8 06 00 | mov ax, 6 | 677 | | 1000:3 | ff d0 | call ax | 678 | | 1000:5 | 40 | inc ax | 679 | | 1000:6 | | mov bp, sp | 680 | | | | add ax, [bp] | 681 | 682 | ### 答案 683 | 684 | ax = 000bh 685 | 686 | ### 解析 687 | 688 | 各条指令加载及执行后相关寄存器的变化情况如下表所示: 689 | 690 | | 汇编指令 | 指令加载完毕 | 指令执行完毕 | 691 | |----------|------------|-------------| 692 | | mov ax, 6 | ip=3 | ax=6 | 693 | | call ax | ip=5 | ip=6,ss:[sp]=5 | 694 | | inc ax | | | 695 | | mov bp, sp| | bp = sp | 696 | | add ax, [bp] | | ax=6 + ss:[bp]=6+ss:[sp]=6+5=000bH | 697 | 698 | ## 检测点10.5 699 | --- 700 | 701 | ### 问题 702 | 703 | (1)下面程序执行后,ax中的数值为多少?(注意:用call指令的原理来分析,不要在debug中单步跟踪来验证你的结论。对于此程序,在Debug中单步跟踪的结果,不能代表CPU的实际执行结果。) 704 | 705 | ```c 706 | 707 | assume cs:code 708 | 709 | stack segment 710 | dw 8 dup (0) 711 | stack ends 712 | 713 | code segment 714 | 715 | start: mov ax, stack 716 | mov ss, ax 717 | mov sp, 16 718 | mov ds, ax 719 | mov ax, 0 720 | call word ptr ds:[0eh] 721 | inc ax 722 | inc ax 723 | inc ax 724 | mov ax, 4c00h 725 | int 21h 726 | 727 | code ends 728 | 729 | end start 730 | 731 | ``` 732 | 733 | (2)下面程序执行后,ax和bx的数值为多少? 734 | 735 | ```c 736 | 737 | assume cs:code 738 | 739 | data segment 740 | dw 8 dup (0) 741 | data ends 742 | 743 | code segment 744 | 745 | start: mov ax, data 746 | mov ss, ax 747 | mov sp, 16 748 | mov word ptr ss:[0], offset s 749 | mov ss:[2], cs 750 | call dword ptr ss:[0] 751 | nop 752 | s: mov ax, offset s 753 | sub ax, ss:[0cH] 754 | mov bx, cs 755 | sub bx, ss:[0eH] 756 | mov ax, 4c00h 757 | int 21h 758 | 759 | code ends 760 | 761 | end start 762 | 763 | ``` 764 | 765 | 766 | ### 答案 767 | (1) 768 | 769 | ax = 3 770 | 771 | (2) 772 | 773 | ax = 1, bx = 0 774 | 775 | ### 解析 776 | (1) 777 | 778 | 当执行call word ptr ds:[0EH]时,执行两步操作,第一步将原IP值入栈,第二步 779 | 将ds:[0EH]处的值赋值给IP。**实际上这两步操作的是同一个内存单元。** 780 | 781 | >此处有个疑问 782 | >刚尝试了一下用debug跟踪,但跟踪结果与理论分析不一致,不知道为什么 783 | > 784 | 785 | (2) 786 | 787 | 在执行【call dword ptr 内存单元地址】指令时,入栈顺序是CS→IP。赋值时双字内存单元前一个字储存的是IP的值,后一个字存储的是CS的值。 788 | 789 | 790 | 791 | ## 检测点11.1 792 | --- 793 | ### 问题: 794 | 795 | 写出下面每条指令执行后,ZF、PF、SF等标志位的值。 796 | 797 | |指令|ZF(零标志位)|PF(奇偶标志位)|SF(符号标志位)| 798 | |:-|||| 799 | |sub al, al|ZF = ___|PF = ___|SF = ___| 800 | |mov al, 1|ZF = ___|PF = ___|SF = ___| 801 | |push ax|ZF = ___|PF = ___|SF = ___| 802 | |pop bx|ZF = ___|PF = ___|SF = ___| 803 | |add al, bl|ZF = ___|PF = ___|SF = ___| 804 | |add al, 10|ZF = ___|PF = ___|SF = ___| 805 | |mul al|ZF = ___|PF = ___|SF = ___| 806 | 807 | ### 答案: 808 | 809 | |指令|ZF(零标志位)|PF(奇偶标志位)|SF(符号标志位)| 810 | |:-|||| 811 | |sub al, al|ZF = 1|PF = 1|SF = 0| 812 | |mov al, 1 |ZF = 1|PF = 1|SF = 0| 813 | |push ax |ZF = 1|PF = 1|SF = 0| 814 | |pop bx |ZF = 1|PF = 1|SF = 0| 815 | |add al, bl|ZF = 0|PF = 0|SF = 0| 816 | |add al, 10|ZF = 0|PF = 1|SF = 0| 817 | |mul al |ZF = 0|PF = 1|SF = 0| 818 | 819 | ### 解析: 820 | 821 | 注意,在8086CPU的指令集中,有的指令是影响标志寄存器的,比如,add、sub、mul、div、inc、or、and等,它们大都是运算指令(逻辑运算,或算术运算);有的指令的执行对标志寄存器没有影响,比如,mov、push、pop等,它们大都是传送指令。 822 | 823 | ## 检测点11.2 824 | --- 825 | ### 问题: 826 | 827 | 写出下面每条指令执行后,ZF、PF、SF、CF、OP等标志位的值。 828 | 829 | |指令|CF|OF|SF|ZF|PF 830 | |-|-|-|-|-|-| 831 | |sub al, al | | | | | | 832 | |mov al, 10H | | | | | | 833 | |add al, 90H | | | | | | 834 | |mov al, 80H | | | | | | 835 | |add al, 80H | | | | | | 836 | |mov al, 0FCH | | | | | | 837 | |add al, 05H | | | | | | 838 | |add al, 7DH | | | | | | 839 | |add al, 0BH | | | | | || 840 | 841 | ### 答案: 842 | 843 | |指令|CF|OF|SF|ZF|PF 844 | |-|-|-|-|-|-| 845 | |sub al, al |0 |0 |0 |1 |1 | 846 | |mov al, 10H |0 |0 |0 |1 |1 | 847 | |add al, 90H |0 |0 |1 |0 |1 | 848 | |mov al, 80H |0 |0 |1 |0 |1 | 849 | |add al, 80H |1 |1 |0 |1 |1 | 850 | |mov al, 0FCH |1 |1 |0 |1 |1 | 851 | |add al, 05H |1 |0 |0 |0 |0 | 852 | |add al, 7DH |1 |0 |0 |0 |0 | 853 | |add al, 0BH |0 |1 |1 |0 |1 | 854 | 855 | ### 解析: 856 | 857 | **CF(CarryFlag)** :进位标志位,记录**无符号运算**结果是否有进位/借位。如果有进位或者借位,则CF = 1。 858 | 859 | **OF(OverflowFlag)**:溢出标记位,记录**有符号运算**结果是否溢出,如果溢出,则OF = 1。 860 | 861 | **SF(SignFlag)**:符号标记位,记录有符号运算结果,如果是有符号运算结果为负数时,SF = 1。 862 | 863 | **ZF(ZeroFlag)**:零标记位,记录运算结果是否为0,如果运算结果为0,则ZF = 1。 864 | 865 | **PF(ParityFlag)**:奇偶标记位,记录运算结果二进制数中1的个数是否是偶数,如果1的个数为偶数,则PF = 1。 866 | 867 | 同时需要注意mov、push、pop等传送指令对flag没有影响。 868 | 869 | ## 检测点11.3 870 | --- 871 | ### 问题 872 | (1)补全下面的程序,统计F000:0处32个字节中,大小在[32,128]的数据的个数。 873 | 874 | ``` 875 | mov ax, 0f000h 876 | mov ds, ax 877 | 878 | mov bx, 0 879 | mov dx, 0 880 | mov cx, 32 881 | s: mov al, [bx] 882 | cmp al, 32 883 | ______________ 884 | cmp al, 128 885 | ______________ 886 | inc dx 887 | s0: inc bx 888 | loop s 889 | ``` 890 | (2)补全下面的程序,统计F000:0处32个字节中,大小在(32, 128)的数据的个数。 891 | ``` 892 | mov ax, 0f000h 893 | mov ds, ax 894 | 895 | mov bx, 0 896 | mov dx, 0 897 | mov cx, 32 898 | s: mov al, [bx] 899 | cmp al, 32 900 | ______________ 901 | cmp al, 128 902 | ______________ 903 | inc dx 904 | s0: inc bx 905 | loop s 906 | ``` 907 | ### 答案 908 | (1) 909 | 910 | ``` 911 | ... 912 | jb so 913 | ... 914 | ja so 915 | ... 916 | ``` 917 | 918 | (2) 919 | 920 | ``` 921 | ... 922 | jna so 923 | ... 924 | jnb so 925 | ... 926 | ``` 927 | 928 | 929 | ### 解析 930 | jb:低于则转移 931 | 932 | ja:高于则转移 933 | 934 | jna:不高于则转移 935 | 936 | jnb:不低于则转移 937 | 938 | ## 检测点 11.4 939 | --- 940 | ### 问题 941 | 下面的程序执行后:(ax)= ? 942 | 943 | ``` 944 | mov ax, 0 945 | push ax 946 | popf 947 | mov ax, 0fff0h 948 | add ax, 0010h 949 | push f 950 | pop ax 951 | and al, 11000101B 952 | and ah, 00001000B 953 | ``` 954 | ### 答案 955 | (ax) = 0000 0000 0100 0101 956 | 957 | ### 解析 958 | ``` 959 | mov ax, 0fff0h 960 | add ax, 0010h 961 | ``` 962 | 这两句代码执行过后,ax = 0000 0000 0000 0000。有进位,无溢出,结果为0。 963 | 所以各标志位的值分别为: 964 | ``` 965 | zf = 1 966 | pf = 1 967 | sf = 0 968 | cf = 1 969 | of = 0 970 | df = 0 971 | ``` 972 | 973 | ## 检测点12.1 974 | --- 975 | ### 问题 976 | (1)用Debug查看内存,情况如下: 977 | ``` 978 | 0000:0000 68 10 A7 00 8B 01 70 00-16 00 9D 03 8B 01 70 00 979 | ``` 980 | 则3号中断源对应的中断处理程序的入口地址为:【 】。 981 | 982 | (2)存储N号中断源对应的中断处理程序入口的偏移地址的内存单元的地址为:【 】。 983 | 存储N号中断源对应的中断处理程序入口的段地址的内存单元的地址为:【 】。 984 | 985 | ### 答案 986 | (1)0070:018B 987 | 988 | (2)4N, 4N+2 989 | 990 | ### 解析 991 | (1)在中断向量表中,一个中断向量占两个字,也即四个字节。高地址字存放段地址,低地址字存放偏移地址。 992 | 993 | (2)同上。 994 | 995 | ## 检测点13.1 996 | --- 997 | 998 | ### 问题 999 | 1000 | (1)在上面的内容中,我们用7ch中断例程实现loop的功能,则上面的7ch中断例程所能进行的最大转移位移是多少? 1001 | 1002 | (2)用7ch中断例程完成 jmp near ptr s 指令的功能,用bx向中断例程传送转移位移。应用举例:在屏幕的第12行,显示 data 段中以0结尾的字符串。 1003 | 代码范例略。 1004 | 1005 | ### 答案 1006 | (1) 1007 | 1008 | 7ch中断例程所能进行的最大转移位移范围是 [-32768, 32767]。即16位补码所能表示的数的范围。 1009 | 1010 | 在7ch例程中,只需满足 -32768 =< [bp+2] + bx <= 32767即可。当 [bp+2] = 0 时,bx最大可以为32767,最小可以为-32768。 1011 | 1012 | (2) 1013 | ```c 1014 | 1015 | ;检测点13.1(2) 1016 | ;用7ch中断例程完成 jmp near ptr s 指令的功能,用bx向中断例程传送转移位移。 1017 | ;应用举例:在屏幕的第12行,显示data段中以0结尾的字符串 1018 | 1019 | assume cs:code 1020 | data segment 1021 | db 'conversation', 0 1022 | data ends 1023 | 1024 | code segment 1025 | 1026 | start: 1027 | ;复制中断例程至指定内存地址 0000:0200h ↓ 1028 | mov ax, cs 1029 | mov ds, ax 1030 | mov si, offset jmpnp 1031 | mov ax, 0 1032 | mov es, ax 1033 | mov di, 0200h 1034 | mov cx, offset jmpnpend - offset jmpnp 1035 | cld 1036 | rep movsb 1037 | 1038 | ;设置中断例程所对应的中断向量表表项 ↓ 1039 | mov ax, 0 1040 | mov es, ax 1041 | mov word ptr es:[7ch * 4], 0200h 1042 | mov word ptr es:[7ch * 4 + 2], 0 1043 | 1044 | ;应用程序 ↓ 1045 | mov ax, data 1046 | mov ds, ax 1047 | mov si, 0 1048 | mov ax, 0b800h 1049 | mov es, ax 1050 | mov di, 12*160 1051 | 1052 | s: cmp byte ptr ds:[si], 0 1053 | je ok 1054 | mov al, ds:[si] 1055 | mov es:[di], al 1056 | mov byte ptr es:[di+1], 01000010b ;红底绿字 1057 | inc si 1058 | add di, 2 1059 | mov bx, offset s - offset ok 1060 | int 7ch 1061 | 1062 | ok: mov ax, 4c00h 1063 | int 21h 1064 | 1065 | ;中断例程 ↓ 1066 | jmpnp: push bp 1067 | mov bp, sp 1068 | add [bp + 2], bx 1069 | pop bp 1070 | iret 1071 | jmpnpend: nop 1072 | 1073 | code ends 1074 | end start 1075 | 1076 | ``` 1077 | 1078 | ### 解析 1079 | 略。 1080 | 1081 | ## 检测点13.2 1082 | --- 1083 | 1084 | ### 问题 1085 | 1086 | 判断下面说法的正误: 1087 | 1088 | (1)我们可以编程改变 FFFF:0 处的指令,使得 CPU 不去执行 BIOS 中的硬件系统检测和初始化程序。 1089 | 1090 | (2)int 19h 中断例程,可以由DOS提供。 1091 | 1092 | ### 答案 1093 | 1094 | (1) 1095 | 错误。 1096 | FFFF:0位于系统ROM区,只可读不可写。 1097 | 1098 | (2) 1099 | 错误。 1100 | 硬件系统检测和初始化完成后,然后调用 int 19h 进行操作系统的引导,之后才会把计算机交付给操作系统(此处是指 DOS )控制。 1101 | 1102 | ### 解析 1103 | 1104 | 略。 1105 | -------------------------------------------------------------------------------- /《汇编语言》第三版阅读笔记.md: -------------------------------------------------------------------------------- 1 | # **《汇编语言》第三版阅读笔记** 2 | --- 3 | ## **第一章 基础知识** 4 | 汇编课程研究重点放在如何利用硬件系统的编程结构和指令集有效灵活的控制系统进行工作。 5 | 6 | ### **1.1 机器语言** 7 | 机器语言是机器指令的集合。电子计算机的机器指令是一列二进制数字。计算机将之转换为一列高低电平,以使计算机的电子器件受到驱动,进行运算。 8 | 9 | 每一种微处理器都有自己的机器指令集,也就是机器语言。 10 | 11 | ### **1.2 汇编语言的产生** 12 | 机器语言难以辩别和记忆,基于此人们发明了汇编语言。 13 | 14 | **寄存器** 简单的讲是CPU中(内部)可以存储数据的器件。 15 | **编译器** 能够将汇编指令转换为机器指令的翻译程序。 16 | 17 | ### **1.3 汇编语言的组成** 18 | 汇编语言主要由以下3类指令组成: 19 | 20 | (1) 汇编指令:机器码的助记符,有对应的机器码。 21 | (2) 伪指令:没有对应的机器码,由编译器执行,计算机并不执行。 22 | (3) 其他符号:如 +、-、*、/等,由编译器识别,没有对应的机器码。 23 | 24 | ### **1.4 存储器** 25 | 26 | ### **1.5 指令和数据** 27 | 指令和数据是应用上的概念。在内存或磁盘上,指令和数据没有任何区别,都是二进制信息。 28 | 29 | ### **1.6 存储单元** 30 | 计算机内的最小信息单位是bit,即一个二进制位。 31 | 计算机内的基本存储单元是Byte,即一个字节。一个字节等于8个二进制位。 32 | 1KB = 1024B, 1MB = 1024KB, 1GB = 1024MB, 1TB = 1024GB 33 | 34 | ### **1.7 CPU 对存储器的读写** 35 | CPU通过地址总线给出数据存储位置。 36 | CPU通过控制总线给出数据存储方向。 37 | CUP通过数据总线进行数据传输。 38 | 39 | ### **1.8 地址总线** 40 | 一个CPU有N根地址线,则可以说这个CPU的地址总线宽度为N。这样的CPU最多可以寻找2的N次方个内存单元。 41 | 42 | ### **1.9 数据总线** 43 | 数据总线的宽度决定了CPU和外界的数据传送速度。 44 | 45 | ### **1.10 控制总线** 46 | CPU对外部器件的控制是通过控制总线来进行的。控制总线是一些不同控制线的集合。 47 | 48 | ### **1.1~1.10 小结** 49 | 50 | >(1) 汇编指令是机器指令的助记符,同机器指令一一对应。 51 | (2) 每一种CPU都有自己的汇编指令集。 52 | (3) CPU可以直接使用的信息在存储器中存放。 53 | (4) 在存储器中指令和数据没有任何区别,都是二进制信息。 54 | (5) 存储单元从零开始顺序编号。 55 | (6) 一个存储单元可以存储8个bit,即8位二进制数。 56 | (7) 1Byte = 8bit, 1KB = 1024B = 2^10B, 1MB = 1024KB = 2^20B, 1GB = 1024MB = 2^30B。2^10 = 1024, 2^16 = 65536。 57 | (8) 每一个CPU芯片都有许多管脚,这些管脚和总线相连。也可以说,这些管脚引出总线。一个CPU可以引出3中总线的宽度标志了这个CPU的不同方面的性能: 58 | 地址总线的宽度决定了CPU的寻址能力; 59 | 数据总线的宽度决定了CPU与其他器件进行数据传送时一次数据传送量; 60 | 控制总线的宽度决定了CPU对系统中其他器件的控制能力。 61 | 62 | ### **1.11 内存地址空间(概述)** 63 | 64 | 内存地址空间就是CPU可以通过地址总线寻址到的内存单元集合。 65 | 66 | ### **1.12 主板** 67 | 68 | ### **1.13 接口卡** 69 | 70 | CPU通过总线向接口卡发送命令,接口卡根据CPU的命令控制外设工作。如:网卡、显卡、声卡等。 71 | 72 | ### **1.14 各类存储器芯片** 73 | 74 | **RAM:** 随机存储器,可读可写,但必须带电存储,断电后存储内容消失。 75 | **ROM:** 只读存储器,只能读出,不能写入。断电后存储内容不消失。 76 | **BIOS:** Basic Input/Output System,基本输入输出系统。BIOS是由主板和各类接口卡(如显卡、网卡等)厂商提供的软件系统。 可以通过它利用该硬件设备进行最基本的输入输出。在主板和某些接口卡上茶油存储相应BIOS的ROM。例如,主板上的ROM中存储着主板的BIOS(通常称为系统BIOS);显卡上的ROM存储着显卡的BIOS;如果网卡上装有ROM,那其中就可以存储网卡的BIOS。 77 | 78 | ### **1.15 内存地址空间** 79 | 最终运行程序的是CPU,我们用汇编语言编程的时候,必须要从CPU的角度考虑问题。对CPU来讲,系统中所有存储器中的存储单元都处于一个统一的逻辑存储器中,它的容量受CPU寻址能力的限制。这个逻辑存储器即是我们所说的内存地址空间。 80 | 81 | --- 82 | 83 | ## **第二章 寄存器** 84 | 一个典型的CPU由运算器、控制器、寄存器等器件构成: 85 | - 运算器进行信息处理; 86 | - 寄存器进行信息存储; 87 | - 控制器控制各种器件进行工作; 88 | - 内部总线连接各种器件,在它们之间进行数据的传送。 89 | 90 | 寄存器是CPU内部的存储器件。 91 | 92 | 8086CPU内部有14个寄存器,分别是:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW。这14个寄存器都是16位的。 93 | 94 | ### **2.1 通用寄存器** 95 | 96 | AX、BX、CX、DX这这四个寄存器通常用来存放一般性数据,被称为通用寄存器。 97 | 98 | 为了保证向前兼容,8086CPU的AX、BX、CX、DX这4个寄存器可以分为两个独立使用的8位寄存器来用: 99 | - AX可以分为AH和AL; 100 | - BX可以分为BH和BL; 101 | - CX可以分为CH和CL; 102 | - DX可以分为DH和DL。 103 | 104 | ### **2.2 字在寄存器中的存储** 105 | 106 | 对于8086CPU来说,一个字由两个字节组成,这两个字节分别称之为高位字节和低位字节,并且存储于寄存器中的高8位和低8位。 107 | 108 | ### **2.3 几条汇编指令** 109 | 110 | mov ax, 001AH——转移指令 111 | add ax, bx——求和指令 112 | 113 | ### **2.4 物理地址** 114 | 115 | 所有内存单元构成的存储空间是一个一维线性空间,每一个内存单元在这个空间都有唯一的地址,我们将这个唯一的地址称为物理地址。 116 | 117 | ### **2.5 16位结构的CPU** 118 | 119 | 8086CPU是16位结构的CPU,这也就是说,在8086内部,能够一次性处理、传输、暂时存储的信息的最大长度是16位的。内存单元的地址在送上地址总线之前,必须在CPU中处理、传输、暂时存放,对于16位CPU,能一次性处理、传输、暂时存储16位的地址。 120 | 121 | 但8086CPU有20根地址总线,那么16位的8086CPU是如何给出20位的地址总线的呢? 122 | 123 | ### **2.6 8086CPU给出物理地址的方法** 124 | 125 | 8086CPU地址总线长度大于字长,导致程序物理地址无法一次性传递给CPU。为此,8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址。 126 | 127 | 当8086CPU要读写内存时: 128 | (1)CPU中的相关部件提供两个16位的地址,一个称为段地址,另一个称为偏移地址; 129 | (2)段地址和偏移地址通过内部总线送入一个称为地址加法器的部件; 130 | (3)地址加法器将两个16位地址合成为一个20位的物理地址; 131 | (4)地址加法器通过内部总线将20位物理地址送入输入输出控制电路; 132 | (5)输入输出控制电路将20位物理地址送上地址总线; 133 | (6)20位物理地址被地址总线送到存储器。 134 | 135 | 地址加法器采用物理地址 = 段地址X16 + 偏移地址的方法用段地址和偏移地址合成物理地址。 136 | 137 | ### **2.7 “段地址X16+偏移地址=物理地址”的本质含义** 138 | “段地址X16+偏移地址=物理地址”的本质含义是:CPU在访问内存时,用一个基础地址(段地址X16)和一个相对基础地址的偏移地址相加,给出内存单元的物理地址。 139 | 140 | 举个例子: 141 | 142 | 假如说隔壁部门的同事张三来找你询问李四的工位?你发现李四的工位不好直接描述,既不是在角落也不是在中间。这时你发现李四旁边坐着的是经理,所以你告诉张三说李四就是经理左边第二位的那个人。这就是生活中使用“基础地址+偏移地址=物理地址”的例子。 143 | 144 | 还有比方说,大家描述学校水房的位置,一般会说在几号餐厅后面,或某宿舍楼旁边。 145 | 146 | ### **2.8 段的概念** 147 | CPU访问内存单元时,必须向内存提供内存单元的物理地址。8086CPU在内部用段地址和偏移地址移位相加的方法形成最终的物理地址。 148 | 149 | CPU可以用不同的段地址和偏移地址形成同一个物理地址。 150 | 151 | 可以根据需要,将地址连续、起始地址为16的倍数的一组内存单元定义为一个段。 152 | 153 | ### **2.9 段寄存器*** 154 | 155 | 8086CPU内部有四个段寄存器:CS、DS、SS、ES。用于存储指定内存单元的段地址。 156 | 157 | ### **2.10 CS和IP** 158 | 159 | 8086PC机中,任意时刻,CPU将CS:IP指向的内容当作指令执行。其中CS为代码段寄存器,IP为指令指针寄存器。 160 | 161 | 8086CPU执行指令过程如下: 162 | 163 | (1) 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器; 164 | (2) IP = IP + 所读指令的长度,从而指向下一条指令; 165 | (3) 执行指令。转到步骤(1),重复这个过程。 166 | 167 | ### **2.11 修改CS、IP的指令** 168 | 能够改变CS、IP内容的指令被统称为转移指令。如jump指令。 169 | 170 | ### **2.12 代码段** 171 | 我们可以将长度为N(N <= 64KB)的一组代码,存在一组地址连续、起始地址为16的倍数的内存单元中。这段地质连续的内存空间就称之为代码段。简单来说也就是存放代码的段。 172 | 173 | ### **2.9~2.12小结** 174 | >(1) 段地址在8086CPU的段寄存器中存放。当8086CPU要访问内存时,由段寄存器提供内存单元的段地址。8086CPU有4个段寄存器,其中CS用来存放指令的段地址。 175 | (2) CS存放指令的段地址,IP存放指令的偏移地址。8086机中,任意时刻,CPU将CS:IP指向的内容当做指令指向。 176 | (3) 8086CPU工作过程:略 177 | (4) 8086CPU提供转移指令修改CS、IP的内容。 178 | 179 | --- 180 | 181 | ## **第三章 寄存器(内存访问)** 182 | 183 | 本章从内存访问的角度学习相关寄存器。 184 | 185 | ### **3.1 内存中字的存储** 186 | 字单元:存放一个字型数据(16位)的内存单元,由两个地址连续的内存单元组成。高地址内存单元存放字型数据的高位字节,低地址单元存放字型数据的低位字节。 187 | 188 | 这种存储方式也被称为小端存储,Intel系列的处理器一般都是小端存储。 189 | 190 | ### **3.2 DS和[Address]** 191 | 192 | 上一章我们学习了CS段寄存器,用于存放代码段段地址。这里我们再引入另外一个段寄存器DS,用于存放数据段段地址。 193 | 194 | 需要特别注意的是,8086CPU不支持将数据直接送入段寄存器。 包括所有的段寄存器CS、DS、SS、ES都不支持将数据从内存直接送入。内存中的数据必须先送入其他中间寄存器,然后在从中间寄存器送入段寄存器。(此处描述有误:栈操作"pop 段寄存器"实际上就是将数据从内存中直接送入段寄存器,此处应该更正为无法通过move指令将数据从内存中直接送入段寄存器) 195 | 196 | "[address]"表示一个内存单元,中括号中的address表示内存单元的偏移地址。默认情况下,8686CPU取DS中的数据作为该内存单元的段地址。 197 | 198 | ### **3.3 字的传送** 199 | 200 | 使用move指令一次可以传送一个字。move指令可以将数据从内存送入寄存器,也可以将数据从寄存器送入内存,也可以将数据从寄存器送入寄存器。**但move指令不支持内存到内存的传送。** 201 | 202 | ### **3.4 mov、add、sub指令** 203 | 204 | add指令和sub指令与mov指令用法类似,他们都有两个操作对象。这两个操作对象可以是如下格式: 205 | 寄存器, 数据 206 | 寄存器, 寄存器 207 | 寄存器, 内存单元 208 | 内存单元, 寄存器 209 | 210 | 有两点需要注意: 211 | (1) mov、add、sub指令的两个操作对象不能同时为内存单元。 212 | (2) 段寄存器只能接收mov指令传送数据,不可以进行算术运算。如 add ds, ax指令是违法的。(此处描述不够严谨,实际上段寄存器也可以接收来自操作栈的pop指令传递的数据) 213 | 214 | ### **3.5 数据段** 215 | 数据段是一段长度为N(N <= 64KB)、地址连续、其实地址为16的倍数的内存单元。我们用段寄存器DS存放数据段的段地址。 216 | 217 | ### **3.1~3.5小结** 218 | > (1) 字在内存中存储时,要用两个地址连续的内存单元来存放,字的低位字节存放在低地址单元中,高位字节存放在高地址单元中。 219 | (2) 用mov指令访问内存单元,可以在mov指令中只给出单元的偏移地址,此时,段地址默认在DS寄存器中。 220 | (3)[address]表示一个偏移地址为address的内存单元。 221 | (4) 在内存和寄存器之间传送数据时,高地址单元和高8位寄存器、低地址单元和低8位寄存器相对应。 222 | (5) mov、add、sub是具有两个操作对象的指令。jmp是具有一个操作对象的指令。 223 | (6) 可以根据自己的推测,在debug中实验指令的新格式。 224 | > 225 | 226 | ### **3.6 栈** 227 | 栈就是一种先进后出的数据结构。LIFO(Last In First Out)。 228 | 229 | ### **3.7 CPU提供的栈机制** 230 | 8086CPU对栈提供两个基本操作指令:PUSH(入栈)和POP(出栈)。 PUSH是将数据送入栈中,POP是将数据移出栈中。 231 | 232 | 前面我们已经学习了CS和DS两个段寄存器。并且知道CS:IP指向的内存单元被当做指令,DS:[address]指向的内存单元被当做数据。这里我们引入另外一个段寄存器SS,SS中保存的是栈顶元素的段地址,此外使用SP保存栈顶元素的偏移地址。故在任意时刻SS:SP都指向栈顶元素。 233 | 234 | PUSH AX的操作详情: 235 | (1)SP=SP-2,SS:SP指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶; 236 | (2)将ax中的内容送入SS:SP指向的内存单元,SS:SP此时指向新的栈顶。 237 | 238 | POP AX的操作详情: 239 | (1)将SS:SP指向的内存单元处的数据送入ax中; 240 | (2)SP=SP+2,SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。 241 | 242 | ### **3.8 栈顶超界问题** 243 | 244 | 当栈满的时候进行PUSH操作或者栈空的时候使用POP操作,都将引发栈顶超界问题。 245 | 246 | 8086CPU并未对栈顶超界做任何处理,程序员在编程的时候应当避免使得栈顶超界的情况发生。 247 | 248 | ### **3.9 push、pop指令** 249 | push指令和pop指令支持如下形式: 250 | ``` 251 | push 寄存器 252 | push 段寄存器 253 | push 内存单元 254 | 255 | pop 寄存器 256 | pop 段寄存器 257 | pop 内存单元 258 | ``` 259 | push、pop实际上就是一种内存传送指令,可以在寄存器和内存之间传送数据,与mov指令不同的是,push和pop指令访问的内存单元的地址不是在指令中给出的,而是由SS:SP指出的。同时,push和pop还要改变sp中的值。CPU执行mov指令仅需一步,CPU执行push和pop指令需要两步:传送数据和修改sp的值。 260 | 261 | 需要注意的是,push、pop等栈操作指令,修改的只是SP,也就是说,栈顶的变化范围最大为:0~FFFFH。 262 | 263 | 264 | >**栈的综述** 265 | (1)8086CPU提供了栈操作机制,方案如下。 266 | 在SS、SP中存放栈顶的段地址和偏移地址; 267 | 提供入栈和出栈指令,它们根据SS:SP指示的地址,按照栈的方式访问内存单元。 268 | (2)push指令的执行步骤:1、 SP=SP-2; 2、向SS:SP指向的字单元中送入数据。 269 | (3)pop指令的执行步骤:1、从SS:SP指向的字单元中读取数据;2、SP=SP+2。 270 | (4)任意时刻,SS:SP指向栈顶元素。 271 | (5)8086CPU只记录栈顶,栈空间的大小我们要自己管理。 272 | (6)用栈来暂存以后要恢复的寄存器的内容时,寄存器出栈的顺序要和入栈的顺序相反。 273 | (7)push、pop实际上是一种内存传送指令,注意它们的灵活应用。 274 | 栈是一种非常重要的机制,一定要深入理解,灵活掌握。 (P67) 275 | 276 | ### **3.10 栈段** 277 | 与代码段、数据段类似,我们在编程时,可以根据需要,将一组内存单元定义为一个段。我们可以将长度为N(N<=64KB)的一组地址连续、起始地址为16的倍数的内存单元,当做栈空间来用。只需要使用SS:SP指向它们。 278 | 279 | 一个栈最大为64KB,即偏移地址所能指向的最大范围。当一个大小为64KB的栈,其SP=0时则表示该栈为空或者栈满。 280 | 281 | > **段的综述** 282 | 我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元(通过偏移地址的移动来访问段内的单元)。这完全是我们自己的安排。 283 | > 284 | >我们可以用一个段存放数据,将它定义为“数据段”; 285 | 我们可以用一个段存放代码,将它定义为“代码段”; 286 | 我们可以用一个段当做栈,将它定义为“栈段”; 287 | > 288 | >我们可以这样安排,但若要让CPU按照我们的安排来访问这些段,就要: 289 | > 290 | > 对于数据段,将它的段地址放在DS中,用mov、add、sub等访问内存单元的指令时,CPU就将我们定义的数据段中的内容当做数据来访问; 291 | > 292 | 对于代码段,将它的段地址放在CS中,将段中第一条指令的偏移地址放在IP中,这样CPU就将执行我们定义的代码段中的指令; 对于栈段,将它的段地址放在SS中,将栈顶单元的偏移地址放在SP中,这样CPU在需要进行栈操作的时候,比如执行push、pop指令等,就将我们定义的栈段当做栈空间来用。 293 | > 294 | >可见,不管我们如何安排,CPU将内存中的某段内容当做代码,是因为CS:IP指向了那里;CPU将某段内存当做栈,是因为SS:SP指向了那里。我们一定要清楚,什么是我们的安排,以及如何让CPU按我们的安排行事。要非常清楚CPU的工作原理,才能在控制CPU按照我们安排运行的时候做到游刃有余。 295 | > 296 | >比如我们将10000H~1001FH安排为代码段,并在里面存储如下代码: 297 | ``` 298 | mov ax, 1000H 299 | mov ss, ax 300 | mov sp, 0020H ;初始化栈顶 301 | mov ax, cs 302 | mov ds, ax ;设置数据段段地址 303 | mov ax, [0] 304 | add ax, [2] 305 | mov bx, [4] 306 | add bx, [6] 307 | push ax 308 | push bx 309 | pop ax 310 | pop bx 311 | ``` 312 | >设置CS=1000H,IP=0,这段代码将得到执行。可以看到,在这段代码中,我们又将10000H~1001FH安排为栈段和数据段。10000H~1001FH这段内存,即是代码段,又是栈段和数据段。 313 | > 314 | >一段内存,可以即是代码的存储空间,又是数据的存储空间,还可以是栈空间,也可以什么也不是。关键在于CPU中寄存器的设置,即CS、IP,SS、SP,DS的指向。 315 | > 316 | >(p69) 317 | 318 | --- 319 | ## **第四章 第一个程序** 320 | 321 | ### **4.1 一个源程序从写出到执行的过程** 322 | 323 | 一个汇编语言程序从写出到最终执行主要经历三步: 324 | 325 | 第一步:编写汇编语言程序; 326 | 第二步:对源程序进行编译连接; 327 | 第三步:执行可执行文件中的程序。 328 | 329 | 对源程序进行编译连接生成可在操作系统中直接运行的可执行文件。可执行文件包含两部分内容。 330 | - 程序(从源程序中的汇编指令翻译过来的机器码)和数据(源程序中定义的数据) 331 | - 相关的描述信息(比如,程序有多大、要占用多少内存空间等) 332 | 333 | ### **4.2 源程序** 334 | 335 | 一段简单的汇编语言源程序: 336 | ```c 337 | assume cs:codesg 338 | 339 | codesg segment 340 | 341 | mov ax, 0123H 342 | mov bx, 0456H 343 | add ax, bx 344 | add ax, ax 345 | 346 | mov ax, 4c00H 347 | int 21H 348 | 349 | codesg ends 350 | 351 | end 352 | ``` 353 | #### **1. 伪指令** 354 | 在汇编语言源程序中,包括两种指令,一种是汇编指令,一种是伪指令。汇编指令是有对应的机器码的指令,可以被编译为机器指令,最终为CPU所执行。而伪指令没有对应的机器指令,最终不被CPU所执行。伪指令是由编译器来执行的指令,编译器根据伪指令来进行相应的编译工作。 355 | 356 | 下面介绍上面程序中所出现的几个伪指令: 357 | 358 | **(1) segment和ends ** 359 | 360 | segment和ends是一对成对使用的伪指令。功能是定义一个段。使用格式为: 361 | ``` 362 | 段名 segment 363 | . 364 | . 365 | 段名 ends 366 | ``` 367 | 一个汇编语言程序是由多个段组成的,这些段被用来存放代码(代码段)、数据(数据段)或当做栈空间(栈段)来使用。 368 | 369 | **(2)end ** 370 | 371 | end是一个汇编语言的结束标记,编译器在编译汇编程序的过程中,如果碰到了伪指令end,就结束对源程序的编译。 372 | 373 | **(3)assume ** 374 | 375 | 这条伪指令的含义为“假设”。它假设某一段寄存器和程序中的某一个用segment...ends定义的段相关联。即将定义的段的段地址存放在段寄存器中。 376 | 377 | #### **2. 源程序中的“程序”** 378 | 379 | 我们将源程序文件中的所有的内容称之为源程序,将源程序中最终由计算机执行、处理的指令或数据,称为程序。 380 | 381 | #### **3. 标号** 382 | 383 | 汇编源程序中,除了汇编指令和伪指令外,还有一些标号,比如“codesg”。**一个标号指代了一个地址**。比如codesg在segment的前面,作为一个段的名称,这个段的名称最终将被编译、连接程序处理为一个段的段地址。 384 | 385 | #### **4. 程序的结构** 386 | 387 | 源程序就是由一些段组成的。我们可以在这些段中存放代码、数据、或将某个段当做栈空间。 388 | 389 | #### **5. 程序返回** 390 | 一个程序结束后,将CPU控制权交还给使它得以运行的程序,我们称这个过程为**程序返回**。 程序返回指令: 391 | ```c 392 | mov ax, 4c00H 393 | int 21H 394 | ``` 395 | 当前我们不必理解这两天语句的含义。只要记住使用这两条指令可以实现程序返回。 396 | 397 | **段结束、程序结束、程序返回的区别** 398 | 399 | |目的 |相关指令 |指令性质 |指令执行者 | 400 | |----------- |------------|-----------|------------ | 401 | |通知编译器一个段结束|段名 ends |伪指令 |编译时,由编译器执行| 402 | |通知编译器程序结束 | end |伪指令 |编译时,由编译器执行 | 403 | |程序返回 |mov ax,4c00H int 21H| 汇编指令| 执行时,由于CPU执行| 404 | 405 | #### **6. 语法错误和逻辑错误** 406 | 407 | 一般来说,程序在编译时被编译器发现的错误是语法错误,如 mov ss, 1234 。 408 | 409 | 在程序编译后,在运行时发生的错误是逻辑错误,如除零操作。 410 | 411 | ### **4.3 编辑源程序** 412 | 413 | 源程序文件以.asm作为后缀。 414 | 415 | ### **4.4 编译** 416 | 417 | 我们使用微软的masm5.0汇编编译器进行编译,文件名为masm.exe。我们以c:\1.asm为例说明编译源程序的方法步骤。 418 | 419 | (1)进入DOS方式,运行masm.exe。 420 | ``` 421 | ... 422 | ... 423 | Source filename [.ASM]: _ 424 | ``` 425 | 426 | (2)输入要编译的源程序文件名,按enter键。 427 | ``` 428 | Source filename [.ASM]: c:\1.asm 429 | Object filename [1.obj]: 430 | ``` 431 | 输入要编译出的目标文件名,如果不输入则默认使用源程序名。 432 | 433 | (3)确定了目标文件名称后,继续按Enter键(两次),忽略中间文件的生成。 434 | 435 | 最终完成对源程序的编译,生成目标文件。目标文件以.obj作为后缀。 436 | 437 | ### **4.5 连接** 438 | 439 | 在对源程序进行编译生成目标文件后,我们需要对目标文件进行连接,从而得到可执行文件。 440 | 441 | 这里我们使用微软的Overlay Linker3.60连接器,文件名为link.exe。我们以4.4中生成的目标文件c:\masm\1.obj为例说明连接操作步骤。 442 | 443 | (1)进入DOS方式,运行link.exe。 444 | ``` 445 | ... 446 | ... 447 | Object Modules [.OBJ]: _ 448 | ``` 449 | 450 | (2)输入目标文件名,按enter键。 451 | ``` 452 | ... 453 | ... 454 | Object Modules [.OBJ]: 1.obj 455 | Run File [1.EXE]: _ 456 | ``` 457 | 键入生成可执行文件的名称。如果不输入则默认使用源程序名。 458 | 459 | (3)确定了可执行文件名后,按Enter键(两次),忽略镜像文件的生成,忽略库文件的连接。 460 | 461 | 经过以上三步后,最终会生成以.exe结尾的可执行文件。 462 | 463 | **连接的作用** 464 | 465 | (1)当源程序很大时,可以将它分为多个源程序文件来单独编译,然后将生成的目标文件连接在一起,节约程序编译时间。 466 | 467 | (2)程序中调用了某个库文件的子程序,需要将这个库文件和该程序生成的目标文件连接在一起,生成一个可执行文件。 468 | 469 | (3)一个源程序编译后,达到了存有机器码的目标文件,目标文件中的有些内容还不能直接用来生成可执行文件,连接程序将这些内容处理为最终的可执行信息。所以,在只有一个源程序文件,而又不需要调用某个库中的子程序的情况下,也必须用连接程序对目标文件进行处理,生成可执行 470 | 文件。 471 | 472 | ### **4.6 以简化的方式进行编译和连接** 473 | 一键编译: 474 | ``` 475 | masm c:\1.asm; 476 | ``` 477 | 478 | 一键连接: 479 | ``` 480 | link c:\1.obj; 481 | ``` 482 | 483 | ### **4.7 1.exe的执行** 484 | 485 | 进入dos环境,直接键入.exe可执行文件的文件名即可执行。 486 | 487 | ### **4.8 谁将可执行文件中的程序装载进入内存并使它运行** 488 | 489 | (1)在DOS中直接执行1.exe时,是正在运行的command,将1.exe中的程序加载入内存; 490 | 491 | (2)command设置CPU的CS:IP指向程序的第一条指令(即程序入口),从而使程序得以运行。 492 | 493 | (3)程序运行结束后,返回到command中,CPU继续运行command。 494 | 495 | > **操作系统的外壳** 496 | > 497 | >操作系统是由多个功能模块组成的庞大、复杂的软件系统。任何通用的操作系统,都要提供一个称为shell(外壳)的程序,用户(操作人员)使用这个程序来操作计算机系统进行工作。 498 | > 499 | >DOS中有一个程序command.exe,这个程序在DOS中称为命令解释器,也就是DOS系统的shell。 500 | > 501 | >DOS启动时,先完成其他重要的初始化工作,然后运行command.exe,command.exe运行后,执行完其他的相关任务后,在屏幕上显示出由当前盘符和当前路径组成的提示符,比如:“c:\”或“c:\windows”等,然后等待用户的输入。 502 | > 503 | >用户可以输入要执行的命令,比如,cd、dir、type等,这些命令由于command执行,command执行完这些命令后,再次显示当前盘符和当前路径组成的提示符,等待用户输入。 504 | > 505 | >如果用户要执行一个程序,则输入该程序可执行文件的名称,command首先根据文件名找到可执行文件,然后将这个可执行文件中的程序加载入内存,设置CS:IP指向程序的入口。此后,command暂停运行,CPU运行程序。程序运行结束后,返回到command中,command再次显示由当前盘符和当前路径组成的提示符,等待用户输入。 506 | > 507 | >在DOS中,command处理各种输入:命令或要执行的程序的文件名。我们就是通过command来进行工作的。 508 | 509 | **shell : 操作人员和OS之间的API。** 510 | 511 | >**汇编程序从写出到执行的过程** 512 | > 513 | >到此,完成了一个汇编程序从写出到执行的全部过程。我们经历了这样一个历程: 编程(Edit) → 1.asm → 编译(masm) → 1.obj → 连接(link) → 1.exe → 加载(command) → 内存中的程序 → 运行(CPU) 514 | 515 | ### **4.9 程序执行过程的跟踪** 516 | 517 | **DOS系统中.exe文件中程序的加载过程** 518 | 519 | (1)找到一段起始地址为SA:0000(即起始地址的偏移地址为0)的容量足够的空闲内存区; 520 | 521 | (2)在这段内存区的前256个字节中,创建一个称为程序段前缀(PSP)的数据区,DOS要利用PSP来和被加载程序进行通信; 522 | 523 | (3)从这段内存区的256字节处开始(在PSP后面),将程序装入,程序的地址被设为SA+10H:0;(空闲内存区从SA:0开始,0~255字节为PSP,从256字节处开始存放程序,为了更好地区分PSP和程序,DOS一般将它们划分到不同的段中,所以有了这样的地址安排: 524 | 空闲内存区:SA:00 525 | PSP区:SA:0 526 | 程序区:SA+10:0 527 | 注意:PSP和程序区虽然物理地址连续,却有着不同的段地址 528 | ) 529 | 530 | (4)将该内存区的段地址存取ds中,初始化其它相关寄存器后,设置CS:IP指向程序入口。 531 | 532 | 程序加载进内存后,cx中存放的是程序的长度,ds存放着程序所在内存区的段地址,cs存放可执行程序的段地址,ip存放着可执行程序的偏移地址。 533 | 534 | **Debug常用命令** 535 | 536 | 我们使用Debug对程序的执行过程进行跟踪。 537 | 用T命令单步执行程序的每一条执行。 538 | 用P命令执行程序结束语句int 21。 539 | 用Q命令退出debug。 540 | 541 | --- 542 | 543 | ## **第五章 [BX]和loop指令** 544 | 545 | **1. [bx]和内存单元的描述** 546 | 547 | 548 | [bx]表示一个**内存单元**,该内存单元的段地址位于ds中,偏移地址位于bx中。 549 | 该内存单元的完整地址为: ds*16 + bx。 550 | 551 | **2. loop** 552 | 553 | 循环指令。指令格式为:loop 标号 554 | 该指令分两步执行。 555 | 556 | 第一步,计算 cx = cx -1 557 | 第二步,判断cx中的值,不为零则跳转至标号出执行程序,如果为零则向下执行。 558 | 559 | **3. 我们定义的描述性的符号:“()”** 560 | 561 | “()”表示一个内存单元或寄存器的内容。也即是存储器中存储的值。 562 | 563 | “()”中的元素可以有3中类型:寄存器名、段寄存器名、内存单元的物理地址(一个20位数据)。 564 | 565 | **4. 约定符号idata表示常量** 566 | 567 | 在以后的学习中我们约定idata表示一个常量。 568 | 569 | ### **5.1 [BX]** 570 | 571 | [bx]表示一个**内存单元。** 572 | 573 | mov ax, [bx] 代码的含义:将ds:bx所指向内存单元的内容放入ax寄存器中。即:(ax)=((ds*16)+(bx)) 574 | 575 | mov [bx], ax 代码的含义:将ax中的内容放入ds:bx所指向的内存单元中。即:((ds*16)+(bx))=(ax) 576 | 577 | ### **5.2 Loop指令** 578 | 首先loop指令的格式是:loop 标号。该指令分两步执行: 579 | 580 | 第一步, 计算cx = cx - 1; 581 | 第二步,判断cx中的值,不为零则跳转至标号处执行程序,如果为零则向下执行。 582 | 583 | 一般使用loop指令实现循环功能。格式如下: 584 | ```c 585 | mov cx, n 586 | s: add ax, ax 587 | loop s 588 | ``` 589 | 以上代码会循环执行n次。(n >= 0) 590 | 591 | ### **5.3 在Debug中跟踪用loop指令实现的循环程序** 592 | 使用Debug调试程序时,有几条经常用到的指令。 593 | 594 | **T指令**,单步执行指令。 595 | 596 | **g指令**, 跳至断点,从当前IP执行至指定IP处。"g 0012"表示程序由当前位置执行至DS:0012处。 597 | 598 | **p指令**,循环执行指令,p指令用于执行完当前次数。 599 | 600 | ### **5.4 Debug和汇编编译器masm对指令的不同处理** 601 | Debug和汇编编译器masm对形如“mov ax, [0]”这类指令的处理是不同的。debug将"[0]"看做是一个内存单元,该内存单元的地址是 ds*6 + 0。而编译器直接将“[0]”看做立即数0。因此有如下约定。 602 | 603 | (1)在汇编源程序中,如果指令访问一个内存单元,则在指令中必须用"[...]"来表示内存单元,如果在“[...]”里用一个常量idata直接给出内存单元的偏移地址,就要在“[]”的前面显式的给出段地址所在的段寄存器。 604 | 605 | (2)如果在“[]”里面用寄存器,比如bx,间接给出内存单元的偏移地址,则段地址默认在ds中。当然,也可以显式的给出段地址所在的段寄存器。 606 | 607 | 以上两点概括来说就是,如果内存单元的偏移地址使用立即数给出,则必须显式指明其段地址所在的段寄存器。 608 | 609 | ### **5.5 loop和[bx]的联合应用** 610 | 通过loop和[bx]联合应用实现对连续内存单元的操作实例: 611 | ```c 612 | ... 613 | mov bx, 0 614 | mov cx, 50 615 | 616 | s: mov ax, [bx] 617 | inc bx 618 | loop s 619 | ... 620 | ``` 621 | 以上代码通过循环实现了对内存单元DS:0000H~DS:0032H内容的操作。 622 | 623 | ### **5.6段前缀** 624 | 如果内存单元的偏移地址由bx给出,如“mov ax, [bx]”,则段地址默认位于ds中。我们也可以在访问内存单元的指令中显式的给出内存单元段地址所在的段寄存器。比如: 625 | 626 | (1)mov ax, ds:[bx] 627 | (2)mov ax, cs:[bx] 628 | (3)mov ax, ss:[bx] 629 | (4)mov ax, cs:[bx] 630 | (5)mov ax, ss:[0] 631 | (6)mov ax, cs:[0] 632 | 633 | 这些出现在访问内存单元的指令中,用于显式的指明内存单元的段地址的“ds:”、“cs:”、“ss:”、“es:”,在汇编语言中称为**段前缀**。 634 | 635 | ### **5.7 一段安全的空间** 636 | (1)我们需要直接向一段内存汇总写入内容; 637 | 638 | (2)这段内存空间不应当存放系统或其他程序的数据或代码,否则写入操作很可能引发错误; 639 | 640 | (3)DOS方式下,一般情况,0:200~0:2ff空间中没有系统或其他程序的数据或代码; 641 | 642 | (4)以后,我们需要直接向一段内存中写入内容时,就使用0:200~0:2ff这段空间。 643 | 644 | ### **5.8 段前缀的使用** 645 | 当需要操作的内存空间跨段时,显式的使用段前缀给出内存单元的段地址,可以避免在循环中对ds的重复设置。 646 | 647 | 也即是说一个内存单元的段地址不仅仅可以由ds给出,也可以通过cs、ss、es给出。 648 | 649 | --- 650 | 651 | ## **第六章 包含多个段的程序** 652 | 程序取得所需空间的方法有两种,一是在加载程序的时候为程序分配,再就是程序在执行的过程向系统分配。在本课程中,我们只讨论第一种方法。 653 | 654 | ### **6.1 在代码段中使用数据** 655 | 下面一段代码用于计算8个数据的累加和,结果放在ax寄存器中: 656 | 657 | ```c 658 | assume cs:code 659 | 660 | code segment 661 | 662 | dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h 663 | 664 | start mov bx, 0 665 | mov ax, 0 666 | 667 | mov cx, 8 668 | s: add ax, cs:[bx] 669 | add bx, 2 670 | loop s 671 | 672 | mov ax, 4c00H 673 | int 21H 674 | code ends 675 | end start 676 | ``` 677 | 分析这段代码,我们使用dw定义了8个字型数据,并且使用 “end 标号”的形式指明了程序的入口。 678 | 679 | ### **6.2 在代码段中使用栈** 680 | ```c 681 | assume cs:codesg 682 | 683 | codesg segment 684 | 685 | dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h 686 | dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 687 | 688 | start: mov ax, cs 689 | mov ss, ax 690 | mov sp, 30h 691 | 692 | mov bx, 0 693 | mov cx, 8 694 | s: push cs:[bx] 695 | add bx, 2 696 | loop s 697 | 698 | mov bx, 0 699 | mov cx, 8 700 | s0: pop cs:[bx] 701 | add bx, 2 702 | loop s0 703 | 704 | mov ax, 4c00h 705 | int 21h 706 | 707 | codesg ends 708 | end start 709 | ``` 710 | 711 | ### **6.3 将数据、代码、栈放入不同的段** 712 | 713 | ```c 714 | assume cs:code, ds:data, ss:stack 715 | 716 | data segment 717 | dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h 718 | data ends 719 | 720 | stack segment 721 | dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 722 | stack ends 723 | 724 | code segment 725 | 726 | start: mov ax, stack 727 | mov ss, ax 728 | mov sp, 20h 729 | 730 | mov ax, data 731 | mov ds, ax 732 | 733 | mov bx, 0 734 | 735 | mov cx, 8 736 | s: push [bx] 737 | add bx, 2 738 | loop s 739 | 740 | mov bx, 0 741 | 742 | mov cx, 8 743 | s0: pop [bx] 744 | add bx, 2 745 | loop s0 746 | 747 | mov ax, 4c00h 748 | int 21h 749 | 750 | code ends 751 | 752 | end start 753 | 754 | ``` 755 | 下面对以上代码进行说明。 756 | 757 | (1)定义多个段的方法 758 | 759 | 定义数据段、栈段与定义代码段的方法没有区别,只是对于不同的段,要有不同的段名。 760 | 761 | (2)对段地址的引用 762 | 763 | 在程序中,段名就相当于一个标号,它代表了段地址。例如程序中“data”段中的数据“0abch”的地址就是:data:6。要将它送入bx中,代码如下: 764 | ```c 765 | mov ax, data 766 | mov ds, ax 767 | mov bx, ds:[6] 768 | ``` 769 | (3)“代码段”、“数据段”、“栈段”完全是我们的安排 770 | 771 | 我们通过“end 标号”的形式来声明程序的入口地址,这个入口信息被写入可执行文件中的描述信息中。可执行文件中的程序被加载入内存后,CPU的CS:IP就会被设置指向这个入口。 772 | 773 | 我们通过如下代码来指定程序的栈段: 774 | ```c 775 | mov ax, stack 776 | mov ss, ax 777 | mov sp, 20h 778 | ``` 779 | 通过如下代码来指定数据段: 780 | ```c 781 | mov ax, data 782 | mov ds, ax 783 | ``` 784 | 总而言之,CPU到底如何处理我们定义的段中的内容,是当作指令执行,当作数据访问,还是当作栈空间,完全靠程序中具体的汇编指令,和汇编指令对CS:IP、SS:SP、DS等寄存器的设置来决定的。 785 | 786 | --- 787 | ## **第七章 更灵活的定位内存地址的方法** 788 | 本章主要讲不同的寻址方式。 789 | 790 | ### **7.1 and和or指令** 791 | and表示逻辑与。or表示逻辑或。 792 | 793 | ### **7.2 关于ASCII码** 794 | ASCII码:American Standard Code for Information Interchange,美国信息交换标准代码。用8位(一个字节)二进制数表示一个字符。起初定义了128个字符,后来扩展至256个。 795 | 796 | 当我们再键盘上按下字母a键,屏幕上显示a字母,这其中经历了哪些过程? 797 | 1. a被ASCII编码为数字61H存储在指定内存空间内。 798 | 2. 文本编辑器软件从内存中取出61H,将其送入显卡显存中。 799 | 3. 显卡根据ASCII编码将61H反译为字母a,同时显卡驱动显示器,将字母a的图像画在屏幕上。 800 | 801 | 通过以上3步,我们就看到了字母a被显示在屏幕上。 802 | 803 | ### **7.3 以字符形式给出的数据** 804 | 在汇编程序中,使用引号‘’括起来的内容被识别为字符,编译器将把它转换为对应的ASCII码。 805 | 806 | ### **7.4 大小写转换的问题** 807 | 在ASCII码中,小写字母的对应范围为:61H - 7AH。大写字母的对应范围为:41H - 5AH。可见同一个字母的大写形式的ASCII码比小写形式的ASCII码小20H。 808 | 809 | 仔细观察大小写字母所对应的ASCII吗二进制形式,可以发现如下规律:大写字母从右数第6位(从1开始计算)全为0,小写字母从右数第6位全为1。 810 | 811 | 综上我们可以总结出大小写转换的两种方式: 812 | 813 | **字母大小写转换方式1:** 814 | 1. 大写字母加上20h可转换为小写字母。 815 | 2. 小写字母减去20h可转换为大写字母。 816 | 817 | **字母大小写转换方式2:** 818 | 1. 字母转大写:逻辑与11011111B。 819 | 2. 字母转小写:逻辑或00100000B。 820 | 821 | ### **7.5 [bx+idata]** 822 | 这是一种"变量+常量"的寻址方式。 823 | 824 | [bx+idata]表示一个内存单元,它的偏移地址为(bx)+idata。 825 | 指令: 826 | ```c 827 | mov ax, [bx+200] 828 | ``` 829 | 表示将一个内存单元的内容送入ax,这个内存单元的长度为2个字节(字单元),存放一个字,偏移地址为bx中的数值加上200,段地址在ds中。 830 | 831 | 该指令的常用格式有: 832 | ```c 833 | mov ax, [bx+200] 834 | mov ax, 200[bx] 835 | mov ax, [bx].200 836 | ``` 837 | 838 | ### **7.6 用[bx+idata]的方式进行数组的处理** 839 | 我们可以将地址连续的多个数据当做数组处理。例如定义如下数据: 840 | ```c 841 | datasg segment 842 | db 'BaSiC' 843 | db 'MinIX' 844 | datasg ends 845 | ``` 846 | 我们可以把如上两个字符串当做两个数组,一个数组下标从0开始,一个数组下标从5开始。在程序中使用[bx+0]和[bx+5]的方式定位两个字符串的首地址。从而可以在一个循环当中同时处理两组数据。 847 | 848 | 回忆我们在高级语言中用到的数组取值方式(如c或java):a[index]。可以看出这就是汇编语言中[bx+idata]形式的变种。a与idata相对应,是一常量,表示了数组的首地址。而下标index与bx对应,是一变量,表示数组下标。 849 | 850 | ### **7.7 SI和DI** 851 | SI是Source Index的缩写。DI是Destination Index的缩写。它俩的功能与bx相近,但SI和DI不能够分成两个8位寄存器来使用。下面三组指令实现了相同的功能: 852 | 853 | ```c 854 | ;(1) 855 | mov bx, 0 856 | mov ax, [bx] 857 | 858 | ;(2) 859 | mov si, 0 860 | mov ax, [si] 861 | 862 | ;(3) 863 | mov di, 0 864 | mov ax, [di] 865 | ``` 866 | 下面的三组指令也实现了相同的功能: 867 | 868 | ```c 869 | ;(1) 870 | mov bx, 0 871 | mov ax, [bx+123] 872 | 873 | ;(2) 874 | mov si, 0 875 | mov ax, [si+123] 876 | 877 | ;(3) 878 | mov di, 0 879 | mov ax, [di+123] 880 | ``` 881 | 882 | ### **7.8 [bx+si]和[bx+di]** 883 | 这是一种“变量+变量”的寻址方式。 884 | 885 | [bx+si]和[bx+di]含义相似,都是表示一个内存单元。该内存单元的段地址位于ds中,偏移地址为bx的值加上si的值(或bx的值加上di的值)。 886 | 887 | 该指令的常用格式有: 888 | 889 | ```c 890 | mov ax, [bx+si] 891 | mov ax, [bx][si] 892 | ``` 893 | 894 | ### **7.9 [bx+si+idata]和[bx+di+idata]** 895 | 这是一种"变量+变量+常量"的寻址方式。 896 | 897 | 常用指令格式: 898 | 899 | ```c 900 | mov ax, [bx+200+si] 901 | mov ax, [200+bx+si] 902 | mov ax, 200[bx][si] 903 | mov ax, [bx].200[si] 904 | mov ax, [bx][si].200 905 | ``` 906 | ### **7.10 不同的寻址方式的灵活应用** 907 | 总结一下前面讲到的几种定位内存地址的方法(寻址方式): 908 | 909 | (1)[idata]用一个常量来表示地址,可用于直接定位一个内存单元; 910 | (2)[bx]用一个变量来表示内存地址,可用于间接定位一个内存单元; 911 | (3)[bx+idata]用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元; 912 | (4)[bx+si]用两个变量表示地址; 913 | (5)[bx+si+idata]用两个变量和一个常量表示地址。 914 | 915 | 916 | 下一章中,我们将对寻址方式的问题进行更深入的探讨,之所以如此重视这个问题,是因为寻址方式的适当应用,使我们可以以更合理的结构来看待所要处理的数据。而**为所要处理的看似杂乱的数据设计一种清晰的数据结构是程序设计的一个关键问题**(个人认为这段话说的很有道理,特记录于此) 917 | 918 | --- 919 | 920 | ## **第八章 数据处理的两个基本问题** 921 | 本章旨在进一步加强对不同寻址方式的理解及运用。 922 | 923 | 计算机是进行数据处理、运算的机器,这其中包含两个基本的问题: 924 | 925 | (1)处理的数据在什么地方? 926 | (2)要处理的数据有多长? 927 | 928 | 携带着这两个问题,我们开启第八章的学习之路。 929 | 930 | ### **8.1 bx、si、di和bp** 931 | 932 | 首先看下这四个寄存器的含义: 933 | 934 | bx, Base,Pointer to base addresss (data)。一般用于存储数据段的基址(首地址)。 935 | si,Source Index,Source string/index pointer。一般用于存储源数组数据索引(下标)。 936 | di,Destination Index,estination string/index pointer。一般用于存储目标数组数据索引(下标)。 937 | bp,Base Pointer,Pointer to base address (stack)。一般用于存储栈的基址。 938 | 939 | 然后在使用过程中有几处需要注意的地方: 940 | 941 | (1)在8086CPU中,只有这4个寄存器可以用在“[....]”中来进行内存单元的寻址。其他寄存器是不可以的,例如“mov bx, [ax]”就是错误的用法。 942 | 943 | (2)在[...]中,这四个寄存器可以单个出现,或只能以4种组合出现:bx和si、bx和di、bp和si、bp和di。为了方便记忆可以将si和di看做一组,将bx和bp看做一组。组间可以自由组合,组内不能组合。(脑补为人类不可以近亲繁殖。) 944 | 945 | (3)只要在[...]中使用寄存器bp,而指令中没有显性的给出段地址,则段地址就默认在ss中。 946 | 947 | ### **8.2 机器指令处理的数据在什么地方** 948 | 949 | 这是我们在开头抛出的两个问题中的第一个。 950 | 951 | 在指令执行前,所要处理的数据可以在3个地方:CPU内部、内存、端口(端口暂时不用知道是什么东西)。 952 | 953 | 我们知道了存储数据的部件,但如果具体找到这些部件存储的数据位置?下一节将解答我们的疑问。 954 | 955 | ### **8.3 汇编语言中数据位置的表达** 956 | 957 | 在汇编语言中如何表达数据的位置? 958 | 959 | 汇编语言中用3个概念来表达数据的位置。 960 | 961 | (1)立即数 962 | 963 | 对于直接包含在机器指令中的数据(执行前在CPU的指令缓冲器中),汇编语言中称为立即数(idata),在汇编指令中直接给出。 964 | 例如: 965 | ```c 966 | mov ax, 1 967 | or bx, 00100000B 968 | ``` 969 | 970 | (2)寄存器 971 | 972 | 指令要处理的数据在寄存器中,在汇编指令中给出相应的寄存器名。例如: 973 | ```c 974 | mov ax, bx 975 | push bx 976 | ``` 977 | 978 | (3)段地址(SA)和偏移地址(EA) 979 | 980 | 指令要处理的数据在内存中,在汇编语言中可以用[X]的格式给出EA,SA在某个段寄存器中。 981 | 982 | 存放段地址的寄存器可以是默认的,也可以显性给出。例如: 983 | ```c 984 | ;段寄存器默认存储在DS中 985 | mov ax, [0] 986 | mov ax, [bx] 987 | mov ax, [di] 988 | ... 989 | 990 | ;段寄存器默认存储在SS中 991 | mov ax, [bp] 992 | mov ax, [bp+si] 993 | mov ax, [bp+di] 994 | ... 995 | 996 | ;段寄存器显性给出 997 | mov ax, ds:[bp] 998 | mov ax, es:[3] 999 | mov ax, ss:[bx+si] 1000 | mov ax, cs:[bx+si+8] 1001 | ``` 1002 | 1003 | ### **8.4 寻址方式** 1004 | 1005 | 这一节我们总结一下所学到过的寻址方式。列表如下: 1006 | 1007 | |寻址方式| 含义 | 名称 | 常用格式举例|备注| 1008 | |--------|----|---|---| 1009 | |[idata]|EA=idata; SA=(ds)| 直接寻址|[idata]|偏移地址=立即数| 1010 | |[bx] |EA=(bx); SA=(ds)| 寄存器间接寻址|[bx]|偏移地址=变量| 1011 | |[bx+idata]| EA=(bx)+idata; SA=(ds)|寄存器相对寻址| 用于结构体:[bx].idata; 用于数组:idata[si],idata[di]; 用于二维数组:[bx][idata] |偏移地址=变量+立即数| 1012 | |[bx+si]|EA=(bx)+(si); SA=(ds)|基址变址寻址|用于二维数组:[bx][si]|偏移地址=变量+变量| 1013 | |[bx+si+idata]|EA=(bx)+(si)+idata; SA=(ds)|相对基址变址寻址| 用于表格(结构)中的数组项:[bx].idata[si]; 用于二维数组idata[bx][si]|偏移地址=变量+变量+立即数| 1014 | 1015 | 注意在8.1节指出的特殊情况,只要在[...]中使用寄存器bp,而指令中没有显性的给出段地址,段地址就默认在ss中。 1016 | 1017 | ### **8.5 指令要处理的数据有多长** 1018 | 1019 | 这是我们在开头抛出的两个问题中的第二个。 1020 | 1021 | 8086CPU的指令,可以处理两种尺寸的数据,byte和word。所以在机器指令中要指明,指令进行的是字操作还是字节操作。对于这个问题,汇编语言中用以下方法处理。 1022 | 1023 | (1)通过寄存器名指定要处理的数据的尺寸。如果寄存器名是字型寄存器(如ax、bx等),则说明指令进行的是字操作。如果寄存器名是字节型寄存器(如al、ah、bl等),则说明指令进行的是字节操作。 1024 | 1025 | (2)在没有寄存器名存在的情况下,用操作符X ptr指明内存单元的长度,X在汇编指令中可以为word或byte。这种情形适用于没有寄存器参与的内存单元访问指令中。例如: 1026 | ```c 1027 | ;下面的指令,用word ptr指明了指令中访问的内存单元是一个字单元 1028 | mov word ptr ds:[0], 1 1029 | inc word ptr [bx] 1030 | 1031 | ;下面的指令,用byte ptr指明了指令访问的内存单元是一个字节单元 1032 | mov byte ptr ds:[0], 1 1033 | inc byte ptr [bx] 1034 | ``` 1035 | 1036 | (3)其他方法。有些指令默认了访问的是字单元还是字节单元,比如,push[1000H]就不用指明访问的是字单元还是字节单元,因为push指令只会进行字操作。 1037 | 1038 | ### **8.6 寻址方式的综合应用** 1039 | 1040 | 8086CPU提供的如[bx+si+idata]的寻址方式为结构化数据的处理提供了方便。使得我们可以在编程的时候,从结构化的角度去看待所要处理的数据。正常情况下,一个结构化的数据包含了多个数据项,而数据项的类型又不相同,有的是字型数据,有的是字节型数据,有的是数组(字符串)。一般来说,我们可以用[bx+idata+si]的方式来访问结构体中的数据。用bx定位整个结构体,用idata定位结构体中的某一个数据项,用si定位数组项中的元素。为此,汇编语言提供了更为贴切的书写方式,如[bx].idata、[bx].idata[si]。 1041 | 1042 | ### **8.7 div指令** 1043 | 1044 | div是除法指令。在使用的过程中应注意以下问题: 1045 | 1046 | (1)除数:有8位和16位两种,在一个reg(寄存器)或内存单元中。 1047 | 1048 | (2)被除数:默认放在AX或DX和AX中,如果除数为8位,被除数则为16位,默认在AX中存放;如果除数为16位,被除数则为32位,在DX和AX中存放,DX存放高16位,AX存放低16位。 1049 | 1050 | (3)结果:如果除数为8位,则AL存储除法操作的商,AH存储除法操作的余数;如果除数为16位,则AX存储除法操作的商,DX存储除法操作的余数。 1051 | 1052 | div使用格式如下: 1053 | >div reg 1054 | >div 内存单元 1055 | ### **8.8 伪指令dd** 1056 | 1057 | dd用来定义dword(双字)型数据。 1058 | 1059 | ### **8.9 dup** 1060 | 1061 | dup(duplication的缩写)用来重复开辟内存空间。 1062 | 1063 | dup指令要和db、dw、dd等数据定义伪指令配合使用,使用格式如下: 1064 | 1065 | > db 重复次数 dup (重复的字节型数据) 1066 | > dw 重复次数 dup (重复的字型数据) 1067 | > dd 重复次数 dup (重复的双字型数据) 1068 | 1069 | 例如,如下代码表示定义了9个字节: 1070 | ```c 1071 | db 3 dup (0,1,2) 1072 | ``` 1073 | 1074 | --- 1075 | ## **第九章 转移指令的原理** 1076 | 1077 | 本章主要讲如何控制CPU执行指令的顺序。 1078 | 1079 | 可以修改IP,或同时修改CS和IP的指令统称为转移指令。概括的降,转移指令就是可以控制CPU执行内存中某处代码的指令。 1080 | 1081 | 8086CPU的转移指令有以下几类。 1082 | 1083 | - 只修改IP时,称为**段内转移**,比如:jum ax。 1084 | - 同时修改CS和IP时,称为**段间转移**,比如:jmp 1000:0。 1085 | 1086 | 由于转移指令对IP的修改范围不同,段内转移又分为:短转移和近转移。 1087 | 1088 | - 短转移IP的修改范围为-128~127。 1089 | - 近转移IP的修改范围为-32768~32767。 1090 | 1091 | 8086CPU的转移指令分为以下几类。 1092 | 1093 | - 无条件转移指令(如jmp) 1094 | - 条件转移指令 1095 | - 循环指令(如loop) 1096 | - 过程 1097 | - 中断 1098 | 1099 | 这些转移指令的前提条件可能不同,但转移的基本原理是相同的。 1100 | 1101 | ### **9.1 操作符 offset** 1102 | 1103 | 操作符offset在汇编语言中是由编译器处理的符号,它的功能是取得标号的偏移地址。 1104 | 1105 | ### **9.2 jmp 指令** 1106 | 1107 | jmp为无条件转移指令,可以只修改IP,也可以同时修改CS和IP。 1108 | 1109 | jmp指令要给出两种信息: 1110 | 1111 | (1)转移的目的地址 1112 | (2)转移的距离(段间转移、段内短转移、段内近转移) 1113 | 1114 | 下面几节将对jmp指令进行详细的介绍。 1115 | 1116 | ### **9.3 依据位移进行转移的jmp指令** 1117 | 1118 | > jmp short 标号 1119 | 1120 | 实现的是段内短转移,执行后: (IP) = (IP)+ 8位位移。 1121 | 1122 | (1)8位位移=标号处的地址-jmp指令后第一个字节的地址; 1123 | (2)short指明此处的位移为8位位移; 1124 | (3)8位位移的范围为-128~127,用补码表示; 1125 | (4)8位位移由编译程序在编译时算出。 1126 | 1127 | > jmp near ptr 标号 1128 | 1129 | 实现的是段内近转移,执行后:(IP) = (IP) + 16位位移。 1130 | 1131 | (1)16位位移=标号处的地址-jmp指令后第一个字节的地址; 1132 | (2)near ptr 指明此处的位移为16位位移,进行的是段内近转移; 1133 | (3)16位位移的范围为-32768~32767,用补码表示; 1134 | (4)16位位移由编译程序在编译时算出。 1135 | 1136 | ### **9.4 转移的目的地址在指令中的jmp指令** 1137 | 1138 | > jmp far ptr 标号 1139 | 1140 | 实现的是段间转移,又称为远转移。功能如下: 1141 | (CS)=标号所在段的段地址;(IP)=标号所在段中的偏移地址。 1142 | 1143 | far ptr指明了指令用标号的段地址和偏移地址修改CS和IP。 1144 | 1145 | 该指令与上节学习的段内转移明显不同的是: 1146 | 段内转移机器指令携带的是位移,段间转移机器指令携带的是目的地址。 1147 | 1148 | 1149 | ### **9.5 转移地址在寄存器中的jmp指令** 1150 | 1151 | > jmp 16位的reg 1152 | 1153 | 该指令实现的功能为:(IP)= (16位的reg) 1154 | 1155 | ### **9.6 转移地址在内存中的jmp指令** 1156 | 1157 | > jmp word ptr 内存单元地址(段内转移) 1158 | 1159 | 功能:从内存单元地址处开始存放一个字,是转移的目的偏移地址。 1160 | 1161 | 内存单元地址可以用之前学过的任一寻址方式给出。 1162 | 1163 | > jmp dword ptr 内存单元地址(段间转移) 1164 | 1165 | 功能:从内存单元地址处开始存放着两个字,高地址处的字是转移的目的段地址,低地址处是转移的目的的偏移地址。 1166 | 1167 | ### **9.7 jcxz 指令** 1168 | 1169 | > jcxz 标号 1170 | 1171 | 功能:如果(cx)=0,则转移到标号处执行。如果(cx)≠ 0,则程序继续向下执行。 1172 | 1173 | jcxz指令为有条件转递指令,所有的有条件转移指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。对IP的修改范围都为:-128~127。 1174 | 1175 | ### **9.8 loop指令** 1176 | 1177 | > loop 标号 1178 | 1179 | 功能:(cx)=(cx)-1,如果(cx)≠ 0,则转移到标号处执行。 1180 | 1181 | ### **9.9 根据位移进行转移的意义** 1182 | 1183 | 方便了程序段在内存中的浮动装配。 1184 | 1185 | ### **9.10 编译器对转移位移超界的检测** 1186 | 1187 | 根据位移进行转移的指令,它们的转移范围受到转移位移的限制,如果在源程序中出现了转移范围超界的问题,在编译的时候,编译器将报错。 1188 | 1189 | --- 1190 | ## **第十章 CALL和RET指令** 1191 | 1192 | > call和ret都是转移指令,它们都修改IP,或同时修改CS和IP。它们经常被共同用来实现子程序的设计。 1193 | 1194 | ### **10.1 ret 和 retf** 1195 | 1196 | 这个两个指令可以理解为高级语言中的return关键字,表示程序返回。 1197 | 1198 | ret 用栈中的数据,修改IP的内容,从而实现近转移; 1199 | retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移。 1200 | 1201 | CPU执行ret指令时,进行下面两步操作: 1202 | 1203 | (1)(IP) = ((SS)\*16+(SP)) 1204 | (2)(sp) = (sp)+2 1205 | 1206 | 以上步骤相当于进行: 1207 | > pop IP 1208 | 1209 | 1210 | CPU执行retf指令时,进行下面4步操作: 1211 | 1212 | (1)(IP) = ((SS)\*16+(SP)) 1213 | (2)(sp) = (sp)+2 1214 | (3)(CS) = ((SS)\*16+(SP)) 1215 | (4)(sp) = (sp)+2 1216 | 1217 | 以上步骤相当于进行: 1218 | >pop IP 1219 | >pop CS 1220 | 1221 | ### **10.2 call 指令** 1222 | 1223 | call指令可以理解为高级语言中的方法(函数)调用功能。 1224 | 1225 | CPU指令call指令时,进行两步操作: 1226 | 1227 | (1)将当前的IP或CS和IP压入栈中。 (保存现场) 1228 | (2)转移。 1229 | 1230 | call指令不能实现短转移,除此之外,call指令实现转移的方法和jmp指令的原理相同。 1231 | 1232 | ### **10.3 依据位移进行转移的call指令** 1233 | 1234 | 指令格式: 1235 | > call 标号 1236 | 1237 | CPU执行该指令时相当于进行: 1238 | > push IP 1239 | > jmp near ptr 标号 1240 | 1241 | ### **10.4 转移的目的地址在指令中的call指令** 1242 | 1243 | 指令格式: 1244 | > call far ptr 标号 1245 | 1246 | CPU执行该指令时相当于进行: 1247 | > push CS 1248 | > push IP 1249 | > jmp far ptr 标号 1250 | 1251 | 该指令编译的机器指令中包含了转移的目的地址。包括段地址CS的值及偏移地址IP的值。 1252 | 1253 | ### **10.5 转移地址在寄存器中的call指令** 1254 | 1255 | 指令格式: 1256 | > call 16位reg 1257 | 1258 | CPU执行该指令时相当于进行: 1259 | >push IP 1260 | >jmp 16位reg 1261 | 1262 | ### **10.6 转移地址在内存中的call指令** 1263 | 1264 | 转移地址在内存中的call指令有两种格式。 1265 | 1266 | (1)第一种指令格式: 1267 | >call word ptr 内存单元地址 1268 | 1269 | CPU执行该指令时相当于进行: 1270 | >push IP 1271 | >jmp word ptr 内存单元地址 1272 | 1273 | (2)第二种指令格式: 1274 | >call dword ptr 内存单元地址 1275 | 1276 | CPU执行该指令时相当于进行: 1277 | >push CS 1278 | >push IP 1279 | >jmp dword ptr 内存单元地址 1280 | 1281 | ### **10.7 call和ret的配合使用** 1282 | 1283 | call和ret的配合使用可以用来实现子程序的机制。call指令在转去执行子程序之前,会将当前指令下一条指令的位置保持在栈中,当子程序执行ret或retf指令后,会用栈中的数据设置ip或cs和ip的值,从而转到call指令后面的代码处继续执行。 1284 | 1285 | ### **10.8 mul指令** 1286 | 1287 | (1)两个相乘的数:练歌相乘的数,要么都是8位,要么都是16位。如果是8位,一个默认放在AL中,另一个放在8位reg或内存字节单元中;如果是16位,一个默认放在AX中,另一个放在16位reg或内存字单元中。 1288 | 1289 | (2)结果:如果是8位乘法,结果默认放在AX中;如果是16位乘法,结果高位默认在DX中存放,低位在AX中存放。 1290 | 1291 | ### **10.9 模块化程序设计** 1292 | 1293 | >现实问题比较复杂,对现实问题进行分析时,把它转化为相互联系、不同层次的子问题,是必须的解决方法。 1294 | 1295 | 在高级语言中的函数或者方法就是这种思想的体现。在汇编语言中我们将高级语言中的方法或函数称之为子程序。 1296 | 1297 | ### **10.10 参数和结果传递的问题** 1298 | 1299 | 当我们设计子程序时面临两个问题: 1300 | 1301 | (1)参数存放的位置? 1302 | (2)计算结果存放的位置? 1303 | 1304 | 实际上,我们可以将参数及结果存放于任何可以存储数据的地方。一般情况下,我们可以将参数存储在寄存器中,也可以存储在普通内存单元中。更一般的做法我们将其存储在栈中进行传递。 1305 | 1306 | ### **10.11 寄存器冲突的问题** 1307 | 1308 | 寄存器数量是有限的,子程序中使用的寄存器,很可能在主程序中也要使用,造成了寄存器使用上的冲突。解决这个问题的简捷方法是,**在子程序的开始将子程序中所有用到的寄存器中的内容都保存起来,在子程序返回前再恢复。** 可以用栈来保存寄存器中的内容。 1309 | 1310 | 栈是临时保存数据的一个比较理想的数据结构。 1311 | 1312 | 1313 | --- 1314 | ## **第十一章 标志寄存器** 1315 | 1316 | 标志寄存器(Flag Register)是我们8086CPU14个寄存器中最为复杂的一个。其他13个寄存器一般用于存放数据,整个寄存器具有一个含义。而flag寄存器是按位起作用的。 1317 | 1318 | 这一章中我们主要学习CF、PF、ZF、SF、OF、DF等标记位,以及其相关部分指令。 1319 | 1320 | ### **11.1 ZF标志** 1321 | 1322 | Zero Flag,零标记位。用于记录相关指令执行后,其结果是否为0。如果结果为0,则ZF=1,如果结果非0,则ZF=0。 1323 | 1324 | 需要特别注意的是: 1325 | 1326 | > 在8086的指令集中,有的指令的执行是影响标志寄存器的,比如,add、sub、mul、div、inc、or、and等,它们大都是运算指令(进行逻辑或算术运算);有的指令的执行对标志寄存器没有影响,比如mov、push、pop等,它们大都是传送指令。 1327 | > 1328 | 1329 | ### **11.2 PF标志** 1330 | 1331 | Parity Flag,奇偶标记位。它用于记录相关指令执行后,其结果的所有bit位中1的个数是否为偶数。如果1的个数为偶数,则pf=1,如果为奇数,则pf=0。 1332 | 1333 | ### **11.3 SF标志** 1334 | 1335 | Sign Flag,符号标记位。它用于记录相关指令执行后,其结果是否为负。如果结果为负,则SF=1,如果结果非负,则SF=0。 1336 | 1337 | 计算机中通常用补码来表示有符号数,补码在形式上与普通的无符号二进制数据并无差异。也即是说,给定的一个二进制数,我们既可以把它当做有符号数的补码形式,也可以当做一个无符号数。对于计算机来说,无论是无符号数还是有符号数的补码形式,在计算方式上并无差异(补码的符号位同样参与运算)。 1338 | 1339 | SF标志,就是CPU对**有符号数**运算结果的一种记录,它记录数据的正负。在我们将数据当做有符号数来运算的时候,可以通过它来得知结果的正负。如果我们将数据当做无符号数来运算,SF的值则没有意义,虽然相关指令影响了它的值。 1340 | 1341 | ### **11.4 CF标志** 1342 | 1343 | Carry Flag,进位标志位。一般情况下,在进行**无符号数运算**的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。 1344 | 1345 | ### **11.5 OF标志** 1346 | 1347 | Overflow Flag,溢出标志位。在进行**有符号数运算**的时候,如果计算结果超出了机器所能表示的范围则发生溢出,此时OF=1。否则,OF=0。 1348 | 1349 | 注意区分CF和OF的区别:CF是对无符号数运算有意义的标志位,OF是对有符号数运算有意义的标志位。 1350 | 1351 | ### **11.6 adc指令** 1352 | 1353 | adc是带进位加法指令,它利用了CF位上记录的进位值。 1354 | 1355 | 指令格式: 1356 | > adc 操作对象1,操作对象2 1357 | 1358 | 功能:操作对象1=操作对象1+操作对象2+CF 1359 | 1360 | 比如指令 adc ax,bx 实现的功能是:(ax)= (ax)+ (bx)+ CF 1361 | 1362 | 1363 | 既然我们已经有了add指令,那为什么还要设计adc指令呢? 1364 | 1365 | 设想一下,之前我们使用add指令做加法运算的时候,相加结果都是16位以内,如果和大于16位就会产生误差。adc指令目的就是对任意大的数据进行加法运算。自习观察加法运算可以得到如下规律: 1366 | 1367 | 任意大的加法运算都可以分解为多步进行,低位相加,高位相加再加上低位相加产生的进位值,直至所有位都相加完毕。 1368 | 1369 | 使用adc指令结合上述规律就可以实现对任意大的数据进行加法运算。 1370 | 1371 | ### **11.7 sbb指令** 1372 | 1373 | sbb是带借位减法指令,它利用了CF位上记录的错位值。 1374 | 1375 | 指令格式: 1376 | > sbb 操作对象1,操作对象2 1377 | 1378 | 功能:操作对象1=操作对象1-操作对象2-CF。 1379 | 1380 | sbb指令和adc指令是基于同样的思想设计的两条指令,在应用思路上和adc指令类似。 1381 | 1382 | ### **11.8 cmp指令** 1383 | 1384 | cmp是比较指令,cmp的功能相当于减法指令,只是不保存结果。cmp指令执行后,将对标志寄存器产生影响。 1385 | 1386 | 指令格式: 1387 | > cmp 操作对象1,操作对象2 1388 | 1389 | 功能:计算操作对象 1 - 操作对象 2 但并不保存结果,仅仅根据计算结果对标志寄存器进行设置。 1390 | 1391 | 利用 cmp ax, bx 指令对两个无符号数ax和bx进行比较,如果执行后: 1392 | 1393 | zf = 1,说明 (ax) = (bx) 1394 | zf = 0,说明 (ax) ≠ (bx) 1395 | cf = 1,说明 (ax) < (bx) 1396 | cf = 0,说明 (ax) ≥ (bx) 1397 | cf = 0 并且 zf = 0,说明 (ax) > (bx) 1398 | cf = 1 或 zf = 1,说明 (ax) ≤ (bx) 1399 | 1400 | 利用 cmp ah,bh 指令对两个有符号数ah和bh进行比较,由于有符号数的比较较为复杂,主要是考虑到溢出的特殊情景,我们分类讨论: 1401 | 1402 | (1) 如果 sf = 1 并且 of = 0 1403 | 1404 | of = 0 说明没有溢出,并且 sf = 1 说明逻辑上真正的结果为负数。所以 (ah) < (bh)。 1405 | 1406 | (2) 如果 sf = 1 并且 of = 1 1407 | 1408 | of = 1 说明存在溢出,**针对补码求和来说,如果结果非0并且产生溢出,正确的逻辑结果符号与实际的结果符号必然相反。** sf = 1 说明实际结果为负,那么正确的逻辑结果应该为正。所以 (ah) > (bh)。 1409 | 1410 | (3) 如果 sf = 0 并且 of = 1 1411 | 1412 | of = 1 说明存在溢出,**针对补码求和来说,如果结果非0并且产生溢出,正确的逻辑结果符号与实际的结果符号必然相反。** sf = 0说明实际运算结果必然不小于0,因为存在溢出所以实际运算结果必不等于0,所以实际运算结果必然大于0,进而推导出正确的逻辑运算结果必然小于0。所以 (ah) < (bh)。 1413 | 1414 | (4) 如果 sf = 0 并且 of = 0 1415 | 1416 | of = 0 说明没有溢出,并且 sf = 0,说明逻辑上真正的结果为非负数。所以 (ah) ≥ (bh)。 1417 | 1418 | (5) 如果 zf = 1 1419 | 1420 | 这种情形比较简单。此时 (ah) = (bh)。 1421 | 1422 | ### **11.9 检测比较结果的条件转移指令** 1423 | 1424 | “转移”指的是它能够修改IP,而“条件”指的是它可以根据某种条件,决定是否修改IP。比如,jcxz就是一个条件转移指令,它可以检测cx中的数值,如果 (cx) = 0,就修改IP,否则什么也不做。**所有条件转移指令的位移都是 [-128, 127](即它们都是短转移)。** 1425 | 1426 | jcxz是根据寄存器cx的值来判断是否转移,除此之外还存在其他条件转移指令,大多数条件转移指令都检测标志寄存器相关标志位,根据检测的结果来决定是否修改IP。 1427 | 1428 | 下表列出了常用的根据无符号数的比较结果进行转移的条件转移指令: 1429 | 1430 | | 指令 | 含义 | 检测的相关标志位 | 备注 | 1431 | |------|------|----------------|------| 1432 | | je | 等于则转移 | zf = 1 | e 表示 equal | 1433 | | jne | 不等于则转移 | zf = 0 | ne 表示 not eauql | 1434 | | jb | 低于则转移 | cf = 1 | b 表示 below | 1435 | | jnb | 不低于则转移 | cf = 0 | nb 表示 not blow | 1436 | | ja | 高于则转移 | cf = 0 且 zf = 0 | a 表示 above | 1437 | | jna | 不高于则转移 | cf = 1 或 zf = 1 | na 表示 not above | 1438 | 1439 | 注意,条件转移指令通常与cmp指令配合使用。 1440 | 1441 | ### **11.10 DF标志和串传送指令** 1442 | 1443 | Direction Flag,方向标志位。在串传送指令中,控制每次操作后 si、di 的增减。 1444 | 1445 | df = 0 ,每次操作后 si、di 递增; 1446 | df = 1 ,每次操作后 si、di 递减。 1447 | 1448 | 下面,我们学习几个常见的串传送指令。(写到这里,突然想吃羊肉串了~~) 1449 | 1450 | **movsb指令** 1451 | 1452 | 格式: 1453 | > movsb 1454 | 1455 | 功能:将ds:si指向的内存单元中的字节送入es:di中,并根据标志寄存器df的值,将si和di递增或递减。 1456 | **movsw指令** 1457 | 1458 | 与 movsb 指令类似,只不过 movsw指令传送的是一个字单元。 1459 | 1460 | **rep 指令** 1461 | 1462 | 本人将其翻译为重复指令(repetition)。movsb 和 movsw 进行的是串传送操作中的一个步骤,一般来说,movsb 和 movsw 都配合 rep 配合使用,格式如下: 1463 | 1464 | > rep movsb 1465 | 1466 | 功能:根据cx的值来决定是否重复执行 movsb操作。使用汇编语法来描述就是> 1467 | 1468 | > s: movsb 1469 | > loop s 1470 | 1471 | 1472 | **cld 指令和 std 指令** 1473 | 1474 | cld 指令:将标志寄存器的 df 位置0; 1475 | std 指令:将标志寄存器的 df 位置1。 1476 | 1477 | 为了方便记忆,可以将 cld 理解为 clear direction 的缩写,将 std 理解为 set direction 的缩写。 1478 | 1479 | ### **11.11 pushf 和 popf** 1480 | 1481 | pushf 的功能是将标志寄存器的值压栈,而 popf 是从栈中弹出数据,送入标志寄存器中。 1482 | 1483 | pushf 和 popf 为直接访问标志寄存器提供了一种方法。 1484 | 1485 | ### **11.12 标志寄存器在Debug中的表示** 1486 | 1487 | 在Debug中,我们使用r命令查看寄存器详情,第二行最后几个双字符字母即是标志寄存器中各标志位的值。 1488 | 1489 | --- 1490 | ## **第十二章 内中断** 1491 | 1492 | 什么是中断?如果你学习过高级编程语言,可以将中断理解为异常的特殊处理过程,就像Java里面的Exception。 1493 | 1494 | 任何一个通用的CPU,都具备一种能力,可以在执行完当前正在执行的指令之后,检测从CPU外部发送过来的或内部产生的一种特殊信息,并且可以立即对所收到的信息进行处理。这种特殊信息,我们可以称其为:中断信息。中断的意思是指,CPU不在接着(刚执行完的指令)向下执行,而是转去处理这个特殊信息。 1495 | 1496 | 中断信息可以来自CPU内部和外部,这一章,我们主要讨论来自CPU内部的中断信息,我们称之为内中断。 1497 | 1498 | ### **12.1 内中断的产生** 1499 | 1500 | 8086CPU使用单元字节大小的数字来标识中断类型。 1501 | 1502 | CPU内部可能产生多种多样的中断,那么应该如何来标识是哪种中断呢,或者说我们如何确定中断源? 1503 | 8086CPU用称为**中断类型码**的数据来标识中断信息的来源。中断类型码为一个字节型数据,可以表示256种中断类型。以后,我们将产生中断信息的事件,即中断信息的来源,称之为**中断源**。 1504 | 1505 | ### **12.2 中断处理程序** 1506 | 1507 | 处理中断信息的程序被称为**中断处理程序**。 1508 | 1509 | ### **12.3 中断向量表** 1510 | 1511 | 中断发生后,CPU要根据中断类型码去执行对应的中断处理程序?但如何根据8位的中断类型码得到中断处理程序的地址呢? 1512 | 1513 | 实际上,8086CPU用8位的中断类型码通过**中断向量表**找到相应的中断处理程序的入口地址。中断向量表就是中断处理程序入口地址的列表,列表的下标索引(从0开始)即是中断类型码的值。中断向量表实际上是中断类型码与中断处理程序入口地址之间的一种映射关系。可以理解为高级编程语言中的Map集合。 1514 | 1515 | 8086CPU中断向量表指定放在内存0处。每个表项占用4个字节,高位字存放段地址,低位字存放偏移地址。 1516 | 1517 | ### **12.4 中断过程** 1518 | 1519 | 用中断类型码找到中断向量,并用它设置CS和IP的值,这个工作是由CPU的硬件自动完成的。CPU硬件完成这个工作的过程被称为**中断过程**。中断过程完成后,CPU就会开始执行中断处理程序。中断过程可以理解为中断环境的初始化。那么在CPU进行中断过程中需要准备哪些工作呢?概括来说,主要进行以下六步准备工作: 1520 | 1521 | (1)(从中断信息中)取得中断类型码; 1522 | (2)标志寄存器的值入栈(因为在中断过程中要改变标志寄存器的值,所以先将其保存在栈中); 1523 | (3)设置标志寄存器的第8位TF和第9位IF的值为0(这一步的目的后面将介绍); 1524 | (4)CS的内容入栈; 1525 | (5)IP的内容入栈; 1526 | (6)从内存地址为中断类型码*4和中断类型码*4+2的两个字单元中读取中断处理程序的入口地址设置IP和CS。 1527 | 1528 | ### **12.5 中断处理程序和iret指令** 1529 | 1530 | 中断处理程序必须一直存储在指定内存中,以应对随时可能发生的中断事件。 1531 | 1532 | 中断处理程序的编写方法和子程序比较相似,下面是常规步骤: 1533 | 1534 | (1)保存用到的寄存器; 1535 | (2)处理中断; 1536 | (3)恢复用到的寄存器; 1537 | (4)用ret指令返回。 1538 | 1539 | iret指令的功能用汇编语法描述为: 1540 | 1541 | ```c 1542 | pop IP 1543 | pop CS 1544 | popf 1545 | ``` 1546 | 在中断过程中,注意标志寄存器入栈和出栈的次序。入栈顺序是标志寄存器、CS、IP,出栈顺序与此相反。 1547 | 1548 | ### **12.6 除法错误中断的处理** 1549 | 1550 | 除法错误将引发0号中断。至于为何是0号中断,我估摸着除法中断时人们最容易想到也最容易遇到的中断了吧。 1551 | 1552 | ### **12.7 编程处理0号中断** 1553 | 1554 | 我们的需求是重新编写一个0号中断处理程序,它的功能是在屏幕中间显示“overflow!”,然后返回到操作系统。 1555 | 1556 | 为了满足以上需求,需要做一下几件事情: 1557 | 1558 | (1)编写可以显示“overflow”的中断处理程序:do0; 1559 | (2)将do0送入内存0000:0200处; 1560 | (3)将do0的入口地址0000:0200存储在中断向量表0号表项中。 1561 | 1562 | 程序的框架如下: 1563 | 1564 | ```c 1565 | assume cs:code 1566 | 1567 | code segment 1568 | 1569 | start: do0安装程序 1570 | 设置中断向量表 1571 | mov ax, 4c00h 1572 | int 21h 1573 | 1574 | do0: 显示字符串"overflow" 1575 | mov ax, 4c00h 1576 | int 21h 1577 | 1578 | code ends 1579 | 1580 | end start 1581 | ``` 1582 | 1583 | 下面摘抄书中比较精辟的一段总结: 1584 | 1585 | >我们如何让一个内存单元成为栈顶?将它的地址放入SS、SP中; 1586 | >我们如何让一个内存单元中的信息被CPU当做指令来执行?将它的地址放入CS、IP中; 1587 | >我们如何让一个内存单元成为要处理的数据?将它的段地址放在DS中;(书中无这句话,个人根据理解补充) 1588 | >那么,我们如何让一段程序成为 N 号中断的中断处理程序呢?将它的入口地址放入中断向量表的 N 好表项中。 1589 | 1590 | ### **12.8 安装** 1591 | 1592 | 所谓安装就是将中断处理程序(do0)送到指定内存处。 1593 | 1594 | 我们可以使用movsb指令,将do0的代码送入0:200处。复习一下movsb的用法:movsb是串传送指令,其功能是将ds:si指向的内存单元中的字节送入es:di中,并根据标志寄存器df的值,将si和di递增或递减。movsb指令往往与rep指令配合使用来实现批量字符串的传送。 1595 | 1596 | 安装程序的框架如下所示: 1597 | 1598 | ```c 1599 | assume cs:code 1600 | code segment 1601 | 1602 | start: 设置es:di指向目的地址 1603 | 设置ds:si指向源地址 1604 | 设置cx为传输长度 1605 | 设置传输方向为正 1606 | rep movsb 1607 | 1608 | 设置中断向量表 1609 | 1610 | mov ax, 4c00h 1611 | int 21h 1612 | 1613 | do0: 显示字符串"overflow!" 1614 | mov ax, 4c00h 1615 | int 21h 1616 | 1617 | code ends 1618 | end start 1619 | 1620 | ``` 1621 | 1622 | 以上步骤的难点在于如何确认中断处理程序do0的长度?最笨的方法是计算do0中每句代码的长度,然后累加,但这样做太麻烦了,不仅要知道每行代码所占的字节数,代码稍有改动那就令人抓狂。书中作者给出一个非常简便的计算方式,利用编译器来帮助我们计算do0的长度。之前我们学过offset指令,他的功能是取得标号的偏移地址,我们在do0后面在添加一个标号do0end,使用 offset do0end - offset do0 即可计算出do0的长度。 1623 | 1624 | 解决了字符传送以及确认do0长度这两个拦路虎后,我们就可以看一下较为完整的安装程序代码了: 1625 | 1626 | ```c 1627 | assume cs:code 1628 | code segment 1629 | start: 1630 | mov ax, cs 1631 | mov ds, ax 1632 | mov si, offset do0 ;设置ds:si指向源地址 1633 | mov ax, 0 1634 | mov es, ax 1635 | mov di, 0200h ;设置es:di指向目标地址 1636 | 1637 | mov cx, offset do0end - offset do0 ;设置cx为传输长度 1638 | cld ;设置传输方向为正 1639 | rep movsb ;传输开始 1640 | 1641 | 设置中断向量表 1642 | 1643 | mov ax, 4c00H 1644 | int 21H 1645 | 1646 | do0: 显示字符串"overflow!" 1647 | mov ax, 4c00h 1648 | int 21h 1649 | code ends 1650 | 1651 | end start 1652 | 1653 | ``` 1654 | 1655 | 这里补充一点,像"+"、"-"、"\*"、"/"、"offset"这类指令都是伪指令,并不是标准的汇编指令,它是由编译器识别并由编译器翻译为对应的汇编指令。 1656 | 1657 | ### **12.9 do0** 1658 | 1659 | do0程序即是我们的0号中断处理程序。其主要目的是显示字符串"overflow!"。 1660 | 1661 | 主要程序代码如下所示: 1662 | 1663 | ```c 1664 | do0: jum short do0start 1665 | db 'overflow!' 1666 | 1667 | do0start: mov ax, cs 1668 | mov ds, ax 1669 | mov si, 202h 1670 | 1671 | mov ax, 0b800h 1672 | mov es, ax 1673 | mov di, 12*160 + 36*2 1674 | 1675 | mov cx, 9 1676 | s:mov al, [si] 1677 | mov es:[di], al 1678 | inc si 1679 | add di, 2 1680 | loop s 1681 | 1682 | mov ax, 4c00h 1683 | int 21h 1684 | 1685 | do0end: nop 1686 | ``` 1687 | 1688 | 这部分代码需要注意的地方是,我们在子程序do0开始处定义了字符串"overflow!",但它并不是可以执行的代码,所以在"overflow!"之前加上一条jmp指令,转移到正式的do0程序。 1689 | 1690 | ### **12.10 设置中断向量** 1691 | 1692 | 设置中断向量,也即是将中断处理程序do0在内存中的入口地址存放在中断向量表0号表项中。0号表项的地址为0:0,其中0:0字单元存放中断处理程序入口地址的偏移地址,0:2字单元存放中断处理程序入口地址的段地址。程序如下: 1693 | 1694 | ```c 1695 | mov ax, 0 1696 | mov es, ax 1697 | mov word ptr es:[0*4], 200h 1698 | mov word ptr es:[0*4+2], 0 1699 | ``` 1700 | 1701 | ### **12.11 单步中断** 1702 | 1703 | 基本上,在CPU执行完一条指令之后,如果检测到标志寄存器的TF位为1,则产生单步中断,引发中断过程。单步中断的中断类型码为1。在一开始我们说CPU在执行中断处理程序之前要先将标志寄存器TF位置0,这就是为了防止CPU在执行1号类型中断(单步中断)时无限递归执行中断。 1704 | 1705 | CPU提供单步中断功能的出发点是,为单步跟踪程序的执行过程,提供了实现机制。 1706 | 1707 | ### **12.12 响应中断的特殊情况** 1708 | 1709 | >一般情况下,CPU在执行完当前指令后,如果检测到中断信息,就响应中断,引发中断过程。可是,在有些情况下,CPU执行完当前指令后,即便是发生中断,也不会响应。例如针对ss修改执行后,下一条指令(一般是修改sp)也会紧接着执行,中间即使发生中断,CPU也不会去响应。这样做的主要原因是,ss:sp联合指向栈顶,而对它们的设置应该连续完成。如果在执行完设置ss的指令后,CPU响应中断,引发中断过程,要在栈中压入标志寄存器、CS和IP的值,而ss改变,sp并未改变,**ss:sp指向的不是正确的栈顶**,将引起错误。 1710 | > 1711 | 1712 | 这种理念在高级编程语言中的具体体现是“原子操作”,即一组操作要么不执行,要么就一次执行完毕,不会存在中间状态。 1713 | -------------------------------------------------------------------------------- /参考资料/8086Registers.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanmianti/AssemblyLanguageTest/8265babc5041e81b0f5435619c87fadbeb867f98/参考资料/8086Registers.pdf -------------------------------------------------------------------------------- /参考资料/DEBUG command - Chinese ver.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanmianti/AssemblyLanguageTest/8265babc5041e81b0f5435619c87fadbeb867f98/参考资料/DEBUG command - Chinese ver.pdf --------------------------------------------------------------------------------