美文网首页
loader.asm源码分析

loader.asm源码分析

作者: 皮了个卡丘喵喵哒 | 来源:发表于2017-10-18 00:51 被阅读0次

    作用:通过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
    

    相关文章

      网友评论

          本文标题:loader.asm源码分析

          本文链接:https://www.haomeiwen.com/subject/hncjuxtx.html