[024][x86汇编语言]第十三章 学习加载程序C13_mbr.asm

学习笔记

《x86汇编语言:从实模式到保护模式》
https://www.jianshu.com/p/d481cb547e9f

第十三章的 代码

  • 用户程序 c13.asm
  • 内核程序 c13_core.asm
  • 加载程序 c13_mbr.asm
[024][x86汇编语言]第十三章 学习加载程序C13_mbr.asm_第1张图片
第十三章的代码文件.png

主引导程序 C13_mbr.asm

  • 主引导程序 C13_mbr.asm 程序结构
    [024][x86汇编语言]第十三章 学习加载程序C13_mbr.asm_第2张图片
    主引导程序 C13_mbr.asm 程序结构.png
  • 加载程序 加载完 内核程序 后的 GDT 布局

    [024][x86汇编语言]第十三章 学习加载程序C13_mbr.asm_第3张图片
    加载程序 加载完内核后的GDT布局.png

  • 内存布局示意图

    [024][x86汇编语言]第十三章 学习加载程序C13_mbr.asm_第4张图片
    内存布局示意图.png

完整源码 code_13_mbr.asm (增加注释)

;--------------------------------------------------------------------------
;代码清单13-1
;文件名:code_13_mbr.asm
;文件说明:硬盘主引导扇区代码
;创建日期:8:43 2018/6/1
;--------------------------------------------------------------------------


;--------------------------------------------------------------------------
;常数
;--------------------------------------------------------------------------
core_base_address equ 0x00040000    ;内核程序将要被加载到的物理地址
core_start_sector equ 0x00000001    ;内核程序位于硬盘的起始逻辑扇区号

;--------------------------------------------------------------------------
;加载程序开始
;--------------------------------------------------------------------------
;--------------------------------------------------------------------------
;                   所有数据都是十六进制
;   表内偏移量           GDT                         描述符索引
;   +38         核心代码段(位于核心数据段之后)        0x38
;   +30         核心数据段(位于系统公用例程段之后)  0x30
;   +28         公用例程段(起始地址为00040000)        0x28
;   +20         文本模式显存(000B8000~00BFFFFF)       0x20
;   +18         初始栈段(00006C00~00007C00)         0x18
;   +10         初始代码段(00007C00~00007DFF)            0x10
;   +08         0~4GB数据段(00000000~FFFFFFFF)     0x08
;   +00         空描述符                                0x00
;--------------------------------------------------------------------------
;加载GDT表 偏移+00~+20 5个项目
;--------------------------------------------------------------------------
    mov ax,cs
    mov ss,ax
    mov sp,0x7c00

    ;计算GDT所在的逻辑段地址
    mov eax,[cs:pdgt+0x7c00+0x02]       ;GDT的32位物理地址
    xor edx,edx
    mov ebx,16
    div ebx                             ;分解成16位逻辑地址
    
    mov ds,eax              ;令DS指向该段以进行操作
    mov ebx,edx             ;段内起始偏移地址
    
    ;跳过0#号描述符的槽位(空描述符)
    ;创建1#描述符,这是一个数据段,对应0~4G的线性地址空间
    mov dword [ebx+0x08],0x0000ffff     ;段基址0x00000000
    mov dword [ebx+0x0c],0x00cf9200     

    ;创建保护模式下初始代码段描述符
    mov dword [ebx+0x10],0x7c0001ff     ;段基址0x00007c00
    mov dword [ebx+0x14],0x00409800
    
    ;建立保护模式下的堆栈段描述符
    mov dword [ebx+0x18],0x7c00fffe     ;段基址0x00007c00
    mov dword [ebx+0x1c],0x00cf9600
    
    ;建立保护模式下的显示缓冲区描述符
    mov dword [ebx+0x20],0x80007fff     ;段基址0x000B8000
    mov dword [ebx+0x24],0x0040920b
    
    ;初始化描述符表寄存器GDTR
    mov word [cs:pdgt+0x7c00],39        ;5*8-1=39
    
    lgdt [cs:pdgt+0x7c00]
    
    in al,0x92                          ;南桥芯片内的端口
    or al,0000_0010B
    out 0x92,al                         ;打开A20
    
    cli                                 ;保护模式下中断机制尚未建立,应该禁止中断
    
    mov eax,cr0
    or eax,1
    mov cr0,eax                         ;设置PE位
    
;--------------------------------------------------------------------------
;以下进入保护模式
;设置 DS  指向 0~4GB内存空间
;       SS  指向堆栈段
;--------------------------------------------------------------------------
    jmp dword 0x0010:flush              ; 16位的描述符选择子:32位偏移
                                        ; dword修饰的是偏移地址
                                        ; 清空流水线并串行化处理器
    [bits 32]
flush:
    mov eax,0x0008                      ;加载(0...4GB)选择子
    mov ds,eax          
    
    mov eax,0x0018                      ;加载堆栈选择子
    mov ss,eax
    xor esp,esp                         ;堆栈指针 = 0
    
;--------------------------------------------------------------------------
;以下加载系统核心程序
;
;--------------------------------------------------------------------------
;读取位于硬盘的内核程序 调用子程序 read_hard_disk_0
;-------------------------------------------------------------------------- 
    mov edi,core_base_address
    
    mov eax,core_start_sector       
    mov ebx,edi                         
    call read_hard_disk_0               ;读取内核程序的起始部分(一个扇区)到内存
    
    ;以下判断整个内核程序有多大
    mov eax,[edi]                       ;内核程序长度
    xor edx,edx
    mov ecx,512
    div ecx
    
    or edx,edx
    jnz @1
    dec eax                     ;已经读了一个扇区,扇区总数减1
@1:
    or eax,eax
    jz setup                    ;jz ZF=1 寄存器值为零成立 则跳转
    
    ;读取剩余扇区
    mov ecx,eax
    mov eax,core_start_sector   ;32位模式下的LOOP使用ECX
    inc eax                     ;从下一个逻辑扇区接着读
@2:
    call read_hard_disk_0
    inc eax
    loop @2                     ;循环读,知道读完整个内核程序
    
;--------------------------------------------------------------------------
;                   所有数据都是十六进制
;   表内偏移量           GDT                         描述符索引
;   +38         核心代码段(位于核心数据段之后)        0x38
;   +30         核心数据段(位于系统公用例程段之后)  0x30
;   +28         公用例程段(起始地址为00040000)        0x28
;   +20         文本模式显存(000B8000~00BFFFFF)       0x20
;   +18         初始栈段(00006C00~00007C00)         0x18
;   +10         初始代码段(00007C00~00007DFF)            0x10
;   +08         0~4GB数据段(00000000~FFFFFFFF)     0x08
;   +00         空描述符                                0x00
;--------------------------------------------------------------------------
;再加载GDT表 偏移+28~+38 3个项目
;--------------------------------------------------------------------------
;********************** 内核程序 c13_core.asm ****************************
;*  ;以下是系统核心的头部,用于加载核心程序                             
;*  core_length      dd core_end                ;核心程序总长度#00;         
;*                                                                       
;*  sys_routine_seg  dd section.sys_routine.start                        
;*                                              ;系统公用例程段位置#04   
;*
;*  core_data_seg    dd section.core_data.start
;*                                              ;核心数据段位置#08
;*
;*  core_code_seg    dd section.core_code.start
;*                                              ;核心代码段位置#0c
;*
;*  core_entry      dd start                    ;核心代码段入口点#10
;*                  dw core_code_seg_sel
;*
;*********************** 内核程序 c13_core.asm ****************************
;--------------------------------------------------------------------------
setup:
    
    mov esi,[0x7c00+pdgt+0x02]  ;使用0~4GB的段来访问,寻址pdgt
    
    ;建立公用例程段描述符     ;mov edi,core_base_address
    mov eax,[edi+0x04]          ;公用例程段 在内核程序中的起始汇编地址
    mov ebx,[edi+0x08]          ;核心数据段 在内核程序中的起始汇编地址
    sub ebx,eax
    dec ebx                     ;公用例程段的 段界限
    add eax,edi                 ;公用例程段的 段基地址
    mov ecx,0x00409800          ;字节粒度的代码段描述符
    call make_gdt_descriptor
    mov [esi+0x28],eax
    mov [esi+0x2c],edx
    

    
    ;建立核心数据段描述符
    mov eax,[edi+0x08]          ;核心数据段 起始汇编地址
    mov ebx,[edi+0x0c]          ;核心代码段 起始汇编地址
    sub ebx,eax
    dec ebx                     ;核心数据段 段界限
    add eax,edi                 ;核心数据段 基地址
    mov ecx,0x00409200          ;字节粒度的数据段描述符
    call make_gdt_descriptor
    mov [esi+0x30],eax
    mov [esi+0x34],edx

    ;建立核心代码段描述符
    mov eax,[edi+0x0c]          ;核心代码段 起始汇编地址
    mov ebx,[edi+0x00]          ;程序总长度
    sub ebx,eax
    dec ebx                     ;核心代码段 段界限
    add eax,edi                 ;核心代码段  段基址
    mov ecx,0x00409800          ;字节粒度的代码段描述符
    call make_gdt_descriptor
    mov [esi+0x38],eax
    mov [esi+0x3c],edx
    
    mov word [0x7c00+pdgt],63   ;8*8-1=63
    lgdt [0x7c00+pdgt]
    

    
;--------------------------------------------------------------------------
;转移控制权给内核程序
;-------------------------------------------------------------------------- 
    jmp far [edi+0x10]

;--------------------------------------------------------------------------

;--------------------------------------------------------------------------
;子程序:   read_hard_disk_0
;--------------------------------------------------------------------------
;功能:        从硬盘读取一个逻辑扇区
;参数:        EAX = 逻辑扇区号
;           DS:EBX=目标缓冲区地址
;返回:        EBX = EBX + 512 
;-------------------------------------------------------------------------- 
read_hard_disk_0:
    
            push eax
            push ecx
            push edx
        
            ;设置28位起始LBA扇区号
            push eax
        
            mov dx,0x1f2    ;0x1f2
            mov al,1        ;读取的扇区数为1
            out dx,al

            inc dx          ;0x1f3
            pop eax         
            out dx,al       ;LBA 7~0
            
            inc dx          ;0x1f4
            mov cl,8
            shr eax,cl
            out dx,al       ;LBA 15~8
            
            inc dx          ;0x1f5
            shr eax,cl
            out dx,al       ;LBA 23~16
            
            inc dx          ;0x1f6
            shr eax,cl
            or al,0xe0      ;主硬盘 LBA 27~24
            out dx,al
            
            inc dx          ;0x1f7
            mov al,0x20     ;读命令
            out dx,al
    .waits:
            in al,dx
            and al,0x88
            cmp al,0x08
            jnz .waits      ;硬盘已经准备好数据传输
            
            mov ecx,256     ;总共要读取的字数
            mov dx,0x1f0    ;0x1f0
    .readw:
            in ax,dx
            mov [ebx],ax
            add ebx,2
            loop .readw
            
            pop edx
            pop ecx
            pop eax
            
ret


;--------------------------------------------------------------------------
;子程序:   make_gdt_descriptor
;--------------------------------------------------------------------------
;功能:    构造描述符
;输入:    EAX=线性基地址
;       EBX=段界限
;       ECX=属性(各属性都在原始位置,其他没用到的位置置为零)
;返回:    EDX:EAX=完整的描述符
;--------------------------------------------------------------------------
make_gdt_descriptor:
        
        mov edx,eax
        shl eax,16
        or ax,bx                    ;描述符低32位构造完毕
            
        and edx,0xffff0000          ;清除基地址中无关的位
        rol edx,8
        bswap   edx                 ;装配基地址的31~24和23~16(80486+)
        
        and ebx,0x000f0000
        or edx,ebx                  ;装配段界限的高4位
        
        or edx,ecx                  ;装配属性
                                    ;描述符高32位构造完毕
ret
;--------------------------------------------------------------------------

;--------------------------------------------------------------------------
;表
;--------------------------------------------------------------------------
    pdgt    dw 0
            dd 0x00007e00       ;GDT的物理地址
;--------------------------------------------------------------------------
;主引导扇区标志
;--------------------------------------------------------------------------
    times 510-($-$$)    db 0
                        db 0x55,0xaa
;--------------------------------------------------------------------------
;加载程序结束
;--------------------------------------------------------------------------

代码说明

  • 加载程序mov eax,[edi]与内核程序core_length dd core_end

    [024][x86汇编语言]第十三章 学习加载程序C13_mbr.asm_第5张图片
    在加载程序中获取内核程序长度

  • 加载程序 jmp far [edi+0x10] 与内核程序 core_entry dd start dw core_code_seg_sel

    [024][x86汇编语言]第十三章 学习加载程序C13_mbr.asm_第6张图片
    转移控制权 从加载程序到内核程序.png

  • 描述符计算 make_gdt_descriptor 举例

    [024][x86汇编语言]第十三章 学习加载程序C13_mbr.asm_第7张图片
    描述符计算 1

    [024][x86汇编语言]第十三章 学习加载程序C13_mbr.asm_第8张图片
    描述符计算 2

你可能感兴趣的:([024][x86汇编语言]第十三章 学习加载程序C13_mbr.asm)