基于扩展INT 13H的光驱引导程序设计
2014-02-15 01:53
309 查看
最近一直在研究操作系统,好多资料的引导程序完全基于软盘设计,在软盘早已被淘汰的今天,用软盘没什么意思了就,这里我是基于最常见光盘设计的,光盘还有一好处,就是引导扇区可以大于512字节,我们要考虑的,仅在于实模式下的地址访问空间(1M),这对于一个引导程序来说,已经很够用了,所以我们可以直接用C语言嵌入汇编实现,毕竟现在GCC是很NB的。
首先参考了基于软盘的引导程序,一般用的是INT13中断,也试了一下,发现INT13获得的所谓的第一扇区是从引导扇区开始的,这样就会产生一个问题:无法读取ISO9660的文件头(ISO9660文件头之后才是引导扇区)。这样的办法一定不行,因为如果这样,我们用的系统盘的引导就没有办法实现了,因为系统盘里面文件更是多而杂,没有ISO文件头的支持,是根本没办法获得文件的相关数据的。所以肯定会有更好的办法,只是还不了解。于是我便从windows7的安装光盘的引导程序下手,IDA反编译看出引导程序调用INT13之前在AH寄存器装入了0x42,搜了一下,还真的有,这个办法叫扩展INT13.
为了确定BIOS是否支持扩展INT13(其实有些多余,不过还是分享下吧),我们先来写程序来测试一下。
入口:AH=0x41,DL=设备号,BX=0x55AA返回:若存在,CF=1,BX=0xAA55,AH=主版本号,若不存在,CF=0,AH=错误码。
这里我在用BOCHS和VMWARE虚拟的时候发现即使存在CF最终也会为1,不知道为什么,在这个问题上我纠结了好久,甚至翻看了BOCHS虚拟机的源代码,也没查出个究竟来。还好有第二个办法:检测BX值。附代码:
有了上面代码的保证,我们就可以更踏实地实现从光驱读数据了。
下面具体了解下扩展INT13的用法,这里我们仅以读为例。
扩展读入口:AH=0x42,DL=设备号,DS:SI指向DAP(Disk Address Packet)结构。
这里要注意扩展INT13和一般INT13的区别,前者的单位扇区大小是由存储介质确定的,而后者固定为512字节。所以这里要小心了,我们的ISO9660文件系统一般单位扇区大小为0x800字节,也就是2048字节,其实ISO9660还有逻辑块的说法,这里我们不做区分,暂且把逻辑块和扇区看做同等,这样不会影响到我们编程。这里我们直接定义一个地址,让DAP结构指针直接指向该地址,这样我们可以更方便地进行调试。
首先我们必须要接触的结构是Primary Volume Descriptor,里面详细记载了整个ISO文件的数据,其实也可以是其他的结构来记录,但因为最常见的是这个结构,所以就以它为例了,想知道更多东西的朋友可以看ECMA文档。代码选自linux源代码的Iso_fs.h,这里提到的711是指ECMA-119中7.1.1章节说明的数据记录方式,下同。这里我们仅注意我们需要的成员:
下一个我们需要研究的结构是Directory Record,这里面记录了一层文件夹的相关数据,因为ISO文件是可以分很多层的,会有各种子目录,所以会有很多这样的结构,这里我们仅研究根目录的数据,毕竟内核文件一般是直接放在根目录的,参考了linux代码,这里我们仿照着也写一个。
这样,基于光驱的引导程序的关键部分我们就完成了,总的来说还是比较容易实现的,毕竟是用C,对于刚学汇编不久的我算是很兴奋的一件事了。
最后建议,千万不要用百度,虽然多数时候他会帮你,但是有时候真的会很坑。。。有点英文基础的,直接上google吧。
有的博客用一句话来结束文章,诸如你不能休息什么的,我觉得没必要说得那么励志了,好像马上要上刀山下火海。我只想说:理想,总是要顺其自然的,跟着心走,听听内心在说什么。千万不要价值观绑架,用别人的眼睛看自己,那样真的会很累,而且还会被时代的大山压得死死的。
参考文献:
Wikipedia
-- INT 13H
ECMA-119
首先参考了基于软盘的引导程序,一般用的是INT13中断,也试了一下,发现INT13获得的所谓的第一扇区是从引导扇区开始的,这样就会产生一个问题:无法读取ISO9660的文件头(ISO9660文件头之后才是引导扇区)。这样的办法一定不行,因为如果这样,我们用的系统盘的引导就没有办法实现了,因为系统盘里面文件更是多而杂,没有ISO文件头的支持,是根本没办法获得文件的相关数据的。所以肯定会有更好的办法,只是还不了解。于是我便从windows7的安装光盘的引导程序下手,IDA反编译看出引导程序调用INT13之前在AH寄存器装入了0x42,搜了一下,还真的有,这个办法叫扩展INT13.
为了确定BIOS是否支持扩展INT13(其实有些多余,不过还是分享下吧),我们先来写程序来测试一下。
入口:AH=0x41,DL=设备号,BX=0x55AA返回:若存在,CF=1,BX=0xAA55,AH=主版本号,若不存在,CF=0,AH=错误码。
这里我在用BOCHS和VMWARE虚拟的时候发现即使存在CF最终也会为1,不知道为什么,在这个问题上我纠结了好久,甚至翻看了BOCHS虚拟机的源代码,也没查出个究竟来。还好有第二个办法:检测BX值。附代码:
char IsExtened()//返回1,即存在;返回0,即不存在 { unsigned int BxRtn = 0;//因为C代码中嵌入汇编一般寄存器会用32位的,所以为了防止内存访问过界,我们也要用32位的 __asm__ __volatile__( "movb $0x41, %%ah\n\r" "movw $0x55aa, %%bx\n\r" "int $0x13\n\r" "movl %0, %%ebx\n\r"//32位赋值 :"=r"(BxRtn)//输出BX到BxRtn,这里要注意gcc中汇编嵌入的输入和输出用到的r,r所代表的是32位的寄存器,百度上很多说明都是错误的 : ); return BxRtn == 0xaa55; }
有了上面代码的保证,我们就可以更踏实地实现从光驱读数据了。
下面具体了解下扩展INT13的用法,这里我们仅以读为例。
扩展读入口:AH=0x42,DL=设备号,DS:SI指向DAP(Disk Address Packet)结构。
struct DiskAddressPacket { unsigned char PacketSize;// 数据包尺寸,一般直接填0x10 unsigned char Reserved;// 必须为0 unsigned short BlockCount;// 要传输的数据块个数(以扇区为单位) unsigned long BufferAddr;// 缓冲区地址 unsigned long long BlockNum;// 磁盘起始绝对扇区数 };
这里要注意扩展INT13和一般INT13的区别,前者的单位扇区大小是由存储介质确定的,而后者固定为512字节。所以这里要小心了,我们的ISO9660文件系统一般单位扇区大小为0x800字节,也就是2048字节,其实ISO9660还有逻辑块的说法,这里我们不做区分,暂且把逻辑块和扇区看做同等,这样不会影响到我们编程。这里我们直接定义一个地址,让DAP结构指针直接指向该地址,这样我们可以更方便地进行调试。
#define DAPAddr 0xa000//DAP结构首地址 int MapIsoBlockToMemory(unsigned long addr,unsigned long long Block,unsigned short count)//内存地址,第几个块,读取的个数 { int status = 0; struct DiskAddressPacket *DAP; DAP = (struct DiskAddressPacket*)DAPAddr; DAP->Reserved = 0; DAP->PacketSize = 0x10; DAP->BlockCount = count; DAP->BlockNum = Block; DAP->BufferAddr = addr; __asm__ __volatile__( "movl %1, %%edx\n\r" "movl %2, %%esi\n\r" "movb $0x42, %%ah\n\r" "int $0x13\n\r" "jne Success\n\r" "movl $0, %0\n\r" "Success:\n\r" "movl $1, %0\n\r" :"=r"(status)//为0失败,为1成功 :"r"(GetCdromNumber()),"p"(DAP)//GetCdromNumber()返回驱动号 ); //DispInt16(status); return status; }到这里,我们就解决的光驱文件映射至内存的问题,下面就是了解ISO9660文件系统并把内核文件映射至内存了,这里我们需要参考文档ECMA-119里面有非常详细的介绍,因为这里我们仅是以映射内核文件至内存为目的,所以并没有完全实现它,够用就好。
首先我们必须要接触的结构是Primary Volume Descriptor,里面详细记载了整个ISO文件的数据,其实也可以是其他的结构来记录,但因为最常见的是这个结构,所以就以它为例了,想知道更多东西的朋友可以看ECMA文档。代码选自linux源代码的Iso_fs.h,这里提到的711是指ECMA-119中7.1.1章节说明的数据记录方式,下同。这里我们仅注意我们需要的成员:
#define ISODCL(from, to) (to - from + 1) struct iso_primary_descriptor { char type [ISODCL ( 1, 1)]; /* 711 */ char id [ISODCL ( 2, 6)]; char version [ISODCL ( 7, 7)]; /* 711 */ char unused1 [ISODCL ( 8, 8)]; char system_id [ISODCL ( 9, 40)]; /* achars */ char volume_id [ISODCL ( 41, 72)]; /* dchars */ char unused2 [ISODCL ( 73, 80)]; char volume_space_size [ISODCL ( 81, 88)]; /* 733 */ char unused3 [ISODCL ( 89, 120)]; char volume_set_size [ISODCL (121, 124)]; /* 723 */ char volume_sequence_number [ISODCL (125, 128)]; /* 723 */ char logical_block_size [ISODCL (129, 132)]; /* 723 */ //我们需要的,逻辑块大小,其实也可以硬性规定为0x800,这里是为了保证通用 char path_table_size [ISODCL (133, 140)]; /* 733 */ char type_l_path_table [ISODCL (141, 144)]; /* 731 */ char opt_type_l_path_table [ISODCL (145, 148)]; /* 731 */ char type_m_path_table [ISODCL (149, 152)]; /* 732 */ char opt_type_m_path_table [ISODCL (153, 156)]; /* 732 */ char root_directory_record [ISODCL (157, 190)]; /* 9.1 */ //我们需要的,记载了记录根文件相关数据的数据结构 char volume_set_id [ISODCL (191, 318)]; /* dchars */ char publisher_id [ISODCL (319, 446)]; /* achars */ char preparer_id [ISODCL (447, 574)]; /* achars */ char application_id [ISODCL (575, 702)]; /* achars */ char copyright_file_id [ISODCL (703, 739)]; /* 7.5 dchars */ char abstract_file_id [ISODCL (740, 776)]; /* 7.5 dchars */ char bibliographic_file_id [ISODCL (777, 813)]; /* 7.5 dchars */ char creation_date [ISODCL (814, 830)]; /* 8.4.26.1 */ char modification_date [ISODCL (831, 847)]; /* 8.4.26.1 */ char expiration_date [ISODCL (848, 864)]; /* 8.4.26.1 */ char effective_date [ISODCL (865, 881)]; /* 8.4.26.1 */ char file_structure_version [ISODCL (882, 882)]; /* 711 */ char unused4 [ISODCL (883, 883)]; char application_data [ISODCL (884, 1395)]; char unused5 [ISODCL (1396, 2048)]; };这样的结构其实不不复杂,我们仅需要用前面的MapIsoBlockToMemory函数将ISO文件的第一扇区(其实是第16扇区)映射到一块缓冲区,缓冲区首地址即是该结构的首地址。
下一个我们需要研究的结构是Directory Record,这里面记录了一层文件夹的相关数据,因为ISO文件是可以分很多层的,会有各种子目录,所以会有很多这样的结构,这里我们仅研究根目录的数据,毕竟内核文件一般是直接放在根目录的,参考了linux代码,这里我们仿照着也写一个。
struct RootDirectoryRecord { char lengh_of_Directory_Record[ISODCL (1, 1)];/*711*/ //需要注意的,循环的时候会用到 char extended_attribute_record_lengh[ISODCL (2,2)];/*711*/ char location_of_extent[ISODCL (3, 10)];/*733*/ //需要注意的,根文件数据入口,以扇区为单位 char data_lengh[ISODCL (11, 18)];/*733*/ //需要注意的,文件大小 char record_of_data_and_time[ISODCL (19, 25)]; char file_flags[ISODCL (26, 26)]; char file_unit_size[ISODCL (27, 27)];/*711*/ char interleave_gap_size[ISODCL (28, 28)];/*711*/ char volumm_sequence_number[ISODCL (29, 32)];/*711*/ char lengh_of_file_identifier[ISODCL (33, 33)];//需要注意的,文件名长度 char file_identifier;//需要注意的,文件名,注意这里我们用到的是char类型,其实没什么意义,读取的时候只需要取此成员的首地址读取就行了 };这里要注意一点,ISO文件头所记录的root_directory_record实质是描述了根文件目录,里面的location of extent正是指向了存放根文件目录的Directory Record结构的扇区,这个扇区里面会存放很多的Directory Record结构,这些结构会对根目录的每个文件进行描述。这里如果大家不太理解,可以用WinHex研究下,很快就能理解了。有了这两个结构,搜索内核文件就容易多了。
const char KernelName[]="KERNEL.BIN"; #define BufferBaseAddr 0x9000//这里便于调试,临时buffer和kernel文件的映射内存区域分开了 #define KernelBaseAddr 0x2000//内核文件映射首地址 #define FirstBlock 16//第一扇区 int MapKernelToMemory() { struct PrimaryVolumeDescriptor *PVD; struct RootDirectoryRecord *RDR; if(!IsExtened()) { DispString16("System Error!",13); return -1; } if(!MapIsoBlockToMemory(BufferBaseAddr,FirstBlock,1))//映射ISO9660文件头 { DispString16("map Error!",13); return -1; } PVD = (struct PrimaryVolumeDescriptor*)BufferBaseAddr; unsigned int BlockSize = 0; BlockSize = (PVD->logical_block_size[2] << 8) + PVD->logical_block_size[3];//获取块大小 //DispInt16(PVD); //DispString16("NextBlock0",10); //DispInt16(BlockSize); RDR = (struct RootDirectoryRecord*)PVD->root_directory_record;//获取描述根目录的数据结构 unsigned int NextBlock = 0; unsigned long FileSize = 0; //DispString16("NextBlock1",10); //DispInt(RDR->location_of_extent[9]); NextBlock = (RDR->location_of_extent[4] << 24) +(RDR->location_of_extent[5] << 16) + (RDR->location_of_extent[6] << 8)+ RDR->location_of_extent[7];//取得记录Directory Record的扇区 //NextBlock = (BlockSize * NextBlock)/512;//千万千万要注意,我就上过当 //DispInt16(NextBlock); if(!MapIsoBlockToMemory(BufferBaseAddr,NextBlock,1)) { DispString16("Map Error...",12); return -1; } RDR = (struct RootDirectoryRecord*)BufferBaseAddr; for(;(*RDR->lengh_of_Directory_Record) != 0;RDR = (struct RootDirectoryRecord*)(((char*)RDR) + (*RDR->lengh_of_Directory_Record)))//激动人心的搜索 { if(*RDR->lengh_of_file_identifier == 0)//可能会有空描述,不鸟他 continue; if(!strcmp(&RDR->file_identifier,KernelName))//这个strcmp要自己实现了,我就不贴了,网上有很多的 {//搜索到了 FileSize = (((RDR->data_lengh[7] + (RDR->data_lengh[6] << 8)) + (RDR->data_lengh[5] << 16)) + (RDR->data_lengh[4] << 24));//文件大小 NextBlock = (RDR->location_of_extent[6] << 8) + RDR->location_of_extent[7];//取2字节 MapIsoBlockToMemory(KernelBaseAddr,NextBlock,(FileSize / BlockSize) + 1);//映射 break;//退出搜索 } } return 0; }
这样,基于光驱的引导程序的关键部分我们就完成了,总的来说还是比较容易实现的,毕竟是用C,对于刚学汇编不久的我算是很兴奋的一件事了。
最后建议,千万不要用百度,虽然多数时候他会帮你,但是有时候真的会很坑。。。有点英文基础的,直接上google吧。
有的博客用一句话来结束文章,诸如你不能休息什么的,我觉得没必要说得那么励志了,好像马上要上刀山下火海。我只想说:理想,总是要顺其自然的,跟着心走,听听内心在说什么。千万不要价值观绑架,用别人的眼睛看自己,那样真的会很累,而且还会被时代的大山压得死死的。
参考文献:
Wikipedia
-- INT 13H
ECMA-119
相关文章推荐
- 基于PXA270嵌入式系统的Windows CE引导程序设计
- U盘启动以及int 13h扩展读取U盘内容
- 扩展Int 13H调用规范
- 扩展Int 13H调用规范
- 扩展int 13H/调用规范 /大硬盘读写中断/FAT NTFS文件结构
- 中断INT 13H的扩展功能
- 扩展int 13H/调用规范 /大硬盘读写中断/FAT NTFS文件结构
- U盘启动以及int 13h扩展读取U盘内容
- 扩展int 13H/调用规范 /大硬盘读写中断/FAT NTFS文件结构
- 基于ECMA 的JavaScript 的面向对象程序设计
- GNS3上实现基于Ftp和www服务的扩展访问列表IP ACL
- 基于位置无关代码PIC的程序设计
- 基于Spring可扩展Schema提供自定义配置支持(spring配置文件中 配置标签支持)
- PHP xml-rpc 应用说明-基于php扩展
- 基于ARM的硬件启动程序设计-初始化堆栈
- WinForm实现基于BindingSource的方法扩展
- 详解基于Linux下正则表达式(基本正则和扩展正则命令使用实例)
- 基于Swoole开发PHP扩展
- 虚拟化技术正从传统的基于虚拟机管理程序的服务器虚拟化,扩展到网络虚拟化。
- 基于FPGA的以太网MII接口扩展设计与实现