学习笔记
《x86汇编语言:从实模式到保护模式》
https://www.jianshu.com/p/d481cb547e9f
第十三章的 代码
- 用户程序
c13.asm
- 内核程序
c13_core.asm
- 加载程序
c13_mbr.asm
主引导程序 C13_mbr.asm
- 主引导程序
C13_mbr.asm
程序结构
-
加载程序 加载完 内核程序 后的 GDT 布局
-
内存布局示意图
完整源码 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
-
加载程序
jmp far [edi+0x10]
与内核程序core_entry dd start dw core_code_seg_sel
-
描述符计算
make_gdt_descriptor
举例