手搓MBR实现ubuntu,win7双启动

实验报告

编写MBR,来实现对win7+ubuntu双系统的启动。大致思路如下:

  1. 显示系统选择界面
  2. 根据键盘输入选择启动的系统
  3. 拉起该操作系统对应的bootloader

使用的工具

虚拟机:QEMU

汇编语言编译器:NASM

二进制文件修改工具:winhex

IDE:vscode

基本流程:在vscode中写好代码,使用NASM将汇编代码转化为二进制文件。使用winhex查看汇编代码的二进制文件,并且修改QEMU的虚拟磁盘。

设置虚拟地址并且初始化寄存器

初始化虚拟地址

SECTION 这是NASM提供的伪代码之一。它的作用就是用来划分代码区域,帮助理解代码。他的存在不会对机器码有什么影响(前提是没有设置vstart,代码中没有使用$ $$ 符号)。而且没有强制的使用限制,你可以随便在代码中插入SECTION

伪代码$,$$。$指本条指令对应的内存地址,例如jmp $ 就是原地跳转,无限循环。$$指本SECTION的起始内存地址,例如jmp $$,就是跳转到该条指令所属的section的起始地址处。

vstart,设置虚拟内存地址。例如vstart=0x7c00,用人话来说:它不是告诉加载器“把这段代码加载到0x7c00处,而是假设加载器会把代码加载到0x7c00处”。为什么要假设呢?还记得上面的$,$$吗?如果没有vstart,那么$$会被NASM翻译成0x0000,而如果vstart=0x7c00,那么$$会被翻译为0x7c00。当然它不仅仅会影响$$,$,也会影响其他东西。

初始化寄存器,代码如下

移动MBR代码

MBR代码会被BIOS加载到内存0x7c00处。那为什么要移动MBR代码到内存其他地方去呢?因为windows的bootloader程序约定为加载至0x7c00处,会覆盖掉我们的MBR代码。

跳转的代码如下:(代码中各个指令的作用可以自行查询)

这里说一下最后一行的作用:当代码移动完成后,我们需要跳转到新代码的下一条指令执行。因此我们在最后一行插入一个标签“new_start”。这个标签没有实际作用,只是指代了一个内存地址(比方说vstart=0x7c00,且这行代码的地址相对于程序开头为0x0020,那么标签指代的地址就是0x7c20。其实这和“$”的作用差不多)。但是我们要注意到

  1. 编译器编码时,jmp后面跟的地址并不是绝对地址,而是jmp指令所在地址与跳转目标地址的偏移量。例如想要从0x7c00跳到0x7c65,在0x7c00处的jmp指令虽然写的是jmp 0x7c65,不过编译器会将它改为jmp 65。(除非写绝对地址,例如jmp 0x0000:0x7c65,这样可以避免这个问题)
  2. 程序一经过编译器编译,形成的二进制码就不变了。

由于我们的程序已经在内存中移动了,所以此处如果仅写jmp new_start,而且new_start没有经过上面的修饰,那么程序仍然会在旧的内存段中运行!

正确方法是,我们通过EQU语句,将new_start标签指代的地址改为“与程序开头地址的偏移量“,就是$-$$。然后再加上我们新程序的起始地址。这样就能避免这个问题。即使jmp后面的地址是与目标地址的偏移量。

显示系统选择界面

我们将要调用由BIOS提供的INT 10H中断服务,来与显示屏互动。INT 10H中断服务的使用手册可以在维基百科上查询。

BIOS提供的中断服务会在BIOS运行时写入实模式下的中断向量表中,里面封装一些常用操作,可供调用。(但是这些程序运行缓慢,而且在操作系统接管后很有可能被弃用,所以一般都是在MBR中调用这些中断服务)

清屏

首先,我们将屏幕上的字符清空,方便显示选择菜单(BIOS运行时会在屏幕上显示字符)。

使用INT 10H 的0x06号子功能完成(该功能可以控制屏幕的滚动)

补充知识:字符的显示。可以理解为内存中专门有一段有限空间存储在屏幕上显示的字符。只需要将需要显示的字符写入该段内存中,屏幕上就会显示字符。同时根据字符在内存中的地址,系统会在屏幕上对应的位置显示。所以滚动屏幕其实就是在改变字符在内存中的地址。而如果一个字符被“滚动”出地址范围了,那么这个字符就没了。

显示选择界面

首先我们设置光标位置

其实不设置也行。从上面介绍的字符显示原理,我们不难看出字符显示的位置只和它在内存中的位置有关,和光标没啥关系。不过为了用户体验,光标还是得设置一下的。

代码如下:

紧接着我们显示字符,方式是显示一行字符,然后将光标位置换行,随后再显示一行。

使用INT 10H中的0x13号子功能显示

显示ubuntu字符串的道理同上

读取键盘输入

我们希望实现:根据用户按键,光标上下移动。当用户按下回车,根据光标所在的位置拉起对应的bootloader。

我们使用INT 16H提供的中断服务来读取键盘输入。此外,我们还要实现一个循环,让用户可以多次移动光标。代码如下:

拉起对应的bootloader

首先是我们的ubuntu的bootloader,其实就是大名鼎鼎的GRUB了。想要拉起GRUB,我们就要

  1. 找到GRUB代码并加载到内存
  2. 使用跳转指令执行GRUB程序。

那GRUB代码在哪?不同的人电脑不同,总的来说你得自己去看原始的MBR,看看它是去哪边加载的。大部分情况下,GRUB是紧跟着MBR的,就在第二扇区。

GRUB代码应该加载到内存的哪里,阅读MBR源码可知,一般加载到0x8000处。

如何读取磁盘中的GRUB代码?使用INT 13H提供的中断服务。另外要注意,请使用LBA模式读取(就是使用42H的子功能号对应的服务,不要用2H对应的服务)

LBA模式与CHS模式差别:早期的磁盘不管是外磁道还是内磁道,每个磁道上对应的扇区数量是一定的。这样就会出现一个问题,外磁道每一个扇区空间大(物理意义上的大),信息密度低。这导致浪费了许多有效空间。所以后来都是等信息密度设计的磁盘。回到正题:CHS模式下你需要提供磁头号,扇区号,磁道号等信息才能找到磁盘对应的位置。而LBA模式将这个过程简化为:只要提供扇区号(一个维度上寻址)。同时LBA支持更大范围的寻址空间。

代码如下:

注意此处的jmp,我们使用绝对地址,这样可以避免jmp会编译为相对地址,而导致跳转地址的错误。

对于windows我们也是同样的操作

复制硬盘分区表和windows签名

处了上面我们完成的MBR,我们还要通过winhex将硬盘分区表和windows签名复制进生成的二进制文件中。

windows签名是指440号字节后面的四个字节

成果展示

选择界面,可以通过键盘控制光标上下移动(w,s 键)。按下回车可以拉起对应的操作系统

例如这是拉起了GRUB界面

  • 31
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值