实战操作系统 loader 编写(上) -- 进入保护模式

2022-06-27 14:53:54 浏览数 (1)

1. 引言

此前的文章中,我们详细介绍了从引导扇区跳转到 loader 的工作: 从启动扇区跳转到 loader

引导扇区的工作已经告一段落,接下来我们的工作就是编写我们的 loader 了。 loader 的工作只有两个:

  1. 将内核载入内存
  2. 跳入保护模式

本文我们就来详细介绍一下。

2. loader 加载内核的过程

有了通过引导扇区加载 loader 的经验,让 loader 加载内核就简单的多了。 从原理上来说,loader 加载内核也同样是从 FAT12 的软盘文件系统上找到内核入口文件,这与引导扇区做的事情并没有很大的区别,这里也不进行详细的介绍,只是分块大致讲解一下。 但是,我们的内核将编译成 ELF 文件,因为只有这样,我们才能够接下来实现用 C 语言编写内核的目的,那么,如何让 loader 将内核 ELF 文件载入内存呢?其原理上一篇文章已经介绍过: 详解 Linux 可执行文件 ELF 文件的内部结构

ELF 文件是在 unix 环境上编译生成的可执行可连接文件,他通过多个 section 来组织编译后的可执行代码,若干个 section 构成一个段,由 program header table 描述如何载入内存。 因此,通过 elf header 与 program header table 中每一个条目的指引,我们就能够将 ELF 文件载入内存了。 但是别急,本文我们先不急于去把 ELF 放在他应该在的内存位置上,因为 ELF 文件必须在保护模式下执行,所以我们先把内核放到一整块内存中,然后进入保护模式,再在保护模式中对他进行调整,根据 ELF 内部的一系列信息将他放到他应该位于的虚拟地址上,然后才能通过跳转指令,将控制权从 loader 再交给 kernel。 本文,我们就来实现将内核载入内存并启动保护模式,也许你会有些失望,本文描述的内容都是此前文章已经介绍过的,不存在新的知识点,但不是有句话说“温故而知新”嘛。

3. 从软盘读取 kernel

我们首先来看看如何让 loader 能够在软盘上找到 kernel,这里的 kernel,我们暂且先使用之前我们写好的快速排序的程序: 如何实现汇编语言与 C 语言之间的相互调用

3.1. 定义 FAT12 磁盘头及相关信息

因为 boot 扇区需要读取 FAT12 磁盘头及相关信息,而 loader 也同样需要,所以我们需要定义一个公共依赖:

代码语言:javascript复制
; FAT12 磁盘头
BS_OEMName        DB 'ForrestY'    ; OEM String, 必须 8 个字节

BPB_BytsPerSec    DW 512            ; 每扇区字节数
BPB_SecPerClus    DB 1            ; 每簇多少扇区
BPB_RsvdSecCnt    DW 1            ; Boot 记录占用多少扇区
BPB_NumFATs        DB 2            ; 共有多少 FAT 表
BPB_RootEntCnt    DW 224            ; 根目录文件数最大值
BPB_TotSec16    DW 2880            ; 逻辑扇区总数
BPB_Media        DB 0xF0            ; 媒体描述符
BPB_FATSz16        DW 9            ; 每FAT扇区数
BPB_SecPerTrk    DW 18            ; 每磁道扇区数
BPB_NumHeads    DW 2            ; 磁头数(面数)
BPB_HiddSec        DD 0            ; 隐藏扇区数
BPB_TotSec32    DD 0            ; 如果 wTotalSectorCount 是 0 由这个值记录扇区数
BS_DrvNum        DB 0            ; 中断 13 的驱动器号
BS_Reserved1    DB 0            ; 未使用
BS_BootSig        DB 29h            ; 扩展引导标记 (29h)
BS_VolID        DD 0            ; 卷序列号
BS_VolLab        DB 'OrangeS0.02'; 卷标, 必须 11 个字节
BS_FileSysType    DB 'FAT12   '    ; 文件系统类型, 必须 8个字节

; 根目录占用空间 RootDirSectors = ((BPB_RootEntCnt*32) (BPB_BytsPerSec–1))/BPB_BytsPerSec
RootDirSectors        equ    14  

; Root Directory 的第一个扇区号    = BPB_RsvdSecCnt   (BPB_NumFATs * BPB_FATSz16)
SectorNoOfRootDirectory    equ    19  

; FAT1 的第一个扇区号    = BPB_RsvdSecCnt
SectorNoOfFAT1        equ    1  

; DeltaSectorNo = BPB_RsvdSecCnt   (BPB_NumFATs * BPB_FATSz16) - 2
; 文件的开始Sector号 = DirEntry中的开始Sector号   根目录占用Sector数目   DeltaSectorNo
DeltaSectorNo        equ    17

3.2. 在软盘中寻找 kernel.bin

想了解更加详细的内容,参考此前引导扇区加载 loader 的代码: 从启动扇区跳转到 loader

主要步骤仍然是:

  1. 循环读取根目录区的一个扇区
  2. 循环读取当前扇区内的一个条目
  3. 比较文件名是否为 KERNEL.BIN,相同则表示已找到

详细代码见附录。

3.3. 将 kernel.bin 读取到内存

如果上一步骤中找到了 kernel.bin 则读取文件内容载入到内存:

  1. 根据 FAT12 头信息,计算出数据区起始扇区
  2. 根据根目录条目中的文件起始簇号,读取文件首个簇
  3. 根据 FAT 项信息定位到文件下一个簇号
  4. 循环读取直到完成整个文件的读取

同样,代码放在附录。

3.4. 运行程序

下面我们编译上一篇文章中的快速排序代码,并把结果命名为 kernel.bin 然后放在 boot.img 的根目录下。 运行我们的系统,就可以看到下图,表示 kernel.bin 已经成功被载入到内存中了:

4. 进入保护模式

如上文所说,loader 的另一个极为重要的工作就是跳转进入保护模式中。 此前我们对保护模式的工作原理、执行方式及相关代码已经有了非常详尽的介绍,我们可以直接复用那些已经写好的代码。 回忆一下,从实地址模式跳转到保护模式需要做哪些事呢?

  1. 创建 GDT 及对应的段选择子
  2. 在段内编写保护模式代码
  3. 将 GDT 首地址通过 lgdt 指令载入 gdtr
  4. 关闭硬件中断
  5. 打开 A20 地址总线
  6. 置位 cr0 寄存器的保护模式标志位
  7. 长跳转进入保护模式

这里需要说明的是,由于此前我们没有编写自己的 booter,而是使用 freedos 系统作为启动扇区拉起我们的系统,所以我们无法预期 freedos 会把我们的代码放在物理内存的哪个位置上,所以我们需要在跳转前动态计算保护模式代码所在的起始位置,然后去覆盖上述最后一步的长跳转指令操作数,这看起来是如此 treak。 如今,我们自己编写的 boot 可以直接指定 loader 被载入内存的起始物理地址,这样,我们在代码编写时就可以计算出进入保护模式的起始位置,因此,再也不需要之前那种 treak 的方法,直接可以在代码中编写操作数实现上述操作了。

5. 运行程序

执行我们的系统,可以看到:

6. 总结

本文详细介绍了 loader 中关键性的两个步骤:

  1. 将内核载入内存
  2. 进入保护模式

正所谓“厚积薄发”,此前我们关于保护模式原理的一系列介绍和总结所积累的大量代码终于派上用场,本文的代码也就显得非常简单易懂了。 然而,事实上,第一步中,我们只是开辟了一块连续的空间来存储“内核”,实际上并没有对 ELF 文件进行处理,所以 ELF 并没有达到可执行的状态,我们也就更没有实现内核的执行了。 敬请期待下一篇文章,让我们在保护模式下,重新放置我们已经载入到内存的内核 ELF 文件,实现通往内核的最后一跳。

8. 附录1 — 源码

由于本文对 boot 代码、kernel 代码均没有任何修改,我们只是使用了此前已经编写、编译好的代码,所以在这部分不再贴出相应代码,Makefile 中也不再包含他们的编译指令。

8.1. Makefile

代码语言:javascript复制
LDR:=loader.asm
KERNEL_BIN:=kernel.bin
LDR_BIN:=$(subst .asm,.bin,$(LDR))

IMG:=boot.img
FLOPPY:=/mnt/floppy/

.PHONY : everything

everything : $(LDR_BIN)
    sudo mount -o loop $(IMG) $(FLOPPY)
    sudo cp $(LDR_BIN) $(FLOPPY) -v
    sudo cp $(KERNEL_BIN) $(FLOPPY) -v
    sudo umount $(FLOPPY)

clean :
    rm -f $(LDR_BIN) *.o

$(LDR_BIN) : $(LDR)
    nasm $< -o $@

8.2. loader.asm

代码语言:javascript复制
org    0100h
jmp    LABEL_START

; ---------------- 内存段描述符宏 -------------
; usage: Descriptor Base, Limit, Attr
;        Base:  dd
;        Limit: dd (low 20 bits available)
;        Attr:  dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3  
    dw    %2 & 0FFFFh                            ; 段界限1  
    dw    %1 & 0FFFFh                            ; 段基址1  
    db    (%1 >> 16) & 0FFh                    ; 段基址2  
    dw    ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)    ; 属性1   段界限2   属性2  
    db    (%1 >> 24) & 0FFh                    ; 段基址3  
%endmacro

BaseOfLoader        equ     09000h    ; LOADER.BIN 被加载到的段地址
OffsetOfLoader        equ      0100h    ; LOADER.BIN 被加载到的偏移地址
BaseOfLoaderPhyAddr equ    BaseOfLoader*10h ; LOADER.BIN 被加载到的物理地址

BaseOfKernelFile    equ     08000h    ; KERNEL.BIN 被加载到的位置段地址
OffsetOfKernelFile  equ         0h    ; KERNEL.BIN 被加载到的位置偏移地址

BaseOfStack    equ    0100h
PageDirBase    equ    100000h    ; 页目录开始地址: 1M
PageTblBase    equ    101000h    ; 页表开始地址:   1M   4K

; -------------- GDT -------------
;                            段基址     段界限, 属性
LABEL_GDT:            Descriptor 0,            0, 0            ; 空描述符
LABEL_DESC_FLAT_C:  Descriptor 0,      0fffffh, 0c09Ah        ; 4GB 可执行代码段
LABEL_DESC_FLAT_RW: Descriptor 0,      0fffffh, 0c092h        ; 4GB 可读写数据段
LABEL_DESC_VIDEO:   Descriptor 0B8000h, 0ffffh, 0f2h        ; 显存段

GdtLen    equ    $ - LABEL_GDT
GdtPtr    dw    GdtLen - 1                            ; 段界限
        dd    BaseOfLoaderPhyAddr   LABEL_GDT        ; 基地址

; ----------- GDT 选择子 ----------
SelectorFlatC        equ    LABEL_DESC_FLAT_C    - LABEL_GDT
SelectorFlatRW        equ    LABEL_DESC_FLAT_RW    - LABEL_GDT
SelectorVideo        equ    LABEL_DESC_VIDEO    - LABEL_GDT   3  

; 下面是 FAT12 磁盘的头, 之所以包含它是因为下面用到了磁盘的一些信息
%include    "fat12hdr.asm"  

LABEL_START:
    mov    ax, cs
    mov    ds, ax
    mov    es, ax
    mov    ss, ax
    mov    sp, BaseOfStack

    mov    dh, 0                    ; "Loading  "  
    call    DispStrRealMode                ; 显示字符串

; ----------- 获取内存信息 -------------
    mov    ebx, 0  
    mov    di, _MemChkBuf            ; es:di 存储地址范围描述符结构 ARDS
.MemChkLoop:
    mov    eax, 0E820h                ; eax = 0000E820h
    mov    ecx, 20                    ; ecx = 地址范围描述符结构大小
    mov    edx, 0534D4150h            ; edx = 'SMAP'  
    int    15h
    jc    .MemChkFail
    add    di, 20  
    inc    dword [_dwMCRNumber]    ; dwMCRNumber = ARDS 的个数
    cmp    ebx, 0  
    jne    .MemChkLoop
    jmp    .MemChkOK
.MemChkFail:
    mov    dword [_dwMCRNumber], 0  
.MemChkOK:

; ----- 在 A 盘根目录寻找 KERNEL.BIN -----
    mov    word [wSectorNo], SectorNoOfRootDirectory

    ; 软盘复位
    xor    ah, ah
    xor    dl, dl
    int    13h

LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
    ; 根目录已读取完成,未找到 kernel.bin
    cmp    word [wRootDirSizeForLoop], 0  
    jz    LABEL_NO_KERNELBIN

    ; 读取根目录区一个扇区
    dec    word [wRootDirSizeForLoop]
    mov    ax, BaseOfKernelFile
    mov    es, ax                        ; es <- BaseOfKernelFile
    mov    bx, OffsetOfKernelFile        ; bx <- OffsetOfKernelFile
    mov    ax, [wSectorNo]                ; ax <- Root Directory 中的某 Sector 号
    mov    cl, 1  
    call    ReadSector

    mov    si, KernelFileName            ; ds:si = "KERNEL  BIN"  
    mov    di, OffsetOfKernelFile        ; es:di = BaseOfKernelFile:OffsetOfKernelFile
    cld                                ; df = 0  

    ; 循环读取目录条目
    mov    dx, 10h                        ; 当前扇区所有目录条目循环次数
LABEL_SEARCH_FOR_KERNELBIN:
    ; 已读取完该扇区
    cmp    dx, 0                  ; `.
    jz    LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR
    dec    dx

    ; 比较文件名
    mov    cx, 11  
LABEL_CMP_FILENAME:
    cmp    cx, 0  
    jz    LABEL_FILENAME_FOUND        ; 已找到 kernel
    dec    cx
    lodsb
    cmp    al, byte [es:di]
    jz    LABEL_GO_ON
    jmp    LABEL_DIFFERENT
LABEL_GO_ON:
    inc    di
    jmp    LABEL_CMP_FILENAME

; 跳转到下一条目
LABEL_DIFFERENT:
    and    di, 0FFE0h                    ; 让 es:di 指向当前条目起始位置
    add    di, 20h                        ; 跳至下一条目
    mov    si, KernelFileName
    jmp    LABEL_SEARCH_FOR_KERNELBIN

; 跳转到下一扇区
LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:
    add    word [wSectorNo], 1  
    jmp    LABEL_SEARCH_IN_ROOT_DIR_BEGIN

; 未找到,显示字符串,终止流程
LABEL_NO_KERNELBIN:
    mov    dh, 2                        ; "No KERNEL."  
    call    DispStrRealMode                    ; 显示字符串
    jmp    $

; 找到 kernel,加载
LABEL_FILENAME_FOUND:
    ; 保存 kernel.bin 的文件大小
    mov     eax, [es : di   01Ch]
    mov     dword [dwKernelSize], eax

    ; 获取 loader.bin 对应的数据区簇号,保存在栈中
    and     di, 0FFF0h
    add     di, 01Ah
    mov     cx, word [es:di]
    push cx

    ; 获取文件所在扇区号,保存在 cx 中
    mov    ax, RootDirSectors
    add    cx, ax
    add    cx, DeltaSectorNo

    ; es:bx = kernel.bin 将要被加载到的内存物理地址
    mov    ax, BaseOfKernelFile
    mov    es, ax
    mov    bx, OffsetOfKernelFile

    ; 循环读取 kernel.bin
    mov    ax, cx
LABEL_GOON_LOADING_FILE:
    ; 打点,表示准备读取一个扇区,展示 Booting....
    push    ax
    push    bx
    mov    ah, 0Eh
    mov    al, '.'  
    mov    bl, 0Fh
    int    10h
    pop    bx
    pop    ax

    ; 根据 FAT 项值循环读取簇
    mov     cl, 1  
    call ReadSector
    pop     ax
    call GetFATEntry
    cmp     ax, 0FFFh
    jz     LABEL_FILE_LOADED
    push ax
    mov     dx, RootDirSectors
    add     ax, dx
    add     ax, DeltaSectorNo
    add     bx, [BPB_BytsPerSec]
    jmp     LABEL_GOON_LOADING_FILE

; 加载完成
LABEL_FILE_LOADED:
    ; 关闭软驱
    call    KillMotor

    ; 显示字符串
    mov    dh, 1  
    call    DispStrRealMode


; ------------ 跳转进入保护模式 -------------
    ; 加载 GDTR
    lgdt    [GdtPtr]

    ; 关中断
    cli

    ; 打开地址线A20
    in    al, 92h
    or    al, 00000010b
    out    92h, al

    ; 准备切换到保护模式
    mov    eax, cr0
    or    eax, 1  
    mov    cr0, eax

    ; 真正进入保护模式
    jmp    dword SelectorFlatC:(BaseOfLoaderPhyAddr   LABEL_PM_START)

    jmp    $

; ---- 显示一个字符串, 函数开始时 dh 中存储字符串序号(0-based) ----
DispStrRealMode:
    mov    ax, MessageLength
    mul    dh
    add    ax, LoadMessage
    mov    bp, ax                ; ┓
    mov    ax, ds                ; ┣ ES:BP = 串地址
    mov    es, ax                ; ┛
    mov    cx, MessageLength    ; CX = 串长度
    mov    ax, 01301h            ; AH = 13,  AL = 01h
    mov    bx, 0007h            ; 页号为0(BH = 0) 黑底白字(BL = 07h)
    mov    dl, 0  
    add    dh, 3                ; 从第 3 行往下显示
    int    10h
    ret

; ------------- 关闭软驱 -----------
KillMotor:
    push    dx
    mov    dx, 03F2h
    mov    al, 0  
    out    dx, al
    pop    dx
    ret

; ----- 从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中 -----
ReadSector:
    push    bp
    mov    bp, sp
    sub    esp, 2                     ; 开辟两个字节的堆栈区域存储扇区数

    mov     byte [bp-2], cl
    push bx
    mov     bl, [BPB_SecPerTrk]    ; bl: 每磁道扇区数
    div    bl                        ; 商保存在 al 中,余数保存在 ah 中
    inc    ah                        ; 获取其实扇区号
    mov    cl, ah                    ; cl <- 起始扇区号
    mov    dh, al
    shr    al, 1                    ; 获取柱面号
    mov    ch, al                    ; ch <- 柱面号
    and    dh, 1                    ; 获取磁头号
    pop    bx
    mov    dl, [BS_DrvNum]            ; 驱动器号 (0 表示 A 盘)
.GoOnReading:
    mov    ah, 2                    ; 读
    mov    al, byte [bp-2]            ; 读 al 个扇区
    int    13h
    jc    .GoOnReading            ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止
    add    esp, 2  
    pop    bp

    ret

; ---- 读取序号为 ax 的 Sector 在 FAT 中的条目, 放在 ax 中 ----
GetFATEntry:
    push    es
    push    bx
    push    ax

    ; 在 BaseOfKernelFile 后面留出 4K 空间用于存放 FAT
    mov    ax, BaseOfKernelFile
    sub    ax, 0100h
    mov    es, ax

    ; 判断 ax 奇偶性,赋值 bOdd 变量
    pop    ax
    mov    byte [bOdd], 0        ; bOdd 变量用于存放当前是奇数次读取还是偶数次读取
    mov    bx, 3  
    mul    bx                    ; dx:ax = ax * 3  
    mov    bx, 2  
    div    bx                    ; dx:ax / 2  ==>  ax <- 商, dx <- 余数
    cmp    dx, 0  
    jz    LABEL_EVEN
    mov    byte [bOdd], 1        ; 奇数

LABEL_EVEN:
    ; 计算 FAT 项所在扇区号
    xor    dx, dx
    mov    bx, [BPB_BytsPerSec]
    div    bx                     ; dx:ax / BPB_BytsPerSec
                               ; ax <- 商 (FATEntry 所在的扇区相对于 FAT 的扇区号)
                               ; dx <- 余数 (FATEntry 在扇区内的偏移)
    push dx
    mov    bx, 0                 ; bx <- 0 于是, es:bx = (BaseOfKernelFile - 100):00  
    add    ax, SectorNoOfFAT1    ; ax = FAT1 起始扇区号   指定读取扇区号 = FATEntry 所在的扇区号
    mov    cl, 2  
    call ReadSector         ; 读取 FATEntry 所在的扇区, 一次读两个

    ; 赋值结果给 ax 并矫正结果
    pop    dx
    add    bx, dx
    mov    ax, [es:bx]
    cmp    byte [bOdd], 1  
    jnz    LABEL_EVEN_2
    shr    ax, 4  
LABEL_EVEN_2:
    and    ax, 0FFFh

LABEL_GET_FAT_ENRY_OK:

    pop    bx
    pop    es
    ret

; --------------- 变量 ----------------
wRootDirSizeForLoop    dw    RootDirSectors        ; Root Directory 占用的扇区数
wSectorNo            dw    0                    ; 要读取的扇区号
bOdd                db    0                    ; 奇数还是偶数
dwKernelSize        dd    0                    ; KERNEL.BIN 文件大小

; -------------- 字符串 ----------------
KernelFileName        db    "KERNEL  BIN", 0    ; KERNEL.BIN 文件名
MessageLength        equ    9  
LoadMessage:        db    "Loading  "  
Message1            db    "Ready.   "  
Message2            db    "No KERNEL"  

; ------------ 32 位代码段 -------------
[SECTION .s32]
ALIGN    32  
[BITS    32]

LABEL_PM_START:
    mov    ax, SelectorVideo
    mov    gs, ax

    mov    ax, SelectorFlatRW
    mov    ds, ax
    mov    es, ax
    mov    fs, ax
    mov    ss, ax
    mov    esp, TopOfStack

    add    esp, 4  

    call    DispMemInfo
    call    SetupPaging

    push    szMemChkTitle
    call    DispStr
    jmp    $


%include    "lib.asm"  

; ----------------- 获取内存信息 ------------------
DispMemInfo:
    push    esi
    push    edi
    push    ecx

    ; 循环获取 ARDS 4 个成员
    mov    esi, MemChkBuf          ; 寻址缓存区
    mov    ecx, [dwMCRNumber]        ; 获取循环次数 ARDS 个数
.loop:
    mov    edx, 5                    ; 循环遍历 ARDS 的 4 个成员
    mov    edi, ARDStruct
.1:

    ; 将缓冲区中成员赋值给 ARDStruct
    mov eax, dword [esi]
    stosd

    add    esi, 4  
    dec    edx
    cmp    edx, 0  
    jnz    .1  

    ; Type 是 AddressRangeMemory 赋值 dwMemSize
    cmp    dword [dwType], 1  
    jne    .2  
    mov    eax, [dwBaseAddrLow]
    add    eax, [dwLengthLow]
    xchg bx, bx
    cmp    eax, [dwMemSize]
    jb    .2  
    mov    [dwMemSize], eax
.2:
    loop    .loop

    pop    ecx
    pop    edi
    pop    esi
    ret

; 启动分页机制 --------------------------------------------------------------
SetupPaging:
    ; 根据内存大小计算应初始化多少PDE以及多少页表
    xor    edx, edx
    mov    eax, [dwMemSize]
    mov    ebx, 400000h    ; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小
    div    ebx
    mov    ecx, eax    ; 此时 ecx 为页表的个数,也即 PDE 应该的个数
    test    edx, edx
    jz    .no_remainder
    inc    ecx        ; 如果余数不为 0 就需增加一个页表
.no_remainder:
    push    ecx        ; 暂存页表个数

    ; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞.

    ; 首先初始化页目录
    mov    ax, SelectorFlatRW
    mov    es, ax
    mov    edi, PageDirBase    ; 此段首地址为 PageDirBase
    xor    eax, eax
    mov    eax, PageTblBase | 7  
.1:
    stosd
    add    eax, 4096        ; 为了简化, 所有页表在内存中是连续的
    loop    .1  

    ; 再初始化所有页表
    pop    eax            ; 页表个数
    mov    ebx, 1024        ; 每个页表 1024 个 PTE
    mul    ebx
    mov    ecx, eax        ; PTE个数 = 页表个数 * 1024  
    mov    edi, PageTblBase    ; 此段首地址为 PageTblBase
    xor    eax, eax
    mov    eax, 7   
.2:
    stosd
    add    eax, 4096        ; 每一页指向 4K 的空间
    loop    .2  

    mov    eax, PageDirBase
    mov    cr3, eax
    mov    eax, cr0
    or    eax, 80000000h
    mov    cr0, eax
    jmp    short .3  
.3:
    nop

    ret

; ---------------- 32 位数据段 ------------------
[SECTION .data1]
ALIGN    32  

LABEL_DATA:
; 实模式下使用这些符号
; 字符串
_szMemChkTitle:    db "Welcome to loader by techlog.cn", 0Ah, 0  
_szReturn:    db 0Ah, 0  

; 变量
_dwMCRNumber:    dd 0    ; Memory Check Result
_dwMemSize:    dd 0  
_ARDStruct:    ; Address Range Descriptor Structure
  _dwBaseAddrLow:        dd    0  
  _dwBaseAddrHigh:        dd    0  
  _dwLengthLow:            dd    0  
  _dwLengthHigh:        dd    0  
  _dwType:            dd    0  
_MemChkBuf:    times    256    db    0  

; 保护模式下使用这些符号
szMemChkTitle        equ    BaseOfLoaderPhyAddr   _szMemChkTitle
szReturn        equ    BaseOfLoaderPhyAddr   _szReturn
dwMemSize        equ    BaseOfLoaderPhyAddr   _dwMemSize
dwMCRNumber        equ    BaseOfLoaderPhyAddr   _dwMCRNumber
ARDStruct        equ    BaseOfLoaderPhyAddr   _ARDStruct
    dwBaseAddrLow    equ    BaseOfLoaderPhyAddr   _dwBaseAddrLow
    dwBaseAddrHigh    equ    BaseOfLoaderPhyAddr   _dwBaseAddrHigh
    dwLengthLow    equ    BaseOfLoaderPhyAddr   _dwLengthLow
    dwLengthHigh    equ    BaseOfLoaderPhyAddr   _dwLengthHigh
    dwType        equ    BaseOfLoaderPhyAddr   _dwType
MemChkBuf        equ    BaseOfLoaderPhyAddr   _MemChkBuf

; 堆栈空间
StackSpace:    times    1024    db    0  
TopOfStack    equ    BaseOfLoaderPhyAddr   $

0 人点赞