loader.asm源码分析

作用:通过int 15h中断获取内存信息,调用的结果是BIOS会填充es:di指向的一块内存,此结构成为ARDS(地址范围描述符结构):

    mov ebx, 0          ; ebx = 后续值, 开始时需为 0
    mov di, _MemChkBuf      ; es:di 指向一个地址范围描述符结构(Address Range Descriptor Structure)
.MemChkLoop:
    mov eax, 0E820h     ; eax = 0000E820h 查询系统地址映射
    mov ecx, 20         ; ecx = 地址范围描述符结构的大小,20字节
    mov edx, 0534D4150h     ; edx = 'SMAP'
    int 15h         ; int 15h,通过这个中断获取内存信息
    jc  .MemChkFail
    add di, 20             ;因为每个ARDS占20字节,所以自增20指向下一个空白位置
    inc dword [_dwMCRNumber]    ; dwMCRNumber = ARDS 的个数
    cmp ebx, 0  ;int 15h将上次调用的计数值填充到ebx中,如果为0表示探测结束,否则继续探测。
    jne .MemChkLoop
    jmp .MemChkOK
.MemChkFail:
    mov dword [_dwMCRNumber], 0
.MemChkOK:

ReaderSector:

作用:从序号(Directory Entry 中的 Sector 号)为 ax 的的 Sector 开始, 将 cl 个 Sector 读入 es:bx 中。

顾名思义,ReaderSector函数的作用就是读扇区。这个函数最最重要的一条语句就是int 13h,这是一个中断服务程序,这里仅仅介绍当ah=2时服务程序的功能。

功能描述:读扇区
入口参数:AH=02H
AL=扇区数
CH=柱面
CL=扇区
DH=磁头
DL=驱动器,00H ~ 7FH:软盘;80H ~ 0FFH:硬盘
ES:BX=缓冲区的地址
出口参数:CF=0——操作成功,AH=00H,AL=传输的扇区数,否则,AH=状态代码,参见功能号01H中的说明

    push    bp
    mov bp, sp
    sub esp, 2          ; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2]

    mov byte [bp-2], cl
    push    bx          ; 保存 bx
    mov bl, [BPB_SecPerTrk] ; bl: 除数
    div bl          ; y 在 al 中, z 在 ah 中
    inc ah          ; z ++
    mov cl, ah          ; cl <- 起始扇区号
    mov dh, al          ; dh <- y
    shr al, 1           ; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2)
    mov ch, al          ; ch <- 柱面号
    and dh, 1           ; dh & 1 = 磁头号
    pop bx          ; 恢复 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

在根目录区寻找KERNEL.BIN

LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
    cmp word [wRootDirSizeForLoop], 0   ; ┓
    jz  LABEL_NO_KERNELBIN      ; ┣ 判断根目录区是不是已经读完, 如果读完表示没有找到 KERNEL.BIN
    dec word [wRootDirSizeForLoop]  ; ┛
    mov ax, BaseOfKernelFile
    mov es, ax          ; es <- BaseOfKernelFile
    mov bx, OffsetOfKernelFile  ; bx <- OffsetOfKernelFile  于是, es:bx = BaseOfKernelFile:OffsetOfKernelFile = BaseOfKernelFile * 10h + OffsetOfKernelFile
    mov ax, [wSectorNo]     ; ax <- Root Directory 中的某 Sector 号
    mov cl, 1
    call    ReadSector ;把一个扇区读到了BaseOfKernelFile:OffsetOfKernelFile

    mov si, KernelFileName  ; ds:si -> "KERNEL  BIN"
    mov di, OffsetOfKernelFile  ; es:di -> BaseOfKernelFile:???? = BaseOfKernelFile*10h+????
    cld
    mov dx, 10h  ;dx作为循环控制,控制一个扇区读的最大次数,因为一个扇区512字节,一个条目占32字节,所以最多读16次!
LABEL_SEARCH_FOR_KERNELBIN:
    cmp dx, 0                   ; ┓
    jz  LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR  ; ┣ 循环次数控制, 如果已经读完了一个 Sector, 就跳到下一个 Sector
    dec dx                  ; ┛
    mov cx, 11 ;"KERNEL  BIN"共11个字节
LABEL_CMP_FILENAME:
    cmp cx, 0           ; ┓
    jz  LABEL_FILENAME_FOUND    ; ┣ 循环次数控制, 如果比较了 11 个字符都相等, 表示找到
    dec cx          ; ┛
    lodsb               ; ds:si -> al
    cmp al, byte [es:di]    ; if al == es:di
    jz  LABEL_GO_ON
    jmp LABEL_DIFFERENT
LABEL_GO_ON:
    inc di
    jmp LABEL_CMP_FILENAME  ;   继续循环

LABEL_DIFFERENT:
    and di, 0FFE0h      ; else┓ 这时di的值不知道是什么, di &= e0 为了让它是 20h 的倍数
    add di, 20h         ;     ┃
    mov si, KernelFileName  ;     ┣ di += 20h  下一个目录条目(一个目录条目32字节)
    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.BIN, 死循环在这里

LABEL_FILENAME_FOUND:
FAT12:
  • 引导扇区里放了一个短跳转指令(jmp LABEL_START)和一些与FAT设置有关的参数(如每扇区的字节数、每簇扇区数...)
  • 根目录区:由若干目录条目(Directory Entry)组成,条目最多有BPB_RootEntCnt(在引导扇区定义)个。一个条目占32字节,主要定义了文件的属性、名称、大小、日期以及在磁盘中的位置。
条目结构
  • FAT表:存放FAT项(FAT Entry),每个FAT项12位,值代表文件的下一个簇号,但如果值大于或等于**0xFF8,则表示当前簇是本文件的最后一个簇。FAT1和FAT2完全一样,多一个备份。

这里说明一下代码中几个常数设置的原因:

  • mov dx, 10h :一个扇区512字节,一个条目占32字节,所以一个扇区里最多存放16个条目。dx中的值用作循环控制,控制读一个扇区时读条目的最大值。
  • mov cx, 11:条目中存放文件名,"KERNEL BIN",占11字节。

加载KERNEL:

调用ReadSector函数把Kernel加载到BaseOfKernelFile:OffsetOfKernelFile

LABEL_FILENAME_FOUND:           ; 找到 KERNEL.BIN 后便来到这里继续
    mov ax, RootDirSectors    ;RootDirSectors:根目录占用空间
    and di, 0FFF0h      ; di -> 当前条目的开始

    push    eax
    mov eax, [es : di + 01Ch]       ; ┓条目[01Ch]处保存着文件大小信息
    mov dword [dwKernelSize], eax   ; ┛保存 KERNEL.BIN 文件大小
    pop eax

    add di, 01Ah        ; 条目[01Ah]处保存此条目对应的开始簇号,因为这里设置一簇一扇区,故开始扇区号等于开始簇号。
    mov cx, word [es:di]
    push    cx          ; 保存此 Sector 在 FAT 中的序号
    add cx, ax
    add cx, DeltaSectorNo   ;文件的开始Sector号 = DirEntry中的开始Sector号 + 根目录占用Sector数目 + DeltaSectorNo,这时 cl 里面是 LOADER.BIN 的起始扇区号 (从 0 开始数的序号)
    mov ax, BaseOfKernelFile
    mov es, ax          ; es <- BaseOfKernelFile
    mov bx, OffsetOfKernelFile  ; bx <- OffsetOfKernelFile  于是, es:bx = BaseOfKernelFile:OffsetOfKernelFile = BaseOfKernelFile * 10h + OffsetOfKernelFile
    mov ax, cx          ; ax <- Sector 号

LABEL_GOON_LOADING_FILE:
    push    ax          ; ┓
    push    bx          ; ┃
    mov ah, 0Eh         ; ┃ 每读一个扇区就在 "Loading  " 后面打一个点, 形成这样的效果:
    mov al, '.'         ; ┃
    mov bl, 0Fh         ; ┃ Loading ......
    int 10h         ; ┃
    pop bx          ; ┃
    pop ax          ; ┛

    mov cl, 1
    call    ReadSector    
    pop ax          ; 取出此 Sector 在 FAT 中的序号
    call    GetFATEntry    ;找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中
    cmp ax, 0FFFh      ;到文件的最后一个簇
    jz  LABEL_FILE_LOADED
    push    ax          ; 保存 Sector 在 FAT 中的序号
    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           ; "Ready."
    call    DispStrRealMode     ; 显示字符串

跳入保护模式:

; 加载 GDTR
    lgdt    [GdtPtr]

; 关中断,保护模式下中断处理的机制是不同的,不关中断会出现错误
    cli

; 打开地址线A20,8086有20根地址线,开机时置A20=0可以保证即使现代计算机有很多根地址线,在实模式下寻址范围还是和8086的结果一样。
    in  al, 92h
    or  al, 00000010b
    out 92h, al

; 准备切换到保护模式,寄存器cr0第一位是PE位,置0,CPU运行在实模式下。置1,CPU运行在保护模式下。
    mov eax, cr0
    or  eax, 1
    mov cr0, eax

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

以下代码运行在保护模式下


由实模式跳到此处:

初始化段寄存器,调用DispMemInfo、SetupPaging、InitKernel

LABEL_PM_START:
    mov ax, SelectorVideo
    mov gs, ax              ;gs指向显存 
    mov ax, SelectorFlatRW
    mov ds, ax              
    mov es, ax
    mov fs, ax
    mov ss, ax              ;ds,es,fs,ss指向SelectorFlatRW
    mov esp, TopOfStack     ;esp指向栈顶

    push    szMemChkTitle    ;szMemChkTitle:"BaseAddrL BaseAddrH LengthLow LengthHigh   Type"
    call    DispStr
    add esp, 4

    call    DispMemInfo
    call    SetupPaging

    mov ah, 0Fh             ; 0000: 黑底    1111: 白字
    mov al, 'P'
    mov [gs:((80 * 0 + 39) * 2)], ax    ; 屏幕第 0 行, 第 39 列。

    call    InitKernel

显示内存信息:

DispMemInfo:
    push    esi
    push    edi
    push    ecx

    mov esi, MemChkBuf
    mov ecx, [dwMCRNumber]  ;for(int i=0;i<[MCRNumber];i++) // 每次得到一个ARDS(Address Range Descriptor Structure)结构
.loop:                  ;{
    mov edx, 5          ;   for(int j=0;j<5;j++)    // 每次得到一个ARDS中的成员,共5个成员
    mov edi, ARDStruct      ;   {           // 依次显示:BaseAddrLow,BaseAddrHigh,LengthLow,LengthHigh,Type
.1:                 ;
    push    dword [esi]     ;
    call    DispInt         ;       DispInt(MemChkBuf[j*4]); // 显示一个成员
    pop eax         ;
    stosd               ;       ARDStruct[j*4] = MemChkBuf[j*4];
    add esi, 4          ;
    dec edx         ;
    cmp edx, 0          ;
    jnz .1          ;   }
    call    DispReturn      ;   printf("\n");
    cmp dword [dwType], 1   ;   if(Type == AddressRangeMemory) // AddressRangeMemory : 1, AddressRangeReserved : 2
    jne .2          ;   {
    mov eax, [dwBaseAddrLow]    ;
    add eax, [dwLengthLow]  ;
    cmp eax, [dwMemSize]    ;       if(BaseAddrLow + LengthLow > MemSize)
    jb  .2          ;
    mov [dwMemSize], eax    ;           MemSize = BaseAddrLow + LengthLow;
.2:                 ;   }
    loop    .loop           ;}
                    ;
    call    DispReturn      ;printf("\n");
    push    szRAMSize       ;
    call    DispStr         ;printf("RAM size:");
    add esp, 4          ;
                    ;
    push    dword [dwMemSize]   ;
    call    DispInt         ;DispInt(MemSize);
    add esp, 4          ;

    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:equ    200000h ,页目录开始地址
    xor eax, eax
    mov eax, PageTblBase | PG_P  | PG_USU | PG_RWW
.1:
    stosd                   ;将 EAX 存储到地址 ES:EDI
    add eax, 4096       ; 为了简化, 所有页表在内存中是连续的.一个页表大小4KB,4096字节
    loop    .1

    ; 再初始化所有页表
    pop eax         ; 页表个数
    mov ebx, 1024       ; 每个页表 1024 个 PTE
    mul ebx              ;PTE个数 = 页表个数 * 1024
    mov ecx, eax        
    mov edi, PageTblBase    ; 此段首地址为 PageTblBase:equ    201000h ,页表开始地址:
    xor eax, eax
    mov eax, PG_P  | PG_USU | PG_RWW
.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
; 分页机制启动完毕 ----------------------------------------------------------

分页机制:

逻辑地址-->分段机制-->线性地址-->分页机制-->物理地址

  • 页:就是一块内存,大小可以是4K、1M等等
  • 开关位于寄存器cr0的PG位,PG=1分页机制开启

寻址方式:


  • 页目录表:大小为4KB,储存在一个物理页中,每个表项4字节长,共1024个表项,每个表项对应第二级的一个页表。
  • 页表:1024项,每项对应一个物理页。

InitKernel:

InitKernel:
        xor   esi, esi
        mov   cx, word [BaseOfKernelFilePhyAddr+2Ch];`. ecx <- pELFHdr->e_phnum
        movzx ecx, cx                               ;/
        mov   esi, [BaseOfKernelFilePhyAddr + 1Ch]  ; esi <- pELFHdr->e_phoff
        add   esi, BaseOfKernelFilePhyAddr;esi<-OffsetOfKernel+pELFHdr->e_phoff
.Begin:
        mov   eax, [esi + 0]
        cmp   eax, 0                      ; PT_NULL
        jz    .NoAction
        push  dword [esi + 010h]    ;size ;`.
        mov   eax, [esi + 04h]            ; |
        add   eax, BaseOfKernelFilePhyAddr; | memcpy((void*)(pPHdr->p_vaddr),
        push  eax           ;src  ; |      uchCode + pPHdr->p_offset,
        push  dword [esi + 08h]     ;dst  ; |      pPHdr->p_filesz;
        call  MemCpy                      ; |
        add   esp, 12                     ;/
.NoAction:
        add   esi, 020h                   ; esi += pELFHdr->e_phentsize
        dec   ecx
        jnz   .Begin

        ret

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容