assume cs:code,ss:stack stack segment db 128 dup (0) stack ends code segment start: mov ax,stack mov ss,ax mov sp,128 call copy_boot ;設置CS:IP為0:7e00h mov ax,0 push ax mov ax,7e00h push ax retf mov ax,4c00h int 21h ;org 7e00h ;引導程序 boot: jmp boot_begin func0 db 'Hk_Mayfly----XIUXIUXIU~',0 func1 db '1) reset pc',0 func2 db '2) start system',0 func3 db '3) clock',0 func4 db '4) set clock',0 ;相減得到的是標號的相對位置,+7e00h得到的絕對位置 func_pos dw offset func0-offset boot+7e00h dw offset func1-offset boot+7e00h dw offset func2-offset boot+7e00h dw offset func3-offset boot+7e00h dw offset func4-offset boot+7e00h time db 'YY/MM/DD hh:mm:ss',0 cmos db 9,8,7,4,2,0 clock1 db 'F1----change the color ESC----return menu',0 clock2 db 'Please input Date and Time,(YY MM DD hh mm ss):',0 change db 12 dup (0),0 boot_begin: call init_boot call cls_screen call show_menu jmp choose mov ax,4c00h int 21h choose: call clear_kb_buffer ;獲取我們輸入的操作,跳轉到對於函數 mov ah,0 int 16h cmp al,'1' je choose_func1 cmp al,'2' je choose_func2 cmp al,'3' je choose_func3 cmp al,'4' je choose_func4 jmp choose ;在題中提到了,開機后進入到ffff:0處執行指令 ;那我們也可以把重啟理解為,跳轉到ffff:0執行指令 ;所以我們利用jmp dword跳轉到ffff:0地址,模擬重啟 choose_func1: mov bx,0ffffh push bx mov bx,0 push bx retf jmp choose ;題中對引導現有的操作系統的描述是調用int 19,這里為了方便就直接寫成函數了 choose_func2: mov bx,0 mov es,bx mov bx,7c00h mov al,1;扇區數 mov ch,0 mov cl,1;扇區 mov dl,80h mov dh,0 mov ah,2;讀取 int 13h mov bx,0 push bx mov bx,7c00h push bx retf jmp choose ;獲取時間 choose_func3: call show_time jmp choose show_time: call init_boot call cls_screen ;顯示按鍵信息 mov si,offset clock1-offset boot+7e00h mov di,160*14+10*2;在14行10列顯示 call show_line show_time_start: ;獲取時間信息,並顯示(將time中的未知字符替換為當前時間) call get_time_info mov di,160*10+30*2;屏幕顯示的偏移地址 mov si,offset time-offset boot+7e00h;time標號的偏移地址 call show_line ;獲取鍵盤緩存區的數據 mov ah,1 int 16h ;沒有數據就跳回show_time_start jz show_time_start ;判斷是否按下F1 cmp ah,3bh je change_color ;判斷是否按下ESC cmp ah,1 je Return_Main ;有數據,但是是無用的鍵盤中斷,清除 cmp al,0 jne clear_kb_buffer2 ;返回開始,重復之前的操作,達到刷新時間的效果。 jmp show_time_start change_color: call change_color_show clear_kb_buffer2: call clear_kb_buffer jmp show_time_start Return_Main: ;返回到開始,重新打印菜單 jmp boot_begin ret choose_func4: call set_time jmp boot_begin set_time: call init_boot call cls_screen call clear_stack ;設置提示信息顯示位置 mov di,160*10+13*2 mov si,offset clock2-offset boot+7e00h call show_line ;顯示修改后change中的內容 mov di,160*12+26*2 mov si,offset change-offset boot+7e00h call show_line call get_string get_string: mov si,offset change - offset boot + 07e00H mov bx,0 getstring: ;獲取鍵盤輸入的時間信息 mov ah,0 int 16h ;輸入的時間為數字0~9 cmp al,'0' jb error_input cmp al,'9' ja error_input ;將我們輸入的時間字符入棧 call char_push ;不能超過輸入的數量 cmp bx,12 ja press_ENTER mov di,160*12+26*2 call show_line jmp getstring error_input: ;判斷是不是按下退格或回車鍵 cmp ah,0eh je press_BS cmp ah,1ch je press_ENTER jmp getstring ;按下回車 press_BS: call char_pop mov di,160*12+26*2 call show_line jmp getstring ;按下enter就退出 press_ENTER: ret char_push: ;只能最多輸入12個梳子 cmp bx,12 ja char_push_end ;將數值移動到對應位置 mov ds:[si+bx],al inc bx;表示我們輸入了多少個字符 char_push_end: ret char_pop: ;判斷是否輸入了設置時間的數值,沒有就相當於刪完了 cmp bx,0 je char_pop_end ;否則用星號替換,相當於刪除 dec bx mov byte ptr ds:[si+bx],'*' char_pop_end: ret clear_stack: push bx push cx mov bx,offset change-offset boot+7e00h mov cx,12 cls_stack: ;替換change段中內容 mov byte ptr ds:[bx],'*' inc bx loop cls_stack pop cx pop bx ret ;獲取時間 get_time_info: ;從cmos ram獲取年月日,時分秒6個數據 mov cx,6 ;獲取存放單元地址 mov bx,offset cmos - offset boot + 7e00H ;通過替換來顯示 mov si,offset time - offset boot + 7e00H next_point: push cx ;獲取單元號 mov al,ds:[bx] ;向70h端口寫入要訪問的單元地址,並從71h端口讀取數據 out 70H,al in al,71H ;右移4位獲取十位 mov ah,al mov cl,4 shr al,cl and ah,00001111b ;將BCD碼轉換為ASCII碼 add ax,3030H ;寫入time中 mov word ptr ds:[si],ax ;下一單元號 inc bx ;每個數據之間距離都是3 add si,3 pop cx loop next_point ret ;改變顏色 change_color_show: push bx push cx mov cx,2000 mov bx,1 next: ;屬性值+1,改變顏色 add byte ptr es:[bx],1 ;當超出字體顏色的數值(0~111h)時,將數值重置 cmp byte ptr es:[bx],00001000b jne change_end ;因為背景是黑色,所以文字顏色就不設置成黑色了 mov byte ptr es:[bx],1 change_end: add bx,2 loop next pop cx pop bx ret clear_kb_buffer: ;1號程序,用來檢測鍵盤緩沖區是否有數據 ;如果有的話ZF!=0,沒有,ZF=0 mov ah,1 int 16h ;通過ZF判斷減緩緩沖區是否有數據,沒有就跳出 jz clear_kb_bf_end mov ah,0 int 16h jmp clear_kb_buffer clear_kb_bf_end: ret init_boot: ;基本設置,注意:程序的直接定址表默認段地址是CS ;當程序轉移到7c00h時,代碼中CS值未發生改變, ;所以需要我們指明段地址 mov bx,0b800h mov es,bx mov bx,0 mov ds,bx ret ;清屏 cls_screen: mov bx,0 mov cx,2000 mov dl,' ' mov dh,2;字體為綠色,不設置的話,在我們顯示菜單時,字體和背景顏色相同 s: mov es:[bx],dx add bx,2 loop s sret: ret ;展示界面 show_menu: ;在10行,30列顯示菜單 mov di,160*10+30*2 ;保存在直接定址表的絕對位置 mov bx,offset func_pos-offset boot+7e00h ;菜單有5行 mov cx,5 s1: ;這里相當於外循環,每次一行 ;獲取func_pos中每行的保存位置的偏移地址 mov si,ds:[bx] ;調用內循環函數,輸出一行的每個字符 call show_line ;下一行偏移地址 add bx,2 ;下一行顯示 add di,160 loop s1 ret show_line: push ax push di push si show_line_start: ;獲取這一行的第si+1個字符 mov al,ds:[si] ;判斷是否到末尾 cmp al,0 je show_line_end ;保存字符到顯示緩沖區 mov es:[di],al add di,2 inc si jmp show_line_start show_line_end: pop si pop di pop ax ret boot_end:nop ;轉存引導程序 copy_boot: ;將引導程序儲存到指定位置 mov ax,0 mov es,ax mov di,7e00h mov ax,cs mov ds,ax mov si,offset boot mov cx,offset boot_end-offset boot cld rep movsb ret code ends end start
具體的在注釋中都說明了。
jz指令:https://zhidao.baidu.com/question/564008138.html
int 16的1號程序:https://zhidao.baidu.com/question/511189643.html
總結
匯編的難度並不大,我認為在有編程的基礎上,學習匯編要做到細致,細致的理解計算機編程的編譯過程,對於我理解其他編程語言也有很大的幫助。歡迎大家關注,一起交流。