硬盘主引导扇区代码 阅读和分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/DanielDingshengli/article/details/81293285

《x86汇编语言-从实模式到保护模式》笔记
1.` ;设置堆栈段和栈指针 ,使堆栈段的逻辑地址和代码段相同, 0x7c00是个分界线,从这里,代码向上扩展,而堆栈向下扩展。

     mov ax,cs      
     mov ss,ax
     mov sp,0x7c00`

2.
描述符不是由用户程序自己建立的,而是在加载时,由操作系统根据你的程序结构而建立的,而用户程序通常是无法建立和修改 GDT 的,也就只能老老实实地在自己的地盘上工作。在这种情况下,操作系统为你的程序建立了几个段,你就只能在这些段内工作,超出这个范围,或者未按预定的方法访问这些段,都将被处理器止。
下面开始定义主引导扇区代码所使用的数据段、代码段和堆栈段。在保护模式下,内存的访问机制完全不同,即,必须通过描述符来进行。所以,这些段必须重新在 GDT 中定义。
这里写图片描述

 gdt_base         dd 0x00007e00     ;GDT的物理地址 

3.段描述符号
这里写图片描述
20 位的段界限用来限制段的扩展范围。因为访问内存的方法是用段基地址加上偏移量,所以,对于向上扩展的段,如代码段和数据段来说,偏移量是从 0 开始递增,段界限决定了偏移量的最大值;对于向下扩展的段,如堆栈段来说,段界限决定了偏移量的最小值。
G 位是粒度(Granularity)位,用于解释段界限的含义。当 G 位是“0”时,段界限以字节为单位。此时,段的扩展范围是从 1 字节到 1 兆字节(1B~1MB),因为描述符中的界限值是 20 位的。相反,如果该位是“1”,那么,段界限是以 4KB 为单位的。这样,段的扩展范围是从 4KB到 4GB。
S 位用于指定描述符的类型(Descriptor Type)。当该位是“0”时,表示是一个系统段;为“1”时,表示是一个代码段或者数据段(堆栈段也是特殊的数据段)。
DPL 表示描述符的特权级(Descriptor Privilege Level,DPL)。这两位用于指定段的特权级。共
有 4 种处理器支持的特权级别,分别是 0、1、2、3,其中 0 是最高特权级别,3 是最低特权级别。
刚进入保护模式时执行的代码具有最高特权级 0(可以看成是从处理器那里继承来的),这些代码通常都是操作系统代码,因此它的特权级别最高。每当操作系统加载一个用户程序时,它通常都会指定一个稍低的特权级,比如 3 特权级。不同特权级别的程序是互相隔离的,其互访是严格限制的,而且有些处理器指令(特权指令)只能由 0 特权级的程序来执行,为的就是安全。
P 是段存在位(Segment Present)。P 位用于指示描述符所对应的段是否存在。一般来说,描述符所指示的段都位于内存中。但是,当内存空间紧张时,有可能只是建立了描述符,对应的内存空间并不存在,这时,就应当把描述符的 P 位清零,表示段并不存在。另外,同样是在内存空间紧张
的情况下,会把很少用到的段换出到硬盘中,腾出空间给当前急需内存的程序使用(当前正在执行的),这时,同样要把段描述符的 P 位清零。当再次轮到它执行时,再装入内存,然后将 P 位置 1。
D/B 位是“默认的操作数大小”(Default Operation Size)或者“默认的堆栈指针大小”(Default Stack Pointer Size),又或者“上部边界”(Upper Bound)标志。设立该标志位,主要是为了能够在 32 位处理器上兼容运行 16 位保护模式的程序。该标志位对不同的段有不同的效果。对于代码段,此位称做“D”位,用于指示指令中默认的偏移地址和操作数尺寸。D=0 表示指令中的偏移地址或者操作数是 16 位的;D=1,指示 32 位的偏移地址或者操作数。如果代码段描述符的 D 位是 0,那么,当处理器在这个段上执行时,将使用 16位的指令指针寄存器 IP 来取指令,否则使用 32 位的 EIP。对于堆栈段来说,该位被叫做“B”位,用于在进行隐式的堆栈操作时,是使用 SP 寄存器还是ESP 寄存器。
这里写图片描述

 ;文件说明:硬盘主引导扇区代码 

         ;设置堆栈段和栈指针 
         mov ax,cs      
         mov ss,ax
         mov sp,0x7c00

         ;计算GDT所在的逻辑段地址,将线性基地址转换成逻辑地址,方法是将 DX:AX 除以 16,得到的商是逻辑段地
址,余数是偏移地址。接着,将 AX 中的逻辑段地址传送到数据段寄存器 DS 中,将偏移地址传送到寄存器 BX 中 
         mov ax,[cs:gdt_base+0x7c00]        ;将 GDT 线性基地址的低 16 位传送到寄存器 AX 中 
         mov dx,[cs:gdt_base+0x7c00+0x02]   ;高16位 
         mov bx,16        
         div bx            
         mov ds,ax                          ;令DS指向该段以进行操作
         mov bx,dx                          ;段内起始偏移地址 

         ;创建0#描述符,它是空描述符,这是处理器的要求
         mov dword [bx+0x00],0x00
         mov dword [bx+0x04],0x00  

         ;创建#1描述符,保护模式下的代码段描述符
         mov dword [bx+0x08],0x7c0001ff     
         mov dword [bx+0x0c],0x00409800  线性基地址为 0x00007C00。
       段界限为 0x001FF,粒度为字节(G=0)。该段的长度为 512 字节。属于存储器的段(S=1)。
这是一个 32 位的段(D=1)。该段目前位于内存中(P=1)。段的特权级为 0(DPL=00)。这是一个只能执行的代码段(TYPE=1000)。   

         ;创建#2描述符,保护模式下的数据段描述符(文本模式下的显示缓冲区) 
         mov dword [bx+0x10],0x8000ffff     
         mov dword [bx+0x14],0x0040920b     
线性基地址为 0x000B8000。段界限为 0x0FFFF,粒度为字节(G=0)。即,该段的长度为 64KB。
属于存储器的段(S=1)。这是一个 32 位的段(D=1)。该段目前位于内存中(P=1)。
段的特权级为 0(DPL=00)。这是一个可读可写、向上扩展的数据段(TYPE=0010;创建#3描述符,保护模式下的堆栈段描述符
         mov dword [bx+0x18],0x00007a00
         mov dword [bx+0x1c],0x00409600

         ;初始化描述符表寄存器GDTR
         mov word [cs: gdt_size+0x7c00],31  ;描述符表的界限(总字节数减一)   

         lgdt [cs: gdt_size+0x7c00]
在这 6 字节的内存区域中,前 16 位是 GDT 的界限值,高 32 位是 GDT 的基地址。在初始状态
下(计算机启动之后),GDTR 的基地址被初始化为 0x00000000;界限值为 0xFFFF

         in al,0x92                         ;南桥芯片内的端口 
         or al,0000_0010B
         out 0x92,al                        ;打开A20

         cli                                ;保护模式下中断机制尚未建立,应 
                                            ;禁止中断 
         mov eax,cr0
         or eax,1
         mov cr0,eax                        ;设置PE位

         ;以下进入保护模式... ...
         jmp dword 0x0008:flush             ;16位的描述符选择子:32位偏移
                                            ;清流水线并串行化处理器 
         [bits 32] 

    flush:
         mov cx,00000000000_10_000B         ;加载数据段选择子(0x10)
         mov ds,cx

         ;以下在屏幕上显示"Protect mode OK." 
         mov byte [0x00],'P'  
         mov byte [0x02],'r'
         mov byte [0x04],'o'
         mov byte [0x06],'t'
         mov byte [0x08],'e'
         mov byte [0x0a],'c'
         mov byte [0x0c],'t'
         mov byte [0x0e],' '
         mov byte [0x10],'m'
         mov byte [0x12],'o'
         mov byte [0x14],'d'
         mov byte [0x16],'e'
         mov byte [0x18],' '
         mov byte [0x1a],'O'
         mov byte [0x1c],'K'

         ;以下用简单的示例来帮助阐述32位保护模式下的堆栈操作 
         mov cx,00000000000_11_000B         ;加载堆栈段选择子
         mov ss,cx
         mov esp,0x7c00

         mov ebp,esp                        ;保存堆栈指针 
         push byte '.'                      ;压入立即数(字节)

         sub ebp,4
         cmp ebp,esp                        ;判断压入立即数时,ESP是否减4 
         jnz ghalt                          
         pop eax
         mov [0x1e],al                      ;显示句点 

  ghalt:     
         hlt                                ;已经禁止中断,将不会被唤醒 

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

         gdt_size         dw 0
         gdt_base         dd 0x00007e00     ;GDT的物理地址 

         times 510-($-$$) db 0
                          db 0x55,0xaa

这里写图片描述

read_hard_disk_0:                        ;从硬盘读取一个逻辑扇区
                                         ;EAX=逻辑扇区号
                                         ;DS:EBX=目标缓冲区地址
                                         ;返回:EBX=EBX+512 
         push eax 
         push ecx
         push edx

         push eax

         mov dx,0x1f2
         mov al,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

安装内核的段描述符
要使内核工作起来,首要的任务是为它的各个段创建描述符。换句话说,还要为 GDT 续添新的描述符。

lgdt [cs: pgdt+0x7c00]

加载全局描述符表寄存器(GDTR),标号 pgdt 所指向的内存位置包含了 GDT 的基地址和大小。现在,我们的任务是重新从标号 pgdt 处取得 GDT 的基地址,为其添加描述符,并修改它的大小,然后用 lgdt 指令重新加载一遍 GDTR 寄存器,使修改生效。

猜你喜欢

转载自blog.csdn.net/DanielDingshengli/article/details/81293285