linux汇编学习(2)-----摆脱MBR大小的限制,加载stage2代码

来源:互联网 发布:java键盘 上下左右监听 编辑:程序博客网 时间:2024/05/24 05:03

完整代码已经归档到 : https://github.com/linzhanglong/mini_bootloader

         我们知道,系统启动会加载磁盘的MBR扇区到内存0x7c00那里去执行。但是毕竟MBR扇区只有512个字节,如果要实现设置GDT,读取内核,引导内核等功能,这512字节显得力不从心。所以我们这里第一步就是通过MBR去磁盘读取一块更大的空间到内存然后去执行这部分代码(称为 stage2)。这里我们尤其要注意一点就是,系统刚从BIOS启动执行磁盘MBR数据,CPU的工作模式还是实模式,寄存器都是16位的。通过段寄存器,最大可以支持的内存是[0xffff,0xffff] = 0xffff * 16 + 0xffff = 1M。总共只能索引1M的内存,而每一个段内只能索引64K范围。

      我们这里先规划我们的内存和磁盘布局:



我们现在实现的代码就是通过MBR代码,把stage2的代码加载到内存0x9000执行。


首先我们要先实现一个读取磁盘的函数接口,对于磁盘的结构理解,网上很多资源:http://www.cnblogs.com/joydinghappy/articles/2511948.html


我们这里要借助BIOS的中断服务来实现磁盘的读操作:

我们看一下对应的BIOS如何读取磁盘:
使用的中断号:
INT 13h AH=02h: Read Sectors From Drive[edit]

传入的参数:
AH 02h
AL Sectors To Read Count
CH Cylinder
CL Sector
DH Head
DL Drive
ES:BX Buffer Address Pointer

结果的返回:
CF Set On Error, Clear If No Error
AH Return Code
AL Actual Sectors Read Count

数据的存放:
磁盘数据存放的内存地址: ES:BX

注意事项:
1. CX寄存器包含了圆柱面号和扇区号,其中同心圆号占10bit,扇区号占6bit【所以一个圆盘最大扇区数目就是64 * 1024】,所以CX[0-5]表示扇区号,CX[6-15]表示同心圆号
CX =       ---CH--- ---CL---
cylinder : 76543210 98
sector   :            543210
2. 根据第一点我们可以知道,这里我们最大索引的磁盘空间是: 256 Head * 1024  Cylinder * 64 Sector * 512 byte = 8G

3. 因为数据拷贝存放的地址是放在ES:BX起始地址,我们知道一个段可以索引的大小是1M(BX:0xFFFF),所以拷贝的数据大小+拷贝的其实地址BX最好小于1M。


现在开始实现我们的读磁盘函数(文件名字 read_disk.asm):

;;@ brief 这里实现把磁盘的数据拷贝到内存 ;@ param dh:cx 从哪个扇区开始拷贝。al表示磁头号,bx决定哪个同心圆哪个扇区;@ param al 拷贝多少个扇区数据;@ return 如果成功,函数直接返回,数据存放到ES:BX的地址。;         如果失败,打印一条错误日志,然后卡主;@ notes 调用者需要先设置好数据的拷贝地址 : ES:BX的地址;read_diskdata:    pusha    mov [READ_SECTER_NR], al    ;开始拷贝的扇区地址分为 (Head)8bit:(Cylinder)10bit:(Sector)6bit    ;其中Head -> DH, Cylinder -> CH, Sector -> CL    ;这里目前只支持拷贝drive0,也就是磁盘hda --> 0x80    mov dl, 0x80    ;开始拷贝磁盘数据    mov ah, 0x02    int 0x13    ;开始判断磁盘拷贝结果,如果CF表示磁盘拷贝失败    jc _READ_ERR    ;判断拷贝的磁盘扇区数目是不是和我们要拷贝的一样,不是也报错    mov dl, al ;保存实际的拷贝的扇区数目到dl    mov al, [READ_SECTER_NR] ;我们想要拷贝的扇区数目al    cmp dl, al    jne _READ_ERR    ;成功拷贝,打印一条日志,然后返回    mov bx, DISK_READ_OK    call print_string    popa    ret    _READ_ERR:    mov bx, ax    call print_hex    mov bx, DISK_READ_ERR    call print_string    jmp $ ;卡主    ret ;Never go there    ;定义打印的字符串DISK_READ_OK db 'Disk Read Ok', 0DISK_READ_ERR db 'Disk Read Error', 0;拷贝的扇区数目我们需要保存起来READ_SECTER_NR equ 0x00

实现完磁盘的读取函数接口,现在我们就开始写MBR代码,MBR代码512字节大小,实现的功能,就是把磁盘地址512到512+32K(也就是第二个扇区开始,读取64个扇区)的数据加载到内存0x9000地址,然后跳过去执行。直接上代码-(文件名字 mbr.asm):

;; Date 2017/11/28; Authon: linzhanglong; notes: 这里是MBR分区,用途:用于加载stage2代码到指定内存位置,然后执行[org 0x7c00];定义几个变量;根据https://www.kernel.org/doc/Documentation/x86/boot.txt,;定义stage2的代码长度,要求512字节对齐!;mbr工作在实模式,一个段内索引最大64k,这里定义stage2 32k大小STAGE2_LEN equ 0x8000 ;32kSTAGE2_MAGIC equ 0x4433 ;用于校验我们是不是拷贝正常; step 1 先初始化号堆栈,免得出异常[xxxxx, 0x8000]mov bp, 0x8000mov sp, bp; step 2 先初始化stage2内存拷贝地址[0x9000, 0x9000 + 64k]mov ax, 0x900mov es, axmov bx, 0; step 3 从磁盘位置[0x200, 0x200 + 64k] => [2 sector, 2 + 128 sector];         拷贝stage2代码到内存mov dh, 0 ;(Head)8bitmov cx, 2 ;(Cylinder)10bit:(Sector)6bitmov ax, STAGE2_LENshr ax, 9 ;al <----- setor num; step 4开始拷贝数据call read_diskdata;check load ok,直接读取stage2在内存最后两个字节,判断是不是魔数 STAGE2_MAGIC;是表示我们加载没有错误,不是表示我们加载有问题mov bx, STAGE2_LENsub bx, 2mov ax, [es:bx]cmp ax, STAGE2_MAGICjne _CHECK_ERRmov bx , MSG_LOAD_STAGE2_OKcall print_stringjmp 0x9000   ;跳到stage2代码执行_CHECK_ERR:mov bx, MSG_LOAD_STAGE2_ERRcall print_stringjmp $ %include "print.asm"%include "read_disk.asm"MSG_LOAD_STAGE2_ERR db 'Load stage2 Err', 0MSG_LOAD_STAGE2_OK db  'Load stage2 Ok', 0times 510-($-$$) db 0dw 0xaa55


最后就是stage2的代码了,目前这格代码只是打印一条日志,后面有时间在实现stage2的真正代码功能(文件名字 boot_kernel.asm):

;; Date 2017/11/28; Authon: linzhanglong; notes: 这里是加载内核,目前先实现一个个打印功能。[org 0x9000];目前只是简单打印一条日志,后面在实现功能mov bx, MSG_BOOT_KERNEL_TESTcall print_stringjmp $%include "print.asm"%include "read_disk.asm"MSG_BOOT_KERNEL_TEST db 'Boot kernel test', 0times 32766-($-$$) db 1dw 0x4433

最后,就是编译文件制作一个可以启动的磁盘:

# !/bin/bashrm -rf ./raw_disknasm mbr.asm -o mbrif [[ $? != 0 ]];then    exit 1fi#写入MBRdd if=./mbr of=./raw_disk bs=512 count=1if [[ $? != 0 ]];then    exit 1finasm boot_kernel.asm -o boot_kernel#写入stage2dd if=./boot_kernel of=./raw_disk bs=512 seek=1if [[ $? != 0 ]];then    exit 1fi#启动qemuqemu-kvm raw_disk -vnc :6exit 0


最终的结果图:




原创粉丝点击