跳转到内容

x86 汇编/引导加载程序

来自维基教科书,开放书籍,开放世界

当计算机打开时,会发出一些哔哔声,闪烁一些灯光,然后出现一个加载屏幕。然后,操作系统神奇地加载到内存中。随之而来的问题是,操作系统是如何加载的?是什么启动了它?答案是 引导加载程序

什么是引导加载程序?

[编辑 | 编辑源代码]

引导加载程序是小型软件片段,在计算机开机时发挥作用,将操作系统加载并准备好执行。这种过程在不同的计算机设计之间有所不同(早期计算机需要用户在每次开机时手动设置),并且在引导加载过程中通常会存在多个阶段。

重要的是要理解,“引导加载程序”只是一个软件分类(而且有时是一个模糊的分类)。对处理器来说,引导加载程序只是它盲目执行的另一段代码。存在许多不同类型的引导加载程序。有些很小,有些很大;有些遵循非常简单的规则,而另一些则显示漂亮的屏幕并提供用户选择。

在 IBM PC 兼容机上,第一个加载的程序是基本输入输出系统 (BIOS)。BIOS 执行许多测试和初始化,如果一切正常,BIOS 的引导加载程序就开始运行。它的目的是加载另一个引导加载程序!它从一个磁盘(或其他存储介质)中选择一个磁盘,并从该磁盘中加载一个二级引导加载程序。

在某些情况下,这个引导加载程序会加载足够的操作系统以开始运行它。在其他情况下,它会从其他地方加载另一个引导加载程序。这通常发生在同一台计算机上安装了多个操作系统的情况下;每个操作系统可能都有自己特定的引导加载程序,以及一个“中心”引导加载程序,根据用户的选择加载其中一个特定的引导加载程序。

大多数引导加载程序都是专门用汇编语言(甚至机器代码)编写的,因为它们需要紧凑,它们没有访问其他语言可能需要的操作系统例程(例如内存分配),它们需要遵循一些特殊的规则,并且它们频繁地使用底层功能。但是,一些引导加载程序,特别是那些具有许多功能并允许用户输入的引导加载程序,相当重量级。这些通常是用汇编语言和 C 语言的组合编写的。例如,GRand Unified Bootloader (GRUB) 就是一个例子。

一些引导加载程序高度特定于操作系统,而另一些则不那么特定 - 当然,BIOS 引导加载程序不特定于操作系统。MS-DOS 引导加载程序(它被放置在所有 MS-DOS 格式化的软盘上)只检查文件 IO.SYSMSDOS.SYS 是否存在;如果不存在,它会显示错误消息“非系统磁盘或磁盘错误”,否则它会加载并开始执行 IO.SYS

最终阶段的引导加载程序可能需要(由操作系统)以某种方式准备计算机,例如,将处理器置于保护模式并将中断控制器编程。虽然可以在操作系统的初始化过程中执行这些操作,但将它们移到引导加载程序中可以简化操作系统设计。一些操作系统要求它们的引导加载程序设置一个小的基本 GDT (全局描述符表) 并进入保护模式,以消除操作系统需要任何 16 位代码的需要。但是,操作系统可能很快用自己的复杂 GDT 替换它。

引导扇区

[编辑 | 编辑源代码]

磁盘的前 512 字节称为 引导扇区主引导记录。引导扇区是磁盘上为引导目的保留的区域。如果磁盘的引导扇区包含有效的引导扇区(扇区的最后一个字必须包含签名 0xAA55),则 BIOS 将磁盘视为可引导的。

引导过程

[编辑 | 编辑源代码]

当打开或重置时,x86 处理器开始执行它在地址 FFFF:0000 处找到的指令(在此阶段它正在 实模式 下运行)(Intel 软件开发人员手册卷 3 第 9 章与该信息相矛盾:执行从物理地址 0xFFFFFFF0 开始,等等)。在 IBM PC 兼容处理器中,该地址映射到包含计算机基本输入输出系统 (BIOS) 代码的 ROM 芯片。BIOS 负责许多测试和初始化;例如,BIOS 可能会执行内存测试,初始化中断控制器和系统计时器,并测试这些设备是否正常工作。

最终,实际的引导加载开始。首先,BIOS 搜索并初始化可用的存储介质(例如软盘驱动器、硬盘驱动器、CD 驱动器),然后它决定将尝试从哪个介质引导。它检查每个设备的可用性(例如,确保软盘驱动器包含一个磁盘),然后检查 0xAA55 签名,以某种预定义的顺序(通常,顺序可以使用 BIOS 设置工具配置)。它将第一个可引导设备的第一个扇区加载到 RAM 中,并启动执行。

理想情况下,这将是另一个引导加载程序,它将继续工作,进行一些准备,然后将控制权传递给其他内容。

虽然 BIOS 保持与 20 年前的软件兼容,但它们也随着时间的推移变得越来越复杂。早期的 BIOS 无法从 CD 驱动器引导,但现在 CD 甚至 DVD 引导都是标准的 BIOS 功能。从 USB 存储设备引导也是可能的,一些系统可以从网络引导。为了实现这种高级功能,BIOS 有时会进入保护模式等,但随后会返回到实模式,以便与旧版引导加载程序兼容。这造成了一个先有鸡还是先有蛋的问题:引导加载程序被编写为与无处不在的 BIOS 协同工作,而 BIOS 被编写为支持所有这些引导加载程序,从而阻止了太多新的引导加载功能。

但是,一种新的引导技术,UEFI,正在开始获得发展势头。它更复杂,本文不会对此进行讨论。

还要注意,其他计算机系统 - 即使是使用 x86 处理器的系统 - 也可能以不同的方式引导。事实上,一些嵌入式系统,其软件足够紧凑以至于可以存储在 ROM 芯片上,可能根本不需要引导加载程序。

技术细节

[编辑 | 编辑源代码]

引导加载程序在某些条件下运行,程序员必须了解这些条件才能创建一个成功的引导加载程序。以下内容适用于由 PC BIOS 启动的引导加载程序。

  1. 驱动器的第一个扇区包含它的引导加载程序。
  2. 一个扇区是 512 字节 - 其中必须是 0xAA55(即 0x55 后跟 0xAA),否则 BIOS 将驱动器视为不可引导的。
  3. 如果一切正常,第一个扇区将被放置在 RAM 地址 0000:7C00,而 BIOS 的作用就结束了,因为它将控制权转移到 0000:7C00(也就是说,它跳转到该地址)。
  4. DL 寄存器将包含正在从其引导的驱动器号,如果您想从驱动器上的其他位置读取更多数据,这很有用。
  5. BIOS 会留下大量代码,这些代码用于处理硬件中断(例如按键)并为引导加载程序和操作系统提供服务(例如键盘输入、磁盘读取和写入屏幕)。您必须了解中断向量表 (IVT) 的目的,并注意不要干扰您所依赖的 BIOS 部分。大多数操作系统会用自己的代码替换 BIOS 代码,但引导加载程序只能使用自己的代码和 BIOS 提供的代码。有用的 BIOS 服务包括 int 10h(用于显示文本/图形)、int 13h(磁盘功能)和 int 16h(键盘输入)。
  6. 这意味着引导加载程序需要的任何代码或数据都必须包含在第一个扇区中(注意不要意外地执行数据)或手动从磁盘的另一个扇区加载到 RAM 中的某个位置。因为操作系统尚未运行,所以大多数 RAM 将处于未使用状态。但是,您必须注意不要干扰上述 BIOS 中断处理程序和服务所需的 RAM。
  7. 操作系统代码本身(或下一个引导加载程序)也需要加载到 RAM 中。
  8. BIOS 将堆栈指针放在引导扇区末尾的 512 字节之外,这意味着堆栈不能超过 512 字节。可能需要将堆栈移动到更大的区域。
  9. 如果要使磁盘在主流操作系统下可读,需要遵循一些约定。例如,您可能希望在软盘上包含一个 BIOS 参数块,以使磁盘在大多数 PC 操作系统下可读。

大多数汇编程序将有一个类似于 ORG 7C00h 的命令或指令,用于通知汇编程序代码将从偏移量 7C00h 开始加载。汇编程序将在计算指令和数据地址时考虑到这一点。如果您省略了这一点,汇编程序会假设代码从地址 0 加载,并且必须在代码中手动进行补偿。

通常,引导加载程序会将内核加载到内存中,然后跳转到内核。 内核将能够回收引导加载程序使用的内存(因为它已经完成了其工作)。 但是,可以在引导扇区中包含操作系统代码,并在操作系统启动后将其保留在内存中。

这是一个为 NASM 设计的简单引导加载程序演示

         org 7C00h
 
         jmp short Start ;Jump over the data (the 'short' keyword makes the jmp instruction smaller)
 
 Msg:    db "Hello World! "
 EndMsg:
 
 Start:  mov bx, 000Fh   ;Page 0, colour attribute 15 (white) for the int 10 calls below
         mov cx, 1       ;We will want to write 1 character
         xor dx, dx      ;Start at top left corner
         mov ds, dx      ;Ensure ds = 0 (to let us load the message)
         cld             ;Ensure direction flag is cleared (for LODSB)
 
 Print:  mov si, Msg     ;Loads the address of the first byte of the message, 7C02h in this case
 
                         ;PC BIOS Interrupt 10 Subfunction 2 - Set cursor position
                         ;AH = 2
 Char:   mov ah, 2       ;BH = page, DH = row, DL = column
         int 10h
         lodsb           ;Load a byte of the message into AL.
                         ;Remember that DS is 0 and SI holds the
                         ;offset of one of the bytes of the message.
 
                         ;PC BIOS Interrupt 10 Subfunction 9 - Write character and colour
                         ;AH = 9
         mov ah, 9       ;BH = page, AL = character, BL = attribute, CX = character count
         int 10h
 
         inc dl          ;Advance cursor
 
         cmp dl, 80      ;Wrap around edge of screen if necessary
         jne Skip
         xor dl, dl
         inc dh
 
         cmp dh, 25      ;Wrap around bottom of screen if necessary
         jne Skip
         xor dh, dh
 
 Skip:   cmp si, EndMsg  ;If we're not at end of message,
         jne Char        ;continue loading characters
         jmp Print       ;otherwise restart from the beginning of the message
 
 
 times 0200h - 2 - ($ - $$)  db 0    ;Zerofill up to 510 bytes
 
         dw 0AA55h       ;Boot Sector signature
 
 ;OPTIONAL:
 ;To zerofill up to the size of a standard 1.44MB, 3.5" floppy disk
 ;times 1474560 - ($ - $$) db 0


要编译上面的文件,假设它被称为'floppy.asm',你可以使用以下命令

nasm -f bin -o floppy.img floppy.asm

虽然严格来说这不是一个引导加载程序,但它是可启动的,并且展示了一些内容

  • 如何在引导扇区中包含和访问数据
  • 如何跳过包含的数据(这对于 BIOS 参数块是必需的)
  • 如何在扇区末尾放置 0xAA55 签名(如果代码太多无法容纳在一个扇区中,NASM 会发出错误)
  • BIOS 中断的使用

在 Linux 上,你可以发出类似的命令

cat floppy.img > /dev/fd0

将映像写入软盘(映像可能小于磁盘大小,在这种情况下,只有映像中的信息会被写入磁盘)。 另一种更复杂的选择是使用 dd 实用程序

 dd if=floppy.img of=/dev/fd0

在 Windows 下,你可以使用诸如 RAWRITE 之类的软件。

硬盘

[edit | edit source]

硬盘通常在此过程中添加一层额外的内容,因为它们可能会被分区。 硬盘的第一个扇区称为主引导记录 (MBR)。 按照惯例,硬盘的分区信息包含在 MBR 的末尾,就在 0xAA55 签名之前。

BIOS 的作用与之前相同:将磁盘的第一个扇区(即 MBR)读入 RAM,并将执行权转移到该扇区的第一个字节。 BIOS 不知道分区方案 - 它只检查 0xAA55 签名的存在。

虽然这意味着可以使用任何想要的方式使用 MBR(例如,省略或扩展分区表),但很少这样做。 尽管分区表设计非常老旧且有限 - 它仅限于四个分区 - 几乎所有为 IBM PC 兼容机设计的操作系统都假设 MBR 将以这种方式格式化。 因此,违反惯例将使你的磁盘无法操作,除非针对专门使用它的操作系统。

实际上,MBR 通常包含一个引导加载程序,其目的是加载另一个引导加载程序 - 位于其中一个分区的开头。 这通常是一个非常简单的程序,它找到第一个标记为活动的分区,将它的第一个扇区加载到 RAM 中,并开始执行。 由于按照惯例,新的引导加载程序也被加载到地址 7C00h,因此旧的加载程序可能需要在执行此操作之前将自己的一部分或全部重新定位到不同的位置。 此外,ES:SI 预计包含分区表在 RAM 中的地址,而 DL 则是引导驱动器号。 违反这些惯例可能会使引导加载程序与其他引导加载程序不兼容。

但是,许多引导管理器(允许用户选择要启动的分区,有时甚至内核的软件)使用自定义 MBR 代码,它从磁盘上的某个位置加载引导管理器代码的其余部分,然后向用户提供有关如何继续引导过程的选项。 引导管理器也可以驻留在分区内,在这种情况下,它必须首先由另一个引导加载程序加载。

大多数引导管理器支持链式加载(即通过通常的将分区第一个扇区加载到地址 7C00 的过程启动另一个引导加载程序),这通常用于 DOS 和 Windows 等系统。 但是,一些引导管理器(特别是 GRUB)支持加载用户选择的内核映像。 这可以与 GNU/Linux 和 Solaris 等系统一起使用,从而在启动系统时提供更大的灵活性。 机制可能与链式加载略有不同。

显然,分区表提出了一个先有鸡还是先有蛋的问题,这对分区方案造成了不合理的限制。 一个正在流行的解决方案是 GUID 分区表;它使用一个虚拟 MBR 分区表,以便传统的操作系统不会干扰 GPT,而更新的操作系统则可以利用该系统提供的众多改进。

GNU GRUB

[edit | edit source]

The GRand Unified Bootloader 支持灵活的 multiboot 引导协议。 该协议旨在通过为各种操作系统提供单一、灵活的协议来简化引导过程。 许多免费操作系统可以使用 multiboot 启动。

GRUB 非常强大,实际上是一个小型操作系统。 它可以读取各种文件系统,因此允许你按文件名指定内核映像以及内核可能使用的单独模块文件。 命令行参数也可以传递给内核 - 这是以维护模式、"安全模式"或使用 VGA 图形等方式启动操作系统的不错方法。 GRUB 可以为用户提供一个菜单供选择,以及允许输入自定义加载参数。

显然,这种功能不可能在 512 字节的代码中提供。 这就是为什么 GRUB 被分成两到三个"阶段"的原因

  • 阶段 1 - 这是一个 512 字节的块,其中包含阶段 1.5 或阶段 2 的位置硬编码到其中。 它加载下一阶段。
  • 阶段 1.5 - 一个可选的阶段,它了解阶段 2 所在的文件系统(例如 FAT32 或 ext3)。 它将找出阶段 2 的位置并加载它。 这个阶段非常小,位于固定区域,通常位于阶段 1 之后。
  • 阶段 2 - 这是一个更大的映像,包含所有 GRUB 功能。

请注意,阶段 1 可以安装到硬盘的主引导记录,也可以安装到其中一个分区中并由另一个引导加载程序链式加载。

Windows 不能使用 multiboot 加载,但 Windows 引导加载程序(就像其他非 multiboot 操作系统的引导加载程序一样)可以从 GRUB 链式加载,这并不是很好,但确实可以让你启动这些系统。

引导加载程序示例 - Linux 内核 v0.01

[edit | edit source]
SYSSIZE=0x8000
|
|	boot.s
|
| boot.s is loaded at 0x7c00 by the bios-startup routines, and moves itself
| out of the way to address 0x90000, and jumps there.
|
| It then loads the system at 0x10000, using BIOS interrupts. Thereafter
| it disables all interrupts, moves the system down to 0x0000, changes
| to protected mode, and calls the start of system. System then must
| RE-initialize the protected mode in it's own tables, and enable
| interrupts as needed.
|
| NOTE! currently system is at most 8*65536 bytes long. This should be no
| problem, even in the future. I want to keep it simple. This 512 kB
| kernel size should be enough - in fact more would mean we'd have to move
| not just these start-up routines, but also do something about the cache-
| memory (block IO devices). The area left over in the lower 640 kB is meant
| for these. No other memory is assumed to be "physical", i.e. all memory
| over 1Mb is demand-paging. All addresses under 1Mb are guaranteed to match
| their physical addresses.
|
| NOTE1 above is no longer valid in it's entirety. cache-memory is allocated
| above the 1Mb mark as well as below. Otherwise it is mainly correct.
|
| NOTE 2! The boot disk type must be set at compile-time, by setting
| the following equ. Having the boot-up procedure hunt for the right
| disk type is severe brain-damage.
| The loader has been made as simple as possible (had to, to get it
| in 512 bytes with the code to move to protected mode), and continuous
| read errors will result in a unbreakable loop. Reboot by hand. It
| loads pretty fast by getting whole sectors at a time whenever possible.

| 1.44Mb disks:
sectors = 18
| 1.2Mb disks:
| sectors = 15
| 720kB disks:
| sectors = 9

.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text

BOOTSEG = 0x07c0
INITSEG = 0x9000
SYSSEG  = 0x1000			| system loaded at 0x10000 (65536).
ENDSEG	= SYSSEG + SYSSIZE

entry start
start:
	mov	ax,#BOOTSEG
	mov	ds,ax
	mov	ax,#INITSEG
	mov	es,ax
	mov	cx,#256
	sub	si,si
	sub	di,di
	rep
	movw
	jmpi	go,INITSEG
go:	mov	ax,cs
	mov	ds,ax
	mov	es,ax
	mov	ss,ax
	mov	sp,#0x400		| arbitrary value >>512

	mov	ah,#0x03	| read cursor pos
	xor	bh,bh
	int	0x10
	
	mov	cx,#24
	mov	bx,#0x0007	| page 0, attribute 7 (normal)
	mov	bp,#msg1
	mov	ax,#0x1301	| write string, move cursor
	int	0x10

| ok, we've written the message, now
| we want to load the system (at 0x10000)

	mov	ax,#SYSSEG
	mov	es,ax		| segment of 0x010000
	call	read_it
	call	kill_motor

| if the read went well we get current cursor position ans save it for
| posterity.

	mov	ah,#0x03	| read cursor pos
	xor	bh,bh
	int	0x10		| save it in known place, con_init fetches
	mov	[510],dx	| it from 0x90510.
		
| now we want to move to protected mode ...

	cli			| no interrupts allowed !

| first we move the system to it's rightful place

	mov	ax,#0x0000
	cld			| 'direction'=0, movs moves forward
do_move:
	mov	es,ax		| destination segment
	add	ax,#0x1000
	cmp	ax,#0x9000
	jz	end_move
	mov	ds,ax		| source segment
	sub	di,di
	sub	si,si
	mov 	cx,#0x8000
	rep
	movsw
	j	do_move

| then we load the segment descriptors

end_move:

	mov	ax,cs		| right, forgot this at first. didn't work :-)
	mov	ds,ax
	lidt	idt_48		| load idt with 0,0
	lgdt	gdt_48		| load gdt with whatever appropriate

| that was painless, now we enable A20

	call	empty_8042
	mov	al,#0xD1		| command write
	out	#0x64,al
	call	empty_8042
	mov	al,#0xDF		| A20 on
	out	#0x60,al
	call	empty_8042

| well, that went ok, I hope. Now we have to reprogram the interrupts :-(
| we put them right after the intel-reserved hardware interrupts, at
| int 0x20-0x2F. There they won't mess up anything. Sadly IBM really
| messed this up with the original PC, and they haven't been able to
| rectify it afterwards. Thus the BIOS puts interrupts at 0x08-0x0f,
| which is used for the internal hardware interrupts as well. We just
| have to reprogram the 8259's, and it isn't fun.

	mov	al,#0x11		| initialization sequence
	out	#0x20,al		| send it to 8259A-1
	.word	0x00eb,0x00eb		| jmp $+2, jmp $+2
	out	#0xA0,al		| and to 8259A-2
	.word	0x00eb,0x00eb
	mov	al,#0x20		| start of hardware int's (0x20)
	out	#0x21,al
	.word	0x00eb,0x00eb
	mov	al,#0x28		| start of hardware int's 2 (0x28)
	out	#0xA1,al
	.word	0x00eb,0x00eb
	mov	al,#0x04		| 8259-1 is master
	out	#0x21,al
	.word	0x00eb,0x00eb
	mov	al,#0x02		| 8259-2 is slave
	out	#0xA1,al
	.word	0x00eb,0x00eb
	mov	al,#0x01		| 8086 mode for both
	out	#0x21,al
	.word	0x00eb,0x00eb
	out	#0xA1,al
	.word	0x00eb,0x00eb
	mov	al,#0xFF		| mask off all interrupts for now
	out	#0x21,al
	.word	0x00eb,0x00eb
	out	#0xA1,al

| well, that certainly wasn't fun :-(. Hopefully it works, and we don't
| need no steenking BIOS anyway (except for the initial loading :-).
| The BIOS-routine wants lots of unnecessary data, and it's less
| "interesting" anyway. This is how REAL programmers do it.
|
| Well, now's the time to actually move into protected mode. To make
| things as simple as possible, we do no register set-up or anything,
| we let the gnu-compiled 32-bit programs do that. We just jump to
| absolute address 0x00000, in 32-bit protected mode.

	mov	ax,#0x0001	| protected mode (PE) bit
	lmsw	ax		| This is it!
	jmpi	0,8		| jmp offset 0 of segment 8 (cs)

| This routine checks that the keyboard command queue is empty
| No timeout is used - if this hangs there is something wrong with
| the machine, and we probably couldn't proceed anyway.
empty_8042:
	.word	0x00eb,0x00eb
	in	al,#0x64	| 8042 status port
	test	al,#2		| is input buffer full?
	jnz	empty_8042	| yes - loop
	ret

| This routine loads the system at address 0x10000, making sure
| no 64kB boundaries are crossed. We try to load it as fast as
| possible, loading whole tracks whenever we can.
|
| in:	es - starting address segment (normally 0x1000)
|
| This routine has to be recompiled to fit another drive type,
| just change the "sectors" variable at the start of the file
| (originally 18, for a 1.44Mb drive)
|
sread:	.word 1			| sectors read of current track
head:	.word 0			| current head
track:	.word 0			| current track
read_it:
	mov ax,es
	test ax,#0x0fff
die:	jne die			| es must be at 64kB boundary
	xor bx,bx		| bx is starting address within segment
rp_read:
	mov ax,es
	cmp ax,#ENDSEG		| have we loaded all yet?
	jb ok1_read
	ret
ok1_read:
	mov ax,#sectors
	sub ax,sread
	mov cx,ax
	shl cx,#9
	add cx,bx
	jnc ok2_read
	je ok2_read
	xor ax,ax
	sub ax,bx
	shr ax,#9
ok2_read:
	call read_track
	mov cx,ax
	add ax,sread
	cmp ax,#sectors
	jne ok3_read
	mov ax,#1
	sub ax,head
	jne ok4_read
	inc track
ok4_read:
	mov head,ax
	xor ax,ax
ok3_read:
	mov sread,ax
	shl cx,#9
	add bx,cx
	jnc rp_read
	mov ax,es
	add ax,#0x1000
	mov es,ax
	xor bx,bx
	jmp rp_read

read_track:
	push ax
	push bx
	push cx
	push dx
	mov dx,track
	mov cx,sread
	inc cx
	mov ch,dl
	mov dx,head
	mov dh,dl
	mov dl,#0
	and dx,#0x0100
	mov ah,#2
	int 0x13
	jc bad_rt
	pop dx
	pop cx
	pop bx
	pop ax
	ret
bad_rt:	mov ax,#0
	mov dx,#0
	int 0x13
	pop dx
	pop cx
	pop bx
	pop ax
	jmp read_track

/*
 * This procedure turns off the floppy drive motor, so
 * that we enter the kernel in a known state, and
 * don't have to worry about it later.
 */
kill_motor:
	push dx
	mov dx,#0x3f2
	mov al,#0
	outb
	pop dx
	ret

gdt:
	.word	0,0,0,0		| dummy

	.word	0x07FF		| 8Mb - limit=2047 (2048*4096=8Mb)
	.word	0x0000		| base address=0
	.word	0x9A00		| code read/exec
	.word	0x00C0		| granularity=4096, 386

	.word	0x07FF		| 8Mb - limit=2047 (2048*4096=8Mb)
	.word	0x0000		| base address=0
	.word	0x9200		| data read/write
	.word	0x00C0		| granularity=4096, 386

idt_48:
	.word	0			| idt limit=0
	.word	0,0			| idt base=0L

gdt_48:
	.word	0x800		| gdt limit=2048, 256 GDT entries
	.word	gdt,0x9		| gdt base = 0X9xxxx
	
msg1:
	.byte 13,10
	.ascii "Loading system ..."
	.byte 13,10,13,10

.text
endtext:
.data
enddata:
.bss
endbss:

测试引导加载程序

[edit | edit source]

也许测试引导加载程序的最简单方法是在虚拟机中,例如 VirtualBox 或 VMware。[1]

有时,如果引导加载程序支持 GDB 远程调试协议,这将很有用。[2]

进一步阅读

[edit | edit source]
华夏公益教科书