; 王爽《汇编语言》的课题2,之前已经做过初步的调试。测试环境:VMware 6.5,操作系统DOS6.22 ;课题要求的功能都能实现。但在细节上还有些不尽如人意的地方。 ;比如程序的结构安排和设置时钟的输入格式等。等我有时间再做调整。 ;by:double; date:2009-10-07; ver:0.1 assume cs:codesg codesg segment start: mov ax,cs mov es,ax ;将代码写到软驱的前三个扇区。第三个扇区未使用的部分没有填充。 mov bx,offset fun_start mov ah,3 mov al,3 mov ch,0 mov cl,1 mov dh,0 mov dl,0 int 13h ;拷贝功能结束。 mov ax,4c00h int 21h ;第一扇区开始工作。 fun_start: ;初始化段寄存器和栈指针。 cli xor ax,ax mov ss,ax mov sp,7c00h push ax pop es push ax pop ds sti ;将自身拷贝到内存0:600h处接着执行。让出0:7c00h给硬盘MBR, ;以免执行本程序功能2时破坏自身的基本功能。 mov si,7c1ch mov di,61ch push es push di ;因为BIOS只读取了软盘的第一个扇区到:7c00h处故CX只设置1e4h,即200h-(之前代码的大小) mov cx,1e4h ;be careful of 'cx' value.The first sector. cld rep movsb retf ;第一个扇区要实现的第一项功能是把软盘第二和第三个扇区的内容拷贝到0:800h开始的内存中。 ;这样,本程序是在0:600开始的内存中。 mov bx,800h mov al,2 mov ch,0 mov cl,2 mov dh,0 mov dl,0 mov ah,2 int 13h ;安装新的int 9中断。实现按ESC后能从功能3和功能4中退出,按F1后改变时间的显示颜色。 ;新中断安装在0:204h开始的内存中。系统原中断入口保存在0:200-0:203h内存单元中。 ;可通过call dword ptr 0:200h调用原中断。 call int9_setup ;set the cursor positon. set_cursor: mov ah,2 mov bh,0 mov dh,12 mov dl,12 int 10h ;显示选项。字符保存在0:800h处。使用int 10h 的功能0eh实现显示。 mov si,800h show_message: lodsb cmp al,0 jz enter_choice mov bl,4 mov ah,0eh int 10h jmp show_message ;选项显示完之后等待用户的输入。 enter_choice: ;set the cursor position, put it at the end of the message. mov ah,2 mov bh,0 mov dh,16 mov dl,36 int 10h ;wait for user input xor cx,cx mov ah,0 int 16h ;show user input at cursor. mov ah,9 mov bh,0 mov bl,7 mov cl,1 int 10h cmp al,'1' je reset_pc cmp al,'2' je start_DOS cmp al,'3' je show_clock cmp al,'4' je set_clock call clear_screen jmp set_cursor set_clock: call clock_set call clear_screen jmp set_cursor ;reset pc 和start DOS由于都不用保存现场,直接用jmp,不用函数。 reset_pc: mov ax,0ffffh push ax mov ax,0 push ax ;使用retf来调用0ffff:0000处的指令,交出控制权。 retf start_DOS: ;clear_screen is used to clear all output. ;在启动DOS之前清屏。 call clear_screen ;set cursor position mov ah,2 mov bh,0 mov dh,0 mov dl,0 int 10h mov bx,7c00h mov ax,0201h mov cx,1 mov dx,80h int 13h push es push bx ;同样使用retf交出控制权。 retf show_clock: ;按ESC后,新中断会设置BP值为1,程序检测BP值来判断是否需要回到主菜单。 ;show_clock的功能是循环检测BP值并循环显示时间。 cmp bp,1 jz show_clock_end call show_start jmp show_clock ;检测到BP值为1时,需重新设置BP值为零,然后清屏,并重新显示主菜单。 show_clock_end: mov bp,0 call clear_screen jmp set_cursor show_start: ;显示时间是一个繁琐的过程,可以通过BIOS中断来简化,但本代码 ;直接从端口取值。 push ax ;set bp to 0. ;when ESC is pressed bp will be set to 1 mov bp,0 mov al,0 out 70h,al in al,71h mov bh,al mov al,2 out 70h,al in al,71h mov bl,al mov al,4 out 70h,al in al,71h mov ch,al mov al,7 out 70h,al in al,71h mov cl,al mov al,8 out 70h,al in al,71h mov dh,al mov al,9 out 70h,al in al,71h mov dl,al mov di,882h mov al,dl call format_show add di,3 mov al,dh call format_show add di,3 mov al,cl call format_show add di,3 mov al,ch call format_show add di,3 mov al,bl call format_show add di,3 mov al,bh call format_show call display pop ax ret format_show: push di push ax push es mov ah,al shr ah,1 shr ah,1 shr ah,1 shr ah,1 add ah,30h mov es:[di],ah and al,00001111b add al,30h mov es:[di + 1],al pop es pop ax pop di ret display: push ax push bx push dx push si mov ah,2 mov bh,0 mov dh,17 mov dl,20 int 10h mov si,882h s: lodsb cmp al,0 jz show_end mov ah,0eh mov bx,1 int 10h jmp s show_end: pop si pop dx pop bx pop ax ret ;esc_pressed: ; call go_to_menu ;用99h个字符a填充第一个扇区。 db 99h dup ('a') ;第二个扇区内容开始。这部分内容必须位于第二个扇区的开头。 ;在内存中它们会位于0:800h中。它们的位置是确定的。 ;the message must be stored at address 0:800h message1 db '1) reset pc',0ah,0dh ;13 message2 db ' 2) start DOS',0ah,0dh ;26 message3 db ' 3) show clock',0ah,0dh ;27 message4 db ' 4) set clock',0ah,0dh ;26 message5 db ' Enter you choice...(1-4) ',0 ;38 db 'bb/bb/bb bb:bb:bb ',0 ;19 db 'Input new time(091005183021):',0 ;30 db '???????????? ' ;13 top dw 0 ;2 fun dw 9cch,9edh,0a03h ;6 db 'Input error!',0 ;新的9号中断的安装代码。 int9_setup: push ax push cx push es push si push di xor ax,ax mov es,ax mov ds,ax ;save original int 9 entry in 0:200~0:203 mov ax,es:[36] mov es:[200h],ax mov ax,es:[38] mov es:[202h],ax ;int 9 point to new address.0:204 cli mov word ptr es:[36],204h mov word ptr es:[38],0 mov si,912h mov di,204h mov cx,offset int9_end_nop - offset int9_start cld rep movsb sti pop di pop si pop es pop cx pop ax ret int9_start: push ax push es push di push cx xor ax,ax mov es,ax ;get keyboard input scan code. ;从键盘60h端口直接读输入字符的扫描码,存于al中。 in al,60h ;模拟CPU执行中断的过程。与中断结束的iret相配合。 ;标志寄存器入栈。 pushf ;CS,IP入栈并调用原int 9中断。 call dword ptr es:[200h] ;以下是我们要想实现的功能。按ESC键,置BP为1,程序检测BP值来判断是否要回到主菜单。 ;按F1键显示的时间变色。显示时间的位置固定。 ;ESC is pressed... cmp al,1 je press_esc ;F1 is pressed... cmp al,3bh je f1 jmp int9_end press_esc: mov bp,1 jmp int9_end f1: ;由于显示的时间固定,此处直接改变显示时间处的字符属性。 mov ax,0b800h mov es,ax mov di,17 * 160 + 20 * 2 + 1 mov cx,18 change_color: inc byte ptr es:[di] add di,2 loop change_color int9_end: pop cx pop di pop es pop ax iret ;此处为新中断的安装程序设置结束标志。 int9_end_nop: nop clock_set: ;功能4,设置时间。 push ax push bx push dx push si ;set cursor to display 'Input new time(091005183021):' mov ah,2 mov bh,0 mov dh,18 mov dl,0 int 10h ;message start address mov si,895h show_settime_mess: lodsb cmp al,0 jz show_settime_end mov ah,0eh int 10h jmp show_settime_mess show_settime_end: ;set cursor to input position mov ah,2 mov bh,0 mov dh,18 mov dl,29 int 10h mov word ptr ds:[8c0h],0 call input_time call write_time ;设置CPU响应键盘中断。 ;accept int 9 sti ;循环检测ESC键,收到信号后clock_set函数结束。该函数的主调函数在该函数结束后 ;会清屏并重回到主菜单。 check_bp_lo: cmp bp,1 jne check_bp_lo pop si pop dx pop bx pop ax ret ;设置系统时间本身并不复杂,不管是直接操作端口还是调用BIOS中断都可以实现。比较麻烦的是 ;在屏幕上显示用户输入的函数。该程序实现:1、检测输入的字符数是否正确。不正确则打印错 ;误提示然后清屏回到主菜单,若输入字符数正确则检测到回车后向BIOS写入新时间。2、按backspace ;键后删除用户最近的一个输入,并在屏幕上显示。本程序的缺点是:1、光标不能跟随用户输入。 ;2、程序检测到用户输入错误后即打印消息,然后直接清屏回到主菜单,没有恢复调用本函数之前的 ;CPU现场,这可能会造成栈溢出。 input_time: push ax get_strs: mov ah,0 int 16h cmp al,20h jb not_char mov ah,0 call char_stack mov ah,2 call char_stack jmp get_strs not_char: cmp ah,0eh je backspace cmp ah,1ch je enter jmp get_strs backspace: mov ah,1 call char_stack mov ah,2 call char_stack jmp get_strs enter: mov ah,2 call char_stack pop ax ret char_stack: push bx push es push di push dx push ds mov bl,ah mov bh,0 add bx,bx jmp word ptr ds:[bx+8c2h] char_push: mov bx,ds:[8c0h] cmp bx,12 jz push_end mov ds:[bx+8b3h],al inc word ptr ds:[8c0h] jmp char_stack_end push_end: call input_error mov ah,0 int 16h call clear_screen jmp set_cursor char_pop: cmp word ptr ds:[8c0h],0 je char_stack_end dec word ptr ds:[8c0h] mov bx,ds:[8c0h] mov al,ds:[bx+8b3h] jmp char_stack_end char_show: mov dx,0b800h mov es,dx mov di,160*18+34 * 2 mov bx,0 show_str: cmp bx,ds:[8c0h] jne not_empty mov byte ptr es:[di],' ' jmp char_stack_end not_empty: mov al,ds:[bx+8b3h] mov es:[di],al inc bx add di,2 jmp show_str char_stack_end: pop ds pop dx pop di pop es pop bx ret write_time: push si push ax mov si,8b3h call get_value mov al,9 out 70h,al mov al,ah out 71h,al add si,2 call get_value mov al,8 out 70h,al mov al,ah out 71h,al add si,2 call get_value mov al,7 out 70h,al mov al,ah out 71h,al add si,2 call get_value mov al,4 out 70h,al mov al,ah out 71h,al add si,2 call get_value mov al,2 out 70h,al mov al,ah out 71h,al add si,2 call get_value mov al,0 out 70h,al mov al,ah out 71h,al pop ax pop si ret get_value: push si push bx ;这里要注意的是时间信息在CMOS中是以BCD码形式存放的。字符转换成数字时要注意进制。 mov al,16 mov bl,ds:[si] sub bl,30h mul bl mov bl,ds:[si+1] sub bl,30h add al,bl mov ah,al pop bx pop si ret ;清屏函数。 clear_screen: push ax push cx push es push di mov ax,0b800h mov es,ax mov di,0 mov cx,2000 clear_screen_lo: mov byte ptr es:[di],' ' mov byte ptr es:[di + 1],7 add di,2 loop clear_screen_lo pop di pop es pop cx pop ax ret ;设置时间时的输入错误函数。 input_error: push ax push si push bx push dx mov ah,2 mov dh,19 mov dl,20 mov bh,0 int 10h mov si,8c8h error_show: lodsb cmp al,0 jz error_show_end mov ah,0eh int 10h jmp error_show error_show_end: pop dx pop bx pop si pop ax ret codesg ends end start