NBP开发手记
以前对PXE只有一点点了解,最近在研究PXE无盘启动XP,所以学习了PXE的spec文档,对PXE有了初步的认识,同时又在调试时遇到了很多的很挫的问题。现在把它们记录下来。
所谓NBP是来自PXE文档的说法,全称 Network Bootstrap Program,也就是我以前理解的 PXE Boot File。他的作用就是 Hook Int 13h 虚拟一个硬盘,对硬盘的读写,通过 PXE API(通常使用UDP)定向到网络,返回数据给调用者,达到通过网络启动的目的。
一、NBP工作流程
1.Int 1Ah 获取PXE接口,这没什么说的。分2.0和2.1或以上版本2种区别,一般只需要兼容2.1就行。
2.PXENV_GET_CACHED_INFO 的3号功能,获取自己的IP和服务器IP。
3.PXENV_UDP_OPEN 初始化 UDP 网络接口。
4.Hook Int13h,I/O PXENV_UDP_READ/PXENV_UDP_WRITE
5.读 MBR 到 0000:7C00。
6.跳转 0000:7C00。
这些流程除了第四步Hook Int13h都不难,只是UDP部分需要注意处理丢包的问题。C/S用会话ID,很好解决。
另外那个UDP接口,收到大于4K的包,容易卡死在UDP_RECV里很久才返回一个错误。所以一次收发包最好小于4KB……
二、Hook Int13h
对于无盘启动XP,需要处理如下的功能号。并尽量按照标准来处理。。
FUN00: IO复位
FUN02: 读扇区
FUN03: 写扇区
FUN08: 读磁盘参数
FUN15: 读磁盘类型
FUN41: 检验扩展功能是否存在
FUN42: 扩展读
FUN43: 扩展写
FUN48: 扩展读磁盘参数
貌似对于无盘启动XP,处理写操作好像并不是必要的。一般情况XP在内核启动前不需要写操作。内核启动后加载磁盘驱动,然后才会有写操作,那时候已经不需要Int13h了。不过NTLDR是设计了写操作接口的,说不准,没准啥时候来个FUN03/43,所以最好还是处理了吧。
而且我在做NBP之前还在想,如果进入保护模式后,还使用Int13h怎么办?因为PXE文档中对保护模式中使用PXE API的说明很模糊。SDK中也没有例子,做起来会很烦,可是真正做的时候发现,在进入保护模式后,根本不需要Int13h了,系统会使用磁盘驱动来完成IO。
而且我发现XP在启动的时候并没有使用扩展Int13h功能,不过调试发现Win7的 Boot Sector/Mgr 会使用Int13x。
三、万恶的 CHS 寻址
(以下段落引用互联网。。。有更正)
—– Quote Start—–
到目前为止,人们常说的硬盘参数还是古老的 CHS (Cylinder/Head/Sector)参数。那么为什么要使用这些参数,它们的意义是什么?它们的取值范围是什么?
很久以前(long long ago …),硬盘的容量还非常小的时候,人们采用与软盘类似的结构生产硬盘。也就是硬盘盘片的每一条磁道都具有相同的扇区数。由此产生了所谓的3D参数 (Disk Geometry)。即磁头数(Heads),柱面数(Cylinders),扇区数(Sectors),以及相应的寻址方式。
其中:
磁头数(Heads) 表示硬盘总共有几个磁头,也就是有几面盘片,最大为 255 (用 8 个二进制位存储);
柱面数(Cylinders) 表示硬盘每一面盘片上有几条磁道,最大为 1023(用 10 个二进制位存储);
扇区数(Sectors) 表示每一条磁道上有几个扇区, 最大为 63 (用 6个二进制位存储)。
每个扇区一般是 512个字节,理论上讲这不是必须的,但好象没有取别的值的。
所以磁盘最大容量为:
256 * 1024 * 63 * 512 / 1048576 = 8064 MB ( 1M = 1048576 Bytes ) 或硬盘厂商常用的单位:
256 * 1024 * 63 * 512 / 1000000 = 8263 MB ( 1M = 1000000 Bytes )
在 CHS 寻址方式中,磁头,柱面,扇区的取值范围分别为 0到 Heads - 1, 0到 Cylinders - 1,1到 Sectors (注意是从1开始)。
—– Quote End—–
上述段落阐述了什么是 CHS 寻址,按照上文的理解Heads是磁头数单位应该是最大的,但是其实不是酱紫的~
按照寻址高低位排序,Cylinder > Head > Sector。
而且 H 不是表示硬盘总共有几个磁头,是最大磁头号。C 不是有几个柱面,是最大柱面号。要注意!
要把 CHS 转换成绝对地址,可以使用下面的公式
BlockAddr = ( Cylinder * NumberOfHeads + Head ) * SectorsPerTrack + Sector - 1;
NumberOfHeads 是磁盘的磁头数,假如最大磁头号码为255,那么NumberOfHeads = 256
SectorsPerTrack 是磁盘每磁道的扇区数 值为 63。
而且注意最后有一个-1。因为Sector取值范围是1-63而不是0-63。
Int13h 8号功能会返回 MAX HEAD,也就是最大磁头号。不要和上面公式的 NumberOfHeads 混淆。
四、万恶的 BootSector 程序
照理来说 XP 的 BootSector 应该使用Int13h的8号功能首先获取磁盘最大磁头号。
而他却使用了 BPB(BIOS Parameter Block) 结构中定义 HeadsPerCylinder。。。
注意这个 HeadsPerCylinder 不是最大磁头号而是磁头数,同上面公式中的 NumberOfHeads。
一般情况这里的值是FF,那么最大磁头号应该是 254!!
;======================================================
; BPB( BIOS Parameter Block )
;======================================================
BytesPerSector DW ? ; 每个扇区的字节数 (512 1024 2048 4096)
SectorsPerCluster DB ? ; 每个簇的扇区数 ( 1 2 4 8 16 32 64 128 )
; 两者相乘不能超过32K(簇最大大小)
ReservedSectors DW ? ; 从卷的第一个扇区开始的保留扇区数目
; 该值不能为0,对于FAT12/FAT16,该值通常为1
; 对于FAT32,典型值为32
NumberOfFATs DB ? ; 卷上FAT数据结构的数目,该值通常应为2
RootEntries DW ? ; 对于FAT12/FAT16,该值表示32字节目录项的数目
; 对于FAT32,该值必须为0
NumberOfSectors16 DW ? ; 该卷上的扇区总数,该字段可以为0,如果该字段
; 为0,则NumberOfSectors32不能为0;对于FAT32
; 该字段必须为0
MediaDescriptor DB ? ; 介质类型
SectorsPerFAT16 DW ? ; 该字段标识一个FAT结构占有的扇区数(FAT12/FAT16)
; 对于FAT32卷,该字段必须为0
SectorsPerTrack DW ? ; 用于INT 0×13中断的每个磁道的扇区数
HeadsPerCylinder DW ? ; 用于INT 0×13中断的每个柱面的磁头数
HiddenSectors DD ? ; 包含该FAT卷的分区之前的隐藏扇区数
NumberOfSectors32 DD ? ; 该字段包含该卷上的所有扇区数目,对于FAT32,该字段
; 不为0;FAT12/FAT16可根据实际大小是否超过65536个扇
; 区数决定是否采用该字段
To Be Continued..
NB哈,最近分析了一下BXP4.5的NBP代码。头一个劲的晕
九月 12th, 2009 at 21:26可否提供哈NBP的代码呢。(如果是非商业的话)
九月 12th, 2009 at 21:29你好,最近我也在研究NBP。
有几个问题想请教下。
1、网上有人说:“NBP的作用就是改修BIOS数据,让BIOS认为有一个硬盘存在,并拦截INT13 把INT13转发到服务器务.”
我觉得应该不需要改修BIOS数据区,冒充有硬盘吧。
2、做MINIPORT虚拟磁盘驱动后,如果BIOS没检查磁盘,
MINPORT虚拟磁盘能正常驱动吗?
谢谢。期待你的回复。
十月 20th, 2009 at 17:03哈哈,基本框架就这样而已,主要就是兼容性很头痛的。实现好不容易的
十月 30th, 2009 at 16:31我照着你描述的实现了int 13h。能跑一下,就停着了。能给个联系方式交流下吗?我QQ:184054949.谢谢!
五月 18th, 2010 at 14:30用in/out实现com口调试输出,然后看调试信息是哪里出的问题
五月 18th, 2010 at 19:23NBP我前段时间实现了。只能进入DOS系统。如果要进入Windows需要做磁盘过滤驱动。不知道你这个实现了没?可否探讨一下。谢谢!
五月 31st, 2010 at 16:28