0%

汇编:史上最全注释,王爽汇编语言,课程设计2源码

课程设计2

题目见 汇编语言 第四版 作者王爽 p312

这篇文章是《汇编语言 第四版》的完结篇。

概述

1、vm虚拟机中安装win98se,以下称为win98

2、成功安装系统后,虚拟机添加一个新软盘(需要创建软盘映像.img文件)。

3、启动win98系统,将下文源码编译,然后执行程序(需要masm汇编器)。

4、关闭win98系统,在虚拟机中,设置软盘为启动盘。

5、启动虚拟机,我们自己写的程序就能执行了。

如果用两个硬盘,即添加新硬盘代替软盘,然后设新硬盘为启动盘后,启动失败(暂不管此问题)!

程序结构

软盘:第1扇区存放我们的引导程序,第2-3扇区存放主程序,实现4个功能。

硬盘1:是win98系统盘,第1扇区有mbr引导程序。

开机后,软盘为启动盘,加载我们自己的引导程序,执行引导程序,然后加载2-3扇区的主程序。

内存结构

汇编源码

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
; 课程设计2
; 实现了4个功能的程序以下称为主程序

assume cs:code
code segment
start:

; 安装我们自己的引导程序到软盘a,占1个扇区
call inst_my_boot

; 安装主程序到软盘a,占2个扇区
call inst_my_main

mov ax,4c00h
int 21h

;---------------- 安装程序 ----------------
inst_my_boot:
mov bx,cs
mov es,bx
mov bx,offset my_boot ; es:bx指向my_boot

; 写入内容到 软盘A,0面,0道,1扇区
mov dl,0 ; 软盘A
mov dh,0 ; 0面
mov ch,0 ; 0道
mov cl,1 ; 第1扇区
mov al,1 ; 写1个扇区
mov ah,3 ; 写
int 13h

ret

inst_my_main:
mov bx,cs
mov es,bx
mov bx,offset my_main ; es:bx指向my_main

; 写入内容到 软盘A,0面,0道,2扇区
mov dl,0 ; 软盘A
mov dh,0 ; 0面
mov ch,0 ; 0道
mov cl,2 ; 第2扇区
mov al,2 ; 写2个扇区
mov ah,3 ; 写
int 13h

ret

;---------------- 引导程序 ----------------
my_boot:
; 设栈
cli
mov ax,0
mov ss,ax
mov sp,7c00h
sti

; 装载新int9h中断例程
call load_newint9

; 将主程序拷贝到7e00h
mov bx,0
mov es,bx
mov bx,7e00h

; 读取 软盘A,0面,0道,2扇区 开始的2个扇区 到0:7e00h
mov dl,0 ; 软盘A
mov dh,0 ; 0面
mov ch,0 ; 0道
mov cl,2 ; 第2扇区
mov al,2 ; 复制2个扇区
mov ah,2 ; 读取
int 13h

mov bx,0
push bx
mov bx,7e00h
push bx
retf ; 从栈中取2字 设CS:IP=0:7e00h 并从此处开始执行

; 装载新int9h中断例程
load_newint9:
push ds
push si
push es
push di
push cx

push cs ; 引导程序执行时,cs:ip=0:7c00h
pop ds

mov cx,0
mov es,cx

mov si,newint9-my_boot+7c00h ; ds:si指向新int9例程
mov di,204h ; es:di指向新int9例程装载位置
mov cx,newint9end-newint9
cld
rep movsb

; 保存旧的int9h中断向量
push es:[9*4]
pop es:[200h]
push es:[9*4+2]
pop es:[202h]

pop cx
pop di
pop es
pop si
pop ds
ret

newint9:
push ax
push bx
push cx
push es

in al,60h

pushf
call dword ptr cs:[200h] ; 此newint9中断放在0:204h处,此中断执行时cs=0

cmp al,3bh ; f1扫描码
je chgcolor
cmp al,01h ; esc键扫描码
je int9ret
jmp newint9ret

backmain:
pop es
pop cx
pop bx
pop ax

add sp,4 ; 跳过cs:ip
popf

mov bx,0
push bx
mov bx,7e00h
push bx
retf

int9ret:
; 恢复原int9中断向量,原中断向量保存在0:200h, 0:202h
mov ax,0
mov es,ax
cli
push es:[200h]
pop es:[9*4]
push es:[202h]
pop es:[9*4+2]
sti
jmp backmain

chgcolor:
mov ax,0b800h
mov es,ax
mov bx,1
mov cx,17
chgcolor_s1:
inc byte ptr es:[bx]
add bx,2
loop chgcolor_s1

newint9ret:
pop es
pop cx
pop bx
pop ax
iret

newint9end:
nop

; 因为引导程序占512字节,这里填充512字节0,作为扇区结束,
; 防止引导程序不够512字节,而复制下面的代码到1扇区内
db 512 dup (0)

;--------------------- 主程序 ---------------------
my_main:
jmp main_start
menu1 db '1) reset pc',0
menu2 db '2) start system',0
menu3 db '3) clock',0
menu4 db '4) set clock',0
menu_addr dw menu1-my_main+7e00h, menu2-my_main+7e00h, menu3-my_main+7e00h, menu4-my_main+7e00h
timestr db 'yy/mm/dd hh:mm:ss',0
timeaddr db 9,8,7,4,2,0
strbuffer db 100 dup (0) ; 输入字符串缓冲区

main_start:
; 初始化数据段寄存器
mov ax,0
mov ds,ax

call clr_src
call show_menu
call choose_item

choose_item: ; 选择菜单项
mov si,strbuffer-my_main+7e00h
call getstr ; 输入字符串,回车确认,ds:si指向字符串缓冲区

cmp byte ptr [si],'1'
je item1
cmp byte ptr [si],'2'
je item2
cmp byte ptr [si],'3'
je item3
cmp byte ptr [si],'4'
je item4
jmp main_start ; 其他输入时,重新显示菜单

item1:
mov bx,0ffffh
push bx
mov bx,0
push bx
retf ; 将从ffff:0处开始执行,会重启系统

item2: ; 引导现有系统
; 将原mbr拷贝到7c00h
mov bx,0
mov es,bx
mov bx,7c00h

; 读取 硬盘c,0面,0道,1扇区 到0:7c00h
mov dl,80h ; 盘c
mov dh,0 ; 0面
mov ch,0 ; 0道
mov cl,1 ; 第1扇区
mov al,1 ; 复制1个扇区
mov ah,2 ; 读取
int 13h

mov bx,0
push bx
mov bx,7c00h
push bx
retf

item3:
call setnewint9
call show_dt
jmp main_start

item4:
call clr_src
mov si,strbuffer-my_main+7e00h
call getstr
call set_dt ; ds:si指向字符串缓冲区
jmp main_start

show_menu: ; 显示主程序菜单
push bx
push es
push si
push cx
push di

mov bx,0b800h
mov es,bx
mov bx,160*10+32*2 ; 中间位置显示菜单,第10行第32列
mov di,menu_addr-my_main+7e00h ; 直接定址表
mov cx,4
show_menu_s1:
mov si,[di] ; ds:di定位直接定址表
call show_str
add di,2
add bx,160 ; 跳到下一行
loop show_menu_s1

pop di
pop cx
pop si
pop es
pop bx
ret

clr_src: ; 清屏
push bx
push cx
push es
mov bx,0b800h
mov es,bx
mov bx,0 ; 显存的偶地址单元为字符
mov cx,2000
clr_src_s1:
mov byte ptr es:[bx],' ' ; 空格填充
add bx,2
loop clr_src_s1
pop es
pop cx
pop bx
ret

; 显示字符串,字符串以0结束
; ds:si指向字符串开头
; es:bx指向显存开始,对应屏幕上的位置
show_str:
push si
push cx
push es
push bx
show_str_s1:
mov cl,[si]
cmp cl,0
je show_str_ret
mov es:[bx],cl
add bx,2
inc si
jmp show_str_s1
show_str_ret:
pop bx
pop es
pop cx
pop si
ret

; 接受字符串输入
; ds:si指向字符栈空间
getstr:
push ax
push dx
push es
push di

mov ax,0b800h
mov es,ax
mov di,160*14+32*2 ; es:di为光标初始位置、字符串输入位置
;mov di,0
call setcur

mov dx,0 ; 字符栈栈指针
getstrs:
mov ah,0
int 16h ; 获取键盘缓冲区内容
cmp al,20h
jb nochar
cmp al,7eh
ja nochar ; 只能输入可见字符
mov ah,0
call charstack ; 字符入栈
mov ah,2
call charstack ; 显示栈中字符
jmp getstrs

nochar:
cmp ah,0eh ; 退格键扫描码
je backspace
cmp ah,1ch ; 回车键扫描码
je enter
jmp getstrs

backspace:
mov ah,1
call charstack ; 字符出栈
mov ah,2
call charstack
jmp getstrs

enter:
mov al,0 ; 把0入栈,作为字符串结束
mov ah,0
call charstack
mov ah,2
call charstack

pop di
pop es
pop dx
pop ax
ret

; 字符入栈、出栈、显示功能
; ah=功能号,0入栈,1出栈,2显示
; ds:si指向字符栈空间
; dx=字符栈栈指针
; 0号功能,al=入栈字符
; 1号功能,al=返回的字符
; 2号功能,es:di指向屏幕位置
charstack: jmp short charstart
table dw charpush-my_main+7e00h,charpop-my_main+7e00h,charshow-my_main+7e00h

charstart:
push bx
push di
push es
;dx为charstack非局部变量,不要入栈
;push dx

cmp ah,2 ; 功能号判断
ja sret
mov bl,ah
mov bh,0
add bx,bx ; 根据功能号取得对应的偏移地址
jmp word ptr table-my_main+7e00h[bx]

charpush:
mov bx,dx
mov [si][bx],al
inc dx
jmp sret

charpop:
cmp dx,0
je sret
dec dx
mov bx,dx
mov al,[si][bx]
jmp sret

charshow:
; es:di指向屏幕位置,由调用者传递
mov bx,0
charshows:
cmp bx,dx
jne noempey
mov byte ptr es:[di],' '
call setcur
jmp sret

noempey:
mov al,[si][bx]
mov es:[di],al
mov byte ptr es:[di+2],' ' ; 清空后一个字符
inc bx
add di,2
jmp charshows

sret:
pop es
pop di
pop bx
ret

setcur: ; 设置光标到es:di位置
push ax
push dx

mov ax,di
mov dh,160
div dh
mov dh,al ; 行号
mov al,ah
mov ah,0
mov dl,2
div dl
mov dl,al ; 列号
mov ah,2 ; 设置光标位置
mov bh,0 ; 第0页
int 10h

pop dx
pop ax
ret

; 显示时间
show_dt:
call get_dt
call delay
jmp show_dt
ret

; 设置新的int9h中断向量
setnewint9:
push es
push bx
mov bx,0
mov es,bx
cli
mov word ptr es:[9*4],204h
mov word ptr es:[9*4+2],0
sti
pop bx
pop es
ret

; 获取CMOS中的系统时间
; ds:si从cmos对应地址取日期时间,ds:di转换后的日期时间字符串
get_dt:
push si
push di
push cx
push ax
push es
push bx

mov si,timeaddr-my_main+7e00h
mov di,timestr-my_main+7e00h
mov cx,6
get_dt_s1:
mov bx,cx
mov al,[si]
out 70h,al ; 70h为地址端口
in al,71h ; 71h为数据端口
mov ah,al
mov cl,4
shr ah,cl ; 右移4位,ah为十进制的十位数
and al,00001111b ; al为十进制的个位数
add ah,30h
add al,30h ; 数值转字符形式
xchg ah,al
mov [di],ax
add di,3
inc si
mov cx,bx
loop get_dt_s1

mov bx,0b800h
mov es,bx
mov bx,0
mov si,timestr-my_main+7e00h
call show_str

pop bx
pop es
pop ax
pop cx
pop di
pop si
ret

; 延时
delay:
push ax
push dx
mov dx,6000h ; 循环6000000h次
mov ax,0
delays1:
sub ax,1
sbb dx,0
cmp ax,0
jne delays1
cmp dx,0
jne delays1
pop dx
pop ax
ret

; 设置系统时间
set_dt:
push si
push di
push cx
push ax
push bx

mov di,timeaddr-my_main+7e00h
mov cx,6
set_dt_s1:
mov ax,[si] ; al为十位数,ah为个位数
sub ah,30h
sub al,30h
and ah,00001111b ; 取个位数
mov bx,cx
mov cl,4
shl al,cl
or al,ah
mov cx,bx
mov ah,al
mov al,[di]
out 70h,al
mov al,ah
out 71h,al
inc di
add si,3
loop set_dt_s1

pop bx
pop ax
pop cx
pop di
pop si
ret

db 1024 dup (0) ; 填充1k字节,原因同上

code ends
end start

执行结果: