ApriorIT

引导加载程序是一个很小但非常重要的软件,它帮助计算机引导操作系统(OS)。即使对于一个熟练的低级开发人员来说,创建一个这样的程序也是一项具有挑战性的任务。这就是为什么Apriorit驱动开发专家决定分享他们在这个主题上的经验。

在本文中,我们将概述系统加载的理论,并向您展示如何编写引导加载程序。你可以在GitHub上查看我们在本教程中描述的解决方案存储库

这篇文章将对开发C语言和C++语言的高级开发人员有用,他们需要学习如何开发引导加载程序。

内容:

阶段1:为引导加载程序开发做准备

阶段2:开发引导加载程序

阶段3:组装引导加载程序

阶段4:在虚拟机和真实硬件上测试引导加载程序

用于引导加载程序调试的工具

结论

阶段1:为引导加载程序开发做准备

让我们从引导加载程序开发基础知识的快速概述开始。

引导加载程序是一种软件,位于硬盘驱动器的第一个扇区中,系统开始引导。该扇区也称为主引导记录(MBR)。计算机固件读取第一个扇区中包含的数据,并在机器通电时将其处理到系统内存。当固件找到引导加载程序时,它将加载该数据,引导加载程序启动操作系统。

引导扇区通常是磁盘的第一个扇区。这对于现代引导系统来说不是必须的,但是大多数开发人员将引导加载程序放在第一个扇区中。所以现在,我们还是坚持第一个扇区。但是请记住,只有基于bios的系统才需要引导扇区。

选择固件接口:UEFI vs BIOS

您可以开发一个引导加载程序BIOS (Basic Input/Output System)统一可扩展固件接口UEFI.BIOS是众所周知的计算机硬件,所有设备都支持它。UEFI是现代硬件上提供的现代BIOS替代品,为开发人员提供了更多的低级开发选项。

使用UEFI而不是BIOS的主要优势:

使用UEFI而不是BIOS的主要优势
  1. 在32/64模式下工作的能力允许您访问更多处理器功能,而BIOS仅在16位模式下工作。
  2. 没有启动磁盘大小限制,允许您使用任何磁盘来启动系统。
  3. 直接用C语言编写代码,使用完整的开发环境,例如EDK2无需研究汇编程序或混合高级和低级代码。
  4. 安全启动验证设备仅使用具有电子签名的受信任软件启动。

但尽管有这些优点,在本教程中,我们将从头开始开发BIOS引导加载程序。第一个原因是BIOS受到更广泛的硬件支持。此外,几乎所有旧设备都只支持BIOS。其次,所有UEFI兼容系统在传统模式下都可以像BIOS系统一样工作,因此您也可以在UEFI系统上运行我们的引导加载程序。

了解系统启动过程

接下来,我们需要对系统引导过程有一个大致的了解。当你引导系统时,组件之间是如何交互的:

系统启动过程

图1。系统启动过程

  1. BIOS读取硬盘的第一个扇区。
  2. BIOS将控制传递给位于地址0000:7c00的MBR,从而触发操作系统启动进程。

之后,引导过程结束,操作系统启动。

相关服务

C/C环境下的外包软件开发++

选择用于开发引导加载程序的语言

大多数常用语言使用虚拟机将中间代码转换为处理器能理解的语言。只有在转换之后,才能执行中间代码。唯一的例外是当您使用一些本地语言集自行实现所有必需的运行时代码时的假设情况。

在开发引导加载程序时,您需要一种不依赖于运行时的语言。你不能使用像Java和c#这样依赖于运行时的语言,因为它们会在编译后生成中间代码。

为了避免这个挑战,在本引导加载程序开发教程中,我们将使用c++作为底层编程的主要语言。

另外,在编写引导加载程序时,了解如何使用汇编程序的基础知识是一个很大的优势。在计算机运行的初始阶段,BIOS通过调用函数来控制机器硬件中断调用,用汇编语言编写。在构建引导加载程序时,并不一定要知道汇编语言,因为我们可以混合使用低级语言命令和高级结构。但是了解它对于调试编写的引导加载程序是必要的。

选择的工具

为了能够混合高级和低级代码,并编写自己的引导加载程序,你至少需要三个工具:

  1. 汇编程序的编译器
  2. c++编译器
  3. 链接器

处理器在16位实数模式下工作,有一定的限制,在32位安全模式下工作,功能齐全。当你打开计算机时,它的处理器在实数模式下工作。这就是为什么构建程序和创建可执行文件需要一个能在16位模式下工作的汇编编译器和链接器。

C++编译器只需要在16位实模式中创建*.Obf文件。

注意:最新的编译器不适合我们的任务,因为它们被设计为仅在32位安全模式下运行。

在测试了几个16位编译器之后,我们为本教程选择了Microsoft编译器和链接器。我们使用它们来构建所有低级语言代码示例和其他引用代码。Microsoft Visual Studio 1.52包已经包含了我们需要的东西:用于汇编和c++的编译器和链接器。

下面是你可以用来开发自定义引导加载程序的链接器和编译器,而不是我们在教程中使用的工具:

汇编编译器

C / c++编译器

链接器

6.15毫升

微软的16位编译器

CL

16位编译器

联系5.16

16位链接器创建*。com文件

DMC

免费编译的数字火星

DMC

免费编译的数字火星

链接

设计用于与DMC编译器一起工作的自由连接器

塔斯姆

Borland的16位编译器

BCC 3.5

Borland的16位编译器

塔斯姆

16位链接创建*.com文件由Borland

注意:如果你在使用我们GitHub提供的cll .exe文件时遇到任何问题存储库,您可以尝试使用DMC编译器。

相关服务

内核和驱动程序开发

阶段2:开发引导加载程序

我们将构建一个基本的引导加载程序,它执行三个简单的任务:

  1. 将引导加载程序指令从0000:7c00地址加载到系统内存。
  2. 调用BootMain用高级语言编写的函数。
  3. 在屏幕上显示“Hello world”消息。

引导加载程序的架构是这样的:

程序架构

图2。引导装载程序架构

第一个元素是起止点.这个实体是用汇编语言编写的,因为高级语言缺乏所需的指令。起止点指示编译器使用特定的内存模型,并列出从磁盘读取数据后要加载到RAM的地址。

BootMain一个实体是否类似主要用高级语言编写的元素,它在之后立即接管控制权起止点

最后,CDisplay进来。它们的作用是显示消息。从图2中可以看到,它们并不相等CDisplay使用CString。

相关服务

操作系统管理方案

设置环境

要开发引导加载程序,我们需要编译器、链接器及其依赖项。我们还将使用Microsoft Visual Studio 2019使该过程更加方便。

要开始配置环境,我们需要使用Makefile project模板创建一个项目。在Microsoft Visual Studio中选择文件>新建>项目>常规并选择Makefile项目.然后单击下一个

系统启动过程

图3。在Microsoft Visual Studio 2019中创建一个新项目

BIOS中断和屏幕清除

在引导加载程序显示消息之前,必须清除屏幕。让我们使用BIOS中断来完成此任务。

BIOS提供各种中断,允许与计算机硬件交互:输入设备、磁盘存储、音频适配器等。中断如下所示:

       
int [number_of_interrupt];

在这里,中断次数为中断号。在本教程中,您将需要以下中断:

  • int 10h,功能00h—改变视频模式,清除屏幕
  • int 10h,功能01h—设置光标的类型
  • int 10h,功能13h-这个函数通过在屏幕上显示一串文本来结束整个程序

在调用中断之前,必须先定义它的参数。的处理器寄存器包含中断的函数号,而其余寄存器存储当前操作的其他参数。例如,让我们看看int 10 h中断在汇编程序中工作。你需要00h功能:改变视频模式,显示清晰画面。

       
Mov al, 02h;这里我们设置80x25图形模式(文本)mov啊,00h;这是函数的代码,允许我们改变视频模式int 10h;这里我们调用中断

混合高级和低级代码

C++编译器的优点之一是它有一个内置的汇编程序翻译器,它允许你用高级语言编写低级代码。用高级代码编写的汇编指令叫做ASM插入。_asm后面是用大括号括起来的汇编指令块。下面是一个低级语言代码插入的例子:

       
__asm;这是一个引入asm插入的关键字{;代码块的开头…;某些asm代码};代码块的结尾

现在,我们将c++代码与清除屏幕的汇编代码结合起来,以说明混合代码技术:

       
void ClearScreen() {__asm {mov al, 02h;这里你设置80x25图形模式(文本)mov啊,00h;这是函数的代码,允许我们改变视频模式int 10h;这里调用interrupt}}

了解了操作系统引导过程后,我们可以继续开发引导加载程序的元素。

另请阅读:
如何开发一个Windows迷你过滤器驱动程序备份数据

实现引导加载程序结构元素

在了解更多关于BIOS中断和混合代码的知识之后,就可以开始实现引导加载程序组件了。让我们从StartPoint.asm开始:

       
;------------------------------------------------------------ . 286;CPU类型  ;------------------------------------------------------------ . 模型小;内存模型  ;---------------------- 走读生  ----------------------------- 条extrn _BootMain:附近;C函数的原型  ;------------------------------------------------------------ ;------------------------------------------------------------ . 代码org 07 c00h;对于BootSector主:jmp短启动;去主要nop  ;----------------------- 代码段  ----------------------- 开始:cli mov ax, cs;设置段寄存器mov ds,ax;使DS正确mov es,ax;使ES正确的mov ss,ax;使SS正确mov bp,7c00h mov sp,7c00h; Setup a stack sti ; start the program call _BootMain ret END main ; End of program

BootMain是作为程序起点的主函数。这是主操作发生的地方。对于我们的引导加载程序,此函数如下所示:

       
// BootMain.cpp #include "CDisplay.h" #define HELLO_STR "\"Hello, world…\",from low…"CDisplay: ShowCursor(假);CDisplay::TextOut(HELLO_STR, 0,0,黑色,白色,假);返回;}

CDisplay类处理与屏幕的交互。它由以下方法组成:

  • ShowCursor控制光标在显示器上的显示。它有两个值:show和hide,分别启用和禁用游标显示。
  • TextOut产生文本输出,即在屏幕上显示一个字符串。
  • ClearScreen通过更改视频模式来清除屏幕。

这是CDisplay类:

       
//该网站发布了一个网站。h(h)的研究结果,若发现了一个F(If)的F(If)的F(If)的研究结果。定义定义(U)的CDisplay(UU////文本显示的颜色,为文本输出的颜色,为文本提供的颜色,为文本提供的颜色,为文本提供的颜色,为文本提供的颜色,为函数/////定义,定义黑色0x0。定义黑色。定义黑色0x0,定义黑色,定义黑色。定义黑色0x0。定义,定义黑色0 0。定义,定义定义蓝色,定义蓝色。定义,定义蓝色0x0。定义,定义,定义蓝色。定义,定义,定义蓝0x0。定义,定义蓝色。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。定义。B#定义浅红色0xC#定义浅红色0xD定义浅棕色0xE定义白色0xF包括“类型.h”包括“CString.h”类CDisplay{public:static void ClearScreen();static void TextOut(const char far*inStrSource,byte inX=0,byte inY=0,byte inBackgroundColor=BLACK,byte inTextColor=WHITE,bool inUpdateCursor=false);静态void ShowCursor包括“CDISPLAY.h”无效CDISPLAY::TextOut(const char far*inStrSource,byte inX,byte inY,byte inBackgroundColor,byte inextcolor,byte inUpdateCursor){byte texttribute=((inTextColor)|(inBackgroundColor<<4)),byte lengthOfString=CString::Strlen::Strlen(inStrSource);\uu asm{推送bp mov al、inUpdateCursor xor bh、bh mov bl、textAttribute xor cx、cx mov cl、lengthOfString mov dh、inY mov dl、inX mov es、word ptr[inStrSource+2]mov bp、word ptr[inStrSource]mov ah、13h int 10h pop bp}无效显示::清除屏幕(){{byte flag=inMode?0:0x32;{mov ch,flag mov cl,0Ah mov ah,01h int 10h}

类使用字符串。属性所包含的字符串的值将被CDisplay类。让我们实现斯特伦这个类的方法。此方法将指向字符串的指针作为其参数传递,计算所获得字符串中的字符数,并返回结果数。实现如下所示:

       
//CString.h#ifndef#CString#define#CString#include“Types.h”类CString{public:static byte Strlen(const char far*inStrSource)};#endif/u CString#///CString.cpp#包括“CString.h”字节CString::Strlen Strlen返回lenghtOfString;}

现在,您可以继续组装程序了。

另请阅读:
如何为Rust中的树莓Pi 3开发最小操作系统

阶段3:组装引导加载程序

编写完引导加载程序代码后,就该将其转换为一个可以在16位操作系统上工作的。com文件了。首先,您需要使用命令行启动汇编器和c++编译器。之后,您需要将所需的参数传递给编译器。因此,您将接收到目标文件。

接下来,一个链接器出现了。你需要它将目标文件合并成一个单独的。com可执行文件。

现在,让我们自动化组装过程。为此,您只需要创建一个.bat文件,其中包含所有必要的命令和参数。组装应用程序的整个过程是这样的:

安装引导装载程序

图4。组装引导加载程序

编译器和链接器必须放在项目文件夹中。在这个文件夹中,你还需要放置一个包含以下内容的.bat文件:

       
.\VC152\CL.EXE /AT /G2 /Gs /Gx /c /Zl *.cpp .\VC152\ML.EXE /AT /c *。asm .\VC152\LINK.EXE /T /NOD起始点。obj bootmain。obj cdisplay。obj装运箱。obj德尔* .obj

V152是包含编译器和链接器的文件夹的名称。您可以重命名它,但其余的内容必须保持不变

我们还可以使用MicrosoftVisualStudio作为本例的开发环境。VisualStudio的一大优点是它支持任何编译器。要启动编译过程,请打开项目>属性>配置属性\常规>配置类型

在配置属性部分,单击NMake页面。然后,在中输入build.bat文件的路径构建命令行重建命令行方框如下截图所示:

配置Makefile项目

图5。配置Makefile项目

完成这些操作后,可以像往常一样按F7或按Ctrl+F7热键开始编译。在此过程中,所有相关信息都显示在Output窗口中。

阶段4:在虚拟机和真实硬件上测试引导加载程序

您可以在为此任务配置的物理机或虚拟机(VM)上测试引导加载程序。通过在物理机器上测试,您可以验证引导加载程序是否工作并评估其性能。

在虚拟机上测试软件的主要优点是它更安全,因为您可以快速修复虚拟机上的任何问题或创建一个新的虚拟机。但是,这种类型的测试不允许您检查引导加载程序的性能。

在本教程中,我们将概述这两种方法。

首先,您需要一个工具来将引导加载程序写入虚拟或物理驱动器。根据您的需要和资源,您可以选择基于控制台或接口的工具。我们的选择是NTFS 3.66的磁盘资源管理器(你也可以找到FAT文件系统的版本)和MS-DOS的诺顿磁盘编辑器2002。

另请阅读:
如何使用QEMU虚拟设备开发Windows驱动程序

在虚拟机上进行测试

1.在测试之前,您需要创建一个虚拟机。您可以使用VMware 5.0或更高版本。虚拟机用于测试引导加载器的最小磁盘空间为1GB。因为我们使用的是NTFS文件系统的工具,所以需要将分配的空间格式化为NTFS。

2.将磁盘映射到VMware,使其成为虚拟磁盘。去文件>映射或断开虚拟驱动器并单击地图在“映射虚拟磁盘”窗口中,在“文件名称框中选择驱动器分区标签驾驶盒子。

映射虚拟磁盘

图6。映射虚拟磁盘

一定要清理以只读模式打开文件复选框。此选项通过禁止将数据写入磁盘来防止数据损坏。您可以保持其余选项不变。

这种快速设置允许我们像处理常规Windows逻辑磁盘一样处理虚拟磁盘。

3.您可以使用NTFS 3.66磁盘资源管理器在0物理偏移量处记录引导加载程序。要做到这一点,请访问文件>驾驶然后选择虚拟驱动器。在“选择驱动器”窗口中,打开逻辑驱动器组并选择具有先前定义的标签的驱动器。

选择映射的驱动器

图7。选择映射的驱动器

然后,选择视图>为十六进制. 在十六进制视图窗口中,将显示磁盘的16位表示形式。内容按偏移和扇区划分。由于磁盘目前是空的,因此只有0。

十六进制视图面板中的空磁盘

图8.十六进制视图面板中的空磁盘

4.让我们将引导加载程序写入驱动器的第一个扇区。移动标记到00位置,如上面的截图所示。选择编辑>粘贴从文件,指定其内容必须写入选定位置的文件的路径,然后单击打开.这将粘贴引导加载程序。第一个扇区的内容会随之改变(见下面的截图;除非您对代码做了一些修改,否则内容将是相同的)。

现在需要指示BIOS将第一个扇区标识为引导扇区,并将其加载到内存中。为此,您需要在1FE偏移量处添加55AAh签名,该偏移量标记开始。为此,按F2热键进入编辑模式,并在定义的偏移量处写入签名所需的字符。编辑完成后,按Esc

包含引导加载程序的磁盘的十六进制视图面板

图9。包含引导加载程序的磁盘的十六进制视图面板

若要确认数据写入,请选择工具>选择权.在“选项”窗口中,选择虚拟写模式并点击.这种模式允许只存储在内存中的修改。

确认数据写

图10.确认数据写入

然后在VMware中断开虚拟磁盘文件>映射或断开虚拟磁盘点击断开连接

现在,让我们运行虚拟机,看看这一胜利时刻,我们在编写低级代码方面的努力取得了成功“Hello world…”,来自低级别的!在屏幕上。

引导加载程序正在工作!

图11。引导加载程序正在工作!

最后,让我们看看在物理机器上测试引导加载程序时是否能取得同样的成功。

在物理机器上进行测试

在物理机上测试引导加载程序的过程与在虚拟机上测试引导加载程序的过程几乎相同。要使用Disk Explorer for NTFS将引导加载程序写入闪存驱动器,您需要执行上述所有步骤,只需稍微更改步骤三。特别是,您需要选择整个物理驱动器而不是逻辑部分使写入以正确的偏移量执行。

选择要测试的物理驱动器

图12。选择要测试的物理驱动器

您还可以使用闪存驱动器而不是硬盘驱动器,以避免数据损坏。但在此之前,请重新启动计算机,引导进入BIOS,并确保您的BIOS支持闪存驱动器。如果没有,唯一安全的解决方案是在虚拟机上执行测试。

用于引导加载程序调试的工具

确保您的引导加载程序有适当的调试工具。另外,请记住调试所需的时间与开发相同。您还需要深入研究汇编机器代码。在这一点上,良好的汇编语言知识是必须的。

下面的调试工具将会派上用场:

  • 涡轮调试器Borland是一个很棒的16位模式调试解决方案
  • CoveViewMicrosoft Visual Studio中是否嵌入了一个16位调试器
  • D86是一个很好的16位调试器,由Eric Isaacson编写,Eric Isaacson是一名在英特尔汇编程序开发方面经验丰富的程序员
  • Bochs是一个虚拟机程序仿真器,带有用于机器命令的内置调试器。

另请阅读:
如何通过USB调试Windows操作系统

结论

在本教程中,我们向您展示了如何创建一个简单但可操作的引导加载器,并在虚拟机和物理设备上对其进行测试。使用我们的指南,您可以更深入地了解系统引导过程,并提高您的低级开发技能。请在此GitHub随意使用我们引导加载器的源代码存储库

如果你手头有更有挑战性的任务,和我们的驱动程序开发专家

告诉我们你的项目
给我们发提案请求吧!我们稍后会告诉你细节和估价。

浏览
单击“发送”,表示您同意处理您的数据

预约一次试探性电话

没有任何特定的任务,但我们的技能似乎很有趣?

获得一个快速的Apriorit介绍,更好地了解我们的团队能力。

联系我们

  • + 1 202-780-9339
  • (电子邮件保护)
  • 美国DE Wilmington Silverside Road 3524 Suite 35B邮编:19810-4929
  • D-U-N-S号码:117063762
Baidu