自己动手实现操作系统引导程序(OS bootloader)——借助QEMU/GDB/losetup/dd等工具

        引导程序可以认为是PC加电启动后运行的第一段代码,它是一段长度为512字节的16位运行于实模式的代码。事实上,机器启动后会首先运行0xFFFF0处(也有的资料说是0xFFFFFFF0,BIOS这块我也不熟:-( )ROM中的BIOS代码,之后会跳转到0x07C00处执行引导程序。

        1,首先给出一段完整的示例代码,此代码只为说明引导程序的执行流程,不具有加载实际操作系统的功能,只是在屏幕上打印一段信息。

#define BOOTSEG 0x07C0
          .code16
          .section ".bstext", "ax"
          .global bootsect_start
  bootsect_start:
  
          # Normalize the start address
          ljmp    $BOOTSEG, $start2
  
  start2:
          movw    %cs, %ax
          movw    %ax, %ds
          movw    %ax, %es
          movw    %ax, %ss
          xorw    %sp, %sp
          sti
          cld
  
          movw    $bugger_off_msg, %si
  
  msg_loop:
          lodsb
          andb    %al, %al
          jz      bs_die
          movb    $0xe, %ah
          movw    $7, %bx
          int     $0x10
          jmp     msg_loop
  
  bs_die:
          # Allow the user to press a key, then reboot
          xorw    %ax, %ax
          int     $0x16
          int     $0x19
  
          # int 0x19 should never return.  In case it does anyway,
          # invoke the BIOS reset code...
          ljmp    $0xf000,$0xfff0
  
  bugger_off_msg:
          .ascii  "Hello Boot!\r\n"
          .ascii  "by harvey\r\n"
          .ascii  "\n"
          .byte   0

          .org 510
          .word 0xAA55
                这段代码有几个地方需要注意:

                1).code16伪指令指示汇编器将此段代码汇编成16位代码。

                2)ljmp    $BOOTSEG, $start2指令中,BOOTSEG定义为0x07C0,并假设标号start2在所在代码段中的偏移为S。我们知道实模式地址模式为:段基址*16+偏移,那么这条ljmp指令执行后,控制会跳转到0x7C00+S处。又因为整个引导代码在之前会被加载到0x7C00处,所以此时控制“正好”跳转到标号start2处代码。

                3)代码末端的伪指令.org 510,将位置计数器(location counter)设置为510,那么紧跟其后的0xAA55就被设置在第511,512字节。0xAA55是BIOS识别并加载引导程序的标志。

        2,编译链接此程序

               1)用as命令汇编生成目标文件。

                       as -gstabs -o boot.o boot.S

               2)用ld命令链接生成可执行文件。

                       ld -o boot boot.o -Tboot.ld

                       boot.ld为链接脚本,内容如下:

OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
OUTPUT_ARCH(i386)
ENTRY(bootsect_start)

SECTIONS
{
    . = 0;
    .boot : {*(.bstext)}
    . = ASSERT(. <= 512, "Boot too big!");
}   

               此脚本指示ld将目标文件boot.o中的.text段链接拷贝到可执行文件boot中的.boot段,并且.boot段的起始VMA地址为0。.boot代码段就是我们需要的引导程序代码。更多链接脚本语法参考http://sourceware.org/binutils/docs/ld/index.html。

       3,制作引导软盘镜像

              1)用dd命令新建软盘镜像flp.img。

                      dd if=/dev/zero of=flp.img bs=512 count=2880

              2)用losetup命令将flp.img与loop设备关联,这样我们可以通过/dev/loop3设备,像操作真实软盘样操作flp.img文件。

                      losetup /dev/loop0 flp.img

              3)将可执行文件boot中的引导代码写入flp.img的第一个扇区。首先我们要确定.boot段在可执行文件boot中的位置,注意此位置不是指.boot段的VMA地址,而是指其存储在磁盘文件boot中的物理位置,我们用objdump命令查看:

                      objdump -h boot

                      输出如下:

                      从File off栏可知.boot段位于距boot文件头0x00001000处。然后用dd命令将.boot段写入flp.img的第一个扇区。

                      dd if=boot ibs=512 skip=8 of=/dev/loop0 obs=512 seek=0 count=1

                      其中skip * ibs = 0x00001000为待写数据,即.boot段,在输入源文件,即boot文件中的偏移距离,seek * obs = 0为待写数据将要被写入输出目标文件,即flp.img文件的起始位置,即从flp.img文件头字节开始写入数据,count*obs=512为待写数据的长度。

                      到这里,引导软盘镜像准备好了。关于dd,losetup,objdump命令更多信息可借助man命令,也可参考我前一篇文章。

        4,借助QEMU从引导软盘镜像启动系统。

                运行如下命令启动QEMU。

                qemu -boot order=a -fda /dev/loop0

                此时,我们应该可以在QEMU模拟器的窗口中看到Hello Boot!字样。


                QEMU也提供单步调试功能。配合GDB,可以方便的调试引导程序。先用如下命令启动QEMU。

                qemu -s -S -boot order=a -fda /dev/loop0

                其中-s -S选项与gdb调试有关,运行此命令后QEMU模拟器会停止并等待gdb发送单步执行命令。更多QEMU信息可参考http://qemu.weilnetz.de/qemu-doc.html。

                在另一个终端调用gdb命令进入gdb命令行,依次输入以下命令。

你可能感兴趣的:(Unix/Linux,Kernel,Development)