楼主: 冰琥珀

自己动手写操作系统系列之----------引导扇区编写

[复制链接]
发表于 2015-10-19 00:47:24 | 显示全部楼层 |阅读模式
本帖最后由 冰琥珀 于 2015-10-19 13:38 编辑

        上一个帖子:http://www.ihonker.org/thread-6625-1-1.html
        上个帖子我们讲述了如何搭建开发环境,同时也写了个简单的"Hello, OS",这次我们来学学引导扇区的编写,确切的说应该是MBR(主引导记录)的编写,它是磁盘的第一个扇区,当我们按下电源键,BIOS完成一些初始化之后,就将MBR读取到0x7c00处,并将控制权转交给MBR来执行,由MBR来完成内核的加载。由于MBR最多只有512个字节(1个扇区),所以加载内核的工作不可能在MBR中完成,那么MBR又是干啥的呢。
        其实MBR只是在磁盘中找到Loader并将其读入内存,然后把控制权转交给Loader,让它来完成接下来的工作。所以MBR的工作就只是在磁盘中找到Loader。
        因为我们现在还没有文件系统,所以一切都只是用的软盘,后期我们有自己的文件系统了,我们会把软盘改为硬盘。在我们接下来的实验中,软盘都格式化为FAT12,所以我们要对FAT12文件系统有所了解。先来看看FAT12文件系统格式
        选区_006.png
        从上图可以看到,引导扇区之后就是FAT1和FAT2(FAT2是FAT1的备份),每个FAT表占9个扇区,FAT后紧跟着的是根目录区,大小在引导扇区的BPB中有设定,根目录区之后是数据区。我们这次要找到Loader,就要从根目录入手,先在根目录中找到Loader的文件名,然后在根据根目录结构中的簇号到FAT表中去找到相应的项,再根据FAT来读取Loader的数据,并将其加载到内存中。说了这么多,我们先看引导扇区的结构,如下图
        选区_008.png
        从上图可以看出,在引导扇区开始的3个字节是一个跳转指令,然后是OEM字符串,它必须是一个8字节长的字符串标,标识了格式化此磁盘的操作系统的名称和版本号
        接下来是每扇区字节数,一般情况下,这个值是512
        接着是每簇扇区数,在这里我们填1
        接着是保留扇区数,这里默认也为1
        接着是FAT表个数,这里有两个,分别是FAT1和FAT2,所以这个值为2
        然后是最大根目录数,用来记录根目录区有多少条目录项
        再下来是逻辑扇区总数,用来记录整个磁盘的扇区数
        接着是磁盘类型标识符,使用0f0h表示3.5寸高密码软盘,用0f8h来表示硬盘
        然后是每FAT扇区数,记录每个FAT表中有多少个扇区
        接着是每磁道扇区数,记录每个磁道中有多少个扇区
         然后是双字节长的磁头数,磁头数指的是磁盘面数,每面都有一个磁头,软盘都是2面的,所以在FAT12格式下此字段固定为2
        接下来是的位于偏移28处类型为双字(4B)长的隐藏扇区数,指的在引导扇区之前的隐藏扇区,在FAT12格式上此字段默认为0,即不隐藏任何扇区
        偏移32处的是类型为双字(4B)长的逻辑扇区总数,如果此分区或磁盘的逻辑扇区总数大于65536则用这个字段来表示逻辑扇区总数,否则设置此字段为0
        偏移36处的是物理驱动器号,类型是字节长,它与BIOS物理驱动器相关,在磁盘中断Int13h相关的操作中使用,第一个软盘驱动器设置为0,第一个硬盘驱动器设置为80h,第二个硬盘驱动器设置为81h,以此类推
        位于偏移37处的字节没有使用,保留并设置为0
        位于偏移38处的是扩展引导标识,类型是字节,操作系统用它来识别引导信息,值可以是28h或29h
        接下来的是位于偏移39处的卷标号,类型是双字(4B)长,在格式化磁盘时所产生的一个随机序号,有助于区分磁盘,可以为0
        然后是位于偏移43处的卷标,长度必须是11字节长(不足以空格20h填充)
        最后是位于偏移54处的是长度为8字节的文件系统类型标识符,不足8字节则以空格20h来填充
        接下来就是引导代码了,最后的两个字节要填入0xaa55,表示引导扇区结束
        引导记录结构介绍了之后,我们来看FAT表,相比之下,FAT表的结构就简单的多了,一句话描述就是每个FAT项(FATEntry)长度位12Bit,即一个半字节,FAT表中每3个字节存放了2个FATEntry,这里也不再贴出它的结构图了,下面看根目录的结构,如下图
        选区_007.png
        可以看到,在根目录中,每个目录项占32个字节,其中,前11个字节是文件名(文件名占8字节,拓展名占3字节),接下来是文件属性(占1个字节),然后是保留位(占10个字节),然后是写入时间和日期(各占2个字节,总共占4个字节),接下来是文件在FAT表中的簇号(占2字节),然后是文件大小(占4字节),总共32个字节。在上面的这些项中,我们不需要关心那么多,只要关注文件名和文件在FAT表中的簇号即可。首先通过文件名的对比来找到相应的目录项,其次再从目录项中读取处文件在FAT表中的簇号,然后根据这个簇号在FAT表中找出相应的项,再根据其来读取文件数据,现在我们就先写代码实现在根目录区中找到Loader,代码如下
       
[AppleScript] 纯文本查看 复制代码
org 0x7c00
	
	jmp		LABEL_START		;跳转	
	nop
	BS_OEMName		db		'FreeDos '		;OEM string,8个字节
	BPB_BytePerSec	dw		512				;每扇区字节数
	BPB_SecPerClus	db		1				;每簇扇区数
	BPB_RsvdSecCnt	dw		1				;保留扇区数
	BPB_NumFATs		db		2				;FAT表数
	BPB_RootEntCnt	dw		224				;根目录文件数最大值
	BPB_TotSec16	dw		2880			;逻辑扇区总数
	BPB_Media		db		0xf0			;媒体描述符
	BPB_FATsz16		dw		9				;每FAT扇区数
	BPB_SecPerTrk	dw		18				;每磁道扇区数
	BPB_NumHeads	dw		2				;磁头数
	BPB_HideSec		dd		0				;隐藏扇区数
	BPB_TotSec32	dd		0				;如果BPB_TotSec16为0,这里记录扇区总数
	BS_DrvNum		db		0				;中断13的驱动器号
	BS_Reserved1	db		0				;未使用
	BS_BootSig		db		0x29			;扩展引导标记
	BS_VolID		dd		0				;卷序列号
	BS_VolLab		db		'Orange OS  '	;卷标,11个字节
	BS_FileSysType	db		'FAT12   '		;文件系统类型

LABEL_START:
	mov ax, cs
	mov ds, ax
	mov es, ax
	mov ss, ax
	mov sp, BaseOfStack

	; 清屏
	mov	ax, 0600h		; AH = 6,  AL = 0h
	mov	bx, 0700h		; 黑底白字(BL = 07h)
	mov	cx, 0			; 左上角: (0, 0)
	mov	dx, 0184fh		; 右下角: (80, 50)
	int	10h			; int 10h

	;显示Booting  
	mov dh, 0
	call DispStr

	;软驱复位
	xor ah, ah
	xor dl, dl
	int 0x13

	mov word [wRootCurSecNum], RootDirSecNum
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
	cmp word [wRootSecCnt], 0
	jz	LABEL_SEARCH_IN_ROOT_DIR_END	;这里跳转表示整个根目录都搜索完毕,没有找到Loader.bin
	
	dec word [wRootSecCnt]
	mov	ax, BaseOfLoader
	mov es, ax
	mov bx, OffsetOfLoader
	mov ax, word [wRootCurSecNum]
	mov cl, 1
	call ReadSector				;读取扇区数据
	mov di, OffsetOfLoader
	cld
	mov dl, 0x10				;每个扇区中目录个数为16

LABEL_CMP_NEXT_FILE_NAME:
	mov si, LoaderFileName
	mov cl, 0x0b				;文件名长度为11个字节

LABEL_CMP_FILE_NAME:
	test cl, cl
	jz LABEL_FOUND_LOADER
	dec cl
	lodsb
	test al, al
	jz LABEL_CMP_NAME_END		;文件名比较完毕
	cmp al, [es:di]
	jz LABEL_GO_ON		;从根目录中读取到的文件名字符和目标文件名字符相等,则继续比较下一个字符

	jmp LABEL_CMP_NAME_END

LABEL_GO_ON:
	inc di
	jmp LABEL_CMP_FILE_NAME

LABEL_CMP_NAME_END:
	dec dl
	jz LABEL_GO_TO_NEXT_SECTOR	;16组目录都比较完毕,则读取下一个扇区
	and di, 0xffe0				;这里把di指针重置到当前目录的起始处
	add di, 0x20				;指针指向下一组目录(每组目录长度为32个字节)
	jmp	LABEL_CMP_NEXT_FILE_NAME	;比较下一组文件名

LABEL_GO_TO_NEXT_SECTOR:
	inc word [wRootCurSecNum]
	jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN

LABEL_SEARCH_IN_ROOT_DIR_END:	;没找到LOADER.BIN
	mov dh, 2
	call DispStr

	jmp $

LABEL_FOUND_LOADER:				;找到LOADER.BIN文件
	jmp $

ReadSector:
	push bp
	mov bp, sp
	sub sp, 2
	mov byte [bp - 2], cl
	push bx
	mov bl, [BPB_SecPerTrk]			
	div bl						;计算出当前扇区所处的磁道
	inc ah						;除得的商放在al,余数放在ah(al中放的是磁道号,ah中放的是扇区号)
	mov cl, ah
	mov dh, al
	shr al, 1					;磁道号除以2得到磁头号
	mov ch, al
	and ch, 1
	pop bx
	mov dl, [BS_DrvNum]
.GoOnReading:
	mov ah, 2					;读扇区
	mov al, byte [bp - 2]		;要读的扇区数
	int 0x13
	jc .GoOnReading				;读扇区失败则继续读
	add sp, 2
	pop bp, 
	ret

DispStr:
	mov ax, MessageLength
	mul dh
	add ax, BootMessage
	mov bp, ax
	mov ax, ds
	mov es, ax
	mov cx, MessageLength
	mov ax, 0x1301
	mov bx, 0x0007
	mov dl, 0
	int 0x10
	ret

	BaseOfStack		equ		0x7c00
	BaseOfLoader	equ		0x9000
	OffsetOfLoader	equ		0x100
	RootDirSecNum	equ		19			;根目录起始扇区号=FAT扇区数(2个) + 引导扇区数
	wRootSecCnt		dw		14			;根目录所占的扇区数
	wRootCurSecNum	dw		0
	wFileNameCnt	db		0
	LoaderFileName	db		'LOADER  BIN', 0	;LOADER.BIN (文件名)
	MessageLength	equ		9
	BootMessage:	db		"Booting  "
	Message1:		db		"Ready.   "
	Message2:		db		"No LOADER"
	times	510-($-$$) db 0
	dw		0xaa55


        代码看着有点长,我们一点一点来分析。这段代码因为是写在引导扇区的,所以要按照引导扇区的格式来写。在上面我们已经看到了引导扇区最开始的3个字节是一个跳转指令,所以在程序的开头我们写入
        jmp                LABEL_START
        这里只占两个字节,剩余的一个字节用nop填充,接下来就是一堆的数据,具体的上面有介绍,我们直接看跳转到的代码
        mov ax, cs
        mov ds, ax
        mov es, ax
        mov ss, ax
        mov sp, BaseOfStack

        ; 清屏
        mov        ax, 0600h                ; AH = 6,  AL = 0h
        mov        bx, 0700h                ; 黑底白字(BL = 07h)
        mov        cx, 0                        ; 左上角: (0, 0)
        mov        dx, 0184fh                ; 右下角: (80, 50)
        int        10h                        ; int 10h

        ;显示Booting  
        mov dh, 0
        call DispStr

        ;软驱复位
        xor ah, ah
        xor dl, dl
        int 0x13
        这里就是一些初始化的工作,设置段寄存器,设置栈,清屏,显示字符串,复位软盘等,真正的算法在下面。我们先来整理思路,要在根目录中找到Loader,那个我们就先要确定根目录的起始偏移,前面我们有介绍过,根目录位于FAT表之后,每个FAT表占9个扇区,两个FAT表就占18个扇区,再加上一个引导扇区,所以根目录的起始扇区是19,既然要在根目录中查找,那就要有个范围,我们已经得到了起始值为19,根目录总的扇区数=根目录文件数最大值 * 32 / 512,这里得出的结果是14,所以我们就从19号扇区开始找,最大找满14个扇区,代码如下
        mov word [wRootCurSecNum], RootDirSecNum                ;RootDirSecNum存放的是根目录的起始扇区号       
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
        cmp word [wRootSecCnt], 0                                                ;wRootSecCnt里存放的是根目录总的扇区数,为0表示根目录所有的扇区都已经查找了
        jz        LABEL_SEARCH_IN_ROOT_DIR_END                ;这里跳转表示整个根目录都搜索完毕,没有找到Loader.bin
       
        上面的代码只是检测根目录是否已经查找完毕,如果查找完毕就跳转到后面显示没找到,没找到后的代码如下
        LABEL_SEARCH_IN_ROOT_DIR_END:        ;没找到LOADER.BIN
        mov dh, 2        ;2表示显示第三组字符串(BootMessage开始的3组字符串),即No LOADER
        call DispStr

        jmp $                :程序停在这
        如果没找到,我们就让程序先停在这,下面我们看根目录查找没有结束的情况,如果没有结束,我们就要读取数据,一次读取一个扇区,代码如下
        dec word [wRootSecCnt]
        mov        ax, BaseOfLoader
        mov es, ax
        mov bx, OffsetOfLoader
        mov ax, word [wRootCurSecNum]
        mov cl, 1
        call ReadSector                                ;读取扇区数据
        上面先是进行一些设置,比如要读取哪个扇区,读取的扇区数,读取到哪个位置等。数据读取出来了之后,我们就要查找Loader的文件名了,这里我们要找的文件名是"LOADER  BIN",查找的方法就是取出一个目录项,然后一个字节一个字节的比较文件名,在上面我们介绍过目录项的结构,开始的11个字节就是文件名,所以我们只比较11个字节,如果全部相等,表示找到,否则取出下一个目录项进行比较,代码如下
        mov di, OffsetOfLoader               
        cld
        mov dl, 0x10                                ;每个扇区中目录个数为16

LABEL_CMP_NEXT_FILE_NAME:
        mov si, LoaderFileName
        mov cl, 0x0b                                ;文件名长度为11个字节

LABEL_CMP_FILE_NAME:
        test cl, cl
        jz LABEL_FOUND_LOADER
        dec cl
        lodsb
        test al, al
        jz LABEL_CMP_NAME_END                ;文件名比较完毕
        cmp al, [es:di]
        jz LABEL_GO_ON                ;从根目录中读取到的文件名字符和目标文件名字符相等,则继续比较下一个字符

        jmp LABEL_CMP_NAME_END

LABEL_GO_ON:
        inc di
        jmp LABEL_CMP_FILE_NAME
        [es:di]中存放的是从根目录中读取到的数据,si中存放的是目标文件名,这里用lodsb从si中取出一个字节代al中,然后用个字节和[es:di]中的进行比较,如果相等就比较下一个(jz LABEL_GO_ON),否则就表示这个目录项比较结束,程序跳转到LABEL_CMP_NAME_END,如果全部比较完成后,发现都相等则跳转到LABEL_FOUND_LOADER,找到后我们先让程序停在那,我们现在看如果当前目录项不是我们要找的,又怎么处理,代码如下
LABEL_CMP_NAME_END:
        dec dl                                        ;目录项数减1
        jz LABEL_GO_TO_NEXT_SECTOR        ;16组目录都比较完毕,则读取下一个扇区
        and di, 0xffe0                                ;这里把di指针重置到当前目录的起始处
        add di, 0x20                                ;指针指向下一组目录(每组目录长度为32个字节)
        jmp        LABEL_CMP_NEXT_FILE_NAME        ;比较下一组文件名

LABEL_GO_TO_NEXT_SECTOR:
        inc word [wRootCurSecNum]
        jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN
        如果当前目录项不是我们要找的,那么我们就跳转到下一个,这时dl中记录的当前扇区目录项的总数要减1,如果dl为0,表示当前扇区的所有目录项都查找完毕,那么我们就要去读取下一个扇区(jz LABEL_GO_TO_NEXT_SECTOR),否则就跳到下一个目录项进行比较,依次循环。
        到了这里,我们这个帖子的目标也达到了,就是在根目录中查找Loader的文件名,至于找到之后该咋办,或者没找到又该怎么处理,我们下个帖子再作处理。
回复

使用道具 举报

发表于 2015-10-19 06:32:07 | 显示全部楼层
支持中国红客联盟(ihonker.org)
回复 支持 反对

使用道具 举报

发表于 2015-10-20 07:59:33 | 显示全部楼层
感谢楼主的分享~
回复 支持 反对

使用道具 举报

发表于 2015-10-21 10:06:43 | 显示全部楼层
流弊流弊O(∩_∩)O~
回复 支持 反对

使用道具 举报

发表于 2015-10-21 21:36:39 | 显示全部楼层
还是不错的哦,顶了
回复 支持 反对

使用道具 举报

发表于 2015-10-22 02:14:35 | 显示全部楼层
支持中国红客联盟(ihonker.org)
回复 支持 反对

使用道具 举报

发表于 2015-10-22 05:55:15 | 显示全部楼层
还是不错的哦,顶了
回复 支持 反对

使用道具 举报

发表于 2015-10-22 15:14:53 | 显示全部楼层
回复 支持 反对

使用道具 举报

发表于 2015-10-23 11:30:03 | 显示全部楼层
回复 支持 反对

使用道具 举报

发表于 2015-10-24 07:29:05 | 显示全部楼层
学习学习技术,加油!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

旗下站点

邮箱系统

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2025-3-7 06:59 , Processed in 0.034965 second(s), 13 queries , Gzip On, MemCache On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部