1 assume cs:code,ss:stack 2 stack segment 3 db 128 dup (0) 4 stack ends 5 code segment 6 start: 7 mov ax,stack 8 mov ss,ax 9 mov sp,128 10 11 call copy_boot 12 13 ;设置CS:IP为0:7e00h 14 mov ax,0 15 push ax 16 mov ax,7e00h 17 push ax 18 retf 19 20 mov ax,4c00h 21 int 21h 22 ;org 7e00h 23 ;引导程序 24 boot: 25 jmp boot_begin 26 func0 db 'Hk_Mayfly----XIUXIUXIU~',0 27 func1 db '1) reset pc',0 28 func2 db '2) start system',0 29 func3 db '3) clock',0 30 func4 db '4) set clock',0 31 ;相减得到的是标号的相对位置,+7e00h得到的绝对位置 32 func_pos dw offset func0-offset boot+7e00h 33 dw offset func1-offset boot+7e00h 34 dw offset func2-offset boot+7e00h 35 dw offset func3-offset boot+7e00h 36 dw offset func4-offset boot+7e00h 37 time db 'YY/MM/DD hh:mm:ss',0 38 cmos db 9,8,7,4,2,0 39 clock1 db 'F1----change the color ESC----return menu',0 40 clock2 db 'Please input Date and Time,(YY MM DD hh mm ss):',0 41 change db 12 dup (0),0 42 43 boot_begin: 44 call init_boot 45 call cls_screen 46 call show_menu 47 jmp choose 48 mov ax,4c00h 49 int 21h 50 51 choose: 52 call clear_kb_buffer 53 ;获取我们输入的操作,跳转到对于函数 54 mov ah,0 55 int 16h 56 cmp al,'1' 57 je choose_func1 58 cmp al,'2' 59 je choose_func2 60 cmp al,'3' 61 je choose_func3 62 cmp al,'4' 63 je choose_func4 64 65 jmp choose 66 67 ;在题中提到了,开机后进入到ffff:0处执行指令 68 ;那我们也可以把重启理解为,跳转到ffff:0执行指令 69 ;所以我们利用jmp dword跳转到ffff:0地址,模拟重启 70 choose_func1: 71 mov bx,0ffffh 72 push bx 73 mov bx,0 74 push bx 75 retf 76 77 jmp choose 78 79 ;题中对引导现有的操作系统的描述是调用int 19,这里为了方便就直接写成函数了 80 choose_func2: 81 mov bx,0 82 mov es,bx 83 mov bx,7c00h 84 85 mov al,1;扇区数 86 mov ch,0 87 mov cl,1;扇区 88 mov dl,0 89 mov dh,0 90 mov ah,2;读取 91 int 13h 92 93 mov bx,0 94 push bx 95 mov bx,7c00h 96 push bx 97 retf 98 99 jmp choose 100 101 ;获取时间 102 choose_func3: 103 call show_time 104 105 jmp choose 106 107 show_time: 108 call init_boot 109 call cls_screen 110 ;显示按键信息 111 mov si,offset clock1-offset boot+7e00h 112 mov di,160*14+10*2;在14行10列显示 113 call show_line 114 show_time_start: 115 ;获取时间信息,并显示(将time中的未知字符替换为当前时间) 116 call get_time_info 117 mov di,160*10+30*2;屏幕显示的偏移地址 118 mov si,offset time-offset boot+7e00h;time标号的偏移地址 119 call show_line 120 121 ;获取键盘缓存区的数据 122 mov ah,1 123 int 16h 124 ;没有数据就跳回show_time_start 125 jz show_time_start 126 ;判断是否按下F1 127 cmp ah,3bh 128 je change_color 129 ;判断是否按下ESC 130 cmp ah,1 131 je Return_Main 132 ;有数据,但是是无用的键盘中断,清除 133 cmp al,0 134 jne clear_kb_buffer2 135 ;返回开始,重复之前的操作,达到刷新时间的效果。 136 jmp show_time_start 137 138 change_color: 139 call change_color_show 140 clear_kb_buffer2: 141 call clear_kb_buffer 142 jmp show_time_start 143 Return_Main: 144 ;返回到开始,重新打印菜单 145 jmp boot_begin 146 ret 147 148 choose_func4: 149 call set_time 150 jmp boot_begin 151 152 set_time: 153 call init_boot 154 call cls_screen 155 call clear_stack 156 157 ;设置提示信息显示位置 158 mov di,160*10+13*2 159 mov si,offset clock2-offset boot+7e00h 160 call show_line 161 ;显示修改后change中的内容 162 mov di,160*12+26*2 163 mov si,offset change-offset boot+7e00h 164 call show_line 165 166 call get_string 167 168 get_string: 169 mov si,offset change - offset boot + 07e00H 170 mov bx,0 171 getstring: 172 ;获取键盘输入的时间信息 173 mov ah,0 174 int 16h 175 176 ;输入的时间为数字0~9 177 cmp al,'0' 178 jb error_input 179 cmp al,'9' 180 ja error_input 181 ;将我们输入的时间字符入栈 182 call char_push 183 ;不能超过输入的数量 184 cmp bx,12 185 ja press_ENTER 186 mov di,160*12+26*2 187 call show_line 188 jmp getstring 189 error_input: 190 ;判断是不是按下退格或回车键 191 cmp ah,0eh 192 je press_BS 193 cmp ah,1ch 194 je press_ENTER 195 196 jmp getstring 197 ;按下回车 198 press_BS: 199 call char_pop 200 mov di,160*12+26*2 201 call show_line 202 jmp getstring 203 ;按下enter就退出 204 press_ENTER: 205 ret 206 207 char_push: 208 ;只能最多输入12个梳子 209 cmp bx,12 210 ja char_push_end 211 ;将数值移动到对应位置 212 mov ds:[si+bx],al 213 inc bx;表示我们输入了多少个字符 214 char_push_end: 215 ret 216 217 char_pop: 218 ;判断是否输入了设置时间的数值,没有就相当于删完了 219 cmp bx,0 220 je char_pop_end 221 ;否则用星号替换,相当于删除 222 dec bx 223 mov byte ptr ds:[si+bx],'*' 224 char_pop_end: 225 ret 226 227 clear_stack: 228 push bx 229 push cx 230 231 mov bx,offset change-offset boot+7e00h 232 mov cx,12 233 cls_stack: 234 ;替换change段中内容 235 mov byte ptr ds:[bx],'*' 236 inc bx 237 loop cls_stack 238 239 pop cx 240 pop bx 241 ret 242 243 244 ;获取时间 245 get_time_info: 246 ;从cmos ram获取年月日,时分秒6个数据 247 mov cx,6 248 ;获取存放单元地址 249 mov bx,offset cmos - offset boot + 7e00H 250 ;通过替换来显示 251 mov si,offset time - offset boot + 7e00H 252 next_point: 253 push cx 254 ;获取单元号 255 mov al,ds:[bx] 256 ;向70h端口写入要访问的单元地址,并从71h端口读取数据 257 out 70H,al 258 in al,71H 259 ;右移4位获取十位 260 mov ah,al 261 mov cl,4 262 shr al,cl 263 and ah,00001111b 264 ;将BCD码转换为ASCII码 265 add ax,3030H 266 ;写入time中 267 mov word ptr ds:[si],ax 268 ;下一单元号 269 inc bx 270 ;每个数据之间距离都是3 271 add si,3 272 pop cx 273 loop next_point 274 ret 275 276 ;改变颜色 277 change_color_show: 278 push bx 279 push cx 280 281 mov cx,2000 282 mov bx,1 283 next: 284 ;属性值+1,改变颜色 285 add byte ptr es:[bx],1 286 ;当超出字体颜色的数值(0~111h)时,将数值重置 287 cmp byte ptr es:[bx],00001000b 288 jne change_end 289 ;因为背景是黑色,所以文字颜色就不设置成黑色了 290 mov byte ptr es:[bx],1 291 change_end: 292 add bx,2 293 loop next 294 295 pop cx 296 pop bx 297 ret 298 299 clear_kb_buffer: 300 ;1号程序,用来检测键盘缓冲区是否有数据 301 ;如果有的话ZF!=0,没有,ZF=0 302 mov ah,1 303 int 16h 304 ;通过ZF判断减缓缓冲区是否有数据,没有就跳出 305 jz clear_kb_bf_end 306 mov ah,0 307 int 16h 308 jmp clear_kb_buffer 309 clear_kb_bf_end: 310 ret 311 312 init_boot: 313 ;基本设置,注意:程序的直接定址表默认段地址是CS 314 ;当程序转移到7c00h时,代码中CS值未发生改变, 315 ;所以需要我们指明段地址 316 mov bx,0b800h 317 mov es,bx 318 mov bx,0 319 mov ds,bx 320 ret 321 322 ;清屏 323 cls_screen: 324 mov bx,0 325 mov cx,2000 326 mov dl,' ' 327 mov dh,2;字体为绿色,不设置的话,在我们显示菜单时,字体和背景颜色相同 328 s: mov es:[bx],dx 329 add bx,2 330 loop s 331 sret: 332 ret 333 334 ;展示界面 335 show_menu: 336 ;在10行,30列显示菜单 337 mov di,160*10+30*2 338 ;保存在直接定址表的绝对位置 339 mov bx,offset func_pos-offset boot+7e00h 340 ;菜单有5行 341 mov cx,5 342 s1: 343 ;这里相当于外循环,每次一行 344 ;获取func_pos中每行的保存位置的偏移地址 345 mov si,ds:[bx] 346 ;调用内循环函数,输出一行的每个字符 347 call show_line 348 ;下一行偏移地址 349 add bx,2 350 ;下一行显示 351 add di,160 352 loop s1 353 ret 354 355 show_line: 356 push ax 357 push di 358 push si 359 show_line_start: 360 ;获取这一行的第si+1个字符 361 mov al,ds:[si] 362 ;判断是否到末尾 363 cmp al,0 364 je show_line_end 365 ;保存字符到显示缓冲区 366 mov es:[di],al 367 add di,2 368 inc si 369 jmp show_line_start 370 show_line_end: 371 pop si 372 pop di 373 pop ax 374 ret 375 376 boot_end:nop 377 378 ;转存引导程序 379 copy_boot: 380 ;将引导程序储存到指定位置 381 mov ax,0 382 mov es,ax 383 mov di,7e00h 384 385 mov ax,cs 386 mov ds,ax 387 mov si,offset boot 388 mov cx,offset boot_end-offset boot 389 cld 390 rep movsb 391 392 ret 393 394 code ends 395 end start
具体的在注释中都说明了。
jz指令:https://zhidao.baidu.com/question/564008138.html
int 16的1号程序:https://zhidao.baidu.com/question/511189643.html
总结
汇编的难度并不大,我认为在有编程的基础上,学习汇编要做到细致,细致的理解计算机编程的编译过程,对于我理解其他编程语言也有很大的帮助。欢迎大家关注,一起交流。