900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > 突破BIOS无法连续读取软盘72扇区的限制

突破BIOS无法连续读取软盘72扇区的限制

时间:2019-05-11 00:04:24

相关推荐

突破BIOS无法连续读取软盘72扇区的限制

具体代码调试和讲解请参看视频:

Linux kernel Hacker, 从零构建自己的内核

一直以来,我们的操作系统加载器,秉承简单够用的原则,只要能把编译好的二进制内核送进内存就可以了,所以加载器的算法是,连续读取软盘扇区,将扇区的内容写入到从0x8000 开始的内存中。以下是我们内核加载器的代码:

org 0x7c00;LoadAddr EQU 08000h entry:mov ax, 0mov ss, axmov ds, axmov es, axmovBX, LoadAddr ; ES:BX 数据存储缓冲区movCH, 1 ;CH 用来存储柱面号movDH, 0 ;DH 用来存储磁头号readFloppy:cmpbyte [load_count], 0je beginLoadmovCL, 1 ;CL 用来存储扇区号movAH, 0x02; AH = 02 表示要做的是读盘操作movAL, 18 ; AL 表示要练习读取几个扇区movDL, 0 ;驱动器编号,一般我们只有一个软盘驱动器,所以写死 ;为0INT0x13;调用BIOS中断实现磁盘读取功能incCHdecbyte [load_count]JC finaddbx, 512 * 18jmpreadFloppybeginLoad:jmpLoadAddrload_count db 3 ;连续读取几个柱面fin:HLTjmp fin

load_count 指的是要读取的软盘柱面数。一个1.44M软盘,其中一个磁面有80个柱面,一个柱面有有两面,上面和背面,每面对应一个磁道,一个磁道有18个扇区,一个扇区有512字节。上面代码中,load_cout 的值设置为3, 也就是程序要连续读取3个柱面,也就是要将软盘中大约 3* 18 * 512 字节,也就是27k的内容写入地址为08000h的内存中。

随着我们开发的操作系统功能越来越强大,其代码量也越来越大,现在内核编译后,已经接近15k了,超过27k是迟早的事情,一旦超过27k,那么我们的软盘在往内存拷贝内核时,就需要连续将4个柱面,也就是72扇区的数据写入到内存中。按照设想,我们只要把上面load_cout的值改成4就可以了。

然而一旦改成4,问题就出现了,因为读取4个柱面,也就要连续向内存读入18*4 = 72个扇区的内容,现在大多数BIOS提供的int 013h软盘读取中断功能,一旦发现调用代码要读取的内容有72扇区以上,它就会返回失败,如果有同学尝试着把上面代码的load_cout该成4,然后再运行程序,就会发现内核加载失败,也就是int 03h 的中断调用返回失败,这样一来,一旦我们的内核大小超过27k, 的话,我们现在的加载器就无法正确加载了。

如果你使用的是虚拟机Bochs 来运行上面的加载器代码,那么连续读取软盘超过72扇区时,Bochs提供的Bios调用会返回失败。我们看看Bochs源码中有个Bios功能的函数如下(rombios.c):

void7273 int13_diskette_function(DS, ES, DI, SI, BP, ELDX, BX, DX, CX, AX, IP, CS, FLAGS)7274 Bit16u DS, ES, DI, SI, BP, ELDX, BX, DX, CX, AX, IP, CS, FLAGS;7275 {......7337 if ((drive > 1) || (head > 1) || (sector == 0) ||7338 (num_sectors == 0) || (num_sectors > 72)) {7339 BX_INFO("int13_diskette: read/write/verify: parameter out of range\n");7340 SET_AH(1);7341 set_diskette_ret_status(1);7342 SET_AL(0); // no sectors read7343 SET_CF(); // error occurred7344 return;7345 }....}

这个函数模拟的就是软盘读取BIOS int 013h中断功能,当我们的代码连续读取几个柱面的扇区时,我怀疑Bochs会把这些要读的扇区请求积累起来,然后把要读取的扇区一次性进行写入,而不是请求一次就执行一次读取动作,因此代码中的条件判断num_sectors > 72 就会成立,于是连续读取超过4个柱面也就是72扇区,Bochs模拟器就会返回失败。由于当前很多虚拟机都大量使用Bochs的源代码,或是实现机制类似,我在mac上用的是parallels ,它的反应跟Bochs一样,也是连续读取软盘超过72扇区时,返回了错误,因此在前面的加载器代码中,一旦连续读取4个柱面以上时,读取请求就会返回失败。

为了绕过这个限制,现在我们加载器的做法是,不再一次连续读取18个扇区,而是一次读取一个扇区,把这个扇区的数据先读入一个给定的,大小为512字节的缓冲区内,然后再把该缓冲区的内容,拷贝到指定的内存中,也就是我们要多做一次没有意义的拷贝工作。

由于我们的内核要加载到内存08000h, 因此,我将08000h前512字节,也就是起始地址为07E00h开始的512字节内存作为软盘一个扇区数据的缓冲区,每次从软盘读入一个扇区数据时,先把数据写入到这个缓冲区,然后再把这个缓冲区的数据拷贝到08000h之后的地址,代码如下:

org 0x7c00;LoadAddr EQU 08000h BufferAddr EQU 7E0hBaseOfStackequ 07c00hentry:mov ax, 0mov ss, axmov ds, axmov ax, BufferAddrmov es, axmov ax, 0mov ss, axmov sp, BaseOfStackmov di, axmov si, axmovBX, 0 ; ES:BX 数据存储缓冲区movCH, 1 ;CH 用来存储柱面号movDH, 0 ;DH 用来存储磁头号movCL, 0 ;CL 用来存储扇区号;每次都把扇区写入地址 07E00处readFloppy:cmpbyte [load_count], 0je beginLoadmovbx, 0incclmovAH, 0x02; AH = 02 表示要做的是读盘操作movAL, 1 ; AL 表示要读取几个扇区movDL, 0 ;驱动器编号,一般我们只有一个软盘驱动器,所以写死 ;为0INT0x13;调用BIOS中断实现磁盘读取功能JC fin;把刚写入07E00的一个扇区的内容写入从08000h开始的地址copySector:push sipush dipush cxmov cx, 0200h ;缓冲区数据大小,也就是512字节mov di, 0mov si, 0mov ax, word [load_section];esmov ds, axcopy:cmp cx, 0je copyendmov al, byte [es:si] ;es:si指向07E00mov byte [ds:di], alinc diinc sidec cxjmp copycopyend:pop cxpop dipop simov bx, dsadd bx, 020hmov ax, 0mov ds, axmov word [load_section], bxmov bx, 0;end of copySectorcmpcl, 18jb readFloppyincCHmovcl, 0decbyte [load_count]jmpreadFloppybeginLoad:mov ax, 0mov ds, axjmpLoadAddrload_count db 10 ;连续读取几个柱面load_section dw 0800hfin:HLTjmp fin

这段代码跟开头的代码,不同之处在于,第一段代码,是连续从软盘读取扇区数据到指定的内存里,这么做可能存在一个隐性的难以发现的问题:

一是编译器可能会对代码进行优化,最终编译出来的二进制代码可能给汇编代码的原意有所不同,编译后的代码可能会把所以读请求积攒起来,然后一次发出读取命令,由于代码要读取4个柱面,每次读取18个扇区,最终编译的代码可能是把4个柱面,总共72个扇区积攒起来,然后一次读取,这样的话,就可能违反了Bochs虚拟机的读取限制。

二是,在读取数据时,只要读取的数据不需要立刻使用的话,CPU可能会将数据读取的请求积累起来,当有数据请求时,才把所有积攒起来的数据读取请求一次发出,从而提高读写效率。

无论是那种情况,都有可能造成一次读取超过72扇区的请求,从而被虚拟机拒绝。改动后的代码是,当读取一个扇区的数据后,程序立马进行数据拷贝,这样的话,上面提到的优化机制就不能产生作用,数据的读取请求就不会被积攒起来,因此就不会遭遇Bochs虚拟机的读取限制。

我们分析下第二段代码,BufferAddr 指的是软盘扇区的数据读取后要写入的缓冲区地址,注意它的值是0x7E0, 为什么是0x7E0,而不是0x7E00呢,这是因为这个值会直接付给段寄存器es, 在实模式下,寻址方式是 段地址:段偏移,转换成直接地址就是 段地址16 + 段偏移,由于0x7E016 = 0x7E00, 因此把0x7E00付给段寄存器,就需要把最后一个0去掉,也就是0x7E00 要除以16.

readFloppy 这段代码通过int 013h调用,让BIOS从软盘中读取一个扇区的数据,然后把数据写入到起始地址为0x7E00的缓冲区,接着copySector这段代码把刚写入缓冲区的数据拷贝到内核的加载地址,也就是08000h之后,变量load_section 用来存储的是内核加载的起始地址,由于这个值要付给段寄存器ds,所以它的实际值是0800h而不是08000h,每次往这个地址写入512字节的数据后,下次写入时,地址要往下偏移512字节,512除以16等于32,因此要把load_section的值加上32,也就是020h.

有了上面改动后,加载代码能读取任意多个扇区数据到内存而不用担心Bochs虚拟机的限制,虽然我用的是Virtual Box, 但据说Virtual Box使用的也是Bochs代码,所以当我连续加载扇区超过72时,Virtual Box对代码的数据读取请求也返回识别,使用上面的修改后,数据的读取问题也能得到解决了。

Message Box的计时器效果

最后,我们实现一个计数器效果,在write_vga_desktop.c里,根据下面代码进行更改:

void CMain(void) {...for(;;) {char* pStr = intToHexStr(counter);counter++;boxfill8(shtMsgBox->buf, 160, COL8_C6C6C6, 40, 28, 119, 43);showString(shtctl, shtMsgBox, 40, 28, COL8_000000,pStr);io_cli();if (fifo8_status(&keyinfo) + fifo8_status(&mouseinfo) == 0) {io_sti();}...}

我们在主循环里,让一个变量从0开始自加,然后把其结果显示在Message Box的主窗体里,如果大家按照上面代码更改后,会发现界面有明显的闪烁效果。这是我们当前图层的刷新机制导致的,在后面课程中,我将与大家研究如何消除这种令人痛苦的闪烁现象。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。