楼主: 冰琥珀

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

[复制链接]
  • TA的每日心情
    奋斗
    2017-1-16 20:03
  • 签到天数: 116 天

    [LV.6]常住居民II

    发表于 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 | 显示全部楼层
    感谢楼主的分享~
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    擦汗
    2019-10-17 06:41
  • 签到天数: 182 天

    [LV.7]常住居民III

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

    使用道具 举报

  • TA的每日心情
    开心
    2022-10-21 10:32
  • 签到天数: 11 天

    [LV.3]偶尔看看II

    发表于 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备2021031567号 )

    GMT+8, 2024-11-22 02:10 , Processed in 0.023418 second(s), 14 queries , Gzip On, MemCache On.

    Powered by ihonker.com

    Copyright © 2015-现在.

  • 返回顶部